jumpstarter-kubernetes 0.6.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of jumpstarter-kubernetes might be problematic. Click here for more details.
- jumpstarter_kubernetes/__init__.py +39 -0
- jumpstarter_kubernetes/clients.py +138 -0
- jumpstarter_kubernetes/clients_test.py +58 -0
- jumpstarter_kubernetes/exporters.py +146 -0
- jumpstarter_kubernetes/exporters_test.py +77 -0
- jumpstarter_kubernetes/install.py +65 -0
- jumpstarter_kubernetes/json.py +14 -0
- jumpstarter_kubernetes/leases.py +103 -0
- jumpstarter_kubernetes/list.py +15 -0
- jumpstarter_kubernetes/py.typed +0 -0
- jumpstarter_kubernetes/serialize.py +14 -0
- jumpstarter_kubernetes/test_leases.py +117 -0
- jumpstarter_kubernetes/util/__init__.py +3 -0
- jumpstarter_kubernetes/util/async_custom_object_api.py +43 -0
- jumpstarter_kubernetes-0.6.0.dist-info/METADATA +15 -0
- jumpstarter_kubernetes-0.6.0.dist-info/RECORD +17 -0
- jumpstarter_kubernetes-0.6.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from .clients import ClientsV1Alpha1Api, V1Alpha1Client, V1Alpha1ClientList, V1Alpha1ClientStatus
|
|
2
|
+
from .exporters import (
|
|
3
|
+
ExportersV1Alpha1Api,
|
|
4
|
+
V1Alpha1Exporter,
|
|
5
|
+
V1Alpha1ExporterDevice,
|
|
6
|
+
V1Alpha1ExporterList,
|
|
7
|
+
V1Alpha1ExporterStatus,
|
|
8
|
+
)
|
|
9
|
+
from .install import helm_installed, install_helm_chart
|
|
10
|
+
from .leases import (
|
|
11
|
+
LeasesV1Alpha1Api,
|
|
12
|
+
V1Alpha1Lease,
|
|
13
|
+
V1Alpha1LeaseList,
|
|
14
|
+
V1Alpha1LeaseSelector,
|
|
15
|
+
V1Alpha1LeaseSpec,
|
|
16
|
+
V1Alpha1LeaseStatus,
|
|
17
|
+
)
|
|
18
|
+
from .list import V1Alpha1List
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"ClientsV1Alpha1Api",
|
|
22
|
+
"V1Alpha1Client",
|
|
23
|
+
"V1Alpha1ClientList",
|
|
24
|
+
"V1Alpha1ClientStatus",
|
|
25
|
+
"ExportersV1Alpha1Api",
|
|
26
|
+
"V1Alpha1Exporter",
|
|
27
|
+
"V1Alpha1ExporterList",
|
|
28
|
+
"V1Alpha1ExporterStatus",
|
|
29
|
+
"V1Alpha1ExporterDevice",
|
|
30
|
+
"LeasesV1Alpha1Api",
|
|
31
|
+
"V1Alpha1Lease",
|
|
32
|
+
"V1Alpha1LeaseStatus",
|
|
33
|
+
"V1Alpha1LeaseList",
|
|
34
|
+
"V1Alpha1LeaseSelector",
|
|
35
|
+
"V1Alpha1LeaseSpec",
|
|
36
|
+
"V1Alpha1List",
|
|
37
|
+
"helm_installed",
|
|
38
|
+
"install_helm_chart",
|
|
39
|
+
]
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import base64
|
|
3
|
+
import logging
|
|
4
|
+
from typing import Literal, Optional
|
|
5
|
+
|
|
6
|
+
from kubernetes_asyncio.client.models import V1ObjectMeta, V1ObjectReference
|
|
7
|
+
from pydantic import Field
|
|
8
|
+
|
|
9
|
+
from .json import JsonBaseModel
|
|
10
|
+
from .list import V1Alpha1List
|
|
11
|
+
from .serialize import SerializeV1ObjectMeta, SerializeV1ObjectReference
|
|
12
|
+
from .util import AbstractAsyncCustomObjectApi
|
|
13
|
+
from jumpstarter.config.client import ClientConfigV1Alpha1, ClientConfigV1Alpha1Drivers
|
|
14
|
+
from jumpstarter.config.common import ObjectMeta
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
CREATE_CLIENT_DELAY = 1
|
|
19
|
+
CREATE_CLIENT_COUNT = 10
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class V1Alpha1ClientStatus(JsonBaseModel):
|
|
23
|
+
credential: Optional[SerializeV1ObjectReference] = None
|
|
24
|
+
endpoint: str
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class V1Alpha1Client(JsonBaseModel):
|
|
28
|
+
api_version: Literal["jumpstarter.dev/v1alpha1"] = Field(alias="apiVersion", default="jumpstarter.dev/v1alpha1")
|
|
29
|
+
kind: Literal["Client"] = Field(default="Client")
|
|
30
|
+
metadata: SerializeV1ObjectMeta
|
|
31
|
+
status: Optional[V1Alpha1ClientStatus]
|
|
32
|
+
|
|
33
|
+
@staticmethod
|
|
34
|
+
def from_dict(dict: dict):
|
|
35
|
+
return V1Alpha1Client(
|
|
36
|
+
api_version=dict["apiVersion"],
|
|
37
|
+
kind=dict["kind"],
|
|
38
|
+
metadata=V1ObjectMeta(
|
|
39
|
+
creation_timestamp=dict["metadata"]["creationTimestamp"],
|
|
40
|
+
generation=dict["metadata"]["generation"],
|
|
41
|
+
name=dict["metadata"]["name"],
|
|
42
|
+
namespace=dict["metadata"]["namespace"],
|
|
43
|
+
resource_version=dict["metadata"]["resourceVersion"],
|
|
44
|
+
uid=dict["metadata"]["uid"],
|
|
45
|
+
),
|
|
46
|
+
status=V1Alpha1ClientStatus(
|
|
47
|
+
credential=V1ObjectReference(name=dict["status"]["credential"]["name"])
|
|
48
|
+
if "credential" in dict["status"]
|
|
49
|
+
else None,
|
|
50
|
+
endpoint=dict["status"].get("endpoint", ""),
|
|
51
|
+
)
|
|
52
|
+
if "status" in dict
|
|
53
|
+
else None,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class V1Alpha1ClientList(V1Alpha1List[V1Alpha1Client]):
|
|
58
|
+
kind: Literal["ClientList"] = Field(default="ClientList")
|
|
59
|
+
|
|
60
|
+
@staticmethod
|
|
61
|
+
def from_dict(dict: dict):
|
|
62
|
+
return V1Alpha1ClientList(items=[V1Alpha1Client.from_dict(c) for c in dict.get("items", [])])
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class ClientsV1Alpha1Api(AbstractAsyncCustomObjectApi):
|
|
66
|
+
"""Interact with the clients custom resource API"""
|
|
67
|
+
|
|
68
|
+
async def create_client(
|
|
69
|
+
self, name: str, labels: dict[str, str] | None = None, oidc_username: str | None = None
|
|
70
|
+
) -> V1Alpha1Client:
|
|
71
|
+
"""Create a client object in the cluster async"""
|
|
72
|
+
# Create the namespaced client object
|
|
73
|
+
await self.api.create_namespaced_custom_object(
|
|
74
|
+
namespace=self.namespace,
|
|
75
|
+
group="jumpstarter.dev",
|
|
76
|
+
plural="clients",
|
|
77
|
+
version="v1alpha1",
|
|
78
|
+
body={
|
|
79
|
+
"apiVersion": "jumpstarter.dev/v1alpha1",
|
|
80
|
+
"kind": "Client",
|
|
81
|
+
"metadata": {"name": name} | {"labels": labels} if labels is not None else {},
|
|
82
|
+
"spec": {"username": oidc_username} if oidc_username is not None else {},
|
|
83
|
+
},
|
|
84
|
+
)
|
|
85
|
+
# Wait for the credentials to become available
|
|
86
|
+
# NOTE: Watch is not working here with the Python kubernetes library
|
|
87
|
+
count = 0
|
|
88
|
+
updated_client = {}
|
|
89
|
+
# Retry for a maximum of 10s
|
|
90
|
+
while count < CREATE_CLIENT_COUNT:
|
|
91
|
+
# Try to get the updated client resource
|
|
92
|
+
updated_client = await self.api.get_namespaced_custom_object(
|
|
93
|
+
namespace=self.namespace, group="jumpstarter.dev", plural="clients", version="v1alpha1", name=name
|
|
94
|
+
)
|
|
95
|
+
# check if the client status is updated with the credentials
|
|
96
|
+
if "status" in updated_client:
|
|
97
|
+
if "credential" in updated_client["status"]:
|
|
98
|
+
return V1Alpha1Client.from_dict(updated_client)
|
|
99
|
+
count += 1
|
|
100
|
+
await asyncio.sleep(CREATE_CLIENT_DELAY)
|
|
101
|
+
raise Exception("Timeout waiting for client credentials")
|
|
102
|
+
|
|
103
|
+
async def list_clients(self) -> V1Alpha1List[V1Alpha1Client]:
|
|
104
|
+
"""List the client objects in the cluster async"""
|
|
105
|
+
res = await self.api.list_namespaced_custom_object(
|
|
106
|
+
namespace=self.namespace, group="jumpstarter.dev", plural="clients", version="v1alpha1"
|
|
107
|
+
)
|
|
108
|
+
return V1Alpha1ClientList.from_dict(res)
|
|
109
|
+
|
|
110
|
+
async def get_client(self, name: str) -> V1Alpha1Client:
|
|
111
|
+
"""Get a single client object from the cluster async"""
|
|
112
|
+
result = await self.api.get_namespaced_custom_object(
|
|
113
|
+
namespace=self.namespace, group="jumpstarter.dev", plural="clients", version="v1alpha1", name=name
|
|
114
|
+
)
|
|
115
|
+
return V1Alpha1Client.from_dict(result)
|
|
116
|
+
|
|
117
|
+
async def get_client_config(self, name: str, allow: list[str], unsafe=False) -> ClientConfigV1Alpha1:
|
|
118
|
+
"""Get a client config for a specified client name"""
|
|
119
|
+
client = await self.get_client(name)
|
|
120
|
+
secret = await self.core_api.read_namespaced_secret(client.status.credential.name, self.namespace)
|
|
121
|
+
endpoint = client.status.endpoint
|
|
122
|
+
token = base64.b64decode(secret.data["token"]).decode("utf8")
|
|
123
|
+
return ClientConfigV1Alpha1(
|
|
124
|
+
alias=name,
|
|
125
|
+
metadata=ObjectMeta(
|
|
126
|
+
namespace=client.metadata.namespace,
|
|
127
|
+
name=client.metadata.name,
|
|
128
|
+
),
|
|
129
|
+
endpoint=endpoint,
|
|
130
|
+
token=token,
|
|
131
|
+
drivers=ClientConfigV1Alpha1Drivers(allow=allow, unsafe=unsafe),
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
async def delete_client(self, name: str):
|
|
135
|
+
"""Delete a client object"""
|
|
136
|
+
await self.api.delete_namespaced_custom_object(
|
|
137
|
+
namespace=self.namespace, group="jumpstarter.dev", plural="clients", version="v1alpha1", name=name
|
|
138
|
+
)
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from kubernetes_asyncio.client.models import V1ObjectMeta
|
|
2
|
+
|
|
3
|
+
from jumpstarter_kubernetes import V1Alpha1Client, V1Alpha1ClientStatus
|
|
4
|
+
|
|
5
|
+
TEST_CLIENT = V1Alpha1Client(
|
|
6
|
+
api_version="jumpstarter.dev/v1alpha1",
|
|
7
|
+
kind="Client",
|
|
8
|
+
metadata=V1ObjectMeta(
|
|
9
|
+
creation_timestamp="2021-10-01T00:00:00Z",
|
|
10
|
+
generation=1,
|
|
11
|
+
name="test-client",
|
|
12
|
+
namespace="default",
|
|
13
|
+
resource_version="1",
|
|
14
|
+
uid="7a25eb81-6443-47ec-a62f-50165bffede8",
|
|
15
|
+
),
|
|
16
|
+
status=V1Alpha1ClientStatus(credential=None, endpoint="https://test-client"),
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def test_client_dump_json():
|
|
21
|
+
assert (
|
|
22
|
+
TEST_CLIENT.dump_json()
|
|
23
|
+
== """{
|
|
24
|
+
"apiVersion": "jumpstarter.dev/v1alpha1",
|
|
25
|
+
"kind": "Client",
|
|
26
|
+
"metadata": {
|
|
27
|
+
"creationTimestamp": "2021-10-01T00:00:00Z",
|
|
28
|
+
"generation": 1,
|
|
29
|
+
"name": "test-client",
|
|
30
|
+
"namespace": "default",
|
|
31
|
+
"resourceVersion": "1",
|
|
32
|
+
"uid": "7a25eb81-6443-47ec-a62f-50165bffede8"
|
|
33
|
+
},
|
|
34
|
+
"status": {
|
|
35
|
+
"credential": null,
|
|
36
|
+
"endpoint": "https://test-client"
|
|
37
|
+
}
|
|
38
|
+
}"""
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_client_dump_yaml():
|
|
43
|
+
assert (
|
|
44
|
+
TEST_CLIENT.dump_yaml()
|
|
45
|
+
== """apiVersion: jumpstarter.dev/v1alpha1
|
|
46
|
+
kind: Client
|
|
47
|
+
metadata:
|
|
48
|
+
creationTimestamp: '2021-10-01T00:00:00Z'
|
|
49
|
+
generation: 1
|
|
50
|
+
name: test-client
|
|
51
|
+
namespace: default
|
|
52
|
+
resourceVersion: '1'
|
|
53
|
+
uid: 7a25eb81-6443-47ec-a62f-50165bffede8
|
|
54
|
+
status:
|
|
55
|
+
credential: null
|
|
56
|
+
endpoint: https://test-client
|
|
57
|
+
"""
|
|
58
|
+
)
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import base64
|
|
3
|
+
from typing import Literal
|
|
4
|
+
|
|
5
|
+
from kubernetes_asyncio.client.models import V1ObjectMeta, V1ObjectReference
|
|
6
|
+
from pydantic import Field
|
|
7
|
+
|
|
8
|
+
from .json import JsonBaseModel
|
|
9
|
+
from .list import V1Alpha1List
|
|
10
|
+
from .serialize import SerializeV1ObjectMeta, SerializeV1ObjectReference
|
|
11
|
+
from .util import AbstractAsyncCustomObjectApi
|
|
12
|
+
from jumpstarter.config.common import ObjectMeta
|
|
13
|
+
from jumpstarter.config.exporter import ExporterConfigV1Alpha1
|
|
14
|
+
|
|
15
|
+
CREATE_EXPORTER_DELAY = 1
|
|
16
|
+
CREATE_EXPORTER_COUNT = 10
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class V1Alpha1ExporterDevice(JsonBaseModel):
|
|
20
|
+
labels: dict[str, str]
|
|
21
|
+
uuid: str
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class V1Alpha1ExporterStatus(JsonBaseModel):
|
|
25
|
+
credential: SerializeV1ObjectReference
|
|
26
|
+
devices: list[V1Alpha1ExporterDevice]
|
|
27
|
+
endpoint: str
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class V1Alpha1Exporter(JsonBaseModel):
|
|
31
|
+
api_version: Literal["jumpstarter.dev/v1alpha1"] = Field(alias="apiVersion", default="jumpstarter.dev/v1alpha1")
|
|
32
|
+
kind: Literal["Exporter"] = Field(default="Exporter")
|
|
33
|
+
metadata: SerializeV1ObjectMeta
|
|
34
|
+
status: V1Alpha1ExporterStatus
|
|
35
|
+
|
|
36
|
+
@staticmethod
|
|
37
|
+
def from_dict(dict: dict):
|
|
38
|
+
return V1Alpha1Exporter(
|
|
39
|
+
api_version=dict["apiVersion"],
|
|
40
|
+
kind=dict["kind"],
|
|
41
|
+
metadata=V1ObjectMeta(
|
|
42
|
+
creation_timestamp=dict["metadata"]["creationTimestamp"],
|
|
43
|
+
generation=dict["metadata"]["generation"],
|
|
44
|
+
name=dict["metadata"]["name"],
|
|
45
|
+
namespace=dict["metadata"]["namespace"],
|
|
46
|
+
resource_version=dict["metadata"]["resourceVersion"],
|
|
47
|
+
uid=dict["metadata"]["uid"],
|
|
48
|
+
),
|
|
49
|
+
status=V1Alpha1ExporterStatus(
|
|
50
|
+
credential=V1ObjectReference(name=dict["status"]["credential"]["name"])
|
|
51
|
+
if "credential" in dict["status"]
|
|
52
|
+
else None,
|
|
53
|
+
endpoint=dict["status"]["endpoint"],
|
|
54
|
+
devices=[V1Alpha1ExporterDevice(labels=d["labels"], uuid=d["uuid"]) for d in dict["status"]["devices"]]
|
|
55
|
+
if "devices" in dict["status"]
|
|
56
|
+
else [],
|
|
57
|
+
),
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class V1Alpha1ExporterList(V1Alpha1List[V1Alpha1Exporter]):
|
|
62
|
+
kind: Literal["ExporterList"] = Field(default="ExporterList")
|
|
63
|
+
|
|
64
|
+
@staticmethod
|
|
65
|
+
def from_dict(dict: dict):
|
|
66
|
+
return V1Alpha1ExporterList(items=[V1Alpha1Exporter.from_dict(c) for c in dict["items"]])
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class ExportersV1Alpha1Api(AbstractAsyncCustomObjectApi):
|
|
70
|
+
"""Interact with the exporters custom resource API"""
|
|
71
|
+
|
|
72
|
+
async def list_exporters(self) -> V1Alpha1List[V1Alpha1Exporter]:
|
|
73
|
+
"""List the exporter objects in the cluster"""
|
|
74
|
+
res = await self.api.list_namespaced_custom_object(
|
|
75
|
+
namespace=self.namespace, group="jumpstarter.dev", plural="exporters", version="v1alpha1"
|
|
76
|
+
)
|
|
77
|
+
return V1Alpha1ExporterList.from_dict(res)
|
|
78
|
+
|
|
79
|
+
async def get_exporter(self, name: str) -> V1Alpha1Exporter:
|
|
80
|
+
"""Get a single exporter object from the cluster"""
|
|
81
|
+
result = await self.api.get_namespaced_custom_object(
|
|
82
|
+
namespace=self.namespace, group="jumpstarter.dev", plural="exporters", version="v1alpha1", name=name
|
|
83
|
+
)
|
|
84
|
+
return V1Alpha1Exporter.from_dict(result)
|
|
85
|
+
|
|
86
|
+
async def create_exporter(
|
|
87
|
+
self, name: str, labels: dict[str, str] | None = None, oidc_username: str | None = None
|
|
88
|
+
) -> V1Alpha1Exporter:
|
|
89
|
+
"""Create an exporter in the cluster"""
|
|
90
|
+
# Create the namespaced exporter object
|
|
91
|
+
await self.api.create_namespaced_custom_object(
|
|
92
|
+
namespace=self.namespace,
|
|
93
|
+
group="jumpstarter.dev",
|
|
94
|
+
plural="exporters",
|
|
95
|
+
version="v1alpha1",
|
|
96
|
+
body={
|
|
97
|
+
"apiVersion": "jumpstarter.dev/v1alpha1",
|
|
98
|
+
"kind": "Exporter",
|
|
99
|
+
"metadata": {"name": name} | {"labels": labels} if labels is not None else {},
|
|
100
|
+
"spec": {"username": oidc_username} if oidc_username is not None else {},
|
|
101
|
+
},
|
|
102
|
+
)
|
|
103
|
+
# Wait for the credentials to become available
|
|
104
|
+
# NOTE: Watch is not working here with the Python kubernetes library
|
|
105
|
+
count = 0
|
|
106
|
+
updated_exporter = {}
|
|
107
|
+
# Retry for a maximum of 10s
|
|
108
|
+
while count < CREATE_EXPORTER_COUNT:
|
|
109
|
+
# Try to get the updated client resource
|
|
110
|
+
updated_exporter = await self.api.get_namespaced_custom_object(
|
|
111
|
+
namespace=self.namespace, group="jumpstarter.dev", plural="exporters", version="v1alpha1", name=name
|
|
112
|
+
)
|
|
113
|
+
# check if the client status is updated with the credentials
|
|
114
|
+
if "status" in updated_exporter:
|
|
115
|
+
if "credential" in updated_exporter["status"]:
|
|
116
|
+
return V1Alpha1Exporter.from_dict(updated_exporter)
|
|
117
|
+
count += 1
|
|
118
|
+
await asyncio.sleep(CREATE_EXPORTER_DELAY)
|
|
119
|
+
raise Exception("Timeout waiting for exporter credentials")
|
|
120
|
+
|
|
121
|
+
async def get_exporter_config(self, name: str) -> ExporterConfigV1Alpha1:
|
|
122
|
+
"""Get an exporter config for a specified exporter name"""
|
|
123
|
+
exporter = await self.get_exporter(name)
|
|
124
|
+
secret = await self.core_api.read_namespaced_secret(exporter.status.credential.name, self.namespace)
|
|
125
|
+
endpoint = exporter.status.endpoint
|
|
126
|
+
token = base64.b64decode(secret.data["token"]).decode("utf8")
|
|
127
|
+
return ExporterConfigV1Alpha1(
|
|
128
|
+
alias=name,
|
|
129
|
+
metadata=ObjectMeta(
|
|
130
|
+
namespace=exporter.metadata.namespace,
|
|
131
|
+
name=exporter.metadata.name,
|
|
132
|
+
),
|
|
133
|
+
endpoint=endpoint,
|
|
134
|
+
token=token,
|
|
135
|
+
export={},
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
async def delete_exporter(self, name: str):
|
|
139
|
+
"""Delete an exporter object"""
|
|
140
|
+
await self.api.delete_namespaced_custom_object(
|
|
141
|
+
namespace=self.namespace,
|
|
142
|
+
name=name,
|
|
143
|
+
group="jumpstarter.dev",
|
|
144
|
+
plural="exporters",
|
|
145
|
+
version="v1alpha1",
|
|
146
|
+
)
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
from kubernetes_asyncio.client.models import V1ObjectMeta, V1ObjectReference
|
|
2
|
+
|
|
3
|
+
from jumpstarter_kubernetes.exporters import V1Alpha1Exporter, V1Alpha1ExporterDevice, V1Alpha1ExporterStatus
|
|
4
|
+
|
|
5
|
+
TEST_EXPORTER = V1Alpha1Exporter(
|
|
6
|
+
api_version="jumpstarter.dev/v1alpha1",
|
|
7
|
+
kind="Exporter",
|
|
8
|
+
metadata=V1ObjectMeta(
|
|
9
|
+
creation_timestamp="2021-10-01T00:00:00Z",
|
|
10
|
+
generation=1,
|
|
11
|
+
name="test-exporter",
|
|
12
|
+
namespace="default",
|
|
13
|
+
resource_version="1",
|
|
14
|
+
uid="7a25eb81-6443-47ec-a62f-50165bffede8",
|
|
15
|
+
),
|
|
16
|
+
status=V1Alpha1ExporterStatus(
|
|
17
|
+
credential=V1ObjectReference(name="test-credential"),
|
|
18
|
+
devices=[V1Alpha1ExporterDevice(labels={"test": "label"}, uuid="f4cf49ab-fc64-46c6-94e7-a40502eb77b1")],
|
|
19
|
+
endpoint="https://test-exporter",
|
|
20
|
+
),
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_exporter_dump_json():
|
|
25
|
+
assert (
|
|
26
|
+
TEST_EXPORTER.dump_json()
|
|
27
|
+
== """{
|
|
28
|
+
"apiVersion": "jumpstarter.dev/v1alpha1",
|
|
29
|
+
"kind": "Exporter",
|
|
30
|
+
"metadata": {
|
|
31
|
+
"creationTimestamp": "2021-10-01T00:00:00Z",
|
|
32
|
+
"generation": 1,
|
|
33
|
+
"name": "test-exporter",
|
|
34
|
+
"namespace": "default",
|
|
35
|
+
"resourceVersion": "1",
|
|
36
|
+
"uid": "7a25eb81-6443-47ec-a62f-50165bffede8"
|
|
37
|
+
},
|
|
38
|
+
"status": {
|
|
39
|
+
"credential": {
|
|
40
|
+
"name": "test-credential"
|
|
41
|
+
},
|
|
42
|
+
"devices": [
|
|
43
|
+
{
|
|
44
|
+
"labels": {
|
|
45
|
+
"test": "label"
|
|
46
|
+
},
|
|
47
|
+
"uuid": "f4cf49ab-fc64-46c6-94e7-a40502eb77b1"
|
|
48
|
+
}
|
|
49
|
+
],
|
|
50
|
+
"endpoint": "https://test-exporter"
|
|
51
|
+
}
|
|
52
|
+
}"""
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def test_exporter_dump_yaml():
|
|
57
|
+
assert (
|
|
58
|
+
TEST_EXPORTER.dump_yaml()
|
|
59
|
+
== """apiVersion: jumpstarter.dev/v1alpha1
|
|
60
|
+
kind: Exporter
|
|
61
|
+
metadata:
|
|
62
|
+
creationTimestamp: '2021-10-01T00:00:00Z'
|
|
63
|
+
generation: 1
|
|
64
|
+
name: test-exporter
|
|
65
|
+
namespace: default
|
|
66
|
+
resourceVersion: '1'
|
|
67
|
+
uid: 7a25eb81-6443-47ec-a62f-50165bffede8
|
|
68
|
+
status:
|
|
69
|
+
credential:
|
|
70
|
+
name: test-credential
|
|
71
|
+
devices:
|
|
72
|
+
- labels:
|
|
73
|
+
test: label
|
|
74
|
+
uuid: f4cf49ab-fc64-46c6-94e7-a40502eb77b1
|
|
75
|
+
endpoint: https://test-exporter
|
|
76
|
+
"""
|
|
77
|
+
)
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import shutil
|
|
3
|
+
from typing import Literal, Optional
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def helm_installed(name: str) -> bool:
|
|
7
|
+
return shutil.which(name) is not None
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
async def install_helm_chart(
|
|
11
|
+
chart: str,
|
|
12
|
+
name: str,
|
|
13
|
+
namespace: str,
|
|
14
|
+
basedomain: str,
|
|
15
|
+
grpc_endpoint: str,
|
|
16
|
+
router_endpoint: str,
|
|
17
|
+
mode: Literal["nodeport"] | Literal["ingress"] | Literal["route"],
|
|
18
|
+
version: str,
|
|
19
|
+
kubeconfig: Optional[str],
|
|
20
|
+
context: Optional[str],
|
|
21
|
+
helm: Optional[str] = "helm",
|
|
22
|
+
):
|
|
23
|
+
grpc_port = grpc_endpoint.split(":")[1]
|
|
24
|
+
router_port = router_endpoint.split(":")[1]
|
|
25
|
+
args = [
|
|
26
|
+
helm,
|
|
27
|
+
"upgrade",
|
|
28
|
+
name,
|
|
29
|
+
"--install",
|
|
30
|
+
chart,
|
|
31
|
+
"--create-namespace",
|
|
32
|
+
"--namespace",
|
|
33
|
+
namespace,
|
|
34
|
+
"--set",
|
|
35
|
+
f"global.baseDomain={basedomain}",
|
|
36
|
+
"--set",
|
|
37
|
+
f"jumpstarter-controller.grpc.endpoint={grpc_endpoint}",
|
|
38
|
+
"--set",
|
|
39
|
+
f"jumpstarter-controller.grpc.routerEndpoint={router_endpoint}",
|
|
40
|
+
"--set",
|
|
41
|
+
"global.metrics.enabled=false",
|
|
42
|
+
"--set",
|
|
43
|
+
f"jumpstarter-controller.grpc.nodeport.enabled={'true' if mode == 'nodeport' else 'false'}",
|
|
44
|
+
"--set",
|
|
45
|
+
f"jumpstarter-controller.grpc.nodeport.port={grpc_port}",
|
|
46
|
+
"--set",
|
|
47
|
+
f"jumpstarter-controller.grpc.nodeport.routerPort={router_port}",
|
|
48
|
+
"--set",
|
|
49
|
+
f"jumpstarter-controller.grpc.mode={mode}",
|
|
50
|
+
"--version",
|
|
51
|
+
version,
|
|
52
|
+
"--wait",
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
if kubeconfig is not None:
|
|
56
|
+
args.append("--kubeconfig")
|
|
57
|
+
args.append(kubeconfig)
|
|
58
|
+
|
|
59
|
+
if context is not None:
|
|
60
|
+
args.append("--kube-context")
|
|
61
|
+
args.append(context)
|
|
62
|
+
|
|
63
|
+
# Attempt to install Jumpstarter using Helm
|
|
64
|
+
process = await asyncio.create_subprocess_exec(*args)
|
|
65
|
+
await process.wait()
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import yaml
|
|
2
|
+
from pydantic import BaseModel, ConfigDict
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class JsonBaseModel(BaseModel):
|
|
6
|
+
"""A Pydantic BaseModel with additional Jumpstarter JSON options applied."""
|
|
7
|
+
|
|
8
|
+
def dump_json(self):
|
|
9
|
+
return self.model_dump_json(indent=4, by_alias=True)
|
|
10
|
+
|
|
11
|
+
def dump_yaml(self):
|
|
12
|
+
return yaml.safe_dump(self.model_dump(mode="json", by_alias=True), indent=2)
|
|
13
|
+
|
|
14
|
+
model_config = ConfigDict(arbitrary_types_allowed=True, populate_by_name=True)
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
from typing import Literal, Optional
|
|
2
|
+
|
|
3
|
+
from kubernetes_asyncio.client.models import V1Condition, V1ObjectMeta, V1ObjectReference
|
|
4
|
+
from pydantic import Field
|
|
5
|
+
|
|
6
|
+
from .json import JsonBaseModel
|
|
7
|
+
from .list import V1Alpha1List
|
|
8
|
+
from .serialize import SerializeV1Condition, SerializeV1ObjectMeta, SerializeV1ObjectReference
|
|
9
|
+
from .util import AbstractAsyncCustomObjectApi
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class V1Alpha1LeaseStatus(JsonBaseModel):
|
|
13
|
+
begin_time: Optional[str] = Field(alias="beginTime")
|
|
14
|
+
conditions: list[SerializeV1Condition]
|
|
15
|
+
end_time: Optional[str] = Field(alias="endTime")
|
|
16
|
+
ended: bool
|
|
17
|
+
exporter: Optional[SerializeV1ObjectReference]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class V1Alpha1LeaseSelector(JsonBaseModel):
|
|
21
|
+
match_labels: dict[str, str] = Field(alias="matchLabels")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class V1Alpha1LeaseSpec(JsonBaseModel):
|
|
25
|
+
client: SerializeV1ObjectReference
|
|
26
|
+
duration: Optional[str]
|
|
27
|
+
selector: V1Alpha1LeaseSelector
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class V1Alpha1Lease(JsonBaseModel):
|
|
31
|
+
api_version: Literal["jumpstarter.dev/v1alpha1"] = Field(alias="apiVersion", default="jumpstarter.dev/v1alpha1")
|
|
32
|
+
kind: Literal["Lease"] = Field(default="Lease")
|
|
33
|
+
metadata: SerializeV1ObjectMeta
|
|
34
|
+
spec: V1Alpha1LeaseSpec
|
|
35
|
+
status: V1Alpha1LeaseStatus
|
|
36
|
+
|
|
37
|
+
@staticmethod
|
|
38
|
+
def from_dict(dict: dict):
|
|
39
|
+
return V1Alpha1Lease(
|
|
40
|
+
api_version=dict["apiVersion"],
|
|
41
|
+
kind=dict["kind"],
|
|
42
|
+
metadata=V1ObjectMeta(
|
|
43
|
+
creation_timestamp=dict["metadata"]["creationTimestamp"],
|
|
44
|
+
generation=dict["metadata"]["generation"],
|
|
45
|
+
managed_fields=dict["metadata"]["managedFields"],
|
|
46
|
+
name=dict["metadata"]["name"],
|
|
47
|
+
namespace=dict["metadata"]["namespace"],
|
|
48
|
+
resource_version=dict["metadata"]["resourceVersion"],
|
|
49
|
+
uid=dict["metadata"]["uid"],
|
|
50
|
+
),
|
|
51
|
+
status=V1Alpha1LeaseStatus(
|
|
52
|
+
begin_time=dict["status"]["beginTime"] if "beginTime" in dict["status"] else None,
|
|
53
|
+
end_time=dict["status"]["endTime"] if "endTime" in dict["status"] else None,
|
|
54
|
+
ended=dict["status"]["ended"],
|
|
55
|
+
exporter=V1ObjectReference(name=dict["status"]["exporterRef"]["name"])
|
|
56
|
+
if "exporterRef" in dict["status"]
|
|
57
|
+
else None,
|
|
58
|
+
conditions=[
|
|
59
|
+
V1Condition(
|
|
60
|
+
last_transition_time=cond["lastTransitionTime"],
|
|
61
|
+
message=cond["message"],
|
|
62
|
+
observed_generation=cond["observedGeneration"],
|
|
63
|
+
reason=cond["reason"],
|
|
64
|
+
status=cond["status"],
|
|
65
|
+
type=cond["type"],
|
|
66
|
+
)
|
|
67
|
+
for cond in dict["status"]["conditions"]
|
|
68
|
+
],
|
|
69
|
+
),
|
|
70
|
+
spec=V1Alpha1LeaseSpec(
|
|
71
|
+
client=V1ObjectReference(name=dict["spec"]["clientRef"]["name"])
|
|
72
|
+
if "clientRef" in dict["spec"]
|
|
73
|
+
else None,
|
|
74
|
+
duration=dict["spec"]["duration"] if "duration" in dict["spec"] else None,
|
|
75
|
+
selector=V1Alpha1LeaseSelector(match_labels=dict["spec"]["selector"]["matchLabels"]),
|
|
76
|
+
),
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class V1Alpha1LeaseList(V1Alpha1List[V1Alpha1Lease]):
|
|
81
|
+
kind: Literal["LeaseList"] = Field(default="LeaseList")
|
|
82
|
+
|
|
83
|
+
@staticmethod
|
|
84
|
+
def from_dict(dict: dict):
|
|
85
|
+
return V1Alpha1LeaseList(items=[V1Alpha1Lease.from_dict(c) for c in dict["items"]])
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class LeasesV1Alpha1Api(AbstractAsyncCustomObjectApi):
|
|
89
|
+
"""Interact with the leases custom resource API"""
|
|
90
|
+
|
|
91
|
+
async def list_leases(self) -> V1Alpha1List[V1Alpha1Lease]:
|
|
92
|
+
"""List the lease objects in the cluster async"""
|
|
93
|
+
result = await self.api.list_namespaced_custom_object(
|
|
94
|
+
namespace=self.namespace, group="jumpstarter.dev", plural="leases", version="v1alpha1"
|
|
95
|
+
)
|
|
96
|
+
return V1Alpha1LeaseList.from_dict(result)
|
|
97
|
+
|
|
98
|
+
async def get_lease(self, name: str) -> V1Alpha1Lease:
|
|
99
|
+
"""Get a single lease object from the cluster async"""
|
|
100
|
+
result = await self.api.get_namespaced_custom_object(
|
|
101
|
+
namespace=self.namespace, group="jumpstarter.dev", plural="leases", version="v1alpha1", name=name
|
|
102
|
+
)
|
|
103
|
+
return V1Alpha1Lease.from_dict(result)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from typing import Generic, Literal, TypeVar
|
|
2
|
+
|
|
3
|
+
from pydantic import Field
|
|
4
|
+
|
|
5
|
+
from .json import JsonBaseModel
|
|
6
|
+
|
|
7
|
+
T = TypeVar("T")
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class V1Alpha1List(JsonBaseModel, Generic[T]):
|
|
11
|
+
"""A generic list result type."""
|
|
12
|
+
|
|
13
|
+
api_version: Literal["jumpstarter.dev/v1alpha1"] = Field(alias="apiVersion", default="jumpstarter.dev/v1alpha1")
|
|
14
|
+
items: list[T]
|
|
15
|
+
kind: Literal["List"] = Field(default="List")
|
|
File without changes
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from typing import Annotated, Any, Dict
|
|
2
|
+
|
|
3
|
+
from kubernetes_asyncio.client.models import V1Condition, V1ObjectMeta, V1ObjectReference
|
|
4
|
+
from pydantic import WrapSerializer
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def k8s_obj_to_dict(value: Any, handler, info) -> Dict[str, Any]:
|
|
8
|
+
result = value.to_dict(serialize=True)
|
|
9
|
+
return {k: v for k, v in result.items() if v is not None}
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
SerializeV1Condition = Annotated[V1Condition, WrapSerializer(k8s_obj_to_dict)]
|
|
13
|
+
SerializeV1ObjectMeta = Annotated[V1ObjectMeta, WrapSerializer(k8s_obj_to_dict)]
|
|
14
|
+
SerializeV1ObjectReference = Annotated[V1ObjectReference, WrapSerializer(k8s_obj_to_dict)]
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
from kubernetes_asyncio.client.models import V1Condition, V1ObjectMeta, V1ObjectReference
|
|
2
|
+
|
|
3
|
+
from jumpstarter_kubernetes import V1Alpha1Lease, V1Alpha1LeaseSelector, V1Alpha1LeaseSpec, V1Alpha1LeaseStatus
|
|
4
|
+
|
|
5
|
+
TEST_LEASE = V1Alpha1Lease(
|
|
6
|
+
api_version="jumpstarter.dev/v1alpha1",
|
|
7
|
+
kind="Lease",
|
|
8
|
+
metadata=V1ObjectMeta(
|
|
9
|
+
creation_timestamp="2021-10-01T00:00:00Z",
|
|
10
|
+
generation=1,
|
|
11
|
+
name="test-lease",
|
|
12
|
+
namespace="default",
|
|
13
|
+
resource_version="1",
|
|
14
|
+
uid="7a25eb81-6443-47ec-a62f-50165bffede8",
|
|
15
|
+
),
|
|
16
|
+
spec=V1Alpha1LeaseSpec(
|
|
17
|
+
client=V1ObjectReference(name="test-client"),
|
|
18
|
+
duration="1h",
|
|
19
|
+
selector=V1Alpha1LeaseSelector(match_labels={"test": "label", "another": "something"}),
|
|
20
|
+
),
|
|
21
|
+
status=V1Alpha1LeaseStatus(
|
|
22
|
+
begin_time="2021-10-01T00:00:00Z",
|
|
23
|
+
conditions=[
|
|
24
|
+
V1Condition(
|
|
25
|
+
last_transition_time="2021-10-01T00:00:00Z", status="True", type="Active", message="", reason=""
|
|
26
|
+
)
|
|
27
|
+
],
|
|
28
|
+
end_time="2021-10-01T01:00:00Z",
|
|
29
|
+
ended=False,
|
|
30
|
+
exporter=V1ObjectReference(name="test-exporter"),
|
|
31
|
+
),
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_lease_dump_json():
|
|
36
|
+
print(TEST_LEASE.dump_json())
|
|
37
|
+
assert (
|
|
38
|
+
TEST_LEASE.dump_json()
|
|
39
|
+
== """{
|
|
40
|
+
"apiVersion": "jumpstarter.dev/v1alpha1",
|
|
41
|
+
"kind": "Lease",
|
|
42
|
+
"metadata": {
|
|
43
|
+
"creationTimestamp": "2021-10-01T00:00:00Z",
|
|
44
|
+
"generation": 1,
|
|
45
|
+
"name": "test-lease",
|
|
46
|
+
"namespace": "default",
|
|
47
|
+
"resourceVersion": "1",
|
|
48
|
+
"uid": "7a25eb81-6443-47ec-a62f-50165bffede8"
|
|
49
|
+
},
|
|
50
|
+
"spec": {
|
|
51
|
+
"client": {
|
|
52
|
+
"name": "test-client"
|
|
53
|
+
},
|
|
54
|
+
"duration": "1h",
|
|
55
|
+
"selector": {
|
|
56
|
+
"matchLabels": {
|
|
57
|
+
"test": "label",
|
|
58
|
+
"another": "something"
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
"status": {
|
|
63
|
+
"beginTime": "2021-10-01T00:00:00Z",
|
|
64
|
+
"conditions": [
|
|
65
|
+
{
|
|
66
|
+
"lastTransitionTime": "2021-10-01T00:00:00Z",
|
|
67
|
+
"message": "",
|
|
68
|
+
"reason": "",
|
|
69
|
+
"status": "True",
|
|
70
|
+
"type": "Active"
|
|
71
|
+
}
|
|
72
|
+
],
|
|
73
|
+
"endTime": "2021-10-01T01:00:00Z",
|
|
74
|
+
"ended": false,
|
|
75
|
+
"exporter": {
|
|
76
|
+
"name": "test-exporter"
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}"""
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def test_lease_dump_yaml():
|
|
84
|
+
print(TEST_LEASE.dump_yaml())
|
|
85
|
+
assert (
|
|
86
|
+
TEST_LEASE.dump_yaml()
|
|
87
|
+
== """apiVersion: jumpstarter.dev/v1alpha1
|
|
88
|
+
kind: Lease
|
|
89
|
+
metadata:
|
|
90
|
+
creationTimestamp: '2021-10-01T00:00:00Z'
|
|
91
|
+
generation: 1
|
|
92
|
+
name: test-lease
|
|
93
|
+
namespace: default
|
|
94
|
+
resourceVersion: '1'
|
|
95
|
+
uid: 7a25eb81-6443-47ec-a62f-50165bffede8
|
|
96
|
+
spec:
|
|
97
|
+
client:
|
|
98
|
+
name: test-client
|
|
99
|
+
duration: 1h
|
|
100
|
+
selector:
|
|
101
|
+
matchLabels:
|
|
102
|
+
another: something
|
|
103
|
+
test: label
|
|
104
|
+
status:
|
|
105
|
+
beginTime: '2021-10-01T00:00:00Z'
|
|
106
|
+
conditions:
|
|
107
|
+
- lastTransitionTime: '2021-10-01T00:00:00Z'
|
|
108
|
+
message: ''
|
|
109
|
+
reason: ''
|
|
110
|
+
status: 'True'
|
|
111
|
+
type: Active
|
|
112
|
+
endTime: '2021-10-01T01:00:00Z'
|
|
113
|
+
ended: false
|
|
114
|
+
exporter:
|
|
115
|
+
name: test-exporter
|
|
116
|
+
"""
|
|
117
|
+
)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from contextlib import AbstractAsyncContextManager
|
|
2
|
+
from typing import Optional, Self
|
|
3
|
+
|
|
4
|
+
from kubernetes_asyncio import config
|
|
5
|
+
from kubernetes_asyncio.client.api import CoreV1Api, CustomObjectsApi
|
|
6
|
+
from kubernetes_asyncio.client.api_client import ApiClient
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class AbstractAsyncCustomObjectApi(AbstractAsyncContextManager):
|
|
10
|
+
"""An abstract async custom object API client"""
|
|
11
|
+
|
|
12
|
+
_client: ApiClient
|
|
13
|
+
config_file: Optional[str]
|
|
14
|
+
context: Optional[str]
|
|
15
|
+
namespace: str
|
|
16
|
+
api: CustomObjectsApi
|
|
17
|
+
core_api: CoreV1Api
|
|
18
|
+
|
|
19
|
+
def __init__(self, namespace: str, config_file: Optional[str] = None, context: Optional[str] = None):
|
|
20
|
+
self.config_file = config_file
|
|
21
|
+
self.context = context
|
|
22
|
+
self.namespace = namespace
|
|
23
|
+
|
|
24
|
+
async def __aenter__(self) -> Self:
|
|
25
|
+
# Load the kubeconfig
|
|
26
|
+
await self._load_kube_config()
|
|
27
|
+
# Construct the API client and enter context
|
|
28
|
+
self._client = ApiClient()
|
|
29
|
+
await self._client.__aenter__()
|
|
30
|
+
# Construct the custom objects API client
|
|
31
|
+
self.api = CustomObjectsApi(self._client)
|
|
32
|
+
self.core_api = CoreV1Api(self._client)
|
|
33
|
+
return self
|
|
34
|
+
|
|
35
|
+
async def _load_kube_config(self):
|
|
36
|
+
await config.load_kube_config(self.config_file, self.context)
|
|
37
|
+
|
|
38
|
+
async def __aexit__(self, exc_type, exc_value, traceback):
|
|
39
|
+
await self._client.__aexit__(exc_type, exc_value, traceback)
|
|
40
|
+
self._client = None
|
|
41
|
+
self.api = None
|
|
42
|
+
self.core_api = None
|
|
43
|
+
return None
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: jumpstarter-kubernetes
|
|
3
|
+
Version: 0.6.0
|
|
4
|
+
Project-URL: Homepage, https://jumpstarter.dev
|
|
5
|
+
Project-URL: source_archive, https://github.com/jumpstarter-dev/repo/archive/c2927a2abac82d224c7bd28f9ed83c57b5222e65.zip
|
|
6
|
+
Author-email: Kirk Brauer <kbrauer@hatci.com>
|
|
7
|
+
License-Expression: Apache-2.0
|
|
8
|
+
Requires-Python: >=3.11
|
|
9
|
+
Requires-Dist: jumpstarter==0.6.0
|
|
10
|
+
Requires-Dist: kubernetes-asyncio>=31.1.0
|
|
11
|
+
Requires-Dist: kubernetes>=31.0.0
|
|
12
|
+
Requires-Dist: pydantic>=2.8.2
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
|
|
15
|
+
# Jumpstarter Kubernetes Library
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
jumpstarter_kubernetes/__init__.py,sha256=kYHnYJYZ4ly6q7dU2cIOgnbWnO64xU_HmkTMkY0ARpE,983
|
|
2
|
+
jumpstarter_kubernetes/clients.py,sha256=uo43zmWhymfsatM5P87PnwJxzg4pWu9s7JTeamWny0Q,5711
|
|
3
|
+
jumpstarter_kubernetes/clients_test.py,sha256=dGWdFlq08CULXQw_yPT-KXjvQS-BVU5dTB2kU2-2tFo,1457
|
|
4
|
+
jumpstarter_kubernetes/exporters.py,sha256=4PS-kXuriJuKMlYIGcDGaB0lhZLkhg_zCoM-9LwuAD4,5903
|
|
5
|
+
jumpstarter_kubernetes/exporters_test.py,sha256=aKZuw6AGN_FGCnKqTAOp3MRAXrThxKtBVboYYhK-e08,2079
|
|
6
|
+
jumpstarter_kubernetes/install.py,sha256=HjfTwNAK_VKMoFXTqdwNuvagXDSaxS23qQ6ap5qGFGQ,1788
|
|
7
|
+
jumpstarter_kubernetes/json.py,sha256=69BNuBjHASbWpPXIdjiZbcLLh7-DXhYREEUFWfREQec,452
|
|
8
|
+
jumpstarter_kubernetes/leases.py,sha256=o-vmnLRypnDR7aWPxP1LZN28-c5gsD6S4ouoQMzHgyY,4235
|
|
9
|
+
jumpstarter_kubernetes/list.py,sha256=sEJEQDAgxwYC8mmGzT_BG5OsSb4KRAMKLOOlyeH93xg,398
|
|
10
|
+
jumpstarter_kubernetes/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
|
+
jumpstarter_kubernetes/serialize.py,sha256=ZFKd-PkDaB5zKTn9lqUN7oeZ1ripst0wogIWiEZdndo,593
|
|
12
|
+
jumpstarter_kubernetes/test_leases.py,sha256=uZE5-NF3vLow9GkQGcruq-7KPupfp4uMWWcj96T7NQ0,3042
|
|
13
|
+
jumpstarter_kubernetes/util/__init__.py,sha256=oNVNglqOgDzvcibGGVoBBpcFNJv_w3x-iE0OEDKfZhM,110
|
|
14
|
+
jumpstarter_kubernetes/util/async_custom_object_api.py,sha256=geIgT3yvNYJerwMy2V9bOOAmYXW4n1nA_5BFdsZw-Co,1486
|
|
15
|
+
jumpstarter_kubernetes-0.6.0.dist-info/METADATA,sha256=5yY_P3sJA11-0gyNgqn3lKqREMQJyZXSC0lb94o5su8,550
|
|
16
|
+
jumpstarter_kubernetes-0.6.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
17
|
+
jumpstarter_kubernetes-0.6.0.dist-info/RECORD,,
|