Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: VRF support #358

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added .DS_Store
Binary file not shown.
83 changes: 83 additions & 0 deletions internal/vrf/create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package vrf

Check failure on line 1 in internal/vrf/create.go

View workflow job for this annotation

GitHub Actions / lint

: found packages vrf (create.go) and main (delete.go) in internal/vrf (typecheck)

import (
"context"
"fmt"
"strconv"
"strings"

metal "github.com/equinix-labs/metal-go/metal/v1"
"github.com/spf13/cobra"
)

func (c *Client) Create() *cobra.Command {
var (
projectID string
metro string
name string
description string
ipRanges []string
localASN int32
tags []string
)

// createVRFCmd represents the creatVRF command
createVRFCmd := &cobra.Command{
Use: `create vrf <my_vrf> [-m <metro_code>] [-AS int] [-I str] [-d <description>]`,
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
}
63 changes: 63 additions & 0 deletions internal/vrf/delete.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package main // or your appropriate package name
irusoeqx marked this conversation as resolved.
Show resolved Hide resolved

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 <metal_vrf_UUID> [-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
}
52 changes: 52 additions & 0 deletions internal/vrf/list.go
Original file line number Diff line number Diff line change
@@ -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 <project_UUID>`,
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.

Check failure on line 40 in internal/vrf/list.go

View workflow job for this annotation

GitHub Actions / lint

missing ',' in composite literal (typecheck)
}
header := []string{"ID", "Description", "VXLAN", "Facility", "Created"}

return c.Out.Output(vrfs, header, &data)

Check failure on line 44 in internal/vrf/list.go

View workflow job for this annotation

GitHub Actions / lint

missing ',' in composite literal (typecheck)
},
}

Check failure on line 46 in internal/vrf/list.go

View workflow job for this annotation

GitHub Actions / lint

expected operand, found '}' (typecheck)
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

Check failure on line 50 in internal/vrf/list.go

View workflow job for this annotation

GitHub Actions / lint

expected ';', found 'return' (typecheck)
}

Check failure on line 52 in internal/vrf/list.go

View workflow job for this annotation

GitHub Actions / lint

expected '}', found 'EOF' (typecheck)
47 changes: 47 additions & 0 deletions internal/vrf/vrf.go
Original file line number Diff line number Diff line change
@@ -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,
}
}
Loading