class PublicIP(RestObject): def __init__(self, connect, base_resource, uid, initial_data): res = "%s/public_ips/%s" % (base_resource, uid) super(PublicIP, self).__init__(connect, res, initial_data) uid = RestAttribute('id', readonly=True) address = RestAttribute('address')
class Credential(RestObject): def __init__(self, connect, res, uid, intial_data): super(Credential, self).__init__(connect, "%s/credentials/%s" % (res, uid), intial_data) uid = RestAttribute("id", readonly=True) text = RestAttribute("text")
class Label(RestObject): def __init__(self, connect, res, uid, intial_data): super(Label, self).__init__(connect, "%s/labels/%s" % (res, uid), intial_data) uid = RestAttribute("id", readonly=True) text = RestAttribute("text") label_type = RestAttribute("type")
class PublishedService(RestObject): def __init__(self, connect, base_resource, uid, initial_data): res = "%s/services/%s" % (base_resource, uid) super(PublishedService, self).__init__(connect, res, initial_data) uid = RestAttribute('id', readonly=True) external_port = RestAttribute('external_port', readonly=True) internal_port = RestAttribute('internal_port') external_ip = RestAttribute('external_ip', readonly=True)
class Hardware(RestObject): def __init__(self, connect, res, initial_data, parent): super(Hardware, self).__init__(connect, res, initial_data, is_full=True, parent=parent, parent_attr="hardware") cpus = RestAttribute("cpus") guest_os = RestAttribute("guestOS", readonly=True) max_cpus = RestAttribute("max_cpus", readonly=True) max_ram = RestAttribute("max_ram", readonly=True) min_ram = RestAttribute("min_ram", readonly=True) ram = RestAttribute("ram") svms = RestAttribute("svms", readonly=True) upgradable = RestAttribute("upgradable", readonly=True) vnc_keymap = RestAttribute("vnc_keymap", readonly=True) def disks(self): return [ Disk(self._connect, self._resource, disk) for disk in self.alldata()['disks'] ] def addDisk(self, size): msg = {"hardware": {"disks": {"new": [size]}}} self._connect.put(self._resource, body=msg)
class Note(RestObject): def __init__(self, connect, res, uid, intial_data): super(Note, self).__init__(connect, "%s/notes/%s" % (res, uid), intial_data, can_refresh=False) uid = RestAttribute("id", readonly=True) text = RestAttribute("text") time = RestAttribute("time", readonly=True) user_id = RestAttribute("user_id", readonly=True)
class Template(AssignableObject): """ A template is a specification of one or more virtual machine images, plus associated resources such as network configurations, as well as metadata such as notes and tags. The purpose of a template is to serve as a blueprint from which any number of runnable configurations can be created. As such, a template is not directly runnable, and there are constraints on the ways a template can be modified once created. Many of the template attributes in the API are only informative, and cannot be modified unless working on an equivalent configuration. For example, the "runstate" attribute will list the state of the VMs in this template, but cannot be changed in the API. """ def __init__(self, connect, uid, intial_data, configuration_cls, user_cls): super(Template, self).__init__(connect, "templates/%s" % (uid), intial_data, "templates", user_cls) self._configuration_cls = configuration_cls uid = RestAttribute('id', readonly=True) name = RestAttribute('name') url = RestAttribute('url', readonly=True) busy = RestAttribute('busy', readonly=True) description = RestAttribute('description') lockversion = RestAttribute('lockversion', readonly=True) public = RestAttribute('public') tagList = RestAttribute('tag_list') region = RestAttribute('region', readonly=True) def vms(self): return VirtualMachines(self._connect, self._resource) def networks(self): return VirtualNetworks(self._connect, self._resource) def create_configuration(self, vm_ids=None): body = {'template_id':self.uid} if vm_ids is not None: body['vm_ids'] = vm_ids result = self._connect.post("configurations", body=body) return self._configuration_cls(self._connect, result['id'], result, Template, self._user_cls) def wait_for(self, check_interval=15, check_limit=20): remaining = check_limit busy = True while busy and remaining > 0: remaining -= 1 self.refresh() busy = self.busy if busy: time.sleep(check_interval) if busy: raise SkytapException("Failed to wait for state change on " "template %s" % self.uid)
class CompanyResourceLimit(RestObject): def __init__(self, connect, res, parent, attrname): super(CompanyResourceLimit, self).__init__(connect, res, parent.alldata().get(attrname), is_full=True, parent=parent, parent_attr=attrname) quota_type = RestAttribute('quota_type', readonly=True) units = RestAttribute('units', readonly=True) limit = RestAttribute('limit', readonly=True) usage = RestAttribute('subscription', readonly=True) max_limit = RestAttribute('max_limit', readonly=True)
class AssignableObject(RestObject): """ Assignable Object is a base class for Skytap objects like Configuration and Template that can assigned or re-assigned an owner. Assignable Objects can also manage access by adding itself to Skytap projects. Attributes: none """ def __init__(self, connect, resource, initial_data, obj_type, user_cls): """ Constructor for a Assignable object. Parameters connect - the Connect object that managed REST requests to skytap resource- the path to skytap resource for this Assignable object initialData - a partial dictionary of properties for the Assignable object. This partial data is obtained from the parent list when the library queries skytap for a list of objects. """ super(AssignableObject, self).__init__(connect, resource, initial_data) self._obj_type = obj_type self._user_cls = user_cls owner_url = RestAttribute('owner') def owner(self): if self.owner_url is None: return None (_, _, path, _, _) = urlparse.urlsplit(self.owner_url) owner_id = path.split('/')[-1] return self._user_cls(self._connect, owner_id, {}) def reassign(self, user, project=None): """ Reassign the user that owns this skytap resource. Parameters user - The skytap object for the user. Lookup the user in the skytap.users() dictionary. project - Reassigning the owner will clear the projects this resource belongs to. You have to option of passing a single project that the resource will be added to. """ body = {'owner': user.uid} if (project is not None): body['reassign_context'] = project.uid self.data = self._connect.put(self._resource, body=body) def add_to_project(self, project, role=None): """ Add this resource to a Skytap project. Parameters project - The project object can be found in the skytap.projects() dictionary. role - Optional parameter used when adding user or group object to a project. Valid values are 'viewer', 'participant', 'editor' or 'manager'. """ project.add(self, self._obj_type, role)
class StatefulObject(RestObject): runstate = RestAttribute('runstate') def check_state(self, states=None): if states is None: return self.runstate != 'busy' else: return self.runstate in states
class VirtualNetwork(RestObject): def __init__(self, connect, base_resource, uid, intial_data): res = "%s/networks/%s" % (base_resource, uid) super(VirtualNetwork, self).__init__(connect, res, intial_data) uid = RestAttribute('id', readonly=True) name = RestAttribute('name') domain = RestAttribute('domain') gateway = RestAttribute('gateway') network_type = RestAttribute('network_type') primary_nameserver = RestAttribute('primary_nameserver') secondary_nameserver = RestAttribute('secondary_nameserver') subnet = RestAttribute('subnet', readonly=True) subnet_addr = RestAttribute('subnet_addr') subnet_size = RestAttribute('subnet_size') tunnelable = RestBoolAttribute('tunnelable') def tunnels(self): return [ Tunnel(self._connect, tunnel['id'], tunnel) for tunnel in self.alldata()['tunnels'] ] def create_tunnel(self, target_network): args = { 'source_network_id': str(self.uid), 'target_network_id': str(target_network) } result = self._connect.post("tunnels", args) return Tunnel(self._connect, result['id'], result) def attach_vpn(self, vpn): body = {'vpn_id': vpn.uid} result = self._connect.post("%s/vpns" % self._resource, body) return AttachedVPN(self._connect, self._resource, vpn.uid, result) def vpns(self): return [ AttachedVPN(self._connect, self._resource, att['vpn']['id'], att) for att in self.alldata()['vpn_attachments'] ]
class PublishSetVM(RestObject): def __init__(self, connect, res, initial_data, vm_cls): super(PublishSetVM, self).__init__(connect, res, initial_data, can_delete=True, is_full=True, can_refresh=False) self._vm_cls = vm_cls uid = RestAttribute("id", readonly=True) name = RestAttribute("name") access = RestAttribute("access") desktop_url = RestAttribute("desktop_url") run_and_use = RestAttribute("run_and_use") def virtual_machine(self): items = self.alldata()['vm_ref'].split("/") res = '/'.join(items[3:-2]) uid = items[-1] return self._vm_cls(self._connect, res, uid, {'id': uid})
class Disk(RestObject): def __init__(self, connect, res, initial_data): super(Disk, self).__init__(connect, res, initial_data, is_full=True, can_refresh=False) controller = RestAttribute('controller', readonly=True) uid = RestAttribute('id', readonly=True) lun = RestAttribute('lun', readonly=True) size = RestAttribute('size', readonly=True) disk_type = RestAttribute('type', readonly=True) def delete(self): msg = {'hardware': {'disks': {'existing': {}}}} msg['hardware']['disks']['existing'][self.uid] = { 'id': self.uid, 'size': None } self._connect.put(self._resource, body=msg) self._active = False
class AttachedVPN(RestObject): """ This object represents the relationship between a Virtual Network and a VPN. A Virtual Network can be attached to multiple VPNs, and a VPN can have multiple Virtual Networks. This object represent a single pair of a an Attached Virtual Network, and the VPN it is attached to. """ def __init__(self, connect, base_resource, vpn_id, intial_data): res = "%s/vpns/%s" % (base_resource, vpn_id) super(AttachedVPN, self).__init__(connect, res, intial_data, is_full=True) uid = RestAttribute('id', readonly=True) connected = RestBoolAttribute('connected') def network_id(self): """ Return the skytap id for the Virtual Network attached to the VPN. """ return self.alldata()['network']['id'] def configuration_id(self): """ Every virtual network belongs to a Skytap configuration. This method returns the id of the configuration that the attached virtual network belongs to. """ return self.alldata()['network']['configuration_id'] def vpn_id(self): """ Return the id for the attached VPN. """ return self.alldata()['vpn']['id'] def vpn(self): """ Return the VPN object the virtual network is attached to. """ data = self.alldata()['vpn'] return VPN(self._connect, self.vpn_id(), data) def detach(self): """ Detach the virtual network from the VPN. This method is equivalent to calling delete() on this object. """ return self.delete()
class Asset(AssignableObject): """ Generally, a customer asset is any data available in the Skytap Cloud context. Typically this is data that a customer has loaded into Skytap Cloud to support running applications. Assets are top-level elements in the API data model. In the current release of the API, Assets can only be viewed, referenced and ownership managed, but not created, modified, or deleted. Attributes uid - unique identifier for asset object. name - human friendly name for the asset. public - True or false this asset is with other users in skytap. size - the size of the asset file. url - a url that can used to download a copy of the asset. """ def __init__(self, connect, uid, initial_data, user_cls): ''' Constructor Create a new Asset object Parameters connect - A connect.Connect object used to communicate with the Skytap REST API. uid - The unique identifier for the Asset object. initialData - A dict containing a partial cache of the name/value attributes for this asset. ''' super(Asset, self).__init__(connect, "assets/%s" % (uid), initial_data, "assets", user_cls) uid = RestAttribute("id", readonly=True) name = RestAttribute("name", readonly=True) public = RestAttribute("public", readonly=True) size = RestAttribute("size", readonly=True) url = RestAttribute("url", readonly=True)
class UsageReport(RestObject): def __init__(self, connect, uid, initial_data): ''' Constructor Create a new Usage Report Parameters connect - A connect.Connect object used to communicate with the Skytap REST API. uid - The unique identifier for the Usage Report object. initialData - A dict containing a partial cache of the name/value attributes for this usage report. ''' super(UsageReport, self).__init__(connect, "reports/%s" % uid, initial_data) uid = RestAttribute("id", readonly=True) start_date = RestAttribute("start_date") end_date = RestAttribute("end_date") utc = RestBoolAttribute("utc") resource_type = RestAttribute("resource_type") region = RestAttribute("region") ready = RestBoolAttribute("ready", readonly=True) url = RestAttribute("url", readonly=True) def wait_for(self, check_interval=10, check_limit=10): """ Blocking call to wait for the usage report to be ready. Once the report is ready (finished generating), the get_reader() method can be used to read the data for the report. """ remaining = check_limit ready = False while not ready and remaining > 0: remaining -= 1 self.refresh() ready = self.ready if not ready: time.sleep(check_interval) if not ready: raise SkytapException("Timeout waiting for report %s" "to be ready." % self.uid) def get_reader(self): """ Returns a reader for the usage report data. The returned object is a csv.DictReader. Use method next() on DictReader to read each row of the report. Each row is a python dict with column names as key, and the values of the current row. """ contents = self._connect.request(self.url, 'GET', accept_type='application/csv') clean_data = contents.encode('ascii', 'ignore') return csv.DictReader(clean_data.split('\n'))
class Tunnel(RestObject): """ A network tunnel created by connecting two virtual skytap networks together. Note: the networks must use non-overlaping subnets. """ def __init__(self, connect, uid, initial_data): super(Tunnel, self).__init__(connect, "tunnels/%s" % (uid), initial_data) uid = RestAttribute("id", readonly=True) status = RestAttribute("status", readonly=True) def source_network(self): """ Returns the network id for the source network in the tunnel. """ return self.alldata()["source_network"]["id"] def target_network(self): """ Returns the network id for the target network in the tunnel. """ return self.alldata()["target_network"]["id"] def check_state(self, states=None): """ Return a boolean value yes or no. The tunnel is in one of the states passed in states argument. If states is None, then check for any state except 'busy'. """ if states is None: return self.status != 'busy' elif self.status in states: return True else: return False
class PublishSet(RestObject): def __init__(self, connect, res, uid, intial_data, vm_cls): super(PublishSet, self).__init__(connect, "%s/publish_sets/%s" % (res, uid), intial_data) self._vm_cls = vm_cls uid = RestAttribute("id", readonly=True) name = RestAttribute("name") password = RestAttribute("password") publish_set_type = RestAttribute("publish_set_type") start_time = RestAttribute("start_time") end_time = RestAttribute("end_time") time_zone = RestAttribute("time_zone") url = RestAttribute("url", readonly=True) useSmartClient = RestAttribute("use_smart_client", readonly=True) def published_vms(self): return [ PublishSetVM(self._connect, self._resource, item, self._vm_cls) for item in self.alldata()['vms'] ]
class VirtualMachine(StatefulObject): def __init__(self, connect, base_resource, uid, initial_data): res = "%s/vms/%s" % (base_resource, uid) super(VirtualMachine, self).__init__(connect, res, initial_data) uid = RestAttribute('id', readonly=True) name = RestAttribute('name') assetId = RestAttribute('asset_id') can_change_object_state = RestAttribute('can_change_object_state') desktop_resizable = RestAttribute('desktop_resizable') error = RestAttribute('error', readonly=True) local_mouse_cursor = RestAttribute('local_mouse_cursor') def credentials(self): return Credentials(self._connect, self._resource) def notes(self): return Notes(self._connect, self._resource) def labels(self): return Labels(self._connect, self._resource, "VmInstance") def interfaces(self): return Interfaces(self._connect, self._resource) def mount_iso(self, asset_id): self._connect.put(self._resource, args={'asset_id': asset_id}) def hardware(self): return Hardware(self._connect, self._resource, self.data['hardware'], self) @classmethod def _extract_path(cls, resource_paths): for path in resource_paths: items = path.split("/") yield ('/'.join(items[3:-2]), items[-1]) def publish_sets(self): publish_set_data = self.alldata().get('publish_set_refs') or [] path_tuples = self._extract_path(publish_set_data) return [ PublishSet(self._connect, pset[0], pset[1], {}, VirtualMachine) for pset in path_tuples ]
class User(RestObject): def __init__(self, connect, uid, intial_data): super(User, self).__init__(connect, "users/%s" % (uid), intial_data) email = RestAttribute("email") first_name = RestAttribute("first_name") uid = RestAttribute("id", readonly=True) last_name = RestAttribute("last_name") login_name = RestAttribute("login_name") title = RestAttribute("title") account_role = RestAttribute("account_role") activated = RestAttribute("activated") can_export = RestAttribute("can_export") can_import = RestAttribute("can_import") sra_compression = RestAttribute("sra_compression") lockversion = RestAttribute("lockversion", readonly=True) password = RestAttribute("password")
class VPN(RestObject): def __init__(self, connect, uid, intial_data): super(VPN, self).__init__(connect, "vpns/%s" % (uid), intial_data) dpd_enabled = RestAttribute('dpd_enabled') enabled = RestAttribute('enabled') error = RestAttribute('error', readonly=True) uid = RestAttribute('id', readonly=True) local_peer_ip = RestAttribute('local_peer_ip') local_subnet = RestAttribute('local_subnet') maximum_segment_size = RestAttribute('maximum_segment_size') phase_1_dh_group = RestAttribute('phase_1_dh_group') phase_1_encryption_algorithm = RestAttribute('phase_1_encryption_algorithm') phase_1_hash_algorithm = RestAttribute('phase_1_hash_algorithm') phase_1_sa_lifetime = RestAttribute('phase_1_sa_lifetime') # pylint: disable=C0103 phase_2_authenticatioDn_algorithm = RestAttribute( 'phase_2_authentication_algorithm') phase_2_encryption_algorithm = RestAttribute('phase_2_encryption_algorithm') phase_2_perfect_forward_secrecy = RestAttribute( 'phase_2_perfect_forward_secrecy') # pylint: enable=C0103 phase_2_pfs_group = RestAttribute('phase_2_pfs_group') phase_2_sa_lifetime = RestAttribute('phase_2_sa_lifetime') remote_peer_ip = RestAttribute('remote_peer_ip') status = RestAttribute('status') region = RestAttribute('region', readonly=True) #u'remote_subnets': [ { u'cidr_block': u'10.1.0.0/23', # u'excluded': False, # u'id': u'10.1.0.0/23'}] #u'test_results': { u'connect': False, # u'phase1': True, # u'phase2': True, # u'ping': False}} #u'network_attachments': [ { u'configuration_id': u'536580', # u'configuration_name': u'rwh-singleNode', # u'configuration_type': u'configuration', # u'connected': True, # u'network_id': u'339508', # u'network_name': u'Network 1', # u'owner_id': u'22638', # u'owner_name': u'Bill Mepham (bmepham)', # u'subnet': u'192.168.0.0/24'} ] def list_allocations(self, min_net_class=24): vpn = self.alldata() total = vpn['local_subnet'].split("/") alloc = [] for net in vpn['network_attachments']: subnet = net['network']['subnet'].split("/") ip_num = VPN.ip_to_number(subnet[0]) alloc.append((net['network']['configuration_id'], ip_num, int(subnet[1]), subnet[0])) alloc.sort(key=lambda tup: tup[1]) start = VPN.ip_to_number(total[0]) end = start + math.pow(2, 32 - int(total[1])) index = start subnets = [] for cur in alloc: if(index < cur[1]): VPN.unallocated(index, cur[1], min_net_class, subnets) network = { "configuration_id": cur[0], "subnet": "%s/%d" % (cur[3], cur[2]) } subnets.append(network) index = cur[1] + math.pow(2, 32 - int(cur[2])) if(index < end): VPN.unallocated(index, end, min_net_class, subnets) return subnets @staticmethod def unallocated(start, end, min_net_class, results): if(start < end): inc = min(32 - min_net_class, math.floor(math.log(end - start, 2))) power = math.pow(2, inc) rem = start % power if(rem != 0): VPN.unallocated(start, start + rem, min_net_class, results) VPN.unallocated(start + rem, end, min_net_class, results) else: rec = { "configuration_id": "UNALLOCATED", "subnet": "%s/%d" % (VPN.number_to_ip(start), 32 - inc) } results.append(rec) if(inc != 0 and start + power < end): VPN.unallocated(start + power, end, min_net_class, results) @staticmethod def ip_to_number(ip_addr): "convert decimal dotted quad string to long integer" hexn = ''.join(["%02X" % long(seg) for seg in ip_addr.split('.')]) return long(hexn, 16) @staticmethod def number_to_ip(val): "convert long int to dotted quad string" denom = 256 * 256 * 256 quad = [] while denom > 0: seg, val = divmod(val, denom) quad.append(str(int(seg))) denom = denom / 256 return '.'.join(quad)
class Interface(RestObject): def __init__(self, connect, base_resource, uid, intial_data): res = "%s/interfaces/%s" % (base_resource, uid) super(Interface, self).__init__(connect, res, intial_data) uid = RestAttribute("id", readonly=True) hostname = RestAttribute("hostname") # pylint: disable=C0103 ip = RestAttribute("ip") # pylint: enable=C0103 mac = RestAttribute("mac") network_id = RestAttribute("network_id", readonly=True) network_name = RestAttribute("network_name", readonly=True) network_subnet = RestAttribute("network_subnet", readonly=True) network_type = RestAttribute("network_type", readonly=True) nic_type = RestAttribute("nic_type") status = RestAttribute("status", readonly=True) vm_id = RestAttribute("vm_id", readonly=True) vm_name = RestAttribute("vm_name", readonly=True) public_ips_count = RestAttribute("public_ips_count", readonly=True) services_count = RestAttribute("services_count", readonly=True) # FIX PUBLIC IPs TO INCLUDE ADD and DELETE def public_ips(self): return [ips['address'] for ips in self.alldata()['public_ips']] def services(self): return PublishedServices(self._connect, self._resource)
class Configuration(AssignableObject, StatefulObject): """ Like a template, a configuration is a specification that describes one or more virtual machine images, associated resources, and metadata around ownership, composition and resources. Unlike a template, many more of the properties of a configuration may be modified. More importantly, a configuration can be run. Attributes: runstate uid name url disable_internet error lockversion routable suspendOnIdle useSmartClient """ def __init__(self, connect, uid, initial_data, template_cls, user_cls): """ Constructor for a Configuration object. Parameters connect - the Connect object that managed REST requests to skytap uid - the skytap unique identifier for the Configuration object initialData - a partial dictionary of properties for the Configuration. This partial data is obtained from the Configurations object when it queries skytap for a list of configurations. templateCls - This parameter a reference to the dxskytap.Template class. It is required as a parameter to solve a dependency problem, which is explained here. """ super(Configuration, self).__init__(connect, "configurations/%s" % (uid), initial_data, "configurations", user_cls) self._template_cls = template_cls uid = RestAttribute('id', readonly=True) name = RestAttribute('name') url = RestAttribute('url', readonly=True) disable_internet = RestAttribute('disable_internet') error = RestAttribute('error', readonly=True) lockversion = RestAttribute('lockversion', readonly=True) routable = RestAttribute('routable') suspendOnIdle = RestAttribute('suspend_on_idle') useSmartClient = RestAttribute('use_smart_client') region = RestAttribute('region', readonly=True) def publish_sets(self): return PublishSets(self._connect, self._resource, VirtualMachine) def vms(self): return VirtualMachines(self._connect, self._resource) def networks(self): return VirtualNetworks(self._connect, self._resource) def notes(self): return Notes(self._connect, self._resource) def labels(self): return Labels(self._connect, self._resource, "ConfigurationTemplate") def merge_template(self, template_id, vm_ids=None): body = {'template_id':template_id} if(vm_ids is not None): body['vm_ids'] = vm_ids return self._connect.put(self._resource, body=body) def create_template(self, vm_ids=None): body = {'configuration_id':self.uid} if(vm_ids is not None): body['vm_ids'] = vm_ids result = self._connect.post("templates", body=body) return self._template_cls(self._connect, result['id'], result, Configuration, self._user_cls) def wait_for(self, states=None, check_interval=15, check_limit=20): resources = [self] resources.extend(self.vms().values()) for network in self.networks().values(): resources.extend(network.tunnels()) # TODO: add VPN to resource list remaining = check_limit ready = False while not ready and remaining > 0: remaining -= 1 ready = True for res in resources: try: res.refresh() except ValueError: pass ready &= res.check_state(states) if not ready: time.sleep(check_interval) if not ready: raise SkytapException("Failed to wait for state change on " "configuration %s" % self.uid)
class Project(RestObject): """ A project is a simple object in skytap used to share resources (configurations, templates and assets) between users. Add resources to projects and assign users roles as part of a project. Attributes: uid name summary show-project-members auto-add-role-name """ def __init__(self, connect, uid, initial_data): """ Constructor for a Project object. Parameters connect - the Connect object that managed REST requests to skytap uid - the skytap unique identifier for the Configuration object initialData - a partial dictionary of properties for the Project. This partial data is obtained from the Projects object when it queries skytap for a list of projects. """ super(Project, self).__init__(connect, "projects/%s" % (uid), initial_data) uid = RestAttribute('id', readonly=True) name = RestAttribute('name') summary = RestAttribute('summary') showProjectMembers = RestBoolAttribute('show-project-members') autoAddRoleName = RestAttribute('auto-add-role-name') def templates(self): """ Returns a list of all Skytap templates that are part of this project. """ results = self._connect.get("templates") return [ Template(self._connect, data['id'], data, Configuration, User) for data in results ] def configurations(self): """ Returns a list of all Skytap configurations that are part of this project. """ results = self._connect.get("configurations") return [ Configuration(self._connect, data['id'], data, Template, User) for data in results ] def add(self, obj, obj_type, role=None): """ :obj: Skytap Resource to be added to the project :obj_type: configurations, templates, assets, group, or users :role: viewer, participant, editor or manager """ args = {} if role is not None: args['role'] = str(role) self._connect.post("%s/%s/%s" % (self._resource, obj_type, obj.uid), args=args)