├── .gitignore ├── LICENSE ├── NOTICE ├── README.md ├── configuration-updates-pipeline.yaml ├── deployer.sh ├── dynamodb-sync.py ├── function.py ├── insertrecord.sh ├── lambda-cloudformation.yaml ├── requirements.txt ├── sample-mappings.json ├── sample-stack.yaml └── sample-user-policy.json /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | __init__.pyc 3 | connector_dynamodb.pyc 4 | constants.pyc 5 | models/__pycache__ 6 | concurrent/futures 7 | dateutil 8 | docutils 9 | docutils-0.13.1.dist-info 10 | enum 11 | enum34-1.1.6.dist-info 12 | futures-3.0.5.dist-info 13 | jmespath 14 | jmespath-0.9.1.dist-info 15 | jmespath-0.9.2.dist-info 16 | python_dateutil-2.6.0.dist-info 17 | requests 18 | six-1.10.0.dist-info 19 | s3transfer-0.1.10.dist-info 20 | s3transfer 21 | requests-2.13.0.dist-info 22 | botocore/docs/bcdoc 23 | boto3 24 | boto3-1.4.4.dist-info 25 | botocore 26 | botocore-1.5.19.dist-info 27 | botocore-1.5.29.dist-info 28 | six.py 29 | six.pyc 30 | concurrent/__init__.py 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with 4 | the License. A copy of the License is located at 5 | 6 | http://aws.amazon.com/apache2.0/ 7 | 8 | or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 9 | CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and 10 | limitations under the License. -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | CUSTOM LOOKUP 2 | Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## CUSTOM LOOKUP LAMBDA FUNCTION 2 | The Python script , [AWS Lambda](https://aws.amazon.com/lambda/) function and [AWS CloudFormation](https://aws.amazon.com/cloudformation/) templates 3 | described queries [Amazon DynamoDB](https://aws.amazon.com/dynamodb/) table with 4 | the inputs from [AWS CloudFormation](https://aws.amazon.com/cloudformation/) 5 | to lookup the mappings. 6 | 7 | For more details refer the blog [here](https://aws.amazon.com/blogs/devops/custom-lookup-using-aws-lambda-and-amazon-dynamodb/) 8 | 9 | ## Use virtualenv for Python execution 10 | 11 | To prevent any problems with your system Python version conflicting with the application, virtualenv can be used. 12 | 13 | Install Python: 14 | `pip install python 2.7` 15 | 16 | Install virtualenv: 17 | 18 | $ pip install virtualenv 19 | $ virtualenv -p PATH_TO_YOUR_PYTHON_2.7 venv2 20 | $ virtualenv ~/.virtualenvs/venv2 21 | $ source ~/.virtualenvs/venv2/bin/activate 22 | $ pip install awscli -------------------------------------------------------------------------------- /configuration-updates-pipeline.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with 3 | # the License. A copy of the License is located at 4 | # http://aws.amazon.com/apache2.0/ 5 | # or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 6 | # CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and 7 | # limitations under the License. 8 | 9 | AWSTemplateFormatVersion: '2010-09-09' 10 | Transform: AWS::Serverless-2016-10-31 11 | Description: custom lookup function configuration pipeline 12 | Parameters: 13 | CodeCommit: 14 | Description: CodeCommit Repository Name 15 | Type: String 16 | Resources: 17 | ArtifactBucket: 18 | Type: AWS::S3::Bucket 19 | DeletionPolicy: Retain 20 | PipeLineRole: 21 | Type: AWS::IAM::Role 22 | Properties: 23 | RoleName: !Sub ${AWS::StackName}-role-${AWS::Region} 24 | AssumeRolePolicyDocument: 25 | Version: 2012-10-17 26 | Statement: 27 | - 28 | Effect: Allow 29 | Principal: 30 | Service: 31 | - codepipeline.amazonaws.com 32 | Action: 33 | - sts:AssumeRole 34 | Path: / 35 | Policies: 36 | - 37 | PolicyDocument: 38 | Version: 2012-10-17 39 | Statement: 40 | - 41 | Effect: Allow 42 | Action: 43 | - codepipeline:* 44 | - iam:ListRoles 45 | - cloudformation:* 46 | - codecommit:List* 47 | - codecommit:Get* 48 | - codecommit:GitPull 49 | - codecommit:UploadArchive 50 | - codecommit:CancelUploadArchive 51 | - codebuild:BatchGetBuilds 52 | - codebuild:StartBuild 53 | - iam:PassRole 54 | - s3:ListAllMyBuckets 55 | - s3:GetBucketLocation 56 | - lambda:InvokeFunction 57 | - lambda:ListFunctions 58 | - lambda:GetFunctionConfiguration 59 | Resource: 60 | - "*" 61 | - 62 | Effect: Allow 63 | Action: 64 | - s3:PutObject 65 | - s3:GetBucketPolicy 66 | - s3:GetObject 67 | - s3:ListBucket 68 | Resource: 69 | - !Join ['',['arn:aws:s3:::',!Ref ArtifactBucket, '/*']] 70 | - !Join ['',['arn:aws:s3:::',!Ref ArtifactBucket]] 71 | PolicyName: !Sub ${AWS::StackName}-policy-${AWS::Region} 72 | BuildProjectRole: 73 | Type: AWS::IAM::Role 74 | Properties: 75 | RoleName: !Sub ${AWS::StackName}-CodeBuildRole 76 | AssumeRolePolicyDocument: 77 | Version: 2012-10-17 78 | Statement: 79 | - 80 | Effect: Allow 81 | Principal: 82 | Service: 83 | - codebuild.amazonaws.com 84 | Action: 85 | - sts:AssumeRole 86 | Path: / 87 | BuildProjectPolicy: 88 | Type: AWS::IAM::Policy 89 | DependsOn: ArtifactBucket 90 | Properties: 91 | PolicyName: !Sub ${AWS::StackName}-CodeBuildPolicy 92 | PolicyDocument: 93 | Version: 2012-10-17 94 | Statement: 95 | - 96 | Effect: Allow 97 | Action: 98 | - s3:PutObject 99 | - s3:GetBucketPolicy 100 | - s3:GetObject 101 | - s3:ListBucket 102 | Resource: 103 | - !Join ['',['arn:aws:s3:::',!Ref ArtifactBucket, '/*']] 104 | - !Join ['',['arn:aws:s3:::',!Ref ArtifactBucket]] 105 | - 106 | Effect: Allow 107 | Action: 108 | - logs:CreateLogGroup 109 | - logs:CreateLogStream 110 | - logs:PutLogEvents 111 | Resource: arn:aws:logs:*:*:* 112 | Roles: 113 | - 114 | !Ref BuildProjectRole 115 | BuildProject: 116 | Type: AWS::CodeBuild::Project 117 | Properties: 118 | Name: !Ref AWS::StackName 119 | Description: !Sub ${AWS::StackName}-buildproject 120 | ServiceRole: !GetAtt BuildProjectRole.Arn 121 | Artifacts: 122 | Type: CODEPIPELINE 123 | Environment: 124 | Type: linuxContainer 125 | ComputeType: BUILD_GENERAL1_SMALL 126 | Image: aws/codebuild/python:2.7.12 127 | EnvironmentVariables: 128 | - Name: S3Bucket 129 | Value: !Ref ArtifactBucket 130 | Source: 131 | Type: CODEPIPELINE 132 | BuildSpec: | 133 | version: 0.1 134 | phases: 135 | install: 136 | commands: 137 | - printenv 138 | - ls -R 139 | - pip install -r requirements.txt -t "$PWD" 140 | build: 141 | commands: 142 | - aws cloudformation package --template-file lambda-cloudformation.yaml --s3-bucket $S3Bucket --s3-prefix custom-lookup-lambda/codebuild --output-template-file samtemplate.yaml 143 | artifacts: 144 | files: samtemplate.yaml 145 | discard-paths: yes 146 | 147 | TimeoutInMinutes: 10 148 | Tags: 149 | - Key: Name 150 | Value: !Ref AWS::StackName 151 | CFDeployerRole: 152 | Type: AWS::IAM::Role 153 | Properties: 154 | RoleName: !Sub ${AWS::StackName}-cfdeployer-role-${AWS::Region} 155 | AssumeRolePolicyDocument: 156 | Version: 2012-10-17 157 | Statement: 158 | - 159 | Effect: Allow 160 | Principal: 161 | Service: 162 | - cloudformation.amazonaws.com 163 | Action: 164 | - sts:AssumeRole 165 | Path: / 166 | CFDeployerPolicy: 167 | Type: AWS::IAM::Policy 168 | Properties: 169 | PolicyName: !Sub ${AWS::StackName}-cfdeployer-policy-${AWS::Region} 170 | PolicyDocument: 171 | Version: 2012-10-17 172 | Statement: 173 | - 174 | Effect: Allow 175 | Action: 176 | - lambda:AddPermission 177 | - lambda:CreateFunction 178 | - lambda:DeleteFunction 179 | - lambda:InvokeFunction 180 | - lambda:RemovePermission 181 | - lambda:UpdateFunctionCode 182 | - lambda:GetFunctionConfiguration 183 | - lambda:GetFunction 184 | - lambda:UpdateFunctionConfiguration 185 | - iam:CreateRole 186 | - iam:CreatePolicy 187 | - iam:GetRole 188 | - iam:DeleteRole 189 | - iam:PutRolePolicy 190 | - iam:PassRole 191 | - iam:DeleteRolePolicy 192 | - cloudformation:* 193 | Resource: "*" 194 | - 195 | Effect: Allow 196 | Action: 197 | - s3:PutObject 198 | - s3:GetBucketPolicy 199 | - s3:GetObject 200 | - s3:ListBucket 201 | Resource: 202 | - !Join ['',['arn:aws:s3:::',!Ref ArtifactBucket, '/*']] 203 | - !Join ['',['arn:aws:s3:::',!Ref ArtifactBucket]] 204 | Roles: 205 | - 206 | !Ref CFDeployerRole 207 | Pipeline: 208 | Type: AWS::CodePipeline::Pipeline 209 | DependsOn: PipeLineRole 210 | Properties: 211 | RoleArn: !GetAtt PipeLineRole.Arn 212 | Name: !Ref AWS::StackName 213 | Stages: 214 | - Name: source-code-checkout 215 | Actions: 216 | - Name: App 217 | ActionTypeId: 218 | Category: Source 219 | Owner: AWS 220 | Version: 1 221 | Provider: CodeCommit 222 | Configuration: 223 | RepositoryName: !Ref CodeCommit 224 | BranchName: feature/codepipeline 225 | OutputArtifacts: 226 | - Name: SCCheckoutArtifact 227 | RunOrder: 1 228 | - 229 | Name: Build 230 | Actions: 231 | - 232 | Name: build-lambda-function 233 | ActionTypeId: 234 | Category: Build 235 | Owner: AWS 236 | Version: 1 237 | Provider: CodeBuild 238 | Configuration: 239 | ProjectName: !Ref BuildProject 240 | RunOrder: 1 241 | InputArtifacts: 242 | - Name: SCCheckoutArtifact 243 | OutputArtifacts: 244 | - Name: BuildOutput 245 | - Name: Deploy 246 | Actions: 247 | - Name: deploy-lambda-function 248 | ActionTypeId: 249 | Category: Deploy 250 | Owner: AWS 251 | Version: 1 252 | Provider: CloudFormation 253 | Configuration: 254 | ChangeSetName: Deploy 255 | ActionMode: CREATE_UPDATE 256 | StackName: !Sub custom-lookup-lambda 257 | Capabilities: CAPABILITY_NAMED_IAM 258 | TemplatePath: BuildOutput::samtemplate.yaml 259 | RoleArn: !GetAtt CFDeployerRole.Arn 260 | InputArtifacts: 261 | - Name: BuildOutput 262 | RunOrder: 1 263 | - 264 | Name: Invoke 265 | Actions: 266 | - 267 | Name: call-function 268 | ActionTypeId: 269 | Category: Invoke 270 | Owner: AWS 271 | Version: 1 272 | Provider: Lambda 273 | Configuration: 274 | FunctionName: insert-lookup 275 | UserParameters: !Sub | 276 | { 277 | "file_to_sync": "sample-mappings.json" 278 | } 279 | InputArtifacts: 280 | - Name: SCCheckoutArtifact 281 | RunOrder: 1 282 | ArtifactStore: 283 | Type: S3 284 | Location: !Ref ArtifactBucket -------------------------------------------------------------------------------- /deployer.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | echo -n "Enter the S3 Bucket to hold lambda package > " 3 | read S3Bucket 4 | FILE="$(uuidgen)" 5 | pip install -r requirements.txt -t "$PWD" 6 | aws cloudformation package --template-file lambda-cloudformation.yaml --s3-bucket $S3Bucket --s3-prefix custom-lookup/codebuild --output-template-file $FILE 7 | aws cloudformation deploy --template-file $FILE --stack-name custom-lookup-lambda --capabilities CAPABILITY_NAMED_IAM 8 | rm $FILE -------------------------------------------------------------------------------- /dynamodb-sync.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | import json 3 | import boto3 4 | import traceback 5 | from boto3.session import Session 6 | import zipfile 7 | import tempfile 8 | import botocore 9 | import decimal 10 | 11 | objmapping = None 12 | code_pipeline = boto3.client('codepipeline') 13 | dynamodb_client = boto3.client('dynamodb') 14 | tablename = 'custom-lookup' 15 | hashkey = 'teamname-environment' 16 | rangekey = 'appname' 17 | 18 | def handler(event,context): 19 | try: 20 | job_id = event['CodePipeline.job']['id'] 21 | job_data = event['CodePipeline.job']['data'] 22 | artifact_data = job_data['inputArtifacts'][0] 23 | 24 | params = get_user_params(job_id, job_data) 25 | artifact = params['file_to_sync'] 26 | 27 | s3 = setup_s3_client(job_data) 28 | objartifact = get_file_from_artifact(s3, artifact_data, artifact) 29 | sampledata = load_data(objartifact) 30 | for item in sampledata: 31 | insert_update_data(item) 32 | 33 | put_job_success(job_id, "Success") 34 | except Exception as e: 35 | print('Function failed due to exception.') 36 | print(e) 37 | traceback.print_exc() 38 | put_job_failure(job_id, 'Function exception: ' + str(e)) 39 | 40 | def load_data(objfile): 41 | mappings = json.loads(objfile, parse_float=decimal.Decimal) 42 | return mappings 43 | def insert_update_data(response): 44 | createtable() 45 | dynamodb_client.put_item( 46 | TableName=tablename, 47 | Item={ 48 | hashkey: {'S': response[hashkey]}, 49 | rangekey: {'S': response[rangekey]}, 50 | 'mappings': {'S': str(response['mappings'])} 51 | } 52 | ) 53 | def createtable(): 54 | try: 55 | dynamodb_client.describe_table(TableName=tablename) 56 | except Exception: 57 | dynamodb_client.create_table( 58 | TableName=tablename, 59 | KeySchema=[ 60 | {'AttributeName': hashkey, 'KeyType': 'HASH'}, 61 | {'AttributeName': rangekey, 'KeyType': 'RANGE'} 62 | ], 63 | AttributeDefinitions=[ 64 | {'AttributeName': hashkey, 'AttributeType': 'S'}, 65 | {'AttributeName': rangekey, 'AttributeType': 'S'} 66 | ], 67 | ProvisionedThroughput={'ReadCapacityUnits': 5, 'WriteCapacityUnits': 5} 68 | ) 69 | 70 | dynamodb_client.get_waiter('table_exists').wait(TableName=tablename) 71 | 72 | 73 | def get_file_from_artifact(s3, artifact, file_in_zip): 74 | bucket = artifact['location']['s3Location']['bucketName'] 75 | key = artifact['location']['s3Location']['objectKey'] 76 | 77 | with tempfile.NamedTemporaryFile() as tmp_file: 78 | s3.download_file(bucket, key, tmp_file.name) 79 | with zipfile.ZipFile(tmp_file.name, 'r') as zip: 80 | print(zip.read(file_in_zip)) 81 | return zip.read(file_in_zip) 82 | 83 | def setup_s3_client(job_data): 84 | key_id = job_data['artifactCredentials']['accessKeyId'] 85 | key_secret = job_data['artifactCredentials']['secretAccessKey'] 86 | session_token = job_data['artifactCredentials']['sessionToken'] 87 | 88 | session = Session(aws_access_key_id=key_id, 89 | aws_secret_access_key=key_secret, 90 | aws_session_token=session_token) 91 | return session.client('s3', config=botocore.client.Config(signature_version='s3v4')) 92 | 93 | def put_job_success(job, message): 94 | """Notify CodePipeline of a successful job 95 | 96 | Args: 97 | job: The CodePipeline job ID 98 | message: A message to be logged relating to the job status 99 | 100 | Raises: 101 | Exception: Any exception thrown by .put_job_success_result() 102 | 103 | """ 104 | print('Putting job success') 105 | print(message) 106 | code_pipeline.put_job_success_result(jobId=job) 107 | 108 | def put_job_failure(job, message): 109 | """Notify CodePipeline of a failed job 110 | 111 | Args: 112 | job: The CodePipeline job ID 113 | message: A message to be logged relating to the job status 114 | 115 | Raises: 116 | Exception: Any exception thrown by .put_job_failure_result() 117 | 118 | """ 119 | print('Putting job failure') 120 | print(message) 121 | code_pipeline.put_job_failure_result(jobId=job, failureDetails={'message': message, 'type': 'JobFailed'}) 122 | 123 | def continue_job_later(job, message): 124 | """Notify CodePipeline of a continuing job 125 | 126 | This will cause CodePipeline to invoke the function again with the 127 | supplied continuation token. 128 | 129 | Args: 130 | job: The JobID 131 | message: A message to be logged relating to the job status 132 | continuation_token: The continuation token 133 | 134 | Raises: 135 | Exception: Any exception thrown by .put_job_success_result() 136 | 137 | """ 138 | 139 | # Use the continuation token to keep track of any job execution state 140 | # This data will be available when a new job is scheduled to continue the current execution 141 | continuation_token = json.dumps({'previous_job_id': job}) 142 | 143 | print('Putting job continuation') 144 | print(message) 145 | code_pipeline.put_job_success_result(jobId=job, continuationToken=continuation_token) 146 | 147 | def get_user_params(job_id,job_data): 148 | try: 149 | user_parameters = job_data['actionConfiguration']['configuration']['UserParameters'] 150 | decoded_parameters = json.loads(user_parameters) 151 | print(decoded_parameters) 152 | except Exception as e: 153 | put_job_failure(job_id,e) 154 | raise Exception('UserParameters could not be decoded as JSON') 155 | 156 | return decoded_parameters 157 | 158 | if __name__ == "__main__": 159 | handler(None, None) 160 | 161 | -------------------------------------------------------------------------------- /function.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with 3 | # the License. A copy of the License is located at 4 | # http://aws.amazon.com/apache2.0/ 5 | # or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 6 | # CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and 7 | # limitations under the License. 8 | 9 | from __future__ import print_function 10 | import os 11 | import json 12 | import boto3 13 | import decimal 14 | import ast 15 | from boto3.dynamodb.conditions import Key 16 | import requests 17 | 18 | dynamodb_client = boto3.client('dynamodb') 19 | tablename = 'custom-lookup' 20 | hashkey = 'teamname-environment' 21 | rangekey = 'appname' 22 | 23 | 24 | def initialize(): 25 | if os.getenv('method') is None: 26 | os.environ["method"] = "query" 27 | print("Running the function in Query Mode") 28 | if os.getenv('method') == 'insert': 29 | print("Running the function in Insert Mode") 30 | 31 | 32 | def handler(event, context): 33 | print(event) 34 | if event['RequestType'] == 'Create' or event['RequestType'] == 'Update': 35 | initialize() 36 | if os.getenv('method') != "query": 37 | sampledata = load_data() 38 | for item in sampledata: 39 | insert_data(item) 40 | else: 41 | data = get_data(event['ResourceProperties']) 42 | respond_cloudformation(event, "SUCCESS", data) 43 | return get_data(event['ResourceProperties']) 44 | else: 45 | respond_cloudformation(event, "SUCCESS") 46 | return 47 | 48 | 49 | def insert_data(response): 50 | createtable() 51 | dynamodb_client.put_item( 52 | TableName=tablename, 53 | Item={ 54 | hashkey: {'S': response[hashkey]}, 55 | rangekey: {'S': response[rangekey]}, 56 | 'mappings': {'S': str(response['mappings'])} 57 | } 58 | ) 59 | 60 | 61 | def load_data(file="sample-mappings.json"): 62 | with open(file) as configuration_file: 63 | mappings = json.load(configuration_file, parse_float=decimal.Decimal) 64 | return mappings 65 | 66 | 67 | def get_data(event): 68 | dynamodb = boto3.resource('dynamodb') 69 | table = dynamodb.Table(tablename) 70 | response = table.query( 71 | KeyConditionExpression=Key(hashkey).eq(event[hashkey]) \ 72 | & Key(rangekey).eq(event[rangekey]) 73 | ) 74 | for item in response['Items']: 75 | objkeypair = ast.literal_eval(item['mappings']) 76 | if 'lookup' in event: 77 | return objkeypair[event['lookup']] 78 | else: 79 | return objkeypair 80 | 81 | 82 | def respond_cloudformation(event, status, data=None): 83 | responseBody = { 84 | 'Status': status, 85 | 'Reason': 'See the details in CloudWatch Log Stream', 86 | 'PhysicalResourceId': 'Custom Lambda Function', 87 | 'StackId': event['StackId'], 88 | 'RequestId': event['RequestId'], 89 | 'LogicalResourceId': event['LogicalResourceId'], 90 | 'Data': data 91 | } 92 | 93 | print('Response = ' + json.dumps(responseBody)) 94 | requests.put(event['ResponseURL'], data=json.dumps(responseBody)) 95 | 96 | 97 | def createtable(): 98 | try: 99 | dynamodb_client.describe_table(TableName=tablename) 100 | except Exception: 101 | dynamodb_client.create_table( 102 | TableName=tablename, 103 | KeySchema=[ 104 | {'AttributeName': hashkey, 'KeyType': 'HASH'}, 105 | {'AttributeName': rangekey, 'KeyType': 'RANGE'} 106 | ], 107 | AttributeDefinitions=[ 108 | {'AttributeName': hashkey, 'AttributeType': 'S'}, 109 | {'AttributeName': rangekey, 'AttributeType': 'S'} 110 | ], 111 | ProvisionedThroughput={'ReadCapacityUnits': 5, 'WriteCapacityUnits': 5} 112 | ) 113 | 114 | dynamodb_client.get_waiter('table_exists').wait(TableName=tablename) 115 | 116 | 117 | # This is the test harness 118 | if __name__ == '__main__': 119 | request1 = { 120 | 'StackId': 'arn:aws:cloudformation:us-west-2:accountgoeshere:stack/sample-stack/stackidgoeshere', 121 | 'ResponseURL': 'https://test.com', 122 | 'ResourceProperties': { 123 | 'teamname-environment': 'team1-dev', 124 | 'ServiceToken': 'lambdaarn', 125 | 'appname': 'app1', 126 | 'lookup': 'vpc' 127 | }, 128 | 'RequestType': 'Create', 129 | 'ServiceToken': 'lambdaarn', 130 | 'ResourceType': 'Custom::Lookup', 131 | 'RequestId': 'sampleid', 132 | 'LogicalResourceId': 'CUSTOMLOOKUP' 133 | } 134 | request2 = { 135 | 'StackId': 'arn:aws:cloudformation:us-west-2:accountgoeshere:stack/sample-stack/stackid', 136 | 'ResponseURL': 'https://test.com', 137 | 'ResourceProperties': { 138 | 'teamname-environment': 'team1-dev', 139 | 'ServiceToken': 'lambdaarn', 140 | 'appname': 'app1' 141 | }, 142 | 'RequestType': 'Create', 143 | 'ServiceToken': 'arn:aws:lambda:us-west-2:accountgoeshere:function:lambdafunction', 144 | 'ResourceType': 'Custom::Lookup', 145 | 'RequestId': 'ramdonid', 146 | 'LogicalResourceId': 'CUSTOMLOOKUP' 147 | } 148 | handler(request1, None) 149 | handler(request2, None) 150 | -------------------------------------------------------------------------------- /insertrecord.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | pip install -r requirements.txt -t "$PWD" 3 | export method=insert 4 | python function.py -------------------------------------------------------------------------------- /lambda-cloudformation.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with 3 | # the License. A copy of the License is located at 4 | # http://aws.amazon.com/apache2.0/ 5 | # or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 6 | # CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and 7 | # limitations under the License. 8 | 9 | AWSTemplateFormatVersion: '2010-09-09' 10 | Description: custom lookup function 11 | Resources: 12 | CUSTOMLOOKUP: 13 | Type: AWS::Lambda::Function 14 | Properties: 15 | FunctionName: custom-lookup 16 | Handler: function.handler 17 | Runtime: python2.7 18 | Description: custom lookup function 19 | Code: ./ 20 | MemorySize: 128 21 | Timeout: 60 22 | Role: !GetAtt CUSTOMLOOKUPLAMBDAROLE.Arn 23 | CUSTOMLOOKUPLAMBDAROLE: 24 | Type: AWS::IAM::Role 25 | Properties: 26 | RoleName: !Sub custom-lookup-lambda-role-${AWS::Region} 27 | AssumeRolePolicyDocument: 28 | Version: 2012-10-17 29 | Statement: 30 | - 31 | Effect: Allow 32 | Principal: 33 | Service: 34 | - lambda.amazonaws.com 35 | Action: 36 | - sts:AssumeRole 37 | Path: / 38 | CUSTOMLOOKUPLAMBDAPOLICY: 39 | Type: AWS::IAM::Policy 40 | Properties: 41 | PolicyName: !Sub custom-lookup-lambda-policy-${AWS::Region} 42 | PolicyDocument: 43 | Version: 2012-10-17 44 | Statement: 45 | - 46 | Effect: Allow 47 | Action: 48 | - dynamodb:* 49 | Resource: 50 | - arn:aws:dynamodb:*:*:table/custom-lookup 51 | - arn:aws:dynamodb:*:*:table/custom-lookup/* 52 | - 53 | Effect: Allow 54 | Action: 55 | - logs:CreateLogGroup 56 | - logs:CreateLogStream 57 | - logs:PutLogEvents 58 | Resource: arn:aws:logs:*:*:* 59 | Roles: 60 | - 61 | !Ref CUSTOMLOOKUPLAMBDAROLE 62 | #Below is for Insert Lambda 63 | INSERTLOOKUP: 64 | Type: AWS::Lambda::Function 65 | Properties: 66 | FunctionName: insert-lookup 67 | Handler: dynamodb-sync.handler 68 | Runtime: python2.7 69 | Description: inserts records from codepipeline to dynamodb 70 | Code: ./ 71 | MemorySize: 128 72 | Timeout: 300 73 | Role: !GetAtt INSERTLOOKUPLAMBDAROLE.Arn 74 | INSERTLOOKUPLAMBDAROLE: 75 | Type: AWS::IAM::Role 76 | Properties: 77 | RoleName: !Sub insert-lookup-lambda-role-${AWS::Region} 78 | AssumeRolePolicyDocument: 79 | Version: 2012-10-17 80 | Statement: 81 | - 82 | Effect: Allow 83 | Principal: 84 | Service: 85 | - lambda.amazonaws.com 86 | Action: 87 | - sts:AssumeRole 88 | Path: / 89 | INSERTLOOKUPLAMBDAPOLICY: 90 | Type: AWS::IAM::Policy 91 | Properties: 92 | PolicyName: !Sub insert-lookup-lambda-policy-${AWS::Region} 93 | PolicyDocument: 94 | Version: 2012-10-17 95 | Statement: 96 | - 97 | Effect: Allow 98 | Action: 99 | - dynamodb:* 100 | Resource: 101 | - arn:aws:dynamodb:*:*:table/custom-lookup 102 | - arn:aws:dynamodb:*:*:table/custom-lookup/* 103 | - 104 | Effect: Allow 105 | Action: 106 | - codepipeline:PutJobFailureResult 107 | - codepipeline:PutJobSuccessResult 108 | Resource: 109 | - "*" 110 | - 111 | Effect: Allow 112 | Action: 113 | - logs:CreateLogGroup 114 | - logs:CreateLogStream 115 | - logs:PutLogEvents 116 | Resource: arn:aws:logs:*:*:* 117 | Roles: 118 | - 119 | !Ref INSERTLOOKUPLAMBDAROLE 120 | Outputs: 121 | LambdaArn: 122 | Description: ARN of the Lambda Function, which returns the mappings 123 | Value: !GetAtt CUSTOMLOOKUP.Arn 124 | Export: 125 | Name: custom-lookup-lambda -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | boto3 2 | requests -------------------------------------------------------------------------------- /sample-mappings.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "teamname-environment": "team1-dev", 4 | "appname": "app1", 5 | "mappings": { 6 | "elbsubnet1": "subnet-123456", 7 | "elbsubnet2": "subnet-234567", 8 | "appsubnet1": "subnet-345678", 9 | "appsubnet2": "subnet-456789", 10 | "vpc": "vpc-123456", 11 | "appname":"app1", 12 | "costcenter":"123456", 13 | "teamname":"team1", 14 | "environment":"dev", 15 | "certificate":"arn-123456qwertyasdfgh", 16 | "compliancetype":"pci", 17 | "amiid":"ami-123456", 18 | "region":"us-west-1", 19 | "publichostedzoneid":"Z234asdf1234asdf", 20 | "privatehostedzoneid":"Z345SDFGCVHD123", 21 | "hostedzonename":"demo.internal" 22 | } 23 | }, 24 | { 25 | "teamname-environment": "team1-test", 26 | "appname": "app1", 27 | "mappings": { 28 | "elbsubnet1": "subnet-123456", 29 | "elbsubnet2": "subnet-234567", 30 | "appsubnet1": "subnet-345678", 31 | "appsubnet2": "subnet-456789", 32 | "vpc": "vpc-123456", 33 | "appname":"app1", 34 | "costcenter":"123456", 35 | "teamname":"team1", 36 | "environment":"dev", 37 | "certificate":"arn-123456qwertyasdfgh", 38 | "compliancetype":"pci", 39 | "amiid":"ami-123456", 40 | "region":"us-west-1", 41 | "publichostedzoneid":"Z234asdf1234asdf", 42 | "privatehostedzoneid":"Z345SDFGCVHD123", 43 | "hostedzonename":"demo.internal" 44 | } 45 | }, 46 | { 47 | "teamname-environment": "team1-prod", 48 | "appname": "app1", 49 | "mappings": { 50 | "elbsubnet1": "subnet-123456", 51 | "elbsubnet2": "subnet-234567", 52 | "appsubnet1": "subnet-345678", 53 | "appsubnet2": "subnet-456789", 54 | "vpc": "vpc-123456", 55 | "appname":"app1", 56 | "costcenter":"123456", 57 | "teamname":"team1", 58 | "environment":"dev", 59 | "certificate":"arn-123456qwertyasdfgh", 60 | "compliancetype":"pci", 61 | "amiid":"ami-123456", 62 | "region":"us-west-1", 63 | "publichostedzoneid":"Z234asdf1234asdf", 64 | "privatehostedzoneid":"Z345SDFGCVHD123", 65 | "hostedzonename":"demo.internal" 66 | } 67 | }, 68 | { 69 | "teamname-environment": "team1-perf", 70 | "appname": "app1", 71 | "mappings": { 72 | "elbsubnet1": "subnet-123456", 73 | "elbsubnet2": "subnet-234567", 74 | "appsubnet1": "subnet-345678", 75 | "appsubnet2": "subnet-456789", 76 | "vpc": "vpc-123456", 77 | "appname":"app1", 78 | "costcenter":"123456", 79 | "teamname":"team1", 80 | "environment":"dev", 81 | "certificate":"arn-123456qwertyasdfgh", 82 | "compliancetype":"pci", 83 | "amiid":"ami-123456", 84 | "region":"us-west-1", 85 | "publichostedzoneid":"Z234asdf1234asdf", 86 | "privatehostedzoneid":"Z345SDFGCVHD123", 87 | "hostedzonename":"demo.internal" 88 | } 89 | } 90 | ] -------------------------------------------------------------------------------- /sample-stack.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with 3 | # the License. A copy of the License is located at 4 | # http://aws.amazon.com/apache2.0/ 5 | # or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 6 | # CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and 7 | # limitations under the License. 8 | AWSTemplateFormatVersion: "2010-09-09" 9 | Resources: 10 | CUSTOMLOOKUP: 11 | Type: Custom::Lookup 12 | Properties: 13 | ServiceToken: !ImportValue custom-lookup-lambda 14 | teamname-environment: team1-dev 15 | appname: app1 16 | Outputs: 17 | AMIID: 18 | Value: !GetAtt CUSTOMLOOKUP.amiid 19 | VPC: 20 | Value: !GetAtt CUSTOMLOOKUP.vpc 21 | COSTCENTER: 22 | Value: !GetAtt CUSTOMLOOKUP.costcenter 23 | PrivateHostedZoneID: 24 | Value: !GetAtt CUSTOMLOOKUP.privatehostedzoneid -------------------------------------------------------------------------------- /sample-user-policy.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Sid": "Stmt1490315351000", 6 | "Effect": "Allow", 7 | "Action": [ 8 | "dynamodb:CreateTable", 9 | "dynamodb:DeleteItem", 10 | "dynamodb:DeleteTable", 11 | "dynamodb:GetItem", 12 | "dynamodb:PutItem", 13 | "dynamodb:Query", 14 | "dynamodb:DescribeTable" 15 | ], 16 | "Resource": [ 17 | "arn:aws:dynamodb:*:*:table/custom-lookup" 18 | ] 19 | }, 20 | { 21 | "Sid": "Stmt1490315351001", 22 | "Effect": "Allow", 23 | "Action": [ 24 | "s3:PutObject", 25 | "s3:GetObject" 26 | ], 27 | "Resource": [ 28 | "*" 29 | ] 30 | }, 31 | { 32 | "Sid": "Stmt1490315351002", 33 | "Effect": "Allow", 34 | "Action": [ 35 | "iam:CreateRole", 36 | "iam:CreatePolicy", 37 | "iam:GetRole", 38 | "iam:DeleteRole", 39 | "iam:PutRolePolicy", 40 | "iam:PassRole", 41 | "iam:DeleteRolePolicy" 42 | ], 43 | "Resource": [ 44 | "arn:aws:iam::*:role/custom-lookup-lambda-role", 45 | "arn:aws:iam::*:policy/custom-lookup-lambda-policy" 46 | ] 47 | }, 48 | { 49 | "Sid": "Stmt1490315443000", 50 | "Effect": "Allow", 51 | "Action": [ 52 | "cloudformation:CreateStack", 53 | "cloudformation:CreateChangeSet", 54 | "cloudformation:DeleteStack", 55 | "cloudformation:DescribeChangeSet", 56 | "cloudformation:DescribeStackEvents", 57 | "cloudformation:DescribeStackResource", 58 | "cloudformation:DescribeStackResources", 59 | "cloudformation:DescribeStacks", 60 | "cloudformation:ExecuteChangeSet", 61 | "cloudformation:ListStackResources", 62 | "cloudformation:UpdateStack", 63 | "cloudformation:ValidateTemplate" 64 | ], 65 | "Resource": [ 66 | "arn:aws:cloudformation:*:*:stack/sample-stack/*", 67 | "arn:aws:cloudformation:*:*:stack/sample-stack", 68 | "arn:aws:cloudformation:*:*:stack/custom-lookup-lambda/*", 69 | "arn:aws:cloudformation:*:*:stack/custom-lookup-lambda", 70 | "arn:aws:cloudformation:*:aws:transform/Serverless-2016-10-31" 71 | ] 72 | }, 73 | { 74 | "Sid": "Stmt1490315956000", 75 | "Effect": "Allow", 76 | "Action": [ 77 | "lambda:AddPermission", 78 | "lambda:CreateFunction", 79 | "lambda:DeleteFunction", 80 | "lambda:InvokeFunction", 81 | "lambda:RemovePermission", 82 | "lambda:UpdateFunctionCode", 83 | "lambda:GetFunctionConfiguration", 84 | "lambda:GetFunction", 85 | "iam:PutRolePolicy" 86 | ], 87 | "Resource": [ 88 | "arn:aws:lambda:*:*:function:custom-lookup" 89 | ] 90 | } 91 | ] 92 | } --------------------------------------------------------------------------------