cloud-mu 2.0.0.pre.beta2 → 2.0.0.pre.beta3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/Berksfile.lock +1 -1
  3. data/cloud-mu.gemspec +4 -3
  4. data/cookbooks/mu-master/templates/default/mu.rc.erb +2 -2
  5. data/cookbooks/mu-tools/files/default/Mu_CA.pem +18 -19
  6. data/cookbooks/mu-tools/recipes/rsyslog.rb +1 -1
  7. data/modules/mu/cleanup.rb +14 -1
  8. data/modules/mu/cloud.rb +40 -22
  9. data/modules/mu/clouds/aws/alarm.rb +6 -0
  10. data/modules/mu/clouds/aws/bucket.rb +29 -0
  11. data/modules/mu/clouds/aws/cache_cluster.rb +6 -0
  12. data/modules/mu/clouds/aws/container_cluster.rb +6 -0
  13. data/modules/mu/clouds/aws/database.rb +6 -0
  14. data/modules/mu/clouds/aws/dnszone.rb +6 -0
  15. data/modules/mu/clouds/aws/endpoint.rb +6 -0
  16. data/modules/mu/clouds/aws/firewall_rule.rb +6 -0
  17. data/modules/mu/clouds/aws/folder.rb +6 -0
  18. data/modules/mu/clouds/aws/function.rb +6 -0
  19. data/modules/mu/clouds/aws/group.rb +6 -0
  20. data/modules/mu/clouds/aws/loadbalancer.rb +6 -0
  21. data/modules/mu/clouds/aws/log.rb +6 -0
  22. data/modules/mu/clouds/aws/msg_queue.rb +6 -0
  23. data/modules/mu/clouds/aws/nosqldb.rb +6 -0
  24. data/modules/mu/clouds/aws/notifier.rb +6 -0
  25. data/modules/mu/clouds/aws/role.rb +97 -11
  26. data/modules/mu/clouds/aws/search_domain.rb +6 -0
  27. data/modules/mu/clouds/aws/server.rb +6 -0
  28. data/modules/mu/clouds/aws/server_pool.rb +6 -0
  29. data/modules/mu/clouds/aws/storage_pool.rb +6 -0
  30. data/modules/mu/clouds/aws/user.rb +6 -0
  31. data/modules/mu/clouds/aws/vpc.rb +25 -1
  32. data/modules/mu/clouds/google.rb +86 -16
  33. data/modules/mu/clouds/google/bucket.rb +78 -3
  34. data/modules/mu/clouds/google/container_cluster.rb +12 -0
  35. data/modules/mu/clouds/google/database.rb +15 -1
  36. data/modules/mu/clouds/google/firewall_rule.rb +18 -2
  37. data/modules/mu/clouds/google/folder.rb +183 -16
  38. data/modules/mu/clouds/google/group.rb +7 -1
  39. data/modules/mu/clouds/google/habitat.rb +139 -24
  40. data/modules/mu/clouds/google/loadbalancer.rb +26 -12
  41. data/modules/mu/clouds/google/server.rb +25 -10
  42. data/modules/mu/clouds/google/server_pool.rb +16 -3
  43. data/modules/mu/clouds/google/user.rb +7 -1
  44. data/modules/mu/clouds/google/vpc.rb +87 -76
  45. data/modules/mu/config.rb +12 -0
  46. data/modules/mu/config/bucket.rb +4 -0
  47. data/modules/mu/config/folder.rb +1 -0
  48. data/modules/mu/config/habitat.rb +1 -1
  49. data/modules/mu/config/role.rb +78 -34
  50. data/modules/mu/config/vpc.rb +1 -0
  51. data/modules/mu/groomers/chef.rb +1 -1
  52. data/modules/mu/kittens.rb +689 -283
  53. metadata +5 -4
@@ -216,6 +216,12 @@ module MU
216
216
  false
217
217
  end
218
218
 
219
+ # Denote whether this resource implementation is experiment, ready for
220
+ # testing, or ready for production use.
221
+ def self.quality
222
+ MU::Cloud::BETA
223
+ end
224
+
219
225
  # Remove all logs associated with the currently loaded deployment.
220
226
  # @param noop [Boolean]: If true, will only print what would be done
221
227
  # @param ignoremaster [Boolean]: If true, will remove resources not flagged as originating from this Mu server
@@ -133,6 +133,12 @@ module MU
133
133
  false
134
134
  end
135
135
 
136
+ # Denote whether this resource implementation is experiment, ready for
137
+ # testing, or ready for production use.
138
+ def self.quality
139
+ MU::Cloud::RELEASE
140
+ end
141
+
136
142
  # Remove all msg_queues associated with the currently loaded deployment.
137
143
  # @param noop [Boolean]: If true, will only print what would be done
138
144
  # @param ignoremaster [Boolean]: If true, will remove resources not flagged as originating from this Mu server
@@ -160,6 +160,12 @@ pp params
160
160
  false
161
161
  end
162
162
 
163
+ # Denote whether this resource implementation is experiment, ready for
164
+ # testing, or ready for production use.
165
+ def self.quality
166
+ MU::Cloud::BETA
167
+ end
168
+
163
169
  # Remove all buckets associated with the currently loaded deployment.
164
170
  # @param noop [Boolean]: If true, will only print what would be done
165
171
  # @param ignoremaster [Boolean]: If true, will remove resources not flagged as originating from this Mu server
@@ -62,6 +62,12 @@ module MU
62
62
  false
63
63
  end
64
64
 
65
+ # Denote whether this resource implementation is experiment, ready for
66
+ # testing, or ready for production use.
67
+ def self.quality
68
+ MU::Cloud::BETA
69
+ end
70
+
65
71
  # Remove all notifiers associated with the currently loaded deployment.
66
72
  # @param noop [Boolean]: If true, will only print what would be done
67
73
  # @param ignoremaster [Boolean]: If true, will remove resources not flagged as originating from this Mu server
@@ -292,6 +292,12 @@ module MU
292
292
  true
293
293
  end
294
294
 
295
+ # Denote whether this resource implementation is experiment, ready for
296
+ # testing, or ready for production use.
297
+ def self.quality
298
+ MU::Cloud::BETA
299
+ end
300
+
295
301
  # Remove all roles associated with the currently loaded deployment.
296
302
  # @param noop [Boolean]: If true, will only print what would be done
297
303
  # @param ignoremaster [Boolean]: If true, will remove resources not flagged as originating from this Mu server
@@ -485,6 +491,42 @@ module MU
485
491
  resp.instance_profile.arn
486
492
  end
487
493
 
494
+ # Schema fragment for IAM policy conditions, which some other resource
495
+ # types may need to import.
496
+ def self.condition_schema
497
+ {
498
+ "items" => {
499
+ "properties" => {
500
+ "conditions" => {
501
+ "type" => "array",
502
+ "items" => {
503
+ "type" => "object",
504
+ "description" => "One or more conditions under which to apply this policy. See also: https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_condition.html",
505
+ "required" => ["comparison", "variable", "values"],
506
+ "properties" => {
507
+ "comparison" => {
508
+ "type" => "string",
509
+ "description" => "A comparison to make, like +DateGreaterThan+ or +IpAddress+."
510
+ },
511
+ "variable" => {
512
+ "type" => "string",
513
+ "description" => "The variable which we will compare, like +aws:CurrentTime+ or +aws:SourceIp+."
514
+ },
515
+ "values" => {
516
+ "type" => "array",
517
+ "items" => {
518
+ "type" => "string",
519
+ "description" => "Value(s) to which we will compare our variable, like +2013-08-16T15:00:00Z+ or +192.0.2.0/24+."
520
+ }
521
+ }
522
+ }
523
+ }
524
+ }
525
+ }
526
+ }
527
+ }
528
+ end
529
+
488
530
  # Cloud-specific configuration properties.
489
531
  # @param config [MU::Config]: The calling MU::Config object
490
532
  # @return [Array<Array,Hash>]: List of required fields, and json-schema Hash of cloud-specific configuration parameters for this resource
@@ -499,9 +541,11 @@ module MU
499
541
  end
500
542
  }.map { |t| MU::Cloud.resource_types[t][:cfg_name] }.sort
501
543
 
544
+
502
545
  schema = {
503
546
  "tags" => MU::Config.tags_primitive,
504
547
  "optional_tags" => MU::Config.optional_tags_primitive,
548
+ "policies" => self.condition_schema,
505
549
  "import" => {
506
550
  "items" => {
507
551
  "description" => "Can be a shorthand reference to a canned IAM policy like +AdministratorAccess+, or a full ARN like +arn:aws:iam::aws:policy/AmazonESCognitoAccess+"
@@ -611,14 +655,15 @@ module MU
611
655
  ok
612
656
  end
613
657
 
614
- private
615
-
616
- # Convert entries from the cloud-neutral @config['policies'] list into
617
- # AWS syntax.
618
- def convert_policies_to_iam
658
+ # Convert our generic internal representation of access policies into
659
+ # structures suitable for AWS IAM policy documents.
660
+ # @param policies [Array<Hash>]: One or more policy chunks
661
+ # @param deploy_obj [MU::MommaCat]: Deployment object to use when looking up sibling Mu resources
662
+ # @return [Array<Hash>]
663
+ def self.genPolicyDocument(policies, deploy_obj: nil)
619
664
  iam_policies = []
620
- if @config['policies']
621
- @config['policies'].each { |policy|
665
+ if policies
666
+ policies.each { |policy|
622
667
  doc = {
623
668
  "Version" => "2012-10-17",
624
669
  "Statement" => [
@@ -633,19 +678,52 @@ module MU
633
678
  policy["permissions"].each { |perm|
634
679
  doc["Statement"].first["Action"] << perm
635
680
  }
681
+ if policy["conditions"]
682
+ doc["Statement"].first["Condition"] ||= {}
683
+ policy["conditions"].each { |cond|
684
+ doc["Statement"].first["Condition"][cond['comparison']] = {
685
+ cond["variable"] => cond["values"]
686
+ }
687
+ }
688
+ end
689
+ if policy["grant_to"] # XXX factor this with target, they're too similar
690
+ doc["Statement"].first["Principal"] ||= []
691
+ policy["grant_to"].each { |grantee|
692
+ if grantee["type"] and deploy_obj
693
+ sibling = deploy_obj.findLitterMate(
694
+ name: grantee["identifier"],
695
+ type: grantee["type"]
696
+ )
697
+ if sibling
698
+ id = sibling.cloudobj.arn
699
+ doc["Statement"].first["Principal"] << id
700
+ else
701
+ raise MuError, "Couldn't find a #{grantee["type"]} named #{grantee["identifier"]} when generating IAM policy"
702
+ end
703
+ else
704
+ doc["Statement"].first["Principal"] << grantee["identifier"]
705
+ end
706
+ }
707
+ if policy["grant_to"].size == 1
708
+ doc["Statement"].first["Principal"] = doc["Statement"].first["Principal"].first
709
+ end
710
+ end
636
711
  if policy["targets"]
637
712
  policy["targets"].each { |target|
638
- if target["type"]
639
- sibling = @deploy.findLitterMate(
713
+ if target["type"] and deploy_obj
714
+ sibling = deploy_obj.findLitterMate(
640
715
  name: target["identifier"],
641
716
  type: target["type"]
642
717
  )
643
718
  if sibling
644
- doc["Statement"].first["Resource"] << sibling.cloudobj.arn
719
+ id = sibling.cloudobj.arn
720
+ id += target["path"] if target["path"]
721
+ doc["Statement"].first["Resource"] << id
645
722
  else
646
- raise MuError, "Couldn't find a #{target["entity_type"]} named #{target["identifier"]} when generating IAM policy in role #{@mu_name}"
723
+ raise MuError, "Couldn't find a #{target["entity_type"]} named #{target["identifier"]} when generating IAM policy"
647
724
  end
648
725
  else
726
+ target["identifier"] += target["path"] if target["path"]
649
727
  doc["Statement"].first["Resource"] << target["identifier"]
650
728
  end
651
729
  }
@@ -657,6 +735,14 @@ module MU
657
735
  iam_policies
658
736
  end
659
737
 
738
+ private
739
+
740
+ # Convert entries from the cloud-neutral @config['policies'] list into
741
+ # AWS syntax.
742
+ def convert_policies_to_iam
743
+ MU::Cloud::AWS::Role.genPolicyDocument(@config['policies'], deploy_obj: @deploy)
744
+ end
745
+
660
746
  def get_tag_params(strip_std = false)
661
747
  @config['tags'] ||= []
662
748
 
@@ -108,6 +108,12 @@ module MU
108
108
  false
109
109
  end
110
110
 
111
+ # Denote whether this resource implementation is experiment, ready for
112
+ # testing, or ready for production use.
113
+ def self.quality
114
+ MU::Cloud::RELEASE
115
+ end
116
+
111
117
  # Remove all search_domains associated with the currently loaded deployment.
112
118
  # @param noop [Boolean]: If true, will only print what would be done
113
119
  # @param ignoremaster [Boolean]: If true, will remove resources not flagged as originating from this Mu server
@@ -1794,6 +1794,12 @@ module MU
1794
1794
  false
1795
1795
  end
1796
1796
 
1797
+ # Denote whether this resource implementation is experiment, ready for
1798
+ # testing, or ready for production use.
1799
+ def self.quality
1800
+ MU::Cloud::RELEASE
1801
+ end
1802
+
1797
1803
  # Remove all instances associated with the currently loaded deployment. Also cleans up associated volumes, droppings in the MU master's /etc/hosts and ~/.ssh, and in whatever Groomer was used.
1798
1804
  # @param noop [Boolean]: If true, will only print what would be done
1799
1805
  # @param ignoremaster [Boolean]: If true, will remove resources not flagged as originating from this Mu server
@@ -989,6 +989,12 @@ module MU
989
989
  false
990
990
  end
991
991
 
992
+ # Denote whether this resource implementation is experiment, ready for
993
+ # testing, or ready for production use.
994
+ def self.quality
995
+ MU::Cloud::RELEASE
996
+ end
997
+
992
998
  # Remove all autoscale groups associated with the currently loaded deployment.
993
999
  # @param noop [Boolean]: If true, will only print what would be done
994
1000
  # @param ignoremaster [Boolean]: If true, will remove resources not flagged as originating from this Mu server
@@ -349,6 +349,12 @@ module MU
349
349
  false
350
350
  end
351
351
 
352
+ # Denote whether this resource implementation is experiment, ready for
353
+ # testing, or ready for production use.
354
+ def self.quality
355
+ MU::Cloud::RELEASE
356
+ end
357
+
352
358
  # Called by {MU::Cleanup}. Locates resources that were created by the
353
359
  # currently-loaded deployment, and purges them.
354
360
  # @param noop [Boolean]: If true, will only print what would be done
@@ -136,6 +136,12 @@ module MU
136
136
  true
137
137
  end
138
138
 
139
+ # Denote whether this resource implementation is experiment, ready for
140
+ # testing, or ready for production use.
141
+ def self.quality
142
+ MU::Cloud::BETA
143
+ end
144
+
139
145
  # Remove all users associated with the currently loaded deployment.
140
146
  # @param noop [Boolean]: If true, will only print what would be done
141
147
  # @param ignoremaster [Boolean]: If true, will remove resources not flagged as originating from this Mu server
@@ -1129,6 +1129,12 @@ module MU
1129
1129
  false
1130
1130
  end
1131
1131
 
1132
+ # Denote whether this resource implementation is experiment, ready for
1133
+ # testing, or ready for production use.
1134
+ def self.quality
1135
+ MU::Cloud::RELEASE
1136
+ end
1137
+
1132
1138
  # Remove all VPC resources associated with the currently loaded deployment.
1133
1139
  # @param noop [Boolean]: If true, will only print what would be done
1134
1140
  # @param ignoremaster [Boolean]: If true, will remove resources not flagged as originating from this Mu server
@@ -1440,10 +1446,28 @@ module MU
1440
1446
  ok
1441
1447
  end
1442
1448
 
1449
+ # Remove all network interfaces associated with the currently loaded deployment.
1450
+ # @param noop [Boolean]: If true, will only print what would be done
1451
+ # @param tagfilters [Array<Hash>]: EC2 tags to filter against when search for resources to purge
1452
+ # @param region [String]: The cloud provider region
1453
+ # @return [void]
1454
+ def self.purge_interfaces(noop = false, tagfilters = [{name: "tag:MU-ID", values: [MU.deploy_id]}], region: MU.curRegion, credentials: nil)
1455
+ resp = MU::Cloud::AWS.ec2(credentials: credentials, region: region).describe_network_interfaces(
1456
+ filters: tagfilters
1457
+ )
1458
+ ifaces = resp.data.network_interfaces
1459
+
1460
+ return if ifaces.nil? or ifaces.size == 0
1461
+
1462
+ ifaces.each { |iface|
1463
+ MU.log "Deleting Network Interface #{iface.network_interface_id}"
1464
+ MU::Cloud::AWS.ec2(credentials: credentials, region: region).delete_network_interface(network_interface_id: iface.network_interface_id)
1465
+ }
1466
+ end
1467
+
1443
1468
 
1444
1469
  private
1445
1470
 
1446
-
1447
1471
  # List the route tables for each subnet in the given VPC
1448
1472
  def self.listAllSubnetRouteTables(vpc_id, region: MU.curRegion, credentials: nil)
1449
1473
  resp = MU::Cloud::AWS.ec2(region: region, credentials: credentials).describe_subnets(
@@ -28,6 +28,7 @@ module MU
28
28
  @@my_hosted_cfg = nil
29
29
  @@authorizers = {}
30
30
  @@acct_to_profile_map = {}
31
+ @@enable_semaphores = {}
31
32
 
32
33
  # Any cloud-specific instance methods we require our resource
33
34
  # implementations to have, above and beyond the ones specified by
@@ -71,6 +72,36 @@ module MU
71
72
  $MU_CFG['google'].keys
72
73
  end
73
74
 
75
+ # A shortcut for {MU::MommaCat.findStray} to resolve a shorthand project
76
+ # name into a cloud object, whether it refers to a sibling by internal
77
+ # name or by cloud identifier.
78
+ # @param name [String]
79
+ # @param deploy [String]
80
+ # @param raise_on_fail [Boolean]
81
+ # @param sibling_only [Boolean]
82
+ # @return [MU::Cloud::Habitat,nil]
83
+ def self.projectLookup(name, deploy = MU.mommacat, raise_on_fail: true, sibling_only: false)
84
+ project_obj = deploy.findLitterMate(type: "habitats", name: name)
85
+
86
+ if !project_obj and !sibling_only
87
+ resp = MU::MommaCat.findStray(
88
+ "Google",
89
+ "habitats",
90
+ deploy_id: deploy.deploy_id,
91
+ cloud_id: name,
92
+ name: name,
93
+ dummy_ok: true
94
+ )
95
+ project_obj = resp.first if resp
96
+ end
97
+
98
+ if (!project_obj or !project_obj.cloud_id) and raise_on_fail
99
+ raise MuError, "Failed to find project '#{name}' in deploy #{deploy.deploy_id}"
100
+ end
101
+
102
+ project_obj
103
+ end
104
+
74
105
  # Resolve the administrative Cloud Storage bucket for a given credential
75
106
  # set, or return a default.
76
107
  # @param credentials [String]
@@ -211,7 +242,7 @@ module MU
211
242
 
212
243
  [name, "log_vol_ebs_key"].each { |obj|
213
244
  MU.log "Granting #{acct} access to #{obj} in Cloud Storage bucket #{adminBucketName(credentials)}"
214
- pp aclobj
245
+
215
246
  MU::Cloud::Google.storage(credentials: credentials).insert_object_access_control(
216
247
  adminBucketName(credentials),
217
248
  obj,
@@ -323,6 +354,10 @@ module MU
323
354
 
324
355
  cfg = credConfig(credentials)
325
356
 
357
+ if cfg['project']
358
+ @@enable_semaphores[cfg['project']] ||= Mutex.new
359
+ end
360
+
326
361
  if cfg
327
362
  data = nil
328
363
  @@authorizers[credentials] ||= {}
@@ -597,7 +632,8 @@ module MU
597
632
  require 'google/apis/cloudresourcemanager_v1'
598
633
 
599
634
  if subclass.nil?
600
- @@resource_api[credentials] ||= MU::Cloud::Google::GoogleEndpoint.new(api: "CloudresourcemanagerV1::CloudResourceManagerService", scopes: ['https://www.googleapis.com/auth/cloud-platform'], credentials: credentials)
635
+ # @@resource_api[credentials] ||= MU::Cloud::Google::GoogleEndpoint.new(api: "CloudresourcemanagerV1::CloudResourceManagerService", scopes: ['https://www.googleapis.com/auth/cloud-platform', 'https://www.googleapis.com/auth/cloudplatformprojects'], masquerade: MU::Cloud::Google.credConfig(credentials)['masquerade_as'], credentials: credentials)
636
+ @@resource_api[credentials] ||= MU::Cloud::Google::GoogleEndpoint.new(api: "CloudresourcemanagerV1::CloudResourceManagerService", scopes: ['https://www.googleapis.com/auth/cloud-platform', 'https://www.googleapis.com/auth/cloudplatformprojects'], credentials: credentials)
601
637
  return @@resource_api[credentials]
602
638
  elsif subclass.is_a?(Symbol)
603
639
  return Object.const_get("::Google").const_get("Apis").const_get("CloudresourcemanagerV1").const_get(subclass)
@@ -605,15 +641,15 @@ module MU
605
641
  end
606
642
 
607
643
  # Google's Cloud Resource Manager API V2, which apparently has all the folder bits
608
- # @param subclass [<Google::Apis::CloudresourcemanagerV2beta1>]: If specified, will return the class ::Google::Apis::CloudresourcemanagerV2beta1::subclass instead of an API client instance
644
+ # @param subclass [<Google::Apis::CloudresourcemanagerV2>]: If specified, will return the class ::Google::Apis::CloudresourcemanagerV2::subclass instead of an API client instance
609
645
  def self.folder(subclass = nil, credentials: nil)
610
- require 'google/apis/cloudresourcemanager_v2beta1'
646
+ require 'google/apis/cloudresourcemanager_v2'
611
647
 
612
648
  if subclass.nil?
613
- @@resource2_api[credentials] ||= MU::Cloud::Google::GoogleEndpoint.new(api: "CloudresourcemanagerV2beta1::CloudResourceManagerService", scopes: ['https://www.googleapis.com/auth/cloud-platform'], credentials: credentials)
649
+ @@resource2_api[credentials] ||= MU::Cloud::Google::GoogleEndpoint.new(api: "CloudresourcemanagerV2::CloudResourceManagerService", scopes: ['https://www.googleapis.com/auth/cloud-platform', 'https://www.googleapis.com/auth/cloudplatformfolders'], credentials: credentials)
614
650
  return @@resource2_api[credentials]
615
651
  elsif subclass.is_a?(Symbol)
616
- return Object.const_get("::Google").const_get("Apis").const_get("CloudresourcemanagerV2beta1").const_get(subclass)
652
+ return Object.const_get("::Google").const_get("Apis").const_get("CloudresourcemanagerV2").const_get(subclass)
617
653
  end
618
654
  end
619
655
 
@@ -682,6 +718,31 @@ module MU
682
718
  end
683
719
  end
684
720
 
721
+ # Google's Cloud Billing Service API
722
+ # @param subclass [<Google::Apis::LoggingV2>]: If specified, will return the class ::Google::Apis::LoggingV2::subclass instead of an API client instance
723
+ def self.billing(subclass = nil, credentials: nil)
724
+ require 'google/apis/cloudbilling_v1'
725
+
726
+ if subclass.nil?
727
+ @@billing_api[credentials] ||= MU::Cloud::Google::GoogleEndpoint.new(api: "CloudbillingV1::CloudbillingService", scopes: ['https://www.googleapis.com/auth/cloud-platform'], credentials: credentials)
728
+ return @@billing_api[credentials]
729
+ elsif subclass.is_a?(Symbol)
730
+ return Object.const_get("::Google").const_get("Apis").const_get("CloudbillingV1").const_get(subclass)
731
+ end
732
+ end
733
+
734
+
735
+ # Retrieve the organization, if any, to which these credentials belong.
736
+ # @param credentials [String]
737
+ # @return [Array<OpenStruct>],nil]
738
+ def self.getOrg(credentials = nil)
739
+ resp = MU::Cloud::Google.resource_manager(credentials: credentials).search_organizations
740
+ if resp and resp.organizations
741
+ # XXX no idea if it's possible to be a member of multiple orgs
742
+ return resp.organizations.first
743
+ end
744
+ nil
745
+ end
685
746
 
686
747
  private
687
748
 
@@ -762,7 +823,7 @@ module MU
762
823
  end
763
824
  # TODO validate that the resource actually went away, because it seems not to do so very reliably
764
825
  rescue ::Google::Apis::ClientError => e
765
- raise e if !e.message.match(/^notFound: /)
826
+ raise e if !e.message.match(/(^notFound: |operation in progress)/)
766
827
  end while failed and retries < 6
767
828
  end
768
829
  }
@@ -798,30 +859,37 @@ module MU
798
859
  else
799
860
  raise MU::MuError, "Service account #{MU::Cloud::Google.svc_account_name} has insufficient privileges to call #{method_sym}"
800
861
  end
801
- rescue ::Google::Apis::ClientError => e
862
+ rescue ::Google::Apis::ClientError, OpenSSL::SSL::SSLError => e
802
863
  if e.message.match(/^invalidParameter:/)
803
864
  MU.log "#{method_sym.to_s}: "+e.message, MU::ERR, details: arguments
804
865
  # uncomment for debugging stuff; this can occur in benign situations so we don't normally want it logging
805
866
  elsif e.message.match(/^forbidden:/)
806
867
  MU.log "Using credentials #{@credentials}: #{method_sym.to_s}: "+e.message, MU::ERR, details: caller
807
868
  end
808
- if retries <= 1 and e.message.match(/^accessNotConfigured/)
869
+ @@enable_semaphores ||= {}
870
+ max_retries = 3
871
+ wait_time = 90
872
+ if retries <= max_retries and e.message.match(/^accessNotConfigured/)
809
873
  enable_obj = nil
810
874
  project = arguments.size > 0 ? arguments.first.to_s : MU::Cloud::Google.defaultProject(@credentials)
875
+ @@enable_semaphores[project] ||= Mutex.new
811
876
  enable_obj = MU::Cloud::Google.service_manager(:EnableServiceRequest).new(
812
877
  consumer_id: "project:"+project
813
878
  )
814
879
  # XXX dumbass way to get this string
815
- e.message.match(/Enable it by visiting https:\/\/console\.developers\.google\.com\/apis\/api\/(.+?)\//)
880
+ e.message.match(/by visiting https:\/\/console\.developers\.google\.com\/apis\/api\/(.+?)\//)
881
+
816
882
  svc_name = Regexp.last_match[1]
817
883
  save_verbosity = MU.verbosity
818
884
  if svc_name != "servicemanagement.googleapis.com"
819
- MU.setLogging(MU::Logger::NORMAL)
820
- MU.log "Attempting to enable #{svc_name} in project #{project}, then waiting for 30s", MU::WARN
821
- MU.setLogging(save_verbosity)
822
- MU::Cloud::Google.service_manager(credentials: @credentials).enable_service(svc_name, enable_obj)
823
- sleep 30
824
885
  retries += 1
886
+ @@enable_semaphores[project].synchronize {
887
+ MU.setLogging(MU::Logger::NORMAL)
888
+ MU.log "Attempting to enable #{svc_name} in project #{project}; will retry #{method_sym.to_s} in #{(wait_time/retries).to_s}s (#{retries.to_s}/#{max_retries.to_s})", MU::NOTICE
889
+ MU.setLogging(save_verbosity)
890
+ MU::Cloud::Google.service_manager(credentials: @credentials).enable_service(svc_name, enable_obj)
891
+ }
892
+ sleep wait_time/retries
825
893
  retry
826
894
  else
827
895
  MU.setLogging(MU::Logger::NORMAL)
@@ -831,7 +899,8 @@ module MU
831
899
  end
832
900
  elsif retries <= 10 and
833
901
  e.message.match(/^resourceNotReady:/) or
834
- (e.message.match(/^resourceInUseByAnotherResource:/) and method_sym.to_s.match(/^delete_/))
902
+ (e.message.match(/^resourceInUseByAnotherResource:/) and method_sym.to_s.match(/^delete_/)) or
903
+ e.message.match(/SSL_connect/)
835
904
  if retries > 0 and retries % 3 == 0
836
905
  MU.log "Will retry #{method_sym} after #{e.message} (retry #{retries})", MU::NOTICE, details: arguments
837
906
  else
@@ -968,6 +1037,7 @@ module MU
968
1037
  @@service_api = {}
969
1038
  @@firestore_api = {}
970
1039
  @@admin_directory_api = {}
1040
+ @@billing_api = {}
971
1041
  end
972
1042
  end
973
1043
  end