Skip to content

Commit

Permalink
Merge pull request #2 from mdgreenwald/matthew/poc1
Browse files Browse the repository at this point in the history
Matthew/poc1
  • Loading branch information
mdgreenwald authored Aug 16, 2019
2 parents a7271a3 + 4e33e90 commit e41614f
Show file tree
Hide file tree
Showing 17 changed files with 229 additions and 29 deletions.
4 changes: 4 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,7 @@ venv.bak/

# macos
.DS_Store

# secrets
*secrets*
*credentials*
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -105,3 +105,7 @@ venv.bak/

# macos
.DS_Store

# secrets
*secrets*
*credentials*
4 changes: 1 addition & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM python:3-slim-stretch
FROM python:3-alpine

WORKDIR /opt/push-deploy

Expand All @@ -9,5 +9,3 @@ RUN pip install --no-cache-dir -r requirements.txt
COPY . .

EXPOSE 5000

CMD [ "python", "./push-deploy.py" ]
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Push-Deploy #

Push-Deploy is a Python application which helps to securely and simply enable communication between external tools (GitHub Actions, Circle CI, etc…) and Kubernetes without exposing credentials.
Push-Deploy is a Python application which helps to securely and simply enable communication between external tools (GitHub Actions, Circle CI, etc…) and Kubernetes without exposing cluster credentials.

In particular for projects which may not have semver inplace where other tools like keel and weave/flux make more sense.
In particular for projects which may not have `semver` inplace where other tools like keel.sh and weave/flux would make more sense.

--

2 changes: 1 addition & 1 deletion Tiltfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# one static YAML file
k8s_yaml('./kubernetes/app.yaml')

docker_build('docker.io/library/push-deploy', '.')
docker_build('push-deploy', '.')

k8s_resource('push-deploy', port_forwards='5000:5000')
Empty file removed __init__.py
Empty file.
6 changes: 0 additions & 6 deletions app/__init__.py

This file was deleted.

9 changes: 9 additions & 0 deletions config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import os
basedir = os.path.abspath(os.path.dirname(__file__))

PD_NAMESPACE = os.environ.get('PD_NAMESPACE')
PD_DEPLOYMENT = os.environ.get('PD_DEPLOYMENT')
PD_REGISTRY = os.environ.get('PD_REGISTRY')
PD_USER = os.environ.get('PD_USER')
PD_PASSWORD = os.environ.get('PD_PASSWORD')
SECRET_KEY = os.environ.get('PD_SECRET_KEY')
10 changes: 5 additions & 5 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ services:
build:
dockerfile: Dockerfile
context: .
entrypoint: /bin/bash
entrypoint: /bin/sh
stdin_open: true
tty: true
restart: always
ports:
- "5000:5000"
volumes:
- "./:/opt/app"
environment:
PD_NAMESPACE: ${PD_NAMESPACE}
PD_DEPLOYMENT: ${PD_DEPLOYMENT}
- "./:/opt/push-deploy"

48 changes: 38 additions & 10 deletions kubernetes/app.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -48,25 +48,53 @@ spec:
serviceAccountName: push-deploy
containers:
- image: push-deploy:latest
imagePullPolicy: Always
command: ["python"]
args: ["push-deploy.py"]
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8000
readinessProbe:
tcpSocket:
port: 8000
initialDelaySeconds: 6
periodSeconds: 10
livenessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 14
periodSeconds: 10
command: ["gunicorn"]
args: ["-w", "4", "--access-logfile", "-", "--error-logfile", "-", "-b", "0.0.0.0", "wsgi:app"]
name: push-deploy
envFrom:
- configMapRef:
name: push-deploy-config
- secretRef:
name: push-deploy-secrets
resources:
requests:
memory: "96Mi"
memory: "192Mi"
cpu: "50m"
limits:
memory: "256Mi"
memory: "320Mi"
cpu: "250m"
env:
- name: PD_NAMESPACE
value: default
- name: PD_DEPLOYMENT
value: "nginx"
imagePullSecrets:
- name: k8s-reg-key
securityContext: {}
terminationGracePeriodSeconds: 30

---
apiVersion: v1
kind: Service
metadata:
name: push-deploy
labels:
app: push-deploy
spec:
ports:
- port: 80
targetPort: 8000
protocol: TCP
selector:
app: push-deploy

---
16 changes: 16 additions & 0 deletions kubernetes/ingress.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: push-deploy
spec:
rules:
- host: pushdeploy.internal
http:
paths:
- path: /
backend:
serviceName: push-deploy
servicePort: 80

---
1 change: 0 additions & 1 deletion push-deploy.py

This file was deleted.

53 changes: 53 additions & 0 deletions pushdeploy/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
from version import __version__
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 pushdeploy import apiv1

def create_app(test_config=None):
"""Create and configure an instance of the Flask application."""
app = Flask(__name__, instance_relative_config=True)
app.config.from_object('config')

app.config['JWT_SECRET_KEY'] = app.config['SECRET_KEY']
jwt = JWTManager(app)

@jwt.expired_token_loader
def my_expired_token_callback(expired_token):
token_type = expired_token['type']
return jsonify({
'status': 401,
'sub_status': 42,
'msg': 'The {} token has expired'.format(token_type)
}), 401

if test_config is None:
app.config.from_pyfile("config.py", silent=False)
else:
app.config.update(test_config)

try:
os.makedirs(app.instance_path)
except OSError:
pass

@app.route('/health', methods=['GET'])
@jwt_optional
def health():
return jsonify(
status='healthy',
releaseId=__version__,
), 200

@app.route('/', methods=['GET'])
@jwt_required
def index():
return jsonify(), 200

app.register_blueprint(apiv1.bp)

return app
85 changes: 85 additions & 0 deletions pushdeploy/apiv1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import functools
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
)

bp = Blueprint("apiv1", __name__, url_prefix="/api/v1")

@bp.before_app_request
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.PD_NAMESPACE = current_app.config['PD_NAMESPACE']
g.PD_DEPLOYMENT = current_app.config['PD_DEPLOYMENT']
g.PD_REGISTRY = current_app.config['PD_REGISTRY']

def read_deployment():
namespace = "%s" % str(g.PD_NAMESPACE)
field = "metadata.name=%s" % str(g.PD_DEPLOYMENT)
api_response = g.api_instance.list_namespaced_deployment(
namespace=namespace,
field_selector=field
)
if len(api_response.items) == 1:
return api_response.items[0]
else:
return "Deployment selector not unique enough."

def update_deployment(deployment, image_name, image_tag):
image = "%s/%s:%s" % (g.PD_REGISTRY, image_name, image_tag)
deployment.spec.template.spec.containers[0].image = image
api_response = g.api_instance.patch_namespaced_deployment(
name=g.PD_DEPLOYMENT,
namespace=g.PD_NAMESPACE,
body=deployment,
field_manager="push-deploy")
print("Deployment updated. status='%s'" % str(api_response.status))

@bp.route('/', methods=['GET'])
@jwt_required
def index():
return jsonify(), 200

@bp.route('/auth', methods=['POST'])
def login():
if not request.is_json:
return jsonify({"msg": "Missing JSON in request"}), 400

username = request.json.get('username', None)
password = request.json.get('password', None)
if not username:
return jsonify({"msg": "Missing username parameter"}), 400
if not password:
return jsonify({"msg": "Missing password parameter"}), 400

if username != current_app.config['PD_USER'] or password != current_app.config['PD_PASSWORD']:
return jsonify({"msg": "Bad username or password"}), 401

# Identity can be any data that is json serializable
access_token = create_access_token(identity=username, expires_delta=timedelta(seconds=90))
return jsonify(access_token=access_token), 200

@bp.route('/deploy', methods=['GET'])
@jwt_required
def deploy():
commit_sha = request.args['commit_sha']
image_name = request.args['image_name']
deploy = update_deployment(deployment=read_deployment(), image_name=image_name, image_tag=commit_sha)
return jsonify(msg=deploy), 201
5 changes: 4 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
kubernetes==10.0.0
kubernetes==10.0.1
Flask==1.1.1
Flask-API==1.1
flask-jwt-extended==3.21.0
gunicorn==19.9.0
2 changes: 2 additions & 0 deletions version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

__version__ = '0.0.1'
5 changes: 5 additions & 0 deletions wsgi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import os
from werkzeug.middleware.proxy_fix import ProxyFix
from pushdeploy import create_app

app = ProxyFix(create_app(), x_for=1, x_host=1)

0 comments on commit e41614f

Please sign in to comment.