ufo 3.5.7 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (191) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +24 -0
  3. data/Gemfile.lock +16 -10
  4. data/README.md +12 -13
  5. data/docs/_config.yml +1 -1
  6. data/docs/_docs/auto-completion.md +4 -4
  7. data/docs/_docs/automated-cleanup.md +1 -1
  8. data/docs/_docs/conventions.md +7 -7
  9. data/docs/_docs/customize-cloudformation.md +36 -0
  10. data/docs/_docs/faq.md +9 -7
  11. data/docs/_docs/fargate.md +102 -0
  12. data/docs/_docs/helpers.md +3 -3
  13. data/docs/_docs/load-balancer.md +72 -0
  14. data/docs/_docs/migrations.md +2 -2
  15. data/docs/_docs/next-steps.md +2 -2
  16. data/docs/_docs/params.md +12 -41
  17. data/docs/_docs/route53-support.md +28 -0
  18. data/docs/_docs/run-in-pieces.md +2 -2
  19. data/docs/_docs/security-groups.md +54 -0
  20. data/docs/_docs/settings-cfn.md +11 -0
  21. data/docs/_docs/settings-network.md +34 -0
  22. data/docs/_docs/settings.md +18 -15
  23. data/docs/_docs/single-task.md +3 -3
  24. data/docs/_docs/ssl-support.md +42 -0
  25. data/docs/_docs/structure.md +5 -1
  26. data/docs/_docs/stuck-cloudformation.md +30 -0
  27. data/docs/_docs/tutorial-ufo-docker-build.md +19 -31
  28. data/docs/_docs/tutorial-ufo-init.md +16 -12
  29. data/docs/_docs/tutorial-ufo-ship.md +50 -54
  30. data/docs/_docs/tutorial-ufo-ships.md +9 -7
  31. data/docs/_docs/tutorial-ufo-tasks-build.md +26 -17
  32. data/docs/_docs/ufo-current.md +50 -0
  33. data/docs/_docs/ufo-env-extra.md +21 -0
  34. data/docs/_docs/ufo-env.md +6 -13
  35. data/docs/_docs/ufo-tasks-register.md +3 -3
  36. data/docs/_docs/upgrade4.md +49 -0
  37. data/docs/_docs/variables.md +5 -5
  38. data/docs/_docs/why-cloudformation.md +22 -0
  39. data/docs/_includes/about.html +1 -1
  40. data/docs/_includes/cfn-customize.md +39 -0
  41. data/docs/_includes/commands.html +6 -6
  42. data/docs/_includes/css/ufo.css +1 -0
  43. data/docs/_includes/example.html +13 -13
  44. data/docs/_includes/reference.md +1 -1
  45. data/docs/_includes/subnav.html +22 -5
  46. data/docs/_includes/ufo-ship-options.md +7 -6
  47. data/docs/_reference/ufo-apps.md +36 -0
  48. data/docs/_reference/ufo-cancel.md +24 -0
  49. data/docs/_reference/ufo-completion.md +1 -1
  50. data/docs/_reference/ufo-completion_script.md +1 -1
  51. data/docs/_reference/ufo-current.md +93 -0
  52. data/docs/_reference/ufo-deploy.md +18 -17
  53. data/docs/_reference/ufo-destroy.md +6 -4
  54. data/docs/_reference/ufo-docker-base.md +7 -7
  55. data/docs/_reference/ufo-docker-build.md +9 -9
  56. data/docs/_reference/ufo-docker-clean.md +8 -8
  57. data/docs/_reference/ufo-docker-name.md +4 -4
  58. data/docs/_reference/ufo-docker.md +4 -2
  59. data/docs/_reference/ufo-init.md +31 -20
  60. data/docs/_reference/ufo-network-help.md +15 -0
  61. data/docs/_reference/ufo-network-init.md +38 -0
  62. data/docs/_reference/ufo-network.md +26 -0
  63. data/docs/_reference/ufo-ps.md +53 -0
  64. data/docs/_reference/ufo-releases.md +40 -0
  65. data/docs/_reference/ufo-resources.md +44 -0
  66. data/docs/_reference/ufo-rollback.md +59 -0
  67. data/docs/_reference/ufo-scale.md +23 -3
  68. data/docs/_reference/ufo-ship.md +54 -27
  69. data/docs/_reference/ufo-ships.md +17 -26
  70. data/docs/_reference/ufo-stop.md +31 -0
  71. data/docs/_reference/ufo-task.md +15 -16
  72. data/docs/_reference/ufo-tasks-build.md +10 -10
  73. data/docs/_reference/ufo-tasks-register.md +3 -3
  74. data/docs/_reference/ufo-tasks.md +1 -1
  75. data/docs/_reference/ufo-upgrade-help.md +15 -0
  76. data/docs/_reference/ufo-upgrade-v2to3.md +15 -0
  77. data/docs/_reference/ufo-upgrade-v3_3to3_4.md +15 -0
  78. data/docs/_reference/ufo-upgrade-v3to4.md +27 -0
  79. data/docs/_reference/ufo-upgrade.md +28 -0
  80. data/docs/_reference/ufo-version.md +1 -1
  81. data/docs/articles.md +2 -2
  82. data/docs/docs.md +1 -1
  83. data/docs/img/docs/cloudformation-resources.png +0 -0
  84. data/docs/img/tutorials/ecs-console-task-definitions.png +0 -0
  85. data/docs/img/tutorials/ecs-console-ufo-ship.png +0 -0
  86. data/docs/img/tutorials/ecs-console-ufo-ships.png +0 -0
  87. data/docs/quick-start.md +21 -9
  88. data/docs/reference.md +10 -2
  89. data/exe/ufo +1 -1
  90. data/lib/cfn/stack.yml +259 -0
  91. data/lib/template/.ufo/params.yml.tt +21 -60
  92. data/lib/template/.ufo/settings.yml.tt +6 -1
  93. data/lib/template/.ufo/settings/cfn/default.yml.tt +55 -0
  94. data/lib/template/.ufo/settings/network/default.yml.tt +18 -0
  95. data/lib/template/.ufo/task_definitions.rb.tt +7 -6
  96. data/lib/template/.ufo/templates/fargate.json.erb +1 -1
  97. data/lib/template/.ufo/templates/main.json.erb +1 -0
  98. data/lib/template/.ufo/variables/base.rb.tt +5 -2
  99. data/lib/template/Dockerfile +10 -15
  100. data/lib/template/bin/deploy.tt +2 -2
  101. data/lib/ufo.rb +29 -20
  102. data/lib/ufo/apps.rb +49 -0
  103. data/lib/ufo/apps/cfn_map.rb +70 -0
  104. data/lib/ufo/apps/service.rb +56 -0
  105. data/lib/ufo/aws_service.rb +15 -6
  106. data/lib/ufo/base.rb +32 -0
  107. data/lib/ufo/cancel.rb +23 -0
  108. data/lib/ufo/cli.rb +91 -27
  109. data/lib/ufo/core.rb +35 -3
  110. data/lib/ufo/current.rb +104 -0
  111. data/lib/ufo/destroy.rb +10 -41
  112. data/lib/ufo/docker/builder.rb +5 -4
  113. data/lib/ufo/docker/cleaner.rb +1 -1
  114. data/lib/ufo/docker/pusher.rb +2 -2
  115. data/lib/ufo/ecr/cleaner.rb +1 -1
  116. data/lib/ufo/help/apps.md +12 -0
  117. data/lib/ufo/help/balancer.md +3 -0
  118. data/lib/ufo/help/current.md +65 -0
  119. data/lib/ufo/help/deploy.md +4 -4
  120. data/lib/ufo/help/destroy.md +3 -3
  121. data/lib/ufo/help/docker.md +3 -1
  122. data/lib/ufo/help/docker/base.md +7 -7
  123. data/lib/ufo/help/docker/build.md +9 -9
  124. data/lib/ufo/help/docker/clean.md +8 -8
  125. data/lib/ufo/help/docker/name.md +4 -4
  126. data/lib/ufo/help/help.md +5 -0
  127. data/lib/ufo/help/init.md +24 -16
  128. data/lib/ufo/help/network/init.md +13 -0
  129. data/lib/ufo/help/ps.md +27 -0
  130. data/lib/ufo/help/releases.md +16 -0
  131. data/lib/ufo/help/resources.md +20 -0
  132. data/lib/ufo/help/rollback.md +35 -0
  133. data/lib/ufo/help/scale.md +22 -2
  134. data/lib/ufo/help/ship.md +40 -14
  135. data/lib/ufo/help/ships.md +4 -13
  136. data/lib/ufo/help/stop.md +7 -0
  137. data/lib/ufo/help/task.md +9 -9
  138. data/lib/ufo/help/tasks/build.md +10 -10
  139. data/lib/ufo/help/tasks/register.md +3 -3
  140. data/lib/ufo/help/upgrade/v3to4.md +3 -0
  141. data/lib/ufo/info.rb +62 -0
  142. data/lib/ufo/init.rb +36 -23
  143. data/lib/ufo/log_group.rb +2 -1
  144. data/lib/ufo/network.rb +24 -0
  145. data/lib/ufo/network/fetch.rb +41 -0
  146. data/lib/ufo/network/helper.rb +23 -0
  147. data/lib/ufo/network/init.rb +26 -0
  148. data/lib/ufo/param.rb +5 -5
  149. data/lib/ufo/ps.rb +102 -0
  150. data/lib/ufo/ps/task.rb +78 -0
  151. data/lib/ufo/releases.rb +14 -0
  152. data/lib/ufo/rollback.rb +53 -0
  153. data/lib/ufo/scale.rb +6 -12
  154. data/lib/ufo/sequence.rb +7 -0
  155. data/lib/ufo/setting.rb +7 -6
  156. data/lib/ufo/setting/profile.rb +24 -0
  157. data/lib/ufo/ship.rb +35 -326
  158. data/lib/ufo/stack.rb +203 -0
  159. data/lib/ufo/stack/context.rb +242 -0
  160. data/lib/ufo/stack/helper.rb +28 -0
  161. data/lib/ufo/stack/status.rb +195 -0
  162. data/lib/ufo/stop.rb +47 -0
  163. data/lib/ufo/task.rb +96 -15
  164. data/lib/ufo/tasks/register.rb +1 -1
  165. data/lib/ufo/template_scope.rb +81 -7
  166. data/lib/ufo/upgrade.rb +32 -0
  167. data/lib/ufo/{upgrade3.rb → upgrade/upgrade3.rb} +1 -1
  168. data/lib/ufo/{upgrade33_to_34.rb → upgrade/upgrade33to34.rb} +2 -2
  169. data/lib/ufo/upgrade/upgrade4.rb +161 -0
  170. data/lib/ufo/util.rb +19 -6
  171. data/lib/ufo/version.rb +1 -1
  172. data/spec/fixtures/apps/describe_services.json +96 -0
  173. data/spec/fixtures/cfn/stack-events-complete.json +1080 -0
  174. data/spec/fixtures/cfn/stack-events-in-progress.json +1080 -0
  175. data/spec/fixtures/cfn/stack-events-update-rollback-complete.json +1086 -0
  176. data/spec/fixtures/deployments.json +50 -0
  177. data/spec/fixtures/ps/describe_tasks.json +58 -0
  178. data/spec/fixtures/settings.yml +2 -0
  179. data/spec/lib/apps_spec.rb +20 -0
  180. data/spec/lib/cli_spec.rb +4 -4
  181. data/spec/lib/ps_spec.rb +14 -0
  182. data/spec/lib/setting_spec.rb +2 -1
  183. data/spec/lib/ship_spec.rb +6 -30
  184. data/spec/lib/stack/status_spec.rb +76 -0
  185. data/spec/lib/stop_spec.rb +13 -0
  186. data/spec/lib/task_spec.rb +5 -2
  187. data/spec/spec_helper.rb +1 -1
  188. data/ufo.gemspec +2 -0
  189. metadata +120 -6
  190. data/docs/_reference/ufo-upgrade3.md +0 -23
  191. data/docs/_reference/ufo-upgrade3_3_to_3_4.md +0 -23
@@ -0,0 +1,14 @@
1
+ require 'text-table'
2
+
3
+ module Ufo
4
+ class Releases < Base
5
+ def list
6
+ puts "Recent task definitions for this service:"
7
+ arns = task_definition_arns(@service)
8
+ task_definitions = arns.map { |arn| arn.split('/').last }
9
+ task_definitions.each do |name|
10
+ puts " #{name}"
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,53 @@
1
+ module Ufo
2
+ class Rollback < Base
3
+ def deploy
4
+ task_definition = normalize_version(@options[:version])
5
+ puts "Rolling back ECS service to task definition #{task_definition}"
6
+ ship = Ship.new(@service, @options.merge(task_definition: task_definition))
7
+ ship.deploy
8
+ end
9
+
10
+ # normalizes the task definition
11
+ # if user passes in:
12
+ # 1 => demo-web:1
13
+ # demo-web:1 => demo-web:1
14
+ def normalize_version(version)
15
+ if version =~ /^\d+$/
16
+ "#{@service}:#{version}"
17
+ elsif version.include?(':') && !version.include?(":ufo-")
18
+ version
19
+ else # assume git sha
20
+ # tongueroo/demo-ufo:ufo-2018-06-21T15-03-52-ac60240
21
+ from_git_sha(version)
22
+ end
23
+ end
24
+
25
+ def from_git_sha(sha)
26
+ task_definition = nil
27
+ max_items = 30
28
+ puts "Looking for task definition based on the git sha. Searching most recent #{max_items} task definitions..."
29
+ arns = task_definition_arns(@service, max_items)
30
+ arns.each do |arn|
31
+ resp = ecs.describe_task_definition(task_definition: arn)
32
+ found = find_sha(resp.task_definition, sha)
33
+ if found
34
+ task_definition = arn.split('/').last
35
+ break
36
+ end
37
+ print '.'
38
+ @final_newline
39
+ end
40
+
41
+ puts '' if @final_newline
42
+ unless task_definition
43
+ puts "Unable to find a task definition with a image with: #{version}"
44
+ end
45
+ task_definition
46
+ end
47
+
48
+ def find_sha(task_definition, sha)
49
+ container = task_definition["container_definitions"].first # assume first
50
+ container["image"].include?(sha)
51
+ end
52
+ end
53
+ end
@@ -1,33 +1,27 @@
1
1
  module Ufo
2
- class Scale
3
- include Util
4
- include AwsService
2
+ class Scale < Base
3
+ delegate :service, to: :info
5
4
 
6
5
  def initialize(service, count, options={})
7
- @service = service
6
+ super(service, options)
8
7
  @count = count
9
- @options = options
10
- @cluster = @options[:cluster] || default_cluster
11
8
  end
12
9
 
13
10
  def update
14
11
  unless service_exists?
15
- puts "Unable to find the #{@service} service on #{@cluster} cluster."
12
+ puts "Unable to find the #{@pretty_service_name.colorize(:green)} service on the #{@cluster.colorize(:green)} cluster."
16
13
  puts "Are you sure you are trying to scale the right service on the right cluster?"
17
14
  exit
18
15
  end
19
16
  ecs.update_service(
20
- service: @service,
17
+ service: service.service_name,
21
18
  cluster: @cluster,
22
19
  desired_count: @count
23
20
  )
24
- puts "Scale #{@service} service in #{@cluster} cluster to #{@count}" unless @options[:mute]
21
+ puts "Scale #{@pretty_service_name.colorize(:green)} service in #{@cluster.colorize(:green)} cluster to #{@count}" unless @options[:mute]
25
22
  end
26
23
 
27
24
  def service_exists?
28
- cluster = ecs.describe_clusters(clusters: [@cluster]).clusters.first
29
- return false unless cluster
30
- service = ecs.describe_services(services: [@service], cluster: @cluster).services.first
31
25
  !!service
32
26
  end
33
27
  end
@@ -6,11 +6,18 @@ module Ufo
6
6
  class Sequence < Thor::Group
7
7
  include Thor::Actions
8
8
 
9
+ add_runtime_options! # force, pretend, quiet, skip options
10
+ # https://github.com/erikhuda/thor/blob/master/lib/thor/actions.rb#L49
11
+
9
12
  def self.source_paths
10
13
  [File.expand_path("../../template", __FILE__)]
11
14
  end
12
15
 
13
16
  private
17
+ def inferred_app
18
+ File.basename(Dir.pwd)
19
+ end
20
+
14
21
  def get_execution_role_arn_input
15
22
  return @execution_role_arn if @execution_role_arn
16
23
 
@@ -2,6 +2,9 @@ require 'yaml'
2
2
 
3
3
  module Ufo
4
4
  class Setting
5
+ extend Memoist
6
+ autoload :Profile, "ufo/setting/profile"
7
+
5
8
  def initialize(check_ufo_project=true)
6
9
  @check_ufo_project = check_ufo_project
7
10
  end
@@ -9,12 +12,8 @@ module Ufo
9
12
  # data contains the settings.yml config. The order or precedence for settings
10
13
  # is the project ufo/settings.yml and then the ~/.ufo/settings.yml.
11
14
  def data
12
- return @data if @data
13
-
14
15
  if @check_ufo_project && !File.exist?(project_settings_path)
15
- puts "ERROR: No settings file at #{project_settings_path}. Are you sure you are in a project with ufo setup?"
16
- puts "If you want to set up ufo for this prjoect, please create a settings file via: ufo init"
17
- exit 1
16
+ Ufo.check_ufo_project!
18
17
  end
19
18
 
20
19
  # project based settings files
@@ -28,8 +27,10 @@ module Ufo
28
27
 
29
28
  all_envs = default.deep_merge(user.deep_merge(project))
30
29
  all_envs = merge_base(all_envs)
31
- @@data = all_envs[Ufo.env] || all_envs["base"] || {}
30
+ data = all_envs[Ufo.env] || all_envs["base"] || {}
31
+ data.deep_symbolize_keys
32
32
  end
33
+ memoize :data
33
34
 
34
35
  private
35
36
  def load_file(path)
@@ -0,0 +1,24 @@
1
+ class Ufo::Setting
2
+ class Profile
3
+ extend Memoist
4
+
5
+ def initialize(type, profile='default')
6
+ @type = type.to_s # cfn or network
7
+ @profile = profile
8
+ end
9
+
10
+ def data
11
+ path = "#{Ufo.root}/.ufo/settings/#{@type}/#{@profile}.yml"
12
+ unless File.exist?(path)
13
+ puts "#{@type.camelize} profile #{path} not found. Please double check that it exists."
14
+ exit 1
15
+ end
16
+
17
+ text = RenderMePretty.result(path)
18
+ # puts "text:".colorize(:cyan)
19
+ # puts text
20
+ YAML.load(text).deep_symbolize_keys
21
+ end
22
+ memoize :data
23
+ end
24
+ end
@@ -3,23 +3,16 @@ require 'colorize'
3
3
  module Ufo
4
4
  class UfoError < RuntimeError; end
5
5
  class ShipmentOverridden < UfoError; end
6
+ autoload :KillTask, 'ufo/ship/kill_task'
6
7
 
7
- class Ship
8
- include AwsService
9
- include Util
10
-
11
- def initialize(service, task_definition, options={})
12
- @service = service
13
- @task_definition = task_definition
14
- @options = options
15
- @target_group_prompt = @options[:target_group_prompt].nil? ? true : @options[:target_group_prompt]
16
- @cluster = @options[:cluster] || default_cluster
17
- @wait_for_deployment = @options[:wait].nil? ? true : @options[:wait]
18
- @stop_old_tasks = @options[:stop_old_tasks].nil? ? false : @options[:stop_old_tasks]
8
+ class Ship < Base
9
+ def initialize(service, options={})
10
+ super
11
+ @task_definition = options[:task_definition]
19
12
  end
20
13
 
21
14
  def deploy
22
- message = "Shipping #{@service}..."
15
+ message = "Deploying #{@service}..."
23
16
  unless @options[:mute]
24
17
  if @options[:noop]
25
18
  puts "NOOP: #{message}"
@@ -31,180 +24,45 @@ module Ufo
31
24
 
32
25
  ensure_log_group_exist
33
26
  ensure_cluster_exist
34
- process_deployment
35
-
36
- puts "Software shipped!" unless @options[:mute]
37
- end
27
+ stop_old_tasks if @options[:stop_old_tasks]
28
+ success = deploy_stack
38
29
 
39
- def ensure_log_group_exist
40
- LogGroup.new(@task_definition, @options).create
41
- end
42
-
43
- def process_deployment
44
- ecs_service = find_ecs_service
45
- deployed_service = if ecs_service
46
- # update all existing service
47
- update_service(ecs_service)
48
- else
49
- # create service on the first cluster
50
- create_service
51
- end
52
-
53
- wait_for_deployment(deployed_service) if @wait_for_deployment && !@options[:noop]
54
- stop_old_task(deployed_service) if @stop_old_tasks
55
- end
56
-
57
- def service_tasks(cluster, service)
58
- all_task_arns = ecs.list_tasks(cluster: cluster, service_name: service).task_arns
59
- return [] if all_task_arns.empty?
60
- ecs.describe_tasks(cluster: cluster, tasks: all_task_arns).tasks
61
- end
62
-
63
- def old_task?(deployed_task_definition_arn, task_definition_arn)
64
- puts "deployed_task_definition_arn: #{deployed_task_definition_arn.inspect}"
65
- puts "task_definition_arn: #{task_definition_arn.inspect}"
66
- deployed_version = deployed_task_definition_arn.split(':').last.to_i
67
- version = task_definition_arn.split(':').last.to_i
68
- puts "deployed_version #{deployed_version.inspect}"
69
- puts "version #{version.inspect}"
70
- is_old = version < deployed_version
71
- puts "is_old #{is_old.inspect}"
72
- is_old
73
- end
74
-
75
- def stop_old_tasks(services)
76
- services.each do |service|
77
- stop_old_task(service)
30
+ return if @options[:mute] || !@options[:wait]
31
+ if success
32
+ puts "Software shipped!"
33
+ else
34
+ puts "Software fail to ship."
78
35
  end
79
36
  end
80
37
 
81
- # aws ecs list-tasks --cluster prod-hi --service-name gr-web-prod
82
- # aws ecs describe-tasks --tasks arn:aws:ecs:us-east-1:467446852200:task/09038fd2-f989-4903-a8c6-1bc41761f93f --cluster prod-hi
83
- def stop_old_task(deployed_service)
84
- deployed_task_definition_arn = deployed_service.task_definition
85
- puts "deployed_task_definition_arn #{deployed_task_definition_arn.inspect}"
86
-
87
- # cannot use @serivce because of multiple mode
88
- all_tasks = service_tasks(@cluster, deployed_service.service_name)
89
- old_tasks = all_tasks.select do |task|
90
- old_task?(deployed_task_definition_arn, task.task_definition_arn)
91
- end
92
-
93
- reason = "Ufo #{Ufo::VERSION} has deployed new code and waited until the newer code is running."
94
- puts reason
95
- # Stopping old tasks after we have confirmed that the new task definition has the same
96
- # number of desired_count and running_count speeds up clean up and ensure that we
97
- # dont have any stale code being served. It seems to take a long time for the
98
- # ELB to drain the register container otherwise. This might cut off some requests but
99
- # providing this as an option that can be turned of beause I've seen deploys go way too
100
- # slow.
101
- old_tasks.each do |task|
102
- puts "stopping task.task_definition_arn #{task.task_definition_arn.inspect}"
103
- ecs.stop_task(cluster: @cluster, task: task.task_arn, reason: reason)
104
- end if @options[:stop_old_tasks]
105
- end
38
+ # Start a thread that will poll for ecs deployments and kill of tasks
39
+ # in old deployments.
40
+ # This must be done in a thread because the stack update process is blocking.
41
+ def stop_old_tasks
42
+ # only works when deployment is blocking
43
+ return unless @options[:wait]
106
44
 
107
- # service is the returned object from aws-sdk not the @service which is just a String.
108
- # Returns [service_name, time_took]
109
- def wait_for_deployment(deployed_service, quiet=false)
110
- start_time = Time.now
111
- deployed_task_name = task_name(deployed_service.task_definition)
112
- puts "Waiting for deployment of task definition #{deployed_task_name.green} to complete" unless quiet
113
- begin
114
- until deployment_complete(deployed_service)
115
- print '.'
116
- sleep 5
45
+ Thread.new do
46
+ stop = Ufo::Stop.new(@service, @options.merge(mute: true))
47
+ while true
48
+ stop.log "checking for old tasks and waiting for 10 seconds"
49
+ stop.run
50
+ sleep 10
117
51
  end
118
- rescue ShipmentOverridden => e
119
- puts "This deployed was overridden by another deploy"
120
- puts e.message
121
52
  end
122
- puts '' unless quiet
123
- took = Time.now - start_time
124
- puts "Time waiting for ECS deployment: #{pretty_time(took).green}." unless quiet
125
- [deployed_service.service_name, took]
126
53
  end
127
54
 
128
- # used for polling
129
- # must pass in a service and cannot use @service for the case of multi_services mode
130
- def find_updated_service(service)
131
- ecs.describe_services(services: [service.service_name], cluster: @cluster).services.first
132
- end
133
-
134
- # aws ecs describe-services --services hi-web-prod --cluster prod-hi
135
- # Passing in the service because we need to capture the deployed task_definition
136
- # that was actually deployed. We use it to pull the describe_services
137
- # until all the paramters we expect upon a completed deployment are updated.
138
- #
139
- def deployment_complete(deployed_service)
140
- deployed_task_definition = deployed_service.task_definition # want the stale task_definition out of the wa
141
- service = find_updated_service(deployed_service) # polling
142
- deployment = service.deployments.first
143
- # Edge case when another deploy superseds this deploy in this case break out of this loop
144
- deployed_task_version = task_version(deployed_task_definition)
145
- current_task_version = task_version(service.task_definition)
146
- if current_task_version > deployed_task_version
147
- raise ShipmentOverridden.new("deployed_task_version was #{deployed_task_version} but task_version is now #{current_task_version}")
148
- end
149
-
150
- (deployment.task_definition == deployed_task_definition &&
151
- deployment.desired_count == deployment.running_count)
55
+ def ensure_log_group_exist
56
+ LogGroup.new(@task_definition, @options).create
152
57
  end
153
58
 
154
- # $ aws ecs create-service --generate-cli-skeleton
155
- # {
156
- # "cluster": "",
157
- # "serviceName": "",
158
- # "taskDefinition": "",
159
- # "desiredCount": 0,
160
- # "loadBalancers": [
161
- # {
162
- # "targetGroupArn": "",
163
- # "containerName": "",
164
- # "containerPort": 0
165
- # }
166
- # ],
167
- # "role": "",
168
- # "clientToken": "",
169
- # "deploymentConfiguration": {
170
- # "maximumPercent": 0,
171
- # "minimumHealthyPercent": 0
172
- # }
173
- # }
174
- #
175
- # If the service needs to be created it will get created with some default settings.
176
- # When does a normal deploy where an update happens only the only thing that ufo
177
- # will update is the task_definition. The other settings should normally be updated with
178
- # the ECS console. `ufo scale` will allow you to updated the desired_count from the
179
- # CLI though.
180
- def create_service
181
- puts "This service #{@service.colorize(:green)} does not yet exist in the #{@cluster.colorize(:green)} cluster. This deploy will create it."
182
- container = container_info(@task_definition)
183
- target_group = target_group_prompt(container)
184
-
185
- message = "#{@service} service created on #{@cluster} cluster"
186
- if @options[:noop]
187
- message = "NOOP #{message}"
188
- else
189
- options = {
190
- cluster: @cluster,
191
- service_name: @service,
192
- task_definition: @task_definition
193
- }
194
- options = options.merge(default_params[:create_service])
195
- unless target_group.nil? || target_group.empty?
196
- add_load_balancer!(container, options, target_group)
197
- end
198
-
199
- puts "Creating ECS service with params:"
200
- display_params(options)
201
- show_aws_cli_command(:create, options)
202
- response = ecs.create_service(options)
203
- service = response.service # must set service here since this might never be called if @wait_for_deployment is false
204
- end
205
-
206
- puts message unless @options[:mute]
207
- service
59
+ def deploy_stack
60
+ options = @options.merge(
61
+ service: @service,
62
+ task_definition: @task_definition,
63
+ )
64
+ stack = Stack.new(options)
65
+ stack.deploy
208
66
  end
209
67
 
210
68
  def show_aws_cli_command(action, params)
@@ -224,143 +82,6 @@ module Ufo
224
82
  puts " aws ecs #{action}-service --cli-input-json #{file_path}".colorize(:green)
225
83
  end
226
84
 
227
- # $ aws ecs update-service --generate-cli-skeleton
228
- # {
229
- # "cluster": "",
230
- # "service": "",
231
- # "taskDefinition": "",
232
- # "desiredCount": 0,
233
- # "deploymentConfiguration": {
234
- # "maximumPercent": 0,
235
- # "minimumHealthyPercent": 0
236
- # }
237
- # }
238
- # Only thing we want to change is the task-definition
239
- def update_service(ecs_service)
240
- message = "#{ecs_service.service_name} service updated on #{ecs_service.cluster_name} cluster with task #{@task_definition}"
241
- if @options[:noop]
242
- message = "NOOP #{message}"
243
- else
244
- params = {
245
- cluster: ecs_service.cluster_arn, # can use the cluster name also since it is unique
246
- service: ecs_service.service_arn, # can use the service name also since it is unique
247
- task_definition: @task_definition
248
- }
249
- params = params.merge(default_params[:update_service] || {})
250
- puts "Updating ECS service with params:"
251
- display_params(params)
252
- show_aws_cli_command(:update, params)
253
-
254
- unless @options[:noop]
255
- response = ecs.update_service(params)
256
- service = response.service # must set service here since this might never be called if @wait_for_deployment is false
257
- end
258
- end
259
-
260
- puts message unless @options[:mute]
261
- service
262
- end
263
-
264
- # Only support Application Load Balancer
265
- # Think there is an AWS bug that complains about not having the LB
266
- # name but you cannot pass both a LB Name and a Target Group.
267
- def add_load_balancer!(container, options, target_group)
268
- options.merge!(
269
- load_balancers: [
270
- {
271
- container_name: container[:name],
272
- container_port: container[:port],
273
- target_group_arn: target_group,
274
- }
275
- ]
276
- )
277
- end
278
-
279
- # Returns the target_group.
280
- # Will only allow an target_group and the service to use a load balancer
281
- # if the container name is "web".
282
- def target_group_prompt(container)
283
- return if @options[:noop]
284
- # If a target_group is provided at the CLI return it right away.
285
- return @options[:target_group] if @options[:target_group]
286
- # Allows skipping the target group prompt.
287
- return unless @target_group_prompt
288
-
289
- # If the container name is web then it is assume that this is a web service that
290
- # needs a target group/elb.
291
- return unless container[:name] == 'web'
292
-
293
- puts "Would you like this service to be associated with an Application Load Balancer?"
294
- puts "If yes, please provide the Application Load Balancer Target Group ARN."
295
- puts "If no, simply press enter."
296
- print "Target Group ARN: "
297
-
298
- arn = $stdin.gets.strip
299
- until arn == '' or validate_target_group(arn)
300
- puts "You have provided an invalid Application Load Balancer Target Group ARN: #{arn}."
301
- puts "It should be in the form: arn:aws:elasticloadbalancing:us-east-1:123456789:targetgroup/target-name/2378947392743"
302
- puts "Please try again or skip adding a Target Group by just pressing enter."
303
- print "Target Group ARN: "
304
- arn = $stdin.gets.strip
305
- end
306
- arn
307
- end
308
-
309
- def validate_target_group(arn)
310
- elb.describe_target_groups(target_group_arns: [arn])
311
- true
312
- rescue Aws::ElasticLoadBalancingV2::Errors::ValidationError
313
- false
314
- end
315
-
316
- # assume only 1 container_definition
317
- # assume only 1 port mapping in that container_defintion
318
- def container_info(task_definition)
319
- Ufo.check_task_definition!(task_definition)
320
- task_definition_path = ".ufo/output/#{task_definition}.json"
321
- task_definition = JSON.load(IO.read(task_definition_path))
322
- container_def = task_definition["containerDefinitions"].first
323
- mappings = container_def["portMappings"]
324
- if mappings
325
- map = mappings.first
326
- port = map["containerPort"]
327
- end
328
- {
329
- name: container_def["name"],
330
- port: port
331
- }
332
- end
333
-
334
- def find_ecs_service
335
- find_all_ecs_services.find { |ecs_service| ecs_service.service_name == @service }
336
- end
337
-
338
- # find all services on a cluster
339
- # yields Ufo::ECS::Service object
340
- def find_all_ecs_services
341
- ecs_services = []
342
- service_arns.each do |service_arn|
343
- ecs_service = Ufo::ECS::Service.new(cluster_arn, service_arn)
344
- yield(ecs_service) if block_given?
345
- ecs_services << ecs_service
346
- end
347
- ecs_services
348
- end
349
-
350
- def service_arns
351
- services = ecs.list_services(cluster: @cluster)
352
- list_service_arns = services.service_arns
353
- while services.next_token != nil
354
- services = ecs.list_services(cluster: @cluster, next_token: services.next_token)
355
- list_service_arns += services.service_arns
356
- end
357
- list_service_arns
358
- end
359
-
360
- def cluster_arn
361
- @cluster_arn ||= ecs_clusters.first.cluster_arn
362
- end
363
-
364
85
  def ensure_cluster_exist
365
86
  cluster = ecs_clusters.first
366
87
  unless cluster && cluster.status == "ACTIVE"
@@ -369,7 +90,7 @@ module Ufo
369
90
  message = "NOOP #{message}"
370
91
  else
371
92
  ecs.create_cluster(cluster_name: @cluster)
372
- # TODO: Aad Waiter logic, sometimes the cluster does not exist by the time
93
+ # TODO: Add Waiter logic, sometimes the cluster does not exist by the time
373
94
  # we create the service
374
95
  end
375
96
 
@@ -380,17 +101,5 @@ module Ufo
380
101
  def ecs_clusters
381
102
  ecs.describe_clusters(clusters: [@cluster]).clusters
382
103
  end
383
-
384
- def task_name(task_definition)
385
- # "arn:aws:ecs:us-east-1:123456789:task-definition/hi-web-prod:72"
386
- # ->
387
- # "task-definition/hi-web-prod:72"
388
- task_definition.split('/').last
389
- end
390
-
391
- def task_version(task_definition)
392
- # "task-definition/hi-web-prod:72" -> 72
393
- task_name(task_definition).split(':').last.to_i
394
- end
395
104
  end
396
105
  end