From f1828565fbe4002db9227f05c49fbfb8d8cdfd07 Mon Sep 17 00:00:00 2001 From: Andrei Vsiakikh Date: Tue, 16 Apr 2024 13:31:02 +1200 Subject: [PATCH 01/10] fargate support --- cmd/runFargate.go | 60 +++++++++++++++ lib/runFargate.go | 185 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 245 insertions(+) create mode 100644 cmd/runFargate.go create mode 100644 lib/runFargate.go diff --git a/cmd/runFargate.go b/cmd/runFargate.go new file mode 100644 index 0000000..476f754 --- /dev/null +++ b/cmd/runFargate.go @@ -0,0 +1,60 @@ +package cmd + +import ( + "os" + + "github.com/apex/log" + "github.com/spf13/cobra" + "github.com/spf13/viper" + "github.com/springload/ecs-tool/lib" +) + +var runCmd = &cobra.Command{ + Use: "runFargate", + Short: "Runs a command", + Long: `Runs the specified command on an ECS cluster, optionally catching its output. + +It can modify the container command. +`, + Args: cobra.MinimumNArgs(1), + Run: func(cmd *cobra.Command, args []string) { + var containerName string + var commandArgs []string + if name := viper.GetString("container_name"); name == "" { + containerName = args[0] + commandArgs = args[1:] + } else { + containerName = name + commandArgs = args + } + + exitCode, err := lib.RunTask( + viper.GetString("profile"), + viper.GetString("cluster"), + viper.GetString("run.service"), + viper.GetString("task_definition"), + viper.GetString("image_tag"), + viper.GetStringSlice("image_tags"), + viper.GetString("workdir"), + containerName, + viper.GetString("log_group"), + viper.GetString("run.launch_type"), + commandArgs, + ) + if err != nil { + log.WithError(err).Error("Can't run task") + } + os.Exit(exitCode) + }, +} + +func init() { + rootCmd.AddCommand(runCmd) + runCmd.PersistentFlags().StringP("log_group", "l", "", "Name of the log group to get output") + runCmd.PersistentFlags().StringP("container_name", "", "", "Name of the container to modify parameters for") + runCmd.PersistentFlags().StringP("task_definition", "t", "", "name of task definition to use (required)") + viper.BindPFlag("log_group", runCmd.PersistentFlags().Lookup("log_group")) + viper.BindPFlag("container_name", runCmd.PersistentFlags().Lookup("container_name")) + viper.BindPFlag("task_definition", runCmd.PersistentFlags().Lookup("task_definition")) + viper.SetDefault("run.launch_type", "EC2") +} diff --git a/lib/runFargate.go b/lib/runFargate.go new file mode 100644 index 0000000..60e2643 --- /dev/null +++ b/lib/runFargate.go @@ -0,0 +1,185 @@ +package lib + +import ( + "fmt" + + "github.com/apex/log" + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ecs" +) + +// RunTask runs the specified one-off task in the cluster using the task definition +func RunFargate(profile, cluster, service, taskDefinitionName, imageTag string, imageTags []string, workDir, containerName, awslogGroup, launchType string, args []string) (exitCode int, err error) { + err = makeSession(profile) + if err != nil { + return 1, err + } + ctx := log.WithFields(&log.Fields{"task_definition": taskDefinitionName}) + + svc := ecs.New(localSession) + + describeResult, err := svc.DescribeTaskDefinition(&ecs.DescribeTaskDefinitionInput{ + TaskDefinition: aws.String(taskDefinitionName), + }) + if err != nil { + ctx.WithError(err).Error("Can't get task definition") + return 1, err + } + taskDefinition := describeResult.TaskDefinition + + var foundContainerName bool + if err := modifyContainerDefinitionImages(imageTag, imageTags, workDir, taskDefinition.ContainerDefinitions, ctx); err != nil { + return 1, err + } + for n, containerDefinition := range taskDefinition.ContainerDefinitions { + if aws.StringValue(containerDefinition.Name) == containerName { + foundContainerName = true + taskDefinition.ContainerDefinitions[n].Command = aws.StringSlice(args) + if awslogGroup != "" { + // modify log output driver to capture output to a predefined CloudWatch log + taskDefinition.ContainerDefinitions[n].LogConfiguration = &ecs.LogConfiguration{ + LogDriver: aws.String("awslogs"), + Options: map[string]*string{ + "awslogs-region": localSession.Config.Region, + "awslogs-group": aws.String(awslogGroup), + "awslogs-stream-prefix": aws.String(cluster), + }, + } + } + } + } + if !foundContainerName { + err := fmt.Errorf("Can't find container with specified name in the task definition") + ctx.WithFields(log.Fields{"container_name": containerName}).Error(err.Error()) + return 1, err + } + registerResult, err := svc.RegisterTaskDefinition(&ecs.RegisterTaskDefinitionInput{ + ContainerDefinitions: taskDefinition.ContainerDefinitions, + Cpu: taskDefinition.Cpu, + ExecutionRoleArn: taskDefinition.ExecutionRoleArn, + Family: taskDefinition.Family, + Memory: taskDefinition.Memory, + NetworkMode: taskDefinition.NetworkMode, + PlacementConstraints: taskDefinition.PlacementConstraints, + RequiresCompatibilities: taskDefinition.Compatibilities, + TaskRoleArn: taskDefinition.TaskRoleArn, + Volumes: taskDefinition.Volumes, + }) + if err != nil { + ctx.WithError(err).Error("Can't register task definition") + return 1, err + } + ctx.WithField( + "task_definition_arn", + aws.StringValue(registerResult.TaskDefinition.TaskDefinitionArn), + ).Debug("Registered the task definition") + + // deregister the task definition + defer func() { + ctx = ctx.WithFields(log.Fields{"task_definition_arn": aws.StringValue(registerResult.TaskDefinition.TaskDefinitionArn)}) + ctx.Debug("Deregistered the task definition") + _, err = svc.DeregisterTaskDefinition(&ecs.DeregisterTaskDefinitionInput{ + TaskDefinition: registerResult.TaskDefinition.TaskDefinitionArn, + }) + if err != nil { + ctx.WithError(err).Error("Can't deregister task definition") + } + }() + + runTaskInput := ecs.RunTaskInput{ + Cluster: aws.String(cluster), + TaskDefinition: registerResult.TaskDefinition.TaskDefinitionArn, + Count: aws.Int64(1), + StartedBy: aws.String("go-deploy"), + LaunchType: aws.String(launchType), + } + + if service != "" { + services, err := svc.DescribeServices(&ecs.DescribeServicesInput{ + Cluster: aws.String(cluster), + Services: []*string{aws.String(service)}, + }) + if err != nil { + ctx.WithError(err).Error("Can't get service") + return 1, err + } + + runTaskInput.NetworkConfiguration = services.Services[0].NetworkConfiguration + } + + runResult, err := svc.RunTask(&runTaskInput) + if err != nil { + ctx.WithError(err).Error("Can't run specified task") + return 1, err + } + + // if there are no running/pending tasks, then it failed to start + if len(runResult.Tasks) == 0 { + ctx.Error("No tasks could be run. Please check if the ECS cluster has enough resources") + return 1, err + } + // the task should be in PENDING state at this point + + ctx.Info("Waiting for the task to finish") + var tasks []*string + for _, task := range runResult.Tasks { + tasks = append(tasks, task.TaskArn) + ctx.WithField("task_arn", aws.StringValue(task.TaskArn)).Debug("Started task") + } + tasksInput := &ecs.DescribeTasksInput{ + Cluster: aws.String(cluster), + Tasks: tasks, + } + err = svc.WaitUntilTasksStopped(tasksInput) + if err != nil { + ctx.WithError(err).Error("The waiter has been finished with an error") + exitCode = 3 + } + tasksOutput, err := svc.DescribeTasks(tasksInput) + if err != nil { + ctx.WithError(err).Error("Can't describe stopped tasks") + return 1, err + } + for _, task := range tasksOutput.Tasks { + for _, container := range task.Containers { + ctx := log.WithFields(log.Fields{ + "container_name": aws.StringValue(container.Name), + }) + reason := aws.StringValue(container.Reason) + if len(reason) != 0 { + exitCode = 11 + ctx = ctx.WithField("reason", reason) + } else { + ctx = ctx.WithField("exit_code", aws.Int64Value(container.ExitCode)) + + } + if aws.Int64Value(container.ExitCode) == 0 && len(reason) == 0 { + ctx.Info("Container exited") + } else { + ctx.Error("Container exited") + } + if aws.StringValue(container.Name) == containerName { + if len(reason) == 0 { + exitCode = int(aws.Int64Value(container.ExitCode)) + if awslogGroup != "" { + // get log output + taskUUID, err := parseTaskUUID(container.TaskArn) + if err != nil { + log.WithFields(log.Fields{"task_arn": aws.StringValue(container.TaskArn)}).WithError(err).Error("Can't parse task uuid") + exitCode = 10 + continue + } + err = fetchCloudWatchLog(cluster, containerName, awslogGroup, taskUUID, false, ctx) + if err != nil { + log.WithError(err).Error("Can't fetch the logs") + exitCode = 10 + } + } + } + } + } + } + + return + +} From c2c4b14d163fd23458447561254f834766d1b4b6 Mon Sep 17 00:00:00 2001 From: Andrei Vsiakikh Date: Tue, 16 Apr 2024 13:38:36 +1200 Subject: [PATCH 02/10] fargate support --- cmd/exec.go | 69 ++++++++++ cmd/root.go | 5 + cmd/run.go | 5 +- cmd/runFargate.go | 97 +++++++------ lib/exec.go | 49 +++++++ lib/run.go | 7 +- lib/runFargate.go | 339 ++++++++++++++++++++++++++++------------------ 7 files changed, 386 insertions(+), 185 deletions(-) create mode 100644 cmd/exec.go create mode 100644 lib/exec.go diff --git a/cmd/exec.go b/cmd/exec.go new file mode 100644 index 0000000..6ef43d3 --- /dev/null +++ b/cmd/exec.go @@ -0,0 +1,69 @@ +package cmd + +import ( + "os" + "github.com/apex/log" + "github.com/spf13/cobra" + "github.com/spf13/viper" + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/ecs" + "github.com/springload/ecs-tool/lib" +) + +// execCmd executes a command in an existing ECS Fargate container. +var execCmd = &cobra.Command{ + Use: "exec", + Short: "Executes a command in an existing ECS Fargate container", + Long: `Executes a specified command in a running container on an ECS Fargate cluster. + This command allows for interactive sessions and command execution in Fargate.`, + Args: cobra.MinimumNArgs(1), + Run: func(cmd *cobra.Command, args []string) { + // Command to be executed within the container + command := args[0] + + // Establish an AWS session using the specified profile and region from configuration + sess, err := session.NewSessionWithOptions(session.Options{ + Profile: viper.GetString("profile"), + Config: aws.Config{ + Region: aws.String(viper.GetString("region")), + }, + }) + if err != nil { + log.WithError(err).Error("Failed to create AWS session") + os.Exit(1) + } + + // Create a new ECS service client with the session + svc := ecs.New(sess) + + // Execute the command in the specified ECS container using the ECS service client + err = lib.ExecuteCommandInContainer(svc, viper.GetString("cluster"), viper.GetString("service_name"), viper.GetString("container_name"), command) + if err != nil { + log.WithError(err).Error("Failed to execute command in ECS Fargate container") + os.Exit(1) + } else { + log.Info("Command executed successfully in ECS Fargate container") + os.Exit(0) + } + }, +} + +func init() { + rootCmd.AddCommand(execCmd) + execCmd.Flags().String("profile", "", "AWS profile to use") + execCmd.Flags().String("region", "", "AWS region to operate in") + execCmd.Flags().String("cluster", "", "Name of the ECS cluster") + execCmd.Flags().String("service_name", "", "Name of the ECS service") + execCmd.Flags().String("container_name", "", "Name of the container in the task") + + viper.BindPFlag("profile", execCmd.Flags().Lookup("profile")) + viper.BindPFlag("region", execCmd.Flags().Lookup("region")) + viper.BindPFlag("cluster", execCmd.Flags().Lookup("cluster")) + viper.BindPFlag("service_name", execCmd.Flags().Lookup("service_name")) + viper.BindPFlag("container_name", execCmd.Flags().Lookup("container_name")) + + // Set default values or read from a configuration file + viper.SetDefault("region", "us-east-1") + viper.SetDefault("container_name", "default-container") +} diff --git a/cmd/root.go b/cmd/root.go index 00d0860..138fe6e 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -51,12 +51,17 @@ func init() { rootCmd.PersistentFlags().StringP("workdir", "w", "", "Set working directory") rootCmd.PersistentFlags().StringP("image_tag", "", "", "Overrides the docker image tag in all container definitions. Overrides \"--image-tags\" flag.") rootCmd.PersistentFlags().StringSliceP("image_tags", "", []string{}, "Modifies the docker image tags in container definitions. Can be specified several times, one for each container definition. Also takes comma-separated values in one tag. I.e. if there are 2 containers and --image-tags is set once to \"new\", then the image tag of the first container will be modified, leaving the second one untouched. Gets overridden by \"--image-tag\". If you have 3 container definitions and want to modify tags for the 1st and the 3rd, but leave the 2nd unchanged, specify it as \"--image_tags first_tag,,last_tag\".") + rootCmd.PersistentFlags().StringP("task_definition", "t", "", "Name of the ECS task definition to use (required)") + + viper.BindPFlag("profile", rootCmd.PersistentFlags().Lookup("profile")) viper.BindPFlag("cluster", rootCmd.PersistentFlags().Lookup("cluster")) viper.BindPFlag("workdir", rootCmd.PersistentFlags().Lookup("workdir")) viper.BindPFlag("image_tag", rootCmd.PersistentFlags().Lookup("image_tag")) viper.BindPFlag("image_tags", rootCmd.PersistentFlags().Lookup("image_tags")) + viper.BindPFlag("task_definition", rootCmd.PersistentFlags().Lookup("task_definition")) + } diff --git a/cmd/run.go b/cmd/run.go index cc94327..5966d70 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -7,6 +7,7 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" "github.com/springload/ecs-tool/lib" + "fmt" ) var runCmd = &cobra.Command{ @@ -52,9 +53,9 @@ func init() { rootCmd.AddCommand(runCmd) runCmd.PersistentFlags().StringP("log_group", "l", "", "Name of the log group to get output") runCmd.PersistentFlags().StringP("container_name", "", "", "Name of the container to modify parameters for") - runCmd.PersistentFlags().StringP("task_definition", "t", "", "name of task definition to use (required)") viper.BindPFlag("log_group", runCmd.PersistentFlags().Lookup("log_group")) viper.BindPFlag("container_name", runCmd.PersistentFlags().Lookup("container_name")) - viper.BindPFlag("task_definition", runCmd.PersistentFlags().Lookup("task_definition")) + //viper.BindPFlag("task_definition", runCmd.PersistentFlags().Lookup("task_definition")) viper.SetDefault("run.launch_type", "EC2") + fmt.Println("Default launch_type set to:", viper.GetString("run.launch_type")) } diff --git a/cmd/runFargate.go b/cmd/runFargate.go index 476f754..4cfc090 100644 --- a/cmd/runFargate.go +++ b/cmd/runFargate.go @@ -1,60 +1,57 @@ package cmd import ( - "os" + "os" - "github.com/apex/log" - "github.com/spf13/cobra" - "github.com/spf13/viper" - "github.com/springload/ecs-tool/lib" + "github.com/apex/log" + "github.com/spf13/cobra" + "github.com/spf13/viper" + "github.com/springload/ecs-tool/lib" ) -var runCmd = &cobra.Command{ - Use: "runFargate", - Short: "Runs a command", - Long: `Runs the specified command on an ECS cluster, optionally catching its output. - -It can modify the container command. -`, - Args: cobra.MinimumNArgs(1), - Run: func(cmd *cobra.Command, args []string) { - var containerName string - var commandArgs []string - if name := viper.GetString("container_name"); name == "" { - containerName = args[0] - commandArgs = args[1:] - } else { - containerName = name - commandArgs = args - } - - exitCode, err := lib.RunTask( - viper.GetString("profile"), - viper.GetString("cluster"), - viper.GetString("run.service"), - viper.GetString("task_definition"), - viper.GetString("image_tag"), - viper.GetStringSlice("image_tags"), - viper.GetString("workdir"), - containerName, - viper.GetString("log_group"), - viper.GetString("run.launch_type"), - commandArgs, - ) - if err != nil { - log.WithError(err).Error("Can't run task") - } - os.Exit(exitCode) - }, +var runFargateCmd = &cobra.Command{ + Use: "runFargate", + Short: "Runs a command in Fargate mode", + Long: `Runs the specified command on an ECS cluster, optionally catching its output. + +This command is specifically tailored for future Fargate-specific functionality but currently duplicates the 'run' command.`, + Args: cobra.MinimumNArgs(1), + Run: func(cmd *cobra.Command, args []string) { + var containerName string + var commandArgs []string + if name := viper.GetString("container_name"); name == "" { + containerName = args[0] + commandArgs = args[1:] + } else { + containerName = name + commandArgs = args + } + + exitCode, err := lib.RunFargate( + viper.GetString("profile"), + viper.GetString("cluster"), + viper.GetString("run.service"), + viper.GetString("task_definition"), + viper.GetString("image_tag"), + viper.GetStringSlice("image_tags"), + viper.GetString("workdir"), + containerName, + viper.GetString("log_group"), + viper.GetString("run.launch_type"), + viper.GetString("run.security_group_filter"), + commandArgs, + ) + if err != nil { + log.WithError(err).Error("Can't run task in Fargate mode") + } + os.Exit(exitCode) + }, } func init() { - rootCmd.AddCommand(runCmd) - runCmd.PersistentFlags().StringP("log_group", "l", "", "Name of the log group to get output") - runCmd.PersistentFlags().StringP("container_name", "", "", "Name of the container to modify parameters for") - runCmd.PersistentFlags().StringP("task_definition", "t", "", "name of task definition to use (required)") - viper.BindPFlag("log_group", runCmd.PersistentFlags().Lookup("log_group")) - viper.BindPFlag("container_name", runCmd.PersistentFlags().Lookup("container_name")) - viper.BindPFlag("task_definition", runCmd.PersistentFlags().Lookup("task_definition")) - viper.SetDefault("run.launch_type", "EC2") + rootCmd.AddCommand(runFargateCmd) + viper.SetDefault("run.security_group_filter", "*ec2*") + viper.SetDefault("run.launch_type", "FARGATE") + + } diff --git a/lib/exec.go b/lib/exec.go new file mode 100644 index 0000000..b5f8238 --- /dev/null +++ b/lib/exec.go @@ -0,0 +1,49 @@ +package lib + +import ( + "fmt" + "github.com/aws/aws-sdk-go/aws" + //"github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/ecs" + "github.com/aws/aws-sdk-go/service/ecs/ecsiface" +) + +// FindLatestTaskArn finds the latest task ARN for the specified service within a cluster +func FindLatestTaskArn(svc ecsiface.ECSAPI, clusterName, serviceName string) (string, error) { + input := &ecs.ListTasksInput{ + Cluster: aws.String(clusterName), + ServiceName: aws.String(serviceName), + DesiredStatus: aws.String("RUNNING"), + MaxResults: aws.Int64(1), + } + + result, err := svc.ListTasks(input) + if err != nil || len(result.TaskArns) == 0 { + return "", fmt.Errorf("no running tasks found for service %s on cluster %s", serviceName, clusterName) + } + + return aws.StringValue(result.TaskArns[0]), nil +} + +// ExecuteCommandInContainer executes a specified command in a running container on an ECS Fargate cluster. +func ExecuteCommandInContainer(svc ecsiface.ECSAPI, cluster, serviceName, containerName, command string) error { + taskArn, err := FindLatestTaskArn(svc, cluster, serviceName) + if err != nil { + return err + } + + input := &ecs.ExecuteCommandInput{ + Cluster: aws.String(cluster), + Task: aws.String(taskArn), + Container: aws.String(containerName), + Interactive: aws.Bool(true), + Command: aws.String(command), + } + + _, err = svc.ExecuteCommand(input) + if err != nil { + return fmt.Errorf("failed to execute command: %v", err) + } + + return nil +} diff --git a/lib/run.go b/lib/run.go index f0768b8..4d6e548 100644 --- a/lib/run.go +++ b/lib/run.go @@ -10,12 +10,15 @@ import ( // RunTask runs the specified one-off task in the cluster using the task definition func RunTask(profile, cluster, service, taskDefinitionName, imageTag string, imageTags []string, workDir, containerName, awslogGroup, launchType string, args []string) (exitCode int, err error) { + ctx := log.WithFields(log.Fields{ + "task_definition": taskDefinitionName, + "launch_type": launchType, + }) err = makeSession(profile) if err != nil { return 1, err } - ctx := log.WithFields(&log.Fields{"task_definition": taskDefinitionName}) - + svc := ecs.New(localSession) describeResult, err := svc.DescribeTaskDefinition(&ecs.DescribeTaskDefinitionInput{ diff --git a/lib/runFargate.go b/lib/runFargate.go index 60e2643..babd767 100644 --- a/lib/runFargate.go +++ b/lib/runFargate.go @@ -1,146 +1,172 @@ package lib import ( - "fmt" - - "github.com/apex/log" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/ecs" + "fmt" + "github.com/apex/log" + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ecs" + "github.com/aws/aws-sdk-go/service/ec2" + "strings" ) -// RunTask runs the specified one-off task in the cluster using the task definition -func RunFargate(profile, cluster, service, taskDefinitionName, imageTag string, imageTags []string, workDir, containerName, awslogGroup, launchType string, args []string) (exitCode int, err error) { - err = makeSession(profile) - if err != nil { - return 1, err - } - ctx := log.WithFields(&log.Fields{"task_definition": taskDefinitionName}) +// RunFargate runs the specified one-off task in the cluster using the task definition +func RunFargate(profile, cluster, service, taskDefinitionName, imageTag string, imageTags []string, workDir, containerName, awslogGroup, launchType string, securityGroupFilter string, args []string) (exitCode int, err error) { + err = makeSession(profile) + if err != nil { + return 1, err + } + ctx := log.WithFields(log.Fields{"task_definition": taskDefinitionName}) - svc := ecs.New(localSession) + svc := ecs.New(localSession) + svcEC2 := ec2.New(localSession) // Assuming makeSession initializes localSession - describeResult, err := svc.DescribeTaskDefinition(&ecs.DescribeTaskDefinitionInput{ - TaskDefinition: aws.String(taskDefinitionName), - }) - if err != nil { - ctx.WithError(err).Error("Can't get task definition") - return 1, err - } - taskDefinition := describeResult.TaskDefinition + // Fetch subnets and security groups + subnets, err := fetchSubnetsByTag(svcEC2, "Tier", "private") + if err != nil { + log.WithError(err).Error("Failed to fetch subnets by tag") + return 1, err + } - var foundContainerName bool - if err := modifyContainerDefinitionImages(imageTag, imageTags, workDir, taskDefinition.ContainerDefinitions, ctx); err != nil { - return 1, err - } - for n, containerDefinition := range taskDefinition.ContainerDefinitions { - if aws.StringValue(containerDefinition.Name) == containerName { - foundContainerName = true - taskDefinition.ContainerDefinitions[n].Command = aws.StringSlice(args) - if awslogGroup != "" { - // modify log output driver to capture output to a predefined CloudWatch log - taskDefinition.ContainerDefinitions[n].LogConfiguration = &ecs.LogConfiguration{ - LogDriver: aws.String("awslogs"), - Options: map[string]*string{ - "awslogs-region": localSession.Config.Region, - "awslogs-group": aws.String(awslogGroup), - "awslogs-stream-prefix": aws.String(cluster), - }, - } - } - } - } - if !foundContainerName { - err := fmt.Errorf("Can't find container with specified name in the task definition") - ctx.WithFields(log.Fields{"container_name": containerName}).Error(err.Error()) - return 1, err - } - registerResult, err := svc.RegisterTaskDefinition(&ecs.RegisterTaskDefinitionInput{ - ContainerDefinitions: taskDefinition.ContainerDefinitions, - Cpu: taskDefinition.Cpu, - ExecutionRoleArn: taskDefinition.ExecutionRoleArn, - Family: taskDefinition.Family, - Memory: taskDefinition.Memory, - NetworkMode: taskDefinition.NetworkMode, - PlacementConstraints: taskDefinition.PlacementConstraints, - RequiresCompatibilities: taskDefinition.Compatibilities, - TaskRoleArn: taskDefinition.TaskRoleArn, - Volumes: taskDefinition.Volumes, - }) - if err != nil { - ctx.WithError(err).Error("Can't register task definition") - return 1, err - } - ctx.WithField( - "task_definition_arn", - aws.StringValue(registerResult.TaskDefinition.TaskDefinitionArn), - ).Debug("Registered the task definition") - - // deregister the task definition - defer func() { - ctx = ctx.WithFields(log.Fields{"task_definition_arn": aws.StringValue(registerResult.TaskDefinition.TaskDefinitionArn)}) - ctx.Debug("Deregistered the task definition") - _, err = svc.DeregisterTaskDefinition(&ecs.DeregisterTaskDefinitionInput{ - TaskDefinition: registerResult.TaskDefinition.TaskDefinitionArn, - }) - if err != nil { - ctx.WithError(err).Error("Can't deregister task definition") - } - }() - - runTaskInput := ecs.RunTaskInput{ - Cluster: aws.String(cluster), - TaskDefinition: registerResult.TaskDefinition.TaskDefinitionArn, - Count: aws.Int64(1), - StartedBy: aws.String("go-deploy"), - LaunchType: aws.String(launchType), - } +securityGroups, err := fetchSecurityGroupsByName(svcEC2, securityGroupFilter) + if err != nil { + log.WithError(err).Error("Failed to fetch security groups by name") + return 1, err + } - if service != "" { - services, err := svc.DescribeServices(&ecs.DescribeServicesInput{ - Cluster: aws.String(cluster), - Services: []*string{aws.String(service)}, - }) - if err != nil { - ctx.WithError(err).Error("Can't get service") - return 1, err - } + // Set up network configuration + networkConfiguration := &ecs.NetworkConfiguration{ + AwsvpcConfiguration: &ecs.AwsVpcConfiguration{ + Subnets: subnets, + SecurityGroups: securityGroups, + AssignPublicIp: aws.String("ENABLED"), // or "ENABLED" if public IP is needed + }, + } - runTaskInput.NetworkConfiguration = services.Services[0].NetworkConfiguration - } - runResult, err := svc.RunTask(&runTaskInput) - if err != nil { - ctx.WithError(err).Error("Can't run specified task") - return 1, err - } + ctx.WithFields(log.Fields{ + "Cluster": aws.StringValue(aws.String(cluster)), + "TaskDefinition": aws.StringValue(aws.String(taskDefinitionName)), + "LaunchType": aws.StringValue(aws.String(launchType)), + "Subnets": fmt.Sprint(subnets), + "SecurityGroups": fmt.Sprint(securityGroups), + "AssignPublicIP": aws.StringValue(networkConfiguration.AwsvpcConfiguration.AssignPublicIp), +}).Info("Attempting to launch task") - // if there are no running/pending tasks, then it failed to start - if len(runResult.Tasks) == 0 { - ctx.Error("No tasks could be run. Please check if the ECS cluster has enough resources") - return 1, err - } - // the task should be in PENDING state at this point + describeResult, err := svc.DescribeTaskDefinition(&ecs.DescribeTaskDefinitionInput{ + TaskDefinition: aws.String(taskDefinitionName), + }) + if err != nil { + ctx.WithError(err).Error("Can't get task definition") + return 1, err + } + taskDefinition := describeResult.TaskDefinition - ctx.Info("Waiting for the task to finish") - var tasks []*string - for _, task := range runResult.Tasks { - tasks = append(tasks, task.TaskArn) - ctx.WithField("task_arn", aws.StringValue(task.TaskArn)).Debug("Started task") - } - tasksInput := &ecs.DescribeTasksInput{ - Cluster: aws.String(cluster), - Tasks: tasks, - } - err = svc.WaitUntilTasksStopped(tasksInput) - if err != nil { - ctx.WithError(err).Error("The waiter has been finished with an error") - exitCode = 3 - } - tasksOutput, err := svc.DescribeTasks(tasksInput) - if err != nil { - ctx.WithError(err).Error("Can't describe stopped tasks") - return 1, err - } - for _, task := range tasksOutput.Tasks { + var foundContainerName bool + if err := modifyContainerDefinitionImages(imageTag, imageTags, workDir, taskDefinition.ContainerDefinitions, ctx); err != nil { + return 1, err + } + for n, containerDefinition := range taskDefinition.ContainerDefinitions { + if aws.StringValue(containerDefinition.Name) == containerName { + foundContainerName = true + // Use shell execution to interpret the command with any arguments + commandLine := strings.Join(args, " ") // Join args into a single command line + containerDefinition.Command = []*string{aws.String("sh"), aws.String("-c"), aws.String(commandLine)} + if awslogGroup != "" { + containerDefinition.LogConfiguration = &ecs.LogConfiguration{ + LogDriver: aws.String("awslogs"), + Options: map[string]*string{ + "awslogs-region": localSession.Config.Region, + "awslogs-group": aws.String(awslogGroup), + "awslogs-stream-prefix": aws.String(cluster), + }, + } + } + taskDefinition.ContainerDefinitions[n] = containerDefinition // Update the container definition + + } +} + if !foundContainerName { + err := fmt.Errorf("Can't find container with specified name in the task definition") + ctx.WithFields(log.Fields{"container_name": containerName}).Error(err.Error()) + return 1, err + } + + registerResult, err := svc.RegisterTaskDefinition(&ecs.RegisterTaskDefinitionInput{ + ContainerDefinitions: taskDefinition.ContainerDefinitions, + Cpu: taskDefinition.Cpu, + ExecutionRoleArn: taskDefinition.ExecutionRoleArn, + Family: taskDefinition.Family, + Memory: taskDefinition.Memory, + NetworkMode: taskDefinition.NetworkMode, + PlacementConstraints: taskDefinition.PlacementConstraints, + RequiresCompatibilities: taskDefinition.Compatibilities, + TaskRoleArn: taskDefinition.TaskRoleArn, + Volumes: taskDefinition.Volumes, + }) + if err != nil { + ctx.WithError(err).Error("Can't register task definition") + return 1, err + } + ctx.WithField("task_definition_arn", aws.StringValue(registerResult.TaskDefinition.TaskDefinitionArn)).Debug("Registered the task definition") + + // Deregister the task definition + defer func() { + _, err = svc.DeregisterTaskDefinition(&ecs.DeregisterTaskDefinitionInput{ + TaskDefinition: registerResult.TaskDefinition.TaskDefinitionArn, + }) + if err != nil { + ctx.WithError(err).Error("Can't deregister task definition") + } + }() + + // Run the task with network configuration + runTaskInput := ecs.RunTaskInput{ + Cluster: aws.String(cluster), + TaskDefinition: registerResult.TaskDefinition.TaskDefinitionArn, + Count: aws.Int64(1), + StartedBy: aws.String("go-deploy"), + LaunchType: aws.String(launchType), + NetworkConfiguration: networkConfiguration, + } + + runResult, err := svc.RunTask(&runTaskInput) + if err != nil { + ctx.WithError(err).Error("Can't run specified task") + return 1, err + } + if len(runResult.Tasks) == 0 { + ctx.Error("No tasks could be run. Please check if the ECS cluster has enough resources") + return 1, err + } + + ctx.Info("Waiting for the task to finish") + var tasks []*string + for _, task := range runResult.Tasks { + tasks = append(tasks, task.TaskArn) + ctx.WithField("task_arn", aws.StringValue(task.TaskArn)).Debug("Started task") + } + tasksInput := &ecs.DescribeTasksInput{ + Cluster: aws.String(cluster), + Tasks: tasks, + } + err = svc.WaitUntilTasksStopped(tasksInput) + if err != nil { + ctx.WithError(err).Error("The waiter has been finished with an error") + exitCode = 3 + return exitCode, err + } + + tasksOutput, err := svc.DescribeTasks(tasksInput) + if err != nil { + ctx.WithError(err).Error("Can't describe stopped tasks") + return 1, err + } + + + + + +for _, task := range tasksOutput.Tasks { for _, container := range task.Containers { ctx := log.WithFields(log.Fields{ "container_name": aws.StringValue(container.Name), @@ -158,9 +184,11 @@ func RunFargate(profile, cluster, service, taskDefinitionName, imageTag string, } else { ctx.Error("Container exited") } + if aws.StringValue(container.Name) == containerName { if len(reason) == 0 { exitCode = int(aws.Int64Value(container.ExitCode)) + if awslogGroup != "" { // get log output taskUUID, err := parseTaskUUID(container.TaskArn) @@ -180,6 +208,55 @@ func RunFargate(profile, cluster, service, taskDefinitionName, imageTag string, } } - return + return exitCode, nil +} + + + +// fetchSubnetsByTag fetches subnet IDs by a specific tag name and value +func fetchSubnetsByTag(svc *ec2.EC2, tagKey, tagValue string) ([]*string, error) { + input := &ec2.DescribeSubnetsInput{ + Filters: []*ec2.Filter{ + { + Name: aws.String(fmt.Sprintf("tag:%s", tagKey)), + Values: []*string{aws.String(tagValue)}, + }, + }, + } + + result, err := svc.DescribeSubnets(input) + if err != nil { + return nil, fmt.Errorf("error describing subnets: %w", err) + } + + var subnets []*string + for _, subnet := range result.Subnets { + subnets = append(subnets, subnet.SubnetId) + } + + return subnets, nil +} + + +func fetchSecurityGroupsByName(svc *ec2.EC2, securityGroupFilter string) ([]*string, error) { + input := &ec2.DescribeSecurityGroupsInput{ + Filters: []*ec2.Filter{ + { + Name: aws.String("group-name"), + Values: []*string{aws.String(securityGroupFilter)}, + }, + }, + } + + result, err := svc.DescribeSecurityGroups(input) + if err != nil { + return nil, fmt.Errorf("error describing security groups: %w", err) + } + + var securityGroups []*string + for _, sg := range result.SecurityGroups { + securityGroups = append(securityGroups, sg.GroupId) + } + return securityGroups, nil } From 117ab1be947e930f7181d9a023f11cca8477d4c4 Mon Sep 17 00:00:00 2001 From: Andrei Vsiakikh Date: Thu, 18 Apr 2024 12:40:19 +1200 Subject: [PATCH 03/10] start working on exec --- cmd/exec.go | 69 +++++++++++++++++------------------------------ cmd/run.go | 2 +- cmd/runFargate.go | 6 ++--- lib/exec.go | 60 +++++++++++++++++++++++++++++++++++------ lib/runFargate.go | 52 ++--------------------------------- lib/util.go | 48 +++++++++++++++++++++++++++++++++ 6 files changed, 130 insertions(+), 107 deletions(-) diff --git a/cmd/exec.go b/cmd/exec.go index 6ef43d3..78eaba3 100644 --- a/cmd/exec.go +++ b/cmd/exec.go @@ -2,68 +2,49 @@ package cmd import ( "os" + "github.com/apex/log" "github.com/spf13/cobra" "github.com/spf13/viper" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/session" - "github.com/aws/aws-sdk-go/service/ecs" "github.com/springload/ecs-tool/lib" ) -// execCmd executes a command in an existing ECS Fargate container. var execCmd = &cobra.Command{ Use: "exec", Short: "Executes a command in an existing ECS Fargate container", - Long: `Executes a specified command in a running container on an ECS Fargate cluster. - This command allows for interactive sessions and command execution in Fargate.`, + + Long: `Executes a specified command in a running container on an ECS Fargate cluster.`, Args: cobra.MinimumNArgs(1), Run: func(cmd *cobra.Command, args []string) { - // Command to be executed within the container - command := args[0] - - // Establish an AWS session using the specified profile and region from configuration - sess, err := session.NewSessionWithOptions(session.Options{ - Profile: viper.GetString("profile"), - Config: aws.Config{ - Region: aws.String(viper.GetString("region")), - }, - }) - if err != nil { - log.WithError(err).Error("Failed to create AWS session") - os.Exit(1) + viper.SetDefault("run.launch_type", "FARGATE") + var containerName string + var commandArgs []string + if name := viper.GetString("container_name"); name == "" { + containerName = args[0] + commandArgs = args[1:] + } else { + containerName = name + commandArgs = args } - // Create a new ECS service client with the session - svc := ecs.New(sess) - - // Execute the command in the specified ECS container using the ECS service client - err = lib.ExecuteCommandInContainer(svc, viper.GetString("cluster"), viper.GetString("service_name"), viper.GetString("container_name"), command) + exitCode, err := lib.ExecFargate( + viper.GetString("profile"), + viper.GetString("cluster"), + viper.GetString("run.service"), + viper.GetString("workdir"), + containerName, + viper.GetString("log_group"), + viper.GetString("run.launch_type"), + viper.GetString("run.security_group_filter"), + commandArgs, + ) if err != nil { - log.WithError(err).Error("Failed to execute command in ECS Fargate container") - os.Exit(1) - } else { - log.Info("Command executed successfully in ECS Fargate container") - os.Exit(0) + log.WithError(err).Error("Can't run task in Fargate mode") } + os.Exit(exitCode) }, } func init() { rootCmd.AddCommand(execCmd) - execCmd.Flags().String("profile", "", "AWS profile to use") - execCmd.Flags().String("region", "", "AWS region to operate in") - execCmd.Flags().String("cluster", "", "Name of the ECS cluster") - execCmd.Flags().String("service_name", "", "Name of the ECS service") - execCmd.Flags().String("container_name", "", "Name of the container in the task") - - viper.BindPFlag("profile", execCmd.Flags().Lookup("profile")) - viper.BindPFlag("region", execCmd.Flags().Lookup("region")) - viper.BindPFlag("cluster", execCmd.Flags().Lookup("cluster")) - viper.BindPFlag("service_name", execCmd.Flags().Lookup("service_name")) - viper.BindPFlag("container_name", execCmd.Flags().Lookup("container_name")) - - // Set default values or read from a configuration file - viper.SetDefault("region", "us-east-1") - viper.SetDefault("container_name", "default-container") } diff --git a/cmd/run.go b/cmd/run.go index 5966d70..fc353f2 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -19,6 +19,7 @@ It can modify the container command. `, Args: cobra.MinimumNArgs(1), Run: func(cmd *cobra.Command, args []string) { + viper.SetDefault("run.launch_type", "EC2") var containerName string var commandArgs []string if name := viper.GetString("container_name"); name == "" { @@ -56,6 +57,5 @@ func init() { viper.BindPFlag("log_group", runCmd.PersistentFlags().Lookup("log_group")) viper.BindPFlag("container_name", runCmd.PersistentFlags().Lookup("container_name")) //viper.BindPFlag("task_definition", runCmd.PersistentFlags().Lookup("task_definition")) - viper.SetDefault("run.launch_type", "EC2") fmt.Println("Default launch_type set to:", viper.GetString("run.launch_type")) } diff --git a/cmd/runFargate.go b/cmd/runFargate.go index 4cfc090..b47fd18 100644 --- a/cmd/runFargate.go +++ b/cmd/runFargate.go @@ -17,6 +17,8 @@ var runFargateCmd = &cobra.Command{ This command is specifically tailored for future Fargate-specific functionality but currently duplicates the 'run' command.`, Args: cobra.MinimumNArgs(1), Run: func(cmd *cobra.Command, args []string) { + viper.SetDefault("run.launch_type", "FARGATE") + viper.SetDefault("run.security_group_filter", "ec2") var containerName string var commandArgs []string if name := viper.GetString("container_name"); name == "" { @@ -50,8 +52,4 @@ This command is specifically tailored for future Fargate-specific functionality func init() { rootCmd.AddCommand(runFargateCmd) - viper.SetDefault("run.security_group_filter", "*ec2*") - viper.SetDefault("run.launch_type", "FARGATE") - - } diff --git a/lib/exec.go b/lib/exec.go index b5f8238..05edafb 100644 --- a/lib/exec.go +++ b/lib/exec.go @@ -2,14 +2,33 @@ package lib import ( "fmt" + "github.com/apex/log" "github.com/aws/aws-sdk-go/aws" - //"github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/ecs" "github.com/aws/aws-sdk-go/service/ecs/ecsiface" ) -// FindLatestTaskArn finds the latest task ARN for the specified service within a cluster -func FindLatestTaskArn(svc ecsiface.ECSAPI, clusterName, serviceName string) (string, error) { +// InitializeSession creates an AWS session for ECS interaction +func InitializeSession(region, profile string) (*session.Session, error) { + sess, err := session.NewSessionWithOptions(session.Options{ + Profile: profile, + Config: aws.Config{ + }, + }) + if err != nil { + log.WithError(err).Error("Failed to create AWS session") + return nil, err + } + return sess, nil +} + +// FindLatestTaskArn locates the most recent task ARN for a specified ECS service +func FindLatestTaskArn(clusterName, serviceName string) (string, error) { + if serviceName == "" { + return "", fmt.Errorf("service name cannot be empty") + } + input := &ecs.ListTasksInput{ Cluster: aws.String(clusterName), ServiceName: aws.String(serviceName), @@ -18,20 +37,35 @@ func FindLatestTaskArn(svc ecsiface.ECSAPI, clusterName, serviceName string) (st } result, err := svc.ListTasks(input) - if err != nil || len(result.TaskArns) == 0 { + if err != nil { + log.WithError(err).Error("Error listing tasks") + return "", fmt.Errorf("error listing tasks for service %s on cluster %s: %v", serviceName, clusterName, err) + } + if len(result.TaskArns) == 0 { + log.WithFields(log.Fields{ + "cluster": clusterName, + "service": serviceName, + }).Error("No running tasks found") return "", fmt.Errorf("no running tasks found for service %s on cluster %s", serviceName, clusterName) } + log.WithFields(log.Fields{ + "taskArn": aws.StringValue(result.TaskArns[0]), + }).Info("Found latest task ARN") return aws.StringValue(result.TaskArns[0]), nil } -// ExecuteCommandInContainer executes a specified command in a running container on an ECS Fargate cluster. -func ExecuteCommandInContainer(svc ecsiface.ECSAPI, cluster, serviceName, containerName, command string) error { - taskArn, err := FindLatestTaskArn(svc, cluster, serviceName) +// ExecuteCommandInContainer runs a command in a specified container on an ECS Fargate service +func ExecFargate(rofile, cluster, service, container, workDir, containerName, awslogGroup, launchType string, command string) (exitCode int, err error) { + if service == "" { + return fmt.Errorf("service name cannot be empty") + } + + taskArn, err := FindLatestTaskArn(cluster, serviceName) if err != nil { return err } - + fmt.Println(taskArn , "we are here") input := &ecs.ExecuteCommandInput{ Cluster: aws.String(cluster), Task: aws.String(taskArn), @@ -42,8 +76,18 @@ func ExecuteCommandInContainer(svc ecsiface.ECSAPI, cluster, serviceName, contai _, err = svc.ExecuteCommand(input) if err != nil { + log.WithError(err).WithFields(log.Fields{ + "taskArn": taskArn, + "containerName": containerName, + "command": command, + }).Error("Failed to execute command") return fmt.Errorf("failed to execute command: %v", err) } + log.WithFields(log.Fields{ + "taskArn": taskArn, + "containerName": containerName, + "command": command, + }).Info("Command executed successfully") return nil } diff --git a/lib/runFargate.go b/lib/runFargate.go index babd767..d3264f4 100644 --- a/lib/runFargate.go +++ b/lib/runFargate.go @@ -17,6 +17,7 @@ func RunFargate(profile, cluster, service, taskDefinitionName, imageTag string, } ctx := log.WithFields(log.Fields{"task_definition": taskDefinitionName}) + svc := ecs.New(localSession) svcEC2 := ec2.New(localSession) // Assuming makeSession initializes localSession @@ -26,13 +27,11 @@ func RunFargate(profile, cluster, service, taskDefinitionName, imageTag string, log.WithError(err).Error("Failed to fetch subnets by tag") return 1, err } - securityGroups, err := fetchSecurityGroupsByName(svcEC2, securityGroupFilter) - if err != nil { + if err != nil { log.WithError(err).Error("Failed to fetch security groups by name") return 1, err } - // Set up network configuration networkConfiguration := &ecs.NetworkConfiguration{ AwsvpcConfiguration: &ecs.AwsVpcConfiguration{ @@ -213,50 +212,3 @@ for _, task := range tasksOutput.Tasks { -// fetchSubnetsByTag fetches subnet IDs by a specific tag name and value -func fetchSubnetsByTag(svc *ec2.EC2, tagKey, tagValue string) ([]*string, error) { - input := &ec2.DescribeSubnetsInput{ - Filters: []*ec2.Filter{ - { - Name: aws.String(fmt.Sprintf("tag:%s", tagKey)), - Values: []*string{aws.String(tagValue)}, - }, - }, - } - - result, err := svc.DescribeSubnets(input) - if err != nil { - return nil, fmt.Errorf("error describing subnets: %w", err) - } - - var subnets []*string - for _, subnet := range result.Subnets { - subnets = append(subnets, subnet.SubnetId) - } - - return subnets, nil -} - - -func fetchSecurityGroupsByName(svc *ec2.EC2, securityGroupFilter string) ([]*string, error) { - input := &ec2.DescribeSecurityGroupsInput{ - Filters: []*ec2.Filter{ - { - Name: aws.String("group-name"), - Values: []*string{aws.String(securityGroupFilter)}, - }, - }, - } - - result, err := svc.DescribeSecurityGroups(input) - if err != nil { - return nil, fmt.Errorf("error describing security groups: %w", err) - } - - var securityGroups []*string - for _, sg := range result.SecurityGroups { - securityGroups = append(securityGroups, sg.GroupId) - } - - return securityGroups, nil -} diff --git a/lib/util.go b/lib/util.go index 032d468..ad6a292 100644 --- a/lib/util.go +++ b/lib/util.go @@ -10,6 +10,7 @@ import ( "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/cloudwatchlogs" "github.com/aws/aws-sdk-go/service/ecs" + "github.com/aws/aws-sdk-go/service/ec2" ) var localSession *session.Session @@ -133,3 +134,50 @@ func modifyContainerDefinitionImages(imageTag string, imageTags []string, workDi } return nil } + +// fetchSubnetsByTag fetches subnet IDs by a specific tag name and value +func fetchSubnetsByTag(svc *ec2.EC2, tagKey, tagValue string) ([]*string, error) { + input := &ec2.DescribeSubnetsInput{ + Filters: []*ec2.Filter{ + { + Name: aws.String(fmt.Sprintf("tag:%s", tagKey)), + Values: []*string{aws.String(tagValue)}, + }, + }, + } + + result, err := svc.DescribeSubnets(input) + if err != nil { + return nil, fmt.Errorf("error describing subnets: %w", err) + } + + var subnets []*string + for _, subnet := range result.Subnets { + subnets = append(subnets, subnet.SubnetId) + } + + return subnets, nil +} + + +func fetchSecurityGroupsByName(svc *ec2.EC2, securityGroupFilter string) ([]*string, error) { + // Describe all security groups + input := &ec2.DescribeSecurityGroupsInput{} + + result, err := svc.DescribeSecurityGroups(input) + if err != nil { + return nil, fmt.Errorf("error describing security groups: %w", err) + } + + var securityGroups []*string + // Loop through the security groups and add those that contain the filter in their name + for _, sg := range result.SecurityGroups { + if strings.Contains(*sg.GroupName, securityGroupFilter) { + securityGroups = append(securityGroups, sg.GroupId) + } + } + + // Return the filtered list of security group IDs + return securityGroups, nil +} + From 02f844adbe6121de2baf47a3a2b360503098787c Mon Sep 17 00:00:00 2001 From: Andrei Vsiakikh Date: Mon, 22 Apr 2024 15:25:20 +1200 Subject: [PATCH 04/10] exec command --- cmd/exec.go | 16 +++--- go.mod | 48 ++++++++++++++++-- go.sum | 141 ++++++++++++++++++++++++++++++++++++++++++++++++++-- lib/exec.go | 120 +++++++++++++++++++------------------------- 4 files changed, 239 insertions(+), 86 deletions(-) diff --git a/cmd/exec.go b/cmd/exec.go index 78eaba3..6e10363 100644 --- a/cmd/exec.go +++ b/cmd/exec.go @@ -1,7 +1,8 @@ package cmd import ( - "os" + //"os" + "strings" "github.com/apex/log" "github.com/spf13/cobra" @@ -27,21 +28,20 @@ var execCmd = &cobra.Command{ commandArgs = args } - exitCode, err := lib.ExecFargate( + // Join the commandArgs to form a single command string + commandString := strings.Join(commandArgs, " ") + + err := lib.ExecFargate( viper.GetString("profile"), viper.GetString("cluster"), viper.GetString("run.service"), - viper.GetString("workdir"), containerName, - viper.GetString("log_group"), - viper.GetString("run.launch_type"), - viper.GetString("run.security_group_filter"), - commandArgs, + commandString, // Pass the combined command string ) if err != nil { log.WithError(err).Error("Can't run task in Fargate mode") } - os.Exit(exitCode) + //os.Exit(exitCode) }, } diff --git a/go.mod b/go.mod index 04198dc..5e0f355 100644 --- a/go.mod +++ b/go.mod @@ -6,31 +6,69 @@ require ( github.com/Shopify/ejson v1.2.1 github.com/apex/log v1.0.0 github.com/aws/aws-sdk-go v1.43.24 + github.com/gorilla/websocket v1.5.1 github.com/imdario/mergo v0.3.11 github.com/spf13/cobra v0.0.3 github.com/spf13/viper v1.0.2 - golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 + golang.org/x/crypto v0.14.0 ) require ( github.com/BurntSushi/toml v0.3.1 // indirect + github.com/Songmu/flextime v0.1.0 // indirect + github.com/Songmu/prompter v0.5.1 // indirect + github.com/alecthomas/kong v0.7.0 // indirect + github.com/aws/aws-sdk-go-v2 v1.26.1 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.4 // indirect + github.com/aws/aws-sdk-go-v2/config v1.26.3 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.16.14 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2 // indirect + github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.31.0 // indirect + github.com/aws/aws-sdk-go-v2/service/ecs v1.41.7 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10 // indirect + github.com/aws/aws-sdk-go-v2/service/sns v1.26.7 // indirect + github.com/aws/aws-sdk-go-v2/service/ssm v1.44.7 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.18.6 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.6 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 // indirect + github.com/aws/smithy-go v1.20.2 // indirect + github.com/creack/pty v1.1.20 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dustin/gojson v0.0.0-20160307161227-2e71ec9dd5ad // indirect github.com/fsnotify/fsnotify v1.4.7 // indirect + github.com/fujiwara/ecsta v0.4.5 // indirect + github.com/fujiwara/tracer v1.0.2 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect + github.com/itchyny/gojq v0.12.11 // indirect + github.com/itchyny/timefmt-go v0.1.5 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/magiconair/properties v1.8.0 // indirect + github.com/mattn/go-isatty v0.0.16 // indirect + github.com/mattn/go-runewidth v0.0.14 // indirect github.com/mitchellh/mapstructure v0.0.0-20180511142126-bb74f1db0675 // indirect + github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/pelletier/go-toml v1.2.0 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/rivo/uniseg v0.3.4 // indirect + github.com/samber/lo v1.36.0 // indirect github.com/smartystreets/goconvey v1.6.4 // indirect github.com/spf13/afero v1.1.1 // indirect github.com/spf13/cast v1.2.0 // indirect github.com/spf13/jwalterweatherman v0.0.0-20180109140146-7c0cea34c8ec // indirect github.com/spf13/pflag v1.0.1 // indirect - github.com/stretchr/testify v1.5.1 // indirect - golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect - golang.org/x/text v0.3.7 // indirect - gopkg.in/yaml.v2 v2.3.0 // indirect + github.com/stretchr/testify v1.8.0 // indirect + github.com/tkuchiki/go-timezone v0.2.2 // indirect + github.com/tkuchiki/parsetime v0.3.0 // indirect + golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 // indirect + golang.org/x/net v0.17.0 // indirect + golang.org/x/sys v0.13.0 // indirect + golang.org/x/term v0.13.0 // indirect + golang.org/x/text v0.13.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/go.sum b/go.sum index 0dea81f..2c2ef2c 100644 --- a/go.sum +++ b/go.sum @@ -2,10 +2,55 @@ github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Shopify/ejson v1.2.1 h1:Dx0Ipn0mUgrZlzIa5oIUrH0rdSmBOyod/UJmQQK1KHo= github.com/Shopify/ejson v1.2.1/go.mod h1:J8cw5GOA0l/aMOPp+uDfwNYVbeqIaBhzRkv1+76UCvk= +github.com/Songmu/flextime v0.1.0 h1:sss5IALl84LbvU/cS5D1cKNd5ffT94N2BZwC+esgAJI= +github.com/Songmu/flextime v0.1.0/go.mod h1:ofUSZ/qj7f1BfQQ6rEH4ovewJ0SZmLOjBF1xa8iE87Q= +github.com/Songmu/prompter v0.5.1 h1:IAsttKsOZWSDw7bV1mtGn9TAmLFAjXbp9I/eYmUUogo= +github.com/Songmu/prompter v0.5.1/go.mod h1:CS3jEPD6h9IaLaG6afrl1orTgII9+uDWuw95dr6xHSw= +github.com/alecthomas/kong v0.7.0 h1:YIjJUiR7AcmHxL87UlbPn0gyIGwl4+nYND0OQ4ojP7k= +github.com/alecthomas/kong v0.7.0/go.mod h1:n1iCIO2xS46oE8ZfYCNDqdR0b0wZNrXAIAqro/2132U= github.com/apex/log v1.0.0 h1:5UWeZC54mWVtOGSCjtuvDPgY/o0QxmjQgvYZ27pLVGQ= github.com/apex/log v1.0.0/go.mod h1:yA770aXIDQrhVOIGurT/pVdfCpSq1GQV/auzMN5fzvY= github.com/aws/aws-sdk-go v1.43.24 h1:7c2PniJ0wpmWsIA6OtYBw6wS7DF0IjbhvPq+0ZQYNXw= github.com/aws/aws-sdk-go v1.43.24/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= +github.com/aws/aws-sdk-go-v2 v1.26.1 h1:5554eUqIYVWpU0YmeeYZ0wU64H2VLBs8TlhRB2L+EkA= +github.com/aws/aws-sdk-go-v2 v1.26.1/go.mod h1:ffIFB97e2yNsv4aTSGkqtHnppsIJzw7G7BReUZ3jCXM= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.4 h1:OCs21ST2LrepDfD3lwlQiOqIGp6JiEUqG84GzTDoyJs= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.4/go.mod h1:usURWEKSNNAcAZuzRn/9ZYPT8aZQkR7xcCtunK/LkJo= +github.com/aws/aws-sdk-go-v2/config v1.26.3 h1:dKuc2jdp10y13dEEvPqWxqLoc0vF3Z9FC45MvuQSxOA= +github.com/aws/aws-sdk-go-v2/config v1.26.3/go.mod h1:Bxgi+DeeswYofcYO0XyGClwlrq3DZEXli0kLf4hkGA0= +github.com/aws/aws-sdk-go-v2/credentials v1.16.14 h1:mMDTwwYO9A0/JbOCOG7EOZHtYM+o7OfGWfu0toa23VE= +github.com/aws/aws-sdk-go-v2/credentials v1.16.14/go.mod h1:cniAUh3ErQPHtCQGPT5ouvSAQ0od8caTO9OOuufZOAE= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11 h1:c5I5iH+DZcH3xOIMlz3/tCKJDaHFwYEmxvlh2fAcFo8= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.11/go.mod h1:cRrYDYAMUohBJUtUnOhydaMHtiK/1NZ0Otc9lIb6O0Y= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5 h1:aw39xVGeRWlWx9EzGVnhOR4yOjQDHPQ6o6NmBlscyQg= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5/go.mod h1:FSaRudD0dXiMPK2UjknVwwTYyZMRsHv3TtkabsZih5I= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5 h1:PG1F3OD1szkuQPzDw3CIQsRIrtTlUC3lP84taWzHlq0= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5/go.mod h1:jU1li6RFryMz+so64PpKtudI+QzbKoIEivqdf6LNpOc= +github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2 h1:GrSw8s0Gs/5zZ0SX+gX4zQjRnRsMJDJ2sLur1gRBhEM= +github.com/aws/aws-sdk-go-v2/internal/ini v1.7.2/go.mod h1:6fQQgfuGmw8Al/3M2IgIllycxV7ZW7WCdVSqfBeUiCY= +github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.31.0 h1:Rk+Ft0Mu/eiNt2iJ2oS8Gf1h5m6q5crwS8cmlTylnvM= +github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.31.0/go.mod h1:jZNaJEtn9TLi3pfxycLz79HVkKxP8ZdYm92iaNFgBsA= +github.com/aws/aws-sdk-go-v2/service/ecs v1.41.7 h1:aFdgmJ8G385PVC9mp8b9roGGHU/XbrKEQTbzl6V0GbE= +github.com/aws/aws-sdk-go-v2/service/ecs v1.41.7/go.mod h1:rcFIIrVk3NGCT3BV84HQM3ut+Dr1PO71UvvT8GeLAv4= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4 h1:/b31bi3YVNlkzkBrm9LfpaKoaYZUxIAj4sHfOTmLfqw= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.4/go.mod h1:2aGXHFmbInwgP9ZfpmdIfOELL79zhdNYNmReK8qDfdQ= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10 h1:DBYTXwIGQSGs9w4jKm60F5dmCQ3EEruxdc0MFh+3EY4= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.10/go.mod h1:wohMUQiFdzo0NtxbBg0mSRGZ4vL3n0dKjLTINdcIino= +github.com/aws/aws-sdk-go-v2/service/sns v1.26.7 h1:DylmW2c1Z7qGxN3Y02k+voPbtM1mh7Rp+gV+7maG5io= +github.com/aws/aws-sdk-go-v2/service/sns v1.26.7/go.mod h1:mLFiISZfiZAqZEfPWUsZBK8gD4dYCKuKAfapV+KrIVQ= +github.com/aws/aws-sdk-go-v2/service/ssm v1.44.7 h1:a8HvP/+ew3tKwSXqL3BCSjiuicr+XTU2eFYeogV9GJE= +github.com/aws/aws-sdk-go-v2/service/ssm v1.44.7/go.mod h1:Q7XIWsMo0JcMpI/6TGD6XXcXcV1DbTj6e9BKNntIMIM= +github.com/aws/aws-sdk-go-v2/service/sso v1.18.6 h1:dGrs+Q/WzhsiUKh82SfTVN66QzyulXuMDTV/G8ZxOac= +github.com/aws/aws-sdk-go-v2/service/sso v1.18.6/go.mod h1:+mJNDdF+qiUlNKNC3fxn74WWNN+sOiGOEImje+3ScPM= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.6 h1:Yf2MIo9x+0tyv76GljxzqA3WtC5mw7NmazD2chwjxE4= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.21.6/go.mod h1:ykf3COxYI0UJmxcfcxcVuz7b6uADi1FkiUz6Eb7AgM8= +github.com/aws/aws-sdk-go-v2/service/sts v1.26.7 h1:NzO4Vrau795RkUdSHKEwiR01FaGzGOH1EETJ+5QHnm0= +github.com/aws/aws-sdk-go-v2/service/sts v1.26.7/go.mod h1:6h2YuIoxaMSCFf5fi1EgZAwdfkGMgDY+DVfa61uLe4U= +github.com/aws/smithy-go v1.20.2 h1:tbp628ireGtzcHDDmLT/6ADHidqnwgF57XOXZe6tp4Q= +github.com/aws/smithy-go v1.20.2/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E= +github.com/crackcomm/go-clitable v0.0.0-20151121230230-53bcff2fea36/go.mod h1:XiV36mPegOHv+dlkCSCazuGdQR2BUTgIZ2FKqTTHles= +github.com/creack/pty v1.1.20 h1:VIPb/a2s17qNeQgDnkfZC35RScx+blkKF8GV68n80J4= +github.com/creack/pty v1.1.20/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -13,14 +58,26 @@ github.com/dustin/gojson v0.0.0-20160307161227-2e71ec9dd5ad h1:Qk76DOWdOp+GlyDKB github.com/dustin/gojson v0.0.0-20160307161227-2e71ec9dd5ad/go.mod h1:mPKfmRa823oBIgl2r20LeMSpTAteW5j7FLkc0vjmzyQ= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fujiwara/ecsta v0.4.5 h1:82M3oL6n+eNd3JgPiFOOKkIYq46ggnts2HbuHOtX3rI= +github.com/fujiwara/ecsta v0.4.5/go.mod h1:SwSlCJuhiVrWfpYSaaHUSZVrs6Ey6nJ9V3flWNX7ndk= +github.com/fujiwara/tracer v1.0.2 h1:ztstnson+QwOpO69Jir4nkUKlYgse3vJ28FO2eOUPk0= +github.com/fujiwara/tracer v1.0.2/go.mod h1:r2QzBEBNsW9OhmoVdmTANG+GEmxWNZk7317/mnW2yIw= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= +github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce h1:xdsDDbiBDQTKASoGEZ+pEmF1OnWuu8AQ9I8iNbHNeno= github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce/go.mod h1:oZtUIOe8dh44I2q6ScRibXws4Ajl+d+nod3AaR9vL5w= github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA= github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/itchyny/gojq v0.12.11 h1:YhLueoHhHiN4mkfM+3AyJV6EPcCxKZsOnYf+aVSwaQw= +github.com/itchyny/gojq v0.12.11/go.mod h1:o3FT8Gkbg/geT4pLI0tF3hvip5F3Y/uskjRz9OYa38g= +github.com/itchyny/timefmt-go v0.1.5 h1:G0INE2la8S6ru/ZI5JecgyzbbJNs5lG1RcBqa7Jm6GE= +github.com/itchyny/timefmt-go v0.1.5/go.mod h1:nEP7L+2YmAbT2kZ2HfSs1d8Xtw9LY8D2stDBckWakZ8= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= @@ -29,14 +86,28 @@ github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= +github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/goveralls v0.0.9/go.mod h1:FRbM1PS8oVsOe9JtdzAAXM+DsvDMMHcM1C7drGJD8HY= github.com/mitchellh/mapstructure v0.0.0-20180511142126-bb74f1db0675 h1:/rdJjIiKG5rRdwG5yxHmSE/7ZREjpyC0kL7GxGT/qJw= github.com/mitchellh/mapstructure v0.0.0-20180511142126-bb74f1db0675/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.3.4 h1:3Z3Eu6FGHZWSfNKJTOUiPatWwfc7DzJRU04jFUqJODw= +github.com/rivo/uniseg v0.3.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/samber/lo v1.36.0 h1:4LaOxH1mHnbDGhTVE0i1z8v/lWaQW8AIfOD3HU4mSaw= +github.com/samber/lo v1.36.0/go.mod h1:HLeWcJRRyLKp3+/XBJvOrerCQn9mhdKMHyd7IRlgeQ8= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= @@ -54,28 +125,90 @@ github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn github.com/spf13/viper v1.0.2 h1:Ncr3ZIuJn322w2k1qmzXDnkLAdQMlJqBa9kfAH+irso= github.com/spf13/viper v1.0.2/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/tkuchiki/go-timezone v0.2.2 h1:MdHR65KwgVTwWFQrota4SKzc4L5EfuH5SdZZGtk/P2Q= +github.com/tkuchiki/go-timezone v0.2.2/go.mod h1:oFweWxYl35C/s7HMVZXiA19Jr9Y0qJHMaG/J2TES4LY= +github.com/tkuchiki/parsetime v0.3.0 h1:cvblFQlPeAPJL8g6MgIGCHnnmHSZvluuY+hexoZCNqc= +github.com/tkuchiki/parsetime v0.3.0/go.mod h1:OJkQmIrf5Ao7R+WYIdITPOfDVj8LmnHGCfQ8DTs3LCA= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc= -golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 h1:3MTrJm4PyNL9NBqvYDSj3DHl46qQakyfqfWo4jgfaEM= +golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/lib/exec.go b/lib/exec.go index 05edafb..7d928c1 100644 --- a/lib/exec.go +++ b/lib/exec.go @@ -1,93 +1,75 @@ package lib import ( + "context" "fmt" "github.com/apex/log" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/session" - "github.com/aws/aws-sdk-go/service/ecs" - "github.com/aws/aws-sdk-go/service/ecs/ecsiface" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/service/ecs" + "github.com/fujiwara/ecsta" ) -// InitializeSession creates an AWS session for ECS interaction -func InitializeSession(region, profile string) (*session.Session, error) { - sess, err := session.NewSessionWithOptions(session.Options{ - Profile: profile, - Config: aws.Config{ - }, - }) - if err != nil { - log.WithError(err).Error("Failed to create AWS session") - return nil, err +var sessionInstance *ecs.Client +var sessionConfig aws.Config // Variable for session configuration + +// InitAWS initializes a new AWS session with the specified profile +func InitAWS(profile string) error { + if sessionInstance == nil { + cfg, err := config.LoadDefaultConfig(context.TODO(), + config.WithSharedConfigProfile(profile), + ) + if err != nil { + return fmt.Errorf("failed to load configuration: %w", err) + } + sessionInstance = ecs.NewFromConfig(cfg) + sessionConfig = cfg // Save session configuration } - return sess, nil + return nil } -// FindLatestTaskArn locates the most recent task ARN for a specified ECS service -func FindLatestTaskArn(clusterName, serviceName string) (string, error) { - if serviceName == "" { - return "", fmt.Errorf("service name cannot be empty") +// ExecFargate executes a command in a specified container on an ECS Fargate service +func ExecFargate(profile, cluster, service, containerName, command string) error { + if err := InitAWS(profile); err != nil { + return fmt.Errorf("failed to initialize AWS session: %w", err) } - input := &ecs.ListTasksInput{ - Cluster: aws.String(clusterName), - ServiceName: aws.String(serviceName), - DesiredStatus: aws.String("RUNNING"), - MaxResults: aws.Int64(1), + region := sessionConfig.Region // Use the saved region from session configuration + ecstaApp, err := ecsta.New(context.TODO(), region, cluster) + if err != nil { + return fmt.Errorf("failed to create ecsta application: %w", err) } + service = "app" - result, err := svc.ListTasks(input) - if err != nil { - log.WithError(err).Error("Error listing tasks") - return "", fmt.Errorf("error listing tasks for service %s on cluster %s: %v", serviceName, clusterName, err) + entrypoint := "/usr/bin/ssm-parent" + configPath := "/app/.ssm-parent.yaml" + fullCommand := fmt.Sprintf("%s -c %s run -- %s", entrypoint, configPath, command) + execOpt := ecsta.ExecOption{ + Service: aws.String(service), + Container: containerName, + Command: fullCommand, } - if len(result.TaskArns) == 0 { - log.WithFields(log.Fields{ - "cluster": clusterName, - "service": serviceName, - }).Error("No running tasks found") - return "", fmt.Errorf("no running tasks found for service %s on cluster %s", serviceName, clusterName) + + if err := ecstaApp.RunExec(context.Background(), &execOpt); err != nil { + return fmt.Errorf("failed to execute command: %w", err) } - log.WithFields(log.Fields{ - "taskArn": aws.StringValue(result.TaskArns[0]), - }).Info("Found latest task ARN") - return aws.StringValue(result.TaskArns[0]), nil + log.Info("Command executed successfully") + return nil } -// ExecuteCommandInContainer runs a command in a specified container on an ECS Fargate service -func ExecFargate(rofile, cluster, service, container, workDir, containerName, awslogGroup, launchType string, command string) (exitCode int, err error) { - if service == "" { - return fmt.Errorf("service name cannot be empty") - } - - taskArn, err := FindLatestTaskArn(cluster, serviceName) +// FindLatestTaskArn locates the most recent task ARN for a specified ECS service +func FindLatestTaskArn(client *ecs.Client, clusterName, serviceName string) (string, error) { + resp, err := client.ListTasks(context.TODO(), &ecs.ListTasksInput{ + Cluster: aws.String(clusterName), + ServiceName: aws.String(serviceName), + }) if err != nil { - return err + return "", fmt.Errorf("error listing tasks: %w", err) } - fmt.Println(taskArn , "we are here") - input := &ecs.ExecuteCommandInput{ - Cluster: aws.String(cluster), - Task: aws.String(taskArn), - Container: aws.String(containerName), - Interactive: aws.Bool(true), - Command: aws.String(command), + if len(resp.TaskArns) == 0 { + return "", fmt.Errorf("no tasks found for service %s on cluster %s", serviceName, clusterName) } - _, err = svc.ExecuteCommand(input) - if err != nil { - log.WithError(err).WithFields(log.Fields{ - "taskArn": taskArn, - "containerName": containerName, - "command": command, - }).Error("Failed to execute command") - return fmt.Errorf("failed to execute command: %v", err) - } - - log.WithFields(log.Fields{ - "taskArn": taskArn, - "containerName": containerName, - "command": command, - }).Info("Command executed successfully") - return nil + return resp.TaskArns[0], nil } From b71fd012d20a7a11f0edcd72d3908d5fe6cba733 Mon Sep 17 00:00:00 2001 From: Andrei Vsiakikh Date: Mon, 22 Apr 2024 16:07:51 +1200 Subject: [PATCH 05/10] exec command --- cmd/exec.go | 1 - lib/exec.go | 20 ++------------------ 2 files changed, 2 insertions(+), 19 deletions(-) diff --git a/cmd/exec.go b/cmd/exec.go index 6e10363..63dd4ec 100644 --- a/cmd/exec.go +++ b/cmd/exec.go @@ -41,7 +41,6 @@ var execCmd = &cobra.Command{ if err != nil { log.WithError(err).Error("Can't run task in Fargate mode") } - //os.Exit(exitCode) }, } diff --git a/lib/exec.go b/lib/exec.go index 7d928c1..60bfb38 100644 --- a/lib/exec.go +++ b/lib/exec.go @@ -13,7 +13,7 @@ import ( var sessionInstance *ecs.Client var sessionConfig aws.Config // Variable for session configuration -// InitAWS initializes a new AWS session with the specified profile +// InitAWS initializes a new AWS session with the specified profile for Ecsta realization func InitAWS(profile string) error { if sessionInstance == nil { cfg, err := config.LoadDefaultConfig(context.TODO(), @@ -45,11 +45,9 @@ func ExecFargate(profile, cluster, service, containerName, command string) error configPath := "/app/.ssm-parent.yaml" fullCommand := fmt.Sprintf("%s -c %s run -- %s", entrypoint, configPath, command) execOpt := ecsta.ExecOption{ - Service: aws.String(service), - Container: containerName, Command: fullCommand, } - + fmt.Println(execOpt) if err := ecstaApp.RunExec(context.Background(), &execOpt); err != nil { return fmt.Errorf("failed to execute command: %w", err) } @@ -58,18 +56,4 @@ func ExecFargate(profile, cluster, service, containerName, command string) error return nil } -// FindLatestTaskArn locates the most recent task ARN for a specified ECS service -func FindLatestTaskArn(client *ecs.Client, clusterName, serviceName string) (string, error) { - resp, err := client.ListTasks(context.TODO(), &ecs.ListTasksInput{ - Cluster: aws.String(clusterName), - ServiceName: aws.String(serviceName), - }) - if err != nil { - return "", fmt.Errorf("error listing tasks: %w", err) - } - if len(resp.TaskArns) == 0 { - return "", fmt.Errorf("no tasks found for service %s on cluster %s", serviceName, clusterName) - } - return resp.TaskArns[0], nil -} From 1a85bf6fda0a11dae6b7533724383b9e36d076d6 Mon Sep 17 00:00:00 2001 From: Andrei Vsiakikh Date: Mon, 22 Apr 2024 19:20:49 +1200 Subject: [PATCH 06/10] exec fixed --- cmd/exec.go | 6 +----- lib/exec.go | 7 ++++--- lib/runFargate.go | 2 +- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/cmd/exec.go b/cmd/exec.go index 63dd4ec..e6496a8 100644 --- a/cmd/exec.go +++ b/cmd/exec.go @@ -18,13 +18,11 @@ var execCmd = &cobra.Command{ Args: cobra.MinimumNArgs(1), Run: func(cmd *cobra.Command, args []string) { viper.SetDefault("run.launch_type", "FARGATE") - var containerName string + //var containerName string var commandArgs []string if name := viper.GetString("container_name"); name == "" { - containerName = args[0] commandArgs = args[1:] } else { - containerName = name commandArgs = args } @@ -34,8 +32,6 @@ var execCmd = &cobra.Command{ err := lib.ExecFargate( viper.GetString("profile"), viper.GetString("cluster"), - viper.GetString("run.service"), - containerName, commandString, // Pass the combined command string ) if err != nil { diff --git a/lib/exec.go b/lib/exec.go index 60bfb38..685da17 100644 --- a/lib/exec.go +++ b/lib/exec.go @@ -8,6 +8,7 @@ import ( "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/service/ecs" "github.com/fujiwara/ecsta" + "os" ) var sessionInstance *ecs.Client @@ -22,6 +23,7 @@ func InitAWS(profile string) error { if err != nil { return fmt.Errorf("failed to load configuration: %w", err) } + os.Setenv("AWS_PROFILE", profile) //required for aws sdk sessionInstance = ecs.NewFromConfig(cfg) sessionConfig = cfg // Save session configuration } @@ -29,7 +31,8 @@ func InitAWS(profile string) error { } // ExecFargate executes a command in a specified container on an ECS Fargate service -func ExecFargate(profile, cluster, service, containerName, command string) error { +func ExecFargate(profile, cluster, command string) error { + if err := InitAWS(profile); err != nil { return fmt.Errorf("failed to initialize AWS session: %w", err) } @@ -39,7 +42,6 @@ func ExecFargate(profile, cluster, service, containerName, command string) error if err != nil { return fmt.Errorf("failed to create ecsta application: %w", err) } - service = "app" entrypoint := "/usr/bin/ssm-parent" configPath := "/app/.ssm-parent.yaml" @@ -47,7 +49,6 @@ func ExecFargate(profile, cluster, service, containerName, command string) error execOpt := ecsta.ExecOption{ Command: fullCommand, } - fmt.Println(execOpt) if err := ecstaApp.RunExec(context.Background(), &execOpt); err != nil { return fmt.Errorf("failed to execute command: %w", err) } diff --git a/lib/runFargate.go b/lib/runFargate.go index d3264f4..8048dfd 100644 --- a/lib/runFargate.go +++ b/lib/runFargate.go @@ -63,7 +63,7 @@ securityGroups, err := fetchSecurityGroupsByName(svcEC2, securityGroupFilter) var foundContainerName bool if err := modifyContainerDefinitionImages(imageTag, imageTags, workDir, taskDefinition.ContainerDefinitions, ctx); err != nil { return 1, err - } + } for n, containerDefinition := range taskDefinition.ContainerDefinitions { if aws.StringValue(containerDefinition.Name) == containerName { foundContainerName = true From c49a42d1c6ff56407d65af188b7278847c4b7609 Mon Sep 17 00:00:00 2001 From: Andrei Vsiakikh Date: Mon, 22 Apr 2024 19:26:56 +1200 Subject: [PATCH 07/10] bump go --- .github/workflows/build_test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build_test.yml b/.github/workflows/build_test.yml index a686d0d..d38a6e8 100644 --- a/.github/workflows/build_test.yml +++ b/.github/workflows/build_test.yml @@ -16,7 +16,7 @@ jobs: name: set up Go uses: actions/setup-go@v1 with: - go-version: 1.15.x + go-version: 1.21.x - name: cache modules uses: actions/cache@v2 From 3db714dad7291111b4a1a9110e37ae1c2ed4e6a7 Mon Sep 17 00:00:00 2001 From: Andrei Vsiakikh Date: Tue, 23 Apr 2024 09:37:14 +1200 Subject: [PATCH 08/10] go version for releaser --- .github/workflows/release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f2f42ad..27f667e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -17,12 +17,12 @@ jobs: name: set up Go uses: actions/setup-go@v1 with: - go-version: 1.17.x + go-version: 1.21.x - name: run GoReleaser uses: goreleaser/goreleaser-action@v1 with: version: latest - args: release --rm-dist + args: release --clean env: GITHUB_TOKEN: ${{ secrets.RELEASE_GITHUB_TOKEN }} From 35b8d6f5bf4bc5b73567f95b60be51a3c3f613fa Mon Sep 17 00:00:00 2001 From: Andrei Vsiakikh Date: Fri, 10 May 2024 11:37:48 +1200 Subject: [PATCH 09/10] public subnets support for Runfargate --- lib/runFargate.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/runFargate.go b/lib/runFargate.go index 8048dfd..bb6687a 100644 --- a/lib/runFargate.go +++ b/lib/runFargate.go @@ -24,9 +24,17 @@ func RunFargate(profile, cluster, service, taskDefinitionName, imageTag string, // Fetch subnets and security groups subnets, err := fetchSubnetsByTag(svcEC2, "Tier", "private") if err != nil { - log.WithError(err).Error("Failed to fetch subnets by tag") + log.WithError(err).Error("Failed to fetch subnets by private tag") return 1, err } + if len(subnets) == 0 { + subnets, err = fetchSubnetsByTag(svcEC2, "Tier", "public") + + if err != nil { + log.WithError(err).Error("Failed to fetch subnets by public tag") + return 1, err + } +} securityGroups, err := fetchSecurityGroupsByName(svcEC2, securityGroupFilter) if err != nil { log.WithError(err).Error("Failed to fetch security groups by name") From f661b9da8156028073d4848eb2f538755da3ce04 Mon Sep 17 00:00:00 2001 From: Andrei Vsiakikh Date: Fri, 10 May 2024 12:06:54 +1200 Subject: [PATCH 10/10] Readme update --- README.md | 21 +++++++++++++++++++++ cmd/runFargate.go | 2 +- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 880db55..f12cddd 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,27 @@ So that `--cluster` can be set by `ECS_CLUSTER` environmental variable, or `--ta Also, `ecs-tool` exit code is the same as the container exit code. +### runFargate + +The runFargate function is a command that is integrated into the ecs-tool utility. This tool simplifies running commands on an AWS ECS (Elastic Container Service) cluster with Fargate. + +That normany use subnet with 'private' 'Tier' tag but if there is zero proivate subnets that will use 'public' + +``` +ecs-tool runFargate -e "preview" -- env +``` + +### EXEC + +ecs-tool exec Executes a specified command in a running container on an ECS Fargate cluster and get the output. +That function use existing container, so it's faster than runFargate +This command also could connect to fargate existing task: + +``` +ecs-tool exec -e "preview" /bin/sh +``` + + ### SSH 'SSH' access availabe to developers using `ecs-tool ssh` diff --git a/cmd/runFargate.go b/cmd/runFargate.go index b47fd18..36676ba 100644 --- a/cmd/runFargate.go +++ b/cmd/runFargate.go @@ -14,7 +14,7 @@ var runFargateCmd = &cobra.Command{ Short: "Runs a command in Fargate mode", Long: `Runs the specified command on an ECS cluster, optionally catching its output. -This command is specifically tailored for future Fargate-specific functionality but currently duplicates the 'run' command.`, +This command is specifically tailored for future Fargate-specific functionality.`, Args: cobra.MinimumNArgs(1), Run: func(cmd *cobra.Command, args []string) { viper.SetDefault("run.launch_type", "FARGATE")