diff --git a/cloudbaseinit/plugins/windows/userdata-plugins/parthandler.py b/cloudbaseinit/plugins/windows/userdata-plugins/parthandler.py deleted file mode 100644 index fcf40195..00000000 --- a/cloudbaseinit/plugins/windows/userdata-plugins/parthandler.py +++ /dev/null @@ -1,66 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2013 Mirantis Inc. -# 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 imp -import os - -from cloudbaseinit.openstack.common import log as logging - -LOG = logging.getLogger("cloudbaseinit") - - -def get_plugin(parent_set): - return PartHandlerScriptHandler(parent_set) - - -def load_from_file(filepath, function): - class_inst = None - - mod_name, file_ext = os.path.splitext(os.path.split(filepath)[-1]) - - if file_ext.lower() == '.py': - py_mod = imp.load_source(mod_name, filepath) - - elif file_ext.lower() == '.pyc': - py_mod = imp.load_compiled(mod_name, filepath) - - if hasattr(py_mod, function): - return getattr(__import__(mod_name), function) - - -class PartHandlerScriptHandler: - def __init__(self, parent_set): - LOG.info("Part-handler script part handler is loaded.") - self.type = "text/part-handler" - self.name = "Part-handler userdata plugin" - self.parent_set = parent_set - - def process(self, part): - handler_path = (self.parent_set.path + "/part-handler/" + - part.get_filename()) - with open(handler_path, "wb") as f: - f.write(part.get_payload()) - - list_types = load_from_file(handler_path, "list_types") - handle_part = load_from_file(handler_path, "handle_part") - - if list_types is not None and handle_part is not None: - parts = list_types() - for part in parts: - LOG.info("Installing new custom handler for type: %s", part) - self.parent_set.custom_handlers[part] = handle_part - self.parent_set.has_custom_handlers = True diff --git a/cloudbaseinit/plugins/windows/userdata.py b/cloudbaseinit/plugins/windows/userdata.py index 9ce4e952..d1c749df 100644 --- a/cloudbaseinit/plugins/windows/userdata.py +++ b/cloudbaseinit/plugins/windows/userdata.py @@ -15,40 +15,18 @@ # under the License. import email -import os -import re -import tempfile -import uuid from cloudbaseinit.metadata.services import base as metadata_services_base from cloudbaseinit.openstack.common import log as logging -from cloudbaseinit.openstack.common import cfg -from cloudbaseinit.osutils import factory as osutils_factory from cloudbaseinit.plugins import base -from cloudbaseinit.plugins.windows import userdata_plugins - - -opts = [ - cfg.StrOpt('user_data_folder', default='cloud-data', - help='Specifies a folder to store multipart data files.'), -] - -CONF = cfg.CONF -CONF.register_opts(opts) +from cloudbaseinit.plugins.windows import userdatautils +from cloudbaseinit.plugins.windows.userdataplugins import factory LOG = logging.getLogger(__name__) class UserDataPlugin(base.BasePlugin): - def __init__(self): - self.msg = None - self.plugin_set = userdata_plugins.PluginSet(self._get_plugin_path()) - self.plugin_set.reload() - - def _get_plugin_path(self): - return os.path.join(os.path.dirname( - os.path.dirname(os.path.realpath(__file__))), - "windows/userdata-plugins") + _part_handler_content_type = "text/part-handler" def execute(self, service, shared_data): try: @@ -59,119 +37,83 @@ class UserDataPlugin(base.BasePlugin): if not user_data: return (base.PLUGIN_EXECUTION_DONE, False) - self._process_userdata(user_data) - return (base.PLUGIN_EXECUTION_DONE, False) - - def _process_userdata(self, user_data): - LOG.debug('User data content:\n%s' % user_data) - if user_data.startswith('Content-Type: multipart'): - for part in self._parse_mime(user_data): - self._process_part(part) - else: - handle(user_data) - - def _process_part(self, part): - part_handler = self._get_part_handler(part) - if part_handler is not None: - try: - self._begin_part_process_event(part) - LOG.info("Processing part %s filename: %s with handler: %s", - part.get_content_type(), - part.get_filename(), - part_handler.name) - part_handler.process(part) - self._end_part_process_event(part) - except Exception, e: - LOG.error('Exception during multipart part handling: ' - '%s %s \n %s', part.get_content_type(), - part.get_filename(), e) - - def _begin_part_process_event(self, part): - handler = self._get_custom_handler(part) - if handler is not None: - try: - handler("", "__begin__", part.get_filename(), - part.get_payload()) - except Exception, e: - LOG.error("Exception occurred during custom handle script " - "invocation (__begin__): %s ", e) - - def _end_part_process_event(self, part): - handler = self._get_custom_handler(part) - if handler is not None: - try: - handler("", "__end__", part.get_filename(), part.get_payload()) - except Exception, e: - LOG.error("Exception occurred during custom handle script " - "invocation (__end__): %s ", e) - - def _get_custom_handler(self, part): - if self.plugin_set.has_custom_handlers: - if part.get_content_type() in self.plugin_set.custom_handlers: - handler = self.plugin_set.custom_handlers[ - part.get_content_type()] - return handler - - def _get_part_handler(self, part): - if part.get_content_type() in self.plugin_set.set: - handler = self.plugin_set.set[part.get_content_type()] - return handler + return self._process_user_data(user_data) def _parse_mime(self, user_data): - self.msg = email.message_from_string(user_data) - return self.msg.walk() + return email.message_from_string(user_data).walk() + def _process_user_data(self, user_data): + plugin_status = base.PLUGIN_EXECUTION_DONE + reboot = False -def handle(user_data): - osutils = osutils_factory.OSUtilsFactory().get_os_utils() + LOG.debug('User data content:\n%s' % user_data) + if user_data.startswith('Content-Type: multipart'): + user_data_plugins_factory = factory.UserDataPluginsFactory() + user_data_plugins = user_data_plugins_factory.load_plugins() + user_handlers = {} - target_path = os.path.join(tempfile.gettempdir(), str(uuid.uuid4())) - if re.search(r'^rem cmd\s', user_data, re.I): - target_path += '.cmd' - args = [target_path] - shell = True - elif re.search(r'^#!', user_data, re.I): - target_path += '.sh' - args = ['bash.exe', target_path] - shell = False - elif re.search(r'^#ps1\s', user_data, re.I): - target_path += '.ps1' - args = ['powershell.exe', '-ExecutionPolicy', 'RemoteSigned', - '-NonInteractive', target_path] - shell = False - elif re.search(r'^#ps1_sysnative\s', user_data, re.I): - if os.path.isdir(os.path.expandvars('%windir%\\sysnative')): - target_path += '.ps1' - args = [os.path.expandvars('%windir%\\sysnative\\' - 'WindowsPowerShell\\v1.0\\' - 'powershell.exe'), - '-ExecutionPolicy', - 'RemoteSigned', '-NonInteractive', target_path] - shell = False + for part in self._parse_mime(user_data): + (plugin_status, reboot) = self._process_part(part, + user_data_plugins, + user_handlers) + if reboot: + break + + return (plugin_status, reboot) else: - # Unable to validate sysnative presence - LOG.warning('Unable to validate sysnative folder presence. ' - 'If Target OS is Server 2003, please ensure you ' - 'have KB942589 installed') - return (base.PLUGIN_EXECUTION_DONE, False) - else: - # Unsupported - LOG.warning('Unsupported user_data format') - return (base.PLUGIN_EXECUTION_DONE, False) + return self._process_non_multi_part(user_data) - try: - with open(target_path, 'wb') as f: - f.write(user_data) - (out, err, ret_val) = osutils.execute_process(args, shell) + def _process_part(self, part, user_data_plugins, user_handlers): + ret_val = None + content_type = part.get_content_type() + user_data_plugin = user_data_plugins.get(content_type) + if not user_data_plugin: + LOG.info("Userdata plugin not found for content type: %s" % + content_type) + else: + try: + if content_type == self._part_handler_content_type: + user_handlers.update(user_data_plugin.process(part)) + else: + handler_func = user_handlers.get(part.get_content_type()) + self._begin_part_process_event(part, handler_func) - LOG.info('User_data script ended with return code: %d' % ret_val) - LOG.debug('User_data stdout:\n%s' % out) - LOG.debug('User_data stderr:\n%s' % err) - except Exception, ex: - LOG.warning('An error occurred during user_data execution: \'%s\'' - % ex) - finally: - if os.path.exists(target_path): - os.remove(target_path) + LOG.info("Executing user data plugin: %s" % + user_data_plugin.__class__.__name__) - return (base.PLUGIN_EXECUTION_DONE, False) + ret_val = user_data_plugin.process(part) + + self._end_part_process_event(part, handler_func) + except Exception, ex: + LOG.error('Exception during multipart part handling: ' + '%(content_type)s, %(filename)s' % + {'content_type': part.get_content_type(), + 'filename': part.get_filename()}) + LOG.exception(ex) + + return self._get_plugin_return_value(ret_val) + + def _begin_part_process_event(self, part, handler_func): + if handler_func: + handler_func("", "__begin__", part.get_filename(), + part.get_payload()) + + def _end_part_process_event(self, part, handler_func): + if handler_func: + handler_func("", "__end__", part.get_filename(), + part.get_payload()) + + def _get_plugin_return_value(self, ret_val): + plugin_status = base.PLUGIN_EXECUTION_DONE + reboot = False + + if ret_val >= 1001 and ret_val <= 1003: + reboot = bool(ret_val & 1) + if ret_val & 2: + plugin_status = base.PLUGIN_EXECUTE_ON_NEXT_BOOT + + return (plugin_status, reboot) + + def _process_non_multi_part(self, user_data): + ret_val = userdatautils.execute_user_data_script(user_data) + return self._get_plugin_return_value(ret_val) diff --git a/cloudbaseinit/plugins/windows/userdata_plugins.py b/cloudbaseinit/plugins/windows/userdata_plugins.py deleted file mode 100644 index 0476f911..00000000 --- a/cloudbaseinit/plugins/windows/userdata_plugins.py +++ /dev/null @@ -1,81 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2013 Mirantis Inc. -# 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 glob -import imp -import os -import sys -import traceback - -from cloudbaseinit.openstack.common import log as logging - -LOG = logging.getLogger(__name__) - - -def load_from_file(filepath, parent_set): - class_inst = None - - mod_name, file_ext = os.path.splitext(os.path.split(filepath)[-1]) - - if file_ext.lower() == '.py': - py_mod = imp.load_source(mod_name, filepath) - - elif file_ext.lower() == '.pyc': - py_mod = imp.load_compiled(mod_name, filepath) - - if hasattr(py_mod, "get_plugin"): - clb = getattr(__import__(mod_name), "get_plugin") - class_inst = clb(parent_set) - - return class_inst - - -class PluginSet: - - def __init__(self, path): - self.path = path - sys.path.append(self.path) - self.set = {} - self.has_custom_handlers = False - self.custom_handlers = {} - - def get_plugin(self, content_type, file_name): - pass - - def load(self): - files = glob.glob(self.path + '/*.py') - - if len(files) == 0: - LOG.debug("No user data plug-ins found in %s:", self.path) - return - - for f in files: - LOG.debug("Trying to load user data plug-in from file: %s", f) - try: - plugin = load_from_file(f, self) - if plugin is not None: - LOG.info("Plugin '%s' loaded.", plugin.name) - self.set[plugin.type] = plugin - except: - exc_type, exc_value, exc_traceback = sys.exc_info() - LOG.error('Can`t load plugin from the file %s. Skip it.', f) - LOG.debug(repr(traceback.format_exception(exc_type, exc_value, - exc_traceback))) - - def reload(self): - self.set = {} - self.load() diff --git a/cloudbaseinit/plugins/windows/userdata-plugins/__init__.py b/cloudbaseinit/plugins/windows/userdataplugins/__init__.py similarity index 86% rename from cloudbaseinit/plugins/windows/userdata-plugins/__init__.py rename to cloudbaseinit/plugins/windows/userdataplugins/__init__.py index 7227b295..93daace9 100644 --- a/cloudbaseinit/plugins/windows/userdata-plugins/__init__.py +++ b/cloudbaseinit/plugins/windows/userdataplugins/__init__.py @@ -1,6 +1,4 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2013 Cloudbase Solutions Srl +# Copyright 2014 Cloudbase Solutions Srl # # 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 diff --git a/cloudbaseinit/plugins/windows/userdataplugins/base.py b/cloudbaseinit/plugins/windows/userdataplugins/base.py new file mode 100644 index 00000000..3368de1b --- /dev/null +++ b/cloudbaseinit/plugins/windows/userdataplugins/base.py @@ -0,0 +1,27 @@ +# Copyright 2014 Cloudbase Solutions Srl +# +# 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 abc + + +class BaseUserDataPlugin(object): + def __init__(self, mime_type): + self._mime_type = mime_type + + def get_mime_type(self): + return self._mime_type + + @abc.abstractmethod + def process(self, part): + pass diff --git a/cloudbaseinit/plugins/windows/userdata-plugins/cloudconfig.py b/cloudbaseinit/plugins/windows/userdataplugins/cloudconfig.py similarity index 63% rename from cloudbaseinit/plugins/windows/userdata-plugins/cloudconfig.py rename to cloudbaseinit/plugins/windows/userdataplugins/cloudconfig.py index b424581d..ba20f73d 100644 --- a/cloudbaseinit/plugins/windows/userdata-plugins/cloudconfig.py +++ b/cloudbaseinit/plugins/windows/userdataplugins/cloudconfig.py @@ -1,7 +1,5 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - # Copyright 2013 Mirantis Inc. -# All Rights Reserved. +# Copyright 2014 Cloudbase Solutions Srl # # 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 @@ -16,20 +14,14 @@ # under the License. from cloudbaseinit.openstack.common import log as logging +from cloudbaseinit.plugins.windows.userdataplugins import base -LOG = logging.getLogger("cloudbaseinit") +LOG = logging.getLogger(__name__) -def get_plugin(parent_set): - return CloudConfigHandler(parent_set) - - -class CloudConfigHandler: - - def __init__(self, parent_set): - LOG.info("Cloud-config part handler is loaded.") - self.type = "text/cloud-config" - self.name = "Cloud-config userdata plugin" +class CloudConfigPlugin(base.BaseUserDataPlugin): + def __init__(self): + super(CloudConfigPlugin, self).__init__("text/cloud-config") def process(self, part): - pass + LOG.info("text/cloud-config content is not currently supported") diff --git a/cloudbaseinit/plugins/windows/userdataplugins/factory.py b/cloudbaseinit/plugins/windows/userdataplugins/factory.py new file mode 100644 index 00000000..be96bb41 --- /dev/null +++ b/cloudbaseinit/plugins/windows/userdataplugins/factory.py @@ -0,0 +1,45 @@ +# Copyright 2014 Cloudbase Solutions Srl +# +# 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. + +from cloudbaseinit.openstack.common import cfg +from cloudbaseinit.utils import classloader + +opts = [ + cfg.ListOpt( + 'user_data_plugins', + default=[ + 'cloudbaseinit.plugins.windows.userdataplugins.parthandler.' + 'PartHandlerPlugin', + 'cloudbaseinit.plugins.windows.userdataplugins.heat.' + 'HeatPlugin', + 'cloudbaseinit.plugins.windows.userdataplugins.cloudconfig.' + 'CloudConfigPlugin', + 'cloudbaseinit.plugins.windows.userdataplugins.shellscript.' + 'ShellScriptPlugin', + ], + help='List of enabled userdata content plugins'), +] + +CONF = cfg.CONF +CONF.register_opts(opts) + + +class UserDataPluginsFactory(object): + def load_plugins(self): + plugins = {} + cl = classloader.ClassLoader() + for class_path in CONF.user_data_plugins: + plugin = cl.load_class(class_path)() + plugins[plugin.get_mime_type()] = plugin + return plugins diff --git a/cloudbaseinit/plugins/windows/userdata-plugins/heathandler.py b/cloudbaseinit/plugins/windows/userdataplugins/heat.py similarity index 51% rename from cloudbaseinit/plugins/windows/userdata-plugins/heathandler.py rename to cloudbaseinit/plugins/windows/userdataplugins/heat.py index 44b6dbac..fbc88fea 100644 --- a/cloudbaseinit/plugins/windows/userdata-plugins/heathandler.py +++ b/cloudbaseinit/plugins/windows/userdataplugins/heat.py @@ -1,7 +1,5 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - # Copyright 2013 Mirantis Inc. -# All Rights Reserved. +# Copyright 2014 Cloudbase Solutions Srl # # 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 @@ -16,24 +14,20 @@ # under the License. from cloudbaseinit.openstack.common import log as logging -from cloudbaseinit.plugins.windows import userdata +from cloudbaseinit.plugins.windows.userdataplugins import base +from cloudbaseinit.plugins.windows import userdatautils -LOG = logging.getLogger("cloudbaseinit") +LOG = logging.getLogger(__name__) -def get_plugin(parent_set): - return HeatUserDataHandler(parent_set) +class HeatPlugin(base.BaseUserDataPlugin): + _heat_user_data_filename = "cfn-userdata" - -class HeatUserDataHandler: - - def __init__(self, parent_set): - LOG.info("Heat user data part handler is loaded.") - self.type = "text/x-cfninitdata" - self.name = "Heat userdata plugin" + def __init__(self): + super(HeatPlugin, self).__init__("text/x-cfninitdata") def process(self, part): - #Only user-data part of Heat multipart data is supported. - #All other cfinitdata part will be skipped - if part.get_filename() == "cfn-userdata": - userdata.handle(part.get_payload()) + # Only user-data part of Heat multipart data is supported. + # All other cfinitdata part will be skipped + if part.get_filename() == self._heat_user_data_filename: + userdatautils.execute_user_data_script(part.get_payload()) diff --git a/cloudbaseinit/plugins/windows/userdataplugins/parthandler.py b/cloudbaseinit/plugins/windows/userdataplugins/parthandler.py new file mode 100644 index 00000000..e615c0f4 --- /dev/null +++ b/cloudbaseinit/plugins/windows/userdataplugins/parthandler.py @@ -0,0 +1,45 @@ +# Copyright 2013 Mirantis Inc. +# Copyright 2014 Cloudbase Solutions Srl +# +# 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 os +import tempfile + +from cloudbaseinit.openstack.common import log as logging +from cloudbaseinit.plugins.windows.userdataplugins import base +from cloudbaseinit.utils import classloader + +LOG = logging.getLogger(__name__) + + +class PartHandlerPlugin(base.BaseUserDataPlugin): + def __init__(self): + super(PartHandlerPlugin, self).__init__("text/part-handler") + + def process(self, part): + temp_dir = tempfile.gettempdir() + part_handler_path = os.path.join(temp_dir, part.get_filename()) + + with open(part_handler_path, "wb") as f: + f.write(part.get_payload()) + + part_handler = classloader.ClassLoader().load_module(part_handler_path) + + if (part_handler and + hasattr(part_handler, "list_types") and + hasattr(part_handler, "handle_part")): + part_handlers_dict = {} + for handled_type in part_handler.list_types(): + part_handlers_dict[handled_type] = part_handler.handle_part + return part_handlers_dict diff --git a/cloudbaseinit/plugins/windows/userdata-plugins/shellscript.py b/cloudbaseinit/plugins/windows/userdataplugins/shellscript.py similarity index 80% rename from cloudbaseinit/plugins/windows/userdata-plugins/shellscript.py rename to cloudbaseinit/plugins/windows/userdataplugins/shellscript.py index cc32cb07..3074410c 100644 --- a/cloudbaseinit/plugins/windows/userdata-plugins/shellscript.py +++ b/cloudbaseinit/plugins/windows/userdataplugins/shellscript.py @@ -1,7 +1,5 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - # Copyright 2013 Mirantis Inc. -# All Rights Reserved. +# Copyright 2014 Cloudbase Solutions Srl # # 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 @@ -15,24 +13,19 @@ # License for the specific language governing permissions and limitations # under the License. -import os import tempfile +import os from cloudbaseinit.openstack.common import log as logging from cloudbaseinit.osutils import factory as osutils_factory +from cloudbaseinit.plugins.windows.userdataplugins import base -LOG = logging.getLogger("cloudbaseinit") +LOG = logging.getLogger(__name__) -def get_plugin(parent_set): - return ShellScriptHandler(parent_set) - - -class ShellScriptHandler: - def __init__(self, parent_set): - LOG.info("Shell-script part handler is loaded.") - self.type = "text/x-shellscript" - self.name = "Shell-script userdata plugin" +class ShellScriptPlugin(base.BaseUserDataPlugin): + def __init__(self): + super(ShellScriptPlugin, self).__init__("text/x-shellscript") def process(self, part): osutils = osutils_factory.OSUtilsFactory().get_os_utils() @@ -46,14 +39,17 @@ class ShellScriptHandler: elif file_name.endswith(".sh"): args = ['bash.exe', target_path] shell = False + elif file_name.endswith(".py"): + args = ['python.exe', target_path] + shell = False elif file_name.endswith(".ps1"): args = ['powershell.exe', '-ExecutionPolicy', 'RemoteSigned', '-NonInteractive', target_path] shell = False else: # Unsupported - LOG.warning('Unsupported shell format') - return False + LOG.warning('Unsupported script type') + return 0 try: with open(target_path, 'wb') as f: @@ -63,11 +59,11 @@ class ShellScriptHandler: LOG.info('User_data script ended with return code: %d' % ret_val) LOG.debug('User_data stdout:\n%s' % out) LOG.debug('User_data stderr:\n%s' % err) + + return ret_val except Exception, ex: LOG.warning('An error occurred during user_data execution: \'%s\'' % ex) finally: if os.path.exists(target_path): os.remove(target_path) - - return False diff --git a/cloudbaseinit/plugins/windows/userdatautils.py b/cloudbaseinit/plugins/windows/userdatautils.py new file mode 100644 index 00000000..f14313cc --- /dev/null +++ b/cloudbaseinit/plugins/windows/userdatautils.py @@ -0,0 +1,82 @@ +# Copyright 2014 Cloudbase Solutions Srl +# +# 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 os +import re +import tempfile +import uuid + +from cloudbaseinit.openstack.common import log as logging +from cloudbaseinit.osutils import factory as osutils_factory + +LOG = logging.getLogger(__name__) + + +def execute_user_data_script(user_data): + osutils = osutils_factory.OSUtilsFactory().get_os_utils() + + target_path = os.path.join(tempfile.gettempdir(), str(uuid.uuid4())) + if re.search(r'^rem cmd\s', user_data, re.I): + target_path += '.cmd' + args = [target_path] + shell = True + elif re.search(r'^#!/usr/bin/env\spython\s', user_data, re.I): + target_path += '.py' + args = ['python.exe', target_path] + shell = False + elif re.search(r'^#!', user_data, re.I): + target_path += '.sh' + args = ['bash.exe', target_path] + shell = False + elif re.search(r'^#ps1\s', user_data, re.I): + target_path += '.ps1' + args = ['powershell.exe', '-ExecutionPolicy', 'RemoteSigned', + '-NonInteractive', target_path] + shell = False + elif re.search(r'^#ps1_sysnative\s', user_data, re.I): + if os.path.isdir(os.path.expandvars('%windir%\\sysnative')): + target_path += '.ps1' + args = [os.path.expandvars('%windir%\\sysnative\\' + 'WindowsPowerShell\\v1.0\\' + 'powershell.exe'), + '-ExecutionPolicy', + 'RemoteSigned', '-NonInteractive', target_path] + shell = False + else: + # Unable to validate sysnative presence + LOG.warning('Unable to validate sysnative folder presence. ' + 'If Target OS is Server 2003, please ensure you ' + 'have KB942589 installed') + return 0 + else: + # Unsupported + LOG.warning('Unsupported user_data format') + return 0 + + try: + with open(target_path, 'wb') as f: + f.write(user_data) + (out, err, ret_val) = osutils.execute_process(args, shell) + + LOG.info('User_data script ended with return code: %d' % ret_val) + LOG.debug('User_data stdout:\n%s' % out) + LOG.debug('User_data stderr:\n%s' % err) + + return ret_val + except Exception, ex: + LOG.warning('An error occurred during user_data execution: \'%s\'' + % ex) + finally: + if os.path.exists(target_path): + os.remove(target_path) diff --git a/cloudbaseinit/test/test.mime b/cloudbaseinit/test/test.mime deleted file mode 100644 index b21713d6..00000000 --- a/cloudbaseinit/test/test.mime +++ /dev/null @@ -1,173 +0,0 @@ -Content-Type: multipart/mixed; boundary="===============1598784645116016685==" -MIME-Version: 1.0 - ---===============1598784645116016685== -Content-Type: text/cloud-config; charset="us-ascii" -MIME-Version: 1.0 -Content-Transfer-Encoding: 7bit -Content-Disposition: attachment; filename="cloud-config" - -runcmd: - - setenforce 0 > /dev/null 2>&1 || true - -user: ec2-user - -cloud_config_modules: - - locale - - set_hostname - - ssh - - timezone - - update_etc_hosts - - update_hostname - - runcmd - -# Capture all subprocess output into a logfile -# Useful for troubleshooting cloud-init issues -output: {all: '| tee -a /var/log/cloud-init-output.log'} - ---===============1598784645116016685== -Content-Type: text/part-handler; charset="us-ascii" -MIME-Version: 1.0 -Content-Transfer-Encoding: 7bit -Content-Disposition: attachment; filename="part-handler.py" - -#part-handler - -import os -import datetime - - -def list_types(): - return(["text/x-cfninitdata"]) - - -def handle_part(data, ctype, filename, payload): - if ctype == "__begin__": - try: - os.makedirs('/var/lib/heat-cfntools', 0700) - except OSError as e: - if e.errno != errno.EEXIST: - raise - return - - if ctype == "__end__": - return - - with open('/var/log/part-handler.log', 'a') as log: - timestamp = datetime.datetime.now() - log.write('%s filename:%s, ctype:%s\n' % (timestamp, filename, ctype)) - - if ctype == 'text/x-cfninitdata': - with open('/var/lib/heat-cfntools/%s' % filename, 'w') as f: - f.write(payload) - - # TODO(sdake) hopefully temporary until users move to heat-cfntools-1.3 - with open('/var/lib/cloud/data/%s' % filename, 'w') as f: - f.write(payload) - ---===============1598784645116016685== -Content-Type: text/x-cfninitdata; charset="us-ascii" -MIME-Version: 1.0 -Content-Transfer-Encoding: 7bit -Content-Disposition: attachment; filename="cfn-userdata" - -#ps1 -Get-Date - ---===============1598784645116016685== -Content-Type: text/x-shellscript; charset="us-ascii" -MIME-Version: 1.0 -Content-Transfer-Encoding: 7bit -Content-Disposition: attachment; filename="loguserdata.py" - -#!/usr/bin/env python - -import sys -import os -import subprocess -import datetime -import pkg_resources -from distutils.version import LooseVersion -import errno - -path = '/var/lib/heat-cfntools' - - -def chk_ci_version(): - v = LooseVersion(pkg_resources.get_distribution('cloud-init').version) - return v >= LooseVersion('0.6.0') - - -def create_log(path): - fd = os.open(path, os.O_WRONLY | os.O_CREAT, 0600) - return os.fdopen(fd, 'w') - - -def call(args, log): - log.write('%s\n' % ' '.join(args)) - log.flush() - p = subprocess.Popen(args, stdout=log, stderr=log) - p.wait() - return p.returncode - - -def main(log): - - if not chk_ci_version(): - # pre 0.6.0 - user data executed via cloudinit, not this helper - log.write('Unable to log provisioning, need a newer version of' - ' cloud-init\n') - return -1 - - userdata_path = os.path.join(path, 'cfn-userdata') - os.chmod(userdata_path, 0700) - - log.write('Provision began: %s\n' % datetime.datetime.now()) - log.flush() - returncode = call([userdata_path], log) - log.write('Provision done: %s\n' % datetime.datetime.now()) - if returncode: - return returncode - - -if __name__ == '__main__': - with create_log('/var/log/heat-provision.log') as log: - returncode = main(log) - if returncode: - log.write('Provision failed') - sys.exit(returncode) - - userdata_path = os.path.join(path, 'provision-finished') - with create_log(userdata_path) as log: - log.write('%s\n' % datetime.datetime.now()) - ---===============1598784645116016685== -Content-Type: text/x-cfninitdata; charset="us-ascii" -MIME-Version: 1.0 -Content-Transfer-Encoding: 7bit -Content-Disposition: attachment; filename="cfn-watch-server" - -http://67.207.197.36:8003 ---===============1598784645116016685== -Content-Type: text/x-cfninitdata; charset="us-ascii" -MIME-Version: 1.0 -Content-Transfer-Encoding: 7bit -Content-Disposition: attachment; filename="cfn-metadata-server" - -http://67.207.197.36:8000 ---===============1598784645116016685== -Content-Type: text/x-cfninitdata; charset="us-ascii" -MIME-Version: 1.0 -Content-Transfer-Encoding: 7bit -Content-Disposition: attachment; filename="cfn-boto-cfg" - -[Boto] -debug = 0 -is_secure = 0 -https_validate_certificates = 1 -cfn_region_name = heat -cfn_region_endpoint = 67.207.197.36 -cloudwatch_region_name = heat -cloudwatch_region_endpoint = 67.207.197.36 ---===============1598784645116016685==-- - diff --git a/cloudbaseinit/utils/classloader.py b/cloudbaseinit/utils/classloader.py index b7d97736..e7ce0e41 100644 --- a/cloudbaseinit/utils/classloader.py +++ b/cloudbaseinit/utils/classloader.py @@ -14,6 +14,9 @@ # License for the specific language governing permissions and limitations # under the License. +import imp +import os + from cloudbaseinit.openstack.common import log as logging LOG = logging.getLogger(__name__) @@ -25,3 +28,13 @@ class ClassLoader(object): parts = class_path.rsplit('.', 1) module = __import__(parts[0], fromlist=parts[1]) return getattr(module, parts[1]) + + def load_module(self, path): + module_name, file_ext = os.path.splitext(os.path.split(path)[-1]) + + if file_ext.lower() == '.py': + module = imp.load_source(module_name, path) + elif file_ext.lower() == '.pyc': + module = imp.load_compiled(module_name, path) + + return module