diff --git a/nixos/tests/nextcloud/basic.nix b/nixos/tests/nextcloud/basic.nix index 428fe0aa10db..2a32f2b4d199 100644 --- a/nixos/tests/nextcloud/basic.nix +++ b/nixos/tests/nextcloud/basic.nix @@ -1,27 +1,27 @@ -args@{ pkgs, nextcloudVersion ? 22, ... }: +{ name, pkgs, testBase, system,... }: -(import ../make-test-python.nix ({ pkgs, ...}: let - adminpass = "notproduction"; - adminuser = "root"; -in { - name = "nextcloud-basic"; +with import ../../lib/testing-python.nix { inherit system pkgs; }; +runTest ({ config, ... }: { + inherit name; meta = with pkgs.lib.maintainers; { - maintainers = [ globin eqyiel ]; + maintainers = [ globin eqyiel ma27 ]; }; - nodes = rec { + imports = [ testBase ]; + + nodes = { # The only thing the client needs to do is download a file. client = { ... }: { services.davfs2.enable = true; systemd.tmpfiles.settings.nextcloud = { "/tmp/davfs2-secrets"."f+" = { mode = "0600"; - argument = "http://nextcloud/remote.php/dav/files/${adminuser} ${adminuser} ${adminpass}"; + argument = "http://nextcloud/remote.php/dav/files/${config.adminuser} ${config.adminuser} ${config.adminpass}"; }; }; virtualisation.fileSystems = { "/mnt/dav" = { - device = "http://nextcloud/remote.php/dav/files/${adminuser}"; + device = "http://nextcloud/remote.php/dav/files/${config.adminuser}"; fsType = "davfs"; options = let davfs2Conf = (pkgs.writeText "davfs2.conf" "secrets /tmp/davfs2-secrets"); @@ -30,11 +30,7 @@ in { }; }; - nextcloud = { config, pkgs, ... }: let - cfg = config; - in { - networking.firewall.allowedTCPPorts = [ 80 ]; - + nextcloud = { config, pkgs, ... }: { systemd.tmpfiles.rules = [ "d /var/lib/nextcloud-data 0750 nextcloud nginx - -" ]; @@ -42,14 +38,7 @@ in { services.nextcloud = { enable = true; datadir = "/var/lib/nextcloud-data"; - hostName = "nextcloud"; - database.createLocally = true; - config = { - # Don't inherit adminuser since "root" is supposed to be the default - adminpassFile = "${pkgs.writeText "adminpass" adminpass}"; # Don't try this at home! - dbtableprefix = "nixos_"; - }; - package = pkgs.${"nextcloud" + (toString nextcloudVersion)}; + config.dbtableprefix = "nixos_"; autoUpdateApps = { enable = true; startAt = "20:00"; @@ -57,64 +46,31 @@ in { phpExtraExtensions = all: [ all.bz2 ]; }; - environment.systemPackages = [ cfg.services.nextcloud.occ ]; + specialisation.withoutMagick.configuration = { + services.nextcloud.enableImagemagick = false; + }; }; - - nextcloudWithoutMagick = args@{ config, pkgs, lib, ... }: - lib.mkMerge - [ (nextcloud args) - { services.nextcloud.enableImagemagick = false; } ]; }; - testScript = { nodes, ... }: let - withRcloneEnv = pkgs.writeScript "with-rclone-env" '' - #!${pkgs.runtimeShell} - export RCLONE_CONFIG_NEXTCLOUD_TYPE=webdav - export RCLONE_CONFIG_NEXTCLOUD_URL="http://nextcloud/remote.php/dav/files/${adminuser}" - export RCLONE_CONFIG_NEXTCLOUD_VENDOR="nextcloud" - export RCLONE_CONFIG_NEXTCLOUD_USER="${adminuser}" - export RCLONE_CONFIG_NEXTCLOUD_PASS="$(${pkgs.rclone}/bin/rclone obscure ${adminpass})" - "''${@}" - ''; - copySharedFile = pkgs.writeScript "copy-shared-file" '' - #!${pkgs.runtimeShell} - echo 'hi' | ${withRcloneEnv} ${pkgs.rclone}/bin/rclone rcat nextcloud:test-shared-file - ''; - - diffSharedFile = pkgs.writeScript "diff-shared-file" '' - #!${pkgs.runtimeShell} - diff <(echo 'hi') <(${pkgs.rclone}/bin/rclone cat nextcloud:test-shared-file) - ''; - + test-helpers.extraTests = { nodes, ... }: let findInClosure = what: drv: pkgs.runCommand "find-in-closure" { exportReferencesGraph = [ "graph" drv ]; inherit what; } '' test -e graph grep "$what" graph >$out || true ''; - nextcloudUsesImagick = findInClosure "imagick" nodes.nextcloud.system.build.vm; - nextcloudWithoutDoesntUseIt = findInClosure "imagick" nodes.nextcloudWithoutMagick.system.build.vm; + nexcloudWithImagick = findInClosure "imagick" nodes.nextcloud.system.build.vm; + nextcloudWithoutImagick = findInClosure "imagick" nodes.nextcloud.specialisation.withoutMagick.configuration.system.build.vm; in '' - assert open("${nextcloudUsesImagick}").read() != "" - assert open("${nextcloudWithoutDoesntUseIt}").read() == "" + with subtest("File is in proper nextcloud home"): + nextcloud.succeed("test -f ${nodes.nextcloud.services.nextcloud.datadir}/data/root/files/test-shared-file") - nextcloud.start() - client.start() - nextcloud.wait_for_unit("multi-user.target") - # This is just to ensure the nextcloud-occ program is working - nextcloud.succeed("nextcloud-occ status") - nextcloud.succeed("curl -sSf http://nextcloud/login") - # Ensure that no OpenSSL 1.1 is used. - nextcloud.succeed( - "${nodes.nextcloud.services.phpfpm.pools.nextcloud.phpPackage}/bin/php -i | grep 'OpenSSL Library Version' | awk -F'=>' '{ print $2 }' | awk '{ print $2 }' | grep -v 1.1" - ) - nextcloud.succeed( - "${withRcloneEnv} ${copySharedFile}" - ) - client.wait_for_unit("multi-user.target") - nextcloud.succeed("test -f /var/lib/nextcloud-data/data/root/files/test-shared-file") - client.succeed( - "${withRcloneEnv} ${diffSharedFile}" - ) - assert "hi" in client.succeed("cat /mnt/dav/test-shared-file") - nextcloud.succeed("grep -vE '^HBEGIN:oc_encryption_module' /var/lib/nextcloud-data/data/root/files/test-shared-file") + with subtest("Closure checks"): + assert open("${nexcloudWithImagick}").read() != "" + assert open("${nextcloudWithoutImagick}").read() == "" + + with subtest("Davfs2"): + assert "hi" in client.succeed("cat /mnt/dav/test-shared-file") + + with subtest("Ensure SSE is disabled by default"): + nextcloud.succeed("grep -vE '^HBEGIN:oc_encryption_module' /var/lib/nextcloud-data/data/root/files/test-shared-file") ''; -})) args +}) diff --git a/nixos/tests/nextcloud/default.nix b/nixos/tests/nextcloud/default.nix index d024adffd9f0..33aa227d2b03 100644 --- a/nixos/tests/nextcloud/default.nix +++ b/nixos/tests/nextcloud/default.nix @@ -5,21 +5,108 @@ with pkgs.lib; -foldl - (matrix: ver: matrix // { - "basic${toString ver}" = import ./basic.nix { inherit system pkgs; nextcloudVersion = ver; }; - "with-postgresql-and-redis${toString ver}" = import ./with-postgresql-and-redis.nix { - inherit system pkgs; - nextcloudVersion = ver; +let + baseModule = { config, ... }: { + imports = [ + { + options.test-helpers = { + rclone = mkOption { type = types.str; }; + upload-sample = mkOption { type = types.str; }; + check-sample = mkOption { type = types.str; }; + init = mkOption { type = types.str; default = ""; }; + extraTests = mkOption { type = types.either types.str (types.functionTo types.str); default = ""; }; + }; + options.adminuser = mkOption { type = types.str; }; + options.adminpass = mkOption { type = types.str; }; + } + ]; + + adminuser = "root"; + adminpass = "hunter2"; + + test-helpers.rclone = "${pkgs.writeShellScript "rclone" '' + set -euo pipefail + export PATH="${pkgs.rclone}/bin:$PATH" + export RCLONE_CONFIG_NEXTCLOUD_TYPE=webdav + export RCLONE_CONFIG_NEXTCLOUD_URL="http://nextcloud/remote.php/dav/files/${config.adminuser}" + export RCLONE_CONFIG_NEXTCLOUD_VENDOR="nextcloud" + export RCLONE_CONFIG_NEXTCLOUD_USER="${config.adminuser}" + export RCLONE_CONFIG_NEXTCLOUD_PASS="$(rclone obscure ${config.adminpass})" + exec "$@" + ''}"; + test-helpers.upload-sample = "${pkgs.writeShellScript "rclone-upload" '' + <<<'hi' rclone rcat nextcloud:test-shared-file + ''}"; + test-helpers.check-sample = "${pkgs.writeShellScript "check-sample" '' + set -e + diff <(echo 'hi') <(rclone cat nextcloud:test-shared-file) + ''}"; + + nodes = { + client = { ... }: {}; + nextcloud = { + networking.firewall.allowedTCPPorts = [ 80 ]; + services.nextcloud = { + enable = true; + hostName = "nextcloud"; + https = false; + database.createLocally = true; + config = { + adminpassFile = "${pkgs.writeText "adminpass" config.adminpass}"; # Don't try this at home! + }; + }; + }; }; - "with-mysql-and-memcached${toString ver}" = import ./with-mysql-and-memcached.nix { - inherit system pkgs; - nextcloudVersion = ver; - }; - "with-declarative-redis-and-secrets${toString ver}" = import ./with-declarative-redis-and-secrets.nix { - inherit system pkgs; - nextcloudVersion = ver; - }; - }) -{ } - [ 27 28 29 ] + + testScript = args@{ nodes, ... }: let + inherit (config) test-helpers; + in mkBefore '' + nextcloud.start() + client.start() + nextcloud.wait_for_unit("multi-user.target") + + ${test-helpers.init} + + with subtest("Ensure nextcloud-occ is working"): + nextcloud.succeed("nextcloud-occ status") + nextcloud.succeed("curl -sSf http://nextcloud/login") + + with subtest("Upload/Download test"): + nextcloud.succeed( + "${test-helpers.rclone} ${test-helpers.upload-sample}" + ) + client.wait_for_unit("multi-user.target") + client.succeed( + "${test-helpers.rclone} ${test-helpers.check-sample}" + ) + + ${if builtins.isFunction test-helpers.extraTests then test-helpers.extraTests args else test-helpers.extraTests} + ''; + }; + + genTests = version: + let + testBase.imports = [ + baseModule + { + nodes.nextcloud = { pkgs, ... }: { + services.nextcloud.package = pkgs.${"nextcloud${toString version}"}; + }; + } + ]; + + callNextcloudTest = path: + let + name = "${removeSuffix ".nix" (baseNameOf path)}${toString version}"; + in nameValuePair name (import path { + inherit system pkgs testBase; + name = "nextcloud-${name}"; + }); + in map callNextcloudTest [ + ./basic.nix + ./with-mysql-and-memcached.nix + ./with-postgresql-and-redis.nix + ./with-objectstore.nix + ]; +in +listToAttrs (concatMap genTests [ 27 28 29 ]) diff --git a/nixos/tests/nextcloud/with-mysql-and-memcached.nix b/nixos/tests/nextcloud/with-mysql-and-memcached.nix index 035a7fdcb0c8..07a3e56fae4a 100644 --- a/nixos/tests/nextcloud/with-mysql-and-memcached.nix +++ b/nixos/tests/nextcloud/with-mysql-and-memcached.nix @@ -1,79 +1,37 @@ -args@{ pkgs, nextcloudVersion ? 22, ... }: +{ pkgs, testBase, system, ... }: -(import ../make-test-python.nix ({ pkgs, ...}: let - adminpass = "hunter2"; - adminuser = "root"; -in { +with import ../../lib/testing-python.nix { inherit system pkgs; }; +runTest ({ config, ... }: { name = "nextcloud-with-mysql-and-memcached"; meta = with pkgs.lib.maintainers; { maintainers = [ eqyiel ]; }; + imports = [ testBase ]; + nodes = { - # The only thing the client needs to do is download a file. - client = { ... }: {}; - nextcloud = { config, pkgs, ... }: { - networking.firewall.allowedTCPPorts = [ 80 ]; - services.nextcloud = { - enable = true; - hostName = "nextcloud"; - https = true; - package = pkgs.${"nextcloud" + (toString nextcloudVersion)}; caching = { apcu = true; redis = false; memcached = true; }; - database.createLocally = true; - config = { - dbtype = "mysql"; - # Don't inherit adminuser since "root" is supposed to be the default - adminpassFile = "${pkgs.writeText "adminpass" adminpass}"; # Don't try this at home! - }; + config.dbtype = "mysql"; }; services.memcached.enable = true; }; }; - testScript = let + test-helpers.init = let configureMemcached = pkgs.writeScript "configure-memcached" '' - #!${pkgs.runtimeShell} nextcloud-occ config:system:set memcached_servers 0 0 --value 127.0.0.1 --type string nextcloud-occ config:system:set memcached_servers 0 1 --value 11211 --type integer nextcloud-occ config:system:set memcache.local --value '\OC\Memcache\APCu' --type string nextcloud-occ config:system:set memcache.distributed --value '\OC\Memcache\Memcached' --type string ''; - withRcloneEnv = pkgs.writeScript "with-rclone-env" '' - #!${pkgs.runtimeShell} - export RCLONE_CONFIG_NEXTCLOUD_TYPE=webdav - export RCLONE_CONFIG_NEXTCLOUD_URL="http://nextcloud/remote.php/dav/files/${adminuser}" - export RCLONE_CONFIG_NEXTCLOUD_VENDOR="nextcloud" - export RCLONE_CONFIG_NEXTCLOUD_USER="${adminuser}" - export RCLONE_CONFIG_NEXTCLOUD_PASS="$(${pkgs.rclone}/bin/rclone obscure ${adminpass})" - ''; - copySharedFile = pkgs.writeScript "copy-shared-file" '' - #!${pkgs.runtimeShell} - echo 'hi' | ${pkgs.rclone}/bin/rclone rcat nextcloud:test-shared-file - ''; - - diffSharedFile = pkgs.writeScript "diff-shared-file" '' - #!${pkgs.runtimeShell} - diff <(echo 'hi') <(${pkgs.rclone}/bin/rclone cat nextcloud:test-shared-file) - ''; in '' - start_all() - nextcloud.wait_for_unit("multi-user.target") nextcloud.succeed("${configureMemcached}") - nextcloud.succeed("curl -sSf http://nextcloud/login") - nextcloud.succeed( - "${withRcloneEnv} ${copySharedFile}" - ) - client.wait_for_unit("multi-user.target") - client.succeed( - "${withRcloneEnv} ${diffSharedFile}" - ) ''; -})) args +}) diff --git a/nixos/tests/nextcloud/with-objectstore.nix b/nixos/tests/nextcloud/with-objectstore.nix new file mode 100644 index 000000000000..fc26760b8bab --- /dev/null +++ b/nixos/tests/nextcloud/with-objectstore.nix @@ -0,0 +1,96 @@ +{ name, pkgs, testBase, system, ... }: + +with import ../../lib/testing-python.nix { inherit system pkgs; }; +runTest ({ config, lib, ... }: let + accessKey = "BKIKJAA5BMMU2RHO6IBB"; + secretKey = "V7f1CwQqAcwo80UEIJEjc5gVQUSSx5ohQ9GSrr12"; + + rootCredentialsFile = pkgs.writeText "minio-credentials-full" '' + MINIO_ROOT_USER=${accessKey} + MINIO_ROOT_PASSWORD=${secretKey} + ''; +in { + inherit name; + meta = with pkgs.lib.maintainers; { + maintainers = [ onny ma27 ]; + }; + + imports = [ testBase ]; + + nodes = { + nextcloud = { config, pkgs, ... }: { + networking.firewall.allowedTCPPorts = [ 9000 ]; + environment.systemPackages = [ pkgs.minio-client ]; + + services.nextcloud.config.objectstore.s3 = { + enable = true; + bucket = "nextcloud"; + autocreate = true; + key = accessKey; + secretFile = "${pkgs.writeText "secretKey" secretKey}"; + hostname = "nextcloud"; + useSsl = false; + port = 9000; + usePathStyle = true; + region = "us-east-1"; + }; + + services.minio = { + enable = true; + listenAddress = "0.0.0.0:9000"; + consoleAddress = "0.0.0.0:9001"; + inherit rootCredentialsFile; + }; + }; + }; + + test-helpers.init = '' + nextcloud.wait_for_open_port(9000) + ''; + + test-helpers.extraTests = { nodes, ... }: '' + with subtest("File is not on the filesystem"): + nextcloud.succeed("test ! -e ${nodes.nextcloud.services.nextcloud.home}/data/root/files/test-shared-file") + + with subtest("Check if file is in S3"): + nextcloud.succeed( + "mc config host add minio http://localhost:9000 ${accessKey} ${secretKey} --api s3v4" + ) + files = nextcloud.succeed('mc ls minio/nextcloud|sort').strip().split('\n') + + # Cannot assert an exact number here, nc27 writes more stuff initially into S3. + # For now let's assume it's always the most recently added file. + assert len(files) > 0, f""" + Expected to have at least one object in minio/nextcloud. But `mc ls` gave output: + + '{files}' + """ + + import re + ptrn = re.compile("^\[[A-Z0-9 :-]+\] +(?P
[A-Za-z0-9 :]+)$") + match = ptrn.match(files[-1].strip()) + assert match, "Cannot match mc client output!" + size, type_, file = tuple(match.group('details').split(' ')) + + assert size == "3B", f""" + Expected size of uploaded file to be 3 bytes, got {size} + """ + + assert type_ == 'STANDARD', f""" + Expected type of bucket entry to be a file, i.e. 'STANDARD'. Got {type_} + """ + + assert file.startswith('urn:oid'), """ + Expected filename to start with 'urn:oid', instead got '{file}. + """ + + with subtest("Test download from S3"): + client.succeed( + "env AWS_ACCESS_KEY_ID=${accessKey} AWS_SECRET_ACCESS_KEY=${secretKey} " + + f"${lib.getExe pkgs.awscli2} s3 cp s3://nextcloud/{file} test --endpoint-url http://nextcloud:9000 " + + "--region us-east-1" + ) + + client.succeed("test hi = $(cat test)") + ''; +}) diff --git a/nixos/tests/nextcloud/with-postgresql-and-redis.nix b/nixos/tests/nextcloud/with-postgresql-and-redis.nix index 06afc589403d..24c17f70932d 100644 --- a/nixos/tests/nextcloud/with-postgresql-and-redis.nix +++ b/nixos/tests/nextcloud/with-postgresql-and-redis.nix @@ -1,45 +1,30 @@ -args@{ pkgs, nextcloudVersion ? 22, ... }: +{ name, pkgs, testBase, system, ... }: -(import ../make-test-python.nix ({ pkgs, ...}: let - adminpass = "hunter2"; - adminuser = "custom-admin-username"; -in { - name = "nextcloud-with-postgresql-and-redis"; +with import ../../lib/testing-python.nix { inherit system pkgs; }; +runTest ({ config, ... }: { + inherit name; meta = with pkgs.lib.maintainers; { - maintainers = [ eqyiel ]; + maintainers = [ eqyiel ma27 ]; }; + imports = [ testBase ]; + nodes = { - # The only thing the client needs to do is download a file. - client = { ... }: {}; - nextcloud = { config, pkgs, lib, ... }: { - networking.firewall.allowedTCPPorts = [ 80 ]; - services.nextcloud = { - enable = true; - hostName = "nextcloud"; - package = pkgs.${"nextcloud" + (toString nextcloudVersion)}; caching = { apcu = false; redis = true; memcached = false; }; - database.createLocally = true; - config = { - dbtype = "pgsql"; - inherit adminuser; - adminpassFile = toString (pkgs.writeText "admin-pass-file" '' - ${adminpass} - ''); - }; + config.dbtype = "pgsql"; notify_push = { enable = true; logLevel = "debug"; }; extraAppsEnable = true; - extraApps = { - inherit (pkgs."nextcloud${lib.versions.major config.services.nextcloud.package.version}Packages".apps) notify_push notes; + extraApps = with config.services.nextcloud.package.packages.apps; { + inherit notify_push notes; }; settings.trusted_proxies = [ "::1" ]; }; @@ -49,50 +34,27 @@ in { }; }; - testScript = let + test-helpers.init = let configureRedis = pkgs.writeScript "configure-redis" '' - #!${pkgs.runtimeShell} nextcloud-occ config:system:set redis 'host' --value 'localhost' --type string nextcloud-occ config:system:set redis 'port' --value 6379 --type integer nextcloud-occ config:system:set memcache.local --value '\OC\Memcache\Redis' --type string nextcloud-occ config:system:set memcache.locking --value '\OC\Memcache\Redis' --type string ''; - withRcloneEnv = pkgs.writeScript "with-rclone-env" '' - #!${pkgs.runtimeShell} - export RCLONE_CONFIG_NEXTCLOUD_TYPE=webdav - export RCLONE_CONFIG_NEXTCLOUD_URL="http://nextcloud/remote.php/dav/files/${adminuser}" - export RCLONE_CONFIG_NEXTCLOUD_VENDOR="nextcloud" - export RCLONE_CONFIG_NEXTCLOUD_USER="${adminuser}" - export RCLONE_CONFIG_NEXTCLOUD_PASS="$(${pkgs.rclone}/bin/rclone obscure ${adminpass})" - "''${@}" - ''; - copySharedFile = pkgs.writeScript "copy-shared-file" '' - #!${pkgs.runtimeShell} - echo 'hi' | ${pkgs.rclone}/bin/rclone rcat nextcloud:test-shared-file - ''; - - diffSharedFile = pkgs.writeScript "diff-shared-file" '' - #!${pkgs.runtimeShell} - diff <(echo 'hi') <(${pkgs.rclone}/bin/rclone cat nextcloud:test-shared-file) - ''; in '' - start_all() - nextcloud.wait_for_unit("multi-user.target") nextcloud.succeed("${configureRedis}") - nextcloud.succeed("curl -sSf http://nextcloud/login") - nextcloud.succeed( - "${withRcloneEnv} ${copySharedFile}" - ) - client.wait_for_unit("multi-user.target") - client.execute("${pkgs.lib.getExe pkgs.nextcloud-notify_push.passthru.test_client} http://nextcloud ${adminuser} ${adminpass} >&2 &") - client.succeed( - "${withRcloneEnv} ${diffSharedFile}" - ) - nextcloud.wait_until_succeeds("journalctl -u nextcloud-notify_push | grep -q \"Sending ping to ${adminuser}\"") - - # redis cache should not be empty - nextcloud.fail('test "[]" = "$(redis-cli --json KEYS "*")"') - - nextcloud.fail("curl -f http://nextcloud/nix-apps/notes/lib/AppInfo/Application.php") ''; -})) args + + test-helpers.extraTests = '' + with subtest("notify-push"): + client.execute("${pkgs.lib.getExe pkgs.nextcloud-notify_push.passthru.test_client} http://nextcloud ${config.adminuser} ${config.adminpass} >&2 &") + nextcloud.wait_until_succeeds("journalctl -u nextcloud-notify_push | grep -q \"Sending ping to ${config.adminuser}\"") + + with subtest("Redis is used for caching"): + # redis cache should not be empty + nextcloud.fail('test "[]" = "$(redis-cli --json KEYS "*")"') + + with subtest("No code is returned when requesting PHP files (regression test)"): + nextcloud.fail("curl -f http://nextcloud/nix-apps/notes/lib/AppInfo/Application.php") + ''; +})