#!/usr/bin/python3 # ============================================================================== # 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 cfnbootstrap from cfnbootstrap.cfn_client import CloudFormationClient from cfnbootstrap import util from optparse import OptionParser from optparse import OptionGroup import base64 import re from cfnbootstrap.packages import requests import socket import sys import json default_id = util.get_instance_id() if not default_id: default_id = socket.getfqdn() parser = OptionParser(usage="usage: %prog [options] [WaitConditionHandle URL]") parser.add_option_group(util.get_cred_options(parser)) parser.add_option_group(util.get_proxy_options(parser)) parser.add_option("-s", "--success", help="If true, signal success to CloudFormation; if false, signal failure. Default: true", dest="success", type="choice", choices=["true", "false"], action="store", default="true") parser.add_option("-i", "--id", help="A unique ID to send with the signal", dest="id", type="string", action="store", default=default_id) parser.add_option("-e", "--exit-code", help="Derive success or failure from specified exit code", dest="exit_code", type="int", action="store") wch_group = OptionGroup(parser, "WaitConditionHandle Signal Options") wch_group.add_option("-r", "--reason", help="The reason for success/failure", dest="reason", type="string", action="store", default="") wch_group.add_option("-d", "--data", help="Data to include with the WaitCondition signal", dest="data", type="string", action="store", default="") parser.add_option_group(wch_group) rs_group = OptionGroup(parser, "Resource Signal Options") rs_group.add_option("", "--stack", help="A CloudFormation stack", dest="stack_name", type="string", action="store") rs_group.add_option("", "--resource", help="A CloudFormation logical resource ID", dest="logical_resource_id", type="string", action="store") rs_group.add_option("", "--url", help="The CloudFormation service URL. The endpoint URL must match the region option. Use of this parameter is discouraged.", dest="endpoint", type="string", action="store") rs_group.add_option("", "--region", help="The CloudFormation region. Default: us-east-1.", dest="region", type="string", default="us-east-1") parser.add_option_group(rs_group) (options, args) = parser.parse_args() signal_success = True if options.exit_code: signal_success = False elif options.success != "true": signal_success = False cfnbootstrap.configureLogging("DEBUG") def get_status(signal_success): return 'SUCCESS' if signal_success else 'FAILURE' @util.retry_on_failure() @util.timeout() def send(url, data): requests.put(url, data=json.dumps(data), headers={"Content-Type": ""}, verify=True, proxies=util.get_proxyinfo(options), timeout=util.REQUEST_TIMEOUT).raise_for_status() def signal_wch(options, args): if not options.reason and not signal_success: options.reason = "Configuration failed." data = {'Status': get_status(signal_success), 'Reason': options.reason, 'Data': options.data, 'UniqueId': options.id} try: url = args[0] if re.match( r'https?://.*', args[0]) else base64.b64decode(args[0]) except TypeError: print("Error: Invalid WaitConditionHandle URL specified: %s" % args[0], file=sys.stderr) sys.exit(1) if isinstance(url, bytes): url = url.decode('utf8') if not re.match(r'https?://.*', url): print("Error: Invalid WaitConditionHandle URL specified: %s" % args[0], file=sys.stderr) sys.exit(1) try: send(url, data) print('CloudFormation signaled successfully with %s.'.format( data['Status'])) sys.exit(0) except IOError as e: print('Error signaling CloudFormation: %s' % str(e), file=sys.stderr) sys.exit(1) def signal_resource(options, args): if not options.stack_name: print("Error: You must specify a stack name when signaling a resource", 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 proxyinfo = util.get_proxyinfo(options) status = get_status(signal_success) try: CloudFormationClient(creds, url=url, region=options.region, proxyinfo=proxyinfo).signal_resource( options.logical_resource_id, options.stack_name, options.id, status) except IOError as e: if e.strerror: print(e.strerror, file=sys.stderr) else: print("Unknown error signaling %s" % options.logical_resource_id, file=sys.stderr) sys.exit(1) is_wch_signal = args and args[0] is_resource_signal = options.logical_resource_id is not None if is_wch_signal and is_resource_signal: print("Error: Cannot specify both a WaitConditionHandle URL and a logical resource id", file=sys.stderr) sys.exit(1) if is_wch_signal: signal_wch(options, args) elif is_resource_signal: signal_resource(options, args) else: print("Error: No WaitConditionHandle URL or logical resource id specified", file=sys.stderr) parser.print_help(sys.stderr) sys.exit(1)