Compare commits

..

No commits in common. "master" and "issue-#4" have entirely different histories.

9 changed files with 82 additions and 53 deletions

View File

@ -6,16 +6,18 @@ LABEL MAINTAINER="Jonathan Ervine <jonathan.ervine@gogox.com>"
ENV LANG='en_US.UTF-8' \ ENV LANG='en_US.UTF-8' \
LANGUAGE='en_US.UTF-8' \ LANGUAGE='en_US.UTF-8' \
FLASK_APP=/data/app-dev/app.py \ FLASK_APP=/data/app-dev/app.py \
VERSION=1.1.5 VERSION=1.1.4
RUN apk update && \ RUN apk update && \
apk -U upgrade --ignore alpine-baselayout && \ apk -U upgrade --ignore alpine-baselayout && \
apk -U add python3 gcc py3-pip python3-dev musl-dev libffi-dev git curl && \ apk -U add python3 gcc py3-pip python3-dev musl-dev libffi-dev git curl && \
adduser -D python && \ adduser -D python && \
mkdir /data && cd /data && git clone --single-branch --branch issue-#2 https://github.com/jervine-gogo/python-helm-web /data && \ mkdir /data && cd /data && git clone --single-branch --branch master https://github.com/jervine-gogo/python-helm-web /data && \
pip3 install -r /data/requirements.txt && \ pip3 install -r /data/requirements.txt && \
curl -L https://get.helm.sh/helm-v3.4.2-linux-amd64.tar.gz -o /tmp/helm-3.4.2.tgz && \ curl -L "https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl" -o /usr/local/bin/kubectl && \
tar -zxvf /tmp/helm-3.4.2.tgz --strip-components=1 -C /usr/local/bin linux-amd64/helm && \ chmod 755 /usr/local/bin/kubectl && \
curl -L https://get.helm.sh/helm-v2.13.1-linux-amd64.tar.gz -o /tmp/helm-2.13.1.tgz && \
tar -zxvf /tmp/helm-2.13.1.tgz --strip-components=1 -C /usr/local/bin linux-amd64/helm && \
rm -rf /tmp/src && rm -rf /var/cache/apk/* rm -rf /tmp/src && rm -rf /var/cache/apk/*
EXPOSE 3000 EXPOSE 3000

View File

@ -8,7 +8,7 @@ from wtforms.validators import InputRequired, Email, Length
from flask_bootstrap import Bootstrap from flask_bootstrap import Bootstrap
class deploySelectForm(FlaskForm): class deploySelectForm(FlaskForm):
ns = SelectField('ns', choices=[], coerce=str) tiller_ns = SelectField('tiller_ns', choices=[], coerce=str)
chart = SelectField('chart', choices=[]) chart = SelectField('chart', choices=[])
version = SelectField('remember me') # Added if/when I want to add additional helm version options version = SelectField('remember me') # Added if/when I want to add additional helm version options
records = SelectField('records', choices=[('10', '10'), ('20', '20'), ('30', '30'), ('40', '40'), ('50', '50'), ('100', '100'), ('150', '150'), ('200', '200'), ('256', '256 (max)')], default=['10']) records = SelectField('records', choices=[('10', '10'), ('20', '20'), ('30', '30'), ('40', '40'), ('50', '50'), ('100', '100'), ('150', '150'), ('200', '200'), ('256', '256 (max)')], default=['10'])

56
main.py
View File

@ -13,18 +13,48 @@ from routes import *
from logging import error, info from logging import error, info
from subprocess import STDOUT, CalledProcessError, check_output from subprocess import STDOUT, CalledProcessError, check_output
from itertools import islice from itertools import islice
from kubernetes import client, config
app.register_blueprint(routes) app.register_blueprint(routes)
def get_namespaces(): def get_namespaces():
config.load_incluster_config() command = "/usr/local/bin/kubectl get ns -ojson"
v1 = client.CoreV1Api() info(f"Running command: {command}")
ns = v1.list_namespace() try:
return ns output = check_output(command.split(" "), stderr=STDOUT).decode("utf-8")
except CalledProcessError as err:
error(err.output.decode("utf-8"))
raise err
info(f"Output from command:\n{output}")
data = json.loads(output)
return data
def get_charts(ns): def get_tiller_namespaces():
command = "/usr/local/bin/helm -n " + ns + " list -ojson" #helm3 command = "/usr/local/bin/kubectl get deploy --all-namespaces -l name=tiller -ojson"
info(f"Running command: {command}")
try:
output = check_output(command.split(" "), stderr=STDOUT).decode("utf-8")
except CalledProcessError as err:
error(err.output.decode("utf-8"))
raise err
info(f"Output from command:\n{output}")
data = json.loads(output)
return data
def get_deployments(namespace):
command = "/usr/local/bin/kubectl -n " + namespace + " get deploy -ojson"
info(f"Running command: {command}")
try:
output = check_output(command.split(" "), stderr=STDOUT).decode("utf-8")
except CalledProcessError as err:
error(err.output.decode("utf-8"))
raise err
info(f"Output from command:\n{output}")
data = json.loads(output)
return data
def get_charts(tiller_ns, namespace):
command = "/usr/local/bin/helm --tiller-namespace " + tiller_ns + " list --output json" #helm2
#command = "/usr/local/bin/helm -n " + namespace + " list -ojson" #helm3
info(f"Running command: {command}") info(f"Running command: {command}")
try: try:
output = check_output(command.split(" "), stderr=STDOUT).decode("utf-8") output = check_output(command.split(" "), stderr=STDOUT).decode("utf-8")
@ -41,8 +71,9 @@ def get_charts(ns):
def sortRevision(n): def sortRevision(n):
return n['revision'] return n['revision']
def get_chartdata(ns, chart, records): def get_chartdata(tiller_ns, namespace, chart, records):
command = "/usr/local/bin/helm -n " + ns + " history " + chart + " --max " + records + " -ojson" #helm3 command = "/usr/local/bin/helm --tiller-namespace " + tiller_ns + " history " + chart + " --max " + records + " --output json" #helm2
#command = "/usr/local/bin/helm -n " + namespace + " history " + chart + " -ojson" #helm3
info(f"Running command: {command}") info(f"Running command: {command}")
try: try:
output = check_output(command.split(" "), stderr=STDOUT).decode("utf-8") output = check_output(command.split(" "), stderr=STDOUT).decode("utf-8")
@ -54,11 +85,12 @@ def get_chartdata(ns, chart, records):
data.sort(reverse=True, key=sortRevision) data.sort(reverse=True, key=sortRevision)
for revision in data: for revision in data:
revision['chartName'] = chart revision['chartName'] = chart
revision["ns"] = ns revision["namespace"] = namespace
revision["tiller_ns"] = tiller_ns
return data return data
def chartRollback(revision, chart, ns): def chartRollback(revision, chart, tiller_ns):
command = "/usr/local/bin/helm -n " + ns + " rollback " + chart + " " + revision # helm3? command = "/usr/local/bin/helm --tiller-namespace " + tiller_ns + " rollback " + chart + " " + revision # helm2
info(f"Running command: {command}") info(f"Running command: {command}")
try: try:
output = check_output(command.split(" "), stderr=STDOUT).decode("utf-8") output = check_output(command.split(" "), stderr=STDOUT).decode("utf-8")

View File

@ -4,4 +4,3 @@ flask_table
Flask-Bootstrap Flask-Bootstrap
flask_wtf flask_wtf
wtforms_components wtforms_components
kubernetes

View File

@ -1,34 +1,36 @@
from flask import render_template, request, jsonify from flask import render_template, request, jsonify
from forms import deploySelectForm from forms import deploySelectForm
from main import get_namespaces, get_charts, get_chartdata, chartRollback from main import get_namespaces, get_charts, get_chartdata, get_tiller_namespaces, chartRollback
from . import routes from . import routes
from tables import chartVersionTable from tables import chartVersionTable
import json import json
@routes.route('/', methods=['GET', 'POST']) @routes.route('/', methods=['GET', 'POST'])
def index(): def index():
ns = get_namespaces() tiller_ns = get_tiller_namespaces()
form = deploySelectForm() form = deploySelectForm()
form.ns.choices = [(name.metadata.name, name.metadata.name) for name in ns.items] form.tiller_ns.choices = [(name['metadata']['namespace'], name['metadata']['namespace']) for name in tiller_ns['items']]
return render_template('nameChartSelect.html', ns=ns, form=form) return render_template('nameChartSelect.html', tiller_ns=tiller_ns, form=form)
@routes.route('/chartSelect', methods=['POST']) @routes.route('/chartSelect', methods=['POST'])
def chartVersions(): def chartVersions():
tiller_ns = request.form['tiller_ns']
chart = request.form['chart'] chart = request.form['chart']
records = request.form['records'] records = request.form['records']
ns = request.form['ns'] namespace = 'default'
chartVersions = get_chartdata(ns, chart, records) chartVersions = get_chartdata(tiller_ns, namespace, chart, records)
table = chartVersionTable(chartVersions) table = chartVersionTable(chartVersions)
table.border = True table.border = True
table.classes = ['table-striped', 'table-condensed', 'table-hover'] table.classes = ['table-striped', 'table-condensed', 'table-hover']
return render_template('chartRevisionList.html', table=table) return render_template('chartRevisionList.html', table=table)
@routes.route('/nsLookup/<ns>')
def namespaceLookup(ns): @routes.route('/nsLookup/<tiller_ns>/<namespace>')
charts = get_charts(ns) def namespaceLookup(tiller_ns, namespace):
charts = get_charts(tiller_ns, namespace)
return jsonify(charts) return jsonify(charts)
@routes.route('/deployChartRevision/<revision>/<chart>/<ns>', methods=['POST']) @routes.route('/deployChartRevision/<revision>/<chart>/<tiller_ns>', methods=['POST'])
def deployChartRevision(revision, chart, ns): def deployChartRevision(revision, chart, tiller_ns):
rollback = chartRollback(revision, chart, ns) rollback = chartRollback(revision, chart, tiller_ns)
return rollback return rollback

View File

@ -4,7 +4,7 @@ class chartVersionTable(Table):
revision = Col('Chart Revision') revision = Col('Chart Revision')
updated = Col('Updated') updated = Col('Updated')
status = Col('Status') status = Col('Status')
ns = Col('Namespace') tiller_ns = Col('Tiiler Namespace')
chart = Col('Chart Version') chart = Col('Chart Version')
description = Col('Description') description = Col('Description')
deploy = ButtonCol('Deploy', 'routes.deployChartRevision', url_kwargs=dict(revision='revision', chart='chartName', ns='ns'), button_attrs={"type" : "submit", "class" : "btn btn-danger"}) deploy = ButtonCol('Deploy', 'routes.deployChartRevision', url_kwargs=dict(revision='revision', chart='chartName', tiller_ns='tiller_ns'), button_attrs={"type" : "submit", "class" : "btn btn-danger"})

View File

@ -23,6 +23,4 @@
{{ table }} {{ table }}
</table> </table>
<a class="btn btn-primary" href="/" role="button">Home</a> <a class="btn btn-primary" href="/" role="button">Home</a>
<br> </body>
<a class="btn btn-primary" href="/oauth2/sign_out" role="button">Log Off</a>
</body>

View File

@ -1,6 +1,6 @@
<html> <html>
<head> <head>
<title>Select Namespace and Chart</title> <title>Select Namespace and Chart *TESTING*</title>
<link rel="stylesheet" media="screen" href ="/static/css/bootstrap.min.css"> <link rel="stylesheet" media="screen" href ="/static/css/bootstrap.min.css">
<link rel="stylesheet" href="/static/css/bootstrap-theme.min.css"> <link rel="stylesheet" href="/static/css/bootstrap-theme.min.css">
<meta name="viewport" content = "width=device-width, initial-scale=1.0"> <meta name="viewport" content = "width=device-width, initial-scale=1.0">
@ -18,8 +18,8 @@
<div class = "row"> <div class = "row">
<div class = "col-sm-6"> <div class = "col-sm-6">
<div class="input-group"> <div class="input-group">
<span class="input-group-addon" id="basic-addon1">Namespace:</span> <span class="input-group-addon" id="basic-addon1">Tiller Namespace:</span>
{{ form.ns(class_="form-control") }} {{ form.tiller_ns(class_="form-control") }}
</div> </div>
</div> </div>
</div> </div>
@ -43,8 +43,6 @@
{{ form.submitButton(class_="btn btn-success") }} {{ form.submitButton(class_="btn btn-success") }}
<a class="btn btn-danger" href="/" role="button">Cancel</a> <a class="btn btn-danger" href="/" role="button">Cancel</a>
</p> </p>
<br>
<a class="btn btn-primary" href="/oauth2/sign_out" role="button">Log Off</a>
</form> </form>
</div> </div>
</div> </div>
@ -52,21 +50,21 @@
</dl> </dl>
<script> <script>
var nsSelect = document.getElementById("ns"); var tillerSelect = document.getElementById("tiller_ns");
var chartSelect = document.getElementById("chart"); var chartSelect = document.getElementById("chart");
nsSelect.onchange = function() {myFunction()}; tillerSelect.onchange = function() {myFunction()};
function myFunction() { function myFunction() {
ns = nsSelect.value; tiller_ns = tillerSelect.value;
fetch('/nsLookup/' + ns).then(function(response) { fetch('/nsLookup/' + tiller_ns + '/default').then(function(response) {
response.json().then(function(data) { response.json().then(function(data) {
if (data != 'EMPTY') { if (data != 'EMPTY') {
var optionHTML = ''; var optionHTML = '';
for (var chart of data) { for (var chart of data.Releases) {
optionHTML += '<option value="' + chart.name + '">' + chart.name + '</option>'; optionHTML += '<option value="' + chart.Name + '">' + chart.Name + '</option>';
} }
chartSelect.innerHTML = optionHTML; chartSelect.innerHTML = optionHTML;
} }
@ -78,4 +76,4 @@
} }
</script> </script>
</body> </body>
</html> </html>

View File

@ -1,6 +1,6 @@
<html> <html>
<head> <head>
<title>Select Namespace and Deployment</title> <title>Select Namespace and Deployment *TESTING*</title>
<link rel="stylesheet" media="screen" href ="/static/css/bootstrap.min.css"> <link rel="stylesheet" media="screen" href ="/static/css/bootstrap.min.css">
<link rel="stylesheet" href="/static/css/bootstrap-theme.min.css"> <link rel="stylesheet" href="/static/css/bootstrap-theme.min.css">
<meta name="viewport" content = "width=device-width, initial-scale=1.0"> <meta name="viewport" content = "width=device-width, initial-scale=1.0">
@ -32,9 +32,7 @@
<p> <p>
{{ form.submitButton(class_="btn btn-success") }} {{ form.submitButton(class_="btn btn-success") }}
<a class="btn btn-danger" href="/" role="button">Cancel</a> <a class="btn btn-danger" href="/" role="button">Cancel</a>
<br> </p>
<a class="btn btn-primary" href="/oauth2/sign_out" role="button">Log Off</a>
</p>
</form> </form>
</div> </div>
</div> </div>
@ -64,4 +62,4 @@
} }
</script> </script>
</body> </body>
</html> </html>