From 4629ca85c8bccf19b5c3a94320d57c70cd5b5f61 Mon Sep 17 00:00:00 2001
From: Dmytro Bogatov <dmytro@dbogatov.org>
Date: Sun, 14 Jul 2019 23:05:31 +0200
Subject: [PATCH] Add kubectl apply, fix shevastream.
---
infra/deploy.py | 136 ++++++++++++++++++++-------
infra/sources/dashboard/ingress.yaml | 2 +-
infra/sources/service/website.yaml | 13 +++
3 files changed, 117 insertions(+), 34 deletions(-)
diff --git a/infra/deploy.py b/infra/deploy.py
index 4ee83cf..6fa15f2 100755
--- a/infra/deploy.py
+++ b/infra/deploy.py
@@ -9,10 +9,12 @@ import requests
import time
import sys
import os
-from kubernetes import client, config
+from kubernetes import client, config, utils
from kubernetes.client.rest import ApiException
import base64
from enum import Enum, auto
+import subprocess
+from jinja2 import Template, Environment, FileSystemLoader
cwd = Path(os.path.dirname(os.path.realpath(__file__)))
@@ -23,6 +25,33 @@ def deploy():
bootstrapCluster()
+def apply(resource, message=None):
+ if len(resource.splitlines()) == 1:
+ argument = resource if "https://" in resource else (cwd / resource).as_posix()
+ kubectl = f"kubectl --kubeconfig={(cwd / 'kubeconfig.yaml').as_posix()} apply -f {argument}"
+ output, error = subprocess.Popen(kubectl.split(), stdout=subprocess.PIPE).communicate()
+ else:
+ kubectl = f"kubectl --kubeconfig={(cwd / 'kubeconfig.yaml').as_posix()} apply -f -"
+ result = subprocess.run(
+ kubectl.split(),
+ stdout=subprocess.PIPE,
+ input=resource,
+ encoding='ascii'
+ )
+ output = result.stdout.encode()
+ error = result.stderr
+
+ if error is None:
+ _logger.debug(f"{kubectl}\n{output.decode()}")
+ else:
+ if message is not None:
+ _logger.critical(f"{message} provision failed")
+ _logger.error(error)
+ raise Exception(f"kubectl failed : {kubectl}")
+ if message is not None:
+ _logger.info(message)
+
+
def bootstrapCluster():
class SecretType(Enum):
@@ -30,6 +59,9 @@ def bootstrapCluster():
BASICAUTH = auto()
REGSITRY = auto()
DASHBOARD = auto()
+ DO = auto()
+ STATUSSITE = auto()
+ SHEVASTREAM = auto()
def createNamespace(namespace):
try:
@@ -41,19 +73,27 @@ def bootstrapCluster():
else:
raise e
- def createTlsSecret(namespace, type):
+ def createSecret(namespace, type):
try:
- if type == SecretType.TLS:
+ data = None
+ stringData = None
+
+ if type == SecretType.TLS or type == SecretType.DASHBOARD:
with open(_secretsPath / "certificate.key", "r") as key:
with open(_secretsPath / "certificate.crt", "r") as certificate:
- data = {'tls.crt': base64.b64encode(certificate.read().encode()).decode(), 'tls.key': base64.b64encode(key.read().encode()).decode()}
- secType = 'kubernetes.io/tls'
- metadata = {'name': 'lets-encrypt'}
+ if type == SecretType.TLS:
+ data = {"tls.crt": base64.b64encode(certificate.read().encode()).decode(), "tls.key": base64.b64encode(key.read().encode()).decode()}
+ secType = "kubernetes.io/tls"
+ metadata = {"name": "lets-encrypt"}
+ else:
+ data = {"certificate.crt": base64.b64encode(certificate.read().encode()).decode(), "certificate.key": base64.b64encode(key.read().encode()).decode()}
+ secType = "Opaque"
+ metadata = {"name": "kubernetes-dashboard-certs"}
elif type == SecretType.BASICAUTH:
with open(_secretsPath / "auth", "r") as auth:
- data = {'auth': base64.b64encode(auth.read().encode()).decode()}
- secType = 'Opaque'
- metadata = {'name': 'basic-auth'}
+ data = {"auth": base64.b64encode(auth.read().encode()).decode()}
+ secType = "Opaque"
+ metadata = {"name": "basic-auth"}
elif type == SecretType.REGSITRY:
data = {
".dockerconfigjson": base64.b64encode(
@@ -69,10 +109,24 @@ def bootstrapCluster():
}).encode()
).decode()
}
- secType = 'kubernetes.io/dockerconfigjson'
- metadata = {'name': 'regsecret'}
-
- api.create_namespaced_secret(namespace, client.V1Secret(data=data, metadata=metadata, api_version="v1", type=secType))
+ secType = "kubernetes.io/dockerconfigjson"
+ metadata = {"name": "regsecret"}
+ elif type == SecretType.DO:
+ stringData = {"access-token": _doToken}
+ secType = "Opaque"
+ metadata = {"name": "digitalocean"}
+ elif type == SecretType.STATUSSITE:
+ with open(_secretsPath / "appsettings.production.yml", "r") as config:
+ data = {"appsettings.production.yml": base64.b64encode(config.read().encode()).decode()}
+ secType = "Opaque"
+ metadata = {"name": "appsettings.production.yml"}
+ elif type == SecretType.SHEVASTREAM:
+ with open(cwd / "sources" / "shevastream" / "appsettings.json", "r") as config:
+ data = {"appsettings.json": base64.b64encode(config.read().encode()).decode()}
+ secType = "Opaque"
+ metadata = {"name": "shevastream-appsettings"}
+
+ api.create_namespaced_secret(namespace, client.V1Secret(data=data, string_data=stringData, metadata=metadata, api_version="v1", type=secType))
_logger.info(f"Secret {type} added to {namespace}")
except ApiException as e:
if "already exists" in e.body:
@@ -80,6 +134,12 @@ def bootstrapCluster():
else:
raise e
+ def createDashboardIngress():
+ templates = Environment(loader=FileSystemLoader(searchpath=str(cwd / "sources" / "dashboard")))
+ config = templates.get_template("ingress.yaml").render(data={"token": "TODO"})
+ print(config)
+ apply(config)
+
if not (cwd / "kubeconfig.yaml").is_file():
raise "Bootstrap called with no Kubeconfig file"
@@ -89,9 +149,20 @@ def bootstrapCluster():
for namespace in namespaces:
createNamespace(namespace)
- createTlsSecret(namespace, SecretType.TLS)
- createTlsSecret(namespace, SecretType.BASICAUTH)
- createTlsSecret(namespace, SecretType.REGSITRY)
+ createSecret(namespace, SecretType.TLS)
+ createSecret(namespace, SecretType.BASICAUTH)
+ createSecret(namespace, SecretType.REGSITRY)
+
+ createSecret("kube-system", SecretType.DASHBOARD)
+ createSecret("kube-system", SecretType.DO)
+ createSecret("status-site", SecretType.STATUSSITE)
+ createSecret("websites", SecretType.SHEVASTREAM)
+
+ apply("sources/dashboard/all.yaml", "Dashboard")
+ apply("sources/nginx/mandatory.yaml", "NGINX ingress controller")
+
+ apply("https://raw.githubusercontent.com/digitalocean/csi-digitalocean/master/deploy/kubernetes/releases/csi-digitalocean-v0.3.1.yaml", "DO Volume provisioner")
+ createDashboardIngress()
def provisionCluster():
@@ -134,9 +205,6 @@ def generateServices():
def generateService(name, image, replicated=True, auth=False, rps=10):
- import subprocess
- from jinja2 import Template, Environment, FileSystemLoader
-
_logger.info(f"Generating {name} service")
if name in domainUrlExceptions:
@@ -168,7 +236,8 @@ def generateService(name, image, replicated=True, auth=False, rps=10):
"replicated": replicated,
"rps": rps,
"auth": auth,
- "hosts": hosts
+ "hosts": hosts,
+ "secret": None if "shevastream" not in name else ("appsettings", "/run/secrets/settings/", "shevastream-appsettings", "appsettings", "appsettings.production.json")
}
)
@@ -196,7 +265,7 @@ def kubeconfig():
if response.status_code == 200:
try:
- response = response.content.decode('utf-8')
+ response = response.content.decode("utf-8")
_logger.debug(f"Kubeconfig \n{response}")
with open(cwd / "kubeconfig.yaml", "w") as kubeconfigFile:
kubeconfigFile.write(response)
@@ -222,14 +291,14 @@ def setup_logger(level):
datefmt=None,
reset=True,
log_colors={
- 'DEBUG': 'cyan',
- 'INFO': 'green',
- 'WARNING': 'yellow',
- 'ERROR': 'red',
- 'CRITICAL': 'red',
+ "DEBUG": "cyan",
+ "INFO": "green",
+ "WARNING": "yellow",
+ "ERROR": "red",
+ "CRITICAL": "red",
}
)
- logger = logging.getLogger('example')
+ logger = logging.getLogger("example")
handler = logging.StreamHandler()
handler.setFormatter(formatter)
logger.addHandler(handler)
@@ -267,7 +336,7 @@ def main():
parser = argparse.ArgumentParser(description="Deploy K8S to Digital Ocean.")
- parser.add_argument('--verbose', '-v', dest="verbose", action='count', default=4, help="Log level: CRITICAL, ERROR, WARNING, INFO, DEBUG")
+ parser.add_argument("--verbose", "-v", dest="verbose", action="count", default=4, help="Log level: CRITICAL, ERROR, WARNING, INFO, DEBUG")
subparsers = parser.add_subparsers(title="commands", dest="command", required=True)
@@ -287,6 +356,7 @@ def main():
bootstrapParser = subparsers.add_parser("bootstrap")
addSecretsPathArgument(bootstrapParser)
addDockerPassArgument(bootstrapParser)
+ addDOTokenArgument(bootstrapParser)
args = parser.parse_args()
@@ -297,10 +367,10 @@ def main():
global _secretsPath
_logger = setup_logger(60 - args.verbose * 10)
- _doToken = args.doToken if 'doToken' in args else ""
- _name = args.name if 'name' in args else ""
- _dockerPass = args.dockerPass if 'dockerPass' in args else ""
- _secretsPath = args.secretsPath if 'secretsPath' in args else ""
+ _doToken = args.doToken if "doToken" in args else ""
+ _name = args.name if "name" in args else ""
+ _dockerPass = args.dockerPass if "dockerPass" in args else ""
+ _secretsPath = args.secretsPath if "secretsPath" in args else ""
if args.command == "deploy":
deploy()
@@ -394,5 +464,5 @@ domainUrlExceptions = {
namespaces = ["websites", "monitoring", "ingress", "status-site", "kube-system", "gitlab", "review"]
-if __name__ == '__main__':
+if __name__ == "__main__":
main()
diff --git a/infra/sources/dashboard/ingress.yaml b/infra/sources/dashboard/ingress.yaml
index 5569bf1..e87dfbf 100644
--- a/infra/sources/dashboard/ingress.yaml
+++ b/infra/sources/dashboard/ingress.yaml
@@ -8,7 +8,7 @@ metadata:
nginx.ingress.kubernetes.io/auth-realm: "Authentication Required!"
nginx.ingress.kubernetes.io/auth-type: basic
nginx.ingress.kubernetes.io/configuration-snippet: |
- proxy_set_header Authorization "Bearer __DASHBOARD_TOKEN__";
+ proxy_set_header Authorization "Bearer {{ data.token }}";
name: dashboard
namespace: kube-system
spec:
diff --git a/infra/sources/service/website.yaml b/infra/sources/service/website.yaml
index 72ea582..7375d13 100644
--- a/infra/sources/service/website.yaml
+++ b/infra/sources/service/website.yaml
@@ -49,6 +49,19 @@ spec:
- name: {{ data.name }}
image: {{ data.image }}
imagePullPolicy: Always
+ {% if data.secret is not none %}
+ volumeMounts:
+ - name: {{ data.secret[0] }}
+ mountPath: {{ data.secret[1] }}
+ volumes:
+ - name: {{ data.secret[0] }}
+ secret:
+ secretName: {{ data.secret[2] }}
+ items:
+ - key: {{ data.secret[3] }}
+ path: {{ data.secret[4] }}
+ {% endif %}
+
{% else %}
--
GitLab