Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/stackit_network-interface_list.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ stackit network-interface list [flags]
### Examples

```
Lists all network interfaces
$ stackit network-interface list

Lists all network interfaces with network ID "xxx"
$ stackit network-interface list --network-id xxx

Expand Down
100 changes: 85 additions & 15 deletions internal/cmd/network-interface/list/list.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package list

import (
"cmp"
"context"
"fmt"
"slices"

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

"github.com/spf13/cobra"
Expand All @@ -30,7 +33,7 @@ type inputModel struct {
*globalflags.GlobalFlagModel
Limit *int64
LabelSelector *string
NetworkId string
NetworkId *string
}

func NewCmd(params *types.CmdParams) *cobra.Command {
Expand All @@ -40,6 +43,11 @@ func NewCmd(params *types.CmdParams) *cobra.Command {
Long: "Lists all network interfaces of a network.",
Args: args.NoArgs,
Example: examples.Build(
// Note: this subcommand uses two different API enpoints, which makes the implementation somewhat messy
examples.NewExample(
`Lists all network interfaces`,
`$ stackit network-interface list`,
),
examples.NewExample(
`Lists all network interfaces with network ID "xxx"`,
`$ stackit network-interface list --network-id xxx`,
Expand Down Expand Up @@ -70,22 +78,50 @@ func NewCmd(params *types.CmdParams) *cobra.Command {
return err
}

// Call API
req := buildRequest(ctx, model, apiClient)
if model.NetworkId == nil {
// Call API to get all NICs in the Project
req := buildProjectRequest(ctx, model, apiClient)

resp, err := req.Execute()
if err != nil {
return fmt.Errorf("list network interfaces: %w", err)
}

if resp.Items == nil || len(*resp.Items) == 0 {
projectLabel, err := projectname.GetProjectName(ctx, params.Printer, params.CliVersion, cmd)
if err != nil {
projectLabel = model.ProjectId
}
params.Printer.Outputf("No network interfaces found for project %q\n", projectLabel)
return nil
}

// Truncate output
items := *resp.Items
if model.Limit != nil && len(items) > int(*model.Limit) {
items = items[:*model.Limit]
}

return outputProjectResult(params.Printer, model.OutputFormat, items)
}

// Call API to get NICs for one Network
req := buildNetworkRequest(ctx, model, apiClient)

resp, err := req.Execute()
if err != nil {
return fmt.Errorf("list network interfaces: %w", err)
}

if resp.Items == nil || len(*resp.Items) == 0 {
networkLabel, err := iaasUtils.GetNetworkName(ctx, apiClient, model.ProjectId, model.Region, model.NetworkId)
networkLabel, err := iaasUtils.GetNetworkName(ctx, apiClient, model.ProjectId, model.Region, *model.NetworkId)
if err != nil {
params.Printer.Debug(print.ErrorLevel, "get network name: %v", err)
networkLabel = model.NetworkId
networkLabel = *model.NetworkId
} else if networkLabel == "" {
networkLabel = model.NetworkId
networkLabel = *model.NetworkId
}
params.Printer.Info("No network interfaces found for network %q\n", networkLabel)
params.Printer.Outputf("No network interfaces found for network %q\n", networkLabel)
return nil
}

Expand All @@ -95,7 +131,7 @@ func NewCmd(params *types.CmdParams) *cobra.Command {
items = items[:*model.Limit]
}

return outputResult(params.Printer, model.OutputFormat, items)
return outputNetworkResult(params.Printer, model.OutputFormat, items)
},
}
configureFlags(cmd)
Expand All @@ -106,9 +142,6 @@ func configureFlags(cmd *cobra.Command) {
cmd.Flags().Var(flags.UUIDFlag(), networkIdFlag, "Network ID")
cmd.Flags().Int64(limitFlag, 0, "Maximum number of entries to list")
cmd.Flags().String(labelSelectorFlag, "", "Filter by label")

err := flags.MarkFlagsRequired(cmd, networkIdFlag)
cobra.CheckErr(err)
}

func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel, error) {
Expand All @@ -129,23 +162,60 @@ func parseInput(p *print.Printer, cmd *cobra.Command, _ []string) (*inputModel,
GlobalFlagModel: globalFlags,
Limit: limit,
LabelSelector: flags.FlagToStringPointer(p, cmd, labelSelectorFlag),
NetworkId: flags.FlagToStringValue(p, cmd, networkIdFlag),
NetworkId: flags.FlagToStringPointer(p, cmd, networkIdFlag),
}

p.DebugInputModel(model)
return &model, nil
}

func buildRequest(ctx context.Context, model *inputModel, apiClient *iaas.APIClient) iaas.ApiListNicsRequest {
req := apiClient.ListNics(ctx, model.ProjectId, model.Region, model.NetworkId)
func buildProjectRequest(ctx context.Context, model *inputModel, apiClient *iaas.APIClient) iaas.ApiListProjectNICsRequest {
req := apiClient.ListProjectNICs(ctx, model.ProjectId, model.Region)
if model.LabelSelector != nil {
req = req.LabelSelector(*model.LabelSelector)
}

return req
}

func outputResult(p *print.Printer, outputFormat string, nics []iaas.NIC) error {
func buildNetworkRequest(ctx context.Context, model *inputModel, apiClient *iaas.APIClient) iaas.ApiListNicsRequest {
req := apiClient.ListNics(ctx, model.ProjectId, model.Region, *model.NetworkId)
if model.LabelSelector != nil {
req = req.LabelSelector(*model.LabelSelector)
}

return req
}

func outputProjectResult(p *print.Printer, outputFormat string, nics []iaas.NIC) error {
return p.OutputResult(outputFormat, nics, func() error {
slices.SortFunc(nics, func(a, b iaas.NIC) int {
return cmp.Compare(*a.NetworkId, *b.NetworkId)
})

table := tables.NewTable()
table.SetHeader("ID", "NAME", "NETWORK ID", "NIC SECURITY", "DEVICE ID", "IPv4 ADDRESS", "STATUS", "TYPE")

for _, nic := range nics {
table.AddRow(
utils.PtrString(nic.Id),
utils.PtrString(nic.Name),
utils.PtrString(nic.NetworkId),
utils.PtrString(nic.NicSecurity),
utils.PtrString(nic.Device),
utils.PtrString(nic.Ipv4),
utils.PtrString(nic.Status),
utils.PtrString(nic.Type),
)
table.AddSeparator()
}

p.Outputln(table.Render())
return nil
})
}

func outputNetworkResult(p *print.Printer, outputFormat string, nics []iaas.NIC) error {
return p.OutputResult(outputFormat, nics, func() error {
table := tables.NewTable()
table.SetHeader("ID", "NAME", "NIC SECURITY", "DEVICE ID", "IPv4 ADDRESS", "STATUS", "TYPE")
Expand Down
94 changes: 87 additions & 7 deletions internal/cmd/network-interface/list/list_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,15 +53,24 @@ func fixtureInputModel(mods ...func(model *inputModel)) *inputModel {
},
Limit: utils.Ptr(int64(10)),
LabelSelector: utils.Ptr(testLabelSelector),
NetworkId: testNetworkId,
NetworkId: utils.Ptr(testNetworkId),
}
for _, mod := range mods {
mod(model)
}
return model
}

func fixtureRequest(mods ...func(request *iaas.ApiListNicsRequest)) iaas.ApiListNicsRequest {
func fixtureProjectRequest(mods ...func(request *iaas.ApiListProjectNICsRequest)) iaas.ApiListProjectNICsRequest {
request := testClient.ListProjectNICs(testCtx, testProjectId, testRegion)
request = request.LabelSelector(testLabelSelector)
for _, mod := range mods {
mod(&request)
}
return request
}

func fixtureNetworkRequest(mods ...func(request *iaas.ApiListNicsRequest)) iaas.ApiListNicsRequest {
request := testClient.ListNics(testCtx, testProjectId, testRegion, testNetworkId)
request = request.LabelSelector(testLabelSelector)
for _, mod := range mods {
Expand Down Expand Up @@ -148,7 +157,35 @@ func TestParseInput(t *testing.T) {
}
}

func TestBuildRequest(t *testing.T) {
func TestBuildProjectRequest(t *testing.T) {
tests := []struct {
description string
model *inputModel
expectedRequest iaas.ApiListProjectNICsRequest
}{
{
description: "base",
model: fixtureInputModel(),
expectedRequest: fixtureProjectRequest(),
},
}

for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
request := buildProjectRequest(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 TestBuildNetworkRequest(t *testing.T) {
tests := []struct {
description string
model *inputModel
Expand All @@ -157,13 +194,13 @@ func TestBuildRequest(t *testing.T) {
{
description: "base",
model: fixtureInputModel(),
expectedRequest: fixtureRequest(),
expectedRequest: fixtureNetworkRequest(),
},
}

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

diff := cmp.Diff(request, tt.expectedRequest,
cmp.AllowUnexported(tt.expectedRequest),
Expand All @@ -176,7 +213,7 @@ func TestBuildRequest(t *testing.T) {
}
}

func TestOutputResult(t *testing.T) {
func TestOutputProjectResult(t *testing.T) {
type args struct {
outputFormat string
nics []iaas.NIC
Expand All @@ -191,12 +228,55 @@ func TestOutputResult(t *testing.T) {
args: args{},
wantErr: false,
},
{
name: "empty NIC in NIC-slice",
args: args{
outputFormat: print.PrettyOutputFormat,
nics: []iaas.NIC{{}},
},
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 := outputProjectResult(p, tt.args.outputFormat, tt.args.nics); (err != nil) != tt.wantErr {
t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}

func TestOutputNetworkResult(t *testing.T) {
type args struct {
outputFormat string
nics []iaas.NIC
}
tests := []struct {
name string
args args
wantErr bool
}{
{
name: "empty",
args: args{},
wantErr: false,
},
{
name: "empty NIC in NIC-slice",
args: args{
outputFormat: print.PrettyOutputFormat,
nics: []iaas.NIC{{}},
},
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.args.outputFormat, tt.args.nics); (err != nil) != tt.wantErr {
if err := outputNetworkResult(p, tt.args.outputFormat, tt.args.nics); (err != nil) != tt.wantErr {
t.Errorf("outputResult() error = %v, wantErr %v", err, tt.wantErr)
}
})
Expand Down
Loading