Compare commits
2 commits
49a1096152
...
89db22f1eb
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
89db22f1eb | ||
|
|
8fe1a01930 |
4 changed files with 144 additions and 2 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -17,8 +17,10 @@ coverage
|
||||||
.deploy-info
|
.deploy-info
|
||||||
deploy/.last-image
|
deploy/.last-image
|
||||||
|
|
||||||
creds/
|
creds/*
|
||||||
!creds/secrets.enc.env
|
!creds/secrets.enc.env
|
||||||
|
!creds/kubeconfig.enc.yaml
|
||||||
|
creds/kubeconfig.yaml
|
||||||
k3s.yaml
|
k3s.yaml
|
||||||
|
|
||||||
# Local-only documentation (now tracked in docs/)
|
# Local-only documentation (now tracked in docs/)
|
||||||
|
|
|
||||||
43
creds/kubeconfig.enc.yaml
Normal file
43
creds/kubeconfig.enc.yaml
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
apiVersion: v1
|
||||||
|
clusters:
|
||||||
|
- cluster:
|
||||||
|
certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJkekNDQVIyZ0F3SUJBZ0lCQURBS0JnZ3Foa2pPUFFRREFqQWpNU0V3SHdZRFZRUUREQmhyTTNNdGMyVnkKZG1WeUxXTmhRREUzTmpNNE1qSTNOakF3SGhjTk1qVXhNVEl5TVRRME5qQXdXaGNOTXpVeE1USXdNVFEwTmpBdwpXakFqTVNFd0h3WURWUVFEREJock0zTXRjMlZ5ZG1WeUxXTmhRREUzTmpNNE1qSTNOakF3V1RBVEJnY3Foa2pPClBRSUJCZ2dxaGtqT1BRTUJCd05DQUFSRVlQd3pheHhFRkdPak8xL2N0NnBFaHlNaXNGVytLWDBCQjcvcmthTVIKRmhNRkxWTDFwVEtvMitrd1ZVbUVBZEpoTHErZkdYMFlMMjZJK1dXcTBVZkdvMEl3UURBT0JnTlZIUThCQWY4RQpCQU1DQXFRd0R3WURWUjBUQVFIL0JBVXdBd0VCL3pBZEJnTlZIUTRFRmdRVUovTDJkNDF6UTlRK3QvM3hiRmdYCm1KRHBpbjR3Q2dZSUtvWkl6ajBFQXdJRFNBQXdSUUlnUDlQY2lCNzZSRlA0WmVrWjRTNy9QNFVaRnBjLzJRTTYKUll2M2pBcFpGWHNDSVFEeGg2QTdIakdjampwbS9tbmVpQzRpUFJJaDBaamdzWHlYdGxwelNDRUlmUT09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
|
||||||
|
server: https://157.180.66.64:6443
|
||||||
|
name: default
|
||||||
|
contexts:
|
||||||
|
- context:
|
||||||
|
cluster: default
|
||||||
|
user: default
|
||||||
|
name: default
|
||||||
|
current-context: default
|
||||||
|
kind: Config
|
||||||
|
preferences: {}
|
||||||
|
users:
|
||||||
|
- name: default
|
||||||
|
user:
|
||||||
|
client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJqekNDQVRlZ0F3SUJBZ0lJREl2MWE5MUFRVDR3Q2dZSUtvWkl6ajBFQXdJd0l6RWhNQjhHQTFVRUF3d1kKYXpOekxXTnNhV1Z1ZEMxallVQXhOell6T0RJeU56WXdNQjRYRFRJMU1URXlNakUwTkRZd01Gb1hEVEkyTVRFeQpNakUwTkRZd01Gb3dNREVYTUJVR0ExVUVDaE1PYzNsemRHVnRPbTFoYzNSbGNuTXhGVEFUQmdOVkJBTVRESE41CmMzUmxiVHBoWkcxcGJqQlpNQk1HQnlxR1NNNDlBZ0VHQ0NxR1NNNDlBd0VIQTBJQUJENTJtL2xZNThLQko2aUMKc0JLbldHeUY2cytyQ3BESG5vZkQySVpkVmVIMGc5MWw4Qms2aGFsOUZyQ2tPOXN2T2RGOTdCU28xVlI4M1NlRwpLdXdpaGZHalNEQkdNQTRHQTFVZER3RUIvd1FFQXdJRm9EQVRCZ05WSFNVRUREQUtCZ2dyQmdFRkJRY0RBakFmCkJnTlZIU01FR0RBV2dCU3c1d2xoTjd3Zm9jRSs0ZThkV3NvWjBmdmh2REFLQmdncWhrak9QUVFEQWdOR0FEQkQKQWlCYWNTQTZ4VGp5Z2twSnBhSE1HbG1iSmJxWlJhK2ExN091QmsyK0dIeU9GZ0lmVitsK1N5S1NiZmVvTkVFSgp3bmRkQUM5L3d3ZHRxOVFZekp1eVJTdHlIdz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0KLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJlRENDQVIyZ0F3SUJBZ0lCQURBS0JnZ3Foa2pPUFFRREFqQWpNU0V3SHdZRFZRUUREQmhyTTNNdFkyeHAKWlc1MExXTmhRREUzTmpNNE1qSTNOakF3SGhjTk1qVXhNVEl5TVRRME5qQXdXaGNOTXpVeE1USXdNVFEwTmpBdwpXakFqTVNFd0h3WURWUVFEREJock0zTXRZMnhwWlc1MExXTmhRREUzTmpNNE1qSTNOakF3V1RBVEJnY3Foa2pPClBRSUJCZ2dxaGtqT1BRTUJCd05DQUFRSDIyUThDNVlRWi9jdGFDeFUra20rbWJOdmhXbTZBV3BBQ0lySnV4SjkKRmQydmQweTlNelNnSjV0WHJobmRDTzRaZG92QUVSSTZVcklleDRnWGswdU9vMEl3UURBT0JnTlZIUThCQWY4RQpCQU1DQXFRd0R3WURWUjBUQVFIL0JBVXdBd0VCL3pBZEJnTlZIUTRFRmdRVXNPY0pZVGU4SDZIQlB1SHZIVnJLCkdkSDc0Ynd3Q2dZSUtvWkl6ajBFQXdJRFNRQXdSZ0loQU5TcEVORDdZaXBqRW1QL2d4TlN5akJEVy9XaDlLZVQKOVFtQ1BCTTZQMGp3QWlFQW1ER2ZHL2dFUEpRd2Yrd0I5Tk1KS2VTbEZNOVVUc0x1NEFmTWZZZGx6MWs9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
|
||||||
|
client-key-data: LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCk1IY0NBUUVFSU9MS2RKUTJXdFQybThIU2dOUmhzcjFZZVc2S3duTTE3YzIyckZPZlExWStvQW9HQ0NxR1NNNDkKQXdFSG9VUURRZ0FFUG5hYitWam53b0VucUlLd0VxZFliSVhxejZzS2tNZWVoOFBZaGwxVjRmU0QzV1h3R1RxRgpxWDBXc0tRNzJ5ODUwWDNzRktqVlZIemRKNFlxN0NLRjhRPT0KLS0tLS1FTkQgRUMgUFJJVkFURSBLRVktLS0tLQo=
|
||||||
|
sops:
|
||||||
|
age:
|
||||||
|
- recipient: age1ducvqxdzdhhluftu5hv4f2xsppmn803uh8tnnqj92v4n7nf6lprq9h3dqp
|
||||||
|
enc: |
|
||||||
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBNYk93aHdHMXBtaENuVFFq
|
||||||
|
TlcrQjdBOXhmenBHRGhQT1NlbkYzSTQ4ZmlnCjRQV3FBakdhUERsblBCWXVVaWdp
|
||||||
|
dENyMkhsTUZDQmdja3hYU3daQ0Jpcm8KLS0tIDgzdi9menlJQ1k5VjNTR29qUGEy
|
||||||
|
RDZyQURucHdncit1WVZqakJaSlNQVVUKtt/O9q3mc/8Q0b7ruG3djq60Tdjr2ZjS
|
||||||
|
YhRaBIC5KJlQjEdNoez6m03yAwV+u10iiLKiKKfxAlBcp1nhvYeqsA==
|
||||||
|
-----END AGE ENCRYPTED FILE-----
|
||||||
|
- recipient: age1hkehkc2rryjl975c2mg5cghmjr54n4wjshncl292h2eg5l394fhs4uydrh
|
||||||
|
enc: |
|
||||||
|
-----BEGIN AGE ENCRYPTED FILE-----
|
||||||
|
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBJUUxMQ05ENjZQeHRLWjhX
|
||||||
|
dzBxUjJlaHUrOGRLb3FHUU1UempUZWhVdVdnCjBQNUx1NkJXL2NJNkhRN3dRcm9G
|
||||||
|
Sy9ZWmFQY3FUQWdSOWVIRXlXa2xSeW8KLS0tIE00bHNackxTVFFXYkxIWGZVakxL
|
||||||
|
SHd5Q243Z2txRlo1R21LREFpUFV5ZUUKNvT8IQCdxGtAXASfrcDxK5OLzcXTRV9G
|
||||||
|
0SoZ74sO+cWOs42tJBPxMIXaTmw2tQHqjOdtUJSZWhSqKlGsDFt/Aw==
|
||||||
|
-----END AGE ENCRYPTED FILE-----
|
||||||
|
lastmodified: "2025-12-13T21:30:04Z"
|
||||||
|
mac: ENC[AES256_GCM,data:EzGg5/wMDyjFvRGnVO+MqnLFH03EeSHABOIzPK2zDSXijjSyqFhRmLLVldJGXRMMs+yHOLYPsMcumgf2GGHANun1P3hCZe2NW3wXnpW3JQg3nrdgBMUGxwqqjGpHKNd9ZegJ4xadncS6EzYF/SPpm7UUzGTl0PRmNyFoxPbM1Hg=,iv:Lor3ifJgvh/KQsm6Kh8xavLICxNH8PWajKGTSOTylro=,tag:fCvONoPAwnVHsm290yJVTg==,type:str]
|
||||||
|
encrypted_regex: ^(AUTH_SECRET|DATABASE_URL|DB_.*|APP_URL|SMTP_.*|DKIM_.*|AUTO_APPROVE_LISTINGS|OPENAI_.*|H(ETZNER|CLOUD)_TOKEN|JOKER_DYNDNS_.*|REGISTRY_.*|NETDATA_.*|ADMIN_.*)$
|
||||||
|
version: 3.11.0
|
||||||
|
|
@ -10,7 +10,7 @@ Kubeconfig
|
||||||
- By default `deploy/deploy.sh` will use `$KUBECONFIG`. If that is unset and `creds/kubeconfig.yaml` exists, it will export `KUBECONFIG=$PWD/creds/kubeconfig.yaml`.
|
- By default `deploy/deploy.sh` will use `$KUBECONFIG`. If that is unset and `creds/kubeconfig.yaml` exists, it will export `KUBECONFIG=$PWD/creds/kubeconfig.yaml`.
|
||||||
- Recommended flow for new devs:
|
- Recommended flow for new devs:
|
||||||
1) Obtain the kubeconfig from the cluster admin.
|
1) Obtain the kubeconfig from the cluster admin.
|
||||||
2) Save it as `creds/kubeconfig.yaml` (ignored by git), or set `KUBECONFIG` to your own path.
|
2) Save it as `creds/kubeconfig.yaml` (ignored by git), or set `KUBECONFIG` to your own path. The repo also includes `creds/kubeconfig.enc.yaml` (sops/age-encrypted) and a plaintext copy can be produced with the age key.
|
||||||
3) Verify access: `kubectl get ns` (you should see `lomavuokraus-test/staging/prod`).
|
3) Verify access: `kubectl get ns` (you should see `lomavuokraus-test/staging/prod`).
|
||||||
- If you want to keep the kubeconfig in-repo but encrypted, store it as `creds/kubeconfig.enc.yaml` with sops/age and decrypt to `creds/kubeconfig.yaml` before deploying:
|
- If you want to keep the kubeconfig in-repo but encrypted, store it as `creds/kubeconfig.enc.yaml` with sops/age and decrypt to `creds/kubeconfig.yaml` before deploying:
|
||||||
- Decrypt: `SOPS_AGE_KEY_FILE=creds/age-key.txt sops -d creds/kubeconfig.enc.yaml > creds/kubeconfig.yaml`
|
- Decrypt: `SOPS_AGE_KEY_FILE=creds/age-key.txt sops -d creds/kubeconfig.enc.yaml > creds/kubeconfig.yaml`
|
||||||
|
|
@ -20,6 +20,7 @@ Deploy commands
|
||||||
- Test: `./deploy/deploy-test.sh`
|
- Test: `./deploy/deploy-test.sh`
|
||||||
- Staging (default): `./deploy/deploy-staging.sh` or `TARGET=staging ./deploy/deploy.sh`
|
- Staging (default): `./deploy/deploy-staging.sh` or `TARGET=staging ./deploy/deploy.sh`
|
||||||
- Prod: `./deploy/deploy-prod.sh`
|
- Prod: `./deploy/deploy-prod.sh`
|
||||||
|
- Check running versions vs registry: `./scripts/check-versions.sh` (needs docker + registry creds and kubeconfig).
|
||||||
|
|
||||||
Notes
|
Notes
|
||||||
- Ensure `deploy/.last-image` exists (run `deploy/build.sh` first).
|
- Ensure `deploy/.last-image` exists (run `deploy/build.sh` first).
|
||||||
|
|
|
||||||
96
scripts/check-versions.sh
Executable file
96
scripts/check-versions.sh
Executable file
|
|
@ -0,0 +1,96 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Helper: print registry "latest" tag digest and show which image each environment is running.
|
||||||
|
# - Uses docker manifest inspect to read digests.
|
||||||
|
# - Uses kubectl to read the deployed container image per namespace.
|
||||||
|
# - Relies on deploy/env.sh for registry/repo/namespaces; will load secrets for registry auth if available.
|
||||||
|
#
|
||||||
|
# Usage: ./scripts/check-versions.sh
|
||||||
|
|
||||||
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
|
||||||
|
# shellcheck source=/dev/null
|
||||||
|
source "$ROOT_DIR/deploy/env.sh"
|
||||||
|
|
||||||
|
# Load secrets (for REGISTRY_USERNAME/PASSWORD) if present.
|
||||||
|
if [[ -f "$ROOT_DIR/scripts/load-secrets.sh" ]]; then
|
||||||
|
# shellcheck source=/dev/null
|
||||||
|
source "$ROOT_DIR/scripts/load-secrets.sh" >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Prefer repo kubeconfig if none set.
|
||||||
|
if [[ -z "${KUBECONFIG:-}" && -f "$ROOT_DIR/creds/kubeconfig.yaml" ]]; then
|
||||||
|
export KUBECONFIG="$ROOT_DIR/creds/kubeconfig.yaml"
|
||||||
|
fi
|
||||||
|
|
||||||
|
login_registry() {
|
||||||
|
if [[ -n "${REGISTRY_USERNAME:-}" && -n "${REGISTRY_PASSWORD:-}" ]]; then
|
||||||
|
docker login "$REGISTRY" -u "$REGISTRY_USERNAME" -p "$REGISTRY_PASSWORD" >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
manifest_digest() {
|
||||||
|
local image="$1"
|
||||||
|
local out
|
||||||
|
out="$(
|
||||||
|
{ docker manifest inspect "$image" 2>/dev/null | python3 - <<'PY'
|
||||||
|
import json,sys
|
||||||
|
try:
|
||||||
|
raw=sys.stdin.read().strip()
|
||||||
|
if not raw:
|
||||||
|
raise SystemExit
|
||||||
|
data=json.loads(raw)
|
||||||
|
except Exception:
|
||||||
|
raise SystemExit
|
||||||
|
# Prefer amd64 manifest when multi-arch.
|
||||||
|
if "manifests" in data:
|
||||||
|
m=[m for m in data["manifests"] if m.get("platform",{}).get("architecture")=="amd64"]
|
||||||
|
if not m:
|
||||||
|
m=data["manifests"]
|
||||||
|
if m:
|
||||||
|
print(m[0].get("digest",""))
|
||||||
|
elif "config" in data and "digest" in data["config"]:
|
||||||
|
print(data["config"]["digest"])
|
||||||
|
PY
|
||||||
|
} || true
|
||||||
|
)"
|
||||||
|
echo "$out"
|
||||||
|
}
|
||||||
|
|
||||||
|
get_deployed_image() {
|
||||||
|
local namespace="$1"
|
||||||
|
kubectl -n "$namespace" get deploy "$DEPLOYMENT_NAME" -o jsonpath='{.spec.template.spec.containers[?(@.name=="'"$DEPLOYMENT_NAME"'")].image}' 2>/dev/null || true
|
||||||
|
echo
|
||||||
|
}
|
||||||
|
|
||||||
|
login_registry
|
||||||
|
|
||||||
|
LATEST_IMAGE="${REGISTRY}/${REGISTRY_REPO}:latest"
|
||||||
|
LATEST_DIGEST="$(manifest_digest "$LATEST_IMAGE")"
|
||||||
|
|
||||||
|
echo "Registry latest: $LATEST_IMAGE"
|
||||||
|
echo " digest: ${LATEST_DIGEST:-n/a (docker unavailable or unauthorized)}"
|
||||||
|
if [[ -f "$ROOT_DIR/deploy/.last-image" ]]; then
|
||||||
|
echo "Local last built: $(cat "$ROOT_DIR/deploy/.last-image")"
|
||||||
|
fi
|
||||||
|
echo
|
||||||
|
|
||||||
|
for row in "testing:$TEST_NAMESPACE" "staging:$STAGING_NAMESPACE" "prod:$PROD_NAMESPACE"; do
|
||||||
|
env_name="${row%%:*}"
|
||||||
|
ns="${row##*:}"
|
||||||
|
img="$(get_deployed_image "$ns")"
|
||||||
|
if [[ -z "$img" ]]; then
|
||||||
|
echo "Env $env_name ($ns): no image found (missing deploy?)"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
digest="$(manifest_digest "$img")"
|
||||||
|
match="no"
|
||||||
|
if [[ -n "$LATEST_DIGEST" && -n "$digest" && "$LATEST_DIGEST" == "$digest" ]]; then
|
||||||
|
match="yes"
|
||||||
|
fi
|
||||||
|
echo "Env $env_name ($ns):"
|
||||||
|
echo " image: $img"
|
||||||
|
echo " digest: ${digest:-n/a}"
|
||||||
|
echo " matches latest: $match"
|
||||||
|
done
|
||||||
Loading…
Add table
Reference in a new issue