ufo 4.6.3 → 5.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 +14 -0
- data/docs/_docs/extras/notification-arns.md +21 -0
- data/docs/_docs/helpers.md +6 -4
- data/docs/_docs/iam-roles.md +111 -0
- data/docs/_docs/secrets.md +112 -0
- data/docs/_docs/settings/cluster.md +7 -13
- data/docs/_includes/subnav.html +3 -0
- data/docs/_reference/ufo-deploy.md +1 -2
- data/docs/_reference/ufo-logs.md +1 -1
- data/docs/_reference/ufo-rollback.md +2 -0
- data/docs/_reference/ufo-ship.md +1 -2
- data/docs/_reference/ufo-ships.md +1 -2
- data/docs/_reference/ufo-tasks-build.md +1 -2
- data/lib/template/.secrets +3 -0
- data/lib/template/.ufo/settings.yml.tt +1 -0
- data/lib/template/.ufo/settings/cfn/default.yml.tt +27 -27
- data/lib/template/.ufo/settings/network/default.yml.tt +9 -0
- data/lib/template/.ufo/templates/fargate.json.erb +3 -0
- data/lib/template/.ufo/templates/main.json.erb +3 -0
- data/lib/template/.ufo/variables/base.rb.tt +1 -0
- data/lib/ufo.rb +2 -1
- data/lib/ufo/autoloader.rb +9 -0
- data/lib/ufo/cli.rb +3 -2
- data/lib/ufo/core.rb +1 -9
- data/lib/ufo/docker/cleaner.rb +1 -1
- data/lib/ufo/dsl.rb +6 -1
- data/lib/ufo/dsl/helper.rb +19 -37
- data/lib/ufo/dsl/helper/vars.rb +98 -0
- data/lib/ufo/dsl/outputter.rb +12 -9
- data/lib/ufo/log_group.rb +1 -0
- data/lib/ufo/role/builder.rb +66 -0
- data/lib/ufo/role/dsl.rb +21 -0
- data/lib/ufo/role/registry.rb +24 -0
- data/lib/ufo/rollback.rb +2 -1
- data/lib/ufo/setting/profile.rb +11 -7
- data/lib/ufo/setting/security_groups.rb +22 -0
- data/lib/ufo/settings.rb +20 -0
- data/lib/ufo/stack.rb +24 -24
- data/lib/ufo/stack/builder.rb +26 -0
- data/lib/ufo/stack/builder/base.rb +54 -0
- data/lib/ufo/stack/builder/conditions.rb +23 -0
- data/lib/ufo/stack/builder/outputs.rb +24 -0
- data/lib/ufo/stack/builder/parameters.rb +45 -0
- data/lib/ufo/stack/builder/resources.rb +20 -0
- data/lib/ufo/stack/builder/resources/base.rb +4 -0
- data/lib/ufo/stack/builder/resources/dns.rb +17 -0
- data/lib/ufo/stack/builder/resources/ecs.rb +63 -0
- data/lib/ufo/stack/builder/resources/elb.rb +45 -0
- data/lib/ufo/stack/builder/resources/listener.rb +42 -0
- data/lib/ufo/stack/builder/resources/listener_ssl.rb +16 -0
- data/lib/ufo/stack/builder/resources/roles/base.rb +22 -0
- data/lib/ufo/stack/builder/resources/roles/execution_role.rb +4 -0
- data/lib/ufo/stack/builder/resources/roles/task_role.rb +4 -0
- data/lib/ufo/stack/builder/resources/security_group/base.rb +4 -0
- data/lib/ufo/stack/builder/resources/security_group/ecs.rb +44 -0
- data/lib/ufo/stack/builder/resources/security_group/ecs_rule.rb +25 -0
- data/lib/ufo/stack/builder/resources/security_group/elb.rb +57 -0
- data/lib/ufo/stack/builder/resources/target_group.rb +39 -0
- data/lib/ufo/stack/builder/resources/task_definition.rb +24 -0
- data/lib/ufo/stack/builder/resources/task_definition/reconstructor.rb +49 -0
- data/lib/ufo/stack/context.rb +41 -48
- data/lib/ufo/stack/custom_properties.rb +59 -0
- data/lib/ufo/stack/helper.rb +2 -5
- data/lib/ufo/stack/template_body.rb +13 -0
- data/lib/ufo/task.rb +2 -7
- data/lib/ufo/tasks.rb +1 -1
- data/lib/ufo/tasks/builder.rb +0 -1
- data/lib/ufo/template_scope.rb +1 -66
- data/lib/ufo/utils/squeezer.rb +24 -0
- data/lib/ufo/version.rb +1 -1
- data/spec/fixtures/iam_roles/task_role.rb +17 -0
- data/spec/lib/role/builder_spec.rb +67 -0
- data/spec/lib/role/dsl_spec.rb +12 -0
- data/ufo.gemspec +1 -0
- metadata +57 -3
- data/lib/cfn/stack.yml +0 -283
@@ -15,3 +15,12 @@ elb_subnets: # defaults to same subnets as ecs_subnets when not set
|
|
15
15
|
# ecs_security_groups:
|
16
16
|
# - sg-bbb
|
17
17
|
# - sg-ccc
|
18
|
+
|
19
|
+
# Also supports extra security groups specific to each ECS service
|
20
|
+
# ecs_security_groups:
|
21
|
+
# demo-web:
|
22
|
+
# - sg-bbb
|
23
|
+
# - sg-ccc
|
24
|
+
# demo-worker:
|
25
|
+
# - sg-bbb
|
26
|
+
# - sg-ccc
|
@@ -2,6 +2,7 @@
|
|
2
2
|
# More info on how variables work: http://ufoships.com/docs/variables/
|
3
3
|
@image = helper.full_image_name # includes the git sha tongueroo/demo-ufo:ufo-[sha].
|
4
4
|
@environment = helper.env_file(".env")
|
5
|
+
@secrets = helper.secrets_file(".secrets")
|
5
6
|
<% if @options[:launch_type] == "fargate" -%>
|
6
7
|
# Ensure that the cpu and memory values are a supported combination by Fargate.
|
7
8
|
# More info: https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-cpu-memory-error.html"
|
data/lib/ufo.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
$stdout.sync = true unless ENV["UFO_STDOUT_SYNC"] == "0"
|
2
2
|
|
3
3
|
$:.unshift(File.expand_path('../', __FILE__))
|
4
|
-
require '
|
4
|
+
require 'active_support/core_ext/class'
|
5
|
+
require 'deep_merge/rails_compat'
|
5
6
|
require 'fileutils'
|
6
7
|
require 'memoist'
|
7
8
|
require 'rainbow/ext/string'
|
data/lib/ufo/autoloader.rb
CHANGED
@@ -14,8 +14,17 @@ module Ufo
|
|
14
14
|
loader = Zeitwerk::Loader.new
|
15
15
|
loader.inflector = Inflector.new
|
16
16
|
loader.push_dir(File.dirname(__dir__)) # lib
|
17
|
+
|
18
|
+
helpers = "#{ufo_root}/.ufo/helpers"
|
19
|
+
loader.push_dir(helpers) if File.exist?(helpers) # project helpers
|
20
|
+
|
17
21
|
loader.setup
|
18
22
|
end
|
23
|
+
|
24
|
+
# Autoloader runs so early that Ufo.root is not available, so we must declare it here
|
25
|
+
def ufo_root
|
26
|
+
ENV['UFO_ROOT'] || '.'
|
27
|
+
end
|
19
28
|
end
|
20
29
|
end
|
21
30
|
end
|
data/lib/ufo/cli.rb
CHANGED
@@ -37,11 +37,11 @@ module Ufo
|
|
37
37
|
option :elb, desc: "Decides to create elb, not create elb or use existing target group."
|
38
38
|
option :elb_eip_ids, type: :array, desc: "EIP Allocation ids to use for network load balancer."
|
39
39
|
option :elb_type, desc: "ELB type: application or network. Keep current deployed elb type when not specified."
|
40
|
-
option :pretty, type: :boolean, default: true, desc: "Pretty format the json for the task definitions"
|
41
40
|
option :scheduling_strategy, desc: "Scheduling strategy to use for the service. IE: replica, daemon"
|
42
41
|
option :stop_old_tasks, type: :boolean, default: false, desc: "Stop old tasks as part of deployment to speed it up"
|
43
42
|
option :task, desc: "ECS task name, to override the task name convention."
|
44
43
|
option :wait, type: :boolean, desc: "Wait for deployment to complete", default: true
|
44
|
+
option :image_override, desc: "Override image in task definition for quick testing"
|
45
45
|
end
|
46
46
|
|
47
47
|
desc "deploy SERVICE", "Deploy task definition to ECS service without re-building the definition."
|
@@ -75,6 +75,7 @@ module Ufo
|
|
75
75
|
|
76
76
|
desc "rollback SERVICE VERSION", "Rolls back to older task definition."
|
77
77
|
long_desc Help.text(:rollback)
|
78
|
+
option :wait, type: :boolean, desc: "Wait for deployment to complete", default: true
|
78
79
|
def rollback(service=:current, version)
|
79
80
|
service = service == :current ? Current.service! : service
|
80
81
|
rollback = Rollback.new(service, options.merge(version: version))
|
@@ -191,7 +192,7 @@ module Ufo
|
|
191
192
|
long_desc Help.text(:logs)
|
192
193
|
option :follow, default: true, type: :boolean, desc: " Whether to continuously poll for new logs. To exit from this mode, use Control-C."
|
193
194
|
option :since, desc: "From what time to begin displaying logs. By default, logs will be displayed starting from 1 minutes in the past. The value provided can be an ISO 8601 timestamp or a relative time."
|
194
|
-
option :format, default: "
|
195
|
+
option :format, default: "detailed", desc: "The format to display the logs. IE: detailed or short. With detailed, the log stream name is also shown."
|
195
196
|
option :filter_pattern, desc: "The filter pattern to use. If not provided, all the events are matched"
|
196
197
|
def logs(service=:current)
|
197
198
|
Logs.new(service, options).run
|
data/lib/ufo/core.rb
CHANGED
@@ -4,6 +4,7 @@ require 'yaml'
|
|
4
4
|
module Ufo
|
5
5
|
module Core
|
6
6
|
extend Memoist
|
7
|
+
include Ufo::Settings
|
7
8
|
|
8
9
|
def check_task_definition!(task_definition)
|
9
10
|
task_definition_path = "#{Ufo.root}/.ufo/output/#{task_definition}.json"
|
@@ -49,15 +50,6 @@ module Ufo
|
|
49
50
|
end
|
50
51
|
end
|
51
52
|
|
52
|
-
def settings
|
53
|
-
Setting.new.data
|
54
|
-
end
|
55
|
-
memoize :settings
|
56
|
-
|
57
|
-
def cfn_profile
|
58
|
-
settings[:cfn_profile] || "default"
|
59
|
-
end
|
60
|
-
|
61
53
|
def check_ufo_project!
|
62
54
|
check_path = "#{Ufo.root}/.ufo/settings.yml"
|
63
55
|
unless File.exist?(check_path)
|
data/lib/ufo/docker/cleaner.rb
CHANGED
data/lib/ufo/dsl.rb
CHANGED
@@ -2,6 +2,8 @@ require 'ostruct'
|
|
2
2
|
|
3
3
|
module Ufo
|
4
4
|
class DSL
|
5
|
+
extend Memoist
|
6
|
+
|
5
7
|
def initialize(template_definitions_path, options={})
|
6
8
|
@template_definitions_path = template_definitions_path
|
7
9
|
@options = options
|
@@ -85,7 +87,10 @@ module Ufo
|
|
85
87
|
end
|
86
88
|
|
87
89
|
def helper
|
88
|
-
Helper.new
|
90
|
+
helper = Helper.new
|
91
|
+
helper.add_project_helpers
|
92
|
+
helper
|
89
93
|
end
|
94
|
+
memoize :helper
|
90
95
|
end
|
91
96
|
end
|
data/lib/ufo/dsl/helper.rb
CHANGED
@@ -6,11 +6,20 @@
|
|
6
6
|
# Simply aggregates a bunch of variables that is useful for the task_definition.
|
7
7
|
module Ufo
|
8
8
|
class DSL
|
9
|
-
# provides some helperally context variables
|
10
9
|
class Helper
|
11
10
|
include Ufo::Util
|
12
11
|
extend Memoist
|
13
12
|
|
13
|
+
# Add helpers from .ufo/helpers folder
|
14
|
+
def add_project_helpers
|
15
|
+
helpers_dir = "#{Ufo.root}/.ufo/helpers"
|
16
|
+
Dir.glob("#{helpers_dir}/**/*").each do |path|
|
17
|
+
next unless File.file?(path)
|
18
|
+
klass = path.gsub(%r{.*\.ufo/helpers/},'').sub(".rb",'').camelize
|
19
|
+
self.class.send(:include, klass.constantize)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
14
23
|
##############
|
15
24
|
# helper variables
|
16
25
|
def dockerfile_port
|
@@ -27,48 +36,21 @@ module Ufo
|
|
27
36
|
|
28
37
|
#############
|
29
38
|
# helper methods
|
30
|
-
def
|
31
|
-
|
32
|
-
lines.map do |line|
|
33
|
-
key,*value = line.strip.split("=").map do |x|
|
34
|
-
remove_surrounding_quotes(x.strip)
|
35
|
-
end
|
36
|
-
value = value.join('=')
|
37
|
-
{
|
38
|
-
name: key,
|
39
|
-
value: value,
|
40
|
-
}
|
41
|
-
end
|
39
|
+
def env(text)
|
40
|
+
Vars.new(text: text).env
|
42
41
|
end
|
42
|
+
alias_method :env_vars, :env
|
43
43
|
|
44
|
-
def
|
45
|
-
|
46
|
-
s.sub(/^["]/, '').gsub(/["]$/,'') # remove surrounding double quotes
|
47
|
-
elsif s =~ /^'/ && s =~ /'$/
|
48
|
-
s.sub(/^[']/, '').gsub(/[']$/,'') # remove surrounding single quotes
|
49
|
-
else
|
50
|
-
s
|
51
|
-
end
|
44
|
+
def env_file(path)
|
45
|
+
Vars.new(file: path).env
|
52
46
|
end
|
53
47
|
|
54
|
-
def
|
55
|
-
|
56
|
-
# remove comment at the end of the line
|
57
|
-
lines.map! { |l| l.sub(/\s+#.*/,'').strip }
|
58
|
-
# filter out commented lines
|
59
|
-
lines = lines.reject { |l| l =~ /(^|\s)#/i }
|
60
|
-
# filter out empty lines
|
61
|
-
lines = lines.reject { |l| l.strip.empty? }
|
48
|
+
def secrets(text)
|
49
|
+
Vars.new(text: text, secrets: true).secrets
|
62
50
|
end
|
63
51
|
|
64
|
-
def
|
65
|
-
|
66
|
-
unless File.exist?(full_path)
|
67
|
-
puts "The #{full_path} env file could not be found. Are you sure it exists?"
|
68
|
-
exit 1
|
69
|
-
end
|
70
|
-
text = IO.read(full_path)
|
71
|
-
env_vars(text)
|
52
|
+
def secrets_file(path)
|
53
|
+
Vars.new(file: path, secrets: true).secrets
|
72
54
|
end
|
73
55
|
|
74
56
|
def current_region
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require "aws_data"
|
2
|
+
|
3
|
+
class Ufo::DSL::Helper
|
4
|
+
class Vars
|
5
|
+
extend Memoist
|
6
|
+
|
7
|
+
def initialize(options={})
|
8
|
+
# use either file or text. text takes higher precedence
|
9
|
+
@file = options[:file]
|
10
|
+
@text = options[:text]
|
11
|
+
@secrets = options[:secrets] # true or false
|
12
|
+
end
|
13
|
+
|
14
|
+
def content
|
15
|
+
@text || read(@file)
|
16
|
+
end
|
17
|
+
|
18
|
+
def read(path)
|
19
|
+
full_path = "#{Ufo.root}/#{path}"
|
20
|
+
unless File.exist?(full_path)
|
21
|
+
puts "The #{full_path} env file could not be found. Are you sure it exists?"
|
22
|
+
exit 1
|
23
|
+
end
|
24
|
+
IO.read(full_path)
|
25
|
+
end
|
26
|
+
|
27
|
+
def env
|
28
|
+
lines = filtered_lines(content)
|
29
|
+
lines.map do |line|
|
30
|
+
key,*value = line.strip.split("=").map do |x|
|
31
|
+
remove_surrounding_quotes(x.strip)
|
32
|
+
end
|
33
|
+
value = value.join('=')
|
34
|
+
{
|
35
|
+
name: key,
|
36
|
+
value: value,
|
37
|
+
}
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def secrets
|
42
|
+
secrets = env
|
43
|
+
secrets.map do |item|
|
44
|
+
value = item.delete(:value)
|
45
|
+
item[:valueFrom] = substitute(expand_secret(value))
|
46
|
+
end
|
47
|
+
secrets
|
48
|
+
end
|
49
|
+
|
50
|
+
def expand_secret(value)
|
51
|
+
case value
|
52
|
+
when /^ssm:/i
|
53
|
+
value.sub(/^ssm:/i, "arn:aws:ssm:#{region}:#{account}:parameter/")
|
54
|
+
when /^secretsmanager:/i
|
55
|
+
value.sub(/^secretsmanager:/i, "arn:aws:secretsmanager:#{region}:#{account}:secret:")
|
56
|
+
else
|
57
|
+
value # assume full arn has been passed
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def substitute(value)
|
62
|
+
value.gsub(":UFO_ENV", Ufo.env)
|
63
|
+
end
|
64
|
+
|
65
|
+
def remove_surrounding_quotes(s)
|
66
|
+
if s =~ /^"/ && s =~ /"$/
|
67
|
+
s.sub(/^["]/, '').gsub(/["]$/,'') # remove surrounding double quotes
|
68
|
+
elsif s =~ /^'/ && s =~ /'$/
|
69
|
+
s.sub(/^[']/, '').gsub(/[']$/,'') # remove surrounding single quotes
|
70
|
+
else
|
71
|
+
s
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def filtered_lines(content)
|
76
|
+
lines = content.split("\n")
|
77
|
+
# remove comment at the end of the line
|
78
|
+
lines.map! { |l| l.sub(/\s+#.*/,'').strip }
|
79
|
+
# filter out commented lines
|
80
|
+
lines = lines.reject { |l| l =~ /(^|\s)#/i }
|
81
|
+
# filter out empty lines
|
82
|
+
lines = lines.reject { |l| l.strip.empty? }
|
83
|
+
end
|
84
|
+
|
85
|
+
def aws_data
|
86
|
+
AwsData.new
|
87
|
+
end
|
88
|
+
memoize :aws_data
|
89
|
+
|
90
|
+
def region
|
91
|
+
aws_data.region
|
92
|
+
end
|
93
|
+
|
94
|
+
def account
|
95
|
+
aws_data.account
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
data/lib/ufo/dsl/outputter.rb
CHANGED
@@ -5,7 +5,6 @@ module Ufo
|
|
5
5
|
@name = name
|
6
6
|
@erb_result = erb_result
|
7
7
|
@options = options
|
8
|
-
@pretty = options[:pretty].nil? ? true : options[:pretty]
|
9
8
|
end
|
10
9
|
|
11
10
|
def write
|
@@ -16,25 +15,29 @@ module Ufo
|
|
16
15
|
path = "#{output_path}/#{@name}.json".sub(/^\.\//,'')
|
17
16
|
puts " #{path}" unless @options[:quiet]
|
18
17
|
validate(@erb_result, path)
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
File.open(path, 'w') {|f| f.write(
|
18
|
+
data = JSON.parse(@erb_result)
|
19
|
+
override_image(data)
|
20
|
+
json = JSON.pretty_generate(data)
|
21
|
+
File.open(path, 'w') {|f| f.write(json) }
|
22
|
+
end
|
23
|
+
|
24
|
+
def override_image(data)
|
25
|
+
return data unless @options[:image_override]
|
26
|
+
data["containerDefinitions"].each do |container_definition|
|
27
|
+
container_definition["image"] = @options[:image_override]
|
28
|
+
end
|
23
29
|
end
|
24
30
|
|
25
31
|
def validate(json, path)
|
26
32
|
begin
|
27
33
|
JSON.parse(json)
|
28
34
|
rescue JSON::ParserError => e
|
35
|
+
puts "#{e.class}: #{e.message}"
|
29
36
|
puts "Invalid json. Output written to #{path} for debugging".color(:red)
|
30
37
|
File.open(path, 'w') {|f| f.write(json) }
|
31
38
|
exit 1
|
32
39
|
end
|
33
40
|
end
|
34
|
-
|
35
|
-
def output_json(json)
|
36
|
-
@options[:pretty] ? JSON.pretty_generate(JSON.parse(json)) : json
|
37
|
-
end
|
38
41
|
end
|
39
42
|
end
|
40
43
|
end
|
data/lib/ufo/log_group.rb
CHANGED
@@ -11,6 +11,7 @@ module Ufo
|
|
11
11
|
def create
|
12
12
|
puts "Ensuring log group for #{@task_definition.color(:green)} task definition exists"
|
13
13
|
return if @options[:noop]
|
14
|
+
return if @options[:rollback] # dont need to create log group because previously deployed
|
14
15
|
|
15
16
|
Ufo.check_task_definition!(@task_definition)
|
16
17
|
task_def = JSON.load(IO.read(task_def_path))
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module Ufo::Role
|
2
|
+
class Builder
|
3
|
+
def initialize(role_type)
|
4
|
+
@role_type = role_type
|
5
|
+
end
|
6
|
+
|
7
|
+
def build
|
8
|
+
resource(policies, managed_policy_arns)
|
9
|
+
end
|
10
|
+
|
11
|
+
def build?
|
12
|
+
!!(policies || managed_policy_arns)
|
13
|
+
end
|
14
|
+
|
15
|
+
def policies
|
16
|
+
items = Registry.policies[@role_type] # Array of Arrays
|
17
|
+
return unless items && !items.empty?
|
18
|
+
|
19
|
+
items.map do |item|
|
20
|
+
policy_name, statements = item # first element has policy name, second element has statements
|
21
|
+
{
|
22
|
+
PolicyName: policy_name,
|
23
|
+
PolicyDocument: {
|
24
|
+
Version: "2012-10-17",
|
25
|
+
Statement: statements
|
26
|
+
}
|
27
|
+
}
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def managed_policy_arns
|
32
|
+
items = Registry.managed_policies[@role_type] # Array of Arrays
|
33
|
+
return unless items && !items.empty?
|
34
|
+
|
35
|
+
items.map do |item|
|
36
|
+
item.include?('iam::aws:policy') ? item : "arn:aws:iam::aws:policy/#{item}"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def resource(policies, managed_policy_arns)
|
41
|
+
properties = {
|
42
|
+
AssumeRolePolicyDocument: {
|
43
|
+
Version: "2012-10-17",
|
44
|
+
Statement: [
|
45
|
+
{
|
46
|
+
Effect: "Allow",
|
47
|
+
Principal: {
|
48
|
+
Service: "ecs-tasks.amazonaws.com"
|
49
|
+
},
|
50
|
+
Action: "sts:AssumeRole"
|
51
|
+
}
|
52
|
+
]
|
53
|
+
},
|
54
|
+
}
|
55
|
+
properties[:Policies] = policies if policies
|
56
|
+
properties[:ManagedPolicyArns] = managed_policy_arns if managed_policy_arns
|
57
|
+
|
58
|
+
attrs = {
|
59
|
+
Type: "AWS::IAM::Role",
|
60
|
+
Properties: properties
|
61
|
+
}
|
62
|
+
|
63
|
+
attrs.deep_stringify_keys
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|