Пример #1
0
 def test_function_call(self):
     self.assert_nix(Call(RawValue("fun_call"), {"a": "b"}),
                     '( fun_call { a = "b"; } )')
     self.assert_nix(
         Call(RawValue("multiline_call"), {"a": "b"}),
         '(\n  multiline_call\n  {\n    a = "b";\n  }\n)',
         maxwidth=0,
     )
Пример #2
0
 def test_invalid(self):
     self.assertRaises(ValueError, nixmerge, [123], {"a": 456})
     self.assertRaises(ValueError, nixmerge, "a", "b")
     self.assertRaises(ValueError, nixmerge, 123, 456)
     self.assertRaises(ValueError, nixmerge, RawValue("a"), RawValue("b"))
     self.assertRaises(ValueError, nixmerge, Function("aaa", {"a": 1}),
                       Function("ccc", {"b": 2}))
     self.assertRaises(ValueError, nixmerge, Function("aaa", {"a": 1}),
                       {"b": 2})
Пример #3
0
 def _eval_flags(self, exprs):
     flags = self._nix_path_flags()
     args = {key: RawValue(val) for key, val in self.args.iteritems()}
     exprs_ = [RawValue(x) if x[0] == '<' else x for x in exprs]
     flags.extend(
         ["--arg", "networkExprs", py2nix(exprs_, inline=True),
          "--arg", "args", py2nix(args, inline=True),
          "--argstr", "uuid", self.uuid,
          "<nixops/eval-machine-info.nix>"])
     return flags
Пример #4
0
    def _configure_initial_nix(self,
                               uefi: bool,
                               instance_id: Optional[int] = None):
        self.log_start("generating the initial configuration... ")
        # 1. We generate the HW configuration and the standard configuration.
        out = self.run_command("nixos-generate-config --root /mnt",
                               capture_stdout=True)
        # 2. We will override the configuration.nix
        nixos_cfg = {
            "imports": [RawValue("./hardware-configuration.nix")],
            ("boot", "kernelParams"): ["console=ttyS0"],
            ("services", "openssh", "enable"):
            True,
            ("services", "qemuGuest", "enable"):
            True,
            ("systemd", "services", "qemu-guest-agent", "serviceConfig", "RuntimeDirectory"):
            "qemu-ga",
            ("systemd", "services", "qemu-guest-agent", "serviceConfig", "ExecStart"):
            RawValue(
                "lib.mkForce \"\\${pkgs.qemu.ga}/bin/qemu-ga -t /var/run/qemu-ga\""
            ),
            ("services", "getty", "autologinUser"):
            "root",
            ("networking", "firewall", "allowedTCPPorts"): [22],
            ("users", "users", "root"): {
                ("openssh", "authorizedKeys", "keys"): [self.public_host_key],
                ("initialPassword"): ""
            },
            ("users", "mutableUsers"):
            False
        }

        if uefi:
            nixos_cfg[("boot", "loader")] = {
                ("efi", "canTouchEfiVariables"): True,
                ("systemd-boot", "enable"): True
            }
        else:
            # Use nix2py to read self.fs_info.
            nixos_cfg[("boot", "loader", "grub", "devices")] = ["/dev/sda"]

        nixos_initial_postinstall_conf = py2nix(
            Function("{ config, pkgs, lib, ... }", nixos_cfg))
        self.run_command(
            f"cat <<EOF > /mnt/etc/nixos/configuration.nix\n{nixos_initial_postinstall_conf}\nEOF"
        )
        self.run_command("echo preinstall > /mnt/.install_status")
        self.log_end("initial configuration generated")
        self.log_start("installing NixOS... ")
        out = self.run_command("nixos-install --no-root-passwd",
                               capture_stdout=True)
        self.log_end("NixOS installed")
        self.run_command("echo installed > /mnt/.install_status")
Пример #5
0
 def test_list_compound(self):
     self.assert_nix(
         [Function("123 //", 456, call=True),
          RawValue("a b c")], '[ (123 // 456) (a b c) ]')
     self.assert_nix([
         RawValue("a b c"), {
             'cde':
             [RawValue("1,2,3"),
              RawValue("4 5 6"),
              RawValue("7\n8\n9")]
         }
     ], '[ (a b c) { cde = [ 1,2,3 (4 5 6) (7\n8\n9) ]; } ]')
Пример #6
0
 def get_physical_backup_spec(self, backupid):
     val = {}
     if backupid in self.backups:
         for dev, snap in self.backups[backupid].items():
             val[dev] = {
                 'snapshot': Call(RawValue("pkgs.lib.mkOverride 10"), snap)
             }
         val = {('deployment', 'gce', 'blockDeviceMapping'): val}
     else:
         val = RawValue(
             "{{}} /* No backup found for id '{0}' */".format(backupid))
     return Function("{ config, pkgs, ... }", val)
Пример #7
0
    def get_physical_spec(self):
        block_device_mapping = {}
        for k, v in self.block_device_mapping.items():
            if (v.get('encrypt', False)
                and v.get('passphrase', "") == ""
                and v.get('generatedKey', "") != ""):
                block_device_mapping[k] = {
                    'passphrase': Call(RawValue("pkgs.lib.mkOverride 10"), v['generatedKey']),
                }

        return {
            'imports': [
                RawValue("<nixpkgs/nixos/modules/virtualisation/google-compute-config.nix>")
            ],
            ('deployment', 'gce', 'blockDeviceMapping'): block_device_mapping,
        }
Пример #8
0
 def _eval_flags(self, exprs):
     flags = self._nix_path_flags()
     args = {key: RawValue(val) for key, val in self.args.iteritems()}
     flags.extend([
         "--arg", "networkExprs",
         py2nix(exprs, inline=True), "--arg", "args",
         py2nix(args, inline=True), "--argstr", "uuid", self.uuid,
         "--show-trace", "<nixops/eval-machine-info.nix>"
     ])
     return flags
Пример #9
0
 def test_functions(self):
     self.assert_nix(Function("Aaa", RawValue("bbb")), "Aaa: bbb")
     self.assert_nix(Function("{ ... }", [1, 2, 3]), "{ ... }: [ 1 2 3 ]")
     self.assert_nix(Function("{ ... }", "a\nb\nc\n"), r'{ ... }: "a\nb\nc\n"')
     self.assert_nix(
         Function("{ ... }", "a\nb\nc\n"),
         "{ ... }: ''\n  a\n  b\n  c\n''",
         maxwidth=0,
     )
     self.assert_nix(
         Function("xxx", {"a": {"b": "c"}}), 'xxx: {\n  a.b = "c";\n}', maxwidth=0
     )
Пример #10
0
 def get_physical_spec(self):
     return Function(
         "{ ... }", {
             'imports':
             [RawValue('<nixpkgs/nixos/modules/profiles/qemu-guest.nix>')],
             ('boot', 'loader', 'grub', 'device'):
             'nodev',
             ('fileSystems', '/'): {
                 'device': '/dev/sda1',
                 'fsType': 'ext4'
             },
             ('users', 'extraUsers', 'root', 'openssh', 'authorizedKeys', 'keys'):
             [self.depl.active_resources.get('ssh-key').public_key],
         })
Пример #11
0
    def get_physical_spec(self) -> Function:
        def prefix_len(netmask):
            return bin(int(codecs.encode(socket.inet_aton(netmask), "hex"),
                           16)).count("1")

        networking = {
            "defaultGateway":
            self.default_gateway,
            "nameservers": ["67.207.67.2",
                            "67.207.67.3"],  # default provided by DO
            ("interfaces", "ens3", "ipv4", "addresses"): [{
                "address":
                self.public_ipv4,
                "prefixLength":
                prefix_len(self.netmask)
            }],
        }
        if self.public_ipv6:
            networking[("interfaces", "ens3", "ipv6", "addresses")] = [{
                "address":
                self.public_ipv6["address"],
                "prefixLength":
                self.public_ipv6["prefixLength"],
            }]
        if self.default_gateway6:
            networking["defaultGateway6"] = self.default_gateway6

        return Function(
            "{ ... }",
            {
                "imports":
                [RawValue("<nixpkgs/nixos/modules/profiles/qemu-guest.nix>")],
                "networking":
                networking,
                (
                    "boot",
                    "loader",
                    "grub",
                    "device",
                ):
                "nodev",  # keep ubuntu bootloader?
                ("fileSystems", "/"): {
                    "device": "/dev/vda1",
                    "fsType": "ext4"
                },
                ("users", "extraUsers", "root", "openssh", "authorizedKeys", "keys"):
                [self.get_ssh_key_resource().public_key],
            },
        )
Пример #12
0
    def destroy_resources(self, include=[], exclude=[], wipe=False):
        """Destroy all active or obsolete resources."""

        with self._get_deployment_lock():
            for r in self.resources.itervalues():
                r._destroyed_event = threading.Event()
                r._errored = False
                for rev_dep in r.destroy_before(self.resources.itervalues()):
                    try:
                        rev_dep._wait_for.append(r)
                    except AttributeError:
                        rev_dep._wait_for = [ r ]

            def worker(m):
                try:
                    if not should_do(m, include, exclude): return
                    try:
                        for dep in m._wait_for:
                            dep._destroyed_event.wait()
                            # !!! Should we print a message here?
                            if dep._errored:
                                m._errored = True
                                return
                    except AttributeError:
                        pass
                    if m.destroy(wipe=wipe): self.delete_resource(m)
                except:
                    m._errored = True
                    raise
                finally:
                    m._destroyed_event.set()

            nixops.parallel.run_tasks(nr_workers=-1, tasks=self.resources.values(), worker_fun=worker)

        # Remove the destroyed machines from the rollback profile.
        # This way, a subsequent "nix-env --delete-generations old" or
        # "nix-collect-garbage -d" will get rid of the machine
        # configurations.
        if self.rollback_enabled: # and len(self.active) == 0:
            profile = self.create_profile()
            attrs = {m.name:
                     Call(RawValue("builtins.storePath", m.cur_toplevel))
                     for m in self.active.itervalues() if m.cur_toplevel}
            if subprocess.call(
                ["nix-env", "-p", profile, "--set", "*", "-I", "nixops=" + self.expr_path,
                 "-f", "<nixops/update-profile.nix>",
                 "--arg", "machines", py2nix(attrs, inline=True)]) != 0:
                raise Exception("cannot update profile ‘{0}’".format(profile))
Пример #13
0
    def get_physical_spec(self):
        prefixLength = bin(int(socket.inet_aton(self.netmask).encode('hex'), 16)).count('1')

        return Function("{ ... }", {
            'imports': [ RawValue('<nixpkgs/nixos/modules/profiles/qemu-guest.nix>') ],
            'networking': {
                'defaultGateway': self.default_gateway,
                'nameservers': ['8.8.8.8'], # default provided by DO
                ('interfaces', 'enp0s3'): {
                    'ip4': [{"address": self.public_ipv4, 'prefixLength': prefixLength}],
                },
            },
            ('boot', 'loader', 'grub', 'device'): 'nodev', # keep ubuntu bootloader?
            ('fileSystems', '/'): { 'device': '/dev/vda1', 'fsType': 'ext4'},
            ('users', 'extraUsers', 'root', 'openssh', 'authorizedKeys', 'keys'): [self.depl.active_resources.get('ssh-key').public_key],
        })
Пример #14
0
 def get_physical_spec(self):
     return Function(
         "{ ... }", {
             'imports':
             [RawValue('<nixpkgs/nixos/modules/profiles/qemu-guest.nix>')],
             ('config', 'boot', 'initrd', 'availableKernelModules'):
             ["ata_piix", "uhci_hcd", "virtio_pci", "sr_mod", "virtio_blk"],
             ('config', 'boot', 'loader', 'grub', 'device'):
             '/dev/vda',
             ('config', 'fileSystems', '/'): {
                 'device': '/dev/vda1',
                 'fsType': 'btrfs'
             },
             ('config', 'users', 'extraUsers', 'root', 'openssh', 'authorizedKeys', 'keys'):
             [self._ssh_public_key]
         })
Пример #15
0
 def test_list_compound(self):
     self.assert_nix([Call(RawValue("123 //"), 456),
                      RawValue("a b c")], '[ (( 123 // 456 )) (a b c) ]')
     self.assert_nix([
         RawValue("a b c"), {
             'cde':
             [RawValue("1,2,3"),
              RawValue("4 5 6"),
              RawValue("7\n8\n9")]
         }
     ], '[ (a b c) { cde = [ 1,2,3 (4 5 6) (7\n8\n9) ]; } ]')
Пример #16
0
    def destroy_resources(self, include=[], exclude=[], wipe=False):
        """Destroy all active and obsolete resources."""

        with self._get_deployment_lock():
            self._destroy_resources(include, exclude, wipe)

        # Remove the destroyed machines from the rollback profile.
        # This way, a subsequent "nix-env --delete-generations old" or
        # "nix-collect-garbage -d" will get rid of the machine
        # configurations.
        if self.rollback_enabled: # and len(self.active) == 0:
            profile = self.create_profile()
            attrs = {m.name:
                     Call(RawValue("builtins.storePath"), m.cur_toplevel)
                     for m in self.active.itervalues() if m.cur_toplevel}
            if subprocess.call(
                ["nix-env", "-p", profile, "--set", "*", "-I", "nixops=" + self.expr_path,
                 "-f", "<nixops/update-profile.nix>",
                 "--arg", "machines", py2nix(attrs, inline=True)]) != 0:
                raise Exception("cannot update profile ‘{0}’".format(profile))
Пример #17
0
 def get_physical_spec(self):
     return {'imports': [RawValue('<nixops/virtualbox-image-nixops.nix>')]}
Пример #18
0
        def do_machine(m):
            defn = self.definitions[m.name]
            attrs_list = attrs_per_resource[m.name]

            # Emit configuration to realise encrypted peer-to-peer links.
            for m2 in active_resources.itervalues():
                ip = m.address_to(m2)
                if ip:
                    hosts[m.name][ip] += [m2.name, m2.name + "-unencrypted"]

            # Always use the encrypted/unencrypted suffixes for aliases rather
            # than for the canonical name!
            hosts[m.name]["127.0.0.1"].append(m.name + "-encrypted")

            for m2_name in defn.encrypted_links_to:

                if m2_name not in active_machines:
                    raise Exception("‘deployment.encryptedLinksTo’ in machine ‘{0}’ refers to an unknown machine ‘{1}’"
                                    .format(m.name, m2_name))
                m2 = active_machines[m2_name]

                # Don't create two tunnels between a pair of machines.
                if m.name in self.definitions[m2.name].encrypted_links_to and m.name >= m2.name:
                    continue
                local_ipv4 = index_to_private_ip(m.index)
                remote_ipv4 = index_to_private_ip(m2.index)
                local_tunnel = 10000 + m2.index
                remote_tunnel = 10000 + m.index
                attrs_list.append({
                    ('networking', 'p2pTunnels', 'ssh', m2.name): {
                        'target': '{0}-unencrypted'.format(m2.name),
                        'targetPort': m2.ssh_port,
                        'localTunnel': local_tunnel,
                        'remoteTunnel': remote_tunnel,
                        'localIPv4': local_ipv4,
                        'remoteIPv4': remote_ipv4,
                        'privateKey': '/root/.ssh/id_charon_vpn',
                    }
                })

                # FIXME: set up the authorized_key file such that ‘m’
                # can do nothing more than create a tunnel.
                authorized_keys[m2.name].append(m.public_vpn_key)
                kernel_modules[m.name].add('tun')
                kernel_modules[m2.name].add('tun')
                hosts[m.name][remote_ipv4] += [m2.name, m2.name + "-encrypted"]
                hosts[m2.name][local_ipv4] += [m.name, m.name + "-encrypted"]
                trusted_interfaces[m.name].add('tun' + str(local_tunnel))
                trusted_interfaces[m2.name].add('tun' + str(remote_tunnel))

            private_ipv4 = m.private_ipv4
            if private_ipv4:
                attrs_list.append({
                    ('networking', 'privateIPv4'): private_ipv4
                })
            public_ipv4 = m.public_ipv4
            if public_ipv4:
                attrs_list.append({
                    ('networking', 'publicIPv4'): public_ipv4
                })
            public_vpn_key = m.public_vpn_key
            if public_vpn_key:
                attrs_list.append({
                    ('networking', 'vpnPublicKey'): public_vpn_key
                })

            # Set system.stateVersion if the Nixpkgs version supports it.
            if nixops.util.parse_nixos_version(defn.config["nixosVersion"]) >= ["15", "09"]:
                attrs_list.append({
                    ('system', 'stateVersion'): Call(RawValue("lib.mkDefault"), m.state_version or '14.12')
                })

            if self.nixos_version_suffix:
                attrs_list.append({
                    ('system', 'nixosVersionSuffix'): self.nixos_version_suffix
                })
Пример #19
0
        def emit_resource(r):
            config = []
            config.extend(attrs_per_resource[r.name])
            if is_machine(r):
                # Sort the hosts by its canonical host names.
                sorted_hosts = sorted(hosts[r.name].iteritems(),
                                      key=lambda item: item[1][0])
                # Just to remember the format:
                #   ip_address canonical_hostname [aliases...]
                extra_hosts = [
                    "{0} {1}".format(ip, ' '.join(names))
                    for ip, names in sorted_hosts
                ]

                if authorized_keys[r.name]:
                    config.append({
                        ('users', 'extraUsers', 'root'): {
                            ('openssh', 'authorizedKeys', 'keys'):
                            authorized_keys[r.name]
                        },
                        ('services', 'openssh'): {
                            'extraConfig': "PermitTunnel yes\n"
                        },
                    })

                config.append({
                    ('boot', 'kernelModules'):
                    list(kernel_modules[r.name]),
                    ('networking', 'firewall'): {
                        'trustedInterfaces': list(trusted_interfaces[r.name])
                    },
                    ('networking', 'extraHosts'):
                    '\n'.join(extra_hosts) + "\n"
                })

                # Add SSH public host keys for all machines in network
                for m2 in active_machines.itervalues():
                    if hasattr(m2, 'public_host_key'):
                        # Using references to files in same tempdir for now, until NixOS has support
                        # for adding the keys directly as string. This way at least it is compatible
                        # with older versions of NixOS as well.
                        # TODO: after reasonable amount of time replace with string option
                        config.append({
                            ('services', 'openssh', 'knownHosts', m2.name): {
                                'hostNames': [
                                    m2.name + "-unencrypted",
                                    m2.name + "-encrypted", m2.name
                                ],
                                'publicKeyFile':
                                RawValue("./{0}.public_host_key".format(
                                    m2.name)),
                            }
                        })

            merged = reduce(nixmerge, config) if len(config) > 0 else {}
            physical = r.get_physical_spec()

            if len(merged) == 0 and len(physical) == 0:
                return {}
            else:
                return r.prefix_definition({
                    r.name:
                    Function("{ config, pkgs, ... }", {
                        'config': merged,
                        'imports': [physical],
                    })
                })
Пример #20
0
 def get_physical_spec(self):
     return {"imports": [RawValue("<virtualbox-image-nixops.nix>")]}
Пример #21
0
 def test_raw_value(self):
     self.assert_nix({'a': RawValue('import <something>')},
                     '{ a = import <something>; }')
     self.assert_nix([RawValue("!")], '[ ! ]')
Пример #22
0
def eval(
    # eval-machine-info args
    networkExpr: NetworkFile,  # Flake conditional
    uuid: str,
    deploymentName: str,
    networkExprs: List[str] = [],
    args: Dict[str, str] = {},
    pluginNixExprs: List[str] = [],
    checkConfigurationOptions: bool = True,
    # Extend internal defaults
    nix_path: List[str] = [],
    # nix-instantiate args
    nix_args: Dict[str, Any] = {},
    attr: Optional[str] = None,
    extra_flags: List[str] = [],
    # Non-propagated args
    stderr: Optional[TextIO] = None,
) -> Any:

    exprs: List[str] = list(networkExprs)
    if not networkExpr.is_flake:
        exprs.append(networkExpr.network)

    argv: List[str] = (
        [
            "nix-instantiate", "--eval-only", "--json", "--strict",
            "--show-trace"
        ] + [os.path.join(get_expr_path(), "eval-machine-info.nix")] +
        ["-I", "nixops=" + get_expr_path()] + [
            "--arg",
            "networkExprs",
            py2nix([RawValue(x) if x[0] == "<" else x for x in exprs]),
        ] + [
            "--arg",
            "args",
            py2nix({key: RawValue(val)
                    for key, val in args.items()},
                   inline=True),
        ] + ["--argstr", "uuid", uuid] +
        ["--argstr", "deploymentName", deploymentName] +
        ["--arg", "pluginNixExprs",
         py2nix(pluginNixExprs)] + [
             "--arg", "checkConfigurationOptions",
             json.dumps(checkConfigurationOptions)
         ] +
        list(itertools.chain(*[["-I", x]
                               for x in (nix_path + pluginNixExprs)])) +
        extra_flags)

    for k, v in nix_args.items():
        argv.extend(["--arg", k, py2nix(v, inline=True)])

    if attr:
        argv.extend(["-A", attr])

    if networkExpr.is_flake:
        argv.extend(["--allowed-uris", get_expr_path()])
        argv.extend(["--argstr", "flakeUri", networkExpr.network])

    try:
        ret = subprocess.check_output(argv, stderr=stderr, text=True)
        return json.loads(ret)
    except OSError as e:
        raise Exception("unable to run ‘nix-instantiate’: {0}".format(e))
    except subprocess.CalledProcessError:
        raise NixEvalError
Пример #23
0
 def test_raw_value(self):
     self.assert_nix({"a": RawValue("import <something>")},
                     "{ a = import <something>; }")
     self.assert_nix([RawValue("!")], "[ ! ]")
Пример #24
0
    def get_physical_spec(self) -> Dict[Any, Any]:

        ipv4 = [{"address": self.public_ipv4, "prefixLength": 32}]
        ipv6 = [{"address": self.public_ipv6[:-3], "prefixLength": 64}]
        for addr in self.ip_addresses.values():
            try:
                socket.inet_pton(socket.AF_INET, addr)
                ipv4.append({"address": addr, "prefixLength": 32})
            except socket.error:  # not a valid address ipv4
                ipv6.append({"address": addr, "prefixLength": 64})

        def get_interface_name(i: int) -> str:
            return f"ens{10+i}" if self.legacy_if_scheme else f"enp{7+i}s0"

        spec = {
            "imports":
            [RawValue("<nixpkgs/nixos/modules/profiles/qemu-guest.nix>")],
            ("boot", "loader", "grub", "device"):
            "nodev",
            ("fileSystems", "/"): {
                "device": "/dev/sda1",
                "fsType": "ext4"
            },
            **{("fileSystems", v["mountPoint"]): {
                   "fsType": v["fsType"],
                   "device": v["device"],
               }
               for k, v in self.volumes.items() if v["mountPoint"]},
            # Hetzner Cloud networking defaults
            ("networking", "defaultGateway"):
            "172.31.1.1",
            ("networking", "nameservers"): [
                "213.133.98.98",
                "213.133.99.99",
                "213.133.100.100",
            ],
            (
                "networking",
                "interfaces",
                "ens3" if self.legacy_if_scheme else "enp1s0",
            ): {
                ("ipv4", "addresses"): ipv4,
                ("ipv6", "addresses"): ipv6,
                "useDHCP": True,
            },
            ("users", "extraUsers", "root", "openssh", "authorizedKeys", "keys"):
            [self.public_client_key],
        }

        for i, v in enumerate(self.server_networks.values()):
            private_ipv4_addresses = [{
                "address": addr,
                "prefixLength": 32
            } for addr in [v["privateIpAddress"]] + v["aliasIpAddresses"]]
            spec[("networking", "interfaces", get_interface_name(i))] = {
                ("ipv4", "addresses"): private_ipv4_addresses,
                "useDHCP": True,
            }

        for v in self.volumes.values():
            if v["fsType"] == "xfs":
                spec[("boot", "kernelModules")] = ["xfs"]
                break

        return spec