mirror of
https://github.com/kennethreitz/bruce-operator.git
synced 2026-06-05 07:06:12 +00:00
let's do this
This commit is contained in:
Vendored
+5
-1
@@ -1,3 +1,7 @@
|
||||
{
|
||||
"python.pythonPath": "C:\\Users\\me\\.virtualenvs\\bruce-server-_01-8y1n\\Scripts\\python.exe"
|
||||
"python.pythonPath": "C:\\Users\\me\\.virtualenvs\\bruce-operator-UKafV52I\\Scripts\\python.exe",
|
||||
"python.unitTest.promptToConfigure": false,
|
||||
"python.unitTest.pyTestEnabled": false,
|
||||
"python.unitTest.unittestEnabled": false,
|
||||
"python.unitTest.nosetestsEnabled": false
|
||||
}
|
||||
@@ -1,4 +1,12 @@
|
||||
FROM kennethreitz/pipenv
|
||||
|
||||
# Instlall kube-ctl.
|
||||
RUN apt-get update && apt-get install -y apt-transport-https
|
||||
RUN curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add -
|
||||
RUN touch /etc/apt/sources.list.d/kubernetes.list
|
||||
RUN echo "deb http://apt.kubernetes.io/ kubernetes-xenial main" | tee -a /etc/apt/sources.list.d/kubernetes.list
|
||||
RUN apt-get update
|
||||
RUN apt-get install -y kubectl
|
||||
|
||||
COPY . /app
|
||||
CMD python3 -m bruce_operator
|
||||
|
||||
@@ -10,8 +10,14 @@ pyyaml = "*"
|
||||
"jinja2" = "*"
|
||||
click = "*"
|
||||
logme = "*"
|
||||
background = "*"
|
||||
|
||||
[dev-packages]
|
||||
"flake8" = "*"
|
||||
black = "*"
|
||||
|
||||
[requires]
|
||||
python_version = "3.7"
|
||||
|
||||
[pipenv]
|
||||
allow_prereleases = true
|
||||
|
||||
Generated
+90
-14
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "32bf55fc946ce7a517b1424e1b1f14579e1b0782d66e9c73279d869a649a25ba"
|
||||
"sha256": "6ce8c782cb25701a9b5c8203f9deee1aeed26ca6cd732df4cda162dd986f149e"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {
|
||||
@@ -31,6 +31,14 @@
|
||||
],
|
||||
"version": "==0.24.0"
|
||||
},
|
||||
"background": {
|
||||
"hashes": [
|
||||
"sha256:8f64df9b1f1d4f91603f16d25f79691fa87eff62244d631b8b69cf121396b725",
|
||||
"sha256:b2fb684c150aaf1c4716686e7bd0e81a5c13db1852e7af48c01b98a3486a84c6"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.1.1"
|
||||
},
|
||||
"bnmutils": {
|
||||
"hashes": [
|
||||
"sha256:09d4fe9ec208b7f244a7d1954fc36209b84a1b7bbb87d85dc2cc69cb2fbccaa0",
|
||||
@@ -137,6 +145,13 @@
|
||||
"index": "pypi",
|
||||
"version": "==0.1.1"
|
||||
},
|
||||
"futures": {
|
||||
"hashes": [
|
||||
"sha256:51ecb45f0add83c806c68e4b06106f90db260585b25ef2abfcda0bd95c0132fd",
|
||||
"sha256:c4884a65654a7c45435063e14ae85280eb1f111d94e542396717ba9828c4337f"
|
||||
],
|
||||
"version": "==3.1.1"
|
||||
},
|
||||
"google-auth": {
|
||||
"hashes": [
|
||||
"sha256:9ca363facbf2622d9ba828017536ccca2e0f58bd15e659b52f312172f8815530",
|
||||
@@ -239,20 +254,14 @@
|
||||
},
|
||||
"pyyaml": {
|
||||
"hashes": [
|
||||
"sha256:3d7da3009c0f3e783b2c873687652d83b1bbfd5c88e9813fb7e5b03c0dd3108b",
|
||||
"sha256:3ef3092145e9b70e3ddd2c7ad59bdd0252a94dfe3949721633e41344de00a6bf",
|
||||
"sha256:40c71b8e076d0550b2e6380bada1f1cd1017b882f7e16f09a65be98e017f211a",
|
||||
"sha256:558dd60b890ba8fd982e05941927a3911dc409a63dcb8b634feaa0cda69330d3",
|
||||
"sha256:a7c28b45d9f99102fa092bb213aa12e0aaf9a6a1f5e395d36166639c1f96c3a1",
|
||||
"sha256:aa7dd4a6a427aed7df6fb7f08a580d68d9b118d90310374716ae90b710280af1",
|
||||
"sha256:bc558586e6045763782014934bfaf39d48b8ae85a2713117d16c39864085c613",
|
||||
"sha256:d46d7982b62e0729ad0175a9bc7e10a566fc07b224d2c79fafb5e032727eaa04",
|
||||
"sha256:d5eef459e30b09f5a098b9cea68bebfeb268697f78d647bd255a085371ac7f3f",
|
||||
"sha256:e01d3203230e1786cd91ccfdc8f8454c8069c91bee3962ad93b87a4b2860f537",
|
||||
"sha256:e170a9e6fcfd19021dd29845af83bb79236068bf5fd4df3327c1be18182b2531"
|
||||
"sha256:254bf6fda2b7c651837acb2c718e213df29d531eebf00edb54743d10bcb694eb",
|
||||
"sha256:3108529b78577327d15eec243f0ff348a0640b0c3478d67ad7f5648f93bac3e2",
|
||||
"sha256:3c17fb92c8ba2f525e4b5f7941d850e7a48c3a59b32d331e2502a3cdc6648e76",
|
||||
"sha256:8d6d96001aa7f0a6a4a95e8143225b5d06e41b1131044913fecb8f85a125714b",
|
||||
"sha256:c8a88edd93ee29ede719080b2be6cb2333dfee1dccba213b422a9c8e97f2967b"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.13"
|
||||
"version": "==4.2b4"
|
||||
},
|
||||
"requests": {
|
||||
"hashes": [
|
||||
@@ -298,5 +307,72 @@
|
||||
"version": "==0.53.0"
|
||||
}
|
||||
},
|
||||
"develop": {}
|
||||
"develop": {
|
||||
"appdirs": {
|
||||
"hashes": [
|
||||
"sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92",
|
||||
"sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e"
|
||||
],
|
||||
"version": "==1.4.3"
|
||||
},
|
||||
"attrs": {
|
||||
"hashes": [
|
||||
"sha256:10cbf6e27dbce8c30807caf056c8eb50917e0eaafe86347671b57254006c3e69",
|
||||
"sha256:ca4be454458f9dec299268d472aaa5a11f67a4ff70093396e1ceae9c76cf4bbb"
|
||||
],
|
||||
"version": "==18.2.0"
|
||||
},
|
||||
"black": {
|
||||
"hashes": [
|
||||
"sha256:817243426042db1d36617910df579a54f1afd659adb96fc5032fcf4b36209739",
|
||||
"sha256:e030a9a28f542debc08acceb273f228ac422798e5215ba2a791a6ddeaaca22a5"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==18.9b0"
|
||||
},
|
||||
"click": {
|
||||
"hashes": [
|
||||
"sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13",
|
||||
"sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==7.0"
|
||||
},
|
||||
"flake8": {
|
||||
"hashes": [
|
||||
"sha256:7253265f7abd8b313e3892944044a365e3f4ac3fcdcfb4298f55ee9ddf188ba0",
|
||||
"sha256:c7841163e2b576d435799169b78703ad6ac1bbb0f199994fc05f700b2a90ea37"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.5.0"
|
||||
},
|
||||
"mccabe": {
|
||||
"hashes": [
|
||||
"sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42",
|
||||
"sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"
|
||||
],
|
||||
"version": "==0.6.1"
|
||||
},
|
||||
"pycodestyle": {
|
||||
"hashes": [
|
||||
"sha256:682256a5b318149ca0d2a9185d365d8864a768a28db66a84a2ea946bcc426766",
|
||||
"sha256:6c4245ade1edfad79c3446fadfc96b0de2759662dc29d07d80a6f27ad1ca6ba9"
|
||||
],
|
||||
"version": "==2.3.1"
|
||||
},
|
||||
"pyflakes": {
|
||||
"hashes": [
|
||||
"sha256:08bd6a50edf8cffa9fa09a463063c425ecaaf10d1eb0335a7e8b1401aef89e6f",
|
||||
"sha256:8d616a382f243dbf19b54743f280b80198be0bca3a5396f1d2e1fca6223e8805"
|
||||
],
|
||||
"version": "==1.6.0"
|
||||
},
|
||||
"toml": {
|
||||
"hashes": [
|
||||
"sha256:380178cde50a6a79f9d2cf6f42a62a5174febe5eea4126fe4038785f1d888d42",
|
||||
"sha256:a7901919d3e4f92ffba7ff40a9d697e35bbbc8a8049fe8da742f34c83606d957"
|
||||
],
|
||||
"version": "==0.9.6"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+78
-43
@@ -1,67 +1,102 @@
|
||||
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
|
||||
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):
|
||||
self.watchers = []
|
||||
self.api_client = None
|
||||
|
||||
config = Configuration()
|
||||
if api_client:
|
||||
self.api_client = api_client
|
||||
else:
|
||||
if not config.api_client:
|
||||
config.api_client = ApiClient()
|
||||
self.api_client = config.api_client
|
||||
# Load Kube configuration into module (ugh).
|
||||
try:
|
||||
kubernetes.config.load_kube_config()
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
|
||||
def _build_url(self, api_version, kind, namespace=None):
|
||||
resource = self.lookup_resource(api_version, kind)
|
||||
if not resource:
|
||||
print("ERR", api_version, kind)
|
||||
exit
|
||||
# Setup clients.
|
||||
self.client = kubernetes.client.CoreV1Api()
|
||||
self.custom_client = kubernetes.client.CustomObjectsApi(self.client.api_client)
|
||||
|
||||
api_prefix = "api" if api_version == "v1" else "apis"
|
||||
if resource["namespaced"] and namespace:
|
||||
if not namespace:
|
||||
namespace = "default"
|
||||
return (
|
||||
f"/{api_prefix}/{api_version}/namespaces/{namespace}/{resource['name']}"
|
||||
# Ensure resource definitions.
|
||||
self.ensure_resource_definitions()
|
||||
# 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
|
||||
|
||||
return f"/{api_prefix}/{api_version}/{resource['name']}"
|
||||
@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)
|
||||
|
||||
def register_watcher(self, callback):
|
||||
self.watchers.append(callback)
|
||||
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("apply -f ./deploy/buildpack-resource-definition.yml")
|
||||
|
||||
# Create Apps resource.
|
||||
self.logger.info("Ensuring App resource definitions...")
|
||||
kubectl("apply -f ./deploy/app-resource-definition.yml")
|
||||
|
||||
def watch(self):
|
||||
url = self._build_url(api_version="v1", kind="", namespace=WATCH_NAMESPACE)
|
||||
|
||||
w = kubernetes.watch.Watch(return_type=object)
|
||||
for event in w.stream(
|
||||
self.generic.list_generic, resource_path=url, timeout_seconds=90000
|
||||
):
|
||||
for watcher in self.watchers:
|
||||
watcher(event)
|
||||
|
||||
|
||||
def app_watcher(event):
|
||||
print(event)
|
||||
|
||||
|
||||
def buildpack_watcher(event):
|
||||
print(event)
|
||||
self.logger.info("Pretending to watch...")
|
||||
time.sleep(5)
|
||||
|
||||
|
||||
operator = Operator()
|
||||
operator.register_watcher(app_watcher)
|
||||
operator.register_watcher(buildpack_watcher)
|
||||
|
||||
|
||||
def watch():
|
||||
operator.watch()
|
||||
while True:
|
||||
operator.watch()
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
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()
|
||||
# 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("apply -f ./deploy/buildpack-resource-definition.yml")
|
||||
|
||||
# Create Apps resource.
|
||||
self.logger.info("Ensuring App resource definitions...")
|
||||
kubectl("apply -f ./deploy/app-resource-definition.yml")
|
||||
|
||||
def watch(self):
|
||||
self.logger.info("Pretending to watch...")
|
||||
time.sleep(5)
|
||||
|
||||
|
||||
operator = Operator()
|
||||
|
||||
|
||||
def watch():
|
||||
while True:
|
||||
operator.watch()
|
||||
@@ -1,3 +1,5 @@
|
||||
import os
|
||||
|
||||
WATCH_NAMESPACE = os.environ.get("WATCH_NAMESPACE", "default")
|
||||
WATCH_NAMESPACE = os.environ.get("WATCH_NAMESPACE", "bruce")
|
||||
API_VERSION = "v1alpha1"
|
||||
API_GROUP = "bruce.kennethreitz.org"
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import json
|
||||
|
||||
|
||||
import delegator
|
||||
|
||||
|
||||
def kubectl(cmd, as_json=True, raise_on_error=True):
|
||||
json_flag = "-o=json" if as_json else ""
|
||||
cmd = f"kubectl {cmd} {json_flag}"
|
||||
|
||||
c = delegator.run(cmd)
|
||||
|
||||
if as_json:
|
||||
try:
|
||||
assert c.ok
|
||||
return json.loads(c.out)
|
||||
except AssertionError as e:
|
||||
if not raise_on_error:
|
||||
return c
|
||||
else:
|
||||
raise e
|
||||
else:
|
||||
return c
|
||||
@@ -0,0 +1,20 @@
|
||||
[colors]
|
||||
CRITICAL =
|
||||
color: PURPLE
|
||||
style: BOLD
|
||||
ERROR = RED
|
||||
WARNING = YELLOW
|
||||
INFO = None
|
||||
DEBUG = GREEN
|
||||
|
||||
[logme]
|
||||
level = DEBUG
|
||||
formatter = {asctime} - {name} - {levelname} - {message}
|
||||
stream =
|
||||
type: StreamHandler
|
||||
active: True
|
||||
level: DEBUG
|
||||
null =
|
||||
type: NullHandler
|
||||
active: False
|
||||
level: NOTSET
|
||||
Reference in New Issue
Block a user