Skip to content

Commit 6106f22

Browse files
committed
feat: implementation of list command
1 parent bc4c8c6 commit 6106f22

File tree

5 files changed

+439
-0
lines changed

5 files changed

+439
-0
lines changed

internal/cmd/beta/beta.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
networkinterface "github.com/stackitcloud/stackit-cli/internal/cmd/beta/network-interface"
1010
publicip "github.com/stackitcloud/stackit-cli/internal/cmd/beta/public-ip"
1111
securitygroup "github.com/stackitcloud/stackit-cli/internal/cmd/beta/security-group"
12+
image "github.com/stackitcloud/stackit-cli/internal/cmd/beta/image"
1213
"github.com/stackitcloud/stackit-cli/internal/cmd/beta/server"
1314
"github.com/stackitcloud/stackit-cli/internal/cmd/beta/sqlserverflex"
1415
"github.com/stackitcloud/stackit-cli/internal/cmd/beta/volume"
@@ -52,4 +53,5 @@ func addSubcommands(cmd *cobra.Command, p *print.Printer) {
5253
cmd.AddCommand(publicip.NewCmd(p))
5354
cmd.AddCommand(securitygroup.NewCmd(p))
5455
cmd.AddCommand(keypair.NewCmd(p))
56+
cmd.AddCommand(image.NewCmd(p))
5557
}

internal/cmd/beta/image/image.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package security_group
2+
3+
import (
4+
list "github.com/stackitcloud/stackit-cli/internal/cmd/beta/image/image"
5+
"github.com/stackitcloud/stackit-cli/internal/pkg/args"
6+
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
7+
8+
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
9+
10+
"github.com/spf13/cobra"
11+
)
12+
13+
func NewCmd(p *print.Printer) *cobra.Command {
14+
cmd := &cobra.Command{
15+
Use: "image",
16+
Short: "Manage server images",
17+
Long: "Manage the lifecycle of server images.",
18+
Args: args.NoArgs,
19+
Run: utils.CmdHelp,
20+
}
21+
addSubcommands(cmd, p)
22+
return cmd
23+
}
24+
25+
func addSubcommands(cmd *cobra.Command, p *print.Printer) {
26+
cmd.AddCommand(
27+
// create.NewCmd(p),
28+
list.NewCmd(p),
29+
)
30+
}
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
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/pkg/args"
11+
"github.com/stackitcloud/stackit-cli/internal/pkg/errors"
12+
"github.com/stackitcloud/stackit-cli/internal/pkg/examples"
13+
"github.com/stackitcloud/stackit-cli/internal/pkg/flags"
14+
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
15+
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
16+
"github.com/stackitcloud/stackit-cli/internal/pkg/projectname"
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+
"github.com/stackitcloud/stackit-sdk-go/services/iaas"
21+
)
22+
23+
type inputModel struct {
24+
*globalflags.GlobalFlagModel
25+
LabelSelector *string
26+
}
27+
28+
const (
29+
labelSelectorFlag = "label-selector"
30+
)
31+
32+
func NewCmd(p *print.Printer) *cobra.Command {
33+
cmd := &cobra.Command{
34+
Use: "list",
35+
Short: "Lists images",
36+
Long: "Lists images by their internal ID.",
37+
Args: args.NoArgs,
38+
Example: examples.Build(
39+
examples.NewExample(`List all images`, `$ stackit beta image list`),
40+
examples.NewExample(`List images with label`, `$ stackit beta image list --label-selector ARM64,dev`),
41+
),
42+
RunE: func(cmd *cobra.Command, _ []string) error {
43+
ctx := context.Background()
44+
model, err := parseInput(p, cmd)
45+
if err != nil {
46+
return err
47+
}
48+
49+
// Configure API client
50+
apiClient, err := client.ConfigureClient(p)
51+
if err != nil {
52+
return err
53+
}
54+
55+
projectLabel, err := projectname.GetProjectName(ctx, p, cmd)
56+
if err != nil {
57+
p.Debug(print.ErrorLevel, "get project name: %v", err)
58+
projectLabel = model.ProjectId
59+
}
60+
61+
// Call API
62+
request := buildRequest(ctx, model, apiClient)
63+
64+
response, err := request.Execute()
65+
if err != nil {
66+
return fmt.Errorf("list images: %w", err)
67+
}
68+
69+
if items := response.GetItems(); items == nil || len(*items) == 0 {
70+
p.Info("No images found for project %q", projectLabel)
71+
} else {
72+
if err := outputResult(p, model.OutputFormat, *items); err != nil {
73+
return fmt.Errorf("output images: %w", err)
74+
}
75+
}
76+
77+
return nil
78+
},
79+
}
80+
81+
configureFlags(cmd)
82+
return cmd
83+
}
84+
85+
func configureFlags(cmd *cobra.Command) {
86+
cmd.Flags().String(labelSelectorFlag, "", "Filter by label")
87+
}
88+
89+
func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) {
90+
globalFlags := globalflags.Parse(p, cmd)
91+
if globalFlags.ProjectId == "" {
92+
return nil, &errors.ProjectIdError{}
93+
}
94+
95+
model := inputModel{
96+
GlobalFlagModel: globalFlags,
97+
LabelSelector: flags.FlagToStringPointer(p, cmd, labelSelectorFlag),
98+
}
99+
100+
if p.IsVerbosityDebug() {
101+
modelStr, err := print.BuildDebugStrFromInputModel(model)
102+
if err != nil {
103+
p.Debug(print.ErrorLevel, "convert model to string for debugging: %v", err)
104+
} else {
105+
p.Debug(print.DebugLevel, "parsed input values: %s", modelStr)
106+
}
107+
}
108+
109+
return &model, nil
110+
}
111+
112+
func buildRequest(ctx context.Context, model *inputModel, apiClient *iaas.APIClient) iaas.ApiListImagesRequest {
113+
request := apiClient.ListImages(ctx, model.ProjectId)
114+
if model.LabelSelector != nil {
115+
request = request.LabelSelector(*model.LabelSelector)
116+
}
117+
118+
return request
119+
}
120+
func outputResult(p *print.Printer, outputFormat string, items []iaas.Image) error {
121+
switch outputFormat {
122+
case print.JSONOutputFormat:
123+
details, err := json.MarshalIndent(items, "", " ")
124+
if err != nil {
125+
return fmt.Errorf("marshal image list: %w", err)
126+
}
127+
p.Outputln(string(details))
128+
129+
return nil
130+
case print.YAMLOutputFormat:
131+
details, err := yaml.MarshalWithOptions(items, yaml.IndentSequence(true))
132+
if err != nil {
133+
return fmt.Errorf("marshal image list: %w", err)
134+
}
135+
p.Outputln(string(details))
136+
137+
return nil
138+
default:
139+
table := tables.NewTable()
140+
table.SetHeader("ID", "NAME", "OS", "DISTRIBUTION", "VERSION", "LABELS")
141+
for _, item := range items {
142+
var (
143+
os string = "n/a"
144+
distro string = "n/a"
145+
version string = "n/a"
146+
)
147+
if cfg := item.Config; cfg != nil {
148+
if v := cfg.OperatingSystem; v != nil {
149+
os = *v
150+
}
151+
if v := cfg.OperatingSystemDistro; v != nil && v.IsSet() {
152+
distro = *v.Get()
153+
154+
}
155+
if v := cfg.OperatingSystemVersion; v != nil && v.IsSet() {
156+
version = *v.Get()
157+
}
158+
}
159+
table.AddRow(utils.PtrString(item.Id),
160+
utils.PtrString(item.Name),
161+
os,
162+
distro,
163+
version,
164+
utils.JoinStringKeysPtr(item.Labels, ","))
165+
}
166+
err := table.Display(p)
167+
if err != nil {
168+
return fmt.Errorf("render table: %w", err)
169+
}
170+
171+
return nil
172+
}
173+
}

0 commit comments

Comments
 (0)