🅱️

Fixes b/8

Change-Id: I5a5779c3688451d89c0601dc913143d75048c9f6
diff --git a/devtools/issues/b/BUILD.bazel b/devtools/issues/b/BUILD.bazel
new file mode 100644
index 0000000..36933d8
--- /dev/null
+++ b/devtools/issues/b/BUILD.bazel
@@ -0,0 +1,42 @@
+load("@io_bazel_rules_docker//container:container.bzl", "container_image", "container_layer", "container_push")
+load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
+
+go_library(
+    name = "go_default_library",
+    srcs = ["main.go"],
+    importpath = "code.hackerspace.pl/hscloud/devtools/issues/b",
+    visibility = ["//visibility:private"],
+    deps = ["@com_github_golang_glog//:go_default_library"],
+)
+
+go_binary(
+    name = "b",
+    embed = [":go_default_library"],
+    visibility = ["//visibility:public"],
+)
+
+container_layer(
+    name = "layer_bin",
+    files = [
+        ":b",
+    ],
+    directory = "/devtools/issues/",
+)
+
+container_image(
+    name = "runtime",
+    base = "@prodimage-bionic//image",
+    layers = [
+        ":layer_bin",
+    ],
+)
+
+container_push(
+    name = "push",
+    image = ":runtime",
+    format = "Docker",
+    registry = "registry.k0.hswaw.net",
+    repository = "q3k/b",
+    tag = "{BUILD_TIMESTAMP}-{STABLE_GIT_COMMIT}",
+)
+
diff --git a/devtools/issues/b/main.go b/devtools/issues/b/main.go
new file mode 100644
index 0000000..8ef8150
--- /dev/null
+++ b/devtools/issues/b/main.go
@@ -0,0 +1,63 @@
+// A minimal redirector for b/123 style links to redmine.
+
+package main
+
+import (
+	"fmt"
+	"regexp"
+
+	"github.com/golang/glog"
+
+	"flag"
+	"net/http"
+)
+
+func init() {
+	flag.Set("logtostderr", "true")
+}
+
+var (
+	flagListen  string
+	flagTarget  string
+	flagProject string
+
+	reIssue = regexp.MustCompile(`^/([0-9]+)$`)
+)
+
+func main() {
+	flag.StringVar(&flagListen, "b_listen", "0.0.0.0:8000", "Address to listen at")
+	flag.StringVar(&flagTarget, "b_target", "issues.hackerspace.pl", "Redmine instance address")
+	flag.StringVar(&flagProject, "b_project", "hswaw", "Redmine project name")
+	flag.Parse()
+
+	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+		scheme := r.URL.Scheme
+		if scheme == "" {
+			scheme = "https"
+		}
+		if r.URL.Path == "/" {
+			http.Redirect(w, r, fmt.Sprintf("%s://%s/my/page", scheme, flagTarget), 302)
+			return
+		}
+		if r.URL.Path == "/new" {
+			http.Redirect(w, r, fmt.Sprintf("%s://%s/projects/%s/issues/new", scheme, flagTarget, flagProject), 302)
+			return
+		}
+		if matches := reIssue.FindStringSubmatch(r.URL.Path); len(matches) == 2 {
+			num := matches[1]
+			http.Redirect(w, r, fmt.Sprintf("%s://%s/issues/%s", scheme, flagTarget, num), 302)
+			return
+		}
+
+		fmt.Fprintf(w, `<!DOCTYPE html>
+			<title>🅱️</title>
+			<center><iframe width="1120" height="630" src="https://www.youtube.com/embed/el0PtDvg2AE?start=4994&autoplay=1" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></center>
+		`)
+	})
+
+	glog.Infof("Listening on %q...", flagListen)
+	err := http.ListenAndServe(flagListen, nil)
+	if err != nil {
+		glog.Exit(err)
+	}
+}
diff --git a/devtools/issues/prod.jsonnet b/devtools/issues/prod.jsonnet
index 2218716..14dbbba 100644
--- a/devtools/issues/prod.jsonnet
+++ b/devtools/issues/prod.jsonnet
@@ -18,6 +18,15 @@
             namespace: "redmine",
             domain: "issues.hackerspace.pl",
 
+            b: {
+                domains: [
+                    "b.hackerspace.pl",
+                    "b.hswaw.net",
+                    "xn--137h.hswaw.net",
+                    "xn--137h.hackerspace.pl",
+                ],
+            },
+
             storage+: {
                 endpoint: "https://object.ceph-waw3.hswaw.net",
                 bucket: "issues",
diff --git a/devtools/issues/redmine.libsonnet b/devtools/issues/redmine.libsonnet
index 420e488..9c1ed6a 100644
--- a/devtools/issues/redmine.libsonnet
+++ b/devtools/issues/redmine.libsonnet
@@ -18,6 +18,11 @@
             port: 5432,
         },
 
+        b: {
+            domains: [],
+            image: "registry.k0.hswaw.net/q3k/b:315532800-6cc2f867951e123909b23955cd7bcbcc3ec24f8a",
+        },
+
         storage: {
             endpoint: error "storage.endpoint must be set",
             region: error "storage.region must be set",
@@ -120,4 +125,59 @@
             ],
         },
     },
+
+    b: (if std.length(cfg.b.domains) > 0 then {
+        deployment: app.ns.Contain(kube.Deployment("b")) {
+            spec+: {
+                replicas: 3,
+                template+: {
+                    spec+: {
+                        containers_: {
+                            default: kube.Container("default") {
+                                image: "registry.k0.hswaw.net/q3k/b:315532800-6cc2f867951e123909b23955cd7bcbcc3ec24f8a",
+                                ports_: {
+                                    http: { containerPort: 8000 },
+                                },
+                                command: [
+                                    "/devtools/issues/b",
+                                ],
+                            },
+                        },
+                    },
+                },
+            },
+        },
+        svc: app.ns.Contain(kube.Service("b")) {
+            target_pod:: app.b.deployment.spec.template,
+        },
+        ingress: app.ns.Contain(kube.Ingress("b")) {
+            metadata+: {
+                annotations+: {
+                    "kubernetes.io/tls-acme": "true",
+                    "certmanager.k8s.io/cluster-issuer": "letsencrypt-prod",
+                    "nginx.ingress.kubernetes.io/proxy-body-size": "0",
+                },
+            },
+            spec+: {
+                tls: [
+                    {
+                        hosts: cfg.b.domains,
+                        secretName: "b-tls",
+                    },
+                ],
+                rules: [
+                    {
+                        host: domain,
+                        http: {
+                            paths: [
+                                { path: "/", backend: app.b.svc.name_port },
+                            ]
+                        },
+                    }
+                    for domain in cfg.b.domains
+                ],
+            },
+        }
+    } else {}),
+
 }