Skip to content

Commit

Permalink
Update to use new api paths and names
Browse files Browse the repository at this point in the history
Also adds new required cred_type and source_type fields.

Closes #86
  • Loading branch information
kdelee committed Dec 8, 2017
1 parent 42ab8c5 commit 7d663b7
Show file tree
Hide file tree
Showing 12 changed files with 505 additions and 440 deletions.
2 changes: 1 addition & 1 deletion camayoc/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@
QCS_CREDENTIALS_PATH = 'credentials/'
"""The path to the credentials endpoint for CRUD tasks."""

QCS_PROFILES_PATH = 'sources/'
QCS_SOURCE_PATH = 'sources/'
"""The path to the profiles endpoint for CRUD tasks."""

QCS_SCAN_PATH = 'scans/'
Expand Down
122 changes: 72 additions & 50 deletions camayoc/qcs_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from camayoc.constants import MASKED_PASSWORD_OUTPUT
from camayoc.constants import (
QCS_CREDENTIALS_PATH,
QCS_PROFILES_PATH,
QCS_SOURCE_PATH,
QCS_SCAN_PATH,
)

Expand Down Expand Up @@ -52,7 +52,22 @@ def payload(self):
"""Return a dictionary for POST or PUT requests."""
return {
k: v for k, v in vars(self).items()
if k not in ['_id', 'client', 'endpoint']
if k not in ['_id',
'client',
'endpoint',
]
}

def update_payload(self):
"""Return a dictionary for POST or PUT requests."""
return {
k: v for k, v in vars(self).items()
if k not in ['_id',
'client',
'endpoint',
'cred_type',
'source_type'
]
}

def to_str(self):
Expand Down Expand Up @@ -83,7 +98,8 @@ def create(self, **kwargs):
the data associated with this object's ``self._id``.
"""
response = self.client.post(self.endpoint, self.payload(), **kwargs)
self._id = response.json().get('id')
if response.status_code in range(200, 203):
self._id = response.json().get('id')
return response

def list(self, **kwargs):
Expand Down Expand Up @@ -115,14 +131,14 @@ def update(self, **kwargs):
:param ``**kwargs``: Additional arguments accepted by Requests's
`request.request()` method.
Sends `self.payload()` as the data of the PUT request, thereby updating
the object on the server with the same `id` as this object with the
fields contained in `self.payload()`.
Sends `self.update_payload()` as the data of the PUT request, thereby
updating the object on the server with the same `id` as this object
with the fields contained in `self.update_payload()`.
Returns a requests.models.Response. The json of this response contains
the data associated with this object's `self._id`.
"""
return self.client.put(self.path(), self.payload(), **kwargs)
return self.client.put(self.path(), self.update_payload(), **kwargs)

def delete(self, **kwargs):
"""Send DELETE request to the self.endpoint/{id} of this object.
Expand All @@ -136,19 +152,19 @@ def delete(self, **kwargs):
return self.client.delete(self.path(), **kwargs)


class HostCredential(QCSObject):
class Credential(QCSObject):
"""A class to aid in CRUD tests of Host Credentials on the QCS server.
Host credentials can be created by instantiating a HostCredential
Host credentials can be created by instantiating a Credential
object. A unique name and username are provided by default.
In order to create a valid host credential you must specify either a
password or ssh_keyfile.
Example::
>>> from camayoc import api
>>> from camayoc.qcs_models import HostCredential
>>> from camayoc.qcs_models import Credential
>>> client = api.QCSClient()
>>> cred = HostCredential(password='foo')
>>> cred = Credential(cred_type='network', password='foo')
>>> # The create method automatically sets the credential's `_id`
>>> cred.create()
>>> actual_cred = cred.read().json()
Expand All @@ -163,13 +179,14 @@ def __init__(
password=None,
ssh_keyfile=None,
sudo_password=None,
cred_type=None,
_id=None):
"""Create a host credential with given data.
If no arguments are passed, then a api.Client will be initialized and a
uuid4 generated for the name and username.
For a HostCredential to be successfully created on the QCS server,
For a Credential to be successfully created on the QCS server,
a password XOR a ssh_keyfile must be provided.
"""
super().__init__(client=client, _id=_id)
Expand All @@ -179,23 +196,24 @@ def __init__(
self.password = password
self.ssh_keyfile = ssh_keyfile
self.sudo_password = sudo_password
self.cred_type = cred_type

def equivalent(self, other):
"""Return true if both objects are equal.
:param other: This can be either another HostCredential or a dictionary
:param other: This can be either another Credential or a dictionary
or json object returned from the QCS server (or crafted by hand.)
If `other` is a HostCredential instance, the two object's fields()
If `other` is a Credential instance, the two object's fields()
will be compared. Otherwise, we expect the password to have been
masked by the server.
"""
if isinstance(other, HostCredential):
if isinstance(other, Credential):
return self.fields() == other.fields()

if not isinstance(other, dict):
raise TypeError(
'Objects of type HostCredential can only be compared to'
'HostCredential objects or dictionaries.'
'Objects of type Credential can only be compared to'
'Credential objects or dictionaries.'
)

password_matcher = re.compile(MASKED_PASSWORD_OUTPUT)
Expand All @@ -209,26 +227,25 @@ def equivalent(self, other):
return True


class NetworkProfile(QCSObject):
"""A class to aid in CRUD test cases for network profiles.
class Source(QCSObject):
"""A class to aid in CRUD test cases for sources.
Network profiles can be created on the quipucords server by
instantiating a NetworkProfile object. A unique name and username are
provided by default. In order to create a valid network profile,
Sources can be created on the quipucords server by
instantiating a Source object. A unique name and username are
provided by default. In order to create a valid source,
you must specify at least one existing host credential and one host.
Example::
>>> from camayoc.qcs_models import NetworkProfile
>>> from camayoc.qcs_models import Source
>>>
>>> hostcred = HostCredential(password='foo')
>>> hostcred.create()
>>> netprof = NetworkProfile(hosts=['0.0.0.0'],
>>> cred = Credential(cred_type='network',password='foo')
>>> cred.create()
>>> source = Source( source_type='network', hosts=['0.0.0.0'],
credential_ids=[hostcred._id])
>>> netprof.create()
>>> actual_prof = netprof.read().json()
>>>
>>> source.create()
>>> actual_source = source.read().json()
>>>
>>> assert actual_prof.equivalent(netprof)
>>> assert source.equivalent(actual_source)
"""

def __init__(
Expand All @@ -238,46 +255,48 @@ def __init__(
hosts=None,
ssh_port=22,
credential_ids=None,
source_type=None,
_id=None):
"""Iniitalize a NetworkProfile object with given data.
"""Iniitalize a Source object with given data.
If no ssh_port is supplied, it will be set to 22 by default.
A uuid4 name and api.Client are also supplied if none are provided.
"""
super().__init__(client=client, _id=_id)
self.name = str(uuid.uuid4()) if name is None else name
self.endpoint = QCS_PROFILES_PATH
self.endpoint = QCS_SOURCE_PATH
self.hosts = hosts
self.ssh_port = ssh_port
self.credentials = credential_ids
self.source_type = source_type

def equivalent(self, other):
"""Return true if both objects are equivalent.
:param other: This can be either another NetworkProfile or a dictionary
:param other: This can be either another Source or a dictionary
or json object returned from the QCS server (or crafted by hand.)
If `other` is a NetworkProfile instance, the two object's fields()
If `other` is a Source instance, the two object's fields()
will be compared. Otherwise, we must extract the credential id's
from the json returned by the server into a list, because that is
all the data that we use to generate the network profile.
all the data that we use to generate the source.
"""
if isinstance(other, NetworkProfile):
if isinstance(other, Source):
return self.fields() == other.fields()

if not isinstance(other, dict):
raise TypeError(
'Objects of type NetworkProfile can only be compared to'
'NetworkProfiles objects or dictionaries.'
'Objects of type Source can only be compared to'
'Sources objects or dictionaries.'
)

for key, value in self.fields().items():
if key == 'credentials':
other_creds = other.get('credentials')
cred_ids = []
# the server returns a list of dictionaries
# one for each credential associated with the NetworkProfile
# one for each credential associated with the Source
# we extract from this all the id's and then compare it with
# the list of id's we used to create the Network Profile
# the list of id's we used to create the source
for cred in other_creds:
cred_ids.append(cred.get('id'))
if sorted(value) != sorted(cred_ids):
Expand All @@ -294,16 +313,19 @@ class Scan(QCSObject):
Scan jobs can be created on the quipucords server by instantiating a Scan
object and then calling its create() method.
The id of an existing NetworkProfile is necessary to create a scan
The id of an existing Source is necessary to create a scan
job.
Example::
>>> hostcred = HostCredential(password='foo')
>>> hostcred.create()
>>> netprof = NetworkProfile(hosts=['0.0.0.0'],
credential_ids=[hostcred._id])
>>> netprof.create()
>>> scan = Scan(profile_id=netprof._id)
>>> cred = Credential(cred_type='network', password='foo')
>>> cred.create()
>>> src = Source(
source_type='network',
hosts=['0.0.0.0'],
credential_ids=[cred._id]
)
>>> src.create()
>>> scan = Scan(source_id=src._id)
>>> scan.create()
>>> scan.pause()
>>> assert scan.status() == 'paused'
Expand All @@ -322,7 +344,7 @@ def __init__(
no value for scan_type is given, the default type is host (for which
facts are collected). The other valid option for scan_type is
'discovery' in which no facts are collected, the server simply sees how
many hosts in the profile it can make contact and log into.
many hosts in the source it can make contact and log into.
"""
super().__init__(client=client, _id=_id)

Expand Down Expand Up @@ -388,7 +410,7 @@ def equivalent(self, other):
will be compared.
For a dictionary, we expect the format returned by the server with
self.read(), in which case we must extract the profile id from a
self.read(), in which case we must extract the source id from a
dictionary.
"""
if isinstance(other, Scan):
Expand All @@ -401,7 +423,7 @@ def equivalent(self, other):
)

for key, value in self.fields().items():
if key == 'profile':
if key == 'source':
if value != other.get(key).get('id'):
return False
else:
Expand Down
Loading

0 comments on commit 7d663b7

Please sign in to comment.