ufo 3.5.7 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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