diff --git a/cmd/cli.go b/cmd/cli.go index b2e865d7..953249e9 100644 --- a/cmd/cli.go +++ b/cmd/cli.go @@ -27,6 +27,7 @@ import ( "github.com/equinix/metal-cli/internal/ssh" "github.com/equinix/metal-cli/internal/twofa" "github.com/equinix/metal-cli/internal/users" + "github.com/equinix/metal-cli/internal/virtualcircuit" "github.com/equinix/metal-cli/internal/vlan" "github.com/equinix/metal-cli/internal/vrf" ) @@ -97,5 +98,6 @@ func (cli *Cli) RegisterCommands(client *root.Client) { ports.NewClient(client, cli.Outputer).NewCommand(), interconnections.NewClient(client, cli.Outputer).NewCommand(), vrf.NewClient(client, cli.Outputer).NewCommand(), + virtualcircuit.NewClient(client, cli.Outputer).NewCommand(), ) } diff --git a/docs/metal.md b/docs/metal.md index 1c59c4b8..73472e66 100644 --- a/docs/metal.md +++ b/docs/metal.md @@ -46,6 +46,7 @@ Command line interface for Equinix Metal * [metal project](metal_project.md) - Project operations: create, get, update, delete, and bgp-enable, bgp-config, bgp-sessions. * [metal ssh-key](metal_ssh-key.md) - SSH key operations: create, get, update, and delete. * [metal user](metal_user.md) - User operations: get and add. +* [metal virtual-circuit](metal_virtual-circuit.md) - virtual-circuit operations: create, get, update, delete * [metal virtual-network](metal_virtual-network.md) - Virtual network (VLAN) operations : create, get, delete. * [metal vrf](metal_vrf.md) - VRF operations : create, get, delete diff --git a/docs/metal_virtual-circuit.md b/docs/metal_virtual-circuit.md new file mode 100644 index 00000000..ab494150 --- /dev/null +++ b/docs/metal_virtual-circuit.md @@ -0,0 +1,37 @@ +## metal virtual-circuit + +virtual-circuit operations: create, get, update, delete + +### Synopsis + +For more information on https://deploy.equinix.com/developers/docs/metal/interconnections. + +### Options + +``` + -h, --help help for virtual-circuit +``` + +### 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](metal.md) - Command line interface for Equinix Metal +* [metal virtual-circuit create](metal_virtual-circuit_create.md) - Creates an create-virtual-circuit for specific interconnection. +* [metal virtual-circuit delete](metal_virtual-circuit_delete.md) - Deletes a virtual-circuit. +* [metal virtual-circuit get](metal_virtual-circuit_get.md) - Retrieves virtual circuit for a specific circuit Id. +* [metal virtual-circuit update](metal_virtual-circuit_update.md) - Updates a virtualcircuit. + diff --git a/docs/metal_virtual-circuit_create.md b/docs/metal_virtual-circuit_create.md new file mode 100644 index 00000000..65577935 --- /dev/null +++ b/docs/metal_virtual-circuit_create.md @@ -0,0 +1,63 @@ +## metal virtual-circuit create + +Creates an create-virtual-circuit for specific interconnection. + +### Synopsis + +Creates an create-virtual-circuit for specific interconnection + +``` +metal virtual-circuit create [-c connection_id] [-p port_id] [-P ] -n [-d ] [--vnid ] [-V ] [-s ] [-t ] [flags] +``` + +### Examples + +``` + # Creates a new virtual-circuit named "interconnection": + metal vc create [-c connection_id] [-p port_id] [-P ] [-n ] [-d ] [--vnid ] [-V ] [-s ] [-t ] + + metal vc create -c 81c9cb9e-b02f-4c73-9e04-06702f1380a0 -p 9c8f0c71-591d-42fe-9519-2f632761e2da -P b4673e33-0f48-4948-961a-c31d6edf64f8 -n test-inter -d test-interconnection -v 15315810-2fda-48b8-b8cd-441ebab684b5 -V 1010 -s 100 + + metal vc create [-c connection_id] [-p port_id] [-P ] [-n ] [-d ] [-v ] [-M ] [-a ] [-S ] [-c ] [-m ] +``` + +### Options + +``` + -c, --connection-id string Specify the UUID of the interconnection. + --customer-ip string An IP address from the subnet that will be used on the Customer side + -d, --description string Description for a Virtual Circuit + -h, --help help for create + -M, --md5 string The plaintext BGP peering password shared by neighbors as an MD5 checksum + -m, --metal-ip string An IP address from the subnet that will be used on the Metal side. + -n, --name string Name of the Virtual Circuit + -a, --peer-asn int The peer ASN that will be used with the VRF on the Virtual Circuit. + -p, --port-id string Specify the UUID of the port. + -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. + -s, --speed int bps speed or string (e.g. 52 - '52m' or '100g' or '4 gbps') + -S, --subnet string The /30 or /31 subnet of one of the VRF IP Blocks that will be used with the VRF for the Virtual Circuit. + -t, --tags strings Adds the tags for the virtual-circuit --tags="tag1,tag2" + -V, --vlan int Adds or updates vlan Must be between 2 and 4094 + --vnid string Specify the UUID of the VLAN. + -v, --vrf-id string The UUID of the VRF that will be associated with the Virtual Circuit. +``` + +### 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 virtual-circuit](metal_virtual-circuit.md) - virtual-circuit operations: create, get, update, delete + diff --git a/docs/metal_virtual-circuit_delete.md b/docs/metal_virtual-circuit_delete.md new file mode 100644 index 00000000..e4dbeb62 --- /dev/null +++ b/docs/metal_virtual-circuit_delete.md @@ -0,0 +1,45 @@ +## metal virtual-circuit delete + +Deletes a virtual-circuit. + +### Synopsis + +Deletes the specified virtual-circuit. + +``` +metal virtual-circuit delete -i [flags] +``` + +### Examples + +``` + # Deletes the specified virtual-circuit: + metal vc delete -i 7ec86e23-8dcf-48ed-bd9b-c25c20958277 +``` + +### Options + +``` + -h, --help help for delete + -i, --id string Specify the UUID of the virtual-circuit. +``` + +### 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 virtual-circuit](metal_virtual-circuit.md) - virtual-circuit operations: create, get, update, delete + diff --git a/docs/metal_virtual-circuit_get.md b/docs/metal_virtual-circuit_get.md new file mode 100644 index 00000000..d245c509 --- /dev/null +++ b/docs/metal_virtual-circuit_get.md @@ -0,0 +1,47 @@ +## metal virtual-circuit get + +Retrieves virtual circuit for a specific circuit Id. + +### Synopsis + +Retrieves virtual circuit for a specific circuit Id. + +``` +metal virtual-circuit get -i [flags] +``` + +### Examples + +``` + # Retrieve virtual circuit for a specific circuit:: + + # Retrieve the details of a specific virtual-circuit: + metal vc get -i e9a969b3-8911-4667-9d99-57cd3dd4ef6f +``` + +### Options + +``` + -h, --help help for get + -i, --id string Specify UUID of the virtual-circuit +``` + +### 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 virtual-circuit](metal_virtual-circuit.md) - virtual-circuit operations: create, get, update, delete + diff --git a/docs/metal_virtual-circuit_update.md b/docs/metal_virtual-circuit_update.md new file mode 100644 index 00000000..c51f01e9 --- /dev/null +++ b/docs/metal_virtual-circuit_update.md @@ -0,0 +1,60 @@ +## metal virtual-circuit update + +Updates a virtualcircuit. + +### Synopsis + +Updates a specified virtualcircuit etiher of vlanID OR vrfID + +``` +metal virtual-circuit update -i [-v ] [-d ] [-n ] [-s ] [-t ] [flags] +``` + +### Examples + +``` + # Updates a specified virtualcircuit etiher of vlanID OR vrfID: + + metal vc update [-i ] [-n ] [-d ] [-v ] [-s ] [-t ] + + metal vc update -i e2edb90b-a8ef-47cb-a577-63b0ba129c29 -d "test-inter-fri-dedicated" + + metal vc update [-i ] [-n ] [-d ] [-M ] [-a ] [-S ] [-c ] [-m ] [-t ] +``` + +### Options + +``` + -c, --customer-ip string An IP address from the subnet that will be used on the Customer side + -d, --description string Description for a Virtual Circuit + -h, --help help for update + -i, --id string Specify the UUID of the virtual-circuit. + -M, --md5 string The plaintext BGP peering password shared by neighbors as an MD5 checksum + -m, --metal-ip string An IP address from the subnet that will be used on the Metal side. + -n, --name string Name of the Virtual Circuit + -a, --peer-asn int The peer ASN that will be used with the VRF on the Virtual Circuit. + -s, --speed string Adds or updates Speed can be changed only if it is an interconnection on a Dedicated Port + -S, --subnet string The /30 or /31 subnet of one of the VRF IP Blocks that will be used with the VRF for the Virtual Circuit. + -t, --tags strings updates the tags for the virtual-circuit --tags="tag1,tag2". + -v, --vnid string A Virtual Network record UUID or the VNID of a Metro Virtual Network in your project. +``` + +### 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 virtual-circuit](metal_virtual-circuit.md) - virtual-circuit operations: create, get, update, delete + diff --git a/internal/virtualcircuit/create.go b/internal/virtualcircuit/create.go new file mode 100644 index 00000000..46a46be2 --- /dev/null +++ b/internal/virtualcircuit/create.go @@ -0,0 +1,172 @@ +package virtualcircuit + +import ( + "context" + "errors" + "fmt" + "strconv" + + metal "github.com/equinix/equinix-sdk-go/services/metalv1" + "github.com/spf13/cobra" +) + +type vcParamBuilder interface { + SetName(string) + SetDescription(string) + SetTags([]string) +} + +// compile-time assertion that parameter types implement vcParamBuilder +var ( + _ vcParamBuilder = (*metal.VlanVirtualCircuitCreateInput)(nil) + _ vcParamBuilder = (*metal.VrfVirtualCircuitCreateInput)(nil) +) + +func setParams(p vcParamBuilder, name, description string, tags []string) { + if name != "" { + p.SetName(name) + } + if description != "" { + p.SetDescription(description) + } + + if len(tags) > 0 { + p.SetTags(tags) + } +} + +func validateParams(nnVlanFlag int, asnFlag int, subnetFlag string) error { + if nnVlanFlag == 0 || asnFlag == 0 || subnetFlag == "" { + return errors.New(" vlan ID, peer ASN and subnet of one of the VRF IP Blocks is required to create VRF virtual circuit") + } + return nil +} + +func createVrfVirtualCircuit(vrfInput *metal.VrfVirtualCircuitCreateInput, projectID, customerIP, metalIP, md5 string, speed int) metal.VirtualCircuitCreateInput { + + vrfInput.SetCustomerIp(customerIP) + vrfInput.SetMetalIp(metalIP) + vrfInput.SetMd5(md5) + if speed > 0 { + vrfInput.SetSpeed(int32(speed)) + } + + return metal.VirtualCircuitCreateInput{VrfVirtualCircuitCreateInput: vrfInput} +} + +func createVlanVirtualCircuit(vlanInput *metal.VlanVirtualCircuitCreateInput, vnid string, nnVlan int, speed int) metal.VirtualCircuitCreateInput { + vlanInput.SetVnid(vnid) + // As per the Spec It range is [ 2 .. 4094 ] + if nnVlan > 2 { + vlanInput.SetNniVlan(int32(nnVlan)) + } + if speed > 0 { + vlanInput.SetSpeed(int32(speed)) + } + vlanInput.SetVnid(vnid) + + return metal.VirtualCircuitCreateInput{VlanVirtualCircuitCreateInput: vlanInput} +} + +func (c *Client) Create() *cobra.Command { + var ( + connectionID string + portID string + name string + description string + projectID string + vnid string + vrf string + subnet string + customerIP string + metalIP string + md5 string + peerAsn int + speed int + nnVlan int + tags []string + ) + + createVirtualCircuitCmd := &cobra.Command{ + Use: `create [-c connection_id] [-p port_id] [-P ] -n [-d ] [--vnid ] [-V ] [-s ] [-t ]`, + Short: "Creates an create-virtual-circuit for specific interconnection.", + Long: "Creates an create-virtual-circuit for specific interconnection", + Example: ` # Creates a new virtual-circuit named "interconnection": + metal vc create [-c connection_id] [-p port_id] [-P ] [-n ] [-d ] [--vnid ] [-V ] [-s ] [-t ] + + metal vc create -c 81c9cb9e-b02f-4c73-9e04-06702f1380a0 -p 9c8f0c71-591d-42fe-9519-2f632761e2da -P b4673e33-0f48-4948-961a-c31d6edf64f8 -n test-inter -d test-interconnection -v 15315810-2fda-48b8-b8cd-441ebab684b5 -V 1010 -s 100 + + metal vc create [-c connection_id] [-p port_id] [-P ] [-n ] [-d ] [-v ] [-M ] [-a ] [-S ] [-c ] [-m ]`, + + RunE: func(cmd *cobra.Command, args []string) error { + cmd.SilenceUsage = true + var ( + createInput metal.VirtualCircuitCreateInput + err error + ) + vrfFlag, _ := cmd.Flags().GetString("vrf") + if vrfFlag != "" { + nnVlanFlag, _ := cmd.Flags().GetInt("nnVlan") + asnFlag, _ := cmd.Flags().GetInt("peerAsn") + subnetFlag, _ := cmd.Flags().GetString("subnet") + + err = validateParams(nnVlanFlag, asnFlag, subnetFlag) + if err != nil { + return fmt.Errorf("%w", err) + } + vrfInput := metal.NewVrfVirtualCircuitCreateInput(int32(nnVlan), int32(peerAsn), projectID, subnet, vrf) + setParams(vrfInput, name, description, tags) + createInput = createVrfVirtualCircuit(vrfInput, projectID, customerIP, metalIP, md5, speed) + } else { + vlanInput := metal.NewVlanVirtualCircuitCreateInput(projectID) + + setParams(vlanInput, name, description, tags) + createInput = createVlanVirtualCircuit(vlanInput, vnid, nnVlan, speed) + } + + vc, _, err := c.Service.CreateInterconnectionPortVirtualCircuit(context.Background(), connectionID, portID).VirtualCircuitCreateInput(createInput).Execute() + if err != nil { + return fmt.Errorf("could not create Virtual Circuit: %w", err) + } + + data := make([][]string, 1) + + if vrfFlag != "" { + data[0] = []string{vc.VrfVirtualCircuit.GetId(), vc.VrfVirtualCircuit.GetName(), string(vc.VrfVirtualCircuit.GetType()), strconv.Itoa(int(vc.VrfVirtualCircuit.GetSpeed())), vc.VrfVirtualCircuit.GetCreatedAt().String()} + } else { + data[0] = []string{vc.VlanVirtualCircuit.GetId(), vc.VlanVirtualCircuit.GetName(), string(vc.VlanVirtualCircuit.GetType()), strconv.Itoa(int(vc.VlanVirtualCircuit.GetSpeed())), vc.VlanVirtualCircuit.GetCreatedAt().String()} + } + + header := []string{"ID", "Name", "Type", "Speed", "Created"} + + return c.Out.Output(vc.VlanVirtualCircuit, header, &data) + }, + } + // Virtual Circuit Params + createVirtualCircuitCmd.Flags().StringVarP(&connectionID, "connection-id", "c", "", "Specify the UUID of the interconnection.") + createVirtualCircuitCmd.Flags().StringVarP(&portID, "port-id", "p", "", "Specify the UUID of the port.") + + // Common params for both VlanVirtualCircuit and VrfVirtualCircit + createVirtualCircuitCmd.Flags().IntVarP(&nnVlan, "vlan", "V", 0, "Adds or updates vlan Must be between 2 and 4094") + createVirtualCircuitCmd.Flags().IntVarP(&speed, "speed", "s", 0, "bps speed or string (e.g. 52 - '52m' or '100g' or '4 gbps')") + createVirtualCircuitCmd.Flags().StringSliceVarP(&tags, "tags", "t", []string{}, `Adds the tags for the virtual-circuit --tags="tag1,tag2"`) + createVirtualCircuitCmd.Flags().StringVarP(&name, "name", "n", "", "Name of the Virtual Circuit") + createVirtualCircuitCmd.Flags().StringVarP(&description, "description", "d", "", "Description for a Virtual Circuit") + createVirtualCircuitCmd.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.") + + //VlanVirtualCircuit input params + createVirtualCircuitCmd.Flags().StringVarP(&vnid, "vnid", "", "", "Specify the UUID of the VLAN.") + + // VrfVirtualCircuit input params + createVirtualCircuitCmd.Flags().StringVarP(&md5, "md5", "M", "", "The plaintext BGP peering password shared by neighbors as an MD5 checksum") + createVirtualCircuitCmd.Flags().IntVarP(&peerAsn, "peer-asn", "a", 0, "The peer ASN that will be used with the VRF on the Virtual Circuit.") + createVirtualCircuitCmd.Flags().StringVarP(&vrf, "vrf-id", "v", "", "The UUID of the VRF that will be associated with the Virtual Circuit.") + createVirtualCircuitCmd.Flags().StringVarP(&subnet, "subnet", "S", "", "The /30 or /31 subnet of one of the VRF IP Blocks that will be used with the VRF for the Virtual Circuit. ") + createVirtualCircuitCmd.Flags().StringVarP(&customerIP, "customer-ip", "", "", "An IP address from the subnet that will be used on the Customer side") + createVirtualCircuitCmd.Flags().StringVarP(&metalIP, "metal-ip", "m", "", "An IP address from the subnet that will be used on the Metal side. ") + + _ = createVirtualCircuitCmd.MarkFlagRequired("connection-id") + _ = createVirtualCircuitCmd.MarkFlagRequired("port-id") + _ = createVirtualCircuitCmd.MarkFlagRequired("project-id") + return createVirtualCircuitCmd +} diff --git a/internal/virtualcircuit/delete.go b/internal/virtualcircuit/delete.go new file mode 100644 index 00000000..01b52287 --- /dev/null +++ b/internal/virtualcircuit/delete.go @@ -0,0 +1,38 @@ +package virtualcircuit + +import ( + "context" + "fmt" + + "github.com/spf13/cobra" +) + +func (c *Client) Delete() *cobra.Command { + var vcID string + + deleteVirtualcircuitCmd := &cobra.Command{ + Use: `delete -i `, + Short: "Deletes a virtual-circuit.", + Long: "Deletes the specified virtual-circuit.", + Example: ` # Deletes the specified virtual-circuit: + metal vc delete -i 7ec86e23-8dcf-48ed-bd9b-c25c20958277`, + + RunE: func(cmd *cobra.Command, args []string) error { + cmd.SilenceUsage = true + inc := []string{} + exc := []string{} + _, _, err := c.Service.DeleteVirtualCircuit(context.Background(), vcID).Include(inc).Exclude(exc).Execute() + if err != nil { + return fmt.Errorf("could not delete Metal Virtual circuit: %w", err) + } + fmt.Println("virtual-circuit deletion initiated. Please check 'metal virtual-circuit get -i", vcID, "' for status") + + return nil + }, + } + + deleteVirtualcircuitCmd.Flags().StringVarP(&vcID, "id", "i", "", "Specify the UUID of the virtual-circuit.") + _ = deleteVirtualcircuitCmd.MarkFlagRequired("id") + + return deleteVirtualcircuitCmd +} diff --git a/internal/virtualcircuit/retrieve.go b/internal/virtualcircuit/retrieve.go new file mode 100644 index 00000000..80fdd010 --- /dev/null +++ b/internal/virtualcircuit/retrieve.go @@ -0,0 +1,65 @@ +package virtualcircuit + +import ( + "context" + "fmt" + "strconv" + "time" + + metal "github.com/equinix/equinix-sdk-go/services/metalv1" + "github.com/spf13/cobra" +) + +type vcParamOuter interface { + GetId() string + GetName() string + GetSpeed() int32 + GetCreatedAt() time.Time +} + +var ( + _ vcParamOuter = (*metal.VlanVirtualCircuit)(nil) + _ vcParamOuter = (*metal.VrfVirtualCircuit)(nil) +) + +func getParams(p vcParamOuter) (id, name, speed, time string) { + id = p.GetId() + name = p.GetName() + sp := p.GetSpeed() + speed = strconv.FormatInt(int64(sp), 10) + time = p.GetCreatedAt().String() + return id, name, speed, time +} + +func (c *Client) Retrieve() *cobra.Command { + var vcID string + retrieveVirtualCircuitCmd := &cobra.Command{ + Use: "get -i ", + Aliases: []string{"list"}, + Short: "Retrieves virtual circuit for a specific circuit Id.", + Long: "Retrieves virtual circuit for a specific circuit Id.", + Example: ` # Retrieve virtual circuit for a specific circuit:: + + # Retrieve the details of a specific virtual-circuit: + metal vc get -i e9a969b3-8911-4667-9d99-57cd3dd4ef6f`, + + RunE: func(cmd *cobra.Command, args []string) error { + cmd.SilenceUsage = true + + inc := []string{} + header := []string{"ID", "Name", "Speed", "Created"} + vcUID, _, err := c.Service.GetVirtualCircuit(context.Background(), vcID).Include(c.Servicer.Includes(inc)).Execute() + if err != nil { + return fmt.Errorf("could not listvirtual circuits : %w", err) + } + data := make([][]string, 1) + + id, name, speed, time := getParams(vcUID.VlanVirtualCircuit) + data[0] = []string{id, name, speed, time} + return c.Out.Output(vcUID, header, &data) + }, + } + retrieveVirtualCircuitCmd.Flags().StringVarP(&vcID, "id", "i", "", "Specify UUID of the virtual-circuit") + _ = retrieveVirtualCircuitCmd.MarkFlagRequired("id") + return retrieveVirtualCircuitCmd +} diff --git a/internal/virtualcircuit/update.go b/internal/virtualcircuit/update.go new file mode 100644 index 00000000..2b1a2951 --- /dev/null +++ b/internal/virtualcircuit/update.go @@ -0,0 +1,150 @@ +package virtualcircuit + +import ( + "context" + "fmt" + "strconv" + + metal "github.com/equinix/equinix-sdk-go/services/metalv1" + "github.com/spf13/cobra" +) + +type vcUpdateParamBuilder interface { + SetName(string) + SetDescription(string) + SetTags([]string) +} + +// compile-time assertion that parameter types implement vcParamBuilder +var ( + _ vcParamBuilder = (*metal.VlanVirtualCircuitUpdateInput)(nil) + _ vcParamBuilder = (*metal.VrfVirtualCircuitUpdateInput)(nil) +) + +func updateParams(p vcUpdateParamBuilder, name, description string, tags []string) { + if name > "" { + p.SetName(name) + } + if description > "" { + p.SetDescription(description) + } + + if len(tags) > 0 { + p.SetTags(tags) + } +} + +func updateVlanVirtualCircuit(vlanUpdateInput *metal.VlanVirtualCircuitUpdateInput, vnid, speed string) metal.VirtualCircuitUpdateInput { + vlanUpdateInput.SetVnid(vnid) + + // As per the Spec It range is [ 2 .. 4094 ] + if speed != "" { + vlanUpdateInput.SetSpeed(speed) + } + + return metal.VirtualCircuitUpdateInput{VlanVirtualCircuitUpdateInput: vlanUpdateInput} +} + +func updateVrfVirtualCircuit(vrfUpdateInput *metal.VrfVirtualCircuitUpdateInput, customerIP, metalIP, subnet, md5, speed string, peerAsn int) metal.VirtualCircuitUpdateInput { + + vrfUpdateInput.SetSpeed(speed) + vrfUpdateInput.SetSubnet(subnet) + vrfUpdateInput.SetCustomerIp(customerIP) + vrfUpdateInput.SetMetalIp(metalIP) + vrfUpdateInput.SetMd5(md5) + vrfUpdateInput.SetCustomerIp(customerIP) + + if peerAsn > 0 { + vrfUpdateInput.SetPeerAsn(int32(peerAsn)) + } + + return metal.VirtualCircuitUpdateInput{VrfVirtualCircuitUpdateInput: vrfUpdateInput} +} + +func (c *Client) Update() *cobra.Command { + var ( + description string + speed string + name string + vnid string + vcID string + subnet string + customerIP string + metalIP string + md5 string + tags []string + peerAsn int + ) + // updateConnectionCmd represents the updateConnectionCmd command + updateVirtualCircuitCmd := &cobra.Command{ + Use: `update -i [-v ] [-d ] [-n ] [-s ] [-t ]`, + Short: "Updates a virtualcircuit.", + Long: "Updates a specified virtualcircuit etiher of vlanID OR vrfID", + Example: ` # Updates a specified virtualcircuit etiher of vlanID OR vrfID: + + metal vc update [-i ] [-n ] [-d ] [-v ] [-s ] [-t ] + + metal vc update -i e2edb90b-a8ef-47cb-a577-63b0ba129c29 -d "test-inter-fri-dedicated" + + metal vc update [-i ] [-n ] [-d ] [-M ] [-a ] [-S ] [-c ] [-m ] [-t ]`, + + RunE: func(cmd *cobra.Command, args []string) error { + cmd.SilenceUsage = true + inc := []string{} + var ( + virtualCircuitUpdateInput metal.VirtualCircuitUpdateInput + err error + ) + header := []string{"ID", "Name", "Type", "Speed", "Created"} + + vnidFlag, _ := cmd.Flags().GetString("vnid") + if vnidFlag != "" { + vlanUpdateInput := metal.NewVlanVirtualCircuitUpdateInput() + updateParams(vlanUpdateInput, name, description, tags) + + virtualCircuitUpdateInput = updateVlanVirtualCircuit(vlanUpdateInput, vnid, speed) + } else { + vrfUpdateInput := metal.NewVrfVirtualCircuitUpdateInput() + + updateParams(vrfUpdateInput, name, description, tags) + + virtualCircuitUpdateInput = updateVrfVirtualCircuit(vrfUpdateInput, customerIP, metalIP, subnet, md5, speed, peerAsn) + } + vc, _, err := c.Service.UpdateVirtualCircuit(context.Background(), vcID).VirtualCircuitUpdateInput(virtualCircuitUpdateInput).Include(c.Servicer.Includes(inc)).Execute() + if err != nil { + return fmt.Errorf("could not update virtual-circuit: %w", err) + } + + data := make([][]string, 1) + if vnidFlag != "" { + data[0] = []string{vc.VlanVirtualCircuit.GetId(), vc.VlanVirtualCircuit.GetName(), string(vc.VlanVirtualCircuit.GetType()), strconv.Itoa(int(vc.VlanVirtualCircuit.GetSpeed())), vc.VlanVirtualCircuit.GetCreatedAt().String()} + } else { + data[0] = []string{vc.VrfVirtualCircuit.GetId(), vc.VrfVirtualCircuit.GetName(), string(vc.VrfVirtualCircuit.GetType()), strconv.Itoa(int(vc.VrfVirtualCircuit.GetSpeed())), vc.VrfVirtualCircuit.GetCreatedAt().String()} + } + + return c.Out.Output(vc, header, &data) + }, + } + + // Virtual Circuit Params + updateVirtualCircuitCmd.Flags().StringVarP(&vcID, "id", "i", "", "Specify the UUID of the virtual-circuit.") + + // Common params for both VlanVirtualCircuit and VrfVirtualCircit + updateVirtualCircuitCmd.Flags().StringVarP(&speed, "speed", "s", "", "Adds or updates Speed can be changed only if it is an interconnection on a Dedicated Port") + updateVirtualCircuitCmd.Flags().StringSliceVarP(&tags, "tags", "t", []string{}, `updates the tags for the virtual-circuit --tags="tag1,tag2".`) + updateVirtualCircuitCmd.Flags().StringVarP(&name, "name", "n", "", "Name of the Virtual Circuit") + updateVirtualCircuitCmd.Flags().StringVarP(&description, "description", "d", "", "Description for a Virtual Circuit") + + //VlanVirtualCircuit update input params + updateVirtualCircuitCmd.Flags().StringVarP(&vnid, "vnid", "v", "", "A Virtual Network record UUID or the VNID of a Metro Virtual Network in your project.") + + // VrfVirtualCircuit update input params + updateVirtualCircuitCmd.Flags().StringVarP(&md5, "md5", "M", "", "The plaintext BGP peering password shared by neighbors as an MD5 checksum") + updateVirtualCircuitCmd.Flags().IntVarP(&peerAsn, "peer-asn", "a", 0, "The peer ASN that will be used with the VRF on the Virtual Circuit.") + updateVirtualCircuitCmd.Flags().StringVarP(&subnet, "subnet", "S", "", "The /30 or /31 subnet of one of the VRF IP Blocks that will be used with the VRF for the Virtual Circuit. ") + updateVirtualCircuitCmd.Flags().StringVarP(&customerIP, "customer-ip", "c", "", "An IP address from the subnet that will be used on the Customer side") + updateVirtualCircuitCmd.Flags().StringVarP(&metalIP, "metal-ip", "m", "", "An IP address from the subnet that will be used on the Metal side. ") + + _ = updateVirtualCircuitCmd.MarkFlagRequired("id") + return updateVirtualCircuitCmd +} diff --git a/internal/virtualcircuit/virtualcircuit.go b/internal/virtualcircuit/virtualcircuit.go new file mode 100644 index 00000000..13d0135b --- /dev/null +++ b/internal/virtualcircuit/virtualcircuit.go @@ -0,0 +1,53 @@ +package virtualcircuit + +import ( + metal "github.com/equinix/equinix-sdk-go/services/metalv1" + "github.com/equinix/metal-cli/internal/outputs" + "github.com/spf13/cobra" +) + +type Client struct { + Servicer Servicer + Service metal.InterconnectionsApiService + Out outputs.Outputer +} + +func (c *Client) NewCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: `virtual-circuit`, + Aliases: []string{"vc"}, + Short: "virtual-circuit operations: create, get, update, delete", + Long: "For more information on https://deploy.equinix.com/developers/docs/metal/interconnections.", + 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).InterconnectionsApi + }, + } + + cmd.AddCommand( + c.Create(), + c.Retrieve(), + c.Update(), + c.Delete(), + ) + + return cmd +} + +type Servicer interface { + MetalAPI(*cobra.Command) *metal.APIClient + Filters() map[string]string + Includes(defaultIncludes []string) (incl []string) + Excludes(defaultExcludes []string) (excl []string) +} + +func NewClient(s Servicer, out outputs.Outputer) *Client { + return &Client{ + Servicer: s, + Out: out, + } +} diff --git a/test/e2e/virtualcircuittest/virtualcircuit_test.go b/test/e2e/virtualcircuittest/virtualcircuit_test.go new file mode 100644 index 00000000..20690662 --- /dev/null +++ b/test/e2e/virtualcircuittest/virtualcircuit_test.go @@ -0,0 +1,186 @@ +package virtualcircuittest + +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/virtualcircuit" + "github.com/equinix/metal-cli/test/helper" + "github.com/spf13/cobra" +) + +func TestCli_Vc_Test(t *testing.T) { + subCommand := "vc" + + 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: "create-virtual-circuit", + fields: fields{ + MainCmd: virtualcircuit.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 + "-vc-create-test" + projectId := helper.CreateTestProject(t, projName) + if projectId.GetId() != "" { + connId := helper.CreateTestInterConnection(t, projectId.GetId(), projName) + + vlanId := helper.CreateTestVlanWithVxLan(t, projectId.GetId(), 1110, projName) + if connId.GetId() != "" && vlanId.GetId() != "" { + portId := helper.GetInterconnPort(t, connId.GetId()) + + root.SetArgs([]string{subCommand, "create", "-P", projectId.GetId(), "-V", "1110", "--vnid", vlanId.GetId(), "-n", projName, "-s", "100", "-p", portId, "-c", connId.GetId()}) + + out := helper.ExecuteAndCaptureOutput(t, root) + + if !strings.Contains(string(out[:]), projName) { + t.Error("expected output should include " + projName + ", in the out string ") + } + idNamePattern := `(?m)^\| ([a-zA-Z0-9-]+) +\| *` + projName + ` *\|` + + // 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 { + vcId := strings.TrimSpace(match[1]) + t.Cleanup(func() { + helper.CleanTestVirtualCircuit(t, vcId) + }) + } + } + } + }, + }, + { + name: "delete-virtual-circuit", + fields: fields{ + MainCmd: virtualcircuit.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 + "-vc-delete-test" + projectId := helper.CreateTestProject(t, projName) + + if projectId.GetId() != "" { + connId := helper.CreateTestInterConnection(t, projectId.GetId(), projName) + + vlanId := helper.CreateTestVlanWithVxLan(t, projectId.GetId(), 1110, projName) + + portId := helper.GetInterconnPort(t, connId.GetId()) + + vcId := helper.CreateTestVirtualCircuit(t, projectId.GetId(), connId.GetId(), portId, vlanId.GetId(), projName) + + if connId.GetId() != "" && portId != "" && vlanId.GetId() != "" && vcId.VlanVirtualCircuit.GetId() != "" { + root.SetArgs([]string{subCommand, "delete", "-i", vcId.VlanVirtualCircuit.GetId()}) + + out := helper.ExecuteAndCaptureOutput(t, root) + + if !strings.Contains(string(out[:]), "virtual-circuit deletion initiated. Please check 'metal virtual-circuit get -i "+vcId.VlanVirtualCircuit.GetId()+" ' for status") { + t.Error("expected output should include virtual-circuit deletion initiated. Please check '" + "metal virtual-circuit get -i " + vcId.VlanVirtualCircuit.GetId() + " ' for status in the out string ") + } + } + } + }, + }, + + { + name: "get-virtual-circuit", + fields: fields{ + MainCmd: virtualcircuit.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 + "-vc-get-test" + + projectId := helper.CreateTestProject(t, projName) + if projectId.GetId() != "" { + connId := helper.CreateTestInterConnection(t, projectId.GetId(), projName) + + vlanId := helper.CreateTestVlanWithVxLan(t, projectId.GetId(), 1110, projName) + + portId := helper.GetInterconnPort(t, connId.GetId()) + + if connId.GetId() != "" && portId != "" && vlanId.GetId() != "" { + vcId := helper.CreateTestVirtualCircuit(t, projectId.GetId(), connId.GetId(), portId, vlanId.GetId(), projName) + if vcId.VlanVirtualCircuit.GetId() != "" { + root.SetArgs([]string{subCommand, "get", "-i", vcId.VlanVirtualCircuit.GetId()}) + + out := helper.ExecuteAndCaptureOutput(t, root) + + if !strings.Contains(string(out[:]), projName) && + !strings.Contains(string(out[:]), vcId.VlanVirtualCircuit.GetId()) { + t.Error("expected output should include " + projName + ", " + vcId.VlanVirtualCircuit.GetId() + "in the out string ") + } + } + } + } + }, + }, + + { + name: "update-virtual-circuit", + fields: fields{ + MainCmd: virtualcircuit.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 + "-vc-update-test" + projectId := helper.CreateTestProject(t, projName) + + if projectId.GetId() != "" { + connId := helper.CreateTestInterConnection(t, projectId.GetId(), projName) + + vlanId := helper.CreateTestVlanWithVxLan(t, projectId.GetId(), 1110, projName) + + portId := helper.GetInterconnPort(t, connId.GetId()) + + vvlanId := helper.CreateTestVlanWithVxLan(t, projectId.GetId(), 1111, projName) + vcId := helper.CreateTestVirtualCircuit(t, projectId.GetId(), connId.GetId(), portId, vlanId.GetId(), projName) + + if connId.GetId() != "" && portId != "" && vlanId.GetId() != "" && vcId.VlanVirtualCircuit.GetId() != "" { + + root.SetArgs([]string{subCommand, "update", "-i", vcId.VlanVirtualCircuit.GetId(), "-n", projName, "-v", vvlanId.GetId()}) + + out := helper.ExecuteAndCaptureOutput(t, root) + + if !strings.Contains(string(out[:]), vcId.VlanVirtualCircuit.GetId()) { + t.Error("expected output should include " + vcId.VlanVirtualCircuit.GetId() + " 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/vlan/vlan_creat_test.go b/test/e2e/vlan/vlan_creat_test.go deleted file mode 100644 index e6c823d5..00000000 --- a/test/e2e/vlan/vlan_creat_test.go +++ /dev/null @@ -1,62 +0,0 @@ -package vlan - -import ( - "strings" - "testing" - - root "github.com/equinix/metal-cli/internal/cli" - outputPkg "github.com/equinix/metal-cli/internal/outputs" - "github.com/equinix/metal-cli/internal/vlan" - "github.com/equinix/metal-cli/test/helper" - "github.com/spf13/cobra" -) - -func TestCli_Vlan_Create(t *testing.T) { - var err error - subCommand := "vlan" - rootClient := root.NewClient(helper.ConsumerToken, helper.URL, helper.Version) - 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: "create_virtual_network", - fields: fields{ - MainCmd: vlan.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() - projectName := "metal-cli-" + helper.GenerateRandomString(5) + "-vlan-create-pro" - project := helper.CreateTestProject(t, projectName) - if err != nil { - t.Error(err) - } - root.SetArgs([]string{subCommand, "create", "-p", project.GetId(), "-m", "da", "--vxlan", "2023", "-d", "metal-cli-vlan-test"}) - - out := helper.ExecuteAndCaptureOutput(t, root) - - if !strings.Contains(string(out[:]), "metal-cli-vlan-test") && - !strings.Contains(string(out[:]), "da") && - !strings.Contains(string(out[:]), "2023") { - t.Error("expected output should include metal-cli-vlan-test, da and 2023 strings 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/vlan/vlan_delete_test.go b/test/e2e/vlan/vlan_delete_test.go deleted file mode 100644 index 8860b63b..00000000 --- a/test/e2e/vlan/vlan_delete_test.go +++ /dev/null @@ -1,64 +0,0 @@ -package vlan - -import ( - "strings" - "testing" - - root "github.com/equinix/metal-cli/internal/cli" - outputPkg "github.com/equinix/metal-cli/internal/outputs" - "github.com/equinix/metal-cli/internal/vlan" - "github.com/equinix/metal-cli/test/helper" - "github.com/spf13/cobra" -) - -func TestCli_Vlan_Clean(t *testing.T) { - var vlanId string - var err error - subCommand := "vlan" - rootClient := root.NewClient(helper.ConsumerToken, helper.URL, helper.Version) - 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: "delete_virtual_network", - fields: fields{ - MainCmd: vlan.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() - projectName := "metal-cli-" + helper.GenerateRandomString(5) + "-vlan-get-pro" - project := helper.CreateTestProject(t, projectName) - if err != nil { - t.Error(err) - } - vlanId, err = helper.CreateTestVlanWithVxLan(t, project.GetId(), 2023, "metal-cli-vlan-get-test") - if len(vlanId) != 0 { - root.SetArgs([]string{subCommand, "delete", "-f", "-i", vlanId}) - - out := helper.ExecuteAndCaptureOutput(t, root) - - if !strings.Contains(string(out[:]), "Virtual Network "+vlanId+" successfully deleted.") { - t.Error("expected output should include Virtual Network " + vlanId + "successfully deleted." + "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/vlan/vlan_get_test.go b/test/e2e/vlan/vlan_get_test.go deleted file mode 100644 index 3e1cbd69..00000000 --- a/test/e2e/vlan/vlan_get_test.go +++ /dev/null @@ -1,68 +0,0 @@ -package vlan - -import ( - "strings" - "testing" - - root "github.com/equinix/metal-cli/internal/cli" - outputPkg "github.com/equinix/metal-cli/internal/outputs" - "github.com/equinix/metal-cli/internal/vlan" - "github.com/equinix/metal-cli/test/helper" - "github.com/spf13/cobra" -) - -func TestCli_Vlan_Get(t *testing.T) { - var vlanId string - var err error - subCommand := "vlan" - rootClient := root.NewClient(helper.ConsumerToken, helper.URL, helper.Version) - 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: "get_virtual_network", - fields: fields{ - MainCmd: vlan.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() - projectName := "metal-cli-" + helper.GenerateRandomString(5) + "-vlan-delete-pro" - project := helper.CreateTestProject(t, projectName) - if err != nil { - t.Error(err) - } - vlanId, err = helper.CreateTestVlanWithVxLan(t, project.GetId(), 2023, "metal-cli-vlan-delete-test") - if len(vlanId) != 0 { - root.SetArgs([]string{subCommand, "get", "-p", project.GetId()}) - - out := helper.ExecuteAndCaptureOutput(t, root) - - if !strings.Contains(string(out[:]), "metal-cli-vlan-get-test") && - !strings.Contains(string(out[:]), "da") && - !strings.Contains(string(out[:]), "2023") { - t.Error("expected output should include metal-cli-vlan-get-test, da and 2023 strings in the out string") - } - - helper.CleanTestVlan(t, vlanId) - } - }, - }, - } - - 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/vlan/vlan_test.go b/test/e2e/vlan/vlan_test.go new file mode 100644 index 00000000..e207260a --- /dev/null +++ b/test/e2e/vlan/vlan_test.go @@ -0,0 +1,128 @@ +package vlan + +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/vlan" + "github.com/equinix/metal-cli/test/helper" + "github.com/spf13/cobra" +) + +func TestCli_Vlan_Test(t *testing.T) { + subCommand := "vlan" + randName := helper.GenerateRandomString(5) + rootClient := root.NewClient(helper.ConsumerToken, helper.URL, helper.Version) + 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: "create_virtual_network", + fields: fields{ + MainCmd: vlan.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 + "-vlan-create-pro-test" + + projectId := helper.CreateTestProject(t, projName) + + root.SetArgs([]string{subCommand, "create", "-p", projectId.GetId(), "-m", "da", "--vxlan", "2023", "-d", projName}) + + out := helper.ExecuteAndCaptureOutput(t, root) + + if !strings.Contains(string(out[:]), projName) && + !strings.Contains(string(out[:]), "da") && + !strings.Contains(string(out[:]), "2023") { + t.Error("expected output should include metal-cli-vlan-test, da and 2023 strings in the out string") + } + idNamePattern := `(?m)^\| ([a-zA-Z0-9-]+) +\| *` + projName + ` *\|` + + // 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 { + vlanId := strings.TrimSpace(match[1]) + t.Cleanup(func() { + helper.CleanTestVlan(t, vlanId) + }) + } + }, + }, + + { + name: "delete_virtual_network", + fields: fields{ + MainCmd: vlan.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 + "-vlan-get-pro-test" + + projectId := helper.CreateTestProject(t, projName) + + vlanId := helper.CreateTestVlanWithVxLan(t, projectId.GetId(), 2023, projName) + if vlanId.GetId() != "" { + root.SetArgs([]string{subCommand, "delete", "-f", "-i", vlanId.GetId()}) + + out := helper.ExecuteAndCaptureOutput(t, root) + + if !strings.Contains(string(out[:]), "Virtual Network "+vlanId.GetId()+" successfully deleted.") { + t.Error("expected output should include Virtual Network " + vlanId.GetId() + "successfully deleted." + "in the out string") + } + } + }, + }, + { + name: "get_virtual_network", + fields: fields{ + MainCmd: vlan.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 + "-vlan-delete-pro-test" + + projectId := helper.CreateTestProject(t, projName) + vlanId := helper.CreateTestVlanWithVxLan(t, projectId.GetId(), 2023, projName) + if vlanId.GetId() != "" { + root.SetArgs([]string{subCommand, "get", "-p", projectId.GetId()}) + + out := helper.ExecuteAndCaptureOutput(t, root) + + if !strings.Contains(string(out[:]), projName) && + !strings.Contains(string(out[:]), "da") && + !strings.Contains(string(out[:]), "2023") { + t.Error("expected output should include " + projName + ", da and 2023 strings 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/helper/helper.go b/test/helper/helper.go index 03671044..82866930 100644 --- a/test/helper/helper.go +++ b/test/helper/helper.go @@ -310,7 +310,7 @@ func CleanTestIps(t *testing.T, ipsId string) error { return nil } -func CreateTestVlanWithVxLan(t *testing.T, projectId string, Id int, desc string) (string, error) { +func CreateTestVlanWithVxLan(t *testing.T, projectId string, Id int, desc string) *metalv1.VirtualNetwork { t.Helper() TestApiClient := TestClient() virtualNetworkCreateInput := *metalv1.NewVirtualNetworkCreateInput() @@ -320,9 +320,14 @@ func CreateTestVlanWithVxLan(t *testing.T, projectId string, Id int, desc string vlanresp, _, err := TestApiClient.VLANsApi.CreateVirtualNetwork(context.Background(), projectId).VirtualNetworkCreateInput(virtualNetworkCreateInput).Execute() if err != nil { - return "", fmt.Errorf("Error when calling `VLANsApi.CreateVirtualNetwork``: %v\n", err) + t.Fatalf("Error when calling `VLANsApi.CreateVirtualNetwork``: %v\n", err) } - return vlanresp.GetId(), nil + + t.Cleanup(func() { + CleanTestVlan(t, vlanresp.GetId()) + }) + + return vlanresp } func CleanTestVlan(t *testing.T, vlanId string) { @@ -386,10 +391,79 @@ func CleanTestGateway(t *testing.T, gatewayId string) error { if err != nil { return fmt.Errorf("Error when calling `MetalGatewaysApi.DeleteMetalGateway`` for %v: %v\n", gatewayId, err) } - return nil } +func CreateTestInterConnection(t *testing.T, projectId, name string) *metalv1.Interconnection { + t.Helper() + TestApiClient := TestClient() + + createOrganizationInterconnectionRequest := metalv1.CreateOrganizationInterconnectionRequest{DedicatedPortCreateInput: metalv1.NewDedicatedPortCreateInput("da", name, "primary", metalv1.DedicatedPortCreateInputType("dedicated"))} + + resp, _, err := TestApiClient.InterconnectionsApi.CreateProjectInterconnection(context.Background(), projectId).CreateOrganizationInterconnectionRequest(createOrganizationInterconnectionRequest).Execute() + if err != nil { + t.Fatalf("Error when calling `InterconnectionsApi.CreateProjectInterconnection``: %v\n", err) + } + + t.Cleanup(func() { + CleanTestInterConnection(t, resp.GetId()) + }) + return resp +} + +func CleanTestInterConnection(t *testing.T, connectionID string) { + t.Helper() + TestApiClient := TestClient() + _, resp, err := TestApiClient.InterconnectionsApi.DeleteInterconnection(context.Background(), connectionID).Execute() + if err != nil && resp.StatusCode != http.StatusNotFound { + t.Fatalf("Error when calling `InterconnectionsApi.DeleteInterconnection``for %v : %v\n", connectionID, err) + } +} + +func GetInterconnPort(t *testing.T, connId string) string { + t.Helper() + TestApiClient := TestClient() + resp, r, err := TestApiClient.InterconnectionsApi.ListInterconnectionPorts(context.Background(), connId).Execute() + if err != nil && r.StatusCode != http.StatusNotFound { + t.Fatalf("Error when calling `InterconnectionsApi.ListInterconnectionPorts``: %v\n", err) + } + + return resp.Ports[len(resp.Ports)-1].GetId() +} + +func CreateTestVirtualCircuit(t *testing.T, projectId, connId, portId, vlanId, name string) *metalv1.VirtualCircuit { + t.Helper() + TestApiClient := TestClient() + + vlanVCCreateInput := metalv1.NewVlanVirtualCircuitCreateInput(projectId) + vlanVCCreateInput.SetVnid(vlanId) + vlanVCCreateInput.SetSpeed(100) + vlanVCCreateInput.SetNniVlan(1110) + vlanVCCreateInput.SetName(name) + + virtualCircuitCreateInput := metalv1.VirtualCircuitCreateInput{VlanVirtualCircuitCreateInput: vlanVCCreateInput} + + resp, _, err := TestApiClient.InterconnectionsApi.CreateInterconnectionPortVirtualCircuit(context.Background(), connId, portId).VirtualCircuitCreateInput(virtualCircuitCreateInput).Execute() + if err != nil { + t.Fatalf("Error when calling `InterconnectionsApi.CreateInterconnectionPortVirtualCircuit``: %v\n", err) + } + + t.Cleanup(func() { + CleanTestVirtualCircuit(t, resp.VlanVirtualCircuit.GetId()) + }) + return resp +} + +func CleanTestVirtualCircuit(t *testing.T, vcId string) { + t.Helper() + TestApiClient := TestClient() + + _, resp, err := TestApiClient.InterconnectionsApi.DeleteVirtualCircuit(context.Background(), vcId).Execute() + if err != nil && resp.StatusCode != http.StatusNotFound { + t.Fatalf("Error when calling `InterconnectionsApi.DeleteVirtualCircuit``for %v : %v\n", vcId, err) + } +} + func CreateTestOrganization(t *testing.T, name string) *metalv1.Organization { TestApiClient := TestClient()