Skip to content
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
7 changes: 7 additions & 0 deletions .drone.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
image: python2.7
notify:
email:
recipients:
- drone@clever.com
script:
- make test
2 changes: 2 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
## 0.1.0 (2016-03-11)
- Initial version.
22 changes: 22 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
.PHONY: all clean deps format lint publish test
SHELL := /bin/bash

all: format lint test

clean:
find -type f -name '*.pyc' -delete

deps:
python setup.py develop

format:
autopep8 -i -r -j0 -a --experimental --max-line-length 100 --indent-size 2 .

lint:
pep8 --config ./pep8 . || true

publish:
./publish.sh

test: deps
nosetests ./test
84 changes: 83 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,84 @@
# discovery-python
Programmatically find service endpoints (i.e. discovery-go for python)

Programmatically find service endpoints.

This library currently is just an abstraction around reading environment variables used for dependent services.

## Installation

### pip

```sh
pip install git+https://<clever-drone Github token>@github.com/Clever/discovery-python.git@<version_tag>
```

The Github token can be found in [dev-passwords](https://github.com/Clever/clever-ops/tree/master/credentials).

### setup.py

```python
from setuptools import setup

# Assuming discovery v0.1.0 is being installed:
setup(

# ...

install_requires=['discovery==0.1.0'],
dependency_links=[
'https://<clever-drone Github token>@github.com/Clever/discovery-python/tarball/v0.1.0#egg=discovery-0.1.0'
],

# ...

)
```

The Github token can be found in [dev-passwords](https://github.com/Clever/clever-ops/tree/master/credentials).

## Usage

```python
import discovery

try:
redis_url = discovery.url('redis', 'tcp')

redis_host_and_port = discovery.host_port('redis', 'tcp')

redis_host = discovery.host('redis', 'tcp')

redis_port = discovery.port('redis', 'tcp')

except discovery.MissingEnvironmentVariableError as e:
print 'ERROR: Redis discovery failed: {}.'.format(e)

```

## Environment Variables

This library currently requires environment variables to be defined in the following format:

```
SERVICE_{SERVICE_NAME}_{EXPOSE}_{PROTO|HOST|PORT}
```

### Example:
```bash
SERVICE_REDIS_TCP_PROTO = "tcp"
SERVICE_REDIS_TCP_HOST = "localhost"
SERVICE_REDIS_TCP_PORT = "6379"
```

## Development

### Publishing a new version

1. Bump the version in the `VERSION` file and update the changelog in `CHANGES.md`.
2. Merge your changes into `master`.
3. Checkout `master`
4. Run the publish script:

```sh
./publish.sh
```
1 change: 1 addition & 0 deletions VERSION
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0.1.0
3 changes: 3 additions & 0 deletions discovery/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from __future__ import absolute_import

from .discovery import *
70 changes: 70 additions & 0 deletions discovery/discovery.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import logger
import os


_ENV_VAR_TEMPLATE = 'SERVICE_{service_name}_{expose}_{component}'


def url(service_name, expose):
"""
Returns the URL for the given service and exposed interface, based on
environment variables of the form
`SERVICE_{SERVICE NAME}_{EXPOSE}_{PROTO|HOST|PORT}`.
"""
return '{}://{}'.format(proto(service_name, expose), host_port(service_name, expose))


def host_port(service_name, expose):
"""
Returns a `{HOST}:{PORT}` string for the given service and exposed interface,
based on environment variables of the form
`SERVICE_{SERVICE NAME}_{EXPOSE}_{HOST|PORT}`.
"""
return '{}:{}'.format(host(service_name, expose), port(service_name, expose))


def host(service_name, expose):
"""
Returns the host name for the given service and exposed interface, based on
environment variables of the form `SERVICE_{SERVICE NAME}_{EXPOSE}_{HOST}`.
"""
return _get_env_var(service_name, expose, 'HOST')


def port(service_name, expose):
"""
Returns the port for the given service and exposed interface, based on
environment variables of the form `SERVICE_{SERVICE NAME}_{EXPOSE}_{PROTO}`.
"""
return _get_env_var(service_name, expose, 'PORT')


def proto(service_name, expose):
"""
Returns the protocol for the given service and exposed interface, based on
environment variables of the form `SERVICE_{SERVICE NAME}_{EXPOSE}_{PROTO}`.
"""
return _get_env_var(service_name, expose, 'PROTO')


def _get_env_var(service_name, expose, component):
var_name = _ENV_VAR_TEMPLATE.format(
service_name=service_name, expose=expose, component=component)
var_name = var_name.upper().replace('-', '_')

value = os.environ.get(var_name)
if (value is None):
raise MissingEnvironmentVariableError(var_name)

return value


class MissingEnvironmentVariableError(Exception):

"""Raised when a required environment cannot be found."""

def __init__(self, var_name):
self.var_name = var_name

def __str__(self):
return 'Missing environment variable `{}`.'.format(self.var_name)
4 changes: 4 additions & 0 deletions pep8
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[pep8]
ignore = E111
exclude = ./.eggs
max-line-length = 100
19 changes: 19 additions & 0 deletions publish.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#!/bin/bash

# Creates git tags!
# Reads version from VERSION file.

version=`cat VERSION`
changelog=CHANGES.md
grep $version $changelog >> /dev/null
if [[ $? -ne 0 ]]; then
echo "Couldn't find version $version in $changelog"
exit
fi

read -p "Tag as v$version? [y/n] " -n 1 -r
if [[ $REPLY =~ ^[Yy]$ ]]; then
# create git tags
git tag -a v$version -m "version $version"
git push --tags
fi
2 changes: 2 additions & 0 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-r requirements.txt
nose>=1.0
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
kayvee==2.0.1
38 changes: 38 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#!/usr/bin/env python

import os
import sys
from setuptools import setup, find_packages
from pip.req import parse_requirements

import pkg_resources

here = os.path.abspath(os.path.dirname(__file__))

with open(os.path.join(here, 'VERSION')) as f:
VERSION = f.read().strip()

pr_kwargs = {}
if pkg_resources.get_distribution("pip").version >= '6.0':
pr_kwargs = {"session": False}

install_reqs = parse_requirements(
os.path.join(
here,
'./requirements.txt' if not sys.argv[1] in ['develop', 'test'] else './requirements-dev.txt'
), **pr_kwargs)

setup(
name='discovery',
version=VERSION,
author='Clever (https://clever.com)',
author_email='tech-notify@clever.com',
url='https://github.com/Clever/discovery-python/',
packages=['discovery'],
install_requires=[str(ir.req) for ir in install_reqs],
setup_requires=['nose>=1.0'],
test_suite='nose.collector',
long_description="""\
Programmatically find service endpoints.
"""
)
77 changes: 77 additions & 0 deletions test/test_discovery.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import discovery
import os
import unittest

from discovery import MissingEnvironmentVariableError


class TestDiscovery(unittest.TestCase):

def test_host_available(self):
host = 'host-available.ops.clever.com'
os.environ['SERVICE_HOST_AVAILABLE_TCP_HOST'] = host

self.assertEqual(discovery.host('host-available', 'tcp'), host)

def test_host_missing(self):
with self.assertRaisesRegexp(MissingEnvironmentVariableError, 'SERVICE_HOST_MISSING_HTTP_HOST'):
discovery.host('host-missing', 'http')

def test_port_available(self):
port = '5000'
os.environ['SERVICE_PORT_AVAILABLE_TCP_PORT'] = port

self.assertEqual(discovery.port('port-available', 'tcp'), port)

def test_port_missing(self):
with self.assertRaisesRegexp(MissingEnvironmentVariableError, 'SERVICE_PORT_MISSING_HTTP_PORT'):
discovery.port('port-missing', 'http')

def test_proto_available(self):
proto = 'http'
os.environ['SERVICE_PROTO_AVAILABLE_TCP_PROTO'] = proto

self.assertEqual(discovery.proto('proto-available', 'tcp'), proto)

def test_proto_missing(self):
with self.assertRaisesRegexp(
MissingEnvironmentVariableError, 'SERVICE_PROTO_MISSING_HTTP_PROTO'):
discovery.proto('proto-missing', 'http')

def test_host_port_available(self):
host = 'host-port-available.ops.clever.com'
os.environ['SERVICE_HOST_PORT_AVAILABLE_HTTP_HOST'] = host

port = '5000'
os.environ['SERVICE_HOST_PORT_AVAILABLE_HTTP_PORT'] = port

self.assertEqual(discovery.host_port('host-port-available', 'http'), '{}:{}'.format(host, port))

def test_host_port_missing_port(self):
host = 'port-missing.ops.clever.com'
os.environ['SERVICE_PORT_MISSING_HTTP_HOST'] = host

with self.assertRaisesRegexp(MissingEnvironmentVariableError, 'SERVICE_PORT_MISSING_HTTP_PORT'):
discovery.host_port('port-missing', 'http')

def test_url_available(self):
proto = 'http'
os.environ['SERVICE_URL_AVAILABLE_BLAH_PROTO'] = proto

host = 'user:pasws@url-available.ops.clever.com'
os.environ['SERVICE_URL_AVAILABLE_BLAH_HOST'] = host

port = '9090'
os.environ['SERVICE_URL_AVAILABLE_BLAH_PORT'] = port

self.assertEqual(discovery.url('url-available', 'blah'), '{}://{}:{}'.format(proto, host, port))

def test_url_missing_host(self):
proto = 'tcp'
os.environ['SERVICE_PROTO_MISSING_TCP_PROTO'] = proto

port = '9090'
os.environ['SERVICE_PROTO_MISSING_TCP_PORT'] = port

with self.assertRaisesRegexp(MissingEnvironmentVariableError, 'SERVICE_PROTO_MISSING_TCP_HOST'):
discovery.url('proto-missing', 'tcp')