Skip to content

Commit f182856

Browse files
committed
fargate support
1 parent 56dc0a0 commit f182856

File tree

2 files changed

+245
-0
lines changed

2 files changed

+245
-0
lines changed

cmd/runFargate.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package cmd
2+
3+
import (
4+
"os"
5+
6+
"github.com/apex/log"
7+
"github.com/spf13/cobra"
8+
"github.com/spf13/viper"
9+
"github.com/springload/ecs-tool/lib"
10+
)
11+
12+
var runCmd = &cobra.Command{
13+
Use: "runFargate",
14+
Short: "Runs a command",
15+
Long: `Runs the specified command on an ECS cluster, optionally catching its output.
16+
17+
It can modify the container command.
18+
`,
19+
Args: cobra.MinimumNArgs(1),
20+
Run: func(cmd *cobra.Command, args []string) {
21+
var containerName string
22+
var commandArgs []string
23+
if name := viper.GetString("container_name"); name == "" {
24+
containerName = args[0]
25+
commandArgs = args[1:]
26+
} else {
27+
containerName = name
28+
commandArgs = args
29+
}
30+
31+
exitCode, err := lib.RunTask(
32+
viper.GetString("profile"),
33+
viper.GetString("cluster"),
34+
viper.GetString("run.service"),
35+
viper.GetString("task_definition"),
36+
viper.GetString("image_tag"),
37+
viper.GetStringSlice("image_tags"),
38+
viper.GetString("workdir"),
39+
containerName,
40+
viper.GetString("log_group"),
41+
viper.GetString("run.launch_type"),
42+
commandArgs,
43+
)
44+
if err != nil {
45+
log.WithError(err).Error("Can't run task")
46+
}
47+
os.Exit(exitCode)
48+
},
49+
}
50+
51+
func init() {
52+
rootCmd.AddCommand(runCmd)
53+
runCmd.PersistentFlags().StringP("log_group", "l", "", "Name of the log group to get output")
54+
runCmd.PersistentFlags().StringP("container_name", "", "", "Name of the container to modify parameters for")
55+
runCmd.PersistentFlags().StringP("task_definition", "t", "", "name of task definition to use (required)")
56+
viper.BindPFlag("log_group", runCmd.PersistentFlags().Lookup("log_group"))
57+
viper.BindPFlag("container_name", runCmd.PersistentFlags().Lookup("container_name"))
58+
viper.BindPFlag("task_definition", runCmd.PersistentFlags().Lookup("task_definition"))
59+
viper.SetDefault("run.launch_type", "EC2")
60+
}

lib/runFargate.go

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
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

Comments
 (0)