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.
- 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
|