Splitting os-collect-config into its own repo.

This commit is contained in:
Clint Byrum 2013-06-26 12:26:06 -07:00
parent 5841dd6e24
commit 05b667a977
16 changed files with 30 additions and 792 deletions

105
README.md
View File

@ -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.

View File

@ -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

View File

@ -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'
' <int|default|netaddress|raw>')
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'))

View File

@ -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)

View File

@ -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

View File

@ -1,2 +0,0 @@
[foo]
database = {{database.url}}

View File

@ -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)

View File

@ -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')

View File

@ -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")

View File

@ -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

View File

@ -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__()

View File

@ -3,4 +3,3 @@ argparse
d2to1
httplib2
pbr
pystache

View File

@ -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 =