From 50d0fe903641dd93984d2b77a7bd349bd0e3c34d Mon Sep 17 00:00:00 2001 From: "Inter, Sven" Date: Tue, 17 Mar 2026 10:46:36 +0100 Subject: [PATCH 01/10] feat(secrets-manager): add KMS flags to create and update instance commands --- .../secrets-manager/instance/create/create.go | 28 ++++++++++++++-- .../secrets-manager/instance/update/update.go | 32 ++++++++++++++++--- 2 files changed, 53 insertions(+), 7 deletions(-) diff --git a/internal/cmd/secrets-manager/instance/create/create.go b/internal/cmd/secrets-manager/instance/create/create.go index 0c6d8420b..9d8e401db 100644 --- a/internal/cmd/secrets-manager/instance/create/create.go +++ b/internal/cmd/secrets-manager/instance/create/create.go @@ -23,6 +23,11 @@ import ( const ( instanceNameFlag = "name" aclFlag = "acl" + + kmsKeyIdFlag = "kms-key-id" + kmsKeyringIdFlag = "kms-keyring-id" + kmsKeyVersionFlag = "kms-key-version" + kmsServiceAccountEmailFlag = "kms-service-account-email" ) type inputModel struct { @@ -30,6 +35,11 @@ type inputModel struct { InstanceName *string Acls *[]string + + KmsKeyId *string + KmsKeyringId *string + KmsKeyVersion *int64 + KmsServiceAccountEmail *string } func NewCmd(params *types.CmdParams) *cobra.Command { @@ -103,8 +113,15 @@ func configureFlags(cmd *cobra.Command) { cmd.Flags().StringP(instanceNameFlag, "n", "", "Instance name") cmd.Flags().Var(flags.CIDRSliceFlag(), aclFlag, "List of IP networks in CIDR notation which are allowed to access this instance") + cmd.Flags().String(kmsKeyIdFlag, "", "ID of the KMS key to use for encryption") + cmd.Flags().String(kmsKeyringIdFlag, "", "ID of the KMS key ring") + cmd.Flags().Int64(kmsKeyVersionFlag, 0, "Version of the KMS key") + cmd.Flags().String(kmsServiceAccountEmailFlag, "", "Service account email for KMS access") + err := flags.MarkFlagsRequired(cmd, instanceNameFlag) cobra.CheckErr(err) + + cmd.MarkFlagsRequiredTogether(kmsKeyIdFlag, kmsKeyringIdFlag, kmsKeyVersionFlag, kmsServiceAccountEmailFlag) } func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, error) { @@ -114,9 +131,13 @@ func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, } model := inputModel{ - GlobalFlagModel: globalFlags, - InstanceName: flags.FlagToStringPointer(p, cmd, instanceNameFlag), - Acls: flags.FlagToStringSlicePointer(p, cmd, aclFlag), + GlobalFlagModel: globalFlags, + InstanceName: flags.FlagToStringPointer(p, cmd, instanceNameFlag), + Acls: flags.FlagToStringSlicePointer(p, cmd, aclFlag), + KmsKeyId: flags.FlagToStringPointer(p, cmd, kmsKeyIdFlag), + KmsKeyringId: flags.FlagToStringPointer(p, cmd, kmsKeyringIdFlag), + KmsKeyVersion: flags.FlagToInt64Pointer(p, cmd, kmsKeyVersionFlag), + KmsServiceAccountEmail: flags.FlagToStringPointer(p, cmd, kmsServiceAccountEmailFlag), } p.DebugInputModel(model) @@ -128,6 +149,7 @@ func buildCreateInstanceRequest(ctx context.Context, model *inputModel, apiClien req = req.CreateInstancePayload(secretsmanager.CreateInstancePayload{ Name: model.InstanceName, + // TODO: Add KMS config here when implementing API integration }) return req diff --git a/internal/cmd/secrets-manager/instance/update/update.go b/internal/cmd/secrets-manager/instance/update/update.go index 58786ffd6..1ed181149 100644 --- a/internal/cmd/secrets-manager/instance/update/update.go +++ b/internal/cmd/secrets-manager/instance/update/update.go @@ -25,6 +25,11 @@ const ( instanceIdArg = "INSTANCE_ID" aclFlag = "acl" + + kmsKeyIdFlag = "kms-key-id" + kmsKeyringIdFlag = "kms-keyring-id" + kmsKeyVersionFlag = "kms-key-version" + kmsServiceAccountEmailFlag = "kms-service-account-email" ) type inputModel struct { @@ -32,6 +37,11 @@ type inputModel struct { InstanceId string Acls *[]string + + KmsKeyId *string + KmsKeyringId *string + KmsKeyVersion *int64 + KmsServiceAccountEmail *string } func NewCmd(params *types.CmdParams) *cobra.Command { @@ -87,6 +97,13 @@ func NewCmd(params *types.CmdParams) *cobra.Command { func configureFlags(cmd *cobra.Command) { cmd.Flags().Var(flags.CIDRSliceFlag(), aclFlag, "List of IP networks in CIDR notation which are allowed to access this instance") + + cmd.Flags().String(kmsKeyIdFlag, "", "ID of the KMS key to use for encryption") + cmd.Flags().String(kmsKeyringIdFlag, "", "ID of the KMS key ring") + cmd.Flags().Int64(kmsKeyVersionFlag, 0, "Version of the KMS key") + cmd.Flags().String(kmsServiceAccountEmailFlag, "", "Service account email for KMS access") + + cmd.MarkFlagsRequiredTogether(kmsKeyIdFlag, kmsKeyringIdFlag, kmsKeyVersionFlag, kmsServiceAccountEmailFlag) } func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inputModel, error) { @@ -98,15 +115,20 @@ func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inpu } acls := flags.FlagToStringSlicePointer(p, cmd, aclFlag) + kmsKeyId := flags.FlagToStringPointer(p, cmd, kmsKeyIdFlag) - if acls == nil { + if acls == nil && kmsKeyId == nil { return nil, &cliErr.EmptyUpdateError{} } model := inputModel{ - GlobalFlagModel: globalFlags, - InstanceId: instanceId, - Acls: acls, + GlobalFlagModel: globalFlags, + InstanceId: instanceId, + Acls: acls, + KmsKeyId: flags.FlagToStringPointer(p, cmd, kmsKeyIdFlag), + KmsKeyringId: flags.FlagToStringPointer(p, cmd, kmsKeyringIdFlag), + KmsKeyVersion: flags.FlagToInt64Pointer(p, cmd, kmsKeyVersionFlag), + KmsServiceAccountEmail: flags.FlagToStringPointer(p, cmd, kmsServiceAccountEmailFlag), } p.DebugInputModel(model) @@ -114,6 +136,8 @@ func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inpu } func buildRequest(ctx context.Context, model *inputModel, apiClient *secretsmanager.APIClient) secretsmanager.ApiUpdateACLsRequest { + // TODO: implement API integration for KMS key updates. + req := apiClient.UpdateACLs(ctx, model.ProjectId, model.InstanceId) cidrs := []secretsmanager.UpdateACLPayload{} From 0c3b692e922fc51b7d3c99f11e933eca761f5b89 Mon Sep 17 00:00:00 2001 From: "Inter, Sven" Date: Wed, 18 Mar 2026 14:27:35 +0100 Subject: [PATCH 02/10] feat(secrets-manager): implement request building with KMS key --- .../secrets-manager/instance/create/create.go | 16 +++++++-- .../secrets-manager/instance/update/update.go | 36 +++++++++++++++++-- 2 files changed, 46 insertions(+), 6 deletions(-) diff --git a/internal/cmd/secrets-manager/instance/create/create.go b/internal/cmd/secrets-manager/instance/create/create.go index 9d8e401db..c653e08ec 100644 --- a/internal/cmd/secrets-manager/instance/create/create.go +++ b/internal/cmd/secrets-manager/instance/create/create.go @@ -147,10 +147,20 @@ func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, func buildCreateInstanceRequest(ctx context.Context, model *inputModel, apiClient *secretsmanager.APIClient) secretsmanager.ApiCreateInstanceRequest { req := apiClient.CreateInstance(ctx, model.ProjectId) - req = req.CreateInstancePayload(secretsmanager.CreateInstancePayload{ + payload := secretsmanager.CreateInstancePayload{ Name: model.InstanceName, - // TODO: Add KMS config here when implementing API integration - }) + } + + if model.KmsKeyId != nil { + payload.KmsKey = &secretsmanager.KmsKeyPayload{ + KeyId: model.KmsKeyId, + KeyRingId: model.KmsKeyringId, + KeyVersion: model.KmsKeyVersion, + ServiceAccountEmail: model.KmsServiceAccountEmail, + } + } + + req = req.CreateInstancePayload(payload) return req } diff --git a/internal/cmd/secrets-manager/instance/update/update.go b/internal/cmd/secrets-manager/instance/update/update.go index 1ed181149..92d385397 100644 --- a/internal/cmd/secrets-manager/instance/update/update.go +++ b/internal/cmd/secrets-manager/instance/update/update.go @@ -82,7 +82,14 @@ func NewCmd(params *types.CmdParams) *cobra.Command { // Call API req := buildRequest(ctx, model, apiClient) - err = req.Execute() + switch request := req.(type) { + case secretsmanager.ApiUpdateInstanceRequest: + err = request.Execute() + case secretsmanager.ApiUpdateACLsRequest: + err = request.Execute() + default: + err = fmt.Errorf("unknown request type") + } if err != nil { return fmt.Errorf("update Secrets Manager instance: %w", err) } @@ -135,9 +142,32 @@ func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inpu return &model, nil } -func buildRequest(ctx context.Context, model *inputModel, apiClient *secretsmanager.APIClient) secretsmanager.ApiUpdateACLsRequest { - // TODO: implement API integration for KMS key updates. +func buildRequest(ctx context.Context, model *inputModel, apiClient *secretsmanager.APIClient) interface{ Execute() error } { + if model.KmsKeyId != nil { + return buildUpdateInstanceRequest(ctx, model, apiClient) + } + + return buildUpdateACLsRequest(ctx, model, apiClient) +} + +func buildUpdateInstanceRequest(ctx context.Context, model *inputModel, apiClient *secretsmanager.APIClient) secretsmanager.ApiUpdateInstanceRequest { + req := apiClient.UpdateInstance(ctx, model.ProjectId, model.InstanceId) + + payload := secretsmanager.UpdateInstancePayload{ + KmsKey: &secretsmanager.KmsKeyPayload{ + KeyId: model.KmsKeyId, + KeyRingId: model.KmsKeyringId, + KeyVersion: model.KmsKeyVersion, + ServiceAccountEmail: model.KmsServiceAccountEmail, + }, + } + + req = req.UpdateInstancePayload(payload) + + return req +} +func buildUpdateACLsRequest(ctx context.Context, model *inputModel, apiClient *secretsmanager.APIClient) secretsmanager.ApiUpdateACLsRequest { req := apiClient.UpdateACLs(ctx, model.ProjectId, model.InstanceId) cidrs := []secretsmanager.UpdateACLPayload{} From 5cc1ad8c14f41173bf48cbd5cff68349375bb6ee Mon Sep 17 00:00:00 2001 From: "Inter, Sven" Date: Thu, 19 Mar 2026 10:49:02 +0100 Subject: [PATCH 03/10] feat(secrets-manager): enforce mutual exclusivity between ACL and KMS key flags --- internal/cmd/secrets-manager/instance/update/update.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/cmd/secrets-manager/instance/update/update.go b/internal/cmd/secrets-manager/instance/update/update.go index 92d385397..70d4c8052 100644 --- a/internal/cmd/secrets-manager/instance/update/update.go +++ b/internal/cmd/secrets-manager/instance/update/update.go @@ -111,6 +111,7 @@ func configureFlags(cmd *cobra.Command) { cmd.Flags().String(kmsServiceAccountEmailFlag, "", "Service account email for KMS access") cmd.MarkFlagsRequiredTogether(kmsKeyIdFlag, kmsKeyringIdFlag, kmsKeyVersionFlag, kmsServiceAccountEmailFlag) + cmd.MarkFlagsMutuallyExclusive(aclFlag, kmsKeyIdFlag) } func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inputModel, error) { From bd630d1574213c6e906be58966931f2037eee3aa Mon Sep 17 00:00:00 2001 From: "Inter, Sven" Date: Fri, 20 Mar 2026 15:35:12 +0100 Subject: [PATCH 04/10] refactor(secrets-manager): refactor flag requirements to use cobra built-in validtion --- .../secrets-manager/instance/update/update.go | 6 +- .../instance/update/update_test.go | 95 ++++++------------- 2 files changed, 28 insertions(+), 73 deletions(-) diff --git a/internal/cmd/secrets-manager/instance/update/update.go b/internal/cmd/secrets-manager/instance/update/update.go index 70d4c8052..d38b1f964 100644 --- a/internal/cmd/secrets-manager/instance/update/update.go +++ b/internal/cmd/secrets-manager/instance/update/update.go @@ -112,6 +112,7 @@ func configureFlags(cmd *cobra.Command) { cmd.MarkFlagsRequiredTogether(kmsKeyIdFlag, kmsKeyringIdFlag, kmsKeyVersionFlag, kmsServiceAccountEmailFlag) cmd.MarkFlagsMutuallyExclusive(aclFlag, kmsKeyIdFlag) + cmd.MarkFlagsOneRequired(aclFlag, kmsKeyIdFlag) } func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inputModel, error) { @@ -123,11 +124,6 @@ func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inpu } acls := flags.FlagToStringSlicePointer(p, cmd, aclFlag) - kmsKeyId := flags.FlagToStringPointer(p, cmd, kmsKeyIdFlag) - - if acls == nil && kmsKeyId == nil { - return nil, &cliErr.EmptyUpdateError{} - } model := inputModel{ GlobalFlagModel: globalFlags, diff --git a/internal/cmd/secrets-manager/instance/update/update_test.go b/internal/cmd/secrets-manager/instance/update/update_test.go index 24e14d1fb..b811faf09 100644 --- a/internal/cmd/secrets-manager/instance/update/update_test.go +++ b/internal/cmd/secrets-manager/instance/update/update_test.go @@ -4,10 +4,8 @@ import ( "context" "testing" - "github.com/stackitcloud/stackit-cli/internal/pkg/types" - "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" - "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" "github.com/google/go-cmp/cmp" @@ -111,13 +109,7 @@ func TestParseInput(t *testing.T) { isValid: false, }, { - description: "no flag values", - argValues: fixtureArgValues(), - flagValues: map[string]string{}, - isValid: false, - }, - { - description: "required flags only (no values to update)", + description: "no update flags", argValues: fixtureArgValues(), flagValues: map[string]string{ projectIdFlag: testProjectId, @@ -172,6 +164,28 @@ func TestParseInput(t *testing.T) { flagValues: fixtureFlagValues(), isValid: false, }, + { + description: "kms key id without other required kms flags", + argValues: fixtureArgValues(), + flagValues: map[string]string{ + projectIdFlag: testProjectId, + kmsKeyIdFlag: "key-id", + }, + isValid: false, + }, + { + description: "acl flag conflicts with kms flags", + argValues: fixtureArgValues(), + flagValues: map[string]string{ + projectIdFlag: testProjectId, + aclFlag: testACL1, + kmsKeyIdFlag: "key-id", + kmsKeyringIdFlag: "keyring-id", + kmsKeyVersionFlag: "1", + kmsServiceAccountEmailFlag: "svc@example.com", + }, + isValid: false, + }, { description: "repeated acl flags", argValues: fixtureArgValues(), @@ -199,64 +213,9 @@ func TestParseInput(t *testing.T) { for _, tt := range tests { t.Run(tt.description, func(t *testing.T) { - p := print.NewPrinter() - cmd := NewCmd(&types.CmdParams{Printer: p}) - err := globalflags.Configure(cmd.Flags()) - if err != nil { - t.Fatalf("configure global flags: %v", err) - } - - for flag, value := range tt.flagValues { - err := cmd.Flags().Set(flag, value) - if err != nil { - if !tt.isValid { - return - } - t.Fatalf("setting flag --%s=%s: %v", flag, value, err) - } - } - - for _, value := range tt.aclValues { - err := cmd.Flags().Set(aclFlag, value) - if err != nil { - if !tt.isValid { - return - } - t.Fatalf("setting flag --%s=%s: %v", aclFlag, value, err) - } - } - - err = cmd.ValidateArgs(tt.argValues) - if err != nil { - if !tt.isValid { - return - } - t.Fatalf("error validating args: %v", err) - } - - err = cmd.ValidateRequiredFlags() - if err != nil { - if !tt.isValid { - return - } - t.Fatalf("error validating flags: %v", err) - } - - model, err := parseInput(p, cmd, tt.argValues) - if err != nil { - if !tt.isValid { - return - } - t.Fatalf("error parsing flags: %v", err) - } - - if !tt.isValid { - t.Fatalf("did not fail on invalid input") - } - diff := cmp.Diff(model, tt.expectedModel) - if diff != "" { - t.Fatalf("Data does not match: %s", diff) - } + testutils.TestParseInputWithAdditionalFlags(t, NewCmd, parseInput, tt.expectedModel, tt.argValues, tt.flagValues, map[string][]string{ + aclFlag: tt.aclValues, + }, tt.isValid) }) } } From db8855fb185ea13481bee9d81d1f734061e85d2c Mon Sep 17 00:00:00 2001 From: "Inter, Sven" Date: Fri, 20 Mar 2026 10:02:26 +0100 Subject: [PATCH 05/10] feat(secrets-manager): add KMS key options examples for create and update commands --- internal/cmd/secrets-manager/instance/create/create.go | 3 +++ internal/cmd/secrets-manager/instance/update/update.go | 3 +++ 2 files changed, 6 insertions(+) diff --git a/internal/cmd/secrets-manager/instance/create/create.go b/internal/cmd/secrets-manager/instance/create/create.go index c653e08ec..12e9b2099 100644 --- a/internal/cmd/secrets-manager/instance/create/create.go +++ b/internal/cmd/secrets-manager/instance/create/create.go @@ -55,6 +55,9 @@ func NewCmd(params *types.CmdParams) *cobra.Command { examples.NewExample( `Create a Secrets Manager instance with name "my-instance" and specify IP range which is allowed to access it`, `$ stackit secrets-manager instance create --name my-instance --acl 1.2.3.0/24`), + examples.NewExample( + `Create a Secrets Manager instance with name "my-instance" and configure KMS key options`, + `$ stackit secrets-manager instance create --name my-instance --kms-key-id key-id --kms-keyring-id keyring-id --kms-key-version 1 --kms-service-account-email my-service-account-1234567@sa.stackit.cloud`), ), RunE: func(cmd *cobra.Command, args []string) error { ctx := context.Background() diff --git a/internal/cmd/secrets-manager/instance/update/update.go b/internal/cmd/secrets-manager/instance/update/update.go index d38b1f964..c7a938925 100644 --- a/internal/cmd/secrets-manager/instance/update/update.go +++ b/internal/cmd/secrets-manager/instance/update/update.go @@ -54,6 +54,9 @@ func NewCmd(params *types.CmdParams) *cobra.Command { examples.NewExample( `Update the range of IPs allowed to access a Secrets Manager instance with ID "xxx"`, "$ stackit secrets-manager instance update xxx --acl 1.2.3.0/24"), + examples.NewExample( + `Update the KMS key settings of a Secrets Manager instance with ID "xxx"`, + "$ stackit secrets-manager instance update xxx --kms-key-id key-id --kms-keyring-id keyring-id --kms-key-version 1 --kms-service-account-email my-service-account-1234567@sa.stackit.cloud"), ), RunE: func(cmd *cobra.Command, args []string) error { ctx := context.Background() From 3b95e6b6e351e5c6098bb966269d8da02cf37fe2 Mon Sep 17 00:00:00 2001 From: "Inter, Sven" Date: Fri, 20 Mar 2026 15:44:04 +0100 Subject: [PATCH 06/10] refactor(secrets-manager): change test data --- internal/cmd/secrets-manager/instance/update/update_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/cmd/secrets-manager/instance/update/update_test.go b/internal/cmd/secrets-manager/instance/update/update_test.go index b811faf09..f58c39c40 100644 --- a/internal/cmd/secrets-manager/instance/update/update_test.go +++ b/internal/cmd/secrets-manager/instance/update/update_test.go @@ -182,7 +182,7 @@ func TestParseInput(t *testing.T) { kmsKeyIdFlag: "key-id", kmsKeyringIdFlag: "keyring-id", kmsKeyVersionFlag: "1", - kmsServiceAccountEmailFlag: "svc@example.com", + kmsServiceAccountEmailFlag: "my-service-account-1234567@sa.stackit.cloud", }, isValid: false, }, From 65487995e8019cc89bbccf076b08e2a1bea640b9 Mon Sep 17 00:00:00 2001 From: "Inter, Sven" Date: Fri, 20 Mar 2026 16:04:37 +0100 Subject: [PATCH 07/10] feat(secrets-manager): add KMS key options to create and update instance tests --- .../instance/create/create_test.go | 47 ++++++++++++ .../instance/update/update_test.go | 74 ++++++++++++++++++- 2 files changed, 120 insertions(+), 1 deletion(-) diff --git a/internal/cmd/secrets-manager/instance/create/create_test.go b/internal/cmd/secrets-manager/instance/create/create_test.go index 4cef0d887..e4c6cedda 100644 --- a/internal/cmd/secrets-manager/instance/create/create_test.go +++ b/internal/cmd/secrets-manager/instance/create/create_test.go @@ -25,6 +25,13 @@ var testClient = &secretsmanager.APIClient{} var testProjectId = uuid.NewString() var testInstanceId = uuid.NewString() +const ( + testKmsKeyId = "key-id" + testKmsKeyringId = "keyring-id" + testKmsKeyVersion = int64(1) + testKmsServiceAccountEmail = "my-service-account-1234567@sa.stackit.cloud" +) + func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string { flagValues := map[string]string{ projectIdFlag: testProjectId, @@ -162,6 +169,24 @@ func TestParseInput(t *testing.T) { *model.Acls = append(*model.Acls, "1.2.3.4/32") }), }, + { + description: "kms flags", + flagValues: fixtureFlagValues(func(flagValues map[string]string) { + delete(flagValues, aclFlag) + flagValues[kmsKeyIdFlag] = testKmsKeyId + flagValues[kmsKeyringIdFlag] = testKmsKeyringId + flagValues[kmsKeyVersionFlag] = "1" + flagValues[kmsServiceAccountEmailFlag] = testKmsServiceAccountEmail + }), + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + model.Acls = nil + model.KmsKeyId = utils.Ptr(testKmsKeyId) + model.KmsKeyringId = utils.Ptr(testKmsKeyringId) + model.KmsKeyVersion = utils.Ptr(testKmsKeyVersion) + model.KmsServiceAccountEmail = utils.Ptr(testKmsServiceAccountEmail) + }), + }, { description: "project id missing", flagValues: fixtureFlagValues(func(flagValues map[string]string) { @@ -205,6 +230,28 @@ func TestBuildCreateInstanceRequest(t *testing.T) { model: fixtureInputModel(), expectedRequest: fixtureRequest(), }, + { + description: "with kms", + model: fixtureInputModel(func(model *inputModel) { + model.Acls = nil + model.KmsKeyId = utils.Ptr(testKmsKeyId) + model.KmsKeyringId = utils.Ptr(testKmsKeyringId) + model.KmsKeyVersion = utils.Ptr(testKmsKeyVersion) + model.KmsServiceAccountEmail = utils.Ptr(testKmsServiceAccountEmail) + }), + expectedRequest: fixtureRequest(func(request *secretsmanager.ApiCreateInstanceRequest) { + payload := secretsmanager.CreateInstancePayload{ + Name: utils.Ptr("example"), + KmsKey: &secretsmanager.KmsKeyPayload{ + KeyId: utils.Ptr(testKmsKeyId), + KeyRingId: utils.Ptr(testKmsKeyringId), + KeyVersion: utils.Ptr(testKmsKeyVersion), + ServiceAccountEmail: utils.Ptr(testKmsServiceAccountEmail), + }, + } + *request = (*request).CreateInstancePayload(payload) + }), + }, } for _, tt := range tests { diff --git a/internal/cmd/secrets-manager/instance/update/update_test.go b/internal/cmd/secrets-manager/instance/update/update_test.go index f58c39c40..577fb58fe 100644 --- a/internal/cmd/secrets-manager/instance/update/update_test.go +++ b/internal/cmd/secrets-manager/instance/update/update_test.go @@ -31,6 +31,13 @@ var ( testInstanceId = uuid.NewString() ) +const ( + testKmsKeyId = "key-id" + testKmsKeyringId = "keyring-id" + testKmsKeyVersion = int64(1) + testKmsServiceAccountEmail = "my-service-account-1234567@sa.stackit.cloud" +) + func fixtureArgValues(mods ...func(argValues []string)) []string { argValues := []string{ testInstanceId, @@ -80,6 +87,23 @@ func fixtureRequest(mods ...func(request *secretsmanager.ApiUpdateACLsRequest)) return request } +func fixtureUpdateInstanceRequest(mods ...func(request *secretsmanager.ApiUpdateInstanceRequest)) secretsmanager.ApiUpdateInstanceRequest { + request := testClient.UpdateInstance(testCtx, testProjectId, testInstanceId) + request = request.UpdateInstancePayload(secretsmanager.UpdateInstancePayload{ + KmsKey: &secretsmanager.KmsKeyPayload{ + KeyId: utils.Ptr(testKmsKeyId), + KeyRingId: utils.Ptr(testKmsKeyringId), + KeyVersion: utils.Ptr(testKmsKeyVersion), + ServiceAccountEmail: utils.Ptr(testKmsServiceAccountEmail), + }, + }) + + for _, mod := range mods { + mod(&request) + } + return request +} + func TestParseInput(t *testing.T) { tests := []struct { description string @@ -209,6 +233,25 @@ func TestParseInput(t *testing.T) { ) }), }, + { + description: "kms flags", + argValues: fixtureArgValues(), + flagValues: map[string]string{ + projectIdFlag: testProjectId, + kmsKeyIdFlag: testKmsKeyId, + kmsKeyringIdFlag: testKmsKeyringId, + kmsKeyVersionFlag: "1", + kmsServiceAccountEmailFlag: testKmsServiceAccountEmail, + }, + isValid: true, + expectedModel: fixtureInputModel(func(model *inputModel) { + model.Acls = nil + model.KmsKeyId = utils.Ptr(testKmsKeyId) + model.KmsKeyringId = utils.Ptr(testKmsKeyringId) + model.KmsKeyVersion = utils.Ptr(testKmsKeyVersion) + model.KmsServiceAccountEmail = utils.Ptr(testKmsServiceAccountEmail) + }), + }, } for _, tt := range tests { @@ -246,8 +289,12 @@ func TestBuildRequest(t *testing.T) { for _, tt := range tests { t.Run(tt.description, func(t *testing.T) { request := buildRequest(testCtx, tt.model, testClient) + aclRequest, ok := request.(secretsmanager.ApiUpdateACLsRequest) + if !ok { + t.Fatalf("expected ACL update request, got %T", request) + } - diff := cmp.Diff(request, tt.expectedRequest, + diff := cmp.Diff(aclRequest, tt.expectedRequest, cmp.AllowUnexported(tt.expectedRequest), cmpopts.EquateComparable(testCtx), ) @@ -257,3 +304,28 @@ func TestBuildRequest(t *testing.T) { }) } } + +func TestBuildRequestKms(t *testing.T) { + model := fixtureInputModel(func(model *inputModel) { + model.Acls = nil + model.KmsKeyId = utils.Ptr(testKmsKeyId) + model.KmsKeyringId = utils.Ptr(testKmsKeyringId) + model.KmsKeyVersion = utils.Ptr(testKmsKeyVersion) + model.KmsServiceAccountEmail = utils.Ptr(testKmsServiceAccountEmail) + }) + + request := buildRequest(testCtx, model, testClient) + updateRequest, ok := request.(secretsmanager.ApiUpdateInstanceRequest) + if !ok { + t.Fatalf("expected instance update request, got %T", request) + } + + expectedRequest := fixtureUpdateInstanceRequest() + diff := cmp.Diff(updateRequest, expectedRequest, + cmp.AllowUnexported(expectedRequest), + cmpopts.EquateComparable(testCtx), + ) + if diff != "" { + t.Fatalf("Data does not match: %s", diff) + } +} From 4e6de2a083e5b306da161a96df0e0b691d485f3c Mon Sep 17 00:00:00 2001 From: "Inter, Sven" Date: Fri, 20 Mar 2026 16:47:48 +0100 Subject: [PATCH 08/10] feat(secrets-manager): add KMS key details to instance output --- .../instance/describe/describe.go | 14 ++++++++++++++ .../instance/describe/describe_test.go | 16 ++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/internal/cmd/secrets-manager/instance/describe/describe.go b/internal/cmd/secrets-manager/instance/describe/describe.go index bbd162bff..75c8cbd7c 100644 --- a/internal/cmd/secrets-manager/instance/describe/describe.go +++ b/internal/cmd/secrets-manager/instance/describe/describe.go @@ -128,6 +128,17 @@ func outputResult(p *print.Printer, outputFormat string, instance *secretsmanage table.AddSeparator() table.AddRow("CREATION DATE", utils.PtrString(instance.CreationStartDate)) table.AddSeparator() + kmsKey := instance.KmsKey + showKms := kmsKey != nil && (kmsKey.KeyId != nil || kmsKey.KeyRingId != nil || kmsKey.KeyVersion != nil || kmsKey.ServiceAccountEmail != nil) + if showKms { + table.AddRow("KMS KEY ID", utils.PtrString(kmsKey.KeyId)) + table.AddSeparator() + table.AddRow("KMS KEYRING ID", utils.PtrString(kmsKey.KeyRingId)) + table.AddSeparator() + table.AddRow("KMS KEY VERSION", utils.PtrString(kmsKey.KeyVersion)) + table.AddSeparator() + table.AddRow("KMS SERVICE ACCOUNT EMAIL", utils.PtrString(kmsKey.ServiceAccountEmail)) + } // Only show ACL if it's present and not empty if aclList.Acls != nil && len(*aclList.Acls) > 0 { var cidrs []string @@ -136,6 +147,9 @@ func outputResult(p *print.Printer, outputFormat string, instance *secretsmanage cidrs = append(cidrs, *acl.Cidr) } + if showKms { + table.AddSeparator() + } table.AddRow("ACL", strings.Join(cidrs, ",")) } err := table.Display(p) diff --git a/internal/cmd/secrets-manager/instance/describe/describe_test.go b/internal/cmd/secrets-manager/instance/describe/describe_test.go index c1e3e0bb7..fb36f10bb 100644 --- a/internal/cmd/secrets-manager/instance/describe/describe_test.go +++ b/internal/cmd/secrets-manager/instance/describe/describe_test.go @@ -9,6 +9,7 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/testutils" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" @@ -247,6 +248,21 @@ func TestOutputResult(t *testing.T) { }, wantErr: false, }, + { + name: "instance with kms key", + args: args{ + instance: &secretsmanager.Instance{ + KmsKey: &secretsmanager.KmsKeyPayload{ + KeyId: utils.Ptr("key-id"), + KeyRingId: utils.Ptr("keyring-id"), + KeyVersion: utils.Ptr(int64(1)), + ServiceAccountEmail: utils.Ptr("my-service-account-1234567@sa.stackit.cloud"), + }, + }, + aclList: &secretsmanager.ListACLsResponse{}, + }, + wantErr: false, + }, } p := print.NewPrinter() p.Cmd = NewCmd(&types.CmdParams{Printer: p}) From d1d74ade547ee5e140e0202d186e5892bdc8618b Mon Sep 17 00:00:00 2001 From: "Inter, Sven" Date: Fri, 20 Mar 2026 16:48:41 +0100 Subject: [PATCH 09/10] feat(secrets-manager): add docs --- docs/stackit_secrets-manager_instance_create.md | 13 ++++++++++--- docs/stackit_secrets-manager_instance_update.md | 11 +++++++++-- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/docs/stackit_secrets-manager_instance_create.md b/docs/stackit_secrets-manager_instance_create.md index 379de7785..65108008a 100644 --- a/docs/stackit_secrets-manager_instance_create.md +++ b/docs/stackit_secrets-manager_instance_create.md @@ -18,14 +18,21 @@ stackit secrets-manager instance create [flags] Create a Secrets Manager instance with name "my-instance" and specify IP range which is allowed to access it $ stackit secrets-manager instance create --name my-instance --acl 1.2.3.0/24 + + Create a Secrets Manager instance with name "my-instance" and configure KMS key options + $ stackit secrets-manager instance create --name my-instance --kms-key-id key-id --kms-keyring-id keyring-id --kms-key-version 1 --kms-service-account-email my-service-account-1234567@sa.stackit.cloud ``` ### Options ``` - --acl strings List of IP networks in CIDR notation which are allowed to access this instance (default []) - -h, --help Help for "stackit secrets-manager instance create" - -n, --name string Instance name + --acl strings List of IP networks in CIDR notation which are allowed to access this instance (default []) + -h, --help Help for "stackit secrets-manager instance create" + --kms-key-id string ID of the KMS key to use for encryption + --kms-key-version int Version of the KMS key + --kms-keyring-id string ID of the KMS key ring + --kms-service-account-email string Service account email for KMS access + -n, --name string Instance name ``` ### Options inherited from parent commands diff --git a/docs/stackit_secrets-manager_instance_update.md b/docs/stackit_secrets-manager_instance_update.md index cf40d3c1a..312588314 100644 --- a/docs/stackit_secrets-manager_instance_update.md +++ b/docs/stackit_secrets-manager_instance_update.md @@ -15,13 +15,20 @@ stackit secrets-manager instance update INSTANCE_ID [flags] ``` Update the range of IPs allowed to access a Secrets Manager instance with ID "xxx" $ stackit secrets-manager instance update xxx --acl 1.2.3.0/24 + + Update the KMS key settings of a Secrets Manager instance with ID "xxx" + $ stackit secrets-manager instance update xxx --kms-key-id key-id --kms-keyring-id keyring-id --kms-key-version 1 --kms-service-account-email my-service-account-1234567@sa.stackit.cloud ``` ### Options ``` - --acl strings List of IP networks in CIDR notation which are allowed to access this instance (default []) - -h, --help Help for "stackit secrets-manager instance update" + --acl strings List of IP networks in CIDR notation which are allowed to access this instance (default []) + -h, --help Help for "stackit secrets-manager instance update" + --kms-key-id string ID of the KMS key to use for encryption + --kms-key-version int Version of the KMS key + --kms-keyring-id string ID of the KMS key ring + --kms-service-account-email string Service account email for KMS access ``` ### Options inherited from parent commands From 2dbe0ebc4c0ba4a4bf797fdef45e287fddd2d285 Mon Sep 17 00:00:00 2001 From: "Inter, Sven" Date: Tue, 24 Mar 2026 19:07:51 +0100 Subject: [PATCH 10/10] fix(secrets-manager): include instance name in payload, as it is required --- .../cmd/secrets-manager/instance/update/update.go | 12 ++++++++---- .../secrets-manager/instance/update/update_test.go | 6 ++++-- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/internal/cmd/secrets-manager/instance/update/update.go b/internal/cmd/secrets-manager/instance/update/update.go index c7a938925..8870ebe80 100644 --- a/internal/cmd/secrets-manager/instance/update/update.go +++ b/internal/cmd/secrets-manager/instance/update/update.go @@ -75,6 +75,9 @@ func NewCmd(params *types.CmdParams) *cobra.Command { if err != nil { params.Printer.Debug(print.ErrorLevel, "get instance name: %v", err) instanceLabel = model.InstanceId + if model.KmsKeyId != nil { + return fmt.Errorf("get instance name: %w", err) + } } prompt := fmt.Sprintf("Are you sure you want to update instance %q?", instanceLabel) @@ -84,7 +87,7 @@ func NewCmd(params *types.CmdParams) *cobra.Command { } // Call API - req := buildRequest(ctx, model, apiClient) + req := buildRequest(ctx, model, instanceLabel, apiClient) switch request := req.(type) { case secretsmanager.ApiUpdateInstanceRequest: err = request.Execute() @@ -142,18 +145,19 @@ func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inpu return &model, nil } -func buildRequest(ctx context.Context, model *inputModel, apiClient *secretsmanager.APIClient) interface{ Execute() error } { +func buildRequest(ctx context.Context, model *inputModel, instanceName string, apiClient *secretsmanager.APIClient) interface{ Execute() error } { if model.KmsKeyId != nil { - return buildUpdateInstanceRequest(ctx, model, apiClient) + return buildUpdateInstanceRequest(ctx, model, instanceName, apiClient) } return buildUpdateACLsRequest(ctx, model, apiClient) } -func buildUpdateInstanceRequest(ctx context.Context, model *inputModel, apiClient *secretsmanager.APIClient) secretsmanager.ApiUpdateInstanceRequest { +func buildUpdateInstanceRequest(ctx context.Context, model *inputModel, instanceName string, apiClient *secretsmanager.APIClient) secretsmanager.ApiUpdateInstanceRequest { req := apiClient.UpdateInstance(ctx, model.ProjectId, model.InstanceId) payload := secretsmanager.UpdateInstancePayload{ + Name: &instanceName, KmsKey: &secretsmanager.KmsKeyPayload{ KeyId: model.KmsKeyId, KeyRingId: model.KmsKeyringId, diff --git a/internal/cmd/secrets-manager/instance/update/update_test.go b/internal/cmd/secrets-manager/instance/update/update_test.go index 577fb58fe..d8c77b942 100644 --- a/internal/cmd/secrets-manager/instance/update/update_test.go +++ b/internal/cmd/secrets-manager/instance/update/update_test.go @@ -32,6 +32,7 @@ var ( ) const ( + testInstanceName = "test-instance" testKmsKeyId = "key-id" testKmsKeyringId = "keyring-id" testKmsKeyVersion = int64(1) @@ -90,6 +91,7 @@ func fixtureRequest(mods ...func(request *secretsmanager.ApiUpdateACLsRequest)) func fixtureUpdateInstanceRequest(mods ...func(request *secretsmanager.ApiUpdateInstanceRequest)) secretsmanager.ApiUpdateInstanceRequest { request := testClient.UpdateInstance(testCtx, testProjectId, testInstanceId) request = request.UpdateInstancePayload(secretsmanager.UpdateInstancePayload{ + Name: utils.Ptr(testInstanceName), KmsKey: &secretsmanager.KmsKeyPayload{ KeyId: utils.Ptr(testKmsKeyId), KeyRingId: utils.Ptr(testKmsKeyringId), @@ -288,7 +290,7 @@ func TestBuildRequest(t *testing.T) { for _, tt := range tests { t.Run(tt.description, func(t *testing.T) { - request := buildRequest(testCtx, tt.model, testClient) + request := buildRequest(testCtx, tt.model, testInstanceName, testClient) aclRequest, ok := request.(secretsmanager.ApiUpdateACLsRequest) if !ok { t.Fatalf("expected ACL update request, got %T", request) @@ -314,7 +316,7 @@ func TestBuildRequestKms(t *testing.T) { model.KmsServiceAccountEmail = utils.Ptr(testKmsServiceAccountEmail) }) - request := buildRequest(testCtx, model, testClient) + request := buildRequest(testCtx, model, testInstanceName, testClient) updateRequest, ok := request.(secretsmanager.ApiUpdateInstanceRequest) if !ok { t.Fatalf("expected instance update request, got %T", request)