Skip to content

Commit

Permalink
Merge pull request #8 from Schille/feature/k8s_fallback_api
Browse files Browse the repository at this point in the history
feat: support k8s apis which are not supported by the python binding anymore
  • Loading branch information
Schille authored Apr 5, 2022
2 parents 144a6e6 + ed3855d commit 351a469
Show file tree
Hide file tree
Showing 8 changed files with 107 additions and 91 deletions.
55 changes: 11 additions & 44 deletions deck.example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ version: "1"
cluster:
provider: k3d
minVersion: 4.0.0
name: another-cluster
name: mumpiz
nativeConfig: # content of the native config file (e.g. https://k3d.io/v5.0.0/usage/configfile/)
apiVersion: k3d.io/v1alpha2
kind: Simple
Expand All @@ -20,48 +20,15 @@ cluster:
- agent[0]

decks:
- name: buzzword-counter
namespace: buzzword
- name: mumpiz-platform
sources:
- type: helm
ref: https://kubernetes.github.io/dashboard/
chart: kubernetes-dashboard
releaseName: dashboard
parameters:
- name: ingress.enabled
value: true
- name: ingress.hosts
value: '{dashboard.127.0.0.1.nip.io}'
- name: protocolHttp
value: true
- name: service.externalPort
value: 8080
- name: serviceAccount.create
value: true
- name: rbac.clusterReadOnlyRole
value: true

- type: helm
ref: https://charts.bitnami.com/bitnami
chart: solr
releaseName: solr
parameters:
- name: ingress.enabled
value: true
- name: ingress.hostname
value: solr.127.0.0.1.nip.io

- type: helm
ref: [email protected]:Blueshoe/buzzword-charts.git
targetRevision: HEAD # only relevant for git
releaseName: buzzword-counter # Release name override (defaults to application name)
path: buzzword-counter
# valueFiles: # Helm values (files) relative to 'path'
# - helm_vars/development/values.development.yaml

# - type: kustomize
# - type: directory
# ref: [email protected]:Blueshoe/buzzword-charts.git
# targetRevision: HEAD
# path: .
# recursive: true
ref: [email protected]:Blueshoe/mumpiz-helm.git
path: mumpiz
releaseName: mumpiz
helmPlugins:
- secret
valueFiles: # Helm values (files) relative to 'path'
- helm_vars/development/values.development.yaml
- helm_vars/development/secrets.development.yaml
- helm_vars/development/values.shared.yaml
4 changes: 0 additions & 4 deletions getdeck/api/get.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,6 @@ def run_deck(
# 1.b check or set up local cluster
if not k8s_provider.exists():
k8s_provider.create()
logger.info(
f"Create a new cluster with provider {k8s_provider.k8s_provider_type} "
f"named {k8s_provider.display_name}"
)
logger.info(f"Published ports are: {k8s_provider.get_ports()}")
cluster_created = True
else:
Expand Down
3 changes: 3 additions & 0 deletions getdeck/api/remove.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ def remove_deck(
k8s_delete_object(config, file.content, generated_deck.namespace)
if progress_callback:
progress_callback(max(50, int(i / total * 50) - 1))
except RuntimeError as e:
logger.error(f"There was an error removing a workload: {e}")
continue
except Exception as e:
logger.error(f"There was an error removing the workload: {e}")
raise e
Expand Down
3 changes: 2 additions & 1 deletion getdeck/deckfile/file.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ class DeckfileHelmSource(BaseModel):
releaseName: str
valueFiles: List[str] = ["values.yaml"]
helmArgs: List[str] = None
helmPlugins: List[str] = None


class DeckfileDirectorySource(BaseModel):
Expand All @@ -54,7 +55,7 @@ class DeckfileDirectorySource(BaseModel):

class DeckfileDeck(BaseModel):
name: str
namespace: str
namespace: str = "default"
sources: List[Union[DeckfileHelmSource, DeckfileDirectorySource]]


Expand Down
122 changes: 85 additions & 37 deletions getdeck/k8s.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import logging
import re
from typing import Callable, Any, Tuple, List
from typing import Callable, Any, Tuple, List, Optional

from getdeck.configuration import ClientConfiguration

Expand All @@ -14,46 +14,55 @@ def k8s_create_or_patch(

api = k8s_select_api(config, obj)
try:
res = k8s_call_api(api, "create", obj, namespace, **kwargs)
logger.debug(
f"Kubernetes: {k8s_describe_object(obj)} created with uid={res.metadata.uid}"
)
res = k8s_call_api(config, api, "create", obj, namespace, **kwargs)
if api:
logger.debug(
f"Kubernetes: {k8s_describe_object(obj)} created with uid={res.metadata.uid}"
)
except ApiException as e:
if e.reason != "Conflict":
raise
try:
res = k8s_call_api(api, "patch", obj, namespace, **kwargs)
logger.debug(
f"Kubernetes: {k8s_describe_object(obj)} patched with uid={res.metadata.uid}"
)
res = k8s_call_api(config, api, "patch", obj, namespace, **kwargs)
if api:
logger.debug(
f"Kubernetes: {k8s_describe_object(obj)} patched with uid={res.metadata.uid}"
)
except ApiException as e:
if e.reason != "Unprocessable Entity":
logger.error(f"Error installing object: {e.reason}")
logger.error(
f"Error installing object {obj['metadata']['name']}: {e.reason}"
)
raise
try:
# try again
logger.debug(
f"Kubernetes: replacing {k8s_describe_object(obj)} "
f"failed. Attempting deletion and recreation."
)
res = k8s_call_api(api, "delete", obj, namespace, **kwargs)
logger.debug(f"Kubernetes: {k8s_describe_object(obj)} deleted")
res = k8s_call_api(api, "create", obj, namespace, **kwargs)
logger.debug(
f"Kubernetes: {k8s_describe_object(obj)} created with uid={res.metadata.uid}"
)
res = k8s_call_api(config, api, "delete", obj, namespace, **kwargs)
if api:
logger.debug(f"Kubernetes: {k8s_describe_object(obj)} deleted")
res = k8s_call_api(config, api, "create", obj, namespace, **kwargs)
if api:
logger.debug(
f"Kubernetes: {k8s_describe_object(obj)} created with uid={res.metadata.uid}"
)
except Exception as ex:
logger.error(
f"Kubernetes: failure updating {k8s_describe_object(obj)}: {ex}"
)
if api:
logger.error(
f"Kubernetes: failure updating {k8s_describe_object(obj)}: {ex}"
)
raise RuntimeError(ex)
except ValueError as e:
logger.warning(
f"Error installing object: {e}. This is probably caused by a skew in the Kubernetes version."
f"Error installing object {obj['metadata']['name']}: {e}. "
f"This is probably caused by a skew in the Kubernetes version."
)
except ValueError as e:
logger.warning(
f"Error installing object: {e}. This is probably caused by a skew in the Kubernetes version."
f"Error installing object {obj['metadata']['name']}: {e}. "
f"This is probably caused by a skew in the Kubernetes version."
)


Expand All @@ -62,7 +71,7 @@ def k8s_delete_object(config: ClientConfiguration, obj, namespace, **kwargs) ->

api = k8s_select_api(config, obj)
try:
k8s_call_api(api, "delete", obj, namespace, **kwargs)
k8s_call_api(config, api, "delete", obj, namespace, **kwargs)
logger.debug(f"Kubernetes: {k8s_describe_object(obj)} deleted")
return True
except ApiException as e:
Expand All @@ -74,7 +83,7 @@ def k8s_delete_object(config: ClientConfiguration, obj, namespace, **kwargs) ->
raise RuntimeError(e)


def k8s_select_api(config: ClientConfiguration, obj) -> Callable:
def k8s_select_api(config: ClientConfiguration, obj) -> Optional[Callable]:
group, _, version = obj["apiVersion"].partition("/")
if version == "":
version = group
Expand All @@ -88,31 +97,70 @@ def k8s_select_api(config: ClientConfiguration, obj) -> Callable:
logger.debug(f"Selecting api {api}")
k8s_api = config.get_k8s_api(api)
if k8s_api is None:
raise ValueError(f"Kubernetes API {api} not available")
logger.debug(f"Kubernetes API {api} not available. Error causing object: {obj}")
return None
return k8s_api


def k8s_call_api(api, action, obj, namespace: str, **args) -> Any:
def k8s_call_api(
config: ClientConfiguration, api, action, obj, namespace: str, **args
) -> Any:
kind = convert_camel_2_snake(obj["kind"])
try:
namespace = obj["metadata"]["namespace"]
except KeyError:
# take the namespace passed as installation target
pass
_func = f"{action}_{kind}"
if hasattr(api, _func):
function = getattr(api, _func)
if api is None:
# api could not be found, try a manual REST call
group, _, version = obj["apiVersion"].partition("/")
kind_plural = {"ingress": "ingresses"}.get(
obj["kind"].lower(), f"{obj['kind']}s"
)
logger.debug(
f"Running a REST {action} operation for {obj['kind']} "
f"/apis/{obj['apiVersion']}/namespaces/{namespace}/{kind_plural}/"
)
if action == "create":
return config.K8S_CORE_API.api_client.call_api(
f"/apis/{obj['apiVersion']}/namespaces/{namespace}/{kind_plural}/",
"POST",
body=obj,
)
elif action == "patch":
return config.K8S_CORE_API.api_client.call_api(
f"/apis/{obj['apiVersion']}/namespaces/{namespace}/{kind_plural}/{obj['metadata']['name']}",
"PUT",
body=obj,
)
elif action == "delete":
from kubernetes.client.models.v1_delete_options import V1DeleteOptions

delobj = V1DeleteOptions()
return config.K8S_CORE_API.api_client.call_api(
f"/apis/{obj['apiVersion']}/namespaces/{namespace}/{kind_plural}/{obj['metadata']['name']}",
"DELETE",
body=delobj,
)
else:
raise ValueError(f"The action {action} is not supported at the moment")

else:
_func = f"{action}_namespaced_{kind}"
function = getattr(api, _func)
args["namespace"] = namespace
if "create" not in _func:
args["name"] = obj["metadata"]["name"]
if "delete" in _func:
from kubernetes.client.models.v1_delete_options import V1DeleteOptions

obj = V1DeleteOptions()
return function(body=obj, **args)
if hasattr(api, _func):
function = getattr(api, _func)
else:
_func = f"{action}_namespaced_{kind}"
function = getattr(api, _func)
args["namespace"] = namespace
if "create" not in _func:
args["name"] = obj["metadata"]["name"]
if "delete" in _func:
from kubernetes.client.models.v1_delete_options import V1DeleteOptions

obj = V1DeleteOptions()

return function(body=obj, **args)


def k8s_describe_object(obj: dict) -> str:
Expand Down
5 changes: 4 additions & 1 deletion getdeck/provider/k3d/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,10 @@ def create(self):
temp.flush()
arguments.extend(["--config", temp.name])
logger.debug(arguments)
self._execute(arguments, print_output=True)
self._execute(
arguments,
print_output=True if logger.level == logging.DEBUG else False,
)
except Exception as e:
logger.debug(traceback.print_exc())
raise e
Expand Down
4 changes: 2 additions & 2 deletions getdeck/sources/tooler.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,9 @@ def build_user_container(config: ClientConfiguration):
RUN addgroup -g ${GROUP_ID} -S tooler && adduser -u ${USER_ID} -S tooler -G tooler
RUN chown ${USER_ID}:${GROUP_ID} /sources
RUN chown ${USER_ID}:${GROUP_ID} /output
WORKDIR /sources
USER tooler
"""
USER tooler"""
).encode("utf-8")
)
build_args = {"USER_ID": str(uid), "GROUP_ID": str(gid)}
Expand Down
2 changes: 0 additions & 2 deletions tooler/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
FROM alpine
LABEL maintainer="Schille"

# Ignore to update versions here
# docker build --no-cache --build-arg KUBECTL_VERSION=${tag} --build-arg HELM_VERSION=${helm} --build-arg KUSTOMIZE_VERSION=${kustomize_version} -t ${image}:${tag} .
ARG HELM_VERSION=3.8.1
ARG KUBECTL_VERSION=1.23.5
ARG KUSTOMIZE_VERSION=v3.8.1
Expand Down

0 comments on commit 351a469

Please sign in to comment.