Skip to content

Commit fd56636

Browse files
committed
feat(objectstorage): onboard compliance lock cmd
relates to STACKITCLI-341
1 parent aa3f6fe commit fd56636

File tree

8 files changed

+852
-0
lines changed

8 files changed

+852
-0
lines changed
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package compliancelock
2+
3+
import (
4+
"github.com/stackitcloud/stackit-cli/internal/cmd/object-storage/compliance-lock/describe"
5+
"github.com/stackitcloud/stackit-cli/internal/cmd/object-storage/compliance-lock/lock"
6+
"github.com/stackitcloud/stackit-cli/internal/cmd/object-storage/compliance-lock/unlock"
7+
"github.com/stackitcloud/stackit-cli/internal/pkg/args"
8+
"github.com/stackitcloud/stackit-cli/internal/pkg/types"
9+
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
10+
11+
"github.com/spf13/cobra"
12+
)
13+
14+
func NewCmd(params *types.CmdParams) *cobra.Command {
15+
cmd := &cobra.Command{
16+
Use: "compliance-lock",
17+
Short: "Provides functionality to manage Object Storage compliance lock",
18+
Long: "Provides functionality to manage Object Storage compliance lock.",
19+
Args: args.NoArgs,
20+
Run: utils.CmdHelp,
21+
}
22+
addSubcommands(cmd, params)
23+
return cmd
24+
}
25+
26+
func addSubcommands(cmd *cobra.Command, params *types.CmdParams) {
27+
cmd.AddCommand(lock.NewCmd(params))
28+
cmd.AddCommand(unlock.NewCmd(params))
29+
cmd.AddCommand(describe.NewCmd(params))
30+
}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
package describe
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/stackitcloud/stackit-cli/internal/pkg/args"
8+
"github.com/stackitcloud/stackit-cli/internal/pkg/errors"
9+
"github.com/stackitcloud/stackit-cli/internal/pkg/examples"
10+
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
11+
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
12+
"github.com/stackitcloud/stackit-cli/internal/pkg/services/object-storage/client"
13+
objectStorageUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/object-storage/utils"
14+
"github.com/stackitcloud/stackit-cli/internal/pkg/tables"
15+
"github.com/stackitcloud/stackit-cli/internal/pkg/types"
16+
17+
"github.com/spf13/cobra"
18+
objectstorage "github.com/stackitcloud/stackit-sdk-go/services/objectstorage/v2api"
19+
)
20+
21+
type inputModel struct {
22+
*globalflags.GlobalFlagModel
23+
}
24+
25+
func NewCmd(params *types.CmdParams) *cobra.Command {
26+
cmd := &cobra.Command{
27+
Use: "describe",
28+
Short: "Describe object storage compliance lock",
29+
Long: "Describe object storage compliance lock.",
30+
Args: args.NoArgs,
31+
Example: examples.Build(
32+
examples.NewExample(
33+
`Describe object storage compliance lock`,
34+
"$ stackit object-storage compliance-lock describe"),
35+
),
36+
RunE: func(cmd *cobra.Command, args []string) error {
37+
ctx := context.Background()
38+
model, err := parseInput(params.Printer, cmd, args)
39+
if err != nil {
40+
return err
41+
}
42+
43+
// Configure API client
44+
apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion)
45+
if err != nil {
46+
return err
47+
}
48+
49+
// Check if the project is enabled before trying to describe
50+
enabled, err := objectStorageUtils.ProjectEnabled(ctx, apiClient.DefaultAPI, model.ProjectId, model.Region)
51+
if err != nil {
52+
return fmt.Errorf("check if Object Storage is enabled: %w", err)
53+
}
54+
if !enabled {
55+
return &errors.ServiceDisabledError{
56+
Service: "object-storage",
57+
}
58+
}
59+
60+
// Call API
61+
req := buildRequest(ctx, model, apiClient)
62+
resp, err := req.Execute()
63+
if err != nil {
64+
return fmt.Errorf("get object storage compliance lock: %w", err)
65+
}
66+
67+
return outputResult(params.Printer, model.OutputFormat, resp)
68+
},
69+
}
70+
return cmd
71+
}
72+
73+
func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, error) {
74+
globalFlags := globalflags.Parse(p, cmd)
75+
if globalFlags.ProjectId == "" {
76+
return nil, &errors.ProjectIdError{}
77+
}
78+
79+
model := inputModel{
80+
GlobalFlagModel: globalFlags,
81+
}
82+
83+
p.DebugInputModel(model)
84+
return &model, nil
85+
}
86+
87+
func buildRequest(ctx context.Context, model *inputModel, apiClient *objectstorage.APIClient) objectstorage.ApiGetComplianceLockRequest {
88+
req := apiClient.DefaultAPI.GetComplianceLock(ctx, model.ProjectId, model.Region)
89+
return req
90+
}
91+
92+
func outputResult(p *print.Printer, outputFormat string, resp *objectstorage.ComplianceLockResponse) error {
93+
return p.OutputResult(outputFormat, resp, func() error {
94+
if resp == nil {
95+
return fmt.Errorf("response is empty")
96+
}
97+
98+
table := tables.NewTable()
99+
table.AddRow("Project", resp.Project)
100+
table.AddSeparator()
101+
table.AddRow("Max retention days", resp.MaxRetentionDays)
102+
table.AddSeparator()
103+
104+
err := table.Display(p)
105+
if err != nil {
106+
return fmt.Errorf("render table: %w", err)
107+
}
108+
109+
return nil
110+
})
111+
}
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
package describe
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
"github.com/stackitcloud/stackit-cli/internal/pkg/types"
8+
9+
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
10+
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
11+
"github.com/stackitcloud/stackit-cli/internal/pkg/testutils"
12+
13+
"github.com/google/go-cmp/cmp"
14+
"github.com/google/go-cmp/cmp/cmpopts"
15+
"github.com/google/uuid"
16+
objectstorage "github.com/stackitcloud/stackit-sdk-go/services/objectstorage/v2api"
17+
)
18+
19+
type testCtxKey struct{}
20+
21+
var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo")
22+
var testClient = &objectstorage.APIClient{DefaultAPI: &objectstorage.DefaultAPIService{}}
23+
var testProjectId = uuid.NewString()
24+
25+
const (
26+
testRegion = "eu01"
27+
)
28+
29+
func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string {
30+
flagValues := map[string]string{
31+
globalflags.ProjectIdFlag: testProjectId,
32+
globalflags.RegionFlag: testRegion,
33+
}
34+
for _, mod := range mods {
35+
mod(flagValues)
36+
}
37+
return flagValues
38+
}
39+
40+
func fixtureInputModel(mods ...func(model *inputModel)) *inputModel {
41+
model := &inputModel{
42+
GlobalFlagModel: &globalflags.GlobalFlagModel{
43+
ProjectId: testProjectId,
44+
Region: testRegion,
45+
Verbosity: globalflags.VerbosityDefault,
46+
},
47+
}
48+
for _, mod := range mods {
49+
mod(model)
50+
}
51+
return model
52+
}
53+
54+
func fixtureRequest(mods ...func(request *objectstorage.ApiGetComplianceLockRequest)) objectstorage.ApiGetComplianceLockRequest {
55+
request := testClient.DefaultAPI.GetComplianceLock(testCtx, testProjectId, testRegion)
56+
for _, mod := range mods {
57+
mod(&request)
58+
}
59+
return request
60+
}
61+
62+
func TestParseInput(t *testing.T) {
63+
tests := []struct {
64+
description string
65+
flagValues map[string]string
66+
isValid bool
67+
expectedModel *inputModel
68+
}{
69+
{
70+
description: "base",
71+
flagValues: fixtureFlagValues(),
72+
isValid: true,
73+
expectedModel: fixtureInputModel(),
74+
},
75+
{
76+
description: "project id missing",
77+
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
78+
delete(flagValues, globalflags.ProjectIdFlag)
79+
}),
80+
isValid: false,
81+
},
82+
{
83+
description: "project id invalid 1",
84+
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
85+
flagValues[globalflags.ProjectIdFlag] = ""
86+
}),
87+
isValid: false,
88+
},
89+
{
90+
description: "project id invalid 2",
91+
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
92+
flagValues[globalflags.ProjectIdFlag] = "invalid-uuid"
93+
}),
94+
isValid: false,
95+
},
96+
}
97+
98+
for _, tt := range tests {
99+
t.Run(tt.description, func(t *testing.T) {
100+
testutils.TestParseInput(t, NewCmd, parseInput, tt.expectedModel, nil, tt.flagValues, tt.isValid)
101+
})
102+
}
103+
}
104+
105+
func TestBuildRequest(t *testing.T) {
106+
tests := []struct {
107+
description string
108+
model *inputModel
109+
expectedRequest objectstorage.ApiGetComplianceLockRequest
110+
}{
111+
{
112+
description: "base",
113+
model: fixtureInputModel(),
114+
expectedRequest: fixtureRequest(),
115+
},
116+
}
117+
118+
for _, tt := range tests {
119+
t.Run(tt.description, func(t *testing.T) {
120+
request := buildRequest(testCtx, tt.model, testClient)
121+
122+
diff := cmp.Diff(request, tt.expectedRequest,
123+
cmp.AllowUnexported(tt.expectedRequest),
124+
cmpopts.EquateComparable(testCtx),
125+
)
126+
if diff != "" {
127+
t.Fatalf("Data does not match: %s", diff)
128+
}
129+
})
130+
}
131+
}
132+
133+
func TestOutputResult(t *testing.T) {
134+
type args struct {
135+
outputFormat string
136+
complianceLock *objectstorage.ComplianceLockResponse
137+
}
138+
tests := []struct {
139+
name string
140+
args args
141+
wantErr bool
142+
}{
143+
{
144+
name: "empty",
145+
args: args{
146+
outputFormat: print.PrettyOutputFormat,
147+
},
148+
wantErr: true,
149+
},
150+
{
151+
name: "set empty compliance lock",
152+
args: args{
153+
outputFormat: print.PrettyOutputFormat,
154+
complianceLock: &objectstorage.ComplianceLockResponse{},
155+
},
156+
wantErr: false,
157+
},
158+
{
159+
name: "set filled lock",
160+
args: args{
161+
outputFormat: print.PrettyOutputFormat,
162+
complianceLock: &objectstorage.ComplianceLockResponse{
163+
Project: uuid.New().String(),
164+
MaxRetentionDays: int32(42),
165+
},
166+
},
167+
wantErr: false,
168+
},
169+
}
170+
p := print.NewPrinter()
171+
p.Cmd = NewCmd(&types.CmdParams{Printer: p})
172+
for _, tt := range tests {
173+
t.Run(tt.name, func(t *testing.T) {
174+
if err := outputResult(p, tt.args.outputFormat, tt.args.complianceLock); (err != nil) != tt.wantErr {
175+
t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr)
176+
}
177+
})
178+
}
179+
}

0 commit comments

Comments
 (0)