cloud-mu 3.4.0 → 3.5.0

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 (104) hide show
  1. checksums.yaml +4 -4
  2. data/ansible/roles/mu-nat/tasks/main.yml +3 -0
  3. data/bin/mu-aws-setup +41 -7
  4. data/bin/mu-azure-setup +34 -0
  5. data/bin/mu-configure +214 -119
  6. data/bin/mu-gcp-setup +37 -2
  7. data/bin/mu-node-manage +3 -0
  8. data/bin/mu-refresh-ssl +67 -0
  9. data/bin/mu-run-tests +14 -4
  10. data/bin/mu-self-update +30 -10
  11. data/bin/mu-upload-chef-artifacts +30 -26
  12. data/cloud-mu.gemspec +8 -6
  13. data/cookbooks/mu-master/attributes/default.rb +5 -1
  14. data/cookbooks/mu-master/metadata.rb +2 -2
  15. data/cookbooks/mu-master/recipes/default.rb +81 -26
  16. data/cookbooks/mu-master/recipes/init.rb +197 -62
  17. data/cookbooks/mu-master/recipes/update_nagios_only.rb +1 -1
  18. data/cookbooks/mu-master/recipes/vault.rb +78 -77
  19. data/cookbooks/mu-master/templates/default/mods/rewrite.conf.erb +1 -0
  20. data/cookbooks/mu-master/templates/default/nagios.conf.erb +103 -0
  21. data/cookbooks/mu-master/templates/default/web_app.conf.erb +14 -30
  22. data/cookbooks/mu-tools/attributes/default.rb +5 -0
  23. data/cookbooks/mu-tools/files/centos-6/CentOS-Base.repo +47 -0
  24. data/cookbooks/mu-tools/libraries/helper.rb +12 -2
  25. data/cookbooks/mu-tools/libraries/monkey.rb +1 -1
  26. data/cookbooks/mu-tools/recipes/apply_security.rb +6 -0
  27. data/cookbooks/mu-tools/recipes/aws_api.rb +6 -4
  28. data/cookbooks/mu-tools/recipes/base_repositories.rb +1 -1
  29. data/cookbooks/mu-tools/recipes/gcloud.rb +2 -9
  30. data/cookbooks/mu-tools/recipes/google_api.rb +5 -2
  31. data/cookbooks/mu-tools/resources/disk.rb +108 -58
  32. data/extras/Gemfile.lock.bootstrap +394 -0
  33. data/extras/bucketstubs/error.html +0 -0
  34. data/extras/bucketstubs/index.html +0 -0
  35. data/extras/clean-stock-amis +9 -9
  36. data/extras/git_rpm/build.sh +20 -0
  37. data/extras/git_rpm/mugit.spec +53 -0
  38. data/extras/image-generators/VMWare/centos8.yaml +15 -0
  39. data/extras/openssl_rpm/build.sh +19 -0
  40. data/extras/openssl_rpm/mussl.spec +46 -0
  41. data/extras/python_rpm/muthon.spec +14 -4
  42. data/extras/ruby_rpm/muby.spec +9 -5
  43. data/extras/sqlite_rpm/build.sh +19 -0
  44. data/extras/sqlite_rpm/muqlite.spec +47 -0
  45. data/install/installer +7 -5
  46. data/modules/mu.rb +12 -5
  47. data/modules/mu/cloud/machine_images.rb +1 -1
  48. data/modules/mu/cloud/providers.rb +6 -1
  49. data/modules/mu/cloud/resource_base.rb +1 -1
  50. data/modules/mu/cloud/ssh_sessions.rb +4 -0
  51. data/modules/mu/config.rb +28 -12
  52. data/modules/mu/config/database.rb +2 -2
  53. data/modules/mu/config/firewall_rule.rb +1 -1
  54. data/modules/mu/config/ref.rb +2 -2
  55. data/modules/mu/config/schema_helpers.rb +12 -3
  56. data/modules/mu/config/server.rb +10 -4
  57. data/modules/mu/config/server_pool.rb +2 -2
  58. data/modules/mu/config/vpc.rb +10 -10
  59. data/modules/mu/defaults/AWS.yaml +32 -32
  60. data/modules/mu/deploy.rb +23 -10
  61. data/modules/mu/groomers/chef.rb +2 -2
  62. data/modules/mu/master.rb +49 -3
  63. data/modules/mu/mommacat.rb +8 -5
  64. data/modules/mu/mommacat/naming.rb +2 -2
  65. data/modules/mu/mommacat/storage.rb +22 -27
  66. data/modules/mu/providers/aws.rb +142 -48
  67. data/modules/mu/providers/aws/alarm.rb +3 -3
  68. data/modules/mu/providers/aws/bucket.rb +19 -19
  69. data/modules/mu/providers/aws/cache_cluster.rb +22 -22
  70. data/modules/mu/providers/aws/cdn.rb +2 -2
  71. data/modules/mu/providers/aws/collection.rb +14 -14
  72. data/modules/mu/providers/aws/container_cluster.rb +27 -27
  73. data/modules/mu/providers/aws/database.rb +40 -39
  74. data/modules/mu/providers/aws/dnszone.rb +5 -5
  75. data/modules/mu/providers/aws/endpoint.rb +35 -35
  76. data/modules/mu/providers/aws/firewall_rule.rb +26 -23
  77. data/modules/mu/providers/aws/function.rb +28 -28
  78. data/modules/mu/providers/aws/group.rb +7 -7
  79. data/modules/mu/providers/aws/habitat.rb +2 -2
  80. data/modules/mu/providers/aws/job.rb +6 -6
  81. data/modules/mu/providers/aws/loadbalancer.rb +34 -34
  82. data/modules/mu/providers/aws/log.rb +14 -14
  83. data/modules/mu/providers/aws/msg_queue.rb +10 -10
  84. data/modules/mu/providers/aws/nosqldb.rb +8 -8
  85. data/modules/mu/providers/aws/notifier.rb +7 -7
  86. data/modules/mu/providers/aws/role.rb +17 -15
  87. data/modules/mu/providers/aws/search_domain.rb +10 -10
  88. data/modules/mu/providers/aws/server.rb +176 -95
  89. data/modules/mu/providers/aws/server_pool.rb +65 -105
  90. data/modules/mu/providers/aws/storage_pool.rb +17 -9
  91. data/modules/mu/providers/aws/user.rb +1 -1
  92. data/modules/mu/providers/aws/vpc.rb +103 -51
  93. data/modules/mu/providers/aws/vpc_subnet.rb +43 -39
  94. data/modules/mu/providers/azure.rb +78 -12
  95. data/modules/mu/providers/azure/server.rb +18 -3
  96. data/modules/mu/providers/cloudformation/server.rb +1 -1
  97. data/modules/mu/providers/google.rb +19 -4
  98. data/modules/mu/providers/google/folder.rb +6 -2
  99. data/modules/mu/providers/google/function.rb +65 -30
  100. data/modules/mu/providers/google/role.rb +1 -1
  101. data/modules/mu/providers/google/vpc.rb +27 -2
  102. data/modules/tests/aws-servers-with-handrolled-iam.yaml +37 -0
  103. data/modules/tests/k8s.yaml +1 -1
  104. metadata +24 -8
@@ -288,9 +288,14 @@ module MU
288
288
  raise MuError, "Nil response from Azure API attempting list_locations(#{subscription})"
289
289
  end
290
290
 
291
- sdk_response.value.each do | region |
292
- @@regions.push(region.name)
293
- end
291
+ sdk_response.value.each { |region|
292
+ begin
293
+ listInstanceTypes(region.name) # use this to filter for broken regions
294
+ @@regions.push(region.name)
295
+ rescue APIError => e
296
+ MU.log "Azure region "+region.name+" does not appear operational, skipping", MU::WARN
297
+ end
298
+ }
294
299
 
295
300
  return us_only ? @@regions.reject { |r| !r.match(/us\d?$/) } : @@regions
296
301
  end
@@ -350,13 +355,42 @@ module MU
350
355
  listRegions.each { |region|
351
356
  next if !deploy.regionsUsed.include?(region)
352
357
  begin
353
- createResourceGroup(deploy.deploy_id+"-"+region.upcase, region, credentials: creds)
358
+ rg_obj = createResourceGroup(deploy.deploy_id+"-"+region.upcase, region, credentials: creds)
359
+ createVault(rg_obj.name, region, deploy, credentials: creds)
354
360
  rescue ::MsRestAzure::AzureOperationError
355
361
  end
356
362
  }
357
363
  }
358
364
  end
359
365
 
366
+ # Arguably this should be a first class resource, but for now we'll do
367
+ # it here since we're going to have a generic deployment vault in every
368
+ # resource group.
369
+ # @param rg [String]: The name of the resource group in which we'll reside
370
+ # @param region [String]: The region in which we'll reside
371
+ # @param deploy [MU::MommaCat]: The deployment which we serve
372
+ # @param credentials [String]:
373
+ def self.createVault(rg, region, deploy, credentials: nil)
374
+ cred_hash = MU::Cloud::Azure.getSDKOptions(credentials)
375
+ vaultname = deploy.getResourceName(region, max_length: 23, disallowed_chars: /[^a-z0-9-]/i, never_gen_unique: true)
376
+ MU::Cloud::Azure.ensureProvider("Microsoft.KeyVault", credentials: credentials)
377
+ sku = MU::Cloud::Azure.keyvault(:Sku).new
378
+ sku.name = "standard" # ...I'm angry about this
379
+
380
+ props = MU::Cloud::Azure.keyvault(:VaultProperties).new
381
+ props.tenant_id = cred_hash[:tenant_id]
382
+ props.enabled_for_deployment = true
383
+ props.sku = sku
384
+ props.access_policies = []
385
+
386
+ params = MU::Cloud::Azure.keyvault(:VaultCreateOrUpdateParameters).new
387
+ params.location = region
388
+ params.properties = props
389
+
390
+ MU.log "Creating KeyVault #{vaultname} in #{region}"
391
+ MU::Cloud::Azure.keyvault(credentials: credentials).vaults.create_or_update(rg, vaultname, params)
392
+ end
393
+
360
394
  @@rg_semaphore = Mutex.new
361
395
 
362
396
  # Purge cloud-specific deploy meta-artifacts (SSH keys, resource groups,
@@ -400,17 +434,30 @@ module MU
400
434
  end
401
435
  }
402
436
  MU.log "Configuring resource group #{name} in #{region}", details: rg_obj
403
- MU::Cloud::Azure.resources(credentials: credentials).resource_groups.create_or_update(
437
+ rg = MU::Cloud::Azure.resources(credentials: credentials).resource_groups.create_or_update(
404
438
  name,
405
439
  rg_obj
406
440
  )
441
+
442
+ rg
407
443
  end
408
444
 
409
445
  # Plant a Mu deploy secret into a storage bucket somewhere for so our kittens can consume it
410
- # @param deploy_id [String]: The deploy for which we're writing the secret
446
+ # @param deploy [MU::MommaCat]: The deploy for which we're writing the secret
411
447
  # @param value [String]: The contents of the secret
412
- def self.writeDeploySecret(deploy_id, value, name = nil, credentials: nil)
413
- # XXX this ain't it hoss
448
+ def self.writeDeploySecret(deploy, value, name = nil, credentials: nil)
449
+ deploy_id = deploy.deploy_id
450
+
451
+ listRegions.each { |region|
452
+ next if !deploy.regionsUsed.include?(region)
453
+ rg = deploy_id+"-"+region.upcase
454
+ vaultname = deploy.getResourceName(region, max_length: 23, disallowed_chars: /[^a-z0-9-]/i, never_gen_unique: true)
455
+
456
+ resp = MU::Cloud::Azure.keyvault(credentials: credentials).vaults.get(rg, vaultname)
457
+ next if !resp
458
+ MU.log "vault existence check #{vaultname}", MU::WARN, details: resp
459
+
460
+ }
414
461
  end
415
462
 
416
463
  # Return the name strings of all known sets of credentials for this cloud
@@ -522,7 +569,7 @@ module MU
522
569
 
523
570
  begin
524
571
  Timeout.timeout(2) do
525
- resp = JSON.parse(open("#{base_url}/?#{arg_str}","Metadata"=>"true").read)
572
+ resp = JSON.parse(URI.open("#{base_url}/?#{arg_str}","Metadata"=>"true").read)
526
573
  MU.log "curl -H Metadata:true "+"#{base_url}/?#{arg_str}", loglevel, details: resp
527
574
  if svc != "instance"
528
575
  return resp
@@ -777,6 +824,24 @@ module MU
777
824
  return @@resources_api[credentials]
778
825
  end
779
826
 
827
+ # The Azure KeyVault API
828
+ # @param model [<Azure::Apis::KeyVault::Mgmt::V2018_02_14::Models>]: If specified, will return the class ::Azure::Apis::KeyVault::Mgmt::V2018_02_14::Models::model instead of an API client instance
829
+ # @param model_version [String]: Use an alternative model version supported by the SDK when requesting a +model+
830
+ # @param alt_object [String]: Return an instance of something other than the usual API client object
831
+ # @param credentials [String]: The credential set (subscription, effectively) in which to operate
832
+ # @return [MU::Cloud::Azure::SDKClient]
833
+ def self.keyvault(model = nil, alt_object: nil, credentials: nil, model_version: "V2018_02_14")
834
+ require 'azure_mgmt_key_vault'
835
+
836
+ if model and model.is_a?(Symbol)
837
+ return Object.const_get("Azure").const_get("KeyVault").const_get("Mgmt").const_get(model_version).const_get("Models").const_get(model)
838
+ else
839
+ @@keyvault_api[credentials] ||= MU::Cloud::Azure::SDKClient.new(api: "KeyVault", credentials: credentials, subclass: alt_object)
840
+ end
841
+
842
+ return @@keyvault_api[credentials]
843
+ end
844
+
780
845
  # The Azure Features API
781
846
  # @param model [<Azure::Apis::Features::Mgmt::V2015_12_01::Models>]: If specified, will return the class ::Azure::Apis::Features::Mgmt::V2015_12_01::Models::model instead of an API client instance
782
847
  # @param model_version [String]: Use an alternative model version supported by the SDK when requesting a +model+
@@ -931,6 +996,7 @@ module MU
931
996
  @@resources_api = {}
932
997
  @@containers_api = {}
933
998
  @@features_api = {}
999
+ @@keyvault_api = {}
934
1000
  @@apis_api = {}
935
1001
  @@marketplace_api = {}
936
1002
  @@service_identity_api = {}
@@ -1069,9 +1135,9 @@ module MU
1069
1135
  retry
1070
1136
  end
1071
1137
 
1072
- MU.log "#{@parent.api.class.name}.#{@myname}.#{method_sym.to_s} returned '"+err["code"]+"' - "+err["message"], MU::WARN, details: caller
1073
- MU.log e.backtrace[0], MU::WARN, details: parsed
1074
- raise MU::Cloud::Azure::APIError, err["code"]+": "+err["message"]+" (call was #{@parent.api.class.name}.#{@myname}.#{method_sym.to_s})"
1138
+ # MU.log "#{@parent.api.class.name}.#{@myname}.#{method_sym.to_s} returned '"+err["code"]+"' - "+err["message"], MU::WARN, details: caller
1139
+ # MU.log e.backtrace[0], MU::WARN, details: parsed
1140
+ raise MU::Cloud::Azure::APIError.new err["code"]+": "+err["message"]+" (call was #{@parent.api.class.name}.#{@myname}.#{method_sym.to_s})", details: parsed, silent: true
1075
1141
  end
1076
1142
  end
1077
1143
  rescue JSON::ParserError
@@ -150,7 +150,7 @@ module MU
150
150
 
151
151
  if !@nat.nil? and @nat.mu_name != @mu_name
152
152
  if @nat.cloud_desc.nil?
153
- MU.log "NAT was missing cloud descriptor when called in #{@mu_name}'s getSSHConfig", MU::ERR
153
+ MU.log "NAT #{@nat} was missing cloud descriptor when called in #{@mu_name}'s getSSHConfig", MU::ERR
154
154
  return nil
155
155
  end
156
156
  _foo, _bar, _baz, nat_ssh_host, nat_ssh_user, nat_ssh_key = @nat.getSSHConfig
@@ -220,6 +220,7 @@ module MU
220
220
  # @return [Hash<String,OpenStruct>]: The cloud provider's complete descriptions of matching instances
221
221
  def self.find(**args)
222
222
  found = {}
223
+ MU.log "Azure::Server.find called", MU::NOTICE, details: args
223
224
  # told one, we may have to search all the ones we can see.
224
225
  resource_groups = if args[:resource_group]
225
226
  [args[:resource_group]]
@@ -417,6 +418,7 @@ module MU
417
418
 
418
419
  # return [String]: A password string.
419
420
  def getWindowsAdminPassword
421
+ @deploy.fetchSecret(@mu_name, "windows_admin_password")
420
422
  end
421
423
 
422
424
  # Add a volume to this instance
@@ -612,7 +614,7 @@ module MU
612
614
  ok = false
613
615
  end
614
616
  MU::Config.addDependency(server, server['name']+"vpc", "vpc")
615
- MU::Config.addDependency(server, server['name']+"vpc-natstion", "server", phase: "groom")
617
+ MU::Config.addDependency(server, server['name']+"vpc-natstion", "server", their_phase: "groom")
616
618
  server['vpc'] = {
617
619
  "name" => server['name']+"vpc",
618
620
  "subnet_pref" => "private"
@@ -770,10 +772,23 @@ module MU
770
772
 
771
773
  os_obj = MU::Cloud::Azure.compute(:OSProfile).new
772
774
  if windows?
775
+ winrm_listen = MU::Cloud::Azure.compute(:WinRMListener).new
776
+ winrm_listen.certificate_url = "goddamn stupid ass thing"
777
+ winrm_listen.protocol = "https"
778
+ winrm = MU::Cloud::Azure.compute(:WinRMConfiguration).new
779
+ winrm.listeners = [winrm_listen]
780
+
773
781
  win_obj = MU::Cloud::Azure.compute(:WindowsConfiguration).new
782
+ win_obj.win_rmconfiguration = winrm
774
783
  os_obj.windows_configuration = win_obj
775
784
  os_obj.admin_username = @config['windows_admin_username']
776
- os_obj.admin_password = MU.generateWindowsPassword
785
+ os_obj.admin_password = begin
786
+ @deploy.fetchSecret(@mu_name, "windows_admin_password")
787
+ rescue MU::MommaCat::SecretError
788
+ pw = MU.generateWindowsPassword
789
+ @deploy.saveNodeSecret(@mu_name, pw, "windows_admin_password")
790
+ pw
791
+ end
777
792
  os_obj.computer_name = @deploy.getResourceName(@config["name"], max_length: 15, disallowed_chars: /[~!@#$%^&*()=+_\[\]{}\\\|;:\.'",<>\/\?]/)
778
793
  else
779
794
  os_obj.admin_username = @config['ssh_user']
@@ -304,7 +304,7 @@ module MU
304
304
  role_name: baserole.role_name,
305
305
  policy_name: name
306
306
  )
307
- policies[name] = URI.decode(resp.policy_document)
307
+ policies[name] = CGI.unescape(resp.policy_document)
308
308
  }
309
309
  }
310
310
  end
@@ -348,7 +348,8 @@ module MU
348
348
  # Plant a Mu deploy secret into a storage bucket somewhere for so our kittens can consume it
349
349
  # @param deploy_id [String]: The deploy for which we're writing the secret
350
350
  # @param value [String]: The contents of the secret
351
- def self.writeDeploySecret(deploy_id, value, name = nil, credentials: nil)
351
+ def self.writeDeploySecret(deploy, value, name = nil, credentials: nil)
352
+ deploy_id = deploy.deploy_id
352
353
  name ||= deploy_id+"-secret"
353
354
  begin
354
355
  MU.log "Writing #{name} to Cloud Storage bucket #{adminBucketName(credentials)}"
@@ -487,7 +488,7 @@ MU.log e.message, MU::WARN, details: e.inspect
487
488
  base_url = "http://metadata.google.internal/computeMetadata/v1"
488
489
  begin
489
490
  Timeout.timeout(2) do
490
- response = open(
491
+ response = URI.open(
491
492
  "#{base_url}/#{param}",
492
493
  "Metadata-Flavor" => "Google"
493
494
  ).read
@@ -981,7 +982,20 @@ MU.log e.message, MU::WARN, details: e.inspect
981
982
  end
982
983
 
983
984
  # Google's Cloud Billing Service API
984
- # @param subclass [<Google::Apis::LoggingV2>]: If specified, will return the class ::Google::Apis::LoggingV2::subclass instead of an API client instance
985
+ # @param subclass [<Google::Apis::CloudbillingV1>]: If specified, will return the class ::Google::Apis::CloudbillingV1::subclass instead of an API client instance
986
+ def self.budgets(subclass = nil, credentials: nil)
987
+ require 'google/apis/billingbudgets_v1'
988
+
989
+ if subclass.nil?
990
+ @@budgets_api[credentials] ||= MU::Cloud::Google::GoogleEndpoint.new(api: "BillingbudgetsV1::CloudBillingBudgetService", scopes: ['cloud-platform', 'cloud-billing'], credentials: credentials, masquerade: MU::Cloud::Google.credConfig(credentials)['masquerade_as'])
991
+ return @@budgets_api[credentials]
992
+ elsif subclass.is_a?(Symbol)
993
+ return Object.const_get("::Google").const_get("Apis").const_get("BillingbudgetsV1").const_get(subclass)
994
+ end
995
+ end
996
+
997
+ # Google's Cloud Billing Budget Service API
998
+ # @param subclass [<Google::Apis::CloudbillingV1>]: If specified, will return the class ::Google::Apis::CloudbillingV1::subclass instead of an API client instance
985
999
  def self.billing(subclass = nil, credentials: nil)
986
1000
  require 'google/apis/cloudbilling_v1'
987
1001
 
@@ -1310,7 +1324,7 @@ MU.log e.message, MU::WARN, details: e.inspect
1310
1324
 
1311
1325
  svc_name = Regexp.last_match[1]
1312
1326
  save_verbosity = MU.verbosity
1313
- if svc_name != "servicemanagement.googleapis.com" and method_sym != :delete
1327
+ if !["servicemanagement.googleapis.com", "billingbudgets.googleapis.com"].include?(svc_name) and method_sym != :delete
1314
1328
  retries += 1
1315
1329
  @@enable_semaphores[project].synchronize {
1316
1330
  MU.setLogging(MU::Logger::NORMAL)
@@ -1565,6 +1579,7 @@ MU.log e.message, MU::WARN, details: e.inspect
1565
1579
  @@firestore_api = {}
1566
1580
  @@admin_directory_api = {}
1567
1581
  @@billing_api = {}
1582
+ @@budgets_api = {}
1568
1583
  @@function_api = {}
1569
1584
  end
1570
1585
  end
@@ -265,8 +265,12 @@ module MU
265
265
 
266
266
  if args[:cloud_id]
267
267
  raw_id = args[:cloud_id].sub(/^folders\//, "")
268
- resp = MU::Cloud::Google.folder(credentials: args[:credentials]).get_folder("folders/"+raw_id)
269
- found[resp.name] = resp if resp
268
+ begin
269
+ resp = MU::Cloud::Google.folder(credentials: args[:credentials]).get_folder("folders/"+raw_id)
270
+ found[resp.name] = resp if resp
271
+ rescue ::Google::Apis::ClientError => e
272
+ raise e if e.message !~ /forbidden: /
273
+ end
270
274
 
271
275
  elsif args[:flags] and args[:flags]['display_name']
272
276
 
@@ -119,6 +119,9 @@ module example.com/cloudfunction
119
119
  # Called automatically by {MU::Deploy#createResources}
120
120
  def groom
121
121
  desc = {}
122
+
123
+ func_obj = buildDesc
124
+
122
125
  labels = Hash[@tags.keys.map { |k|
123
126
  [k.downcase, @tags[k].downcase.gsub(/[^-_a-z0-9]/, '-')] }
124
127
  ]
@@ -140,6 +143,10 @@ module example.com/cloudfunction
140
143
  if cloud_desc.available_memory_mb != @config['memory']
141
144
  need_update = true
142
145
  end
146
+ if cloud_desc.service_account_email != func_obj.service_account_email
147
+ need_update = true
148
+ end
149
+
143
150
  if @config['environment_variable']
144
151
  @config['environment_variable'].each { |var|
145
152
  if !cloud_desc.environment_variables or
@@ -161,7 +168,17 @@ module example.com/cloudfunction
161
168
  File.read("#{dir}/current.zip")
162
169
  }
163
170
 
164
- new = if @config['code']['zip_file']
171
+ tempfile = nil
172
+ new = if @config['code']['zip_file'] or @config['code']['path']
173
+ if @config['code']['path']
174
+ tempfile = Tempfile.new(["function", ".zip"])
175
+ MU.log "#{@mu_name} using code at #{@config['code']['path']}"
176
+ MU::Master.zipDir(@config['code']['path'], tempfile.path)
177
+ @config['code']['zip_file'] = tempfile.path
178
+ else
179
+ MU.log "#{@mu_name} using code packaged at #{@config['code']['zip_file']}"
180
+ end
181
+ # @code_sha256 = Base64.encode64(Digest::SHA256.digest(zip)).chomp
165
182
  File.read(@config['code']['zip_file'])
166
183
  elsif @config['code']['gs_url']
167
184
  @config['code']['gs_url'].match(/^gs:\/\/([^\/]+)\/(.*)/)
@@ -172,25 +189,31 @@ module example.com/cloudfunction
172
189
  File.read(dir+"/new.zip")
173
190
  }
174
191
  end
192
+
175
193
  if @config['code']['gs_url'] and
176
194
  (@config['code']['gs_url'] != cloud_desc.source_archive_url or
177
195
  current != new)
178
196
  need_update = true
179
- elsif @config['code']['zip_file'] and current != new
197
+ elsif (@config['code']['zip_file'] or @config['code']['path']) and current != new
180
198
  need_update = true
181
- desc[:source_archive_url] = MU::Cloud::Google::Function.uploadPackage(@config['code']['zip_file'], @mu_name+"-cloudfunction.zip", credentials: @credentials)
199
+ end
200
+
201
+ if @config['vpc_connector']
202
+ if cloud_desc.vpc_connector != @config['vpc_connector'] or
203
+ cloud_desc.vpc_connector_egress_settings != (@config['vpc_connector_allow_all_egress'] ? "ALL_TRAFFIC" : "PRIVATE_RANGES_ONLY")
204
+ need_update = true
205
+ end
182
206
  end
183
207
 
184
208
  if need_update
185
- func_obj = buildDesc
186
- MU.log "Updating Cloud Function #{@mu_name}", MU::NOTICE, details: func_obj
209
+ MU.log "Updating Cloud Function #{@cloud_id}", MU::NOTICE, details: func_obj
187
210
  begin
188
- # MU::Cloud::Google.function(credentials: @credentials).patch_project_location_function(
189
- # @cloud_id,
190
- # func_obj
191
- # )
192
- rescue ::Google::Apis::ClientError
193
- MU.log "Error updating Cloud Function #{@mu_name}.", MU::ERR
211
+ MU::Cloud::Google.function(credentials: @credentials).patch_project_location_function(
212
+ @cloud_id,
213
+ func_obj
214
+ )
215
+ rescue ::Google::Apis::ClientError => e
216
+ MU.log "Error updating Cloud Function #{@mu_name}.", MU::ERR, e.message
194
217
  if desc[:source_archive_url]
195
218
  main_file = nil
196
219
  HELLO_WORLDS.each_pair { |runtime, code|
@@ -207,6 +230,11 @@ module example.com/cloudfunction
207
230
  # service_account_email: sa.kitten.cloud_desc.email,
208
231
  # labels: labels,
209
232
 
233
+ if tempfile
234
+ tempfile.close
235
+ tempfile.unlink
236
+ end
237
+
210
238
  end
211
239
 
212
240
  # Return the metadata for this project's configuration
@@ -354,6 +382,7 @@ module example.com/cloudfunction
354
382
  def self.schema(config)
355
383
  toplevel_required = ["runtime"]
356
384
  schema = {
385
+ "roles" => MU::Cloud.resourceClass("Google", "User").schema(config)[1]["roles"],
357
386
  "triggers" => {
358
387
  "type" => "array",
359
388
  "items" => {
@@ -448,6 +477,7 @@ module example.com/cloudfunction
448
477
  content_type: "application/zip",
449
478
  name: filename
450
479
  )
480
+
451
481
  MU::Cloud::Google.storage(credentials: credentials).insert_object(
452
482
  bucket,
453
483
  obj_obj,
@@ -487,7 +517,7 @@ module example.com/cloudfunction
487
517
  end
488
518
  # XXX list_project_locations
489
519
 
490
- if !function['code'] or (!function['code']['zip_file'] and !function['code']['gs_url'])
520
+ if !function['code'] or (!function['code']['zip_file'] and !function['code']['gs_url'] and !function['code']['path'])
491
521
  MU.log "Must specify a code source in Cloud Function #{function['name']}", MU::ERR
492
522
  ok = false
493
523
  elsif function['code']['zip_file']
@@ -557,22 +587,14 @@ module example.com/cloudfunction
557
587
 
558
588
  location = "projects/"+@config['project']+"/locations/"+@config['region']
559
589
  sa = nil
560
- retries = 0
561
- begin
562
- sa_ref = MU::Config::Ref.get(@config['service_account'])
563
- sa = @deploy.findLitterMate(name: sa_ref.name, type: "users")
564
- if !sa or !sa.cloud_desc
565
- sleep 10
566
- end
567
- rescue ::Google::Apis::ClientError => e
568
- if e.message.match(/notFound:/)
569
- sleep 10
570
- retries += 1
571
- retry
572
- end
573
- end while !sa or !sa.cloud_desc and retries < 5
590
+ need_sa = Proc.new {
591
+ !sa or !sa.kitten or !sa.kitten.cloud_desc
592
+ }
593
+ MU.retrier(loop_if: need_sa, wait: 10, max: 6) { |retries, _wait|
594
+ sa = MU::Config::Ref.get(@config['service_account'])
595
+ }
574
596
 
575
- if !sa or !sa.cloud_desc
597
+ if need_sa.call()
576
598
  raise MuError, "Failed to get service account cloud id from #{@config['service_account'].to_s}"
577
599
  end
578
600
 
@@ -583,7 +605,7 @@ module example.com/cloudfunction
583
605
  # entry_point: "hello_world",
584
606
  entry_point: @config['handler'],
585
607
  description: @deploy.deploy_id,
586
- service_account_email: sa.cloud_desc.email,
608
+ service_account_email: sa.kitten.cloud_desc.email,
587
609
  labels: labels,
588
610
  available_memory_mb: @config['memory']
589
611
  }
@@ -596,7 +618,6 @@ module example.com/cloudfunction
596
618
  if @config['vpc_connector']
597
619
  desc[:vpc_connector] = @config['vpc_connector']
598
620
  desc[:vpc_connector_egress_settings] = @config['vpc_connector_allow_all_egress'] ? "ALL_TRAFFIC" : "PRIVATE_RANGES_ONLY"
599
- pp desc
600
621
  elsif @vpc
601
622
  desc[:network] = @vpc.url.sub(/^.*?\/projects\//, 'projects/')
602
623
  end
@@ -627,8 +648,22 @@ module example.com/cloudfunction
627
648
  # }
628
649
  if @config['code']['gs_url']
629
650
  desc[:source_archive_url] = @config['code']['gs_url']
630
- elsif @config['code']['zip_file']
651
+ elsif @config['code']['zip_file'] or @config['code']['path']
652
+ tempfile = nil
653
+ if @config['code']['path']
654
+ tempfile = Tempfile.new(["function", ".zip"])
655
+ MU.log "#{@mu_name} using code at #{@config['code']['path']}"
656
+ MU::Master.zipDir(@config['code']['path'], tempfile.path)
657
+ @config['code']['zip_file'] = tempfile.path
658
+ else
659
+ MU.log "#{@mu_name} using code packaged at #{@config['code']['zip_file']}"
660
+ end
631
661
  desc[:source_archive_url] = MU::Cloud::Google::Function.uploadPackage(@config['code']['zip_file'], @mu_name+"-cloudfunction.zip", credentials: @credentials)
662
+
663
+ if tempfile
664
+ tempfile.close
665
+ tempfile.unlink
666
+ end
632
667
  end
633
668
 
634
669
  # Dir.mktmpdir(@mu_name) { |dir|