diff --git a/doc/source/grafana-dashboard.rst b/doc/source/grafana-dashboard.rst index 1bf42dd..d652831 100644 --- a/doc/source/grafana-dashboard.rst +++ b/doc/source/grafana-dashboard.rst @@ -16,20 +16,12 @@ files. OPTIONS ======= --h, --help Show the help. ---config-dir DIR Path to a config directory to pull \*.conf files from. This - file set is sorted, so as to provide a predictable parse - order if individual options are over-ridden. The set is - parsed after the file(s) specified via previous - --config-file, arguments hence over-ridden options in the - directory take precedence. ---config-file PATH Path to a config file to use. Multiple config files can be - specified, with values in later files taking precedence. The - default files used are: None. ---debug Print debugging output(set logging level to DEBUG instead - of default INFO level). ---nodebug The inverse of --debug. ---version Show program's version number and exit. +-h, --help Show this help message and exit +--config-file CONFIG Path to a config file to use. The default files used + is: /etc/grafyaml/grafyaml.conf +--debug Print debugging output (set logging level to DEBUG + instead of default INFO level) +--version Show program's version number and exit COMMANDS ======== diff --git a/etc/grafyaml.conf b/etc/grafyaml.conf index 3cc9e76..9a47d62 100644 --- a/etc/grafyaml.conf +++ b/etc/grafyaml.conf @@ -1,28 +1,14 @@ -[DEFAULT] - - -[grafana] - -# -# From grafyaml.builder -# - -# URL for grafana server. (string value) -#url = http://grafana.example.org - -# API key for access grafana. (string value) -#apikey = - - [cache] - -# -# From grafyaml.cache -# - # Directory used by grafyaml to store its cache files. (string value) #cachedir = ~/.cache/grafyaml # Maintain a special cache that contains an MD5 of every generated # dashboard. (boolean value) #enabled = true + +[grafana] +# URL for grafana server. (string value) +#url = http://localhost:8080 + +# API key for access grafana. (string value) +#apikey = diff --git a/grafana_dashboards/builder.py b/grafana_dashboards/builder.py index c00bbbe..fa04226 100644 --- a/grafana_dashboards/builder.py +++ b/grafana_dashboards/builder.py @@ -15,37 +15,44 @@ import logging import os -from oslo_config import cfg - from grafana_dashboards.cache import Cache from grafana_dashboards.grafana import Grafana from grafana_dashboards.parser import YamlParser -grafana_opts = [ - cfg.StrOpt( - 'url', default='http://grafana.example.org', - help='URL for grafana server.'), - cfg.StrOpt( - 'apikey', default=None, - help='API key for access grafana.'), -] - -grafana_group = cfg.OptGroup( - name='grafana', title='Grafana options') -list_opts = lambda: [(grafana_group, grafana_opts), ] - -CONF = cfg.CONF -CONF.register_group(grafana_group) -CONF.register_opts(grafana_opts, group='grafana') - LOG = logging.getLogger(__name__) class Builder(object): - def __init__(self): - self.cache = Cache() - self.grafana = Grafana(CONF.grafana.url, CONF.grafana.apikey) + + def __init__(self, config): + self.grafana = self._setup_grafana(config) self.parser = YamlParser() + self.cache = self._setup_cache(config) + + def _setup_cache(self, config): + if config.has_option('cache', 'enabled'): + self.cache_enabled = config.getboolean('cache', 'enabled') + else: + self.cache_enabled = True + + if config.has_option('cache', 'cachedir'): + cachedir = config.get('cache', 'cachedir') + else: + cachedir = '~/.cache/grafyaml' + + return Cache(cachedir) + + def _setup_grafana(self, config): + if config.has_option('grafana', 'apikey'): + key = config.get('grafana', 'apikey') + else: + key = None + + if config.has_option('grafana', 'url'): + url = config.get('grafana', 'url') + else: + url = 'http://localhost:8080' + return Grafana(url, key) def load_files(self, path): files_to_process = [] @@ -66,7 +73,7 @@ class Builder(object): LOG.info('Number of dashboards generated: %d', len(dashboards)) for name in dashboards: data, md5 = self.parser.get_dashboard(name) - if self.cache.has_changed(name, md5): + if self.cache.has_changed(name, md5) or not self.cache_enabled: self.grafana.create_dashboard(name, data, overwrite=True) self.cache.set(name, md5) else: diff --git a/grafana_dashboards/cache.py b/grafana_dashboards/cache.py index 4719050..17f3268 100644 --- a/grafana_dashboards/cache.py +++ b/grafana_dashboards/cache.py @@ -16,35 +16,14 @@ import logging import os from dogpile.cache.region import make_region -from oslo_config import cfg - -cache_opts = [ - cfg.StrOpt( - 'cachedir', default='~/.cache/grafyaml', - help='Directory used by grafyaml to store its cache files.'), - cfg.BoolOpt( - 'enabled', default=True, - help='Maintain a special cache that contains an MD5 of every ' - 'generated dashboard.'), -] -cache_group = cfg.OptGroup( - name='cache', title='Cache options') -list_opts = lambda: [(cache_group, cache_opts), ] - -CONF = cfg.CONF -CONF.register_opts(cache_opts) -CONF.register_opts(cache_opts, group='cache') LOG = logging.getLogger(__name__) class Cache(object): - def __init__(self): - if not CONF.cache.enabled: - return - - cache_dir = self._get_cache_dir() + def __init__(self, cachedir): + cache_dir = self._get_cache_dir(cachedir) self.region = make_region().configure( 'dogpile.cache.dbm', arguments={ @@ -53,22 +32,19 @@ class Cache(object): ) def get(self, title): - if CONF.cache.enabled: - res = self.region.get(title) - return res if res else None - return None + res = self.region.get(title) + return res if res else None def has_changed(self, title, md5): - if CONF.cache.enabled and self.get(title) == md5: + if self.get(title) == md5: return False return True def set(self, title, md5): - if CONF.cache.enabled: - self.region.set(title, md5) + self.region.set(title, md5) - def _get_cache_dir(self): - path = os.path.expanduser(CONF.cache.cachedir) + def _get_cache_dir(self, cachedir): + path = os.path.expanduser(cachedir) if not os.path.isdir(path): os.makedirs(path) return path diff --git a/grafana_dashboards/cmd.py b/grafana_dashboards/cmd.py index 70cf9f4..01380f2 100644 --- a/grafana_dashboards/cmd.py +++ b/grafana_dashboards/cmd.py @@ -12,73 +12,90 @@ # License for the specific language governing permissions and limitations # under the License. -import inspect +import argparse import logging +import os import sys -from oslo_config import cfg +from six.moves import configparser as ConfigParser from grafana_dashboards.builder import Builder -from grafana_dashboards import config +from grafana_dashboards import version -CONF = cfg.CONF LOG = logging.getLogger(__name__) -class Commands(object): +class Client(object): - def __init__(self): - self.builder = Builder() + def main(self): + self.parse_arguments() + self.read_config() + self.setup_logging() - def execute(self): - exec_method = getattr(self, CONF.action.name) - args = inspect.getargspec(exec_method) - args.args.remove('self') - kwargs = {} - for arg in args.args: - kwargs[arg] = getattr(CONF.action, arg) - exec_method(**kwargs) + self.args.func() - def update(self, path): - LOG.info('Updating dashboards in %s', path) - self.builder.update_dashboard(path) + def parse_arguments(self): + parser = argparse.ArgumentParser() + parser.add_argument( + '--config-file', dest='config', help='Path to a config file to ' + 'use. The default file used is: /etc/grafyaml/grafyaml.conf') + parser.add_argument( + '--debug', dest='debug', action='store_true', + help='Print debugging output (set logging level to DEBUG instead ' + ' of default INFO level)') + parser.add_argument( + '--version', dest='version', action='version', + version=version.version_info.release_string(), help="show " + "program's version number and exit") - def validate(self, path): - LOG.info('Validating dashboards in %s', path) + subparsers = parser.add_subparsers( + title='commands') + + parser_update = subparsers.add_parser('update') + parser_update.add_argument( + 'path', help='colon-separated list of paths to YAML files or' + ' directories') + parser_update.set_defaults(func=self.update) + + parser_validate = subparsers.add_parser('validate') + parser_validate.add_argument( + 'path', help='colon-separated list of paths to YAML files or' + ' directories') + parser_validate.set_defaults(func=self.validate) + + self.args = parser.parse_args() + + def read_config(self): + self.config = ConfigParser.ConfigParser() + if self.args.config: + fp = self.args.config + else: + fp = '/etc/grafyaml/grafyaml.conf' + self.config.read(os.path.expanduser(fp)) + + def setup_logging(self): + if self.args.debug: + logging.basicConfig(level=logging.DEBUG) + else: + logging.basicConfig(level=logging.INFO) + + def update(self): + LOG.info('Updating dashboards in %s', self.args.path) + builder = Builder(self.config) + builder.update_dashboard(self.args.path) + + def validate(self): + LOG.info('Validating dashboards in %s', self.args.path) + builder = Builder(self.config) try: - self.builder.load_files(path) + builder.load_files(self.args.path) print('SUCCESS!') except Exception as e: - print('%s: ERROR: %s' % (path, e)) + print('%s: ERROR: %s' % (self.args.path, e)) sys.exit(1) -def add_command_parsers(subparsers): - parser_update = subparsers.add_parser('update') - parser_update.add_argument( - 'path', help='colon-separated list of paths to YAML files or' - ' directories') - - parser_validate = subparsers.add_parser('validate') - parser_validate.add_argument( - 'path', help='colon-separated list of paths to YAML files or' - ' directories') - - -command_opt = cfg.SubCommandOpt('action', handler=add_command_parsers) -logging_opts = cfg.BoolOpt( - 'debug', default=False, help='Print debugging output (set logging level ' - 'to DEBUG instead of default INFO level).') - - def main(): - CONF.register_cli_opt(command_opt) - CONF.register_cli_opt(logging_opts) - config.prepare_args(sys.argv) - if CONF.debug: - logging.basicConfig(level=logging.DEBUG) - else: - logging.basicConfig(level=logging.INFO) - - Commands().execute() + client = Client() + client.main() sys.exit(0) diff --git a/grafana_dashboards/config.py b/grafana_dashboards/config.py deleted file mode 100644 index bb95eeb..0000000 --- a/grafana_dashboards/config.py +++ /dev/null @@ -1,23 +0,0 @@ -# Copyright 2015 Red Hat, Inc. -# -# 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 oslo_config import cfg - -from grafana_dashboards import version - - -def prepare_args(argv): - cfg.CONF( - argv[1:], project='grafana_dashboards', - version=version.version_info.release_string()) diff --git a/requirements.txt b/requirements.txt index 05b1edc..3bd7d5f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ dogpile.cache -oslo.config>=1.11.0 python-slugify PyYAML>=3.1.0 requests +six>=1.6.0 voluptuous>=0.7 diff --git a/tests/base.py b/tests/base.py index f679be7..e72c240 100644 --- a/tests/base.py +++ b/tests/base.py @@ -16,13 +16,18 @@ # License for the specific language governing permissions and limitations # under the License. +import logging import os import re +import shutil +import tempfile import fixtures +from six.moves import configparser as ConfigParser import testtools -from tests import conf_fixture +FIXTURE_DIR = os.path.join( + os.path.dirname(__file__), 'fixtures') def get_scenarios(fixtures_path, in_ext='yaml', out_ext='json'): @@ -52,5 +57,16 @@ class TestCase(testtools.TestCase): def setUp(self): super(TestCase, self).setUp() - self.log_fixture = self.useFixture(fixtures.FakeLogger()) - self.useFixture(conf_fixture.ConfFixture()) + self.log_fixture = self.useFixture(fixtures.FakeLogger( + level=logging.DEBUG)) + self.setup_config() + self.cachedir = tempfile.mkdtemp() + self.config.set('cache', 'cachedir', self.cachedir) + self.addCleanup(self.cleanup_cachedir) + + def setup_config(self): + self.config = ConfigParser.ConfigParser() + self.config.read(os.path.join(FIXTURE_DIR, 'grafyaml.conf')) + + def cleanup_cachedir(self): + shutil.rmtree(self.cachedir) diff --git a/tests/cmd/base.py b/tests/cmd/base.py index e33ca3e..37da17c 100644 --- a/tests/cmd/base.py +++ b/tests/cmd/base.py @@ -23,15 +23,6 @@ from tests.base import TestCase class TestCase(TestCase): - def setUp(self): - super(TestCase, self).setUp() - - def clear(): - cmd.CONF.reset() - cmd.CONF.unregister_opt(cmd.command_opt) - cmd.CONF.reset() - self.addCleanup(clear) - def shell(self, argstr, exitcodes=(0,)): orig = sys.stdout orig_stderr = sys.stderr diff --git a/tests/fixtures/grafyaml.conf b/tests/fixtures/grafyaml.conf new file mode 100644 index 0000000..f2c0900 --- /dev/null +++ b/tests/fixtures/grafyaml.conf @@ -0,0 +1,5 @@ +[grafana] +url = http://grafana.example.org + +[cache] +enabled = true diff --git a/tests/test_builder.py b/tests/test_builder.py index 3e5ba6f..0c51b54 100644 --- a/tests/test_builder.py +++ b/tests/test_builder.py @@ -24,7 +24,7 @@ class TestCaseBuilder(TestCase): def setUp(self): super(TestCaseBuilder, self).setUp() - self.builder = builder.Builder() + self.builder = builder.Builder(self.config) @mock.patch('grafana_dashboards.grafana.Grafana.create_dashboard') def test_update_dashboard(self, mock_grafana): @@ -36,7 +36,7 @@ class TestCaseBuilder(TestCase): self.assertEqual(mock_grafana.call_count, 1) # Create a new builder to avoid duplicate dashboards. - builder2 = builder.Builder() + builder2 = builder.Builder(self.config) # Update again with same dashboard, ensure we don't update grafana. builder2.update_dashboard(dashboard) self.assertEqual(mock_grafana.call_count, 1) diff --git a/tests/test_cache.py b/tests/test_cache.py index 92018a9..76e1df9 100644 --- a/tests/test_cache.py +++ b/tests/test_cache.py @@ -12,13 +12,9 @@ # License for the specific language governing permissions and limitations # under the License. -from oslo_config import cfg - from grafana_dashboards import cache from tests.base import TestCase -CONF = cfg.CONF - class TestCaseCache(TestCase): @@ -28,10 +24,10 @@ class TestCaseCache(TestCase): def setUp(self): super(TestCaseCache, self).setUp() - self.storage = None + cachedir = self.config.get('cache', 'cachedir') + self.storage = cache.Cache(cachedir) def test_cache_has_changed(self): - self.storage = cache.Cache() res = self.storage.has_changed( 'hello-world', self.dashboard['hello-world']) self.assertTrue(res) @@ -40,28 +36,10 @@ class TestCaseCache(TestCase): 'hello-world', self.dashboard['hello-world']) self.assertFalse(res) - def test_cache_disabled_has_changed(self): - CONF.cache.enabled = False - self.storage = cache.Cache() - res = self.storage.has_changed( - 'hello-world', self.dashboard['hello-world']) - self.assertTrue(res) - self.storage.set('hello-world', self.dashboard['hello-world']) - res = self.storage.has_changed( - 'hello-world', self.dashboard['hello-world']) - self.assertTrue(res) - def test_cache_get_empty(self): - self.storage = cache.Cache() self.assertEqual(self.storage.get('empty'), None) - def test_cache_disabled_get_empty(self): - CONF.cache.enabled = False - self.storage = cache.Cache() - self.assertEqual(self.storage.get('disabled'), None) - def test_cache_set_multiple(self): - self.storage = cache.Cache() self.storage.set('hello-world', self.dashboard['hello-world']) self.assertEqual( self.storage.get('hello-world'), self.dashboard['hello-world']) @@ -77,15 +55,6 @@ class TestCaseCache(TestCase): self.storage.get('hello-world'), self.dashboard['hello-world']) def test_cache_set_single(self): - self.storage = cache.Cache() self.storage.set('hello-world', self.dashboard['hello-world']) self.assertEqual( self.storage.get('hello-world'), self.dashboard['hello-world']) - - def test_cache_disabled_set_single(self): - CONF.cache.enabled = False - self.storage = cache.Cache() - self.storage.set('hello-world', self.dashboard['hello-world']) - # Make sure cache is empty. - self.assertEqual( - self.storage.get('hello-world'), None) diff --git a/tox.ini b/tox.ini index 2b1844f..d20ce2b 100644 --- a/tox.ini +++ b/tox.ini @@ -12,14 +12,6 @@ deps = -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt commands = python setup.py test --slowest --testr-args='{posargs}' -[testenv:genconfig] -commands = - oslo-config-generator \ - --namespace grafyaml.builder \ - --namespace grafyaml.cache \ - --namespace oslo.log \ - --output-file etc/grafyaml.conf - [testenv:pep8] commands = flake8