1# Copyright 2015 Google Inc. All rights reserved. 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15import copy 16import dateutil.parser 17import json 18import re 19 20import boto3 21 22import packer 23import service 24import util 25import validate 26 27AMI_CACHE = {} 28 29def amazon_get_amis(region_name, aws_access_key_id, aws_secret_access_key): 30 ec2 = boto3.client('ec2', region_name=region_name, aws_access_key_id=aws_access_key_id, 31 aws_secret_access_key=aws_secret_access_key) 32 result = ec2.describe_images(Owners=['self']) 33 images = result['Images'] 34 return [(i['Name'], dateutil.parser.parse(i['CreationDate'])) for i in images] 35 36def amazon_get_ami_by_name(region_name, aws_access_key_id, aws_secret_access_key, name): 37 ec2 = boto3.client('ec2', region_name=region_name, aws_access_key_id=aws_access_key_id, 38 aws_secret_access_key=aws_secret_access_key) 39 result = ec2.describe_images(Owners=['self'], Filters=[{'Name': 'name', 'Values': [name]}]) 40 images = result['Images'] 41 assert len(images) == 1, util.jsonstr(images) 42 image = images[0] 43 return image['ImageId'] 44 45 46class AmazonPackerBuildArtefact(packer.PackerBuildArtefact): 47 def __init__(self, image, environment): 48 super(AmazonPackerBuildArtefact, self).__init__(image['cmds']) 49 50 self.instanceType = image['instanceType'] 51 self.sourceAmi = image['sourceAmi'] 52 53 self.accessKey = environment['accessKey'] 54 self.secretKey = environment['secretKey'] 55 self.sshUser = image['sshUser'] 56 self.region = environment['region'] 57 58 def builderHashCode(self): 59 builder_hash = 0; 60 builder_hash ^= packer.hash_string(self.sourceAmi) 61 builder_hash ^= packer.hash_string(self.instanceType) 62 return builder_hash 63 64 def builder(self): 65 return { 66 'ami_name': self.name(), 67 68 'type': 'amazon-ebs', 69 'access_key': self.accessKey, 70 'secret_key': self.secretKey, 71 'region': self.region, 72 'source_ami': self.sourceAmi, 73 'instance_type': self.instanceType, 74 'ssh_username': self.sshUser, 75 } 76 77 def needsBuild(self): 78 print 'Checking if AMI exists: %s/%s' % (self.region, self.name()) 79 if (self.region, self.accessKey) in AMI_CACHE: 80 existing_image_names = AMI_CACHE[self.region, self.accessKey] 81 else: 82 existing_image_names = [ 83 img[0] 84 for img in amazon_get_amis(self.region, self.accessKey, self.secretKey)] 85 AMI_CACHE[self.region, self.accessKey] = existing_image_names 86 return self.name() not in existing_image_names 87 88 def doBuild(self, dirpath): 89 super(AmazonPackerBuildArtefact, self).doBuild(dirpath) 90 if self.accessKey not in AMI_CACHE: 91 AMI_CACHE[self.accessKey] = [] 92 AMI_CACHE[self.accessKey] += [self.name()] 93 94 def postBuild(self): 95 self.id = amazon_get_ami_by_name(self.region, self.accessKey, self.secretKey, self.name()) 96 97 98class AmazonService(service.Service): 99 100 def validateEnvironment(self, root, path): 101 fields = {'kind', 'accessKey', 'secretKey', 'region'} 102 validate.obj_only(root, path, fields) 103 validate.path_val(root, path + ['region'], 'string') 104 validate.path_val(root, path + ['accessKey'], 'string') 105 validate.path_val(root, path + ['secretKey'], 'string') 106 107 def validateService(self, root, path): 108 super(AmazonService, self).validateService(root, path) 109 infra_path = path + ['infrastructure'] 110 validate.path_val(root, infra_path, 'object', {}) 111 inst_path = infra_path + ['aws_instance'] 112 instances = validate.path_val(root, inst_path, 'object', {}) 113 114 # Validate image configs 115 for inst_name, inst in instances.iteritems(): 116 self.validateCmds(root, inst_path + [inst_name, 'cmds']) 117 self.validateCmds(root, inst_path + [inst_name, 'bootCmds']) 118 image = inst.get('ami') 119 if isinstance(image, dict): 120 self.validateImage(root, inst_path + [inst_name, 'ami']) 121 122 def validateImage(self, root, path): 123 super(AmazonService, self).validateImage(root, path) 124 validate.path_val(root, path + ['instanceType'], 'string', 'n1-standard-1') 125 validate.path_val(root, path + ['sourceAmi'], 'string') 126 validate.path_val(root, path + ['sshUser'], 'string') 127 validate.obj_only(root, path, {'cmds', 'instanceType', 'sourceAmi', 'sshUser'}) 128 129 def compileProvider(self, environment_name, environment): 130 return { 131 'environment.%s.tf.json' % environment_name: { 132 'provider': { 133 'aws': { 134 'alias': environment_name, 135 'access_key': environment['accessKey'], 136 'secret_key': environment['secretKey'], 137 'region' : environment['region'], 138 }, 139 }, 140 }, 141 } 142 143 def getBuildArtefacts(self, environment, ctx, service): 144 service = copy.deepcopy(service) 145 barts = {} # Build artefacts. 146 infra = service['infrastructure'] 147 148 instances = infra.get('aws_instance', {}) 149 150 # Process AMI configs 151 for inst_name, inst in instances.iteritems(): 152 ami = inst['ami'] 153 if isinstance(ami, dict): 154 bart = AmazonPackerBuildArtefact(ami, environment) 155 barts[bart.name()] = bart 156 inst['ami'] = bart.name() 157 158 return service, barts 159 160 def compile(self, ctx, service_name, service, barts): 161 infra = service['infrastructure'] 162 163 # Add provider attributes 164 for res_kind_name, res_kind_obj in infra.iteritems(): 165 for res_name, res in res_kind_obj.iteritems(): 166 res['provider'] = 'aws.%s' % service.get('environment', 'default') 167 168 instances = infra.get('aws_instance', {}) 169 170 # Convert AMI name to id 171 for inst_name, inst in instances.iteritems(): 172 ami = inst['ami'] 173 if ami in barts: 174 inst['ami'] = barts[ami].id 175 176 # Process commands 177 for inst_name, inst in instances.iteritems(): 178 cmds = inst['cmds'] 179 boot_cmds = inst['bootCmds'] 180 if inst.get('user_data'): 181 raise RuntimeError('Cannot use user_data, use cmds instead.') 182 inst['user_data'] = self.compileStartupScript(cmds, boot_cmds) 183 inst.pop('cmds', None) 184 inst.pop('bootCmds', None) 185 186 return { 187 'service.%s.tf.json' % self.fullName(ctx + [service_name]): { 188 'resource': infra, 189 'output': { 190 k: { 'value': v } 191 for k, v in service['outputs'].iteritems() 192 } 193 } 194 } 195