🚀 Big News:Socket Has Acquired Secure Annex.Learn More
Socket
Book a DemoSign in
Socket

mygithub.libinneed.workers.dev/stackitcloud/stackit-cli

Package Overview
Dependencies
Versions
178
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

mygithub.libinneed.workers.dev/stackitcloud/stackit-cli - go Package Compare versions

Comparing version
v0.56.0
to
v0.57.0
+54
docs/stackit_network-area_routing-table_route_create.md
## stackit network-area routing-table route create
Creates a route in a routing-table
### Synopsis
Creates a route in a routing-table.
```
stackit network-area routing-table route create [flags]
```
### Examples
```
Create a route with CIDRv4 destination and IPv4 nexthop
$ stackit network-area routing-table route create --routing-table-id xxx --organization-id yyy --network-area-id zzz --destination-type cidrv4 --destination-value <ipv4-cidr> --nexthop-type ipv4 --nexthop-value <ipv4-address>
Create a route with CIDRv6 destination and IPv6 nexthop
$ stackit network-area routing-table route create --routing-table-id xxx --organization-id yyy --network-area-id zzz --destination-type cidrv6 --destination-value <ipv6-cidr> --nexthop-type ipv6 --nexthop-value <ipv6-address>
Create a route with CIDRv6 destination and Nexthop Internet
$ stackit network-area routing-table route create --routing-table-id xxx --organization-id yyy --network-area-id zzz --destination-type cidrv6 --destination-value <ipv6-cidr> --nexthop-type internet
```
### Options
```
--destination-type string Destination type
--destination-value string Destination value
-h, --help Help for "stackit network-area routing-table route create"
--labels stringToString Key=value labels (default [])
--network-area-id string Network-Area ID
--nexthop-type string Next hop type
--nexthop-value string NextHop value
--organization-id string Organization ID
--routing-table-id string Routing-Table ID
```
### Options inherited from parent commands
```
-y, --assume-yes If set, skips all confirmation prompts
--async If set, runs the command asynchronously
-o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"]
-p, --project-id string Project ID
--region string Target region for region-specific requests
--verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info")
```
### SEE ALSO
* [stackit network-area routing-table route](./stackit_network-area_routing-table_route.md) - Manages routes of a routing-table
## stackit network-area routing-table route delete
Deletes a route within a routing-table
### Synopsis
Deletes a route within a routing-table
```
stackit network-area routing-table route delete routing-table-id [flags]
```
### Examples
```
Deletes a route within a routing-table
$ stackit network-area routing-table route delete xxx --routing-table-id xxx --organization-id yyy --network-area-id zzz
```
### Options
```
-h, --help Help for "stackit network-area routing-table route delete"
--network-area-id string Network-Area ID
--organization-id string Organization ID
--routing-table-id string Routing-Table ID
```
### Options inherited from parent commands
```
-y, --assume-yes If set, skips all confirmation prompts
--async If set, runs the command asynchronously
-o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"]
-p, --project-id string Project ID
--region string Target region for region-specific requests
--verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info")
```
### SEE ALSO
* [stackit network-area routing-table route](./stackit_network-area_routing-table_route.md) - Manages routes of a routing-table
## stackit network-area routing-table route describe
Describes a route within a routing-table
### Synopsis
Describes a route within a routing-table
```
stackit network-area routing-table route describe ROUTE_ID [flags]
```
### Examples
```
Describe a route within a routing-table
$ stackit network-area routing-table route describe xxx --routing-table-id xxx --organization-id yyy --network-area-id zzz
```
### Options
```
-h, --help Help for "stackit network-area routing-table route describe"
--network-area-id string Network-Area ID
--organization-id string Organization ID
--routing-table-id string Routing-Table ID
```
### Options inherited from parent commands
```
-y, --assume-yes If set, skips all confirmation prompts
--async If set, runs the command asynchronously
-o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"]
-p, --project-id string Project ID
--region string Target region for region-specific requests
--verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info")
```
### SEE ALSO
* [stackit network-area routing-table route](./stackit_network-area_routing-table_route.md) - Manages routes of a routing-table
## stackit network-area routing-table route list
Lists all routes within a routing-table
### Synopsis
Lists all routes within a routing-table
```
stackit network-area routing-table route list [flags]
```
### Examples
```
List all routes within a routing-table
$ stackit network-area routing-table route list --routing-table-id xxx --organization-id yyy --network-area-id zzz
List all routes within a routing-table with labels
$ stackit network-area routing-table list --routing-table-id xxx --organization-id yyy --network-area-id zzz --label-selector env=dev,env=rc
List all routes within a routing-tables with labels and limit to 10
$ stackit network-area routing-table list --routing-table-id xxx --organization-id yyy --network-area-id zzz --label-selector env=dev,env=rc --limit 10
```
### Options
```
-h, --help Help for "stackit network-area routing-table route list"
--label-selector string Filter by label
--limit int Maximum number of entries to list
--network-area-id string Network-Area ID
--organization-id string Organization ID
--routing-table-id string Routing-Table ID
```
### Options inherited from parent commands
```
-y, --assume-yes If set, skips all confirmation prompts
--async If set, runs the command asynchronously
-o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"]
-p, --project-id string Project ID
--region string Target region for region-specific requests
--verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info")
```
### SEE ALSO
* [stackit network-area routing-table route](./stackit_network-area_routing-table_route.md) - Manages routes of a routing-table
## stackit network-area routing-table route update
Updates a route in a routing-table
### Synopsis
Updates a route in a routing-table.
```
stackit network-area routing-table route update ROUTE_ID [flags]
```
### Examples
```
Updates the label(s) of a route with ID "xxx" in a routing-table ID "xxx" in organization with ID "yyy" and network-area with ID "zzz"
$ stackit network-area routing-table route update xxx --labels key=value,foo=bar --routing-table-id xxx --organization-id yyy --network-area-id zzz
```
### Options
```
-h, --help Help for "stackit network-area routing-table route update"
--labels stringToString Labels are key-value string pairs which can be attached to a route. A label can be provided with the format key=value and the flag can be used multiple times to provide a list of labels (default [])
--network-area-id string Network-Area ID
--organization-id string Organization ID
--routing-table-id string Routing-Table ID
```
### Options inherited from parent commands
```
-y, --assume-yes If set, skips all confirmation prompts
--async If set, runs the command asynchronously
-o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"]
-p, --project-id string Project ID
--region string Target region for region-specific requests
--verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info")
```
### SEE ALSO
* [stackit network-area routing-table route](./stackit_network-area_routing-table_route.md) - Manages routes of a routing-table
## stackit network-area routing-table route
Manages routes of a routing-table
### Synopsis
Manages routes of a routing-table
```
stackit network-area routing-table route [flags]
```
### Options
```
-h, --help Help for "stackit network-area routing-table route"
```
### Options inherited from parent commands
```
-y, --assume-yes If set, skips all confirmation prompts
--async If set, runs the command asynchronously
-o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"]
-p, --project-id string Project ID
--region string Target region for region-specific requests
--verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info")
```
### SEE ALSO
* [stackit network-area routing-table](./stackit_network-area_routing-table.md) - Manage routing-tables and its according routes
* [stackit network-area routing-table route create](./stackit_network-area_routing-table_route_create.md) - Creates a route in a routing-table
* [stackit network-area routing-table route delete](./stackit_network-area_routing-table_route_delete.md) - Deletes a route within a routing-table
* [stackit network-area routing-table route describe](./stackit_network-area_routing-table_route_describe.md) - Describes a route within a routing-table
* [stackit network-area routing-table route list](./stackit_network-area_routing-table_route_list.md) - Lists all routes within a routing-table
* [stackit network-area routing-table route update](./stackit_network-area_routing-table_route_update.md) - Updates a route in a routing-table
## stackit server security-group attach
Attaches a security group to a server
### Synopsis
Attaches a security group to a server.
```
stackit server security-group attach [flags]
```
### Examples
```
Attach a security group with ID "xxx" to a server with ID "yyy"
$ stackit server security-group attach --server-id yyy --security-group-id xxx
```
### Options
```
-h, --help Help for "stackit server security-group attach"
--security-group-id string Security Group ID
--server-id string Server ID
```
### Options inherited from parent commands
```
-y, --assume-yes If set, skips all confirmation prompts
--async If set, runs the command asynchronously
-o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"]
-p, --project-id string Project ID
--region string Target region for region-specific requests
--verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info")
```
### SEE ALSO
* [stackit server security-group](./stackit_server_security-group.md) - Allows attaching/detaching security groups to servers
## stackit server security-group detach
Detaches a security group from a server
### Synopsis
Detaches a security group from a server.
```
stackit server security-group detach [flags]
```
### Examples
```
Detach a security group with ID "xxx" from a server with ID "yyy"
$ stackit server security-group detach --server-id yyy --security-group-id xxx
```
### Options
```
-h, --help Help for "stackit server security-group detach"
--security-group-id string Security Group ID
--server-id string Server ID
```
### Options inherited from parent commands
```
-y, --assume-yes If set, skips all confirmation prompts
--async If set, runs the command asynchronously
-o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"]
-p, --project-id string Project ID
--region string Target region for region-specific requests
--verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info")
```
### SEE ALSO
* [stackit server security-group](./stackit_server_security-group.md) - Allows attaching/detaching security groups to servers
## stackit server security-group
Allows attaching/detaching security groups to servers
### Synopsis
Allows attaching/detaching security groups to servers.
```
stackit server security-group [flags]
```
### Options
```
-h, --help Help for "stackit server security-group"
```
### Options inherited from parent commands
```
-y, --assume-yes If set, skips all confirmation prompts
--async If set, runs the command asynchronously
-o, --output-format string Output format, one of ["json" "pretty" "none" "yaml"]
-p, --project-id string Project ID
--region string Target region for region-specific requests
--verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info")
```
### SEE ALSO
* [stackit server](./stackit_server.md) - Provides functionality for servers
* [stackit server security-group attach](./stackit_server_security-group_attach.md) - Attaches a security group to a server
* [stackit server security-group detach](./stackit_server_security-group_detach.md) - Detaches a security group from a server
package create
import (
"context"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/google/uuid"
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
"github.com/stackitcloud/stackit-cli/internal/pkg/testutils"
"github.com/stackitcloud/stackit-cli/internal/pkg/types"
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
"github.com/stackitcloud/stackit-sdk-go/services/iaas"
)
type testCtxKey struct{}
var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo")
var testClient = &iaas.APIClient{}
const testRegion = "eu01"
var testOrgId = uuid.NewString()
var testNetworkAreaId = uuid.NewString()
var testRoutingTableId = uuid.NewString()
const testDestinationTypeFlag = destTypeCIDRv4
const testDestinationValueFlag = "1.1.1.0/24"
const testNextHopTypeFlag = nextHopTypeIPv4
const testNextHopValueFlag = "1.1.1.1"
const testLabelSelectorFlag = "key1=value1,key2=value2"
var testLabels = &map[string]string{
"key1": "value1",
"key2": "value2",
}
func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string {
flagValues := map[string]string{
globalflags.RegionFlag: testRegion,
labelFlag: testLabelSelectorFlag,
organizationIdFlag: testOrgId,
networkAreaIdFlag: testNetworkAreaId,
routingTableIdFlag: testRoutingTableId,
destinationTypeFlag: testDestinationTypeFlag,
destinationValueFlag: testDestinationValueFlag,
nextHopTypeFlag: testNextHopTypeFlag,
nextHopValueFlag: testNextHopValueFlag,
}
for _, mod := range mods {
mod(flagValues)
}
return flagValues
}
func fixtureInputModel(mods ...func(model *inputModel)) *inputModel {
model := &inputModel{
GlobalFlagModel: &globalflags.GlobalFlagModel{
Verbosity: globalflags.VerbosityDefault,
Region: testRegion,
},
OrganizationId: testOrgId,
NetworkAreaId: testNetworkAreaId,
RoutingTableId: testRoutingTableId,
DestinationType: testDestinationTypeFlag,
DestinationValue: utils.Ptr(testDestinationValueFlag),
NextHopType: testNextHopTypeFlag,
NextHopValue: utils.Ptr(testNextHopValueFlag),
Labels: testLabels,
}
for _, mod := range mods {
mod(model)
}
return model
}
func fixtureRequest(mods ...func(request *iaas.ApiAddRoutesToRoutingTableRequest)) iaas.ApiAddRoutesToRoutingTableRequest {
request := testClient.AddRoutesToRoutingTable(testCtx, testOrgId, testNetworkAreaId, testRegion, testRoutingTableId)
request = request.AddRoutesToRoutingTablePayload(fixturePayload())
for _, mod := range mods {
mod(&request)
}
return request
}
func fixturePayload(mods ...func(payload *iaas.AddRoutesToRoutingTablePayload)) iaas.AddRoutesToRoutingTablePayload {
payload := iaas.AddRoutesToRoutingTablePayload{
Items: &[]iaas.Route{
{
Destination: &iaas.RouteDestination{
DestinationCIDRv4: &iaas.DestinationCIDRv4{
Type: utils.Ptr(testDestinationTypeFlag),
Value: utils.Ptr(testDestinationValueFlag),
},
},
Nexthop: &iaas.RouteNexthop{
NexthopIPv4: &iaas.NexthopIPv4{
Type: utils.Ptr(testNextHopTypeFlag),
Value: utils.Ptr(testNextHopValueFlag),
},
},
Labels: utils.ConvertStringMapToInterfaceMap(testLabels),
},
},
}
for _, mod := range mods {
mod(&payload)
}
return payload
}
func TestParseInput(t *testing.T) {
tests := []struct {
description string
argValues []string
flagValues map[string]string
isValid bool
expectedModel *inputModel
}{
{
description: "valid input",
flagValues: fixtureFlagValues(),
isValid: true,
expectedModel: fixtureInputModel(),
},
{
description: "routing-table ID missing",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, routingTableIdFlag)
}),
isValid: false,
},
{
description: "destination value missing",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, destinationValueFlag)
}),
isValid: false,
},
{
description: "destination type missing",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, destinationTypeFlag)
}),
isValid: false,
},
{
description: "next hop type missing",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, nextHopTypeFlag)
}),
isValid: false,
},
{
description: "next hop value missing",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, nextHopValueFlag)
}),
isValid: false,
},
{
description: "no values",
flagValues: map[string]string{},
isValid: false,
},
{
description: "organization ID missing",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, organizationIdFlag)
}),
isValid: false,
},
{
description: "organization ID invalid - empty",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[organizationIdFlag] = ""
}),
isValid: false,
},
{
description: "organization ID invalid - format",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[organizationIdFlag] = "invalid-uuid"
}),
isValid: false,
},
{
description: "network area ID missing",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, networkAreaIdFlag)
}),
isValid: false,
},
{
description: "network area ID invalid - empty",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[networkAreaIdFlag] = ""
}),
isValid: false,
},
{
description: "network area ID invalid - format",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[networkAreaIdFlag] = "invalid-uuid"
}),
isValid: false,
},
{
description: "invalid destination type enum",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[destinationTypeFlag] = nextHopTypeIPv4 // Deliberately invalid for dest
}),
isValid: false,
},
{
description: "destination value not IPv4 CIDR",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[destinationValueFlag] = "0.0.0.0"
}),
isValid: false,
},
{
description: "destination value not IPv6 CIDR",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[destinationTypeFlag] = destTypeCIDRv6
flagValues[destinationValueFlag] = "2001:db8::"
}),
isValid: false,
},
{
description: "destination value is IPv6 CIDR",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[destinationTypeFlag] = destTypeCIDRv6
flagValues[destinationValueFlag] = "2001:db8::/32"
}),
isValid: true,
expectedModel: fixtureInputModel(func(model *inputModel) {
model.DestinationType = destTypeCIDRv6
model.DestinationValue = utils.Ptr("2001:db8::/32")
}),
},
{
description: "invalid next hop type enum",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[nextHopTypeFlag] = destTypeCIDRv4 // Deliberately invalid for hop
}),
isValid: false,
},
{
description: "next hop type is internet and next hop value is provided",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[nextHopTypeFlag] = nextHopTypeInternet
flagValues[nextHopValueFlag] = "1.1.1.1" // should not be allowed
}),
isValid: false,
},
{
description: "next hop type is blackhole and next hop value is provided",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[nextHopTypeFlag] = nextHopTypeBlackhole
flagValues[nextHopValueFlag] = "1.1.1.1"
}),
isValid: false,
},
{
description: "next hop type is internet and next hop value is not provided",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[nextHopTypeFlag] = nextHopTypeInternet
delete(flagValues, nextHopValueFlag)
}),
expectedModel: fixtureInputModel(func(model *inputModel) {
model.NextHopType = nextHopTypeInternet
model.NextHopValue = nil
}),
isValid: true,
},
{
description: "next hop type is blackhole and next hop value is not provided",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[nextHopTypeFlag] = nextHopTypeBlackhole
delete(flagValues, nextHopValueFlag)
}),
expectedModel: fixtureInputModel(func(model *inputModel) {
model.NextHopType = nextHopTypeBlackhole
model.NextHopValue = nil
}),
isValid: true,
},
{
description: "next hop type is IPv4 and next hop value is missing",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[nextHopTypeFlag] = nextHopTypeIPv4
delete(flagValues, nextHopValueFlag)
}),
isValid: false,
},
{
description: "next hop type is IPv6 and next hop value is missing",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[nextHopTypeFlag] = nextHopTypeIPv6
delete(flagValues, nextHopValueFlag)
}),
isValid: false,
},
{
description: "invalid next hop type provided",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[nextHopTypeFlag] = "invalid-type"
}),
isValid: false,
},
{
description: "optional labels are provided",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[labelFlag] = "key=value"
}),
expectedModel: fixtureInputModel(func(model *inputModel) {
model.Labels = utils.Ptr(map[string]string{"key": "value"})
}),
isValid: true,
},
{
description: "argument value is empty string",
argValues: []string{""},
flagValues: fixtureFlagValues(),
isValid: false,
expectedModel: fixtureInputModel(),
},
{
description: "argument value wrong",
argValues: []string{"foo-bar"},
flagValues: fixtureFlagValues(),
isValid: false,
expectedModel: fixtureInputModel(),
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
testutils.TestParseInput(t, NewCmd, parseInput, tt.expectedModel, tt.argValues, tt.flagValues, tt.isValid)
})
}
}
func TestBuildNextHop(t *testing.T) {
tests := []struct {
description string
model *inputModel
expected *iaas.RouteNexthop
}{
{
description: "IPv4 next hop",
model: fixtureInputModel(func(m *inputModel) {
m.NextHopType = nextHopTypeIPv4
m.NextHopValue = utils.Ptr("1.1.1.1")
}),
expected: &iaas.RouteNexthop{
NexthopIPv4: &iaas.NexthopIPv4{
Type: utils.Ptr(nextHopTypeIPv4),
Value: utils.Ptr("1.1.1.1"),
},
},
},
{
description: "IPv6 next hop",
model: fixtureInputModel(func(m *inputModel) {
m.NextHopType = nextHopTypeIPv6
m.NextHopValue = utils.Ptr("::1")
}),
expected: &iaas.RouteNexthop{
NexthopIPv6: &iaas.NexthopIPv6{
Type: utils.Ptr(nextHopTypeIPv6),
Value: utils.Ptr("::1"),
},
},
},
{
description: "Internet next hop",
model: fixtureInputModel(func(m *inputModel) {
m.NextHopType = nextHopTypeInternet
m.NextHopValue = nil
}),
expected: &iaas.RouteNexthop{
NexthopInternet: &iaas.NexthopInternet{
Type: utils.Ptr(nextHopTypeInternet),
},
},
},
{
description: "Blackhole next hop",
model: fixtureInputModel(func(m *inputModel) {
m.NextHopType = nextHopTypeBlackhole
m.NextHopValue = nil
}),
expected: &iaas.RouteNexthop{
NexthopBlackhole: &iaas.NexthopBlackhole{
Type: utils.Ptr(nextHopTypeBlackhole),
},
},
},
{
description: "Unsupported next hop type",
model: fixtureInputModel(func(m *inputModel) {
m.NextHopType = "unsupported"
}),
expected: nil,
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
got := buildNextHop(tt.model)
if diff := cmp.Diff(tt.expected, got); diff != "" {
t.Errorf("buildNextHop() mismatch (-want +got):\n%s", diff)
}
})
}
}
func TestBuildDestination(t *testing.T) {
tests := []struct {
description string
model *inputModel
expected *iaas.RouteDestination
}{
{
description: "CIDRv4 destination",
model: fixtureInputModel(func(m *inputModel) {
m.DestinationType = destTypeCIDRv4
m.DestinationValue = utils.Ptr("192.168.1.0/24")
}),
expected: &iaas.RouteDestination{
DestinationCIDRv4: &iaas.DestinationCIDRv4{
Type: utils.Ptr(destTypeCIDRv4),
Value: utils.Ptr("192.168.1.0/24"),
},
},
},
{
description: "CIDRv6 destination",
model: fixtureInputModel(func(m *inputModel) {
m.DestinationType = destTypeCIDRv6
m.DestinationValue = utils.Ptr("2001:db8::/32")
}),
expected: &iaas.RouteDestination{
DestinationCIDRv6: &iaas.DestinationCIDRv6{
Type: utils.Ptr(destTypeCIDRv6),
Value: utils.Ptr("2001:db8::/32"),
},
},
},
{
description: "unsupported destination type",
model: fixtureInputModel(func(m *inputModel) {
m.DestinationType = "other"
m.DestinationValue = utils.Ptr("1.1.1.1")
}),
expected: nil,
},
{
description: "nil destination value",
model: fixtureInputModel(func(m *inputModel) {
m.DestinationValue = nil
}),
expected: nil,
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
got := buildDestination(tt.model)
if diff := cmp.Diff(tt.expected, got); diff != "" {
t.Errorf("buildDestination() mismatch (-want +got):\n%s", diff)
}
})
}
}
func TestBuildRequest(t *testing.T) {
tests := []struct {
description string
model *inputModel
expectedRequest iaas.ApiAddRoutesToRoutingTableRequest
}{
{
description: "base",
model: fixtureInputModel(),
expectedRequest: fixtureRequest(),
},
{
description: "optional labels provided",
model: fixtureInputModel(func(model *inputModel) {
model.Labels = utils.Ptr(map[string]string{"key": "value"})
}),
expectedRequest: fixtureRequest(func(request *iaas.ApiAddRoutesToRoutingTableRequest) {
*request = (*request).AddRoutesToRoutingTablePayload(fixturePayload(func(payload *iaas.AddRoutesToRoutingTablePayload) {
(*payload.Items)[0].Labels = utils.ConvertStringMapToInterfaceMap(utils.Ptr(map[string]string{"key": "value"}))
}))
}),
},
{
description: "destination is cidrv6 and nexthop is ipv6",
model: fixtureInputModel(func(model *inputModel) {
model.DestinationType = destTypeCIDRv6
model.DestinationValue = utils.Ptr("2001:db8::/32")
model.NextHopType = nextHopTypeIPv6
model.NextHopValue = utils.Ptr("2001:db8::1")
}),
expectedRequest: fixtureRequest(func(request *iaas.ApiAddRoutesToRoutingTableRequest) {
*request = (*request).AddRoutesToRoutingTablePayload(iaas.AddRoutesToRoutingTablePayload{
Items: &[]iaas.Route{
{
Destination: &iaas.RouteDestination{
DestinationCIDRv6: &iaas.DestinationCIDRv6{
Type: utils.Ptr(destTypeCIDRv6),
Value: utils.Ptr("2001:db8::/32"),
},
},
Nexthop: &iaas.RouteNexthop{
NexthopIPv6: &iaas.NexthopIPv6{
Type: utils.Ptr(nextHopTypeIPv6),
Value: utils.Ptr("2001:db8::1"),
},
},
Labels: utils.ConvertStringMapToInterfaceMap(testLabels),
},
},
})
}),
},
{
description: "nexthop type is internet (no value)",
model: fixtureInputModel(func(model *inputModel) {
model.NextHopType = nextHopTypeInternet
model.NextHopValue = nil
}),
expectedRequest: fixtureRequest(func(request *iaas.ApiAddRoutesToRoutingTableRequest) {
payload := fixturePayload(func(payload *iaas.AddRoutesToRoutingTablePayload) {
(*payload.Items)[0].Nexthop = &iaas.RouteNexthop{
NexthopInternet: &iaas.NexthopInternet{
Type: utils.Ptr(nextHopTypeInternet),
},
}
})
*request = (*request).AddRoutesToRoutingTablePayload(payload)
}),
},
{
description: "nexthop type is blackhole (no value)",
model: fixtureInputModel(func(model *inputModel) {
model.NextHopType = nextHopTypeBlackhole
model.NextHopValue = nil
}),
expectedRequest: fixtureRequest(func(request *iaas.ApiAddRoutesToRoutingTableRequest) {
payload := fixturePayload(func(payload *iaas.AddRoutesToRoutingTablePayload) {
(*payload.Items)[0].Nexthop = &iaas.RouteNexthop{
NexthopBlackhole: &iaas.NexthopBlackhole{
Type: utils.Ptr(nextHopTypeBlackhole),
},
}
})
*request = (*request).AddRoutesToRoutingTablePayload(payload)
}),
},
{
description: "nexthop type is ipv4 with value",
model: fixtureInputModel(func(model *inputModel) {
model.NextHopType = nextHopTypeIPv4
model.NextHopValue = utils.Ptr("1.2.3.4")
}),
expectedRequest: fixtureRequest(func(request *iaas.ApiAddRoutesToRoutingTableRequest) {
payload := fixturePayload(func(payload *iaas.AddRoutesToRoutingTablePayload) {
(*payload.Items)[0].Nexthop = &iaas.RouteNexthop{
NexthopIPv4: &iaas.NexthopIPv4{
Type: utils.Ptr(nextHopTypeIPv4),
Value: utils.Ptr("1.2.3.4"),
},
}
})
*request = (*request).AddRoutesToRoutingTablePayload(payload)
}),
},
{
description: "nexthop type is ipv6 with value",
model: fixtureInputModel(func(model *inputModel) {
model.NextHopType = nextHopTypeIPv6
model.NextHopValue = utils.Ptr("2001:db8::1")
}),
expectedRequest: fixtureRequest(func(request *iaas.ApiAddRoutesToRoutingTableRequest) {
payload := fixturePayload(func(payload *iaas.AddRoutesToRoutingTablePayload) {
(*payload.Items)[0].Nexthop = &iaas.RouteNexthop{
NexthopIPv6: &iaas.NexthopIPv6{
Type: utils.Ptr(nextHopTypeIPv6),
Value: utils.Ptr("2001:db8::1"),
},
}
})
*request = (*request).AddRoutesToRoutingTablePayload(payload)
}),
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
request, err := buildRequest(testCtx, tt.model, testClient)
if err != nil {
t.Fatalf("buildRequest returned error: %v", err)
}
if diff := cmp.Diff(request, tt.expectedRequest,
cmp.AllowUnexported(tt.expectedRequest),
cmpopts.EquateComparable(testCtx)); diff != "" {
t.Errorf("buildRequest() mismatch (-got +want):\n%s", diff)
}
})
}
}
func TestOutputResult(t *testing.T) {
dummyRoute := iaas.Route{
Id: utils.Ptr("route-foo"),
Destination: &iaas.RouteDestination{
DestinationCIDRv4: &iaas.DestinationCIDRv4{
Type: utils.Ptr(destTypeCIDRv4),
Value: utils.Ptr("10.0.0.0/24"),
},
},
Nexthop: &iaas.RouteNexthop{
NexthopIPv4: &iaas.NexthopIPv4{
Type: utils.Ptr(nextHopTypeIPv4),
Value: utils.Ptr("10.0.0.1"),
},
},
Labels: utils.ConvertStringMapToInterfaceMap(testLabels),
CreatedAt: utils.Ptr(time.Now()),
UpdatedAt: utils.Ptr(time.Now()),
}
tests := []struct {
name string
outputFormat string
routes []iaas.Route
wantErr bool
}{
{
name: "nil routes should return error",
outputFormat: print.PrettyOutputFormat,
routes: nil,
wantErr: true,
},
{
name: "empty routes list",
outputFormat: print.PrettyOutputFormat,
routes: []iaas.Route{},
wantErr: true,
},
{
name: "route list with empty struct",
outputFormat: print.PrettyOutputFormat,
routes: []iaas.Route{{}},
wantErr: false,
},
{
name: "pretty output with one route",
outputFormat: print.PrettyOutputFormat,
routes: []iaas.Route{dummyRoute},
wantErr: false,
},
{
name: "pretty output with multiple routes",
outputFormat: print.PrettyOutputFormat,
routes: []iaas.Route{dummyRoute, dummyRoute, dummyRoute},
wantErr: false,
},
{
name: "json output with one route",
outputFormat: print.JSONOutputFormat,
routes: []iaas.Route{dummyRoute},
wantErr: false,
},
{
name: "yaml output with one route",
outputFormat: print.YAMLOutputFormat,
routes: []iaas.Route{dummyRoute},
wantErr: false,
},
}
p := print.NewPrinter()
p.Cmd = NewCmd(&types.CmdParams{Printer: p})
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := outputResult(p, tt.outputFormat, tt.routes); (err != nil) != tt.wantErr {
t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
package create
import (
"context"
"errors"
"fmt"
"strings"
"github.com/spf13/cobra"
"github.com/stackitcloud/stackit-cli/internal/pkg/args"
"github.com/stackitcloud/stackit-cli/internal/pkg/examples"
"github.com/stackitcloud/stackit-cli/internal/pkg/flags"
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
"github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client"
iaasUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/utils"
"github.com/stackitcloud/stackit-cli/internal/pkg/types"
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
"github.com/stackitcloud/stackit-sdk-go/services/iaas"
)
const (
destinationTypeFlag = "destination-type"
destinationValueFlag = "destination-value"
labelFlag = "labels"
networkAreaIdFlag = "network-area-id"
nextHopTypeFlag = "nexthop-type"
nextHopValueFlag = "nexthop-value"
organizationIdFlag = "organization-id"
routingTableIdFlag = "routing-table-id"
// Destination Type Constants
destTypeCIDRv4 = "cidrv4"
destTypeCIDRv6 = "cidrv6"
// NextHop Type Constants
nextHopTypeIPv4 = "ipv4"
nextHopTypeIPv6 = "ipv6"
nextHopTypeInternet = "internet"
nextHopTypeBlackhole = "blackhole"
)
type inputModel struct {
*globalflags.GlobalFlagModel
DestinationType string
DestinationValue *string
Labels *map[string]string
NetworkAreaId string
NextHopType string
NextHopValue *string
OrganizationId string
RoutingTableId string
}
func NewCmd(params *types.CmdParams) *cobra.Command {
cmd := &cobra.Command{
Use: "create",
Short: "Creates a route in a routing-table",
Long: "Creates a route in a routing-table.",
Args: args.NoArgs,
Example: examples.Build(
examples.NewExample("Create a route with CIDRv4 destination and IPv4 nexthop",
`$ stackit network-area routing-table route create --routing-table-id xxx --organization-id yyy --network-area-id zzz --destination-type cidrv4 --destination-value <ipv4-cidr> --nexthop-type ipv4 --nexthop-value <ipv4-address>`),
examples.NewExample("Create a route with CIDRv6 destination and IPv6 nexthop",
`$ stackit network-area routing-table route create --routing-table-id xxx --organization-id yyy --network-area-id zzz --destination-type cidrv6 --destination-value <ipv6-cidr> --nexthop-type ipv6 --nexthop-value <ipv6-address>`),
examples.NewExample("Create a route with CIDRv6 destination and Nexthop Internet",
`$ stackit network-area routing-table route create --routing-table-id xxx --organization-id yyy --network-area-id zzz --destination-type cidrv6 --destination-value <ipv6-cidr> --nexthop-type internet`),
),
RunE: func(cmd *cobra.Command, _ []string) error {
ctx := context.Background()
model, err := parseInput(params.Printer, cmd, nil)
if err != nil {
return err
}
apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion)
if err != nil {
return err
}
routingTableLabel, err := iaasUtils.GetRoutingTableOfAreaName(ctx, apiClient, model.OrganizationId, model.NetworkAreaId, model.Region, model.RoutingTableId)
if err != nil {
params.Printer.Debug(print.ErrorLevel, "get routing-table name: %v", err)
routingTableLabel = model.RoutingTableId
} else if routingTableLabel == "" {
routingTableLabel = model.RoutingTableId
}
prompt := fmt.Sprintf("Are you sure you want to create a route for routing-table %q?", routingTableLabel)
err = params.Printer.PromptForConfirmation(prompt)
if err != nil {
return err
}
req, err := buildRequest(ctx, model, apiClient)
if err != nil {
return err
}
resp, err := req.Execute()
if err != nil {
return fmt.Errorf("create route request failed: %w", err)
}
return outputResult(params.Printer, model.OutputFormat, resp.GetItems())
},
}
configureFlags(cmd)
return cmd
}
func configureFlags(cmd *cobra.Command) {
cmd.Flags().Var(flags.CIDRFlag(), destinationValueFlag, "Destination value")
cmd.Flags().String(nextHopValueFlag, "", "NextHop value")
cmd.Flags().Var(flags.UUIDFlag(), networkAreaIdFlag, "Network-Area ID")
cmd.Flags().Var(flags.UUIDFlag(), organizationIdFlag, "Organization ID")
cmd.Flags().Var(flags.UUIDFlag(), routingTableIdFlag, "Routing-Table ID")
cmd.Flags().Var(
flags.EnumFlag(true, "", destTypeCIDRv4, destTypeCIDRv6),
destinationTypeFlag,
"Destination type")
cmd.Flags().Var(
flags.EnumFlag(true, "", nextHopTypeIPv4, nextHopTypeIPv6, nextHopTypeInternet, nextHopTypeBlackhole),
nextHopTypeFlag,
"Next hop type")
cmd.Flags().StringToString(labelFlag, nil, "Key=value labels")
err := flags.MarkFlagsRequired(cmd, organizationIdFlag, networkAreaIdFlag, routingTableIdFlag, destinationTypeFlag, destinationValueFlag, nextHopTypeFlag)
cobra.CheckErr(err)
}
func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, error) {
globalFlags := globalflags.Parse(p, cmd)
model := &inputModel{
GlobalFlagModel: globalFlags,
DestinationType: flags.FlagToStringValue(p, cmd, destinationTypeFlag),
DestinationValue: flags.FlagToStringPointer(p, cmd, destinationValueFlag),
Labels: flags.FlagToStringToStringPointer(p, cmd, labelFlag),
NetworkAreaId: flags.FlagToStringValue(p, cmd, networkAreaIdFlag),
NextHopType: flags.FlagToStringValue(p, cmd, nextHopTypeFlag),
NextHopValue: flags.FlagToStringPointer(p, cmd, nextHopValueFlag),
OrganizationId: flags.FlagToStringValue(p, cmd, organizationIdFlag),
RoutingTableId: flags.FlagToStringValue(p, cmd, routingTableIdFlag),
}
// Next Hop validation logic
switch strings.ToLower(model.NextHopType) {
case nextHopTypeInternet, nextHopTypeBlackhole:
if model.NextHopValue != nil && *model.NextHopValue != "" {
return nil, errors.New("--nexthop-value is not allowed when --nexthop-type is 'internet' or 'blackhole'")
}
case nextHopTypeIPv4, nextHopTypeIPv6:
if model.NextHopValue == nil || *model.NextHopValue == "" {
return nil, errors.New("--nexthop-value is required when --nexthop-type is 'ipv4' or 'ipv6'")
}
default:
return nil, fmt.Errorf("invalid nexthop-type: %q", model.NextHopType)
}
p.DebugInputModel(model)
return model, nil
}
func buildRequest(ctx context.Context, model *inputModel, apiClient *iaas.APIClient) (iaas.ApiAddRoutesToRoutingTableRequest, error) {
destination := buildDestination(model)
nextHop := buildNextHop(model)
if destination != nil && nextHop != nil {
payload := iaas.AddRoutesToRoutingTablePayload{
Items: &[]iaas.Route{
{
Destination: destination,
Nexthop: nextHop,
Labels: utils.ConvertStringMapToInterfaceMap(model.Labels),
},
},
}
return apiClient.AddRoutesToRoutingTable(
ctx,
model.OrganizationId,
model.NetworkAreaId,
model.Region,
model.RoutingTableId,
).AddRoutesToRoutingTablePayload(payload), nil
}
return nil, fmt.Errorf("invalid input")
}
func buildDestination(model *inputModel) *iaas.RouteDestination {
if model.DestinationValue == nil {
return nil
}
destinationType := strings.ToLower(model.DestinationType)
switch destinationType {
case destTypeCIDRv4:
return &iaas.RouteDestination{
DestinationCIDRv4: &iaas.DestinationCIDRv4{
Type: &model.DestinationType,
Value: model.DestinationValue,
},
}
case destTypeCIDRv6:
return &iaas.RouteDestination{
DestinationCIDRv6: &iaas.DestinationCIDRv6{
Type: &model.DestinationType,
Value: model.DestinationValue,
},
}
default:
return nil
}
}
func buildNextHop(model *inputModel) *iaas.RouteNexthop {
nextHopType := strings.ToLower(model.NextHopType)
switch nextHopType {
case nextHopTypeIPv4:
return &iaas.RouteNexthop{
NexthopIPv4: &iaas.NexthopIPv4{
Type: &model.NextHopType,
Value: model.NextHopValue,
},
}
case nextHopTypeIPv6:
return &iaas.RouteNexthop{
NexthopIPv6: &iaas.NexthopIPv6{
Type: &model.NextHopType,
Value: model.NextHopValue,
},
}
case nextHopTypeInternet:
return &iaas.RouteNexthop{
NexthopInternet: &iaas.NexthopInternet{
Type: &model.NextHopType,
},
}
case nextHopTypeBlackhole:
return &iaas.RouteNexthop{
NexthopBlackhole: &iaas.NexthopBlackhole{
Type: &model.NextHopType,
},
}
default:
return nil
}
}
func outputResult(p *print.Printer, outputFormat string, routes []iaas.Route) error {
if len(routes) == 0 {
return fmt.Errorf("create routes response is empty")
}
return p.OutputResult(outputFormat, routes, func() error {
for _, route := range routes {
p.Outputf("Created route with ID %q\n", utils.PtrString(route.Id))
}
return nil
})
}
package delete
import (
"testing"
"github.com/google/uuid"
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
"github.com/stackitcloud/stackit-cli/internal/pkg/testutils"
)
var (
testOrgId = uuid.NewString()
testNetworkAreaId = uuid.NewString()
testRoutingTableId = uuid.NewString()
testRouteId = uuid.NewString()
)
func fixtureFlagValues(mods ...func(map[string]string)) map[string]string {
flagValues := map[string]string{
organizationIdFlag: testOrgId,
networkAreaIdFlag: testNetworkAreaId,
routingTableIdFlag: testRoutingTableId,
}
for _, mod := range mods {
mod(flagValues)
}
return flagValues
}
func fixtureInputModel(mods ...func(*inputModel)) *inputModel {
model := &inputModel{
GlobalFlagModel: &globalflags.GlobalFlagModel{
Verbosity: globalflags.InfoVerbosity,
},
OrganizationId: testOrgId,
NetworkAreaId: testNetworkAreaId,
RoutingTableId: testRoutingTableId,
RouteID: testRouteId,
}
for _, mod := range mods {
mod(model)
}
return model
}
func TestParseInput(t *testing.T) {
tests := []struct {
description string
argValues []string
flagValues map[string]string
isValid bool
expectedModel *inputModel
}{
{
description: "valid input",
argValues: []string{testRouteId},
flagValues: fixtureFlagValues(),
isValid: true,
expectedModel: fixtureInputModel(func(m *inputModel) {
m.RouteID = testRouteId
}),
},
{
description: "missing route id arg",
argValues: []string{},
flagValues: fixtureFlagValues(),
isValid: false,
},
{
description: "missing organization-id flag",
argValues: []string{testRouteId},
flagValues: fixtureFlagValues(func(m map[string]string) {
delete(m, "organization-id")
}),
isValid: false,
},
{
description: "missing network-area-id flag",
argValues: []string{testRouteId},
flagValues: fixtureFlagValues(func(m map[string]string) {
delete(m, "network-area-id")
}),
isValid: false,
},
{
description: "missing routing-table-id flag",
argValues: []string{testRouteId},
flagValues: fixtureFlagValues(func(m map[string]string) {
delete(m, "routing-table-id")
}),
isValid: false,
},
{
description: "arg value missing",
argValues: []string{""},
flagValues: fixtureFlagValues(),
isValid: false,
expectedModel: fixtureInputModel(),
},
{
description: "arg value wrong",
argValues: []string{"foo-bar"},
flagValues: fixtureFlagValues(),
isValid: false,
expectedModel: fixtureInputModel(),
},
{
description: "invalid organization-id flag",
argValues: []string{testRouteId},
flagValues: map[string]string{"organization-id": "invalid-org"},
isValid: false,
},
{
description: "invalid network-area-id flag",
argValues: []string{testRouteId},
flagValues: map[string]string{"network-area-id": "invalid-area"},
isValid: false,
},
{
description: "invalid routing-table-id flag",
argValues: []string{testRouteId},
flagValues: map[string]string{"routing-table-id": "invalid-table"},
isValid: false,
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
testutils.TestParseInput(t, NewCmd, parseInput, tt.expectedModel, tt.argValues, tt.flagValues, tt.isValid)
})
}
}
package delete
import (
"context"
"fmt"
"github.com/spf13/cobra"
"github.com/stackitcloud/stackit-cli/internal/pkg/args"
"github.com/stackitcloud/stackit-cli/internal/pkg/examples"
"github.com/stackitcloud/stackit-cli/internal/pkg/flags"
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
"github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client"
iaasUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/utils"
"github.com/stackitcloud/stackit-cli/internal/pkg/types"
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
)
const (
networkAreaIdFlag = "network-area-id"
organizationIdFlag = "organization-id"
routeIdArg = "ROUTE_ID"
routingTableIdFlag = "routing-table-id"
)
type inputModel struct {
*globalflags.GlobalFlagModel
NetworkAreaId string
OrganizationId string
RouteID string
RoutingTableId string
}
func NewCmd(params *types.CmdParams) *cobra.Command {
cmd := &cobra.Command{
Use: fmt.Sprintf("delete %s", routingTableIdFlag),
Short: "Deletes a route within a routing-table",
Long: "Deletes a route within a routing-table",
Args: args.SingleArg(routeIdArg, utils.ValidateUUID),
Example: examples.Build(
examples.NewExample(
`Deletes a route within a routing-table`,
`$ stackit network-area routing-table route delete xxx --routing-table-id xxx --organization-id yyy --network-area-id zzz`,
),
),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
model, err := parseInput(params.Printer, cmd, args)
if err != nil {
return err
}
// Configure API client
apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion)
if err != nil {
return err
}
routingTableLabel, err := iaasUtils.GetRoutingTableOfAreaName(ctx, apiClient, model.OrganizationId, model.NetworkAreaId, model.Region, model.RoutingTableId)
if err != nil {
params.Printer.Debug(print.ErrorLevel, "get routing-table name: %v", err)
routingTableLabel = model.RoutingTableId
} else if routingTableLabel == "" {
routingTableLabel = model.RoutingTableId
}
prompt := fmt.Sprintf("Are you sure you want to delete the route %q in routing-table %q for network area id %q?", model.RouteID, routingTableLabel, model.NetworkAreaId)
err = params.Printer.PromptForConfirmation(prompt)
if err != nil {
return err
}
// Call API
req := apiClient.DeleteRouteFromRoutingTable(
ctx,
model.OrganizationId,
model.NetworkAreaId,
model.Region,
model.RoutingTableId,
model.RouteID,
)
err = req.Execute()
if err != nil {
return fmt.Errorf("delete route from routing-table: %w", err)
}
params.Printer.Outputf("Route %q from routing-table %q deleted.\n", model.RouteID, model.RoutingTableId)
return nil
},
}
configureFlags(cmd)
return cmd
}
func configureFlags(cmd *cobra.Command) {
cmd.Flags().Var(flags.UUIDFlag(), networkAreaIdFlag, "Network-Area ID")
cmd.Flags().Var(flags.UUIDFlag(), organizationIdFlag, "Organization ID")
cmd.Flags().Var(flags.UUIDFlag(), routingTableIdFlag, "Routing-Table ID")
err := flags.MarkFlagsRequired(cmd, organizationIdFlag, networkAreaIdFlag, routingTableIdFlag)
cobra.CheckErr(err)
}
func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inputModel, error) {
globalFlags := globalflags.Parse(p, cmd)
routeId := inputArgs[0]
model := inputModel{
GlobalFlagModel: globalFlags,
NetworkAreaId: flags.FlagToStringValue(p, cmd, networkAreaIdFlag),
OrganizationId: flags.FlagToStringValue(p, cmd, organizationIdFlag),
RouteID: routeId,
RoutingTableId: flags.FlagToStringValue(p, cmd, routingTableIdFlag),
}
p.DebugInputModel(model)
return &model, nil
}
package describe
import (
"testing"
"time"
"github.com/google/uuid"
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
"github.com/stackitcloud/stackit-cli/internal/pkg/testutils"
"github.com/stackitcloud/stackit-cli/internal/pkg/types"
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
"github.com/stackitcloud/stackit-sdk-go/services/iaas"
)
const testRegion = "eu01"
var testOrgId = uuid.NewString()
var testNetworkAreaId = uuid.NewString()
var testRoutingTableId = uuid.NewString()
var testRouteId = uuid.NewString()
var testLabels = &map[string]string{
"key1": "value1",
"key2": "value2",
}
func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string {
flagValues := map[string]string{
globalflags.RegionFlag: testRegion,
organizationIdFlag: testOrgId,
networkAreaIdFlag: testNetworkAreaId,
routingTableIdFlag: testRoutingTableId,
}
for _, mod := range mods {
mod(flagValues)
}
return flagValues
}
func fixtureInputModel(mods ...func(model *inputModel)) *inputModel {
model := &inputModel{
GlobalFlagModel: &globalflags.GlobalFlagModel{
Verbosity: globalflags.VerbosityDefault,
Region: testRegion,
},
OrganizationId: testOrgId,
NetworkAreaId: testNetworkAreaId,
RoutingTableId: testRoutingTableId,
RouteID: testRouteId,
}
for _, mod := range mods {
mod(model)
}
return model
}
func fixtureArgValues(mods ...func(argValues []string)) []string {
argValues := []string{
testRouteId,
}
for _, mod := range mods {
mod(argValues)
}
return argValues
}
func TestParseInput(t *testing.T) {
tests := []struct {
description string
flagValues map[string]string
argValues []string
isValid bool
expectedModel *inputModel
}{
{
description: "base",
flagValues: fixtureFlagValues(),
argValues: fixtureArgValues(),
isValid: true,
expectedModel: fixtureInputModel(),
},
{
description: "no values",
argValues: []string{},
flagValues: map[string]string{},
isValid: false,
},
{
description: "routing-table-id missing",
argValues: fixtureArgValues(),
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, routingTableIdFlag)
}),
isValid: false,
},
{
description: "network-area-id missing",
argValues: fixtureArgValues(),
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, networkAreaIdFlag)
}),
isValid: false,
},
{
description: "org-id missing",
argValues: fixtureArgValues(),
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, organizationIdFlag)
}),
isValid: false,
},
{
description: "invalid routing-table-id",
argValues: fixtureArgValues(),
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[routingTableIdFlag] = "invalid-id"
}),
isValid: false,
},
{
description: "arg value missing",
argValues: []string{""},
flagValues: fixtureFlagValues(),
isValid: false,
expectedModel: fixtureInputModel(),
},
{
description: "arg value wrong",
argValues: []string{"foo-bar"},
flagValues: fixtureFlagValues(),
isValid: false,
expectedModel: fixtureInputModel(),
},
{
description: "invalid organization-id",
argValues: fixtureArgValues(),
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[organizationIdFlag] = "invalid-org"
}),
isValid: false,
},
{
description: "invalid network-area-id",
argValues: fixtureArgValues(),
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[networkAreaIdFlag] = "invalid-area"
}),
isValid: false,
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
testutils.TestParseInput(t, NewCmd, parseInput, tt.expectedModel, tt.argValues, tt.flagValues, tt.isValid)
})
}
}
func TestOutputResult(t *testing.T) {
dummyRoute := iaas.Route{
Id: utils.Ptr("route-foo"),
Destination: &iaas.RouteDestination{
DestinationCIDRv4: &iaas.DestinationCIDRv4{
Type: utils.Ptr("cidrv4"),
Value: utils.Ptr("10.0.0.0/24"),
},
},
Nexthop: &iaas.RouteNexthop{
NexthopIPv4: &iaas.NexthopIPv4{
Type: utils.Ptr("ipv4"),
Value: utils.Ptr("10.0.0.1"),
},
},
Labels: utils.ConvertStringMapToInterfaceMap(testLabels),
CreatedAt: utils.Ptr(time.Now()),
UpdatedAt: utils.Ptr(time.Now()),
}
tests := []struct {
name string
outputFormat string
route *iaas.Route
wantErr bool
}{
{
name: "nil route should return error",
outputFormat: print.PrettyOutputFormat,
route: nil,
wantErr: true,
},
{
name: "empty route",
outputFormat: print.PrettyOutputFormat,
route: &iaas.Route{},
wantErr: false,
},
{
name: "json empty route",
outputFormat: print.JSONOutputFormat,
route: &iaas.Route{},
wantErr: false,
},
{
name: "pretty output with one route",
outputFormat: print.PrettyOutputFormat,
route: &dummyRoute,
wantErr: false,
},
{
name: "json output with one route",
outputFormat: print.JSONOutputFormat,
route: &dummyRoute,
wantErr: false,
},
{
name: "yaml output with one route",
outputFormat: print.YAMLOutputFormat,
route: &dummyRoute,
wantErr: false,
},
}
p := print.NewPrinter()
p.Cmd = NewCmd(&types.CmdParams{Printer: p})
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := outputResult(p, tt.outputFormat, tt.route); (err != nil) != tt.wantErr {
t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
package describe
import (
"context"
"fmt"
"strings"
"github.com/spf13/cobra"
"github.com/stackitcloud/stackit-cli/internal/pkg/args"
"github.com/stackitcloud/stackit-cli/internal/pkg/examples"
"github.com/stackitcloud/stackit-cli/internal/pkg/flags"
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
"github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client"
routeUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/network-area/routing-table/utils"
"github.com/stackitcloud/stackit-cli/internal/pkg/tables"
"github.com/stackitcloud/stackit-cli/internal/pkg/types"
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
"github.com/stackitcloud/stackit-sdk-go/services/iaas"
)
const (
networkAreaIdFlag = "network-area-id"
organizationIdFlag = "organization-id"
routeIdArg = "ROUTE_ID"
routingTableIdFlag = "routing-table-id"
)
type inputModel struct {
*globalflags.GlobalFlagModel
NetworkAreaId string
OrganizationId string
RouteID string
RoutingTableId string
}
func NewCmd(params *types.CmdParams) *cobra.Command {
cmd := &cobra.Command{
Use: fmt.Sprintf("describe %s", routeIdArg),
Short: "Describes a route within a routing-table",
Long: "Describes a route within a routing-table",
Args: args.SingleArg(routeIdArg, utils.ValidateUUID),
Example: examples.Build(
examples.NewExample(
`Describe a route within a routing-table`,
`$ stackit network-area routing-table route describe xxx --routing-table-id xxx --organization-id yyy --network-area-id zzz`,
),
),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
model, err := parseInput(params.Printer, cmd, args)
if err != nil {
return err
}
// Configure API client
apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion)
if err != nil {
return err
}
// Call API
request := apiClient.GetRouteOfRoutingTable(
ctx,
model.OrganizationId,
model.NetworkAreaId,
model.Region,
model.RoutingTableId,
model.RouteID,
)
response, err := request.Execute()
if err != nil {
return fmt.Errorf("describe route: %w", err)
}
return outputResult(params.Printer, model.OutputFormat, response)
},
}
configureFlags(cmd)
return cmd
}
func configureFlags(cmd *cobra.Command) {
cmd.Flags().Var(flags.UUIDFlag(), networkAreaIdFlag, "Network-Area ID")
cmd.Flags().Var(flags.UUIDFlag(), organizationIdFlag, "Organization ID")
cmd.Flags().Var(flags.UUIDFlag(), routingTableIdFlag, "Routing-Table ID")
err := flags.MarkFlagsRequired(cmd, organizationIdFlag, networkAreaIdFlag, routingTableIdFlag)
cobra.CheckErr(err)
}
func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inputModel, error) {
globalFlags := globalflags.Parse(p, cmd)
routeId := inputArgs[0]
model := inputModel{
GlobalFlagModel: globalFlags,
NetworkAreaId: flags.FlagToStringValue(p, cmd, networkAreaIdFlag),
OrganizationId: flags.FlagToStringValue(p, cmd, organizationIdFlag),
RouteID: routeId,
RoutingTableId: flags.FlagToStringValue(p, cmd, routingTableIdFlag),
}
p.DebugInputModel(model)
return &model, nil
}
func outputResult(p *print.Printer, outputFormat string, route *iaas.Route) error {
if route == nil {
return fmt.Errorf("describe route response is empty")
}
return p.OutputResult(outputFormat, route, func() error {
routeDetails := routeUtils.ExtractRouteDetails(*route)
table := tables.NewTable()
table.AddRow("ID", utils.PtrString(route.Id))
table.AddSeparator()
table.AddRow("DESTINATION TYPE", routeDetails.DestType)
table.AddSeparator()
table.AddRow("DESTINATION VALUE", routeDetails.DestValue)
table.AddSeparator()
table.AddRow("NEXTHOP TYPE", routeDetails.HopType)
table.AddSeparator()
table.AddRow("NEXTHOP VALUE", routeDetails.HopValue)
table.AddSeparator()
if route.Labels != nil && len(*route.Labels) > 0 {
var labels []string
for key, value := range *route.Labels {
labels = append(labels, fmt.Sprintf("%s: %s", key, value))
}
table.AddRow("LABELS", strings.Join(labels, "\n"))
table.AddSeparator()
}
table.AddRow("CREATED AT", routeDetails.CreatedAt)
table.AddSeparator()
table.AddRow("UPDATED AT", routeDetails.UpdatedAt)
table.AddSeparator()
err := table.Display(p)
if err != nil {
return fmt.Errorf("render table: %w", err)
}
return nil
})
}
package list
import (
"strconv"
"testing"
"time"
"github.com/google/uuid"
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
"github.com/stackitcloud/stackit-cli/internal/pkg/testutils"
"github.com/stackitcloud/stackit-cli/internal/pkg/types"
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
"github.com/stackitcloud/stackit-sdk-go/services/iaas"
)
const testRegion = "eu01"
var testOrgId = uuid.NewString()
var testNetworkAreaId = uuid.NewString()
var testRoutingTableId = uuid.NewString()
const testLabelSelectorFlag = "key1=value1,key2=value2"
var testLabels = &map[string]string{
"key1": "value1",
"key2": "value2",
}
var testLimitFlag = int64(10)
func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string {
flagValues := map[string]string{
globalflags.RegionFlag: testRegion,
organizationIdFlag: testOrgId,
networkAreaIdFlag: testNetworkAreaId,
routingTableIdFlag: testRoutingTableId,
labelSelectorFlag: testLabelSelectorFlag,
limitFlag: strconv.Itoa(int(testLimitFlag)),
}
for _, mod := range mods {
mod(flagValues)
}
return flagValues
}
func fixtureInputModel(mods ...func(model *inputModel)) *inputModel {
model := &inputModel{
GlobalFlagModel: &globalflags.GlobalFlagModel{
Verbosity: globalflags.VerbosityDefault,
Region: testRegion,
},
OrganizationId: testOrgId,
NetworkAreaId: testNetworkAreaId,
RoutingTableId: testRoutingTableId,
LabelSelector: utils.Ptr(testLabelSelectorFlag),
Limit: utils.Ptr(testLimitFlag),
}
for _, mod := range mods {
mod(model)
}
return model
}
func TestParseInput(t *testing.T) {
tests := []struct {
description string
flagValues map[string]string
argValues []string
isValid bool
expectedModel *inputModel
}{
{
description: "base",
flagValues: fixtureFlagValues(),
isValid: true,
expectedModel: fixtureInputModel(),
},
{
description: "no values",
flagValues: map[string]string{},
isValid: false,
},
{
description: "routing-table-id missing",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, routingTableIdFlag)
}),
isValid: false,
},
{
description: "network-area-id missing",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, networkAreaIdFlag)
}),
isValid: false,
},
{
description: "org-id missing",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, organizationIdFlag)
}),
isValid: false,
},
{
description: "labels missing",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, labelSelectorFlag)
}),
isValid: true,
expectedModel: fixtureInputModel(func(model *inputModel) {
model.LabelSelector = nil
}),
},
{
description: "limit missing",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, limitFlag)
}),
isValid: true,
expectedModel: fixtureInputModel(func(model *inputModel) {
model.Limit = nil
}),
},
{
description: "invalid limit flag",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[limitFlag] = "invalid"
}),
isValid: false,
},
{
description: "negative limit flag",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[limitFlag] = "-10"
}),
isValid: false,
},
{
description: "limit zero flag",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[limitFlag] = "0"
}),
isValid: false,
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
testutils.TestParseInput(t, NewCmd, parseInput, tt.expectedModel, tt.argValues, tt.flagValues, tt.isValid)
})
}
}
func TestOutputResult(t *testing.T) {
dummyRoute := iaas.Route{
Id: utils.Ptr("route-foo"),
Destination: &iaas.RouteDestination{
DestinationCIDRv4: &iaas.DestinationCIDRv4{
Type: utils.Ptr("cidrv4"),
Value: utils.Ptr("10.0.0.0/24"),
},
},
Nexthop: &iaas.RouteNexthop{
NexthopIPv4: &iaas.NexthopIPv4{
Type: utils.Ptr("ipv4"),
Value: utils.Ptr("10.0.0.1"),
},
},
Labels: utils.ConvertStringMapToInterfaceMap(testLabels),
CreatedAt: utils.Ptr(time.Now()),
UpdatedAt: utils.Ptr(time.Now()),
}
tests := []struct {
name string
outputFormat string
routes []iaas.Route
wantErr bool
}{
{
name: "nil routes should return error",
outputFormat: print.PrettyOutputFormat,
routes: nil,
wantErr: true,
},
{
name: "empty routes list",
outputFormat: print.PrettyOutputFormat,
routes: []iaas.Route{},
wantErr: false,
},
{
name: "empty routes list json output",
outputFormat: print.JSONOutputFormat,
routes: []iaas.Route{},
wantErr: false,
},
{
name: "empty routes list json output",
outputFormat: print.YAMLOutputFormat,
routes: []iaas.Route{},
wantErr: false,
},
{
name: "route list with empty struct",
outputFormat: print.PrettyOutputFormat,
routes: []iaas.Route{{}},
wantErr: false,
},
{
name: "pretty output with one route",
outputFormat: print.PrettyOutputFormat,
routes: []iaas.Route{dummyRoute},
wantErr: false,
},
{
name: "pretty output with multiple routes",
outputFormat: print.PrettyOutputFormat,
routes: []iaas.Route{dummyRoute, dummyRoute, dummyRoute},
wantErr: false,
},
{
name: "json output with one route",
outputFormat: print.JSONOutputFormat,
routes: []iaas.Route{dummyRoute},
wantErr: false,
},
{
name: "yaml output with one route",
outputFormat: print.YAMLOutputFormat,
routes: []iaas.Route{dummyRoute},
wantErr: false,
},
}
p := print.NewPrinter()
p.Cmd = NewCmd(&types.CmdParams{Printer: p})
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := outputResult(p, tt.outputFormat, tt.routes, "dummy-org", "dummy-route-table-id"); (err != nil) != tt.wantErr {
t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
package list
import (
"context"
"fmt"
"github.com/spf13/cobra"
"github.com/stackitcloud/stackit-cli/internal/pkg/args"
"github.com/stackitcloud/stackit-cli/internal/pkg/errors"
"github.com/stackitcloud/stackit-cli/internal/pkg/examples"
"github.com/stackitcloud/stackit-cli/internal/pkg/flags"
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
"github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client"
routeUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/network-area/routing-table/utils"
"github.com/stackitcloud/stackit-cli/internal/pkg/tables"
"github.com/stackitcloud/stackit-cli/internal/pkg/types"
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
"github.com/stackitcloud/stackit-sdk-go/services/iaas"
)
const (
labelSelectorFlag = "label-selector"
limitFlag = "limit"
networkAreaIdFlag = "network-area-id"
organizationIdFlag = "organization-id"
routingTableIdFlag = "routing-table-id"
)
type inputModel struct {
*globalflags.GlobalFlagModel
LabelSelector *string
Limit *int64
NetworkAreaId string
OrganizationId string
RoutingTableId string
}
func NewCmd(params *types.CmdParams) *cobra.Command {
cmd := &cobra.Command{
Use: "list",
Short: "Lists all routes within a routing-table",
Long: "Lists all routes within a routing-table",
Args: args.NoArgs,
Example: examples.Build(
examples.NewExample(
`List all routes within a routing-table`,
`$ stackit network-area routing-table route list --routing-table-id xxx --organization-id yyy --network-area-id zzz`,
),
examples.NewExample(
`List all routes within a routing-table with labels`,
`$ stackit network-area routing-table list --routing-table-id xxx --organization-id yyy --network-area-id zzz --label-selector env=dev,env=rc`,
),
examples.NewExample(
`List all routes within a routing-tables with labels and limit to 10`,
`$ stackit network-area routing-table list --routing-table-id xxx --organization-id yyy --network-area-id zzz --label-selector env=dev,env=rc --limit 10`,
),
),
RunE: func(cmd *cobra.Command, _ []string) error {
ctx := context.Background()
model, err := parseInput(params.Printer, cmd, nil)
if err != nil {
return err
}
// Configure API client
apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion)
if err != nil {
return err
}
// Call API
request := apiClient.ListRoutesOfRoutingTable(
ctx,
model.OrganizationId,
model.NetworkAreaId,
model.Region,
model.RoutingTableId,
)
if model.LabelSelector != nil {
request.LabelSelector(*model.LabelSelector)
}
response, err := request.Execute()
if err != nil {
return fmt.Errorf("list routes: %w", err)
}
routes := utils.GetSliceFromPointer(response.Items)
// Truncate output
if model.Limit != nil && len(routes) > int(*model.Limit) {
routes = routes[:*model.Limit]
}
return outputResult(params.Printer, model.OutputFormat, routes, model.OrganizationId, model.RoutingTableId)
},
}
configureFlags(cmd)
return cmd
}
func configureFlags(cmd *cobra.Command) {
cmd.Flags().Int64(limitFlag, 0, "Maximum number of entries to list")
cmd.Flags().String(labelSelectorFlag, "", "Filter by label")
cmd.Flags().Var(flags.UUIDFlag(), networkAreaIdFlag, "Network-Area ID")
cmd.Flags().Var(flags.UUIDFlag(), organizationIdFlag, "Organization ID")
cmd.Flags().Var(flags.UUIDFlag(), routingTableIdFlag, "Routing-Table ID")
err := flags.MarkFlagsRequired(cmd, organizationIdFlag, networkAreaIdFlag, routingTableIdFlag)
cobra.CheckErr(err)
}
func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, error) {
globalFlags := globalflags.Parse(p, cmd)
limit := flags.FlagToInt64Pointer(p, cmd, limitFlag)
if limit != nil && *limit < 1 {
return nil, &errors.FlagValidationError{
Flag: limitFlag,
Details: "must be greater than 0",
}
}
model := inputModel{
GlobalFlagModel: globalFlags,
LabelSelector: flags.FlagToStringPointer(p, cmd, labelSelectorFlag),
Limit: limit,
NetworkAreaId: flags.FlagToStringValue(p, cmd, networkAreaIdFlag),
OrganizationId: flags.FlagToStringValue(p, cmd, organizationIdFlag),
RoutingTableId: flags.FlagToStringValue(p, cmd, routingTableIdFlag),
}
p.DebugInputModel(model)
return &model, nil
}
func outputResult(p *print.Printer, outputFormat string, routes []iaas.Route, orgId, routeTableId string) error {
if routes == nil {
return fmt.Errorf("list routes routes are nil")
}
return p.OutputResult(outputFormat, routes, func() error {
if len(routes) == 0 {
p.Outputf("No routes found for routing-table %q in organization %q\n", routeTableId, orgId)
return nil
}
table := tables.NewTable()
table.SetHeader("ID", "DESTINATION TYPE", "DESTINATION VALUE", "NEXTHOP TYPE", "NEXTHOP VALUE", "LABELS", "CREATED AT", "UPDATED AT")
for _, route := range routes {
routeDetails := routeUtils.ExtractRouteDetails(route)
table.AddRow(
utils.PtrString(route.Id),
routeDetails.DestType,
routeDetails.DestValue,
routeDetails.HopType,
routeDetails.HopValue,
routeDetails.Labels,
routeDetails.CreatedAt,
routeDetails.UpdatedAt,
)
}
err := table.Display(p)
if err != nil {
return fmt.Errorf("render table: %w", err)
}
return nil
})
}
package route
import (
"github.com/spf13/cobra"
"github.com/stackitcloud/stackit-cli/internal/cmd/network-area/routingtable/route/create"
"github.com/stackitcloud/stackit-cli/internal/cmd/network-area/routingtable/route/delete"
"github.com/stackitcloud/stackit-cli/internal/cmd/network-area/routingtable/route/describe"
"github.com/stackitcloud/stackit-cli/internal/cmd/network-area/routingtable/route/list"
"github.com/stackitcloud/stackit-cli/internal/cmd/network-area/routingtable/route/update"
"github.com/stackitcloud/stackit-cli/internal/pkg/args"
"github.com/stackitcloud/stackit-cli/internal/pkg/types"
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
)
func NewCmd(params *types.CmdParams) *cobra.Command {
cmd := &cobra.Command{
Use: "route",
Short: "Manages routes of a routing-table",
Long: "Manages routes of a routing-table",
Args: args.NoArgs,
Run: utils.CmdHelp,
}
addSubcommands(cmd, params)
return cmd
}
func addSubcommands(cmd *cobra.Command, params *types.CmdParams) {
cmd.AddCommand(describe.NewCmd(params))
cmd.AddCommand(list.NewCmd(params))
cmd.AddCommand(delete.NewCmd(params))
cmd.AddCommand(update.NewCmd(params))
cmd.AddCommand(create.NewCmd(params))
}
package update
import (
"context"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/google/uuid"
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
"github.com/stackitcloud/stackit-cli/internal/pkg/testutils"
"github.com/stackitcloud/stackit-cli/internal/pkg/types"
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
"github.com/stackitcloud/stackit-sdk-go/services/iaas"
)
type testCtxKey struct{}
var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo")
var testClient = &iaas.APIClient{}
const testRegion = "eu01"
var testOrgId = uuid.NewString()
var testNetworkAreaId = uuid.NewString()
var testRoutingTableId = uuid.NewString()
var testRouteId = uuid.NewString()
const testLabelSelectorFlag = "key1=value1,key2=value2"
var testLabels = &map[string]string{
"key1": "value1",
"key2": "value2",
}
func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string {
flagValues := map[string]string{
globalflags.RegionFlag: testRegion,
organizationIdFlag: testOrgId,
networkAreaIdFlag: testNetworkAreaId,
routingTableIdFlag: testRoutingTableId,
labelFlag: testLabelSelectorFlag,
}
for _, mod := range mods {
mod(flagValues)
}
return flagValues
}
func fixtureInputModel(mods ...func(model *inputModel)) *inputModel {
model := &inputModel{
GlobalFlagModel: &globalflags.GlobalFlagModel{
Verbosity: globalflags.VerbosityDefault,
Region: testRegion,
},
OrganizationId: testOrgId,
NetworkAreaId: testNetworkAreaId,
RoutingTableId: testRoutingTableId,
RouteId: testRouteId,
Labels: testLabels,
}
for _, mod := range mods {
mod(model)
}
return model
}
func fixtureArgValues(mods ...func(argValues []string)) []string {
argValues := []string{
testRouteId,
}
for _, mod := range mods {
mod(argValues)
}
return argValues
}
func fixtureRequest(mods ...func(req *iaas.ApiUpdateRouteOfRoutingTableRequest)) iaas.ApiUpdateRouteOfRoutingTableRequest {
req := testClient.UpdateRouteOfRoutingTable(
testCtx,
testOrgId,
testNetworkAreaId,
testRegion,
testRoutingTableId,
testRouteId,
)
payload := iaas.UpdateRouteOfRoutingTablePayload{
Labels: utils.ConvertStringMapToInterfaceMap(testLabels),
}
req = req.UpdateRouteOfRoutingTablePayload(payload)
for _, mod := range mods {
mod(&req)
}
return req
}
func TestParseInput(t *testing.T) {
tests := []struct {
description string
flagValues map[string]string
argValues []string
isValid bool
expectedModel *inputModel
}{
{
description: "base",
flagValues: fixtureFlagValues(),
argValues: fixtureArgValues(),
isValid: true,
expectedModel: fixtureInputModel(),
},
{
description: "no values",
argValues: []string{},
flagValues: map[string]string{},
isValid: false,
},
{
description: "routing-table-id missing",
argValues: fixtureArgValues(),
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, routingTableIdFlag)
}),
isValid: false,
},
{
description: "network-area-id missing",
argValues: fixtureArgValues(),
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, networkAreaIdFlag)
}),
isValid: false,
},
{
description: "org-id missing",
argValues: fixtureArgValues(),
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, organizationIdFlag)
}),
isValid: false,
},
{
description: "routing-table-id missing",
argValues: fixtureArgValues(),
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, routingTableIdFlag)
}),
isValid: false,
},
{
description: "arg value missing",
argValues: []string{""},
flagValues: fixtureFlagValues(),
isValid: false,
expectedModel: fixtureInputModel(),
},
{
description: "arg value wrong",
argValues: []string{"foo-bar"},
flagValues: fixtureFlagValues(),
isValid: false,
expectedModel: fixtureInputModel(),
},
{
description: "labels are missing",
argValues: []string{},
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, labelFlag)
}),
isValid: false,
},
{
description: "invalid label format",
argValues: []string{},
flagValues: map[string]string{labelFlag: "invalid-label"},
isValid: false,
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
testutils.TestParseInput(t, NewCmd, parseInput, tt.expectedModel, tt.argValues, tt.flagValues, tt.isValid)
})
}
}
func TestBuildRequest(t *testing.T) {
tests := []struct {
description string
model *inputModel
expectedRequest iaas.ApiUpdateRouteOfRoutingTableRequest
}{
{
description: "base",
model: fixtureInputModel(),
expectedRequest: fixtureRequest(),
},
{
description: "labels nil",
model: fixtureInputModel(func(m *inputModel) {
m.Labels = nil
}),
expectedRequest: fixtureRequest(func(request *iaas.ApiUpdateRouteOfRoutingTableRequest) {
*request = (*request).UpdateRouteOfRoutingTablePayload(iaas.UpdateRouteOfRoutingTablePayload{
Labels: nil,
})
}),
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
gotReq := buildRequest(testCtx, tt.model, testClient)
if diff := cmp.Diff(
tt.expectedRequest,
gotReq,
cmp.AllowUnexported(tt.expectedRequest),
cmpopts.EquateComparable(testCtx),
); diff != "" {
t.Errorf("buildRequest() mismatch (-want +got):\n%s", diff)
}
})
}
}
func TestOutputResult(t *testing.T) {
dummyRoute := iaas.Route{
Id: utils.Ptr("route-foo"),
Destination: &iaas.RouteDestination{
DestinationCIDRv4: &iaas.DestinationCIDRv4{
Type: utils.Ptr("cidrv4"),
Value: utils.Ptr("10.0.0.0/24"),
},
},
Nexthop: &iaas.RouteNexthop{
NexthopIPv4: &iaas.NexthopIPv4{
Type: utils.Ptr("ipv4"),
Value: utils.Ptr("10.0.0.1"),
},
},
Labels: utils.ConvertStringMapToInterfaceMap(testLabels),
CreatedAt: utils.Ptr(time.Now()),
UpdatedAt: utils.Ptr(time.Now()),
}
tests := []struct {
name string
outputFormat string
route *iaas.Route
wantErr bool
}{
{
name: "nil route should return error",
outputFormat: print.PrettyOutputFormat,
route: nil,
wantErr: true,
},
{
name: "empty route",
outputFormat: print.PrettyOutputFormat,
route: &iaas.Route{},
// should fail on pretty format
wantErr: true,
},
{
name: "pretty output with one route",
outputFormat: print.PrettyOutputFormat,
route: &dummyRoute,
wantErr: false,
},
{
name: "json output with one route",
outputFormat: print.JSONOutputFormat,
route: &dummyRoute,
wantErr: false,
},
{
name: "yaml output with one route",
outputFormat: print.YAMLOutputFormat,
route: &dummyRoute,
wantErr: false,
},
}
p := print.NewPrinter()
p.Cmd = NewCmd(&types.CmdParams{Printer: p})
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := outputResult(p, tt.outputFormat, "", "", tt.route); (err != nil) != tt.wantErr {
t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
package update
import (
"context"
"fmt"
"github.com/spf13/cobra"
"github.com/stackitcloud/stackit-cli/internal/pkg/args"
cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors"
"github.com/stackitcloud/stackit-cli/internal/pkg/examples"
"github.com/stackitcloud/stackit-cli/internal/pkg/flags"
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
"github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client"
iaasUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/utils"
"github.com/stackitcloud/stackit-cli/internal/pkg/types"
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
"github.com/stackitcloud/stackit-sdk-go/services/iaas"
)
const (
labelFlag = "labels"
networkAreaIdFlag = "network-area-id"
organizationIdFlag = "organization-id"
routeIdArg = "ROUTE_ID"
routingTableIdFlag = "routing-table-id"
)
type inputModel struct {
*globalflags.GlobalFlagModel
Labels *map[string]string
NetworkAreaId string
OrganizationId string
RouteId string
RoutingTableId string
}
func NewCmd(params *types.CmdParams) *cobra.Command {
cmd := &cobra.Command{
Use: fmt.Sprintf("update %s", routeIdArg),
Short: "Updates a route in a routing-table",
Long: "Updates a route in a routing-table.",
Args: args.SingleArg(routeIdArg, utils.ValidateUUID),
Example: examples.Build(
examples.NewExample(
`Updates the label(s) of a route with ID "xxx" in a routing-table ID "xxx" in organization with ID "yyy" and network-area with ID "zzz"`,
"$ stackit network-area routing-table route update xxx --labels key=value,foo=bar --routing-table-id xxx --organization-id yyy --network-area-id zzz",
),
),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
model, err := parseInput(params.Printer, cmd, args)
if err != nil {
return err
}
// Configure API client
apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion)
if err != nil {
return err
}
routingTableLabel, err := iaasUtils.GetRoutingTableOfAreaName(ctx, apiClient, model.OrganizationId, model.NetworkAreaId, model.Region, model.RoutingTableId)
if err != nil {
params.Printer.Debug(print.ErrorLevel, "get routing-table name: %v", err)
routingTableLabel = model.RoutingTableId
} else if routingTableLabel == "" {
routingTableLabel = model.RoutingTableId
}
prompt := fmt.Sprintf("Are you sure you want to update route %q for routing-table %q?", model.RouteId, routingTableLabel)
err = params.Printer.PromptForConfirmation(prompt)
if err != nil {
return err
}
req := buildRequest(ctx, model, apiClient)
resp, err := req.Execute()
if err != nil {
return fmt.Errorf("update route %q of routing-table %q : %w", model.RouteId, model.RoutingTableId, err)
}
return outputResult(params.Printer, model.OutputFormat, model.RoutingTableId, model.NetworkAreaId, resp)
},
}
configureFlags(cmd)
return cmd
}
func configureFlags(cmd *cobra.Command) {
cmd.Flags().StringToString(labelFlag, nil, "Labels are key-value string pairs which can be attached to a route. A label can be provided with the format key=value and the flag can be used multiple times to provide a list of labels")
cmd.Flags().Var(flags.UUIDFlag(), networkAreaIdFlag, "Network-Area ID")
cmd.Flags().Var(flags.UUIDFlag(), organizationIdFlag, "Organization ID")
cmd.Flags().Var(flags.UUIDFlag(), routingTableIdFlag, "Routing-Table ID")
err := flags.MarkFlagsRequired(cmd, labelFlag, organizationIdFlag, networkAreaIdFlag, routingTableIdFlag)
cobra.CheckErr(err)
}
func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inputModel, error) {
globalFlags := globalflags.Parse(p, cmd)
routeId := inputArgs[0]
labels := flags.FlagToStringToStringPointer(p, cmd, labelFlag)
if labels == nil {
return nil, &cliErr.EmptyUpdateError{}
}
model := inputModel{
GlobalFlagModel: globalFlags,
Labels: labels,
NetworkAreaId: flags.FlagToStringValue(p, cmd, networkAreaIdFlag),
OrganizationId: flags.FlagToStringValue(p, cmd, organizationIdFlag),
RouteId: routeId,
RoutingTableId: flags.FlagToStringValue(p, cmd, routingTableIdFlag),
}
p.DebugInputModel(model)
return &model, nil
}
func outputResult(p *print.Printer, outputFormat, routingTableId, networkAreaId string, route *iaas.Route) error {
return p.OutputResult(outputFormat, route, func() error {
if route == nil {
return fmt.Errorf("update route response is empty")
}
if route.Id == nil || *route.Id == "" {
return fmt.Errorf("update route response has empty id")
}
p.Outputf("Updated route %q for routing-table %q in network-area %q.\n", *route.Id, routingTableId, networkAreaId)
return nil
})
}
func buildRequest(ctx context.Context, model *inputModel, apiClient *iaas.APIClient) iaas.ApiUpdateRouteOfRoutingTableRequest {
req := apiClient.UpdateRouteOfRoutingTable(
ctx,
model.OrganizationId,
model.NetworkAreaId,
model.Region,
model.RoutingTableId,
model.RouteId,
)
payload := iaas.UpdateRouteOfRoutingTablePayload{
Labels: utils.ConvertStringMapToInterfaceMap(model.Labels),
}
return req.UpdateRouteOfRoutingTablePayload(payload)
}
package attach
import (
"context"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/google/uuid"
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
"github.com/stackitcloud/stackit-cli/internal/pkg/testutils"
"github.com/stackitcloud/stackit-sdk-go/services/iaas"
)
const (
testRegion = "eu01"
)
type testCtxKey struct{}
var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo")
var testClient = &iaas.APIClient{}
var testProjectId = uuid.NewString()
var testServerId = uuid.NewString()
var testSecurityGroupId = uuid.NewString()
func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string {
flagValues := map[string]string{
globalflags.ProjectIdFlag: testProjectId,
globalflags.RegionFlag: testRegion,
serverIdFlag: testServerId,
securityGroupIdFlag: testSecurityGroupId,
}
for _, mod := range mods {
mod(flagValues)
}
return flagValues
}
func fixtureInputModel(mods ...func(model *inputModel)) *inputModel {
model := &inputModel{
GlobalFlagModel: &globalflags.GlobalFlagModel{
Verbosity: globalflags.VerbosityDefault,
ProjectId: testProjectId,
Region: testRegion,
},
ServerId: testServerId,
SecurityGroupId: testSecurityGroupId,
}
for _, mod := range mods {
mod(model)
}
return model
}
func fixtureRequest(mods ...func(request *iaas.ApiAddSecurityGroupToServerRequest)) iaas.ApiAddSecurityGroupToServerRequest {
request := testClient.AddSecurityGroupToServer(testCtx, testProjectId, testRegion, testServerId, testSecurityGroupId)
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: "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: "security group id missing",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, securityGroupIdFlag)
}),
isValid: false,
},
{
description: "security group id invalid 1",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[securityGroupIdFlag] = ""
}),
isValid: false,
},
{
description: "security group id invalid 2",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[securityGroupIdFlag] = "invalid-uuid"
}),
isValid: false,
},
{
description: "server id flag missing",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, serverIdFlag)
}),
isValid: false,
},
{
description: "server id invalid 1",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[serverIdFlag] = ""
}),
isValid: false,
},
{
description: "server id invalid 2",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[serverIdFlag] = "invalid-uuid"
}),
isValid: false,
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
testutils.TestParseInput(t, NewCmd, parseInput, tt.expectedModel, []string{}, tt.flagValues, tt.isValid)
})
}
}
func TestBuildRequest(t *testing.T) {
tests := []struct {
description string
model *inputModel
expectedRequest iaas.ApiAddSecurityGroupToServerRequest
}{
{
description: "base",
model: fixtureInputModel(),
expectedRequest: fixtureRequest(),
},
}
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),
)
if diff != "" {
t.Fatalf("Data does not match: %s", diff)
}
})
}
}
package attach
import (
"context"
"fmt"
"github.com/stackitcloud/stackit-cli/internal/pkg/types"
"github.com/spf13/cobra"
"github.com/stackitcloud/stackit-cli/internal/pkg/args"
cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors"
"github.com/stackitcloud/stackit-cli/internal/pkg/examples"
"github.com/stackitcloud/stackit-cli/internal/pkg/flags"
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
"github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client"
iaasUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/utils"
"github.com/stackitcloud/stackit-sdk-go/services/iaas"
)
const (
serverIdFlag = "server-id"
securityGroupIdFlag = "security-group-id"
)
type inputModel struct {
*globalflags.GlobalFlagModel
ServerId string
SecurityGroupId string
}
func NewCmd(params *types.CmdParams) *cobra.Command {
cmd := &cobra.Command{
Use: "attach",
Short: "Attaches a security group to a server",
Long: "Attaches a security group to a server.",
Args: args.NoArgs,
Example: examples.Build(
examples.NewExample(
`Attach a security group with ID "xxx" to a server with ID "yyy"`,
`$ stackit server security-group attach --server-id yyy --security-group-id xxx`,
),
),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
model, err := parseInput(params.Printer, cmd, args)
if err != nil {
return err
}
// Configure API client
apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion)
if err != nil {
return err
}
serverLabel, err := iaasUtils.GetServerName(ctx, apiClient, model.ProjectId, model.Region, model.ServerId)
if err != nil {
params.Printer.Debug(print.ErrorLevel, "get server name: %v", err)
serverLabel = model.ServerId
} else if serverLabel == "" {
serverLabel = model.ServerId
}
securityGroupLabel, err := iaasUtils.GetSecurityGroupName(ctx, apiClient, model.ProjectId, model.Region, model.SecurityGroupId)
if err != nil {
params.Printer.Debug(print.ErrorLevel, "get security group name: %v", err)
securityGroupLabel = model.SecurityGroupId
}
prompt := fmt.Sprintf("Are you sure you want to attach security group %q to server %q?", securityGroupLabel, serverLabel)
err = params.Printer.PromptForConfirmation(prompt)
if err != nil {
return err
}
// Call API
req := buildRequest(ctx, model, apiClient)
if err := req.Execute(); err != nil {
return fmt.Errorf("attach security group to server: %w", err)
}
params.Printer.Outputf("Attached security group %q to server %q\n", securityGroupLabel, serverLabel)
return nil
},
}
configureFlags(cmd)
return cmd
}
func configureFlags(cmd *cobra.Command) {
cmd.Flags().Var(flags.UUIDFlag(), serverIdFlag, "Server ID")
cmd.Flags().Var(flags.UUIDFlag(), securityGroupIdFlag, "Security Group ID")
err := flags.MarkFlagsRequired(cmd, serverIdFlag, securityGroupIdFlag)
cobra.CheckErr(err)
}
func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, error) {
globalFlags := globalflags.Parse(p, cmd)
if globalFlags.ProjectId == "" {
return nil, &cliErr.ProjectIdError{}
}
model := inputModel{
GlobalFlagModel: globalFlags,
ServerId: flags.FlagToStringValue(p, cmd, serverIdFlag),
SecurityGroupId: flags.FlagToStringValue(p, cmd, securityGroupIdFlag),
}
p.DebugInputModel(model)
return &model, nil
}
func buildRequest(ctx context.Context, model *inputModel, apiClient *iaas.APIClient) iaas.ApiAddSecurityGroupToServerRequest {
req := apiClient.AddSecurityGroupToServer(ctx, model.ProjectId, model.Region, model.ServerId, model.SecurityGroupId)
return req
}
package detach
import (
"context"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/google/uuid"
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
"github.com/stackitcloud/stackit-cli/internal/pkg/testutils"
"github.com/stackitcloud/stackit-sdk-go/services/iaas"
)
const (
testRegion = "eu01"
)
type testCtxKey struct{}
var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo")
var testClient = &iaas.APIClient{}
var testProjectId = uuid.NewString()
var testServerId = uuid.NewString()
var testSecurityGroupId = uuid.NewString()
func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string {
flagValues := map[string]string{
globalflags.ProjectIdFlag: testProjectId,
globalflags.RegionFlag: testRegion,
serverIdFlag: testServerId,
securityGroupIdFlag: testSecurityGroupId,
}
for _, mod := range mods {
mod(flagValues)
}
return flagValues
}
func fixtureInputModel(mods ...func(model *inputModel)) *inputModel {
model := &inputModel{
GlobalFlagModel: &globalflags.GlobalFlagModel{
Verbosity: globalflags.VerbosityDefault,
ProjectId: testProjectId,
Region: testRegion,
},
ServerId: testServerId,
SecurityGroupId: testSecurityGroupId,
}
for _, mod := range mods {
mod(model)
}
return model
}
func fixtureRequest(mods ...func(request *iaas.ApiRemoveSecurityGroupFromServerRequest)) iaas.ApiRemoveSecurityGroupFromServerRequest {
request := testClient.RemoveSecurityGroupFromServer(testCtx, testProjectId, testRegion, testServerId, testSecurityGroupId)
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: "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: "security group id missing",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, securityGroupIdFlag)
}),
isValid: false,
},
{
description: "security group id invalid 1",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[securityGroupIdFlag] = ""
}),
isValid: false,
},
{
description: "security group id invalid 2",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[securityGroupIdFlag] = "invalid-uuid"
}),
isValid: false,
},
{
description: "server id flag missing",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, serverIdFlag)
}),
isValid: false,
},
{
description: "server id invalid 1",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[serverIdFlag] = ""
}),
isValid: false,
},
{
description: "server id invalid 2",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[serverIdFlag] = "invalid-uuid"
}),
isValid: false,
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
testutils.TestParseInput(t, NewCmd, parseInput, tt.expectedModel, []string{}, tt.flagValues, tt.isValid)
})
}
}
func TestBuildRequest(t *testing.T) {
tests := []struct {
description string
model *inputModel
expectedRequest iaas.ApiRemoveSecurityGroupFromServerRequest
}{
{
description: "base",
model: fixtureInputModel(),
expectedRequest: fixtureRequest(),
},
}
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),
)
if diff != "" {
t.Fatalf("Data does not match: %s", diff)
}
})
}
}
package detach
import (
"context"
"fmt"
"github.com/stackitcloud/stackit-cli/internal/pkg/types"
"github.com/spf13/cobra"
"github.com/stackitcloud/stackit-cli/internal/pkg/args"
cliErr "github.com/stackitcloud/stackit-cli/internal/pkg/errors"
"github.com/stackitcloud/stackit-cli/internal/pkg/examples"
"github.com/stackitcloud/stackit-cli/internal/pkg/flags"
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
"github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/client"
iaasUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/utils"
"github.com/stackitcloud/stackit-sdk-go/services/iaas"
)
const (
serverIdFlag = "server-id"
securityGroupIdFlag = "security-group-id"
)
type inputModel struct {
*globalflags.GlobalFlagModel
ServerId string
SecurityGroupId string
}
func NewCmd(params *types.CmdParams) *cobra.Command {
cmd := &cobra.Command{
Use: "detach",
Short: "Detaches a security group from a server",
Long: "Detaches a security group from a server.",
Args: args.NoArgs,
Example: examples.Build(
examples.NewExample(
`Detach a security group with ID "xxx" from a server with ID "yyy"`,
`$ stackit server security-group detach --server-id yyy --security-group-id xxx`,
),
),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := context.Background()
model, err := parseInput(params.Printer, cmd, args)
if err != nil {
return err
}
// Configure API client
apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion)
if err != nil {
return err
}
serverLabel, err := iaasUtils.GetServerName(ctx, apiClient, model.ProjectId, model.Region, model.ServerId)
if err != nil {
params.Printer.Debug(print.ErrorLevel, "get server name: %v", err)
serverLabel = model.ServerId
} else if serverLabel == "" {
serverLabel = model.ServerId
}
securityGroupLabel, err := iaasUtils.GetSecurityGroupName(ctx, apiClient, model.ProjectId, model.Region, model.SecurityGroupId)
if err != nil {
params.Printer.Debug(print.ErrorLevel, "get security group name: %v", err)
securityGroupLabel = model.SecurityGroupId
}
prompt := fmt.Sprintf("Are you sure you want to detach security group %q from server %q?", securityGroupLabel, serverLabel)
err = params.Printer.PromptForConfirmation(prompt)
if err != nil {
return err
}
// Call API
req := buildRequest(ctx, model, apiClient)
if err := req.Execute(); err != nil {
return fmt.Errorf("detach security group from server: %w", err)
}
params.Printer.Outputf("Detached security group %q from server %q\n", securityGroupLabel, serverLabel)
return nil
},
}
configureFlags(cmd)
return cmd
}
func configureFlags(cmd *cobra.Command) {
cmd.Flags().Var(flags.UUIDFlag(), serverIdFlag, "Server ID")
cmd.Flags().Var(flags.UUIDFlag(), securityGroupIdFlag, "Security Group ID")
err := flags.MarkFlagsRequired(cmd, serverIdFlag, securityGroupIdFlag)
cobra.CheckErr(err)
}
func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, error) {
globalFlags := globalflags.Parse(p, cmd)
if globalFlags.ProjectId == "" {
return nil, &cliErr.ProjectIdError{}
}
model := inputModel{
GlobalFlagModel: globalFlags,
ServerId: flags.FlagToStringValue(p, cmd, serverIdFlag),
SecurityGroupId: flags.FlagToStringValue(p, cmd, securityGroupIdFlag),
}
p.DebugInputModel(model)
return &model, nil
}
func buildRequest(ctx context.Context, model *inputModel, apiClient *iaas.APIClient) iaas.ApiRemoveSecurityGroupFromServerRequest {
req := apiClient.RemoveSecurityGroupFromServer(ctx, model.ProjectId, model.Region, model.ServerId, model.SecurityGroupId)
return req
}
package securitygroup
import (
"github.com/stackitcloud/stackit-cli/internal/cmd/server/security-group/attach"
"github.com/stackitcloud/stackit-cli/internal/cmd/server/security-group/detach"
"github.com/stackitcloud/stackit-cli/internal/pkg/args"
"github.com/stackitcloud/stackit-cli/internal/pkg/types"
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
"github.com/spf13/cobra"
)
func NewCmd(params *types.CmdParams) *cobra.Command {
cmd := &cobra.Command{
Use: "security-group",
Short: "Allows attaching/detaching security groups to servers",
Long: "Allows attaching/detaching security groups to servers.",
Args: args.NoArgs,
Run: utils.CmdHelp,
}
addSubcommands(cmd, params)
return cmd
}
func addSubcommands(cmd *cobra.Command, params *types.CmdParams) {
cmd.AddCommand(attach.NewCmd(params))
cmd.AddCommand(detach.NewCmd(params))
}
package utils
import (
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
"github.com/stackitcloud/stackit-sdk-go/services/iaas"
)
const ipv4 = "ipv4"
const ipv6 = "ipv6"
const cidrv4 = "cidrv4"
const cidrv6 = "cidrv6"
func TestExtractRouteDetails(t *testing.T) {
created := time.Date(2024, 1, 2, 3, 4, 5, 0, time.UTC)
updated := time.Date(2024, 1, 2, 4, 5, 6, 0, time.UTC)
tests := []struct {
description string
input *iaas.Route
want RouteDetails
}{
{
description: "completely empty route (zero value)",
input: &iaas.Route{},
want: RouteDetails{},
},
{
description: "destination only, no nexthop, no labels",
input: &iaas.Route{
Destination: &iaas.RouteDestination{
DestinationCIDRv4: &iaas.DestinationCIDRv4{
Type: utils.Ptr(cidrv4),
Value: utils.Ptr("10.0.0.0/24"),
},
},
},
want: RouteDetails{
DestType: cidrv4,
DestValue: "10.0.0.0/24",
},
},
{
description: "nexthop only, no destination, empty labels map",
input: &iaas.Route{
Nexthop: &iaas.RouteNexthop{
NexthopIPv4: &iaas.NexthopIPv4{
Type: utils.Ptr(ipv4),
Value: utils.Ptr("10.0.0.1"),
},
},
Labels: &map[string]interface{}{}, // empty but non-nil
},
want: RouteDetails{
HopType: ipv4,
HopValue: "10.0.0.1",
},
},
{
description: "destination present, nexthop struct nil, labels nil",
input: &iaas.Route{
Destination: &iaas.RouteDestination{
DestinationCIDRv6: &iaas.DestinationCIDRv6{
Type: utils.Ptr(cidrv6),
Value: utils.Ptr("2001:db8::/32"),
},
},
Nexthop: nil,
Labels: nil,
},
want: RouteDetails{
DestType: cidrv6,
DestValue: "2001:db8::/32",
},
},
{
description: "CIDRv4 destination, IPv4 nexthop, with labels",
input: &iaas.Route{
Destination: &iaas.RouteDestination{
DestinationCIDRv4: &iaas.DestinationCIDRv4{
Type: utils.Ptr(cidrv4),
Value: utils.Ptr("10.0.0.0/24"),
},
},
Nexthop: &iaas.RouteNexthop{
NexthopIPv4: &iaas.NexthopIPv4{
Type: utils.Ptr(ipv4),
Value: utils.Ptr("10.0.0.1"),
},
},
Labels: &map[string]interface{}{
"key": "value",
},
},
want: RouteDetails{
DestType: cidrv4,
DestValue: "10.0.0.0/24",
HopType: ipv4,
HopValue: "10.0.0.1",
Labels: "key: value",
},
},
{
description: "CIDRv6 destination, IPv6 nexthop, with no labels",
input: &iaas.Route{
Destination: &iaas.RouteDestination{
DestinationCIDRv6: &iaas.DestinationCIDRv6{
Type: utils.Ptr(cidrv6),
Value: utils.Ptr("2001:db8::/32"),
},
},
Nexthop: &iaas.RouteNexthop{
NexthopIPv6: &iaas.NexthopIPv6{
Type: utils.Ptr(ipv6),
Value: utils.Ptr("2001:db8::1"),
},
},
Labels: nil,
},
want: RouteDetails{
DestType: cidrv6,
DestValue: "2001:db8::/32",
HopType: ipv6,
HopValue: "2001:db8::1",
},
},
{
description: "Internet nexthop without value",
input: &iaas.Route{
Destination: &iaas.RouteDestination{
DestinationCIDRv4: &iaas.DestinationCIDRv4{
Type: utils.Ptr(cidrv4),
Value: utils.Ptr("0.0.0.0/0"),
},
},
Nexthop: &iaas.RouteNexthop{
NexthopInternet: &iaas.NexthopInternet{
Type: utils.Ptr("Internet"),
},
},
Labels: nil,
},
want: RouteDetails{
DestType: cidrv4,
DestValue: "0.0.0.0/0",
HopType: "Internet",
// HopValue empty
},
},
{
description: "Blackhole nexthop without value and nil labels map",
input: &iaas.Route{
Destination: &iaas.RouteDestination{
DestinationCIDRv6: &iaas.DestinationCIDRv6{
Type: utils.Ptr(cidrv6),
Value: utils.Ptr("::/0"),
},
},
Nexthop: &iaas.RouteNexthop{
NexthopBlackhole: &iaas.NexthopBlackhole{
Type: utils.Ptr("Blackhole"),
},
},
Labels: nil,
},
want: RouteDetails{
DestType: cidrv6,
DestValue: "::/0",
HopType: "Blackhole",
},
},
{
description: "route with created and updated timestamps",
input: &iaas.Route{
Destination: &iaas.RouteDestination{
DestinationCIDRv4: &iaas.DestinationCIDRv4{
Type: utils.Ptr(cidrv4),
Value: utils.Ptr("10.0.0.0/24"),
},
},
Nexthop: &iaas.RouteNexthop{
NexthopIPv4: &iaas.NexthopIPv4{
Type: utils.Ptr(ipv4),
Value: utils.Ptr("10.0.0.1"),
},
},
CreatedAt: &created,
UpdatedAt: &updated,
},
want: RouteDetails{
DestType: cidrv4,
DestValue: "10.0.0.0/24",
HopType: ipv4,
HopValue: "10.0.0.1",
CreatedAt: created.Format(time.RFC3339),
UpdatedAt: updated.Format(time.RFC3339),
Labels: "",
},
},
{
description: "route with created timestamp only",
input: &iaas.Route{
Destination: &iaas.RouteDestination{
DestinationCIDRv4: &iaas.DestinationCIDRv4{
Type: utils.Ptr(cidrv4),
Value: utils.Ptr("10.0.0.0/24"),
},
},
Nexthop: &iaas.RouteNexthop{
NexthopIPv4: &iaas.NexthopIPv4{
Type: utils.Ptr(ipv4),
Value: utils.Ptr("10.0.0.1"),
},
},
CreatedAt: &created,
},
want: RouteDetails{
DestType: cidrv4,
DestValue: "10.0.0.0/24",
HopType: ipv4,
HopValue: "10.0.0.1",
CreatedAt: created.Format(time.RFC3339),
UpdatedAt: "",
Labels: "",
},
},
{
description: "route with updated timestamp only",
input: &iaas.Route{
Destination: &iaas.RouteDestination{
DestinationCIDRv4: &iaas.DestinationCIDRv4{
Type: utils.Ptr(cidrv4),
Value: utils.Ptr("10.0.0.0/24"),
},
},
Nexthop: &iaas.RouteNexthop{
NexthopIPv4: &iaas.NexthopIPv4{
Type: utils.Ptr(ipv4),
Value: utils.Ptr("10.0.0.1"),
},
},
UpdatedAt: &updated,
},
want: RouteDetails{
DestType: cidrv4,
DestValue: "10.0.0.0/24",
HopType: ipv4,
HopValue: "10.0.0.1",
CreatedAt: "",
UpdatedAt: updated.Format(time.RFC3339),
Labels: "",
},
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
got := ExtractRouteDetails(*tt.input)
if diff := cmp.Diff(tt.want, got); diff != "" {
t.Fatalf("ExtractRouteDetails() mismatch (-want +got):\n%s", diff)
}
})
}
}
package utils
import (
"fmt"
"time"
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
"github.com/stackitcloud/stackit-sdk-go/services/iaas"
)
type RouteDetails struct {
DestType string
DestValue string
HopType string
HopValue string
CreatedAt string
UpdatedAt string
Labels string
}
func ExtractRouteDetails(route iaas.Route) RouteDetails {
var routeDetails RouteDetails
if route.Destination != nil {
if route.Destination.DestinationCIDRv4 != nil {
routeDetails.DestType = utils.PtrString(route.Destination.DestinationCIDRv4.Type)
routeDetails.DestValue = utils.PtrString(route.Destination.DestinationCIDRv4.Value)
} else if route.Destination.DestinationCIDRv6 != nil {
routeDetails.DestType = utils.PtrString(route.Destination.DestinationCIDRv6.Type)
routeDetails.DestValue = utils.PtrString(route.Destination.DestinationCIDRv6.Value)
}
}
if route.Nexthop != nil {
if route.Nexthop.NexthopIPv4 != nil {
routeDetails.HopType = utils.PtrString(route.Nexthop.NexthopIPv4.Type)
routeDetails.HopValue = utils.PtrString(route.Nexthop.NexthopIPv4.Value)
} else if route.Nexthop.NexthopIPv6 != nil {
routeDetails.HopType = utils.PtrString(route.Nexthop.NexthopIPv6.Type)
routeDetails.HopValue = utils.PtrString(route.Nexthop.NexthopIPv6.Value)
} else if route.Nexthop.NexthopInternet != nil {
routeDetails.HopType = utils.PtrString(route.Nexthop.NexthopInternet.Type)
} else if route.Nexthop.NexthopBlackhole != nil {
routeDetails.HopType = utils.PtrString(route.Nexthop.NexthopBlackhole.Type)
}
}
if route.Labels != nil && len(*route.Labels) > 0 {
stringMap := make(map[string]string)
for key, value := range *route.Labels {
stringMap[key] = fmt.Sprintf("%v", value)
}
routeDetails.Labels = utils.JoinStringMap(stringMap, ": ", "\n")
}
if route.CreatedAt != nil {
routeDetails.CreatedAt = route.CreatedAt.Format(time.RFC3339)
}
if route.UpdatedAt != nil {
routeDetails.UpdatedAt = route.UpdatedAt.Format(time.RFC3339)
}
return routeDetails
}
+1
-1

@@ -68,3 +68,3 @@ name: CI

- name: Check new code coverage
uses: fgrosse/go-coverage-report@v1.2.0
uses: fgrosse/go-coverage-report@v1.3.0
continue-on-error: true # Add this line to prevent pipeline failures in forks

@@ -71,0 +71,0 @@ with:

@@ -40,3 +40,4 @@ ## stackit network-area routing-table

* [stackit network-area routing-table list](./stackit_network-area_routing-table_list.md) - Lists all routing-tables
* [stackit network-area routing-table route](./stackit_network-area_routing-table_route.md) - Manages routes of a routing-table
* [stackit network-area routing-table update](./stackit_network-area_routing-table_update.md) - Updates a routing-table

@@ -18,2 +18,5 @@ ## stackit object-storage bucket create

$ stackit object-storage bucket create my-bucket
Create an Object Storage bucket with enabled object-lock
$ stackit object-storage bucket create my-bucket --object-lock-enabled
```

@@ -24,3 +27,4 @@

```
-h, --help Help for "stackit object-storage bucket create"
-h, --help Help for "stackit object-storage bucket create"
--object-lock-enabled is the object-lock enabled for the bucket
```

@@ -27,0 +31,0 @@

@@ -49,2 +49,3 @@ ## stackit server

* [stackit server resize](./stackit_server_resize.md) - Resizes the server to the given machine type
* [stackit server security-group](./stackit_server_security-group.md) - Allows attaching/detaching security groups to servers
* [stackit server service-account](./stackit_server_service-account.md) - Allows attaching/detaching service accounts to servers

@@ -51,0 +52,0 @@ * [stackit server start](./stackit_server_start.md) - Starts an existing server or allocates the server if deallocated

+2
-2

@@ -18,3 +18,3 @@ module github.com/stackitcloud/stackit-cli

github.com/spf13/viper v1.21.0
github.com/stackitcloud/stackit-sdk-go/core v0.22.0
github.com/stackitcloud/stackit-sdk-go/core v0.23.0
github.com/stackitcloud/stackit-sdk-go/services/alb v0.10.0

@@ -273,3 +273,3 @@ github.com/stackitcloud/stackit-sdk-go/services/authorization v0.12.0

github.com/stackitcloud/stackit-sdk-go/services/mariadb v0.25.6
github.com/stackitcloud/stackit-sdk-go/services/objectstorage v1.4.5
github.com/stackitcloud/stackit-sdk-go/services/objectstorage v1.7.0
github.com/stackitcloud/stackit-sdk-go/services/observability v0.17.0

@@ -276,0 +276,0 @@ github.com/stackitcloud/stackit-sdk-go/services/rabbitmq v0.26.0

@@ -255,2 +255,3 @@ package create

outputFormat string
async bool
projectLabel string

@@ -291,3 +292,3 @@ resp *sfs.CreateResourcePoolResponse

t.Run(tt.name, func(t *testing.T) {
if err := outputResult(p, tt.args.outputFormat, tt.args.projectLabel, tt.args.resp); (err != nil) != tt.wantErr {
if err := outputResult(p, tt.args.outputFormat, tt.args.async, tt.args.projectLabel, tt.args.resp); (err != nil) != tt.wantErr {
t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr)

@@ -294,0 +295,0 @@ }

@@ -112,3 +112,3 @@ package create

return outputResult(params.Printer, model.OutputFormat, projectLabel, resp)
return outputResult(params.Printer, model.OutputFormat, model.Async, projectLabel, resp)
},

@@ -174,3 +174,3 @@ }

func outputResult(p *print.Printer, outputFormat, projectLabel string, resp *sfs.CreateResourcePoolResponse) error {
func outputResult(p *print.Printer, outputFormat string, async bool, projectLabel string, resp *sfs.CreateResourcePoolResponse) error {
return p.OutputResult(outputFormat, resp, func() error {

@@ -181,5 +181,9 @@ if resp == nil || resp.ResourcePool == nil {

}
p.Outputf("Created resource pool for project %q. Resource pool ID: %s\n", projectLabel, utils.PtrString(resp.ResourcePool.Id))
operationState := "Created"
if async {
operationState = "Triggered creation of"
}
p.Outputf("%s resource pool for project %q. Resource pool ID: %s\n", operationState, projectLabel, utils.PtrString(resp.ResourcePool.Id))
return nil
})
}

@@ -178,2 +178,3 @@ package delete

outputFormat string
async bool
resourcePoolName string

@@ -205,3 +206,3 @@ response map[string]interface{}

t.Run(tt.name, func(t *testing.T) {
if err := outputResult(p, tt.args.outputFormat, tt.args.resourcePoolName, tt.args.response); (err != nil) != tt.wantErr {
if err := outputResult(p, tt.args.outputFormat, tt.args.async, tt.args.resourcePoolName, tt.args.response); (err != nil) != tt.wantErr {
t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr)

@@ -208,0 +209,0 @@ }

@@ -85,3 +85,3 @@ package delete

return outputResult(params.Printer, model.OutputFormat, resourcePoolName, resp)
return outputResult(params.Printer, model.OutputFormat, model.Async, resourcePoolName, resp)
},

@@ -114,7 +114,11 @@ }

func outputResult(p *print.Printer, outputFormat, resourcePoolName string, response map[string]interface{}) error {
func outputResult(p *print.Printer, outputFormat string, async bool, resourcePoolName string, response map[string]interface{}) error {
return p.OutputResult(outputFormat, response, func() error {
p.Outputf("Deleted resource pool %q\n", resourcePoolName)
operationState := "Deleted"
if async {
operationState = "Triggered deletion of"
}
p.Outputf("%s resource pool %q\n", operationState, resourcePoolName)
return nil
})
}

@@ -311,2 +311,3 @@ package update

outputFormat string
async bool
resp *sfs.UpdateResourcePoolResponse

@@ -357,3 +358,3 @@ }

t.Run(tt.name, func(t *testing.T) {
if err := outputResult(p, tt.args.outputFormat, tt.args.resp); (err != nil) != tt.wantErr {
if err := outputResult(p, tt.args.outputFormat, tt.args.async, tt.args.resp); (err != nil) != tt.wantErr {
t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr)

@@ -360,0 +361,0 @@ }

@@ -113,3 +113,3 @@ package update

return outputResult(params.Printer, model.OutputFormat, resp)
return outputResult(params.Printer, model.OutputFormat, model.Async, resp)
},

@@ -169,3 +169,3 @@ }

func outputResult(p *print.Printer, outputFormat string, resp *sfs.UpdateResourcePoolResponse) error {
func outputResult(p *print.Printer, outputFormat string, async bool, resp *sfs.UpdateResourcePoolResponse) error {
return p.OutputResult(outputFormat, resp, func() error {

@@ -176,5 +176,9 @@ if resp == nil || resp.ResourcePool == nil {

}
p.Outputf("Updated resource pool %s\n", utils.PtrString(resp.ResourcePool.Name))
operationState := "Updated"
if async {
operationState = "Triggered update of"
}
p.Outputf("%s resource pool %s\n", operationState, utils.PtrString(resp.ResourcePool.Name))
return nil
})
}

@@ -178,4 +178,6 @@ package create

type args struct {
model *inputModel
resp *git.Instance
outputFormat string
async bool
instanceName string
resp *git.Instance
}

@@ -188,6 +190,8 @@ tests := []struct {

{
name: "nil",
name: "nil response",
args: args{
model: nil,
resp: nil,
outputFormat: "",
async: false,
instanceName: "",
resp: nil,
},

@@ -199,4 +203,6 @@ wantErr: true,

args: args{
model: &inputModel{},
resp: &git.Instance{},
outputFormat: "",
async: false,
instanceName: "",
resp: &git.Instance{Id: utils.Ptr(uuid.NewString())},
},

@@ -208,8 +214,6 @@ wantErr: false,

args: args{
model: &inputModel{
GlobalFlagModel: &globalflags.GlobalFlagModel{
OutputFormat: print.JSONOutputFormat,
},
},
resp: nil,
outputFormat: print.JSONOutputFormat,
async: true,
instanceName: testName,
resp: &git.Instance{Id: utils.Ptr(uuid.NewString())},
},

@@ -223,3 +227,3 @@ wantErr: false,

t.Run(tt.name, func(t *testing.T) {
if err := outputResult(p, tt.args.model, tt.args.resp); (err != nil) != tt.wantErr {
if err := outputResult(p, tt.args.outputFormat, tt.args.async, tt.args.instanceName, tt.args.resp); (err != nil) != tt.wantErr {
t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr)

@@ -226,0 +230,0 @@ }

@@ -18,3 +18,2 @@ package create

"github.com/stackitcloud/stackit-cli/internal/pkg/spinner"
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
"github.com/stackitcloud/stackit-sdk-go/services/git"

@@ -32,3 +31,2 @@ "github.com/stackitcloud/stackit-sdk-go/services/git/wait"

*globalflags.GlobalFlagModel
Id *string
Name string

@@ -85,3 +83,2 @@ Flavor string

}
model.Id = result.Id

@@ -92,3 +89,3 @@ // Wait for async operation, if async mode not enabled

s.Start("Creating stackit git instance")
_, err = wait.CreateGitInstanceWaitHandler(ctx, apiClient, model.ProjectId, *model.Id).WaitWithContext(ctx)
_, err = wait.CreateGitInstanceWaitHandler(ctx, apiClient, model.ProjectId, *result.Id).WaitWithContext(ctx)
if err != nil {

@@ -100,3 +97,3 @@ return fmt.Errorf("wait for stackit git Instance creation: %w", err)

return outputResult(params.Printer, model, result)
return outputResult(params.Printer, model.OutputFormat, model.Async, model.Name, result)
},

@@ -151,15 +148,18 @@ }

func outputResult(p *print.Printer, model *inputModel, resp *git.Instance) error {
if model == nil {
return fmt.Errorf("input model is nil")
func outputResult(p *print.Printer, outputFormat string, async bool, instanceName string, resp *git.Instance) error {
if resp == nil {
return fmt.Errorf("API resp is nil")
}
var outputFormat string
if model.GlobalFlagModel != nil {
outputFormat = model.OutputFormat
if resp.Id == nil {
return fmt.Errorf("API resp is missing instance id")
}
return p.OutputResult(outputFormat, resp, func() error {
p.Outputf("Created instance %q with id %s\n", model.Name, utils.PtrString(model.Id))
operationState := "Created"
if async {
operationState = "Triggered creation of"
}
p.Outputf("%s instance %q with id %s\n", operationState, instanceName, *resp.Id)
return nil
})
}

@@ -5,3 +5,2 @@ package create

"context"
"encoding/json"
"fmt"

@@ -11,3 +10,2 @@

"github.com/goccy/go-yaml"
"github.com/spf13/cobra"

@@ -164,18 +162,3 @@ "github.com/stackitcloud/stackit-cli/internal/pkg/args"

switch model.OutputFormat {
case print.JSONOutputFormat:
details, err := json.MarshalIndent(resp, "", " ")
if err != nil {
return fmt.Errorf("marshal KMS key: %w", err)
}
p.Outputln(string(details))
case print.YAMLOutputFormat:
details, err := yaml.MarshalWithOptions(resp, yaml.IndentSequence(true), yaml.UseJSONMarshaler())
if err != nil {
return fmt.Errorf("marshal KMS key: %w", err)
}
p.Outputln(string(details))
default:
return p.OutputResult(model.OutputFormat, resp, func() error {
operationState := "Created"

@@ -186,4 +169,4 @@ if model.Async {

p.Outputf("%s the KMS key %q. Key ID: %s\n", operationState, utils.PtrString(resp.DisplayName), utils.PtrString(resp.Id))
}
return nil
return nil
})
}

@@ -190,0 +173,0 @@

@@ -5,3 +5,2 @@ package delete

"context"
"encoding/json"
"fmt"

@@ -11,3 +10,2 @@

"github.com/goccy/go-yaml"
"github.com/spf13/cobra"

@@ -132,20 +130,6 @@ "github.com/stackitcloud/stackit-cli/internal/pkg/args"

switch outputFormat {
case print.JSONOutputFormat:
details, err := json.MarshalIndent(resp, "", " ")
if err != nil {
return fmt.Errorf("marshal output to JSON: %w", err)
}
p.Outputln(string(details))
case print.YAMLOutputFormat:
details, err := yaml.MarshalWithOptions(resp, yaml.IndentSequence(true), yaml.UseJSONMarshaler())
if err != nil {
return fmt.Errorf("marshal output to YAML: %w", err)
}
p.Outputln(string(details))
default:
return p.OutputResult(outputFormat, resp, func() error {
p.Outputf("Deletion of KMS key %s scheduled successfully for the deletion date: %s\n", utils.PtrString(resp.DisplayName), utils.PtrString(resp.DeletionDate))
}
return nil
return nil
})
}

@@ -6,3 +6,2 @@ package importKey

"encoding/base64"
"encoding/json"
"fmt"

@@ -12,3 +11,2 @@

"github.com/goccy/go-yaml"
"github.com/spf13/cobra"

@@ -153,22 +151,6 @@ "github.com/stackitcloud/stackit-cli/internal/pkg/args"

switch outputFormat {
case print.JSONOutputFormat:
details, err := json.MarshalIndent(resp, "", " ")
if err != nil {
return fmt.Errorf("marshal KMS key: %w", err)
}
p.Outputln(string(details))
case print.YAMLOutputFormat:
details, err := yaml.MarshalWithOptions(resp, yaml.IndentSequence(true), yaml.UseJSONMarshaler())
if err != nil {
return fmt.Errorf("marshal KMS key: %w", err)
}
p.Outputln(string(details))
default:
return p.OutputResult(outputFormat, resp, func() error {
p.Outputf("Imported a new version for the key %q inside the key ring %q\n", keyName, keyRingName)
}
return nil
return nil
})
}

@@ -175,0 +157,0 @@

@@ -5,3 +5,2 @@ package list

"context"
"encoding/json"
"fmt"

@@ -11,3 +10,2 @@

"github.com/goccy/go-yaml"
"github.com/spf13/cobra"

@@ -111,18 +109,3 @@ "github.com/stackitcloud/stackit-cli/internal/pkg/args"

switch outputFormat {
case print.JSONOutputFormat:
details, err := json.MarshalIndent(keys, "", " ")
if err != nil {
return fmt.Errorf("marshal KMS Keys list: %w", err)
}
p.Outputln(string(details))
case print.YAMLOutputFormat:
details, err := yaml.MarshalWithOptions(keys, yaml.IndentSequence(true), yaml.UseJSONMarshaler())
if err != nil {
return fmt.Errorf("marshal KMS Keys list: %w", err)
}
p.Outputln(string(details))
default:
return p.OutputResult(outputFormat, keys, func() error {
if len(keys) == 0 {

@@ -150,4 +133,4 @@ p.Outputf("No keys found for project %q under the key ring %q\n", projectId, keyRingId)

}
}
return nil
return nil
})
}

@@ -5,3 +5,2 @@ package restore

"context"
"encoding/json"
"fmt"

@@ -11,3 +10,2 @@

"github.com/goccy/go-yaml"
"github.com/spf13/cobra"

@@ -131,20 +129,6 @@ "github.com/stackitcloud/stackit-cli/internal/pkg/args"

switch outputFormat {
case print.JSONOutputFormat:
details, err := json.MarshalIndent(resp, "", " ")
if err != nil {
return fmt.Errorf("marshal output to JSON: %w", err)
}
p.Outputln(string(details))
case print.YAMLOutputFormat:
details, err := yaml.MarshalWithOptions(resp, yaml.IndentSequence(true), yaml.UseJSONMarshaler())
if err != nil {
return fmt.Errorf("marshal output to YAML: %w", err)
}
p.Outputln(string(details))
default:
return p.OutputResult(outputFormat, resp, func() error {
p.Outputf("Successfully restored KMS key %q\n", utils.PtrString(resp.DisplayName))
}
return nil
return nil
})
}

@@ -5,3 +5,2 @@ package rotate

"context"
"encoding/json"
"fmt"

@@ -11,3 +10,2 @@

"github.com/goccy/go-yaml"
"github.com/spf13/cobra"

@@ -125,22 +123,6 @@ "github.com/stackitcloud/stackit-cli/internal/pkg/args"

switch outputFormat {
case print.JSONOutputFormat:
details, err := json.MarshalIndent(resp, "", " ")
if err != nil {
return fmt.Errorf("marshal KMS key version: %w", err)
}
p.Outputln(string(details))
case print.YAMLOutputFormat:
details, err := yaml.MarshalWithOptions(resp, yaml.IndentSequence(true), yaml.UseJSONMarshaler())
if err != nil {
return fmt.Errorf("marshal KMS key version: %w", err)
}
p.Outputln(string(details))
default:
return p.OutputResult(outputFormat, resp, func() error {
p.Outputf("Rotated key %s\n", utils.PtrString(resp.KeyId))
}
return nil
return nil
})
}

@@ -5,3 +5,2 @@ package create

"context"
"encoding/json"
"fmt"

@@ -11,3 +10,2 @@

"github.com/goccy/go-yaml"
"github.com/spf13/cobra"

@@ -152,18 +150,3 @@ "github.com/stackitcloud/stackit-cli/internal/pkg/args"

switch model.OutputFormat {
case print.JSONOutputFormat:
details, err := json.MarshalIndent(resp, "", " ")
if err != nil {
return fmt.Errorf("marshal KMS key ring: %w", err)
}
p.Outputln(string(details))
case print.YAMLOutputFormat:
details, err := yaml.MarshalWithOptions(resp, yaml.IndentSequence(true), yaml.UseJSONMarshaler())
if err != nil {
return fmt.Errorf("marshal KMS key ring: %w", err)
}
p.Outputln(string(details))
default:
return p.OutputResult(model.OutputFormat, resp, func() error {
operationState := "Created"

@@ -174,4 +157,4 @@ if model.Async {

p.Outputf("%s key ring. KMS key ring ID: %s\n", operationState, utils.PtrString(resp.Id))
}
return nil
return nil
})
}

@@ -178,0 +161,0 @@

@@ -5,3 +5,2 @@ package list

"context"
"encoding/json"
"fmt"

@@ -11,3 +10,2 @@

"github.com/goccy/go-yaml"
"github.com/spf13/cobra"

@@ -96,18 +94,3 @@ "github.com/stackitcloud/stackit-cli/internal/pkg/args"

switch outputFormat {
case print.JSONOutputFormat:
details, err := json.MarshalIndent(keyRings, "", " ")
if err != nil {
return fmt.Errorf("marshal KMS key rings list: %w", err)
}
p.Outputln(string(details))
case print.YAMLOutputFormat:
details, err := yaml.MarshalWithOptions(keyRings, yaml.IndentSequence(true), yaml.UseJSONMarshaler())
if err != nil {
return fmt.Errorf("marshal KMS key rings list: %w", err)
}
p.Outputln(string(details))
default:
return p.OutputResult(outputFormat, keyRings, func() error {
if len(keyRings) == 0 {

@@ -134,5 +117,4 @@ p.Outputf("No key rings found for project %q\n", projectId)

}
}
return nil
return nil
})
}

@@ -5,3 +5,2 @@ package destroy

"context"
"encoding/json"
"fmt"

@@ -12,3 +11,2 @@ "strconv"

"github.com/goccy/go-yaml"
"github.com/spf13/cobra"

@@ -130,22 +128,6 @@ "github.com/stackitcloud/stackit-cli/internal/pkg/args"

switch outputFormat {
case print.JSONOutputFormat:
details, err := json.MarshalIndent(resp, "", " ")
if err != nil {
return fmt.Errorf("marshal KMS key: %w", err)
}
p.Outputln(string(details))
case print.YAMLOutputFormat:
details, err := yaml.MarshalWithOptions(resp, yaml.IndentSequence(true), yaml.UseJSONMarshaler())
if err != nil {
return fmt.Errorf("marshal KMS key: %w", err)
}
p.Outputln(string(details))
default:
return p.OutputResult(outputFormat, resp, func() error {
p.Outputf("Destroyed version %d of the key %q\n", utils.PtrValue(resp.Number), utils.PtrValue(resp.KeyId))
}
return nil
return nil
})
}

@@ -285,2 +285,3 @@ package disable

outputFormat string
async bool
resp *kms.Version

@@ -316,3 +317,3 @@ }{

t.Run(tt.description, func(t *testing.T) {
err := outputResult(p, tt.outputFormat, tt.resp)
err := outputResult(p, tt.outputFormat, tt.async, tt.resp)
if (err != nil) != tt.wantErr {

@@ -319,0 +320,0 @@ t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr)

@@ -5,3 +5,2 @@ package disable

"context"
"encoding/json"
"fmt"

@@ -12,3 +11,2 @@ "strconv"

"github.com/goccy/go-yaml"
"github.com/spf13/cobra"

@@ -92,3 +90,3 @@ "github.com/stackitcloud/stackit-cli/internal/pkg/args"

return outputResult(params.Printer, model.OutputFormat, resp)
return outputResult(params.Printer, model.OutputFormat, model.Async, resp)
},

@@ -139,3 +137,3 @@ }

func outputResult(p *print.Printer, outputFormat string, resp *kms.Version) error {
func outputResult(p *print.Printer, outputFormat string, async bool, resp *kms.Version) error {
if resp == nil {

@@ -145,22 +143,10 @@ return fmt.Errorf("response is nil")

switch outputFormat {
case print.JSONOutputFormat:
details, err := json.MarshalIndent(resp, "", " ")
if err != nil {
return fmt.Errorf("marshal KMS key: %w", err)
return p.OutputResult(outputFormat, resp, func() error {
operationState := "Disabled"
if async {
operationState = "Triggered disable of"
}
p.Outputln(string(details))
case print.YAMLOutputFormat:
details, err := yaml.MarshalWithOptions(resp, yaml.IndentSequence(true), yaml.UseJSONMarshaler())
if err != nil {
return fmt.Errorf("marshal KMS key: %w", err)
}
p.Outputln(string(details))
default:
p.Outputf("Disabled version %d of the key %q\n", utils.PtrValue(resp.Number), utils.PtrValue(resp.KeyId))
}
return nil
p.Outputf("%s version %d of the key %q\n", operationState, utils.PtrValue(resp.Number), utils.PtrValue(resp.KeyId))
return nil
})
}

@@ -285,2 +285,3 @@ package enable

outputFormat string
async bool
resp *kms.Version

@@ -316,3 +317,3 @@ }{

t.Run(tt.description, func(t *testing.T) {
err := outputResult(p, tt.outputFormat, tt.resp)
err := outputResult(p, tt.outputFormat, tt.async, tt.resp)
if (err != nil) != tt.wantErr {

@@ -319,0 +320,0 @@ t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr)

@@ -5,3 +5,2 @@ package enable

"context"
"encoding/json"
"fmt"

@@ -12,3 +11,2 @@ "strconv"

"github.com/goccy/go-yaml"
"github.com/spf13/cobra"

@@ -92,3 +90,3 @@ "github.com/stackitcloud/stackit-cli/internal/pkg/args"

return outputResult(params.Printer, model.OutputFormat, resp)
return outputResult(params.Printer, model.OutputFormat, model.Async, resp)
},

@@ -139,3 +137,3 @@ }

func outputResult(p *print.Printer, outputFormat string, resp *kms.Version) error {
func outputResult(p *print.Printer, outputFormat string, async bool, resp *kms.Version) error {
if resp == nil {

@@ -145,22 +143,10 @@ return fmt.Errorf("response is nil")

switch outputFormat {
case print.JSONOutputFormat:
details, err := json.MarshalIndent(resp, "", " ")
if err != nil {
return fmt.Errorf("marshal KMS key: %w", err)
return p.OutputResult(outputFormat, resp, func() error {
operationState := "Enabled"
if async {
operationState = "Triggered enable of"
}
p.Outputln(string(details))
case print.YAMLOutputFormat:
details, err := yaml.MarshalWithOptions(resp, yaml.IndentSequence(true), yaml.UseJSONMarshaler())
if err != nil {
return fmt.Errorf("marshal KMS key: %w", err)
}
p.Outputln(string(details))
default:
p.Outputf("Enabled version %d of the key %q\n", utils.PtrValue(resp.Number), utils.PtrValue(resp.KeyId))
}
return nil
p.Outputf("%s version %d of the key %q\n", operationState, utils.PtrValue(resp.Number), utils.PtrValue(resp.KeyId))
return nil
})
}

@@ -5,3 +5,2 @@ package list

"context"
"encoding/json"
"fmt"

@@ -11,3 +10,2 @@

"github.com/goccy/go-yaml"
"github.com/spf13/cobra"

@@ -105,18 +103,3 @@ "github.com/stackitcloud/stackit-cli/internal/pkg/args"

switch outputFormat {
case print.JSONOutputFormat:
details, err := json.MarshalIndent(versions, "", " ")
if err != nil {
return fmt.Errorf("marshal key versions list: %w", err)
}
p.Outputln(string(details))
case print.YAMLOutputFormat:
details, err := yaml.MarshalWithOptions(versions, yaml.IndentSequence(true), yaml.UseJSONMarshaler())
if err != nil {
return fmt.Errorf("marshal key versions list: %w", err)
}
p.Outputln(string(details))
default:
return p.OutputResult(outputFormat, versions, func() error {
if len(versions) == 0 {

@@ -143,5 +126,4 @@ p.Outputf("No key versions found for project %q for the key %q\n", projectId, keyId)

}
}
return nil
return nil
})
}

@@ -148,0 +130,0 @@

@@ -5,3 +5,2 @@ package restore

"context"
"encoding/json"
"fmt"

@@ -12,3 +11,2 @@ "strconv"

"github.com/goccy/go-yaml"
"github.com/spf13/cobra"

@@ -130,20 +128,6 @@ "github.com/stackitcloud/stackit-cli/internal/pkg/args"

switch outputFormat {
case print.JSONOutputFormat:
details, err := json.MarshalIndent(resp, "", " ")
if err != nil {
return fmt.Errorf("marshal output to JSON: %w", err)
}
p.Outputln(string(details))
case print.YAMLOutputFormat:
details, err := yaml.MarshalWithOptions(resp, yaml.IndentSequence(true), yaml.UseJSONMarshaler())
if err != nil {
return fmt.Errorf("marshal output to YAML: %w", err)
}
p.Outputln(string(details))
default:
return p.OutputResult(outputFormat, resp, func() error {
p.Outputf("Restored version %d of the key %q\n", utils.PtrValue(resp.Number), utils.PtrValue(resp.KeyId))
}
return nil
return nil
})
}

@@ -5,3 +5,2 @@ package create

"context"
"encoding/json"
"fmt"

@@ -11,3 +10,2 @@

"github.com/goccy/go-yaml"
"github.com/spf13/cobra"

@@ -149,18 +147,3 @@ "github.com/stackitcloud/stackit-cli/internal/pkg/args"

switch model.OutputFormat {
case print.JSONOutputFormat:
details, err := json.MarshalIndent(resp, "", " ")
if err != nil {
return fmt.Errorf("marshal KMS wrapping key: %w", err)
}
p.Outputln(string(details))
case print.YAMLOutputFormat:
details, err := yaml.MarshalWithOptions(resp, yaml.IndentSequence(true), yaml.UseJSONMarshaler())
if err != nil {
return fmt.Errorf("marshal KMS wrapping key: %w", err)
}
p.Outputln(string(details))
default:
return p.OutputResult(model.OutputFormat, resp, func() error {
operationState := "Created"

@@ -171,5 +154,4 @@ if model.Async {

p.Outputf("%s wrapping key. Wrapping key ID: %s\n", operationState, utils.PtrString(resp.Id))
}
return nil
return nil
})
}

@@ -176,0 +158,0 @@

@@ -5,3 +5,2 @@ package list

"context"
"encoding/json"
"fmt"

@@ -11,3 +10,2 @@

"github.com/goccy/go-yaml"
"github.com/spf13/cobra"

@@ -110,18 +108,3 @@ "github.com/stackitcloud/stackit-cli/internal/pkg/args"

switch outputFormat {
case print.JSONOutputFormat:
details, err := json.MarshalIndent(wrappingKeys, "", " ")
if err != nil {
return fmt.Errorf("marshal KMS wrapping keys list: %w", err)
}
p.Outputln(string(details))
case print.YAMLOutputFormat:
details, err := yaml.MarshalWithOptions(wrappingKeys, yaml.IndentSequence(true), yaml.UseJSONMarshaler())
if err != nil {
return fmt.Errorf("marshal KMS wrapping keys list: %w", err)
}
p.Outputln(string(details))
default:
return p.OutputResult(outputFormat, wrappingKeys, func() error {
if len(wrappingKeys) == 0 {

@@ -150,5 +133,4 @@ p.Outputf("No wrapping keys found under the key ring %q\n", keyRingId)

}
}
return nil
return nil
})
}

@@ -111,3 +111,7 @@ package restore

params.Printer.Outputf("Restored instance %q with backup %q\n", model.InstanceId, model.BackupId)
operationState := "Restored"
if model.Async {
operationState = "Triggered restore of"
}
params.Printer.Outputf("%s instance %q with backup %q\n", operationState, model.InstanceId, model.BackupId)
return nil

@@ -133,3 +137,7 @@ }

params.Printer.Outputf("Cloned instance %q from backup with timestamp %q\n", model.InstanceId, model.Timestamp)
operationState := "Cloned"
if model.Async {
operationState = "Triggered clone of"
}
params.Printer.Outputf("%s instance %q from backup with timestamp %q\n", operationState, model.InstanceId, model.Timestamp)
return nil

@@ -136,0 +144,0 @@ },

@@ -270,2 +270,3 @@ package create

outputFormat string
async bool
region string

@@ -304,3 +305,3 @@ networkAreaLabel string

t.Run(tt.name, func(t *testing.T) {
if err := outputResult(p, tt.args.outputFormat, tt.args.region, tt.args.networkAreaLabel, tt.args.regionalArea); (err != nil) != tt.wantErr {
if err := outputResult(p, tt.args.outputFormat, tt.args.async, tt.args.region, tt.args.networkAreaLabel, tt.args.regionalArea); (err != nil) != tt.wantErr {
t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr)

@@ -307,0 +308,0 @@ }

@@ -120,3 +120,3 @@ package create

return outputResult(params.Printer, model.OutputFormat, model.Region, networkAreaLabel, *resp)
return outputResult(params.Printer, model.OutputFormat, model.Async, model.Region, networkAreaLabel, *resp)
},

@@ -190,7 +190,11 @@ }

func outputResult(p *print.Printer, outputFormat, region, networkAreaLabel string, regionalArea iaas.RegionalArea) error {
func outputResult(p *print.Printer, outputFormat string, async bool, region, networkAreaLabel string, regionalArea iaas.RegionalArea) error {
return p.OutputResult(outputFormat, regionalArea, func() error {
p.Outputf("Create region configuration for SNA %q.\nRegion: %s\n", networkAreaLabel, region)
operationState := "Created"
if async {
operationState = "Triggered creation of"
}
p.Outputf("%s region configuration for SNA %q.\nRegion: %s\n", operationState, networkAreaLabel, region)
return nil
})
}

@@ -9,2 +9,3 @@ package routingtable

rtList "github.com/stackitcloud/stackit-cli/internal/cmd/network-area/routingtable/list"
rtRoute "github.com/stackitcloud/stackit-cli/internal/cmd/network-area/routingtable/route"
rtUpdate "github.com/stackitcloud/stackit-cli/internal/cmd/network-area/routingtable/update"

@@ -38,3 +39,4 @@ "github.com/stackitcloud/stackit-cli/internal/pkg/args"

rtDelete.NewCmd(params),
rtRoute.NewCmd(params),
)
}

@@ -16,3 +16,3 @@ package create

"github.com/google/uuid"
"github.com/stackitcloud/stackit-sdk-go/services/objectstorage"
objectstorage "github.com/stackitcloud/stackit-sdk-go/services/objectstorage/v2api"
)

@@ -23,3 +23,3 @@

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

@@ -60,3 +60,4 @@

},
BucketName: testBucketName,
BucketName: testBucketName,
ObjectLockEnabled: false,
}

@@ -69,6 +70,6 @@ for _, mod := range mods {

func fixtureRequest(mods ...func(request *objectstorage.ApiCreateBucketRequest)) objectstorage.ApiCreateBucketRequest {
request := testClient.CreateBucket(testCtx, testProjectId, testRegion, testBucketName)
func fixtureRequest(mods ...func(request objectstorage.ApiCreateBucketRequest) objectstorage.ApiCreateBucketRequest) objectstorage.ApiCreateBucketRequest {
request := testClient.DefaultAPI.CreateBucket(testCtx, testProjectId, testRegion, testBucketName).ObjectLockEnabled(false)
for _, mod := range mods {
mod(&request)
request = mod(request)
}

@@ -141,2 +142,13 @@ return request

},
{
description: "enable object-lock",
argValues: fixtureArgValues(),
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[objectLockEnabledFlag] = "true"
}),
expectedModel: fixtureInputModel(func(model *inputModel) {
model.ObjectLockEnabled = true
}),
isValid: true,
},
}

@@ -162,2 +174,11 @@

},
{
description: "object-lock enabled",
model: fixtureInputModel(func(model *inputModel) {
model.ObjectLockEnabled = true
}),
expectedRequest: fixtureRequest(func(request objectstorage.ApiCreateBucketRequest) objectstorage.ApiCreateBucketRequest {
return request.ObjectLockEnabled(true)
}),
},
}

@@ -170,3 +191,3 @@

diff := cmp.Diff(request, tt.expectedRequest,
cmp.AllowUnexported(tt.expectedRequest),
cmp.AllowUnexported(tt.expectedRequest, objectstorage.DefaultAPIService{}),
cmpopts.EquateComparable(testCtx),

@@ -173,0 +194,0 @@ )

@@ -7,2 +7,3 @@ package create

"github.com/stackitcloud/stackit-cli/internal/pkg/flags"
"github.com/stackitcloud/stackit-cli/internal/pkg/types"

@@ -20,8 +21,9 @@

"github.com/spf13/cobra"
"github.com/stackitcloud/stackit-sdk-go/services/objectstorage"
"github.com/stackitcloud/stackit-sdk-go/services/objectstorage/wait"
objectstorage "github.com/stackitcloud/stackit-sdk-go/services/objectstorage/v2api"
"github.com/stackitcloud/stackit-sdk-go/services/objectstorage/v2api/wait"
)
const (
bucketNameArg = "BUCKET_NAME"
bucketNameArg = "BUCKET_NAME"
objectLockEnabledFlag = "object-lock-enabled"
)

@@ -31,3 +33,4 @@

*globalflags.GlobalFlagModel
BucketName string
BucketName string
ObjectLockEnabled bool
}

@@ -45,2 +48,5 @@

"$ stackit object-storage bucket create my-bucket"),
examples.NewExample(
`Create an Object Storage bucket with enabled object-lock`,
`$ stackit object-storage bucket create my-bucket --object-lock-enabled`),
),

@@ -67,3 +73,3 @@ RunE: func(cmd *cobra.Command, args []string) error {

// Check if the project is enabled before trying to create
enabled, err := utils.ProjectEnabled(ctx, apiClient, model.ProjectId, model.Region)
enabled, err := utils.ProjectEnabled(ctx, apiClient.DefaultAPI, model.ProjectId, model.Region)
if err != nil {

@@ -89,3 +95,3 @@ return fmt.Errorf("check if Object Storage is enabled: %w", err)

s.Start("Creating bucket")
_, err = wait.CreateBucketWaitHandler(ctx, apiClient, model.ProjectId, model.Region, model.BucketName).WaitWithContext(ctx)
_, err = wait.CreateBucketWaitHandler(ctx, apiClient.DefaultAPI, model.ProjectId, model.Region, model.BucketName).WaitWithContext(ctx)
if err != nil {

@@ -100,5 +106,10 @@ return fmt.Errorf("wait for Object Storage bucket creation: %w", err)

}
configureFlags(cmd)
return cmd
}
func configureFlags(cmd *cobra.Command) {
cmd.Flags().Bool(objectLockEnabledFlag, false, "is the object-lock enabled for the bucket")
}
func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inputModel, error) {

@@ -113,4 +124,5 @@ bucketName := inputArgs[0]

model := inputModel{
GlobalFlagModel: globalFlags,
BucketName: bucketName,
GlobalFlagModel: globalFlags,
BucketName: bucketName,
ObjectLockEnabled: flags.FlagToBoolValue(p, cmd, objectLockEnabledFlag),
}

@@ -123,3 +135,3 @@

func buildRequest(ctx context.Context, model *inputModel, apiClient *objectstorage.APIClient) objectstorage.ApiCreateBucketRequest {
req := apiClient.CreateBucket(ctx, model.ProjectId, model.Region, model.BucketName)
req := apiClient.DefaultAPI.CreateBucket(ctx, model.ProjectId, model.Region, model.BucketName).ObjectLockEnabled(model.ObjectLockEnabled)
return req

@@ -126,0 +138,0 @@ }

@@ -13,3 +13,3 @@ package delete

"github.com/google/uuid"
"github.com/stackitcloud/stackit-sdk-go/services/objectstorage"
objectstorage "github.com/stackitcloud/stackit-sdk-go/services/objectstorage/v2api"
)

@@ -20,3 +20,3 @@

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

@@ -66,3 +66,3 @@

func fixtureRequest(mods ...func(request *objectstorage.ApiDeleteBucketRequest)) objectstorage.ApiDeleteBucketRequest {
request := testClient.DeleteBucket(testCtx, testProjectId, testRegion, testBucketName)
request := testClient.DefaultAPI.DeleteBucket(testCtx, testProjectId, testRegion, testBucketName)
for _, mod := range mods {

@@ -164,3 +164,3 @@ mod(&request)

diff := cmp.Diff(request, tt.expectedRequest,
cmp.AllowUnexported(tt.expectedRequest),
cmp.AllowUnexported(tt.expectedRequest, objectstorage.DefaultAPIService{}),
cmpopts.EquateComparable(testCtx),

@@ -167,0 +167,0 @@ )

@@ -18,4 +18,4 @@ package delete

"github.com/spf13/cobra"
"github.com/stackitcloud/stackit-sdk-go/services/objectstorage"
"github.com/stackitcloud/stackit-sdk-go/services/objectstorage/wait"
objectstorage "github.com/stackitcloud/stackit-sdk-go/services/objectstorage/v2api"
"github.com/stackitcloud/stackit-sdk-go/services/objectstorage/v2api/wait"
)

@@ -73,3 +73,3 @@

s.Start("Deleting bucket")
_, err = wait.DeleteBucketWaitHandler(ctx, apiClient, model.ProjectId, model.Region, model.BucketName).WaitWithContext(ctx)
_, err = wait.DeleteBucketWaitHandler(ctx, apiClient.DefaultAPI, model.ProjectId, model.Region, model.BucketName).WaitWithContext(ctx)
if err != nil {

@@ -110,4 +110,4 @@ return fmt.Errorf("wait for Object Storage bucket deletion: %w", err)

func buildRequest(ctx context.Context, model *inputModel, apiClient *objectstorage.APIClient) objectstorage.ApiDeleteBucketRequest {
req := apiClient.DeleteBucket(ctx, model.ProjectId, model.Region, model.BucketName)
req := apiClient.DefaultAPI.DeleteBucket(ctx, model.ProjectId, model.Region, model.BucketName)
return req
}

@@ -16,3 +16,3 @@ package describe

"github.com/google/uuid"
"github.com/stackitcloud/stackit-sdk-go/services/objectstorage"
objectstorage "github.com/stackitcloud/stackit-sdk-go/services/objectstorage/v2api"
)

@@ -23,3 +23,3 @@

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

@@ -69,3 +69,3 @@

func fixtureRequest(mods ...func(request *objectstorage.ApiGetBucketRequest)) objectstorage.ApiGetBucketRequest {
request := testClient.GetBucket(testCtx, testProjectId, testRegion, testBucketName)
request := testClient.DefaultAPI.GetBucket(testCtx, testProjectId, testRegion, testBucketName)
for _, mod := range mods {

@@ -167,3 +167,3 @@ mod(&request)

diff := cmp.Diff(request, tt.expectedRequest,
cmp.AllowUnexported(tt.expectedRequest),
cmp.AllowUnexported(tt.expectedRequest, objectstorage.DefaultAPIService{}),
cmpopts.EquateComparable(testCtx),

@@ -181,3 +181,3 @@ )

outputFormat string
bucket *objectstorage.Bucket
resp *objectstorage.GetBucketResponse
}

@@ -195,5 +195,5 @@ tests := []struct {

{
name: "set empty bucket",
name: "set empty response",
args: args{
bucket: &objectstorage.Bucket{},
resp: &objectstorage.GetBucketResponse{},
},

@@ -207,3 +207,3 @@ wantErr: false,

t.Run(tt.name, func(t *testing.T) {
if err := outputResult(p, tt.args.outputFormat, tt.args.bucket); (err != nil) != tt.wantErr {
if err := outputResult(p, tt.args.outputFormat, tt.args.resp); (err != nil) != tt.wantErr {
t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr)

@@ -210,0 +210,0 @@ }

@@ -16,6 +16,5 @@ package describe

"github.com/stackitcloud/stackit-cli/internal/pkg/tables"
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
"github.com/spf13/cobra"
"github.com/stackitcloud/stackit-sdk-go/services/objectstorage"
objectstorage "github.com/stackitcloud/stackit-sdk-go/services/objectstorage/v2api"
)

@@ -65,3 +64,3 @@

return outputResult(params.Printer, model.OutputFormat, resp.Bucket)
return outputResult(params.Printer, model.OutputFormat, resp)
},

@@ -90,21 +89,23 @@ }

func buildRequest(ctx context.Context, model *inputModel, apiClient *objectstorage.APIClient) objectstorage.ApiGetBucketRequest {
req := apiClient.GetBucket(ctx, model.ProjectId, model.Region, model.BucketName)
req := apiClient.DefaultAPI.GetBucket(ctx, model.ProjectId, model.Region, model.BucketName)
return req
}
func outputResult(p *print.Printer, outputFormat string, bucket *objectstorage.Bucket) error {
if bucket == nil {
return fmt.Errorf("bucket is empty")
func outputResult(p *print.Printer, outputFormat string, resp *objectstorage.GetBucketResponse) error {
if resp == nil {
return fmt.Errorf("response is nil")
}
return p.OutputResult(outputFormat, bucket, func() error {
return p.OutputResult(outputFormat, resp.Bucket, func() error {
table := tables.NewTable()
table.AddRow("Name", utils.PtrString(bucket.Name))
table.AddRow("Name", resp.Bucket.Name)
table.AddSeparator()
table.AddRow("Region", utils.PtrString(bucket.Region))
table.AddRow("Region", resp.Bucket.Region)
table.AddSeparator()
table.AddRow("URL (Path Style)", utils.PtrString(bucket.UrlPathStyle))
table.AddRow("URL (Path Style)", resp.Bucket.UrlPathStyle)
table.AddSeparator()
table.AddRow("URL (Virtual Hosted Style)", utils.PtrString(bucket.UrlVirtualHostedStyle))
table.AddRow("URL (Virtual Hosted Style)", resp.Bucket.UrlVirtualHostedStyle)
table.AddSeparator()
table.AddRow("Object Lock Enabled", resp.Bucket.ObjectLockEnabled)
table.AddSeparator()
err := table.Display(p)

@@ -111,0 +112,0 @@ if err != nil {

@@ -17,3 +17,3 @@ package list

"github.com/google/uuid"
"github.com/stackitcloud/stackit-sdk-go/services/objectstorage"
objectstorage "github.com/stackitcloud/stackit-sdk-go/services/objectstorage/v2api"
)

@@ -24,3 +24,3 @@

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

@@ -57,3 +57,3 @@ var testRegion = "eu01"

func fixtureRequest(mods ...func(request *objectstorage.ApiListBucketsRequest)) objectstorage.ApiListBucketsRequest {
request := testClient.ListBuckets(testCtx, testProjectId, testRegion)
request := testClient.DefaultAPI.ListBuckets(testCtx, testProjectId, testRegion)
for _, mod := range mods {

@@ -146,3 +146,3 @@ mod(&request)

diff := cmp.Diff(request, tt.expectedRequest,
cmp.AllowUnexported(tt.expectedRequest),
cmp.AllowUnexported(tt.expectedRequest, objectstorage.DefaultAPIService{}),
cmpopts.EquateComparable(testCtx),

@@ -149,0 +149,0 @@ )

@@ -19,4 +19,3 @@ package list

"github.com/stackitcloud/stackit-cli/internal/pkg/tables"
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
"github.com/stackitcloud/stackit-sdk-go/services/objectstorage"
objectstorage "github.com/stackitcloud/stackit-sdk-go/services/objectstorage/v2api"
)

@@ -118,3 +117,3 @@

func buildRequest(ctx context.Context, model *inputModel, apiClient *objectstorage.APIClient) objectstorage.ApiListBucketsRequest {
req := apiClient.ListBuckets(ctx, model.ProjectId, model.Region)
req := apiClient.DefaultAPI.ListBuckets(ctx, model.ProjectId, model.Region)
return req

@@ -135,10 +134,11 @@ }

table := tables.NewTable()
table.SetHeader("NAME", "REGION", "URL (PATH STYLE)", "URL (VIRTUAL HOSTED STYLE)")
table.SetHeader("NAME", "REGION", "URL (PATH STYLE)", "URL (VIRTUAL HOSTED STYLE)", "OBJECT LOCK ENABLED")
for i := range buckets {
bucket := buckets[i]
table.AddRow(
utils.PtrString(bucket.Name),
utils.PtrString(bucket.Region),
utils.PtrString(bucket.UrlPathStyle),
utils.PtrString(bucket.UrlVirtualHostedStyle),
bucket.Name,
bucket.Region,
bucket.UrlPathStyle,
bucket.UrlVirtualHostedStyle,
bucket.ObjectLockEnabled,
)

@@ -145,0 +145,0 @@ }

@@ -9,11 +9,9 @@ package create

"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/google/uuid"
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
"github.com/stackitcloud/stackit-cli/internal/pkg/testutils"
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/google/uuid"
"github.com/stackitcloud/stackit-sdk-go/services/objectstorage"
objectstorage "github.com/stackitcloud/stackit-sdk-go/services/objectstorage/v2api"
)

@@ -24,3 +22,3 @@

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

@@ -62,3 +60,3 @@

payload := objectstorage.CreateCredentialsGroupPayload{
DisplayName: utils.Ptr(testCredentialsGroupName),
DisplayName: testCredentialsGroupName,
}

@@ -72,3 +70,3 @@ for _, mod := range mods {

func fixtureRequest(mods ...func(request *objectstorage.ApiCreateCredentialsGroupRequest)) objectstorage.ApiCreateCredentialsGroupRequest {
request := testClient.CreateCredentialsGroup(testCtx, testProjectId, testRegion)
request := testClient.DefaultAPI.CreateCredentialsGroup(testCtx, testProjectId, testRegion)
request = request.CreateCredentialsGroupPayload(fixturePayload())

@@ -155,3 +153,3 @@ for _, mod := range mods {

diff := cmp.Diff(request, tt.expectedRequest,
cmp.AllowUnexported(tt.expectedRequest),
cmp.AllowUnexported(tt.expectedRequest, objectstorage.DefaultAPIService{}),
cmpopts.EquateComparable(testCtx),

@@ -186,3 +184,3 @@ )

},
wantErr: true,
wantErr: false,
},

@@ -193,3 +191,3 @@ {

createCredentialsGroupResponse: &objectstorage.CreateCredentialsGroupResponse{
CredentialsGroup: &objectstorage.CredentialsGroup{},
CredentialsGroup: objectstorage.CredentialsGroup{},
},

@@ -196,0 +194,0 @@ },

@@ -9,2 +9,3 @@ package create

"github.com/spf13/cobra"
"github.com/stackitcloud/stackit-cli/internal/pkg/args"

@@ -17,6 +18,3 @@ "github.com/stackitcloud/stackit-cli/internal/pkg/errors"

"github.com/stackitcloud/stackit-cli/internal/pkg/services/object-storage/client"
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
"github.com/spf13/cobra"
"github.com/stackitcloud/stackit-sdk-go/services/objectstorage"
objectstorage "github.com/stackitcloud/stackit-sdk-go/services/objectstorage/v2api"
)

@@ -100,5 +98,5 @@

func buildRequest(ctx context.Context, model *inputModel, apiClient *objectstorage.APIClient) objectstorage.ApiCreateCredentialsGroupRequest {
req := apiClient.CreateCredentialsGroup(ctx, model.ProjectId, model.Region)
req := apiClient.DefaultAPI.CreateCredentialsGroup(ctx, model.ProjectId, model.Region)
req = req.CreateCredentialsGroupPayload(objectstorage.CreateCredentialsGroupPayload{
DisplayName: utils.Ptr(model.CredentialsGroupName),
DisplayName: model.CredentialsGroupName,
})

@@ -109,14 +107,14 @@ return req

func outputResult(p *print.Printer, outputFormat string, resp *objectstorage.CreateCredentialsGroupResponse) error {
if resp == nil || resp.CredentialsGroup == nil {
return fmt.Errorf("create createndials group response is empty")
}
return p.OutputResult(outputFormat, resp, func() error {
if resp == nil {
return fmt.Errorf("create credentials group response is empty")
}
return p.OutputResult(outputFormat, resp, func() error {
p.Outputf("Created credentials group %q. Credentials group ID: %s\n\n",
utils.PtrString(resp.CredentialsGroup.DisplayName),
utils.PtrString(resp.CredentialsGroup.CredentialsGroupId),
resp.CredentialsGroup.DisplayName,
resp.CredentialsGroup.CredentialsGroupId,
)
p.Outputf("URN: %s\n", utils.PtrString(resp.CredentialsGroup.Urn))
p.Outputf("URN: %s\n", resp.CredentialsGroup.Urn)
return nil
})
}

@@ -13,3 +13,3 @@ package delete

"github.com/google/uuid"
"github.com/stackitcloud/stackit-sdk-go/services/objectstorage"
objectstorage "github.com/stackitcloud/stackit-sdk-go/services/objectstorage/v2api"
)

@@ -20,3 +20,3 @@

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

@@ -64,3 +64,3 @@ var testCredentialsGroupId = uuid.NewString()

func fixtureRequest(mods ...func(request *objectstorage.ApiDeleteCredentialsGroupRequest)) objectstorage.ApiDeleteCredentialsGroupRequest {
request := testClient.DeleteCredentialsGroup(testCtx, testProjectId, testRegion, testCredentialsGroupId)
request := testClient.DefaultAPI.DeleteCredentialsGroup(testCtx, testProjectId, testRegion, testCredentialsGroupId)
for _, mod := range mods {

@@ -168,3 +168,3 @@ mod(&request)

diff := cmp.Diff(request, tt.expectedRequest,
cmp.AllowUnexported(tt.expectedRequest),
cmp.AllowUnexported(tt.expectedRequest, objectstorage.DefaultAPIService{}),
cmpopts.EquateComparable(testCtx),

@@ -171,0 +171,0 @@ )

@@ -19,3 +19,3 @@ package delete

"github.com/spf13/cobra"
"github.com/stackitcloud/stackit-sdk-go/services/objectstorage"
objectstorage "github.com/stackitcloud/stackit-sdk-go/services/objectstorage/v2api"
)

@@ -56,3 +56,3 @@

credentialsGroupLabel, err := objectStorageUtils.GetCredentialsGroupName(ctx, apiClient, model.ProjectId, model.CredentialsGroupId, model.Region)
credentialsGroupLabel, err := objectStorageUtils.GetCredentialsGroupName(ctx, apiClient.DefaultAPI, model.ProjectId, model.CredentialsGroupId, model.Region)
if err != nil {

@@ -101,4 +101,4 @@ params.Printer.Debug(print.ErrorLevel, "get credentials group name: %v", err)

func buildRequest(ctx context.Context, model *inputModel, apiClient *objectstorage.APIClient) objectstorage.ApiDeleteCredentialsGroupRequest {
req := apiClient.DeleteCredentialsGroup(ctx, model.ProjectId, model.Region, model.CredentialsGroupId)
req := apiClient.DefaultAPI.DeleteCredentialsGroup(ctx, model.ProjectId, model.Region, model.CredentialsGroupId)
return req
}

@@ -17,3 +17,3 @@ package list

"github.com/google/uuid"
"github.com/stackitcloud/stackit-sdk-go/services/objectstorage"
objectstorage "github.com/stackitcloud/stackit-sdk-go/services/objectstorage/v2api"
)

@@ -24,3 +24,3 @@

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

@@ -58,3 +58,3 @@

func fixtureRequest(mods ...func(request *objectstorage.ApiListCredentialsGroupsRequest)) objectstorage.ApiListCredentialsGroupsRequest {
request := testClient.ListCredentialsGroups(testCtx, testProjectId, testRegion)
request := testClient.DefaultAPI.ListCredentialsGroups(testCtx, testProjectId, testRegion)
for _, mod := range mods {

@@ -147,3 +147,3 @@ mod(&request)

diff := cmp.Diff(request, tt.expectedRequest,
cmp.AllowUnexported(tt.expectedRequest),
cmp.AllowUnexported(tt.expectedRequest, objectstorage.DefaultAPIService{}),
cmpopts.EquateComparable(testCtx),

@@ -150,0 +150,0 @@ )

@@ -18,4 +18,3 @@ package list

"github.com/stackitcloud/stackit-cli/internal/pkg/tables"
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
"github.com/stackitcloud/stackit-sdk-go/services/objectstorage"
objectstorage "github.com/stackitcloud/stackit-sdk-go/services/objectstorage/v2api"
)

@@ -109,3 +108,3 @@

func buildRequest(ctx context.Context, model *inputModel, apiClient *objectstorage.APIClient) objectstorage.ApiListCredentialsGroupsRequest {
req := apiClient.ListCredentialsGroups(ctx, model.ProjectId, model.Region)
req := apiClient.DefaultAPI.ListCredentialsGroups(ctx, model.ProjectId, model.Region)
return req

@@ -126,5 +125,5 @@ }

table.AddRow(
utils.PtrString(c.CredentialsGroupId),
utils.PtrString(c.DisplayName),
utils.PtrString(c.Urn),
c.CredentialsGroupId,
c.DisplayName,
c.Urn,
)

@@ -131,0 +130,0 @@ }

@@ -18,3 +18,3 @@ package create

"github.com/google/uuid"
"github.com/stackitcloud/stackit-sdk-go/services/objectstorage"
objectstorage "github.com/stackitcloud/stackit-sdk-go/services/objectstorage/v2api"
)

@@ -25,3 +25,3 @@

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

@@ -84,3 +84,3 @@ var testCredentialsGroupId = uuid.NewString()

func fixtureRequest(mods ...func(request *objectstorage.ApiCreateAccessKeyRequest)) objectstorage.ApiCreateAccessKeyRequest {
request := testClient.CreateAccessKey(testCtx, testProjectId, testRegion)
request := testClient.DefaultAPI.CreateAccessKey(testCtx, testProjectId, testRegion)
request = request.CreateAccessKeyPayload(fixturePayload())

@@ -213,3 +213,3 @@ request = request.CredentialsGroup(testCredentialsGroupId)

diff := cmp.Diff(request, tt.expectedRequest,
cmp.AllowUnexported(tt.expectedRequest),
cmp.AllowUnexported(tt.expectedRequest, objectstorage.DefaultAPIService{}),
cmpopts.EquateComparable(testCtx),

@@ -216,0 +216,0 @@ )

@@ -19,4 +19,3 @@ package create

objectStorageUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/object-storage/utils"
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
"github.com/stackitcloud/stackit-sdk-go/services/objectstorage"
objectstorage "github.com/stackitcloud/stackit-sdk-go/services/objectstorage/v2api"
)

@@ -64,3 +63,3 @@

credentialsGroupLabel, err := objectStorageUtils.GetCredentialsGroupName(ctx, apiClient, model.ProjectId, model.CredentialsGroupId, model.Region)
credentialsGroupLabel, err := objectStorageUtils.GetCredentialsGroupName(ctx, apiClient.DefaultAPI, model.ProjectId, model.CredentialsGroupId, model.Region)
if err != nil {

@@ -124,3 +123,3 @@ params.Printer.Debug(print.ErrorLevel, "get credentials group name: %v", err)

func buildRequest(ctx context.Context, model *inputModel, apiClient *objectstorage.APIClient) objectstorage.ApiCreateAccessKeyRequest {
req := apiClient.CreateAccessKey(ctx, model.ProjectId, model.Region)
req := apiClient.DefaultAPI.CreateAccessKey(ctx, model.ProjectId, model.Region)
req = req.CredentialsGroup(model.CredentialsGroupId)

@@ -140,9 +139,9 @@ req = req.CreateAccessKeyPayload(objectstorage.CreateAccessKeyPayload{

expireDate := "Never"
if resp.Expires != nil && resp.Expires.IsSet() && *resp.Expires.Get() != "" {
if resp.Expires.IsSet() && *resp.Expires.Get() != "" {
expireDate = *resp.Expires.Get()
}
p.Outputf("Created credentials in group %q. Credentials ID: %s\n\n", credentialsGroupLabel, utils.PtrString(resp.KeyId))
p.Outputf("Access Key ID: %s\n", utils.PtrString(resp.AccessKey))
p.Outputf("Secret Access Key: %s\n", utils.PtrString(resp.SecretAccessKey))
p.Outputf("Created credentials in group %q. Credentials ID: %s\n\n", credentialsGroupLabel, resp.KeyId)
p.Outputf("Access Key ID: %s\n", resp.AccessKey)
p.Outputf("Secret Access Key: %s\n", resp.SecretAccessKey)
p.Outputf("Expire Date: %s\n", expireDate)

@@ -149,0 +148,0 @@

@@ -13,3 +13,3 @@ package delete

"github.com/google/uuid"
"github.com/stackitcloud/stackit-sdk-go/services/objectstorage"
objectstorage "github.com/stackitcloud/stackit-sdk-go/services/objectstorage/v2api"
)

@@ -20,3 +20,3 @@

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

@@ -69,3 +69,3 @@ var testCredentialsGroupId = uuid.NewString()

func fixtureRequest(mods ...func(request *objectstorage.ApiDeleteAccessKeyRequest)) objectstorage.ApiDeleteAccessKeyRequest {
request := testClient.DeleteAccessKey(testCtx, testProjectId, testRegion, testCredentialsId)
request := testClient.DefaultAPI.DeleteAccessKey(testCtx, testProjectId, testRegion, testCredentialsId)
request = request.CredentialsGroup(testCredentialsGroupId)

@@ -189,3 +189,3 @@ for _, mod := range mods {

diff := cmp.Diff(request, tt.expectedRequest,
cmp.AllowUnexported(tt.expectedRequest),
cmp.AllowUnexported(tt.expectedRequest, objectstorage.DefaultAPIService{}),
cmpopts.EquateComparable(testCtx),

@@ -192,0 +192,0 @@ )

@@ -18,3 +18,3 @@ package delete

objectStorageUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/object-storage/utils"
"github.com/stackitcloud/stackit-sdk-go/services/objectstorage"
objectstorage "github.com/stackitcloud/stackit-sdk-go/services/objectstorage/v2api"
)

@@ -57,3 +57,3 @@

credentialsGroupLabel, err := objectStorageUtils.GetCredentialsGroupName(ctx, apiClient, model.ProjectId, model.CredentialsGroupId, model.Region)
credentialsGroupLabel, err := objectStorageUtils.GetCredentialsGroupName(ctx, apiClient.DefaultAPI, model.ProjectId, model.CredentialsGroupId, model.Region)
if err != nil {

@@ -64,3 +64,3 @@ params.Printer.Debug(print.ErrorLevel, "get credentials group name: %v", err)

credentialsLabel, err := objectStorageUtils.GetCredentialsName(ctx, apiClient, model.ProjectId, model.CredentialsGroupId, model.CredentialsId, model.Region)
credentialsLabel, err := objectStorageUtils.GetCredentialsName(ctx, apiClient.DefaultAPI, model.ProjectId, model.CredentialsGroupId, model.CredentialsId, model.Region)
if err != nil {

@@ -118,5 +118,5 @@ params.Printer.Debug(print.ErrorLevel, "get credentials name: %v", err)

func buildRequest(ctx context.Context, model *inputModel, apiClient *objectstorage.APIClient) objectstorage.ApiDeleteAccessKeyRequest {
req := apiClient.DeleteAccessKey(ctx, model.ProjectId, model.Region, model.CredentialsId)
req := apiClient.DefaultAPI.DeleteAccessKey(ctx, model.ProjectId, model.Region, model.CredentialsId)
req = req.CredentialsGroup(model.CredentialsGroupId)
return req
}

@@ -17,3 +17,3 @@ package list

"github.com/google/uuid"
"github.com/stackitcloud/stackit-sdk-go/services/objectstorage"
objectstorage "github.com/stackitcloud/stackit-sdk-go/services/objectstorage/v2api"
)

@@ -24,3 +24,3 @@

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

@@ -60,3 +60,3 @@ var testCredentialsGroupId = uuid.NewString()

func fixtureRequest(mods ...func(request *objectstorage.ApiListAccessKeysRequest)) objectstorage.ApiListAccessKeysRequest {
request := testClient.ListAccessKeys(testCtx, testProjectId, testRegion)
request := testClient.DefaultAPI.ListAccessKeys(testCtx, testProjectId, testRegion)
request = request.CredentialsGroup(testCredentialsGroupId)

@@ -171,3 +171,3 @@ for _, mod := range mods {

diff := cmp.Diff(request, tt.expectedRequest,
cmp.AllowUnexported(tt.expectedRequest),
cmp.AllowUnexported(tt.expectedRequest, objectstorage.DefaultAPIService{}),
cmpopts.EquateComparable(testCtx),

@@ -174,0 +174,0 @@ )

@@ -19,4 +19,3 @@ package list

"github.com/stackitcloud/stackit-cli/internal/pkg/tables"
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
"github.com/stackitcloud/stackit-sdk-go/services/objectstorage"
objectstorage "github.com/stackitcloud/stackit-sdk-go/services/objectstorage/v2api"
)

@@ -73,3 +72,3 @@

credentialsGroupLabel, err := objectStorageUtils.GetCredentialsGroupName(ctx, apiClient, model.ProjectId, model.CredentialsGroupId, model.Region)
credentialsGroupLabel, err := objectStorageUtils.GetCredentialsGroupName(ctx, apiClient.DefaultAPI, model.ProjectId, model.CredentialsGroupId, model.Region)
if err != nil {

@@ -124,3 +123,3 @@ params.Printer.Debug(print.ErrorLevel, "get credentials group name: %v", err)

func buildRequest(ctx context.Context, model *inputModel, apiClient *objectstorage.APIClient) objectstorage.ApiListAccessKeysRequest {
req := apiClient.ListAccessKeys(ctx, model.ProjectId, model.Region)
req := apiClient.DefaultAPI.ListAccessKeys(ctx, model.ProjectId, model.Region)
req = req.CredentialsGroup(model.CredentialsGroupId)

@@ -142,4 +141,7 @@ return req

expiresAt := utils.PtrStringDefault(c.Expires, "Never")
table.AddRow(utils.PtrString(c.KeyId), utils.PtrString(c.DisplayName), expiresAt)
expiresAt := "Never"
if c.Expires != "" {
expiresAt = c.Expires
}
table.AddRow(c.KeyId, c.DisplayName, expiresAt)
}

@@ -146,0 +148,0 @@ err := table.Display(p)

@@ -13,3 +13,3 @@ package disable

"github.com/google/uuid"
"github.com/stackitcloud/stackit-sdk-go/services/objectstorage"
objectstorage "github.com/stackitcloud/stackit-sdk-go/services/objectstorage/v2api"
)

@@ -20,3 +20,3 @@

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

@@ -52,3 +52,3 @@

func fixtureRequest(mods ...func(request *objectstorage.ApiDisableServiceRequest)) objectstorage.ApiDisableServiceRequest {
request := testClient.DisableService(testCtx, testProjectId, testRegion)
request := testClient.DefaultAPI.DisableService(testCtx, testProjectId, testRegion)
for _, mod := range mods {

@@ -127,3 +127,3 @@ mod(&request)

diff := cmp.Diff(request, tt.expectedRequest,
cmp.AllowUnexported(tt.expectedRequest),
cmp.AllowUnexported(tt.expectedRequest, objectstorage.DefaultAPIService{}),
cmpopts.EquateComparable(testCtx),

@@ -130,0 +130,0 @@ )

@@ -18,3 +18,3 @@ package disable

"github.com/spf13/cobra"
"github.com/stackitcloud/stackit-sdk-go/services/objectstorage"
objectstorage "github.com/stackitcloud/stackit-sdk-go/services/objectstorage/v2api"
)

@@ -95,4 +95,4 @@

func buildRequest(ctx context.Context, model *inputModel, apiClient *objectstorage.APIClient) objectstorage.ApiDisableServiceRequest {
req := apiClient.DisableService(ctx, model.ProjectId, model.Region)
req := apiClient.DefaultAPI.DisableService(ctx, model.ProjectId, model.Region)
return req
}

@@ -13,3 +13,3 @@ package enable

"github.com/google/uuid"
"github.com/stackitcloud/stackit-sdk-go/services/objectstorage"
objectstorage "github.com/stackitcloud/stackit-sdk-go/services/objectstorage/v2api"
)

@@ -20,3 +20,3 @@

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

@@ -52,3 +52,3 @@

func fixtureRequest(mods ...func(request *objectstorage.ApiEnableServiceRequest)) objectstorage.ApiEnableServiceRequest {
request := testClient.EnableService(testCtx, testProjectId, testRegion)
request := testClient.DefaultAPI.EnableService(testCtx, testProjectId, testRegion)
for _, mod := range mods {

@@ -127,3 +127,3 @@ mod(&request)

diff := cmp.Diff(request, tt.expectedRequest,
cmp.AllowUnexported(tt.expectedRequest),
cmp.AllowUnexported(tt.expectedRequest, objectstorage.DefaultAPIService{}),
cmpopts.EquateComparable(testCtx),

@@ -130,0 +130,0 @@ )

@@ -18,3 +18,3 @@ package enable

"github.com/spf13/cobra"
"github.com/stackitcloud/stackit-sdk-go/services/objectstorage"
objectstorage "github.com/stackitcloud/stackit-sdk-go/services/objectstorage/v2api"
)

@@ -95,4 +95,4 @@

func buildRequest(ctx context.Context, model *inputModel, apiClient *objectstorage.APIClient) objectstorage.ApiEnableServiceRequest {
req := apiClient.EnableService(ctx, model.ProjectId, model.Region)
req := apiClient.DefaultAPI.EnableService(ctx, model.ProjectId, model.Region)
return req
}

@@ -8,2 +8,3 @@ package describe

"github.com/stackitcloud/stackit-cli/internal/pkg/types"
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"

@@ -224,2 +225,31 @@ "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"

},
{
name: "host and hosts",
args: args{
credentials: &opensearch.CredentialsResponse{
Raw: &opensearch.RawCredentials{
Credentials: &opensearch.Credentials{
Host: utils.Ptr("host"),
Hosts: utils.Ptr([]string{
"hosts-a",
"hosts-b",
}),
},
},
},
},
},
{
name: "raw credentials nil host & hosts",
args: args{
credentials: &opensearch.CredentialsResponse{
Raw: &opensearch.RawCredentials{
Credentials: &opensearch.Credentials{
Host: nil,
Hosts: nil,
},
},
},
},
},
}

@@ -226,0 +256,0 @@ p := print.NewPrinter()

@@ -6,2 +6,3 @@ package describe

"fmt"
"strings"

@@ -125,2 +126,9 @@ "github.com/stackitcloud/stackit-cli/internal/pkg/types"

table.AddRow("URI", utils.PtrString(credentials.Raw.Credentials.Uri))
table.AddSeparator()
table.AddRow("HOST", utils.PtrString(credentials.Raw.Credentials.Host))
hosts := credentials.Raw.Credentials.Hosts
if hosts != nil && len(*hosts) > 0 {
table.AddSeparator()
table.AddRow("HOSTS", strings.Join(*hosts, "\n"))
}
}

@@ -127,0 +135,0 @@ err := table.Display(p)

@@ -385,2 +385,3 @@ package create

outputFormat string
async bool
projectLabel string

@@ -411,3 +412,3 @@ server *iaas.Server

t.Run(tt.name, func(t *testing.T) {
if err := outputResult(p, tt.args.outputFormat, tt.args.projectLabel, tt.args.server); (err != nil) != tt.wantErr {
if err := outputResult(p, tt.args.outputFormat, tt.args.async, tt.args.projectLabel, tt.args.server); (err != nil) != tt.wantErr {
t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr)

@@ -414,0 +415,0 @@ }

@@ -156,3 +156,3 @@ package create

return outputResult(params.Printer, model.OutputFormat, projectLabel, resp)
return outputResult(params.Printer, model.OutputFormat, model.Async, projectLabel, resp)
},

@@ -326,3 +326,3 @@ }

func outputResult(p *print.Printer, outputFormat, projectLabel string, server *iaas.Server) error {
func outputResult(p *print.Printer, outputFormat string, async bool, projectLabel string, server *iaas.Server) error {
if server == nil {

@@ -332,5 +332,9 @@ return fmt.Errorf("server response is empty")

return p.OutputResult(outputFormat, server, func() error {
p.Outputf("Created server for project %q.\nServer ID: %s\n", projectLabel, utils.PtrString(server.Id))
operationState := "Created"
if async {
operationState = "Triggered creation of"
}
p.Outputf("%s server for project %q.\nServer ID: %s\n", operationState, projectLabel, utils.PtrString(server.Id))
return nil
})
}

@@ -20,2 +20,3 @@ package server

"github.com/stackitcloud/stackit-cli/internal/cmd/server/resize"
securitygroup "github.com/stackitcloud/stackit-cli/internal/cmd/server/security-group"
serviceaccount "github.com/stackitcloud/stackit-cli/internal/cmd/server/service-account"

@@ -55,2 +56,3 @@ "github.com/stackitcloud/stackit-cli/internal/cmd/server/start"

cmd.AddCommand(publicip.NewCmd(params))
cmd.AddCommand(securitygroup.NewCmd(params))
cmd.AddCommand(serviceaccount.NewCmd(params))

@@ -57,0 +59,0 @@ cmd.AddCommand(update.NewCmd(params))

@@ -66,3 +66,3 @@ package detach

prompt := fmt.Sprintf("Are your sure you want to detach service account %q from a server %q?", model.ServiceAccMail, serverLabel)
prompt := fmt.Sprintf("Are you sure you want to detach service account %q from a server %q?", model.ServiceAccMail, serverLabel)
err = params.Printer.PromptForConfirmation(prompt)

@@ -69,0 +69,0 @@ if err != nil {

@@ -85,2 +85,3 @@ package generatepayload

payload = &ske.CreateOrUpdateClusterPayload{
Access: resp.Access,
Extensions: resp.Extensions,

@@ -90,2 +91,3 @@ Hibernation: resp.Hibernation,

Maintenance: resp.Maintenance,
Network: resp.Network,
Nodepools: resp.Nodepools,

@@ -92,0 +94,0 @@ Status: resp.Status,

@@ -187,5 +187,9 @@ package create

return p.OutputResult(model.OutputFormat, volume, func() error {
p.Outputf("Created volume for project %q.\nVolume ID: %s\n", projectLabel, utils.PtrString(volume.Id))
operationState := "Created"
if model.Async {
operationState = "Triggered creation of"
}
p.Outputf("%s volume for project %q.\nVolume ID: %s\n", operationState, projectLabel, utils.PtrString(volume.Id))
return nil
})
}

@@ -88,2 +88,4 @@ package auth

var listenerErr error
var ipv6Listener net.Listener
var ipv6ListenerErr error
var port int

@@ -98,6 +100,12 @@ startingPort := defaultPort

port = startingPort + i
portString := fmt.Sprintf(":%s", strconv.Itoa(port))
ipv4addr := fmt.Sprintf("127.0.0.1:%d", port)
ipv6addr := fmt.Sprintf("[::1]:%d", port)
p.Debug(print.DebugLevel, "trying to bind port %d for login redirect", port)
listener, listenerErr = net.Listen("tcp", portString)
ipv6Listener, ipv6ListenerErr = net.Listen("tcp6", ipv6addr)
if ipv6ListenerErr != nil {
continue
}
listener, listenerErr = net.Listen("tcp4", ipv4addr)
if listenerErr == nil {
_ = ipv6Listener.Close()
redirectURL = fmt.Sprintf("http://localhost:%d", port)

@@ -109,4 +117,7 @@ p.Debug(print.DebugLevel, "bound port %d for login redirect", port)

}
if ipv6ListenerErr != nil {
return fmt.Errorf("unable to bind port for login redirect, tried from port %d to %d: %w", startingPort, port, ipv6ListenerErr)
}
if listenerErr != nil {
return fmt.Errorf("unable to bind port for login redirect, tried from port %d to %d: %w", defaultPort, port, listenerErr)
return fmt.Errorf("unable to bind port for login redirect, tried from port %d to %d: %w", startingPort, port, listenerErr)
}

@@ -113,0 +124,0 @@

@@ -9,7 +9,7 @@ package client

"github.com/spf13/viper"
"github.com/stackitcloud/stackit-sdk-go/services/objectstorage"
objectstorage "github.com/stackitcloud/stackit-sdk-go/services/objectstorage/v2api"
)
func ConfigureClient(p *print.Printer, cliVersion string) (*objectstorage.APIClient, error) {
return genericclient.ConfigureClientGeneric(p, cliVersion, viper.GetString(config.ObjectStorageCustomEndpointKey), true, genericclient.CreateApiClient[*objectstorage.APIClient](objectstorage.NewAPIClient))
return genericclient.ConfigureClientGeneric(p, cliVersion, viper.GetString(config.ObjectStorageCustomEndpointKey), false, genericclient.CreateApiClient[*objectstorage.APIClient](objectstorage.NewAPIClient))
}

@@ -5,7 +5,5 @@ package utils

"context"
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"testing"

@@ -15,5 +13,4 @@

"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
"github.com/stackitcloud/stackit-sdk-go/core/config"
"github.com/stackitcloud/stackit-sdk-go/core/oapierror"
"github.com/stackitcloud/stackit-sdk-go/services/objectstorage"
objectstorage "github.com/stackitcloud/stackit-sdk-go/services/objectstorage/v2api"
)

@@ -24,4 +21,2 @@

testCredentialsGroupId = uuid.NewString()
testCredentialsId = "credentialsID" //nolint:gosec // linter false positive
testRegion = "eu01"
)

@@ -32,33 +27,47 @@

testCredentialsName = "testCredential"
testCredentialsId = "credentialsID" //nolint:gosec // linter false positive
testRegion = "eu01"
)
type objectStorageClientMocked struct {
serviceDisabled bool
getServiceStatusFails bool
type mockSettings struct {
serviceDisabled bool
getServiceStatusFails bool
listCredentialsGroupsFails bool
listCredentialsGroupsResp *objectstorage.ListCredentialsGroupsResponse
listAccessKeysReq objectstorage.ApiListAccessKeysRequest
}
func (m *objectStorageClientMocked) GetServiceStatusExecute(_ context.Context, _, _ string) (*objectstorage.ProjectStatus, error) {
if m.getServiceStatusFails {
return nil, fmt.Errorf("could not get service status")
}
if m.serviceDisabled {
return nil, &oapierror.GenericOpenAPIError{StatusCode: 404}
}
return &objectstorage.ProjectStatus{}, nil
listAccessKeysFails bool
listAccessKeysResp *objectstorage.ListAccessKeysResponse
}
func (m *objectStorageClientMocked) ListCredentialsGroupsExecute(_ context.Context, _, _ string) (*objectstorage.ListCredentialsGroupsResponse, error) {
if m.listCredentialsGroupsFails {
return nil, fmt.Errorf("could not list credentials groups")
func newAPIMock(settings *mockSettings) objectstorage.DefaultAPI {
return &objectstorage.DefaultAPIServiceMock{
GetServiceStatusExecuteMock: utils.Ptr(func(_ objectstorage.ApiGetServiceStatusRequest) (*objectstorage.ProjectStatus, error) {
if settings.getServiceStatusFails {
return nil, fmt.Errorf("could not get service status")
}
if settings.serviceDisabled {
return nil, &oapierror.GenericOpenAPIError{StatusCode: http.StatusNotFound}
}
return &objectstorage.ProjectStatus{}, nil
}),
ListCredentialsGroupsExecuteMock: utils.Ptr(func(_ objectstorage.ApiListCredentialsGroupsRequest) (*objectstorage.ListCredentialsGroupsResponse, error) {
if settings.listCredentialsGroupsFails {
return nil, fmt.Errorf("could not list credentials groups")
}
return settings.listCredentialsGroupsResp, nil
}),
ListAccessKeysExecuteMock: utils.Ptr(func(_ objectstorage.ApiListAccessKeysRequest) (*objectstorage.ListAccessKeysResponse, error) {
if settings.listAccessKeysFails {
return nil, &oapierror.GenericOpenAPIError{StatusCode: http.StatusBadGateway}
}
return settings.listAccessKeysResp, nil
}),
}
return m.listCredentialsGroupsResp, nil
}
func (m *objectStorageClientMocked) ListAccessKeys(_ context.Context, _, _ string) objectstorage.ApiListAccessKeysRequest {
return m.listAccessKeysReq
}
func TestProjectEnabled(t *testing.T) {

@@ -92,6 +101,6 @@ tests := []struct {

t.Run(tt.description, func(t *testing.T) {
client := &objectStorageClientMocked{
client := newAPIMock(&mockSettings{
serviceDisabled: tt.serviceDisabled,
getServiceStatusFails: tt.getProjectFails,
}
})

@@ -128,6 +137,6 @@ output, err := ProjectEnabled(context.Background(), client, testProjectId, testRegion)

listCredentialsGroupsResp: &objectstorage.ListCredentialsGroupsResponse{
CredentialsGroups: &[]objectstorage.CredentialsGroup{
CredentialsGroups: []objectstorage.CredentialsGroup{
{
CredentialsGroupId: utils.Ptr(testCredentialsGroupId),
DisplayName: utils.Ptr(testCredentialsGroupName),
CredentialsGroupId: testCredentialsGroupId,
DisplayName: testCredentialsGroupName,
},

@@ -147,10 +156,10 @@ },

listCredentialsGroupsResp: &objectstorage.ListCredentialsGroupsResponse{
CredentialsGroups: &[]objectstorage.CredentialsGroup{
CredentialsGroups: []objectstorage.CredentialsGroup{
{
CredentialsGroupId: utils.Ptr("test-UUID"),
DisplayName: utils.Ptr("test-name"),
CredentialsGroupId: "test-UUID",
DisplayName: "test-name",
},
{
CredentialsGroupId: utils.Ptr(testCredentialsGroupId),
DisplayName: utils.Ptr(testCredentialsGroupName),
CredentialsGroupId: testCredentialsGroupId,
DisplayName: testCredentialsGroupName,
},

@@ -170,7 +179,7 @@ },

{
description: "nil credentials group id",
description: "empty credentials group id",
listCredentialsGroupsResp: &objectstorage.ListCredentialsGroupsResponse{
CredentialsGroups: &[]objectstorage.CredentialsGroup{
CredentialsGroups: []objectstorage.CredentialsGroup{
{
CredentialsGroupId: nil,
CredentialsGroupId: "",
},

@@ -182,20 +191,8 @@ },

{
description: "nil credentials group name",
listCredentialsGroupsResp: &objectstorage.ListCredentialsGroupsResponse{
CredentialsGroups: &[]objectstorage.CredentialsGroup{
{
CredentialsGroupId: utils.Ptr(testCredentialsGroupId),
DisplayName: nil,
},
},
},
isValid: false,
},
{
description: "empty credentials group name",
listCredentialsGroupsResp: &objectstorage.ListCredentialsGroupsResponse{
CredentialsGroups: &[]objectstorage.CredentialsGroup{
CredentialsGroups: []objectstorage.CredentialsGroup{
{
CredentialsGroupId: utils.Ptr(testCredentialsGroupId),
DisplayName: utils.Ptr(""),
CredentialsGroupId: testCredentialsGroupId,
DisplayName: "",
},

@@ -210,6 +207,6 @@ },

t.Run(tt.description, func(t *testing.T) {
client := &objectStorageClientMocked{
client := newAPIMock(&mockSettings{
listCredentialsGroupsFails: tt.listCredentialsGroupsFails,
listCredentialsGroupsResp: tt.listCredentialsGroupsResp,
}
})

@@ -245,6 +242,6 @@ output, err := GetCredentialsGroupName(context.Background(), client, testProjectId, testCredentialsGroupId, testRegion)

listAccessKeysResp: &objectstorage.ListAccessKeysResponse{
AccessKeys: &[]objectstorage.AccessKey{
AccessKeys: []objectstorage.AccessKey{
{
KeyId: utils.Ptr(testCredentialsId),
DisplayName: utils.Ptr(testCredentialsName),
KeyId: testCredentialsId,
DisplayName: testCredentialsName,
},

@@ -264,10 +261,10 @@ },

listAccessKeysResp: &objectstorage.ListAccessKeysResponse{
AccessKeys: &[]objectstorage.AccessKey{
AccessKeys: []objectstorage.AccessKey{
{
KeyId: utils.Ptr("test-UUID"),
DisplayName: utils.Ptr("test-name"),
KeyId: "test-UUID",
DisplayName: "test-name",
},
{
KeyId: utils.Ptr(testCredentialsId),
DisplayName: utils.Ptr(testCredentialsName),
KeyId: testCredentialsId,
DisplayName: testCredentialsName,
},

@@ -287,7 +284,7 @@ },

{
description: "nil credentials id",
description: "empty credentials id",
listAccessKeysResp: &objectstorage.ListAccessKeysResponse{
AccessKeys: &[]objectstorage.AccessKey{
AccessKeys: []objectstorage.AccessKey{
{
KeyId: nil,
KeyId: "",
},

@@ -299,20 +296,8 @@ },

{
description: "nil credentials name",
listAccessKeysResp: &objectstorage.ListAccessKeysResponse{
AccessKeys: &[]objectstorage.AccessKey{
{
KeyId: utils.Ptr(testCredentialsId),
DisplayName: nil,
},
},
},
isValid: false,
},
{
description: "empty credentials name",
listAccessKeysResp: &objectstorage.ListAccessKeysResponse{
AccessKeys: &[]objectstorage.AccessKey{
AccessKeys: []objectstorage.AccessKey{
{
KeyId: utils.Ptr(testCredentialsId),
DisplayName: utils.Ptr(""),
KeyId: testCredentialsId,
DisplayName: "",
},

@@ -327,33 +312,6 @@ },

t.Run(tt.description, func(t *testing.T) {
mockedRespBytes, err := json.Marshal(tt.listAccessKeysResp)
if err != nil {
t.Fatalf("Failed to marshal mocked response: %v", err)
}
handler := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "application/json")
if tt.getCredentialsNameFails {
w.WriteHeader(http.StatusBadGateway)
w.Header().Set("Content-Type", "application/json")
_, err := w.Write([]byte("{\"message\": \"Something bad happened\""))
if err != nil {
t.Errorf("Failed to write bad response: %v", err)
}
return
}
_, err := w.Write(mockedRespBytes)
if err != nil {
t.Errorf("Failed to write response: %v", err)
}
client := newAPIMock(&mockSettings{
listAccessKeysFails: tt.getCredentialsNameFails,
listAccessKeysResp: tt.listAccessKeysResp,
})
mockedServer := httptest.NewServer(handler)
defer mockedServer.Close()
client, err := objectstorage.NewAPIClient(
config.WithEndpoint(mockedServer.URL),
config.WithoutAuthentication(),
)
if err != nil {
t.Fatalf("Failed to initialize client: %v", err)
}

@@ -360,0 +318,0 @@ output, err := GetCredentialsName(context.Background(), client, testProjectId, testCredentialsGroupId, testCredentialsId, testRegion)

@@ -9,13 +9,7 @@ package utils

"github.com/stackitcloud/stackit-sdk-go/core/oapierror"
"github.com/stackitcloud/stackit-sdk-go/services/objectstorage"
objectstorage "github.com/stackitcloud/stackit-sdk-go/services/objectstorage/v2api"
)
type ObjectStorageClient interface {
GetServiceStatusExecute(ctx context.Context, projectId, region string) (*objectstorage.ProjectStatus, error)
ListCredentialsGroupsExecute(ctx context.Context, projectId, region string) (*objectstorage.ListCredentialsGroupsResponse, error)
ListAccessKeys(ctx context.Context, projectId, region string) objectstorage.ApiListAccessKeysRequest
}
func ProjectEnabled(ctx context.Context, apiClient ObjectStorageClient, projectId, region string) (bool, error) {
_, err := apiClient.GetServiceStatusExecute(ctx, projectId, region)
func ProjectEnabled(ctx context.Context, apiClient objectstorage.DefaultAPI, projectId, region string) (bool, error) {
_, err := apiClient.GetServiceStatus(ctx, projectId, region).Execute()
if err != nil {

@@ -34,4 +28,4 @@ oapiErr, ok := err.(*oapierror.GenericOpenAPIError) //nolint:errorlint //complaining that error.As should be used to catch wrapped errors, but this error should not be wrapped

func GetCredentialsGroupName(ctx context.Context, apiClient ObjectStorageClient, projectId, credentialsGroupId, region string) (string, error) {
resp, err := apiClient.ListCredentialsGroupsExecute(ctx, projectId, region)
func GetCredentialsGroupName(ctx context.Context, apiClient objectstorage.DefaultAPI, projectId, credentialsGroupId, region string) (string, error) {
resp, err := apiClient.ListCredentialsGroups(ctx, projectId, region).Execute()
if err != nil {

@@ -46,5 +40,5 @@ return "", fmt.Errorf("list Object Storage credentials groups: %w", err)

for _, group := range *credentialsGroups {
if group.CredentialsGroupId != nil && *group.CredentialsGroupId == credentialsGroupId && group.DisplayName != nil && *group.DisplayName != "" {
return *group.DisplayName, nil
for _, group := range credentialsGroups {
if group.CredentialsGroupId == credentialsGroupId && group.DisplayName != "" {
return group.DisplayName, nil
}

@@ -56,3 +50,3 @@ }

func GetCredentialsName(ctx context.Context, apiClient ObjectStorageClient, projectId, credentialsGroupId, keyId, region string) (string, error) {
func GetCredentialsName(ctx context.Context, apiClient objectstorage.DefaultAPI, projectId, credentialsGroupId, keyId, region string) (string, error) {
req := apiClient.ListAccessKeys(ctx, projectId, region)

@@ -71,5 +65,5 @@ req = req.CredentialsGroup(credentialsGroupId)

for _, credential := range *credentials {
if credential.KeyId != nil && *credential.KeyId == keyId && credential.DisplayName != nil && *credential.DisplayName != "" {
return *credential.DisplayName, nil
for _, credential := range credentials {
if credential.KeyId == keyId && credential.DisplayName != "" {
return credential.DisplayName, nil
}

@@ -76,0 +70,0 @@ }

Sorry, the diff of this file is too big to display