cloud-mu 3.1.3 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Dockerfile +15 -3
- data/ansible/roles/mu-windows/README.md +33 -0
- data/ansible/roles/mu-windows/defaults/main.yml +2 -0
- data/ansible/roles/mu-windows/files/LaunchConfig.json +9 -0
- data/ansible/roles/mu-windows/files/config.xml +76 -0
- data/ansible/roles/mu-windows/handlers/main.yml +2 -0
- data/ansible/roles/mu-windows/meta/main.yml +53 -0
- data/ansible/roles/mu-windows/tasks/main.yml +36 -0
- data/ansible/roles/mu-windows/tests/inventory +2 -0
- data/ansible/roles/mu-windows/tests/test.yml +5 -0
- data/ansible/roles/mu-windows/vars/main.yml +2 -0
- data/bin/mu-adopt +21 -13
- data/bin/mu-azure-tests +57 -0
- data/bin/mu-cleanup +2 -4
- data/bin/mu-configure +52 -0
- data/bin/mu-deploy +3 -3
- data/bin/mu-findstray-tests +25 -0
- data/bin/mu-gen-docs +2 -4
- data/bin/mu-load-config.rb +4 -4
- data/bin/mu-node-manage +15 -16
- data/bin/mu-run-tests +147 -37
- data/cloud-mu.gemspec +22 -20
- data/cookbooks/mu-activedirectory/resources/domain.rb +4 -4
- data/cookbooks/mu-activedirectory/resources/domain_controller.rb +4 -4
- data/cookbooks/mu-tools/libraries/helper.rb +3 -2
- data/cookbooks/mu-tools/libraries/monkey.rb +35 -0
- data/cookbooks/mu-tools/recipes/apply_security.rb +14 -14
- data/cookbooks/mu-tools/recipes/aws_api.rb +9 -0
- data/cookbooks/mu-tools/recipes/eks.rb +2 -2
- data/cookbooks/mu-tools/recipes/google_api.rb +2 -2
- data/cookbooks/mu-tools/recipes/selinux.rb +2 -1
- data/cookbooks/mu-tools/recipes/windows-client.rb +163 -164
- data/cookbooks/mu-tools/resources/disk.rb +1 -1
- data/cookbooks/mu-tools/resources/windows_users.rb +44 -43
- data/extras/clean-stock-amis +25 -19
- data/extras/generate-stock-images +1 -0
- data/extras/image-generators/AWS/win2k12.yaml +18 -13
- data/extras/image-generators/AWS/win2k16.yaml +18 -13
- data/extras/image-generators/AWS/win2k19.yaml +21 -0
- data/extras/image-generators/Google/centos6.yaml +1 -0
- data/extras/image-generators/Google/centos7.yaml +1 -1
- data/modules/mommacat.ru +6 -16
- data/modules/mu.rb +158 -111
- data/modules/mu/adoption.rb +404 -71
- data/modules/mu/cleanup.rb +221 -306
- data/modules/mu/cloud.rb +129 -1633
- data/modules/mu/cloud/database.rb +49 -0
- data/modules/mu/cloud/dnszone.rb +44 -0
- data/modules/mu/cloud/machine_images.rb +212 -0
- data/modules/mu/cloud/providers.rb +81 -0
- data/modules/mu/cloud/resource_base.rb +926 -0
- data/modules/mu/cloud/server.rb +40 -0
- data/modules/mu/cloud/server_pool.rb +1 -0
- data/modules/mu/cloud/ssh_sessions.rb +228 -0
- data/modules/mu/cloud/winrm_sessions.rb +237 -0
- data/modules/mu/cloud/wrappers.rb +169 -0
- data/modules/mu/config.rb +171 -1767
- data/modules/mu/config/alarm.rb +2 -6
- data/modules/mu/config/bucket.rb +32 -3
- data/modules/mu/config/cache_cluster.rb +2 -2
- data/modules/mu/config/cdn.rb +100 -0
- data/modules/mu/config/collection.rb +4 -4
- data/modules/mu/config/container_cluster.rb +9 -4
- data/modules/mu/config/database.rb +84 -105
- data/modules/mu/config/database.yml +1 -2
- data/modules/mu/config/dnszone.rb +10 -9
- data/modules/mu/config/doc_helpers.rb +516 -0
- data/modules/mu/config/endpoint.rb +5 -4
- data/modules/mu/config/firewall_rule.rb +103 -4
- data/modules/mu/config/folder.rb +4 -4
- data/modules/mu/config/function.rb +19 -10
- data/modules/mu/config/group.rb +4 -4
- data/modules/mu/config/habitat.rb +4 -4
- data/modules/mu/config/job.rb +89 -0
- data/modules/mu/config/loadbalancer.rb +60 -14
- data/modules/mu/config/log.rb +4 -4
- data/modules/mu/config/msg_queue.rb +4 -4
- data/modules/mu/config/nosqldb.rb +4 -4
- data/modules/mu/config/notifier.rb +10 -21
- data/modules/mu/config/ref.rb +411 -0
- data/modules/mu/config/role.rb +4 -4
- data/modules/mu/config/schema_helpers.rb +509 -0
- data/modules/mu/config/search_domain.rb +4 -4
- data/modules/mu/config/server.rb +98 -71
- data/modules/mu/config/server.yml +1 -0
- data/modules/mu/config/server_pool.rb +5 -9
- data/modules/mu/config/storage_pool.rb +1 -1
- data/modules/mu/config/tail.rb +200 -0
- data/modules/mu/config/user.rb +4 -4
- data/modules/mu/config/vpc.rb +71 -27
- data/modules/mu/config/vpc.yml +0 -1
- data/modules/mu/defaults/AWS.yaml +91 -68
- data/modules/mu/defaults/Azure.yaml +1 -0
- data/modules/mu/defaults/Google.yaml +3 -2
- data/modules/mu/deploy.rb +43 -26
- data/modules/mu/groomer.rb +17 -2
- data/modules/mu/groomers/ansible.rb +188 -41
- data/modules/mu/groomers/chef.rb +116 -55
- data/modules/mu/logger.rb +127 -148
- data/modules/mu/master.rb +410 -2
- data/modules/mu/master/chef.rb +3 -4
- data/modules/mu/master/ldap.rb +3 -3
- data/modules/mu/master/ssl.rb +12 -3
- data/modules/mu/mommacat.rb +218 -2612
- data/modules/mu/mommacat/daemon.rb +403 -0
- data/modules/mu/mommacat/naming.rb +473 -0
- data/modules/mu/mommacat/search.rb +495 -0
- data/modules/mu/mommacat/storage.rb +722 -0
- data/modules/mu/{clouds → providers}/README.md +1 -1
- data/modules/mu/{clouds → providers}/aws.rb +380 -122
- data/modules/mu/{clouds → providers}/aws/alarm.rb +7 -5
- data/modules/mu/{clouds → providers}/aws/bucket.rb +297 -59
- data/modules/mu/{clouds → providers}/aws/cache_cluster.rb +37 -71
- data/modules/mu/providers/aws/cdn.rb +782 -0
- data/modules/mu/{clouds → providers}/aws/collection.rb +26 -25
- data/modules/mu/{clouds → providers}/aws/container_cluster.rb +724 -744
- data/modules/mu/providers/aws/database.rb +1744 -0
- data/modules/mu/{clouds → providers}/aws/dnszone.rb +88 -70
- data/modules/mu/providers/aws/endpoint.rb +1072 -0
- data/modules/mu/{clouds → providers}/aws/firewall_rule.rb +220 -247
- data/modules/mu/{clouds → providers}/aws/folder.rb +8 -8
- data/modules/mu/{clouds → providers}/aws/function.rb +300 -142
- data/modules/mu/{clouds → providers}/aws/group.rb +31 -29
- data/modules/mu/{clouds → providers}/aws/habitat.rb +18 -15
- data/modules/mu/providers/aws/job.rb +466 -0
- data/modules/mu/{clouds → providers}/aws/loadbalancer.rb +66 -56
- data/modules/mu/{clouds → providers}/aws/log.rb +17 -14
- data/modules/mu/{clouds → providers}/aws/msg_queue.rb +29 -19
- data/modules/mu/{clouds → providers}/aws/nosqldb.rb +114 -16
- data/modules/mu/{clouds → providers}/aws/notifier.rb +142 -65
- data/modules/mu/{clouds → providers}/aws/role.rb +158 -118
- data/modules/mu/{clouds → providers}/aws/search_domain.rb +201 -59
- data/modules/mu/{clouds → providers}/aws/server.rb +844 -1139
- data/modules/mu/{clouds → providers}/aws/server_pool.rb +74 -65
- data/modules/mu/{clouds → providers}/aws/storage_pool.rb +26 -44
- data/modules/mu/{clouds → providers}/aws/user.rb +24 -25
- data/modules/mu/{clouds → providers}/aws/userdata/README.md +0 -0
- data/modules/mu/{clouds → providers}/aws/userdata/linux.erb +5 -4
- data/modules/mu/{clouds → providers}/aws/userdata/windows.erb +2 -1
- data/modules/mu/{clouds → providers}/aws/vpc.rb +525 -931
- data/modules/mu/providers/aws/vpc_subnet.rb +286 -0
- data/modules/mu/{clouds → providers}/azure.rb +29 -9
- data/modules/mu/{clouds → providers}/azure/container_cluster.rb +3 -8
- data/modules/mu/{clouds → providers}/azure/firewall_rule.rb +18 -11
- data/modules/mu/{clouds → providers}/azure/habitat.rb +8 -6
- data/modules/mu/{clouds → providers}/azure/loadbalancer.rb +5 -5
- data/modules/mu/{clouds → providers}/azure/role.rb +8 -10
- data/modules/mu/{clouds → providers}/azure/server.rb +97 -49
- data/modules/mu/{clouds → providers}/azure/user.rb +6 -8
- data/modules/mu/{clouds → providers}/azure/userdata/README.md +0 -0
- data/modules/mu/{clouds → providers}/azure/userdata/linux.erb +0 -0
- data/modules/mu/{clouds → providers}/azure/userdata/windows.erb +0 -0
- data/modules/mu/{clouds → providers}/azure/vpc.rb +16 -21
- data/modules/mu/{clouds → providers}/cloudformation.rb +18 -7
- data/modules/mu/{clouds → providers}/cloudformation/alarm.rb +3 -3
- data/modules/mu/{clouds → providers}/cloudformation/cache_cluster.rb +3 -3
- data/modules/mu/{clouds → providers}/cloudformation/collection.rb +3 -3
- data/modules/mu/{clouds → providers}/cloudformation/database.rb +6 -17
- data/modules/mu/{clouds → providers}/cloudformation/dnszone.rb +3 -3
- data/modules/mu/{clouds → providers}/cloudformation/firewall_rule.rb +3 -3
- data/modules/mu/{clouds → providers}/cloudformation/loadbalancer.rb +3 -3
- data/modules/mu/{clouds → providers}/cloudformation/log.rb +3 -3
- data/modules/mu/{clouds → providers}/cloudformation/server.rb +7 -7
- data/modules/mu/{clouds → providers}/cloudformation/server_pool.rb +5 -5
- data/modules/mu/{clouds → providers}/cloudformation/vpc.rb +5 -7
- data/modules/mu/{clouds → providers}/docker.rb +0 -0
- data/modules/mu/{clouds → providers}/google.rb +68 -30
- data/modules/mu/{clouds → providers}/google/bucket.rb +13 -15
- data/modules/mu/{clouds → providers}/google/container_cluster.rb +85 -78
- data/modules/mu/{clouds → providers}/google/database.rb +11 -21
- data/modules/mu/{clouds → providers}/google/firewall_rule.rb +15 -14
- data/modules/mu/{clouds → providers}/google/folder.rb +20 -17
- data/modules/mu/{clouds → providers}/google/function.rb +140 -168
- data/modules/mu/{clouds → providers}/google/group.rb +29 -34
- data/modules/mu/{clouds → providers}/google/habitat.rb +21 -22
- data/modules/mu/{clouds → providers}/google/loadbalancer.rb +19 -21
- data/modules/mu/{clouds → providers}/google/role.rb +94 -58
- data/modules/mu/{clouds → providers}/google/server.rb +243 -156
- data/modules/mu/{clouds → providers}/google/server_pool.rb +26 -45
- data/modules/mu/{clouds → providers}/google/user.rb +95 -31
- data/modules/mu/{clouds → providers}/google/userdata/README.md +0 -0
- data/modules/mu/{clouds → providers}/google/userdata/linux.erb +0 -0
- data/modules/mu/{clouds → providers}/google/userdata/windows.erb +0 -0
- data/modules/mu/{clouds → providers}/google/vpc.rb +103 -79
- data/modules/tests/aws-jobs-functions.yaml +46 -0
- data/modules/tests/bucket.yml +4 -0
- data/modules/tests/centos6.yaml +15 -0
- data/modules/tests/centos7.yaml +15 -0
- data/modules/tests/centos8.yaml +12 -0
- data/modules/tests/ecs.yaml +23 -0
- 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/includes-and-params.yaml +2 -1
- data/modules/tests/microservice_app.yaml +288 -0
- data/modules/tests/rds.yaml +108 -0
- data/modules/tests/regrooms/aws-iam.yaml +201 -0
- data/modules/tests/regrooms/bucket.yml +19 -0
- data/modules/tests/regrooms/rds.yaml +123 -0
- data/modules/tests/server-with-scrub-muisms.yaml +2 -1
- data/modules/tests/super_complex_bok.yml +2 -2
- data/modules/tests/super_simple_bok.yml +3 -5
- data/modules/tests/win2k12.yaml +17 -5
- data/modules/tests/win2k16.yaml +25 -0
- data/modules/tests/win2k19.yaml +25 -0
- data/requirements.txt +1 -0
- data/spec/mu/clouds/azure_spec.rb +2 -2
- metadata +240 -154
- data/extras/image-generators/AWS/windows.yaml +0 -18
- data/modules/mu/clouds/aws/database.rb +0 -1985
- data/modules/mu/clouds/aws/endpoint.rb +0 -592
|
@@ -30,7 +30,7 @@ module MU
|
|
|
30
30
|
end
|
|
31
31
|
end
|
|
32
32
|
|
|
33
|
-
@mu_name ||= @deploy.getResourceName(@config["name"])
|
|
33
|
+
@mu_name ||= @deploy.getResourceName(@config["name"], max_length: 64)
|
|
34
34
|
end
|
|
35
35
|
|
|
36
36
|
# Called automatically by {MU::Deploy#createResources}
|
|
@@ -43,7 +43,7 @@ module MU
|
|
|
43
43
|
|
|
44
44
|
policy_name = @mu_name+"-"+policy.keys.first.upcase
|
|
45
45
|
MU.log "Creating IAM policy #{policy_name}"
|
|
46
|
-
|
|
46
|
+
MU::Cloud::AWS.iam(credentials: @config['credentials']).create_policy(
|
|
47
47
|
policy_name: policy_name,
|
|
48
48
|
path: "/"+@deploy.deploy_id+"/",
|
|
49
49
|
policy_document: JSON.generate(policy.values.first),
|
|
@@ -56,8 +56,8 @@ module MU
|
|
|
56
56
|
MU.log "Creating IAM role #{@mu_name}"
|
|
57
57
|
@cloud_id = @mu_name
|
|
58
58
|
path = @config['strip_path'] ? nil : "/"+@deploy.deploy_id+"/"
|
|
59
|
-
|
|
60
|
-
path:
|
|
59
|
+
MU::Cloud::AWS.iam(credentials: @config['credentials']).create_role(
|
|
60
|
+
path: path,
|
|
61
61
|
role_name: @mu_name,
|
|
62
62
|
description: "Generated by Mu",
|
|
63
63
|
assume_role_policy_document: gen_assume_role_policy_doc,
|
|
@@ -89,7 +89,6 @@ module MU
|
|
|
89
89
|
end
|
|
90
90
|
|
|
91
91
|
if @config['raw_policies'] or @config['attachable_policies']
|
|
92
|
-
attached_policies = []
|
|
93
92
|
configured_policies = []
|
|
94
93
|
|
|
95
94
|
if @config['raw_policies']
|
|
@@ -128,7 +127,7 @@ module MU
|
|
|
128
127
|
|
|
129
128
|
# XXX not sure we're binding these sanely, validate that
|
|
130
129
|
if @config['raw_policies']
|
|
131
|
-
|
|
130
|
+
MU::Cloud::AWS::Role.manageRawPolicies(
|
|
132
131
|
@config['raw_policies'],
|
|
133
132
|
basename: @deploy.getResourceName(@config['name']),
|
|
134
133
|
credentials: @credentials
|
|
@@ -183,7 +182,7 @@ module MU
|
|
|
183
182
|
desc
|
|
184
183
|
end
|
|
185
184
|
|
|
186
|
-
rescue Aws::IAM::Errors::NoSuchEntity
|
|
185
|
+
rescue Aws::IAM::Errors::NoSuchEntity
|
|
187
186
|
MU.log "Creating IAM policy #{policy_name}", details: policy.values.first
|
|
188
187
|
MU::Cloud::AWS.iam(credentials: credentials).create_policy(
|
|
189
188
|
policy_name: policy_name,
|
|
@@ -202,65 +201,87 @@ module MU
|
|
|
202
201
|
def arn
|
|
203
202
|
desc = cloud_desc
|
|
204
203
|
if desc["role"]
|
|
205
|
-
desc[
|
|
204
|
+
if desc['role'].is_a?(Hash)
|
|
205
|
+
desc["role"][:arn] # why though
|
|
206
|
+
else
|
|
207
|
+
desc["role"].arn
|
|
208
|
+
end
|
|
206
209
|
else
|
|
207
210
|
nil
|
|
208
211
|
end
|
|
209
212
|
end
|
|
210
213
|
|
|
214
|
+
@cloud_desc_cache = nil
|
|
211
215
|
# Return a hash containing a +role+ element and a +policies+ element,
|
|
212
216
|
# populated with one or both depending on what this resource has
|
|
213
217
|
# defined.
|
|
214
|
-
def cloud_desc
|
|
215
|
-
|
|
218
|
+
def cloud_desc(use_cache: true)
|
|
219
|
+
|
|
220
|
+
# we might inherit a naive cached description from the base cloud
|
|
221
|
+
# layer; rearrange it to our tastes
|
|
222
|
+
if @cloud_desc_cache.is_a?(::Aws::IAM::Types::Role)
|
|
223
|
+
new_desc = {
|
|
224
|
+
"role" => @cloud_desc_cache
|
|
225
|
+
}
|
|
226
|
+
@cloud_desc_cache = new_desc
|
|
227
|
+
elsif @cloud_desc_cache.is_a?(::Aws::IAM::Types::Policy)
|
|
228
|
+
new_desc = {
|
|
229
|
+
"policies" => [@cloud_desc_cache]
|
|
230
|
+
}
|
|
231
|
+
@cloud_desc_cache = new_desc
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
return @cloud_desc_cache if @cloud_desc_cache and !@cloud_desc_cache.empty? and use_cache
|
|
235
|
+
|
|
236
|
+
@cloud_desc_cache = {}
|
|
216
237
|
if @config['bare_policies']
|
|
217
238
|
if @cloud_id
|
|
218
239
|
pol_desc = MU::Cloud::AWS::Role.find(credentials: @credentials, cloud_id: @cloud_id).values.first
|
|
219
240
|
if pol_desc
|
|
220
|
-
|
|
221
|
-
return
|
|
241
|
+
@cloud_desc_cache['policies'] = [pol_desc]
|
|
242
|
+
return @cloud_desc_cache
|
|
222
243
|
end
|
|
223
244
|
end
|
|
224
245
|
|
|
225
246
|
if @deploy and @deploy.deploy_id
|
|
226
|
-
|
|
247
|
+
@cloud_desc_cache["policies"] = MU::Cloud::AWS.iam(credentials: @credentials).list_policies(
|
|
227
248
|
path_prefix: "/"+@deploy.deploy_id+"/"
|
|
228
249
|
).policies
|
|
229
|
-
|
|
250
|
+
@cloud_desc_cache["policies"].reject! { |p|
|
|
230
251
|
!p.policy_name.match(/^#{Regexp.quote(@mu_name)}-/)
|
|
231
252
|
}
|
|
232
253
|
# this is quasi-wrong because we can be mulitple cloud is, but
|
|
233
254
|
# we can't really set this type to has_multiples because that's
|
|
234
255
|
# just how managed policies work not anything else, goddammit
|
|
235
256
|
# AWS why can't you just bundle everything in roles
|
|
236
|
-
if
|
|
237
|
-
@cloud_id ||=
|
|
257
|
+
if @cloud_desc_cache["policies"] and @cloud_desc_cache["policies"].size > 0
|
|
258
|
+
@cloud_id ||= @cloud_desc_cache["policies"].first.arn
|
|
238
259
|
end
|
|
239
260
|
end
|
|
240
261
|
else
|
|
241
262
|
if @cloud_id.match(/^arn:aws(:?-us-gov)?:[^:]*:[^:]*:\d*:policy\//)
|
|
242
263
|
pol_desc = MU::Cloud::AWS::Role.find(credentials: @credentials, cloud_id: @cloud_id).values.first
|
|
243
264
|
if pol_desc
|
|
244
|
-
|
|
245
|
-
return
|
|
265
|
+
@cloud_desc_cache['policies'] = [pol_desc]
|
|
266
|
+
return @cloud_desc_cache
|
|
246
267
|
end
|
|
247
268
|
end
|
|
248
269
|
begin
|
|
249
|
-
|
|
250
|
-
|
|
270
|
+
@cloud_desc_cache['role'] = MU::Cloud::AWS::Role.find(credentials: @credentials, cloud_id: @cloud_id).values.first
|
|
271
|
+
@cloud_desc_cache['role'] ||= MU::Cloud::AWS::Role.find(credentials: @credentials, cloud_id: @mu_name).values.first
|
|
251
272
|
MU::Cloud::AWS.iam(credentials: @credentials).list_attached_role_policies(
|
|
252
273
|
role_name: @mu_name
|
|
253
274
|
).attached_policies.each { |p|
|
|
254
|
-
|
|
255
|
-
|
|
275
|
+
@cloud_desc_cache["policies"] ||= []
|
|
276
|
+
@cloud_desc_cache["policies"] << MU::Cloud::AWS.iam(credentials: @credentials).get_policy(
|
|
256
277
|
policy_arn: p.policy_arn
|
|
257
278
|
).policy
|
|
258
279
|
}
|
|
259
280
|
|
|
260
281
|
inline = MU::Cloud::AWS.iam(credentials: @credentials).list_role_policies(role_name: @mu_name).policy_names
|
|
261
282
|
inline.each { |pol_name|
|
|
262
|
-
|
|
263
|
-
|
|
283
|
+
@cloud_desc_cache["policies"] ||= []
|
|
284
|
+
@cloud_desc_cache["policies"] << MU::Cloud::AWS.iam(credentials: @credentials).get_role_policy(
|
|
264
285
|
role_name: @mu_name,
|
|
265
286
|
policy_name: pol_name
|
|
266
287
|
)
|
|
@@ -270,9 +291,9 @@ rescue ::Aws::IAM::Errors::ValidationError => e
|
|
|
270
291
|
MU.log @cloud_id+" "+@mu_name, MU::WARN, details: e.inspect
|
|
271
292
|
end
|
|
272
293
|
end
|
|
273
|
-
|
|
294
|
+
@cloud_desc_cache['cloud_id'] ||= @cloud_id
|
|
274
295
|
|
|
275
|
-
|
|
296
|
+
@cloud_desc_cache
|
|
276
297
|
end
|
|
277
298
|
|
|
278
299
|
# Return the metadata for this user cofiguration
|
|
@@ -284,26 +305,25 @@ end
|
|
|
284
305
|
# Insert a new target entity into an existing policy.
|
|
285
306
|
# @param policy [String]: The name of the policy to which we're appending, which must already exist as part of this role resource
|
|
286
307
|
# @param targets [Array<String>]: The target resource. If +target_type+ isn't specified, this should be a fully-resolved ARN.
|
|
287
|
-
|
|
288
|
-
def injectPolicyTargets(policy, targets, mu_type = nil)
|
|
308
|
+
def injectPolicyTargets(policy, targets)
|
|
289
309
|
if !policy.match(/^#{@deploy.deploy_id}/)
|
|
290
310
|
policy = @mu_name+"-"+policy.upcase
|
|
291
311
|
end
|
|
292
|
-
|
|
293
|
-
my_policies = cloud_desc["policies"]
|
|
312
|
+
my_policies = cloud_desc(use_cache: false)["policies"]
|
|
294
313
|
my_policies ||= []
|
|
295
|
-
|
|
314
|
+
|
|
315
|
+
seen_policy = false
|
|
296
316
|
my_policies.each { |p|
|
|
297
317
|
if p.policy_name == policy
|
|
318
|
+
seen_policy = true
|
|
298
319
|
old = MU::Cloud::AWS.iam(credentials: @config['credentials']).get_policy_version(
|
|
299
320
|
policy_arn: p.arn,
|
|
300
321
|
version_id: p.default_version_id
|
|
301
322
|
).policy_version
|
|
302
323
|
|
|
303
324
|
doc = JSON.parse URI.decode_www_form_component old.document
|
|
304
|
-
|
|
305
325
|
need_update = false
|
|
306
|
-
|
|
326
|
+
|
|
307
327
|
doc["Statement"].each { |s|
|
|
308
328
|
targets.each { |target|
|
|
309
329
|
target_string = target
|
|
@@ -332,6 +352,10 @@ end
|
|
|
332
352
|
end
|
|
333
353
|
end
|
|
334
354
|
}
|
|
355
|
+
|
|
356
|
+
if !seen_policy
|
|
357
|
+
MU.log "Was given new targets for policy #{policy}, but I don't see any such policy attached to role #{@cloud_id}", MU::WARN, details: targets
|
|
358
|
+
end
|
|
335
359
|
end
|
|
336
360
|
|
|
337
361
|
# Delete an IAM policy, along with attendant versions and attachments.
|
|
@@ -409,16 +433,15 @@ end
|
|
|
409
433
|
# Remove all roles associated with the currently loaded deployment.
|
|
410
434
|
# @param noop [Boolean]: If true, will only print what would be done
|
|
411
435
|
# @param ignoremaster [Boolean]: If true, will remove resources not flagged as originating from this Mu server
|
|
412
|
-
# @param region [String]: The cloud provider region
|
|
413
436
|
# @return [void]
|
|
414
|
-
def self.cleanup(noop: false,
|
|
437
|
+
def self.cleanup(noop: false, deploy_id: MU.deploy_id, ignoremaster: false, credentials: nil, flags: {})
|
|
415
438
|
|
|
416
439
|
resp = MU::Cloud::AWS.iam(credentials: credentials).list_policies(
|
|
417
|
-
path_prefix: "/"+
|
|
440
|
+
path_prefix: "/"+deploy_id+"/"
|
|
418
441
|
)
|
|
419
442
|
if resp and resp.policies
|
|
420
443
|
resp.policies.each { |policy|
|
|
421
|
-
MU.log "Deleting IAM policy /#{
|
|
444
|
+
MU.log "Deleting IAM policy /#{deploy_id}/#{policy.policy_name}"
|
|
422
445
|
if !noop
|
|
423
446
|
purgePolicy(policy.arn, credentials)
|
|
424
447
|
end
|
|
@@ -429,21 +452,31 @@ end
|
|
|
429
452
|
roles = MU::Cloud::AWS::Role.find(credentials: credentials).values
|
|
430
453
|
roles.each { |r|
|
|
431
454
|
next if !r.respond_to?(:role_name)
|
|
432
|
-
if r.path.match(/^\/#{Regexp.quote(
|
|
455
|
+
if r.path.match(/^\/#{Regexp.quote(deploy_id)}/)
|
|
433
456
|
deleteme << r
|
|
434
457
|
next
|
|
435
458
|
end
|
|
436
459
|
# For some dumb reason, the list output that .find gets doesn't
|
|
437
460
|
# include the tags, so we need to fetch each role individually to
|
|
438
461
|
# check tags. Hardly seems efficient.
|
|
439
|
-
desc =
|
|
462
|
+
desc = begin
|
|
463
|
+
MU::Cloud::AWS.iam(credentials: credentials).get_role(role_name: r.role_name)
|
|
464
|
+
rescue Aws::IAM::Errors::NoSuchEntity
|
|
465
|
+
next
|
|
466
|
+
end
|
|
440
467
|
if desc.role and desc.role.tags and desc.role.tags
|
|
468
|
+
master_match = false
|
|
469
|
+
deploy_match = false
|
|
441
470
|
desc.role.tags.each { |t|
|
|
442
|
-
if t.key == "MU-ID" and t.value ==
|
|
443
|
-
|
|
444
|
-
|
|
471
|
+
if t.key == "MU-ID" and t.value == deploy_id
|
|
472
|
+
deploy_match = true
|
|
473
|
+
elsif t.key == "MU-MASTER-IP" and t.value == MU.mu_public_ip
|
|
474
|
+
master_match = true
|
|
445
475
|
end
|
|
446
476
|
}
|
|
477
|
+
if deploy_match and (master_match or ignoremaster)
|
|
478
|
+
deleteme << r
|
|
479
|
+
end
|
|
447
480
|
end
|
|
448
481
|
}
|
|
449
482
|
|
|
@@ -481,7 +514,7 @@ end
|
|
|
481
514
|
MU::Cloud::AWS.iam(credentials: credentials).delete_instance_profile(instance_profile_name: r.role_name)
|
|
482
515
|
rescue Aws::IAM::Errors::ValidationError => e
|
|
483
516
|
MU.log "Cleaning up IAM role #{r.role_name}: #{e.inspect}", MU::WARN
|
|
484
|
-
rescue Aws::IAM::Errors::NoSuchEntity
|
|
517
|
+
rescue Aws::IAM::Errors::NoSuchEntity
|
|
485
518
|
end
|
|
486
519
|
|
|
487
520
|
MU::Cloud::AWS.iam(credentials: credentials).delete_role(
|
|
@@ -502,7 +535,7 @@ end
|
|
|
502
535
|
|
|
503
536
|
begin
|
|
504
537
|
# managed policies get fetched by ARN, roles by plain name. Ok!
|
|
505
|
-
if args[:cloud_id].match(/^arn
|
|
538
|
+
if args[:cloud_id].match(/^arn:.*?:policy\//)
|
|
506
539
|
resp = MU::Cloud::AWS.iam(credentials: args[:credentials]).get_policy(
|
|
507
540
|
policy_arn: args[:cloud_id]
|
|
508
541
|
)
|
|
@@ -511,39 +544,26 @@ end
|
|
|
511
544
|
end
|
|
512
545
|
else
|
|
513
546
|
resp = MU::Cloud::AWS.iam(credentials: args[:credentials]).get_role(
|
|
514
|
-
role_name: args[:cloud_id]
|
|
547
|
+
role_name: args[:cloud_id].sub(/^arn:.*?\/([^:\/]+)$/, '\1') # XXX if it's an ARN, actually parse it and look in the correct account when applicable
|
|
515
548
|
)
|
|
549
|
+
|
|
516
550
|
if resp and resp.role
|
|
517
|
-
found[
|
|
551
|
+
found[resp.role.role_name] = resp.role
|
|
518
552
|
end
|
|
519
553
|
end
|
|
520
554
|
rescue ::Aws::IAM::Errors::NoSuchEntity
|
|
521
555
|
end
|
|
522
|
-
|
|
556
|
+
|
|
523
557
|
else
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
)
|
|
529
|
-
break if !resp or !resp.roles
|
|
530
|
-
resp.roles.each { |role|
|
|
531
|
-
found[role.role_name] = role
|
|
532
|
-
}
|
|
533
|
-
marker = resp.marker
|
|
534
|
-
end while marker
|
|
558
|
+
resp = MU::Cloud::AWS.iam(credentials: args[:credentials]).list_roles
|
|
559
|
+
resp.roles.each { |role|
|
|
560
|
+
found[role.role_name] = role
|
|
561
|
+
}
|
|
535
562
|
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
)
|
|
541
|
-
break if !resp or !resp.policies
|
|
542
|
-
resp.policies.each { |pol|
|
|
543
|
-
found[pol.arn] = pol
|
|
544
|
-
}
|
|
545
|
-
marker = resp.marker
|
|
546
|
-
end while marker
|
|
563
|
+
resp = MU::Cloud::AWS.iam(credentials: args[:credentials]).list_policies(scope: "Local")
|
|
564
|
+
resp.policies.each { |pol|
|
|
565
|
+
found[pol.arn] = pol
|
|
566
|
+
}
|
|
547
567
|
end
|
|
548
568
|
|
|
549
569
|
found
|
|
@@ -552,7 +572,7 @@ end
|
|
|
552
572
|
# Reverse-map our cloud description into a runnable config hash.
|
|
553
573
|
# We assume that any values we have in +@config+ are placeholders, and
|
|
554
574
|
# calculate our own accordingly based on what's live in the cloud.
|
|
555
|
-
def toKitten(
|
|
575
|
+
def toKitten(**_args)
|
|
556
576
|
bok = {
|
|
557
577
|
"cloud" => "AWS",
|
|
558
578
|
"credentials" => @config['credentials'],
|
|
@@ -601,14 +621,13 @@ end
|
|
|
601
621
|
)
|
|
602
622
|
JSON.parse(URI.decode(version.policy_version.document))
|
|
603
623
|
end
|
|
604
|
-
|
|
605
624
|
bok["policies"] = MU::Cloud::AWS::Role.doc2MuPolicies(pol.policy_name, doc, bok["policies"])
|
|
606
625
|
end
|
|
607
626
|
}
|
|
608
627
|
|
|
609
628
|
return bok if @config['bare_policies']
|
|
610
629
|
end
|
|
611
|
-
|
|
630
|
+
|
|
612
631
|
if desc.tags and desc.tags.size > 0
|
|
613
632
|
bok["tags"] = MU.structToHash(desc.tags, stringify_keys: true)
|
|
614
633
|
end
|
|
@@ -681,6 +700,7 @@ end
|
|
|
681
700
|
end
|
|
682
701
|
|
|
683
702
|
bok["attachable_policies"].uniq! if bok["attachable_policies"]
|
|
703
|
+
bok["name"].gsub!(/[^a-zA-Z0-9_\-]/, "_")
|
|
684
704
|
|
|
685
705
|
bok
|
|
686
706
|
end
|
|
@@ -693,6 +713,10 @@ end
|
|
|
693
713
|
def self.doc2MuPolicies(basename, doc, policies = [])
|
|
694
714
|
policies ||= []
|
|
695
715
|
|
|
716
|
+
if !doc["Statement"].is_a?(Array)
|
|
717
|
+
doc["Statement"] = [doc["Statement"]]
|
|
718
|
+
end
|
|
719
|
+
|
|
696
720
|
doc["Statement"].each { |s|
|
|
697
721
|
if !s["Action"]
|
|
698
722
|
MU.log "Statement in policy document for #{basename} didn't have an Action field", MU::WARN, details: doc
|
|
@@ -721,7 +745,7 @@ end
|
|
|
721
745
|
"targets" => s["Resource"].map { |r|
|
|
722
746
|
if r.match(/^arn:aws(-us-gov)?:([^:]+):.*?:([^:]*)$/)
|
|
723
747
|
# XXX which cases even count for blind references to sibling resources?
|
|
724
|
-
|
|
748
|
+
if Regexp.last_match[1] == "s3"
|
|
725
749
|
"bucket"
|
|
726
750
|
elsif Regexp.last_match[1]
|
|
727
751
|
MU.log "Service #{Regexp.last_match[1]} to type...", MU::WARN, details: r
|
|
@@ -741,7 +765,7 @@ end
|
|
|
741
765
|
|
|
742
766
|
# Attach this role or group of loose policies to the specified entity.
|
|
743
767
|
# @param entitytype [String]: The type of entity (user, group or role for policies; instance_profile for roles)
|
|
744
|
-
def bindTo(entitytype, entityname
|
|
768
|
+
def bindTo(entitytype, entityname)
|
|
745
769
|
if entitytype == "instance_profile"
|
|
746
770
|
begin
|
|
747
771
|
resp = MU::Cloud::AWS.iam(credentials: @config['credentials']).get_instance_profile(
|
|
@@ -754,7 +778,7 @@ end
|
|
|
754
778
|
role_name: @mu_name
|
|
755
779
|
)
|
|
756
780
|
end
|
|
757
|
-
rescue
|
|
781
|
+
rescue StandardError => e
|
|
758
782
|
MU.log "Error binding role #{@mu_name} to instance profile #{entityname}: #{e.message}", MU::ERR
|
|
759
783
|
raise e
|
|
760
784
|
end
|
|
@@ -783,7 +807,6 @@ end
|
|
|
783
807
|
rescue Aws::IAM::Errors::NoSuchEntity => e
|
|
784
808
|
if subpaths.size > 0
|
|
785
809
|
p_arn = "arn:"+(MU::Cloud::AWS.isGovCloud?(@config["region"]) ? "aws-us-gov" : "aws")+":iam::aws:policy/#{subpaths.shift}/"+policy
|
|
786
|
-
retried = true
|
|
787
810
|
retry
|
|
788
811
|
end
|
|
789
812
|
raise e
|
|
@@ -833,6 +856,7 @@ end
|
|
|
833
856
|
else
|
|
834
857
|
raise MuError, "Invalid entitytype '#{entitytype}' passed to MU::Cloud::AWS::Role.bindTo. Must be be one of: user, group, role, instance_profile"
|
|
835
858
|
end
|
|
859
|
+
cloud_desc(use_cache: false)
|
|
836
860
|
end
|
|
837
861
|
|
|
838
862
|
# Create an instance profile for EC2 instances, named identically and
|
|
@@ -847,7 +871,7 @@ end
|
|
|
847
871
|
MU::Cloud::AWS.iam(credentials: @config['credentials']).create_instance_profile(
|
|
848
872
|
instance_profile_name: @mu_name
|
|
849
873
|
)
|
|
850
|
-
rescue Aws::IAM::Errors::EntityAlreadyExists
|
|
874
|
+
rescue Aws::IAM::Errors::EntityAlreadyExists
|
|
851
875
|
MU::Cloud::AWS.iam(credentials: @config['credentials']).get_instance_profile(
|
|
852
876
|
instance_profile_name: @mu_name
|
|
853
877
|
)
|
|
@@ -905,13 +929,13 @@ end
|
|
|
905
929
|
end
|
|
906
930
|
|
|
907
931
|
# Cloud-specific configuration properties.
|
|
908
|
-
# @param
|
|
932
|
+
# @param _config [MU::Config]: The calling MU::Config object
|
|
909
933
|
# @return [Array<Array,Hash>]: List of required fields, and json-schema Hash of cloud-specific configuration parameters for this resource
|
|
910
|
-
def self.schema(
|
|
934
|
+
def self.schema(_config)
|
|
911
935
|
toplevel_required = []
|
|
912
936
|
aws_resource_types = MU::Cloud.resource_types.keys.reject { |t|
|
|
913
937
|
begin
|
|
914
|
-
MU::Cloud.
|
|
938
|
+
MU::Cloud.resourceClass("AWS", t)
|
|
915
939
|
false
|
|
916
940
|
rescue MuCloudResourceNotImplemented
|
|
917
941
|
true
|
|
@@ -1012,10 +1036,9 @@ end
|
|
|
1012
1036
|
subpaths = ["service-role", "aws-service-role", "job-function"]
|
|
1013
1037
|
begin
|
|
1014
1038
|
MU::Cloud::AWS.iam(credentials: credentials).get_policy(policy_arn: arn)
|
|
1015
|
-
rescue Aws::IAM::Errors::NoSuchEntity
|
|
1039
|
+
rescue Aws::IAM::Errors::NoSuchEntity
|
|
1016
1040
|
if subpaths.size > 0
|
|
1017
1041
|
arn = "arn:"+(MU::Cloud::AWS.isGovCloud?(region) ? "aws-us-gov" : "aws")+":iam::aws:policy/#{subpaths.shift}/"+ref["id"]
|
|
1018
|
-
retried = true
|
|
1019
1042
|
retry
|
|
1020
1043
|
end
|
|
1021
1044
|
MU.log "No such canned AWS IAM policy '#{arn}'", MU::ERR
|
|
@@ -1029,9 +1052,9 @@ end
|
|
|
1029
1052
|
|
|
1030
1053
|
# Cloud-specific pre-processing of {MU::Config::BasketofKittens::roles}, bare and unvalidated.
|
|
1031
1054
|
# @param role [Hash]: The resource to process and validate
|
|
1032
|
-
# @param
|
|
1055
|
+
# @param _configurator [MU::Config]: The overall deployment configurator of which this resource is a member
|
|
1033
1056
|
# @return [Boolean]: True if validation succeeded, False otherwise
|
|
1034
|
-
def self.validateConfig(role,
|
|
1057
|
+
def self.validateConfig(role, _configurator)
|
|
1035
1058
|
ok = true
|
|
1036
1059
|
|
|
1037
1060
|
# munge things declared with the deprecated import keyword into
|
|
@@ -1074,11 +1097,7 @@ end
|
|
|
1074
1097
|
role['policies'].each { |policy|
|
|
1075
1098
|
policy['targets'].each { |target|
|
|
1076
1099
|
if target['type']
|
|
1077
|
-
role['
|
|
1078
|
-
role['dependencies'] << {
|
|
1079
|
-
"name" => target['identifier'],
|
|
1080
|
-
"type" => target['type']
|
|
1081
|
-
}
|
|
1100
|
+
MU::Config.addDependency(role, target['identifier'], target['type'], no_create_wait: true)
|
|
1082
1101
|
end
|
|
1083
1102
|
}
|
|
1084
1103
|
}
|
|
@@ -1094,15 +1113,14 @@ end
|
|
|
1094
1113
|
# @param policies [Array<Hash>]: One or more policy chunks
|
|
1095
1114
|
# @param deploy_obj [MU::MommaCat]: Deployment object to use when looking up sibling Mu resources
|
|
1096
1115
|
# @return [Array<Hash>]
|
|
1097
|
-
def self.genPolicyDocument(policies, deploy_obj: nil)
|
|
1098
|
-
iam_policies = []
|
|
1099
|
-
|
|
1116
|
+
def self.genPolicyDocument(policies, deploy_obj: nil, bucket_style: false, version: "2012-10-17", doc_id: nil)
|
|
1100
1117
|
if policies
|
|
1101
1118
|
name = nil
|
|
1102
1119
|
doc = {
|
|
1103
|
-
"Version" =>
|
|
1120
|
+
"Version" => version,
|
|
1104
1121
|
"Statement" => []
|
|
1105
1122
|
}
|
|
1123
|
+
doc["Id"] = doc_id if doc_id
|
|
1106
1124
|
policies.each { |policy|
|
|
1107
1125
|
policy["flag"] ||= "Allow"
|
|
1108
1126
|
statement = {
|
|
@@ -1134,12 +1152,28 @@ end
|
|
|
1134
1152
|
)
|
|
1135
1153
|
if sibling
|
|
1136
1154
|
id = sibling.cloudobj.arn
|
|
1137
|
-
|
|
1155
|
+
if bucket_style
|
|
1156
|
+
statement["Principal"] << { "AWS" => id }
|
|
1157
|
+
else
|
|
1158
|
+
statement["Principal"] << id
|
|
1159
|
+
end
|
|
1138
1160
|
else
|
|
1139
1161
|
raise MuError, "Couldn't find a #{grantee["type"]} named #{grantee["identifier"]} when generating IAM policy"
|
|
1140
1162
|
end
|
|
1141
1163
|
else
|
|
1142
|
-
|
|
1164
|
+
bucket_prefix = if grantee["identifier"].match(/^[^\.]+\.amazonaws\.com$/)
|
|
1165
|
+
"Service"
|
|
1166
|
+
elsif grantee["identifier"] =~ /^[a-f0-9]+$/
|
|
1167
|
+
"CanonicalUser"
|
|
1168
|
+
else
|
|
1169
|
+
"AWS"
|
|
1170
|
+
end
|
|
1171
|
+
|
|
1172
|
+
if bucket_style
|
|
1173
|
+
statement["Principal"] << { bucket_prefix => grantee["identifier"] }
|
|
1174
|
+
else
|
|
1175
|
+
statement["Principal"] << grantee["identifier"]
|
|
1176
|
+
end
|
|
1143
1177
|
end
|
|
1144
1178
|
}
|
|
1145
1179
|
if policy["grant_to"].size == 1
|
|
@@ -1162,9 +1196,11 @@ end
|
|
|
1162
1196
|
stream_id = id.sub(/:([^:]+)$/, ":log-stream:*")
|
|
1163
1197
|
# "arn:aws:logs:us-east-2:accountID:log-group:log_group_name:log-stream:CloudTrail_log_stream_name_prefix*"
|
|
1164
1198
|
statement["Resource"] << stream_id
|
|
1199
|
+
elsif id.match(/:s3:/)
|
|
1200
|
+
statement["Resource"] << id+"/*"
|
|
1165
1201
|
end
|
|
1166
1202
|
else
|
|
1167
|
-
raise MuError, "Couldn't find a #{target["
|
|
1203
|
+
raise MuError, "Couldn't find a #{target["type"]} named #{target["identifier"]} when generating IAM policy"
|
|
1168
1204
|
end
|
|
1169
1205
|
else
|
|
1170
1206
|
target["identifier"] += target["path"] if target["path"]
|
|
@@ -1180,6 +1216,32 @@ end
|
|
|
1180
1216
|
[]
|
|
1181
1217
|
end
|
|
1182
1218
|
|
|
1219
|
+
# Update a policy, handling deletion of old versions as needed
|
|
1220
|
+
# @param arn [String]:
|
|
1221
|
+
# @param doc [Hash]:
|
|
1222
|
+
# @param credentials [String]:
|
|
1223
|
+
def self.update_policy(arn, doc, credentials: nil)
|
|
1224
|
+
# XXX this is just blindly replacing identical versions, when it should check
|
|
1225
|
+
# and guard
|
|
1226
|
+
begin
|
|
1227
|
+
MU::Cloud::AWS.iam(credentials: credentials).create_policy_version(
|
|
1228
|
+
policy_arn: arn,
|
|
1229
|
+
set_as_default: true,
|
|
1230
|
+
policy_document: JSON.generate(doc)
|
|
1231
|
+
)
|
|
1232
|
+
rescue Aws::IAM::Errors::LimitExceeded
|
|
1233
|
+
delete_version = MU::Cloud::AWS.iam(credentials: credentials).list_policy_versions(
|
|
1234
|
+
policy_arn: arn,
|
|
1235
|
+
).versions.last.version_id
|
|
1236
|
+
MU.log "Purging oldest version (#{delete_version}) of IAM policy #{arn}", MU::NOTICE
|
|
1237
|
+
MU::Cloud::AWS.iam(credentials: credentials).delete_policy_version(
|
|
1238
|
+
policy_arn: arn,
|
|
1239
|
+
version_id: delete_version
|
|
1240
|
+
)
|
|
1241
|
+
retry
|
|
1242
|
+
end
|
|
1243
|
+
end
|
|
1244
|
+
|
|
1183
1245
|
private
|
|
1184
1246
|
|
|
1185
1247
|
# Convert entries from the cloud-neutral @config['policies'] list into
|
|
@@ -1261,28 +1323,6 @@ end
|
|
|
1261
1323
|
MU::Cloud::AWS::Role.update_policy(arn, doc, credentials: @credentials)
|
|
1262
1324
|
end
|
|
1263
1325
|
|
|
1264
|
-
# Update a policy, handling deletion of old versions as needed
|
|
1265
|
-
def self.update_policy(arn, doc, credentials: nil)
|
|
1266
|
-
# XXX this is just blindly replacing identical versions, when it should check
|
|
1267
|
-
# and guard
|
|
1268
|
-
begin
|
|
1269
|
-
MU::Cloud::AWS.iam(credentials: credentials).create_policy_version(
|
|
1270
|
-
policy_arn: arn,
|
|
1271
|
-
set_as_default: true,
|
|
1272
|
-
policy_document: JSON.generate(doc)
|
|
1273
|
-
)
|
|
1274
|
-
rescue Aws::IAM::Errors::LimitExceeded => e
|
|
1275
|
-
delete_version = MU::Cloud::AWS.iam(credentials: credentials).list_policy_versions(
|
|
1276
|
-
policy_arn: arn,
|
|
1277
|
-
).versions.last.version_id
|
|
1278
|
-
MU.log "Purging oldest version (#{delete_version}) of IAM policy #{arn}", MU::NOTICE
|
|
1279
|
-
MU::Cloud::AWS.iam(credentials: credentials).delete_policy_version(
|
|
1280
|
-
policy_arn: arn,
|
|
1281
|
-
version_id: delete_version
|
|
1282
|
-
)
|
|
1283
|
-
retry
|
|
1284
|
-
end
|
|
1285
|
-
end
|
|
1286
1326
|
|
|
1287
1327
|
end
|
|
1288
1328
|
end
|