Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package forgejo-runner for openSUSE:Factory checked in at 2026-03-17 19:05:37 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/forgejo-runner (Old) and /work/SRC/openSUSE:Factory/.forgejo-runner.new.8177 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "forgejo-runner" Tue Mar 17 19:05:37 2026 rev:41 rq:1339565 version:12.7.2 Changes: -------- --- /work/SRC/openSUSE:Factory/forgejo-runner/forgejo-runner.changes 2026-02-23 17:04:22.966832352 +0100 +++ /work/SRC/openSUSE:Factory/.forgejo-runner.new.8177/forgejo-runner.changes 2026-03-17 19:07:32.472239766 +0100 @@ -1,0 +2,10 @@ +Tue Mar 17 10:44:55 UTC 2026 - Richard Rahl <[email protected]> + +- Update to version 12.7.2: + * fix: only ping Forgejo during offline registration when --connect is enabled + * fix: URL comparison when determining token auth support +- Update to version 12.7.1: + * fix: implement idempotent FetchTask API calls to reduce risk of lost tasks + * fix: accept boolean workflow_call inputs that are booleans + +------------------------------------------------------------------- Old: ---- forgejo-runner-12.7.0.obscpio New: ---- forgejo-runner-12.7.2.obscpio ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ forgejo-runner.spec ++++++ --- /var/tmp/diff_new_pack.QJKNVP/_old 2026-03-17 19:07:33.244271760 +0100 +++ /var/tmp/diff_new_pack.QJKNVP/_new 2026-03-17 19:07:33.248271925 +0100 @@ -18,7 +18,7 @@ %define services %{name}.service Name: forgejo-runner -Version: 12.7.0 +Version: 12.7.2 Release: 0 Summary: Daemon that connects to a Forgejo instance and runs CI jobs License: GPL-3.0-or-later ++++++ _service ++++++ --- /var/tmp/diff_new_pack.QJKNVP/_old 2026-03-17 19:07:33.284273417 +0100 +++ /var/tmp/diff_new_pack.QJKNVP/_new 2026-03-17 19:07:33.288273584 +0100 @@ -2,7 +2,7 @@ <service name="obs_scm" mode="manual"> <param name="url">https://code.forgejo.org/forgejo/runner</param> <param name="scm">git</param> - <param name="revision">refs/tags/v12.7.0</param> + <param name="revision">refs/tags/v12.7.2</param> <param name="versionformat">@PARENT_TAG@</param> <param name="changesgenerate">disable</param> <param name="versionrewrite-pattern">v(.*)</param> ++++++ forgejo-runner-12.7.0.obscpio -> forgejo-runner-12.7.2.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/forgejo-runner-12.7.0/.forgejo/workflows/build-release-integration.yml new/forgejo-runner-12.7.2/.forgejo/workflows/build-release-integration.yml --- old/forgejo-runner-12.7.0/.forgejo/workflows/build-release-integration.yml 2026-02-19 02:28:46.000000000 +0100 +++ new/forgejo-runner-12.7.2/.forgejo/workflows/build-release-integration.yml 2026-03-09 17:04:00.000000000 +0100 @@ -29,7 +29,7 @@ - uses: https://data.forgejo.org/actions/checkout@v4 - id: forgejo - uses: https://data.forgejo.org/actions/[email protected] + uses: https://data.forgejo.org/actions/[email protected] with: user: root password: admin1234 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/forgejo-runner-12.7.0/.forgejo/workflows/docker-build-push-action-in-lxc.yml new/forgejo-runner-12.7.2/.forgejo/workflows/docker-build-push-action-in-lxc.yml --- old/forgejo-runner-12.7.0/.forgejo/workflows/docker-build-push-action-in-lxc.yml 2026-02-19 02:28:46.000000000 +0100 +++ new/forgejo-runner-12.7.2/.forgejo/workflows/docker-build-push-action-in-lxc.yml 2026-03-09 17:04:00.000000000 +0100 @@ -34,7 +34,7 @@ - name: install Forgejo so it can be used as a container registry id: registry - uses: https://data.forgejo.org/actions/[email protected] + uses: https://data.forgejo.org/actions/[email protected] with: user: ${{ env.FORGEJO_USER }} password: ${{ env.FORGEJO_PASSWORD }} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/forgejo-runner-12.7.0/.forgejo/workflows/example-lxc-systemd.yml new/forgejo-runner-12.7.2/.forgejo/workflows/example-lxc-systemd.yml --- old/forgejo-runner-12.7.0/.forgejo/workflows/example-lxc-systemd.yml 2026-02-19 02:28:46.000000000 +0100 +++ new/forgejo-runner-12.7.2/.forgejo/workflows/example-lxc-systemd.yml 2026-03-09 17:04:00.000000000 +0100 @@ -54,7 +54,7 @@ done - id: forgejo - uses: https://data.forgejo.org/actions/[email protected] + uses: https://data.forgejo.org/actions/[email protected] with: user: root password: admin1234 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/forgejo-runner-12.7.0/.forgejo/workflows/example-on-demand.yml new/forgejo-runner-12.7.2/.forgejo/workflows/example-on-demand.yml --- old/forgejo-runner-12.7.0/.forgejo/workflows/example-on-demand.yml 2026-02-19 02:28:46.000000000 +0100 +++ new/forgejo-runner-12.7.2/.forgejo/workflows/example-on-demand.yml 2026-03-09 17:04:00.000000000 +0100 @@ -29,7 +29,7 @@ - uses: https://data.forgejo.org/actions/checkout@v5 - - uses: https://data.forgejo.org/actions/[email protected] + - uses: https://data.forgejo.org/actions/[email protected] with: user: "${{ env.username }}" password: "${{ env.password }}" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/forgejo-runner-12.7.0/.forgejo/workflows/release-notes-assistant.yml new/forgejo-runner-12.7.2/.forgejo/workflows/release-notes-assistant.yml --- old/forgejo-runner-12.7.0/.forgejo/workflows/release-notes-assistant.yml 2026-02-19 02:28:46.000000000 +0100 +++ new/forgejo-runner-12.7.2/.forgejo/workflows/release-notes-assistant.yml 2026-03-09 17:04:00.000000000 +0100 @@ -12,7 +12,7 @@ - labeled env: - RNA_VERSION: v1.5.2 # renovate: datasource=forgejo-releases depName=forgejo/release-notes-assistant + RNA_VERSION: v1.6.1 # renovate: datasource=forgejo-releases depName=forgejo/release-notes-assistant jobs: release-notes: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/forgejo-runner-12.7.0/Makefile new/forgejo-runner-12.7.2/Makefile --- old/forgejo-runner-12.7.0/Makefile 2026-02-19 02:28:46.000000000 +0100 +++ new/forgejo-runner-12.7.2/Makefile 2026-03-09 17:04:00.000000000 +0100 @@ -13,8 +13,8 @@ GO_FMT_FILES := $(shell find . -type f -name "*.go" ! -name "generated.*") GOFILES := $(shell find . -type f -name "*.go" -o -name "go.mod" ! -name "generated.*") -MOCKERY_PACKAGE ?= github.com/vektra/mockery/[email protected] # renovate: datasource=go -GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/[email protected] # renovate: datasource=go +MOCKERY_PACKAGE ?= github.com/vektra/mockery/[email protected] # renovate: datasource=go +GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/[email protected] # renovate: datasource=go DOCKER_IMAGE ?= gitea/act_runner DOCKER_TAG ?= nightly diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/forgejo-runner-12.7.0/act/runner/expression.go new/forgejo-runner-12.7.2/act/runner/expression.go --- old/forgejo-runner-12.7.0/act/runner/expression.go 2026-02-19 02:28:46.000000000 +0100 +++ new/forgejo-runner-12.7.2/act/runner/expression.go 2026-03-09 17:04:00.000000000 +0100 @@ -458,7 +458,7 @@ _ = v.Default.Decode(&value) } if v.Type == "boolean" { - inputs[k] = value == "true" + inputs[k] = value == true || value == "true" } else { inputs[k] = value } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/forgejo-runner-12.7.0/act/runner/runner_test.go new/forgejo-runner-12.7.2/act/runner/runner_test.go --- old/forgejo-runner-12.7.0/act/runner/runner_test.go 2026-02-19 02:28:46.000000000 +0100 +++ new/forgejo-runner-12.7.2/act/runner/runner_test.go 2026-03-09 17:04:00.000000000 +0100 @@ -270,7 +270,7 @@ {workdir, "uses-composite-check-for-input-shadowing", "push", "", platforms, secrets}, {workdir, "uses-nested-composite", "push", "", platforms, secrets}, {workdir, "uses-composite-check-for-input-in-if-uses", "push", "", platforms, secrets}, - // {workdir, "remote-action-composite-js-pre-with-defaults", "push", "", platforms, secrets}, + {workdir, "remote-action-composite-js-pre-with-defaults", "push", "", platforms, secrets}, {workdir, "remote-action-composite-action-ref", "push", "", platforms, secrets}, {workdir, "uses-workflow-local", "push", "", platforms, map[string]string{"secret": "keep_it_private"}}, {workdir, "uses-workflow-remote-absolute", "push", "", platforms, map[string]string{"secret": "keep_it_private"}}, @@ -294,7 +294,7 @@ {workdir, "checkout", "push", "", platforms, secrets}, {workdir, "remote-action-docker", "push", "", platforms, secrets}, {workdir, "remote-action-js", "push", "", platforms, secrets}, - // {workdir, "remote-action-js-node-user", "push", "", platforms, secrets}, // Test if this works with non root container + {workdir, "remote-action-js-node-user", "push", "", platforms, secrets}, {workdir, "matrix", "push", "", platforms, secrets}, {workdir, "matrix-include-exclude", "push", "", platforms, secrets}, {workdir, "matrix-exitcode", "push", "Job 'test' failed", platforms, secrets}, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/forgejo-runner-12.7.0/act/runner/step_action_remote.go new/forgejo-runner-12.7.2/act/runner/step_action_remote.go --- old/forgejo-runner-12.7.0/act/runner/step_action_remote.go 2026-02-19 02:28:46.000000000 +0100 +++ new/forgejo-runner-12.7.2/act/runner/step_action_remote.go 2026-03-09 17:04:00.000000000 +0100 @@ -32,7 +32,7 @@ var stepActionRemoteGitClone = git.Clone -var protoRegex = regexp.MustCompile("^https?://") +var schemePattern = regexp.MustCompile("^https?://") func (sar *stepActionRemote) prepareActionExecutor() common.Executor { return func(ctx context.Context) error { @@ -71,11 +71,16 @@ if err != nil { common.Logger(ctx).Warnf("failed to determine token auth support from server version '%s': %w", sar.RunContext.Config.ServerVersion, err) } + common.Logger(ctx).Debugf("Authentication with token supported: %v", tokenSupported) // If we're pulling the action from the instance itself, set the auth token - if tokenSupported && (sar.remoteAction.URL == "" && sar.RunContext.Config.DefaultActionInstance == sar.RunContext.Config.GitHubInstance) || (protoRegex.ReplaceAllString(sar.remoteAction.URL, "") == sar.RunContext.Config.GitHubInstance) { + if tokenSupported && actionHostedOnSameInstanceAsWorkflow(sar.remoteAction.URL, sar.RunContext.Config.GitHubInstance, sar.RunContext.Config.DefaultActionInstance) { token = github.Token + } else { + common.Logger(ctx).Debugf("Authentication with token disabled for action URL %q, origin URL %q, and default URL %q", + sar.remoteAction.URL, sar.RunContext.Config.GitHubInstance, sar.RunContext.Config.DefaultActionInstance) } + wt, err := stepActionRemoteGitClone(ctx, git.CloneInput{ CacheDir: sar.RunContext.ActionCacheDir(), URL: sar.remoteAction.CloneURL(sar.RunContext.Config.DefaultActionInstance), @@ -332,3 +337,35 @@ return c.Check(v), nil } + +// actionHostedOnSameInstanceAsWorkflow tests whether an action is hosted on the same instance as the workflow that is +// being run. Returns `true` if both URLs match (including scheme, ports, and paths), `false` otherwise. +func actionHostedOnSameInstanceAsWorkflow(actionURL, originInstance, defaultActionsInstance string) bool { + // Remove trailing slashes if present. It preserves the meaning of the URLs while removing a source of configuration + // problems. + actionURL = strings.TrimSuffix(actionURL, "/") + originInstance = strings.TrimSuffix(originInstance, "/") + defaultActionsInstance = strings.TrimSuffix(defaultActionsInstance, "/") + + isHostname := func(s string) bool { + return !strings.HasPrefix(s, "http://") && !strings.HasPrefix(s, "https://") + } + + // Handle an action reference with an empty URL like `uses: actions/checkout`. + if actionURL == "" { + if isHostname(originInstance) || isHostname(defaultActionsInstance) { + originInstance = schemePattern.ReplaceAllString(originInstance, "") + defaultActionsInstance = schemePattern.ReplaceAllString(defaultActionsInstance, "") + } + return originInstance == defaultActionsInstance + } + + // Test if an action loaded from an arbitrary URL (like `uses: https://forge.example.com/actions/checkout`) comes + // from the same instance as the workflow that is being run. Because Forgejo can be hosted on paths or use + // arbitrary ports, looking at the hostname is not sufficient. Both URLs have to be equal. + if isHostname(originInstance) || isHostname(actionURL) { + originInstance = schemePattern.ReplaceAllString(originInstance, "") + actionURL = schemePattern.ReplaceAllString(actionURL, "") + } + return originInstance == actionURL +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/forgejo-runner-12.7.0/act/runner/step_action_remote_test.go new/forgejo-runner-12.7.2/act/runner/step_action_remote_test.go --- old/forgejo-runner-12.7.0/act/runner/step_action_remote_test.go 2026-02-19 02:28:46.000000000 +0100 +++ new/forgejo-runner-12.7.2/act/runner/step_action_remote_test.go 2026-03-09 17:04:00.000000000 +0100 @@ -648,7 +648,7 @@ expectedToken string }{ { - name: "Token set when instances match and default actions URL is used", + name: "Token set when instance hostnames match and default actions URL is used", defaultActionInstance: "forgejo.my-instance.com", githubInstance: "forgejo.my-instance.com", serverVersion: "14.0.0", @@ -657,6 +657,51 @@ expectedToken: "test-token", }, { + name: "Token set when instance hostnames match and default actions URL is a hostname", + defaultActionInstance: "forgejo.my-instance.com", + githubInstance: "https://forgejo.my-instance.com", + serverVersion: "14.0.0", + remoteURL: "", + token: "test-token", + expectedToken: "test-token", + }, + { + name: "Token empty when instance hostnames match but port not", + defaultActionInstance: "forgejo.my-instance.com:8080", + githubInstance: "forgejo.my-instance.com", + serverVersion: "14.0.0", + remoteURL: "", + token: "test-token", + expectedToken: "", + }, + { + name: "Token set when instance URLs match and default actions URL is used", + defaultActionInstance: "https://forgejo.my-instance.com/", + githubInstance: "https://forgejo.my-instance.com", + serverVersion: "14.0.0", + remoteURL: "", + token: "test-token", + expectedToken: "test-token", + }, + { + name: "Token empty when scheme of instance URL and default actions URL do not match", + defaultActionInstance: "https://forgejo.my-instance.com/", + githubInstance: "http://forgejo.my-instance.com", + serverVersion: "14.0.0", + remoteURL: "", + token: "test-token", + expectedToken: "", + }, + { + name: "Token empty when port of instance URL and default actions URL do not match", + defaultActionInstance: "https://forgejo.my-instance.com:8080/", + githubInstance: "https://forgejo.my-instance.com", + serverVersion: "14.0.0", + remoteURL: "", + token: "test-token", + expectedToken: "", + }, + { name: "Token empty when instances differ and default actions URL is used", defaultActionInstance: "forgejo.my-instance.com", githubInstance: "forgejo.other-instance.com", @@ -675,7 +720,7 @@ expectedToken: "", }, { - name: "Token set when explicit URL is provided and it matches the instance URL", + name: "Token set when explicit URL is provided and it matches the instance hostname", defaultActionInstance: "forgejo.my-instance.com", githubInstance: "forgejo.my-instance.com", serverVersion: "14.0.0", @@ -683,6 +728,15 @@ token: "test-token", expectedToken: "test-token", }, + { + name: "Token set when explicit URL is provided and it matches the instance URL", + defaultActionInstance: "https://example.com/", + githubInstance: "https://forgejo.my-instance.com:8080/", + serverVersion: "14.0.0", + remoteURL: "https://forgejo.my-instance.com:8080", + token: "test-token", + expectedToken: "test-token", + }, { name: "Token empty when server version < 13.0.0", defaultActionInstance: "forgejo.my-instance.com", diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/forgejo-runner-12.7.0/act/runner/testdata/remote-action-js-node-user/push.yml new/forgejo-runner-12.7.2/act/runner/testdata/remote-action-js-node-user/push.yml --- old/forgejo-runner-12.7.0/act/runner/testdata/remote-action-js-node-user/push.yml 2026-02-19 02:28:46.000000000 +0100 +++ new/forgejo-runner-12.7.2/act/runner/testdata/remote-action-js-node-user/push.yml 2026-03-09 17:04:00.000000000 +0100 @@ -27,4 +27,4 @@ with: who-to-greet: 'Mona the Octocat' - - uses: cloudposse/actions/github/[email protected] + - uses: https://github.com/cloudposse/actions/github/[email protected] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/forgejo-runner-12.7.0/act/runner/testdata/workflow_call_inputs/workflow_call_inputs.yml new/forgejo-runner-12.7.2/act/runner/testdata/workflow_call_inputs/workflow_call_inputs.yml --- old/forgejo-runner-12.7.0/act/runner/testdata/workflow_call_inputs/workflow_call_inputs.yml 2026-02-19 02:28:46.000000000 +0100 +++ new/forgejo-runner-12.7.2/act/runner/testdata/workflow_call_inputs/workflow_call_inputs.yml 2026-03-09 17:04:00.000000000 +0100 @@ -14,6 +14,10 @@ description: an input of type boolean required: false type: boolean + boolean_with_default: + description: an input of type boolean with a default value + default: true + type: boolean jobs: test: @@ -21,11 +25,11 @@ steps: - name: test required input run: | - echo input.required=${{ inputs.required }} + echo inputs.required=${{ inputs.required }} [[ "${{ inputs.required }}" = "required input" ]] || exit 1 - name: test input with default run: | - echo input.with_default=${{ inputs.with_default }} + echo inputs.with_default=${{ inputs.with_default }} [[ "${{ inputs.with_default }}" = "default" ]] || exit 1 - id: boolean-test name: run on boolean input @@ -34,3 +38,7 @@ - name: has boolean test? run: | [[ "${{ steps.boolean-test.outputs.value }}" = "executed" ]] || exit 1 + - name: test boolean with default input + run: | + echo "inputs.boolean_with_default=${{ inputs.boolean_with_default }}" + [[ "${{ inputs.boolean_with_default }}" == "true" ]] || exit 1 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/forgejo-runner-12.7.0/act/workflowpattern/workflow_pattern.go new/forgejo-runner-12.7.2/act/workflowpattern/workflow_pattern.go --- old/forgejo-runner-12.7.0/act/workflowpattern/workflow_pattern.go 2026-02-19 02:28:46.000000000 +0100 +++ new/forgejo-runner-12.7.2/act/workflowpattern/workflow_pattern.go 2026-03-09 17:04:00.000000000 +0100 @@ -127,7 +127,7 @@ if errorMessage.Len() > 0 { errorMessage.WriteString(", ") } - errorMessage.WriteString(fmt.Sprintf("Position: %d Error: %s", position, err)) + fmt.Fprintf(&errorMessage, "Position: %d Error: %s", position, err) } return "", fmt.Errorf("invalid Pattern '%s': %s", pattern, errorMessage.String()) } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/forgejo-runner-12.7.0/examples/docker-compose/compose-forgejo-and-runner.yml new/forgejo-runner-12.7.2/examples/docker-compose/compose-forgejo-and-runner.yml --- old/forgejo-runner-12.7.0/examples/docker-compose/compose-forgejo-and-runner.yml 2026-02-19 02:28:46.000000000 +0100 +++ new/forgejo-runner-12.7.2/examples/docker-compose/compose-forgejo-and-runner.yml 2026-03-09 17:04:00.000000000 +0100 @@ -51,7 +51,7 @@ - 8080:3000 runner-register: - image: data.forgejo.org/forgejo/runner:12.6.4 + image: data.forgejo.org/forgejo/runner:12.7.1 links: - docker-in-docker - forgejo @@ -66,7 +66,7 @@ forgejo-runner create-runner-file --connect --instance http://forgejo:3000 --name runner --secret {SHARED_SECRET} && break ; sleep 1 ; done ; - sed -i -e "s|\"labels\": null|\"labels\": [\"docker-cli:docker://code.forgejo.org/oci/docker:cli\",\"node-bookworm:docker://code.forgejo.org/oci/node:20-bookworm\"]|" .runner ; + sed -i -e "s|\"labels\": \[\]|\"labels\": [\"docker-cli:docker://code.forgejo.org/oci/docker:cli\",\"node-bookworm:docker://code.forgejo.org/oci/node:20-bookworm\"]|" .runner ; forgejo-runner generate-config > config.yml ; sed -i -e "s| level: info| level: debug|" config.yml ; sed -i -e "s|network: .*|network: host|" config.yml ; @@ -77,7 +77,7 @@ ' runner-daemon: - image: data.forgejo.org/forgejo/runner:12.6.4 + image: data.forgejo.org/forgejo/runner:12.7.1 links: - docker-in-docker - forgejo diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/forgejo-runner-12.7.0/examples/lxc-systemd/forgejo-runner-service.sh new/forgejo-runner-12.7.2/examples/lxc-systemd/forgejo-runner-service.sh --- old/forgejo-runner-12.7.0/examples/lxc-systemd/forgejo-runner-service.sh 2026-02-19 02:28:46.000000000 +0100 +++ new/forgejo-runner-12.7.2/examples/lxc-systemd/forgejo-runner-service.sh 2026-03-09 17:04:00.000000000 +0100 @@ -22,13 +22,13 @@ : ${INPUTS_LIFETIME:=7d} DEFAULT_LXC_HELPERS_VERSION=1.1.3 # renovate: datasource=forgejo-tags depName=forgejo/lxc-helpers : ${INPUTS_LXC_HELPERS_VERSION:=$DEFAULT_LXC_HELPERS_VERSION} -DEFAULT_RUNNER_VERSION=12.6.4 # renovate: datasource=forgejo-releases depName=forgejo/runner +DEFAULT_RUNNER_VERSION=12.7.1 # renovate: datasource=forgejo-releases depName=forgejo/runner : ${INPUTS_RUNNER_VERSION:=$DEFAULT_RUNNER_VERSION} : ${KILL_AFTER:=21600} # 6h == 21600 NODEJS_VERSION=24 # renovate: datasource=node-version depName=forgejo-runner-service-node DEBIAN_RELEASE=trixie # renovate: datasource=docker depName=forgejo-runner-service-debian packageName=code.forgejo.org/oci/debian versioning=debian -YQ_VERSION=v4.52.2 # renovate: datasource=github-releases depName=forgejo-runner-service-yq packageName=mikefarah/yq +YQ_VERSION=v4.52.4 # renovate: datasource=github-releases depName=forgejo-runner-service-yq packageName=mikefarah/yq SELF=${BASH_SOURCE[0]} SELF_FILENAME=$(basename "$SELF") SELF_INSTALLED=/usr/local/bin/$SELF_FILENAME diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/forgejo-runner-12.7.0/go.mod new/forgejo-runner-12.7.2/go.mod --- old/forgejo-runner-12.7.0/go.mod 2026-02-19 02:28:46.000000000 +0100 +++ new/forgejo-runner-12.7.2/go.mod 2026-03-09 17:04:00.000000000 +0100 @@ -2,7 +2,7 @@ go 1.25.0 -toolchain go1.25.7 +toolchain go1.25.8 require ( code.forgejo.org/forgejo/actions-proto v0.6.0 @@ -18,8 +18,8 @@ github.com/docker/cli v28.5.2+incompatible github.com/docker/docker v28.5.2+incompatible github.com/docker/go-connections v0.6.0 - github.com/go-git/go-billy/v5 v5.6.2 - github.com/go-git/go-git/v5 v5.16.5 + github.com/go-git/go-billy/v5 v5.8.0 + github.com/go-git/go-git/v5 v5.17.0 github.com/gobwas/glob v0.2.3 github.com/google/go-cmp v0.7.0 github.com/google/uuid v1.6.0 @@ -39,9 +39,9 @@ github.com/timshannon/bolthold v0.0.0-20240314194003-30aac6950928 go.etcd.io/bbolt v1.4.3 go.yaml.in/yaml/v3 v3.0.4 - golang.org/x/sys v0.41.0 + golang.org/x/sys v0.42.0 golang.org/x/term v0.40.0 - golang.org/x/time v0.14.0 + golang.org/x/time v0.15.0 google.golang.org/protobuf v1.36.11 gotest.tools/v3 v3.5.2 ) @@ -52,7 +52,8 @@ github.com/Microsoft/go-winio v0.6.2 // indirect github.com/ProtonMail/go-crypto v1.1.6 // indirect github.com/bmatcuk/doublestar/v4 v4.9.1 // indirect - github.com/cloudflare/circl v1.6.1 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/cloudflare/circl v1.6.3 // indirect github.com/containerd/errdefs/pkg v0.3.0 // indirect github.com/containerd/log v0.1.0 // indirect github.com/cyphar/filepath-securejoin v0.6.0 // indirect @@ -63,7 +64,7 @@ github.com/fatih/color v1.18.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect - github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-viper/mapstructure/v2 v2.4.0 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect @@ -95,13 +96,13 @@ github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonschema v1.2.0 // indirect - go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 // indirect - go.opentelemetry.io/otel v1.36.0 // indirect + go.opentelemetry.io/otel v1.40.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0 // indirect - go.opentelemetry.io/otel/metric v1.36.0 // indirect - go.opentelemetry.io/otel/sdk v1.21.0 // indirect - go.opentelemetry.io/otel/trace v1.36.0 // indirect + go.opentelemetry.io/otel/metric v1.40.0 // indirect + go.opentelemetry.io/otel/sdk v1.40.0 // indirect + go.opentelemetry.io/otel/trace v1.40.0 // indirect go.yaml.in/yaml/v4 v4.0.0-rc.3 // indirect golang.org/x/crypto v0.45.0 // indirect golang.org/x/net v0.47.0 // indirect diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/forgejo-runner-12.7.0/go.sum new/forgejo-runner-12.7.2/go.sum --- old/forgejo-runner-12.7.0/go.sum 2026-02-19 02:28:46.000000000 +0100 +++ new/forgejo-runner-12.7.2/go.sum 2026-03-09 17:04:00.000000000 +0100 @@ -27,8 +27,10 @@ github.com/bmatcuk/doublestar/v4 v4.9.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= -github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= -github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8= +github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= @@ -72,15 +74,15 @@ github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= -github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM= -github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU= +github.com/go-git/go-billy/v5 v5.8.0 h1:I8hjc3LbBlXTtVuFNJuwYuMiHvQJDq1AT6u4DwDzZG0= +github.com/go-git/go-billy/v5 v5.8.0/go.mod h1:RpvI/rw4Vr5QA+Z60c6d6LXH0rYJo0uD5SqfmrrheCY= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= -github.com/go-git/go-git/v5 v5.16.5 h1:mdkuqblwr57kVfXri5TTH+nMFLNUxIj9Z7F5ykFbw5s= -github.com/go-git/go-git/v5 v5.16.5/go.mod h1:QOMLpNf1qxuSY4StA/ArOdfFR2TrKEjJiye2kel2m+M= +github.com/go-git/go-git/v5 v5.17.0 h1:AbyI4xf+7DsjINHMu35quAh4wJygKBKBuXVjV/pxesM= +github.com/go-git/go-git/v5 v5.17.0/go.mod h1:f82C4YiLx+Lhi8eHxltLeGC5uBTXSFa6PC5WW9o4SjI= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= @@ -208,22 +210,22 @@ go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo= go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E= go.etcd.io/gofail v0.1.0/go.mod h1:VZBCXYGZhHAinaBiiqYvuDynvahNsAyLFwB3kEHKz1M= -go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= -go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 h1:aFJWCqJMNjENlcleuuOkGAPH82y0yULBScfXcIEdS24= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1/go.mod h1:sEGXWArGqc3tVa+ekntsN65DmVbVeW+7lTKTjZF3/Fo= -go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= -go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= +go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms= +go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 h1:cl5P5/GIfFh4t6xyruOgJP5QiA1pw4fYYdv6nc6CBWw= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0/go.mod h1:zgBdWWAu7oEEMC06MMKc5NLbA/1YDXV1sMpSqEeLQLg= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0 h1:digkEZCJWobwBqMwC0cwCq8/wkkRy/OowZg5OArWZrM= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0/go.mod h1:/OpE/y70qVkndM0TrxT4KBoN3RsFZP0QaofcfYrj76I= -go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE= -go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs= -go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZXQ8= -go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E= -go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w= -go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= +go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g= +go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc= +go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8= +go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE= +go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw= +go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA= go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= @@ -250,16 +252,16 @@ golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= -golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= +golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg= golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= -golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= -golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= +golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= +golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 h1:wpZ8pe2x1Q3f2KyT5f8oP/fa9rHAKgFPr/HZdNuS+PQ= google.golang.org/genproto/googleapis/api v0.0.0-20231120223509-83a465c0220f h1:2yNACc1O40tTnrsbk9Cv6oxiW8pxI/pXj0wRtdlYmgY= diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/forgejo-runner-12.7.0/internal/app/cmd/create-runner-file.go new/forgejo-runner-12.7.2/internal/app/cmd/create-runner-file.go --- old/forgejo-runner-12.7.0/internal/app/cmd/create-runner-file.go 2026-02-19 02:28:46.000000000 +0100 +++ new/forgejo-runner-12.7.2/internal/app/cmd/create-runner-file.go 2026-03-09 17:04:00.000000000 +0100 @@ -127,8 +127,10 @@ // // Verify the Forgejo instance is reachable // - if err := ping(cfg, reg); err != nil { - return err + if args.Connect { + if err := ping(cfg, reg); err != nil { + return err + } } // diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/forgejo-runner-12.7.0/internal/app/cmd/create-runner-file_test.go new/forgejo-runner-12.7.2/internal/app/cmd/create-runner-file_test.go --- old/forgejo-runner-12.7.0/internal/app/cmd/create-runner-file_test.go 2026-02-19 02:28:46.000000000 +0100 +++ new/forgejo-runner-12.7.2/internal/app/cmd/create-runner-file_test.go 2026-03-09 17:04:00.000000000 +0100 @@ -13,6 +13,7 @@ "code.forgejo.org/forgejo/runner/v12/internal/pkg/config" "code.forgejo.org/forgejo/runner/v12/internal/pkg/ver" "connectrpc.com/connect" + "github.com/stretchr/testify/require" "github.com/stretchr/testify/assert" "go.yaml.in/yaml/v3" @@ -61,6 +62,29 @@ assert.NoError(t, ping(cfg, reg)) } +func TestCreateRunnerFile_OfflineRunnerFileCreation(t *testing.T) { + configFile, configuration, err := prepareConfig(t.TempDir()) + require.NoError(t, err) + + secret := "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + instance := "https://example.com/" + name := "offline-runner" + + cmd := createRunnerFileCmd(t.Context(), &configFile) + output, _, _, err := executeCommand(t.Context(), t, cmd, "--secret", secret, "--instance", instance, "--name", name) + + require.NoError(t, err) + assert.Empty(t, output) + + reg, err := config.LoadRegistration(configuration.Runner.File) + require.NoError(t, err) + assert.Zero(t, reg.ID) + assert.Equal(t, "41414141-4141-4141-4141-414141414141", reg.UUID) + assert.Equal(t, secret, reg.Token) + assert.Equal(t, instance, reg.Address) + assert.Equal(t, name, reg.Name) +} + func Test_runCreateRunnerFile(t *testing.T) { if testing.Short() { t.Skip("skipping integration test") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/forgejo-runner-12.7.0/internal/app/job/job.go new/forgejo-runner-12.7.2/internal/app/job/job.go --- old/forgejo-runner-12.7.0/internal/app/job/job.go 2026-02-19 02:28:46.000000000 +0100 +++ new/forgejo-runner-12.7.2/internal/app/job/job.go 2026-03-09 17:04:00.000000000 +0100 @@ -49,21 +49,7 @@ if !ok { return fmt.Errorf("could not fetch task") } - return j.runTaskWithRecover(ctx, task) -} - -func (j *Job) runTaskWithRecover(ctx context.Context, task *runnerv1.Task) error { - defer func() { - if r := recover(); r != nil { - err := fmt.Errorf("panic: %v", r) - log.WithError(err).Error("panic in runTaskWithRecover") - } - }() - - if err := j.runner.Run(ctx, task); err != nil { - log.WithError(err).Error("failed to run task") - return err - } + j.runner.Run(ctx, task) return nil } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/forgejo-runner-12.7.0/internal/app/job/job_test.go new/forgejo-runner-12.7.2/internal/app/job/job_test.go --- old/forgejo-runner-12.7.0/internal/app/job/job_test.go 2026-02-19 02:28:46.000000000 +0100 +++ new/forgejo-runner-12.7.2/internal/app/job/job_test.go 2026-03-09 17:04:00.000000000 +0100 @@ -17,6 +17,7 @@ "code.forgejo.org/forgejo/runner/v12/internal/pkg/client" "code.forgejo.org/forgejo/runner/v12/internal/pkg/config" + gouuid "github.com/google/uuid" log "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" ) @@ -43,6 +44,10 @@ return time.Second } +func (o mockClient) SetRequestKey(requestKey gouuid.UUID) func() { + return func() {} +} + func (o *mockClient) FetchTask(ctx context.Context, _ *connect.Request[runnerv1.FetchTaskRequest]) (*connect.Response[runnerv1.FetchTaskResponse], error) { if o.sleep > 0 { select { @@ -73,38 +78,19 @@ } type mockRunner struct { - cfg *config.Runner - log chan string - panics bool - err error + cfg *config.Runner + log chan string } -func (o *mockRunner) Run(ctx context.Context, _ *runnerv1.Task) error { +func (o *mockRunner) Run(ctx context.Context, _ *runnerv1.Task) { o.log <- "runner starts" - if o.panics { - log.Trace("panics") - o.log <- "runner panics" - o.panics = false - panic("whatever") - } - if o.err != nil { - log.Trace("error") - o.log <- "runner error" - err := o.err - o.err = nil - return err - } - for { - select { - case <-ctx.Done(): - log.Trace("shutdown") - o.log <- "runner shutdown" - return nil - case <-time.After(o.cfg.Timeout): - log.Trace("after") - o.log <- "runner timeout" - return nil - } + select { + case <-ctx.Done(): + log.Trace("shutdown") + o.log <- "runner shutdown" + case <-time.After(o.cfg.Timeout): + log.Trace("after") + o.log <- "runner timeout" } } @@ -202,8 +188,6 @@ name string noTask bool clientErr error - runnerErr error - runnerPanics bool expectError bool errorContains string }{ @@ -223,16 +207,6 @@ expectError: true, errorContains: "could not fetch task", }, - { - name: "Runner error", - runnerErr: fmt.Errorf("runner failed"), - expectError: true, - }, - { - name: "Runner panics", - runnerPanics: true, - expectError: false, - }, } { t.Run(testCase.name, func(t *testing.T) { logChan := make(chan string, 10) @@ -250,10 +224,8 @@ err: testCase.clientErr, }, &mockRunner{ - cfg: &configRunner, - log: logChan, - panics: testCase.runnerPanics, - err: testCase.runnerErr, + cfg: &configRunner, + log: logChan, }, ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/forgejo-runner-12.7.0/internal/app/poll/poller.go new/forgejo-runner-12.7.2/internal/app/poll/poller.go --- old/forgejo-runner-12.7.0/internal/app/poll/poller.go 2026-02-19 02:28:46.000000000 +0100 +++ new/forgejo-runner-12.7.2/internal/app/poll/poller.go 2026-03-09 17:04:00.000000000 +0100 @@ -6,7 +6,6 @@ import ( "context" "errors" - "fmt" "sync" "sync/atomic" @@ -18,6 +17,7 @@ "code.forgejo.org/forgejo/runner/v12/internal/app/run" "code.forgejo.org/forgejo/runner/v12/internal/pkg/client" "code.forgejo.org/forgejo/runner/v12/internal/pkg/config" + gouuid "github.com/google/uuid" ) const PollerID = "PollerID" @@ -115,6 +115,22 @@ return } + // When the FetchTask() API is invoked to create a task, unpreventable environmental errors may occur; for example, + // network disconnects and timeouts. It's possible that these errors occur after the server-side has assigned a task + // to the runner during the API call, in which case the error would cause that task to be lost between the two + // systems -- the server will think it's assigned to the runner, and the runner never received it. + // + // The solution implemented here is idempotency in the FetchTask() API call, which means that the "same" FetchTask() + // API call is expected to return the same values. Specifically, the runner creates a unique identifier `requestKey` + // which is transmitted to the server along with each FetchTask() invocation which defines the sameness of the call, + // and the runner retains the `requestKey` value until the API call receives a successful response. If the server + // implements idempotency, it can use this key to identify repeated invocations of FetchTask() and when the same + // request is received, the same response is provided. + // + // Runner's responsibility is to send the same request key consistently if any error occurred, and, change it to a + // new key when a successful call is received. + requestKey := gouuid.New() + for { if err := limiter.Wait(p.pollingCtx); err != nil { log.Infof("[poller] shutdown begin, %d tasks currently running", inProgressTasks.Load()) @@ -125,20 +141,24 @@ availableCapacity := capacity - inProgressTasks.Load() if availableCapacity > 0 { log.Tracef("[poller] fetching at most %d tasks from client %s", availableCapacity, client.Address()) - tasks, ok := p.fetchTasks(p.pollingCtx, client, taskVersions, availableCapacity) + tasks, reuseRequestKey := p.fetchTasks(p.pollingCtx, client, taskVersions, availableCapacity, requestKey) inProgressTasks.Add(int64(len(tasks))) if len(tasks) > 0 && ephemeral { p.shutdownPolling() } <-fetchMutex // unlock mutex by draining channel - if !ok { + + if !reuseRequestKey { + requestKey = gouuid.New() + } + if len(tasks) == 0 { continue } log.Tracef("[poller] successfully fetched %d tasks from client %s", len(tasks), client.Address()) for _, task := range tasks { wg.Go(func() { - p.runTaskWithRecover(p.jobsCtx, runner, task) + runner.Run(p.jobsCtx, task) inProgressTasks.Add(-1) }) } @@ -165,22 +185,15 @@ } } -func (p *poller) runTaskWithRecover(ctx context.Context, runner run.RunnerInterface, task *runnerv1.Task) { - defer func() { - if r := recover(); r != nil { - err := fmt.Errorf("panic: %v", r) - log.WithError(err).Error("panic in runTaskWithRecover") - } - }() +func (p *poller) fetchTasks(ctx context.Context, client client.Client, tasksVersion *atomic.Int64, availableCapacity int64, requestKey gouuid.UUID) (taskSlice []*runnerv1.Task, reuseRequestKey bool) { + taskSlice = nil + reuseRequestKey = true // Default to reusing requestKey until we get a successful response - if err := runner.Run(ctx, task); err != nil { - log.WithError(err).Error("failed to run task") - } -} + cleanupRequestKey := client.SetRequestKey(requestKey) + defer cleanupRequestKey() -func (p *poller) fetchTasks(ctx context.Context, client client.Client, tasksVersion *atomic.Int64, availableCapacity int64) ([]*runnerv1.Task, bool) { if availableCapacity == 0 { - return nil, false + return taskSlice, reuseRequestKey } reqCtx, cancel := context.WithTimeout(ctx, p.cfg.Runner.FetchTimeout) @@ -192,27 +205,30 @@ TaskCapacity: &availableCapacity, })) if errors.Is(err, context.DeadlineExceeded) { - log.Trace("deadline exceeded") - err = nil - } - if err != nil { + log.Trace("failed to fetch task: deadline exceeded") + return taskSlice, reuseRequestKey + } else if err != nil { if errors.Is(err, context.Canceled) { log.WithError(err).Debugf("shutdown, fetch task canceled") } else { log.WithError(err).Error("failed to fetch task") } - return nil, false + return taskSlice, reuseRequestKey } if resp == nil || resp.Msg == nil { - return nil, false + return taskSlice, reuseRequestKey } + // We've made a successful request without an error, regardless of whether we've received tasks or not, we now + // signal the calling loop that the request key should not be reused: + reuseRequestKey = false + if resp.Msg.GetTasksVersion() > v { tasksVersion.CompareAndSwap(v, resp.Msg.GetTasksVersion()) } - taskSlice := []*runnerv1.Task{} + taskSlice = []*runnerv1.Task{} // Normally we'd expect to get a Task, and maybe AdditionalTasks. But we're resilient here to a bug in Forgejo // 14.0.0 & 14.0.1 where we might, rarely, get AdditionalTasks without a Task. if resp.Msg.Task != nil { @@ -221,12 +237,12 @@ taskSlice = append(taskSlice, resp.Msg.GetAdditionalTasks()...) if len(taskSlice) == 0 { - return nil, false + return taskSlice, reuseRequestKey } else if resp.Msg.Task == nil { log.Warn("FetchTask received tasks in AdditionalTasks field but not Task field; this is unexpected but runner will run them") } // got a task, set `tasksVersion` to zero to force query db in next request. tasksVersion.CompareAndSwap(resp.Msg.GetTasksVersion(), 0) - return taskSlice, true + return taskSlice, reuseRequestKey } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/forgejo-runner-12.7.0/internal/app/poll/poller_test.go new/forgejo-runner-12.7.2/internal/app/poll/poller_test.go --- old/forgejo-runner-12.7.0/internal/app/poll/poller_test.go 2026-02-19 02:28:46.000000000 +0100 +++ new/forgejo-runner-12.7.2/internal/app/poll/poller_test.go 2026-03-09 17:04:00.000000000 +0100 @@ -5,7 +5,9 @@ import ( "context" + "errors" "fmt" + "sync" "sync/atomic" "testing" "testing/synctest" @@ -22,6 +24,7 @@ "code.forgejo.org/forgejo/runner/v12/internal/pkg/client/mocks" "code.forgejo.org/forgejo/runner/v12/internal/pkg/config" + gouuid "github.com/google/uuid" log "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -59,6 +62,10 @@ return time.Second } +func (o mockClient) SetRequestKey(requestKey gouuid.UUID) func() { + return func() {} +} + func (o *mockClient) FetchTask(ctx context.Context, req *connect.Request[runnerv1.FetchTaskRequest]) (*connect.Response[runnerv1.FetchTaskResponse], error) { if o.sleep > 0 { select { @@ -95,38 +102,19 @@ } type mockRunner struct { - cfg *config.Runner - log chan string - panics bool - err error + cfg *config.Runner + log chan string } -func (o *mockRunner) Run(ctx context.Context, task *runnerv1.Task) error { +func (o *mockRunner) Run(ctx context.Context, task *runnerv1.Task) { o.log <- "runner starts" - if o.panics { - log.Trace("panics") - o.log <- "runner panics" - o.panics = false - panic("whatever") - } - if o.err != nil { - log.Trace("error") - o.log <- "runner error" - err := o.err - o.err = nil - return err - } - for { - select { - case <-ctx.Done(): - log.Trace("shutdown") - o.log <- "runner shutdown" - return nil - case <-time.After(o.cfg.Timeout): - log.Trace("after") - o.log <- "runner timeout" - return nil - } + select { + case <-ctx.Done(): + log.Trace("shutdown") + o.log <- "runner shutdown" + case <-time.After(o.cfg.Timeout): + log.Trace("after") + o.log <- "runner timeout" } } @@ -151,8 +139,6 @@ name string timeout time.Duration noTask bool - panics bool - err error expected string contextTimeout time.Duration }{ @@ -162,18 +148,6 @@ expected: "runner shutdown", }, { - name: "Panics", - timeout: 10 * time.Second, - panics: true, - expected: "runner panics", - }, - { - name: "Error", - timeout: 10 * time.Second, - err: fmt.Errorf("ERROR"), - expected: "runner error", - }, - { name: "PollTaskError", timeout: 10 * time.Second, noTask: true, @@ -202,10 +176,8 @@ noTask: testCase.noTask, }}, []run.RunnerInterface{&mockRunner{ - cfg: &configRunner, - log: runnerLog, - panics: testCase.panics, - err: testCase.err, + cfg: &configRunner, + log: runnerLog, }}) go p.Poll() assert.Equal(t, "runner starts", <-runnerLog) @@ -251,8 +223,9 @@ cancel: true, }, { - name: "NoTask", - noTask: true, + name: "NoTask", + success: true, + noTask: true, }, { name: "AdditionalTasks", @@ -291,13 +264,13 @@ }}, []run.RunnerInterface{&mockRunner{}}, ) - task, ok := p.fetchTasks(context.Background(), p.clients[0], &atomic.Int64{}, 100) + task, reuseRequestKey := p.fetchTasks(context.Background(), p.clients[0], &atomic.Int64{}, 100, gouuid.New()) if testCase.success { - assert.True(t, ok) + assert.False(t, reuseRequestKey) assert.NotNil(t, task) assert.Len(t, task, testCase.taskCount) } else { - assert.False(t, ok) + assert.True(t, reuseRequestKey) assert.Nil(t, task) } }) @@ -309,6 +282,7 @@ mockClient := mocks.NewClient(t) mockClient.On("FetchInterval").Return(1 * time.Second) mockClient.On("Address").Return("https://client") + mockClient.On("SetRequestKey", mock.Anything).Return(func() {}) mockRunner := mock_runner.NewRunnerInterface(t) poller := New(pollingCtx, &config.Config{ Runner: config.Runner{ @@ -498,6 +472,7 @@ mockClient := mocks.NewClient(t) mockClient.On("FetchInterval").Return(10 * time.Millisecond) mockClient.On("Address").Return("https://client") + mockClient.On("SetRequestKey", mock.Anything).Return(func() {}) mockRunner := mock_runner.NewRunnerInterface(t) poller := New(pollingCtx, &config.Config{ Runner: config.Runner{ @@ -601,9 +576,11 @@ mockClient1 := mocks.NewClient(t) mockClient1.On("FetchInterval").Return(1 * time.Second) mockClient1.On("Address").Return("https://client1") + mockClient1.On("SetRequestKey", mock.Anything).Return(func() {}) mockClient2 := mocks.NewClient(t) mockClient2.On("FetchInterval").Return(30 * time.Second) mockClient2.On("Address").Return("https://client2") + mockClient2.On("SetRequestKey", mock.Anything).Return(func() {}) mockRunner := mock_runner.NewRunnerInterface(t) poller := New(pollingCtx, &config.Config{ Runner: config.Runner{ @@ -765,3 +742,112 @@ }) }) } + +func TestPollerPollRequestKey(t *testing.T) { + setup := func(t *testing.T, pollingCtx context.Context) (*mocks.Client, *mock_runner.RunnerInterface, Poller) { + mockClient := mocks.NewClient(t) + mockClient.On("FetchInterval").Return(1 * time.Second) + mockClient.On("Address").Return("https://client") + mockRunner := mock_runner.NewRunnerInterface(t) + poller := New(pollingCtx, &config.Config{ + Runner: config.Runner{ + Capacity: 3, + }, + }, []client.Client{mockClient}, []run.RunnerInterface{mockRunner}) + return mockClient, mockRunner, poller + } + teardown := func(t *testing.T, mockClient *mocks.Client, mockRunner *mock_runner.RunnerInterface) { + mockClient.AssertExpectations(t) + mockRunner.AssertExpectations(t) + } + emptyResponse := connect.NewResponse(&runnerv1.FetchTaskResponse{ + Task: nil, + TasksVersion: int64(1), + AdditionalTasks: nil, + }) + + t.Run("each poll sets unique requestKey on client", func(t *testing.T) { + synctest.Test(t, func(t *testing.T) { + pollingCtx, cancel := context.WithCancel(t.Context()) + defer cancel() + + var requestKeyMutex sync.Mutex // prevents this test from being flagged as a data race + var requestKey1 gouuid.UUID + var requestKey2 gouuid.UUID + + mockClient, mockRunner, poller := setup(t, pollingCtx) + mockClient.On("FetchTask", mock.Anything, mock.Anything).Return(emptyResponse, nil) + mockClient.On("SetRequestKey", mock.Anything). + Once(). + Run(func(args mock.Arguments) { + requestKeyMutex.Lock() + requestKey1 = args.Get(0).(gouuid.UUID) + requestKeyMutex.Unlock() + }). + Return(func() {}) + mockClient.On("SetRequestKey", mock.Anything). + Once(). + Run(func(args mock.Arguments) { + requestKeyMutex.Lock() + requestKey2 = args.Get(0).(gouuid.UUID) + requestKeyMutex.Unlock() + }). + Return(func() {}) + + go poller.Poll() + time.Sleep(1500 * time.Millisecond) // synctest delay to ensure two poll runs are complete + + requestKeyMutex.Lock() + assert.NotEqualValues(t, 0, requestKey1.ID()) // invocation with a non-zero UUID + assert.NotEqualValues(t, 0, requestKey2.ID()) // invocation with a non-zero UUID + assert.NotEqualValues(t, requestKey1.String(), requestKey2.String()) // different UUIDs + requestKeyMutex.Unlock() + + require.NoError(t, poller.Shutdown(t.Context())) + teardown(t, mockClient, mockRunner) + }) + }) + + t.Run("retains same requestKey on error", func(t *testing.T) { + synctest.Test(t, func(t *testing.T) { + pollingCtx, cancel := context.WithCancel(t.Context()) + defer cancel() + + var requestKeyMutex sync.Mutex // prevents this test from being flagged as a data race + var requestKey1 gouuid.UUID + var requestKey2 gouuid.UUID + + mockClient, mockRunner, poller := setup(t, pollingCtx) + mockClient.On("FetchTask", mock.Anything, mock.Anything). + Return(nil, errors.New("some error")) + mockClient.On("SetRequestKey", mock.Anything). + Once(). + Run(func(args mock.Arguments) { + requestKeyMutex.Lock() + requestKey1 = args.Get(0).(gouuid.UUID) + requestKeyMutex.Unlock() + }). + Return(func() {}) + mockClient.On("SetRequestKey", mock.Anything). + Once(). + Run(func(args mock.Arguments) { + requestKeyMutex.Lock() + requestKey2 = args.Get(0).(gouuid.UUID) + requestKeyMutex.Unlock() + }). + Return(func() {}) + + go poller.Poll() + time.Sleep(1500 * time.Millisecond) // synctest delay to ensure two poll runs are complete + + requestKeyMutex.Lock() + assert.NotEqualValues(t, 0, requestKey1.ID()) // invocation with a non-zero UUID + assert.NotEqualValues(t, 0, requestKey2.ID()) // invocation with a non-zero UUID + assert.EqualValues(t, requestKey1.String(), requestKey2.String()) // same UUIDs + requestKeyMutex.Unlock() + + require.NoError(t, poller.Shutdown(t.Context())) + teardown(t, mockClient, mockRunner) + }) + }) +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/forgejo-runner-12.7.0/internal/app/run/mocks/RunnerInterface.go new/forgejo-runner-12.7.2/internal/app/run/mocks/RunnerInterface.go --- old/forgejo-runner-12.7.0/internal/app/run/mocks/RunnerInterface.go 2026-02-19 02:28:46.000000000 +0100 +++ new/forgejo-runner-12.7.2/internal/app/run/mocks/RunnerInterface.go 2026-03-09 17:04:00.000000000 +0100 @@ -16,21 +16,8 @@ } // Run provides a mock function with given fields: ctx, task -func (_m *RunnerInterface) Run(ctx context.Context, task *runnerv1.Task) error { - ret := _m.Called(ctx, task) - - if len(ret) == 0 { - panic("no return value specified for Run") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, *runnerv1.Task) error); ok { - r0 = rf(ctx, task) - } else { - r0 = ret.Error(0) - } - - return r0 +func (_m *RunnerInterface) Run(ctx context.Context, task *runnerv1.Task) { + _m.Called(ctx, task) } // NewRunnerInterface creates a new instance of RunnerInterface. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/forgejo-runner-12.7.0/internal/app/run/runner.go new/forgejo-runner-12.7.2/internal/app/run/runner.go --- old/forgejo-runner-12.7.0/internal/app/run/runner.go 2026-02-19 02:28:46.000000000 +0100 +++ new/forgejo-runner-12.7.2/internal/app/run/runner.go 2026-03-09 17:04:00.000000000 +0100 @@ -51,7 +51,7 @@ //go:generate mockery --name RunnerInterface type RunnerInterface interface { - Run(ctx context.Context, task *runnerv1.Task) error + Run(ctx context.Context, task *runnerv1.Task) } func NewRunner(cfg *config.Config, name string, ls labels.Labels, cli client.Client, cacheProxy *cacheproxy.Handler) *Runner { @@ -150,11 +150,15 @@ return cacheProxy } -func (r *Runner) Run(ctx context.Context, task *runnerv1.Task) error { - if _, ok := r.runningTasks.Load(task.Id); ok { - return fmt.Errorf("task %d is already running", task.Id) +func (r *Runner) Run(ctx context.Context, task *runnerv1.Task) { + if _, loaded := r.runningTasks.LoadOrStore(task.Id, struct{}{}); loaded { + // Normally the responsibility of Run() is to ensure that, no matter what occurs within running the job, a + // reporter is created that will report the job status to the server. But in this case, for some reason another + // goroutine is currently running this exact same task on this same runner -- we can't report status in this + // case because it would corrupt the status of the other running job. + log.Errorf("task %d is already running", task.Id) + return } - r.runningTasks.Store(task.Id, struct{}{}) defer r.runningTasks.Delete(task.Id) ctx, cancel := context.WithTimeout(ctx, r.cfg.Runner.Timeout) @@ -169,8 +173,6 @@ }() reporter.RunDaemon() runErr = r.run(ctx, task, reporter) - - return nil } func logAndReport(reporter *report.Reporter, message string, args ...any) { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/forgejo-runner-12.7.0/internal/app/run/runner_test.go new/forgejo-runner-12.7.2/internal/app/run/runner_test.go --- old/forgejo-runner-12.7.0/internal/app/run/runner_test.go 2026-02-19 02:28:46.000000000 +0100 +++ new/forgejo-runner-12.7.2/internal/app/run/runner_test.go 2026-03-09 17:04:00.000000000 +0100 @@ -20,6 +20,7 @@ "code.forgejo.org/forgejo/runner/v12/internal/pkg/labels" "code.forgejo.org/forgejo/runner/v12/internal/pkg/report" "connectrpc.com/connect" + gouuid "github.com/google/uuid" log "github.com/sirupsen/logrus" "google.golang.org/protobuf/types/known/structpb" "gotest.tools/v3/skip" @@ -95,6 +96,11 @@ return time.Duration(args.Int(0)) } +func (m *forgejoClientMock) SetRequestKey(requestKey gouuid.UUID) func() { + args := m.Called(requestKey) + return args.Get(0).(func()) +} + func (m *forgejoClientMock) Ping(ctx context.Context, request *connect.Request[pingv1.PingRequest]) (*connect.Response[pingv1.PingResponse], error) { args := m.Called(ctx, request) if args.Get(0) == nil { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/forgejo-runner-12.7.0/internal/pkg/client/client.go new/forgejo-runner-12.7.2/internal/pkg/client/client.go --- old/forgejo-runner-12.7.0/internal/pkg/client/client.go 2026-02-19 02:28:46.000000000 +0100 +++ new/forgejo-runner-12.7.2/internal/pkg/client/client.go 2026-03-09 17:04:00.000000000 +0100 @@ -8,6 +8,7 @@ "code.forgejo.org/forgejo/actions-proto/ping/v1/pingv1connect" "code.forgejo.org/forgejo/actions-proto/runner/v1/runnerv1connect" + gouuid "github.com/google/uuid" ) // A Client manages communication with the runner. @@ -19,4 +20,5 @@ Address() string Insecure() bool FetchInterval() time.Duration + SetRequestKey(gouuid.UUID) func() } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/forgejo-runner-12.7.0/internal/pkg/client/header.go new/forgejo-runner-12.7.2/internal/pkg/client/header.go --- old/forgejo-runner-12.7.0/internal/pkg/client/header.go 2026-02-19 02:28:46.000000000 +0100 +++ new/forgejo-runner-12.7.2/internal/pkg/client/header.go 2026-03-09 17:04:00.000000000 +0100 @@ -4,6 +4,7 @@ package client const ( - UUIDHeader = "x-runner-uuid" - TokenHeader = "x-runner-token" + UUIDHeader = "x-runner-uuid" + TokenHeader = "x-runner-token" + RequestKeyHeader = "x-runner-request-key" ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/forgejo-runner-12.7.0/internal/pkg/client/http.go new/forgejo-runner-12.7.2/internal/pkg/client/http.go --- old/forgejo-runner-12.7.0/internal/pkg/client/http.go 2026-02-19 02:28:46.000000000 +0100 +++ new/forgejo-runner-12.7.2/internal/pkg/client/http.go 2026-03-09 17:04:00.000000000 +0100 @@ -15,6 +15,7 @@ "code.forgejo.org/forgejo/actions-proto/ping/v1/pingv1connect" "code.forgejo.org/forgejo/actions-proto/runner/v1/runnerv1connect" "connectrpc.com/connect" + gouuid "github.com/google/uuid" log "github.com/sirupsen/logrus" ) @@ -55,6 +56,12 @@ func New(endpoint string, insecure bool, uuid, token, version string, fetchInterval time.Duration, opts ...connect.ClientOption) *HTTPClient { baseURL := strings.TrimRight(endpoint, "/") + "/api/actions" + client := &HTTPClient{ + endpoint: endpoint, + insecure: insecure, + fetchInterval: fetchInterval, + } + opts = append(opts, connect.WithInterceptors(connect.UnaryInterceptorFunc(func(next connect.UnaryFunc) connect.UnaryFunc { return func(ctx context.Context, req connect.AnyRequest) (connect.AnyResponse, error) { if uuid != "" { @@ -63,25 +70,25 @@ if token != "" { req.Header().Set(TokenHeader, token) } + if client.requestKey != nil { + req.Header().Set(RequestKeyHeader, client.requestKey.String()) + } return next(ctx, req) } }))) - return &HTTPClient{ - PingServiceClient: pingv1connect.NewPingServiceClient( - getHTTPClient(endpoint, insecure), - baseURL, - opts..., - ), - RunnerServiceClient: runnerv1connect.NewRunnerServiceClient( - getHTTPClient(endpoint, insecure), - baseURL, - opts..., - ), - endpoint: endpoint, - insecure: insecure, - fetchInterval: fetchInterval, - } + client.PingServiceClient = pingv1connect.NewPingServiceClient( + getHTTPClient(endpoint, insecure), + baseURL, + opts..., + ) + client.RunnerServiceClient = runnerv1connect.NewRunnerServiceClient( + getHTTPClient(endpoint, insecure), + baseURL, + opts..., + ) + + return client } func (c *HTTPClient) Address() string { @@ -96,6 +103,13 @@ return c.fetchInterval } +func (c *HTTPClient) SetRequestKey(uuid gouuid.UUID) func() { + c.requestKey = &uuid + return func() { + c.requestKey = nil + } +} + var _ Client = (*HTTPClient)(nil) // An HTTPClient manages communication with the runner API. @@ -105,4 +119,5 @@ endpoint string insecure bool fetchInterval time.Duration + requestKey *gouuid.UUID } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/forgejo-runner-12.7.0/internal/pkg/client/mocks/Client.go new/forgejo-runner-12.7.2/internal/pkg/client/mocks/Client.go --- old/forgejo-runner-12.7.0/internal/pkg/client/mocks/Client.go 2026-02-19 02:28:46.000000000 +0100 +++ new/forgejo-runner-12.7.2/internal/pkg/client/mocks/Client.go 2026-03-09 17:04:00.000000000 +0100 @@ -14,6 +14,8 @@ runnerv1 "code.forgejo.org/forgejo/actions-proto/runner/v1" time "time" + + uuid "github.com/google/uuid" ) // Client is an autogenerated mock type for the Client type @@ -195,6 +197,26 @@ return r0, r1 } +// SetRequestKey provides a mock function with given fields: _a0 +func (_m *Client) SetRequestKey(_a0 uuid.UUID) func() { + ret := _m.Called(_a0) + + if len(ret) == 0 { + panic("no return value specified for SetRequestKey") + } + + var r0 func() + if rf, ok := ret.Get(0).(func(uuid.UUID) func()); ok { + r0 = rf(_a0) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(func()) + } + } + + return r0 +} + // UpdateLog provides a mock function with given fields: _a0, _a1 func (_m *Client) UpdateLog(_a0 context.Context, _a1 *connect.Request[runnerv1.UpdateLogRequest]) (*connect.Response[runnerv1.UpdateLogResponse], error) { ret := _m.Called(_a0, _a1) ++++++ forgejo-runner.obsinfo ++++++ --- /var/tmp/diff_new_pack.QJKNVP/_old 2026-03-17 19:07:35.184352160 +0100 +++ /var/tmp/diff_new_pack.QJKNVP/_new 2026-03-17 19:07:35.196352657 +0100 @@ -1,5 +1,5 @@ name: forgejo-runner -version: 12.7.0 -mtime: 1771464526 -commit: bb4d56ff714d68e11fa67b9a975fb7de3d6b38cc +version: 12.7.2 +mtime: 1773072240 +commit: 7d42cbf894cb44b28171604c1fd7e9e2cc927540 ++++++ vendor.tar.gz ++++++ ++++ 48687 lines of diff (skipped)
