From 6d2f82aca047dd850c699fbd2178c910be39e041 Mon Sep 17 00:00:00 2001 From: Angus Salkeld Date: Thu, 26 Apr 2012 14:07:12 +1000 Subject: [PATCH] Add file support to cfn-init Signed-off-by: Angus Salkeld --- heat/cfntools/cfn_helper.py | 80 +++++++++++++++++++++++++++++++++---- heat/tests/test_cfn.py | 53 ++++++++++++++++++++++++ 2 files changed, 126 insertions(+), 7 deletions(-) diff --git a/heat/cfntools/cfn_helper.py b/heat/cfntools/cfn_helper.py index 475d1e7e..3b6e491a 100644 --- a/heat/cfntools/cfn_helper.py +++ b/heat/cfntools/cfn_helper.py @@ -30,9 +30,13 @@ Not implemented yet: """ import ConfigParser +import errno +import grp import json import logging import os +import os.path +import pwd import rpmUtils.updates as rpmupdates import rpmUtils.miscutils as rpmutils import subprocess @@ -451,6 +455,8 @@ class PackagesHandler(object): * apt * yum """ + if not self._packages: + return packages = sorted(self._packages.iteritems(), PackagesHandler._pkgsort) for manager, package_entries in packages: @@ -460,6 +466,58 @@ class PackagesHandler(object): else: handler(self, package_entries) +class FilesHandler(object): + def __init__(self, files): + self._files = files + + def apply_files(self): + if not self._files: + return + for fdest, meta in self._files.iteritems(): + dest = fdest.encode() + try: + os.makedirs(os.path.dirname(dest)) + except OSError as e: + if e.errno == errno.EEXIST: + logging.debug(str(e)) + else: + logging.exception(e) + + if 'content' in meta: + if isinstance(meta['content'], basestring): + f = open(dest, 'w') + f.write(meta['content']) + f.close() + else: + f = open(dest, 'w') + f.write(json.dumps(meta['content'], indent=4)) + f.close() + elif 'source' in meta: + CommandRunner('wget -O %s %s' % (dest, meta['source'])).run() + else: + logging.error('%s %s' % (dest, str(meta))) + continue + + uid = -1 + gid = -1 + if 'owner' in meta: + try: + user_info = pwd.getpwnam(meta['owner']) + uid = user_info[2] + except KeyError as ex: + pass + + if 'group' in meta: + try: + group_info = grp.getgrnam(meta['group']) + gid = group_info[2] + except KeyError as ex: + pass + + os.chown(dest, uid, gid) + if 'mode' in meta: + os.chmod(dest, int(meta['mode'], 8)) + class ServicesHandler(object): _services = {} @@ -566,6 +624,8 @@ class ServicesHandler(object): """ Starts, stops, enables, disables services """ + if not self._services: + return for manager, service_entries in self._services.iteritems(): handler = self._service_handler(manager) if not handler: @@ -577,6 +637,8 @@ class ServicesHandler(object): """ Restarts failed services, and runs hooks. """ + if not self._services: + return for manager, service_entries in self._services.iteritems(): handler = self._service_handler(manager) if not handler: @@ -603,13 +665,17 @@ class Metadata(object): self._is_local_metadata = True self._metadata = None - def retrieve(self): + def retrieve(self, meta_str=None): """ - Read the metadata from the given filename and return the string + Read the metadata from the given filename """ - f = open("/var/lib/cloud/data/cfn-init-data") - self._data = f.read() - f.close() + if meta_str: + self._data = meta_str + else: + f = open("/var/lib/cloud/data/cfn-init-data") + self._data = f.read() + f.close() + if isinstance(self._data, str): self._metadata = json.loads(self._data) else: @@ -633,7 +699,7 @@ class Metadata(object): * sources (not yet) * users (not yet) * groups (not yet) - * files (not yet) + * files * commands (not yet) * services """ @@ -643,7 +709,7 @@ class Metadata(object): #FIXME: handle sources #FIXME: handle users #FIXME: handle groups - #FIXME: handle files + FilesHandler(self._config.get("files")).apply_files() #FIXME: handle commands ServicesHandler(self._config.get("services")).apply_services() diff --git a/heat/tests/test_cfn.py b/heat/tests/test_cfn.py index 6f0a08c7..1f5c62e8 100644 --- a/heat/tests/test_cfn.py +++ b/heat/tests/test_cfn.py @@ -9,6 +9,7 @@ import sys import nose from nose.plugins.attrib import attr from nose import with_setup +import shutil from heat.cfntools.cfn_helper import * @@ -97,6 +98,58 @@ runas=root assert(c.hooks['bla'].runas == 'root') +def tearDown_metadata_files(): + shutil.rmtree('/tmp/_files_test_', ignore_errors=True) + + +@with_setup(None, tearDown_metadata_files) +@attr(tag=['unit', 'cfn-metadata']) +@attr(speed='fast') +def test_metadata_files(): + + j = ''' { + "AWS::CloudFormation::Init" : { + "config" : { + "files" : { + "/tmp/_files_test_/epel.repo" : { + "source" : "https://raw.github.com/heat-api/heat/master/README.rst", + "mode" : "000644" + }, + "/tmp/_files_test_/_with/some/dirs/to/make/small.conf" : { + "content" : "not much really", + "mode" : "000777" + }, + "/tmp/_files_test_/node.json": { + "content": { + "myapp": { + "db": { + "database": "RefDBName", + "user": "RefDBUser", + "host": "Fn::GetAttDBInstance.Endpoint.Address", + "password": "RefDBPassword" + } + }, + "run_list": ["recipe[wordpress]", "bla"] + }, + "mode": "000600" + } + } + } + } + } +''' + + metadata = Metadata('tester', + 'ronald') + metadata.retrieve(j) + metadata.cfn_init() + + # mask out the file type + mask = int('007777', 8) + assert(os.stat('/tmp/_files_test_/node.json').st_mode & mask == 0600) + assert(os.stat('/tmp/_files_test_/epel.repo').st_mode & mask == 0644) + assert(os.stat('/tmp/_files_test_/_with/some/dirs/to/make/small.conf').st_mode & mask == 0777) + if __name__ == '__main__': sys.argv.append(__file__) nose.main() -- 2.45.2