Adds tuskar-load-role to load a single role and associated extra-data
This adds a stand-alone tuskar-load-role command which takes a name and path to the main file defining this role. If no name is provided this is deduced from the path (filename) as currently occurs with tuskar-load-roles. If a path is not specified then a SystemExit occurs. This also wires up the relative_path into the role creation (this is added to the model by the parent commit) Optionally you may specify the extra-data files that the given role uses as multiple '--extra-data' arguments to tuskar-load-role. Change-Id: I9c143afb52c43e4258c3a6797a9707c08d8dcdaf
This commit is contained in:
parent
2ddbb23c08
commit
cf604be22e
@ -28,6 +28,7 @@ console_scripts =
|
||||
tuskar-load-roles = tuskar.cmd.load_roles:main
|
||||
tuskar-load-seed = tuskar.cmd.load_seed:main
|
||||
tuskar-delete-roles = tuskar.cmd.delete_roles:main
|
||||
tuskar-load-role = tuskar.cmd.load_role:main
|
||||
|
||||
[build_sphinx]
|
||||
all_files = 1
|
||||
|
@ -27,7 +27,7 @@ from tuskar.storage.delete_roles import delete_roles
|
||||
def _print_names(message, names):
|
||||
print("{0}: \n {1}".format(message, '\n '.join(names)))
|
||||
|
||||
cfg.CONF.register_cli_opt(cfg.BoolOpt('dryrun', short='n', default=False))
|
||||
cfg.CONF.register_cli_opt(cfg.BoolOpt('dryrun', default=False))
|
||||
|
||||
cfg.CONF.register_cli_opt(cfg.ListOpt(
|
||||
'uuids', help='List of role uuid to delete'))
|
||||
|
58
tuskar/cmd/load_role.py
Normal file
58
tuskar/cmd/load_role.py
Normal file
@ -0,0 +1,58 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Copyright 2015 Red Hat
|
||||
# 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_role
|
||||
|
||||
|
||||
def _print_names(message, names):
|
||||
print("{0}: \n {1}".format(message, '\n '.join(names)))
|
||||
|
||||
cfg.CONF.register_cli_opt(cfg.StrOpt('name', short='n', dest='name'))
|
||||
cfg.CONF.register_cli_opt(cfg.StrOpt(
|
||||
'filepath', dest='file_path', short='f'))
|
||||
cfg.CONF.register_cli_opt(cfg.StrOpt('relative-path', dest='relative_path'))
|
||||
cfg.CONF.register_cli_opt(cfg.MultiStrOpt('extra-data', short='e'))
|
||||
|
||||
|
||||
def main(argv=None):
|
||||
if argv is None:
|
||||
argv = sys.argv
|
||||
|
||||
service.prepare_service(argv)
|
||||
if not cfg.CONF.file_path:
|
||||
sys.stderr.write("You must specify the path to the main template "
|
||||
"which defines this role.")
|
||||
sys.exit(1)
|
||||
|
||||
name = cfg.CONF.name if cfg.CONF.name else ''
|
||||
relative_path = cfg.CONF.relative_path if cfg.CONF.relative_path else None
|
||||
created, updated = load_role(name, cfg.CONF.file_path,
|
||||
extra_data=cfg.CONF.extra_data,
|
||||
relative_path=relative_path)
|
||||
|
||||
if len(created):
|
||||
_print_names("Created", created)
|
||||
|
||||
if len(updated):
|
||||
_print_names("Updated", updated)
|
@ -34,7 +34,7 @@ class BaseDriver(object):
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def create(self, store, name, contents):
|
||||
def create(self, store, name, contents, relative_path):
|
||||
"""Given the store, name and contents create a new file and return a
|
||||
`StoredFile` instance representing it.
|
||||
|
||||
@ -77,7 +77,7 @@ class BaseDriver(object):
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def update(self, store, uuid, contents):
|
||||
def update(self, store, uuid, contents, relative_path):
|
||||
"""Given the store, uuid, name and contents update the existing stored
|
||||
file and return an instance of StoredFile that reflects the updates.
|
||||
Either name and/or contents can be provided. If they are not then they
|
||||
|
@ -87,21 +87,23 @@ class SQLAlchemyDriver(BaseDriver):
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
def _create(self, store, name, contents, version):
|
||||
def _create(self, store, name, contents, version, relative_path=''):
|
||||
|
||||
stored_file = StoredFile(
|
||||
uuid=self._generate_uuid(),
|
||||
contents=contents,
|
||||
object_type=store.object_type,
|
||||
name=name,
|
||||
version=version
|
||||
version=version,
|
||||
relative_path=relative_path
|
||||
)
|
||||
|
||||
return self._upsert(store, stored_file)
|
||||
|
||||
def create(self, store, name, contents):
|
||||
def create(self, store, name, contents, relative_path=''):
|
||||
"""Given the store, name and contents create a new file and return a
|
||||
`StoredFile` instance representing it.
|
||||
`StoredFile` instance representing it. The optional relative_path
|
||||
is appended to the generated template directory structure.
|
||||
|
||||
Some of the stored items such as environment files do not have names.
|
||||
When working with these, name must be passed explicitly as None. This
|
||||
@ -116,6 +118,9 @@ class SQLAlchemyDriver(BaseDriver):
|
||||
:param contents: String containing the file contents
|
||||
:type contents: str
|
||||
|
||||
:param relative_path: String relative path to place the template under
|
||||
: type relative_path: str
|
||||
|
||||
:return: StoredFile instance containing the file metadata and contents
|
||||
:rtype: tuskar.storage.models.StoredFile
|
||||
"""
|
||||
@ -136,7 +141,7 @@ class SQLAlchemyDriver(BaseDriver):
|
||||
except UnknownName:
|
||||
pass
|
||||
|
||||
return self._create(store, name, contents, version)
|
||||
return self._create(store, name, contents, version, relative_path)
|
||||
|
||||
def _retrieve(self, object_type, uuid):
|
||||
|
||||
@ -172,7 +177,7 @@ class SQLAlchemyDriver(BaseDriver):
|
||||
stored_file = self._retrieve(store.object_type, uuid)
|
||||
return self._to_storage_model(store, stored_file)
|
||||
|
||||
def update(self, store, uuid, contents):
|
||||
def update(self, store, uuid, contents, relative_path=''):
|
||||
"""Given the store, uuid, name and contents update the existing stored
|
||||
file and return an instance of StoredFile that reflects the updates.
|
||||
Either name and/or contents can be provided. If they are not then they
|
||||
@ -201,10 +206,13 @@ class SQLAlchemyDriver(BaseDriver):
|
||||
|
||||
stored_file.contents = contents
|
||||
|
||||
stored_file.relative_path = relative_path if relative_path else None
|
||||
|
||||
if store.versioned:
|
||||
version = self._get_latest_version(store, stored_file.name) + 1
|
||||
return self._create(
|
||||
store, stored_file.name, stored_file.contents, version)
|
||||
store, stored_file.name, stored_file.contents, version,
|
||||
relative_path)
|
||||
|
||||
return self._upsert(store, stored_file)
|
||||
|
||||
|
@ -26,6 +26,7 @@ from tuskar.storage.stores import MasterSeedStore
|
||||
from tuskar.storage.stores import ResourceRegistryMappingStore
|
||||
from tuskar.storage.stores import ResourceRegistryStore
|
||||
from tuskar.storage.stores import TemplateExtraStore
|
||||
from tuskar.storage.stores import TemplateStore
|
||||
from tuskar.templates import parser
|
||||
|
||||
MASTER_SEED_NAME = '_master_seed'
|
||||
@ -47,6 +48,16 @@ def load_seed(seed_file, resource_registry_path):
|
||||
return created, updated
|
||||
|
||||
|
||||
def load_role(name, file_path, extra_data=None, relative_path=''):
|
||||
name = role_name_from_path(file_path) if (name == '') else name
|
||||
all_roles, created, updated = load_roles(
|
||||
roles=[], seed_file=None,
|
||||
resource_registry_path=None, role_extra=extra_data)
|
||||
process_role(file_path, name, TemplateStore(), all_roles, created,
|
||||
updated, relative_path)
|
||||
return created, updated
|
||||
|
||||
|
||||
def load_roles(roles, seed_file=None, resource_registry_path=None,
|
||||
role_extra=None):
|
||||
"""Given a list of roles files import them into the
|
||||
|
@ -21,24 +21,24 @@ def load_file(role_path):
|
||||
return role_file.read()
|
||||
|
||||
|
||||
def _create_or_update(name, contents, store=None):
|
||||
def _create_or_update(name, contents, store=None, relative_path=''):
|
||||
if store is None:
|
||||
store = TemplateStore()
|
||||
|
||||
try:
|
||||
role = store.retrieve_by_name(name)
|
||||
|
||||
if role.contents != contents:
|
||||
role = store.update(role.uuid, contents)
|
||||
role = store.update(role.uuid, contents, relative_path)
|
||||
|
||||
return False, role
|
||||
except UnknownName:
|
||||
return True, store.create(name, contents)
|
||||
return True, store.create(name, contents, relative_path)
|
||||
|
||||
|
||||
def process_role(role_path, role_name, store, all_roles, created, updated):
|
||||
def process_role(role_path, role_name, store, all_roles, created, updated,
|
||||
relative_path=''):
|
||||
contents = load_file(role_path)
|
||||
role_created, _ = _create_or_update(role_name, contents, store)
|
||||
role_created, _ = _create_or_update(role_name, contents, store,
|
||||
relative_path)
|
||||
|
||||
if all_roles is not None:
|
||||
all_roles.append(role_name)
|
||||
|
@ -75,7 +75,7 @@ class _BaseStore(object):
|
||||
"""
|
||||
return self._driver.retrieve(self, uuid)
|
||||
|
||||
def update(self, uuid, contents):
|
||||
def update(self, uuid, contents, relative_path=''):
|
||||
"""Given the uuid and contents update the existing stored file
|
||||
and return an instance of StoredFile that reflects the updates.
|
||||
|
||||
@ -91,7 +91,7 @@ class _BaseStore(object):
|
||||
:raises: tuskar.storage.exceptions.UnknownUUID if the UUID can't be
|
||||
found
|
||||
"""
|
||||
return self._driver.update(self, uuid, contents)
|
||||
return self._driver.update(self, uuid, contents, relative_path)
|
||||
|
||||
def delete(self, uuid):
|
||||
"""Delete the file in this store with the matching uuid.
|
||||
@ -120,7 +120,7 @@ class _NamedStore(_BaseStore):
|
||||
where required.
|
||||
"""
|
||||
|
||||
def create(self, name, contents):
|
||||
def create(self, name, contents, relative_path=''):
|
||||
"""Given the name and contents create a new file and return a
|
||||
`StoredFile` instance representing it.
|
||||
|
||||
@ -139,7 +139,7 @@ class _NamedStore(_BaseStore):
|
||||
:raises: tuskar.storage.exceptions.NameAlreadyUsed if the name is
|
||||
already in use
|
||||
"""
|
||||
return self._driver.create(self, name, contents)
|
||||
return self._driver.create(self, name, contents, relative_path)
|
||||
|
||||
def retrieve_by_name(self, name):
|
||||
"""Returns the stored file for a given store that matches the provided
|
||||
|
@ -15,6 +15,7 @@
|
||||
from mock import call
|
||||
from mock import patch
|
||||
|
||||
from tuskar.cmd import load_role
|
||||
from tuskar.cmd import load_roles
|
||||
from tuskar.cmd import load_seed
|
||||
from tuskar.tests.base import TestCase
|
||||
@ -70,3 +71,40 @@ resource_registry:
|
||||
|
||||
self.assertEqual([call('Created', expected_created)],
|
||||
mock_print.call_args_list)
|
||||
|
||||
@patch('tuskar.storage.load_utils.load_file', return_value="YAML")
|
||||
@patch('tuskar.cmd.load_role._print_names')
|
||||
def test_load_role(self, mock_print, mock_read):
|
||||
main_args = (" tuskar-load-role -n Compute"
|
||||
" --filepath /path/to/puppet/compute-puppet.yaml "
|
||||
" --extra-data /path/to/puppet/hieradata/compute.yaml "
|
||||
" --extra-data /path/to/puppet/hieradata/common.yaml ")
|
||||
expected_res = ['extra_compute_yaml', 'extra_common_yaml', 'Compute']
|
||||
|
||||
load_role.main(argv=(main_args).split())
|
||||
|
||||
self.assertEqual([call('Created', expected_res)],
|
||||
mock_print.call_args_list)
|
||||
|
||||
@patch('tuskar.storage.load_utils.load_file', return_value="YAML")
|
||||
@patch('tuskar.cmd.load_role._print_names')
|
||||
def test_load_role_no_name(self, mock_print, mock_read):
|
||||
main_args = (" tuskar-load-role"
|
||||
" -f /path/to/puppet/compute-puppet.yaml "
|
||||
" --extra-data /path/to/puppet/hieradata/compute.yaml "
|
||||
" --extra-data /path/to/puppet/hieradata/common.yaml ")
|
||||
expected_res = ['extra_compute_yaml', 'extra_common_yaml',
|
||||
'compute-puppet']
|
||||
|
||||
load_role.main(argv=(main_args).split())
|
||||
|
||||
self.assertEqual([call('Created', expected_res)],
|
||||
mock_print.call_args_list)
|
||||
|
||||
@patch('tuskar.storage.load_utils.load_file', return_value="YAML")
|
||||
@patch('tuskar.cmd.load_role._print_names')
|
||||
def test_load_role_no_path(self, mock_print, mock_read):
|
||||
main_args = (" tuskar-load-role"
|
||||
" --extra-data /path/to/puppet/hieradata/compute.yaml "
|
||||
" --extra-data /path/to/puppet/hieradata/common.yaml ")
|
||||
self.assertRaises(SystemExit, load_role.main, (main_args.split()))
|
||||
|
@ -41,7 +41,8 @@ class BaseStoreTests(TestCase):
|
||||
uuid = "d131dd02c5e6eec4"
|
||||
contents = "Stored contents"
|
||||
self.store.update(uuid, contents)
|
||||
self.driver.update.assert_called_once_with(self.store, uuid, contents)
|
||||
self.driver.update.assert_called_once_with(self.store, uuid,
|
||||
contents, "")
|
||||
|
||||
def test_retrieve(self):
|
||||
uuid = "d131dd02c5e6eec5"
|
||||
@ -70,13 +71,14 @@ class NamedStoreTests(TestCase):
|
||||
name = "Object name"
|
||||
self.store.create(name, "My contents")
|
||||
self.driver.create.assert_called_once_with(
|
||||
self.store, name, "My contents")
|
||||
self.store, name, "My contents", '')
|
||||
|
||||
def test_update(self):
|
||||
uuid = "d131dd02c5e6eec4"
|
||||
contents = "Stored contents"
|
||||
self.store.update(uuid, contents)
|
||||
self.driver.update.assert_called_once_with(self.store, uuid, contents)
|
||||
self.driver.update.assert_called_once_with(self.store, uuid,
|
||||
contents, '')
|
||||
|
||||
def test_retrieve(self):
|
||||
uuid = "d131dd02c5e6eec5"
|
||||
@ -110,13 +112,14 @@ class VersionedStoreTests(TestCase):
|
||||
name = "Object name"
|
||||
self.store.create(name, "My contents")
|
||||
self.driver.create.assert_called_once_with(
|
||||
self.store, name, "My contents")
|
||||
self.store, name, "My contents", "")
|
||||
|
||||
def test_update(self):
|
||||
uuid = "d131dd02c5e6eec4"
|
||||
contents = "Stored contents"
|
||||
self.store.update(uuid, contents)
|
||||
self.driver.update.assert_called_once_with(self.store, uuid, contents)
|
||||
self.driver.update.assert_called_once_with(self.store, uuid,
|
||||
contents, "")
|
||||
|
||||
def test_retrieve(self):
|
||||
uuid = "d131dd02c5e6eec5"
|
||||
@ -156,8 +159,9 @@ class TemplateStoreTests(TestCase):
|
||||
def test_create(self):
|
||||
name = "template name"
|
||||
contents = "template contents"
|
||||
self.store.create(name, contents)
|
||||
self.driver.create.assert_called_once_with(self.store, name, contents)
|
||||
self.store.create(name, contents, "")
|
||||
self.driver.create.assert_called_once_with(self.store, name,
|
||||
contents, "")
|
||||
|
||||
|
||||
class TemplateExtraStoreTests(TestCase):
|
||||
@ -172,7 +176,8 @@ class TemplateExtraStoreTests(TestCase):
|
||||
name = "template_name_name"
|
||||
contents = "template extra contents"
|
||||
self.store.create(name, contents)
|
||||
self.driver.create.assert_called_once_with(self.store, name, contents)
|
||||
self.driver.create.assert_called_once_with(self.store, name,
|
||||
contents, "")
|
||||
|
||||
|
||||
class MasterSeedStoreTests(TestCase):
|
||||
@ -187,7 +192,8 @@ class MasterSeedStoreTests(TestCase):
|
||||
name = "master seed"
|
||||
contents = "seed contents"
|
||||
self.store.create(name, contents)
|
||||
self.driver.create.assert_called_once_with(self.store, name, contents)
|
||||
self.driver.create.assert_called_once_with(self.store, name,
|
||||
contents, "")
|
||||
|
||||
def test_object_type(self):
|
||||
self.assertEqual(stores.MasterSeedStore.object_type, "master_seed")
|
||||
@ -261,7 +267,8 @@ class DeploymentPlanMockedTests(TestCase):
|
||||
self.driver.create.return_value = self._stored_file(name, contents)
|
||||
|
||||
result = self.store.create(name, 'Template UUID', 'Environment UUID')
|
||||
self.driver.create.assert_called_once_with(self.store, name, contents)
|
||||
self.driver.create.assert_called_once_with(self.store, name,
|
||||
contents, "")
|
||||
|
||||
self.assertEqual(result.name, name)
|
||||
|
||||
@ -285,7 +292,8 @@ class DeploymentPlanMockedTests(TestCase):
|
||||
'deployment_plan name', '')
|
||||
self.assertItemsEqual(self.environment_store.create.call_args_list, [])
|
||||
|
||||
self.driver.create.assert_called_once_with(self.store, name, contents)
|
||||
self.driver.create.assert_called_once_with(self.store, name,
|
||||
contents, "")
|
||||
|
||||
self.assertEqual(result.name, name)
|
||||
self.master_template_store.retrieve.assert_called_once_with('UUID1')
|
||||
@ -307,7 +315,8 @@ class DeploymentPlanMockedTests(TestCase):
|
||||
self.assertItemsEqual(
|
||||
self.master_template_store.create.call_args_list, [])
|
||||
|
||||
self.driver.create.assert_called_once_with(self.store, name, contents)
|
||||
self.driver.create.assert_called_once_with(self.store, name,
|
||||
contents, "")
|
||||
|
||||
self.assertEqual(result.name, name)
|
||||
self.master_template_store.retrieve.assert_called_once_with(
|
||||
|
Loading…
x
Reference in New Issue
Block a user