ufo 6.1.5 → 6.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/.cody/acceptance/bin/build.sh +1 -1
  3. data/.github/ISSUE_TEMPLATE/bug_report.md +84 -0
  4. data/.github/ISSUE_TEMPLATE/documentation.md +12 -0
  5. data/.github/ISSUE_TEMPLATE/feature_request.md +64 -0
  6. data/.github/ISSUE_TEMPLATE/question.md +14 -0
  7. data/.github/ISSUE_TEMPLATE.md +7 -0
  8. data/.github/PULL_REQUEST_TEMPLATE.md +50 -0
  9. data/CHANGELOG.md +11 -0
  10. data/lib/ufo/aws_services/concerns.rb +55 -0
  11. data/lib/ufo/aws_services.rb +9 -40
  12. data/lib/ufo/cfn/stack/builder.rb +2 -1
  13. data/lib/ufo/cfn/stack/params.rb +2 -1
  14. data/lib/ufo/cfn/stack/status.rb +1 -1
  15. data/lib/ufo/cfn/stack.rb +4 -4
  16. data/lib/ufo/cli/destroy.rb +1 -1
  17. data/lib/ufo/cli/ps/errors.rb +40 -0
  18. data/lib/ufo/command.rb +17 -7
  19. data/lib/ufo/config.rb +47 -3
  20. data/lib/ufo/docker/builder.rb +1 -1
  21. data/lib/ufo/docker/compiler.rb +3 -3
  22. data/lib/ufo/docker/state/base.rb +14 -0
  23. data/lib/ufo/docker/state/bucket.rb +2 -0
  24. data/lib/ufo/docker/state/file.rb +52 -0
  25. data/lib/ufo/docker/state/s3.rb +80 -0
  26. data/lib/ufo/docker/state.rb +16 -50
  27. data/lib/ufo/info.rb +1 -1
  28. data/lib/ufo/layering/layer.rb +27 -37
  29. data/lib/ufo/s3/aws_setup.rb +17 -0
  30. data/lib/ufo/s3/bucket.rb +174 -0
  31. data/lib/ufo/s3/rollback.rb +52 -0
  32. data/lib/ufo/task_definition/erb.rb +2 -2
  33. data/lib/ufo/task_definition/helpers/{core.rb → docker.rb} +9 -24
  34. data/lib/ufo/task_definition/helpers/{aws_helper.rb → vars/aws_helper.rb} +2 -1
  35. data/lib/ufo/task_definition/helpers/vars/builder.rb +124 -0
  36. data/lib/ufo/task_definition/helpers/vars.rb +11 -114
  37. data/lib/ufo/upgrade/upgrade4.rb +0 -9
  38. data/lib/ufo/version.rb +1 -1
  39. data/ufo.gemspec +1 -0
  40. metadata +33 -4
data/lib/ufo/config.rb CHANGED
@@ -85,6 +85,9 @@ module Ufo
85
85
  config.exec.command = "/bin/bash" # aws ecs execute-command cli
86
86
  config.exec.enabled = true # EcsService EnableExecuteCommand
87
87
 
88
+ config.layering = ActiveSupport::OrderedOptions.new
89
+ config.layering.show = show_layers?
90
+
88
91
  config.log = ActiveSupport::OrderedOptions.new
89
92
  config.log.root = Ufo.log_root
90
93
  config.logger = ufo_logger
@@ -114,7 +117,10 @@ module Ufo
114
117
  config.ship.docker.quiet = false # only affects ufo ship docker commands output
115
118
 
116
119
  config.state = ActiveSupport::OrderedOptions.new
120
+ config.state.bucket = nil # Set to use existing bucket. When not set ufo creates a managed s3 bucket
121
+ config.state.managed = true # false will disable creation of managed bucket entirely
117
122
  config.state.reminder = true
123
+ config.state.storage = "s3" # s3 or file
118
124
 
119
125
  config.waf = ActiveSupport::OrderedOptions.new
120
126
  config.waf.web_acl_arn = nil
@@ -157,15 +163,52 @@ module Ufo
157
163
  role = layer_levels(".ufo/config/#{Ufo.app}/#{Ufo.role}")
158
164
  layers += root + env + role
159
165
  end
160
- # load_project_config gets called so early that logger is not yet configured. use puts
161
- puts "Config layers:" if ENV['UFO_SHOW_ALL_LAYERS']
166
+ # load_project_config gets called so early that logger is not yet configured.
167
+ # Cannot use Ufo.config yet and cannot use logger which relies on Ufo.config
168
+ # Use puts and use show_layers? which parses for the config
169
+ show = show_layers?
170
+ puts "Config Layers" if show
162
171
  layers.each do |layer|
163
172
  path = "#{Ufo.root}/#{layer}"
164
- puts " #{layer}" if ENV['UFO_SHOW_ALL_LAYERS']
173
+ if ENV['UFO_LAYERS_ALL']
174
+ puts " #{pretty_path(path)}"
175
+ elsif show
176
+ puts " #{pretty_path(path)}" if File.exist?(path)
177
+ end
165
178
  evaluate_file(path)
166
179
  end
167
180
  end
168
181
 
182
+ def show_layers?
183
+ ENV['UFO_LAYERS'] || parse_for_layering_show
184
+ end
185
+ private :show_layers?
186
+
187
+ # Some limitations:
188
+ #
189
+ # * Only parsing one file: .ufo/config.rb
190
+ # * If user is using Ruby code that cannot be parse will fallback to default
191
+ #
192
+ # Think it's worth it so user only has to configure
193
+ #
194
+ # config.layering.show = true
195
+ #
196
+ def parse_for_layering_show
197
+ lines = IO.readlines("#{Ufo.root}/.ufo/config.rb")
198
+ config_line = lines.find { |l| l =~ /config\.layering.show.*=/ && l !~ /^\s+#/ }
199
+ return false unless config_line # default is false
200
+ config_value = config_line.gsub(/.*=/,'').strip.gsub(/["']/,'')
201
+ config_value != "false" && config_value != "nil"
202
+ rescue Exception => e
203
+ if ENV['UFO_DEBUG']
204
+ puts "#{e.class} #{e.message}".color(:yellow)
205
+ puts "WARN: Unable to parse for config.layering.show".color(:yellow)
206
+ puts "Using default: config.layering.show = false"
207
+ end
208
+ false
209
+ end
210
+ memoize :parse_for_layering_show
211
+
169
212
  # Works similiar to Layering::Layer. Consider combining the logic and usin Layering::Layer
170
213
  #
171
214
  # Examples:
@@ -180,6 +223,7 @@ module Ufo
180
223
  #
181
224
  def layer_levels(prefix=nil)
182
225
  levels = ["", "base", Ufo.env]
226
+ levels << "#{Ufo.env}-#{Ufo.extra}" if Ufo.extra
183
227
  paths = levels.map do |i|
184
228
  # base layer has prefix of '', reject with blank so it doesnt produce '//'
185
229
  [prefix, i].join('/')
@@ -162,7 +162,7 @@ module Ufo::Docker
162
162
 
163
163
  def update_dockerfile
164
164
  updater = if File.exist?("#{Ufo.root}/Dockerfile.erb") # dont use @dockerfile on purpose
165
- State.new(docker_image, @options)
165
+ State.new(@options.merge(base_image: docker_image))
166
166
  else
167
167
  Dockerfile.new(docker_image, @options)
168
168
  end
@@ -9,9 +9,9 @@ module Ufo::Docker
9
9
  return unless File.exist?(@erb_file)
10
10
 
11
11
  puts "Compiled #{File.basename(@erb_file).color(:green)} to #{File.basename(@dockerfile).color(:green)}"
12
- path = "#{Ufo.root}/.ufo/state/data.yml"
13
- vars = YAML.load_file(path)[Ufo.env] if File.exist?(path)
14
- vars ||= {}
12
+
13
+ state = State.new
14
+ vars = state.read
15
15
  result = RenderMePretty.result(@erb_file, vars)
16
16
  comment =<<~EOL.chop # remove the trailing newline
17
17
  # IMPORTANT: This file was generated from #{File.basename(@erb_file)} as a part of running:
@@ -0,0 +1,14 @@
1
+ class Ufo::Docker::State
2
+ class Base
3
+ include Ufo::Utils::Logging
4
+ include Ufo::Utils::Pretty
5
+
6
+ def initialize(options={})
7
+ @options = options
8
+ # base_image only passed in with: ufo docker base
9
+ # State#update uses it.
10
+ # State#read wont have access to it and gets it from stored state
11
+ @base_image = options[:base_image]
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,2 @@
1
+ class Ufo::Docker::State
2
+ class S3 < Base
@@ -0,0 +1,52 @@
1
+ class Ufo::Docker::State
2
+ class File < Base
3
+ def read
4
+ current_data
5
+ end
6
+
7
+ def update
8
+ data = current_data
9
+ data["base_image"] = @base_image
10
+
11
+ pretty_path = state_path.sub("#{Ufo.root}/", "")
12
+ FileUtils.mkdir_p(::File.dirname(state_path))
13
+ IO.write(state_path, YAML.dump(data))
14
+
15
+ logger.info "The #{pretty_path} base_image has been updated with the latest base image:".color(:green)
16
+ logger.info " #{@base_image}".color(:green)
17
+ reminder_message
18
+ end
19
+
20
+ def current_data
21
+ ::File.exist?(state_path) ? YAML.load_file(state_path) : {}
22
+ end
23
+
24
+ def state_path
25
+ "#{Ufo.root}/.ufo/state/#{Ufo.app}/#{Ufo.env}/data.yml"
26
+ end
27
+
28
+ def reminder_message
29
+ return unless Ufo.config.state.reminder
30
+ repo = ENV['UFO_CENTRAL_REPO']
31
+ return unless repo
32
+ logger.info "It looks like you're using a central deployer pattern".color(:yellow)
33
+ logger.info <<~EOL
34
+ Remember to commit the state file:
35
+
36
+ state file: #{pretty_path(state_path)}
37
+ repo: #{repo}
38
+
39
+ EOL
40
+
41
+ logger.info <<~EOL
42
+ You can disable these reminder messages with:
43
+
44
+ .ufo/config.rb
45
+
46
+ Ufo.configure do |config|
47
+ config.state.reminder = false
48
+ end
49
+ EOL
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,80 @@
1
+ class Ufo::Docker::State
2
+ class S3 < Base
3
+ extend Memoist
4
+ include Ufo::AwsServices
5
+
6
+ def read
7
+ current_data
8
+ end
9
+
10
+ def update
11
+ data = current_data
12
+ data["base_image"] = @base_image
13
+
14
+ # write data to s3
15
+ body = YAML.dump(data)
16
+ s3.put_object(
17
+ body: body,
18
+ bucket: s3_bucket,
19
+ key: s3_key,
20
+ )
21
+ logger.info "Updated base image in s3://#{s3_bucket}/#{s3_key}"
22
+ logger.info " #{@base_image}".color(:green)
23
+ end
24
+
25
+ # TODO: edge cases: no bucket, no permission
26
+ def current_data
27
+ resp = s3.get_object(bucket: s3_bucket, key: s3_key)
28
+ YAML.load(resp.body)
29
+ rescue Aws::S3::Errors::NoSuchKey
30
+ logger.debug "WARN: s3 key does not exist: #{s3_key}"
31
+ {}
32
+ rescue Aws::S3::Errors::NoSuchBucket
33
+ logger.error "ERROR: S3 bucket does not exist to store state: #{s3_bucket}".color(:red)
34
+ logger.error <<~EOL
35
+ Please double check the config.
36
+
37
+ See: http://ufoships.com/docs/config/state/
38
+
39
+ EOL
40
+ exit 1
41
+ end
42
+
43
+ def s3_key
44
+ "ufo/state/#{app}/#{Ufo.env}/data.yml"
45
+ end
46
+
47
+ # ufo docker base is called before Ufo.config is loaded. This ensures it is loaded
48
+ def app
49
+ Ufo.config
50
+ Ufo.app
51
+ end
52
+
53
+ def s3_bucket
54
+ state = Ufo.config.state
55
+ if state.bucket
56
+ state.bucket
57
+ elsif state.managed
58
+ ensure_s3_bucket_exist
59
+ Ufo::S3::Bucket.name
60
+ else
61
+ logger.error "ERROR: No s3 bucket to store state".color(:red)
62
+ logger.error <<~EOL
63
+ UFO needs a bucket to store the built docker base image.
64
+
65
+ Configure an existing bucket or enable UFO to create a bucket.
66
+
67
+ See: http://ufoships.com/docs/config/state/
68
+ EOL
69
+ exit 1
70
+ end
71
+ end
72
+
73
+ def ensure_s3_bucket_exist
74
+ bucket = Ufo::S3::Bucket.new
75
+ return if bucket.exist?
76
+ bucket.deploy
77
+ end
78
+ memoize :ensure_s3_bucket_exist
79
+ end
80
+ end
@@ -1,63 +1,29 @@
1
1
  module Ufo::Docker
2
2
  class State
3
- include Ufo::Utils::Logging
4
- include Ufo::Utils::Pretty
3
+ extend Memoist
5
4
 
6
- def initialize(docker_image, options={})
7
- @docker_image, @options = docker_image, options
5
+ def initialize(options={})
6
+ @options = options
8
7
  end
9
8
 
10
9
  def update
11
- data = current_data
12
- data[Ufo.env] ||= {}
13
- data[Ufo.env]["base_image"] = @docker_image
14
- pretty_path = state_path.sub("#{Ufo.root}/", "")
15
- FileUtils.mkdir_p(File.dirname(state_path))
16
- IO.write(state_path, YAML.dump(data))
17
- logger.info "The #{pretty_path} base_image has been updated with the latest base image:".color(:green)
18
- logger.info " #{@docker_image}".color(:green)
19
- reminder_message
10
+ storage.update
20
11
  end
21
12
 
22
- def current_data
23
- File.exist?(state_path) ? YAML.load_file(state_path) : {}
13
+ def read
14
+ storage.read
24
15
  end
25
16
 
26
- def state_path
27
- path = "#{Ufo.root}/.ufo/state"
28
- if ENV['UFO_APP'] # env var activates app path
29
- path = "#{Ufo.root}/.ufo/state/#{Ufo.app}"
30
- end
31
- "#{path}/data.yml"
32
- end
33
-
34
- def reminder_message
35
- return unless Ufo.config.state.reminder
36
- repo = ENV['UFO_CENTRAL_REPO']
37
- return unless repo
38
- logger.info "It looks like you're using a central deployer pattern".color(:yellow)
39
- logger.info <<~EOL
40
- Remember to commit the state file:
41
-
42
- state file: #{pretty_path(state_path)}
43
- repo: #{repo}
44
-
45
- EOL
46
-
47
- unless ENV['UFO_APP']
48
- logger.info "WARN: It also doesnt look like UFO_ENV is set".color(:yellow)
49
- logger.info "UFO_ENV should be set when you're using ufo in a central manner"
50
- end
51
-
52
- logger.info <<~EOL
53
- You can disable these reminder messages with:
54
-
55
- .ufo/config.rb
56
-
57
- Ufo.configure do |config|
58
- config.state.reminder = false
59
- end
60
- EOL
17
+ private
18
+ # Examples:
19
+ # File.new(@docker_image, @options)
20
+ # S3.new(@docker_image, @options)
21
+ def storage
22
+ storage = Ufo.config.state.storage
23
+ class_name = "Ufo::Docker::State::#{storage.camelize}"
24
+ klass = class_name.constantize
25
+ klass.new(@options)
61
26
  end
27
+ memoize :storage
62
28
  end
63
29
  end
data/lib/ufo/info.rb CHANGED
@@ -73,7 +73,7 @@ module Ufo
73
73
  end
74
74
 
75
75
  def stack_resources
76
- resp = cloudformation.describe_stack_resources(stack_name: @stack_name)
76
+ resp = cfn.describe_stack_resources(stack_name: @stack_name)
77
77
  resp.stack_resources
78
78
  end
79
79
  memoize :stack_resources
@@ -23,46 +23,44 @@ module Ufo::Layering
23
23
  end
24
24
 
25
25
  def paths
26
- core = full_layers(".ufo/vars")
27
- app = full_layers(".ufo/vars/#{Ufo.app}")
28
- paths = core + app
26
+ # core = full_layers(".ufo/vars")
27
+ # app = full_layers(".ufo/vars/#{Ufo.app}")
28
+
29
+ core = layer_levels(".ufo/vars")
30
+ role = layer_levels(".ufo/vars/#{@task_definition.role}")
31
+ app = layer_levels(".ufo/vars/#{Ufo.app}")
32
+ app_role = layer_levels(".ufo/vars/#{Ufo.app}/#{@task_definition.role}")
33
+
34
+ paths = core + role + app + app_role
29
35
  add_ext!(paths)
30
36
  paths.map! { |p| "#{Ufo.root}/#{p}" }
31
37
  show_layers(paths)
32
38
  paths
33
39
  end
34
40
 
35
- def full_layering
36
- # layers defined in Lono::Layering module
37
- all = layers.map { |layer| layer.sub(/\/$/,'') } # strip trailing slash
38
- all.inject([]) do |sum, layer|
39
- sum += layer_levels(layer) unless layer.nil?
40
- sum
41
- end
42
- end
43
-
44
- # interface method
45
- def main_layers
46
- ['']
47
- end
48
-
49
41
  # adds prefix and to each layer pair that has base and Ufo.env. IE:
50
42
  #
51
43
  # "#{prefix}/base"
52
44
  # "#{prefix}/#{Ufo.env}"
53
45
  #
54
46
  def layer_levels(prefix=nil)
55
- levels = ["base", Ufo.env]
47
+ levels = ["", "base", Ufo.env]
48
+ levels << "#{Ufo.env}-#{Ufo.extra}" if Ufo.extra
56
49
  levels.map! do |i|
57
50
  # base layer has prefix of '', reject with blank so it doesnt produce '//'
58
51
  [prefix, i].reject(&:blank?).join('/')
59
52
  end
60
- levels.unshift(prefix) # unless prefix.blank? # IE: params/us-west-2.txt
53
+ levels.map! { |level| level.sub(/\/$/,'') } # strip trailing slash
54
+ # levels.unshift(prefix) # unless prefix.blank? # IE: params/us-west-2.txt
61
55
  levels
62
56
  end
63
57
 
58
+ # interface method
59
+ def main_layers
60
+ ['']
61
+ end
62
+
64
63
  def add_ext!(paths)
65
- ext = "rb"
66
64
  paths.map! do |path|
67
65
  path = path.sub(/\/$/,'') if path.ends_with?('/')
68
66
  "#{path}.rb"
@@ -70,26 +68,18 @@ module Ufo::Layering
70
68
  paths
71
69
  end
72
70
 
73
- def full_layers(dir)
74
- layers = full_layering.map do |layer|
75
- "#{dir}/#{layer}"
76
- end
77
- role_layers = full_layering.map do |layer|
78
- "#{dir}/#{@task_definition.role}/#{layer}" # Note: layer can be '' will clean up
79
- end
80
- layers += role_layers
81
- layers.map { |l| l.gsub('//','/') } # cleanup // if layer is ''
82
- end
83
-
84
- @@shown_layers = false
71
+ @@shown = false
85
72
  def show_layers(paths)
86
- return if @@shown_layers
87
- logger.info "Vars Layers:" if ENV['UFO_SHOW_ALL_LAYERS']
73
+ return if @@shown
74
+ logger.debug "Layers:"
88
75
  paths.each do |path|
89
- show_layer = File.exist?(path) && logger.level <= Logger::DEBUG
90
- logger.info " #{pretty_path(path)}" if show_layer || ENV['UFO_SHOW_ALL_LAYERS']
76
+ if ENV['UFO_LAYERS_ALL']
77
+ logger.info " #{pretty_path(path)}"
78
+ elsif Ufo.config.layering.show
79
+ logger.info " #{pretty_path(path)}" if File.exist?(path)
80
+ end
91
81
  end
92
- @@shown_layers = true
82
+ @@shown = true
93
83
  end
94
84
  end
95
85
  end
@@ -0,0 +1,17 @@
1
+ module Ufo::S3
2
+ class AwsSetup
3
+ include Ufo::AwsServices
4
+ include Ufo::Utils::Logging
5
+
6
+ def check!
7
+ s3.config.region
8
+ rescue Aws::Errors::MissingRegionError => e
9
+ logger.info "ERROR: #{e.class}: #{e.message}".color(:red)
10
+ logger.info <<~EOL
11
+ Unable to detect the AWS_REGION to make AWS API calls. This is might be because the AWS access
12
+ has not been set up yet. Please either your ~/.aws files.
13
+ EOL
14
+ exit 1
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,174 @@
1
+ module Ufo::S3
2
+ class Bucket
3
+ extend Memoist
4
+ extend Ufo::AwsServices
5
+ include Ufo::AwsServices
6
+ include Ufo::Utils::Logging
7
+
8
+ STACK_NAME = ENV['UFO_STACK_NAME'] || "ufo"
9
+ def initialize(options={})
10
+ @options = options
11
+ end
12
+
13
+ def deploy
14
+ stack = find_stack
15
+ if rollback.complete?
16
+ logger.info "Existing '#{STACK_NAME}' stack in ROLLBACK_COMPLETE state. Deleting stack before continuing."
17
+ disable_termination_protection
18
+ cfn.delete_stack(stack_name: STACK_NAME)
19
+ status.wait
20
+ stack = nil
21
+ end
22
+
23
+ if stack
24
+ update
25
+ else
26
+ create
27
+ end
28
+ end
29
+
30
+ def exist?
31
+ !!bucket_name
32
+ end
33
+
34
+ def bucket_name
35
+ self.class.name
36
+ end
37
+
38
+ def show
39
+ if bucket_name
40
+ logger.info "UFO bucket name: #{bucket_name}"
41
+ else
42
+ logger.info "UFO bucket does not exist yet."
43
+ end
44
+ end
45
+
46
+ # Launches a cloudformation to create an s3 bucket
47
+ def create
48
+ logger.info "Creating #{STACK_NAME} stack for s3 bucket to store state"
49
+ cfn.create_stack(
50
+ stack_name: STACK_NAME,
51
+ template_body: template_body,
52
+ enable_termination_protection: true,
53
+ )
54
+ success = status.wait
55
+ unless success
56
+ logger.info "ERROR: Unable to create UFO stack with managed s3 bucket".color(:red)
57
+ exit 1
58
+ end
59
+ end
60
+
61
+ def update
62
+ logger.info "Updating #{STACK_NAME} stack with the s3 bucket"
63
+ cfn.update_stack(stack_name: STACK_NAME, template_body: template_body)
64
+ rescue Aws::CloudFormation::Errors::ValidationError => e
65
+ raise unless e.message.include?("No updates are to be performed")
66
+ end
67
+
68
+ def delete
69
+ are_you_sure?
70
+
71
+ logger.info "Deleting #{STACK_NAME} stack with the s3 bucket"
72
+ disable_termination_protection
73
+ empty_bucket!
74
+ cfn.delete_stack(stack_name: STACK_NAME)
75
+ end
76
+
77
+ def disable_termination_protection
78
+ cfn.update_termination_protection(
79
+ stack_name: STACK_NAME,
80
+ enable_termination_protection: false,
81
+ )
82
+ end
83
+
84
+ def find_stack
85
+ resp = cfn.describe_stacks(stack_name: STACK_NAME)
86
+ resp.stacks.first
87
+ rescue Aws::CloudFormation::Errors::ValidationError
88
+ nil
89
+ end
90
+
91
+ def status
92
+ CfnStatus.new(STACK_NAME)
93
+ end
94
+
95
+ private
96
+
97
+ def empty_bucket!
98
+ return unless bucket_name # in case of UFO stack ROLLBACK_COMPLETE from failed bucket creation
99
+
100
+ resp = s3.list_objects(bucket: bucket_name)
101
+ if resp.contents.size > 0
102
+ # IE: objects = [{key: "objectkey1"}, {key: "objectkey2"}]
103
+ objects = resp.contents.map { |item| {key: item.key} }
104
+ s3.delete_objects(
105
+ bucket: bucket_name,
106
+ delete: {
107
+ objects: objects,
108
+ quiet: false,
109
+ }
110
+ )
111
+ empty_bucket! # keep deleting objects until bucket is empty
112
+ end
113
+ end
114
+
115
+
116
+ def are_you_sure?
117
+ return true if @options[:yes]
118
+
119
+ if bucket_name.nil?
120
+ logger.info "The UFO stack and s3 bucket does not exist."
121
+ exit
122
+ end
123
+
124
+ logger.info "Are you yes you want the UFO bucket #{bucket_name.color(:green)} to be emptied and deleted? (y/N)"
125
+ yes = $stdin.gets.strip
126
+ confirmed = yes =~ /^Y/i
127
+ unless confirmed
128
+ logger.info "Phew that was close."
129
+ exit
130
+ end
131
+ end
132
+
133
+ def template_body
134
+ <<~YAML
135
+ Description: UFO managed s3 bucket
136
+ Resources:
137
+ Bucket:
138
+ Type: AWS::S3::Bucket
139
+ Properties:
140
+ BucketEncryption:
141
+ ServerSideEncryptionConfiguration:
142
+ - ServerSideEncryptionByDefault:
143
+ SSEAlgorithm: AES256
144
+ Tags:
145
+ - Key: Name
146
+ Value: UFO
147
+ Outputs:
148
+ Bucket:
149
+ Value:
150
+ Ref: Bucket
151
+ YAML
152
+ end
153
+
154
+ def rollback
155
+ Rollback.new(STACK_NAME)
156
+ end
157
+
158
+ class << self
159
+ @@name = nil
160
+ def name
161
+ return @@name if @@name # only memoize once bucket has been created
162
+
163
+ AwsSetup.new.check!
164
+
165
+ stack = new.find_stack
166
+ return unless stack
167
+
168
+ stack_resources = find_stack_resources(STACK_NAME)
169
+ bucket = stack_resources.find { |r| r.logical_resource_id == "Bucket" }
170
+ @@name = bucket.physical_resource_id # actual bucket name
171
+ end
172
+ end
173
+ end
174
+ end
@@ -0,0 +1,52 @@
1
+ module Ufo::S3
2
+ class Rollback
3
+ extend Memoist
4
+ include Ufo::AwsServices
5
+ include Ufo::Utils::Logging
6
+
7
+ def initialize(stack)
8
+ @stack = stack
9
+ end
10
+
11
+ def delete_stack
12
+ return unless complete?
13
+ logger.info "Existing stack in ROLLBACK_COMPLETE state. Deleting stack before continuing."
14
+ cfn.delete_stack(stack_name: @stack)
15
+ status.wait
16
+ status.reset
17
+ true
18
+ end
19
+
20
+ def continue_update
21
+ continue_update?
22
+ begin
23
+ cfn.continue_update_rollback(stack_name: @stack)
24
+ rescue Aws::CloudFormation::Errors::ValidationError => e
25
+ logger.info "ERROR: Continue update: #{e.message}".color(:red)
26
+ quit 1
27
+ end
28
+ end
29
+
30
+ def continue_update?
31
+ logger.info <<~EOL
32
+ The stack is in the UPDATE_ROLLBACK_FAILED state. More info here: https://amzn.to/2IiEjc5
33
+ Would you like to try to continue the update rollback? (y/N)
34
+ EOL
35
+
36
+ yes = @options[:yes] ? "y" : $stdin.gets
37
+ unless yes =~ /^y/
38
+ logger.info "Exiting without continuing the update rollback."
39
+ quit 0
40
+ end
41
+ end
42
+
43
+ def complete?
44
+ stack&.stack_status == 'ROLLBACK_COMPLETE'
45
+ end
46
+
47
+ def stack
48
+ find_stack(@stack)
49
+ end
50
+ memoize :stack
51
+ end
52
+ end