cloud-mu 3.4.0 → 3.5.0

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