diff --git a/.dockerignore b/.dockerignore index e872a73e5..d6c4cf642 100644 --- a/.dockerignore +++ b/.dockerignore @@ -61,3 +61,8 @@ libs/digger_config/**/.idea # flyctl launch added from libs/orchestrator/.gitignore libs/orchestrator/**/.idea + +# Don't copy node_modules - we install fresh in the container +ui/node_modules +ui/dist +ui/.next diff --git a/.github/workflows/backend-ee-release.yml b/.github/workflows/backend-ee-release.yml new file mode 100644 index 000000000..eb369f833 --- /dev/null +++ b/.github/workflows/backend-ee-release.yml @@ -0,0 +1,96 @@ +name: Backend EE Docker Release + +on: + push: + tags: + - 'backend-ee/v*' + +permissions: + contents: write + packages: write + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }}/digger-backend-ee + +jobs: + build-and-push: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Derive version + id: meta + run: | + TAG="${GITHUB_REF_NAME}" # e.g. backend-ee/v1.2.3 + VERSION="${TAG##*/}" # v1.2.3 + echo "version=$VERSION" >> $GITHUB_OUTPUT + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata + id: docker-meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=raw,value=${{ steps.meta.outputs.version }} + type=ref,event=tag + type=raw,value=latest + + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: . + file: ./Dockerfile_backend_ee + push: true + platforms: linux/amd64,linux/arm64 + tags: ${{ steps.docker-meta.outputs.tags }} + labels: ${{ steps.docker-meta.outputs.labels }} + build-args: | + COMMIT_SHA=${{ github.sha }} + VERSION=${{ steps.meta.outputs.version }} + + create-release: + needs: [build-and-push] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Derive version + id: meta + run: | + TAG="${GITHUB_REF_NAME}" # e.g. backend-ee/v1.2.3 + VERSION="${TAG##*/}" # v1.2.3 + echo "version=$VERSION" >> $GITHUB_OUTPUT + + - name: Create GitHub Release + uses: softprops/action-gh-release@v1 + with: + tag_name: ${{ github.ref_name }} + name: Digger Backend EE ${{ steps.meta.outputs.version }} + body: | + ## Digger Backend (Enterprise Edition) ${{ steps.meta.outputs.version }} + + Terraform orchestration service with advanced features and multi-VCS support. + + ### Docker Image + ```bash + docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }} + ``` + + draft: false + prerelease: false + diff --git a/.github/workflows/drift-release.yml b/.github/workflows/drift-release.yml new file mode 100644 index 000000000..d1d53524f --- /dev/null +++ b/.github/workflows/drift-release.yml @@ -0,0 +1,96 @@ +name: Drift Docker Release + +on: + push: + tags: + - 'drift/v*' + +permissions: + contents: write + packages: write + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }}/drift + +jobs: + build-and-push: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Derive version + id: meta + run: | + TAG="${GITHUB_REF_NAME}" # e.g. drift/v1.2.3 + VERSION="${TAG##*/}" # v1.2.3 + echo "version=$VERSION" >> $GITHUB_OUTPUT + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata + id: docker-meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=raw,value=${{ steps.meta.outputs.version }} + type=ref,event=tag + type=raw,value=latest + + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: . + file: ./Dockerfile_drift + push: true + platforms: linux/amd64,linux/arm64 + tags: ${{ steps.docker-meta.outputs.tags }} + labels: ${{ steps.docker-meta.outputs.labels }} + build-args: | + COMMIT_SHA=${{ github.sha }} + VERSION=${{ steps.meta.outputs.version }} + + create-release: + needs: [build-and-push] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Derive version + id: meta + run: | + TAG="${GITHUB_REF_NAME}" # e.g. drift/v1.2.3 + VERSION="${TAG##*/}" # v1.2.3 + echo "version=$VERSION" >> $GITHUB_OUTPUT + + - name: Create GitHub Release + uses: softprops/action-gh-release@v1 + with: + tag_name: ${{ github.ref_name }} + name: Drift ${{ steps.meta.outputs.version }} + body: | + ## Drift Detection Service ${{ steps.meta.outputs.version }} + + Automated infrastructure drift detection and reporting service (Enterprise Edition). + + ### Docker Image + ```bash + docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }} + ``` + + draft: false + prerelease: false + diff --git a/.github/workflows/helm-release.yml b/.github/workflows/helm-release.yml index 145d0a10e..793f047f2 100644 --- a/.github/workflows/helm-release.yml +++ b/.github/workflows/helm-release.yml @@ -14,6 +14,14 @@ permissions: jobs: release: runs-on: ubuntu-latest + strategy: + matrix: + chart: + - digger-backend + - taco-drift + - taco-statesman + - taco-ui + - opentaco steps: - name: Checkout uses: actions/checkout@v4 @@ -25,8 +33,8 @@ jobs: run: | echo "${{ secrets.GITHUB_TOKEN }}" | helm registry login ghcr.io -u ${{ github.actor }} --password-stdin - - name: Package and push chart + - name: Package and push ${{ matrix.chart }} run: | - cd helm-charts/digger-backend + cd helm-charts/${{ matrix.chart }} helm package . - helm push digger-backend-*.tgz oci://ghcr.io/diggerhq/helm-charts \ No newline at end of file + helm push ${{ matrix.chart }}-*.tgz oci://ghcr.io/diggerhq/helm-charts \ No newline at end of file diff --git a/.github/workflows/ui-release.yml b/.github/workflows/ui-release.yml new file mode 100644 index 000000000..0054dd658 --- /dev/null +++ b/.github/workflows/ui-release.yml @@ -0,0 +1,96 @@ +name: UI Docker Release + +on: + push: + tags: + - 'ui/v*' + +permissions: + contents: write + packages: write + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }}/taco-ui + +jobs: + build-and-push: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Derive version + id: meta + run: | + TAG="${GITHUB_REF_NAME}" # e.g. ui/v1.2.3 + VERSION="${TAG##*/}" # v1.2.3 + echo "version=$VERSION" >> $GITHUB_OUTPUT + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata + id: docker-meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=raw,value=${{ steps.meta.outputs.version }} + type=ref,event=tag + type=raw,value=latest + + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: . + file: ./Dockerfile_ui + push: true + platforms: linux/amd64,linux/arm64 + tags: ${{ steps.docker-meta.outputs.tags }} + labels: ${{ steps.docker-meta.outputs.labels }} + build-args: | + COMMIT_SHA=${{ github.sha }} + VERSION=${{ steps.meta.outputs.version }} + + create-release: + needs: [build-and-push] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Derive version + id: meta + run: | + TAG="${GITHUB_REF_NAME}" # e.g. ui/v1.2.3 + VERSION="${TAG##*/}" # v1.2.3 + echo "version=$VERSION" >> $GITHUB_OUTPUT + + - name: Create GitHub Release + uses: softprops/action-gh-release@v1 + with: + tag_name: ${{ github.ref_name }} + name: Taco UI ${{ steps.meta.outputs.version }} + body: | + ## Taco UI ${{ steps.meta.outputs.version }} + + Web-based frontend for OpenTaco infrastructure management platform. + + ### Docker Image + ```bash + docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }} + ``` + + draft: false + prerelease: false + diff --git a/.gitignore b/.gitignore index e10d3e5e5..937614b0f 100644 --- a/.gitignore +++ b/.gitignore @@ -37,6 +37,16 @@ data/ taco/data/ +.registry-config +.secrets/ + +# Helm chart archives (generated by helm dependency update) +**/charts/*.tgz +**/Chart.lock + +# Helm values with actual infrastructure config (team-specific) +helm-charts/opentaco/values-production.yaml +helm-charts/opentaco/values-test.yaml # Atlas migration checksums (auto-generated) **/migrations/**/atlas.sum diff --git a/Dockerfile_backend_ee b/Dockerfile_backend_ee index 59bd3c085..f08a977d3 100644 --- a/Dockerfile_backend_ee +++ b/Dockerfile_backend_ee @@ -43,7 +43,7 @@ RUN curl -sSf https://atlasgo.sh | sh # Set gin to production -#ENV GIN_MODE=release +ENV GIN_MODE=release # Expose the running port EXPOSE 3000 diff --git a/Dockerfile_drift b/Dockerfile_drift index 7083ee087..8d2778a36 100644 --- a/Dockerfile_drift +++ b/Dockerfile_drift @@ -35,7 +35,7 @@ RUN curl -sSf https://atlasgo.sh | sh # Set gin to production -#ENV GIN_MODE=release +ENV GIN_MODE=release # Expose the running port EXPOSE 3000 diff --git a/Dockerfile_ui b/Dockerfile_ui new file mode 100644 index 000000000..e27bc9953 --- /dev/null +++ b/Dockerfile_ui @@ -0,0 +1,47 @@ +# Multi-stage build for Taco UI +FROM node:20-alpine AS builder + +WORKDIR /app + +# Copy package files +COPY ui/package.json ./ + +# Install dependencies first +# Explicitly tell npm to install for linux/amd64 platform (for cloud deployment) +# This ensures correct optional dependencies like @rollup/rollup-linux-x64-musl +RUN npm install --cpu=x64 --os=linux --libc=musl && \ + npm cache clean --force + +# Copy source code (node_modules excluded via .dockerignore) +COPY ui/ ./ + +# Build the application for linux/amd64 (with node adapter) +RUN npm run build + +# Production stage +FROM node:20-alpine + +WORKDIR /app + +# Copy package file +COPY ui/package.json ./ + +# Install production dependencies only for linux/amd64 +RUN npm install --omit=dev --cpu=x64 --os=linux --libc=musl && \ + npm cache clean --force + +# Copy built application and server entry from builder +COPY --from=builder /app/dist ./dist +COPY --from=builder /app/server-start.js ./ + +# Expose port +EXPOSE 3030 + +# Set environment variables +ENV NODE_ENV=production +ENV PORT=3030 +ENV HOST=0.0.0.0 + +# Start the Node.js production server +CMD ["node", "server-start.js"] + diff --git a/go.work.sum b/go.work.sum index 6d7b134a1..67c9e0cac 100644 --- a/go.work.sum +++ b/go.work.sum @@ -1,5 +1,6 @@ ariga.io/atlas v0.14.3-0.20231010104048-0c071bfc9161 h1:xZS2wAf1AzRNA/8iD2LTAXtIZuIDYDXsZRlYBAyBu0A= ariga.io/atlas v0.14.3-0.20231010104048-0c071bfc9161/go.mod h1:isZrlzJ5cpoCoKFoY9knZug7Lq4pP1cm8g3XciLZ0Pw= +ariga.io/atlas v0.32.0 h1:y+77nueMrExLiKlz1CcPKh/nU7VSlWfBbwCShsJyvCw= ariga.io/atlas v0.32.0/go.mod h1:Oe1xWPuu5q9LzyrWfbZmEZxFYeu4BHTyzfjeW2aZp/w= ariga.io/atlas-go-sdk v0.7.2/go.mod h1:cFq7bnvHgKTWHCsU46mtkGxdl41rx2o7SjaLoh6cO8M= cel.dev/expr v0.15.0/go.mod h1:TRSuuV7DlVCE/uwv5QbAiW/v8l5O8C4eEPHeu7gf7Sg= @@ -1050,6 +1051,7 @@ github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab h1:xveKWz2iauee github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-openapi/inflect v0.19.0 h1:9jCH9scKIbHeV9m12SmPilScz6krDxKRasNNSNPXu/4= github.com/go-openapi/inflect v0.19.0/go.mod h1:lHpZVlpIQqLyKwJ4N+YSc9hchQy/i12fJykb83CRBH4= github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1 h1:wSt/4CYxs70xbATrGXhokKF1i0tZjENLOo1ioIO13zk= github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9 h1:tF+augKRWlWx0J0B7ZyyKSiTyV6E1zZe+7b3qQlcEf8= @@ -1723,6 +1725,7 @@ golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2 h1:IRJeR9r1pYWsHKTRe/IInb7lYvbBVIqOgsX/u0mbOWY= golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457 h1:zf5N6UOrA487eEFacMePxjXAJctxKmyjKUsjA11Uzuk= golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0= +golang.org/x/telemetry v0.0.0-20250710130107-8d8967aff50b h1:DU+gwOBXU+6bO0sEyO7o/NeMlxZxCZEvI7v+J4a1zRQ= golang.org/x/telemetry v0.0.0-20250710130107-8d8967aff50b/go.mod h1:4ZwOYna0/zsOKwuR5X/m0QFOJpSZvAxFfkQT+Erd9D4= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= diff --git a/helm-charts/README.md b/helm-charts/README.md index 20134dad3..743631a82 100644 --- a/helm-charts/README.md +++ b/helm-charts/README.md @@ -1,126 +1,391 @@ -# Digger Backend Helm Chart +# OpenTaco Helm Charts -This Helm chart deploys the Digger backend orchestrator for managing Terraform/OpenTofu runs in your CI/CD pipelines. +Production-ready Kubernetes deployment for the OpenTaco infrastructure management platform. -## Installation - -### Install from GitHub Container Registry +## Quick Start ```bash -# Install directly -helm install digger-backend oci://ghcr.io/diggerhq/helm-charts/digger-backend \ - --namespace digger \ - --create-namespace \ - --values values.yaml +# 1. Configure values file (see Configuration Checklist below) +curl -O https://raw.githubusercontent.com/diggerhq/digger/develop/helm-charts/opentaco/values-test.yaml.example +mv values-test.yaml.example values-test.yaml +# Edit values-test.yaml with your GCP project ID and settings + +# 2. Create namespace +kubectl create namespace opentaco + +# 3. Create secrets (see Secret Management below) +kubectl create secret generic ui-secrets \ + --from-env-file=.secrets/ui.env -n opentaco + +kubectl create secret generic backend-secrets \ + --from-env-file=.secrets/digger-backend.env -n opentaco + +kubectl create secret generic statesman-secrets \ + --from-env-file=.secrets/statesman.env -n opentaco -# Or pull a specific version -helm pull oci://ghcr.io/diggerhq/helm-charts/digger-backend --version 0.1.12 +kubectl create secret generic drift-secrets \ + --from-env-file=.secrets/drift.env -n opentaco + +# 4. Deploy from OCI registry +helm install opentaco oci://ghcr.io/diggerhq/helm-charts/opentaco \ + -f values-test.yaml \ + -n opentaco ``` -### Installation Steps +## Architecture + +The umbrella chart deploys 4 services: -The installation is a two-step process: +- **digger-backend** (port 3000) - Terraform orchestration +- **drift** (port 3004) - Infrastructure drift detection +- **statesman** (port 8080) - IaC state management with Cloud SQL +- **ui** (port 3030) - Web frontend -1. **Initial deployment**: Install the helm chart with basic configuration -2. **GitHub App setup**: Navigate to `https://your-digger-hostname/github/setup` to create and configure the GitHub App -3. **Update configuration**: Add the GitHub App credentials to your values and upgrade the release +## Configuration Checklist -## Configuration +Before deploying, you need to configure placeholder values in your values file. -### Basic Configuration +### Required Placeholders in `values-test.yaml` or `values-production.yaml` -Create a `values.yaml` file with your configuration: +#### 1. **Cloud SQL Configuration** (if using Cloud SQL for statesman) ```yaml -digger: - # Docker image - image: - repository: registry.digger.dev/diggerhq/digger_backend - tag: "v0.6.106" # Should match appVersion in Chart.yaml - - # Service configuration - service: - type: ClusterIP - port: 3000 - - # Ingress configuration - ingress: - enabled: true - host: "digger.example.com" # Your domain - annotations: {} # Add your ingress controller annotations - tls: - secretName: "digger-tls" # If using TLS - - # Required secrets - secret: - httpBasicAuthUsername: "admin" - httpBasicAuthPassword: "" - bearerAuthToken: "" - hostname: "digger.example.com" - - # GitHub App credentials (filled after setup) - githubOrg: "" - githubAppID: "" # Note: uppercase ID - githubAppClientID: "" - githubAppClientSecret: "" - githubAppKeyFile: "" # base64 encoded private key - githubWebhookSecret: "" - - # PostgreSQL configuration - postgres: - user: "postgres" - database: "digger" - host: "your-postgres-host" - password: "" - - # Resource limits (optional) - resources: - requests: - cpu: 100m - memory: 128Mi - limits: - cpu: 500m - memory: 512Mi -``` - -### Database Options - -#### Option 1: External PostgreSQL (Recommended for production) +cloudSql: + enabled: true + instanceConnectionName: "YOUR-PROJECT-ID:YOUR-REGION:YOUR-INSTANCE" # ❌ CHANGE THIS + credentialsSecret: "cloudsql-credentials" + serviceAccount: "cloudsql-sa" +``` + +**What to do:** +- Replace `YOUR-PROJECT-ID` with your GCP project ID (e.g., `my-prod-project`) +- Replace `YOUR-REGION` with your Cloud SQL region (e.g., `us-central1`) +- Replace `YOUR-INSTANCE` with your Cloud SQL instance name (e.g., `opentaco-postgres`) + +Example: `my-prod-project:us-central1:opentaco-postgres` + +#### 2. **Image Registry** (optional - defaults to public GHCR) + ```yaml -digger: - postgres: - user: "digger" - database: "digger" - host: "postgresql.example.com" - password: "your-secure-password" - sslmode: "require" # or "disable" for non-SSL connections +global: + imageRegistry: ghcr.io/diggerhq/digger # ✅ Public registry (no auth needed) + # Or use your private registry: + # imageRegistry: us-central1-docker.pkg.dev/YOUR-PROJECT/YOUR-REPO ``` -#### Option 2: Built-in PostgreSQL (Testing only) +**What to do:** +- Keep default for public images (recommended) +- OR replace with your private registry path if using custom builds + +#### 3. **UI Ingress Configuration** (for production with custom domain) + ```yaml -postgres: - enabled: true - secret: - postgresPassword: "" +taco-ui: + ui: + ingress: + enabled: false # Set to true for production + hosts: + - host: app.opentaco.example.com # ❌ CHANGE THIS + paths: + - path: / + pathType: Prefix + tls: + - secretName: opentaco-ui-tls + hosts: + - app.opentaco.example.com # ❌ CHANGE THIS ``` -### Using Existing Secrets +**What to do:** +- Replace `app.opentaco.example.com` with your actual domain +- Set `enabled: true` when ready to expose publicly +- Ensure you have an Ingress Controller installed (see Ingress Setup below) -Instead of putting secrets in values.yaml, reference an existing Kubernetes secret: +#### 4. **Service Replica Counts** (optional - defaults to 1) ```yaml -digger: - secret: - useExistingSecret: true - existingSecretName: "digger-secrets" +digger-backend: + digger: + replicaCount: 1 # Increase for high availability + +taco-statesman: + taco: + replicaCount: 1 # Increase for high availability ``` -## Upgrade After GitHub App Setup +**What to do:** +- Keep `1` for test/dev environments +- Increase to `2+` for production high availability -After configuring the GitHub App at `/github/setup`, update your values with the app credentials and upgrade: +### Quick Validation + +Before deploying, check your values file for these patterns: + +```bash +# In your values-test.yaml or values-production.yaml +grep -E "YOUR-|example\.com|CHANGE THIS" opentaco/values-test.yaml +``` + +If this returns any results, you have placeholders that need to be filled in! + +## Secret Management + +### 1. Copy Example Files + +```bash +cp -r secrets-example/ .secrets/ +``` + +### 2. Fill In Values + +Edit each file in `.secrets/` with your actual credentials: ```bash -helm upgrade digger-backend oci://ghcr.io/diggerhq/helm-charts/digger-backend \ - --namespace digger \ - --values values.yaml +.secrets/ +├── digger-backend.env # GitHub App, database, Sentry, etc. +├── drift.env # GitHub App, database connection +├── statesman.env # Auth0, S3, PostgreSQL (Cloud SQL) +└── ui.env # WorkOS, backend service URLs ``` + +**Key values to configure:** +- GitHub App credentials (create at https://github.com/settings/apps) +- Database connection strings +- S3/storage credentials +- Authentication providers (WorkOS, Auth0) + +### 3. Create Kubernetes Secrets + +```bash +kubectl create secret generic ui-secrets \ + --from-env-file=.secrets/ui.env -n opentaco + +kubectl create secret generic backend-secrets \ + --from-env-file=.secrets/digger-backend.env -n opentaco + +kubectl create secret generic statesman-secrets \ + --from-env-file=.secrets/statesman.env -n opentaco + +kubectl create secret generic drift-secrets \ + --from-env-file=.secrets/drift.env -n opentaco +``` + +### 4. Update Secrets + +```bash +# Delete old secret +kubectl delete secret statesman-secrets -n opentaco + +# Recreate with new values +kubectl create secret generic statesman-secrets \ + --from-env-file=.secrets/statesman.env -n opentaco + +# Restart pods to pick up changes +kubectl delete pods -l app.kubernetes.io/name=statesman -n opentaco +``` + +## Cloud SQL Setup + +Statesman uses Google Cloud SQL for database. Backend and Drift can use external databases (Supabase, etc.) or Cloud SQL. + +### 1. Create Cloud SQL Instance + +```bash +gcloud sql instances create taco-postgres \ + --database-version=POSTGRES_15 \ + --tier=db-f1-micro \ + --region=us-central1 \ + --database-flags=max_connections=100 +``` + +### 2. Create Database + +```bash +gcloud sql databases create taco \ + --instance=taco-postgres +``` + +### 3. Create Service Account + +```bash +# Create service account for Cloud SQL proxy +gcloud iam service-accounts create cloudsql-sa \ + --display-name="Cloud SQL Proxy Service Account" + +# Grant Cloud SQL Client role +gcloud projects add-iam-policy-binding YOUR_PROJECT_ID \ + --member="serviceAccount:cloudsql-sa@YOUR_PROJECT_ID.iam.gserviceaccount.com" \ + --role="roles/cloudsql.client" + +# Create and download key +gcloud iam service-accounts keys create cloudsql-key.json \ + --iam-account=cloudsql-sa@YOUR_PROJECT_ID.iam.gserviceaccount.com +``` + +### 4. Create Kubernetes Secret for Cloud SQL + +```bash +kubectl create secret generic cloudsql-credentials \ + --from-file=credentials.json=cloudsql-key.json \ + -n opentaco +``` + +### 5. Configure in values-test.yaml + +```yaml +statesman: + enabled: true + taco: + cloudSql: + enabled: true + instanceConnectionName: "PROJECT_ID:REGION:INSTANCE_NAME" # e.g., "dev-XXXXXXX:us-west2:taco-postgres" + credentialsSecret: "cloudsql-credentials" +``` + +### 6. Set Database Connection in statesman.env + +```bash +# Cloud SQL uses localhost via proxy sidecar +OPENTACO_POSTGRES_HOST=localhost +OPENTACO_POSTGRES_PORT=5432 +OPENTACO_POSTGRES_USER=postgres +OPENTACO_POSTGRES_PASSWORD=YOUR_DB_PASSWORD +OPENTACO_POSTGRES_DBNAME=taco +OPENTACO_QUERY_BACKEND=postgres +``` + +The Cloud SQL proxy runs as a sidecar container, connecting to your Cloud SQL instance and exposing it on localhost:5432. + +## Deployment + +### Test Environment + +```bash +cd opentaco +helm install opentaco . -f values-test.yaml -n opentaco +``` + +### Production Environment + +```bash +# Review and customize production values +vim opentaco/values-production.yaml + +# Deploy +helm install opentaco . -f values-production.yaml -n opentaco +``` + +### Verify Deployment + +```bash +# Check pods +kubectl get pods -n opentaco + +# Check logs +kubectl logs -f deployment/opentaco-statesman -n opentaco -c statesman + +# Access UI locally +kubectl port-forward svc/opentaco-ui 3030:3030 -n opentaco +open http://localhost:3030 +``` + +## Service Communication + +Services communicate via Kubernetes DNS: + +```bash +# From within the cluster: +http://opentaco-digger-backend-web:3000 +http://opentaco-drift:3004 +http://opentaco-statesman:8080 +http://opentaco-ui:3030 +``` + +These URLs are configured in `ui.env`: +```bash +ORCHESTRATOR_BACKEND_URL="http://opentaco-digger-backend-web:3000" +DRIFT_REPORTING_BACKEND_URL="http://opentaco-drift:3004" +STATESMAN_BACKEND_URL="http://opentaco-statesman:8080" +``` + +## Upgrading + +```bash +# Update dependencies +cd opentaco +helm dependency update + +# Upgrade deployment +helm upgrade opentaco . -f values-test.yaml -n opentaco + +# Force pod recreation if needed +kubectl delete pods --all -n opentaco +``` + +## Troubleshooting + +### Pods not starting + +```bash +# Check pod status +kubectl get pods -n opentaco + +# Check events +kubectl describe pod POD_NAME -n opentaco + +# Check logs +kubectl logs POD_NAME -n opentaco +``` + +### Secret issues + +```bash +# List secrets +kubectl get secrets -n opentaco + +# Verify secret contents +kubectl get secret backend-secrets -n opentaco -o jsonpath='{.data}' | jq 'keys' +``` + +### Cloud SQL connection issues + +```bash +# Check Cloud SQL proxy sidecar logs +kubectl logs POD_NAME -n opentaco -c cloud-sql-proxy + +# Verify instance connection name +gcloud sql instances describe INSTANCE_NAME --format="value(connectionName)" +``` + +## Chart Structure + +``` +helm-charts/ +├── opentaco/ # Umbrella chart +│ ├── Chart.yaml +│ ├── values.yaml # Default values +│ ├── values-test.yaml # Test environment +│ └── values-production.yaml +├── digger-backend/ # Terraform orchestration +├── digger-drift/ # Drift detection +├── taco-statesman/ # State management +├── taco-ui/ # Web frontend +└── secrets-example/ # Example secret files +``` + +## Required External Services + +- **GitHub App** - Repository access and webhooks +- **WorkOS** - UI authentication (or configure alternative) +- **Auth0** - Statesman authentication (or configure alternative) +- **S3-compatible storage** - State and artifact storage +- **Cloud SQL or PostgreSQL** - Database + +## Configuration Files + +| File | Purpose | +|------|---------| +| `values.yaml` | Default configuration for all services | +| `values-test.yaml` | Minimal config for testing | +| `values-production.yaml` | Production-ready settings | +| `.secrets/*.env` | Environment-specific secrets (not committed) | + diff --git a/helm-charts/opentaco/.helmignore b/helm-charts/opentaco/.helmignore new file mode 100644 index 000000000..898df4886 --- /dev/null +++ b/helm-charts/opentaco/.helmignore @@ -0,0 +1,24 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ + diff --git a/helm-charts/opentaco/Chart.yaml b/helm-charts/opentaco/Chart.yaml new file mode 100644 index 000000000..c0d189884 --- /dev/null +++ b/helm-charts/opentaco/Chart.yaml @@ -0,0 +1,81 @@ +apiVersion: v2 +name: opentaco +description: OpenTaco - Complete Infrastructure-as-Code platform deployment +type: application +version: 0.1.0 +appVersion: "0.1.0" + +# Umbrella chart that deploys all OpenTaco components +# This chart orchestrates: +# - PostgreSQL database (optional - can use Cloud SQL instead) +# - Digger Managed (terraform orchestration backend) +# - Taco Statesman (IaC state management) +# - Drift Detection service +# - Taco UI (React frontend) + +dependencies: + # Optional PostgreSQL - disable if using Cloud SQL + - name: cloudnative-pg + version: "~0.22.0" + repository: https://cloudnative-pg.github.io/charts + condition: postgresql.enabled + tags: + - database + + # Digger Managed - terraform orchestration backend + - name: taco-orchestrator + version: "0.1.0" + # For production: use OCI registry + repository: "oci://ghcr.io/diggerhq/helm-charts" + # For local testing: use file reference + #repository: "file://../taco-orchestrator" + condition: taco-orchestrator.enabled + tags: + - backend + + # Taco Statesman - IaC state management + - name: statesman + alias: taco-statesman + version: "0.1.0" + # For production: use OCI registry + repository: "oci://ghcr.io/diggerhq/helm-charts" + # For local testing: use file reference + #repository: "file://../taco-statesman" + condition: taco-statesman.enabled + tags: + - backend + + # Drift Detection + - name: drift + version: "0.1.0" + # For production: use OCI registry + repository: "oci://ghcr.io/diggerhq/helm-charts" + # For local testing: use file reference + #repository: "file://../taco-drift" + condition: drift.enabled + tags: + - backend + + # Taco UI - React frontend + - name: ui + alias: taco-ui + version: "0.1.0" + # For production: use OCI registry + repository: "oci://ghcr.io/diggerhq/helm-charts" + # For local testing: use file reference + #repository: "file://../taco-ui" + condition: taco-ui.enabled + tags: + - frontend + +maintainers: + - name: OpenTaco Team + email: info@digger.dev + +keywords: + - infrastructure + - terraform + - iac + - opentaco + - digger + diff --git a/helm-charts/opentaco/templates/NOTES.txt b/helm-charts/opentaco/templates/NOTES.txt new file mode 100644 index 000000000..aceb38db8 --- /dev/null +++ b/helm-charts/opentaco/templates/NOTES.txt @@ -0,0 +1,80 @@ +╔════════════════════════════════════════════════════════════════╗ +║ OpenTaco Deployment ║ +╚════════════════════════════════════════════════════════════════╝ + +Thank you for installing {{ .Chart.Name }}. + +Your release is named {{ .Release.Name }}. + +DEPLOYMENT STATUS: +================== + +{{- if .Values.postgresql.enabled }} +✓ PostgreSQL Database: Enabled (in-cluster) + Connection: {{ .Release.Name }}-postgresql:5432 +{{- else if .Values.cloudSql.enabled }} +✓ Cloud SQL: Enabled + Instance: {{ .Values.cloudSql.instanceConnectionName }} +{{- else }} +⚠ No database configured! + Please enable postgresql or cloudSql in values.yaml +{{- end }} + +{{- if index .Values "taco-orchestrator" "enabled" }} +✓ Digger Managed: Enabled + Service: digger-managed:3000 +{{- end }} + +{{- if index .Values "taco-statesman" "enabled" }} +✓ Taco Statesman: Enabled + Service: taco-statesman:8080 +{{- end }} + +{{- if .Values.drift.enabled }} +✓ Drift Detection: Enabled + Service: drift:3004 +{{- end }} + +{{- if index .Values "taco-ui" "enabled" }} +✓ Taco UI: Enabled + Service: taco-ui:3030 +{{- if index .Values "taco-ui" "ui" "ingress" "enabled" }} + URL: https://{{ (index (index .Values "taco-ui" "ui" "ingress" "hosts") 0).host }} +{{- else }} + + To access the UI, run: + kubectl port-forward svc/taco-ui 3030:3030 + Then visit: http://localhost:3030 +{{- end }} +{{- end }} + +NEXT STEPS: +=========== + +1. Check deployment status: + kubectl get pods -n {{ .Release.Namespace }} + +2. View logs: + kubectl logs -f deployment/taco-ui -n {{ .Release.Namespace }} + +3. Configure ingress (if not done): + helm upgrade {{ .Release.Name }} ./helm-charts/opentaco \ + --set taco-ui.ui.ingress.enabled=true \ + --set taco-ui.ui.ingress.hosts[0].host=app.yourdomain.com + +4. Create required secrets if not exists: +{{- if not .Values.postgresql.enabled }} + kubectl create secret generic digger-db-secret --from-literal=password=YOUR_PASSWORD + kubectl create secret generic taco-db-secret --from-literal=password=YOUR_PASSWORD + kubectl create secret generic drift-db-secret --from-literal=password=YOUR_PASSWORD +{{- end }} + kubectl create secret generic workos-secrets \ + --from-literal=api-key=YOUR_API_KEY \ + --from-literal=cookie-password=YOUR_COOKIE_PASSWORD + +For more information, see: + docs: https://docs.opentaco.dev + repo: https://github.com/diggerhq/digger + +Happy Infrastructure-as-Coding! 🚀 + diff --git a/helm-charts/opentaco/templates/_helpers.tpl b/helm-charts/opentaco/templates/_helpers.tpl new file mode 100644 index 000000000..11eb59771 --- /dev/null +++ b/helm-charts/opentaco/templates/_helpers.tpl @@ -0,0 +1,76 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "opentaco.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +*/}} +{{- define "opentaco.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "opentaco.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "opentaco.labels" -}} +helm.sh/chart: {{ include "opentaco.chart" . }} +{{ include "opentaco.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "opentaco.selectorLabels" -}} +app.kubernetes.io/name: {{ include "opentaco.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Database host - returns the appropriate database host based on configuration +*/}} +{{- define "opentaco.database.host" -}} +{{- if .Values.cloudSql.enabled -}} +127.0.0.1 +{{- else if .Values.postgresql.enabled -}} +{{ .Release.Name }}-postgresql +{{- else -}} +{{- required "Either postgresql.enabled or cloudSql.enabled must be true, or provide external database host" .Values.externalDatabase.host -}} +{{- end -}} +{{- end }} + +{{/* +Database port +*/}} +{{- define "opentaco.database.port" -}} +{{- if .Values.cloudSql.enabled -}} +5432 +{{- else if .Values.postgresql.enabled -}} +5432 +{{- else -}} +{{ .Values.externalDatabase.port | default 5432 }} +{{- end -}} +{{- end }} + diff --git a/helm-charts/opentaco/values-production.yaml.example b/helm-charts/opentaco/values-production.yaml.example new file mode 100644 index 000000000..43fc20510 --- /dev/null +++ b/helm-charts/opentaco/values-production.yaml.example @@ -0,0 +1,125 @@ +# Production values for OpenTaco +# Copy this file and customize for your environment + +global: + imageRegistry: ghcr.io/diggerhq/digger + imagePullPolicy: IfNotPresent + +# ============================================================================ +# Database - Use Cloud SQL for production +# ============================================================================ +postgresql: + enabled: false + +cloudSql: + enabled: true + instanceConnectionName: "YOUR-PROJECT-ID:YOUR-REGION:YOUR-INSTANCE" + credentialsSecret: "cloudsql-credentials" + serviceAccount: "cloudsql-sa" + +# ============================================================================ +# Digger Managed +# ============================================================================ +digger-managed: + enabled: true + digger: + replicaCount: 2 + resources: + requests: + memory: 512Mi + cpu: 500m + limits: + memory: 2Gi + cpu: 2000m + ingress: + enabled: true + className: "nginx" + annotations: + cert-manager.io/cluster-issuer: "letsencrypt-prod" + host: "api.opentaco.example.com" # CHANGE THIS + path: / + tls: + secretName: digger-managed-tls + +# ============================================================================ +# Taco Statesman +# ============================================================================ +taco-statesman: + enabled: true + taco: + replicaCount: 2 + resources: + requests: + memory: 512Mi + cpu: 500m + limits: + memory: 2Gi + cpu: 2000m + storage: + type: "s3" # Use S3/GCS for production + s3: + bucket: "opentaco-state-bucket" # CHANGE THIS + region: "us-central1" + +# ============================================================================ +# Drift Detection +# ============================================================================ +drift: + enabled: true + drift: + replicaCount: 2 + resources: + requests: + memory: 512Mi + cpu: 500m + limits: + memory: 2Gi + cpu: 2000m + +# ============================================================================ +# Taco UI +# ============================================================================ +taco-ui: + enabled: true + ui: + replicaCount: 3 + resources: + requests: + memory: 256Mi + cpu: 250m + limits: + memory: 1Gi + cpu: 1000m + ingress: + enabled: true + className: "nginx" + annotations: + cert-manager.io/cluster-issuer: "letsencrypt-prod" + nginx.ingress.kubernetes.io/ssl-redirect: "true" + hosts: + - host: app.opentaco.example.com # CHANGE THIS + paths: + - path: / + pathType: Prefix + tls: + - secretName: opentaco-ui-tls + hosts: + - app.opentaco.example.com # CHANGE THIS + env: + allowedHosts: "app.opentaco.example.com" # CHANGE THIS + workos: + clientId: "client_XXXXX" # CHANGE THIS - your WorkOS client ID + secretName: "workos-secrets" + posthog: + key: "" # Optional: Add PostHog key + host: "https://app.posthog.com" + +# ============================================================================ +# Optional Features +# ============================================================================ +networkPolicies: + enabled: true + +serviceMonitor: + enabled: false # Set to true if using Prometheus + diff --git a/helm-charts/opentaco/values-test.yaml.example b/helm-charts/opentaco/values-test.yaml.example new file mode 100644 index 000000000..6ddbb1cbe --- /dev/null +++ b/helm-charts/opentaco/values-test.yaml.example @@ -0,0 +1,83 @@ +# Minimal Test Configuration for OpenTaco +# This file has the minimum changes needed for a working test deployment + +# ============================================================================ +# Global Configuration +# ============================================================================ +global: + imageRegistry: ghcr.io/diggerhq/digger + imagePullPolicy: IfNotPresent + # Note: imagePullSecrets not needed for public GHCR images + # imagePullSecrets: + # - name: gcr-json-key + +# ============================================================================ +# Database - Use Cloud SQL +# ============================================================================ +postgresql: + enabled: false # Not using in-cluster PostgreSQL + +cloudSql: + enabled: false # Only statesman uses Cloud SQL, configured per-service below + instanceConnectionName: "YOUR-PROJECT-ID:YOUR-REGION:YOUR-INSTANCE" + credentialsSecret: "cloudsql-credentials" + serviceAccount: "cloudsql-sa" + +# ============================================================================ +# Components - All enabled with defaults +# ============================================================================ +digger-managed: + enabled: true + # Note: imagePullSecrets not needed for public images + # global: + # imagePullSecrets: + # - name: gcr-json-key + digger: + replicaCount: 1 + secret: + useExistingSecret: true + existingSecretName: "backend-secrets" + +statesman: + enabled: true + # Note: imagePullSecrets not needed for public images + # global: + # imagePullSecrets: + # - name: gcr-json-key + taco: + replicaCount: 1 + existingSecretName: "statesman-secrets" + cloudSql: + enabled: true # Only statesman uses Cloud SQL + instanceConnectionName: "YOUR-PROJECT-ID:YOUR-REGION:YOUR-INSTANCE" + credentialsSecret: "cloudsql-credentials" + storage: + type: "s3" + +drift: + enabled: true + # Note: imagePullSecrets not needed for public images + # global: + # imagePullSecrets: + # - name: gcr-json-key + drift: + replicaCount: 1 + existingSecretName: "drift-secrets" + +ui: + enabled: true + # Note: imagePullSecrets not needed for public images + # imagePullSecrets: + # - name: gcr-json-key + # global: + # imagePullSecrets: + # - name: gcr-json-key + ui: + replicaCount: 1 + existingSecretName: "ui-secrets" + env: + # Backend URLs point to in-cluster services (default is good) + allowedHosts: "localhost" + ingress: + enabled: false # Using port-forward for testing + diff --git a/helm-charts/opentaco/values.yaml b/helm-charts/opentaco/values.yaml new file mode 100644 index 000000000..cd06566c2 --- /dev/null +++ b/helm-charts/opentaco/values.yaml @@ -0,0 +1,246 @@ +# OpenTaco Umbrella Chart - Default Values +# +# This chart deploys the complete OpenTaco platform: +# - Database (PostgreSQL or Cloud SQL) +# - Digger Managed +# - Taco Statesman +# - Drift Detection +# - Taco UI + +# ============================================================================ +# Global Configuration +# ============================================================================ +global: + # Image registry for all custom images + imageRegistry: ghcr.io/diggerhq/digger + + # Image pull policy + imagePullPolicy: IfNotPresent + + # Shared secrets (create these before deploying) + secrets: + # WorkOS authentication (required for UI) + workosSecretName: "workos-secrets" + +# ============================================================================ +# Database Configuration +# ============================================================================ + +# Option 1: Use in-cluster PostgreSQL (bitnami chart) +# Set postgresql.enabled=true for development/testing +postgresql: + enabled: false # Set to true to deploy PostgreSQL in cluster + auth: + username: opentaco + password: changeme # CHANGE THIS or use existingSecret + database: opentaco + existingSecret: "" # Reference existing secret for password + primary: + persistence: + enabled: true + size: 20Gi + resources: + requests: + memory: 256Mi + cpu: 250m + limits: + memory: 1Gi + cpu: 1000m + +# Option 2: Use Cloud SQL (recommended for production) +# See helm-charts/scripts/setup-cloud-sql.sh to create Cloud SQL instance +cloudSql: + enabled: true # Set to true to use Cloud SQL + # Connection details + instanceConnectionName: "YOUR-PROJECT-ID:YOUR-REGION:YOUR-INSTANCE" + # Database credentials (create secret first) + credentialsSecret: "cloudsql-credentials" + # Service account for Workload Identity + serviceAccount: "cloudsql-sa" + +# ============================================================================ +# Digger Managed Configuration +# ============================================================================ +digger-managed: + enabled: true + + digger: + image: + repository: digger-backend-ee + tag: "latest" + + replicaCount: 1 + + # Secret management + secret: + useExistingSecret: true + existingSecretName: "taco-orchestrator-secrets" + + # Resource limits (IMPORTANT: set for production) + resources: {} + # requests: + # memory: 512Mi + # cpu: 500m + # limits: + # memory: 2Gi + # cpu: 2000m + + # Ingress configuration + ingress: + enabled: false + className: "nginx" + host: "api.opentaco.example.com" + path: / + + # For detailed configuration (database, GitHub App, secrets, etc.), + # see helm-charts/taco-orchestrator/values.yaml or use existingSecret above + +# ============================================================================ +# Taco Statesman Configuration +# ============================================================================ +taco-statesman: + enabled: true + + taco: + image: + repository: taco-statesman + tag: "latest" + + replicaCount: 1 + + # Secret management + secret: + useExistingSecret: true + existingSecretName: "statesman-secrets" + + # Storage configuration (critical: choose memory or s3) + storage: + type: "memory" # "memory" for dev, "s3" for production + s3: + bucket: "" # S3 bucket name (required if type=s3) + region: "us-east-1" + + # Cloud SQL configuration (for GCP) + cloudSql: + enabled: false + instanceConnectionName: "" + serviceAccount: "default" + credentialsSecret: "" + + # Resource limits (IMPORTANT: set for production) + resources: {} + # requests: + # memory: 256Mi + # cpu: 250m + # limits: + # memory: 1Gi + # cpu: 1000m + + # For detailed configuration (auth, JWT, postgres, OAuth, etc.), + # see helm-charts/taco-statesman/values.yaml or use existingSecret above + +# ============================================================================ +# Drift Detection Configuration +# ============================================================================ +drift: + enabled: true + + drift: + image: + repository: drift + tag: "latest" + + replicaCount: 1 + + # Secret management + secret: + useExistingSecret: true + existingSecretName: "drift-secrets" + + # Resource limits (IMPORTANT: set for production) + resources: {} + # requests: + # memory: 256Mi + # cpu: 250m + # limits: + # memory: 1Gi + # cpu: 1000m + + # Ingress configuration + ingress: + enabled: false + className: "nginx" + host: "drift.opentaco.example.com" + path: / + + # For detailed configuration (GitHub App, database, webhook secrets, etc.), + # see helm-charts/taco-drift/values.yaml or use existingSecret above + +# ============================================================================ +# Taco UI Configuration +# ============================================================================ +taco-ui: + enabled: true + + ui: + image: + repository: taco-ui + tag: "latest" + + replicaCount: 1 + + # Secret management + secret: + useExistingSecret: true + existingSecretName: "ui-secrets" + + # Environment configuration (cross-service URLs) + env: + # Allowed hosts (customize for your domain) + allowedHosts: "" + + # Backend service URLs (for server-side API calls) + backends: + orchestratorUrl: "http://digger-managed:3000" + driftReportingUrl: "http://drift:3004" + statesmanUrl: "http://taco-statesman:8080" + + ingress: + enabled: false + className: "nginx" + annotations: + cert-manager.io/cluster-issuer: "letsencrypt-prod" + hosts: + - host: app.opentaco.example.com + paths: + - path: / + pathType: Prefix + tls: + - secretName: opentaco-ui-tls + hosts: + - app.opentaco.example.com + + # Resource limits (IMPORTANT: set for production) + resources: {} + # requests: + # memory: 256Mi + # cpu: 250m + # limits: + # memory: 1Gi + # cpu: 1000m + + # For detailed configuration (WorkOS, PostHog, webhook secrets, etc.), + # see helm-charts/taco-ui/values.yaml or use existingSecret above + +# ============================================================================ +# Additional Configuration +# ============================================================================ + +# Create NetworkPolicies (optional) +networkPolicies: + enabled: false + +# Service Monitor for Prometheus (optional) +serviceMonitor: + enabled: false + diff --git a/helm-charts/secrets-example/digger-backend.env b/helm-charts/secrets-example/digger-backend.env new file mode 100644 index 000000000..7d0781679 --- /dev/null +++ b/helm-charts/secrets-example/digger-backend.env @@ -0,0 +1,40 @@ +# Digger Backend Environment Variables +# Copy this file to ../.secrets/digger-backend.env and fill in your actual values + +# Database Configuration +DATABASE_URL=your_connection_string_here + +# Digger Configuration +DIGGER_ENABLE_API_ENDPOINTS=true +DIGGER_ENABLE_INTERNAL_ENDPOINTS=true +DIGGER_ENCRYPTION_SECRET=YOUR_ENCRYPTION_SECRET_32_CHARS +DIGGER_GENERATION_API_TOKEN=YOUR_GENERATION_API_TOKEN +DIGGER_GENERATION_ENDPOINT=https://some.endpoint.biz + +DIGGER_INTERNAL_SECRET=YOUR_INTERNAL_SECRET_50_CHARS +DIGGER_LICENSE_KEY=YOUR_LICENSE_KEY + +DIGGER_LOAD_PROJECTS_ON_PUSH=false +DIGGER_LOG_LEVEL=DEBUG +DIGGER_MAX_PROJECTS_PER_CHANGE=100 + +# GitHub App Configuration +# Get these from https://github.com/settings/apps/YOUR_APP +GITHUB_APP_CLIENT_ID=YOUR_GITHUB_APP_CLIENT_ID +GITHUB_APP_CLIENT_SECRET=YOUR_GITHUB_APP_CLIENT_SECRET +GITHUB_APP_ID=YOUR_GITHUB_APP_ID +GITHUB_APP_PRIVATE_KEY_BASE64=YOUR_BASE64_ENCODED_PRIVATE_KEY +GITHUB_WEBHOOK_SECRET=YOUR_GITHUB_WEBHOOK_SECRET + +# Go Configuration +GODEBUG=off +GOFIPS140=off + +# Application URLs +HOSTNAME=https://app.yourdomain.com +JWT_AUTH=true + +# Optional: Analytics & Monitoring +SEGMENT_API_KEY=YOUR_SEGMENT_API_KEY +SENTRY_DSN=YOUR_SENTRY_DSN + diff --git a/helm-charts/secrets-example/drift.env b/helm-charts/secrets-example/drift.env new file mode 100644 index 000000000..df0d3cfac --- /dev/null +++ b/helm-charts/secrets-example/drift.env @@ -0,0 +1,20 @@ +# Digger Drift Detection Service Environment Variables +# Copy this file to ../.secrets/drift.env and fill in your actual values + +# Database Configuration +DATABASE_URL=your_connection_string_here + + +# Application URLs +DIGGER_APP_URL=https://app.yourdomain.com +DIGGER_DRIFT_REPORTER_HOSTNAME=https://app.yourdomain.com/drift-reporting +DIGGER_HOSTNAME=https://app.yourdomain.com + +# Webhook Secret +DIGGER_WEBHOOK_SECRET=YOUR_WEBHOOK_SECRET_32_CHARS + +GITHUB_APP_PRIVATE_KEY_BASE64="YOUR_BASE64_ENCODED_PRIVATE_KEY" + +# Optional: Sentry Monitoring +SENTRY_DSN=YOUR_SENTRY_DSN + diff --git a/helm-charts/secrets-example/statesman.env b/helm-charts/secrets-example/statesman.env new file mode 100644 index 000000000..ef56a1e96 --- /dev/null +++ b/helm-charts/secrets-example/statesman.env @@ -0,0 +1,53 @@ +# Taco Statesman Environment Variables +# Copy this file to ../.secrets/statesman.env and fill in your actual values + +# S3/Object Storage Configuration +OPENTACO_S3_BUCKET=your-bucket-name +OPENTACO_S3_REGION=us-east-2 +OPENTACO_S3_PREFIX=your-prefix + +# Auth0 Configuration (for authentication) +# Get these from https://manage.auth0.com/ +OPENTACO_AUTH_ISSUER=https://your-tenant.us.auth0.com/ +OPENTACO_AUTH_CLIENT_ID=YOUR_AUTH0_CLIENT_ID +OPENTACO_AUTH_CLIENT_SECRET=YOUR_AUTH0_CLIENT_SECRET +OPENTACO_AUTH_AUTH_URL=https://your-tenant.us.auth0.com/authorize +OPENTACO_AUTH_TOKEN_URL=https://your-tenant.us.auth0.com/oauth/token + +# AWS/S3 Credentials (or compatible storage like Tigris) +AWS_ACCESS_KEY_ID=YOUR_AWS_ACCESS_KEY_ID +AWS_SECRET_ACCESS_KEY=YOUR_AWS_SECRET_ACCESS_KEY +AWS_REGION=auto +AWS_ENDPOINT=https://your-s3-endpoint.com + +# Internal API Authentication +OPENTACO_ENABLE_INTERNAL_ENDPOINTS=YOUR_INTERNAL_SECRET_64_CHARS + +# JWT Token Configuration (Optional - uses secure defaults if not set) +# Key ID for JWT signing +OPENTACO_TOKENS_KID=k1 +# Token lifetimes +OPENTACO_TOKENS_ACCESS_TTL=1h +OPENTACO_TOKENS_REFRESH_TTL=720h +# Optional: Path to Ed25519 private key PEM file (generates ephemeral key if not set) +# OPENTACO_TOKENS_PRIVATE_KEY_PEM_PATH=/secrets/jwt-private-key.pem + +# Public Base URL (used as JWT issuer and for OAuth redirects) +# IMPORTANT: Set to your actual public URL in production +OPENTACO_PUBLIC_BASE_URL=https://your-domain.com + +# OAuth State Encryption Key +# CRITICAL: Set to a random 32+ character string in production! +# Used to encrypt OAuth state parameters during PKCE authentication flows +OPENTACO_OAUTH_STATE_KEY=CHANGE_THIS_TO_RANDOM_32_CHAR_STRING_IN_PRODUCTION + +# Example CloudSQL + PostgreSQL Configuration +# Note: For Cloud SQL, use localhost:5432 (Cloud SQL proxy handles connection) +OPENTACO_POSTGRES_HOST=localhost +OPENTACO_POSTGRES_PORT=5432 +OPENTACO_POSTGRES_USER=taco +OPENTACO_POSTGRES_PASSWORD=YOUR_DB_PASSWORD +OPENTACO_POSTGRES_DBNAME=taco +OPENTACO_POSTGRES_SSLMODE=disable +OPENTACO_QUERY_BACKEND=postgres + diff --git a/helm-charts/secrets-example/ui.env b/helm-charts/secrets-example/ui.env new file mode 100644 index 000000000..761077b25 --- /dev/null +++ b/helm-charts/secrets-example/ui.env @@ -0,0 +1,26 @@ +# Taco UI Environment Variables +# Copy this file to ../.secrets/ui.env and fill in your actual values + +# WorkOS Configuration (for authentication) +# Get these from https://dashboard.workos.com/ +WORKOS_REDIRECT_URI=https://your-domain.com/api/auth/callback +WORKOS_API_KEY='YOUR_WORKOS_API_KEY' +WORKOS_CLIENT_ID='YOUR_WORKOS_CLIENT_ID' +WORKOS_COOKIE_PASSWORD='YOUR_32_CHAR_RANDOM_STRING' +WORKOS_WEBHOOK_SECRET=YOUR_WORKOS_WEBHOOK_SECRET + +# Allowed Hosts (comma-separated list) +ALLOWED_HOSTS="your-domain.com,localhost" + +# Backend Service URLs +# Use Kubernetes service names for in-cluster communication +# Format: http://-: +ORCHESTRATOR_BACKEND_URL="http://opentaco-digger-backend-web:3000" +ORCHESTRATOR_BACKEND_SECRET=YOUR_ORCHESTRATOR_SECRET + +DRIFT_REPORTING_BACKEND_URL=http://opentaco-drift:3004 +DRIFT_REPORTING_BACKEND_WEBHOOK_SECRET=YOUR_DRIFT_WEBHOOK_SECRET + +STATESMAN_BACKEND_URL=http://opentaco-statesman:8080 +STATESMAN_BACKEND_WEBHOOK_SECRET=YOUR_STATESMAN_WEBHOOK_SECRET + diff --git a/helm-charts/taco-drift/.helmignore b/helm-charts/taco-drift/.helmignore new file mode 100644 index 000000000..c479619bb --- /dev/null +++ b/helm-charts/taco-drift/.helmignore @@ -0,0 +1,27 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ +# Test files +tests/ +*.test.yaml + diff --git a/helm-charts/taco-drift/Chart.yaml b/helm-charts/taco-drift/Chart.yaml new file mode 100644 index 000000000..c25a33dd3 --- /dev/null +++ b/helm-charts/taco-drift/Chart.yaml @@ -0,0 +1,8 @@ +apiVersion: v2 +name: drift +description: Digger Drift - Automated infrastructure drift detection and reporting service +type: application +version: 0.1.0 +appVersion: "v0.1.0" +icon: https://raw.githubusercontent.com/diggerhq/digger/main/docs/logo/digger-logo.png + diff --git a/helm-charts/taco-drift/templates/_helpers.tpl b/helm-charts/taco-drift/templates/_helpers.tpl new file mode 100644 index 000000000..de3d6e83a --- /dev/null +++ b/helm-charts/taco-drift/templates/_helpers.tpl @@ -0,0 +1,59 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "digger-drift.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "digger-drift.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "digger-drift.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "digger-drift.labels" -}} +helm.sh/chart: {{ include "digger-drift.chart" . }} +{{ include "digger-drift.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "digger-drift.selectorLabels" -}} +app.kubernetes.io/name: {{ include "digger-drift.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "digger-drift.serviceAccountName" -}} +{{- default "default" .Values.drift.serviceAccount.name }} +{{- end }} + diff --git a/helm-charts/taco-drift/templates/deployment.yaml b/helm-charts/taco-drift/templates/deployment.yaml new file mode 100644 index 000000000..4a53587d9 --- /dev/null +++ b/helm-charts/taco-drift/templates/deployment.yaml @@ -0,0 +1,75 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "digger-drift.fullname" . }} + labels: + {{- include "digger-drift.labels" . | nindent 4 }} +spec: + replicas: {{ .Values.drift.replicaCount }} + selector: + matchLabels: + {{- include "digger-drift.selectorLabels" . | nindent 6 }} + template: + metadata: + labels: + {{- include "digger-drift.selectorLabels" . | nindent 8 }} + spec: + {{- if .Values.global }} + {{- with .Values.global.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- end }} + containers: + - name: {{ .Chart.Name }} + image: "{{ .Values.global.imageRegistry | default "ghcr.io/diggerhq/digger" }}/{{ .Values.drift.image.repository }}:{{ .Values.drift.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.drift.image.pullPolicy | default "IfNotPresent" }} + ports: + - name: http + containerPort: {{ .Values.drift.service.port }} + protocol: TCP + envFrom: + - secretRef: + {{- if .Values.drift.secret.useExistingSecret }} + name: {{ .Values.drift.secret.existingSecretName }} + {{- else }} + name: {{ include "digger-drift.fullname" . }}-secret + {{- end }} + env: + - name: DIGGER_PORT + value: "{{ .Values.drift.service.port }}" + - name: DIGGER_LOG_LEVEL + value: "{{ .Values.drift.logLevel }}" + # Custom environment variables + {{- range .Values.drift.customEnv }} + - name: {{ .name }} + value: {{ .value | quote }} + {{- end }} + {{- with .Values.drift.livenessProbe }} + livenessProbe: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.drift.startupProbe }} + startupProbe: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.drift.readinessProbe }} + readinessProbe: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.drift.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.drift.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.drift.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.drift.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/helm-charts/taco-drift/templates/ingress.yaml b/helm-charts/taco-drift/templates/ingress.yaml new file mode 100644 index 000000000..3075ab15a --- /dev/null +++ b/helm-charts/taco-drift/templates/ingress.yaml @@ -0,0 +1,34 @@ +{{- if .Values.drift.ingress.enabled }} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ include "digger-drift.fullname" . }} + labels: + {{- include "digger-drift.labels" . | nindent 4 }} + {{- with .Values.drift.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if .Values.drift.ingress.className }} + ingressClassName: {{ .Values.drift.ingress.className }} + {{- end }} + {{- if .Values.drift.ingress.tls.secretName }} + tls: + - hosts: + - {{ .Values.drift.ingress.host }} + secretName: {{ .Values.drift.ingress.tls.secretName }} + {{- end }} + rules: + - host: {{ .Values.drift.ingress.host }} + http: + paths: + - path: {{ .Values.drift.ingress.path }} + pathType: Prefix + backend: + service: + name: {{ include "digger-drift.fullname" . }} + port: + name: http +{{- end }} + diff --git a/helm-charts/taco-drift/templates/secret.yaml b/helm-charts/taco-drift/templates/secret.yaml new file mode 100644 index 000000000..f87049cb2 --- /dev/null +++ b/helm-charts/taco-drift/templates/secret.yaml @@ -0,0 +1,39 @@ +{{- if not .Values.drift.secret.useExistingSecret }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "digger-drift.fullname" . }}-secret + labels: + {{- include "digger-drift.labels" . | nindent 4 }} +type: Opaque +stringData: + # Database configuration + {{- if .Values.drift.secret.databaseUrl }} + DATABASE_URL: {{ .Values.drift.secret.databaseUrl | quote }} + {{- end }} + + # Drift service configuration + {{- if .Values.drift.secret.diggerAppUrl }} + DIGGER_APP_URL: {{ .Values.drift.secret.diggerAppUrl | quote }} + {{- end }} + {{- if .Values.drift.secret.diggerDriftReporterHostname }} + DIGGER_DRIFT_REPORTER_HOSTNAME: {{ .Values.drift.secret.diggerDriftReporterHostname | quote }} + {{- end }} + {{- if .Values.drift.secret.diggerHostname }} + DIGGER_HOSTNAME: {{ .Values.drift.secret.diggerHostname | quote }} + {{- end }} + {{- if .Values.drift.secret.diggerWebhookSecret }} + DIGGER_WEBHOOK_SECRET: {{ .Values.drift.secret.diggerWebhookSecret | quote }} + {{- end }} + + # GitHub configuration + {{- if .Values.drift.secret.githubAppPrivateKeyBase64 }} + GITHUB_APP_PRIVATE_KEY_BASE64: {{ .Values.drift.secret.githubAppPrivateKeyBase64 | quote }} + {{- end }} + + # Optional: Sentry monitoring + {{- if .Values.drift.secret.sentryDsn }} + SENTRY_DSN: {{ .Values.drift.secret.sentryDsn | quote }} + {{- end }} +{{- end }} + diff --git a/helm-charts/taco-drift/templates/service.yaml b/helm-charts/taco-drift/templates/service.yaml new file mode 100644 index 000000000..e1b5ad508 --- /dev/null +++ b/helm-charts/taco-drift/templates/service.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "digger-drift.fullname" . }} + labels: + {{- include "digger-drift.labels" . | nindent 4 }} +spec: + type: {{ .Values.drift.service.type }} + ports: + - port: {{ .Values.drift.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "digger-drift.selectorLabels" . | nindent 4 }} + diff --git a/helm-charts/taco-drift/values.yaml b/helm-charts/taco-drift/values.yaml new file mode 100644 index 000000000..cbeebb717 --- /dev/null +++ b/helm-charts/taco-drift/values.yaml @@ -0,0 +1,104 @@ +# values.yaml +# +# This chart creates environment variables from these values. +# You can either: +# 1. Set values here (chart creates secrets automatically) +# 2. Use secret.useExistingSecret=true and create your own secret +# +# See secrets-example/drift.env for a complete example + +drift: + # Image configuration + # Note: Full registry path comes from global.imageRegistry + # Public image: ghcr.io/diggerhq/digger/drift + image: + repository: drift + tag: "latest" + pullPolicy: "IfNotPresent" + + # Number of replicas + replicaCount: 1 + + # Custom environment variables + customEnv: [] + # - name: MY_CUSTOM_ENV + # value: "my-value" + + # Set the log level for the drift service + # Creates: DIGGER_LOG_LEVEL + # DEBUG will enable the debug logs, any other value will set it to INFO + logLevel: "INFO" # DIGGER_LOG_LEVEL + + # Service configuration + # Creates: DIGGER_PORT (set automatically from port) + service: + type: ClusterIP + port: 3004 # DIGGER_PORT + + # Ingress configuration + ingress: + enabled: false + className: "nginx" + annotations: {} + # cert-manager.io/cluster-issuer: letsencrypt-prod + host: "drift.example.com" + path: / + tls: + secretName: "taco-drift-tls" + + # Liveness and startup probe settings + livenessProbe: + httpGet: + path: /health + port: 3004 + periodSeconds: 20 + + startupProbe: + httpGet: + path: /health + port: 3004 + failureThreshold: 30 + periodSeconds: 10 + + readinessProbe: + httpGet: + path: /health + port: 3004 + periodSeconds: 10 + + # Resource limits + resources: {} + # requests: + # cpu: 100m + # memory: 256Mi + # limits: + # cpu: 500m + # memory: 512Mi + + # Node selector + nodeSelector: {} + + # Tolerations + tolerations: [] + + # Affinity + affinity: {} + + # Secret configuration + # For production, create secret externally: kubectl create secret generic drift-secrets --from-env-file=.secrets/drift.env + # For development, set useExistingSecret=false and fill secret fields below + secret: + useExistingSecret: true + existingSecretName: "drift-secrets" + # Required secret fields (only used if useExistingSecret=false): + databaseUrl: "" # DATABASE_URL - PostgreSQL connection string + diggerAppUrl: "" # DIGGER_APP_URL + diggerDriftReporterHostname: "" # DIGGER_DRIFT_REPORTER_HOSTNAME + diggerHostname: "" # DIGGER_HOSTNAME + diggerWebhookSecret: "" # DIGGER_WEBHOOK_SECRET + githubAppPrivateKeyBase64: "" # GITHUB_APP_PRIVATE_KEY_BASE64 + sentryDsn: "" # SENTRY_DSN (optional) + +# Global configuration (optional) +global: + imagePullSecrets: [] diff --git a/helm-charts/taco-orchestrator/.helmignore b/helm-charts/taco-orchestrator/.helmignore new file mode 100644 index 000000000..5b6e763e5 --- /dev/null +++ b/helm-charts/taco-orchestrator/.helmignore @@ -0,0 +1,24 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ +tests/ \ No newline at end of file diff --git a/helm-charts/taco-orchestrator/Chart.yaml b/helm-charts/taco-orchestrator/Chart.yaml new file mode 100644 index 000000000..7079289a4 --- /dev/null +++ b/helm-charts/taco-orchestrator/Chart.yaml @@ -0,0 +1,24 @@ +apiVersion: v2 +name: digger-managed +description: A Helm chart for Digger Backend (Managed Version) - requires external secrets + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 0.1.0 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: "latest" diff --git a/helm-charts/taco-orchestrator/templates/_helpers.tpl b/helm-charts/taco-orchestrator/templates/_helpers.tpl new file mode 100644 index 000000000..502c34e45 --- /dev/null +++ b/helm-charts/taco-orchestrator/templates/_helpers.tpl @@ -0,0 +1,51 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "digger-managed.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "digger-managed.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "digger-managed.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "digger-managed.labels" -}} +helm.sh/chart: {{ include "digger-managed.chart" . }} +{{ include "digger-managed.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "digger-managed.selectorLabels" -}} +app.kubernetes.io/name: {{ include "digger-managed.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} diff --git a/helm-charts/taco-orchestrator/templates/backend-deployment.yaml b/helm-charts/taco-orchestrator/templates/backend-deployment.yaml new file mode 100644 index 000000000..1acbc9e02 --- /dev/null +++ b/helm-charts/taco-orchestrator/templates/backend-deployment.yaml @@ -0,0 +1,77 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "digger-managed.fullname" . }}-web + labels: + {{- include "digger-managed.labels" . | nindent 4 }} +spec: + replicas: {{ .Values.digger.replicaCount }} + selector: + matchLabels: + app: {{ include "digger-managed.name" . }}-web + template: + metadata: + labels: + app: {{ include "digger-managed.name" . }}-web + {{- include "digger-managed.selectorLabels" . | nindent 8 }} + spec: + {{- if .Values.global }} + {{- with .Values.global.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- end }} + containers: + - name: web + image: "{{ .Values.global.imageRegistry | default "ghcr.io/diggerhq/digger" }}/{{ .Values.digger.image.repository }}:{{ .Values.digger.image.tag }}" + imagePullPolicy: {{ .Values.digger.image.pullPolicy | default "IfNotPresent" }} + ports: + - name: http + containerPort: 3000 + protocol: TCP + envFrom: + - secretRef: + {{- if .Values.digger.secret.useExistingSecret }} + name: {{ .Values.digger.secret.existingSecretName }} + {{- else }} + name: {{ include "digger-managed.fullname" . }}-secret + {{- end }} + env: + {{- if .Values.digger.postgres.existingSecretName }} + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Values.digger.postgres.existingSecretName }} + key: {{ .Values.digger.postgres.existingSecretKey }} + - name: DATABASE_URL + value: "postgres://{{ .Values.digger.postgres.user }}:$(POSTGRES_PASSWORD)@{{ .Values.digger.postgres.host }}:{{ .Values.digger.postgres.port }}/{{ .Values.digger.postgres.database }}?sslmode={{ .Values.digger.postgres.sslmode }}" + {{- end }} + - name: ALLOW_DIRTY + value: "{{ .Values.digger.postgres.allowDirty }}" + {{- with .Values.digger.customEnv }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- if .Values.digger.livenessProbe }} + livenessProbe: + {{- toYaml .Values.digger.livenessProbe | nindent 10 }} + {{- end }} + {{- if .Values.digger.startupProbe }} + startupProbe: + {{- toYaml .Values.digger.startupProbe | nindent 10 }} + {{- end }} + {{- with .Values.digger.resources }} + resources: + {{- toYaml . | nindent 10 }} + {{- end }} + {{- with .Values.digger.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.digger.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.digger.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/helm-charts/taco-orchestrator/templates/backend-ingress.yaml b/helm-charts/taco-orchestrator/templates/backend-ingress.yaml new file mode 100644 index 000000000..bc9192898 --- /dev/null +++ b/helm-charts/taco-orchestrator/templates/backend-ingress.yaml @@ -0,0 +1,44 @@ +{{- if .Values.digger.ingress.enabled -}} +{{- if and .Values.digger.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} + {{- if not (hasKey .Values.digger.ingress.annotations "kubernetes.io/ingress.class") }} + {{- $_ := set .Values.digger.ingress.annotations "kubernetes.io/ingress.class" .Values.digger.ingress.className }} + {{- end }} +{{- end }} +{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1 +{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1beta1 +{{- else -}} +apiVersion: extensions/v1beta1 +{{- end }} +kind: Ingress +metadata: + name: {{ include "digger-managed.fullname" . }} + annotations: + {{- range $key, $value := .Values.digger.ingress.annotations }} + {{ $key }}: {{ $value | quote }} + {{- end }} + labels: + {{- include "digger-managed.labels" . | nindent 4 }} +spec: + {{- if and .Values.digger.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} + ingressClassName: {{ .Values.digger.ingress.className }} + {{- end }} + {{- if .Values.digger.ingress.tls }} + tls: + - hosts: + - {{ .Values.digger.ingress.host }} + secretName: {{ .Values.digger.ingress.tls.secretName }} + {{- end }} + rules: + - host: {{ .Values.digger.ingress.host }} + http: + paths: + - path: {{ .Values.digger.ingress.path }} + pathType: Prefix + backend: + service: + name: {{ include "digger-managed.fullname" . }}-web + port: + number: {{ .Values.digger.service.port }} +{{- end }} diff --git a/helm-charts/taco-orchestrator/templates/backend-service.yaml b/helm-charts/taco-orchestrator/templates/backend-service.yaml new file mode 100644 index 000000000..2859e7964 --- /dev/null +++ b/helm-charts/taco-orchestrator/templates/backend-service.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "digger-managed.fullname" . }}-web +spec: + type: {{ .Values.digger.service.type }} + ports: + - port: {{ .Values.digger.service.port }} + targetPort: 3000 + selector: + app: {{ include "digger-managed.name" . }}-web diff --git a/helm-charts/taco-orchestrator/templates/postgres-secret.yaml b/helm-charts/taco-orchestrator/templates/postgres-secret.yaml new file mode 100644 index 000000000..301396c55 --- /dev/null +++ b/helm-charts/taco-orchestrator/templates/postgres-secret.yaml @@ -0,0 +1,12 @@ +{{- if and (not .Values.digger.secret.useExistingSecret) (not .Values.digger.postgres.existingSecretName) .Values.digger.postgres.password }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "digger-managed.fullname" . }}-postgres-secret + labels: + {{- include "digger-managed.labels" . | nindent 4 }} +type: Opaque +stringData: + postgres-password: {{ .Values.digger.postgres.password | quote }} +{{- end }} + diff --git a/helm-charts/taco-orchestrator/templates/secret.yaml b/helm-charts/taco-orchestrator/templates/secret.yaml new file mode 100644 index 000000000..7ca18e0b5 --- /dev/null +++ b/helm-charts/taco-orchestrator/templates/secret.yaml @@ -0,0 +1,76 @@ +{{- if not .Values.digger.secret.useExistingSecret }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "digger-managed.fullname" . }}-secret + labels: + {{- include "digger-managed.labels" . | nindent 4 }} +type: Opaque +stringData: + # Database Configuration + {{- if or .Values.digger.postgres.host .Values.digger.postgres.user .Values.digger.postgres.database }} + DATABASE_URL: "postgres://{{ .Values.digger.postgres.user }}:{{ .Values.digger.postgres.password }}@{{ .Values.digger.postgres.host }}:{{ .Values.digger.postgres.port }}/{{ .Values.digger.postgres.database }}?sslmode={{ .Values.digger.postgres.sslmode }}" + {{- end }} + + # Digger Configuration + {{- if .Values.digger.config.enableApiEndpoints }} + DIGGER_ENABLE_API_ENDPOINTS: "{{ .Values.digger.config.enableApiEndpoints }}" + {{- end }} + {{- if .Values.digger.config.enableInternalEndpoints }} + DIGGER_ENABLE_INTERNAL_ENDPOINTS: "{{ .Values.digger.config.enableInternalEndpoints }}" + {{- end }} + {{- if .Values.digger.config.encryptionSecret }} + DIGGER_ENCRYPTION_SECRET: {{ .Values.digger.config.encryptionSecret | quote }} + {{- end }} + {{- if .Values.digger.config.generationApiToken }} + DIGGER_GENERATION_API_TOKEN: {{ .Values.digger.config.generationApiToken | quote }} + {{- end }} + {{- if .Values.digger.config.generationEndpoint }} + DIGGER_GENERATION_ENDPOINT: {{ .Values.digger.config.generationEndpoint | quote }} + {{- end }} + {{- if .Values.digger.config.internalSecret }} + DIGGER_INTERNAL_SECRET: {{ .Values.digger.config.internalSecret | quote }} + {{- end }} + {{- if .Values.digger.config.licenseKey }} + DIGGER_LICENSE_KEY: {{ .Values.digger.config.licenseKey | quote }} + {{- end }} + DIGGER_LOAD_PROJECTS_ON_PUSH: "{{ .Values.digger.config.loadProjectsOnPush }}" + DIGGER_LOG_LEVEL: {{ .Values.digger.logLevel | quote }} + DIGGER_MAX_PROJECTS_PER_CHANGE: "{{ .Values.digger.config.maxProjectsPerChange }}" + + # GitHub App Configuration + {{- if .Values.digger.github.appClientId }} + GITHUB_APP_CLIENT_ID: {{ .Values.digger.github.appClientId | quote }} + {{- end }} + {{- if .Values.digger.github.appClientSecret }} + GITHUB_APP_CLIENT_SECRET: {{ .Values.digger.github.appClientSecret | quote }} + {{- end }} + {{- if .Values.digger.github.appId }} + GITHUB_APP_ID: {{ .Values.digger.github.appId | quote }} + {{- end }} + {{- if .Values.digger.github.appPrivateKeyBase64 }} + GITHUB_APP_PRIVATE_KEY_BASE64: {{ .Values.digger.github.appPrivateKeyBase64 | quote }} + {{- end }} + {{- if .Values.digger.github.webhookSecret }} + GITHUB_WEBHOOK_SECRET: {{ .Values.digger.github.webhookSecret | quote }} + {{- end }} + + # Go Configuration + GODEBUG: {{ .Values.digger.godebug | quote }} + GOFIPS140: {{ .Values.digger.gofips140 | quote }} + + # Application URLs + {{- if .Values.digger.hostname }} + HOSTNAME: {{ .Values.digger.hostname | quote }} + {{- end }} + JWT_AUTH: "{{ .Values.digger.jwtAuth }}" + + # Optional: Analytics & Monitoring + {{- if .Values.digger.segmentApiKey }} + SEGMENT_API_KEY: {{ .Values.digger.segmentApiKey | quote }} + {{- end }} + {{- if .Values.digger.sentryDsn }} + SENTRY_DSN: {{ .Values.digger.sentryDsn | quote }} + {{- end }} +{{- end }} + diff --git a/helm-charts/digger-backend/tests/deployments_test.yaml b/helm-charts/taco-orchestrator/tests/deployments_test.yaml similarity index 100% rename from helm-charts/digger-backend/tests/deployments_test.yaml rename to helm-charts/taco-orchestrator/tests/deployments_test.yaml diff --git a/helm-charts/taco-orchestrator/values.yaml b/helm-charts/taco-orchestrator/values.yaml new file mode 100644 index 000000000..7eff08a2b --- /dev/null +++ b/helm-charts/taco-orchestrator/values.yaml @@ -0,0 +1,147 @@ +# Digger Managed - Terraform Orchestration Service +# +# This chart creates environment variables from these values. +# You can either: +# 1. Set values here (chart creates secrets automatically) +# 2. Use secret.useExistingSecret=true and create your own secret +# +# See secrets-example/digger-backend.env for a complete example + +digger: + # Replica count + replicaCount: 1 + + # Image configuration + # Note: Full registry path comes from global.imageRegistry + # Public image: ghcr.io/diggerhq/digger/digger-backend-ee + image: + repository: digger-backend-ee + tag: "latest" + pullPolicy: IfNotPresent + + # Custom environment variables + # Format: + # customEnv: + # - name: MY_CUSTOM_ENV + # value: "my-value" + customEnv: [] + + # Log level: DEBUG or INFO + # Creates: DIGGER_LOG_LEVEL + logLevel: "INFO" # DIGGER_LOG_LEVEL + + # Resource limits and requests + resources: {} + # requests: + # cpu: 100m + # memory: 256Mi + # limits: + # cpu: 500m + # memory: 512Mi + + # Health check probes + livenessProbe: + httpGet: + path: /health + port: 3000 + periodSeconds: 20 + + startupProbe: + httpGet: + path: /health + port: 3000 + failureThreshold: 30 + periodSeconds: 10 + + # Service configuration + service: + type: ClusterIP + port: 3000 + + # Ingress configuration + ingress: + enabled: false + className: "" + annotations: {} + host: "" + path: / + tls: + secretName: "digger-backend-tls" + + # PostgreSQL configuration + # Creates: DATABASE_URL (constructed), ALLOW_DIRTY + postgres: + # Use existingSecret to pull password from an existing secret + existingSecretName: "" + existingSecretKey: "postgres-password" + + # Database connection details + sslmode: "disable" # Part of DATABASE_URL + user: "postgres" # Part of DATABASE_URL + database: "digger" # Part of DATABASE_URL + host: "postgresql.default.svc.cluster.local" # Part of DATABASE_URL + password: "" # Part of DATABASE_URL (from secret if existingSecretName set) + port: "5432" # Part of DATABASE_URL + allowDirty: false # ALLOW_DIRTY + + # Digger configuration + # Creates: DIGGER_ENABLE_API_ENDPOINTS, DIGGER_ENABLE_INTERNAL_ENDPOINTS, + # DIGGER_ENCRYPTION_SECRET, DIGGER_GENERATION_API_TOKEN, DIGGER_GENERATION_ENDPOINT, + # DIGGER_INTERNAL_SECRET, DIGGER_LICENSE_KEY, DIGGER_LOAD_PROJECTS_ON_PUSH, + # DIGGER_LOG_LEVEL, DIGGER_MAX_PROJECTS_PER_CHANGE, + config: + enableApiEndpoints: true # DIGGER_ENABLE_API_ENDPOINTS + enableInternalEndpoints: true # DIGGER_ENABLE_INTERNAL_ENDPOINTS + encryptionSecret: "" # DIGGER_ENCRYPTION_SECRET (32 chars) + generationApiToken: "" # DIGGER_GENERATION_API_TOKEN + generationEndpoint: "" # DIGGER_GENERATION_ENDPOINT + internalSecret: "" # DIGGER_INTERNAL_SECRET (50 chars) + licenseKey: "" # DIGGER_LICENSE_KEY + loadProjectsOnPush: false # DIGGER_LOAD_PROJECTS_ON_PUSH + maxProjectsPerChange: 100 # DIGGER_MAX_PROJECTS_PER_CHANGE + + + # GitHub App configuration (get from https://github.com/settings/apps) + # Creates: GITHUB_APP_CLIENT_ID, GITHUB_APP_CLIENT_SECRET, GITHUB_APP_ID, + # GITHUB_APP_PRIVATE_KEY_BASE64, GITHUB_WEBHOOK_SECRET + github: + appClientId: "" # GITHUB_APP_CLIENT_ID + appClientSecret: "" # GITHUB_APP_CLIENT_SECRET + appId: "" # GITHUB_APP_ID + appPrivateKeyBase64: "" # GITHUB_APP_PRIVATE_KEY_BASE64 (base64 encoded private key) + webhookSecret: "" # GITHUB_WEBHOOK_SECRET + + # Application URLs + # Creates: HOSTNAME, JWT_AUTH + hostname: "" # HOSTNAME (e.g., https://app.yourdomain.com) + jwtAuth: true # JWT_AUTH + + # Optional: Analytics & Monitoring + # Creates: SEGMENT_API_KEY, SENTRY_DSN + segmentApiKey: "" # SEGMENT_API_KEY (optional) + sentryDsn: "" # SENTRY_DSN (optional) + + # Go Configuration + # Creates: GODEBUG, GOFIPS140 + godebug: "off" # GODEBUG + gofips140: "off" # GOFIPS140 + + # Secret configuration + # For production, create secret externally: kubectl create secret generic taco-orchestrator-secrets --from-env-file=.secrets/digger-backend.env + # For development, set useExistingSecret=false and fill secret fields below + secret: + useExistingSecret: true + existingSecretName: "taco-orchestrator-secrets" + + # Node selector + nodeSelector: {} + + # Tolerations + tolerations: [] + + # Affinity + affinity: {} + +# Global configuration (optional) +global: + imagePullSecrets: [] diff --git a/helm-charts/taco-statesman/Chart.yaml b/helm-charts/taco-statesman/Chart.yaml index 42893c1db..fe1b1f26d 100644 --- a/helm-charts/taco-statesman/Chart.yaml +++ b/helm-charts/taco-statesman/Chart.yaml @@ -1,6 +1,6 @@ apiVersion: v2 -name: taco-statesman -description: A minimalist Helm chart for Taco Statesman service +name: statesman +description: Taco Statesman - Infrastructure-as-Code state management and coordination service type: application version: 0.1.0 appVersion: "v0.1.0" diff --git a/helm-charts/taco-statesman/README.md b/helm-charts/taco-statesman/README.md deleted file mode 100644 index 4292dd0b8..000000000 --- a/helm-charts/taco-statesman/README.md +++ /dev/null @@ -1,36 +0,0 @@ -# Taco Statesman Helm Chart - -A minimalist Helm chart for deploying the Taco Statesman service. - -## Quick Start - -```bash -# Install with default settings (memory storage) -helm install taco-statesman ./helm-charts/taco-statesman - -# Install with S3 storage -helm install taco-statesman ./helm-charts/taco-statesman \ - --set taco.storage.type=s3 \ - --set taco.storage.s3.bucket=my-bucket \ - --set taco.storage.s3.region=us-east-1 - -# Disable authentication (development) -helm install taco-statesman ./helm-charts/taco-statesman \ - --set taco.auth.disable=true -``` - -## Configuration - -| Parameter | Description | Default | -|-----------|-------------|---------| -| `taco.image.repository` | Image repository | `ghcr.io/diggerhq/digger/taco-statesman` | -| `taco.image.tag` | Image tag | `v0.1.0` | -| `taco.replicaCount` | Number of replicas | `1` | -| `taco.service.port` | Service port | `8080` | -| `taco.storage.type` | Storage type (`memory` or `s3`) | `memory` | -| `taco.auth.disable` | Disable authentication | `false` | - -## Storage - -- **Memory**: Default, no configuration needed -- **S3**: Set `taco.storage.type=s3` and provide S3 credentials diff --git a/helm-charts/taco-statesman/templates/deployment.yaml b/helm-charts/taco-statesman/templates/deployment.yaml index 1bf125fde..b01476236 100644 --- a/helm-charts/taco-statesman/templates/deployment.yaml +++ b/helm-charts/taco-statesman/templates/deployment.yaml @@ -14,14 +14,30 @@ spec: labels: {{- include "taco-statesman.selectorLabels" . | nindent 8 }} spec: + {{- if and .Values.taco.cloudSql .Values.taco.cloudSql.enabled }} + serviceAccountName: {{ .Values.taco.cloudSql.serviceAccount | default "default" }} + {{- end }} + {{- if .Values.global }} + {{- with .Values.global.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- end }} containers: - image: "{{ .Values.taco.image.repository }}:{{ .Values.taco.image.tag | default .Chart.AppVersion }}" - imagePullPolicy: {{ .Values.taco.image.pullPolicy | default "IfNotPresent" }} + - name: {{ .Chart.Name }} + image: "{{ .Values.global.imageRegistry | default "ghcr.io/diggerhq/digger" }}/{{ .Values.taco.image.repository }}:{{ .Values.taco.image.tag | default .Chart.AppVersion }}" imagePullPolicy: {{ .Values.taco.image.pullPolicy | default "IfNotPresent" }} ports: - name: http containerPort: {{ .Values.taco.service.port }} protocol: TCP + envFrom: + - secretRef: + {{- if .Values.taco.secret.useExistingSecret }} + name: {{ .Values.taco.secret.existingSecretName }} + {{- else }} + name: {{ include "taco-statesman.fullname" . }}-secret + {{- end }} env: - name: OPENTACO_PORT value: "{{ .Values.taco.service.port }}" @@ -31,25 +47,15 @@ spec: - name: OPENTACO_AUTH_DISABLE value: "true" {{- end }} - {{- if and (eq .Values.taco.storage.type "s3") .Values.taco.storage.s3.bucket }} - - name: OPENTACO_S3_BUCKET - value: "{{ .Values.taco.storage.s3.bucket }}" - {{- end }} - {{- if and (eq .Values.taco.storage.type "s3") .Values.taco.storage.s3.region }} - - name: OPENTACO_S3_REGION - value: "{{ .Values.taco.storage.s3.region }}" - {{- end }} - {{- if and (eq .Values.taco.storage.type "s3") .Values.taco.storage.s3.secretName }} - - name: OPENTACO_S3_ACCESS_KEY_ID + {{- if .Values.taco.postgres.existingSecretName }} + - name: OPENTACO_POSTGRES_PASSWORD valueFrom: secretKeyRef: - name: {{ .Values.taco.storage.s3.secretName }} - key: access-key-id - - name: OPENTACO_S3_SECRET_ACCESS_KEY - valueFrom: - secretKeyRef: - name: {{ .Values.taco.storage.s3.secretName }} - key: secret-access-key + name: {{ .Values.taco.postgres.existingSecretName }} + key: {{ .Values.taco.postgres.existingSecretKey }} + {{- end }} + {{- with .Values.taco.customEnv }} + {{- toYaml . | nindent 12 }} {{- end }} livenessProbe: httpGet: @@ -63,3 +69,42 @@ spec: port: http initialDelaySeconds: 5 periodSeconds: 5 + {{- with .Values.taco.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- if and .Values.taco.cloudSql .Values.taco.cloudSql.enabled }} + - name: cloud-sql-proxy + image: gcr.io/cloud-sql-connectors/cloud-sql-proxy:2.11.0 + args: + - "--structured-logs" + - "--port=5432" + - "{{ .Values.taco.cloudSql.instanceConnectionName }}" + securityContext: + runAsNonRoot: true + {{- if .Values.taco.cloudSql.credentialsSecret }} + env: + - name: GOOGLE_APPLICATION_CREDENTIALS + value: /secrets/cloudsql/credentials.json + volumeMounts: + - name: cloudsql-credentials + mountPath: /secrets/cloudsql + readOnly: true + {{- end }} + volumes: + - name: cloudsql-credentials + secret: + secretName: {{ .Values.taco.cloudSql.credentialsSecret }} + {{- end }} + {{- with .Values.taco.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.taco.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.taco.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/helm-charts/taco-statesman/templates/secret.yaml b/helm-charts/taco-statesman/templates/secret.yaml new file mode 100644 index 000000000..2f15d751e --- /dev/null +++ b/helm-charts/taco-statesman/templates/secret.yaml @@ -0,0 +1,102 @@ +{{- if not .Values.taco.secret.useExistingSecret }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "taco-statesman.fullname" . }}-secret + labels: + {{- include "taco-statesman.labels" . | nindent 4 }} +type: Opaque +stringData: + # Storage configuration + {{- if eq .Values.taco.storage.type "s3" }} + OPENTACO_S3_BUCKET: {{ .Values.taco.storage.s3.bucket | quote }} + OPENTACO_S3_REGION: {{ .Values.taco.storage.s3.region | quote }} + {{- if .Values.taco.storage.s3.prefix }} + OPENTACO_S3_PREFIX: {{ .Values.taco.storage.s3.prefix | quote }} + {{- end }} + {{- if .Values.taco.storage.s3.endpoint }} + AWS_ENDPOINT: {{ .Values.taco.storage.s3.endpoint | quote }} + {{- end }} + {{- if .Values.taco.storage.s3.accessKeyId }} + AWS_ACCESS_KEY_ID: {{ .Values.taco.storage.s3.accessKeyId | quote }} + {{- end }} + {{- if .Values.taco.storage.s3.secretAccessKey }} + AWS_SECRET_ACCESS_KEY: {{ .Values.taco.storage.s3.secretAccessKey | quote }} + {{- end }} + {{- if .Values.taco.storage.s3.awsRegion }} + AWS_REGION: {{ .Values.taco.storage.s3.awsRegion | quote }} + {{- end }} + {{- end }} + + # Auth configuration + {{- if not .Values.taco.auth.disable }} + {{- if .Values.taco.auth.issuer }} + OPENTACO_AUTH_ISSUER: {{ .Values.taco.auth.issuer | quote }} + {{- end }} + {{- if .Values.taco.auth.clientId }} + OPENTACO_AUTH_CLIENT_ID: {{ .Values.taco.auth.clientId | quote }} + {{- end }} + {{- if .Values.taco.auth.clientSecret }} + OPENTACO_AUTH_CLIENT_SECRET: {{ .Values.taco.auth.clientSecret | quote }} + {{- end }} + {{- if .Values.taco.auth.authUrl }} + OPENTACO_AUTH_AUTH_URL: {{ .Values.taco.auth.authUrl | quote }} + {{- end }} + {{- if .Values.taco.auth.tokenUrl }} + OPENTACO_AUTH_TOKEN_URL: {{ .Values.taco.auth.tokenUrl | quote }} + {{- end }} + {{- end }} + + # Internal API configuration + {{- if .Values.taco.internalSecret }} + OPENTACO_ENABLE_INTERNAL_ENDPOINTS: {{ .Values.taco.internalSecret | quote }} + {{- end }} + + # JWT Token configuration + {{- if .Values.taco.tokens.kid }} + OPENTACO_TOKENS_KID: {{ .Values.taco.tokens.kid | quote }} + {{- end }} + {{- if .Values.taco.tokens.accessTTL }} + OPENTACO_TOKENS_ACCESS_TTL: {{ .Values.taco.tokens.accessTTL | quote }} + {{- end }} + {{- if .Values.taco.tokens.refreshTTL }} + OPENTACO_TOKENS_REFRESH_TTL: {{ .Values.taco.tokens.refreshTTL | quote }} + {{- end }} + {{- if .Values.taco.tokens.privateKeyPemPath }} + OPENTACO_TOKENS_PRIVATE_KEY_PEM_PATH: {{ .Values.taco.tokens.privateKeyPemPath | quote }} + {{- end }} + + # Public Base URL + {{- if .Values.taco.publicBaseUrl }} + OPENTACO_PUBLIC_BASE_URL: {{ .Values.taco.publicBaseUrl | quote }} + {{- end }} + + # OAuth State Key + {{- if .Values.taco.oauthStateKey }} + OPENTACO_OAUTH_STATE_KEY: {{ .Values.taco.oauthStateKey | quote }} + {{- end }} + + # PostgreSQL configuration + {{- if .Values.taco.postgres.host }} + OPENTACO_POSTGRES_HOST: {{ .Values.taco.postgres.host | quote }} + {{- end }} + {{- if .Values.taco.postgres.port }} + OPENTACO_POSTGRES_PORT: {{ .Values.taco.postgres.port | quote }} + {{- end }} + {{- if .Values.taco.postgres.user }} + OPENTACO_POSTGRES_USER: {{ .Values.taco.postgres.user | quote }} + {{- end }} + {{- if .Values.taco.postgres.database }} + OPENTACO_POSTGRES_DBNAME: {{ .Values.taco.postgres.database | quote }} + {{- end }} + {{- if .Values.taco.postgres.sslmode }} + OPENTACO_POSTGRES_SSLMODE: {{ .Values.taco.postgres.sslmode | quote }} + {{- end }} + {{- if .Values.taco.queryBackend }} + OPENTACO_QUERY_BACKEND: {{ .Values.taco.queryBackend | quote }} + {{- end }} + {{- if and (not .Values.taco.postgres.existingSecretName) .Values.taco.postgres.password }} + OPENTACO_POSTGRES_PASSWORD: {{ .Values.taco.postgres.password | quote }} + {{- end }} +{{- end }} + diff --git a/helm-charts/taco-statesman/values.yaml b/helm-charts/taco-statesman/values.yaml index fc355e2d6..71156e889 100644 --- a/helm-charts/taco-statesman/values.yaml +++ b/helm-charts/taco-statesman/values.yaml @@ -1,30 +1,147 @@ # values.yaml +# +# This chart creates environment variables from these values. +# You can either: +# 1. Set values here (chart creates secrets automatically) +# 2. Use secret.useExistingSecret=true and create your own secret +# +# See secrets-example/statesman.env for a complete example taco: # Image configuration + # Note: Full registry path comes from global.imageRegistry + # Public image: ghcr.io/diggerhq/digger/taco-statesman image: - repository: ghcr.io/diggerhq/digger/taco-statesman - tag: "v0.1.0" + repository: taco-statesman + tag: "latest" pullPolicy: "IfNotPresent" # Number of replicas replicaCount: 1 # Service configuration + # Creates: OPENTACO_PORT (set automatically from port) service: type: ClusterIP - port: 8080 + port: 8080 # OPENTACO_PORT - # Storage type: "memory" or "s3" + # Storage configuration + # Creates: OPENTACO_STORAGE storage: + # Storage type: "memory" or "s3" type: "memory" + # S3 configuration (if using S3 storage) + # Creates: OPENTACO_S3_BUCKET, OPENTACO_S3_REGION, OPENTACO_S3_PREFIX + # AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION, AWS_ENDPOINT s3: - bucket: "" - region: "us-east-1" - # Use Kubernetes secrets for credentials - secretName: "taco-s3-credentials" + bucket: "" # OPENTACO_S3_BUCKET + region: "us-east-1" # OPENTACO_S3_REGION + prefix: "" # OPENTACO_S3_PREFIX (optional) + endpoint: "" # AWS_ENDPOINT (optional, for S3-compatible storage like Tigris) + # AWS credentials - can be overridden by existingSecretName + accessKeyId: "" # AWS_ACCESS_KEY_ID + secretAccessKey: "" # AWS_SECRET_ACCESS_KEY + awsRegion: "auto" # AWS_REGION (for S3-compatible storage) - # Authentication + # Authentication configuration + # Creates: OPENTACO_AUTH_DISABLE, OPENTACO_AUTH_ISSUER, OPENTACO_AUTH_CLIENT_ID, + # OPENTACO_AUTH_CLIENT_SECRET, OPENTACO_AUTH_AUTH_URL, OPENTACO_AUTH_TOKEN_URL auth: disable: false + # Auth0 configuration (get from https://manage.auth0.com/) + issuer: "" # OPENTACO_AUTH_ISSUER (e.g., https://your-tenant.auth0.com/) + clientId: "" # OPENTACO_AUTH_CLIENT_ID + clientSecret: "" # OPENTACO_AUTH_CLIENT_SECRET + authUrl: "" # OPENTACO_AUTH_AUTH_URL (e.g., https://your-tenant.auth0.com/authorize) + tokenUrl: "" # OPENTACO_AUTH_TOKEN_URL (e.g., https://your-tenant.auth0.com/oauth/token) + + # Internal API endpoints + # Creates: OPENTACO_ENABLE_INTERNAL_ENDPOINTS + # If set, enables internal endpoints with this secret (64 char secret) + internalSecret: "" # OPENTACO_ENABLE_INTERNAL_ENDPOINTS + + + + # JWT Token Configuration + # Creates: OPENTACO_TOKENS_KID, OPENTACO_TOKENS_ACCESS_TTL, OPENTACO_TOKENS_REFRESH_TTL, + # OPENTACO_TOKENS_PRIVATE_KEY_PEM_PATH + tokens: + kid: "k1" # OPENTACO_TOKENS_KID (Key ID for JWT signing) + accessTTL: "1h" # OPENTACO_TOKENS_ACCESS_TTL (access token lifetime) + refreshTTL: "720h" # OPENTACO_TOKENS_REFRESH_TTL (refresh token lifetime, 30 days) + privateKeyPemPath: "" # OPENTACO_TOKENS_PRIVATE_KEY_PEM_PATH (optional, generates ephemeral key if empty) + + # Public Base URL + # Creates: OPENTACO_PUBLIC_BASE_URL + # This is the public URL where OpenTaco is accessible (used as JWT issuer) + publicBaseUrl: "http://localhost:8080" # OPENTACO_PUBLIC_BASE_URL + + # OAuth Configuration + # Creates: OPENTACO_OAUTH_STATE_KEY + # IMPORTANT: Set this to a random 32-character string in production for security! + # Used to encrypt OAuth state parameters during PKCE flow + oauthStateKey: "" # OPENTACO_OAUTH_STATE_KEY (32+ char random string, REQUIRED for production) + + # Query Backend Configuration + # Creates: OPENTACO_QUERY_BACKEND + # Options: "postgres", "mssql", "sqlite", or other supported backends + queryBackend: "postgres" # OPENTACO_QUERY_BACKEND + + # PostgreSQL configuration (only used if queryBackend=postgres) + # Creates: OPENTACO_POSTGRES_HOST, OPENTACO_POSTGRES_PORT, OPENTACO_POSTGRES_USER, + # OPENTACO_POSTGRES_PASSWORD, OPENTACO_POSTGRES_DBNAME, OPENTACO_POSTGRES_SSLMODE + postgres: + # Use existingSecret to pull password from an existing secret + existingSecretName: "" + existingSecretKey: "postgres-password" + + # Database connection details + # Note: For Cloud SQL, use "localhost" (proxy provides local access) + host: "localhost" # OPENTACO_POSTGRES_HOST + port: "5432" # OPENTACO_POSTGRES_PORT + user: "taco" # OPENTACO_POSTGRES_USER + password: "" # OPENTACO_POSTGRES_PASSWORD + database: "taco" # OPENTACO_POSTGRES_DBNAME + sslmode: "disable" # OPENTACO_POSTGRES_SSLMODE (disable, require, verify-ca, verify-full) + + # Custom environment variables + customEnv: [] + # - name: MY_CUSTOM_ENV + # value: "my-value" + + # Secret configuration + # For production, create secret externally: kubectl create secret generic statesman-secrets --from-env-file=.secrets/statesman.env + # For development, set useExistingSecret=false and fill secret fields below + secret: + useExistingSecret: true + existingSecretName: "statesman-secrets" + + # Resource limits + resources: {} + # requests: + # cpu: 100m + # memory: 256Mi + # limits: + # cpu: 500m + # memory: 512Mi + + # Node selector + nodeSelector: {} + + # Tolerations + tolerations: [] + + # Affinity + affinity: {} + + # Cloud SQL Proxy configuration (for GCP) + cloudSql: + enabled: false + instanceConnectionName: "" + serviceAccount: "default" + credentialsSecret: "" + +# Global configuration (optional) +global: + imagePullSecrets: [] diff --git a/helm-charts/taco-ui/.helmignore b/helm-charts/taco-ui/.helmignore new file mode 100644 index 000000000..c479619bb --- /dev/null +++ b/helm-charts/taco-ui/.helmignore @@ -0,0 +1,27 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ +# Test files +tests/ +*.test.yaml + diff --git a/helm-charts/taco-ui/Chart.yaml b/helm-charts/taco-ui/Chart.yaml new file mode 100644 index 000000000..919fb933b --- /dev/null +++ b/helm-charts/taco-ui/Chart.yaml @@ -0,0 +1,8 @@ +apiVersion: v2 +name: ui +description: Taco UI - Web-based frontend for OpenTaco infrastructure management platform +type: application +version: 0.1.0 +appVersion: "v0.1.0" +icon: https://raw.githubusercontent.com/diggerhq/digger/main/docs/logo/digger-logo.png + diff --git a/helm-charts/taco-ui/templates/_helpers.tpl b/helm-charts/taco-ui/templates/_helpers.tpl new file mode 100644 index 000000000..717c4b3e2 --- /dev/null +++ b/helm-charts/taco-ui/templates/_helpers.tpl @@ -0,0 +1,59 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "taco-ui.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "taco-ui.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "taco-ui.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "taco-ui.labels" -}} +helm.sh/chart: {{ include "taco-ui.chart" . }} +{{ include "taco-ui.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "taco-ui.selectorLabels" -}} +app.kubernetes.io/name: {{ include "taco-ui.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "taco-ui.serviceAccountName" -}} +{{- default "default" .Values.ui.serviceAccount.name }} +{{- end }} + diff --git a/helm-charts/taco-ui/templates/deployment.yaml b/helm-charts/taco-ui/templates/deployment.yaml new file mode 100644 index 000000000..3e07b4729 --- /dev/null +++ b/helm-charts/taco-ui/templates/deployment.yaml @@ -0,0 +1,91 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "taco-ui.fullname" . }} + labels: + {{- include "taco-ui.labels" . | nindent 4 }} +spec: + replicas: {{ .Values.ui.replicaCount }} + selector: + matchLabels: + {{- include "taco-ui.selectorLabels" . | nindent 6 }} + template: + metadata: + labels: + {{- include "taco-ui.selectorLabels" . | nindent 8 }} + spec: + {{- if .Values.global }} + {{- with .Values.global.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- end }} + containers: + - name: {{ .Chart.Name }} + image: "{{ .Values.global.imageRegistry | default "ghcr.io/diggerhq/digger" }}/{{ .Values.ui.image.repository }}:{{ .Values.ui.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.ui.image.pullPolicy | default "IfNotPresent" }} + ports: + - name: http + containerPort: {{ .Values.ui.service.port }} + protocol: TCP + envFrom: + - secretRef: + {{- if .Values.ui.secret.useExistingSecret }} + name: {{ .Values.ui.secret.existingSecretName }} + {{- else }} + name: {{ include "taco-ui.fullname" . }}-secret + {{- end }} + env: + - name: PORT + value: "{{ .Values.ui.service.port }}" + {{- if .Values.ui.env.apiUrl }} + - name: VITE_API_URL + value: "{{ .Values.ui.env.apiUrl }}" + {{- end }} + {{- if .Values.ui.env.allowedHosts }} + - name: ALLOWED_HOSTS + value: "{{ .Values.ui.env.allowedHosts }}" + {{- end }} + {{- if .Values.ui.env.posthog.key }} + - name: VITE_POSTHOG_KEY + value: "{{ .Values.ui.env.posthog.key }}" + {{- end }} + {{- if .Values.ui.env.posthog.host }} + - name: VITE_POSTHOG_HOST + value: "{{ .Values.ui.env.posthog.host }}" + {{- end }} + {{- with .Values.ui.customEnv }} + {{- toYaml . | nindent 12 }} + {{- end }} + livenessProbe: + httpGet: + path: / + port: http + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + readinessProbe: + httpGet: + path: / + port: http + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 3 + failureThreshold: 3 + {{- with .Values.ui.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.ui.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.ui.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.ui.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/helm-charts/taco-ui/templates/ingress.yaml b/helm-charts/taco-ui/templates/ingress.yaml new file mode 100644 index 000000000..92be1dcc2 --- /dev/null +++ b/helm-charts/taco-ui/templates/ingress.yaml @@ -0,0 +1,42 @@ +{{- if .Values.ui.ingress.enabled -}} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ include "taco-ui.fullname" . }} + labels: + {{- include "taco-ui.labels" . | nindent 4 }} + {{- with .Values.ui.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if .Values.ui.ingress.className }} + ingressClassName: {{ .Values.ui.ingress.className }} + {{- end }} + {{- if .Values.ui.ingress.tls }} + tls: + {{- range .Values.ui.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- range .Values.ui.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + pathType: {{ .pathType }} + backend: + service: + name: {{ include "taco-ui.fullname" $ }} + port: + name: http + {{- end }} + {{- end }} +{{- end }} + diff --git a/helm-charts/taco-ui/templates/secret.yaml b/helm-charts/taco-ui/templates/secret.yaml new file mode 100644 index 000000000..a3b72f986 --- /dev/null +++ b/helm-charts/taco-ui/templates/secret.yaml @@ -0,0 +1,47 @@ +{{- if not .Values.ui.secret.useExistingSecret }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "taco-ui.fullname" . }}-secret + labels: + {{- include "taco-ui.labels" . | nindent 4 }} +type: Opaque +stringData: + # WorkOS configuration + {{- if .Values.ui.env.workos.redirectUri }} + WORKOS_REDIRECT_URI: {{ .Values.ui.env.workos.redirectUri | quote }} + {{- end }} + {{- if .Values.ui.env.workos.apiKey }} + WORKOS_API_KEY: {{ .Values.ui.env.workos.apiKey | quote }} + {{- end }} + {{- if .Values.ui.env.workos.clientId }} + WORKOS_CLIENT_ID: {{ .Values.ui.env.workos.clientId | quote }} + {{- end }} + {{- if .Values.ui.env.workos.cookiePassword }} + WORKOS_COOKIE_PASSWORD: {{ .Values.ui.env.workos.cookiePassword | quote }} + {{- end }} + {{- if .Values.ui.env.workos.webhookSecret }} + WORKOS_WEBHOOK_SECRET: {{ .Values.ui.env.workos.webhookSecret | quote }} + {{- end }} + + # Backend service URLs + {{- if .Values.ui.env.backends.orchestratorUrl }} + ORCHESTRATOR_BACKEND_URL: {{ .Values.ui.env.backends.orchestratorUrl | quote }} + {{- end }} + {{- if .Values.ui.env.backends.orchestratorSecret }} + ORCHESTRATOR_BACKEND_SECRET: {{ .Values.ui.env.backends.orchestratorSecret | quote }} + {{- end }} + {{- if .Values.ui.env.backends.driftReportingUrl }} + DRIFT_REPORTING_BACKEND_URL: {{ .Values.ui.env.backends.driftReportingUrl | quote }} + {{- end }} + {{- if .Values.ui.env.backends.driftReportingWebhookSecret }} + DRIFT_REPORTING_BACKEND_WEBHOOK_SECRET: {{ .Values.ui.env.backends.driftReportingWebhookSecret | quote }} + {{- end }} + {{- if .Values.ui.env.backends.statesmanUrl }} + STATESMAN_BACKEND_URL: {{ .Values.ui.env.backends.statesmanUrl | quote }} + {{- end }} + {{- if .Values.ui.env.backends.statesmanWebhookSecret }} + STATESMAN_BACKEND_WEBHOOK_SECRET: {{ .Values.ui.env.backends.statesmanWebhookSecret | quote }} + {{- end }} +{{- end }} + diff --git a/helm-charts/taco-ui/templates/service.yaml b/helm-charts/taco-ui/templates/service.yaml new file mode 100644 index 000000000..77412540d --- /dev/null +++ b/helm-charts/taco-ui/templates/service.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "taco-ui.fullname" . }} + labels: + {{- include "taco-ui.labels" . | nindent 4 }} +spec: + type: {{ .Values.ui.service.type }} + ports: + - port: {{ .Values.ui.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "taco-ui.selectorLabels" . | nindent 4 }} + diff --git a/helm-charts/taco-ui/tests/deployment_test.yaml b/helm-charts/taco-ui/tests/deployment_test.yaml new file mode 100644 index 000000000..971ed8f8a --- /dev/null +++ b/helm-charts/taco-ui/tests/deployment_test.yaml @@ -0,0 +1,44 @@ +suite: test deployment +templates: + - deployment.yaml +tests: + - it: should create a deployment + asserts: + - isKind: + of: Deployment + - equal: + path: metadata.name + value: RELEASE-NAME-taco-ui + - equal: + path: spec.replicas + value: 1 + + - it: should set correct container image + asserts: + - equal: + path: spec.template.spec.containers[0].image + value: "ghcr.io/diggerhq/digger/taco-ui:v0.1.0" + + - it: should set environment variables when apiUrl is provided + set: + ui.env.apiUrl: "http://test-backend:8080" + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: VITE_API_URL + value: "http://test-backend:8080" + + - it: should configure workos when secretName is provided + set: + ui.env.workos.secretName: "workos-test" + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: WORKOS_API_KEY + valueFrom: + secretKeyRef: + name: workos-test + key: api-key + diff --git a/helm-charts/taco-ui/values.yaml b/helm-charts/taco-ui/values.yaml new file mode 100644 index 000000000..ec315660b --- /dev/null +++ b/helm-charts/taco-ui/values.yaml @@ -0,0 +1,122 @@ +# values.yaml +# +# This chart creates environment variables from these values. +# You can either: +# 1. Set values here (chart creates secrets automatically) +# 2. Use secret.useExistingSecret=true and create your own secret +# +# See secrets-example/ui.env for a complete example + +ui: + # Image configuration + # This is a standalone Node.js + TanStack Start SSR app + # The image includes: + # - Production-optimized Node.js server with static asset serving + # - Built for linux/amd64 platform (GCP/GKE compatible) + # + # Note: Full registry path comes from global.imageRegistry + # Public image: ghcr.io/diggerhq/digger/taco-ui + image: + repository: taco-ui + tag: "latest" + pullPolicy: "IfNotPresent" + + # Number of replicas + replicaCount: 1 + + # Service configuration + # Creates: PORT (set automatically from port) + service: + type: ClusterIP + port: 3030 # PORT - taco-ui Node.js server listens on port 3030 + + # Environment configuration + # Creates environment variables for the UI service + env: + # Allowed hosts (comma-separated) + # Creates: ALLOWED_HOSTS + allowedHosts: "" # ALLOWED_HOSTS + + # WorkOS configuration (get from https://dashboard.workos.com/) + # Creates: WORKOS_REDIRECT_URI, WORKOS_API_KEY, WORKOS_CLIENT_ID, + # WORKOS_COOKIE_PASSWORD, WORKOS_WEBHOOK_SECRET + workos: + redirectUri: "" # WORKOS_REDIRECT_URI (e.g., https://your-domain.com/api/auth/callback) + apiKey: "" # WORKOS_API_KEY + clientId: "" # WORKOS_CLIENT_ID + cookiePassword: "" # WORKOS_COOKIE_PASSWORD (32 char random string) + webhookSecret: "" # WORKOS_WEBHOOK_SECRET + + # PostHog configuration (optional) + # Creates: VITE_POSTHOG_KEY, VITE_POSTHOG_HOST + posthog: + key: "" # VITE_POSTHOG_KEY + host: "" # VITE_POSTHOG_HOST + + # Backend service URLs (for in-cluster communication) + # Creates: ORCHESTRATOR_BACKEND_URL, ORCHESTRATOR_BACKEND_SECRET, + # DRIFT_REPORTING_BACKEND_URL, DRIFT_REPORTING_BACKEND_WEBHOOK_SECRET, + # STATESMAN_BACKEND_URL, STATESMAN_BACKEND_WEBHOOK_SECRET + backends: + # Orchestrator (Digger Backend) + orchestratorUrl: "" # ORCHESTRATOR_BACKEND_URL (e.g., http://opentaco-digger-managed-web:3000) -> the orchestrator is the taco-orchestrator service + orchestratorSecret: "" # ORCHESTRATOR_BACKEND_SECRET + + # Drift Reporting + driftReportingUrl: "" # DRIFT_REPORTING_BACKEND_URL (e.g., http://opentaco-drift:3004) -> the drift service + driftReportingWebhookSecret: "" # DRIFT_REPORTING_BACKEND_WEBHOOK_SECRET + + # Statesman + statesmanUrl: "" # STATESMAN_BACKEND_URL (e.g., http://opentaco-statesman:8080) -> the statesman service + statesmanWebhookSecret: "" # STATESMAN_BACKEND_WEBHOOK_SECRET + + # Custom environment variables + customEnv: [] + # - name: MY_CUSTOM_ENV + # value: "my-value" + + # Secret configuration + # For production, create secret externally: kubectl create secret generic ui-secrets --from-env-file=.secrets/ui.env + # For development, set useExistingSecret=false and fill secret fields below + secret: + useExistingSecret: true + existingSecretName: "ui-secrets" + + # Ingress configuration + ingress: + enabled: false + className: "nginx" + annotations: {} + # cert-manager.io/cluster-issuer: letsencrypt-prod + # kubernetes.io/tls-acme: "true" + hosts: + - host: taco.example.com + paths: + - path: / + pathType: Prefix + tls: [] + # - secretName: taco-ui-tls + # hosts: + # - taco.example.com + + # Resource limits + resources: {} + # limits: + # cpu: 500m + # memory: 512Mi + # requests: + # cpu: 250m + # memory: 256Mi + + # Node selector + nodeSelector: {} + + # Tolerations + tolerations: [] + + # Affinity + affinity: {} + +# Global configuration (optional) +global: + imagePullSecrets: [] diff --git a/ui/package.json b/ui/package.json index a8eadfa0d..b7f637ebd 100644 --- a/ui/package.json +++ b/ui/package.json @@ -7,7 +7,10 @@ "scripts": { "dev": "vite dev", "build": "vite build && tsc --noEmit", - "preview": "vite preview" + "start": "NODE_ENV=production node dist/server/server.js", + "preview": "vite preview", + "type-check": "tsc --noEmit", + "type-check:watch": "tsc --noEmit --watch" }, "keywords": [], "author": "", diff --git a/ui/server-start.js b/ui/server-start.js new file mode 100644 index 000000000..8e6682707 --- /dev/null +++ b/ui/server-start.js @@ -0,0 +1,102 @@ +// Simple Node.js HTTP server that runs the TanStack Start fetch handler +import { createServer } from 'node:http'; +import { readFile } from 'node:fs/promises'; +import { join, extname } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import serverHandler from './dist/server/server.js'; + +const __dirname = fileURLToPath(new URL('.', import.meta.url)); +const PORT = process.env.PORT || 3030; +const HOST = process.env.HOST || '0.0.0.0'; + +// MIME type mapping +const MIME_TYPES = { + '.html': 'text/html', + '.js': 'application/javascript', + '.mjs': 'application/javascript', + '.css': 'text/css', + '.json': 'application/json', + '.png': 'image/png', + '.jpg': 'image/jpeg', + '.gif': 'image/gif', + '.svg': 'image/svg+xml', + '.ico': 'image/x-icon', + '.woff': 'font/woff', + '.woff2': 'font/woff2', +}; + +const server = createServer(async (req, res) => { + try { + const url = new URL(req.url, `http://${req.headers.host}`); + const pathname = url.pathname; + + // Try to serve static files from dist/client first + if (pathname.startsWith('/assets/') || pathname === '/favicon.svg' || pathname === '/favicon.png' || pathname === '/favicon.ico') { + try { + const filePath = join(__dirname, 'dist', 'client', pathname); + const content = await readFile(filePath); + const ext = extname(pathname); + const mimeType = MIME_TYPES[ext] || 'application/octet-stream'; + + res.writeHead(200, { + 'Content-Type': mimeType, + 'Cache-Control': 'public, max-age=31536000, immutable', + }); + res.end(content); + return; + } catch (err) { + // File not found, fall through to SSR handler + } + } + + // Get request body if present + let body = undefined; + if (req.method !== 'GET' && req.method !== 'HEAD') { + const chunks = []; + for await (const chunk of req) { + chunks.push(chunk); + } + body = Buffer.concat(chunks); + } + + // Create Web Standard Request + const request = new Request(`http://${req.headers.host}${req.url}`, { + method: req.method, + headers: req.headers, + body: body, + }); + + // Call the TanStack Start fetch handler + const response = await serverHandler.fetch(request); + + // Convert Web Standard Response to Node.js response + res.statusCode = response.status; + res.statusMessage = response.statusText; + + // Set headers + response.headers.forEach((value, key) => { + res.setHeader(key, value); + }); + + // Stream the response body + if (response.body) { + const reader = response.body.getReader(); + while (true) { + const { done, value } = await reader.read(); + if (done) break; + res.write(value); + } + } + + res.end(); + } catch (error) { + console.error('Server error:', error); + res.statusCode = 500; + res.end('Internal Server Error'); + } +}); + +server.listen(PORT, HOST, () => { + console.log(`🚀 Server running at http://${HOST}:${PORT}/`); +}); + diff --git a/ui/src/routeTree.gen.ts b/ui/src/routeTree.gen.ts index 58d428ed1..5ca2315e7 100644 --- a/ui/src/routeTree.gen.ts +++ b/ui/src/routeTree.gen.ts @@ -34,7 +34,7 @@ import { Route as AuthenticatedDashboardDashboardProjectsIndexRouteImport } from import { Route as AuthenticatedDashboardDashboardUnitsUnitIdRouteImport } from './routes/_authenticated/_dashboard/dashboard/units.$unitId' import { Route as AuthenticatedDashboardDashboardReposConnectRouteImport } from './routes/_authenticated/_dashboard/dashboard/repos.connect' import { Route as AuthenticatedDashboardDashboardReposRepoIdRouteImport } from './routes/_authenticated/_dashboard/dashboard/repos.$repoId' -import { Route as AuthenticatedDashboardDashboardProjectsProjectIdRouteImport } from './routes/_authenticated/_dashboard/dashboard/projects.$projectId' +import { Route as AuthenticatedDashboardDashboardProjectsProjectidRouteImport } from './routes/_authenticated/_dashboard/dashboard/projects.$projectid' import { Route as AuthenticatedDashboardDashboardConnectionsConnectionIdRouteImport } from './routes/_authenticated/_dashboard/dashboard/connections.$connectionId' import { Route as OrchestratorReposNamespaceProjectsProjectNamePlan_policyRouteImport } from './routes/_orchestrator/repos/$namespace/projects/$projectName/plan_policy' import { Route as OrchestratorReposNamespaceProjectsProjectNameAccess_policyRouteImport } from './routes/_orchestrator/repos/$namespace/projects/$projectName/access_policy' @@ -181,10 +181,10 @@ const AuthenticatedDashboardDashboardReposRepoIdRoute = path: '/$repoId', getParentRoute: () => AuthenticatedDashboardDashboardReposRoute, } as any) -const AuthenticatedDashboardDashboardProjectsProjectIdRoute = - AuthenticatedDashboardDashboardProjectsProjectIdRouteImport.update({ - id: '/$projectId', - path: '/$projectId', +const AuthenticatedDashboardDashboardProjectsProjectidRoute = + AuthenticatedDashboardDashboardProjectsProjectidRouteImport.update({ + id: '/$projectid', + path: '/$projectid', getParentRoute: () => AuthenticatedDashboardDashboardProjectsRoute, } as any) const AuthenticatedDashboardDashboardConnectionsConnectionIdRoute = @@ -233,7 +233,7 @@ export interface FileRoutesByFullPath { '/api/auth/workos/switch-org': typeof ApiAuthWorkosSwitchOrgRoute '/api/auth/workos/webhooks': typeof ApiAuthWorkosWebhooksRoute '/dashboard/connections/$connectionId': typeof AuthenticatedDashboardDashboardConnectionsConnectionIdRoute - '/dashboard/projects/$projectId': typeof AuthenticatedDashboardDashboardProjectsProjectIdRoute + '/dashboard/projects/$projectid': typeof AuthenticatedDashboardDashboardProjectsProjectidRoute '/dashboard/repos/$repoId': typeof AuthenticatedDashboardDashboardReposRepoIdRoute '/dashboard/repos/connect': typeof AuthenticatedDashboardDashboardReposConnectRoute '/dashboard/units/$unitId': typeof AuthenticatedDashboardDashboardUnitsUnitIdRoute @@ -261,7 +261,7 @@ export interface FileRoutesByTo { '/api/auth/workos/switch-org': typeof ApiAuthWorkosSwitchOrgRoute '/api/auth/workos/webhooks': typeof ApiAuthWorkosWebhooksRoute '/dashboard/connections/$connectionId': typeof AuthenticatedDashboardDashboardConnectionsConnectionIdRoute - '/dashboard/projects/$projectId': typeof AuthenticatedDashboardDashboardProjectsProjectIdRoute + '/dashboard/projects/$projectid': typeof AuthenticatedDashboardDashboardProjectsProjectidRoute '/dashboard/repos/$repoId': typeof AuthenticatedDashboardDashboardReposRepoIdRoute '/dashboard/repos/connect': typeof AuthenticatedDashboardDashboardReposConnectRoute '/dashboard/units/$unitId': typeof AuthenticatedDashboardDashboardUnitsUnitIdRoute @@ -294,7 +294,7 @@ export interface FileRoutesById { '/api/auth/workos/switch-org': typeof ApiAuthWorkosSwitchOrgRoute '/api/auth/workos/webhooks': typeof ApiAuthWorkosWebhooksRoute '/_authenticated/_dashboard/dashboard/connections/$connectionId': typeof AuthenticatedDashboardDashboardConnectionsConnectionIdRoute - '/_authenticated/_dashboard/dashboard/projects/$projectId': typeof AuthenticatedDashboardDashboardProjectsProjectIdRoute + '/_authenticated/_dashboard/dashboard/projects/$projectid': typeof AuthenticatedDashboardDashboardProjectsProjectidRoute '/_authenticated/_dashboard/dashboard/repos/$repoId': typeof AuthenticatedDashboardDashboardReposRepoIdRoute '/_authenticated/_dashboard/dashboard/repos/connect': typeof AuthenticatedDashboardDashboardReposConnectRoute '/_authenticated/_dashboard/dashboard/units/$unitId': typeof AuthenticatedDashboardDashboardUnitsUnitIdRoute @@ -326,7 +326,7 @@ export interface FileRouteTypes { | '/api/auth/workos/switch-org' | '/api/auth/workos/webhooks' | '/dashboard/connections/$connectionId' - | '/dashboard/projects/$projectId' + | '/dashboard/projects/$projectid' | '/dashboard/repos/$repoId' | '/dashboard/repos/connect' | '/dashboard/units/$unitId' @@ -354,7 +354,7 @@ export interface FileRouteTypes { | '/api/auth/workos/switch-org' | '/api/auth/workos/webhooks' | '/dashboard/connections/$connectionId' - | '/dashboard/projects/$projectId' + | '/dashboard/projects/$projectid' | '/dashboard/repos/$repoId' | '/dashboard/repos/connect' | '/dashboard/units/$unitId' @@ -386,7 +386,7 @@ export interface FileRouteTypes { | '/api/auth/workos/switch-org' | '/api/auth/workos/webhooks' | '/_authenticated/_dashboard/dashboard/connections/$connectionId' - | '/_authenticated/_dashboard/dashboard/projects/$projectId' + | '/_authenticated/_dashboard/dashboard/projects/$projectid' | '/_authenticated/_dashboard/dashboard/repos/$repoId' | '/_authenticated/_dashboard/dashboard/repos/connect' | '/_authenticated/_dashboard/dashboard/units/$unitId' @@ -593,11 +593,11 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof AuthenticatedDashboardDashboardReposRepoIdRouteImport parentRoute: typeof AuthenticatedDashboardDashboardReposRoute } - '/_authenticated/_dashboard/dashboard/projects/$projectId': { - id: '/_authenticated/_dashboard/dashboard/projects/$projectId' - path: '/$projectId' - fullPath: '/dashboard/projects/$projectId' - preLoaderRoute: typeof AuthenticatedDashboardDashboardProjectsProjectIdRouteImport + '/_authenticated/_dashboard/dashboard/projects/$projectid': { + id: '/_authenticated/_dashboard/dashboard/projects/$projectid' + path: '/$projectid' + fullPath: '/dashboard/projects/$projectid' + preLoaderRoute: typeof AuthenticatedDashboardDashboardProjectsProjectidRouteImport parentRoute: typeof AuthenticatedDashboardDashboardProjectsRoute } '/_authenticated/_dashboard/dashboard/connections/$connectionId': { @@ -647,14 +647,14 @@ const AuthenticatedDashboardDashboardConnectionsRouteWithChildren = ) interface AuthenticatedDashboardDashboardProjectsRouteChildren { - AuthenticatedDashboardDashboardProjectsProjectIdRoute: typeof AuthenticatedDashboardDashboardProjectsProjectIdRoute + AuthenticatedDashboardDashboardProjectsProjectidRoute: typeof AuthenticatedDashboardDashboardProjectsProjectidRoute AuthenticatedDashboardDashboardProjectsIndexRoute: typeof AuthenticatedDashboardDashboardProjectsIndexRoute } const AuthenticatedDashboardDashboardProjectsRouteChildren: AuthenticatedDashboardDashboardProjectsRouteChildren = { - AuthenticatedDashboardDashboardProjectsProjectIdRoute: - AuthenticatedDashboardDashboardProjectsProjectIdRoute, + AuthenticatedDashboardDashboardProjectsProjectidRoute: + AuthenticatedDashboardDashboardProjectsProjectidRoute, AuthenticatedDashboardDashboardProjectsIndexRoute: AuthenticatedDashboardDashboardProjectsIndexRoute, } diff --git a/ui/src/routes/_authenticated/_dashboard/dashboard/projects.$projectid.tsx b/ui/src/routes/_authenticated/_dashboard/dashboard/projects.$projectid.tsx index 86ed44761..2d7ba333e 100644 --- a/ui/src/routes/_authenticated/_dashboard/dashboard/projects.$projectid.tsx +++ b/ui/src/routes/_authenticated/_dashboard/dashboard/projects.$projectid.tsx @@ -34,12 +34,12 @@ const getDriftIcon = (status: string) => { } export const Route = createFileRoute( - '/_authenticated/_dashboard/dashboard/projects/$projectId', + '/_authenticated/_dashboard/dashboard/projects/$projectid', )({ component: RouteComponent, - loader: async ({ context, params: {projectId} }) => { + loader: async ({ context, params: {projectid} }) => { const { user, organisationId } = context; - const project = await getProjectFn({data: {projectId, organisationId, userId: user?.id || ''}}) + const project = await getProjectFn({data: {projectId: projectid, organisationId, userId: user?.id || ''}}) return { project } } }) diff --git a/ui/src/routes/_authenticated/_dashboard/dashboard/projects.index.tsx b/ui/src/routes/_authenticated/_dashboard/dashboard/projects.index.tsx index f6727e3c6..b93b36900 100644 --- a/ui/src/routes/_authenticated/_dashboard/dashboard/projects.index.tsx +++ b/ui/src/routes/_authenticated/_dashboard/dashboard/projects.index.tsx @@ -126,7 +126,7 @@ function RouteComponent() { diff --git a/ui/vite.config.ts b/ui/vite.config.ts index bf3f0e1ab..658f7dbb2 100644 --- a/ui/vite.config.ts +++ b/ui/vite.config.ts @@ -2,7 +2,6 @@ import { defineConfig, loadEnv } from 'vite'; import tsConfigPaths from 'vite-tsconfig-paths'; import { tanstackStart } from '@tanstack/react-start/plugin/vite'; import viteReact from '@vitejs/plugin-react'; -import netlify from '@netlify/vite-plugin-tanstack-start'; export default defineConfig(({ mode }) => { @@ -26,8 +25,6 @@ export default defineConfig(({ mode }) => { tsConfigPaths({ projects: ['./tsconfig.json'], }), - netlify(), - // cloudflare({ viteEnvironment: { name: 'ssr' } }), tanstackStart(), viteReact(), ],