From 0240a766543be05900246f741d24c37cf959f6c4 Mon Sep 17 00:00:00 2001 From: Arash Ghoreyshi Date: Wed, 12 Jun 2013 18:49:59 -0500 Subject: [PATCH 1/6] Add the intial version of the client command line tool Currently the only implemented action is 'create' Logging [temporarily] disabled in client.py since it interferes with the command line tool --- barbicanclient/client.py | 6 ++- cloudkeyp | 93 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+), 2 deletions(-) create mode 100755 cloudkeyp diff --git a/barbicanclient/client.py b/barbicanclient/client.py index 1020f5a4..fba568cd 100644 --- a/barbicanclient/client.py +++ b/barbicanclient/client.py @@ -15,8 +15,6 @@ from openstack.common.timeutils import parse_isotime from urlparse import urljoin -config.parse_args() -log.setup('barbicanclient') LOG = log.getLogger(__name__) @@ -285,3 +283,7 @@ class Connection(object): resp_body = '' return response.headers, resp_body + +if __name__ == '__main__': + config.parse_args() + log.setup('barbicanclient') diff --git a/cloudkeyp b/cloudkeyp new file mode 100755 index 00000000..1ff8a3b5 --- /dev/null +++ b/cloudkeyp @@ -0,0 +1,93 @@ +#!/usr/bin/env python + +import argparse + +from barbicanclient import client + + +class CloudKeyp: + def __init__(self): + self.parser = argparse.ArgumentParser(description='Access the Barbican' + ' key management sevice.') + self.subparsers = self.parser.add_subparsers(title='subcommands', + description= + 'Action to perform') + self.parser.add_argument('type', + choices=["order", "secret"], + help="Type to operate on") + self.parser.add_argument('--auth_endpoint', '-A', + help='The URL to authenticate against') + self.parser.add_argument('--user', '-U', + help='The user to authenticate as') + self.parser.add_argument('--password', '-P', + help='The API key or password to ' + 'authenticate with') + self.parser.add_argument('--tenant', '-T', + help='The tenant ID') + self.parser.add_argument('--endpoint', '-E', + help='The URL of the barbican server') + self.parser.add_argument('--token', '-K', + help='The authentication token') + self.add_create_args() + self.add_delete_args() + self.add_get_args() + self.add_list_args() + + self.execute() + + def add_create_args(self): + create_parser = self.subparsers.add_parser('create', help='Create a ' + 'secret or an order') + create_parser.add_argument('--mime_type', '-m', default='text/plain', + help='The MIME type used to fetch the secre' + 't (default: %(default)s)') + create_parser.add_argument('--name', '-n', help='A human-friendly name' + ' used only for reference') + create_parser.add_argument('--algorithm', '-a', help='The algorithm us' + 'ed only for reference') + create_parser.add_argument('--bit_length', '-b', help='The bit length ' + 'of the secret used only for reference') + create_parser.add_argument('--cypher_type', '-c', help='The cypher typ' + 'e used only for reference') + create_parser.add_argument('--plain_text', '-p', help='The unencrypted' + ' secret (only used for creating secrets)') + create_parser.add_argument('--expiration', '-e', help='Expiration time' + ' for the secret in ISO 8601 format') + create_parser.set_defaults(func=self.create) + + def add_delete_args(self): + delete_parser = self.subparsers.add_parser('delete', help='Delete a se' + 'cret or an order') + + def add_get_args(self): + get_parser = self.subparsers.add_parser('get', help='Retrieve a secret' + ' or an order') + + def add_list_args(self): + list_parser = self.subparsers.add_parser('list', + help='List secrets or orders') + + def create(self, args): + if self.args.type == 'secret': + secret = self.conn.create_secret(self.args.mime_type, + self.args.plain_text, self.args.name, + self.args.algorithm, self.args.bit_length, + self.args.cypher_type, self.args.expiration) + print secret.secret_ref + + def execute(self): + self.args = self.parser.parse_args() + self.conn = client.Connection(self.args.auth_endpoint, self.args.user, + self.args.password, self.args.tenant, + self.args.token, + endpoint=self.args.endpoint) + self.args.func(self.args) + print self.args + + +def main(): + CloudKeyp() + + +if __name__ == '__main__': + main() From a5d9ca5ec6626e70417c4298108bb83cad846073 Mon Sep 17 00:00:00 2001 From: Arash Ghoreyshi Date: Fri, 14 Jun 2013 13:05:33 -0500 Subject: [PATCH 2/6] Implement secret and order list paging Update the unit tests to reflect all changes made rename the command line tool to "keep" and add it to setup.py Add nosetest to tox --- barbicanclient/client.py | 80 ++++++++++++++++++++++++++++-------- cloudkeyp => keep | 13 +++--- setup.py | 3 +- tests/client_test.py | 88 ++++++++++++++++++++++++++++++++-------- tox.ini | 8 +++- 5 files changed, 151 insertions(+), 41 deletions(-) rename cloudkeyp => keep (90%) diff --git a/barbicanclient/client.py b/barbicanclient/client.py index fba568cd..3ea402c9 100644 --- a/barbicanclient/client.py +++ b/barbicanclient/client.py @@ -103,22 +103,46 @@ class Connection(object): self._token = value self._session.headers['X-Auth-Token'] = value - def list_secrets(self): + def list_secrets(self, limit=20, offset=0): """ - Returns the list of secrets for the auth'd tenant + Returns a tuple containing three items: a list of secrets pertaining + to the given offset and limit, a reference to the previous set of + secrets, and a reference to the next set of secrets. Either of the + references may be None. """ - LOG.debug(_("Listing secrets")) - href = "{0}/{1}?limit=100".format(self._tenant, self.SECRETS_PATH) + LOG.debug(_("Listing secrets - offset: {0}, limit: {1}").format(offset, + limit)) + href = "{0}/{1}?limit={2}&offset={3}".format(self._tenant, + self.SECRETS_PATH, + limit, offset) + return self.list_secrets_by_href(href) + + def list_secrets_by_href(self, href): + """ + Returns a tuple containing three items: a list of secrets pertaining + to the offset and limit within href, a reference to the previous set + of secrets, and a reference to the next set of secrets. Either of the + references may be None. + """ + LOG.debug(_("Listing secrets by href")) LOG.debug("href: {0}".format(href)) hdrs, body = self._perform_http(href=href, method='GET') LOG.debug(_("Response - headers: {0}\nbody: {1}").format(hdrs, body)) secrets_dict = body['secrets'] - secrets = [] - for s in secrets_dict: - secrets.append(Secret(self._conn, s)) + secrets = [Secret(self._conn, s) for s in secrets_dict] - return secrets + if 'previous' in body: + prev_ref = body['previous'] + else: + prev_ref = None + + if 'next' in body: + next_ref = body['next'] + else: + next_ref = None + + return secrets, prev_ref, next_ref def create_secret(self, mime_type, @@ -182,22 +206,46 @@ class Connection(object): LOG.debug(_("Response - headers: {0}\nbody: {1}").format(hdrs, body)) return body - def list_orders(self): + def list_orders(self, limit=20, offset=0): """ - Returns the list of orders + Returns a tuple containing three items: a list of orders pertaining + to the given offset and limit, a reference to the previous set of + orders, and a reference to the next set of orders. Either of the + references may be None. """ - LOG.debug(_("Listing orders")) - href = "{0}/{1}?limit=100".format(self._tenant, self.ORDERS_PATH) + LOG.debug(_("Listing orders - offset: {0}, limit: {1}").format(offset, + limit)) + href = "{0}/{1}?limit={2}&offset={3}".format(self._tenant, + self.ORDERS_PATH, + limit, offset) + return self.list_orders_by_href(href) + + def list_orders_by_href(self, href): + """ + Returns a tuple containing three items: a list of orders pertaining + to the offset and limit within href, a reference to the previous set + of orders, and a reference to the next set of orders. Either of the + references may be None. + """ + LOG.debug(_("Listing orders by href")) LOG.debug("href: {0}".format(href)) hdrs, body = self._perform_http(href=href, method='GET') LOG.debug(_("Response - headers: {0}\nbody: {1}").format(hdrs, body)) orders_dict = body['orders'] - orders = [] - for o in orders_dict: - orders.append(Order(self._conn, o)) + orders = [Order(self._conn, o) for o in orders_dict] - return orders + if 'previous' in body: + prev_ref = body['previous'] + else: + prev_ref = None + + if 'next' in body: + next_ref = body['next'] + else: + next_ref = None + + return orders, prev_ref, next_ref def create_order(self, mime_type, diff --git a/cloudkeyp b/keep similarity index 90% rename from cloudkeyp rename to keep index 1ff8a3b5..6d8e8188 100755 --- a/cloudkeyp +++ b/keep @@ -5,7 +5,7 @@ import argparse from barbicanclient import client -class CloudKeyp: +class Keep: def __init__(self): self.parser = argparse.ArgumentParser(description='Access the Barbican' ' key management sevice.') @@ -70,9 +70,12 @@ class CloudKeyp: def create(self, args): if self.args.type == 'secret': secret = self.conn.create_secret(self.args.mime_type, - self.args.plain_text, self.args.name, - self.args.algorithm, self.args.bit_length, - self.args.cypher_type, self.args.expiration) + self.args.plain_text, + self.args.name, + self.args.algorithm, + self.args.bit_length, + self.args.cypher_type, + self.args.expiration) print secret.secret_ref def execute(self): @@ -86,7 +89,7 @@ class CloudKeyp: def main(): - CloudKeyp() + Keep() if __name__ == '__main__': diff --git a/setup.py b/setup.py index 23a56464..6081cad9 100644 --- a/setup.py +++ b/setup.py @@ -54,5 +54,6 @@ setuptools.setup( 'Operating System :: OS Independent', 'Programming Language :: Python :: 2.7', 'Environment :: No Input/Output (Daemon)', - ] + ], + scripts = ['keep'] ) diff --git a/tests/client_test.py b/tests/client_test.py index 28f08f63..44205c3b 100644 --- a/tests/client_test.py +++ b/tests/client_test.py @@ -14,7 +14,7 @@ # limitations under the License. import json -import unittest +import unittest2 as unittest from mock import MagicMock @@ -146,10 +146,13 @@ class WhenTestingConnection(unittest.TestCase): body0 = {'secrets': []} secrets = [] self.request.return_value.content = json.dumps(body0) - self.assertTrue(self._are_equivalent(secrets, - self.connection.list_secrets())) + secret_list, prev_ref, next_ref = self.connection.list_secrets(0, 0) + self.assertTrue(self._are_equivalent(secrets, secret_list)) + self.assertIsNone(prev_ref) + self.assertIsNone(next_ref) def test_list_single_secret(self): + limit = 1 body1 = {'secrets': [{'status': 'ACTIVE', 'content_types': {'default': 'text/plain'}, 'updated': '2013-06-03T21:16:58.349230', @@ -162,13 +165,22 @@ class WhenTestingConnection(unittest.TestCase): '7-4090-bbef-bbb6025e5e7b', 'expiration': None, 'bit_length': None, - 'mime_type': 'text/plain'}]} + 'mime_type': 'text/plain'}], + 'next': "{0}/{1}?limit={2}&offset={2}".format(self.connection. + _tenant, + self.connection. + SECRETS_PATH, + limit)} secrets = [client.Secret(self.connection, body1['secrets'][0])] self.request.return_value.content = json.dumps(body1) - self.assertTrue(self._are_equivalent(secrets, - self.connection.list_secrets())) + secret_list, prev_ref, next_ref = self.connection.list_secrets(limit, + 0) + self.assertTrue(self._are_equivalent(secrets, secret_list)) + self.assertIsNone(prev_ref) + self.assertEqual(body1['next'], next_ref) def test_list_multiple_secrets(self): + limit = 2 body1 = {'secrets': [{'status': 'ACTIVE', 'content_types': {'default': 'text/plain'}, 'updated': '2013-06-03T21:16:58.349230', @@ -181,29 +193,43 @@ class WhenTestingConnection(unittest.TestCase): '7-4090-bbef-bbb6025e5e7b', 'expiration': None, 'bit_length': None, - 'mime_type': 'text/plain'}]} + 'mime_type': 'text/plain'}], + 'previous': "{0}/{1}?limit={2}&offset={2}".format( + self.connection._tenant, + self.connection. + SECRETS_PATH, + limit)} body2 = body1 body2['secrets'][0]['name'] = 'test_2' body2['secrets'][0]['secret_ref'] = 'http://localhost:9311/v1/No'\ + 'ne/secrets/bbd2036f-7307-'\ + '4090-bbef-bbb6025eabcd' + body2['previous'] = 'http://localhost:9311/v1/None/secrets/19106'\ + + 'b6e-4ef1-48d1-8950-170c1a5838e1' + body2['next'] = None secrets = [client.Secret(self.connection, b['secrets'][0]) for b in (body1, body2)] body2['secrets'].insert(0, body1['secrets'][0]) self.request.return_value.content = json.dumps(body2) - self.assertTrue(self._are_equivalent(secrets, - self.connection.list_secrets())) + secret_list, prev_ref, next_ref = self.connection.list_secrets(limit, + 1) + self.assertTrue(self._are_equivalent(secrets, secret_list)) + self.assertEqual(body2['previous'], prev_ref) + self.assertIsNone(next_ref) def test_list_no_orders(self): body0 = {'orders': []} orders = [] self.request.return_value.content = json.dumps(body0) - self.assertTrue(self._are_equivalent(orders, - self.connection.list_orders())) + order_list, prev_ref, next_ref = self.connection.list_orders(0, 0) + self.assertTrue(self._are_equivalent(orders, order_list)) + self.assertIsNone(prev_ref) + self.assertIsNone(next_ref) def test_list_single_order(self): + limit = 1 body1 = {'orders': [{'status': 'PENDING', 'updated': '2013-06-05T15:15:30.904760', 'created': '2013-06-05T15:15:30.904752', @@ -217,13 +243,21 @@ class WhenTestingConnection(unittest.TestCase): 'algorithm': None, 'expiration': None, 'bit_length': None, - 'mime_type': 'text/plain'}}]} + 'mime_type': 'text/plain'}}], + 'next': "{0}/{1}?limit={2}&offset={2}".format(self.connection. + _tenant, + self.connection. + ORDERS_PATH, + limit)} orders = [client.Order(self.connection, body1['orders'][0])] self.request.return_value.content = json.dumps(body1) - self.assertTrue(self._are_equivalent(orders, - self.connection.list_orders())) + order_list, prev_ref, next_ref = self.connection.list_orders(limit, 0) + self.assertTrue(self._are_equivalent(orders, order_list)) + self.assertIsNone(prev_ref) + self.assertEqual(body1['next'], next_ref) def test_list_multiple_orders(self): + limit = 2 body1 = {'orders': [{'status': 'PENDING', 'updated': '2013-06-05T15:15:30.904760', 'created': '2013-06-05T15:15:30.904752', @@ -237,19 +271,34 @@ class WhenTestingConnection(unittest.TestCase): 'algorithm': None, 'expiration': None, 'bit_length': None, - 'mime_type': 'text/plain'}}]} + 'mime_type': 'text/plain'}}], + 'previous': "{0}/{1}?limit={2}&offset={2}".format( + self.connection._tenant, + self.connection. + SECRETS_PATH, + limit)} body2 = body1 body2['orders'][0]['order_ref'] = 'http://localhost:9311/v1/No'\ + 'ne/orders/9f651441-3ccd-4'\ + '5b3-bc60-3051656382fj' body2['orders'][0]['secret']['name'] = 'test_2' + body2['orders'][0]['name'] = 'test_2' + body2['orders'][0]['secret_ref'] = 'http://localhost:9311/v1/No'\ + + 'ne/secrets/bbd2036f-7307-'\ + + '4090-bbef-bbb6025eabcd' + body2['previous'] = 'http://localhost:9311/v1/None/orders/19106'\ + + 'b6e-4ef1-48d1-8950-170c1a5838e1' + body2['next'] = None + orders = [client.Order(self.connection, b['orders'][0]) for b in (body1, body2)] body2['orders'].insert(0, body1['orders'][0]) self.request.return_value.content = json.dumps(body2) - self.assertTrue(self._are_equivalent(orders, - self.connection.list_orders())) + order_list, prev_ref, next_ref = self.connection.list_orders(limit, 1) + self.assertTrue(self._are_equivalent(orders, order_list)) + self.assertEqual(body2['previous'], prev_ref) + self.assertIsNone(next_ref) def test_should_get_response(self): self._setup_request() @@ -272,8 +321,11 @@ class WhenTestingConnection(unittest.TestCase): def test_should_raise_exception(self): self._setup_request() self.request.return_value.ok = False - with self.assertRaises(ClientException): + self.request.return_value.status_code = 404 + with self.assertRaises(ClientException) as e: self.connection._perform_http('GET', self.href) + exception = e.exception + self.assertEqual(404, exception.http_status) def _setup_request(self): self.request.return_value.headers = {'Accept': 'application/json'} diff --git a/tox.ini b/tox.ini index 2a3e4941..e61c7277 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,7 @@ # and then run "tox" from this directory. [tox] -envlist = py26, py27, py33, pep8 +envlist = py26, py27 [testenv] setenv = VIRTUAL_ENV={envdir} @@ -33,3 +33,9 @@ downloadcache = ~/cache/pip ignore = F,H show-source = True exclude = .venv,.tox,dist,doc,*egg + +[testenv:py26] +commands = nosetests {posargs:--with-xcoverage --all-modules --cover-inclusive --traverse-namespace --with-xunit --cover-package=barbican} + +[testenv:py27] +commands = nosetests {posargs:--with-xcoverage --all-modules --cover-inclusive --traverse-namespace --with-xunit --cover-package=barbican} From 22ac56043244ca2aa47f88f58435d87effee16bb Mon Sep 17 00:00:00 2001 From: Arash Ghoreyshi Date: Mon, 17 Jun 2013 16:38:35 -0500 Subject: [PATCH 3/6] Add complete functionality to the command line tool Make the __str__() method for secrets and orders more descriptive --- barbicanclient/client.py | 6 +- barbicanclient/orders.py | 9 +- barbicanclient/secrets.py | 14 ++- keep | 181 ++++++++++++++++++++++++++++---------- 4 files changed, 157 insertions(+), 53 deletions(-) diff --git a/barbicanclient/client.py b/barbicanclient/client.py index 3ea402c9..1fbaf3a3 100644 --- a/barbicanclient/client.py +++ b/barbicanclient/client.py @@ -16,6 +16,7 @@ from urlparse import urljoin LOG = log.getLogger(__name__) +log.setup('barbicanclient') class Connection(object): @@ -103,7 +104,7 @@ class Connection(object): self._token = value self._session.headers['X-Auth-Token'] = value - def list_secrets(self, limit=20, offset=0): + def list_secrets(self, limit=10, offset=0): """ Returns a tuple containing three items: a list of secrets pertaining to the given offset and limit, a reference to the previous set of @@ -206,7 +207,7 @@ class Connection(object): LOG.debug(_("Response - headers: {0}\nbody: {1}").format(hdrs, body)) return body - def list_orders(self, limit=20, offset=0): + def list_orders(self, limit=10, offset=0): """ Returns a tuple containing three items: a list of orders pertaining to the given offset and limit, a reference to the previous set of @@ -334,4 +335,3 @@ class Connection(object): if __name__ == '__main__': config.parse_args() - log.setup('barbicanclient') diff --git a/barbicanclient/orders.py b/barbicanclient/orders.py index b23e329a..3345e458 100644 --- a/barbicanclient/orders.py +++ b/barbicanclient/orders.py @@ -36,4 +36,11 @@ class Order(object): self.connection.delete_order(self) def __str__(self): - return "" % self.id + return ("Order - ID: {0}\n" + " order reference: {1}\n" + " secret reference: {2}\n" + " created: {3}\n" + " status: {4}\n" + .format(self.id, self.order_ref, self.secret_ref, self.created, + self.status) + ) diff --git a/barbicanclient/secrets.py b/barbicanclient/secrets.py index 293771cf..53e69736 100644 --- a/barbicanclient/secrets.py +++ b/barbicanclient/secrets.py @@ -39,4 +39,16 @@ class Secret(object): return self._id def __str__(self): - return "" % self.id + return ("Secret - ID: {0}\n" + " reference: {1}\n" + " name: {2}\n" + " created: {3}\n" + " MIME type: {4}\n" + " status: {5}\n" + " bit length: {6}\n" + " algorithm: {7}\n" + " cypher type: {8}\n" + .format(self.id, self.secret_ref, self.name, self.created, + self.mime_type, self.status, self.bit_length, + self.algorithm, self.cypher_type) + ) diff --git a/keep b/keep index 6d8e8188..fc56c229 100755 --- a/keep +++ b/keep @@ -7,89 +7,174 @@ from barbicanclient import client class Keep: def __init__(self): - self.parser = argparse.ArgumentParser(description='Access the Barbican' - ' key management sevice.') + self.parser = self.get_main_parser() self.subparsers = self.parser.add_subparsers(title='subcommands', description= 'Action to perform') - self.parser.add_argument('type', - choices=["order", "secret"], - help="Type to operate on") - self.parser.add_argument('--auth_endpoint', '-A', - help='The URL to authenticate against') - self.parser.add_argument('--user', '-U', - help='The user to authenticate as') - self.parser.add_argument('--password', '-P', - help='The API key or password to ' - 'authenticate with') - self.parser.add_argument('--tenant', '-T', - help='The tenant ID') - self.parser.add_argument('--endpoint', '-E', - help='The URL of the barbican server') - self.parser.add_argument('--token', '-K', - help='The authentication token') self.add_create_args() self.add_delete_args() self.add_get_args() self.add_list_args() - self.execute() + def get_main_parser(self): + parser = argparse.ArgumentParser(description='Access the Barbican' + ' key management sevice.') + parser.add_argument('type', + choices=["order", "secret"], + help="type to operate on") + parser.add_argument('--auth_endpoint', '-A', + help='the URL to authenticate against') + parser.add_argument('--user', '-U', help='the user to authenticate as') + parser.add_argument('--password', '-P', + help='the API key or password to ' + 'authenticate with') + parser.add_argument('--tenant', '-T', help='the tenant ID') + parser.add_argument('--endpoint', '-E', + help='the URL of the barbican server') + parser.add_argument('--token', '-K', help='the authentication token') + return parser def add_create_args(self): create_parser = self.subparsers.add_parser('create', help='Create a ' 'secret or an order') create_parser.add_argument('--mime_type', '-m', default='text/plain', - help='The MIME type used to fetch the secre' - 't (default: %(default)s)') - create_parser.add_argument('--name', '-n', help='A human-friendly name' + help='the MIME type of the raw secret (defa' + 'ult: %(default)s)') + create_parser.add_argument('--name', '-n', help='a human-friendly name' ' used only for reference') - create_parser.add_argument('--algorithm', '-a', help='The algorithm us' + create_parser.add_argument('--algorithm', '-a', help='the algorithm us' 'ed only for reference') - create_parser.add_argument('--bit_length', '-b', help='The bit length ' - 'of the secret used only for reference') - create_parser.add_argument('--cypher_type', '-c', help='The cypher typ' + create_parser.add_argument('--bit_length', '-b', default=256, + help='the bit length of the secret used ' + 'only for reference (default: %(default)s)', + type=int) + create_parser.add_argument('--cypher_type', '-c', help='the cypher typ' 'e used only for reference') - create_parser.add_argument('--plain_text', '-p', help='The unencrypted' - ' secret (only used for creating secrets)') - create_parser.add_argument('--expiration', '-e', help='Expiration time' - ' for the secret in ISO 8601 format') + create_parser.add_argument('--plain_text', '-p', help='the unencrypted' + ' secret (only used for secrets)') + create_parser.add_argument('--expiration', '-e', help='expiration time' + ' for the secret in ISO 8601 format ' + '(only used for secrets)') create_parser.set_defaults(func=self.create) def add_delete_args(self): delete_parser = self.subparsers.add_parser('delete', help='Delete a se' - 'cret or an order') + 'cret or an order either by' + ' id or by href') + delete_parser.add_argument('--href', '-r', help='the reference to the ' + 'secret or order') + delete_parser.add_argument('--id', '-i', help='the id of the secret or' + 'order') + delete_parser.set_defaults(func=self.delete) def add_get_args(self): get_parser = self.subparsers.add_parser('get', help='Retrieve a secret' - ' or an order') + ' or an order either by id or' + ' by href. The raw secret of ' + 'the type specified with --mi' + 'me_type can be retrieved ins' + 'tead for secrets using the -' + '-raw flag.') + get_parser.add_argument('--href', '-r', help='the reference to the ' + 'secret or order') + get_parser.add_argument('--id', '-i', help='the id of the secret or' + ' order') + get_parser.add_argument('--raw', '-w', help='if specified, gets the ra' + 'w secret of type specified with --mime_type (' + 'only used for secrets)', action='store_true') + get_parser.add_argument('--mime_type', '-m', default='text/plain', + help='the MIME type of the raw secret (defa' + 'ult: %(default)s; only used for secrets)') + get_parser.set_defaults(func=self.get) def add_list_args(self): list_parser = self.subparsers.add_parser('list', help='List secrets or orders') + list_parser.add_argument('--href', '-r', help='the reference to what i' + 's to be listed; put in quotes to avoid unint' + 'entional backgrounding by way of \'&\'') + list_parser.add_argument('--limit', '-l', default=10, help='specify t' + 'he number of items to list per page (defaul' + 't: %(default)s; maximum: 100)', type=int) + list_parser.add_argument('--offset', '-o', default=0, help='specify t' + 'he page offset (default: %(default)s)', + type=int) + list_parser.set_defaults(func=self.lst) def create(self, args): - if self.args.type == 'secret': - secret = self.conn.create_secret(self.args.mime_type, - self.args.plain_text, - self.args.name, - self.args.algorithm, - self.args.bit_length, - self.args.cypher_type, - self.args.expiration) + if args.type == 'secret': + secret = self.conn.create_secret(args.mime_type, + args.plain_text, + args.name, + args.algorithm, + args.bit_length, + args.cypher_type, + args.expiration) print secret.secret_ref + else: + order = self.conn.create_order(args.mime_type, + args.name, + args.algorithm, + args.bit_length, + args.cypher_type) + print order.order_ref + + def delete(self, args): + if args.type == 'secret': + if args.href: + self.conn.delete_secret(args.href) + elif args.id: + self.conn.delete_secret_by_id(args.id) + else: + if args.href: + self.conn.delete_order(args.href) + elif args.id: + self.conn.delete_order_by_id(args.id) + + def get(self, args): + if args.type == 'secret': + if args.href and args.raw: + print self.conn.get_raw_secret(args.href, args.mime_type) + elif args.href: + print self.conn.get_secret(args.href) + elif args.id and args.raw: + print self.conn.get_raw_secret_by_id(args.id, args.mime_type) + elif args.id: + print self.conn.get_secret_by_id(args.id) + else: + if args.href: + print self.conn.get_order(args.href) + elif args.id: + print self.conn.get_order_by_id(args.id) + + def lst(self, args): + if args.type == 'secret': + if args.href: + l = self.conn.list_secrets_by_href(args.href) + else: + l = self.conn.list_secrets(args.limit, args.offset) + else: + if args.href: + l = self.conn.list_orders_by_href(args.href) + else: + l = self.conn.list_orders(args.limit, args.offset) + for i in l[0]: + print i + print 'previous reference: ', l[1] + print 'next reference: ', l[2] def execute(self): - self.args = self.parser.parse_args() - self.conn = client.Connection(self.args.auth_endpoint, self.args.user, - self.args.password, self.args.tenant, - self.args.token, - endpoint=self.args.endpoint) - self.args.func(self.args) - print self.args + args = self.parser.parse_args() + self.conn = client.Connection(args.auth_endpoint, args.user, + args.password, args.tenant, + args.token, + endpoint=args.endpoint) + args.func(args) def main(): - Keep() + k = Keep() + k.execute() if __name__ == '__main__': From 607480a9d955bd60e50dd01b2dc7d5380fb8b707 Mon Sep 17 00:00:00 2001 From: Arash Ghoreyshi Date: Tue, 18 Jun 2013 13:30:10 -0500 Subject: [PATCH 4/6] In keep, modify 'get' and 'delete' to use UUIDS --- barbicanclient/client.py | 6 +-- keep | 84 +++++++++++++++------------------------- 2 files changed, 33 insertions(+), 57 deletions(-) diff --git a/barbicanclient/client.py b/barbicanclient/client.py index 1fbaf3a3..79de55cd 100644 --- a/barbicanclient/client.py +++ b/barbicanclient/client.py @@ -4,7 +4,6 @@ eventlet.monkey_patch(socket=True, select=True) import json import requests -from barbicanclient.common import config from barbicanclient.secrets import Secret from barbicanclient.orders import Order from barbicanclient.common import auth @@ -79,7 +78,7 @@ class Connection(object): self._session.verify = True if token: - LOG.warn(_("Bypassing authentication - using provided token")) + #LOG.warn(_("Bypassing authentication - using provided token")) self.auth_token = token else: LOG.debug(_("Authenticating token")) @@ -332,6 +331,3 @@ class Connection(object): resp_body = '' return response.headers, resp_body - -if __name__ == '__main__': - config.parse_args() diff --git a/keep b/keep index fc56c229..6d66c532 100755 --- a/keep +++ b/keep @@ -52,34 +52,26 @@ class Keep: 'e used only for reference') create_parser.add_argument('--plain_text', '-p', help='the unencrypted' ' secret (only used for secrets)') - create_parser.add_argument('--expiration', '-e', help='expiration time' - ' for the secret in ISO 8601 format ' + create_parser.add_argument('--expiration', '-e', help='the expiration ' + 'time for the secret in ISO 8601 format ' '(only used for secrets)') create_parser.set_defaults(func=self.create) def add_delete_args(self): delete_parser = self.subparsers.add_parser('delete', help='Delete a se' - 'cret or an order either by' - ' id or by href') - delete_parser.add_argument('--href', '-r', help='the reference to the ' - 'secret or order') - delete_parser.add_argument('--id', '-i', help='the id of the secret or' - 'order') + 'cret or an order by provid' + 'ing its UUID') + delete_parser.add_argument('UUID', help='the universally unique identi' + 'fier of the the secret or order') delete_parser.set_defaults(func=self.delete) def add_get_args(self): get_parser = self.subparsers.add_parser('get', help='Retrieve a secret' - ' or an order either by id or' - ' by href. The raw secret of ' - 'the type specified with --mi' - 'me_type can be retrieved ins' - 'tead for secrets using the -' - '-raw flag.') - get_parser.add_argument('--href', '-r', help='the reference to the ' - 'secret or order') - get_parser.add_argument('--id', '-i', help='the id of the secret or' - ' order') - get_parser.add_argument('--raw', '-w', help='if specified, gets the ra' + ' or an order by providing its' + ' UUID.') + get_parser.add_argument('UUID', help='the universally unique identi' + 'fier of the the secret or order') + get_parser.add_argument('--raw', '-r', help='if specified, gets the ra' 'w secret of type specified with --mime_type (' 'only used for secrets)', action='store_true') get_parser.add_argument('--mime_type', '-m', default='text/plain', @@ -90,15 +82,16 @@ class Keep: def add_list_args(self): list_parser = self.subparsers.add_parser('list', help='List secrets or orders') - list_parser.add_argument('--href', '-r', help='the reference to what i' - 's to be listed; put in quotes to avoid unint' - 'entional backgrounding by way of \'&\'') list_parser.add_argument('--limit', '-l', default=10, help='specify t' - 'he number of items to list per page (defaul' - 't: %(default)s; maximum: 100)', type=int) + 'he limit to the number of items to list per' + ' page (default: %(default)s; maximum: 100)', + type=int) list_parser.add_argument('--offset', '-o', default=0, help='specify t' 'he page offset (default: %(default)s)', type=int) + list_parser.add_argument('--URI', '-u', help='the full reference to ' + 'what is to be listed; put in quotes to avoid' + ' backgrounding when \'&\' is in the URI') list_parser.set_defaults(func=self.lst) def create(self, args): @@ -110,58 +103,45 @@ class Keep: args.bit_length, args.cypher_type, args.expiration) - print secret.secret_ref + print secret else: order = self.conn.create_order(args.mime_type, args.name, args.algorithm, args.bit_length, args.cypher_type) - print order.order_ref + print order def delete(self, args): if args.type == 'secret': - if args.href: - self.conn.delete_secret(args.href) - elif args.id: - self.conn.delete_secret_by_id(args.id) + self.conn.delete_secret_by_id(args.UUID) else: - if args.href: - self.conn.delete_order(args.href) - elif args.id: - self.conn.delete_order_by_id(args.id) + self.conn.delete_order_by_id(args.UUID) def get(self, args): if args.type == 'secret': - if args.href and args.raw: - print self.conn.get_raw_secret(args.href, args.mime_type) - elif args.href: - print self.conn.get_secret(args.href) - elif args.id and args.raw: - print self.conn.get_raw_secret_by_id(args.id, args.mime_type) - elif args.id: - print self.conn.get_secret_by_id(args.id) + if args.raw: + print self.conn.get_raw_secret_by_id(args.UUID, args.mime_type) + else: + print self.conn.get_secret_by_id(args.UUID) else: - if args.href: - print self.conn.get_order(args.href) - elif args.id: - print self.conn.get_order_by_id(args.id) + print self.conn.get_order_by_id(args.UUID) def lst(self, args): if args.type == 'secret': - if args.href: - l = self.conn.list_secrets_by_href(args.href) + if args.URI: + l = self.conn.list_secrets_by_href(args.URI) else: l = self.conn.list_secrets(args.limit, args.offset) else: - if args.href: - l = self.conn.list_orders_by_href(args.href) + if args.URI: + l = self.conn.list_orders_by_href(args.URI) else: l = self.conn.list_orders(args.limit, args.offset) for i in l[0]: print i - print 'previous reference: ', l[1] - print 'next reference: ', l[2] + print 'Displayed {0} {1}s - offset: {2}'.format(len(l[0]), args.type, + args.offset) def execute(self): args = self.parser.parse_args() From 6eae8b4607287deda5ca3bdd5f40f9a70be50e84 Mon Sep 17 00:00:00 2001 From: Arash Ghoreyshi Date: Tue, 18 Jun 2013 15:10:54 -0500 Subject: [PATCH 5/6] In keep, make environment variables the defaults of several args --- keep | 41 ++++++++++++++++++++++++++++++++--------- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/keep b/keep index 6d66c532..577e8394 100755 --- a/keep +++ b/keep @@ -1,6 +1,7 @@ #!/usr/bin/env python import argparse +import os from barbicanclient import client @@ -23,15 +24,23 @@ class Keep: choices=["order", "secret"], help="type to operate on") parser.add_argument('--auth_endpoint', '-A', - help='the URL to authenticate against') - parser.add_argument('--user', '-U', help='the user to authenticate as') - parser.add_argument('--password', '-P', - help='the API key or password to ' - 'authenticate with') - parser.add_argument('--tenant', '-T', help='the tenant ID') - parser.add_argument('--endpoint', '-E', - help='the URL of the barbican server') - parser.add_argument('--token', '-K', help='the authentication token') + default=env('OS_AUTH_URL'), + help='the URL to authenticate against (default: ' + '%(default)s)') + parser.add_argument('--user', '-U', default=env('OS_USERNAME'), + help='the user to authenticate as (default: %(de' + 'fault)s)') + parser.add_argument('--password', '-P', default=env('OS_PASSWORD'), + help='the API key or password to authenticate with' + ' (default: %(default)s)') + parser.add_argument('--tenant', '-T', default=env('OS_TENANT_NAME'), + help='the tenant ID (default: %(default)s)') + parser.add_argument('--endpoint', '-E', default=env('SERVICE_ENDPOINT') + , help='the URL of the barbican server (default: %' + '(default)s)') + parser.add_argument('--token', '-K', default=env('SERVICE_TOKEN'), + help='the authentication token (default: %(default' + ')s)') return parser def add_create_args(self): @@ -152,6 +161,20 @@ class Keep: args.func(args) +def env(*vars, **kwargs): + """Search for the first defined of possibly many env vars + + Returns the first environment variable defined in vars, or + returns the default defined in kwargs. + + """ + for v in vars: + value = os.environ.get(v, None) + if value: + return value + return kwargs.get('default', '') + + def main(): k = Keep() k.execute() From 02b130c822b27ab97d5ef83255370e0beb1bebd3 Mon Sep 17 00:00:00 2001 From: Arash Ghoreyshi Date: Wed, 19 Jun 2013 13:29:04 -0500 Subject: [PATCH 6/6] Add docstrings to the client library along with a couple minor changes --- barbicanclient/client.py | 79 +++++++++++++++++++++++++++++++--------- keep | 1 + 2 files changed, 63 insertions(+), 17 deletions(-) diff --git a/barbicanclient/client.py b/barbicanclient/client.py index 79de55cd..acf77240 100644 --- a/barbicanclient/client.py +++ b/barbicanclient/client.py @@ -78,7 +78,6 @@ class Connection(object): self._session.verify = True if token: - #LOG.warn(_("Bypassing authentication - using provided token")) self.auth_token = token else: LOG.debug(_("Authenticating token")) @@ -126,21 +125,18 @@ class Connection(object): """ LOG.debug(_("Listing secrets by href")) LOG.debug("href: {0}".format(href)) + if href is None: + return [], None, None + hdrs, body = self._perform_http(href=href, method='GET') LOG.debug(_("Response - headers: {0}\nbody: {1}").format(hdrs, body)) secrets_dict = body['secrets'] secrets = [Secret(self._conn, s) for s in secrets_dict] - if 'previous' in body: - prev_ref = body['previous'] - else: - prev_ref = None + prev_ref = body.get('previous') - if 'next' in body: - next_ref = body['next'] - else: - next_ref = None + next_ref = body.get('next') return secrets, prev_ref, next_ref @@ -152,6 +148,18 @@ class Connection(object): bit_length=None, cypher_type=None, expiration=None): + """ + Creates and returns a Secret object with all of its metadata filled in. + + arguments: + mime_type - The MIME type of the secret + plain_text - The unencrypted secret + name - A friendly name for the secret + algorithm - The algorithm the secret is used with + bit_length - The bit length of the secret + cypher_type - The cypher type (e.g. block cipher mode of operation) + expiration - The expiration time for the secret in ISO 8601 format + """ LOG.debug(_("Creating secret of mime_type {0}").format(mime_type)) href = "{0}/{1}".format(self._tenant, self.SECRETS_PATH) LOG.debug(_("href: {0}").format(href)) @@ -176,30 +184,48 @@ class Connection(object): return self.get_secret(body['secret_ref']) def delete_secret_by_id(self, secret_id): + """ + Deletes a secret using its UUID + """ href = "{0}/{1}/{2}".format(self._tenant, self.SECRETS_PATH, secret_id) LOG.info(_("Deleting secret - Secret ID: {0}").format(secret_id)) return self.delete_secret(href) def delete_secret(self, href): + """ + Deletes a secret using its full reference + """ hdrs, body = self._perform_http(href=href, method='DELETE') LOG.debug(_("Response - headers: {0}\nbody: {1}").format(hdrs, body)) def get_secret_by_id(self, secret_id): + """ + Returns a Secret object using the secret's UUID + """ LOG.debug(_("Getting secret - Secret ID: {0}").format(secret_id)) href = "{0}/{1}/{2}".format(self._tenant, self.SECRETS_PATH, secret_id) return self.get_secret(href) def get_secret(self, href): + """ + Returns a Secret object using the secret's full reference + """ hdrs, body = self._perform_http(href=href, method='GET') LOG.debug(_("Response - headers: {0}\nbody: {1}").format(hdrs, body)) return Secret(self._conn, body) def get_raw_secret_by_id(self, secret_id, mime_type): + """ + Returns the raw secret using the secret's UUID and MIME type + """ LOG.debug(_("Getting raw secret - Secret ID: {0}").format(secret_id)) href = "{0}/{1}/{2}".format(self._tenant, self.SECRETS_PATH, secret_id) return self.get_raw_secret(href, mime_type) def get_raw_secret(self, href, mime_type): + """ + Returns the raw secret using the secret's UUID and MIME type + """ hdrs = {"Accept": mime_type} hdrs, body = self._perform_http(href=href, method='GET', headers=hdrs, parse_json=False) @@ -229,21 +255,18 @@ class Connection(object): """ LOG.debug(_("Listing orders by href")) LOG.debug("href: {0}".format(href)) + if href is None: + return [], None, None + hdrs, body = self._perform_http(href=href, method='GET') LOG.debug(_("Response - headers: {0}\nbody: {1}").format(hdrs, body)) orders_dict = body['orders'] orders = [Order(self._conn, o) for o in orders_dict] - if 'previous' in body: - prev_ref = body['previous'] - else: - prev_ref = None + prev_ref = body.get('previous') - if 'next' in body: - next_ref = body['next'] - else: - next_ref = None + next_ref = body.get('next') return orders, prev_ref, next_ref @@ -253,6 +276,16 @@ class Connection(object): algorithm=None, bit_length=None, cypher_type=None): + """ + Creates and returns an Order object with all of its metadata filled in. + + arguments: + mime_type - The MIME type of the secret + name - A friendly name for the secret + algorithm - The algorithm the secret is used with + bit_length - The bit length of the secret + cypher_type - The cypher type (e.g. block cipher mode of operation) + """ LOG.debug(_("Creating order of mime_type {0}").format(mime_type)) href = "{0}/{1}".format(self._tenant, self.ORDERS_PATH) LOG.debug("href: {0}".format(href)) @@ -273,20 +306,32 @@ class Connection(object): return self.get_order(body['order_ref']) def delete_order_by_id(self, order_id): + """ + Deletes an order using its UUID + """ LOG.info(_("Deleting order - Order ID: {0}").format(order_id)) href = "{0}/{1}/{2}".format(self._tenant, self.ORDERS_PATH, order_id) return self.delete_order(href) def delete_order(self, href): + """ + Deletes an order using its full reference + """ hdrs, body = self._perform_http(href=href, method='DELETE') LOG.debug(_("Response - headers: {0}\nbody: {1}").format(hdrs, body)) def get_order_by_id(self, order_id): + """ + Returns an Order object using the order's UUID + """ LOG.debug(_("Getting order - Order ID: {0}").format(order_id)) href = "{0}/{1}/{2}".format(self._tenant, self.ORDERS_PATH, order_id) return self.get_order(href) def get_order(self, href): + """ + Returns an Order object using the order's full reference + """ hdrs, body = self._perform_http(href=href, method='GET') LOG.debug(_("Response - headers: {0}\nbody: {1}").format(hdrs, body)) return Order(self._conn, body) diff --git a/keep b/keep index 577e8394..adcd4a14 100755 --- a/keep +++ b/keep @@ -167,6 +167,7 @@ def env(*vars, **kwargs): Returns the first environment variable defined in vars, or returns the default defined in kwargs. + Source: Keystone's shell.py """ for v in vars: value = os.environ.get(v, None)