cloud-mu 3.1.5 → 3.1.6

Sign up to get free protection for your applications and to get access to all the features.
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(/\./, "_"))