ufo 6.1.3 → 6.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 881d674019c976e4c8cae9030f69bd2125e59c7af970a1fa2005dcbfb77a4887
4
- data.tar.gz: 5c3e8f73e200390396c99aa292f949f5c0a7b0e457bab47e2b88dbc583adc0fa
3
+ metadata.gz: 5b86133b126853170107907bce7c02f4620387d8b11c4a5e6e0f03176fda1349
4
+ data.tar.gz: 3c543c7b436ec7214043d5f5cf45a798af2ce054aed970fa5de991848f6a4664
5
5
  SHA512:
6
- metadata.gz: 7eaca7eaa3999ce52dd4087dd2bd7862156f34c59443fcf06140f50356beb50237c4dea35d2dc68a1171576e17529c18144e5c97bc6aa4da4ea1edfa3b9411ef
7
- data.tar.gz: 820645713455edf1d3e7fd242c68c21f32267f6eb031c67dd9236578d93c56e0a616750170f202ab27f32c86a0093a673fa5e7321c800b3d82121f38f156b29f
6
+ metadata.gz: f4658807edc6f36b07cf8048f9f7f9c75ed90f3a03cd481ea1b763d4332bdf887a17d9199988021fa3866d73e7d0b0f43b1d03ef031915fdf5e0d3ad00a5904c
7
+ data.tar.gz: d52d10df7faf76256f336abfc89a9ccd1afb999973d52e7cbf660481c17eb54ae8333c09e600de12455020c707fd1821a32beee0a893c72750337c1aecbcfaaa
@@ -22,7 +22,7 @@ REPO=$(aws ecr describe-repositories --repository-name test/demo | jq -r '.repos
22
22
 
23
23
  # DockerHub
24
24
  # toomanyrequests: You have reached your pull rate limit. You may increase the limit by authenticating
25
- docker login --username $DOCKER_USER --password $DOCKER_PASS
25
+ echo "$DOCKER_PASS" | docker login --username $DOCKER_USER --password-stdin
26
26
  TOKEN=$(curl -s --user "$DOCKER_USER:$DOCKER_PASS" "https://auth.docker.io/token?service=registry.docker.io&scope=repository:ratelimitpreview/test:pull" | jq -r .token)
27
27
  echo "Current rate limit:"
28
28
  curl -s --head -H "Authorization: Bearer $TOKEN" https://registry-1.docker.io/v2/ratelimitpreview/test/manifests/latest
data/CHANGELOG.md CHANGED
@@ -3,6 +3,15 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  This project *tries* to adhere to [Semantic Versioning](http://semver.org/), even before v1.0.
5
5
 
6
+ ## [6.2.0] - 2022-03-16
7
+ - [#152](https://github.com/tongueroo/ufo/pull/152) ufo docker base: s3 storage support
8
+
9
+ ## [6.1.5] - 2022-03-16
10
+ - [#151](https://github.com/tongueroo/ufo/pull/151) fix symlink when .git ext is in the repo
11
+
12
+ ## [6.1.4] - 2022-03-16
13
+ - [#150](https://github.com/tongueroo/ufo/pull/150) ufo central: fix edge case when trying report broken symlink and logger not available
14
+
6
15
  ## [6.1.3] - 2022-03-14
7
16
  - [#149](https://github.com/tongueroo/ufo/pull/149) stack_output helper improve message when stack not found
8
17
 
@@ -0,0 +1,55 @@
1
+ require "cfn_status"
2
+
3
+ module Ufo::AwsServices
4
+ module Concerns
5
+ extend Memoist
6
+
7
+ def find_stack(stack_name)
8
+ resp = cfn.describe_stacks(stack_name: stack_name)
9
+ resp.stacks.first
10
+ rescue Aws::CloudFormation::Errors::ValidationError => e
11
+ # example: Stack with id demo-web does not exist
12
+ if e.message =~ /Stack with/ && e.message =~ /does not exist/
13
+ nil
14
+ else
15
+ raise
16
+ end
17
+ end
18
+
19
+ def stack_resources(stack_name)
20
+ resp = cfn.describe_stack_resources(stack_name: stack_name)
21
+ resp.stack_resources
22
+ rescue Aws::CloudFormation::Errors::ValidationError => e
23
+ e.message.include?("does not exist") ? return : raise
24
+ end
25
+
26
+ def task_definition_arns(family, max_items=10)
27
+ resp = ecs.list_task_definitions(
28
+ family_prefix: family,
29
+ sort: "DESC",
30
+ )
31
+ arns = resp.task_definition_arns
32
+ arns = arns.select do |arn|
33
+ task_definition = arn.split('/').last.split(':').first
34
+ task_definition == family
35
+ end
36
+ arns[0..max_items]
37
+ end
38
+
39
+ def status
40
+ CfnStatus.new(@stack_name) # NOTE: @stack_name must be set in the including Class
41
+ end
42
+ memoize :status
43
+
44
+ def find_stack_resources(stack_name)
45
+ resp = cfn.describe_stack_resources(stack_name: stack_name)
46
+ resp.stack_resources
47
+ rescue Aws::CloudFormation::Errors::ValidationError => e
48
+ if e.message.include?("does not exist")
49
+ nil
50
+ else
51
+ raise
52
+ end
53
+ end
54
+ end
55
+ end
@@ -6,15 +6,16 @@ require "aws-sdk-ec2"
6
6
  require "aws-sdk-ecr"
7
7
  require "aws-sdk-ecs"
8
8
  require "aws-sdk-elasticloadbalancingv2"
9
+ require "aws-sdk-s3"
9
10
  require "aws-sdk-ssm"
10
11
  require "aws-sdk-wafv2"
11
12
 
12
13
  require "aws_mfa_secure/ext/aws" # add MFA support
13
- require "cfn_status"
14
14
 
15
15
  module Ufo
16
16
  module AwsServices
17
17
  extend Memoist
18
+ include Concerns
18
19
 
19
20
  def acm
20
21
  Aws::ACM::Client.new(aws_options)
@@ -26,10 +27,10 @@ module Ufo
26
27
  end
27
28
  memoize :applicationautoscaling
28
29
 
29
- def cloudformation
30
+ def cfn
30
31
  Aws::CloudFormation::Client.new(aws_options)
31
32
  end
32
- memoize :cloudformation
33
+ memoize :cfn
33
34
 
34
35
  def cloudwatchlogs
35
36
  Aws::CloudWatchLogs::Client.new(aws_options)
@@ -56,6 +57,11 @@ module Ufo
56
57
  end
57
58
  memoize :elb
58
59
 
60
+ def s3
61
+ Aws::S3::Client.new(aws_options)
62
+ end
63
+ memoize :s3
64
+
59
65
  # ssm is a helper method
60
66
  def ssm_client
61
67
  Aws::SSM::Client.new(aws_options)
@@ -98,42 +104,5 @@ module Ufo
98
104
  ) if ENV['UFO_DEBUG_AWS_SDK']
99
105
  options
100
106
  end
101
-
102
- def find_stack(stack_name)
103
- resp = cloudformation.describe_stacks(stack_name: stack_name)
104
- resp.stacks.first
105
- rescue Aws::CloudFormation::Errors::ValidationError => e
106
- # example: Stack with id demo-web does not exist
107
- if e.message =~ /Stack with/ && e.message =~ /does not exist/
108
- nil
109
- else
110
- raise
111
- end
112
- end
113
-
114
- def stack_resources(stack_name)
115
- resp = cloudformation.describe_stack_resources(stack_name: stack_name)
116
- resp.stack_resources
117
- rescue Aws::CloudFormation::Errors::ValidationError => e
118
- e.message.include?("does not exist") ? return : raise
119
- end
120
-
121
- def task_definition_arns(family, max_items=10)
122
- resp = ecs.list_task_definitions(
123
- family_prefix: family,
124
- sort: "DESC",
125
- )
126
- arns = resp.task_definition_arns
127
- arns = arns.select do |arn|
128
- task_definition = arn.split('/').last.split(':').first
129
- task_definition == family
130
- end
131
- arns[0..max_items]
132
- end
133
-
134
- def status
135
- CfnStatus.new(@stack_name) # NOTE: @stack_name must be set in the including Class
136
- end
137
- memoize :status
138
107
  end
139
108
  end
@@ -122,7 +122,7 @@ class Ufo::Cfn::Stack
122
122
 
123
123
  # refreshes the loaded events in memory
124
124
  def refresh_events
125
- resp = cloudformation.describe_stack_events(stack_name: @stack_name)
125
+ resp = cfn.describe_stack_events(stack_name: @stack_name)
126
126
  @events = resp["stack_events"]
127
127
  rescue Aws::CloudFormation::Errors::ValidationError => e
128
128
  if e.message =~ /Stack .* does not exis/
data/lib/ufo/cfn/stack.rb CHANGED
@@ -31,7 +31,7 @@ module Ufo::Cfn
31
31
  @stack = find_stack(@stack_name)
32
32
  if @stack && rollback_complete?(@stack)
33
33
  logger.info "Existing stack in ROLLBACK_COMPLETE state. Deleting stack before continuing."
34
- cloudformation.delete_stack(stack_name: @stack_name)
34
+ cfn.delete_stack(stack_name: @stack_name)
35
35
  status.wait
36
36
  status.reset
37
37
  @stack = nil # at this point stack has been deleted
@@ -53,7 +53,7 @@ module Ufo::Cfn
53
53
 
54
54
  def perform(action)
55
55
  logger.info "#{action[0..-2].capitalize}ing stack #{@stack_name.color(:green)}"
56
- cloudformation.send("#{action}_stack", stack_options) # Example: cloudformation.send("update_stack", stack_options)
56
+ cfn.send("#{action}_stack", stack_options) # Example: cfn.send("update_stack", stack_options)
57
57
  rescue Aws::CloudFormation::Errors::ValidationError => e
58
58
  handle_stack_error(e)
59
59
  end
@@ -152,10 +152,10 @@ module Ufo::Cfn
152
152
  end
153
153
 
154
154
  if stack.stack_status == "CREATE_IN_PROGRESS"
155
- cloudformation.delete_stack(stack_name: @stack_name)
155
+ cfn.delete_stack(stack_name: @stack_name)
156
156
  logger.info "Canceling stack creation"
157
157
  elsif stack.stack_status == "UPDATE_IN_PROGRESS"
158
- cloudformation.cancel_update_stack(stack_name: @stack_name)
158
+ cfn.cancel_update_stack(stack_name: @stack_name)
159
159
  logger.info "Canceling stack update"
160
160
  else
161
161
  logger.info "The stack is not in a state to that is cancelable: #{stack.stack_status}"
@@ -1,5 +1,6 @@
1
1
  class Ufo::CLI::Central
2
2
  class Base
3
+ include Ufo::Utils::Logging
3
4
  include Ufo::Utils::Pretty
4
5
  include Ufo::Utils::Sure
5
6
 
@@ -30,6 +30,7 @@ class Ufo::CLI::Central
30
30
 
31
31
  # FileUtils.ln_s(target, link, options)
32
32
  # ~/.ufo/central/repo -> .ufo
33
+ src.sub!(/\.git$/,'')
33
34
  FileUtils.ln_sf(src, ".ufo", verbose: false) # force in case of existing broken symlink
34
35
  FileUtils.rm_rf(".ufo.bak")
35
36
 
@@ -17,7 +17,7 @@ class Ufo::CLI
17
17
  return
18
18
  end
19
19
 
20
- cloudformation.delete_stack(stack_name: @stack_name)
20
+ cfn.delete_stack(stack_name: @stack_name)
21
21
  puts "Deleting stack #{@stack_name.color(:green)}"
22
22
 
23
23
  return unless @options[:wait]
data/lib/ufo/config.rb CHANGED
@@ -114,7 +114,10 @@ module Ufo
114
114
  config.ship.docker.quiet = false # only affects ufo ship docker commands output
115
115
 
116
116
  config.state = ActiveSupport::OrderedOptions.new
117
+ config.state.bucket = nil # Set to use existing bucket. When not set ufo creates a managed s3 bucket
118
+ config.state.managed = true # false will disable creation of managed bucket entirely
117
119
  config.state.reminder = true
120
+ config.state.storage = "s3" # s3 or file
118
121
 
119
122
  config.waf = ActiveSupport::OrderedOptions.new
120
123
  config.waf.web_acl_arn = nil
@@ -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
@@ -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
data/lib/ufo/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Ufo
2
- VERSION = "6.1.3"
2
+ VERSION = "6.2.0"
3
3
  end
data/ufo.gemspec CHANGED
@@ -28,6 +28,7 @@ Gem::Specification.new do |spec|
28
28
  spec.add_dependency "aws-sdk-ecr"
29
29
  spec.add_dependency "aws-sdk-ecs"
30
30
  spec.add_dependency "aws-sdk-elasticloadbalancingv2"
31
+ spec.add_dependency "aws-sdk-s3"
31
32
  spec.add_dependency "aws-sdk-ssm"
32
33
  spec.add_dependency "aws-sdk-wafv2"
33
34
  spec.add_dependency "aws_data"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ufo
3
3
  version: !ruby/object:Gem::Version
4
- version: 6.1.3
4
+ version: 6.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tung Nguyen
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-03-14 00:00:00.000000000 Z
11
+ date: 2022-03-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: aws-logs
@@ -150,6 +150,20 @@ dependencies:
150
150
  - - ">="
151
151
  - !ruby/object:Gem::Version
152
152
  version: '0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: aws-sdk-s3
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ type: :runtime
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
153
167
  - !ruby/object:Gem::Dependency
154
168
  name: aws-sdk-ssm
155
169
  requirement: !ruby/object:Gem::Requirement
@@ -472,6 +486,7 @@ files:
472
486
  - lib/ufo.rb
473
487
  - lib/ufo/autoloader.rb
474
488
  - lib/ufo/aws_services.rb
489
+ - lib/ufo/aws_services/concerns.rb
475
490
  - lib/ufo/booter.rb
476
491
  - lib/ufo/cfn/base.rb
477
492
  - lib/ufo/cfn/deploy.rb
@@ -581,6 +596,10 @@ files:
581
596
  - lib/ufo/docker/dockerfile.rb
582
597
  - lib/ufo/docker/pusher.rb
583
598
  - lib/ufo/docker/state.rb
599
+ - lib/ufo/docker/state/base.rb
600
+ - lib/ufo/docker/state/bucket.rb
601
+ - lib/ufo/docker/state/file.rb
602
+ - lib/ufo/docker/state/s3.rb
584
603
  - lib/ufo/ecr/auth.rb
585
604
  - lib/ufo/ecr/cleaner.rb
586
605
  - lib/ufo/ecs/service.rb
@@ -598,6 +617,9 @@ files:
598
617
  - lib/ufo/logger/formatter.rb
599
618
  - lib/ufo/names.rb
600
619
  - lib/ufo/param.rb
620
+ - lib/ufo/s3/aws_setup.rb
621
+ - lib/ufo/s3/bucket.rb
622
+ - lib/ufo/s3/rollback.rb
601
623
  - lib/ufo/task_definition.rb
602
624
  - lib/ufo/task_definition/builder.rb
603
625
  - lib/ufo/task_definition/context.rb