lib/plugins/aws/package/compile/events/s3/index.js/AwsCompileS3Events/existingS3Buckets
class AwsCompileS3Events:  existingS3Buckets() {
    const { service } = this.serverless;
    const { provider } = service;
    const { compiledCloudFormationTemplate } = provider;
    const { Resources } = compiledCloudFormationTemplate;
    const iamRoleStatements = [];

    // used to keep track of the custom resources created for each bucket
    const bucketResources = {};

    service.getAllFunctions().forEach(functionName => {
      let numEventsForFunc = 0;
      let currentBucketName = null;
      let funcUsesExistingS3Bucket = false;
      const functionObj = service.getFunction(functionName);
      const FunctionName = functionObj.name;

      if (functionObj.events) {
        functionObj.events.forEach(event => {
          if (event.s3 && _.isObject(event.s3) && event.s3.existing) {
            numEventsForFunc++;
            let rules = null;
            const bucket = event.s3.bucket;
            const notificationEvent = event.s3.event || 's3:ObjectCreated:*';
            funcUsesExistingS3Bucket = true;

            if (!currentBucketName) {
              currentBucketName = bucket;
            }
            if (bucket !== currentBucketName) {
              const errorMessage = [
                'Only one S3 Bucket can be configured per function.',
                ` In "${FunctionName}" you're attempting to configure "${currentBucketName}" and "${bucket}" at the same time.`,
              ].join('');
              throw new this.serverless.classes.Error(errorMessage);
            }

            rules = _.map(event.s3.rules, rule => {
              const key = Object.keys(rule)[0];
              const value = rule[key];
              return {
                [_.startCase(key)]: value,
              };
            });

            const eventFunctionLogicalId = this.provider.naming.getLambdaLogicalId(functionName);
            const customResourceFunctionLogicalId = this.provider.naming.getCustomResourceS3HandlerFunctionLogicalId();
            const customS3ResourceLogicalId = this.provider.naming.getCustomResourceS3ResourceLogicalId(
              functionName
            );

            // store how often the custom S3 resource is used
            if (bucketResources[bucket]) {
              bucketResources[bucket] = _.union(bucketResources[bucket], [
                customS3ResourceLogicalId,
              ]);
            } else {
              Object.assign(bucketResources, {
                [bucket]: [customS3ResourceLogicalId],
              });
            }

            let customS3Resource;
            if (numEventsForFunc === 1) {
              customS3Resource = {
                [customS3ResourceLogicalId]: {
                  Type: 'Custom::S3',
                  Version: 1.0,
                  DependsOn: [eventFunctionLogicalId, customResourceFunctionLogicalId],
                  Properties: {
                    ServiceToken: {
                      'Fn::GetAtt': [customResourceFunctionLogicalId, 'Arn'],
                    },
                    FunctionName,
                    BucketName: bucket,
                    BucketConfigs: [
                      {
                        Event: notificationEvent,
                        Rules: rules,
                      },
                    ],
                  },
                },
              };

              iamRoleStatements.push({
                Effect: 'Allow',
                Resource: {
                  'Fn::Join': [':', ['arn', { Ref: 'AWS::Partition' }, 's3', '', '', bucket]],
                },
                Action: ['s3:PutBucketNotification', 's3:GetBucketNotification'],
              });
            } else {
              Resources[customS3ResourceLogicalId].Properties.BucketConfigs.push({
                Event: notificationEvent,
                Rules: rules,
              });
            }

            _.merge(Resources, customS3Resource);
          }
        });
      }

      if (funcUsesExistingS3Bucket) {
        iamRoleStatements.push({
          Effect: 'Allow',
          Resource: {
            'Fn::Join': [
              ':',
              [
                'arn',
                { Ref: 'AWS::Partition' },
                'lambda',
                { Ref: 'AWS::Region' },
                { Ref: 'AWS::AccountId' },
                'function',
                FunctionName,
              ],
            ],
          },
          Action: ['lambda:AddPermission', 'lambda:RemovePermission'],
        });
      }
    });

    // check if we need to add DependsOn clauses in case more than 1
    // custom resources are created for one bucket (to avoid race conditions)
    if (Object.keys(bucketResources).length > 0) {
      Object.keys(bucketResources).forEach(bucket => {
        const resources = bucketResources[bucket];
        if (resources.length > 1) {
          resources.forEach((currResourceLogicalId, idx) => {
            if (idx > 0) {
              const prevResourceLogicalId = resources[idx - 1];
              Resources[currResourceLogicalId].DependsOn.push(prevResourceLogicalId);
            }
          });
        }
      });
    }

    if (iamRoleStatements.length) {
      return addCustomResourceToService(this.provider, 's3', iamRoleStatements);
    }

    return null;
  }