155 lines
5.2 KiB
Python
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()
|