clair/cloudformation/generate_stack.py
2015-11-13 13:57:36 -05:00

155 lines
5.2 KiB
Python

# Copyright 2015 CoreOS, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http:# www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import argparse
import re
import logging
import json
import yaml
import sys
import hashlib
import boto.cloudformation as cloudformation
import boto.s3 as s3
from boto.s3.key import Key
from jinja2 import FileSystemLoader, Environment, StrictUndefined
from container_cloud_config import CloudConfigContext
logger = logging.getLogger(__name__)
def userdata(value, json_indent=2):
encoded = {
"Fn::Base64": {
"Fn::Join": ["", [line + '\n' for line in value.split('\n')]]
}
}
return json.dumps(encoded, indent=json_indent)
def bootstrap_user_data(user_data, expiration_seconds=3600):
uploaded = upload_s3_unique(user_data)
signed_url = uploaded.generate_url(expires_in=expiration_seconds)
template = ENV.get_template('bootstrap_cloudconfig.yaml')
return template.render(cloudconfig_url=signed_url)
ENV = Environment(loader=FileSystemLoader('templates'), undefined=StrictUndefined, extensions=['jinja2.ext.do'])
ENV.filters['userdata'] = userdata
ENV.filters['bootstrap_user_data'] = bootstrap_user_data
CONFIG_CONTEXT = CloudConfigContext()
CONFIG_CONTEXT.populate_jinja_environment(ENV)
ARGUMENT = re.compile(r'(-[\w])|(--[\w]+)')
def parse_args():
desc = 'Generate the cloud config for all nodes in the cluster.'
parser = argparse.ArgumentParser(description=desc)
parser.add_argument('template', help='Template file to use when creating stack')
parser.add_argument('region', help='AWS Region',)
parser.add_argument('cfbucket', help='AWS CloudFormation Bucket')
parser.add_argument('accesskey', help='AWS Access Key ID')
parser.add_argument('secretkey', help='AWS Secret Access Key')
parser.add_argument('--json', dest='json', help='Output json config (default).',
action='store_true')
parser.add_argument('--yaml', dest='json', help='Output yaml config.', action='store_false')
parser.add_argument('--upload', dest='stackname',
help='Upload the stack to cloud formation with the given name.')
parser.set_defaults(json=True)
logger.debug('Parsing all args')
_, unknown = parser.parse_known_args()
logger.debug('Unknown args: %s', unknown)
added_args = set()
while (len(unknown) > 0 and ARGUMENT.match(unknown[0]) and
ARGUMENT.match(unknown[0]).end() == len(unknown[0])):
logger.debug('Adding argument: %s', unknown[0])
added_args.add(unknown[0].lstrip('-'))
parser.add_argument(unknown[0])
_, unknown = parser.parse_known_args()
logger.debug('Parsing final set of args')
return parser.parse_args(), added_args
def upload_s3_unique(region, cfbucket, credentials, file_contents):
logger.debug('Checking for file in s3')
json_stack_filename = hashlib.sha1(file_contents).hexdigest()
ess_three = s3.connect_to_region(region, **credentials)
bucket = ess_three.get_bucket(cfbucket, validate=False)
template_key = bucket.get_key(json_stack_filename)
if template_key is None:
logger.debug('Uploading file to s3')
template_key = Key(bucket)
template_key.key = json_stack_filename
template_key.set_contents_from_string(file_contents)
return template_key
def upload(region, cfbucket, credentials, stack_name, json_stack_def):
template_key = upload_s3_unique(region, cfbucket, credentials, json_stack_def)
template_url = template_key.generate_url(expires_in=0, query_auth=False)
logger.debug('Template available in s3 at url: %s', template_url)
logger.debug('Uploading stack definition with name: %s', stack_name)
cf = cloudformation.connect_to_region(region, **credentials)
cf.create_stack(stack_name, capabilities=['CAPABILITY_IAM'], template_url=template_url)
logger.debug('Done uploading stack definition')
def main():
logging.basicConfig(level=logging.DEBUG)
all_args, added_args = parse_args()
template_kwargs = {added: getattr(all_args, added, None) for added in added_args}
credentials = {
'aws_access_key_id': all_args.accesskey,
'aws_secret_access_key': all_args.secretkey,
}
logger.debug('Rendering yaml template')
template = ENV.get_template(all_args.template)
yaml_stack_def = template.render(**template_kwargs)
logger.debug('Validating yaml')
parsed = yaml.load(yaml_stack_def)
if not all_args.json and all_args.stackname:
logger.error('YAML cannot be uploaded directly to cloud formation, please use json')
sys.exit(1)
if all_args.json:
logger.debug('Rendering json')
if all_args.stackname:
json_stack_def = json.dumps(parsed)
CONFIG_CONTEXT.prime_flattened_image_cache()
upload(all_args.region, all_args.cfbucket, credentials, all_args.stackname, json_stack_def)
else:
print json.dumps(parsed, indent=2)
else:
print yaml_stack_def
if __name__ == '__main__':
main()