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 +4 -4
- data/.cody/acceptance/bin/build.sh +1 -1
- data/CHANGELOG.md +9 -0
- data/lib/ufo/aws_services/concerns.rb +55 -0
- data/lib/ufo/aws_services.rb +9 -40
- data/lib/ufo/cfn/stack/status.rb +1 -1
- data/lib/ufo/cfn/stack.rb +4 -4
- data/lib/ufo/cli/central/base.rb +1 -0
- data/lib/ufo/cli/central/update.rb +1 -0
- data/lib/ufo/cli/destroy.rb +1 -1
- data/lib/ufo/config.rb +3 -0
- data/lib/ufo/docker/builder.rb +1 -1
- data/lib/ufo/docker/compiler.rb +3 -3
- data/lib/ufo/docker/state/base.rb +14 -0
- data/lib/ufo/docker/state/bucket.rb +2 -0
- data/lib/ufo/docker/state/file.rb +52 -0
- data/lib/ufo/docker/state/s3.rb +80 -0
- data/lib/ufo/docker/state.rb +16 -50
- data/lib/ufo/info.rb +1 -1
- data/lib/ufo/s3/aws_setup.rb +17 -0
- data/lib/ufo/s3/bucket.rb +174 -0
- data/lib/ufo/s3/rollback.rb +52 -0
- data/lib/ufo/version.rb +1 -1
- data/ufo.gemspec +1 -0
- metadata +24 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5b86133b126853170107907bce7c02f4620387d8b11c4a5e6e0f03176fda1349
|
4
|
+
data.tar.gz: 3c543c7b436ec7214043d5f5cf45a798af2ce054aed970fa5de991848f6a4664
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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
|
data/lib/ufo/aws_services.rb
CHANGED
@@ -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
|
30
|
+
def cfn
|
30
31
|
Aws::CloudFormation::Client.new(aws_options)
|
31
32
|
end
|
32
|
-
memoize :
|
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
|
data/lib/ufo/cfn/stack/status.rb
CHANGED
@@ -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 =
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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}"
|
data/lib/ufo/cli/central/base.rb
CHANGED
data/lib/ufo/cli/destroy.rb
CHANGED
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
|
data/lib/ufo/docker/builder.rb
CHANGED
@@ -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(
|
165
|
+
State.new(@options.merge(base_image: docker_image))
|
166
166
|
else
|
167
167
|
Dockerfile.new(docker_image, @options)
|
168
168
|
end
|
data/lib/ufo/docker/compiler.rb
CHANGED
@@ -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
|
-
|
13
|
-
|
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,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
|
data/lib/ufo/docker/state.rb
CHANGED
@@ -1,63 +1,29 @@
|
|
1
1
|
module Ufo::Docker
|
2
2
|
class State
|
3
|
-
|
4
|
-
include Ufo::Utils::Pretty
|
3
|
+
extend Memoist
|
5
4
|
|
6
|
-
def initialize(
|
7
|
-
@
|
5
|
+
def initialize(options={})
|
6
|
+
@options = options
|
8
7
|
end
|
9
8
|
|
10
9
|
def update
|
11
|
-
|
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
|
23
|
-
|
13
|
+
def read
|
14
|
+
storage.read
|
24
15
|
end
|
25
16
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
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
@@ -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
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.
|
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-
|
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
|