"use strict";
// Copyright 2016-2018, Pulumi Corporation.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
Object.defineProperty(exports, "__esModule", { value: true });
exports.HttpCallbackFunction = exports.CallbackFunction = void 0;
const pulumi = require("@pulumi/pulumi");
const cloudfunctions = require(".");
const storage = require("../storage");
const filepath = require("path");
const utils = require("../utils");
/**
 * A CallbackFunction is a special type of gcp.cloudfunctions.Function that can be created out of an
 * actual JavaScript function instance.  The function instance will be analyzed and packaged up
 * (including dependencies) into a form that can be used by Cloud Functions.  See
 * https://github.com/pulumi/docs/blob/master/reference/serializing-functions.md for additional
 * details on this process.
 *
 * Note: CallbackFunctions create Google Cloud Functions that uses the [nodejs8] runtime.
 * Importantly, calls follow the `(data, context) => ...` form, not the `(event, callback) => ...`
 * form that is used with the [nodejs6] runtime.  This also adds support for asynchronous functions
 * as well. See
 * https://cloud.google.com/functions/docs/writing/background#functions_background_parameters-node8
 * for more details.
 */
class CallbackFunction extends pulumi.ComponentResource {
    constructor(name, args, opts = {}) {
        super("gcp:cloudfunctions:CallbackFunction", name, undefined, opts);
        const parentOpts = { parent: this };
        const handlerName = "handler";
        const serializedFileNameNoExtension = "index";
        const func = args.callback || args.callbackFactory;
        if (!func) {
            throw new Error("One of [callback] or [callbackFactory] must be provided.");
        }
        const closure = pulumi.runtime.serializeFunction(func, {
            serialize: _ => true,
            exportName: handlerName,
            isFactoryFunction: !!args.callbackFactory,
        });
        const codePaths = computeCodePaths(closure, serializedFileNameNoExtension, args.codePathOptions);
        // https://cloud.google.com/storage/docs/naming
        //
        // Bucket names must contain only lowercase letters, numbers, dashes (-), underscores (_),
        // and dots.
        const bucketName = name.toLowerCase().replace(/[^-_a-z0-9]/gi, "-");
        this.bucket = args.bucket || new storage.Bucket(bucketName, {
            project: args.project,
            location: args.location || "US", // The default of "US" here is what it previously defaulted to
        }, parentOpts);
        this.bucketObject = new storage.BucketObject(`${name}`, {
            bucket: this.bucket.name,
            source: new pulumi.asset.AssetArchive(codePaths),
        }, parentOpts);
        const argsCopy = {
            ...args
        };
        // remove the values we've added on top of everything.
        delete argsCopy.callback;
        delete argsCopy.callbackFactory;
        delete argsCopy.codePathOptions;
        // Function names : can only contain letters, numbers, underscores and hyphens
        const functionName = name.replace(/[^-_a-z0-9]/gi, "-");
        this.function = new cloudfunctions.Function(functionName, {
            ...argsCopy,
            runtime: utils.ifUndefined(args.runtime, "nodejs18"),
            entryPoint: handlerName,
            sourceArchiveBucket: this.bucket.name,
            sourceArchiveObject: this.bucketObject.name,
        }, parentOpts);
        const invoker = new cloudfunctions.FunctionIamMember(`${functionName}-invoker`, {
            project: this.function.project,
            region: this.function.region,
            cloudFunction: this.function.name,
            role: utils.ifUndefined(args.iamRole, "roles/cloudfunctions.invoker"),
            member: utils.ifUndefined(args.iamMember, "allUsers")
        }, {
            ...parentOpts,
            /**
             * This alias exists to be backwards compatible for previous versions that did
             * not set a parent on this resource.
             * https://github.com/pulumi/pulumi-gcp/issues/543
             */
            aliases: [{ parent: undefined }],
        });
        this.registerOutputs();
    }
}
exports.CallbackFunction = CallbackFunction;
/**
 * An http-triggered Cloud-Function that, when invoked, will execute the code supplied by a
 * user-provided JavaScript-Function.  To handle HTTP, Cloud Functions uses Express 4.16.3.
 *
 * You invoke HTTP functions from standard HTTP requests. These HTTP requests wait for the response
 * and support handling of common HTTP request methods like GET, PUT, POST, DELETE and OPTIONS. When
 * you use Cloud Functions, a TLS certificate is automatically provisioned for you, so all HTTP
 * functions can be invoked via a secure connection.
 *
 * See more information at: https://cloud.google.com/functions/docs/writing/http
 */
class HttpCallbackFunction extends CallbackFunction {
    constructor(name, callbackOrArgs, opts = {}) {
        const argsCopy = callbackOrArgs instanceof Function
            ? { callback: callbackOrArgs, triggerHttp: true }
            : { ...callbackOrArgs, triggerHttp: true };
        super(name, argsCopy, opts);
        this.httpsTriggerUrl = this.function.httpsTriggerUrl;
    }
}
exports.HttpCallbackFunction = HttpCallbackFunction;
// computeCodePaths calculates an AssetMap of files to include in the Function package.
async function computeCodePaths(closure, serializedFileNameNoExtension, codePathOptions = {}) {
    codePathOptions.extraIncludePaths = codePathOptions.extraIncludePaths || [];
    codePathOptions.extraIncludePackages = codePathOptions.extraIncludePackages || [];
    codePathOptions.extraExcludePackages = codePathOptions.extraExcludePackages || [];
    if (codePathOptions.extraIncludePackages.length > 0) {
        throw new pulumi.ResourceError("codePathOptions.extraIncludePackages not currently supported in GCP.", codePathOptions.logResource);
    }
    const serializedFunction = await closure;
    const excludedPackages = new Set();
    for (const p of codePathOptions.extraExcludePackages) {
        excludedPackages.add(p);
    }
    // Include packages to package.json file
    const packageJson = await producePackageJson(excludedPackages);
    return {
        // Always include the serialized function.
        [serializedFileNameNoExtension + ".js"]: new pulumi.asset.StringAsset(serializedFunction.text),
        // Include package.json and GCP will install packages for us
        ["package.json"]: new pulumi.asset.StringAsset(packageJson),
        // Include files added by user
        ...processExtraIncludePaths(codePathOptions.extraIncludePaths)
    };
}
// Get the list of packages declare in package.json, exclude pulumi and exclusions made by user,
// generate the new package.json to be uploaded to GCP.
// GCP will restore the packages itself.
async function producePackageJson(excludedPackages) {
    const PackageJson = require("@npmcli/package-json");
    const pkgJson = await PackageJson.load('./');
    const packageJson = pkgJson.content;
    // Override dependencies by removing @pulumi and excludedPackages
    const dependencies = Object.keys(packageJson.dependencies || {})
        .filter(pkg => !excludedPackages.has(pkg) && !pkg.startsWith("@pulumi"))
        .reduce((obj, key) => {
        obj[key] = packageJson.dependencies[key];
        return obj;
    }, {});
    return JSON.stringify({
        dependencies: dependencies,
    });
}
// Create file assets from specified by user list,
// files will be added to the main directory under the file name thus duplicates will be overridden
// e.g. ['./dir1/file1', './file1'] will add only the last file.
function processExtraIncludePaths(extraIncludePaths) {
    const filesToInclude = {};
    for (const fullPath of extraIncludePaths) {
        const fileName = filepath.basename(fullPath);
        filesToInclude[fileName] = new pulumi.asset.FileAsset(fullPath);
    }
    return filesToInclude;
}
//# sourceMappingURL=zMixins.js.map