Skip to content

Commit db95609

Browse files
committed
feat(ufw): onboard service
- created folder structure - added UfwCustomEndpointKey constant - realized link between root and ufw rules - added handling for ufw rules listing
1 parent 5d87a6f commit db95609

19 files changed

Lines changed: 257 additions & 0 deletions

File tree

‎go.mod‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ require (
3838
github.com/stackitcloud/stackit-sdk-go/services/serviceenablement v1.7.1
3939
github.com/stackitcloud/stackit-sdk-go/services/ske v1.21.1
4040
github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex v1.18.0
41+
github.com/stackitcloud/stackit-sdk-go/services/ufw v0.1.0
4142
github.com/stackitcloud/stackit-sdk-go/services/valkey v0.2.0
4243
github.com/stackitcloud/stackit-sdk-go/services/vpn v0.15.0
4344
github.com/zalando/go-keyring v0.2.8

‎go.sum‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -654,6 +654,8 @@ github.com/stackitcloud/stackit-sdk-go/services/ske v1.21.1 h1:dSkUoaMip0uA8tnRE
654654
github.com/stackitcloud/stackit-sdk-go/services/ske v1.21.1/go.mod h1:TbqmZhLMofmfl+HhVl6oHYcI3zvXTm1vRjN3A/fOkM4=
655655
github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex v1.18.0 h1:k4dgOFQvkBhSNTtmFOZwMbkKf6ZCEGB/D1WQgiO9gwc=
656656
github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex v1.18.0/go.mod h1:AiUoMAqQcOlMgDtkVJlqI7P/VGD5xjN3dYjERGnwN/M=
657+
github.com/stackitcloud/stackit-sdk-go/services/ufw v0.1.0 h1:LnDUxI7v507T341Smff/fVzSSRP59uIOUmcHAbUf1hE=
658+
github.com/stackitcloud/stackit-sdk-go/services/ufw v0.1.0/go.mod h1:c/JxzGai01jCK/5OCi0TxCUiQ1Gl7SboGuErflWxLqQ=
657659
github.com/stackitcloud/stackit-sdk-go/services/valkey v0.2.0 h1:RP9ITvjsPhA2+Xi9MhqYP9xkEsURyxYmRB3JKXIB5zc=
658660
github.com/stackitcloud/stackit-sdk-go/services/valkey v0.2.0/go.mod h1:etzt/a723p327dqha1V72cCoBWjUBS2DtHy3ZTac0SA=
659661
github.com/stackitcloud/stackit-sdk-go/services/vpn v0.15.0 h1:JpMJjWBa6fwNNcAHoq00v+8+DwMD0/fe4WPjvbHdY2o=

‎internal/cmd/root.go‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"time"
77

88
"github.com/stackitcloud/stackit-cli/internal/cmd/sqlserverflex"
9+
"github.com/stackitcloud/stackit-cli/internal/cmd/ufw"
910
"github.com/stackitcloud/stackit-cli/internal/pkg/types"
1011

1112
affinityGroups "github.com/stackitcloud/stackit-cli/internal/cmd/affinity-groups"
@@ -207,6 +208,7 @@ func addSubcommands(cmd *cobra.Command, params *types.CmdParams) {
207208
cmd.AddCommand(kms.NewCmd(params))
208209
cmd.AddCommand(sqlserverflex.NewCmd(params))
209210
cmd.AddCommand(valkey.NewCmd(params))
211+
cmd.AddCommand(ufw.NewCmd(params))
210212
}
211213

212214
// traverseCommands calls f for c and all of its children.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
package create
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
package create
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
package delete
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
package delete
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
package describe
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
package describe
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
package list
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
serviceEnablementClient "github.com/stackitcloud/stackit-cli/internal/pkg/services/service-enablement/client"
8+
serviceEnablementUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/service-enablement/utils"
9+
"github.com/stackitcloud/stackit-cli/internal/pkg/types"
10+
ufw "github.com/stackitcloud/stackit-sdk-go/services/ufw/v1api"
11+
12+
"github.com/spf13/cobra"
13+
"github.com/stackitcloud/stackit-cli/internal/pkg/args"
14+
"github.com/stackitcloud/stackit-cli/internal/pkg/errors"
15+
"github.com/stackitcloud/stackit-cli/internal/pkg/examples"
16+
"github.com/stackitcloud/stackit-cli/internal/pkg/flags"
17+
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
18+
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
19+
"github.com/stackitcloud/stackit-cli/internal/pkg/projectname"
20+
"github.com/stackitcloud/stackit-cli/internal/pkg/services/ufw/client"
21+
"github.com/stackitcloud/stackit-cli/internal/pkg/tables"
22+
)
23+
24+
const (
25+
limitFlag = "limit"
26+
)
27+
28+
type inputModel struct {
29+
*globalflags.GlobalFlagModel
30+
Limit *int64
31+
}
32+
33+
func NewCmd(params *types.CmdParams) *cobra.Command {
34+
cmd := &cobra.Command{
35+
Use: "list",
36+
Short: "Lists all UFW rules",
37+
Long: "Lists all STACKIT Unified Firewall (UFW) rules.",
38+
Args: args.NoArgs,
39+
Example: examples.Build(
40+
examples.NewExample(
41+
`List all UFW rules`,
42+
"$ stackit ufw rules list"),
43+
examples.NewExample(
44+
`List all UFW rules in JSON format`,
45+
"$ stackit ufw rules list --output-format json"),
46+
examples.NewExample(
47+
`List up to 10 UFW rules`,
48+
"$ stackit ufw rules list --limit 10"),
49+
),
50+
RunE: func(cmd *cobra.Command, args []string) error {
51+
ctx := context.Background()
52+
model, err := parseInput(params.Printer, cmd, args)
53+
if err != nil {
54+
return err
55+
}
56+
57+
apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion)
58+
if err != nil {
59+
return err
60+
}
61+
62+
serviceEnablementApiClient, err := serviceEnablementClient.ConfigureClient(params.Printer, params.CliVersion)
63+
if err != nil {
64+
return err
65+
}
66+
67+
req := buildRequest(ctx, model, apiClient)
68+
resp, err := req.Execute()
69+
if err != nil {
70+
enabled, enabledErr := serviceEnablementUtils.ProjectEnabled(ctx, serviceEnablementApiClient.DefaultAPI, model.ProjectId, model.Region)
71+
if enabledErr != nil {
72+
return fmt.Errorf("check if project is enabled failed: %w", enabledErr)
73+
}
74+
if !enabled {
75+
return &errors.ServiceDisabledError{
76+
Service: "ufw",
77+
}
78+
}
79+
return fmt.Errorf("get UFW rules: %w", err)
80+
}
81+
rules := resp.Rules
82+
83+
if model.Limit != nil && len(rules) > int(*model.Limit) {
84+
rules = rules[:*model.Limit]
85+
}
86+
87+
projectLabel := model.ProjectId
88+
if len(rules) == 0 {
89+
projectLabel, err = projectname.GetProjectName(ctx, params.Printer, params.CliVersion, cmd)
90+
if err != nil {
91+
params.Printer.Debug(print.ErrorLevel, "get project name: %v", err)
92+
}
93+
}
94+
95+
if resp.Rules == nil {
96+
params.Printer.Info("(...)", projectLabel)
97+
return nil
98+
}
99+
100+
return outputResult(params.Printer, model.OutputFormat, projectLabel, rules)
101+
},
102+
}
103+
104+
configureFlags(cmd)
105+
return cmd
106+
}
107+
108+
// Configure command flags (type, default value, and description)
109+
func configureFlags(cmd *cobra.Command) {
110+
cmd.Flags().Int64(limitFlag, 0, "Maximum number of entries to list")
111+
}
112+
113+
// Parse command input and return a standardized model
114+
func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, error) {
115+
globalFlags := globalflags.Parse(p, cmd)
116+
if globalFlags.ProjectId == "" {
117+
return nil, &errors.ProjectIdError{}
118+
}
119+
120+
if globalFlags.Region == "" {
121+
return nil, &errors.RegionError{}
122+
}
123+
124+
limit := flags.FlagToInt64Pointer(p, cmd, limitFlag)
125+
if limit != nil && *limit < 1 {
126+
return nil, &errors.FlagValidationError{
127+
Flag: limitFlag,
128+
Details: "must be greater than 0",
129+
}
130+
}
131+
132+
model := inputModel{
133+
GlobalFlagModel: globalFlags,
134+
Limit: flags.FlagToInt64Pointer(p, cmd, limitFlag),
135+
}
136+
137+
p.DebugInputModel(model)
138+
return &model, nil
139+
}
140+
141+
// Build request to the API
142+
func buildRequest(ctx context.Context, model *inputModel, apiClient *ufw.APIClient) ufw.ApiListRulesRequest {
143+
req := apiClient.DefaultAPI.ListRules(ctx, model.ProjectId, model.Region)
144+
return req
145+
}
146+
147+
// Output result based on the configured output format
148+
func outputResult(p *print.Printer, outputFormat, projectLabel string, resources []ufw.RuleResponse) error {
149+
return p.OutputResult(outputFormat, resources, func() error {
150+
if len(resources) == 0 {
151+
p.Outputf("No rules found for project %q\n", projectLabel)
152+
return nil
153+
}
154+
155+
table := tables.NewTable()
156+
table.SetHeader("PRODUCT", "DEPLOYMENT TARGET", "PROTOCOL",
157+
"DIRECTION", "PORT RANGE", "ETHER TYPE", "STATUS")
158+
for i := range resources {
159+
resource := resources[i]
160+
table.AddRow(resource.Product, resource.InstanceName, resource.Protocol, resource.Direction,
161+
resource.PortRange, resource.EtherType, resource.Status)
162+
}
163+
err := table.Display(p)
164+
if err != nil {
165+
return fmt.Errorf("render table: %w", err)
166+
}
167+
return nil
168+
})
169+
}

0 commit comments

Comments
 (0)