Skip to content

Commit 3917e29

Browse files
author
GitHub Actions
committed
Auto commit from main repo: manual-sync
1 parent 29e9307 commit 3917e29

11 files changed

Lines changed: 328 additions & 0 deletions

File tree

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import contextlib
2+
from typing import AsyncIterator
3+
from typing import Generator
4+
from typing import IO
5+
from typing import Union
6+
7+
import httpx
8+
import universalasync
9+
10+
from armis_sdk.core import response_utils
11+
from armis_sdk.core.base_entity_client import BaseEntityClient
12+
from armis_sdk.entities.collector_image import CollectorImage
13+
from armis_sdk.entities.download_progress import DownloadProgress
14+
from armis_sdk.enums.collector_image_type import CollectorImageType
15+
16+
17+
@universalasync.wrap
18+
class CollectorsClient(BaseEntityClient):
19+
# pylint: disable=line-too-long
20+
"""
21+
A client for interacting with Armis collectors.
22+
23+
The primary entity for this client is [CollectorImage][armis_sdk.entities.collector_image.CollectorImage].
24+
"""
25+
26+
async def download_image(
27+
self,
28+
destination: Union[str, IO[bytes]],
29+
image_type: CollectorImageType = CollectorImageType.OVA,
30+
) -> AsyncIterator[DownloadProgress]:
31+
"""Download a collector image to a specified destination path / file.
32+
33+
Args:
34+
destination: The file path or file-like object where the collector image will be saved.
35+
image_type: The type of collector image to download. Defaults to "OVA".
36+
37+
Returns:
38+
An (async) iterator of `DownloadProgress` object.
39+
40+
Example:
41+
```python linenums="1" hl_lines="10 15"
42+
import asyncio
43+
44+
from armis_sdk.clients.collectors_client import CollectorsClient
45+
46+
47+
async def main():
48+
collectors_client = CollectorsClient()
49+
50+
# Download to a path
51+
async for progress in armis_sdk.collectors.download_image("/tmp/collector.ova"):
52+
print(progress.percent)
53+
54+
# Download to a file
55+
with open("/tmp/collector.ova", "wb") as file:
56+
async for progress in armis_sdk.collectors.download_image(file):
57+
print(progress.percent)
58+
59+
asyncio.run(main())
60+
```
61+
Will output:
62+
```python linenums="1"
63+
1%
64+
2%
65+
3%
66+
```
67+
etc.
68+
"""
69+
collector_image = await self.get_image(image_type=image_type)
70+
async with httpx.AsyncClient() as client:
71+
async with client.stream("GET", collector_image.url) as response:
72+
response.raise_for_status()
73+
total_size = int(response.headers.get("Content-Length", "0"))
74+
with self.open_file(destination) as file:
75+
async for chunk in response.aiter_bytes():
76+
file.write(chunk)
77+
yield DownloadProgress(downloaded=file.tell(), total=total_size)
78+
79+
async def get_image(
80+
self, image_type: CollectorImageType = CollectorImageType.OVA
81+
) -> CollectorImage:
82+
"""Get collector image information including download URL and credentials.
83+
84+
Args:
85+
image_type: The type of collector image to retrieve. Defaults to "OVA".
86+
87+
Returns:
88+
A `CollectorImage` object.
89+
90+
Example:
91+
```python linenums="1" hl_lines="8"
92+
import asyncio
93+
94+
from armis_sdk.clients.collectors_client import CollectorsClient
95+
96+
97+
async def main():
98+
collectors_client = CollectorsClient()
99+
print(await collectors_client.get_image(image_type="OVA"))
100+
101+
asyncio.run(main())
102+
```
103+
Will output:
104+
```python linenums="1"
105+
CollectorImage(url="...", ...)
106+
```
107+
"""
108+
async with self._armis_client.client() as client:
109+
response = await client.get(
110+
"/v3/collectors/_image", params={"image_type": image_type.value}
111+
)
112+
data = response_utils.get_data_dict(response)
113+
return CollectorImage.model_validate(data)
114+
115+
@classmethod
116+
@contextlib.contextmanager
117+
def open_file(
118+
cls, destination: Union[str, IO[bytes]]
119+
) -> Generator[IO[bytes], None, None]:
120+
if isinstance(destination, str):
121+
with open(destination, "wb") as file:
122+
yield file
123+
else:
124+
yield destination

armis_sdk/core/armis_sdk.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from typing import Optional
22

33
from armis_sdk.clients.assets_client import AssetsClient
4+
from armis_sdk.clients.collectors_client import CollectorsClient
45
from armis_sdk.clients.data_export_client import DataExportClient
56
from armis_sdk.clients.device_custom_properties_client import (
67
DeviceCustomPropertiesClient,
@@ -19,6 +20,7 @@ class ArmisSdk: # pylint: disable=too-few-public-methods
1920
Attributes:
2021
client (ArmisClient): An instance of [ArmisClient][armis_sdk.core.armis_client.ArmisClient]
2122
assets (AssetsClient): An instance of [AssetsClient][armis_sdk.clients.assets_client.AssetsClient]
23+
collectors (CollectorsClient): An instance of [CollectorsClient][armis_sdk.clients.collectors_client.CollectorsClient]
2224
data_export (DataExportClient): An instance of [DataExportClient][armis_sdk.clients.data_export_client.DataExportClient]
2325
device_custom_properties (DeviceCustomPropertiesClient): An instance of [DeviceCustomPropertiesClient][armis_sdk.clients.device_custom_properties_client.DeviceCustomPropertiesClient]
2426
sites (SitesClient): An instance of [SitesClient][armis_sdk.clients.sites_client.SitesClient]
@@ -42,6 +44,7 @@ async def main():
4244
def __init__(self, credentials: Optional[ClientCredentials] = None):
4345
self.client: ArmisClient = ArmisClient(credentials=credentials)
4446
self.assets: AssetsClient = AssetsClient(self.client)
47+
self.collectors: CollectorsClient = CollectorsClient(self.client)
4548
self.data_export: DataExportClient = DataExportClient(self.client)
4649
self.device_custom_properties: DeviceCustomPropertiesClient = (
4750
DeviceCustomPropertiesClient(self.client)
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import datetime
2+
3+
from armis_sdk.core.base_entity import BaseEntity
4+
from armis_sdk.enums.collector_image_type import CollectorImageType
5+
from pydantic import Field
6+
7+
8+
class CollectorImage(BaseEntity):
9+
"""
10+
An entity that represents the details required to download and run a collector image.
11+
"""
12+
13+
image_type: CollectorImageType = Field(strict=False)
14+
"""The type of the image."""
15+
16+
image_password: str
17+
"""The password for the OS that is encapsulated by the image."""
18+
19+
url: str
20+
"""The temporary, presigned URL from which the OS image file can be downloaded."""
21+
22+
url_expiration_date: datetime.datetime = Field(strict=False)
23+
"""Expiration date of the URL."""
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
from armis_sdk.core.base_entity import BaseEntity
2+
3+
4+
class DownloadProgress(BaseEntity):
5+
downloaded: int
6+
"""How much bytes were already downloaded."""
7+
8+
total: int
9+
"""Total number of bytes to download."""
10+
11+
@property
12+
def percent(self) -> str:
13+
"""Percentage of progress."""
14+
return f"{self.downloaded/self.total:.4%}"
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
from enum import StrEnum
2+
3+
4+
class CollectorImageType(StrEnum):
5+
DARWIN_AMD64_BROKER = "DARWIN_AMD64_BROKER"
6+
""""""
7+
8+
DARWIN_ARM64_BROKER = "DARWIN_ARM64_BROKER"
9+
""""""
10+
11+
DEB = "DEB"
12+
""""""
13+
14+
LINUX_AMD64_BROKER = "LINUX_AMD64_BROKER"
15+
""""""
16+
17+
LINUX_ARM64_BROKER = "LINUX_ARM64_BROKER"
18+
""""""
19+
20+
OVA = "OVA"
21+
""""""
22+
23+
QCOW2 = "QCOW2"
24+
""""""
25+
26+
RPM = "RPM"
27+
""""""
28+
29+
VHD = "VHD"
30+
""""""
31+
32+
VHDX = "VHDX"
33+
""""""
34+
35+
WINDOWS_BROKER = "WINDOWS_BROKER"
36+
""""""

docs/clients/CollectorsClient.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
::: armis_sdk.clients.collectors_client.CollectorsClient

docs/entities/CollectorImage.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
::: armis_sdk.entities.collector_image.CollectorImage

docs/entities/DownloadProgress.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
::: armis_sdk.entities.download_progress.DownloadProgress

docs/enums/CollectorImageType.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
::: armis_sdk.enums.collector_image_type.CollectorImageType

mkdocs.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,24 @@ nav:
1313
- RiskFactor: entities/data_export/RiskFactor.md
1414
- Vulnerability: entities/data_export/Vulnerability.md
1515
- Boundary: entities/Boundary.md
16+
- CollectorImage: entities/CollectorImage.md
1617
- Device: entities/Device.md
1718
- DeviceCustomProperty: entities/DeviceCustomProperty.md
19+
- DownloadProgress: entities/DownloadProgress.md
1820
- NetworkInterface: entities/NetworkInterface.md
1921
- Site: entities/Site.md
2022
- Clients:
2123
- AssetsClient: clients/AssetsClient.md
24+
- CollectorsClient: clients/CollectorsClient.md
2225
- DataExportClient: clients/DataExportClient.md
2326
- DeviceCustomPropertiesClient: clients/DeviceCustomPropertiesClient.md
2427
- SitesClient: clients/SitesClient.md
2528
- Core:
2629
- ArmisClient: core/ArmisClient.md
2730
- ArmisSdk: core/ArmisSdk.md
2831
- Errors: core/errors.md
32+
- Enums:
33+
- CollectorImageType: enums/CollectorImageType.md
2934
- About Armis: about.md
3035

3136
theme:

0 commit comments

Comments
 (0)