devtools: add sourcegraph

Change-Id: Ic3c40768c761e598e0f42b17a4b9f0d4ebcb2bb2
diff --git a/devtools/kube/prod.jsonnet b/devtools/kube/prod.jsonnet
index add1899..3bdccd2 100644
--- a/devtools/kube/prod.jsonnet
+++ b/devtools/kube/prod.jsonnet
@@ -1,7 +1,9 @@
 local mirko = import "../../kube/mirko.libsonnet";
+local policies = import "../../kube/policies.libsonnet";
 
 local depotview = import "depotview.libsonnet";
 local hackdoc = import "hackdoc.libsonnet";
+local sourcegraph = import "sourcegraph.libsonnet";
 
 {
     devtools(name):: mirko.Environment(name) {
@@ -13,14 +15,23 @@
             hackdoc: hackdoc.cfg {
                 publicFQDN: "hackdoc.hackerspace.pl",
             },
+            sourcegraph: sourcegraph.cfg {
+                publicFQDN: "cs.hackerspace.pl",
+            },
         },
 
         components: {
             depotview: depotview.component(cfg.depotview, env),
             hackdoc: hackdoc.component(cfg.hackdoc, env),
+            // This is configurated manually through the web interface, q3k has an account
+            // and can create more administrative ones if needed.
+            sourcegraph: sourcegraph.component(cfg.sourcegraph, env),
         },
     },
 
     prod: self.devtools("devtools-prod") {
+        local env = self,
+        // For SourceGraph's tini container mess.
+        policy: policies.AllowNamespaceMostlySecure(env.cfg.namespace),
     },
 }
diff --git a/devtools/kube/sourcegraph.libsonnet b/devtools/kube/sourcegraph.libsonnet
new file mode 100644
index 0000000..9e2454d
--- /dev/null
+++ b/devtools/kube/sourcegraph.libsonnet
@@ -0,0 +1,101 @@
+local mirko = import "../../kube/mirko.libsonnet";
+local kube = import "../../kube/kube.libsonnet";
+
+// Deploy SourceGraph, a code serach tool. Its configuration is fully managed
+// within sourcegraph itself, including user accounts.
+
+{
+    cfg:: {
+        image: "sourcegraph/server:3.17.1",
+        publicFQDN: error "public FQDN must be set",
+        storageClassName: "waw-hdd-redundant-3",
+    },
+
+    component(cfg, env):: mirko.Component(env, "sourcegraph") {
+        local sourcegraph = self,
+        cfg+: {
+            image: cfg.image,
+            volumes+: {
+                data: kube.PersistentVolumeClaimVolume(sourcegraph.pvc.data),
+                etc: kube.PersistentVolumeClaimVolume(sourcegraph.pvc.etc),
+            },
+            securityContext: {
+                runAsUser: 0,
+                fsGroup: 70,
+            },
+            container: sourcegraph.Container("main") {
+                volumeMounts_+: {
+                    data: { mountPath: "/var/opt/sourcegraph" },
+                    etc: { mountPath: "/etc/sourcegraph" },
+                },
+                resources: {
+                    requests: {
+                        cpu: "100m",
+                        memory: "1Gi",
+                    },
+                    limits: {
+                        cpu: "1",
+                        memory: "2Gi",
+                    },
+                },
+            },
+            ports+: {
+                publicHTTP: {
+                    public: {
+                        port: 7080,
+                        dns: cfg.publicFQDN,
+                        // Authenticate as 'Anonymous' user by default. This is done in tandem
+                        // with Sourcegraphs authenticate-by-http-header feature, and is a
+                        // workaround for the lack of a public view in the self-hosted free
+                        // version of Sourcegraph.
+                        // https://twitter.com/sqs/status/1272659451292422144
+                        setHeaders: ["X-Forwarded-User Anonymous"],
+                    },
+                },
+            },
+            extraPaths: [
+                {
+                    // Redirect anonymous user settings to a service that doesn't
+                    // have any endpoints/backends.
+                    path: "/users/Anonymous/settings",
+                    backend: { serviceName: sourcegraph.blocksvc.metadata.name, servicePort: 8080 },
+                },
+            ],
+        },
+
+        blocksvc: kube.Service(sourcegraph.makeName("blocksvc")) {
+            metadata+: sourcegraph.metadata,
+            spec+: {
+                selector: null,
+                ports: [{ port: 2137, targetPort: 2137 }],
+            },
+        },
+
+        pvc: {
+            data: kube.PersistentVolumeClaim(sourcegraph.makeName("data")) {
+                metadata+: sourcegraph.metadata,
+                spec+: {
+                    storageClassName: cfg.storageClassName,
+                    accessModes: [ "ReadWriteOnce" ],
+                    resources: {
+                        requests: {
+                            storage: "40Gi",
+                        },
+                    },
+                },
+            },
+            etc: kube.PersistentVolumeClaim(sourcegraph.makeName("etc")) {
+                metadata+: sourcegraph.metadata,
+                spec+: {
+                    storageClassName: cfg.storageClassName,
+                    accessModes: [ "ReadWriteOnce" ],
+                    resources: {
+                        requests: {
+                            storage: "4Gi",
+                        },
+                    },
+                },
+            },
+        },
+    }
+}