diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..6b627ad --- /dev/null +++ b/.editorconfig @@ -0,0 +1,19 @@ +# EditorConfig is awesome: https://EditorConfig.org +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 2 +indent_style = space +insert_final_newline = true +max_line_length = 120 +trim_trailing_whitespace = true + +[*.md] +max_line_length = 0 +trim_trailing_whitespace = false + +[*.{yaml,yml,json}] +indent_style = space +indent_size = 2 diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..266c4b9 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,124 @@ +# Copilot Instructions — helm-engram + +Helm chart repository for **Engram Cloud** — a Go-based AI persistent memory server backed by PostgreSQL. +The chart lives at `charts/engram/` and follows standard Helm chart conventions. + +--- + +## Build, Test & Lint + +```bash +# Install dependencies (run once or after Chart.yaml changes) +helm repo add bitnami https://charts.bitnami.com/bitnami +helm dependency update charts/engram/ + +# Lint +npm run lint # helm lint charts/engram +npm run lint:full # lint with full CI values + +# Unit tests (helm-unittest plugin required) +npm run test # all tests +npm run test:verbose # with verbose output +npm run test:update-snapshot # regenerate __snapshot__ files after template changes + +# Run a single test suite +helm unittest charts/engram/ -f tests/deployment_test.yaml + +# Template smoke tests +npm run template # minimal values (bundled PG) +npm run template:full # HPA + PDB + Ingress + resources +npm run template:ingress # ingress-only values + +# Regenerate README.md from README.md.gotmpl +npm run docs # requires helm-docs + +# Bump chart patch version +npm run version:bump +``` + +Install `helm-unittest`: +```bash +helm plugin install https://github.com/helm-unittest/helm-unittest --verify=false +``` + +--- + +## Architecture + +Single chart at `charts/engram/` with one subchart dependency (`bitnami/postgresql`). + +``` +charts/engram/ +├── Chart.yaml # version mirrors upstream Engram appVersion +├── values.yaml # annotated defaults +├── values.schema.json # JSON Schema validation for values +├── templates/ +│ ├── _helpers.tpl # shared named templates +│ ├── deployment.yaml +│ ├── configmap.yaml # non-secret env vars (port, host, allowedProjects) +│ ├── secret.yaml # ENGRAM_DATABASE_URL + ENGRAM_JWT_SECRET (rendered only if existingSecret is empty) +│ ├── extra-objects.yaml # renders .Values.extraObjects via tpl +│ └── ... # service, ingress, hpa, pdb, serviceaccount +├── tests/ # helm-unittest YAML test suites +│ └── __snapshot__/ # commit these when updating snapshot tests +└── ci/ # CI values files used by helm lint/template + ├── minimal-values.yaml + ├── full-values.yaml + └── ingress-values.yaml +``` + +**Config split — ConfigMap vs Secret:** +- `ConfigMap`: `ENGRAM_PORT`, `ENGRAM_CLOUD_HOST`, `ENGRAM_ALLOWED_PROJECTS`, `ENGRAM_INSECURE_NO_AUTH` +- `Secret`: `ENGRAM_DATABASE_URL`, `ENGRAM_JWT_SECRET` + +**Secret resolution (`engram.secretName` helper):** +1. If `engram.existingSecret` is set, use that Secret and skip rendering `templates/secret.yaml` entirely. +2. If `engram.existingSecret` is not set, render `templates/secret.yaml` and create a Secret from `engram.databaseUrl` + `engram.jwtSecret`. + +**DSN auto-build:** When `postgresql.enabled=true` and `engram.databaseUrl=""`, the `engram.databaseUrl` helper in `_helpers.tpl` assembles the DSN from `postgresql.auth.*` values. Explicit `engram.databaseUrl` always wins. + +**Init container:** When `postgresql.enabled=true`, the deployment automatically injects a `wait-for-postgresql` busybox init container (unless `postgresql.waitForReady.disabled=true`). + +--- + +## Key Conventions + +### Versioning +- `Chart.yaml` `version` and `appVersion` are kept in sync with upstream Engram releases. +- UpdateCLI (`.github/updatecli/`) opens automated PRs to bump both fields + `image.tag` in `values.yaml`. +- Use `npm run version:bump` for manual patch bumps. + +### Tests +- Test files live in `charts/engram/tests/` with the naming pattern `_test.yaml`. +- Each suite declares `templates:` listing which templates it covers. +- Snapshot files in `tests/__snapshot__/` must be committed after running `--update-snapshot`. +- CI values files in `ci/` are the canonical inputs for `helm lint` and `helm template` checks. + +### `extraObjects` +- Rendered via `tpl`, so they support `{{ .Release.Name }}`, `{{ .Values.* }}`, etc. +- Typical uses: `ExternalSecret`, `NetworkPolicy`, `ServiceMonitor`. + +### `_helpers.tpl` named templates +All chart-specific helpers are prefixed `engram.*`: +- `engram.name`, `engram.fullname`, `engram.chart` +- `engram.labels`, `engram.selectorLabels` +- `engram.serviceAccountName` +- `engram.secretName` — resolves existingSecret vs chart-managed secret +- `engram.databaseUrl` — auto-builds DSN from subchart values + +### Security defaults (do not weaken without tests) +- `runAsUser: 10001`, `runAsGroup: 10001`, `fsGroup: 10001` +- `runAsNonRoot: true`, `readOnlyRootFilesystem: true`, `allowPrivilegeEscalation: false` +- `capabilities.drop: [ALL]` + +### CI Workflows +| Workflow | Trigger | Purpose | +|----------|---------|---------| +| `helm-lint-test.yml` | PR | `helm lint` + `helm unittest` | +| `helm-release.yml` | push to main | Publishes chart to GitHub Pages via chart-releaser | +| `helm-check-engram-release.yml` | schedule | Checks for new upstream Engram releases | +| `github-auto-assign.yml` | PR | Auto-assigns reviewers | + +### pre-commit hooks +Hooks run: `trailing-whitespace`, `end-of-file-fixer`, `check-yaml`, `check-json`, `mixed-line-ending`, `helmlint`. +Install with `pre-commit install`. diff --git a/.github/cr.yaml b/.github/cr.yaml new file mode 100644 index 0000000..48ed784 --- /dev/null +++ b/.github/cr.yaml @@ -0,0 +1,3 @@ +generate-release-notes: true +release-name-template: "v{{ .Version }}" +skip-existing: true diff --git a/.github/ct.yaml b/.github/ct.yaml new file mode 100644 index 0000000..2a214f3 --- /dev/null +++ b/.github/ct.yaml @@ -0,0 +1,9 @@ +# See https://github.com/helm/chart-testing#configuration +chart-dirs: + - charts +check-version-increment: true +debug: false +remote: origin +target-branch: main +validate-maintainers: false +validate-yaml: true diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..1fd480a --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,33 @@ +version: 2 +updates: + # Keep GitHub Actions up to date + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + day: "monday" + time: "08:00" + timezone: "Europe/Madrid" + groups: + github-actions: + patterns: + - "*" + labels: + - "dependencies" + - "github-actions" + commit-message: + prefix: "ci" + + # Keep npm devDependencies up to date (helm-docs, etc.) + - package-ecosystem: "npm" + directory: "/" + schedule: + interval: "weekly" + day: "monday" + time: "08:00" + timezone: "Europe/Madrid" + labels: + - "dependencies" + - "npm" + commit-message: + prefix: "chore" diff --git a/.github/updatecli/helm-appversion.yaml b/.github/updatecli/helm-appversion.yaml new file mode 100644 index 0000000..90b8a93 --- /dev/null +++ b/.github/updatecli/helm-appversion.yaml @@ -0,0 +1,33 @@ +sources: + engram: + kind: githubrelease + spec: + owner: "Gentleman-Programming" + repository: "engram" + token: {{ requiredEnv "GITHUB_TOKEN" }} + versionFilter: + kind: semver + pattern: ">=v1.0.0" +conditions: {} +targets: + imageTag: + name: bump engram image tag in values.yaml + kind: helmchart + spec: + name: charts/engram + file: values.yaml + key: $.image.tag + sourceid: engram + appVersion: + name: bump appVersion in Chart.yaml + kind: helmchart + spec: + name: charts/engram + file: Chart.yaml + key: $.appVersion + sourceid: engram + # NOTE: chart version ($.version) is NOT managed here. + # When a new Engram release is detected, the helm-check-engram-release.yml workflow + # resets the chart version to {major}.{minor}.0 (e.g., v1.16.0 → 1.16.0). + # For chart-only changes, bump the patch manually: 1.16.0 → 1.16.1 → 1.16.2 + # using: npm run version:bump diff --git a/.github/workflows/github-auto-assign.yml b/.github/workflows/github-auto-assign.yml new file mode 100644 index 0000000..05a56f2 --- /dev/null +++ b/.github/workflows/github-auto-assign.yml @@ -0,0 +1,19 @@ +name: Auto-assign Issue + +on: + issues: + types: [opened] + pull_request_target: + types: [opened, ready_for_review] + +jobs: + auto-assign: + permissions: + contents: read + issues: write + pull-requests: write + uses: devops-ia/.github/.github/workflows/github-auto-assign.yml@main + with: + teams: devops-ia + secrets: + PAT_GITHUB: ${{ secrets.PAT_GITHUB }} \ No newline at end of file diff --git a/.github/workflows/helm-check-engram-release.yml b/.github/workflows/helm-check-engram-release.yml new file mode 100644 index 0000000..6b5b07b --- /dev/null +++ b/.github/workflows/helm-check-engram-release.yml @@ -0,0 +1,85 @@ +name: Check Engram release + +permissions: + contents: write + pull-requests: write + +on: + workflow_dispatch: + schedule: + - cron: '0 8 * * 1' # every Monday at 08:00 UTC + +jobs: + check-and-release: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 + + - name: Get current release + id: current_release + run: | + current_release=$(grep appVersion charts/engram/Chart.yaml | awk '{ print $2 }' | tr -d '"') + echo "current_release=$current_release" >> $GITHUB_OUTPUT + + - name: Install updatecli + uses: updatecli/updatecli-action@2c3221bc5f4499a99fec2c87d9de4a83cb30e990 # v3.1.3 + + - name: Update engram version + run: | + updatecli apply --config .github/updatecli/helm-appversion.yaml --commit=false + env: + GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" + + - name: Get latest release + id: latest_release + run: | + latest_release=$(grep appVersion charts/engram/Chart.yaml | awk '{ print $2 }' | tr -d '"') + echo "latest_release=$latest_release" >> $GITHUB_OUTPUT + + - name: Check if release changed + id: check_changes + run: | + if [ "${{ steps.latest_release.outputs.latest_release }}" != "${{ steps.current_release.outputs.current_release }}" ]; then + echo "release_changed=true" >> $GITHUB_OUTPUT + fi + + - name: Update README.md + if: steps.check_changes.outputs.release_changed == 'true' + uses: losisin/helm-docs-github-action@2ccf3e77eb70dc80d62f8cc26f15d0a96b75fef4 # v1.8.0 + with: + chart-search-root: charts/engram + + - name: Reset chart version for new Engram release + if: steps.check_changes.outputs.release_changed == 'true' + run: | + # Strip leading 'v' from the new appVersion and reset chart patch to 0 + # e.g., v1.16.0 → 1.16.0 / v1.17.3 → 1.17.0 + new_app="${{ steps.latest_release.outputs.latest_release }}" + new_app="${new_app#v}" + major=$(echo "$new_app" | cut -d. -f1) + minor=$(echo "$new_app" | cut -d. -f2) + chart_version="${major}.${minor}.0" + sed -i "s/^version:.*/version: ${chart_version}/" charts/engram/Chart.yaml + echo "Chart version reset to ${chart_version}" + + - name: Create PR with changes + if: steps.check_changes.outputs.release_changed == 'true' + uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1 + with: + token: ${{ secrets.PAT_GITHUB }} + commit-message: "fix: update engram version to ${{ steps.latest_release.outputs.latest_release }}" + signoff: false + branch: chore/upgrade-engram-${{ steps.latest_release.outputs.latest_release }} + delete-branch: true + title: '[engram] new release: ${{ steps.latest_release.outputs.latest_release }}' + body: | + Engram Cloud version: + - :information_source: Current: `${{ steps.current_release.outputs.current_release }}` + - :up: Upgrade: `${{ steps.latest_release.outputs.latest_release }}` + + Changelog: https://github.com/Gentleman-Programming/engram/releases/tag/${{ steps.latest_release.outputs.latest_release }} + labels: | + auto-pr-bump-version diff --git a/.github/workflows/helm-lint-test.yml b/.github/workflows/helm-lint-test.yml new file mode 100644 index 0000000..9428ac0 --- /dev/null +++ b/.github/workflows/helm-lint-test.yml @@ -0,0 +1,54 @@ +name: Lint and Test Helm Charts + +on: + workflow_dispatch: + pull_request: + +jobs: + lint-test: + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + + - name: Set up Helm + uses: azure/setup-helm@1a275c3b69536ee54be43f2070a358922e12c8d4 # v4.3.1 + + - name: Set up Python + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + with: + python-version: 3.x + check-latest: true + + - name: Set up chart-testing + uses: helm/chart-testing-action@6ec842c01de15ebb84c8627d2744a0c2f2755c9f # v2.8.0 + + - name: Run chart-testing (list-changed) + id: list-changed + run: | + changed=$(ct list-changed --config .github/ct.yaml) + if [[ -n "${changed}" ]]; then + echo "changed=true" >> "$GITHUB_OUTPUT" + fi + + - name: Install helm-unittest plugin + run: helm plugin install https://github.com/helm-unittest/helm-unittest --version 0.8.2 --verify=false + + - name: Run Helm unit tests + run: helm unittest charts/engram/ + + - name: Run chart-testing (lint) + if: steps.list-changed.outputs.changed == 'true' + run: ct lint --config .github/ct.yaml + + - name: Create kind cluster + if: steps.list-changed.outputs.changed == 'true' || github.event_name == 'workflow_dispatch' + uses: helm/kind-action@ef37e7f390d99f746eb8b610417061a60e82a6cc # v1.14.0 + + - name: Run chart-testing (install) + if: steps.list-changed.outputs.changed == 'true' + run: ct install --config .github/ct.yaml --helm-extra-args "--timeout 900s --debug" diff --git a/.github/workflows/helm-release.yml b/.github/workflows/helm-release.yml new file mode 100644 index 0000000..d1bdb36 --- /dev/null +++ b/.github/workflows/helm-release.yml @@ -0,0 +1,84 @@ +name: Release Helm Chart + +on: + workflow_dispatch: + push: + branches: + - main + paths: + - "charts/engram/**" + +permissions: + contents: read + pull-requests: write + +jobs: + get-version: + runs-on: ubuntu-latest + outputs: + tag: ${{ steps.version.outputs.tag }} + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Get chart version from Chart.yaml + id: version + run: | + VERSION=$(grep '^version:' charts/engram/Chart.yaml | awk '{print $2}') + echo "tag=v${VERSION}" >> $GITHUB_OUTPUT + + create-tag: + needs: get-version + runs-on: ubuntu-latest + permissions: + contents: write + outputs: + created: ${{ steps.tag.outputs.created }} + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 + + - name: Create version tag if it does not exist + id: tag + env: + TAG: ${{ needs.get-version.outputs.tag }} + run: | + if git ls-remote --tags origin "refs/tags/${TAG}" | grep -q "${TAG}"; then + echo "Tag ${TAG} already exists, skipping release" + echo "created=false" >> $GITHUB_OUTPUT + else + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git tag "${TAG}" + git push origin "${TAG}" + echo "created=true" >> $GITHUB_OUTPUT + fi + + release: + needs: [get-version, create-tag] + if: needs.create-tag.outputs.created == 'true' + runs-on: ubuntu-latest + permissions: + contents: write + packages: write + id-token: write + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 + + - name: Configure Git + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + - name: Install Helm + uses: azure/setup-helm@dda3372f752e03dde6b3237bc9431cdc2f7a02a2 # v5.0.0 + + - name: Run chart-releaser + uses: helm/chart-releaser-action@cbe3e82e3fd46e5e3f21ed29148e1c50c1e4b1e1 # v1.7.0 + with: + config: .github/cr.yaml + charts_dir: charts + env: + CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..67ce290 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +*.tgz +node_modules/ +.cr-release-packages/ +.ct/ +.DS_Store +*.swp diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..933d0cc --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,17 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: trailing-whitespace + exclude: ".*\\.md$" + - id: end-of-file-fixer + - id: check-yaml + args: [--allow-multiple-documents] + - id: check-json + - id: mixed-line-ending + args: [--fix=lf] + + - repo: https://github.com/gruntwork-io/pre-commit + rev: v0.1.24 + hooks: + - id: helmlint diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 0000000..0fdfbed --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1,2 @@ +# Owners of the entire repository +* @devops-ia \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..6d1dda9 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,41 @@ +# How to contribute to OpenCTI Helm Chart + +This document provides guidelines for contributing to the *OpenCTI Helm Chart* project. + +## How can I contribute? + +### Did you find a bug? + +* **Ensure the bug has not already been reported** by searching on GitHub under [Issues](https://github.com/devops-ia/helm-opencti/issues). +* If you cannot find an open issue addressing the problem, [open a new one](https://github.com/devops-ia/helm-opencti/issues/new). Include a **title and clear description**, as much relevant information as possible, and a **code sample** or an **executable test case** demonstrating the unexpected behavior. +* Use the relevant bug report templates to create the issue, if available. + +### Do you intend to add a new feature or change an existing one? + +* Please discuss first ([open an issue](https://github.com/devops-ia/helm-opencti/issues)) before starting any significant pull request (e.g., implementing features, refactoring code) to avoid spending time on something that might not be merged. +* Adhere to the project's coding conventions (indentation, accurate comments, etc.) and any other requirements (such as test coverage, documentation). + +## Styleguides + +### YAML Styleguide + +All YAML files must adhere to the following style guide: + +* Indentation: Use 2 spaces for indentation. +* No trailing spaces. +* Use hyphens for list items. +* Use camelCase for key names. +* Ensure there are no syntax errors. + +Additional rules: + +* Always use double quotes for strings. +* Keep lines to a maximum of 80 characters. +* Ensure proper alignment of nested elements. + +### Git Commit Messages + +* Use the present tense ("Add feature" not "Added feature"). +* Use the imperative mood ("Move cursor to..." not "Moves cursor to..."). +* Limit the first line to 72 characters or less. +* Reference issues and pull requests liberally after the first line. \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..b6eddf8 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 DevOps Solutions + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 110cda4..0c947fc 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,203 @@ # helm-engram -Helm chart for Engram Cloud + +> Community-maintained Helm chart for **[Engram Cloud](https://github.com/Gentleman-Programming/engram)** — +> the AI-powered persistent memory server that lets LLM agents share context and observations across +> sessions and team members. + +[![Artifact Hub](https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/helm-engram)](https://artifacthub.io/packages/search?repo=helm-engram) +[![Helm Lint & Test](https://github.com/devops-ia/helm-engram/actions/workflows/helm-lint-test.yml/badge.svg)](https://github.com/devops-ia/helm-engram/actions/workflows/helm-lint-test.yml) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) + +--- + +## What is this repository? + +This repo ships and maintains the `helm-engram/engram` Helm chart — one chart, one application. +It is **not** the upstream Engram application; for that see +[Gentleman-Programming/engram](https://github.com/Gentleman-Programming/engram). + +The chart packages Engram Cloud for Kubernetes with: + +- Internal PostgreSQL StatefulSet (no external Helm repo needed) +- Horizontal Pod Autoscaler and PodDisruptionBudget +- Optional NetworkPolicy +- Flexible secret management (chart-managed, `existingSecret`, ESO, Sealed Secrets) +- Full test suite via `helm-unittest` (99 tests) +- Automated version tracking via UpdateCLI + +--- + +## Architecture + +```mermaid +flowchart TD + subgraph Cluster["Kubernetes Cluster"] + direction TB + + ING["Ingress\n(optional)"] + SVC["Service\nClusterIP :18080"] + CM["ConfigMap\nHOST · PORT · PROJECTS · NO_AUTH"] + SEC["Secret\nDATABASE_URL · JWT_SECRET\nCLOUD_ADMIN · CLOUD_TOKEN"] + HPA["HPA (optional)"] + PDB["PDB (optional)"] + NP["NetworkPolicy (optional)"] + + subgraph Deployment["Deployment"] + IC["Init Container\nwait-for-postgresql"] + APP["engram cloud :18080\n/tmp emptyDir · UID 10001 non-root"] + IC -->|ready| APP + APP -.->|envFrom| CM + APP -.->|secretKeyRef| SEC + end + + subgraph PG["PostgreSQL StatefulSet"] + PGC["postgres:16-alpine :5432"] + PVC[("PVC")] + PGC --> PVC + end + + ING -->|HTTP/HTTPS| SVC + SVC --> Deployment + Deployment -->|TCP :5432| PG + HPA -.->|scales| Deployment + PDB -.->|protects| Deployment + NP -.->|restricts| Deployment + end + + AGENT(["AI Agents\nCopilot CLI · MCP clients"]) + ADMIN(["Admin\n/dashboard/admin"]) + + AGENT -->|"HTTP :18080"| ING + ADMIN -->|"adminToken"| ING +``` + +--- + +## Quick Start + +```bash +helm repo add helm-engram https://devops-ia.github.io/helm-engram +helm repo update + +# Production (authenticated mode) — generate strong random secrets +helm install engram helm-engram/engram \ + --set engram.jwtSecret="$(openssl rand -hex 32)" \ + --set engram.cloudToken="$(openssl rand -hex 32)" \ + --set engram.allowedProjects="my-project" \ + --set postgresql.auth.password="$(openssl rand -hex 16)" +``` + +> **Dev/local only** — disable auth for quick local testing: +> ```bash +> helm install engram helm-engram/engram \ +> --set engram.insecureNoAuth=true \ +> --set engram.allowedProjects="my-project" +> ``` +> Never use `insecureNoAuth=true` in production. + +--- + +## Authentication Modes + +Engram Cloud enforces one of two mutually exclusive modes: + +| Mode | Values | Use case | +|------|--------|----------| +| **Authenticated** (default) | `insecureNoAuth: false` + `cloudToken` + `jwtSecret` | Production | +| **Insecure** | `insecureNoAuth: true` | Local dev / CI only | + +**Constraints enforced by the binary** (Helm will also fail-fast): +- `cloudToken` is **required** when `insecureNoAuth=false` +- `cloudToken` must be **empty** when `insecureNoAuth=true` (mutually exclusive) +- `adminToken` (for `/dashboard/admin`) requires `insecureNoAuth=false` +- `allowedProjects` is **always required** + +When using `existingSecret`, the chart skips value validation — ensure your Secret contains +`ENGRAM_DATABASE_URL`, `ENGRAM_JWT_SECRET`, and `ENGRAM_CLOUD_TOKEN`. + +--- + +## Repository Structure + +``` +helm-engram/ +├── charts/engram/ # The Helm chart +│ ├── Chart.yaml +│ ├── values.yaml # Annotated defaults (source of truth for all config) +│ ├── values.schema.json # JSON Schema validation +│ ├── templates/ # Kubernetes manifest templates +│ ├── tests/ # helm-unittest test suites (99 tests) +│ ├── ci/ # CI values files (minimal, full, ingress) +│ └── README.md # Chart reference — auto-generated by helm-docs +├── .github/ +│ ├── workflows/ # CI: lint+test, release, version check +│ └── updatecli/ # Automated upstream version tracking +├── TESTING.md # Local development & testing guide +└── CONTRIBUTING.md # Contribution guidelines +``` + +--- + +## Development + +See [TESTING.md](TESTING.md) for the full local development workflow. Quick reference: + +```bash +# Lint +npm run lint # helm lint charts/engram +npm run lint:full # lint with full CI values + +# Unit tests (99 tests, 9 suites) +npm run test + +# Template smoke tests +npm run template # minimal values +npm run template:full # all features enabled +npm run template:ingress # ingress with TLS + +# Regenerate charts/engram/README.md from README.md.gotmpl +npm run docs +``` + +Install the `helm-unittest` plugin once: + +```bash +helm plugin install https://github.com/helm-unittest/helm-unittest --verify=false +``` + +--- + +## Automated Version Tracking + +[UpdateCLI](https://www.updatecli.io/) monitors +[Gentleman-Programming/engram releases](https://github.com/Gentleman-Programming/engram/releases) +and opens automated PRs to bump `image.tag`, `Chart.yaml appVersion`, and `Chart.yaml version`. + +Pipeline: `.github/updatecli/helm-appversion.yaml` + +--- + +## Contributing + +1. Fork → feature branch (`feat/my-feature`) +2. Change chart templates and/or values +3. Add or update tests in `charts/engram/tests/` +4. Run `npm run test && npm run lint` +5. Open a Pull Request — CI runs lint + unit tests + kind install automatically + +See [CONTRIBUTING.md](CONTRIBUTING.md) for details. + +--- + +## Links + +| | | +|--|--| +| Engram upstream | | +| ArtifactHub | | +| Chart reference | [charts/engram/README.md](charts/engram/README.md) | +| UpdateCLI | | + +## License + +MIT — see [LICENSE](LICENSE). diff --git a/TESTING.md b/TESTING.md new file mode 100644 index 0000000..7c48ac9 --- /dev/null +++ b/TESTING.md @@ -0,0 +1,81 @@ +# Testing + +This document describes how to run tests for the `helm-engram` chart locally. + +## Prerequisites + +- [Helm](https://helm.sh/docs/intro/install/) 3+ +- [helm-unittest](https://github.com/helm-unittest/helm-unittest) plugin + +## Install helm-unittest + +```bash +helm plugin install https://github.com/helm-unittest/helm-unittest --verify=false +``` + +## Run all unit tests + +```bash +helm unittest charts/engram/ +``` + +## Run tests with verbose output + +```bash +helm unittest -v charts/engram/ +``` + +## Update snapshots + +If you add or modify tests that use `matchSnapshot`, update the snapshot files: + +```bash +helm unittest --update-snapshot charts/engram/ +``` + +Commit the updated `charts/engram/tests/__snapshot__/` files. + +## Run helm lint + +```bash +# Default values +helm lint charts/engram + +# With CI values +helm lint charts/engram -f charts/engram/ci/minimal-values.yaml +helm lint charts/engram -f charts/engram/ci/full-values.yaml +helm lint charts/engram -f charts/engram/ci/ingress-values.yaml +``` + +## Run template smoke tests + +```bash +# Minimal — verify basic rendering +helm template engram charts/engram -f charts/engram/ci/minimal-values.yaml + +# Full — verify HPA, PDB, Ingress, resources +helm template engram charts/engram -f charts/engram/ci/full-values.yaml + +# Verify existingSecret skips Secret creation +helm template engram charts/engram \ + --set engram.existingSecret=my-secret \ + --set engram.databaseUrl="" \ + --set engram.jwtSecret="" +``` + +## npm scripts (convenience) + +If you have Node.js installed, you can use the npm scripts defined in `package.json`: + +```bash +npm run lint # helm lint charts/engram +npm run test # helm unittest charts/engram/ +npm run test:verbose +npm run test:update-snapshot +npm run template # smoke test with minimal values +npm run docs # generate README.md via helm-docs +``` + +## CI + +Tests run automatically on every pull request via `.github/workflows/helm-lint-test.yml`. diff --git a/charts/engram/.helmignore b/charts/engram/.helmignore new file mode 100644 index 0000000..e040b00 --- /dev/null +++ b/charts/engram/.helmignore @@ -0,0 +1,21 @@ +# Patterns to ignore when building packages. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Test and CI files +ci/ +tests/ +# Documentation (keep README.md) +TESTING.md diff --git a/charts/engram/Chart.yaml b/charts/engram/Chart.yaml new file mode 100644 index 0000000..01e0bd8 --- /dev/null +++ b/charts/engram/Chart.yaml @@ -0,0 +1,45 @@ +apiVersion: v2 +name: engram +description: A Helm chart for Engram Cloud — AI-powered persistent memory server for LLM agents +type: application +version: 1.15.10 +appVersion: v1.15.10 +home: https://github.com/Gentleman-Programming/engram +sources: + - https://github.com/Gentleman-Programming/engram + - https://github.com/devops-ia/helm-engram +maintainers: + - name: amartingarcia + email: adrianmg231189@gmail.com + - name: ialejandro + email: hello@ialejandro.rocks +keywords: + - engram + - ai + - memory + - llm + - mcp + - agent + - golang +annotations: + artifacthub.io/license: MIT + artifacthub.io/category: ai-ml + artifacthub.io/operator: "false" + artifacthub.io/prerelease: "false" + artifacthub.io/changes: | + - kind: added + description: "Built-in /tmp emptyDir volume for readOnlyRootFilesystem compatibility (tmpVolume.enabled)" + - kind: added + description: "ENGRAM_CLOUD_ADMIN support via engram.adminToken (dashboard /admin access)" + - kind: added + description: "ENGRAM_CLOUD_TOKEN support via engram.cloudToken (bearer token auth)" + - kind: added + description: "Optional NetworkPolicy template (networkPolicy.enabled)" + - kind: added + description: "PDB maxUnavailable option, mutually exclusive with minAvailable" + - kind: added + description: "runAsNonRoot: true in podSecurityContext" + - kind: fixed + description: "CI: minimal-values.yaml was missing allowedProjects (caused silent crash on install)" + - kind: changed + description: "CI: helm unittest runs before ct lint and ct install" diff --git a/charts/engram/README.md b/charts/engram/README.md new file mode 100644 index 0000000..f9dd898 --- /dev/null +++ b/charts/engram/README.md @@ -0,0 +1,181 @@ +# engram + +A Helm chart for Engram Cloud — AI-powered persistent memory server for LLM agents + +[![Artifact Hub](https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/helm-engram)](https://artifacthub.io/packages/search?repo=helm-engram) +[![Helm Lint & Test](https://github.com/devops-ia/helm-engram/actions/workflows/helm-lint-test.yml/badge.svg)](https://github.com/devops-ia/helm-engram/actions/workflows/helm-lint-test.yml) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/devops-ia/helm-engram/blob/main/LICENSE) + +## Maintainers + +| Name | Email | Url | +| ---- | ------ | --- | +| amartingarcia | | | +| ialejandro | | | + +## TL;DR + +```console +helm repo add helm-engram https://devops-ia.github.io/helm-engram +helm repo update + +# Authenticated mode (production) +helm install my-engram helm-engram/engram \ + --set engram.jwtSecret="$(openssl rand -hex 32)" \ + --set engram.cloudToken="$(openssl rand -hex 32)" \ + --set engram.allowedProjects="my-project" \ + --set postgresql.auth.password="$(openssl rand -hex 16)" +``` + +> **Local dev only:** add `--set engram.insecureNoAuth=true` and omit `cloudToken`/`jwtSecret`. +> See the [repository README](https://github.com/devops-ia/helm-engram#authentication-modes) +> for full auth documentation. + +## Prerequisites + +- Helm 3+ +- Kubernetes 1.25+ + +No additional Helm repositories required — the chart ships with its own internal PostgreSQL templates. + +## Add repository + +```console +helm repo add helm-engram https://devops-ia.github.io/helm-engram +helm repo update +``` + +## Install chart + +### Bundled PostgreSQL (default) + +```console +helm install my-engram helm-engram/engram \ + --set engram.jwtSecret="" \ + --set engram.cloudToken="" \ + --set engram.allowedProjects="my-project" \ + --set postgresql.auth.password="" +``` + +### External PostgreSQL + +```console +helm install my-engram helm-engram/engram \ + --set postgresql.enabled=false \ + --set engram.databaseUrl="postgres://user:pass@host:5432/engram_cloud?sslmode=require" \ + --set engram.jwtSecret="" \ + --set engram.cloudToken="" \ + --set engram.allowedProjects="my-project" +``` + +### Values file (recommended) + +```console +helm install my-engram helm-engram/engram -f my-values.yaml +``` + +## Upgrade + +```console +helm upgrade my-engram helm-engram/engram -f my-values.yaml +``` + +## Uninstall + +```console +helm uninstall my-engram +``` + +> PostgreSQL PVC is **not** deleted automatically on uninstall. + +## Values + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| affinity | object | `{}` | Affinity rules for pod scheduling | +| autoscaling | object | `{"enabled":false,"maxReplicas":10,"minReplicas":1,"targetCPUUtilizationPercentage":80}` | HorizontalPodAutoscaler configuration | +| autoscaling.enabled | bool | `false` | Enable HorizontalPodAutoscaler | +| autoscaling.maxReplicas | int | `10` | Maximum number of replicas | +| autoscaling.minReplicas | int | `1` | Minimum number of replicas | +| autoscaling.targetCPUUtilizationPercentage | int | `80` | Target CPU utilization percentage | +| deploymentAnnotations | object | `{}` | Deployment annotations | +| engram | object | `{"adminToken":"","allowedProjects":"","cloudToken":"","databaseUrl":"","existingSecret":"","host":"0.0.0.0","insecureNoAuth":false,"jwtSecret":"","port":"18080"}` | Engram Cloud configuration | +| engram.adminToken | string | `""` | Admin token for the dashboard /admin routes (ENGRAM_CLOUD_ADMIN). When set, grants access to /dashboard/admin. Store this in a Secret in production. | +| engram.allowedProjects | string | `""` | Required. Comma-separated list of allowed project names that Engram will serve. Must be set even when insecureNoAuth is true. Example: "project1,project2" | +| engram.cloudToken | string | `""` | Bearer token for client authentication (ENGRAM_CLOUD_TOKEN). REQUIRED when insecureNoAuth=false (production/authenticated mode). Must be an unguessable random string — use `openssl rand -hex 32`. Must be EMPTY when insecureNoAuth=true (dev-only insecure mode; the two are mutually exclusive). | +| engram.databaseUrl | string | `""` | Required. PostgreSQL DSN e.g. postgres://user:pass@host:5432/engram_cloud?sslmode=disable | +| engram.existingSecret | string | `""` | Name of an existing Secret containing ENGRAM_DATABASE_URL and ENGRAM_JWT_SECRET. When set, the chart does NOT create a Secret — use this for External Secrets Operator, Sealed Secrets, Vault Agent, or any external secrets manager. | +| engram.host | string | `"0.0.0.0"` | Bind address for the HTTP server | +| engram.insecureNoAuth | bool | `false` | Disable authentication. DEV ONLY — never enable in production | +| engram.jwtSecret | string | `""` | Required. JWT signing secret used to sign and verify tokens | +| engram.port | string | `"18080"` | HTTP listen port | +| env | list | `[]` | Environment variables to configure application | +| envFrom | list | `[]` | Variables from ConfigMap or Secret | +| extraContainers | list | `[]` | Extra sidecar containers to add to the pod | +| extraObjects | list | `[]` | Extra Kubernetes objects to deploy with this release. Useful for ExternalSecret, SealedSecret, NetworkPolicy, ServiceMonitor, etc. Each entry is rendered via tpl, so Helm template functions are supported. | +| extraVolumeMounts | list | `[]` | Extra volume mounts to add to the engram container | +| extraVolumes | list | `[]` | Extra volumes to add to the pod | +| fullnameOverride | string | `""` | String to fully override engram.fullname template | +| image | object | `{"pullPolicy":"IfNotPresent","repository":"ghcr.io/gentleman-programming/engram","tag":"v1.15.10"}` | Image registry | +| image.repository | string | `"ghcr.io/gentleman-programming/engram"` | Image repository. Published to GHCR via cloud-image.yml workflow | +| image.tag | string | `"v1.15.10"` | Image tag. Overrides the image tag whose default is the chart appVersion. Managed by updatecli monitoring Gentleman-Programming/engram releases. | +| imagePullSecrets | list | `[]` | Registry secret names as an array | +| ingress | object | `{"annotations":{},"className":"","enabled":false,"hosts":[{"host":"engram.local","paths":[{"path":"/","pathType":"ImplementationSpecific"}]}],"tls":[]}` | Ingress configuration | +| ingress.annotations | object | `{}` | Ingress annotations | +| ingress.className | string | `""` | Ingress class name (e.g. nginx, traefik) | +| ingress.enabled | bool | `false` | Enable ingress | +| ingress.hosts | list | `[{"host":"engram.local","paths":[{"path":"/","pathType":"ImplementationSpecific"}]}]` | Ingress hosts | +| ingress.tls | list | `[]` | Ingress TLS configuration | +| initContainers | list | `[]` | Init containers to add to the pod (appended after the auto-generated wait-for-postgresql init container) | +| livenessProbe | object | `{"httpGet":{"path":"/health","port":"http"},"initialDelaySeconds":10,"periodSeconds":10}` | Configure liveness probe Ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/ | +| nameOverride | string | `""` | String to partially override engram.fullname template (will maintain the release name) | +| networkPolicy | object | `{"enabled":false}` | NetworkPolicy configuration | +| networkPolicy.enabled | bool | `false` | Enable NetworkPolicy. When enabled, restricts ingress/egress for Engram pods: - Ingress: allowed on service port from ingress controller pods (if ingress.enabled) and any pod in the release namespace - Egress: allowed to PostgreSQL on port 5432, DNS on port 53, and Kubernetes API Leave disabled if your cluster uses a different network security model. | +| nodeSelector | object | `{}` | Node selector for pod scheduling | +| podAnnotations | object | `{}` | Pod annotations | +| podDisruptionBudget | object | `{"enabled":false,"maxUnavailable":null,"minAvailable":1}` | PodDisruptionBudget configuration | +| podDisruptionBudget.enabled | bool | `false` | Enable PodDisruptionBudget | +| podDisruptionBudget.maxUnavailable | string | `nil` | Maximum number of pods that may be unavailable during voluntary disruptions. Mutually exclusive with minAvailable — only one may be set. Set to null to use minAvailable. | +| podDisruptionBudget.minAvailable | int | `1` | Minimum number of pods that must be available during voluntary disruptions. Mutually exclusive with maxUnavailable — only one may be set. | +| podLabels | object | `{}` | Pod labels | +| podSecurityContext | object | `{"fsGroup":10001,"runAsGroup":10001,"runAsNonRoot":true,"runAsUser":10001}` | Privilege and access control settings for the Pod (pod-level) Engram Cloud runs as UID=10001 (engram user) | +| postgresql | object | `{"auth":{"database":"engram_cloud","password":"engram_change_me","username":"engram"},"enabled":true,"image":{"pullPolicy":"IfNotPresent","registry":"docker.io","repository":"postgres","tag":"16-alpine"},"persistence":{"accessMode":"ReadWriteOnce","enabled":true,"size":"1Gi","storageClass":""},"resources":{},"service":{"port":5432},"waitForReady":{"disabled":false}}` | Bundled PostgreSQL (internal templates using official postgres image). Enable to deploy a PostgreSQL StatefulSet alongside Engram Cloud. For production, set postgresql.enabled=false and configure an external PostgreSQL via engram.databaseUrl or engram.existingSecret. | +| postgresql.auth.database | string | `"engram_cloud"` | PostgreSQL database name | +| postgresql.auth.password | string | `"engram_change_me"` | PostgreSQL password. Change in production! | +| postgresql.auth.username | string | `"engram"` | PostgreSQL username for Engram Cloud | +| postgresql.enabled | bool | `true` | Enable the bundled PostgreSQL deployment. When true, ENGRAM_DATABASE_URL is auto-configured from auth values below. When false, you must provide engram.databaseUrl or engram.existingSecret. | +| postgresql.image | object | `{"pullPolicy":"IfNotPresent","registry":"docker.io","repository":"postgres","tag":"16-alpine"}` | PostgreSQL container image (official Docker Hub image) | +| postgresql.persistence | object | `{"accessMode":"ReadWriteOnce","enabled":true,"size":"1Gi","storageClass":""}` | Persistence configuration | +| postgresql.persistence.accessMode | string | `"ReadWriteOnce"` | Access mode | +| postgresql.persistence.enabled | bool | `true` | Enable persistent storage for PostgreSQL data | +| postgresql.persistence.size | string | `"1Gi"` | Storage size | +| postgresql.persistence.storageClass | string | `""` | Storage class (empty = cluster default) | +| postgresql.resources | object | `{}` | Resource requests and limits for PostgreSQL container | +| postgresql.service | object | `{"port":5432}` | Service configuration | +| postgresql.service.port | int | `5432` | PostgreSQL service port | +| postgresql.waitForReady | object | `{"disabled":false}` | Wait for PostgreSQL to be ready before starting Engram. Adds an init container that polls port 5432 with nc. Set disabled: true only if you manage readiness externally. | +| readinessProbe | object | `{"httpGet":{"path":"/health","port":"http"},"initialDelaySeconds":5,"periodSeconds":5}` | Configure readiness probe | +| replicaCount | int | `1` | Number of Engram Cloud replicas | +| resources | object | `{}` | Resource requests and limits Ref: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ | +| securityContext | object | `{"allowPrivilegeEscalation":false,"capabilities":{"drop":["ALL"]},"readOnlyRootFilesystem":true,"runAsNonRoot":true,"runAsUser":10001}` | Privilege and access control settings for the container | +| service | object | `{"annotations":{},"port":18080,"type":"ClusterIP"}` | Service configuration | +| service.annotations | object | `{}` | Annotations to add to the Service | +| service.port | int | `18080` | Service port (must match engram.port) | +| service.type | string | `"ClusterIP"` | Kubernetes Service type | +| serviceAccount | object | `{"annotations":{},"automountServiceAccountToken":false,"create":true,"name":""}` | Enable creation of ServiceAccount | +| serviceAccount.annotations | object | `{}` | Annotations to add to the service account (e.g. IAM role ARN for IRSA) | +| serviceAccount.automountServiceAccountToken | bool | `false` | Specifies if you don't want the kubelet to automatically mount a ServiceAccount's API credentials | +| serviceAccount.create | bool | `true` | Specifies whether a service account should be created | +| serviceAccount.name | string | `""` | The name of the service account to use. If not set and create is true, a name is generated using the fullname template | +| startupProbe | object | `{"failureThreshold":30,"httpGet":{"path":"/health","port":"http"},"periodSeconds":10}` | Configure startup probe. Gives the container up to 300s (30 × 10s) to start, allowing time for PostgreSQL migrations on cold start | +| tmpVolume | object | `{"enabled":true,"medium":"","sizeLimit":""}` | Built-in writable /tmp volume. Required when securityContext.readOnlyRootFilesystem=true (chart default). The Engram process needs /tmp for temporary file operations. Disable only if you provide your own /tmp via extraVolumes/extraVolumeMounts. | +| tmpVolume.enabled | bool | `true` | Enable the built-in /tmp emptyDir volume | +| tmpVolume.medium | string | `""` | Memory-backed storage medium (leave empty for disk) | +| tmpVolume.sizeLimit | string | `""` | Size limit for the /tmp volume | +| tolerations | list | `[]` | Tolerations for pod scheduling | +| topologySpreadConstraints | list | `[]` | Topology spread constraints for pod distribution across zones/nodes | + +## Source Code + +* +* diff --git a/charts/engram/README.md.gotmpl b/charts/engram/README.md.gotmpl new file mode 100644 index 0000000..bc6eed7 --- /dev/null +++ b/charts/engram/README.md.gotmpl @@ -0,0 +1,90 @@ +# {{ template "chart.name" . }} + +{{ template "chart.description" . }} + +[![Artifact Hub](https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/helm-engram)](https://artifacthub.io/packages/search?repo=helm-engram) +[![Helm Lint & Test](https://github.com/devops-ia/helm-engram/actions/workflows/helm-lint-test.yml/badge.svg)](https://github.com/devops-ia/helm-engram/actions/workflows/helm-lint-test.yml) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/devops-ia/helm-engram/blob/main/LICENSE) + +{{ template "chart.maintainersSection" . }} + +## TL;DR + +```console +helm repo add helm-engram https://devops-ia.github.io/helm-engram +helm repo update + +# Authenticated mode (production) +helm install my-engram helm-engram/engram \ + --set engram.jwtSecret="$(openssl rand -hex 32)" \ + --set engram.cloudToken="$(openssl rand -hex 32)" \ + --set engram.allowedProjects="my-project" \ + --set postgresql.auth.password="$(openssl rand -hex 16)" +``` + +> **Local dev only:** add `--set engram.insecureNoAuth=true` and omit `cloudToken`/`jwtSecret`. +> See the [repository README](https://github.com/devops-ia/helm-engram#authentication-modes) +> for full auth documentation. + +## Prerequisites + +- Helm 3+ +- Kubernetes 1.25+ + +No additional Helm repositories required — the chart ships with its own internal PostgreSQL templates. + +{{ template "chart.requirementsSection" . }} + +## Add repository + +```console +helm repo add helm-engram https://devops-ia.github.io/helm-engram +helm repo update +``` + +## Install chart + +### Bundled PostgreSQL (default) + +```console +helm install my-engram helm-engram/engram \ + --set engram.jwtSecret="" \ + --set engram.cloudToken="" \ + --set engram.allowedProjects="my-project" \ + --set postgresql.auth.password="" +``` + +### External PostgreSQL + +```console +helm install my-engram helm-engram/engram \ + --set postgresql.enabled=false \ + --set engram.databaseUrl="postgres://user:pass@host:5432/engram_cloud?sslmode=require" \ + --set engram.jwtSecret="" \ + --set engram.cloudToken="" \ + --set engram.allowedProjects="my-project" +``` + +### Values file (recommended) + +```console +helm install my-engram helm-engram/engram -f my-values.yaml +``` + +## Upgrade + +```console +helm upgrade my-engram helm-engram/engram -f my-values.yaml +``` + +## Uninstall + +```console +helm uninstall my-engram +``` + +> PostgreSQL PVC is **not** deleted automatically on uninstall. + +{{ template "chart.valuesSection" . }} + +{{ template "chart.sourcesSection" . }} diff --git a/charts/engram/artifacthub-repo.yml b/charts/engram/artifacthub-repo.yml new file mode 100644 index 0000000..c3779ea --- /dev/null +++ b/charts/engram/artifacthub-repo.yml @@ -0,0 +1,6 @@ +repositoryID: 32b19680-da41-4197-ae23-5bcd956e7ca1 +owners: + - name: amartingarcia + email: adrianmg231189@gmail.com + - name: ialejandro + email: hello@ialejandro.rocks diff --git a/charts/engram/ci/full-values.yaml b/charts/engram/ci/full-values.yaml new file mode 100644 index 0000000..fdd7c8e --- /dev/null +++ b/charts/engram/ci/full-values.yaml @@ -0,0 +1,53 @@ +# Full CI values — all optional features enabled +# databaseUrl omitted: auto-generated from postgresql.auth values +replicaCount: 1 + +engram: + jwtSecret: "ci-test-jwt-secret-do-not-use-in-production" + allowedProjects: "project1,project2" + insecureNoAuth: true + +podAnnotations: + test-annotation: "true" + +podLabels: + test-label: "true" + +deploymentAnnotations: + deployment-annotation: "true" + +resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: 500m + memory: 256Mi + +autoscaling: + enabled: true + minReplicas: 1 + maxReplicas: 5 + targetCPUUtilizationPercentage: 80 + +podDisruptionBudget: + enabled: true + minAvailable: 1 + +ingress: + enabled: true + className: nginx + hosts: + - host: engram.example.com + paths: + - path: / + pathType: Prefix + +serviceAccount: + annotations: + test-sa-annotation: "true" + +nodeSelector: + kubernetes.io/os: linux + +# tmpVolume.enabled=true by default; no need for extraVolumes override diff --git a/charts/engram/ci/ingress-values.yaml b/charts/engram/ci/ingress-values.yaml new file mode 100644 index 0000000..445e666 --- /dev/null +++ b/charts/engram/ci/ingress-values.yaml @@ -0,0 +1,20 @@ +# Ingress CI values — databaseUrl auto-generated from postgresql.auth values +engram: + jwtSecret: "ci-test-jwt-secret-do-not-use-in-production" + allowedProjects: "project1,project2" + insecureNoAuth: true + +ingress: + enabled: true + className: nginx + annotations: + nginx.ingress.kubernetes.io/rewrite-target: / + hosts: + - host: engram.example.com + paths: + - path: / + pathType: Prefix + tls: + - secretName: engram-tls + hosts: + - engram.example.com diff --git a/charts/engram/ci/minimal-values.yaml b/charts/engram/ci/minimal-values.yaml new file mode 100644 index 0000000..9ccae2d --- /dev/null +++ b/charts/engram/ci/minimal-values.yaml @@ -0,0 +1,7 @@ +# Minimal CI values — uses bundled PostgreSQL (postgresql.enabled=true by default) +# insecureNoAuth=true disables cloud token auth (CI-only, never use in production) +engram: + jwtSecret: "ci-test-jwt-secret-do-not-use-in-production" + insecureNoAuth: true + # allowedProjects is required even in insecureNoAuth mode — binary exits immediately when empty + allowedProjects: "project1,project2" diff --git a/charts/engram/templates/NOTES.txt b/charts/engram/templates/NOTES.txt new file mode 100644 index 0000000..748b2d6 --- /dev/null +++ b/charts/engram/templates/NOTES.txt @@ -0,0 +1,26 @@ +{{- include "engram.validateConfig" . -}} +Engram Cloud has been deployed to {{ .Release.Namespace }}! + +1. Get the application URL: +{{- if .Values.ingress.enabled }} +{{- range $host := .Values.ingress.hosts }} + {{- range .paths }} + http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} + {{- end }} +{{- end }} +{{- else }} + kubectl port-forward --namespace {{ .Release.Namespace }} svc/{{ include "engram.fullname" . }} 18080:{{ .Values.service.port }} + curl http://localhost:18080/healthz +{{- end }} + +2. Check Engram Cloud status: + kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "engram.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" + +{{- if not .Values.engram.databaseUrl }} +{{- if not .Values.engram.existingSecret }} + +⚠ WARNING: engram.databaseUrl is not set and no existingSecret is configured. + Engram Cloud will fail to start without a PostgreSQL connection. + Set engram.databaseUrl or engram.existingSecret in your values. +{{- end }} +{{- end }} diff --git a/charts/engram/templates/_helpers.tpl b/charts/engram/templates/_helpers.tpl new file mode 100644 index 0000000..047a538 --- /dev/null +++ b/charts/engram/templates/_helpers.tpl @@ -0,0 +1,119 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "engram.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 "engram.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 "engram.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "engram.labels" -}} +helm.sh/chart: {{ include "engram.chart" . }} +{{ include "engram.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "engram.selectorLabels" -}} +app.kubernetes.io/name: {{ include "engram.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "engram.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "engram.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} + +{{/* +Return the name of the secret containing sensitive env vars. +Uses existingSecret if set, otherwise the chart-managed secret. +*/}} +{{- define "engram.secretName" -}} +{{- if .Values.engram.existingSecret }} +{{- .Values.engram.existingSecret }} +{{- else }} +{{- include "engram.fullname" . }} +{{- end }} +{{- end }} + +{{/* +Fail-fast configuration validation. +Called from NOTES.txt so the error surfaces immediately on install/upgrade. +Skips auth checks when existingSecret is set — Helm cannot inspect Secret contents. +*/}} +{{- define "engram.validateConfig" -}} +{{- if empty .Values.engram.allowedProjects }} + {{- fail "\n\nERROR: engram.allowedProjects is required.\nSet it to a comma-separated list of project names, e.g.:\n --set engram.allowedProjects=\"my-project\"\n" }} +{{- end }} +{{- if not .Values.engram.insecureNoAuth }} + {{- if empty .Values.engram.existingSecret }} + {{- if empty .Values.engram.cloudToken }} + {{- fail "\n\nERROR: engram.cloudToken is required when engram.insecureNoAuth=false (authenticated mode).\nSet engram.cloudToken or use engram.existingSecret.\nFor local dev only: set engram.insecureNoAuth=true instead.\n" }} + {{- end }} + {{- if empty .Values.engram.jwtSecret }} + {{- fail "\n\nERROR: engram.jwtSecret is required when engram.insecureNoAuth=false.\nSet engram.jwtSecret or use engram.existingSecret.\n" }} + {{- end }} + {{- end }} +{{- else }} + {{- if not (empty .Values.engram.cloudToken) }} + {{- fail "\n\nERROR: engram.cloudToken must be empty when engram.insecureNoAuth=true.\nThese are mutually exclusive auth modes.\n" }} + {{- end }} + {{- if not (empty .Values.engram.adminToken) }} + {{- fail "\n\nERROR: engram.adminToken requires authenticated mode (engram.insecureNoAuth must be false).\n" }} + {{- end }} +{{- end }} +{{- end }} + +{{/* +Return the PostgreSQL database URL for Engram Cloud. +When postgresql subchart is enabled and engram.databaseUrl is not set, +auto-builds the DSN from the subchart connection values. +*/}} +{{- define "engram.databaseUrl" -}} +{{- if .Values.engram.databaseUrl -}} +{{- .Values.engram.databaseUrl -}} +{{- else if .Values.postgresql.enabled -}} +{{- printf "postgres://%s:%s@%s-postgresql:5432/%s?sslmode=disable" + .Values.postgresql.auth.username + .Values.postgresql.auth.password + .Release.Name + .Values.postgresql.auth.database -}} +{{- end -}} +{{- end }} diff --git a/charts/engram/templates/configmap.yaml b/charts/engram/templates/configmap.yaml new file mode 100644 index 0000000..aeebc4c --- /dev/null +++ b/charts/engram/templates/configmap.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "engram.fullname" . }} + labels: + {{- include "engram.labels" . | nindent 4 }} +data: + ENGRAM_CLOUD_HOST: {{ .Values.engram.host | quote }} + ENGRAM_PORT: {{ .Values.engram.port | quote }} + {{- if .Values.engram.allowedProjects }} + ENGRAM_CLOUD_ALLOWED_PROJECTS: {{ .Values.engram.allowedProjects | quote }} + {{- end }} + {{- if .Values.engram.insecureNoAuth }} + ENGRAM_CLOUD_INSECURE_NO_AUTH: "1" + {{- end }} diff --git a/charts/engram/templates/deployment.yaml b/charts/engram/templates/deployment.yaml new file mode 100644 index 0000000..250240f --- /dev/null +++ b/charts/engram/templates/deployment.yaml @@ -0,0 +1,150 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "engram.fullname" . }} + labels: + {{- include "engram.labels" . | nindent 4 }} + {{- with .Values.deploymentAnnotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include "engram.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "engram.selectorLabels" . | nindent 8 }} + {{- with .Values.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "engram.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + {{- $initContainers := .Values.initContainers | default list }} + {{- if and .Values.postgresql.enabled (not .Values.postgresql.waitForReady.disabled) }} + {{- $waitInit := dict "name" "wait-for-postgresql" "image" "busybox:1.36" "command" (list "sh" "-c" (printf "until nc -z %s-postgresql 5432; do echo waiting for postgresql; sleep 2; done" .Release.Name)) "securityContext" (dict "runAsNonRoot" true "runAsUser" 65534 "allowPrivilegeEscalation" false "capabilities" (dict "drop" (list "ALL"))) }} + {{- $initContainers = prepend $initContainers $waitInit }} + {{- end }} + {{- with $initContainers }} + initContainers: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: {{ .Chart.Name }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + command: + - engram + args: + - cloud + - serve + ports: + - name: http + containerPort: {{ .Values.engram.port | int }} + protocol: TCP + envFrom: + - configMapRef: + name: {{ include "engram.fullname" . }} + {{- with .Values.envFrom }} + {{- toYaml . | nindent 12 }} + {{- end }} + env: + - name: ENGRAM_DATABASE_URL + valueFrom: + secretKeyRef: + name: {{ include "engram.secretName" . }} + key: ENGRAM_DATABASE_URL + - name: ENGRAM_JWT_SECRET + valueFrom: + secretKeyRef: + name: {{ include "engram.secretName" . }} + key: ENGRAM_JWT_SECRET + - name: ENGRAM_CLOUD_ADMIN + valueFrom: + secretKeyRef: + name: {{ include "engram.secretName" . }} + key: ENGRAM_CLOUD_ADMIN + optional: true + - name: ENGRAM_CLOUD_TOKEN + valueFrom: + secretKeyRef: + name: {{ include "engram.secretName" . }} + key: ENGRAM_CLOUD_TOKEN + optional: true + {{- with .Values.env }} + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.livenessProbe }} + livenessProbe: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.readinessProbe }} + readinessProbe: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.startupProbe }} + startupProbe: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- if or .Values.tmpVolume.enabled .Values.extraVolumeMounts }} + volumeMounts: + {{- if .Values.tmpVolume.enabled }} + - name: tmp + mountPath: /tmp + {{- end }} + {{- with .Values.extraVolumeMounts }} + {{- toYaml . | nindent 12 }} + {{- end }} + {{- end }} + {{- with .Values.extraContainers }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.topologySpreadConstraints }} + topologySpreadConstraints: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- if or .Values.tmpVolume.enabled .Values.extraVolumes }} + volumes: + {{- if .Values.tmpVolume.enabled }} + {{- $emptyDir := dict }} + {{- if .Values.tmpVolume.medium }}{{- $emptyDir = set $emptyDir "medium" .Values.tmpVolume.medium }}{{- end }} + {{- if .Values.tmpVolume.sizeLimit }}{{- $emptyDir = set $emptyDir "sizeLimit" .Values.tmpVolume.sizeLimit }}{{- end }} + - name: tmp + emptyDir: {{ toYaml $emptyDir | trim }} + {{- end }} + {{- with .Values.extraVolumes }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- end }} diff --git a/charts/engram/templates/extra-objects.yaml b/charts/engram/templates/extra-objects.yaml new file mode 100644 index 0000000..fc9a76b --- /dev/null +++ b/charts/engram/templates/extra-objects.yaml @@ -0,0 +1,8 @@ +{{ range .Values.extraObjects }} +--- +{{ if typeIs "string" . }} + {{- tpl . $ }} +{{- else }} + {{- tpl (toYaml .) $ }} +{{- end }} +{{ end }} diff --git a/charts/engram/templates/hpa.yaml b/charts/engram/templates/hpa.yaml new file mode 100644 index 0000000..9aa51ed --- /dev/null +++ b/charts/engram/templates/hpa.yaml @@ -0,0 +1,24 @@ +{{- if .Values.autoscaling.enabled }} +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "engram.fullname" . }} + labels: + {{- include "engram.labels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "engram.fullname" . }} + minReplicas: {{ .Values.autoscaling.minReplicas }} + maxReplicas: {{ .Values.autoscaling.maxReplicas }} + metrics: + {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} + {{- end }} +{{- end }} diff --git a/charts/engram/templates/ingress.yaml b/charts/engram/templates/ingress.yaml new file mode 100644 index 0000000..6a63b89 --- /dev/null +++ b/charts/engram/templates/ingress.yaml @@ -0,0 +1,48 @@ +{{- if .Values.ingress.enabled -}} +{{- $fullName := include "engram.fullname" . -}} +{{- $svcPort := .Values.service.port -}} +{{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} + {{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }} + {{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}} + {{- end }} +{{- end }} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ $fullName }} + labels: + {{- include "engram.labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} + ingressClassName: {{ .Values.ingress.className }} + {{- end }} + {{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + pathType: {{ .pathType | default "ImplementationSpecific" }} + backend: + service: + name: {{ $fullName }} + port: + number: {{ $svcPort }} + {{- end }} + {{- end }} +{{- end }} diff --git a/charts/engram/templates/networkpolicy.yaml b/charts/engram/templates/networkpolicy.yaml new file mode 100644 index 0000000..33d5cae --- /dev/null +++ b/charts/engram/templates/networkpolicy.yaml @@ -0,0 +1,70 @@ +{{- if .Values.networkPolicy.enabled }} +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: {{ include "engram.fullname" . }} + labels: + {{- include "engram.labels" . | nindent 4 }} +spec: + podSelector: + matchLabels: + {{- include "engram.selectorLabels" . | nindent 6 }} + policyTypes: + - Ingress + - Egress + ingress: + # Allow traffic on the Engram HTTP port from any pod in the same namespace + - ports: + - port: {{ .Values.service.port }} + protocol: TCP + from: + - podSelector: {} + {{- if .Values.ingress.enabled }} + # Allow ingress controller traffic (standard nginx/traefik labels) + - ports: + - port: {{ .Values.service.port }} + protocol: TCP + from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: ingress-nginx + podSelector: + matchLabels: + app.kubernetes.io/name: ingress-nginx + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: traefik + podSelector: + matchLabels: + app.kubernetes.io/name: traefik + {{- end }} + egress: + # Allow DNS resolution (UDP + TCP) + - ports: + - port: 53 + protocol: UDP + - port: 53 + protocol: TCP + {{- if .Values.postgresql.enabled }} + # Allow egress to the bundled PostgreSQL StatefulSet + - ports: + - port: {{ .Values.postgresql.service.port }} + protocol: TCP + to: + - podSelector: + matchLabels: + app.kubernetes.io/name: postgresql + app.kubernetes.io/instance: {{ .Release.Name }} + {{- else }} + # Allow egress to external PostgreSQL (any destination, restricted by port) + - ports: + - port: 5432 + protocol: TCP + {{- end }} + # Allow Kubernetes API server access (for service account token refresh) + - ports: + - port: 443 + protocol: TCP + - port: 6443 + protocol: TCP +{{- end }} diff --git a/charts/engram/templates/pdb.yaml b/charts/engram/templates/pdb.yaml new file mode 100644 index 0000000..8922520 --- /dev/null +++ b/charts/engram/templates/pdb.yaml @@ -0,0 +1,17 @@ +{{- if .Values.podDisruptionBudget.enabled }} +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: {{ include "engram.fullname" . }} + labels: + {{- include "engram.labels" . | nindent 4 }} +spec: + {{- if .Values.podDisruptionBudget.maxUnavailable }} + maxUnavailable: {{ .Values.podDisruptionBudget.maxUnavailable }} + {{- else }} + minAvailable: {{ .Values.podDisruptionBudget.minAvailable }} + {{- end }} + selector: + matchLabels: + {{- include "engram.selectorLabels" . | nindent 6 }} +{{- end }} diff --git a/charts/engram/templates/postgresql/secret.yaml b/charts/engram/templates/postgresql/secret.yaml new file mode 100644 index 0000000..1e43be8 --- /dev/null +++ b/charts/engram/templates/postgresql/secret.yaml @@ -0,0 +1,12 @@ +{{- if .Values.postgresql.enabled }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ .Release.Name }}-postgresql + labels: + {{- include "engram.labels" . | nindent 4 }} + app.kubernetes.io/component: postgresql +type: Opaque +data: + password: {{ .Values.postgresql.auth.password | b64enc | quote }} +{{- end }} diff --git a/charts/engram/templates/postgresql/service.yaml b/charts/engram/templates/postgresql/service.yaml new file mode 100644 index 0000000..17320e8 --- /dev/null +++ b/charts/engram/templates/postgresql/service.yaml @@ -0,0 +1,19 @@ +{{- if .Values.postgresql.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }}-postgresql + labels: + {{- include "engram.labels" . | nindent 4 }} + app.kubernetes.io/component: postgresql +spec: + type: ClusterIP + ports: + - name: postgresql + port: {{ .Values.postgresql.service.port }} + targetPort: postgresql + protocol: TCP + selector: + app.kubernetes.io/name: {{ .Release.Name }}-postgresql + app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} diff --git a/charts/engram/templates/postgresql/statefulset.yaml b/charts/engram/templates/postgresql/statefulset.yaml new file mode 100644 index 0000000..a3025b8 --- /dev/null +++ b/charts/engram/templates/postgresql/statefulset.yaml @@ -0,0 +1,95 @@ +{{- if .Values.postgresql.enabled }} +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ .Release.Name }}-postgresql + labels: + {{- include "engram.labels" . | nindent 4 }} + app.kubernetes.io/component: postgresql +spec: + serviceName: {{ .Release.Name }}-postgresql + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: {{ .Release.Name }}-postgresql + app.kubernetes.io/instance: {{ .Release.Name }} + template: + metadata: + labels: + app.kubernetes.io/name: {{ .Release.Name }}-postgresql + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/component: postgresql + spec: + securityContext: + fsGroup: 999 + containers: + - name: postgresql + image: "{{ .Values.postgresql.image.registry }}/{{ .Values.postgresql.image.repository }}:{{ .Values.postgresql.image.tag }}" + imagePullPolicy: {{ .Values.postgresql.image.pullPolicy }} + env: + - name: POSTGRES_USER + value: {{ .Values.postgresql.auth.username | quote }} + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: {{ .Release.Name }}-postgresql + key: password + - name: POSTGRES_DB + value: {{ .Values.postgresql.auth.database | quote }} + - name: PGDATA + value: /var/lib/postgresql/data/pgdata + ports: + - name: postgresql + containerPort: 5432 + protocol: TCP + livenessProbe: + exec: + command: + - pg_isready + - -U + - {{ .Values.postgresql.auth.username | quote }} + - -d + - {{ .Values.postgresql.auth.database | quote }} + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 6 + readinessProbe: + exec: + command: + - pg_isready + - -U + - {{ .Values.postgresql.auth.username | quote }} + - -d + - {{ .Values.postgresql.auth.database | quote }} + initialDelaySeconds: 5 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 6 + {{- with .Values.postgresql.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} + volumeMounts: + - name: data + mountPath: /var/lib/postgresql/data + {{- if not .Values.postgresql.persistence.enabled }} + volumes: + - name: data + emptyDir: {} + {{- end }} + {{- if .Values.postgresql.persistence.enabled }} + volumeClaimTemplates: + - metadata: + name: data + spec: + accessModes: + - {{ .Values.postgresql.persistence.accessMode | quote }} + {{- with .Values.postgresql.persistence.storageClass }} + storageClassName: {{ . | quote }} + {{- end }} + resources: + requests: + storage: {{ .Values.postgresql.persistence.size | quote }} + {{- end }} +{{- end }} diff --git a/charts/engram/templates/secret.yaml b/charts/engram/templates/secret.yaml new file mode 100644 index 0000000..7f73efb --- /dev/null +++ b/charts/engram/templates/secret.yaml @@ -0,0 +1,18 @@ +{{- if not .Values.engram.existingSecret }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "engram.fullname" . }} + labels: + {{- include "engram.labels" . | nindent 4 }} +type: Opaque +data: + ENGRAM_DATABASE_URL: {{ include "engram.databaseUrl" . | b64enc | quote }} + ENGRAM_JWT_SECRET: {{ .Values.engram.jwtSecret | b64enc | quote }} + {{- if .Values.engram.adminToken }} + ENGRAM_CLOUD_ADMIN: {{ .Values.engram.adminToken | b64enc | quote }} + {{- end }} + {{- if .Values.engram.cloudToken }} + ENGRAM_CLOUD_TOKEN: {{ .Values.engram.cloudToken | b64enc | quote }} + {{- end }} +{{- end }} diff --git a/charts/engram/templates/service.yaml b/charts/engram/templates/service.yaml new file mode 100644 index 0000000..7a2c529 --- /dev/null +++ b/charts/engram/templates/service.yaml @@ -0,0 +1,19 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "engram.fullname" . }} + labels: + {{- include "engram.labels" . | nindent 4 }} + {{- with .Values.service.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "engram.selectorLabels" . | nindent 4 }} diff --git a/charts/engram/templates/serviceaccount.yaml b/charts/engram/templates/serviceaccount.yaml new file mode 100644 index 0000000..d68aa10 --- /dev/null +++ b/charts/engram/templates/serviceaccount.yaml @@ -0,0 +1,13 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "engram.serviceAccountName" . }} + labels: + {{- include "engram.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +automountServiceAccountToken: {{ .Values.serviceAccount.automountServiceAccountToken }} +{{- end }} diff --git a/charts/engram/templates/tests/test-connection.yaml b/charts/engram/templates/tests/test-connection.yaml new file mode 100644 index 0000000..b77f822 --- /dev/null +++ b/charts/engram/templates/tests/test-connection.yaml @@ -0,0 +1,35 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "{{ include "engram.fullname" . }}-test-connection" + labels: + {{- include "engram.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": test + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded +spec: + restartPolicy: Never + securityContext: + runAsNonRoot: true + runAsUser: 65534 + fsGroup: 65534 + containers: + - name: test-connection + image: curlimages/curl:8.11.1 + imagePullPolicy: IfNotPresent + command: + - sh + - -c + - | + set -e + echo "Testing connection to {{ include "engram.fullname" . }}:{{ .Values.service.port }}/health ..." + curl -fsS --max-time 10 \ + http://{{ include "engram.fullname" . }}:{{ .Values.service.port }}/health + echo "" + echo "Connection test passed." + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + capabilities: + drop: + - ALL diff --git a/charts/engram/tests/__snapshot__/.gitkeep b/charts/engram/tests/__snapshot__/.gitkeep new file mode 100644 index 0000000..fdffa2a --- /dev/null +++ b/charts/engram/tests/__snapshot__/.gitkeep @@ -0,0 +1 @@ +# placeholder diff --git a/charts/engram/tests/configmap_test.yaml b/charts/engram/tests/configmap_test.yaml new file mode 100644 index 0000000..d933a85 --- /dev/null +++ b/charts/engram/tests/configmap_test.yaml @@ -0,0 +1,74 @@ +suite: configmap tests +templates: + - templates/configmap.yaml + +tests: + - it: should create a ConfigMap + asserts: + - hasDocuments: + count: 1 + - equal: + path: kind + value: ConfigMap + + - it: should set ENGRAM_PORT from values + asserts: + - equal: + path: data.ENGRAM_PORT + value: "18080" + + - it: should set ENGRAM_CLOUD_HOST from values + asserts: + - equal: + path: data.ENGRAM_CLOUD_HOST + value: "0.0.0.0" + + - it: should override ENGRAM_PORT when set + set: + engram.port: "9090" + asserts: + - equal: + path: data.ENGRAM_PORT + value: "9090" + + - it: should override ENGRAM_CLOUD_HOST when set + set: + engram.host: "127.0.0.1" + asserts: + - equal: + path: data.ENGRAM_CLOUD_HOST + value: "127.0.0.1" + + - it: should NOT include ENGRAM_CLOUD_ALLOWED_PROJECTS when allowedProjects is empty + asserts: + - notExists: + path: data.ENGRAM_CLOUD_ALLOWED_PROJECTS + + - it: should include ENGRAM_CLOUD_ALLOWED_PROJECTS when allowedProjects is set + set: + engram.allowedProjects: "project1,project2" + asserts: + - equal: + path: data.ENGRAM_CLOUD_ALLOWED_PROJECTS + value: "project1,project2" + + - it: should NOT include ENGRAM_CLOUD_INSECURE_NO_AUTH when insecureNoAuth is false + set: + engram.insecureNoAuth: false + asserts: + - notExists: + path: data.ENGRAM_CLOUD_INSECURE_NO_AUTH + + - it: should set ENGRAM_CLOUD_INSECURE_NO_AUTH to 1 when insecureNoAuth is true + set: + engram.insecureNoAuth: true + asserts: + - equal: + path: data.ENGRAM_CLOUD_INSECURE_NO_AUTH + value: "1" + + - it: should have standard labels + asserts: + - equal: + path: metadata.labels["app.kubernetes.io/name"] + value: engram diff --git a/charts/engram/tests/deployment_test.yaml b/charts/engram/tests/deployment_test.yaml new file mode 100644 index 0000000..ee8fefb --- /dev/null +++ b/charts/engram/tests/deployment_test.yaml @@ -0,0 +1,292 @@ +suite: deployment tests +templates: + - templates/deployment.yaml + - templates/secret.yaml + - templates/configmap.yaml + +tests: + - it: should use the correct image repository and tag by default + template: templates/deployment.yaml + asserts: + - equal: + path: spec.template.spec.containers[0].image + value: "ghcr.io/gentleman-programming/engram:v1.15.10" + + - it: should set image pull policy to IfNotPresent by default + template: templates/deployment.yaml + asserts: + - equal: + path: spec.template.spec.containers[0].imagePullPolicy + value: IfNotPresent + + - it: should set container name to chart name + template: templates/deployment.yaml + asserts: + - equal: + path: spec.template.spec.containers[0].name + value: engram + + - it: should expose HTTP port 18080 + template: templates/deployment.yaml + asserts: + - equal: + path: spec.template.spec.containers[0].ports[0].containerPort + value: 18080 + - equal: + path: spec.template.spec.containers[0].ports[0].name + value: http + + - it: should run as non-root user with UID 10001 + template: templates/deployment.yaml + asserts: + - equal: + path: spec.template.spec.securityContext.runAsUser + value: 10001 + - equal: + path: spec.template.spec.securityContext.runAsGroup + value: 10001 + - equal: + path: spec.template.spec.securityContext.fsGroup + value: 10001 + + - it: should set container security context + template: templates/deployment.yaml + asserts: + - equal: + path: spec.template.spec.containers[0].securityContext.runAsNonRoot + value: true + - equal: + path: spec.template.spec.containers[0].securityContext.runAsUser + value: 10001 + - equal: + path: spec.template.spec.containers[0].securityContext.allowPrivilegeEscalation + value: false + - equal: + path: spec.template.spec.containers[0].securityContext.readOnlyRootFilesystem + value: true + + - it: should inject ENGRAM_DATABASE_URL from chart-managed secret by default + template: templates/deployment.yaml + asserts: + - equal: + path: spec.template.spec.containers[0].env[0].name + value: ENGRAM_DATABASE_URL + - equal: + path: spec.template.spec.containers[0].env[0].valueFrom.secretKeyRef.name + value: RELEASE-NAME-engram + - equal: + path: spec.template.spec.containers[0].env[0].valueFrom.secretKeyRef.key + value: ENGRAM_DATABASE_URL + + - it: should inject ENGRAM_JWT_SECRET from chart-managed secret by default + template: templates/deployment.yaml + asserts: + - equal: + path: spec.template.spec.containers[0].env[1].name + value: ENGRAM_JWT_SECRET + - equal: + path: spec.template.spec.containers[0].env[1].valueFrom.secretKeyRef.name + value: RELEASE-NAME-engram + + - it: should switch secret reference when existingSecret is set + template: templates/deployment.yaml + set: + engram.existingSecret: my-external-secret + asserts: + - equal: + path: spec.template.spec.containers[0].env[0].valueFrom.secretKeyRef.name + value: my-external-secret + - equal: + path: spec.template.spec.containers[0].env[1].valueFrom.secretKeyRef.name + value: my-external-secret + + - it: should use liveness probe on /health by default + template: templates/deployment.yaml + asserts: + - equal: + path: spec.template.spec.containers[0].livenessProbe.httpGet.path + value: /health + - equal: + path: spec.template.spec.containers[0].livenessProbe.httpGet.port + value: http + + - it: should use readiness probe on /health by default + template: templates/deployment.yaml + asserts: + - equal: + path: spec.template.spec.containers[0].readinessProbe.httpGet.path + value: /health + + - it: should use startup probe on /health with failureThreshold 30 + template: templates/deployment.yaml + asserts: + - equal: + path: spec.template.spec.containers[0].startupProbe.httpGet.path + value: /health + - equal: + path: spec.template.spec.containers[0].startupProbe.failureThreshold + value: 30 + + - it: should set args to cloud serve + template: templates/deployment.yaml + asserts: + - equal: + path: spec.template.spec.containers[0].args[0] + value: cloud + - equal: + path: spec.template.spec.containers[0].args[1] + value: serve + + - it: should set command to engram + template: templates/deployment.yaml + asserts: + - equal: + path: spec.template.spec.containers[0].command[0] + value: engram + + - it: should omit replicas field when HPA is enabled + template: templates/deployment.yaml + set: + autoscaling.enabled: true + asserts: + - notExists: + path: spec.replicas + + - it: should set replicas when HPA is disabled + template: templates/deployment.yaml + set: + replicaCount: 3 + asserts: + - equal: + path: spec.replicas + value: 3 + + - it: should apply deployment annotations + template: templates/deployment.yaml + set: + deploymentAnnotations: + my-annotation: my-value + asserts: + - equal: + path: metadata.annotations["my-annotation"] + value: my-value + + - it: should apply pod annotations + template: templates/deployment.yaml + set: + podAnnotations: + pod-annotation: pod-value + asserts: + - equal: + path: spec.template.metadata.annotations["pod-annotation"] + value: pod-value + + - it: should use custom image tag when overridden + template: templates/deployment.yaml + set: + image.tag: v2.0.0 + asserts: + - equal: + path: spec.template.spec.containers[0].image + value: "ghcr.io/gentleman-programming/engram:v2.0.0" + + - it: should reference chart configmap via envFrom + template: templates/deployment.yaml + asserts: + - equal: + path: spec.template.spec.containers[0].envFrom[0].configMapRef.name + value: RELEASE-NAME-engram + + - it: should inject ENGRAM_CLOUD_ADMIN env var from secret (optional) + template: templates/deployment.yaml + asserts: + - equal: + path: spec.template.spec.containers[0].env[2].name + value: ENGRAM_CLOUD_ADMIN + - equal: + path: spec.template.spec.containers[0].env[2].valueFrom.secretKeyRef.key + value: ENGRAM_CLOUD_ADMIN + - equal: + path: spec.template.spec.containers[0].env[2].valueFrom.secretKeyRef.optional + value: true + + - it: should inject ENGRAM_CLOUD_TOKEN env var from secret (optional) + template: templates/deployment.yaml + asserts: + - equal: + path: spec.template.spec.containers[0].env[3].name + value: ENGRAM_CLOUD_TOKEN + - equal: + path: spec.template.spec.containers[0].env[3].valueFrom.secretKeyRef.key + value: ENGRAM_CLOUD_TOKEN + - equal: + path: spec.template.spec.containers[0].env[3].valueFrom.secretKeyRef.optional + value: true + + - it: should use existingSecret name for ENGRAM_CLOUD_ADMIN env var + template: templates/deployment.yaml + set: + engram.existingSecret: my-external-secret + asserts: + - equal: + path: spec.template.spec.containers[0].env[2].valueFrom.secretKeyRef.name + value: my-external-secret + + - it: should mount /tmp emptyDir volume by default + template: templates/deployment.yaml + asserts: + - contains: + path: spec.template.spec.volumes + content: + name: tmp + emptyDir: {} + - contains: + path: spec.template.spec.containers[0].volumeMounts + content: + name: tmp + mountPath: /tmp + + - it: should NOT mount /tmp when tmpVolume.enabled is false + template: templates/deployment.yaml + set: + tmpVolume.enabled: false + asserts: + - notContains: + path: spec.template.spec.containers[0].volumeMounts + content: + name: tmp + mountPath: /tmp + + - it: should add wait-for-postgresql init container when postgresql.enabled is true + template: templates/deployment.yaml + asserts: + - equal: + path: spec.template.spec.initContainers[0].name + value: wait-for-postgresql + - equal: + path: spec.template.spec.initContainers[0].image + value: busybox:1.36 + + - it: should NOT add wait-for-postgresql init container when postgresql.enabled is false + template: templates/deployment.yaml + set: + postgresql.enabled: false + engram.databaseUrl: "postgres://user:pass@external:5432/db" + asserts: + - notExists: + path: spec.template.spec.initContainers + + - it: should NOT add wait-for-postgresql init container when waitForReady.disabled is true + template: templates/deployment.yaml + set: + postgresql.waitForReady.disabled: true + asserts: + - notExists: + path: spec.template.spec.initContainers + + - it: should set runAsNonRoot on podSecurityContext by default + template: templates/deployment.yaml + asserts: + - equal: + path: spec.template.spec.securityContext.runAsNonRoot + value: true diff --git a/charts/engram/tests/hpa_test.yaml b/charts/engram/tests/hpa_test.yaml new file mode 100644 index 0000000..6e34977 --- /dev/null +++ b/charts/engram/tests/hpa_test.yaml @@ -0,0 +1,60 @@ +suite: HPA tests +templates: + - templates/hpa.yaml + +tests: + - it: should NOT create an HPA when autoscaling is disabled (default) + asserts: + - hasDocuments: + count: 0 + + - it: should create an HPA when autoscaling is enabled + set: + autoscaling.enabled: true + asserts: + - hasDocuments: + count: 1 + - equal: + path: kind + value: HorizontalPodAutoscaler + + - it: should set minReplicas and maxReplicas from values + set: + autoscaling.enabled: true + autoscaling.minReplicas: 2 + autoscaling.maxReplicas: 8 + asserts: + - equal: + path: spec.minReplicas + value: 2 + - equal: + path: spec.maxReplicas + value: 8 + + - it: should set targetCPUUtilizationPercentage from values + set: + autoscaling.enabled: true + autoscaling.targetCPUUtilizationPercentage: 70 + asserts: + - equal: + path: spec.metrics[0].resource.target.averageUtilization + value: 70 + + - it: should target the correct deployment + set: + autoscaling.enabled: true + asserts: + - equal: + path: spec.scaleTargetRef.name + value: RELEASE-NAME-engram + - equal: + path: spec.scaleTargetRef.kind + value: Deployment + + - it: should have standard labels + set: + autoscaling.enabled: true + asserts: + - equal: + path: metadata.labels["app.kubernetes.io/name"] + value: engram diff --git a/charts/engram/tests/ingress_test.yaml b/charts/engram/tests/ingress_test.yaml new file mode 100644 index 0000000..c454c69 --- /dev/null +++ b/charts/engram/tests/ingress_test.yaml @@ -0,0 +1,69 @@ +suite: ingress tests +templates: + - templates/ingress.yaml + +tests: + - it: should not render Ingress when disabled by default + asserts: + - hasDocuments: + count: 0 + + - it: should render Ingress when enabled + set: + ingress.enabled: true + asserts: + - hasDocuments: + count: 1 + - equal: + path: kind + value: Ingress + + - it: should set default host from values + set: + ingress.enabled: true + asserts: + - equal: + path: spec.rules[0].host + value: engram.local + + - it: should point backend to the chart service on port 18080 + set: + ingress.enabled: true + asserts: + - equal: + path: spec.rules[0].http.paths[0].backend.service.name + value: RELEASE-NAME-engram + - equal: + path: spec.rules[0].http.paths[0].backend.service.port.number + value: 18080 + + - it: should set ingressClassName when provided + set: + ingress.enabled: true + ingress.className: nginx + asserts: + - equal: + path: spec.ingressClassName + value: nginx + + - it: should render TLS block when configured + set: + ingress.enabled: true + ingress.tls: + - secretName: engram-tls + hosts: + - engram.local + asserts: + - equal: + path: spec.tls[0].secretName + value: engram-tls + + - it: should apply ingress annotations + set: + ingress.enabled: true + ingress.annotations: + nginx.ingress.kubernetes.io/rewrite-target: / + asserts: + - equal: + path: metadata.annotations["nginx.ingress.kubernetes.io/rewrite-target"] + value: / diff --git a/charts/engram/tests/pdb_test.yaml b/charts/engram/tests/pdb_test.yaml new file mode 100644 index 0000000..66a0db4 --- /dev/null +++ b/charts/engram/tests/pdb_test.yaml @@ -0,0 +1,66 @@ +suite: PDB tests +templates: + - templates/pdb.yaml + +tests: + - it: should NOT create a PDB when disabled (default) + asserts: + - hasDocuments: + count: 0 + + - it: should create a PDB when enabled + set: + podDisruptionBudget.enabled: true + asserts: + - hasDocuments: + count: 1 + - equal: + path: kind + value: PodDisruptionBudget + + - it: should use minAvailable by default when PDB is enabled + set: + podDisruptionBudget.enabled: true + podDisruptionBudget.minAvailable: 1 + asserts: + - equal: + path: spec.minAvailable + value: 1 + - notExists: + path: spec.maxUnavailable + + - it: should use maxUnavailable when set (and default minAvailable applies) + set: + podDisruptionBudget.enabled: true + podDisruptionBudget.maxUnavailable: 1 + asserts: + - equal: + path: spec.maxUnavailable + value: 1 + - notExists: + path: spec.minAvailable + + - it: should support percentage string for maxUnavailable + set: + podDisruptionBudget.enabled: true + podDisruptionBudget.maxUnavailable: "25%" + asserts: + - equal: + path: spec.maxUnavailable + value: "25%" + + - it: should match pod selector labels + set: + podDisruptionBudget.enabled: true + asserts: + - equal: + path: spec.selector.matchLabels["app.kubernetes.io/name"] + value: engram + + - it: should have standard labels + set: + podDisruptionBudget.enabled: true + asserts: + - equal: + path: metadata.labels["app.kubernetes.io/name"] + value: engram diff --git a/charts/engram/tests/postgresql_test.yaml b/charts/engram/tests/postgresql_test.yaml new file mode 100644 index 0000000..dc76931 --- /dev/null +++ b/charts/engram/tests/postgresql_test.yaml @@ -0,0 +1,222 @@ +suite: postgresql internal templates tests +templates: + - templates/secret.yaml + - templates/deployment.yaml + - templates/postgresql/secret.yaml + - templates/postgresql/service.yaml + - templates/postgresql/statefulset.yaml + +tests: + # ── DSN auto-generation ──────────────────────────────────────────────────── + + - it: should auto-build ENGRAM_DATABASE_URL from internal postgresql when postgresql.enabled=true + template: templates/secret.yaml + set: + postgresql.enabled: true + postgresql.auth.username: engram + postgresql.auth.password: supersecret + postgresql.auth.database: engram_cloud + engram.jwtSecret: my-jwt-secret + asserts: + - isKind: + of: Secret + - matchRegex: + path: data.ENGRAM_DATABASE_URL + pattern: "^.+$" + + - it: should prefer engram.databaseUrl over auto-generated DSN when both are set + template: templates/secret.yaml + set: + postgresql.enabled: true + postgresql.auth.username: engram + postgresql.auth.password: ignored + postgresql.auth.database: engram_cloud + engram.databaseUrl: "postgres://custom:custom@my-db:5432/mydb?sslmode=require" + engram.jwtSecret: my-jwt-secret + asserts: + - isKind: + of: Secret + - equal: + path: data.ENGRAM_DATABASE_URL + value: "cG9zdGdyZXM6Ly9jdXN0b206Y3VzdG9tQG15LWRiOjU0MzIvbXlkYj9zc2xtb2RlPXJlcXVpcmU=" + + - it: should not render engram secret when existingSecret is set + template: templates/secret.yaml + set: + postgresql.enabled: true + engram.existingSecret: my-external-secret + engram.jwtSecret: my-jwt-secret + asserts: + - hasDocuments: + count: 0 + + - it: should reference correct postgresql host in auto-generated DSN + template: templates/secret.yaml + release: + name: my-release + set: + postgresql.enabled: true + postgresql.auth.username: engram + postgresql.auth.password: testpass + postgresql.auth.database: engram_cloud + engram.jwtSecret: my-jwt-secret + engram.databaseUrl: "" + asserts: + - isKind: + of: Secret + - equal: + path: data.ENGRAM_DATABASE_URL + value: "cG9zdGdyZXM6Ly9lbmdyYW06dGVzdHBhc3NAbXktcmVsZWFzZS1wb3N0Z3Jlc3FsOjU0MzIvZW5ncmFtX2Nsb3VkP3NzbG1vZGU9ZGlzYWJsZQ==" + + # ── PostgreSQL Secret ────────────────────────────────────────────────────── + + - it: should render postgresql secret when postgresql.enabled=true + template: templates/postgresql/secret.yaml + set: + postgresql.enabled: true + postgresql.auth.password: mysecretpassword + engram.jwtSecret: jwt + asserts: + - isKind: + of: Secret + - equal: + path: data.password + value: "bXlzZWNyZXRwYXNzd29yZA==" + + - it: should not render postgresql secret when postgresql.enabled=false + template: templates/postgresql/secret.yaml + set: + postgresql.enabled: false + engram.jwtSecret: jwt + asserts: + - hasDocuments: + count: 0 + + # ── PostgreSQL Service ───────────────────────────────────────────────────── + + - it: should render postgresql service when postgresql.enabled=true + template: templates/postgresql/service.yaml + release: + name: test + set: + postgresql.enabled: true + postgresql.service.port: 5432 + engram.jwtSecret: jwt + asserts: + - isKind: + of: Service + - equal: + path: metadata.name + value: test-postgresql + - equal: + path: spec.ports[0].port + value: 5432 + + - it: should not render postgresql service when postgresql.enabled=false + template: templates/postgresql/service.yaml + set: + postgresql.enabled: false + engram.jwtSecret: jwt + asserts: + - hasDocuments: + count: 0 + + # ── PostgreSQL StatefulSet ───────────────────────────────────────────────── + + - it: should render postgresql statefulset when postgresql.enabled=true + template: templates/postgresql/statefulset.yaml + release: + name: test + set: + postgresql.enabled: true + postgresql.image.registry: docker.io + postgresql.image.repository: postgres + postgresql.image.tag: "16-alpine" + postgresql.auth.username: engram + postgresql.auth.database: engram_cloud + engram.jwtSecret: jwt + asserts: + - isKind: + of: StatefulSet + - equal: + path: metadata.name + value: test-postgresql + - equal: + path: spec.template.spec.containers[0].image + value: "docker.io/postgres:16-alpine" + + - it: should set POSTGRES_USER and POSTGRES_DB env vars + template: templates/postgresql/statefulset.yaml + set: + postgresql.enabled: true + postgresql.auth.username: myuser + postgresql.auth.database: mydb + engram.jwtSecret: jwt + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: POSTGRES_USER + value: "myuser" + - contains: + path: spec.template.spec.containers[0].env + content: + name: POSTGRES_DB + value: "mydb" + + - it: should reference postgresql secret for POSTGRES_PASSWORD + template: templates/postgresql/statefulset.yaml + release: + name: rel + set: + postgresql.enabled: true + postgresql.auth.username: engram + postgresql.auth.database: engram_cloud + engram.jwtSecret: jwt + asserts: + - equal: + path: spec.template.spec.containers[0].env[1].valueFrom.secretKeyRef.name + value: rel-postgresql + - equal: + path: spec.template.spec.containers[0].env[1].valueFrom.secretKeyRef.key + value: password + + - it: should create volumeClaimTemplate when persistence.enabled=true + template: templates/postgresql/statefulset.yaml + set: + postgresql.enabled: true + postgresql.persistence.enabled: true + postgresql.persistence.size: 5Gi + postgresql.persistence.accessMode: ReadWriteOnce + engram.jwtSecret: jwt + asserts: + - isNotEmpty: + path: spec.volumeClaimTemplates + - equal: + path: spec.volumeClaimTemplates[0].spec.resources.requests.storage + value: "5Gi" + + - it: should use emptyDir when persistence.enabled=false + template: templates/postgresql/statefulset.yaml + set: + postgresql.enabled: true + postgresql.persistence.enabled: false + engram.jwtSecret: jwt + asserts: + - isNull: + path: spec.volumeClaimTemplates + - contains: + path: spec.template.spec.volumes + content: + name: data + emptyDir: {} + + - it: should not render postgresql statefulset when postgresql.enabled=false + template: templates/postgresql/statefulset.yaml + set: + postgresql.enabled: false + engram.jwtSecret: jwt + asserts: + - hasDocuments: + count: 0 + diff --git a/charts/engram/tests/secret_test.yaml b/charts/engram/tests/secret_test.yaml new file mode 100644 index 0000000..771f678 --- /dev/null +++ b/charts/engram/tests/secret_test.yaml @@ -0,0 +1,84 @@ +suite: secret tests +templates: + - templates/secret.yaml + +tests: + - it: should create a Secret when existingSecret is empty + asserts: + - hasDocuments: + count: 1 + - equal: + path: kind + value: Secret + - equal: + path: type + value: Opaque + + - it: should NOT create a Secret when existingSecret is set + set: + engram.existingSecret: my-external-secret + asserts: + - hasDocuments: + count: 0 + + - it: should contain ENGRAM_DATABASE_URL key + set: + engram.databaseUrl: "postgres://user:pass@host:5432/db" + engram.jwtSecret: "test-secret" + asserts: + - isNotNullOrEmpty: + path: data["ENGRAM_DATABASE_URL"] + + - it: should contain ENGRAM_JWT_SECRET key + set: + engram.databaseUrl: "postgres://user:pass@host:5432/db" + engram.jwtSecret: "test-secret" + asserts: + - isNotNullOrEmpty: + path: data["ENGRAM_JWT_SECRET"] + + - it: should base64-encode the databaseUrl value + set: + engram.databaseUrl: "postgres://user:pass@host:5432/db" + asserts: + - equal: + path: data["ENGRAM_DATABASE_URL"] + value: "cG9zdGdyZXM6Ly91c2VyOnBhc3NAaG9zdDo1NDMyL2Ri" + + - it: should have standard labels + asserts: + - equal: + path: metadata.labels["app.kubernetes.io/name"] + value: engram + + - it: should include ENGRAM_CLOUD_ADMIN key when adminToken is set + set: + engram.adminToken: "my-admin-token" + asserts: + - isNotNullOrEmpty: + path: data["ENGRAM_CLOUD_ADMIN"] + + - it: should NOT include ENGRAM_CLOUD_ADMIN key when adminToken is empty + asserts: + - notExists: + path: data["ENGRAM_CLOUD_ADMIN"] + + - it: should include ENGRAM_CLOUD_TOKEN key when cloudToken is set + set: + engram.cloudToken: "my-cloud-token" + asserts: + - isNotNullOrEmpty: + path: data["ENGRAM_CLOUD_TOKEN"] + + - it: should NOT include ENGRAM_CLOUD_TOKEN key when cloudToken is empty + asserts: + - notExists: + path: data["ENGRAM_CLOUD_TOKEN"] + + - it: should base64-encode adminToken + set: + engram.adminToken: "supersecretadmin" + asserts: + - equal: + path: data["ENGRAM_CLOUD_ADMIN"] + value: "c3VwZXJzZWNyZXRhZG1pbg==" diff --git a/charts/engram/tests/service_test.yaml b/charts/engram/tests/service_test.yaml new file mode 100644 index 0000000..6f7a4db --- /dev/null +++ b/charts/engram/tests/service_test.yaml @@ -0,0 +1,62 @@ +suite: service tests +templates: + - templates/service.yaml + +tests: + - it: should create a Service by default + asserts: + - hasDocuments: + count: 1 + - equal: + path: kind + value: Service + + - it: should set type to ClusterIP by default + asserts: + - equal: + path: spec.type + value: ClusterIP + + - it: should expose port 18080 by default + asserts: + - equal: + path: spec.ports[0].port + value: 18080 + + - it: should target port http + asserts: + - equal: + path: spec.ports[0].targetPort + value: http + + - it: should set port protocol to TCP + asserts: + - equal: + path: spec.ports[0].protocol + value: TCP + + - it: should apply service annotations + set: + service.annotations: + service.beta.kubernetes.io/aws-load-balancer-type: external + asserts: + - equal: + path: metadata.annotations["service.beta.kubernetes.io/aws-load-balancer-type"] + value: external + + - it: should support NodePort type + set: + service.type: NodePort + asserts: + - equal: + path: spec.type + value: NodePort + + - it: should have correct selector labels + asserts: + - equal: + path: spec.selector["app.kubernetes.io/name"] + value: engram + - equal: + path: spec.selector["app.kubernetes.io/instance"] + value: RELEASE-NAME diff --git a/charts/engram/tests/serviceaccount_test.yaml b/charts/engram/tests/serviceaccount_test.yaml new file mode 100644 index 0000000..e61f037 --- /dev/null +++ b/charts/engram/tests/serviceaccount_test.yaml @@ -0,0 +1,62 @@ +suite: service account tests +templates: + - templates/serviceaccount.yaml + +tests: + - it: should create a ServiceAccount by default + asserts: + - hasDocuments: + count: 1 + - equal: + path: kind + value: ServiceAccount + - equal: + path: metadata.name + value: RELEASE-NAME-engram + + - it: should not create a ServiceAccount when disabled + set: + serviceAccount.create: false + asserts: + - hasDocuments: + count: 0 + + - it: should use custom service account name when provided + set: + serviceAccount.name: my-custom-sa + asserts: + - equal: + path: metadata.name + value: my-custom-sa + + - it: should add annotations to ServiceAccount + set: + serviceAccount.annotations: + eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/engram-role + asserts: + - equal: + path: metadata.annotations["eks.amazonaws.com/role-arn"] + value: arn:aws:iam::123456789012:role/engram-role + + - it: should not automount service account token by default + asserts: + - equal: + path: automountServiceAccountToken + value: false + + - it: should enable automounting when configured + set: + serviceAccount.automountServiceAccountToken: true + asserts: + - equal: + path: automountServiceAccountToken + value: true + + - it: should have standard labels + asserts: + - equal: + path: metadata.labels["app.kubernetes.io/name"] + value: engram + - equal: + path: metadata.labels["app.kubernetes.io/instance"] + value: RELEASE-NAME diff --git a/charts/engram/values.schema.json b/charts/engram/values.schema.json new file mode 100644 index 0000000..acf77d2 --- /dev/null +++ b/charts/engram/values.schema.json @@ -0,0 +1,133 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "engram Helm chart values", + "description": "Configuration values for the Engram Cloud Helm chart.", + "type": "object", + "properties": { + "replicaCount": { + "type": "integer", + "minimum": 1, + "description": "Number of Engram Cloud pod replicas." + }, + "image": { + "type": "object", + "description": "Container image configuration.", + "properties": { + "repository": { "type": "string", "minLength": 1 }, + "tag": { "type": "string" }, + "pullPolicy": { "type": "string", "enum": ["Always", "IfNotPresent", "Never"] } + }, + "required": ["repository"] + }, + "imagePullSecrets": { "type": "array" }, + "nameOverride": { "type": "string" }, + "fullnameOverride": { "type": "string" }, + "serviceAccount": { + "type": "object", + "properties": { + "create": { "type": "boolean" }, + "annotations": { "type": "object" }, + "name": { "type": "string" }, + "automountServiceAccountToken": { "type": "boolean" } + } + }, + "podAnnotations": { "type": "object" }, + "podLabels": { "type": "object" }, + "deploymentAnnotations": { "type": "object" }, + "podSecurityContext": { "type": "object" }, + "securityContext": { "type": "object" }, + "engram": { + "type": "object", + "description": "Engram Cloud application configuration.", + "properties": { + "databaseUrl": { "type": "string" }, + "jwtSecret": { "type": "string" }, + "allowedProjects": { "type": "string" }, + "host": { "type": "string" }, + "port": { "type": "string" }, + "insecureNoAuth": { "type": "boolean" }, + "adminToken": { "type": "string", "description": "Admin token for /dashboard/admin routes (ENGRAM_CLOUD_ADMIN)." }, + "cloudToken": { "type": "string", "description": "Bearer token for authentication (ENGRAM_CLOUD_TOKEN)." }, + "existingSecret": { "type": "string" } + } + }, + "env": { "type": "array" }, + "envFrom": { "type": "array" }, + "service": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["ClusterIP", "NodePort", "LoadBalancer"] + }, + "port": { + "type": "integer", + "minimum": 1, + "maximum": 65535 + }, + "annotations": { "type": "object" } + } + }, + "ingress": { + "type": "object", + "properties": { + "enabled": { "type": "boolean" }, + "className": { "type": "string" }, + "annotations": { "type": "object" }, + "hosts": { "type": "array" }, + "tls": { "type": "array" } + } + }, + "livenessProbe": { "type": "object" }, + "readinessProbe": { "type": "object" }, + "startupProbe": { "type": "object" }, + "resources": { "type": "object" }, + "nodeSelector": { "type": "object" }, + "tolerations": { "type": "array" }, + "affinity": { "type": "object" }, + "topologySpreadConstraints": { "type": "array" }, + "tmpVolume": { + "type": "object", + "description": "Built-in /tmp emptyDir volume for readOnlyRootFilesystem compatibility.", + "properties": { + "enabled": { "type": "boolean" }, + "medium": { "type": "string" }, + "sizeLimit": { "type": "string" } + } + }, + "networkPolicy": { + "type": "object", + "description": "Kubernetes NetworkPolicy for Engram pods.", + "properties": { + "enabled": { "type": "boolean" } + } + }, + "extraVolumes": { "type": "array" }, + "extraVolumeMounts": { "type": "array" }, + "extraContainers": { "type": "array" }, + "extraObjects": { "type": "array" }, + "podDisruptionBudget": { + "type": "object", + "properties": { + "enabled": { "type": "boolean" }, + "minAvailable": { "type": "integer", "minimum": 0 }, + "maxUnavailable": { + "oneOf": [ + { "type": "integer", "minimum": 0 }, + { "type": "string", "pattern": "^[0-9]+%$" }, + { "type": "null" } + ] + } + } + }, + "autoscaling": { + "type": "object", + "properties": { + "enabled": { "type": "boolean" }, + "minReplicas": { "type": "integer", "minimum": 1 }, + "maxReplicas": { "type": "integer", "minimum": 1 }, + "targetCPUUtilizationPercentage": { "type": "integer", "minimum": 1, "maximum": 100 } + } + } + } +} diff --git a/charts/engram/values.yaml b/charts/engram/values.yaml new file mode 100644 index 0000000..bd4c2f0 --- /dev/null +++ b/charts/engram/values.yaml @@ -0,0 +1,305 @@ +# Default values for engram. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +# -- Number of Engram Cloud replicas +replicaCount: 1 + +# -- Image registry +image: + # -- Image repository. Published to GHCR via cloud-image.yml workflow + repository: ghcr.io/gentleman-programming/engram + pullPolicy: IfNotPresent + # -- Image tag. Overrides the image tag whose default is the chart appVersion. + # Managed by updatecli monitoring Gentleman-Programming/engram releases. + tag: "v1.15.10" + +# -- Registry secret names as an array +imagePullSecrets: [] + +# -- String to partially override engram.fullname template (will maintain the release name) +nameOverride: "" + +# -- String to fully override engram.fullname template +fullnameOverride: "" + +# -- Enable creation of ServiceAccount +serviceAccount: + # -- Specifies whether a service account should be created + create: true + # -- Annotations to add to the service account (e.g. IAM role ARN for IRSA) + annotations: {} + # -- The name of the service account to use. + # If not set and create is true, a name is generated using the fullname template + name: "" + # -- Specifies if you don't want the kubelet to automatically mount + # a ServiceAccount's API credentials + automountServiceAccountToken: false + +# -- Pod annotations +podAnnotations: {} + +# -- Pod labels +podLabels: {} + +# -- Deployment annotations +deploymentAnnotations: {} + +# -- Privilege and access control settings for the Pod (pod-level) +# Engram Cloud runs as UID=10001 (engram user) +podSecurityContext: + fsGroup: 10001 + runAsGroup: 10001 + runAsUser: 10001 + runAsNonRoot: true + +# -- Privilege and access control settings for the container +securityContext: + runAsNonRoot: true + runAsUser: 10001 + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + capabilities: + drop: + - ALL + +# -- Engram Cloud configuration +engram: + # -- Required. PostgreSQL DSN e.g. postgres://user:pass@host:5432/engram_cloud?sslmode=disable + databaseUrl: "" + # -- Required. JWT signing secret used to sign and verify tokens + jwtSecret: "" + # -- Required. Comma-separated list of allowed project names that Engram will serve. + # Must be set even when insecureNoAuth is true. Example: "project1,project2" + allowedProjects: "" + # -- Bind address for the HTTP server + host: "0.0.0.0" + # -- HTTP listen port + port: "18080" + # -- Disable authentication. DEV ONLY — never enable in production + insecureNoAuth: false + # -- Admin token for the dashboard /admin routes (ENGRAM_CLOUD_ADMIN). + # When set, grants access to /dashboard/admin. Store this in a Secret in production. + adminToken: "" + # -- Bearer token for client authentication (ENGRAM_CLOUD_TOKEN). + # REQUIRED when insecureNoAuth=false (production/authenticated mode). + # Must be an unguessable random string — use `openssl rand -hex 32`. + # Must be EMPTY when insecureNoAuth=true (dev-only insecure mode; the two are mutually exclusive). + cloudToken: "" + # -- Name of an existing Secret containing ENGRAM_DATABASE_URL and ENGRAM_JWT_SECRET. + # When set, the chart does NOT create a Secret — use this for External Secrets Operator, + # Sealed Secrets, Vault Agent, or any external secrets manager. + existingSecret: "" + +# -- Environment variables to configure application +env: [] +# - name: MY_VAR +# value: "my-value" + +# -- Variables from ConfigMap or Secret +envFrom: [] +# - configMapRef: +# name: my-configmap + +# -- Service configuration +service: + # -- Kubernetes Service type + type: ClusterIP + # -- Service port (must match engram.port) + port: 18080 + # -- Annotations to add to the Service + annotations: {} + +# -- Ingress configuration +ingress: + # -- Enable ingress + enabled: false + # -- Ingress class name (e.g. nginx, traefik) + className: "" + # -- Ingress annotations + annotations: {} + # -- Ingress hosts + hosts: + - host: engram.local + paths: + - path: / + pathType: ImplementationSpecific + # -- Ingress TLS configuration + tls: [] + # - secretName: engram-tls + # hosts: + # - engram.local + +# -- Configure liveness probe +# Ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/ +livenessProbe: + httpGet: + path: /health + port: http + initialDelaySeconds: 10 + periodSeconds: 10 + +# -- Configure readiness probe +readinessProbe: + httpGet: + path: /health + port: http + initialDelaySeconds: 5 + periodSeconds: 5 + +# -- Configure startup probe. Gives the container up to 300s (30 × 10s) to start, +# allowing time for PostgreSQL migrations on cold start +startupProbe: + httpGet: + path: /health + port: http + periodSeconds: 10 + failureThreshold: 30 + +# -- Resource requests and limits +# Ref: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ +resources: {} +# requests: +# cpu: 100m +# memory: 128Mi +# limits: +# cpu: 500m +# memory: 256Mi + +# -- Node selector for pod scheduling +nodeSelector: {} + +# -- Tolerations for pod scheduling +tolerations: [] + +# -- Affinity rules for pod scheduling +affinity: {} + +# -- Topology spread constraints for pod distribution across zones/nodes +topologySpreadConstraints: [] + +# -- Built-in writable /tmp volume. +# Required when securityContext.readOnlyRootFilesystem=true (chart default). +# The Engram process needs /tmp for temporary file operations. +# Disable only if you provide your own /tmp via extraVolumes/extraVolumeMounts. +tmpVolume: + # -- Enable the built-in /tmp emptyDir volume + enabled: true + # -- Memory-backed storage medium (leave empty for disk) + medium: "" + # -- Size limit for the /tmp volume + sizeLimit: "" + +# -- NetworkPolicy configuration +networkPolicy: + # -- Enable NetworkPolicy. When enabled, restricts ingress/egress for Engram pods: + # - Ingress: allowed on service port from ingress controller pods (if ingress.enabled) + # and any pod in the release namespace + # - Egress: allowed to PostgreSQL on port 5432, DNS on port 53, and Kubernetes API + # Leave disabled if your cluster uses a different network security model. + enabled: false + +# -- Extra volumes to add to the pod +extraVolumes: [] +# - name: tmp +# emptyDir: {} + +# -- Extra volume mounts to add to the engram container +extraVolumeMounts: [] +# - name: tmp +# mountPath: /tmp + +# -- Extra sidecar containers to add to the pod +extraContainers: [] + +# -- Init containers to add to the pod (appended after the auto-generated wait-for-postgresql init container) +initContainers: [] + +# -- Extra Kubernetes objects to deploy with this release. +# Useful for ExternalSecret, SealedSecret, NetworkPolicy, ServiceMonitor, etc. +# Each entry is rendered via tpl, so Helm template functions are supported. +extraObjects: [] +# - apiVersion: external-secrets.io/v1beta1 +# kind: ExternalSecret +# metadata: +# name: engram-secret +# spec: +# refreshInterval: 1h +# secretStoreRef: +# name: my-store +# kind: SecretStore +# target: +# name: engram-existing-secret +# data: +# - secretKey: ENGRAM_DATABASE_URL +# remoteRef: +# key: engram/database-url +# - secretKey: ENGRAM_JWT_SECRET +# remoteRef: +# key: engram/jwt-secret + +# -- PodDisruptionBudget configuration +podDisruptionBudget: + # -- Enable PodDisruptionBudget + enabled: false + # -- Minimum number of pods that must be available during voluntary disruptions. + # Mutually exclusive with maxUnavailable — only one may be set. + minAvailable: 1 + # -- Maximum number of pods that may be unavailable during voluntary disruptions. + # Mutually exclusive with minAvailable — only one may be set. Set to null to use minAvailable. + maxUnavailable: ~ + +# -- HorizontalPodAutoscaler configuration +autoscaling: + # -- Enable HorizontalPodAutoscaler + enabled: false + # -- Minimum number of replicas + minReplicas: 1 + # -- Maximum number of replicas + maxReplicas: 10 + # -- Target CPU utilization percentage + targetCPUUtilizationPercentage: 80 + +# -- Bundled PostgreSQL (internal templates using official postgres image). +# Enable to deploy a PostgreSQL StatefulSet alongside Engram Cloud. +# For production, set postgresql.enabled=false and configure an external PostgreSQL +# via engram.databaseUrl or engram.existingSecret. +postgresql: + # -- Enable the bundled PostgreSQL deployment. + # When true, ENGRAM_DATABASE_URL is auto-configured from auth values below. + # When false, you must provide engram.databaseUrl or engram.existingSecret. + enabled: true + # -- Wait for PostgreSQL to be ready before starting Engram. + # Adds an init container that polls port 5432 with nc. + # Set disabled: true only if you manage readiness externally. + waitForReady: + disabled: false + # -- PostgreSQL container image (official Docker Hub image) + image: + registry: docker.io + repository: postgres + tag: "16-alpine" + pullPolicy: IfNotPresent + auth: + # -- PostgreSQL username for Engram Cloud + username: engram + # -- PostgreSQL password. Change in production! + password: "engram_change_me" + # -- PostgreSQL database name + database: engram_cloud + # -- Service configuration + service: + # -- PostgreSQL service port + port: 5432 + # -- Persistence configuration + persistence: + # -- Enable persistent storage for PostgreSQL data + enabled: true + # -- Storage size + size: 1Gi + # -- Storage class (empty = cluster default) + storageClass: "" + # -- Access mode + accessMode: ReadWriteOnce + # -- Resource requests and limits for PostgreSQL container + resources: {} diff --git a/context7.json b/context7.json new file mode 100644 index 0000000..f2e8605 --- /dev/null +++ b/context7.json @@ -0,0 +1,4 @@ +{ + "url": "https://context7.com/devops-ia/helm-engram", + "public_key": "pk_poNqP0E20Jn62M589pdY6" +} \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..4b26df9 --- /dev/null +++ b/package.json @@ -0,0 +1,16 @@ +{ + "name": "helm-engram", + "private": true, + "scripts": { + "lint": "helm lint charts/engram", + "lint:full": "helm lint charts/engram -f charts/engram/ci/full-values.yaml", + "test": "helm unittest charts/engram/", + "test:verbose": "helm unittest -v charts/engram/", + "test:update-snapshot": "helm unittest --update-snapshot charts/engram/", + "docs": "helm-docs --chart-search-root charts", + "template": "helm template engram charts/engram -f charts/engram/ci/minimal-values.yaml", + "template:full": "helm template engram charts/engram -f charts/engram/ci/full-values.yaml", + "template:ingress": "helm template engram charts/engram -f charts/engram/ci/ingress-values.yaml", + "version:bump": "node -e \"const fs=require('fs');const f='charts/engram/Chart.yaml';const c=fs.readFileSync(f,'utf8');const m=c.match(/^version:\\s*(\\d+)\\.(\\d+)\\.(\\d+)/m);if(!m)throw new Error('version not found');const[,ma,mi,pa]=m;const nv=ma+'.'+mi+'.'+(+pa+1);const nc=c.replace(/^version:.*/m,'version: '+nv);fs.writeFileSync(f,nc);console.log('Chart version bumped to '+nv);\"" + } +}