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.
Files changed (156) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +1 -1
  3. data/ansible/roles/mu-nat/tasks/main.yml +3 -0
  4. data/bin/mu-adopt +12 -1
  5. data/bin/mu-aws-setup +41 -7
  6. data/bin/mu-azure-setup +34 -0
  7. data/bin/mu-configure +214 -119
  8. data/bin/mu-gcp-setup +37 -2
  9. data/bin/mu-load-config.rb +2 -1
  10. data/bin/mu-node-manage +3 -0
  11. data/bin/mu-refresh-ssl +67 -0
  12. data/bin/mu-run-tests +28 -6
  13. data/bin/mu-self-update +30 -10
  14. data/bin/mu-upload-chef-artifacts +30 -26
  15. data/cloud-mu.gemspec +10 -8
  16. data/cookbooks/mu-master/attributes/default.rb +5 -1
  17. data/cookbooks/mu-master/metadata.rb +2 -2
  18. data/cookbooks/mu-master/recipes/default.rb +81 -26
  19. data/cookbooks/mu-master/recipes/init.rb +197 -62
  20. data/cookbooks/mu-master/recipes/update_nagios_only.rb +1 -1
  21. data/cookbooks/mu-master/recipes/vault.rb +78 -77
  22. data/cookbooks/mu-master/templates/default/mods/rewrite.conf.erb +1 -0
  23. data/cookbooks/mu-master/templates/default/nagios.conf.erb +103 -0
  24. data/cookbooks/mu-master/templates/default/web_app.conf.erb +14 -30
  25. data/cookbooks/mu-tools/attributes/default.rb +12 -0
  26. data/cookbooks/mu-tools/files/centos-6/CentOS-Base.repo +47 -0
  27. data/cookbooks/mu-tools/libraries/helper.rb +98 -4
  28. data/cookbooks/mu-tools/libraries/monkey.rb +1 -1
  29. data/cookbooks/mu-tools/recipes/apply_security.rb +31 -9
  30. data/cookbooks/mu-tools/recipes/aws_api.rb +8 -2
  31. data/cookbooks/mu-tools/recipes/base_repositories.rb +1 -1
  32. data/cookbooks/mu-tools/recipes/gcloud.rb +2 -9
  33. data/cookbooks/mu-tools/recipes/google_api.rb +7 -0
  34. data/cookbooks/mu-tools/recipes/rsyslog.rb +8 -1
  35. data/cookbooks/mu-tools/resources/disk.rb +113 -42
  36. data/cookbooks/mu-tools/resources/mommacat_request.rb +1 -2
  37. data/cookbooks/mu-tools/templates/centos-8/sshd_config.erb +215 -0
  38. data/extras/Gemfile.lock.bootstrap +394 -0
  39. data/extras/bucketstubs/error.html +0 -0
  40. data/extras/bucketstubs/index.html +0 -0
  41. data/extras/clean-stock-amis +11 -3
  42. data/extras/generate-stock-images +6 -3
  43. data/extras/git_rpm/build.sh +20 -0
  44. data/extras/git_rpm/mugit.spec +53 -0
  45. data/extras/image-generators/AWS/centos7.yaml +19 -16
  46. data/extras/image-generators/AWS/{rhel7.yaml → rhel71.yaml} +0 -0
  47. data/extras/image-generators/AWS/{win2k12.yaml → win2k12r2.yaml} +0 -0
  48. data/extras/image-generators/VMWare/centos8.yaml +15 -0
  49. data/extras/openssl_rpm/build.sh +19 -0
  50. data/extras/openssl_rpm/mussl.spec +46 -0
  51. data/extras/python_rpm/muthon.spec +14 -4
  52. data/extras/ruby_rpm/muby.spec +9 -5
  53. data/extras/sqlite_rpm/build.sh +19 -0
  54. data/extras/sqlite_rpm/muqlite.spec +47 -0
  55. data/install/installer +7 -5
  56. data/modules/mommacat.ru +2 -2
  57. data/modules/mu.rb +14 -7
  58. data/modules/mu/adoption.rb +5 -5
  59. data/modules/mu/cleanup.rb +47 -25
  60. data/modules/mu/cloud.rb +29 -1
  61. data/modules/mu/cloud/dnszone.rb +0 -2
  62. data/modules/mu/cloud/machine_images.rb +1 -1
  63. data/modules/mu/cloud/providers.rb +6 -1
  64. data/modules/mu/cloud/resource_base.rb +16 -7
  65. data/modules/mu/cloud/ssh_sessions.rb +5 -1
  66. data/modules/mu/cloud/wrappers.rb +20 -7
  67. data/modules/mu/config.rb +28 -12
  68. data/modules/mu/config/bucket.rb +31 -2
  69. data/modules/mu/config/cache_cluster.rb +1 -1
  70. data/modules/mu/config/cdn.rb +100 -0
  71. data/modules/mu/config/container_cluster.rb +1 -1
  72. data/modules/mu/config/database.rb +3 -3
  73. data/modules/mu/config/dnszone.rb +4 -3
  74. data/modules/mu/config/endpoint.rb +1 -0
  75. data/modules/mu/config/firewall_rule.rb +1 -1
  76. data/modules/mu/config/function.rb +16 -7
  77. data/modules/mu/config/job.rb +89 -0
  78. data/modules/mu/config/notifier.rb +7 -18
  79. data/modules/mu/config/ref.rb +55 -9
  80. data/modules/mu/config/schema_helpers.rb +12 -3
  81. data/modules/mu/config/server.rb +11 -5
  82. data/modules/mu/config/server_pool.rb +2 -2
  83. data/modules/mu/config/vpc.rb +11 -10
  84. data/modules/mu/defaults/AWS.yaml +106 -106
  85. data/modules/mu/deploy.rb +40 -14
  86. data/modules/mu/groomers/chef.rb +2 -2
  87. data/modules/mu/master.rb +70 -3
  88. data/modules/mu/mommacat.rb +28 -9
  89. data/modules/mu/mommacat/daemon.rb +13 -7
  90. data/modules/mu/mommacat/naming.rb +2 -2
  91. data/modules/mu/mommacat/search.rb +16 -5
  92. data/modules/mu/mommacat/storage.rb +67 -32
  93. data/modules/mu/providers/aws.rb +298 -85
  94. data/modules/mu/providers/aws/alarm.rb +5 -5
  95. data/modules/mu/providers/aws/bucket.rb +284 -50
  96. data/modules/mu/providers/aws/cache_cluster.rb +26 -26
  97. data/modules/mu/providers/aws/cdn.rb +782 -0
  98. data/modules/mu/providers/aws/collection.rb +16 -16
  99. data/modules/mu/providers/aws/container_cluster.rb +84 -64
  100. data/modules/mu/providers/aws/database.rb +59 -55
  101. data/modules/mu/providers/aws/dnszone.rb +29 -12
  102. data/modules/mu/providers/aws/endpoint.rb +535 -50
  103. data/modules/mu/providers/aws/firewall_rule.rb +32 -26
  104. data/modules/mu/providers/aws/folder.rb +1 -1
  105. data/modules/mu/providers/aws/function.rb +300 -134
  106. data/modules/mu/providers/aws/group.rb +16 -14
  107. data/modules/mu/providers/aws/habitat.rb +4 -4
  108. data/modules/mu/providers/aws/job.rb +469 -0
  109. data/modules/mu/providers/aws/loadbalancer.rb +67 -45
  110. data/modules/mu/providers/aws/log.rb +17 -17
  111. data/modules/mu/providers/aws/msg_queue.rb +22 -13
  112. data/modules/mu/providers/aws/nosqldb.rb +99 -8
  113. data/modules/mu/providers/aws/notifier.rb +137 -65
  114. data/modules/mu/providers/aws/role.rb +119 -83
  115. data/modules/mu/providers/aws/search_domain.rb +166 -30
  116. data/modules/mu/providers/aws/server.rb +209 -118
  117. data/modules/mu/providers/aws/server_pool.rb +95 -130
  118. data/modules/mu/providers/aws/storage_pool.rb +19 -11
  119. data/modules/mu/providers/aws/user.rb +5 -5
  120. data/modules/mu/providers/aws/userdata/linux.erb +5 -4
  121. data/modules/mu/providers/aws/vpc.rb +109 -54
  122. data/modules/mu/providers/aws/vpc_subnet.rb +43 -39
  123. data/modules/mu/providers/azure.rb +78 -12
  124. data/modules/mu/providers/azure/server.rb +20 -4
  125. data/modules/mu/providers/cloudformation/server.rb +1 -1
  126. data/modules/mu/providers/google.rb +21 -5
  127. data/modules/mu/providers/google/bucket.rb +1 -1
  128. data/modules/mu/providers/google/container_cluster.rb +1 -1
  129. data/modules/mu/providers/google/database.rb +1 -1
  130. data/modules/mu/providers/google/firewall_rule.rb +1 -1
  131. data/modules/mu/providers/google/folder.rb +7 -3
  132. data/modules/mu/providers/google/function.rb +66 -31
  133. data/modules/mu/providers/google/group.rb +1 -1
  134. data/modules/mu/providers/google/habitat.rb +1 -1
  135. data/modules/mu/providers/google/loadbalancer.rb +1 -1
  136. data/modules/mu/providers/google/role.rb +6 -3
  137. data/modules/mu/providers/google/server.rb +1 -1
  138. data/modules/mu/providers/google/server_pool.rb +1 -1
  139. data/modules/mu/providers/google/user.rb +1 -1
  140. data/modules/mu/providers/google/vpc.rb +28 -3
  141. data/modules/tests/aws-jobs-functions.yaml +46 -0
  142. data/modules/tests/aws-servers-with-handrolled-iam.yaml +37 -0
  143. data/modules/tests/centos6.yaml +4 -0
  144. data/modules/tests/centos7.yaml +4 -0
  145. data/modules/tests/ecs.yaml +2 -2
  146. data/modules/tests/eks.yaml +1 -1
  147. data/modules/tests/functions/node-function/lambda_function.js +10 -0
  148. data/modules/tests/functions/python-function/lambda_function.py +12 -0
  149. data/modules/tests/k8s.yaml +1 -1
  150. data/modules/tests/microservice_app.yaml +288 -0
  151. data/modules/tests/rds.yaml +5 -5
  152. data/modules/tests/regrooms/rds.yaml +5 -5
  153. data/modules/tests/server-with-scrub-muisms.yaml +1 -1
  154. data/modules/tests/super_complex_bok.yml +2 -2
  155. data/modules/tests/super_simple_bok.yml +2 -2
  156. 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: @config["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: @config["region"],
83
- credentials: @config['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(MU.deploy_id)
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: @config['credentials'], region: @config['region']).create_bucket(
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: @config['region'], credentials: @credentials).values.first
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: @config['region'], credentials: @credentials).values.first
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] ||= @config['region']
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: @config['credentials'], region: @config['region']).put_bucket_tagging(
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] ||= @config['region']
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
- @config['policies'].each { |pol|
95
- pol['grant_to'] ||= [
96
- { "id" => "*" }
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
- policy_docs = MU::Cloud.resourceClass("AWS", "Role").genPolicyDocument(@config['policies'], deploy_obj: @deploy, bucket_style: true)
101
- policy_docs.each { |doc|
102
- MU.log "Applying S3 bucket policy #{doc.keys.first} to bucket #{@cloud_id}", MU::NOTICE, details: JSON.pretty_generate(doc.values.first)
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: @config['credentials'], region: @config['region']).put_bucket_website(
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: @config['credentials'], region: @config['region']).delete_bucket_website(
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
- 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: @config['credentials'], region: @config['region']).put_bucket_versioning(
133
- bucket: @cloud_id,
134
- versioning_configuration: {
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
- 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: @config['credentials'], region: @config['region']).put_bucket_versioning(
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
- versioning_configuration: {
144
- mfa_delete: "Disabled",
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
- MU::Cloud::AWS.s3(region: region, credentials: credentials).put_object(
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 == MU.deploy_id
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?(@config["region"]) ? "aws-us-gov" : "aws")+":s3:::"+@cloud_id
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: @config['credentials'], region: @config['region'])
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: true, credentials: args[:credentials], region: args[:region])
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
- loc_resp = MU::Cloud::AWS.s3(credentials: args[:credentials], region: args[:region]).get_bucket_location(bucket: b.name)
293
- if !loc_resp or loc_resp.location_constraint != args[:region]
428
+ bucket_region = location.call(b.name)
429
+ if !args[:allregions] and bucket_region != args[:region]
294
430
  next
295
431
  end
296
- found[b.name] = describe_bucket(b.name, minimal: true, credentials: args[:credentials], region: args[:region])
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
- "acl" => {
314
- "type" => "string",
315
- "enum" => ["private", "public-read", "public-read-write", "authenticated-read"],
316
- "default" => "private"
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