From bb877ec14e9782b320cd63cb38e77b7f39d3f32f Mon Sep 17 00:00:00 2001 From: Ilja Rotar Date: Tue, 8 Sep 2026 14:13:50 +0200 Subject: [PATCH 1/4] list external members test --- cmd/admin/v2/network.go | 105 +++++++++++++++++++++++++- cmd/tableprinters/common.go | 6 ++ cmd/tableprinters/network.go | 33 +++++++++ go.mod | 12 +-- go.sum | 27 ++++--- tests/e2e/admin/network_test.go | 127 ++++++++++++++++++++++++++++++++ 6 files changed, 289 insertions(+), 21 deletions(-) diff --git a/cmd/admin/v2/network.go b/cmd/admin/v2/network.go index e924374..0855bbf 100644 --- a/cmd/admin/v2/network.go +++ b/cmd/admin/v2/network.go @@ -103,7 +103,44 @@ func newNetworkCmd(c *config.Config) *cobra.Command { }, } - return genericcli.NewCmds(cmdsConfig) + networkListExternalMembersCmd := &cobra.Command{ + Use: "list-external-members ", + Short: "lists external members of the network", + Long: "lists switch ports that are members of this network but are not connected to any registered machine.", + RunE: func(cmd *cobra.Command, args []string) error { + return w.listExternalMembers(args) + }, + } + + networkListExternalMembersCmd.Flags().String("partition", "", "filter by partition") + networkListExternalMembersCmd.Flags().String("rack", "", "filter by rack") + networkListExternalMembersCmd.Flags().String("switch", "", "filter by switch") + + networkAddExternalMembersCmd := &cobra.Command{ + Use: "add-external-members ", + Short: "adds external members to the network", + Long: "adds switch ports of a rack as members to the network.", + RunE: func(cmd *cobra.Command, args []string) error { + return w.addExternalMembers(args) + }, + } + + networkAddExternalMembersCmd.Flags().StringSlice("rack", nil, "rack of the external members") + networkAddExternalMembersCmd.Flags().StringSlice("ports", nil, "ports to add to the network") + + networkRemoveExternalMembersCmd := &cobra.Command{ + Use: "remove-external-members ", + Short: "removes external members from the network", + Long: "removes switch ports of a rack from the network.", + RunE: func(cmd *cobra.Command, args []string) error { + return w.removeExternalMembers(args) + }, + } + + networkRemoveExternalMembersCmd.Flags().StringSlice("rack", nil, "rack of the external members") + networkRemoveExternalMembersCmd.Flags().StringSlice("ports", nil, "ports to add to the network") + + return genericcli.NewCmds(cmdsConfig, networkListExternalMembersCmd, networkAddExternalMembersCmd, networkRemoveExternalMembersCmd) } func (c *networkCmd) Get(id string) (*apiv2.Network, error) { @@ -223,6 +260,72 @@ func (c *networkCmd) Update(rq *adminv2.NetworkServiceUpdateRequest) (*apiv2.Net return resp.Network, nil } +func (c *networkCmd) listExternalMembers(args []string) error { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + nw, err := genericcli.GetExactlyOneArg(args) + if err != nil { + return err + } + + res, err := c.c.Client.Adminv2().Network().ListExternalMembers(ctx, &adminv2.NetworkServiceListExternalMembersRequest{ + Network: nw, + Query: &apiv2.ExternalNetworkMemberQuery{ + Switch: pointer.PointerOrNil(viper.GetString("switch")), + Rack: pointer.PointerOrNil(viper.GetString("rack")), + Partition: pointer.PointerOrNil(viper.GetString("partition")), + }, + }) + if err != nil { + return err + } + + return c.c.ListPrinter.Print(res) +} + +func (c *networkCmd) addExternalMembers(args []string) error { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + nw, err := genericcli.GetExactlyOneArg(args) + if err != nil { + return err + } + + res, err := c.c.Client.Adminv2().Network().AddExternalMembers(ctx, &adminv2.NetworkServiceAddExternalMembersRequest{ + Network: nw, + Rack: viper.GetString("rack"), + Ports: viper.GetStringSlice("ports"), + }) + if err != nil { + return err + } + + return c.c.ListPrinter.Print(res) +} + +func (c *networkCmd) removeExternalMembers(args []string) error { + ctx, cancel := c.c.NewRequestContext() + defer cancel() + + nw, err := genericcli.GetExactlyOneArg(args) + if err != nil { + return err + } + + res, err := c.c.Client.Adminv2().Network().RemoveExternalMembers(ctx, &adminv2.NetworkServiceRemoveExternalMembersRequest{ + Network: nw, + Rack: viper.GetString("rack"), + Ports: viper.GetStringSlice("ports"), + }) + if err != nil { + return err + } + + return c.c.ListPrinter.Print(res) +} + func (c *networkCmd) Convert(r *apiv2.Network) (string, *adminv2.NetworkServiceCreateRequest, *adminv2.NetworkServiceUpdateRequest, error) { addressFamily, err := helpers.AddressFamilyFromPrefixes(r.Prefixes...) if err != nil { diff --git a/cmd/tableprinters/common.go b/cmd/tableprinters/common.go index f7c820f..b5ed197 100644 --- a/cmd/tableprinters/common.go +++ b/cmd/tableprinters/common.go @@ -60,6 +60,12 @@ func (t *TablePrinter) ToHeaderAndRows(data any, wide bool) ([]string, [][]strin return t.NetworkTable(pointer.WrapInSlice(d), wide) case []*apiv2.Network: return t.NetworkTable(d, wide) + case *adminv2.NetworkServiceListExternalMembersResponse: + return t.NetworkExternalMembersTable(d) + case *adminv2.NetworkServiceAddExternalMembersResponse: + return t.NetworkExternalMembersChangedTable(d.Network, d.Switches) + case *adminv2.NetworkServiceRemoveExternalMembersResponse: + return t.NetworkExternalMembersChangedTable(d.Network, d.Switches) case *apiv2.Machine: return t.MachineTable(pointer.WrapInSlice(d), wide) diff --git a/cmd/tableprinters/network.go b/cmd/tableprinters/network.go index b6b6306..7179ee2 100644 --- a/cmd/tableprinters/network.go +++ b/cmd/tableprinters/network.go @@ -6,6 +6,7 @@ import ( "github.com/fatih/color" "github.com/metal-stack/api/go/enum" + adminv2 "github.com/metal-stack/api/go/metalstack/admin/v2" apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" "github.com/metal-stack/metal-lib/pkg/genericcli" "github.com/metal-stack/metal-lib/pkg/pointer" @@ -68,6 +69,38 @@ func (t *TablePrinter) NetworkTable(data []*apiv2.Network, wide bool) ([]string, return header, rows, nil } +func (t *TablePrinter) NetworkExternalMembersTable(data *adminv2.NetworkServiceListExternalMembersResponse) ([]string, [][]string, error) { + var ( + rows = [][]string{} + header = []string{"NETWORK", "SWITCH", "PORTS"} + ) + + if len(data.Members) < 1 { + return header, append(rows, []string{data.Network}), nil + } + + for i, member := range data.Members { + for j, port := range member.Ports { + var ( + first, second string + ) + if i == 0 && j == 0 { + first = data.Network + } + if j == 0 { + second = member.Switch + } + rows = append(rows, []string{first, second, port}) + } + } + + return header, rows, nil +} + +func (t *TablePrinter) NetworkExternalMembersChangedTable(network *apiv2.Network, switches []*apiv2.Switch) ([]string, [][]string, error) { + panic("unimplemented") +} + func renderNetworkRow(prefix string, n *apiv2.Network, wide bool) ([]string, error) { var ( id = fmt.Sprintf("%s%s", prefix, n.Id) diff --git a/go.mod b/go.mod index eeb669c..2e9e89d 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/fatih/color v1.19.0 github.com/google/go-cmp v0.7.0 github.com/google/uuid v1.6.0 - github.com/metal-stack/api v0.5.1 + github.com/metal-stack/api v0.5.6-0.20260903115825-6286b8ccc7d3 github.com/metal-stack/metal-lib v0.26.3 github.com/metal-stack/v v1.0.3 github.com/spf13/afero v1.15.0 @@ -23,8 +23,9 @@ require ( require ( buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.1 // indirect - buf.build/go/protovalidate v1.3.0 // indirect + buf.build/go/protovalidate v1.4.0 // indirect buf.build/go/protoyaml v0.7.0 // indirect + cel.dev/cel-go v0.32.0 // indirect cel.dev/expr v0.25.3 // indirect filippo.io/edwards25519 v1.2.0 // indirect github.com/akutz/memconn v0.1.0 // indirect @@ -52,12 +53,11 @@ require ( github.com/golang-jwt/jwt/v5 v5.3.1 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/google/btree v1.1.3 // indirect - github.com/google/cel-go v0.31.0 // indirect github.com/hdevalence/ed25519consensus v0.2.0 // indirect github.com/huin/goupnp v1.3.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jsimonetti/rtnetlink v1.4.2 // indirect - github.com/klauspost/compress v1.19.2 // indirect + github.com/klauspost/compress v1.20.0 // indirect github.com/klauspost/connect-compress/v2 v2.1.1 // indirect github.com/mattn/go-colorable v0.1.15 // indirect github.com/mattn/go-isatty v0.0.24 // indirect @@ -102,8 +102,8 @@ require ( golang.org/x/time v0.15.0 // indirect golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect golang.zx2c4.com/wireguard/windows v1.0.1 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260825221802-da73d73af1c5 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260825221802-da73d73af1c5 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260831171406-18b4a7587f8a // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260831171406-18b4a7587f8a // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect gvisor.dev/gvisor v0.0.0-20260224225140-573d5e7127a8 // indirect diff --git a/go.sum b/go.sum index 2ffa8a5..ecc4505 100644 --- a/go.sum +++ b/go.sum @@ -2,10 +2,12 @@ 9fans.net/go v0.0.8-0.20250307142834-96bdba94b63f/go.mod h1:hHyrZRryGqVdqrknjq5OWDLGCTJ2NeEvtrpR96mjraM= buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.1 h1:Slv0uGxx219srASyiaI5C9cDlyG8kNDcXpTSYcuAeE4= buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.1/go.mod h1:TCt1lluMFnctISJXvkIQ4x3ABrPuUKCWKyjKdkJNBpw= -buf.build/go/protovalidate v1.3.0 h1:8ITcnZGkAHx6TyhZvro+iET/AyqU8gEWQJK2WsT62ms= -buf.build/go/protovalidate v1.3.0/go.mod h1:82s5g+rFRj1CZPiLv6OTA31jBu2fpq7mLXHwa9mZfEs= +buf.build/go/protovalidate v1.4.0 h1:UjLrYbt5VX7+TMOs2+pG5FhZhIG1mSfK4EIopbb4LcM= +buf.build/go/protovalidate v1.4.0/go.mod h1:8vJfzNT6NIG2qm3uFsJDXMlRmG+bQJzbcIn1Aa0vPGs= buf.build/go/protoyaml v0.7.0 h1:z4oVoFicbpPefhT7WAykxUdfp0yEQlhMQ2mCZOY5V38= buf.build/go/protoyaml v0.7.0/go.mod h1:+a0cavd0uMvirb87xdu2ZMMmjlIQoiH/N2Ich5MGSQ0= +cel.dev/cel-go v0.32.0 h1:irvpFKr5EuGPyxeME03ERh0rii1TX+BDAnB9eL3IvNk= +cel.dev/cel-go v0.32.0/go.mod h1:DnVip7tpJSsgZymwfT+m1tnEVy3ivAjSMXPx12YrMkU= cel.dev/expr v0.25.3 h1:A2jO8jwOugrrovveCWfj0KEZOfqiLgAcwjpHPhzIGw0= cel.dev/expr v0.25.3/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= connectrpc.com/connect v1.20.0 h1:6TNDAB+WeNd2uolWNlYczB5E0KNNaVMNUEx8JEUsPmQ= @@ -139,8 +141,6 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= -github.com/google/cel-go v0.31.0 h1:H0bhpFTqOvmHrBGrWKp7ZlhBm5Hh8PYUEXnwxT1LL7A= -github.com/google/cel-go v0.31.0/go.mod h1:X0bD6iVNR8pkROSOoHVdgTkzmRcosof7WQqCD6wcMc8= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-tpm v0.9.4 h1:awZRf9FwOeTunQmHoDYSHJps3ie6f1UlhS1fOdPEt1I= @@ -165,8 +165,8 @@ github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9Y github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jsimonetti/rtnetlink v1.4.2 h1:Df9w9TZ3npHTyDn0Ev9e1uzmN2odmXd0QX+J5GTEn90= github.com/jsimonetti/rtnetlink v1.4.2/go.mod h1:92s6LJdE+1iOrw+F2/RO7LYI2Qd8pPpFNNUYW06gcoM= -github.com/klauspost/compress v1.19.2 h1:hMRETovs/pu/dVWN7zIT1PGG8t509MwT6bO7XSi26R8= -github.com/klauspost/compress v1.19.2/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= +github.com/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA= +github.com/klauspost/compress v1.20.0/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI= github.com/klauspost/connect-compress/v2 v2.1.1 h1:ycZNp4rWOZBodVE2Ls5AzK4aHkyK+GteEfzRZgKNs+c= github.com/klauspost/connect-compress/v2 v2.1.1/go.mod h1:9oilsPHJMzGKkjafSBk9J7iVo4mO+dw0G0KSdVpnlVE= github.com/kortschak/wol v0.0.0-20200729010619-da482cc4850a h1:+RR6SqnTkDLWyICxS1xpjCi/3dhyV+TgZwA6Ww3KncQ= @@ -191,8 +191,8 @@ github.com/mdlayher/sdnotify v1.0.0 h1:Ma9XeLVN/l0qpyx1tNeMSeTjCPH6NtuD6/N9XdTlQ github.com/mdlayher/sdnotify v1.0.0/go.mod h1:HQUmpM4XgYkhDLtd+Uad8ZFK1T9D5+pNxnXQjCeJlGE= github.com/mdlayher/socket v0.6.1 h1:M7uj2NtuujUY4mYr1C57NmfNiRHbkKpnBxO856lsc3A= github.com/mdlayher/socket v0.6.1/go.mod h1:+/SGtqc9V+5dAuRgQsU0fGBI+oRDiW7O2Obx10OIWfg= -github.com/metal-stack/api v0.5.1 h1:cA4i56RYXwcda/EkEWb2MG/YGQAEqJ4RvwxS7RtdUaA= -github.com/metal-stack/api v0.5.1/go.mod h1:QbDG4YRFIkDf1z6cGmeu0m/791IAMyk8/9U/lsrxH8E= +github.com/metal-stack/api v0.5.6-0.20260903115825-6286b8ccc7d3 h1:rRyXXZMnsrgGva8ti1JUWobtqEEr38BZrYmY2KHnbl4= +github.com/metal-stack/api v0.5.6-0.20260903115825-6286b8ccc7d3/go.mod h1:rWdj0dTAg2w/WuQhIZ+JVp9b68v6VVCm1k7sLx4U+PQ= github.com/metal-stack/metal-lib v0.26.3 h1:K5gLoD65m6p3l6qCPrfavIdvNdWfmF2QdXrvU2URaZs= github.com/metal-stack/metal-lib v0.26.3/go.mod h1:cNXjPBs8SFnjqfBobuSbm5mDk6E/jS8PVeIOrV/7POE= github.com/metal-stack/v v1.0.3 h1:Sh2oBlnxrCUD+mVpzfC8HiqL045YWkxs0gpTvkjppqs= @@ -333,17 +333,16 @@ golang.zx2c4.com/wireguard v0.0.0-20260522210424-ecfc5a8d5446 h1:cqHQ3AycTHvM2R7 golang.zx2c4.com/wireguard v0.0.0-20260522210424-ecfc5a8d5446/go.mod h1:rpwXGsirqLqN2L0JDJQlwOboGHmptD5ZD6T2VmcqhTw= golang.zx2c4.com/wireguard/windows v1.0.1 h1:eOxiDVbywPC+ZQqvdCK7x+ZwWXKbYv50TtH8ysFIbw8= golang.zx2c4.com/wireguard/windows v1.0.1/go.mod h1:+fbT3FFdX4zzYDLwJh5+HPEcNN/3HyNdzhNSVsQM+zs= -google.golang.org/genproto/googleapis/api v0.0.0-20260825221802-da73d73af1c5 h1:izFU9hz7aeLI/Mi1J0991ae+xcwRLr7hTqWnB/9aIIU= -google.golang.org/genproto/googleapis/api v0.0.0-20260825221802-da73d73af1c5/go.mod h1:3LhxRw4YYkf+ylAfgaY9JlVLFKhokkCV8duhLLe7+t0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260825221802-da73d73af1c5 h1:1VUiZAXyC+zmiFYi+WLtBzr68Cj8wOofHjjrA/kkizc= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260825221802-da73d73af1c5/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= +google.golang.org/genproto/googleapis/api v0.0.0-20260831171406-18b4a7587f8a h1:i3TAXhpKc7TUP1VAPiBBrv45kamjoizCC3rOC0cAbOs= +google.golang.org/genproto/googleapis/api v0.0.0-20260831171406-18b4a7587f8a/go.mod h1:CvYJHpbzPlT0fb/PsgtAamdwru/GVxUsomFdXTpOTI8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260831171406-18b4a7587f8a h1:3Dnd1cDaZlB68lziofO+bJXpjOy8UfRv8Unt+yH8tQ4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260831171406-18b4a7587f8a/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= google.golang.org/grpc v1.83.2 h1:EManeRomTObA0BU7I8vXgg/78uE5MJ9M8B39EX2WscU= google.golang.org/grpc v1.83.2/go.mod h1:YPI1hK3kDked6iHvgX3tR0y+nX/qpMFKhPgFsokw1S8= google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/tests/e2e/admin/network_test.go b/tests/e2e/admin/network_test.go index d0e82f9..5d56a4e 100644 --- a/tests/e2e/admin/network_test.go +++ b/tests/e2e/admin/network_test.go @@ -390,3 +390,130 @@ func Test_AdminNetworkCmd_Update(t *testing.T) { tt.TestCmd(t) } } + +func Test_AdminNetworkCmd_ListExternalMembers(t *testing.T) { + var ( + p01r01switch01 = &apiv2.Switch{ + Id: "p01r01switch01", + Partition: "p01", + Rack: new("p01r01"), + } + p01r02switch01 = &apiv2.Switch{ + Id: "p01r02switch01", + Partition: "p01", + Rack: new("p01r02"), + } + p02r01switch01 = &apiv2.Switch{ + Id: "p02r01switch01", + Partition: "p02", + Rack: new("p02r01"), + } + ) + + tests := []*e2e.Test[adminv2.NetworkServiceListResponse, apiv2.Network]{ + { + Name: "list empty members", + CmdArgs: []string{"admin", "network", "list-external-members", + testresources.Network2().Id, + }, + NewRootCmd: e2erootcmd.NewRootCmd(t, &e2erootcmd.TestConfig{ + ClientCalls: []client.ClientCall{ + { + WantRequest: &adminv2.NetworkServiceListExternalMembersRequest{ + Network: testresources.Network2().Id, + Query: &apiv2.ExternalNetworkMemberQuery{}, + }, + WantResponse: func() connect.AnyResponse { + return connect.NewResponse(&adminv2.NetworkServiceListExternalMembersResponse{ + Network: testresources.Network1().Id, + Members: []*apiv2.ExternalNetworkMember{}, + }) + }, + }, + }, + }), + WantTable: new(` + NETWORK SWITCH PORTS + 6988ebb0-9531-4f9b-a893-d7868258e2ef + `), + WantWideTable: new(` + NETWORK SWITCH PORTS + 6988ebb0-9531-4f9b-a893-d7868258e2ef + `), + Template: new("{{ .network }} {{ range .members }}{{ .switch }}{{ end }}"), + WantTemplate: new(`6988ebb0-9531-4f9b-a893-d7868258e2ef`), + WantMarkdown: new(` + | NETWORK | SWITCH | PORTS | + |--------------------------------------|--------|-------| + | 6988ebb0-9531-4f9b-a893-d7868258e2ef | | | + `), + }, + { + Name: "list all", + CmdArgs: []string{"admin", "network", "list-external-members", + testresources.Network1().Id, + }, + NewRootCmd: e2erootcmd.NewRootCmd(t, &e2erootcmd.TestConfig{ + ClientCalls: []client.ClientCall{ + { + WantRequest: &adminv2.NetworkServiceListExternalMembersRequest{ + Network: testresources.Network1().Id, + Query: &apiv2.ExternalNetworkMemberQuery{}, + }, + WantResponse: func() connect.AnyResponse { + return connect.NewResponse(&adminv2.NetworkServiceListExternalMembersResponse{ + Network: testresources.Network1().Id, + Members: []*apiv2.ExternalNetworkMember{ + { + Switch: p01r01switch01.Id, + Ports: []string{"Ethernet0", "Ethernet1"}, + }, + { + Switch: p01r02switch01.Id, + Ports: []string{"Ethernet0", "Ethernet1"}, + }, + { + Switch: p02r01switch01.Id, + Ports: []string{"Ethernet0"}, + }, + }, + }) + }, + }, + }, + }), + WantTable: new(` + NETWORK SWITCH PORTS + 6988ebb0-9531-4f9b-a893-d7868258e2ef p01r01switch01 Ethernet0 + Ethernet1 + p01r02switch01 Ethernet0 + Ethernet1 + p02r01switch01 Ethernet0 + `), + WantWideTable: new(` + NETWORK SWITCH PORTS + 6988ebb0-9531-4f9b-a893-d7868258e2ef p01r01switch01 Ethernet0 + Ethernet1 + p01r02switch01 Ethernet0 + Ethernet1 + p02r01switch01 Ethernet0 + `), + Template: new("{{ .network }} {{ range .members }}{{ .switch }} {{ end }}"), + WantTemplate: new(` + 6988ebb0-9531-4f9b-a893-d7868258e2ef p01r01switch01 p01r02switch01 p02r01switch01 + `), + WantMarkdown: new(` + | NETWORK | SWITCH | PORTS | + |--------------------------------------|----------------|-----------| + | 6988ebb0-9531-4f9b-a893-d7868258e2ef | p01r01switch01 | Ethernet0 | + | | | Ethernet1 | + | | p01r02switch01 | Ethernet0 | + | | | Ethernet1 | + | | p02r01switch01 | Ethernet0 | + `), + }, + } + for _, tt := range tests { + tt.TestCmd(t) + } +} From 83020b723fd72699c24b8a00f64060c516128923 Mon Sep 17 00:00:00 2001 From: Ilja Rotar Date: Wed, 9 Sep 2026 11:17:11 +0200 Subject: [PATCH 2/4] table printer for network member funcs --- cmd/admin/v2/network.go | 4 +- cmd/tableprinters/common.go | 6 +- cmd/tableprinters/network.go | 45 ++-- go.mod | 2 +- go.sum | 4 +- tests/e2e/admin/network_test.go | 379 ++++++++++++++++++++++++++++---- 6 files changed, 379 insertions(+), 61 deletions(-) diff --git a/cmd/admin/v2/network.go b/cmd/admin/v2/network.go index 0855bbf..5a30897 100644 --- a/cmd/admin/v2/network.go +++ b/cmd/admin/v2/network.go @@ -125,7 +125,7 @@ func newNetworkCmd(c *config.Config) *cobra.Command { }, } - networkAddExternalMembersCmd.Flags().StringSlice("rack", nil, "rack of the external members") + networkAddExternalMembersCmd.Flags().String("rack", "", "rack of the external members") networkAddExternalMembersCmd.Flags().StringSlice("ports", nil, "ports to add to the network") networkRemoveExternalMembersCmd := &cobra.Command{ @@ -137,7 +137,7 @@ func newNetworkCmd(c *config.Config) *cobra.Command { }, } - networkRemoveExternalMembersCmd.Flags().StringSlice("rack", nil, "rack of the external members") + networkRemoveExternalMembersCmd.Flags().String("rack", "", "rack of the external members") networkRemoveExternalMembersCmd.Flags().StringSlice("ports", nil, "ports to add to the network") return genericcli.NewCmds(cmdsConfig, networkListExternalMembersCmd, networkAddExternalMembersCmd, networkRemoveExternalMembersCmd) diff --git a/cmd/tableprinters/common.go b/cmd/tableprinters/common.go index b5ed197..0695619 100644 --- a/cmd/tableprinters/common.go +++ b/cmd/tableprinters/common.go @@ -61,11 +61,11 @@ func (t *TablePrinter) ToHeaderAndRows(data any, wide bool) ([]string, [][]strin case []*apiv2.Network: return t.NetworkTable(d, wide) case *adminv2.NetworkServiceListExternalMembersResponse: - return t.NetworkExternalMembersTable(d) + return t.NetworkExternalMembersTable(d.Network, d.Members, wide) case *adminv2.NetworkServiceAddExternalMembersResponse: - return t.NetworkExternalMembersChangedTable(d.Network, d.Switches) + return t.NetworkExternalMembersTable(d.Network, d.Members, wide) case *adminv2.NetworkServiceRemoveExternalMembersResponse: - return t.NetworkExternalMembersChangedTable(d.Network, d.Switches) + return t.NetworkExternalMembersTable(d.Network, d.Members, wide) case *apiv2.Machine: return t.MachineTable(pointer.WrapInSlice(d), wide) diff --git a/cmd/tableprinters/network.go b/cmd/tableprinters/network.go index 7179ee2..deb34ab 100644 --- a/cmd/tableprinters/network.go +++ b/cmd/tableprinters/network.go @@ -6,7 +6,6 @@ import ( "github.com/fatih/color" "github.com/metal-stack/api/go/enum" - adminv2 "github.com/metal-stack/api/go/metalstack/admin/v2" apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" "github.com/metal-stack/metal-lib/pkg/genericcli" "github.com/metal-stack/metal-lib/pkg/pointer" @@ -69,38 +68,54 @@ func (t *TablePrinter) NetworkTable(data []*apiv2.Network, wide bool) ([]string, return header, rows, nil } -func (t *TablePrinter) NetworkExternalMembersTable(data *adminv2.NetworkServiceListExternalMembersResponse) ([]string, [][]string, error) { +func (t *TablePrinter) NetworkExternalMembersTable(network *apiv2.Network, members []*apiv2.ExternalNetworkMember, wide bool) ([]string, [][]string, error) { var ( - rows = [][]string{} - header = []string{"NETWORK", "SWITCH", "PORTS"} + rows = [][]string{} ) - if len(data.Members) < 1 { - return header, append(rows, []string{data.Network}), nil + header := []string{"ID", "NAME", "SWITCH", "PARTITION", "RACK", "PORTS"} + if wide { + header = []string{"ID", "NAME", "TYPE", "PREFIXES", "SWITCH", "PARTITION", "RACK", "PORTS"} + } + + if len(members) < 1 { + return header, append(rows, []string{network.Id}), nil } - for i, member := range data.Members { + for i, member := range members { for j, port := range member.Ports { var ( - first, second string + id string + name string + nwType string + prefixes string + sw string + partition string + rack string ) if i == 0 && j == 0 { - first = data.Network + id = network.Id + name = pointer.SafeDeref(network.Name) + nwType = network.Type.String() + prefixes = strings.Join(network.Prefixes, ",") } if j == 0 { - second = member.Switch + sw = member.Switch + partition = member.Partition + rack = member.Rack + } + + if wide { + rows = append(rows, []string{id, name, nwType, prefixes, sw, partition, rack, port}) + } else { + rows = append(rows, []string{id, name, sw, partition, rack, port}) } - rows = append(rows, []string{first, second, port}) } } return header, rows, nil } -func (t *TablePrinter) NetworkExternalMembersChangedTable(network *apiv2.Network, switches []*apiv2.Switch) ([]string, [][]string, error) { - panic("unimplemented") -} - func renderNetworkRow(prefix string, n *apiv2.Network, wide bool) ([]string, error) { var ( id = fmt.Sprintf("%s%s", prefix, n.Id) diff --git a/go.mod b/go.mod index 2e9e89d..c1cc6a1 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/fatih/color v1.19.0 github.com/google/go-cmp v0.7.0 github.com/google/uuid v1.6.0 - github.com/metal-stack/api v0.5.6-0.20260903115825-6286b8ccc7d3 + github.com/metal-stack/api v0.5.6-0.20260909090323-c58acb0d1dba github.com/metal-stack/metal-lib v0.26.3 github.com/metal-stack/v v1.0.3 github.com/spf13/afero v1.15.0 diff --git a/go.sum b/go.sum index ecc4505..12aeba3 100644 --- a/go.sum +++ b/go.sum @@ -191,8 +191,8 @@ github.com/mdlayher/sdnotify v1.0.0 h1:Ma9XeLVN/l0qpyx1tNeMSeTjCPH6NtuD6/N9XdTlQ github.com/mdlayher/sdnotify v1.0.0/go.mod h1:HQUmpM4XgYkhDLtd+Uad8ZFK1T9D5+pNxnXQjCeJlGE= github.com/mdlayher/socket v0.6.1 h1:M7uj2NtuujUY4mYr1C57NmfNiRHbkKpnBxO856lsc3A= github.com/mdlayher/socket v0.6.1/go.mod h1:+/SGtqc9V+5dAuRgQsU0fGBI+oRDiW7O2Obx10OIWfg= -github.com/metal-stack/api v0.5.6-0.20260903115825-6286b8ccc7d3 h1:rRyXXZMnsrgGva8ti1JUWobtqEEr38BZrYmY2KHnbl4= -github.com/metal-stack/api v0.5.6-0.20260903115825-6286b8ccc7d3/go.mod h1:rWdj0dTAg2w/WuQhIZ+JVp9b68v6VVCm1k7sLx4U+PQ= +github.com/metal-stack/api v0.5.6-0.20260909090323-c58acb0d1dba h1:4Dyhunb5seTQSY3hgGqZ5C0QT638xkEx5yS58YVGjG8= +github.com/metal-stack/api v0.5.6-0.20260909090323-c58acb0d1dba/go.mod h1:rWdj0dTAg2w/WuQhIZ+JVp9b68v6VVCm1k7sLx4U+PQ= github.com/metal-stack/metal-lib v0.26.3 h1:K5gLoD65m6p3l6qCPrfavIdvNdWfmF2QdXrvU2URaZs= github.com/metal-stack/metal-lib v0.26.3/go.mod h1:cNXjPBs8SFnjqfBobuSbm5mDk6E/jS8PVeIOrV/7POE= github.com/metal-stack/v v1.0.3 h1:Sh2oBlnxrCUD+mVpzfC8HiqL045YWkxs0gpTvkjppqs= diff --git a/tests/e2e/admin/network_test.go b/tests/e2e/admin/network_test.go index 5d56a4e..c7b6be9 100644 --- a/tests/e2e/admin/network_test.go +++ b/tests/e2e/admin/network_test.go @@ -425,7 +425,7 @@ func Test_AdminNetworkCmd_ListExternalMembers(t *testing.T) { }, WantResponse: func() connect.AnyResponse { return connect.NewResponse(&adminv2.NetworkServiceListExternalMembersResponse{ - Network: testresources.Network1().Id, + Network: testresources.Network1(), Members: []*apiv2.ExternalNetworkMember{}, }) }, @@ -433,20 +433,20 @@ func Test_AdminNetworkCmd_ListExternalMembers(t *testing.T) { }, }), WantTable: new(` - NETWORK SWITCH PORTS + ID NAME SWITCH PARTITION RACK PORTS 6988ebb0-9531-4f9b-a893-d7868258e2ef - `), + `), WantWideTable: new(` - NETWORK SWITCH PORTS + ID NAME TYPE PREFIXES SWITCH PARTITION RACK PORTS 6988ebb0-9531-4f9b-a893-d7868258e2ef - `), - Template: new("{{ .network }} {{ range .members }}{{ .switch }}{{ end }}"), + `), + Template: new("{{ .network.id }} {{ range .members }}{{ .switch }}{{ end }}"), WantTemplate: new(`6988ebb0-9531-4f9b-a893-d7868258e2ef`), WantMarkdown: new(` - | NETWORK | SWITCH | PORTS | - |--------------------------------------|--------|-------| - | 6988ebb0-9531-4f9b-a893-d7868258e2ef | | | - `), + | ID | NAME | SWITCH | PARTITION | RACK | PORTS | + |--------------------------------------|------|--------|-----------|------|-------| + | 6988ebb0-9531-4f9b-a893-d7868258e2ef | | | | | | + `), }, { Name: "list all", @@ -462,19 +462,25 @@ func Test_AdminNetworkCmd_ListExternalMembers(t *testing.T) { }, WantResponse: func() connect.AnyResponse { return connect.NewResponse(&adminv2.NetworkServiceListExternalMembersResponse{ - Network: testresources.Network1().Id, + Network: testresources.Network1(), Members: []*apiv2.ExternalNetworkMember{ { - Switch: p01r01switch01.Id, - Ports: []string{"Ethernet0", "Ethernet1"}, + Switch: p01r01switch01.Id, + Partition: p01r01switch01.Partition, + Rack: pointer.SafeDeref(p01r01switch01.Rack), + Ports: []string{"Ethernet0", "Ethernet1"}, }, { - Switch: p01r02switch01.Id, - Ports: []string{"Ethernet0", "Ethernet1"}, + Switch: p01r02switch01.Id, + Partition: p01r02switch01.Partition, + Rack: pointer.SafeDeref(p01r02switch01.Rack), + Ports: []string{"Ethernet0", "Ethernet1"}, }, { - Switch: p02r01switch01.Id, - Ports: []string{"Ethernet0"}, + Switch: p02r01switch01.Id, + Partition: p02r01switch01.Partition, + Rack: pointer.SafeDeref(p02r01switch01.Rack), + Ports: []string{"Ethernet0"}, }, }, }) @@ -483,33 +489,330 @@ func Test_AdminNetworkCmd_ListExternalMembers(t *testing.T) { }, }), WantTable: new(` - NETWORK SWITCH PORTS - 6988ebb0-9531-4f9b-a893-d7868258e2ef p01r01switch01 Ethernet0 - Ethernet1 - p01r02switch01 Ethernet0 - Ethernet1 - p02r01switch01 Ethernet0 + ID NAME SWITCH PARTITION RACK PORTS + 6988ebb0-9531-4f9b-a893-d7868258e2ef internet p01r01switch01 p01 p01r01 Ethernet0 + Ethernet1 + p01r02switch01 p01 p01r02 Ethernet0 + Ethernet1 + p02r01switch01 p02 p02r01 Ethernet0 `), WantWideTable: new(` - NETWORK SWITCH PORTS - 6988ebb0-9531-4f9b-a893-d7868258e2ef p01r01switch01 Ethernet0 - Ethernet1 - p01r02switch01 Ethernet0 - Ethernet1 - p02r01switch01 Ethernet0 - `), - Template: new("{{ .network }} {{ range .members }}{{ .switch }} {{ end }}"), + ID NAME TYPE PREFIXES SWITCH PARTITION RACK PORTS + 6988ebb0-9531-4f9b-a893-d7868258e2ef internet NETWORK_TYPE_EXTERNAL 10.0.0.0/16,2001:db8::/32 p01r01switch01 p01 p01r01 Ethernet0 + Ethernet1 + p01r02switch01 p01 p01r02 Ethernet0 + Ethernet1 + p02r01switch01 p02 p02r01 Ethernet0 + `), + Template: new("{{ .network.id }} {{ range .members }}{{ .switch }} {{ end }}"), WantTemplate: new(` 6988ebb0-9531-4f9b-a893-d7868258e2ef p01r01switch01 p01r02switch01 p02r01switch01 `), WantMarkdown: new(` - | NETWORK | SWITCH | PORTS | - |--------------------------------------|----------------|-----------| - | 6988ebb0-9531-4f9b-a893-d7868258e2ef | p01r01switch01 | Ethernet0 | - | | | Ethernet1 | - | | p01r02switch01 | Ethernet0 | - | | | Ethernet1 | - | | p02r01switch01 | Ethernet0 | + | ID | NAME | SWITCH | PARTITION | RACK | PORTS | + |--------------------------------------|----------|----------------|-----------|--------|-----------| + | 6988ebb0-9531-4f9b-a893-d7868258e2ef | internet | p01r01switch01 | p01 | p01r01 | Ethernet0 | + | | | | | | Ethernet1 | + | | | p01r02switch01 | p01 | p01r02 | Ethernet0 | + | | | | | | Ethernet1 | + | | | p02r01switch01 | p02 | p02r01 | Ethernet0 | + `), + }, + { + Name: "query", + CmdArgs: []string{"admin", "network", "list-external-members", + testresources.Network1().Id, + "--partition", p01r01switch01.Partition, + "--rack", *p01r01switch01.Rack, + "--switch", p01r01switch01.Id, + }, + AssertExhaustiveArgs: true, + NewRootCmd: e2erootcmd.NewRootCmd(t, &e2erootcmd.TestConfig{ + ClientCalls: []client.ClientCall{ + { + WantRequest: &adminv2.NetworkServiceListExternalMembersRequest{ + Network: testresources.Network1().Id, + Query: &apiv2.ExternalNetworkMemberQuery{ + Switch: new(p01r01switch01.Id), + Rack: p01r01switch01.Rack, + Partition: new(p01r01switch01.Partition), + }, + }, + WantResponse: func() connect.AnyResponse { + return connect.NewResponse(&adminv2.NetworkServiceListExternalMembersResponse{ + Network: testresources.Network1(), + Members: []*apiv2.ExternalNetworkMember{ + { + Switch: p01r01switch01.Id, + Partition: p01r01switch01.Partition, + Rack: pointer.SafeDeref(p01r01switch01.Rack), + Ports: []string{"Ethernet0", "Ethernet1"}, + }, + }, + }) + }, + }, + }, + }), + WantTable: new(` + ID NAME SWITCH PARTITION RACK PORTS + 6988ebb0-9531-4f9b-a893-d7868258e2ef internet p01r01switch01 p01 p01r01 Ethernet0 + Ethernet1 + `), + WantWideTable: new(` + ID NAME TYPE PREFIXES SWITCH PARTITION RACK PORTS + 6988ebb0-9531-4f9b-a893-d7868258e2ef internet NETWORK_TYPE_EXTERNAL 10.0.0.0/16,2001:db8::/32 p01r01switch01 p01 p01r01 Ethernet0 + Ethernet1 + `), + Template: new("{{ .network.id }} {{ range .members }}{{ .switch }} {{ end }}"), + WantTemplate: new(` + 6988ebb0-9531-4f9b-a893-d7868258e2ef p01r01switch01 + `), + WantMarkdown: new(` + | ID | NAME | SWITCH | PARTITION | RACK | PORTS | + |--------------------------------------|----------|----------------|-----------|--------|-----------| + | 6988ebb0-9531-4f9b-a893-d7868258e2ef | internet | p01r01switch01 | p01 | p01r01 | Ethernet0 | + | | | | | | Ethernet1 | + `), + }, + } + for _, tt := range tests { + tt.TestCmd(t) + } +} + +func Test_AdminNetworkCmd_AddExternalMembers(t *testing.T) { + var ( + p01r01switch01 = &apiv2.Switch{ + Id: "p01r01switch01", + Partition: "p01", + Rack: new("p01r01"), + } + p01r01switch02 = &apiv2.Switch{ + Id: "p01r01switch02", + Partition: "p01", + Rack: new("p01r01"), + } + ) + + tests := []*e2e.Test[adminv2.NetworkServiceListResponse, apiv2.Network]{ + { + Name: "add no members to network", + CmdArgs: []string{"admin", "network", "add-external-members", + testresources.Network2().Id, + }, + NewRootCmd: e2erootcmd.NewRootCmd(t, &e2erootcmd.TestConfig{ + ClientCalls: []client.ClientCall{ + { + WantRequest: &adminv2.NetworkServiceAddExternalMembersRequest{ + Network: testresources.Network2().Id, + }, + WantResponse: func() connect.AnyResponse { + return connect.NewResponse(&adminv2.NetworkServiceAddExternalMembersResponse{ + Network: testresources.Network1(), + Members: []*apiv2.ExternalNetworkMember{}, + }) + }, + }, + }, + }), + WantTable: new(` + ID NAME SWITCH PARTITION RACK PORTS + 6988ebb0-9531-4f9b-a893-d7868258e2ef + `), + WantWideTable: new(` + ID NAME TYPE PREFIXES SWITCH PARTITION RACK PORTS + 6988ebb0-9531-4f9b-a893-d7868258e2ef + `), + Template: new("{{ .network.id }} {{ range .members }}{{ .switch }}{{ end }}"), + WantTemplate: new(`6988ebb0-9531-4f9b-a893-d7868258e2ef`), + WantMarkdown: new(` + | ID | NAME | SWITCH | PARTITION | RACK | PORTS | + |--------------------------------------|------|--------|-----------|------|-------| + | 6988ebb0-9531-4f9b-a893-d7868258e2ef | | | | | | + `), + }, + { + Name: "add members to network", + CmdArgs: []string{"admin", "network", "add-external-members", + testresources.Network2().Id, + "--rack", pointer.SafeDeref(p01r01switch01.Rack), + "--ports", "Ethernet0,Ethernet1", + }, + AssertExhaustiveArgs: true, + NewRootCmd: e2erootcmd.NewRootCmd(t, &e2erootcmd.TestConfig{ + ClientCalls: []client.ClientCall{ + { + WantRequest: &adminv2.NetworkServiceAddExternalMembersRequest{ + Network: testresources.Network2().Id, + Rack: pointer.SafeDeref(p01r01switch01.Rack), + Ports: []string{"Ethernet0", "Ethernet1"}, + }, + WantResponse: func() connect.AnyResponse { + return connect.NewResponse(&adminv2.NetworkServiceAddExternalMembersResponse{ + Network: testresources.Network1(), + Members: []*apiv2.ExternalNetworkMember{ + { + Switch: p01r01switch01.Id, + Partition: p01r01switch01.Partition, + Rack: pointer.SafeDeref(p01r01switch01.Rack), + Ports: []string{"Ethernet0", "Ethernet1"}, + }, + { + Switch: p01r01switch02.Id, + Partition: p01r01switch02.Partition, + Rack: pointer.SafeDeref(p01r01switch02.Rack), + Ports: []string{"Ethernet0", "Ethernet1"}, + }, + }, + }) + }, + }, + }, + }), + WantTable: new(` + ID NAME SWITCH PARTITION RACK PORTS + 6988ebb0-9531-4f9b-a893-d7868258e2ef internet p01r01switch01 p01 p01r01 Ethernet0 + Ethernet1 + p01r01switch02 p01 p01r01 Ethernet0 + Ethernet1 + `), + WantWideTable: new(` + ID NAME TYPE PREFIXES SWITCH PARTITION RACK PORTS + 6988ebb0-9531-4f9b-a893-d7868258e2ef internet NETWORK_TYPE_EXTERNAL 10.0.0.0/16,2001:db8::/32 p01r01switch01 p01 p01r01 Ethernet0 + Ethernet1 + p01r01switch02 p01 p01r01 Ethernet0 + Ethernet1 + `), + Template: new("{{ .network.id }} {{ range .members }}{{ .switch }} {{ end }}"), + WantTemplate: new(`6988ebb0-9531-4f9b-a893-d7868258e2ef p01r01switch01 p01r01switch02`), + WantMarkdown: new(` + | ID | NAME | SWITCH | PARTITION | RACK | PORTS | + |--------------------------------------|----------|----------------|-----------|--------|-----------| + | 6988ebb0-9531-4f9b-a893-d7868258e2ef | internet | p01r01switch01 | p01 | p01r01 | Ethernet0 | + | | | | | | Ethernet1 | + | | | p01r01switch02 | p01 | p01r01 | Ethernet0 | + | | | | | | Ethernet1 | + `), + }, + } + for _, tt := range tests { + tt.TestCmd(t) + } +} + +func Test_AdminNetworkCmd_RemoveExternalMembers(t *testing.T) { + var ( + p01r01switch01 = &apiv2.Switch{ + Id: "p01r01switch01", + Partition: "p01", + Rack: new("p01r01"), + } + p01r01switch02 = &apiv2.Switch{ + Id: "p01r01switch02", + Partition: "p01", + Rack: new("p01r01"), + } + ) + + tests := []*e2e.Test[adminv2.NetworkServiceListResponse, apiv2.Network]{ + { + Name: "remove no members from network", + CmdArgs: []string{"admin", "network", "remove-external-members", + testresources.Network2().Id, + }, + NewRootCmd: e2erootcmd.NewRootCmd(t, &e2erootcmd.TestConfig{ + ClientCalls: []client.ClientCall{ + { + WantRequest: &adminv2.NetworkServiceRemoveExternalMembersRequest{ + Network: testresources.Network2().Id, + }, + WantResponse: func() connect.AnyResponse { + return connect.NewResponse(&adminv2.NetworkServiceRemoveExternalMembersResponse{ + Network: testresources.Network1(), + Members: []*apiv2.ExternalNetworkMember{}, + }) + }, + }, + }, + }), + WantTable: new(` + ID NAME SWITCH PARTITION RACK PORTS + 6988ebb0-9531-4f9b-a893-d7868258e2ef + `), + WantWideTable: new(` + ID NAME TYPE PREFIXES SWITCH PARTITION RACK PORTS + 6988ebb0-9531-4f9b-a893-d7868258e2ef + `), + Template: new("{{ .network.id }} {{ range .members }}{{ .switch }}{{ end }}"), + WantTemplate: new(`6988ebb0-9531-4f9b-a893-d7868258e2ef`), + WantMarkdown: new(` + | ID | NAME | SWITCH | PARTITION | RACK | PORTS | + |--------------------------------------|------|--------|-----------|------|-------| + | 6988ebb0-9531-4f9b-a893-d7868258e2ef | | | | | | + `), + }, + { + Name: "remove members members network", + CmdArgs: []string{"admin", "network", "remove-external-members", + testresources.Network2().Id, + "--rack", pointer.SafeDeref(p01r01switch01.Rack), + "--ports", "Ethernet0,Ethernet1", + }, + AssertExhaustiveArgs: true, + NewRootCmd: e2erootcmd.NewRootCmd(t, &e2erootcmd.TestConfig{ + ClientCalls: []client.ClientCall{ + { + WantRequest: &adminv2.NetworkServiceRemoveExternalMembersRequest{ + Network: testresources.Network2().Id, + Rack: pointer.SafeDeref(p01r01switch01.Rack), + Ports: []string{"Ethernet0", "Ethernet1"}, + }, + WantResponse: func() connect.AnyResponse { + return connect.NewResponse(&adminv2.NetworkServiceRemoveExternalMembersResponse{ + Network: testresources.Network1(), + Members: []*apiv2.ExternalNetworkMember{ + { + Switch: p01r01switch01.Id, + Partition: p01r01switch01.Partition, + Rack: pointer.SafeDeref(p01r01switch01.Rack), + Ports: []string{"Ethernet0", "Ethernet1"}, + }, + { + Switch: p01r01switch02.Id, + Partition: p01r01switch02.Partition, + Rack: pointer.SafeDeref(p01r01switch02.Rack), + Ports: []string{"Ethernet0", "Ethernet1"}, + }, + }, + }) + }, + }, + }, + }), + WantTable: new(` + ID NAME SWITCH PARTITION RACK PORTS + 6988ebb0-9531-4f9b-a893-d7868258e2ef internet p01r01switch01 p01 p01r01 Ethernet0 + Ethernet1 + p01r01switch02 p01 p01r01 Ethernet0 + Ethernet1 + `), + WantWideTable: new(` + ID NAME TYPE PREFIXES SWITCH PARTITION RACK PORTS + 6988ebb0-9531-4f9b-a893-d7868258e2ef internet NETWORK_TYPE_EXTERNAL 10.0.0.0/16,2001:db8::/32 p01r01switch01 p01 p01r01 Ethernet0 + Ethernet1 + p01r01switch02 p01 p01r01 Ethernet0 + Ethernet1 + `), + Template: new("{{ .network.id }} {{ range .members }}{{ .switch }} {{ end }}"), + WantTemplate: new(`6988ebb0-9531-4f9b-a893-d7868258e2ef p01r01switch01 p01r01switch02`), + WantMarkdown: new(` + | ID | NAME | SWITCH | PARTITION | RACK | PORTS | + |--------------------------------------|----------|----------------|-----------|--------|-----------| + | 6988ebb0-9531-4f9b-a893-d7868258e2ef | internet | p01r01switch01 | p01 | p01r01 | Ethernet0 | + | | | | | | Ethernet1 | + | | | p01r01switch02 | p01 | p01r01 | Ethernet0 | + | | | | | | Ethernet1 | `), }, } From 45545b846073b523ad435191f20a1138f303374f Mon Sep 17 00:00:00 2001 From: Ilja Rotar Date: Wed, 9 Sep 2026 14:46:15 +0200 Subject: [PATCH 3/4] make --- docs/admin/metalctlv2_admin_component_list.md | 2 +- docs/admin/metalctlv2_admin_network.md | 3 ++ ...tlv2_admin_network_add-external-members.md | 37 ++++++++++++++++++ ...lv2_admin_network_list-external-members.md | 38 +++++++++++++++++++ ...2_admin_network_remove-external-members.md | 37 ++++++++++++++++++ 5 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 docs/admin/metalctlv2_admin_network_add-external-members.md create mode 100644 docs/admin/metalctlv2_admin_network_list-external-members.md create mode 100644 docs/admin/metalctlv2_admin_network_remove-external-members.md diff --git a/docs/admin/metalctlv2_admin_component_list.md b/docs/admin/metalctlv2_admin_component_list.md index 197f61c..1b6ca32 100644 --- a/docs/admin/metalctlv2_admin_component_list.md +++ b/docs/admin/metalctlv2_admin_component_list.md @@ -11,7 +11,7 @@ metalctlv2 admin component list [flags] ``` -h, --help help for list --identifier string lists only component with this identifier - --sort-by strings sort by (comma separated) column(s), sort direction can be changed by appending :asc or :desc behind the column identifier. possible values: identifier|started|type + --sort-by strings sort by (comma separated) column(s), sort direction can be changed by appending :asc or :desc behind the column identifier. possible values: expiration|identifier|started|type --type string lists only component of this type --uuid string lists only component with this uuid ``` diff --git a/docs/admin/metalctlv2_admin_network.md b/docs/admin/metalctlv2_admin_network.md index 70a5409..e576617 100644 --- a/docs/admin/metalctlv2_admin_network.md +++ b/docs/admin/metalctlv2_admin_network.md @@ -28,11 +28,14 @@ networks can be attached to a machine or firewall such that they can communicate ### SEE ALSO * [metalctlv2 admin](metalctlv2_admin.md) - admin commands +* [metalctlv2 admin network add-external-members](metalctlv2_admin_network_add-external-members.md) - adds external members to the network * [metalctlv2 admin network apply](metalctlv2_admin_network_apply.md) - applies one or more networks from a given file * [metalctlv2 admin network create](metalctlv2_admin_network_create.md) - creates the network * [metalctlv2 admin network delete](metalctlv2_admin_network_delete.md) - deletes the network * [metalctlv2 admin network describe](metalctlv2_admin_network_describe.md) - describes the network * [metalctlv2 admin network edit](metalctlv2_admin_network_edit.md) - edit the network through an editor and update * [metalctlv2 admin network list](metalctlv2_admin_network_list.md) - list all networks +* [metalctlv2 admin network list-external-members](metalctlv2_admin_network_list-external-members.md) - lists external members of the network +* [metalctlv2 admin network remove-external-members](metalctlv2_admin_network_remove-external-members.md) - removes external members from the network * [metalctlv2 admin network update](metalctlv2_admin_network_update.md) - updates the network diff --git a/docs/admin/metalctlv2_admin_network_add-external-members.md b/docs/admin/metalctlv2_admin_network_add-external-members.md new file mode 100644 index 0000000..5cad572 --- /dev/null +++ b/docs/admin/metalctlv2_admin_network_add-external-members.md @@ -0,0 +1,37 @@ +## metalctlv2 admin network add-external-members + +adds external members to the network + +### Synopsis + +adds switch ports of a rack as members to the network. + +``` +metalctlv2 admin network add-external-members [flags] +``` + +### Options + +``` + -h, --help help for add-external-members + --ports strings ports to add to the network + --rack string rack of the external members +``` + +### Options inherited from parent commands + +``` + --api-token string the token used for api requests + --api-url string the url to the metal-stack.io api + -c, --config string alternative config file path, (default is ~/.metal-stack/config.yaml) + --debug debug output + --force-color force colored output even without tty + -o, --output-format string output format (table|wide|markdown|json|yaml|template), wide is a table with more columns. (default "table") + --template string output template for template output-format, go template format. For property names inspect the output of -o json or -o yaml for reference. + --timeout duration request timeout used for api requests +``` + +### SEE ALSO + +* [metalctlv2 admin network](metalctlv2_admin_network.md) - manage network entities + diff --git a/docs/admin/metalctlv2_admin_network_list-external-members.md b/docs/admin/metalctlv2_admin_network_list-external-members.md new file mode 100644 index 0000000..47a6aad --- /dev/null +++ b/docs/admin/metalctlv2_admin_network_list-external-members.md @@ -0,0 +1,38 @@ +## metalctlv2 admin network list-external-members + +lists external members of the network + +### Synopsis + +lists switch ports that are members of this network but are not connected to any registered machine. + +``` +metalctlv2 admin network list-external-members [flags] +``` + +### Options + +``` + -h, --help help for list-external-members + --partition string filter by partition + --rack string filter by rack + --switch string filter by switch +``` + +### Options inherited from parent commands + +``` + --api-token string the token used for api requests + --api-url string the url to the metal-stack.io api + -c, --config string alternative config file path, (default is ~/.metal-stack/config.yaml) + --debug debug output + --force-color force colored output even without tty + -o, --output-format string output format (table|wide|markdown|json|yaml|template), wide is a table with more columns. (default "table") + --template string output template for template output-format, go template format. For property names inspect the output of -o json or -o yaml for reference. + --timeout duration request timeout used for api requests +``` + +### SEE ALSO + +* [metalctlv2 admin network](metalctlv2_admin_network.md) - manage network entities + diff --git a/docs/admin/metalctlv2_admin_network_remove-external-members.md b/docs/admin/metalctlv2_admin_network_remove-external-members.md new file mode 100644 index 0000000..66a8c05 --- /dev/null +++ b/docs/admin/metalctlv2_admin_network_remove-external-members.md @@ -0,0 +1,37 @@ +## metalctlv2 admin network remove-external-members + +removes external members from the network + +### Synopsis + +removes switch ports of a rack from the network. + +``` +metalctlv2 admin network remove-external-members [flags] +``` + +### Options + +``` + -h, --help help for remove-external-members + --ports strings ports to add to the network + --rack string rack of the external members +``` + +### Options inherited from parent commands + +``` + --api-token string the token used for api requests + --api-url string the url to the metal-stack.io api + -c, --config string alternative config file path, (default is ~/.metal-stack/config.yaml) + --debug debug output + --force-color force colored output even without tty + -o, --output-format string output format (table|wide|markdown|json|yaml|template), wide is a table with more columns. (default "table") + --template string output template for template output-format, go template format. For property names inspect the output of -o json or -o yaml for reference. + --timeout duration request timeout used for api requests +``` + +### SEE ALSO + +* [metalctlv2 admin network](metalctlv2_admin_network.md) - manage network entities + From bf6f88c33b209f2e01bc97efded8da4f3a63505b Mon Sep 17 00:00:00 2001 From: Ilja Rotar Date: Wed, 9 Sep 2026 16:47:26 +0200 Subject: [PATCH 4/4] tidy --- go.mod | 2 +- go.sum | 10 ++++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index 91135cc..d6cd984 100644 --- a/go.mod +++ b/go.mod @@ -24,7 +24,7 @@ require ( require ( buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 // indirect - buf.build/go/protovalidate v1.3.0 // indirect + buf.build/go/protovalidate v1.4.0 // indirect buf.build/go/protoyaml v0.7.0 // indirect cel.dev/cel-go v0.32.0 // indirect cel.dev/expr v0.25.3 // indirect diff --git a/go.sum b/go.sum index afccc32..be0406a 100644 --- a/go.sum +++ b/go.sum @@ -2,8 +2,8 @@ 9fans.net/go v0.0.8-0.20250307142834-96bdba94b63f/go.mod h1:hHyrZRryGqVdqrknjq5OWDLGCTJ2NeEvtrpR96mjraM= buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2 h1:NbnlmV26O7oZ1iM5tsCI+GEx+3ZSdrhvnKQ/eWSrLiY= buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.12-20260825204119-511051f7f437.2/go.mod h1:TCt1lluMFnctISJXvkIQ4x3ABrPuUKCWKyjKdkJNBpw= -buf.build/go/protovalidate v1.3.0 h1:8ITcnZGkAHx6TyhZvro+iET/AyqU8gEWQJK2WsT62ms= -buf.build/go/protovalidate v1.3.0/go.mod h1:82s5g+rFRj1CZPiLv6OTA31jBu2fpq7mLXHwa9mZfEs= +buf.build/go/protovalidate v1.4.0 h1:UjLrYbt5VX7+TMOs2+pG5FhZhIG1mSfK4EIopbb4LcM= +buf.build/go/protovalidate v1.4.0/go.mod h1:8vJfzNT6NIG2qm3uFsJDXMlRmG+bQJzbcIn1Aa0vPGs= buf.build/go/protoyaml v0.7.0 h1:z4oVoFicbpPefhT7WAykxUdfp0yEQlhMQ2mCZOY5V38= buf.build/go/protoyaml v0.7.0/go.mod h1:+a0cavd0uMvirb87xdu2ZMMmjlIQoiH/N2Ich5MGSQ0= cel.dev/cel-go v0.32.0 h1:irvpFKr5EuGPyxeME03ERh0rii1TX+BDAnB9eL3IvNk= @@ -191,8 +191,8 @@ github.com/mdlayher/sdnotify v1.0.0 h1:Ma9XeLVN/l0qpyx1tNeMSeTjCPH6NtuD6/N9XdTlQ github.com/mdlayher/sdnotify v1.0.0/go.mod h1:HQUmpM4XgYkhDLtd+Uad8ZFK1T9D5+pNxnXQjCeJlGE= github.com/mdlayher/socket v0.6.1 h1:M7uj2NtuujUY4mYr1C57NmfNiRHbkKpnBxO856lsc3A= github.com/mdlayher/socket v0.6.1/go.mod h1:+/SGtqc9V+5dAuRgQsU0fGBI+oRDiW7O2Obx10OIWfg= -github.com/metal-stack/api v0.5.5 h1:AzPjTKi8Y4XNiJaEcRDCAjuOmIaLuuOEhKXVWliTtWc= -github.com/metal-stack/api v0.5.5/go.mod h1:U8c+awSMxXaRJjzWpo1IMcvlNxOi1ewu/fk1BzDM7hI= +github.com/metal-stack/api v0.5.6-0.20260909090323-c58acb0d1dba h1:4Dyhunb5seTQSY3hgGqZ5C0QT638xkEx5yS58YVGjG8= +github.com/metal-stack/api v0.5.6-0.20260909090323-c58acb0d1dba/go.mod h1:rWdj0dTAg2w/WuQhIZ+JVp9b68v6VVCm1k7sLx4U+PQ= github.com/metal-stack/metal-console v0.7.7-0.20260909095012-75ec3d1e3e1a h1:zFB1a0PMyzgBGtJU3HID75pEsD6l694BViuxJxKDdLg= github.com/metal-stack/metal-console v0.7.7-0.20260909095012-75ec3d1e3e1a/go.mod h1:ZkselOaxF/iELFL9Tku3maXgpX4DM0CHjoG7CGuky6E= github.com/metal-stack/metal-lib v0.26.3 h1:K5gLoD65m6p3l6qCPrfavIdvNdWfmF2QdXrvU2URaZs= @@ -345,8 +345,6 @@ google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fw google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=