-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Kubernetes CRD client and schemas (#33)
* Add Kubernetes CRD client and schemas
- Loading branch information
Showing
13 changed files
with
787 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
# Copyright Axis Communications AB. | ||
# | ||
# For a full list of individual contributors, please see the commit history. | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
"""Environment custom resource manager ETOS.""" | ||
import logging | ||
from .etos import Kubernetes, Resource | ||
|
||
|
||
class Environment(Resource): | ||
"""Environment handles the Environment custom Kubernetes resources.""" | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
def __init__(self, client: Kubernetes): | ||
"""Set up Kubernetes client.""" | ||
self.client = client.environments | ||
self.namespace = client.namespace |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
# Copyright Axis Communications AB. | ||
# | ||
# For a full list of individual contributors, please see the commit history. | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
"""Environment request custom resource manager ETOS.""" | ||
import logging | ||
from .etos import Kubernetes, Resource | ||
|
||
|
||
class EnvironmentRequest(Resource): | ||
"""EnvironmentRequest handles the EnvironmentRequest custom Kubernetes resources.""" | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
def __init__(self, client: Kubernetes): | ||
"""Set up Kubernetes client.""" | ||
self.client = client.environment_requests | ||
self.namespace = client.namespace |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
# Copyright Axis Communications AB. | ||
# | ||
# For a full list of individual contributors, please see the commit history. | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
"""Kubernetes client for ETOS custom resources.""" | ||
import os | ||
import logging | ||
from pathlib import Path | ||
from typing import Optional | ||
from pydantic import BaseModel | ||
from kubernetes import config | ||
from kubernetes.client import api_client | ||
from kubernetes.dynamic import DynamicClient | ||
from kubernetes.dynamic.resource import Resource as DynamicResource, ResourceInstance | ||
from kubernetes.dynamic.exceptions import NotFoundError | ||
|
||
config.load_config() | ||
NAMESPACE_FILE = Path("/var/run/secrets/kubernetes.io/serviceaccount/namespace") | ||
|
||
|
||
class NoNamespace(Exception): | ||
"""NoNamespace exception is raised when ETOS could not determine the current namespace.""" | ||
|
||
|
||
class Resource: | ||
"""Resource is the base resource client for ETOS custom resources. | ||
This resource base class is used by our custom resources to, somewhat, mimic | ||
the behavior of a built-in resource from Kubernetes. This means that we don't | ||
do any error handling as that is not done in the built-in Kubernetes client. | ||
While we do somewhat mimic the behavior we don't necessarily mimic the "API" | ||
of the built-in resources. For example, we return boolean where the built-in | ||
would return the Kubernetes API response. We do this because of how we typically | ||
use Kubernetes in our services. If the Kubernetes API response is preferred | ||
we can still use the client :obj:`DynamicResource` directly. | ||
""" | ||
|
||
client: DynamicResource | ||
namespace: str = "default" | ||
|
||
def get(self, name: str) -> Optional[ResourceInstance]: | ||
"""Get a resource from Kubernetes by name.""" | ||
return self.client.get(name=name, namespace=self.namespace) # type: ignore | ||
|
||
def delete(self, name: str) -> bool: | ||
"""Delete a resource by name.""" | ||
if self.client.delete(name=name, namespace=self.namespace): # type: ignore | ||
return True | ||
return False | ||
|
||
def create(self, model: BaseModel) -> bool: | ||
"""Create a resource from a pydantic model.""" | ||
if self.client.create(body=model.model_dump(), namespace=self.namespace): # type: ignore | ||
return True | ||
return False | ||
|
||
def exists(self, name: str) -> bool: | ||
"""Test if a resource with name exists.""" | ||
try: | ||
return self.get(name) is not None | ||
except NotFoundError: | ||
return False | ||
|
||
|
||
class Kubernetes: | ||
"""Kubernetes is a client for fetching ETOS custom resources from Kubernetes.""" | ||
|
||
__providers = None | ||
__requests = None | ||
__testruns = None | ||
__environments = None | ||
__namespace = None | ||
logger = logging.getLogger(__name__) | ||
|
||
def __init__(self, version="v1alpha1"): | ||
"""Initialize a dynamic client with version.""" | ||
self.version = f"etos.eiffel-community.github.io/{version}" | ||
self.__client = DynamicClient(api_client.ApiClient()) | ||
|
||
@property | ||
def namespace(self) -> str: | ||
"""Namespace returns the current namespace of the machine this code is running on.""" | ||
if self.__namespace is None: | ||
if not NAMESPACE_FILE.exists(): | ||
self.logger.warning( | ||
"Not running in Kubernetes? Namespace file not found: %s", NAMESPACE_FILE | ||
) | ||
etos_ns = os.getenv("ETOS_NAMESPACE") | ||
if etos_ns: | ||
self.logger.warning( | ||
"Defauling to environment variable 'ETOS_NAMESPACE': %s", etos_ns | ||
) | ||
else: | ||
self.logger.warning("ETOS_NAMESPACE environment variable not set!") | ||
raise NoNamespace("Failed to determine Kubernetes namespace!") | ||
self.__namespace = etos_ns | ||
else: | ||
self.__namespace = NAMESPACE_FILE.read_text(encoding="utf-8") | ||
return self.__namespace | ||
|
||
@property | ||
def providers(self) -> DynamicResource: | ||
"""Providers request returns a client for Provider resources.""" | ||
if self.__providers is None: | ||
self.__providers = self.__client.resources.get( | ||
api_version=self.version, kind="Provider" | ||
) | ||
return self.__providers | ||
|
||
@property | ||
def environment_requests(self) -> DynamicResource: | ||
"""Environment requests returns a client for EnvironmentRequest resources.""" | ||
if self.__requests is None: | ||
self.__requests = self.__client.resources.get( | ||
api_version=self.version, kind="EnvironmentRequest" | ||
) | ||
return self.__requests | ||
|
||
@property | ||
def environments(self) -> DynamicResource: | ||
"""Environments returns a client for Environment resources.""" | ||
if self.__environments is None: | ||
self.__environments = self.__client.resources.get( | ||
api_version=self.version, kind="Environment" | ||
) | ||
return self.__environments | ||
|
||
@property | ||
def testruns(self) -> DynamicResource: | ||
"""Testruns returns a client for TestRun resources.""" | ||
if self.__testruns is None: | ||
self.__testruns = self.__client.resources.get(api_version=self.version, kind="TestRun") | ||
return self.__testruns |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
# Copyright Axis Communications AB. | ||
# | ||
# For a full list of individual contributors, please see the commit history. | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
"""Provider custom resource manager ETOS.""" | ||
from .etos import Kubernetes, Resource | ||
|
||
|
||
class Provider(Resource): | ||
"""Provider handles the Provider custom Kubernetes resources.""" | ||
|
||
def __init__(self, client: Kubernetes): | ||
"""Set up Kubernetes client.""" | ||
self.client = client.providers | ||
self.namespace = client.namespace |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
# Copyright Axis Communications AB. | ||
# | ||
# For a full list of individual contributors, please see the commit history. | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
"""ETOS Kubernetes schemas.""" | ||
from .common import Metadata | ||
from .environment import * | ||
from .environment_request import * | ||
from .testrun import * | ||
from .provider import * |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
# Copyright Axis Communications AB. | ||
# | ||
# For a full list of individual contributors, please see the commit history. | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
"""Common schemas that are used between most kubernetes resources.""" | ||
from typing import Optional | ||
from pydantic import BaseModel | ||
|
||
|
||
class OwnerReference(BaseModel): | ||
"""Owner reference describes the owner of a kubernetes resource.""" | ||
|
||
apiVersion: str | ||
kind: str | ||
name: str | ||
uid: str | ||
controller: Optional[bool] | ||
blockOwnerDeletion: bool | ||
|
||
|
||
class Metadata(BaseModel): | ||
"""Metadata describes the metadata of a kubernetes resource.""" | ||
|
||
name: Optional[str] = None | ||
generateName: Optional[str] = None | ||
namespace: str = "default" | ||
uid: Optional[str] = None | ||
ownerReferences: list[OwnerReference] = [] | ||
labels: Optional[dict[str, str]] = None | ||
annotations: Optional[dict[str, str]] = None | ||
|
||
|
||
class Image(BaseModel): | ||
"""Image is a container image representation.""" | ||
|
||
image: str | ||
imagePullPolicy: str = "IfNotPresent" | ||
|
||
|
||
class Retention(BaseModel): | ||
"""Retention for ETOS testruns.""" | ||
|
||
failure: Optional[str] = None | ||
success: Optional[str] = None |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
# Copyright Axis Communications AB. | ||
# | ||
# For a full list of individual contributors, please see the commit history. | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
"""Models for the Environment resource.""" | ||
from typing import Optional | ||
from pydantic import BaseModel | ||
from .common import Metadata | ||
from .testrun import Test, Suite | ||
|
||
|
||
class EnvironmentSpec(BaseModel): | ||
"""EnvironmentSpec is the specification of a Environment Kubernetes resource.""" | ||
|
||
name: str | ||
suite_id: str | ||
sub_suite_id: str | ||
test_suite_started_id: str | ||
artifact: str | ||
context: str | ||
priority: int = 1 | ||
test_runner: str | ||
recipes: list[Test] | ||
iut: dict | ||
executor: dict | ||
log_area: dict | ||
|
||
@classmethod | ||
def from_subsuite(cls, sub_suite: dict) -> "EnvironmentSpec": | ||
"""Create environment spec from sub suite definition.""" | ||
sub_suite["recipes"] = Suite.tests_from_recipes(sub_suite.pop("recipes")) | ||
spec = EnvironmentSpec(**sub_suite) | ||
return spec | ||
|
||
|
||
class Environment(BaseModel): | ||
"""Environment Kubernetes resource.""" | ||
|
||
apiVersion: Optional[str] = "etos.eiffel-community.github.io/v1alpha1" | ||
kind: Optional[str] = "Environment" | ||
metadata: Metadata | ||
spec: EnvironmentSpec |
Oops, something went wrong.