Skip to content
This repository has been archived by the owner on Jan 9, 2023. It is now read-only.

Add support for amqp+ssl URL's and configured username and password #17

Merged
merged 7 commits into from
Aug 2, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 24 additions & 4 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,31 @@ Available tools
* ``qb session outgoing`` - List outgoing sessions from the server.


Environment variables
---------------------
Configuration & Environment variables
-------------------------------------
Several options exist to configure Qpid Bow. In order of preference:

``AMQP_SERVERS`` - comma-separated list of main and failover servers to connect to
**Pass in arguments**
One can always override the used server URL using arguments:

``AMQP_TEST_SERVERS`` - Same as ``AMQP_SERVERS``, used solely for unittests
* For the CLI tools, use the ``--broker-url`` command line argument.
* For the library pass in the keyword argument ``server_url``.

**Configure using a dict**
When using Qpid Bow as a library, one can pass in config using a dict to:
``qpid_bow.config.configure``

The dict can contain the following entries:

* ``amqp_url`` - Comma-separated list of main and failover servers to connect to.
* ``username`` - Username to use when no username is provided in the URL.
* ``password`` - Password to use when no password is provided in the URL.

**Environment variables**
The easiest way to configure Qpid Bow's tools and library is to use environment variables.
These variables can be added to your shell's profile and will automatically get picked up.

* ``AMQP_SERVERS`` - Comma-separated list of main and failover servers to connect to.
* ``AMQP_TEST_SERVERS`` - Same as ``AMQP_SERVERS``, used solely for unittests.

example: ``AMQP_SERVERS=amqp://user:[email protected]:5672,amqp://user:[email protected]:5672``
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.0.2
1.1.0
4 changes: 2 additions & 2 deletions docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
copyright = '2018, Bynder B.V.'
author = 'Bynder B.V.'

version = '1.0' # The short X.Y version.
release = '1.0.2' # The full version, including alpha/beta/rc tags.
version = '1.1' # The short X.Y version.
release = '1.1.0' # The full version, including alpha/beta/rc tags.

exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']

Expand Down
56 changes: 39 additions & 17 deletions qpid_bow/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
Optional,
)

from urllib.parse import urlsplit, urlunsplit

config: dict = {}


Expand All @@ -19,30 +21,50 @@ def configure(new_config: Mapping):
config.update(new_config)


def get_urls(urls: Optional[str] = None) -> List[str]:
"""Retrieves server urls from one of the sources.
def process_url(url: str) -> str:
"""Processes a URL for usage with Qpid Proton.

The sources priority comes in the following order: passed arguments,
global config, AMQP_SERVERS environment variable.
- ActiveMQ amqp+ssl scheme is replaced with amqps.
- Adds username and password from config.

Args:
urls: Comma-separated urls.
url: Input URL.

Returns:
List[str]: Returns list of urls to connect to.
str: Processed URL.
"""
if urls:
return [url.strip() for url in urls.split(',')]
split_url = urlsplit(url.strip())
if split_url.scheme == 'amqp+ssl':
split_url = split_url._replace(scheme='amqps')

if ((not split_url.username or not split_url.password) and
'username' in config and 'password' in config):
user_pass = f"{config['username']}:{config['password']}@"
new_netloc = user_pass + split_url.netloc
split_url = split_url._replace(netloc=new_netloc)

return urlunsplit(split_url)


if config.get('amqp_url'):
return [url.strip() for url in config['amqp_url'].split(',')]
def get_urls(argument_urls: Optional[str] = None) -> List[str]:
"""Retrieves server argument_urls from one of the sources.

amqp_servers = environ.get('AMQP_SERVERS')
The sources priority comes in the following order: passed arguments,
global config, AMQP_SERVERS environment variable.

Args:
argument_urls: Comma-separated argument_urls.

if amqp_servers:
environ_urls = []
for server in amqp_servers.split(','):
environ_urls.append(server.strip())
return environ_urls
Returns:
List[str]: Returns list of argument_urls to connect to.
"""
if argument_urls:
raw_urls = argument_urls
elif 'amqp_url' in config:
raw_urls = config['amqp_url']
elif 'AMQP_SERVERS' in environ:
raw_urls = environ['AMQP_SERVERS']
else:
raise ValueError('AMQP server url is not configured')

raise ValueError('AMQP server url is not configured')
return [process_url(url) for url in raw_urls.split(',')]
85 changes: 85 additions & 0 deletions test/test_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
from contextlib import suppress
from os import environ
from unittest import TestCase

from qpid_bow.config import configure, config, get_urls, process_url

class TestGetURLs(TestCase):
def setUp(self):
self.old_config = config
self.old_env = environ.get('AMQP_SERVERS')

def tearDown(self):
config.clear()
config.update(self.old_config)
if self.old_env:
environ['AMQP_SERVERS'] = self.old_env
else:
with suppress(KeyError):
del environ['AMQP_SERVERS']

def test_no_config(self):
with suppress(KeyError):
del environ['AMQP_SERVERS']

with self.assertRaises(ValueError):
get_urls()

def test_get_urls_priority_from_environ(self):
environ['AMQP_SERVERS'] = 'amqps://environ.example'
self.assertEqual(get_urls(), ['amqps://environ.example'])

def test_get_urls_priority_from_config(self):
environ['AMQP_SERVERS'] = 'amqps://environ.example'
configure({'amqp_url': 'amqps://config.example'})

self.assertEqual(get_urls(), ['amqps://config.example'])

def test_get_urls_priority_from_args(self):
environ['AMQP_SERVERS'] = 'amqps://environ.example'
configure({'amqp_url': 'amqps://config.example'})

self.assertEqual(get_urls('amqps://args.example'),
['amqps://args.example'])

def test_get_urls_comma_seperated(self):
self.assertEqual(
get_urls('amqps://args1.example, amqps://args2.example'),
['amqps://args1.example', 'amqps://args2.example'])

def test_get_urls_activemq_format(self):
self.assertEqual(get_urls('amqp+ssl://args.example'),
['amqps://args.example'])

def test_get_urls_activemq_format_comma_seperated(self):
self.assertEqual(
get_urls('amqp+ssl://args1.example, amqp+ssl://args2.example'),
['amqps://args1.example', 'amqps://args2.example'])

def test_get_urls_user_passwd_config_mixed(self):
config['username'] = 'otheruser'
config['password'] = 'otherpass'
self.assertEqual(
get_urls('amqps://args1.example,amqps://user:[email protected]'),
['amqps://otheruser:[email protected]',
'amqps://user:[email protected]'])

def test_process_url_noop(self):
valid_url = 'amqp://some.example'
self.assertEqual(process_url(valid_url), valid_url)

def test_process_url_activemq(self):
self.assertEqual(process_url('amqp+ssl://some.example'),
'amqps://some.example')

def test_process_url_user_passwd_config(self):
config['username'] = 'user'
config['password'] = 'pass'
self.assertEqual(process_url('amqps://some.example'),
'amqps://user:[email protected]')

def test_process_url_user_passwd_no_override(self):
config['username'] = 'otheruser'
config['password'] = 'otherpass'
valid_url = 'amqps://user:[email protected]'
self.assertEqual(process_url(valid_url), valid_url)