From 2019dec757321ae3dae3e32ac63f352df5a534a4 Mon Sep 17 00:00:00 2001 From: Angus Salkeld Date: Mon, 23 Apr 2012 13:08:32 +1000 Subject: [PATCH] Move common cfn code into cfn_helper.py Signed-off-by: Angus Salkeld --- bin/heat | 2 +- heat/cfntools/__init__.py | 0 heat/cfntools/cfn-init | 542 +----------------------- heat/cfntools/cfn_helper.py | 546 +++++++++++++++++++++++++ heat/jeos/F16-i386-cfntools-jeos.tdl | 1 + heat/jeos/F16-x86_64-cfntools-jeos.tdl | 1 + heat/jeos/F17-i386-cfntools-jeos.tdl | 1 + heat/jeos/F17-x86_64-cfntools-jeos.tdl | 1 + 8 files changed, 561 insertions(+), 533 deletions(-) create mode 100644 heat/cfntools/__init__.py create mode 100644 heat/cfntools/cfn_helper.py diff --git a/bin/heat b/bin/heat index dc79e5879..2b5e01b25 100755 --- a/bin/heat +++ b/bin/heat @@ -370,7 +370,7 @@ def jeos_create(options, arguments): # and injecting them into the TDL at the appropriate place if instance_type == 'cfntools': tdl_xml = libxml2.parseFile(tdl_path) - for cfnname in ['cfn-init', 'cfn-hup', 'cfn-signal']: + for cfnname in ['cfn-init', 'cfn-hup', 'cfn-signal', 'cfn_helper.py']: f = open('%s/%s' % (cfntools_path, cfnname), 'r') cfscript_e64 = base64.b64encode(f.read()) f.close() diff --git a/heat/cfntools/__init__.py b/heat/cfntools/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/heat/cfntools/cfn-init b/heat/cfntools/cfn-init index 4ea48f7ff..224d41347 100755 --- a/heat/cfntools/cfn-init +++ b/heat/cfntools/cfn-init @@ -31,543 +31,19 @@ Not implemented yet: """ import argparse -import json import logging import os -import rpmUtils.updates as rpmupdates -import rpmUtils.miscutils as rpmutils -import subprocess import sys -log_file_name = "/var/log/cfn-init.log" +if os.path.exists('/opt/aws/bin'): + sys.path.insert(0, '/opt/aws/bin') + from cfn_helper import * +else: + from heat.cfntools.cfn_helper import * + log_format = '%(levelname)s [%(asctime)s] %(message)s' -# setup stdout logging logging.basicConfig(format=log_format, level=logging.INFO) -# setup file logging -file_handler = logging.FileHandler(log_file_name) -file_handler.setFormatter(logging.Formatter(log_format)) -logging.getLogger().addHandler(file_handler) - -class CommandRunner(object): - """ - Helper class to run a command and store the output. - """ - - def __init__(self, command): - self._command = command - self._stdout = None - self._stderr = None - self._status = None - - - def __str__(self): - s = "CommandRunner:" - s += "\n\tcommand: %s" % self._command - if self._status: - s += "\n\tstatus: %s" % self._status - if self._stdout: - s += "\n\tstdout: %s" % self._stdout - if self._stderr: - s += "\n\tstderr: %s" % self._stderr - return s - - - def run(self): - """ - Run the Command and return the output. - - Returns: - self - """ - logging.debug("Running command: %s" % self._command) - cmd = self._command.split() - subproc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - output = subproc.communicate() - - self._status = subproc.returncode - self._stdout = output[0] - self._stderr = output[1] - - return self - - @property - def stdout(self): - return self._stdout - - @property - def stderr(self): - return self._stderr - - @property - def status(self): - return self._status - - -class RpmHelper(object): - - _rpm_util = rpmupdates.Updates([], []) - - @classmethod - def prepcache(cls): - """ - Prepare the yum cache - """ - CommandRunner("yum -y makecache").run() - - - @classmethod - def compare_rpm_versions(cls, v1, v2): - """ - Compare two RPM version strings. - - Arguments: - v1 -- a version string - v2 -- a version string - - Returns: - 0 -- the versions are equal - 1 -- v1 is greater - -1 -- v2 is greater - """ - if v1 and v2: - return rpmutils.compareVerOnly(v1, v2) - elif v1: - return 1 - elif v2: - return -1 - else: - return 0 - - - @classmethod - def newest_rpm_version(cls, versions): - """ - Returns the highest (newest) version from a list of versions. - - Arguments: - versions -- A list of version strings - e.g., ['2.0', '2.2', '2.2-1.fc16', '2.2.22-1.fc16'] - """ - if versions: - if isinstance(versions, basestring): - return versions - versions = sorted(versions, rpmutils.compareVerOnly, - reverse=True) - return versions[0] - else: - return None - - - @classmethod - def rpm_package_version(cls, pkg): - """ - Returns the version of an installed RPM. - - Arguments: - pkg -- A package name - """ - cmd = "rpm -q --queryformat '%{VERSION}-%{RELEASE}' %s" % pkg - command = CommandRunner(cmd).run() - return command.stdout - - - @classmethod - def rpm_package_installed(cls, pkg): - """ - Indicates whether pkg is in rpm database. - - Arguments: - pkg -- A package name (with optional version and release spec). - e.g., httpd - e.g., httpd-2.2.22 - e.g., httpd-2.2.22-1.fc16 - """ - command = CommandRunner("rpm -q %s" % pkg).run() - return command.status == 0 - - - @classmethod - def yum_package_available(cls, pkg): - """ - Indicates whether pkg is available via yum - - Arguments: - pkg -- A package name (with optional version and release spec). - e.g., httpd - e.g., httpd-2.2.22 - e.g., httpd-2.2.22-1.fc16 - """ - command = CommandRunner("yum -C -y --showduplicates list available %s" % pkg).run() - return command.status == 0 - - - @classmethod - def install(cls, packages, rpms=True): - """ - Installs (or upgrades) a set of packages via RPM or via Yum. - - Arguments: - packages -- a list of packages to install - rpms -- if True: - * use RPM to install the packages - * packages must be a list of URLs to retrieve RPMs - if False: - * use Yum to install packages - * packages is a list of: - - pkg name (httpd), or - - pkg name with version spec (httpd-2.2.22), or - - pkg name with version-release spec (httpd-2.2.22-1.fc16) - """ - if rpms: - cmd = "rpm -U --force --nosignature " - cmd += " ".join(packages) - logging.info("Installing packages: %s" % cmd) - else: - cmd = "yum -y install " - cmd += " ".join(packages) - logging.info("Installing packages: %s" % cmd) - command = CommandRunner(cmd).run() - if command.status: - logging.warn("Failed to install packages: %s" % cmd) - - - @classmethod - def downgrade(cls, packages, rpms=True): - """ - Downgrades a set of packages via RPM or via Yum. - - Arguments: - packages -- a list of packages to downgrade - rpms -- if True: - * use RPM to downgrade (replace) the packages - * packages must be a list of URLs to retrieve the RPMs - if False: - * use Yum to downgrade packages - * packages is a list of: - - pkg name with version spec (httpd-2.2.22), or - - pkg name with version-release spec (httpd-2.2.22-1.fc16) - """ - if rpms: - cls.install(packages) - else: - cmd = "yum -y downgrade " - cmd += " ".join(packages) - logging.info("Downgrading packages: %s" % cmd) - command = Command(cmd).run() - if command.status: - logging.warn("Failed to downgrade packages: %s" % cmd) - - -class PackagesHandler(object): - _packages = {} - - _package_order = ["dpkg", "rpm", "apt", "yum"] - - @staticmethod - def _pkgsort(pkg1, pkg2): - order = PackagesHandler._package_order - p1_name = pkg1[0] - p2_name = pkg2[0] - if p1_name in order and p2_name in order: - return cmp(order.index(p1_name), order.index(p2_name)) - elif p1_name in order: - return -1 - elif p2_name in order: - return 1 - else: - return cmp(p1_name.lower(), p2_name.lower()) - - - def __init__(self, packages): - self._packages = packages - - - def _handle_gem_packages(self, packages): - #FIXME: handle rubygems - pass - - - def _handle_python_packages(self, packages): - #FIXME: handle python easyinstall - pass - - - def _handle_yum_packages(self, packages): - """ - Handle installation, upgrade, or downgrade of a set of packages via yum. - - Arguments: - packages -- a package entries map of the form: - "pkg_name" : "version", - "pkg_name" : ["v1", "v2"], - "pkg_name" : [] - - For each package entry: - * if no version is supplied and the package is already installed, do - nothing - * if no version is supplied and the package is _not_ already - installed, install it - * if a version string is supplied, and the package is already - installed, determine whether to downgrade or upgrade (or do nothing - if version matches installed package) - * if a version array is supplied, choose the highest version from the - array and follow same logic for version string above - """ - # collect pkgs for batch processing at end - installs = [] - downgrades = [] - # update yum cache - RpmHelper.prepcache() - for pkg_name, versions in packages.iteritems(): - ver = RpmHelper.newest_rpm_version(versions) - pkg = "%s-%s" % (pkg_name, ver) if ver else pkg_name - if RpmHelper.rpm_package_installed(pkg): - pass # FIXME:print non-error, but skipping pkg - elif not RpmHelper.yum_package_available(pkg): - logging.warn("Skipping package '%s'. Not available via yum" % pkg) - elif not ver: - installs.append(pkg) - else: - current_ver = RpmHelper.rpm_package_version(pkg) - rc = RpmHelper.compare_rpm_versions(current_ver, ver) - if rc < 0: - installs.append(pkg) - elif rc > 0: - downgrades.append(pkg) - if installs: - RpmHelper.install(installs, rpms=False) - if downgrades: - RpmHelper.downgrade(downgrades) - - - def _handle_rpm_packages(sef, packages): - """ - Handle installation, upgrade, or downgrade of a set of packages via rpm. - - Arguments: - packages -- a package entries map of the form: - "pkg_name" : "url" - - For each package entry: - * if the EXACT package is already installed, skip it - * if a different version of the package is installed, overwrite it - * if the package isn't installed, install it - """ - #FIXME: handle rpm installs - pass - - - def _handle_apt_packages(self, packages): - #FIXME: handle apt-get - pass - - - # map of function pionters to handle different package managers - _package_handlers = { - "yum" : _handle_yum_packages, - "rpm" : _handle_rpm_packages, - "apt" : _handle_apt_packages, - "rubygems" : _handle_gem_packages, - "python" : _handle_python_packages - } - - def _package_handler(self, manager_name): - handler = None - if manager_name in self._package_handlers: - handler = self._package_handlers[manager_name] - return handler - - - def apply_packages(self): - """ - Install, upgrade, or downgrade packages listed - Each package is a dict containing package name and a list of versions - Install order: - * dpkg - * rpm - * apt - * yum - """ - packages = sorted(self._packages.iteritems(), PackagesHandler._pkgsort) - - for manager, package_entries in packages: - handler = self._package_handler(manager) - if not handler: - logging.warn("Skipping invalid package type: %s" % manager) - else: - handler(self, package_entries) - - -class ServicesHandler(object): - _services = {} - - - def __init__(self, services): - self._services = services - - - def _handle_sysv_command(self, service, command): - service_exe = "/sbin/service" - enable_exe = "/sbin/chkconfig" - cmd = "" - if "enable" == command: - cmd = "%s %s on" % (enable_exe, service) - elif "disable" == command: - cmd = "%s %s off" % (enable_exe, service) - elif "start" == command: - cmd = "%s %s start" % (service_exe, service) - elif "stop" == command: - cmd = "%s %s stop" % (service_exe, service) - elif "status" == command: - cmd = "%s %s status" % (service_exe, service) - command = CommandRunner(cmd) - command.run() - return command - - - def _handle_systemd_command(self, service, command): - exe = "/bin/systemctl" - cmd = "" - service = '%s.service' % service - if "enable" == command: - cmd = "%s enable %s" % (exe, service) - elif "disable" == command: - cmd = "%s disable %s" % (exe, service) - elif "start" == command: - cmd = "%s start %s" % (exe, service) - elif "stop" == command: - cmd = "%s stop %s" % (exe, service) - elif "status" == command: - cmd = "%s status %s" % (exe, service) - command = CommandRunner(cmd) - command.run() - return command - - - def _handle_service(self, handler, service, properties): - if "enabled" in properties: - enable = to_boolean(properties["enabled"]) - if enable: - logging.info("Enabling service %s" % service) - handler(self, service, "enable") - else: - logging.info("Disabling service %s" % service) - handler(self, service, "disable") - - if "ensureRunning" in properties: - ensure_running = to_boolean(properties["ensureRunning"]) - command = handler(self, service, "status") - running = command.status == 0 - if ensure_running and not running: - logging.info("Starting service %s" % service) - handler(self, service, "start") - elif not ensure_running and running: - logging.info("Stopping service %s" % service) - handler(self, service, "stop") - - - def _handle_services(self, handler, services): - for service, properties in services.iteritems(): - self._handle_service(handler, service, properties) - - - # map of function pointers to various service handlers - _service_handlers = { - "sysvinit" : _handle_sysv_command, - "systemd" : _handle_systemd_command - } - - - def _service_handler(self, manager_name): - handler = None - if manager_name in self._service_handlers: - handler = self._service_handlers[manager_name] - return handler - - - def apply_services(self): - """ - Starts, stops, enables, disables services - """ - for manager, service_entries in self._services.iteritems(): - handler = self._service_handler(manager) - if not handler: - logging.warn("Skipping invalid service type: %s" % manager) - else: - self._handle_services(handler, service_entries) - - -class Metadata(object): - _metadata = None - _init_key = "AWS::CloudFormation::Init" - - def __init__(self, metadata): - self._metadata = json.loads(metadata) - - - def _is_valid_metadata(self): - """ - Should find the AWS::CloudFormation::Init json key - """ - is_valid = self._metadata and self._init_key in self._metadata and self._metadata[self._init_key] - if is_valid: - self._metadata = self._metadata[self._init_key] - return is_valid - - - def _process_config(self): - """ - Parse and process a config section - * packages - * sources (not yet) - * users (not yet) - * groups (not yet) - * files (not yet) - * commands (not yet) - * services - """ - - self._config = self._metadata["config"] - PackagesHandler(self._config.get("packages")).apply_packages() - #FIXME: handle sources - #FIXME: handle users - #FIXME: handle groups - #FIXME: handle files - #FIXME: handle commands - ServicesHandler(self._config.get("services")).apply_services() - - - def process(self): - """ - Process the resource metadata - """ - # FIXME: when config sets are implemented, this should select the correct - # config set from the metadata, and send each config in the config set to - # process_config - if not self._is_valid_metadata(): - raise Exception("invalid metadata") - else: - self._process_config() - - -def to_boolean(b): - val = b.lower().strip() if isinstance(b, basestring) else b - return b in [True, 'true', 'yes', '1', 1] - - -def get_metadata(fname): - """ - Read the metadata from the given filename and return the string - """ - f = open(fname) - meta = f.read() - f.close() - return meta - - -## Main -metadata_file = "/var/lib/cloud/data/cfn-init-data" description = " " parser = argparse.ArgumentParser(description=description) @@ -594,9 +70,11 @@ parser.add_argument('--region', args = parser.parse_args() # FIXME: implement real arg -metadata = Metadata(get_metadata(metadata_file)) +metadata = Metadata(stack, resource, access_key=access_key, + secret_key=secret_key, region=region) +metadata.retrieve() try: - metadata.process() + metadata.cfn_init() except Exception as e: logging.exception("Error processing metadata") exit(1) diff --git a/heat/cfntools/cfn_helper.py b/heat/cfntools/cfn_helper.py new file mode 100644 index 000000000..a6eec665d --- /dev/null +++ b/heat/cfntools/cfn_helper.py @@ -0,0 +1,546 @@ +# +# 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. + +""" +Implements cfn metadata handling + +Resource metadata currently implemented: + * config/packages + * config/services + +Not implemented yet: + * config sets + * config/sources + * config/commands + * config/files + * config/users + * config/groups + * command line args + - placeholders are ignored +""" + +import json +import logging +import os +import rpmUtils.updates as rpmupdates +import rpmUtils.miscutils as rpmutils +import subprocess +import sys + + +def to_boolean(b): + val = b.lower().strip() if isinstance(b, basestring) else b + return b in [True, 'true', 'yes', '1', 1] + + +class CommandRunner(object): + """ + Helper class to run a command and store the output. + """ + + def __init__(self, command): + self._command = command + self._stdout = None + self._stderr = None + self._status = None + + def __str__(self): + s = "CommandRunner:" + s += "\n\tcommand: %s" % self._command + if self._status: + s += "\n\tstatus: %s" % self._status + if self._stdout: + s += "\n\tstdout: %s" % self._stdout + if self._stderr: + s += "\n\tstderr: %s" % self._stderr + return s + + def run(self): + """ + Run the Command and return the output. + + Returns: + self + """ + logging.debug("Running command: %s" % self._command) + cmd = self._command.split() + subproc = subprocess.Popen(cmd, stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + output = subproc.communicate() + + self._status = subproc.returncode + self._stdout = output[0] + self._stderr = output[1] + + return self + + @property + def stdout(self): + return self._stdout + + @property + def stderr(self): + return self._stderr + + @property + def status(self): + return self._status + + +class RpmHelper(object): + + _rpm_util = rpmupdates.Updates([], []) + + @classmethod + def prepcache(cls): + """ + Prepare the yum cache + """ + CommandRunner("yum -y makecache").run() + + @classmethod + def compare_rpm_versions(cls, v1, v2): + """ + Compare two RPM version strings. + + Arguments: + v1 -- a version string + v2 -- a version string + + Returns: + 0 -- the versions are equal + 1 -- v1 is greater + -1 -- v2 is greater + """ + if v1 and v2: + return rpmutils.compareVerOnly(v1, v2) + elif v1: + return 1 + elif v2: + return -1 + else: + return 0 + + @classmethod + def newest_rpm_version(cls, versions): + """ + Returns the highest (newest) version from a list of versions. + + Arguments: + versions -- A list of version strings + e.g., ['2.0', '2.2', '2.2-1.fc16', '2.2.22-1.fc16'] + """ + if versions: + if isinstance(versions, basestring): + return versions + versions = sorted(versions, rpmutils.compareVerOnly, + reverse=True) + return versions[0] + else: + return None + + @classmethod + def rpm_package_version(cls, pkg): + """ + Returns the version of an installed RPM. + + Arguments: + pkg -- A package name + """ + cmd = "rpm -q --queryformat '%{VERSION}-%{RELEASE}' %s" % pkg + command = CommandRunner(cmd).run() + return command.stdout + + @classmethod + def rpm_package_installed(cls, pkg): + """ + Indicates whether pkg is in rpm database. + + Arguments: + pkg -- A package name (with optional version and release spec). + e.g., httpd + e.g., httpd-2.2.22 + e.g., httpd-2.2.22-1.fc16 + """ + command = CommandRunner("rpm -q %s" % pkg).run() + return command.status == 0 + + @classmethod + def yum_package_available(cls, pkg): + """ + Indicates whether pkg is available via yum + + Arguments: + pkg -- A package name (with optional version and release spec). + e.g., httpd + e.g., httpd-2.2.22 + e.g., httpd-2.2.22-1.fc16 + """ + cmd_str = "yum -C -y --showduplicates list available %s" % pkg + command = CommandRunner(cmd_str).run() + return command.status == 0 + + @classmethod + def install(cls, packages, rpms=True): + """ + Installs (or upgrades) a set of packages via RPM or via Yum. + + Arguments: + packages -- a list of packages to install + rpms -- if True: + * use RPM to install the packages + * packages must be a list of URLs to retrieve RPMs + if False: + * use Yum to install packages + * packages is a list of: + - pkg name (httpd), or + - pkg name with version spec (httpd-2.2.22), or + - pkg name with version-release spec (httpd-2.2.22-1.fc16) + """ + if rpms: + cmd = "rpm -U --force --nosignature " + cmd += " ".join(packages) + logging.info("Installing packages: %s" % cmd) + else: + cmd = "yum -y install " + cmd += " ".join(packages) + logging.info("Installing packages: %s" % cmd) + command = CommandRunner(cmd).run() + if command.status: + logging.warn("Failed to install packages: %s" % cmd) + + @classmethod + def downgrade(cls, packages, rpms=True): + """ + Downgrades a set of packages via RPM or via Yum. + + Arguments: + packages -- a list of packages to downgrade + rpms -- if True: + * use RPM to downgrade (replace) the packages + * packages must be a list of URLs to retrieve the RPMs + if False: + * use Yum to downgrade packages + * packages is a list of: + - pkg name with version spec (httpd-2.2.22), or + - pkg name with version-release spec (httpd-2.2.22-1.fc16) + """ + if rpms: + cls.install(packages) + else: + cmd = "yum -y downgrade " + cmd += " ".join(packages) + logging.info("Downgrading packages: %s" % cmd) + command = Command(cmd).run() + if command.status: + logging.warn("Failed to downgrade packages: %s" % cmd) + + +class PackagesHandler(object): + _packages = {} + + _package_order = ["dpkg", "rpm", "apt", "yum"] + + @staticmethod + def _pkgsort(pkg1, pkg2): + order = PackagesHandler._package_order + p1_name = pkg1[0] + p2_name = pkg2[0] + if p1_name in order and p2_name in order: + return cmp(order.index(p1_name), order.index(p2_name)) + elif p1_name in order: + return -1 + elif p2_name in order: + return 1 + else: + return cmp(p1_name.lower(), p2_name.lower()) + + def __init__(self, packages): + self._packages = packages + + def _handle_gem_packages(self, packages): + #FIXME: handle rubygems + pass + + def _handle_python_packages(self, packages): + #FIXME: handle python easyinstall + pass + + def _handle_yum_packages(self, packages): + """ + Handle installation, upgrade, or downgrade of a set of packages via yum. + + Arguments: + packages -- a package entries map of the form: + "pkg_name" : "version", + "pkg_name" : ["v1", "v2"], + "pkg_name" : [] + + For each package entry: + * if no version is supplied and the package is already installed, do + nothing + * if no version is supplied and the package is _not_ already + installed, install it + * if a version string is supplied, and the package is already + installed, determine whether to downgrade or upgrade (or do nothing + if version matches installed package) + * if a version array is supplied, choose the highest version from the + array and follow same logic for version string above + """ + # collect pkgs for batch processing at end + installs = [] + downgrades = [] + # update yum cache + RpmHelper.prepcache() + for pkg_name, versions in packages.iteritems(): + ver = RpmHelper.newest_rpm_version(versions) + pkg = "%s-%s" % (pkg_name, ver) if ver else pkg_name + if RpmHelper.rpm_package_installed(pkg): + # FIXME:print non-error, but skipping pkg + pass + elif not RpmHelper.yum_package_available(pkg): + logging.warn("Skipping package '%s'. Not available via yum" % pkg) + elif not ver: + installs.append(pkg) + else: + current_ver = RpmHelper.rpm_package_version(pkg) + rc = RpmHelper.compare_rpm_versions(current_ver, ver) + if rc < 0: + installs.append(pkg) + elif rc > 0: + downgrades.append(pkg) + if installs: + RpmHelper.install(installs, rpms=False) + if downgrades: + RpmHelper.downgrade(downgrades) + + def _handle_rpm_packages(sef, packages): + """ + Handle installation, upgrade, or downgrade of a set of packages via rpm. + + Arguments: + packages -- a package entries map of the form: + "pkg_name" : "url" + + For each package entry: + * if the EXACT package is already installed, skip it + * if a different version of the package is installed, overwrite it + * if the package isn't installed, install it + """ + #FIXME: handle rpm installs + pass + + def _handle_apt_packages(self, packages): + #FIXME: handle apt-get + pass + + # map of function pionters to handle different package managers + _package_handlers = { + "yum": _handle_yum_packages, + "rpm": _handle_rpm_packages, + "apt": _handle_apt_packages, + "rubygems": _handle_gem_packages, + "python": _handle_python_packages + } + + def _package_handler(self, manager_name): + handler = None + if manager_name in self._package_handlers: + handler = self._package_handlers[manager_name] + return handler + + def apply_packages(self): + """ + Install, upgrade, or downgrade packages listed + Each package is a dict containing package name and a list of versions + Install order: + * dpkg + * rpm + * apt + * yum + """ + packages = sorted(self._packages.iteritems(), PackagesHandler._pkgsort) + + for manager, package_entries in packages: + handler = self._package_handler(manager) + if not handler: + logging.warn("Skipping invalid package type: %s" % manager) + else: + handler(self, package_entries) + + +class ServicesHandler(object): + _services = {} + + def __init__(self, services): + self._services = services + + def _handle_sysv_command(self, service, command): + service_exe = "/sbin/service" + enable_exe = "/sbin/chkconfig" + cmd = "" + if "enable" == command: + cmd = "%s %s on" % (enable_exe, service) + elif "disable" == command: + cmd = "%s %s off" % (enable_exe, service) + elif "start" == command: + cmd = "%s %s start" % (service_exe, service) + elif "stop" == command: + cmd = "%s %s stop" % (service_exe, service) + elif "status" == command: + cmd = "%s %s status" % (service_exe, service) + command = CommandRunner(cmd) + command.run() + return command + + def _handle_systemd_command(self, service, command): + exe = "/bin/systemctl" + cmd = "" + service = '%s.service' % service + if "enable" == command: + cmd = "%s enable %s" % (exe, service) + elif "disable" == command: + cmd = "%s disable %s" % (exe, service) + elif "start" == command: + cmd = "%s start %s" % (exe, service) + elif "stop" == command: + cmd = "%s stop %s" % (exe, service) + elif "status" == command: + cmd = "%s status %s" % (exe, service) + command = CommandRunner(cmd) + command.run() + return command + + def _initialize_service(self, handler, service, properties): + if "enabled" in properties: + enable = to_boolean(properties["enabled"]) + if enable: + logging.info("Enabling service %s" % service) + handler(self, service, "enable") + else: + logging.info("Disabling service %s" % service) + handler(self, service, "disable") + + if "ensureRunning" in properties: + ensure_running = to_boolean(properties["ensureRunning"]) + command = handler(self, service, "status") + running = command.status == 0 + if ensure_running and not running: + logging.info("Starting service %s" % service) + handler(self, service, "start") + elif not ensure_running and running: + logging.info("Stopping service %s" % service) + handler(self, service, "stop") + + + def _handle_services(self, handler, services): + for service, properties in services.iteritems(): + self._handle_service(handler, service, properties) + + def _initialize_services(self, handler, services): + for service, properties in services.iteritems(): + self._initialize_service(handler, service, properties) + + # map of function pointers to various service handlers + _service_handlers = { + "sysvinit": _handle_sysv_command, + "systemd": _handle_systemd_command + } + + + def _service_handler(self, manager_name): + handler = None + if manager_name in self._service_handlers: + handler = self._service_handlers[manager_name] + return handler + + + def apply_services(self): + """ + Starts, stops, enables, disables services + """ + for manager, service_entries in self._services.iteritems(): + handler = self._service_handler(manager) + if not handler: + logging.warn("Skipping invalid service type: %s" % manager) + else: + self._handle_services(handler, service_entries) + + +class Metadata(object): + _metadata = None + _init_key = "AWS::CloudFormation::Init" + + def __init__(self, stack, resource, access_key=None, + secret_key=None, credentials_file=None, region=None): + + self.stack = stack + self.resource = resource + self.access_key = access_key + self.secret_key = secret_key + self.credentials_file = credentials_file + self.region = region + + self._metadata = None + + def retrieve(self): + """ + Read the metadata from the given filename and return the string + """ + f = open("/var/lib/cloud/data/cfn-init-data") + self._metadata = f.read() + f.close() + + def _is_valid_metadata(self): + """ + Should find the AWS::CloudFormation::Init json key + """ + is_valid = self._metadata and \ + self._init_key in self._metadata and \ + self._metadata[self._init_key] + if is_valid: + self._metadata = self._metadata[self._init_key] + return is_valid + + def _process_config(self): + """ + Parse and process a config section + * packages + * sources (not yet) + * users (not yet) + * groups (not yet) + * files (not yet) + * commands (not yet) + * services + """ + + self._config = self._metadata["config"] + PackagesHandler(self._config.get("packages")).apply_packages() + #FIXME: handle sources + #FIXME: handle users + #FIXME: handle groups + #FIXME: handle files + #FIXME: handle commands + ServicesHandler(self._config.get("services")).apply_services() + + def cfn_init(self): + """ + Process the resource metadata + """ + # FIXME: when config sets are implemented, this should select the correct + # config set from the metadata, and send each config in the config set to + # process_config + if not self._is_valid_metadata(): + raise Exception("invalid metadata") + else: + self._process_config() diff --git a/heat/jeos/F16-i386-cfntools-jeos.tdl b/heat/jeos/F16-i386-cfntools-jeos.tdl index 0bcc97179..6f556101e 100644 --- a/heat/jeos/F16-i386-cfntools-jeos.tdl +++ b/heat/jeos/F16-i386-cfntools-jeos.tdl @@ -21,5 +21,6 @@ EOF + diff --git a/heat/jeos/F16-x86_64-cfntools-jeos.tdl b/heat/jeos/F16-x86_64-cfntools-jeos.tdl index ab12ae0be..fb6f9fafa 100644 --- a/heat/jeos/F16-x86_64-cfntools-jeos.tdl +++ b/heat/jeos/F16-x86_64-cfntools-jeos.tdl @@ -21,5 +21,6 @@ EOF + diff --git a/heat/jeos/F17-i386-cfntools-jeos.tdl b/heat/jeos/F17-i386-cfntools-jeos.tdl index 51c6d7fda..69a154ed6 100644 --- a/heat/jeos/F17-i386-cfntools-jeos.tdl +++ b/heat/jeos/F17-i386-cfntools-jeos.tdl @@ -21,5 +21,6 @@ EOF + diff --git a/heat/jeos/F17-x86_64-cfntools-jeos.tdl b/heat/jeos/F17-x86_64-cfntools-jeos.tdl index 6c6227788..dae5637aa 100644 --- a/heat/jeos/F17-x86_64-cfntools-jeos.tdl +++ b/heat/jeos/F17-x86_64-cfntools-jeos.tdl @@ -21,6 +21,7 @@ EOF +