Add Records and Servers CLI commands

Change-Id: I61249dc1d9d70b35255a1cad7080cba306a85d42
This commit is contained in:
Kiall Mac Innes 2012-11-28 13:28:14 +00:00
parent 68319c6e89
commit 18cae5e5ee
11 changed files with 489 additions and 51 deletions

@ -76,11 +76,6 @@ class ListCommand(Command, Lister):
class GetCommand(Command, ShowOne): class GetCommand(Command, ShowOne):
def get_parser(self, prog_name):
parser = super(GetCommand, self).get_parser(prog_name)
parser.add_argument('id', help='The ID or Name to get')
return parser
def post_execute(self, results): def post_execute(self, results):
return results.keys(), results.values() return results.keys(), results.values()

@ -33,12 +33,12 @@ class GetDomainCommand(base.GetCommand):
def get_parser(self, prog_name): def get_parser(self, prog_name):
parser = super(GetDomainCommand, self).get_parser(prog_name) parser = super(GetDomainCommand, self).get_parser(prog_name)
parser.add_argument('--domain-id', help="Domain ID", required=True) parser.add_argument('id', help="Domain ID", required=True)
return parser return parser
def execute(self, parsed_args): def execute(self, parsed_args):
return self.client.domains.get(parsed_args.domain_id) return self.client.domains.get(parsed_args.id)
class CreateDomainCommand(base.CreateCommand): class CreateDomainCommand(base.CreateCommand):
@ -47,16 +47,15 @@ class CreateDomainCommand(base.CreateCommand):
def get_parser(self, prog_name): def get_parser(self, prog_name):
parser = super(CreateDomainCommand, self).get_parser(prog_name) parser = super(CreateDomainCommand, self).get_parser(prog_name)
parser.add_argument('--domain-name', help="Domain Name", required=True) parser.add_argument('--name', help="Domain Name", required=True)
parser.add_argument('--domain-email', help="Domain Email", parser.add_argument('--email', help="Domain Email", required=True)
required=True)
return parser return parser
def execute(self, parsed_args): def execute(self, parsed_args):
domain = Domain( domain = Domain(
name=parsed_args.domain_name, name=parsed_args.name,
email=parsed_args.domain_email email=parsed_args.email,
) )
return self.client.domains.create(domain) return self.client.domains.create(domain)
@ -68,18 +67,22 @@ class UpdateDomainCommand(base.UpdateCommand):
def get_parser(self, prog_name): def get_parser(self, prog_name):
parser = super(UpdateDomainCommand, self).get_parser(prog_name) parser = super(UpdateDomainCommand, self).get_parser(prog_name)
parser.add_argument('--domain-id', help="Domain ID", required=True) parser.add_argument('id', help="Domain ID", required=True)
parser.add_argument('--domain-name', help="Domain Name") parser.add_argument('--name', help="Domain Name")
parser.add_argument('--domain-email', help="Domain Email") parser.add_argument('--email', help="Domain Email")
return parser return parser
def execute(self, parsed_args): def execute(self, parsed_args):
# TODO: API needs updating.. this get is silly # TODO: API needs updating.. this get is silly
domain = self.client.domains.get(parsed_args.domain_id) domain = self.client.domains.get(parsed_args.id)
# TODO: How do we tell if an arg was supplied or intentionally set to # TODO: How do we tell if an arg was supplied or intentionally set to
# None? # None?
domain.update({
'name': parsed_args.name,
'email': parsed_args.email,
})
return self.client.domains.update(domain) return self.client.domains.update(domain)
@ -90,9 +93,9 @@ class DeleteDomainCommand(base.DeleteCommand):
def get_parser(self, prog_name): def get_parser(self, prog_name):
parser = super(DeleteDomainCommand, self).get_parser(prog_name) parser = super(DeleteDomainCommand, self).get_parser(prog_name)
parser.add_argument('--domain-id', help="Domain ID") parser.add_argument('id', help="Domain ID", required=True)
return parser return parser
def execute(self, parsed_args): def execute(self, parsed_args):
return self.client.domains.delete(parsed_args.domain_id) return self.client.domains.delete(parsed_args.id)

@ -0,0 +1,121 @@
# 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.records import Record
LOG = logging.getLogger(__name__)
class ListRecordsCommand(base.ListCommand):
""" List Records """
def get_parser(self, prog_name):
parser = super(ListRecordsCommand, self).get_parser(prog_name)
parser.add_argument('domain_id', help="Domain ID")
return parser
def execute(self, parsed_args):
return self.client.records.list(parsed_args.domain_id)
class GetRecordCommand(base.GetCommand):
""" Get Record """
def get_parser(self, prog_name):
parser = super(GetRecordCommand, self).get_parser(prog_name)
parser.add_argument('domain_id', help="Domain ID")
parser.add_argument('id', help="Record ID")
return parser
def execute(self, parsed_args):
return self.client.records.get(parsed_args.domain_id, parsed_args.id)
class CreateRecordCommand(base.CreateCommand):
""" Create Record """
def get_parser(self, prog_name):
parser = super(CreateRecordCommand, self).get_parser(prog_name)
parser.add_argument('domain_id', help="Domain ID")
parser.add_argument('--name', help="Record Name", required=True)
parser.add_argument('--type', help="Record Type", required=True)
parser.add_argument('--data', help="Record Data", required=True)
parser.add_argument('--ttl', type=int, help="Record TTL")
return parser
def execute(self, parsed_args):
record = Record(
name=parsed_args.name,
type=parsed_args.type,
data=parsed_args.data,
ttl=parsed_args.ttl,
)
return self.client.records.create(parsed_args.domain_id, record)
class UpdateRecordCommand(base.UpdateCommand):
""" Update Record """
def get_parser(self, prog_name):
parser = super(UpdateRecordCommand, self).get_parser(prog_name)
parser.add_argument('domain_id', help="Domain ID")
parser.add_argument('id', help="Record ID")
parser.add_argument('--name', help="Record Name")
parser.add_argument('--type', help="Record Type")
parser.add_argument('--data', help="Record Data")
parser.add_argument('--ttl', type=int, help="Record TTL")
return parser
def execute(self, parsed_args):
# TODO: API needs updating.. this get is silly
record = self.client.records.get(parsed_args.domain_id, parsed_args.id)
# TODO: How do we tell if an arg was supplied or intentionally set to
# None?
record.update({
'name': parsed_args.name,
'type': parsed_args.type,
'data': parsed_args.data,
'ttl': parsed_args.ttl,
})
return self.client.records.update(parsed_args.domain_id, record)
class DeleteRecordCommand(base.DeleteCommand):
""" Delete Record """
def get_parser(self, prog_name):
parser = super(DeleteRecordCommand, self).get_parser(prog_name)
parser.add_argument('domain_id', help="Domain ID")
parser.add_argument('id', help="Record ID")
return parser
def execute(self, parsed_args):
return self.client.records.delete(parsed_args.domain_id,
parsed_args.id)

@ -0,0 +1,105 @@
# 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.servers import Server
LOG = logging.getLogger(__name__)
class ListServersCommand(base.ListCommand):
""" List Servers """
def execute(self, parsed_args):
return self.client.servers.list()
class GetServerCommand(base.GetCommand):
""" Get Server """
def get_parser(self, prog_name):
parser = super(GetServerCommand, self).get_parser(prog_name)
parser.add_argument('id', help="Server ID", required=True)
return parser
def execute(self, parsed_args):
return self.client.servers.get(parsed_args.id)
class CreateServerCommand(base.CreateCommand):
""" Create Server """
def get_parser(self, prog_name):
parser = super(CreateServerCommand, self).get_parser(prog_name)
parser.add_argument('--name', help="Server Name", required=True)
parser.add_argument('--ipv4', help="Server IPv4 Address")
parser.add_argument('--ipv6', help="Server IPv6 Address")
return parser
def execute(self, parsed_args):
server = Server(
name=parsed_args.name,
ipv4=parsed_args.ipv4,
ipv6=parsed_args.ipv6,
)
return self.client.servers.create(server)
class UpdateServerCommand(base.UpdateCommand):
""" Update Server """
def get_parser(self, prog_name):
parser = super(UpdateServerCommand, self).get_parser(prog_name)
parser.add_argument('id', help="Server ID", required=True)
parser.add_argument('--name', help="Server Name")
parser.add_argument('--ipv4', help="Server IPv4 Address")
parser.add_argument('--ipv6', help="Server IPv6 Address")
return parser
def execute(self, parsed_args):
# TODO: API needs updating.. this get is silly
server = self.client.servers.get(parsed_args.id)
# TODO: How do we tell if an arg was supplied or intentionally set to
# None?
server.update({
'name': parsed_args.name,
'ipv4': parsed_args.ipv4,
'ipv6': parsed_args.ipv6,
})
return self.client.servers.update(server)
class DeleteServerCommand(base.DeleteCommand):
""" Delete Server """
def get_parser(self, prog_name):
parser = super(DeleteServerCommand, self).get_parser(prog_name)
parser.add_argument('id', help="Server ID", required=True)
return parser
def execute(self, parsed_args):
return self.client.servers.delete(parsed_args.id)

@ -16,8 +16,8 @@
"name": { "name": {
"type": "string", "type": "string",
"description": "Domain name", "description": "Domain name",
"format": "host-name",
"maxLength": 255, "maxLength": 255,
"pattern": "^.+[^\\.]$",
"required": true "required": true
}, },
"email": { "email": {
@ -43,10 +43,20 @@
"readonly": true "readonly": true
}, },
"updated_at": { "updated_at": {
"type": "string", "type": ["string", "null"],
"description": "Date and time of image registration", "description": "Date and time of image registration",
"format": "date-time", "format": "date-time",
"readonly": true "readonly": true
} }
} },
"links": [{
"rel": "self",
"href": "/domains/{id}"
}, {
"rel": "records",
"href": "/domains/{id}/records"
}, {
"rel": "collection",
"href": "/domains"
}]
} }

@ -13,17 +13,24 @@
"pattern": "^([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}$", "pattern": "^([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}$",
"readonly": true "readonly": true
}, },
"domain_id": {
"type": "string",
"description": "Domain Identifier",
"pattern": "^([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}$",
"readonly": true
},
"name": { "name": {
"type": "string", "type": "string",
"description": "DNS Record Name", "description": "DNS Record Name",
"format": "host-name",
"maxLength": 255, "maxLength": 255,
"pattern": "^.+[^\\.]$",
"required": true "required": true
}, },
"type": { "type": {
"type": "string", "type": "string",
"description": "DNS Record Type", "description": "DNS Record Type",
"enum": ["A", "AAAA", "CNAME", "MX", "SRV", "TXT", "SPF", "NS"] "enum": ["A", "AAAA", "CNAME", "MX", "SRV", "TXT", "SPF", "NS"],
"required": true
}, },
"data": { "data": {
"type": "string", "type": "string",
@ -31,8 +38,14 @@
"maxLength": 255, "maxLength": 255,
"required": true "required": true
}, },
"priority": {
"type": ["integer", "null"],
"description": "DNS Record Priority",
"min": 0,
"max": 99999
},
"ttl": { "ttl": {
"type": "integer", "type": ["integer", "null"],
"description": "Time to live", "description": "Time to live",
"min": 60 "min": 60
}, },
@ -43,10 +56,131 @@
"readonly": true "readonly": true
}, },
"updated_at": { "updated_at": {
"type": "string", "type": ["string", "null"],
"description": "Date and time of image registration", "description": "Date and time of image registration",
"format": "date-time", "format": "date-time",
"readonly": true "readonly": true
} }
} },
"oneOf": [{
"description": "An A Record",
"properties": {
"type": {
"type": "string",
"enum": ["A"]
},
"data": {
"format": "ip-address",
"required": true
},
"priority": {
"type": "null"
}
}
}, {
"description": "An AAAA Record",
"properties": {
"type": {
"type": "string",
"enum": ["AAAA"]
},
"data": {
"format": "ipv6",
"required": true
},
"priority": {
"type": "null"
}
}
}, {
"description": "A CNAME Record",
"properties": {
"type": {
"type": "string",
"enum": ["CNAME"]
},
"data": {
"format": "host-name",
"required": true
},
"priority": {
"type": "null"
}
}
}, {
"description": "A MX Record",
"properties": {
"type": {
"type": "string",
"enum": ["MX"]
},
"data": {
"format": "host-name",
"required": true
},
"priority": {
"type": "integer",
"required": true
}
}
}, {
"description": "A SRV Record",
"properties": {
"type": {
"type": "string",
"enum": ["SRV"]
},
"priority": {
"type": "integer",
"required": true
}
}
}, {
"description": "A TXT Record",
"properties": {
"type": {
"type": "string",
"enum": ["TXT"]
},
"priority": {
"type": "null"
}
}
}, {
"description": "A SPF Record",
"properties": {
"type": {
"type": "string",
"enum": ["SPF"]
},
"priority": {
"type": "null"
}
}
}, {
"description": "A NS Record",
"properties": {
"type": {
"type": "string",
"enum": ["NS"]
},
"data": {
"format": "host-name",
"required": true
},
"priority": {
"type": "null"
}
}
}],
"links": [{
"rel": "self",
"href": "/domains/{domain_id}/records/{id}"
}, {
"rel": "domain",
"href": "/domains/{domain_id}"
}, {
"rel": "collection",
"href": "/domains/{domain_id}/records"
}]
} }

@ -16,15 +16,14 @@
"name": { "name": {
"type": "string", "type": "string",
"description": "Server DNS name", "description": "Server DNS name",
"format": "host-name",
"maxLength": 255, "maxLength": 255,
"pattern": "^.+[^\\.]$",
"required": true "required": true
}, },
"ipv4": { "ipv4": {
"type": "string", "type": "string",
"description": "IPv4 address of server", "description": "IPv4 address of server",
"format": "ip-address", "format": "ip-address"
"required": true
}, },
"ipv6": { "ipv6": {
"type": "string", "type": "string",
@ -38,10 +37,32 @@
"readonly": true "readonly": true
}, },
"updated_at": { "updated_at": {
"type": "string", "type": ["string", "null"],
"description": "Date and time of last server update", "description": "Date and time of last server update",
"format": "date-time", "format": "date-time",
"readonly": true "readonly": true
} }
} },
"anyOf": [{
"description": "IPv4",
"properties": {
"ipv4": {
"required": true
}
}
}, {
"description": "IPv6",
"properties": {
"ipv6": {
"required": true
}
}
}],
"links": [{
"rel": "self",
"href": "/servers/{id}"
}, {
"rel": "collection",
"href": "/servers"
}]
} }

@ -17,63 +17,97 @@ import json
from monikerclient import warlock from monikerclient import warlock
from monikerclient import utils from monikerclient import utils
from monikerclient.v1.base import Controller from monikerclient.v1.base import Controller
from monikerclient.v1.domains import Domain
Record = warlock.model_factory(utils.load_schema('v1', 'record')) Record = warlock.model_factory(utils.load_schema('v1', 'record'))
class RecordsController(Controller): class RecordsController(Controller):
def list(self): def list(self, domain):
""" """
Retrieve a list of records Retrieve a list of records
:param domain: :class:`Domain` or Domain Identifier
:returns: A list of :class:`Record`s :returns: A list of :class:`Record`s
""" """
response = self.client.get('/records') domain_id = domain.id if isinstance(domain, Domain) else domain
response = self.client.get('/domains/%(domain_id)s/records' % {
'domain_id': domain_id
})
return [Record(i) for i in response.json['records']] return [Record(i) for i in response.json['records']]
def get(self, record_id): def get(self, domain, record_id):
""" """
Retrieve a record Retrieve a record
:param domain: :class:`Domain` or Domain Identifier
:param record_id: Record Identifier :param record_id: Record Identifier
:returns: :class:`Record` :returns: :class:`Record`
""" """
response = self.client.get('/records/%s' % record_id) domain_id = domain.id if isinstance(domain, Domain) else domain
uri = '/domains/%(domain_id)s/records/%(record_id)s' % {
'domain_id': domain_id,
'record_id': record_id
}
response = self.client.get(uri)
return Record(response.json) return Record(response.json)
def create(self, record): def create(self, domain, record):
""" """
Create a record Create a record
:param domain: :class:`Domain` or Domain Identifier
:param record: A :class:`Record` to create :param record: A :class:`Record` to create
:returns: :class:`Record` :returns: :class:`Record`
""" """
response = self.client.post('/records', data=json.dumps(record)) domain_id = domain.id if isinstance(domain, Domain) else domain
return record.update(response.json) uri = '/domains/%(domain_id)s/records' % {
'domain_id': domain_id
}
def update(self, record): response = self.client.post(uri, data=json.dumps(record))
return Record(response.json)
def update(self, domain, record):
""" """
Update a record Update a record
:param domain: :class:`Domain` or Domain Identifier
:param record: A :class:`Record` to update :param record: A :class:`Record` to update
:returns: :class:`Record` :returns: :class:`Record`
""" """
response = self.client.put('/records/%s' % record.id, domain_id = domain.id if isinstance(domain, Domain) else domain
data=json.dumps(record))
uri = '/domains/%(domain_id)s/records/%(record_id)s' % {
'domain_id': domain_id,
'record_id': record.id
}
response = self.client.put(uri, data=json.dumps(record))
return record.update(response.json) return record.update(response.json)
def delete(self, record): def delete(self, domain, record):
""" """
Delete a record Delete a record
:param domain: :class:`Domain` or Domain Identifier
:param record: A :class:`Record`, or Record Identifier to delete :param record: A :class:`Record`, or Record Identifier to delete
""" """
if isinstance(record, Record): domain_id = domain.id if isinstance(domain, Domain) else domain
self.client.delete('/records/%s' % record.id) record_id = record.id if isinstance(record, Record) else record
else:
self.client.delete('/records/%s' % record) uri = '/domains/%(domain_id)s/records/%(record_id)s' % {
'domain_id': domain_id,
'record_id': record_id
}
self.client.delete(uri)

@ -53,7 +53,7 @@ class ServersController(Controller):
""" """
response = self.client.post('/servers', data=json.dumps(server)) response = self.client.post('/servers', data=json.dumps(server))
return server.update(response.json) return Server(response.json)
def update(self, server): def update(self, server):
""" """

@ -16,8 +16,11 @@
# Hopefully we can upstream the changes ASAP. # Hopefully we can upstream the changes ASAP.
# #
import copy import copy
import logging
import jsonschema import jsonschema
LOG = logging.getLogger(__name__)
class InvalidOperation(RuntimeError): class InvalidOperation(RuntimeError):
pass pass
@ -38,8 +41,8 @@ def model_factory(schema):
"""Apply a JSON schema to an object""" """Apply a JSON schema to an object"""
try: try:
jsonschema.validate(obj, schema) jsonschema.validate(obj, schema)
except jsonschema.ValidationError: except jsonschema.ValidationError, e:
raise ValidationError() raise ValidationError(str(e))
class Model(dict): class Model(dict):
"""Self-validating model for arbitrary objects""" """Self-validating model for arbitrary objects"""
@ -51,8 +54,8 @@ def model_factory(schema):
self.__dict__['validator'] = validator self.__dict__['validator'] = validator
try: try:
self.validator(d) self.validator(d)
except ValidationError: except ValidationError, e:
raise ValueError() raise ValueError('Validation Error: %s' % str(e))
else: else:
dict.__init__(self, d) dict.__init__(self, d)

@ -54,6 +54,18 @@ setup(
domain-create = monikerclient.cli.domains:CreateDomainCommand domain-create = monikerclient.cli.domains:CreateDomainCommand
domain-update = monikerclient.cli.domains:UpdateDomainCommand domain-update = monikerclient.cli.domains:UpdateDomainCommand
domain-delete = monikerclient.cli.domains:DeleteDomainCommand domain-delete = monikerclient.cli.domains:DeleteDomainCommand
record-list = monikerclient.cli.records:ListRecordsCommand
record-get = monikerclient.cli.records:GetRecordCommand
record-create = monikerclient.cli.records:CreateRecordCommand
record-update = monikerclient.cli.records:UpdateRecordCommand
record-delete = monikerclient.cli.records:DeleteRecordCommand
server-list = monikerclient.cli.servers:ListServersCommand
server-get = monikerclient.cli.servers:GetServerCommand
server-create = monikerclient.cli.servers:CreateServerCommand
server-update = monikerclient.cli.servers:UpdateServerCommand
server-delete = monikerclient.cli.servers:DeleteServerCommand
"""), """),
classifiers=[ classifiers=[
'Development Status :: 3 - Alpha', 'Development Status :: 3 - Alpha',