Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ jobs:
# - name: Set up Python
# uses: actions/setup-python@v5
# with:
# python-version: 3.8
# python-version: "3.10"
# - name: Install poetry
# run: |
# pip install poetry
Expand Down
15 changes: 10 additions & 5 deletions scaleway-core/scaleway_core/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,10 +122,14 @@ def _request(
if method == "POST" or method == "PUT" or method == "PATCH":
additional_headers["Content-Type"] = "application/json; charset=utf-8"

if body is None:
body = {}
if body is None:
body = {}

raw_body = json.dumps(body) if body is not None else None
raw_body: Union[bytes, str]
if isinstance(body, bytes):
raw_body = body
else:
raw_body = json.dumps(body) if body is not None else None

request_params: List[Tuple[str, Any]] = []
for k, v in params.items():
Expand Down Expand Up @@ -155,9 +159,10 @@ def _request(
url=url,
params=request_params,
headers=headers,
body=raw_body,
body=raw_body.decode("utf-8", errors="replace")
if isinstance(raw_body, bytes)
else raw_body,
)

response = requests.request(
method=method,
url=url,
Expand Down
130 changes: 130 additions & 0 deletions scaleway/scaleway/instance/v1/custom_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
from typing import Optional, Dict

from requests import Response

from scaleway_core.bridge import Zone as ScwZone
from scaleway_core.utils import validate_path_param
from .api import InstanceV1API
from .custom_marshalling import marshal_GetServerUserDataRequest
from .custom_types import GetServerUserDataRequest, GetAllServerUserDataResponse


class InstanceUtilsV1API(InstanceV1API):
"""
This API extends InstanceV1API by adding utility methods for managing Instance resources,
such as getting and setting server user data, while inheriting all methods of InstanceV1API.
"""

def get_server_user_data(
self, server_id: str, key: str, zone: Optional[ScwZone] = None
) -> Response:
"""
GetServerUserData gets the content of a user data on a server for the given key.
:param zone: Zone to target. If none is passed will use default zone from the config.
:param server_id:
:param key:
:return: A plain text response with data user information

Usage:
::

result = api.get_server_user_data(
server_id="example",
key="example",
)
"""
param_zone = validate_path_param("zone", zone or self.client.default_zone)
param_server_id = validate_path_param("server_id", server_id)

res = self._request(
"GET",
f"/instance/v1/zones/{param_zone}/servers/{param_server_id}/user_data/{key}",
body=marshal_GetServerUserDataRequest(
GetServerUserDataRequest(
zone=zone,
server_id=server_id,
key=key,
),
self.client,
),
)
self._throw_on_error(res)
return res

def set_server_user_data(
self, server_id: str, key: str, content: bytes, zone: Optional[ScwZone] = None
) -> Response:
"""
Sets the content of a user data on a server for the given key.
:param zone: Zone to target. If none is passed, it will use the default zone from the config.
:param server_id: The ID of the server.
:param key: The user data key.
:param content: The content to set as user data in bytes.
:return: A plain text response confirming the operation.
"""
param_zone = validate_path_param("zone", zone or self.client.default_zone)
param_server_id = validate_path_param("server_id", server_id)
headers = {
"Content-Type": "text/plain",
}
res = self._request(
"PATCH",
f"/instance/v1/zones/{param_zone}/servers/{param_server_id}/user_data/{key}",
body=content,
headers=headers,
)

self._throw_on_error(res)
return res

def get_all_server_user_data(
self, server_id: str, zone: Optional[ScwZone] = None
) -> GetAllServerUserDataResponse:
param_zone = validate_path_param("zone", zone or self.client.default_zone)
param_server_id = validate_path_param("server_id", server_id)

all_user_data_res = InstanceUtilsV1API.list_server_user_data(
self, server_id=param_server_id, zone=param_zone
)

user_data: Dict[str, bytes] = {}
for key in all_user_data_res.user_data:
value = InstanceUtilsV1API.get_server_user_data(
self, server_id=param_server_id, key=key
)
print("value: ", value)
user_data[key] = value.content

res = GetAllServerUserDataResponse(user_data=user_data)

return res

def set_all_server_user_data(
self,
server_id: str,
user_data: Dict[str, bytes],
zone: Optional[ScwZone] = None,
) -> Optional[None]:
param_zone = validate_path_param("zone", zone or self.client.default_zone)
param_server_id = validate_path_param("server_id", server_id)

all_user_data_res = InstanceUtilsV1API.list_server_user_data(
self, server_id=param_server_id, zone=param_zone
)
for key in all_user_data_res.user_data:
if user_data.get(key) is not None:
continue
InstanceUtilsV1API.delete_server_user_data(
self, server_id=param_server_id, key=key
)

for key in user_data:
InstanceUtilsV1API.set_server_user_data(
self,
server_id=param_server_id,
zone=param_zone,
key=key,
content=user_data[key],
)

return None
35 changes: 35 additions & 0 deletions scaleway/scaleway/instance/v1/custom_marshalling.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from typing import Dict, Any

from scaleway.instance.v1.custom_types import (
GetServerUserDataRequest,
GetAllServerUserDataRequest,
)
from scaleway_core.profile import ProfileDefaults


def marshal_GetServerUserDataRequest(
request: GetServerUserDataRequest, defaults: ProfileDefaults
) -> Dict[str, Any]:
output: Dict[str, Any] = {}

if request.server_id is not None:
output["server_id"] = request.server_id
if request.key is not None:
output["key"] = request.key
if request.zone is not None:
output["zone"] = request.zone

return output


def marshal_ListServerUserDataRequest(
request: GetAllServerUserDataRequest, defaults: ProfileDefaults
) -> Dict[str, Any]:
output: Dict[str, Any] = {}

if request.server_id is not None:
output["server_id"] = request.server_id
if request.zone is not None:
output["zone"] = request.zone

return output
46 changes: 46 additions & 0 deletions scaleway/scaleway/instance/v1/custom_types.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
from dataclasses import dataclass
from typing import Optional, Dict

from scaleway_core.bridge import Zone as ScwZone


@dataclass
class GetServerUserDataRequest:
server_id: str

"""
Key defines the user data key to get
"""
key: str

"""
Zone of the user data to get
"""
zone: Optional[ScwZone]


@dataclass
class GetAllServerUserDataRequest:
server_id: str

"""
Zone of the user data to get
"""
zone: Optional[ScwZone]


@dataclass
class GetAllServerUserDataResponse:
user_data: Dict[str, bytes]


@dataclass
class SetAllServerUserDataRequest:
server_id: str

user_data: Dict[str, bytes]

"""
Zone of the user data to set
"""
zone: Optional[ScwZone]
63 changes: 63 additions & 0 deletions scaleway/scaleway/instance/v1/test_user_data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import sys
import unittest
import logging
from typing import Dict

from scaleway_core.client import Client
from .custom_api import InstanceUtilsV1API

logger = logging.getLogger()
logger.level = logging.DEBUG
stream_handler = logging.StreamHandler(sys.stdout)
logger.addHandler(stream_handler)


class TestServerUserData(unittest.TestCase):
def setUp(self) -> None:
self.client = Client()
self.instance_api = InstanceUtilsV1API(self.client, bypass_validation=True)
self.server = self.instance_api._create_server(
commercial_type="DEV1-S",
zone="fr-par-1",
image="ubuntu_jammy",
name="my-server-web",
volumes={},
)

@unittest.skip("API Test is not up")
def test_set_and_get_server_user_data(self) -> None:
if self.server is None or self.server.server is None:
self.fail("Server setup failed.")
key = "first key"
content = b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x10"
self.instance_api.set_server_user_data(
server_id=self.server.server.id, key=key, content=content
)
user_data = self.instance_api.get_server_user_data(
server_id=self.server.server.id, key=key
)
self.assertIsNotNone(user_data)

@unittest.skip("API Test is not up")
def test_set_and_get_all_user_data(self) -> None:
if self.server is None or self.server.server is None:
self.fail("Server setup failed.")
key = "first key"
content = b"content first key"
key_bis = "second key"
content_bis = b"test content"
another_key = "third key"
another_content = b"another content to test"

user_data: Dict[str, bytes] = {
key_bis: content_bis,
another_key: another_content,
key: content,
}
self.instance_api.set_all_server_user_data(
server_id=self.server.server.id, user_data=user_data
)
response = self.instance_api.get_all_server_user_data(
server_id=self.server.server.id
)
self.assertIsNotNone(response)
Loading