forked from Fediversity/Fediversity
test deployments using data obtained through the data model in VMs. caveats: - SSH currently has a `run` abstraction that the nixops4 model still lacks - the deployed (trivial) configuration on activation has not facilitated new ssh connections (for subsequent) updates, i.e. a more sophisticated configuration would be needed for real-life usage. Reviewed-on: Fediversity/Fediversity#505
378 lines
14 KiB
Nix
378 lines
14 KiB
Nix
{
|
|
inputs,
|
|
lib,
|
|
hostPkgs,
|
|
config,
|
|
...
|
|
}:
|
|
|
|
let
|
|
inherit (lib)
|
|
getExe
|
|
;
|
|
|
|
## Some places need a dummy file that will in fact never be used. We create
|
|
## it here.
|
|
dummyFile = hostPkgs.writeText "dummy" "dummy";
|
|
panelPort = 8000;
|
|
|
|
panelUser = "test";
|
|
panelEmail = "test@test.com";
|
|
panelPassword = "ouiprdaaa43"; # panel's manager complains if too close to username or email
|
|
|
|
fediUser = "test";
|
|
fediEmail = "test@test.com";
|
|
fediPassword = "testtest";
|
|
fediName = "Testy McTestface";
|
|
|
|
toPythonBool = b: if b then "True" else "False";
|
|
|
|
interactWithPanel =
|
|
{
|
|
baseUri,
|
|
enableMastodon,
|
|
enablePeertube,
|
|
enablePixelfed,
|
|
}:
|
|
hostPkgs.writers.writePython3Bin "interact-with-panel"
|
|
{
|
|
libraries = with hostPkgs.python3Packages; [ selenium ];
|
|
flakeIgnore = [
|
|
"E302" # expected 2 blank lines, found 0
|
|
"E303" # too many blank lines
|
|
"E305" # expected 2 blank lines after end of function or class
|
|
"E501" # line too long (> 79 characters)
|
|
"E731" # do not assign lambda expression, use a def
|
|
];
|
|
}
|
|
''
|
|
from selenium import webdriver
|
|
from selenium.webdriver.common.by import By
|
|
from selenium.webdriver.firefox.options import Options
|
|
from selenium.webdriver.support.ui import WebDriverWait
|
|
|
|
print("Create and configure driver...")
|
|
options = Options()
|
|
options.add_argument("--headless")
|
|
options.binary_location = "${getExe hostPkgs.firefox-unwrapped}"
|
|
service = webdriver.FirefoxService(executable_path="${getExe hostPkgs.geckodriver}")
|
|
driver = webdriver.Firefox(options=options, service=service)
|
|
driver.set_window_size(1280, 960)
|
|
driver.implicitly_wait(360)
|
|
driver.command_executor.set_timeout(3600)
|
|
|
|
print("Open login page...")
|
|
driver.get("${baseUri}/login/")
|
|
print("Enter username...")
|
|
driver.find_element(By.XPATH, "//input[@name = 'username']").send_keys("${panelUser}")
|
|
print("Enter password...")
|
|
driver.find_element(By.XPATH, "//input[@name = 'password']").send_keys("${panelPassword}")
|
|
print("Click “Login” button...")
|
|
driver.find_element(By.XPATH, "//button[normalize-space() = 'Login']").click()
|
|
|
|
print("Open configuration page...")
|
|
driver.get("${baseUri}/configuration/")
|
|
|
|
# Helpers to actually set and not add or switch input values.
|
|
def input_set(elt, keys):
|
|
elt.clear()
|
|
elt.send_keys(keys)
|
|
def checkbox_set(elt, new_value):
|
|
if new_value != elt.is_selected():
|
|
elt.click()
|
|
|
|
print("Enable Fediversity...")
|
|
checkbox_set(driver.find_element(By.XPATH, "//input[@name = 'enable']"), True)
|
|
|
|
print("Fill in initialUser info...")
|
|
input_set(driver.find_element(By.XPATH, "//input[@name = 'initialUser.username']"), "${fediUser}")
|
|
input_set(driver.find_element(By.XPATH, "//input[@name = 'initialUser.password']"), "${fediPassword}")
|
|
input_set(driver.find_element(By.XPATH, "//input[@name = 'initialUser.email']"), "${fediEmail}")
|
|
input_set(driver.find_element(By.XPATH, "//input[@name = 'initialUser.displayName']"), "${fediName}")
|
|
|
|
print("Enable services...")
|
|
checkbox_set(driver.find_element(By.XPATH, "//input[@name = 'mastodon.enable']"), ${toPythonBool enableMastodon})
|
|
checkbox_set(driver.find_element(By.XPATH, "//input[@name = 'peertube.enable']"), ${toPythonBool enablePeertube})
|
|
checkbox_set(driver.find_element(By.XPATH, "//input[@name = 'pixelfed.enable']"), ${toPythonBool enablePixelfed})
|
|
|
|
print("Start deployment...")
|
|
driver.find_element(By.XPATH, "//button[@id = 'deploy-button']").click()
|
|
|
|
print("Wait for deployment status to show up...")
|
|
get_deployment_result = lambda d: d.find_element(By.XPATH, "//div[@id = 'deployment-result']//p")
|
|
WebDriverWait(driver, timeout=3660, poll_frequency=10).until(get_deployment_result)
|
|
deployment_result = get_deployment_result(driver).get_attribute('innerHTML')
|
|
|
|
print("Quit...")
|
|
driver.quit()
|
|
|
|
match deployment_result:
|
|
case 'Deployment Succeeded':
|
|
print("Deployment has succeeded; exiting normally")
|
|
exit(0)
|
|
case 'Deployment Failed':
|
|
print("Deployment has failed; exiting with return code `1`")
|
|
exit(1)
|
|
case _:
|
|
print(f"Unexpected deployment result: {deployment_result}; exiting with return code `2`")
|
|
exit(2)
|
|
'';
|
|
|
|
in
|
|
|
|
{
|
|
_class = "nixosTest";
|
|
|
|
name = "deployment-panel";
|
|
|
|
sourceFileset = lib.fileset.unions [
|
|
./constants.nix
|
|
./deployment.nix
|
|
(config.pathToCwd + "/flake-under-test.nix")
|
|
|
|
# REVIEW: I would like to be able to grab all of `/deployment` minus
|
|
# `/deployment/check`, but I can't because there is a bunch of other files
|
|
# in `/deployment`. Maybe we can think of a reorg making things more robust
|
|
# here? (comment also in CLI test)
|
|
../../default.nix
|
|
../../options.nix
|
|
|
|
../../../services/fediversity
|
|
];
|
|
|
|
## The panel's module sets `nixpkgs.overlays` which clashes with
|
|
## `pkgsReadOnly`. We disable it here.
|
|
node.pkgsReadOnly = false;
|
|
|
|
nodes.deployer =
|
|
{ pkgs, ... }:
|
|
{
|
|
imports = [
|
|
(import ../../../panel { }).module
|
|
];
|
|
|
|
## FIXME: This should be in the common stuff.
|
|
security.acme = {
|
|
acceptTerms = true;
|
|
defaults.email = "test@test.com";
|
|
defaults.server = "https://acme.test/dir";
|
|
};
|
|
security.pki.certificateFiles = [
|
|
(import "${inputs.nixpkgs}/nixos/tests/common/acme/server/snakeoil-certs.nix").ca.cert
|
|
];
|
|
networking.extraHosts = "${config.acmeNodeIP} acme.test";
|
|
|
|
services.panel = {
|
|
enable = true;
|
|
production = true;
|
|
domain = "deployer";
|
|
secrets = {
|
|
SECRET_KEY = dummyFile;
|
|
};
|
|
port = panelPort;
|
|
|
|
deployment = {
|
|
flake = "/run/fedipanel/flake";
|
|
name = "check-deployment-panel";
|
|
};
|
|
};
|
|
|
|
environment.systemPackages = [ pkgs.expect ];
|
|
|
|
## FIXME: The following dependencies are necessary but I do not
|
|
## understand why they are not covered by the fake node.
|
|
system.extraDependencies = with pkgs; [
|
|
peertube
|
|
peertube.inputDerivation
|
|
gixy # a configuration checker for nginx
|
|
gixy.inputDerivation
|
|
];
|
|
|
|
system.extraDependenciesFromModule = {
|
|
imports = [ ../../../services/fediversity ];
|
|
fediversity = {
|
|
domain = "fediversity.net"; # would write `dummy` but that would not type
|
|
garage.enable = true;
|
|
mastodon = {
|
|
enable = true;
|
|
s3AccessKeyFile = dummyFile;
|
|
s3SecretKeyFile = dummyFile;
|
|
};
|
|
peertube = {
|
|
enable = true;
|
|
secretsFile = dummyFile;
|
|
s3AccessKeyFile = dummyFile;
|
|
s3SecretKeyFile = dummyFile;
|
|
};
|
|
pixelfed = {
|
|
enable = true;
|
|
s3AccessKeyFile = dummyFile;
|
|
s3SecretKeyFile = dummyFile;
|
|
};
|
|
temp.cores = 1;
|
|
temp.initialUser = {
|
|
username = "dummy";
|
|
displayName = "dummy";
|
|
email = "dummy";
|
|
passwordFile = dummyFile;
|
|
};
|
|
};
|
|
};
|
|
};
|
|
|
|
nodes.client =
|
|
{ pkgs, ... }:
|
|
{
|
|
environment.systemPackages = with pkgs; [
|
|
httpie
|
|
dnsutils # for `dig`
|
|
openssl
|
|
cacert
|
|
wget
|
|
python3
|
|
python3Packages.selenium
|
|
firefox-unwrapped
|
|
geckodriver
|
|
];
|
|
|
|
security.pki.certificateFiles = [
|
|
config.nodes.acme.test-support.acme.caCert
|
|
];
|
|
networking.extraHosts = "${config.acmeNodeIP} acme.test";
|
|
};
|
|
|
|
## NOTE: The target machines may need more RAM than the default to handle
|
|
## being deployed to, otherwise we get something like:
|
|
##
|
|
## pixelfed # [ 616.785499 ] sshd-session[1167]: Conection closed by 2001:db8:1::2 port 45004
|
|
## deployer # error: writing to file: No space left on device
|
|
## pixelfed # [ 616.788538 ] sshd-session[1151]: pam_unix(sshd:session): session closed for user port
|
|
## pixelfed # [ 616.793929 ] systemd-logind[719]: Session 4 logged out. Waiting for processes to exit.
|
|
## deployer # Error: Could not create resource
|
|
##
|
|
## These values have been trimmed down to the gigabyte.
|
|
nodes.mastodon.virtualisation.memorySize = 4 * 1024;
|
|
nodes.pixelfed.virtualisation.memorySize = 4 * 1024;
|
|
nodes.peertube.virtualisation.memorySize = 5 * 1024;
|
|
|
|
## FIXME: The test of presence of the services are very simple: we only
|
|
## check that there is a systemd service of the expected name on the
|
|
## machine. This proves at least that NixOps4 did something, and we cannot
|
|
## really do more for now because the services aren't actually working
|
|
## properly, in particular because of DNS issues. We should fix the services
|
|
## and check that they are working properly.
|
|
|
|
extraTestScript = ''
|
|
## TODO: We want a nicer way to control where the FediPanel consumes its
|
|
## flake, which can default to the store but could also be somewhere else if
|
|
## someone wanted to change the code of the flake.
|
|
##
|
|
with subtest("Give the panel access to the flake"):
|
|
deployer.succeed("mkdir /run/fedipanel /run/fedipanel/flake >&2")
|
|
deployer.succeed("cp -R . /run/fedipanel/flake >&2")
|
|
deployer.succeed("chown -R panel:panel /run/fedipanel >&2")
|
|
|
|
## TODO: I want a programmatic way to provide an SSH key to the panel (and
|
|
## therefore NixOps4). This should happen either in the Python code, but
|
|
## maybe it is fair that that one picks up on the user's key? It could
|
|
## also be in the Nix packaging.
|
|
##
|
|
with subtest("Set up the panel's SSH keys"):
|
|
deployer.succeed("mkdir /home/panel/.ssh >&2")
|
|
deployer.succeed("cp -R /root/.ssh/* /home/panel/.ssh >&2")
|
|
deployer.succeed("chown -R panel:panel /home/panel/.ssh >&2")
|
|
deployer.succeed("chmod 600 /home/panel/.ssh/* >&2")
|
|
|
|
## TODO: This is a hack to accept the root CA used by Pebble on the client
|
|
## machine. Pebble randomizes everything, so the only way to get it is to
|
|
## call the /roots/0 endpoint at runtime, leaving not much margin for a nice
|
|
## Nixy way of adding the certificate. There is no way around it as this is
|
|
## by design in Pebble, showing in fact that Pebble was not the appropriate
|
|
## tool for our use and that nixpkgs does not in fact provide an easy way to
|
|
## generate _usable_ certificates in NixOS tests. I suggest we merge this,
|
|
## and track the task to set it up in a cleaner way. I would tackle this in
|
|
## a subsequent PR, and hopefully even contribute this BetterWay(tm) to
|
|
## nixpkgs. — Niols
|
|
##
|
|
with subtest("Set up ACME root CA on client"):
|
|
client.succeed("""
|
|
cd /etc/ssl/certs
|
|
curl -o pebble-root-ca.pem https://acme.test:15000/roots/0
|
|
curl -o pebble-intermediate-ca.pem https://acme.test:15000/intermediates/0
|
|
{ cat ca-bundle.crt
|
|
cat pebble-root-ca.pem
|
|
cat pebble-intermediate-ca.pem
|
|
} > new-ca-bundle.crt
|
|
rm ca-bundle.crt ca-certificates.crt
|
|
mv new-ca-bundle.crt ca-bundle.crt
|
|
ln -s ca-bundle.crt ca-certificates.crt
|
|
""")
|
|
|
|
## TODO: I would hope for a more declarative way to add users. This should
|
|
## be handled by the Nix packaging of the FediPanel. — Niols
|
|
##
|
|
with subtest("Create panel user"):
|
|
deployer.succeed("""
|
|
expect -c '
|
|
spawn manage createsuperuser --username ${panelUser} --email ${panelEmail}
|
|
expect "Password: "; send "${panelPassword}\\n";
|
|
expect "Password (again): "; send "${panelPassword}\\n"
|
|
interact
|
|
' >&2
|
|
""")
|
|
|
|
with subtest("Check the status of the services - there should be none"):
|
|
garage.fail("systemctl status garage.service")
|
|
mastodon.fail("systemctl status mastodon-web.service")
|
|
peertube.fail("systemctl status peertube.service")
|
|
pixelfed.fail("systemctl status phpfpm-pixelfed.service")
|
|
|
|
with subtest("Run deployment with no services enabled"):
|
|
client.succeed("${
|
|
interactWithPanel {
|
|
baseUri = "https://deployer";
|
|
enableMastodon = false;
|
|
enablePeertube = false;
|
|
enablePixelfed = false;
|
|
}
|
|
}/bin/interact-with-panel >&2")
|
|
|
|
with subtest("Check the status of the services - there should still be none"):
|
|
garage.fail("systemctl status garage.service")
|
|
mastodon.fail("systemctl status mastodon-web.service")
|
|
peertube.fail("systemctl status peertube.service")
|
|
pixelfed.fail("systemctl status phpfpm-pixelfed.service")
|
|
|
|
with subtest("Run deployment with Mastodon and Pixelfed enabled"):
|
|
client.succeed("${
|
|
interactWithPanel {
|
|
baseUri = "https://deployer";
|
|
enableMastodon = true;
|
|
enablePeertube = false;
|
|
enablePixelfed = true;
|
|
}
|
|
}/bin/interact-with-panel >&2")
|
|
|
|
with subtest("Check the status of the services - expecting Garage, Mastodon and Pixelfed"):
|
|
garage.succeed("systemctl status garage.service")
|
|
mastodon.succeed("systemctl status mastodon-web.service")
|
|
peertube.fail("systemctl status peertube.service")
|
|
pixelfed.succeed("systemctl status phpfpm-pixelfed.service")
|
|
|
|
with subtest("Run deployment with only Peertube enabled"):
|
|
client.succeed("${
|
|
interactWithPanel {
|
|
baseUri = "https://deployer";
|
|
enableMastodon = false;
|
|
enablePeertube = true;
|
|
enablePixelfed = false;
|
|
}
|
|
}/bin/interact-with-panel >&2")
|
|
|
|
with subtest("Check the status of the services - expecting Garage and Peertube"):
|
|
garage.succeed("systemctl status garage.service")
|
|
mastodon.fail("systemctl status mastodon-web.service")
|
|
peertube.succeed("systemctl status peertube.service")
|
|
pixelfed.fail("systemctl status phpfpm-pixelfed.service")
|
|
'';
|
|
}
|