Skip to content

Commit b8751b7

Browse files
committed
add snapshot list subcommand
1 parent 6c9fe0e commit b8751b7

File tree

2 files changed

+428
-0
lines changed

2 files changed

+428
-0
lines changed
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
package list
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
8+
"github.com/goccy/go-yaml"
9+
"github.com/spf13/cobra"
10+
"github.com/stackitcloud/stackit-cli/internal/cmd/params"
11+
"github.com/stackitcloud/stackit-cli/internal/pkg/args"
12+
"github.com/stackitcloud/stackit-cli/internal/pkg/errors"
13+
"github.com/stackitcloud/stackit-cli/internal/pkg/examples"
14+
"github.com/stackitcloud/stackit-cli/internal/pkg/flags"
15+
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
16+
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
17+
"github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client"
18+
"github.com/stackitcloud/stackit-cli/internal/pkg/tables"
19+
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
20+
21+
"github.com/stackitcloud/stackit-sdk-go/services/iaas"
22+
)
23+
24+
const (
25+
limitFlag = "limit"
26+
labelSelectorFlag = "label-selector"
27+
)
28+
29+
type inputModel struct {
30+
*globalflags.GlobalFlagModel
31+
Limit *int64
32+
LabelSelector *string
33+
}
34+
35+
func NewCmd(params *params.CmdParams) *cobra.Command {
36+
cmd := &cobra.Command{
37+
Use: "list",
38+
Short: "Lists all snapshots",
39+
Long: "Lists all snapshots in a project.",
40+
Args: args.NoArgs,
41+
Example: examples.Build(
42+
examples.NewExample(
43+
`List all snapshots`,
44+
"$ stackit volume snapshot list"),
45+
examples.NewExample(
46+
`List snapshots with a limit of 10`,
47+
"$ stackit volume snapshot list --limit 10"),
48+
examples.NewExample(
49+
`List snapshots filtered by label`,
50+
"$ stackit volume snapshot list --label-selector key1=value1"),
51+
),
52+
RunE: func(cmd *cobra.Command, args []string) error {
53+
ctx := context.Background()
54+
model, err := parseInput(params.Printer, cmd)
55+
if err != nil {
56+
return err
57+
}
58+
59+
// Configure API client
60+
apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion)
61+
if err != nil {
62+
return err
63+
}
64+
65+
// Call API
66+
req := buildRequest(ctx, model, apiClient)
67+
resp, err := req.Execute()
68+
if err != nil {
69+
return fmt.Errorf("list snapshots: %w", err)
70+
}
71+
72+
// Filter results by label selector
73+
snapshots := *resp.Items
74+
if model.LabelSelector != nil {
75+
filtered := []iaas.Snapshot{}
76+
for _, s := range snapshots {
77+
if s.Labels != nil {
78+
for k, v := range *s.Labels {
79+
if fmt.Sprintf("%s=%s", k, v) == *model.LabelSelector {
80+
filtered = append(filtered, s)
81+
break
82+
}
83+
}
84+
}
85+
}
86+
snapshots = filtered
87+
}
88+
89+
// Apply limit if specified
90+
if model.Limit != nil && int(*model.Limit) < len(snapshots) {
91+
snapshots = snapshots[:*model.Limit]
92+
}
93+
94+
return outputResult(params.Printer, model.OutputFormat, snapshots)
95+
},
96+
}
97+
98+
configureFlags(cmd)
99+
return cmd
100+
}
101+
102+
func configureFlags(cmd *cobra.Command) {
103+
cmd.Flags().Int64(limitFlag, 0, "Maximum number of entries to list")
104+
cmd.Flags().String(labelSelectorFlag, "", "Filter snapshots by labels")
105+
}
106+
107+
func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) {
108+
globalFlags := globalflags.Parse(p, cmd)
109+
if globalFlags.ProjectId == "" {
110+
return nil, &errors.ProjectIdError{}
111+
}
112+
113+
limit := flags.FlagToInt64Pointer(p, cmd, limitFlag)
114+
if limit != nil && *limit < 1 {
115+
return nil, fmt.Errorf("limit must be greater than 0")
116+
}
117+
118+
labelSelector := flags.FlagToStringPointer(p, cmd, labelSelectorFlag)
119+
120+
model := inputModel{
121+
GlobalFlagModel: globalFlags,
122+
Limit: limit,
123+
LabelSelector: labelSelector,
124+
}
125+
126+
if p.IsVerbosityDebug() {
127+
modelStr, err := print.BuildDebugStrFromInputModel(model)
128+
if err != nil {
129+
p.Debug(print.ErrorLevel, "convert model to string for debugging: %v", err)
130+
} else {
131+
p.Debug(print.DebugLevel, "parsed input values: %s", modelStr)
132+
}
133+
}
134+
135+
return &model, nil
136+
}
137+
138+
func buildRequest(ctx context.Context, model *inputModel, apiClient *iaas.APIClient) iaas.ApiListSnapshotsRequest {
139+
return apiClient.ListSnapshots(ctx, model.ProjectId)
140+
}
141+
142+
func outputResult(p *print.Printer, outputFormat string, snapshots []iaas.Snapshot) error {
143+
if snapshots == nil {
144+
return fmt.Errorf("list snapshots response is empty")
145+
}
146+
147+
switch outputFormat {
148+
case print.JSONOutputFormat:
149+
details, err := json.MarshalIndent(snapshots, "", " ")
150+
if err != nil {
151+
return fmt.Errorf("marshal snapshots: %w", err)
152+
}
153+
p.Outputln(string(details))
154+
return nil
155+
156+
case print.YAMLOutputFormat:
157+
details, err := yaml.MarshalWithOptions(snapshots, yaml.IndentSequence(true), yaml.UseJSONMarshaler())
158+
if err != nil {
159+
return fmt.Errorf("marshal snapshots: %w", err)
160+
}
161+
p.Outputln(string(details))
162+
return nil
163+
164+
default:
165+
table := tables.NewTable()
166+
table.SetHeader("ID", "NAME", "SIZE", "STATUS", "VOLUME ID", "LABELS", "CREATED AT", "UPDATED AT")
167+
168+
for i := range snapshots {
169+
snapshot := snapshots[i]
170+
table.AddRow(
171+
utils.PtrString(snapshot.Id),
172+
utils.PtrString(snapshot.Name),
173+
utils.PtrByteSizeDefault((*int64)(snapshot.Size), ""),
174+
utils.PtrString(snapshot.Status),
175+
utils.PtrString(snapshot.VolumeId),
176+
utils.PtrStringDefault(snapshot.Labels, ""),
177+
utils.ConvertTimePToDateTimeString(snapshot.CreatedAt),
178+
utils.ConvertTimePToDateTimeString(snapshot.UpdatedAt),
179+
)
180+
}
181+
err := table.Display(p)
182+
if err != nil {
183+
return fmt.Errorf("render table: %w", err)
184+
}
185+
186+
return nil
187+
}
188+
}

0 commit comments

Comments
 (0)