cloud-mu 3.2.0 → 3.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Dockerfile +1 -1
- data/ansible/roles/mu-nat/tasks/main.yml +3 -0
- data/bin/mu-adopt +12 -1
- data/bin/mu-aws-setup +41 -7
- data/bin/mu-azure-setup +34 -0
- data/bin/mu-configure +214 -119
- data/bin/mu-gcp-setup +37 -2
- data/bin/mu-load-config.rb +2 -1
- data/bin/mu-node-manage +3 -0
- data/bin/mu-refresh-ssl +67 -0
- data/bin/mu-run-tests +28 -6
- data/bin/mu-self-update +30 -10
- data/bin/mu-upload-chef-artifacts +30 -26
- data/cloud-mu.gemspec +10 -8
- data/cookbooks/mu-master/attributes/default.rb +5 -1
- data/cookbooks/mu-master/metadata.rb +2 -2
- data/cookbooks/mu-master/recipes/default.rb +81 -26
- data/cookbooks/mu-master/recipes/init.rb +197 -62
- data/cookbooks/mu-master/recipes/update_nagios_only.rb +1 -1
- data/cookbooks/mu-master/recipes/vault.rb +78 -77
- data/cookbooks/mu-master/templates/default/mods/rewrite.conf.erb +1 -0
- data/cookbooks/mu-master/templates/default/nagios.conf.erb +103 -0
- data/cookbooks/mu-master/templates/default/web_app.conf.erb +14 -30
- data/cookbooks/mu-tools/attributes/default.rb +12 -0
- data/cookbooks/mu-tools/files/centos-6/CentOS-Base.repo +47 -0
- data/cookbooks/mu-tools/libraries/helper.rb +98 -4
- data/cookbooks/mu-tools/libraries/monkey.rb +1 -1
- data/cookbooks/mu-tools/recipes/apply_security.rb +31 -9
- data/cookbooks/mu-tools/recipes/aws_api.rb +8 -2
- data/cookbooks/mu-tools/recipes/base_repositories.rb +1 -1
- data/cookbooks/mu-tools/recipes/gcloud.rb +2 -9
- data/cookbooks/mu-tools/recipes/google_api.rb +7 -0
- data/cookbooks/mu-tools/recipes/rsyslog.rb +8 -1
- data/cookbooks/mu-tools/resources/disk.rb +113 -42
- data/cookbooks/mu-tools/resources/mommacat_request.rb +1 -2
- data/cookbooks/mu-tools/templates/centos-8/sshd_config.erb +215 -0
- data/extras/Gemfile.lock.bootstrap +394 -0
- data/extras/bucketstubs/error.html +0 -0
- data/extras/bucketstubs/index.html +0 -0
- data/extras/clean-stock-amis +11 -3
- data/extras/generate-stock-images +6 -3
- data/extras/git_rpm/build.sh +20 -0
- data/extras/git_rpm/mugit.spec +53 -0
- data/extras/image-generators/AWS/centos7.yaml +19 -16
- data/extras/image-generators/AWS/{rhel7.yaml → rhel71.yaml} +0 -0
- data/extras/image-generators/AWS/{win2k12.yaml → win2k12r2.yaml} +0 -0
- data/extras/image-generators/VMWare/centos8.yaml +15 -0
- data/extras/openssl_rpm/build.sh +19 -0
- data/extras/openssl_rpm/mussl.spec +46 -0
- data/extras/python_rpm/muthon.spec +14 -4
- data/extras/ruby_rpm/muby.spec +9 -5
- data/extras/sqlite_rpm/build.sh +19 -0
- data/extras/sqlite_rpm/muqlite.spec +47 -0
- data/install/installer +7 -5
- data/modules/mommacat.ru +2 -2
- data/modules/mu.rb +14 -7
- data/modules/mu/adoption.rb +5 -5
- data/modules/mu/cleanup.rb +47 -25
- data/modules/mu/cloud.rb +29 -1
- data/modules/mu/cloud/dnszone.rb +0 -2
- data/modules/mu/cloud/machine_images.rb +1 -1
- data/modules/mu/cloud/providers.rb +6 -1
- data/modules/mu/cloud/resource_base.rb +16 -7
- data/modules/mu/cloud/ssh_sessions.rb +5 -1
- data/modules/mu/cloud/wrappers.rb +20 -7
- data/modules/mu/config.rb +28 -12
- data/modules/mu/config/bucket.rb +31 -2
- data/modules/mu/config/cache_cluster.rb +1 -1
- data/modules/mu/config/cdn.rb +100 -0
- data/modules/mu/config/container_cluster.rb +1 -1
- data/modules/mu/config/database.rb +3 -3
- data/modules/mu/config/dnszone.rb +4 -3
- data/modules/mu/config/endpoint.rb +1 -0
- data/modules/mu/config/firewall_rule.rb +1 -1
- data/modules/mu/config/function.rb +16 -7
- data/modules/mu/config/job.rb +89 -0
- data/modules/mu/config/notifier.rb +7 -18
- data/modules/mu/config/ref.rb +55 -9
- data/modules/mu/config/schema_helpers.rb +12 -3
- data/modules/mu/config/server.rb +11 -5
- data/modules/mu/config/server_pool.rb +2 -2
- data/modules/mu/config/vpc.rb +11 -10
- data/modules/mu/defaults/AWS.yaml +106 -106
- data/modules/mu/deploy.rb +40 -14
- data/modules/mu/groomers/chef.rb +2 -2
- data/modules/mu/master.rb +70 -3
- data/modules/mu/mommacat.rb +28 -9
- data/modules/mu/mommacat/daemon.rb +13 -7
- data/modules/mu/mommacat/naming.rb +2 -2
- data/modules/mu/mommacat/search.rb +16 -5
- data/modules/mu/mommacat/storage.rb +67 -32
- data/modules/mu/providers/aws.rb +298 -85
- data/modules/mu/providers/aws/alarm.rb +5 -5
- data/modules/mu/providers/aws/bucket.rb +284 -50
- data/modules/mu/providers/aws/cache_cluster.rb +26 -26
- data/modules/mu/providers/aws/cdn.rb +782 -0
- data/modules/mu/providers/aws/collection.rb +16 -16
- data/modules/mu/providers/aws/container_cluster.rb +84 -64
- data/modules/mu/providers/aws/database.rb +59 -55
- data/modules/mu/providers/aws/dnszone.rb +29 -12
- data/modules/mu/providers/aws/endpoint.rb +535 -50
- data/modules/mu/providers/aws/firewall_rule.rb +32 -26
- data/modules/mu/providers/aws/folder.rb +1 -1
- data/modules/mu/providers/aws/function.rb +300 -134
- data/modules/mu/providers/aws/group.rb +16 -14
- data/modules/mu/providers/aws/habitat.rb +4 -4
- data/modules/mu/providers/aws/job.rb +469 -0
- data/modules/mu/providers/aws/loadbalancer.rb +67 -45
- data/modules/mu/providers/aws/log.rb +17 -17
- data/modules/mu/providers/aws/msg_queue.rb +22 -13
- data/modules/mu/providers/aws/nosqldb.rb +99 -8
- data/modules/mu/providers/aws/notifier.rb +137 -65
- data/modules/mu/providers/aws/role.rb +119 -83
- data/modules/mu/providers/aws/search_domain.rb +166 -30
- data/modules/mu/providers/aws/server.rb +209 -118
- data/modules/mu/providers/aws/server_pool.rb +95 -130
- data/modules/mu/providers/aws/storage_pool.rb +19 -11
- data/modules/mu/providers/aws/user.rb +5 -5
- data/modules/mu/providers/aws/userdata/linux.erb +5 -4
- data/modules/mu/providers/aws/vpc.rb +109 -54
- data/modules/mu/providers/aws/vpc_subnet.rb +43 -39
- data/modules/mu/providers/azure.rb +78 -12
- data/modules/mu/providers/azure/server.rb +20 -4
- data/modules/mu/providers/cloudformation/server.rb +1 -1
- data/modules/mu/providers/google.rb +21 -5
- data/modules/mu/providers/google/bucket.rb +1 -1
- data/modules/mu/providers/google/container_cluster.rb +1 -1
- data/modules/mu/providers/google/database.rb +1 -1
- data/modules/mu/providers/google/firewall_rule.rb +1 -1
- data/modules/mu/providers/google/folder.rb +7 -3
- data/modules/mu/providers/google/function.rb +66 -31
- data/modules/mu/providers/google/group.rb +1 -1
- data/modules/mu/providers/google/habitat.rb +1 -1
- data/modules/mu/providers/google/loadbalancer.rb +1 -1
- data/modules/mu/providers/google/role.rb +6 -3
- data/modules/mu/providers/google/server.rb +1 -1
- data/modules/mu/providers/google/server_pool.rb +1 -1
- data/modules/mu/providers/google/user.rb +1 -1
- data/modules/mu/providers/google/vpc.rb +28 -3
- data/modules/tests/aws-jobs-functions.yaml +46 -0
- data/modules/tests/aws-servers-with-handrolled-iam.yaml +37 -0
- data/modules/tests/centos6.yaml +4 -0
- data/modules/tests/centos7.yaml +4 -0
- data/modules/tests/ecs.yaml +2 -2
- data/modules/tests/eks.yaml +1 -1
- data/modules/tests/functions/node-function/lambda_function.js +10 -0
- data/modules/tests/functions/python-function/lambda_function.py +12 -0
- data/modules/tests/k8s.yaml +1 -1
- data/modules/tests/microservice_app.yaml +288 -0
- data/modules/tests/rds.yaml +5 -5
- data/modules/tests/regrooms/rds.yaml +5 -5
- data/modules/tests/server-with-scrub-muisms.yaml +1 -1
- data/modules/tests/super_complex_bok.yml +2 -2
- data/modules/tests/super_simple_bok.yml +2 -2
- metadata +42 -17
@@ -36,7 +36,7 @@ module MU
|
|
36
36
|
@dependencies[dimension["depclass"]][dimension["name"]].cloudobj.cloud_id
|
37
37
|
end
|
38
38
|
elsif dimension["mu_name"] and dimension["deploy_id"]
|
39
|
-
found = MU::MommaCat.findStray("AWS", deps_class, deploy_id: dimension["deploy_id"], mu_name: dimension["mu_name"], region: @
|
39
|
+
found = MU::MommaCat.findStray("AWS", deps_class, deploy_id: dimension["deploy_id"], mu_name: dimension["mu_name"], region: @region)
|
40
40
|
raise MuError, "Couldn't find #{deps_class} #{dimension["mu_name"]}" if found.nil? || found.empty?
|
41
41
|
resp = found.first.deploydata["cloud_id"]
|
42
42
|
resp.downcase if %w{database cache_cluster}.include?(deps_class)
|
@@ -79,8 +79,8 @@ module MU
|
|
79
79
|
evaluation_periods: @config["evaluation_periods"],
|
80
80
|
threshold: @config["threshold"],
|
81
81
|
comparison_operator: @config["comparison_operator"],
|
82
|
-
region: @
|
83
|
-
credentials: @
|
82
|
+
region: @region,
|
83
|
+
credentials: @credentials
|
84
84
|
)
|
85
85
|
|
86
86
|
@cloud_id = @mu_name
|
@@ -124,7 +124,7 @@ module MU
|
|
124
124
|
# @param ignoremaster [Boolean]: If true, will remove resources not flagged as originating from this Mu server
|
125
125
|
# @param region [String]: The cloud provider region
|
126
126
|
# @return [void]
|
127
|
-
def self.cleanup(noop: false, ignoremaster: false, region: MU.curRegion, credentials: nil, flags: {})
|
127
|
+
def self.cleanup(noop: false, deploy_id: MU.deploy_id, ignoremaster: false, region: MU.curRegion, credentials: nil, flags: {})
|
128
128
|
MU.log "AWS::Alarm.cleanup: need to support flags['known']", MU::DEBUG, details: flags
|
129
129
|
MU.log "Placeholder: AWS Alarm artifacts do not support tags, so ignoremaster cleanup flag has no effect", MU::DEBUG, details: ignoremaster
|
130
130
|
alarms = []
|
@@ -132,7 +132,7 @@ module MU
|
|
132
132
|
# This can miss alarms in some cases (eg. cache_cluster) so we might want to delete alarms from each API as well.
|
133
133
|
MU::Cloud::AWS.cloudwatch(credentials: credentials, region: region).describe_alarms.each { |page|
|
134
134
|
page.metric_alarms.map(&:alarm_name).each { |alarm_name|
|
135
|
-
alarms << alarm_name if alarm_name.match(
|
135
|
+
alarms << alarm_name if alarm_name.match(deploy_id)
|
136
136
|
}
|
137
137
|
}
|
138
138
|
|
@@ -21,6 +21,12 @@ module MU
|
|
21
21
|
@@region_cache = {}
|
22
22
|
@@region_cache_semaphore = Mutex.new
|
23
23
|
|
24
|
+
# Map some filename extensions to mime types. S3 does most of this on
|
25
|
+
# its own, add to this for cases it doesn't cover.
|
26
|
+
MIME_MAP = {
|
27
|
+
".svg" => "image/svg+xml"
|
28
|
+
}
|
29
|
+
|
24
30
|
# Initialize this cloud resource object. Calling +super+ will invoke the initializer defined under {MU::Cloud}, which should set the attribtues listed in {MU::Cloud::PUBLIC_ATTRS} as well as applicable dependency shortcuts, like +@vpc+, for us.
|
25
31
|
# @param args [Hash]: Hash of named arguments passed via Ruby's double-splat
|
26
32
|
def initialize(**args)
|
@@ -33,20 +39,20 @@ module MU
|
|
33
39
|
bucket_name = @deploy.getResourceName(@config["name"], max_length: 63).downcase
|
34
40
|
|
35
41
|
MU.log "Creating S3 bucket #{bucket_name}"
|
36
|
-
MU::Cloud::AWS.s3(credentials: @
|
42
|
+
MU::Cloud::AWS.s3(credentials: @credentials, region: @region).create_bucket(
|
37
43
|
acl: @config['acl'],
|
38
44
|
bucket: bucket_name
|
39
45
|
)
|
40
46
|
|
41
47
|
@cloud_id = bucket_name
|
42
|
-
is_live = MU::Cloud::AWS::Bucket.find(cloud_id: @cloud_id, region: @
|
48
|
+
is_live = MU::Cloud::AWS::Bucket.find(cloud_id: @cloud_id, region: @region, credentials: @credentials).values.first
|
43
49
|
begin
|
44
|
-
is_live = MU::Cloud::AWS::Bucket.find(cloud_id: @cloud_id, region: @
|
50
|
+
is_live = MU::Cloud::AWS::Bucket.find(cloud_id: @cloud_id, region: @region, credentials: @credentials).values.first
|
45
51
|
sleep 3
|
46
52
|
end while !is_live
|
47
53
|
|
48
54
|
@@region_cache_semaphore.synchronize {
|
49
|
-
@@region_cache[@cloud_id] ||= @
|
55
|
+
@@region_cache[@cloud_id] ||= @region
|
50
56
|
}
|
51
57
|
|
52
58
|
tagBucket if !@config['scrub_mu_isms']
|
@@ -72,7 +78,7 @@ module MU
|
|
72
78
|
}
|
73
79
|
end
|
74
80
|
|
75
|
-
MU::Cloud::AWS.s3(credentials: @
|
81
|
+
MU::Cloud::AWS.s3(credentials: @credentials, region: @region).put_bucket_tagging(
|
76
82
|
bucket: @cloud_id,
|
77
83
|
tagging: {
|
78
84
|
tag_set: tagset
|
@@ -81,35 +87,90 @@ module MU
|
|
81
87
|
|
82
88
|
end
|
83
89
|
|
90
|
+
# @return [String]
|
91
|
+
def url
|
92
|
+
"https://#{@cloud_id}.s3.amazonaws.com"
|
93
|
+
end
|
94
|
+
|
95
|
+
# Grant access via our bucket policy to the specified resource
|
96
|
+
# @param principal [String]
|
97
|
+
# @param permissions [Array<String>]
|
98
|
+
# @param paths [Array<String>]
|
99
|
+
def allowPrincipal(principal, permissions: ["GetObject", "ListBucket"], paths: [""], doc_id: nil, name: nil)
|
100
|
+
@config['policies'] ||= []
|
101
|
+
name ||= principal.sub(/.*?([0-9a-z\-_]+)$/i, '\1')
|
102
|
+
@config['policies'] << {
|
103
|
+
"name" => name,
|
104
|
+
"grant_to" => [ { "identifier" => principal } ],
|
105
|
+
"permissions" => permissions.map { |p| "s3:"+p },
|
106
|
+
"flag" => "allow",
|
107
|
+
"targets" => paths.map { |p|
|
108
|
+
{
|
109
|
+
"path" => p,
|
110
|
+
"type" => "bucket",
|
111
|
+
"identifier" => @config['name']
|
112
|
+
}
|
113
|
+
}
|
114
|
+
}
|
115
|
+
|
116
|
+
applyPolicies(doc_id: doc_id)
|
117
|
+
end
|
118
|
+
|
84
119
|
# Called automatically by {MU::Deploy#createResources}
|
85
120
|
def groom
|
86
121
|
|
87
122
|
@@region_cache_semaphore.synchronize {
|
88
|
-
@@region_cache[@cloud_id] ||= @
|
123
|
+
@@region_cache[@cloud_id] ||= @region
|
89
124
|
}
|
90
125
|
tagBucket if !@config['scrub_mu_isms']
|
91
126
|
|
92
127
|
current = cloud_desc
|
93
|
-
if @config['policies']
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
128
|
+
applyPolicies if @config['policies']
|
129
|
+
|
130
|
+
if @config['versioning'] and current["versioning"].status != "Enabled"
|
131
|
+
MU.log "Enabling versioning on S3 bucket #{@cloud_id}", MU::NOTICE
|
132
|
+
MU::Cloud::AWS.s3(credentials: @credentials, region: @region).put_bucket_versioning(
|
133
|
+
bucket: @cloud_id,
|
134
|
+
versioning_configuration: {
|
135
|
+
mfa_delete: "Disabled",
|
136
|
+
status: "Enabled"
|
137
|
+
}
|
138
|
+
)
|
139
|
+
elsif !@config['versioning'] and current["versioning"].status == "Enabled"
|
140
|
+
MU.log "Suspending versioning on S3 bucket #{@cloud_id}", MU::NOTICE
|
141
|
+
MU::Cloud::AWS.s3(credentials: @credentials, region: @region).put_bucket_versioning(
|
142
|
+
bucket: @cloud_id,
|
143
|
+
versioning_configuration: {
|
144
|
+
mfa_delete: "Disabled",
|
145
|
+
status: "Suspended"
|
146
|
+
}
|
147
|
+
)
|
148
|
+
end
|
149
|
+
|
150
|
+
if @config['upload']
|
151
|
+
@config['upload'].each { |batch|
|
152
|
+
urlbase = "s3://"+@cloud_id+batch['destination']
|
153
|
+
urlbase += "/" if urlbase !~ /\/$/
|
154
|
+
upload_me = if File.directory?(batch['source'])
|
155
|
+
Dir[batch['source']+'/**/*'].reject {|d|
|
156
|
+
File.directory?(d)
|
157
|
+
}.map { |f|
|
158
|
+
[ f, urlbase+f.sub(/^#{Regexp.quote(batch['source'])}\/?/, '') ]
|
159
|
+
}
|
160
|
+
else
|
161
|
+
batch['source'].match(/([^\/]+)$/)
|
162
|
+
[ [batch['source'], urlbase+Regexp.last_match[1]] ]
|
163
|
+
end
|
99
164
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
MU::Cloud::AWS.s3(credentials: @config['credentials'], region: @config['region']).put_bucket_policy(
|
104
|
-
bucket: @cloud_id,
|
105
|
-
policy: JSON.generate(doc.values.first)
|
106
|
-
)
|
165
|
+
Hash[upload_me].each_pair { |file, url|
|
166
|
+
self.class.upload(url, file: file, credentials: @credentials, region: @region, acl: batch['acl'])
|
167
|
+
}
|
107
168
|
}
|
108
169
|
end
|
109
170
|
|
110
171
|
if @config['web'] and current["website"].nil?
|
111
172
|
MU.log "Enabling web service on S3 bucket #{@cloud_id}", MU::NOTICE
|
112
|
-
MU::Cloud::AWS.s3(credentials: @
|
173
|
+
MU::Cloud::AWS.s3(credentials: @credentials, region: @region).put_bucket_website(
|
113
174
|
bucket: @cloud_id,
|
114
175
|
website_configuration: {
|
115
176
|
error_document: {
|
@@ -120,32 +181,62 @@ module MU
|
|
120
181
|
}
|
121
182
|
}
|
122
183
|
)
|
184
|
+
['web_error_object', 'web_index_object'].each { |key|
|
185
|
+
begin
|
186
|
+
MU::Cloud::AWS.s3(credentials: @credentials, region: @region).head_object(
|
187
|
+
bucket: @cloud_id,
|
188
|
+
key: @config[key]
|
189
|
+
)
|
190
|
+
rescue Aws::S3::Errors::NotFound
|
191
|
+
MU.log "Uploading placeholder #{@config[key]} to bucket #{@cloud_id}"
|
192
|
+
MU::Cloud::AWS.s3(credentials: @credentials, region: @region).put_object(
|
193
|
+
acl: "public-read",
|
194
|
+
bucket: @cloud_id,
|
195
|
+
key: @config[key],
|
196
|
+
body: ""
|
197
|
+
)
|
198
|
+
end
|
199
|
+
}
|
200
|
+
# XXX check if error and index objs exist, and if not provide placeholders
|
123
201
|
elsif !@config['web'] and !current["website"].nil?
|
124
202
|
MU.log "Disabling web service on S3 bucket #{@cloud_id}", MU::NOTICE
|
125
|
-
MU::Cloud::AWS.s3(credentials: @
|
203
|
+
MU::Cloud::AWS.s3(credentials: @credentials, region: @region).delete_bucket_website(
|
126
204
|
bucket: @cloud_id
|
127
205
|
)
|
128
206
|
end
|
129
207
|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
mfa_delete: "Disabled",
|
136
|
-
status: "Enabled"
|
208
|
+
symbolify_keys = Proc.new { |parent|
|
209
|
+
if parent.is_a?(Hash)
|
210
|
+
newhash = {}
|
211
|
+
parent.each_pair { |k, v|
|
212
|
+
newhash[k.to_sym] = symbolify_keys.call(v)
|
137
213
|
}
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
214
|
+
newhash
|
215
|
+
elsif parent.is_a?(Array)
|
216
|
+
newarr = []
|
217
|
+
parent.each { |child|
|
218
|
+
newarr << symbolify_keys.call(child)
|
219
|
+
}
|
220
|
+
newarr
|
221
|
+
else
|
222
|
+
parent
|
223
|
+
end
|
224
|
+
}
|
225
|
+
|
226
|
+
if @config['cors']
|
227
|
+
MU.log "Setting CORS rules on #{@cloud_id}", details: @config['cors']
|
228
|
+
MU::Cloud::AWS.s3(credentials: @credentials, region: @region).put_bucket_cors(
|
142
229
|
bucket: @cloud_id,
|
143
|
-
|
144
|
-
|
145
|
-
status: "Suspended"
|
230
|
+
cors_configuration: {
|
231
|
+
cors_rules: symbolify_keys.call(@config['cors'])
|
146
232
|
}
|
147
233
|
)
|
148
234
|
end
|
235
|
+
|
236
|
+
MU.log "Bucket #{@config['name']}: s3://#{@cloud_id}", MU::SUMMARY
|
237
|
+
if @config['web']
|
238
|
+
MU.log "Bucket #{@config['name']} web access: http://#{@cloud_id}.s3-website-#{@region}.amazonaws.com/", MU::SUMMARY
|
239
|
+
end
|
149
240
|
end
|
150
241
|
|
151
242
|
# Upload a file to a bucket.
|
@@ -160,7 +251,7 @@ module MU
|
|
160
251
|
|
161
252
|
if file and !file.empty?
|
162
253
|
if !File.exist?(file) or !File.readable?(file)
|
163
|
-
raise MuError, "Unable to read #{file} for upload to #{url}"
|
254
|
+
raise MuError, "Unable to read #{file} for upload to #{url} (I'm at #{Dir.pwd}"
|
164
255
|
else
|
165
256
|
data = File.read(file)
|
166
257
|
end
|
@@ -177,12 +268,19 @@ module MU
|
|
177
268
|
|
178
269
|
begin
|
179
270
|
MU.log "Writing #{path} to S3 bucket #{bucket}"
|
180
|
-
|
271
|
+
params = {
|
181
272
|
acl: acl,
|
182
273
|
bucket: bucket,
|
183
274
|
key: path,
|
184
275
|
body: data
|
185
|
-
|
276
|
+
}
|
277
|
+
|
278
|
+
MIME_MAP.each_pair { |extension, content_type|
|
279
|
+
if path =~ /#{Regexp.quote(extension)}$/i
|
280
|
+
params[:content_type] = content_type
|
281
|
+
end
|
282
|
+
}
|
283
|
+
MU::Cloud::AWS.s3(region: region, credentials: credentials).put_object(params)
|
186
284
|
rescue Aws::S3::Errors => e
|
187
285
|
raise MuError, "Got #{e.inspect} trying to write #{path} to #{bucket} (region: #{region}, credentials: #{credentials})"
|
188
286
|
end
|
@@ -207,7 +305,7 @@ module MU
|
|
207
305
|
# @param ignoremaster [Boolean]: If true, will remove resources not flagged as originating from this Mu server
|
208
306
|
# @param region [String]: The cloud provider region
|
209
307
|
# @return [void]
|
210
|
-
def self.cleanup(noop: false, ignoremaster: false, region: MU.curRegion, credentials: nil, flags: {})
|
308
|
+
def self.cleanup(noop: false, deploy_id: MU.deploy_id, ignoremaster: false, region: MU.curRegion, credentials: nil, flags: {})
|
211
309
|
MU.log "AWS::Bucket.cleanup: need to support flags['known']", MU::DEBUG, details: flags
|
212
310
|
|
213
311
|
resp = MU::Cloud::AWS.s3(credentials: credentials, region: region).list_buckets
|
@@ -242,7 +340,7 @@ module MU
|
|
242
340
|
deploy_match = false
|
243
341
|
master_match = false
|
244
342
|
tags.each { |tag|
|
245
|
-
if tag.key == "MU-ID" and tag.value ==
|
343
|
+
if tag.key == "MU-ID" and tag.value == deploy_id
|
246
344
|
deploy_match = true
|
247
345
|
elsif tag.key == "MU-MASTER-IP" and tag.value == MU.mu_public_ip
|
248
346
|
master_match = true
|
@@ -254,6 +352,21 @@ module MU
|
|
254
352
|
MU::Cloud::AWS.s3(credentials: credentials, region: region).delete_bucket(bucket: bucket.name)
|
255
353
|
end
|
256
354
|
end
|
355
|
+
rescue Aws::S3::Errors::BucketNotEmpty => e
|
356
|
+
if flags["skipsnapshots"]
|
357
|
+
del = MU::Cloud::AWS.s3(credentials: credentials, region: region).list_objects(bucket: bucket.name).contents.map { |o| { key: o.key } }
|
358
|
+
del.concat(MU::Cloud::AWS.s3(credentials: credentials, region: region).list_object_versions(bucket: bucket.name).versions.map { |o| { key: o.key, version_id: o.version_id } })
|
359
|
+
|
360
|
+
MU.log "Purging #{del.size.to_s} objects and versions from #{bucket.name}"
|
361
|
+
begin
|
362
|
+
batch = del.slice!(0, (del.length >= 1000 ? 1000 : del.length))
|
363
|
+
MU::Cloud::AWS.s3(credentials: credentials, region: region).delete_objects(bucket: bucket.name, delete: { objects: batch } ) if !noop
|
364
|
+
end while del.size > 0
|
365
|
+
|
366
|
+
retry if !noop
|
367
|
+
else
|
368
|
+
MU.log "Bucket #{bucket.name} is non-empty, will preserve it and its contents. Use --skipsnapshots to forcibly remove.", MU::WARN
|
369
|
+
end
|
257
370
|
rescue Aws::S3::Errors::NoSuchTagSet, Aws::S3::Errors::PermanentRedirect
|
258
371
|
next
|
259
372
|
end
|
@@ -264,13 +377,13 @@ module MU
|
|
264
377
|
# Canonical Amazon Resource Number for this resource
|
265
378
|
# @return [String]
|
266
379
|
def arn
|
267
|
-
"arn:"+(MU::Cloud::AWS.isGovCloud?(@
|
380
|
+
"arn:"+(MU::Cloud::AWS.isGovCloud?(@region) ? "aws-us-gov" : "aws")+":s3:::"+@cloud_id
|
268
381
|
end
|
269
382
|
|
270
383
|
# Return the metadata for this user cofiguration
|
271
384
|
# @return [Hash]
|
272
385
|
def notify
|
273
|
-
desc = MU::Cloud::AWS::Bucket.describe_bucket(@cloud_id, credentials: @
|
386
|
+
desc = MU::Cloud::AWS::Bucket.describe_bucket(@cloud_id, credentials: @credentials, region: @region)
|
274
387
|
MU.structToHash(desc)
|
275
388
|
end
|
276
389
|
|
@@ -279,9 +392,32 @@ module MU
|
|
279
392
|
def self.find(**args)
|
280
393
|
found = {}
|
281
394
|
|
395
|
+
args[:region] ||= MU::Cloud::AWS.myRegion(args[:credentials])
|
396
|
+
if args[:flags] and args[:flags][:allregions]
|
397
|
+
args[:allregions] = args[:flags][:allregions]
|
398
|
+
end
|
399
|
+
minimal = args[:full] ? false : true
|
400
|
+
|
401
|
+
location = Proc.new { |name|
|
402
|
+
begin
|
403
|
+
loc_resp = MU::Cloud::AWS.s3(credentials: args[:credentials], region: args[:region]).get_bucket_location(bucket: name)
|
404
|
+
|
405
|
+
if loc_resp.location_constraint and !loc_resp.location_constraint.empty?
|
406
|
+
loc_resp.location_constraint
|
407
|
+
else
|
408
|
+
nil
|
409
|
+
end
|
410
|
+
rescue Aws::S3::Errors::AccessDenied
|
411
|
+
nil
|
412
|
+
end
|
413
|
+
}
|
414
|
+
|
282
415
|
if args[:cloud_id]
|
283
416
|
begin
|
284
|
-
found[args[:cloud_id]] = describe_bucket(args[:cloud_id], minimal:
|
417
|
+
found[args[:cloud_id]] = describe_bucket(args[:cloud_id], minimal: minimal, credentials: args[:credentials], region: args[:region])
|
418
|
+
found[args[:cloud_id]]['region'] ||= location.call(args[:cloud_id])
|
419
|
+
found[args[:cloud_id]]['region'] ||= args[:region]
|
420
|
+
found[args[:cloud_id]]['name'] ||= args[:cloud_id]
|
285
421
|
rescue ::Aws::S3::Errors::NoSuchBucket
|
286
422
|
end
|
287
423
|
else
|
@@ -289,11 +425,14 @@ module MU
|
|
289
425
|
if resp and resp.buckets
|
290
426
|
resp.buckets.each { |b|
|
291
427
|
begin
|
292
|
-
|
293
|
-
if !
|
428
|
+
bucket_region = location.call(b.name)
|
429
|
+
if !args[:allregions] and bucket_region != args[:region]
|
294
430
|
next
|
295
431
|
end
|
296
|
-
|
432
|
+
bucket_region ||= args[:region]
|
433
|
+
found[b.name] = describe_bucket(b.name, minimal: minimal, credentials: args[:credentials], region: bucket_region)
|
434
|
+
found[b.name]["region"] ||= bucket_region
|
435
|
+
found[b.name]['name'] ||= b.name
|
297
436
|
rescue Aws::S3::Errors::AccessDenied
|
298
437
|
end
|
299
438
|
}
|
@@ -303,6 +442,28 @@ module MU
|
|
303
442
|
found
|
304
443
|
end
|
305
444
|
|
445
|
+
# Reverse-map our cloud description into a runnable config hash.
|
446
|
+
# We assume that any values we have in +@config+ are placeholders, and
|
447
|
+
# calculate our own accordingly based on what's live in the cloud.
|
448
|
+
def toKitten(**_args)
|
449
|
+
bok = {
|
450
|
+
"cloud" => "AWS",
|
451
|
+
"credentials" => @credentials,
|
452
|
+
"cloud_id" => @cloud_id
|
453
|
+
}
|
454
|
+
|
455
|
+
if @cloud_id =~ /espier/i
|
456
|
+
MU.log @cloud_id, MU::WARN, details: cloud_desc
|
457
|
+
end
|
458
|
+
|
459
|
+
if !cloud_desc
|
460
|
+
MU.log "toKitten failed to load a cloud_desc from #{@cloud_id}", MU::ERR, details: @config
|
461
|
+
return nil
|
462
|
+
end
|
463
|
+
|
464
|
+
nil
|
465
|
+
end
|
466
|
+
|
306
467
|
# Cloud-specific configuration properties.
|
307
468
|
# @param _config [MU::Config]: The calling MU::Config object
|
308
469
|
# @return [Array<Array,Hash>]: List of required fields, and json-schema Hash of cloud-specific configuration parameters for this resource
|
@@ -310,15 +471,67 @@ module MU
|
|
310
471
|
toplevel_required = []
|
311
472
|
schema = {
|
312
473
|
"policies" => MU::Cloud.resourceClass("AWS", "Role").condition_schema,
|
313
|
-
"
|
314
|
-
"
|
315
|
-
|
316
|
-
|
474
|
+
"upload" => {
|
475
|
+
"items" => {
|
476
|
+
"properties" => {
|
477
|
+
"acl" => {
|
478
|
+
"type" => "string",
|
479
|
+
"enum" => ["private", "public-read", "public-read-write", "authenticated-read"],
|
480
|
+
"default" => "private"
|
481
|
+
}
|
482
|
+
}
|
483
|
+
}
|
317
484
|
},
|
318
485
|
"storage_class" => {
|
319
486
|
"type" => "string",
|
320
487
|
"enum" => ["STANDARD", "REDUCED_REDUNDANCY", "STANDARD_IA", "ONEZONE_IA", "INTELLIGENT_TIERING", "GLACIER"],
|
321
488
|
"default" => "STANDARD"
|
489
|
+
},
|
490
|
+
"cors" => {
|
491
|
+
"type" => "array",
|
492
|
+
"items" => {
|
493
|
+
"type" => "object",
|
494
|
+
"description" => "AWS S3 Cross-origin resource sharing policy",
|
495
|
+
"required" => ["allowed_origins"],
|
496
|
+
"properties" => {
|
497
|
+
"allowed_headers" => {
|
498
|
+
"type" => "array",
|
499
|
+
"default" => ["*"],
|
500
|
+
"items" => {
|
501
|
+
"type" => "string",
|
502
|
+
"description" => "Specifies which headers are allowed in a preflight request through the +Access-Control-Request-Headers+ header."
|
503
|
+
}
|
504
|
+
},
|
505
|
+
"allowed_methods" => {
|
506
|
+
"type" => "array",
|
507
|
+
"default" => ["GET"],
|
508
|
+
"items" => {
|
509
|
+
"type" => "string",
|
510
|
+
"enum" => %w{GET PUT POST DELETE HEAD},
|
511
|
+
"description" => "Specifies which HTTP methods for which cross-domain request are permitted"
|
512
|
+
}
|
513
|
+
},
|
514
|
+
"allowed_origins" => {
|
515
|
+
"type" => "array",
|
516
|
+
"items" => {
|
517
|
+
"type" => "string",
|
518
|
+
"description" => "Origins (in URL form) for which cross-domain request are permitted"
|
519
|
+
}
|
520
|
+
},
|
521
|
+
"expose_headers" => {
|
522
|
+
"type" => "array",
|
523
|
+
"items" => {
|
524
|
+
"type" => "string",
|
525
|
+
"description" => "Headers in the response which should be visible to the requesting application"
|
526
|
+
}
|
527
|
+
},
|
528
|
+
"max_age_seconds" => {
|
529
|
+
"type" => "integer",
|
530
|
+
"default" => 3600,
|
531
|
+
"description" => "Maximum cache time for preflight requests"
|
532
|
+
}
|
533
|
+
}
|
534
|
+
}
|
322
535
|
}
|
323
536
|
}
|
324
537
|
[toplevel_required, schema]
|
@@ -384,6 +597,27 @@ module MU
|
|
384
597
|
desc
|
385
598
|
end
|
386
599
|
|
600
|
+
private
|
601
|
+
|
602
|
+
def applyPolicies(doc_id: nil)
|
603
|
+
return if !@config['policies']
|
604
|
+
|
605
|
+
@config['policies'].each { |pol|
|
606
|
+
pol['grant_to'] ||= [
|
607
|
+
{ "id" => "*" }
|
608
|
+
]
|
609
|
+
}
|
610
|
+
|
611
|
+
policy_docs = MU::Cloud.resourceClass("AWS", "Role").genPolicyDocument(@config['policies'], deploy_obj: @deploy, bucket_style: true, version: "2008-10-17", doc_id: doc_id)
|
612
|
+
policy_docs.each { |doc|
|
613
|
+
MU.log "Applying S3 bucket policy #{doc.keys.first} to bucket #{@cloud_id}", MU::NOTICE, details: JSON.pretty_generate(doc.values.first)
|
614
|
+
MU::Cloud::AWS.s3(credentials: @credentials, region: @region).put_bucket_policy(
|
615
|
+
bucket: @cloud_id,
|
616
|
+
policy: JSON.generate(doc.values.first)
|
617
|
+
)
|
618
|
+
}
|
619
|
+
end
|
620
|
+
|
387
621
|
end
|
388
622
|
end
|
389
623
|
end
|