Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
db95609
feat(ufw): onboard service
alexTiugan Sep 2, 2026
65099c9
refactor(ufw):
alexTiugan Sep 3, 2026
0fba4e5
refactor(ufw):
alexTiugan Sep 3, 2026
8832391
refactor(ufw):
alexTiugan Sep 4, 2026
c09913a
refactor(ufw):
alexTiugan Sep 4, 2026
8ab1d41
refactor(ufw):
alexTiugan Sep 7, 2026
5f4a80d
refactor(ufw):
alexTiugan Sep 7, 2026
73e3ca3
refactor(ufw):
alexTiugan Sep 7, 2026
2d5bcd4
refactor(ufw):
alexTiugan Sep 7, 2026
113ff44
refactor(ufw):
alexTiugan Sep 7, 2026
b3f6256
refactor(ufw):
alexTiugan Sep 8, 2026
f573dc2
refactor(ufw):
alexTiugan Sep 8, 2026
c95fa6d
refactor(ufw):
alexTiugan Sep 8, 2026
29eaa37
Merge branch 'main' into feat/onboard-ufw
alexTiugan Sep 9, 2026
275db0c
docs(ufw):
alexTiugan Sep 9, 2026
23b6f0d
refactor(ufw):
alexTiugan Sep 9, 2026
76fc58e
refactor(ufw):
alexTiugan Sep 9, 2026
30151c5
refactor(ufw):
alexTiugan Sep 9, 2026
9a5b3c0
refactor(ufw):
alexTiugan Sep 9, 2026
2588cf3
refactor(ufw):
alexTiugan Sep 9, 2026
abc2f49
docs(ufw):
alexTiugan Sep 9, 2026
cb3a768
Merge branch 'main' into feat/onboard-ufw
alexTiugan Sep 10, 2026
ced19dd
refactoring(ufw):
alexTiugan Sep 10, 2026
81dc025
refactoring(ufw):
alexTiugan Sep 10, 2026
ac8dd73
refactor(ufw):
alexTiugan Sep 10, 2026
1ad099a
Merge branch 'main' into feat/onboard-ufw
alexTiugan Sep 11, 2026
c7f2245
Merge branch 'main' into feat/onboard-ufw
alexTiugan Sep 14, 2026
4420fc8
Merge branch 'main' into feat/onboard-ufw
alexTiugan Sep 15, 2026
9306418
Merge branch 'main' into feat/onboard-ufw
alexTiugan Sep 16, 2026
6d0e685
Merge branch 'main' into feat/onboard-ufw
alexTiugan Sep 18, 2026
7f648c8
Merge branch 'main' into feat/onboard-ufw
alexTiugan Sep 21, 2026
5eb46ea
Merge branch 'main' into feat/onboard-ufw
alexTiugan Sep 25, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
refactor(ufw):
- added tests for create implementation
- fixed -p and -o flags that were used for something else
  • Loading branch information
alexTiugan committed Sep 7, 2026
commit 113ff44cbb82e6b9d2032f4eff0daf1b96fe67a6
4 changes: 2 additions & 2 deletions internal/cmd/ufw/rules/create/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,15 +113,15 @@ func NewCmd(params *types.CmdParams) *cobra.Command {
}

func configureFlags(cmd *cobra.Command) {
cmd.Flags().StringP(productFlag, "p", "", "The source service (e.g., Load Balancer, Redis) where you want to attach a rule")
cmd.Flags().String(productFlag, "", "The source service (e.g., Load Balancer, Redis) where you want to attach a rule")
cmd.Flags().StringP(typeFlag, "t", "", "Type (ACL/SecurityRule/SecurityGroup/PublicIP) You can check /provider-options route for them")
cmd.Flags().StringP(sourceIpFlag, "s", "", "The IP (CIDR) to which the rule applies (e.g. 192.168.0.1/32)")
cmd.Flags().StringP(instanceIdFlag, "i", "", "Instance ID that will have attached your rule")
cmd.Flags().StringP(directionFlag, "d", "", "Direction (the direction of the traffic, typically ingress or egress, for security rules type)")
cmd.Flags().StringP(descriptionFlag, "D", "", "Description")
cmd.Flags().StringP(etherTypeFlag, "e", "", "Specifies the bound of the rule (for security rules type)")
cmd.Flags().StringP(portRangeFlag, "r", "", "Port range (the Port range to which the rule applies, for security rules type)")
cmd.Flags().StringP(protocolFlag, "o", "", "The network protocol (e.g. TCP, UDP, ICMP, for security rules type)")
cmd.Flags().String(protocolFlag, "", "The network protocol (e.g. TCP, UDP, ICMP, for security rules type)")
cmd.Flags().Int32P(offsetFlag, "f", -1, "Offset - Position in the ACL list of an instance, will be ignored at creation")
cmd.Flags().StringP(securityGroupIdFlag, "g", "", "Security group ID - The ID of the Security Group")

Expand Down
265 changes: 265 additions & 0 deletions internal/cmd/ufw/rules/create/create_test.go
Original file line number Diff line number Diff line change
@@ -1 +1,266 @@
package create

import (
"context"
"testing"

"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/google/uuid"
"github.com/spf13/cobra"
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
"github.com/stackitcloud/stackit-cli/internal/pkg/testparams"
"github.com/stackitcloud/stackit-cli/internal/pkg/testutils"
ufw "github.com/stackitcloud/stackit-sdk-go/services/ufw/v1api"
)

type testCtxKey struct{}

var (
testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo")
testClient = &ufw.APIClient{DefaultAPI: &ufw.DefaultAPIService{}}
testProjectId = uuid.NewString()
testInstanceId = uuid.NewString()
)

const (
testRegion = "eu01"
testProduct = "redis"
testType = "ACL"
testSourceIp = "1.1.1.1/32"
)

func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string {
flagValues := map[string]string{
globalflags.ProjectIdFlag: testProjectId,
globalflags.RegionFlag: testRegion,
productFlag: testProduct,
typeFlag: testType,
sourceIpFlag: testSourceIp,
instanceIdFlag: testInstanceId,
directionFlag: "ingress",
descriptionFlag: "example-description",
etherTypeFlag: "IPv4",
portRangeFlag: "80-443",
protocolFlag: "TCP",
offsetFlag: "10",
securityGroupIdFlag: "example-sec-group",
}
for _, mod := range mods {
mod(flagValues)
}
return flagValues
}

func fixtureInputModel(mods ...func(model *inputModel)) *inputModel {
model := &inputModel{
GlobalFlagModel: &globalflags.GlobalFlagModel{
ProjectId: testProjectId,
Region: testRegion,
Verbosity: globalflags.VerbosityDefault,
},
Product: new(testProduct),
Type: new(testType),
SourceIp: new(testSourceIp),
InstanceId: new(testInstanceId),
Direction: new("ingress"),
Description: new("example-description"),
EtherType: new("IPv4"),
PortRange: new("80-443"),
Protocol: new("TCP"),
Offset: new(int32(10)),
SecurityGroupId: new("example-sec-group"),
}
for _, mod := range mods {
mod(model)
}
return model
}

func fixtureRequest(mods ...func(request *ufw.ApiCreateRuleRequest)) ufw.ApiCreateRuleRequest {
request := testClient.DefaultAPI.CreateRule(testCtx, testProjectId, testRegion)
request = request.CreateRulePayload(ufw.CreateRulePayload{
Product: testProduct,
Type: testType,
SourceIP: testSourceIp,
InstanceId: testInstanceId,
Direction: new("ingress"),
Description: new("example-description"),
EtherType: new("IPv4"),
PortRange: new("80-443"),
Protocol: new("TCP"),
Offset: new(int32(10)),
SecurityGroupId: new("example-sec-group"),
})
for _, mod := range mods {
mod(&request)
}
return request
}

func TestParseInput(t *testing.T) {
tests := []struct {
description string
flagValues map[string]string
isValid bool
expectedModel *inputModel
}{
{
description: "base",
flagValues: fixtureFlagValues(),
isValid: true,
expectedModel: fixtureInputModel(),
},
{
description: "no values",
flagValues: map[string]string{},
isValid: false,
},
{
description: "required fields only",
flagValues: map[string]string{
globalflags.ProjectIdFlag: testProjectId,
globalflags.RegionFlag: testRegion,
productFlag: testProduct,
typeFlag: testType,
sourceIpFlag: testSourceIp,
instanceIdFlag: testInstanceId,
},
isValid: true,
expectedModel: &inputModel{
GlobalFlagModel: &globalflags.GlobalFlagModel{
ProjectId: testProjectId,
Region: testRegion,
Verbosity: globalflags.VerbosityDefault,
},
Product: new(testProduct),
Type: new(testType),
SourceIp: new(testSourceIp),
InstanceId: new(testInstanceId),
},
},
{
description: "project id missing",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, globalflags.ProjectIdFlag)
}),
isValid: false,
},
{
description: "project id invalid 1",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[globalflags.ProjectIdFlag] = ""
}),
isValid: false,
},
{
description: "project id invalid 2",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[globalflags.ProjectIdFlag] = "invalid-uuid"
}),
isValid: false,
},
{
description: "region missing",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, globalflags.RegionFlag)
}),
isValid: false,
},
}

for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
parseInputWrapper := func(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, error) {
return parseInput(p, cmd)
}
testutils.TestParseInput(t, NewCmd, parseInputWrapper, tt.expectedModel, nil, tt.flagValues, tt.isValid)
})
}
}

func TestBuildRequest(t *testing.T) {
tests := []struct {
description string
model *inputModel
expectedRequest ufw.ApiCreateRuleRequest
}{
{
description: "base",
model: fixtureInputModel(),
expectedRequest: fixtureRequest(),
},
{
description: "required fields only",
model: &inputModel{
GlobalFlagModel: &globalflags.GlobalFlagModel{
ProjectId: testProjectId,
Region: testRegion,
Verbosity: globalflags.VerbosityDefault,
},
Product: new(testProduct),
Type: new(testType),
SourceIp: new(testSourceIp),
InstanceId: new(testInstanceId),
},
expectedRequest: testClient.DefaultAPI.CreateRule(testCtx, testProjectId, testRegion).
CreateRulePayload(ufw.CreateRulePayload{
Product: testProduct,
Type: testType,
SourceIP: testSourceIp,
InstanceId: testInstanceId,
}),
},
}

for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
request := buildRequest(testCtx, tt.model, testClient)

diff := cmp.Diff(request, tt.expectedRequest,
cmp.AllowUnexported(tt.expectedRequest),
cmpopts.EquateComparable(testCtx, ufw.DefaultAPIService{}),
)
if diff != "" {
t.Fatalf("Data does not match: %s", diff)
}
})
}
}

func TestOutputResult(t *testing.T) {
type args struct {
outputFormat string
async bool
projectLabel string
rule *ufw.CreateRuleResponse
}
tests := []struct {
name string
args args
wantErr bool
}{
{
name: "empty",
args: args{},
wantErr: true,
},
{
name: "set empty response",
args: args{
rule: &ufw.CreateRuleResponse{},
},
wantErr: false,
},
}

params := testparams.NewTestParams()
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := outputResult(params.Printer, tt.args.outputFormat, tt.args.async, tt.args.projectLabel, tt.args.rule); (err != nil) != tt.wantErr {
t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}