github.com/hazelops/ize@v1.1.12-0.20230915191306-97d7c0e48f11/internal/commands/exec.go (about) 1 package commands 2 3 import ( 4 "fmt" 5 "strings" 6 "text/template" 7 8 "github.com/aws/aws-sdk-go/aws" 9 "github.com/aws/aws-sdk-go/aws/awserr" 10 "github.com/aws/aws-sdk-go/service/ecs" 11 "github.com/hazelops/ize/internal/config" 12 "github.com/hazelops/ize/internal/requirements" 13 "github.com/hazelops/ize/pkg/ssmsession" 14 "github.com/hazelops/ize/pkg/templates" 15 "github.com/pterm/pterm" 16 "github.com/sirupsen/logrus" 17 "github.com/spf13/cobra" 18 ) 19 20 type ExecOptions struct { 21 Config *config.Project 22 AppName string 23 EcsCluster string 24 Command []string 25 Task string 26 ContainerName string 27 Explain bool 28 } 29 30 var explainExecTmpl = ` 31 TASK_ID=$(aws ecs list-tasks --cluster {{.Env}}-{{.Namespace}} --service-name {{.Env}}-{{svc}} --desired-status "RUNNING" | jq -r '.taskArns[]' | cut -d'/' -f3 | head -n 1) 32 33 aws ecs execute-command \ 34 --interactive \ 35 --region {{.AwsRegion}} \ 36 --cluster {{.Env}}-{{.Namespace}} \ 37 --task $TASK_ID \ 38 --container {{svc}} \ 39 --command {{command}} 40 ` 41 42 var execExample = templates.Examples(` 43 # Connect to a container in the ECS via AWS SSM and run command. 44 ize exec goblin ps aux 45 `) 46 47 func NewExecFlags(project *config.Project) *ExecOptions { 48 return &ExecOptions{ 49 Config: project, 50 } 51 } 52 53 func NewCmdExec(project *config.Project) *cobra.Command { 54 o := NewExecFlags(project) 55 56 cmd := &cobra.Command{ 57 Use: "exec [app-name] -- [commands]", 58 Example: execExample, 59 Short: "Execute command in ECS container", 60 Long: "Connect to a container in the ECS via AWS SSM and run command.\nIt uses app name as an argument.", 61 Args: cobra.MinimumNArgs(1), 62 ValidArgsFunction: config.GetApps, 63 RunE: func(cmd *cobra.Command, args []string) error { 64 cmd.SilenceUsage = true 65 argsLenAtDash := cmd.ArgsLenAtDash() 66 err := o.Complete(cmd, args, argsLenAtDash) 67 if err != nil { 68 return err 69 } 70 71 err = o.Validate() 72 if err != nil { 73 return err 74 } 75 76 err = o.Run() 77 if err != nil { 78 return err 79 } 80 81 return nil 82 }, 83 } 84 85 cmd.Flags().StringVar(&o.EcsCluster, "ecs-cluster", "", "set ECS cluster name") 86 cmd.Flags().StringVar(&o.Task, "task", "", "set task id") 87 cmd.Flags().StringVar(&o.ContainerName, "container-name", "", "set container name") 88 cmd.Flags().BoolVar(&o.Explain, "explain", false, "bash alternative shown") 89 90 return cmd 91 } 92 93 func (o *ExecOptions) Complete(cmd *cobra.Command, args []string, argsLenAtDash int) error { 94 if err := requirements.CheckRequirements(requirements.WithSSMPlugin()); err != nil { 95 return err 96 } 97 98 if o.EcsCluster == "" { 99 o.EcsCluster = fmt.Sprintf("%s-%s", o.Config.Env, o.Config.Namespace) 100 } 101 102 o.AppName = cmd.Flags().Args()[0] 103 104 if len(o.ContainerName) == 0 { 105 o.ContainerName = o.AppName 106 } 107 108 if argsLenAtDash > -1 { 109 o.Command = args[argsLenAtDash:] 110 } 111 112 return nil 113 } 114 115 func (o *ExecOptions) Validate() error { 116 if len(o.AppName) == 0 { 117 return fmt.Errorf("can't validate: app name must be specified") 118 } 119 120 if len(o.Command) == 0 { 121 return fmt.Errorf("can't validate: you must specify at least one command for the container") 122 } 123 124 return nil 125 } 126 127 func (o *ExecOptions) Run() error { 128 appName := fmt.Sprintf("%s-%s", o.Config.Env, o.AppName) 129 130 if o.Explain { 131 err := o.Config.Generate(explainExecTmpl, template.FuncMap{ 132 "svc": func() string { 133 return o.AppName 134 }, 135 "command": func() string { 136 return strings.Join(o.Command, " ") 137 }, 138 }) 139 if err != nil { 140 return err 141 } 142 143 return nil 144 } 145 146 logrus.Infof("app name: %s, cluster name: %s", appName, o.EcsCluster) 147 logrus.Infof("region: %s, profile: %s", o.Config.AwsProfile, o.Config.AwsRegion) 148 149 s, _ := pterm.DefaultSpinner.WithRemoveWhenDone().Start("Getting access to container...") 150 151 if o.Task == "" { 152 lto, err := o.Config.AWSClient.ECSClient.ListTasks(&ecs.ListTasksInput{ 153 Cluster: &o.EcsCluster, 154 DesiredStatus: aws.String(ecs.DesiredStatusRunning), 155 ServiceName: &appName, 156 }) 157 if aerr, ok := err.(awserr.Error); ok { 158 switch aerr.Code() { 159 case "ClusterNotFoundException": 160 return fmt.Errorf("ECS cluster %s not found", o.EcsCluster) 161 default: 162 return err 163 } 164 } 165 166 logrus.Debugf("list task output: %s", lto) 167 168 if len(lto.TaskArns) == 0 { 169 return fmt.Errorf("running task not found") 170 } 171 172 o.Task = *lto.TaskArns[0] 173 } 174 175 s.UpdateText("Executing command...") 176 177 out, err := o.Config.AWSClient.ECSClient.ExecuteCommand(&ecs.ExecuteCommandInput{ 178 Container: &o.AppName, 179 Interactive: aws.Bool(true), 180 Cluster: &o.EcsCluster, 181 Task: &o.Task, 182 Command: aws.String(strings.Join(o.Command, " ")), 183 }) 184 if aerr, ok := err.(awserr.Error); ok { 185 switch aerr.Code() { 186 case "ClusterNotFoundException": 187 return fmt.Errorf("ECS cluster %s not found", o.EcsCluster) 188 default: 189 return err 190 } 191 } 192 193 s.Success() 194 195 ssmCmd := ssmsession.NewSSMPluginCommand(o.Config.AwsRegion) 196 err = ssmCmd.Start(out.Session) 197 if err != nil { 198 return err 199 } 200 201 return nil 202 }