heat/heat/engine/resources/openstack/heat/multi_part.py

169 lines
5.8 KiB
Python

#
# 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 email
from email.mime import multipart
from email.mime import text
import os
from heat.common.i18n import _
from heat.engine import constraints
from heat.engine import properties
from heat.engine.resources.openstack.heat import software_config
from heat.engine import support
from heat.rpc import api as rpc_api
class MultipartMime(software_config.SoftwareConfig):
'''
A resource which assembles a collection of software configurations
as a multi-part mime message.
Parts in the message can be populated with inline configuration or
references to other config resources. If the referenced resource is itself
a valid multi-part mime message, that will be broken into parts and
those parts appended to this message.
The resulting multi-part mime message will be stored by the configs API
and can be referenced in properties such as OS::Nova::Server user_data.
This resource is generally used to build a list of cloud-init
configuration elements including scripts and cloud-config. Since
cloud-init is boot-only configuration, any changes to the definition
will result in the replacement of all servers which reference it.
'''
support_status = support.SupportStatus(version='2014.1')
PROPERTIES = (
PARTS, CONFIG, FILENAME, TYPE, SUBTYPE
) = (
'parts', 'config', 'filename', 'type', 'subtype'
)
TYPES = (
TEXT, MULTIPART
) = (
'text', 'multipart'
)
properties_schema = {
PARTS: properties.Schema(
properties.Schema.LIST,
_('Parts belonging to this message.'),
default=[],
schema=properties.Schema(
properties.Schema.MAP,
schema={
CONFIG: properties.Schema(
properties.Schema.STRING,
_('Content of part to attach, either inline or by '
'referencing the ID of another software config '
'resource'),
required=True
),
FILENAME: properties.Schema(
properties.Schema.STRING,
_('Optional filename to associate with part.')
),
TYPE: properties.Schema(
properties.Schema.STRING,
_('Whether the part content is text or multipart.'),
default=TEXT,
constraints=[constraints.AllowedValues(TYPES)]
),
SUBTYPE: properties.Schema(
properties.Schema.STRING,
_('Optional subtype to specify with the type.')
),
}
)
)
}
message = None
def handle_create(self):
props = {
self.NAME: self.physical_resource_name(),
self.CONFIG: self.get_message(),
self.GROUP: 'Heat::Ungrouped'
}
sc = self.rpc_client().create_software_config(self.context, **props)
self.resource_id_set(sc[rpc_api.SOFTWARE_CONFIG_ID])
def get_message(self):
if self.message:
return self.message
subparts = []
for item in self.properties[self.PARTS]:
config = item.get(self.CONFIG)
part_type = item.get(self.TYPE, self.TEXT)
part = config
try:
sc = self.rpc_client().show_software_config(
self.context, config)
except Exception as ex:
self.rpc_client().ignore_error_named(ex, 'NotFound')
else:
part = sc[rpc_api.SOFTWARE_CONFIG_CONFIG]
if part_type == self.MULTIPART:
self._append_multiparts(subparts, part)
else:
filename = item.get(self.FILENAME, '')
subtype = item.get(self.SUBTYPE, '')
self._append_part(subparts, part, subtype, filename)
mime_blob = multipart.MIMEMultipart(_subparts=subparts)
self.message = mime_blob.as_string()
return self.message
@staticmethod
def _append_multiparts(subparts, multi_part):
multi_parts = email.message_from_string(multi_part)
if not multi_parts or not multi_parts.is_multipart():
return
for part in multi_parts.get_payload():
MultipartMime._append_part(
subparts,
part.get_payload(),
part.get_content_subtype(),
part.get_filename())
@staticmethod
def _append_part(subparts, part, subtype, filename):
if not subtype and filename:
subtype = os.path.splitext(filename)[0]
msg = MultipartMime._create_message(part, subtype, filename)
subparts.append(msg)
@staticmethod
def _create_message(part, subtype, filename):
msg = (text.MIMEText(part, _subtype=subtype)
if subtype else text.MIMEText(part))
if filename:
msg.add_header('Content-Disposition', 'attachment',
filename=filename)
return msg
def resource_mapping():
return {
'OS::Heat::MultipartMime': MultipartMime,
}