let's do this

This commit is contained in:
2018-09-29 12:15:19 -04:00
parent 6ace9d1f98
commit 9504ae0709
9 changed files with 335 additions and 59 deletions
+5 -1
View File
@@ -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
}
+8
View File
@@ -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
+6
View File
@@ -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
View File
@@ -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
View File
@@ -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()
+3 -1
View File
@@ -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"
+23
View File
@@ -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
+20
View File
@@ -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