Skip to content

Commit

Permalink
Attach ebs volumes (#72)
Browse files Browse the repository at this point in the history
* Create automation script to attach the volume to the EC2 instance with a simple test

* Added test stack

* clean attach volumne to create automation assume role
  • Loading branch information
sdechgan authored and nanalakshmanan committed Feb 12, 2018
1 parent c7e3fee commit e07f2fe
Show file tree
Hide file tree
Showing 8 changed files with 456 additions and 0 deletions.
26 changes: 26 additions & 0 deletions Automation/AttachEBSVolumes/Design/Design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Attach EBS Volume

## Notes

Initial version will only support attaching an existing volume to an instance.

## Document Design

Refer to schema.json

Document Steps:
1. aws:createStack - Execute CloudFormation Template to attach the volume.
* Parameters:
* Device: {{Device}} - The device name (for example, /dev/sdh or xvdh).
* InstanceId: {{InstanceId}} - The ID of the instance.
* VolumeId: {{VolumeId}} - The ID of the EBS volume. The volume and instance must be within the same Availability Zone.
2. aws:deleteStack - Delete CloudFormation Template.

## Test script

Python script will:
1. Create a test stack with an instance and a volume
2. Execute automation document to attach volume to instance
3. Ensure the automation has executed successfully
4. Detach volume
5. Clean up test stack
25 changes: 25 additions & 0 deletions Automation/AttachEBSVolumes/Design/schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"description": "Attach EBS Volume",
"schemaVersion": "0.3",
"assumeRole": "{{ AutomationAssumeRole }}",
"parameters": {
"Device": {
"type": "String",
"description": "(Required) The device name (for example, /dev/sdh or xvdh )"
},
"InstanceId": {
"type": "String",
"description": "(Required) The ID of the instance"
},
"VolumeId": {
"type": "String",
"description": "(Required) The ID of the EBS volume. The volume and instance must be within the same Availability Zone"
},
"AutomationAssumeRole": {
"type": "String",
"description": "(Optional) The ARN of the role that allows Automation to perform the actions on your behalf. ",
"default": ""
}
},
"mainSteps": []
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
AWSTemplateFormatVersion: '2010-09-09'
Description: Template to attach a EBS volume to an EC2 Instance
Parameters:
Device:
Description: >
The device name (for example, /dev/sdh or xvdh )
Type: String
InstanceId:
Description: >
The ID of the instance
Type: String
VolumeId:
Description: >
The ID of the EBS volume. The volume and instance must be within the same Availability Zone
Type: String
Resources:
TestResource:
Type: AWS::EC2::VolumeAttachment
DeletionPolicy: Retain
Properties:
Device: !Ref Device
InstanceId: !Ref InstanceId
VolumeId: !Ref VolumeId
58 changes: 58 additions & 0 deletions Automation/AttachEBSVolumes/Documents/aws-AttachEBSVolume.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
{
"description": "Attach EBS Volume",
"schemaVersion": "0.3",
"assumeRole": "{{ AutomationAssumeRole }}",
"parameters": {
"Device": {
"type": "String",
"description": "(Required) The device name (for example, /dev/sdh or xvdh )"
},
"InstanceId": {
"type": "String",
"description": "(Required) The ID of the instance"
},
"VolumeId": {
"type": "String",
"description": "(Required) The ID of the EBS volume. The volume and instance must be within the same Availability Zone"
},
"AutomationAssumeRole": {
"type": "String",
"description": "(Optional) The ARN of the role that allows Automation to perform the actions on your behalf. ",
"default": ""
}
},
"mainSteps": [
{
"name": "createDocumentStack",
"action": "aws:createStack",
"inputs": {
"Capabilities": [
"CAPABILITY_IAM"
],
"StackName": "AttachEBSVolumeStack{{automation:EXECUTION_ID}}",
"Parameters": [
{
"ParameterKey": "Device",
"ParameterValue": "{{Device}}"
},
{
"ParameterKey": "InstanceId",
"ParameterValue": "{{InstanceId}}"
},
{
"ParameterKey": "VolumeId",
"ParameterValue": "{{VolumeId}}"
}
],
"TemplateBody": "..."
}
},
{
"name": "deleteCloudFormationTemplate",
"action": "aws:deleteStack",
"inputs": {
"StackName": "AttachEBSVolumeStack{{automation:EXECUTION_ID}}"
}
}
]
}
18 changes: 18 additions & 0 deletions Automation/AttachEBSVolumes/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
TARGET_DIR = "./Output"

documents: targetdir createdocuments
@echo "Done making documents"

targetdir:
@echo "Making $(TARGET_DIR)"
mkdir -p ./Output

createdocuments:
python ./Setup/create_document.py > ./Output/aws-AttachEBSVolume.json

test: documents
python -m unittest discover Tests

clean:
@echo "Removing $(TARGET_DIR)"
@rm -rf ./Output
89 changes: 89 additions & 0 deletions Automation/AttachEBSVolumes/Setup/create_document.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import os
import sys

import json
import yaml

LAMBDA_DIR = os.path.abspath(os.path.join(
os.path.dirname(os.path.realpath(__file__)),
"../Documents/Lambdas"
))
CFT_DIR = os.path.abspath(os.path.join(
os.path.dirname(os.path.realpath(__file__)),
"../Documents/CloudFormationTemplates"
))
DOCUMENT_DIR = os.path.abspath(os.path.join(
os.path.dirname(os.path.realpath(__file__)),
"../Documents"
))


def aws_tag_multi_constructor(loader, tag_suffix, node):
if tag_suffix not in ['Ref', 'Condition']:
tag_suffix = "Fn::{}".format(tag_suffix)

if tag_suffix == "Fn::GetAtt":
result = node.value.split(".")
elif isinstance(node, yaml.ScalarNode):
result = loader.construct_scalar(node)
elif isinstance(node, yaml.SequenceNode):
result = loader.construct_sequence(node)
elif isinstance(node, yaml.MappingNode):
result = loader.construct_mapping(node)
else:
raise "Bad value for {}".format(tag_suffix)

return {tag_suffix: result}


yaml.add_multi_constructor("!", aws_tag_multi_constructor)


def insert_lambda_in_cft(template, resource_name, lambda_file):
lambda_file = os.path.normpath(os.path.join(LAMBDA_DIR, lambda_file))

with open(lambda_file) as fp:
# to shave some bytes we convert 4 spaces to tab
file_content = fp.read().replace(" ", "\t")

file_size = len(file_content)
print >> sys.stderr, lambda_file + " is " + str(file_size) + "/4096 of max size"
assert file_size <= 4096, "Lambda function must be less then 4096"
template["Resources"][resource_name]["Properties"]["Code"]["ZipFile"] = file_content


def insert_cft_in_document(template, step_name, cft_template):
print >> sys.stderr, "Cloud Formation Template is " + str(len(cft_template)) + "/51200 of max size"
assert len(cft_template) < 51200, "CloudFormation template too long, must be less then 50000"
for step in template["mainSteps"]:
if step["name"] == step_name:
step["inputs"]["TemplateBody"] = cft_template
break


def open_cloud_formation_template(file_name):
cloud_formation_file = os.path.normpath(os.path.join(CFT_DIR, file_name))
with open(cloud_formation_file) as fp:
file_content = fp.read()

return yaml.load(file_content)


def open_document(file_name):
cloud_formation_file = os.path.normpath(os.path.join(DOCUMENT_DIR, file_name))
with open(cloud_formation_file) as fp:
return json.load(fp)


def process():
# opens the cloud formation template
template = open_cloud_formation_template("CloudFormationAttachVolume.yml")

# replace document create stack with actual template body.
document = open_document("aws-AttachEBSVolume.json")
insert_cft_in_document(document, "createDocumentStack", yaml.safe_dump(template, indent=2))
print json.dumps(document, indent=2)


if __name__ == '__main__':
process()
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
---
AWSTemplateFormatVersion: '2010-09-09'
Description: Test stack for Attach EBS Volumes
Outputs:
InstanceId:
Description: Instance Id
Value:
Ref: Instance0
VolumeId:
Description: Volume Id
Value:
Ref: Volume0
AutomationAssumeRoleName:
Description: Automation Assume Role Name
Value: !Ref AutomationAssumeRole
AutomationAssumeRoleARN:
Description: Automation Assume Role ARN
Value: !GetAtt AutomationAssumeRole.Arn
Parameters:
AMI:
Description: AMI ID for instances.
Type: String
INSTANCETYPE:
Description: AMI Instance Type (t2.micro, m1.large, etc.)
Type: String
UserARN:
Description: user ARN
Type: String
Resources:
AutomationAssumeRole:
Type: "AWS::IAM::Role"
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Principal:
Service:
- "lambda.amazonaws.com"
- "ssm.amazonaws.com"
Action:
- "sts:AssumeRole"
- Effect: "Allow"
Principal:
AWS: !Ref UserARN
Action:
- "sts:AssumeRole"
ManagedPolicyArns:
- "arn:aws:iam::aws:policy/AdministratorAccess"
Instance0:
Type: AWS::EC2::Instance
Properties:
ImageId: !Ref AMI
InstanceType: !Ref INSTANCETYPE
Volume0:
Type: AWS::EC2::Volume
Properties:
Size: 1
AvailabilityZone: !GetAtt Instance0.AvailabilityZone
Loading

0 comments on commit e07f2fe

Please sign in to comment.