cloud-mu 3.1.5 → 3.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +5 -1
  3. data/ansible/roles/mu-windows/files/LaunchConfig.json +9 -0
  4. data/ansible/roles/mu-windows/files/config.xml +76 -0
  5. data/ansible/roles/mu-windows/tasks/main.yml +16 -0
  6. data/bin/mu-adopt +2 -1
  7. data/bin/mu-configure +16 -0
  8. data/bin/mu-node-manage +15 -16
  9. data/cloud-mu.gemspec +2 -2
  10. data/cookbooks/mu-activedirectory/resources/domain.rb +4 -4
  11. data/cookbooks/mu-activedirectory/resources/domain_controller.rb +4 -4
  12. data/cookbooks/mu-tools/recipes/eks.rb +2 -2
  13. data/cookbooks/mu-tools/recipes/windows-client.rb +25 -22
  14. data/extras/clean-stock-amis +25 -19
  15. data/extras/image-generators/AWS/win2k12.yaml +2 -0
  16. data/extras/image-generators/AWS/win2k16.yaml +2 -0
  17. data/extras/image-generators/AWS/win2k19.yaml +2 -0
  18. data/modules/mommacat.ru +1 -1
  19. data/modules/mu.rb +6 -5
  20. data/modules/mu/adoption.rb +19 -4
  21. data/modules/mu/cleanup.rb +181 -293
  22. data/modules/mu/cloud.rb +58 -17
  23. data/modules/mu/clouds/aws.rb +36 -1
  24. data/modules/mu/clouds/aws/container_cluster.rb +30 -21
  25. data/modules/mu/clouds/aws/role.rb +1 -1
  26. data/modules/mu/clouds/aws/vpc.rb +5 -1
  27. data/modules/mu/clouds/azure.rb +10 -0
  28. data/modules/mu/clouds/cloudformation.rb +10 -0
  29. data/modules/mu/clouds/google.rb +18 -4
  30. data/modules/mu/clouds/google/bucket.rb +2 -2
  31. data/modules/mu/clouds/google/container_cluster.rb +10 -7
  32. data/modules/mu/clouds/google/database.rb +3 -3
  33. data/modules/mu/clouds/google/firewall_rule.rb +3 -3
  34. data/modules/mu/clouds/google/function.rb +3 -3
  35. data/modules/mu/clouds/google/loadbalancer.rb +4 -4
  36. data/modules/mu/clouds/google/role.rb +18 -9
  37. data/modules/mu/clouds/google/server.rb +16 -14
  38. data/modules/mu/clouds/google/server_pool.rb +4 -4
  39. data/modules/mu/clouds/google/user.rb +2 -2
  40. data/modules/mu/clouds/google/vpc.rb +9 -13
  41. data/modules/mu/config.rb +1 -1
  42. data/modules/mu/config/container_cluster.rb +5 -0
  43. data/modules/mu/config/doc_helpers.rb +1 -1
  44. data/modules/mu/config/ref.rb +12 -6
  45. data/modules/mu/config/schema_helpers.rb +8 -3
  46. data/modules/mu/config/server.rb +7 -0
  47. data/modules/mu/config/tail.rb +1 -0
  48. data/modules/mu/config/vpc.rb +15 -7
  49. data/modules/mu/config/vpc.yml +0 -1
  50. data/modules/mu/defaults/AWS.yaml +48 -48
  51. data/modules/mu/deploy.rb +1 -1
  52. data/modules/mu/groomer.rb +1 -1
  53. data/modules/mu/groomers/ansible.rb +69 -4
  54. data/modules/mu/groomers/chef.rb +48 -4
  55. data/modules/mu/master.rb +75 -3
  56. data/modules/mu/mommacat.rb +104 -855
  57. data/modules/mu/mommacat/naming.rb +28 -0
  58. data/modules/mu/mommacat/search.rb +463 -0
  59. data/modules/mu/mommacat/storage.rb +185 -183
  60. data/modules/tests/super_simple_bok.yml +1 -3
  61. metadata +8 -5
@@ -49,7 +49,7 @@ module MU
49
49
  generic_instance_methods = [:create, :notify, :mu_name, :cloud_id, :config]
50
50
 
51
51
  # Class methods which the base of a cloud implementation must implement
52
- generic_class_methods_toplevel = [:required_instance_methods, :myRegion, :listRegions, :listAZs, :hosted?, :hosted_config, :config_example, :writeDeploySecret, :listCredentials, :credConfig, :listInstanceTypes, :adminBucketName, :adminBucketUrl, :habitat]
52
+ generic_class_methods_toplevel = [:required_instance_methods, :myRegion, :listRegions, :listAZs, :hosted?, :hosted_config, :config_example, :writeDeploySecret, :listCredentials, :credConfig, :listInstanceTypes, :adminBucketName, :adminBucketUrl, :listHabitats, :habitat, :virtual?]
53
53
 
54
54
  # Public attributes which will be available on all instantiated cloud resource objects
55
55
  #
@@ -529,7 +529,7 @@ module MU
529
529
  images.deep_merge!(YAML.load(response))
530
530
  break
531
531
  end
532
- rescue StandardError
532
+ rescue StandardError => e
533
533
  if fail_hard
534
534
  raise MuError, "Failed to fetch stock images from #{base_url}/#{cloud}.yaml (#{e.message})"
535
535
  else
@@ -644,9 +644,16 @@ module MU
644
644
 
645
645
  # Shorthand lookup for resource type names. Given any of the shorthand class name, configuration name (singular or plural), or full class name, return all four as a set.
646
646
  # @param type [String]: A string that looks like our short or full class name or singular or plural configuration names.
647
+ # @param assert [Boolean]: Raise an exception if the type isn't valid
647
648
  # @return [Array]: Class name (Symbol), singular config name (String), plural config name (String), full class name (Object)
648
- def self.getResourceNames(type)
649
- return [nil, nil, nil, nil, {}] if !type
649
+ def self.getResourceNames(type, assert = true)
650
+ if !type
651
+ if assert
652
+ raise MuError, "nil resource type requested in getResourceNames"
653
+ else
654
+ return [nil, nil, nil, nil, {}]
655
+ end
656
+ end
650
657
  @@resource_types.each_pair { |name, cloudclass|
651
658
  if name == type.to_sym or
652
659
  cloudclass[:cfg_name] == type or
@@ -656,6 +663,10 @@ module MU
656
663
  return [type.to_sym, cloudclass[:cfg_name], cloudclass[:cfg_plural], Object.const_get("MU").const_get("Cloud").const_get(name), cloudclass]
657
664
  end
658
665
  }
666
+ if assert
667
+ raise MuError, "Invalid resource type #{type} requested in getResourceNames"
668
+ end
669
+
659
670
  [nil, nil, nil, nil, {}]
660
671
  end
661
672
 
@@ -684,6 +695,14 @@ module MU
684
695
  @@supportedCloudList
685
696
  end
686
697
 
698
+ # Raise an exception if the cloud provider specified isn't valid
699
+ def self.assertSupportedCloud(cloud)
700
+ if cloud.nil? or !supportedClouds.include?(cloud.to_s)
701
+ raise MuError, "Cloud provider #{cloud} is not supported"
702
+ end
703
+ Object.const_get("MU").const_get("Cloud").const_get(cloud.to_s)
704
+ end
705
+
687
706
  # List of known/supported Cloud providers for which we have at least one
688
707
  # set of credentials configured.
689
708
  # @return [Array<String>]
@@ -701,6 +720,14 @@ module MU
701
720
  available
702
721
  end
703
722
 
723
+ # Raise an exception if the cloud provider specified isn't valid or we
724
+ # don't have any credentials configured for it.
725
+ def self.assertAvailableCloud(cloud)
726
+ if cloud.nil? or availableClouds.include?(cloud.to_s)
727
+ raise MuError, "Cloud provider #{cloud} is not available"
728
+ end
729
+ end
730
+
704
731
  # Load the container class for each cloud we know about, and inject autoload
705
732
  # code for each of its supported resource type classes.
706
733
  failed = []
@@ -823,20 +850,20 @@ module MU
823
850
  @cloud_class_cache[cloud] = {} if !@cloud_class_cache.has_key?(cloud)
824
851
  begin
825
852
  cloudclass = Object.const_get("MU").const_get("Cloud").const_get(cloud)
826
- myclass = Object.const_get("MU").const_get("Cloud").const_get(cloud).const_get(type)
827
- @@resource_types[type.to_sym][:class].each { |class_method|
853
+ myclass = Object.const_get("MU").const_get("Cloud").const_get(cloud).const_get(shortclass)
854
+ @@resource_types[shortclass.to_sym][:class].each { |class_method|
828
855
  if !myclass.respond_to?(class_method) or myclass.method(class_method).owner.to_s != "#<Class:#{myclass}>"
829
- raise MuError, "MU::Cloud::#{cloud}::#{type} has not implemented required class method #{class_method}"
856
+ raise MuError, "MU::Cloud::#{cloud}::#{shortclass} has not implemented required class method #{class_method}"
830
857
  end
831
858
  }
832
- @@resource_types[type.to_sym][:instance].each { |instance_method|
859
+ @@resource_types[shortclass.to_sym][:instance].each { |instance_method|
833
860
  if !myclass.public_instance_methods.include?(instance_method)
834
- raise MuCloudResourceNotImplemented, "MU::Cloud::#{cloud}::#{type} has not implemented required instance method #{instance_method}"
861
+ raise MuCloudResourceNotImplemented, "MU::Cloud::#{cloud}::#{shortclass} has not implemented required instance method #{instance_method}"
835
862
  end
836
863
  }
837
864
  cloudclass.required_instance_methods.each { |instance_method|
838
865
  if !myclass.public_instance_methods.include?(instance_method)
839
- MU.log "MU::Cloud::#{cloud}::#{type} has not implemented required instance method #{instance_method}, will declare as attr_accessor", MU::DEBUG
866
+ MU.log "MU::Cloud::#{cloud}::#{shortclass} has not implemented required instance method #{instance_method}, will declare as attr_accessor", MU::DEBUG
840
867
  end
841
868
  }
842
869
 
@@ -844,7 +871,7 @@ module MU
844
871
  return myclass
845
872
  rescue NameError => e
846
873
  @cloud_class_cache[cloud][type] = nil
847
- raise MuCloudResourceNotImplemented, "The '#{type}' resource is not supported in cloud #{cloud} (tried MU::#{cloud}::#{type})", e.backtrace
874
+ raise MuCloudResourceNotImplemented, "The '#{type}' resource is not supported in cloud #{cloud} (tried MU::Cloud::#{cloud}::#{shortclass})", e.backtrace
848
875
  end
849
876
  end
850
877
 
@@ -867,6 +894,8 @@ module MU
867
894
  Object.const_get("MU").const_get("Cloud").const_get(name).class_eval {
868
895
  attr_reader :cloudclass
869
896
  attr_reader :cloudobj
897
+ attr_reader :credentials
898
+ attr_reader :config
870
899
  attr_reader :destroyed
871
900
  attr_reader :delayed_save
872
901
 
@@ -921,14 +950,27 @@ module MU
921
950
  # @return [String]: Our new +deploy_id+
922
951
  def intoDeploy(mommacat, force: false)
923
952
  if force or (!@deploy)
924
- MU.log "Inserting #{self} (#{self.object_id}) into #{mommacat.deploy_id}", MU::DEBUG
953
+ MU.log "Inserting #{self} [#{self.object_id}] into #{mommacat.deploy_id} as a #{@config['name']}", MU::DEBUG
954
+
925
955
  @deploy = mommacat
956
+ @deploy.addKitten(@cloudclass.cfg_plural, @config['name'], self)
926
957
  @deploy_id = @deploy.deploy_id
927
958
  @cloudobj.intoDeploy(mommacat, force: force) if @cloudobj
928
959
  end
929
960
  @deploy_id
930
961
  end
931
962
 
963
+ # Return the +virtual_name+ config field, if it is set.
964
+ # @param name [String]: If set, will only return a value if +virtual_name+ matches this string
965
+ # @return [String,nil]
966
+ def virtual_name(name = nil)
967
+ if @config and @config['virtual_name'] and
968
+ (!name or name == @config['virtual_name'])
969
+ return @config['virtual_name']
970
+ end
971
+ nil
972
+ end
973
+
932
974
  # @param mommacat [MU::MommaCat]: The deployment containing this cloud resource
933
975
  # @param mu_name [String]: Optional- specify the full Mu resource name of an existing resource to load, instead of creating a new one
934
976
  # @param cloud_id [String]: Optional- specify the cloud provider's identifier for an existing resource to load, instead of creating a new one
@@ -955,7 +997,6 @@ module MU
955
997
  if my_cloud.nil? or !MU::Cloud.supportedClouds.include?(my_cloud)
956
998
  raise MuError, "Can't instantiate a MU::Cloud object without a valid cloud (saw '#{my_cloud}')"
957
999
  end
958
-
959
1000
  @cloudclass = MU::Cloud.loadCloudType(my_cloud, self.class.shortname)
960
1001
  @cloudparentclass = Object.const_get("MU").const_get("Cloud").const_get(my_cloud)
961
1002
  @cloudobj = @cloudclass.new(
@@ -981,7 +1022,6 @@ module MU
981
1022
  MU.log "#{self} in #{@deploy.deploy_id} didn't generate a mu_name after being loaded/initialized, dependencies on this resource will probably be confused!", MU::ERR, details: [caller, args.keys]
982
1023
  end
983
1024
 
984
-
985
1025
  # We are actually a child object invoking this via super() from its
986
1026
  # own initialize(), so initialize all the attributes and instance
987
1027
  # variables we know to be universal.
@@ -2021,17 +2061,18 @@ puts "CHOOSING #{@vpc.to_s} 'cause it has #{@config['vpc']['subnet_name']}"
2021
2061
  loglevel = retries > 4 ? MU::NOTICE : MU::DEBUG
2022
2062
  MU.log "Calling WinRM on #{@mu_name}", loglevel, details: opts
2023
2063
  opts = {
2024
- endpoint: 'https://'+@mu_name+':5986/wsman',
2025
2064
  retry_limit: winrm_retries,
2026
2065
  no_ssl_peer_verification: true, # XXX this should not be necessary; we get 'hostname "foo" does not match the server certificate' even when it clearly does match
2027
2066
  ca_trust_path: "#{MU.mySSLDir}/Mu_CA.pem",
2028
2067
  transport: :ssl,
2029
2068
  operation_timeout: timeout,
2030
2069
  }
2031
- if retries % 2 == 0
2070
+ if retries % 2 == 0 # NTLM password over https
2071
+ opts[:endpoint] = 'https://'+canonical_ip+':5986/wsman'
2032
2072
  opts[:user] = @config['windows_admin_username']
2033
2073
  opts[:password] = getWindowsAdminPassword
2034
- else
2074
+ else # certificate auth over https
2075
+ opts[:endpoint] = 'https://'+@mu_name+':5986/wsman'
2035
2076
  opts[:client_cert] = "#{MU.mySSLDir}/#{@mu_name}-winrm.crt"
2036
2077
  opts[:client_key] = "#{MU.mySSLDir}/#{@mu_name}-winrm.key"
2037
2078
  end
@@ -33,6 +33,18 @@ module MU
33
33
  module AdditionalResourceMethods
34
34
  end
35
35
 
36
+ # Is this a "real" cloud provider, or a stub like CloudFormation?
37
+ def self.virtual?
38
+ false
39
+ end
40
+
41
+ # List all AWS projects available to our credentials
42
+ def self.listHabitats(credentials = nil)
43
+ cfg = credConfig(credentials)
44
+ return [] if !cfg or !cfg['account_number']
45
+ [cfg['account_number']]
46
+ end
47
+
36
48
  # A hook that is always called just before any of the instance method of
37
49
  # our resource implementations gets invoked, so that we can ensure that
38
50
  # repetitive setup tasks (like resolving +:resource_group+ for Azure
@@ -344,6 +356,27 @@ end
344
356
  # etc)
345
357
  # @param deploy_id [MU::MommaCat]
346
358
  def self.cleanDeploy(deploy_id, credentials: nil, noop: false)
359
+
360
+ if !noop
361
+ MU.log "Deleting s3://#{adminBucketName(credentials)}/#{deploy_id}-secret"
362
+ MU::Cloud::AWS.s3(credentials: credentials).delete_object(
363
+ bucket: adminBucketName(credentials),
364
+ key: "#{deploy_id}-secret"
365
+ )
366
+ listRegions(credentials: credentials).each { |r|
367
+ resp = MU::Cloud::AWS.ec2(region: r, credentials: credentials).describe_key_pairs(
368
+ filters: [{name: "key-name", values: ["deploy-#{MU.deploy_id}"]}]
369
+ )
370
+ resp.data.key_pairs.each { |keypair|
371
+ MU.log "Deleting key pair #{keypair.key_name} from #{r}"
372
+ MU::Cloud::AWS.ec2(region: r, credentials: credentials).delete_key_pair(key_name: keypair.key_name) if !noop
373
+ }
374
+ }
375
+
376
+ end
377
+ if hosted?
378
+ MU::Cloud::AWS.openFirewallForClients
379
+ end
347
380
  end
348
381
 
349
382
  # Plant a Mu deploy secret into a storage bucket somewhere for so our kittens can consume it
@@ -1394,7 +1427,7 @@ end
1394
1427
  # Create an AWS API client
1395
1428
  # @param region [String]: Amazon region so we know what endpoint to use
1396
1429
  # @param api [String]: Which API are we wrapping?
1397
- def initialize(region: MU.curRegion, api: "EC2", credentials: nil)
1430
+ def initialize(region: nil, api: "EC2", credentials: nil)
1398
1431
  @cred_obj = MU::Cloud::AWS.loadCredentials(credentials)
1399
1432
  @credentials = MU::Cloud::AWS.credConfig(credentials, name_only: true)
1400
1433
 
@@ -1403,6 +1436,8 @@ end
1403
1436
  end
1404
1437
 
1405
1438
  params = {}
1439
+ region ||= MU::Cloud::AWS.credConfig(credentials)['region']
1440
+ region ||= MU.myRegion
1406
1441
 
1407
1442
  if region
1408
1443
  @region = region
@@ -67,16 +67,15 @@ module MU
67
67
  # soul-crushing, yet effective
68
68
  if e.message.match(/because (#{Regexp.quote(@config['region'])}[a-z]), the targeted availability zone, does not currently have sufficient capacity/)
69
69
  bad_az = Regexp.last_match(1)
70
- deletia = nil
70
+ deletia = []
71
71
  mySubnets.each { |subnet|
72
- if subnet.az == bad_az
73
- deletia = subnet.cloud_id
74
- break
75
- end
72
+ deletia << subnet.cloud_id if subnet.az == bad_az
73
+ }
74
+ raise e if deletia.empty?
75
+ MU.log "#{bad_az} does not have EKS capacity. Dropping unsupported subnets from ContainerCluster '#{@config['name']}' and retrying.", MU::NOTICE, details: deletia
76
+ deletia.each { |subnet|
77
+ params[:resources_vpc_config][:subnet_ids].delete(subnet)
76
78
  }
77
- raise e if deletia.nil?
78
- MU.log "#{bad_az} does not have EKS capacity. Dropping #{deletia} from ContainerCluster '#{@config['name']}' and retrying.", MU::NOTICE
79
- params[:resources_vpc_config][:subnet_ids].delete(deletia)
80
79
  end
81
80
  }
82
81
 
@@ -1372,6 +1371,9 @@ MU.log c.name, MU::NOTICE, details: t
1372
1371
  "name" => cluster["name"]+"pods",
1373
1372
  "phase" => "groom"
1374
1373
  }
1374
+ if !MU::Master.kubectl
1375
+ MU.log "Since I can't find a kubectl executable, you will have to handle all service account, user, and role bindings manually!", MU::WARN
1376
+ end
1375
1377
  end
1376
1378
 
1377
1379
  if MU::Cloud::AWS.isGovCloud?(cluster["region"]) and cluster["flavor"] == "EKS"
@@ -1470,6 +1472,11 @@ MU.log c.name, MU::NOTICE, details: t
1470
1472
  end
1471
1473
 
1472
1474
  if cluster["flavor"] == "EKS"
1475
+
1476
+ if !MU::Master.kubectl
1477
+ MU.log "Without a kubectl executable, I cannot bind IAM roles to EKS worker nodes", MU::ERR
1478
+ ok = false
1479
+ end
1473
1480
  worker_pool["canned_iam_policies"] = [
1474
1481
  "AmazonEKSWorkerNodePolicy",
1475
1482
  "AmazonEKS_CNI_Policy",
@@ -1602,19 +1609,21 @@ MU.log c.name, MU::NOTICE, details: t
1602
1609
  raise MuError, "Failed to apply #{authmap_cmd}" if $?.exitstatus != 0
1603
1610
  end
1604
1611
 
1605
- admin_user_cmd = %Q{#{MU::Master.kubectl} --kubeconfig "#{kube_conf}" apply -f "#{MU.myRoot}/extras/admin-user.yaml"}
1606
- admin_role_cmd = %Q{#{MU::Master.kubectl} --kubeconfig "#{kube_conf}" apply -f "#{MU.myRoot}/extras/admin-role-binding.yaml"}
1607
- MU.log "Configuring Kubernetes admin-user and role", MU::NOTICE, details: admin_user_cmd+"\n"+admin_role_cmd
1608
- %x{#{admin_user_cmd}}
1609
- %x{#{admin_role_cmd}}
1610
-
1611
- if @config['kubernetes_resources']
1612
- MU::Master.applyKubernetesResources(
1613
- @config['name'],
1614
- @config['kubernetes_resources'],
1615
- kubeconfig: kube_conf,
1616
- outputdir: @deploy.deploy_dir
1617
- )
1612
+ if MU::Master.kubectl
1613
+ admin_user_cmd = %Q{#{MU::Master.kubectl} --kubeconfig "#{kube_conf}" apply -f "#{MU.myRoot}/extras/admin-user.yaml"}
1614
+ admin_role_cmd = %Q{#{MU::Master.kubectl} --kubeconfig "#{kube_conf}" apply -f "#{MU.myRoot}/extras/admin-role-binding.yaml"}
1615
+ MU.log "Configuring Kubernetes admin-user and role", MU::NOTICE, details: admin_user_cmd+"\n"+admin_role_cmd
1616
+ %x{#{admin_user_cmd}}
1617
+ %x{#{admin_role_cmd}}
1618
+
1619
+ if @config['kubernetes_resources']
1620
+ MU::Master.applyKubernetesResources(
1621
+ @config['name'],
1622
+ @config['kubernetes_resources'],
1623
+ kubeconfig: kube_conf,
1624
+ outputdir: @deploy.deploy_dir
1625
+ )
1626
+ end
1618
1627
  end
1619
1628
 
1620
1629
  MU.log %Q{How to interact with your EKS cluster\nkubectl --kubeconfig "#{kube_conf}" get all\nkubectl --kubeconfig "#{kube_conf}" create -f some_k8s_deploy.yml\nkubectl --kubeconfig "#{kube_conf}" get nodes}, MU::SUMMARY
@@ -1186,7 +1186,7 @@ end
1186
1186
  statement["Resource"] << id+"/*"
1187
1187
  end
1188
1188
  else
1189
- raise MuError, "Couldn't find a #{target["entity_type"]} named #{target["identifier"]} when generating IAM policy"
1189
+ raise MuError, "Couldn't find a #{target["type"]} named #{target["identifier"]} when generating IAM policy"
1190
1190
  end
1191
1191
  else
1192
1192
  target["identifier"] += target["path"] if target["path"]
@@ -1270,7 +1270,11 @@ module MU
1270
1270
  def peerWith(peer)
1271
1271
  peer_ref = MU::Config::Ref.get(peer['vpc'])
1272
1272
  peer_obj = peer_ref.kitten
1273
- peer_id = peer_ref.cloud_id
1273
+ peer_id = peer_ref.kitten.cloud_id
1274
+ if peer_id == @cloud_id
1275
+ MU.log "#{@mu_name} attempted to peer with itself (#{@cloud_id})", MU::ERR, details: peer
1276
+ raise "#{@mu_name} attempted to peer with itself (#{@cloud_id})"
1277
+ end
1274
1278
 
1275
1279
  if peer_obj and peer_obj.config['peers']
1276
1280
  peer_obj.config['peers'].each { |peerpeer|
@@ -47,6 +47,11 @@ module MU
47
47
  guid_chunks.join("-")
48
48
  end
49
49
 
50
+ # List all Azure subscriptions available to our credentials
51
+ def self.listHabitats(credentials = nil)
52
+ []
53
+ end
54
+
50
55
  # A hook that is always called just before any of the instance method of
51
56
  # our resource implementations gets invoked, so that we can ensure that
52
57
  # repetitive setup tasks (like resolving +:resource_group+ for Azure
@@ -77,6 +82,11 @@ module MU
77
82
  [:resource_group]
78
83
  end
79
84
 
85
+ # Is this a "real" cloud provider, or a stub like CloudFormation?
86
+ def self.virtual?
87
+ false
88
+ end
89
+
80
90
  # Stub class to represent Azure's resource identifiers, which look like:
81
91
  # /subscriptions/3d20ddd8-4652-4074-adda-0d127ef1f0e0/resourceGroups/mu/providers/Microsoft.Network/virtualNetworks/mu-vnet
82
92
  # Various API calls need chunks of this in different contexts, and this
@@ -28,6 +28,16 @@ module MU
28
28
 
29
29
  @@cloudformation_mode = false
30
30
 
31
+ # Is this a "real" cloud provider, or a stub like CloudFormation?
32
+ def self.virtual?
33
+ true
34
+ end
35
+
36
+ # List all AWS projects available to our credentials
37
+ def self.listHabitats(credentials = nil)
38
+ MU::Cloud::AWS.listHabitats(credentials)
39
+ end
40
+
31
41
  # Return what we think of as a cloud object's habitat. In AWS, this means
32
42
  # the +account_number+ in which it's resident. If this is not applicable,
33
43
  # such as for a {Habitat} or {Folder}, returns nil.
@@ -52,6 +52,11 @@ module MU
52
52
  [:url]
53
53
  end
54
54
 
55
+ # Is this a "real" cloud provider, or a stub like CloudFormation?
56
+ def self.virtual?
57
+ false
58
+ end
59
+
55
60
  # Most of our resource implementation +find+ methods have to mangle their
56
61
  # args to make sure they've extracted a project or location argument from
57
62
  # other available information. This does it for them.
@@ -337,6 +342,7 @@ module MU
337
342
  # etc)
338
343
  # @param deploy_id [MU::MommaCat]
339
344
  def self.cleanDeploy(deploy_id, credentials: nil, noop: false)
345
+ removeDeploySecretsAndRoles(deploy_id, noop: noop, credentials: credentials)
340
346
  end
341
347
 
342
348
  # Plant a Mu deploy secret into a storage bucket somewhere for so our kittens can consume it
@@ -548,7 +554,7 @@ MU.log e.message, MU::WARN, details: e.inspect
548
554
  begin
549
555
  listRegions(credentials: credentials)
550
556
  listInstanceTypes(credentials: credentials)
551
- listProjects(credentials)
557
+ listHabitats(credentials)
552
558
  rescue ::Google::Apis::ClientError
553
559
  MU.log "Found machine credentials #{@@svc_account_name}, but these don't appear to have sufficient permissions or scopes", MU::WARN, details: scopes
554
560
  @@authorizers.delete(credentials)
@@ -701,12 +707,20 @@ MU.log e.message, MU::WARN, details: e.inspect
701
707
  end
702
708
 
703
709
  # List all Google Cloud Platform projects available to our credentials
704
- def self.listProjects(credentials = nil)
710
+ def self.listHabitats(credentials = nil)
705
711
  cfg = credConfig(credentials)
706
- return [] if !cfg or !cfg['project']
712
+ return [] if !cfg
713
+ if cfg['restrict_to_habitats'] and cfg['restrict_to_habitats'].is_a?(Array)
714
+ cfg['restrict_to_habitats'] << cfg['project'] if cfg['project']
715
+ return cfg['restrict_to_habitats'].uniq
716
+ end
707
717
  result = MU::Cloud::Google.resource_manager(credentials: credentials).list_projects
708
718
  result.projects.reject! { |p| p.lifecycle_state == "DELETE_REQUESTED" }
709
- result.projects.map { |p| p.project_id }
719
+ allprojects = result.projects.map { |p| p.project_id }
720
+ if cfg['ignore_habitats'] and cfg['ignore_habitats'].is_a?(Array)
721
+ allprojects.reject! { |p| cfg['ignore_habitats'].include?(p) }
722
+ end
723
+ allprojects
710
724
  end
711
725
 
712
726
  @@regions = {}
@@ -145,9 +145,9 @@ module MU
145
145
  # @param ignoremaster [Boolean]: If true, will remove resources not flagged as originating from this Mu server
146
146
  # @return [void]
147
147
  def self.cleanup(noop: false, ignoremaster: false, credentials: nil, flags: {})
148
- flags["project"] ||= MU::Cloud::Google.defaultProject(credentials)
148
+ flags["habitat"] ||= MU::Cloud::Google.defaultProject(credentials)
149
149
 
150
- resp = MU::Cloud::Google.storage(credentials: credentials).list_buckets(flags['project'])
150
+ resp = MU::Cloud::Google.storage(credentials: credentials).list_buckets(flags['habitat'])
151
151
  if resp and resp.items
152
152
  resp.items.each { |bucket|
153
153
  if bucket.labels and bucket.labels["mu-id"] == MU.deploy_id.downcase and (ignoremaster or bucket.labels['mu-master-ip'] == MU.mu_public_ip.gsub(/\./, "_"))