From 081cb744cc52992283abd3def92286f28cf0d54b Mon Sep 17 00:00:00 2001 From: "Homayoon (Hue) Alimohammadi" Date: Tue, 26 Nov 2024 13:32:28 +0400 Subject: [PATCH 1/4] Fix url --- ops/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ops/setup.py b/ops/setup.py index 4ec8486..78301fe 100644 --- a/ops/setup.py +++ b/ops/setup.py @@ -22,7 +22,7 @@ keywords=["juju", "charming", "kubernetes", "ops", "framework", "interface"], name="ops.interface_kube_control", packages=find_namespace_packages(include=["ops.*"]), - url="https://github.com/juju-solutions/interface-kube-control/blob/HEAD/ops", + url="https://github.com/charmed-kubernetes/interface-kube-control/blob/HEAD/ops", version="0.2.0", zip_safe=True, install_requires=[ From 18b6818eee9c4f17adb6ffbaf5f7752535bd24d6 Mon Sep 17 00:00:00 2001 From: "Homayoon (Hue) Alimohammadi" Date: Tue, 26 Nov 2024 19:19:00 +0400 Subject: [PATCH 2/4] Refactor auth credentials --- ops/ops/interface_kube_control/consts.py | 6 +++++ ops/ops/interface_kube_control/model.py | 31 ++++++++++++++++++---- ops/ops/interface_kube_control/provides.py | 16 ++++++++--- ops/ops/interface_kube_control/requires.py | 26 +++++++++--------- 4 files changed, 57 insertions(+), 22 deletions(-) create mode 100644 ops/ops/interface_kube_control/consts.py diff --git a/ops/ops/interface_kube_control/consts.py b/ops/ops/interface_kube_control/consts.py new file mode 100644 index 0000000..126c535 --- /dev/null +++ b/ops/ops/interface_kube_control/consts.py @@ -0,0 +1,6 @@ +from typing import Final + +CLIENT_TOKEN_SECERT_FIELD: Final[str] = "client-token" +KUBELET_TOKEN_SECRET_FIELD: Final[str] = "kubelet-token" +PROXY_TOKEN_SECRET_FIELD: Final[str] = "proxy-token" +SECRET_LABEL_FORMAT: Final[str] = "{user}-creds" diff --git a/ops/ops/interface_kube_control/model.py b/ops/ops/interface_kube_control/model.py index acc5c61..cc825db 100644 --- a/ops/ops/interface_kube_control/model.py +++ b/ops/ops/interface_kube_control/model.py @@ -1,9 +1,15 @@ -from pydantic import Field, AnyHttpUrl, BaseModel, Json +from pydantic import Field, AnyHttpUrl, BaseModel, Json, SecretStr import json import ops from typing import List, Dict, Optional import re +from interface_kube_control.consts import ( + SECRET_LABEL_FORMAT, + CLIENT_TOKEN_SECERT_FIELD, + KUBELET_TOKEN_SECRET_FIELD, + PROXY_TOKEN_SECRET_FIELD, +) class _ValidatedStr: def __init__(self, value, *groups) -> None: @@ -94,22 +100,22 @@ class Creds(BaseModel): secret_id: Optional[str] = Field(alias="secret-id", default=None) def _get_secret_content(self, model: ops.Model, user: str) -> Dict[str, str]: - secret = model.get_secret(id=self.secret_id, label=f"{user}-creds") + secret = model.get_secret(id=self.secret_id, label=SECRET_LABEL_FORMAT.format(user=user)) return secret.get_content(refresh=True) def load_client_token(self, model: ops.Model, user: str) -> str: if self.secret_id: - return self._get_secret_content(model, user)["client-token"] + return self._get_secret_content(model, user)[CLIENT_TOKEN_SECERT_FIELD] return self.client_token def load_kubelet_token(self, model, user: str) -> str: if self.secret_id: - return self._get_secret_content(model, user)["kubelet-token"] + return self._get_secret_content(model, user)[KUBELET_TOKEN_SECRET_FIELD] return self.kubelet_token def load_proxy_token(self, model, user: str) -> str: if self.secret_id: - return self._get_secret_content(model, user)["proxy-token"] + return self._get_secret_content(model, user)[PROXY_TOKEN_SECRET_FIELD] return self.proxy_token @@ -139,3 +145,18 @@ def get_ca_certificate(self, model: ops.Model) -> Optional[bytes]: return secret.get_content(refresh=True)["ca-certificate"].encode() except ops.SecretNotFoundError: return None + +class AuthCredentials(BaseModel): + """ + AuthCredentials is a Pydantic model that holds authentication credentials for accessing a Kubernetes cluster. + + Attributes: + user (str): The username for authentication. + kubelet_token (SecretStr): The token used for authenticating to the kubelet. + proxy_token (SecretStr): The token used for authenticating to the proxy. + client_token (SecretStr): The token used for authenticating to the client. + """ + user: str + kubelet_token: SecretStr + proxy_token: SecretStr + client_token: SecretStr diff --git a/ops/ops/interface_kube_control/provides.py b/ops/ops/interface_kube_control/provides.py index 4ad120e..fbc97be 100644 --- a/ops/ops/interface_kube_control/provides.py +++ b/ops/ops/interface_kube_control/provides.py @@ -5,6 +5,13 @@ from ops import CharmBase, Relation, SecretNotFoundError, Unit from typing import Generator, List, Tuple +from interface_kube_control.consts import ( + CLIENT_TOKEN_SECERT_FIELD, + KUBELET_TOKEN_SECRET_FIELD, + PROXY_TOKEN_SECRET_FIELD, + SECRET_LABEL_FORMAT, +) + log = logging.getLogger("KubeControlProvides") @@ -214,11 +221,12 @@ def sign_auth_request( if 1 in request.schema_vers and request_relation: # Requesting unit can use schema 1, use juju secrets content = { - "client-token": client_token, - "kubelet-token": kubelet_token, - "proxy-token": proxy_token, + CLIENT_TOKEN_SECERT_FIELD: client_token, + KUBELET_TOKEN_SECRET_FIELD: kubelet_token, + PROXY_TOKEN_SECRET_FIELD: proxy_token, } - label = f"{request.user}-creds" + + label = SECRET_LABEL_FORMAT.format(user=request.user) description = f"Credentials for {request.user}" secret = self.refresh_secret_content(label, content, description) if secret.id: diff --git a/ops/ops/interface_kube_control/requires.py b/ops/ops/interface_kube_control/requires.py index ca03f8c..02f6fda 100644 --- a/ops/ops/interface_kube_control/requires.py +++ b/ops/ops/interface_kube_control/requires.py @@ -14,8 +14,8 @@ import yaml from backports.cached_property import cached_property -from .model import AuthRequest, Creds, Data, Taint, Label -from pydantic import ValidationError +from .model import AuthCredentials, AuthRequest, Creds, Data, Taint, Label +from pydantic import SecretStr, ValidationError from ops.charm import CharmBase, RelationBrokenEvent from ops.framework import Object @@ -84,7 +84,7 @@ def create_kubeconfig( creds = self.get_auth_credentials(k8s_user) endpoints = self.get_api_endpoints() server = endpoints[0] if endpoints else None - token = creds["client_token"] if creds else None + token = creds.client_token if creds else None if ca_content := self.get_ca_certificate(): ca_b64 = base64.b64encode(ca_content).decode("utf-8") @@ -137,23 +137,23 @@ def get_ca_certificate(self) -> Optional[bytes]: return self._data.get_ca_certificate(self.model) - def get_auth_credentials(self, user) -> Optional[Mapping[str, str]]: + def get_auth_credentials(self, user) -> Optional[AuthCredentials]: """Return the authentication credentials.""" if not self.is_ready: return None - users: Dict[str, Creds] = self._data.creds + users_to_creds: Dict[str, Creds] = self._data.creds - if creds := users.get(user): - return { - "user": user, - "kubelet_token": creds.load_kubelet_token(self.model, user), - "proxy_token": creds.load_proxy_token(self.model, user), - "client_token": creds.load_client_token(self.model, user), - } + if creds := users_to_creds.get(user): + return AuthCredentials( + user=user, + kubelet_token=SecretStr(creds.load_kubelet_token(self.model, user)), + proxy_token=SecretStr(creds.load_proxy_token(self.model, user)), + client_token=SecretStr(creds.load_client_token(self.model, user)), + ) return None - def get_dns(self) -> Mapping[str, str]: + def get_dns(self) -> Mapping[str, str|None]: """ Return DNS info provided by the control-plane. """ From 086ed2e4664cf9855d050f802010b69d92917ffb Mon Sep 17 00:00:00 2001 From: "Homayoon (Hue) Alimohammadi" Date: Tue, 26 Nov 2024 19:54:48 +0400 Subject: [PATCH 3/4] Fix lint --- ops/ops/interface_kube_control/model.py | 27 ++++++++++++++-------- ops/ops/interface_kube_control/provides.py | 2 +- ops/ops/interface_kube_control/requires.py | 10 ++++---- 3 files changed, 23 insertions(+), 16 deletions(-) diff --git a/ops/ops/interface_kube_control/model.py b/ops/ops/interface_kube_control/model.py index cc825db..0d8e749 100644 --- a/ops/ops/interface_kube_control/model.py +++ b/ops/ops/interface_kube_control/model.py @@ -1,16 +1,17 @@ -from pydantic import Field, AnyHttpUrl, BaseModel, Json, SecretStr +from pydantic import Field, AnyHttpUrl, BaseModel, Json import json import ops from typing import List, Dict, Optional import re -from interface_kube_control.consts import ( +from .consts import ( SECRET_LABEL_FORMAT, CLIENT_TOKEN_SECERT_FIELD, KUBELET_TOKEN_SECRET_FIELD, PROXY_TOKEN_SECRET_FIELD, ) + class _ValidatedStr: def __init__(self, value, *groups) -> None: self._str = value @@ -100,7 +101,10 @@ class Creds(BaseModel): secret_id: Optional[str] = Field(alias="secret-id", default=None) def _get_secret_content(self, model: ops.Model, user: str) -> Dict[str, str]: - secret = model.get_secret(id=self.secret_id, label=SECRET_LABEL_FORMAT.format(user=user)) + secret = model.get_secret( + id=self.secret_id, + label=SECRET_LABEL_FORMAT.format(user=user), + ) return secret.get_content(refresh=True) def load_client_token(self, model: ops.Model, user: str) -> str: @@ -146,17 +150,20 @@ def get_ca_certificate(self, model: ops.Model) -> Optional[bytes]: except ops.SecretNotFoundError: return None + class AuthCredentials(BaseModel): """ - AuthCredentials is a Pydantic model that holds authentication credentials for accessing a Kubernetes cluster. + AuthCredentials is a Pydantic model that holds authentication credentials + for accessing a Kubernetes cluster. Attributes: user (str): The username for authentication. - kubelet_token (SecretStr): The token used for authenticating to the kubelet. - proxy_token (SecretStr): The token used for authenticating to the proxy. - client_token (SecretStr): The token used for authenticating to the client. + kubelet_token (str): The token used for authenticating to the kubelet. + proxy_token (str): The token used for authenticating to the proxy. + client_token (str): The token used for authenticating to the client. """ + user: str - kubelet_token: SecretStr - proxy_token: SecretStr - client_token: SecretStr + kubelet_token: str + proxy_token: str + client_token: str diff --git a/ops/ops/interface_kube_control/provides.py b/ops/ops/interface_kube_control/provides.py index fbc97be..b876e2f 100644 --- a/ops/ops/interface_kube_control/provides.py +++ b/ops/ops/interface_kube_control/provides.py @@ -5,7 +5,7 @@ from ops import CharmBase, Relation, SecretNotFoundError, Unit from typing import Generator, List, Tuple -from interface_kube_control.consts import ( +from .consts import ( CLIENT_TOKEN_SECERT_FIELD, KUBELET_TOKEN_SECRET_FIELD, PROXY_TOKEN_SECRET_FIELD, diff --git a/ops/ops/interface_kube_control/requires.py b/ops/ops/interface_kube_control/requires.py index 02f6fda..701faec 100644 --- a/ops/ops/interface_kube_control/requires.py +++ b/ops/ops/interface_kube_control/requires.py @@ -15,7 +15,7 @@ import yaml from backports.cached_property import cached_property from .model import AuthCredentials, AuthRequest, Creds, Data, Taint, Label -from pydantic import SecretStr, ValidationError +from pydantic import ValidationError from ops.charm import CharmBase, RelationBrokenEvent from ops.framework import Object @@ -147,13 +147,13 @@ def get_auth_credentials(self, user) -> Optional[AuthCredentials]: if creds := users_to_creds.get(user): return AuthCredentials( user=user, - kubelet_token=SecretStr(creds.load_kubelet_token(self.model, user)), - proxy_token=SecretStr(creds.load_proxy_token(self.model, user)), - client_token=SecretStr(creds.load_client_token(self.model, user)), + kubelet_token=creds.load_kubelet_token(self.model, user), + proxy_token=creds.load_proxy_token(self.model, user), + client_token=creds.load_client_token(self.model, user), ) return None - def get_dns(self) -> Mapping[str, str|None]: + def get_dns(self) -> Mapping[str, Optional[str]]: """ Return DNS info provided by the control-plane. """ From 485c5e8c52c94527a0b00c29ad8aa608e74edb5c Mon Sep 17 00:00:00 2001 From: "Homayoon (Hue) Alimohammadi" Date: Mon, 9 Dec 2024 19:32:41 +0400 Subject: [PATCH 4/4] Switch back url to Juju solutions --- ops/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ops/setup.py b/ops/setup.py index 78301fe..4ec8486 100644 --- a/ops/setup.py +++ b/ops/setup.py @@ -22,7 +22,7 @@ keywords=["juju", "charming", "kubernetes", "ops", "framework", "interface"], name="ops.interface_kube_control", packages=find_namespace_packages(include=["ops.*"]), - url="https://github.com/charmed-kubernetes/interface-kube-control/blob/HEAD/ops", + url="https://github.com/juju-solutions/interface-kube-control/blob/HEAD/ops", version="0.2.0", zip_safe=True, install_requires=[