Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Onboard Secrets Manager (ACLs): create command #163

Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/stackit_secrets-manager_instance_create.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,15 @@ stackit secrets-manager instance create [flags]
```
Create a Secrets Manager instance with name "my-instance"
$ stackit secrets-manager instance create --name my-instance

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
```

### 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
```
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ require (
github.com/stackitcloud/stackit-sdk-go/services/opensearch v0.10.1
github.com/stackitcloud/stackit-sdk-go/services/postgresflex v0.10.0
github.com/stackitcloud/stackit-sdk-go/services/resourcemanager v0.7.7
github.com/stackitcloud/stackit-sdk-go/services/secretsmanager v0.5.6
github.com/stackitcloud/stackit-sdk-go/services/secretsmanager v0.6.0
github.com/stackitcloud/stackit-sdk-go/services/serviceaccount v0.3.6
github.com/stackitcloud/stackit-sdk-go/services/ske v0.10.1
github.com/zalando/go-keyring v0.2.4
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,8 @@ github.com/stackitcloud/stackit-sdk-go/services/redis v0.10.1 h1:/tRad17HUcGRm44
github.com/stackitcloud/stackit-sdk-go/services/redis v0.10.1/go.mod h1:vR/0cYTcVrPTTAHJGH2VT0H2g1D+wlx1n2WiAo6r5LI=
github.com/stackitcloud/stackit-sdk-go/services/resourcemanager v0.7.7 h1:yFxTdMj5al2pR4ZIOKKxoN8CHo2kTylurArt+jJMzxI=
github.com/stackitcloud/stackit-sdk-go/services/resourcemanager v0.7.7/go.mod h1:GvNV2GR0x0VGHzixGNgAJibqjwiVFwbxakpyu+qdijc=
github.com/stackitcloud/stackit-sdk-go/services/secretsmanager v0.5.6 h1:SC9p/+HP6e90NorFD6bRg6G+0VxJ7kRoihhbfjw2Mbs=
github.com/stackitcloud/stackit-sdk-go/services/secretsmanager v0.5.6/go.mod h1:vwyWRIKMD7J6S4qlnNP8uefouHtIZba9WpnoihSZByU=
github.com/stackitcloud/stackit-sdk-go/services/secretsmanager v0.6.0 h1:VC7VWadRo8r0eQUXMrYv6vEyS/5acW8faMSv9lxQMgw=
github.com/stackitcloud/stackit-sdk-go/services/secretsmanager v0.6.0/go.mod h1:KRoLXZdH8yuO6FBu2Grl5VGqW9arH03qYAC0P6H8h9o=
github.com/stackitcloud/stackit-sdk-go/services/serviceaccount v0.3.6 h1:3kkNh2kHi55w9dgh0MC1Zbn8fDpYxcXl3tvYjH8t9xo=
github.com/stackitcloud/stackit-sdk-go/services/serviceaccount v0.3.6/go.mod h1:OOciROyQxPOYLo8OM/DE5ESH11+DvAyRt6wg7R+HVkg=
github.com/stackitcloud/stackit-sdk-go/services/ske v0.10.1 h1:MZABtJ8HFOKG3KCCv5duibxBSAU1zTFAO0V9bso3N9M=
Expand Down
40 changes: 37 additions & 3 deletions internal/cmd/secrets-manager/instance/create/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,22 @@ import (
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
"github.com/stackitcloud/stackit-cli/internal/pkg/projectname"
"github.com/stackitcloud/stackit-cli/internal/pkg/services/secrets-manager/client"
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
"github.com/stackitcloud/stackit-sdk-go/services/secretsmanager"

"github.com/spf13/cobra"
)

const (
instanceNameFlag = "name"
aclFlag = "acl"
)

type inputModel struct {
*globalflags.GlobalFlagModel

InstanceName *string
Acls *[]string
}

func NewCmd() *cobra.Command {
Expand All @@ -37,6 +40,9 @@ func NewCmd() *cobra.Command {
examples.NewExample(
`Create a Secrets Manager instance with name "my-instance"`,
`$ stackit secrets-manager instance create --name my-instance`),
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`),
),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
Expand Down Expand Up @@ -65,14 +71,26 @@ func NewCmd() *cobra.Command {
}
}

// Call API
req := buildRequest(ctx, model, apiClient)
// Call API to create instance
req := buildCreateInstanceRequest(ctx, model, apiClient)
resp, err := req.Execute()
if err != nil {
return fmt.Errorf("create Secrets Manager instance: %w", err)
}
instanceId := *resp.Id

// Call API to create ACLs for instance, if ACLs are provided
if model.Acls != nil {
updateReq := buildUpdateACLsRequest(ctx, model, instanceId, apiClient)
err = updateReq.Execute()
if err != nil {
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.

If you want to retry configuring the ACLs, you can do it via:
$ stackit secrets-manager instance update %s --acl %s`, instanceId, *model.Acls)
}
}

cmd.Printf("Created instance for project %q. Instance ID: %s\n", projectLabel, instanceId)
return nil
},
Expand All @@ -83,6 +101,7 @@ func NewCmd() *cobra.Command {

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")

err := flags.MarkFlagsRequired(cmd, instanceNameFlag)
cobra.CheckErr(err)
Expand All @@ -97,10 +116,11 @@ func parseInput(cmd *cobra.Command) (*inputModel, error) {
return &inputModel{
GlobalFlagModel: globalFlags,
InstanceName: flags.FlagToStringPointer(cmd, instanceNameFlag),
Acls: flags.FlagToStringSlicePointer(cmd, aclFlag),
}, nil
}

func buildRequest(ctx context.Context, model *inputModel, apiClient *secretsmanager.APIClient) secretsmanager.ApiCreateInstanceRequest {
func buildCreateInstanceRequest(ctx context.Context, model *inputModel, apiClient *secretsmanager.APIClient) secretsmanager.ApiCreateInstanceRequest {
req := apiClient.CreateInstance(ctx, model.ProjectId)

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

return req
}

func buildUpdateACLsRequest(ctx context.Context, model *inputModel, instanceId string, apiClient *secretsmanager.APIClient) secretsmanager.ApiUpdateACLsRequest {
req := apiClient.UpdateACLs(ctx, model.ProjectId, instanceId)

cidrs := []secretsmanager.AclUpdate{}
DiogoFerrao marked this conversation as resolved.
Show resolved Hide resolved

for _, acl := range *model.Acls {
cidrs = append(cidrs, secretsmanager.AclUpdate{Cidr: utils.Ptr(acl)})
}

req = req.UpdateACLsPayload(secretsmanager.UpdateACLsPayload{Cidrs: &cidrs})

return req
}
118 changes: 116 additions & 2 deletions internal/cmd/secrets-manager/instance/create/create_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,13 @@ type testCtxKey struct{}
var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo")
var testClient = &secretsmanager.APIClient{}
var testProjectId = uuid.NewString()
var testInstanceId = uuid.NewString()

func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string {
flagValues := map[string]string{
projectIdFlag: testProjectId,
instanceNameFlag: "example",
aclFlag: "198.51.100.14/24",
}
for _, mod := range mods {
mod(flagValues)
Expand All @@ -37,6 +39,7 @@ func fixtureInputModel(mods ...func(model *inputModel)) *inputModel {
ProjectId: testProjectId,
},
InstanceName: utils.Ptr("example"),
Acls: utils.Ptr([]string{"198.51.100.14/24"}),
}
for _, mod := range mods {
mod(model)
Expand All @@ -55,10 +58,24 @@ func fixtureRequest(mods ...func(request *secretsmanager.ApiCreateInstanceReques
return request
}

func fixtureUpdateACLsRequest(mods ...func(request *secretsmanager.ApiUpdateACLsRequest)) secretsmanager.ApiUpdateACLsRequest {
request := testClient.UpdateACLs(testCtx, testProjectId, testInstanceId)
request = request.UpdateACLsPayload(secretsmanager.UpdateACLsPayload{
Cidrs: utils.Ptr([]secretsmanager.AclUpdate{
{Cidr: utils.Ptr("198.51.100.14/24")},
})})

for _, mod := range mods {
mod(&request)
}
return request
}

func TestParseInput(t *testing.T) {
tests := []struct {
description string
flagValues map[string]string
aclValues []string
isValid bool
expectedModel *inputModel
}{
Expand Down Expand Up @@ -94,6 +111,55 @@ func TestParseInput(t *testing.T) {
}),
isValid: false,
},
{
description: "acl missing",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, aclFlag)
}),
isValid: true,
expectedModel: fixtureInputModel(func(model *inputModel) {
model.Acls = nil
}),
},
{
description: "acl empty",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[aclFlag] = ""
}),
isValid: false,
},
{
description: "repeated acl flags",
flagValues: fixtureFlagValues(),
aclValues: []string{"198.51.100.14/24", "198.51.100.14/32"},
isValid: true,
expectedModel: fixtureInputModel(func(model *inputModel) {
model.Acls = utils.Ptr(
append(*model.Acls, "198.51.100.14/24", "198.51.100.14/32"),
)
}),
},
{
description: "repeated acl flag with list value",
flagValues: fixtureFlagValues(),
aclValues: []string{"198.51.100.14/24,198.51.100.14/32"},
isValid: true,
expectedModel: fixtureInputModel(func(model *inputModel) {
model.Acls = utils.Ptr(
append(*model.Acls, "198.51.100.14/24", "198.51.100.14/32"),
)
}),
},
{
description: "multiple acls",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[aclFlag] = "198.51.100.14/24,1.2.3.4/32"
}),
isValid: true,
expectedModel: fixtureInputModel(func(model *inputModel) {
*model.Acls = append(*model.Acls, "1.2.3.4/32")
}),
},
{
description: "project id missing",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
Expand Down Expand Up @@ -135,6 +201,16 @@ func TestParseInput(t *testing.T) {
}
}

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.ValidateRequiredFlags()
if err != nil {
if !tt.isValid {
Expand Down Expand Up @@ -162,7 +238,7 @@ func TestParseInput(t *testing.T) {
}
}

func TestBuildRequest(t *testing.T) {
func TestBuildCreateInstanceRequest(t *testing.T) {
tests := []struct {
description string
model *inputModel
Expand All @@ -177,7 +253,45 @@ func TestBuildRequest(t *testing.T) {

for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
request := buildRequest(testCtx, tt.model, testClient)
request := buildCreateInstanceRequest(testCtx, tt.model, testClient)

diff := cmp.Diff(request, tt.expectedRequest,
cmp.AllowUnexported(tt.expectedRequest),
cmpopts.EquateComparable(testCtx),
)
if diff != "" {
t.Fatalf("Data does not match: %s", diff)
}
})
}
}
func TestBuildCreateACLRequests(t *testing.T) {
tests := []struct {
description string
model *inputModel
expectedRequest secretsmanager.ApiUpdateACLsRequest
}{
{
description: "base",
model: fixtureInputModel(),
expectedRequest: fixtureUpdateACLsRequest(),
},
{
description: "multiple ACLs",
model: fixtureInputModel(func(model *inputModel) {
*model.Acls = append(*model.Acls, "1.2.3.4/32")
}),
expectedRequest: fixtureUpdateACLsRequest().UpdateACLsPayload(secretsmanager.UpdateACLsPayload{
Cidrs: utils.Ptr([]secretsmanager.AclUpdate{
{Cidr: utils.Ptr("198.51.100.14/24")},
{Cidr: utils.Ptr("1.2.3.4/32")},
})}),
},
}

for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
request := buildUpdateACLsRequest(testCtx, tt.model, testInstanceId, testClient)

diff := cmp.Diff(request, tt.expectedRequest,
cmp.AllowUnexported(tt.expectedRequest),
Expand Down