diff --git a/nixos/modules/services/continuous-integration/gitea-actions-runner.nix b/nixos/modules/services/continuous-integration/gitea-actions-runner.nix index c3edba52433f..30be56f8eeab 100644 --- a/nixos/modules/services/continuous-integration/gitea-actions-runner.nix +++ b/nixos/modules/services/continuous-integration/gitea-actions-runner.nix @@ -203,6 +203,8 @@ in TOKEN = "${instance.token}"; } // optionalAttrs (wantsPodman) { DOCKER_HOST = "unix:///run/podman/podman.sock"; + } // { + HOME = "/var/lib/gitea-runner/${name}"; }; path = with pkgs; [ coreutils diff --git a/nixos/tests/forgejo.nix b/nixos/tests/forgejo.nix index b14df0a2c74f..8b9ee46ff5d3 100644 --- a/nixos/tests/forgejo.nix +++ b/nixos/tests/forgejo.nix @@ -22,8 +22,27 @@ let ''; signingPrivateKeyId = "4D642DE8B678C79D"; + actionsWorkflowYaml = '' + run-name: dummy workflow + on: + push: + jobs: + cat: + runs-on: native + steps: + - uses: http://localhost:3000/test/checkout@main + - run: cat testfile + ''; + # https://github.com/actions/checkout/releases + checkoutActionSource = pkgs.fetchFromGitHub { + owner = "actions"; + repo = "checkout"; + rev = "v4.1.1"; + hash = "sha256-h2/UIp8IjPo3eE4Gzx52Fb7pcgG/Ww7u31w5fdKVMos="; + }; + supportedDbTypes = [ "mysql" "postgres" "sqlite3" ]; - makeGForgejoTest = type: nameValuePair type (makeTest { + makeForgejoTest = type: nameValuePair type (makeTest { name = "forgejo-${type}"; meta.maintainers = with maintainers; [ bendlas emilylange ]; @@ -36,21 +55,28 @@ let settings.service.DISABLE_REGISTRATION = true; settings."repository.signing".SIGNING_KEY = signingPrivateKeyId; settings.actions.ENABLED = true; + settings.repository = { + ENABLE_PUSH_CREATE_USER = true; + DEFAULT_PUSH_CREATE_PRIVATE = false; + }; }; - environment.systemPackages = [ config.services.forgejo.package pkgs.gnupg pkgs.jq pkgs.file ]; + environment.systemPackages = [ config.services.forgejo.package pkgs.gnupg pkgs.jq pkgs.file pkgs.htmlq ]; services.openssh.enable = true; specialisation.runner = { inheritParentConfig = true; - configuration.services.gitea-actions-runner.instances."test" = { - enable = true; - name = "ci"; - url = "http://localhost:3000"; - labels = [ - # don't require docker/podman - "native:host" - ]; - tokenFile = "/var/lib/forgejo/runner_token"; + configuration.services.gitea-actions-runner = { + package = pkgs.forgejo-runner; + instances."test" = { + enable = true; + name = "ci"; + url = "http://localhost:3000"; + labels = [ + # type ":host" does not depend on docker/podman/lxc + "native:host" + ]; + tokenFile = "/var/lib/forgejo/runner_token"; + }; }; }; specialisation.dump = { @@ -62,11 +88,20 @@ let }; }; }; - client1 = { config, pkgs, ... }: { - environment.systemPackages = [ pkgs.git ]; - }; - client2 = { config, pkgs, ... }: { - environment.systemPackages = [ pkgs.git ]; + client = { ... }: { + programs.git = { + enable = true; + config = { + user.email = "test@localhost"; + user.name = "test"; + init.defaultBranch = "main"; + }; + }; + programs.ssh.extraConfig = '' + Host * + StrictHostKeyChecking no + IdentityFile ~/.ssh/privk + ''; }; }; @@ -75,26 +110,23 @@ let inherit (import ./ssh-keys.nix pkgs) snakeOilPrivateKey snakeOilPublicKey; serverSystem = nodes.server.system.build.toplevel; dumpFile = with nodes.server.specialisation.dump.configuration.services.forgejo.dump; "${backupDir}/${file}"; + remoteUri = "forgejo@server:test/repo"; + remoteUriCheckoutAction = "forgejo@server:test/checkout"; in '' import json - GIT_SSH_COMMAND = "ssh -i $HOME/.ssh/privk -o StrictHostKeyChecking=no" - REPO = "forgejo@server:test/repo" - PRIVK = "${snakeOilPrivateKey}" start_all() - client1.succeed("mkdir /tmp/repo") - client1.succeed("mkdir -p $HOME/.ssh") - client1.succeed(f"cat {PRIVK} > $HOME/.ssh/privk") - client1.succeed("chmod 0400 $HOME/.ssh/privk") - client1.succeed("git -C /tmp/repo init") - client1.succeed("echo hello world > /tmp/repo/testfile") - client1.succeed("git -C /tmp/repo add .") - client1.succeed("git config --global user.email test@localhost") - client1.succeed("git config --global user.name test") - client1.succeed("git -C /tmp/repo commit -m 'Initial import'") - client1.succeed(f"git -C /tmp/repo remote add origin {REPO}") + client.succeed("mkdir -p ~/.ssh") + client.succeed("(umask 0077; cat ${snakeOilPrivateKey} > ~/.ssh/privk)") + + client.succeed("mkdir /tmp/repo") + client.succeed("git -C /tmp/repo init") + client.succeed("echo 'hello world' > /tmp/repo/testfile") + client.succeed("git -C /tmp/repo add .") + client.succeed("git -C /tmp/repo commit -m 'Initial import'") + client.succeed("git -C /tmp/repo remote add origin ${remoteUri}") server.wait_for_unit("forgejo.service") server.wait_for_open_port(3000) @@ -143,18 +175,14 @@ let + ' -d \'{"key":"${snakeOilPublicKey}","read_only":true,"title":"SSH"}\''' ) - client1.succeed( - f"GIT_SSH_COMMAND='{GIT_SSH_COMMAND}' git -C /tmp/repo push origin master" - ) + client.succeed("git -C /tmp/repo push origin main") - client2.succeed("mkdir -p $HOME/.ssh") - client2.succeed(f"cat {PRIVK} > $HOME/.ssh/privk") - client2.succeed("chmod 0400 $HOME/.ssh/privk") - client2.succeed(f"GIT_SSH_COMMAND='{GIT_SSH_COMMAND}' git clone {REPO}") - client2.succeed('test "$(cat repo/testfile | xargs echo -n)" = "hello world"') + client.succeed("git clone ${remoteUri} /tmp/repo-clone") + print(client.succeed("ls -lash /tmp/repo-clone")) + assert "hello world" == client.succeed("cat /tmp/repo-clone/testfile").strip() with subtest("Testing git protocol version=2 over ssh"): - git_protocol = client2.succeed(f"GIT_SSH_COMMAND='{GIT_SSH_COMMAND}' GIT_TRACE2_EVENT=true git -C repo fetch |& grep negotiated-version") + git_protocol = client.succeed("GIT_TRACE2_EVENT=true git -C /tmp/repo-clone fetch |& grep negotiated-version") version = json.loads(git_protocol).get("value") assert version == "2", f"git did not negotiate protocol version 2, but version {version} instead." @@ -164,7 +192,7 @@ let timeout=10 ) - with subtest("Testing runner registration"): + with subtest("Testing runner registration and action workflow"): server.succeed( "su -l forgejo -c 'GITEA_WORK_DIR=/var/lib/forgejo gitea actions generate-runner-token' | sed 's/^/TOKEN=/' | tee /var/lib/forgejo/runner_token" ) @@ -172,6 +200,52 @@ let server.wait_for_unit("gitea-runner-test.service") server.succeed("journalctl -o cat -u gitea-runner-test.service | grep -q 'Runner registered successfully'") + # enable actions feature for this repository, defaults to disabled + server.succeed( + "curl --fail -X PATCH http://localhost:3000/api/v1/repos/test/repo " + + "-H 'Accept: application/json' -H 'Content-Type: application/json' " + + f"-H 'Authorization: token {api_token}'" + + ' -d \'{"has_actions":true}\''' + ) + + # mirror "actions/checkout" action + client.succeed("cp -R ${checkoutActionSource}/ /tmp/checkout") + client.succeed("git -C /tmp/checkout init") + client.succeed("git -C /tmp/checkout add .") + client.succeed("git -C /tmp/checkout commit -m 'Initial import'") + client.succeed("git -C /tmp/checkout remote add origin ${remoteUriCheckoutAction}") + client.succeed("git -C /tmp/checkout push origin main") + + # push workflow to initial repo + client.succeed("mkdir -p /tmp/repo/.forgejo/workflows") + client.succeed("cp ${pkgs.writeText "dummy-workflow.yml" actionsWorkflowYaml} /tmp/repo/.forgejo/workflows/") + client.succeed("git -C /tmp/repo add .") + client.succeed("git -C /tmp/repo commit -m 'Add dummy workflow'") + client.succeed("git -C /tmp/repo push origin main") + + def poll_workflow_action_status(_) -> bool: + output = server.succeed( + "curl --fail http://localhost:3000/test/repo/actions | " + + 'htmlq ".flex-item-leading span" --attribute "data-tooltip-content"' + ).strip() + + # values taken from https://codeberg.org/forgejo/forgejo/src/commit/af47c583b4fb3190fa4c4c414500f9941cc02389/options/locale/locale_en-US.ini#L3649-L3661 + if output in [ "Failure", "Canceled", "Skipped", "Blocked" ]: + raise Exception(f"Workflow status is '{output}', which we consider failed.") + server.log(f"Command returned '{output}', which we consider failed.") + + elif output in [ "Unknown", "Waiting", "Running", "" ]: + server.log(f"Workflow status is '{output}'. Waiting some more...") + return False + + elif output in [ "Success" ]: + return True + + raise Exception(f"Workflow status is '{output}', which we don't know. Value mappings likely need updating.") + + with server.nested("Waiting for the workflow run to be successful"): + retry(poll_workflow_action_status) + with subtest("Testing backup service"): server.succeed("${serverSystem}/specialisation/dump/bin/switch-to-configuration test") server.systemctl("start forgejo-dump") @@ -181,4 +255,4 @@ let }); in -listToAttrs (map makeGForgejoTest supportedDbTypes) +listToAttrs (map makeForgejoTest supportedDbTypes) diff --git a/pkgs/by-name/fo/forgejo-runner/package.nix b/pkgs/by-name/fo/forgejo-runner/package.nix index 10914f4d637f..983a19455c4f 100644 --- a/pkgs/by-name/fo/forgejo-runner/package.nix +++ b/pkgs/by-name/fo/forgejo-runner/package.nix @@ -3,6 +3,7 @@ , fetchFromGitea , testers , forgejo-runner +, nixosTests }: buildGoModule rec { @@ -27,9 +28,12 @@ buildGoModule rec { doCheck = false; # Test try to lookup code.forgejo.org. - passthru.tests.version = testers.testVersion { - package = forgejo-runner; - version = src.rev; + passthru.tests = { + inherit (nixosTests.forgejo) sqlite3; + version = testers.testVersion { + package = forgejo-runner; + version = src.rev; + }; }; meta = with lib; {