diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..76be012 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,3 @@ +issues: + exclude: + SA1019 diff --git a/cmd/db.go b/cmd/db.go index ad666bc..43db6c3 100644 --- a/cmd/db.go +++ b/cmd/db.go @@ -42,14 +42,34 @@ func DBUsage() string { // ExecuteDB launches several different subcommands and as of today is the main entry point // into automation of Astra -func ExecuteDB(args []string, confFile string, verbose bool) error { - clientInfo, err := pkg.ReadLogin(confFile) +func ExecuteDB(args []string, confFile pkg.ConfFiles, verbose bool) error { + hasToken, err := confFile.HasToken() if err != nil { - return fmt.Errorf("%v", err) + return fmt.Errorf("unable to read conf file %v with error %v", confFile.TokenPath, err) } - client, err := astraops.Authenticate(clientInfo, verbose) - if err != nil { - return fmt.Errorf("authenticate failed with error %v", err) + var client *astraops.AuthenticatedClient + if hasToken { + token, err := pkg.ReadToken(confFile.TokenPath) + if err != nil { + return fmt.Errorf("found token at %v but unable to read it with error %v", confFile.TokenPath, err) + } + client = astraops.AuthenticateToken(token, verbose) + } else { + hasSa, err := confFile.HasServiceAccount() + if err != nil { + return fmt.Errorf("unable to read conf file %v with error %v", confFile.SaPath, err) + } + if !hasSa { + return fmt.Errorf("unable to access any configuration, run astra-cli login first") + } + clientInfo, err := pkg.ReadLogin(confFile.SaPath) + if err != nil { + return fmt.Errorf("%v", err) + } + client, err = astraops.Authenticate(clientInfo, verbose) + if err != nil { + return fmt.Errorf("authenticate failed with error %v", err) + } } if len(args) == 0 { return &pkg.ParseError{ diff --git a/cmd/db/create.go b/cmd/db/create.go index b754f67..9a8a5aa 100644 --- a/cmd/db/create.go +++ b/cmd/db/create.go @@ -46,20 +46,22 @@ func ExecuteCreate(args []string, client *astraops.AuthenticatedClient) error { Err: err, } } + capacity := int32(*createDbCapacityUnitFlag) createDb := astraops.CreateDb{ Name: *createDbNameFlag, Keyspace: *createDbKeyspaceFlag, - CapacityUnits: *createDbCapacityUnitFlag, + CapacityUnits: capacity, Region: *createDbRegionFlag, User: *createDbUserFlag, Password: *createDbPasswordFlag, Tier: *createDbTierFlag, CloudProvider: *createDbCloudProviderFlag, } - id, _, err := client.CreateDb(createDb) + db, err := client.CreateDb(createDb) if err != nil { return fmt.Errorf("unable to create '%v' with error %v", createDb, err) } + id := db.ID fmt.Printf("database %v created\n", id) return nil } diff --git a/cmd/db/get.go b/cmd/db/get.go index e28b0dc..5bd3cce 100644 --- a/cmd/db/get.go +++ b/cmd/db/get.go @@ -49,7 +49,7 @@ func ExecuteGet(args []string, client *astraops.AuthenticatedClient) error { } } id := args[0] - var db astraops.DataBase + var db astraops.Database var err error if db, err = client.FindDb(id); err != nil { return fmt.Errorf("unable to get '%s' with error %v\n", id, err) @@ -59,7 +59,7 @@ func ExecuteGet(args []string, client *astraops.AuthenticatedClient) error { case "text": var rows [][]string rows = append(rows, []string{"name", "id", "status"}) - rows = append(rows, []string{db.Info.Name, db.ID, db.Status}) + rows = append(rows, []string{db.Info.Name, db.ID, string(db.Status)}) for _, row := range pkg.PadColumns(rows) { fmt.Println(strings.Join(row, " ")) } diff --git a/cmd/db/list.go b/cmd/db/list.go index f047780..136f60b 100644 --- a/cmd/db/list.go +++ b/cmd/db/list.go @@ -46,7 +46,7 @@ func ExecuteList(args []string, client *astraops.AuthenticatedClient) error { Err: err, } } - var dbs []astraops.DataBase + var dbs []astraops.Database var err error if dbs, err = client.ListDb(*includeFlag, *providerFlag, *startingAfterFlag, int32(*limitFlag)); err != nil { return fmt.Errorf("unable to get list of dbs with error %v", err) @@ -56,7 +56,7 @@ func ExecuteList(args []string, client *astraops.AuthenticatedClient) error { var rows [][]string rows = append(rows, []string{"name", "id", "status"}) for _, db := range dbs { - rows = append(rows, []string{db.Info.Name, db.ID, db.Status}) + rows = append(rows, []string{db.Info.Name, db.ID, string(db.Status)}) } for _, row := range pkg.PadColumns(rows) { fmt.Println(strings.Join(row, " ")) diff --git a/cmd/db/tiers.go b/cmd/db/tiers.go index 97f32c3..269ba92 100644 --- a/cmd/db/tiers.go +++ b/cmd/db/tiers.go @@ -66,7 +66,7 @@ func ExecuteTiers(args []string, client *astraops.AuthenticatedClient) error { rows = append(rows, []string{ tier.Tier, tier.CloudProvider, - tier.RegionDisplay, + tier.Region, fmt.Sprintf("%v/%v", tier.DatabaseCountUsed, tier.DatabaseCountLimit), fmt.Sprintf("%v/%v", tier.CapacityUnitsUsed, tier.CapacityUnitsLimit), fmt.Sprintf("$%.2f", costMonth), diff --git a/cmd/db/unpark.go b/cmd/db/unpark.go index 0cde111..0f512c0 100644 --- a/cmd/db/unpark.go +++ b/cmd/db/unpark.go @@ -38,7 +38,7 @@ func ExecuteUnpark(args []string, client *astraops.AuthenticatedClient) error { } id := args[0] fmt.Printf("starting to unpark database %v\n", id) - if err := client.UnPark(id); err != nil { + if err := client.Unpark(id); err != nil { return fmt.Errorf("unable to unpark '%s' with error %v\n", id, err) } fmt.Printf("database %v unparked\n", id) diff --git a/cmd/login.go b/cmd/login.go index f1f88cd..77e0177 100644 --- a/cmd/login.go +++ b/cmd/login.go @@ -29,7 +29,8 @@ var loginCmd = flag.NewFlagSet("login", flag.ExitOnError) var clientIDFlag = loginCmd.String("id", "", "clientId from service account. Ignored if -json flag is used.") var clientNameFlag = loginCmd.String("name", "", "clientName from service account. Ignored if -json flag is used.") var clientSecretFlag = loginCmd.String("secret", "", "clientSecret from service account. Ignored if -json flag is used.") -var clientJSONFlag = loginCmd.String("json", "", "copy the json for service account from the Astra page") +var clientJSONFlag = loginCmd.String("json", "", "copy the json for service account from the Astra site") +var authTokenFlag = loginCmd.String("token", "", "authtoken generated with enough rights to perform the devops actions. Generated from the Astra site") //LoginUsage returns the usage text for login func LoginUsage() string { @@ -37,13 +38,16 @@ func LoginUsage() string { } //ExecuteLogin logs into Astra -func ExecuteLogin(args []string, confDir string, confFile string) error { +func ExecuteLogin(args []string, confDir string, confFiles pkg.ConfFiles) error { if err := loginCmd.Parse(args); err != nil { return &pkg.ParseError{ Args: args, Err: fmt.Errorf("incorrect options with error %v", err), } } + if authTokenFlag != nil { + return makeConf(confDir, confFiles.TokenPath, *authTokenFlag) + } var clientJSON string if clientJSONFlag != nil { clientJSON = *clientJSONFlag @@ -71,13 +75,16 @@ func ExecuteLogin(args []string, confDir string, confFile string) error { Err: fmt.Errorf("clientSecret missing"), } } - } else { clientID := *clientIDFlag clientName := *clientNameFlag clientSecret := *clientSecretFlag clientJSON = fmt.Sprintf("{\"clientId\":\"%v\",\"clientName\":\"%v\",\"clientSecret\":\"%v:\"}", clientID, clientName, clientSecret) } + return makeConf(confDir, confFiles.SaPath, clientJSON) +} + +func makeConf(confDir, confFile, content string) error { if err := os.MkdirAll(confDir, 0700); err != nil { return fmt.Errorf("unable to get make config directory with error %s", err) } @@ -92,7 +99,7 @@ func ExecuteLogin(args []string, confDir string, confFile string) error { }() writer := bufio.NewWriter(f) //safe to write after validation - _, err = writer.Write([]byte(clientJSON)) + _, err = writer.Write([]byte(content)) if err != nil { return fmt.Errorf("error writing file") } diff --git a/go.mod b/go.mod index a59594c..0a7b48b 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,4 @@ module github.com/rsds143/astra-cli go 1.16 -require github.com/rsds143/astra-devops-sdk-go v0.2.0 +require github.com/rsds143/astra-devops-sdk-go v0.3.0 diff --git a/go.sum b/go.sum index 8a643ac..2e7253d 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,2 @@ -github.com/rsds143/astra-devops-sdk-go v0.2.0 h1:Oq5fWejjQ7pr3D7IeFrEO7hybvGHa++MdTMtvA+VEIU= -github.com/rsds143/astra-devops-sdk-go v0.2.0/go.mod h1:LQaUwm75Ydy/z71nl466Xv0yw8ib5b9L6laTFXbvtHU= +github.com/rsds143/astra-devops-sdk-go v0.3.0 h1:ymkYLcf5AfM1zYbyEmiETHzR34HcItp4n9b4GKGVj+Q= +github.com/rsds143/astra-devops-sdk-go v0.3.0/go.mod h1:LQaUwm75Ydy/z71nl466Xv0yw8ib5b9L6laTFXbvtHU= diff --git a/main.go b/main.go index 5dde8f2..8b24ea8 100644 --- a/main.go +++ b/main.go @@ -35,7 +35,7 @@ func usage() { } func main() { flag.Parse() - confDir, confFile, err := pkg.GetHome() + confDir, confFiles, err := pkg.GetHome() if err != nil { fmt.Printf("%v\n", err) os.Exit(3) @@ -46,9 +46,9 @@ func main() { } switch flag.Arg(0) { case "login": - err = cmd.ExecuteLogin(flag.Args()[1:], confDir, confFile) + err = cmd.ExecuteLogin(flag.Args()[1:], confDir, confFiles) case "db": - err = cmd.ExecuteDB(flag.Args()[1:], confFile, *verbose) + err = cmd.ExecuteDB(flag.Args()[1:], confFiles, *verbose) default: fmt.Printf("%q is not valid command.\n", flag.Arg(1)) os.Exit(1) diff --git a/pkg/conf.go b/pkg/conf.go index 9335b5a..d5b1f61 100644 --- a/pkg/conf.go +++ b/pkg/conf.go @@ -23,19 +23,78 @@ import ( "io" "os" "path" + "strings" ) +//ConfFiles supports both formats of credentials and will say if the token one is present +type ConfFiles struct { + TokenPath string + SaPath string +} + +//HasServiceAccount returns true if there is a service account file present and accessible +func (c ConfFiles) HasServiceAccount() (bool, error) { + if _, err := os.Stat(c.SaPath); err != nil { + if os.IsNotExist(err) { + return false, nil + } + return false, fmt.Errorf("warning error of %v is unexpected", err) + } + return true, nil +} + +//Hastoken returns true if there is a token file present and accessible +func (c ConfFiles) HasToken() (bool, error) { + if _, err := os.Stat(c.TokenPath); err != nil { + if os.IsNotExist(err) { + return false, nil + } + return false, fmt.Errorf("warning error of %v is unexpected", err) + } + return true, nil +} + // GetHome returns the configuration directory and file // error will return if there is no user home folder -func GetHome() (confDir string, confFile string, err error) { +func GetHome() (confDir string, confFiles ConfFiles, err error) { var home string home, err = os.UserHomeDir() if err != nil { - return "", "", fmt.Errorf("unable to get user home directory with error %s", err) + return "", ConfFiles{}, fmt.Errorf("unable to get user home directory with error %s", err) } confDir = path.Join(home, ".config", "astra") - confFile = path.Join(confDir, "sa.json") - return confDir, confFile, nil + + tokenFile := path.Join(confDir, "token") + saFile := path.Join(confDir, "sa.json") + return confDir, ConfFiles{ + TokenPath: tokenFile, + SaPath: saFile, + }, nil +} + +// ReadToken retrieves the login from the specified json file +func ReadToken(tokenFile string) (string, error) { + f, err := os.Open(tokenFile) + if err != nil { + return "", &FileNotFoundError{ + Path: tokenFile, + Err: fmt.Errorf("unable to read login file with error %w", err), + } + } + defer func() { + if err := f.Close(); err != nil { + fmt.Printf("warning unable to close %v with error %v", tokenFile, err) + } + }() + b, err := io.ReadAll(f) + if err != nil { + return "", fmt.Errorf("unable to read login file %s with error %w", tokenFile, err) + } + token := strings.Trim(string(b), "\n") + if !strings.HasPrefix(token, "AstraCS") { + return "", fmt.Errorf("invalid token in login file %s with error %s", tokenFile, err) + } + return token, nil } // ReadLogin retrieves the login from the specified json file