From 9d02f8fff04ca1a1b4131332a826cb961f7c8281 Mon Sep 17 00:00:00 2001 From: vasubabu Date: Mon, 5 Jun 2023 21:53:36 +0530 Subject: [PATCH] Added client metal-go --- go.mod | 1 + go.sum | 2 ++ internal/cli/root.go | 44 +++++++++++++++++++++++++++-- internal/init/init.go | 66 ++++++++++++++++++++++++++++--------------- 4 files changed, 89 insertions(+), 24 deletions(-) diff --git a/go.mod b/go.mod index c1c3e2f6..7d55e1af 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/equinix/metal-cli go 1.19 require ( + github.com/equinix-labs/metal-go v0.7.1 github.com/manifoldco/promptui v0.9.0 github.com/olekukonko/tablewriter v0.0.5 github.com/packethost/packngo v0.29.0 diff --git a/go.sum b/go.sum index 2af9d4e9..1e172e4a 100644 --- a/go.sum +++ b/go.sum @@ -62,6 +62,8 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/equinix-labs/metal-go v0.7.1 h1:dcLkBhPlTn2v4VJJBDZorYLo3QUs10FxQnwKJ8IczHk= +github.com/equinix-labs/metal-go v0.7.1/go.mod h1:qVHxbntL4uTflH4+OhtM7MNawG8j3swquGh3LLLPdfw= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= diff --git a/internal/cli/root.go b/internal/cli/root.go index b339385f..5a0c4395 100644 --- a/internal/cli/root.go +++ b/internal/cli/root.go @@ -29,6 +29,7 @@ import ( "runtime" "strings" + metal "github.com/equinix-labs/metal-go/metal/v1" "github.com/packethost/packngo" "github.com/spf13/cobra" "github.com/spf13/pflag" @@ -40,11 +41,13 @@ import ( const ( envPrefix = "METAL" configFileWithoutExtension = "metal" + debugVar = "PACKNGO_DEBUG" ) type Client struct { // apiClient client - apiClient *packngo.Client + apiClient *packngo.Client + metalApiClient *metal.APIClient includes *[]string // nolint:unused excludes *[]string // nolint:unused @@ -83,16 +86,33 @@ func NewClient(consumerToken, apiURL, Version string) *Client { } } +// This function provides backwards compatibility for the packngo +// debug environment variable while allowing us to introduce a new +// debug variable in the future that is not tied to packngo +func checkEnvForDebug() bool { + return os.Getenv(debugVar) != "" +} + func (c *Client) apiConnect(httpClient *http.Client) error { client, err := packngo.NewClientWithBaseURL(c.consumerToken, c.metalToken, httpClient, c.apiURL) if err != nil { - return fmt.Errorf("Could not create Client: %w", err) + return fmt.Errorf("could not create client: %w", err) } client.UserAgent = fmt.Sprintf("metal-cli/%s %s", c.Version, client.UserAgent) c.apiClient = client return nil } +func (c *Client) metalApiConnect(httpClient *http.Client) error { + configuration := metal.NewConfiguration() + configuration.Debug = checkEnvForDebug() + configuration.AddDefaultHeader("X-Auth-Token", c.Token()) + configuration.UserAgent = fmt.Sprintf("metal-cli/%s %s", c.Version, configuration.UserAgent) + metalgoClient := metal.NewAPIClient(configuration) + c.metalApiClient = metalgoClient + return nil +} + func (c *Client) Config(cmd *cobra.Command) *viper.Viper { if c.viper == nil { v := viper.New() @@ -169,6 +189,26 @@ func (c *Client) API(cmd *cobra.Command) *packngo.Client { return c.apiClient } +func (c *Client) MetalAPI(cmd *cobra.Command) *metal.APIClient { + if c.metalToken == "" { + log.Fatal("Equinix Metal authentication token not provided. Please set the 'METAL_AUTH_TOKEN' environment variable or create a configuration file using 'metal init'.") + } + + if c.metalApiClient == nil { + httpClient := &http.Client{ + Transport: &headerTransport{ + header: getAdditionalHeaders(cmd), + }, + } + + err := c.metalApiConnect(httpClient) + if err != nil { + log.Fatal(err) + } + } + return c.metalApiClient +} + func (c *Client) Token() string { return c.metalToken } diff --git a/internal/init/init.go b/internal/init/init.go index 7577c02c..15500b55 100644 --- a/internal/init/init.go +++ b/internal/init/init.go @@ -22,12 +22,13 @@ THE SOFTWARE. package init import ( + "context" "fmt" "os" "path/filepath" "syscall" - "github.com/packethost/packngo" + metal "github.com/equinix-labs/metal-go/metal/v1" "github.com/spf13/cobra" "golang.org/x/term" "sigs.k8s.io/yaml" @@ -35,8 +36,8 @@ import ( type Client struct { Servicer Servicer - UserService packngo.UserService - ProjectService packngo.ProjectService + UserService metal.UsersApiService + ProjectService metal.ProjectsApiService } func NewClient(s Servicer) *Client { @@ -85,25 +86,26 @@ func (c *Client) NewCommand() *cobra.Command { fmt.Println() token := string(b) c.Servicer.SetToken(token) - metalClient := c.Servicer.API(cmd) - c.UserService = metalClient.Users - c.ProjectService = metalClient.Projects + metalGoClient := c.Servicer.MetalAPI(cmd) + c.UserService = *metalGoClient.UsersApi + c.ProjectService = *metalGoClient.ProjectsApi - user, _, err := c.UserService.Current() + user, _, err := c.UserService.FindCurrentUser(context.Background()).Execute() if err != nil { return err } - organization := user.DefaultOrganizationID - project := "" - if user.DefaultProjectID != nil { - project = *user.DefaultProjectID - } + organization := user.AdditionalProperties["default_organization_id"] + project := fmt.Sprintf("%v", user.AdditionalProperties["default_project_id"]) fmt.Printf("Organization ID [%s]: ", organization) + defaultOrganizationId := fmt.Sprintf("%v", organization) + userOrg := "" fmt.Scanln(&userOrg) if userOrg == "" { - userOrg = organization + if defaultOrganizationId != "" { + userOrg = defaultOrganizationId + } } // Choose the first project in the preferred org @@ -113,6 +115,7 @@ func (c *Client) NewCommand() *cobra.Command { return err } } + fmt.Printf("Project ID [%s]: ", project) userProj := "" @@ -132,18 +135,38 @@ func (c *Client) NewCommand() *cobra.Command { return initCmd } -func getFirstProjectID(s packngo.ProjectService, userOrg string) (string, error) { - listOpts := &packngo.ListOptions{} - listOpts.Including("organization") - listOpts.Excluding("devices", "members", "memberships", "invitations", "max_devices", "ssh_keys", "volumes", "backend_transfer_enabled", "updated_at", "customdata", "event_alert_configuration") +func getAllProjects(s metal.ProjectsApiService) ([]metal.Project, error) { + var projects []metal.Project + + include := []string{"organization"} // []string | Nested attributes to include. Included objects will return their full attributes. Attribute names can be dotted (up to 3 levels) to included deeply nested objects. (optional) + exclude := []string{"devices", "members", "memberships", "invitations", "ssh_keys", "volumes", "backend_transfer_enabled", "updated_at", "customdata", "event_alert_configuration"} + page := int32(1) // int32 | Page to return (optional) (default to 1) + perPage := int32(56) // int32 | Items returned per page (optional) (default to 10) + for { + projectPage, _, err := s.FindProjects(context.Background()).Include(include).Exclude(exclude).Page(page).PerPage(perPage).Execute() + if err != nil { + return nil, err + } + projects = append(projects, projectPage.GetProjects()...) + if projectPage.Meta.GetLastPage() > projectPage.Meta.GetCurrentPage() { + page = page + 1 + continue + } + return projects, nil + } +} - projects, _, err := s.List(listOpts) +func getFirstProjectID(s metal.ProjectsApiService, userOrg string) (string, error) { + projects, err := getAllProjects(s) if err != nil { return "", err } + for _, p := range projects { - if p.Organization.ID == userOrg { - return p.ID, nil + organization := p.AdditionalProperties["organization"].(map[string]interface{}) + organization_id := fmt.Sprintf("%v", organization["id"]) + if organization_id == userOrg { + return p.GetId(), nil } } @@ -170,8 +193,7 @@ func writeConfig(config string, b []byte) error { } type Servicer interface { - API(*cobra.Command) *packngo.Client - ListOptions(defaultIncludes, defaultExcludes []string) *packngo.ListOptions + MetalAPI(*cobra.Command) *metal.APIClient SetToken(string) DefaultConfig(bool) string }