Skip to content

Commit 8832391

Browse files
committed
refactor(ufw):
- added create implementation
1 parent 0fba4e5 commit 8832391

2 files changed

Lines changed: 207 additions & 1 deletion

File tree

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,206 @@
11
package create
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/spf13/cobra"
8+
"github.com/stackitcloud/stackit-cli/internal/pkg/args"
9+
"github.com/stackitcloud/stackit-cli/internal/pkg/errors"
10+
"github.com/stackitcloud/stackit-cli/internal/pkg/examples"
11+
"github.com/stackitcloud/stackit-cli/internal/pkg/flags"
12+
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
13+
"github.com/stackitcloud/stackit-cli/internal/pkg/projectname"
14+
"github.com/stackitcloud/stackit-cli/internal/pkg/spinner"
15+
"github.com/stackitcloud/stackit-cli/internal/pkg/types"
16+
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
17+
ufw "github.com/stackitcloud/stackit-sdk-go/services/ufw/v1api"
18+
19+
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
20+
"github.com/stackitcloud/stackit-cli/internal/pkg/services/ufw/client"
21+
wait "github.com/stackitcloud/stackit-sdk-go/services/ufw/v1api/wait"
22+
)
23+
24+
const (
25+
productFlag = "product"
26+
typeFlag = "type"
27+
sourceIpFlag = "sourceIp"
28+
instanceIdFlag = "instanceId"
29+
directionFlag = "direction"
30+
descriptionFlag = "description"
31+
etherTypeFlag = "etherType"
32+
portRangeFlag = "portRange"
33+
protocolFlag = "protocol"
34+
offsetFlag = "offset"
35+
securityGroupIdFlag = "securityGroupId"
36+
)
37+
38+
type inputModel struct {
39+
*globalflags.GlobalFlagModel
40+
41+
Product *string
42+
Type *string
43+
SourceIp *string
44+
InstanceId *string
45+
Direction *string
46+
Description *string
47+
EtherType *string
48+
PortRange *string
49+
Protocol *string
50+
Offset *int32
51+
SecurityGroupId *string
52+
}
53+
54+
func NewCmd(params *types.CmdParams) *cobra.Command {
55+
cmd := &cobra.Command{
56+
Use: "create",
57+
Short: "Creates an UFW rule instance",
58+
Long: "Creates a STACKIT Unified Firewall (UFW) rule instance.",
59+
Args: args.NoArgs,
60+
Example: examples.Build(
61+
examples.NewExample(
62+
`Create a UFW rule instance of type ACL with sourceIp "1.1.1.1/32" of product "redis" for instance with id=ID`,
63+
"$ stackit ufw instance create --product redis --sourceIp 1.1.1.1/32 --type ACL --instanceId ID"),
64+
// TODO add more examples for creating Security Rule and Group types
65+
),
66+
RunE: func(cmd *cobra.Command, args []string) error {
67+
ctx := context.Background()
68+
model, err := parseInput(params.Printer, cmd)
69+
if err != nil {
70+
return err
71+
}
72+
73+
apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion)
74+
if err != nil {
75+
return err
76+
}
77+
78+
projectLabel, err := projectname.GetProjectName(ctx, params.Printer, params.CliVersion, cmd)
79+
if err != nil {
80+
params.Printer.Debug(print.ErrorLevel, "get project name: %v", err)
81+
projectLabel = model.ProjectId
82+
}
83+
84+
prompt := fmt.Sprintf("Are you sure you want to create a UFW rule instance for project %q?", projectLabel)
85+
err = params.Printer.PromptForConfirmation(prompt)
86+
if err != nil {
87+
return err
88+
}
89+
90+
req := buildRequest(ctx, model, apiClient)
91+
92+
resp, err := req.Execute()
93+
if err != nil {
94+
return fmt.Errorf("create UFW rule instance: %w", err)
95+
}
96+
instanceId := resp.RefId
97+
98+
if !model.Async {
99+
err := spinner.Run(params.Printer, "Creating ufw rule instance", func() error {
100+
_, err = wait.CreateRuleWaitHandler(ctx, apiClient.DefaultAPI, model.ProjectId, model.Region, *instanceId).WaitWithContext(ctx)
101+
return err
102+
})
103+
if err != nil {
104+
return fmt.Errorf("wait for UFW rule instance creation: %w", err)
105+
}
106+
}
107+
108+
return outputResult(params.Printer, model.OutputFormat, model.Async, projectLabel, resp)
109+
},
110+
}
111+
configureFlags(cmd)
112+
return cmd
113+
}
114+
115+
func configureFlags(cmd *cobra.Command) {
116+
cmd.Flags().StringP(productFlag, "p", "", "The source service (e.g., Load Balancer, Redis) where you want to attach a rule")
117+
cmd.Flags().StringP(typeFlag, "t", "", "Type (ACL/SecurityRule/SecurityGroup/PublicIP) You can check /provider-options route for them")
118+
cmd.Flags().StringP(sourceIpFlag, "s", "", "The IP (CIDR) to which the rule applies (e.g. 192.168.0.1/32)")
119+
cmd.Flags().StringP(instanceIdFlag, "i", "", "Instance ID that will have attached your rule")
120+
cmd.Flags().StringP(directionFlag, "d", "", "Direction (the direction of the traffic, typically ingress or egress, for security rules type)")
121+
cmd.Flags().StringP(descriptionFlag, "D", "", "Description")
122+
cmd.Flags().StringP(etherTypeFlag, "e", "", "Specifies the bound of the rule (for security rules type)")
123+
cmd.Flags().StringP(portRangeFlag, "r", "", "Port range (the Port range to which the rule applies, for security rules type)")
124+
cmd.Flags().StringP(protocolFlag, "o", "", "The network protocol (e.g. TCP, UDP, ICMP, for security rules type)")
125+
cmd.Flags().Int32P(offsetFlag, "f", -1, "Offset - Position in the ACL list of an instance, will be ignored at creation")
126+
cmd.Flags().StringP(securityGroupIdFlag, "g", "", "Security group ID - The ID of the Security Group")
127+
128+
err := flags.MarkFlagsRequired(cmd, instanceIdFlag)
129+
cobra.CheckErr(err)
130+
131+
err = flags.MarkFlagsRequired(cmd, productFlag)
132+
cobra.CheckErr(err)
133+
134+
err = flags.MarkFlagsRequired(cmd, sourceIpFlag)
135+
cobra.CheckErr(err)
136+
137+
err = flags.MarkFlagsRequired(cmd, typeFlag)
138+
cobra.CheckErr(err)
139+
}
140+
141+
func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) {
142+
globalFlags := globalflags.Parse(p, cmd)
143+
if globalFlags.ProjectId == "" {
144+
return nil, &errors.ProjectIdError{}
145+
}
146+
147+
if globalFlags.Region == "" {
148+
return nil, &errors.RegionError{}
149+
}
150+
151+
model := inputModel{
152+
GlobalFlagModel: globalFlags,
153+
154+
Product: flags.FlagToStringPointer(p, cmd, productFlag),
155+
Type: flags.FlagToStringPointer(p, cmd, typeFlag),
156+
SourceIp: flags.FlagToStringPointer(p, cmd, sourceIpFlag),
157+
InstanceId: flags.FlagToStringPointer(p, cmd, instanceIdFlag),
158+
Direction: flags.FlagToStringPointer(p, cmd, directionFlag),
159+
Description: flags.FlagToStringPointer(p, cmd, descriptionFlag),
160+
EtherType: flags.FlagToStringPointer(p, cmd, etherTypeFlag),
161+
PortRange: flags.FlagToStringPointer(p, cmd, portRangeFlag),
162+
Protocol: flags.FlagToStringPointer(p, cmd, protocolFlag),
163+
Offset: flags.FlagToInt32Pointer(p, cmd, offsetFlag),
164+
SecurityGroupId: flags.FlagToStringPointer(p, cmd, securityGroupIdFlag),
165+
}
166+
167+
p.DebugInputModel(model)
168+
return &model, nil
169+
}
170+
171+
func buildRequest(ctx context.Context, model *inputModel, apiClient *ufw.APIClient) ufw.ApiCreateRuleRequest {
172+
req := apiClient.DefaultAPI.CreateRule(ctx, model.ProjectId, model.Region)
173+
174+
// TODO - add logic for field checking: existing ACLs, correct product, type, instanceID maybe
175+
176+
req = req.CreateRulePayload(ufw.CreateRulePayload{
177+
Product: *model.Product,
178+
Type: *model.Type,
179+
SourceIP: *model.SourceIp,
180+
InstanceId: *model.InstanceId,
181+
Direction: model.Direction,
182+
Description: model.Description,
183+
EtherType: model.EtherType,
184+
PortRange: model.PortRange,
185+
Protocol: model.Protocol,
186+
Offset: model.Offset,
187+
SecurityGroupId: model.SecurityGroupId,
188+
})
189+
190+
return req
191+
}
192+
193+
func outputResult(p *print.Printer, outputFormat string, async bool, projectLabel string, rule *ufw.CreateRuleResponse) error {
194+
if rule == nil {
195+
return fmt.Errorf("response is nil")
196+
}
197+
198+
return p.OutputResult(outputFormat, rule, func() error {
199+
operationState := "Created"
200+
if async {
201+
operationState = "Triggered creation of"
202+
}
203+
p.Outputf("%s rule for project %q. Rule refID: %s\n", operationState, projectLabel, utils.PtrString(rule.RefId))
204+
return nil
205+
})
206+
}

‎internal/cmd/ufw/ufw.go‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package ufw
22

33
import (
44
"github.com/spf13/cobra"
5+
"github.com/stackitcloud/stackit-cli/internal/cmd/ufw/rules/create"
56
"github.com/stackitcloud/stackit-cli/internal/cmd/ufw/rules/delete"
67
"github.com/stackitcloud/stackit-cli/internal/cmd/ufw/rules/describe"
78
"github.com/stackitcloud/stackit-cli/internal/cmd/ufw/rules/list"
@@ -25,7 +26,7 @@ func NewCmd(params *types.CmdParams) *cobra.Command {
2526
func addSubcommands(cmd *cobra.Command, params *types.CmdParams) {
2627
cmd.AddCommand(list.NewCmd(params))
2728
cmd.AddCommand(describe.NewCmd(params))
28-
//cmd.AddCommand(create.NewCmd(params))
29+
cmd.AddCommand(create.NewCmd(params))
2930
//cmd.AddCommand(update.NewCmd(params))
3031
cmd.AddCommand(delete.NewCmd(params))
3132
}

0 commit comments

Comments
 (0)