Skip to content

Commit deff194

Browse files
authored
Onboard Secrets Manager (ACL) (#172)
* Onboard Secrets Manager (ACLs): create command (#163) * Onboard Secrets Manager (ACL): update command (#167) * Onboard Secrets Manager (ACL): describe command (#168)
1 parent 6f6eb6a commit deff194

File tree

10 files changed

+690
-16
lines changed

10 files changed

+690
-16
lines changed

docs/stackit_secrets-manager_instance.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,5 @@ stackit secrets-manager instance [flags]
3232
* [stackit secrets-manager instance delete](./stackit_secrets-manager_instance_delete.md) - Deletes a Secrets Manager instance
3333
* [stackit secrets-manager instance describe](./stackit_secrets-manager_instance_describe.md) - Shows details of a Secrets Manager instance
3434
* [stackit secrets-manager instance list](./stackit_secrets-manager_instance_list.md) - Lists all Secrets Manager instances
35+
* [stackit secrets-manager instance update](./stackit_secrets-manager_instance_update.md) - Updates a Secrets Manager instance
3536

docs/stackit_secrets-manager_instance_create.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,15 @@ stackit secrets-manager instance create [flags]
1515
```
1616
Create a Secrets Manager instance with name "my-instance"
1717
$ stackit secrets-manager instance create --name my-instance
18+
19+
Create a Secrets Manager instance with name "my-instance" and specify IP range which is allowed to access it
20+
$ stackit secrets-manager instance create --name my-instance --acl 1.2.3.0/24
1821
```
1922

2023
### Options
2124

2225
```
26+
--acl strings List of IP networks in CIDR notation which are allowed to access this instance (default [])
2327
-h, --help Help for "stackit secrets-manager instance create"
2428
-n, --name string Instance name
2529
```
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
## stackit secrets-manager instance update
2+
3+
Updates a Secrets Manager instance
4+
5+
### Synopsis
6+
7+
Updates a Secrets Manager instance.
8+
9+
```
10+
stackit secrets-manager instance update INSTANCE_ID [flags]
11+
```
12+
13+
### Examples
14+
15+
```
16+
Update the range of IPs allowed to access a Secrets Manager instance with ID "xxx"
17+
$ stackit secrets-manager instance update xxx --acl 1.2.3.0/24
18+
```
19+
20+
### Options
21+
22+
```
23+
--acl strings List of IP networks in CIDR notation which are allowed to access this instance (default [])
24+
-h, --help Help for "stackit secrets-manager instance update"
25+
```
26+
27+
### Options inherited from parent commands
28+
29+
```
30+
-y, --assume-yes If set, skips all confirmation prompts
31+
--async If set, runs the command asynchronously
32+
-o, --output-format string Output format, one of ["json" "pretty"]
33+
-p, --project-id string Project ID
34+
```
35+
36+
### SEE ALSO
37+
38+
* [stackit secrets-manager instance](./stackit_secrets-manager_instance.md) - Provides functionality for Secrets Manager instances
39+

internal/cmd/secrets-manager/instance/create/create.go

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,22 @@ import (
1212
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
1313
"github.com/stackitcloud/stackit-cli/internal/pkg/projectname"
1414
"github.com/stackitcloud/stackit-cli/internal/pkg/services/secrets-manager/client"
15+
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
1516
"github.com/stackitcloud/stackit-sdk-go/services/secretsmanager"
1617

1718
"github.com/spf13/cobra"
1819
)
1920

2021
const (
2122
instanceNameFlag = "name"
23+
aclFlag = "acl"
2224
)
2325

2426
type inputModel struct {
2527
*globalflags.GlobalFlagModel
2628

2729
InstanceName *string
30+
Acls *[]string
2831
}
2932

3033
func NewCmd() *cobra.Command {
@@ -37,6 +40,9 @@ func NewCmd() *cobra.Command {
3740
examples.NewExample(
3841
`Create a Secrets Manager instance with name "my-instance"`,
3942
`$ stackit secrets-manager instance create --name my-instance`),
43+
examples.NewExample(
44+
`Create a Secrets Manager instance with name "my-instance" and specify IP range which is allowed to access it`,
45+
`$ stackit secrets-manager instance create --name my-instance --acl 1.2.3.0/24`),
4046
),
4147
RunE: func(cmd *cobra.Command, args []string) error {
4248
ctx := context.Background()
@@ -65,14 +71,26 @@ func NewCmd() *cobra.Command {
6571
}
6672
}
6773

68-
// Call API
69-
req := buildRequest(ctx, model, apiClient)
74+
// Call API to create instance
75+
req := buildCreateInstanceRequest(ctx, model, apiClient)
7076
resp, err := req.Execute()
7177
if err != nil {
7278
return fmt.Errorf("create Secrets Manager instance: %w", err)
7379
}
7480
instanceId := *resp.Id
7581

82+
// Call API to create ACLs for instance, if ACLs are provided
83+
if model.Acls != nil {
84+
updateReq := buildUpdateACLsRequest(ctx, model, instanceId, apiClient)
85+
err = updateReq.Execute()
86+
if err != nil {
87+
return fmt.Errorf(`the Secrets Manager instance was successfully created, but the configuration of the ACLs failed. The default behavior is to have no ACL.
88+
89+
If you want to retry configuring the ACLs, you can do it via:
90+
$ stackit secrets-manager instance update %s --acl %s`, instanceId, *model.Acls)
91+
}
92+
}
93+
7694
cmd.Printf("Created instance for project %q. Instance ID: %s\n", projectLabel, instanceId)
7795
return nil
7896
},
@@ -83,6 +101,7 @@ func NewCmd() *cobra.Command {
83101

84102
func configureFlags(cmd *cobra.Command) {
85103
cmd.Flags().StringP(instanceNameFlag, "n", "", "Instance name")
104+
cmd.Flags().Var(flags.CIDRSliceFlag(), aclFlag, "List of IP networks in CIDR notation which are allowed to access this instance")
86105

87106
err := flags.MarkFlagsRequired(cmd, instanceNameFlag)
88107
cobra.CheckErr(err)
@@ -97,10 +116,11 @@ func parseInput(cmd *cobra.Command) (*inputModel, error) {
97116
return &inputModel{
98117
GlobalFlagModel: globalFlags,
99118
InstanceName: flags.FlagToStringPointer(cmd, instanceNameFlag),
119+
Acls: flags.FlagToStringSlicePointer(cmd, aclFlag),
100120
}, nil
101121
}
102122

103-
func buildRequest(ctx context.Context, model *inputModel, apiClient *secretsmanager.APIClient) secretsmanager.ApiCreateInstanceRequest {
123+
func buildCreateInstanceRequest(ctx context.Context, model *inputModel, apiClient *secretsmanager.APIClient) secretsmanager.ApiCreateInstanceRequest {
104124
req := apiClient.CreateInstance(ctx, model.ProjectId)
105125

106126
req = req.CreateInstancePayload(secretsmanager.CreateInstancePayload{
@@ -109,3 +129,17 @@ func buildRequest(ctx context.Context, model *inputModel, apiClient *secretsmana
109129

110130
return req
111131
}
132+
133+
func buildUpdateACLsRequest(ctx context.Context, model *inputModel, instanceId string, apiClient *secretsmanager.APIClient) secretsmanager.ApiUpdateACLsRequest {
134+
req := apiClient.UpdateACLs(ctx, model.ProjectId, instanceId)
135+
136+
cidrs := make([]secretsmanager.AclUpdate, len(*model.Acls))
137+
138+
for i, acl := range *model.Acls {
139+
cidrs[i] = secretsmanager.AclUpdate{Cidr: utils.Ptr(acl)}
140+
}
141+
142+
req = req.UpdateACLsPayload(secretsmanager.UpdateACLsPayload{Cidrs: &cidrs})
143+
144+
return req
145+
}

internal/cmd/secrets-manager/instance/create/create_test.go

Lines changed: 116 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,13 @@ type testCtxKey struct{}
1919
var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo")
2020
var testClient = &secretsmanager.APIClient{}
2121
var testProjectId = uuid.NewString()
22+
var testInstanceId = uuid.NewString()
2223

2324
func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string {
2425
flagValues := map[string]string{
2526
projectIdFlag: testProjectId,
2627
instanceNameFlag: "example",
28+
aclFlag: "198.51.100.14/24",
2729
}
2830
for _, mod := range mods {
2931
mod(flagValues)
@@ -37,6 +39,7 @@ func fixtureInputModel(mods ...func(model *inputModel)) *inputModel {
3739
ProjectId: testProjectId,
3840
},
3941
InstanceName: utils.Ptr("example"),
42+
Acls: utils.Ptr([]string{"198.51.100.14/24"}),
4043
}
4144
for _, mod := range mods {
4245
mod(model)
@@ -55,10 +58,24 @@ func fixtureRequest(mods ...func(request *secretsmanager.ApiCreateInstanceReques
5558
return request
5659
}
5760

61+
func fixtureUpdateACLsRequest(mods ...func(request *secretsmanager.ApiUpdateACLsRequest)) secretsmanager.ApiUpdateACLsRequest {
62+
request := testClient.UpdateACLs(testCtx, testProjectId, testInstanceId)
63+
request = request.UpdateACLsPayload(secretsmanager.UpdateACLsPayload{
64+
Cidrs: utils.Ptr([]secretsmanager.AclUpdate{
65+
{Cidr: utils.Ptr("198.51.100.14/24")},
66+
})})
67+
68+
for _, mod := range mods {
69+
mod(&request)
70+
}
71+
return request
72+
}
73+
5874
func TestParseInput(t *testing.T) {
5975
tests := []struct {
6076
description string
6177
flagValues map[string]string
78+
aclValues []string
6279
isValid bool
6380
expectedModel *inputModel
6481
}{
@@ -94,6 +111,55 @@ func TestParseInput(t *testing.T) {
94111
}),
95112
isValid: false,
96113
},
114+
{
115+
description: "acl missing",
116+
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
117+
delete(flagValues, aclFlag)
118+
}),
119+
isValid: true,
120+
expectedModel: fixtureInputModel(func(model *inputModel) {
121+
model.Acls = nil
122+
}),
123+
},
124+
{
125+
description: "acl empty",
126+
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
127+
flagValues[aclFlag] = ""
128+
}),
129+
isValid: false,
130+
},
131+
{
132+
description: "repeated acl flags",
133+
flagValues: fixtureFlagValues(),
134+
aclValues: []string{"198.51.100.14/24", "198.51.100.14/32"},
135+
isValid: true,
136+
expectedModel: fixtureInputModel(func(model *inputModel) {
137+
model.Acls = utils.Ptr(
138+
append(*model.Acls, "198.51.100.14/24", "198.51.100.14/32"),
139+
)
140+
}),
141+
},
142+
{
143+
description: "repeated acl flag with list value",
144+
flagValues: fixtureFlagValues(),
145+
aclValues: []string{"198.51.100.14/24,198.51.100.14/32"},
146+
isValid: true,
147+
expectedModel: fixtureInputModel(func(model *inputModel) {
148+
model.Acls = utils.Ptr(
149+
append(*model.Acls, "198.51.100.14/24", "198.51.100.14/32"),
150+
)
151+
}),
152+
},
153+
{
154+
description: "multiple acls",
155+
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
156+
flagValues[aclFlag] = "198.51.100.14/24,1.2.3.4/32"
157+
}),
158+
isValid: true,
159+
expectedModel: fixtureInputModel(func(model *inputModel) {
160+
*model.Acls = append(*model.Acls, "1.2.3.4/32")
161+
}),
162+
},
97163
{
98164
description: "project id missing",
99165
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
@@ -135,6 +201,16 @@ func TestParseInput(t *testing.T) {
135201
}
136202
}
137203

204+
for _, value := range tt.aclValues {
205+
err := cmd.Flags().Set(aclFlag, value)
206+
if err != nil {
207+
if !tt.isValid {
208+
return
209+
}
210+
t.Fatalf("setting flag --%s=%s: %v", aclFlag, value, err)
211+
}
212+
}
213+
138214
err = cmd.ValidateRequiredFlags()
139215
if err != nil {
140216
if !tt.isValid {
@@ -162,7 +238,7 @@ func TestParseInput(t *testing.T) {
162238
}
163239
}
164240

165-
func TestBuildRequest(t *testing.T) {
241+
func TestBuildCreateInstanceRequest(t *testing.T) {
166242
tests := []struct {
167243
description string
168244
model *inputModel
@@ -177,7 +253,45 @@ func TestBuildRequest(t *testing.T) {
177253

178254
for _, tt := range tests {
179255
t.Run(tt.description, func(t *testing.T) {
180-
request := buildRequest(testCtx, tt.model, testClient)
256+
request := buildCreateInstanceRequest(testCtx, tt.model, testClient)
257+
258+
diff := cmp.Diff(request, tt.expectedRequest,
259+
cmp.AllowUnexported(tt.expectedRequest),
260+
cmpopts.EquateComparable(testCtx),
261+
)
262+
if diff != "" {
263+
t.Fatalf("Data does not match: %s", diff)
264+
}
265+
})
266+
}
267+
}
268+
func TestBuildCreateACLRequests(t *testing.T) {
269+
tests := []struct {
270+
description string
271+
model *inputModel
272+
expectedRequest secretsmanager.ApiUpdateACLsRequest
273+
}{
274+
{
275+
description: "base",
276+
model: fixtureInputModel(),
277+
expectedRequest: fixtureUpdateACLsRequest(),
278+
},
279+
{
280+
description: "multiple ACLs",
281+
model: fixtureInputModel(func(model *inputModel) {
282+
*model.Acls = append(*model.Acls, "1.2.3.4/32")
283+
}),
284+
expectedRequest: fixtureUpdateACLsRequest().UpdateACLsPayload(secretsmanager.UpdateACLsPayload{
285+
Cidrs: utils.Ptr([]secretsmanager.AclUpdate{
286+
{Cidr: utils.Ptr("198.51.100.14/24")},
287+
{Cidr: utils.Ptr("1.2.3.4/32")},
288+
})}),
289+
},
290+
}
291+
292+
for _, tt := range tests {
293+
t.Run(tt.description, func(t *testing.T) {
294+
request := buildUpdateACLsRequest(testCtx, tt.model, testInstanceId, testClient)
181295

182296
diff := cmp.Diff(request, tt.expectedRequest,
183297
cmp.AllowUnexported(tt.expectedRequest),

0 commit comments

Comments
 (0)