text: 'use strict';
  
  const AWS = require('aws-sdk');
  const BbPromise = require('bluebird');
  const crypto = require('crypto');
  const fs = require('fs');
  const _ = require('lodash');
  const path = require('path');
  const ServerlessError = require('../../../../serverless-error');
  const deepSortObjectByKey = require('../../../../utils/deepSortObjectByKey');
  const getHashForFilePath = require('../lib/getHashForFilePath');
  const parseS3URI = require('../../utils/parse-s3-uri');
  const { legacy, log } = require('@serverless/utils/log');
  
  class AwsCompileFunctions {
    constructor(serverless, options) {
      this.serverless = serverless;
      this.options = options;
      const serviceDir = this.serverless.serviceDir || '';
      this.packagePath =
        this.serverless.service.package.path || path.join(serviceDir || '.', '.serverless');
  
      this.provider = this.serverless.getProvider('aws');
  
      this.ensureTargetExecutionPermission = _.memoize(this.ensureTargetExecutionPermission);
      if (
        this.serverless.service.provider.name === 'aws' &&
        this.serverless.service.provider.versionFunctions == null
      ) {
        this.serverless.service.provider.versionFunctions = true;
      }
  
      this.hooks = {
        'initialize': () => {
          if (_.get(this.serverless.service.serviceObject, 'awsKmsKeyArn')) {
            this.serverless._logDeprecation(
              'AWS_KMS_KEY_ARN',
              'Starting with next major version, ' +
                '"awsKmsKeyArn" service property will be replaced by "provider.kmsKeyArn"'
            );
          }
          if (
            Object.values(this.serverless.service.functions).some(({ awsKmsKeyArn }) => awsKmsKeyArn)
          ) {
            this.serverless._logDeprecation(
              'AWS_KMS_KEY_ARN',
              'Starting with next major version, ' +
                '"awsKmsKeyArn" function property will be replaced by "kmsKeyArn"'
            );
          }
          if (
            !this.serverless.service.provider.lambdaHashingVersion &&
            !this.options['enforce-hash-update'] &&
            Object.keys(this.serverless.service.functions).length &&
            Object.values(this.serverless.service.functions).some(({ handler }) => handler) &&
            (this.serverless.service.provider.versionFunctions ||
              Object.values(this.serverless.service.functions).some(
                ({ versionFunction }) => versionFunction
              ))
          ) {
            this.serverless._logDeprecation(
              'LAMBDA_HASHING_VERSION_V2',
              'Resolution of lambda version hashes was improved with better algorithm, ' +
                'which will be used in next major release.\n' +
                'Switch to it now by setting "provider.lambdaHashingVersion" to "20201221".\n' +
                'While it is highly encouraged to upgrade to new algorithm, you can still use the old approach by setting "provider.lambdaHashingVersion" to "20200924".'
            );
          }
          if (
            !this.serverless.service.provider.runtime &&
            Object.keys(this.serverless.service.functions).length &&
            Object.values(this.serverless.service.functions).some(
              ({ handler, runtime }) => handler && !runtime
            )
          ) {
            this.serverless._logDeprecation(
              'CHANGE_OF_DEFAULT_RUNTIME_TO_NODEJS14X',
              'Starting with `v3.0.0`, the default runtime will change from `nodejs12.x` to `nodejs14.x`. In order to hide the deprecation message and ensure seamless upgrade, please set the runtime explicitly.'
            );
          }
        },
        'package:compileFunctions': async () =>
          BbPromise.bind(this).then(this.downloadPackageArtifacts).then(this.compileFunctions),
      };
    }
  
    compileRole(newFunction, role) {
      const compiledFunction = newFunction;
      if (typeof role === 'string') {
        if (role.startsWith('arn:')) {
          // role is a statically defined iam arn
          compiledFunction.Properties.Role = role;
        } else if (role === 'IamRoleLambdaExecution') {
          // role is the default role generated by the framework
          compiledFunction.Properties.Role = { 'Fn::GetAtt': [role, 'Arn'] };
        } else {
          // role is a Logical Role Name
          compiledFunction.Properties.Role = { 'Fn::GetAtt': [role, 'Arn'] };
          compiledFunction.DependsOn = (compiledFunction.DependsOn || []).concat(role);
        }
      } else if ('Fn::GetAtt' in role) {
        // role is an "Fn::GetAtt" object
        compiledFunction.Properties.Role = role;
        compiledFunction.DependsOn = (compiledFunction.DependsOn || []).concat(role['Fn::GetAtt'][0]);
      } else {
        // role is an "Fn::ImportValue" or "Fn::Sub" object
        compiledFunction.Properties.Role = role;
      }
    }
  
    async downloadPackageArtifact(functionName) {
      const { region } = this.options;
      const S3 = new AWS.S3({ region });
  
      const functionObject = this.serverless.service.getFunction(functionName);
      if (functionObject.image) return;
  
      const artifactFilePath =
        _.get(functionObject, 'package.artifact') ||
        _.get(this, 'serverless.service.package.artifact');
  
      const s3Object = parseS3URI(artifactFilePath);
      if (!s3Object) return;
      if (process.env.SLS_DEBUG) {
        legacy.log(`Downloading ${s3Object.Key} from bucket ${s3Object.Bucket}`);
      }
      log.info(`Downloading ${s3Object.Key} from bucket ${s3Object.Bucket}`);
      await new Promise((resolve, reject) => {
        const tmpDir = this.serverless.utils.getTmpDirPath();
        const filePath = path.join(tmpDir, path.basename(s3Object.Key));
  
        const readStream = S3.getObject(s3Object).createReadStream();
  
        const writeStream = fs.createWriteStream(filePath);
        readStream
          .pipe(writeStream)
          .on('error', reject)
          .on('close', () => {
            if (functionObject.package.artifact) {
              functionObject.package.artifact = filePath;
            } else {
              this.serverless.service.package.artifact = filePath;
            }
            return resolve(filePath);
          });
      });
    }
  
    async addFileToHash(filePath, hash) {
      const lambdaHashingVersion = this.serverless.service.provider.lambdaHashingVersion;
      if (lambdaHashingVersion >= 20201221 || this.options['enforce-hash-update']) {
        const filePathHash = await getHashForFilePath(filePath);
        hash.write(filePathHash);
      } else {
        await addFileContentsToHashes(filePath, [hash]);
      }
    }
  
    async compileFunction(functionName) {
      const cfTemplate = this.serverless.service.provider.compiledCloudFormationTemplate;
      const functionResource = this.cfLambdaFunctionTemplate();
      const functionObject = this.serverless.service.getFunction(functionName);
      functionObject.package = functionObject.package || {};
      const enforceHashUpdate = this.options['enforce-hash-update'];
  
      if (!functionObject.handler && !functionObject.image) {
        throw new ServerlessError(
          `Either "handler" or "image" property needs to be set on function "${functionName}"`,
          'FUNCTION_NEITHER_HANDLER_NOR_IMAGE_DEFINED_ERROR'
        );
      }
      if (functionObject.handler && functionObject.image) {
        throw new ServerlessError(
          `Either "handler" or "image" property (not both) needs to be set on function "${functionName}".`,
          'FUNCTION_BOTH_HANDLER_AND_IMAGE_DEFINED_ERROR'
        );
      }
  
      let functionImageUri;
      let functionImageSha;
  
      if (functionObject.image) {
        ({ functionImageUri, functionImageSha } = await this.provider.resolveImageUriAndSha(
          functionName
        ));
        if (_.isObject(functionObject.image)) {
          const imageConfig = {};
          if (functionObject.image.command) {
            imageConfig.Command = functionObject.image.command;
          }
  
          if (functionObject.image.entryPoint) {
            imageConfig.EntryPoint = functionObject.image.entryPoint;
          }
  
          if (functionObject.image.workingDirectory) {
            imageConfig.WorkingDirectory = functionObject.image.workingDirectory;
          }
  
          if (Object.keys(imageConfig).length) {
            functionResource.Properties.ImageConfig = imageConfig;
          }
        }
      }
  
      // publish these properties to the platform
      functionObject.memory =
        functionObject.memorySize || this.serverless.service.provider.memorySize || 1024;
      if (!functionObject.timeout) {
        functionObject.timeout = this.serverless.service.provider.timeout || 6;
      }
  
      let artifactFilePath;
  
      if (functionObject.handler) {
        const serviceArtifactFileName = this.provider.naming.getServiceArtifactName();
        const functionArtifactFileName = this.provider.naming.getFunctionArtifactName(functionName);
  
        artifactFilePath =
          functionObject.package.artifact || this.serverless.service.package.artifact;
  
        if (
          !artifactFilePath ||
          (this.serverless.service.artifact && !functionObject.package.artifact)
        ) {
          let artifactFileName = serviceArtifactFileName;
          if (this.serverless.service.package.individually || functionObject.package.individually) {
            artifactFileName = functionArtifactFileName;
          }
  
          artifactFilePath = path.join(this.serverless.serviceDir, '.serverless', artifactFileName);
        }
  
        functionObject.runtime = this.provider.getRuntime(functionObject.runtime);
        functionResource.Properties.Handler = functionObject.handler;
        functionResource.Properties.Code.S3Bucket = this.serverless.service.package.deploymentBucket
          ? this.serverless.service.package.deploymentBucket
          : { Ref: 'ServerlessDeploymentBucket' };
  
        functionResource.Properties.Code.S3Key = `${
          this.serverless.service.package.artifactDirectoryName
        }/${artifactFilePath.split(path.sep).pop()}`;
        functionResource.Properties.Runtime = functionObject.runtime;
      } else {
        functionResource.Properties.Code.ImageUri = functionImageUri;
        functionResource.Properties.PackageType = 'Image';
      }
      functionResource.Properties.FunctionName = functionObject.name;
      functionResource.Properties.MemorySize = functionObject.memory;
      functionResource.Properties.Timeout = functionObject.timeout;
  
      const functionArchitecture =
        functionObject.architecture || this.serverless.service.provider.architecture;
      if (functionArchitecture) functionResource.Properties.Architectures = [functionArchitecture];
  
      if (functionObject.description) {
        functionResource.Properties.Description = functionObject.description;
      }
  
      if (functionObject.condition) {
        functionResource.Condition = functionObject.condition;
      }
  
      if (functionObject.dependsOn) {
        functionResource.DependsOn = (functionResource.DependsOn || []).concat(
          functionObject.dependsOn
        );
      }
  
      if (functionObject.tags || this.serverless.service.provider.tags) {
        const tags = Object.assign({}, this.serverless.service.provider.tags, functionObject.tags);
        functionResource.Properties.Tags = [];
        Object.entries(tags).forEach(([Key, Value]) => {
          functionResource.Properties.Tags.push({ Key, Value });
        });
      }
  
      if (functionObject.onError) {
        const arn = functionObject.onError;
  
        if (typeof arn === 'string') {
          const iamRoleLambdaExecution = cfTemplate.Resources.IamRoleLambdaExecution;
          functionResource.Properties.DeadLetterConfig = {
            TargetArn: arn,
          };
  
          // update the PolicyDocument statements (if default policy is used)
          if (iamRoleLambdaExecution) {
            iamRoleLambdaExecution.Properties.Policies[0].PolicyDocument.Statement.push({
              Effect: 'Allow',
              Action: ['sns:Publish'],
              Resource: [arn],
            });
          }
        } else {
          functionResource.Properties.DeadLetterConfig = {
            TargetArn: arn,
          };
        }
      }
  
      let kmsKeyArn;
      const serviceObj = this.serverless.service.serviceObject;
      if (serviceObj && serviceObj.awsKmsKeyArn) kmsKeyArn = serviceObj.awsKmsKeyArn;
      if (this.serverless.service.provider.kmsKeyArn) {
        kmsKeyArn = this.serverless.service.provider.kmsKeyArn;
      }
      if (functionObject.awsKmsKeyArn) kmsKeyArn = functionObject.awsKmsKeyArn;
      if (functionObject.kmsKeyArn) kmsKeyArn = functionObject.kmsKeyArn;
  
      if (kmsKeyArn) {
        if (typeof kmsKeyArn === 'string') {
          functionResource.Properties.KmsKeyArn = kmsKeyArn;
  
          // update the PolicyDocument statements (if default policy is used)
          const iamRoleLambdaExecution = cfTemplate.Resources.IamRoleLambdaExecution;
          if (iamRoleLambdaExecution) {
            iamRoleLambdaExecution.Properties.Policies[0].PolicyDocument.Statement = _.unionWith(
              iamRoleLambdaExecution.Properties.Policies[0].PolicyDocument.Statement,
              [
                {
                  Effect: 'Allow',
                  Action: ['kms:Decrypt'],
                  Resource: [kmsKeyArn],
                },
              ],
              _.isEqual
            );
          }
        } else {
          functionResource.Properties.KmsKeyArn = kmsKeyArn;
        }
      }
  
      const tracing =
        functionObject.tracing ||
        (this.serverless.service.provider.tracing && this.serverless.service.provider.tracing.lambda);
  
      if (tracing) {
        let mode = tracing;
  
        if (typeof tracing === 'boolean') {
          mode = 'Active';
        }
  
        const iamRoleLambdaExecution = cfTemplate.Resources.IamRoleLambdaExecution;
  
        functionResource.Properties.TracingConfig = {
          Mode: mode,
        };
  
        const stmt = {
          Effect: 'Allow',
          Action: ['xray:PutTraceSegments', 'xray:PutTelemetryRecords'],
          Resource: ['*'],
        };
  
        // update the PolicyDocument statements (if default policy is used)
        if (iamRoleLambdaExecution) {
          iamRoleLambdaExecution.Properties.Policies[0].PolicyDocument.Statement = _.unionWith(
            iamRoleLambdaExecution.Properties.Policies[0].PolicyDocument.Statement,
            [stmt],
            _.isEqual
          );
        }
      }
  
      if (functionObject.environment || this.serverless.service.provider.environment) {
        functionResource.Properties.Environment = {};
        functionResource.Properties.Environment.Variables = Object.assign(
          {},
          this.serverless.service.provider.environment,
          functionObject.environment
        );
      }
  
      const role = this.provider.getCustomExecutionRole(functionObject);
      this.compileRole(functionResource, role || 'IamRoleLambdaExecution');
  
      // ensure provider VPC is not used if function VPC explicitly unset
      if (functionObject.vpc !== null && functionObject.vpc !== false) {
        if (!functionObject.vpc) functionObject.vpc = {};
        if (!this.serverless.service.provider.vpc) this.serverless.service.provider.vpc = {};
  
        functionResource.Properties.VpcConfig = {
          SecurityGroupIds:
            functionObject.vpc.securityGroupIds ||
            this.serverless.service.provider.vpc.securityGroupIds,
          SubnetIds: functionObject.vpc.subnetIds || this.serverless.service.provider.vpc.subnetIds,
        };
  
        if (
          !functionResource.Properties.VpcConfig.SecurityGroupIds ||
          !functionResource.Properties.VpcConfig.SubnetIds
        ) {
          delete functionResource.Properties.VpcConfig;
        }
      }
  
      const fileSystemConfig = functionObject.fileSystemConfig;
  
      if (fileSystemConfig) {
        if (!functionResource.Properties.VpcConfig) {
          const errorMessage = [
            `Function "${functionName}": when using fileSystemConfig, `,
            'ensure that function has vpc configured ',
            'on function or provider level',
          ].join('');
          throw new ServerlessError(errorMessage, 'LAMBDA_FILE_SYSTEM_CONFIG_MISSING_VPC');
        }
  
        const iamRoleLambdaExecution = cfTemplate.Resources.IamRoleLambdaExecution;
  
        const stmt = {
          Effect: 'Allow',
          Action: ['elasticfilesystem:ClientMount', 'elasticfilesystem:ClientWrite'],
          Resource: [fileSystemConfig.arn],
        };
  
        // update the PolicyDocument statements (if default policy is used)
        if (iamRoleLambdaExecution) {
          iamRoleLambdaExecution.Properties.Policies[0].PolicyDocument.Statement.push(stmt);
        }
  
        const cfFileSystemConfig = {
          Arn: fileSystemConfig.arn,
          LocalMountPath: fileSystemConfig.localMountPath,
        };
  
        functionResource.Properties.FileSystemConfigs = [cfFileSystemConfig];
      }
  
      if (functionObject.reservedConcurrency || functionObject.reservedConcurrency === 0) {
        functionResource.Properties.ReservedConcurrentExecutions = functionObject.reservedConcurrency;
      }
  
      if (!functionObject.disableLogs) {
        functionResource.DependsOn = [this.provider.naming.getLogGroupLogicalId(functionName)].concat(
          functionResource.DependsOn || []
        );
      }
  
      if (functionObject.layers) {
        functionResource.Properties.Layers = functionObject.layers;
      } else if (this.serverless.service.provider.layers) {
        functionResource.Properties.Layers = this.serverless.service.provider.layers;
      }
  
      const functionLogicalId = this.provider.naming.getLambdaLogicalId(functionName);
      const newFunctionObject = {
        [functionLogicalId]: functionResource,
      };
  
      Object.assign(cfTemplate.Resources, newFunctionObject);
  
      const shouldVersionFunction =
        functionObject.versionFunction != null
          ? functionObject.versionFunction
          : this.serverless.service.provider.versionFunctions;
  
      if (shouldVersionFunction || functionObject.provisionedConcurrency) {
        // Create hashes for the artifact and the logical id of the version resource
        // The one for the version resource must include the function configuration
        // to make sure that a new version is created on configuration changes and
        // not only on source changes.
  
        if (enforceHashUpdate) {
          functionResource.Properties.Description = 'temporary-description-to-enforce-hash-update';
        }
  
        const versionHash = crypto.createHash('sha256');
        versionHash.setEncoding('base64');
        const layerConfigurations = _.cloneDeep(
          extractLayerConfigurationsFromFunction(functionResource.Properties, cfTemplate)
        );
  
        const versionResource = this.cfLambdaVersionTemplate();
  
        if (functionImageSha) {
          versionResource.Properties.CodeSha256 = functionImageSha;
        } else {
          const fileHash = await getHashForFilePath(artifactFilePath);
          versionResource.Properties.CodeSha256 = fileHash;
  
          await this.addFileToHash(artifactFilePath, versionHash);
        }
        // Include all referenced layer code in the version id hash
        const layerArtifactPaths = [];
        layerConfigurations.forEach((layer) => {
          const layerArtifactPath = this.provider.resolveLayerArtifactName(layer.name);
          layerArtifactPaths.push(layerArtifactPath);
        });
  
        for (const layerArtifactPath of layerArtifactPaths.sort()) {
          await this.addFileToHash(layerArtifactPath, versionHash);
        }
  
        // Include function and layer configuration details in the version id hash
        for (const layerConfig of layerConfigurations) {
          delete layerConfig.properties.Content.S3Key;
        }
  
        const functionProperties = _.cloneDeep(functionResource.Properties);
        // In `image` case, we assume it's path to ECR image digest
        if (!functionObject.image) delete functionProperties.Code;
        // Properties applied to function globally (not specific to version or alias)
        delete functionProperties.ReservedConcurrentExecutions;
        delete functionProperties.Tags;
  
        const lambdaHashingVersion = this.serverless.service.provider.lambdaHashingVersion;
        if (lambdaHashingVersion >= 20201221 || enforceHashUpdate) {
          functionProperties.layerConfigurations = layerConfigurations;
          versionHash.write(JSON.stringify(deepSortObjectByKey(functionProperties)));
        } else {
          // sort the layer configurations for hash consistency
          const sortedLayerConfigurations = {};
          const byKey = ([key1], [key2]) => key1.localeCompare(key2);
          for (const { name, properties: layerProperties } of layerConfigurations) {
            sortedLayerConfigurations[name] = _.fromPairs(
              Object.entries(layerProperties).sort(byKey)
            );
          }
          functionProperties.layerConfigurations = sortedLayerConfigurations;
          const sortedFunctionProperties = _.fromPairs(
            Object.entries(functionProperties).sort(byKey)
          );
  
          versionHash.write(JSON.stringify(sortedFunctionProperties));
        }
  
        versionHash.end();
        const versionDigest = versionHash.read();
  
        versionResource.Properties.FunctionName = { Ref: functionLogicalId };
        if (functionObject.description) {
          versionResource.Properties.Description = functionObject.description;
        }
  
        // use the version SHA in the logical resource ID of the version because
        // AWS::Lambda::Version resource will not support updates
        const versionLogicalId = this.provider.naming.getLambdaVersionLogicalId(
          functionName,
          versionDigest
        );
        functionObject.versionLogicalId = versionLogicalId;
        const newVersionObject = {
          [versionLogicalId]: versionResource,
        };
  
        Object.assign(cfTemplate.Resources, newVersionObject);
  
        // Add function versions to Outputs section
        const functionVersionOutputLogicalId =
          this.provider.naming.getLambdaVersionOutputLogicalId(functionName);
        const newVersionOutput = this.cfOutputLatestVersionTemplate();
  
        newVersionOutput.Value = { Ref: versionLogicalId };
  
        Object.assign(cfTemplate.Outputs, {
          [functionVersionOutputLogicalId]: newVersionOutput,
        });
  
        if (functionObject.provisionedConcurrency) {
          if (!shouldVersionFunction) delete versionResource.DeletionPolicy;
  
          const provisionedConcurrency = Number(functionObject.provisionedConcurrency);
          const aliasLogicalId =
            this.provider.naming.getLambdaProvisionedConcurrencyAliasLogicalId(functionName);
          const aliasName = this.provider.naming.getLambdaProvisionedConcurrencyAliasName();
  
          functionObject.targetAlias = { name: aliasName, logicalId: aliasLogicalId };
  
          const aliasResource = {
            Type: 'AWS::Lambda::Alias',
            Properties: {
              FunctionName: { Ref: functionLogicalId },
              FunctionVersion: { 'Fn::GetAtt': [versionLogicalId, 'Version'] },
              Name: aliasName,
              ProvisionedConcurrencyConfig: {
                ProvisionedConcurrentExecutions: provisionedConcurrency,
              },
            },
            DependsOn: functionLogicalId,
          };
  
          cfTemplate.Resources[aliasLogicalId] = aliasResource;
        }
      }
  
      this.compileFunctionEventInvokeConfig(functionName);
    }
  
    compileFunctionEventInvokeConfig(functionName) {
      const functionObject = this.serverless.service.getFunction(functionName);
      const { destinations, maximumEventAge, maximumRetryAttempts } = functionObject;
  
      if (!destinations && !maximumEventAge && maximumRetryAttempts == null) {
        return;
      }
  
      const destinationConfig = {};
  
      if (destinations) {
        const executionRole = this.provider.getCustomExecutionRole(functionObject);
        const hasAccessPoliciesHandledExternally = Boolean(executionRole);
  
        if (destinations.onSuccess) {
          if (!hasAccessPoliciesHandledExternally) {
            this.ensureTargetExecutionPermission(destinations.onSuccess);
          }
          destinationConfig.OnSuccess = {
            Destination: destinations.onSuccess.startsWith('arn:')
              ? destinations.onSuccess
              : this.provider.resolveFunctionArn(destinations.onSuccess),
          };
        }
  
        if (destinations.onFailure) {
          if (!hasAccessPoliciesHandledExternally) {
            this.ensureTargetExecutionPermission(destinations.onFailure);
          }
          destinationConfig.OnFailure = {
            Destination: destinations.onFailure.startsWith('arn:')
              ? destinations.onFailure
              : this.provider.resolveFunctionArn(destinations.onFailure),
          };
        }
      }
  
      const cfResources = this.serverless.service.provider.compiledCloudFormationTemplate.Resources;
      const functionLogicalId = this.provider.naming.getLambdaLogicalId(functionName);
  
      const resource = {
        Type: 'AWS::Lambda::EventInvokeConfig',
        Properties: {
          FunctionName: { Ref: functionLogicalId },
          DestinationConfig: destinationConfig,
          Qualifier: functionObject.targetAlias ? functionObject.targetAlias.name : '$LATEST',
        },
        DependsOn: _.get(functionObject.targetAlias, 'logicalId'),
      };
  
      if (maximumEventAge) {
        resource.Properties.MaximumEventAgeInSeconds = maximumEventAge;
      }
  
      if (maximumRetryAttempts != null) {
        resource.Properties.MaximumRetryAttempts = maximumRetryAttempts;
      }
  
      cfResources[this.provider.naming.getLambdaEventConfigLogicalId(functionName)] = resource;
    }
  
    // Memoized in a constructor
    ensureTargetExecutionPermission(functionAddress) {
      const iamPolicyStatements =
        this.serverless.service.provider.compiledCloudFormationTemplate.Resources
          .IamRoleLambdaExecution.Properties.Policies[0].PolicyDocument.Statement;
  
      const action = (() => {
        if (!functionAddress.startsWith('arn:') || functionAddress.includes(':function:')) {
          return 'lambda:InvokeFunction';
        }
        if (functionAddress.includes(':sqs:')) return 'sqs:SendMessage';
        if (functionAddress.includes(':sns:')) return 'sns:Publish';
        if (functionAddress.includes(':event-bus/')) return 'events:PutEvents';
        throw new ServerlessError(
          `Unsupported destination target ${functionAddress}`,
          'UNSUPPORTED_DESTINATION_TARGET'
        );
      })();
      iamPolicyStatements.push({
        Effect: 'Allow',
        Action: action,
        // Note: Cannot address function via { 'Fn::GetAtt': [targetLogicalId, 'Arn'] }
        // as same IAM settings are used for target function and that will introduce
        // circular dependency error. Relying on Fn::Sub as a workaround
        Resource: functionAddress.startsWith('arn:')
          ? functionAddress
          : {
              'Fn::Sub': `arn:\${AWS::Partition}:lambda:\${AWS::Region}:\${AWS::AccountId}:function:${
                this.serverless.service.getFunction(functionAddress).name
              }`,
            },
      });
    }
  
    async downloadPackageArtifacts() {
      const allFunctions = this.serverless.service.getAllFunctions();
      return BbPromise.each(allFunctions, (functionName) =>
        this.downloadPackageArtifact(functionName)
      );
    }
  
    async compileFunctions() {
      const allFunctions = this.serverless.service.getAllFunctions();
      return Promise.all(allFunctions.map((functionName) => this.compileFunction(functionName)));
    }
  
    cfLambdaFunctionTemplate() {
      return {
        Type: 'AWS::Lambda::Function',
        Properties: {
          Code: {},
        },
      };
    }
  
    cfLambdaVersionTemplate() {
      return {
        Type: 'AWS::Lambda::Version',
        // Retain old versions even though they will not be in future
        // CloudFormation stacks. On stack delete, these will be removed when
        // their associated function is removed.
        DeletionPolicy: 'Retain',
        Properties: {
          FunctionName: 'FunctionName',
          CodeSha256: 'CodeSha256',
        },
      };
    }
  
    cfOutputLatestVersionTemplate() {
      return {
        Description: 'Current Lambda function version',
        Value: 'Value',
      };
    }
  }
  
  async function addFileContentsToHashes(filePath, hashes) {
    return new Promise((resolve, reject) => {
      const readStream = fs.createReadStream(filePath);
      readStream
        .on('data', (chunk) => {
          hashes.forEach((hash) => {
            hash.write(chunk);
          });
        })
        .on('close', () => {
          resolve();
        })
        .on('error', (error) => {
          reject(new Error(`Could not add file content to hash: ${error}`));
        });
    });
  }
  
  function extractLayerConfigurationsFromFunction(functionProperties, cfTemplate) {
    const layerConfigurations = [];
    if (!functionProperties.Layers) return layerConfigurations;
    functionProperties.Layers.forEach((potentialLocalLayerObject) => {
      if (potentialLocalLayerObject.Ref) {
        const configuration = cfTemplate.Resources[potentialLocalLayerObject.Ref];
  
        if (!configuration) {
          log.info(`Could not find reference to layer: ${potentialLocalLayerObject.Ref}.`);
          if (process.env.SLS_DEBUG) {
            legacy.log(`Could not find reference to layer: ${potentialLocalLayerObject.Ref}.`);
          }
          return;
        }
  
        layerConfigurations.push({
          name: configuration._serverlessLayerName,
          ref: potentialLocalLayerObject.Ref,
          properties: configuration.Properties,
        });
      }
    });
    return layerConfigurations;
  }
  
  module.exports = AwsCompileFunctions;
  