Initial Python client bindings and CLI

Change-Id: Iac4a73acfb515c1e213a1dd0865a62bc39e3ed0f
This commit is contained in:
Kiall Mac Innes 2012-11-19 23:08:35 +00:00
parent fd06cdfad0
commit 6f280a8693
20 changed files with 847 additions and 31 deletions

4
doc/requirements.txt Normal file

@ -0,0 +1,4 @@
cliff
jsonschema>=0.7
requests
python-keystoneclient>=0.2.0

243
doc/source/conf.py Normal file

@ -0,0 +1,243 @@
# -*- coding: utf-8 -*-
#
# monikerclient documentation build configuration file, created by
# sphinx-quickstart on Wed Oct 31 18:58:17 2012.
#
# 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 sys, os
# 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.viewcode']
# 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'monikerclient'
copyright = u'2012, Managed I.T.'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
from monikerclient.version import version_info as monikerclient_version
version = monikerclient_version.canonical_version_string()
# The full version, including alpha/beta/rc tags.
release = monikerclient_version.version_string_with_vcs()
# 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 = []
# 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 = True
# 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 = []
# -- 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 = 'default'
# 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
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
#html_last_updated_fmt = '%b %d, %Y'
# 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 = True
# If false, no index is generated.
#html_use_index = True
# 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 = 'monikerclientdoc'
# -- 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', 'monikerclient.tex', u'Moniker Client Documentation',
u'Managed I.T.', '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 manual page output --------------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
('index', 'monikerclient', u'Moniker Client Documentation',
[u'Managed I.T.'], 1)
]
# If true, show URL addresses after external links.
#man_show_urls = False
# -- 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', 'monikerclient', u'Moniker Client Documentation',
u'Managed I.T.', 'monikerclient', 'One line description of project.',
'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'

21
doc/source/index.rst Normal file

@ -0,0 +1,21 @@
.. moniker documentation master file, created by
sphinx-quickstart on Wed Oct 31 18:58:17 2012.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
Welcome to monikerclients's documentation!
===================================
Contents:
.. toctree::
:maxdepth: 2
Indices and tables
==================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`

94
monikerclient/auth.py Normal file

@ -0,0 +1,94 @@
# Copyright 2012 Managed I.T.
#
# Author: Kiall Mac Innes <kiall@managedit.ie>
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from urlparse import urlparse
from requests.auth import AuthBase
from keystoneclient.v2_0.client import Client
class KeystoneAuth(AuthBase):
def __init__(self, auth_url, username=None, password=None, tenant_id=None,
tenant_name=None, token=None, service_type=None,
endpoint_type=None):
self.auth_url = str(auth_url).rstrip('/')
self.username = username
self.password = password
self.tenant_id = tenant_id
self.tenant_name = tenant_name
self.token = token
if (not username and not password) and not token:
raise ValueError('A username and password, or token is required')
if not service_type or not endpoint_type:
raise ValueError("Need service_type and/or endpoint_type")
self.service_type = service_type
self.endpoint_type = endpoint_type
self.refresh_auth()
def __call__(self, request):
if not self.token:
self.refresh_auth()
request.headers['X-Auth-Token'] = self.token
return request
def get_ksclient(self):
insecure = urlparse(self.auth_url).scheme != 'https'
return Client(username=self.username,
password=self.password,
tenant_id=self.tenant_id,
tenant_name=self.tenant_name,
auth_url=self.auth_url,
insecure=insecure)
def get_endpoints(self, service_type=None, endpoint_type=None):
return self.service_catalog.get_endpoints(
service_type=service_type,
endpoint_type=endpoint_type)
def get_url(self, service_type=None, endpoint_type=None):
service_type = service_type or self.service_type
endpoint_type = endpoint_type or self.endpoint_type
endpoints = self.get_endpoints(service_type, endpoint_type)
return endpoints[service_type][0][endpoint_type].rstrip('/')
def refresh_auth(self):
ks = self.get_ksclient()
self.token = ks.auth_token
self.service_catalog = ks.service_catalog
def args_hook(self, args):
url = urlparse(args['url'])
if str(url.scheme) == '':
if not self.token:
self.refresh_token()
endpoints = self.get_endpoints()
if url.netloc in endpoints.keys():
args['url'] = '%s/%s?%s' % (
self.get_url(),
url.path.lstrip('/'),
url.query
)

94
monikerclient/cli/base.py Normal file

@ -0,0 +1,94 @@
# Copyright 2012 Managed I.T.
#
# Author: Kiall Mac Innes <kiall@managedit.ie>
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import abc
from cliff.command import Command as CliffCommand
from cliff.lister import Lister
from cliff.show import ShowOne
from monikerclient.v1 import Client
class Command(CliffCommand):
__metaclass__ = abc.ABCMeta
def run(self, parsed_args):
client_args = {
'endpoint': self.app.options.os_endpoint,
'auth_url': self.app.options.os_auth_url,
'username': self.app.options.os_username,
'password': self.app.options.os_password,
'tenant_id': self.app.options.os_tenant_id,
'tenant_name': self.app.options.os_tenant_name,
'token': self.app.options.os_token,
'region_name': self.app.options.os_region_name,
}
self.client = Client(**client_args)
return super(Command, self).run(parsed_args)
@abc.abstractmethod
def execute(self, parsed_args):
"""
Execute something, this is since we overload self.take_action()
in order to format the data
This method __NEEDS__ to be overloaded!
:param parsed_args: The parsed args that are given by take_action()
"""
def post_execute(self, data):
"""
Format the results locally if needed, by default we just return data
:param data: Whatever is returned by self.execute()
"""
return data
def take_action(self, parsed_args):
# TODO: Common Exception Handling Here
results = self.execute(parsed_args)
return self.post_execute(results)
class ListCommand(Command, Lister):
def post_execute(self, results):
if len(results) > 0:
column_names = results[0].keys()
data = [r.values() for r in results]
return column_names, data
else:
return [], ()
class GetCommand(Command, ShowOne):
def post_execute(self, results):
return results.keys(), results.values()
class CreateCommand(Command, ShowOne):
def post_execute(self, results):
return results.keys(), results.values()
class UpdateCommand(Command, ShowOne):
def post_execute(self, results):
return results.keys(), results.values()
class DeleteCommand(Command):
pass

@ -0,0 +1,98 @@
# Copyright 2012 Managed I.T.
#
# Author: Kiall Mac Innes <kiall@managedit.ie>
#
# 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 logging
from monikerclient.cli import base
from monikerclient.v1.domains import Domain
LOG = logging.getLogger(__name__)
class ListDomainsCommand(base.ListCommand):
""" List Domains """
def execute(self, parsed_args):
return self.client.domains.list()
class GetDomainCommand(base.GetCommand):
""" Get Domain """
def get_parser(self, prog_name):
parser = super(GetDomainCommand, self).get_parser(prog_name)
parser.add_argument('--domain-id', help="Domain ID", required=True)
return parser
def execute(self, parsed_args):
return self.client.domains.get(parsed_args.domain_id)
class CreateDomainCommand(base.CreateCommand):
""" Create Domain """
def get_parser(self, prog_name):
parser = super(CreateDomainCommand, self).get_parser(prog_name)
parser.add_argument('--domain-name', help="Domain Name", required=True)
parser.add_argument('--domain-email', help="Domain Email",
required=True)
return parser
def execute(self, parsed_args):
domain = Domain(
name=parsed_args.domain_name,
email=parsed_args.domain_email
)
return self.client.domains.create(domain)
class UpdateDomainCommand(base.UpdateCommand):
""" Update Domain """
def get_parser(self, prog_name):
parser = super(UpdateDomainCommand, self).get_parser(prog_name)
parser.add_argument('--domain-id', help="Domain ID", required=True)
parser.add_argument('--domain-name', help="Domain Name")
parser.add_argument('--domain-email', help="Domain Email")
return parser
def execute(self, parsed_args):
# TODO: API needs updating.. this get is silly
domain = self.client.domains.get(parsed_args.domain_id)
# TODO: How do we tell if an arg was supplied or intentionally set to
# None?
return self.client.domains.update(domain)
class DeleteDomainCommand(base.DeleteCommand):
""" Delete Domain """
def get_parser(self, prog_name):
parser = super(DeleteDomainCommand, self).get_parser(prog_name)
parser.add_argument('--domain-id', help="Domain ID")
return parser
def execute(self, parsed_args):
return self.client.domains.delete(parsed_args.domain_id)

@ -21,3 +21,23 @@ class Base(Exception):
class ResourceNotFound(Base):
pass
class RemoteError(Base):
pass
class Unknown(RemoteError):
pass
class Forbidden(RemoteError):
pass
class Conflict(RemoteError):
pass
class NotFound(RemoteError):
pass

@ -13,7 +13,7 @@
# 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 os
from cliff.app import App
from cliff.commandmanager import CommandManager
@ -25,3 +25,41 @@ class MonikerShell(App):
version='0.1',
command_manager=CommandManager('moniker.cli'),
)
def build_option_parser(self, description, version, argparse_kwargs=None):
parser = super(MonikerShell, self).build_option_parser(
description, version, argparse_kwargs)
parser.add_argument('--os-endpoint',
default=os.environ.get('OS_SERVICE_ENDPOINT'),
help="Defaults to env[OS_SERVICE_ENDPOINT]")
parser.add_argument('--os-auth-url',
default=os.environ.get('OS_AUTH_URL'),
help="Defaults to env[OS_AUTH_URL]")
parser.add_argument('--os-username',
default=os.environ.get('OS_USERNAME'),
help="Defaults to env[OS_USERNAME]")
parser.add_argument('--os-password',
default=os.environ.get('OS_PASSWORD'),
help="Defaults to env[OS_PASSWORD]")
parser.add_argument('--os-tenant-id',
default=os.environ.get('OS_TENANT_ID'),
help="Defaults to env[OS_TENANT_ID]")
parser.add_argument('--os-tenant-name',
default=os.environ.get('OS_TENANT_NAME'),
help="Defaults to env[OS_TENANT_NAME]")
parser.add_argument('--os-token',
default=os.environ.get('OS_SERVICE_TOKEN'),
help="Defaults to env[OS_SERVICE_TOKEN]")
parser.add_argument('--os-region-name',
default=os.environ.get('OS_REGION_NAME'),
help="Defaults to env[OS_REGION_NAME]")
return parser

@ -0,0 +1,98 @@
# Copyright 2012 Managed I.T.
#
# Author: Kiall Mac Innes <kiall@managedit.ie>
#
# 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 requests
from urlparse import urlparse
from monikerclient import exceptions
from monikerclient.auth import KeystoneAuth
from monikerclient.v1 import domains
from monikerclient.v1 import records
from monikerclient.v1 import servers
class Client(object):
""" Client for the Moniker v1 API """
def __init__(self, endpoint=None, auth_url=None, username=None,
password=None, tenant_id=None, tenant_name=None, token=None,
region_name=None, endpoint_type='publicURL'):
"""
:param endpoint: Endpoint URL
:param auth_url: Keystone auth_url
:param username: The username to auth with
:param password: The password to auth with
:param tenant_id: The tenant ID
:param tenant_name: The tenant name
:param token: A token instead of username / password
:param region_name: The region name
:param endpoint_type: The endpoint type (publicURL for example)
"""
if auth_url:
auth = KeystoneAuth(auth_url, username, password, tenant_id,
tenant_name, token, 'dns', endpoint_type)
endpoint = auth.get_url()
elif endpoint:
auth = None
else:
raise ValueError('Either an auth_url or endpoint must be supplied')
headers = {'Content-Type': 'application/json'}
def _ensure_url_hook(args):
url_ = urlparse(args['url'])
if not url_.scheme:
args['url'] = endpoint + url_.path
hooks = {'args': _ensure_url_hook}
self.requests = requests.session(
auth=auth,
headers=headers,
hooks=hooks)
self.domains = domains.DomainsController(client=self)
self.records = records.RecordsController(client=self)
self.servers = servers.ServersController(client=self)
def wrap_api_call(self, func, *args, **kw):
"""
Wrap a self.<rest function> with exception handling
:param func: The function to wrap
"""
response = func(*args, **kw)
if response.status_code in (401, 403):
raise exceptions.Forbidden()
elif response.status_code == 404:
raise exceptions.NotFound()
elif response.status_code == 409:
raise exceptions.Conflict()
elif response.status_code == 500:
raise exceptions.Unknown()
else:
return response
def get(self, path, **kw):
return self.wrap_api_call(self.requests.get, path, **kw)
def post(self, path, **kw):
return self.wrap_api_call(self.requests.post, path, **kw)
def put(self, path, **kw):
return self.wrap_api_call(self.requests.put, path, **kw)
def delete(self, path, **kw):
return self.wrap_api_call(self.requests.delete, path, **kw)

53
monikerclient/v1/base.py Normal file

@ -0,0 +1,53 @@
# Copyright 2012 Managed I.T.
#
# Author: Kiall Mac Innes <kiall@managedit.ie>
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import abc
class Controller(object):
__metaclass__ = abc.ABCMeta
def __init__(self, client):
self.client = client
def list(self, *args, **kw):
"""
List something
"""
raise NotImplementedError
def get(self, *args, **kw):
"""
Get something
"""
raise NotImplementedError
def create(self, *args, **kw):
"""
Create something
"""
raise NotImplementedError
def update(self, *args, **kw):
"""
Update something
"""
raise NotImplementedError
def delete(self, *args, **kw):
"""
Delete something
"""
raise NotImplementedError

@ -1,27 +0,0 @@
# Copyright 2012 Managed I.T.
#
# Author: Kiall Mac Innes <kiall@managedit.ie>
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from monikerclient.v1 import domains
from monikerclient.v1 import records
from monikerclient.v1 import servers
class Client(object):
""" Client for the Moniker v1 API """
def __init__(self):
self.domains = domains.Controller()
self.records = records.Controller()
self.servers = servers.Controller()

@ -13,20 +13,25 @@
# 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 json
from monikerclient import warlock
from monikerclient import utils
from monikerclient.v1.base import Controller
Domain = warlock.model_factory(utils.load_schema('v1', 'domain'))
class Controller(object):
class DomainsController(Controller):
def list(self):
"""
Retrieve a list of domains
:returns: A list of :class:`Domain`s
"""
response = self.client.get('/domains')
return [Domain(i) for i in response.json['domains']]
def get(self, domain_id):
"""
@ -35,6 +40,9 @@ class Controller(object):
:param domain_id: Domain Identifier
:returns: :class:`Domain`
"""
response = self.client.get('/domains/%s' % domain_id)
return Domain(response.json)
def create(self, domain):
"""
@ -43,6 +51,9 @@ class Controller(object):
:param domain: A :class:`Domain` to create
:returns: :class:`Domain`
"""
response = self.client.post('/domains', data=json.dumps(domain))
return Domain(response.json)
def update(self, domain):
"""
@ -51,6 +62,10 @@ class Controller(object):
:param domain: A :class:`Domain` to update
:returns: :class:`Domain`
"""
response = self.client.put('/domains/%s' % domain.id,
data=json.dumps(domain.changes))
return Domain(response.json)
def delete(self, domain):
"""
@ -58,3 +73,7 @@ class Controller(object):
:param domain: A :class:`Domain`, or Domain Identifier to delete
"""
if isinstance(domain, Domain):
self.client.delete('/domains/%s' % domain.id)
else:
self.client.delete('/domains/%s' % domain)

@ -13,20 +13,25 @@
# 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 json
from monikerclient import warlock
from monikerclient import utils
from monikerclient.v1.base import Controller
Record = warlock.model_factory(utils.load_schema('v1', 'record'))
class Controller(object):
class RecordsController(Controller):
def list(self):
"""
Retrieve a list of records
:returns: A list of :class:`Record`s
"""
response = self.client.get('/records')
return [Record(i) for i in response.json['records']]
def get(self, record_id):
"""
@ -35,6 +40,9 @@ class Controller(object):
:param record_id: Record Identifier
:returns: :class:`Record`
"""
response = self.client.get('/records/%s' % record_id)
return Record(response.json)
def create(self, record):
"""
@ -43,6 +51,9 @@ class Controller(object):
:param record: A :class:`Record` to create
:returns: :class:`Record`
"""
response = self.client.post('/records', data=json.dumps(record))
return record.update(response.json)
def update(self, record):
"""
@ -51,6 +62,10 @@ class Controller(object):
:param record: A :class:`Record` to update
:returns: :class:`Record`
"""
response = self.client.put('/records/%s' % record.id,
data=json.dumps(record))
return record.update(response.json)
def delete(self, record):
"""
@ -58,3 +73,7 @@ class Controller(object):
:param record: A :class:`Record`, or Record Identifier to delete
"""
if isinstance(record, Record):
self.client.delete('/records/%s' % record.id)
else:
self.client.delete('/records/%s' % record)

@ -13,20 +13,25 @@
# 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 json
from monikerclient import warlock
from monikerclient import utils
from monikerclient.v1.base import Controller
Server = warlock.model_factory(utils.load_schema('v1', 'server'))
class Controller(object):
class ServersController(Controller):
def list(self):
"""
Retrieve a list of servers
:returns: A list of :class:`Server`s
"""
response = self.client.get('/servers')
return [Server(i) for i in response.json['servers']]
def get(self, server_id):
"""
@ -35,6 +40,9 @@ class Controller(object):
:param server_id: Server Identifier
:returns: :class:`Server`
"""
response = self.client.get('/servers/%s' % server_id)
return Server(response.json)
def create(self, server):
"""
@ -43,6 +51,9 @@ class Controller(object):
:param server: A :class:`Server` to create
:returns: :class:`Server`
"""
response = self.client.post('/servers', data=json.dumps(server))
return server.update(response.json)
def update(self, server):
"""
@ -51,6 +62,10 @@ class Controller(object):
:param server: A :class:`Server` to update
:returns: :class:`Server`
"""
response = self.client.put('/servers/%s' % server.id,
data=json.dumps(server))
return server.update(response.json)
def delete(self, server):
"""
@ -58,3 +73,7 @@ class Controller(object):
:param server: A :class:`Server`, or Server Identifier to delete
"""
if isinstance(server, Server):
self.client.delete('/servers/%s' % server.id)
else:
self.client.delete('/servers/%s' % server)

@ -113,6 +113,9 @@ def model_factory(schema):
def itervalues(self):
return copy.deepcopy(dict(self)).itervalues()
def keys(self):
return copy.deepcopy(dict(self)).keys()
def values(self):
return copy.deepcopy(dict(self)).values()

@ -6,3 +6,11 @@ cover-inclusive=true
verbosity=2
detailed-errors=1
where=monikerclient/tests
[build_sphinx]
source-dir = doc/source
build-dir = doc/build
all_files = 1
[upload_docs]
upload-dir = doc/build/html

@ -14,6 +14,7 @@
# 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 textwrap
from setuptools import setup, find_packages
from monikerclient.openstack.common import setup as common_setup
from monikerclient.version import version_info as version
@ -46,6 +47,14 @@ setup(
'bin/moniker',
],
cmdclass=common_setup.get_cmdclass(),
entry_points=textwrap.dedent("""
[moniker.cli]
domain-list = monikerclient.cli.domains:ListDomainsCommand
domain-get = monikerclient.cli.domains:GetDomainCommand
domain-create = monikerclient.cli.domains:CreateDomainCommand
domain-update = monikerclient.cli.domains:UpdateDomainCommand
domain-delete = monikerclient.cli.domains:DeleteDomainCommand
"""),
classifiers=[
'Development Status :: 3 - Alpha',
'Topic :: Internet :: Name Service (DNS)',

@ -1,2 +1,4 @@
cliff
jsonschema>=0.7
requests
python-keystoneclient>=0.2.0

@ -1,3 +1,4 @@
nose
mox
openstack.nose_plugin
sphinx