Add validate command
Like JJB, Zuul and Nodepool, we need to have a CLI command to validate our configuration files. Change-Id: I4ccac21a2d77917667e1e844ab9ee1f1f281ea9f Signed-off-by: Paul Belanger <pabelanger@redhat.com>
This commit is contained in:
parent
103a882cbf
commit
f4b18fdd74
@ -45,8 +45,11 @@ class Builder(object):
|
|||||||
self.grafana = Grafana(CONF.grafana.url, CONF.grafana.apikey)
|
self.grafana = Grafana(CONF.grafana.url, CONF.grafana.apikey)
|
||||||
self.parser = YamlParser()
|
self.parser = YamlParser()
|
||||||
|
|
||||||
def update_dashboard(self, path):
|
def load_files(self, path):
|
||||||
self.parser.parse(path)
|
self.parser.parse(path)
|
||||||
|
|
||||||
|
def update_dashboard(self, path):
|
||||||
|
self.load_files(path)
|
||||||
dashboards = self.parser.data.get('dashboard', {})
|
dashboards = self.parser.data.get('dashboard', {})
|
||||||
for item in dashboards:
|
for item in dashboards:
|
||||||
data, md5 = self.parser.get_dashboard(item)
|
data, md5 = self.parser.get_dashboard(item)
|
||||||
|
@ -42,6 +42,14 @@ class Commands(object):
|
|||||||
def update(self, path):
|
def update(self, path):
|
||||||
self.builder.update_dashboard(path)
|
self.builder.update_dashboard(path)
|
||||||
|
|
||||||
|
def validate(self, path):
|
||||||
|
try:
|
||||||
|
self.builder.load_files(path)
|
||||||
|
print('SUCCESS!')
|
||||||
|
except Exception as e:
|
||||||
|
print('%s: ERROR: %s' % (path, e))
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
def add_command_parsers(subparsers):
|
def add_command_parsers(subparsers):
|
||||||
parser_update = subparsers.add_parser('update')
|
parser_update = subparsers.add_parser('update')
|
||||||
@ -49,6 +57,11 @@ def add_command_parsers(subparsers):
|
|||||||
'path', help='colon-separated list of paths to YAML files or'
|
'path', help='colon-separated list of paths to YAML files or'
|
||||||
' directories')
|
' 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)
|
command_opt = cfg.SubCommandOpt('action', handler=add_command_parsers)
|
||||||
|
|
||||||
|
@ -16,12 +16,37 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
|
||||||
import fixtures
|
import fixtures
|
||||||
import testtools
|
import testtools
|
||||||
|
|
||||||
from tests import conf_fixture
|
from tests import conf_fixture
|
||||||
|
|
||||||
|
|
||||||
|
def get_scenarios(fixtures_path, in_ext='yaml', out_ext='json'):
|
||||||
|
scenarios = []
|
||||||
|
files = []
|
||||||
|
for dirpath, dirs, fs in os.walk(fixtures_path):
|
||||||
|
files.extend([os.path.join(dirpath, f) for f in fs])
|
||||||
|
|
||||||
|
input_files = [f for f in files if re.match(r'.*\.{0}$'.format(in_ext), f)]
|
||||||
|
|
||||||
|
for input_filename in input_files:
|
||||||
|
output_candidate = re.sub(
|
||||||
|
r'\.{0}$'.format(in_ext), '.{0}'.format(out_ext), input_filename)
|
||||||
|
if output_candidate not in files:
|
||||||
|
output_candidate = None
|
||||||
|
|
||||||
|
scenarios.append((input_filename, {
|
||||||
|
'in_filename': input_filename,
|
||||||
|
'out_filename': output_candidate,
|
||||||
|
}))
|
||||||
|
|
||||||
|
return scenarios
|
||||||
|
|
||||||
|
|
||||||
class TestCase(testtools.TestCase):
|
class TestCase(testtools.TestCase):
|
||||||
"""Test case base class for all unit tests."""
|
"""Test case base class for all unit tests."""
|
||||||
|
|
||||||
|
0
tests/cmd/__init__.py
Normal file
0
tests/cmd/__init__.py
Normal file
55
tests/cmd/base.py
Normal file
55
tests/cmd/base.py
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import fixtures
|
||||||
|
import six
|
||||||
|
|
||||||
|
from grafana_dashboards import cmd
|
||||||
|
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
|
||||||
|
try:
|
||||||
|
sys.stdout = six.StringIO()
|
||||||
|
sys.stderr = six.StringIO()
|
||||||
|
argv = ['grafana-dashboards']
|
||||||
|
argv += argstr.split()
|
||||||
|
self.useFixture(fixtures.MonkeyPatch('sys.argv', argv))
|
||||||
|
cmd.main()
|
||||||
|
except SystemExit:
|
||||||
|
exc_type, exc_value, exc_trackback = sys.exc_info()
|
||||||
|
self.assertIn(exc_value.code, exitcodes)
|
||||||
|
finally:
|
||||||
|
stdout = sys.stdout.getvalue()
|
||||||
|
sys.stdout.close()
|
||||||
|
sys.stdout = orig
|
||||||
|
stderr = sys.stderr.getvalue()
|
||||||
|
sys.stderr.close()
|
||||||
|
sys.stderr = orig_stderr
|
||||||
|
return (stdout, stderr)
|
71
tests/cmd/test_validate.py
Normal file
71
tests/cmd/test_validate.py
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
|
||||||
|
from testscenarios.testcase import TestWithScenarios
|
||||||
|
from testtools import matchers
|
||||||
|
|
||||||
|
from tests.base import get_scenarios
|
||||||
|
from tests.cmd.base import TestCase
|
||||||
|
|
||||||
|
|
||||||
|
class TestCaseValidateScenarios(TestWithScenarios, TestCase):
|
||||||
|
fixtures_path = os.path.join(
|
||||||
|
os.path.dirname(__file__), '../fixtures/cmd/validate')
|
||||||
|
scenarios = get_scenarios(fixtures_path)
|
||||||
|
|
||||||
|
def test_command(self):
|
||||||
|
if os.path.basename(self.in_filename).startswith('good-'):
|
||||||
|
self._validate_success()
|
||||||
|
else:
|
||||||
|
self._validate_failure()
|
||||||
|
|
||||||
|
def _validate_failure(self):
|
||||||
|
required = [
|
||||||
|
'%s: ERROR:' % self.in_filename,
|
||||||
|
]
|
||||||
|
stdout, stderr = self.shell(
|
||||||
|
'validate %s' % self.in_filename, exitcodes=[1])
|
||||||
|
for r in required:
|
||||||
|
self.assertThat(
|
||||||
|
(stdout + stderr),
|
||||||
|
matchers.MatchesRegex(r, re.DOTALL | re.MULTILINE))
|
||||||
|
|
||||||
|
def _validate_success(self):
|
||||||
|
required = [
|
||||||
|
'SUCCESS!',
|
||||||
|
]
|
||||||
|
stdout, stderr = self.shell(
|
||||||
|
'validate %s' % self.in_filename, exitcodes=[0])
|
||||||
|
for r in required:
|
||||||
|
self.assertThat(
|
||||||
|
(stdout + stderr),
|
||||||
|
matchers.MatchesRegex(r, re.DOTALL | re.MULTILINE))
|
||||||
|
|
||||||
|
|
||||||
|
class TestCaseValidate(TestCase):
|
||||||
|
|
||||||
|
def test_validate_without_path(self):
|
||||||
|
required = [
|
||||||
|
'.*?^usage: grafana-dashboards validate \[-h\] path',
|
||||||
|
'.*?^grafana-dashboards validate: error: (too few arguments|the '
|
||||||
|
'following arguments are required: path)',
|
||||||
|
]
|
||||||
|
stdout, stderr = self.shell('validate', exitcodes=[2])
|
||||||
|
for r in required:
|
||||||
|
self.assertThat(
|
||||||
|
(stdout + stderr),
|
||||||
|
matchers.MatchesRegex(r, re.DOTALL | re.MULTILINE))
|
2
tests/fixtures/cmd/validate/bad-dashboard-0001.yaml
vendored
Normal file
2
tests/fixtures/cmd/validate/bad-dashboard-0001.yaml
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
board:
|
||||||
|
title: New dashboard
|
2
tests/fixtures/cmd/validate/good-dashboard-0001.yaml
vendored
Normal file
2
tests/fixtures/cmd/validate/good-dashboard-0001.yaml
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
dashboard:
|
||||||
|
title: New dashboard
|
@ -17,8 +17,6 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import os
|
|
||||||
import re
|
|
||||||
|
|
||||||
import doctest
|
import doctest
|
||||||
import testtools
|
import testtools
|
||||||
@ -26,28 +24,6 @@ import testtools
|
|||||||
from grafana_dashboards.parser import YamlParser
|
from grafana_dashboards.parser import YamlParser
|
||||||
|
|
||||||
|
|
||||||
def get_scenarios(fixtures_path, in_ext='yaml', out_ext='json'):
|
|
||||||
scenarios = []
|
|
||||||
files = []
|
|
||||||
for dirpath, dirs, fs in os.walk(fixtures_path):
|
|
||||||
files.extend([os.path.join(dirpath, f) for f in fs])
|
|
||||||
|
|
||||||
input_files = [f for f in files if re.match(r'.*\.{0}$'.format(in_ext), f)]
|
|
||||||
|
|
||||||
for input_filename in input_files:
|
|
||||||
output_candidate = re.sub(
|
|
||||||
r'\.{0}$'.format(in_ext), '.{0}'.format(out_ext), input_filename)
|
|
||||||
if output_candidate not in files:
|
|
||||||
output_candidate = None
|
|
||||||
|
|
||||||
scenarios.append((input_filename, {
|
|
||||||
'in_filename': input_filename,
|
|
||||||
'out_filename': output_candidate,
|
|
||||||
}))
|
|
||||||
|
|
||||||
return scenarios
|
|
||||||
|
|
||||||
|
|
||||||
class TestCase(object):
|
class TestCase(object):
|
||||||
"""Test case base class for all unit tests."""
|
"""Test case base class for all unit tests."""
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ import os
|
|||||||
from testscenarios.testcase import TestWithScenarios
|
from testscenarios.testcase import TestWithScenarios
|
||||||
from testtools import TestCase
|
from testtools import TestCase
|
||||||
|
|
||||||
from tests.schema.base import get_scenarios
|
from tests.base import get_scenarios
|
||||||
from tests.schema.base import TestCase as BaseTestCase
|
from tests.schema.base import TestCase as BaseTestCase
|
||||||
|
|
||||||
|
|
||||||
|
@ -13,44 +13,14 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import sys
|
|
||||||
|
|
||||||
import fixtures
|
|
||||||
import six
|
|
||||||
from testtools import matchers
|
from testtools import matchers
|
||||||
|
|
||||||
from grafana_dashboards import cmd
|
from tests.cmd.base import TestCase
|
||||||
from tests.base import TestCase
|
|
||||||
|
|
||||||
|
|
||||||
class TestCaseCmd(TestCase):
|
class TestCaseCmd(TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(TestCaseCmd, self).setUp()
|
|
||||||
cmd.CONF.reset()
|
|
||||||
|
|
||||||
def shell(self, argstr, exitcodes=(0,)):
|
|
||||||
orig = sys.stdout
|
|
||||||
orig_stderr = sys.stderr
|
|
||||||
try:
|
|
||||||
sys.stdout = six.StringIO()
|
|
||||||
sys.stderr = six.StringIO()
|
|
||||||
argv = ['grafana-dashboards']
|
|
||||||
argv += argstr.split()
|
|
||||||
self.useFixture(fixtures.MonkeyPatch('sys.argv', argv))
|
|
||||||
cmd.main()
|
|
||||||
except SystemExit:
|
|
||||||
exc_type, exc_value, exc_trackback = sys.exc_info()
|
|
||||||
self.assertIn(exc_value.code, exitcodes)
|
|
||||||
finally:
|
|
||||||
stdout = sys.stdout.getvalue()
|
|
||||||
sys.stdout.close()
|
|
||||||
sys.stdout = orig
|
|
||||||
stderr = sys.stderr.getvalue()
|
|
||||||
sys.stderr.close()
|
|
||||||
sys.stderr = orig_stderr
|
|
||||||
return (stdout, stderr)
|
|
||||||
|
|
||||||
def test_update_without_path(self):
|
def test_update_without_path(self):
|
||||||
required = [
|
required = [
|
||||||
'.*?^usage: grafana-dashboards update \[-h\] path',
|
'.*?^usage: grafana-dashboards update \[-h\] path',
|
||||||
|
Loading…
x
Reference in New Issue
Block a user