Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package kargo-cli for openSUSE:Factory checked in at 2026-01-19 18:37:15 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/kargo-cli (Old) and /work/SRC/openSUSE:Factory/.kargo-cli.new.1928 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "kargo-cli" Mon Jan 19 18:37:15 2026 rev:39 rq:1327992 version:1.8.6 Changes: -------- --- /work/SRC/openSUSE:Factory/kargo-cli/kargo-cli.changes 2025-11-26 17:18:27.681845611 +0100 +++ /work/SRC/openSUSE:Factory/.kargo-cli.new.1928/kargo-cli.changes 2026-01-19 18:41:25.530301648 +0100 @@ -1,0 +2,15 @@ +Mon Jan 19 06:50:39 UTC 2026 - Johannes Kastl <[email protected]> + +- Update to version 1.8.6: + This release contains no code changes compared to v1.8.5. It has + been retagged to address CI pipeline issues in the release + process. +- Update to version 1.8.5: + * chore(backport release-1.8): chore(deps): bump + github.com/expr-lang/expr from 1.17.6 to 1.17.7 (#5515) + * chore(backport release-1.8): chore: port conflict for + development clusters (#5493) + * chore(backport release-1.8): docs: update CLI auth callback URL + to use HTTP (#5441) + +------------------------------------------------------------------- Old: ---- kargo-cli-1.8.4.obscpio New: ---- kargo-cli-1.8.6.obscpio ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ kargo-cli.spec ++++++ --- /var/tmp/diff_new_pack.GxK0yN/_old 2026-01-19 18:41:26.554344019 +0100 +++ /var/tmp/diff_new_pack.GxK0yN/_new 2026-01-19 18:41:26.554344019 +0100 @@ -1,7 +1,7 @@ # # spec file for package kargo-cli # -# Copyright (c) 2025 SUSE LLC and contributors +# Copyright (c) 2026 SUSE LLC and contributors # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -19,7 +19,7 @@ %define executable_name kargo Name: kargo-cli -Version: 1.8.4 +Version: 1.8.6 Release: 0 Summary: CLI for the Kubernetes Application lifecycle orchestration License: Apache-2.0 ++++++ _service ++++++ --- /var/tmp/diff_new_pack.GxK0yN/_old 2026-01-19 18:41:26.614346501 +0100 +++ /var/tmp/diff_new_pack.GxK0yN/_new 2026-01-19 18:41:26.618346667 +0100 @@ -3,7 +3,7 @@ <param name="url">https://github.com/akuity/kargo</param> <param name="scm">git</param> <param name="exclude">.git</param> - <param name="revision">v1.8.4</param> + <param name="revision">v1.8.6</param> <param name="versionformat">@PARENT_TAG@</param> <param name="versionrewrite-pattern">v(.*)</param> <param name="changesgenerate">enable</param> ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.GxK0yN/_old 2026-01-19 18:41:26.646347826 +0100 +++ /var/tmp/diff_new_pack.GxK0yN/_new 2026-01-19 18:41:26.646347826 +0100 @@ -1,6 +1,6 @@ <servicedata> <service name="tar_scm"> <param name="url">https://github.com/akuity/kargo</param> - <param name="changesrevision">dc38d05ff871ace7ae40fc23ef333ca717c96909</param></service></servicedata> + <param name="changesrevision">d9a709fa1adfc2e74e4b5811ebd072f5d60d5bbf</param></service></servicedata> (No newline at EOF) ++++++ kargo-cli-1.8.4.obscpio -> kargo-cli-1.8.6.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kargo-cli-1.8.4/Makefile new/kargo-cli-1.8.6/Makefile --- old/kargo-cli-1.8.4/Makefile 2025-11-25 22:45:38.000000000 +0100 +++ new/kargo-cli-1.8.6/Makefile 2026-01-17 01:21:59.000000000 +0100 @@ -333,7 +333,7 @@ .PHONY: hack-build hack-build: build-base-image { \ - $(CONTAINER_RUNTIME) run -d -p $(LOCAL_REG_PORT):5000 --name tmp-registry registry:2; \ + $(CONTAINER_RUNTIME) run -d -p $(LOCAL_REG_PORT):5000 --name tmp-registry registry:3.0.0; \ trap '$(CONTAINER_RUNTIME) rm -f tmp-registry' EXIT; \ $(CONTAINER_RUNTIME) push $(BASE_IMAGE):latest-amd64; \ $(CONTAINER_RUNTIME) push $(BASE_IMAGE):latest-arm64; \ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kargo-cli-1.8.4/docs/docs/20-quickstart/index.md new/kargo-cli-1.8.6/docs/docs/20-quickstart/index.md --- old/kargo-cli-1.8.4/docs/docs/20-quickstart/index.md 2025-11-25 22:45:38.000000000 +0100 +++ new/kargo-cli-1.8.6/docs/docs/20-quickstart/index.md 2026-01-17 01:21:59.000000000 +0100 @@ -735,8 +735,8 @@ :::info The `uat` and `prod` instances of our site should be accessible at: -* `uat`: [localhost:30082](http://localhost:30082) -* `prod`: [localhost:30083](http://localhost:30083) +* `uat`: [localhost:32081](http://localhost:32081) +* `prod`: [localhost:32082](http://localhost:32082) ::: :::info diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kargo-cli-1.8.4/docs/docs/40-operator-guide/40-security/20-openid-connect/30-aws-cognito/index.md new/kargo-cli-1.8.6/docs/docs/40-operator-guide/40-security/20-openid-connect/30-aws-cognito/index.md --- old/kargo-cli-1.8.4/docs/docs/40-operator-guide/40-security/20-openid-connect/30-aws-cognito/index.md 2025-11-25 22:45:38.000000000 +0100 +++ new/kargo-cli-1.8.6/docs/docs/40-operator-guide/40-security/20-openid-connect/30-aws-cognito/index.md 2026-01-17 01:21:59.000000000 +0100 @@ -146,7 +146,7 @@ This is where users of the Kargo UI will be redirected to after logging in. - * `https://localhost/auth/callback` + * `http://localhost/auth/callback` This is where users of the `kargo` CLI will be redirected to redirected to after logging in. (The CLI launches a server to serve diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kargo-cli-1.8.4/docs/docs/40-operator-guide/40-security/20-openid-connect/index.md new/kargo-cli-1.8.6/docs/docs/40-operator-guide/40-security/20-openid-connect/index.md --- old/kargo-cli-1.8.4/docs/docs/40-operator-guide/40-security/20-openid-connect/index.md 2025-11-25 22:45:38.000000000 +0100 +++ new/kargo-cli-1.8.6/docs/docs/40-operator-guide/40-security/20-openid-connect/index.md 2026-01-17 01:21:59.000000000 +0100 @@ -64,7 +64,7 @@ **With a compatible identity provider:** - `https://<hostname for api server>/login` (for the UI) - - `https://localhost/auth/callback` (for the CLI) + - `http://localhost/auth/callback` (for the CLI) <br/> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kargo-cli-1.8.4/docs/docs/50-user-guide/50-security/20-access-controls/index.md new/kargo-cli-1.8.6/docs/docs/50-user-guide/50-security/20-access-controls/index.md --- old/kargo-cli-1.8.4/docs/docs/50-user-guide/50-security/20-access-controls/index.md 2025-11-25 22:45:38.000000000 +0100 +++ new/kargo-cli-1.8.6/docs/docs/50-user-guide/50-security/20-access-controls/index.md 2026-01-17 01:21:59.000000000 +0100 @@ -124,7 +124,7 @@ user-to-`ServiceAccount` mappings and the permissions associated with those `ServiceAccount` resources. -Three such "Kargo roles" are pre-defined in a project's namespace when a new +There are several "Kargo roles" pre-defined in a project's namespace when a new `Project` resource is created: 1. `default`: This Kargo role exists by virtue of the existence of the `default` @@ -141,6 +141,15 @@ annotated as being Kargo-managed, and as such, the "Kargo role" that abstracts them can be modified or deleted via the UI or CLI. +1. `kargo-promoter`: This Kargo role is a trio of `ServiceAccount`, `Role`, and + `RoleBinding` resources created by the Kargo management controller. Its + permissions are pre-defined as those necessary to promote `Stage`s and create + `Promotion`s, but not to create, update, or delete core pipeline resources + such as `Stage`s and `Warehouse`s. It is not + initially mapped to any users. All three resources are annotated as being + Kargo-managed, and as such, the "Kargo role" that abstracts them can be + modified or deleted via the UI or CLI. + 1. `kargo-viewer`: This Kargo role is a trio of `ServiceAccount`, `Role`, and `RoleBinding` resources created by the Kargo management controller. Its permissions are pre-defined as those necessary to view, but not modify or @@ -179,10 +188,11 @@ ``` ```shell - NAME KARGO MANAGED AGE - default false 18h - kargo-admin true 18h - kargo-viewer true 18h + NAME KARGO MANAGED AGE + default false 18h + kargo-admin true 18h + kargo-promoter true 18h + kargo-viewer true 18h ``` * The details of a specific role can be examined by naming the role and diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kargo-cli-1.8.4/docs/docs/50-user-guide/60-reference-docs/30-promotion-steps/git-merge-pr.md new/kargo-cli-1.8.6/docs/docs/50-user-guide/60-reference-docs/30-promotion-steps/git-merge-pr.md --- old/kargo-cli-1.8.4/docs/docs/50-user-guide/60-reference-docs/30-promotion-steps/git-merge-pr.md 2025-11-25 22:45:38.000000000 +0100 +++ new/kargo-cli-1.8.6/docs/docs/50-user-guide/60-reference-docs/30-promotion-steps/git-merge-pr.md 2026-01-17 01:21:59.000000000 +0100 @@ -10,6 +10,23 @@ `git-merge-pr` merges an open pull request. This step commonly follows a [`git-open-pr`](git-open-pr.md) step. +:::important +This step only executes synchronous merges. It can neither initiate an +asynchronous merge by placing a PR on a merge queue (or similar), nor can it +recognize when an open PR is already _in_ a merge queue (having been placed +there by someone or something else), and thus cannot wait for an aynchronous +merge in-progress to complete. +::: + +:::caution +__GitHub__ repositories can be configured with branch protection rules that +require PRs to be merged via a merge queue. When such a rule is in place, the +results of the `git-merge-pr` step attempting a synchronous merge will depend +upon permissions. With sufficient permissions to bypass branch protection rules, +the merge queue will be bypassed. Without such permissions, the step's attempt +to merge will fail. +::: + ## Configuration | Name | Type | Required | Description | @@ -49,8 +66,8 @@ ### Merge with Wait This example demonstrates merging a pull request with waiting enabled. If the pull -request is not immediately ready to merge, the step will return a running status and -Kargo will retry it later. +request is not yet mergeable for any reason, the step will return a running +status and Kargo will retry it on the next reconciliation. ```yaml steps: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kargo-cli-1.8.4/docs/docs/50-user-guide/60-reference-docs/40-expressions.md new/kargo-cli-1.8.6/docs/docs/50-user-guide/60-reference-docs/40-expressions.md --- old/kargo-cli-1.8.4/docs/docs/50-user-guide/60-reference-docs/40-expressions.md 2025-11-25 22:45:38.000000000 +0100 +++ new/kargo-cli-1.8.6/docs/docs/50-user-guide/60-reference-docs/40-expressions.md 2026-01-17 01:21:59.000000000 +0100 @@ -633,7 +633,7 @@ repoURL: oci://example.com/my-other-chart freightCreationCriteria: expression: | - chartFrom('oci://example.com/my-chart').Version == chartFrom('oci://example.com/my-other-chart').Tag + chartFrom('oci://example.com/my-chart').Version == chartFrom('oci://example.com/my-other-chart').Version ``` :::info diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kargo-cli-1.8.4/go.mod new/kargo-cli-1.8.6/go.mod --- old/kargo-cli-1.8.4/go.mod 2025-11-25 22:45:38.000000000 +0100 +++ new/kargo-cli-1.8.6/go.mod 2026-01-17 01:21:59.000000000 +0100 @@ -23,7 +23,7 @@ github.com/coreos/go-oidc/v3 v3.16.0 github.com/cyphar/filepath-securejoin v0.6.0 github.com/evanphx/json-patch/v5 v5.9.11 - github.com/expr-lang/expr v1.17.6 + github.com/expr-lang/expr v1.17.7 github.com/fatih/structtag v1.2.0 github.com/fluxcd/pkg/kustomize v1.23.0 github.com/go-git/go-git/v5 v5.16.3 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kargo-cli-1.8.4/go.sum new/kargo-cli-1.8.6/go.sum --- old/kargo-cli-1.8.4/go.sum 2025-11-25 22:45:38.000000000 +0100 +++ new/kargo-cli-1.8.6/go.sum 2026-01-17 01:21:59.000000000 +0100 @@ -142,8 +142,8 @@ github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM= github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f h1:Wl78ApPPB2Wvf/TIe2xdyJxTlb6obmF18d8QdkxNDu4= github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f/go.mod h1:OSYXu++VVOHnXeitef/D8n/6y4QV8uLHSFXX4NeXMGc= -github.com/expr-lang/expr v1.17.6 h1:1h6i8ONk9cexhDmowO/A64VPxHScu7qfSl2k8OlINec= -github.com/expr-lang/expr v1.17.6/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4= +github.com/expr-lang/expr v1.17.7 h1:Q0xY/e/2aCIp8g9s/LGvMDCC5PxYlvHgDZRQ4y16JX8= +github.com/expr-lang/expr v1.17.7/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4= diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kargo-cli-1.8.4/hack/quickstart/k3d.sh new/kargo-cli-1.8.6/hack/quickstart/k3d.sh --- old/kargo-cli-1.8.4/hack/quickstart/k3d.sh 2025-11-25 22:45:38.000000000 +0100 +++ new/kargo-cli-1.8.6/hack/quickstart/k3d.sh 2026-01-17 01:21:59.000000000 +0100 @@ -10,7 +10,7 @@ --no-lb \ --k3s-arg '--disable=traefik@server:0' \ -p '31443-31445:31443-31445@servers:0:direct' \ - -p '30081-30083:30081-30083@servers:0:direct' \ + -p '32080-32082:32080-32082@servers:0:direct' \ --wait helm install cert-manager cert-manager \ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kargo-cli-1.8.4/hack/quickstart/kind.sh new/kargo-cli-1.8.6/hack/quickstart/kind.sh --- old/kargo-cli-1.8.4/hack/quickstart/kind.sh 2025-11-25 22:45:38.000000000 +0100 +++ new/kargo-cli-1.8.6/hack/quickstart/kind.sh 2026-01-17 01:21:59.000000000 +0100 @@ -20,12 +20,12 @@ hostPort: 31444 - containerPort: 31445 # External webhooks server hostPort: 31445 - - containerPort: 30081 # test application instance - hostPort: 30081 - - containerPort: 30082 # UAT application instance - hostPort: 30082 - - containerPort: 30083 # prod application instance - hostPort: 30083 + - containerPort: 32080 # test application instance + hostPort: 32080 + - containerPort: 32081 # UAT application instance + hostPort: 32081 + - containerPort: 32082 # prod application instance + hostPort: 32082 EOF diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kargo-cli-1.8.4/pkg/controller/git/base_repo.go new/kargo-cli-1.8.6/pkg/controller/git/base_repo.go --- old/kargo-cli-1.8.4/pkg/controller/git/base_repo.go 2025-11-25 22:45:38.000000000 +0100 +++ new/kargo-cli-1.8.6/pkg/controller/git/base_repo.go 2026-01-17 01:21:59.000000000 +0100 @@ -359,7 +359,7 @@ "--heads", "--exit-code", // Return 2 if not found b.accessURL, - branch, + "refs/heads/"+branch, )) var exitErr *libExec.ExitError if errors.As(err, &exitErr) && exitErr.ExitCode == 2 { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kargo-cli-1.8.4/pkg/controller/git/repo.go new/kargo-cli-1.8.6/pkg/controller/git/repo.go --- old/kargo-cli-1.8.4/pkg/controller/git/repo.go 2025-11-25 22:45:38.000000000 +0100 +++ new/kargo-cli-1.8.6/pkg/controller/git/repo.go 2026-01-17 01:21:59.000000000 +0100 @@ -128,6 +128,9 @@ if opts.Depth > 0 { args = append(args, "--depth", fmt.Sprint(opts.Depth)) } + if opts.Filter != "" { + args = append(args, "--filter", opts.Filter) + } args = append(args, r.accessURL, r.dir) cmd := r.buildGitCommand(args...) cmd.Dir = r.homeDir // Override the cmd.Dir that's set by r.buildGitCommand() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kargo-cli-1.8.4/pkg/controller/management/projects/event_handlers.go new/kargo-cli-1.8.6/pkg/controller/management/projects/event_handlers.go --- old/kargo-cli-1.8.4/pkg/controller/management/projects/event_handlers.go 2025-11-25 22:45:38.000000000 +0100 +++ new/kargo-cli-1.8.6/pkg/controller/management/projects/event_handlers.go 2026-01-17 01:21:59.000000000 +0100 @@ -148,7 +148,7 @@ case oldCond == nil || newCond == nil: fallthrough case oldCond.Status != newCond.Status: - logger.Info("Warehouse health changed, enqueueing Project") + logger.Info("Stage health changed, enqueueing Project") wq.Add(reconcile.Request{ NamespacedName: types.NamespacedName{Name: oldStage.Namespace}, }) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kargo-cli-1.8.4/pkg/controller/promotions/promotions.go new/kargo-cli-1.8.6/pkg/controller/promotions/promotions.go --- old/kargo-cli-1.8.4/pkg/controller/promotions/promotions.go 2025-11-25 22:45:38.000000000 +0100 +++ new/kargo-cli-1.8.6/pkg/controller/promotions/promotions.go 2026-01-17 01:21:59.000000000 +0100 @@ -329,6 +329,7 @@ newStatus := promo.Status.DeepCopy() var suggestedRequeueInterval *time.Duration + var promoteErr error // Wrap the promoteFn() call in an anonymous function to recover() any panics, so // we can update the promo's phase with Error if it does. This breaks an infinite @@ -346,7 +347,6 @@ } }() var otherStatus *kargoapi.PromotionStatus - var promoteErr error otherStatus, suggestedRequeueInterval, promoteErr = r.promoteFn( promoCtx, *promo, @@ -357,7 +357,10 @@ newStatus = otherStatus } if promoteErr != nil { - newStatus.Phase = kargoapi.PromotionPhaseErrored + // Preserve Running status for progressive backoff on retryable errors. + if newStatus.Phase != kargoapi.PromotionPhaseRunning || otherStatus == nil { + newStatus.Phase = kargoapi.PromotionPhaseErrored + } newStatus.Message = promoteErr.Error() logger.Error(promoteErr, "error executing Promotion") } @@ -458,9 +461,13 @@ return ctrl.Result{}, err } - // If the promotion is still running, we'll need to periodically check on - // it. + // If the promotion is still running, we'll need to periodically check on it. if newStatus.Phase == kargoapi.PromotionPhaseRunning { + if promoteErr != nil { + // Retryable error: use progressive backoff. + return ctrl.Result{}, promoteErr + } + // Waiting for external condition: use calculated interval. return ctrl.Result{ RequeueAfter: calculateRequeueInterval(promo, suggestedRequeueInterval), }, nil @@ -584,7 +591,6 @@ ) } if err != nil { - workingPromo.Status.Phase = kargoapi.PromotionPhaseErrored return &workingPromo.Status, nil, err } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kargo-cli-1.8.4/pkg/gitprovider/bitbucket/bitbucket.go new/kargo-cli-1.8.6/pkg/gitprovider/bitbucket/bitbucket.go --- old/kargo-cli-1.8.4/pkg/gitprovider/bitbucket/bitbucket.go 2025-11-25 22:45:38.000000000 +0100 +++ new/kargo-cli-1.8.6/pkg/gitprovider/bitbucket/bitbucket.go 2026-01-17 01:21:59.000000000 +0100 @@ -333,7 +333,7 @@ // Check if PR is draft - cannot merge draft PRs if bbPR.Draft { - return nil, false, fmt.Errorf("cannot merge pull request %d: pull request is in draft state", id) + return nil, false, nil } // TODO: The Bitbucket API lacks comprehensive merge eligibility checks. We diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kargo-cli-1.8.4/pkg/gitprovider/gitea/gitea.go new/kargo-cli-1.8.6/pkg/gitprovider/gitea/gitea.go --- old/kargo-cli-1.8.4/pkg/gitprovider/gitea/gitea.go 2025-11-25 22:45:38.000000000 +0100 +++ new/kargo-cli-1.8.6/pkg/gitprovider/gitea/gitea.go 2026-01-17 01:21:59.000000000 +0100 @@ -297,16 +297,10 @@ case giteaPR.State != gitea.StateOpen: return nil, false, fmt.Errorf("pull request %d is closed but not merged", id) - case !giteaPR.Mergeable: + case giteaPR.Draft || !giteaPR.Mergeable: return nil, false, nil } - // TODO(fykaa): We should also check if the PR is in draft status before - // merging. The Gitea API includes a draft field, but the go-sdk doesn't - // expose it yet. - // - // See: https://gitea.com/gitea/go-sdk/pulls/731 - // Merge the PR if _, err = p.client.MergePullRequest( ctx, p.owner, p.repo, int(id), &gitea.MergePullRequestOption{}, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kargo-cli-1.8.4/pkg/gitprovider/github/github.go new/kargo-cli-1.8.6/pkg/gitprovider/github/github.go --- old/kargo-cli-1.8.4/pkg/gitprovider/github/github.go 2025-11-25 22:45:38.000000000 +0100 +++ new/kargo-cli-1.8.6/pkg/gitprovider/github/github.go 2026-01-17 01:21:59.000000000 +0100 @@ -316,7 +316,7 @@ case ptr.Deref(ghPR.State, prStateClosed) != prStateOpen: return nil, false, fmt.Errorf("pull request %d is closed but not merged", id) - case ghPR.Mergeable == nil || !*ghPR.Mergeable || (ghPR.Draft != nil && *ghPR.Draft): + case ptr.Deref(ghPR.Draft, false) || !ptr.Deref(ghPR.Mergeable, false): return nil, false, nil } @@ -345,6 +345,7 @@ } pr := convertGithubPR(*updatedPR) + return &pr, true, nil } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kargo-cli-1.8.4/pkg/gitprovider/gitlab/gitlab.go new/kargo-cli-1.8.6/pkg/gitprovider/gitlab/gitlab.go --- old/kargo-cli-1.8.4/pkg/gitprovider/gitlab/gitlab.go 2025-11-25 22:45:38.000000000 +0100 +++ new/kargo-cli-1.8.6/pkg/gitprovider/gitlab/gitlab.go 2026-01-17 01:21:59.000000000 +0100 @@ -226,7 +226,7 @@ case glMR.State != "opened": return nil, false, fmt.Errorf("pull request %d is closed but not merged", id) - case glMR.DetailedMergeStatus != "mergeable": + case glMR.Draft || glMR.DetailedMergeStatus != "mergeable": return nil, false, nil } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kargo-cli-1.8.4/pkg/promotion/simple_engine.go new/kargo-cli-1.8.6/pkg/promotion/simple_engine.go --- old/kargo-cli-1.8.4/pkg/promotion/simple_engine.go 2025-11-25 22:45:38.000000000 +0100 +++ new/kargo-cli-1.8.6/pkg/promotion/simple_engine.go 2026-01-17 01:21:59.000000000 +0100 @@ -2,7 +2,6 @@ import ( "context" - "errors" "fmt" "os" "regexp" @@ -83,12 +82,7 @@ defer os.RemoveAll(workDir) } - result := e.executeSteps(ctx, promoCtx, steps, workDir) - if result.Status == kargoapi.PromotionPhaseErrored { - err = errors.New(result.Message) - } - - return result, err + return e.executeSteps(ctx, promoCtx, steps, workDir) } // executeSteps executes a list of Steps in sequence. @@ -97,7 +91,7 @@ pCtx Context, steps []Step, workDir string, -) Result { +) (Result, error) { // NB: Make a deep copy of the Context so that we don't modify the original. promoCtx := pCtx.DeepCopy() if promoCtx.State == nil { @@ -128,8 +122,15 @@ exprDataCache = e.cacheFunc() } - if e.shouldSkipStep(ctx, exprDataCache, promoCtx, step, stepExecMeta) { - continue + // Only evaluate the "if" conditio when the step has not yet started. + // If the step has already started (on a previous reconciliation), we + // should not re-evaluate whether to skip it. Re-evaluating could cause + // a step's own Failed status from a previous attempt to incorrectly + // trigger the skip condition. + if stepExecMeta.StartedAt == nil { + if e.shouldSkipStep(ctx, exprDataCache, promoCtx, step, stepExecMeta) { + continue + } } // Get a StepRunner for the step. @@ -182,7 +183,7 @@ // Determine what to do based on the result. if !e.determineStepCompletion(step, registration.Metadata, stepExecMeta, err) { - // The step is still running, so we need to wait + // Step incomplete; return error (if any) for progressive backoff. return Result{ Status: kargoapi.PromotionPhaseRunning, CurrentStep: i, @@ -190,7 +191,7 @@ State: promoCtx.State, HealthChecks: healthChecks, RetryAfter: result.RetryAfter, - } + }, err } // If the step succeeded, we can add any health checks to the list. @@ -211,7 +212,7 @@ StepExecutionMetadata: promoCtx.StepExecutionMetadata, State: promoCtx.State, HealthChecks: healthChecks, - } + }, nil } func (e *simpleEngine) prepareStepMetadata(promoCtx *Context, step Step) *kargoapi.StepExecutionMetadata { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kargo-cli-1.8.4/pkg/promotion/simple_engine_test.go new/kargo-cli-1.8.6/pkg/promotion/simple_engine_test.go --- old/kargo-cli-1.8.4/pkg/promotion/simple_engine_test.go 2025-11-25 22:45:38.000000000 +0100 +++ new/kargo-cli-1.8.6/pkg/promotion/simple_engine_test.go 2026-01-17 01:21:59.000000000 +0100 @@ -49,7 +49,7 @@ {Kind: "error-step"}, }, assertions: func(t *testing.T, result Result, err error) { - assert.ErrorContains(t, err, "error running step") + assert.NoError(t, err) assert.Equal(t, kargoapi.PromotionPhaseErrored, result.Status) }, }, @@ -62,13 +62,33 @@ {Kind: "context-waiter"}, }, assertions: func(t *testing.T, result Result, err error) { - assert.ErrorContains(t, err, context.Canceled.Error()) + assert.NoError(t, err) assert.Equal(t, kargoapi.PromotionPhaseErrored, result.Status) + assert.Contains(t, result.Message, context.Canceled.Error()) assert.Len(t, result.StepExecutionMetadata, 1) assert.Equal(t, kargoapi.PromotionStepStatusErrored, result.StepExecutionMetadata[0].Status) assert.Contains(t, result.StepExecutionMetadata[0].Message, context.Canceled.Error()) }, }, + { + name: "running promotion with recoverable error returns error for backoff", + promoCtx: Context{ + Project: "test-project", + }, + steps: []Step{ + { + Kind: "recoverable-error-step", + Alias: "step1", + Retry: &kargoapi.PromotionStepRetry{ErrorThreshold: 3}, + }, + }, + assertions: func(t *testing.T, result Result, err error) { + assert.Error(t, err) + assert.Equal(t, kargoapi.PromotionPhaseRunning, result.Status) + assert.Len(t, result.StepExecutionMetadata, 1) + assert.Equal(t, kargoapi.PromotionStepStatusErrored, result.StepExecutionMetadata[0].Status) + }, + }, } for _, tt := range tests { @@ -121,6 +141,19 @@ }, }, ) + testRegistry.register( + "recoverable-error-step", + StepRunnerRegistration{ + Factory: func(StepRunnerCapabilities) StepRunner { + return &MockStepRunner{ + RunResult: StepResult{ + Status: kargoapi.PromotionStepStatusErrored, + }, + RunErr: errors.New("recoverable error"), + } + }, + }, + ) engine := &simpleEngine{registry: testRegistry} @@ -136,12 +169,13 @@ register func(stepRunnerRegistry) promoCtx Context steps []Step - assertions func(*testing.T, Result) + assertions func(*testing.T, Result, error) }{ { name: "runner not found", steps: []Step{{Kind: "unknown-step"}}, - assertions: func(t *testing.T, result Result) { + assertions: func(t *testing.T, result Result, err error) { + require.NoError(t, err) assert.Equal(t, kargoapi.PromotionPhaseErrored, result.Status) assert.Contains(t, result.Message, "no promotion step runner found for kind") assert.Equal(t, int64(0), result.CurrentStep) @@ -157,7 +191,8 @@ { name: "error determining whether to skip step", steps: []Step{{If: "${{ bogus() }}"}}, - assertions: func(t *testing.T, result Result) { + assertions: func(t *testing.T, result Result, err error) { + require.NoError(t, err) assert.Equal(t, kargoapi.PromotionPhaseErrored, result.Status) assert.Contains(t, result.Message, "error checking if step") assert.Contains(t, result.Message, "should be skipped") @@ -179,7 +214,8 @@ {Kind: "success-step", Alias: "step1"}, {Kind: "success-step", Alias: "step2"}, }, - assertions: func(t *testing.T, result Result) { + assertions: func(t *testing.T, result Result, err error) { + require.NoError(t, err) assert.Equal(t, kargoapi.PromotionPhaseSucceeded, result.Status) assert.Empty(t, result.Message) assert.Equal(t, int64(1), result.CurrentStep) @@ -209,7 +245,8 @@ {Kind: "skipped-step", Alias: "step1"}, {Kind: "skipped-step", Alias: "step2"}, }, - assertions: func(t *testing.T, result Result) { + assertions: func(t *testing.T, result Result, err error) { + require.NoError(t, err) assert.Equal(t, kargoapi.PromotionPhaseSucceeded, result.Status) assert.Empty(t, result.Message) assert.Equal(t, int64(1), result.CurrentStep) @@ -240,7 +277,8 @@ {Kind: "error-step", Alias: "step2", If: "${{ false }}"}, {Kind: "success-step", Alias: "step3", If: "${{ true }}"}, }, - assertions: func(t *testing.T, result Result) { + assertions: func(t *testing.T, result Result, err error) { + require.NoError(t, err) assert.Equal(t, kargoapi.PromotionPhaseSucceeded, result.Status) assert.Empty(t, result.Message) assert.Equal(t, int64(2), result.CurrentStep) @@ -288,7 +326,8 @@ // This step should be run {Kind: "success-step", Alias: "step2"}, }, - assertions: func(t *testing.T, result Result) { + assertions: func(t *testing.T, result Result, err error) { + require.NoError(t, err) assert.Equal(t, kargoapi.PromotionPhaseSucceeded, result.Status) assert.Empty(t, result.Message) assert.Equal(t, int64(1), result.CurrentStep) @@ -315,7 +354,8 @@ {Kind: "success-step", Alias: "step1"}, {Kind: "terminal-error-step", Alias: "step2"}, }, - assertions: func(t *testing.T, result Result) { + assertions: func(t *testing.T, result Result, err error) { + require.NoError(t, err) assert.Equal(t, kargoapi.PromotionPhaseErrored, result.Status) assert.Contains(t, result.Message, "an unrecoverable error occurred") assert.Equal(t, int64(1), result.CurrentStep) @@ -346,7 +386,8 @@ {Kind: "success-step", Alias: "step1"}, {Kind: "error-step", Alias: "step2"}, }, - assertions: func(t *testing.T, result Result) { + assertions: func(t *testing.T, result Result, err error) { + require.NoError(t, err) assert.Equal(t, kargoapi.PromotionPhaseErrored, result.Status) assert.Contains(t, result.Message, "met error threshold") assert.Equal(t, int64(1), result.CurrentStep) @@ -380,7 +421,8 @@ Retry: &kargoapi.PromotionStepRetry{ErrorThreshold: 3}, }, }, - assertions: func(t *testing.T, result Result) { + assertions: func(t *testing.T, result Result, err error) { + require.Error(t, err) assert.Equal(t, kargoapi.PromotionPhaseRunning, result.Status) assert.Empty(t, result.Message) assert.Equal(t, int64(0), result.CurrentStep) @@ -413,7 +455,8 @@ }, }, }, - assertions: func(t *testing.T, result Result) { + assertions: func(t *testing.T, result Result, err error) { + require.NoError(t, err) assert.Equal(t, kargoapi.PromotionPhaseErrored, result.Status) assert.Contains(t, result.Message, "timed out after") assert.Equal(t, int64(0), result.CurrentStep) @@ -444,7 +487,8 @@ }, }, }, - assertions: func(t *testing.T, result Result) { + assertions: func(t *testing.T, result Result, err error) { + require.NoError(t, err) assert.Equal(t, kargoapi.PromotionPhaseErrored, result.Status) assert.Contains(t, result.Message, "timed out after") assert.Equal(t, int64(0), result.CurrentStep) @@ -459,7 +503,8 @@ { name: "step is still running; timeout not elapsed", steps: []Step{{Kind: "running-step"}}, - assertions: func(t *testing.T, result Result) { + assertions: func(t *testing.T, result Result, err error) { + require.NoError(t, err) assert.Equal(t, kargoapi.PromotionPhaseRunning, result.Status) assert.Empty(t, result.Message) assert.Equal(t, int64(0), result.CurrentStep) @@ -477,7 +522,8 @@ {Kind: "context-waiter"}, // Closes context and errors {Kind: "success-step"}, // Won't run because of canceled context }, - assertions: func(t *testing.T, result Result) { + assertions: func(t *testing.T, result Result, err error) { + require.NoError(t, err) assert.Equal(t, kargoapi.PromotionPhaseErrored, result.Status) assert.Contains(t, result.Message, context.Canceled.Error()) assert.Equal(t, int64(1), result.CurrentStep) @@ -516,7 +562,8 @@ Kind: "task-level-output-step", Alias: "task-1::custom-output", }}, - assertions: func(t *testing.T, result Result) { + assertions: func(t *testing.T, result Result, err error) { + require.NoError(t, err) assert.Equal(t, kargoapi.PromotionPhaseSucceeded, result.Status) assert.Empty(t, result.Message) assert.Equal(t, int64(0), result.CurrentStep) @@ -564,7 +611,8 @@ Kind: "task-level-output-step", Alias: "custom-output", }}, - assertions: func(t *testing.T, result Result) { + assertions: func(t *testing.T, result Result, err error) { + require.NoError(t, err) assert.Equal(t, kargoapi.PromotionPhaseSucceeded, result.Status) assert.Empty(t, result.Message) assert.Equal(t, int64(0), result.CurrentStep) @@ -582,6 +630,31 @@ }, }, { + name: "previously failed step does not skip on retry", + promoCtx: Context{ + StepExecutionMetadata: kargoapi.StepExecutionMetadataList{{ + Alias: "step1", + StartedAt: ptr.To(metav1.NewTime(time.Now().Add(-time.Minute))), + Status: kargoapi.PromotionStepStatusFailed, + Message: "previous failure", + }}, + }, + steps: []Step{ + {Kind: "success-step", Alias: "step1"}, + }, + assertions: func(t *testing.T, result Result, err error) { + require.NoError(t, err) + assert.Equal(t, kargoapi.PromotionPhaseSucceeded, result.Status) + assert.Equal(t, int64(0), result.CurrentStep) + + require.Len(t, result.StepExecutionMetadata, 1) + + assert.Equal(t, kargoapi.PromotionStepStatusSucceeded, result.StepExecutionMetadata[0].Status) + assert.NotNil(t, result.StepExecutionMetadata[0].StartedAt) + assert.NotNil(t, result.StepExecutionMetadata[0].FinishedAt) + }, + }, + { name: "panic during step execution", register: func(registry stepRunnerRegistry) { registry.register( @@ -614,7 +687,8 @@ {Kind: "panic-step", Alias: "step2"}, {Kind: "success-step", Alias: "step3"}, }, - assertions: func(t *testing.T, result Result) { + assertions: func(t *testing.T, result Result, err error) { + require.NoError(t, err) assert.Equal(t, kargoapi.PromotionPhaseErrored, result.Status) assert.Contains(t, result.Message, "something went wrong") assert.Equal(t, int64(2), result.CurrentStep) @@ -727,7 +801,8 @@ kargoClient: fake.NewClientBuilder().Build(), } - tt.assertions(t, engine.executeSteps(ctx, tt.promoCtx, tt.steps, t.TempDir())) + result, err := engine.executeSteps(ctx, tt.promoCtx, tt.steps, t.TempDir()) + tt.assertions(t, result, err) }) } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kargo-cli-1.8.4/pkg/webhook/external/artifactory.go new/kargo-cli-1.8.6/pkg/webhook/external/artifactory.go --- old/kargo-cli-1.8.4/pkg/webhook/external/artifactory.go 2025-11-25 22:45:38.000000000 +0100 +++ new/kargo-cli-1.8.6/pkg/webhook/external/artifactory.go 2026-01-17 01:21:59.000000000 +0100 @@ -109,7 +109,7 @@ } mac := hmac.New(sha256.New, token) - mac.Write(requestBody) + _, _ = mac.Write(requestBody) computedSig := hex.EncodeToString(mac.Sum(nil)) if !hmac.Equal([]byte(sig), []byte(computedSig)) { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kargo-cli-1.8.4/pkg/webhook/external/github.go new/kargo-cli-1.8.6/pkg/webhook/external/github.go --- old/kargo-cli-1.8.4/pkg/webhook/external/github.go 2025-11-25 22:45:38.000000000 +0100 +++ new/kargo-cli-1.8.6/pkg/webhook/external/github.go 2026-01-17 01:21:59.000000000 +0100 @@ -210,11 +210,10 @@ return case *gh.PushEvent: - // TODO(krancour): GetHTMLURL() gives us a repo URL starting with - // https://. By refreshing Warehouses using a normalized representation of - // that URL, we will miss any Warehouses that are subscribed to the same - // repository using a different URL format. - repoURLs = []string{urls.NormalizeGit(e.GetRepo().GetCloneURL())} + repoURLs = []string{ + urls.NormalizeGit(e.GetRepo().GetCloneURL()), + urls.NormalizeGit(e.GetRepo().GetSSHURL()), + } ref := e.GetRef() qualifiers = []string{ref} logger = logger.WithValues("ref", ref) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kargo-cli-1.8.4/pkg/webhook/external/github_test.go new/kargo-cli-1.8.6/pkg/webhook/external/github_test.go --- old/kargo-cli-1.8.4/pkg/webhook/external/github_test.go 2025-11-25 22:45:38.000000000 +0100 +++ new/kargo-cli-1.8.6/pkg/webhook/external/github_test.go 2026-01-17 01:21:59.000000000 +0100 @@ -33,6 +33,12 @@ CloneURL: gh.Ptr("https://github.com/example/repo"), }, } + validPushEventSshRepoURL := &gh.PushEvent{ + Ref: gh.Ptr("refs/heads/main"), + Repo: &gh.PushEventRepository{ + SSHURL: gh.Ptr("[email protected]:user/repo.git"), + }, + } validPackageEventImage := &gh.PackageEvent{ Action: gh.Ptr("published"), Package: &gh.Package{ @@ -502,7 +508,7 @@ }, }, { - name: "warehouse refreshed (push event, git)", + name: "warehouse refreshed (push event, git, https)", secretData: testSecretData, client: fake.NewClientBuilder().WithScheme(testScheme).WithObjects( &kargoapi.Warehouse{ @@ -529,6 +535,46 @@ require.NoError(t, err) req := httptest.NewRequest( http.MethodPost, + testURL, + bytes.NewBuffer(bodyBytes), + ) + req.Header.Set(gh.EventTypeHeader, githubEventTypePush) + req.Header.Set(gh.SHA256SignatureHeader, sign(bodyBytes)) + return req + }, + assertions: func(t *testing.T, rr *httptest.ResponseRecorder) { + require.Equal(t, http.StatusOK, rr.Code) + require.JSONEq(t, `{"msg":"refreshed 1 warehouse(s)"}`, rr.Body.String()) + }, + }, + { + name: "warehouse refreshed (push event, git, ssh)", + secretData: testSecretData, + client: fake.NewClientBuilder().WithScheme(testScheme).WithObjects( + &kargoapi.Warehouse{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testProjectName, + Name: "fake-warehouse", + }, + Spec: kargoapi.WarehouseSpec{ + Subscriptions: []kargoapi.RepoSubscription{{ + Git: &kargoapi.GitSubscription{ + RepoURL: "[email protected]:user/repo.git", + Branch: "main", + }, + }}, + }, + }, + ).WithIndex( + &kargoapi.Warehouse{}, + indexer.WarehousesBySubscribedURLsField, + indexer.WarehousesBySubscribedURLs, + ).Build(), + req: func() *http.Request { + bodyBytes, err := json.Marshal(validPushEventSshRepoURL) + require.NoError(t, err) + req := httptest.NewRequest( + http.MethodPost, testURL, bytes.NewBuffer(bodyBytes), ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kargo-cli-1.8.4/pkg/webhook/external/gitlab.go new/kargo-cli-1.8.6/pkg/webhook/external/gitlab.go --- old/kargo-cli-1.8.4/pkg/webhook/external/gitlab.go 2025-11-25 22:45:38.000000000 +0100 +++ new/kargo-cli-1.8.6/pkg/webhook/external/gitlab.go 2026-01-17 01:21:59.000000000 +0100 @@ -120,7 +120,10 @@ case *gl.PushEvent: var repoURLs []string if e.Repository != nil { - repoURLs = []string{urls.NormalizeGit(e.Repository.GitHTTPURL)} + repoURLs = []string{ + urls.NormalizeGit(e.Repository.GitHTTPURL), + urls.NormalizeGit(e.Repository.GitSSHURL), + } } logger = logger.WithValues( "repoURLs", repoURLs, @@ -131,7 +134,10 @@ case *gl.TagEvent: var repoURLs []string if e.Repository != nil { - repoURLs = []string{urls.NormalizeGit(e.Repository.GitHTTPURL)} + repoURLs = []string{ + urls.NormalizeGit(e.Repository.GitHTTPURL), + urls.NormalizeGit(e.Repository.GitSSHURL), + } } logger = logger.WithValues( "repoURLs", repoURLs, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kargo-cli-1.8.4/pkg/webhook/external/gitlab_test.go new/kargo-cli-1.8.6/pkg/webhook/external/gitlab_test.go --- old/kargo-cli-1.8.4/pkg/webhook/external/gitlab_test.go 2025-11-25 22:45:38.000000000 +0100 +++ new/kargo-cli-1.8.6/pkg/webhook/external/gitlab_test.go 2026-01-17 01:21:59.000000000 +0100 @@ -22,7 +22,8 @@ { "ref": "refs/heads/main", "repository":{ - "git_http_url": "https://gitlab.com/example/repo" + "git_http_url": "https://gitlab.com/example/repo.git", + "git_ssh_url": "[email protected]:example/repo.git" } }` @@ -30,7 +31,8 @@ { "ref": "refs/tags/v1.0.0", "repository":{ - "git_http_url": "https://gitlab.com/example/repo" + "git_http_url": "https://gitlab.com/example/repo.git", + "git_ssh_url": "[email protected]:example/repo.git" } }` @@ -152,7 +154,7 @@ }, }, { - name: "warehouse refreshed (push event)", + name: "warehouse refreshed (push event, https)", secretData: testSecretData, client: fake.NewClientBuilder().WithScheme(testScheme).WithObjects( &kargoapi.Warehouse{ @@ -193,6 +195,47 @@ }, }, { + name: "warehouse refreshed (push event, ssh)", + secretData: testSecretData, + client: fake.NewClientBuilder().WithScheme(testScheme).WithObjects( + &kargoapi.Warehouse{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testProjectName, + Name: "fake-warehouse", + }, + Spec: kargoapi.WarehouseSpec{ + Subscriptions: []kargoapi.RepoSubscription{{ + Git: &kargoapi.GitSubscription{ + RepoURL: "[email protected]:example/repo", + Branch: "main", + }, + }}, + }, + }, + ).WithIndex( + &kargoapi.Warehouse{}, + indexer.WarehousesBySubscribedURLsField, + indexer.WarehousesBySubscribedURLs, + ).Build(), + req: func() *http.Request { + bodyBuf := bytes.NewBuffer([]byte(gitlabPushEventRequestBody)) + req := httptest.NewRequest( + http.MethodPost, + testURL, + bodyBuf, + ) + req.Header.Set(gitlabTokenHeader, testToken) + req.Header.Set(gitlabEventHeader, string(gl.EventTypePush)) + return req + }, + assertions: func(t *testing.T, rr *httptest.ResponseRecorder) { + require.Equal(t, http.StatusOK, rr.Code) + require.JSONEq( + t, `{"msg":"refreshed 1 warehouse(s)"}`, rr.Body.String(), + ) + }, + }, + { name: "no ref match (tag event)", // This event would prompt the Warehouse to refresh if not for the ref in // the event being for a tag falling outside the subscription's semver @@ -238,7 +281,7 @@ }, }, { - name: "warehouse refreshed (tag event)", + name: "warehouse refreshed (tag event, https)", secretData: testSecretData, client: fake.NewClientBuilder().WithScheme(testScheme).WithObjects( &kargoapi.Warehouse{ @@ -253,6 +296,48 @@ CommitSelectionStrategy: kargoapi.CommitSelectionStrategySemVer, SemverConstraint: "^1.0.0", }, + }}, + }, + }, + ).WithIndex( + &kargoapi.Warehouse{}, + indexer.WarehousesBySubscribedURLsField, + indexer.WarehousesBySubscribedURLs, + ).Build(), + req: func() *http.Request { + bodyBuf := bytes.NewBuffer([]byte(gitlabTagPushEventRequestBody)) + req := httptest.NewRequest( + http.MethodPost, + testURL, + bodyBuf, + ) + req.Header.Set(gitlabTokenHeader, testToken) + req.Header.Set(gitlabEventHeader, string(gl.EventTypeTagPush)) + return req + }, + assertions: func(t *testing.T, rr *httptest.ResponseRecorder) { + require.Equal(t, http.StatusOK, rr.Code) + require.JSONEq( + t, `{"msg":"refreshed 1 warehouse(s)"}`, rr.Body.String(), + ) + }, + }, + { + name: "warehouse refreshed (tag event, ssh)", + secretData: testSecretData, + client: fake.NewClientBuilder().WithScheme(testScheme).WithObjects( + &kargoapi.Warehouse{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testProjectName, + Name: "fake-warehouse", + }, + Spec: kargoapi.WarehouseSpec{ + Subscriptions: []kargoapi.RepoSubscription{{ + Git: &kargoapi.GitSubscription{ + RepoURL: "[email protected]:example/repo", + CommitSelectionStrategy: kargoapi.CommitSelectionStrategySemVer, + SemverConstraint: "^1.0.0", + }, }}, }, }, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kargo-cli-1.8.4/pkg/yaml/yaml.go new/kargo-cli-1.8.6/pkg/yaml/yaml.go --- old/kargo-cli-1.8.4/pkg/yaml/yaml.go 2025-11-25 22:45:38.000000000 +0100 +++ new/kargo-cli-1.8.6/pkg/yaml/yaml.go 2026-01-17 01:21:59.000000000 +0100 @@ -11,6 +11,14 @@ "go.yaml.in/yaml/v3" ) +// The maximum size of the input buffer for processing YAML files. Given that the max size of a +// Kubernetes manifest (the most common thing we're dealing with) is 1 MB, this number is chosen to +// assume that the max value of any given key would be just shy of that number (imagine a config map +// with a single large value which is a config file for something in the container). Please note +// that we do not allocate a buffer of this size up front; this is just the maximum size that we +// will allow when processing input. +const maxBufferSize = 1024 * 1024 // 1 MB + // Update represents a discrete update to be made to a YAML document. type Update struct { // Key is the dot-separated path to the field to update. @@ -86,6 +94,11 @@ scanner := bufio.NewScanner(bytes.NewBuffer(inBytes)) scanner.Split(bufio.ScanLines) + // Create an initial buffer of 100B which should be plenty for most lines. This means we will + // likely avoid an allocation on the first scan. If we encounter a line that exceeds this size, + // the buffer will grow up to maxBufferSize. + var buf = make([]byte, 0, 100) + scanner.Buffer(buf, maxBufferSize) var line int for scanner.Scan() { const errMsg = "error writing to byte buffer" @@ -117,6 +130,9 @@ } line++ } + if scanner.Err() != nil { + return nil, fmt.Errorf("error scanning input bytes: %w", scanner.Err()) + } return outBuf.Bytes(), nil } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kargo-cli-1.8.4/pkg/yaml/yaml_test.go new/kargo-cli-1.8.6/pkg/yaml/yaml_test.go --- old/kargo-cli-1.8.4/pkg/yaml/yaml_test.go 2025-11-25 22:45:38.000000000 +0100 +++ new/kargo-cli-1.8.6/pkg/yaml/yaml_test.go 2026-01-17 01:21:59.000000000 +0100 @@ -106,6 +106,36 @@ ) }, }, + { + name: "really long lines still work", + // nolint:lll + inBytes: []byte(` +characters: +- name: Anakin + affiliation: Light side + temptation: "` + strings.Repeat("Did you ever hear the tragedy of Darth Plagueis The Wise? I thought not. It's not a story the Jedi would tell you. It's a Sith legend. Darth Plagueis was a Dark Lord of the Sith, so powerful and so wise he could use the Force to influence the midichlorians to create life...He had such a knowledge of the dark side that he could even keep the ones he cared about from dying. The dark side of the Force is a pathway to many abilities some consider to be unnatural. He became so powerful...the only thing he was afraid of was losing his power, which eventually, of course, he did. Unfortunately, he taught his apprentice everything he knew, then his apprentice killed him in his sleep. Ironic. He could save others from death, but not himself.", 1000) + `" +`), + updates: []Update{ + { + Key: "characters.0.affiliation", + Value: "Dark side", + }, + }, + assertions: func(t *testing.T, bytes []byte, err error) { + require.NoError(t, err) + require.Equal( + t, + // nolint:lll + []byte(` +characters: +- name: Anakin + affiliation: Dark side + temptation: "`+strings.Repeat("Did you ever hear the tragedy of Darth Plagueis The Wise? I thought not. It's not a story the Jedi would tell you. It's a Sith legend. Darth Plagueis was a Dark Lord of the Sith, so powerful and so wise he could use the Force to influence the midichlorians to create life...He had such a knowledge of the dark side that he could even keep the ones he cared about from dying. The dark side of the Force is a pathway to many abilities some consider to be unnatural. He became so powerful...the only thing he was afraid of was losing his power, which eventually, of course, he did. Unfortunately, he taught his apprentice everything he knew, then his apprentice killed him in his sleep. Ironic. He could save others from death, but not himself.", 1000)+`" +`), + bytes, + ) + }, + }, } for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { ++++++ kargo-cli.obsinfo ++++++ --- /var/tmp/diff_new_pack.GxK0yN/_old 2026-01-19 18:41:29.274456566 +0100 +++ /var/tmp/diff_new_pack.GxK0yN/_new 2026-01-19 18:41:29.286457063 +0100 @@ -1,5 +1,5 @@ name: kargo-cli -version: 1.8.4 -mtime: 1764107138 -commit: dc38d05ff871ace7ae40fc23ef333ca717c96909 +version: 1.8.6 +mtime: 1768609319 +commit: d9a709fa1adfc2e74e4b5811ebd072f5d60d5bbf ++++++ vendor.tar.gz ++++++ /work/SRC/openSUSE:Factory/kargo-cli/vendor.tar.gz /work/SRC/openSUSE:Factory/.kargo-cli.new.1928/vendor.tar.gz differ: char 134, line 2
