Skip to content

Commit b28e683

Browse files
authored
Add managed certificate support (#116)
Signed-off-by: Lukas Kämmerling <[email protected]>
1 parent b76fffc commit b28e683

File tree

6 files changed

+449
-13
lines changed

6 files changed

+449
-13
lines changed

CHANGELOG.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22
History
33
=======
44

5+
v1.12.0 (2021-04-06)
6+
---------------------
7+
* Feature: Add support for managed Certificates
8+
59
v1.11.0 (2021-03-11)
610
---------------------
711
* Feature: Add support for Firewalls

hcloud/__version__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
VERSION = '1.11.0'
1+
VERSION = '1.12.0'

hcloud/certificates/client.py

Lines changed: 142 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,55 @@
11
# -*- coding: utf-8 -*-
2+
from hcloud.actions.client import BoundAction
23
from hcloud.core.client import ClientEntityBase, BoundModelBase, GetEntityByNameMixin
34

4-
from hcloud.certificates.domain import Certificate
5+
from hcloud.certificates.domain import Certificate, CreateManagedCertificateResponse, ManagedCertificateStatus, ManagedCertificateError
6+
from hcloud.core.domain import add_meta_to_result
57

68

79
class BoundCertificate(BoundModelBase):
810
model = Certificate
911

12+
def __init__(self, client, data, complete=True):
13+
status = data.get('status')
14+
if status is not None:
15+
error_data = status.get('error')
16+
error = None
17+
if error_data:
18+
error = ManagedCertificateError(code=error_data['code'], message=error_data['message'])
19+
data['status'] = ManagedCertificateStatus(
20+
issuance=status['issuance'],
21+
renewal=status['renewal'],
22+
error=error)
23+
super(BoundCertificate, self).__init__(client, data, complete)
24+
25+
def get_actions_list(self, status=None, sort=None, page=None, per_page=None):
26+
# type: (Optional[List[str]], Optional[List[str]], Optional[int], Optional[int]) -> PageResults[List[BoundAction, Meta]]
27+
"""Returns all action objects for a Certificate.
28+
29+
:param status: List[str] (optional)
30+
Response will have only actions with specified statuses. Choices: `running` `success` `error`
31+
:param sort: List[str] (optional)
32+
Specify how the results are sorted. Choices: `id` `id:asc` `id:desc` `command` `command:asc` `command:desc` `status` `status:asc` `status:desc` `progress` `progress:asc` `progress:desc` `started` `started:asc` `started:desc` `finished` `finished:asc` `finished:desc`
33+
:param page: int (optional)
34+
Specifies the page to fetch
35+
:param per_page: int (optional)
36+
Specifies how many results are returned by page
37+
:return: (List[:class:`BoundAction <hcloud.actions.client.BoundAction>`], :class:`Meta <hcloud.core.domain.Meta>`)
38+
"""
39+
return self._client.get_actions_list(self, status, sort, page, per_page)
40+
41+
def get_actions(self, status=None, sort=None):
42+
# type: (Optional[List[str]], Optional[List[str]]) -> List[BoundAction]
43+
"""Returns all action objects for a Certificate.
44+
45+
:param status: List[str] (optional)
46+
Response will have only actions with specified statuses. Choices: `running` `success` `error`
47+
:param sort: List[str] (optional)
48+
Specify how the results are sorted. Choices: `id` `id:asc` `id:desc` `command` `command:asc` `command:desc` `status` `status:asc` `status:desc` `progress` `progress:asc` `progress:desc` `started` `started:asc` `started:desc` `finished` `finished:asc` `finished:desc`
49+
:return: List[:class:`BoundAction <hcloud.actions.client.BoundAction>`]
50+
"""
51+
return self._client.get_actions(self, status, sort)
52+
1053
def update(self, name=None, labels=None):
1154
# type: (Optional[str], Optional[Dict[str, str]]) -> BoundCertificate
1255
"""Updates an certificate. You can update an certificate name and the certificate labels.
@@ -26,6 +69,13 @@ def delete(self):
2669
"""
2770
return self._client.delete(self)
2871

72+
def retry_issuance(self):
73+
# type: () -> BoundAction
74+
"""Retry a failed Certificate issuance or renewal.
75+
:return: BoundAction
76+
"""
77+
return self._client.retry_issuance(self)
78+
2979

3080
class CertificatesClient(ClientEntityBase, GetEntityByNameMixin):
3181
results_list_attribute_name = 'certificates'
@@ -102,7 +152,8 @@ def get_by_name(self, name):
102152

103153
def create(self, name, certificate, private_key, labels=None):
104154
# type: (str, str, Optional[Dict[str, str]]) -> BoundCertificate
105-
"""Creates a new Certificate with the given name, certificate and private_key.
155+
"""Creates a new Certificate with the given name, certificate and private_key. This methods allows only creating
156+
custom uploaded certificates. If you want to create a managed certificate use :func:`~hcloud.certificates.client.CertificatesClient.create_managed`
106157
107158
:param name: str
108159
:param certificate: str
@@ -116,13 +167,37 @@ def create(self, name, certificate, private_key, labels=None):
116167
data = {
117168
'name': name,
118169
'certificate': certificate,
119-
'private_key': private_key
170+
'private_key': private_key,
171+
'type': Certificate.TYPE_UPLOADED
120172
}
121173
if labels is not None:
122174
data['labels'] = labels
123175
response = self._client.request(url="/certificates", method="POST", json=data)
124176
return BoundCertificate(self, response['certificate'])
125177

178+
def create_managed(self, name, domain_names, labels=None):
179+
# type: (str, List[str], Optional[Dict[str, str]]) -> CreateManagedCertificateResponse
180+
"""Creates a new managed Certificate with the given name and domain names. This methods allows only creating
181+
managed certificates for domains that are using the Hetzner DNS service. If you want to create a custom uploaded certificate use :func:`~hcloud.certificates.client.CertificatesClient.create`
182+
183+
:param name: str
184+
:param domain_names: List[str]
185+
Domains and subdomains that should be contained in the Certificate
186+
:param labels: Dict[str, str] (optional)
187+
User-defined labels (key-value pairs)
188+
:return: :class:`BoundCertificate <hcloud.certificates.client.BoundCertificate>`
189+
"""
190+
data = {
191+
'name': name,
192+
'type': Certificate.TYPE_MANAGED,
193+
'domain_names': domain_names
194+
}
195+
if labels is not None:
196+
data['labels'] = labels
197+
response = self._client.request(url="/certificates", method="POST", json=data)
198+
return CreateManagedCertificateResponse(certificate=BoundCertificate(self, response['certificate']),
199+
action=BoundAction(self._client.actions, response['action']))
200+
126201
def update(self, certificate, name=None, labels=None):
127202
# type: (Certificate, Optional[str], Optional[Dict[str, str]]) -> BoundCertificate
128203
"""Updates a Certificate. You can update a certificate name and labels.
@@ -155,3 +230,67 @@ def delete(self, certificate):
155230
"""
156231
# Return always true, because the API does not return an action for it. When an error occurs a HcloudAPIException will be raised
157232
return True
233+
234+
def get_actions_list(
235+
self, certificate, status=None, sort=None, page=None, per_page=None
236+
):
237+
# type: (Certificate, Optional[List[str]], Optional[List[str]], Optional[int], Optional[int]) -> PageResults[List[BoundAction], Meta]
238+
"""Returns all action objects for a Certificate.
239+
240+
:param certificate: :class:`BoundCertificate <hcloud.certificates.client.BoundCertificate>` or :class:`Certificate <hcloud.certificates.domain.Certificate>`
241+
:param status: List[str] (optional)
242+
Response will have only actions with specified statuses. Choices: `running` `success` `error`
243+
:param sort: List[str] (optional)
244+
Specify how the results are sorted. Choices: `id` `id:asc` `id:desc` `command` `command:asc` `command:desc` `status` `status:asc` `status:desc` `progress` `progress:asc` `progress:desc` `started` `started:asc` `started:desc` `finished` `finished:asc` `finished:desc`
245+
:param page: int (optional)
246+
Specifies the page to fetch
247+
:param per_page: int (optional)
248+
Specifies how many results are returned by page
249+
:return: (List[:class:`BoundAction <hcloud.actions.client.BoundAction>`], :class:`Meta <hcloud.core.domain.Meta>`)
250+
"""
251+
params = {}
252+
if status is not None:
253+
params["status"] = status
254+
if sort is not None:
255+
params["sort"] = sort
256+
if page is not None:
257+
params["page"] = page
258+
if per_page is not None:
259+
params["per_page"] = per_page
260+
261+
response = self._client.request(
262+
url="/certificates/{certificate_id}/actions".format(certificate_id=certificate.id),
263+
method="GET",
264+
params=params,
265+
)
266+
actions = [
267+
BoundAction(self._client.actions, action_data)
268+
for action_data in response["actions"]
269+
]
270+
return add_meta_to_result(actions, response, "actions")
271+
272+
def get_actions(self, certificate, status=None, sort=None):
273+
# type: (Certificate, Optional[List[str]], Optional[List[str]]) -> List[BoundAction]
274+
"""Returns all action objects for a Certificate.
275+
276+
:param certificate: :class:`BoundCertificate <hcloud.certificates.client.BoundCertificate>` or :class:`Certificate <hcloud.certificates.domain.Certificate>`
277+
:param status: List[str] (optional)
278+
Response will have only actions with specified statuses. Choices: `running` `success` `error`
279+
:param sort: List[str] (optional)
280+
Specify how the results are sorted. Choices: `id` `id:asc` `id:desc` `command` `command:asc` `command:desc` `status` `status:asc` `status:desc` `progress` `progress:asc` `progress:desc` `started` `started:asc` `started:desc` `finished` `finished:asc` `finished:desc`
281+
:return: List[:class:`BoundAction <hcloud.actions.client.BoundAction>`]
282+
"""
283+
return super(CertificatesClient, self).get_actions(
284+
certificate, status=status, sort=sort
285+
)
286+
287+
def retry_issuance(self, certificate):
288+
# type: (Certificate) -> BoundAction
289+
"""Returns all action objects for a Certificate.
290+
291+
:param certificate: :class:`BoundCertificate <hcloud.certificates.client.BoundCertificate>` or :class:`Certificate <hcloud.certificates.domain.Certificate>`
292+
:return: :class:`BoundAction <hcloud.actions.client.BoundAction>`
293+
"""
294+
response = self._client.request(url="/certificates/{certificate_id}/actions/retry".format(certificate_id=certificate.id),
295+
method="POST")
296+
return BoundAction(self._client.actions, response['action'])

hcloud/certificates/domain.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ class Certificate(BaseDomain, DomainIdentityMixin):
2020
User-defined labels (key-value pairs)
2121
:param created: datetime
2222
Point in time when the certificate was created
23+
:param type: str Type of Certificate
24+
:param status: ManagedCertificateStatus Current status of a type managed Certificate, always none for type uploaded Certificates
2325
"""
2426
__slots__ = (
2527
"id",
@@ -31,7 +33,11 @@ class Certificate(BaseDomain, DomainIdentityMixin):
3133
"fingerprint",
3234
"created",
3335
"labels",
36+
"type",
37+
"status"
3438
)
39+
TYPE_UPLOADED = "uploaded"
40+
TYPE_MANAGED = "managed"
3541

3642
def __init__(
3743
self,
@@ -44,13 +50,69 @@ def __init__(
4450
fingerprint=None,
4551
created=None,
4652
labels=None,
53+
type=None,
54+
status=None,
4755
):
4856
self.id = id
4957
self.name = name
58+
self.type = type
5059
self.certificate = certificate
5160
self.domain_names = domain_names
5261
self.fingerprint = fingerprint
5362
self.not_valid_before = isoparse(not_valid_before) if not_valid_before else None
5463
self.not_valid_after = isoparse(not_valid_after) if not_valid_after else None
5564
self.created = isoparse(created) if created else None
5665
self.labels = labels
66+
self.status = status
67+
68+
69+
class ManagedCertificateStatus(BaseDomain):
70+
"""ManagedCertificateStatus Domain
71+
72+
:param issuance: str
73+
Status of the issuance process of the Certificate
74+
:param renewal: str
75+
Status of the renewal process of the Certificate
76+
:param error: ManagedCertificateError
77+
If issuance or renewal reports failure, this property contains information about what happened
78+
"""
79+
80+
def __init__(self, issuance=None, renewal=None, error=None):
81+
self.issuance = issuance
82+
self.renewal = renewal
83+
self.error = error
84+
85+
86+
class ManagedCertificateError(BaseDomain):
87+
"""ManagedCertificateError Domain
88+
89+
:param code: str
90+
Error code identifying the error
91+
:param message:
92+
Message detailing the error
93+
"""
94+
def __init__(self, code=None, message=None):
95+
self.code = code
96+
self.message = message
97+
98+
99+
class CreateManagedCertificateResponse(BaseDomain):
100+
"""Create Managed Certificate Response Domain
101+
102+
:param certificate: :class:`BoundCertificate <hcloud.certificate.client.BoundCertificate>`
103+
The created server
104+
:param action: :class:`BoundAction <hcloud.actions.client.BoundAction>`
105+
Shows the progress of the certificate creation
106+
"""
107+
__slots__ = (
108+
"certificate",
109+
"action",
110+
)
111+
112+
def __init__(
113+
self,
114+
certificate, # type: BoundCertificate
115+
action, # type: BoundAction
116+
):
117+
self.certificate = certificate
118+
self.action = action

0 commit comments

Comments
 (0)