Skip to content

Commit faed3cc

Browse files
committed
add list command
1 parent 5a7f5c4 commit faed3cc

5 files changed

Lines changed: 384 additions & 0 deletions

File tree

internal/cmd/beta/beta.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/stackitcloud/stackit-cli/internal/cmd/beta/intake"
1212
"github.com/stackitcloud/stackit-cli/internal/cmd/beta/sfs"
1313
"github.com/stackitcloud/stackit-cli/internal/cmd/beta/sqlserverflex"
14+
"github.com/stackitcloud/stackit-cli/internal/cmd/beta/vpn"
1415
"github.com/stackitcloud/stackit-cli/internal/pkg/args"
1516
"github.com/stackitcloud/stackit-cli/internal/pkg/examples"
1617
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
@@ -47,4 +48,5 @@ func addSubcommands(cmd *cobra.Command, params *types.CmdParams) {
4748
cmd.AddCommand(edge.NewCmd(params))
4849
cmd.AddCommand(intake.NewCmd(params))
4950
cmd.AddCommand(cdn.NewCmd(params))
51+
cmd.AddCommand(vpn.NewCmd(params))
5052
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package gateway
2+
3+
import (
4+
"github.com/stackitcloud/stackit-cli/internal/cmd/beta/vpn/gateway/list"
5+
"github.com/stackitcloud/stackit-cli/internal/pkg/args"
6+
"github.com/stackitcloud/stackit-cli/internal/pkg/types"
7+
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
8+
9+
"github.com/spf13/cobra"
10+
)
11+
12+
func NewCmd(params *types.CmdParams) *cobra.Command {
13+
cmd := &cobra.Command{
14+
Use: "gateway",
15+
Short: "Provides functionality for VPN gateway",
16+
Long: "Provides functionality for VPN gateway.",
17+
Args: args.NoArgs,
18+
Run: utils.CmdHelp,
19+
}
20+
addSubcommands(cmd, params)
21+
return cmd
22+
}
23+
24+
func addSubcommands(cmd *cobra.Command, params *types.CmdParams) {
25+
cmd.AddCommand(list.NewCmd(params))
26+
}
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
package list
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/spf13/cobra"
8+
9+
"github.com/stackitcloud/stackit-cli/internal/pkg/args"
10+
"github.com/stackitcloud/stackit-cli/internal/pkg/errors"
11+
"github.com/stackitcloud/stackit-cli/internal/pkg/examples"
12+
"github.com/stackitcloud/stackit-cli/internal/pkg/flags"
13+
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
14+
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
15+
"github.com/stackitcloud/stackit-cli/internal/pkg/projectname"
16+
"github.com/stackitcloud/stackit-cli/internal/pkg/services/vpn/client"
17+
"github.com/stackitcloud/stackit-cli/internal/pkg/tables"
18+
"github.com/stackitcloud/stackit-cli/internal/pkg/types"
19+
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
20+
vpn "github.com/stackitcloud/stackit-sdk-go/services/vpn/v1api"
21+
)
22+
23+
const (
24+
limitFlag = "limit"
25+
)
26+
27+
type inputModel struct {
28+
*globalflags.GlobalFlagModel
29+
Limit *int64
30+
}
31+
32+
func NewCmd(params *types.CmdParams) *cobra.Command {
33+
cmd := &cobra.Command{
34+
Use: "list",
35+
Short: "Lists all vpn gateways",
36+
Long: "Lists all vpn gateways.",
37+
Args: args.NoArgs,
38+
Example: examples.Build(
39+
examples.NewExample(
40+
`List all vpn gateways`,
41+
"$ stackit beta vpn gateway list",
42+
),
43+
),
44+
RunE: func(cmd *cobra.Command, args []string) error {
45+
ctx := context.Background()
46+
model, err := parseInput(params.Printer, cmd, args)
47+
if err != nil {
48+
return fmt.Errorf("unable to parse input: %w", err)
49+
}
50+
51+
// Configure API client
52+
apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion)
53+
if err != nil {
54+
return err
55+
}
56+
57+
// Call API
58+
req := buildRequest(ctx, model, apiClient)
59+
resp, err := req.Execute()
60+
if err != nil {
61+
return fmt.Errorf("list vpn gateways: %w", err)
62+
}
63+
64+
// Truncate output
65+
items := utils.GetSliceFromPointer(&resp.Gateways)
66+
if model.Limit != nil && len(items) > int(*model.Limit) {
67+
items = items[:*model.Limit]
68+
}
69+
70+
projectLabel, err := projectname.GetProjectName(ctx, params.Printer, params.CliVersion, cmd)
71+
if err != nil || projectLabel == "" {
72+
projectLabel = model.ProjectId
73+
}
74+
75+
return outputResult(params.Printer, model.OutputFormat, items, projectLabel)
76+
},
77+
}
78+
configureFlags(cmd)
79+
return cmd
80+
}
81+
82+
func configureFlags(cmd *cobra.Command) {
83+
cmd.Flags().Int64(limitFlag, 0, "Maximum number of entries to list")
84+
}
85+
86+
func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, error) {
87+
globalFlags := globalflags.Parse(p, cmd)
88+
if globalFlags.ProjectId == "" {
89+
return nil, &errors.ProjectIdError{}
90+
}
91+
92+
limit := flags.FlagToInt64Pointer(p, cmd, limitFlag)
93+
if limit != nil && *limit < 1 {
94+
return nil, &errors.FlagValidationError{
95+
Flag: limitFlag,
96+
Details: "must be grater than 0",
97+
}
98+
}
99+
100+
model := inputModel{
101+
GlobalFlagModel: globalFlags,
102+
Limit: limit,
103+
}
104+
105+
p.DebugInputModel(model)
106+
return &model, nil
107+
}
108+
109+
func buildRequest(ctx context.Context, model *inputModel, apiClient *vpn.APIClient) vpn.ApiListGatewaysRequest {
110+
return apiClient.DefaultAPI.ListGateways(ctx, model.ProjectId, vpn.Region(model.Region))
111+
}
112+
113+
func outputResult(p *print.Printer, outputFormat string, gateways []vpn.GatewayResponse, projectLabel string) error {
114+
return p.OutputResult(outputFormat, gateways, func() error {
115+
116+
if len(gateways) == 0 {
117+
p.Info("No gateways found for %q\n", projectLabel)
118+
return nil
119+
}
120+
121+
table := tables.NewTable()
122+
table.SetHeader("ID", "NAME", "STATE")
123+
124+
for _, gateway := range gateways {
125+
table.AddRow(
126+
gateway.Id,
127+
gateway.DisplayName,
128+
gateway.State,
129+
)
130+
}
131+
p.Outputln(table.Render())
132+
return nil
133+
})
134+
}
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
package list
2+
3+
import (
4+
"context"
5+
"strconv"
6+
"testing"
7+
8+
"github.com/google/go-cmp/cmp"
9+
"github.com/google/go-cmp/cmp/cmpopts"
10+
"github.com/google/uuid"
11+
vpn "github.com/stackitcloud/stackit-sdk-go/services/vpn/v1api"
12+
13+
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
14+
"github.com/stackitcloud/stackit-cli/internal/pkg/testparams"
15+
"github.com/stackitcloud/stackit-cli/internal/pkg/testutils"
16+
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
17+
)
18+
19+
var projectIdFlag = globalflags.ProjectIdFlag
20+
var regionFlag = globalflags.RegionFlag
21+
22+
type testCtxKey struct{}
23+
24+
var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo")
25+
var testClient = &vpn.APIClient{DefaultAPI: &vpn.DefaultAPIService{}}
26+
27+
var testProjectId = uuid.NewString()
28+
var testRegion = "eu01"
29+
30+
var testLimit int64 = 10
31+
32+
func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string {
33+
flagValues := map[string]string{
34+
projectIdFlag: testProjectId,
35+
regionFlag: testRegion,
36+
limitFlag: strconv.FormatInt(testLimit, 10),
37+
}
38+
for _, mod := range mods {
39+
mod(flagValues)
40+
}
41+
return flagValues
42+
}
43+
44+
func fixtureInputModel(mods ...func(model *inputModel)) *inputModel {
45+
model := &inputModel{
46+
GlobalFlagModel: &globalflags.GlobalFlagModel{
47+
ProjectId: testProjectId,
48+
Verbosity: globalflags.VerbosityDefault,
49+
Region: testRegion,
50+
},
51+
Limit: utils.Ptr(testLimit),
52+
}
53+
for _, mod := range mods {
54+
mod(model)
55+
}
56+
return model
57+
}
58+
59+
func fixtureRequest(mods ...func(request *vpn.ApiListGatewaysRequest)) vpn.ApiListGatewaysRequest {
60+
request := testClient.DefaultAPI.ListGateways(testCtx, testProjectId, vpn.Region(testRegion))
61+
for _, mod := range mods {
62+
mod(&request)
63+
}
64+
return request
65+
}
66+
67+
func TestParseInput(t *testing.T) {
68+
tests := []struct {
69+
description string
70+
argValues []string
71+
flagValues map[string]string
72+
isValid bool
73+
expectedModel *inputModel
74+
}{
75+
{
76+
description: "base",
77+
flagValues: fixtureFlagValues(),
78+
isValid: true,
79+
expectedModel: fixtureInputModel(),
80+
},
81+
{
82+
description: "no flag values",
83+
flagValues: map[string]string{},
84+
isValid: false,
85+
},
86+
{
87+
description: "project id missing",
88+
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
89+
delete(flagValues, projectIdFlag)
90+
}),
91+
isValid: false,
92+
},
93+
{
94+
description: "project id invalid 1",
95+
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
96+
flagValues[projectIdFlag] = ""
97+
}),
98+
isValid: false,
99+
},
100+
{
101+
description: "project id invalid 2",
102+
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
103+
flagValues[projectIdFlag] = "invalid-uuid"
104+
}),
105+
isValid: false,
106+
},
107+
{
108+
description: "invalid limit 1",
109+
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
110+
flagValues[limitFlag] = "0"
111+
}),
112+
isValid: false,
113+
},
114+
{
115+
description: "invalid limit 2",
116+
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
117+
flagValues[limitFlag] = "-1"
118+
}),
119+
isValid: false,
120+
},
121+
}
122+
for _, tt := range tests {
123+
t.Run(tt.description, func(t *testing.T) {
124+
testutils.TestParseInput(t, NewCmd, parseInput, tt.expectedModel, tt.argValues, tt.flagValues, tt.isValid)
125+
})
126+
}
127+
}
128+
129+
func TestBuildRequest(t *testing.T) {
130+
tests := []struct {
131+
description string
132+
model *inputModel
133+
expectedRequest vpn.ApiListGatewaysRequest
134+
}{
135+
{
136+
description: "base",
137+
model: fixtureInputModel(),
138+
expectedRequest: fixtureRequest(),
139+
},
140+
}
141+
for _, tt := range tests {
142+
t.Run(tt.description, func(t *testing.T) {
143+
request := buildRequest(testCtx, tt.model, testClient)
144+
145+
diff := cmp.Diff(request, tt.expectedRequest,
146+
cmp.AllowUnexported(tt.expectedRequest, vpn.DefaultAPIService{}),
147+
cmpopts.EquateComparable(testCtx),
148+
)
149+
if diff != "" {
150+
t.Fatalf("Data does not match: %s", diff)
151+
}
152+
})
153+
}
154+
}
155+
156+
func TestOutputResult(t *testing.T) {
157+
type args struct {
158+
outputFormat string
159+
projectLabel string
160+
gateways []vpn.GatewayResponse
161+
}
162+
tests := []struct {
163+
name string
164+
args args
165+
wantErr bool
166+
}{
167+
{
168+
name: "empty",
169+
args: args{},
170+
wantErr: false,
171+
},
172+
{
173+
name: "set empty gateway in gateways",
174+
args: args{
175+
gateways: []vpn.GatewayResponse{{}},
176+
},
177+
wantErr: false,
178+
},
179+
{
180+
name: "set empty gateway",
181+
args: args{
182+
gateways: []vpn.GatewayResponse{},
183+
},
184+
wantErr: false,
185+
},
186+
}
187+
params := testparams.NewTestParams()
188+
189+
for _, tt := range tests {
190+
t.Run(tt.name, func(t *testing.T) {
191+
if err := outputResult(params.Printer, tt.args.outputFormat, tt.args.gateways, tt.args.projectLabel); (err != nil) != tt.wantErr {
192+
t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr)
193+
}
194+
})
195+
}
196+
}

internal/cmd/beta/vpn/vpn.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package vpn
2+
3+
import (
4+
"github.com/stackitcloud/stackit-cli/internal/cmd/beta/vpn/gateway"
5+
"github.com/stackitcloud/stackit-cli/internal/pkg/args"
6+
"github.com/stackitcloud/stackit-cli/internal/pkg/types"
7+
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
8+
9+
"github.com/spf13/cobra"
10+
)
11+
12+
func NewCmd(params *types.CmdParams) *cobra.Command {
13+
cmd := &cobra.Command{
14+
Use: "vpn",
15+
Short: "Provides functionality for VPN",
16+
Long: "Provides functionality for VPN.",
17+
Args: args.NoArgs,
18+
Run: utils.CmdHelp,
19+
}
20+
addSubcommands(cmd, params)
21+
return cmd
22+
}
23+
24+
func addSubcommands(cmd *cobra.Command, params *types.CmdParams) {
25+
cmd.AddCommand(gateway.NewCmd(params))
26+
}

0 commit comments

Comments
 (0)