Skip to content

Commit

Permalink
Add atmos aws commands. Add atmos aws eks update-kubeconfig comma…
Browse files Browse the repository at this point in the history
…nd (#136)

* Add `eks_utils`

* updates

* updates

* updates

* updates

* Add `atmos aws` commands

* Add `atmos aws` commands

* Add `atmos aws` commands

* Add `atmos aws` commands

* Add `atmos aws` commands

* Add `atmos aws` commands

* Add `atmos aws` commands

* Add `atmos aws` commands

* Add `atmos aws` commands

* Add `atmos aws` commands

* Add `atmos aws` commands

* Add `atmos aws` commands

* Add `atmos aws` commands

* Add `atmos aws` commands

* Add `atmos aws` commands

* Update cmd/aws_eks_update_kubeconfig.go

Co-authored-by: nitrocode <[email protected]>

* Update cmd/aws_eks.go

Co-authored-by: nitrocode <[email protected]>

* Update cmd/aws.go

Co-authored-by: nitrocode <[email protected]>

* Add `atmos aws` commands

* Update internal/exec/aws_eks_update_kubeconfig.go

Co-authored-by: Nuru <[email protected]>

* Update internal/exec/aws_eks_update_kubeconfig.go

Co-authored-by: Nuru <[email protected]>

* Add `atmos aws` commands

Co-authored-by: nitrocode <[email protected]>
Co-authored-by: Nuru <[email protected]>
  • Loading branch information
3 people authored Apr 21, 2022
1 parent 803ec0f commit 0cf815f
Show file tree
Hide file tree
Showing 14 changed files with 400 additions and 53 deletions.
17 changes: 17 additions & 0 deletions cmd/aws.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package cmd

import (
"github.com/spf13/cobra"
)

// awsCmd executes 'aws' CLI commands
var awsCmd = &cobra.Command{
Use: "aws",
Short: "Execute 'aws' commands",
Long: `This command executes 'aws' CLI commands`,
FParseErrWhitelist: struct{ UnknownFlags bool }{UnknownFlags: false},
}

func init() {
RootCmd.AddCommand(awsCmd)
}
17 changes: 17 additions & 0 deletions cmd/aws_eks.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package cmd

import (
"github.com/spf13/cobra"
)

// awsCmd executes 'aws eks' CLI commands
var awsEksCmd = &cobra.Command{
Use: "eks",
Short: "Execute 'aws eks' commands",
Long: `This command executes 'aws eks' CLI commands`,
FParseErrWhitelist: struct{ UnknownFlags bool }{UnknownFlags: false},
}

func init() {
awsCmd.AddCommand(awsEksCmd)
}
57 changes: 57 additions & 0 deletions cmd/aws_eks_update_kubeconfig.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package cmd

import (
e "github.com/cloudposse/atmos/internal/exec"
u "github.com/cloudposse/atmos/pkg/utils"
"github.com/spf13/cobra"
)

// awsEksCmdUpdateKubeconfigCmd executes 'aws eks update-kubeconfig' command
var awsEksCmdUpdateKubeconfigCmd = &cobra.Command{
Use: "update-kubeconfig",
Short: "Execute 'aws eks update-kubeconfig' command",

Long: `This command executes 'aws eks update-kubeconfig' in three different ways:
1. If all the required parameters (cluster name and AWS profile/role) are provided on the command-line,
then 'atmos' executes the command without requiring atmos CLI config and context.
For example: atmos aws eks update-kubeconfig --profile=<profile> --name=<cluster_name>
2. If 'component' and 'stack' are provided on the command-line,
then 'atmos' executes the command using atmos CLI config and stack's context by searching for the following settings:
- 'components.helmfile.cluster_name_pattern' in 'atmos.yaml' CLI config (and calculates the '--name' parameter using the pattern)
- 'components.helmfile.helm_aws_profile_pattern' in 'atmos.yaml' CLI config (and calculates the '--profile' parameter using the pattern)
- 'components.helmfile.kubeconfig_path' in 'atmos.yaml' CLI config
- the variables for the component in the provided stack
- 'region' from the variables for the component in the stack
For example: atmos aws eks update-kubeconfig <component> -s <stack>
3. Combination of the above. Provide a component and a stack, and override other parameters on the command line:
For example: atmos aws eks update-kubeconfig <component> -s <stack> --kubeconfig=<path_to_kubeconfig> --region=<region>
See https://docs.aws.amazon.com/cli/latest/reference/eks/update-kubeconfig.html for more information.`,

FParseErrWhitelist: struct{ UnknownFlags bool }{UnknownFlags: false},
Run: func(cmd *cobra.Command, args []string) {
err := e.ExecuteAwsEksUpdateKubeconfigCommand(cmd, args)
if err != nil {
u.PrintErrorToStdErrorAndExit(err)
}
},
}

// https://docs.aws.amazon.com/cli/latest/reference/eks/update-kubeconfig.html
func init() {
awsEksCmdUpdateKubeconfigCmd.DisableFlagParsing = false
awsEksCmdUpdateKubeconfigCmd.PersistentFlags().StringP("stack", "s", "", "atmos aws eks update-kubeconfig <component> -s <stack>")
awsEksCmdUpdateKubeconfigCmd.PersistentFlags().String("profile", "", "atmos aws eks update-kubeconfig --profile <profile>")
awsEksCmdUpdateKubeconfigCmd.PersistentFlags().String("name", "", "atmos aws eks update-kubeconfig --name <cluster name>")
awsEksCmdUpdateKubeconfigCmd.PersistentFlags().String("region", "", "atmos aws eks update-kubeconfig --region <region>")
awsEksCmdUpdateKubeconfigCmd.PersistentFlags().String("kubeconfig", "", "atmos aws eks update-kubeconfig --kubeconfig <path_to_kubeconfig>")
awsEksCmdUpdateKubeconfigCmd.PersistentFlags().String("role-arn", "", "atmos aws eks update-kubeconfig --role-arn <ARN>")
awsEksCmdUpdateKubeconfigCmd.PersistentFlags().Bool("dry-run", false, "atmos aws eks update-kubeconfig --dry-run=true")
awsEksCmdUpdateKubeconfigCmd.PersistentFlags().Bool("verbose", false, "atmos aws eks update-kubeconfig --verbose=true")
awsEksCmdUpdateKubeconfigCmd.PersistentFlags().String("alias", "", "atmos aws eks update-kubeconfig --alias <alias for the cluster context name>")

awsEksCmd.AddCommand(awsEksCmdUpdateKubeconfigCmd)
}
2 changes: 1 addition & 1 deletion cmd/describe_stacks.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (
"github.com/spf13/cobra"
)

// describeComponentCmd describes configuration for components
// describeStacksCmd describes configuration for stacks and components in the stacks
var describeStacksCmd = &cobra.Command{
Use: "stacks",
Short: "Execute 'describe stacks' command",
Expand Down
216 changes: 216 additions & 0 deletions internal/exec/aws_eks_update_kubeconfig.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
package exec

import (
"fmt"
c "github.com/cloudposse/atmos/pkg/config"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"path"
)

func ExecuteAwsEksUpdateKubeconfigCommand(cmd *cobra.Command, args []string) error {
flags := cmd.Flags()

stack, err := flags.GetString("stack")
if err != nil {
return err
}

profile, err := flags.GetString("profile")
if err != nil {
return err
}

name, err := flags.GetString("name")
if err != nil {
return err
}

region, err := flags.GetString("region")
if err != nil {
return err
}

kubeconfig, err := flags.GetString("kubeconfig")
if err != nil {
return err
}

roleArn, err := flags.GetString("role-arn")
if err != nil {
return err
}

dryRun, err := flags.GetBool("dry-run")
if err != nil {
return err
}

verbose, err := flags.GetBool("verbose")
if err != nil {
return err
}

alias, err := flags.GetString("alias")
if err != nil {
return err
}

component := ""
if len(args) > 0 {
component = args[0]
}

executeAwsEksUpdateKubeconfigContext := c.AwsEksUpdateKubeconfigContext{
Component: component,
Stack: stack,
Profile: profile,
ClusterName: name,
Region: region,
Kubeconfig: kubeconfig,
RoleArn: roleArn,
DryRun: dryRun,
Verbose: verbose,
Alias: alias,
}

return ExecuteAwsEksUpdateKubeconfig(executeAwsEksUpdateKubeconfigContext)
}

// ExecuteAwsEksUpdateKubeconfig executes 'aws eks update-kubeconfig'
// https://docs.aws.amazon.com/cli/latest/reference/eks/update-kubeconfig.html
func ExecuteAwsEksUpdateKubeconfig(kubeconfigContext c.AwsEksUpdateKubeconfigContext) error {
// AWS profile to authenticate to the cluster
profile := kubeconfigContext.Profile

// To assume a role for cluster authentication, specify an IAM role ARN with this option. For example, if you created a cluster while
// assuming an IAM role, then you must also assume that role to connect to the cluster the first time
roleArn := kubeconfigContext.RoleArn

if profile != "" && roleArn != "" {
return errors.New(fmt.Sprintf("Either 'profile' or 'role-arn' can be specified, but not both. Profile: '%s'. Role ARN: '%s'", profile, roleArn))
}

// AWS region
region := kubeconfigContext.Region

// Print the merged kubeconfig to stdout instead of writing it to the specified file
dryRun := kubeconfigContext.DryRun

// Print more detailed output when writing to the kubeconfig file, including the appended entries
verbose := kubeconfigContext.Verbose

// The name of the cluster for which to create a kubeconfig entry. This cluster must exist in your account and in
// the specified or configured default Region for your AWS CLI installation
clusterName := kubeconfigContext.ClusterName

// Optionally specify a kubeconfig file to append with your configuration. By default, the configuration is written to the first file path
// in the KUBECONFIG environment variable (if it is set) or the default kubeconfig path (.kube/config) in your home directory
kubeconfigPath := kubeconfigContext.Kubeconfig

// Alias for the cluster context name. Defaults to match cluster ARN
alias := kubeconfigContext.Alias

// Check if all the required parameters are provided to execute the command without needing `atmos.yaml` config and context
// The rest of the parameters are optional
requiredParamsProvided := clusterName != "" && (profile != "" || roleArn != "")

shellCommandWorkingDir := ""

if !requiredParamsProvided {
// If stack is not provided, calculate the stack name from the context (tenant, environment, stage)
if kubeconfigContext.Stack == "" {
err := c.InitConfig()
if err != nil {
return err
}

if len(c.Config.Stacks.NamePattern) < 1 {
return errors.New("stack name pattern must be provided in 'stacks.name_pattern' CLI config or 'ATMOS_STACKS_NAME_PATTERN' ENV variable")
}

stack, err := c.GetStackNameFromContextAndStackNamePattern(kubeconfigContext.Tenant,
kubeconfigContext.Environment, kubeconfigContext.Stage, c.Config.Stacks.NamePattern)
if err != nil {
return err
}

kubeconfigContext.Stack = stack
}

var configAndStacksInfo c.ConfigAndStacksInfo
configAndStacksInfo.ComponentFromArg = kubeconfigContext.Component
configAndStacksInfo.Stack = kubeconfigContext.Stack

configAndStacksInfo.ComponentType = "terraform"
configAndStacksInfo, err := ProcessStacks(configAndStacksInfo, true)
shellCommandWorkingDir = path.Join(c.ProcessedConfig.TerraformDirAbsolutePath, configAndStacksInfo.ComponentFolderPrefix, configAndStacksInfo.FinalComponent)
if err != nil {
configAndStacksInfo.ComponentType = "helmfile"
configAndStacksInfo, err = ProcessStacks(configAndStacksInfo, true)
shellCommandWorkingDir = path.Join(c.ProcessedConfig.HelmfileDirAbsolutePath, configAndStacksInfo.ComponentFolderPrefix, configAndStacksInfo.FinalComponent)
if err != nil {
return err
}
}

context := c.GetContextFromVars(configAndStacksInfo.ComponentVarsSection)

// `kubeconfig` can be overridden on the command line
if kubeconfigPath == "" {
kubeconfigPath = fmt.Sprintf("%s/%s-kubecfg", c.Config.Components.Helmfile.KubeconfigPath, kubeconfigContext.Stack)
}
// `clusterName` can be overridden on the command line
if clusterName == "" {
clusterName = c.ReplaceContextTokens(context, c.Config.Components.Helmfile.ClusterNamePattern)
}
// `profile` can be overridden on the command line
// `--role-arn` suppresses `profile` being automatically set
if profile == "" && roleArn == "" {
profile = c.ReplaceContextTokens(context, c.Config.Components.Helmfile.HelmAwsProfilePattern)
}
// `region` can be overridden on the command line
if region == "" {
region = context.Region
}
}

var args []string

// `--role-arn` suppresses `profile` being automatically set
if profile != "" && roleArn == "" {
args = append(args, fmt.Sprintf("--profile=%s", profile))
}

args = append(args, []string{
"eks",
"update-kubeconfig",
fmt.Sprintf("--name=%s", clusterName),
}...)

if dryRun {
args = append(args, "--dry-run")
}
if verbose {
args = append(args, "--verbose")
}
if roleArn != "" {
args = append(args, fmt.Sprintf("--role-arn=%s", roleArn))
}
if kubeconfigPath != "" {
args = append(args, fmt.Sprintf("--kubeconfig=%s", kubeconfigPath))
}
if alias != "" {
args = append(args, fmt.Sprintf("--alias=%s", alias))
}
if region != "" {
args = append(args, fmt.Sprintf("--region=%s", region))
}

err := ExecuteShellCommand("aws", args, shellCommandWorkingDir, nil, dryRun)
if err != nil {
return err
}

return nil
}
4 changes: 2 additions & 2 deletions internal/exec/helmfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ func ExecuteHelmfile(cmd *cobra.Command, args []string) error {
clusterName := c.ReplaceContextTokens(context, c.Config.Components.Helmfile.ClusterNamePattern)
color.Cyan(fmt.Sprintf("Downloading kubeconfig from the cluster '%s' and saving it to %s\n\n", clusterName, kubeconfigPath))

err = execCommand("aws",
err = ExecuteShellCommand("aws",
[]string{
"--profile",
helmAwsProfile,
Expand Down Expand Up @@ -157,7 +157,7 @@ func ExecuteHelmfile(cmd *cobra.Command, args []string) error {
fmt.Println(v)
}

err = execCommand(info.Command, allArgsAndFlags, componentPath, envVars, info.DryRun)
err = ExecuteShellCommand(info.Command, allArgsAndFlags, componentPath, envVars, info.DryRun)
if err != nil {
return err
}
Expand Down
4 changes: 2 additions & 2 deletions internal/exec/help.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func processHelp(componentType string, command string) error {
"and use it to authenticate with the cluster")
}

err := execCommand(componentType, []string{"--help"}, "", nil, false)
err := ExecuteShellCommand(componentType, []string{"--help"}, "", nil, false)
if err != nil {
return err
}
Expand All @@ -54,7 +54,7 @@ func processHelp(componentType string, command string) error {
color.Cyan(fmt.Sprintf("atmos %s %s <component> -s <stack> [options]", componentType, command))
color.Cyan(fmt.Sprintf("atmos %s %s <component> --stack <stack> [options]", componentType, command))

err := execCommand(componentType, []string{command, "--help"}, "", nil, false)
err := ExecuteShellCommand(componentType, []string{command, "--help"}, "", nil, false)
if err != nil {
return err
}
Expand Down
4 changes: 2 additions & 2 deletions internal/exec/shell_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import (
"strings"
)

// execCommand prints and executes the provided command with args and flags
func execCommand(command string, args []string, dir string, env []string, dryRun bool) error {
// ExecuteShellCommand prints and executes the provided command with args and flags
func ExecuteShellCommand(command string, args []string, dir string, env []string, dryRun bool) error {
cmd := exec.Command(command, args...)
cmd.Env = append(os.Environ(), env...)
cmd.Dir = dir
Expand Down
8 changes: 4 additions & 4 deletions internal/exec/terraform.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ func ExecuteTerraform(cmd *cobra.Command, args []string) error {
if info.SubCommand == "workspace" || c.Config.Components.Terraform.InitRunReconfigure == true {
initCommandWithArguments = []string{"init", "-reconfigure"}
}
err = execCommand(info.Command, initCommandWithArguments, componentPath, info.ComponentEnvList, info.DryRun)
err = ExecuteShellCommand(info.Command, initCommandWithArguments, componentPath, info.ComponentEnvList, info.DryRun)
if err != nil {
return err
}
Expand Down Expand Up @@ -254,9 +254,9 @@ func ExecuteTerraform(cmd *cobra.Command, args []string) error {

// Run `terraform workspace`
if info.SubCommand != "init" {
err = execCommand(info.Command, []string{"workspace", "select", info.TerraformWorkspace}, componentPath, info.ComponentEnvList, info.DryRun)
err = ExecuteShellCommand(info.Command, []string{"workspace", "select", info.TerraformWorkspace}, componentPath, info.ComponentEnvList, info.DryRun)
if err != nil {
err = execCommand(info.Command, []string{"workspace", "new", info.TerraformWorkspace}, componentPath, info.ComponentEnvList, info.DryRun)
err = ExecuteShellCommand(info.Command, []string{"workspace", "new", info.TerraformWorkspace}, componentPath, info.ComponentEnvList, info.DryRun)
if err != nil {
return err
}
Expand Down Expand Up @@ -305,7 +305,7 @@ func ExecuteTerraform(cmd *cobra.Command, args []string) error {

// Execute the provided command
if info.SubCommand != "workspace" {
err = execCommand(info.Command, allArgsAndFlags, componentPath, info.ComponentEnvList, info.DryRun)
err = ExecuteShellCommand(info.Command, allArgsAndFlags, componentPath, info.ComponentEnvList, info.DryRun)
if err != nil {
return err
}
Expand Down
Loading

0 comments on commit 0cf815f

Please sign in to comment.