#!/usr/bin/python3 import argparse # ============================================================================== # Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved. # # 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 sys import cfnbootstrap from cfnbootstrap.cfn_client import CloudFormationClient from optparse import OptionParser from cfnbootstrap.construction import Contractor, WorkLog from cfnbootstrap import util import logging import os import json parser = OptionParser() parser.add_option_group(util.get_cred_options(parser)) parser.add_option_group(util.get_proxy_options(parser)) # user can also specify a file as a positional argument (args[0]) # or pipe in data through stdin (using '-' as an argument) parser.usage = ("%prog [options]\n" " or: %prog [options] \n" " or: cat | %prog [options] -") parser.add_option("-s", "--stack", help="A CloudFormation stack", type="string", dest="stack_name") parser.add_option("-r", "--resource", help="A CloudFormation logical resource ID", type="string", dest="logical_resource_id") parser.add_option("-c", "--configsets", help='An optional list of configSets (default: "default")', type="string", dest="configsets") parser.add_option("-u", "--url", help="The CloudFormation service URL. The endpoint URL must match the region option. Use of this parameter is discouraged.", type="string", dest="endpoint") parser.add_option("", "--region", help="The CloudFormation region. Default: us-east-1.", type="string", dest="region", default="us-east-1") parser.add_option("-v", "--verbose", help="Enables verbose logging", action="store_true", dest="verbose") parser.add_option("-t", "--strict", help=argparse.SUPPRESS, action="store_true", dest="strict") if os.name == "nt": parser.add_option("", "--resume", help="Resume from a previous cfn-init run", action="store_true", dest="resume") (options, args) = parser.parse_args() cfnbootstrap.configureLogging("DEBUG" if options.verbose else "INFO") strict_mode = options.strict worklog = WorkLog() if os.name == "nt" and options.resume: if not worklog.has_key('metadata'): print("Error: cannot resume from previous session; no metadata stored", file=sys.stderr) sys.exit(1) try: worklog.resume() except Exception as e: print("Error occurred during resume: {}".format(str(e)), file=sys.stderr) logging.exception("Unhandled exception during resume: %s", str(e)) sys.exit(1) sys.exit(0) read_from_file = (len(args) > 0) and (args[0] != '-') read_from_stdin = (len(args) > 0) and (args[0] == '-') stack_arg_present = bool(options.stack_name or options.logical_resource_id) read_from_stack = bool(options.stack_name and options.logical_resource_id) if read_from_file or read_from_stdin: if (len(args) > 1) or (read_from_file and read_from_stdin) or (stack_arg_present): print("Error: You cannot specify more than one input source for metadata", file=sys.stderr) parser.print_help(sys.stderr) sys.exit(1) elif not stack_arg_present: print("Error: You must specify an input source for metadata: a stack name and logical resource id, or a file", file=sys.stderr) parser.print_help(sys.stderr) sys.exit(1) elif not read_from_stack: print("Error: You must specify both a stack name and logical resource id", file=sys.stderr) parser.print_help(sys.stderr) sys.exit(1) creds = util.get_creds_or_die(options) url = CloudFormationClient.endpointForRegion(options.region) if options.endpoint: url = options.endpoint configSets = ["default"] if options.configsets: configSets = options.configsets.split(',') proxyinfo = util.get_proxyinfo(options) if read_from_stdin: try: metadata = json.load(sys.stdin) except Exception as e: # the type of exception thrown when reading bad JSON depends on whether json or simplejson was imported if type(e).__name__ == "ValueError" or type(e).__name__ == "JSONDecodeError": print("Error decoding JSON from stdin: {}".format( str(e)), file=sys.stderr) else: print("Unknown error reading metadata from stdin", file=sys.stderr) sys.exit(1) elif read_from_file: try: with open(args[0], 'r+') as f: metadata = json.load(f) except IOError as e: if e.strerror: print(e.strerror, file=sys.stderr) else: print("Unknown error reading from file {}".format( args[0]), file=sys.stderr) sys.exit(1) except Exception as e: if type(e).__name__ == "ValueError" or type(e).__name__ == "JSONDecodeError": print("Error decoding JSON from {0}: {1}".format( args[0], str(e)), file=sys.stderr) else: print("Unknown error reading from file {}".format( args[0]), file=sys.stderr) sys.exit(1) else: try: detail = CloudFormationClient(creds, url=url, region=options.region, proxyinfo=proxyinfo).describe_stack_resource( options.logical_resource_id, options.stack_name) except IOError as e: if e.strerror: print(e.strerror, file=sys.stderr) else: print("Unknown error retrieving {}".format( options.logical_resource_id), file=sys.stderr) sys.exit(1) if not detail.metadata: print("Error: {} does not specify any metadata".format( detail.logicalResourceId), file=sys.stderr) sys.exit(1) metadata = detail.metadata if os.name == 'nt': data_dir = os.path.expandvars(r'${SystemDrive}\cfn\cfn-init\data') else: data_dir = '/var/lib/cfn-init/data' if not os.path.isdir(data_dir) and not os.path.exists(data_dir): os.makedirs(data_dir) if os.path.isdir(data_dir): with open(os.path.join(data_dir, "metadata.json"), "w") as f: json.dump(metadata, f, indent=4) else: print("Could not create {} to store metadata".format( data_dir), file=sys.stderr) logging.error("Could not create {} to store metadata".format(data_dir)) if Contractor.metadataValid(metadata): try: logging.info("Starting build".center(60, '-')) worklog.build(metadata, configSets, strict_mode) except Exception as e: logging.error("BUILD FAILED!".center(60, '-')) print("Error occurred during build: {}".format(str(e)), file=sys.stderr) logging.exception("Unhandled exception during build: %s" % str(e)) sys.exit(1) else: logging.info("Build complete".center(60, '-')) else: if read_from_stack: print("Could not find 'AWS::CloudFormation::Init' key in metadata for {}.".format(options.logical_resource_id), file=sys.stderr) elif read_from_file: print("Could not find 'AWS::CloudFormation::Init' key in {}.".format( args[0]), file=sys.stderr) else: print("Could not find 'AWS::CloudFormation::Init' key in stdin input.", file=sys.stderr) sys.exit(0)