Skip to content

Commit

Permalink
refactoring and add account list if account is not specified
Browse files Browse the repository at this point in the history
  • Loading branch information
pgier committed Oct 17, 2021
1 parent ad1da31 commit 4dc70ce
Show file tree
Hide file tree
Showing 7 changed files with 191 additions and 148 deletions.
95 changes: 23 additions & 72 deletions cmd/kcloud/aws.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
package main

import (
"bufio"
"encoding/json"
"fmt"
"io"
"os"
"os/exec"
"strings"
"sync"
)

type awsListOp struct {
type awsListProfilesOp struct{}

type awsListClustersOp struct {
profile string
}

Expand All @@ -27,19 +26,16 @@ var awsRegions = map[string]bool{
"us-west-2": true,
}

// awsClusterSep is a separator used for the aws region and cluster name
const awsClusterSep = "/"

const awsCmd = "aws"

// parseAWSArgs expects at least one argument which is the AWS profile to use.
// If additional args are provided, tries to interpret them as the region and cluster name.
func parseAWSArgs(args []string) Op {
switch len(args) {
case 0:
return ErrorOp{fmt.Errorf("must specify aws profile name when using aws provider")}
return awsListProfilesOp{}
case 1:
return awsListOp{
return awsListClustersOp{
profile: args[0],
}
default:
Expand All @@ -55,24 +51,35 @@ func parseAWSArgs(args []string) Op {
}
}

// Run lists the available profiles
func (aws awsListProfilesOp) Run(stdout, stderr io.Writer) error {
profiles, err := awsListAvailableProfiles(DefaultAWSCredsFilePath())
if err != nil {
return fmt.Errorf("unable to parse AWS credentials: %w", err)
}
for _, profile := range profiles {
fmt.Println(profile)
}
return nil
}

// Run lists the clusters available to the given profile in the known regions.
// runs the 'aws eks list-clusters' command once for each region in parallel
func (aws awsListOp) Run(stdout, stderr io.Writer) error {
func (aws awsListClustersOp) Run(stdout, stderr io.Writer) error {
clusters := []string{}
wg := sync.WaitGroup{}
var groupErr error
for region := range awsRegions {
// TODO: could these calls be made in parallel?
wg.Add(1)
go func(profile, region string) {
defer wg.Done()
regionClusters, err := awsListClustersByRegion(profile, region)
regionClusters, err := awsListClustersInRegion(profile, region)
if err != nil {
groupErr = err
return
}
for _, c := range regionClusters {
clusters = append(clusters, region+awsClusterSep+c)
clusters = append(clusters, region+clusterNameSep+c)
}
}(aws.profile, region)
}
Expand All @@ -91,15 +98,7 @@ func (aws awsUpdateOp) Run(stdout, stderr io.Writer) error {
if !awsRegions[aws.region] {
fmt.Printf("warning: unrecognized region %v\n", aws.region)
}
cmd := exec.Command("aws", "--profile", aws.profile, "eks", "--region", aws.region, "update-kubeconfig", "--name", aws.cluster)
output, err := cmd.CombinedOutput()
if err != nil {
fmt.Println("failed command: ", cmd)
fmt.Print(string(output))
return fmt.Errorf("failed to build aws command: %w", err)
}
fmt.Println(string(output))
return nil
return RunCommand(awsCmd, "--profile", aws.profile, "eks", "--region", aws.region, "update-kubeconfig", "--name", aws.cluster)
}

type awsCreds struct {
Expand All @@ -108,56 +107,8 @@ type awsCreds struct {
secretAccessKey string
}

// TODO: resolve user home directory
const defaultAWSCredsFile = "/Users/paulgier/.aws/credentials"

func awsReadCredentials(credsFile string) ([]awsCreds, error) {
f, err := os.Open(credsFile)
if err != nil {
return nil, fmt.Errorf("unable to open AWS creds file %v: %w", credsFile, err)
}
creds := []awsCreds{}
scanner := bufio.NewScanner(f)

var section []string
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if strings.HasPrefix(line, "#") {
continue
} else if strings.HasPrefix(line, "[") {
if len(section) > 0 {
creds = append(creds, awsParseCredentials(section))
}
section = []string{line}
} else if line != "" {
section = append(section, line)
}
}
creds = append(creds, awsParseCredentials(section))
return creds, nil
}

func awsParseCredentials(section []string) awsCreds {
creds := awsCreds{
profile: strings.TrimSuffix(strings.TrimPrefix(section[0], "["), "]"),
}
for _, line := range section[1:] {
pair := strings.Split(line, "=")
if len(pair) > 1 {
switch strings.TrimSpace(pair[0]) {
case "aws_access_key_id":
creds.accessKeyID = strings.TrimSpace(pair[1])
case "aws_secret_access_key":
creds.secretAccessKey = strings.TrimSpace(pair[1])
}
}

}
return creds
}

func awsListClustersByRegion(profile string, region string) ([]string, error) {
cmd := exec.Command("aws", "--profile", profile, "eks", "--region", region, "list-clusters")
func awsListClustersInRegion(profile string, region string) ([]string, error) {
cmd := exec.Command(awsCmd, "--profile", profile, "eks", "--region", region, "list-clusters")
output, err := cmd.CombinedOutput()
if err != nil {
fmt.Println("failed command: ", cmd)
Expand Down
79 changes: 79 additions & 0 deletions cmd/kcloud/aws_credentials.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package main

import (
"bufio"
"fmt"
"os"
"path/filepath"
"strings"
)

const defaultAWSCredsFile = ".aws/credentials"

func DefaultAWSCredsFilePath() string {
homedir, _ := os.UserHomeDir()
return filepath.Join(homedir, defaultAWSCredsFile)
}

func awsListAvailableProfiles(credsFile string) ([]string, error) {
f, err := os.Open(credsFile)
if err != nil {
return nil, fmt.Errorf("unable to open AWS creds file %v: %w", credsFile, err)
}
profiles := []string{}
scanner := bufio.NewScanner(f)

for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if strings.HasPrefix(line, "[") {
profile := strings.TrimRight(strings.TrimLeft(line, "["), "]")
profiles = append(profiles, profile)
}
}
return profiles, nil
}

func awsReadCredentials(credsFile string) ([]awsCreds, error) {
f, err := os.Open(credsFile)
if err != nil {
return nil, fmt.Errorf("unable to open AWS creds file %v: %w", credsFile, err)
}
creds := []awsCreds{}
scanner := bufio.NewScanner(f)

var section []string
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if strings.HasPrefix(line, "#") {
continue
} else if strings.HasPrefix(line, "[") {
if len(section) > 0 {
creds = append(creds, awsParseCredentials(section))
}
section = []string{line}
} else if line != "" {
section = append(section, line)
}
}
creds = append(creds, awsParseCredentials(section))
return creds, nil
}

func awsParseCredentials(section []string) awsCreds {
creds := awsCreds{
profile: strings.TrimSuffix(strings.TrimPrefix(section[0], "["), "]"),
}
for _, line := range section[1:] {
pair := strings.Split(line, "=")
if len(pair) > 1 {
switch strings.TrimSpace(pair[0]) {
case "aws_access_key_id":
creds.accessKeyID = strings.TrimSpace(pair[1])
case "aws_secret_access_key":
creds.secretAccessKey = strings.TrimSpace(pair[1])
}
}

}
return creds
}
57 changes: 28 additions & 29 deletions cmd/kcloud/azure.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@ import (

const azureCmd = "az"

type azureListOp struct {
type azureListSubscriptionsOp struct{}

type azureListClustersOp struct {
subscription string
}

type azureUpdateOp struct {
type azureUpdateConfigOp struct {
subscription string
resourceGroup string
cluster string
Expand Down Expand Up @@ -46,65 +48,62 @@ func lookupSubscriptionID(subscription string) string {
func parseAzureArgs(args []string) Op {
switch len(args) {
case 0:
return ErrorOp{fmt.Errorf("must specify azure subscription name when using azure cloud provider")}
return azureListSubscriptionsOp{}
case 1:
return azureListOp{
return azureListClustersOp{
subscription: args[0],
}
default:
resourceGroup, cluster, err := parseQualifierCluster(args[1:])
if err != nil {
return ErrorOp{err}
}
return azureUpdateOp{
return azureUpdateConfigOp{
subscription: args[0],
resourceGroup: resourceGroup,
cluster: cluster,
}
}
}

// Run lists the clusters available in the given project.
type AzureSubscription struct {
Name string
Id string
}

// Run lists the subscriptions available to the user.
// runs the 'gcloud container clusters list' command
func (azr azureListOp) Run(stdout, stderr io.Writer) error {
func (azr azureListSubscriptionsOp) Run(stdout, stderr io.Writer) error {
return RunCommand(azureCmd, "account", "list", "--query", "[].{id: id, name: name}", "--out", "tsv")
}

// Run lists the clusters available in the given subscription.
// runs the 'az aks list' command
func (azr azureListClustersOp) Run(stdout, stderr io.Writer) error {
cmd := exec.Command(azureCmd, "aks", "list", "--subscription", lookupSubscriptionID(azr.subscription), "--query", "[].{name: name, resourceGroup: resourceGroup}")
output, err := cmd.CombinedOutput()
if err != nil {
fmt.Println("failed command: ", cmd)
fmt.Print(string(output))
return fmt.Errorf("failed to run az account set command: %w", err)
return err
}
//fmt.Println("command: ", cmd)
clusters, _ := parseAzureClusterList(output)
for _, c := range clusters {
clusterList := []azureCluster{}
if err := json.Unmarshal(output, &clusterList); err != nil {
return err
}
for _, c := range clusterList {
fmt.Println(c.ResourceGroup + clusterNameSep + c.Name)
}
return nil
}

// Run updates the kubeconfig file for the given region and cluster.
func (azr azureUpdateOp) Run(stdout, stderr io.Writer) error {
cmd := exec.Command(azureCmd, "aks", "get-credentials", "--overwrite-existing", "--subscription", lookupSubscriptionID(azr.subscription),
func (azr azureUpdateConfigOp) Run(stdout, stderr io.Writer) error {
return RunCommand(azureCmd, "aks", "get-credentials", "--overwrite-existing", "--subscription", lookupSubscriptionID(azr.subscription),
"--resource-group", azr.resourceGroup, "--name", azr.cluster)
output, err := cmd.CombinedOutput()
if err != nil {
fmt.Println("failed command: ", cmd)
fmt.Print(string(output))
return fmt.Errorf("failed to run az account set command: %w", err)
}
fmt.Println(string(output))
return nil
}

type azureCluster struct {
Name string
ResourceGroup string
}

func parseAzureClusterList(cmdOutput []byte) ([]azureCluster, error) {
clusterList := []azureCluster{}
if err := json.Unmarshal(cmdOutput, &clusterList); err != nil {
return nil, err
}
return clusterList, nil
}
17 changes: 17 additions & 0 deletions cmd/kcloud/command.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package main

import (
"fmt"
"os/exec"
)

// RunCommand runs the given command and prints the output
func RunCommand(command string, args ...string) error {
cmd := exec.Command(command, args...)
output, err := cmd.CombinedOutput()
fmt.Print(string(output))
if err != nil {
return fmt.Errorf("failed command: %v %w", cmd, err)
}
return nil
}
30 changes: 30 additions & 0 deletions cmd/kcloud/command_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package main

import (
"runtime"
"testing"
)

func TestRunCommand(t *testing.T) {
var err error
fooCmd := "foo"
err = RunCommand(fooCmd)
if err == nil {
t.Fatal("expected foo to fail but it succeeded")
}

emptyCmd := " "
err = RunCommand(emptyCmd)
if err == nil {
t.Fatal("expected foo to fail but it succeeded")
}

gcloudCmd := "ls -l"
if runtime.GOOS == "windows" {
gcloudCmd = "dir"
}
err = RunCommand(gcloudCmd)
if err != nil {
t.Fatal(err)
}
}
Loading

0 comments on commit 4dc70ce

Please sign in to comment.