From 5f1df103695c0182d83ebd11134b6d05e2c80cca Mon Sep 17 00:00:00 2001 From: Dougal Matthews Date: Mon, 11 Aug 2014 10:32:37 +0100 Subject: [PATCH] Tuskar command to import a set of roles This change adds a command to import a set of roles into the Tuskar storage. This will typically be ran during the installation and setup to provide a set of initial roles. Change-Id: I3395363c709c89072025083ca3da3189aae9e5d9 --- setup.cfg | 1 + tuskar/cmd/load_roles.py | 56 +++++++++++++ tuskar/storage/load_roles.py | 104 ++++++++++++++++++++++++ tuskar/tests/cmd/test_load_roles.py | 36 ++++++++ tuskar/tests/storage/test_load_roles.py | 104 ++++++++++++++++++++++++ 5 files changed, 301 insertions(+) create mode 100644 tuskar/cmd/load_roles.py create mode 100644 tuskar/storage/load_roles.py create mode 100644 tuskar/tests/cmd/test_load_roles.py create mode 100644 tuskar/tests/storage/test_load_roles.py diff --git a/setup.cfg b/setup.cfg index 8334ca8d..0272ca3f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -26,6 +26,7 @@ packages = console_scripts = tuskar-api = tuskar.cmd.api:main tuskar-dbsync = tuskar.cmd.dbsync:main + tuskar-load-roles = tuskar.cmd.load_roles:main [build_sphinx] all_files = 1 diff --git a/tuskar/cmd/load_roles.py b/tuskar/cmd/load_roles.py new file mode 100644 index 00000000..5a0b669e --- /dev/null +++ b/tuskar/cmd/load_roles.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python +# +# Copyright 2013 Hewlett-Packard Development Company, L.P. +# All Rights Reserved. +# +# 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 __future__ import print_function + +import sys + +from oslo.config import cfg + +from tuskar.common import service +from tuskar.storage.load_roles import load_roles + + +def _print_names(message, names): + print("{0}: \n {1}".format(message, '\n '.join(names))) + + +cfg.CONF.register_cli_opt(cfg.BoolOpt('dry-run')) +cfg.CONF.register_cli_opt(cfg.StrOpt('directory', positional=True)) + + +def main(argv=None): + + if argv is None: + argv = sys.argv + + service.prepare_service(argv) + + all_roles, created, updated = load_roles(cfg.CONF.directory, + cfg.CONF.dry_run) + + if len(created): + _print_names("Created", created) + + if len(updated): + _print_names("Updated", updated) + + if not cfg.CONF.dry_run: + print("Imported {0} roles".format(len(all_roles))) + else: + _print_names("Found", all_roles) + print("Imported 0 roles") diff --git a/tuskar/storage/load_roles.py b/tuskar/storage/load_roles.py new file mode 100644 index 00000000..8e7f998d --- /dev/null +++ b/tuskar/storage/load_roles.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python +# +# Copyright 2013 Hewlett-Packard Development Company, L.P. +# All Rights Reserved. +# +# 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 __future__ import print_function + +from os import listdir +from os import path + +from tuskar.storage.exceptions import UnknownName +from tuskar.storage.stores import TemplateStore + + +def _list_roles(directory): + """Scan a directory and yield a tuple for all the roles containing the + role name and the full path to the role. + """ + + if not path.isdir(directory): + raise ValueError("The given path is not a valid directory.") + + directory = path.abspath(directory) + + for filename in listdir(directory): + + if not filename.endswith("yaml") and not filename.endswith("yml"): + continue + + yield filename, path.join(directory, filename) + + +def _read_role(role_path): + + with open(role_path) as role_file: + return role_file.read() + + +def _create_or_update(name, contents, store=None): + + if store is None: + store = TemplateStore() + + try: + role = store.retrieve_by_name(name) + + if role.contents != contents: + role = store.update(role.uuid, contents) + + return False, role + except UnknownName: + return True, store.create(name, contents) + + +def load_roles(directory, dry_run=False): + """Given a directory path, import the YAML role files into the + TemplateStore. When dry_run=True is passed, run through the roles but don't + add any to the store. + + The returned tuple contains all the role names and then the names split + over where were created and updated. On a dry run the first item will + contain all of the roles found while the second two will be empty lists as + no files were updated or created. + + :param directory: Directory name containing the roles + :type directory: str + + :return: Summary of the results as a tuple with the total count and then + the names of the created and updated roles. + :rtype: tuple(list, list, list) + """ + + all_roles, created, updated = [], [], [] + + roles = _list_roles(directory) + + for name, role_path in roles: + + contents = _read_role(role_path) + all_roles.append(name) + + if dry_run: + continue + + role_created, _ = _create_or_update(name, contents) + + if role_created: + created.append(name) + else: + updated.append(name) + + return all_roles, created, updated diff --git a/tuskar/tests/cmd/test_load_roles.py b/tuskar/tests/cmd/test_load_roles.py new file mode 100644 index 00000000..25fe7cbe --- /dev/null +++ b/tuskar/tests/cmd/test_load_roles.py @@ -0,0 +1,36 @@ +# -*- encoding: utf-8 -*- +# +# 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 mock import call +from mock import patch + +from tuskar.cmd import load_roles +from tuskar.tests.base import TestCase + + +class LoadRoleTests(TestCase): + + @patch('tuskar.storage.load_roles._list_roles', + return_value=[['role_name.yaml', '/path/role_name.yaml']]) + @patch('tuskar.storage.load_roles._read_role', return_value="YAML") + @patch('tuskar.cmd.load_roles._print_names') + def test_main(self, mock_print, mock_read, mock_list): + + # test + load_roles.main("/path/".split()) + + # verify + self.assertEqual([ + call('Created', ['role_name.yaml']) + ], mock_print.call_args_list) diff --git a/tuskar/tests/storage/test_load_roles.py b/tuskar/tests/storage/test_load_roles.py new file mode 100644 index 00000000..cc784b4d --- /dev/null +++ b/tuskar/tests/storage/test_load_roles.py @@ -0,0 +1,104 @@ +# -*- encoding: utf-8 -*- +# +# 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 os import path +from shutil import rmtree +from tempfile import mkdtemp + +from tuskar.storage.load_roles import _create_or_update +from tuskar.storage.load_roles import _list_roles +from tuskar.storage.load_roles import load_roles +from tuskar.storage.stores import TemplateStore +from tuskar.tests.base import TestCase + + +class LoadRoleTests(TestCase): + + def setUp(self): + super(LoadRoleTests, self).setUp() + self.directory = mkdtemp() + + self.store = TemplateStore() + + roles = ['role1.yaml', 'rubbish', 'role2.yml'] + for role in roles: + self._create_role(role) + + def tearDown(self): + super(LoadRoleTests, self).tearDown() + rmtree(self.directory) + + def _create_role(self, role): + """Create a mock role file which simple contains it's own name as + the file contents. + """ + with open(path.join(self.directory, role), 'w') as f: + f.write("CONTENTS FOR {0}".format(role)) + + def test_list_roles(self): + + # test + roles = sorted(_list_roles(self.directory)) + + # verify + self.assertEqual([ + ('role1.yaml', path.join(self.directory, "role1.yaml")), + ('role2.yml', path.join(self.directory, "role2.yml")), + ], roles) + + def test_list_roles_invalid(self): + + # setup + invalid_path = path.join(self.directory, "FAKEPATH/") + self.assertFalse(path.isdir(invalid_path)) + + # test + list_call = _list_roles(invalid_path) + + # verify + self.assertRaises(ValueError, list, list_call) + + def test_dry_run(self): + + # test + total, created, updated = load_roles( + self.directory, dry_run=True) + + # verify + self.assertEqual(['role1.yaml', 'role2.yml'], sorted(total)) + self.assertEqual([], created) + self.assertEqual([], updated) + + def test_import(self): + + # test + total, created, updated = load_roles(self.directory) + + # verify + self.assertEqual(['role1.yaml', 'role2.yml'], sorted(total)) + self.assertEqual(['role1.yaml', 'role2.yml'], sorted(created)) + self.assertEqual([], updated) + + def test_import_update(self): + + # setup + _create_or_update("role2.yml", "contents") + + # test + total, created, updated = load_roles(self.directory) + + # verify + self.assertEqual(['role1.yaml', 'role2.yml'], sorted(total)) + self.assertEqual(['role1.yaml', ], created) + self.assertEqual(['role2.yml', ], updated)