Bare minimum to make HTTP calls

This commit is contained in:
Frédéric Guillot 2017-05-15 14:16:39 -04:00
parent c0b24963b3
commit 75b72d4e34
24 changed files with 361 additions and 140 deletions

View File

@ -1,3 +0,0 @@
# Format is:
# <preferred e-mail> <other e-mail 1>
# <preferred e-mail> <other e-mail 2>

View File

@ -1,4 +0,0 @@
python-almanachclient Style Commandments
========================================
Read the OpenStack Style Commandments http://docs.openstack.org/developer/hacking/

View File

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

View File

View File

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

View File

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

View File

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

View File

@ -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__),
}

View File

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

View File

@ -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__':

View File

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

View File

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

View File

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

View File

View File

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

View File

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

View File

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

View File

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

View File

@ -1,2 +0,0 @@
[python: **.py]

View File

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

View File

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

View File

@ -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'],

View File

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

12
tox.ini
View File

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