Initial baseline of OpenStack Charm documentation

This commit is contained in:
James Page 2016-06-20 11:58:44 +01:00
commit cf6a5a2e15
20 changed files with 1916 additions and 0 deletions

20
.gitignore vendored Normal file
View File

@ -0,0 +1,20 @@
# Testenvironment
.tox/*
# Build directories
doc/build/*
# generated during doc builds
AUTHORS
ChangeLog
# Packages
*.egg-info/*
# Editors
*~
.*.swp
.bak
# For Mac Users
.DS_Store

4
.gitreview Normal file
View File

@ -0,0 +1,4 @@
[gerrit]
host=review.openstack.org
port=29418
project=openstack/openstack-charm-guide.git

202
LICENSE Normal file
View File

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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.

10
README.rst Normal file
View File

@ -0,0 +1,10 @@
=====================
OpenStack Charm Guide
=====================
To build the guide, execute the following command::
$ tox
After running ``tox``, the documentation will be available for viewing
in HTML format in the ``doc/build/`` directory. View these new documents
in your favorite web browser.

View File

@ -0,0 +1,17 @@
.. _backporting:
Backporting Policy
==================
This page documents the OpenStack Charms backport policy.
Backport candidates
-------------------
- Critical and High bugs fixes, if reported in Launchpad.
Backport exclusions
-------------------
- Medium and Low bug fixes
- Features

View File

@ -0,0 +1,676 @@
.. _coding_guidelines:
=================
Coding Guidelines
=================
Introduction
------------
We write code for OpenStack charms. Mostly in Python. They say that code is
read roughly 20 times more than writing it, and thats just the process of
writing code. Reviewing code and modifying it means that it will be read many,
many times. Lets make it as easy as possible. Were lucky(!) with Python as
the syntax ensures that it roughly always looks the same.
As OpenStack charms are for OpenStack its a good idea to adhere to the
OpenStack Python coding standard. So first things first:
* Read the `OpenStack Coding standard <http://docs.openstack.org/developer/hacking/>`__.
* Read PEP8 (again).
Topics
------
Multiple roots -- symlinks
~~~~~~~~~~~~~~~~~~~~~~~~~~
Multiple roots with symlinks create issues in charms. This is where, for
example, charmhelpers is symlinked into both a hooks and actions subdirectory.
This creates a situation where the *same* modules are loaded into the Python
interpreter's memory twice at two different module paths. This creates
problems with testing as *depending on the load time ordering* it might not be
clear which particular module path you're trying to mock out and which one is
first in the module path map.
So only every have ONE root for your python code in a charm. e.g. put it in
``/lib`` and add that to path by ``sys.path.append(lib).``
Incidentally, if you **are** mocking out code in charmhelpers in you charms,
**it's probably not a good idea**. Only mock code in the target object file,
rather than an included module.
Install-time vs Load-time vs Runtime code
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The hooks in charms are effectively short-term running scripts. However,
despite being short-lived, the code invoked is often complex with multiple
modules being imported which also import other modules.
Its important to be clear on what is *load time* code and _runtime_ code.
Although there is no actual distinction in Python, its useful to think of
*runtime* starting when the following code is reached:
.. code:: python
if __name__ == __main__:
do_something()
I.e. the code execution of ``do_something()`` is runtime, with everything
preceding being loadtime.
So why is the distinction useful? Put simply, *its much harder to test*
load-time code in comparison to runtime code with respect to mocking. Consider
these two fragments:
**Bad:**
.. code:: python
import a.something
OUR_CONFIG = {
some_thing: a.something.config(a-value),
}
**Good:**
.. code:: python
import a.something
def get_our_config():
return {
some_thing: a.something.config(a-value),
}
If performance is an issue (i.e. multiple calls to ``config()`` are expensive)
then either use a ``@caching`` type decorator, or just doing it manually. e.g.
.. code:: python
_our_config = None
def get_our_config():
if _our_config is None:
_our_config = {
'some_thing': a.something.config('a-value'),
}
return _our_config
In the bad example, in order to mock out the config module we have to do
something like:
.. code:: python
with patch(a.something.config) as mock_config:
import a.something.config
This also relies on this being the _first_ time that module has been imported.
Otherwise, the module is already cached and config cant be mocked out.
Compare this with the good example.
.. code:: python
def test_out_config(self):
with patch(module.a.something.config) as mock_config:
mock_config.return_value = thing
x = model.get_out_config()
This brings us to:
CONSTANTS should be simple
~~~~~~~~~~~~~~~~~~~~~~~~~~
In the bad example above, the constant ``OUR_CONFIG`` is defined as load-time by
calling ``a.something.config()``. Thus, in reality, the constant is being
defined at load-time using a runtime function that returns a value - it's
dynamic.
Dont:
.. code:: python
CONFIG = {
some_key: config(something),
}
This is actually a *function in disguise*.
Prefer:
.. code:: python
def get_config():
return {
some_key: config(something),
}
Why?
So that you can mock out ``get_config()`` or ``config()`` at the test run time,
rather than before the module loads. This makes testing easier, more
predictable, and also makes it obvious that its not really a constant, but
actually a function which returns a structure that is dynamically generated
from configuration.
And **definitely** dont do this at the top level in a file:
.. code:: python
CONFIGS = register_configs()
Youve just created a load time test problem _and_ created a CONSTANT that
isnt really one. Just use ``register_configs()`` directly in the code and write
``register_configs()`` to be ``@cached`` if performance is an issue.
Decorators
~~~~~~~~~~
There shouldn't be much need to write a decorator. They definitely **should
not** be used instead of function application or instead of context managers.
When they are used it's preferable that they are orthogonal to the function
they are decorating, and don't change the nature of the function.
functools.wraps(f)
++++++++++++++++++
If they are used, then they should definitely make use of ``functools.wraps`` to
preserve the function name of the original function and it's docstring. This
makes stacktraces more readable. e.g.:
.. code:: python
def my_decorator(f):
functools.wraps(f):
def decoration(*args, **kwargs):
# do soemthing before the function call?
r = f(*args, **kwargs)
# do soemthing after the function call?
return r
return decoration
Mocking out decorators
++++++++++++++++++++++
If the decorator's functionality is orthogonal to the function, then mocking
out the decorator shouldn't be necessary. However, if it *isn't* then tweaking
how the decorator is written can make it easier to mock out the decorator.
Consider the following code:
.. code:: python
@a_decorator("Hello")
def some_function():
pass
def a_decorator(name):
def outer(f):
@functools.wraps(f)
def inner(*args, **kwargs):
# do something before the function
r = f(*args, **kwargs)
# do something after the function
return r
return inner
return outer
It's very difficult to test some_function without invoking the decorator, and
equally, it's difficult to stop the decorator from being applied to the
function without mocking out ``@a_decorator`` before importing the module under
test.
However, with a little tweaking of the decorator we can mock out the decorator
without having to jump through hoops:
.. code:: python
def a_decorator(name):
def outer(f):
@functools.wraps(f)
def inner(*args, **kwargs):
return _inner(name, args, kwargs)
return inner
return outer
def _inner(name, args, kwargs):
# do something before the function
r = f(*args, **kwargs)
# do something afterwards
return r
Now, we can easily mock ``_inner()`` after the module has been loaded, thus
changing the function of the decorator _after_ it has been applied.
Import ordering and style
~~~~~~~~~~~~~~~~~~~~~~~~~
Let's be consistent and ensure that we have the same import ordering and style
across all of the charms (and other code) that we release.
Use absolute imports
++++++++++++++++++++
Use absolute imports. In Python 2 code this means also that we should force
absolute imports:
.. code:: python
from __future__ import absolute_import
We should use absolute imports so that we don't run into module name clashes
across our own modules, nor with system and 3rd party packages. See
https://www.python.org/dev/peps/pep-0328/#id8 for more details.
Import ordering
+++++++++++++++
* Core Python system packages
* Third party modules
* Local modules
They should be be alphabetical order, with a single space between them, and
preferably in alphabetical order. If load order is important (and it shouldnt
be!) then thats the only reason they shouldnt be in alpha order.
Import Style
++++++++++++
It's preferable to import a module rather than an object, class, function or
instance from a module.
Prefer:
.. code:: python
import module
module.function()
over:
.. code:: python
from module import function
function()
However, if there are good reasons to import from a module, and there is more
than one item, then the style is:
.. code:: python
from module import (
one_import_per_line,
)
Why?
Using ``import module; module.function()`` rather than ``from module import
function`` is preferable because:
* with multiple imports, more symbols are being brought into the importing
modules namespace.
* It's clearer in the code when an external function is being used, as it is
always prefixed by the external module name. This is useful as it makes it
more obvious what is happening in the code.
Only patch mocks in the file/module under test
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
A unit test often needs to mock out functions, classes or instances in the file
under test. The mocks should _only_ be applied to the file that contains the
item that is being tested.
Don't:
.. code:: python
# object.py
import something
def function_under_test(x):
return something.doing(x)
In the unit test file: ``test_unit.py``:
.. code:: python
# test_unit.py
def unit_test():
with patch('something.doing') as y:
y.return_value = 5
assert function_under_test(3) == 5
Prefer:
.. code:: python
# object.py
import something
def function_under_test(x):
return something.doing(x)
In the unit test file: ``test_unit.py``:
.. code:: python
# test_unit.py
def unit_test():
with patch('object.something.doing') as y:
y.return_value = 5
assert function_under_test(3) == 5
i.e. the thing that is patched is in object.py **not** in the library file
'something.py'
Don't use _underscore_methods outside of the class
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Underscore methods are supposed to be, by convention, private to the enclosing
scope, be that a module or a class. They are used to signal that the method is
_private_ even though the privacy can't be enforced.
Thus don't do this:
.. code:: python
class A():
def _private_method():
pass
x = A()
x._private_method()
Simply rename the method without the underscore. Otherwise you break the
convention and people will not understand how you are using *private methods*.
Equally, don't use them in derived classes _either_. A private method is
supposed to be private to the class, and not used in derived classes.
Only use list comprehensions when you want the list
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Dont:
.. code:: python
[do_something_with(thing) for thing in mylist]
Prefer:
.. code:: python
for thing in mylist:
do_something_with(thing)
Why?
You just created a list and then threw it away. And its actually less clear
what you are doing. Do use list comprehensions when you actually want a list
to do something with.
Avoid C-style dictionary access in loops
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Dont:
.. code:: python
for key in dictionary:
do_something_with(key, dictionary[key])
Prefer:
.. code:: python
for key, value in dictionary.items():
do_something_with(key, value)
Why?
Using a list of keys to access a dictionary is less efficient and less obvious
as to whats happening. ``key, value`` could actually be ``config_name`` and
``config_item`` which means the code is more self-documenting.
Also remember that ``dictionary.keys()`` & ``dictionary.values()`` exist if you
want to explicitly iterate just over the keys or values of a dictionary. Also,
its preferable to iterate of ``dictionary.keys()`` rather than ``dictionary``
because, whilst they do the same thing, its not as obvious what is happening.
If performance is an issue (Python2) then ``iterkeys()`` and ``itervalues()`` for
generators, which is the default on Python3.
Prefer tuples to lists
~~~~~~~~~~~~~~~~~~~~~~
Tuples are non malleable lists, and should be used where the list isnt going
to change. They have (slight) performance advantages, but come with a
guarantee that the list wont change - note the objects within the tuple could
change, just not their position or reference.
Thus dont:
.. code:: python
if x in [hello, there]:
do_something()
Prefer:
.. code:: python
if x in (hello, there):
do_something()
However, remember the caveat. A single item tuple literal has to have a
trailing comma:
.. code:: python
my_tuple = (item, )
Prefer CONSTANTS to string literals or numbers
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This is the “No magic numbers” rule. In a lot of the OS charms there is code
like:
.. code:: python
db = kv()
previous_thing = db.get('thing_key', thing)
Prefer:
.. code:: python
THING_KEY = thing_key
db = kv()
previous_thing = db.get(THING_KEY, thing)
Why?
String literals introduce a vector for mistakes. We cant use the language to
help prevent spelling mistakes, nor our tools to do autocompletion, nor use
lint to find undefined variables. This also means that if you use the same
number or string literal more than once in code you should create a constant
for that value and use that in code. This includes fixed array accesses,
offsets, etc.
Dont abuse __call__()
~~~~~~~~~~~~~~~~~~~~~~
``__call__()`` is a method that is invoked when ``()`` is invoked on an object --
``()`` on a class invokes ``__call__`` on the metaclass for the class.
A good example of abuse of ``__call__`` is the class ``HookData()`` which, to
access the context manager, is invoked as:
.. code:: python
with HookData()() as hd:
hd.kv.set(...)
The sequence ``()()`` is almost certainly a *code smell*. There is hidden
behaviour that requires you to go to the class to see what is actually
happening. It would have been more obvious if that method was just called
``cm()`` or ``context()``:
.. code:: python
with HookData().context() as hd:
hd.kv.set(...)
Dont use old style string interpolation
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code:: python
action_fail("Cannot remove service: %s" % service.host)
Prefer:
.. code:: python
action_fail("Cannot remove service: {}".format(service.host))
Why?
Its the new style, and the old style is deprecated; eventually it will be
removed. Plus the new style is way more powerful: keywords, dictionary
support, to name but a few.
Docstrings and comments
~~~~~~~~~~~~~~~~~~~~~~~
Every function exported by a module should have a docstring. Generally, this
means all functions mentioned in ``__ALL__`` or implicitly those that do not
start with an ``_``.
The preferred format for documenting parameters and return values is
ReStructuredText (reST) as described: http://docutils.sourceforge.net/rst.html
The field lists are described here:
http://www.sphinx-doc.org/en/stable/domains.html#info-field-lists
An example of an acceptable function docstring is:
.. code:: python
def mult(a, b):
"""Multiple a * b and return the result.
:param a: Number
:param b: Number
:returns Number: a * b
:raises: ValueError, TypeError if the params are not numbers
"""
return a * b
Other comments should be used to support the code, but not just re-say what the
code is doing.
Ensure there's a comma on the last item of a dictionary
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This helps when the developer adds an item to a dictionary literal, in that
they don't have to edit the previous line to add a comma. It also means that
the review doesn't indicate that the previous line has changed (due to the
addition of a comma).
Prefer:
.. code:: python
a_dict = {
'one': 1,
'two': 2,
}
over:
.. code:: python
a_dict = {
'one': 1,
'two': 2
}
Avoid dynamic default arguments in functions
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Don't use a dynamic assignment to a default argument. e.g.
.. code:: python
def a(b=[]):
b.append('hello')
print b
In [2]: a()
['hello']
In [3]: a()
['hello', 'hello']
As you can see, the list is only assigned the first time, and thereafter it
'remember' the previous values.
Also avoid other default, dynamic, assignments:
.. code:: python
def f():
return ['Hello']
def a(b=f()):
b.append('there')
print b
In [3]: a()
['Hello', 'there']
In [4]: a()
['Hello', 'there', 'there']
Instead, prefer:
.. code:: python
def a(b=None):
if b is None:
b = f()
b.append('there')
print b
In [6]: a()
['Hello', 'there']
In [7]: a()
['Hello', 'there']
Why?
Although it can be a handy side-effect for allowing a function to remember
previous values, due to a quirk in the interpreter in only assigning the
reference once, it may be changed in the future and it hides the intention of
the code.

274
doc/source/conf.py Normal file
View File

@ -0,0 +1,274 @@
# -*- coding: utf-8 -*-
#
# Tempest documentation build configuration file, created by
# sphinx-quickstart on Tue May 21 17:43:32 2013.
#
# This file is execfile()d with the current directory set to its containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
import datetime
import sys
import os
from jinja2.utils import Markup
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#sys.path.insert(0, os.path.abspath('.'))
# -- General configuration -----------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = ['sphinx.ext.autodoc',
'sphinx.ext.todo',
'sphinx.ext.viewcode',
'oslosphinx'
]
todo_include_todos = True
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix of source filenames.
source_suffix = '.rst'
# The encoding of source files.
#source_encoding = 'utf-8-sig'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = u'OpenStack Charms Guide'
copyright = Markup(u'%s, OpenStack Contributors '
u'- use the <a href="https://git.openstack.org/cgit/'
u'openstack/openstack-charms-guide">openstack-charms-guide git repo</a> '
u'to propose changes' % datetime.date.today().year)
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#language = None
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = ['_build']
# The reST default role (used for this markup: `text`) to use for all documents.
#default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
add_module_names = False
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# A list of ignored prefixes for module index sorting.
modindex_common_prefix = ['puppet-openstack-guide.']
# -- Options for man page output ----------------------------------------------
man_pages = []
# -- Options for HTML output ---------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
html_theme = 'nature'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory.
#html_theme_path = []
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
#html_title = None
# A shorter title for the navigation bar. Default is the same as html_title.
#html_short_title = None
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
#html_logo = None
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#html_favicon = None
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
git_cmd = "git log --pretty=format:'%ad, commit %h' --date=local -n1"
html_last_updated_fmt = os.popen(git_cmd).read()
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
#html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
#html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
#html_additional_pages = {}
# If false, no module index is generated.
html_domain_indices = False
# If false, no index is generated.
html_use_index = False
# If true, the index is split into individual pages for each letter.
#html_split_index = False
# If true, links to the reST sources are added to the pages.
#html_show_sourcelink = True
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
#html_show_sphinx = True
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
#html_show_copyright = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
#html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = None
# Output file base name for HTML help builder.
htmlhelp_basename = 'OpenStack-Charms-Guidedoc'
# -- Options for LaTeX output --------------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#'preamble': '',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual]).
latex_documents = [
('index', 'OpenStack-Charms-guide.tex', u'OpenStack Charms Guide',
u'OpenStack Contributors', 'manual'),
]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
#latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
#latex_use_parts = False
# If true, show page references after internal links.
#latex_show_pagerefs = False
# If true, show URL addresses after external links.
#latex_show_urls = False
# Documents to append as an appendix to all manuals.
#latex_appendices = []
# If false, no module index is generated.
#latex_domain_indices = True
# -- Options for Texinfo output ------------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
('index', 'OpenStack-Charms-guide', u'OpenStack Charms Guide',
u'OpenStack Contributors', 'OpenStack-Charms-guide',
'OpenStack Charms Guide.',
'Miscellaneous'),
]
# Documents to append as an appendix to all manuals.
#texinfo_appendices = []
# If false, no module index is generated.
#texinfo_domain_indices = True
# How to display URL addresses: 'footnote', 'no', or 'inline'.
#texinfo_show_urls = 'footnote'
# -- Options for Epub output ---------------------------------------------------
# Bibliographic Dublin Core info.
epub_title = u'OpenStack Charms Guide'
epub_author = u'OpenStack Contributors'
epub_publisher = u'OpenStack Contributors'
epub_copyright = u'%s, OpenStack Contributors' % datetime.date.today().year
# The language of the text. It defaults to the language option
# or en if the language is not set.
#epub_language = ''
# The scheme of the identifier. Typical schemes are ISBN or URL.
#epub_scheme = ''
# The unique identifier of the text. This can be a ISBN number
# or the project homepage.
#epub_identifier = ''
# A unique identification for the text.
#epub_uid = ''
# A tuple containing the cover image and cover page html template filenames.
#epub_cover = ()
# HTML files that should be inserted before the pages created by sphinx.
# The format is a list of tuples containing the path and title.
#epub_pre_files = []
# HTML files shat should be inserted after the pages created by sphinx.
# The format is a list of tuples containing the path and title.
#epub_post_files = []
# A list of files that should not be packed into the epub file.
#epub_exclude_files = []
# The depth of the table of contents in toc.ncx.
#epub_tocdepth = 3
# Allow duplicate toc entries.
#epub_tocdup = True

15
doc/source/find-us.rst Normal file
View File

@ -0,0 +1,15 @@
==========
Talk to us
==========
* Talk to us on IRC channel ``#openstack-charms`` on Freenode.
* Join the conversation on our `Mailing list <http://docs.openstack.org/developer/openstack-charm-guide/mailing-list.html>`_.
* Participate to our `Online Meetings <http://docs.openstack.org/developer/openstack-charm-guide/meetings.html>`_.
.. toctree::
:maxdepth: 1
:hidden:
mailing-list
meetings

View File

@ -0,0 +1,23 @@
===============
Getting started
===============
* The OpenStack Charms use Juju to deploy and manage OpenStack services, so
getting to know Juju_ is a great first step.
* You might want to tryout the charms on your laptop using the Juju LXD
provider and the
`OpenStack on LXD bundle <http://github.com/openstack-charmers/openstack-on-lxd>`__
if you don't have physical servers to use or just want to contribute a fix
or change.
* For bare-metal deployment, Juju uses MAAS_ to provision and configure
physical servers, so that's a good next step.
* Once you have MAAS setup, you can try out the
`OpenStack Base bundle <http://jujucharms.com/openstack-base>`__ from
the Juju charm store.
.. _Juju: https://jujucharms.com/docs/devel/getting-started
.. _MAAS: http://maas.io/get-started
.. toctree::
:maxdepth: 1
:hidden:

View File

@ -0,0 +1,22 @@
=================
How to contribute
=================
The OpenStack charms are part of the OpenStack project, and follow the
same development process as other projects.
For details on how to submit changes to an OpenStack project please refer
to the OpenStack `development documentation <http://docs.openstack.org/infra/manual/developers.html>`__.
and then take a read through the rest of this section on how to contribute
to the OpenStack Charms.
.. toctree::
:titlesonly:
:maxdepth: 1
coding-guidelines
testing
making-a-change
new-charm
backport-policy
reviews

21
doc/source/index.rst Normal file
View File

@ -0,0 +1,21 @@
====================================
Welcome to the OpenStack Charm Guide
====================================
The OpenStack charms deliver fast, repeatable OpenStack deployment
with lose coupling between OpenStack Services.
.. toctree::
:maxdepth: 2
:includehidden:
getting-started
how-to-contribute
find-us
====================
Indices and tables
====================
* :ref:`genindex`
* :ref:`search`

View File

@ -0,0 +1,16 @@
.. _mailing_list:
=============
Mailing lists
=============
Please use the mailing list when possible as it makes the information
discussed more readily available so that others who have the same
question can search for (and find) those answers.
- Dev discussions:
`openstack-dev@lists.openstack.org <http://lists.openstack.org/cgi-bin/mailman/listinfo/openstack-dev>`__
with ``[charms]`` tag.
- Usage discussions:
`openstack-operators@lists.openstack.org <http://lists.openstack.org/cgi-bin/mailman/listinfo/openstack-operators>`__
with ``[charms]`` tag.

View File

@ -0,0 +1,79 @@
Making a change
===============
Development Workflow
~~~~~~~~~~~~~~~~~~~~
Broadly the workflow for making a change to a charm is:
.. code:: bash
git clone http://github.com/openstack/charm-cinder
cd charm-cinder
git checkout -b bug/XXXXXX master
Make the changes you need within the charm, and then run unit and pep8 tests using tox:
.. code:: bash
tox
resolve any problems this throws up, and then commit your changes:
.. code:: bash
git add .
git commit
Commit messages should have an appropriate title, and more detail in the body; they
can also refer to bugs:
.. code:: bash
Closes-Bug: #######
Partial-Bug: #######
Related-Bug: #######
Gerrit will automatically link your proposal to the bug reports on launchpad and
mark them fix commited when changes are merged.
Execute pep8 and unit tests:
.. code:: bash
tox
Finally, submit your change for review (if they pass pep8 and unit tests!):
.. code:: bash
git review
This will push your proposed changes to Gerrit and provide you with a URL for the
review board on http://review.openstack.org/.
To make amendments to your proposed change, update your local branch and then:
.. code:: bash
git commit --amend
git review
Stable charm updates
~~~~~~~~~~~~~~~~~~~~
Any update to a stable charm must first be applied into the master branch; it should
then be cherry-picked in a review for the stable branch corresponding to your target
release (ensuring that any interim releases have the fix landed):
.. code:: bash
git checkout -b stable/bug/XXXX stable/YYYY
git cherry-pick -x <hash of master branch commit>
git review
Where XXXX is the launchpad bug ID corresponding to the fix you want to backport and
YYYY is the release name you are targeting e.g. 16.04
.. note:: when cherry-picking a commit and/or modifying the commit message, always ensure that
the original Change-Id is left intact.

44
doc/source/meetings.rst Normal file
View File

@ -0,0 +1,44 @@
.. _meetings:
########
Meetings
########
1. `Weekly meeting`_
2. `Next meeting`_
3. `Previous meetings`_
4. `Meeting organizers`_
Weekly meeting
==============
If you're interested in the OpenStack Charms, we hold public meetings weekly on
``#ubuntu-meeting`` on freenode.
.. list-table::
:widths: 25 75
:header-rows: 1
* - Meeting Time
- Local Time
* - UTC 1700 Wednesdays
- http://www.timeanddate.com/worldclock/fixedtime.html?msg=OpenStack+Charms&iso=20160622T17
Next meeting
============
- `Wednesday Jun 22, 2016 @ 1700 UTC
<http://www.timeanddate.com/worldclock/fixedtime.html?msg=OpenStack+Charms&iso
=20160622T17>`_ on ``#ubuntu-meeting`` on freenode
Agenda
======
https://etherpad.openstack.org/p/openstack-charms-weekly-meeting-20160622
Previous meetings
=================
Meeting organizers
==================

353
doc/source/new-charm.rst Normal file
View File

@ -0,0 +1,353 @@
.. _new_api_charm:
New API Charm
=============
Overview
--------
This guide will walk through the creation of a basic API charm for the Openstack
`Congress <https://wiki.openstack.org/wiki/Congress>`__ service.
The charm will use prewritten Openstack `layers and interfaces <https://github.com/openstack-charmers>`__.
Once the charm is written it will be composed using `charm tools <https://github.com/juju/charm-tools/>`__.
The Congress service needs to register endpoints with Keystone. It needs
a service username and password and it also needs a MySQL backend to
store its schema.
Create the skeleton charm
-------------------------
Firstly create a directory for the new charm and manage the charm with git.
.. code:: bash
mkdir -p congress/src
cd congress
git init
The top layer of this charm is the Congress specific code this code will live in the charm subdirectory.
.. code:: bash
mkdir -p src/{reactive,lib/charm/openstack}
Describe the Service and required layer(s)
------------------------------------------
The new charm needs a basic src/metadata.yaml to describe what service the charm provides. Edit src/metadata.yaml
.. code:: yaml
name: congress
summary: Policy as a service
description: |
Congress is an open policy framework for the cloud. With Congress, a cloud
operator can declare, monitor, enforce, and audit "policy" in a heterogeneous
cloud environment.
The `openstack-api layer <https://github.com/openstack-charmers/charm-layer-openstack-api>`__
defines a series of config options and interfaces which are mostly common across Openstack
API services e.g. including the openstack-api-layer will pull in the Keystone and MySQL
interfaces (among others) as well as the charm layers the new Congress charm can
leverage.
To instruct "charm build" to pull in the openstack-api layer edit src/layer.yaml:
.. code:: yaml
includes: ['layer:openstack-api']
Add Congress configuration
--------------------------
Define Congress attributes
~~~~~~~~~~~~~~~~~~~~~~~~~~
There is a base OpenStackCharm class which provides the skeleton for the charm.
Creating a child class from OpenStackCharm allows Congress specific attributes
to be set, like which packages to install, which config files need rendering
etc. This is all done in the src/lib/charm/openstack/congress.py file.
.. code:: python
import charmhelpers.contrib.openstack.utils as ch_utils
import charms_openstack.charm
import charms_openstack.adapters
import charms_openstack.ip as os_ip
class CongressCharm(charms_openstack.charm.OpenStackCharm):
service_name = 'congress'
release = 'mitaka'
# Packages the service needs installed
packages = ['congress-server', 'congress-common', 'python-antlr3',
'python-pymysql']
# Init services the charm manages
services = ['congress-server']
# Standard interface adapters class to use.
adapters_class = charms_openstack.adapters.OpenStackRelationAdapters
# Ports that need exposing.
default_service = 'congress-api'
api_ports = {
'congress-api': {
os_ip.PUBLIC: 1789,
os_ip.ADMIN: 1789,
os_ip.INTERNAL: 1789,
}
}
# Database sync command used to initalise the schema.
sync_cmd = ['congress-db-manage', '--config-file',
'/etc/congress/congress.conf', 'upgrade', 'head']
# The restart map defines which services should be restarted when a given
# file changes
restart_map = {
'/etc/congress/congress.conf': ['congress-server'],
'/etc/congress/api-paste.ini': ['congress-server'],
'/etc/congress/policy.json': ['congress-server'],
}
def __init__(self, release=None, **kwargs):
"""Custom initialiser for class
If no release is passed, then the charm determines the release from the
ch_utils.os_release() function.
"""
if release is None:
release = ch_utils.os_release('python-keystonemiddleware')
super(CongressCharm, self).__init__(release=release, **kwargs)
def install(self):
"""Customise the installation, configure the source and then call the
parent install() method to install the packages
"""
self.configure_source()
# and do the actual install
super(CongressCharm, self).install()
For reasons methods are needed to wrap the calls to the Congress charms class
methods. These can be appended to the bottom of the
src/lib/charm/openstack/congress.py file.
.. code:: python
def install():
"""Use the singleton from the CongressCharm to install the packages on the
unit
"""
CongressCharm.singleton.install()
def restart_all():
"""Use the singleton from the CongressCharm to restart services on the
unit
"""
CongressCharm.singleton.restart_all()
def db_sync():
"""Use the singleton from the CongressCharm to run db migration
"""
CongressCharm.singleton.db_sync()
def setup_endpoint(keystone):
"""When the keystone interface connects, register this unit in the keystone
catalogue.
"""
charm = CongressCharm.singleton
keystone.register_endpoints(charm.service_name,
charm.region,
charm.public_url,
charm.internal_url,
charm.admin_url)
def render_configs(interfaces_list):
"""Using a list of interfaces, render the configs and, if they have
changes, restart the services on the unit.
"""
CongressCharm.singleton.render_with_interfaces(interfaces_list)
Add Congress code to react to events
------------------------------------
Install Congress Packages
~~~~~~~~~~~~~~~~~~~~~~~~~
The reactive framework is going to emit events that the Congress charm can react
to. The charm needs to define how its going to react to these events and also
raise new events as needed.
The first action a charm needs to do is to install the Congress code. This is
by done running the install method from CongressCharm created earlier.
Edit src/reactive/handlers.py.
.. code:: python
import charms.reactive as reactive
import charmhelpers.core.hookenv as hookenv
# This charm's library contains all of the handler code associated with
# congress
import charm.openstack.congress as congress
# use a synthetic state to ensure that it get it to be installed independent of
# the install hook.
@reactive.when_not('charm.installed')
def install_packages():
congress.install()
reactive.set_state('charm.installed')
Configure Congress Relation
~~~~~~~~~~~~~~~~~~~~~~~~~~~
At this point the charm could be built and deployed and it would deploy a unit,
and install congress. However there is no code to specify how this charm should
interact with the services it depend on. For example when joining the database
the charm needs to specify the user and database it requires. The following code
configures the relations with the dependant services.
Append to src/reactive/handlers.py:
.. code:: python
@reactive.when('amqp.connected')
def setup_amqp_req(amqp):
"""Use the amqp interface to request access to the amqp broker using our
local configuration.
"""
amqp.request_access(username='congress',
vhost='openstack')
@reactive.when('shared-db.connected')
def setup_database(database):
"""On receiving database credentials, configure the database on the
interface.
"""
database.configure('congress', 'congress', hookenv.unit_private_ip())
@reactive.when('identity-service.connected')
def setup_endpoint(keystone):
congress.setup_endpoint(keystone)
Configure Congress
------------------
Now that the charm has the relations defined that it needs the Congress charm
is in a postion to generate its configuration files.
Create templates
~~~~~~~~~~~~~~~~
The charm code searches through the templates directories looking for a directory
corresponding to the Openstack release being installed or earlier. Since Mitaka
is the earliest release the charm is supporting a directory called mitaka will
house the templates and files.
.. code:: bash
( cd /tmp; apt-get source congress-server; )
mkdir -p templates/mitaka
cp /tmp/congress*/etc/{api-paste.ini,policy.json} templates/mitaka
A template for congress.conf is needed which will have have connection
information for MySQL, RabbitMQ and Keystone as well as user controllable
config options
.. code:: bash
[DEFAULT]
auth_strategy = keystone
drivers = congress.datasources.neutronv2_driver.NeutronV2Driver,congress.datasources.glancev2_driver.GlanceV2Driver,congress.datasources.nova_driver.NovaDriver,congress.datasources.keystone_driver.KeystoneDriver,congress.datasources.ceilometer_driver.CeilometerDriver,congress.datasources.cinder_driver.CinderDriver,congress.datasources.swift_driver.SwiftDriver,congress.datasources.plexxi_driver.PlexxiDriver,congress.datasources.vCenter_driver.VCenterDriver,congress.datasources.murano_driver.MuranoDriver,congress.datasources.ironic_driver.IronicDriver
[database]
connection = {{ shared_db.uri }}
[keystone_authtoken]
{% if identity_service.auth_host -%}
auth_uri = {{ identity_service.service_protocol }}://{{
identity_service.service_host }}:{{ identity_service.service_port }}
auth_url = {{ identity_service.auth_protocol }}://{{ identity_service.auth_host
}}:{{ identity_service.auth_port }}
auth_plugin = password
project_domain_id = default
user_domain_id = default
project_name = {{ identity_service.service_tenant }}
username = {{ identity_service.service_username }}
password = {{ identity_service.service_password }}
{% endif -%}
Render the config
~~~~~~~~~~~~~~~~~
Now the templates and interfaces are in place the configs can be
rendered. A side-effect of rendering the configs is that any associated
services are restarted. Finally, set the config.complete state this
will be used later to trigger other events.
Append to charm/reactive/handlers.py
.. code:: python
@reactive.when('shared-db.available')
@reactive.when('identity-service.available')
@reactive.when('amqp.available')
def render_stuff(*args):
congress.render_configs(args)
reactive.set_state('config.complete')
Run DB Migration
~~~~~~~~~~~~~~~~
The DB migration can only be run once the config files are in place
since as congress.conf will contain the DB connection information.
To achieve this the DB migration is gated on the config.complete
being set. Finally set the db.synched event so that this is only
run once.
Append to src/reactive/handlers.py
.. code:: python
@reactive.when('config.complete')
@reactive.when_not('db.synched')
def run_db_migration():
congress.db_sync()
congress.restart_all()
reactive.set_state('db.synched')
Build and Deploy charm
----------------------
Build the charm to pull down the interfaces and layers.
.. code:: bash
mkdir build
charm build -obuild src
The built charm can now be deployed with Juju.
.. code:: bash
juju deploy <full path>/build/congress
juju add-relation congress mysql
juju add-relation congress keystone
juju add-relation congress rabbitmq-server
Deploying an existing Openstack environment is not covered here.

76
doc/source/testing.rst Normal file
View File

@ -0,0 +1,76 @@
.. _testing:
=======
Testing
=======
Every proposed change to a charm is run through testing during the review
verification process. If you want to contribute a change or fix to a charm,
please take time to review the `Unit Testing`_ and `Functional Testing`_
sections of this document.
OpenStack Charm CI will verify your changes, but please execute at least
unit tests locally before submitting patches to reduce load on the OpenStack
CI infrastructure.
The OpenStack Charms are compliant with the OpenStack
`Consistent Testing Interface <https://governance.openstack.org/reference/cti/python_cti.html>`__;
take a read on how this works to understand in full.
Lint
====
You can verify the compliance of your code changes using flake8 and the charm
proof tool using the pep8 tox environment:
.. code:: bash
tox -e pep8
Ensure that any non-compliance is corrected prior to raising/updating a review.
Unit Testing
============
Execute the unit tests for a charm using the tox py27 environment:
.. code:: bash
tox -e py27
Unit tests are stored in the ``unit_tests`` folder; when adding features or
changing existing code, please ensure that appropriate unit tests are added
or updated to cover the changes you are making.
Unit tests are written in Python using standard mocking techniques to isolate
the unit tests from the underlying host operating system.
Functional Testing
==================
Amulet
~~~~~~
Functional tests for a charm are written using the Amulet_ test framework and
should exercise the target charm with a subset of a full OpenStack deployment
to ensure that the charm is able to correctly deploy and configure the
service that is encapsulates.
The OpenStack charm helpers provide some Amulet deployment helpers to ease
testing of different OpenStack release combinations; typically each charm will
test the OpenStack and Ubuntu release combinations currently supported by
Ubuntu.
Execute of Amulet tests currently requires use of the Makefile:
.. code:: bash
make functional_test
This will execute the full suite of Amulet tests under the ``tests/`` folder.
The ``tests/README`` file in each charm will contain more details on how to
name tests and how to execute them individually.
.. _Amulet: https://jujucharms.com/docs/devel/tools-amulet

3
requirements.txt Normal file
View File

@ -0,0 +1,3 @@
sphinx>=1.1.2,!=1.2.0,!=1.3b1,<1.3 # BSD
oslosphinx>=2.5.0,!=3.4.0 # Apache-2.0
pbr>=1.6 # Apache-2.0

23
setup.cfg Normal file
View File

@ -0,0 +1,23 @@
[metadata]
name = openstack-charm-guide
summary = OpenStack Charm Guide
description-file =
README.rst
author = OpenStack
author-email = openstack@lists.openstack.org
home-page = http://www.openstack.org/
classifier =
Intended Audience :: Developers
License :: OSI Approved :: Apache Software License
Operating System :: POSIX :: Linux
[build_sphinx]
all_files = 1
build-dir = doc/build
source-dir = doc/source
[pbr]
warnerrors = True
[wheel]
universal = 1

21
setup.py Normal file
View File

@ -0,0 +1,21 @@
#!/usr/bin/env python
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
#
# 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 setuptools
setuptools.setup(
setup_requires=['pbr'],
pbr=True)

17
tox.ini Normal file
View File

@ -0,0 +1,17 @@
[tox]
minversion = 1.6
envlist = docs
skipsdist = True
[testenv]
basepython = python2.7
usedevelop = True
setenv = VIRTUAL_ENV={envdir}
install_command = pip install -U {opts} {packages}
deps = -r{toxinidir}/requirements.txt
[testenv:venv]
commands = {posargs}
[testenv:docs]
commands = python setup.py build_sphinx