From c85a559358af17706895ff55566082a4aa0ac4c8 Mon Sep 17 00:00:00 2001 From: irusoeqx Date: Wed, 4 Oct 2023 17:44:10 +0300 Subject: [PATCH 1/3] feat: VRF support --- .DS_Store | Bin 0 -> 6148 bytes internal/vrf/create.go | 83 +++++++++++++++++++++++++++++++++++++++++ internal/vrf/delete.go | 63 +++++++++++++++++++++++++++++++ internal/vrf/list.go | 52 ++++++++++++++++++++++++++ internal/vrf/vrf.go | 47 +++++++++++++++++++++++ 5 files changed, 245 insertions(+) create mode 100644 .DS_Store create mode 100644 internal/vrf/create.go create mode 100644 internal/vrf/delete.go create mode 100644 internal/vrf/list.go create mode 100644 internal/vrf/vrf.go diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..2b3075d94cc42f10532dc0d71d0b7b8dccd82b3c GIT binary patch literal 6148 zcmeHK%Wl&^6upy}#%YC!DiCatEU}GB3P`QQCd~s>iKZwHbO9*Xb!e?Pb`?8h5k<-t z_y>LgvEobMUs%DJN2J(s#10`eH<~%~xMwE!iIpw%-%0yB8XjT(kZp5O?}`K z)V$xp$P2W3w3oBSm&_VB6sJdBI;IAgJz$5P=CRq`47-TEuxI*vvQHT=gFK25QJnGn znep4A3;6MG9c5!LTx{@-X+op)x`poAo$(@F0ar0(2J&xXcBs{;M#J+ESu$i<@RiLSheGg;4DOt%i+nH=Gb^Rp5t>0cJ9L^l5~}0h-8W>NyjV_bkz@ANmDAHUEx9^Mqo@*fs)G96@#g}1Cw;j z*OmW5qNEcOJ7XQSGgCJdrmh~$67Iz65_PQ=& [-m ] [-AS int] [-I str] [-d ]`, + Short: "Creates a virtual network.", + Long: "Creates a VRF", + Example: `# Creates a VRF, metal vrf create `, + + RunE: func(cmd *cobra.Command, args []string) error { + cmd.SilenceUsage = true + + req := metal.VrfCreateInput{ + Metro: metro, + Name: name, + IpRanges: ipRanges, + LocalAsn: &localASN, + Tags: tags, + Description: &description, + } + // Retreived these params from packetngo based on the VRF function there, assuming vrfs need these to work + // From Packetngo >>>> + // IPRanges is a list of all IPv4 and IPv6 Ranges that will be available to + // BGP Peers. IPv4 addresses must be /8 or smaller with a minimum size of + // /29. IPv6 must be /56 or smaller with a minimum size of /64. Ranges must + // not overlap other ranges within the VRF. + // >>>>>>>>>>>>>>>>>>> + // Not quite sure if a CIDR can be specified here using a "/" or how it works exactly in tandem with VRFs + // may also need some logic for processing errors on local asn, also not sure about this + request := c.Service.CreateVrf(context.Background(), projectID).VrfCreateInput(req).Exclude(nil).Include(nil) + n, _, err := request.Execute() + if err != nil { + return fmt.Errorf("Could not create VRF: %w", err) + } + + data := make([][]string, 1) + + // This output block below is probably incorrect but leaving it for now for testing later. + data[0] = []string{n.GetName(), *n.GetMetro().Code, n.GetDescription(), strconv.Itoa(int(n.GetLocalAsn())), strings.Join(n.GetIpRanges(), ","), n.GetCreatedAt().String()} + header := []string{"Name", "Metro", "Description", "LocalASN", "IPrange", "Created"} + + return c.Out.Output(n, header, &data) + }, + } + + createVRFCmd.Flags().StringVarP(&projectID, "project-id", "p", "", "The project's UUID. This flag is required, unless specified in the config created by metal init or set as METAL_PROJECT_ID environment variable.") + createVRFCmd.Flags().StringSliceVarP(&tags, "tags", "t", []string{}, `Tag or list of tags for the VRF: --tags="tag1,tag2".`) + createVRFCmd.Flags().StringVarP(&name, "name", "n", "", "Name of the VRF") + createVRFCmd.Flags().StringVarP(&description, "description", "d", "", "Description of the VRF.") + + createVRFCmd.Flags().StringVarP(&metro, "metro", "m", "", "Code of the metro where the VRF will be created") + createVRFCmd.Flags().Int32VarP(&localASN, "local-asn", "a", 0, "Local ASN for the VRF") + createVRFCmd.Flags().StringSliceVarP(&ipRanges, "ip-ranges", "r", []string{}, "IP range or list of IP ranges for the VRF") + + // making them all required here + _ = createVRFCmd.MarkFlagRequired("name") + _ = createVRFCmd.MarkFlagRequired("metro") + _ = createVRFCmd.MarkFlagRequired("local-asn") + _ = createVRFCmd.MarkFlagRequired("IPrange") + + return createVRFCmd +} \ No newline at end of file diff --git a/internal/vrf/delete.go b/internal/vrf/delete.go new file mode 100644 index 00000000..22ab5e9a --- /dev/null +++ b/internal/vrf/delete.go @@ -0,0 +1,63 @@ +package main // or your appropriate package name + +import ( + "fmt" + + "github.com/manifoldco/promptui" + "github.com/spf13/cobra" +) + +func (c *Client) Delete() *cobra.Command { + var ( + vrfID string + force bool + ) + + deleteVrf := func(id string) error { + _, err := c.Service.Delete(id) + if err != nil { + return err + } + fmt.Println("VRF", id, "successfully deleted.") + return nil // No need to return 'err' here; it's always nil. + } + + deleteMetalVrfCmd := &cobra.Command{ + Use: "delete vrf -i [-f]", + Short: "Deletes a VRF.", + Long: "Deletes the specified VRF with a confirmation prompt. To skip the confirmation, use --force.", + Example: `# Deletes a VRF, with confirmation. + metal delete vrf -i 77e6d57a-d7a4-4816-b451-cf9b043444e2 + + # Deletes a VRF, skipping confirmation. + metal delete vrf -f -i 77e6d57a-d7a4-4816-b451-cf9b043444e2`, + RunE: func(cmd *cobra.Command, args []string) error { + cmd.SilenceUsage = true + + if !force { + prompt := promptui.Prompt{ + Label: fmt.Sprintf("Are you sure you want to delete VRF %s: ", vrfID), + IsConfirm: true, + } + + result, err := prompt.Run() + if err != nil || result != "y" { + fmt.Println("VRF deletion aborted.") + return nil + } + } + + if err := deleteVrf(vrfID); err != nil { + return fmt.Errorf("Could not delete VRF: %w", err) + } + + return nil + }, + } + + deleteMetalVrfCmd.Flags().StringVarP(&vrfID, "id", "i", "", "UUID of the VRF.") + _ = deleteMetalVrfCmd.MarkFlagRequired("id") + deleteMetalVrfCmd.Flags().BoolVarP(&force, "force", "f", false, "Skips confirmation for the removal of the VRF.") + + return deleteMetalVrfCmd +} diff --git a/internal/vrf/list.go b/internal/vrf/list.go new file mode 100644 index 00000000..caa7d1d9 --- /dev/null +++ b/internal/vrf/list.go @@ -0,0 +1,52 @@ +package vrf + +import ( + metal "github.com/equinix-labs/metal-go/metal/v1" + "github.com/equinix/metal-cli/internal/outputs" + "github.com/spf13/cobra" + "fmt" +) + +type Client struct { + Servicer Servicer + Service metal.VRFsApiService + Out outputs.Outputer +} + + +func (c *Client) Retrieve() *cobra.Command { + var projectID string + + // retrieveVrfsCmd represents the retrieveMetalGateways command + retrieveVrfsCmd := &cobra.Command{ + Use: `get -p `, + Aliases: []string{"list"}, + Short: "Lists VRFs.", + Long: "Retrieves a list of all VRFs for the specified project.", + Example: ` + # Lists VRFs for project 3b0795ba-ec9a-4a9e-83a7-043e7e11407c: + metal vrf list -p 3b0795ba-ec9a-4a9e-83a7-043e7e11407c`, + + RunE: func(cmd *cobra.Command, args []string) error { + cmd.SilenceUsage = true + vrfs, _, err := c.Service.Get(ProjectID, c.Servicer.GetOptions(nil, nil)) // <- Not sure about the correct service to call Im assuming its this? https://github.com/packethost/packngo/blob/master/vrf.go + if err != nil { + return fmt.Errorf("Could not list VRFs: %w", err) + } + + data := make([][]string, len(vrfs.VirtualRoutingFunctions)) + + for i, n := range vnets.VirtualRoutingFunctions { + data[i] = []string{n.ID, n.Description, n.IPranges, n.LocalASN n.Metro, n.CreatedAt} //Data should contain all info about VRF not sure where the formatting comes from, going with this. + } + header := []string{"ID", "Description", "VXLAN", "Facility", "Created"} + + return c.Out.Output(vrfs, header, &data) + }, +} +retrieveVrfsCmd.Flags().StringVarP(&projectID, "project-id", "p", "", "The project's UUID. This flag is required, unless specified in the config created by metal init or set as METAL_PROJECT_ID environment variable.") +_ = retrieveVrfsCmd.MarkFlagRequired("project-id") + +return retrieveVrfsCmd +} + diff --git a/internal/vrf/vrf.go b/internal/vrf/vrf.go new file mode 100644 index 00000000..e60ad765 --- /dev/null +++ b/internal/vrf/vrf.go @@ -0,0 +1,47 @@ +package vrf + +import ( + metal "github.com/equinix-labs/metal-go/metal/v1" + "github.com/equinix/metal-cli/internal/outputs" + "github.com/spf13/cobra" +) + +type Client struct { + Servicer Servicer + Service metal.VRFsApiService + Out outputs.Outputer +} + +func (c *Client) NewCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: `vrf`, + Aliases: []string{"vrf"}, + Short: "VRF operations : create, TODO: make other commands.", + Long: "Experimental VRF function", + + PersistentPreRun: func(cmd *cobra.Command, args []string) { + if root := cmd.Root(); root != nil { + if root.PersistentPreRun != nil { + root.PersistentPreRun(cmd, args) + } + } + c.Service = *c.Servicer.MetalAPI(cmd).VRFsApi + }, + } + + cmd.AddCommand( + c.Create(), + ) + return cmd +} + +type Servicer interface { + MetalAPI(*cobra.Command) *metal.APIClient +} + +func NewClient(s Servicer, out outputs.Outputer) *Client { + return &Client{ + Servicer: s, + Out: out, + } +} \ No newline at end of file From ac4aa7004b7c31400b145da2baa34c15569b82ea Mon Sep 17 00:00:00 2001 From: irusoeqx Date: Thu, 5 Oct 2023 11:37:15 +0300 Subject: [PATCH 2/3] completed PR w cli, go, & sum --- cmd/cli.go | 2 ++ go.mod | 2 ++ go.sum | 4 ++++ 3 files changed, 8 insertions(+) diff --git a/cmd/cli.go b/cmd/cli.go index 41ee8629..6b7853c7 100644 --- a/cmd/cli.go +++ b/cmd/cli.go @@ -27,6 +27,7 @@ import ( "github.com/equinix/metal-cli/internal/twofa" "github.com/equinix/metal-cli/internal/users" "github.com/equinix/metal-cli/internal/vlan" + "github.com/equinix/metal-cli/internal/vrf" ) // Cli struct @@ -93,5 +94,6 @@ func (cli *Cli) RegisterCommands(client *root.Client) { twofa.NewClient(client, cli.Outputer).NewCommand(), gateway.NewClient(client, cli.Outputer).NewCommand(), ports.NewClient(client, cli.Outputer).NewCommand(), + vrf.NewClient(client, cli.Outputer).NewCommand(), ) } diff --git a/go.mod b/go.mod index 494b1316..6faa7c57 100644 --- a/go.mod +++ b/go.mod @@ -32,8 +32,10 @@ require ( github.com/spf13/cast v1.5.1 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/subosito/gotenv v1.4.2 // indirect + golang.org/x/mod v0.12.0 // indirect golang.org/x/sys v0.12.0 // indirect golang.org/x/text v0.9.0 // indirect + golang.org/x/tools v0.13.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index ad899496..56dd151e 100644 --- a/go.sum +++ b/go.sum @@ -246,6 +246,8 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -399,6 +401,8 @@ golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= From e137b83ec0d4e2943d49cfc2c75334291ce259e2 Mon Sep 17 00:00:00 2001 From: ilkka ruso <102891943+irusoeqx@users.noreply.github.com> Date: Thu, 12 Oct 2023 22:08:03 +0300 Subject: [PATCH 3/3] Update internal/vrf/delete.go Co-authored-by: Marques Johansson --- internal/vrf/delete.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/vrf/delete.go b/internal/vrf/delete.go index 22ab5e9a..af6c2bee 100644 --- a/internal/vrf/delete.go +++ b/internal/vrf/delete.go @@ -1,4 +1,4 @@ -package main // or your appropriate package name +package vrf import ( "fmt"