Skip to content

Commit 754cab4

Browse files
committed
add create command
1 parent 27e06c0 commit 754cab4

3 files changed

Lines changed: 409 additions & 0 deletions

File tree

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
package create
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/spinner"
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+
"github.com/stackitcloud/stackit-sdk-go/services/vpn/v1api/wait"
22+
)
23+
24+
const (
25+
availabilityZoneTunnel1Flag = "availability-zone-tunnel-1"
26+
availabilityZoneTunnel2Flag = "availability-zone-tunnel-2"
27+
bgpLocalAsnFlag = "bgp-local-asn"
28+
bgpOverrideAdvertisedRoutesFlag = "bgp-override-advertised-routes"
29+
nameFlag = "name"
30+
labelsFlag = "labels"
31+
planIdFlag = "plan-id"
32+
routingTypeFlag = "routing-type"
33+
)
34+
35+
type inputModel struct {
36+
*globalflags.GlobalFlagModel
37+
AvailabilityZone vpn.CreateGatewayPayloadAvailabilityZones
38+
Bgp *vpn.BGPGatewayConfig
39+
Name string
40+
Labels *map[string]string
41+
PlanId string
42+
RoutingType vpn.RoutingType
43+
}
44+
45+
func NewCmd(params *types.CmdParams) *cobra.Command {
46+
cmd := &cobra.Command{
47+
Use: "create",
48+
Short: "Creates a vpn gateway",
49+
Long: "Creates a vpn gateway.",
50+
Args: args.NoArgs,
51+
Example: examples.Build(
52+
examples.NewExample(
53+
`Create a vpn gateway with name "xxx"`,
54+
"$ stackit beta vpn gateway create --name xxx",
55+
),
56+
),
57+
RunE: func(cmd *cobra.Command, args []string) error {
58+
ctx := context.Background()
59+
model, err := parseInput(params.Printer, cmd, args)
60+
if err != nil {
61+
return fmt.Errorf("unable to parse input: %w", err)
62+
}
63+
64+
// Configure API client
65+
apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion)
66+
if err != nil {
67+
return err
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+
prompt := fmt.Sprintf("Are you sure you want to create a vpn gateway for project %q?", projectLabel)
76+
err = params.Printer.PromptForConfirmation(prompt)
77+
if err != nil {
78+
return err
79+
}
80+
81+
// Call API
82+
req := buildRequest(ctx, model, apiClient)
83+
resp, err := req.Execute()
84+
if err != nil {
85+
return fmt.Errorf("create vpn gateway: %w", err)
86+
}
87+
var gatewayId string
88+
if resp != nil && resp.HasId() {
89+
gatewayId = *resp.Id
90+
}
91+
92+
// Wait for async operation, if async mode not enabled
93+
if !model.Async {
94+
err := spinner.Run(params.Printer, "Creating vpn gateway", func() error {
95+
_, err = wait.CreateGatewayWaitHandler(ctx, apiClient.DefaultAPI, model.ProjectId, vpn.Region(model.Region), gatewayId).WaitWithContext(ctx)
96+
return err
97+
})
98+
if err != nil {
99+
return fmt.Errorf("waiting for vpn gateway creation: %w", err)
100+
}
101+
}
102+
103+
return outputResult(params.Printer, model.OutputFormat, model.Async, projectLabel, resp)
104+
},
105+
}
106+
configureFlags(cmd)
107+
return cmd
108+
}
109+
110+
func configureFlags(cmd *cobra.Command) {
111+
cmd.Flags().String(availabilityZoneTunnel1Flag, "", "Availability Zone of Tunnel 1")
112+
cmd.Flags().String(availabilityZoneTunnel2Flag, "", "Availability Zone of Tunnel 2")
113+
cmd.Flags().Int64(bgpLocalAsnFlag, 0, "ASN for private use (reserved by IANA), both 16Bit and 32Bit ranges are valid (RFC 6996)")
114+
cmd.Flags().StringArray(bgpOverrideAdvertisedRoutesFlag, nil, "A list of IPv4 Prefixes to advertise via BGP")
115+
cmd.Flags().String(nameFlag, "", "Gateway name")
116+
cmd.Flags().StringToString(labelsFlag, nil, "Labels in key=value format, separated by commas")
117+
cmd.Flags().String(planIdFlag, "", "Plan ID")
118+
cmd.Flags().String(routingTypeFlag, "", "Routing Type: \"POLICY_BASED\", \"ROUTE_BASED\" or \"BGP_ROUTE_BASED\"")
119+
120+
err := flags.MarkFlagsRequired(cmd, availabilityZoneTunnel1Flag, availabilityZoneTunnel2Flag, nameFlag, planIdFlag, routingTypeFlag)
121+
cobra.CheckErr(err)
122+
}
123+
124+
func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, error) {
125+
globalFlags := globalflags.Parse(p, cmd)
126+
if globalFlags.ProjectId == "" {
127+
return nil, &errors.ProjectIdError{}
128+
}
129+
130+
bgpLocalAsn := flags.FlagToInt64Pointer(p, cmd, bgpLocalAsnFlag)
131+
if bgpLocalAsn != nil {
132+
if *bgpLocalAsn < 0 {
133+
return nil, &errors.FlagValidationError{
134+
Flag: bgpLocalAsnFlag,
135+
Details: "must be a positive integer",
136+
}
137+
}
138+
}
139+
bgpOverrideAdvertisedRoutes := flags.FlagToStringArrayValue(p, cmd, bgpOverrideAdvertisedRoutesFlag)
140+
141+
var bgp *vpn.BGPGatewayConfig
142+
if bgpLocalAsn != nil || bgpOverrideAdvertisedRoutes != nil {
143+
bgp = &vpn.BGPGatewayConfig{
144+
LocalAsn: bgpLocalAsn,
145+
OverrideAdvertisedRoutes: flags.FlagToStringArrayValue(p, cmd, bgpOverrideAdvertisedRoutesFlag),
146+
}
147+
}
148+
149+
model := inputModel{
150+
GlobalFlagModel: globalFlags,
151+
AvailabilityZone: vpn.CreateGatewayPayloadAvailabilityZones{
152+
Tunnel1: flags.FlagToStringValue(p, cmd, availabilityZoneTunnel1Flag),
153+
Tunnel2: flags.FlagToStringValue(p, cmd, availabilityZoneTunnel2Flag),
154+
},
155+
Bgp: bgp,
156+
Name: flags.FlagToStringValue(p, cmd, nameFlag),
157+
Labels: flags.FlagToStringToStringPointer(p, cmd, labelsFlag),
158+
PlanId: flags.FlagToStringValue(p, cmd, planIdFlag),
159+
RoutingType: vpn.RoutingType(flags.FlagToStringValue(p, cmd, routingTypeFlag)),
160+
}
161+
162+
p.DebugInputModel(model)
163+
return &model, nil
164+
}
165+
166+
func buildRequest(ctx context.Context, model *inputModel, apiClient *vpn.APIClient) vpn.ApiCreateGatewayRequest {
167+
req := apiClient.DefaultAPI.CreateGateway(ctx, model.ProjectId, vpn.Region(model.Region))
168+
req = req.CreateGatewayPayload(
169+
vpn.CreateGatewayPayload{
170+
AvailabilityZones: model.AvailabilityZone,
171+
Bgp: model.Bgp,
172+
DisplayName: model.Name,
173+
Labels: model.Labels,
174+
PlanId: model.PlanId,
175+
RoutingType: model.RoutingType,
176+
},
177+
)
178+
return req
179+
}
180+
181+
func outputResult(p *print.Printer, outputFormat string, async bool, projectLabel string, item *vpn.GatewayResponse) error {
182+
return p.OutputResult(outputFormat, item, func() error {
183+
if item == nil {
184+
p.Outputln("vpn gateway response is empty")
185+
return nil
186+
}
187+
operation := "Created"
188+
if async {
189+
operation = "Triggered creation of"
190+
}
191+
p.Outputf(
192+
"%s vpn gateway %q in project %q.\nGateway ID: %s\n",
193+
operation,
194+
item.DisplayName,
195+
projectLabel,
196+
utils.PtrString(item.Id),
197+
)
198+
return nil
199+
})
200+
}

0 commit comments

Comments
 (0)