From 75b72d4e34be80ac37dd86bf7df91d97db2cd888 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Guillot?= <fguillot@internap.com>
Date: Mon, 15 May 2017 14:16:39 -0400
Subject: [PATCH] Bare minimum to make HTTP calls

---
 .mailmap                                     |  3 -
 HACKING.rst                                  |  4 -
 README.rst                                   |  3 +-
 almanachclient/commands/__init__.py          |  0
 almanachclient/commands/endpoint.py          | 22 ++++++
 almanachclient/commands/version.py           | 23 ++++++
 almanachclient/exceptions.py                 | 25 ++++++
 almanachclient/http_client.py                | 41 ++++++++++
 almanachclient/keystone_client.py            | 51 +++++++++++++
 almanachclient/shell.py                      | 80 ++++++++++++++------
 almanachclient/tests/base.py                 | 22 +++---
 almanachclient/tests/test_almanachclient.py  | 28 -------
 almanachclient/tests/test_keystone_client.py | 46 +++++++++++
 almanachclient/tests/v1/__init__.py          |  0
 almanachclient/tests/v1/test_client.py       | 39 ++++++++++
 almanachclient/v1/client.py                  | 28 +++++++
 almanachclient/v1/version_cli.py             | 25 ------
 almanachclient/version.py                    | 20 ++---
 babel.cfg                                    |  2 -
 requirements.txt                             | 10 +--
 setup.cfg                                    |  6 +-
 setup.py                                     |  7 --
 test-requirements.txt                        |  4 -
 tox.ini                                      | 12 +--
 24 files changed, 361 insertions(+), 140 deletions(-)
 delete mode 100644 .mailmap
 delete mode 100644 HACKING.rst
 create mode 100644 almanachclient/commands/__init__.py
 create mode 100644 almanachclient/commands/endpoint.py
 create mode 100644 almanachclient/commands/version.py
 create mode 100644 almanachclient/exceptions.py
 create mode 100644 almanachclient/http_client.py
 create mode 100644 almanachclient/keystone_client.py
 delete mode 100644 almanachclient/tests/test_almanachclient.py
 create mode 100644 almanachclient/tests/test_keystone_client.py
 create mode 100644 almanachclient/tests/v1/__init__.py
 create mode 100644 almanachclient/tests/v1/test_client.py
 create mode 100644 almanachclient/v1/client.py
 delete mode 100644 almanachclient/v1/version_cli.py
 delete mode 100644 babel.cfg

diff --git a/.mailmap b/.mailmap
deleted file mode 100644
index 516ae6f..0000000
--- a/.mailmap
+++ /dev/null
@@ -1,3 +0,0 @@
-# Format is:
-# <preferred e-mail> <other e-mail 1>
-# <preferred e-mail> <other e-mail 2>
diff --git a/HACKING.rst b/HACKING.rst
deleted file mode 100644
index 71f9c17..0000000
--- a/HACKING.rst
+++ /dev/null
@@ -1,4 +0,0 @@
-python-almanachclient Style Commandments
-========================================
-
-Read the OpenStack Style Commandments http://docs.openstack.org/developer/hacking/
diff --git a/README.rst b/README.rst
index 2ac531b..3a7c7b3 100644
--- a/README.rst
+++ b/README.rst
@@ -1,5 +1,5 @@
 ===================
-Almanach API client
+Almanach API Client
 ===================
 
 This is a client library for Almanach built on the Almanch REST API. 
@@ -9,3 +9,4 @@ tool (almanach-client).
 * Free software: Apache license
 * Source: http://git.openstack.org/cgit/openstack/python-almanachclient
 * Bugs: http://bugs.launchpad.net/almanach
+* Documentation: https://almanach-client.readthedocs.io/
diff --git a/almanachclient/commands/__init__.py b/almanachclient/commands/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/almanachclient/commands/endpoint.py b/almanachclient/commands/endpoint.py
new file mode 100644
index 0000000..04855b2
--- /dev/null
+++ b/almanachclient/commands/endpoint.py
@@ -0,0 +1,22 @@
+# Copyright 2017 INAP
+#
+# 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 cliff.command import Command
+
+
+class EndpointCommand(Command):
+    """Show the Almanach Endpoint URL"""
+
+    def take_action(self, parsed_args):
+        self.app.stdout.write('{}\n'.format(self.app.get_client().get_url()))
diff --git a/almanachclient/commands/version.py b/almanachclient/commands/version.py
new file mode 100644
index 0000000..25c1261
--- /dev/null
+++ b/almanachclient/commands/version.py
@@ -0,0 +1,23 @@
+# Copyright 2017 INAP
+#
+# 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 cliff.command import Command
+
+
+class VersionCommand(Command):
+    """Show the Almanach version number"""
+
+    def take_action(self, parsed_args):
+        info = self.app.get_client().get_info()
+        self.app.stdout.write('{}\n'.format(info.get('info', {}).get('version')))
diff --git a/almanachclient/exceptions.py b/almanachclient/exceptions.py
new file mode 100644
index 0000000..48cf56a
--- /dev/null
+++ b/almanachclient/exceptions.py
@@ -0,0 +1,25 @@
+# Copyright 2017 INAP
+#
+# 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 ClientError(Exception):
+    pass
+
+
+class EndpointNotFound(ClientError):
+    pass
+
+
+class HTTPError(ClientError):
+    pass
diff --git a/almanachclient/http_client.py b/almanachclient/http_client.py
new file mode 100644
index 0000000..6bdb544
--- /dev/null
+++ b/almanachclient/http_client.py
@@ -0,0 +1,41 @@
+# Copyright 2017 INAP
+#
+# 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 abc
+import logging
+
+import requests
+
+from almanachclient import exceptions
+from almanachclient import version as client_version
+
+logger = logging.getLogger(__name__)
+
+
+class HttpClient(metaclass=abc.ABCMeta):
+    def _get(self, url):
+        logger.debug(url)
+        response = requests.get(url, headers=self._get_headers())
+
+        if response.status_code != 200:
+            raise exceptions.HTTPError('HTTP Error ({})'.format(response.status_code))
+
+        return response.json()
+
+    def _get_headers(self):
+        return {
+            'Content-Type': 'application/json',
+            'Accept': 'application/json',
+            'User-Agent': 'python-almanachclient/{}'.format(client_version.__version__),
+        }
diff --git a/almanachclient/keystone_client.py b/almanachclient/keystone_client.py
new file mode 100644
index 0000000..a23a5b6
--- /dev/null
+++ b/almanachclient/keystone_client.py
@@ -0,0 +1,51 @@
+# Copyright 2017 INAP
+#
+# 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 keystoneauth1.identity import v3
+from keystoneauth1 import session
+from keystoneclient.v3 import client as keystone_client
+
+from almanachclient import exceptions
+
+
+class KeystoneClient(object):
+    def __init__(self, auth_url, username, password, service, region_name,
+                 domain_name='default', user_domain_id='default'):
+        self.auth_url = auth_url
+        self.username = username
+        self.password = password
+        self.service = service
+        self.region_name = region_name
+        self.domain_name = domain_name
+        self.user_domain_id = user_domain_id
+
+    def get_endpoint_url(self, visibility='admin'):
+        keystone = self._get_keystone_client()
+        endpoints = keystone.endpoints.list(service=self.service, region=self.region_name)
+
+        for endpoint in endpoints:
+            if endpoint.interface == visibility:
+                return endpoint.url
+
+        raise exceptions.EndpointNotFound('Endpoint URL Not Found')
+
+    def _get_keystone_client(self):
+        auth = v3.Password(auth_url=self.auth_url,
+                           username=self.username,
+                           password=self.password,
+                           domain_name=self.domain_name,
+                           user_domain_id=self.user_domain_id)
+
+        sess = session.Session(auth=auth)
+        return keystone_client.Client(session=sess)
diff --git a/almanachclient/shell.py b/almanachclient/shell.py
index baf9a88..d545d31 100644
--- a/almanachclient/shell.py
+++ b/almanachclient/shell.py
@@ -1,33 +1,34 @@
+# Copyright 2017 INAP
 #
-#    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
+# 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.
+#     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.
 
-"""
-Command-line interface to the OpenStack Almanach API.
-"""
-
+import os
 import sys
 
 from cliff import app
 from cliff import commandmanager
 
-from almanachclient import version
-from almanachclient.v1 import version_cli
+from almanachclient.commands.endpoint import EndpointCommand
+from almanachclient.commands.version import VersionCommand
+from almanachclient.keystone_client import KeystoneClient
+from almanachclient.v1.client import Client
+from almanachclient import version as client_version
 
 
 class AlmanachCommandManager(commandmanager.CommandManager):
     SHELL_COMMANDS = {
-        "version": version_cli.CliVersionShow,
+        'version': VersionCommand,
+        'endpoint': EndpointCommand,
     }
 
     def load_commands(self, namespace):
@@ -38,18 +39,49 @@ class AlmanachCommandManager(commandmanager.CommandManager):
 class AlmanachApp(app.App):
 
     def __init__(self):
-        super(AlmanachApp, self).__init__(
-            description='Almanach command line client',
-            version=version.__version__,
+        super().__init__(
+            description='Almanach Command Line Client',
+            version=client_version.__version__,
             command_manager=AlmanachCommandManager(None),
             deferred_help=True,
         )
 
+    def build_option_parser(self, description, version, argparse_kwargs=None):
+        parser = super().build_option_parser(description, version, argparse_kwargs)
 
-def main(args=None):
-    if args is None:
-        args = sys.argv[1:]
-    return AlmanachApp().run(args)
+        parser.add_argument('--os-auth-url',
+                            default=os.environ.get('OS_AUTH_URL'),
+                            help='Keystone V3 URL (Env: OS_AUTH_URL).')
+
+        parser.add_argument('--os-region-name',
+                            default=os.environ.get('OS_REGION_NAME'),
+                            help='OpenStack region name (Env: OS_REGION_NAME).')
+
+        parser.add_argument('--os-password',
+                            default=os.environ.get('OS_PASSWORD'),
+                            help='OpenStack password (Env: OS_PASSWORD).')
+
+        parser.add_argument('--os-username',
+                            default=os.environ.get('OS_USERNAME'),
+                            help='OpenStack username (Env: OS_USERNAME).')
+
+        parser.add_argument('--almanach-service',
+                            default=os.environ.get('ALMANACH_SERVICE'),
+                            help='Almanach keystone service name (Env: ALMANACH_SERVICE).')
+        return parser
+
+    def get_client(self):
+        keystone = KeystoneClient(auth_url=self.options.os_auth_url,
+                                  username=self.options.os_username,
+                                  password=self.options.os_password,
+                                  service=self.options.almanach_service,
+                                  region_name=self.options.os_region_name)
+
+        return Client(keystone.get_endpoint_url())
+
+
+def main(argv=sys.argv[1:]):
+    return AlmanachApp().run(argv)
 
 
 if __name__ == '__main__':
diff --git a/almanachclient/tests/base.py b/almanachclient/tests/base.py
index 1c30cdb..126d57b 100644
--- a/almanachclient/tests/base.py
+++ b/almanachclient/tests/base.py
@@ -1,23 +1,19 @@
-# -*- coding: utf-8 -*-
-
-# Copyright 2010-2011 OpenStack Foundation
-# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
+# Copyright 2017 INAP
 #
-# 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
+# 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
+#     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.
+# 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 oslotest import base
 
 
 class TestCase(base.BaseTestCase):
-
     """Test case base class for all unit tests."""
diff --git a/almanachclient/tests/test_almanachclient.py b/almanachclient/tests/test_almanachclient.py
deleted file mode 100644
index 48b16f6..0000000
--- a/almanachclient/tests/test_almanachclient.py
+++ /dev/null
@@ -1,28 +0,0 @@
-# -*- coding: utf-8 -*-
-
-# 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.
-
-"""
-test_almanachclient
-----------------------------------
-
-Tests for `almanachclient` module.
-"""
-
-from almanachclient.tests import base
-
-
-class TestAlmanachclient(base.TestCase):
-
-    def test_something(self):
-        pass
diff --git a/almanachclient/tests/test_keystone_client.py b/almanachclient/tests/test_keystone_client.py
new file mode 100644
index 0000000..c12daf5
--- /dev/null
+++ b/almanachclient/tests/test_keystone_client.py
@@ -0,0 +1,46 @@
+# Copyright 2017 INAP
+#
+# 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 unittest import mock
+
+from almanachclient import exceptions
+from almanachclient.keystone_client import KeystoneClient
+from almanachclient.tests import base
+
+
+class TestKeystoneClient(base.TestCase):
+    def setUp(self):
+        super().setUp()
+        self.almanach_url = 'http://almanach_url'
+        self.auth_url = 'http://keystone_url'
+        self.username = 'username'
+        self.password = 'password'
+        self.service = 'almanach'
+        self.region_name = 'some region'
+        self.client = KeystoneClient(self.auth_url, self.username, self.password, self.service, self.region_name)
+
+    @mock.patch('keystoneclient.v3.client.Client')
+    def test_get_endpoint_url(self, keystone):
+        endpoint_manager = mock.Mock()
+        keystone.return_value = endpoint_manager
+        endpoint_manager.endpoints.list.return_value = [mock.Mock(interface='admin', url=self.almanach_url)]
+
+        self.assertEqual(self.almanach_url, self.client.get_endpoint_url())
+
+        endpoint_manager.endpoints.list.assert_called_once_with(service=self.service, region=self.region_name)
+
+    @mock.patch('keystoneclient.v3.client.Client')
+    def test_get_endpoint_url_not_found(self, keystone):
+        keystone.return_value.endpoints.list.return_value = []
+        self.assertRaises(exceptions.EndpointNotFound, self.client.get_endpoint_url)
diff --git a/almanachclient/tests/v1/__init__.py b/almanachclient/tests/v1/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/almanachclient/tests/v1/test_client.py b/almanachclient/tests/v1/test_client.py
new file mode 100644
index 0000000..ed3a6f6
--- /dev/null
+++ b/almanachclient/tests/v1/test_client.py
@@ -0,0 +1,39 @@
+# Copyright 2017 INAP
+#
+# 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 unittest import mock
+
+from almanachclient.tests import base
+from almanachclient.v1.client import Client
+
+
+class TestClient(base.TestCase):
+    def setUp(self):
+        super().setUp()
+        self.almanach_url = 'http://almanach_url'
+        self.client = Client(self.almanach_url)
+
+    @mock.patch('requests.get')
+    def test_get_info(self, requests):
+        response = mock.Mock()
+        expected = {
+            'info': {'version': '1.2.3'},
+            "database": {'all_entities': 2, 'active_entities': 1}
+        }
+
+        requests.return_value = response
+        response.json.return_value = expected
+        response.status_code = 200
+
+        self.assertEqual(expected, self.client.get_info())
diff --git a/almanachclient/v1/client.py b/almanachclient/v1/client.py
new file mode 100644
index 0000000..187e119
--- /dev/null
+++ b/almanachclient/v1/client.py
@@ -0,0 +1,28 @@
+# Copyright 2017 INAP
+#
+# 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 almanachclient.http_client import HttpClient
+
+
+class Client(HttpClient):
+    api_version = 'v1'
+
+    def __init__(self, url):
+        self.url = url
+
+    def get_url(self):
+        return self.url
+
+    def get_info(self):
+        return self._get('{}/{}/info'.format(self.url, self.api_version))
diff --git a/almanachclient/v1/version_cli.py b/almanachclient/v1/version_cli.py
deleted file mode 100644
index c1dfe68..0000000
--- a/almanachclient/v1/version_cli.py
+++ /dev/null
@@ -1,25 +0,0 @@
-#
-#    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 cliff.command import Command
-
-
-from almanachclient import version
-
-
-class CliVersionShow(Command):
-    """Show the client version number"""
-
-    def take_action(self, parsed_args):
-        self.app.stdout.write(version.__version__ + '\n')
diff --git a/almanachclient/version.py b/almanachclient/version.py
index e58667d..2d0bb50 100644
--- a/almanachclient/version.py
+++ b/almanachclient/version.py
@@ -1,16 +1,16 @@
+# Copyright 2017 INAP
 #
-#    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
+# 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.
+#     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 pbr.version
 
diff --git a/babel.cfg b/babel.cfg
deleted file mode 100644
index 15cd6cb..0000000
--- a/babel.cfg
+++ /dev/null
@@ -1,2 +0,0 @@
-[python: **.py]
-
diff --git a/requirements.txt b/requirements.txt
index 09a66dd..b50f9f1 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,6 +1,4 @@
-# The order of packages is significant, because pip processes them in the order
-# of appearance. Changing the order has an impact on the overall integration
-# process, which may cause wedges in the gate later.
-
-pbr>=1.6 # Apache-2.0
-cliff>1.16.0 # Apache-2.0
+pbr>=2.0.0,!=2.1.0  # Apache-2.0
+cliff>=2.6.0  # Apache-2.0
+requests>=2.10.0,!=2.12.2,!=2.13.0  # Apache-2.0
+python-keystoneclient>=3.8.0  # Apache-2.0
\ No newline at end of file
diff --git a/setup.cfg b/setup.cfg
index 3240ae9..b50023a 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -13,11 +13,9 @@ classifier =
     License :: OSI Approved :: Apache Software License
     Operating System :: POSIX :: Linux
     Programming Language :: Python
-    Programming Language :: Python :: 2
-    Programming Language :: Python :: 2.7
     Programming Language :: Python :: 3
-    Programming Language :: Python :: 3.3
     Programming Language :: Python :: 3.4
+    Programming Language :: Python :: 3.5
 
 [files]
 packages =
@@ -25,7 +23,7 @@ packages =
 
 [entry_points]
 console_scripts =
-    almanach = almanachclient.shell:main
+    almanach-client = almanachclient.shell:main
 
 [build_sphinx]
 source-dir = doc/source
diff --git a/setup.py b/setup.py
index 056c16c..155c200 100644
--- a/setup.py
+++ b/setup.py
@@ -16,13 +16,6 @@
 # THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT
 import setuptools
 
-# In python < 2.7.4, a lazy loading of package `pbr` will break
-# setuptools if some other modules registered functions in `atexit`.
-# solution from: http://bugs.python.org/issue15881#msg170215
-try:
-    import multiprocessing  # noqa
-except ImportError:
-    pass
 
 setuptools.setup(
     setup_requires=['pbr'],
diff --git a/test-requirements.txt b/test-requirements.txt
index a3fcd81..044347a 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -1,7 +1,3 @@
-# The order of packages is significant, because pip processes them in the order
-# of appearance. Changing the order has an impact on the overall integration
-# process, which may cause wedges in the gate later.
-
 hacking<0.12,>=0.11.0 # Apache-2.0
 
 coverage>=3.6 # Apache-2.0
diff --git a/tox.ini b/tox.ini
index d9e6688..d493a44 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,6 +1,6 @@
 [tox]
 minversion = 2.0
-envlist = py34,py27,pep8
+envlist = py34,py35,pep8
 skipsdist = True
 
 [testenv]
@@ -12,14 +12,12 @@ deps = -r{toxinidir}/test-requirements.txt
 commands = python setup.py test --slowest --testr-args='{posargs}'
 
 [testenv:pep8]
+basepython = python3
 commands = flake8 {posargs}
 
 [testenv:venv]
 commands = {posargs}
 
-[testenv:cover]
-commands = python setup.py test --coverage --testr-args='{posargs}'
-
 [testenv:docs]
 commands = python setup.py build_sphinx
 
@@ -27,13 +25,9 @@ commands = python setup.py build_sphinx
 commands =
   sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html
 
-[testenv:debug]
-commands = oslo_debug_helper {posargs}
-
 [flake8]
-# E123, E125 skipped as they are invalid PEP-8.
-
 show-source = True
 ignore = E123,E125
 builtins = _
+max-line-length = 120
 exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build