gordon-janitor
: Reconciliation Service for Gordon¶
Cloud DNS reconciliation - a service that checks cloud DNS records against a source of truth and submits corrections to gordon.
Release v0.0.1.dev7 (What’s new?).
Warning
This is still in the planning phase and under active development. Gordon-Janitor should not be used in production, yet.
Development¶
For development and running tests, your system must have all supported versions of Python installed. We suggest using pyenv.
Setup¶
$ git clone git@github.com:spotify/gordon-janitor.git && cd gordon-janitor
# make a virtualenv
(env) $ pip install -r dev-requirements.txt
Running tests¶
To run the entire test suite:
# outside of the virtualenv
# if tox is not yet installed
$ pip install tox
$ tox
If you want to run the test suite for a specific version of Python:
# outside of the virtualenv
$ tox -e py36
To run an individual test, call pytest
directly:
# inside virtualenv
(env) $ pytest tests/test_foo.py
Build docs¶
To generate documentation:
(env) $ pip install -r docs-requirements.txt
(env) $ cd docs && make html # builds HTML files into _build/html/
(env) $ cd _build/html
(env) $ python -m http.server $PORT
Then navigate to localhost:$PORT
!
To watch for changes and automatically reload in the browser:
(env) $ cd docs
(env) $ make livehtml # default port 8888
# to change port
(env) $ make livehtml PORT=8080
Code of Conduct¶
This project adheres to the Open Code of Conduct. By participating, you are expected to honor this code.
User’s Guide¶
Configuring the Gordon Janitor Service¶
Example Configuration¶
An example of a gordon-janitor.toml
file:
# Gordon Janitor Core Config
[core]
plugins = ["foo.plugin"]
debug = false
[core.logging]
level = "info"
handlers = ["syslog"]
# Plugin Config
["foo"]
# global config to the general "foo" package
bar = baz
["foo.plugin"]
# specific plugin config within "foo" package
baz = bla
You may choose to have a gordon-janitor-user.toml
file for development. All tables are deep merged into gordon-janitor.toml
, to limit the amount of config duplication needed. For example, you can override core.debug
without having to redeclare which plugins you’d like to run.
[core]
debug = true
[core.logging]
level = "debug"
handlers = ["stream"]
Supported Configuration¶
The following sections are supported:
core¶
-
plugins
=LIST-OF-STRINGS
¶ Plugins that the Gordon Janitor service needs to load. If a plugin is not listed, the Janitor will skip it even if there’s configuration.
The strings must match the plugin’s config key. See the plugin’s documentation for config key names.
-
debug
=true|false
¶ Whether or not to run the Gordon Janitor service in
debug
mode.If
true
, the Janitor will continue running even if installed & configured plugins can not be loaded. Plugin exceptions will be logged as warnings with tracebacks.If
false
, the Janitor will exit out if it can’t load one or more plugins.
core.logging¶
-
level
=info(default)|debug|warning|error|critical
¶ Any log level that is supported by the Python standard
logging
library.
-
handlers
=LIST-OF-STRINGS
¶ handlers
support any of the following handlers:stream
,syslog
, andstackdriver
. Multiple handlers are supported. Defaults tosyslog
if none are defined.Note
If
stackdriver
is selected,ulogger[stackdriver]
needs to be installed as its dependencies are not installed by default.
Gordon Janitor’s Plugin System¶
Module for loading plugins distributed via third-party packages.
Plugin discovery is done via entry_points
defined in a package’s
setup.py
, registered under 'gordon.plugins'
. For example:
# setup.py
from setuptools import setup
setup(
name=NAME,
# snip
entry_points={
'gordon.plugins': [
'gcp.gpubsub = gordon_gcp.gpubsub:EventClient',
'gcp.gce.a = gordon_gcp.gce.a:ReferenceSourceClient',
'gcp.gce.b = gordon_gcp.gce.b:ReferenceSourceClient',
'gcp.gdns = gordon_gcp.gdns:DNSProviderClient',
],
},
# snip
)
Plugins are initialized with any config defined in gordon-user.toml
and gordon.toml
. See Configuring the Gordon Janitor Service for more details.
Once a plugin is found, the loader looks up its configuration via the
same key defined in its entry point, e.g. gcp.gpubsub
.
The value of the entry point (e.g. gordon_gcp.gpubsub:EventClient
)
must point to a class. The plugin class is instantiated with its config.
A plugin will not have access to another plugin’s configuration. For
example, the gcp.gpusub
will not have access to the configuration
for gcp.gdns
.
See Gordon Janitor’s Plugin System for details on how to write a plugin for Gordon.
-
gordon.plugins_loader.
load_plugins
(config, plugin_kwargs)[source] Discover and instantiate plugins.
Parameters: Returns: list of names of plugins, list of instantiated plugin objects, and any errors encountered while loading/instantiating plugins. A tuple of three empty lists is returned if there are no plugins found or activated in gordon config.
Return type: Tuple of 3 lists
Writing a Plugin¶
Todo
Add documentation once interfaces are firmed up
Plugin Interfaces¶
Interface definitions for Gordon Janitor Plugins.
Please see Gordon Janitor’s Plugin System for more information on writing a plugin for the Gordon Janitor service.
-
interface
gordon_janitor.interfaces.
IAuthority
(config, rrset_channel, metrics=None)[source]¶ Scan source of truth(s) of hosts and emit messages to Reconciler.
The purpose of this client is to consult a source of truth, for example, the list instances APIs in Google Compute Engine or AWS EC2, or consulting one’s own database of hosts. A message per DNS zone with every instance record (per service owner’s own requirements) will then be put onto the
rrset_channel
queue for a Reconciler to - you guessed it - reconcile.Parameters: - config (dict) – Authority-specific configuration.
- rrset_channel (asyncio.Queue) – queue to put record set messages for later validation.
- metrics (obj) – Optional object to emit Authority-specific metrics.
-
run
()¶ Start plugin in the main event loop.
Once required work is all processed,
cleanup()
needs to be called.
-
cleanup
()¶ Cleanup once plugin-specific work is cleanup.
Cleanup work might include allowing outstanding asynchronous Python tasks to finish, cancelling them if they extend beyond a desired timeout, and/or closing HTTP sessions.
-
interface
gordon_janitor.interfaces.
IGenericPlugin
(config, **plugin_kwargs)[source]¶ Do not implement this interface directly.
Use
IAuthority
,IReconciler
, orIPublisher
instead.Parameters: -
run
()¶ Start plugin in the main event loop.
Once required work is all processed,
cleanup()
needs to be called.
-
cleanup
()¶ Cleanup once plugin-specific work is cleanup.
Cleanup work might include allowing outstanding asynchronous Python tasks to finish, cancelling them if they extend beyond a desired timeout, and/or closing HTTP sessions.
-
-
interface
gordon_janitor.interfaces.
IPublisher
(config, changes_channel, metrics=None)[source]¶ Publish change messages to the pub/sub Gordon consumes.
Clients that implement
IPublisher
will consume from thechanges_channel
queue and publish the message to the configured pub/sub for which Gordon subscribes.Parameters: - config (dict) – Publisher-specific configuration.
- changes_channel (asyncio.Queue) – queue to consume the corrective messages needing to be published.
- metrics (obj) – Optional object to emit Publisher-specific metrics.
-
run
()¶ Start plugin in the main event loop.
Once required work is all processed,
cleanup()
needs to be called.
-
cleanup
()¶ Cleanup once plugin-specific work is cleanup.
Cleanup work might include allowing outstanding asynchronous Python tasks to finish, cancelling them if they extend beyond a desired timeout, and/or closing HTTP sessions.
-
interface
gordon_janitor.interfaces.
IReconciler
(config, rrset_channel, changes_channel, metrics=None)[source]¶ Validate current records in DNS against desired Authority.
Clients that implement
IReconciler
will create a change message for the configuredIPublisher
client plugin to consume if there is a discrepancy between records in DNS and the desired state.Once validation is done, the
IReconciler
client will need to emit aNone
message to thechanges_channel
queue, signalling to anIPublisher
client to publish the message to a pub/sub to which Gordon subscribes.Parameters: - config (dict) – Reconciler-specific configuration.
- rrset_channel (asyncio.Queue) – queue from which to consume record set messages to validate.
- changes_channel (asyncio.Queue) – queue to publish corrective messages to be published.
- metrics (obj) – Optional object to emit Reconciler-specific metrics.
-
run
()¶ Start plugin in the main event loop.
Once required work is all processed,
cleanup()
needs to be called.
-
cleanup
()¶ Cleanup once plugin-specific work is cleanup.
Cleanup work might include allowing outstanding asynchronous Python tasks to finish, cancelling them if they extend beyond a desired timeout, and/or closing HTTP sessions.
API Reference¶
main¶
Main module to run the Gordon Janitor service.
The service expects a gordon-janitor.toml
and/or a
gordon-janitor-user.toml
file for configuration in the current
working directory, or in a provided root directory.
Any configuration defined in gordon-janitor-user.toml
overwrites
those in gordon-janitor.toml
.
Example:
$ python gordon_janitor/main.py
$ python gordon_janitor/main.py -c /etc/default/
$ python gordon_janitor/main.py --config-root /etc/default/
-
gordon_janitor.main.
setup
(config_root='')[source]¶ Service configuration and logging setup.
Configuration defined in
gordon-janitor-user.toml
will overwritegordon-janitor.toml
.Parameters: config_root (str) – where configuration should load from, defaults to current working directory. Returns: A dict for Gordon service configuration
-
gordon_janitor.main.
setup
(config_root='')[source] Service configuration and logging setup.
Configuration defined in
gordon-janitor-user.toml
will overwritegordon-janitor.toml
.Parameters: config_root (str) – where configuration should load from, defaults to current working directory. Returns: A dict for Gordon service configuration
plugins_loader¶
Module for loading plugins distributed via third-party packages.
Plugin discovery is done via entry_points
defined in a package’s
setup.py
, registered under 'gordon.plugins'
. For example:
# setup.py
from setuptools import setup
setup(
name=NAME,
# snip
entry_points={
'gordon.plugins': [
'gcp.gpubsub = gordon_gcp.gpubsub:EventClient',
'gcp.gce.a = gordon_gcp.gce.a:ReferenceSourceClient',
'gcp.gce.b = gordon_gcp.gce.b:ReferenceSourceClient',
'gcp.gdns = gordon_gcp.gdns:DNSProviderClient',
],
},
# snip
)
Plugins are initialized with any config defined in gordon-user.toml
and gordon.toml
. See Configuring the Gordon Janitor Service for more details.
Once a plugin is found, the loader looks up its configuration via the
same key defined in its entry point, e.g. gcp.gpubsub
.
The value of the entry point (e.g. gordon_gcp.gpubsub:EventClient
)
must point to a class. The plugin class is instantiated with its config.
A plugin will not have access to another plugin’s configuration. For
example, the gcp.gpusub
will not have access to the configuration
for gcp.gdns
.
See Gordon Janitor’s Plugin System for details on how to write a plugin for Gordon.
-
gordon.plugins_loader.
load_plugins
(config, plugin_kwargs)[source]¶ Discover and instantiate plugins.
Parameters: Returns: list of names of plugins, list of instantiated plugin objects, and any errors encountered while loading/instantiating plugins. A tuple of three empty lists is returned if there are no plugins found or activated in gordon config.
Return type: Tuple of 3 lists
-
gordon.plugins_loader.
load_plugins
(config, plugin_kwargs)[source] Discover and instantiate plugins.
Parameters: Returns: list of names of plugins, list of instantiated plugin objects, and any errors encountered while loading/instantiating plugins. A tuple of three empty lists is returned if there are no plugins found or activated in gordon config.
Return type: Tuple of 3 lists
Project Information¶
License and Credits¶
gordon-janitor
is licensed under the Apache 2.0 license.
The full license text can be also found in the source code repository.
How to Contribute¶
Every open source project lives from the generous help by contributors that sacrifice their time and gordon-janitor
is no different.
This project adheres to the Open Code of Conduct. By participating, you are expected to honor this code. If the core project maintainers/owners feel that this Code of Conduct has been violated, we reserve the right to take appropriate action, including but not limited to: private or public reprimand; temporary or permanent ban from the project; request for public apology.
Communication/Support¶
Feel free to drop by the Spotify FOSS Slack organization in the #gordon channel.
Contributor Guidelines/Requirements¶
Contributors should expect a response within one week of an issue being opened or a pull request being submitted. More time should be allowed around holidays. Feel free to ping your issue or PR if you have not heard a timely response.
Submitting Bugs¶
Before submitting, users/contributors should do the following:
- Basic troubleshooting:
- Make sure you’re on the latest supported version. The problem may be solved already in a later release.
- Try older versions. If you’re on the latest version, try rolling back a few minor versions. This will help maintainers narrow down the issue.
- Try the same for dependency versions - up/downgrading versions.
- Search the project’s issues to make sure it’s not already known, or if there is already an outstanding pull request to fix it.
- If you don’t find a pre-existing issue, check the discussion on Slack. There may be some discussion history, and if not, you can ask for help in figuring out if it’s a bug or not.
What to include in a bug report:
- What version of Python is being used? i.e. 2.7.13, 3.6.2, PyPy 2.0
- What operating system are you on? i.e. Ubuntu 14.04, RHEL 7.4
- What version(s) of the software are you using?
- How can the developers recreate the bug? Steps to reproduce or a simple base case that causes the bug is extremely helpful.
Contributing Patches¶
No contribution is too small. We welcome fixes for typos and grammar bloopers just as much as feature additions and fixes for code bloopers!
- Check the outstanding issues and pull requests first to see if development is not already being done for what you which to change/add/fix.
- If an issue has the
available
label on it, it’s up for grabs for anyone to work on. If you wish to work on it, just comment on the ticket so we can remove theavailable
label. - Do not break backwards compatibility.
- Once any feedback is addressed, please comment on the pull request with a short note, so we know that you’re done.
- Write good commit messages.
Workflow¶
- This project follows the gitflow branching model. Please name your branch accordingly.
- Always make a new branch for your work, no matter how small. Name the branch a short clue to the problem you’re trying to fix or feature you’re adding.
- Ideally, a branch should map to a pull request. It is possible to have multiple pull requests on one branch, but is discouraged for simplicity.
- Do not submit unrelated changes on the same branch/pull request.
- Multiple commits on a branch/pull request is fine, but all should be atomic, and relevant to the goal of the PR. Code changes for a bug fix, plus additional tests (or fixes to tests) and documentation should all be in one commit.
- Pull requests should be rebased off of master.
- To finish and merge a release branch, project maintainers should first create a PR to merge the branch into
develop
. Then, they should merge the release branch intomaster
locally and push to master afterwards. - Bugfixes meant for a specific release branch should be merged into that branch through PRs.
Code¶
- See docs on how to setup your environment for development.
- Code should follow the Google Python Style Guide.
- Documentation is not optional.
- Docstrings are required for public API functions, methods, etc. Any additions/changes to the API functions should be noted in their docstrings (i.e. “added in 2.5”)
- If it’s a new feature, or a big change to a current feature, consider adding additional prose documentation, including useful code snippets.
- Tests aren’t optional.
- Any bug fix should have a test case that invokes the bug.
- Any new feature should have test coverage hitting at least $PERCENTAGE.
- Make sure your tests pass on our CI. You will not get any feedback until it’s green, unless you ask for help.
- Write asserts as “expected == actual” to avoid any confusion.
- Add good docstrings for test cases.
Github Labels¶
The super secret decoder ring for the labels applied to issues and pull requests.
Triage Status¶
needs triaging
: a new issue or pull request that needs to be triaged by the goalieno repro
: a filed (closed) bug that can not be reproduced - issue can be reopened and commented upon for more informationwon’t fix
: a filed issue deemed not relevant to the project or otherwise already answered elsewhere (i.e. questions that were answered via linking to documentation or stack overflow, or is about GCP products/something we don’t own)duplicate
: a duplicate issue or pull requestwaiting for author
: issue/PR has questions or requests feedback, and is awaiting the other for a response/update
Development Status¶
To be prefixed with Status:
, e.g. Status: abandoned
.
abandoned
: issue or PR is stale or otherwise abandonedavailable
: bug/feature has been confirmed, and is available for anyone to work on (but won’t be worked on by maintainers)blocked
: issue/PR is blocked (reason should be commented)completed
: issue has been addressed (PR should be linked)wip
: issue is currently being worked onon hold
: issue/PR has development on it, but is currently on hold (reason should be commented)pending
: the issue has been triaged, and is pending prioritization for development by maintainersreview needed
: awaiting a review from project maintainers
Types¶
To be prefixed with Type:
e.g. Type: bug
.
bug
: a bug confirmed via triagefeature
: a feature request/idea/proposalimprovement
: an improvement on existing featuresmaintenance
: a task for required maintenance (e.g. update a dependency for security patches)extension
: issues, feature requests, or PRs that support other services/libraries separate from core
Local Development Environment¶
TODO