Compare commits
19 Commits
03d526208b
...
feature/ba
| Author | SHA1 | Date | |
|---|---|---|---|
| ecbb1f8638 | |||
| 424be7ec7e | |||
| 5afdf00964 | |||
| 2781c96d43 | |||
| a456a11460 | |||
| b5e442d92b | |||
| 2d756295bf | |||
| 026bcb2b31 | |||
| aa6775bed2 | |||
| 06522b2f19 | |||
| 4c65035485 | |||
| 84f4bebc08 | |||
| 5394b2c714 | |||
| c4e586a7be | |||
| 1fa070b041 | |||
| 9c905355e3 | |||
| 6b1115ec28 | |||
| 2fb276a62c | |||
| 3efe1b68ef |
40
README.md
40
README.md
@@ -1,9 +1,9 @@
|
||||
# Kubernetes Cluster - GitOps Configuration
|
||||
|
||||
> **Kubernetes cluster bootstrapping and GitOps configuration repository** using ArgoCD for multi-cloud Kubernetes (UpCloud, AWS EKS, Azure AKS, GCP GKE)
|
||||
> **Kubernetes cluster bootstrapping and GitOps configuration repository** using ArgoCD for UpCloud Managed Kubernetes
|
||||
|
||||
[](https://argoproj.github.io/cd/)
|
||||
[]()
|
||||
[](https://upcloud.com/)
|
||||
|
||||
---
|
||||
|
||||
@@ -95,26 +95,14 @@ This repository contains the complete GitOps configuration for our Kubernetes cl
|
||||
│ │ ├── renovate.yaml
|
||||
│ │ ├── ... # All other Application manifests
|
||||
│ │ └── secrets.yaml
|
||||
│ ├── overlays/ # Per-cluster overrides (Kustomize)
|
||||
│ │ ├── upc-dev/ # UpCloud Dev (uses base as-is)
|
||||
│ │ ├── upc-prod/ # UpCloud Prod (patches value paths)
|
||||
│ │ ├── aws-dev/ # AWS EKS Dev
|
||||
│ │ ├── aws-prod/ # AWS EKS Prod
|
||||
│ │ ├── azure-dev/ # Azure AKS Dev
|
||||
│ │ ├── azure-prod/ # Azure AKS Prod
|
||||
│ │ ├── gcp-dev/ # GCP GKE Dev
|
||||
│ │ └── gcp-prod/ # GCP GKE Prod
|
||||
│ ├── overlays/ # Per-cluster overrides
|
||||
│ │ ├── upc-dev/ # UpCloud Dev cluster (uses base as-is)
|
||||
│ │ └── upc-prod/ # UpCloud Prod cluster (patches value paths)
|
||||
│ ├── dashboards/ # Grafana dashboard ConfigMaps
|
||||
│ └── values/ # Helm value overrides
|
||||
│ ├── base/ # Shared cloud-agnostic values
|
||||
│ ├── upc-dev/ # UpCloud Dev (storage, LB, pricing)
|
||||
│ ├── upc-prod/ # UpCloud Prod
|
||||
│ ├── aws-dev/ # AWS EKS Dev
|
||||
│ ├── aws-prod/ # AWS EKS Prod
|
||||
│ ├── azure-dev/ # Azure AKS Dev
|
||||
│ ├── azure-prod/ # Azure AKS Prod
|
||||
│ ├── gcp-dev/ # GCP GKE Dev
|
||||
│ └── gcp-prod/ # GCP GKE Prod
|
||||
│ ├── base/ # Shared values (all clusters)
|
||||
│ ├── upc-dev/ # UpCloud Dev-specific values
|
||||
│ └── upc-prod/ # UpCloud Prod-specific values
|
||||
│
|
||||
├── apps/ # Business Applications
|
||||
│ ├── mcp10x.yaml
|
||||
@@ -373,7 +361,7 @@ kubectl patch application myapp -n argocd \
|
||||
## 📖 Key Concepts
|
||||
|
||||
### App-of-Apps Pattern
|
||||
`_app-of-apps-{cluster}.yaml` is the root Application that manages all other Applications in `infra/`. Kustomize overlays in `infra/overlays/{cluster}/` render the base Applications with per-cluster patches (e.g., swapping value file paths). Supported clusters: `upc-dev`, `upc-prod`, `aws-dev`, `aws-prod`, `azure-dev`, `azure-prod`, `gcp-dev`, `gcp-prod`.
|
||||
`_app-of-apps.yaml` is the root Application that manages all other Applications in `infra/`. Kustomize overlays in `infra/overlays/{upc-dev,upc-prod}/` render the base Applications with per-cluster patches (e.g., swapping value file paths from `upc-dev` to `upc-prod`).
|
||||
|
||||
### Multi-Source Pattern
|
||||
Applications reference both:
|
||||
@@ -470,14 +458,16 @@ Documentation lives in `docs/`. To update:
|
||||
## 📝 Notes
|
||||
|
||||
### Current Environment
|
||||
- **Provider**: Multi-cloud (UpCloud, AWS EKS, Azure AKS, GCP GKE)
|
||||
- **Active clusters**: UpCloud (upc-dev, upc-prod)
|
||||
- **Provider**: UpCloud Managed Kubernetes
|
||||
- **Environment**: Production (internal use only)
|
||||
- **Clusters**: Multi-cluster (upc-dev, upc-prod) via Kustomize overlays
|
||||
- **Auth**: Disabled for ArgoCD (internal access)
|
||||
- **Backup**: Gitea daily backup to S3-compatible storage
|
||||
- **Backup**: None (cluster rebuildable via GitOps)
|
||||
|
||||
### Known Limitations
|
||||
- No automated backups (yet)
|
||||
- Secret rotation not automated
|
||||
- Multi-cluster limited to upc-dev and upc-prod environments
|
||||
- DNS management is manual
|
||||
|
||||
**Future improvements**: See [Operations Runbook - Disaster Recovery](docs/OPERATIONS-RUNBOOK.md#disaster-recovery)
|
||||
@@ -514,7 +504,7 @@ Internal use only. Not for public distribution.
|
||||
|
||||
---
|
||||
|
||||
**Last Updated**: 2026-04-22
|
||||
**Last Updated**: 2026-03-16
|
||||
**Documentation Version**: 1.0.0
|
||||
|
||||
**🚀 Ready to get started? Check out the [Documentation Index](docs/README.md)!**
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: monitoring
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "-1"
|
||||
---
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: infrastructure-apps
|
||||
namespace: argocd
|
||||
labels:
|
||||
app.kubernetes.io/name: infrastructure-apps
|
||||
app.kubernetes.io/part-of: platform
|
||||
finalizers:
|
||||
- resources-finalizer.argocd.argoproj.io
|
||||
spec:
|
||||
project: default
|
||||
source:
|
||||
repoURL: git@github.com:fortedigital/sturdy-adventure.git
|
||||
targetRevision: HEAD
|
||||
path: infra/overlays/aws-dev
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: default
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
@@ -1,32 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: monitoring
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "-1"
|
||||
---
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: infrastructure-apps
|
||||
namespace: argocd
|
||||
labels:
|
||||
app.kubernetes.io/name: infrastructure-apps
|
||||
app.kubernetes.io/part-of: platform
|
||||
finalizers:
|
||||
- resources-finalizer.argocd.argoproj.io
|
||||
spec:
|
||||
project: default
|
||||
source:
|
||||
repoURL: git@github.com:fortedigital/sturdy-adventure.git
|
||||
targetRevision: HEAD
|
||||
path: infra/overlays/aws-prod
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: default
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
@@ -1,32 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: monitoring
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "-1"
|
||||
---
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: infrastructure-apps
|
||||
namespace: argocd
|
||||
labels:
|
||||
app.kubernetes.io/name: infrastructure-apps
|
||||
app.kubernetes.io/part-of: platform
|
||||
finalizers:
|
||||
- resources-finalizer.argocd.argoproj.io
|
||||
spec:
|
||||
project: default
|
||||
source:
|
||||
repoURL: git@github.com:fortedigital/sturdy-adventure.git
|
||||
targetRevision: HEAD
|
||||
path: infra/overlays/azure-dev
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: default
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
@@ -1,32 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: monitoring
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "-1"
|
||||
---
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: infrastructure-apps
|
||||
namespace: argocd
|
||||
labels:
|
||||
app.kubernetes.io/name: infrastructure-apps
|
||||
app.kubernetes.io/part-of: platform
|
||||
finalizers:
|
||||
- resources-finalizer.argocd.argoproj.io
|
||||
spec:
|
||||
project: default
|
||||
source:
|
||||
repoURL: git@github.com:fortedigital/sturdy-adventure.git
|
||||
targetRevision: HEAD
|
||||
path: infra/overlays/azure-prod
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: default
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
@@ -1,32 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: monitoring
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "-1"
|
||||
---
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: infrastructure-apps
|
||||
namespace: argocd
|
||||
labels:
|
||||
app.kubernetes.io/name: infrastructure-apps
|
||||
app.kubernetes.io/part-of: platform
|
||||
finalizers:
|
||||
- resources-finalizer.argocd.argoproj.io
|
||||
spec:
|
||||
project: default
|
||||
source:
|
||||
repoURL: git@github.com:fortedigital/sturdy-adventure.git
|
||||
targetRevision: HEAD
|
||||
path: infra/overlays/gcp-dev
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: default
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
@@ -1,32 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: monitoring
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "-1"
|
||||
---
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: infrastructure-apps
|
||||
namespace: argocd
|
||||
labels:
|
||||
app.kubernetes.io/name: infrastructure-apps
|
||||
app.kubernetes.io/part-of: platform
|
||||
finalizers:
|
||||
- resources-finalizer.argocd.argoproj.io
|
||||
spec:
|
||||
project: default
|
||||
source:
|
||||
repoURL: git@github.com:fortedigital/sturdy-adventure.git
|
||||
targetRevision: HEAD
|
||||
path: infra/overlays/gcp-prod
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: default
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
@@ -4,4 +4,5 @@ resources:
|
||||
- dot-ai-stack.yaml
|
||||
- mcp10x.yaml
|
||||
- musicman.yaml
|
||||
- ts-mcp.yaml
|
||||
- argo-mcp.yaml
|
||||
|
||||
50
apps/base/ts-mcp.yaml
Normal file
50
apps/base/ts-mcp.yaml
Normal file
@@ -0,0 +1,50 @@
|
||||
---
|
||||
# Namespace must be created first (sync-wave: -1)
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: ts-mcp
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "-1"
|
||||
---
|
||||
# ArgoCD Application syncs last (sync-wave: 11)
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: ts-mcp
|
||||
namespace: argocd
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "11"
|
||||
notifications.argoproj.io/subscribe.on-sync-succeeded.slack: ""
|
||||
notifications.argoproj.io/subscribe.on-sync-failed.slack: ""
|
||||
notifications.argoproj.io/subscribe.on-degraded.slack: ""
|
||||
labels:
|
||||
app.kubernetes.io/name: ts-mcp
|
||||
app.kubernetes.io/part-of: apps
|
||||
app.kubernetes.io/managed-by: argocd
|
||||
finalizers:
|
||||
- resources-finalizer.argocd.argoproj.io
|
||||
spec:
|
||||
project: default
|
||||
sources:
|
||||
- repoURL: ssh://git@git.forteapps.net:2222/Forte/forte-helm.git
|
||||
path: forteapp
|
||||
targetRevision: HEAD
|
||||
helm:
|
||||
valueFiles:
|
||||
- $values/ts-mcp/values.yaml
|
||||
|
||||
- repoURL: ssh://git@git.forteapps.net:2222/Forte/helm-prod-values.git
|
||||
targetRevision: HEAD
|
||||
ref: values
|
||||
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: ts-mcp
|
||||
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
43
cluster-resources/backstage-keycloak-client-config.yaml
Normal file
43
cluster-resources/backstage-keycloak-client-config.yaml
Normal file
@@ -0,0 +1,43 @@
|
||||
# Self-service Keycloak client config for Backstage.
|
||||
# Kyverno clones this to the keycloak namespace, where the
|
||||
# keycloak-client-registrar CronJob processes it and creates
|
||||
# the backstage-oidc-credentials Secret in the backstage namespace.
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: keycloak-client-backstage
|
||||
namespace: backstage
|
||||
labels:
|
||||
keycloak.forteapps.net/client-config: "true"
|
||||
stringData:
|
||||
client.json: |
|
||||
{
|
||||
"clientId": "backstage",
|
||||
"name": "Backstage Developer Portal",
|
||||
"redirectUris": ["https://backstage.forteapps.net/api/auth/oidc/handler/frame"],
|
||||
"webOrigins": ["https://backstage.forteapps.net"],
|
||||
"defaultClientScopes": ["openid", "email", "profile"],
|
||||
"protocolMappers": [
|
||||
{
|
||||
"name": "email_verified",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-hardcoded-claim-mapper",
|
||||
"config": {
|
||||
"claim.name": "email_verified",
|
||||
"claim.value": "true",
|
||||
"jsonType.label": "boolean",
|
||||
"id.token.claim": "true",
|
||||
"access.token.claim": "true",
|
||||
"userinfo.token.claim": "true"
|
||||
}
|
||||
}
|
||||
],
|
||||
"secret": {
|
||||
"namespace": "backstage",
|
||||
"name": "backstage-oidc-credentials",
|
||||
"keys": {
|
||||
"clientId": "AUTH_OIDC_CLIENT_ID",
|
||||
"clientSecret": "AUTH_OIDC_CLIENT_SECRET"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -57,17 +57,17 @@ spec:
|
||||
- sh
|
||||
- -c
|
||||
- |
|
||||
mc alias set s3 "${S3_ENDPOINT}" "${AWS_ACCESS_KEY_ID}" "${AWS_SECRET_ACCESS_KEY}"
|
||||
mc alias set upcloud "${S3_ENDPOINT}" "${AWS_ACCESS_KEY_ID}" "${AWS_SECRET_ACCESS_KEY}"
|
||||
|
||||
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
|
||||
KEY="gitea-dump-${TIMESTAMP}.zip"
|
||||
echo "Uploading ${KEY}..."
|
||||
mc cp /backup/gitea-dump.zip "s3/${S3_BUCKET}/${KEY}" && \
|
||||
mc cp /backup/gitea-dump.zip "upcloud/${S3_BUCKET}/${KEY}" && \
|
||||
echo "Upload complete."
|
||||
|
||||
# Prune backups older than 7 days
|
||||
echo "Pruning backups older than 7 days..."
|
||||
mc rm --older-than 7d --force "s3/${S3_BUCKET}/" 2>&1 || true
|
||||
mc rm --older-than 7d --force "upcloud/${S3_BUCKET}/" 2>&1 || true
|
||||
echo "Pruning complete."
|
||||
envFrom:
|
||||
- secretRef:
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
apiVersion: kyverno.io/v1
|
||||
kind: ClusterPolicy
|
||||
metadata:
|
||||
name: require-labels
|
||||
annotations:
|
||||
policies.kyverno.io/title: Require Labels
|
||||
policies.kyverno.io/category: Best Practices
|
||||
policies.kyverno.io/minversion: 1.6.0
|
||||
policies.kyverno.io/severity: medium
|
||||
policies.kyverno.io/subject: Pod, Label
|
||||
policies.kyverno.io/description: Define and use labels that identify semantic attributes of your application or Deployment. A common set of labels allows tools to work collaboratively, describing objects in a common manner that all tools can understand. The recommended labels describe applications in a way that can be queried. This policy validates that the label `app.kubernetes.io/name` is specified with some value.
|
||||
spec:
|
||||
validationFailureAction: Audit
|
||||
background: true
|
||||
rules:
|
||||
- name: check-for-labels
|
||||
skipBackgroundRequests: true
|
||||
exclude:
|
||||
any:
|
||||
- resources:
|
||||
namespaces:
|
||||
- kube-system
|
||||
- istio-system
|
||||
- argocd
|
||||
- cert-manager
|
||||
- monitoring
|
||||
- secrets
|
||||
- kyverno
|
||||
- trivy-system
|
||||
match:
|
||||
any:
|
||||
- resources:
|
||||
kinds:
|
||||
- Pod
|
||||
validate:
|
||||
message: The label `app.kubernetes.io/name` is required.
|
||||
allowExistingViolations: true
|
||||
pattern:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: "?*"
|
||||
@@ -1,10 +0,0 @@
|
||||
clusterName: dev-eks # <- adjust to your EKS cluster name
|
||||
domain: example.com # <- adjust to your domain
|
||||
argocdDomain: argocd.example.com
|
||||
grafanaDomain: grafana.example.com
|
||||
keycloakDomain: id.example.com
|
||||
dotaiDomain: kubemcp.example.com
|
||||
dotaiUiDomain: kubemcpui.example.com
|
||||
letsencryptEmail: admin@example.com # <- adjust
|
||||
trustedIPs: "10.0.0.0/8" # <- adjust to your VPC CIDR
|
||||
cloudProvider: aws
|
||||
@@ -1,10 +0,0 @@
|
||||
clusterName: prod-eks # <- adjust to your EKS cluster name
|
||||
domain: example.com # <- adjust to your domain
|
||||
argocdDomain: argocd.example.com
|
||||
grafanaDomain: grafana.example.com
|
||||
keycloakDomain: id.example.com
|
||||
dotaiDomain: kubemcp.example.com
|
||||
dotaiUiDomain: kubemcpui.example.com
|
||||
letsencryptEmail: admin@example.com # <- adjust
|
||||
trustedIPs: "10.0.0.0/8" # <- adjust to your VPC CIDR
|
||||
cloudProvider: aws
|
||||
@@ -1,10 +0,0 @@
|
||||
clusterName: dev-aks # <- adjust to your AKS cluster name
|
||||
domain: example.com # <- adjust to your domain
|
||||
argocdDomain: argocd.example.com
|
||||
grafanaDomain: grafana.example.com
|
||||
keycloakDomain: id.example.com
|
||||
dotaiDomain: kubemcp.example.com
|
||||
dotaiUiDomain: kubemcpui.example.com
|
||||
letsencryptEmail: admin@example.com # <- adjust
|
||||
trustedIPs: "10.0.0.0/8,168.63.129.16/32" # <- VNet CIDR + Azure health probe
|
||||
cloudProvider: azure
|
||||
@@ -1,10 +0,0 @@
|
||||
clusterName: prod-aks # <- adjust to your AKS cluster name
|
||||
domain: example.com # <- adjust to your domain
|
||||
argocdDomain: argocd.example.com
|
||||
grafanaDomain: grafana.example.com
|
||||
keycloakDomain: id.example.com
|
||||
dotaiDomain: kubemcp.example.com
|
||||
dotaiUiDomain: kubemcpui.example.com
|
||||
letsencryptEmail: admin@example.com # <- adjust
|
||||
trustedIPs: "10.0.0.0/8,168.63.129.16/32" # <- VNet CIDR + Azure health probe
|
||||
cloudProvider: azure
|
||||
@@ -1,10 +0,0 @@
|
||||
clusterName: dev-gke # <- adjust to your GKE cluster name
|
||||
domain: example.com # <- adjust to your domain
|
||||
argocdDomain: argocd.example.com
|
||||
grafanaDomain: grafana.example.com
|
||||
keycloakDomain: id.example.com
|
||||
dotaiDomain: kubemcp.example.com
|
||||
dotaiUiDomain: kubemcpui.example.com
|
||||
letsencryptEmail: admin@example.com # <- adjust
|
||||
trustedIPs: "10.0.0.0/8,35.191.0.0/16,130.211.0.0/22" # <- subnet CIDR + GCP health checks
|
||||
cloudProvider: gcp
|
||||
@@ -1,10 +0,0 @@
|
||||
clusterName: prod-gke # <- adjust to your GKE cluster name
|
||||
domain: example.com # <- adjust to your domain
|
||||
argocdDomain: argocd.example.com
|
||||
grafanaDomain: grafana.example.com
|
||||
keycloakDomain: id.example.com
|
||||
dotaiDomain: kubemcp.example.com
|
||||
dotaiUiDomain: kubemcpui.example.com
|
||||
letsencryptEmail: admin@example.com # <- adjust
|
||||
trustedIPs: "10.0.0.0/8,35.191.0.0/16,130.211.0.0/22" # <- subnet CIDR + GCP health checks
|
||||
cloudProvider: gcp
|
||||
@@ -962,6 +962,46 @@ User sees application (authenticated)
|
||||
|
||||
---
|
||||
|
||||
### Accessing Authenticated User Information
|
||||
|
||||
The auth sidecar handles all authentication before requests reach your application. Your app never sees unauthenticated traffic — the sidecar returns 401 or redirects to the IdP first.
|
||||
|
||||
After successful authentication, the sidecar forwards the request to your application with user identity injected as HTTP headers:
|
||||
|
||||
| Header | Description | Available in |
|
||||
|--------|-------------|-------------|
|
||||
| `X-Auth-User` | Username or display name | Token, OIDC, MCP |
|
||||
| `X-Auth-Email` | User email address | OIDC |
|
||||
| `X-Auth-Subject` | OIDC `sub` claim (stable user ID) | OIDC, MCP |
|
||||
| `X-Auth-Groups` | Comma-separated group memberships | OIDC (if scope includes `groups`) |
|
||||
| `X-Auth-Token` | The validated access token | All modes |
|
||||
|
||||
**Your application reads these headers — no auth library needed:**
|
||||
|
||||
```javascript
|
||||
// Express.js example
|
||||
app.get('/profile', (req, res) => {
|
||||
const user = req.headers['x-auth-user'];
|
||||
const email = req.headers['x-auth-email'];
|
||||
res.json({ user, email });
|
||||
});
|
||||
```
|
||||
|
||||
```python
|
||||
# Flask example
|
||||
@app.route('/profile')
|
||||
def profile():
|
||||
user = request.headers.get('X-Auth-User')
|
||||
email = request.headers.get('X-Auth-Email')
|
||||
return jsonify(user=user, email=email)
|
||||
```
|
||||
|
||||
**Why this is safe**: The Kyverno-generated NetworkPolicy restricts ingress to the sidecar port only. Traffic cannot bypass the sidecar to reach the application port directly, so the `X-Auth-*` headers can be trusted unconditionally.
|
||||
|
||||
**Key principle**: Your application is zero-trust-unaware by design. It reads headers and renders UI. All authentication complexity lives in the sidecar and Kyverno policy.
|
||||
|
||||
---
|
||||
|
||||
### Authentication Configuration Reference
|
||||
|
||||
#### Helm Values Schema
|
||||
|
||||
@@ -12,11 +12,11 @@
|
||||
|
||||
## Overview
|
||||
|
||||
This Kubernetes cluster uses a **GitOps approach** powered by **ArgoCD**, where Git repositories serve as the single source of truth for both infrastructure and application deployments. The cluster setup is **cloud-agnostic**, with ready-to-use configurations for **UpCloud**, **AWS EKS**, **Azure AKS**, and **GCP GKE**.
|
||||
This Kubernetes cluster uses a **GitOps approach** powered by **ArgoCD**, where Git repositories serve as the single source of truth for both infrastructure and application deployments. The cluster is running on **UpCloud Managed Kubernetes** but is designed to be cloud-agnostic.
|
||||
|
||||
### Key Characteristics
|
||||
- **Environment**: Production (internal use only)
|
||||
- **Cluster Type**: Multi-cloud, multi-cluster via Kustomize overlays (UpCloud, AWS, Azure, GCP)
|
||||
- **Cluster Type**: Multi-cluster (upc-dev, upc-prod) via Kustomize overlays
|
||||
- **GitOps Tool**: ArgoCD
|
||||
- **Deployment Pattern**: App-of-Apps
|
||||
- **Secret Management**: Sealed Secrets (kubeseal)
|
||||
@@ -63,7 +63,7 @@ This Kubernetes cluster uses a **GitOps approach** powered by **ArgoCD**, where
|
||||
▼
|
||||
┌────────────────────────────────┐
|
||||
│ Kubernetes Clusters │
|
||||
│ (UpCloud, AWS, Azure, GCP) │
|
||||
│ (UpCloud: upc-dev, upc-prod) │
|
||||
│ │
|
||||
│ ┌──────────────────────────┐ │
|
||||
│ │ ArgoCD │ │
|
||||
@@ -131,22 +131,26 @@ launchpad/
|
||||
│ │ ├── renovate.yaml
|
||||
│ │ ├── ... # All other Application manifests
|
||||
│ │ └── secrets.yaml
|
||||
│ ├── overlays/ # Per-cluster Kustomize overrides
|
||||
│ ├── overlays/ # Per-cluster overrides
|
||||
│ │ ├── upc-dev/ # UpCloud Dev (uses base as-is)
|
||||
│ │ ├── upc-prod/ # UpCloud Prod (patches value paths)
|
||||
│ │ ├── aws-dev/ # AWS EKS Dev
|
||||
│ │ ├── aws-prod/ # AWS EKS Prod
|
||||
│ │ ├── azure-dev/ # Azure AKS Dev
|
||||
│ │ ├── azure-prod/ # Azure AKS Prod
|
||||
│ │ ├── gcp-dev/ # GCP GKE Dev
|
||||
│ │ └── gcp-prod/ # GCP GKE Prod
|
||||
│ │ └── upc-prod/ # UpCloud Prod (patches value paths)
|
||||
│ ├── dashboards/ # Grafana dashboard ConfigMaps
|
||||
│ └── values/ # Helm value overrides for infra
|
||||
│ ├── base/ # Cloud-agnostic shared values
|
||||
│ ├── upc-{dev,prod}/ # UpCloud: storage class, LB, pricing
|
||||
│ ├── aws-{dev,prod}/ # AWS: gp3, NLB, CUR pricing
|
||||
│ ├── azure-{dev,prod}/ # Azure: managed-csi-premium, Standard LB
|
||||
│ └── gcp-{dev,prod}/ # GCP: premium-rwo, L4 LB
|
||||
│ ├── base/ # Shared values (all clusters)
|
||||
│ │ ├── traefik-values.yaml
|
||||
│ │ ├── keycloak-values.yaml
|
||||
│ │ ├── grafana-values.yaml
|
||||
│ │ ├── prometheus-values.yaml
|
||||
│ │ ├── gitea-values.yaml
|
||||
│ │ └── ...
|
||||
│ ├── upc-dev/ # upc-dev cluster-specific values
|
||||
│ │ ├── traefik-values.yaml
|
||||
│ │ ├── keycloak-values.yaml
|
||||
│ │ └── grafana-values.yaml
|
||||
│ └── upc-prod/ # upc-prod cluster-specific values
|
||||
│ ├── traefik-values.yaml
|
||||
│ ├── keycloak-values.yaml
|
||||
│ └── grafana-values.yaml
|
||||
│
|
||||
├── apps/ # Business Application ArgoCD manifests (Kustomize)
|
||||
│ ├── base/ # Base app manifests
|
||||
@@ -283,7 +287,7 @@ app-repository/
|
||||
### The App-of-Apps Pattern
|
||||
|
||||
```
|
||||
_app-of-apps-{cluster}.yaml (Root, per cluster — e.g. upc-dev, aws-prod, gcp-dev)
|
||||
_app-of-apps-{upc-dev,upc-prod}.yaml (Root, per cluster)
|
||||
│
|
||||
├── infrastructure-apps (manages infra/)
|
||||
│ ├── cluster-resources-application
|
||||
@@ -373,15 +377,6 @@ patches:
|
||||
value: $values/infra/values/upc-prod/traefik-values.yaml
|
||||
```
|
||||
|
||||
Cloud-specific values (storage classes, load balancer annotations, cost model) are isolated in per-cluster value files. Base values are fully cloud-agnostic:
|
||||
|
||||
| Cloud | Storage Class | Load Balancer | OpenCost Provider |
|
||||
|-------|--------------|---------------|-------------------|
|
||||
| **UpCloud** | `upcloud-block-storage-maxiops` | UpCloud LB (ProxyProtocol v2) | Custom pricing |
|
||||
| **AWS EKS** | `gp3` (EBS CSI) | NLB (ProxyProtocol v2) | AWS CUR |
|
||||
| **Azure AKS** | `managed-csi-premium` | Standard LB (`externalTrafficPolicy: Local`) | Azure Billing API |
|
||||
| **GCP GKE** | `premium-rwo` (PD CSI) | L4 passthrough NLB | GCP Cloud Billing |
|
||||
|
||||
**Benefits**:
|
||||
- Single source of truth for Application definitions
|
||||
- Cluster-specific values isolated per overlay
|
||||
@@ -663,6 +658,6 @@ Notifications include:
|
||||
|
||||
---
|
||||
|
||||
**Last Updated**: 2026-04-22
|
||||
**Last Updated**: 2026-03-16
|
||||
**Maintained By**: Platform Team
|
||||
**Questions?**: Contact #platform-support on Slack
|
||||
|
||||
@@ -37,7 +37,7 @@ Bootstrap a new cluster from scratch:
|
||||
|
||||
#### Prerequisites
|
||||
|
||||
1. **Kubernetes cluster running** (UpCloud, AWS EKS, Azure AKS, GCP GKE, or any K8s cluster)
|
||||
1. **Kubernetes cluster running** (UpCloud or any K8s cluster)
|
||||
2. **kubectl configured** with admin access
|
||||
3. **Repositories cloned** locally
|
||||
|
||||
@@ -54,13 +54,11 @@ kubectl get nodes
|
||||
git clone https://git.forteapps.net/Forte/launchpad
|
||||
cd launchpad
|
||||
|
||||
# 2. Run bootstrap script with cluster target
|
||||
# Available clusters: upc-dev, upc-prod, aws-dev, aws-prod,
|
||||
# azure-dev, azure-prod, gcp-dev, gcp-prod
|
||||
./bootstrap.sh upc-dev
|
||||
# 2. Set cluster name (optional)
|
||||
export CLUSTER_NAME="prod-cluster-01"
|
||||
|
||||
# Cluster config is loaded from clusters/<cluster>.yaml
|
||||
# (cloudProvider, trustedIPs, domain, etc.)
|
||||
# 3. Run bootstrap script
|
||||
./bootstrap.sh
|
||||
```
|
||||
|
||||
**What Happens:**
|
||||
@@ -1264,21 +1262,13 @@ spec:
|
||||
|
||||
### Backup Strategy
|
||||
|
||||
**Current State**: Gitea daily backups to S3-compatible storage
|
||||
**Current State**: No automated backups
|
||||
|
||||
**What Is Backed Up**:
|
||||
- ✅ Gitea repositories + database: Daily CronJob (`cluster-resources/gitea-backup-cronjob.yaml`) uploads to S3-compatible storage with 7-day retention
|
||||
- ✅ Git repositories: Full cluster config recoverable from Git
|
||||
- ⚠️ Secrets: Sealed secrets in Git; unseal keys need safekeeping
|
||||
|
||||
**What Is NOT Backed Up**:
|
||||
- ❌ Cluster state (recreate via GitOps)
|
||||
- ❌ Other persistent volumes (Prometheus, Loki, Tempo data)
|
||||
|
||||
**Per-cloud backup scripts** (manual restore helpers):
|
||||
- UpCloud/AWS: `scripts/gitea-backup.sh` / `scripts/gitea-backup-aws.sh` (MinIO CLI, S3-compatible)
|
||||
- Azure: `scripts/gitea-backup-azure.sh` (Azure CLI + Blob Storage)
|
||||
- GCP: `scripts/gitea-backup-gcp.sh` (gsutil + GCS)
|
||||
**What Needs Backup**:
|
||||
- ❌ Cluster state (not backed up - recreate via GitOps)
|
||||
- ❌ Persistent volumes (currently not critical)
|
||||
- ✅ Git repositories (Gitea provides backup)
|
||||
- ⚠️ Secrets (sealed secrets in Git, unseal keys need safekeeping)
|
||||
|
||||
### Cluster Rebuild
|
||||
|
||||
@@ -1380,9 +1370,6 @@ kubectl get pods -n argocd
|
||||
|
||||
```bash
|
||||
# UpCloud: Upgrade via control panel or CLI
|
||||
# AWS EKS: eksctl upgrade cluster / AWS Console
|
||||
# Azure AKS: az aks upgrade / Azure Portal
|
||||
# GCP GKE: gcloud container clusters upgrade / Cloud Console
|
||||
|
||||
# After upgrade, verify cluster
|
||||
kubectl version
|
||||
@@ -1520,35 +1507,18 @@ git push
|
||||
|
||||
### Multi-Cluster Setup
|
||||
|
||||
The repository supports multiple clusters across multiple clouds via Kustomize overlays:
|
||||
The repository supports multiple clusters via Kustomize overlays:
|
||||
|
||||
**Active clusters:**
|
||||
- **upc-dev** (default): `infra/overlays/upc-dev/` — uses base Applications as-is
|
||||
- **upc-prod**: `infra/overlays/upc-prod/` — patches value file paths from `upc-dev` to `upc-prod`
|
||||
|
||||
**Cloud-ready templates (fill in `clusters/*.yaml` before use):**
|
||||
- **aws-dev** / **aws-prod**: AWS EKS with NLB, gp3 storage, AWS CUR pricing
|
||||
- **azure-dev** / **azure-prod**: Azure AKS with Standard LB, managed-csi-premium storage
|
||||
- **gcp-dev** / **gcp-prod**: GCP GKE with L4 LB, premium-rwo storage
|
||||
|
||||
Each cluster has its own:
|
||||
- Root app-of-apps: `_app-of-apps-{cluster}.yaml`
|
||||
- Cluster config: `clusters/{cluster}.yaml` (domain, trustedIPs, cloudProvider)
|
||||
- Kustomize overlay: `infra/overlays/{cluster}/kustomization.yaml`
|
||||
- Helm value overrides: `infra/values/{cluster}/` (traefik, gitea, opencost)
|
||||
- Sealed secrets: `secrets/{cluster}/` (as needed)
|
||||
- Apps overlay: `apps/overlays/{cluster}/`
|
||||
- Root app-of-apps file: `_app-of-apps-upc-dev.yaml` / `_app-of-apps-upc-prod.yaml`
|
||||
- Cluster-specific Helm values: `infra/values/upc-dev/` / `infra/values/upc-prod/`
|
||||
- Sealed secrets: `secrets/upc-dev/` (others as needed)
|
||||
- Apps overlay: `apps/overlays/upc-dev/` / `apps/overlays/upc-prod/`
|
||||
|
||||
Cloud-specific values handled per-cluster:
|
||||
|
||||
| Concern | UpCloud | AWS EKS | Azure AKS | GCP GKE |
|
||||
|---------|---------|---------|-----------|---------|
|
||||
| **Storage class** | `upcloud-block-storage-maxiops` | `gp3` | `managed-csi-premium` | `premium-rwo` |
|
||||
| **Load balancer** | UpCloud LB + ProxyProtocol v2 | NLB + ProxyProtocol v2 | Standard LB + `externalTrafficPolicy: Local` | L4 passthrough NLB |
|
||||
| **Cost monitoring** | Custom pricing | AWS CUR | Azure Billing API | GCP Cloud Billing |
|
||||
| **Backup storage** | UpCloud S3-compat | AWS S3 (native) | Azure Blob Storage | GCS |
|
||||
|
||||
To add a new cluster, create a new overlay directory (e.g., `infra/overlays/aws-staging/`) with patches that swap the value file paths, and a matching `clusters/aws-staging.yaml`.
|
||||
To add a new cluster, create a new overlay directory (e.g., `infra/overlays/upc-staging/`) with patches that swap the value file paths.
|
||||
|
||||
### Blue-Green Deployments
|
||||
|
||||
@@ -1691,6 +1661,6 @@ echo "Remember to delete: $SECRET_FILE"
|
||||
|
||||
---
|
||||
|
||||
**Last Updated**: 2026-04-22
|
||||
**Last Updated**: 2026-03-16
|
||||
**Maintained By**: Platform Team
|
||||
**Emergency Contact**: #platform-support on Slack
|
||||
|
||||
@@ -180,7 +180,7 @@ Reference for:
|
||||
│
|
||||
▼
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ Kubernetes Clusters (UpCloud, AWS, Azure, GCP) │
|
||||
│ Kubernetes Clusters (UpCloud: upc-dev, upc-prod) │
|
||||
│ ┌──────────────────────────────────────────────────────┐ │
|
||||
│ │ Infrastructure: Traefik, Cert-Manager, Kyverno │ │
|
||||
│ ├──────────────────────────────────────────────────────┤ │
|
||||
@@ -194,7 +194,7 @@ Reference for:
|
||||
### Key Technologies
|
||||
|
||||
- **GitOps**: ArgoCD
|
||||
- **Kubernetes**: Multi-cloud (UpCloud, AWS EKS, Azure AKS, GCP GKE)
|
||||
- **Kubernetes**: UpCloud Managed Kubernetes (multi-cluster: upc-dev, upc-prod)
|
||||
- **Ingress**: Traefik v2
|
||||
- **Certificates**: Cert-Manager + Let's Encrypt
|
||||
- **Policies**: Kyverno
|
||||
@@ -299,16 +299,11 @@ docs/
|
||||
## 🔄 Documentation Versions
|
||||
|
||||
**Current Version**: 1.0.0
|
||||
**Last Updated**: 2026-04-22
|
||||
**Last Updated**: 2026-03-16
|
||||
**Maintained By**: Platform Team
|
||||
|
||||
### Changelog
|
||||
|
||||
- **v1.1.0 (2026-04-22)**: Multi-cloud support
|
||||
- Cloud-agnostic base values (storage, LB, pricing moved to per-cluster overlays)
|
||||
- Added AWS EKS, Azure AKS, GCP GKE configurations
|
||||
- Per-cloud backup scripts
|
||||
- Updated all documentation
|
||||
- **v1.0.0 (2026-03-16)**: Initial comprehensive documentation release
|
||||
- GitOps Architecture guide
|
||||
- Developer Onboarding guide
|
||||
|
||||
@@ -19,9 +19,9 @@
|
||||
|
||||
| Component | Value |
|
||||
|-----------|-------|
|
||||
| **Provider** | Multi-cloud (UpCloud, AWS EKS, Azure AKS, GCP GKE) |
|
||||
| **Active clusters** | UpCloud (upc-dev, upc-prod) |
|
||||
| **Cloud-ready templates** | AWS, Azure, GCP (dev + prod each) |
|
||||
| **Provider** | UpCloud Managed Kubernetes |
|
||||
| **Environment** | Production (internal use) |
|
||||
| **Cluster Count** | Multi-cluster (upc-dev, upc-prod) |
|
||||
| **GitOps Tool** | ArgoCD |
|
||||
| **Ingress Controller** | Traefik v2 |
|
||||
| **Certificate Management** | Cert-Manager + Let's Encrypt |
|
||||
@@ -42,7 +42,7 @@ Internet
|
||||
[DNS: *.forteapps.net]
|
||||
│
|
||||
▼
|
||||
[Cloud Load Balancer]
|
||||
[UpCloud LoadBalancer]
|
||||
│
|
||||
▼
|
||||
[Traefik Ingress Controller]
|
||||
@@ -602,6 +602,15 @@ retry:
|
||||
4. 40 seconds
|
||||
5. 80 seconds (capped at 3 minutes)
|
||||
|
||||
### Global Settings (`argocd-cm`)
|
||||
|
||||
| Setting | Value | Purpose |
|
||||
|---------|-------|---------|
|
||||
| `application.resourceTrackingMethod` | `annotation` | Track resources via annotations |
|
||||
| `timeout.reconciliation` | `60s` | Reconciliation interval |
|
||||
| `admin.enabled` | `true` | Enable admin account |
|
||||
| `git.submodule.enabled` | `false` | Disable git submodule checkout — submodules are not needed for manifest generation |
|
||||
|
||||
---
|
||||
|
||||
## Infrastructure Components
|
||||
@@ -956,6 +965,83 @@ ignore:
|
||||
- Check Gitea Actions tab for workflow run status and logs
|
||||
- Monitor Anthropic usage dashboard for token consumption
|
||||
|
||||
### Backstage / RHDH (Developer Portal)
|
||||
|
||||
**Chart**: `backstage` (RHDH — Red Hat Developer Hub)
|
||||
**Version**: `5.8.0`
|
||||
**Namespace**: `backstage`
|
||||
**Helm Repo**: `https://redhat-developer.github.io/rhdh-chart`
|
||||
**Image**: `quay.io/rhdh-community/rhdh:next`
|
||||
|
||||
**Purpose**: Internal developer portal where teams register and broadcast themselves, their applications, APIs, and systems. Provides a unified catalog, templates, and documentation hub.
|
||||
|
||||
**Why RHDH over vanilla Backstage**: Ships 27+ plugins pre-bundled (ArgoCD, Kubernetes, Keycloak, GitHub, GitLab, Jira, SonarQube, Tekton, Jenkins, Quay, and more). Supports dynamic plugin installation at runtime — no image rebuilds needed.
|
||||
|
||||
**Configuration** (`infra/values/base/backstage-values.yaml`):
|
||||
- OpenShift Route disabled (`route.enabled: false`) — uses Traefik ingress instead
|
||||
- PostgreSQL subchart enabled for persistence (2Gi)
|
||||
- SecurityContext configured for vanilla Kubernetes (non-OpenShift)
|
||||
- Traefik ingress with `websecure` entrypoint
|
||||
- App title: "Forte Developer Portal"
|
||||
- Dynamic plugins: loads `dynamic-plugins.default.yaml` (all 27+ bundled plugins)
|
||||
- Catalog rules: Component, System, API, Resource, Location, Template, Group, User, Domain
|
||||
|
||||
**Authentication** (Keycloak OIDC):
|
||||
- Uses the self-service registrar pattern (see [Keycloak Client Registrar](#keycloak-client-registrar))
|
||||
- Config Secret: `cluster-resources/backstage-keycloak-client-config.yaml`
|
||||
- Kyverno clones it → registrar creates `backstage-oidc-credentials` Secret in `backstage` namespace
|
||||
- Credential keys: `AUTH_OIDC_CLIENT_ID`, `AUTH_OIDC_CLIENT_SECRET` (loaded via `extraEnvVarsSecrets`)
|
||||
- Redirect URI: `https://backstage.forteapps.net/api/auth/oidc/handler/frame`
|
||||
- Sign-in resolver: `emailMatchingUserEntityProfileEmail`
|
||||
|
||||
**Catalog Discovery** (Gitea):
|
||||
- Auto-discovers `catalog-info.yaml` from all repos in the `Forte` organization
|
||||
- Scans every 30 minutes via the Gitea catalog provider plugin
|
||||
- Gitea SCM integration configured for URL resolution (`git.forteapps.net`)
|
||||
|
||||
**Catalog Registration**:
|
||||
Teams register services by adding a `catalog-info.yaml` to their repo root:
|
||||
```yaml
|
||||
apiVersion: backstage.io/v1alpha1
|
||||
kind: Component
|
||||
metadata:
|
||||
name: my-service
|
||||
description: My service description
|
||||
annotations:
|
||||
backstage.io/source-location: url:https://git.forteapps.net/Forte/my-service
|
||||
spec:
|
||||
type: service
|
||||
lifecycle: production
|
||||
owner: team-name
|
||||
```
|
||||
|
||||
Repos with this file are auto-discovered — no manual registration needed.
|
||||
|
||||
**Dynamic Plugins**:
|
||||
Add plugins at runtime via `global.dynamic.plugins` in values — no image rebuild:
|
||||
```yaml
|
||||
global:
|
||||
dynamic:
|
||||
plugins:
|
||||
- package: "@scope/my-plugin@1.0.0"
|
||||
integrity: "sha512-..."
|
||||
```
|
||||
|
||||
**Per-cluster Configuration** (`infra/values/upc-dev/backstage-values.yaml`):
|
||||
```yaml
|
||||
global:
|
||||
host: backstage.forteapps.net
|
||||
upstream:
|
||||
backstage:
|
||||
appConfig:
|
||||
app:
|
||||
baseUrl: https://backstage.forteapps.net
|
||||
backend:
|
||||
baseUrl: https://backstage.forteapps.net
|
||||
ingress:
|
||||
host: backstage.forteapps.net
|
||||
```
|
||||
|
||||
### Keycloak Client Registrar
|
||||
|
||||
**Type**: CronJob (deployed via Keycloak Helm chart `extraDeploy`)
|
||||
@@ -1516,7 +1602,23 @@ Forward to Application (localhost:3000)
|
||||
Application processes request
|
||||
```
|
||||
|
||||
**See**: [Developer Guide - Enabling Authentication](DEVELOPER-GUIDE.md#enabling-authentication-for-applications) for usage examples.
|
||||
#### Forwarded Headers
|
||||
|
||||
After successful authentication, the sidecar injects user identity as HTTP headers before forwarding the request to the application container:
|
||||
|
||||
| Header | Description | Auth Modes |
|
||||
|--------|-------------|------------|
|
||||
| `X-Auth-User` | Username or display name | Token, OIDC, MCP |
|
||||
| `X-Auth-Email` | User email address | OIDC |
|
||||
| `X-Auth-Subject` | OIDC `sub` claim (stable user ID) | OIDC, MCP |
|
||||
| `X-Auth-Groups` | Comma-separated group memberships | OIDC (if `groups` scope) |
|
||||
| `X-Auth-Token` | The validated access token | All modes |
|
||||
|
||||
These headers are trustworthy because the auto-generated `NetworkPolicy` restricts pod ingress to the sidecar port only — external traffic cannot reach the application container directly, so headers cannot be spoofed.
|
||||
|
||||
Applications should read these headers to obtain authenticated user information (e.g. for display, authorisation decisions, or audit logging) instead of implementing their own authentication.
|
||||
|
||||
**See**: [Developer Guide - Accessing Authenticated User Information](DEVELOPER-GUIDE.md#accessing-authenticated-user-information) for code examples.
|
||||
|
||||
---
|
||||
|
||||
@@ -1550,22 +1652,14 @@ Recommended resource allocation:
|
||||
|
||||
### Storage Classes
|
||||
|
||||
Storage classes are cloud-specific and configured in per-cluster value overrides (`infra/values/{cluster}/gitea-values.yaml`):
|
||||
|
||||
| Cloud | Storage Class | Driver |
|
||||
|-------|--------------|--------|
|
||||
| **UpCloud** | `upcloud-block-storage-maxiops` | UpCloud CSI |
|
||||
| **AWS EKS** | `gp3` | EBS CSI |
|
||||
| **Azure AKS** | `managed-csi-premium` | Azure Disk CSI |
|
||||
| **GCP GKE** | `premium-rwo` | PD CSI |
|
||||
Default storage class used: **UpCloud default** (varies by provider)
|
||||
|
||||
```yaml
|
||||
# Example: base values omit storageClass (set in per-cluster overlay)
|
||||
persistence:
|
||||
enabled: true
|
||||
storageClass: "" # Uses default
|
||||
accessMode: ReadWriteOnce
|
||||
size: 5Gi
|
||||
# storageClass set by infra/values/{cluster}/gitea-values.yaml
|
||||
```
|
||||
|
||||
---
|
||||
@@ -1761,6 +1855,6 @@ team: platform
|
||||
|
||||
---
|
||||
|
||||
**Last Updated**: 2026-04-22
|
||||
**Last Updated**: 2026-04-16
|
||||
**Maintained By**: Platform Team
|
||||
**Version**: 1.0.0
|
||||
|
||||
43
infra/base/backstage.yaml
Normal file
43
infra/base/backstage.yaml
Normal file
@@ -0,0 +1,43 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: backstage
|
||||
namespace: argocd
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "1"
|
||||
labels:
|
||||
app.kubernetes.io/name: backstage
|
||||
app.kubernetes.io/part-of: developer-portal
|
||||
app.kubernetes.io/managed-by: argocd
|
||||
finalizers:
|
||||
- resources-finalizer.argocd.argoproj.io
|
||||
spec:
|
||||
project: default
|
||||
|
||||
sources:
|
||||
- repoURL: https://redhat-developer.github.io/rhdh-chart
|
||||
chart: backstage
|
||||
targetRevision: "5.8.0"
|
||||
helm:
|
||||
releaseName: backstage
|
||||
valueFiles:
|
||||
- $values/infra/values/base/backstage-values.yaml
|
||||
- $values/infra/values/upc-dev/backstage-values.yaml
|
||||
|
||||
- repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git
|
||||
targetRevision: HEAD
|
||||
ref: values
|
||||
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: backstage
|
||||
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
allowEmpty: false
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
- Validate=true
|
||||
- ServerSideApply=true
|
||||
@@ -22,7 +22,6 @@ spec:
|
||||
releaseName: gitea
|
||||
valueFiles:
|
||||
- $values/infra/values/base/gitea-values.yaml
|
||||
- $values/infra/values/upc-dev/gitea-values.yaml
|
||||
|
||||
- repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git
|
||||
targetRevision: HEAD
|
||||
|
||||
@@ -22,3 +22,4 @@ resources:
|
||||
- tempo.yaml
|
||||
- grafana-dashboards.yaml
|
||||
- network-policies-application.yaml
|
||||
- backstage.yaml
|
||||
|
||||
@@ -22,7 +22,6 @@ spec:
|
||||
releaseName: opencost
|
||||
valueFiles:
|
||||
- $values/infra/values/base/opencost-values.yaml
|
||||
- $values/infra/values/upc-dev/opencost-values.yaml
|
||||
|
||||
- repoURL: git@github.com:fortedigital/sturdy-adventure.git
|
||||
targetRevision: HEAD
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
- ../../base
|
||||
|
||||
patches:
|
||||
# Traefik: swap upc-dev → aws-dev
|
||||
- target:
|
||||
kind: Application
|
||||
name: traefik
|
||||
patch: |
|
||||
- op: replace
|
||||
path: /spec/sources/0/helm/valueFiles/1
|
||||
value: $values/infra/values/aws-dev/traefik-values.yaml
|
||||
|
||||
# Gitea: swap upc-dev → aws-dev
|
||||
- target:
|
||||
kind: Application
|
||||
name: gitea
|
||||
patch: |
|
||||
- op: replace
|
||||
path: /spec/sources/0/helm/valueFiles/1
|
||||
value: $values/infra/values/aws-dev/gitea-values.yaml
|
||||
|
||||
# OpenCost: swap upc-dev → aws-dev
|
||||
- target:
|
||||
kind: Application
|
||||
name: opencost
|
||||
patch: |
|
||||
- op: replace
|
||||
path: /spec/sources/0/helm/valueFiles/1
|
||||
value: $values/infra/values/aws-dev/opencost-values.yaml
|
||||
|
||||
# TODO: add patches for keycloak, grafana, secrets, enterprise-apps
|
||||
# when deploying to this cluster (these are deployment-specific, not cloud-specific)
|
||||
@@ -1,35 +0,0 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
- ../../base
|
||||
|
||||
patches:
|
||||
# Traefik: swap upc-dev → aws-prod
|
||||
- target:
|
||||
kind: Application
|
||||
name: traefik
|
||||
patch: |
|
||||
- op: replace
|
||||
path: /spec/sources/0/helm/valueFiles/1
|
||||
value: $values/infra/values/aws-prod/traefik-values.yaml
|
||||
|
||||
# Gitea: swap upc-dev → aws-prod
|
||||
- target:
|
||||
kind: Application
|
||||
name: gitea
|
||||
patch: |
|
||||
- op: replace
|
||||
path: /spec/sources/0/helm/valueFiles/1
|
||||
value: $values/infra/values/aws-prod/gitea-values.yaml
|
||||
|
||||
# OpenCost: swap upc-dev → aws-prod
|
||||
- target:
|
||||
kind: Application
|
||||
name: opencost
|
||||
patch: |
|
||||
- op: replace
|
||||
path: /spec/sources/0/helm/valueFiles/1
|
||||
value: $values/infra/values/aws-prod/opencost-values.yaml
|
||||
|
||||
# TODO: add patches for keycloak, grafana, secrets, enterprise-apps
|
||||
# when deploying to this cluster (these are deployment-specific, not cloud-specific)
|
||||
@@ -1,35 +0,0 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
- ../../base
|
||||
|
||||
patches:
|
||||
# Traefik: swap upc-dev → azure-dev
|
||||
- target:
|
||||
kind: Application
|
||||
name: traefik
|
||||
patch: |
|
||||
- op: replace
|
||||
path: /spec/sources/0/helm/valueFiles/1
|
||||
value: $values/infra/values/azure-dev/traefik-values.yaml
|
||||
|
||||
# Gitea: swap upc-dev → azure-dev
|
||||
- target:
|
||||
kind: Application
|
||||
name: gitea
|
||||
patch: |
|
||||
- op: replace
|
||||
path: /spec/sources/0/helm/valueFiles/1
|
||||
value: $values/infra/values/azure-dev/gitea-values.yaml
|
||||
|
||||
# OpenCost: swap upc-dev → azure-dev
|
||||
- target:
|
||||
kind: Application
|
||||
name: opencost
|
||||
patch: |
|
||||
- op: replace
|
||||
path: /spec/sources/0/helm/valueFiles/1
|
||||
value: $values/infra/values/azure-dev/opencost-values.yaml
|
||||
|
||||
# TODO: add patches for keycloak, grafana, secrets, enterprise-apps
|
||||
# when deploying to this cluster (these are deployment-specific, not cloud-specific)
|
||||
@@ -1,35 +0,0 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
- ../../base
|
||||
|
||||
patches:
|
||||
# Traefik: swap upc-dev → azure-prod
|
||||
- target:
|
||||
kind: Application
|
||||
name: traefik
|
||||
patch: |
|
||||
- op: replace
|
||||
path: /spec/sources/0/helm/valueFiles/1
|
||||
value: $values/infra/values/azure-prod/traefik-values.yaml
|
||||
|
||||
# Gitea: swap upc-dev → azure-prod
|
||||
- target:
|
||||
kind: Application
|
||||
name: gitea
|
||||
patch: |
|
||||
- op: replace
|
||||
path: /spec/sources/0/helm/valueFiles/1
|
||||
value: $values/infra/values/azure-prod/gitea-values.yaml
|
||||
|
||||
# OpenCost: swap upc-dev → azure-prod
|
||||
- target:
|
||||
kind: Application
|
||||
name: opencost
|
||||
patch: |
|
||||
- op: replace
|
||||
path: /spec/sources/0/helm/valueFiles/1
|
||||
value: $values/infra/values/azure-prod/opencost-values.yaml
|
||||
|
||||
# TODO: add patches for keycloak, grafana, secrets, enterprise-apps
|
||||
# when deploying to this cluster (these are deployment-specific, not cloud-specific)
|
||||
@@ -1,35 +0,0 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
- ../../base
|
||||
|
||||
patches:
|
||||
# Traefik: swap upc-dev → gcp-dev
|
||||
- target:
|
||||
kind: Application
|
||||
name: traefik
|
||||
patch: |
|
||||
- op: replace
|
||||
path: /spec/sources/0/helm/valueFiles/1
|
||||
value: $values/infra/values/gcp-dev/traefik-values.yaml
|
||||
|
||||
# Gitea: swap upc-dev → gcp-dev
|
||||
- target:
|
||||
kind: Application
|
||||
name: gitea
|
||||
patch: |
|
||||
- op: replace
|
||||
path: /spec/sources/0/helm/valueFiles/1
|
||||
value: $values/infra/values/gcp-dev/gitea-values.yaml
|
||||
|
||||
# OpenCost: swap upc-dev → gcp-dev
|
||||
- target:
|
||||
kind: Application
|
||||
name: opencost
|
||||
patch: |
|
||||
- op: replace
|
||||
path: /spec/sources/0/helm/valueFiles/1
|
||||
value: $values/infra/values/gcp-dev/opencost-values.yaml
|
||||
|
||||
# TODO: add patches for keycloak, grafana, secrets, enterprise-apps
|
||||
# when deploying to this cluster (these are deployment-specific, not cloud-specific)
|
||||
@@ -1,35 +0,0 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
- ../../base
|
||||
|
||||
patches:
|
||||
# Traefik: swap upc-dev → gcp-prod
|
||||
- target:
|
||||
kind: Application
|
||||
name: traefik
|
||||
patch: |
|
||||
- op: replace
|
||||
path: /spec/sources/0/helm/valueFiles/1
|
||||
value: $values/infra/values/gcp-prod/traefik-values.yaml
|
||||
|
||||
# Gitea: swap upc-dev → gcp-prod
|
||||
- target:
|
||||
kind: Application
|
||||
name: gitea
|
||||
patch: |
|
||||
- op: replace
|
||||
path: /spec/sources/0/helm/valueFiles/1
|
||||
value: $values/infra/values/gcp-prod/gitea-values.yaml
|
||||
|
||||
# OpenCost: swap upc-dev → gcp-prod
|
||||
- target:
|
||||
kind: Application
|
||||
name: opencost
|
||||
patch: |
|
||||
- op: replace
|
||||
path: /spec/sources/0/helm/valueFiles/1
|
||||
value: $values/infra/values/gcp-prod/opencost-values.yaml
|
||||
|
||||
# TODO: add patches for keycloak, grafana, secrets, enterprise-apps
|
||||
# when deploying to this cluster (these are deployment-specific, not cloud-specific)
|
||||
@@ -48,21 +48,3 @@ patches:
|
||||
- op: replace
|
||||
path: /spec/source/path
|
||||
value: apps/overlays/upc-prod
|
||||
|
||||
# Gitea: swap upc-dev → upc-prod
|
||||
- target:
|
||||
kind: Application
|
||||
name: gitea
|
||||
patch: |
|
||||
- op: replace
|
||||
path: /spec/sources/0/helm/valueFiles/1
|
||||
value: $values/infra/values/upc-prod/gitea-values.yaml
|
||||
|
||||
# OpenCost: swap upc-dev → upc-prod
|
||||
- target:
|
||||
kind: Application
|
||||
name: opencost
|
||||
patch: |
|
||||
- op: replace
|
||||
path: /spec/sources/0/helm/valueFiles/1
|
||||
value: $values/infra/values/upc-prod/opencost-values.yaml
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
# AWS EBS gp3 storage class (requires EBS CSI driver)
|
||||
persistence:
|
||||
storageClass: gp3
|
||||
postgresql:
|
||||
primary:
|
||||
persistence:
|
||||
storageClass: gp3
|
||||
@@ -1,13 +0,0 @@
|
||||
# AWS native pricing via Cost and Usage Reports
|
||||
opencost:
|
||||
exporter:
|
||||
customPricing:
|
||||
enabled: true
|
||||
provider: aws
|
||||
aws:
|
||||
service_key_name: "" # <- populate or use IRSA
|
||||
service_key_secret: ""
|
||||
spot_data_region: ""
|
||||
spot_data_bucket: ""
|
||||
spot_data_prefix: ""
|
||||
account_id: ""
|
||||
@@ -1,18 +0,0 @@
|
||||
# AWS EKS — NLB with Proxy Protocol v2 for real client IPs
|
||||
service:
|
||||
annotations:
|
||||
service.beta.kubernetes.io/aws-load-balancer-type: "external"
|
||||
service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: "ip"
|
||||
service.beta.kubernetes.io/aws-load-balancer-scheme: "internet-facing"
|
||||
service.beta.kubernetes.io/aws-load-balancer-proxy-protocol: "*"
|
||||
ports:
|
||||
web:
|
||||
proxyProtocol:
|
||||
trustedIPs: "10.0.0.0/8" # <- adjust to your VPC CIDR
|
||||
forwardedHeaders:
|
||||
trustedIPs: "10.0.0.0/8"
|
||||
websecure:
|
||||
proxyProtocol:
|
||||
trustedIPs: "10.0.0.0/8"
|
||||
forwardedHeaders:
|
||||
trustedIPs: "10.0.0.0/8"
|
||||
@@ -1,7 +0,0 @@
|
||||
# AWS EBS gp3 storage class (requires EBS CSI driver)
|
||||
persistence:
|
||||
storageClass: gp3
|
||||
postgresql:
|
||||
primary:
|
||||
persistence:
|
||||
storageClass: gp3
|
||||
@@ -1,13 +0,0 @@
|
||||
# AWS native pricing via Cost and Usage Reports
|
||||
opencost:
|
||||
exporter:
|
||||
customPricing:
|
||||
enabled: true
|
||||
provider: aws
|
||||
aws:
|
||||
service_key_name: "" # <- populate or use IRSA
|
||||
service_key_secret: ""
|
||||
spot_data_region: ""
|
||||
spot_data_bucket: ""
|
||||
spot_data_prefix: ""
|
||||
account_id: ""
|
||||
@@ -1,18 +0,0 @@
|
||||
# AWS EKS — NLB with Proxy Protocol v2 for real client IPs
|
||||
service:
|
||||
annotations:
|
||||
service.beta.kubernetes.io/aws-load-balancer-type: "external"
|
||||
service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: "ip"
|
||||
service.beta.kubernetes.io/aws-load-balancer-scheme: "internet-facing"
|
||||
service.beta.kubernetes.io/aws-load-balancer-proxy-protocol: "*"
|
||||
ports:
|
||||
web:
|
||||
proxyProtocol:
|
||||
trustedIPs: "10.0.0.0/8" # <- adjust to your VPC CIDR
|
||||
forwardedHeaders:
|
||||
trustedIPs: "10.0.0.0/8"
|
||||
websecure:
|
||||
proxyProtocol:
|
||||
trustedIPs: "10.0.0.0/8"
|
||||
forwardedHeaders:
|
||||
trustedIPs: "10.0.0.0/8"
|
||||
@@ -1,7 +0,0 @@
|
||||
# Azure Managed Disk (Premium SSD via CSI driver)
|
||||
persistence:
|
||||
storageClass: managed-csi-premium
|
||||
postgresql:
|
||||
primary:
|
||||
persistence:
|
||||
storageClass: managed-csi-premium
|
||||
@@ -1,11 +0,0 @@
|
||||
# Azure native pricing via Billing API
|
||||
opencost:
|
||||
exporter:
|
||||
customPricing:
|
||||
enabled: true
|
||||
provider: azure
|
||||
azure:
|
||||
subscriptionID: "" # <- populate
|
||||
clientID: ""
|
||||
clientSecret: ""
|
||||
tenantID: ""
|
||||
@@ -1,16 +0,0 @@
|
||||
# Azure AKS — Standard Load Balancer
|
||||
# Note: Azure Standard LB does not support Proxy Protocol.
|
||||
# Use externalTrafficPolicy: Local on the Traefik service to preserve
|
||||
# client IPs, or deploy behind Azure Application Gateway.
|
||||
service:
|
||||
annotations:
|
||||
service.beta.kubernetes.io/azure-load-balancer-health-probe-request-path: "/ping"
|
||||
spec:
|
||||
externalTrafficPolicy: Local
|
||||
ports:
|
||||
web:
|
||||
forwardedHeaders:
|
||||
trustedIPs: "10.0.0.0/8,168.63.129.16/32" # <- VNet CIDR + Azure health probe
|
||||
websecure:
|
||||
forwardedHeaders:
|
||||
trustedIPs: "10.0.0.0/8,168.63.129.16/32"
|
||||
@@ -1,7 +0,0 @@
|
||||
# Azure Managed Disk (Premium SSD via CSI driver)
|
||||
persistence:
|
||||
storageClass: managed-csi-premium
|
||||
postgresql:
|
||||
primary:
|
||||
persistence:
|
||||
storageClass: managed-csi-premium
|
||||
@@ -1,11 +0,0 @@
|
||||
# Azure native pricing via Billing API
|
||||
opencost:
|
||||
exporter:
|
||||
customPricing:
|
||||
enabled: true
|
||||
provider: azure
|
||||
azure:
|
||||
subscriptionID: "" # <- populate
|
||||
clientID: ""
|
||||
clientSecret: ""
|
||||
tenantID: ""
|
||||
@@ -1,16 +0,0 @@
|
||||
# Azure AKS — Standard Load Balancer
|
||||
# Note: Azure Standard LB does not support Proxy Protocol.
|
||||
# Use externalTrafficPolicy: Local on the Traefik service to preserve
|
||||
# client IPs, or deploy behind Azure Application Gateway.
|
||||
service:
|
||||
annotations:
|
||||
service.beta.kubernetes.io/azure-load-balancer-health-probe-request-path: "/ping"
|
||||
spec:
|
||||
externalTrafficPolicy: Local
|
||||
ports:
|
||||
web:
|
||||
forwardedHeaders:
|
||||
trustedIPs: "10.0.0.0/8,168.63.129.16/32" # <- VNet CIDR + Azure health probe
|
||||
websecure:
|
||||
forwardedHeaders:
|
||||
trustedIPs: "10.0.0.0/8,168.63.129.16/32"
|
||||
@@ -2,12 +2,21 @@ configs:
|
||||
secret:
|
||||
createSecret: true
|
||||
argocdServerAdminPassword: "$2b$12$Tmb1jH7ADvwWoUoNPXXsfOf6JqEluqhq8mL06a8DGT2AP1GzbNsCm"
|
||||
ssh:
|
||||
knownHosts: |
|
||||
[git.forteapps.net]:2222 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDTwi40de8yTGUuRT0i/XGicQ672BLhYR6D/lDquJrp/tdrWoZhVVPy0wxSkWsq1V92iiAUuQnXagOGsLBGZT9uDLWKvEmNDnCfjzTMq3J1iA3vk2rQ8WBlCzhvmeCV/r0ufl6vsgfwxSRomLZeqa2UkLHx69gy2Njb1S2/aZK1Q53f466hCUfDULZrTn2Nn5Sj8cEbJ8EyvVN2YG9HYBxQdzKRPZEmS1vyzmn8YrYIkZseIRQElabzWGh86owuaaqnwJhTJj1j2sEUeIet04sGKJcnxx2UL4H90N66LKMldmMiuli+ve/CjJmMwDl0zGkjIniT3XR8CyEXYHli7B1hR8Z+dbK6DBgjz+28lFgMIRY70KkZJNsJcBNZLZ5fHwCI13a9U3Uhg3Pu/6s0zlosM4CrAQNQCRe95ZPtCpdFhlGrOl4m1rdSK2meL6rND0TBBuZbaFF6Py7TawLCAiO2KRaVqhu9OFVjwJ/nifgLzFGwWj+WcYmpuR+DwozrF/Hl7QYsz1x4GO1SONY07KbIFkUCHOMAh0AELY5YE4eGI4mtG6SecdPaAdLREGZYK4IcyP5i1QW9g0wmfRSsV9jy+r0ivBxixxh4yJiNpkg6NXak40gQtGIme9EJ+DxrRLruNsfDILWcdSuH/wvuorv56NpQFGB0FzB6LXMloSYptQ==
|
||||
cm:
|
||||
application.resourceTrackingMethod: annotation
|
||||
timeout.reconciliation: 60s
|
||||
admin.enabled: "true"
|
||||
params:
|
||||
"server.insecure": true
|
||||
repoServer:
|
||||
env:
|
||||
# Disable git submodule checkout - submodules (e.g. shared-prompts)
|
||||
# are not needed for K8s manifest generation
|
||||
- name: ARGOCD_GIT_MODULES_ENABLED
|
||||
value: "false"
|
||||
server:
|
||||
ingress:
|
||||
enabled: false
|
||||
|
||||
150
infra/values/base/backstage-values.yaml
Normal file
150
infra/values/base/backstage-values.yaml
Normal file
@@ -0,0 +1,150 @@
|
||||
# Red Hat Developer Hub (RHDH) - Internal Developer Portal
|
||||
# Helm chart: https://github.com/redhat-developer/rhdh-chart
|
||||
# Includes 27+ plugins out of the box: ArgoCD, Kubernetes, Keycloak,
|
||||
# GitHub, GitLab, Jira, SonarQube, Tekton, Jenkins, and more.
|
||||
|
||||
global:
|
||||
auth:
|
||||
backend:
|
||||
enabled: true
|
||||
dynamic:
|
||||
includes:
|
||||
- dynamic-plugins.default.yaml
|
||||
plugins: []
|
||||
|
||||
# Disable OpenShift Route (not on OpenShift)
|
||||
route:
|
||||
enabled: false
|
||||
|
||||
upstream:
|
||||
backstage:
|
||||
image:
|
||||
registry: quay.io
|
||||
repository: rhdh-community/rhdh
|
||||
tag: next
|
||||
|
||||
podSecurityContext:
|
||||
runAsUser: 1001
|
||||
runAsGroup: 1001
|
||||
fsGroup: 1001
|
||||
|
||||
resources:
|
||||
requests:
|
||||
cpu: 250m
|
||||
memory: 512Mi
|
||||
limits:
|
||||
cpu: 1000m
|
||||
memory: 1Gi
|
||||
|
||||
extraEnvVarsSecrets:
|
||||
- backstage-oidc-credentials
|
||||
- backstage-session-secret
|
||||
|
||||
appConfig:
|
||||
app:
|
||||
title: "Forte Backstage"
|
||||
baseUrl: http://localhost:7007
|
||||
|
||||
backend:
|
||||
baseUrl: http://localhost:7007
|
||||
|
||||
# -- Keycloak OIDC authentication
|
||||
signInPage: oidc
|
||||
auth:
|
||||
session:
|
||||
secret: ${AUTH_SESSION_SECRET}
|
||||
environment: production
|
||||
providers:
|
||||
oidc:
|
||||
production:
|
||||
metadataUrl: https://id.forteapps.net/realms/forte/.well-known/openid-configuration
|
||||
clientId: ${AUTH_OIDC_CLIENT_ID}
|
||||
clientSecret: ${AUTH_OIDC_CLIENT_SECRET}
|
||||
prompt: auto
|
||||
# Allow login before User entities exist in the catalog.
|
||||
# Remove once org data is populated.
|
||||
dangerouslyAllowSignInWithoutUserInCatalog: true
|
||||
signIn:
|
||||
resolvers:
|
||||
- resolver: emailMatchingUserEntityProfileEmail
|
||||
|
||||
# -- Gitea SCM integration (for catalog URL resolution)
|
||||
integrations:
|
||||
gitea:
|
||||
- host: git.forteapps.net
|
||||
|
||||
# -- Software catalog
|
||||
catalog:
|
||||
rules:
|
||||
- allow:
|
||||
- Component
|
||||
- System
|
||||
- API
|
||||
- Resource
|
||||
- Location
|
||||
- Template
|
||||
- Group
|
||||
- User
|
||||
- Domain
|
||||
providers:
|
||||
# Auto-import users and groups from Keycloak
|
||||
keycloakOrg:
|
||||
default:
|
||||
baseUrl: https://id.forteapps.net
|
||||
realm: forte
|
||||
clientId: ${AUTH_OIDC_CLIENT_ID}
|
||||
clientSecret: ${AUTH_OIDC_CLIENT_SECRET}
|
||||
schedule:
|
||||
frequency: { minutes: 30 }
|
||||
timeout: { minutes: 3 }
|
||||
initialDelay: { seconds: 15 }
|
||||
# Auto-discover catalog-info.yaml from all Forte org repos
|
||||
gitea:
|
||||
forte:
|
||||
organization: Forte
|
||||
host: git.forteapps.net
|
||||
catalogPath: catalog-info.yaml
|
||||
schedule:
|
||||
frequency: { minutes: 30 }
|
||||
timeout: { minutes: 3 }
|
||||
locations:
|
||||
# Backstage's own org data (bootstrap teams, systems, domains)
|
||||
# - type: url
|
||||
# target: https://git.forteapps.net/Forte/backstage-catalog/raw/branch/main/org.yaml
|
||||
# rules:
|
||||
# - allow: [Group, User, System, Domain]
|
||||
|
||||
ingress:
|
||||
enabled: true
|
||||
className: traefik
|
||||
annotations:
|
||||
traefik.ingress.kubernetes.io/router.entrypoints: websecure
|
||||
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||
tls:
|
||||
enabled: true
|
||||
secretName: backstage-tls
|
||||
|
||||
postgresql:
|
||||
enabled: true
|
||||
auth:
|
||||
# Fixed passwords prevent Helm from regenerating the Secret on
|
||||
# each sync, which would mismatch with the PVC-persisted data.
|
||||
password: backstage-db-pw
|
||||
postgresPassword: backstage-admin-pw
|
||||
primary:
|
||||
persistence:
|
||||
enabled: true
|
||||
size: 2Gi
|
||||
podSecurityContext:
|
||||
enabled: true
|
||||
fsGroup: 26
|
||||
runAsUser: 26
|
||||
resources:
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 128Mi
|
||||
limits:
|
||||
cpu: 250m
|
||||
memory: 512Mi
|
||||
volumePermissions:
|
||||
enabled: false
|
||||
@@ -130,6 +130,7 @@ persistence:
|
||||
size: 10Gi
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
storageClass: upcloud-block-storage-maxiops
|
||||
|
||||
# -- Recreate strategy to avoid Multi-Attach errors with RWO volumes
|
||||
strategy:
|
||||
@@ -155,6 +156,7 @@ postgresql:
|
||||
persistence:
|
||||
enabled: true
|
||||
size: 8Gi
|
||||
storageClass: upcloud-block-storage-maxiops
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
|
||||
@@ -116,12 +116,12 @@ extraDeploy:
|
||||
metadata:
|
||||
name: keycloak-client-registrar
|
||||
rules:
|
||||
- apiGroups: [""]
|
||||
resources: ["secrets"]
|
||||
verbs: ["get", "list", "create", "update", "patch"]
|
||||
- apiGroups: [""]
|
||||
resources: ["namespaces"]
|
||||
verbs: ["get", "list"]
|
||||
- apiGroups: [ "" ]
|
||||
resources: [ "secrets" ]
|
||||
verbs: [ "get", "list", "create", "update", "patch" ]
|
||||
- apiGroups: [ "" ]
|
||||
resources: [ "namespaces" ]
|
||||
verbs: [ "get", "list" ]
|
||||
|
||||
# -- ClusterRoleBinding for the registrar ServiceAccount
|
||||
- apiVersion: rbac.authorization.k8s.io/v1
|
||||
@@ -158,7 +158,7 @@ extraDeploy:
|
||||
containers:
|
||||
- name: registrar
|
||||
image: alpine:3.20
|
||||
command: ["/bin/sh", "-c"]
|
||||
command: [ "/bin/sh", "-c" ]
|
||||
args:
|
||||
- |
|
||||
set -e
|
||||
|
||||
@@ -10,8 +10,18 @@ opencost:
|
||||
serviceName: prometheus-server
|
||||
namespaceName: monitoring
|
||||
port: 80
|
||||
# Cloud-specific pricing is in per-cluster value overrides
|
||||
# (e.g. infra/values/upc-dev/opencost-values.yaml)
|
||||
customPricing:
|
||||
enabled: true
|
||||
provider: custom
|
||||
costModel:
|
||||
description: "UpCloud 4-node cluster pricing"
|
||||
CPU: "5.86"
|
||||
RAM: "1.46"
|
||||
GPU: "0"
|
||||
storage: "0.34"
|
||||
zoneNetworkEgress: "0"
|
||||
regionNetworkEgress: "0"
|
||||
internetNetworkEgress: "0"
|
||||
ui:
|
||||
enabled: false
|
||||
service:
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
# GCP Persistent Disk (SSD via CSI driver)
|
||||
persistence:
|
||||
storageClass: premium-rwo
|
||||
postgresql:
|
||||
primary:
|
||||
persistence:
|
||||
storageClass: premium-rwo
|
||||
@@ -1,9 +0,0 @@
|
||||
# GCP native pricing via Cloud Billing API
|
||||
opencost:
|
||||
exporter:
|
||||
customPricing:
|
||||
enabled: true
|
||||
provider: gcp
|
||||
gcp:
|
||||
projectID: "" # <- populate with your GCP project ID
|
||||
key: "" # <- or use Workload Identity
|
||||
@@ -1,15 +0,0 @@
|
||||
# GCP GKE — External passthrough Network Load Balancer
|
||||
service:
|
||||
annotations:
|
||||
cloud.google.com/l4-rbs: "enabled"
|
||||
ports:
|
||||
web:
|
||||
proxyProtocol:
|
||||
trustedIPs: "10.0.0.0/8,35.191.0.0/16,130.211.0.0/22" # <- subnet CIDR + GCP health checks
|
||||
forwardedHeaders:
|
||||
trustedIPs: "10.0.0.0/8,35.191.0.0/16,130.211.0.0/22"
|
||||
websecure:
|
||||
proxyProtocol:
|
||||
trustedIPs: "10.0.0.0/8,35.191.0.0/16,130.211.0.0/22"
|
||||
forwardedHeaders:
|
||||
trustedIPs: "10.0.0.0/8,35.191.0.0/16,130.211.0.0/22"
|
||||
@@ -1,7 +0,0 @@
|
||||
# GCP Persistent Disk (SSD via CSI driver)
|
||||
persistence:
|
||||
storageClass: premium-rwo
|
||||
postgresql:
|
||||
primary:
|
||||
persistence:
|
||||
storageClass: premium-rwo
|
||||
@@ -1,9 +0,0 @@
|
||||
# GCP native pricing via Cloud Billing API
|
||||
opencost:
|
||||
exporter:
|
||||
customPricing:
|
||||
enabled: true
|
||||
provider: gcp
|
||||
gcp:
|
||||
projectID: "" # <- populate with your GCP project ID
|
||||
key: "" # <- or use Workload Identity
|
||||
@@ -1,15 +0,0 @@
|
||||
# GCP GKE — External passthrough Network Load Balancer
|
||||
service:
|
||||
annotations:
|
||||
cloud.google.com/l4-rbs: "enabled"
|
||||
ports:
|
||||
web:
|
||||
proxyProtocol:
|
||||
trustedIPs: "10.0.0.0/8,35.191.0.0/16,130.211.0.0/22" # <- subnet CIDR + GCP health checks
|
||||
forwardedHeaders:
|
||||
trustedIPs: "10.0.0.0/8,35.191.0.0/16,130.211.0.0/22"
|
||||
websecure:
|
||||
proxyProtocol:
|
||||
trustedIPs: "10.0.0.0/8,35.191.0.0/16,130.211.0.0/22"
|
||||
forwardedHeaders:
|
||||
trustedIPs: "10.0.0.0/8,35.191.0.0/16,130.211.0.0/22"
|
||||
12
infra/values/upc-dev/backstage-values.yaml
Normal file
12
infra/values/upc-dev/backstage-values.yaml
Normal file
@@ -0,0 +1,12 @@
|
||||
global:
|
||||
host: backstage.forteapps.net
|
||||
|
||||
upstream:
|
||||
backstage:
|
||||
appConfig:
|
||||
app:
|
||||
baseUrl: https://backstage.forteapps.net
|
||||
backend:
|
||||
baseUrl: https://backstage.forteapps.net
|
||||
ingress:
|
||||
host: backstage.forteapps.net
|
||||
@@ -1,7 +0,0 @@
|
||||
# UpCloud storage class for Gitea and its embedded PostgreSQL
|
||||
persistence:
|
||||
storageClass: upcloud-block-storage-maxiops
|
||||
postgresql:
|
||||
primary:
|
||||
persistence:
|
||||
storageClass: upcloud-block-storage-maxiops
|
||||
@@ -1,15 +0,0 @@
|
||||
# UpCloud custom pricing (no native OpenCost integration)
|
||||
opencost:
|
||||
exporter:
|
||||
customPricing:
|
||||
enabled: true
|
||||
provider: custom
|
||||
costModel:
|
||||
description: "UpCloud 4-node cluster pricing"
|
||||
CPU: "5.86"
|
||||
RAM: "1.46"
|
||||
GPU: "0"
|
||||
storage: "0.34"
|
||||
zoneNetworkEgress: "0"
|
||||
regionNetworkEgress: "0"
|
||||
internetNetworkEgress: "0"
|
||||
@@ -1,7 +0,0 @@
|
||||
# UpCloud storage class for Gitea and its embedded PostgreSQL
|
||||
persistence:
|
||||
storageClass: upcloud-block-storage-maxiops
|
||||
postgresql:
|
||||
primary:
|
||||
persistence:
|
||||
storageClass: upcloud-block-storage-maxiops
|
||||
@@ -1,15 +0,0 @@
|
||||
# UpCloud custom pricing (no native OpenCost integration)
|
||||
opencost:
|
||||
exporter:
|
||||
customPricing:
|
||||
enabled: true
|
||||
provider: custom
|
||||
costModel:
|
||||
description: "UpCloud 4-node cluster pricing"
|
||||
CPU: "5.86"
|
||||
RAM: "1.46"
|
||||
GPU: "0"
|
||||
storage: "0.34"
|
||||
zoneNetworkEgress: "0"
|
||||
regionNetworkEgress: "0"
|
||||
internetNetworkEgress: "0"
|
||||
@@ -1,94 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Gitea backup helper for AWS S3
|
||||
# Uses the gitea-backup-s3 secret in the gitea namespace
|
||||
# (same secret schema: S3_ENDPOINT, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, S3_BUCKET)
|
||||
#
|
||||
# For AWS, S3_ENDPOINT is typically https://s3.<region>.amazonaws.com
|
||||
#
|
||||
# Usage:
|
||||
# ./scripts/gitea-backup-aws.sh list # list all backups
|
||||
# ./scripts/gitea-backup-aws.sh download <filename> # download a backup to current dir
|
||||
# ./scripts/gitea-backup-aws.sh download latest # download the most recent backup
|
||||
|
||||
NAMESPACE="gitea"
|
||||
SECRET="gitea-backup-s3"
|
||||
IMAGE="minio/mc:latest"
|
||||
POD_NAME="gitea-backup-helper"
|
||||
ALIAS_CMD='mc alias set s3 ${S3_ENDPOINT} ${AWS_ACCESS_KEY_ID} ${AWS_SECRET_ACCESS_KEY} > /dev/null'
|
||||
|
||||
cleanup() {
|
||||
kubectl -n "$NAMESPACE" delete pod "$POD_NAME" --ignore-not-found --grace-period=0 > /dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
mc_run() {
|
||||
cleanup
|
||||
kubectl -n "$NAMESPACE" run "$POD_NAME" --restart=Never \
|
||||
--image="$IMAGE" \
|
||||
--overrides="{
|
||||
\"spec\":{\"containers\":[{
|
||||
\"name\":\"$POD_NAME\",
|
||||
\"image\":\"$IMAGE\",
|
||||
\"env\":[{\"name\":\"HOME\",\"value\":\"/tmp\"}],
|
||||
\"command\":[\"sh\",\"-c\",\"${ALIAS_CMD}; $1\"],
|
||||
\"envFrom\":[{\"secretRef\":{\"name\":\"$SECRET\"}}]
|
||||
}]}
|
||||
}" > /dev/null 2>&1
|
||||
|
||||
kubectl -n "$NAMESPACE" wait --for=jsonpath='{.status.phase}'=Succeeded "pod/$POD_NAME" --timeout=120s > /dev/null 2>&1
|
||||
kubectl -n "$NAMESPACE" logs "$POD_NAME"
|
||||
cleanup
|
||||
}
|
||||
|
||||
case "${1:-help}" in
|
||||
list)
|
||||
echo "Listing backups..."
|
||||
mc_run 'mc ls s3/${S3_BUCKET}/'
|
||||
;;
|
||||
|
||||
download)
|
||||
FILE="${2:?Usage: $0 download <filename|latest>}"
|
||||
|
||||
if [ "$FILE" = "latest" ]; then
|
||||
echo "Finding latest backup..."
|
||||
FILE=$(mc_run 'mc ls s3/${S3_BUCKET}/' | sort | tail -1 | awk '{print $NF}' | tr -d '[:space:]')
|
||||
if [ -z "$FILE" ]; then
|
||||
echo "No backups found."
|
||||
exit 1
|
||||
fi
|
||||
echo "Latest: $FILE"
|
||||
fi
|
||||
|
||||
echo "Downloading $FILE..."
|
||||
cleanup
|
||||
kubectl -n "$NAMESPACE" run "$POD_NAME" --restart=Never \
|
||||
--image="$IMAGE" \
|
||||
--overrides="{
|
||||
\"spec\":{\"containers\":[{
|
||||
\"name\":\"$POD_NAME\",
|
||||
\"image\":\"$IMAGE\",
|
||||
\"env\":[{\"name\":\"HOME\",\"value\":\"/tmp\"}],
|
||||
\"command\":[\"sh\",\"-c\",\"sleep 300\"],
|
||||
\"envFrom\":[{\"secretRef\":{\"name\":\"$SECRET\"}}]
|
||||
}]}
|
||||
}" > /dev/null 2>&1
|
||||
|
||||
kubectl -n "$NAMESPACE" wait --for=condition=Ready "pod/$POD_NAME" --timeout=60s > /dev/null 2>&1
|
||||
|
||||
echo "Saving to ./$FILE ..."
|
||||
kubectl -n "$NAMESPACE" exec "$POD_NAME" -- sh -c "${ALIAS_CMD} && mc cat s3/\${S3_BUCKET}/$FILE" > "./$FILE"
|
||||
cleanup
|
||||
|
||||
echo "Downloaded: ./$FILE"
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "Gitea backup helper (AWS S3)"
|
||||
echo ""
|
||||
echo "Usage:"
|
||||
echo " $0 list List all backups in S3"
|
||||
echo " $0 download <filename> Download a specific backup"
|
||||
echo " $0 download latest Download the most recent backup"
|
||||
;;
|
||||
esac
|
||||
@@ -1,100 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Gitea backup helper for Azure Blob Storage
|
||||
# Uses the gitea-backup-azure secret in the gitea namespace
|
||||
# Required secret keys:
|
||||
# AZURE_STORAGE_ACCOUNT — storage account name
|
||||
# AZURE_STORAGE_KEY — storage account key
|
||||
# AZURE_CONTAINER — blob container name
|
||||
#
|
||||
# Usage:
|
||||
# ./scripts/gitea-backup-azure.sh list # list all backups
|
||||
# ./scripts/gitea-backup-azure.sh download <filename> # download a backup
|
||||
# ./scripts/gitea-backup-azure.sh download latest # download the most recent backup
|
||||
|
||||
NAMESPACE="gitea"
|
||||
SECRET="gitea-backup-azure"
|
||||
IMAGE="mcr.microsoft.com/azure-cli:latest"
|
||||
POD_NAME="gitea-backup-helper"
|
||||
|
||||
cleanup() {
|
||||
kubectl -n "$NAMESPACE" delete pod "$POD_NAME" --ignore-not-found --grace-period=0 > /dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
az_run() {
|
||||
cleanup
|
||||
kubectl -n "$NAMESPACE" run "$POD_NAME" --restart=Never \
|
||||
--image="$IMAGE" \
|
||||
--overrides="{
|
||||
\"spec\":{\"containers\":[{
|
||||
\"name\":\"$POD_NAME\",
|
||||
\"image\":\"$IMAGE\",
|
||||
\"env\":[{\"name\":\"HOME\",\"value\":\"/tmp\"}],
|
||||
\"command\":[\"sh\",\"-c\",\"$1\"],
|
||||
\"envFrom\":[{\"secretRef\":{\"name\":\"$SECRET\"}}]
|
||||
}]}
|
||||
}" > /dev/null 2>&1
|
||||
|
||||
kubectl -n "$NAMESPACE" wait --for=jsonpath='{.status.phase}'=Succeeded "pod/$POD_NAME" --timeout=120s > /dev/null 2>&1
|
||||
kubectl -n "$NAMESPACE" logs "$POD_NAME"
|
||||
cleanup
|
||||
}
|
||||
|
||||
case "${1:-help}" in
|
||||
list)
|
||||
echo "Listing backups..."
|
||||
az_run 'az storage blob list --account-name ${AZURE_STORAGE_ACCOUNT} --account-key ${AZURE_STORAGE_KEY} --container-name ${AZURE_CONTAINER} --output table --query "[].{Name:name, Size:properties.contentLength, Modified:properties.lastModified}"'
|
||||
;;
|
||||
|
||||
download)
|
||||
FILE="${2:?Usage: $0 download <filename|latest>}"
|
||||
|
||||
if [ "$FILE" = "latest" ]; then
|
||||
echo "Finding latest backup..."
|
||||
FILE=$(az_run 'az storage blob list --account-name ${AZURE_STORAGE_ACCOUNT} --account-key ${AZURE_STORAGE_KEY} --container-name ${AZURE_CONTAINER} --query "sort_by([], &properties.lastModified)[-1].name" -o tsv' | tr -d '[:space:]')
|
||||
if [ -z "$FILE" ]; then
|
||||
echo "No backups found."
|
||||
exit 1
|
||||
fi
|
||||
echo "Latest: $FILE"
|
||||
fi
|
||||
|
||||
echo "Downloading $FILE..."
|
||||
cleanup
|
||||
kubectl -n "$NAMESPACE" run "$POD_NAME" --restart=Never \
|
||||
--image="$IMAGE" \
|
||||
--overrides="{
|
||||
\"spec\":{\"containers\":[{
|
||||
\"name\":\"$POD_NAME\",
|
||||
\"image\":\"$IMAGE\",
|
||||
\"env\":[{\"name\":\"HOME\",\"value\":\"/tmp\"}],
|
||||
\"command\":[\"sh\",\"-c\",\"sleep 300\"],
|
||||
\"envFrom\":[{\"secretRef\":{\"name\":\"$SECRET\"}}]
|
||||
}]}
|
||||
}" > /dev/null 2>&1
|
||||
|
||||
kubectl -n "$NAMESPACE" wait --for=condition=Ready "pod/$POD_NAME" --timeout=60s > /dev/null 2>&1
|
||||
|
||||
echo "Saving to ./$FILE ..."
|
||||
kubectl -n "$NAMESPACE" exec "$POD_NAME" -- \
|
||||
az storage blob download \
|
||||
--account-name "\${AZURE_STORAGE_ACCOUNT}" \
|
||||
--account-key "\${AZURE_STORAGE_KEY}" \
|
||||
--container-name "\${AZURE_CONTAINER}" \
|
||||
--name "$FILE" \
|
||||
--file /dev/stdout 2>/dev/null > "./$FILE"
|
||||
cleanup
|
||||
|
||||
echo "Downloaded: ./$FILE"
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "Gitea backup helper (Azure Blob Storage)"
|
||||
echo ""
|
||||
echo "Usage:"
|
||||
echo " $0 list List all backups in Azure Blob"
|
||||
echo " $0 download <filename> Download a specific backup"
|
||||
echo " $0 download latest Download the most recent backup"
|
||||
;;
|
||||
esac
|
||||
@@ -1,95 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Gitea backup helper for Google Cloud Storage
|
||||
# Uses the gitea-backup-gcs secret in the gitea namespace
|
||||
# Required secret keys:
|
||||
# GCS_BUCKET — bucket name (without gs:// prefix)
|
||||
# GOOGLE_APPLICATION_CREDENTIALS_JSON — service account key JSON
|
||||
# (alternatively, use Workload Identity and omit the key)
|
||||
#
|
||||
# Usage:
|
||||
# ./scripts/gitea-backup-gcp.sh list # list all backups
|
||||
# ./scripts/gitea-backup-gcp.sh download <filename> # download a backup
|
||||
# ./scripts/gitea-backup-gcp.sh download latest # download the most recent backup
|
||||
|
||||
NAMESPACE="gitea"
|
||||
SECRET="gitea-backup-gcs"
|
||||
IMAGE="gcr.io/google.com/cloudsdktool/google-cloud-cli:slim"
|
||||
POD_NAME="gitea-backup-helper"
|
||||
AUTH_CMD='if [ -n "${GOOGLE_APPLICATION_CREDENTIALS_JSON:-}" ]; then echo "${GOOGLE_APPLICATION_CREDENTIALS_JSON}" > /tmp/gcs-key.json && gcloud auth activate-service-account --key-file=/tmp/gcs-key.json > /dev/null 2>&1; fi'
|
||||
|
||||
cleanup() {
|
||||
kubectl -n "$NAMESPACE" delete pod "$POD_NAME" --ignore-not-found --grace-period=0 > /dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
gcs_run() {
|
||||
cleanup
|
||||
kubectl -n "$NAMESPACE" run "$POD_NAME" --restart=Never \
|
||||
--image="$IMAGE" \
|
||||
--overrides="{
|
||||
\"spec\":{\"containers\":[{
|
||||
\"name\":\"$POD_NAME\",
|
||||
\"image\":\"$IMAGE\",
|
||||
\"env\":[{\"name\":\"HOME\",\"value\":\"/tmp\"}],
|
||||
\"command\":[\"sh\",\"-c\",\"${AUTH_CMD}; $1\"],
|
||||
\"envFrom\":[{\"secretRef\":{\"name\":\"$SECRET\"}}]
|
||||
}]}
|
||||
}" > /dev/null 2>&1
|
||||
|
||||
kubectl -n "$NAMESPACE" wait --for=jsonpath='{.status.phase}'=Succeeded "pod/$POD_NAME" --timeout=120s > /dev/null 2>&1
|
||||
kubectl -n "$NAMESPACE" logs "$POD_NAME"
|
||||
cleanup
|
||||
}
|
||||
|
||||
case "${1:-help}" in
|
||||
list)
|
||||
echo "Listing backups..."
|
||||
gcs_run 'gsutil ls -l gs://${GCS_BUCKET}/'
|
||||
;;
|
||||
|
||||
download)
|
||||
FILE="${2:?Usage: $0 download <filename|latest>}"
|
||||
|
||||
if [ "$FILE" = "latest" ]; then
|
||||
echo "Finding latest backup..."
|
||||
FILE=$(gcs_run 'gsutil ls gs://${GCS_BUCKET}/' | grep -v '^$' | grep -v 'TOTAL' | sort | tail -1 | xargs -I{} basename {} | tr -d '[:space:]')
|
||||
if [ -z "$FILE" ]; then
|
||||
echo "No backups found."
|
||||
exit 1
|
||||
fi
|
||||
echo "Latest: $FILE"
|
||||
fi
|
||||
|
||||
echo "Downloading $FILE..."
|
||||
cleanup
|
||||
kubectl -n "$NAMESPACE" run "$POD_NAME" --restart=Never \
|
||||
--image="$IMAGE" \
|
||||
--overrides="{
|
||||
\"spec\":{\"containers\":[{
|
||||
\"name\":\"$POD_NAME\",
|
||||
\"image\":\"$IMAGE\",
|
||||
\"env\":[{\"name\":\"HOME\",\"value\":\"/tmp\"}],
|
||||
\"command\":[\"sh\",\"-c\",\"sleep 300\"],
|
||||
\"envFrom\":[{\"secretRef\":{\"name\":\"$SECRET\"}}]
|
||||
}]}
|
||||
}" > /dev/null 2>&1
|
||||
|
||||
kubectl -n "$NAMESPACE" wait --for=condition=Ready "pod/$POD_NAME" --timeout=60s > /dev/null 2>&1
|
||||
|
||||
echo "Saving to ./$FILE ..."
|
||||
kubectl -n "$NAMESPACE" exec "$POD_NAME" -- sh -c "${AUTH_CMD} && gsutil cat gs://\${GCS_BUCKET}/$FILE" > "./$FILE"
|
||||
cleanup
|
||||
|
||||
echo "Downloaded: ./$FILE"
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "Gitea backup helper (Google Cloud Storage)"
|
||||
echo ""
|
||||
echo "Usage:"
|
||||
echo " $0 list List all backups in GCS"
|
||||
echo " $0 download <filename> Download a specific backup"
|
||||
echo " $0 download latest Download the most recent backup"
|
||||
;;
|
||||
esac
|
||||
Reference in New Issue
Block a user