1package awsat
2
3import (
4	"errors"
5	"testing"
6
7	"github.com/aws/aws-sdk-go/aws/awserr"
8	"github.com/aws/aws-sdk-go/service/ecs"
9)
10
11func TestContainerTask(t *testing.T) {
12	t.Run("start", func(t *testing.T) {
13		t.Run("service", func(t *testing.T) {
14			Template("start containertask name=my-new-service cluster=my-cluster-name desired-count=3 type=service "+
15				"role=arn:of:container:role deployment-name=prod loadbalancer.container-name=redis loadbalancer.container-port=6379 "+
16				"loadbalancer.targetgroup=arn:of:my:targetgroup").
17				Mock(&ecsMock{
18					CreateServiceFunc: func(param0 *ecs.CreateServiceInput) (*ecs.CreateServiceOutput, error) {
19						return &ecs.CreateServiceOutput{
20							Service: &ecs.Service{ServiceArn: String("arn:of:my:new:service")},
21						}, nil
22					},
23				}).ExpectInput("CreateService", &ecs.CreateServiceInput{
24				TaskDefinition: String("my-new-service"),
25				Cluster:        String("my-cluster-name"),
26				DesiredCount:   Int64(3),
27				Role:           String("arn:of:container:role"),
28				ServiceName:    String("prod"),
29				LoadBalancers: []*ecs.LoadBalancer{
30					{
31						ContainerName:  String("redis"),
32						ContainerPort:  Int64(6379),
33						TargetGroupArn: String("arn:of:my:targetgroup"),
34					},
35				},
36			}).
37				ExpectCommandResult("arn:of:my:new:service").ExpectCalls("CreateService").Run(t)
38		})
39		t.Run("task", func(t *testing.T) {
40			Template("start containertask name=my-new-task cluster=my-cluster-name desired-count=3 type=task").
41				Mock(&ecsMock{
42					RunTaskFunc: func(param0 *ecs.RunTaskInput) (*ecs.RunTaskOutput, error) {
43						return &ecs.RunTaskOutput{
44							Tasks: []*ecs.Task{{TaskArn: String("arn:of:new:task")}},
45						}, nil
46					},
47				}).ExpectInput("RunTask", &ecs.RunTaskInput{
48				TaskDefinition: String("my-new-task"),
49				Cluster:        String("my-cluster-name"),
50				Count:          Int64(3),
51			}).
52				ExpectCommandResult("arn:of:new:task").ExpectCalls("RunTask").Run(t)
53		})
54	})
55
56	t.Run("stop", func(t *testing.T) {
57		t.Run("service", func(t *testing.T) {
58			Template("stop containertask cluster=my-cluster-name type=service deployment-name=prod").
59				Mock(&ecsMock{
60					DeleteServiceFunc: func(param0 *ecs.DeleteServiceInput) (*ecs.DeleteServiceOutput, error) {
61						return nil, nil
62					},
63				}).ExpectInput("DeleteService", &ecs.DeleteServiceInput{
64				Cluster: String("my-cluster-name"),
65				Service: String("prod"),
66			}).ExpectCalls("DeleteService").Run(t)
67		})
68
69		t.Run("task", func(t *testing.T) {
70			Template("stop containertask cluster=my-cluster-name type=task run-arn=arn:task:to:stop").
71				Mock(&ecsMock{
72					StopTaskFunc: func(param0 *ecs.StopTaskInput) (*ecs.StopTaskOutput, error) {
73						return nil, nil
74					},
75				}).ExpectInput("StopTask", &ecs.StopTaskInput{
76				Cluster: String("my-cluster-name"),
77				Task:    String("arn:task:to:stop"),
78			}).ExpectCalls("StopTask").Run(t)
79		})
80	})
81
82	t.Run("update", func(t *testing.T) {
83		Template("update containertask name=my-service cluster=my-cluster-name deployment-name=prod desired-count=5").
84			Mock(&ecsMock{
85				UpdateServiceFunc: func(param0 *ecs.UpdateServiceInput) (*ecs.UpdateServiceOutput, error) {
86					return nil, nil
87				},
88			}).ExpectInput("UpdateService", &ecs.UpdateServiceInput{
89			TaskDefinition: String("my-service"),
90			Cluster:        String("my-cluster-name"),
91			Service:        String("prod"),
92			DesiredCount:   Int64(5),
93		}).ExpectCalls("UpdateService").Run(t)
94	})
95
96	t.Run("attach", func(t *testing.T) {
97		t.Run("first container in task", func(t *testing.T) {
98			Template("attach containertask name=my-task container-name=redis image=redis/redis memory-hard-limit=128 command='redis --start --fake-param' env=User:Jdoe,DbPasswd:VERYSECRET privileged=true workdir=/home ports=6379,8080:80").
99				Mock(&ecsMock{
100					DescribeTaskDefinitionFunc: func(param0 *ecs.DescribeTaskDefinitionInput) (*ecs.DescribeTaskDefinitionOutput, error) {
101						return nil, awserr.New("ClientException", "unable to describe task definition", errors.New("task does not exist"))
102					},
103					RegisterTaskDefinitionFunc: func(param0 *ecs.RegisterTaskDefinitionInput) (*ecs.RegisterTaskDefinitionOutput, error) {
104						return &ecs.RegisterTaskDefinitionOutput{TaskDefinition: &ecs.TaskDefinition{TaskDefinitionArn: String("arn:of:my:new:definition")}}, nil
105					},
106				}).ExpectInput("DescribeTaskDefinition", &ecs.DescribeTaskDefinitionInput{
107				TaskDefinition: String("my-task"),
108			}).ExpectInput("RegisterTaskDefinition", &ecs.RegisterTaskDefinitionInput{
109				Family: String("my-task"),
110				ContainerDefinitions: []*ecs.ContainerDefinition{
111					{
112						Name:             String("redis"),
113						Image:            String("redis/redis"),
114						Memory:           Int64(128),
115						Command:          []*string{String("redis"), String("--start"), String("--fake-param")},
116						Environment:      []*ecs.KeyValuePair{{Name: String("User"), Value: String("Jdoe")}, {Name: String("DbPasswd"), Value: String("VERYSECRET")}},
117						Privileged:       Bool(true),
118						WorkingDirectory: String("/home"),
119						PortMappings:     []*ecs.PortMapping{{ContainerPort: Int64(6379)}, {ContainerPort: Int64(80), HostPort: Int64(8080)}},
120					},
121				},
122			}).ExpectCommandResult("arn:of:my:new:definition").ExpectCalls("DescribeTaskDefinition", "RegisterTaskDefinition").Run(t)
123		})
124
125		t.Run("more containers in existing task", func(t *testing.T) {
126			existingContainer := &ecs.ContainerDefinition{
127				Name:             String("redis"),
128				Image:            String("redis/redis"),
129				Memory:           Int64(128),
130				Command:          []*string{String("redis"), String("--start"), String("--fake-param")},
131				Environment:      []*ecs.KeyValuePair{{Name: String("User"), Value: String("Jdoe")}, {Name: String("DbPasswd"), Value: String("VERYSECRET")}},
132				Privileged:       Bool(true),
133				WorkingDirectory: String("/home"),
134				PortMappings:     []*ecs.PortMapping{{ContainerPort: Int64(6379)}, {ContainerPort: Int64(80), HostPort: Int64(8080)}},
135			}
136
137			Template("attach containertask name=my-task container-name=postgresql image=postgresql memory-hard-limit=64 command=postgresql,--port,3306 ports=3306:3306/tcp").
138				Mock(&ecsMock{
139					DescribeTaskDefinitionFunc: func(param0 *ecs.DescribeTaskDefinitionInput) (*ecs.DescribeTaskDefinitionOutput, error) {
140						return &ecs.DescribeTaskDefinitionOutput{
141							TaskDefinition: &ecs.TaskDefinition{
142								ContainerDefinitions: []*ecs.ContainerDefinition{existingContainer},
143								Family:               String("my-task"),
144								NetworkMode:          String("bridge"),
145							},
146						}, nil
147					},
148					RegisterTaskDefinitionFunc: func(param0 *ecs.RegisterTaskDefinitionInput) (*ecs.RegisterTaskDefinitionOutput, error) {
149						return &ecs.RegisterTaskDefinitionOutput{TaskDefinition: &ecs.TaskDefinition{TaskDefinitionArn: String("arn:of:my:updated:definition")}}, nil
150					},
151				}).ExpectInput("DescribeTaskDefinition", &ecs.DescribeTaskDefinitionInput{
152				TaskDefinition: String("my-task"),
153			}).ExpectInput("RegisterTaskDefinition", &ecs.RegisterTaskDefinitionInput{
154				ContainerDefinitions: []*ecs.ContainerDefinition{
155					existingContainer,
156					{
157						Name:         String("postgresql"),
158						Image:        String("postgresql"),
159						Memory:       Int64(64),
160						Command:      []*string{String("postgresql"), String("--port"), String("3306")},
161						PortMappings: []*ecs.PortMapping{{ContainerPort: Int64(3306), HostPort: Int64(3306), Protocol: String("tcp")}},
162					},
163				},
164				Family:      String("my-task"),
165				NetworkMode: String("bridge"),
166			}).ExpectCommandResult("arn:of:my:updated:definition").ExpectCalls("DescribeTaskDefinition", "RegisterTaskDefinition").Run(t)
167		})
168	})
169
170	t.Run("detach", func(t *testing.T) {
171		container1Def := &ecs.ContainerDefinition{
172			Name:  String("redis"),
173			Image: String("redis/redis"),
174		}
175		container2Def := &ecs.ContainerDefinition{
176			Name: String("posgresql"),
177		}
178		t.Run("at least 2 containers in task", func(t *testing.T) {
179			Template("detach containertask name=my-task container-name=posgresql").
180				Mock(&ecsMock{
181					DescribeTaskDefinitionFunc: func(param0 *ecs.DescribeTaskDefinitionInput) (*ecs.DescribeTaskDefinitionOutput, error) {
182						return &ecs.DescribeTaskDefinitionOutput{
183							TaskDefinition: &ecs.TaskDefinition{
184								ContainerDefinitions: []*ecs.ContainerDefinition{container1Def, container2Def},
185								Family:               String("my-task"),
186							},
187						}, nil
188					},
189					RegisterTaskDefinitionFunc: func(param0 *ecs.RegisterTaskDefinitionInput) (*ecs.RegisterTaskDefinitionOutput, error) {
190						return nil, nil
191					},
192				}).ExpectInput("DescribeTaskDefinition", &ecs.DescribeTaskDefinitionInput{
193				TaskDefinition: String("my-task"),
194			}).ExpectInput("RegisterTaskDefinition", &ecs.RegisterTaskDefinitionInput{
195				ContainerDefinitions: []*ecs.ContainerDefinition{container1Def},
196				Family:               String("my-task"),
197			}).ExpectCalls("DescribeTaskDefinition", "RegisterTaskDefinition").Run(t)
198		})
199
200		t.Run("last container in task", func(t *testing.T) {
201			Template("detach containertask name=my-task container-name=redis/redis").
202				Mock(&ecsMock{
203					DescribeTaskDefinitionFunc: func(param0 *ecs.DescribeTaskDefinitionInput) (*ecs.DescribeTaskDefinitionOutput, error) {
204						return &ecs.DescribeTaskDefinitionOutput{
205							TaskDefinition: &ecs.TaskDefinition{
206								ContainerDefinitions: []*ecs.ContainerDefinition{container1Def},
207								Family:               String("my-task"),
208								TaskDefinitionArn:    String("arn:my-task-to-deregister"),
209							},
210						}, nil
211					},
212					DeregisterTaskDefinitionFunc: func(param0 *ecs.DeregisterTaskDefinitionInput) (*ecs.DeregisterTaskDefinitionOutput, error) {
213						return nil, nil
214					},
215				}).ExpectInput("DescribeTaskDefinition", &ecs.DescribeTaskDefinitionInput{
216				TaskDefinition: String("my-task"),
217			}).ExpectInput("DeregisterTaskDefinition", &ecs.DeregisterTaskDefinitionInput{
218				TaskDefinition: String("arn:my-task-to-deregister"),
219			}).ExpectCalls("DescribeTaskDefinition", "DeregisterTaskDefinition").Run(t)
220		})
221	})
222
223	t.Run("delete", func(t *testing.T) {
224		Template("delete containertask name=my-task-to-delete all-versions=true").
225			Mock(&ecsMock{
226				ListTaskDefinitionsFunc: func(param0 *ecs.ListTaskDefinitionsInput) (*ecs.ListTaskDefinitionsOutput, error) {
227					return &ecs.ListTaskDefinitionsOutput{TaskDefinitionArns: []*string{String("arn:of:task:to:delete")}}, nil
228				},
229				DeregisterTaskDefinitionFunc: func(param0 *ecs.DeregisterTaskDefinitionInput) (*ecs.DeregisterTaskDefinitionOutput, error) {
230					return nil, nil
231				},
232			}).ExpectInput("ListTaskDefinitions", &ecs.ListTaskDefinitionsInput{
233			FamilyPrefix: String("my-task-to-delete"),
234		}).ExpectInput("DeregisterTaskDefinition", &ecs.DeregisterTaskDefinitionInput{
235			TaskDefinition: String("arn:of:task:to:delete"),
236		}).ExpectCalls("ListTaskDefinitions", "DeregisterTaskDefinition").Run(t)
237
238	})
239
240}
241