diff --git a/Dockerfile b/Dockerfile index b5ab31e..470a555 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,4 +9,5 @@ RUN apt-get update RUN apt-get install -y kubectl COPY . /app +RUN pip3 install -e . CMD bruce-operator watch diff --git a/Pipfile b/Pipfile index f07155e..ea92071 100644 --- a/Pipfile +++ b/Pipfile @@ -12,9 +12,10 @@ click = "*" logme = "*" background = "*" docopt = "*" -bruce-operator = {editable = true, path = "."} +kubeconfig = "*" [dev-packages] +bruce-operator = {editable = true, path = "."} "flake8" = "*" black = "*" diff --git a/Pipfile.lock b/Pipfile.lock index b71b6d5..d21ecce 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "f234e7a7715fe7dfaf399ba918c77088dca9ee4df199158a9572fb6f83e64ace" + "sha256": "de5a76d65e30ec8c0b2bc15d1392f2bbd7b39e77f986268ff6d605b6fd5c6824" }, "pipfile-spec": 6, "requires": { @@ -47,10 +47,6 @@ "markers": "python_version >= '3'", "version": "==1.0.2" }, - "bruce-operator": { - "editable": true, - "path": "." - }, "cachetools": { "hashes": [ "sha256:90f1d559512fc073483fe573ef5ceb39bf6ad3d39edc98dc55178a2b2b176fa3", @@ -185,6 +181,13 @@ "index": "pypi", "version": "==2.10" }, + "kubeconfig": { + "hashes": [ + "sha256:393bdf6b03d4830c11c8d47e267fd65a3bed82518492b20b9fe554c7cd6c8111" + ], + "index": "pypi", + "version": "==1.0.1" + }, "kubernetes": { "hashes": [ "sha256:5ee6e2e949ca800ad8a73da6f67c2a637c2c803945b006e6105beae83e43b273", @@ -341,6 +344,10 @@ "index": "pypi", "version": "==18.9b0" }, + "bruce-operator": { + "editable": true, + "path": "." + }, "click": { "hashes": [ "sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", diff --git a/bruce_operator/__main__.py b/bruce_operator/__main__.py index 7bb402b..d2faac2 100644 --- a/bruce_operator/__main__.py +++ b/bruce_operator/__main__.py @@ -2,6 +2,7 @@ Usage: bruce-operator watch + bruce-operator fetch [--buildpack=] bruce-operator (-h | --help) Options: @@ -20,6 +21,10 @@ def main(): if args["watch"]: watch() + if args["fetch"]: + print("fetching") + exit() + if __name__ == "__main__": main() diff --git a/bruce_operator/core.py b/bruce_operator/core.py index b40c6d0..ceee2e4 100644 --- a/bruce_operator/core.py +++ b/bruce_operator/core.py @@ -1,14 +1,22 @@ import time import json +from uuid import uuid4 from functools import lru_cache import logme import kubernetes import background +from kubeconfig import KubeConfig from kubernetes.client.configuration import Configuration from kubernetes.client.api_client import ApiClient -from .env import WATCH_NAMESPACE, API_GROUP, API_VERSION +from .env import ( + WATCH_NAMESPACE, + API_GROUP, + API_VERSION, + OPERATOR_IMAGE, + KUBECONFIG_PATH, +) from .kubectl import kubectl # https://github.com/kubernetes-client/python/blob/master/examples/create_thirdparty_resource.md @@ -82,6 +90,27 @@ class Operator: except kubernetes.client.rest.ApiException: return None + def spawn_self(self, cmd, label, env=None): + if env is None: + env = {} + + # TODO: ENV + _hash = uuid4().hex + return kubectl( + f"run bruce-operator-{label}-{_hash} --image={OPERATOR_IMAGE} -n {WATCH_NAMESPACE} --restart=Never --quiet=True --record=True --image-pull-policy=Always -- bruce-operator {cmd}" + ) + + def ensure_kube_config(self): + if IN_KUBERNETES: + host = os.environ["KUBERNETES_SERVICE_HOST"] + port = os.environ["KUBERNETES_SERVICE_PORT"] + conf = KubeConfig() + kc.set_cluster( + name='the-cluster', + server='https://{host}:{port}' + certificate_authority=CERT_LOCATION, + ) + def ensure_resource_definitions(self): # Create Buildpacks resource. self.logger.info("Ensuring Buildpack resource definitions...") @@ -97,12 +126,13 @@ class Operator: self.logger.info("Ensuring Buildpack volume resource...") kubectl(f"apply -f ./deploy/buildpacks-volume.yml -n {WATCH_NAMESPACE}") - def fetch_buildpack(self, buildpack_name): + def spawn_fetch_buildpack(self, buildpack_name): self.logger.info(f"Pretending to fetch {buildpack_name!r} buildpack!") + self.spawn_self(f"fetch --buildpack={buildpack_name}", label="fetch") def fetch_buildpacks(self): for buildpack in self.installed_buildpacks: - self.fetch_buildpack(buildpack["metadata"]["name"]) + self.spawn_fetch_buildpack(buildpack["metadata"]["name"]) def watch(self): self.logger.info("Pretending to watch...") diff --git a/bruce_operator/core.py.c46fb31d35cc0636f376c70f90af377f.py b/bruce_operator/core.py.c46fb31d35cc0636f376c70f90af377f.py deleted file mode 100644 index b40c6d0..0000000 --- a/bruce_operator/core.py.c46fb31d35cc0636f376c70f90af377f.py +++ /dev/null @@ -1,117 +0,0 @@ -import time -import json -from functools import lru_cache - -import logme -import kubernetes -import background -from kubernetes.client.configuration import Configuration -from kubernetes.client.api_client import ApiClient - -from .env import WATCH_NAMESPACE, API_GROUP, API_VERSION -from .kubectl import kubectl - -# https://github.com/kubernetes-client/python/blob/master/examples/create_thirdparty_resource.md - - -@logme.log -class Operator: - def __init__(self, api_client=None): - - # Load Kube configuration into module (ugh). - try: - kubernetes.config.load_kube_config() - except FileNotFoundError: - pass - - # Setup clients. - self.client = kubernetes.client.CoreV1Api() - self.custom_client = kubernetes.client.CustomObjectsApi(self.client.api_client) - - # Ensure resource definitions. - self.ensure_resource_definitions() - self.ensure_volumes() - self.fetch_buildpacks() - # print(self.installed_buildpacks) - # exit() - - @property - def installed_buildpacks(self): - - group = API_GROUP # str | The custom resource's group name - version = API_VERSION # str | The custom resource's version - namespace = WATCH_NAMESPACE # str | The custom resource's namespace - plural = ( - "buildpacks" - ) # str | The custom resource's plural name. For TPRs this would be lowercase plural kind. - pretty = ( - "true" - ) # str | If 'true', then the output is pretty printed. (optional) - watch = ( - False - ) # bool | Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. (optional) - - try: - api_response = self.custom_client.list_namespaced_custom_object( - group, version, namespace, plural, pretty=pretty, watch=watch - ) - return api_response["items"] - except kubernetes.client.rest.ApiException: - return None - - @property - def installed_apps(self): - group = "bruce.kennethreitz.org" # str | The custom resource's group name - version = "v1alpha1" # str | The custom resource's version - namespace = WATCH_NAMESPACE # str | The custom resource's namespace - plural = ( - "apps" - ) # str | The custom resource's plural name. For TPRs this would be lowercase plural kind. - pretty = ( - "true" - ) # str | If 'true', then the output is pretty printed. (optional) - watch = ( - False - ) # bool | Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. (optional) - - try: - api_response = self.custom_client.list_namespaced_custom_object( - group, version, namespace, plural, pretty=pretty, watch=watch - ) - return api_response["items"] - except kubernetes.client.rest.ApiException: - return None - - def ensure_resource_definitions(self): - # Create Buildpacks resource. - self.logger.info("Ensuring Buildpack resource definitions...") - kubectl( - f"apply -f ./deploy/buildpack-resource-definition.yml -n {WATCH_NAMESPACE}" - ) - - # Create Apps resource. - self.logger.info("Ensuring App resource definitions...") - kubectl(f"apply -f ./deploy/app-resource-definition.yml -n {WATCH_NAMESPACE}") - - def ensure_volumes(self): - self.logger.info("Ensuring Buildpack volume resource...") - kubectl(f"apply -f ./deploy/buildpacks-volume.yml -n {WATCH_NAMESPACE}") - - def fetch_buildpack(self, buildpack_name): - self.logger.info(f"Pretending to fetch {buildpack_name!r} buildpack!") - - def fetch_buildpacks(self): - for buildpack in self.installed_buildpacks: - self.fetch_buildpack(buildpack["metadata"]["name"]) - - def watch(self): - self.logger.info("Pretending to watch...") - time.sleep(5) - - -operator = Operator() - - -def watch(): - while True: - operator.watch() diff --git a/bruce_operator/env.py b/bruce_operator/env.py index 68a749d..b5cc1d5 100644 --- a/bruce_operator/env.py +++ b/bruce_operator/env.py @@ -3,3 +3,11 @@ import os WATCH_NAMESPACE = os.environ.get("WATCH_NAMESPACE", "bruce") API_VERSION = "v1alpha1" API_GROUP = "bruce.kennethreitz.org" + +BUILDPACKS_DIR = "/opt/buildpacks" +APPCACHE_DIR = "/opt/caches" +OPERATOR_IMAGE = "bruceproject/operator:latest" +TOKEN_LOCATION = "/var/run/secrets/kubernetes.io/serviceaccount/token" +CERT_LOCATION = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt" +IN_KUBERNETES = os.path.isfile(TOKEN_LOCATION) +KUBECONFIG_PATH = os.path.expanduser("~/.kube/config") diff --git a/bruce_operator/kubectl.py b/bruce_operator/kubectl.py index 66bf6c9..691dce5 100644 --- a/bruce_operator/kubectl.py +++ b/bruce_operator/kubectl.py @@ -1,13 +1,24 @@ +import os import json import logme import delegator +from .env import TOKEN_LOCATION, IN_KUBERNETES, CERT_LOCATION + @logme.log def kubectl(cmd, as_json=True, raise_on_error=True, logger=None): json_flag = "-o=json" if as_json else "" - cmd = f"kubectl {cmd} {json_flag}" + if IN_KUBERNETES: + host = os.environ["KUBERNETES_SERVICE_HOST"] + port = os.environ["KUBERNETES_SERVICE_PORT"] + with open(TOKEN_LOCATION, "r") as f: + token = f.read() + server_flag = f"--server=https://{host}:{port} --token={token} --certificate-authority={CERT_LOCATION}" + else: + server_flag = "" + cmd = f"kubectl {json_flag} {server_flag} {cmd}" logger.debug(f"$ {cmd}") c = delegator.run(cmd) diff --git a/setup.py b/setup.py index 4936772..738858a 100644 --- a/setup.py +++ b/setup.py @@ -17,7 +17,7 @@ DESCRIPTION = "My short description for my project." URL = "https://github.com/bruce-project/bruce-operator" EMAIL = "me@example.com" AUTHOR = "Kenneth Reitz" -REQUIRES_PYTHON = ">=3.7.0" +REQUIRES_PYTHON = ">=3.6.0" VERSION = None # What packages are required for this module to be executed? diff --git a/test.bat b/test.bat new file mode 100644 index 0000000..1e3b8bc --- /dev/null +++ b/test.bat @@ -0,0 +1,4 @@ +docker build --tag bruceproject/operator . +docker push bruceproject/operator +kubectl delete -f .\deploy\operator.yml -n bruce +kubectl create -f .\deploy\operator.yml -n bruce