diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..49c10b5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +# Prevents Nix results from `nix build`, etc, from being checked in +# accidentally. +*result* + +# Github Workflows are not what we use to perform CI, we use Hercules-CI +# instead. +.github/workflows + +# Dockerfiles, or docker-compose files are not how we build or deploy software. +# Only Nix expressions are allowed. +*Dockerfile* +*docker-compose* diff --git a/examples/flake.lock b/examples/flake.lock deleted file mode 100644 index f89872e..0000000 --- a/examples/flake.lock +++ /dev/null @@ -1,124 +0,0 @@ -{ - "nodes": { - "examples": { - "inputs": { - "nixinate": "nixinate_2", - "nixpkgs": "nixpkgs_2" - }, - "locked": { - "narHash": "sha256-1iruH96Aame+4NAAwSwVZAbHzfnKxhMLjgoVvJar6ls=", - "path": "./examples", - "type": "path" - }, - "original": { - "path": "./examples", - "type": "path" - } - }, - "nixinate": { - "inputs": { - "examples": "examples", - "nixpkgs": "nixpkgs_3" - }, - "locked": { - "lastModified": 1646587087, - "narHash": "sha256-SwOHL/tte1H8VvftnxtWCr5FIlZaGvNy57P9sMSrZ5Q=", - "owner": "matthewcroughan", - "repo": "nixinate", - "rev": "886c6a2b3bef14cacf6c3021df0a75bb57f9fbc7", - "type": "github" - }, - "original": { - "owner": "matthewcroughan", - "repo": "nixinate", - "type": "github" - } - }, - "nixinate_2": { - "inputs": { - "nixpkgs": "nixpkgs" - }, - "locked": { - "narHash": "sha256-lk8eIWYxtHqDT4ZmSFuXMlG067RdPqLCQocnN+hNE7U=", - "path": "/etc/nixos/nixinate", - "type": "path" - }, - "original": { - "path": "/etc/nixos/nixinate", - "type": "path" - } - }, - "nixpkgs": { - "locked": { - "lastModified": 1640887906, - "narHash": "sha256-Eupk1UlNicCD2UNZuEKt6yhE6kFWAxXM/HyziOjG9CA=", - "owner": "nixos", - "repo": "nixpkgs", - "rev": "8a053bc2255659c5ca52706b9e12e76a8f50dbdd", - "type": "github" - }, - "original": { - "owner": "nixos", - "ref": "nixos-21.11", - "repo": "nixpkgs", - "type": "github" - } - }, - "nixpkgs_2": { - "locked": { - "lastModified": 1641147223, - "narHash": "sha256-eJnmISYGR7LeqEev4bsI/qcU0SgeFKHs3jnL4vMGL+k=", - "owner": "nixos", - "repo": "nixpkgs", - "rev": "08370e1e271f6fe00d302bebbe510fe0e2c611ca", - "type": "github" - }, - "original": { - "owner": "nixos", - "ref": "nixos-21.11", - "repo": "nixpkgs", - "type": "github" - } - }, - "nixpkgs_3": { - "locked": { - "lastModified": 1647893727, - "narHash": "sha256-pOi7VdCb+s5Cwh5CS7YEZVRgH9uCmE87J5W7iXv29Ck=", - "owner": "nixos", - "repo": "nixpkgs", - "rev": "1ec61dd4167f04be8d05c45780818826132eea0d", - "type": "github" - }, - "original": { - "owner": "nixos", - "ref": "nixos-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, - "nixpkgs_4": { - "locked": { - "lastModified": 1641147223, - "narHash": "sha256-eJnmISYGR7LeqEev4bsI/qcU0SgeFKHs3jnL4vMGL+k=", - "owner": "nixos", - "repo": "nixpkgs", - "rev": "08370e1e271f6fe00d302bebbe510fe0e2c611ca", - "type": "github" - }, - "original": { - "owner": "nixos", - "ref": "nixos-21.11", - "repo": "nixpkgs", - "type": "github" - } - }, - "root": { - "inputs": { - "nixinate": "nixinate", - "nixpkgs": "nixpkgs_4" - } - } - }, - "root": "root", - "version": 7 -} diff --git a/flake.nix b/flake.nix index e54f503..c589bbc 100644 --- a/flake.nix +++ b/flake.nix @@ -3,12 +3,15 @@ inputs = { nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; }; - outputs = { self, nixpkgs, ... }: + outputs = { self, nixpkgs, ... }@inputs: let version = builtins.substring 0 8 self.lastModifiedDate; supportedSystems = [ "x86_64-linux" "x86_64-darwin" "aarch64-linux" "aarch64-darwin" ]; - forAllSystems = nixpkgs.lib.genAttrs supportedSystems; - nixpkgsFor = forAllSystems (system: import nixpkgs { inherit system; overlays = [ self.overlay ]; }); + forSystems = systems: f: + nixpkgs.lib.genAttrs systems + (system: f system nixpkgs.legacyPackages.${system}); + forAllSystems = forSystems supportedSystems; + nixpkgsFor = forAllSystems (system: pkgs: import nixpkgs { inherit system; overlays = [ self.overlay ]; }); in rec { overlay = final: prev: { @@ -69,6 +72,19 @@ ); }; }; - nixinate = forAllSystems (system: nixpkgsFor.${system}.generateApps); + nixinate = forAllSystems (system: pkgs: nixpkgsFor.${system}.generateApps); + checks = forAllSystems (system: pkgs: + let + vmTests = import ./tests { + makeTest = (import (nixpkgs + "/nixos/lib/testing-python.nix") { inherit system; }).makeTest; + inherit pkgs inputs; + }; + in + pkgs.lib.optionalAttrs pkgs.stdenv.isLinux vmTests # vmTests can only be ran on Linux, so append them only if on Linux. + // + { + # Other checks here... + } + ); }; } diff --git a/tests/default.nix b/tests/default.nix new file mode 100644 index 0000000..0826e0e --- /dev/null +++ b/tests/default.nix @@ -0,0 +1,5 @@ +{ pkgs, makeTest, inputs }: +{ + vmTestLocal = (import ./vmTest { inherit pkgs makeTest inputs; }).local; + vmTestRemote = (import ./vmTest { inherit pkgs makeTest inputs; }).remote; +} diff --git a/tests/vmTest/default.nix b/tests/vmTest/default.nix new file mode 100644 index 0000000..cbbcedb --- /dev/null +++ b/tests/vmTest/default.nix @@ -0,0 +1,109 @@ +{ pkgs, makeTest, inputs }: +let + # Return a store path with a closure containing everything including + # derivations and all build dependency outputs, all the way down. + allDrvOutputs = pkg: + let name = "allDrvOutputs-${pkg.pname or pkg.name or "unknown"}"; + in + pkgs.runCommand name { refs = pkgs.writeReferencesToFile pkg.drvPath; } '' + touch $out + while read ref; do + case $ref in + *.drv) + cat $ref >>$out + ;; + esac + done <$refs + ''; + # Imports a flake with inputs passed in by hand, rather than + # builtins.getFlake, which cannot be used in this way. + callLocklessFlake = path: inputs: let + r = {outPath = path;} // + ((import (path + "/flake.nix")).outputs (inputs // {self = r;})); + in + r; + exampleFlake = pkgs.writeTextFile { + name = "nixinate-example-flake"; + destination = "/flake.nix"; + text = '' + { + outputs = { self, nixpkgs }: + let + makeTest = (import (nixpkgs + "/nixos/lib/testing-python.nix") { system = "${pkgs.hostPlatform.system}"; }).makeTest; + baseConfig = ((makeTest { nodes.baseConfig = { ... }: {}; testScript = "";}).nodes {}).baseConfig.extendModules { + modules = [ + ${builtins.readFile ./nixinateeBase.nix} + ${builtins.readFile ./nixinateeAdditional.nix} + { + _module.args.nixinate = { + host = "nixinatee"; + sshUser = "nixinator"; + buildOn = "local"; # valid args are "local" or "remote" + }; + } + ]; + }; + in + { + nixosConfigurations = { + nixinatee = baseConfig; + }; + }; + } + ''; + }; + deployScript = inputs.self.nixinate.${pkgs.hostPlatform.system} (callLocklessFlake "${exampleFlake}" { nixpkgs = inputs.nixpkgs; }); + exampleSystem = (callLocklessFlake "${exampleFlake}" { nixpkgs = inputs.nixpkgs; }).nixosConfigurations.nixinatee.config.system.build.toplevel; + mkNixinateTest = buildOn: makeTest { + nodes = { + nixinatee = { ... }: { + imports = [ + ./nixinateeBase.nix + ]; + virtualisation = { + writableStore = true; + }; + }; + nixinator = { ... }: { + virtualisation = { + additionalPaths = [ + (allDrvOutputs exampleSystem) + ] + ++ pkgs.lib.optional (buildOn == "remote") exampleFlake; + }; + nix = { + extraOptions = + let empty_registry = builtins.toFile "empty-flake-registry.json" ''{"flakes":[],"version":2}''; in + '' + experimental-features = nix-command flakes + flake-registry = ${empty_registry} + ''; + registry.nixpkgs.flake = inputs.nixpkgs; + }; + }; + }; + testScript = + '' + start_all() + nixinatee.wait_for_unit("sshd.service") + nixinator.wait_for_unit("multi-user.target") + nixinator.succeed("mkdir ~/.ssh/") + nixinator.succeed("ssh-keyscan -H nixinatee >> ~/.ssh/known_hosts") + nixinator.succeed("exec ${deployScript.nixinate.nixinatee.program} >&2") + nixinatee.wait_for_unit("nginx.service") + nixinatee.wait_for_open_port("80") + with subtest("Check that Nginx webserver can be reached by deployer after deployment"): + assert "Welcome to nginx!" in nixinator.succeed( + "curl -sSf http:/nixinatee/ | grep title" + ) + with subtest("Check that Nginx webserver can be reached by deployee after deployment"): + assert "Welcome to nginx!" in nixinatee.succeed( + "curl -sSf http:/127.0.0.1/ | grep title" + ) + ''; + }; +in +{ + local = (mkNixinateTest "local"); + remote = (mkNixinateTest "remote"); +} diff --git a/tests/vmTest/nixinateeAdditional.nix b/tests/vmTest/nixinateeAdditional.nix new file mode 100644 index 0000000..d2fb098 --- /dev/null +++ b/tests/vmTest/nixinateeAdditional.nix @@ -0,0 +1,8 @@ +# Configuration that will be added to the nixinatee node. Nixinate will deploy +# the combination of nixinateBase.nix + nixinateAdditional.nix +{ + config = { + services.nginx.enable = true; + networking.firewall.allowedTCPPorts = [ 80 ]; + }; +} diff --git a/tests/vmTest/nixinateeBase.nix b/tests/vmTest/nixinateeBase.nix new file mode 100644 index 0000000..3601657 --- /dev/null +++ b/tests/vmTest/nixinateeBase.nix @@ -0,0 +1,32 @@ +# Common configuration of nixinatee node in the vmTest. This is the base +# configuration which is required to perform the test. +{ + config = { + nix.trustedUsers = [ "nixinator" ]; + security.sudo.extraRules = [{ + users = [ "nixinator" ]; + commands = [{ + command = "ALL"; + options = [ "NOPASSWD" ]; + }]; + }]; + users = { + mutableUsers = false; + users = { + nixinator = { + extraGroups = [ + "wheel" + ]; + password = ""; + isNormalUser = true; + }; + }; + }; + services.openssh = { + enable = true; + extraConfig = "PermitEmptyPasswords yes"; + }; + documentation.enable = false; + boot.loader.grub.enable = false; + }; +}