Resource definition operations handled in the fuel2
Common part of implementation for operations extracted to cli.base. Change-Id: Iaad6e1e4655e5e60b7a97af4d6b07528b6579e65
This commit is contained in:
parent
76867c6aa4
commit
a175c31291
10
setup.cfg
10
setup.cfg
|
@ -62,6 +62,11 @@ tuning_box.cli =
|
|||
comp_show = tuning_box.cli.components:ShowComponent
|
||||
comp_delete = tuning_box.cli.components:DeleteComponent
|
||||
comp_update = tuning_box.cli.components:UpdateComponent
|
||||
def_create = tuning_box.cli.resource_definitions:CreateResourceDefinition
|
||||
def_list = tuning_box.cli.resource_definitions:ListResourceDefinitions
|
||||
def_show = tuning_box.cli.resource_definitions:ShowResourceDefinition
|
||||
def_delete = tuning_box.cli.resource_definitions:DeleteResourceDefinition
|
||||
def_update = tuning_box.cli.resource_definitions:UpdateResourceDefinition
|
||||
fuelclient =
|
||||
config_get = tuning_box.fuelclient:Get
|
||||
config_set = tuning_box.fuelclient:Set
|
||||
|
@ -76,6 +81,11 @@ fuelclient =
|
|||
config_comp_show = tuning_box.fuelclient:ShowComponent
|
||||
config_comp_delete = tuning_box.fuelclient:DeleteComponent
|
||||
config_comp_update = tuning_box.fuelclient:UpdateComponent
|
||||
config_def_create = tuning_box.fuelclient:CreateResourceDefinition
|
||||
config_def_list = tuning_box.fuelclient:ListResourceDefinitions
|
||||
config_def_show = tuning_box.fuelclient:ShowResourceDefinition
|
||||
config_def_delete = tuning_box.fuelclient:DeleteResourceDefinition
|
||||
config_def_update = tuning_box.fuelclient:UpdateResourceDefinition
|
||||
console_scripts =
|
||||
tuningbox_db_upgrade = tuning_box.migration:upgrade
|
||||
tuningbox_db_downgrade = tuning_box.migration:downgrade
|
||||
|
|
|
@ -15,6 +15,10 @@ import json
|
|||
import yaml
|
||||
|
||||
from cliff import command
|
||||
from cliff import lister
|
||||
from cliff import show
|
||||
from fuelclient.cli import error as fc_error
|
||||
from fuelclient.common import data_utils
|
||||
import six
|
||||
|
||||
|
||||
|
@ -67,6 +71,18 @@ class BaseCommand(command.Command):
|
|||
six.text_type(param).split(','))
|
||||
return list(six.moves.map(cast_to, result))
|
||||
|
||||
def read_json(self):
|
||||
return json.load(self.app.stdin)
|
||||
|
||||
def read_yaml(self):
|
||||
docs_gen = yaml.safe_load_all(self.app.stdin)
|
||||
doc = next(docs_gen)
|
||||
guard = object()
|
||||
if next(docs_gen, guard) is not guard:
|
||||
self.app.stderr.write("Warning: will use only first "
|
||||
"document from YAML stream")
|
||||
return doc
|
||||
|
||||
|
||||
class BaseOneCommand(BaseCommand):
|
||||
|
||||
|
@ -106,6 +122,28 @@ class BaseDeleteCommand(BaseOneCommand):
|
|||
return self.get_deletion_message(parsed_args)
|
||||
|
||||
|
||||
class BaseListCommand(BaseCommand, lister.Lister):
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
result = self.get_client().get(self.base_url)
|
||||
try:
|
||||
data = data_utils.get_display_data_multi(self.columns, result)
|
||||
return self.columns, data
|
||||
except fc_error.BadDataException:
|
||||
return zip(*result.items())
|
||||
|
||||
|
||||
class BaseShowCommand(BaseOneCommand, show.ShowOne):
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
result = self.get_client().get(self.get_url(parsed_args))
|
||||
try:
|
||||
data = data_utils.get_display_data_single(self.columns, result)
|
||||
return self.columns, data
|
||||
except fc_error.BadDataException:
|
||||
return zip(*result.items())
|
||||
|
||||
|
||||
class FormattedCommand(BaseCommand):
|
||||
format_choices = ('json', 'yaml', 'plain')
|
||||
|
||||
|
|
|
@ -10,10 +10,7 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from cliff import lister
|
||||
from cliff import show
|
||||
from fuelclient.cli import error as fc_error
|
||||
from fuelclient.common import data_utils
|
||||
|
||||
from tuning_box.cli import base
|
||||
|
||||
|
@ -24,15 +21,16 @@ class ComponentsCommand(base.BaseCommand):
|
|||
columns = ('id', 'name', 'resource_definitions')
|
||||
|
||||
|
||||
class ListComponents(ComponentsCommand, lister.Lister):
|
||||
class ListComponents(ComponentsCommand, base.BaseListCommand):
|
||||
pass
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
result = self.get_client().get(self.base_url)
|
||||
try:
|
||||
data = data_utils.get_display_data_multi(self.columns, result)
|
||||
return self.columns, data
|
||||
except fc_error.BadDataException:
|
||||
return zip(*result.items())
|
||||
|
||||
class ShowComponent(ComponentsCommand, base.BaseShowCommand):
|
||||
pass
|
||||
|
||||
|
||||
class DeleteComponent(ComponentsCommand, base.BaseDeleteCommand):
|
||||
pass
|
||||
|
||||
|
||||
class CreateComponent(ComponentsCommand, show.ShowOne):
|
||||
|
@ -49,26 +47,11 @@ class CreateComponent(ComponentsCommand, show.ShowOne):
|
|||
|
||||
def take_action(self, parsed_args):
|
||||
result = self.get_client().post(
|
||||
'/components', {'name': parsed_args.name,
|
||||
self.base_url, {'name': parsed_args.name,
|
||||
'resource_definitions': []})
|
||||
return zip(*result.items())
|
||||
|
||||
|
||||
class ShowComponent(ComponentsCommand, base.BaseOneCommand, show.ShowOne):
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
result = self.get_client().get(self.get_url(parsed_args))
|
||||
try:
|
||||
data = data_utils.get_display_data_single(self.columns, result)
|
||||
return self.columns, data
|
||||
except fc_error.BadDataException:
|
||||
return zip(*result.items())
|
||||
|
||||
|
||||
class DeleteComponent(ComponentsCommand, base.BaseDeleteCommand):
|
||||
pass
|
||||
|
||||
|
||||
class UpdateComponent(ComponentsCommand, base.BaseOneCommand):
|
||||
|
||||
def get_parser(self, *args, **kwargs):
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
# 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.
|
||||
|
||||
|
||||
class TuningBoxCliError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class IncompatibleParams(TuningBoxCliError):
|
||||
pass
|
||||
|
||||
|
||||
class UnsupportedDataType(TuningBoxCliError):
|
||||
pass
|
|
@ -0,0 +1,147 @@
|
|||
# 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 json
|
||||
import yaml
|
||||
|
||||
from cliff import show
|
||||
|
||||
from tuning_box.cli import base
|
||||
from tuning_box.cli import errors
|
||||
|
||||
|
||||
class ResourceDefinitionsCommand(base.BaseCommand):
|
||||
entity_name = 'resource_definition'
|
||||
base_url = '/resource_definitions'
|
||||
columns = ('id', 'name', 'component_id', 'content')
|
||||
|
||||
|
||||
class ListResourceDefinitions(ResourceDefinitionsCommand,
|
||||
base.BaseListCommand):
|
||||
pass
|
||||
|
||||
|
||||
class ShowResourceDefinition(ResourceDefinitionsCommand,
|
||||
base.BaseShowCommand):
|
||||
pass
|
||||
|
||||
|
||||
class DeleteResourceDefinition(ResourceDefinitionsCommand,
|
||||
base.BaseDeleteCommand):
|
||||
pass
|
||||
|
||||
|
||||
class ModifyResourceDefinitionCommand(ResourceDefinitionsCommand):
|
||||
|
||||
def get_parser(self, *args, **kwargs):
|
||||
parser = super(ModifyResourceDefinitionCommand, self).get_parser(
|
||||
*args, **kwargs)
|
||||
parser.add_argument(
|
||||
'-n', '--name',
|
||||
dest='name',
|
||||
type=str,
|
||||
help="Resource definition name"
|
||||
)
|
||||
parser.add_argument(
|
||||
'-i', '--component-id',
|
||||
dest='component_id',
|
||||
type=int,
|
||||
help="Component Id"
|
||||
)
|
||||
parser.add_argument(
|
||||
'-p', '--content',
|
||||
dest='content',
|
||||
type=str,
|
||||
help="Content to be set"
|
||||
)
|
||||
parser.add_argument(
|
||||
'-t', '--type',
|
||||
choices=('json', 'yaml'),
|
||||
help="Content type"
|
||||
)
|
||||
parser.add_argument(
|
||||
'-d', '--data-format',
|
||||
dest='data_format',
|
||||
choices=('json', 'yaml'),
|
||||
help="Format of data passed to stdin to be set to content"
|
||||
)
|
||||
return parser
|
||||
|
||||
def verify_arguments(self, parsed_args):
|
||||
if parsed_args.content is not None:
|
||||
if parsed_args.data_format is not None:
|
||||
raise errors.IncompatibleParams(
|
||||
"You shouldn't specify --data-format if you pass "
|
||||
"content in command line, specify --type instead."
|
||||
)
|
||||
elif parsed_args.type is None:
|
||||
raise errors.IncompatibleParams(
|
||||
"You should specify --type if you pass "
|
||||
"content in command line."
|
||||
)
|
||||
elif parsed_args.data_format is None:
|
||||
raise errors.IncompatibleParams(
|
||||
"You should specify --data-format for stdin data if you "
|
||||
"don't pass content in command line."
|
||||
)
|
||||
elif parsed_args.type is not None:
|
||||
raise errors.IncompatibleParams(
|
||||
"--type and --data-format parameters can't "
|
||||
"be used together."
|
||||
)
|
||||
|
||||
def get_content(self, parsed_args):
|
||||
type_ = parsed_args.type
|
||||
if type_ == 'json':
|
||||
return json.loads(parsed_args.content)
|
||||
elif type_ == 'yaml':
|
||||
return yaml.safe_load(parsed_args.content)
|
||||
elif type_ is None:
|
||||
data_format = parsed_args.data_format
|
||||
if data_format == 'json':
|
||||
return self.read_json()
|
||||
elif data_format == 'yaml':
|
||||
return self.read_yaml()
|
||||
raise errors.UnsupportedDataType(
|
||||
"Unsupported format: {0}".format(data_format)
|
||||
)
|
||||
raise errors.UnsupportedDataType("Unsupported type: {0}".format(type_))
|
||||
|
||||
|
||||
class CreateResourceDefinition(ModifyResourceDefinitionCommand, show.ShowOne):
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
self.verify_arguments(parsed_args)
|
||||
data = {
|
||||
'name': parsed_args.name,
|
||||
'component_id': parsed_args.component_id,
|
||||
'content': self.get_content(parsed_args)
|
||||
}
|
||||
result = self.get_client().post(self.base_url, data)
|
||||
return zip(*result.items())
|
||||
|
||||
|
||||
class UpdateResourceDefinition(ModifyResourceDefinitionCommand,
|
||||
base.BaseOneCommand):
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
data = {}
|
||||
if parsed_args.name is not None:
|
||||
data['name'] = parsed_args.name
|
||||
if parsed_args.component_id is not None:
|
||||
data['component_id'] = parsed_args.component_id
|
||||
if (parsed_args.content is not None
|
||||
or parsed_args.data_format is not None):
|
||||
data['content'] = self.get_content(parsed_args)
|
||||
|
||||
self.get_client().patch(self.get_url(parsed_args), data)
|
||||
return self.get_update_message(parsed_args)
|
|
@ -19,6 +19,7 @@ from tuning_box import cli
|
|||
from tuning_box.cli import base as cli_base
|
||||
from tuning_box.cli import components
|
||||
from tuning_box.cli import environments
|
||||
from tuning_box.cli import resource_definitions
|
||||
from tuning_box.cli import resources
|
||||
from tuning_box import client as tb_client
|
||||
|
||||
|
@ -96,6 +97,41 @@ class UpdateComponent(FuelBaseCommand, components.UpdateComponent):
|
|||
pass
|
||||
|
||||
|
||||
class CreateResourceDefinition(
|
||||
FuelBaseCommand,
|
||||
resource_definitions.CreateResourceDefinition
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
class ListResourceDefinitions(
|
||||
FuelBaseCommand,
|
||||
resource_definitions.ListResourceDefinitions
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
class ShowResourceDefinition(
|
||||
FuelBaseCommand,
|
||||
resource_definitions.ShowResourceDefinition
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
class DeleteResourceDefinition(
|
||||
FuelBaseCommand,
|
||||
resource_definitions.DeleteResourceDefinition
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
class UpdateResourceDefinition(
|
||||
FuelBaseCommand,
|
||||
resource_definitions.UpdateResourceDefinition
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
class Config(command.Command):
|
||||
def get_parser(self, *args, **kwargs):
|
||||
parser = super(Config, self).get_parser(*args, **kwargs)
|
||||
|
|
|
@ -0,0 +1,149 @@
|
|||
# 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 testscenarios
|
||||
import yaml
|
||||
|
||||
from tuning_box.tests.cli import _BaseCLITest
|
||||
|
||||
|
||||
class TestCreateResourceDefinition(testscenarios.WithScenarios, _BaseCLITest):
|
||||
scenarios = [
|
||||
(s[0],
|
||||
dict(zip(('args', 'expected_body', 'stdin'), s[1])))
|
||||
for s in [
|
||||
('json', ('def create -n n -i 1 -d json -f yaml',
|
||||
'content: {}\ncomponent_id: 1\nid: 1\nname: n\n',
|
||||
'{"a": 3}')),
|
||||
('yaml', ('def create -n n -i 1 -d yaml -f yaml',
|
||||
'content: {}\ncomponent_id: 1\nid: 1\nname: n\n',
|
||||
'a: b\n')),
|
||||
]
|
||||
]
|
||||
|
||||
args = None
|
||||
expected_body = None
|
||||
stdin = None
|
||||
|
||||
def test_post(self):
|
||||
url = self.BASE_URL + '/resource_definitions'
|
||||
self.req_mock.post(
|
||||
url,
|
||||
headers={'Content-Type': 'application/json'},
|
||||
json={'id': 1, 'component_id': 1, 'name': 'n', 'content': {}}
|
||||
)
|
||||
if self.stdin:
|
||||
self.cli.stdin.write(self.stdin)
|
||||
self.cli.stdin.seek(0)
|
||||
self.cli.run(self.args.split())
|
||||
self.assertEqual(
|
||||
yaml.safe_load(self.expected_body),
|
||||
yaml.safe_load(self.cli.stdout.getvalue())
|
||||
)
|
||||
|
||||
|
||||
class TestListResourceDefinitions(testscenarios.WithScenarios, _BaseCLITest):
|
||||
|
||||
scenarios = [
|
||||
(s[0], dict(zip(('mock_url', 'args', 'expected_result'), s[1])))
|
||||
for s in [
|
||||
('json', ('/resource_definitions', 'def list -f json', '[]')),
|
||||
('yaml', ('/resource_definitions', 'def list --format yaml',
|
||||
'[]\n')),
|
||||
]
|
||||
]
|
||||
mock_url = None
|
||||
args = None
|
||||
expected_result = None
|
||||
|
||||
def test_get(self):
|
||||
self.req_mock.get(
|
||||
self.BASE_URL + self.mock_url,
|
||||
headers={'Content-Type': 'application/json'},
|
||||
json=[],
|
||||
)
|
||||
self.cli.run(self.args.split())
|
||||
self.assertEqual(self.expected_result, self.cli.stdout.getvalue())
|
||||
|
||||
|
||||
class TestShowComponent(testscenarios.WithScenarios, _BaseCLITest):
|
||||
|
||||
scenarios = [
|
||||
(s[0], dict(zip(('mock_url', 'args', 'expected_result'), s[1])))
|
||||
for s in [
|
||||
('yaml', ('/resource_definitions/9', 'def show 9 -f yaml',
|
||||
'id: 1\nname: n\ncomponent_id: 2\ncontent: {}\n')),
|
||||
]
|
||||
]
|
||||
mock_url = None
|
||||
args = None
|
||||
expected_result = None
|
||||
|
||||
def test_get(self):
|
||||
self.req_mock.get(
|
||||
self.BASE_URL + self.mock_url,
|
||||
headers={'Content-Type': 'application/json'},
|
||||
json={'id': 1, 'name': 'n', 'component_id': 2, 'content': {}},
|
||||
)
|
||||
self.cli.run(self.args.split())
|
||||
self.assertEqual(self.expected_result, self.cli.stdout.getvalue())
|
||||
|
||||
|
||||
class TestDeleteComponent(testscenarios.WithScenarios, _BaseCLITest):
|
||||
|
||||
scenarios = [
|
||||
(s[0], dict(zip(('mock_url', 'args', 'expected_result'), s[1])))
|
||||
for s in [
|
||||
('', ('/resource_definitions/9', 'def delete 9', '')),
|
||||
]
|
||||
]
|
||||
mock_url = None
|
||||
args = None
|
||||
expected_result = None
|
||||
|
||||
def test_delete(self):
|
||||
self.req_mock.delete(
|
||||
self.BASE_URL + self.mock_url,
|
||||
headers={'Content-Type': 'application/json'}
|
||||
)
|
||||
self.cli.run(self.args.split())
|
||||
self.assertEqual(self.expected_result, self.cli.stdout.getvalue())
|
||||
|
||||
|
||||
class TestUpdateResourceDefinition(testscenarios.WithScenarios, _BaseCLITest):
|
||||
|
||||
scenarios = [
|
||||
(s[0], dict(zip(('args', 'expected_result', 'stdin'), s[1])))
|
||||
for s in [
|
||||
('no_data', ('def update 9', '')),
|
||||
('name', ('def update 9 -n comp_name', '', False)),
|
||||
('component_id', ('def update 9 -i 1', '', False)),
|
||||
('content', ('def update 9 -p "a" -t yaml', '', False)),
|
||||
('stdin_content', ('def update 9 -d yaml', '', 'a: b')),
|
||||
('stdin_content', ('def update 9 -d yaml', '', 'a: b')),
|
||||
]
|
||||
]
|
||||
args = None
|
||||
expected_result = None
|
||||
stdin = None
|
||||
|
||||
def test_update(self):
|
||||
self.req_mock.patch(
|
||||
self.BASE_URL + '/resource_definitions/9',
|
||||
headers={'Content-Type': 'application/json'},
|
||||
json={}
|
||||
)
|
||||
if self.stdin:
|
||||
self.cli.stdin.write(self.stdin)
|
||||
self.cli.stdin.seek(0)
|
||||
self.cli.run(self.args.split())
|
||||
self.assertEqual(self.expected_result, self.cli.stdout.getvalue())
|
Loading…
Reference in New Issue