Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions hetzner/key.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import datetime


class Key:
name: str
fingerprint: str
size: int
data: str
created_at: datetime.datetime
Comment on lines +5 to +9
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding type hints while at the same time using deprecated stuff such as class Foo(object): and super(Key, self) is somewhat inconsistent. However, since even I nowadays use type hints and more modern features in other projects, I'm wondering whether it's time to even drop support for Python 2 for this project. That however has to happen for a new major version, though.

So either we stick here to being 2.x compatible and merge this pull request or we use Python 3.x features (consistently) here but wait for the next major release.


def __init__(self, conn, fingerprint: str = None, data: dict = None):
super(Key, self).__init__()
self._conn = conn

if data is None:
self.fingerprint = fingerprint
else:
self.update_info(data)

def update_info(self, result=None):
if result is None:
result = self._conn.request("get", f"/key/{self.fingerprint}")

data = result["key"]

for key in ("name", "fingerprint", "size", "data"):
setattr(self, key, data[key])
self.created_at = datetime.datetime.strptime(data['created_at'], '%Y-%m-%dT%H:%M:%S.%fZ')

def rename(self, new_name: str):
return self._conn.request('POST', f"/key/{self.fingerprint}", {"name": new_name})

def __repr__(self):
return f"<Key {self.name}:{self.fingerprint}>"
31 changes: 31 additions & 0 deletions hetzner/robot.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

from base64 import b64encode

from hetzner.key import Key

try:
from httplib import BadStatusLine, ResponseNotReady
except ImportError:
Expand All @@ -17,6 +19,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
Expand Down Expand Up @@ -427,10 +430,38 @@ 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_):
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You've used box_id in storagebox.py, which not only is more consistent but also doesn't come with the underscore-suffix which I consider to be a workaround.

"""
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 KeysManager(object):
def __init__(self, conn):
self._conn = conn

def __iter__(self):
return iter([Key(self._conn, data=k) for k in self._conn.get('/key')])

def delete(self, fingerprint: str):
return self._conn.request('DELETE', f"/key/{fingerprint}")

def add(self, name: str, data: str):
return Key(self._conn, data=self._conn.request('POST', "/key", {"name": name, "data": data})["key"])

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)
self.keys = KeysManager(self.conn)
1 change: 1 addition & 0 deletions hetzner/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,7 @@ def update_info(self, result=None):
self.status = data['status']
self.cancelled = data['cancelled']
self.paid_until = datetime.strptime(data['paid_until'], '%Y-%m-%d')
self.linked_storagebox = data['linked_storagebox']

def observed_reboot(self, *args, **kwargs):
msg = ("Server.observed_reboot() is deprecated. Please use"
Expand Down
118 changes: 118 additions & 0 deletions hetzner/storagebox.py
Original file line number Diff line number Diff line change
@@ -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):
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):
data = f'/storagebox/{self.box_id}/subaccount/{self.username}/password'
result = self.conn.post(data, None)
return result['password']

def delete(self):
self.conn.delete('/storagebox/{0}/subaccount/{1}'.format(self.box_id, self.username))

def __repr__(self):
return "<SubAccount {0}>".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)
21 changes: 21 additions & 0 deletions hetznerctl
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,26 @@ 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 = ["{0}: {1}".format(key, val)
for key, val in info.items()]

self.putline("{0} ({1})".format(storagebox.login, ", ".join(infolist)))


def main():
subcommands = [
Config,
Expand All @@ -353,6 +373,7 @@ def main():
ReverseDNS,
Admin,
Failover,
ListStorageboxes,
]

common_parser = argparse.ArgumentParser(
Expand Down