cloud-mu 3.1.4 → 3.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (203) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +5 -1
  3. data/ansible/roles/mu-windows/README.md +33 -0
  4. data/ansible/roles/mu-windows/defaults/main.yml +2 -0
  5. data/ansible/roles/mu-windows/files/LaunchConfig.json +9 -0
  6. data/ansible/roles/mu-windows/files/config.xml +76 -0
  7. data/ansible/roles/mu-windows/handlers/main.yml +2 -0
  8. data/ansible/roles/mu-windows/meta/main.yml +53 -0
  9. data/ansible/roles/mu-windows/tasks/main.yml +36 -0
  10. data/ansible/roles/mu-windows/tests/inventory +2 -0
  11. data/ansible/roles/mu-windows/tests/test.yml +5 -0
  12. data/ansible/roles/mu-windows/vars/main.yml +2 -0
  13. data/bin/mu-adopt +16 -12
  14. data/bin/mu-azure-tests +57 -0
  15. data/bin/mu-cleanup +2 -4
  16. data/bin/mu-configure +52 -0
  17. data/bin/mu-deploy +3 -3
  18. data/bin/mu-findstray-tests +25 -0
  19. data/bin/mu-gen-docs +2 -4
  20. data/bin/mu-load-config.rb +2 -1
  21. data/bin/mu-node-manage +15 -16
  22. data/bin/mu-run-tests +37 -12
  23. data/cloud-mu.gemspec +5 -3
  24. data/cookbooks/mu-activedirectory/resources/domain.rb +4 -4
  25. data/cookbooks/mu-activedirectory/resources/domain_controller.rb +4 -4
  26. data/cookbooks/mu-tools/libraries/helper.rb +1 -1
  27. data/cookbooks/mu-tools/recipes/apply_security.rb +14 -14
  28. data/cookbooks/mu-tools/recipes/aws_api.rb +9 -0
  29. data/cookbooks/mu-tools/recipes/eks.rb +2 -2
  30. data/cookbooks/mu-tools/recipes/selinux.rb +2 -1
  31. data/cookbooks/mu-tools/recipes/windows-client.rb +163 -164
  32. data/cookbooks/mu-tools/resources/windows_users.rb +44 -43
  33. data/extras/clean-stock-amis +25 -19
  34. data/extras/generate-stock-images +1 -0
  35. data/extras/image-generators/AWS/win2k12.yaml +18 -13
  36. data/extras/image-generators/AWS/win2k16.yaml +18 -13
  37. data/extras/image-generators/AWS/win2k19.yaml +21 -0
  38. data/modules/mommacat.ru +1 -1
  39. data/modules/mu.rb +158 -107
  40. data/modules/mu/adoption.rb +386 -59
  41. data/modules/mu/cleanup.rb +214 -303
  42. data/modules/mu/cloud.rb +128 -1632
  43. data/modules/mu/cloud/database.rb +49 -0
  44. data/modules/mu/cloud/dnszone.rb +44 -0
  45. data/modules/mu/cloud/machine_images.rb +212 -0
  46. data/modules/mu/cloud/providers.rb +81 -0
  47. data/modules/mu/cloud/resource_base.rb +926 -0
  48. data/modules/mu/cloud/server.rb +40 -0
  49. data/modules/mu/cloud/server_pool.rb +1 -0
  50. data/modules/mu/cloud/ssh_sessions.rb +228 -0
  51. data/modules/mu/cloud/winrm_sessions.rb +237 -0
  52. data/modules/mu/cloud/wrappers.rb +169 -0
  53. data/modules/mu/config.rb +135 -82
  54. data/modules/mu/config/alarm.rb +2 -6
  55. data/modules/mu/config/bucket.rb +32 -3
  56. data/modules/mu/config/cache_cluster.rb +2 -2
  57. data/modules/mu/config/cdn.rb +100 -0
  58. data/modules/mu/config/collection.rb +1 -1
  59. data/modules/mu/config/container_cluster.rb +7 -2
  60. data/modules/mu/config/database.rb +84 -105
  61. data/modules/mu/config/database.yml +1 -2
  62. data/modules/mu/config/dnszone.rb +5 -4
  63. data/modules/mu/config/doc_helpers.rb +5 -6
  64. data/modules/mu/config/endpoint.rb +2 -1
  65. data/modules/mu/config/firewall_rule.rb +3 -19
  66. data/modules/mu/config/folder.rb +1 -1
  67. data/modules/mu/config/function.rb +17 -8
  68. data/modules/mu/config/group.rb +1 -1
  69. data/modules/mu/config/habitat.rb +1 -1
  70. data/modules/mu/config/job.rb +89 -0
  71. data/modules/mu/config/loadbalancer.rb +57 -11
  72. data/modules/mu/config/log.rb +1 -1
  73. data/modules/mu/config/msg_queue.rb +1 -1
  74. data/modules/mu/config/nosqldb.rb +1 -1
  75. data/modules/mu/config/notifier.rb +8 -19
  76. data/modules/mu/config/ref.rb +92 -14
  77. data/modules/mu/config/role.rb +1 -1
  78. data/modules/mu/config/schema_helpers.rb +38 -37
  79. data/modules/mu/config/search_domain.rb +1 -1
  80. data/modules/mu/config/server.rb +12 -13
  81. data/modules/mu/config/server.yml +1 -0
  82. data/modules/mu/config/server_pool.rb +3 -7
  83. data/modules/mu/config/storage_pool.rb +1 -1
  84. data/modules/mu/config/tail.rb +11 -0
  85. data/modules/mu/config/user.rb +1 -1
  86. data/modules/mu/config/vpc.rb +27 -23
  87. data/modules/mu/config/vpc.yml +0 -1
  88. data/modules/mu/defaults/AWS.yaml +91 -68
  89. data/modules/mu/defaults/Azure.yaml +1 -0
  90. data/modules/mu/defaults/Google.yaml +1 -0
  91. data/modules/mu/deploy.rb +33 -19
  92. data/modules/mu/groomer.rb +16 -1
  93. data/modules/mu/groomers/ansible.rb +123 -21
  94. data/modules/mu/groomers/chef.rb +64 -11
  95. data/modules/mu/logger.rb +120 -144
  96. data/modules/mu/master.rb +97 -4
  97. data/modules/mu/master/ssl.rb +0 -1
  98. data/modules/mu/mommacat.rb +154 -867
  99. data/modules/mu/mommacat/daemon.rb +23 -14
  100. data/modules/mu/mommacat/naming.rb +110 -3
  101. data/modules/mu/mommacat/search.rb +495 -0
  102. data/modules/mu/mommacat/storage.rb +225 -192
  103. data/modules/mu/{clouds → providers}/README.md +1 -1
  104. data/modules/mu/{clouds → providers}/aws.rb +281 -64
  105. data/modules/mu/{clouds → providers}/aws/alarm.rb +3 -3
  106. data/modules/mu/{clouds → providers}/aws/bucket.rb +275 -41
  107. data/modules/mu/{clouds → providers}/aws/cache_cluster.rb +14 -50
  108. data/modules/mu/providers/aws/cdn.rb +782 -0
  109. data/modules/mu/{clouds → providers}/aws/collection.rb +5 -5
  110. data/modules/mu/{clouds → providers}/aws/container_cluster.rb +708 -749
  111. data/modules/mu/providers/aws/database.rb +1744 -0
  112. data/modules/mu/{clouds → providers}/aws/dnszone.rb +75 -57
  113. data/modules/mu/providers/aws/endpoint.rb +1072 -0
  114. data/modules/mu/{clouds → providers}/aws/firewall_rule.rb +212 -242
  115. data/modules/mu/{clouds → providers}/aws/folder.rb +1 -1
  116. data/modules/mu/{clouds → providers}/aws/function.rb +289 -134
  117. data/modules/mu/{clouds → providers}/aws/group.rb +18 -20
  118. data/modules/mu/{clouds → providers}/aws/habitat.rb +3 -3
  119. data/modules/mu/providers/aws/job.rb +466 -0
  120. data/modules/mu/{clouds → providers}/aws/loadbalancer.rb +50 -41
  121. data/modules/mu/{clouds → providers}/aws/log.rb +5 -5
  122. data/modules/mu/{clouds → providers}/aws/msg_queue.rb +14 -11
  123. data/modules/mu/{clouds → providers}/aws/nosqldb.rb +96 -5
  124. data/modules/mu/{clouds → providers}/aws/notifier.rb +135 -63
  125. data/modules/mu/{clouds → providers}/aws/role.rb +94 -57
  126. data/modules/mu/{clouds → providers}/aws/search_domain.rb +173 -42
  127. data/modules/mu/{clouds → providers}/aws/server.rb +782 -1107
  128. data/modules/mu/{clouds → providers}/aws/server_pool.rb +36 -46
  129. data/modules/mu/{clouds → providers}/aws/storage_pool.rb +21 -38
  130. data/modules/mu/{clouds → providers}/aws/user.rb +12 -16
  131. data/modules/mu/{clouds → providers}/aws/userdata/README.md +0 -0
  132. data/modules/mu/{clouds → providers}/aws/userdata/linux.erb +5 -4
  133. data/modules/mu/{clouds → providers}/aws/userdata/windows.erb +2 -1
  134. data/modules/mu/{clouds → providers}/aws/vpc.rb +429 -849
  135. data/modules/mu/providers/aws/vpc_subnet.rb +286 -0
  136. data/modules/mu/{clouds → providers}/azure.rb +13 -0
  137. data/modules/mu/{clouds → providers}/azure/container_cluster.rb +1 -5
  138. data/modules/mu/{clouds → providers}/azure/firewall_rule.rb +8 -1
  139. data/modules/mu/{clouds → providers}/azure/habitat.rb +0 -0
  140. data/modules/mu/{clouds → providers}/azure/loadbalancer.rb +0 -0
  141. data/modules/mu/{clouds → providers}/azure/role.rb +0 -0
  142. data/modules/mu/{clouds → providers}/azure/server.rb +32 -24
  143. data/modules/mu/{clouds → providers}/azure/user.rb +1 -1
  144. data/modules/mu/{clouds → providers}/azure/userdata/README.md +0 -0
  145. data/modules/mu/{clouds → providers}/azure/userdata/linux.erb +0 -0
  146. data/modules/mu/{clouds → providers}/azure/userdata/windows.erb +0 -0
  147. data/modules/mu/{clouds → providers}/azure/vpc.rb +4 -6
  148. data/modules/mu/{clouds → providers}/cloudformation.rb +10 -0
  149. data/modules/mu/{clouds → providers}/cloudformation/alarm.rb +3 -3
  150. data/modules/mu/{clouds → providers}/cloudformation/cache_cluster.rb +3 -3
  151. data/modules/mu/{clouds → providers}/cloudformation/collection.rb +3 -3
  152. data/modules/mu/{clouds → providers}/cloudformation/database.rb +6 -17
  153. data/modules/mu/{clouds → providers}/cloudformation/dnszone.rb +3 -3
  154. data/modules/mu/{clouds → providers}/cloudformation/firewall_rule.rb +3 -3
  155. data/modules/mu/{clouds → providers}/cloudformation/loadbalancer.rb +3 -3
  156. data/modules/mu/{clouds → providers}/cloudformation/log.rb +3 -3
  157. data/modules/mu/{clouds → providers}/cloudformation/server.rb +7 -7
  158. data/modules/mu/{clouds → providers}/cloudformation/server_pool.rb +5 -5
  159. data/modules/mu/{clouds → providers}/cloudformation/vpc.rb +3 -3
  160. data/modules/mu/{clouds → providers}/docker.rb +0 -0
  161. data/modules/mu/{clouds → providers}/google.rb +29 -6
  162. data/modules/mu/{clouds → providers}/google/bucket.rb +5 -5
  163. data/modules/mu/{clouds → providers}/google/container_cluster.rb +59 -37
  164. data/modules/mu/{clouds → providers}/google/database.rb +5 -12
  165. data/modules/mu/{clouds → providers}/google/firewall_rule.rb +5 -5
  166. data/modules/mu/{clouds → providers}/google/folder.rb +5 -9
  167. data/modules/mu/{clouds → providers}/google/function.rb +14 -8
  168. data/modules/mu/{clouds → providers}/google/group.rb +9 -17
  169. data/modules/mu/{clouds → providers}/google/habitat.rb +4 -8
  170. data/modules/mu/{clouds → providers}/google/loadbalancer.rb +5 -5
  171. data/modules/mu/{clouds → providers}/google/role.rb +50 -31
  172. data/modules/mu/{clouds → providers}/google/server.rb +142 -55
  173. data/modules/mu/{clouds → providers}/google/server_pool.rb +14 -14
  174. data/modules/mu/{clouds → providers}/google/user.rb +34 -24
  175. data/modules/mu/{clouds → providers}/google/userdata/README.md +0 -0
  176. data/modules/mu/{clouds → providers}/google/userdata/linux.erb +0 -0
  177. data/modules/mu/{clouds → providers}/google/userdata/windows.erb +0 -0
  178. data/modules/mu/{clouds → providers}/google/vpc.rb +46 -15
  179. data/modules/tests/aws-jobs-functions.yaml +46 -0
  180. data/modules/tests/centos6.yaml +15 -0
  181. data/modules/tests/centos7.yaml +15 -0
  182. data/modules/tests/centos8.yaml +12 -0
  183. data/modules/tests/ecs.yaml +23 -0
  184. data/modules/tests/eks.yaml +1 -1
  185. data/modules/tests/functions/node-function/lambda_function.js +10 -0
  186. data/modules/tests/functions/python-function/lambda_function.py +12 -0
  187. data/modules/tests/includes-and-params.yaml +2 -1
  188. data/modules/tests/microservice_app.yaml +288 -0
  189. data/modules/tests/rds.yaml +108 -0
  190. data/modules/tests/regrooms/rds.yaml +123 -0
  191. data/modules/tests/server-with-scrub-muisms.yaml +2 -1
  192. data/modules/tests/super_complex_bok.yml +2 -2
  193. data/modules/tests/super_simple_bok.yml +3 -5
  194. data/modules/tests/win2k12.yaml +25 -0
  195. data/modules/tests/win2k16.yaml +25 -0
  196. data/modules/tests/win2k19.yaml +25 -0
  197. data/requirements.txt +1 -0
  198. data/spec/mu/clouds/azure_spec.rb +2 -2
  199. metadata +169 -93
  200. data/extras/image-generators/AWS/windows.yaml +0 -18
  201. data/modules/mu/clouds/aws/database.rb +0 -1974
  202. data/modules/mu/clouds/aws/endpoint.rb +0 -596
  203. data/modules/tests/needwork/win2k12.yaml +0 -13
@@ -89,7 +89,7 @@ module MU
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,
@@ -145,7 +145,7 @@ module MU
145
145
  raise MuError, "My second argument should be a hash of variables to pass into ERB templates"
146
146
  end
147
147
  $mu = OpenStruct.new(template_variables)
148
- userdata_dir = File.expand_path(MU.myRoot+"/modules/mu/clouds/aws/userdata")
148
+ userdata_dir = File.expand_path(MU.myRoot+"/modules/mu/providers/aws/userdata")
149
149
  platform = "linux" if %w{centos centos6 centos7 ubuntu ubuntu14 rhel rhel7 rhel71 amazon}.include? platform
150
150
  platform = "windows" if %w{win2k12r2 win2k12 win2k8 win2k8r2 win2k16}.include? platform
151
151
  erbfile = "#{userdata_dir}/#{platform}.erb"
@@ -240,8 +240,14 @@ module MU
240
240
  end
241
241
  MU::MommaCat.unlock(instance.instance_id+"-create")
242
242
  else
243
- MU::Cloud::AWS.createStandardTags(instance.instance_id, region: @config['region'], credentials: @config['credentials'])
244
- MU::Cloud::AWS.createTag(instance.instance_id, "Name", @mu_name, region: @config['region'], credentials: @config['credentials'])
243
+ MU::Cloud::AWS.createStandardTags(
244
+ instance.instance_id,
245
+ region: @config['region'],
246
+ credentials: @config['credentials'],
247
+ optional: @config['optional_tags'],
248
+ nametag: @mu_name,
249
+ othertags: @config['tags']
250
+ )
245
251
  end
246
252
  done = true
247
253
  rescue StandardError => e
@@ -262,14 +268,11 @@ module MU
262
268
  return @config
263
269
  end
264
270
 
265
-
266
-
267
271
  # Create an Amazon EC2 instance.
268
272
  def createEc2Instance
269
- node = @config['mu_name']
270
273
 
271
274
  instance_descriptor = {
272
- :image_id => @config["ami_id"],
275
+ :image_id => @config["image_id"],
273
276
  :key_name => @deploy.ssh_key_name,
274
277
  :instance_type => @config["size"],
275
278
  :disable_api_termination => true,
@@ -277,63 +280,26 @@ module MU
277
280
  :max_count => 1
278
281
  }
279
282
 
280
- arn = nil
281
- if @config['generate_iam_role']
282
- role = @deploy.findLitterMate(name: @config['name'], type: "roles")
283
- 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|
284
- 'arn:'+(MU::Cloud::AWS.isGovCloud?(@config['region']) ? "aws-us-gov" : "aws")+':s3:::'+MU::Cloud::AWS.adminBucketName(@credentials)+'/'+file
285
- }
286
- MU.log "Adding S3 read permissions to #{@mu_name}'s IAM profile", MU::NOTICE, details: s3_objs
287
- role.cloudobj.injectPolicyTargets("MuSecrets", s3_objs)
288
-
289
- @config['iam_role'] = role.mu_name
290
- arn = role.cloudobj.createInstanceProfile
291
- # @cfm_role_name, @cfm_prof_name
292
-
293
- elsif @config['iam_role'].nil?
294
- raise MuError, "#{@mu_name} has generate_iam_role set to false, but no iam_role assigned."
295
- end
296
- if !@config["iam_role"].nil?
297
- if arn
298
- instance_descriptor[:iam_instance_profile] = {arn: arn}
299
- else
300
- instance_descriptor[:iam_instance_profile] = {name: @config["iam_role"]}
301
- end
302
- end
303
-
304
- security_groups = []
305
- if @dependencies.has_key?("firewall_rule")
306
- @dependencies['firewall_rule'].values.each { |sg|
307
- security_groups << sg.cloud_id
308
- }
309
- end
283
+ instance_descriptor[:iam_instance_profile] = getIAMProfile
310
284
 
285
+ security_groups = myFirewallRules.map { |fw| fw.cloud_id }
311
286
  if security_groups.size > 0
312
287
  instance_descriptor[:security_group_ids] = security_groups
313
288
  else
314
289
  raise MuError, "Didn't get any security groups assigned to be in #{@mu_name}, that shouldn't happen"
315
290
  end
316
291
 
317
- if !@config['private_ip'].nil?
292
+ if @config['private_ip']
318
293
  instance_descriptor[:private_ip_address] = @config['private_ip']
319
294
  end
320
295
 
321
296
  if !@vpc.nil? and @config.has_key?("vpc")
322
- subnet_conf = @config['vpc']
323
- subnet_conf = @config['vpc']['subnets'].first if @config['vpc'].has_key?("subnets") and !@config['vpc']['subnets'].empty?
324
- tag_key, tag_value = subnet_conf['tag'].split(/=/, 2) if !subnet_conf['tag'].nil?
325
-
326
- subnet = @vpc.getSubnet(
327
- cloud_id: subnet_conf['subnet_id'],
328
- name: subnet_conf['subnet_name'],
329
- tag_key: tag_key,
330
- tag_value: tag_value
331
- )
297
+ subnet = mySubnets.sample
332
298
  if subnet.nil?
333
- raise MuError, "Got null subnet id out of #{subnet_conf['vpc']}"
299
+ raise MuError, "Got null subnet id out of #{@config['vpc']}"
334
300
  end
335
- MU.log "Deploying #{node} into VPC #{@vpc.cloud_id} Subnet #{subnet.cloud_id}"
336
- punchAdminNAT
301
+ MU.log "Deploying #{@mu_name} into VPC #{@vpc.cloud_id} Subnet #{subnet.cloud_id}"
302
+ allowBastionAccess
337
303
  instance_descriptor[:subnet_id] = subnet.cloud_id
338
304
  end
339
305
 
@@ -341,37 +307,10 @@ module MU
341
307
  instance_descriptor[:user_data] = Base64.encode64(@userdata)
342
308
  end
343
309
 
344
- MU::Cloud::AWS::Server.waitForAMI(@config["ami_id"], region: @config['region'], credentials: @config['credentials'])
310
+ MU::Cloud::AWS::Server.waitForAMI(@config["image_id"], region: @config['region'], credentials: @config['credentials'])
345
311
 
346
- # Figure out which devices are embedded in the AMI already.
347
- image = MU::Cloud::AWS.ec2(region: @config['region'], credentials: @config['credentials']).describe_images(image_ids: [@config["ami_id"]]).images.first
348
- ext_disks = {}
349
- if !image.block_device_mappings.nil?
350
- image.block_device_mappings.each { |disk|
351
- if !disk.device_name.nil? and !disk.device_name.empty? and !disk.ebs.nil? and !disk.ebs.empty?
352
- ext_disks[disk.device_name] = MU.structToHash(disk.ebs)
353
- end
354
- }
355
- end
356
-
357
- configured_storage = Array.new
358
- if @config["storage"]
359
- @config["storage"].each { |vol|
360
- # Drop the "encrypted" flag if a snapshot for this device exists
361
- # in the AMI, even if they both agree about the value of said
362
- # flag. Apparently that's a thing now.
363
- if ext_disks.has_key?(vol["device"])
364
- if ext_disks[vol["device"]].has_key?(:snapshot_id)
365
- vol.delete("encrypted")
366
- end
367
- end
368
- mapping, _cfm_mapping = MU::Cloud::AWS::Server.convertBlockDeviceMapping(vol)
369
- configured_storage << mapping
370
- }
371
- end
312
+ instance_descriptor[:block_device_mappings] = MU::Cloud::AWS::Server.configureBlockDevices(image_id: @config["image_id"], storage: @config['storage'], region: @config['region'], credentials: @credentials)
372
313
 
373
- instance_descriptor[:block_device_mappings] = configured_storage
374
- instance_descriptor[:block_device_mappings].concat(@ephemeral_mappings)
375
314
  instance_descriptor[:monitoring] = {enabled: @config['monitoring']}
376
315
 
377
316
  if @tags and @tags.size > 0
@@ -383,37 +322,24 @@ module MU
383
322
  }]
384
323
  end
385
324
 
386
- MU.log "Creating EC2 instance #{node}"
387
- MU.log "Instance details for #{node}: #{instance_descriptor}", MU::DEBUG
388
- # if instance_descriptor[:block_device_mappings].empty?
389
- # instance_descriptor.delete(:block_device_mappings)
390
- # end
325
+ MU.log "Creating EC2 instance #{@mu_name}", details: instance_descriptor
391
326
 
392
- retries = 0
393
- instance = begin
394
- response = MU::Cloud::AWS.ec2(region: @config['region'], credentials: @config['credentials']).run_instances(instance_descriptor)
395
- if response and response.instances and response.instances.size > 0
396
- response.instances.first
397
- else
398
- MU.log "Got a confusing response from run_instances", MU::ERR, details: response
399
- end
327
+ instance = resp = nil
328
+ loop_if = Proc.new {
329
+ instance = resp.instances.first if resp and resp.instances
330
+ resp.nil? or resp.instances.nil? or instance.nil?
331
+ }
332
+
333
+ begin
334
+ 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)
336
+ }
400
337
  rescue Aws::EC2::Errors::InvalidRequest => e
401
338
  MU.log e.message, MU::ERR, details: instance_descriptor
402
339
  raise e
403
- rescue Aws::EC2::Errors::InvalidGroupNotFound, Aws::EC2::Errors::InvalidSubnetIDNotFound, Aws::EC2::Errors::InvalidParameterValue => e
404
- if retries < 10
405
- if retries > 7
406
- MU.log "Seeing #{e.inspect} while trying to launch #{node}, retrying a few more times...", MU::WARN, details: instance_descriptor
407
- end
408
- sleep 10
409
- retries = retries + 1
410
- retry
411
- else
412
- raise MuError, e.inspect
413
- end
414
340
  end
415
341
 
416
- MU.log "#{node} (#{instance.instance_id}) coming online"
342
+ MU.log "#{@mu_name} (#{instance.instance_id}) coming online"
417
343
 
418
344
  instance
419
345
  end
@@ -473,13 +399,13 @@ module MU
473
399
  # Figure out what's needed to SSH into this server.
474
400
  # @return [Array<String>]: nat_ssh_key, nat_ssh_user, nat_ssh_host, canonical_ip, ssh_user, ssh_key_name, alternate_names
475
401
  def getSSHConfig
476
- describe(cloud_id: @cloud_id)
402
+ cloud_desc(use_cache: false) # make sure we're current
477
403
  # XXX add some awesome alternate names from metadata and make sure they end
478
404
  # up in MU::MommaCat's ssh config wangling
479
405
  return nil if @config.nil? or @deploy.nil?
480
406
 
481
407
  nat_ssh_key = nat_ssh_user = nat_ssh_host = nil
482
- if !@config["vpc"].nil? and !MU::Cloud::AWS::VPC.haveRouteToInstance?(cloud_desc, region: @config['region'], credentials: @config['credentials'])
408
+ if !@config["vpc"].nil? and !MU::Cloud.resourceClass("AWS", "VPC").haveRouteToInstance?(cloud_desc, region: @config['region'], credentials: @config['credentials'])
483
409
  if !@nat.nil?
484
410
  if @nat.is_a?(Struct) && @nat.nat_gateway_id && @nat.nat_gateway_id.start_with?("nat-")
485
411
  raise MuError, "Configured to use NAT Gateway, but I have no route to instance. Either use Bastion, or configure VPC peering"
@@ -517,445 +443,81 @@ module MU
517
443
  # Apply tags, bootstrap our configuration management, and other
518
444
  # administravia for a new instance.
519
445
  def postBoot(instance_id = nil)
520
- if !instance_id.nil?
521
- @cloud_id = instance_id
522
- end
523
- node, _config, deploydata = describe(cloud_id: @cloud_id)
524
- instance = cloud_desc
525
- raise MuError, "Couldn't find instance #{@mu_name} (#{@cloud_id})" if !instance
526
- @cloud_id = instance.instance_id
527
- return false if !MU::MommaCat.lock(instance.instance_id+"-orchestrate", true)
528
- return false if !MU::MommaCat.lock(instance.instance_id+"-groom", true)
529
-
530
- MU::Cloud::AWS.createStandardTags(instance.instance_id, region: @config['region'], credentials: @config['credentials'])
531
- MU::Cloud::AWS.createTag(instance.instance_id, "Name", node, region: @config['region'], credentials: @config['credentials'])
532
-
533
- if @config['optional_tags']
534
- MU::MommaCat.listOptionalTags.each { |key, value|
535
- MU::Cloud::AWS.createTag(instance.instance_id, key, value, region: @config['region'], credentials: @config['credentials'])
536
- }
537
- end
446
+ @cloud_id ||= instance_id
447
+ _node, _config, deploydata = describe(cloud_id: @cloud_id)
448
+
449
+ raise MuError, "Couldn't find instance #{@mu_name} (#{@cloud_id})" if !cloud_desc
450
+ return false if !MU::MommaCat.lock(@cloud_id+"-orchestrate", true)
451
+ return false if !MU::MommaCat.lock(@cloud_id+"-groom", true)
452
+ finish = Proc.new { |status|
453
+ MU::MommaCat.unlock(@cloud_id+"-orchestrate")
454
+ MU::MommaCat.unlock(@cloud_id+"-groom")
455
+ return status
456
+ }
538
457
 
539
- if !@config['tags'].nil?
540
- @config['tags'].each { |tag|
541
- MU::Cloud::AWS.createTag(instance.instance_id, tag['key'], tag['value'], region: @config['region'], credentials: @config['credentials'])
542
- }
543
- end
544
- MU.log "Tagged #{node} (#{instance.instance_id}) with MU-ID=#{MU.deploy_id}", MU::DEBUG
458
+ MU::Cloud::AWS.createStandardTags(
459
+ @cloud_id,
460
+ region: @config['region'],
461
+ credentials: @config['credentials'],
462
+ optional: @config['optional_tags'],
463
+ nametag: @mu_name,
464
+ othertags: @config['tags']
465
+ )
545
466
 
546
467
  # Make double sure we don't lose a cached mu_windows_name value.
547
- if windows? or !@config['active_directory'].nil?
548
- if @mu_windows_name.nil?
549
- @mu_windows_name = deploydata['mu_windows_name']
550
- end
468
+ if (windows? or !@config['active_directory'].nil?)
469
+ @mu_windows_name ||= deploydata['mu_windows_name']
551
470
  end
552
471
 
553
- retries = -1
554
- max_retries = 30
555
- begin
556
- if instance.nil? or instance.state.name != "running"
557
- retries = retries + 1
558
- if !instance.nil? and instance.state.name == "terminated"
559
- raise MuError, "#{@cloud_id} appears to have been terminated mid-bootstrap!"
560
- end
561
- if retries % 3 == 0
562
- MU.log "Waiting for EC2 instance #{node} (#{@cloud_id}) to be ready...", MU::NOTICE
563
- end
564
- sleep 40
565
- # Get a fresh AWS descriptor
566
- instance = MU::Cloud::Server.find(cloud_id: @cloud_id, region: @config['region'], credentials: @config['credentials']).values.first
567
- if instance and instance.state.name == "terminated"
568
- raise MuError, "EC2 instance #{node} (#{@cloud_id}) terminating during bootstrap!"
569
- end
472
+ loop_if = Proc.new {
473
+ !cloud_desc(use_cache: false) or cloud_desc.state.name != "running"
474
+ }
475
+ MU.retrier([Aws::EC2::Errors::ServiceError], max: 30, wait: 40, loop_if: loop_if) { |retries, _wait|
476
+ if cloud_desc and cloud_desc.state.name == "terminated"
477
+ raise MuError, "#{@cloud_id} appears to have been terminated mid-bootstrap!"
570
478
  end
571
- rescue Aws::EC2::Errors::ServiceError => e
572
- if retries < max_retries
573
- MU.log "Got #{e.inspect} during initial instance creation of #{@cloud_id}, retrying...", MU::NOTICE, details: instance
574
- retries = retries + 1
575
- retry
576
- else
577
- raise MuError, "Too many retries creating #{node} (#{e.inspect})"
479
+ if retries % 3 == 0
480
+ MU.log "Waiting for EC2 instance #{@mu_name} (#{@cloud_id}) to be ready...", MU::NOTICE
578
481
  end
579
- end while instance.nil? or (instance.state.name != "running" and retries < max_retries)
580
-
581
- punchAdminNAT
582
-
583
-
584
- # If we came up via AutoScale, the Alarm module won't have had our
585
- # instance ID to associate us with itself. So invoke that here.
586
- # XXX might be possible to do this with regular alarm resources and
587
- # dependencies now
588
- if !@config['basis'].nil? and @config["alarms"] and !@config["alarms"].empty?
589
- @config["alarms"].each { |alarm|
590
- alarm_obj = MU::MommaCat.findStray(
591
- "AWS",
592
- "alarms",
593
- region: @config["region"],
594
- deploy_id: @deploy.deploy_id,
595
- name: alarm['name']
596
- ).first
597
- alarm["dimensions"] = [{:name => "InstanceId", :value => @cloud_id}]
598
-
599
- if alarm["enable_notifications"]
600
- topic_arn = MU::Cloud::AWS::Notification.createTopic(alarm["notification_group"], region: @config["region"], credentials: @config['credentials'])
601
- MU::Cloud::AWS::Notification.subscribe(arn: topic_arn, protocol: alarm["notification_type"], endpoint: alarm["notification_endpoint"], region: @config["region"], credentials: @config["credentials"])
602
- alarm["alarm_actions"] = [topic_arn]
603
- alarm["ok_actions"] = [topic_arn]
604
- end
482
+ }
605
483
 
606
- alarm_name = alarm_obj ? alarm_obj.cloud_id : "#{node}-#{alarm['name']}".upcase
607
-
608
- MU::Cloud::AWS::Alarm.setAlarm(
609
- name: alarm_name,
610
- ok_actions: alarm["ok_actions"],
611
- alarm_actions: alarm["alarm_actions"],
612
- insufficient_data_actions: alarm["no_data_actions"],
613
- metric_name: alarm["metric_name"],
614
- namespace: alarm["namespace"],
615
- statistic: alarm["statistic"],
616
- dimensions: alarm["dimensions"],
617
- period: alarm["period"],
618
- unit: alarm["unit"],
619
- evaluation_periods: alarm["evaluation_periods"],
620
- threshold: alarm["threshold"],
621
- comparison_operator: alarm["comparison_operator"],
622
- region: @config["region"],
623
- credentials: @config['credentials']
624
- )
625
- }
626
- end
484
+ allowBastionAccess
627
485
 
628
- # We have issues sometimes where our dns_records are pointing at the wrong node name and IP address.
629
- # Make sure that doesn't happen. Happens with server pools only
630
- if @config['dns_records'] && !@config['dns_records'].empty?
631
- @config['dns_records'].each { |dnsrec|
632
- if dnsrec.has_key?("name")
633
- if dnsrec['name'].start_with?(MU.deploy_id.downcase) && !dnsrec['name'].start_with?(node.downcase)
634
- MU.log "DNS records for #{node} seem to be wrong, deleting from current config", MU::WARN, details: dnsrec
635
- dnsrec.delete('name')
636
- dnsrec.delete('target')
637
- end
638
- end
639
- }
640
- end
486
+ setAlarms
641
487
 
642
488
  # Unless we're planning on associating a different IP later, set up a
643
489
  # DNS entry for this thing and let it sync in the background. We'll come
644
490
  # back to it later.
645
- if @config['static_ip'].nil? && !@named
491
+ if @config['static_ip'].nil? and !@named
646
492
  MU::MommaCat.nameKitten(self)
647
493
  @named = true
648
494
  end
649
495
 
650
496
  if !@config['src_dst_check'] and !@config["vpc"].nil?
651
- MU.log "Disabling source_dest_check #{node} (making it NAT-worthy)"
497
+ MU.log "Disabling source_dest_check #{@mu_name} (making it NAT-worthy)"
652
498
  MU::Cloud::AWS.ec2(region: @config['region'], credentials: @config['credentials']).modify_instance_attribute(
653
- instance_id: @cloud_id,
654
- source_dest_check: {:value => false}
499
+ instance_id: @cloud_id,
500
+ source_dest_check: { value: false }
655
501
  )
656
502
  end
657
503
 
658
504
  # Set console termination protection. Autoscale nodes won't set this
659
505
  # by default.
660
506
  MU::Cloud::AWS.ec2(region: @config['region'], credentials: @config['credentials']).modify_instance_attribute(
661
- instance_id: @cloud_id,
662
- disable_api_termination: {:value => true}
507
+ instance_id: @cloud_id,
508
+ disable_api_termination: { value: true}
663
509
  )
664
510
 
665
- has_elastic_ip = false
666
- if !instance.public_ip_address.nil?
667
- begin
668
- resp = MU::Cloud::AWS.ec2(region: @config['region'], credentials: @config['credentials']).describe_addresses(public_ips: [instance.public_ip_address])
669
- if resp.addresses.size > 0 and resp.addresses.first.instance_id == @cloud_id
670
- has_elastic_ip = true
671
- end
672
- rescue Aws::EC2::Errors::InvalidAddressNotFound
673
- # XXX this is ok to ignore, it means the public IP isn't Elastic
674
- end
675
- end
676
-
677
- win_admin_password = nil
678
- ec2config_password = nil
679
- sshd_password = nil
680
- if windows?
681
- if @config['use_cloud_provider_windows_password']
682
- win_admin_password = getWindowsAdminPassword
683
- elsif @config['windows_auth_vault'] && !@config['windows_auth_vault'].empty?
684
- if @config["windows_auth_vault"].has_key?("password_field")
685
- win_admin_password = @groomer.getSecret(
686
- vault: @config['windows_auth_vault']['vault'],
687
- item: @config['windows_auth_vault']['item'],
688
- field: @config["windows_auth_vault"]["password_field"]
689
- )
690
- else
691
- win_admin_password = getWindowsAdminPassword
692
- end
693
-
694
- if @config["windows_auth_vault"].has_key?("ec2config_password_field")
695
- ec2config_password = @groomer.getSecret(
696
- vault: @config['windows_auth_vault']['vault'],
697
- item: @config['windows_auth_vault']['item'],
698
- field: @config["windows_auth_vault"]["ec2config_password_field"]
699
- )
700
- end
701
-
702
- if @config["windows_auth_vault"].has_key?("sshd_password_field")
703
- sshd_password = @groomer.getSecret(
704
- vault: @config['windows_auth_vault']['vault'],
705
- item: @config['windows_auth_vault']['item'],
706
- field: @config["windows_auth_vault"]["sshd_password_field"]
707
- )
708
- end
709
- end
710
-
711
- win_admin_password = MU.generateWindowsPassword if win_admin_password.nil?
712
- ec2config_password = MU.generateWindowsPassword if ec2config_password.nil?
713
- sshd_password = MU.generateWindowsPassword if sshd_password.nil?
714
-
715
- # We're creating the vault here so when we run
716
- # MU::Cloud::Server.initialSSHTasks and we need to set the Windows
717
- # Admin password we can grab it from said vault.
718
- creds = {
719
- "username" => @config['windows_admin_username'],
720
- "password" => win_admin_password,
721
- "ec2config_username" => "ec2config",
722
- "ec2config_password" => ec2config_password,
723
- "sshd_username" => "sshd_service",
724
- "sshd_password" => sshd_password
725
- }
726
- @groomer.saveSecret(vault: @mu_name, item: "windows_credentials", data: creds, permissions: "name:#{@mu_name}")
727
- end
728
-
729
- subnet = nil
730
- if !@vpc.nil? and @config.has_key?("vpc") and !instance.subnet_id.nil?
731
- subnet = @vpc.getSubnet(
732
- cloud_id: instance.subnet_id
733
- )
734
- if subnet.nil?
735
- raise MuError, "Got null subnet id out of #{@config['vpc']} when asking for #{instance.subnet_id}"
736
- end
737
- end
738
-
739
- if !subnet.nil?
740
- if !subnet.private? or (!@config['static_ip'].nil? and !@config['static_ip']['assign_ip'].nil?)
741
- if !@config['static_ip'].nil?
742
- if !@config['static_ip']['ip'].nil?
743
- MU::Cloud::AWS::Server.associateElasticIp(instance.instance_id, classic: false, ip: @config['static_ip']['ip'])
744
- elsif !has_elastic_ip
745
- MU::Cloud::AWS::Server.associateElasticIp(instance.instance_id)
746
- end
747
- end
748
- end
749
-
750
- _nat_ssh_key, _nat_ssh_user, nat_ssh_host, _canonical_ip, _ssh_user, _ssh_key_name = getSSHConfig
751
- if subnet.private? and !nat_ssh_host and !MU::Cloud::AWS::VPC.haveRouteToInstance?(cloud_desc, region: @config['region'], credentials: @config['credentials'])
752
- raise MuError, "#{node} is in a private subnet (#{subnet}), but has no bastion host configured, and I have no other route to it"
753
- end
754
-
755
- # If we've asked for additional subnets (and this @config is not a
756
- # member of a Server Pool, which has different semantics), create
757
- # extra interfaces to accomodate.
758
- if !@config['vpc']['subnets'].nil? and @config['basis'].nil?
759
- device_index = 1
760
- @vpc.subnets.each { |s|
761
- subnet_id = s.cloud_id
762
- MU.log "Adding network interface on subnet #{subnet_id} for #{node}"
763
- iface = MU::Cloud::AWS.ec2(region: @config['region'], credentials: @config['credentials']).create_network_interface(subnet_id: subnet_id).network_interface
764
- MU::Cloud::AWS.createStandardTags(iface.network_interface_id, region: @config['region'], credentials: @config['credentials'])
765
- MU::Cloud::AWS.createTag(iface.network_interface_id, "Name", node+"-ETH"+device_index.to_s, region: @config['region'], credentials: @config['credentials'])
766
-
767
- if @config['optional_tags']
768
- MU::MommaCat.listOptionalTags.each { |key, value|
769
- MU::Cloud::AWS.createTag(iface.network_interface_id, key, value, region: @config['region'], credentials: @config['credentials'])
770
- }
771
- end
772
-
773
- if !@config['tags'].nil?
774
- @config['tags'].each { |tag|
775
- MU::Cloud::AWS.createTag(iface.network_interface_id, tag['key'], tag['value'], region: @config['region'], credentials: @config['credentials'])
776
- }
777
- end
778
-
779
- MU::Cloud::AWS.ec2(region: @config['region'], credentials: @config['credentials']).attach_network_interface(
780
- network_interface_id: iface.network_interface_id,
781
- instance_id: instance.instance_id,
782
- device_index: device_index
783
- )
784
- device_index = device_index + 1
785
- }
786
- end
787
- elsif !@config['static_ip'].nil?
788
- if !@config['static_ip']['ip'].nil?
789
- MU::Cloud::AWS::Server.associateElasticIp(instance.instance_id, classic: true, ip: @config['static_ip']['ip'])
790
- elsif !has_elastic_ip
791
- MU::Cloud::AWS::Server.associateElasticIp(instance.instance_id, classic: true)
792
- end
793
- end
794
-
511
+ tagVolumes
512
+ configureNetworking
513
+ saveCredentials
795
514
 
796
515
  if !@config['image_then_destroy']
797
516
  notify
798
517
  end
799
518
 
800
- MU.log "EC2 instance #{node} has id #{instance.instance_id}", MU::DEBUG
801
-
802
- @config["private_dns_name"] = instance.private_dns_name
803
- @config["public_dns_name"] = instance.public_dns_name
804
- @config["private_ip_address"] = instance.private_ip_address
805
- @config["public_ip_address"] = instance.public_ip_address
806
-
807
- # Root disk on standard CentOS AMI
808
- # tagVolumes(instance.instance_id, "/dev/sda", "Name", "ROOT-"+MU.deploy_id+"-"+@config["name"].upcase)
809
- # Root disk on standard Ubuntu AMI
810
- # tagVolumes(instance.instance_id, "/dev/sda1", "Name", "ROOT-"+MU.deploy_id+"-"+@config["name"].upcase)
811
-
812
- # Generic deploy ID tag
813
- # tagVolumes(instance.instance_id)
814
-
815
- # Tag volumes with all our standard tags.
816
- # Maybe replace tagVolumes with this? There is one more place tagVolumes is called from
817
- volumes = MU::Cloud::AWS.ec2(region: @config['region'], credentials: @config['credentials']).describe_volumes(filters: [name: "attachment.instance-id", values: [instance.instance_id]])
818
- volumes.each { |vol|
819
- vol.volumes.each { |volume|
820
- volume.attachments.each { |attachment|
821
- MU::MommaCat.listStandardTags.each_pair { |key, value|
822
- MU::Cloud::AWS.createTag(attachment.volume_id, key, value, region: @config['region'], credentials: @config['credentials'])
823
-
824
- if attachment.device == "/dev/sda" or attachment.device == "/dev/sda1"
825
- MU::Cloud::AWS.createTag(attachment.volume_id, "Name", "ROOT-#{MU.deploy_id}-#{@config["name"].upcase}", region: @config['region'], credentials: @config['credentials'])
826
- else
827
- MU::Cloud::AWS.createTag(attachment.volume_id, "Name", "#{MU.deploy_id}-#{@config["name"].upcase}-#{attachment.device.upcase}", region: @config['region'], credentials: @config['credentials'])
828
- end
829
- }
830
-
831
- if @config['optional_tags']
832
- MU::MommaCat.listOptionalTags.each { |key, value|
833
- MU::Cloud::AWS.createTag(attachment.volume_id, key, value, region: @config['region'], credentials: @config['credentials'])
834
- }
835
- end
836
-
837
- if @config['tags']
838
- @config['tags'].each { |tag|
839
- MU::Cloud::AWS.createTag(attachment.volume_id, tag['key'], tag['value'], region: @config['region'], credentials: @config['credentials'])
840
- }
841
- end
842
- }
843
- }
844
- }
845
-
846
- canonical_name = instance.public_dns_name
847
- canonical_name = instance.private_dns_name if !canonical_name or nat_ssh_host != nil
848
- @config['canonical_name'] = canonical_name
849
-
850
- if !@config['add_private_ips'].nil?
851
- instance.network_interfaces.each { |int|
852
- if int.private_ip_address == instance.private_ip_address and int.private_ip_addresses.size < (@config['add_private_ips'] + 1)
853
- MU.log "Adding #{@config['add_private_ips']} extra private IP addresses to #{instance.instance_id}"
854
- MU::Cloud::AWS.ec2(region: @config['region'], credentials: @config['credentials']).assign_private_ip_addresses(
855
- network_interface_id: int.network_interface_id,
856
- secondary_private_ip_address_count: @config['add_private_ips'],
857
- allow_reassignment: false
858
- )
859
- end
860
- }
861
- notify
862
- end
863
-
864
- begin
865
- if @config['groom'].nil? or @config['groom']
866
- if windows?
867
- # kick off certificate generation early; WinRM will need it
868
- @deploy.nodeSSLCerts(self)
869
- if @config.has_key?("basis")
870
- @deploy.nodeSSLCerts(self, true)
871
- end
872
- if !@groomer.haveBootstrapped?
873
- session = getWinRMSession(50, 60, reboot_on_problems: true)
874
- initialWinRMTasks(session)
875
- begin
876
- session.close
877
- rescue StandardError
878
- # this is allowed to fail- we're probably rebooting anyway
879
- end
880
- else # for an existing Windows node: WinRM, then SSH if it fails
881
- begin
882
- session = getWinRMSession(1, 60)
883
- rescue StandardError # yeah, yeah
884
- session = getSSHSession(1, 60)
885
- # XXX maybe loop at least once if this also fails?
886
- end
887
- end
888
- else
889
- session = getSSHSession(40, 30)
890
- initialSSHTasks(session)
891
- end
892
- end
893
- rescue BootstrapTempFail
894
- sleep 45
895
- retry
896
- ensure
897
- session.close if !session.nil? and !windows?
898
- end
899
-
900
- if @config["existing_deploys"] && !@config["existing_deploys"].empty?
901
- @config["existing_deploys"].each { |ext_deploy|
902
- if ext_deploy["cloud_id"]
903
- found = MU::MommaCat.findStray(
904
- @config['cloud'],
905
- ext_deploy["cloud_type"],
906
- cloud_id: ext_deploy["cloud_id"],
907
- region: @config['region'],
908
- dummy_ok: false
909
- ).first
910
-
911
- MU.log "Couldn't find existing resource #{ext_deploy["cloud_id"]}, #{ext_deploy["cloud_type"]}", MU::ERR if found.nil?
912
- @deploy.notify(ext_deploy["cloud_type"], found.config["name"], found.deploydata, mu_name: found.mu_name, triggering_node: @mu_name)
913
- elsif ext_deploy["mu_name"] && ext_deploy["deploy_id"]
914
- MU.log "#{ext_deploy["mu_name"]} / #{ext_deploy["deploy_id"]}"
915
- found = MU::MommaCat.findStray(
916
- @config['cloud'],
917
- ext_deploy["cloud_type"],
918
- deploy_id: ext_deploy["deploy_id"],
919
- mu_name: ext_deploy["mu_name"],
920
- region: @config['region'],
921
- dummy_ok: false
922
- ).first
923
-
924
- MU.log "Couldn't find existing resource #{ext_deploy["mu_name"]}/#{ext_deploy["deploy_id"]}, #{ext_deploy["cloud_type"]}", MU::ERR if found.nil?
925
- @deploy.notify(ext_deploy["cloud_type"], found.config["name"], found.deploydata, mu_name: ext_deploy["mu_name"], triggering_node: @mu_name)
926
- else
927
- MU.log "Trying to find existing deploy, but either the cloud_id is not valid or no mu_name and deploy_id where provided", MU::ERR
928
- end
929
- }
930
- end
931
-
932
- # See if this node already exists in our config management. If it does,
933
- # we're done.
934
- if MU.inGem?
935
- MU.log "Deploying from a gem, not grooming"
936
- MU::MommaCat.unlock(instance.instance_id+"-orchestrate")
937
- MU::MommaCat.unlock(instance.instance_id+"-groom")
938
-
939
- return true
940
- elsif @groomer.haveBootstrapped?
941
- MU.log "Node #{node} has already been bootstrapped, skipping groomer setup.", MU::NOTICE
942
-
943
- if @config['groom'].nil? or @config['groom']
944
- @groomer.saveDeployData
945
- end
946
-
947
- MU::MommaCat.unlock(instance.instance_id+"-orchestrate")
948
- MU::MommaCat.unlock(instance.instance_id+"-groom")
949
- return true
950
- end
951
-
952
- begin
953
- @groomer.bootstrap if @config['groom'].nil? or @config['groom']
954
- rescue MU::Groomer::RunError
955
- MU::MommaCat.unlock(instance.instance_id+"-groom")
956
- MU::MommaCat.unlock(instance.instance_id+"-orchestrate")
957
- return false
958
- end
519
+ getIAMProfile
520
+ finish.call(false) if !bootstrapGroomer
959
521
 
960
522
  # Make sure we got our name written everywhere applicable
961
523
  if !@named
@@ -963,140 +525,75 @@ module MU
963
525
  @named = true
964
526
  end
965
527
 
966
- MU::MommaCat.unlock(instance.instance_id+"-groom")
967
- MU::MommaCat.unlock(instance.instance_id+"-orchestrate")
968
- return true
969
- end
970
-
971
- # postBoot
528
+ finish.call(true)
529
+ end #postboot
972
530
 
973
531
  # Locate an existing instance or instances and return an array containing matching AWS resource descriptors for those that match.
974
532
  # @return [Hash<String,OpenStruct>]: The cloud provider's complete descriptions of matching instances
975
533
  def self.find(**args)
976
534
  ip ||= args[:flags]['ip'] if args[:flags] and args[:flags]['ip']
977
535
 
978
- if !args[:region].nil?
979
- regions = [args[:region]]
980
- else
981
- regions = MU::Cloud::AWS.listRegions
982
- end
536
+ regions = args[:region].nil? ? MU::Cloud::AWS.listRegions : [args[:region]]
983
537
 
984
538
  found = {}
985
539
  search_semaphore = Mutex.new
986
540
  search_threads = []
987
541
 
988
- if !ip and !args[:cloud_id] and !args[:tag_value]
989
- regions.each { |r|
990
- search_threads << Thread.new {
991
- MU::Cloud::AWS.ec2(region: r, credentials: args[:credentials]).describe_instances(
992
- filters: [
993
- {
994
- name: "instance-state-name",
995
- values: ["running", "pending", "stopped"]
996
- }
997
- ]
998
- ).reservations.each { |resp|
999
- if !resp.nil? and !resp.instances.nil?
1000
- resp.instances.each { |i|
1001
- search_semaphore.synchronize {
1002
- found[i.instance_id] = i
1003
- }
1004
- }
1005
- end
1006
- }
1007
- }
1008
- }
542
+ base_filter = { name: "instance-state-name", values: ["running", "pending", "stopped"] }
543
+ searches = []
1009
544
 
1010
- search_threads.each { |t|
1011
- t.join
545
+ if args[:cloud_id]
546
+ searches << {
547
+ :instance_ids => [args[:cloud_id]],
548
+ :filters => [base_filter]
1012
549
  }
1013
-
1014
- return found
1015
550
  end
1016
551
 
1017
- # If we got an instance id, go get it
1018
- if args[:cloud_id]
1019
- regions.each { |r|
1020
- search_threads << Thread.new {
1021
- MU.log "Hunting for instance with cloud id '#{args[:cloud_id]}' in #{r}", MU::DEBUG
1022
- retries = 0
1023
- begin
1024
- MU::Cloud::AWS.ec2(region: r, credentials: args[:credentials]).describe_instances(
1025
- instance_ids: [args[:cloud_id]],
1026
- filters: [
1027
- {
1028
- name: "instance-state-name",
1029
- values: ["running", "pending", "stopped"]
1030
- }
1031
- ]
1032
- ).reservations.each { |resp|
1033
- if !resp.nil? and !resp.instances.nil?
1034
- resp.instances.each { |i|
1035
- search_semaphore.synchronize {
1036
- found[i.instance_id] = i
1037
- }
1038
- }
1039
- end
1040
- }
1041
- rescue Aws::EC2::Errors::InvalidInstanceIDNotFound => e
1042
- retries += 1
1043
- if retries <= 5
1044
- sleep 5
1045
- else
1046
- raise MuError, "#{e.inspect} in region #{r}"
1047
- end
1048
- end
552
+ if ip
553
+ ["ip-address", "private-ip-address"].each { |ip_type|
554
+ searches << {
555
+ filters: [base_filter, {name: ip_type, values: [ip]} ],
1049
556
  }
1050
557
  }
1051
- done_threads = []
1052
- begin
1053
- search_threads.each { |t|
1054
- joined = t.join(2)
1055
- done_threads << joined if !joined.nil?
1056
- }
1057
- end while found.size < 1 and done_threads.size != search_threads.size
1058
558
  end
1059
559
 
1060
- return found if found.size > 0
1061
-
1062
- # Ok, well, let's try looking it up by IP then
1063
- if !ip.nil?
1064
- MU.log "Hunting for instance by IP '#{ip}'", MU::DEBUG
1065
- ["ip-address", "private-ip-address"].each { |filter|
1066
- regions.each { |r|
1067
- response = MU::Cloud::AWS.ec2(region: r, credentials: args[:credentials]).describe_instances(
1068
- filters: [
1069
- {name: filter, values: [ip]},
1070
- {name: "instance-state-name", values: ["running", "pending", "stopped"]}
1071
- ]
1072
- ).reservations.first
1073
- response.instances.each { |i|
1074
- found[i.instance_id] = i
1075
- }
1076
- }
560
+ if args[:tag_value] and args[:tag_key]
561
+ searches << {
562
+ filters: [
563
+ base_filter,
564
+ {name: ip_type, values: [ip]},
565
+ {name: "tag:#{args[:tag_key]}", values: [args[:tag_value]]},
566
+ ]
1077
567
  }
1078
568
  end
1079
569
 
1080
- return found if found.size > 0
570
+ if searches.empty?
571
+ searches << { filters: [base_filter] }
572
+ end
1081
573
 
1082
- # Fine, let's try it by tag.
1083
- if args[:tag_value]
1084
- MU.log "Searching for instance by tag '#{args[:tag_key]}=#{args[:tag_value]}'", MU::DEBUG
1085
- regions.each { |r|
1086
- MU::Cloud::AWS.ec2(region: r, credentials: args[:credentials]).describe_instances(
1087
- filters: [
1088
- {name: "tag:#{args[:tag_key]}", values: [args[:tag_value]]},
1089
- {name: "instance-state-name", values: ["running", "pending", "stopped"]}
1090
- ]
1091
- ).reservations.each { |resp|
1092
- if !resp.nil? and resp.instances.size > 0
1093
- resp.instances.each { |i|
1094
- found[i.instance_id] = i
574
+ regions.each { |r|
575
+ searches.each { |search|
576
+ search_threads << Thread.new(search) { |params|
577
+ MU.retrier([Aws::EC2::Errors::InvalidInstanceIDNotFound], wait: 5, max: 5, ignoreme: [Aws::EC2::Errors::InvalidInstanceIDNotFound]) {
578
+ MU::Cloud::AWS.ec2(region: r, credentials: args[:credentials]).describe_instances(params).reservations.each { |resp|
579
+ next if resp.nil? or resp.instances.nil?
580
+ resp.instances.each { |i|
581
+ search_semaphore.synchronize {
582
+ found[i.instance_id] = i
583
+ }
584
+ }
1095
585
  }
1096
- end
586
+ }
1097
587
  }
1098
588
  }
1099
- end
589
+ }
590
+ done_threads = []
591
+ begin
592
+ search_threads.each { |t|
593
+ joined = t.join(2)
594
+ done_threads << joined if !joined.nil?
595
+ }
596
+ end while found.size < 1 and done_threads.size != search_threads.size
1100
597
 
1101
598
  return found
1102
599
  end
@@ -1117,7 +614,7 @@ module MU
1117
614
  return nil
1118
615
  end
1119
616
 
1120
- asgs = MU::Cloud::AWS::ServerPool.find(
617
+ asgs = MU::Cloud.resourceClass("AWS", "ServerPool").find(
1121
618
  instance_id: @cloud_id,
1122
619
  region: @config['region'],
1123
620
  credentials: @credentials
@@ -1211,8 +708,8 @@ module MU
1211
708
 
1212
709
  int.private_ip_addresses.each { |priv_ip|
1213
710
  if !priv_ip.primary
1214
- bok['add_private_ips'] ||= []
1215
- bok['add_private_ips'] << priv_ip.private_ip_address
711
+ bok['add_private_ips'] ||= 0
712
+ bok['add_private_ips'] += 1
1216
713
  end
1217
714
  if priv_ip.association and priv_ip.association.public_ip
1218
715
  bok['associate_public_ip'] = true
@@ -1227,15 +724,15 @@ module MU
1227
724
 
1228
725
  if int.groups.size > 0
1229
726
 
1230
- require 'mu/clouds/aws/firewall_rule'
1231
- ifaces = MU::Cloud::AWS::FirewallRule.getAssociatedInterfaces(int.groups.map { |sg| sg.group_id }, credentials: @credentials, region: @config['region'])
727
+ 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'])
1232
729
  done_local_rules = false
1233
730
  int.groups.each { |sg|
1234
731
  if !done_local_rules and ifaces[sg.group_id].size == 1
1235
- sg_desc = MU::Cloud::AWS::FirewallRule.find(cloud_id: sg.group_id, credentials: @credentials, region: @config['region']).values.first
732
+ sg_desc = MU::Cloud.resourceClass("AWS", "FirewallRule").find(cloud_id: sg.group_id, credentials: @credentials, region: @config['region']).values.first
1236
733
  if sg_desc
1237
- bok["ingress_rules"] = MU::Cloud::AWS::FirewallRule.rulesToBoK(sg_desc.ip_permissions)
1238
- bok["ingress_rules"].concat(MU::Cloud::AWS::FirewallRule.rulesToBoK(sg_desc.ip_permissions_egress, egress: true))
734
+ bok["ingress_rules"] = MU::Cloud.resourceClass("AWS", "FirewallRule").rulesToBoK(sg_desc.ip_permissions)
735
+ bok["ingress_rules"].concat(MU::Cloud.resourceClass("AWS", "FirewallRule").rulesToBoK(sg_desc.ip_permissions_egress, egress: true))
1239
736
  done_local_rules = true
1240
737
  next
1241
738
  end
@@ -1304,52 +801,16 @@ module MU
1304
801
  end
1305
802
  deploydata["region"] = @config['region'] if !@config['region'].nil?
1306
803
  if !@named
1307
- MU::MommaCat.nameKitten(self)
804
+ MU::MommaCat.nameKitten(self, no_dns: true)
1308
805
  @named = true
1309
806
  end
1310
807
 
1311
808
  return deploydata
1312
809
  end
1313
810
 
1314
- # If the specified server is in a VPC, and has a NAT, make sure we'll
1315
- # be letting ssh traffic in from said NAT.
1316
- def punchAdminNAT
1317
- if @config['vpc'].nil? or
1318
- (
1319
- !@config['vpc'].has_key?("nat_host_id") and
1320
- !@config['vpc'].has_key?("nat_host_tag") and
1321
- !@config['vpc'].has_key?("nat_host_ip") and
1322
- !@config['vpc'].has_key?("nat_host_name")
1323
- )
1324
- return nil
1325
- end
1326
-
1327
- return nil if @nat.is_a?(Struct) && @nat.nat_gateway_id && @nat.nat_gateway_id.start_with?("nat-")
1328
-
1329
- dependencies if @nat.nil?
1330
- if @nat.nil? or @nat.cloud_desc.nil?
1331
- raise MuError, "#{@mu_name} (#{MU.deploy_id}) is configured to use #{@config['vpc']} but I can't find the cloud descriptor for a matching NAT instance"
1332
- end
1333
- MU.log "Adding administrative holes for NAT host #{@nat.cloud_desc.private_ip_address} to #{@mu_name}"
1334
- if !@deploy.kittens['firewall_rules'].nil?
1335
- @deploy.kittens['firewall_rules'].values.each { |acl|
1336
- if acl.config["admin"]
1337
- acl.addRule([@nat.cloud_desc.private_ip_address], proto: "tcp")
1338
- acl.addRule([@nat.cloud_desc.private_ip_address], proto: "udp")
1339
- acl.addRule([@nat.cloud_desc.private_ip_address], proto: "icmp")
1340
- end
1341
- }
1342
- end
1343
- end
1344
-
1345
811
  # Called automatically by {MU::Deploy#createResources}
1346
812
  def groom
1347
813
  MU::MommaCat.lock(@cloud_id+"-groom")
1348
- node, _config, deploydata = describe(cloud_id: @cloud_id)
1349
-
1350
- if node.nil? or node.empty?
1351
- raise MuError, "MU::Cloud::AWS::Server.groom was called without a mu_name"
1352
- end
1353
814
 
1354
815
  # Make double sure we don't lose a cached mu_windows_name value.
1355
816
  if windows? or !@config['active_directory'].nil?
@@ -1358,9 +819,9 @@ module MU
1358
819
  end
1359
820
  end
1360
821
 
1361
- punchAdminNAT
822
+ allowBastionAccess
1362
823
 
1363
- MU::Cloud::AWS::Server.tagVolumes(@cloud_id, credentials: @config['credentials'])
824
+ tagVolumes
1364
825
 
1365
826
  # If we have a loadbalancer configured, attach us to it
1366
827
  if !@config['loadbalancers'].nil?
@@ -1389,55 +850,31 @@ module MU
1389
850
  end
1390
851
 
1391
852
  begin
853
+ getIAMProfile
854
+
855
+ dbs = @deploy.findLitterMate(type: "database", return_all: true)
856
+ if dbs
857
+ dbs.each_pair { |sib_name, sib|
858
+ @groomer.groomer_class.grantSecretAccess(@mu_name, sib_name, "database_credentials")
859
+ if sib.config and sib.config['auth_vault']
860
+ @groomer.groomer_class.grantSecretAccess(@mu_name, sib.config['auth_vault']['vault'], sib.config['auth_vault']['item'])
861
+ end
862
+ }
863
+ end
864
+
1392
865
  if @config['groom'].nil? or @config['groom']
1393
- @groomer.run(purpose: "Full Initial Run", max_retries: 15, reboot_first_fail: windows?, timeout: @config['groomer_timeout'])
866
+ @groomer.run(purpose: "Full Initial Run", max_retries: 15, reboot_first_fail: (windows? and @config['groomer'] != "Ansible"), timeout: @config['groomer_timeout'])
1394
867
  end
1395
868
  rescue MU::Groomer::RunError => e
1396
- MU.log "Proceeding after failed initial Groomer run, but #{node} may not behave as expected!", MU::WARN, details: e.message
869
+ raise e if !@config['create_image'].nil? and !@config['image_created']
870
+ MU.log "Proceeding after failed initial Groomer run, but #{@mu_name} may not behave as expected!", MU::WARN, details: e.message
1397
871
  rescue StandardError => e
1398
- MU.log "Caught #{e.inspect} on #{node} in an unexpected place (after @groomer.run on Full Initial Run)", MU::ERR
872
+ raise e if !@config['create_image'].nil? and !@config['image_created']
873
+ MU.log "Caught #{e.inspect} on #{@mu_name} in an unexpected place (after @groomer.run on Full Initial Run)", MU::ERR
1399
874
  end
1400
875
 
1401
876
  if !@config['create_image'].nil? and !@config['image_created']
1402
- img_cfg = @config['create_image']
1403
- # Scrub things that don't belong on an AMI
1404
- session = getSSHSession
1405
- sudo = purgecmd = ""
1406
- sudo = "sudo" if @config['ssh_user'] != "root"
1407
- if windows?
1408
- purgecmd = "rm -rf /cygdrive/c/mu_installed_chef"
1409
- else
1410
- purgecmd = "rm -rf /opt/mu_installed_chef"
1411
- end
1412
- if img_cfg['image_then_destroy']
1413
- if windows?
1414
- purgecmd = "rm -rf /cygdrive/c/chef/ /home/#{@config['windows_admin_username']}/.ssh/authorized_keys /home/Administrator/.ssh/authorized_keys /cygdrive/c/mu-installer-ran-updates /cygdrive/c/mu_installed_chef"
1415
- # session.exec!("powershell -Command \"& {(Get-WmiObject -Class Win32_Product -Filter \"Name='UniversalForwarder'\").Uninstall()}\"")
1416
- else
1417
- purgecmd = "#{sudo} rm -rf /var/lib/cloud/instances/i-* /root/.ssh/authorized_keys /etc/ssh/ssh_host_*key* /etc/chef /etc/opscode/* /.mu-installer-ran-updates /var/chef /opt/mu_installed_chef /opt/chef ; #{sudo} sed -i 's/^HOSTNAME=.*//' /etc/sysconfig/network"
1418
- end
1419
- end
1420
- session.exec!(purgecmd)
1421
- session.close
1422
- ami_ids = MU::Cloud::AWS::Server.createImage(
1423
- name: @mu_name,
1424
- instance_id: @cloud_id,
1425
- storage: @config['storage'],
1426
- exclude_storage: img_cfg['image_exclude_storage'],
1427
- copy_to_regions: img_cfg['copy_to_regions'],
1428
- make_public: img_cfg['public'],
1429
- region: @config['region'],
1430
- tags: @config['tags'],
1431
- credentials: @config['credentials']
1432
- )
1433
- @deploy.notify("images", @config['name'], ami_ids)
1434
- @config['image_created'] = true
1435
- if img_cfg['image_then_destroy']
1436
- MU::Cloud::AWS::Server.waitForAMI(ami_ids[@config['region']], region: @config['region'], credentials: @config['credentials'])
1437
- MU.log "AMI #{ami_ids[@config['region']]} ready, removing source node #{node}"
1438
- MU::Cloud::AWS::Server.terminateInstance(id: @cloud_id, region: @config['region'], deploy_id: @deploy.deploy_id, mu_name: @mu_name, credentials: @config['credentials'])
1439
- destroy
1440
- end
877
+ createImage
1441
878
  end
1442
879
 
1443
880
  MU::MommaCat.unlock(@cloud_id+"-groom")
@@ -1454,6 +891,7 @@ module MU
1454
891
  # @return [Openstruct]
1455
892
  def cloud_desc(use_cache: true)
1456
893
  return @cloud_desc_cache if @cloud_desc_cache and use_cache
894
+ return nil if !@cloud_id
1457
895
  max_retries = 5
1458
896
  retries = 0
1459
897
  if !@cloud_id.nil?
@@ -1485,23 +923,19 @@ module MU
1485
923
  # bastion hosts that may be in the path, see getSSHConfig if that's what
1486
924
  # you need.
1487
925
  def canonicalIP
1488
- _mu_name, _config, deploydata = describe(cloud_id: @cloud_id)
1489
-
1490
- instance = cloud_desc
1491
-
1492
- if !instance
926
+ if !cloud_desc
1493
927
  raise MuError, "Couldn't retrieve cloud descriptor for server #{self}"
1494
928
  end
1495
929
 
1496
930
  if deploydata.nil? or
1497
931
  (!deploydata.has_key?("private_ip_address") and
1498
932
  !deploydata.has_key?("public_ip_address"))
1499
- return nil if instance.nil?
933
+ return nil if cloud_desc.nil?
1500
934
  @deploydata = {} if @deploydata.nil?
1501
- @deploydata["public_ip_address"] = instance.public_ip_address
1502
- @deploydata["public_dns_name"] = instance.public_dns_name
1503
- @deploydata["private_ip_address"] = instance.private_ip_address
1504
- @deploydata["private_dns_name"] = instance.private_dns_name
935
+ @deploydata["public_ip_address"] = cloud_desc.public_ip_address
936
+ @deploydata["public_dns_name"] = cloud_desc.public_dns_name
937
+ @deploydata["private_ip_address"] = cloud_desc.private_ip_address
938
+ @deploydata["private_dns_name"] = cloud_desc.private_dns_name
1505
939
 
1506
940
  notify
1507
941
  end
@@ -1509,14 +943,14 @@ module MU
1509
943
  # Our deploydata gets corrupted often with server pools, this will cause us to use the wrong IP to identify a node
1510
944
  # which will cause us to create certificates, DNS records and other artifacts with incorrect information which will cause our deploy to fail.
1511
945
  # The cloud_id is always correct so lets use 'cloud_desc' to get the correct IPs
1512
- if MU::Cloud::AWS::VPC.haveRouteToInstance?(cloud_desc, region: @config['region'], credentials: @config['credentials']) or @deploydata["public_ip_address"].nil?
1513
- @config['canonical_ip'] = instance.private_ip_address
1514
- @deploydata["private_ip_address"] = instance.private_ip_address
1515
- return instance.private_ip_address
946
+ if MU::Cloud.resourceClass("AWS", "VPC").haveRouteToInstance?(cloud_desc, region: @config['region'], credentials: @config['credentials']) or @deploydata["public_ip_address"].nil?
947
+ @config['canonical_ip'] = cloud_desc.private_ip_address
948
+ @deploydata["private_ip_address"] = cloud_desc.private_ip_address
949
+ return cloud_desc.private_ip_address
1516
950
  else
1517
- @config['canonical_ip'] = instance.public_ip_address
1518
- @deploydata["public_ip_address"] = instance.public_ip_address
1519
- return instance.public_ip_address
951
+ @config['canonical_ip'] = cloud_desc.public_ip_address
952
+ @deploydata["public_ip_address"] = cloud_desc.public_ip_address
953
+ return cloud_desc.public_ip_address
1520
954
  end
1521
955
  end
1522
956
 
@@ -1709,11 +1143,27 @@ module MU
1709
1143
  # Retrieves the Cloud provider's randomly generated Windows password
1710
1144
  # Will only work on stock Amazon Windows AMIs or custom AMIs that where created with Administrator Password set to random in EC2Config
1711
1145
  # return [String]: A password string.
1712
- def getWindowsAdminPassword
1713
- if @cloud_id.nil?
1714
- describe
1715
- @cloud_id = cloud_desc.instance_id
1146
+ def getWindowsAdminPassword(use_cache: true)
1147
+ @config['windows_auth_vault'] ||= {
1148
+ "vault" => @mu_name,
1149
+ "item" => "windows_credentials",
1150
+ "password_field" => "password"
1151
+ }
1152
+
1153
+ if use_cache
1154
+ begin
1155
+ win_admin_password = @groomer.getSecret(
1156
+ vault: @config['windows_auth_vault']['vault'],
1157
+ item: @config['windows_auth_vault']['item'],
1158
+ field: @config["windows_auth_vault"]["password_field"]
1159
+ )
1160
+
1161
+ return win_admin_password if win_admin_password
1162
+ rescue MU::Groomer::MuNoSuchSecret, MU::Groomer::RunError
1163
+ end
1716
1164
  end
1165
+
1166
+ @cloud_id ||= cloud_desc(use_cache: false).instance_id
1717
1167
  ssh_keydir = "#{Etc.getpwuid(Process.uid).dir}/.ssh"
1718
1168
  ssh_key_name = @deploy.ssh_key_name
1719
1169
 
@@ -1748,6 +1198,8 @@ module MU
1748
1198
  pem_bytes = File.open("#{ssh_keydir}/#{ssh_key_name}", 'rb') { |f| f.read }
1749
1199
  private_key = OpenSSL::PKey::RSA.new(pem_bytes)
1750
1200
  decrypted_password = private_key.private_decrypt(decoded)
1201
+ saveCredentials(decrypted_password)
1202
+
1751
1203
  return decrypted_password
1752
1204
  end
1753
1205
 
@@ -1821,60 +1273,37 @@ module MU
1821
1273
  # @param type [String]: Cloud storage type of the volume, if applicable
1822
1274
  # @param delete_on_termination [Boolean]: Value of delete_on_termination flag to set
1823
1275
  def addVolume(dev, size, type: "gp2", delete_on_termination: false)
1824
- if @cloud_id.nil? or @cloud_id.empty?
1825
- MU.log "#{self} didn't have a cloud id, couldn't determine 'active?' status", MU::ERR
1826
- return true
1276
+
1277
+ if setDeleteOntermination(dev, delete_on_termination)
1278
+ MU.log "A volume #{device} already attached to #{self}, skipping", MU::NOTICE
1279
+ return
1827
1280
  end
1828
- az = nil
1829
- MU::Cloud::AWS.ec2(region: @config['region'], credentials: @config['credentials']).describe_instances(
1830
- instance_ids: [@cloud_id]
1831
- ).reservations.each { |resp|
1832
- if !resp.nil? and !resp.instances.nil?
1833
- resp.instances.each { |instance|
1834
- az = instance.placement.availability_zone
1835
- mappings = MU.structToHash(instance.block_device_mappings)
1836
- mappings.each { |vol|
1837
- if vol[:ebs]
1838
- vol[:ebs].delete(:attach_time)
1839
- vol[:ebs].delete(:status)
1840
- end
1841
- }
1842
- mappings.each { |vol|
1843
- if vol[:device_name] == dev
1844
- MU.log "A volume #{dev} already attached to #{self}, skipping", MU::NOTICE
1845
- if vol[:ebs][:delete_on_termination] != delete_on_termination
1846
- vol[:ebs][:delete_on_termination] = delete_on_termination
1847
- MU.log "Setting delete_on_termination flag to #{delete_on_termination.to_s} on #{@mu_name}'s #{dev}"
1848
- MU::Cloud::AWS.ec2(region: @config['region'], credentials: @config['credentials']).modify_instance_attribute(
1849
- instance_id: @cloud_id,
1850
- block_device_mappings: mappings
1851
- )
1852
- end
1853
- return
1854
- end
1855
- }
1856
- }
1857
- end
1858
- }
1281
+
1859
1282
  MU.log "Creating #{size}GB #{type} volume on #{dev} for #{@cloud_id}"
1860
1283
  creation = MU::Cloud::AWS.ec2(region: @config['region'], credentials: @config['credentials']).create_volume(
1861
- availability_zone: az,
1284
+ availability_zone: cloud_desc.placement.availability_zone,
1862
1285
  size: size,
1863
1286
  volume_type: type
1864
1287
  )
1865
- begin
1866
- sleep 3
1288
+
1289
+ MU.retrier(wait: 3, loop_if: Proc.new {
1867
1290
  creation = MU::Cloud::AWS.ec2(region: @config['region'], credentials: @config['credentials']).describe_volumes(volume_ids: [creation.volume_id]).volumes.first
1868
1291
  if !["creating", "available"].include?(creation.state)
1869
1292
  raise MuError, "Saw state '#{creation.state}' while creating #{size}GB #{type} volume on #{dev} for #{@cloud_id}"
1870
1293
  end
1871
- end while creation.state != "available"
1294
+ creation.state != "available"
1295
+ })
1296
+
1872
1297
 
1873
1298
  if @deploy
1874
- MU::MommaCat.listStandardTags.each_pair { |key, value|
1875
- MU::Cloud::AWS.createTag(creation.volume_id, key, value, region: @config['region'], credentials: @config['credentials'])
1876
- }
1877
- MU::Cloud::AWS.createTag(creation.volume_id, "Name", "#{MU.deploy_id}-#{@config["name"].upcase}-#{dev.upcase}", region: @config['region'], credentials: @config['credentials'])
1299
+ MU::Cloud::AWS.createStandardTags(
1300
+ creation.volume_id,
1301
+ region: @config['region'],
1302
+ credentials: @config['credentials'],
1303
+ optional: @config['optional_tags'],
1304
+ nametag: @mu_name+"-"+dev.upcase,
1305
+ othertags: @config['tags']
1306
+ )
1878
1307
  end
1879
1308
 
1880
1309
  attachment = MU::Cloud::AWS.ec2(region: @config['region'], credentials: @config['credentials']).attach_volume(
@@ -1893,29 +1322,7 @@ module MU
1893
1322
 
1894
1323
  # Set delete_on_termination, which for some reason is an instance
1895
1324
  # attribute and not on the attachment
1896
- mappings = MU.structToHash(cloud_desc.block_device_mappings)
1897
- changed = false
1898
-
1899
- mappings.each { |mapping|
1900
- if mapping[:ebs]
1901
- mapping[:ebs].delete(:attach_time)
1902
- mapping[:ebs].delete(:status)
1903
- end
1904
- if mapping[:device_name] == dev and
1905
- mapping[:ebs][:delete_on_termination] != delete_on_termination
1906
- changed = true
1907
- mapping[:ebs][:delete_on_termination] = delete_on_termination
1908
- end
1909
- }
1910
-
1911
- if changed
1912
- MU.log "Setting delete_on_termination flag to #{delete_on_termination.to_s} on #{@mu_name}'s #{dev}"
1913
- MU::Cloud::AWS.ec2(region: @config['region'], credentials: @config['credentials']).modify_instance_attribute(
1914
- instance_id: @cloud_id,
1915
- block_device_mappings: mappings
1916
- )
1917
- end
1918
-
1325
+ setDeleteOntermination(dev, delete_on_termination)
1919
1326
  end
1920
1327
 
1921
1328
  # Determine whether the node in question exists at the Cloud provider
@@ -1953,13 +1360,13 @@ module MU
1953
1360
  # @param ip [String]: Request a specific IP address.
1954
1361
  # @param region [String]: The cloud provider region
1955
1362
  # @return [void]
1956
- def self.associateElasticIp(instance_id, classic: false, ip: nil, region: MU.curRegion)
1363
+ def self.associateElasticIp(instance_id, classic: false, ip: nil, region: MU.curRegion, credentials: nil)
1957
1364
  MU.log "associateElasticIp called: #{instance_id}, classic: #{classic}, ip: #{ip}, region: #{region}", MU::DEBUG
1958
1365
  elastic_ip = nil
1959
1366
  @eip_semaphore.synchronize {
1960
1367
  if !ip.nil?
1961
1368
  filters = [{name: "public-ip", values: [ip]}]
1962
- resp = MU::Cloud::AWS.ec2(region: region).describe_addresses(filters: filters)
1369
+ resp = MU::Cloud::AWS.ec2(region: region, credentials: credentials).describe_addresses(filters: filters)
1963
1370
  if @eips_used.include?(ip)
1964
1371
  is_free = false
1965
1372
  resp.addresses.each { |address|
@@ -1988,54 +1395,44 @@ module MU
1988
1395
  @eips_used << elastic_ip.public_ip
1989
1396
  MU.log "Associating Elastic IP #{elastic_ip.public_ip} with #{instance_id}", details: elastic_ip
1990
1397
  }
1991
- attempts = 0
1992
- begin
1398
+
1399
+ on_retry = Proc.new { |e|
1400
+ if e.class == Aws::EC2::Errors::ResourceAlreadyAssociated
1401
+ # A previous association attempt may have succeeded, albeit slowly.
1402
+ resp = MU::Cloud::AWS.ec2(region: region, credentials: credentials).describe_addresses(
1403
+ allocation_ids: [elastic_ip.allocation_id]
1404
+ )
1405
+ first_addr = resp.addresses.first
1406
+ if first_addr and first_addr.instance_id != instance_id
1407
+ raise MuError, "Tried to associate #{elastic_ip.public_ip} with #{instance_id}, but it's already associated with #{first_addr.instance_id}!"
1408
+ end
1409
+ end
1410
+ }
1411
+
1412
+ MU.retrier([Aws::EC2::Errors::IncorrectInstanceState, Aws::EC2::Errors::ResourceAlreadyAssociated], wait: 5, max: 6, on_retry: on_retry) {
1993
1413
  if classic
1994
- resp = MU::Cloud::AWS.ec2(region: region).associate_address(
1995
- instance_id: instance_id,
1996
- public_ip: elastic_ip.public_ip
1414
+ MU::Cloud::AWS.ec2(region: region, credentials: credentials).associate_address(
1415
+ instance_id: instance_id,
1416
+ public_ip: elastic_ip.public_ip
1997
1417
  )
1998
1418
  else
1999
- resp = MU::Cloud::AWS.ec2(region: region).associate_address(
2000
- instance_id: instance_id,
2001
- allocation_id: elastic_ip.allocation_id,
2002
- allow_reassociation: false
1419
+ MU::Cloud::AWS.ec2(region: region, credentials: credentials).associate_address(
1420
+ instance_id: instance_id,
1421
+ allocation_id: elastic_ip.allocation_id,
1422
+ allow_reassociation: false
2003
1423
  )
2004
1424
  end
2005
- rescue Aws::EC2::Errors::IncorrectInstanceState => e
2006
- attempts = attempts + 1
2007
- if attempts < 6
2008
- MU.log "Got #{e.message} associating #{elastic_ip.allocation_id} with #{instance_id}, retrying", MU::WARN
2009
- sleep 5
2010
- retry
2011
- end
2012
- raise MuError "#{e.message} associating #{elastic_ip.allocation_id} with #{instance_id}"
2013
- rescue Aws::EC2::Errors::ResourceAlreadyAssociated => e
2014
- # A previous association attempt may have succeeded, albeit slowly.
2015
- resp = MU::Cloud::AWS.ec2(region: region).describe_addresses(
2016
- allocation_ids: [elastic_ip.allocation_id]
2017
- )
2018
- first_addr = resp.addresses.first
2019
- if !first_addr.nil? and first_addr.instance_id == instance_id
2020
- MU.log "#{elastic_ip.public_ip} already associated with #{instance_id}", MU::WARN
2021
- else
2022
- MU.log "#{elastic_ip.public_ip} shows as already associated!", MU::ERR, details: resp
2023
- raise MuError, "#{elastic_ip.public_ip} shows as already associated with #{first_addr.instance_id}!"
2024
- end
2025
- end
1425
+ }
2026
1426
 
2027
- instance = MU::Cloud::AWS.ec2(region: region).describe_instances(instance_ids: [instance_id]).reservations.first.instances.first
2028
- waited = false
2029
- if instance.public_ip_address != elastic_ip.public_ip
2030
- waited = true
2031
- begin
2032
- sleep 10
2033
- MU.log "Waiting for Elastic IP association of #{elastic_ip.public_ip} to #{instance_id} to take effect", MU::NOTICE
2034
- instance = MU::Cloud::AWS.ec2(region: region).describe_instances(instance_ids: [instance_id]).reservations.first.instances.first
2035
- end while instance.public_ip_address != elastic_ip.public_ip
2036
- end
1427
+ loop_if = Proc.new {
1428
+ instance = find(cloud_id: instance_id, region: region, credentials: credentials).values.first
1429
+ instance.public_ip_address != elastic_ip.public_ip
1430
+ }
1431
+ MU.retrier(loop_if: loop_if, wait: 10, max: 3) {
1432
+ MU.log "Waiting for Elastic IP association of #{elastic_ip.public_ip} to #{instance_id} to take effect", MU::NOTICE
1433
+ }
2037
1434
 
2038
- MU.log "Elastic IP #{elastic_ip.public_ip} now associated with #{instance_id}" if waited
1435
+ MU.log "Elastic IP #{elastic_ip.public_ip} now associated with #{instance_id}"
2039
1436
 
2040
1437
  return elastic_ip.public_ip
2041
1438
  end
@@ -2058,11 +1455,11 @@ module MU
2058
1455
  # @param ignoremaster [Boolean]: If true, will remove resources not flagged as originating from this Mu server
2059
1456
  # @param region [String]: The cloud provider region
2060
1457
  # @return [void]
2061
- def self.cleanup(noop: false, ignoremaster: false, region: MU.curRegion, credentials: nil, flags: {})
1458
+ def self.cleanup(noop: false, deploy_id: MU.deploy_id, ignoremaster: false, region: MU.curRegion, credentials: nil, flags: {})
2062
1459
  onlycloud = flags["onlycloud"]
2063
1460
  skipsnapshots = flags["skipsnapshots"]
2064
1461
  tagfilters = [
2065
- {name: "tag:MU-ID", values: [MU.deploy_id]}
1462
+ {name: "tag:MU-ID", values: [deploy_id]}
2066
1463
  ]
2067
1464
  if !ignoremaster
2068
1465
  tagfilters << {name: "tag:MU-MASTER-IP", values: [MU.mu_public_ip]}
@@ -2096,7 +1493,7 @@ module MU
2096
1493
  threads << Thread.new(instance) { |myinstance|
2097
1494
  MU.dupGlobals(parent_thread_id)
2098
1495
  Thread.abort_on_exception = true
2099
- MU::Cloud::AWS::Server.terminateInstance(id: myinstance.instance_id, noop: noop, onlycloud: onlycloud, region: region, deploy_id: MU.deploy_id, credentials: credentials)
1496
+ MU::Cloud::AWS::Server.terminateInstance(id: myinstance.instance_id, noop: noop, onlycloud: onlycloud, region: region, deploy_id: deploy_id, credentials: credentials)
2100
1497
  }
2101
1498
  }
2102
1499
 
@@ -2107,7 +1504,7 @@ module MU
2107
1504
  threads << Thread.new(volume) { |myvolume|
2108
1505
  MU.dupGlobals(parent_thread_id)
2109
1506
  Thread.abort_on_exception = true
2110
- delete_volume(myvolume, noop, skipsnapshots, credentials: credentials)
1507
+ delete_volume(myvolume, noop, skipsnapshots, credentials: credentials, deploy_id: deploy_id)
2111
1508
  }
2112
1509
  }
2113
1510
 
@@ -2117,193 +1514,113 @@ module MU
2117
1514
  }
2118
1515
  end
2119
1516
 
1517
+ # Return an instance's AWS-assigned IP addresses and hostnames.
1518
+ # @param instance [OpenStruct]
1519
+ # @param id [String]
1520
+ # @param region [String]
1521
+ # @param credentials [@String]
1522
+ # @return [Array<Array>]
1523
+ def self.getAddresses(instance = nil, id: nil, region: MU.curRegion, credentials: nil)
1524
+ return nil if !instance and !id
1525
+
1526
+ instance ||= find(cloud_id: id, region: region, credentials: credentials).values.first
1527
+ return if !instance
1528
+
1529
+ ips = []
1530
+ names = []
1531
+ instance.network_interfaces.each { |iface|
1532
+ iface.private_ip_addresses.each { |ip|
1533
+ ips << ip.private_ip_address
1534
+ names << ip.private_dns_name
1535
+ if ip.association
1536
+ ips << ip.association.public_ip
1537
+ names << ip.association.public_dns_name
1538
+ end
1539
+ }
1540
+ }
1541
+
1542
+ [ips, names]
1543
+ end
1544
+
2120
1545
  # Terminate an instance.
2121
1546
  # @param instance [OpenStruct]: The cloud provider's description of the instance.
2122
1547
  # @param id [String]: The cloud provider's identifier for the instance, to use if the full description is not available.
2123
1548
  # @param region [String]: The cloud provider region
2124
1549
  # @return [void]
2125
1550
  def self.terminateInstance(instance: nil, noop: false, id: nil, onlycloud: false, region: MU.curRegion, deploy_id: MU.deploy_id, mu_name: nil, credentials: nil)
2126
- ips = Array.new
2127
- if !instance
2128
- if id
2129
- begin
2130
- resp = MU::Cloud::AWS.ec2(credentials: credentials, region: region).describe_instances(instance_ids: [id])
2131
- rescue Aws::EC2::Errors::InvalidInstanceIDNotFound
2132
- MU.log "Instance #{id} no longer exists", MU::WARN
2133
- end
2134
- if !resp.nil? and !resp.reservations.nil? and !resp.reservations.first.nil?
2135
- instance = resp.reservations.first.instances.first
2136
- ips << instance.public_ip_address if !instance.public_ip_address.nil?
2137
- ips << instance.private_ip_address if !instance.private_ip_address.nil?
2138
- end
2139
- else
2140
- MU.log "You must supply an instance handle or id to terminateInstance", MU::ERR
2141
- end
2142
- else
2143
- id = instance.instance_id
2144
- end
2145
- if !MU.deploy_id.empty?
2146
- deploy_dir = File.expand_path("#{MU.dataDir}/deployments/"+MU.deploy_id)
2147
- if Dir.exist?(deploy_dir) and !noop
2148
- FileUtils.touch("#{deploy_dir}/.cleanup-"+id)
2149
- end
1551
+ if !id and !instance
1552
+ MU.log "You must supply an instance handle or id to terminateInstance", MU::ERR
1553
+ return
2150
1554
  end
1555
+ instance ||= find(cloud_id: id, region: region, credentials: credentials).values.first
1556
+ return if !instance
2151
1557
 
2152
- server_obj = MU::MommaCat.findStray(
2153
- "AWS",
2154
- "servers",
2155
- region: region,
2156
- deploy_id: deploy_id,
2157
- cloud_id: id,
2158
- mu_name: mu_name
2159
- ).first
2160
-
1558
+ id ||= instance.instance_id
2161
1559
  begin
2162
- MU::Cloud::AWS.ec2(credentials: credentials, region: region).describe_instances(instance_ids: [id])
2163
- rescue Aws::EC2::Errors::InvalidInstanceIDNotFound
2164
- MU.log "Instance #{id} no longer exists", MU::DEBUG
2165
- end
2166
-
2167
- if !server_obj.nil? and MU::Cloud::AWS.hosted? and !MU::Cloud::AWS.isGovCloud?
2168
- # DNS cleanup is now done in MU::Cloud::DNSZone. Keeping this for now
2169
- cleaned_dns = false
2170
- mu_name = server_obj.mu_name
2171
- mu_zone = MU::Cloud::DNSZone.find(cloud_id: "platform-mu", credentials: credentials).values.first
2172
- if !mu_zone.nil?
2173
- zone_rrsets = []
2174
- rrsets = MU::Cloud::AWS.route53(credentials: credentials).list_resource_record_sets(hosted_zone_id: mu_zone.id)
2175
- rrsets.resource_record_sets.each{ |record|
2176
- zone_rrsets << record
2177
- }
1560
+ MU::MommaCat.lock(".cleanup-"+id)
1561
+ rescue Errno::ENOENT => e
1562
+ MU.log "No lock for terminating instance #{id} due to missing metadata", MU::DEBUG
1563
+ end
2178
1564
 
2179
- # AWS API returns a maximum of 100 results. DNS zones are likely to have more than 100 records, lets page and make sure we grab all records in a given zone
2180
- while rrsets.next_record_name && rrsets.next_record_type
2181
- rrsets = MU::Cloud::AWS.route53(credentials: credentials).list_resource_record_sets(hosted_zone_id: mu_zone.id, start_record_name: rrsets.next_record_name, start_record_type: rrsets.next_record_type)
2182
- rrsets.resource_record_sets.each{ |record|
2183
- zone_rrsets << record
2184
- }
2185
- end
2186
- end
2187
- if !onlycloud and !mu_name.nil?
2188
- # DNS cleanup is now done in MU::Cloud::DNSZone. Keeping this for now
2189
- if !zone_rrsets.nil? and !zone_rrsets.empty?
2190
- zone_rrsets.each { |rrset|
2191
- if rrset.name.match(/^#{mu_name.downcase}\.server\.#{MU.myInstanceId}\.platform-mu/i)
2192
- rrset.resource_records.each { |record|
2193
- MU::Cloud::DNSZone.genericMuDNSEntry(name: mu_name, target: record.value, cloudclass: MU::Cloud::Server, delete: true)
2194
- cleaned_dns = true
2195
- }
2196
- end
2197
- }
2198
- end
1565
+ ips, names = getAddresses(instance, region: region, credentials: credentials)
1566
+ targets = ips +names
2199
1567
 
2200
- if !noop
2201
- if !server_obj.nil? and !server_obj.config.nil?
2202
- MU.mommacat.notify(MU::Cloud::Server.cfg_plural, server_obj.config['name'], {}, mu_name: server_obj.mu_name, remove: true) if MU.mommacat
2203
- end
2204
- end
1568
+ server_obj = MU::MommaCat.findStray(
1569
+ "AWS",
1570
+ "servers",
1571
+ region: region,
1572
+ deploy_id: deploy_id,
1573
+ cloud_id: id,
1574
+ mu_name: mu_name,
1575
+ dummy_ok: true
1576
+ ).first
2205
1577
 
2206
- # If we didn't manage to find this instance's Route53 entry by sifting
2207
- # deployment metadata, see if we can get it with the Name tag.
2208
- if !mu_zone.nil? and !cleaned_dns and !instance.nil?
2209
- instance.tags.each { |tag|
2210
- if tag.key == "Name"
2211
- zone_rrsets.each { |rrset|
2212
- if rrset.name.match(/^#{tag.value.downcase}\.server\.#{MU.myInstanceId}\.platform-mu/i)
2213
- rrset.resource_records.each { |record|
2214
- MU::Cloud::DNSZone.genericMuDNSEntry(name: tag.value, target: record.value, cloudclass: MU::Cloud::Server, delete: true) if !noop
2215
- }
2216
- end
2217
- }
2218
- end
2219
- }
2220
- end
2221
- end
1578
+ if MU::Cloud::AWS.hosted? and !MU::Cloud::AWS.isGovCloud? and server_obj
1579
+ targets.each { |target|
1580
+ MU::Cloud::DNSZone.genericMuDNSEntry(name: server_obj.mu_name, target: target, cloudclass: MU::Cloud::Server, delete: true, noop: noop)
1581
+ }
2222
1582
  end
2223
1583
 
2224
- if ips.size > 0 and !onlycloud
2225
- known_hosts_files = [Etc.getpwuid(Process.uid).dir+"/.ssh/known_hosts"]
2226
- if Etc.getpwuid(Process.uid).name == "root" and !MU.inGem?
2227
- begin
2228
- known_hosts_files << Etc.getpwnam("nagios").dir+"/.ssh/known_hosts"
2229
- rescue ArgumentError
2230
- # we're in a non-nagios environment and that's ok
2231
- end
2232
- end
2233
- known_hosts_files.each { |known_hosts|
2234
- next if !File.exist?(known_hosts)
2235
- MU.log "Cleaning up #{ips} from #{known_hosts}"
2236
- if !noop
2237
- File.open(known_hosts, File::CREAT|File::RDWR, 0644) { |f|
2238
- f.flock(File::LOCK_EX)
2239
- newlines = Array.new
2240
- f.readlines.each { |line|
2241
- ip_match = false
2242
- ips.each { |ip|
2243
- if line.match(/(^|,| )#{ip}( |,)/)
2244
- MU.log "Expunging #{ip} from #{known_hosts}"
2245
- ip_match = true
2246
- end
2247
- }
2248
- newlines << line if !ip_match
2249
- }
2250
- f.rewind
2251
- f.truncate(0)
2252
- f.puts(newlines)
2253
- f.flush
2254
- f.flock(File::LOCK_UN)
2255
- }
2256
- end
1584
+ if targets.size > 0 and !onlycloud
1585
+ MU::Master.removeInstanceFromEtcHosts(server_obj.mu_name) if !noop and server_obj
1586
+ targets.each { |target|
1587
+ next if !target.match(/^\d+\.\d+\.\d+\.\d+$/)
1588
+ MU::Master.removeIPFromSSHKnownHosts(target, noop: noop)
2257
1589
  }
2258
1590
  end
2259
1591
 
2260
- return if instance.nil?
1592
+ on_retry = Proc.new {
1593
+ instance = MU::Cloud::AWS.ec2(credentials: credentials, region: region).describe_instances(instance_ids: [instance.instance_id]).reservations.first.instances.first
1594
+ if instance.state.name == "terminated"
1595
+ MU.log "#{instance.instance_id}#{server_obj ? " ("+server_obj.mu_name+")" : ""} has already been terminated, skipping"
1596
+ MU::MommaCat.unlock(".cleanup-"+id)
1597
+ return
1598
+ end
1599
+ }
2261
1600
 
2262
- name = ""
2263
- instance.tags.each { |tag|
2264
- name = tag.value if tag.key == "Name"
1601
+ loop_if = Proc.new {
1602
+ instance = MU::Cloud::AWS.ec2(credentials: credentials, region: region).describe_instances(instance_ids: [instance.instance_id]).reservations.first.instances.first
1603
+ instance.state.name != "terminated"
2265
1604
  }
2266
1605
 
2267
- if instance.state.name == "terminated"
2268
- MU.log "#{instance.instance_id} (#{name}) has already been terminated, skipping"
2269
- else
2270
- if instance.state.name == "terminating"
2271
- MU.log "#{instance.instance_id} (#{name}) already terminating, waiting"
2272
- elsif instance.state.name != "running" and instance.state.name != "pending" and instance.state.name != "stopping" and instance.state.name != "stopped"
2273
- MU.log "#{instance.instance_id} (#{name}) is in state #{instance.state.name}, waiting"
2274
- else
2275
- MU.log "Terminating #{instance.instance_id} (#{name}) #{noop}"
2276
- if !noop
2277
- begin
2278
- MU::Cloud::AWS.ec2(credentials: credentials, region: region).modify_instance_attribute(
2279
- instance_id: instance.instance_id,
2280
- disable_api_termination: {value: false}
2281
- )
2282
- MU::Cloud::AWS.ec2(credentials: credentials, region: region).terminate_instances(instance_ids: [instance.instance_id])
2283
- # Small race window here with the state changing from under us
2284
- rescue Aws::EC2::Errors::IncorrectInstanceState => e
2285
- resp = MU::Cloud::AWS.ec2(credentials: credentials, region: region).describe_instances(instance_ids: [id])
2286
- if !resp.nil? and !resp.reservations.nil? and !resp.reservations.first.nil?
2287
- instance = resp.reservations.first.instances.first
2288
- if !instance.nil? and instance.state.name != "terminated" and instance.state.name != "terminating"
2289
- sleep 5
2290
- retry
2291
- end
2292
- end
2293
- rescue Aws::EC2::Errors::InternalError => e
2294
- MU.log "Error #{e.inspect} while Terminating instance #{instance.instance_id} (#{name}), retrying", MU::WARN, details: e.inspect
2295
- sleep 5
2296
- retry
2297
- end
2298
- end
2299
- end
2300
- while instance.state.name != "terminated" and !noop
2301
- sleep 30
2302
- instance_response = MU::Cloud::AWS.ec2(credentials: credentials, region: region).describe_instances(instance_ids: [instance.instance_id])
2303
- instance = instance_response.reservations.first.instances.first
2304
- end
2305
- MU.log "#{instance.instance_id} (#{name}) terminated" if !noop
1606
+ MU.log "Terminating #{instance.instance_id}#{server_obj ? " ("+server_obj.mu_name+")" : ""}"
1607
+ if !noop
1608
+ MU.retrier([Aws::EC2::Errors::IncorrectInstanceState, Aws::EC2::Errors::InternalError], wait: 30, max: 60, loop_if: loop_if, on_retry: on_retry) {
1609
+ MU::Cloud::AWS.ec2(credentials: credentials, region: region).modify_instance_attribute(
1610
+ instance_id: instance.instance_id,
1611
+ disable_api_termination: {value: false}
1612
+ )
1613
+ MU::Cloud::AWS.ec2(credentials: credentials, region: region).terminate_instances(instance_ids: [instance.instance_id])
1614
+ }
2306
1615
  end
1616
+
1617
+ MU.log "#{instance.instance_id}#{server_obj ? " ("+server_obj.mu_name+")" : ""} terminated" if !noop
1618
+ begin
1619
+ MU::MommaCat.unlock(".cleanup-"+id)
1620
+ rescue Errno::ENOENT => e
1621
+ MU.log "No lock for terminating instance #{id} due to missing metadata", MU::DEBUG
1622
+ end
1623
+
2307
1624
  end
2308
1625
 
2309
1626
  # Return a BoK-style config hash describing a NAT instance. We use this
@@ -2360,26 +1677,7 @@ module MU
2360
1677
  "type" => "object"
2361
1678
  }
2362
1679
  },
2363
- "ingress_rules" => {
2364
- "items" => {
2365
- "properties" => {
2366
- "sgs" => {
2367
- "type" => "array",
2368
- "items" => {
2369
- "description" => "Other AWS Security Groups; resources that are associated with this group will have this rule applied to their traffic",
2370
- "type" => "string"
2371
- }
2372
- },
2373
- "lbs" => {
2374
- "type" => "array",
2375
- "items" => {
2376
- "description" => "AWS Load Balancers which will have this rule applied to their traffic",
2377
- "type" => "string"
2378
- }
2379
- }
2380
- }
2381
- }
2382
- },
1680
+ "ingress_rules" => MU::Cloud.resourceClass("AWS", "FirewallRule").ingressRuleAddtlSchema,
2383
1681
  "ssh_user" => {
2384
1682
  "type" => "string",
2385
1683
  "default" => "root",
@@ -2447,8 +1745,7 @@ module MU
2447
1745
 
2448
1746
  MU::Cloud.availableClouds.each { |cloud|
2449
1747
  next if cloud == "AWS"
2450
- cloudbase = Object.const_get("MU").const_get("Cloud").const_get(cloud)
2451
- foreign_types = (cloudbase.listInstanceTypes).values.first
1748
+ foreign_types = (MU::Cloud.cloudClass(cloud).listInstanceTypes).values.first
2452
1749
  if foreign_types.size == 1
2453
1750
  foreign_types = foreign_types.values.first
2454
1751
  end
@@ -2479,6 +1776,45 @@ module MU
2479
1776
  size
2480
1777
  end
2481
1778
 
1779
+ # Boilerplate generation of an instance role
1780
+ # @param server [Hash]: The BoK-style config hash for a +Server+ or +ServerPool+
1781
+ # @param configurator [MU::Config]
1782
+ def self.generateStandardRole(server, configurator)
1783
+ role = {
1784
+ "name" => server["name"],
1785
+ "credentials" => server["credentials"],
1786
+ "can_assume" => [
1787
+ {
1788
+ "entity_id" => "ec2.amazonaws.com",
1789
+ "entity_type" => "service"
1790
+ }
1791
+ ],
1792
+ "policies" => [
1793
+ {
1794
+ "name" => "MuSecrets",
1795
+ "permissions" => ["s3:GetObject"],
1796
+ "targets" => [
1797
+ {
1798
+ "identifier" => 'arn:'+(MU::Cloud::AWS.isGovCloud?(server['region']) ? "aws-us-gov" : "aws")+':s3:::'+MU::Cloud::AWS.adminBucketName(server['credentials'])+'/Mu_CA.pem'
1799
+ }
1800
+ ]
1801
+ }
1802
+ ]
1803
+ }
1804
+ if server['iam_policies']
1805
+ role['iam_policies'] = server['iam_policies'].dup
1806
+ end
1807
+ if server['canned_iam_policies']
1808
+ role['import'] = server['canned_iam_policies'].dup
1809
+ end
1810
+ if server['iam_role']
1811
+ # XXX maybe break this down into policies and add those?
1812
+ end
1813
+
1814
+ configurator.insertKitten(role, "roles")
1815
+ MU::Config.addDependency(server, server["name"], "role")
1816
+ end
1817
+
2482
1818
  # Cloud-specific pre-processing of {MU::Config::BasketofKittens::servers}, bare and unvalidated.
2483
1819
  # @param server [Hash]: The resource to process and validate
2484
1820
  # @param configurator [MU::Config]: The overall deployment configurator of which this resource is a member
@@ -2499,43 +1835,7 @@ module MU
2499
1835
  ok = false
2500
1836
  end
2501
1837
  else
2502
- role = {
2503
- "name" => server["name"],
2504
- "credentials" => server["credentials"],
2505
- "can_assume" => [
2506
- {
2507
- "entity_id" => "ec2.amazonaws.com",
2508
- "entity_type" => "service"
2509
- }
2510
- ],
2511
- "policies" => [
2512
- {
2513
- "name" => "MuSecrets",
2514
- "permissions" => ["s3:GetObject"],
2515
- "targets" => [
2516
- {
2517
- "identifier" => 'arn:'+(MU::Cloud::AWS.isGovCloud?(server['region']) ? "aws-us-gov" : "aws")+':s3:::'+MU::Cloud::AWS.adminBucketName(server['credentials'])+'/Mu_CA.pem'
2518
- }
2519
- ]
2520
- }
2521
- ]
2522
- }
2523
- if server['iam_policies']
2524
- role['iam_policies'] = server['iam_policies'].dup
2525
- end
2526
- if server['canned_iam_policies']
2527
- role['import'] = server['canned_iam_policies'].dup
2528
- end
2529
- if server['iam_role']
2530
- # XXX maybe break this down into policies and add those?
2531
- end
2532
-
2533
- configurator.insertKitten(role, "roles")
2534
- server["dependencies"] ||= []
2535
- server["dependencies"] << {
2536
- "type" => "role",
2537
- "name" => server["name"]
2538
- }
1838
+ generateStandardRole(server, configurator)
2539
1839
  end
2540
1840
  if !server['create_image'].nil?
2541
1841
  if server['create_image'].has_key?('copy_to_regions') and
@@ -2547,12 +1847,12 @@ module MU
2547
1847
  end
2548
1848
  end
2549
1849
 
2550
- server['ami_id'] ||= server['image_id']
1850
+ server['image_id'] ||= server['ami_id']
2551
1851
 
2552
- if server['ami_id'].nil?
1852
+ if server['image_id'].nil?
2553
1853
  img_id = MU::Cloud.getStockImage("AWS", platform: server['platform'], region: server['region'])
2554
1854
  if img_id
2555
- server['ami_id'] = configurator.getTail("server"+server['name']+"AMI", value: img_id, prettyname: "server"+server['name']+"AMI", cloudtype: "AWS::EC2::Image::Id")
1855
+ server['image_id'] = configurator.getTail("server"+server['name']+"AMI", value: img_id, prettyname: "server"+server['name']+"AMI", cloudtype: "AWS::EC2::Image::Id")
2556
1856
  else
2557
1857
  MU.log "No AMI specified for #{server['name']} and no default available for platform #{server['platform']} in region #{server['region']}", MU::ERR, details: server
2558
1858
  ok = false
@@ -2561,22 +1861,13 @@ module MU
2561
1861
 
2562
1862
  if !server["loadbalancers"].nil?
2563
1863
  server["loadbalancers"].each { |lb|
2564
- if lb["concurrent_load_balancer"] != nil
2565
- server["dependencies"] << {
2566
- "type" => "loadbalancer",
2567
- "name" => lb["concurrent_load_balancer"]
2568
- }
1864
+ lb["name"] ||= lb["concurrent_load_balancer"]
1865
+ if lb["name"]
1866
+ MU::Config.addDependency(server, lb["name"], "loadbalancer")
2569
1867
  end
2570
1868
  }
2571
1869
  end
2572
1870
 
2573
- if !server["vpc"].nil?
2574
- if server["vpc"]["subnet_name"].nil? and server["vpc"]["subnet_id"].nil? and server["vpc"]["subnet_pref"].nil?
2575
- MU.log "A server VPC block must specify a target subnet", MU::ERR
2576
- ok = false
2577
- end
2578
- end
2579
-
2580
1871
  ok
2581
1872
  end
2582
1873
 
@@ -2599,23 +1890,24 @@ module MU
2599
1890
  # @param volume [OpenStruct]: The cloud provider's description of the volume.
2600
1891
  # @param region [String]: The cloud provider region
2601
1892
  # @return [void]
2602
- def self.delete_volume(volume, noop, skipsnapshots, region: MU.curRegion, credentials: nil)
1893
+ def self.delete_volume(volume, noop, skipsnapshots, region: MU.curRegion, credentials: nil, deploy_id: MU.deploy_id)
2603
1894
  if !volume.nil?
2604
1895
  resp = MU::Cloud::AWS.ec2(region: region, credentials: credentials).describe_volumes(volume_ids: [volume.volume_id])
2605
1896
  volume = resp.data.volumes.first
2606
1897
  end
2607
- name = ""
1898
+ name = nil
2608
1899
  volume.tags.each { |tag|
2609
1900
  name = tag.value if tag.key == "Name"
2610
1901
  }
1902
+ name ||= volume.volume_id
2611
1903
 
2612
1904
  MU.log("Deleting volume #{volume.volume_id} (#{name})")
2613
1905
  if !noop
2614
1906
  if !skipsnapshots
2615
1907
  if !name.nil? and !name.empty?
2616
- desc = "#{MU.deploy_id}-MUfinal (#{name})"
1908
+ desc = "#{deploy_id}-MUfinal (#{name})"
2617
1909
  else
2618
- desc = "#{MU.deploy_id}-MUfinal"
1910
+ desc = "#{deploy_id}-MUfinal"
2619
1911
  end
2620
1912
 
2621
1913
  begin
@@ -2630,31 +1922,414 @@ module MU
2630
1922
  end
2631
1923
  end
2632
1924
 
2633
- retries = 0
2634
1925
  begin
2635
- MU::Cloud::AWS.ec2(region: region, credentials: credentials).delete_volume(volume_id: volume.volume_id)
2636
- rescue Aws::EC2::Errors::IncorrectState => e
2637
- MU.log "Volume #{volume.volume_id} (#{name}) in incorrect state (#{e.message}), will retry", MU::WARN
2638
- sleep 30
2639
- retry
2640
- rescue Aws::EC2::Errors::InvalidVolumeNotFound
2641
- MU.log "Volume #{volume.volume_id} (#{name}) disappeared before I could remove it!", MU::WARN
1926
+ MU.retrier([Aws::EC2::Errors::IncorrectState, Aws::EC2::Errors::VolumeInUse], ignoreme: [Aws::EC2::Errors::InvalidVolumeNotFound], wait: 30, max: 10){
1927
+ MU::Cloud::AWS.ec2(region: region, credentials: credentials).delete_volume(volume_id: volume.volume_id)
1928
+ }
2642
1929
  rescue Aws::EC2::Errors::VolumeInUse
2643
- if retries < 10
2644
- volume.attachments.each { |attachment|
2645
- MU.log "#{volume.volume_id} is attached to #{attachment.instance_id} as #{attachment.device}", MU::NOTICE
2646
- }
2647
- MU.log "Volume '#{name}' is still attached, waiting...", MU::NOTICE
2648
- sleep 30
2649
- retries = retries + 1
2650
- retry
1930
+ MU.log "Failed to delete #{name}", MU::ERR
1931
+ end
1932
+
1933
+ end
1934
+ end
1935
+ private_class_method :delete_volume
1936
+
1937
+ # Given some combination of a base image, BoK-configured storage, and
1938
+ # ephemeral devices, return the structure passed to EC2 to declare
1939
+ # block devicde mappings.
1940
+ # @param image_id [String]
1941
+ # @param storage [Array]
1942
+ # @param add_ephemeral [Boolean]
1943
+ # @param region [String]
1944
+ # @param credentials [String]
1945
+ def self.configureBlockDevices(image_id: nil, storage: nil, add_ephemeral: true, region: MU.myRegion, credentials: nil)
1946
+ ext_disks = {}
1947
+
1948
+ # Figure out which devices are embedded in the AMI already.
1949
+ if image_id
1950
+ image = MU::Cloud::AWS.ec2(region: region, credentials: credentials).describe_images(image_ids: [image_id]).images.first
1951
+ if !image.block_device_mappings.nil?
1952
+ image.block_device_mappings.each { |disk|
1953
+ if !disk.device_name.nil? and !disk.device_name.empty? and !disk.ebs.nil? and !disk.ebs.empty?
1954
+ ext_disks[disk.device_name] = MU.structToHash(disk.ebs)
1955
+ end
1956
+ }
1957
+ end
1958
+ end
1959
+
1960
+ configured_storage = []
1961
+ if storage
1962
+ storage.each { |vol|
1963
+ # Drop the "encrypted" flag if a snapshot for this device exists
1964
+ # in the AMI, even if they both agree about the value of said
1965
+ # flag. Apparently that's a thing now.
1966
+ if ext_disks.has_key?(vol["device"])
1967
+ if ext_disks[vol["device"]].has_key?(:snapshot_id)
1968
+ vol.delete("encrypted")
1969
+ end
1970
+ end
1971
+ mapping, _cfm_mapping = MU::Cloud::AWS::Server.convertBlockDeviceMapping(vol)
1972
+ configured_storage << mapping
1973
+ }
1974
+ end
1975
+
1976
+ configured_storage.concat(@ephemeral_mappings) if add_ephemeral
1977
+
1978
+ configured_storage
1979
+ end
1980
+
1981
+ # Return all of the IP addresses, public and private, from all of our
1982
+ # network interfaces.
1983
+ # @return [Array<String>]
1984
+ def listIPs
1985
+ MU::Cloud::AWS::Server.getAddresses(cloud_desc).first
1986
+ end
1987
+
1988
+ private
1989
+
1990
+ def bootstrapGroomer
1991
+ if (@config['groom'].nil? or @config['groom']) and !@groomer.haveBootstrapped?
1992
+ MU.retrier([BootstrapTempFail], wait: 45) {
1993
+ if windows?
1994
+ # kick off certificate generation early; WinRM will need it
1995
+ @deploy.nodeSSLCerts(self)
1996
+ @deploy.nodeSSLCerts(self, true) if @config.has_key?("basis")
1997
+ session = getWinRMSession(50, 60, reboot_on_problems: true)
1998
+ initialWinRMTasks(session)
1999
+ begin
2000
+ session.close
2001
+ rescue StandardError
2002
+ # session.close is allowed to fail- we're probably rebooting
2003
+ end
2651
2004
  else
2652
- MU.log "Failed to delete #{name}", MU::ERR
2005
+ session = getSSHSession(40, 30)
2006
+ initialSSHTasks(session)
2007
+ end
2008
+ }
2009
+ end
2010
+
2011
+ # See if this node already exists in our config management. If it
2012
+ # does, we're done.
2013
+
2014
+ if MU.inGem?
2015
+ MU.log "Deploying from a gem, not grooming"
2016
+ elsif @config['groom'].nil? or @config['groom']
2017
+ if @groomer.haveBootstrapped?
2018
+ MU.log "Node #{@mu_name} has already been bootstrapped, skipping groomer setup.", MU::NOTICE
2019
+ else
2020
+ begin
2021
+ @groomer.bootstrap
2022
+ rescue MU::Groomer::RunError
2023
+ return false
2653
2024
  end
2654
2025
  end
2026
+ @groomer.saveDeployData
2027
+ end
2028
+
2029
+ true
2030
+ end
2031
+
2032
+ def saveCredentials(win_admin_password = nil)
2033
+ ec2config_password = nil
2034
+ sshd_password = nil
2035
+ if windows?
2036
+ if @config['use_cloud_provider_windows_password']
2037
+ win_admin_password ||= getWindowsAdminPassword
2038
+ elsif @config['windows_auth_vault'] and !@config['windows_auth_vault'].empty?
2039
+ if @config["windows_auth_vault"].has_key?("password_field")
2040
+ win_admin_password ||= @groomer.getSecret(
2041
+ vault: @config['windows_auth_vault']['vault'],
2042
+ item: @config['windows_auth_vault']['item'],
2043
+ field: @config["windows_auth_vault"]["password_field"]
2044
+ )
2045
+ else
2046
+ win_admin_password ||= getWindowsAdminPassword
2047
+ end
2048
+
2049
+ if @config["windows_auth_vault"].has_key?("ec2config_password_field")
2050
+ ec2config_password = @groomer.getSecret(
2051
+ vault: @config['windows_auth_vault']['vault'],
2052
+ item: @config['windows_auth_vault']['item'],
2053
+ field: @config["windows_auth_vault"]["ec2config_password_field"]
2054
+ )
2055
+ end
2056
+
2057
+ if @config["windows_auth_vault"].has_key?("sshd_password_field")
2058
+ sshd_password = @groomer.getSecret(
2059
+ vault: @config['windows_auth_vault']['vault'],
2060
+ item: @config['windows_auth_vault']['item'],
2061
+ field: @config["windows_auth_vault"]["sshd_password_field"]
2062
+ )
2063
+ end
2064
+ end
2065
+
2066
+ win_admin_password ||= MU.generateWindowsPassword
2067
+ ec2config_password ||= MU.generateWindowsPassword
2068
+ sshd_password ||= MU.generateWindowsPassword
2069
+
2070
+ # We're creating the vault here so when we run
2071
+ # MU::Cloud::Server.initialSSHTasks and we need to set the Windows
2072
+ # Admin password we can grab it from said vault.
2073
+ creds = {
2074
+ "username" => @config['windows_admin_username'],
2075
+ "password" => win_admin_password,
2076
+ "ec2config_username" => "ec2config",
2077
+ "ec2config_password" => ec2config_password,
2078
+ "sshd_username" => "sshd_service",
2079
+ "sshd_password" => sshd_password
2080
+ }
2081
+ @groomer.saveSecret(vault: @mu_name, item: "windows_credentials", data: creds, permissions: "name:#{@mu_name}")
2082
+ end
2083
+ end
2084
+
2085
+ def haveElasticIP?
2086
+ if !cloud_desc.public_ip_address.nil?
2087
+ begin
2088
+ resp = MU::Cloud::AWS.ec2(region: @config['region'], credentials: @config['credentials']).describe_addresses(public_ips: [cloud_desc.public_ip_address])
2089
+ if resp.addresses.size > 0 and resp.addresses.first.instance_id == @cloud_id
2090
+ return true
2091
+ end
2092
+ rescue Aws::EC2::Errors::InvalidAddressNotFound
2093
+ # XXX this is ok to ignore, it means the public IP isn't Elastic
2094
+ end
2095
+ end
2096
+
2097
+ false
2098
+ end
2099
+
2100
+ def configureNetworking
2101
+ if !@config['static_ip'].nil?
2102
+ if !@config['static_ip']['ip'].nil?
2103
+ MU::Cloud::AWS::Server.associateElasticIp(@cloud_id, classic: @vpc.nil?, ip: @config['static_ip']['ip'])
2104
+ elsif !haveElasticIP?
2105
+ MU::Cloud::AWS::Server.associateElasticIp(@cloud_id, classic: @vpc.nil?)
2106
+ end
2107
+ end
2108
+
2109
+ if !@vpc.nil? and @config.has_key?("vpc")
2110
+ subnet = @vpc.getSubnet(cloud_id: cloud_desc.subnet_id)
2111
+
2112
+ _nat_ssh_key, _nat_ssh_user, nat_ssh_host, _canonical_ip, _ssh_user, _ssh_key_name = getSSHConfig
2113
+ if subnet.private? and !nat_ssh_host and !MU::Cloud.resourceClass("AWS", "VPC").haveRouteToInstance?(cloud_desc, region: @config['region'], credentials: @config['credentials'])
2114
+ raise MuError, "#{@mu_name} is in a private subnet (#{subnet}), but has no bastion host configured, and I have no other route to it"
2115
+ end
2116
+
2117
+ # If we've asked for additional subnets (and this @config is not a
2118
+ # member of a Server Pool, which has different semantics), create
2119
+ # extra interfaces to accomodate.
2120
+ if !@config['vpc']['subnets'].nil? and @config['basis'].nil?
2121
+ device_index = 1
2122
+ mySubnets.each { |s|
2123
+ next if s.cloud_id == cloud_desc.subnet_id
2124
+
2125
+ if cloud_desc.placement.availability_zone != s.az
2126
+ MU.log "Cannot create interface in subnet #{s.to_s} for #{@mu_name} due to AZ mismatch", MU::WARN
2127
+ next
2128
+ end
2129
+ MU.log "Adding network interface on subnet #{s.cloud_id} for #{@mu_name}"
2130
+ iface = MU::Cloud::AWS.ec2(region: @config['region'], credentials: @config['credentials']).create_network_interface(subnet_id: s.cloud_id).network_interface
2131
+ MU::Cloud::AWS.createStandardTags(
2132
+ iface.network_interface_id,
2133
+ region: @config['region'],
2134
+ credentials: @config['credentials'],
2135
+ optional: @config['optional_tags'],
2136
+ nametag: @mu_name+"-ETH"+device_index.to_s,
2137
+ othertags: @config['tags']
2138
+ )
2139
+
2140
+ MU::Cloud::AWS.ec2(region: @config['region'], credentials: @config['credentials']).attach_network_interface(
2141
+ network_interface_id: iface.network_interface_id,
2142
+ instance_id: cloud_desc.instance_id,
2143
+ device_index: device_index
2144
+ )
2145
+ device_index = device_index + 1
2146
+ }
2147
+ cloud_desc(use_cache: false)
2148
+ end
2149
+ end
2150
+
2151
+ [:private_dns_name, :public_dns_name, :private_ip_address, :public_ip_address].each { |field|
2152
+ @config[field.to_s] = cloud_desc.send(field)
2153
+ }
2154
+
2155
+ if !@config['add_private_ips'].nil?
2156
+ cloud_desc.network_interfaces.each { |int|
2157
+ if int.private_ip_address == cloud_desc.private_ip_address and int.private_ip_addresses.size < (@config['add_private_ips'] + 1)
2158
+ MU.log "Adding #{@config['add_private_ips']} extra private IP addresses to #{cloud_desc.instance_id}"
2159
+ MU::Cloud::AWS.ec2(region: @config['region'], credentials: @config['credentials']).assign_private_ip_addresses(
2160
+ network_interface_id: int.network_interface_id,
2161
+ secondary_private_ip_address_count: @config['add_private_ips'],
2162
+ allow_reassignment: false
2163
+ )
2164
+ end
2165
+ }
2166
+ end
2167
+ end
2168
+
2169
+ def tagVolumes
2170
+ volumes = MU::Cloud::AWS.ec2(region: @config['region'], credentials: @config['credentials']).describe_volumes(filters: [name: "attachment.instance-id", values: [@cloud_id]])
2171
+ volumes.each { |vol|
2172
+ vol.volumes.each { |volume|
2173
+ volume.attachments.each { |attachment|
2174
+ MU::Cloud::AWS.createStandardTags(
2175
+ attachment.volume_id,
2176
+ region: @config['region'],
2177
+ credentials: @config['credentials'],
2178
+ optional: @config['optional_tags'],
2179
+ nametag: ["/dev/sda", "/dev/sda1"].include?(attachment.device) ? "ROOT-"+@mu_name : @mu_name+"-"+attachment.device.upcase,
2180
+ othertags: @config['tags']
2181
+ )
2182
+
2183
+ }
2184
+ }
2185
+ }
2186
+ end
2187
+
2188
+ # If we came up via AutoScale, the Alarm module won't have had our
2189
+ # instance ID to associate us with itself. So invoke that here.
2190
+ # XXX might be possible to do this with regular alarm resources and
2191
+ # dependencies now
2192
+ def setAlarms
2193
+ if !@config['basis'].nil? and @config["alarms"] and !@config["alarms"].empty?
2194
+ @config["alarms"].each { |alarm|
2195
+ alarm_obj = MU::MommaCat.findStray(
2196
+ "AWS",
2197
+ "alarms",
2198
+ region: @config["region"],
2199
+ deploy_id: @deploy.deploy_id,
2200
+ name: alarm['name']
2201
+ ).first
2202
+ alarm["dimensions"] = [{:name => "InstanceId", :value => @cloud_id}]
2203
+
2204
+ if alarm["enable_notifications"]
2205
+ # XXX vile, this should be a sibling resource generated by the
2206
+ # parser
2207
+ topic_arn = MU::Cloud.resourceClass("AWS", "Notification").createTopic(alarm["notification_group"], region: @config["region"], credentials: @config['credentials'])
2208
+ MU::Cloud.resourceClass("AWS", "Notification").subscribe(topic_arn, alarm["notification_endpoint"], alarm["notification_type"], region: @config["region"], credentials: @config["credentials"])
2209
+ alarm["alarm_actions"] = [topic_arn]
2210
+ alarm["ok_actions"] = [topic_arn]
2211
+ end
2212
+
2213
+ alarm_name = alarm_obj ? alarm_obj.cloud_id : "#{@mu_name}-#{alarm['name']}".upcase
2214
+
2215
+ MU::Cloud.resourceClass("AWS", "Alarm").setAlarm(
2216
+ name: alarm_name,
2217
+ ok_actions: alarm["ok_actions"],
2218
+ alarm_actions: alarm["alarm_actions"],
2219
+ insufficient_data_actions: alarm["no_data_actions"],
2220
+ metric_name: alarm["metric_name"],
2221
+ namespace: alarm["namespace"],
2222
+ statistic: alarm["statistic"],
2223
+ dimensions: alarm["dimensions"],
2224
+ period: alarm["period"],
2225
+ unit: alarm["unit"],
2226
+ evaluation_periods: alarm["evaluation_periods"],
2227
+ threshold: alarm["threshold"],
2228
+ comparison_operator: alarm["comparison_operator"],
2229
+ region: @config["region"],
2230
+ credentials: @config['credentials']
2231
+ )
2232
+ }
2233
+ end
2234
+ end
2235
+
2236
+ # We have issues sometimes where our dns_records are pointing at the wrong node name and IP address.
2237
+
2238
+ def getIAMProfile
2239
+ arn = if @config['generate_iam_role']
2240
+ role = @deploy.findLitterMate(name: @config['name'], type: "roles")
2241
+ 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|
2242
+ 'arn:'+(MU::Cloud::AWS.isGovCloud?(@config['region']) ? "aws-us-gov" : "aws")+':s3:::'+MU::Cloud::AWS.adminBucketName(@credentials)+'/'+file
2243
+ }
2244
+ MU.log "Adding S3 read permissions to #{@mu_name}'s IAM profile", MU::NOTICE, details: s3_objs
2245
+ role.cloudobj.injectPolicyTargets("MuSecrets", s3_objs)
2246
+
2247
+ @config['iam_role'] = role.mu_name
2248
+ role.cloudobj.createInstanceProfile
2249
+
2250
+ elsif @config['iam_role'].nil?
2251
+ raise MuError, "#{@mu_name} has generate_iam_role set to false, but no iam_role assigned."
2252
+ end
2253
+
2254
+ if !@config["iam_role"].nil?
2255
+ if arn
2256
+ return {arn: arn}
2257
+ else
2258
+ return {name: @config["iam_role"]}
2259
+ end
2260
+ end
2261
+
2262
+ nil
2263
+ end
2264
+
2265
+ def setDeleteOntermination(device, delete_on_termination = false)
2266
+ mappings = MU.structToHash(cloud_desc.block_device_mappings)
2267
+ mappings.each { |vol|
2268
+ if vol[:ebs]
2269
+ vol[:ebs].delete(:attach_time)
2270
+ vol[:ebs].delete(:status)
2271
+ end
2272
+ if vol[:device_name] == device
2273
+ if vol[:ebs][:delete_on_termination] != delete_on_termination
2274
+ vol[:ebs][:delete_on_termination] = delete_on_termination
2275
+ MU.log "Setting delete_on_termination flag to #{delete_on_termination.to_s} on #{@mu_name}'s #{dev}"
2276
+ MU::Cloud::AWS.ec2(region: @config['region'], credentials: @config['credentials']).modify_instance_attribute(
2277
+ instance_id: @cloud_id,
2278
+ block_device_mappings: mappings
2279
+ )
2280
+ end
2281
+ return true
2282
+ end
2283
+ }
2284
+
2285
+ false
2286
+ end
2287
+
2288
+ def createImage
2289
+ img_cfg = @config['create_image']
2290
+ # Scrub things that don't belong on an AMI
2291
+ session = windows? ? getWinRMSession : getSSHSession
2292
+ sudo = purgecmd = ""
2293
+ sudo = "sudo" if @config['ssh_user'] != "root"
2294
+ if windows?
2295
+ purgecmd = "rm -rf /cygdrive/c/mu_installed_chef"
2296
+ else
2297
+ purgecmd = "rm -rf /opt/mu_installed_chef"
2298
+ end
2299
+ if img_cfg['image_then_destroy']
2300
+ if windows?
2301
+ purgecmd = "rm -rf /cygdrive/c/chef/ /home/#{@config['windows_admin_username']}/.ssh/authorized_keys /home/Administrator/.ssh/authorized_keys /cygdrive/c/mu-installer-ran-updates /cygdrive/c/mu_installed_chef"
2302
+ # session.exec!("powershell -Command \"& {(Get-WmiObject -Class Win32_Product -Filter \"Name='UniversalForwarder'\").Uninstall()}\"")
2303
+ else
2304
+ purgecmd = "#{sudo} rm -rf /var/lib/cloud/instances/i-* /root/.ssh/authorized_keys /etc/ssh/ssh_host_*key* /etc/chef /etc/opscode/* /.mu-installer-ran-updates /var/chef /opt/mu_installed_chef /opt/chef ; #{sudo} sed -i 's/^HOSTNAME=.*//' /etc/sysconfig/network"
2305
+ end
2306
+ end
2307
+ if windows?
2308
+ session.run(purgecmd)
2309
+ else
2310
+ session.exec!(purgecmd)
2311
+ end
2312
+ session.close
2313
+ ami_ids = MU::Cloud::AWS::Server.createImage(
2314
+ name: @mu_name,
2315
+ instance_id: @cloud_id,
2316
+ storage: @config['storage'],
2317
+ exclude_storage: img_cfg['image_exclude_storage'],
2318
+ copy_to_regions: img_cfg['copy_to_regions'],
2319
+ make_public: img_cfg['public'],
2320
+ region: @config['region'],
2321
+ tags: @config['tags'],
2322
+ credentials: @config['credentials']
2323
+ )
2324
+ @deploy.notify("images", @config['name'], ami_ids)
2325
+ @config['image_created'] = true
2326
+ if img_cfg['image_then_destroy']
2327
+ MU::Cloud::AWS::Server.waitForAMI(ami_ids[@config['region']], region: @config['region'], credentials: @config['credentials'])
2328
+ MU.log "AMI #{ami_ids[@config['region']]} ready, removing source node #{@mu_name}"
2329
+ MU::Cloud::AWS::Server.terminateInstance(id: @cloud_id, region: @config['region'], deploy_id: @deploy.deploy_id, mu_name: @mu_name, credentials: @config['credentials'])
2330
+ destroy
2655
2331
  end
2656
2332
  end
2657
- private_class_method :delete_volume
2658
2333
 
2659
2334
  end #class
2660
2335
  end #class