ufo 3.5.7 → 4.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +24 -0
- data/Gemfile.lock +16 -10
- data/README.md +12 -13
- data/docs/_config.yml +1 -1
- data/docs/_docs/auto-completion.md +4 -4
- data/docs/_docs/automated-cleanup.md +1 -1
- data/docs/_docs/conventions.md +7 -7
- data/docs/_docs/customize-cloudformation.md +36 -0
- data/docs/_docs/faq.md +9 -7
- data/docs/_docs/fargate.md +102 -0
- data/docs/_docs/helpers.md +3 -3
- data/docs/_docs/load-balancer.md +72 -0
- data/docs/_docs/migrations.md +2 -2
- data/docs/_docs/next-steps.md +2 -2
- data/docs/_docs/params.md +12 -41
- data/docs/_docs/route53-support.md +28 -0
- data/docs/_docs/run-in-pieces.md +2 -2
- data/docs/_docs/security-groups.md +54 -0
- data/docs/_docs/settings-cfn.md +11 -0
- data/docs/_docs/settings-network.md +34 -0
- data/docs/_docs/settings.md +18 -15
- data/docs/_docs/single-task.md +3 -3
- data/docs/_docs/ssl-support.md +42 -0
- data/docs/_docs/structure.md +5 -1
- data/docs/_docs/stuck-cloudformation.md +30 -0
- data/docs/_docs/tutorial-ufo-docker-build.md +19 -31
- data/docs/_docs/tutorial-ufo-init.md +16 -12
- data/docs/_docs/tutorial-ufo-ship.md +50 -54
- data/docs/_docs/tutorial-ufo-ships.md +9 -7
- data/docs/_docs/tutorial-ufo-tasks-build.md +26 -17
- data/docs/_docs/ufo-current.md +50 -0
- data/docs/_docs/ufo-env-extra.md +21 -0
- data/docs/_docs/ufo-env.md +6 -13
- data/docs/_docs/ufo-tasks-register.md +3 -3
- data/docs/_docs/upgrade4.md +49 -0
- data/docs/_docs/variables.md +5 -5
- data/docs/_docs/why-cloudformation.md +22 -0
- data/docs/_includes/about.html +1 -1
- data/docs/_includes/cfn-customize.md +39 -0
- data/docs/_includes/commands.html +6 -6
- data/docs/_includes/css/ufo.css +1 -0
- data/docs/_includes/example.html +13 -13
- data/docs/_includes/reference.md +1 -1
- data/docs/_includes/subnav.html +22 -5
- data/docs/_includes/ufo-ship-options.md +7 -6
- data/docs/_reference/ufo-apps.md +36 -0
- data/docs/_reference/ufo-cancel.md +24 -0
- data/docs/_reference/ufo-completion.md +1 -1
- data/docs/_reference/ufo-completion_script.md +1 -1
- data/docs/_reference/ufo-current.md +93 -0
- data/docs/_reference/ufo-deploy.md +18 -17
- data/docs/_reference/ufo-destroy.md +6 -4
- data/docs/_reference/ufo-docker-base.md +7 -7
- data/docs/_reference/ufo-docker-build.md +9 -9
- data/docs/_reference/ufo-docker-clean.md +8 -8
- data/docs/_reference/ufo-docker-name.md +4 -4
- data/docs/_reference/ufo-docker.md +4 -2
- data/docs/_reference/ufo-init.md +31 -20
- data/docs/_reference/ufo-network-help.md +15 -0
- data/docs/_reference/ufo-network-init.md +38 -0
- data/docs/_reference/ufo-network.md +26 -0
- data/docs/_reference/ufo-ps.md +53 -0
- data/docs/_reference/ufo-releases.md +40 -0
- data/docs/_reference/ufo-resources.md +44 -0
- data/docs/_reference/ufo-rollback.md +59 -0
- data/docs/_reference/ufo-scale.md +23 -3
- data/docs/_reference/ufo-ship.md +54 -27
- data/docs/_reference/ufo-ships.md +17 -26
- data/docs/_reference/ufo-stop.md +31 -0
- data/docs/_reference/ufo-task.md +15 -16
- data/docs/_reference/ufo-tasks-build.md +10 -10
- data/docs/_reference/ufo-tasks-register.md +3 -3
- data/docs/_reference/ufo-tasks.md +1 -1
- data/docs/_reference/ufo-upgrade-help.md +15 -0
- data/docs/_reference/ufo-upgrade-v2to3.md +15 -0
- data/docs/_reference/ufo-upgrade-v3_3to3_4.md +15 -0
- data/docs/_reference/ufo-upgrade-v3to4.md +27 -0
- data/docs/_reference/ufo-upgrade.md +28 -0
- data/docs/_reference/ufo-version.md +1 -1
- data/docs/articles.md +2 -2
- data/docs/docs.md +1 -1
- data/docs/img/docs/cloudformation-resources.png +0 -0
- data/docs/img/tutorials/ecs-console-task-definitions.png +0 -0
- data/docs/img/tutorials/ecs-console-ufo-ship.png +0 -0
- data/docs/img/tutorials/ecs-console-ufo-ships.png +0 -0
- data/docs/quick-start.md +21 -9
- data/docs/reference.md +10 -2
- data/exe/ufo +1 -1
- data/lib/cfn/stack.yml +259 -0
- data/lib/template/.ufo/params.yml.tt +21 -60
- data/lib/template/.ufo/settings.yml.tt +6 -1
- data/lib/template/.ufo/settings/cfn/default.yml.tt +55 -0
- data/lib/template/.ufo/settings/network/default.yml.tt +18 -0
- data/lib/template/.ufo/task_definitions.rb.tt +7 -6
- data/lib/template/.ufo/templates/fargate.json.erb +1 -1
- data/lib/template/.ufo/templates/main.json.erb +1 -0
- data/lib/template/.ufo/variables/base.rb.tt +5 -2
- data/lib/template/Dockerfile +10 -15
- data/lib/template/bin/deploy.tt +2 -2
- data/lib/ufo.rb +29 -20
- data/lib/ufo/apps.rb +49 -0
- data/lib/ufo/apps/cfn_map.rb +70 -0
- data/lib/ufo/apps/service.rb +56 -0
- data/lib/ufo/aws_service.rb +15 -6
- data/lib/ufo/base.rb +32 -0
- data/lib/ufo/cancel.rb +23 -0
- data/lib/ufo/cli.rb +91 -27
- data/lib/ufo/core.rb +35 -3
- data/lib/ufo/current.rb +104 -0
- data/lib/ufo/destroy.rb +10 -41
- data/lib/ufo/docker/builder.rb +5 -4
- data/lib/ufo/docker/cleaner.rb +1 -1
- data/lib/ufo/docker/pusher.rb +2 -2
- data/lib/ufo/ecr/cleaner.rb +1 -1
- data/lib/ufo/help/apps.md +12 -0
- data/lib/ufo/help/balancer.md +3 -0
- data/lib/ufo/help/current.md +65 -0
- data/lib/ufo/help/deploy.md +4 -4
- data/lib/ufo/help/destroy.md +3 -3
- data/lib/ufo/help/docker.md +3 -1
- data/lib/ufo/help/docker/base.md +7 -7
- data/lib/ufo/help/docker/build.md +9 -9
- data/lib/ufo/help/docker/clean.md +8 -8
- data/lib/ufo/help/docker/name.md +4 -4
- data/lib/ufo/help/help.md +5 -0
- data/lib/ufo/help/init.md +24 -16
- data/lib/ufo/help/network/init.md +13 -0
- data/lib/ufo/help/ps.md +27 -0
- data/lib/ufo/help/releases.md +16 -0
- data/lib/ufo/help/resources.md +20 -0
- data/lib/ufo/help/rollback.md +35 -0
- data/lib/ufo/help/scale.md +22 -2
- data/lib/ufo/help/ship.md +40 -14
- data/lib/ufo/help/ships.md +4 -13
- data/lib/ufo/help/stop.md +7 -0
- data/lib/ufo/help/task.md +9 -9
- data/lib/ufo/help/tasks/build.md +10 -10
- data/lib/ufo/help/tasks/register.md +3 -3
- data/lib/ufo/help/upgrade/v3to4.md +3 -0
- data/lib/ufo/info.rb +62 -0
- data/lib/ufo/init.rb +36 -23
- data/lib/ufo/log_group.rb +2 -1
- data/lib/ufo/network.rb +24 -0
- data/lib/ufo/network/fetch.rb +41 -0
- data/lib/ufo/network/helper.rb +23 -0
- data/lib/ufo/network/init.rb +26 -0
- data/lib/ufo/param.rb +5 -5
- data/lib/ufo/ps.rb +102 -0
- data/lib/ufo/ps/task.rb +78 -0
- data/lib/ufo/releases.rb +14 -0
- data/lib/ufo/rollback.rb +53 -0
- data/lib/ufo/scale.rb +6 -12
- data/lib/ufo/sequence.rb +7 -0
- data/lib/ufo/setting.rb +7 -6
- data/lib/ufo/setting/profile.rb +24 -0
- data/lib/ufo/ship.rb +35 -326
- data/lib/ufo/stack.rb +203 -0
- data/lib/ufo/stack/context.rb +242 -0
- data/lib/ufo/stack/helper.rb +28 -0
- data/lib/ufo/stack/status.rb +195 -0
- data/lib/ufo/stop.rb +47 -0
- data/lib/ufo/task.rb +96 -15
- data/lib/ufo/tasks/register.rb +1 -1
- data/lib/ufo/template_scope.rb +81 -7
- data/lib/ufo/upgrade.rb +32 -0
- data/lib/ufo/{upgrade3.rb → upgrade/upgrade3.rb} +1 -1
- data/lib/ufo/{upgrade33_to_34.rb → upgrade/upgrade33to34.rb} +2 -2
- data/lib/ufo/upgrade/upgrade4.rb +161 -0
- data/lib/ufo/util.rb +19 -6
- data/lib/ufo/version.rb +1 -1
- data/spec/fixtures/apps/describe_services.json +96 -0
- data/spec/fixtures/cfn/stack-events-complete.json +1080 -0
- data/spec/fixtures/cfn/stack-events-in-progress.json +1080 -0
- data/spec/fixtures/cfn/stack-events-update-rollback-complete.json +1086 -0
- data/spec/fixtures/deployments.json +50 -0
- data/spec/fixtures/ps/describe_tasks.json +58 -0
- data/spec/fixtures/settings.yml +2 -0
- data/spec/lib/apps_spec.rb +20 -0
- data/spec/lib/cli_spec.rb +4 -4
- data/spec/lib/ps_spec.rb +14 -0
- data/spec/lib/setting_spec.rb +2 -1
- data/spec/lib/ship_spec.rb +6 -30
- data/spec/lib/stack/status_spec.rb +76 -0
- data/spec/lib/stop_spec.rb +13 -0
- data/spec/lib/task_spec.rb +5 -2
- data/spec/spec_helper.rb +1 -1
- data/ufo.gemspec +2 -0
- metadata +120 -6
- data/docs/_reference/ufo-upgrade3.md +0 -23
- data/docs/_reference/ufo-upgrade3_3_to_3_4.md +0 -23
data/lib/ufo/releases.rb
ADDED
@@ -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
|
data/lib/ufo/rollback.rb
ADDED
@@ -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
|
data/lib/ufo/scale.rb
CHANGED
@@ -1,33 +1,27 @@
|
|
1
1
|
module Ufo
|
2
|
-
class Scale
|
3
|
-
|
4
|
-
include AwsService
|
2
|
+
class Scale < Base
|
3
|
+
delegate :service, to: :info
|
5
4
|
|
6
5
|
def initialize(service, count, options={})
|
7
|
-
|
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 #{@
|
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:
|
17
|
+
service: service.service_name,
|
21
18
|
cluster: @cluster,
|
22
19
|
desired_count: @count
|
23
20
|
)
|
24
|
-
puts "Scale #{@
|
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
|
data/lib/ufo/sequence.rb
CHANGED
@@ -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
|
|
data/lib/ufo/setting.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
data/lib/ufo/ship.rb
CHANGED
@@ -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
|
-
|
9
|
-
|
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 = "
|
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
|
-
|
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
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
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
|
-
#
|
82
|
-
#
|
83
|
-
|
84
|
-
|
85
|
-
|
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
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
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
|
-
|
129
|
-
|
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
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
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:
|
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
|