cloud-mu 3.1.4 → 3.3.1

Sign up to get free protection for your applications and to get access to all the features.
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