ctucx.git: dns.nix

fork of https://github.com/kirelagin/dns.nix

commit 9f2749629dd24d73a0bc2f2fad2e0cd924215ded
parent 7740441dafc72bd4c2d7cd4cdeb73b9fdb5a2ee8
Author: Kirill Elagin <kirelagin@gmail.com>
Date: Wed, 13 Mar 2019 12:11:34 +0100

Add support for plain-string simple records

As a prerequisite I had to clean up and simplify record name handling.
10 files changed, 85 insertions(+), 83 deletions(-)
M
README.md
|
8
++++----
M
dns/types/record.nix
|
86
++++++++++++++++++++++++++++++++++++++-----------------------------------------
M
dns/types/records/A.nix
|
1
+
M
dns/types/records/AAAA.nix
|
1
+
M
dns/types/records/CNAME.nix
|
1
+
M
dns/types/records/NS.nix
|
1
+
M
dns/types/records/SRV.nix
|
4
++--
M
dns/types/records/TXT.nix
|
1
+
M
dns/types/zone.nix
|
52
+++++++++++++++++++++++++++-------------------------
M
example.nix
|
13
++++++-------
diff --git a/README.md b/README.md
@@ -21,7 +21,7 @@ with dns.combinators; {
     # Sane defaults for the remaining ones
   };
 
-  NS = map ns [  # Why not `map` over your records?
+  NS = [  # A zone consists of lists of records grouped by type
     "ns.test.com."
     "ns2.test.com."
   ];

@@ -29,12 +29,12 @@ with dns.combinators; {
   A = [
     { address = "203.0.113.1"; }  # Generic A record
     { address = "203.0.113.2"; ttl = 60 * 60; }  # Generic A with TTL
-    (a "203.0.113.3")  # Simple a record create with the `a` combinator
+    (a "203.0.113.3")  # Simple a record created with the `a` combinator
     (ttl (60 * 60) (a "203.0.113.4"))  # Equivalent to the second one
   ];
 
   AAAA = [
-    (aaaa "4321:0:1:2:3:4:567:89ab")
+    "4321:0:1:2:3:4:567:89ab"  # For simple records you can use a plain string
   ];
 
   CAA = letsEncrypt "admin@example.com";  # Common template combinators included

@@ -46,7 +46,7 @@ with dns.combinators; {
   ];
 
   subdomains = {
-    www.A = [ (a "203.0.114.1") ];
+    www.A = [ "203.0.114.1" ];
 
     staging = delegateTo [  # Another shortcut combinator
       "ns1.another.com."
diff --git a/dns/types/record.nix b/dns/types/record.nix
@@ -8,52 +8,48 @@
 
 let
   inherit (pkgs) lib;
-  inherit (lib) const mkOption types;
+  inherit (lib) const isString mkOption types;
+
+  defaults = {
+    class = "IN";
+    ttl = 24 * 60 * 60;
+  };
+
+  recordType = rsubt:
+    let
+      submodule = types.submodule {
+        options = {
+          class = mkOption {
+            type = types.enum ["IN"];
+            default = defaults.class;
+            example = "IN";
+            description = "Resource record class. Only IN is supported";
+          };
+          ttl = mkOption {
+            type = types.ints.unsigned;  # TODO: u32
+            default = defaults.ttl;
+            example = 300;
+            description = "Record caching duration (in seconds)";
+          };
+        } // rsubt.options;
+      };
+    in
+      (if rsubt ? fromString then types.either types.str else lib.id) submodule;
+
+  writeRecord = name: rsubt: data:
+    let
+      data' =
+        if isString data && rsubt ? fromString
+        then defaults // rsubt.fromString data
+        else data;
+      name' = rsubt.nameFixup or (n: _: n) name data';
+      rtype = rsubt.rtype;
+    in with data';
+      "${name'}. ${toString ttl} ${class} ${rtype} ${rsubt.dataToString data'}";
 
-  recordTypes = import ./records { inherit pkgs; };
 in
 
-recordType: name: types.submodule {
-  options = {
-    name = mkOption {
-      type = types.str;
-      default = name;
-      example = "example.com";
-      description = "Name of the node to which this resource record pertains";
-    };
-    rtype = mkOption {
-      type = types.enum (lib.mapAttrsToList (n: v: v.rtype) recordTypes);
-      readOnly = true;
-      visible = false;
-      description = "Type of the record. Do not set this option yourself!";
-    };
-    _rtype = mkOption {
-      readOnly = true;
-      visible = false;
-    };
-    class = mkOption {
-      type = types.enum ["IN"];
-      default = "IN";
-      example = "IN";
-      description = "Resource record class. Only IN is supported";
-    };
-    ttl = mkOption {
-      type = types.ints.unsigned;  # TODO: u32
-      default = 24 * 60 * 60;
-      example = 300;
-      description = "Record caching duration (in seconds)";
-    };
-    __toString = mkOption {
-      readOnly = true;
-      visible = false;
-    };
-  } // recordType.options;
-  config = {
-    rtype  = recordType.rtype;
-    _rtype = recordType;
-    __toString = data@{name, rtype, class, ttl, _rtype, ...}:
-      let
-        name' = _rtype.nameFixup or (const name) data;
-      in "${name'}. ${toString ttl} ${class} ${rtype} ${_rtype.dataToString data}";
-  };
+{
+  inherit recordType;
+  inherit writeRecord;
 }
diff --git a/dns/types/records/A.nix b/dns/types/records/A.nix
@@ -21,4 +21,5 @@ in
     };
   };
   dataToString = {address, ...}: address;
+  fromString = address: { inherit address; };
 }
diff --git a/dns/types/records/AAAA.nix b/dns/types/records/AAAA.nix
@@ -21,4 +21,5 @@ in
     };
   };
   dataToString = {address, ...}: address;
+  fromString = address: { inherit address; };
 }
diff --git a/dns/types/records/CNAME.nix b/dns/types/records/CNAME.nix
@@ -21,4 +21,5 @@ in
     };
   };
   dataToString = {cname, ...}: "${cname}";
+  fromString = cname: { inherit cname; };
 }
diff --git a/dns/types/records/NS.nix b/dns/types/records/NS.nix
@@ -21,4 +21,5 @@ in
     };
   };
   dataToString = {nsdname, ...}: "${nsdname}";
+  fromString = nsdname: { inherit nsdname; };
 }
diff --git a/dns/types/records/SRV.nix b/dns/types/records/SRV.nix
@@ -49,6 +49,6 @@ in
   };
   dataToString = data: with data;
     "${toString priority} ${toString weight} ${toString port} ${target}";
-  nameFixup = self:
-    "_${self.service}._${self.proto}.${self.name}";
+  nameFixup = name: self:
+    "_${self.service}._${self.proto}.${name}";
 }
diff --git a/dns/types/records/TXT.nix b/dns/types/records/TXT.nix
@@ -21,4 +21,5 @@ in
     };
   };
   dataToString = {data, ...}: ''"${data}"'';
+  fromString = data: { inherit data; };
 }
diff --git a/dns/types/zone.nix b/dns/types/zone.nix
@@ -7,19 +7,19 @@
 { pkgs }:
 
 let
-  inherit (builtins) filter hasAttr map removeAttrs;
-  inherit (pkgs.lib) concatMapStringsSep concatStringsSep id mapAttrs
-                     optionalString;
+  inherit (builtins) attrValues filter map removeAttrs;
+  inherit (pkgs.lib) concatMapStringsSep concatStringsSep mapAttrs
+                     mapAttrsToList optionalString;
   inherit (pkgs.lib) mkOption types;
 
-  record = import ./record.nix { inherit pkgs; };
+  inherit (import ./record.nix { inherit pkgs; }) recordType writeRecord;
 
-  recordTypes = import ./records { inherit pkgs; };
-  recordTypes' = removeAttrs recordTypes ["SOA"];
+  rsubtypes = import ./records { inherit pkgs; };
+  rsubtypes' = removeAttrs rsubtypes ["SOA"];
 
-  subzoneOptions = name: {
+  subzoneOptions = {
     subdomains = mkOption {
-      type = types.attrsOf (subzone name);
+      type = types.attrsOf subzone;
       default = {};
       example = {
         www = {

@@ -33,35 +33,33 @@ let
     };
   } //
     mapAttrs (n: t: mkOption rec {
-      type = types.listOf (record t name);
+      type = types.listOf (recordType t);
       default = [];
       example = [ t.example ];
       description = "List of ${t} records for this zone/subzone";
-    }) recordTypes';
+    }) rsubtypes';
 
-  subzone = pname:
-    types.submodule ({name, ...}: {
-      options = subzoneOptions "${name}.${pname}";
-    });
+  subzone = types.submodule {
+      options = subzoneOptions;
+    };
 
-  writeSubzone = zone:
+  writeSubzone = name: zone:
     let
-      groupToString = n:
-        concatMapStringsSep "\n" toString (zone."${n}");
-      groups = map groupToString (builtins.attrNames recordTypes');
+      groupToString = subt:
+        concatMapStringsSep "\n" (writeRecord name subt) (zone."${subt.rtype}");
+      groups = map groupToString (attrValues rsubtypes');
       groups' = filter (s: s != "") groups;
 
-      sub = concatMapStringsSep "\n\n" writeSubzone (builtins.attrValues zone.subdomains);
+      writeSubzone' = subname: writeSubzone "${subname}.${name}";
+      sub = concatStringsSep "\n\n" (mapAttrsToList writeSubzone' zone.subdomains);
     in
       concatStringsSep "\n\n" groups'
       + optionalString (sub != "") ("\n\n" + sub);
-in
 
-rec {
   zone = types.submodule ({name, ...}: {
     options = {
       SOA = mkOption rec {
-        type = record recordTypes.SOA name;
+        type = recordType rsubtypes.SOA;
         example = {
           ttl = 24 * 60 * 60;
         } // type.example;

@@ -71,16 +69,20 @@ rec {
         readOnly = true;
         visible = false;
       };
-    } // subzoneOptions name;
+    } // subzoneOptions;
 
     config = {
       __toString = zone@{SOA, ...}:
           ''
-            ${toString SOA}
+            ${writeRecord name rsubtypes.SOA SOA}
 
-          '' + writeSubzone zone + "\n";
+          '' + writeSubzone name zone + "\n";
     };
   });
 
+in
+
+{
+  inherit zone;
   inherit subzone;
 }
diff --git a/example.nix b/example.nix
@@ -14,19 +14,19 @@ let
       serial = 2019030800;
     };
 
-    NS = map ns [
+    NS = [
       "ns.test.com."
       "ns2.test.com."
     ];
 
     A = [
       { address = "203.0.113.1"; ttl = 60 * 60; }
-      (a "203.0.113.2")
+      "203.0.113.2"
       (ttl (60 * 60) (a "203.0.113.3"))
     ];
 
     AAAA = [
-      (aaaa "4321:0:1:2:3:4:567:89ab")
+      "4321:0:1:2:3:4:567:89ab"
     ];
 
     MX = mx.google;

@@ -46,14 +46,13 @@ let
     ];
 
     subdomains = {
-      www = {
-        A = map a [ "203.0.113.4" ];
-      };
+      www.A = [ "203.0.113.4" ];
+
       staging = delegateTo [
         "ns1.another.com."
         "ns2.another.com."
       ];
-      foo.subdomains.www.CNAME = map cname [ "foo.test.com" ];
+      foo.subdomains.www.CNAME = [ "foo.test.com." ];
     };
   };
 in