kube/postgres: add versioned library

also use in mastodon-qa

Change-Id: I628293fcfe9081c350087572ecda9e51ee18238f
Reviewed-on: https://gerrit.hackerspace.pl/c/hscloud/+/1422
Reviewed-by: q3k <q3k@hackerspace.pl>
diff --git a/kube/postgres_v.libsonnet b/kube/postgres_v.libsonnet
new file mode 100644
index 0000000..1e156f4
--- /dev/null
+++ b/kube/postgres_v.libsonnet
@@ -0,0 +1,155 @@
+# PostgreSQL on Kubernetes, with versioned names
+
+local kube = import "kube.libsonnet";
+
+{
+    local postgres = self,
+    local cfg = postgres.cfg,
+    cfg:: {
+        namespace: error "namespace must be set",
+        appName: error "app name must be set",
+        storageClassName: "waw-hdd-paranoid-2",
+        prefix: "", # if set, should be 'foo-'
+        version: "10.4", # valid version tag for https://hub.docker.com/_/postgres/
+
+        image: "postgres:" + cfg.version,
+        database: error "database must be set",
+        username: error "username must be set",
+        # not literal, instead ref for env (like { secretKeyRef: ... })
+        password: error "password must be set",
+
+        storageSize: "30Gi",
+
+        # This option can be used to customize initial database creation. For
+        # available options see: https://www.postgresql.org/docs/9.5/app-initdb.html
+        # Changing this option in already existing deployments will not affect
+        # existing database.
+        initdbArgs: null,
+
+        # Extra postgres configuration options passed on startup. Accepts only
+        # string values.
+        # Example: { max_connections: "300" }
+        opts: {},
+    },
+    safeVersion:: std.strReplace(cfg.version, ".", "-"),
+
+    makeName(suffix):: cfg.prefix + suffix + postgres.safeVersion,
+
+    metadata:: {
+        namespace: cfg.namespace,
+        labels: {
+            "app.kubernetes.io/name": cfg.appName,
+            "app.kubernetes.io/managed-by": "kubecfg",
+            "app.kubernetes.io/component": "postgres_v",
+            "hswaw.net/postgres-version": postgres.safeVersion,
+        },
+    },
+
+    volumeClaim: kube.PersistentVolumeClaim(postgres.makeName("postgres")) {
+        metadata+: postgres.metadata,
+        spec+: {
+            storageClassName: cfg.storageClassName,
+            accessModes: [ "ReadWriteOnce" ],
+            resources: {
+                requests: {
+                    storage: cfg.storageSize,
+                },
+            },
+        },
+    },
+    deployment: kube.Deployment(postgres.makeName("postgres")) {
+        metadata+: postgres.metadata,
+        spec+: {
+            replicas: 1,
+            template+: {
+                spec+: {
+                    volumes_: {
+                        data: kube.PersistentVolumeClaimVolume(postgres.volumeClaim),
+                    },
+                    containers_: {
+                        postgres: kube.Container(postgres.makeName("postgres")) {
+                            image: cfg.image,
+                            ports_: {
+                                client: { containerPort: 5432 },
+                            },
+                            env_: {
+                                POSTGRES_DB: cfg.database,
+                                POSTGRES_USER: cfg.username,
+                                POSTGRES_PASSWORD: cfg.password,
+                                PGDATA: "/var/lib/postgresql/data/pgdata",
+                            } + if cfg.initdbArgs != null then {
+                                POSTGRES_INITDB_ARGS: cfg.initdbArgs,
+                            } else {},
+
+                            args: std.flatMap(
+                                function(k) ["-c", "%s=%s" % [k, cfg.opts[k]]],
+                                std.objectFields(cfg.opts),
+                            ),
+
+                            volumeMounts_: {
+                                data: { mountPath: "/var/lib/postgresql/data" },
+                            },
+                        },
+                    },
+                    securityContext: {
+                        runAsUser: 999,
+                    },
+                },
+            },
+        },
+    },
+
+    svc: kube.Service(postgres.makeName("postgres")) {
+        metadata+: postgres.metadata,
+        target_pod:: postgres.deployment.spec.template,
+        spec+: {
+            ports: [
+                { name: "client", port: 5432, targetPort: 5432, protocol: "TCP" },
+            ],
+            type: "ClusterIP",
+        },
+    },
+
+    bouncer: {
+        deployment: kube.Deployment(postgres.makeName("bouncer")) {
+            metadata+: postgres.metadata {
+                labels+: {
+                    "app.kubernetes.io/component": "bouncer_v",
+                }
+            },
+            spec+: {
+                replicas: 1,
+                template+: {
+                    spec+: {
+                        containers_: {
+                            bouncer: kube.Container(postgres.makeName("bouncer")) {
+                                image: "edoburu/pgbouncer:1.11.0",
+                                ports_: {
+                                    client: { containerPort: 5432 },
+                                },
+                                env: [
+                                    { name: "POSTGRES_PASSWORD", valueFrom: cfg.password },
+                                    { name: "DATABASE_URL", value: "postgres://%s:$(POSTGRES_PASSWORD)@%s/%s" % [cfg.username, postgres.svc.host, cfg.database] },
+                                ],
+                            },
+                        },
+                    },
+                },
+            },
+        },
+        svc: kube.Service(postgres.makeName("bouncer")) {
+            metadata+: postgres.metadata {
+                labels+: {
+                    "app.kubernetes.io/component": "bouncer",
+                }
+            },
+            target_pod:: postgres.bouncer.deployment.spec.template,
+            spec+: {
+                ports: [
+                    { name: "client", port: 5432, targetPort: 5432, protocol: "TCP" },
+                ],
+                type: "ClusterIP",
+            },
+        },
+    },
+}