You're Invited:Meet the Socket Team at RSAC and BSidesSF 2026, March 23–26.RSVP
Socket
Book a DemoSign in
Socket

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

Package Overview
Dependencies
Versions
174
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.18.0
to
v0.19.0
+46
docs/stackit_beta_server_network-interface_attach.md
## stackit beta server network-interface attach
Attaches a network interface to a server
### Synopsis
Attaches a network interface to a server.
```
stackit beta server network-interface attach [flags]
```
### Examples
```
Attach a network interface with ID "xxx" to a server with ID "yyy"
$ stackit beta server network-interface attach --network-interface-id xxx --server-id yyy
Create a network interface for network with ID "xxx" and attach it to a server with ID "yyy"
$ stackit beta server network-interface attach --network-id xxx --server-id yyy --create
```
### Options
```
-b, --create If this is set a network interface will be created. (default false)
-h, --help Help for "stackit beta server network-interface attach"
--network-id string Network ID
--network-interface-id string Network Interface 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
--verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info")
```
### SEE ALSO
* [stackit beta server network-interface](./stackit_beta_server_network-interface.md) - Allows attaching/detaching network interfaces to servers
## stackit beta server network-interface detach
Detaches a network interface from a server
### Synopsis
Detaches a network interface from a server.
```
stackit beta server network-interface detach [flags]
```
### Examples
```
Detach a network interface with ID "xxx" from a server with ID "yyy"
$ stackit beta server network-interface detach --network-interface-id xxx --server-id yyy
Detach and delete all network interfaces for network with ID "xxx" and detach them from a server with ID "yyy"
$ stackit beta server network-interface detach --network-id xxx --server-id yyy --delete
```
### Options
```
-b, --delete If this is set all network interfaces will be deleted. (default false)
-h, --help Help for "stackit beta server network-interface detach"
--network-id string Network ID
--network-interface-id string Network Interface 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
--verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info")
```
### SEE ALSO
* [stackit beta server network-interface](./stackit_beta_server_network-interface.md) - Allows attaching/detaching network interfaces to servers
## stackit beta server network-interface list
Lists all attached network interfaces of a server
### Synopsis
Lists all attached network interfaces of a server.
```
stackit beta server network-interface list [flags]
```
### Examples
```
Lists all attached network interfaces of server with ID "xxx"
$ stackit beta server network-interface list --server-id xxx
Lists all attached network interfaces of server with ID "xxx" in JSON format
$ stackit beta server network-interface list --server-id xxx --output-format json
Lists up to 10 attached network interfaces of server with ID "xxx"
$ stackit beta server network-interface list --server-id xxx --limit 10
```
### Options
```
-h, --help Help for "stackit beta server network-interface list"
--limit int Maximum number of entries to list
--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
--verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info")
```
### SEE ALSO
* [stackit beta server network-interface](./stackit_beta_server_network-interface.md) - Allows attaching/detaching network interfaces to servers
## stackit beta server network-interface
Allows attaching/detaching network interfaces to servers
### Synopsis
Allows attaching/detaching network interfaces to servers.
```
stackit beta server network-interface [flags]
```
### Options
```
-h, --help Help for "stackit beta server network-interface"
```
### 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
--verbosity string Verbosity of the CLI, one of ["debug" "info" "warning" "error"] (default "info")
```
### SEE ALSO
* [stackit beta server](./stackit_beta_server.md) - Provides functionality for servers
* [stackit beta server network-interface attach](./stackit_beta_server_network-interface_attach.md) - Attaches a network interface to a server
* [stackit beta server network-interface detach](./stackit_beta_server_network-interface_detach.md) - Detaches a network interface from a server
* [stackit beta server network-interface list](./stackit_beta_server_network-interface_list.md) - Lists all attached network interfaces of a server
package attach
import (
"context"
"testing"
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
"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/iaas"
)
var projectIdFlag = globalflags.ProjectIdFlag
type testCtxKey struct{}
var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo")
var testClient = &iaas.APIClient{}
var testProjectId = uuid.NewString()
var testServerId = uuid.NewString()
var testNicId = uuid.NewString()
var testNetworkId = uuid.NewString()
// contains nic id
func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string {
flagValues := map[string]string{
projectIdFlag: testProjectId,
serverIdFlag: testServerId,
networkInterfaceIdFlag: testNicId,
}
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,
},
ServerId: utils.Ptr(testServerId),
NicId: utils.Ptr(testNicId),
}
for _, mod := range mods {
mod(model)
}
return model
}
func fixtureRequestAttach(mods ...func(request *iaas.ApiAddNicToServerRequest)) iaas.ApiAddNicToServerRequest {
request := testClient.AddNicToServer(testCtx, testProjectId, testServerId, testNicId)
for _, mod := range mods {
mod(&request)
}
return request
}
func fixtureRequestCreateAndAttach(mods ...func(request *iaas.ApiAddNetworkToServerRequest)) iaas.ApiAddNetworkToServerRequest {
request := testClient.AddNetworkToServer(testCtx, testProjectId, testServerId, testNetworkId)
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, projectIdFlag)
}),
isValid: false,
},
{
description: "project id invalid 1",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[projectIdFlag] = ""
}),
isValid: false,
},
{
description: "project id invalid 2",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[projectIdFlag] = "invalid-uuid"
}),
isValid: false,
},
{
description: "server id 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,
},
// only create
{
description: "provided flags invalid 1",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[createFlag] = "true"
delete(flagValues, networkInterfaceIdFlag)
}),
isValid: false,
},
// only network id
{
description: "provided flags invalid 2",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, networkInterfaceIdFlag)
flagValues[networkIdFlag] = testNetworkId
}),
isValid: false,
},
// create and nic id
{
description: "provided flags invalid 3",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[createFlag] = "true"
}),
isValid: false,
},
// create and network id (valid)
{
description: "provided flags valid",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[createFlag] = "true"
delete(flagValues, networkInterfaceIdFlag)
flagValues[networkIdFlag] = testNetworkId
}),
isValid: true,
expectedModel: fixtureInputModel(func(model *inputModel) {
model.Create = utils.Ptr(true)
model.NicId = nil
model.NetworkId = utils.Ptr(testNetworkId)
}),
},
// create, nic id and network id
{
description: "provided flags invalid 4",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[createFlag] = "true"
flagValues[networkIdFlag] = testNetworkId
}),
isValid: false,
expectedModel: fixtureInputModel(func(model *inputModel) {
model.Create = utils.Ptr(true)
model.NetworkId = utils.Ptr(testNetworkId)
}),
},
// network id and nic id
{
description: "provided flags invalid 5",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[networkIdFlag] = testNetworkId
}),
isValid: false,
expectedModel: fixtureInputModel(func(model *inputModel) {
model.NetworkId = utils.Ptr(testNetworkId)
}),
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
p := print.NewPrinter()
cmd := NewCmd(p)
err := globalflags.Configure(cmd.Flags())
if err != nil {
t.Fatalf("configure global flags: %v", err)
}
for flag, value := range tt.flagValues {
err := cmd.Flags().Set(flag, value)
if err != nil {
if !tt.isValid {
return
}
t.Fatalf("setting flag --%s=%s: %v", flag, value, err)
}
}
err = cmd.ValidateFlagGroups()
if err != nil {
if !tt.isValid {
return
}
t.Fatalf("error validating flag groups: %v", err)
}
err = cmd.ValidateRequiredFlags()
if err != nil {
if !tt.isValid {
return
}
t.Fatalf("error validating flags: %v", err)
}
model, err := parseInput(p, cmd)
if err != nil {
if !tt.isValid {
return
}
t.Fatalf("error parsing input: %v", err)
}
if !tt.isValid {
t.Fatalf("did not fail on invalid input")
}
diff := cmp.Diff(model, tt.expectedModel)
if diff != "" {
t.Fatalf("Data does not match: %s", diff)
}
})
}
}
func TestBuildRequestAttach(t *testing.T) {
tests := []struct {
description string
model *inputModel
expectedRequest iaas.ApiAddNicToServerRequest
}{
{
description: "base",
model: fixtureInputModel(),
expectedRequest: fixtureRequestAttach(),
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
request := buildRequestAttach(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)
}
})
}
}
func TestBuildRequestCreateAndAttach(t *testing.T) {
tests := []struct {
description string
model *inputModel
expectedRequest iaas.ApiAddNetworkToServerRequest
}{
{
description: "base",
model: fixtureInputModel(func(model *inputModel) {
model.NicId = nil
model.NetworkId = utils.Ptr(testNetworkId)
model.Create = utils.Ptr(true)
}),
expectedRequest: fixtureRequestCreateAndAttach(),
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
request := buildRequestCreateAndAttach(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/spf13/cobra"
"github.com/stackitcloud/stackit-cli/internal/pkg/args"
"github.com/stackitcloud/stackit-cli/internal/pkg/errors"
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"
networkInterfaceIdFlag = "network-interface-id"
createFlag = "create"
networkIdFlag = "network-id"
defaultCreateFlag = false
)
type inputModel struct {
*globalflags.GlobalFlagModel
ServerId *string
NicId *string
NetworkId *string
Create *bool
}
func NewCmd(p *print.Printer) *cobra.Command {
cmd := &cobra.Command{
Use: "attach",
Short: "Attaches a network interface to a server",
Long: "Attaches a network interface to a server.",
Args: args.NoArgs,
Example: examples.Build(
examples.NewExample(
`Attach a network interface with ID "xxx" to a server with ID "yyy"`,
`$ stackit beta server network-interface attach --network-interface-id xxx --server-id yyy`,
),
examples.NewExample(
`Create a network interface for network with ID "xxx" and attach it to a server with ID "yyy"`,
`$ stackit beta server network-interface attach --network-id xxx --server-id yyy --create`,
),
),
RunE: func(cmd *cobra.Command, _ []string) error {
ctx := context.Background()
model, err := parseInput(p, cmd)
if err != nil {
return err
}
// Configure API client
apiClient, err := client.ConfigureClient(p)
if err != nil {
return err
}
serverLabel, err := iaasUtils.GetServerName(ctx, apiClient, model.ProjectId, *model.ServerId)
if err != nil {
p.Debug(print.ErrorLevel, "get server name: %v", err)
serverLabel = *model.ServerId
}
// if the create flag is provided a network interface will be created and attached
if model.Create != nil && *model.Create {
networkLabel, err := iaasUtils.GetNetworkName(ctx, apiClient, model.ProjectId, *model.NetworkId)
if err != nil {
p.Debug(print.ErrorLevel, "get network name: %v", err)
networkLabel = *model.NetworkId
}
if !model.AssumeYes {
prompt := fmt.Sprintf("Are you sure you want to create a network interface for network %q and attach it to server %q?", networkLabel, serverLabel)
err = p.PromptForConfirmation(prompt)
if err != nil {
return err
}
}
// Call API
req := buildRequestCreateAndAttach(ctx, model, apiClient)
err = req.Execute()
if err != nil {
return fmt.Errorf("create and attach network interface: %w", err)
}
p.Info("Created a network interface for network %q and attached it to server %q\n", networkLabel, serverLabel)
return nil
}
if !model.AssumeYes {
prompt := fmt.Sprintf("Are you sure you want to attach network interface %q to server %q?", *model.NicId, serverLabel)
err = p.PromptForConfirmation(prompt)
if err != nil {
return err
}
}
// Call API
req := buildRequestAttach(ctx, model, apiClient)
err = req.Execute()
if err != nil {
return fmt.Errorf("attach network interface: %w", err)
}
p.Info("Attached network interface %q to server %q\n", *model.NicId, 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(), networkInterfaceIdFlag, "Network Interface ID")
cmd.Flags().Var(flags.UUIDFlag(), networkIdFlag, "Network ID")
cmd.Flags().BoolP(createFlag, "b", defaultCreateFlag, "If this is set a network interface will be created. (default false)")
cmd.MarkFlagsRequiredTogether(createFlag, networkIdFlag)
cmd.MarkFlagsMutuallyExclusive(createFlag, networkInterfaceIdFlag)
cmd.MarkFlagsMutuallyExclusive(networkIdFlag, networkInterfaceIdFlag)
err := flags.MarkFlagsRequired(cmd, serverIdFlag)
cobra.CheckErr(err)
}
func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) {
globalFlags := globalflags.Parse(p, cmd)
if globalFlags.ProjectId == "" {
return nil, &errors.ProjectIdError{}
}
// if create is not provided then network-interface-id is needed
networkInterfaceId := flags.FlagToStringPointer(p, cmd, networkInterfaceIdFlag)
create := flags.FlagToBoolPointer(p, cmd, createFlag)
if create == nil && networkInterfaceId == nil {
return nil, &cliErr.ServerNicAttachMissingNicIdError{Cmd: cmd}
}
model := inputModel{
GlobalFlagModel: globalFlags,
ServerId: flags.FlagToStringPointer(p, cmd, serverIdFlag),
NetworkId: flags.FlagToStringPointer(p, cmd, networkIdFlag),
NicId: networkInterfaceId,
Create: create,
}
if p.IsVerbosityDebug() {
modelStr, err := print.BuildDebugStrFromInputModel(model)
if err != nil {
p.Debug(print.ErrorLevel, "convert model to string for debugging: %v", err)
} else {
p.Debug(print.DebugLevel, "parsed input values: %s", modelStr)
}
}
return &model, nil
}
func buildRequestAttach(ctx context.Context, model *inputModel, apiClient *iaas.APIClient) iaas.ApiAddNicToServerRequest {
return apiClient.AddNicToServer(ctx, model.ProjectId, *model.ServerId, *model.NicId)
}
func buildRequestCreateAndAttach(ctx context.Context, model *inputModel, apiClient *iaas.APIClient) iaas.ApiAddNetworkToServerRequest {
return apiClient.AddNetworkToServer(ctx, model.ProjectId, *model.ServerId, *model.NetworkId)
}
package detach
import (
"context"
"testing"
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
"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/iaas"
)
var projectIdFlag = globalflags.ProjectIdFlag
type testCtxKey struct{}
var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo")
var testClient = &iaas.APIClient{}
var testProjectId = uuid.NewString()
var testServerId = uuid.NewString()
var testNicId = uuid.NewString()
var testNetworkId = uuid.NewString()
// contains nic id
func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string {
flagValues := map[string]string{
projectIdFlag: testProjectId,
serverIdFlag: testServerId,
networkInterfaceIdFlag: testNicId,
}
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,
},
ServerId: utils.Ptr(testServerId),
NicId: utils.Ptr(testNicId),
}
for _, mod := range mods {
mod(model)
}
return model
}
func fixtureRequestDetach(mods ...func(request *iaas.ApiRemoveNicFromServerRequest)) iaas.ApiRemoveNicFromServerRequest {
request := testClient.RemoveNicFromServer(testCtx, testProjectId, testServerId, testNicId)
for _, mod := range mods {
mod(&request)
}
return request
}
func fixtureRequestDetachAndDelete(mods ...func(request *iaas.ApiRemoveNetworkFromServerRequest)) iaas.ApiRemoveNetworkFromServerRequest {
request := testClient.RemoveNetworkFromServer(testCtx, testProjectId, testServerId, testNetworkId)
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, projectIdFlag)
}),
isValid: false,
},
{
description: "project id invalid 1",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[projectIdFlag] = ""
}),
isValid: false,
},
{
description: "project id invalid 2",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[projectIdFlag] = "invalid-uuid"
}),
isValid: false,
},
{
description: "server id 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,
},
// only delete
{
description: "provided flags invalid 1",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[deleteFlag] = "true"
delete(flagValues, networkInterfaceIdFlag)
}),
isValid: false,
},
// only network id
{
description: "provided flags invalid 2",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
delete(flagValues, networkInterfaceIdFlag)
flagValues[networkIdFlag] = testNetworkId
}),
isValid: false,
},
// delete and nic id
{
description: "provided flags invalid 3",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[deleteFlag] = "true"
}),
isValid: false,
},
// delete and network id (valid)
{
description: "provided flags valid",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[deleteFlag] = "true"
delete(flagValues, networkInterfaceIdFlag)
flagValues[networkIdFlag] = testNetworkId
}),
isValid: true,
expectedModel: fixtureInputModel(func(model *inputModel) {
model.Delete = utils.Ptr(true)
model.NicId = nil
model.NetworkId = utils.Ptr(testNetworkId)
}),
},
// delete, nic id and network id
{
description: "provided flags invalid 4",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[deleteFlag] = "true"
flagValues[networkIdFlag] = testNetworkId
}),
isValid: false,
expectedModel: fixtureInputModel(func(model *inputModel) {
model.Delete = utils.Ptr(true)
model.NetworkId = utils.Ptr(testNetworkId)
}),
},
// network id and nic id
{
description: "provided flags invalid 5",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[networkIdFlag] = testNetworkId
}),
isValid: false,
expectedModel: fixtureInputModel(func(model *inputModel) {
model.NetworkId = utils.Ptr(testNetworkId)
}),
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
p := print.NewPrinter()
cmd := NewCmd(p)
err := globalflags.Configure(cmd.Flags())
if err != nil {
t.Fatalf("configure global flags: %v", err)
}
for flag, value := range tt.flagValues {
err := cmd.Flags().Set(flag, value)
if err != nil {
if !tt.isValid {
return
}
t.Fatalf("setting flag --%s=%s: %v", flag, value, err)
}
}
err = cmd.ValidateFlagGroups()
if err != nil {
if !tt.isValid {
return
}
t.Fatalf("error validating flag groups: %v", err)
}
err = cmd.ValidateRequiredFlags()
if err != nil {
if !tt.isValid {
return
}
t.Fatalf("error validating flags: %v", err)
}
model, err := parseInput(p, cmd)
if err != nil {
if !tt.isValid {
return
}
t.Fatalf("error parsing input: %v", err)
}
if !tt.isValid {
t.Fatalf("did not fail on invalid input")
}
diff := cmp.Diff(model, tt.expectedModel)
if diff != "" {
t.Fatalf("Data does not match: %s", diff)
}
})
}
}
func TestBuildRequestDetach(t *testing.T) {
tests := []struct {
description string
model *inputModel
expectedRequest iaas.ApiRemoveNicFromServerRequest
}{
{
description: "base",
model: fixtureInputModel(),
expectedRequest: fixtureRequestDetach(),
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
request := buildRequestDetach(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)
}
})
}
}
func TestBuildRequestDetachAndDelete(t *testing.T) {
tests := []struct {
description string
model *inputModel
expectedRequest iaas.ApiRemoveNetworkFromServerRequest
}{
{
description: "base",
model: fixtureInputModel(func(model *inputModel) {
model.NicId = nil
model.NetworkId = utils.Ptr(testNetworkId)
model.Delete = utils.Ptr(true)
}),
expectedRequest: fixtureRequestDetachAndDelete(),
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
request := buildRequestDetachAndDelete(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/spf13/cobra"
"github.com/stackitcloud/stackit-cli/internal/pkg/args"
"github.com/stackitcloud/stackit-cli/internal/pkg/errors"
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"
networkInterfaceIdFlag = "network-interface-id"
networkIdFlag = "network-id"
deleteFlag = "delete"
defaultDeleteFlag = false
)
type inputModel struct {
*globalflags.GlobalFlagModel
ServerId *string
NicId *string
NetworkId *string
Delete *bool
}
func NewCmd(p *print.Printer) *cobra.Command {
cmd := &cobra.Command{
Use: "detach",
Short: "Detaches a network interface from a server",
Long: "Detaches a network interface from a server.",
Args: args.NoArgs,
Example: examples.Build(
examples.NewExample(
`Detach a network interface with ID "xxx" from a server with ID "yyy"`,
`$ stackit beta server network-interface detach --network-interface-id xxx --server-id yyy`,
),
examples.NewExample(
`Detach and delete all network interfaces for network with ID "xxx" and detach them from a server with ID "yyy"`,
`$ stackit beta server network-interface detach --network-id xxx --server-id yyy --delete`,
),
),
RunE: func(cmd *cobra.Command, _ []string) error {
ctx := context.Background()
model, err := parseInput(p, cmd)
if err != nil {
return err
}
// Configure API client
apiClient, err := client.ConfigureClient(p)
if err != nil {
return err
}
serverLabel, err := iaasUtils.GetServerName(ctx, apiClient, model.ProjectId, *model.ServerId)
if err != nil {
p.Debug(print.ErrorLevel, "get server name: %v", err)
serverLabel = *model.ServerId
}
// if the delete flag is provided a network interface is detached and deleted
if model.Delete != nil && *model.Delete {
networkLabel, err := iaasUtils.GetNetworkName(ctx, apiClient, model.ProjectId, *model.NetworkId)
if err != nil {
p.Debug(print.ErrorLevel, "get network name: %v", err)
networkLabel = *model.NetworkId
}
if !model.AssumeYes {
prompt := fmt.Sprintf("Are you sure you want to detach and delete all network interfaces of network %q from server %q? (This cannot be undone)", networkLabel, serverLabel)
err = p.PromptForConfirmation(prompt)
if err != nil {
return err
}
}
// Call API
req := buildRequestDetachAndDelete(ctx, model, apiClient)
err = req.Execute()
if err != nil {
return fmt.Errorf("detach and delete network interfaces: %w", err)
}
p.Info("Detached and deleted all network interfaces of network %q from server %q\n", networkLabel, serverLabel)
return nil
}
if !model.AssumeYes {
prompt := fmt.Sprintf("Are you sure you want to detach network interface %q from server %q?", *model.NicId, serverLabel)
err = p.PromptForConfirmation(prompt)
if err != nil {
return err
}
}
// Call API
req := buildRequestDetach(ctx, model, apiClient)
err = req.Execute()
if err != nil {
return fmt.Errorf("detach network interface: %w", err)
}
p.Info("Detached network interface %q from server %q\n", *model.NicId, 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(), networkInterfaceIdFlag, "Network Interface ID")
cmd.Flags().Var(flags.UUIDFlag(), networkIdFlag, "Network ID")
cmd.Flags().BoolP(deleteFlag, "b", defaultDeleteFlag, "If this is set all network interfaces will be deleted. (default false)")
cmd.MarkFlagsRequiredTogether(deleteFlag, networkIdFlag)
cmd.MarkFlagsMutuallyExclusive(deleteFlag, networkInterfaceIdFlag)
cmd.MarkFlagsMutuallyExclusive(networkIdFlag, networkInterfaceIdFlag)
err := flags.MarkFlagsRequired(cmd, serverIdFlag)
cobra.CheckErr(err)
}
func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) {
globalFlags := globalflags.Parse(p, cmd)
if globalFlags.ProjectId == "" {
return nil, &errors.ProjectIdError{}
}
// if delete is not provided then network-interface-id is needed
networkInterfaceId := flags.FlagToStringPointer(p, cmd, networkInterfaceIdFlag)
deleteValue := flags.FlagToBoolPointer(p, cmd, deleteFlag)
if deleteValue == nil && networkInterfaceId == nil {
return nil, &cliErr.ServerNicDetachMissingNicIdError{Cmd: cmd}
}
model := inputModel{
GlobalFlagModel: globalFlags,
ServerId: flags.FlagToStringPointer(p, cmd, serverIdFlag),
NetworkId: flags.FlagToStringPointer(p, cmd, networkIdFlag),
NicId: networkInterfaceId,
Delete: deleteValue,
}
if p.IsVerbosityDebug() {
modelStr, err := print.BuildDebugStrFromInputModel(model)
if err != nil {
p.Debug(print.ErrorLevel, "convert model to string for debugging: %v", err)
} else {
p.Debug(print.DebugLevel, "parsed input values: %s", modelStr)
}
}
return &model, nil
}
func buildRequestDetach(ctx context.Context, model *inputModel, apiClient *iaas.APIClient) iaas.ApiRemoveNicFromServerRequest {
return apiClient.RemoveNicFromServer(ctx, model.ProjectId, *model.ServerId, *model.NicId)
}
func buildRequestDetachAndDelete(ctx context.Context, model *inputModel, apiClient *iaas.APIClient) iaas.ApiRemoveNetworkFromServerRequest {
return apiClient.RemoveNetworkFromServer(ctx, model.ProjectId, *model.ServerId, *model.NetworkId)
}
package list
// TODO: hier sind wir
import (
"context"
"testing"
"github.com/stackitcloud/stackit-cli/internal/pkg/globalflags"
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
"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/iaas"
)
var projectIdFlag = globalflags.ProjectIdFlag
type testCtxKey struct{}
var testCtx = context.WithValue(context.Background(), testCtxKey{}, "foo")
var testClient = &iaas.APIClient{}
var testProjectId = uuid.NewString()
var testServerId = uuid.NewString()
func fixtureFlagValues(mods ...func(flagValues map[string]string)) map[string]string {
flagValues := map[string]string{
projectIdFlag: testProjectId,
limitFlag: "10",
serverIdFlag: testServerId,
}
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,
},
Limit: utils.Ptr(int64(10)),
ServerId: utils.Ptr(testServerId),
}
for _, mod := range mods {
mod(model)
}
return model
}
func fixtureRequest(mods ...func(request *iaas.ApiListServerNicsRequest)) iaas.ApiListServerNicsRequest {
request := testClient.ListServerNics(testCtx, testProjectId, testServerId)
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, projectIdFlag)
}),
isValid: false,
},
{
description: "project id invalid 1",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[projectIdFlag] = ""
}),
isValid: false,
},
{
description: "project id invalid 2",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[projectIdFlag] = "invalid-uuid"
}),
isValid: false,
},
{
description: "server id 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,
},
{
description: "limit invalid",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[limitFlag] = "invalid"
}),
isValid: false,
},
{
description: "limit invalid 2",
flagValues: fixtureFlagValues(func(flagValues map[string]string) {
flagValues[limitFlag] = "0"
}),
isValid: false,
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
p := print.NewPrinter()
cmd := NewCmd(p)
err := globalflags.Configure(cmd.Flags())
if err != nil {
t.Fatalf("configure global flags: %v", err)
}
for flag, value := range tt.flagValues {
err := cmd.Flags().Set(flag, value)
if err != nil {
if !tt.isValid {
return
}
t.Fatalf("setting flag --%s=%s: %v", flag, value, err)
}
}
err = cmd.ValidateRequiredFlags()
if err != nil {
if !tt.isValid {
return
}
t.Fatalf("error validating flags: %v", err)
}
model, err := parseInput(p, cmd)
if err != nil {
if !tt.isValid {
return
}
t.Fatalf("error parsing input: %v", err)
}
if !tt.isValid {
t.Fatalf("did not fail on invalid input")
}
diff := cmp.Diff(model, tt.expectedModel)
if diff != "" {
t.Fatalf("Data does not match: %s", diff)
}
})
}
}
func TestBuildRequest(t *testing.T) {
tests := []struct {
description string
model *inputModel
expectedRequest iaas.ApiListServerNicsRequest
}{
{
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 list
import (
"context"
"encoding/json"
"fmt"
"github.com/goccy/go-yaml"
"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"
iaasUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/iaas/utils"
"github.com/stackitcloud/stackit-cli/internal/pkg/tables"
"github.com/stackitcloud/stackit-sdk-go/services/iaas"
"github.com/spf13/cobra"
)
const (
serverIdFlag = "server-id"
limitFlag = "limit"
)
type inputModel struct {
*globalflags.GlobalFlagModel
ServerId *string
Limit *int64
}
func NewCmd(p *print.Printer) *cobra.Command {
cmd := &cobra.Command{
Use: "list",
Short: "Lists all attached network interfaces of a server",
Long: "Lists all attached network interfaces of a server.",
Args: args.NoArgs,
Example: examples.Build(
examples.NewExample(
`Lists all attached network interfaces of server with ID "xxx"`,
"$ stackit beta server network-interface list --server-id xxx",
),
examples.NewExample(
`Lists all attached network interfaces of server with ID "xxx" in JSON format`,
"$ stackit beta server network-interface list --server-id xxx --output-format json",
),
examples.NewExample(
`Lists up to 10 attached network interfaces of server with ID "xxx"`,
"$ stackit beta server network-interface list --server-id xxx --limit 10",
),
),
RunE: func(cmd *cobra.Command, _ []string) error {
ctx := context.Background()
model, err := parseInput(p, cmd)
if err != nil {
return err
}
// Configure API client
apiClient, err := client.ConfigureClient(p)
if err != nil {
return err
}
// Call API
req := buildRequest(ctx, model, apiClient)
resp, err := req.Execute()
if err != nil {
return fmt.Errorf("list attached network interfaces: %w", err)
}
if resp.Items == nil || len(*resp.Items) == 0 {
serverLabel, err := iaasUtils.GetServerName(ctx, apiClient, model.ProjectId, *model.ServerId)
if err != nil {
p.Debug(print.ErrorLevel, "get server name: %v", err)
serverLabel = *model.ServerId
}
p.Info("No attached network interfaces found for server %q\n", serverLabel)
return nil
}
// Truncate output
items := *resp.Items
if model.Limit != nil && len(items) > int(*model.Limit) {
items = items[:*model.Limit]
}
return outputResult(p, model.OutputFormat, *model.ServerId, items)
},
}
configureFlags(cmd)
return cmd
}
func configureFlags(cmd *cobra.Command) {
cmd.Flags().Var(flags.UUIDFlag(), serverIdFlag, "Server ID")
cmd.Flags().Int64(limitFlag, 0, "Maximum number of entries to list")
err := flags.MarkFlagsRequired(cmd, serverIdFlag)
cobra.CheckErr(err)
}
func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) {
globalFlags := globalflags.Parse(p, cmd)
if globalFlags.ProjectId == "" {
return nil, &errors.ProjectIdError{}
}
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,
ServerId: flags.FlagToStringPointer(p, cmd, serverIdFlag),
Limit: limit,
}
if p.IsVerbosityDebug() {
modelStr, err := print.BuildDebugStrFromInputModel(model)
if err != nil {
p.Debug(print.ErrorLevel, "convert model to string for debugging: %v", err)
} else {
p.Debug(print.DebugLevel, "parsed input values: %s", modelStr)
}
}
return &model, nil
}
func buildRequest(ctx context.Context, model *inputModel, apiClient *iaas.APIClient) iaas.ApiListServerNicsRequest {
return apiClient.ListServerNics(ctx, model.ProjectId, *model.ServerId)
}
func outputResult(p *print.Printer, outputFormat, serverId string, serverNics []iaas.NIC) error {
switch outputFormat {
case print.JSONOutputFormat:
details, err := json.MarshalIndent(serverNics, "", " ")
if err != nil {
return fmt.Errorf("marshal server network interfaces: %w", err)
}
p.Outputln(string(details))
return nil
case print.YAMLOutputFormat:
details, err := yaml.MarshalWithOptions(serverNics, yaml.IndentSequence(true))
if err != nil {
return fmt.Errorf("marshal server network interfaces: %w", err)
}
p.Outputln(string(details))
return nil
default:
table := tables.NewTable()
table.SetHeader("NIC ID", "SERVER ID")
for i := range serverNics {
nic := serverNics[i]
table.AddRow(*nic.Id, serverId)
}
table.EnableAutoMergeOnColumns(2)
p.Outputln(table.Render())
return nil
}
}
package networkinterface
import (
"github.com/stackitcloud/stackit-cli/internal/cmd/beta/server/network-interface/attach"
"github.com/stackitcloud/stackit-cli/internal/cmd/beta/server/network-interface/detach"
"github.com/stackitcloud/stackit-cli/internal/cmd/beta/server/network-interface/list"
"github.com/stackitcloud/stackit-cli/internal/pkg/args"
"github.com/stackitcloud/stackit-cli/internal/pkg/print"
"github.com/stackitcloud/stackit-cli/internal/pkg/utils"
"github.com/spf13/cobra"
)
func NewCmd(p *print.Printer) *cobra.Command {
cmd := &cobra.Command{
Use: "network-interface",
Short: "Allows attaching/detaching network interfaces to servers",
Long: "Allows attaching/detaching network interfaces to servers.",
Args: args.NoArgs,
Run: utils.CmdHelp,
}
addSubcommands(cmd, p)
return cmd
}
func addSubcommands(cmd *cobra.Command, p *print.Printer) {
cmd.AddCommand(attach.NewCmd(p))
cmd.AddCommand(list.NewCmd(p))
cmd.AddCommand(detach.NewCmd(p))
}
+1
-1

@@ -16,5 +16,5 @@ name: Renovate

- name: Self-hosted Renovate
uses: renovatebot/github-action@v41.0.0
uses: renovatebot/github-action@v41.0.5
with:
configurationFile: .github/renovate.json
token: ${{ secrets.RENOVATE_TOKEN }}

@@ -38,2 +38,3 @@ ## stackit beta server

* [stackit beta server list](./stackit_beta_server_list.md) - Lists all servers of a project
* [stackit beta server network-interface](./stackit_beta_server_network-interface.md) - Allows attaching/detaching network interfaces to servers
* [stackit beta server public-ip](./stackit_beta_server_public-ip.md) - Allows attaching/detaching public IPs to servers

@@ -40,0 +41,0 @@ * [stackit beta server update](./stackit_beta_server_update.md) - Updates a server

+2
-2

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

github.com/fatih/color v1.18.0
github.com/goccy/go-yaml v1.15.6
github.com/goccy/go-yaml v1.15.7
github.com/golang-jwt/jwt/v5 v5.2.1

@@ -31,3 +31,3 @@ github.com/google/go-cmp v0.6.0

github.com/stackitcloud/stackit-sdk-go/services/serviceaccount v0.5.0
github.com/stackitcloud/stackit-sdk-go/services/serviceenablement v0.3.0
github.com/stackitcloud/stackit-sdk-go/services/serviceenablement v0.4.0
github.com/stackitcloud/stackit-sdk-go/services/ske v0.20.0

@@ -34,0 +34,0 @@ github.com/stackitcloud/stackit-sdk-go/services/sqlserverflex v0.8.0

+4
-4

@@ -29,4 +29,4 @@ al.essio.dev/pkg/shellescape v1.5.1 h1:86HrALUujYS/h+GtqoB26SBEdkWfmMI6FubjXlsXyho=

github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/goccy/go-yaml v1.15.6 h1:gy5kf1yjMia3/c3wWD+u1z3lU5XlhpT8FZGaLJU9cOA=
github.com/goccy/go-yaml v1.15.6/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/goccy/go-yaml v1.15.7 h1:L7XuKpd/A66X4w/dlk08lVfiIADdy79a1AzRoIefC98=
github.com/goccy/go-yaml v1.15.7/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=

@@ -160,4 +160,4 @@ github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=

github.com/stackitcloud/stackit-sdk-go/services/serviceaccount v0.5.0/go.mod h1:Wpqj80yGruCNYGr2yxqhRaLLj4gPSkhJqZyWRXUh/QU=
github.com/stackitcloud/stackit-sdk-go/services/serviceenablement v0.3.0 h1:6IZBX9fyza9Eln3FHGHquvLNXQslk+dtkQp41G9+7+Y=
github.com/stackitcloud/stackit-sdk-go/services/serviceenablement v0.3.0/go.mod h1:zyg0hpiNdZLRbelkJb2KDf9OHQKLqqcTpePQ1qHL5dE=
github.com/stackitcloud/stackit-sdk-go/services/serviceenablement v0.4.0 h1:K5fVTcJxjOVwJBa3kiWRsYNAq+I3jAYdU1U+f6no5lE=
github.com/stackitcloud/stackit-sdk-go/services/serviceenablement v0.4.0/go.mod h1:zyg0hpiNdZLRbelkJb2KDf9OHQKLqqcTpePQ1qHL5dE=
github.com/stackitcloud/stackit-sdk-go/services/ske v0.20.0 h1:ssEywzCS8IdRtzyxweLUKBG5GFbgwjNWJh++wGqigJM=

@@ -164,0 +164,0 @@ github.com/stackitcloud/stackit-sdk-go/services/ske v0.20.0/go.mod h1:A4+9KslxCA31JvxnT+O/GC67eAOdw+iqhBzewZZaCD0=

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

"github.com/stackitcloud/stackit-cli/internal/cmd/beta/server/list"
networkinterface "github.com/stackitcloud/stackit-cli/internal/cmd/beta/server/network-interface"
publicip "github.com/stackitcloud/stackit-cli/internal/cmd/beta/server/public-ip"

@@ -43,2 +44,3 @@ "github.com/stackitcloud/stackit-cli/internal/cmd/beta/server/update"

cmd.AddCommand(volume.NewCmd(p))
cmd.AddCommand(networkinterface.NewCmd(p))
}

@@ -149,4 +149,24 @@ package errors

IAAS_SERVER_MISSING_IMAGE_OR_VOLUME_FLAGS = `Either "image-id" or "boot-volume-source-type" and "boot-volume-source-id" flags must be provided.`
IAAS_SERVER_NIC_ATTACH_MISSING_NIC_ID = `The "network-interface-id" flag must be provided if the "create" flag is not provided.`
IAAS_SERVER_NIC_DETACH_MISSING_NIC_ID = `The "network-interface-id" flag must be provided if the "delete" flag is not provided.`
)
type ServerNicAttachMissingNicIdError struct {
Cmd *cobra.Command
}
func (e *ServerNicAttachMissingNicIdError) Error() string {
return IAAS_SERVER_NIC_ATTACH_MISSING_NIC_ID
}
type ServerNicDetachMissingNicIdError struct {
Cmd *cobra.Command
}
func (e *ServerNicDetachMissingNicIdError) Error() string {
return IAAS_SERVER_NIC_DETACH_MISSING_NIC_ID
}
type ServerCreateMissingVolumeIdError struct {

@@ -153,0 +173,0 @@ Cmd *cobra.Command

@@ -81,2 +81,24 @@ package print

func fixtureHTTPRequestUnescaped(mods ...func(req *http.Request)) *http.Request {
testBody, err := json.Marshal(map[string]string{"key": "value"})
if err != nil {
return nil
}
request, err := http.NewRequest("GET", "http://example.com/v2/projects?limit=50&member=User.Name%40stackit.cloud", bytes.NewReader(testBody))
if err != nil {
return nil
}
request.Header.Set("Content-Type", "application/json")
request.Header.Set("Accept", "application/json")
request.Header.Set("Content-Length", "15")
for _, mod := range mods {
mod(request)
}
return request
}
func fixtureHTTPResponse(mods ...func(resp *http.Response)) *http.Response {

@@ -406,2 +428,12 @@ testBody, err := json.Marshal(map[string]string{"key": "value"})

},
{
description: "unescaped test",
inputReq: fixtureHTTPRequestUnescaped(),
expected: []string{
"request to http://example.com/v2/projects?limit=50&member=User.Name@stackit.cloud: GET HTTP/1.1",
"request headers: [Accept: application/json, Content-Length: 15, Content-Type: application/json]",
"request body: [key: value]",
},
isValid: true,
},
}

@@ -408,0 +440,0 @@

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

"net/http"
"net/url"
"slices"

@@ -145,4 +146,10 @@ "sort"

status := fmt.Sprintf("request to %s: %s %s", req.URL, req.Method, req.Proto)
// unescape url in order to get rid of e.g. %40
unescapedURL, err := url.PathUnescape(req.URL.String())
if err != nil {
return nil, fmt.Errorf("unescape request url: %w", err)
}
status := fmt.Sprintf("request to %s: %s %s", unescapedURL, req.Method, req.Proto)
headersMap := buildHeaderMap(req.Header, includeHeaders)

@@ -152,3 +159,2 @@ headers := fmt.Sprintf("request headers: %v", BuildDebugStrFromMap(headersMap))

var save io.ReadCloser
var err error

@@ -190,4 +196,11 @@ save, req.Body, err = drainBody(req.Body)

status := fmt.Sprintf("response from %s: %s %s", resp.Request.URL, resp.Proto, resp.Status)
var err error
// unescape url in order to get rid of e.g. %40
unescapedURL, err := url.PathUnescape(resp.Request.URL.String())
if err != nil {
return nil, fmt.Errorf("unescape response url: %w", err)
}
status := fmt.Sprintf("response from %s: %s %s", unescapedURL, resp.Proto, resp.Status)
headersMap := buildHeaderMap(resp.Header, includeHeaders)

@@ -197,3 +210,2 @@ headers := fmt.Sprintf("response headers: %v", BuildDebugStrFromMap(headersMap))

var save io.ReadCloser
var err error

@@ -200,0 +212,0 @@ save, resp.Body, err = drainBody(resp.Body)