modules/mu/providers/google/function.rb in cloud-mu-3.5.1 vs modules/mu/providers/google/function.rb in cloud-mu-3.6.3

- old
+ new

@@ -118,11 +118,11 @@ # Called automatically by {MU::Deploy#createResources} def groom desc = {} - func_obj = buildDesc + func_obj = buildDesc(true) labels = Hash[@tags.keys.map { |k| [k.downcase, @tags[k].downcase.gsub(/[^-_a-z0-9]/, '-')] } ] labels["name"] = MU::Cloud::Google.nameStr(@mu_name) @@ -159,12 +159,19 @@ if !cloud_desc.event_trigger or cloud_desc.event_trigger.event_type != @config['triggers'].first['event'] or cloud_desc.event_trigger.resource != @config['triggers'].first['resource'] need_update = true end + elsif !cloud_desc.https_trigger + need_update = true end + if (@config['internal_only'] and cloud_desc.ingress_settings != "ALLOW_INTERNAL_ONLY") or + (!@config['internal_only'] and cloud_desc.ingress_settings != "ALLOW_ALL") + need_update = true + end + current = Dir.mktmpdir(@mu_name+"-current") { |dir| MU::Cloud::Google::Function.downloadPackage(@cloud_id, dir+"/current.zip", credentials: @credentials) File.read("#{dir}/current.zip") } @@ -204,10 +211,11 @@ need_update = true end end if need_update + MU::Cloud::Google::Function.uploadPackage(@config['code']['zip_file'], @mu_name+"-cloudfunction.zip", credentials: @credentials) MU.log "Updating Cloud Function #{@cloud_id}", MU::NOTICE, details: func_obj begin MU::Cloud::Google.function(credentials: @credentials).patch_project_location_function( @cloud_id, func_obj @@ -233,10 +241,75 @@ if tempfile tempfile.close tempfile.unlink end + policy = MU::Cloud::Google.function(credentials: @credentials).get_project_location_function_iam_policy(@cloud_id) + + if @config['allow_unauthenticated'] and !allowsUnauthencated? + policy ||= MU::Cloud::Google.function(:Policy).new( + bindings: [] + ) + policy.bindings ||= [] + policy.bindings << MU::Cloud::Google.function(:Binding).new( + members: ["allUsers"], + role: "roles/cloudfunctions.invoker" + ) + + pol_req = MU::Cloud::Google.function(:SetIamPolicyRequest).new( + policy: policy + ) + MU.log "Enabling anonymous invocation of Cloud Function #{@mu_name}", MU::NOTICE + MU::Cloud::Google.function(credentials: @credentials).set_function_iam_policy(@cloud_id, pol_req) + elsif !@config['allow_unauthenticated'] and allowsUnauthencated? + policy.bindings.reject! { |b| + b.members.include?("allUsers") and b.role == "roles/cloudfunctions.invoker" + } + pol_req = MU::Cloud::Google.function(:SetIamPolicyRequest).new( + policy: policy + ) + MU.log "Disabling anonymous invocation of Cloud Function #{@mu_name}", MU::NOTICE + MU::Cloud::Google.function(credentials: @credentials).set_function_iam_policy(@cloud_id, pol_req) + end + + # If we have a loadbalancer configured, attach us to it + if !@config['loadbalancers'].nil? + if @loadbalancers.nil? + raise MuError, "#{@mu_name} is configured to use LoadBalancers, but none have been loaded by dependencies()" + end + + neg_name = @deploy.getResourceName(@config["name"], max_length: 19, never_gen_unique: true).downcase + neg_desc = begin + MU::Cloud::Google.compute(credentials: @config['credentials']).get_region_network_endpoint_group(@project_id, @config['region'], neg_name) + rescue ::Google::Apis::ClientError => e + raise e if e.message !~ /notFound:/ + neg_obj = MU::Cloud::Google.compute(:NetworkEndpointGroup).new( + name: neg_name, + description: @deploy.deploy_id, + cloud_function: MU::Cloud::Google.compute(:NetworkEndpointGroupCloudFunction).new( + function: @cloud_id.gsub(/.*?\//, '') + ), + network_endpoint_type: "SERVERLESS" + ) + MU.log "Creating Network Endpoint Group #{neg_name}", details: neg_obj + MU::Cloud::Google.compute(credentials: @config['credentials']).insert_region_network_endpoint_group(@project_id, @config['region'], neg_obj) + retry + end + + @loadbalancers.each { |lb| +# if !lb.targetgroups +# MU.retrier([], max: 6, wait: 15, loop_if: Proc.new { !lb.targetgroups }) { +# lb.cloud_desc(use_cache: false) +# } +# end +# lb.targetgroups.each_pair { |tg_name, tg| +# addTrigger(tg.target_group_arn, "elasticloadbalancing", tg_name) +# } + lb.registerTarget(neg_desc.self_link) + } + end + end # Return the metadata for this project's configuration # @return [Hash] def notify @@ -328,10 +401,16 @@ bok["runtime"] = cloud_desc.runtime bok["memory"] = cloud_desc.available_memory_mb bok["handler"] = cloud_desc.entry_point bok["timeout"] = cloud_desc.timeout.gsub(/[^\d]/, '').to_i + if cloud_desc.ingress_settings and cloud_desc.ingress_settings == "ALLOW_INTERNAL_ONLY" + bok['internal_only'] = true + else + bok['internal_only'] = false + end + if cloud_desc.vpc_connector bok["vpc_connector"] = cloud_desc.vpc_connector elsif cloud_desc.network cloud_desc.network.match(/^projects\/(.*?)\/.*?\/networks\/([^\/]+)(?:$|\/)/) vpc_proj = Regexp.last_match[1] @@ -348,11 +427,11 @@ ), credentials: @credentials, type: "vpcs" ) end - + if cloud_desc.environment_variables and cloud_desc.environment_variables.size > 0 bok['environment_variable'] = cloud_desc.environment_variables.keys.map { |k| { "key" => k, "value" => cloud_desc.environment_variables[k] } } end if cloud_desc.labels and cloud_desc.labels.size > 0 bok['tags'] = cloud_desc.labels.keys.map { |k| { "key" => k, "value" => cloud_desc.labels[k] } } @@ -371,10 +450,13 @@ return nil if !MU::Cloud::Google::Function.downloadPackage(@cloud_id, codefile, credentials: @config['credentials']) bok['code'] = { 'zip_file' => codefile } + + bok['allow_unauthenticated'] = allowsUnauthencated? + bok end # Cloud-specific configuration properties. # @param config [MU::Config]: The calling MU::Config object @@ -409,10 +491,20 @@ }, "vpc_connector" => { "type" => "string", "description" => "+DEPRECATED+ VPC Connector to attach, of the form +projects/my-project/locations/some-region/connectors/my-connector+. This option will be removed once proper google-cloud-sdk support for VPC Connectors becomes available, at which point we will piggyback on the normal +vpc+ stanza and resolve connectors as needed." }, + "allow_unauthenticated" => { + "type" => "boolean", + "default" => false, + "description" => "Only applicable for HTTPS-triggered functions; allows function invocation without credentials" + }, + "internal_only" => { + "type" => "boolean", + "default" => false, + "description" => "Permit only traffic from VPC networks in the same project or VPC SC perimeter" + }, "vpc_connector_allow_all_egress" => { "type" => "boolean", "default" => false, "description" => "+DEPRECATED+ Allow VPC connector egress traffic to any IP range, instead of just private IPs. This option will be removed once proper google-cloud-sdk support for VPC Connectors becomes available, at which point we will piggyback on the normal +vpc+ stanza and resolve connectors as needed." }, @@ -471,15 +563,17 @@ # @param filename [String]: Target filename # @param credentials [String] # @return [String]: The Cloud Storage URL to the result def self.uploadPackage(zipfile, filename, credentials: nil) bucket = MU::Cloud::Google.adminBucketName(credentials) + obj_obj = MU::Cloud::Google.storage(:Object).new( content_type: "application/zip", name: filename ) + MU.log "Uploading #{zipfile} to #{bucket}/#{filename}" MU::Cloud::Google.storage(credentials: credentials).insert_object( bucket, obj_obj, upload_source: zipfile ) @@ -572,16 +666,37 @@ # else # MU.log "Cloud Function #{function['name']} must declare a VPC", MU::ERR # ok = false # end + if !function["loadbalancers"].nil? + function["loadbalancers"].each { |lb| + lb["name"] ||= lb["concurrent_load_balancer"] + if lb["name"] + MU::Config.addDependency(function, lb["name"], "loadbalancer") + end + } + end + ok end private - def buildDesc + def allowsUnauthencated? + policy = MU::Cloud::Google.function(credentials: @credentials).get_project_location_function_iam_policy(@cloud_id) + if policy and policy.bindings + policy.bindings.each { |b| + if b.members.include?("allUsers") and b.role == "roles/cloudfunctions.invoker" + return true + end + } + end + false + end + + def buildDesc(no_upload = false) labels = Hash[@tags.keys.map { |k| [k.downcase, @tags[k].downcase.gsub(/[^-_a-z0-9]/, '-')] } ] labels["name"] = MU::Cloud::Google.nameStr(@mu_name) @@ -629,11 +744,17 @@ ) else desc[:https_trigger] = MU::Cloud::Google.function(:HttpsTrigger).new end + if @config["internal_only"] + desc[:ingress_settings] = "ALLOW_INTERNAL_ONLY" + else + desc[:ingress_settings] = "ALLOW_ALL" + end + if @config['environment_variable'] @config['environment_variable'].each { |var| desc[:environment_variables] ||= {} desc[:environment_variables][var["key"].to_s] = var["value"].to_s } @@ -656,10 +777,15 @@ MU::Master.zipDir(@config['code']['path'], tempfile.path) @config['code']['zip_file'] = tempfile.path else MU.log "#{@mu_name} using code packaged at #{@config['code']['zip_file']}" end - desc[:source_archive_url] = MU::Cloud::Google::Function.uploadPackage(@config['code']['zip_file'], @mu_name+"-cloudfunction.zip", credentials: @credentials) + bucket = MU::Cloud::Google.adminBucketName(credentials) + + desc[:source_archive_url] = "gs://#{bucket}/#{@mu_name}-cloudfunction.zip" + if !no_upload + MU::Cloud::Google::Function.uploadPackage(@config['code']['zip_file'], @mu_name+"-cloudfunction.zip", credentials: @credentials) + end if tempfile tempfile.close tempfile.unlink end