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-06-10 15:53:11 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/kargo-cli (Old) and /work/SRC/openSUSE:Factory/.kargo-cli.new.2375 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "kargo-cli" Wed Jun 10 15:53:11 2026 rev:54 rq:1358282 version:1.10.6 Changes: -------- --- /work/SRC/openSUSE:Factory/kargo-cli/kargo-cli.changes 2026-06-02 16:08:18.396815189 +0200 +++ /work/SRC/openSUSE:Factory/.kargo-cli.new.2375/kargo-cli.changes 2026-06-10 15:53:30.073583463 +0200 @@ -1,0 +2,15 @@ +Wed Jun 10 05:23:20 UTC 2026 - Johannes Kastl <[email protected]> + +- Update to version 1.10.6: + Kargo v1.10.6 is a patch release gathering four bug fixes, + including a regression from v1.10.5 that prevented project Roles + from loading in the UI. + CLI-related changes: + * Clearer error when resource creation conflicts (#6428): + Creating a resource that conflicts with an existing one — for + example, recreating a Project whose namespace is still + terminating — previously surfaced an opaque, empty 500 error. + These 409 Conflict responses are now parsed and presented with + a meaningful message in the CLI and UI. + +------------------------------------------------------------------- Old: ---- kargo-cli-1.10.5.obscpio New: ---- kargo-cli-1.10.6.obscpio ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ kargo-cli.spec ++++++ --- /var/tmp/diff_new_pack.LE63gx/_old 2026-06-10 15:53:33.621730496 +0200 +++ /var/tmp/diff_new_pack.LE63gx/_new 2026-06-10 15:53:33.621730496 +0200 @@ -19,7 +19,7 @@ %define executable_name kargo Name: kargo-cli -Version: 1.10.5 +Version: 1.10.6 Release: 0 Summary: CLI for the Kubernetes Application lifecycle orchestration License: Apache-2.0 ++++++ _service ++++++ --- /var/tmp/diff_new_pack.LE63gx/_old 2026-06-10 15:53:33.657731987 +0200 +++ /var/tmp/diff_new_pack.LE63gx/_new 2026-06-10 15:53:33.665732319 +0200 @@ -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.10.5</param> + <param name="revision">v1.10.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.LE63gx/_old 2026-06-10 15:53:33.689733314 +0200 +++ /var/tmp/diff_new_pack.LE63gx/_new 2026-06-10 15:53:33.693733479 +0200 @@ -1,6 +1,6 @@ <servicedata> <service name="tar_scm"> <param name="url">https://github.com/akuity/kargo</param> - <param name="changesrevision">b2a335a22f3c8235393877817a9d756f6f695e50</param></service></servicedata> + <param name="changesrevision">1ec8d6094bdb9b2f29f4858f788f2af13ac37fb1</param></service></servicedata> (No newline at EOF) ++++++ kargo-cli-1.10.5.obscpio -> kargo-cli-1.10.6.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kargo-cli-1.10.5/docs/docs/100-roadmap.md new/kargo-cli-1.10.6/docs/docs/100-roadmap.md --- old/kargo-cli-1.10.5/docs/docs/100-roadmap.md 2026-05-29 21:18:01.000000000 +0200 +++ new/kargo-cli-1.10.6/docs/docs/100-roadmap.md 2026-06-09 14:17:09.000000000 +0200 @@ -29,34 +29,39 @@ ## In Progress -### v1.10.0 - -**Expected:** Mid-April, 2026 +### v1.11.0 -🧬 Evolution; not revolution — apart from the usual slate of bug fixes and -performance + stability improvements, v1.10.0 is set to deliver a broad -collection of small, but meaningful quality-of-life improvements. - -A modest selection of anticipated highlights: - -* UI "My Projects" filter -* Path filtering for push events from Git repositories ➡️ fewer Warehouses - executing unnecessary artifact discovery cycles -* Broad range of new and improved promotion steps -* Enhanced trust model for commits made by Kargo -* New Helm chart options to support common operational concerns +**Expected:** Mid-June, 2026 -v1.10.0 will also include a partial UI transition from the deprecated gRPC API -to new RESTful API. +v1.11.0 advances Kargo's event-driven model and continues improving operator +and end-user experience. The release also advances the ongoing UI migration +from the deprecated ConnectRPC API to the REST API. Freight and Stage details +aim to become richer with configurable links to external platforms. ## Upcoming -### v1.11.0 +### v1.12.0 -**Expected:** Mid-June, 2026 +The following deprecated features are scheduled for **removal** in v1.12.0: + +* The deprecated ConnectRPC (gRPC) API in favor of the new REST API. +* The `createTargetBranch` option in the `git-open-pr` promotion step. +* The `author` configuration block on the `git-commit` promotion step. Use + `git-clone` instead. +* The default `git-push` integration policy changes from `AlwaysRebase` to + `RebaseOrMerge`. Set the policy explicitly if you rely on unconditional + rebase behavior. ## Completed +### v1.10.0 + +Evolution, not revolution — a broad collection of quality-of-life improvements, +new and improved promotion steps, and UI enhancements. Also introduced +controller heartbeats with liveness surfaced in the UI. + +See [release notes](./80-release-notes/89-v1.10.0.md) for full details. + ### v1.9.0 Focused on stability, performance, and usability. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kargo-cli-1.10.5/docs/docs/40-operator-guide/40-security/30-access-controls.md new/kargo-cli-1.10.6/docs/docs/40-operator-guide/40-security/30-access-controls.md --- old/kargo-cli-1.10.5/docs/docs/40-operator-guide/40-security/30-access-controls.md 2026-05-29 21:18:01.000000000 +0200 +++ new/kargo-cli-1.10.6/docs/docs/40-operator-guide/40-security/30-access-controls.md 2026-06-09 14:17:09.000000000 +0200 @@ -99,13 +99,17 @@ rbac.kargo.akuity.io/claims: | { "sub": ["alice", "bob" ], - "email": "[email protected]", + "email": ["[email protected]"], "groups": ["devops", "kargo-admin"] } ``` :::info +Claim values may also be specified as a scalar string instead of a single-element +list (e.g. `"email": "[email protected]"`). Both forms are accepted, but the list +form is preferred for consistency. + Mappings specified using annotations with keys of the form `rbac.kargo.akuity.io/claim.<name>` with comma-delimited values are also supported for reasons of backwards compatibility. The effective mapping is diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kargo-cli-1.10.5/docs/docs/50-user-guide/50-security/20-access-controls/index.md new/kargo-cli-1.10.6/docs/docs/50-user-guide/50-security/20-access-controls/index.md --- old/kargo-cli-1.10.5/docs/docs/50-user-guide/50-security/20-access-controls/index.md 2026-05-29 21:18:01.000000000 +0200 +++ new/kargo-cli-1.10.6/docs/docs/50-user-guide/50-security/20-access-controls/index.md 2026-06-09 14:17:09.000000000 +0200 @@ -97,13 +97,17 @@ rbac.kargo.akuity.io/claims: | { "sub": ["alice", "bob" ], - "email": "[email protected]", + "email": ["[email protected]"], "groups": ["devops", "kargo-admin"] } ``` :::info +Claim values may also be specified as a scalar string instead of a single-element +list (e.g. `"email": "[email protected]"`). Both forms are accepted, but the list +form is preferred for consistency. + Mappings specified using annotations with keys of the form `rbac.kargo.akuity.io/claim.<name>` with comma-delimited values are also supported for reasons of backwards compatibility. The effective mapping is diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kargo-cli-1.10.5/pkg/client/generated/models/resource_error_response.go new/kargo-cli-1.10.6/pkg/client/generated/models/resource_error_response.go --- old/kargo-cli-1.10.5/pkg/client/generated/models/resource_error_response.go 1970-01-01 01:00:00.000000000 +0100 +++ new/kargo-cli-1.10.6/pkg/client/generated/models/resource_error_response.go 2026-06-09 14:17:09.000000000 +0200 @@ -0,0 +1,47 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +import ( + "context" + + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// ResourceErrorResponse resource error response +// +// swagger:model ResourceErrorResponse +type ResourceErrorResponse struct { + + // error + Error string `json:"error,omitempty"` +} + +// Validate validates this resource error response +func (m *ResourceErrorResponse) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this resource error response based on context it is used +func (m *ResourceErrorResponse) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *ResourceErrorResponse) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *ResourceErrorResponse) UnmarshalBinary(b []byte) error { + var res ResourceErrorResponse + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kargo-cli-1.10.5/pkg/client/generated/resources/create_resource_responses.go new/kargo-cli-1.10.6/pkg/client/generated/resources/create_resource_responses.go --- old/kargo-cli-1.10.5/pkg/client/generated/resources/create_resource_responses.go 2026-05-29 21:18:01.000000000 +0200 +++ new/kargo-cli-1.10.6/pkg/client/generated/resources/create_resource_responses.go 2026-06-09 14:17:09.000000000 +0200 @@ -28,6 +28,12 @@ return nil, err } return result, nil + case 409: + result := NewCreateResourceConflict() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return nil, result default: return nil, runtime.NewAPIError("[POST /v1beta1/resources] CreateResource", response, response.Code()) } @@ -97,6 +103,76 @@ // response payload if err := consumer.Consume(response.Body(), o.Payload); err != nil && !stderrors.Is(err, io.EOF) { + return err + } + + return nil +} + +// NewCreateResourceConflict creates a CreateResourceConflict with default headers values +func NewCreateResourceConflict() *CreateResourceConflict { + return &CreateResourceConflict{} +} + +/* +CreateResourceConflict describes a response with status code 409, with default header values. + +Conflict +*/ +type CreateResourceConflict struct { + Payload *models.ResourceErrorResponse +} + +// IsSuccess returns true when this create resource conflict response has a 2xx status code +func (o *CreateResourceConflict) IsSuccess() bool { + return false +} + +// IsRedirect returns true when this create resource conflict response has a 3xx status code +func (o *CreateResourceConflict) IsRedirect() bool { + return false +} + +// IsClientError returns true when this create resource conflict response has a 4xx status code +func (o *CreateResourceConflict) IsClientError() bool { + return true +} + +// IsServerError returns true when this create resource conflict response has a 5xx status code +func (o *CreateResourceConflict) IsServerError() bool { + return false +} + +// IsCode returns true when this create resource conflict response a status code equal to that given +func (o *CreateResourceConflict) IsCode(code int) bool { + return code == 409 +} + +// Code gets the status code for the create resource conflict response +func (o *CreateResourceConflict) Code() int { + return 409 +} + +func (o *CreateResourceConflict) Error() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[POST /v1beta1/resources][%d] createResourceConflict %s", 409, payload) +} + +func (o *CreateResourceConflict) String() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[POST /v1beta1/resources][%d] createResourceConflict %s", 409, payload) +} + +func (o *CreateResourceConflict) GetPayload() *models.ResourceErrorResponse { + return o.Payload +} + +func (o *CreateResourceConflict) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + o.Payload = new(models.ResourceErrorResponse) + + // response payload + if err := consumer.Consume(response.Body(), o.Payload); err != nil && !stderrors.Is(err, io.EOF) { return err } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kargo-cli-1.10.5/pkg/controller/git/work_tree.go new/kargo-cli-1.10.6/pkg/controller/git/work_tree.go --- old/kargo-cli-1.10.5/pkg/controller/git/work_tree.go 2026-05-29 21:18:01.000000000 +0200 +++ new/kargo-cli-1.10.6/pkg/controller/git/work_tree.go 2026-06-09 14:17:09.000000000 +0200 @@ -60,8 +60,10 @@ // who cloned the repo associated with this working tree. HomeDir() string // GetDiffPathsForCommitID returns a string slice indicating the paths, - // relative to the root of the repository, of any files that are new or - // modified in the commit with the given ID. + // relative to the root of the repository, of any files changed in the + // commit with the given ID. For renamed or moved files, both the old and + // new paths are included so that path-based filters can match on either + // side of the move. GetDiffPathsForCommitID(commitID string) ([]string, error) // IsAncestor returns true if parent branch is an ancestor of child IsAncestor(parent string, child string) (bool, error) @@ -399,18 +401,36 @@ } func (w *workTree) GetDiffPathsForCommitID(commitID string) ([]string, error) { - resBytes, err := libExec.Exec(w.buildGitCommand("show", "--pretty=", "--name-only", "--first-parent", commitID)) + resBytes, err := libExec.Exec(w.buildGitCommand("show", "--pretty=", "--name-status", "--first-parent", commitID)) if err != nil { return nil, fmt.Errorf("error getting diff paths for commit %q: %w", commitID, err) } var paths []string scanner := bufio.NewScanner(bytes.NewReader(resBytes)) - scanner.Split(bufio.ScanLines) for scanner.Scan() { - paths = append( - paths, - scanner.Text(), - ) + line := scanner.Text() + if line == "" { + continue + } + // --name-status output is tab-separated: <status>\t<path> for most + // changes, or <status>\t<old-path>\t<new-path> for renames and copies. + // The status field for renames and copies includes a similarity score + // (e.g. R100, C85), so we use HasPrefix rather than exact equality. + parts := strings.SplitN(line, "\t", 3) + if len(parts) < 2 { + return nil, fmt.Errorf( + "unexpected output from git show for commit %q: %q", + commitID, line, + ) + } + // For renames (R), include both old and new paths so that a path filter + // matching the source directory detects the removal. Copies (C) leave + // the source unchanged, so only the destination path is relevant. + if len(parts) == 3 && strings.HasPrefix(parts[0], "R") { + paths = append(paths, parts[1], parts[2]) + } else { + paths = append(paths, parts[1]) + } } return paths, nil } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kargo-cli-1.10.5/pkg/controller/git/work_tree_test.go new/kargo-cli-1.10.6/pkg/controller/git/work_tree_test.go --- old/kargo-cli-1.10.5/pkg/controller/git/work_tree_test.go 2026-05-29 21:18:01.000000000 +0200 +++ new/kargo-cli-1.10.6/pkg/controller/git/work_tree_test.go 2026-06-09 14:17:09.000000000 +0200 @@ -400,3 +400,45 @@ require.NoError(t, err) require.Equal(t, []string{"foo/file1.txt"}, paths) } + +func TestGetDiffPathsForMovedFile(t *testing.T) { + testServer, testRepoURL, testRepoCreds := setupRemoteRepo(t) + defer testServer.Close() + + rep, err := Clone( + testRepoURL, + &ClientOptions{Credentials: &testRepoCreds}, + nil, + ) + require.NoError(t, err) + require.NotNil(t, rep) + defer rep.Close() + + wt := internalWorkTree(t, rep) + + // Create initial commit with a file in app/ + require.NoError(t, os.MkdirAll(fmt.Sprintf("%s/app", rep.Dir()), 0o755)) + require.NoError(t, os.WriteFile( + fmt.Sprintf("%s/app/pod.yaml", rep.Dir()), + []byte("kind: Pod"), + 0o600, + )) + require.NoError(t, rep.AddAllAndCommit("initial commit", nil)) + + // Move the file to a different directory + require.NoError(t, os.MkdirAll(fmt.Sprintf("%s/different-app", rep.Dir()), 0o755)) + _, err = libExec.Exec(wt.buildGitCommand( + "mv", "app/pod.yaml", "different-app/pod.yaml", + )) + require.NoError(t, err) + require.NoError(t, rep.AddAllAndCommit("move pod.yaml to different-app/", nil)) + + moveCommitID, err := rep.LastCommitID() + require.NoError(t, err) + + // GetDiffPathsForCommitID should return both the old and new paths so + // that a warehouse watching app/ detects the removal. + paths, err := rep.GetDiffPathsForCommitID(moveCommitID) + require.NoError(t, err) + require.ElementsMatch(t, []string{"app/pod.yaml", "different-app/pod.yaml"}, paths) +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kargo-cli-1.10.5/pkg/credentials/ecr/managed_identity.go new/kargo-cli-1.10.6/pkg/credentials/ecr/managed_identity.go --- old/kargo-cli-1.10.5/pkg/credentials/ecr/managed_identity.go 2026-05-29 21:18:01.000000000 +0200 +++ new/kargo-cli-1.10.6/pkg/credentials/ecr/managed_identity.go 2026-06-09 14:17:09.000000000 +0200 @@ -106,7 +106,7 @@ if req.Type != credentials.TypeImage && req.Type != credentials.TypeHelm { return false, nil } - return true, nil + return ecrURLRegex.MatchString(req.RepoURL), nil } func (p *ManagedIdentityProvider) GetCredentials( diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kargo-cli-1.10.5/pkg/credentials/ecr/managed_identity_test.go new/kargo-cli-1.10.6/pkg/credentials/ecr/managed_identity_test.go --- old/kargo-cli-1.10.5/pkg/credentials/ecr/managed_identity_test.go 2026-05-29 21:18:01.000000000 +0200 +++ new/kargo-cli-1.10.6/pkg/credentials/ecr/managed_identity_test.go 2026-06-09 14:17:09.000000000 +0200 @@ -62,6 +62,24 @@ repoURL: fakeRepoURL, expected: false, }, + { + name: "non-ECR image URL not supported", + provider: &ManagedIdentityProvider{ + accountID: fakeAccountID, + }, + credType: credentials.TypeImage, + repoURL: "us-docker.pkg.dev/project/repo/image", + expected: false, + }, + { + name: "non-ECR helm URL not supported", + provider: &ManagedIdentityProvider{ + accountID: fakeAccountID, + }, + credType: credentials.TypeHelm, + repoURL: "us-docker.pkg.dev/project/repo/chart", + expected: false, + }, } for _, testCase := range testCases { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kargo-cli-1.10.5/pkg/server/create_resource_v1alpha1.go new/kargo-cli-1.10.6/pkg/server/create_resource_v1alpha1.go --- old/kargo-cli-1.10.5/pkg/server/create_resource_v1alpha1.go 2026-05-29 21:18:01.000000000 +0200 +++ new/kargo-cli-1.10.6/pkg/server/create_resource_v1alpha1.go 2026-06-09 14:17:09.000000000 +0200 @@ -31,6 +31,11 @@ Error string `json:"error,omitempty"` } // @name CreateResourceResult +// resourceErrorResponse is the response body for resource API errors +type resourceErrorResponse struct { + Error string `json:"error"` +} // @name ResourceErrorResponse + func (s *server) CreateResource( ctx context.Context, req *connect.Request[svcv1alpha1.CreateResourceRequest], @@ -107,6 +112,7 @@ // @Produce json // @Param manifest body string true "YAML or JSON manifest(s)" // @Success 201 {object} createResourceResponse "Created successfully" +// @Failure 409 {object} resourceErrorResponse "Conflict" // @Router /v1beta1/resources [post] func (s *server) createResources(c *gin.Context) { ctx := c.Request.Context() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kargo-cli-1.10.5/pkg/server/rbac/policy_rules.go new/kargo-cli-1.10.6/pkg/server/rbac/policy_rules.go --- old/kargo-cli-1.10.5/pkg/server/rbac/policy_rules.go 2026-05-29 21:18:01.000000000 +0200 +++ new/kargo-cli-1.10.6/pkg/server/rbac/policy_rules.go 2026-06-09 14:17:09.000000000 +0200 @@ -182,7 +182,7 @@ switch resource { case "analysisruns", "analysistemplates", "configmaps", "events", "freights", "freights/status", "projectconfigs", "promotions", "rolebindings", "roles", - "secrets", "serviceaccounts", "stages", "warehouses": + "secrets", "serviceaccounts", "stages", "warehouses", "promotiontasks": return nil case "analysisrun", "analysistemplate", "configmap", "event", "freight", "projectconfig", "promotion", "role", "rolebinding", "secret", diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kargo-cli-1.10.5/pkg/server/rest_router.go new/kargo-cli-1.10.6/pkg/server/rest_router.go --- old/kargo-cli-1.10.5/pkg/server/rest_router.go 2026-05-29 21:18:01.000000000 +0200 +++ new/kargo-cli-1.10.6/pkg/server/rest_router.go 2026-06-09 14:17:09.000000000 +0200 @@ -308,7 +308,7 @@ // Check for MaxBytesError (body too large) var maxBytesErr *http.MaxBytesError if errors.As(err, &maxBytesErr) { - c.JSON(http.StatusRequestEntityTooLarge, gin.H{"error": "request body too large"}) + c.JSON(http.StatusRequestEntityTooLarge, resourceErrorResponse{Error: "request body too large"}) return } @@ -319,15 +319,15 @@ Error(err, "internal server error") c.JSON( http.StatusInternalServerError, - gin.H{"error": "internal server error"}, + resourceErrorResponse{Error: "internal server error"}, ) } - c.JSON(httpErr.Code(), gin.H{"error": httpErr.Error()}) + c.JSON(httpErr.Code(), resourceErrorResponse{Error: httpErr.Error()}) return } var statusErr *apierrors.StatusError if ok := errors.As(err, &statusErr); ok { - c.JSON(int(statusErr.Status().Code), gin.H{"error": err.Error()}) + c.JSON(int(statusErr.Status().Code), resourceErrorResponse{Error: err.Error()}) return } _ = c.Error(libhttp.Error( diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kargo-cli-1.10.5/pkg/webhook/kubernetes/project/webhook.go new/kargo-cli-1.10.6/pkg/webhook/kubernetes/project/webhook.go --- old/kargo-cli-1.10.5/pkg/webhook/kubernetes/project/webhook.go 2026-05-29 21:18:01.000000000 +0200 +++ new/kargo-cli-1.10.6/pkg/webhook/kubernetes/project/webhook.go 2026-06-09 14:17:09.000000000 +0200 @@ -133,6 +133,23 @@ ), ) } + // The namespace is a Project namespace but is still being terminated from + // a previous deletion. Attempting to create resources in a terminating + // namespace will fail with a confusing 500 error, so surface a clear 409 + // Conflict here instead and tell the user to retry. + if ns.DeletionTimestamp != nil { + return apierrors.NewConflict( + projectGroupResource, + project.Name, + fmt.Errorf( + "failed to initialize Project %q because namespace %q is still "+ + "being terminated; please try again after the namespace has "+ + "been fully deleted", + project.Name, + project.Name, + ), + ) + } logger.Debug("namespace exists but no conflict was found") return nil } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kargo-cli-1.10.5/pkg/webhook/kubernetes/project/webhook_test.go new/kargo-cli-1.10.6/pkg/webhook/kubernetes/project/webhook_test.go --- old/kargo-cli-1.10.5/pkg/webhook/kubernetes/project/webhook_test.go 2026-05-29 21:18:01.000000000 +0200 +++ new/kargo-cli-1.10.6/pkg/webhook/kubernetes/project/webhook_test.go 2026-06-09 14:17:09.000000000 +0200 @@ -27,6 +27,7 @@ require.NoError(t, kargoapi.AddToScheme(scheme)) testProjectName := "test-project" + deletionTimestamp := metav1.Now() testNs := &corev1.Namespace{ ObjectMeta: metav1.ObjectMeta{ Name: testProjectName, @@ -40,6 +41,16 @@ Name: testProjectName, }, } + testNsTerminating := &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: testProjectName, + Labels: map[string]string{ + kargoapi.LabelKeyProject: kargoapi.LabelValueTrue, + }, + DeletionTimestamp: &deletionTimestamp, + Finalizers: []string{"kubernetes"}, + }, + } tests := []struct { name string @@ -93,6 +104,25 @@ }, }, { + name: "namespace is terminating", + project: &kargoapi.Project{ + ObjectMeta: metav1.ObjectMeta{ + Name: testProjectName, + }, + }, + objects: []client.Object{testNsTerminating}, + assertions: func(t *testing.T, warnings admission.Warnings, err error) { + assert.Empty(t, warnings) + require.Error(t, err) + + var statusErr *apierrors.StatusError + require.True(t, errors.As(err, &statusErr)) + + assert.Equal(t, metav1.StatusReasonConflict, statusErr.ErrStatus.Reason) + assert.Contains(t, statusErr.ErrStatus.Message, "being terminated") + }, + }, + { name: "dry run request", project: &kargoapi.Project{ ObjectMeta: metav1.ObjectMeta{ @@ -165,6 +195,7 @@ require.NoError(t, kargoapi.AddToScheme(scheme)) testProjectName := "test-project" + deletionTimestamp := metav1.Now() testNs := &corev1.Namespace{ ObjectMeta: metav1.ObjectMeta{ Name: testProjectName, @@ -178,6 +209,16 @@ Name: testProjectName, }, } + testNsTerminating := &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: testProjectName, + Labels: map[string]string{ + kargoapi.LabelKeyProject: kargoapi.LabelValueTrue, + }, + DeletionTimestamp: &deletionTimestamp, + Finalizers: []string{"kubernetes"}, + }, + } tests := []struct { name string @@ -225,6 +266,22 @@ assert.Contains(t, statusErr.ErrStatus.Message, "not labeled as a Project namespace") }, }, + { + name: "namespace is terminating", + project: &kargoapi.Project{ + ObjectMeta: metav1.ObjectMeta{ + Name: testProjectName, + }, + }, + objects: []client.Object{testNsTerminating}, + assertions: func(t *testing.T, err error) { + require.Error(t, err) + var statusErr *apierrors.StatusError + require.True(t, errors.As(err, &statusErr)) + assert.Equal(t, metav1.StatusReasonConflict, statusErr.ErrStatus.Reason) + assert.Contains(t, statusErr.ErrStatus.Message, "being terminated") + }, + }, } for _, tt := range tests { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kargo-cli-1.10.5/swagger.json new/kargo-cli-1.10.6/swagger.json --- old/kargo-cli-1.10.5/swagger.json 2026-05-29 21:18:01.000000000 +0200 +++ new/kargo-cli-1.10.6/swagger.json 2026-06-09 14:17:09.000000000 +0200 @@ -2902,6 +2902,12 @@ "schema": { "$ref": "#/definitions/CreateResourceResponse" } + }, + "409": { + "description": "Conflict", + "schema": { + "$ref": "#/definitions/ResourceErrorResponse" + } } } }, @@ -5054,6 +5060,14 @@ } } }, + "ResourceErrorResponse": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + } + }, "RevokeRequest": { "type": "object", "properties": { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kargo-cli-1.10.5/ui/src/gen/api/v2/models/index.ts new/kargo-cli-1.10.6/ui/src/gen/api/v2/models/index.ts --- old/kargo-cli-1.10.5/ui/src/gen/api/v2/models/index.ts 2026-05-29 21:18:01.000000000 +0200 +++ new/kargo-cli-1.10.6/ui/src/gen/api/v2/models/index.ts 2026-06-09 14:17:09.000000000 +0200 @@ -160,6 +160,7 @@ export * from './queryFreightsRestParams'; export * from './rbacRole'; export * from './resourceDetails'; +export * from './resourceErrorResponse'; export * from './revokeRequest'; export * from './rolloutsAnalysisRun'; export * from './rolloutsAnalysisRunSpec'; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kargo-cli-1.10.5/ui/src/gen/api/v2/models/resourceErrorResponse.ts new/kargo-cli-1.10.6/ui/src/gen/api/v2/models/resourceErrorResponse.ts --- old/kargo-cli-1.10.5/ui/src/gen/api/v2/models/resourceErrorResponse.ts 1970-01-01 01:00:00.000000000 +0100 +++ new/kargo-cli-1.10.6/ui/src/gen/api/v2/models/resourceErrorResponse.ts 2026-06-09 14:17:09.000000000 +0200 @@ -0,0 +1,11 @@ +/** + * Generated by orval v7.19.0 🍺 + * Do not edit manually. + * Kargo API + * REST API for Kargo + * OpenAPI spec version: v1alpha1 + */ + +export interface ResourceErrorResponse { + error?: string; +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/kargo-cli-1.10.5/ui/src/gen/api/v2/resources/resources.ts new/kargo-cli-1.10.6/ui/src/gen/api/v2/resources/resources.ts --- old/kargo-cli-1.10.5/ui/src/gen/api/v2/resources/resources.ts 2026-05-29 21:18:01.000000000 +0200 +++ new/kargo-cli-1.10.6/ui/src/gen/api/v2/resources/resources.ts 2026-06-09 14:17:09.000000000 +0200 @@ -17,6 +17,7 @@ CreateOrUpdateResourceResponse, CreateResourceResponse, DeleteResourceResponse, + ResourceErrorResponse, UpdateResourceManifestBody, UpdateResourceParams } from '.././models'; @@ -138,10 +139,19 @@ status: 201; }; +export type createResourceResponse409 = { + data: ResourceErrorResponse; + status: 409; +}; + export type createResourceResponseSuccess = createResourceResponse201 & { headers: Headers; }; -export type createResourceResponse = createResourceResponseSuccess; +export type createResourceResponseError = createResourceResponse409 & { + headers: Headers; +}; + +export type createResourceResponse = createResourceResponseSuccess | createResourceResponseError; export const getCreateResourceUrl = () => { return `/v1beta1/resources`; @@ -159,7 +169,10 @@ }); }; -export const getCreateResourceMutationOptions = <TError = unknown, TContext = unknown>(options?: { +export const getCreateResourceMutationOptions = < + TError = ResourceErrorResponse, + TContext = unknown +>(options?: { mutation?: UseMutationOptions< Awaited<ReturnType<typeof createResource>>, TError, @@ -194,12 +207,12 @@ export type CreateResourceMutationResult = NonNullable<Awaited<ReturnType<typeof createResource>>>; export type CreateResourceMutationBody = UpdateResourceManifestBody; -export type CreateResourceMutationError = unknown; +export type CreateResourceMutationError = ResourceErrorResponse; /** * @summary Create resources */ -export const useCreateResource = <TError = unknown, TContext = unknown>( +export const useCreateResource = <TError = ResourceErrorResponse, TContext = unknown>( options?: { mutation?: UseMutationOptions< Awaited<ReturnType<typeof createResource>>, ++++++ kargo-cli.obsinfo ++++++ --- /var/tmp/diff_new_pack.LE63gx/_old 2026-06-10 15:53:39.373968864 +0200 +++ /var/tmp/diff_new_pack.LE63gx/_new 2026-06-10 15:53:39.397969859 +0200 @@ -1,5 +1,5 @@ name: kargo-cli -version: 1.10.5 -mtime: 1780082281 -commit: b2a335a22f3c8235393877817a9d756f6f695e50 +version: 1.10.6 +mtime: 1781007429 +commit: 1ec8d6094bdb9b2f29f4858f788f2af13ac37fb1 ++++++ vendor.tar.gz ++++++ /work/SRC/openSUSE:Factory/kargo-cli/vendor.tar.gz /work/SRC/openSUSE:Factory/.kargo-cli.new.2375/vendor.tar.gz differ: char 13, line 1
