diff --git a/docs/metal_vrf.md b/docs/metal_vrf.md index 22bb9c33..ffe1784b 100644 --- a/docs/metal_vrf.md +++ b/docs/metal_vrf.md @@ -31,7 +31,11 @@ VRF operations : It defines a collection of customer-managed IP blocks that can * [metal](metal.md) - Command line interface for Equinix Metal * [metal vrf create](metal_vrf_create.md) - Creates a Virtual Routing and Forwarding(VRF) for a specified project. +* [metal vrf create-route](metal_vrf_create-route.md) - Create a route in a VRF. Currently only static default routes are supported. * [metal vrf delete](metal_vrf_delete.md) - Deletes a VRF. +* [metal vrf delete-route](metal_vrf_delete-route.md) - Delete a VRF Route * [metal vrf get](metal_vrf_get.md) - Lists VRFs. +* [metal vrf get-route](metal_vrf_get-route.md) - Retrieve all routes in the VRF * [metal vrf ips](metal_vrf_ips.md) - Retrieves the list of VRF IP Reservations for the VRF. +* [metal vrf update-route](metal_vrf_update-route.md) - Requests a VRF Route be redeployed/update across the network. diff --git a/docs/metal_vrf_create-route.md b/docs/metal_vrf_create-route.md new file mode 100644 index 00000000..e95a3a3d --- /dev/null +++ b/docs/metal_vrf_create-route.md @@ -0,0 +1,49 @@ +## metal vrf create-route + +Create a route in a VRF. Currently only static default routes are supported. + +### Synopsis + +Create a route in a VRF. Currently only static default routes are supported. + +``` +metal vrf create-route [-i ] [-p ] [-n NextHop] [-t ] [flags] +``` + +### Examples + +``` + # Create a route in a VRF. Currently only static default routes are supported.. + + metal vrf create-route [-i ] [-p ] [-n nextHop] [-t ] +``` + +### Options + +``` + -h, --help help for create-route + -i, --id string Specify the VRF UUID activate route configurations + -n, --nextHop string The IPv4 address within the VRF of the host that will handle this route + -p, --prefix string The IPv4 prefix for the route, in CIDR-style notation. For a static default route, this will always be '0.0.0.0/0' + -t, --tags strings Adds the tags for the connection --tags="tag1,tag2". +``` + +### Options inherited from parent commands + +``` + --config string Path to JSON or YAML configuration file (METAL_CONFIG) + --exclude strings Comma separated Href references to collapse in results, may be dotted three levels deep + --filter stringArray Filter 'get' actions with name value pairs. Filter is not supported by all resources and is implemented as request query parameters. + --http-header strings Headers to add to requests (in format key=value) + --include strings Comma separated Href references to expand in results, may be dotted three levels deep + -o, --output string Output format (*table, json, yaml). env output formats are (*sh, terraform, capp). + --search string Search keyword for use in 'get' actions. Search is not supported by all resources. + --sort-by string Sort fields for use in 'get' actions. Sort is not supported by all resources. + --sort-dir string Sort field direction for use in 'get' actions. Sort is not supported by all resources. + --token string Metal API Token (METAL_AUTH_TOKEN) +``` + +### SEE ALSO + +* [metal vrf](metal_vrf.md) - VRF operations : create, get, delete + diff --git a/docs/metal_vrf_create.md b/docs/metal_vrf_create.md index 1fdbc317..ec56e833 100644 --- a/docs/metal_vrf_create.md +++ b/docs/metal_vrf_create.md @@ -28,7 +28,7 @@ metal vrf create [-p ] [-m ] [-n ] [- -m, --metro string The UUID (or metro code) for the Metro in which to create this Virtual Routing and Forwarding -n, --name string Name of the Virtual Routing and Forwarding -p, --project-id string 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. - -t, --tags strings Adds or updates the tags for the connection --tags="tag1,tag2". + -t, --tags strings Adds the tags for the virtual-circuit --tags "tag1,tag2" OR --tags "tag1" --tags "tag2" ``` ### Options inherited from parent commands diff --git a/docs/metal_vrf_delete-route.md b/docs/metal_vrf_delete-route.md new file mode 100644 index 00000000..fa285ac1 --- /dev/null +++ b/docs/metal_vrf_delete-route.md @@ -0,0 +1,51 @@ +## metal vrf delete-route + +Delete a VRF Route + +### Synopsis + +Delete a VRF Route + +``` +metal vrf delete-route [-i ] [flags] +``` + +### Examples + +``` + #Delete a VRF Route + metal vrf delete-route -i 77e6d57a-d7a4-4816-b451-cf9b043444e2 + > + ✔ Are you sure you want to delete device 7ec86e23-8dcf-48ed-bd9b-c25c20958277 [Y/N]: y + + # Deletes a VRF, skipping confirmation. + metal vrf delete-route -f -i 77e6d57a-d7a4-4816-b451-cf9b043444e2 +``` + +### Options + +``` + -f, --force Skips confirmation for the removal of the VRF routes. + -h, --help help for delete-route + -i, --id string Specify the VRF UUID to delete the associated route configurations. +``` + +### Options inherited from parent commands + +``` + --config string Path to JSON or YAML configuration file (METAL_CONFIG) + --exclude strings Comma separated Href references to collapse in results, may be dotted three levels deep + --filter stringArray Filter 'get' actions with name value pairs. Filter is not supported by all resources and is implemented as request query parameters. + --http-header strings Headers to add to requests (in format key=value) + --include strings Comma separated Href references to expand in results, may be dotted three levels deep + -o, --output string Output format (*table, json, yaml). env output formats are (*sh, terraform, capp). + --search string Search keyword for use in 'get' actions. Search is not supported by all resources. + --sort-by string Sort fields for use in 'get' actions. Sort is not supported by all resources. + --sort-dir string Sort field direction for use in 'get' actions. Sort is not supported by all resources. + --token string Metal API Token (METAL_AUTH_TOKEN) +``` + +### SEE ALSO + +* [metal vrf](metal_vrf.md) - VRF operations : create, get, delete + diff --git a/docs/metal_vrf_get-route.md b/docs/metal_vrf_get-route.md new file mode 100644 index 00000000..aeb77391 --- /dev/null +++ b/docs/metal_vrf_get-route.md @@ -0,0 +1,46 @@ +## metal vrf get-route + +Retrieve all routes in the VRF + +### Synopsis + +Retrieve all routes in the VRF + +``` +metal vrf get-route [-i ] [flags] +``` + +### Examples + +``` + #Retrieve all routes in the VRF + # Retrieve all routes in the VRF + metal vrf get-route -i bb526d47-8536-483c-b436-116a5fb72235 +``` + +### Options + +``` + -h, --help help for get-route + -i, --id string Specify the VRF UUID to list its associated routes configurations +``` + +### Options inherited from parent commands + +``` + --config string Path to JSON or YAML configuration file (METAL_CONFIG) + --exclude strings Comma separated Href references to collapse in results, may be dotted three levels deep + --filter stringArray Filter 'get' actions with name value pairs. Filter is not supported by all resources and is implemented as request query parameters. + --http-header strings Headers to add to requests (in format key=value) + --include strings Comma separated Href references to expand in results, may be dotted three levels deep + -o, --output string Output format (*table, json, yaml). env output formats are (*sh, terraform, capp). + --search string Search keyword for use in 'get' actions. Search is not supported by all resources. + --sort-by string Sort fields for use in 'get' actions. Sort is not supported by all resources. + --sort-dir string Sort field direction for use in 'get' actions. Sort is not supported by all resources. + --token string Metal API Token (METAL_AUTH_TOKEN) +``` + +### SEE ALSO + +* [metal vrf](metal_vrf.md) - VRF operations : create, get, delete + diff --git a/docs/metal_vrf_update-route.md b/docs/metal_vrf_update-route.md new file mode 100644 index 00000000..844e2f61 --- /dev/null +++ b/docs/metal_vrf_update-route.md @@ -0,0 +1,49 @@ +## metal vrf update-route + +Requests a VRF Route be redeployed/update across the network. + +### Synopsis + +Requests a VRF Route be redeployed/update across the network. + +``` +metal vrf update-route [-i ] [-p ] [-n NextHop] [-t ] [flags] +``` + +### Examples + +``` + #Requests a VRF Route be redeployed/update across the network. + + metal vrf update-route [-i ] [-p ] [-n nextHop] [-t ] +``` + +### Options + +``` + -h, --help help for update-route + -i, --id string Specify the VRF UUID to update the associated route configurations. + -n, --nextHop string Name of the Virtual Routing and Forwarding + -p, --prefix string The IPv4 prefix for the route, in CIDR-style notation. For a static default route, this will always be '0.0.0.0/0' + -t, --tags strings updates the tags for the Virtual Routing and Forwarding --tags "tag1,tag2" OR --tags "tag1" --tags "tag2" (NOTE: --tags "" will remove all tags from the Virtual Routing and Forwarding +``` + +### Options inherited from parent commands + +``` + --config string Path to JSON or YAML configuration file (METAL_CONFIG) + --exclude strings Comma separated Href references to collapse in results, may be dotted three levels deep + --filter stringArray Filter 'get' actions with name value pairs. Filter is not supported by all resources and is implemented as request query parameters. + --http-header strings Headers to add to requests (in format key=value) + --include strings Comma separated Href references to expand in results, may be dotted three levels deep + -o, --output string Output format (*table, json, yaml). env output formats are (*sh, terraform, capp). + --search string Search keyword for use in 'get' actions. Search is not supported by all resources. + --sort-by string Sort fields for use in 'get' actions. Sort is not supported by all resources. + --sort-dir string Sort field direction for use in 'get' actions. Sort is not supported by all resources. + --token string Metal API Token (METAL_AUTH_TOKEN) +``` + +### SEE ALSO + +* [metal vrf](metal_vrf.md) - VRF operations : create, get, delete + diff --git a/internal/gateway/create.go b/internal/gateway/create.go index c7f13564..d7146426 100644 --- a/internal/gateway/create.go +++ b/internal/gateway/create.go @@ -31,8 +31,12 @@ import ( ) func (c *Client) Create() *cobra.Command { - var projectID, vnID, reservationID string - var netSize int32 + var ( + projectID string + vnID string + reservationID string + netSize int32 + ) // createMetalGatewayCmd represents the createMetalGateway command createMetalGatewayCmd := &cobra.Command{ @@ -48,35 +52,49 @@ func (c *Client) Create() *cobra.Command { RunE: func(cmd *cobra.Command, args []string) error { cmd.SilenceUsage = true includes := []string{"virtual_network", "ip_reservation"} - + var req metal.CreateMetalGatewayRequest if reservationID == "" && netSize == 0 { return errors.New("Invalid input. Provide either 'private-subnet-size' or 'ip-reservation-id'") } - - req := metal.CreateMetalGatewayRequest{ - MetalGatewayCreateInput: &metal.MetalGatewayCreateInput{ - VirtualNetworkId: vnID, - }, - } - if reservationID != "" { - req.MetalGatewayCreateInput.SetIpReservationId(reservationID) + if reservationID != "" && vnID != "" || netSize == 0 { + req = metal.CreateMetalGatewayRequest{ + VrfMetalGatewayCreateInput: &metal.VrfMetalGatewayCreateInput{ + VirtualNetworkId: vnID, + IpReservationId: reservationID, + }, + } } else { - req.MetalGatewayCreateInput.SetPrivateIpv4SubnetSize(netSize) + req = metal.CreateMetalGatewayRequest{ + MetalGatewayCreateInput: &metal.MetalGatewayCreateInput{ + VirtualNetworkId: vnID, + }, + } + if reservationID != "" { + req.MetalGatewayCreateInput.SetIpReservationId(reservationID) + } else { + req.MetalGatewayCreateInput.SetPrivateIpv4SubnetSize(netSize) + } } - n, _, err := c.Service. - CreateMetalGateway(context.Background(), projectID). - Include(c.Servicer.Includes(includes)). - Exclude(c.Servicer.Excludes(nil)). - CreateMetalGatewayRequest(req). - Execute() + n, _, err := c.Service.CreateMetalGateway(context.Background(), projectID).CreateMetalGatewayRequest(req).Include(c.Servicer.Includes(includes)).Exclude(c.Servicer.Excludes(nil)).Execute() if err != nil { - return fmt.Errorf("Could not create Metal Gateway: %w", err) + return fmt.Errorf("could not create Metal Gateway: %w", err) } data := make([][]string, 1) address := "" + header := []string{"ID", "Metro", "VXLAN", "Addresses", "State", "Created"} + if reservationID != "" && vnID != "" || netSize == 0 { + vrfGway := n.VrfMetalGateway + ipReservation := vrfGway.IpReservation + if ipReservation != nil { + address = ipReservation.GetAddress() + "/" + strconv.Itoa(int(ipReservation.GetCidr())) + } + data[0] = []string{vrfGway.GetId(), vrfGway.VirtualNetwork.GetMetroCode(), + strconv.Itoa(int(vrfGway.VirtualNetwork.GetVxlan())), address, string(vrfGway.GetState()), vrfGway.GetCreatedAt().String()} + return c.Out.Output(vrfGway, header, &data) + } gway := n.MetalGateway ipReservation := gway.IpReservation if ipReservation != nil { @@ -86,8 +104,6 @@ func (c *Client) Create() *cobra.Command { data[0] = []string{gway.GetId(), gway.VirtualNetwork.GetMetroCode(), strconv.Itoa(int(gway.VirtualNetwork.GetVxlan())), address, string(gway.GetState()), gway.GetCreatedAt().String()} - header := []string{"ID", "Metro", "VXLAN", "Addresses", "State", "Created"} - return c.Out.Output(gway, header, &data) }, } diff --git a/internal/vrf/create.go b/internal/vrf/create.go index 6af054f0..33f75416 100644 --- a/internal/vrf/create.go +++ b/internal/vrf/create.go @@ -59,7 +59,7 @@ func (c *Client) Create() *cobra.Command { } 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{}, `Adds or updates the tags for the connection --tags="tag1,tag2".`) + createVRFCmd.Flags().StringSliceVarP(&tags, "tags", "t", []string{}, `Adds the tags for the virtual-circuit --tags "tag1,tag2" OR --tags "tag1" --tags "tag2"`) createVRFCmd.Flags().StringVarP(&name, "name", "n", "", "Name of the Virtual Routing and Forwarding") createVRFCmd.Flags().StringVarP(&description, "description", "d", "", "Description of the Virtual Routing and Forwarding.") diff --git a/internal/vrf/createroute.go b/internal/vrf/createroute.go new file mode 100644 index 00000000..f8d1364a --- /dev/null +++ b/internal/vrf/createroute.go @@ -0,0 +1,65 @@ +package vrf + +import ( + "context" + "fmt" + + "github.com/equinix/equinix-sdk-go/services/metalv1" + "github.com/spf13/cobra" +) + +func (c *Client) CreateRoute() *cobra.Command { + var ( + vrfID string + prefix string + nextHop string + tags []string + ) + + // CreateVrfRouteCmd represents the CreateVrfRouteCmd command + createRouteCmd := &cobra.Command{ + Use: "create-route [-i ] [-p ] [-n NextHop] [-t ]", + Aliases: []string{"route"}, + Short: "Create a route in a VRF. Currently only static default routes are supported.", + Long: "Create a route in a VRF. Currently only static default routes are supported.", + Example: ` # Create a route in a VRF. Currently only static default routes are supported.. + + metal vrf create-route [-i ] [-p ] [-n nextHop] [-t ]`, + RunE: func(cmd *cobra.Command, args []string) error { + cmd.SilenceUsage = true + inc := []string{} + exc := []string{} + + vrfRouteCreateInput := metalv1.VrfRouteCreateInput{ + Prefix: prefix, + NextHop: nextHop, + Tags: tags, + } + + vrfRoute, _, err := c.Service.CreateVrfRoute(context.Background(), vrfID).VrfRouteCreateInput(vrfRouteCreateInput).Include(c.Servicer.Includes(inc)).Exclude(c.Servicer.Excludes(exc)).Execute() + if err != nil { + return fmt.Errorf("could not create VrfRoute: %w", err) + } + + data := make([][]string, 1) + + // This output block below is probably incorrect but leaving it for now for testing later. + data[0] = []string{vrfRoute.GetId(), string(vrfRoute.GetType()), vrfRoute.GetPrefix(), vrfRoute.GetNextHop(), vrfRoute.CreatedAt.String()} + header := []string{"ID", "Type", "Prefix", "NextHop", "Created"} + + return c.Out.Output(vrfRoute, header, &data) + }, + } + + createRouteCmd.Flags().StringVarP(&vrfID, "id", "i", "", "Specify the VRF UUID activate route configurations") + createRouteCmd.Flags().StringVarP(&prefix, "prefix", "p", "", "The IPv4 prefix for the route, in CIDR-style notation. For a static default route, this will always be '0.0.0.0/0'") + createRouteCmd.Flags().StringVarP(&nextHop, "nextHop", "n", "", "The IPv4 address within the VRF of the host that will handle this route") + createRouteCmd.Flags().StringSliceVarP(&tags, "tags", "t", []string{}, `Adds the tags for the connection --tags="tag1,tag2".`) + + // making them all required here + _ = createRouteCmd.MarkFlagRequired("id") + _ = createRouteCmd.MarkFlagRequired("prefix") + _ = createRouteCmd.MarkFlagRequired("nextHop") + + return createRouteCmd +} diff --git a/internal/vrf/deleteroute.go b/internal/vrf/deleteroute.go new file mode 100644 index 00000000..787a0186 --- /dev/null +++ b/internal/vrf/deleteroute.go @@ -0,0 +1,73 @@ +package vrf + +import ( + "context" + "fmt" + "strings" + + "github.com/spf13/cobra" +) + +func (c *Client) DeleteRoute() *cobra.Command { + var ( + vrfID string + force bool + confirmation string + ) + deleteRoute := func(id string) error { + _, _, err := c.Service.DeleteVrfRouteById(context.Background(), id).Execute() + if err != nil { + return err + } + fmt.Println("VRF Route", id, "successfully deleted.") + fmt.Println("VRF Route deletion initiated. Please check 'metal vrf get-route -i", vrfID, "' for status") + return nil // No need to return 'err' here; it's always nil. + } + + // GetVrfRouteCmd represents the GetVrfRouteCmd command + DeleteRouteCmd := &cobra.Command{ + Use: "delete-route [-i ]", + Short: "Delete a VRF Route", + Long: "Delete a VRF Route", + Example: ` #Delete a VRF Route + metal vrf delete-route -i 77e6d57a-d7a4-4816-b451-cf9b043444e2 + > + ✔ Are you sure you want to delete device 7ec86e23-8dcf-48ed-bd9b-c25c20958277 [Y/N]: y + + # Deletes a VRF, skipping confirmation. + metal vrf delete-route -f -i 77e6d57a-d7a4-4816-b451-cf9b043444e2`, + + RunE: func(cmd *cobra.Command, args []string) error { + cmd.SilenceUsage = true + + if !force { + fmt.Printf("Are you sure you want to delete VRF %s [Y/N]: ", vrfID) + + _, err := fmt.Scanln(&confirmation) + if err != nil { + fmt.Println("Error reading confirmation:", err) + return nil + } + + confirmation = strings.TrimSpace(strings.ToLower(confirmation)) + if confirmation != "yes" && confirmation != "y" { + fmt.Println("VRF deletion cancelled.") + return nil + } + } + + if err := deleteRoute(vrfID); err != nil { + return fmt.Errorf("could not delete VRF: %w", err) + } + + return nil + }, + } + + DeleteRouteCmd.Flags().StringVarP(&vrfID, "id", "i", "", "Specify the VRF UUID to delete the associated route configurations.") + DeleteRouteCmd.Flags().BoolVarP(&force, "force", "f", false, "Skips confirmation for the removal of the VRF routes.") + // making them all required here + _ = DeleteRouteCmd.MarkFlagRequired("id") + + return DeleteRouteCmd +} diff --git a/internal/vrf/retrieveroute.go b/internal/vrf/retrieveroute.go new file mode 100644 index 00000000..42528f0f --- /dev/null +++ b/internal/vrf/retrieveroute.go @@ -0,0 +1,48 @@ +package vrf + +import ( + "context" + "fmt" + + "github.com/spf13/cobra" +) + +func (c *Client) RetrieveRoute() *cobra.Command { + var ( + vrfID string + ) + + // GetVrfRouteCmd represents the GetVrfRouteCmd command + GetRouteCmd := &cobra.Command{ + Use: "get-route [-i ]", + Short: "Retrieve all routes in the VRF", + Long: "Retrieve all routes in the VRF", + Example: ` #Retrieve all routes in the VRF + # Retrieve all routes in the VRF + metal vrf get-route -i bb526d47-8536-483c-b436-116a5fb72235`, + RunE: func(cmd *cobra.Command, args []string) error { + cmd.SilenceUsage = true + inc := []string{} + exc := []string{} + + vrfRouteList, _, err := c.Service.GetVrfRoutes(context.Background(), vrfID).Include(c.Servicer.Includes(inc)).Exclude(c.Servicer.Excludes(exc)).Execute() + if err != nil { + return fmt.Errorf("could not Get all routes in the VRF: %w", err) + } + vrfRoute := vrfRouteList.GetRoutes() + data := make([][]string, len(vrfRoute)) + header := []string{"ID", "Type", "Prefix", "NextHop", "Created"} + for i, route := range vrfRoute { + data[i] = []string{route.GetId(), string(route.GetType()), route.GetPrefix(), route.GetNextHop(), route.CreatedAt.String()} + } + return c.Out.Output(vrfRoute, header, &data) + }, + } + + GetRouteCmd.Flags().StringVarP(&vrfID, "id", "i", "", "Specify the VRF UUID to list its associated routes configurations") + + // making them all required here + _ = GetRouteCmd.MarkFlagRequired("id") + + return GetRouteCmd +} diff --git a/internal/vrf/updateroute.go b/internal/vrf/updateroute.go new file mode 100644 index 00000000..3aa79486 --- /dev/null +++ b/internal/vrf/updateroute.go @@ -0,0 +1,61 @@ +package vrf + +import ( + "context" + "fmt" + + "github.com/equinix/equinix-sdk-go/services/metalv1" + "github.com/spf13/cobra" +) + +func (c *Client) UpdateRoute() *cobra.Command { + var ( + vrfID string + prefix string + nextHop string + tags []string + ) + + // CreateVrfRouteCmd represents the CreateVrfRouteCmd command + UpdateRouteCmd := &cobra.Command{ + Use: "update-route [-i ] [-p ] [-n NextHop] [-t ]", + Short: "Requests a VRF Route be redeployed/update across the network.", + Long: "Requests a VRF Route be redeployed/update across the network.", + Example: ` #Requests a VRF Route be redeployed/update across the network. + + metal vrf update-route [-i ] [-p ] [-n nextHop] [-t ]`, + RunE: func(cmd *cobra.Command, args []string) error { + cmd.SilenceUsage = true + inc := []string{} + exc := []string{} + + vrfRouteUpdateInput := metalv1.VrfRouteUpdateInput{ + Prefix: &prefix, + NextHop: &nextHop, + Tags: tags, + } + + vrfRoute, _, err := c.Service.UpdateVrfRouteById(context.Background(), vrfID).VrfRouteUpdateInput(vrfRouteUpdateInput).Include(c.Servicer.Includes(inc)).Exclude(c.Servicer.Excludes(exc)).Execute() + if err != nil { + return fmt.Errorf("could not update VrfRoute: %w", err) + } + + data := make([][]string, 1) + + data[0] = []string{vrfRoute.GetId(), string(vrfRoute.GetType()), vrfRoute.GetPrefix(), vrfRoute.GetNextHop(), vrfRoute.CreatedAt.String()} + header := []string{"ID", "Type", "Prefix", "NextHop", "Created"} + + return c.Out.Output(vrfRoute, header, &data) + }, + } + + UpdateRouteCmd.Flags().StringVarP(&vrfID, "id", "i", "", "Specify the VRF UUID to update the associated route configurations.") + UpdateRouteCmd.Flags().StringVarP(&prefix, "prefix", "p", "", "The IPv4 prefix for the route, in CIDR-style notation. For a static default route, this will always be '0.0.0.0/0'") + UpdateRouteCmd.Flags().StringVarP(&nextHop, "nextHop", "n", "", "Name of the Virtual Routing and Forwarding") + UpdateRouteCmd.Flags().StringSliceVarP(&tags, "tags", "t", []string{}, `updates the tags for the Virtual Routing and Forwarding --tags "tag1,tag2" OR --tags "tag1" --tags "tag2" (NOTE: --tags "" will remove all tags from the Virtual Routing and Forwarding`) + + // making them all required here + _ = UpdateRouteCmd.MarkFlagRequired("id") + + return UpdateRouteCmd +} diff --git a/internal/vrf/vrf.go b/internal/vrf/vrf.go index a4123276..50963ad7 100644 --- a/internal/vrf/vrf.go +++ b/internal/vrf/vrf.go @@ -34,6 +34,10 @@ func (c *Client) NewCommand() *cobra.Command { c.Retrieve(), c.Delete(), c.Ips(), + c.CreateRoute(), + c.DeleteRoute(), + c.RetrieveRoute(), + c.UpdateRoute(), ) return cmd } diff --git a/test/e2e/ipstest/ips_request_test.go b/test/e2e/ipstest/ips_request_test.go index a8f7facc..c82830ee 100644 --- a/test/e2e/ipstest/ips_request_test.go +++ b/test/e2e/ipstest/ips_request_test.go @@ -65,8 +65,8 @@ func TestCli_Vlan_Create(t *testing.T) { root := c.Root() projectName := "metal-cli-" + helper.GenerateRandomString(5) + "-ips-request-vrf" project := helper.CreateTestProject(t, projectName) - _ = helper.CreateTestVlanWithVxLan(t, project.GetId(), 5678, projectName) - vrf := helper.CreateTestVrfs(t, project.GetId(), projectName) + _ = helper.CreateTestVlanWithVxLan(t, project.GetId(), 3988, projectName) + vrf := helper.CreateTestVrfs(t, project.GetId(), projectName, 3988) root.SetArgs([]string{subCommand, "request", "-v", vrf.GetId(), "-t", "vrf", "--cidr", "24", "-n", "10.10.1.0", "--tags", "foobar", "--tags", "barfoo"}) diff --git a/test/e2e/vrfstest/vrf_route_test.go b/test/e2e/vrfstest/vrf_route_test.go new file mode 100644 index 00000000..76d18bcc --- /dev/null +++ b/test/e2e/vrfstest/vrf_route_test.go @@ -0,0 +1,196 @@ +package vrfstest + +import ( + "regexp" + "strings" + "testing" + + root "github.com/equinix/metal-cli/internal/cli" + outputPkg "github.com/equinix/metal-cli/internal/outputs" + "github.com/equinix/metal-cli/internal/vrf" + "github.com/equinix/metal-cli/test/helper" + "github.com/spf13/cobra" +) + +func TestCli_Vrf_Route(t *testing.T) { + subCommand := "vrf" + rootClient := root.NewClient(helper.ConsumerToken, helper.URL, helper.Version) + randName := helper.GenerateRandomString(5) + + type fields struct { + MainCmd *cobra.Command + Outputer outputPkg.Outputer + } + + tests := []struct { + name string + fields fields + want *cobra.Command + cmdFunc func(*testing.T, *cobra.Command) + }{ + { + name: "vrf-create-route-test", + fields: fields{ + MainCmd: vrf.NewClient(rootClient, outputPkg.Outputer(&outputPkg.Standard{})).NewCommand(), + Outputer: outputPkg.Outputer(&outputPkg.Standard{}), + }, + want: &cobra.Command{}, + cmdFunc: func(t *testing.T, c *cobra.Command) { + root := c.Root() + projName := "metal-cli-" + randName + "-vrf-create-route-test" + projectId := helper.CreateTestProject(t, projName) + if projectId.GetId() != "" { + vlan := helper.CreateTestVlanWithVxLan(t, projectId.GetId(), 3987, projName) + vrf := helper.CreateTestVrfs(t, projectId.GetId(), projName, 3987) + + ipReservation := helper.CreateTestVrfIpRequest(t, projectId.GetId(), vrf.GetId()) + _ = helper.CreateTestVrfGateway(t, projectId.GetId(), ipReservation.VrfIpReservation.GetId(), vlan.GetId()) + + if vlan.GetId() != "" && vrf.GetId() != "" { + root.SetArgs([]string{subCommand, "create-route", "-i", vrf.GetId(), "-p", "0.0.0.0/0", "-n", "10.10.1.2", "-t", "foo"}) + + out := helper.ExecuteAndCaptureOutput(t, root) + + if !strings.Contains(string(out[:]), "TYPE") && + !strings.Contains(string(out[:]), "static") && + !strings.Contains(string(out[:]), "PREFIX") && + !strings.Contains(string(out[:]), "0.0.0.0/0") { + t.Error("expected output should include TYPE static PREFIX and 0.0.0.0/0, in the out string ") + } + + idNamePattern := `(?m)^\| ([a-zA-Z0-9-]+) +\| *` + + // Find the match of the ID and NAME pattern in the table string + match := regexp.MustCompile(idNamePattern).FindStringSubmatch(string(out[:])) + // Extract the ID from the match + if len(match) > 1 { + routeId := strings.TrimSpace(match[1]) + helper.CleanTestVrfRoute(t, routeId) + } else { + t.Errorf("No match found for %v in %v", idNamePattern, string(out[:])) + } + } + } + }, + }, + { + name: "vrf-delete-route-test", + fields: fields{ + MainCmd: vrf.NewClient(rootClient, outputPkg.Outputer(&outputPkg.Standard{})).NewCommand(), + Outputer: outputPkg.Outputer(&outputPkg.Standard{}), + }, + want: &cobra.Command{}, + cmdFunc: func(t *testing.T, c *cobra.Command) { + root := c.Root() + + projName := "metal-cli-" + randName + "-vrf-delete-test" + projectId := helper.CreateTestProject(t, projName) + + if projectId.GetId() != "" { + vlan := helper.CreateTestVlanWithVxLan(t, projectId.GetId(), 3987, projName) + vrf := helper.CreateTestVrfs(t, projectId.GetId(), projName, 3987) + + ipReservation := helper.CreateTestVrfIpRequest(t, projectId.GetId(), vrf.GetId()) + _ = helper.CreateTestVrfGateway(t, projectId.GetId(), ipReservation.VrfIpReservation.GetId(), vlan.GetId()) + vrfRoute := helper.CreateTestVrfRoute(t, vrf.GetId()) + if vlan.GetId() != "" && vrf.GetId() != "" && vrfRoute.GetId() != "" { + root.SetArgs([]string{subCommand, "delete-route", "-i", vrfRoute.GetId()}) + + out := helper.ExecuteAndCaptureOutput(t, root) + + if !strings.Contains(string(out[:]), vrfRoute.GetId()) { + t.Error("expected output should include VRF Route deletion initiated. Please check 'metal vrf GetRoute -i " + vrfRoute.GetId() + " ' for status, in the out string ") + } + } + } + }, + }, + { + name: "vrf-update-route-test", + fields: fields{ + MainCmd: vrf.NewClient(rootClient, outputPkg.Outputer(&outputPkg.Standard{})).NewCommand(), + Outputer: outputPkg.Outputer(&outputPkg.Standard{}), + }, + want: &cobra.Command{}, + cmdFunc: func(t *testing.T, c *cobra.Command) { + // Actually user have to wait for 5 min to updae the VRF-routes. This test case is skipped intentionally + if true { + t.Skip("Skipping this test because someCondition is true") + } + root := c.Root() + + projName := "metal-cli-" + randName + "-vrf-list-test" + projectId := helper.CreateTestProject(t, projName) + + if projectId.GetId() != "" { + vlan := helper.CreateTestVlanWithVxLan(t, projectId.GetId(), 3987, projName) + vrf := helper.CreateTestVrfs(t, projectId.GetId(), projName, 3987) + + ipReservation := helper.CreateTestVrfIpRequest(t, projectId.GetId(), vrf.GetId()) + _ = helper.CreateTestVrfGateway(t, projectId.GetId(), ipReservation.VrfIpReservation.GetId(), vlan.GetId()) + _ = helper.CreateTestVrfRoute(t, vrf.GetId()) + + if vlan.GetId() != "" && vrf.GetId() != "" { + root.SetArgs([]string{subCommand, "update-route", "-i", vrf.GetId(), "-t", "foobar"}) + + out := helper.ExecuteAndCaptureOutput(t, root) + + if !strings.Contains(string(out[:]), "TYPE") && + !strings.Contains(string(out[:]), "static") && + !strings.Contains(string(out[:]), "PREFIX") && + !strings.Contains(string(out[:]), "0.0.0.0/0") { + t.Error("expected output should include TYPE static PREFIX and 0.0.0.0/0, in the out string ") + } + } + } + }, + }, + { + name: "vrf-get-route-test", + fields: fields{ + MainCmd: vrf.NewClient(rootClient, outputPkg.Outputer(&outputPkg.Standard{})).NewCommand(), + Outputer: outputPkg.Outputer(&outputPkg.Standard{}), + }, + want: &cobra.Command{}, + cmdFunc: func(t *testing.T, c *cobra.Command) { + root := c.Root() + + projName := "metal-cli-" + randName + "-vrf-list-test" + projectId := helper.CreateTestProject(t, projName) + + if projectId.GetId() != "" { + vlan := helper.CreateTestVlanWithVxLan(t, projectId.GetId(), 3987, projName) + vrf := helper.CreateTestVrfs(t, projectId.GetId(), projName, 3987) + + ipReservation := helper.CreateTestVrfIpRequest(t, projectId.GetId(), vrf.GetId()) + _ = helper.CreateTestVrfGateway(t, projectId.GetId(), ipReservation.VrfIpReservation.GetId(), vlan.GetId()) + // vrfRoute := helper.CreateTestVrfRoute(t, vrf.GetId()) + _ = helper.CreateTestVrfRoute(t, vrf.GetId()) + + if vlan.GetId() != "" && vrf.GetId() != "" { + root.SetArgs([]string{subCommand, "get-route", "-i", vrf.GetId()}) + + out := helper.ExecuteAndCaptureOutput(t, root) + + if !strings.Contains(string(out[:]), "TYPE") && + !strings.Contains(string(out[:]), "static") && + !strings.Contains(string(out[:]), "PREFIX") && + !strings.Contains(string(out[:]), "ID") && + !strings.Contains(string(out[:]), vrf.GetId()) && + !strings.Contains(string(out[:]), "0.0.0.0/0") { + t.Error("expected output should include TYPE static PREFIX and 0.0.0.0/0, in the out string ") + } + } + } + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + rootCmd := rootClient.NewCommand() + rootCmd.AddCommand(tt.fields.MainCmd) + tt.cmdFunc(t, tt.fields.MainCmd) + }) + } +} diff --git a/test/e2e/vrfstest/vrf_test.go b/test/e2e/vrfstest/vrf_test.go index eba53d1e..cfc19b17 100644 --- a/test/e2e/vrfstest/vrf_test.go +++ b/test/e2e/vrfstest/vrf_test.go @@ -64,8 +64,9 @@ func TestCli_Vrf_Create(t *testing.T) { projectId := helper.CreateTestProject(t, projName) if projectId.GetId() != "" { - vrf := helper.CreateTestVrfs(t, projectId.GetId(), projName) - if vrf.GetId() != "" { + vlan := helper.CreateTestVlanWithVxLan(t, projectId.GetId(), 3983, projName) + vrf := helper.CreateTestVrfs(t, projectId.GetId(), projName, 3983) + if vrf.GetId() != "" && vlan.GetId() != "" { root.SetArgs([]string{subCommand, "delete", "-i", vrf.GetId(), "-f"}) out := helper.ExecuteAndCaptureOutput(t, root) @@ -90,7 +91,8 @@ func TestCli_Vrf_Create(t *testing.T) { projectId := helper.CreateTestProject(t, projName) if projectId.GetId() != "" { - vrf := helper.CreateTestVrfs(t, projectId.GetId(), projName) + _ = helper.CreateTestVlanWithVxLan(t, projectId.GetId(), 3982, projName) + vrf := helper.CreateTestVrfs(t, projectId.GetId(), projName, 3982) root.SetArgs([]string{subCommand, "get", "-p", projectId.GetId()}) out := helper.ExecuteAndCaptureOutput(t, root) @@ -116,7 +118,8 @@ func TestCli_Vrf_Create(t *testing.T) { projectId := helper.CreateTestProject(t, projName) if projectId.GetId() != "" { - vrf := helper.CreateTestVrfs(t, projectId.GetId(), projName) + _ = helper.CreateTestVlanWithVxLan(t, projectId.GetId(), 3981, projName) + vrf := helper.CreateTestVrfs(t, projectId.GetId(), projName, 3981) root.SetArgs([]string{subCommand, "get", "-p", projectId.GetId()}) out := helper.ExecuteAndCaptureOutput(t, root) @@ -143,7 +146,8 @@ func TestCli_Vrf_Create(t *testing.T) { projectId := helper.CreateTestProject(t, projName) if projectId.GetId() != "" { - vrf := helper.CreateTestVrfs(t, projectId.GetId(), projName) + _ = helper.CreateTestVlanWithVxLan(t, projectId.GetId(), 3979, projName) + vrf := helper.CreateTestVrfs(t, projectId.GetId(), projName, 3979) if vrf.GetId() != "" { root.SetArgs([]string{subCommand, "get", "-v", vrf.GetId()}) out := helper.ExecuteAndCaptureOutput(t, root) @@ -170,8 +174,8 @@ func TestCli_Vrf_Create(t *testing.T) { project := helper.CreateTestProject(t, projName) if project.GetId() != "" { - - vrf := helper.CreateTestVrfs(t, project.GetId(), projName) + _ = helper.CreateTestVlanWithVxLan(t, project.GetId(), 3978, projName) + vrf := helper.CreateTestVrfs(t, project.GetId(), projName, 3978) if vrf.GetId() != "" { ipRequest := helper.CreateTestVrfIpRequest(t, project.GetId(), vrf.GetId()) diff --git a/test/helper/helper.go b/test/helper/helper.go index 021d8631..592169ca 100644 --- a/test/helper/helper.go +++ b/test/helper/helper.go @@ -642,14 +642,14 @@ func waitForInterconnectionDeleted(apiClient *metalv1.APIClient, connId string, } //nolint:staticcheck -func CreateTestVrfs(t *testing.T, projectId, name string) *metalv1.Vrf { +func CreateTestVrfs(t *testing.T, projectId, name string, vlan int) *metalv1.Vrf { t.Helper() TestApiClient := TestClient() var IpRanges []string vrfCreateInput := *metalv1.NewVrfCreateInput("da", name) - vrfCreateInput.SetLocalAsn(5678) + vrfCreateInput.SetLocalAsn(int32(vlan)) IpRanges = append(IpRanges, "10.10.1.0/24") vrfCreateInput.SetIpRanges(IpRanges) @@ -695,7 +695,7 @@ func CreateTestVrfIpRequest(t *testing.T, projectId, vrfId string) *metalv1.Requ } t.Cleanup(func() { - CleanTestVrfIpRequest(t, reservation.IPReservation.GetId()) + CleanTestVrfIpRequest(t, reservation.VrfIpReservation.GetId()) }) return reservation @@ -709,3 +709,72 @@ func CleanTestVrfIpRequest(t *testing.T, IPReservationId string) { t.Fatalf("Error when calling `IPAddressesApi.DeleteIPAddress``for %v: %v\n", IPReservationId, err) } } + +func CreateTestVrfRoute(t *testing.T, vrfId string) *metalv1.VrfRoute { + t.Helper() + TestApiClient := TestClient() + tags := []string{"foobar"} + + vrfRouteCreateInput := metalv1.VrfRouteCreateInput{ + Prefix: "0.0.0.0/0", + NextHop: "10.10.1.2", + Tags: tags, + } + + vrfRoute, _, err := TestApiClient.VRFsApi.CreateVrfRoute(context.Background(), vrfId).VrfRouteCreateInput(vrfRouteCreateInput).Execute() + if err != nil { + t.Fatalf("Error when calling `VRFsApi.CreateVrfRoute`` for %v: %v\n", vrfId, err) + } + + t.Cleanup(func() { + CleanTestVrfIpRequest(t, vrfRoute.GetId()) + }) + + return vrfRoute + +} + +func CleanTestVrfRoute(t *testing.T, vrfRouteId string) { + t.Helper() + + TestApiClient := TestClient() + + _, resp, err := TestApiClient.VRFsApi.DeleteVrfRouteById(context.Background(), vrfRouteId).Execute() + if err != nil && resp.StatusCode != http.StatusNotFound { + t.Fatalf("Error when calling `VRFsApi.DeleteVrfRouteById`` for %v: %v\n", vrfRouteId, err) + } +} + +func CreateTestVrfGateway(t *testing.T, projectId, reservationId, vlanId string) *metalv1.VrfMetalGateway { + t.Helper() + TestApiClient := TestClient() + includes := []string{"virtual_network", "ip_reservation"} + + gatewayCreateInput := metalv1.CreateMetalGatewayRequest{ + VrfMetalGatewayCreateInput: &metalv1.VrfMetalGatewayCreateInput{ + VirtualNetworkId: vlanId, + IpReservationId: reservationId, + }, + } + gateway, _, err := TestApiClient.MetalGatewaysApi.CreateMetalGateway(context.Background(), projectId).CreateMetalGatewayRequest(gatewayCreateInput).Include(includes).Execute() + if err != nil { + t.Fatalf("Error when calling `MetalGatewaysApi.CreateMetalGateway`: %v\n", err) + } + + t.Cleanup(func() { + CleanTestVrfGateway(t, gateway.VrfMetalGateway.GetId()) + }) + + return gateway.VrfMetalGateway +} + +func CleanTestVrfGateway(t *testing.T, gatewayId string) { + t.Helper() + + TestApiClient := TestClient() + includes := []string{"ip_reservation"} + _, resp, err := TestApiClient.MetalGatewaysApi.DeleteMetalGateway(context.Background(), gatewayId).Include(includes).Execute() + if err != nil && resp.StatusCode != http.StatusNotFound { + t.Fatalf("Error when calling `MetalGatewaysApi.DeleteMetalGateway`` for %v: %v\n", gatewayId, err) + } +}