From c09b86c2135f0e9f9d547177a8c35e8f7e314552 Mon Sep 17 00:00:00 2001 From: Bryan Strassner Date: Mon, 17 Jul 2017 17:45:02 -0500 Subject: [PATCH] Add commands to support parts in the cli Updated the setup of the session for the api client to use more standard fields and make less assumptions. Moved url parsing to cli code --- drydock_provisioner/cli/commands.py | 22 +++-- drydock_provisioner/cli/part/actions.py | 87 +++++++++++++++++++ drydock_provisioner/cli/part/commands.py | 81 +++++++++++++++++ drydock_provisioner/drydock_client/session.py | 12 ++- setup.py | 1 + 5 files changed, 192 insertions(+), 11 deletions(-) create mode 100644 drydock_provisioner/cli/part/actions.py create mode 100644 drydock_provisioner/cli/part/commands.py diff --git a/drydock_provisioner/cli/commands.py b/drydock_provisioner/cli/commands.py index 7448436b..75e4c625 100644 --- a/drydock_provisioner/cli/commands.py +++ b/drydock_provisioner/cli/commands.py @@ -11,15 +11,17 @@ # 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. -""" The entrypoint for the cli commands +""" The entry point for the cli commands """ import os import logging -import click +from urllib.parse import urlparse +import click from drydock_provisioner.drydock_client.session import DrydockSession from drydock_provisioner.drydock_client.client import DrydockClient from .design import commands as design +from .part import commands as part @click.group() @click.option('--debug/--no-debug', @@ -43,12 +45,12 @@ def drydock(ctx, debug, token, url): ctx.obj['DEBUG'] = debug if not token: - ctx.fail("Error: Token must be specified either by " - "--token or DD_TOKEN from the environment") + ctx.fail('Error: Token must be specified either by ' + '--token or DD_TOKEN from the environment') if not url: - ctx.fail("Error: URL must be specified either by " - "--url or DD_URL from the environment") + ctx.fail('Error: URL must be specified either by ' + '--url or DD_URL from the environment') # setup logging for the CLI # Setup root logger @@ -63,7 +65,13 @@ def drydock(ctx, debug, token, url): logger.debug('logging for cli initialized') # setup the drydock client using the passed parameters. - ctx.obj['CLIENT'] = DrydockClient(DrydockSession(host=url, + url_parse_result = urlparse(url) + logger.debug(url_parse_result) + if not url_parse_result.scheme: + ctx.fail('URL must specify a scheme and hostname, optionally a port') + ctx.obj['CLIENT'] = DrydockClient(DrydockSession(scheme=url_parse_result.scheme, + host=url_parse_result.netloc, token=token)) drydock.add_command(design.design) +drydock.add_command(part.part) diff --git a/drydock_provisioner/cli/part/actions.py b/drydock_provisioner/cli/part/actions.py new file mode 100644 index 00000000..1a189e19 --- /dev/null +++ b/drydock_provisioner/cli/part/actions.py @@ -0,0 +1,87 @@ +# Copyright 2017 AT&T Intellectual Property. All other 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. +""" Actions related to part command +""" + + +from drydock_provisioner.cli.action import CliAction + +class PartBase(CliAction): # pylint: disable=too-few-public-methods + """ base class to set up part actions requiring a design_id + """ + def __init__(self, api_client, design_id): + super().__init__(api_client) + self.design_id = design_id + self.logger.debug('Initializing a Part action with design_id=%s', design_id) + +class PartList(PartBase): # pylint: disable=too-few-public-methods + """ Action to list parts of a design + """ + def __init__(self, api_client, design_id): + """ + :param DrydockClient api_client: The api client used for invocation. + :param string design_id: The UUID of the design for which to list parts + """ + super().__init__(api_client, design_id) + self.logger.debug('PartList action initialized') + + def invoke(self): + #TODO: change the api call + #return self.api_client.get_design_ids() + pass + +class PartCreate(PartBase): # pylint: disable=too-few-public-methods + """ Action to create parts of a design + """ + def __init__(self, api_client, design_id, yaml): + """ + :param DrydockClient api_client: The api client used for invocation. + :param string design_id: The UUID of the design for which to create a part + :param yaml: The file containing the specification of the part + """ + super().__init__(api_client, design_id) + self.yaml = yaml + self.logger.debug('PartCreate action initialized with yaml=%s', yaml[:100]) + + def invoke(self): + return self.api_client.load_parts(self.design_id, self.yaml) + +class PartShow(PartBase): # pylint: disable=too-few-public-methods + """ Action to show a part of a design. + """ + def __init__(self, api_client, design_id, kind, key, source='designed'): + """ + :param DrydockClient api_client: The api client used for invocation. + :param string design_id: the UUID of the design containing this part + :param string kind: the string represesnting the 'kind' of the document to return + :param string key: the string representing the key of the document to return. + :param string source: 'designed' (default) if this is the designed version, + 'compiled' if the compiled version (after merging) + """ + super().__init__(api_client, design_id) + self.kind = kind + self.key = key + self.source = source + self.logger.debug('DesignShow action initialized for design_id=%s,' + ' kind=%s, key=%s, source=%s', + design_id, + kind, + key, + source) + + def invoke(self): + return self.api_client.get_part(design_id=self.design_id, + kind=self.kind, + key=self.key, + source=self.source) diff --git a/drydock_provisioner/cli/part/commands.py b/drydock_provisioner/cli/part/commands.py new file mode 100644 index 00000000..86d3251b --- /dev/null +++ b/drydock_provisioner/cli/part/commands.py @@ -0,0 +1,81 @@ +# Copyright 2017 AT&T Intellectual Property. All other 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. +""" cli.part.commands + Contains commands related to parts of designs +""" +import click + +from drydock_provisioner.cli.part.actions import PartList +from drydock_provisioner.cli.part.actions import PartShow +from drydock_provisioner.cli.part.actions import PartCreate + +@click.group() +@click.option('--design-id', + '-d', + help='The id of the design containing the target parts') +@click.pass_context +def part(ctx, design_id=None): + """ Drydock part commands + """ + if not design_id: + ctx.fail('Error: Design id must be specified using --design-id') + + ctx.obj['DESIGN_ID'] = design_id + +@part.command(name='create') +@click.option('--file', + '-f', + help='The file name containing the part to create') +@click.pass_context +def part_create(ctx, file=None): + """ Create a part + """ + if not file: + ctx.fail('A file to create a part is required using --file') + + with open(file, 'r') as file_input: + file_contents = file_input.read() + # here is where some yaml validation could be done + click.echo(PartCreate(ctx.obj['CLIENT'], + design_id=ctx.obj['DESIGN_ID'], + yaml=file_contents).invoke()) + +@part.command(name='list') +@click.pass_context +def part_list(ctx): + """ List parts of a design + """ + click.echo(PartList(ctx.obj['CLIENT'], design_id=ctx.obj['DESIGN_ID']).invoke()) + +@part.command(name='show') +@click.option('--source', + '-s', + help='designed | compiled') +@click.option('--kind', + '-k', + help='The kind value of the document to show') +@click.option('--key', + '-i', + help='The key value of the document to show') +@click.pass_context +def part_show(ctx, source, kind, key): + """ show a part of a design + """ + if not kind: + ctx.fail('The kind must be specified by --kind') + + if not key: + ctx.fail('The key must be specified by --key') + + click.echo(PartShow(ctx.obj['CLIENT'], design_id=ctx.obj['DESIGN_ID'], kind=kind, key=key, source=source).invoke()) diff --git a/drydock_provisioner/drydock_client/session.py b/drydock_provisioner/drydock_client/session.py index 174a6670..28c3fe37 100644 --- a/drydock_provisioner/drydock_client/session.py +++ b/drydock_provisioner/drydock_client/session.py @@ -23,15 +23,19 @@ class DrydockSession(object): :param string marker: (optional) external context marker """ - def __init__(self, host, *, port=None, token=None, marker=None): + def __init__(self, host, *, port=None, scheme='http', token=None, marker=None): self.__session = requests.Session() self.__session.headers.update({'X-Auth-Token': token, 'X-Context-Marker': marker}) self.host = host - self.port = port + self.scheme = scheme + if port: - self.base_url = "http://%s:%d/api/" % (host, port) + self.port = port + self.base_url = "%s://%s:%s/api/" % (self.scheme, self.host, self.port) else: - self.base_url = "http://%s/api/" % (host) + #assume default port for scheme + self.base_url = "%s://%s/api/" % (self.scheme, self.host) + self.token = token self.marker = marker diff --git a/setup.py b/setup.py index 9b49a6a3..227af94e 100644 --- a/setup.py +++ b/setup.py @@ -43,6 +43,7 @@ setup(name='drydock_provisioner', 'drydock_provisioner.control', 'drydock_provisioner.cli', 'drydock_provisioner.cli.design', + 'drydock_provisioner.cli.part', 'drydock_provisioner.drydock_client'], install_requires=[ 'PyYAML',