def _cs_request(self, url, method, **kwargs): # Check that certain things are called correctly if method in ['GET', 'DELETE']: assert 'body' not in kwargs elif method == 'PUT': assert 'body' in kwargs # Call the method args = urlutils.parse_qsl(urlutils.urlparse(url)[4]) kwargs.update(args) munged_url = url.rsplit('?', 1)[0] munged_url = munged_url.strip('/').replace('/', '_').replace('.', '_') munged_url = munged_url.replace('-', '_') callback = "%s_%s" % (method.lower(), munged_url) if not hasattr(self, callback): raise AssertionError('Called unknown API method: %s %s, ' 'expected fakes method name: %s' % (method, url, callback)) # Note the call self.callstack.append((method, url, kwargs.get('body', None))) status, body = getattr(self, callback)(**kwargs) if hasattr(status, 'items'): return httplib2.Response(status), body else: return httplib2.Response({"status": status}), body
def FnGetAtt(self, key): url, token_id = self.swift().get_auth() parsed = list(urlutils.urlparse(url)) if key == 'DomainName': return parsed[1].split(':')[0] elif key == 'WebsiteURL': return '%s://%s%s/%s' % (parsed[0], parsed[1], parsed[2], self.resource_id) elif key == 'RootURL': return '%s://%s%s' % (parsed[0], parsed[1], parsed[2]) elif self.resource_id and key in ( 'ObjectCount', 'BytesUsed', 'HeadContainer'): try: headers = self.swift().head_container(self.resource_id) except clients.swiftclient.ClientException as ex: logger.warn(_("Head container failed: %s") % str(ex)) return None else: if key == 'ObjectCount': return headers['x-container-object-count'] elif key == 'BytesUsed': return headers['x-container-bytes-used'] elif key == 'HeadContainer': return headers else: raise exception.InvalidTemplateAttribute(resource=self.name, key=key)
def _load_mysql_dump_file(self, engine, file_name): for key, eng in self.engines.items(): if eng is engine: conn_string = self.test_databases[key] conn_pieces = urlutils.urlparse(conn_string) if conn_string.startswith('mysql'): break else: return (user, password, database, host) = \ test_migrations.get_db_connection_info(conn_pieces) cmd = ( 'mysql -u \"%(user)s\" -p\"%(password)s\" -h %(host)s %(db)s ') % { 'user': user, 'password': password, 'host': host, 'db': database } file_path = os.path.join(os.path.dirname(__file__), file_name) with open(file_path) as sql_file: process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stdin=sql_file, stderr=subprocess.STDOUT) output = process.communicate()[0] self.assertEqual(0, process.returncode, "Failed to run: %s\n%s" % (cmd, output))
def _reset_databases(self): for key, engine in self.engines.items(): conn_string = self.test_databases[key] conn_pieces = urlutils.urlparse(conn_string) engine.dispose() if conn_string.startswith('sqlite'): # We can just delete the SQLite database, which is # the easiest and cleanest solution db_path = conn_pieces.path.strip('/') if os.path.exists(db_path): os.unlink(db_path) # No need to recreate the SQLite DB. SQLite will # create it for us if it's not there... elif conn_string.startswith('mysql'): # We can execute the MySQL client to destroy and re-create # the MYSQL database, which is easier and less error-prone # than using SQLAlchemy to do this via MetaData...trust me. (user, password, database, host) = \ get_db_connection_info(conn_pieces) sql = ("drop database if exists %(db)s; " "create database %(db)s;") % {'db': database} cmd = ("mysql -u \"%(user)s\" -p\"%(password)s\" -h %(host)s " "-e \"%(sql)s\"") % {'user': user, 'password': password, 'host': host, 'sql': sql} self.execute_cmd(cmd) elif conn_string.startswith('postgresql'): self._reset_pg(conn_pieces)
def _load_mysql_dump_file(self, engine, file_name): for key, eng in self.engines.items(): if eng is engine: conn_string = self.test_databases[key] conn_pieces = urlutils.urlparse(conn_string) if conn_string.startswith('mysql'): break else: return (user, password, database, host) = \ test_migrations.get_db_connection_info(conn_pieces) cmd = ('mysql -u \"%(user)s\" -p\"%(password)s\" -h %(host)s %(db)s ' ) % {'user': user, 'password': password, 'host': host, 'db': database} file_path = os.path.join(os.path.dirname(__file__), file_name) with open(file_path) as sql_file: process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stdin=sql_file, stderr=subprocess.STDOUT) output = process.communicate()[0] self.assertEqual(0, process.returncode, "Failed to run: %s\n%s" % (cmd, output))
def _resolve_attribute(self, name): url = self.swift().get_auth()[0] parsed = list(urlutils.urlparse(url)) if name == 'DomainName': return parsed[1].split(':')[0] elif name == 'WebsiteURL': return '%s://%s%s/%s' % (parsed[0], parsed[1], parsed[2], self.resource_id)
def _get_signed_url(self, signal_type=SIGNAL): """Create properly formatted and pre-signed URL. This uses the created user for the credentials. See boto/auth.py::QuerySignatureV2AuthHandler :param signal_type: either WAITCONDITION or SIGNAL. """ try: stored = db_api.resource_data_get(self, 'ec2_signed_url') except exception.NotFound: stored = None if stored is not None: return stored try: access_key = db_api.resource_data_get(self, 'access_key') secret_key = db_api.resource_data_get(self, 'secret_key') except exception.NotFound: logger.warning( _('Cannot generate signed url, ' 'no stored access/secret key')) return waitcond_url = cfg.CONF.heat_waitcondition_server_url signal_url = waitcond_url.replace('/waitcondition', signal_type) host_url = urlutils.urlparse(signal_url) path = self.identifier().arn_url_path() # Note the WSGI spec apparently means that the webob request we end up # prcessing in the CFN API (ec2token.py) has an unquoted path, so we # need to calculate the signature with the path component unquoted, but # ensure the actual URL contains the quoted version... unquoted_path = urlutils.unquote(host_url.path + path) request = { 'host': host_url.netloc.lower(), 'verb': SIGNAL_VERB[signal_type], 'path': unquoted_path, 'params': { 'SignatureMethod': 'HmacSHA256', 'SignatureVersion': '2', 'AWSAccessKeyId': access_key, 'Timestamp': self.created_time.strftime("%Y-%m-%dT%H:%M:%SZ") } } # Sign the request signer = ec2_utils.Ec2Signer(secret_key) request['params']['Signature'] = signer.generate(request) qs = urlutils.urlencode(request['params']) url = "%s%s?%s" % (signal_url.lower(), path, qs) db_api.resource_data_set(self, 'ec2_signed_url', url) return url
def _get_signed_url(self, signal_type=SIGNAL): """Create properly formatted and pre-signed URL. This uses the created user for the credentials. See boto/auth.py::QuerySignatureV2AuthHandler :param signal_type: either WAITCONDITION or SIGNAL. """ try: stored = db_api.resource_data_get(self, 'ec2_signed_url') except exception.NotFound: stored = None if stored is not None: return stored try: access_key = db_api.resource_data_get(self, 'access_key') secret_key = db_api.resource_data_get(self, 'secret_key') except exception.NotFound: logger.warning(_('Cannot generate signed url, ' 'no stored access/secret key')) return waitcond_url = cfg.CONF.heat_waitcondition_server_url signal_url = waitcond_url.replace('/waitcondition', signal_type) host_url = urlutils.urlparse(signal_url) path = self.identifier().arn_url_path() # Note the WSGI spec apparently means that the webob request we end up # prcessing in the CFN API (ec2token.py) has an unquoted path, so we # need to calculate the signature with the path component unquoted, but # ensure the actual URL contains the quoted version... unquoted_path = urlutils.unquote(host_url.path + path) request = {'host': host_url.netloc.lower(), 'verb': SIGNAL_VERB[signal_type], 'path': unquoted_path, 'params': {'SignatureMethod': 'HmacSHA256', 'SignatureVersion': '2', 'AWSAccessKeyId': access_key, 'Timestamp': self.created_time.strftime("%Y-%m-%dT%H:%M:%SZ") }} # Sign the request signer = ec2_utils.Ec2Signer(secret_key) request['params']['Signature'] = signer.generate(request) qs = urlutils.urlencode(request['params']) url = "%s%s?%s" % (signal_url.lower(), path, qs) db_api.resource_data_set(self, 'ec2_signed_url', url) return url
def test_get_collection_links_does_not_overwrite_other_params(self): self.setUpGetCollectionLinks() self.request.params = {'limit': '2', 'foo': 'bar'} links = views_common.get_collection_links(self.request, self.items) next_link = filter(lambda link: link['rel'] == 'next', links).pop() url = next_link['href'] query_string = urlutils.urlparse(url).query params = {} params.update(urlutils.parse_qsl(query_string)) self.assertEqual('2', params['limit']) self.assertEqual('bar', params['foo'])
def get(url, allowed_schemes=('http', 'https')): ''' Get the data at the specifier URL. The URL must use the http: or https: schemes. The file: scheme is also supported if you override the allowed_schemes argument. Raise an IOError if getting the data fails. ''' logger.info(_('Fetching data from %s') % url) components = urlutils.urlparse(url) if components.scheme not in allowed_schemes: raise IOError(_('Invalid URL scheme %s') % components.scheme) if components.scheme == 'file': try: return urlutils.urlopen(url).read() except urlutils.URLError as uex: raise IOError(_('Failed to retrieve template: %s') % str(uex)) try: resp = requests.get(url, stream=True) resp.raise_for_status() # We cannot use resp.text here because it would download the # entire file, and a large enough file would bring down the # engine. The 'Content-Length' header could be faked, so it's # necessary to download the content in chunks to until # max_template_size is reached. The chunk_size we use needs # to balance CPU-intensive string concatenation with accuracy # (eg. it's possible to fetch 1000 bytes greater than # max_template_size with a chunk_size of 1000). reader = resp.iter_content(chunk_size=1000) result = "" for chunk in reader: result += chunk if len(result) > cfg.CONF.max_template_size: raise IOError("Template exceeds maximum allowed size (%s " "bytes)" % cfg.CONF.max_template_size) return result except exceptions.RequestException as ex: raise IOError(_('Failed to retrieve template: %s') % str(ex))
def from_arn_url(cls, url): """ Return a new HeatIdentifier generated by parsing the supplied URL The URL is expected to contain a valid arn as part of the path """ # Sanity check the URL urlp = urlutils.urlparse(url) if urlp.scheme not in ("http", "https") or not urlp.netloc or not urlp.path: raise ValueError(_('"%s" is not a valid URL') % url) # Remove any query-string and extract the ARN arn_url_prefix = "/arn%3Aopenstack%3Aheat%3A%3A" match = re.search(arn_url_prefix, urlp.path, re.IGNORECASE) if match is None: raise ValueError(_('"%s" is not a valid ARN URL') % url) # the +1 is to skip the leading / url_arn = urlp.path[match.start() + 1 :] arn = urlutils.unquote(url_arn) return cls.from_arn(arn)
def from_arn_url(cls, url): ''' Return a new HeatIdentifier generated by parsing the supplied URL The URL is expected to contain a valid arn as part of the path ''' # Sanity check the URL urlp = urlutils.urlparse(url) if (urlp.scheme not in ('http', 'https') or not urlp.netloc or not urlp.path): raise ValueError(_('"%s" is not a valid URL') % url) # Remove any query-string and extract the ARN arn_url_prefix = '/arn%3Aopenstack%3Aheat%3A%3A' match = re.search(arn_url_prefix, urlp.path, re.IGNORECASE) if match is None: raise ValueError(_('"%s" is not a valid ARN URL') % url) # the +1 is to skip the leading / url_arn = urlp.path[match.start() + 1:] arn = urlutils.unquote(url_arn) return cls.from_arn(arn)
def build_userdata(resource, userdata=None, instance_user=None, user_data_format='HEAT_CFNTOOLS'): ''' Build multipart data blob for CloudInit which includes user-supplied Metadata, user data, and the required Heat in-instance configuration. :param resource: the resource implementation :type resource: heat.engine.Resource :param userdata: user data string :type userdata: str or None :param instance_user: the user to create on the server :type instance_user: string :param user_data_format: Format of user data to return :type user_data_format: string :returns: multipart mime as a string ''' if user_data_format == 'RAW': return userdata is_cfntools = user_data_format == 'HEAT_CFNTOOLS' is_software_config = user_data_format == 'SOFTWARE_CONFIG' def make_subpart(content, filename, subtype=None): if subtype is None: subtype = os.path.splitext(filename)[0] msg = MIMEText(content, _subtype=subtype) msg.add_header('Content-Disposition', 'attachment', filename=filename) return msg def read_cloudinit_file(fn): return pkgutil.get_data('heat', 'cloudinit/%s' % fn) if instance_user: config_custom_user = '******' % instance_user # FIXME(shadower): compatibility workaround for cloud-init 0.6.3. We # can drop this once we stop supporting 0.6.3 (which ships with Ubuntu # 12.04 LTS). # # See bug https://bugs.launchpad.net/heat/+bug/1257410 boothook_custom_user = r"""useradd -m %s echo -e '%s\tALL=(ALL)\tNOPASSWD: ALL' >> /etc/sudoers """ % (instance_user, instance_user) else: config_custom_user = '' boothook_custom_user = '' cloudinit_config = string.Template( read_cloudinit_file('config')).safe_substitute( add_custom_user=config_custom_user) cloudinit_boothook = string.Template( read_cloudinit_file('boothook.sh')).safe_substitute( add_custom_user=boothook_custom_user) attachments = [(cloudinit_config, 'cloud-config'), (cloudinit_boothook, 'boothook.sh', 'cloud-boothook'), (read_cloudinit_file('part_handler.py'), 'part-handler.py')] if is_cfntools: attachments.append((userdata, 'cfn-userdata', 'x-cfninitdata')) elif is_software_config: # attempt to parse userdata as a multipart message, and if it # is, add each part as an attachment userdata_parts = None try: userdata_parts = email.message_from_string(userdata) except: pass if userdata_parts and userdata_parts.is_multipart(): for part in userdata_parts.get_payload(): attachments.append((part.get_payload(), part.get_filename(), part.get_content_subtype())) else: attachments.append((userdata, 'userdata', 'x-shellscript')) if is_cfntools: attachments.append((read_cloudinit_file('loguserdata.py'), 'loguserdata.py', 'x-shellscript')) metadata = resource.metadata if metadata: attachments.append((json.dumps(metadata), 'cfn-init-data', 'x-cfninitdata')) attachments.append((cfg.CONF.heat_watch_server_url, 'cfn-watch-server', 'x-cfninitdata')) if is_cfntools: attachments.append((cfg.CONF.heat_metadata_server_url, 'cfn-metadata-server', 'x-cfninitdata')) # Create a boto config which the cfntools on the host use to know # where the cfn and cw API's are to be accessed cfn_url = urlutils.urlparse(cfg.CONF.heat_metadata_server_url) cw_url = urlutils.urlparse(cfg.CONF.heat_watch_server_url) is_secure = cfg.CONF.instance_connection_is_secure vcerts = cfg.CONF.instance_connection_https_validate_certificates boto_cfg = "\n".join(["[Boto]", "debug = 0", "is_secure = %s" % is_secure, "https_validate_certificates = %s" % vcerts, "cfn_region_name = heat", "cfn_region_endpoint = %s" % cfn_url.hostname, "cloudwatch_region_name = heat", "cloudwatch_region_endpoint = %s" % cw_url.hostname]) attachments.append((boto_cfg, 'cfn-boto-cfg', 'x-cfninitdata')) subparts = [make_subpart(*args) for args in attachments] mime_blob = MIMEMultipart(_subparts=subparts) return mime_blob.as_string()
def __init__(self, url): self.url = urlutils.urlparse(url)
def build_userdata(resource, userdata=None, instance_user=None, user_data_format='HEAT_CFNTOOLS'): ''' Build multipart data blob for CloudInit which includes user-supplied Metadata, user data, and the required Heat in-instance configuration. :param resource: the resource implementation :type resource: heat.engine.Resource :param userdata: user data string :type userdata: str or None :param instance_user: the user to create on the server :type instance_user: string :param user_data_format: Format of user data to return :type user_data_format: string :returns: multipart mime as a string ''' if user_data_format == 'RAW': return userdata is_cfntools = user_data_format == 'HEAT_CFNTOOLS' is_software_config = user_data_format == 'SOFTWARE_CONFIG' def make_subpart(content, filename, subtype=None): if subtype is None: subtype = os.path.splitext(filename)[0] msg = MIMEText(content, _subtype=subtype) msg.add_header('Content-Disposition', 'attachment', filename=filename) return msg def read_cloudinit_file(fn): return pkgutil.get_data('heat', 'cloudinit/%s' % fn) if instance_user: config_custom_user = '******' % instance_user # FIXME(shadower): compatibility workaround for cloud-init 0.6.3. We # can drop this once we stop supporting 0.6.3 (which ships with Ubuntu # 12.04 LTS). # # See bug https://bugs.launchpad.net/heat/+bug/1257410 boothook_custom_user = r"""useradd -m %s echo -e '%s\tALL=(ALL)\tNOPASSWD: ALL' >> /etc/sudoers """ % (instance_user, instance_user) else: config_custom_user = '' boothook_custom_user = '' cloudinit_config = string.Template( read_cloudinit_file('config')).safe_substitute( add_custom_user=config_custom_user) cloudinit_boothook = string.Template( read_cloudinit_file('boothook.sh')).safe_substitute( add_custom_user=boothook_custom_user) attachments = [(cloudinit_config, 'cloud-config'), (cloudinit_boothook, 'boothook.sh', 'cloud-boothook'), (read_cloudinit_file('part_handler.py'), 'part-handler.py')] if is_cfntools: attachments.append((userdata, 'cfn-userdata', 'x-cfninitdata')) elif is_software_config: # attempt to parse userdata as a multipart message, and if it # is, add each part as an attachment userdata_parts = None try: userdata_parts = email.message_from_string(userdata) except: pass if userdata_parts and userdata_parts.is_multipart(): for part in userdata_parts.get_payload(): attachments.append((part.get_payload(), part.get_filename(), part.get_content_subtype())) else: attachments.append((userdata, 'userdata', 'x-shellscript')) if is_cfntools: attachments.append((read_cloudinit_file('loguserdata.py'), 'loguserdata.py', 'x-shellscript')) metadata = resource.metadata if metadata: attachments.append( (json.dumps(metadata), 'cfn-init-data', 'x-cfninitdata')) attachments.append( (cfg.CONF.heat_watch_server_url, 'cfn-watch-server', 'x-cfninitdata')) if is_cfntools: attachments.append((cfg.CONF.heat_metadata_server_url, 'cfn-metadata-server', 'x-cfninitdata')) # Create a boto config which the cfntools on the host use to know # where the cfn and cw API's are to be accessed cfn_url = urlutils.urlparse(cfg.CONF.heat_metadata_server_url) cw_url = urlutils.urlparse(cfg.CONF.heat_watch_server_url) is_secure = cfg.CONF.instance_connection_is_secure vcerts = cfg.CONF.instance_connection_https_validate_certificates boto_cfg = "\n".join([ "[Boto]", "debug = 0", "is_secure = %s" % is_secure, "https_validate_certificates = %s" % vcerts, "cfn_region_name = heat", "cfn_region_endpoint = %s" % cfn_url.hostname, "cloudwatch_region_name = heat", "cloudwatch_region_endpoint = %s" % cw_url.hostname ]) attachments.append((boto_cfg, 'cfn-boto-cfg', 'x-cfninitdata')) subparts = [make_subpart(*args) for args in attachments] mime_blob = MIMEMultipart(_subparts=subparts) return mime_blob.as_string()
def build_userdata(resource, userdata=None, instance_user=None, user_data_format='HEAT_CFNTOOLS'): ''' Build multipart data blob for CloudInit which includes user-supplied Metadata, user data, and the required Heat in-instance configuration. :param resource: the resource implementation :type resource: heat.engine.Resource :param userdata: user data string :type userdata: str or None :param instance_user: the user to create on the server :type instance_user: string :param user_data_format: Format of user data to return :type user_data_format: string :returns: multipart mime as a string ''' if user_data_format == 'RAW': return userdata def make_subpart(content, filename, subtype=None): if subtype is None: subtype = os.path.splitext(filename)[0] msg = MIMEText(content, _subtype=subtype) msg.add_header('Content-Disposition', 'attachment', filename=filename) return msg def read_cloudinit_file(fn): data = pkgutil.get_data('heat', 'cloudinit/%s' % fn) data = data.replace('@INSTANCE_USER@', instance_user or cfg.CONF.instance_user) return data attachments = [(read_cloudinit_file('config'), 'cloud-config'), (read_cloudinit_file('boothook.sh'), 'boothook.sh', 'cloud-boothook'), (read_cloudinit_file('part_handler.py'), 'part-handler.py'), (userdata, 'cfn-userdata', 'x-cfninitdata'), (read_cloudinit_file('loguserdata.py'), 'loguserdata.py', 'x-shellscript')] if 'Metadata' in resource.t: attachments.append( (json.dumps(resource.metadata), 'cfn-init-data', 'x-cfninitdata')) attachments.append( (cfg.CONF.heat_watch_server_url, 'cfn-watch-server', 'x-cfninitdata')) attachments.append((cfg.CONF.heat_metadata_server_url, 'cfn-metadata-server', 'x-cfninitdata')) # Create a boto config which the cfntools on the host use to know # where the cfn and cw API's are to be accessed cfn_url = urlutils.urlparse(cfg.CONF.heat_metadata_server_url) cw_url = urlutils.urlparse(cfg.CONF.heat_watch_server_url) is_secure = cfg.CONF.instance_connection_is_secure vcerts = cfg.CONF.instance_connection_https_validate_certificates boto_cfg = "\n".join([ "[Boto]", "debug = 0", "is_secure = %s" % is_secure, "https_validate_certificates = %s" % vcerts, "cfn_region_name = heat", "cfn_region_endpoint = %s" % cfn_url.hostname, "cloudwatch_region_name = heat", "cloudwatch_region_endpoint = %s" % cw_url.hostname ]) attachments.append((boto_cfg, 'cfn-boto-cfg', 'x-cfninitdata')) subparts = [make_subpart(*args) for args in attachments] mime_blob = MIMEMultipart(_subparts=subparts) return mime_blob.as_string()
def build_userdata(resource, userdata=None, instance_user=None, user_data_format="HEAT_CFNTOOLS"): """ Build multipart data blob for CloudInit which includes user-supplied Metadata, user data, and the required Heat in-instance configuration. :param resource: the resource implementation :type resource: heat.engine.Resource :param userdata: user data string :type userdata: str or None :param instance_user: the user to create on the server :type instance_user: string :param user_data_format: Format of user data to return :type user_data_format: string :returns: multipart mime as a string """ if user_data_format == "RAW": return userdata def make_subpart(content, filename, subtype=None): if subtype is None: subtype = os.path.splitext(filename)[0] msg = MIMEText(content, _subtype=subtype) msg.add_header("Content-Disposition", "attachment", filename=filename) return msg def read_cloudinit_file(fn): data = pkgutil.get_data("heat", "cloudinit/%s" % fn) data = data.replace("@INSTANCE_USER@", instance_user or cfg.CONF.instance_user) return data attachments = [ (read_cloudinit_file("config"), "cloud-config"), (read_cloudinit_file("boothook.sh"), "boothook.sh", "cloud-boothook"), (read_cloudinit_file("part_handler.py"), "part-handler.py"), (userdata, "cfn-userdata", "x-cfninitdata"), (read_cloudinit_file("loguserdata.py"), "loguserdata.py", "x-shellscript"), ] if "Metadata" in resource.t: attachments.append((json.dumps(resource.metadata), "cfn-init-data", "x-cfninitdata")) attachments.append((cfg.CONF.heat_watch_server_url, "cfn-watch-server", "x-cfninitdata")) attachments.append((cfg.CONF.heat_metadata_server_url, "cfn-metadata-server", "x-cfninitdata")) # Create a boto config which the cfntools on the host use to know # where the cfn and cw API's are to be accessed cfn_url = urlutils.urlparse(cfg.CONF.heat_metadata_server_url) cw_url = urlutils.urlparse(cfg.CONF.heat_watch_server_url) is_secure = cfg.CONF.instance_connection_is_secure vcerts = cfg.CONF.instance_connection_https_validate_certificates boto_cfg = "\n".join( [ "[Boto]", "debug = 0", "is_secure = %s" % is_secure, "https_validate_certificates = %s" % vcerts, "cfn_region_name = heat", "cfn_region_endpoint = %s" % cfn_url.hostname, "cloudwatch_region_name = heat", "cloudwatch_region_endpoint = %s" % cw_url.hostname, ] ) attachments.append((boto_cfg, "cfn-boto-cfg", "x-cfninitdata")) subparts = [make_subpart(*args) for args in attachments] mime_blob = MIMEMultipart(_subparts=subparts) return mime_blob.as_string()
def build_userdata(resource, userdata=None, instance_user=None, user_data_format='HEAT_CFNTOOLS'): ''' Build multipart data blob for CloudInit which includes user-supplied Metadata, user data, and the required Heat in-instance configuration. :param resource: the resource implementation :type resource: heat.engine.Resource :param userdata: user data string :type userdata: str or None :param instance_user: the user to create on the server :type instance_user: string :param user_data_format: Format of user data to return :type user_data_format: string :returns: multipart mime as a string ''' if user_data_format == 'RAW': return userdata is_cfntools = user_data_format == 'HEAT_CFNTOOLS' is_software_config = user_data_format == 'SOFTWARE_CONFIG' def make_subpart(content, filename, subtype=None): if subtype is None: subtype = os.path.splitext(filename)[0] msg = MIMEText(content, _subtype=subtype) msg.add_header('Content-Disposition', 'attachment', filename=filename) return msg def read_cloudinit_file(fn): data = pkgutil.get_data('heat', 'cloudinit/%s' % fn) data = data.replace('@INSTANCE_USER@', instance_user or cfg.CONF.instance_user) return data attachments = [(read_cloudinit_file('config'), 'cloud-config'), (read_cloudinit_file('boothook.sh'), 'boothook.sh', 'cloud-boothook')] attachments.append((read_cloudinit_file('part_handler.py'), 'part-handler.py')) if is_cfntools: attachments.append((userdata, 'cfn-userdata', 'x-cfninitdata')) elif is_software_config: # attempt to parse userdata as a multipart message, and if it # is, add each part as an attachment userdata_parts = None try: userdata_parts = email.message_from_string(userdata) except: pass if userdata_parts and userdata_parts.is_multipart(): for part in userdata_parts.get_payload(): attachments.append((part.get_payload(), part.get_filename(), part.get_content_subtype())) else: attachments.append((userdata, 'userdata', 'x-shellscript')) if is_cfntools: attachments.append((read_cloudinit_file('loguserdata.py'), 'loguserdata.py', 'x-shellscript')) metadata = resource.metadata if metadata: attachments.append((json.dumps(metadata), 'cfn-init-data', 'x-cfninitdata')) attachments.append((cfg.CONF.heat_watch_server_url, 'cfn-watch-server', 'x-cfninitdata')) if is_cfntools: attachments.append((cfg.CONF.heat_metadata_server_url, 'cfn-metadata-server', 'x-cfninitdata')) # Create a boto config which the cfntools on the host use to know # where the cfn and cw API's are to be accessed cfn_url = urlutils.urlparse(cfg.CONF.heat_metadata_server_url) cw_url = urlutils.urlparse(cfg.CONF.heat_watch_server_url) is_secure = cfg.CONF.instance_connection_is_secure vcerts = cfg.CONF.instance_connection_https_validate_certificates boto_cfg = "\n".join(["[Boto]", "debug = 0", "is_secure = %s" % is_secure, "https_validate_certificates = %s" % vcerts, "cfn_region_name = heat", "cfn_region_endpoint = %s" % cfn_url.hostname, "cloudwatch_region_name = heat", "cloudwatch_region_endpoint = %s" % cw_url.hostname]) attachments.append((boto_cfg, 'cfn-boto-cfg', 'x-cfninitdata')) subparts = [make_subpart(*args) for args in attachments] mime_blob = MIMEMultipart(_subparts=subparts) return mime_blob.as_string()