Make more stuff configurable

main
Lauri Võsandi 2 months ago
parent 04629cdff3
commit ebb34fbb72
  1. 24
      app/sandbox-dashboard.py
  2. 242
      app/templates/detail.html
  3. 8
      config/playground.yaml

@ -1,7 +1,7 @@
#!/usr/bin/env python3
import argparse
import os
import random
import sys
import string
import yaml
from functools import wraps
@ -12,13 +12,25 @@ from kubernetes_asyncio import client, config
from sanic_wtf import SanicForm
from wtforms import BooleanField
parser = argparse.ArgumentParser(description="Run Kubernetes cluster sandbox dashboard")
parser.add_argument("--cluster-name",
default="codemowers.eu")
parser.add_argument("--cluster-api-url",
default="https://kube.codemowers.eu")
parser.add_argument("--context-name-prefix",
default="codemowers.eu/")
parser.add_argument("--sandbox-template-url",
default="git@git.k-space.ee:codemowers/lab-template")
parser.add_argument("--feature-flags-spec",
default="/config/playground.yml")
args = parser.parse_args()
ANNOTATION_MANAGED_BY = "sandbox-dashboard"
fallback_email = "lauri.vosandi@gmail.com"
characters = "abcdefghijkmnpqrstuvwxyz23456789"
app = Sanic("dashboard")
session = {}
_, path = sys.argv
if not app.config.get("WTF_CSRF_SECRET_KEY", ""):
raise ValueError("SANIC_WTF_CSRF_SECRET_KEY environment variable not set")
@ -28,9 +40,11 @@ class PlaygroundForm(SanicForm):
pass
with open(path) as fh:
with open(args.feature_flags_spec) as fh:
feature_flags = yaml.safe_load(fh.read())
for feature_flag in feature_flags:
if feature_flag.get("disabled"):
continue
kwargs = {
"name": feature_flag["name"],
"description": feature_flag["description"],
@ -114,7 +128,7 @@ async def create_sandbox(user, values):
"spec": {
"project": "default",
"source": {
"repoURL": "git@git.k-space.ee:codemowers/lab-template",
"repoURL": args.sandbox_template_url,
"path": "./",
"targetRevision": "HEAD",
"helm": {
@ -194,6 +208,7 @@ async def sandbox_detail(request, sandbox_name):
network_api = client.NetworkingV1Api(api)
argo_app = await api_instance.get_namespaced_custom_object("argoproj.io", "v1alpha1", "argocd", "applications", sandbox_name)
return {
"args": args,
"namespace": sandbox_name,
"sandbox": wrap_sandbox_parameters(argo_app),
"pods": (await v1.list_namespaced_pod(sandbox_name)).items,
@ -218,6 +233,7 @@ async def handler(request):
if not email or w["parameters"].get("email") == email:
sandboxes.append(w)
return {
"args": args,
"feature_flags": feature_flags,
"sandboxes": sandboxes
}

@ -4,7 +4,7 @@
<br/>
<br/>
<br/>
<h1>Sandbox {{ namespace }} details</h1>
<a href="https://argocd.codemowers.eu/applications/{{ sandbox.namespace }}?resource=&view=network" class="btn btn-primary" target="_blank">Argo status</a>
{% if sandbox.parameters.prometheus %}
<a href="https://prom{{ sandbox.hostname_suffix }}" class="btn btn-warning">Monitoring</a>
@ -16,78 +16,56 @@
<a href="https://traefik{{ sandbox.hostname_suffix }}/dashboard/" class="btn btn-info">Traefik</a>
{% endif %}
<button type="button" class="btn btn-danger" data-bs-toggle="modal" data-bs-target="#delete-confirmation">Delete</button>
<div class="modal fade" id="delete-confirmation" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">Sandbox delete confirmation</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
Are you sure you want to delete sandbox {{ sandbox.namespace }}?
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<a href="/sandbox/{{ sandbox.namespace }}/delete" type="submit" class="btn btn-danger">Confirm</a>
<br/>
<br/>
<nav>
<div class="nav nav-tabs" id="nav-tab" role="tablist">
<button class="nav-link active" id="nav-home-tab" data-bs-toggle="tab" data-bs-target="#nav-home" type="button" role="tab" aria-controls="nav-home" aria-selected="true">Cluster access</button>
<button class="nav-link" id="nav-pod-tab" data-bs-toggle="tab" data-bs-target="#nav-pod" type="button" role="tab" aria-controls="nav-pod" aria-selected="false">Running pods</button>
<button class="nav-link" id="nav-ingress-tab" data-bs-toggle="tab" data-bs-target="#nav-ingress" type="button" role="tab" aria-controls="nav-ingress" aria-selected="false">Ingresses</button>
<button class="nav-link" id="nav-dev-tab" data-bs-toggle="tab" data-bs-target="#nav-dev" type="button" role="tab" aria-controls="nav-dev" aria-selected="false">Developing</button>
</div>
</nav>
<div class="tab-content" id="nav-tabContent">
<div class="tab-pane fade show active" id="nav-home" role="tabpanel" aria-labelledby="nav-home-tab">
<div class="modal fade" id="delete-confirmation" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">Sandbox delete confirmation</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
Are you sure you want to delete sandbox {{ sandbox.namespace }}?
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<a href="/sandbox/{{ sandbox.namespace }}/delete" type="submit" class="btn btn-danger">Confirm</a>
</div>
</div>
</div>
</div>
</div>
</div>
<h2>Running pods</h2>
<table class="table">
<thead>
<tr>
<th scope="col">Pod name</th>
<th scope="col">Status</th>
<th scope="col">Links</th>
</tr>
</thead>
<tbody>
{% for pod in pods %}
<tr>
<td>{{ pod.metadata.name }}</td>
<td>{{ pod.status.phase }}</td>
<td>
{% if sandbox.parameters.prometheus %}
<a href="https://prom{{ sandbox.hostname_suffix }}/graph?g0.expr=%7Bnamespace%3D%22{{ namespace }}%22%2Cpod%3D%22{{ pod.metadata.name}}%22%7D&g0.tab=1&g0.stacked=0&g0.show_exemplars=0&g0.range_input=1h" class="btn btn-primary"><i class="fas fa-chart-line"></i></a>
{% endif %}
<a href="https://log.codemowers.eu?kubernetes.namespace={{ namespace }}&kubernetes.pod.name={{ pod.metadata.name }}" class="btn btn-primary"><i class="fas fa-clock-rotate-left"></i></a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% for i in ingress %}
{% for rule in i.spec.rules %}
<a href="https://{{ rule.host }}" class="btn btn-info">{{ rule.host }}</a>
{% endfor %}
{% endfor %}
<h2>Sandbox access</h2>
<p>To configure cluster access run following snippet,
it creates a Kubernetes client context named codemowers.eu/{{ sandbox.namespace }}:
<pre>
<h2>Sandbox access</h2>
<p>To configure cluster access run following snippet,
it creates a Kubernetes client context named {{ args.context_name_prefix}}{{ sandbox.namespace }}:
<pre>
cat << EOF > kubeconfig-new
apiVersion: v1
kind: Config
clusters:
- name: kube.codemowers.eu
- name: {{ args.cluster_name }}
cluster:
server: https://kube.codemowers.eu
server: {{ args.cluster_api_url }}
contexts:
- name: codemowers.eu/{{ sandbox.namespace }}
- name: {{ args.context_name_prefix}}{{ sandbox.namespace }}
context:
user: dex.codemowers.eu
cluster: kube.codemowers.eu
user: dex.{{ args.cluster_name }}
cluster: {{ args.cluster_name }}
namespace: {{ sandbox.namespace }}
users:
- name: dex.codemowers.eu
- name: dex.{{ args.cluster_name }}
user:
exec:
apiVersion: client.authentication.k8s.io/v1beta1
@ -96,7 +74,7 @@ users:
- oidc-login
- get-token
- --listen-address=127.0.0.1:27890
- --oidc-issuer-url=https://dex.codemowers.eu
- --oidc-issuer-url=https://dex.{{ args.cluster_name }}
- --oidc-client-id=kubelogin
- --oidc-use-pkce
- --oidc-extra-scope=profile,email,groups
@ -104,39 +82,107 @@ EOF
KUBECONFIG="$HOME/.kube/config:kubeconfig-new" kubectl config view --raw > kubeconfig-merged
mv kubeconfig-merged $HOME/.kube/config
rm -fv kubeconfig-new
</pre>
<h2>Frequently used commands:</h2>
<pre>kubectl apply --context codemowers.eu/{{ sandbox.namespace }} -f foobar.yaml</pre>
<pre>kubectl get pods --context codemowers.eu/{{ sandbox.namespace }}</pre>
<pre>kubectl get deployments --context codemowers.eu/{{ sandbox.namespace }}</pre>
<pre>kubectl get statefulsets --context codemowers.eu/{{ sandbox.namespace }}</pre>
<pre>kubectl get svc --context codemowers.eu/{{ sandbox.namespace }}</pre>
<pre>kubectl get ep --context codemowers.eu/{{ sandbox.namespace }}</pre>
<pre>kubectl get ing --context codemowers.eu/{{ sandbox.namespace }}</pre>
<h2>Developing with Skaffold</h2>
<p>To <a href="https://skaffold.dev/docs/install/">Skaffold</a> develop using one of the template repositories for
<a href="https://github.com/codemowers/hello-python">Flask</a>,
<a href="https://github.com/codemowers/hello-nodejs">Express.js</a>:
</p>
<pre>skaffold dev --kube-context codemowers.eu/{{ sandbox.namespace }} --default-repo harbor.codemowers.eu/{{ sandbox.namespace }} --namespace {{ sandbox.namespace }}</pre>
<h2>Building locally with Docker</h2>
<p>In some cases you might want to have Skaffold run <pre>docker build</pre>
locally. In that case you need to copy the Docker registry credentials from the cluster.
<pre>cat ~/.docker/config.json | jq ".auths.\"harbor.codemowers.eu\".auth = \"$(kubectl get secret --context codemowers.eu/{{ sandbox.namespace }} kaniko-secret -o jsonpath='{.data.config\.json}' | base64 -d | jq -s '.[].auths."harbor.codemowers.eu".auth' -r)\"" > .dockerconfig.json && mv .dockerconfig.json ~/.docker/config.json</pre>
<p>To reconfigure Skaffold either comment or remove `build.cluster` section in the `skaffold.yaml` file</p>
</pre>
<h2>Frequently used commands:</h2>
<pre>kubectl apply --context {{ args.context_name_prefix}}{{ sandbox.namespace }} -f foobar.yaml</pre>
<pre>kubectl get deployments --context {{ args.context_name_prefix}}{{ sandbox.namespace }}</pre>
<pre>kubectl get statefulsets --context {{ args.context_name_prefix}}{{ sandbox.namespace }}</pre>
</div>
<div class="tab-pane fade" id="nav-pod" role="tabpanel" aria-labelledby="nav-pod-tab">
<p>To interact on command line:</p>
<pre>kubectl get pods --context {{ args.context_name_prefix}}{{ sandbox.namespace }}</pre>
<table class="table">
<thead>
<tr>
<th scope="col">Pod name</th>
<th scope="col">Status</th>
<th scope="col">Links</th>
</tr>
</thead>
<tbody>
{% for pod in pods %}
<tr>
<td>{{ pod.metadata.name }}</td>
<td>{{ pod.status.phase }}</td>
<td>
<button type="button" class="btn btn-danger" data-bs-toggle="modal" data-bs-target="#pod-snippets-{{ pod.metadata.name }}"><i class="fas fa-terminal"></i></button>
<div class="modal fade" id="pod-snippets-{{ pod.metadata.name }}" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">Command line snippets for this pod</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<p>Pod details:</p>
<pre>kubectl get -o wide \
pod/{{ pod.metadata.name }} \
--context {{ args.context_name_prefix}}{{ sandbox.namespace }}</pre>
<p>Pod status:</p>
<pre>kubectl describe \
pod/{{ pod.metadata.name }} \
--context {{ args.context_name_prefix}}{{ sandbox.namespace }}</pre>
<p>Pod logs:</p>
<pre>kubectl logs \
pod/{{ pod.metadata.name }} \
--context {{ args.context_name_prefix}}{{ sandbox.namespace }}</pre>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
{% if sandbox.parameters.prometheus %}
<a href="https://prom{{ sandbox.hostname_suffix }}/graph?g0.expr=%7Bnamespace%3D%22{{ namespace }}%22%2Cpod%3D%22{{ pod.metadata.name}}%22%7D&g0.tab=1&g0.stacked=0&g0.show_exemplars=0&g0.range_input=1h" class="btn btn-primary" target="_blank"><i class="fas fa-chart-line"></i></a>
{% endif %}
<a href="https://log.codemowers.eu?kubernetes.namespace={{namespace}}&kubernetes.pod.name={{pod.metadata.name}}" class="btn btn-primary" target="_blank"><i class="fas fa-clock-rotate-left"></i></a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="tab-pane fade" id="nav-ingress" role="tabpanel" aria-labelledby="nav-ingress-tab">
<p>To list all <a href="https://kubernetes.io/docs/concepts/services-networking/service/" target="_blank">services</a> in this sandbox</p>
<pre>kubectl get svc --context {{ args.context_name_prefix}}{{ sandbox.namespace }}</pre>
<p>To list all <a href="https://kubernetes.io/docs/concepts/services-networking/service/#endpoints" target="_blank">endpoints</a> in this sandbox</p>
<pre>kubectl get ep --context {{ args.context_name_prefix}}{{ sandbox.namespace }}</pre>
<p>To list all <a href="https://kubernetes.io/docs/concepts/services-networking/ingress/" target="_blank">ingress rules</a> in this sandbox</p>
<pre>kubectl get ing --context {{ args.context_name_prefix}}{{ sandbox.namespace }}</pre>
<table class="table">
<thead>
<tr>
<th scope="col">URL</th>
<th scope="col">Status</th>
</tr>
</thead>
<tbody>
{% for i in ingress %}
{% for rule in i.spec.rules %}
<tr>
<td><a href="https://{{ rule.host }}" target="_blank">{{ rule.host }}</a></td>
<td>&nbsp;</td>
</tr>
{% endfor %}
{% endfor %}
</tbody>
</table>
</div>
<div class="tab-pane fade" id="nav-dev" role="tabpanel" aria-labelledby="nav-dev-tab">
<h2>Developing with Skaffold</h2>
<p>To <a href="https://skaffold.dev/docs/install/">Skaffold</a> develop using one of the template repositories for
<a href="https://github.com/codemowers/hello-python">Flask</a>,
<a href="https://github.com/codemowers/hello-nodejs">Express.js</a>:
</p>
<pre>skaffold dev --kube-context {{ args.context_name_prefix}}{{ sandbox.namespace }} --default-repo harbor.{{ args.context_name_prefix}}{{ sandbox.namespace }} --namespace {{ sandbox.namespace }}</pre>
<h2>Building locally with Docker</h2>
<p>In some cases you might want to have Skaffold run <pre>docker build</pre>
locally. In that case you need to copy the Docker registry credentials from the cluster.
<pre>cat ~/.docker/config.json | jq ".auths.\"harbor.codemowers.eu\".auth = \"$(kubectl get secret --context {{ args.context_name_prefix}}{{ sandbox.namespace }} kaniko-secret -o jsonpath='{.data.config\.json}' | base64 -d | jq -s '.[].auths."harbor.codemowers.eu".auth' -r)\"" > .dockerconfig.json && mv .dockerconfig.json ~/.docker/config.json</pre>
<p>To reconfigure Skaffold either comment or remove `build.cluster` section in the `skaffold.yaml` file</p>
</div>
</div>
{% endblock %}

@ -17,11 +17,11 @@
- name: subdomain
default: false
description: "Create dedicated subdomain under codemowers.cloud"
- name: argocd
description: "Set up separate ArgoCD instance for this namespace"
default: false
icon: https://cncf-branding.netlify.app/img/projects/argo/stacked/color/argo-stacked-color.png
- name: dex
description: "Dedicated Dex instance"
default: false
icon: https://cncf-branding.netlify.app/img/projects/dex/stacked/color/dex-stacked-color.png
- name: argocd
description: "Set up separate ArgoCD instance for this namespace"
default: false
icon: https://cncf-branding.netlify.app/img/projects/argo/stacked/color/argo-stacked-color.png

Loading…
Cancel
Save