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
@@ -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: @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: @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: @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: @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" => @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: @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: @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: @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']
@@ -547,7 +683,7 @@ module MU
547
683
  params[:log_publishing_options]["SEARCH_SLOW_LOGS"] = {}
548
684
  params[:log_publishing_options]["SEARCH_SLOW_LOGS"][:enabled] = true
549
685
  params[:log_publishing_options]["SEARCH_SLOW_LOGS"][:cloud_watch_logs_log_group_arn] = arn
550
- MU::Cloud.resourceClass("AWS", "Log").allowService("es.amazonaws.com", arn, @config['region'])
686
+ MU::Cloud.resourceClass("AWS", "Log").allowService("es.amazonaws.com", arn, @region)
551
687
  end
552
688
  end
553
689
 
@@ -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: @region, credentials: @credentials).add_tags(
681
817
  arn: domain.arn,
682
818
  tag_list: tags
683
819
  )
@@ -85,11 +85,11 @@ module MU
85
85
  MU::Cloud.fetchUserdata(
86
86
  platform: @config["platform"],
87
87
  cloud: "AWS",
88
- credentials: @config['credentials'],
88
+ credentials: @credentials,
89
89
  template_variables: {
90
90
  "deployKey" => Base64.urlsafe_encode64(@deploy.public_key),
91
91
  "deploySSHKey" => @deploy.ssh_public_key,
92
- "muID" => MU.deploy_id,
92
+ "muID" => @deploy.deploy_id,
93
93
  "muUser" => MU.mu_user,
94
94
  "publicIP" => MU.mu_public_ip,
95
95
  "mommaCatPort" => MU.mommaCatPort,
@@ -242,8 +242,8 @@ module MU
242
242
  else
243
243
  MU::Cloud::AWS.createStandardTags(
244
244
  instance.instance_id,
245
- region: @config['region'],
246
- credentials: @config['credentials'],
245
+ region: @region,
246
+ credentials: @credentials,
247
247
  optional: @config['optional_tags'],
248
248
  nametag: @mu_name,
249
249
  othertags: @config['tags']
@@ -258,7 +258,7 @@ module MU
258
258
  parent_thread_id = Thread.current.object_id
259
259
  Thread.new {
260
260
  MU.dupGlobals(parent_thread_id)
261
- MU::Cloud::AWS::Server.cleanup(noop: false, ignoremaster: false, region: @config['region'], credentials: @config['credentials'], flags: { "skipsnapshots" => true } )
261
+ MU::Cloud::AWS::Server.cleanup(noop: false, ignoremaster: false, region: @region, credentials: @credentials, flags: { "skipsnapshots" => true } )
262
262
  }
263
263
  end
264
264
  end
@@ -307,9 +307,9 @@ module MU
307
307
  instance_descriptor[:user_data] = Base64.encode64(@userdata)
308
308
  end
309
309
 
310
- MU::Cloud::AWS::Server.waitForAMI(@config["image_id"], region: @config['region'], credentials: @config['credentials'])
310
+ MU::Cloud::AWS::Server.waitForAMI(@config["image_id"], region: @region, credentials: @credentials)
311
311
 
312
- instance_descriptor[:block_device_mappings] = MU::Cloud::AWS::Server.configureBlockDevices(image_id: @config["image_id"], storage: @config['storage'], region: @config['region'], credentials: @credentials)
312
+ instance_descriptor[:block_device_mappings] = MU::Cloud::AWS::Server.configureBlockDevices(image_id: @config["image_id"], storage: @config['storage'], region: @region, credentials: @credentials)
313
313
 
314
314
  instance_descriptor[:monitoring] = {enabled: @config['monitoring']}
315
315
 
@@ -330,10 +330,26 @@ module MU
330
330
  resp.nil? or resp.instances.nil? or instance.nil?
331
331
  }
332
332
 
333
+ bad_subnets = []
334
+ mysubnet_ids = if mySubnets
335
+ mySubnets.map { |s| s.cloud_id }
336
+ end
333
337
  begin
334
338
  MU.retrier([Aws::EC2::Errors::InvalidGroupNotFound, Aws::EC2::Errors::InvalidSubnetIDNotFound, Aws::EC2::Errors::InvalidParameterValue], loop_if: loop_if, loop_msg: "Waiting for run_instances to return #{@mu_name}") {
335
- resp = MU::Cloud::AWS.ec2(region: @config['region'], credentials: @config['credentials']).run_instances(instance_descriptor)
339
+ resp = MU::Cloud::AWS.ec2(region: @region, credentials: @credentials).run_instances(instance_descriptor)
336
340
  }
341
+ rescue Aws::EC2::Errors::Unsupported => e
342
+ bad_subnets << instance_descriptor[:subnet_id]
343
+ better_subnet = (mysubnet_ids - bad_subnets).sample
344
+ if e.message !~ /is not supported in your requested Availability Zone/ and
345
+ (mysubnet_ids.nil? or mysubnet_ids.empty? or
346
+ mysubnet_ids.size == bad_subnets.size or
347
+ better_subnet.nil? or better_subnet == "")
348
+ raise MuError.new e.message, details: mysubnet_ids
349
+ end
350
+ instance_descriptor[:subnet_id] = (mysubnet_ids - bad_subnets).sample
351
+ MU.log "One or more subnets does not support this instance type, attempting with #{instance_descriptor[:subnet_id]} instead", MU::WARN, details: bad_subnets
352
+ retry
337
353
  rescue Aws::EC2::Errors::InvalidRequest => e
338
354
  MU.log e.message, MU::ERR, details: instance_descriptor
339
355
  raise e
@@ -351,12 +367,12 @@ module MU
351
367
  if hard
352
368
  groupname = nil
353
369
  if !@config['basis'].nil?
354
- resp = MU::Cloud::AWS.autoscale(region: @config['region'], credentials: @config['credentials']).describe_auto_scaling_instances(
370
+ resp = MU::Cloud::AWS.autoscale(region: @region, credentials: @credentials).describe_auto_scaling_instances(
355
371
  instance_ids: [@cloud_id]
356
372
  )
357
373
  groupname = resp.auto_scaling_instances.first.auto_scaling_group_name
358
374
  MU.log "Pausing Autoscale processes in #{groupname}", MU::NOTICE
359
- MU::Cloud::AWS.autoscale(region: @config['region'], credentials: @config['credentials']).suspend_processes(
375
+ MU::Cloud::AWS.autoscale(region: @region, credentials: @credentials).suspend_processes(
360
376
  auto_scaling_group_name: groupname,
361
377
  scaling_processes: [
362
378
  "Terminate",
@@ -365,22 +381,22 @@ module MU
365
381
  end
366
382
  begin
367
383
  MU.log "Stopping #{@mu_name} (#{@cloud_id})", MU::NOTICE
368
- MU::Cloud::AWS.ec2(region: @config['region'], credentials: @config['credentials']).stop_instances(
384
+ MU::Cloud::AWS.ec2(region: @region, credentials: @credentials).stop_instances(
369
385
  instance_ids: [@cloud_id]
370
386
  )
371
- MU::Cloud::AWS.ec2(region: @config['region'], credentials: @config['credentials']).wait_until(:instance_stopped, instance_ids: [@cloud_id]) do |waiter|
387
+ MU::Cloud::AWS.ec2(region: @region, credentials: @credentials).wait_until(:instance_stopped, instance_ids: [@cloud_id]) do |waiter|
372
388
  waiter.before_attempt do
373
389
  MU.log "Waiting for #{@mu_name} to stop for hard reboot"
374
390
  end
375
391
  end
376
392
  MU.log "Starting #{@mu_name} (#{@cloud_id})"
377
- MU::Cloud::AWS.ec2(region: @config['region'], credentials: @config['credentials']).start_instances(
393
+ MU::Cloud::AWS.ec2(region: @region, credentials: @credentials).start_instances(
378
394
  instance_ids: [@cloud_id]
379
395
  )
380
396
  ensure
381
397
  if !groupname.nil?
382
398
  MU.log "Resuming Autoscale processes in #{groupname}", MU::NOTICE
383
- MU::Cloud::AWS.autoscale(region: @config['region'], credentials: @config['credentials']).resume_processes(
399
+ MU::Cloud::AWS.autoscale(region: @region, credentials: @credentials).resume_processes(
384
400
  auto_scaling_group_name: groupname,
385
401
  scaling_processes: [
386
402
  "Terminate",
@@ -390,7 +406,7 @@ module MU
390
406
  end
391
407
  else
392
408
  MU.log "Rebooting #{@mu_name} (#{@cloud_id})"
393
- MU::Cloud::AWS.ec2(region: @config['region'], credentials: @config['credentials']).reboot_instances(
409
+ MU::Cloud::AWS.ec2(region: @region, credentials: @credentials).reboot_instances(
394
410
  instance_ids: [@cloud_id]
395
411
  )
396
412
  end
@@ -405,7 +421,7 @@ module MU
405
421
  return nil if @config.nil? or @deploy.nil?
406
422
 
407
423
  nat_ssh_key = nat_ssh_user = nat_ssh_host = nil
408
- if !@config["vpc"].nil? and !MU::Cloud.resourceClass("AWS", "VPC").haveRouteToInstance?(cloud_desc, region: @config['region'], credentials: @config['credentials'])
424
+ if !@config["vpc"].nil? and !MU::Cloud.resourceClass("AWS", "VPC").haveRouteToInstance?(cloud_desc, region: @region, credentials: @credentials)
409
425
  if !@nat.nil?
410
426
  if @nat.is_a?(Struct) && @nat.nat_gateway_id && @nat.nat_gateway_id.start_with?("nat-")
411
427
  raise MuError, "Configured to use NAT Gateway, but I have no route to instance. Either use Bastion, or configure VPC peering"
@@ -449,6 +465,9 @@ module MU
449
465
  raise MuError, "Couldn't find instance #{@mu_name} (#{@cloud_id})" if !cloud_desc
450
466
  return false if !MU::MommaCat.lock(@cloud_id+"-orchestrate", true)
451
467
  return false if !MU::MommaCat.lock(@cloud_id+"-groom", true)
468
+
469
+ getIAMProfile
470
+
452
471
  finish = Proc.new { |status|
453
472
  MU::MommaCat.unlock(@cloud_id+"-orchestrate")
454
473
  MU::MommaCat.unlock(@cloud_id+"-groom")
@@ -457,8 +476,8 @@ module MU
457
476
 
458
477
  MU::Cloud::AWS.createStandardTags(
459
478
  @cloud_id,
460
- region: @config['region'],
461
- credentials: @config['credentials'],
479
+ region: @region,
480
+ credentials: @credentials,
462
481
  optional: @config['optional_tags'],
463
482
  nametag: @mu_name,
464
483
  othertags: @config['tags']
@@ -474,7 +493,15 @@ module MU
474
493
  }
475
494
  MU.retrier([Aws::EC2::Errors::ServiceError], max: 30, wait: 40, loop_if: loop_if) { |retries, _wait|
476
495
  if cloud_desc and cloud_desc.state.name == "terminated"
477
- raise MuError, "#{@cloud_id} appears to have been terminated mid-bootstrap!"
496
+ logs = if !@config['basis'].nil?
497
+ pool = @deploy.findLitterMate(type: "server_pools", name: @config["name"])
498
+ if pool
499
+ MU::Cloud::AWS.autoscale(region: @region, credentials: @credentials).describe_scaling_activities(auto_scaling_group_name: pool.cloud_id).activities
500
+ else
501
+ nil
502
+ end
503
+ end
504
+ raise MuError.new, "#{@cloud_id} appears to have been terminated mid-bootstrap!", details: logs
478
505
  end
479
506
  if retries % 3 == 0
480
507
  MU.log "Waiting for EC2 instance #{@mu_name} (#{@cloud_id}) to be ready...", MU::NOTICE
@@ -495,7 +522,7 @@ module MU
495
522
 
496
523
  if !@config['src_dst_check'] and !@config["vpc"].nil?
497
524
  MU.log "Disabling source_dest_check #{@mu_name} (making it NAT-worthy)"
498
- MU::Cloud::AWS.ec2(region: @config['region'], credentials: @config['credentials']).modify_instance_attribute(
525
+ MU::Cloud::AWS.ec2(region: @region, credentials: @credentials).modify_instance_attribute(
499
526
  instance_id: @cloud_id,
500
527
  source_dest_check: { value: false }
501
528
  )
@@ -503,7 +530,7 @@ module MU
503
530
 
504
531
  # Set console termination protection. Autoscale nodes won't set this
505
532
  # by default.
506
- MU::Cloud::AWS.ec2(region: @config['region'], credentials: @config['credentials']).modify_instance_attribute(
533
+ MU::Cloud::AWS.ec2(region: @region, credentials: @credentials).modify_instance_attribute(
507
534
  instance_id: @cloud_id,
508
535
  disable_api_termination: { value: true}
509
536
  )
@@ -516,7 +543,6 @@ module MU
516
543
  notify
517
544
  end
518
545
 
519
- getIAMProfile
520
546
  finish.call(false) if !bootstrapGroomer
521
547
 
522
548
  # Make sure we got our name written everywhere applicable
@@ -574,7 +600,7 @@ module MU
574
600
  regions.each { |r|
575
601
  searches.each { |search|
576
602
  search_threads << Thread.new(search) { |params|
577
- MU.retrier([Aws::EC2::Errors::InvalidInstanceIDNotFound], wait: 5, max: 5, ignoreme: [Aws::EC2::Errors::InvalidInstanceIDNotFound]) {
603
+ MU.retrier([], wait: 5, max: 5, ignoreme: [Aws::EC2::Errors::InvalidInstanceIDNotFound]) {
578
604
  MU::Cloud::AWS.ec2(region: r, credentials: args[:credentials]).describe_instances(params).reservations.each { |resp|
579
605
  next if resp.nil? or resp.instances.nil?
580
606
  resp.instances.each { |i|
@@ -604,9 +630,9 @@ module MU
604
630
  def toKitten(**_args)
605
631
  bok = {
606
632
  "cloud" => "AWS",
607
- "credentials" => @config['credentials'],
633
+ "credentials" => @credentials,
608
634
  "cloud_id" => @cloud_id,
609
- "region" => @config['region']
635
+ "region" => @region
610
636
  }
611
637
 
612
638
  if !cloud_desc
@@ -616,7 +642,7 @@ module MU
616
642
 
617
643
  asgs = MU::Cloud.resourceClass("AWS", "ServerPool").find(
618
644
  instance_id: @cloud_id,
619
- region: @config['region'],
645
+ region: @region,
620
646
  credentials: @credentials
621
647
  )
622
648
  if asgs.size > 0
@@ -651,7 +677,7 @@ module MU
651
677
 
652
678
  bok['image_id'] = cloud_desc.image_id
653
679
 
654
- ami = MU::Cloud::AWS.ec2(region: @config['region'], credentials: @credentials).describe_images(image_ids: [bok['image_id']]).images.first
680
+ ami = MU::Cloud::AWS.ec2(region: @region, credentials: @credentials).describe_images(image_ids: [bok['image_id']]).images.first
655
681
 
656
682
  if ami.nil? or ami.empty?
657
683
  MU.log "#{@mu_name} source image #{bok['image_id']} no longer exists", MU::WARN
@@ -660,7 +686,7 @@ module MU
660
686
 
661
687
  if cloud_desc.block_device_mappings and !cloud_desc.block_device_mappings.empty?
662
688
  vol_map = {}
663
- MU::Cloud::AWS.ec2(region: @config['region'], credentials: @credentials).describe_volumes(
689
+ MU::Cloud::AWS.ec2(region: @region, credentials: @credentials).describe_volumes(
664
690
  volume_ids: cloud_desc.block_device_mappings.map { |d| d.ebs.volume_id if d.ebs }
665
691
  ).volumes.each { |vol|
666
692
  vol_map[vol.volume_id] = vol
@@ -696,7 +722,7 @@ module MU
696
722
  id: int.vpc_id,
697
723
  cloud: "AWS",
698
724
  credentials: @credentials,
699
- region: @config['region'],
725
+ region: @region,
700
726
  subnet_id: int.subnet_id,
701
727
  habitat: MU::Config::Ref.get(
702
728
  id: int.owner_id,
@@ -725,11 +751,11 @@ module MU
725
751
  if int.groups.size > 0
726
752
 
727
753
  require 'mu/providers/aws/firewall_rule'
728
- ifaces = MU::Cloud.resourceClass("AWS", "FirewallRule").getAssociatedInterfaces(int.groups.map { |sg| sg.group_id }, credentials: @credentials, region: @config['region'])
754
+ ifaces = MU::Cloud.resourceClass("AWS", "FirewallRule").getAssociatedInterfaces(int.groups.map { |sg| sg.group_id }, credentials: @credentials, region: @region)
729
755
  done_local_rules = false
730
756
  int.groups.each { |sg|
731
757
  if !done_local_rules and ifaces[sg.group_id].size == 1
732
- sg_desc = MU::Cloud.resourceClass("AWS", "FirewallRule").find(cloud_id: sg.group_id, credentials: @credentials, region: @config['region']).values.first
758
+ sg_desc = MU::Cloud.resourceClass("AWS", "FirewallRule").find(cloud_id: sg.group_id, credentials: @credentials, region: @region).values.first
733
759
  if sg_desc
734
760
  bok["ingress_rules"] = MU::Cloud.resourceClass("AWS", "FirewallRule").rulesToBoK(sg_desc.ip_permissions)
735
761
  bok["ingress_rules"].concat(MU::Cloud.resourceClass("AWS", "FirewallRule").rulesToBoK(sg_desc.ip_permissions_egress, egress: true))
@@ -743,7 +769,7 @@ module MU
743
769
  cloud: "AWS",
744
770
  credentials: @credentials,
745
771
  type: "firewall_rules",
746
- region: @config['region']
772
+ region: @region
747
773
  )
748
774
  }
749
775
  end
@@ -799,7 +825,7 @@ module MU
799
825
  if !@config['chef_data'].nil?
800
826
  deploydata.merge!(@config['chef_data'])
801
827
  end
802
- deploydata["region"] = @config['region'] if !@config['region'].nil?
828
+ deploydata["region"] = @region if !@region.nil?
803
829
  if !@named
804
830
  MU::MommaCat.nameKitten(self, no_dns: true)
805
831
  @named = true
@@ -883,7 +909,7 @@ module MU
883
909
  # Canonical Amazon Resource Number for this resource
884
910
  # @return [String]
885
911
  def arn
886
- "arn:"+(MU::Cloud::AWS.isGovCloud?(@config["region"]) ? "aws-us-gov" : "aws")+":ec2:"+@config['region']+":"+MU::Cloud::AWS.credToAcct(@config['credentials'])+":instance/"+@cloud_id
912
+ "arn:"+(MU::Cloud::AWS.isGovCloud?(@region) ? "aws-us-gov" : "aws")+":ec2:"+@region+":"+MU::Cloud::AWS.credToAcct(@credentials)+":instance/"+@cloud_id
887
913
  end
888
914
 
889
915
  @cloud_desc_cache = nil
@@ -891,11 +917,12 @@ module MU
891
917
  # @return [Openstruct]
892
918
  def cloud_desc(use_cache: true)
893
919
  return @cloud_desc_cache if @cloud_desc_cache and use_cache
920
+ return nil if !@cloud_id
894
921
  max_retries = 5
895
922
  retries = 0
896
923
  if !@cloud_id.nil?
897
924
  begin
898
- resp = MU::Cloud::AWS.ec2(region: @config['region'], credentials: @config['credentials']).describe_instances(instance_ids: [@cloud_id])
925
+ resp = MU::Cloud::AWS.ec2(region: @region, credentials: @credentials).describe_instances(instance_ids: [@cloud_id])
899
926
  if resp and resp.reservations and resp.reservations.first and
900
927
  resp.reservations.first.instances and
901
928
  resp.reservations.first.instances.first
@@ -942,7 +969,7 @@ module MU
942
969
  # Our deploydata gets corrupted often with server pools, this will cause us to use the wrong IP to identify a node
943
970
  # which will cause us to create certificates, DNS records and other artifacts with incorrect information which will cause our deploy to fail.
944
971
  # The cloud_id is always correct so lets use 'cloud_desc' to get the correct IPs
945
- if MU::Cloud.resourceClass("AWS", "VPC").haveRouteToInstance?(cloud_desc, region: @config['region'], credentials: @config['credentials']) or @deploydata["public_ip_address"].nil?
972
+ if MU::Cloud.resourceClass("AWS", "VPC").haveRouteToInstance?(cloud_desc, region: @region, credentials: @credentials) or @deploydata["public_ip_address"].nil?
946
973
  @config['canonical_ip'] = cloud_desc.private_ip_address
947
974
  @deploydata["private_ip_address"] = cloud_desc.private_ip_address
948
975
  return cloud_desc.private_ip_address
@@ -1169,7 +1196,7 @@ module MU
1169
1196
  retries = 0
1170
1197
  MU.log "Waiting for Windows instance password to be set by Amazon and flagged as available from the API. Note- if you're using a source AMI that already has its password set, this may fail. You'll want to set use_cloud_provider_windows_password to false if this is the case.", MU::NOTICE
1171
1198
  begin
1172
- MU::Cloud::AWS.ec2(region: @config['region'], credentials: @config['credentials']).wait_until(:password_data_available, instance_id: @cloud_id) do |waiter|
1199
+ MU::Cloud::AWS.ec2(region: @region, credentials: @credentials).wait_until(:password_data_available, instance_id: @cloud_id) do |waiter|
1173
1200
  waiter.max_attempts = 60
1174
1201
  waiter.before_attempt do |attempts|
1175
1202
  MU.log "Waiting for Windows password data to be available for node #{@mu_name}", MU::NOTICE if attempts % 5 == 0
@@ -1181,15 +1208,15 @@ module MU
1181
1208
  rescue Aws::Waiters::Errors::TooManyAttemptsError => e
1182
1209
  if retries < 2
1183
1210
  retries = retries + 1
1184
- MU.log "wait_until(:password_data_available, instance_id: #{@cloud_id}) in #{@config['region']} never got a good response, retrying (#{retries}/2)", MU::WARN, details: e.inspect
1211
+ MU.log "wait_until(:password_data_available, instance_id: #{@cloud_id}) in #{@region} never got a good response, retrying (#{retries}/2)", MU::WARN, details: e.inspect
1185
1212
  retry
1186
1213
  else
1187
- MU.log "wait_until(:password_data_available, instance_id: #{@cloud_id}) in #{@config['region']} never returned- this image may not be configured to have its password set by AWS.", MU::ERR
1214
+ MU.log "wait_until(:password_data_available, instance_id: #{@cloud_id}) in #{@region} never returned- this image may not be configured to have its password set by AWS.", MU::ERR
1188
1215
  return nil
1189
1216
  end
1190
1217
  end
1191
1218
 
1192
- resp = MU::Cloud::AWS.ec2(region: @config['region'], credentials: @config['credentials']).get_password_data(instance_id: @cloud_id)
1219
+ resp = MU::Cloud::AWS.ec2(region: @region, credentials: @credentials).get_password_data(instance_id: @cloud_id)
1193
1220
  encrypted_password = resp.password_data
1194
1221
 
1195
1222
  # Note: This is already implemented in the decrypt_windows_password API call
@@ -1208,7 +1235,7 @@ module MU
1208
1235
  # instead of VPC.
1209
1236
  # @param ip [String]: Request a specific IP address.
1210
1237
  # @param region [String]: The cloud provider region
1211
- def self.findFreeElasticIp(classic: false, ip: nil, region: MU.curRegion)
1238
+ def self.findFreeElasticIp(classic: false, ip: nil, region: MU.curRegion, credentials: nil)
1212
1239
  filters = Array.new
1213
1240
  if !classic
1214
1241
  filters << {name: "domain", values: ["vpc"]}
@@ -1218,25 +1245,22 @@ module MU
1218
1245
  filters << {name: "public-ip", values: [ip]} if ip != nil
1219
1246
 
1220
1247
  if filters.size > 0
1221
- resp = MU::Cloud::AWS.ec2(region: region).describe_addresses(filters: filters)
1248
+ resp = MU::Cloud::AWS.ec2(region: region, credentials: credentials).describe_addresses(filters: filters)
1222
1249
  else
1223
- resp = MU::Cloud::AWS.ec2(region: region).describe_addresses()
1250
+ resp = MU::Cloud::AWS.ec2(region: region, credentials: credentials).describe_addresses
1224
1251
  end
1225
1252
  resp.addresses.each { |address|
1226
- return address if (address.network_interface_id.nil? || address.network_interface_id.empty?) && !@eips_used.include?(address.public_ip)
1253
+ return address if (address.network_interface_id.nil? or address.network_interface_id.empty?) or !@eips_used.include?(address.public_ip)
1227
1254
  }
1228
- if ip != nil
1229
- if !classic
1230
- raise MuError, "Requested EIP #{ip}, but no such IP exists or is avaulable in VPC"
1231
- else
1232
- raise MuError, "Requested EIP #{ip}, but no such IP exists or is available in EC2 Classic"
1233
- end
1255
+ if !ip.nil?
1256
+ mode = classic ? "EC2 Classic" : "VPC"
1257
+ raise MuError.new "Requested EIP #{ip}, but no such IP exists or is available in #{mode} mode#{credentials ? " with credentials #{credentials}" : ""}", details: { "describe_address filters" => filters, "describe_address response" => resp }
1234
1258
  end
1235
1259
  if !classic
1236
- resp = MU::Cloud::AWS.ec2(region: region).allocate_address(domain: "vpc")
1260
+ resp = MU::Cloud::AWS.ec2(region: region, credentials: credentials).allocate_address(domain: "vpc")
1237
1261
  new_ip = resp.public_ip
1238
1262
  else
1239
- new_ip = MU::Cloud::AWS.ec2(region: region).allocate_address().public_ip
1263
+ new_ip = MU::Cloud::AWS.ec2(region: region, credentials: credentials).allocate_address().public_ip
1240
1264
  end
1241
1265
  filters = [{name: "public-ip", values: [new_ip]}]
1242
1266
  if resp.domain
@@ -1252,8 +1276,8 @@ module MU
1252
1276
  begin
1253
1277
  begin
1254
1278
  sleep 5
1255
- resp = MU::Cloud::AWS.ec2(region: region).describe_addresses(
1256
- filters: filters
1279
+ resp = MU::Cloud::AWS.ec2(region: region, credentials: credentials).describe_addresses(
1280
+ filters: filters
1257
1281
  )
1258
1282
  addr = resp.addresses.first
1259
1283
  end while resp.addresses.size < 1 or addr.public_ip.nil?
@@ -1274,19 +1298,19 @@ module MU
1274
1298
  def addVolume(dev, size, type: "gp2", delete_on_termination: false)
1275
1299
 
1276
1300
  if setDeleteOntermination(dev, delete_on_termination)
1277
- MU.log "A volume #{device} already attached to #{self}, skipping", MU::NOTICE
1301
+ MU.log "A volume #{dev} already attached to #{self}, skipping", MU::NOTICE
1278
1302
  return
1279
1303
  end
1280
1304
 
1281
1305
  MU.log "Creating #{size}GB #{type} volume on #{dev} for #{@cloud_id}"
1282
- creation = MU::Cloud::AWS.ec2(region: @config['region'], credentials: @config['credentials']).create_volume(
1306
+ creation = MU::Cloud::AWS.ec2(region: @region, credentials: @credentials).create_volume(
1283
1307
  availability_zone: cloud_desc.placement.availability_zone,
1284
1308
  size: size,
1285
1309
  volume_type: type
1286
1310
  )
1287
1311
 
1288
1312
  MU.retrier(wait: 3, loop_if: Proc.new {
1289
- creation = MU::Cloud::AWS.ec2(region: @config['region'], credentials: @config['credentials']).describe_volumes(volume_ids: [creation.volume_id]).volumes.first
1313
+ creation = MU::Cloud::AWS.ec2(region: @region, credentials: @credentials).describe_volumes(volume_ids: [creation.volume_id]).volumes.first
1290
1314
  if !["creating", "available"].include?(creation.state)
1291
1315
  raise MuError, "Saw state '#{creation.state}' while creating #{size}GB #{type} volume on #{dev} for #{@cloud_id}"
1292
1316
  end
@@ -1297,27 +1321,35 @@ module MU
1297
1321
  if @deploy
1298
1322
  MU::Cloud::AWS.createStandardTags(
1299
1323
  creation.volume_id,
1300
- region: @config['region'],
1301
- credentials: @config['credentials'],
1324
+ region: @region,
1325
+ credentials: @credentials,
1302
1326
  optional: @config['optional_tags'],
1303
1327
  nametag: @mu_name+"-"+dev.upcase,
1304
1328
  othertags: @config['tags']
1305
1329
  )
1306
1330
  end
1307
1331
 
1308
- attachment = MU::Cloud::AWS.ec2(region: @config['region'], credentials: @config['credentials']).attach_volume(
1309
- device: dev,
1310
- instance_id: @cloud_id,
1311
- volume_id: creation.volume_id
1312
- )
1332
+ MU.log "Attaching #{creation.volume_id} as #{dev} to #{@cloud_id} in #{@region} (credentials #{@credentials})"
1333
+ attachment = nil
1334
+ MU.retrier([Aws::EC2::Errors::IncorrectState], wait: 15, max: 4) {
1335
+ attachment = MU::Cloud::AWS.ec2(region: @region, credentials: @credentials).attach_volume(
1336
+ device: dev,
1337
+ instance_id: @cloud_id,
1338
+ volume_id: creation.volume_id
1339
+ )
1340
+ }
1313
1341
 
1314
1342
  begin
1315
- sleep 3
1316
- attachment = MU::Cloud::AWS.ec2(region: @config['region'], credentials: @config['credentials']).describe_volumes(volume_ids: [attachment.volume_id]).volumes.first.attachments.first
1317
- if !["attaching", "attached"].include?(attachment.state)
1318
- raise MuError, "Saw state '#{creation.state}' while creating #{size}GB #{type} volume on #{dev} for #{@cloud_id}"
1343
+ att_resp = MU::Cloud::AWS.ec2(region: @region, credentials: @credentials).describe_volumes(volume_ids: [attachment.volume_id])
1344
+ if att_resp and att_resp.volumes and !att_resp.volumes.empty? and
1345
+ att_resp.volumes.first.attachments and
1346
+ !att_resp.volumes.first.attachments.empty?
1347
+ attachment = att_resp.volumes.first.attachments.first
1348
+ if !attachment.nil? and !["attaching", "attached"].include?(attachment.state)
1349
+ raise MuError, "Saw state '#{creation.state}' while creating #{size}GB #{type} volume on #{dev} for #{@cloud_id}"
1350
+ end
1319
1351
  end
1320
- end while attachment.state != "attached"
1352
+ end while attachment.nil? or attachment.state != "attached"
1321
1353
 
1322
1354
  # Set delete_on_termination, which for some reason is an instance
1323
1355
  # attribute and not on the attachment
@@ -1333,7 +1365,7 @@ module MU
1333
1365
  return true
1334
1366
  end
1335
1367
  begin
1336
- MU::Cloud::AWS.ec2(region: @config['region'], credentials: @config['credentials']).describe_instances(
1368
+ MU::Cloud::AWS.ec2(region: @region, credentials: @credentials).describe_instances(
1337
1369
  instance_ids: [@cloud_id]
1338
1370
  ).reservations.each { |resp|
1339
1371
  if !resp.nil? and !resp.instances.nil?
@@ -1384,7 +1416,7 @@ module MU
1384
1416
  }
1385
1417
  end
1386
1418
  end
1387
- elastic_ip = findFreeElasticIp(classic: classic, ip: ip)
1419
+ elastic_ip = findFreeElasticIp(classic: classic, ip: ip, credentials: credentials)
1388
1420
  if !ip.nil? and (elastic_ip.nil? or ip != elastic_ip.public_ip)
1389
1421
  raise MuError, "Requested EIP #{ip}, but this IP does not exist or is not available"
1390
1422
  end
@@ -1454,11 +1486,11 @@ module MU
1454
1486
  # @param ignoremaster [Boolean]: If true, will remove resources not flagged as originating from this Mu server
1455
1487
  # @param region [String]: The cloud provider region
1456
1488
  # @return [void]
1457
- def self.cleanup(noop: false, ignoremaster: false, region: MU.curRegion, credentials: nil, flags: {})
1489
+ def self.cleanup(noop: false, deploy_id: MU.deploy_id, ignoremaster: false, region: MU.curRegion, credentials: nil, flags: {})
1458
1490
  onlycloud = flags["onlycloud"]
1459
1491
  skipsnapshots = flags["skipsnapshots"]
1460
1492
  tagfilters = [
1461
- {name: "tag:MU-ID", values: [MU.deploy_id]}
1493
+ {name: "tag:MU-ID", values: [deploy_id]}
1462
1494
  ]
1463
1495
  if !ignoremaster
1464
1496
  tagfilters << {name: "tag:MU-MASTER-IP", values: [MU.mu_public_ip]}
@@ -1492,7 +1524,7 @@ module MU
1492
1524
  threads << Thread.new(instance) { |myinstance|
1493
1525
  MU.dupGlobals(parent_thread_id)
1494
1526
  Thread.abort_on_exception = true
1495
- MU::Cloud::AWS::Server.terminateInstance(id: myinstance.instance_id, noop: noop, onlycloud: onlycloud, region: region, deploy_id: MU.deploy_id, credentials: credentials)
1527
+ MU::Cloud::AWS::Server.terminateInstance(id: myinstance.instance_id, noop: noop, onlycloud: onlycloud, region: region, deploy_id: deploy_id, credentials: credentials)
1496
1528
  }
1497
1529
  }
1498
1530
 
@@ -1503,7 +1535,7 @@ module MU
1503
1535
  threads << Thread.new(volume) { |myvolume|
1504
1536
  MU.dupGlobals(parent_thread_id)
1505
1537
  Thread.abort_on_exception = true
1506
- delete_volume(myvolume, noop, skipsnapshots, credentials: credentials)
1538
+ delete_volume(myvolume, noop, skipsnapshots, credentials: credentials, deploy_id: deploy_id)
1507
1539
  }
1508
1540
  }
1509
1541
 
@@ -1736,6 +1768,7 @@ module MU
1736
1768
  return size
1737
1769
  end
1738
1770
 
1771
+
1739
1772
  return size if types.has_key?(size)
1740
1773
 
1741
1774
  if size.nil? or !types.has_key?(size)
@@ -1781,7 +1814,8 @@ module MU
1781
1814
  def self.generateStandardRole(server, configurator)
1782
1815
  role = {
1783
1816
  "name" => server["name"],
1784
- "credentials" => server["credentials"],
1817
+ "bare_policies" => !server['generate_iam_role'],
1818
+ "strip_path" => server["role_strip_path"],
1785
1819
  "can_assume" => [
1786
1820
  {
1787
1821
  "entity_id" => "ec2.amazonaws.com",
@@ -1800,6 +1834,7 @@ module MU
1800
1834
  }
1801
1835
  ]
1802
1836
  }
1837
+ role["credentials"] = server["credentials"] if server["credentials"]
1803
1838
  if server['iam_policies']
1804
1839
  role['iam_policies'] = server['iam_policies'].dup
1805
1840
  end
@@ -1833,9 +1868,10 @@ module MU
1833
1868
  MU.log "Cannot mix iam_policies with generate_iam_role set to false", MU::ERR
1834
1869
  ok = false
1835
1870
  end
1836
- else
1837
- generateStandardRole(server, configurator)
1838
1871
  end
1872
+
1873
+ generateStandardRole(server, configurator)
1874
+
1839
1875
  if !server['create_image'].nil?
1840
1876
  if server['create_image'].has_key?('copy_to_regions') and
1841
1877
  (server['create_image']['copy_to_regions'].nil? or
@@ -1889,7 +1925,7 @@ module MU
1889
1925
  # @param volume [OpenStruct]: The cloud provider's description of the volume.
1890
1926
  # @param region [String]: The cloud provider region
1891
1927
  # @return [void]
1892
- def self.delete_volume(volume, noop, skipsnapshots, region: MU.curRegion, credentials: nil)
1928
+ def self.delete_volume(volume, noop, skipsnapshots, region: MU.curRegion, credentials: nil, deploy_id: MU.deploy_id)
1893
1929
  if !volume.nil?
1894
1930
  resp = MU::Cloud::AWS.ec2(region: region, credentials: credentials).describe_volumes(volume_ids: [volume.volume_id])
1895
1931
  volume = resp.data.volumes.first
@@ -1904,9 +1940,9 @@ module MU
1904
1940
  if !noop
1905
1941
  if !skipsnapshots
1906
1942
  if !name.nil? and !name.empty?
1907
- desc = "#{MU.deploy_id}-MUfinal (#{name})"
1943
+ desc = "#{deploy_id}-MUfinal (#{name})"
1908
1944
  else
1909
- desc = "#{MU.deploy_id}-MUfinal"
1945
+ desc = "#{deploy_id}-MUfinal"
1910
1946
  end
1911
1947
 
1912
1948
  begin
@@ -2084,7 +2120,7 @@ module MU
2084
2120
  def haveElasticIP?
2085
2121
  if !cloud_desc.public_ip_address.nil?
2086
2122
  begin
2087
- resp = MU::Cloud::AWS.ec2(region: @config['region'], credentials: @config['credentials']).describe_addresses(public_ips: [cloud_desc.public_ip_address])
2123
+ resp = MU::Cloud::AWS.ec2(region: @region, credentials: @credentials).describe_addresses(public_ips: [cloud_desc.public_ip_address])
2088
2124
  if resp.addresses.size > 0 and resp.addresses.first.instance_id == @cloud_id
2089
2125
  return true
2090
2126
  end
@@ -2099,9 +2135,9 @@ module MU
2099
2135
  def configureNetworking
2100
2136
  if !@config['static_ip'].nil?
2101
2137
  if !@config['static_ip']['ip'].nil?
2102
- MU::Cloud::AWS::Server.associateElasticIp(@cloud_id, classic: @vpc.nil?, ip: @config['static_ip']['ip'])
2138
+ MU::Cloud::AWS::Server.associateElasticIp(@cloud_id, classic: @vpc.nil?, ip: @config['static_ip']['ip'], credentials: @credentials)
2103
2139
  elsif !haveElasticIP?
2104
- MU::Cloud::AWS::Server.associateElasticIp(@cloud_id, classic: @vpc.nil?)
2140
+ MU::Cloud::AWS::Server.associateElasticIp(@cloud_id, classic: @vpc.nil?, credentials: @credentials)
2105
2141
  end
2106
2142
  end
2107
2143
 
@@ -2109,7 +2145,7 @@ module MU
2109
2145
  subnet = @vpc.getSubnet(cloud_id: cloud_desc.subnet_id)
2110
2146
 
2111
2147
  _nat_ssh_key, _nat_ssh_user, nat_ssh_host, _canonical_ip, _ssh_user, _ssh_key_name = getSSHConfig
2112
- if subnet.private? and !nat_ssh_host and !MU::Cloud.resourceClass("AWS", "VPC").haveRouteToInstance?(cloud_desc, region: @config['region'], credentials: @config['credentials'])
2148
+ if subnet.private? and !nat_ssh_host and !MU::Cloud.resourceClass("AWS", "VPC").haveRouteToInstance?(cloud_desc, region: @region, credentials: @credentials)
2113
2149
  raise MuError, "#{@mu_name} is in a private subnet (#{subnet}), but has no bastion host configured, and I have no other route to it"
2114
2150
  end
2115
2151
 
@@ -2126,17 +2162,17 @@ module MU
2126
2162
  next
2127
2163
  end
2128
2164
  MU.log "Adding network interface on subnet #{s.cloud_id} for #{@mu_name}"
2129
- iface = MU::Cloud::AWS.ec2(region: @config['region'], credentials: @config['credentials']).create_network_interface(subnet_id: s.cloud_id).network_interface
2165
+ iface = MU::Cloud::AWS.ec2(region: @region, credentials: @credentials).create_network_interface(subnet_id: s.cloud_id).network_interface
2130
2166
  MU::Cloud::AWS.createStandardTags(
2131
2167
  iface.network_interface_id,
2132
- region: @config['region'],
2133
- credentials: @config['credentials'],
2168
+ region: @region,
2169
+ credentials: @credentials,
2134
2170
  optional: @config['optional_tags'],
2135
2171
  nametag: @mu_name+"-ETH"+device_index.to_s,
2136
2172
  othertags: @config['tags']
2137
2173
  )
2138
2174
 
2139
- MU::Cloud::AWS.ec2(region: @config['region'], credentials: @config['credentials']).attach_network_interface(
2175
+ MU::Cloud::AWS.ec2(region: @region, credentials: @credentials).attach_network_interface(
2140
2176
  network_interface_id: iface.network_interface_id,
2141
2177
  instance_id: cloud_desc.instance_id,
2142
2178
  device_index: device_index
@@ -2155,7 +2191,7 @@ module MU
2155
2191
  cloud_desc.network_interfaces.each { |int|
2156
2192
  if int.private_ip_address == cloud_desc.private_ip_address and int.private_ip_addresses.size < (@config['add_private_ips'] + 1)
2157
2193
  MU.log "Adding #{@config['add_private_ips']} extra private IP addresses to #{cloud_desc.instance_id}"
2158
- MU::Cloud::AWS.ec2(region: @config['region'], credentials: @config['credentials']).assign_private_ip_addresses(
2194
+ MU::Cloud::AWS.ec2(region: @region, credentials: @credentials).assign_private_ip_addresses(
2159
2195
  network_interface_id: int.network_interface_id,
2160
2196
  secondary_private_ip_address_count: @config['add_private_ips'],
2161
2197
  allow_reassignment: false
@@ -2166,14 +2202,14 @@ module MU
2166
2202
  end
2167
2203
 
2168
2204
  def tagVolumes
2169
- volumes = MU::Cloud::AWS.ec2(region: @config['region'], credentials: @config['credentials']).describe_volumes(filters: [name: "attachment.instance-id", values: [@cloud_id]])
2205
+ volumes = MU::Cloud::AWS.ec2(region: @region, credentials: @credentials).describe_volumes(filters: [name: "attachment.instance-id", values: [@cloud_id]])
2170
2206
  volumes.each { |vol|
2171
2207
  vol.volumes.each { |volume|
2172
2208
  volume.attachments.each { |attachment|
2173
2209
  MU::Cloud::AWS.createStandardTags(
2174
2210
  attachment.volume_id,
2175
- region: @config['region'],
2176
- credentials: @config['credentials'],
2211
+ region: @region,
2212
+ credentials: @credentials,
2177
2213
  optional: @config['optional_tags'],
2178
2214
  nametag: ["/dev/sda", "/dev/sda1"].include?(attachment.device) ? "ROOT-"+@mu_name : @mu_name+"-"+attachment.device.upcase,
2179
2215
  othertags: @config['tags']
@@ -2194,15 +2230,17 @@ module MU
2194
2230
  alarm_obj = MU::MommaCat.findStray(
2195
2231
  "AWS",
2196
2232
  "alarms",
2197
- region: @config["region"],
2233
+ region: @region,
2198
2234
  deploy_id: @deploy.deploy_id,
2199
2235
  name: alarm['name']
2200
2236
  ).first
2201
2237
  alarm["dimensions"] = [{:name => "InstanceId", :value => @cloud_id}]
2202
2238
 
2203
2239
  if alarm["enable_notifications"]
2204
- topic_arn = MU::Cloud.resourceClass("AWS", "Notification").createTopic(alarm["notification_group"], region: @config["region"], credentials: @config['credentials'])
2205
- MU::Cloud.resourceClass("AWS", "Notification").subscribe(arn: topic_arn, protocol: alarm["notification_type"], endpoint: alarm["notification_endpoint"], region: @config["region"], credentials: @config["credentials"])
2240
+ # XXX vile, this should be a sibling resource generated by the
2241
+ # parser
2242
+ topic_arn = MU::Cloud.resourceClass("AWS", "Notification").createTopic(alarm["notification_group"], region: @region, credentials: @credentials)
2243
+ MU::Cloud.resourceClass("AWS", "Notification").subscribe(topic_arn, alarm["notification_endpoint"], alarm["notification_type"], region: @region, credentials: @credentials)
2206
2244
  alarm["alarm_actions"] = [topic_arn]
2207
2245
  alarm["ok_actions"] = [topic_arn]
2208
2246
  end
@@ -2223,36 +2261,88 @@ module MU
2223
2261
  evaluation_periods: alarm["evaluation_periods"],
2224
2262
  threshold: alarm["threshold"],
2225
2263
  comparison_operator: alarm["comparison_operator"],
2226
- region: @config["region"],
2227
- credentials: @config['credentials']
2264
+ region: @region,
2265
+ credentials: @credentials
2228
2266
  )
2229
2267
  }
2230
2268
  end
2231
2269
  end
2232
2270
 
2233
- # We have issues sometimes where our dns_records are pointing at the wrong node name and IP address.
2234
-
2235
2271
  def getIAMProfile
2236
- arn = if @config['generate_iam_role']
2237
- role = @deploy.findLitterMate(name: @config['name'], type: "roles")
2238
- s3_objs = ["#{@deploy.deploy_id}-secret", "#{role.mu_name}.pfx", "#{role.mu_name}.crt", "#{role.mu_name}.key", "#{role.mu_name}-winrm.crt", "#{role.mu_name}-winrm.key"].map { |file|
2239
- 'arn:'+(MU::Cloud::AWS.isGovCloud?(@config['region']) ? "aws-us-gov" : "aws")+':s3:::'+MU::Cloud::AWS.adminBucketName(@credentials)+'/'+file
2272
+ self.class.getIAMProfile(
2273
+ @config['name'],
2274
+ @deploy,
2275
+ generated: @config['generate_iam_role'],
2276
+ role_name: @config['iam_role'],
2277
+ region: @region,
2278
+ credentials: @credentials,
2279
+ want_arn: true
2280
+ )
2281
+ end
2282
+
2283
+ # XXX move to public section
2284
+ def self.getIAMProfile(myname, deploy, generated: true, role_name: nil, region: nil, credentials: nil, want_arn: false)
2285
+
2286
+ arn = if generated
2287
+ role = deploy.findLitterMate(name: myname, type: "roles", debug: true)
2288
+ if !role
2289
+ raise MuError, "Failed to find a role matching #{myname}"
2290
+ end
2291
+ s3_objs = ["#{deploy.deploy_id}-secret", "#{role.mu_name}.pfx", "#{role.mu_name}.crt", "#{role.mu_name}.key", "#{role.mu_name}-winrm.crt", "#{role.mu_name}-winrm.key"].map { |file|
2292
+ 'arn:'+(MU::Cloud::AWS.isGovCloud?(region) ? "aws-us-gov" : "aws")+':s3:::'+MU::Cloud::AWS.adminBucketName(credentials)+'/'+file
2240
2293
  }
2241
- MU.log "Adding S3 read permissions to #{@mu_name}'s IAM profile", MU::NOTICE, details: s3_objs
2294
+ MU.log "Adding S3 read permissions to #{myname}'s IAM profile", MU::NOTICE, details: s3_objs
2242
2295
  role.cloudobj.injectPolicyTargets("MuSecrets", s3_objs)
2243
2296
 
2244
- @config['iam_role'] = role.mu_name
2297
+ role_name = role.mu_name
2245
2298
  role.cloudobj.createInstanceProfile
2246
2299
 
2247
- elsif @config['iam_role'].nil?
2248
- raise MuError, "#{@mu_name} has generate_iam_role set to false, but no iam_role assigned."
2300
+ elsif role_name.nil?
2301
+ raise MuError, "#{myname} has generate_iam_role set to false, but no iam_role assigned."
2302
+ else
2303
+ begin
2304
+ ext_prof = MU::Cloud::AWS.iam(credentials: credentials).get_instance_profile(instance_profile_name: role_name)
2305
+ role_name = ext_prof.instance_profile.instance_profile_name
2306
+ ext_prof.instance_profile.arn
2307
+ rescue Aws::IAM::Errors::NoSuchEntity
2308
+ role = MU::MommaCat.findStray("AWS", "role", cloud_id: role_name, dummy_ok: true, credentials: credentials).first
2309
+ if !role
2310
+ raise MuError, "#{myname} specified iam_role '#{role_name}', but I can't find a role with that name to use when creating an instance profile"
2311
+ end
2312
+ role.cloudobj.createInstanceProfile
2313
+ end
2249
2314
  end
2250
2315
 
2251
- if !@config["iam_role"].nil?
2252
- if arn
2316
+ role_or_policy = deploy.findLitterMate(name: myname, type: "roles")
2317
+
2318
+ # Make sure our permissions to read our identity secrets are set
2319
+ s3_objs = [
2320
+ "#{deploy.deploy_id}-secret",
2321
+ "#{role_or_policy.mu_name}.pfx",
2322
+ "#{role_or_policy.mu_name}.crt",
2323
+ "#{role_or_policy.mu_name}.key",
2324
+ "#{role_or_policy.mu_name}-winrm.crt",
2325
+ "#{role_or_policy.mu_name}-winrm.key"].map { |file|
2326
+ 'arn:'+(MU::Cloud::AWS.isGovCloud?(region) ? "aws-us-gov" : "aws")+':s3:::'+MU::Cloud::AWS.adminBucketName(credentials)+'/'+file
2327
+ }
2328
+ if generated
2329
+ role_or_policy.injectPolicyTargets("MuSecrets", s3_objs)
2330
+ elsif role_name
2331
+ realrole = MU::MommaCat.findStray("AWS", "role", cloud_id: role_name, dummy_ok: true, credentials: credentials).first
2332
+ if !role_or_policy
2333
+ raise MuError, "I should have a bare policy littermate named #{name} but I can't find it"
2334
+ end
2335
+ if realrole
2336
+ role_or_policy.bindTo("role", realrole.cloud_id)
2337
+ realrole.injectPolicyTargets(role_or_policy.mu_name+"-MUSECRETS", s3_objs)
2338
+ end
2339
+ end
2340
+
2341
+ if !role_name.nil?
2342
+ if arn and want_arn
2253
2343
  return {arn: arn}
2254
2344
  else
2255
- return {name: @config["iam_role"]}
2345
+ return {name: role_name}
2256
2346
  end
2257
2347
  end
2258
2348
 
@@ -2269,8 +2359,8 @@ module MU
2269
2359
  if vol[:device_name] == device
2270
2360
  if vol[:ebs][:delete_on_termination] != delete_on_termination
2271
2361
  vol[:ebs][:delete_on_termination] = delete_on_termination
2272
- MU.log "Setting delete_on_termination flag to #{delete_on_termination.to_s} on #{@mu_name}'s #{dev}"
2273
- MU::Cloud::AWS.ec2(region: @config['region'], credentials: @config['credentials']).modify_instance_attribute(
2362
+ MU.log "Setting delete_on_termination flag to #{delete_on_termination.to_s} on #{@mu_name}'s #{device}"
2363
+ MU::Cloud::AWS.ec2(region: @region, credentials: @credentials).modify_instance_attribute(
2274
2364
  instance_id: @cloud_id,
2275
2365
  block_device_mappings: mappings
2276
2366
  )
@@ -2314,16 +2404,17 @@ module MU
2314
2404
  exclude_storage: img_cfg['image_exclude_storage'],
2315
2405
  copy_to_regions: img_cfg['copy_to_regions'],
2316
2406
  make_public: img_cfg['public'],
2317
- region: @config['region'],
2407
+ region: @region,
2318
2408
  tags: @config['tags'],
2319
- credentials: @config['credentials']
2409
+ credentials: @credentials
2320
2410
  )
2411
+
2321
2412
  @deploy.notify("images", @config['name'], ami_ids)
2322
2413
  @config['image_created'] = true
2323
2414
  if img_cfg['image_then_destroy']
2324
- MU::Cloud::AWS::Server.waitForAMI(ami_ids[@config['region']], region: @config['region'], credentials: @config['credentials'])
2325
- MU.log "AMI #{ami_ids[@config['region']]} ready, removing source node #{@mu_name}"
2326
- MU::Cloud::AWS::Server.terminateInstance(id: @cloud_id, region: @config['region'], deploy_id: @deploy.deploy_id, mu_name: @mu_name, credentials: @config['credentials'])
2415
+ MU::Cloud::AWS::Server.waitForAMI(ami_ids[@region], region: @region, credentials: @credentials)
2416
+ MU.log "AMI #{ami_ids[@region]} ready, removing source node #{@mu_name}"
2417
+ MU::Cloud::AWS::Server.terminateInstance(id: @cloud_id, region: @region, deploy_id: @deploy.deploy_id, mu_name: @mu_name, credentials: @credentials)
2327
2418
  destroy
2328
2419
  end
2329
2420
  end