cloudformation_test.go 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373
  1. package backend
  2. import (
  3. "fmt"
  4. "reflect"
  5. "testing"
  6. "github.com/aws/aws-sdk-go/service/elbv2"
  7. "github.com/awslabs/goformation/v4/cloudformation"
  8. "github.com/awslabs/goformation/v4/cloudformation/ec2"
  9. "github.com/awslabs/goformation/v4/cloudformation/ecs"
  10. "github.com/awslabs/goformation/v4/cloudformation/elasticloadbalancingv2"
  11. "github.com/awslabs/goformation/v4/cloudformation/iam"
  12. "github.com/awslabs/goformation/v4/cloudformation/logs"
  13. "github.com/compose-spec/compose-go/cli"
  14. "github.com/compose-spec/compose-go/loader"
  15. "github.com/compose-spec/compose-go/types"
  16. "github.com/docker/ecs-plugin/pkg/compose"
  17. "gotest.tools/v3/assert"
  18. "gotest.tools/v3/golden"
  19. )
  20. func TestSimpleConvert(t *testing.T) {
  21. project := load(t, "testdata/input/simple-single-service.yaml")
  22. result := convertResultAsString(t, project)
  23. expected := "simple/simple-cloudformation-conversion.golden"
  24. golden.Assert(t, result, expected)
  25. }
  26. func TestLogging(t *testing.T) {
  27. template := convertYaml(t, "test", `
  28. services:
  29. foo:
  30. image: hello_world
  31. logging:
  32. options:
  33. awslogs-datetime-pattern: "FOO"
  34. x-aws-logs_retention: 10
  35. `)
  36. def := template.Resources["FooTaskDefinition"].(*ecs.TaskDefinition)
  37. logging := def.ContainerDefinitions[0].LogConfiguration
  38. assert.Equal(t, logging.Options["awslogs-datetime-pattern"], "FOO")
  39. logGroup := template.Resources["LogGroup"].(*logs.LogGroup)
  40. assert.Equal(t, logGroup.RetentionInDays, 10)
  41. }
  42. func TestEnvFile(t *testing.T) {
  43. template := convertYaml(t, "test", `
  44. services:
  45. foo:
  46. image: hello_world
  47. env_file:
  48. - testdata/input/envfile
  49. `)
  50. def := template.Resources["FooTaskDefinition"].(*ecs.TaskDefinition)
  51. env := def.ContainerDefinitions[0].Environment
  52. var found bool
  53. for _, pair := range env {
  54. if pair.Name == "FOO" {
  55. assert.Equal(t, pair.Value, "BAR")
  56. found = true
  57. }
  58. }
  59. assert.Check(t, found, "environment variable FOO not set")
  60. }
  61. func TestEnvFileAndEnv(t *testing.T) {
  62. template := convertYaml(t, "test", `
  63. services:
  64. foo:
  65. image: hello_world
  66. env_file:
  67. - testdata/input/envfile
  68. environment:
  69. - "FOO=ZOT"
  70. `)
  71. def := template.Resources["FooTaskDefinition"].(*ecs.TaskDefinition)
  72. env := def.ContainerDefinitions[0].Environment
  73. var found bool
  74. for _, pair := range env {
  75. if pair.Name == "FOO" {
  76. assert.Equal(t, pair.Value, "ZOT")
  77. found = true
  78. }
  79. }
  80. assert.Check(t, found, "environment variable FOO not set")
  81. }
  82. func TestRollingUpdateLimits(t *testing.T) {
  83. template := convertYaml(t, "test", `
  84. services:
  85. foo:
  86. image: hello_world
  87. deploy:
  88. replicas: 4
  89. update_config:
  90. parallelism: 2
  91. `)
  92. service := template.Resources["FooService"].(*ecs.Service)
  93. assert.Check(t, service.DeploymentConfiguration.MaximumPercent == 150)
  94. assert.Check(t, service.DeploymentConfiguration.MinimumHealthyPercent == 50)
  95. }
  96. func TestRollingUpdateExtension(t *testing.T) {
  97. template := convertYaml(t, "test", `
  98. services:
  99. foo:
  100. image: hello_world
  101. deploy:
  102. update_config:
  103. x-aws-min_percent: 25
  104. x-aws-max_percent: 125
  105. `)
  106. service := template.Resources["FooService"].(*ecs.Service)
  107. assert.Check(t, service.DeploymentConfiguration.MaximumPercent == 125)
  108. assert.Check(t, service.DeploymentConfiguration.MinimumHealthyPercent == 25)
  109. }
  110. func TestRolePolicy(t *testing.T) {
  111. template := convertYaml(t, "test", `
  112. services:
  113. foo:
  114. image: hello_world
  115. x-aws-pull_credentials: "secret"
  116. `)
  117. role := template.Resources["FooTaskExecutionRole"].(*iam.Role)
  118. assert.Check(t, role != nil)
  119. assert.Check(t, role.ManagedPolicyArns[0] == ECSTaskExecutionPolicy)
  120. assert.Check(t, role.ManagedPolicyArns[1] == ECRReadOnlyPolicy)
  121. // We expect an extra policy has been created for x-aws-pull_credentials
  122. assert.Check(t, len(role.Policies) == 1)
  123. policy := role.Policies[0].PolicyDocument.(*PolicyDocument)
  124. expected := []string{"secretsmanager:GetSecretValue", "ssm:GetParameters", "kms:Decrypt"}
  125. assert.DeepEqual(t, expected, policy.Statement[0].Action)
  126. assert.DeepEqual(t, []string{"secret"}, policy.Statement[0].Resource)
  127. }
  128. func TestMapNetworksToSecurityGroups(t *testing.T) {
  129. template := convertYaml(t, "test", `
  130. services:
  131. test:
  132. image: hello_world
  133. networks:
  134. - front-tier
  135. - back-tier
  136. networks:
  137. front-tier:
  138. name: public
  139. back-tier:
  140. internal: true
  141. `)
  142. assert.Check(t, template.Resources["TestPublicNetwork"] != nil)
  143. assert.Check(t, template.Resources["TestBacktierNetwork"] != nil)
  144. assert.Check(t, template.Resources["TestBacktierNetworkIngress"] != nil)
  145. ingress := template.Resources["TestPublicNetworkIngress"].(*ec2.SecurityGroupIngress)
  146. assert.Check(t, ingress != nil)
  147. assert.Check(t, ingress.SourceSecurityGroupId == cloudformation.Ref("TestPublicNetwork"))
  148. }
  149. func TestLoadBalancerTypeApplication(t *testing.T) {
  150. template := convertYaml(t, "test123456789009876543211234567890", `
  151. services:
  152. test:
  153. image: nginx
  154. ports:
  155. - 80:80
  156. `)
  157. lb := template.Resources["TestLoadBalancer"].(*elasticloadbalancingv2.LoadBalancer)
  158. assert.Check(t, lb != nil)
  159. assert.Check(t, len(lb.Name) <= 32)
  160. assert.Check(t, lb.Type == elbv2.LoadBalancerTypeEnumApplication)
  161. assert.Check(t, len(lb.SecurityGroups) > 0)
  162. }
  163. func TestNoLoadBalancerIfNoPortExposed(t *testing.T) {
  164. template := convertYaml(t, "test", `
  165. services:
  166. test:
  167. image: nginx
  168. foo:
  169. image: bar
  170. `)
  171. for _, r := range template.Resources {
  172. assert.Check(t, r.AWSCloudFormationType() != "AWS::ElasticLoadBalancingV2::TargetGroup")
  173. assert.Check(t, r.AWSCloudFormationType() != "AWS::ElasticLoadBalancingV2::Listener")
  174. assert.Check(t, r.AWSCloudFormationType() != "AWS::ElasticLoadBalancingV2::LoadBalancer")
  175. }
  176. }
  177. func TestServiceReplicas(t *testing.T) {
  178. template := convertYaml(t, "test", `
  179. services:
  180. test:
  181. image: nginx
  182. deploy:
  183. replicas: 10
  184. `)
  185. s := template.Resources["TestService"].(*ecs.Service)
  186. assert.Check(t, s != nil)
  187. assert.Check(t, s.DesiredCount == 10)
  188. }
  189. func TestTaskSizeConvert(t *testing.T) {
  190. template := convertYaml(t, "test", `
  191. services:
  192. test:
  193. image: nginx
  194. deploy:
  195. resources:
  196. limits:
  197. cpus: '0.5'
  198. memory: 2048M
  199. reservations:
  200. cpus: '0.5'
  201. memory: 2048M
  202. `)
  203. def := template.Resources["TestTaskDefinition"].(*ecs.TaskDefinition)
  204. assert.Equal(t, def.Cpu, "512")
  205. assert.Equal(t, def.Memory, "2048")
  206. template = convertYaml(t, "test", `
  207. services:
  208. test:
  209. image: nginx
  210. deploy:
  211. resources:
  212. limits:
  213. cpus: '4'
  214. memory: 8192M
  215. reservations:
  216. cpus: '4'
  217. memory: 8192M
  218. `)
  219. def = template.Resources["TestTaskDefinition"].(*ecs.TaskDefinition)
  220. assert.Equal(t, def.Cpu, "4096")
  221. assert.Equal(t, def.Memory, "8192")
  222. }
  223. func TestTaskSizeConvertFailure(t *testing.T) {
  224. model := loadConfig(t, "test", `
  225. services:
  226. test:
  227. image: nginx
  228. deploy:
  229. resources:
  230. limits:
  231. cpus: '0.5'
  232. memory: 2043248M
  233. `)
  234. _, err := Backend{}.Convert(model)
  235. assert.ErrorContains(t, err, "the resources requested are not supported by ECS/Fargate")
  236. }
  237. func TestLoadBalancerTypeNetwork(t *testing.T) {
  238. template := convertYaml(t, "test", `
  239. services:
  240. test:
  241. image: nginx
  242. ports:
  243. - 80:80
  244. - 88:88
  245. `)
  246. lb := template.Resources["TestLoadBalancer"].(*elasticloadbalancingv2.LoadBalancer)
  247. assert.Check(t, lb != nil)
  248. assert.Check(t, lb.Type == elbv2.LoadBalancerTypeEnumNetwork)
  249. }
  250. func TestServiceMapping(t *testing.T) {
  251. template := convertYaml(t, "test", `
  252. services:
  253. test:
  254. image: "image"
  255. command: "command"
  256. entrypoint: "entrypoint"
  257. environment:
  258. - "FOO=BAR"
  259. cap_add:
  260. - SYS_PTRACE
  261. cap_drop:
  262. - SYSLOG
  263. init: true
  264. user: "user"
  265. working_dir: "working_dir"
  266. `)
  267. def := template.Resources["TestTaskDefinition"].(*ecs.TaskDefinition)
  268. container := def.ContainerDefinitions[0]
  269. assert.Equal(t, container.Image, "image")
  270. assert.Equal(t, container.Command[0], "command")
  271. assert.Equal(t, container.EntryPoint[0], "entrypoint")
  272. assert.Equal(t, get(container.Environment, "FOO"), "BAR")
  273. assert.Check(t, container.LinuxParameters.InitProcessEnabled)
  274. assert.Equal(t, container.LinuxParameters.Capabilities.Add[0], "SYS_PTRACE")
  275. assert.Equal(t, container.LinuxParameters.Capabilities.Drop[0], "SYSLOG")
  276. assert.Equal(t, container.User, "user")
  277. assert.Equal(t, container.WorkingDirectory, "working_dir")
  278. }
  279. func get(l []ecs.TaskDefinition_KeyValuePair, name string) string {
  280. for _, e := range l {
  281. if e.Name == name {
  282. return e.Value
  283. }
  284. }
  285. return ""
  286. }
  287. func TestResourcesHaveProjectTagSet(t *testing.T) {
  288. template := convertYaml(t, "test", `
  289. services:
  290. test:
  291. image: nginx
  292. ports:
  293. - 80:80
  294. - 88:88
  295. `)
  296. for _, r := range template.Resources {
  297. tags := reflect.Indirect(reflect.ValueOf(r)).FieldByName("Tags")
  298. if !tags.IsValid() {
  299. continue
  300. }
  301. for i := 0; i < tags.Len(); i++ {
  302. k := tags.Index(i).FieldByName("Key").String()
  303. v := tags.Index(i).FieldByName("Value").String()
  304. if k == compose.ProjectTag {
  305. assert.Equal(t, v, "Test")
  306. }
  307. }
  308. }
  309. }
  310. func convertResultAsString(t *testing.T, project *types.Project) string {
  311. backend, err := NewBackend("", "")
  312. assert.NilError(t, err)
  313. result, err := backend.Convert(project)
  314. assert.NilError(t, err)
  315. resultAsJSON, err := result.JSON()
  316. assert.NilError(t, err)
  317. return fmt.Sprintf("%s\n", string(resultAsJSON))
  318. }
  319. func load(t *testing.T, paths ...string) *types.Project {
  320. options := cli.ProjectOptions{
  321. Name: t.Name(),
  322. ConfigPaths: paths,
  323. }
  324. project, err := cli.ProjectFromOptions(&options)
  325. assert.NilError(t, err)
  326. return project
  327. }
  328. func convertYaml(t *testing.T, name string, yaml string) *cloudformation.Template {
  329. model := loadConfig(t, name, yaml)
  330. template, err := Backend{}.Convert(model)
  331. assert.NilError(t, err)
  332. return template
  333. }
  334. func loadConfig(t *testing.T, name string, yaml string) *types.Project {
  335. dict, err := loader.ParseYAML([]byte(yaml))
  336. assert.NilError(t, err)
  337. model, err := loader.Load(types.ConfigDetails{
  338. ConfigFiles: []types.ConfigFile{
  339. {Config: dict},
  340. },
  341. }, func(options *loader.Options) {
  342. options.Name = "Test"
  343. })
  344. assert.NilError(t, err)
  345. return model
  346. }