From e05eea7ed9eaa064f8419b36ef046309cd4a631e Mon Sep 17 00:00:00 2001 From: mdgreenwald Date: Tue, 19 May 2020 15:59:20 -0400 Subject: [PATCH] Workload support update for push-deploy Signed-off-by: mdgreenwald --- README.md | 27 +++++++-- pushdeploy/__init__.py | 5 +- pushdeploy/apiv1.py | 132 +++++++++++++++++++++++++++++++++-------- requirements.txt | 2 +- tests/setup.yaml | 89 +++++++++++++++++++++++++++ version.py | 2 +- 6 files changed, 221 insertions(+), 36 deletions(-) create mode 100644 tests/setup.yaml diff --git a/README.md b/README.md index 25f0fc4..9ec8350 100644 --- a/README.md +++ b/README.md @@ -21,17 +21,34 @@ This is pre-release software and is very limited. It will have bugs and lacks ma ```bash TOKEN=$( - curl -H "Content-Type: application/json" -X POST \ - -d '{"username":"service_account_name","password":"as8djareallylongstring9asdj8a8sdj"}' \ - http://pushdeploy.domain.com/api/v1/auth + curl --header "Content-Type: application/json" \ + --request POST \ + -d '{"username":"${USERNAME}","password":"${PASSWORD}"}' \ + https://pushdeploy.domain.com/api/v1/auth ) ``` ```bash -curl -H "Authorization: Bearer $TOKEN" \ -http://pushdeploy.domain.com/api/v1/deploy?image_name=my_image&image_tag=v1.0.3 +curl --header "Authorization: Bearer ${TOKEN}" \ +--request POST \ +"https://pushdeploy.domain.com/api/v1/deployment?name=${NAME}&namespace=${NAMESPACE}&image_name=${IMAGE_NAME}&image_tag=${IMAGE_TAG}" ``` +### v1 Endpoints + +- /api/v1/cronjob -> v1beta1/cronjob +- /api/v1/daemonset -> apps/v1/daemonset +- /api/v1/deployment -> apps/v1/deployment + +- /api/v1/deploy -> apps/v1/deployments (**Deprecated**) + +### v1 Parameters + +- name=name of object +- namespace=namespace of object +- image_name=image name +- image_tag=image tag + ## Contributing #### Dependencies diff --git a/pushdeploy/__init__.py b/pushdeploy/__init__.py index ab3921c..faf7fa9 100755 --- a/pushdeploy/__init__.py +++ b/pushdeploy/__init__.py @@ -2,10 +2,7 @@ import os from kubernetes import client, config from flask import Flask, jsonify -from flask_jwt_extended import ( - JWTManager, jwt_required, jwt_optional, create_access_token, - get_jwt_identity - ) +from flask_jwt_extended import JWTManager, jwt_required, jwt_optional, create_access_token, get_jwt_identity from pushdeploy import apiv1 def create_app(test_config=None): diff --git a/pushdeploy/apiv1.py b/pushdeploy/apiv1.py index 254cff8..f8ec538 100755 --- a/pushdeploy/apiv1.py +++ b/pushdeploy/apiv1.py @@ -2,21 +2,8 @@ import json from kubernetes import client, config from datetime import timedelta -from flask import Blueprint -from flask import current_app -from flask import flash -from flask import g -from flask import redirect -from flask import render_template -from flask import request -from flask import session -from flask import url_for -from flask import jsonify - -from flask_jwt_extended import ( - JWTManager, jwt_required, jwt_optional, create_access_token, - get_jwt_identity - ) +from flask import Blueprint, current_app, flash, g, redirect, render_template, request, session, url_for, jsonify +from flask_jwt_extended import JWTManager, jwt_required, jwt_optional, create_access_token, get_jwt_identity bp = Blueprint("apiv1", __name__, url_prefix="/api/v1") @@ -25,26 +12,71 @@ def init_api(): """Creates instances of the incluster config and client API and stores them in global""" g.configuration = config.load_incluster_config() - g.api_instance = client.AppsV1Api(client.ApiClient(g.configuration)) + g.apps_v1_api_instance = client.AppsV1Api(client.ApiClient(g.configuration)) + g.batch_v1beta1_instance = client.BatchV1beta1Api(client.ApiClient(g.configuration)) g.PD_REGISTRY = current_app.config['PD_REGISTRY'] -def read_deployment(deployment, namespace): +def list_cron_job(name, namespace): + namespace = "%s" % str(namespace) + name = "metadata.name=%s" % str(name) + api_response = g.batch_v1beta1_instance.list_namespaced_cron_job( + namespace=namespace, + field_selector=name + ) + if len(api_response.items) == 1: + return api_response.items[0] + else: + return "CronJob selector not unique enough." + +def list_daemon_set(name, namespace): namespace = "%s" % str(namespace) - field = "metadata.name=%s" % str(deployment) - api_response = g.api_instance.list_namespaced_deployment( + name = "metadata.name=%s" % str(name) + api_response = g.apps_v1_api_instance.list_namespaced_daemon_set( namespace=namespace, - field_selector=field + field_selector=name + ) + if len(api_response.items) == 1: + return api_response.items[0] + else: + return "DaemonSet selector not unique enough." + +def list_deployment(name, namespace): + namespace = "%s" % str(namespace) + name = "metadata.name=%s" % str(name) + api_response = g.apps_v1_api_instance.list_namespaced_deployment( + namespace=namespace, + field_selector=name ) if len(api_response.items) == 1: return api_response.items[0] else: return "Deployment selector not unique enough." -def update_deployment(deployment_object, image_name, image_tag, deployment, namespace): +def patch_cron_job(cron_job_object, image_name, image_tag, name, namespace): + image = "%s/%s:%s" % (g.PD_REGISTRY, image_name, image_tag) + cron_job_object.spec.job_template.spec.template.spec.containers[0].image = image + api_response = g.batch_v1beta1_instance.patch_namespaced_cron_job( + name=name, + namespace=namespace, + body=cron_job_object, + field_manager="push-deploy") + print("CronJob updated. status='%s'" % str(api_response.status)) + +def patch_daemon_set(daemon_set_object, image_name, image_tag, name, namespace): + image = "%s/%s:%s" % (g.PD_REGISTRY, image_name, image_tag) + daemon_set_object.spec.template.spec.containers[0].image = image + api_response = g.apps_v1_api_instance.patch_namespaced_daemon_set( + name=name, + namespace=namespace, + body=daemon_set_object, + field_manager="push-deploy") + print("DaemonSet updated. status='%s'" % str(api_response.status)) + +def patch_deployment(deployment_object, image_name, image_tag, name, namespace): image = "%s/%s:%s" % (g.PD_REGISTRY, image_name, image_tag) deployment_object.spec.template.spec.containers[0].image = image - api_response = g.api_instance.patch_namespaced_deployment( - name=deployment, + api_response = g.apps_v1_api_instance.patch_namespaced_deployment( + name=name, namespace=namespace, body=deployment_object, field_manager="push-deploy") @@ -74,12 +106,62 @@ def login(): access_token = create_access_token(identity=username, expires_delta=timedelta(seconds=90)) return jsonify(access_token=access_token), 200 +@bp.route('/cronjob', methods=['POST']) +@jwt_required +def cronjob(): + image_tag = request.args['image_tag'] + image_name = request.args['image_name'] + name = request.args['name'] + namespace = request.args['namespace'] + cronjob = patch_cron_job( + cron_job_object=list_cron_job(name=name, namespace=namespace), + image_name=image_name, + image_tag=image_tag, + name=name, + namespace=namespace) + return jsonify(msg=cronjob), 201 + +@bp.route('/daemonset', methods=['POST']) +@jwt_required +def daemonset(): + image_tag = request.args['image_tag'] + image_name = request.args['image_name'] + name = request.args['name'] + namespace = request.args['namespace'] + daemonset = patch_daemon_set( + daemon_set_object=list_daemon_set(name=name, namespace=namespace), + image_name=image_name, + image_tag=image_tag, + name=name, + namespace=namespace) + return jsonify(msg=daemonset), 201 + @bp.route('/deploy', methods=['GET']) @jwt_required def deploy(): image_tag = request.args['image_tag'] image_name = request.args['image_name'] - deployment = request.args['deployment'] + name = request.args['deployment'] namespace = request.args['namespace'] - deploy = update_deployment(deployment_object=read_deployment(deployment=deployment, namespace=namespace), image_name=image_name, image_tag=image_tag, deployment=deployment, namespace=namespace) + deploy = patch_deployment( + deployment_object=list_deployment(name=name, namespace=namespace), + image_name=image_name, + image_tag=image_tag, + name=name, + namespace=namespace) return jsonify(msg=deploy), 201 + +@bp.route('/deployment', methods=['POST']) +@jwt_required +def deployment(): + image_tag = request.args['image_tag'] + image_name = request.args['image_name'] + name = request.args['name'] + namespace = request.args['namespace'] + deployment = patch_deployment( + deployment_object=list_deployment(name=name, namespace=namespace), + image_name=image_name, + image_tag=image_tag, + name=name, + namespace=namespace) + return jsonify(msg=deployment), 201 diff --git a/requirements.txt b/requirements.txt index d97d48b..5bcf755 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ kubernetes==11.0.0 -Flask==1.1.1 +Flask==1.1.2 Flask-API==2.0 flask-jwt-extended==3.24.1 gunicorn==20.0.4 diff --git a/tests/setup.yaml b/tests/setup.yaml new file mode 100644 index 0000000..5ca46af --- /dev/null +++ b/tests/setup.yaml @@ -0,0 +1,89 @@ +--- +apiVersion: batch/v1beta1 +kind: CronJob +metadata: + name: cronjob-test + labels: + environment: test +spec: + schedule: "*/1 * * * *" + concurrencyPolicy: "Forbid" + failedJobsHistoryLimit: 1 + successfulJobsHistoryLimit: 1 + jobTemplate: + spec: + backoffLimit: 6 + ttlSecondsAfterFinished: 30 + template: + spec: + containers: + - name: busybox + image: busybox:1.24.0 + args: + - /bin/sh + - -c + - date; echo Hello from the Kubernetes cluster + resources: + limits: + cpu: 20m + memory: 32Mi + requests: + cpu: 10m + memory: 16Mi + restartPolicy: OnFailure + +--- +apiVersion: apps/v1 +kind: DaemonSet +metadata: + name: daemonset-test + labels: + environment: test +spec: + selector: + matchLabels: + name: daemonset-test + template: + metadata: + labels: + name: daemonset-test + spec: + containers: + - name: busybox + image: busybox:1.24.0 + command: ['sh', '-c', "until nslookup myservice.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo waiting for myservice; sleep 2; done"] + resources: + limits: + cpu: 20m + memory: 32Mi + requests: + cpu: 10m + memory: 16Mi + +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: deployment-test + labels: + environment: test +spec: + selector: + matchLabels: + app: deployment-test + template: + metadata: + labels: + app: deployment-test + spec: + containers: + - name: busybox + image: busybox:1.24.0 + command: ['sh', '-c', "until nslookup myservice.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo waiting for myservice; sleep 2; done"] + resources: + limits: + cpu: 20m + memory: 32Mi + requests: + cpu: 10m + memory: 16Mi diff --git a/version.py b/version.py index cfe018d..9fbfc84 100644 --- a/version.py +++ b/version.py @@ -1,2 +1,2 @@ -__version__ = '0.0.4' +__version__ = '0.0.5'