feat: deploy
This commit is contained in:
parent
560014d0b8
commit
9bbf8a96aa
16 changed files with 542 additions and 292 deletions
55
Makefile
55
Makefile
|
|
@ -7,10 +7,15 @@ BDD_COMPOSE := docker compose -f docker-compose.bdd.yml
|
||||||
HELM ?= helm
|
HELM ?= helm
|
||||||
HELM_CHART := deploy/helm/go-guess
|
HELM_CHART := deploy/helm/go-guess
|
||||||
HELM_RELEASE ?= go-guess
|
HELM_RELEASE ?= go-guess
|
||||||
HELM_NAMESPACE ?= go-guess
|
HELM_NAMESPACE ?= go-insane
|
||||||
KUBECONFIG ?= $(HOME)/.config/kubectl/rancher.urpi.local.yaml
|
KUBECONFIG ?= $(HOME)/.kube/config
|
||||||
|
HELM_KUBECTL := KUBECONFIG="$(KUBECONFIG)" $(HELM)
|
||||||
|
GO_LOOSE_SET ?= --set-string api.goLoose.tenants.nmbs.clientID="$${GO_LOOSE_NMBS_CLIENT_ID:-}" \
|
||||||
|
--set-string api.goLoose.tenants.nmbs.clientSecret="$${GO_LOOSE_NMBS_CLIENT_SECRET:-}" \
|
||||||
|
--set-string api.goLoose.tenants.ypto.clientID="$${GO_LOOSE_YPTO_CLIENT_ID:-}" \
|
||||||
|
--set-string api.goLoose.tenants.ypto.clientSecret="$${GO_LOOSE_YPTO_CLIENT_SECRET:-}"
|
||||||
|
|
||||||
.PHONY: help dev db-up db-down migrate migrate-down seed backend frontend frontend-install test test-backend test-frontend test-integration test-bdd test-bdd-down test-env test-env-down lint build rancher-storage helm-lint helm-deploy-development helm-deploy-production helm-down-production helm-down-development
|
.PHONY: help dev db-up db-down migrate migrate-down seed backend frontend frontend-install test test-backend test-frontend test-integration test-bdd test-bdd-down test-env test-env-down lint build rancher-storage ingress-nginx-install cert-manager-install cluster-prepare helm-lint helm-deploy-development helm-deploy-production helm-down-production helm-down-development
|
||||||
|
|
||||||
help:
|
help:
|
||||||
@echo "make dev builds the demo docker image"
|
@echo "make dev builds the demo docker image"
|
||||||
|
|
@ -33,6 +38,9 @@ help:
|
||||||
@echo "make lint runs lint on the frontend"
|
@echo "make lint runs lint on the frontend"
|
||||||
@echo "make build builds the entire project"
|
@echo "make build builds the entire project"
|
||||||
@echo "make rancher-storage installs the persistent local-path StorageClass"
|
@echo "make rancher-storage installs the persistent local-path StorageClass"
|
||||||
|
@echo "make ingress-nginx-install installs the ingress-nginx controller serving 80/443"
|
||||||
|
@echo "make cert-manager-install installs cert-manager for chart-issued TLS certificates"
|
||||||
|
@echo "make cluster-prepare runs every cluster prerequisite in order"
|
||||||
@echo "make helm-lint lints development and production Helm configurations"
|
@echo "make helm-lint lints development and production Helm configurations"
|
||||||
@echo "make helm-deploy-development deploys the development Helm release"
|
@echo "make helm-deploy-development deploys the development Helm release"
|
||||||
@echo "make helm-deploy-production deploys the production Helm release"
|
@echo "make helm-deploy-production deploys the production Helm release"
|
||||||
|
|
@ -104,6 +112,27 @@ build:
|
||||||
rancher-storage:
|
rancher-storage:
|
||||||
KUBECONFIG="$(KUBECONFIG)" kubectl apply -f deploy/rancher/local-path.yaml
|
KUBECONFIG="$(KUBECONFIG)" kubectl apply -f deploy/rancher/local-path.yaml
|
||||||
|
|
||||||
|
ingress-nginx-install:
|
||||||
|
$(HELM_KUBECTL) repo add ingress-nginx https://kubernetes.github.io/ingress-nginx --force-update
|
||||||
|
$(HELM_KUBECTL) repo update
|
||||||
|
$(HELM_KUBECTL) upgrade --install ingress-nginx ingress-nginx/ingress-nginx \
|
||||||
|
--namespace ingress-nginx --create-namespace \
|
||||||
|
--set controller.service.type=NodePort \
|
||||||
|
--set controller.hostPort.enabled=true \
|
||||||
|
--set controller.kind=DaemonSet \
|
||||||
|
--set controller.admissionWebhooks.enabled=false \
|
||||||
|
--wait --timeout 10m
|
||||||
|
|
||||||
|
cert-manager-install:
|
||||||
|
$(HELM_KUBECTL) repo add jetstack https://charts.jetstack.io --force-update
|
||||||
|
$(HELM_KUBECTL) repo update
|
||||||
|
$(HELM_KUBECTL) upgrade --install cert-manager jetstack/cert-manager \
|
||||||
|
--namespace cert-manager --create-namespace \
|
||||||
|
--set crds.enabled=true \
|
||||||
|
--wait --timeout 10m
|
||||||
|
|
||||||
|
cluster-prepare: rancher-storage ingress-nginx-install cert-manager-install
|
||||||
|
|
||||||
helm-lint:
|
helm-lint:
|
||||||
KUBECONFIG="$(KUBECONFIG)" $(HELM) lint $(HELM_CHART) -f $(HELM_CHART)/values-development.yaml
|
KUBECONFIG="$(KUBECONFIG)" $(HELM) lint $(HELM_CHART) -f $(HELM_CHART)/values-development.yaml
|
||||||
KUBECONFIG="$(KUBECONFIG)" $(HELM) lint $(HELM_CHART) -f $(HELM_CHART)/values-production.yaml
|
KUBECONFIG="$(KUBECONFIG)" $(HELM) lint $(HELM_CHART) -f $(HELM_CHART)/values-production.yaml
|
||||||
|
|
@ -113,17 +142,25 @@ helm-lint:
|
||||||
-f $(HELM_CHART)/values-production.yaml >/dev/null
|
-f $(HELM_CHART)/values-production.yaml >/dev/null
|
||||||
|
|
||||||
helm-deploy-development:
|
helm-deploy-development:
|
||||||
KUBECONFIG="$(KUBECONFIG)" $(HELM) upgrade --install $(HELM_RELEASE) $(HELM_CHART) --namespace $(HELM_NAMESPACE)-development --create-namespace -f $(HELM_CHART)/values-development.yaml --rollback-on-failure --wait --timeout 10m
|
$(HELM_KUBECTL) upgrade --install $(HELM_RELEASE) $(HELM_CHART) \
|
||||||
|
--namespace $(HELM_NAMESPACE)-development --create-namespace \
|
||||||
|
-f $(HELM_CHART)/values-development.yaml \
|
||||||
|
--set secrets.postgresqlPassword="go_guess" \
|
||||||
|
--set secrets.rabbitmqPassword="guest" \
|
||||||
|
$(GO_LOOSE_SET) \
|
||||||
|
--rollback-on-failure --wait --timeout 10m
|
||||||
|
|
||||||
helm-deploy-production:
|
helm-deploy-production:
|
||||||
KUBECONFIG="$(KUBECONFIG)" $(HELM) upgrade --install $(HELM_RELEASE) $(HELM_CHART) \
|
$(HELM_KUBECTL) upgrade --install $(HELM_RELEASE) $(HELM_CHART) \
|
||||||
--namespace $(HELM_NAMESPACE)-production --create-namespace \
|
--namespace $(HELM_NAMESPACE)-production --create-namespace \
|
||||||
-f $(HELM_CHART)/values-production.yaml --wait --timeout 10m\
|
-f $(HELM_CHART)/values-production.yaml \
|
||||||
--set secrets.postgresqlPassword="go_guess" \
|
--set secrets.postgresqlPassword="go_guess" \
|
||||||
--set secrets.rabbitmqPassword="guest"
|
--set secrets.rabbitmqPassword="guest" \
|
||||||
|
$(GO_LOOSE_SET) \
|
||||||
|
--wait --timeout 10m
|
||||||
|
|
||||||
helm-down-production:
|
helm-down-production:
|
||||||
KUBECONFIG="$(KUBECONFIG)" $(HELM) uninstall $(HELM_RELEASE) -n $(HELM_NAMESPACE)-production
|
$(HELM_KUBECTL) uninstall $(HELM_RELEASE) -n $(HELM_NAMESPACE)-production
|
||||||
|
|
||||||
helm-down-development:
|
helm-down-development:
|
||||||
KUBECONFIG="$(KUBECONFIG)" $(HELM) uninstall $(HELM_RELEASE) -n $(HELM_NAMESPACE)-development
|
$(HELM_KUBECTL) uninstall $(HELM_RELEASE) -n $(HELM_NAMESPACE)-development
|
||||||
|
|
|
||||||
86
README.md
86
README.md
|
|
@ -156,36 +156,56 @@ make test-env-down
|
||||||
|
|
||||||
## Production image
|
## Production image
|
||||||
|
|
||||||
Build the complete application as a single image:
|
Two images are published to the private registry: `go-guess-api:0.0.1` for the Go
|
||||||
|
service and `go-guess-web:0.0.1` for the React front end behind nginx.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker build -t go-guess .
|
docker build -t batty1039.startdedicated.net:5000/go-guess-api:0.0.1 ./backend
|
||||||
|
docker build -t batty1039.startdedicated.net:5000/go-guess-web:0.0.1 ./frontend
|
||||||
```
|
```
|
||||||
|
|
||||||
The image serves the frontend and API on port `8080`. It applies database
|
The API image listens on `8080` and applies database migrations and shared
|
||||||
migrations and shared production fixtures before startup. PostgreSQL, RabbitMQ,
|
fixtures before serving. The web image listens on `80`, serves the built assets,
|
||||||
and these environment variables must be provided:
|
and proxies `/api/` to `http://api:8080`, so both containers must share a
|
||||||
|
network with the API reachable under the DNS name `api`. PostgreSQL, RabbitMQ,
|
||||||
|
and these environment variables must be provided to the API:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker run --rm -p 8080:8080 \
|
docker run --rm -p 8080:8080 \
|
||||||
-e DATABASE_URL='postgres://user:password@postgres:5432/go_guess?sslmode=disable' \
|
-e DATABASE_URL='postgres://user:password@postgres:5432/go_guess?sslmode=disable' \
|
||||||
-e RABBITMQ_URL='amqp://user:password@rabbitmq:5672/' \
|
-e RABBITMQ_URL='amqp://user:password@rabbitmq:5672/' \
|
||||||
-e JWT_SECRET='replace-with-at-least-32-characters' \
|
-e JWT_SECRET='replace-with-at-least-32-characters' \
|
||||||
-e FRONTEND_URL='https://go-guess.example.com' \
|
-e FRONTEND_URL='https://nmbs.guess.local' \
|
||||||
go-guess
|
batty1039.startdedicated.net:5000/go-guess-api:0.0.1
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The `Dockerfile` at the repository root still builds one combined image that
|
||||||
|
serves both halves on port `8080`. It is convenient for local runs and browser
|
||||||
|
BDD tests, while Kubernetes deploys the two images separately.
|
||||||
|
|
||||||
Pushes to `master` publish `go-guess:latest` and `go-guess:<commit-sha>` to the
|
Pushes to `master` publish `go-guess:latest` and `go-guess:<commit-sha>` to the
|
||||||
private registry configured in `.github/workflows/publish-image.yml`. The
|
private registry configured in `.github/workflows/publish-image.yml`. The
|
||||||
repository must provide `REGISTRY_USERNAME` and `REGISTRY_PASSWORD` secrets.
|
repository must provide `REGISTRY_USERNAME` and `REGISTRY_PASSWORD` secrets.
|
||||||
|
|
||||||
## Rancher and Helm
|
## Rancher and Helm
|
||||||
|
|
||||||
The chart under `deploy/helm/go-guess` deploys the application, PostgreSQL, and
|
The chart under `deploy/helm/go-guess` deploys the API image, the web image, and
|
||||||
RabbitMQ. It works with any Kubernetes cluster managed by Rancher; Rancher's free
|
their PostgreSQL and RabbitMQ. Every tenant gets its own HTTPS host at
|
||||||
edition does not restrict Helm or GitHub Actions deployments.
|
`https://<tenant>.guess.local`. The chart values are documented in
|
||||||
|
[deploy/helm/go-guess/README.md](deploy/helm/go-guess/README.md).
|
||||||
|
|
||||||
Validate or deploy one of the environment profiles:
|
One web deployment serves every tenant host because the API resolves the tenant
|
||||||
|
from the request `Host` header, so a new tenant is one entry in `tenants`.
|
||||||
|
|
||||||
|
Install the cluster prerequisites once per cluster. Storage for persistent
|
||||||
|
volumes, an ingress-nginx controller for the `nginx` ingress class, and
|
||||||
|
cert-manager for the chart-issued certificate:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make cluster-prepare
|
||||||
|
```
|
||||||
|
|
||||||
|
Then validate or deploy one of the environment profiles:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
make helm-lint
|
make helm-lint
|
||||||
|
|
@ -193,25 +213,41 @@ make helm-deploy-development
|
||||||
make helm-deploy-production
|
make helm-deploy-production
|
||||||
```
|
```
|
||||||
|
|
||||||
For the repository's single-node Docker Rancher setup, run
|
Development uses `latest` images, development fixtures, and ephemeral database
|
||||||
`make rancher-storage` once before the first production deployment. This
|
and broker storage in namespace `go-insane-development`. Production uses the
|
||||||
installs the default persistent StorageClass documented in
|
released `0.0.1` tags, production fixtures, and persistent volumes in namespace
|
||||||
`rancher-install.txt`.
|
`go-insane-production`.
|
||||||
|
|
||||||
Development uses demo fixtures, ephemeral database and message-broker storage,
|
Certificates are self-signed and issued by the chart, so every browser or client
|
||||||
and NodePort `30080`. Production uses production fixtures, persistent volumes,
|
that opens a tenant host needs the CA from the release:
|
||||||
generated secrets that are preserved across upgrades, and larger resource
|
|
||||||
limits. It exposes the application at NodePort `31374` for the dedicated-server
|
```bash
|
||||||
Nginx proxy while PostgreSQL and RabbitMQ remain cluster-internal. The public
|
kubectl -n go-insane-production get secret go-guess-web-ca \
|
||||||
URL is `https://goguess.urpi.be`; one.com DNS, Nginx, Certbot, firewall, and
|
-o jsonpath='{.data.ca\.crt}' | base64 -d > go-guess-ca.crt
|
||||||
verification instructions are in `deploy/nginx/README.md`.
|
```
|
||||||
|
|
||||||
|
Point each tenant host at the ingress controller address, then open
|
||||||
|
`https://nmbs.guess.local` and `https://ypto.guess.local`. Browser sign-in
|
||||||
|
through Go Loose needs the per-tenant client credentials; pass them from the
|
||||||
|
environment to `make helm-deploy-production`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export GO_LOOSE_NMBS_CLIENT_ID=... GO_LOOSE_NMBS_CLIENT_SECRET=...
|
||||||
|
export GO_LOOSE_YPTO_CLIENT_ID=... GO_LOOSE_YPTO_CLIENT_SECRET=...
|
||||||
|
```
|
||||||
|
|
||||||
|
Without credentials for a tenant, that host keeps password login. The cluster
|
||||||
|
served by `~/.kube/config` is managed by Rancher at `rancher.urpi.be`, which is
|
||||||
|
the management interface and not the deployment target. One.com DNS, Nginx,
|
||||||
|
Certbot, and firewall instructions for the single-node dedicated server are in
|
||||||
|
`deploy/nginx/README.md`.
|
||||||
|
|
||||||
To use external managed services, disable the bundled StatefulSets and provide
|
To use external managed services, disable the bundled StatefulSets and provide
|
||||||
full connection URLs:
|
full connection URLs:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
helm upgrade --install go-guess deploy/helm/go-guess \
|
helm upgrade --install go-guess deploy/helm/go-guess \
|
||||||
--namespace go-guess-production --create-namespace \
|
--namespace go-insane-production --create-namespace \
|
||||||
-f deploy/helm/go-guess/values-production.yaml \
|
-f deploy/helm/go-guess/values-production.yaml \
|
||||||
--set postgresql.enabled=false \
|
--set postgresql.enabled=false \
|
||||||
--set rabbitmq.enabled=false \
|
--set rabbitmq.enabled=false \
|
||||||
|
|
@ -229,8 +265,8 @@ workflow succeeds. Create GitHub environments named `development` and
|
||||||
- Secrets `REGISTRY_USERNAME`, `REGISTRY_PASSWORD`, and
|
- Secrets `REGISTRY_USERNAME`, `REGISTRY_PASSWORD`, and
|
||||||
`GO_GUESS_JWT_SECRET`.
|
`GO_GUESS_JWT_SECRET`.
|
||||||
- Variable `RANCHER_NAMESPACE` for the target namespace.
|
- Variable `RANCHER_NAMESPACE` for the target namespace.
|
||||||
- Optional variable `GO_GUESS_FRONTEND_URL` when overriding an environment's
|
- Optional secrets `GO_LOOSE_NMBS_CLIENT_ID`, `GO_LOOSE_NMBS_CLIENT_SECRET`,
|
||||||
configured public URL.
|
`GO_LOOSE_YPTO_CLIENT_ID`, and `GO_LOOSE_YPTO_CLIENT_SECRET`.
|
||||||
|
|
||||||
Because the configured image registry uses HTTP, every Rancher cluster node must
|
Because the configured image registry uses HTTP, every Rancher cluster node must
|
||||||
trust `batty1039.startdedicated.net:5000` as an insecure registry. Use HTTPS for
|
trust `batty1039.startdedicated.net:5000` as an insecure registry. Use HTTPS for
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -30,6 +31,13 @@ type GoLooseTenant struct {
|
||||||
ClientSecret string
|
ClientSecret string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GoLooseCredentialEnv returns the environment variable names holding the Go
|
||||||
|
// Loose client credentials of a tenant.
|
||||||
|
func GoLooseCredentialEnv(slug string) (idEnv, secretEnv string) {
|
||||||
|
prefix := "GO_LOOSE_" + strings.ToUpper(slug)
|
||||||
|
return prefix + "_CLIENT_ID", prefix + "_CLIENT_SECRET"
|
||||||
|
}
|
||||||
|
|
||||||
func Load() (Config, error) {
|
func Load() (Config, error) {
|
||||||
ttl, err := time.ParseDuration(env("TOKEN_TTL", "8h"))
|
ttl, err := time.ParseDuration(env("TOKEN_TTL", "8h"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -62,15 +70,16 @@ func loadGoLoose() GoLooseConfig {
|
||||||
CAFile: os.Getenv("GO_LOOSE_CA_FILE"),
|
CAFile: os.Getenv("GO_LOOSE_CA_FILE"),
|
||||||
Tenants: map[string]GoLooseTenant{},
|
Tenants: map[string]GoLooseTenant{},
|
||||||
}
|
}
|
||||||
add := func(slug, idEnv, secretEnv string) {
|
add := func(slug string) {
|
||||||
|
idEnv, secretEnv := GoLooseCredentialEnv(slug)
|
||||||
id, secret := os.Getenv(idEnv), os.Getenv(secretEnv)
|
id, secret := os.Getenv(idEnv), os.Getenv(secretEnv)
|
||||||
if id == "" || secret == "" {
|
if id == "" || secret == "" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
cfg.Tenants[slug] = GoLooseTenant{ClientID: id, ClientSecret: secret}
|
cfg.Tenants[slug] = GoLooseTenant{ClientID: id, ClientSecret: secret}
|
||||||
}
|
}
|
||||||
add("nmbs", "GO_LOOSE_NMBS_CLIENT_ID", "GO_LOOSE_NMBS_CLIENT_SECRET")
|
add("nmbs")
|
||||||
add("ypto", "GO_LOOSE_YPTO_CLIENT_ID", "GO_LOOSE_YPTO_CLIENT_SECRET")
|
add("ypto")
|
||||||
return cfg
|
return cfg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,12 @@ package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
goloose "github.com/danyel/go-loose/client"
|
goloose "github.com/danyel/go-loose/client"
|
||||||
|
|
||||||
|
"github.com/danyel/go-guess/backend/internal/config"
|
||||||
"github.com/danyel/go-guess/backend/internal/golooseauth"
|
"github.com/danyel/go-guess/backend/internal/golooseauth"
|
||||||
webmapper "github.com/danyel/go-guess/backend/internal/web/mapper"
|
webmapper "github.com/danyel/go-guess/backend/internal/web/mapper"
|
||||||
webmodel "github.com/danyel/go-guess/backend/internal/web/model"
|
webmodel "github.com/danyel/go-guess/backend/internal/web/model"
|
||||||
|
|
@ -14,6 +16,8 @@ import (
|
||||||
var (
|
var (
|
||||||
errMissingSession = errors.New("missing Go Loose session")
|
errMissingSession = errors.New("missing Go Loose session")
|
||||||
errTenantMismatch = errors.New("Go Loose session does not match this tenant")
|
errTenantMismatch = errors.New("Go Loose session does not match this tenant")
|
||||||
|
errLoginDisabled = errors.New("Go Loose browser login is not enabled")
|
||||||
|
errNoTenantHost = errors.New("this host does not identify a configured tenant")
|
||||||
)
|
)
|
||||||
|
|
||||||
func (h *Handler) SetBrowserAuth(auth *golooseauth.Registry) {
|
func (h *Handler) SetBrowserAuth(auth *golooseauth.Registry) {
|
||||||
|
|
@ -28,27 +32,27 @@ func (h *Handler) AppDomain() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) BeginExternalLogin(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) BeginExternalLogin(w http.ResponseWriter, r *http.Request) {
|
||||||
auth, ok := h.externalAuth(r)
|
auth, slug, ok := h.externalAuthWithSlug(r)
|
||||||
if !ok {
|
if !ok {
|
||||||
http.NotFound(w, r)
|
writeExternalAuthError(w, h, slug)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
auth.LoginHandler(w, r)
|
auth.LoginHandler(w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) CompleteExternalLogin(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) CompleteExternalLogin(w http.ResponseWriter, r *http.Request) {
|
||||||
auth, ok := h.externalAuth(r)
|
auth, slug, ok := h.externalAuthWithSlug(r)
|
||||||
if !ok {
|
if !ok {
|
||||||
http.NotFound(w, r)
|
writeExternalAuthError(w, h, slug)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
auth.CallbackHandler(w, r)
|
auth.CallbackHandler(w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) ExternalLogout(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) ExternalLogout(w http.ResponseWriter, r *http.Request) {
|
||||||
auth, ok := h.externalAuth(r)
|
auth, slug, ok := h.externalAuthWithSlug(r)
|
||||||
if !ok {
|
if !ok {
|
||||||
http.NotFound(w, r)
|
writeExternalAuthError(w, h, slug)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
auth.LogoutHandler(w, r)
|
auth.LogoutHandler(w, r)
|
||||||
|
|
@ -57,7 +61,7 @@ func (h *Handler) ExternalLogout(w http.ResponseWriter, r *http.Request) {
|
||||||
func (h *Handler) ExternalSession(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) ExternalSession(w http.ResponseWriter, r *http.Request) {
|
||||||
auth, slug, ok := h.externalAuthWithSlug(r)
|
auth, slug, ok := h.externalAuthWithSlug(r)
|
||||||
if !ok {
|
if !ok {
|
||||||
http.NotFound(w, r)
|
writeExternalAuthError(w, h, slug)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if _, err := r.Cookie(golooseauth.SessionCookie); err != nil {
|
if _, err := r.Cookie(golooseauth.SessionCookie); err != nil {
|
||||||
|
|
@ -90,3 +94,17 @@ func (h *Handler) externalAuthWithSlug(r *http.Request) (*goloose.BrowserAuth, s
|
||||||
}
|
}
|
||||||
return h.browser.ForHost(r.Host)
|
return h.browser.ForHost(r.Host)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func writeExternalAuthError(w http.ResponseWriter, h *Handler, slug string) {
|
||||||
|
if h.browser == nil {
|
||||||
|
writeError(w, http.StatusNotFound, errLoginDisabled)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if slug == "" {
|
||||||
|
writeError(w, http.StatusNotFound, errNoTenantHost)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
idEnv, secretEnv := config.GoLooseCredentialEnv(slug)
|
||||||
|
writeError(w, http.StatusServiceUnavailable,
|
||||||
|
fmt.Errorf("Go Loose login is not configured for tenant %q: set %s and %s, then restart the API", slug, idEnv, secretEnv))
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -341,6 +341,52 @@ func TestTenantHostLoginRedirectsToMatchingGoLooseClient(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTenantHostLoginExplainsUnconfiguredGoLooseTenant(t *testing.T) {
|
||||||
|
h := newTestHandler(t, nil)
|
||||||
|
browser, err := golooseauth.New(config.GoLooseConfig{
|
||||||
|
AuthDomain: "auth.dev",
|
||||||
|
AppDomain: "guess.dev",
|
||||||
|
Tenants: map[string]config.GoLooseTenant{"nmbs": {ClientID: "glc_nmbs", ClientSecret: "secret"}},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
h.SetBrowserAuth(browser)
|
||||||
|
api := New(h, security.NewTokenManager("01234567890123456789012345678901", time.Hour), "http://localhost:5173")
|
||||||
|
|
||||||
|
request := httptest.NewRequest(http.MethodGet, "/api/auth/login", nil)
|
||||||
|
request.Host = "ypto.guess.dev"
|
||||||
|
response := httptest.NewRecorder()
|
||||||
|
api.ServeHTTP(response, request)
|
||||||
|
if response.Code != http.StatusServiceUnavailable ||
|
||||||
|
!strings.Contains(response.Body.String(), "GO_LOOSE_YPTO_CLIENT_ID") {
|
||||||
|
t.Fatalf("ypto login = %d %s", response.Code, response.Body.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
request = httptest.NewRequest(http.MethodGet, "/api/auth/login", nil)
|
||||||
|
request.Host = "guess.dev"
|
||||||
|
response = httptest.NewRecorder()
|
||||||
|
api.ServeHTTP(response, request)
|
||||||
|
if response.Code != http.StatusNotFound || !strings.Contains(response.Body.String(), "tenant") {
|
||||||
|
t.Fatalf("apex login = %d %s", response.Code, response.Body.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoginWithoutGoLooseRegistryIsNotFound(t *testing.T) {
|
||||||
|
api := New(
|
||||||
|
newTestHandler(t, nil),
|
||||||
|
security.NewTokenManager("01234567890123456789012345678901", time.Hour),
|
||||||
|
"http://localhost:5173",
|
||||||
|
)
|
||||||
|
request := httptest.NewRequest(http.MethodGet, "/api/auth/login", nil)
|
||||||
|
request.Host = "nmbs.guess.dev"
|
||||||
|
response := httptest.NewRecorder()
|
||||||
|
api.ServeHTTP(response, request)
|
||||||
|
if response.Code != http.StatusNotFound || !strings.Contains(response.Body.String(), "not enabled") {
|
||||||
|
t.Fatalf("login without registry = %d %s", response.Code, response.Body.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type stubDriver struct{}
|
type stubDriver struct{}
|
||||||
|
|
||||||
func (stubDriver) Open(string) (driver.Conn, error) { return stubConn{}, nil }
|
func (stubDriver) Open(string) (driver.Conn, error) { return stubConn{}, nil }
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
apiVersion: v2
|
apiVersion: v2
|
||||||
name: go-guess
|
name: go-guess
|
||||||
description: Go Guess recruitment workspace
|
description: Go Guess recruitment workspace with per-tenant web hosts
|
||||||
type: application
|
type: application
|
||||||
version: 0.2.0
|
version: 0.3.0
|
||||||
appVersion: "0.2.0"
|
appVersion: "0.3.0"
|
||||||
|
|
@ -1,17 +1,48 @@
|
||||||
Go Guess has been installed in namespace {{ .Release.Namespace }}.
|
Go Guess is installed in namespace {{ .Release.Namespace }}.
|
||||||
{{- if .Values.goLoose.enabled }}
|
|
||||||
Browser sign-in uses Go Loose at https://<tenant>.{{ .Values.goLoose.authDomain }}.
|
Components:
|
||||||
{{- else }}
|
API service/{{ include "go-guess.apiServiceName" . }}:{{ .Values.api.service.port }} (cluster-internal)
|
||||||
Browser sign-in uses the application password login. Enable goLoose to redirect tenant hosts.
|
Web service/{{ include "go-guess.webDeploymentName" . }}:{{ .Values.web.service.port }}
|
||||||
|
Ingress {{ if .Values.ingress.enabled }}class {{ if .Values.ingress.className }}{{ .Values.ingress.className }}{{ else }}default{{ end }}{{ else }}disabled{{ end }}
|
||||||
|
{{ if .Values.ingress.enabled }}
|
||||||
|
Tenant URLs:
|
||||||
|
{{- range .Values.tenants }}
|
||||||
|
{{ if $.Values.ingress.tls.enabled }}https{{ else }}http{{ end }}://{{ printf "%s.%s" . $.Values.domain }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|
||||||
{{- if .Values.ingress.enabled }}
|
Point every tenant host at the ingress controller address, for example in
|
||||||
Open {{ .Values.app.frontendURL }}
|
/etc/hosts:
|
||||||
{{- else if eq .Values.service.type "NodePort" }}
|
{{- range .Values.tenants }}
|
||||||
The application is exposed on cluster node port {{ .Values.service.nodePort }}.
|
<ingress-controller-ip> {{ printf "%s.%s" . $.Values.domain }}
|
||||||
Open {{ .Values.app.frontendURL }} through the external reverse proxy.
|
{{- end }}
|
||||||
|
{{- if and .Values.ingress.tls.enabled .Values.certManager.enabled (not .Values.ingress.tls.existingSecret) }}
|
||||||
|
|
||||||
|
The certificate is self-signed, so trust the chart CA in every browser or client
|
||||||
|
that opens these hosts:
|
||||||
|
kubectl -n {{ .Release.Namespace }} get secret {{ include "go-guess.certManagerCASecretName" . }} \
|
||||||
|
-o jsonpath='{.data.ca\.crt}' | base64 -d > go-guess-ca.crt
|
||||||
|
{{- end }}
|
||||||
|
{{- if .Values.api.goLoose.enabled }}
|
||||||
|
|
||||||
|
Browser sign-in redirects to https://<tenant>.{{ .Values.api.goLoose.authDomain }}:
|
||||||
|
{{- range $tenant, $credentials := .Values.api.goLoose.tenants }}
|
||||||
|
{{- if and $credentials.clientID $credentials.clientSecret }}
|
||||||
|
{{ $tenant }}: Go Loose browser sign-in
|
||||||
{{- else }}
|
{{- else }}
|
||||||
|
{{ $tenant }}: no client credentials configured, password login only
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- else }}
|
||||||
|
|
||||||
|
Browser sign-in uses the application password login.
|
||||||
|
{{- end }}
|
||||||
|
{{- else if and (eq .Values.web.service.type "NodePort") .Values.web.service.nodePort }}
|
||||||
|
|
||||||
|
The web Service is exposed on cluster node port {{ .Values.web.service.nodePort }}.
|
||||||
|
{{- else }}
|
||||||
|
|
||||||
Run:
|
Run:
|
||||||
kubectl -n {{ .Release.Namespace }} port-forward service/{{ include "go-guess.fullname" . }} 8080:{{ .Values.service.port }}
|
kubectl -n {{ .Release.Namespace }} port-forward service/{{ include "go-guess.webDeploymentName" . }} 8080:{{ .Values.web.service.port }}
|
||||||
|
|
||||||
Then open http://127.0.0.1:8080.
|
Then open http://127.0.0.1:8080.
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|
@ -39,3 +39,72 @@ app.kubernetes.io/instance: {{ .Release.Name }}
|
||||||
{{- define "go-guess.rabbitmqName" -}}
|
{{- define "go-guess.rabbitmqName" -}}
|
||||||
{{- printf "%s-rabbitmq" (include "go-guess.fullname" .) }}
|
{{- printf "%s-rabbitmq" (include "go-guess.fullname" .) }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|
||||||
|
{{- define "go-guess.apiDeploymentName" -}}
|
||||||
|
{{- printf "%s-api" (include "go-guess.fullname" .) | trunc 63 | trimSuffix "-" }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{- define "go-guess.webDeploymentName" -}}
|
||||||
|
{{- printf "%s-web" (include "go-guess.fullname" .) | trunc 63 | trimSuffix "-" }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/* DNS name the API is reachable under. The web image hardcodes "api:8080". */}}
|
||||||
|
{{- define "go-guess.apiServiceName" -}}
|
||||||
|
{{- default "api" .Values.api.service.name }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/* Host serving one tenant, for example nmbs.guess.local. */}}
|
||||||
|
{{- define "go-guess.tenantHost" -}}
|
||||||
|
{{- printf "%s.%s" (index . 0) (index . 1) }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/* Fallback public URL for invitation and meeting links. */}}
|
||||||
|
{{- define "go-guess.frontendURL" -}}
|
||||||
|
{{- if .Values.api.frontendURL }}
|
||||||
|
{{- .Values.api.frontendURL }}
|
||||||
|
{{- else if .Values.tenants }}
|
||||||
|
{{- printf "https://%s.%s" (first .Values.tenants) .Values.domain }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/* Secret holding the wildcard-per-tenant web certificate. */}}
|
||||||
|
{{- define "go-guess.tlsSecretName" -}}
|
||||||
|
{{- if .Values.ingress.tls.existingSecret }}
|
||||||
|
{{- .Values.ingress.tls.existingSecret }}
|
||||||
|
{{- else if .Values.ingress.tls.secretName }}
|
||||||
|
{{- .Values.ingress.tls.secretName }}
|
||||||
|
{{- else }}
|
||||||
|
{{- printf "%s-web-tls" (include "go-guess.fullname" .) }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/* Cluster-scoped by default so one issuer signs every tenant release. */}}
|
||||||
|
{{- define "go-guess.certManagerIssuerRef" -}}
|
||||||
|
{{- if .Values.certManager.issuerRef }}
|
||||||
|
toYaml .Values.certManager.issuerRef
|
||||||
|
{{- else }}
|
||||||
|
name: selfsigned
|
||||||
|
kind: Issuer
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{- define "go-guess.validate" -}}
|
||||||
|
{{- if not .Values.tenants }}
|
||||||
|
{{- fail "tenants must list at least one tenant slug" }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if not .Values.domain }}
|
||||||
|
{{- fail "domain is required" }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if and .Values.ingress.enabled .Values.ingress.tls.enabled (not .Values.certManager.enabled) (not .Values.ingress.tls.existingSecret) }}
|
||||||
|
{{- fail "ingress.tls.enabled requires certManager.enabled or ingress.tls.existingSecret" }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/* Secret holding the CA that signs the leaf web certificate. */}}
|
||||||
|
{{- define "go-guess.certManagerCASecretName" -}}
|
||||||
|
{{- if .Values.certManager.caSecretName }}
|
||||||
|
{{- .Values.certManager.caSecretName }}
|
||||||
|
{{- else }}
|
||||||
|
{{- printf "%s-web-ca" (include "go-guess.fullname" .) }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
|
@ -1,105 +0,0 @@
|
||||||
apiVersion: apps/v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: {{ include "go-guess.fullname" . }}
|
|
||||||
labels:
|
|
||||||
{{- include "go-guess.labels" . | nindent 4 }}
|
|
||||||
spec:
|
|
||||||
replicas: {{ .Values.replicaCount }}
|
|
||||||
strategy:
|
|
||||||
type: {{ .Values.strategy.type }}
|
|
||||||
selector:
|
|
||||||
matchLabels:
|
|
||||||
{{- include "go-guess.selectorLabels" . | nindent 6 }}
|
|
||||||
app.kubernetes.io/component: application
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
annotations:
|
|
||||||
checksum/secrets: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }}
|
|
||||||
{{- with .Values.podAnnotations }}
|
|
||||||
{{- toYaml . | nindent 8 }}
|
|
||||||
{{- end }}
|
|
||||||
labels:
|
|
||||||
{{- include "go-guess.selectorLabels" . | nindent 8 }}
|
|
||||||
app.kubernetes.io/component: application
|
|
||||||
{{- with .Values.podLabels }}
|
|
||||||
{{- toYaml . | nindent 8 }}
|
|
||||||
{{- end }}
|
|
||||||
spec:
|
|
||||||
automountServiceAccountToken: false
|
|
||||||
{{- with .Values.imagePullSecrets }}
|
|
||||||
imagePullSecrets:
|
|
||||||
{{- toYaml . | nindent 8 }}
|
|
||||||
{{- end }}
|
|
||||||
securityContext:
|
|
||||||
{{- toYaml .Values.podSecurityContext | nindent 8 }}
|
|
||||||
containers:
|
|
||||||
- name: go-guess
|
|
||||||
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
|
|
||||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
|
||||||
securityContext:
|
|
||||||
{{- toYaml .Values.securityContext | nindent 12 }}
|
|
||||||
env:
|
|
||||||
- name: APP_ENV
|
|
||||||
value: {{ .Values.app.environment | quote }}
|
|
||||||
- name: FRONTEND_URL
|
|
||||||
value: {{ .Values.app.frontendURL | quote }}
|
|
||||||
- name: TOKEN_TTL
|
|
||||||
value: {{ .Values.app.tokenTTL | quote }}
|
|
||||||
- name: MAX_UPLOAD_MB
|
|
||||||
value: {{ .Values.app.maxUploadMB | quote }}
|
|
||||||
- name: DATABASE_URL
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: {{ include "go-guess.secretName" . }}
|
|
||||||
key: database-url
|
|
||||||
- name: RABBITMQ_URL
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: {{ include "go-guess.secretName" . }}
|
|
||||||
key: rabbitmq-url
|
|
||||||
- name: JWT_SECRET
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: {{ include "go-guess.secretName" . }}
|
|
||||||
key: jwt-secret
|
|
||||||
ports:
|
|
||||||
- name: http
|
|
||||||
containerPort: 8080
|
|
||||||
protocol: TCP
|
|
||||||
readinessProbe:
|
|
||||||
httpGet:
|
|
||||||
path: /api/health
|
|
||||||
port: http
|
|
||||||
initialDelaySeconds: 5
|
|
||||||
periodSeconds: 5
|
|
||||||
timeoutSeconds: 3
|
|
||||||
failureThreshold: 12
|
|
||||||
livenessProbe:
|
|
||||||
httpGet:
|
|
||||||
path: /api/health
|
|
||||||
port: http
|
|
||||||
initialDelaySeconds: 20
|
|
||||||
periodSeconds: 10
|
|
||||||
timeoutSeconds: 3
|
|
||||||
failureThreshold: 3
|
|
||||||
resources:
|
|
||||||
{{- toYaml .Values.resources | nindent 12 }}
|
|
||||||
volumeMounts:
|
|
||||||
- name: temporary-files
|
|
||||||
mountPath: /tmp
|
|
||||||
volumes:
|
|
||||||
- name: temporary-files
|
|
||||||
emptyDir: {}
|
|
||||||
{{- with .Values.nodeSelector }}
|
|
||||||
nodeSelector:
|
|
||||||
{{- toYaml . | nindent 8 }}
|
|
||||||
{{- end }}
|
|
||||||
{{- with .Values.affinity }}
|
|
||||||
affinity:
|
|
||||||
{{- toYaml . | nindent 8 }}
|
|
||||||
{{- end }}
|
|
||||||
{{- with .Values.tolerations }}
|
|
||||||
tolerations:
|
|
||||||
{{- toYaml . | nindent 8 }}
|
|
||||||
{{- end }}
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
apiVersion: v1
|
|
||||||
kind: Service
|
|
||||||
metadata:
|
|
||||||
name: {{ include "go-guess.fullname" . }}
|
|
||||||
labels:
|
|
||||||
{{- include "go-guess.labels" . | nindent 4 }}
|
|
||||||
spec:
|
|
||||||
type: {{ .Values.service.type }}
|
|
||||||
selector:
|
|
||||||
{{- include "go-guess.selectorLabels" . | nindent 4 }}
|
|
||||||
app.kubernetes.io/component: application
|
|
||||||
ports:
|
|
||||||
- name: http
|
|
||||||
port: {{ .Values.service.port }}
|
|
||||||
targetPort: http
|
|
||||||
protocol: TCP
|
|
||||||
{{- if and (eq .Values.service.type "NodePort") .Values.service.nodePort }}
|
|
||||||
nodePort: {{ .Values.service.nodePort }}
|
|
||||||
{{- end }}
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
{{- if .Values.ingress.enabled }}
|
{{- if .Values.ingress.enabled }}
|
||||||
|
{{- $webService := include "go-guess.webDeploymentName" . }}
|
||||||
apiVersion: networking.k8s.io/v1
|
apiVersion: networking.k8s.io/v1
|
||||||
kind: Ingress
|
kind: Ingress
|
||||||
metadata:
|
metadata:
|
||||||
|
|
@ -13,23 +14,25 @@ spec:
|
||||||
{{- with .Values.ingress.className }}
|
{{- with .Values.ingress.className }}
|
||||||
ingressClassName: {{ . }}
|
ingressClassName: {{ . }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{- with .Values.ingress.tls }}
|
{{- if .Values.ingress.tls.enabled }}
|
||||||
tls:
|
tls:
|
||||||
{{- toYaml . | nindent 4 }}
|
- hosts:
|
||||||
|
{{- range .Values.tenants }}
|
||||||
|
- {{ printf "%s.%s" . $.Values.domain | quote }}
|
||||||
|
{{- end }}
|
||||||
|
secretName: {{ include "go-guess.tlsSecretName" . }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
rules:
|
rules:
|
||||||
{{- range .Values.ingress.hosts }}
|
{{- range .Values.tenants }}
|
||||||
- host: {{ .host | quote }}
|
- host: {{ printf "%s.%s" . $.Values.domain | quote }}
|
||||||
http:
|
http:
|
||||||
paths:
|
paths:
|
||||||
{{- range .paths }}
|
- path: {{ $.Values.ingress.path }}
|
||||||
- path: {{ .path }}
|
pathType: {{ $.Values.ingress.pathType }}
|
||||||
pathType: {{ .pathType }}
|
|
||||||
backend:
|
backend:
|
||||||
service:
|
service:
|
||||||
name: {{ include "go-guess.fullname" $ }}
|
name: {{ $webService }}
|
||||||
port:
|
port:
|
||||||
number: {{ $.Values.service.port }}
|
number: {{ $.Values.web.service.port }}
|
||||||
{{- end }}
|
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|
@ -32,3 +32,14 @@ data:
|
||||||
rabbitmq-url: {{ $rabbitmqURL | b64enc | quote }}
|
rabbitmq-url: {{ $rabbitmqURL | b64enc | quote }}
|
||||||
postgresql-password: {{ $postgresPassword | b64enc | quote }}
|
postgresql-password: {{ $postgresPassword | b64enc | quote }}
|
||||||
rabbitmq-password: {{ $rabbitmqPassword | b64enc | quote }}
|
rabbitmq-password: {{ $rabbitmqPassword | b64enc | quote }}
|
||||||
|
{{- if .Values.api.goLoose.enabled }}
|
||||||
|
{{- range $tenant, $credentials := .Values.api.goLoose.tenants }}
|
||||||
|
{{- $prefix := printf "go-loose-%s" (lower $tenant) }}
|
||||||
|
{{- if $credentials.clientID }}
|
||||||
|
{{ printf "%s-client-id" $prefix }}: {{ $credentials.clientID | b64enc | quote }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if $credentials.clientSecret }}
|
||||||
|
{{ printf "%s-client-secret" $prefix }}: {{ $credentials.clientSecret | b64enc | quote }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
|
@ -17,4 +17,11 @@ spec:
|
||||||
- wget
|
- wget
|
||||||
args:
|
args:
|
||||||
- -qO-
|
- -qO-
|
||||||
- "http://{{ include "go-guess.fullname" . }}:{{ .Values.service.port }}/api/health"
|
- "http://{{ include "go-guess.apiServiceName" . }}:{{ .Values.api.service.port }}/api/health"
|
||||||
|
- name: wget-web
|
||||||
|
image: busybox:1.37
|
||||||
|
command:
|
||||||
|
- wget
|
||||||
|
args:
|
||||||
|
- -qO-
|
||||||
|
- "http://{{ include "go-guess.webDeploymentName" . }}:{{ .Values.web.service.port }}/"
|
||||||
|
|
@ -1,19 +1,21 @@
|
||||||
image:
|
# Development profile: demo fixtures, ephemeral database and broker storage, and
|
||||||
|
# `latest` image tags. TLS still uses the chart's self-signed certificate so
|
||||||
|
# https://<tenant>.guess.local behaves like the production profile.
|
||||||
|
domain: guess.local
|
||||||
|
|
||||||
|
tenants:
|
||||||
|
- nmbs
|
||||||
|
- ypto
|
||||||
|
|
||||||
|
api:
|
||||||
|
environment: development
|
||||||
|
image:
|
||||||
tag: latest
|
tag: latest
|
||||||
pullPolicy: Always
|
pullPolicy: Always
|
||||||
|
goLoose:
|
||||||
app:
|
enabled: true
|
||||||
environment: development
|
authDomain: auth.dev
|
||||||
frontendURL: http://localhost:30080
|
resources:
|
||||||
|
|
||||||
service:
|
|
||||||
type: NodePort
|
|
||||||
nodePort: 30080
|
|
||||||
|
|
||||||
ingress:
|
|
||||||
enabled: false
|
|
||||||
|
|
||||||
resources:
|
|
||||||
requests:
|
requests:
|
||||||
cpu: 50m
|
cpu: 50m
|
||||||
memory: 96Mi
|
memory: 96Mi
|
||||||
|
|
@ -21,6 +23,22 @@ resources:
|
||||||
cpu: 300m
|
cpu: 300m
|
||||||
memory: 384Mi
|
memory: 384Mi
|
||||||
|
|
||||||
|
web:
|
||||||
|
replicaCount: 1
|
||||||
|
image:
|
||||||
|
tag: latest
|
||||||
|
pullPolicy: Always
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 25m
|
||||||
|
memory: 32Mi
|
||||||
|
limits:
|
||||||
|
cpu: 200m
|
||||||
|
memory: 96Mi
|
||||||
|
|
||||||
|
ingress:
|
||||||
|
enabled: true
|
||||||
|
|
||||||
postgresql:
|
postgresql:
|
||||||
persistence:
|
persistence:
|
||||||
enabled: false
|
enabled: false
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,19 @@
|
||||||
image:
|
# Production profile: production fixtures, persistent volumes, released image
|
||||||
tag: latest
|
# tags, and self-signed certificates served for https://<tenant>.guess.local.
|
||||||
pullPolicy: IfNotPresent
|
domain: guess.local
|
||||||
|
|
||||||
app:
|
tenants:
|
||||||
|
- nmbs
|
||||||
|
- ypto
|
||||||
|
|
||||||
|
api:
|
||||||
environment: production
|
environment: production
|
||||||
frontendURL: https://goguess.urpi.be
|
image:
|
||||||
|
tag: "0.0.1"
|
||||||
service:
|
pullPolicy: IfNotPresent
|
||||||
type: NodePort
|
goLoose:
|
||||||
nodePort: 31374
|
enabled: true
|
||||||
|
resources:
|
||||||
ingress:
|
|
||||||
enabled: false
|
|
||||||
|
|
||||||
resources:
|
|
||||||
requests:
|
requests:
|
||||||
cpu: 200m
|
cpu: 200m
|
||||||
memory: 256Mi
|
memory: 256Mi
|
||||||
|
|
@ -21,6 +21,26 @@ resources:
|
||||||
cpu: "1"
|
cpu: "1"
|
||||||
memory: 768Mi
|
memory: 768Mi
|
||||||
|
|
||||||
|
web:
|
||||||
|
replicaCount: 2
|
||||||
|
image:
|
||||||
|
tag: "0.0.1"
|
||||||
|
pullPolicy: IfNotPresent
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 50m
|
||||||
|
memory: 64Mi
|
||||||
|
limits:
|
||||||
|
cpu: 300m
|
||||||
|
memory: 128Mi
|
||||||
|
|
||||||
|
ingress:
|
||||||
|
enabled: true
|
||||||
|
className: nginx
|
||||||
|
|
||||||
|
certManager:
|
||||||
|
enabled: true
|
||||||
|
|
||||||
postgresql:
|
postgresql:
|
||||||
persistence:
|
persistence:
|
||||||
enabled: true
|
enabled: true
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,142 @@
|
||||||
nameOverride: ""
|
nameOverride: ""
|
||||||
fullnameOverride: ""
|
fullnameOverride: ""
|
||||||
|
|
||||||
replicaCount: 1
|
# Public application domain. Every tenant is served from https://<tenant>.<domain>.
|
||||||
|
domain: guess.local
|
||||||
|
|
||||||
image:
|
# Tenant slugs. Each one gets its own ingress host and TLS SAN.
|
||||||
repository: batty1039.startdedicated.net:5000/go-guess
|
tenants:
|
||||||
tag: latest
|
- nmbs
|
||||||
pullPolicy: IfNotPresent
|
- ypto
|
||||||
|
|
||||||
imagePullSecrets: []
|
imagePullSecrets: []
|
||||||
|
|
||||||
app:
|
api:
|
||||||
environment: production
|
replicaCount: 1
|
||||||
frontendURL: http://localhost:8080
|
image:
|
||||||
|
repository: batty1039.startdedicated.net:5000/go-guess-api
|
||||||
|
tag: "0.0.1"
|
||||||
|
pullPolicy: IfNotPresent
|
||||||
|
environment: development
|
||||||
|
# Public base URL used when building invitation and meeting links. Defaults to
|
||||||
|
# https://<first tenant>.<domain> when empty.
|
||||||
|
frontendURL: ""
|
||||||
tokenTTL: 8h
|
tokenTTL: 8h
|
||||||
maxUploadMB: 10
|
maxUploadMB: 10
|
||||||
|
service:
|
||||||
goLoose:
|
# The web image proxies /api/ to the literal upstream name "api:8080", so this
|
||||||
|
# Service name has to stay "api" unless the web image is rebuilt.
|
||||||
|
name: api
|
||||||
|
type: ClusterIP
|
||||||
|
port: 8080
|
||||||
|
goLoose:
|
||||||
enabled: false
|
enabled: false
|
||||||
authDomain: auth.dev
|
authDomain: auth.dev
|
||||||
appDomain: guess.dev
|
|
||||||
ca:
|
ca:
|
||||||
|
# Mount an internal CA bundle and point GO_LOOSE_CA_FILE at it.
|
||||||
existingSecret: ""
|
existingSecret: ""
|
||||||
key: ca.crt
|
key: ca.crt
|
||||||
mountPath: /certs/local-ca.crt
|
mountPath: /certs/local-ca.crt
|
||||||
tenants: []
|
# Per-tenant browser authentication credentials, written to the release secret.
|
||||||
|
tenants:
|
||||||
|
nmbs:
|
||||||
|
clientID: ""
|
||||||
|
clientSecret: ""
|
||||||
|
ypto:
|
||||||
|
clientID: ""
|
||||||
|
clientSecret: ""
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 50m
|
||||||
|
memory: 128Mi
|
||||||
|
limits:
|
||||||
|
cpu: 500m
|
||||||
|
memory: 512Mi
|
||||||
|
podAnnotations: {}
|
||||||
|
podLabels: {}
|
||||||
|
# The image runs as its own "app" user (uid 100), so no uid is forced here:
|
||||||
|
# runAsNonRoot then validates whatever the image declares.
|
||||||
|
podSecurityContext: {}
|
||||||
|
securityContext:
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
capabilities:
|
||||||
|
drop:
|
||||||
|
- ALL
|
||||||
|
readOnlyRootFilesystem: true
|
||||||
|
runAsNonRoot: true
|
||||||
|
# The API container applies migrations and seeds before serving, so old pods must
|
||||||
|
# be gone before the new one starts.
|
||||||
|
strategy:
|
||||||
|
type: Recreate
|
||||||
|
|
||||||
|
web:
|
||||||
|
replicaCount: 2
|
||||||
|
image:
|
||||||
|
repository: batty1039.startdedicated.net:5000/go-guess-web
|
||||||
|
tag: "0.0.1"
|
||||||
|
pullPolicy: IfNotPresent
|
||||||
|
service:
|
||||||
|
type: ClusterIP
|
||||||
|
port: 80
|
||||||
|
nodePort: null
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 25m
|
||||||
|
memory: 32Mi
|
||||||
|
limits:
|
||||||
|
cpu: 300m
|
||||||
|
memory: 128Mi
|
||||||
|
podAnnotations: {}
|
||||||
|
podLabels: {}
|
||||||
|
# nginx workers run as uid 101, which the image already declares.
|
||||||
|
podSecurityContext: {}
|
||||||
|
# nginx binds the privileged port 80 and forks workers as the nginx user, so it
|
||||||
|
# needs the root user plus a small capability set and writable scratch paths.
|
||||||
|
securityContext:
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
capabilities:
|
||||||
|
drop:
|
||||||
|
- ALL
|
||||||
|
add:
|
||||||
|
- CHOWN
|
||||||
|
- SETGID
|
||||||
|
- SETUID
|
||||||
|
- NET_BIND_SERVICE
|
||||||
|
readOnlyRootFilesystem: false
|
||||||
|
runAsNonRoot: false
|
||||||
|
runAsUser: 0
|
||||||
|
strategy:
|
||||||
|
type: RollingUpdate
|
||||||
|
rollingUpdate:
|
||||||
|
maxSurge: 1
|
||||||
|
maxUnavailable: 0
|
||||||
|
|
||||||
|
ingress:
|
||||||
|
enabled: true
|
||||||
|
className: nginx
|
||||||
|
annotations:
|
||||||
|
nginx.ingress.kubernetes.io/proxy-body-size: 10m
|
||||||
|
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
|
||||||
|
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
|
||||||
|
nginx.ingress.kubernetes.io/proxy-buffering: "off"
|
||||||
|
nginx.ingress.kubernetes.io/ssl-redirect: "true"
|
||||||
|
path: /
|
||||||
|
pathType: Prefix
|
||||||
|
tls:
|
||||||
|
enabled: true
|
||||||
|
# Use a certificate you already manage instead of the chart-issued one.
|
||||||
|
existingSecret: ""
|
||||||
|
# Defaults to <fullname>-web-tls when empty and certManager.enabled is true.
|
||||||
|
secretName: ""
|
||||||
|
|
||||||
|
certManager:
|
||||||
|
# Requires cert-manager in the cluster: make cert-manager-install.
|
||||||
|
enabled: true
|
||||||
|
# Reuse an existing cert-manager issuer instead of creating a self-signed one.
|
||||||
|
issuerRef: {}
|
||||||
|
duration: 8760h
|
||||||
|
renewBefore: 720h
|
||||||
|
caSecretName: ""
|
||||||
|
|
||||||
secrets:
|
secrets:
|
||||||
jwtSecret: ""
|
jwtSecret: ""
|
||||||
|
|
@ -33,49 +145,6 @@ secrets:
|
||||||
postgresqlPassword: ""
|
postgresqlPassword: ""
|
||||||
rabbitmqPassword: ""
|
rabbitmqPassword: ""
|
||||||
|
|
||||||
service:
|
|
||||||
type: ClusterIP
|
|
||||||
port: 80
|
|
||||||
nodePort: null
|
|
||||||
|
|
||||||
ingress:
|
|
||||||
enabled: false
|
|
||||||
className: nginx
|
|
||||||
annotations: {}
|
|
||||||
hosts:
|
|
||||||
- host: go-guess.local
|
|
||||||
paths:
|
|
||||||
- path: /
|
|
||||||
pathType: Prefix
|
|
||||||
tls: []
|
|
||||||
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
cpu: 100m
|
|
||||||
memory: 128Mi
|
|
||||||
limits:
|
|
||||||
cpu: 500m
|
|
||||||
memory: 512Mi
|
|
||||||
|
|
||||||
podAnnotations: {}
|
|
||||||
podLabels: {}
|
|
||||||
|
|
||||||
podSecurityContext:
|
|
||||||
fsGroup: 10001
|
|
||||||
fsGroupChangePolicy: OnRootMismatch
|
|
||||||
|
|
||||||
securityContext:
|
|
||||||
allowPrivilegeEscalation: false
|
|
||||||
capabilities:
|
|
||||||
drop:
|
|
||||||
- ALL
|
|
||||||
readOnlyRootFilesystem: true
|
|
||||||
runAsNonRoot: true
|
|
||||||
runAsUser: 10001
|
|
||||||
|
|
||||||
strategy:
|
|
||||||
type: Recreate
|
|
||||||
|
|
||||||
postgresql:
|
postgresql:
|
||||||
enabled: true
|
enabled: true
|
||||||
image:
|
image:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue