Skip to content

Commit

Permalink
feat: use both project and user API token and populate default org id…
Browse files Browse the repository at this point in the history
… and projectid (#325)

#265
  • Loading branch information
cprivitere authored Jan 30, 2024
2 parents 36f4f5e + d8f3e05 commit bf56e76
Showing 1 changed file with 55 additions and 30 deletions.
85 changes: 55 additions & 30 deletions internal/init/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"context"
"fmt"
"os"
"path"
"path/filepath"
"syscall"

Expand Down Expand Up @@ -87,41 +88,29 @@ func (c *Client) NewCommand() *cobra.Command {
token := string(b)
c.Servicer.SetToken(token)
metalGoClient := c.Servicer.MetalAPI(cmd)
c.UserService = *metalGoClient.UsersApi
c.ProjectService = *metalGoClient.ProjectsApi
c.UserService = *metalGoClient.UsersApi

include := []string{} // []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",
"timezone", "features", "avatar_url", "avatar_thumb_url", "two_factor_auth", "mailing_address", "max_projects", "verification_stage", "emails", "phone_number", "restricted",
"full_name", "email", "social_accounts", "opt_in_updated_at", "opt_in", "first_name", "last_name", "last_login_at"}
user, _, err := c.UserService.FindCurrentUser(context.Background()).Include(include).Exclude(exclude).Execute()
// Parse the entered key to figure out if it's a user or project
// level API Key
defaultOrgId, defaultProjectId, err := parseServiceToken(c.ProjectService, c.UserService)
if err != nil {
return err
}
organization := user.GetDefaultOrganizationId()
project := user.GetDefaultProjectId()
fmt.Printf("Organization ID [%s]: ", organization)

fmt.Printf("Organization ID [%s]: ", defaultOrgId)
userOrg := ""
fmt.Scanln(&userOrg)
if userOrg == "" {
userOrg = organization
}

// Choose the first project in the preferred org
if project == "" {
project, err = getFirstProjectID(c.ProjectService, userOrg)
if err != nil {
return err
}
userOrg = defaultOrgId
}

fmt.Printf("Project ID [%s]: ", project)
fmt.Printf("Project ID [%s]: ", defaultProjectId)

userProj := ""
fmt.Scanln(&userProj)
if userProj == "" {
userProj = project
userProj = defaultProjectId
}

b, err = formatConfig(userProj, userOrg, token)
Expand All @@ -135,22 +124,58 @@ func (c *Client) NewCommand() *cobra.Command {
return initCmd
}

func getFirstProjectID(s metal.ProjectsApiService, userOrg string) (string, error) {
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"}
resp, err := s.FindProjects(context.Background()).Include(include).Exclude(exclude).ExecuteWithPagination()
func parseServiceToken(p metal.ProjectsApiService, u metal.UsersApiService) (string, string, error) {
var defaultOrgId, defaultProjectId string
// Get first page of projects associated with provided token
projects, err := getFirstPageOfProjects(p)
if err != nil {
return "", err
return "", "", err
}

projects := resp.Projects
for _, p := range projects {
if p.Organization.GetId() == userOrg {
return p.GetId(), nil
switch numProj := len(projects); {
case numProj == 0:
// If no projects come back, warn the user but assume it's a valid user
// token
fmt.Println("WARN: No available projects found with the provided API Token")
defaultOrgId, defaultProjectId, err = getDefaultIds(u)
if err != nil {
return "", "", err
}
case numProj == 1:
// If only one project comes back, assume it's a project token
// and grab the ProjectID and Org Id from that single project
defaultProjectId = projects[0].GetId()
defaultOrgId = path.Base(projects[0].Organization.AdditionalProperties["href"].(string))
case numProj > 1:
// If more than one project comes back it must be a user token.
defaultOrgId, defaultProjectId, err = getDefaultIds(u)
if err != nil {
return "", "", err
}
}

return defaultOrgId, defaultProjectId, nil
}

func getDefaultIds(u metal.UsersApiService) (string, string, error) {
// Set up exclude list for user query
exclude := []string{"devices", "members", "memberships", "invitations", "ssh_keys", "volumes", "backend_transfer_enabled", "updated_at", "customdata", "event_alert_configuration",
"timezone", "features", "avatar_url", "avatar_thumb_url", "two_factor_auth", "mailing_address", "max_projects", "verification_stage", "emails", "phone_number", "restricted",
"full_name", "email", "social_accounts", "opt_in_updated_at", "opt_in", "first_name", "last_name", "last_login_at"}
user, _, err := u.FindCurrentUser(context.Background()).Exclude(exclude).Execute()
if err != nil {
return "", "", err
}
return user.GetDefaultOrganizationId(), user.GetDefaultProjectId(), err
}

return "", nil // it's ok to have no projects and no default project
func getFirstPageOfProjects(p metal.ProjectsApiService) ([]metal.Project, error) {
exclude := []string{"address", "backend_transfer_enabled", "created_at", "customdata", "description", "devices", "event_alert_configuration", "members", "memberships", "invitations", "ssh_keys", "tags", "transfers", "volumes", "updated_at"}
projectList, _, err := p.FindProjects(context.Background()).Exclude(exclude).Page(1).Execute()
if err != nil {
return nil, err
}
return projectList.Projects, err
}

func formatConfig(userProj, userOrg, token string) ([]byte, error) {
Expand Down

0 comments on commit bf56e76

Please sign in to comment.