diff --git a/README.md b/README.md index 1c55d8d..0a374ce 100644 --- a/README.md +++ b/README.md @@ -1,106 +1,47 @@ os-apply-config =============== -Apply configuration from cloud metadata (JSON). +Collect configuration from cloud metadata sources. # What does it do? -It turns a cloud-metadata file like this: -```javascript -{"keystone": {"database": {"host": "127.0.0.1", "user": "keystone", "password": "foobar"}}} -``` -into service config files like this: -``` -[sql] -connection = mysql://keystone:foobar@127.0.0.1/keystone -...other settings... -``` +It collects data from defined configuration sources and runs a defined hook whenever the metadata has changed. # Usage -Just pass it the path to a directory tree of templates: -``` -sudo os-apply-config -t /home/me/my_templates +You must define what sources to collect configuration data from in /etc/os-collect-config/sources.ini + +The format of this file is +```ini +[default] +command=os-refresh-config + +[ec2] +type=ec2-metadata + +[cfn] +type=cloudformation ``` -# Templates - -The template directory structure should mimic a root filesystem, and contain templates for only those files you want configured. - -e.g. -``` -~/my_templates$ tree -. -└── etc - ├── keystone - │   └── keystone.conf - └── mysql - └── mysql.conf -``` - -An example tree [can be found here](https://github.com/tripleo/openstack_config_templates). - -If a template is executable it will be treated as an **executable template**. -Otherwise, it will be treated as a **mustache template**. - -## Mustache Templates - -If you don't need any logic, just some string substitution, use a mustache template. - -Metadata settings are accessed with dot ('.') notation: +These sources will be processed in order, and whenever any of them changes, default.command will be run. OS_CONFIG_FILES will be set in the environment as a colon (":") separated list of the current copy of each metadata source. So in the example above, "os-refresh-config" would be executed with something like this in OS_CONFIG_FILES: ``` -[sql] -connection = mysql://{{keystone.database.user}}:{{keystone.database.password}@{{keystone.database.host}}/keystone +/var/run/os-collect-config/ec2.json:/var/run/os-collect-config/cfn.json ``` -## Executable Templates +The sources can also be crafted using runtime arguments: -Configuration requiring logic is expressed in executable templates. - -An executable template is a script which accepts configuration as a JSON string on standard in, and writes a config file to standard out. - -The script should exit non-zero if it encounters a problem, so that os-apply-config knows what's up. - -The output of the script will be written to the path corresponding to the executable template's path in the template tree. - - -```ruby -#!/usr/bin/env ruby -require 'json' -params = JSON.parse STDIN.read -puts "connection = mysql://#{c['keystone']['database']['user']}:#{c['keystone']['database']['password']}@#{c['keystone']['database']['host']}/keystone" ``` - -You could even embed mustache in a heredoc, and use that: -```ruby -#!/usr/bin/env ruby -require 'json' -require 'mustache' -params = JSON.parse STDIN.read - -template = <<-eos -[sql] -connection = mysql://{{keystone.database.user}}:{{keystone.database.password}}@{{keystone.database.host}}/keystone - -[log] -... -eos - -# tweak params here... - -puts Mustache.render(template, params) +os-collect-config --command=os-refresh-config --source ec2:type=ec2-metadata --source cfn:type=cloudformation ``` # Quick Start -```bash -# install it -sudo pip install -U git+git://github.com/stackforge/os-config-applier.git -# grab example templates -git clone git://github.com/stackforge/triple-image-elements /tmp/config +sudo pip install -U git+git://github.com/stackforge/os-collect-config.git -# run it -os-apply-config -t /tmp/config/elements/nova/os-config-applier/ -m /tmp/config/elements/boot-stack/config.json -o /tmp/config_output +# run it on an OpenStack instance with access to ec2 metadata: +os-collect-config --print --source "ec2:ec2-metadata" ``` + +That should print out a json representation of the entire ec2 metadata tree. diff --git a/os_apply_config/config_exception.py b/os_apply_config/config_exception.py deleted file mode 100644 index 672b819..0000000 --- a/os_apply_config/config_exception.py +++ /dev/null @@ -1,18 +0,0 @@ -# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. -# -# 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. - - -class ConfigException(Exception): - pass diff --git a/os_apply_config/os_apply_config.py b/os_apply_config/os_apply_config.py deleted file mode 100755 index 3ae735d..0000000 --- a/os_apply_config/os_apply_config.py +++ /dev/null @@ -1,238 +0,0 @@ -# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. -# -# 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 argparse -import json -import logging -import os -import subprocess -import sys -import tempfile - -from pystache import context - -from config_exception import ConfigException -from renderers import JsonRenderer -from value_types import ensure_type - -TEMPLATES_DIR = os.environ.get('OS_CONFIG_APPLIER_TEMPLATES', None) -if TEMPLATES_DIR is None: - TEMPLATES_DIR = '/opt/stack/os-apply-config/templates' - if not os.path.isdir(TEMPLATES_DIR): - # Backwards compat with the old name. - TEMPLATES_DIR = '/opt/stack/os-config-applier/templates' - - -def install_config(config_path, template_root, - output_path, validate, subhash=None): - config = strip_hash(read_config(config_path), subhash) - tree = build_tree(template_paths(template_root), config) - if not validate: - for path, contents in tree.items(): - write_file(os.path.join( - output_path, strip_prefix('/', path)), contents) - - -def print_key(config_path, key, type_name, default=None): - config = read_config(config_path) - keys = key.split('.') - for key in keys: - try: - config = config[key] - except KeyError: - if default is not None: - print default - return - else: - raise ConfigException( - 'key %s does not exist in %s' % (key, config_path)) - ensure_type(config, type_name) - print config - - -def write_file(path, contents): - logger.info("writing %s", path) - d = os.path.dirname(path) - os.path.exists(d) or os.makedirs(d) - with tempfile.NamedTemporaryFile(dir=d, delete=False) as newfile: - newfile.write(contents) - os.chmod(newfile.name, 0644) - os.rename(newfile.name, path) - -# return a map of filenames->filecontents - - -def build_tree(templates, config): - res = {} - for in_file, out_file in templates: - res[out_file] = render_template(in_file, config) - return res - - -def render_template(template, config): - if is_executable(template): - return render_executable(template, config) - else: - try: - return render_moustache(open(template).read(), config) - except context.KeyNotFoundError as e: - raise ConfigException( - "key '%s' from template '%s' does not exist in metadata file." - % (e.key, template)) - except Exception as e: - raise ConfigException( - "could not render moustache template %s" % template) - - -def is_executable(path): - return os.path.isfile(path) and os.access(path, os.X_OK) - - -def render_moustache(text, config): - r = JsonRenderer(missing_tags='ignore') - return r.render(text, config) - - -def render_executable(path, config): - p = subprocess.Popen([path], - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - stdout, stderr = p.communicate(json.dumps(config)) - p.wait() - if p.returncode != 0: - raise ConfigException( - "config script failed: %s\n\nwith output:\n\n%s" % - (path, stdout + stderr)) - return stdout - - -def read_config(paths): - for path in paths: - if os.path.exists(path): - try: - return json.loads(open(path).read()) - except Exception: - raise ConfigException("invalid metadata file: %s" % path) - raise ConfigException("No metadata found.") - - -def template_paths(root): - res = [] - for cur_root, subdirs, files in os.walk(root): - for f in files: - inout = (os.path.join(cur_root, f), os.path.join( - strip_prefix(root, cur_root), f)) - res.append(inout) - return res - - -def strip_prefix(prefix, s): - return s[len(prefix):] if s.startswith(prefix) else s - - -def strip_hash(h, keys): - if not keys: - return h - for k in keys.split('.'): - if k in h and isinstance(h[k], dict): - h = h[k] - else: - raise ConfigException( - "key '%s' does not correspond to a hash in the metadata file" - % keys) - return h - - -def parse_opts(argv): - parser = argparse.ArgumentParser() - parser.add_argument('-t', '--templates', metavar='TEMPLATE_ROOT', - help="""path to template root directory (default: - %(default)s)""", - default=TEMPLATES_DIR) - parser.add_argument('-o', '--output', metavar='OUT_DIR', - help='root directory for output (default:%(default)s)', - default='/') - parser.add_argument('-m', '--metadata', metavar='METADATA_FILE', nargs='*', - help='path to metadata files. First one that exists' - ' will be used. (default: %(default)s)', - default=['/var/cache/heat-cfntools/last_metadata', - '/var/lib/heat-cfntools/cfn-init-data', - '/var/lib/cloud/data/cfn-init-data']) - parser.add_argument( - '-v', '--validate', help='validate only. do not write files', - default=False, action='store_true') - parser.add_argument( - '--print-templates', default=False, action='store_true', - help='Print templates root and exit.') - parser.add_argument('-s', '--subhash', - help='use the sub-hash named by this key,' - ' instead of the full metadata hash') - parser.add_argument('--key', metavar='KEY', default=None, - help='print the specified key and exit.' - ' (may be used with --type and --key-default)') - parser.add_argument('--type', default='default', - help='exit with error if the specified --key does not' - ' match type. Valid types are' - ' ') - parser.add_argument('--key-default', - help='This option only affects running with --key.' - ' Print this if key is not found. This value is' - ' not subject to type restrictions. If --key is' - ' specified and no default is specified, program' - ' exits with an error on missing key.') - opts = parser.parse_args(argv[1:]) - - return opts - - -def main(argv=sys.argv): - opts = parse_opts(argv) - if opts.print_templates: - print(opts.templates) - return 0 - - try: - if opts.templates is None: - raise ConfigException('missing option --templates') - - if opts.key: - print_key(opts.metadata, - opts.key, - opts.type, - opts.key_default) - else: - install_config(opts.metadata, opts.templates, opts.output, - opts.validate, opts.subhash) - logger.info("success") - except ConfigException as e: - logger.error(e) - return 1 - return 0 - - -# logging -LOG_FORMAT = '[%(asctime)s] [%(levelname)s] %(message)s' -DATE_FORMAT = '%Y/%m/%d %I:%M:%S %p' - - -def add_handler(logger, handler): - handler.setFormatter(logging.Formatter(LOG_FORMAT, datefmt=DATE_FORMAT)) - logger.addHandler(handler) -logger = logging.getLogger('os-apply-config') -logger.setLevel(logging.INFO) -add_handler(logger, logging.StreamHandler()) -if os.geteuid() == 0: - add_handler(logger, logging.FileHandler('/var/log/os-apply-config.log')) diff --git a/os_apply_config/renderers.py b/os_apply_config/renderers.py deleted file mode 100644 index 07b6cf2..0000000 --- a/os_apply_config/renderers.py +++ /dev/null @@ -1,41 +0,0 @@ -# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. -# -# 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 json - -import pystache - - -class JsonRenderer(pystache.Renderer): - def __init__(self, - file_encoding=None, - string_encoding=None, - decode_errors=None, - search_dirs=None, - file_extension=None, - escape=None, - partials=None, - missing_tags=None): - # json would be html escaped otherwise - if escape is None: - escape = lambda u: u - return super(JsonRenderer, self).__init__(file_encoding, - string_encoding, - decode_errors, search_dirs, - file_extension, escape, - partials, missing_tags) - - def str_coerce(self, val): - return json.dumps(val) diff --git a/os_apply_config/tests/__init__.py b/os_apply_config/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/os_apply_config/tests/templates/etc/glance/script.conf b/os_apply_config/tests/templates/etc/glance/script.conf deleted file mode 100755 index c1c4372..0000000 --- a/os_apply_config/tests/templates/etc/glance/script.conf +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env python -import json -import sys -params = json.loads(sys.stdin.read()) -x = params["x"] -if x is None: raise Exception("undefined: x") -print x diff --git a/os_apply_config/tests/templates/etc/keystone/keystone.conf b/os_apply_config/tests/templates/etc/keystone/keystone.conf deleted file mode 100644 index 335417f..0000000 --- a/os_apply_config/tests/templates/etc/keystone/keystone.conf +++ /dev/null @@ -1,2 +0,0 @@ -[foo] -database = {{database.url}} diff --git a/os_apply_config/tests/test_json_renderer.py b/os_apply_config/tests/test_json_renderer.py deleted file mode 100644 index 2b34218..0000000 --- a/os_apply_config/tests/test_json_renderer.py +++ /dev/null @@ -1,38 +0,0 @@ -# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. -# -# 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 json - -import testtools -from testtools import content - -from os_apply_config import renderers - -TEST_JSON = '{"a":{"b":[1,2,3,"foo"],"c": "the quick brown fox"}}' - - -class JsonRendererTestCase(testtools.TestCase): - - def test_json_renderer(self): - context = json.loads(TEST_JSON) - x = renderers.JsonRenderer() - result = x.render('{{a.b}}', context) - self.addDetail('result', content.text_content(result)) - result_structure = json.loads(result) - desire_structure = json.loads('[1,2,3,"foo"]') - self.assertEqual(desire_structure, result_structure) - result = x.render('{{a.c}}', context) - self.addDetail('result', content.text_content(result)) - self.assertEqual(u'the quick brown fox', result) diff --git a/os_apply_config/tests/test_os_apply_config.py b/os_apply_config/tests/test_os_apply_config.py deleted file mode 100644 index 102fa61..0000000 --- a/os_apply_config/tests/test_os_apply_config.py +++ /dev/null @@ -1,254 +0,0 @@ -# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. -# -# 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 json -import os -import tempfile - -import fixtures -import testtools - -from os_apply_config import config_exception -from os_apply_config import os_apply_config as oca - -# example template tree -TEMPLATES = os.path.join(os.path.dirname(__file__), 'templates') -TEMPLATE_PATHS = [ - "/etc/glance/script.conf", - "/etc/keystone/keystone.conf" -] - -# config for example tree -CONFIG = { - "x": "foo", - "database": { - "url": "sqlite:///blah" - } -} - -# config for example tree - with subhash -CONFIG_SUBHASH = { - "OpenStack::Config": { - "x": "foo", - "database": { - "url": "sqlite:///blah" - } - } -} - -# expected output for example tree -OUTPUT = { - "/etc/glance/script.conf": "foo\n", - "/etc/keystone/keystone.conf": "[foo]\ndatabase = sqlite:///blah\n" -} - - -def main_path(): - return ( - os.path.dirname(os.path.realpath(__file__)) + - '/../os_apply_config.py') - - -def template(relpath): - return os.path.join(TEMPLATES, relpath[1:]) - - -class TestRunOSConfigApplier(testtools.TestCase): - - def setUp(self): - super(TestRunOSConfigApplier, self).setUp() - self.useFixture(fixtures.NestedTempfile()) - self.stdout = self.useFixture(fixtures.StringStream('stdout')).stream - self.useFixture(fixtures.MonkeyPatch('sys.stdout', self.stdout)) - stderr = self.useFixture(fixtures.StringStream('stderr')).stream - self.useFixture(fixtures.MonkeyPatch('sys.stderr', stderr)) - self.logger = self.useFixture( - fixtures.FakeLogger(name="os-apply-config")) - fd, self.path = tempfile.mkstemp() - with os.fdopen(fd, 'w') as t: - t.write(json.dumps(CONFIG)) - t.flush() - - def test_print_key(self): - self.assertEqual(0, oca.main( - ['os-apply-config.py', '--metadata', self.path, '--key', - 'database.url', '--type', 'raw'])) - self.stdout.seek(0) - self.assertEqual(CONFIG['database']['url'], - self.stdout.read().strip()) - self.assertEqual('', self.logger.output) - - def test_print_key_missing(self): - self.assertEqual(1, oca.main( - ['os-apply-config.py', '--metadata', self.path, '--key', - 'does.not.exist'])) - self.assertIn('does not exist', self.logger.output) - - def test_print_key_missing_default(self): - self.assertEqual(0, oca.main( - ['os-apply-config.py', '--metadata', self.path, '--key', - 'does.not.exist', '--key-default', ''])) - self.stdout.seek(0) - self.assertEqual('', self.stdout.read().strip()) - self.assertEqual('', self.logger.output) - - def test_print_key_wrong_type(self): - self.assertEqual(1, oca.main( - ['os-apply-config.py', '--metadata', self.path, '--key', - 'x', '--type', 'int'])) - self.assertIn('cannot interpret value', self.logger.output) - - def test_print_templates(self): - oca.main(['os-apply-config', '--print-templates']) - self.stdout.seek(0) - self.assertEqual(self.stdout.read().strip(), oca.TEMPLATES_DIR) - self.assertEqual('', self.logger.output) - - -class OSConfigApplierTestCase(testtools.TestCase): - - def setUp(self): - super(OSConfigApplierTestCase, self).setUp() - self.useFixture(fixtures.FakeLogger('os-apply-config')) - self.useFixture(fixtures.NestedTempfile()) - - def test_install_config(self): - fd, path = tempfile.mkstemp() - with os.fdopen(fd, 'w') as t: - t.write(json.dumps(CONFIG)) - t.flush() - tmpdir = tempfile.mkdtemp() - oca.install_config([path], TEMPLATES, tmpdir, False) - for path, contents in OUTPUT.items(): - full_path = os.path.join(tmpdir, path[1:]) - assert os.path.exists(full_path) - self.assertEqual(open(full_path).read(), contents) - - def test_install_config_subhash(self): - fd, tpath = tempfile.mkstemp() - with os.fdopen(fd, 'w') as t: - t.write(json.dumps(CONFIG_SUBHASH)) - t.flush() - tmpdir = tempfile.mkdtemp() - oca.install_config( - [tpath], TEMPLATES, tmpdir, False, 'OpenStack::Config') - for path, contents in OUTPUT.items(): - full_path = os.path.join(tmpdir, path[1:]) - assert os.path.exists(full_path) - self.assertEqual(open(full_path).read(), contents) - - def test_build_tree(self): - self.assertEqual(oca.build_tree( - oca.template_paths(TEMPLATES), CONFIG), OUTPUT) - - def test_render_template(self): - # execute executable files, moustache non-executables - self.assertEqual(oca.render_template(template( - "/etc/glance/script.conf"), {"x": "abc"}), "abc\n") - self.assertRaises( - config_exception.ConfigException, oca.render_template, template( - "/etc/glance/script.conf"), {}) - - def test_render_moustache(self): - self.assertEqual(oca.render_moustache("ab{{x.a}}cd", { - "x": {"a": "123"}}), "ab123cd") - - def test_render_moustache_bad_key(self): - self.assertEqual(oca.render_moustache("{{badkey}}", {}), u'') - - def test_render_executable(self): - params = {"x": "foo"} - self.assertEqual(oca.render_executable(template( - "/etc/glance/script.conf"), params), "foo\n") - - def test_render_executable_failure(self): - self.assertRaises( - config_exception.ConfigException, - oca.render_executable, template("/etc/glance/script.conf"), {}) - - def test_template_paths(self): - expected = map(lambda p: (template(p), p), TEMPLATE_PATHS) - actual = oca.template_paths(TEMPLATES) - expected.sort(key=lambda tup: tup[1]) - actual.sort(key=lambda tup: tup[1]) - self.assertEqual(actual, expected) - - def test_read_config(self): - with tempfile.NamedTemporaryFile() as t: - d = {"a": {"b": ["c", "d"]}} - t.write(json.dumps(d)) - t.flush() - self.assertEqual(oca.read_config([t.name]), d) - - def test_read_config_bad_json(self): - with tempfile.NamedTemporaryFile() as t: - t.write("{{{{") - t.flush() - self.assertRaises(config_exception.ConfigException, - oca.read_config, [t.name]) - - def test_read_config_no_file(self): - self.assertRaises(config_exception.ConfigException, - oca.read_config, ["/nosuchfile"]) - - def test_read_config_multi(self): - with tempfile.NamedTemporaryFile(mode='wb') as t1: - with tempfile.NamedTemporaryFile(mode='wb') as t2: - d1 = {"a": {"b": [1, 2]}} - d2 = {"x": {"y": [8, 9]}} - t1.write(json.dumps(d1)) - t1.flush() - t2.write(json.dumps(d2)) - t2.flush() - result = oca.read_config([t1.name, t2.name]) - self.assertEqual(d1, result) - - def test_read_config_multi_missing1(self): - with tempfile.NamedTemporaryFile(mode='wb') as t1: - pass - with tempfile.NamedTemporaryFile(mode='wb') as t2: - d2 = {"x": {"y": [8, 9]}} - t2.write(json.dumps(d2)) - t2.flush() - result = oca.read_config([t1.name, t2.name]) - self.assertEqual(d2, result) - - def test_read_config_multi_missing_bad1(self): - with tempfile.NamedTemporaryFile(mode='wb') as t1: - t1.write('{{{') - t1.flush() - with tempfile.NamedTemporaryFile(mode='wb') as t2: - pass - d2 = {"x": {"y": [8, 9]}} - t2.write(json.dumps(d2)) - t2.flush() - self.assertRaises(config_exception.ConfigException, - oca.read_config, [t1.name, t2.name]) - - def test_read_config_multi_missing_all(self): - with tempfile.NamedTemporaryFile(mode='wb') as t1: - pass - with tempfile.NamedTemporaryFile(mode='wb') as t2: - pass - self.assertRaises(config_exception.ConfigException, - oca.read_config, [t1.name, t2.name]) - - def test_strip_hash(self): - h = {'a': {'b': {'x': 'y'}}, "c": [1, 2, 3]} - self.assertEqual(oca.strip_hash(h, 'a.b'), {'x': 'y'}) - self.assertRaises(config_exception.ConfigException, - oca.strip_hash, h, 'a.nonexistent') - self.assertRaises(config_exception.ConfigException, - oca.strip_hash, h, 'a.c') diff --git a/os_apply_config/tests/test_value_type.py b/os_apply_config/tests/test_value_type.py deleted file mode 100644 index 946458e..0000000 --- a/os_apply_config/tests/test_value_type.py +++ /dev/null @@ -1,69 +0,0 @@ -# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. -# -# 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 testtools - -from os_apply_config import config_exception -from os_apply_config import value_types - - -class ValueTypeTestCase(testtools.TestCase): - - def test_unknown_type(self): - self.assertRaises( - ValueError, value_types.ensure_type, "foo", "badtype") - - def test_int(self): - self.assertEqual("123", value_types.ensure_type("123", "int")) - - def test_default(self): - self.assertEqual("foobar", - value_types.ensure_type("foobar", "default")) - self.assertEqual("x86_64", - value_types.ensure_type("x86_64", "default")) - - def test_default_bad(self): - self.assertRaises(config_exception.ConfigException, - value_types.ensure_type, "foo\nbar", "default") - - def test_default_empty(self): - self.assertEqual('', - value_types.ensure_type('', 'default')) - - def test_raw_empty(self): - self.assertEqual('', - value_types.ensure_type('', 'raw')) - - def test_net_address_ipv4(self): - self.assertEqual('192.0.2.1', value_types.ensure_type('192.0.2.1', - 'netaddress')) - - def test_net_address_cidr(self): - self.assertEqual('192.0.2.0/24', - value_types.ensure_type('192.0.2.0/24', 'netaddress')) - - def test_ent_address_ipv6(self): - self.assertEqual('::', value_types.ensure_type('::', 'netaddress')) - self.assertEqual('2001:db8::2:1', value_types.ensure_type( - '2001:db8::2:1', 'netaddress')) - - def test_net_address_dns(self): - self.assertEqual('host.0domain-name.test', - value_types.ensure_type('host.0domain-name.test', - 'netaddress')) - - def test_net_address_bad(self): - self.assertRaises(config_exception.ConfigException, - value_types.ensure_type, "192.0.2.1;DROP TABLE foo") diff --git a/os_apply_config/value_types.py b/os_apply_config/value_types.py deleted file mode 100644 index 2176879..0000000 --- a/os_apply_config/value_types.py +++ /dev/null @@ -1,35 +0,0 @@ -# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. -# -# 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 re - -from config_exception import ConfigException - -TYPES = { - "int": "^[0-9]+$", - "default": "^[A-Za-z0-9_]*$", - "netaddress": "^[A-Za-z0-9/.:-]+$", - "raw": "" -} - - -def ensure_type(string_value, type_name='default'): - if type_name not in TYPES: - raise ValueError( - "requested validation of unknown type: %s" % type_name) - if not re.match(TYPES[type_name], string_value): - raise ConfigException("cannot interpret value '%s' as type %s" % ( - string_value, type_name)) - return string_value diff --git a/os_apply_config/__init__.py b/os_collect_config/__init__.py similarity index 100% rename from os_apply_config/__init__.py rename to os_collect_config/__init__.py diff --git a/os_apply_config/collect.py b/os_collect_config/collect.py similarity index 99% rename from os_apply_config/collect.py rename to os_collect_config/collect.py index a188fd2..95efc13 100644 --- a/os_apply_config/collect.py +++ b/os_collect_config/collect.py @@ -20,8 +20,10 @@ def collect_ec2(): ec2_metadata[item] = _fetch_metadata('/%s' % item) return ec2_metadata + def __main__(): print json.dumps(collect_ec2()) + if __name__ == '__main__': __main__() diff --git a/os_apply_config/tests/test_collect.py b/os_collect_config/tests/test_collect.py similarity index 100% rename from os_apply_config/tests/test_collect.py rename to os_collect_config/tests/test_collect.py diff --git a/requirements.txt b/requirements.txt index 2ec6bda..ba32247 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,4 +3,3 @@ argparse d2to1 httplib2 pbr -pystache diff --git a/setup.cfg b/setup.cfg index cbc777c..9b9dfcd 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,11 +1,11 @@ [metadata] -name = os-apply-config +name = os-collect-config author = OpenStack author-email = openstack-dev@lists.openstack.org -summary = Config files from cloud metadata +summary = Collect and cache metadata, run hooks on changes. description-file = README.md -home-page = http://github.com/stackforge/os-config-applier +home-page = http://github.com/stackforge/os-collect-config classifier = Development Status :: 4 - Beta Environment :: Console @@ -18,7 +18,7 @@ classifier = [files] packages = - os_apply_config + os_collect_config [global] setup-hooks = @@ -26,9 +26,7 @@ setup-hooks = [entry_points] console_scripts = - os-config-applier = os_apply_config.os_apply_config:main - os-apply-config = os_apply_config.os_apply_config:main - os-collect-config = os_apply_config.collect:__main__ + os-collect-config = os_collect_config.collect:__main__ [egg_info] tag_build =