File: //usr/bin/cfn-signal
#!/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 status {}".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)