ufo 4.6.3 → 5.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 +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
|