def ip_allocate(self, public=False): """ Allocates a new :any:`IPAddress` for this Instance. Additional public IPs require justification, and you may need to open a :any:`SupportTicket` before you can add one. You may only have, at most, one private IP per Instance. :param public: If the new IP should be public or private. Defaults to private. :type public: bool :returns: The new IPAddress :rtype: IPAddress """ result = self._client.post("{}/ips".format(Instance.api_endpoint), model=self, data={ "type": "ipv4", "public": public, }) if not 'address' in result: raise UnexpectedResponseError('Unexpected response allocating IP!', json=result) i = IPAddress(self._client, result['address'], result) return i
def _get_objects(self, endpoint, cls, model=None, parent_id=None, filters=None): response_json = self.get(endpoint, model=model, filters=filters) if not "data" in response_json: raise UnexpectedResponseError("Problem with response!", json=response_json) if 'pages' in response_json: formatted_endpoint = endpoint if model: formatted_endpoint = formatted_endpoint.format(**vars(model)) return PaginatedList.make_paginated_list( response_json, self, cls, parent_id=parent_id, page_url=formatted_endpoint[1:], filters=filters) return PaginatedList.make_list(response_json["data"], self, cls, parent_id=parent_id)
def clone(self, to_linode=None, region=None, service=None, configs=[], disks=[], label=None, group=None, with_backups=None): """ Clones this linode into a new linode or into a new linode in the given region """ if to_linode and region: raise ValueError('You may only specify one of "to_linode" and "region"') if region and not service: raise ValueError('Specifying a region requires a "service" as well') if not isinstance(configs, list) and not isinstance(configs, PaginatedList): configs = [configs] if not isinstance(disks, list) and not isinstance(disks, PaginatedList): disks = [disks] cids = [ c.id if issubclass(type(c), Base) else c for c in configs ] dids = [ d.id if issubclass(type(d), Base) else d for d in disks ] params = { "linode_id": to_linode.id if issubclass(type(to_linode), Base) else to_linode, "region": region.id if issubclass(type(region), Base) else region, "type": service.id if issubclass(type(service), Base) else service, "configs": cids if cids else None, "disks": dids if dids else None, "label": label, "group": group, "with_backups": with_backups, } result = self._client.post('{}/clone'.format(Instance.api_endpoint), model=self, data=params) if not 'id' in result: raise UnexpectedResponseError('Unexpected response cloning Instance!', json=result) l = Instance(self._client, result['id'], result) return l
def available_backups(self): """ The backups response contains what backups are available to be restored. """ if not hasattr(self, '_avail_backups'): result = self._client.get("{}/backups".format(Instance.api_endpoint), model=self) if not 'automatic' in result: raise UnexpectedResponseError('Unexpected response loading available backups!', json=result) automatic = [] for a in result['automatic']: cur = Backup(self._client, a['id'], self.id, a) automatic.append(cur) snap = None if result['snapshot']['current']: snap = Backup(self._client, result['snapshot']['current']['id'], self.id, result['snapshot']['current']) psnap = None if result['snapshot']['in_progress']: psnap = Backup(self._client, result['snapshot']['in_progress']['id'], self.id, result['snapshot']['in_progress']) self._set('_avail_backups', MappedObject(**{ "automatic": automatic, "snapshot": { "current": snap, "in_progress": psnap, } })) return self._avail_backups
def image_create(self, disk, label=None, description=None): """ Creates a new Image from a disk you own. :param disk: The Disk to imagize. :type disk: Disk or int :param label: The label for the resulting Image (defaults to the disk's label. :type label: str :param description: The description for the new Image. :type description: str :returns: The new Image. :rtype: Image """ params = { "disk_id": disk.id if issubclass(type(disk), Base) else disk, } if label is not None: params["label"] = label if description is not None: params["description"] = description result = self.post('/images', data=params) if not 'id' in result: raise UnexpectedResponseError( 'Unexpected response when creating an ' 'Image from disk {}'.format(disk)) return Image(self, result['id'], result)
def domain_create(self, domain, master=True, **kwargs): """ Registers a new Domain on the acting user's account. Make sure to point your registrar to Linode's nameservers so that Linode's DNS manager will correctly serve your domain. :param domain: The domain to register to Linode's DNS manager. :type domain: str :param master: Whether this is a master (defaults to true) :type master: bool :returns: The new Domain object. :rtype: Domain """ params = { 'domain': domain, 'type': 'master' if master else 'slave', } params.update(kwargs) result = self.post('/domains', data=params) if not 'id' in result: raise UnexpectedResponseError( 'Unexpected response when creating Domain!', json=result) d = Domain(self, result['id'], result) return d
def ip_allocate(self, linode, public=True): """ Allocates an IP to a Instance you own. Additional IPs must be requested by opening a support ticket first. :param linode: The Instance to allocate the new IP for. :type linode: Instance or int :param public: If True, allocate a public IP address. Defaults to True. :type public: bool :returns: The new IPAddress :rtype: IPAddress """ result = self.client.post( '/networking/ips/', data={ "linode_id": linode.id if isinstance(linode, Base) else linode, "type": "ipv4", "public": public, }) if not 'address' in result: raise UnexpectedResponseError( 'Unexpected response when adding IPv4 address!', json=result) ip = IPAddress(self.client, result['address'], result) return ip
def node_pool_create(self, node_type, node_count, **kwargs): """ Creates a new :any:`LKENodePool` for this cluster. :param node_type: The type of nodes to create in this pool. :type node_type: :any:`Type` or str :param node_count: The number of nodes to create in this pool. :type node_count: int :param kwargs: Any other arguments to pass to the API. See the API docs for possible values. :returns: The new Node Pool :rtype: LKENodePool """ params = { "type": node_type, "count": node_count, } params.update(kwargs) result = self._client.post("{}/pools".format(LKECluster.api_endpoint), model=self, data=params) self.invalidate() if not 'id' in result: raise UnexpectedResponseError( 'Unexpected response creating node pool!', json=result) return LKENodePool(self._client, result["id"], self.id, result)
def ticket_open(self, summary, description, regarding=None): """ """ params = { "summary": summary, "description": description, } if regarding: if isinstance(regarding, Instance): params['linode_id'] = regarding.id elif isinstance(regarding, Domain): params['domain_id'] = regarding.id elif isinstance(regarding, NodeBalancer): params['nodebalancer_id'] = regarding.id elif isinstance(regarding, Volume): params['volume_id'] = regarding.id else: raise ValueError( 'Cannot open ticket regarding type {}!'.format( type(regarding))) result = self.client.post('/support/tickets', data=params) if not 'id' in result: raise UnexpectedResponseError( 'Unexpected response when creating ticket!', json=result) t = SupportTicket(self.client, result['id'], result) return t
def stackscript_create(self, label, script, images, desc=None, public=False, **kwargs): """ Creates a new :any:`StackScript` on your account. :param label: The label for this StackScript. :type label: str :param script: The script to run when an :any:`Instance` is deployed with this StackScript. Must begin with a shebang (#!). :type script: str :param images: A list of :any:`Images<Image>` that this StackScript supports. Instances will not be deployed from this StackScript unless deployed from one of these Images. :type images: list of Image :param desc: A description for this StackScript. :type desc: str :param public: Whether this StackScript is public. Defaults to False. Once a StackScript is made public, it may not be set back to private. :type public: bool :returns: The new StackScript :rtype: StackScript """ image_list = None if type(images) is list or type(images) is PaginatedList: image_list = [d.id if issubclass(type(d), Base) else d for d in images ] elif type(images) is Image: image_list = [images.id] elif type(images) is str: image_list = [images] else: raise ValueError('images must be a list of Images or a single Image') script_body = script if not script.startswith("#!"): # it doesn't look like a stackscript body, let's see if it's a file import os if os.path.isfile(script): with open(script) as f: script_body = f.read() else: raise ValueError("script must be the script text or a path to a file") params = { "label": label, "images": image_list, "is_public": public, "script": script_body, "description": desc if desc else '', } params.update(kwargs) result = self.client.post('/linode/stackscripts', data=params) if not 'id' in result: raise UnexpectedResponseError('Unexpected response when creating StackScript!', json=result) s = StackScript(self.client, result['id'], result) return s
def duplicate(self): result = self._client.post(Disk.api_endpoint, model=self, data={}) if not 'id' in result: raise UnexpectedResponseError('Unexpected response duplicating disk!', json=result) d = Disk(self._client, result['id'], self.linode_id, result) return d
def transfer(self): """ Returns a MappedObject containing the account's transfer pool data """ result = self.client.get('/account/transfer') if not 'used' in result: raise UnexpectedResponseError('Unexpected response when getting Transfer Pool!') return MappedObject(**result)
def ips(self): """ The ips related collection is not normalized like the others, so we have to make an ad-hoc object to return for its response """ if not hasattr(self, '_ips'): result = self._client.get("{}/ips".format(Instance.api_endpoint), model=self) if not "ipv4" in result: raise UnexpectedResponseError( 'Unexpected response loading IPs', json=result) v4pub = [] for c in result['ipv4']['public']: i = IPAddress(self._client, c['address'], c) v4pub.append(i) v4pri = [] for c in result['ipv4']['private']: i = IPAddress(self._client, c['address'], c) v4pri.append(i) shared_ips = [] for c in result['ipv4']['shared']: i = IPAddress(self._client, c['address'], c) shared_ips.append(i) slaac = IPAddress(self._client, result['ipv6']['slaac']['address'], result['ipv6']['slaac']) link_local = IPAddress(self._client, result['ipv6']['link_local']['address'], result['ipv6']['link_local']) pools = [] for p in result['ipv6']['global']: pools.append(IPv6Pool(self._client, p['range'])) ips = MappedObject( **{ "ipv4": { "public": v4pub, "private": v4pri, "shared": shared_ips, }, "ipv6": { "slaac": slaac, "link_local": link_local, "pools": pools, }, }) self._set('_ips', ips) return self._ips
def config_create(self, label=None, **kwargs): params = kwargs if label: params['label'] = label result = self._client.post("{}/configs".format(NodeBalancer.api_endpoint), model=self, data=params) self.invalidate() if not 'id' in result: raise UnexpectedResponseError('Unexpected response creating config!', json=result) c = NodeBalancerConfig(self._client, result['id'], self.id, result) return c
def post_reply(self, description): """ """ result = self._client.post("{}/replies".format(SupportTicket.api_endpoint), model=self, data={ "description": description, }) if not 'id' in result: raise UnexpectedResponseError('Unexpected response when creating ticket reply!', json=result) r = TicketReply(self._client, result['id'], self.id, result) return r
def snapshot(self, label=None): result = self._client.post("{}/backups".format(Instance.api_endpoint), model=self, data={ "label": label }) if not 'id' in result: raise UnexpectedResponseError('Unexpected response taking snapshot!', json=result) # so the changes show up the next time they're accessed if hasattr(self, '_avail_backups'): del self._avail_backups b = Backup(self._client, result['id'], self.id, result) return b
def settings(self): """ Resturns the account settings data for this acocunt. This is not a listing endpoint. """ result = self.client.get('/account/settings') if not 'managed' in result: raise UnexpectedResponseError('Unexpected response when getting account settings!', json=result) s = AccountSettings(self.client, result['managed'], result) return s
def reset_secret(self): """ Resets the client secret for this client. """ result = self._client.post("{}/reset_secret".format( OAuthClient.api_endpoint), model=self) if not 'id' in result: raise UnexpectedResponseError( 'Unexpected response when resetting secret!', json=result) self._populate(result) return self.secret
def rebuild(self, image, root_pass=None, authorized_keys=None, **kwargs): """ Rebuilding an Instance deletes all existing Disks and Configs and deploys a new :any:`Image` to it. This can be used to reset an existing Instance or to install an Image on an empty Instance. :param image: The Image to deploy to this Instance :type image: str or Image :param root_pass: The root password for the newly rebuilt Instance. If omitted, a password will be generated and returned. :type root_pass: str :param authorized_keys: The ssh public keys to install in the linode's /root/.ssh/authorized_keys file. Each entry may be a single key, or a path to a file containing the key. :type authorized_keys: list or str :returns: The newly generated password, if one was not provided (otherwise True) :rtype: str or bool """ ret_pass = None if not root_pass: ret_pass = Instance.generate_root_password() root_pass = ret_pass authorized_keys = load_and_validate_keys(authorized_keys) params = { 'image': image.id if issubclass(type(image), Base) else image, 'root_pass': root_pass, 'authorized_keys': authorized_keys, } params.update(kwargs) result = self._client.post('{}/rebuild'.format(Instance.api_endpoint), model=self, data=params) if not 'id' in result: raise UnexpectedResponseError( 'Unexpected response issuing rebuild!', json=result) # update ourself with the newly-returned information self._populate(result) if not ret_pass: return True else: return ret_pass
def attach(self, to_linode, config=None): """ Attaches this Volume to the given Linode """ result = self._client.post('{}/attach'.format(Volume.api_endpoint), model=self, data={ "linode_id": to_linode.id if issubclass(type(to_linode), Base) else to_linode, "config": None if not config else config.id if issubclass(type(config), Base) else config, }) if not 'id' in result: raise UnexpectedResponseError('Unexpected response when attaching volume!', json=result) self._populate(result) return True
def record_create(self, record_type, **kwargs): params = { "type": record_type, } params.update(kwargs) result = self._client.post("{}/records".format(Domain.api_endpoint), model=self, data=params) self.invalidate() if not 'id' in result: raise UnexpectedResponseError('Unexpected response creating domain record!', json=result) zr = DomainRecord(self._client, result['id'], self.id, result) return zr
def transfer(self): """ Get per-linode transfer """ if not hasattr(self, '_transfer'): result = self._client.get("{}/transfer".format(Instance.api_endpoint), model=self) if not 'used' in result: raise UnexpectedResponseError('Unexpected response when getting Transfer Pool!') mapped = MappedObject(**result) setattr(self, '_transfer', mapped) return self._transfer
def clone(self, label): """ Clones this volume to a new volume in the same region with the given label :param label: The label for the new volume. :returns: The new volume object. """ result = self._client.post('{}/clone'.format(Volume.api_endpoint), model=self, data={'label': label}) if not 'id' in result: raise UnexpectedResponseError('Unexpected response cloning volume!') return Volume(self._client, result['id'], result)
def add_whitelist_entry(self, address, netmask, note=None): """ Adds a new entry to this user's IP whitelist, if enabled """ result = self._client.post("{}/whitelist".format(Profile.api_endpoint), data={ "address": address, "netmask": netmask, "note": note, }) if not 'id' in result: raise UnexpectedResponseError("Unexpected response creating whitelist entry!") return WhitelistEntry(result['id'], self._client, json=result)
def node_create(self, label, address, **kwargs): params = { "label": label, "address": address, } params.update(kwargs) result = self._client.post("{}/nodes".format(NodeBalancerConfig.api_endpoint), model=self, data=params) self.invalidate() if not 'id' in result: raise UnexpectedResponseError('Unexpected response creating node!', json=result) # this is three levels deep, so we need a special constructor n = NodeBalancerNode(self._client, result['id'], self.id, self.nodebalancer_id, result) return n
def __call__(self): """ Retrieves information about the acting user's account, such as billing information. This is intended to be called off of the :any:`LinodeClient` class, like this:: account = client.account() :returns: Returns the acting user's account information. :rtype: Account """ result = self.client.get('/account') if not 'email' in result: raise UnexpectedResponseError('Unexpected response when getting account!', json=result) return Account(self.client, result['email'], result)
def __call__(self): """ Retrieve the acting user's Profile, containing information about the current user such as their email address, username, and uid. This is intended to be called off of a :any:`LinodeClient` object, like this:: profile = client.profile() :returns: The acting user's profile. :rtype: Profile """ result = self.client.get('/profile') if not 'username' in result: raise UnexpectedResponseError('Unexpected response when getting profile!', json=result) p = Profile(self, result['username'], result) return p
def oauth_client_create(self, name, redirect_uri, **kwargs): """ Make a new OAuth Client and return it """ params = { "label": name, "redirect_uri": redirect_uri, } params.update(kwargs) result = self.client.post('/account/oauth-clients', data=params) if not 'id' in result: raise UnexpectedResponseError( 'Unexpected response when creating OAuth Client!', json=result) c = OAuthClient(self.client, result['id'], result) return c
def reset_root_password(self, root_password=None): rpass = root_password if not rpass: rpass = Instance.generate_root_password() params = { 'password': rpass, } result = self._client.post(Disk.api_endpoint, model=self, data=params) if not 'id' in result: raise UnexpectedResponseError('Unexpected response duplicating disk!', json=result) self._populate(result) if not root_password: return True, rpass return True
def volume_create(self, label, region=None, linode=None, size=20, **kwargs): """ Creates a new Block Storage Volume, either in the given Region or attached to the given Instance. :param label: The label for the new Volume. :type label: str :param region: The Region to create this Volume in. Not required if `linode` is provided. :type region: Region or str :param linode: The Instance to attach this Volume to. If not given, the new Volume will not be attached to anything. :type linode: Instance or int :param size: The size, in GB, of the new Volume. Defaults to 20. :type size: int :returns: The new Volume. :rtype: Volume """ if not (region or linode): raise ValueError('region or linode required!') params = { "label": label, "size": size, "region": region.id if issubclass(type(region), Base) else region, "linode_id": linode.id if issubclass(type(linode), Base) else linode, } params.update(kwargs) result = self.post('/volumes', data=params) if not 'id' in result: raise UnexpectedResponseError( 'Unexpected response when creating volume!', json=result) v = Volume(self, result['id'], result) return v