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:
marios 2015-03-20 14:01:21 +02:00 committed by Jeff Peeler
parent 2ddbb23c08
commit cf604be22e
10 changed files with 158 additions and 33 deletions

View File

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

View File

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

View File

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

View File

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

View 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

View File

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

View File

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

View File

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

View File

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