Skip to content

Commit 29fe847

Browse files
committed
ACLP Logs - Extend destination API with new updates
1 parent bf57354 commit 29fe847

6 files changed

Lines changed: 545 additions & 67 deletions

File tree

linode_api4/groups/monitor.py

Lines changed: 46 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@
1818
MonitorService,
1919
MonitorServiceToken,
2020
)
21+
from linode_api4.objects.monitor import (
22+
AkamaiObjectStorageLogsDestinationDetails,
23+
CustomHTTPSLogsDestinationDetails,
24+
)
2125

2226
__all__ = [
2327
"MonitorGroup",
@@ -367,44 +371,60 @@ def destination_create(
367371
self,
368372
label: str,
369373
type: Union[LogsDestinationType, str],
370-
access_key_id: str,
371-
access_key_secret: str,
372-
bucket_name: str,
373-
host: str,
374-
path: Optional[str] = None,
374+
details: Union[
375+
AkamaiObjectStorageLogsDestinationDetails,
376+
CustomHTTPSLogsDestinationDetails,
377+
],
375378
) -> LogsDestination:
376379
"""
377-
Creates a new :any:`LogsDestination` for logs on this account with
378-
the given label, type, and object storage details. For example::
380+
Creates a new :any:`LogsDestination` for logs on this account.
381+
382+
For an ``akamai_object_storage`` destination::
379383
380384
client = LinodeClient(TOKEN)
381385
382386
new_destination = client.monitor.destination_create(
383387
label="OBJ_logs_destination",
384388
type="akamai_object_storage",
385-
access_key_id="1ABCD23EFG4HIJKLMNO5",
386-
access_key_secret="1aB2CD3e4fgHi5JK6lmnop7qR8STU9VxYzabcdefHh",
387-
bucket_name="primary-bucket",
388-
host="primary-bucket-1.us-east-12.linodeobjects.com",
389-
path="audit-logs"
390-
)
389+
details=AkamaiObjectStorageLogsDestinationDetails(
390+
access_key_id="1ABCD23EFG4HIJKLMNO5",
391+
access_key_secret="1aB2CD3e4fgHi5JK6lmnop7qR8STU9VxYzabcdefHh",
392+
bucket_name="primary-bucket",
393+
host="primary-bucket-1.us-east-12.linodeobjects.com",
394+
path="audit-logs",
395+
)
396+
)
397+
398+
For a ``custom_https`` destination::
399+
400+
new_destination = client.monitor.destination_create(
401+
label="custom_logs_destination",
402+
type="custom_https",
403+
details=CustomHTTPSLogsDestinationDetails(
404+
endpoint_url="https://my-site.com/log-storage/basicAuth",
405+
authentication=DestinationAuthentication(
406+
type="basic",
407+
details=BasicAuthenticationDetails(
408+
basic_authentication_user="user",
409+
basic_authentication_password="pass",
410+
),
411+
),
412+
data_compression="gzip",
413+
content_type="application/json",
414+
)
415+
)
391416
392417
API Documentation: https://techdocs.akamai.com/linode-api/reference/post-destination
393418
394-
:param label: The name for this logs destination
419+
:param label: The name for this logs destination.
395420
:type label: str
396-
:param type: The type of destination for logs data sync. Currently, only ``akamai_object_storage`` is supported for use.
421+
:param type: The type of destination ``akamai_object_storage`` or ``custom_https``.
397422
:type type: str or LogsDestinationType
398-
:param access_key_id: The unique identifier assigned to the Object Storage key required for authentication to the bucket.
399-
:type access_key_id: str
400-
:param access_key_secret: The Object Storage key's secret key.
401-
:type access_key_secret: str
402-
:param bucket_name: The name of the Object Storage bucket
403-
:type bucket_name: str
404-
:param host: The hostname where the Object Storage bucket can be accessed
405-
:type host: str
406-
:param path: (Optional) Custom path for log storage in your Object Storage bucket.
407-
:type path: Optional[str]
423+
:param details: A typed details object matching the destination type.
424+
Use :class:`AkamaiObjectStorageLogsDestinationDetails` for
425+
``akamai_object_storage`` or :class:`CustomHTTPSLogsDestinationDetails`
426+
for ``custom_https``.
427+
:type details: AkamaiObjectStorageLogsDestinationDetails or CustomHTTPSLogsDestinationDetails
408428
409429
:returns: The newly created logs destination.
410430
:rtype: LogsDestination
@@ -413,17 +433,9 @@ def destination_create(
413433
params = {
414434
"label": label,
415435
"type": type,
416-
"details": {
417-
"access_key_id": access_key_id,
418-
"access_key_secret": access_key_secret,
419-
"bucket_name": bucket_name,
420-
"host": host,
421-
},
436+
"details": details.dict,
422437
}
423438

424-
if path is not None:
425-
params["details"]["path"] = path
426-
427439
result = self.client.post("/monitor/streams/destinations", data=params)
428440

429441
if "id" not in result:

linode_api4/objects/monitor.py

Lines changed: 152 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from dataclasses import dataclass, field
2-
from typing import List, Optional, Union
2+
from typing import Any, Dict, List, Optional, Union
33

44
from linode_api4.objects import DerivedBase
55
from linode_api4.objects.base import Base, Property
@@ -20,8 +20,10 @@
2020
"MonitorServiceToken",
2121
"RuleCriteria",
2222
"TriggerConditions",
23+
"AkamaiObjectStorageLogsDestinationDetails",
24+
"CustomHTTPSLogsDestinationDetails",
25+
"LogsDestinationDetailsBase",
2326
"LogsDestination",
24-
"LogsDestinationDetails",
2527
"LogsDestinationHistory",
2628
"LogsDestinationStatus",
2729
"LogsDestinationType",
@@ -143,10 +145,26 @@ class AlertStatus(StrEnum):
143145

144146
class LogsDestinationType(StrEnum):
145147
"""
146-
The type of destination for logs data sync. Currently, only ``akamai_object_storage`` is supported.
148+
The type of destination for logs data sync.
147149
"""
148150

149151
akamai_object_storage = "akamai_object_storage"
152+
custom_https = "custom_https"
153+
154+
155+
class AuthenticationType(StrEnum):
156+
none = "none"
157+
basic = "basic"
158+
159+
160+
class DataCompressionType(StrEnum):
161+
gzip = "gzip"
162+
none = "none"
163+
164+
165+
class ContentType(StrEnum):
166+
json = "application/json"
167+
json_utf8 = "application/json; charset=utf-8"
150168

151169

152170
class LogsDestinationStatus(StrEnum):
@@ -541,9 +559,97 @@ class AlertChannel(Base):
541559

542560

543561
@dataclass
544-
class LogsDestinationDetails(JSONObject):
562+
class BasicAuthenticationDetails(JSONObject):
563+
"""
564+
Includes additional parameters necessary to define basic authentication.
565+
"""
566+
567+
basic_authentication_user: Optional[str] = None
568+
basic_authentication_password: Optional[str] = None
569+
570+
571+
@dataclass
572+
class DestinationAuthentication(JSONObject):
573+
"""
574+
Authentication details required to access the endpoint_url.
575+
"""
576+
577+
type: Optional[AuthenticationType] = None
578+
details: Optional[BasicAuthenticationDetails] = None
579+
580+
581+
@dataclass
582+
class CustomHeader(JSONObject):
583+
"""
584+
Pairs of parameters used to optionally include custom headers in the request.
585+
"""
586+
587+
name: str = ""
588+
value: str = ""
589+
590+
591+
@dataclass
592+
class ClientCertificateDetails(JSONObject):
593+
"""
594+
Contains TLS client certificate information to additionally secure the connection.
595+
"""
596+
597+
client_ca_certificate: Optional[str] = None
598+
client_certificate: Optional[str] = None
599+
client_private_key: Optional[str] = None
600+
tls_hostname: Optional[str] = None
601+
602+
603+
@dataclass
604+
class LogsDestinationDetailsBase(JSONObject):
605+
"""
606+
Base class for Logs Destination details.
607+
Use the factory method to instantiate the correct subclass based on destination type.
608+
"""
609+
610+
@classmethod
611+
def load_by_type(
612+
cls, dest_type: str, json_dict: dict
613+
) -> Optional["LogsDestinationDetailsBase"]:
614+
"""
615+
Factory method that instantiates the correct details subclass
616+
based on the destination type string.
617+
618+
:param dest_type: The destination type (e.g. "akamai_object_storage", "custom_https").
619+
:param json_dict: The raw JSON dict for the details block.
620+
:returns: A populated subclass instance, or None if json_dict is empty/None.
621+
"""
622+
if not json_dict:
623+
return None
624+
625+
if dest_type == LogsDestinationType.akamai_object_storage:
626+
return AkamaiObjectStorageLogsDestinationDetails.from_json(
627+
json_dict
628+
)
629+
elif dest_type == LogsDestinationType.custom_https:
630+
return CustomHTTPSLogsDestinationDetails.from_json(json_dict)
631+
632+
return None
633+
634+
635+
@dataclass
636+
class CustomHTTPSLogsDestinationDetails(LogsDestinationDetailsBase):
545637
"""
546-
Represents the details block for LogsDestination.
638+
Represents the details block for custom_https LogsDestination type.
639+
"""
640+
641+
endpoint_url: str = ""
642+
authentication: Optional[DestinationAuthentication] = None
643+
data_compression: Optional[DataCompressionType] = None
644+
content_type: Optional[ContentType] = None
645+
custom_headers: Optional[List[CustomHeader]] = None
646+
client_certificate_details: Optional[ClientCertificateDetails] = None
647+
648+
649+
@dataclass
650+
class AkamaiObjectStorageLogsDestinationDetails(LogsDestinationDetailsBase):
651+
"""
652+
Represents the details block for Akamai Object Storage LogsDestination type.
547653
Fields:
548654
- access_key_id: str - The unique identifier assigned to the Object Storage key required for authentication to the bucket.
549655
- bucket_name: str - The name of the Object Storage bucket.
@@ -568,7 +674,7 @@ class LogsDestinationHistory(Base):
568674
properties = {
569675
"created": Property(is_datetime=True),
570676
"created_by": Property(),
571-
"details": Property(json_object=LogsDestinationDetails),
677+
"details": Property(),
572678
"id": Property(identifier=True),
573679
"label": Property(),
574680
"status": Property(),
@@ -578,6 +684,17 @@ class LogsDestinationHistory(Base):
578684
"version": Property(),
579685
}
580686

687+
def _populate(self, json):
688+
super()._populate(json)
689+
690+
if json and "details" in json and "type" in json:
691+
self._set(
692+
"details",
693+
LogsDestinationDetailsBase.load_by_type(
694+
json["type"], json["details"]
695+
),
696+
)
697+
581698

582699
class LogsDestination(Base):
583700
"""
@@ -591,7 +708,7 @@ class LogsDestination(Base):
591708
properties = {
592709
"created": Property(is_datetime=True),
593710
"created_by": Property(),
594-
"details": Property(mutable=True, json_object=LogsDestinationDetails),
711+
"details": Property(mutable=True),
595712
"id": Property(identifier=True),
596713
"label": Property(mutable=True),
597714
"status": Property(),
@@ -601,6 +718,17 @@ class LogsDestination(Base):
601718
"version": Property(),
602719
}
603720

721+
def _populate(self, json):
722+
super()._populate(json)
723+
724+
if json and "details" in json and "type" in json:
725+
self._set(
726+
"details",
727+
LogsDestinationDetailsBase.load_by_type(
728+
json["type"], json["details"]
729+
),
730+
)
731+
604732
@property
605733
def history(self):
606734
"""
@@ -636,7 +764,23 @@ class LogsStreamDestination(JSONObject):
636764
id: int = 0
637765
label: str = ""
638766
type: Optional[LogsDestinationType] = None
639-
details: Optional[LogsDestinationDetails] = None
767+
details: Optional[LogsDestinationDetailsBase] = None
768+
769+
@classmethod
770+
def from_json(
771+
cls, json: Dict[str, Any]
772+
) -> Optional["LogsStreamDestination"]:
773+
if json is None:
774+
return None
775+
776+
obj = super().from_json(json)
777+
778+
if obj and json.get("type"):
779+
obj.details = LogsDestinationDetailsBase.load_by_type(
780+
json["type"], json.get("details")
781+
)
782+
783+
return obj
640784

641785

642786
class LogsStreamHistory(Base):
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
{
2+
"id": 2,
3+
"label": "my-custom-https-stream",
4+
"type": "audit_logs",
5+
"status": "active",
6+
"destinations": [
7+
{
8+
"id": 2,
9+
"label": "my-custom-https-destination",
10+
"type": "custom_https",
11+
"details": {
12+
"endpoint_url": "https://my-site.com/log-storage/basicAuth",
13+
"authentication": {
14+
"type": "basic",
15+
"details": {
16+
"basic_authentication_user": "John_Q",
17+
"basic_authentication_password": "p@$$w0Rd"
18+
}
19+
},
20+
"data_compression": "gzip",
21+
"content_type": "application/json",
22+
"custom_headers": [
23+
{
24+
"name": "Cache-Control",
25+
"value": "max-age=0"
26+
}
27+
],
28+
"client_certificate_details": {
29+
"client_ca_certificate": "-----BEGIN CERTIFICATE-----\nMIIBIjANBgkq...\n-----END CERTIFICATE-----",
30+
"client_certificate": "-----BEGIN CERTIFICATE-----\nMIIBIjANBgkq...\n-----END CERTIFICATE-----",
31+
"client_private_key": "-----BEGIN PRIVATE KEY-----\nMIIBIjANBgkq...\n-----END PRIVATE KEY-----",
32+
"tls_hostname": "my-site.com"
33+
}
34+
}
35+
}
36+
],
37+
"created": "2024-08-01T12:00:00",
38+
"updated": "2024-08-01T12:00:00",
39+
"created_by": "tester",
40+
"updated_by": "tester",
41+
"version": 1
42+
}
43+

0 commit comments

Comments
 (0)