From 4fe6d30489976233e81b0c4e1214c16d2872c428 Mon Sep 17 00:00:00 2001 From: Alexandre Bergere Date: Thu, 8 Oct 2026 18:12:34 +0200 Subject: [PATCH] fix: offer --slug on update only where the API acts on it Customers, deployment zones, entitlements, entitlement groups, licenses and service accounts are never renamed: their update accepts the slug already in the path and refuses any other. Their update commands still offered --slug, and the SDK dropped the field, so `entitlements update webhook --slug webhooks` printed "Entitlement updated", exited 0 and left the slug alone. --slug is now registered on create only for those six, so passing it to update is an unknown flag and exits 2. Components and instances keep it on update, where the API does act on a slug. Signed-off-by: Alexandre Bergere --- internal/cmd/customers.go | 7 ++++++ internal/cmd/deployment_zones.go | 7 ++++++ internal/cmd/entitlement_groups.go | 7 ++++++ internal/cmd/entitlements.go | 7 ++++++ internal/cmd/exit_test.go | 4 ++++ internal/cmd/input_sources_test.go | 34 ++++++++++++++++++++++++++++++ internal/cmd/licenses.go | 7 ++++++ internal/cmd/service_accounts.go | 7 ++++++ 8 files changed, 80 insertions(+) diff --git a/internal/cmd/customers.go b/internal/cmd/customers.go index 63f6953..64d728c 100644 --- a/internal/cmd/customers.go +++ b/internal/cmd/customers.go @@ -101,6 +101,7 @@ func newCustomersCreateCommand() *cobra.Command { } addInputSourceFlags(cmd, &file, &payload, "customer") inline.register(cmd) + inline.registerSlug(cmd) return cmd } @@ -168,6 +169,12 @@ type customerInputFlags struct { func (f *customerInputFlags) register(cmd *cobra.Command) { cmd.Flags().StringVar(&f.name, "name", "", "Customer name") cmd.Flags().StringVar(&f.externalCustomerID, "external-customer-id", "", "External customer ID") +} + +// registerSlug adds --slug to create only. The API never renames a customer: +// its update accepts the slug already in the path and refuses any other, so the +// flag could only restate the argument or be refused. +func (f *customerInputFlags) registerSlug(cmd *cobra.Command) { cmd.Flags().StringVar(&f.slug, "slug", "", "Customer slug") } diff --git a/internal/cmd/deployment_zones.go b/internal/cmd/deployment_zones.go index b77a09c..80bee94 100644 --- a/internal/cmd/deployment_zones.go +++ b/internal/cmd/deployment_zones.go @@ -101,6 +101,7 @@ func newDeploymentZonesCreateCommand() *cobra.Command { } addInputSourceFlags(cmd, &file, &payload, "deployment zone") inline.register(cmd) + inline.registerSlug(cmd) return cmd } @@ -175,6 +176,12 @@ func (f *deploymentZoneInputFlags) register(cmd *cobra.Command) { cmd.Flags().StringVar(&f.description, "description", "", "Deployment zone description") cmd.Flags().StringVar(&f.metadata, "metadata-json", "", "Deployment zone metadata as inline JSON or YAML object") cmd.Flags().StringVar(&f.releaseID, "release-id", "", "Release ID") +} + +// registerSlug adds --slug to create only. The API never renames a deployment zone: +// its update accepts the slug already in the path and refuses any other, so the +// flag could only restate the argument or be refused. +func (f *deploymentZoneInputFlags) registerSlug(cmd *cobra.Command) { cmd.Flags().StringVar(&f.slug, "slug", "", "Deployment zone slug") } diff --git a/internal/cmd/entitlement_groups.go b/internal/cmd/entitlement_groups.go index eb9770c..7a06930 100644 --- a/internal/cmd/entitlement_groups.go +++ b/internal/cmd/entitlement_groups.go @@ -104,6 +104,7 @@ func newEntitlementGroupsCreateCommand() *cobra.Command { } addInputSourceFlags(cmd, &file, &payload, "entitlement group") inline.register(cmd) + inline.registerSlug(cmd) return cmd } @@ -251,6 +252,12 @@ type entitlementGroupInputFlags struct { func (f *entitlementGroupInputFlags) register(cmd *cobra.Command) { cmd.Flags().StringVar(&f.name, "name", "", "Entitlement group name") cmd.Flags().StringVar(&f.description, "description", "", "Entitlement group description") +} + +// registerSlug adds --slug to create only. The API never renames an entitlement group: +// its update accepts the slug already in the path and refuses any other, so the +// flag could only restate the argument or be refused. +func (f *entitlementGroupInputFlags) registerSlug(cmd *cobra.Command) { cmd.Flags().StringVar(&f.slug, "slug", "", "Entitlement group slug") } diff --git a/internal/cmd/entitlements.go b/internal/cmd/entitlements.go index d41c820..3c67830 100644 --- a/internal/cmd/entitlements.go +++ b/internal/cmd/entitlements.go @@ -101,6 +101,7 @@ func newEntitlementsCreateCommand() *cobra.Command { } addInputSourceFlags(cmd, &file, &payload, "entitlement") inline.register(cmd) + inline.registerSlug(cmd) return cmd } @@ -175,6 +176,12 @@ func (f *entitlementInputFlags) register(cmd *cobra.Command) { cmd.Flags().StringVar(&f.kind, "type", "", "Entitlement type") cmd.Flags().StringVar(&f.aggregationMethod, "aggregation-method", "", "Entitlement aggregation method") cmd.Flags().StringArrayVar(&f.groupSlugs, "group-slug", nil, "Entitlement group slug, repeat for multiple values") +} + +// registerSlug adds --slug to create only. The API never renames an entitlement: +// its update accepts the slug already in the path and refuses any other, so the +// flag could only restate the argument or be refused. +func (f *entitlementInputFlags) registerSlug(cmd *cobra.Command) { cmd.Flags().StringVar(&f.slug, "slug", "", "Entitlement slug") } diff --git a/internal/cmd/exit_test.go b/internal/cmd/exit_test.go index 8e7f73a..5f77a23 100644 --- a/internal/cmd/exit_test.go +++ b/internal/cmd/exit_test.go @@ -105,6 +105,10 @@ func TestExitCodeForUsageMistakes(t *testing.T) { name: "unsupported config key", args: []string{"config", "set", "bogus", "value"}, }, + { + name: "a slug on the update of a resource the API never renames", + args: []string{"--base-url", "http://127.0.0.1:1", "entitlements", "update", "webhook", "--name", "webhook", "--slug", "webhooks"}, + }, { name: "destructive command with a non-interactive stdin and no --yes", args: []string{"--base-url", "http://127.0.0.1:1", "customers", "delete", "acme"}, diff --git a/internal/cmd/input_sources_test.go b/internal/cmd/input_sources_test.go index 0f3b36a..a2e581a 100644 --- a/internal/cmd/input_sources_test.go +++ b/internal/cmd/input_sources_test.go @@ -273,6 +273,40 @@ func TestInlineFlagsAreRecognisedByTheCommandThatRegistersThem(t *testing.T) { }) } +// TestSlugFlagIsOfferedOnlyWhereTheAPIActsOnIt pins --slug to the commands where a slug +// does something. Six resources are never renamed: their update accepts the slug already +// in the path and refuses any other. The SDK dropped the field on those updates, so +// "update webhook --slug webhooks" printed a success and left the slug alone. Components +// and instances do take a slug on update, so they keep the flag there. +func TestSlugFlagIsOfferedOnlyWhereTheAPIActsOnIt(t *testing.T) { + root := NewRootCommand() + + for path, want := range map[string]bool{ + "customers create": true, + "customers update": false, + "deployment-zones create": true, + "deployment-zones update": false, + "entitlement-groups create": true, + "entitlement-groups update": false, + "entitlements create": true, + "entitlements update": false, + "licenses create": true, + "licenses update": false, + "service-accounts create": true, + "service-accounts update": false, + "components update": true, + "instances update": true, + } { + cmd, _, err := root.Find(strings.Split(path, " ")) + if err != nil { + t.Fatalf("Find(%q) error = %v", path, err) + } + if got := cmd.Flags().Lookup("slug") != nil; got != want { + t.Errorf("%q offers --slug = %t, want %t", path, got, want) + } + } +} + // placeholderArgs supplies one throwaway value per positional argument in the command's // Use line, so that argument validation is satisfied and the run reaches the input check. func placeholderArgs(cmd *cobra.Command) []string { diff --git a/internal/cmd/licenses.go b/internal/cmd/licenses.go index deb59c3..01cf5a5 100644 --- a/internal/cmd/licenses.go +++ b/internal/cmd/licenses.go @@ -102,6 +102,7 @@ func newLicensesCreateCommand() *cobra.Command { } addInputSourceFlags(cmd, &file, &payload, "license") inline.register(cmd) + inline.registerSlug(cmd) return cmd } @@ -331,6 +332,12 @@ func (f *licenseInputFlags) register(cmd *cobra.Command) { cmd.Flags().StringVar(&f.version, "version", "", "License version") cmd.Flags().StringVar(&f.versionName, "version-name", "", "Human-readable license version name") cmd.Flags().BoolVar(&f.isDefault, "default", false, "Whether the license is the default") +} + +// registerSlug adds --slug to create only. The API never renames a license: +// its update accepts the slug already in the path and refuses any other, so the +// flag could only restate the argument or be refused. +func (f *licenseInputFlags) registerSlug(cmd *cobra.Command) { cmd.Flags().StringVar(&f.slug, "slug", "", "License slug") } diff --git a/internal/cmd/service_accounts.go b/internal/cmd/service_accounts.go index 404b2d8..1573bfe 100644 --- a/internal/cmd/service_accounts.go +++ b/internal/cmd/service_accounts.go @@ -101,6 +101,7 @@ func newServiceAccountsCreateCommand() *cobra.Command { } addInputSourceFlags(cmd, &file, &payload, "service account") inline.register(cmd) + inline.registerSlug(cmd) return cmd } @@ -238,6 +239,12 @@ type serviceAccountInputFlags struct { func (f *serviceAccountInputFlags) register(cmd *cobra.Command) { cmd.Flags().StringVar(&f.name, "name", "", "Service account name") +} + +// registerSlug adds --slug to create only. The API never renames a service account: +// its update accepts the slug already in the path and refuses any other, so the +// flag could only restate the argument or be refused. +func (f *serviceAccountInputFlags) registerSlug(cmd *cobra.Command) { cmd.Flags().StringVar(&f.slug, "slug", "", "Service account slug") }