Skip to content

Commit bc1c7af

Browse files
committed
add describe command
1 parent faed3cc commit bc1c7af

3 files changed

Lines changed: 329 additions & 0 deletions

File tree

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
package describe
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/spf13/cobra"
8+
vpn "github.com/stackitcloud/stackit-sdk-go/services/vpn/v1api"
9+
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/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+
)
21+
22+
const (
23+
gatewayIdArg = "GATEWAY_ID"
24+
)
25+
26+
type inputModel struct {
27+
*globalflags.GlobalFlagModel
28+
GatewayId string
29+
}
30+
31+
func NewCmd(params *types.CmdParams) *cobra.Command {
32+
cmd := &cobra.Command{
33+
Use: fmt.Sprintf("describe %s", gatewayIdArg),
34+
Short: "Shows details of a gateway",
35+
Long: "Shows details of a gateway.",
36+
Args: args.SingleArg(gatewayIdArg, utils.ValidateUUID),
37+
Example: examples.Build(
38+
examples.NewExample(
39+
`Describe a gateway with ID "xxx"`,
40+
"$ stackit beta vpn gateway describe xxx",
41+
),
42+
),
43+
RunE: func(cmd *cobra.Command, inputArgs []string) error {
44+
ctx := context.Background()
45+
model, err := parseInput(params.Printer, cmd, inputArgs)
46+
if err != nil {
47+
return fmt.Errorf("unable to parse input: %w", err)
48+
}
49+
50+
// Configure API client
51+
apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion)
52+
if err != nil {
53+
return err
54+
}
55+
56+
// Call API
57+
req := buildRequest(ctx, model, apiClient)
58+
resp, err := req.Execute()
59+
if err != nil {
60+
return fmt.Errorf("describe vpn gateway: %w", err)
61+
}
62+
63+
projectLabel, err := projectname.GetProjectName(ctx, params.Printer, params.CliVersion, cmd)
64+
if err != nil || projectLabel == "" {
65+
projectLabel = model.ProjectId
66+
}
67+
68+
return outputResult(params.Printer, model.OutputFormat, model.GatewayId, projectLabel, resp)
69+
},
70+
}
71+
return cmd
72+
}
73+
74+
func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inputModel, error) {
75+
gatewayId := inputArgs[0]
76+
globalFlags := globalflags.Parse(p, cmd)
77+
if globalFlags.ProjectId == "" {
78+
return nil, &errors.ProjectIdError{}
79+
}
80+
81+
model := inputModel{
82+
GlobalFlagModel: globalFlags,
83+
GatewayId: gatewayId,
84+
}
85+
86+
p.DebugInputModel(model)
87+
return &model, nil
88+
}
89+
90+
func buildRequest(ctx context.Context, model *inputModel, apiClient *vpn.APIClient) vpn.ApiGetGatewayRequest {
91+
return apiClient.DefaultAPI.GetGateway(ctx, model.ProjectId, vpn.Region(model.Region), model.GatewayId)
92+
}
93+
94+
func outputResult(p *print.Printer, outputFormat, gatewayId, projectLabel string, gateway *vpn.GatewayResponse) error {
95+
return p.OutputResult(outputFormat, gateway, func() error {
96+
if gateway == nil {
97+
p.Outputf("gateway %q not found in project %q\n", gatewayId, projectLabel)
98+
return nil
99+
}
100+
101+
table := tables.NewTable()
102+
table.SetTitle("Gateway")
103+
104+
table.AddRow("ID", utils.PtrString(gateway.Id))
105+
table.AddSeparator()
106+
table.AddRow("NAME", gateway.DisplayName)
107+
table.AddSeparator()
108+
table.AddRow("STATE", utils.PtrString(gateway.State))
109+
110+
if err := table.Display(p); err != nil {
111+
return fmt.Errorf("render tables: %w", err)
112+
}
113+
return nil
114+
})
115+
}
Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
package describe
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
"github.com/google/go-cmp/cmp"
8+
"github.com/google/go-cmp/cmp/cmpopts"
9+
"github.com/google/uuid"
10+
vpn "github.com/stackitcloud/stackit-sdk-go/services/vpn/v1api"
11+
12+
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
13+
"github.com/stackitcloud/stackit-cli/internal/pkg/testparams"
14+
"github.com/stackitcloud/stackit-cli/internal/pkg/testutils"
15+
)
16+
17+
var projectIdFlag = globalflags.ProjectIdFlag
18+
var regionFlag = globalflags.RegionFlag
19+
20+
type testCtxKey struct{}
21+
22+
var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo")
23+
var testClient = &vpn.APIClient{DefaultAPI: &vpn.DefaultAPIService{}}
24+
25+
var testProjectId = uuid.NewString()
26+
var testRegion = "eu01"
27+
28+
var testGatewayId = uuid.NewString()
29+
30+
func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string {
31+
flagValues := map[string]string{
32+
projectIdFlag: testProjectId,
33+
regionFlag: testRegion,
34+
}
35+
for _, mod := range mods {
36+
mod(flagValues)
37+
}
38+
return flagValues
39+
}
40+
41+
func fixtureArgValues(mods ...func(argValues []string)) []string {
42+
argValues := []string{
43+
testGatewayId,
44+
}
45+
for _, mod := range mods {
46+
mod(argValues)
47+
}
48+
return argValues
49+
}
50+
51+
func fixtureInputModel(mods ...func(model *inputModel)) *inputModel {
52+
model := &inputModel{
53+
GlobalFlagModel: &globalflags.GlobalFlagModel{
54+
ProjectId: testProjectId,
55+
Verbosity: globalflags.VerbosityDefault,
56+
Region: testRegion,
57+
},
58+
GatewayId: testGatewayId,
59+
}
60+
for _, mod := range mods {
61+
mod(model)
62+
}
63+
return model
64+
}
65+
66+
func fixtureRequest(mods ...func(request *vpn.ApiGetGatewayRequest)) vpn.ApiGetGatewayRequest {
67+
request := testClient.DefaultAPI.GetGateway(testCtx, testProjectId, vpn.Region(testRegion), testGatewayId)
68+
for _, mod := range mods {
69+
mod(&request)
70+
}
71+
return request
72+
}
73+
74+
func TestParseInput(t *testing.T) {
75+
tests := []struct {
76+
description string
77+
argValues []string
78+
flagValues map[string]string
79+
isValid bool
80+
expectedModel *inputModel
81+
}{
82+
{
83+
description: "base",
84+
argValues: fixtureArgValues(),
85+
flagValues: fixtureFlagValues(),
86+
isValid: true,
87+
expectedModel: fixtureInputModel(),
88+
},
89+
{
90+
description: "no values",
91+
argValues: []string{},
92+
flagValues: map[string]string{},
93+
isValid: false,
94+
},
95+
{
96+
description: "no arg values",
97+
argValues: []string{},
98+
flagValues: fixtureFlagValues(),
99+
isValid: false,
100+
},
101+
{
102+
description: "no flag values",
103+
argValues: fixtureArgValues(),
104+
flagValues: map[string]string{},
105+
isValid: false,
106+
},
107+
{
108+
description: "project id missing",
109+
argValues: fixtureArgValues(),
110+
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
111+
delete(flagValues, projectIdFlag)
112+
}),
113+
isValid: false,
114+
},
115+
{
116+
description: "project id invalid 1",
117+
argValues: fixtureArgValues(),
118+
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
119+
flagValues[projectIdFlag] = ""
120+
}),
121+
isValid: false,
122+
},
123+
{
124+
description: "project id invalid 2",
125+
argValues: fixtureArgValues(),
126+
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
127+
flagValues[projectIdFlag] = "invalid-uuid"
128+
}),
129+
isValid: false,
130+
},
131+
{
132+
description: "gateway id invalid 1",
133+
argValues: []string{""},
134+
flagValues: fixtureFlagValues(),
135+
isValid: false,
136+
},
137+
{
138+
description: "gateway id invalid 2",
139+
argValues: []string{"invalid-uuid"},
140+
flagValues: fixtureFlagValues(),
141+
isValid: false,
142+
},
143+
}
144+
for _, tt := range tests {
145+
t.Run(tt.description, func(t *testing.T) {
146+
testutils.TestParseInput(t, NewCmd, parseInput, tt.expectedModel, tt.argValues, tt.flagValues, tt.isValid)
147+
})
148+
}
149+
}
150+
151+
func TestBuildRequest(t *testing.T) {
152+
tests := []struct {
153+
description string
154+
model *inputModel
155+
expectedRequest vpn.ApiGetGatewayRequest
156+
}{
157+
{
158+
description: "base",
159+
model: fixtureInputModel(),
160+
expectedRequest: fixtureRequest(),
161+
},
162+
}
163+
for _, tt := range tests {
164+
t.Run(tt.description, func(t *testing.T) {
165+
request := buildRequest(testCtx, tt.model, testClient)
166+
167+
diff := cmp.Diff(request, tt.expectedRequest,
168+
cmp.AllowUnexported(tt.expectedRequest, vpn.DefaultAPIService{}),
169+
cmpopts.EquateComparable(testCtx),
170+
)
171+
if diff != "" {
172+
t.Fatalf("Data does not match: %s", diff)
173+
}
174+
})
175+
}
176+
}
177+
178+
func TestOutputResult(t *testing.T) {
179+
type args struct {
180+
outputFormat string
181+
gatewayId string
182+
projectLabel string
183+
gateway *vpn.GatewayResponse
184+
}
185+
tests := []struct {
186+
name string
187+
args args
188+
wantErr bool
189+
}{
190+
{
191+
name: "empty",
192+
args: args{},
193+
wantErr: false,
194+
},
195+
{
196+
name: "set empty response",
197+
args: args{
198+
gateway: &vpn.GatewayResponse{},
199+
},
200+
wantErr: false,
201+
},
202+
}
203+
params := testparams.NewTestParams()
204+
205+
for _, tt := range tests {
206+
t.Run(tt.name, func(t *testing.T) {
207+
if err := outputResult(params.Printer, tt.args.outputFormat, tt.args.projectLabel, tt.args.gatewayId, tt.args.gateway); (err != nil) != tt.wantErr {
208+
t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr)
209+
}
210+
})
211+
}
212+
}

internal/cmd/beta/vpn/gateway/gateway.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package gateway
22

33
import (
4+
"github.com/stackitcloud/stackit-cli/internal/cmd/beta/vpn/gateway/describe"
45
"github.com/stackitcloud/stackit-cli/internal/cmd/beta/vpn/gateway/list"
56
"github.com/stackitcloud/stackit-cli/internal/pkg/args"
67
"github.com/stackitcloud/stackit-cli/internal/pkg/types"
@@ -23,4 +24,5 @@ func NewCmd(params *types.CmdParams) *cobra.Command {
2324

2425
func addSubcommands(cmd *cobra.Command, params *types.CmdParams) {
2526
cmd.AddCommand(list.NewCmd(params))
27+
cmd.AddCommand(describe.NewCmd(params))
2628
}

0 commit comments

Comments
 (0)