-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Ghosh
committed
Jan 2, 2018
1 parent
0af8ac5
commit 90a3983
Showing
13 changed files
with
321 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
{ | ||
"AWSTemplateFormatVersion":"2010-09-09", | ||
"Description":" This cloudformation template creates resources required for synching new instances with the latest deployment from AWS CodeDeploy based on the CodeDeployAppname, DeploymentGroup tags on the instance", | ||
"Resources":{ | ||
"EventRule": { | ||
|
||
"Type": "AWS::Events::Rule", | ||
"Properties": { | ||
"Description": "EventRule", | ||
"EventPattern": { | ||
"source": [ | ||
"aws.ec2" | ||
], | ||
"detail-type": [ | ||
"EC2 Instance State-change Notification" | ||
], | ||
"detail": { | ||
"state": [ | ||
"running" | ||
] | ||
} | ||
}, | ||
"State": "ENABLED", | ||
"Targets": [{ | ||
"Arn": { "Fn::GetAtt": ["CodeDeploySyncNewInstance", "Arn"] }, | ||
"Id": "TargetFunctionV1" | ||
}] | ||
} | ||
}, | ||
"PermissionForEventsToInvokeLambda": { | ||
"Type": "AWS::Lambda::Permission", | ||
"Properties": { | ||
"FunctionName": { "Ref": "CodeDeploySyncNewInstance" }, | ||
"Action": "lambda:InvokeFunction", | ||
"Principal": "events.amazonaws.com", | ||
"SourceArn": { "Fn::GetAtt": ["EventRule", "Arn"] } | ||
} | ||
}, | ||
|
||
"CodeDeployServiceRole":{ | ||
"Type":"AWS::IAM::Role", | ||
"Properties":{ | ||
"ManagedPolicyArns":[ | ||
"arn:aws:iam::aws:policy/service-role/AWSCodeDeployRole" | ||
], | ||
"AssumeRolePolicyDocument":{ | ||
"Version":"2012-10-17", | ||
"Statement":[ | ||
{ | ||
"Effect":"Allow", | ||
"Principal":{ | ||
"Service":[ | ||
"codedeploy.amazonaws.com" | ||
] | ||
}, | ||
"Action":[ | ||
"sts:AssumeRole" | ||
] | ||
} | ||
] | ||
} | ||
} | ||
}, | ||
"CodeDeployLambdaRole":{ | ||
"Type":"AWS::IAM::Role", | ||
"Properties":{ | ||
"ManagedPolicyArns":[ | ||
"arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", | ||
"arn:aws:iam::aws:policy/AmazonEC2ReadOnlyAccess", | ||
], | ||
"AssumeRolePolicyDocument":{ | ||
"Version":"2012-10-17", | ||
"Statement":[ | ||
{ | ||
"Effect":"Allow", | ||
"Principal":{ | ||
"Service":[ | ||
"ec2.amazonaws.com", | ||
"lambda.amazonaws.com" | ||
] | ||
}, | ||
"Action":[ | ||
"sts:AssumeRole" | ||
] | ||
} | ||
] | ||
}, | ||
"Path":"/", | ||
"Policies":[ | ||
{ | ||
"PolicyName":"Lambda-codedeploy", | ||
"PolicyDocument":{ | ||
"Version":"2012-10-17", | ||
"Statement":[{ | ||
"Sid": "StartContinuousAssessmentLambdaPolicyStmt", | ||
"Effect": "Allow", | ||
"Action":["codedeploy:Get*","codedeploy:UpdateDeploymentGroup", "codedeploy:List*", "codedeploy:CreateDeployment" ], | ||
"Resource": ["*"] | ||
}] | ||
} | ||
}, | ||
{ | ||
"PolicyName":"IamPassRole", | ||
"PolicyDocument":{ | ||
"Version":"2012-10-17", | ||
"Statement":[{ | ||
"Sid": "StartContinuousAssessmentLambdaPolicyStmt", | ||
"Effect": "Allow", | ||
"Action":["iam:PassRole" ], | ||
"Resource": [{ "Fn::GetAtt": ["CodeDeployServiceRole", "Arn"] }] | ||
}] | ||
} | ||
}, | ||
] | ||
} | ||
|
||
}, | ||
"CodeDeploySyncNewInstance":{ | ||
"Type":"AWS::Lambda::Function", | ||
"Properties":{ | ||
"Role":{ | ||
"Fn::GetAtt":[ | ||
"CodeDeployLambdaRole", | ||
"Arn" | ||
] | ||
}, | ||
"Environment": { | ||
"Variables": { | ||
"CodeDeployRole": { "Fn::GetAtt": ["CodeDeployServiceRole", "Arn"] } | ||
} | ||
}, | ||
"Code": { | ||
"S3Bucket": "awslabs-new-instance-sync-lambda", | ||
"S3Key": "new-instance-code-sync.zip" | ||
}, | ||
"Runtime":"python2.7", | ||
"Timeout":300, | ||
"Handler":"new-instance-code-sync.lambda_handler", | ||
"MemorySize":512 | ||
} | ||
} | ||
}, | ||
"Outputs":{ | ||
"NewInstanceLambdaFunction":{ | ||
"Description":"The Lambda function that creates Resources for synching new instances", | ||
"Value":{ | ||
"Ref":"CodeDeploySyncNewInstance" | ||
} | ||
} | ||
} | ||
} |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
from __future__ import print_function | ||
from dateutil.parser import parse | ||
from datetime import datetime | ||
import json | ||
import time | ||
import os | ||
import boto3 | ||
ec2client = boto3.client('ec2') | ||
client = boto3.client('codedeploy') | ||
codedeployrole = os.environ.get("CodeDeployRole") | ||
|
||
""" Main method controlling the sequence of actions as follows | ||
1. Parses EC2 Launch event | ||
2. Checks whether its a Spot EC2 Instance or an on-demand instance | ||
3. For Spot Instance, it looks up the Tags from the request and propagates them to the Spot Instance | ||
4. Based on the CodeDeployApplication, CodeDeployDeploymentGroup and Name Tag, it adds the EC2 Instance to its respective CodeDeploy Deployment Group | ||
5. Ignores the EC2 if it has a duplicate Name tag in the Deployment Group. Also if its part of an Auto Scaling Group | ||
6. Once added, it looks up the last successful deployment from AWS CodeDeploy | ||
7. It creates a new deployment based on the artifacts from the prior deployment | ||
8. Returns "Done" | ||
:param event: Input json from SNS | ||
:param context: Not used, but required for Lambda function | ||
:return: "Done" | ||
:exception: Catch CustomException only | ||
""" | ||
|
||
def lambda_handler(event, context): | ||
print("Received event: " + json.dumps(event, indent=2)) | ||
instance_id=event['detail']['instance-id'] | ||
try: | ||
spotid=detect_spot(ec2client,instance_id) | ||
if spotid: | ||
tags=get_tags_from_spot_request(ec2client,instance_id,spotid) | ||
if ("aws:autoscaling:groupName") in tags: | ||
raise CustomException("EC2 is part of autoscaling group...Ignoring") | ||
instancetags=mktags(tags) | ||
createtags(ec2client,instance_id,instancetags) | ||
else: | ||
tags=get_instance_info(ec2client,instance_id) | ||
if ('aws:autoscaling:groupName') in tags: | ||
raise CustomException("EC2 is part of autoscaling group...Ignoring") | ||
time.sleep(60) | ||
name=tags.get('Name') | ||
codedeploygroup=tags.get('CodeDeployDeploymentGroup') | ||
appname=tags.get('CodeDeployApplication') | ||
depresponse=deployment_group_tag(appname,codedeploygroup) | ||
ec2TagFilters = depresponse['deploymentGroupInfo']['ec2TagFilters'] | ||
if name in str(ec2TagFilters): | ||
raise CustomException("EC2 has exact name tag as existing EC2's in deployment group..ignoring...") | ||
for i in range(len(ec2TagFilters)): | ||
ec2TagFilters.append({ | ||
'Key': 'Name', | ||
'Value': name, | ||
'Type': 'KEY_AND_VALUE'}) | ||
update_response = deployment_group_update(appname, codedeploygroup, ec2TagFilters) | ||
deploymentId =depresponse['deploymentGroupInfo']['lastSuccessfulDeployment']['deploymentId'] | ||
print ("DepId is" +deploymentId) | ||
depIdResponse=get_dep(deploymentId) | ||
bucket= depIdResponse['s3Location']['bucket'] | ||
key= depIdResponse['s3Location']['key'] | ||
syncresp=deploy_now(appname,codedeploygroup,bucket,key) | ||
print (syncresp) | ||
return 'Done' | ||
except CustomException as ce: | ||
print(ce) | ||
return 'Done' | ||
|
||
|
||
|
||
def createtags(ec2client,instance_id,instancetags): | ||
ec2client.create_tags( | ||
Resources = [instance_id], | ||
Tags= instancetags | ||
) | ||
|
||
def detect_spot(ec2client, instance_id): | ||
|
||
response = ec2client.describe_instances( | ||
InstanceIds=[ | ||
instance_id, | ||
] | ||
) | ||
spotid = ""; | ||
#Getting hostname for instance-id | ||
for reservation in response["Reservations"]: | ||
for instance in reservation["Instances"]: | ||
if instance.get("SpotInstanceRequestId") : | ||
spotid=instance["SpotInstanceRequestId"] | ||
return spotid; | ||
|
||
class CustomException(Exception): | ||
"""Raise for my specific kind of exception""" | ||
|
||
def get_instance_info(ec2client, instance_id): | ||
|
||
response = ec2client.describe_tags( | ||
Filters = [ | ||
|
||
{ | ||
'Name': 'resource-id', | ||
'Values': [ | ||
instance_id, | ||
] | ||
}, | ||
|
||
] | ||
) | ||
tagdict = {}; | ||
for j in range(len(response['Tags'])): | ||
tagdict[response['Tags'][j]['Key']] = response['Tags'][j]['Value'] | ||
|
||
return tagdict; | ||
|
||
def deployment_group_tag(appname,codedeploygroup): | ||
response = client.get_deployment_group( | ||
applicationName = appname, | ||
deploymentGroupName = codedeploygroup | ||
) | ||
return response; | ||
|
||
|
||
def deployment_group_update(appname, codedeploygroup, ec2TagFilters): | ||
response = client.update_deployment_group( | ||
applicationName = appname, | ||
currentDeploymentGroupName = codedeploygroup, | ||
ec2TagFilters = ec2TagFilters, | ||
serviceRoleArn = codedeployrole | ||
) | ||
return response | ||
|
||
def get_dep(deploymentId): | ||
response=client.get_deployment(deploymentId=deploymentId) | ||
return response['deploymentInfo']['revision'] | ||
|
||
def deploy_now(appname,codedeploygroup,bucket,key): | ||
response = client.create_deployment( | ||
applicationName=appname, | ||
deploymentGroupName=codedeploygroup, | ||
deploymentConfigName='CodeDeployDefault.OneAtATime', | ||
description='Test', | ||
ignoreApplicationStopFailures=True, | ||
updateOutdatedInstancesOnly=True, | ||
fileExistsBehavior='OVERWRITE') | ||
|
||
return response | ||
|
||
def get_tags_from_spot_request(ec2client,instance_id,spot_instance_request_id): | ||
response=ec2client.describe_tags( | ||
Filters = [ | ||
|
||
{ | ||
'Name': 'resource-id', | ||
'Values': [ | ||
spot_instance_request_id, | ||
] | ||
}, | ||
|
||
] | ||
) | ||
tagdict = {}; | ||
for j in range(len(response['Tags'])): | ||
tagdict[response['Tags'][j]['Key']] = response['Tags'][j]['Value'] | ||
return tagdict | ||
|
||
def mktags(taglst): | ||
tags = [] | ||
for t in taglst: | ||
tags.append({'Key': t, 'Value': taglst[t]}) | ||
return tags | ||
|