From c03078428067e150dcdb9d15414209134c2cce76 Mon Sep 17 00:00:00 2001 From: pajowu Date: Tue, 25 Aug 2020 21:38:53 +0200 Subject: [PATCH 1/3] Add support for StorageBoxes --- hetzner/robot.py | 15 ++++++ hetzner/storagebox.py | 118 ++++++++++++++++++++++++++++++++++++++++++ hetznerctl | 22 ++++++++ 3 files changed, 155 insertions(+) create mode 100644 hetzner/storagebox.py diff --git a/hetzner/robot.py b/hetzner/robot.py index 47ec8f0..de25b07 100644 --- a/hetzner/robot.py +++ b/hetzner/robot.py @@ -17,6 +17,7 @@ from hetzner import WebRobotError, RobotError from hetzner.server import Server +from hetzner.storagebox import StorageBox from hetzner.rdns import ReverseDNSManager from hetzner.failover import FailoverManager from hetzner.util.http import ValidatedHTTPSConnection @@ -427,10 +428,24 @@ def get(self, ip): def __iter__(self): return iter([Server(self.conn, s) for s in self.conn.get('/server')]) +class StorageBoxManager(object): + def __init__(self, conn): + self.conn = conn + + def get(self, id_): + """ + Get storage boxes by providing its main id + """ + return StorageBox(self.conn, self.conn.get('/storagebox/{0}'.format(id_))) + + def __iter__(self): + return iter([StorageBox(self.conn, s) for s in self.conn.get('/storagebox')]) + class Robot(object): def __init__(self, user, passwd): self.conn = RobotConnection(user, passwd) self.servers = ServerManager(self.conn) + self.storageboxes = StorageBoxManager(self.conn) self.rdns = ReverseDNSManager(self.conn) self.failover = FailoverManager(self.conn, self.servers) diff --git a/hetzner/storagebox.py b/hetzner/storagebox.py new file mode 100644 index 0000000..b772276 --- /dev/null +++ b/hetzner/storagebox.py @@ -0,0 +1,118 @@ +import logging + +from datetime import datetime + +__all__ = ['StorageBox', 'SubAccount', 'SubAccountManager'] + + +class SubAccount(object): + def __init__(self, conn, box_id_, result): + self.conn = conn + self.box_id_ = box_id_ + self.update_info(result) + + def update_info(self, result): + """ + Update the information of the subaccount. + """ + data = result['subaccount'] + + self.username = data['username'] + self.accountid = data['accountid'] + self.server = data['server'] + self.homedirectory = data['homedirectory'] + self.samba = data['samba'] + self.ssh = data['ssh'] + self.external_reachability = data['external_reachability'] + self.webdav = data['webdav'] + self.readonly = data['readonly'] + self.createtime = datetime.strptime(data['createtime'], '%Y-%m-%d %H:%M:%S') + self.comment = data['comment'] + + def update(self, homedirectory, samba, ssh, external_reachability, webdav, readonly, comment): + result = self.conn.put('/storagebox/{0}/subaccount/{1}'.format(self.box_id_, self.username), + {'homedirectory': homedirectory, + 'samba': samba, + 'ssh': ssh, + 'external_reachability': external_reachability, + 'webdav': webdav, + 'readonly': readonly, + 'comment': comment}) + + return result + + def reset_password(self): + result = self.conn.post('/storagebox/{0}/subaccount/{1}/password'.format(self.box_id_, self.username), None) + return result['password'] + + def delete(self): + self.conn.delete('/storagebox/{0}/subaccount/{1}'.format(self.box_id_, self.username)) + + def __repr__(self): + return "".format(self.username) + + +class SubAccountManager(object): + def __init__(self, conn, box_id_): + self.conn = conn + self.box_id_ = box_id_ + + def create(self, homedirectory, samba, ssh, external_reachability, webdav, readonly, comment): + result = self.conn.post('/storagebox/{0}/subaccount'.format(self.box_id_), + {'homedirectory': homedirectory, + 'samba': samba, + 'ssh': ssh, + 'external_reachability': external_reachability, + 'webdav': webdav, + 'readonly': readonly, + 'comment': comment}) + + return result + + def delete(self, username): + self.conn.delete('/storagebox/{0}/subaccount/{1}'.format(self.box_id_, username)) + + def __iter__(self): + return iter([SubAccount(self.conn, self.box_id_, s) for s in self.conn.get('/storagebox/{0}/subaccount'.format(self.box_id_))]) + + +class StorageBox(object): + def __init__(self, conn, result): + self.conn = conn + self.update_info(result) + self.subaccounts = SubAccountManager(self.conn, self.id_) + self.logger = logging.getLogger("StorageBox #{0}".format(self.id_)) + + def update_info(self, result=None): + """ + Updates the information of the current SubAccount instance either by + sending a new GET request or by parsing the response given by result. + """ + if result is None: + result = self.conn.get('/storagebox/{0}'.format(self.id_)) + data = result['storagebox'] + + self.id_ = data['id'] + self.login = data['login'] + self.name = data['name'] + self.product = data['product'] + self.cancelled = data['cancelled'] + self.locked = data['locked'] + self.location = data['location'] + self.linked_server = data['linked_server'] + self.paid_until = datetime.strptime(data['paid_until'], '%Y-%m-%d') + if 'disk_quota' in data: + self.disk_quota = data['disk_quota'] + self.disk_usage = data['disk_usage'] + self.disk_usage_data = data['disk_usage_data'] + self.disk_usage_snapshots = data['disk_usage_snapshots'] + self.webdav = data['webdav'] + self.samba = data['samba'] + self.ssh = data['ssh'] + self.external_reachability = data['external_reachability'] + self.zfs = data['zfs'] + self.server = data['server'] + self.host_system = data['host_system'] + + def __repr__(self): + return "<{0} (#{1} {2})>".format(self.login, self.id_, self.product) diff --git a/hetznerctl b/hetznerctl index 95408a4..a6e0262 100755 --- a/hetznerctl +++ b/hetznerctl @@ -339,6 +339,27 @@ class Config(SubCommand): self.config.write(fp) +class ListStorageboxes(SubCommand): + command = 'list-storageboxes' + description = "List all storageboxes" + + def execute(self, robot, parser, args): + for storagebox in robot.storageboxes: + info = { + 'login': storagebox.login, + 'product': storagebox.product + } + + if storagebox.name != "": + info['name'] = storagebox.name + + infolist = [u"{0}: {1}".format(key, val) + for key, val in info.items()] + + self.putline(u"{0} ({1})".format(storagebox.login, u", ".join(infolist))) + + + def main(): subcommands = [ Config, @@ -350,6 +371,7 @@ def main(): ReverseDNS, Admin, Failover, + ListStorageboxes ] common_parser = argparse.ArgumentParser( From cd3e4d7792dd06d44eb540bbc8109f957396bd4d Mon Sep 17 00:00:00 2001 From: pajowu Date: Sun, 4 Oct 2020 22:29:24 +0200 Subject: [PATCH 2/3] Apply suggestions from code review (Part 1) Co-authored-by: aszlig --- hetznerctl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/hetznerctl b/hetznerctl index a6e0262..52f4a49 100755 --- a/hetznerctl +++ b/hetznerctl @@ -359,7 +359,6 @@ class ListStorageboxes(SubCommand): self.putline(u"{0} ({1})".format(storagebox.login, u", ".join(infolist))) - def main(): subcommands = [ Config, @@ -371,7 +370,7 @@ def main(): ReverseDNS, Admin, Failover, - ListStorageboxes + ListStorageboxes, ] common_parser = argparse.ArgumentParser( From a206ff7078244509f4697b80aa6a823ea5c0ad17 Mon Sep 17 00:00:00 2001 From: pajowu Date: Sun, 4 Oct 2020 22:29:34 +0200 Subject: [PATCH 3/3] Apply suggestions from code review (Part 2) --- hetzner/storagebox.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/hetzner/storagebox.py b/hetzner/storagebox.py index b772276..4bb157a 100644 --- a/hetzner/storagebox.py +++ b/hetzner/storagebox.py @@ -30,19 +30,19 @@ def update_info(self, result): self.comment = data['comment'] def update(self, homedirectory, samba, ssh, external_reachability, webdav, readonly, comment): - result = self.conn.put('/storagebox/{0}/subaccount/{1}'.format(self.box_id_, self.username), - {'homedirectory': homedirectory, - 'samba': samba, - 'ssh': ssh, - 'external_reachability': external_reachability, - 'webdav': webdav, - 'readonly': readonly, - 'comment': comment}) - - return result + path = f'/storagebox/{self.box_id_}/subaccount/{self.username}' + data = {'homedirectory': homedirectory, + 'samba': samba, + 'ssh': ssh, + 'external_reachability': external_reachability, + 'webdav': webdav, + 'readonly': readonly, + 'comment': comment} + return self.conn.put(path, data) def reset_password(self): - result = self.conn.post('/storagebox/{0}/subaccount/{1}/password'.format(self.box_id_, self.username), None) + data = f'/storagebox/{self.box_id_}/subaccount/{self.username}/password' + result = self.conn.post(data, None) return result['password'] def delete(self):