cloud-mu 3.2.0 → 3.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (91) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +1 -1
  3. data/bin/mu-adopt +12 -1
  4. data/bin/mu-load-config.rb +2 -1
  5. data/bin/mu-run-tests +14 -2
  6. data/cloud-mu.gemspec +3 -3
  7. data/modules/mu.rb +2 -2
  8. data/modules/mu/adoption.rb +5 -5
  9. data/modules/mu/cleanup.rb +47 -25
  10. data/modules/mu/cloud.rb +29 -1
  11. data/modules/mu/cloud/dnszone.rb +0 -2
  12. data/modules/mu/cloud/resource_base.rb +9 -3
  13. data/modules/mu/cloud/wrappers.rb +4 -0
  14. data/modules/mu/config.rb +1 -1
  15. data/modules/mu/config/bucket.rb +31 -2
  16. data/modules/mu/config/cache_cluster.rb +1 -1
  17. data/modules/mu/config/cdn.rb +100 -0
  18. data/modules/mu/config/container_cluster.rb +1 -1
  19. data/modules/mu/config/database.rb +1 -1
  20. data/modules/mu/config/dnszone.rb +4 -3
  21. data/modules/mu/config/endpoint.rb +1 -0
  22. data/modules/mu/config/function.rb +16 -7
  23. data/modules/mu/config/job.rb +89 -0
  24. data/modules/mu/config/notifier.rb +7 -18
  25. data/modules/mu/config/ref.rb +53 -7
  26. data/modules/mu/config/server.rb +1 -1
  27. data/modules/mu/config/vpc.rb +1 -0
  28. data/modules/mu/defaults/AWS.yaml +26 -26
  29. data/modules/mu/deploy.rb +13 -0
  30. data/modules/mu/master.rb +21 -0
  31. data/modules/mu/mommacat.rb +1 -0
  32. data/modules/mu/mommacat/daemon.rb +13 -7
  33. data/modules/mu/providers/aws.rb +115 -16
  34. data/modules/mu/providers/aws/alarm.rb +2 -2
  35. data/modules/mu/providers/aws/bucket.rb +274 -40
  36. data/modules/mu/providers/aws/cache_cluster.rb +4 -4
  37. data/modules/mu/providers/aws/cdn.rb +782 -0
  38. data/modules/mu/providers/aws/collection.rb +2 -2
  39. data/modules/mu/providers/aws/container_cluster.rb +57 -37
  40. data/modules/mu/providers/aws/database.rb +11 -11
  41. data/modules/mu/providers/aws/dnszone.rb +24 -7
  42. data/modules/mu/providers/aws/endpoint.rb +535 -50
  43. data/modules/mu/providers/aws/firewall_rule.rb +6 -3
  44. data/modules/mu/providers/aws/folder.rb +1 -1
  45. data/modules/mu/providers/aws/function.rb +288 -125
  46. data/modules/mu/providers/aws/group.rb +9 -7
  47. data/modules/mu/providers/aws/habitat.rb +2 -2
  48. data/modules/mu/providers/aws/job.rb +466 -0
  49. data/modules/mu/providers/aws/loadbalancer.rb +9 -8
  50. data/modules/mu/providers/aws/log.rb +3 -3
  51. data/modules/mu/providers/aws/msg_queue.rb +12 -3
  52. data/modules/mu/providers/aws/nosqldb.rb +96 -5
  53. data/modules/mu/providers/aws/notifier.rb +135 -63
  54. data/modules/mu/providers/aws/role.rb +51 -37
  55. data/modules/mu/providers/aws/search_domain.rb +165 -29
  56. data/modules/mu/providers/aws/server.rb +12 -9
  57. data/modules/mu/providers/aws/server_pool.rb +26 -13
  58. data/modules/mu/providers/aws/storage_pool.rb +2 -2
  59. data/modules/mu/providers/aws/user.rb +4 -4
  60. data/modules/mu/providers/aws/userdata/linux.erb +5 -4
  61. data/modules/mu/providers/aws/vpc.rb +3 -3
  62. data/modules/mu/providers/azure/server.rb +2 -1
  63. data/modules/mu/providers/google.rb +1 -0
  64. data/modules/mu/providers/google/bucket.rb +1 -1
  65. data/modules/mu/providers/google/container_cluster.rb +1 -1
  66. data/modules/mu/providers/google/database.rb +1 -1
  67. data/modules/mu/providers/google/firewall_rule.rb +1 -1
  68. data/modules/mu/providers/google/folder.rb +1 -1
  69. data/modules/mu/providers/google/function.rb +1 -1
  70. data/modules/mu/providers/google/group.rb +1 -1
  71. data/modules/mu/providers/google/habitat.rb +1 -1
  72. data/modules/mu/providers/google/loadbalancer.rb +1 -1
  73. data/modules/mu/providers/google/role.rb +4 -2
  74. data/modules/mu/providers/google/server.rb +1 -1
  75. data/modules/mu/providers/google/server_pool.rb +1 -1
  76. data/modules/mu/providers/google/user.rb +1 -1
  77. data/modules/mu/providers/google/vpc.rb +1 -1
  78. data/modules/tests/aws-jobs-functions.yaml +46 -0
  79. data/modules/tests/centos6.yaml +4 -0
  80. data/modules/tests/centos7.yaml +4 -0
  81. data/modules/tests/ecs.yaml +2 -2
  82. data/modules/tests/eks.yaml +1 -1
  83. data/modules/tests/functions/node-function/lambda_function.js +10 -0
  84. data/modules/tests/functions/python-function/lambda_function.py +12 -0
  85. data/modules/tests/microservice_app.yaml +288 -0
  86. data/modules/tests/rds.yaml +5 -5
  87. data/modules/tests/regrooms/rds.yaml +5 -5
  88. data/modules/tests/server-with-scrub-muisms.yaml +1 -1
  89. data/modules/tests/super_complex_bok.yml +2 -2
  90. data/modules/tests/super_simple_bok.yml +2 -2
  91. metadata +12 -4
@@ -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}
@@ -216,7 +216,22 @@ module MU
216
216
  # populated with one or both depending on what this resource has
217
217
  # defined.
218
218
  def cloud_desc(use_cache: true)
219
- return @cloud_desc_cache if @cloud_desc_cache and use_cache
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
220
235
 
221
236
  @cloud_desc_cache = {}
222
237
  if @config['bare_policies']
@@ -419,14 +434,14 @@ end
419
434
  # @param noop [Boolean]: If true, will only print what would be done
420
435
  # @param ignoremaster [Boolean]: If true, will remove resources not flagged as originating from this Mu server
421
436
  # @return [void]
422
- def self.cleanup(noop: false, ignoremaster: false, credentials: nil, flags: {})
437
+ def self.cleanup(noop: false, deploy_id: MU.deploy_id, ignoremaster: false, credentials: nil, flags: {})
423
438
 
424
439
  resp = MU::Cloud::AWS.iam(credentials: credentials).list_policies(
425
- path_prefix: "/"+MU.deploy_id+"/"
440
+ path_prefix: "/"+deploy_id+"/"
426
441
  )
427
442
  if resp and resp.policies
428
443
  resp.policies.each { |policy|
429
- MU.log "Deleting IAM policy /#{MU.deploy_id}/#{policy.policy_name}"
444
+ MU.log "Deleting IAM policy /#{deploy_id}/#{policy.policy_name}"
430
445
  if !noop
431
446
  purgePolicy(policy.arn, credentials)
432
447
  end
@@ -437,19 +452,23 @@ end
437
452
  roles = MU::Cloud::AWS::Role.find(credentials: credentials).values
438
453
  roles.each { |r|
439
454
  next if !r.respond_to?(:role_name)
440
- if r.path.match(/^\/#{Regexp.quote(MU.deploy_id)}/)
455
+ if r.path.match(/^\/#{Regexp.quote(deploy_id)}/)
441
456
  deleteme << r
442
457
  next
443
458
  end
444
459
  # For some dumb reason, the list output that .find gets doesn't
445
460
  # include the tags, so we need to fetch each role individually to
446
461
  # check tags. Hardly seems efficient.
447
- desc = MU::Cloud::AWS.iam(credentials: credentials).get_role(role_name: r.role_name)
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
448
467
  if desc.role and desc.role.tags and desc.role.tags
449
468
  master_match = false
450
469
  deploy_match = false
451
470
  desc.role.tags.each { |t|
452
- if t.key == "MU-ID" and t.value == MU.deploy_id
471
+ if t.key == "MU-ID" and t.value == deploy_id
453
472
  deploy_match = true
454
473
  elsif t.key == "MU-MASTER-IP" and t.value == MU.mu_public_ip
455
474
  master_match = true
@@ -516,7 +535,7 @@ end
516
535
 
517
536
  begin
518
537
  # managed policies get fetched by ARN, roles by plain name. Ok!
519
- if args[:cloud_id].match(/^arn:/)
538
+ if args[:cloud_id].match(/^arn:.*?:policy\//)
520
539
  resp = MU::Cloud::AWS.iam(credentials: args[:credentials]).get_policy(
521
540
  policy_arn: args[:cloud_id]
522
541
  )
@@ -525,39 +544,26 @@ end
525
544
  end
526
545
  else
527
546
  resp = MU::Cloud::AWS.iam(credentials: args[:credentials]).get_role(
528
- 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
529
548
  )
549
+
530
550
  if resp and resp.role
531
- found[args[:cloud_id]] = resp.role
551
+ found[resp.role.role_name] = resp.role
532
552
  end
533
553
  end
534
554
  rescue ::Aws::IAM::Errors::NoSuchEntity
535
555
  end
536
556
 
537
557
  else
538
- marker = nil
539
- begin
540
- resp = MU::Cloud::AWS.iam(credentials: args[:credentials]).list_roles(
541
- marker: marker
542
- )
543
- break if !resp or !resp.roles
544
- resp.roles.each { |role|
545
- found[role.role_name] = role
546
- }
547
- marker = resp.marker
548
- 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
+ }
549
562
 
550
- begin
551
- resp = MU::Cloud::AWS.iam(credentials: args[:credentials]).list_policies(
552
- scope: "Local",
553
- marker: marker
554
- )
555
- break if !resp or !resp.policies
556
- resp.policies.each { |pol|
557
- found[pol.arn] = pol
558
- }
559
- marker = resp.marker
560
- 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
+ }
561
567
  end
562
568
 
563
569
  found
@@ -1091,7 +1097,7 @@ end
1091
1097
  role['policies'].each { |policy|
1092
1098
  policy['targets'].each { |target|
1093
1099
  if target['type']
1094
- MU::Config.addDependency(role, target['identifier'], target['type'])
1100
+ MU::Config.addDependency(role, target['identifier'], target['type'], no_create_wait: true)
1095
1101
  end
1096
1102
  }
1097
1103
  }
@@ -1107,13 +1113,14 @@ end
1107
1113
  # @param policies [Array<Hash>]: One or more policy chunks
1108
1114
  # @param deploy_obj [MU::MommaCat]: Deployment object to use when looking up sibling Mu resources
1109
1115
  # @return [Array<Hash>]
1110
- def self.genPolicyDocument(policies, deploy_obj: nil, bucket_style: false)
1116
+ def self.genPolicyDocument(policies, deploy_obj: nil, bucket_style: false, version: "2012-10-17", doc_id: nil)
1111
1117
  if policies
1112
1118
  name = nil
1113
1119
  doc = {
1114
- "Version" => "2012-10-17",
1120
+ "Version" => version,
1115
1121
  "Statement" => []
1116
1122
  }
1123
+ doc["Id"] = doc_id if doc_id
1117
1124
  policies.each { |policy|
1118
1125
  policy["flag"] ||= "Allow"
1119
1126
  statement = {
@@ -1154,7 +1161,14 @@ end
1154
1161
  raise MuError, "Couldn't find a #{grantee["type"]} named #{grantee["identifier"]} when generating IAM policy"
1155
1162
  end
1156
1163
  else
1157
- bucket_prefix = grantee["identifier"].match(/^[^\.]+\.amazonaws\.com$/) ? "Service" : "AWS"
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
+
1158
1172
  if bucket_style
1159
1173
  statement["Principal"] << { bucket_prefix => grantee["identifier"] }
1160
1174
  else
@@ -22,9 +22,9 @@ module MU
22
22
  # @param args [Hash]: Hash of named arguments passed via Ruby's double-splat
23
23
  def initialize(**args)
24
24
  super
25
- if @cloud_id and !@config['domain_name']
26
- @config['domain_name'] = @cloud_id
27
- end
25
+ describe if @mu_name and !@deploydata
26
+ @cloud_id ||= @deploydata['domain_name'] if @deploydata
27
+
28
28
  @mu_name ||= @deploy.getResourceName(@config["name"])
29
29
  end
30
30
 
@@ -35,7 +35,8 @@ module MU
35
35
  params = genParams
36
36
 
37
37
  MU.log "Creating ElasticSearch domain #{@config['domain_name']}", details: params
38
- MU::Cloud::AWS.elasticsearch(region: @config['region'], credentials: @config['credentials']).create_elasticsearch_domain(params).domain_status
38
+ @cloud_id = @config['domain_name']
39
+ MU::Cloud::AWS.elasticsearch(region: @config['region'], credentials: @credentials).create_elasticsearch_domain(params).domain_status
39
40
 
40
41
  tagDomain
41
42
 
@@ -44,17 +45,18 @@ module MU
44
45
  # Called automatically by {MU::Deploy#createResources}
45
46
  def groom
46
47
  tagDomain
47
- @config['domain_name'] ||= @deploydata['domain_name']
48
+ @config['domain_name'] ||= @cloud_id
48
49
  params = genParams(cloud_desc) # get parameters that would change only
49
50
 
50
51
  if params.size > 1
51
52
  waitWhileProcessing # wait until the create finishes, if still going
52
53
 
53
54
  MU.log "Updating ElasticSearch domain #{@config['domain_name']}", MU::NOTICE, details: params
54
- MU::Cloud::AWS.elasticsearch(region: @config['region'], credentials: @config['credentials']).update_elasticsearch_domain_config(params)
55
+ MU::Cloud::AWS.elasticsearch(region: @config['region'], credentials: @credentials).update_elasticsearch_domain_config(params)
55
56
  end
56
57
 
57
58
  waitWhileProcessing # don't return until creation/updating is complete
59
+ MU.log "Search Domain #{@config['name']}: #{cloud_desc.endpoint}", MU::SUMMARY
58
60
  end
59
61
 
60
62
  @cloud_desc_cache = nil
@@ -63,31 +65,30 @@ module MU
63
65
  # our druthers.
64
66
  def cloud_desc(use_cache: true)
65
67
  return @cloud_desc_cache if @cloud_desc_cache and use_cache
66
- @cloud_desc_cache = if @config['domain_name']
67
- MU::Cloud::AWS.elasticsearch(region: @config['region'], credentials: @config['credentials']).describe_elasticsearch_domain(
68
- domain_name: @config['domain_name']
68
+ @cloud_id ||= @config['domain_name']
69
+ return nil if !@cloud_id
70
+ MU.retrier([::Aws::ElasticsearchService::Errors::ResourceNotFoundException], wait: 10, max: 12) {
71
+ @cloud_desc_cache = MU::Cloud::AWS.elasticsearch(region: @config['region'], credentials: @credentials).describe_elasticsearch_domain(
72
+ domain_name: @cloud_id
69
73
  ).domain_status
70
- elsif @deploydata and @deploydata['domain_name']
71
- MU::Cloud::AWS.elasticsearch(region: @config['region'], credentials: @config['credentials']).describe_elasticsearch_domain(
72
- domain_name: @deploydata['domain_name']
73
- ).domain_status
74
- else
75
- raise MuError, "#{@mu_name} can't find its official Elasticsearch domain name!"
76
- end
74
+ }
75
+
77
76
  @cloud_desc_cache
78
77
  end
79
78
 
80
79
  # Canonical Amazon Resource Number for this resource
81
80
  # @return [String]
82
81
  def arn
83
- cloud_desc.arn
82
+ return nil if !cloud_desc
83
+ cloud_desc.arn.dup
84
84
  end
85
85
 
86
86
  # Return the metadata for this SearchDomain rule
87
87
  # @return [Hash]
88
88
  def notify
89
- deploy_struct = MU.structToHash(cloud_desc)
90
- tags = MU::Cloud::AWS.elasticsearch(region: @config['region'], credentials: @config['credentials']).list_tags(arn: deploy_struct[:arn]).tag_list
89
+ return nil if !cloud_desc(use_cache: false)
90
+ deploy_struct = MU.structToHash(cloud_desc, stringify_keys: true)
91
+ tags = MU::Cloud::AWS.elasticsearch(region: @config['region'], credentials: @credentials).list_tags(arn: arn).tag_list
91
92
  deploy_struct['tags'] = tags.map { |t| { t.key => t.value } }
92
93
  if deploy_struct['endpoint']
93
94
  deploy_struct['kibana'] = deploy_struct['endpoint']+"/_plugin/kibana/"
@@ -119,7 +120,7 @@ module MU
119
120
  # @param ignoremaster [Boolean]: If true, will remove resources not flagged as originating from this Mu server
120
121
  # @param region [String]: The cloud provider region
121
122
  # @return [void]
122
- def self.cleanup(noop: false, ignoremaster: false, region: MU.curRegion, credentials: nil, flags: {})
123
+ def self.cleanup(noop: false, deploy_id: MU.deploy_id, ignoremaster: false, region: MU.curRegion, credentials: nil, flags: {})
123
124
  MU.log "AWS::SearchDomain.cleanup: need to support flags['known']", MU::DEBUG, details: flags
124
125
 
125
126
  list = MU::Cloud::AWS.elasticsearch(region: region, credentials: credentials).list_domain_names
@@ -135,7 +136,7 @@ module MU
135
136
  deploy_match = false
136
137
  master_match = false
137
138
  tags.tag_list.each { |tag|
138
- if tag.key == "MU-ID" and tag.value == MU.deploy_id
139
+ if tag.key == "MU-ID" and tag.value == deploy_id
139
140
  deploy_match = true
140
141
  elsif tag.key == "MU-MASTER-IP" and tag.value == MU.mu_public_ip
141
142
  master_match = true
@@ -157,7 +158,7 @@ module MU
157
158
  resp = MU::Cloud::AWS.iam(credentials: credentials).list_roles(marker: marker)
158
159
  resp.roles.each{ |role|
159
160
  # XXX Maybe we should have a more generic way to delete IAM profiles and policies. The call itself should be moved from MU::Cloud.resourceClass("AWS", "Server").
160
- # MU::Cloud.resourceClass("AWS", "Server").removeIAMProfile(role.role_name) if role.role_name.match(/^#{Regexp.quote(MU.deploy_id)}/)
161
+ # MU::Cloud.resourceClass("AWS", "Server").removeIAMProfile(role.role_name) if role.role_name.match(/^#{Regexp.quote(deploy_id)}/)
161
162
  }
162
163
  marker = resp.marker
163
164
  end while resp.is_truncated
@@ -191,6 +192,96 @@ module MU
191
192
  found
192
193
  end
193
194
 
195
+ # Reverse-map our cloud description into a runnable config hash.
196
+ # We assume that any values we have in +@config+ are placeholders, and
197
+ # calculate our own accordingly based on what's live in the cloud.
198
+ def toKitten(**_args)
199
+ bok = {
200
+ "cloud" => "AWS",
201
+ "credentials" => @credentials,
202
+ "cloud_id" => @cloud_id,
203
+ "region" => @config['region']
204
+ }
205
+
206
+ if !cloud_desc
207
+ MU.log "toKitten failed to load a cloud_desc from #{@cloud_id}", MU::ERR, details: @config
208
+ return nil
209
+ end
210
+
211
+ bok['name'] = cloud_desc.domain_name
212
+ bok['elasticsearch_version'] = cloud_desc.elasticsearch_version
213
+ bok['instance_count'] = cloud_desc.elasticsearch_cluster_config.instance_count
214
+ bok['instance_type'] = cloud_desc.elasticsearch_cluster_config.instance_type
215
+ bok['zone_aware'] = cloud_desc.elasticsearch_cluster_config.zone_awareness_enabled
216
+
217
+ if cloud_desc.elasticsearch_cluster_config.dedicated_master_enabled
218
+ bok['dedicated_masters'] = cloud_desc.elasticsearch_cluster_config.dedicated_master_count
219
+ bok['master_instance_type'] = cloud_desc.elasticsearch_cluster_config.dedicated_master_type
220
+ end
221
+
222
+ if cloud_desc.access_policies and !cloud_desc.access_policies.empty?
223
+ bok['access_policies'] = JSON.parse(cloud_desc.access_policies)
224
+ end
225
+
226
+ if cloud_desc.advanced_options and !cloud_desc.advanced_options.empty?
227
+ bok['advanced_options'] = cloud_desc.advanced_options
228
+ end
229
+
230
+ bok['ebs_size'] = cloud_desc.ebs_options.volume_size
231
+ bok['ebs_type'] = cloud_desc.ebs_options.volume_type
232
+ bok['ebs_iops'] = cloud_desc.ebs_options.iops if cloud_desc.ebs_options.iops
233
+
234
+ if cloud_desc.snapshot_options and cloud_desc.snapshot_options.automated_snapshot_start_hour
235
+ bok['snapshot_hour'] = cloud_desc.snapshot_options.automated_snapshot_start_hour
236
+ end
237
+
238
+ if cloud_desc.cognito_options.user_pool_id and
239
+ cloud_desc.cognito_options.identity_pool_id
240
+ bok['user_pool_id'] = cloud_desc.cognito_options.user_pool_id
241
+ bok['identity_pool_id'] = cloud_desc.cognito_options.identity_pool_id
242
+ end
243
+
244
+ tags = MU::Cloud::AWS.elasticsearch(region: @config['region'], credentials: @credentials).list_tags(arn: cloud_desc.arn).tag_list
245
+ if tags and !tags.empty?
246
+ bok['tags'] = MU.structToHash(tags)
247
+ end
248
+
249
+ if cloud_desc.vpc_options
250
+ bok['vpc'] = MU::Config::Ref.get(
251
+ id: cloud_desc.vpc_options.vpc_id,
252
+ cloud: "AWS",
253
+ credentials: @credentials,
254
+ type: "vpcs",
255
+ region: @config['region'],
256
+ subnets: cloud_desc.vpc_options.subnet_ids.map { |s| { "subnet_id" => s } }
257
+ )
258
+ if cloud_desc.vpc_options.security_group_ids and
259
+ !cloud_desc.vpc_options.security_group_ids.empty?
260
+ bok['add_firewall_rules'] = cloud_desc.vpc_options.security_group_ids.map { |sg|
261
+ MU::Config::Ref.get(
262
+ id: sg,
263
+ cloud: "AWS",
264
+ credentials: @credentials,
265
+ region: @config['region'],
266
+ type: "firewall_rules",
267
+ )
268
+ }
269
+ end
270
+ end
271
+
272
+ if cloud_desc.log_publishing_options
273
+ # XXX this is primitive... there are multiple other log types now,
274
+ # and this should be a Ref blob, not a flat string
275
+ cloud_desc.log_publishing_options.each_pair { |type, whither|
276
+ if type == "SEARCH_SLOW_LOGS"
277
+ bok['slow_logs'] = whither.cloud_watch_logs_log_group_arn
278
+ end
279
+ }
280
+ end
281
+
282
+ bok
283
+ end
284
+
194
285
  # Cloud-specific configuration properties.
195
286
  # @param _config [MU::Config]: The calling MU::Config object
196
287
  # @return [Array<Array,Hash>]: List of required fields, and json-schema Hash of cloud-specific configuration parameters for this resource
@@ -200,7 +291,7 @@ module MU
200
291
  versions = begin
201
292
  MU::Cloud::AWS.elasticsearch.list_elasticsearch_versions.elasticsearch_versions
202
293
  rescue MuError
203
- ["7.1", "6.8", "6.7", "6.5", "6.4", "6.3", "6.2", "6.0", "5.6"]
294
+ ["7.4", "7.1", "6.8", "6.7", "6.5", "6.4", "6.3", "6.2", "6.0", "5.6"]
204
295
  end
205
296
  instance_types = begin
206
297
  MU::Cloud::AWS.elasticsearch.list_elasticsearch_instance_types(
@@ -215,6 +306,8 @@ module MU
215
306
  ).elasticsearch_instance_types
216
307
  end
217
308
 
309
+ polschema = MU::Config::Role.schema["properties"]["policies"]
310
+ polschema.deep_merge!(MU::Cloud.resourceClass("AWS", "Role").condition_schema)
218
311
 
219
312
  schema = {
220
313
  "name" => {
@@ -236,9 +329,10 @@ module MU
236
329
  "default" => 0,
237
330
  "description" => "Separate, dedicated master node(s), over and above the search instances specified in instance_count."
238
331
  },
332
+ "policies" => polschema,
239
333
  "access_policies" => {
240
334
  "type" => "object",
241
- "description" => "An IAM policy document for access to ElasticSearch. Our parser expects this to be defined inline like the rest of your YAML/JSON Basket of Kittens, not as raw JSON. For guidance on ElasticSearch IAM capabilities, see: https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/es-ac.html"
335
+ "description" => "An IAM policy document for access to ElasticSearch (see {policies} for setting complex access policies with runtime dependencies). Our parser expects this to be defined inline like the rest of your YAML/JSON Basket of Kittens, not as raw JSON. For guidance on ElasticSearch IAM capabilities, see: https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/es-ac.html"
242
336
  },
243
337
  "master_instance_type" => {
244
338
  "type" => "string",
@@ -246,7 +340,7 @@ module MU
246
340
  },
247
341
  "ebs_type" => {
248
342
  "type" => "string",
249
- "default" => "standard",
343
+ "default" => "gp2",
250
344
  "description" => "Type of EBS storage to use for cluster nodes. If 'none' is specified, EBS storage will not be used, but this is only valid for certain instance types.",
251
345
  "enum" => ["standard", "gp2", "io1", "none"]
252
346
  },
@@ -509,9 +603,51 @@ module MU
509
603
  params[:snapshot_options][:automated_snapshot_start_hour] = @config['snapshot_hour']
510
604
  end
511
605
 
512
- if @config['access_policies']
513
- # TODO check against ext.access_policies.options
514
- params[:access_policies] = JSON.generate(@config['access_policies'])
606
+ if ext
607
+ # Despite being called access_policies, this parameter actually
608
+ # only accepts one policy. So, we'll munge everything we have
609
+ # together into one policy with multiple Statements.
610
+ policy = nil
611
+ # TODO check against ext.access_policy.options
612
+
613
+ if @config['access_policies']
614
+ policy = @config['access_policies']
615
+ # ensure the "Statement" key is cased in a predictable way
616
+ statement_key = nil
617
+ policy.each_pair { |k, v|
618
+ if k.downcase == "statement" and k != "Statement"
619
+ statement_key = k
620
+ break
621
+ end
622
+ }
623
+ if statement_key
624
+ policy["Statement"] = policy.delete(statement_key)
625
+ end
626
+ if !policy["Statement"].is_a?(Array)
627
+ policy["Statement"] = [policy["Statement"]]
628
+ end
629
+ end
630
+
631
+ if @config['policies']
632
+ @config['policies'].each { |p|
633
+ p['targets'].each { |t|
634
+ if t['path']
635
+ t['path'].gsub!(/#SELF/, @mu_name.downcase)
636
+ end
637
+ }
638
+ parsed = MU::Cloud.resourceClass("AWS", "Role").genPolicyDocument([p], deploy_obj: @deploy, bucket_style: true).first.values.first
639
+
640
+ if policy and policy["Statement"]
641
+ policy["Statement"].concat(parsed["Statement"])
642
+ else
643
+ policy = parsed
644
+ end
645
+ }
646
+ end
647
+
648
+ if policy
649
+ params[:access_policies] = JSON.generate(policy)
650
+ end
515
651
  end
516
652
 
517
653
  if @config['slow_logs']
@@ -677,7 +813,7 @@ module MU
677
813
  raise MU::MuError, "Can't tag ElasticSearch domain, cloud descriptor came back without an ARN"
678
814
  end
679
815
 
680
- MU::Cloud::AWS.elasticsearch(region: @config['region'], credentials: @config['credentials']).add_tags(
816
+ MU::Cloud::AWS.elasticsearch(region: @config['region'], credentials: @credentials).add_tags(
681
817
  arn: domain.arn,
682
818
  tag_list: tags
683
819
  )