|
| 1 | +package lib |
| 2 | + |
| 3 | +import ( |
| 4 | + "fmt" |
| 5 | + |
| 6 | + "github.com/apex/log" |
| 7 | + "github.com/aws/aws-sdk-go/aws" |
| 8 | + "github.com/aws/aws-sdk-go/service/ecs" |
| 9 | +) |
| 10 | + |
| 11 | +// RunTask runs the specified one-off task in the cluster using the task definition |
| 12 | +func RunFargate(profile, cluster, service, taskDefinitionName, imageTag string, imageTags []string, workDir, containerName, awslogGroup, launchType string, args []string) (exitCode int, err error) { |
| 13 | + err = makeSession(profile) |
| 14 | + if err != nil { |
| 15 | + return 1, err |
| 16 | + } |
| 17 | + ctx := log.WithFields(&log.Fields{"task_definition": taskDefinitionName}) |
| 18 | + |
| 19 | + svc := ecs.New(localSession) |
| 20 | + |
| 21 | + describeResult, err := svc.DescribeTaskDefinition(&ecs.DescribeTaskDefinitionInput{ |
| 22 | + TaskDefinition: aws.String(taskDefinitionName), |
| 23 | + }) |
| 24 | + if err != nil { |
| 25 | + ctx.WithError(err).Error("Can't get task definition") |
| 26 | + return 1, err |
| 27 | + } |
| 28 | + taskDefinition := describeResult.TaskDefinition |
| 29 | + |
| 30 | + var foundContainerName bool |
| 31 | + if err := modifyContainerDefinitionImages(imageTag, imageTags, workDir, taskDefinition.ContainerDefinitions, ctx); err != nil { |
| 32 | + return 1, err |
| 33 | + } |
| 34 | + for n, containerDefinition := range taskDefinition.ContainerDefinitions { |
| 35 | + if aws.StringValue(containerDefinition.Name) == containerName { |
| 36 | + foundContainerName = true |
| 37 | + taskDefinition.ContainerDefinitions[n].Command = aws.StringSlice(args) |
| 38 | + if awslogGroup != "" { |
| 39 | + // modify log output driver to capture output to a predefined CloudWatch log |
| 40 | + taskDefinition.ContainerDefinitions[n].LogConfiguration = &ecs.LogConfiguration{ |
| 41 | + LogDriver: aws.String("awslogs"), |
| 42 | + Options: map[string]*string{ |
| 43 | + "awslogs-region": localSession.Config.Region, |
| 44 | + "awslogs-group": aws.String(awslogGroup), |
| 45 | + "awslogs-stream-prefix": aws.String(cluster), |
| 46 | + }, |
| 47 | + } |
| 48 | + } |
| 49 | + } |
| 50 | + } |
| 51 | + if !foundContainerName { |
| 52 | + err := fmt.Errorf("Can't find container with specified name in the task definition") |
| 53 | + ctx.WithFields(log.Fields{"container_name": containerName}).Error(err.Error()) |
| 54 | + return 1, err |
| 55 | + } |
| 56 | + registerResult, err := svc.RegisterTaskDefinition(&ecs.RegisterTaskDefinitionInput{ |
| 57 | + ContainerDefinitions: taskDefinition.ContainerDefinitions, |
| 58 | + Cpu: taskDefinition.Cpu, |
| 59 | + ExecutionRoleArn: taskDefinition.ExecutionRoleArn, |
| 60 | + Family: taskDefinition.Family, |
| 61 | + Memory: taskDefinition.Memory, |
| 62 | + NetworkMode: taskDefinition.NetworkMode, |
| 63 | + PlacementConstraints: taskDefinition.PlacementConstraints, |
| 64 | + RequiresCompatibilities: taskDefinition.Compatibilities, |
| 65 | + TaskRoleArn: taskDefinition.TaskRoleArn, |
| 66 | + Volumes: taskDefinition.Volumes, |
| 67 | + }) |
| 68 | + if err != nil { |
| 69 | + ctx.WithError(err).Error("Can't register task definition") |
| 70 | + return 1, err |
| 71 | + } |
| 72 | + ctx.WithField( |
| 73 | + "task_definition_arn", |
| 74 | + aws.StringValue(registerResult.TaskDefinition.TaskDefinitionArn), |
| 75 | + ).Debug("Registered the task definition") |
| 76 | + |
| 77 | + // deregister the task definition |
| 78 | + defer func() { |
| 79 | + ctx = ctx.WithFields(log.Fields{"task_definition_arn": aws.StringValue(registerResult.TaskDefinition.TaskDefinitionArn)}) |
| 80 | + ctx.Debug("Deregistered the task definition") |
| 81 | + _, err = svc.DeregisterTaskDefinition(&ecs.DeregisterTaskDefinitionInput{ |
| 82 | + TaskDefinition: registerResult.TaskDefinition.TaskDefinitionArn, |
| 83 | + }) |
| 84 | + if err != nil { |
| 85 | + ctx.WithError(err).Error("Can't deregister task definition") |
| 86 | + } |
| 87 | + }() |
| 88 | + |
| 89 | + runTaskInput := ecs.RunTaskInput{ |
| 90 | + Cluster: aws.String(cluster), |
| 91 | + TaskDefinition: registerResult.TaskDefinition.TaskDefinitionArn, |
| 92 | + Count: aws.Int64(1), |
| 93 | + StartedBy: aws.String("go-deploy"), |
| 94 | + LaunchType: aws.String(launchType), |
| 95 | + } |
| 96 | + |
| 97 | + if service != "" { |
| 98 | + services, err := svc.DescribeServices(&ecs.DescribeServicesInput{ |
| 99 | + Cluster: aws.String(cluster), |
| 100 | + Services: []*string{aws.String(service)}, |
| 101 | + }) |
| 102 | + if err != nil { |
| 103 | + ctx.WithError(err).Error("Can't get service") |
| 104 | + return 1, err |
| 105 | + } |
| 106 | + |
| 107 | + runTaskInput.NetworkConfiguration = services.Services[0].NetworkConfiguration |
| 108 | + } |
| 109 | + |
| 110 | + runResult, err := svc.RunTask(&runTaskInput) |
| 111 | + if err != nil { |
| 112 | + ctx.WithError(err).Error("Can't run specified task") |
| 113 | + return 1, err |
| 114 | + } |
| 115 | + |
| 116 | + // if there are no running/pending tasks, then it failed to start |
| 117 | + if len(runResult.Tasks) == 0 { |
| 118 | + ctx.Error("No tasks could be run. Please check if the ECS cluster has enough resources") |
| 119 | + return 1, err |
| 120 | + } |
| 121 | + // the task should be in PENDING state at this point |
| 122 | + |
| 123 | + ctx.Info("Waiting for the task to finish") |
| 124 | + var tasks []*string |
| 125 | + for _, task := range runResult.Tasks { |
| 126 | + tasks = append(tasks, task.TaskArn) |
| 127 | + ctx.WithField("task_arn", aws.StringValue(task.TaskArn)).Debug("Started task") |
| 128 | + } |
| 129 | + tasksInput := &ecs.DescribeTasksInput{ |
| 130 | + Cluster: aws.String(cluster), |
| 131 | + Tasks: tasks, |
| 132 | + } |
| 133 | + err = svc.WaitUntilTasksStopped(tasksInput) |
| 134 | + if err != nil { |
| 135 | + ctx.WithError(err).Error("The waiter has been finished with an error") |
| 136 | + exitCode = 3 |
| 137 | + } |
| 138 | + tasksOutput, err := svc.DescribeTasks(tasksInput) |
| 139 | + if err != nil { |
| 140 | + ctx.WithError(err).Error("Can't describe stopped tasks") |
| 141 | + return 1, err |
| 142 | + } |
| 143 | + for _, task := range tasksOutput.Tasks { |
| 144 | + for _, container := range task.Containers { |
| 145 | + ctx := log.WithFields(log.Fields{ |
| 146 | + "container_name": aws.StringValue(container.Name), |
| 147 | + }) |
| 148 | + reason := aws.StringValue(container.Reason) |
| 149 | + if len(reason) != 0 { |
| 150 | + exitCode = 11 |
| 151 | + ctx = ctx.WithField("reason", reason) |
| 152 | + } else { |
| 153 | + ctx = ctx.WithField("exit_code", aws.Int64Value(container.ExitCode)) |
| 154 | + |
| 155 | + } |
| 156 | + if aws.Int64Value(container.ExitCode) == 0 && len(reason) == 0 { |
| 157 | + ctx.Info("Container exited") |
| 158 | + } else { |
| 159 | + ctx.Error("Container exited") |
| 160 | + } |
| 161 | + if aws.StringValue(container.Name) == containerName { |
| 162 | + if len(reason) == 0 { |
| 163 | + exitCode = int(aws.Int64Value(container.ExitCode)) |
| 164 | + if awslogGroup != "" { |
| 165 | + // get log output |
| 166 | + taskUUID, err := parseTaskUUID(container.TaskArn) |
| 167 | + if err != nil { |
| 168 | + log.WithFields(log.Fields{"task_arn": aws.StringValue(container.TaskArn)}).WithError(err).Error("Can't parse task uuid") |
| 169 | + exitCode = 10 |
| 170 | + continue |
| 171 | + } |
| 172 | + err = fetchCloudWatchLog(cluster, containerName, awslogGroup, taskUUID, false, ctx) |
| 173 | + if err != nil { |
| 174 | + log.WithError(err).Error("Can't fetch the logs") |
| 175 | + exitCode = 10 |
| 176 | + } |
| 177 | + } |
| 178 | + } |
| 179 | + } |
| 180 | + } |
| 181 | + } |
| 182 | + |
| 183 | + return |
| 184 | + |
| 185 | +} |
0 commit comments