diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..84f3410 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,19 @@ +root = true + +[Earthfile] +end_of_line = lf +charset = utf-8 +indent_style = space +indent_size = 2 + +[*.tpl] +end_of_line = lf +charset = utf-8 +indent_style = space +indent_size = 2 + +[*.{yaml,yml}] +end_of_line = lf +charset = utf-8 +indent_style = space +indent_size = 2 \ No newline at end of file diff --git a/.github/labeler.yml b/.github/labeler.yml new file mode 100644 index 0000000..ff7e750 --- /dev/null +++ b/.github/labeler.yml @@ -0,0 +1,12 @@ +docs: + - changed-files: + - any-glob-to-any-file: "**/*.md" + +feature: + - head-branch: ["^feat", "feat"] + +bug: + - head-branch: ["^fix", "fix"] + +release: + - base-branch: "main" diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml deleted file mode 100644 index 6ffebeb..0000000 --- a/.github/workflows/main.yaml +++ /dev/null @@ -1,55 +0,0 @@ -name: Default -on: - merge_group: - push: - branches: - - main - - releases/* - pull_request: - types: [ assigned, opened, synchronize, reopened, labeled ] - - -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true - -jobs: - PR: - if: github.event_name == 'pull_request' - name: Check PR Title - runs-on: ubuntu-latest - permissions: - statuses: write - steps: - - uses: amannn/action-semantic-pull-request@v5 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - Dirty: - runs-on: "ubuntu-latest" - steps: - - uses: 'actions/checkout@v4' - with: - fetch-depth: 0 - - uses: earthly/actions-setup@v1 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - version: "latest" - use-cache: true - - run: > - earthly - --allow-privileged - --secret SPEAKEASY_API_KEY=$SPEAKEASY_API_KEY - ${{ contains(github.event.pull_request.labels.*.name, 'no-cache') && '--no-cache' || '' }} - +pre-commit - env: - SPEAKEASY_API_KEY: ${{ secrets.SPEAKEASY_API_KEY }} - - name: Get changed files - id: changed-files - shell: bash - run: | - hasChanged=$(git status --porcelain) - if (( $(echo ${#hasChanged}) != 0 )); then - git status - echo "There are changes in the repository" - exit 1 - fi \ No newline at end of file diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml new file mode 100644 index 0000000..12c3a3d --- /dev/null +++ b/.github/workflows/pull_request.yml @@ -0,0 +1,92 @@ +name: Pull Request +on: + merge_group: + pull_request: + types: [ assigned, opened, synchronize, reopened, labeled, unlabeled ] + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + labeler: + name: Chart Diff Labeler + permissions: + contents: read + pull-requests: write + runs-on: ubuntu-latest + steps: + - uses: 'actions/checkout@v4' + with: + fetch-depth: 0 + - uses: actions/labeler@v5 + with: + sync-labels: true + - name: Retrieve chart changes + id: chart_changes + run: | + CHARTS=$(git diff ${{ github.base_ref }} --stat | grep -oE 'charts/[^/]+/' | sed 's|charts/||;s|/||' | sort -u | jq -R -s -c 'split("\n") | map(select(. != ""))') + echo "Changed charts: $CHARTS" + echo "::set-output name=changed_charts::$CHARTS" + + - name: Label PR based on chart changes + if: steps.chart_changes.outputs.changed_charts != '' + uses: actions/github-script@v7 + env: + CHARTS: ${{ steps.chart_changes.outputs.changed_charts }} + with: + script: | + const charts = JSON.parse(process.env.CHARTS); + const labels = charts.map(chart => `chart-${chart.trim()}`).filter(label => label.length > 0); + + if (labels.length > 0) { + console.log(`Applying labels: ${labels.join(', ')}`); + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + labels: labels + }); + } else { + console.log('No charts changed, skipping labeling.'); + } + + pr_name: + if: github.event_name == 'pull_request' + name: Check PR Title + runs-on: ubuntu-latest + permissions: + statuses: write + steps: + - uses: amannn/action-semantic-pull-request@v5 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + tests: + name: Tests + runs-on: ubuntu-latest + steps: + - name: Install earthly + uses: earthly/actions-setup@v1 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + - uses: 'actions/checkout@v4' + with: + fetch-depth: 0 + - name: Tests + run: earthly +ci + - name: Get changed files + id: changed-files + shell: bash + run: | + hasChanged=$(git status --porcelain) + if (( $(echo ${#hasChanged}) != 0 )); then + git status + echo "There are changes in the repository" + exit 1 + fi + - name: Archive chart + uses: actions/upload-artifact@v4 + with: + name: charts_${{ github.ref_type == 'branch' && github.sha }}.tgz + path: charts/**/*.tgz + retention-days: 3 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..e71f00a --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,54 @@ +name: Release +on: + push: + branches: + - main + +permissions: + contents: write + packages: write + +jobs: + tests: + name: Tests + runs-on: ubuntu-latest + steps: + - name: Install earthly + uses: earthly/actions-setup@v1 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + - uses: 'actions/checkout@v4' + with: + fetch-depth: 0 + - name: Tests + run: earthly +ci + - name: Get changed files + id: changed-files + shell: bash + run: | + hasChanged=$(git status --porcelain) + if (( $(echo ${#hasChanged}) != 0 )); then + git status + echo "There are changes in the repository" + exit 1 + fi + release: + name: Release + needs: + - tests + permissions: + contents: write + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Configure Git + run: | + git config user.name "$GITHUB_ACTOR" + git config user.email "$GITHUB_ACTOR@users.noreply.github.com" + - name: Release + run: earthly +release + env: + EARTHLY_SECRETS: "GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }}" \ No newline at end of file diff --git a/.gitignore b/.gitignore index f58190a..4e7a4f2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,10 @@ ./dist/ *.tgz -Chart.lock \ No newline at end of file + +.DS_Store + +dist + +charts/**/LICENSE + +.tmp-earthly-out \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..4810f0e --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,111 @@ +# Helm Charts + +## Working Per Chart + +### Building the Charts + +Each chart must implement the common targets interface with the following minimum required files: + +- `Chart.yaml` +- `Earthfile` + +Each `Earthfile` Chart **must** implement the following targets to integrate with the CI: + +- `+sources`: Raw sources of the chart without the dependencies. +> [!IMPORTANT] +> A LICENSE file is included in every chart through the helper [SOURCE](./charts/Earthfile) +- `+dependencies`: Raw sources with dependencies updated. +- `+validate`: Validates the chart, including its dependencies. +- `+package`: Packages the chart from validated sources. +- `+readme`: (Optional): Include README when dependendies are validated. +> The README file is generated with `helm-docs` and included in the chart through the helper [README_GENERATOR](./charts/Earthfile). + +> [!TIP] +> A file named `README.md.gotmpl` can be added to the chart to customize the README generation. +- `+schema`: (Optional): Generate a values schema from the `values.yaml` and include it in the chart sources. Then it will be validated with the `+validate` target. + + +### Core Library + +Each chart must implement the core Helm library as a dependency to include the common helpers for: + +- Improve resource naming +- [Kubernetes recommended labels](https://kubernetes.io/docs/concepts/overview/working-with-objects/common-labels/) +- Values naming + +- Global values structure accross all charts, including settings + - Monitoring + - Traces(OTLP) + - Metrics(OTLP) + - Logs(JSON) + - Storage + - PostgreSQL (Bitnami) (Internal or External) + - Nats (Nats.io) (Internal) + +- (Optionals): + + - AWS IAM + - AWS Target Groups + - Ingress + - PodDisruptionBudget + - HorizontalPodAutoscaler + - ServiceAccount + - RBAC + - NetworkPolicy + +## Validate repository changes + +To validate the all the changes arround the repository, run the following command: + +```bash +earthly +pre-commit +``` + +1. First run +once running it for the first time, it will build all the dependencies and validate all the charts and the charts. + +2. Second run +It will validate the future changes only where it needs to be validated thanks to caches. + +## Tests core and charts + +- Naming conventions for included resource +- Labels selection +- Default environment variables bindings +- Resources disablings + - HorizontalPodAutoscaler + - Ingress + - PodDisruptionBudget + - Subchart disabling +- Secret mapping +- Configmap mapping +- Managed Stacks Features: Disable GRPC communication with any type of Agent + +## CI: GitHub Actions + +The CI is based on GitHub Actions, triggered on each PR and the main branch. It is composed of the following workflows: + +- **Pull Request**: PR + - Validates the PR name. + - Labels the PR with the charts who have changed in the `charts/` directory. + - Lint, Template, Generate Readmes for any charts who has changed. And Test Requirements accross on all charts `earthly +ci`. +- **Release**: Main + - Lint, Template, Generate Readmes for any charts who has changed. And Test Requirements accross on all charts `earthly +ci`. + - Release any `Chart.yaml` `.version` that have been upgraded. + - `chart-releaser` is based on builded Artifact. - It creates a new tag with the chart version and releases on github it to the Helm repository. + +## CD: from sources + +External repositories can rely on the `+package` target and artifact to deploy from a specific branch or tag. + +```bash +earthly github.com/formancehq/helm/charts/cloudprem+package +``` + +## CD: from Helm repository + +The helm repository is `ghcr.io/formancehq/helm` and can be used to deploy the charts. + +```bash +helm upgrade --install regions ghcr.io/formancehq/helm/regions --version v2.0.18 +``` diff --git a/Earthfile b/Earthfile index 316fea7..163dce7 100644 --- a/Earthfile +++ b/Earthfile @@ -1,20 +1,62 @@ -VERSION 0.8 +VERSION --wildcard-builds --wildcard-copy 0.8 IMPORT ./charts AS charts +IMPORT github.com/formancehq/earthly:tags/v0.16.2 AS core + +sources: + ARG --required PATH + FROM core+base-image + WORKDIR /src + WORKDIR /src/${PATH} + COPY --dir ./${PATH} . + SAVE ARTIFACT /src/${PATH} + +readme: + FROM core+base-image + RUN apk add go + RUN touch README.md + COPY --dir charts /charts + COPY (./tools/readme+sources/*) /src + WORKDIR /src + RUN --mount=type=cache,id=gomod,target=${GOPATH}/pkg/mod \ + --mount=type=cache,id=gobuild,target=/root/.cache/go-build \ + go run ./ readme --chart-dir /charts >> README.md + SAVE ARTIFACT README.md AS LOCAL README.md validate: - LOCALLY - BUILD charts+helm-validate --PROJECT=demo - + BUILD ./charts/*+validate + +tests: + BUILD ./test/helm+tests + package: - LOCALLY - BUILD charts+helm-package --PROJECT=demo - -publish: - LOCALLY - BUILD charts+helm-publish --PROJECT=demo - + FROM core+base-image + COPY (./charts/*+package/*) /build/ + SAVE ARTIFACT /build AS LOCAL ./ + +ci: + FROM core+base-image + BUILD +pre-commit + BUILD +tests + COPY (+package/*) /build + SAVE ARTIFACT /build + pre-commit: - LOCALLY - BUILD +validate - BUILD +package + BUILD +validate + BUILD +readme + +release: + FROM core+builder-image + GIT CLONE --branch=v1.6.1 https://github.com/helm/chart-releaser /src/chart-releaser + WORKDIR /src/chart-releaser + DO core+GO_INSTALL --package ./... + COPY ./cr.yaml . + COPY (+ci/*) /build + RUN --secret GITHUB_TOKEN cr upload \ + --config cr.yaml \ + --git-repo helm \ + --token ${GITHUB_TOKEN} \ + --skip-existing \ + --package-path /build + + diff --git a/README.md b/README.md index 982752c..81a9add 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,22 @@ # Formance Helm charts -[![Artifact Hub](https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/formance-ledger)](https://artifacthub.io/packages/search?repo=formance-ledger) ## How to use Helm charts +| Readme | Chart Version | App Version | Description | Hub | +|--------|---------------|-------------|-------------|-----| +| [Cloudprem](./charts/cloudprem/README.md) | v2.0.0-beta.11 |v0.35.3 | Formance control-plane | [![Artifact HUB](https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/cloudprem)](https://artifacthub.io/packages/search?repo=cloudprem) | +| [Console](./charts/console/README.md) | v1.0.0-beta.1 |9431e5f4b4b1a03cb8f02ef1676507b9c023f2bb | Formance Console | [![Artifact HUB](https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/console)](https://artifacthub.io/packages/search?repo=console) | +| [Core](./charts/core/README.md) | v1.0.0-beta.1 |latest | Formance Core Library | [![Artifact HUB](https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/core)](https://artifacthub.io/packages/search?repo=core) | +| [Demo](./charts/demo/README.md) | 2.0.0 |latest | Formance Private Regions Demo | [![Artifact HUB](https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/demo)](https://artifacthub.io/packages/search?repo=demo) | +| [Membership](./charts/membership/README.md) | v1.0.0-beta.1 |v0.35.3 | Formance Membership API. Manage stacks, organizations, regions, invitations, users, roles, and permissions. | [![Artifact HUB](https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/membership)](https://artifacthub.io/packages/search?repo=membership) | +| [Portal](./charts/portal/README.md) | v1.0.0-beta.1 |764bb7e199e1d2882e4d5cd205eada0ef0abc283 | Formance Portal | [![Artifact HUB](https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/portal)](https://artifacthub.io/packages/search?repo=portal) | +| [Stargate](./charts/stargate/README.md) | 0.3.0 |latest | Formance Stargate gRPC Gateway | [![Artifact HUB](https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/stargate)](https://artifacthub.io/packages/search?repo=stargate) | + +## How to contribute + +Please refer to the [CONTRIBUTING.md](./CONTRIBUTING.md) file for information on how to contribute to this project. + +## License + +This project is licensed under the MIT License - see the [LICENSE](./LICENSE) file for details. + diff --git a/charts/Earthfile b/charts/Earthfile index ed502b6..c091faa 100644 --- a/charts/Earthfile +++ b/charts/Earthfile @@ -1,50 +1,73 @@ -VERSION 0.8 - -IMPORT github.com/formancehq/earthly:tags/v0.16.2 AS core - -helm-sources: - ARG --required PROJECT - FROM core+helm-base - WORKDIR /src/helm - COPY --dir ./$PROJECT/* /src/helm - SAVE ARTIFACT /src/helm - -helm-readme-generator: - ARG --required PROJECT - FROM node:20 - WORKDIR /src/readme-generator-for-helm - GIT CLONE git@github.com:bitnami/readme-generator-for-helm.git . - RUN npm install - WORKDIR /src/helm - COPY --pass-args (+helm-sources/*) . - RUN node /src/readme-generator-for-helm/bin/index.js --values values.yaml --schema values.schema.json --readme README.md - SAVE ARTIFACT README.md AS LOCAL ./$PROJECT/README.md - -helm-validate: - ARG --required PROJECT - FROM +helm-sources --PROJECT=$PROJECT - RUN apk add --no-cache git - RUN helm plugin install https://github.com/losisin/helm-values-schema-json.git - WORKDIR /src/helm - RUN helm dependencies update - RUN helm lint ./ --strict - RUN helm schema -input values.yaml -output values.schema.json - SAVE ARTIFACT /src/helm AS LOCAL ./$PROJECT - -helm-package: - ARG --required PROJECT - FROM +helm-sources --PROJECT=$PROJECT - WORKDIR /src/helm - RUN helm package . --destination /src/build/ - SAVE ARTIFACT /src/build - -helm-publish: - ARG --required PROJECT - FROM +helm-package --PROJECT=$PROJECT - WORKDIR /src/build - WITH DOCKER - RUN --secret GITHUB_TOKEN echo $GITHUB_TOKEN | docker login ghcr.io -u NumaryBot --password-stdin - END - WITH DOCKER - RUN helm push ./*.tgz oci://ghcr.io/formancehq/helm - END \ No newline at end of file +VERSION --wildcard-builds --wildcard-copy 0.8 + +IMPORT github.com/formancehq/earthly:tags/v0.16.3 AS core +IMPORT .. AS root + +BASE: + FUNCTION + FROM core+base-image + RUN apk update && apk add openssl helm + +SOURCES: + FUNCTION + COPY (root+sources/* --PATH=LICENSE) . + COPY --if-exists Chart.lock . + COPY Chart.yaml . + COPY --if-exists values.yaml . + COPY --dir --if-exists templates . + SAVE ARTIFACT ./* AS LOCAL ./ + +VALIDATE: + FUNCTION + DO --pass-args +LINT + DO --pass-args +TEMPLATE + SAVE ARTIFACT ./*.lock AS LOCAL ./ + SAVE ARTIFACT ./README.md AS LOCAL ./ + SAVE ARTIFACT ./values.schema.json AS LOCAL ./ + +LINT: + FUNCTION + ARG additionalArgs + RUN helm lint ./ --strict $additionalArgs + +TEMPLATE: + FUNCTION + RUN helm template ./ $additionalArgs + +DEPENDENCIES: + FUNCTION + RUN helm dependencies update + SAVE ARTIFACT ./* AS LOCAL ./ + +PACKAGE: + FUNCTION + RUN helm package . + SAVE ARTIFACT *.tgz AS LOCAL ./ + +README_GENERATOR: + FUNCTION + RUN helm-docs --chart-search-root=.. --document-dependency-values + SAVE ARTIFACT README.md AS LOCAL README.md + +readme-base: + DO --pass-args +BASE + RUN apk add go + ENV GOPATH /go + ENV GOTOOLCHAIN=local + ARG GOCACHE=/go-cache + ARG GOMODCACHE=/go-mod-cache + ARG HELM_DOCS_VERSION=v1.14 + ENV PATH $GOPATH/bin:/usr/local/go/bin:$PATH + DO --pass-args core+GO_INSTALL --package=github.com/norwoodj/helm-docs/cmd/helm-docs@$HELM_DOCS_VERSION + +SCHEMA: + FUNCTION + COPY values.yaml . + RUN helm schema -input values.yaml -output values.schema.json + SAVE ARTIFACT values.schema.json AS LOCAL values.schema.json + SAVE ARTIFACT values.yaml + +schema-base: + DO --pass-args +BASE + RUN apk add --no-cache git + RUN helm plugin install https://github.com/losisin/helm-values-schema-json.git diff --git a/charts/README.md b/charts/README.md deleted file mode 100644 index 19ba5d8..0000000 --- a/charts/README.md +++ /dev/null @@ -1,539 +0,0 @@ -# Formance Cloudprem Helm Chart - -This chart is used to deploy Formance Cloudprem on a Kubernetes cluster. - -[Formance Cloudprem](https://docs.formance.com/deployment/cloudprem2/intro) is a platform that allows you to manage your users, organizations and your data plane. - -## TL;DR - -```bash -export BASE_DOMAIN=example.com -export BASE_DOMAIN_WILDCARD_CERTIFICATE=example-com-wildcard-certificate-tls - -helm install cloudprem oci://ghcr.io/formancehq/helm/cloudprem \ - --set global.serviceHost=$BASE_DOMAIN \ - --set membership.ingress.tls[0].secretName=$BASE_DOMAIN_WILDCARD_CERTIFICATE \ - --set portal.ingress.tls[0].secretName=$BASE_DOMAIN_WILDCARD_CERTIFICATE \ - --set console.ingress.tls[0].secretName=$BASE_DOMAIN_WILDCARD_CERTIFICATE \ - --set dex.ingress.tls[0].secretName=$BASE_DOMAIN_WILDCARD_CERTIFICATE -``` - -## Requirements - -- SSL Certificate (Let's Encrypt or another) -- Public domain according to the certificate authority -- Kubernetes Cluster > 1.14 - -## Control plane components - -- Portal > 0.0.1 -- Console > 2.2.1 -- Membership > 0.28.0 -- Dex > 0.28.0 - - - -## Introduction - -This chart bootstraps 5 different components that form the Formance Control Plane, additionally you will need to install the Formance Data Plane composed of a Kubernetes Operator. - -In order to deploy the 5 different components, you must have a Kubernetes cluster with an Ingress Controller and valid SSL certificates for the different domains. - -Following components need his own database: - -- [Dex](https://dexidp.io/) (Authentication) -- Membership (User and Organization Management) - -Addtionnaly all components need a public URL that can be accessible through a VPN or a public domain. - -The deployment is done via Helm. Make sure you have Helm installed and configured. - -You will first have to create a `values.yaml` file to define your values. - -> [!TIP] ->By default, a PostgreSQL database is included in this configuration without any data persistence. - -In order to do your first deployment you will need to complete the following steps: -- [Complete de minimal profile](#minimal-valuesyaml-profile) -- [Deploy Operator](https://docs.formance.com/operator/Installation) -- [Deploy Stack](#deploy-stack) -- [Init Cloudprem](#init-cloudprem) - -## Minimal `values.yaml` profile - -> [!IMPORTANT] -> Each certificate must be in the form of `.global.serviceHost` following the example below: ->- Console: `console.{{ .Values.global.serviceHost }}` ->- Portal: `portal.{{ .Values.global.serviceHost }}` ->- Membership: `membership.{{ .Values.global.serviceHost }}` ->- Dex: `dex.{{ .Values.global.serviceHost }}` - -> [!TIP] -> A quick win is to use a wildcard certificate for all the components on `*.{{ .Values.global.serviceHost }}`. ->Then reference the secret same as the example bellow. - -> [!TIP] -> You can use [Cert Manager](https://cert-manager.io/docs/) to manage your certificates. - -```yaml - -global: - serviceHost: "" - -membership: - ingress: - tls: - - secretName: example-com-wildcard-certificate-tls - -portal: - ingress: - tls: - - secretName: example-com-wildcard-certificate-tls - -console: - ingress: - tls: - - secretName: example-com-wildcard-certificate-tls - -dex: - ingress: - tls: - - secretName: example-com-wildcard-certificate-tls - -``` - - - -## Init Cloudprem - -### [Install FCTL](https://docs.formance.com/getting-started/fctl-quick-start) - -```bash -# Linux amd64, arm64 -ARCH="amd64"; curl -L -o fctl.tar.gz "https://github.com/formancehq/stack/releases/download/v2.0.9/fctl_linux-$ARCH.tar.gz" \ -&& tar -xvf fctl.tar.gz \ -&& sudo mv fctl /usr/local/bin \ -&& chmod +x /usr/local/bin/fctl \ -&& rm fctl.tar.gz - -# MacOS -brew install formancehq/tap/fctl -brew upgrade fctl - - -## Debian -deb [trusted=yes] https://apt.fury.io/formance/ / > /etc/apt/sources.list -apt update && apt install fctl -``` - -```bash -fctl version -``` - -### Login on membership - -> [!IMPORTANT] -> According to [Dex default configuration](#dex-configuration), you can login with the following credentials: `admin@formance.com` / `password`. -> You can also define `Google`, `Github`, `Microsoft` as OAuth2 connectors. -> Additional configuration can be found on the [Dex documentation](https://artifacthub.io/packages/helm/dex/dex). - -```bash -export BASE_DOMAIN=example.com -fctl -p $user login --membership-uri https://membership.$BASE_DOMAIN/api -``` - -This will create a new organization and a user. - -### (Optional) Add domain for Auto Login - -```bash - -export EMAIL_DOMAIN_NAME=example.com - -fctl cloud organizations list - -fctl cloud organizations update ORGANIZATION_ID --domain=$EMAIL_DOMAIN_NAME --default-organization-role=GUEST --default-stack-role=GUEST - -fctl cloud organizations list -``` - -The possible values are GUEST or ADMIN. -This allows to give the rights by default to a user who logs in with an email of domain DOMAIN_NAME. - -### Init databases - -```sql -insert into membership."regions" (id, base_url, name, creator_id, created_at, production, active) values ( - gen_random_uuid(), - 'https://${BASE_DOMAIN}', - 'default', - (select id from membership."users" where id = ( - select owner_id from membership."organizations" limit 1 - )), - now(), - true, - true -); -insert into membership."stacks" (name, organization_id, id, region_id, created_at, updated_at, stargate_enabled, client_secret, state, status, expected_status) values ( - 'default', - (select id from membership."organizations" limit 1), - 'sdgsd', -- update if needed, this is your stack id - (select id from membership."regions" limit 1), - now(), - now(), - false, - gen_random_uuid(), - 'ACTIVE', - 'READY', - 'READY' -); -``` - -This query will create the region and a stack associated with your organization in that region. - -### Create stack on cluster - -After creating your region and your Stack. -You can retrieve your Organization ID and search & replace the value of your organization ID and BASE_URL - -> [!IMPORTANT] -> In the Formance CRDs, the stack name has format `-` - -```bash -cat <<"EOF" > stack.sh - -#!bin/bash - -export BASE_DOMAIN_WILDCARD_CERTIFICATE=example-com-wildcard-certificate-tls -export BASE_DOMAIN=example.com -export ORGANIZATION_ID=ylzsigispivc -export STACK_ID=sdgs - -cat < stack.yaml ---- -apiVersion: formance.com/v1beta1 -kind: Stack -metadata: - name: ${ORGANIZATION_ID}-${STACK_ID} -spec: - debug: true - dev: true - versionsFromFile: v2.0 ---- -apiVersion: formance.com/v1beta1 -kind: Gateway -metadata: - name: ${ORGANIZATION_ID}-${STACK_ID} -spec: - stack: ${ORGANIZATION_ID}-${STACK_ID} - ingress: - host: ${ORGANIZATION_ID}-${STACK_ID}.${BASE_DOMAIN} - scheme: https - tls: - secretName: ${BASE_DOMAIN_WILDCARD_CERTIFICATE} ---- -apiVersion: formance.com/v1beta1 -kind: Ledger -metadata: - name: ${ORGANIZATION_ID}-${STACK_ID} -spec: - stack: ${ORGANIZATION_ID}-${STACK_ID} ---- -apiVersion: formance.com/v1beta1 -kind: Auth -metadata: - name: ${ORGANIZATION_ID}-${STACK_ID} -spec: - stack: ${ORGANIZATION_ID}-${STACK_ID} - enableScopes: true - delegatedOIDCServer: - clientID: stack_${ORGANIZATION_ID}_${STACK_ID} - clientSecret: changeMe - issuer: $(echo "https://membership.${BASE_DOMAIN}/api") -EOF - -echo "EOF" >> stack.sh && bash stack.sh - -kubectl apply -f . /stack.yml -``` - -Here, we have deployed a basic configuration. Refer to [the Formance operator documentation](https://docs.formance.com/operator/Requirements) for information on possible configurations. - -### Access to the portal - -```bash -fctl ui -``` - -## Migration - -### From v1.0.X To v2.0.X - -A global configuration has been introduced to manage values accross different services. To see the detail of the default values, please refer to the [Global Parameters](#global-configuration) section. - -Each platform key has beeen moved to `global.platform` - -This chart adds several `existingSecret` and `secretKeys` to manage the secret either from configuration or from a secret. - -The chart is now divided into 5 charts : -- Portal -- Console -- Membership -- Dex -- Postgresql - -#### Breaking changes by service - -Global: -- `.serviceAccount` has been removed, use `..serviceAccount` instead. -- `.commonLabels` has been removed - -Console: -- `.console.membership` has been removed, and is now managed through the `.global.platform.membership.oauthClient`. It's going to be used by all platform services. - -Dex: -- `.dex.config` has been moved `.dex.configOverrides` enabled by default with`.dex.createConfigSecretOverrides` allowing templating. - -#### Others changes - -Console: -- `.console.config.stargate_url` has been removed, it will be managed for a kubernetes service. -- `.console.config.redirect_url` has been deprecated, it is now templated with https://console`.global.serviceHost`. -- `.console.config.feature_disabled` has been removed, it will be managed through `.console.config.additionalEnv.FEATURE_DISABLED`. -- `.console.config.encryption_key` has been deprecated, it will be managed through `.global.platform.cookie.encryptionKey`. -- `.console.config.managed_stack` has been removed, console now manage the stack through portal. -- `.console.config.database` has been removed, console now manage the session through portal cookie. -- `.console.monitoring.traces` has been deprecated, it will be managed through `.global.monitoring.traces` - -Membership: -- `.membership.config.url` is deprecated, it will be templated through `https://memberhsip.{{ .Values.global.serviceHost }}` or ingress - -- `.membership.config.postgresqlUrl` has been deprecated, it will be mangaged through `.global.postgresql.auth`. - -Dex: - -- `.dex.envVars` and `.dex.configOverrides.staticClients.[].secretEnv` can be used together to set static clients secrets. - - -## Parameters - -### Global configuration - -| Name | Description | Value | -| ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------- | -| `global.debug` | Enable debug mode | `false` | -| `global.serviceHost` | is the base domain for console, portal, membership | `""` | -| `global.platform.cookie.encryptionKey` | is used to encrypt a cookie that share authentication between platform services (console, portal, ...),is used to store the current state organizationId-stackId | `changeMe00` | -| `global.platform.cookie.existingSecret` | is the name of the secret | `""` | -| `global.platform.cookie.secretKeys.encryptionKey` | is the key contained within the secret | `""` | -| `global.platform.membership.oauthClient.id` | is the id of the client | `platform` | -| `global.platform.membership.oauthClient.secret` | is the secret of the client | `changeMe1` | -| `global.platform.membership.oauthClient.existingSecret` | is the name of the secret | `""` | -| `global.platform.membership.oauthClient.secretKeys.secret` | is the key contained within the secret | `""` | -| `global.postgresql.auth.postgresPassword` | Password for the "postgres" admin user (overrides `auth.postgresPassword`) | `formance` | -| `global.postgresql.auth.username` | Name for a custom user to create (overrides `auth.username`) | `formance` | -| `global.postgresql.auth.password` | Password for the custom user to create (overrides `auth.password`) | `formance` | -| `global.postgresql.auth.database` | Name for a custom database to create (overrides `auth.database`) | `formance` | -| `global.postgresql.auth.existingSecret` | Name of existing secret to use for PostgreSQL credentials (overrides `auth.existingSecret`). | `""` | -| `global.postgresql.auth.secretKeys.adminPasswordKey` | Name of key in existing secret to use for PostgreSQL credentials (overrides `auth.secretKeys.adminPasswordKey`). Only used when `global.postgresql.auth.existingSecret` is set. | `""` | -| `global.postgresql.auth.secretKeys.userPasswordKey` | Name of key in existing secret to use for PostgreSQL credentials (overrides `auth.secretKeys.userPasswordKey`). Only used when `global.postgresql.auth.existingSecret` is set. | `""` | -| `global.postgresql.host` | Host for PostgreSQL (overrides included postgreql `host`) | `""` | -| `global.postgresql.additionalArgs` | Additional arguments for PostgreSQL Connection URI | `sslmode=disable` | -| `global.postgresql.service.ports.postgresql` | PostgreSQL service port (overrides `service.ports.postgresql`) | `5432` | - -### Portal configuration - -| Name | Description | Value | -| ------------------------------------------- | -------------------------------- | ------------------------------------------ | -| `portal.enabled` | Enable portal | `true` | -| `portal.serviceAccount.create` | Service account creation | `true` | -| `portal.serviceAccount.name` | Service account name | `""` | -| `portal.serviceAccount.annotations` | Service account annotations | `{}` | -| `portal.replicas` | Number of replicas | `1` | -| `portal.image.repository` | Portal image repository | `ghcr.io/formancehq/membership-ui` | -| `portal.image.pullPolicy` | Portal image pull policy | `IfNotPresent` | -| `portal.image.tag` | Portal image tag | `764bb7e199e1d2882e4d5cd205eada0ef0abc283` | -| `portal.ingress.enabled` | Enable portal ingress | `true` | -| `portal.ingress.className` | Ingress class name | `""` | -| `portal.ingress.annotations` | Ingress annotations | `{}` | -| `portal.ingress.hosts[0].host` | Ingress host | `portal.{{ .Values.global.serviceHost }}` | -| `portal.ingress.hosts[0].paths[0].path` | Ingress path | `/` | -| `portal.ingress.hosts[0].paths[0].pathType` | Ingress path type | `Prefix` | -| `portal.ingress.tls` | Ingress tls | `[]` | -| `portal.config.environment` | Portal environment | `production` | -| `portal.config.cookie.secret` | Cookie secret | `changeMe2` | -| `portal.config.cookie.existingSecret` | Cookie existing secret | `""` | -| `portal.config.cookie.secretKeys.secret` | Cookie secret key | `""` | -| `portal.config.additionalEnv` | Additional environment variables | `{}` | -| `portal.config.cookie.secret` | Cookie secret | `changeMe2` | -| `portal.service.annotations` | Service annotations | `{}` | -| `portal.service.type` | Service type | `ClusterIP` | -| `portal.service.clusterIP` | Service cluster IP | `""` | -| `portal.service.ports.http.port` | Service http port | `3000` | -| `portal.service.ports.http.nodePort` | Service node port | `undefined` | -| `portal.volumeMounts` | Portal volume mounts | `[]` | -| `portal.volumes` | Portal volumes | `[]` | -| `portal.resources` | Portal resources | `{}` | -| `portal.readinessProbe` | Portal readiness probe | `{}` | -| `portal.livenessProbe` | Portal liveness probe | `{}` | -| `portal.nodeSelector` | Portal node selector | `{}` | -| `portal.tolerations` | Portal tolerations | `[]` | -| `portal.affinity` | Portal affinity | `{}` | - -### Console configuration - -| Name | Description | Value | -| ----------------------------------------------- | --------------------------------------------------------- | ------------------------------------------ | -| `console.enabled` | Enable console | `true` | -| `console.serviceAccount.create` | Service account creation | `true` | -| `console.serviceAccount.name` | Service account name | `""` | -| `console.serviceAccount.annotations` | Service account annotations | `{}` | -| `console.replicas` | Number of replicas | `1` | -| `console.image.repository` | Console image repository | `ghcr.io/formancehq/console` | -| `console.image.pullPolicy` | Console image pull policy | `IfNotPresent` | -| `console.image.tag` | Console image tag | `9431e5f4b4b1a03cb8f02ef1676507b9c023f2bb` | -| `console.resources` | Console resources | `{}` | -| `console.readinessProbe` | Console readiness probe | `{}` | -| `console.livenessProbe` | Console liveness probe | `{}` | -| `console.service.annotations` | Console service annotations | `{}` | -| `console.service.type` | Console service type | `ClusterIP` | -| `console.service.clusterIP` | Console service cluster IP | `""` | -| `console.service.ports.http.port` | Console service http port | `3000` | -| `console.service.ports.http.nodePort` | Console service node port | `undefined` | -| `console.ingress.enabled` | Console ingress enabled | `true` | -| `console.ingress.className` | Console ingress class name | `""` | -| `console.ingress.annotations` | Console ingress annotations | `{}` | -| `console.ingress.hosts[0].host` | Console ingress host | `console.{{ .Values.global.serviceHost }}` | -| `console.ingress.hosts[0].paths[0].path` | Console ingress path | `/` | -| `console.ingress.hosts[0].paths[0].pathType` | Console ingress path type | `Prefix` | -| `console.ingress.tls` | Console ingress tls | `[]` | -| `console.volumeMounts` | Console volume mounts | `[]` | -| `console.volumes` | Console volumes | `[]` | -| `console.nodeSelector` | Console node selector | `{}` | -| `console.tolerations` | Console tolerations | `[]` | -| `console.affinity` | Console affinity | `{}` | -| `console.config.environment` | Console environment | `production` | -| `console.config.additionalEnv` | Console additional environment variables | `{}` | -| `console.config.additionalEnv.HOST` | Console additional environment variables HOST | | -| `console.config.additionalEnv.FEATURE_DISABLED` | Console additional environment variables FEATURE_DISABLED | | -| `console.config.monitoring.traces.enabled` | Console monitoring traces enabled | `false` | -| `console.config.monitoring.traces.url` | Console monitoring traces url | `""` | -| `console.config.monitoring.traces.port` | Console monitoring traces port | `4317` | -| `console.config.monitoring.traces.attributes` | Console monitoring traces attributes | `""` | - -### Membership configuration - -| Name | Description | Value | -| --------------------------------------------------------------- | ----------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | -| `membership.enabled` | Enable membership | `true` | -| `membership.serviceAccount.create` | Membership service account creation | `true` | -| `membership.serviceAccount.name` | Membership service account name | `""` | -| `membership.serviceAccount.annotations` | Membership service account annotations | `{}` | -| `membership.replicaCount` | Count of replicas | `1` | -| `membership.image.repository` | Membership image repository | `ghcr.io/formancehq/membership` | -| `membership.image.pullPolicy` | Membership image pull policy | `IfNotPresent` | -| `membership.image.tag` | Membership image tag | `v0.35.1` | -| `membership.imagePullSecrets` | Membership image pull secrets | `[]` | -| `membership.nameOverride` | Membership name override | `""` | -| `membership.fullnameOverride` | Membership fullname override | `""` | -| `membership.feature.managedStacks` | Membership feature managed stacks | `true` | -| `membership.feature.disableEvents` | Membership feature disable events | `true` | -| `membership.podSecurityContext` | Membership pod security context | `{}` | -| `membership.securityContext.capabilities.drop` | Membership security context capabilities drop | `["ALL"]` | -| `membership.securityContext.readOnlyRootFilesystem` | Membership security context read only root filesystem | `true` | -| `membership.securityContext.runAsNonRoot` | Membership security context run as non root | `true` | -| `membership.securityContext.runAsUser` | Membership security context run as user | `1000` | -| `membership.resources` | Membership resources | `{}` | -| `membership.autoscaling` | Membership autoscaling | `{}` | -| `membership.nodeSelector` | Membership node selector | `{}` | -| `membership.tolerations` | Membership tolerations | `[]` | -| `membership.affinity` | Membership affinity | `{}` | -| `membership.debug` | Membership debug | `false` | -| `membership.dev` | Membership dev | `false` | -| `membership.initContainers` | Membership init containers | `[]` | -| `membership.volumeMounts` | Membership volume mounts | `[]` | -| `membership.volumes` | Membership volumes | `[]` | -| `membership.service.annotations` | Membership service annotations | `{}` | -| `membership.service.type` | Membership service type | `ClusterIP` | -| `membership.service.clusterIP` | Membership service cluster IP | `""` | -| `membership.service.ports.http.nodePort` | Membership service node port | `undefined` | -| `membership.service.ports.http.port` | Membership service port | `8080` | -| `membership.service.ports.grpc.port` | Membership service grpc port | `8082` | -| `membership.service.ports.grpc.nodePort` | Membership service grpc node port | `undefined` | -| `membership.ingress.enabled` | Membership ingress enabled | `true` | -| `membership.ingress.className` | Membership ingress class name | `""` | -| `membership.ingress.annotations` | Membership ingress annotations | `{}` | -| `membership.ingress.hosts[0].host` | Membership ingress host | `membership.{{ .Values.global.serviceHost }}` | -| `membership.ingress.hosts[0].paths[0].path` | Membership ingress path | `/api` | -| `membership.ingress.hosts[0].paths[0].pathType` | Membership ingress path type | `Prefix` | -| `membership.ingress.tls` | Membership ingress tls | `[]` | -| `membership.config.postgresqlUrl` | DEPRECATED Membership postgresql coonection url | | -| `membership.config.url` | DEPRECATED Membership pulic url, use ingress.host | `https://membership.{{ .Values.global.serviceHost }}` | -| `membership.config.oidc.issuer` | Membership oidc issuer | `https://dex.{{ .Values.global.serviceHost }}` | -| `membership.config.oidc.clientId` | Membership oidc client id | `membership` | -| `membership.config.oidc.clientSecret` | Membership oidc client secret | `{}` | -| `membership.config.oidc.existingSecret` | Membership oidc existing secret | `""` | -| `membership.config.oidc.secretKeys.secret` | Membership oidc secret key | `""` | -| `membership.config.migration.annotations` | Membership migration annotations | `{}` | -| `membership.config.configMap.clients[0].id` | FCTL clients id | `fctl` | -| `membership.config.configMap.clients[0].public` | FCTL is a public client | `true` | -| `membership.config.configMap.clients[1].secrets` | Platform oauth client secret | `["$PLATFORM_OAUTH_CLIENT_SECRET"]` | -| `membership.config.configMap.clients[1].id` | Platform oauth client id | `{{ .Values.global.platform.membership.oauthClient.id }}` | -| `membership.config.configMap.clients[1].redirectUris` | Platform oauth client redirect uris | `["https://console.{{ .Values.global.serviceHost }}/auth/login","https://portal.{{ .Values.global.serviceHost }}/auth/login"]` | -| `membership.config.configMap.clients[1].postLogoutRedirectUris` | Platform oauth client post logout redirect uris | `["https://console.{{ .Values.global.serviceHost }}/auth/logout","https://portal.{{ .Values.global.serviceHost }}/auth/logout"]` | -| `membership.config.configMap.clients[1].scopes` | PLatform oauth client scopes | `["supertoken","accesses","remember_me","keep_refresh_token"]` | - -### Dex configuration - -| Name | Description | Value | -| --------------------------------------------------- | ------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | -| `dex.enabled` | Enable dex | `true` | -| `dex.image.repository` | Dex image repository | `ghcr.io/formancehq/dex` | -| `dex.image.pullPolicy` | Dex image pull policy | `IfNotPresent` | -| `dex.image.tag` | Dex image tag | `v0.33.10` | -| `dex.ingress.enabled` | Dex ingress enabled | `true` | -| `dex.ingress.className` | Dex ingress class name | `""` | -| `dex.ingress.annotations` | Dex ingress annotations | `{}` | -| `dex.ingress.hosts[0].host` | Dex ingress host | `dex.{{ .Values.global.serviceHost }}` | -| `dex.ingress.hosts[0].paths[0].path` | Dex ingress path | `/` | -| `dex.ingress.hosts[0].paths[0].pathType` | Dex ingress path type | `Prefix` | -| `dex.ingress.tls` | Dex ingress tls | `[]` | -| `dex.resources` | Dex resources | `{}` | -| `dex.configSecret.create` | Dex config secret create | `false` | -| `dex.configSecret.createConfigSecretOverrides` | Dex config secret create config secret overrides | `true` | -| `dex.configSecret.name` | Dex config secret name | `membership-dex-config` | -| `dex.envVars` | Dex additional environment variables | `[]` | -| `dex.envVars[0].name` | Membership client secret | | -| `dex.envVars[0].valueFrom.secretKeyRef.name` | Membership client secret secret name | | -| `dex.envVars[0].valueFrom.secretKeyRef.key` | Membership client secret key | | -| `dex.configOverrides.issuer` | issuer url | `https://dex.{{ .Values.global.serviceHost }}` | -| `dex.configOverrides.oauth2.skipApprovalScreen` | oauth2 skip approval screen | `true` | -| `dex.configOverrides.oauth2.responseTypes` | oauth2 response types | `["code","token","id_token"]` | -| `dex.configOverrides.logger.format` | logger format | `json` | -| `dex.configOverrides.storage.type` | storage type | `postgres` | -| `dex.configOverrides.storage.config.host` | storage config host | `postgresql.formance-control.svc` | -| `dex.configOverrides.storage.config.port` | storage config port | `5432` | -| `dex.configOverrides.storage.config.database` | storage config database | `formance` | -| `dex.configOverrides.storage.config.user` | storage config user | `formance` | -| `dex.configOverrides.storage.config.password` | storage config password | `formance` | -| `dex.configOverrides.storage.config.ssl.mode` | storage config ssl mode | `disable` | -| `dex.configOverrides.staticClients[0].name` | static clients name | `membership` | -| `dex.configOverrides.staticClients[0].id` | static clients id | `{{ .Values.membership.config.oidc.clientId }}` | -| `dex.configOverrides.staticClients[0].secret` | static clients secret | `{{ tpl .Values.membership.config.oidc.clientSecret $ }}` | -| `dex.configOverrides.staticClients[0].secretEnv` | static clients secret env var, do not use secret and secretEnv at the same time | | -| `dex.configOverrides.staticClients[0].secret` | static clients secret string | | -| `dex.configOverrides.staticClients[0].redirectURIs` | static clients redirect uris | `["https://membership.{{ .Values.global.serviceHost }}/api/authorize/callback"]` | -| `dex.configOverrides.enablePasswordDB` | enable password db | `true` | -| `dex.configOverrides.staticPasswords[0].email` | static passwords email | `admin@formance.com` | -| `dex.configOverrides.staticPasswords[0].hash` | static passwords hash | `$2a$10$2b2cU8CPhOTaGrs1HRQuAueS7JTT5ZHsHSzYiFPm1leZck7Mc8T4W` | -| `dex.configOverrides.staticPasswords[0].username` | static passwords username | `admin` | -| `dex.configOverrides.staticPasswords[0].userID` | static passwords user id | `08a8684b-db88-4b73-90a9-3cd1661f5466` | - -### Postgresql configuration - -| Name | Description | Value | -| ---------------------------------------- | -------------------------------------- | ------------ | -| `postgresql.enabled` | Enable postgresql | `true` | -| `postgresql.fullnameOverride` | Postgresql fullname override | `postgresql` | -| `postgresql.architecture` | Postgresql architecture | `standalone` | -| `postgresql.primary.persistence.enabled` | Postgresql primary persistence enabled | `false` | diff --git a/charts/cloudprem/.helmignore b/charts/cloudprem/.helmignore new file mode 100644 index 0000000..7e64ae2 --- /dev/null +++ b/charts/cloudprem/.helmignore @@ -0,0 +1,27 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ + +build/ + +Earthfile \ No newline at end of file diff --git a/charts/cloudprem/Chart.lock b/charts/cloudprem/Chart.lock new file mode 100644 index 0000000..dd12c30 --- /dev/null +++ b/charts/cloudprem/Chart.lock @@ -0,0 +1,12 @@ +dependencies: +- name: membership + repository: file://../membership + version: v1.0.0-beta.1 +- name: portal + repository: file://../portal + version: v1.0.0-beta.1 +- name: console + repository: file://../console + version: v1.0.0-beta.1 +digest: sha256:8d9e67381f2428636964a2807382aed0f87d01860ae6eeff68fd1250f8fb2ee4 +generated: "2024-10-01T13:42:18.437988+02:00" diff --git a/charts/cloudprem/Chart.yaml b/charts/cloudprem/Chart.yaml new file mode 100644 index 0000000..b802686 --- /dev/null +++ b/charts/cloudprem/Chart.yaml @@ -0,0 +1,58 @@ +annotations: {} +apiVersion: v2 +name: cloudprem +description: |- + Formance control-plane + +home: "https://formance.com" + +maintainers: + - name: "Formance Team" + email: "support@formance.com" +icon: "https://avatars.githubusercontent.com/u/84325077?s=200&v=4" + +keywords: + - cloudprem + - oidc + - portal + - membership + - dex + - portal + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: "v2.0.0-beta.11" + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: "v0.35.3" + +kubeVersion: ">=1.14.0-0" +sources: [] + +dependencies: + - name: membership + version: v1.0.0-beta.1 + repository: file://../membership + condition: membership.enabled + - name: portal + version: v1.0.0-beta.1 + repository: file://../portal + condition: portal.enabled + - name: console + version: v1.0.0-beta.1 + repository: file://../console + condition: console.enabled diff --git a/charts/cloudprem/Earthfile b/charts/cloudprem/Earthfile new file mode 100644 index 0000000..58c7b79 --- /dev/null +++ b/charts/cloudprem/Earthfile @@ -0,0 +1,57 @@ +VERSION --wildcard-builds --wildcard-copy 0.8 + +IMPORT ../console AS console +IMPORT ../membership AS membership +IMPORT ../portal AS portal +IMPORT ../core AS core +IMPORT .. AS charts + +schema: + FROM charts+schema-base + WORKDIR /src + DO --pass-args charts+SCHEMA + +readme: + FROM charts+readme-base + WORKDIR /src/core + COPY --dir (core+dependencies/*) . + WORKDIR /src/console + COPY --dir (console+dependencies/*) . + WORKDIR /src/membership + COPY --dir (membership+dependencies/*) . + WORKDIR /src/portal + COPY --dir (portal+dependencies/*) . + + WORKDIR /src/cloudprem + COPY (+sources/*) . + COPY *.gotmpl . + DO --pass-args charts+README_GENERATOR + +sources: + DO --pass-args charts+BASE + WORKDIR /src/cloudprem + COPY --dir profiles . + COPY (+schema/*) . + DO --pass-args charts+SOURCES + +dependencies: + FROM +sources + WORKDIR /src/console + COPY --dir (console+dependencies/*) . + WORKDIR /src/membership + COPY --dir (membership+dependencies/*) . + WORKDIR /src/portal + COPY --dir (portal+dependencies/*) . + WORKDIR /src/cloudprem + DO --pass-args charts+DEPENDENCIES + +validate: + FROM +dependencies + WORKDIR /src/cloudprem + COPY (+readme/*) . + DO --pass-args charts+VALIDATE + +package: + FROM +validate + WORKDIR /src/cloudprem + DO --pass-args charts+PACKAGE diff --git a/charts/cloudprem/README.md b/charts/cloudprem/README.md new file mode 100644 index 0000000..3f0e662 --- /dev/null +++ b/charts/cloudprem/README.md @@ -0,0 +1,536 @@ +[![Artifact Hub](https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/cloudprem)](https://artifacthub.io/packages/search?repo=cloudprem) +![Version: v2.0.0-beta.11](https://img.shields.io/badge/Version-v2.0.0--beta.11-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: v0.35.3](https://img.shields.io/badge/AppVersion-v0.35.3-informational?style=flat-square) + +# Formance Cloudprem Helm Chart + +Formance control-plane + +[Formance Cloudprem](https://docs.formance.com/deployment/cloudprem2/intro) is a platform that allows you to manage your users, organizations and your data plane. + +## TL;DR + +```bash +export BASE_DOMAIN=example.com +export BASE_DOMAIN_WILDCARD_CERTIFICATE=example-com-wildcard-certificate-tls + +helm install cloudprem oci://ghcr.io/formancehq/helm/cloudprem \ + --set global.serviceHost=$BASE_DOMAIN \ + --set membership.ingress.tls[0].secretName=$BASE_DOMAIN_WILDCARD_CERTIFICATE \ + --set portal.ingress.tls[0].secretName=$BASE_DOMAIN_WILDCARD_CERTIFICATE \ + --set console.ingress.tls[0].secretName=$BASE_DOMAIN_WILDCARD_CERTIFICATE \ + --set dex.ingress.tls[0].secretName=$BASE_DOMAIN_WILDCARD_CERTIFICATE +``` + +## Requirements + +- SSL Certificate (Let's Encrypt or another) +- Public domain according to the certificate authority + +## Control plane components + +- Portal > 0.0.1 +- Console > 2.2.1 +- Membership > 0.28.0 +- Dex > 0.28.0 + +## Introduction + +This chart bootstraps 5 different components that form the Formance Control Plane, additionally you will need to install the Formance Data Plane composed of a Kubernetes Operator. + +In order to deploy the 5 different components, you must have a Kubernetes cluster with an Ingress Controller and valid SSL certificates for the different domains. + +Following components need his own database: + +- [Dex](https://dexidp.io/) (Authentication) +- Membership (User and Organization Management) + +Addtionnaly all components need a public URL that can be accessible through a VPN or a public domain. + +The deployment is done via Helm. Make sure you have Helm installed and configured. + +You will first have to create a `values.yaml` file to define your values. + +> [!TIP] +>By default, a PostgreSQL database is included in this configuration without any data persistence. + +In order to do your first deployment you will need to complete the following steps: +- [Complete de minimal profile](#minimal-valuesyaml-profile) +- [Deploy Operator](https://docs.formance.com/operator/Installation) +- [Deploy Stack](#deploy-stack) +- [Init Cloudprem](#init-cloudprem) + +## Minimal `values.yaml` profile + +> [!IMPORTANT] +> Each certificate must be in the form of `.global.serviceHost` following the example below: +>- Console: `console.{{ .Values.global.serviceHost }}` +>- Portal: `portal.{{ .Values.global.serviceHost }}` +>- Membership: `membership.{{ .Values.global.serviceHost }}` +>- Dex: `dex.{{ .Values.global.serviceHost }}` + +> [!TIP] +> A quick win is to use a wildcard certificate for all the components on `*.{{ .Values.global.serviceHost }}}`. +>Then reference the secret same as the example bellow. + +> [!TIP] +> You can use [Cert Manager](https://cert-manager.io/docs/) to manage your certificates. + +```yaml +global: + serviceHost: "example.com" + +membership: + ingress: + enabled: true + tls: + - secretName: example-com-wildcard-certificate-tls + dex: + ingress: + enabled: true + tls: + - secretName: example-com-wildcard-certificate-tls + +portal: + ingress: + enabled: true + tls: + - secretName: example-com-wildcard-certificate-tls + +console: + ingress: + enabled: true + tls: + - secretName: example-com-wildcard-certificate-tls +``` + +## Init Cloudprem + +### [Install FCTL](https://docs.formance.com/getting-started/fctl-quick-start) + +```bash +# Linux amd64, arm64 +ARCH="amd64"; curl -L -o fctl.tar.gz "https://github.com/formancehq/stack/releases/download/v2.0.9/fctl_linux-$ARCH.tar.gz" \ +&& tar -xvf fctl.tar.gz \ +&& sudo mv fctl /usr/local/bin \ +&& chmod +x /usr/local/bin/fctl \ +&& rm fctl.tar.gz + +# MacOS +brew install formancehq/tap/fctl +brew upgrade fctl + +## Debian +deb [trusted=yes] https://apt.fury.io/formance/ / > /etc/apt/sources.list +apt update && apt install fctl +``` + +```bash +fctl version +``` + +### Login on membership + +> [!IMPORTANT] +> According to [Dex default configuration](#dex-configuration), you can login with the following credentials: `admin@formance.com` / `password`. +> You can also define `Google`, `Github`, `Microsoft` as OAuth2 connectors. +> Additional configuration can be found on the [Dex documentation](https://artifacthub.io/packages/helm/dex/dex). + +```bash +export BASE_DOMAIN=example.com +fctl -p $user login --membership-uri https://membership.$BASE_DOMAIN/api +``` + +This will create a new organization and a user. + +### (Optional) Add domain for Auto Login + +```bash + +export EMAIL_DOMAIN_NAME=example.com + +fctl cloud organizations list + +fctl cloud organizations update ORGANIZATION_ID --domain=$EMAIL_DOMAIN_NAME --default-organization-role=GUEST --default-stack-role=GUEST + +fctl cloud organizations list +``` + +The possible values are GUEST or ADMIN. +This allows to give the rights by default to a user who logs in with an email of domain DOMAIN_NAME. + +### Init databases + +```sql +insert into membership."regions" (id, base_url, name, creator_id, created_at, production, active) values ( + gen_random_uuid(), + 'https://${BASE_DOMAIN}', + 'default', + (select id from membership."users" where id = ( + select owner_id from membership."organizations" limit 1 + )), + now(), + true, + true +); +insert into membership."stacks" (name, organization_id, id, region_id, created_at, updated_at, stargate_enabled, client_secret, state, status, expected_status) values ( + 'default', + (select id from membership."organizations" limit 1), + 'sdgsd', -- update if needed, this is your stack id + (select id from membership."regions" limit 1), + now(), + now(), + false, + gen_random_uuid(), + 'ACTIVE', + 'READY', + 'READY' +); +``` + +This query will create the region and a stack associated with your organization in that region. + +### Create stack on cluster + +After creating your region and your Stack. +You can retrieve your Organization ID and search & replace the value of your organization ID and BASE_URL + +> [!IMPORTANT] +> In the Formance CRDs, the stack name has format `-` + +```bash +cat <<"EOF" > stack.sh + +#!bin/bash + +export BASE_DOMAIN_WILDCARD_CERTIFICATE=example-com-wildcard-certificate-tls +export BASE_DOMAIN=example.com +export ORGANIZATION_ID=ylzsigispivc +export STACK_ID=sdgs + +cat < stack.yaml +--- +apiVersion: formance.com/v1beta1 +kind: Stack +metadata: + name: ${ORGANIZATION_ID}-${STACK_ID} +spec: + debug: true + dev: true + versionsFromFile: v2.0 +--- +apiVersion: formance.com/v1beta1 +kind: Gateway +metadata: + name: ${ORGANIZATION_ID}-${STACK_ID} +spec: + stack: ${ORGANIZATION_ID}-${STACK_ID} + ingress: + host: ${ORGANIZATION_ID}-${STACK_ID}.${BASE_DOMAIN} + scheme: https + tls: + secretName: ${BASE_DOMAIN_WILDCARD_CERTIFICATE} +--- +apiVersion: formance.com/v1beta1 +kind: Ledger +metadata: + name: ${ORGANIZATION_ID}-${STACK_ID} +spec: + stack: ${ORGANIZATION_ID}-${STACK_ID} +--- +apiVersion: formance.com/v1beta1 +kind: Auth +metadata: + name: ${ORGANIZATION_ID}-${STACK_ID} +spec: + stack: ${ORGANIZATION_ID}-${STACK_ID} + enableScopes: true + delegatedOIDCServer: + clientID: stack_${ORGANIZATION_ID}_${STACK_ID} + clientSecret: changeMe + issuer: $(echo "https://membership.${BASE_DOMAIN}/api") +EOF + +echo "EOF" >> stack.sh && bash stack.sh + +kubectl apply -f . /stack.yml +``` + +Here, we have deployed a basic configuration. Refer to [the Formance operator documentation](https://docs.formance.com/operator/Requirements) for information on possible configurations. + +### Access to the portal + +```bash +fctl ui +``` + +## Migration + +### From v1.0.X To v2.0.X + +A global configuration has been introduced to manage values accross different services. To see the detail of the default values, please refer to the [Global Parameters](#global-configuration) section. + +Each platform key has beeen moved to `global.platform` + +This chart adds several `existingSecret` and `secretKeys` to manage the secret either from configuration or from a secret. + +The chart is now divided into 5 charts : +- Portal +- Console +- Membership +- Dex +- Postgresql + +#### Breaking changes by service + +Labels: +- Each service now depends on the global configuration to manage the labels according to [Kubernetes best recommendations](https://kubernetes.io/docs/concepts/overview/working-with-objects/common-labels/) + - Each service now has a static `app.kubernetes.io/app` label overridable by with `..nameOverride` +- By default, if not using the `..nameOverride`, the `app.kubernetes.io/name` changes and will generate a breaking change in the deployment. + - To keep the default behavior managed by formance. Make sure if you use the embedded postgresql to `Retain` the related volume. Then you can `helm uninstall --namespace ` and then `helm install --namespace `. (If you change the name of the release, make sure to bind the `postgresql.primary.persistence.existingClaim` accordingly) + - This change permits Formance to help you debugging any resources created by the chart by using the `app.kubernetes.io/name`, and ``app.kubernetes.io/instance` labels. (then with `kubectl get pods -l app.kubernetes.io/name=console -A` or all the instance `kubectl get pods -l app.kubernetes.io/instance=cloudprem -A`) + +Global: +- `.serviceAccount` has been removed, use `..serviceAccount` instead. +- `.commonLabels` has been removed + +Console: +- `.console.membership` has been removed, and is now managed through the `.global.platform.membership.oauthClient`. It's going to be used by all platform services. + +Dex: +- `.dex.config` has been moved `.dex.configOverrides` enabled by default with`.dex.createConfigSecretOverrides` allowing templating. + +#### Others changes + +Console: +- `.console.config.stargate_url` has been removed, it will be managed for a kubernetes service. +- `.console.config.feature_disabled` has been removed, it will be managed through `.console.config.additionalEnv.FEATURE_DISABLED`. +- `.console.config.managed_stack` has been removed, console now manage the stack through portal. +- `.console.config.database` has been removed, console now manage the session through portal cookie. + +- `.console.config.redirect_url` has been deprecated, it is now templated with `https://console.global.serviceHost`. +- `.console.config.encryption_key` has been deprecated, it will be managed through `.global.platform.cookie.encryptionKey`. + +Membership: +- `.membership.config.url` has been removed, it will be templated through `.global.platform.membership.host` and `.global.platform.membership.scheme` +- `.membership.config.postgresqlUrl` has been deprecated, it will be mangaged through `.global.postgresql.auth`. +- OAuth clients are now managed within the template and disablable with `.global.platform.enabled`, `.membership.config.fctl`. Additionaly, you can add new client with `.membership.config.additionalOAuthClients` + +Dex: + +- `.dex.envVars` and `.dex.configOverrides.staticClients.[].secretEnv` can be used together to set static clients secrets. + +**Homepage:** + +## Maintainers + +| Name | Email | Url | +| ---- | ------ | --- | +| Formance Team | | | + +## Values + +### Global configuration + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| global.aws.iam | bool | `false` | Enable AWS IAM Authentification | +| global.debug | bool | `false` | Enable debug mode | +| global.monitoring.logs.enabled | bool | `true` | Enable logging | +| global.monitoring.logs.format | string | `"json"` | Format | +| global.monitoring.logs.level | string | `"info"` | Level: Info, Debug, Error | +| global.monitoring.traces.enabled | bool | `false` | Enable otel tracing | +| global.monitoring.traces.endpoint | string | `"localhost"` | Endpoint | +| global.monitoring.traces.exporter | string | `"otlp"` | Exporter | +| global.monitoring.traces.insecure | bool | `true` | Insecure | +| global.monitoring.traces.mode | string | `"grpc"` | Mode | +| global.monitoring.traces.port | int | `4317` | Port | +| global.platform.console.host | string | `"console.{{ .Values.global.serviceHost }}"` | is the host for the console | +| global.platform.console.scheme | string | `"https"` | is the scheme for the console | +| global.platform.cookie.encryptionKey | string | `"changeMe00"` | is used to encrypt a cookie that share authentication between platform services (console, portal, ...),is used to store the current state organizationId-stackId | +| global.platform.cookie.existingSecret | string | `""` | is the name of the secret | +| global.platform.cookie.secretKeys | object | `{"encryptionKey":""}` | is the key contained within the secret | +| global.platform.enabled | bool | `true` | Enable platform oauth2 client | +| global.platform.membership.host | string | `"membership.{{ .Values.global.serviceHost }}"` | is the host for the membership | +| global.platform.membership.oauthClient.existingSecret | string | `""` | is the name of the secret | +| global.platform.membership.oauthClient.id | string | `"platform"` | is the id of the client | +| global.platform.membership.oauthClient.secret | string | `"changeMe1"` | is the secret of the client | +| global.platform.membership.oauthClient.secretKeys | object | `{"secret":""}` | is the key contained within the secret | +| global.platform.membership.relyingParty.host | string | `"dex.{{ .Values.global.serviceHost }}"` | is the host for the membership | +| global.platform.membership.relyingParty.scheme | string | `"https"` | is the scheme for the membership | +| global.platform.membership.scheme | string | `"https"` | is the scheme for the membership | +| global.platform.portal.host | string | `"portal.{{ .Values.global.serviceHost }}"` | is the host for the portal | +| global.platform.portal.scheme | string | `"https"` | is the scheme for the portal | +| global.postgresql.additionalArgs | string | `"sslmode=disable"` | Additional arguments for PostgreSQL Connection URI | +| global.postgresql.auth.database | string | `"formance"` | Name for a custom database to create (overrides `auth.database`) | +| global.postgresql.auth.existingSecret | string | `""` | Name of existing secret to use for PostgreSQL credentials (overrides `auth.existingSecret`). | +| global.postgresql.auth.password | string | `"formance"` | Password for the "postgres" admin user (overrides `auth.postgresPassword`) | +| global.postgresql.auth.postgresPassword | string | `"formance"` | Password for the custom user to create (overrides `auth.password`) | +| global.postgresql.auth.secretKeys.adminPasswordKey | string | `""` | Name of key in existing secret to use for PostgreSQL credentials (overrides `auth.secretKeys.adminPasswordKey`). Only used when `global.postgresql.auth.existingSecret` is set. | +| global.postgresql.auth.secretKeys.userPasswordKey | string | `""` | Name of key in existing secret to use for PostgreSQL credentials (overrides `auth.secretKeys.userPasswordKey`). Only used when `global.postgresql.auth.existingSecret` is set. | +| global.postgresql.auth.username | string | `"formance"` | Name for a custom user to create (overrides `auth.username`) | +| global.postgresql.host | string | `""` | Host for PostgreSQL (overrides included postgreql `host`) | +| global.postgresql.service.ports.postgresql | int | `5432` | PostgreSQL service port (overrides `service.ports.postgresql`) | +| global.serviceHost | string | `""` | is the base domain for portal and console | + +### Dex configuration + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| membership.dex.configOverrides | object | `{"enablePasswordDB":true,"oauth2":{"responseTypes":["code","token","id_token"],"skipApprovalScreen":true},"staticPasswords":[{"email":"admin@formance.com","hash":"$2a$10$2b2cU8CPhOTaGrs1HRQuAueS7JTT5ZHsHSzYiFPm1leZck7Mc8T4W","userID":"08a8684b-db88-4b73-90a9-3cd1661f5466","username":"admin"}]}` | Config override allow template function. Database is setup on the chart global, make sure that user/password when using kubernetes secret | +| membership.dex.configOverrides.enablePasswordDB | bool | `true` | enable password db | +| membership.dex.configOverrides.oauth2.responseTypes | list | `["code","token","id_token"]` | oauth2 response types | +| membership.dex.configOverrides.oauth2.skipApprovalScreen | bool | `true` | oauth2 skip approval screen | +| membership.dex.configOverrides.staticPasswords[0].email | string | `"admin@formance.com"` | static passwords email | +| membership.dex.configOverrides.staticPasswords[0].hash | string | `"$2a$10$2b2cU8CPhOTaGrs1HRQuAueS7JTT5ZHsHSzYiFPm1leZck7Mc8T4W"` | static passwords hash | +| membership.dex.configOverrides.staticPasswords[0].userID | string | `"08a8684b-db88-4b73-90a9-3cd1661f5466"` | static passwords user id | +| membership.dex.configOverrides.staticPasswords[0].username | string | `"admin"` | static passwords username | +| membership.dex.configSecret.create | bool | `false` | Dex config secret create Default secret provided by the dex chart | +| membership.dex.configSecret.createConfigSecretOverrides | bool | `true` | Dex config secret create config secret overrides Enable secret config overrides provided by the cloudprem chart | +| membership.dex.configSecret.name | string | `"membership-dex-config"` | Dex config secret name | +| membership.dex.enabled | bool | `true` | Enable dex | +| membership.dex.envVars | list | `[]` | Dex additional environment variables | +| membership.dex.image.pullPolicy | string | `"IfNotPresent"` | image pull policy | +| membership.dex.image.repository | string | `"ghcr.io/formancehq/dex"` | image repository | +| membership.dex.image.tag | string | `"v0.33.10"` | image tag | +| membership.dex.ingress.annotations | object | `{}` | Dex ingress annotations | +| membership.dex.ingress.className | string | `""` | Dex ingress class name | +| membership.dex.ingress.enabled | bool | `true` | Dex ingress enabled | +| membership.dex.ingress.hosts[0].host | string | `"{{ tpl .Values.global.platform.membership.relyingParty.host $ }}"` | Dex ingress host | +| membership.dex.ingress.hosts[0].paths[0].path | string | `"/"` | Dex ingress path | +| membership.dex.ingress.hosts[0].paths[0].pathType | string | `"Prefix"` | Dex ingress path type | +| membership.dex.ingress.tls | list | `[]` | Dex ingress tls | +| membership.dex.resources | object | `{}` | Dex resources | + +### Postgresql configuration + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| membership.postgresql.architecture | string | `"standalone"` | Postgresql architecture | +| membership.postgresql.enabled | bool | `true` | Enable postgresql | +| membership.postgresql.fullnameOverride | string | `"postgresql"` | Postgresql fullname override | +| membership.postgresql.primary | object | `{"persistence":{"enabled":false}}` | Postgresql primary persistence enabled | + +### Other Values + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| global.platform.membership.oidc.host | string | `"dex.{{ .Values.global.serviceHost }}"` | is the host for the oidc | +| global.platform.membership.oidc.scheme | string | `"https"` | is the scheme for the issuer | +| console.affinity | object | `{}` | Console affinity | +| console.config.additionalEnv | object | `{}` | Console additional environment variables | +| console.config.environment | string | `"production"` | Console environment | +| console.config.monitoring | object | `{"traces":{"attributes":"","enabled":false,"port":4317,"url":""}}` | Otel collector configuration | +| console.config.monitoring.traces.attributes | string | `""` | Console monitoring traces attributes | +| console.config.monitoring.traces.enabled | bool | `false` | Console monitoring traces enabled | +| console.config.monitoring.traces.port | int | `4317` | Console monitoring traces port | +| console.config.monitoring.traces.url | string | `""` | Console monitoring traces url | +| console.image.pullPolicy | string | `"IfNotPresent"` | image pull policy | +| console.image.repository | string | `"ghcr.io/formancehq/console"` | image repository | +| console.image.tag | string | `"9431e5f4b4b1a03cb8f02ef1676507b9c023f2bb"` | image tag | +| console.ingress.annotations | object | `{}` | ingress annotations | +| console.ingress.className | string | `""` | ingress class name | +| console.ingress.enabled | bool | `true` | ingress enabled | +| console.ingress.hosts[0] | object | `{"host":"{{ tpl .Values.global.platform.console.host $ }}","paths":[{"path":"/","pathType":"Prefix"}]}` | ingress host | +| console.ingress.hosts[0].paths[0].path | string | `"/"` | ingress path | +| console.ingress.hosts[0].paths[0].pathType | string | `"Prefix"` | ingress path type | +| console.ingress.tls | list | `[]` | ingress tls | +| console.livenessProbe | object | `{}` | Console liveness probe | +| console.nodeSelector | object | `{}` | Console node selector | +| console.readinessProbe | object | `{}` | Console readiness probe | +| console.replicas | int | `1` | Number of replicas | +| console.resources | object | `{}` | Console resources | +| console.service.annotations | object | `{}` | service annotations | +| console.service.clusterIP | string | `""` | service cluster IP | +| console.service.ports.http | object | `{"port":3000}` | service http port | +| console.service.type | string | `"ClusterIP"` | service type | +| console.serviceAccount.annotations | object | `{}` | Service account annotations | +| console.serviceAccount.create | bool | `true` | Service account creation | +| console.serviceAccount.name | string | `""` | Service account name | +| console.tolerations | list | `[]` | Console tolerations | +| console.volumeMounts | list | `[]` | Console volume mounts | +| console.volumes | list | `[]` | Console volumes | +| membership.affinity | object | `{}` | Membership affinity | +| membership.autoscaling | object | `{}` | Membership autoscaling | +| membership.commonLabels | object | `{}` | DEPRECATED Membership service | +| membership.config.additionalOAuthClients | list | `[]` | | +| membership.config.fctl | bool | `true` | Enable Fctl | +| membership.config.migration.annotations | object | `{"helm.sh/hook":"pre-upgrade","helm.sh/hook-delete-policy":"before-hook-creation,hook-succeeded,hook-failed"}` | Membership migration annotations | +| membership.config.migration.annotations."helm.sh/hook" | string | `"pre-upgrade"` | Membership migration helm hook | +| membership.config.migration.annotations."helm.sh/hook-delete-policy" | string | `"before-hook-creation,hook-succeeded,hook-failed"` | Membership migration hook delete policy | +| membership.config.oidc | object | `{"clientId":"membership","clientSecret":"changeMe","existingSecret":"","secretKeys":{"secret":""}}` | DEPRECATED Membership postgresql connection url postgresqlUrl: "postgresql://formance:formance@postgresql.formance-control.svc:5432/formance?sslmode=disable" | +| membership.config.oidc.clientId | string | `"membership"` | Membership oidc client id | +| membership.config.oidc.clientSecret | string | `"changeMe"` | Membership oidc client secret | +| membership.config.oidc.existingSecret | string | `""` | Membership oidc existing secret | +| membership.config.oidc.secretKeys | object | `{"secret":""}` | Membership oidc secret key | +| membership.debug | bool | `false` | Membership debug | +| membership.dev | bool | `false` | Membership dev | +| membership.feature.disableEvents | bool | `true` | Membership feature disable events | +| membership.feature.managedStacks | bool | `true` | Membership feature managed stacks | +| membership.fullnameOverride | string | `""` | Membership fullname override | +| membership.image.pullPolicy | string | `"IfNotPresent"` | Membership image pull policy | +| membership.image.repository | string | `"ghcr.io/formancehq/membership"` | Membership image repository | +| membership.image.tag | string | `""` | Membership image tag | +| membership.imagePullSecrets | list | `[]` | Membership image pull secrets | +| membership.ingress.annotations | object | `{}` | Membership ingress annotations | +| membership.ingress.className | string | `""` | Membership ingress class name | +| membership.ingress.enabled | bool | `true` | Membership ingress enabled | +| membership.ingress.hosts[0] | object | `{"host":"{{ tpl .Values.global.platform.membership.host $ }}","paths":[{"path":"/api","pathType":"Prefix"}]}` | Membership ingress host | +| membership.ingress.hosts[0].paths[0].path | string | `"/api"` | Membership ingress path | +| membership.ingress.hosts[0].paths[0].pathType | string | `"Prefix"` | Membership ingress path type | +| membership.ingress.tls | list | `[]` | Membership ingress tls | +| membership.initContainers | list | `[]` | Membership init containers | +| membership.nameOverride | string | `""` | Membership name override | +| membership.nodeSelector | object | `{}` | Membership node selector | +| membership.podSecurityContext | object | `{}` | Membership pod security context | +| membership.replicaCount | int | `1` | Count of replicas | +| membership.resources | object | `{}` | Membership resources | +| membership.securityContext.capabilities | object | `{"drop":["ALL"]}` | Membership security context capabilities drop | +| membership.securityContext.readOnlyRootFilesystem | bool | `true` | Membership security context read only root filesystem | +| membership.securityContext.runAsNonRoot | bool | `true` | Membership security context run as non root | +| membership.securityContext.runAsUser | int | `1000` | Membership security context run as user | +| membership.service.annotations | object | `{}` | service annotations | +| membership.service.clusterIP | string | `""` | service cluster IP | +| membership.service.ports.grpc | object | `{"port":8082}` | service grpc port | +| membership.service.ports.http | object | `{"port":8080}` | service http port | +| membership.service.type | string | `"ClusterIP"` | service type | +| membership.serviceAccount.annotations | object | `{}` | Service account annotations | +| membership.serviceAccount.create | bool | `true` | Service account creation | +| membership.serviceAccount.name | string | `""` | Service account name | +| membership.tolerations | list | `[]` | Membership tolerations | +| membership.volumeMounts | list | `[]` | Membership volume mounts | +| membership.volumes | list | `[]` | Membership volumes | +| portal.affinity | object | `{}` | Portal affinity | +| portal.config.additionalEnv | object | `{}` | Additional environment variables | +| portal.config.cookie.existingSecret | string | `""` | Cookie existing secret | +| portal.config.cookie.secret | string | `"changeMe2"` | Cookie secret | +| portal.config.cookie.secretKeys | object | `{"secret":""}` | Cookie secret key | +| portal.config.environment | string | `"production"` | Portal environment | +| portal.image.pullPolicy | string | `"IfNotPresent"` | image pull policy | +| portal.image.repository | string | `"ghcr.io/formancehq/membership-ui"` | image repository | +| portal.image.tag | string | `"764bb7e199e1d2882e4d5cd205eada0ef0abc283"` | image tag | +| portal.ingress.annotations | object | `{}` | ingress annotations | +| portal.ingress.className | string | `""` | ingress class name | +| portal.ingress.enabled | bool | `true` | ingress enabled | +| portal.ingress.hosts[0] | object | `{"host":"{{ tpl .Values.global.platform.portal.host $ }}","paths":[{"path":"/","pathType":"Prefix"}]}` | ingress host | +| portal.ingress.hosts[0].paths[0].path | string | `"/"` | ingress path | +| portal.ingress.hosts[0].paths[0].pathType | string | `"Prefix"` | ingress path type | +| portal.ingress.tls | list | `[]` | ingress tls | +| portal.livenessProbe | object | `{}` | Portal liveness probe | +| portal.nodeSelector | object | `{}` | Portal node selector | +| portal.podDisruptionBudget.enabled | bool | `false` | Enable pod disruption budget | +| portal.podDisruptionBudget.maxUnavailable | int | `0` | Maximum unavailable pods | +| portal.podDisruptionBudget.minAvailable | int | `1` | Minimum available pods | +| portal.readinessProbe | object | `{}` | Portal readiness probe | +| portal.replicas | int | `1` | Number of replicas | +| portal.resources | object | `{}` | Portal resources | +| portal.service.annotations | object | `{}` | service annotations | +| portal.service.clusterIP | string | `""` | service cluster IP | +| portal.service.ports.http | object | `{"port":3000}` | service http port | +| portal.service.type | string | `"ClusterIP"` | service type | +| portal.serviceAccount.annotations | object | `{}` | Service account annotations | +| portal.serviceAccount.create | bool | `true` | Service account creation | +| portal.serviceAccount.name | string | `""` | Service account name | +| portal.tolerations | list | `[]` | Portal tolerations | +| portal.volumeMounts | list | `[]` | Portal volume mounts | +| portal.volumes | list | `[]` | Portal volumes | + diff --git a/charts/cloudprem/README.md.gotmpl b/charts/cloudprem/README.md.gotmpl new file mode 100644 index 0000000..19ddbf0 --- /dev/null +++ b/charts/cloudprem/README.md.gotmpl @@ -0,0 +1,335 @@ +[![Artifact Hub](https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/cloudprem)](https://artifacthub.io/packages/search?repo=cloudprem) +{{ template "chart.badgesSection" . }} + +# Formance Cloudprem Helm Chart + +{{ template "chart.description" . }} + +[Formance Cloudprem](https://docs.formance.com/deployment/cloudprem2/intro) is a platform that allows you to manage your users, organizations and your data plane. + +## TL;DR + +```bash +export BASE_DOMAIN=example.com +export BASE_DOMAIN_WILDCARD_CERTIFICATE=example-com-wildcard-certificate-tls + +helm install cloudprem oci://ghcr.io/formancehq/helm/cloudprem \ + --set global.serviceHost=$BASE_DOMAIN \ + --set membership.ingress.tls[0].secretName=$BASE_DOMAIN_WILDCARD_CERTIFICATE \ + --set portal.ingress.tls[0].secretName=$BASE_DOMAIN_WILDCARD_CERTIFICATE \ + --set console.ingress.tls[0].secretName=$BASE_DOMAIN_WILDCARD_CERTIFICATE \ + --set dex.ingress.tls[0].secretName=$BASE_DOMAIN_WILDCARD_CERTIFICATE +``` + +## Requirements + +- SSL Certificate (Let's Encrypt or another) +- Public domain according to the certificate authority + +## Control plane components + +- Portal > 0.0.1 +- Console > 2.2.1 +- Membership > 0.28.0 +- Dex > 0.28.0 + +## Introduction + +This chart bootstraps 5 different components that form the Formance Control Plane, additionally you will need to install the Formance Data Plane composed of a Kubernetes Operator. + +In order to deploy the 5 different components, you must have a Kubernetes cluster with an Ingress Controller and valid SSL certificates for the different domains. + +Following components need his own database: + +- [Dex](https://dexidp.io/) (Authentication) +- Membership (User and Organization Management) + +Addtionnaly all components need a public URL that can be accessible through a VPN or a public domain. + +The deployment is done via Helm. Make sure you have Helm installed and configured. + +You will first have to create a `values.yaml` file to define your values. + +> [!TIP] +>By default, a PostgreSQL database is included in this configuration without any data persistence. + +In order to do your first deployment you will need to complete the following steps: +- [Complete de minimal profile](#minimal-valuesyaml-profile) +- [Deploy Operator](https://docs.formance.com/operator/Installation) +- [Deploy Stack](#deploy-stack) +- [Init Cloudprem](#init-cloudprem) + +## Minimal `values.yaml` profile + +> [!IMPORTANT] +> Each certificate must be in the form of `.global.serviceHost` following the example below: +>- Console: `console.{{ printf "{{ .Values.global.serviceHost }}" }}` +>- Portal: `portal.{{ printf "{{ .Values.global.serviceHost }}" }}` +>- Membership: `membership.{{ printf "{{ .Values.global.serviceHost }}" }}` +>- Dex: `dex.{{ printf "{{ .Values.global.serviceHost }}" }}` + +> [!TIP] +> A quick win is to use a wildcard certificate for all the components on `*.{{ printf "{{ .Values.global.serviceHost }}" }}}`. +>Then reference the secret same as the example bellow. + +> [!TIP] +> You can use [Cert Manager](https://cert-manager.io/docs/) to manage your certificates. + +```yaml +global: + serviceHost: "example.com" + +membership: + ingress: + enabled: true + tls: + - secretName: example-com-wildcard-certificate-tls + dex: + ingress: + enabled: true + tls: + - secretName: example-com-wildcard-certificate-tls + +portal: + ingress: + enabled: true + tls: + - secretName: example-com-wildcard-certificate-tls + +console: + ingress: + enabled: true + tls: + - secretName: example-com-wildcard-certificate-tls +``` + + +## Init Cloudprem + +### [Install FCTL](https://docs.formance.com/getting-started/fctl-quick-start) + +```bash +# Linux amd64, arm64 +ARCH="amd64"; curl -L -o fctl.tar.gz "https://github.com/formancehq/stack/releases/download/v2.0.9/fctl_linux-$ARCH.tar.gz" \ +&& tar -xvf fctl.tar.gz \ +&& sudo mv fctl /usr/local/bin \ +&& chmod +x /usr/local/bin/fctl \ +&& rm fctl.tar.gz + +# MacOS +brew install formancehq/tap/fctl +brew upgrade fctl + + +## Debian +deb [trusted=yes] https://apt.fury.io/formance/ / > /etc/apt/sources.list +apt update && apt install fctl +``` + +```bash +fctl version +``` + +### Login on membership + +> [!IMPORTANT] +> According to [Dex default configuration](#dex-configuration), you can login with the following credentials: `admin@formance.com` / `password`. +> You can also define `Google`, `Github`, `Microsoft` as OAuth2 connectors. +> Additional configuration can be found on the [Dex documentation](https://artifacthub.io/packages/helm/dex/dex). + +```bash +export BASE_DOMAIN=example.com +fctl -p $user login --membership-uri https://membership.$BASE_DOMAIN/api +``` + +This will create a new organization and a user. + +### (Optional) Add domain for Auto Login + +```bash + +export EMAIL_DOMAIN_NAME=example.com + +fctl cloud organizations list + +fctl cloud organizations update ORGANIZATION_ID --domain=$EMAIL_DOMAIN_NAME --default-organization-role=GUEST --default-stack-role=GUEST + +fctl cloud organizations list +``` + +The possible values are GUEST or ADMIN. +This allows to give the rights by default to a user who logs in with an email of domain DOMAIN_NAME. + +### Init databases + +```sql +insert into membership."regions" (id, base_url, name, creator_id, created_at, production, active) values ( + gen_random_uuid(), + 'https://${BASE_DOMAIN}', + 'default', + (select id from membership."users" where id = ( + select owner_id from membership."organizations" limit 1 + )), + now(), + true, + true +); +insert into membership."stacks" (name, organization_id, id, region_id, created_at, updated_at, stargate_enabled, client_secret, state, status, expected_status) values ( + 'default', + (select id from membership."organizations" limit 1), + 'sdgsd', -- update if needed, this is your stack id + (select id from membership."regions" limit 1), + now(), + now(), + false, + gen_random_uuid(), + 'ACTIVE', + 'READY', + 'READY' +); +``` + +This query will create the region and a stack associated with your organization in that region. + +### Create stack on cluster + +After creating your region and your Stack. +You can retrieve your Organization ID and search & replace the value of your organization ID and BASE_URL + +> [!IMPORTANT] +> In the Formance CRDs, the stack name has format `-` + +```bash +cat <<"EOF" > stack.sh + +#!bin/bash + +export BASE_DOMAIN_WILDCARD_CERTIFICATE=example-com-wildcard-certificate-tls +export BASE_DOMAIN=example.com +export ORGANIZATION_ID=ylzsigispivc +export STACK_ID=sdgs + +cat < stack.yaml +--- +apiVersion: formance.com/v1beta1 +kind: Stack +metadata: + name: ${ORGANIZATION_ID}-${STACK_ID} +spec: + debug: true + dev: true + versionsFromFile: v2.0 +--- +apiVersion: formance.com/v1beta1 +kind: Gateway +metadata: + name: ${ORGANIZATION_ID}-${STACK_ID} +spec: + stack: ${ORGANIZATION_ID}-${STACK_ID} + ingress: + host: ${ORGANIZATION_ID}-${STACK_ID}.${BASE_DOMAIN} + scheme: https + tls: + secretName: ${BASE_DOMAIN_WILDCARD_CERTIFICATE} +--- +apiVersion: formance.com/v1beta1 +kind: Ledger +metadata: + name: ${ORGANIZATION_ID}-${STACK_ID} +spec: + stack: ${ORGANIZATION_ID}-${STACK_ID} +--- +apiVersion: formance.com/v1beta1 +kind: Auth +metadata: + name: ${ORGANIZATION_ID}-${STACK_ID} +spec: + stack: ${ORGANIZATION_ID}-${STACK_ID} + enableScopes: true + delegatedOIDCServer: + clientID: stack_${ORGANIZATION_ID}_${STACK_ID} + clientSecret: changeMe + issuer: $(echo "https://membership.${BASE_DOMAIN}/api") +EOF + +echo "EOF" >> stack.sh && bash stack.sh + +kubectl apply -f . /stack.yml +``` + +Here, we have deployed a basic configuration. Refer to [the Formance operator documentation](https://docs.formance.com/operator/Requirements) for information on possible configurations. + +### Access to the portal + +```bash +fctl ui +``` + +## Migration + +### From v1.0.X To v2.0.X + +A global configuration has been introduced to manage values accross different services. To see the detail of the default values, please refer to the [Global Parameters](#global-configuration) section. + +Each platform key has beeen moved to `global.platform` + +This chart adds several `existingSecret` and `secretKeys` to manage the secret either from configuration or from a secret. + +The chart is now divided into 5 charts : +- Portal +- Console +- Membership +- Dex +- Postgresql + +#### Breaking changes by service + +Labels: +- Each service now depends on the global configuration to manage the labels according to [Kubernetes best recommendations](https://kubernetes.io/docs/concepts/overview/working-with-objects/common-labels/) + - Each service now has a static `app.kubernetes.io/app` label overridable by with `..nameOverride` +- By default, if not using the `..nameOverride`, the `app.kubernetes.io/name` changes and will generate a breaking change in the deployment. + - To keep the default behavior managed by formance. Make sure if you use the embedded postgresql to `Retain` the related volume. Then you can `helm uninstall --namespace ` and then `helm install --namespace `. (If you change the name of the release, make sure to bind the `postgresql.primary.persistence.existingClaim` accordingly) + - This change permits Formance to help you debugging any resources created by the chart by using the `app.kubernetes.io/name`, and ``app.kubernetes.io/instance` labels. (then with `kubectl get pods -l app.kubernetes.io/name=console -A` or all the instance `kubectl get pods -l app.kubernetes.io/instance=cloudprem -A`) + +Global: +- `.serviceAccount` has been removed, use `..serviceAccount` instead. +- `.commonLabels` has been removed + +Console: +- `.console.membership` has been removed, and is now managed through the `.global.platform.membership.oauthClient`. It's going to be used by all platform services. + +Dex: +- `.dex.config` has been moved `.dex.configOverrides` enabled by default with`.dex.createConfigSecretOverrides` allowing templating. + +#### Others changes + +Console: +- `.console.config.stargate_url` has been removed, it will be managed for a kubernetes service. +- `.console.config.feature_disabled` has been removed, it will be managed through `.console.config.additionalEnv.FEATURE_DISABLED`. +- `.console.config.managed_stack` has been removed, console now manage the stack through portal. +- `.console.config.database` has been removed, console now manage the session through portal cookie. + +- `.console.config.redirect_url` has been deprecated, it is now templated with `https://console.global.serviceHost`. +- `.console.config.encryption_key` has been deprecated, it will be managed through `.global.platform.cookie.encryptionKey`. + +Membership: +- `.membership.config.url` has been removed, it will be templated through `.global.platform.membership.host` and `.global.platform.membership.scheme` +- `.membership.config.postgresqlUrl` has been deprecated, it will be mangaged through `.global.postgresql.auth`. +- OAuth clients are now managed within the template and disablable with `.global.platform.enabled`, `.membership.config.fctl`. Additionaly, you can add new client with `.membership.config.additionalOAuthClients` + +Dex: + +- `.dex.envVars` and `.dex.configOverrides.staticClients.[].secretEnv` can be used together to set static clients secrets. + + +{{ template "chart.deprecationWarning" . }} + +{{ template "chart.homepageLine" . }} + +{{ template "chart.maintainersSection" . }} + +{{ template "chart.sourcesSection" . }} + +{{ template "chart.valuesSection" . }} + +{{ template "helm-docs.versionFooter" . }} diff --git a/charts/cloudprem/profiles/aws.yaml b/charts/cloudprem/profiles/aws.yaml new file mode 100644 index 0000000..bed8c2d --- /dev/null +++ b/charts/cloudprem/profiles/aws.yaml @@ -0,0 +1,9 @@ +global: + aws: + iam: true + + +membership: + serviceAccount: + annotations: + eks.amazonaws.com/role-arn: "" \ No newline at end of file diff --git a/charts/cloudprem/profiles/mapping-all-secrets.yaml b/charts/cloudprem/profiles/mapping-all-secrets.yaml new file mode 100644 index 0000000..661fcb6 --- /dev/null +++ b/charts/cloudprem/profiles/mapping-all-secrets.yaml @@ -0,0 +1,50 @@ +# Before all: +# This chart use only 1 db user account, you might to have separated user for dex/membership. You are allowed to bind any secret and override the default config under .membership.dex.configOverrides with template +# +# Make sure to create the following secret within the release namespace: +# - platform +# - membership +# - postgresql-credentials +# - portal +# You will be able to rename them accordingly +# + +global: + platform: + cookie: + existingSecret: "platform" + secretKeys: + encryptionKey: "encryption-key" + membership: + oauthClient: + existingSecret: "platform" + secretKeys: + secret: "membership-oauth-client-secret" + + postgresql: + auth: + existingSecret: "postgresql-credentials" + secretKeys: + adminPasswordKey: "admin-password" + userPasswordKey: "admin-password" + +portal: + config: + cookie: + existingSecret: "portal-secrets" + secretKeys: + secret: "portal-cookie-secret" + +membership: + dex: + envVars: + - name: MEMBERSHIP_CLIENT_SECRET + valueFrom: + secretKeyRef: + name: "membership" + key: "dex-client-secret" + - name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: "postgresql-credentials" + key: "admin-password" diff --git a/charts/cloudprem/profiles/minimal.yaml b/charts/cloudprem/profiles/minimal.yaml new file mode 100644 index 0000000..c5a5bb1 --- /dev/null +++ b/charts/cloudprem/profiles/minimal.yaml @@ -0,0 +1,25 @@ +global: + serviceHost: "example.com" + +membership: + ingress: + enabled: true + tls: + - secretName: example-com-wildcard-certificate-tls + dex: + ingress: + enabled: true + tls: + - secretName: example-com-wildcard-certificate-tls + +portal: + ingress: + enabled: true + tls: + - secretName: example-com-wildcard-certificate-tls + +console: + ingress: + enabled: true + tls: + - secretName: example-com-wildcard-certificate-tls diff --git a/charts/cloudprem/values.schema.json b/charts/cloudprem/values.schema.json new file mode 100644 index 0000000..b0d2510 --- /dev/null +++ b/charts/cloudprem/values.schema.json @@ -0,0 +1,207 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "global": { + "properties": { + "aws": { + "properties": { + "iam": { + "type": "boolean" + } + }, + "type": "object" + }, + "debug": { + "type": "boolean" + }, + "monitoring": { + "properties": { + "logs": { + "properties": { + "enabled": { + "type": "boolean" + }, + "format": { + "type": "string" + }, + "level": { + "type": "string" + } + }, + "type": "object" + }, + "traces": { + "properties": { + "enabled": { + "type": "boolean" + }, + "endpoint": { + "type": "string" + }, + "exporter": { + "type": "string" + }, + "insecure": { + "type": "boolean" + }, + "mode": { + "type": "string" + }, + "port": { + "type": "integer" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "platform": { + "properties": { + "console": { + "properties": { + "host": { + "type": "string" + }, + "scheme": { + "type": "string" + } + }, + "type": "object" + }, + "enabled": { + "type": "boolean" + }, + "membership": { + "properties": { + "host": { + "type": "string" + }, + "oauthClient": { + "properties": { + "existingSecret": { + "type": "string" + }, + "id": { + "type": "string" + }, + "secret": { + "type": "string" + }, + "secretKeys": { + "properties": { + "secret": { + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "oidc": { + "properties": { + "host": { + "type": "string" + }, + "scheme": { + "type": "string" + } + }, + "type": "object" + }, + "relyingParty": { + "properties": { + "host": { + "type": "string" + }, + "scheme": { + "type": "string" + } + }, + "type": "object" + }, + "scheme": { + "type": "string" + } + }, + "type": "object" + }, + "portal": { + "properties": { + "host": { + "type": "string" + }, + "scheme": { + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "postgresql": { + "properties": { + "additionalArgs": { + "type": "string" + }, + "auth": { + "properties": { + "database": { + "type": "string" + }, + "existingSecret": { + "type": "string" + }, + "password": { + "type": "string" + }, + "postgresPassword": { + "type": "string" + }, + "secretKeys": { + "properties": { + "adminPasswordKey": { + "type": "string" + }, + "userPasswordKey": { + "type": "string" + } + }, + "type": "object" + }, + "username": { + "type": "string" + } + }, + "type": "object" + }, + "host": { + "type": "string" + }, + "service": { + "properties": { + "ports": { + "properties": { + "postgresql": { + "type": "integer" + } + }, + "type": "object" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "serviceHost": { + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object" +} diff --git a/charts/cloudprem/values.yaml b/charts/cloudprem/values.yaml new file mode 100644 index 0000000..6893179 --- /dev/null +++ b/charts/cloudprem/values.yaml @@ -0,0 +1,135 @@ +global: + # -- Enable debug mode + # @section -- Global configuration + debug: false + # -- is the base domain for portal and console + # @section -- Global configuration + serviceHost: "" + + aws: + # -- Enable AWS IAM Authentification + # @section -- Global configuration + iam: false + + platform: + # -- Enable platform oauth2 client + # @section -- Global configuration + enabled: true + console: + # -- is the scheme for the console + # @section -- Global configuration + scheme: "https" + # -- is the host for the console + # @section -- Global configuration + host: "console.{{ .Values.global.serviceHost }}" + portal: + # -- is the scheme for the portal + # @section -- Global configuration + scheme: "https" + # -- is the host for the portal + # @section -- Global configuration + host: "portal.{{ .Values.global.serviceHost }}" + membership: + # -- is the scheme for the membership + # @section -- Global configuration + scheme: "https" + # -- is the host for the membership + # @section -- Global configuration + host: "membership.{{ .Values.global.serviceHost }}" + + relyingParty: + # -- is the scheme for the membership + # @section -- Global configuration + scheme: "https" + # -- is the host for the membership + # @section -- Global configuration + host: "dex.{{ .Values.global.serviceHost }}" + + oauthClient: + # -- is the id of the client + # @section -- Global configuration + id: "platform" + # -- is the secret of the client + # @section -- Global configuration + secret: "changeMe1" + # -- is the name of the secret + # @section -- Global configuration + existingSecret: "" + # -- is the key contained within the secret + # @section -- Global configuration + secretKeys: + secret: "" + + oidc: + # -- is the scheme for the issuer + scheme: "https" + # -- is the host for the oidc + host: "dex.{{ .Values.global.serviceHost }}" + + monitoring: + traces: + # -- Enable otel tracing + # @section -- Global configuration + enabled: false + # -- Endpoint + # @section -- Global configuration + endpoint: "localhost" + # -- Exporter + # @section -- Global configuration + exporter: "otlp" + # -- Insecure + # @section -- Global configuration + insecure: true + # -- Mode + # @section -- Global configuration + mode: "grpc" + # -- Port + # @section -- Global configuration + port: 4317 + logs: + # -- Enable logging + # @section -- Global configuration + enabled: true + # -- Level: Info, Debug, Error + # @section -- Global configuration + level: "info" + # -- Format + # @section -- Global configuration + format: "json" + + postgresql: + # -- Host for PostgreSQL (overrides included postgreql `host`) + # @section -- Global configuration + host: "" + # -- Additional arguments for PostgreSQL Connection URI + # @section -- Global configuration + additionalArgs: "sslmode=disable" + auth: + # -- Name for a custom user to create (overrides `auth.username`) + # @section -- Global configuration + username: formance + # -- Password for the "postgres" admin user (overrides `auth.postgresPassword`) + # @section -- Global configuration + password: formance + # -- Name for a custom database to create (overrides `auth.database`) + # @section -- Global configuration + database: formance + # -- Password for the custom user to create (overrides `auth.password`) + # @section -- Global configuration + postgresPassword: formance + # -- Name of existing secret to use for PostgreSQL credentials (overrides `auth.existingSecret`). + # @section -- Global configuration + existingSecret: "" + secretKeys: + # -- Name of key in existing secret to use for PostgreSQL credentials (overrides `auth.secretKeys.adminPasswordKey`). Only used when `global.postgresql.auth.existingSecret` is set. + # @section -- Global configuration + adminPasswordKey: "" + # -- Name of key in existing secret to use for PostgreSQL credentials (overrides `auth.secretKeys.userPasswordKey`). Only used when `global.postgresql.auth.existingSecret` is set. + # @section -- Global configuration + userPasswordKey: "" + + service: + ports: + # -- PostgreSQL service port (overrides `service.ports.postgresql`) + # @section -- Global configuration + postgresql: 5432 diff --git a/charts/console/.helmignore b/charts/console/.helmignore new file mode 100644 index 0000000..0e8a0eb --- /dev/null +++ b/charts/console/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/charts/console/Chart.lock b/charts/console/Chart.lock new file mode 100644 index 0000000..fe99dcc --- /dev/null +++ b/charts/console/Chart.lock @@ -0,0 +1,6 @@ +dependencies: +- name: core + repository: file://../core + version: v1.0.0-beta.1 +digest: sha256:476f02fc2e76ae3a272789ded9cf18ab9a286f6f8b6266cc183bb1930f19d9e4 +generated: "2024-09-20T15:12:17.919454769Z" diff --git a/charts/console/Chart.yaml b/charts/console/Chart.yaml new file mode 100644 index 0000000..4ae9633 --- /dev/null +++ b/charts/console/Chart.yaml @@ -0,0 +1,39 @@ +apiVersion: v2 +name: console +description: Formance Console + +home: "https://formance.com" + +maintainers: + - name: "Formance Team" + email: "support@formance.com" +icon: "https://avatars.githubusercontent.com/u/84325077?s=200&v=4" + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: "v1.0.0-beta.1" + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: "9431e5f4b4b1a03cb8f02ef1676507b9c023f2bb" +kubeVersion: ">=1.14.0-0" +sources: + - https://github.com/formancehq/console + +dependencies: + - name: core + version: "v1.0.0-beta.1" + repository: file://../core diff --git a/charts/console/Earthfile b/charts/console/Earthfile new file mode 100644 index 0000000..f897bb6 --- /dev/null +++ b/charts/console/Earthfile @@ -0,0 +1,41 @@ +VERSION --wildcard-builds --wildcard-copy 0.8 + +IMPORT ../ AS charts +IMPORT ../core AS core + +schema: + FROM charts+schema-base + WORKDIR /src + DO --pass-args charts+SCHEMA + +readme: + FROM charts+readme-base + WORKDIR /src/core + COPY --dir (core+dependencies/*) . + WORKDIR /src/console + COPY --dir (+dependencies/*) . + DO --pass-args charts+README_GENERATOR + +sources: + DO --pass-args charts+BASE + WORKDIR /src/console + COPY (+schema/*) . + DO --pass-args charts+SOURCES + +dependencies: + FROM +sources + WORKDIR /src/core + COPY --dir (core+dependencies/*) . + WORKDIR /src/console + DO --pass-args charts+DEPENDENCIES + +validate: + FROM +dependencies + WORKDIR /src/console + COPY (+readme/*) . + DO --pass-args charts+VALIDATE + +package: + FROM +validate + WORKDIR /src/console + DO --pass-args charts+PACKAGE diff --git a/charts/console/README.md b/charts/console/README.md new file mode 100644 index 0000000..6ca7e48 --- /dev/null +++ b/charts/console/README.md @@ -0,0 +1,86 @@ +# console + +![Version: v1.0.0-beta.1](https://img.shields.io/badge/Version-v1.0.0--beta.1-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 9431e5f4b4b1a03cb8f02ef1676507b9c023f2bb](https://img.shields.io/badge/AppVersion-9431e5f4b4b1a03cb8f02ef1676507b9c023f2bb-informational?style=flat-square) + +Formance Console + +**Homepage:** + +## Maintainers + +| Name | Email | Url | +| ---- | ------ | --- | +| Formance Team | | | + +## Source Code + +* + +## Requirements + +Kubernetes: `>=1.14.0-0` + +| Repository | Name | Version | +|------------|------|---------| +| file://../core | core | v1.0.0-beta.1 | + +## Values + +### Global configuration + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| global.debug | bool | `false` | Enable debug mode | +| global.platform.console.host | string | `"console.{{ .Values.global.serviceHost }}"` | is the host for the console | +| global.platform.console.scheme | string | `"https"` | is the scheme for the console | +| global.platform.cookie.encryptionKey | string | `"changeMe00"` | is used to encrypt a cookie that share authentication between platform services (console, portal, ...),is used to store the current state organizationId-stackId | +| global.platform.cookie.existingSecret | string | `""` | is the name of the secret | +| global.platform.cookie.secretKeys | object | `{"encryptionKey":""}` | is the key contained within the secret | +| global.platform.membership.host | string | `"membership.{{ .Values.global.serviceHost }}"` | is the host for the membership | +| global.platform.membership.oauthClient.existingSecret | string | `""` | is the name of the secret | +| global.platform.membership.oauthClient.id | string | `"platform"` | is the id of the client | +| global.platform.membership.oauthClient.secret | string | `"changeMe1"` | is the secret of the client | +| global.platform.membership.oauthClient.secretKeys | object | `{"secret":""}` | is the key contained within the secret | +| global.platform.membership.scheme | string | `"https"` | is the scheme for the membership | +| global.platform.portal.host | string | `"portal.{{ .Values.global.serviceHost }}"` | is the host for the portal | +| global.platform.portal.scheme | string | `"https"` | is the scheme for the portal | +| global.serviceHost | string | `""` | is the base domain for portal and console | + +### Other Values + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| affinity | object | `{}` | Console affinity | +| config.additionalEnv | object | `{}` | Console additional environment variables | +| config.environment | string | `"production"` | Console environment | +| config.monitoring | object | `{"traces":{"attributes":"","enabled":false,"port":4317,"url":""}}` | Otel collector configuration | +| config.monitoring.traces.attributes | string | `""` | Console monitoring traces attributes | +| config.monitoring.traces.enabled | bool | `false` | Console monitoring traces enabled | +| config.monitoring.traces.port | int | `4317` | Console monitoring traces port | +| config.monitoring.traces.url | string | `""` | Console monitoring traces url | +| image.pullPolicy | string | `"IfNotPresent"` | image pull policy | +| image.repository | string | `"ghcr.io/formancehq/console"` | image repository | +| image.tag | string | `"9431e5f4b4b1a03cb8f02ef1676507b9c023f2bb"` | image tag | +| ingress.annotations | object | `{}` | ingress annotations | +| ingress.className | string | `""` | ingress class name | +| ingress.enabled | bool | `true` | ingress enabled | +| ingress.hosts[0] | object | `{"host":"{{ tpl .Values.global.platform.console.host $ }}","paths":[{"path":"/","pathType":"Prefix"}]}` | ingress host | +| ingress.hosts[0].paths[0].path | string | `"/"` | ingress path | +| ingress.hosts[0].paths[0].pathType | string | `"Prefix"` | ingress path type | +| ingress.tls | list | `[]` | ingress tls | +| livenessProbe | object | `{}` | Console liveness probe | +| nodeSelector | object | `{}` | Console node selector | +| readinessProbe | object | `{}` | Console readiness probe | +| replicas | int | `1` | Number of replicas | +| resources | object | `{}` | Console resources | +| service.annotations | object | `{}` | service annotations | +| service.clusterIP | string | `""` | service cluster IP | +| service.ports.http | object | `{"port":3000}` | service http port | +| service.type | string | `"ClusterIP"` | service type | +| serviceAccount.annotations | object | `{}` | Service account annotations | +| serviceAccount.create | bool | `true` | Service account creation | +| serviceAccount.name | string | `""` | Service account name | +| tolerations | list | `[]` | Console tolerations | +| volumeMounts | list | `[]` | Console volume mounts | +| volumes | list | `[]` | Console volumes | + diff --git a/charts/console/templates/_helpers.tpl b/charts/console/templates/_helpers.tpl new file mode 100644 index 0000000..9f72958 --- /dev/null +++ b/charts/console/templates/_helpers.tpl @@ -0,0 +1,102 @@ + +{{/** + + # Stargate + # API URL is either Stargate gRPC Gateway or the stack gateway to stacks + # It can be external or internal + # + # Console: + # NODE_ENV is the environment of the app + # REDIRECT_URI is the url to redirect after login + # + # Platform_ Cookie: + # ENCRYPTION_KEY is the key to encrypt the auth cookies + # COOKIE_DOMAIN is the domain to set the auth cookies + # UNSECURE_COOKIES is a flag to set the cookies as secure or not + # + # Portal: + # PLATFORM_URL is the url to redirect after logout + # + # Membership: + # MEMBERSHIP_CLIENT_ID is the client id of the membership api + # MEMBERSHIP_CLIENT_SECRET is the client secret of the membership api + # MEMBERSHIP_URL_API is the url to the membership api + # + # Monitoring: + # OTEL_TRACES is a flag to enable tracing + # OTEL_TRACES_ENDPOINT is the url to the tracing endpoint + # OTEL_TRACES_EXPORTER is the exporter to use + # OTEL_TRACES_EXPORTER_OTLP_ENDPOINT is the url to the tracing endpoint + # OTEL_TRACES_EXPORTER_OTLP_INSECURE is a flag to set the exporter as insecure + # OTEL_TRACES_EXPORTER_OTLP_MODE is the mode to use + # OTEL_TRACES_PORT is the port to use + # OTEL_RESOURCE_ATTRIBUTES is the attributes to set + +**/}} +{{- define "console.env" -}} +- name: API_URL + value: {{ (default "http://gateway.#{organizationId}-#{stackId}.svc:8080/api" .Values.config.stargate_url) }} +- name: REDIRECT_URI + value: {{ tpl (default (printf "%s://%s" .Values.global.platform.console.scheme .Values.global.platform.console.host) .Values.config.redirect_url) $ }} +- name: ENCRYPTION_KEY + {{- if .Values.global.platform.cookie.existingSecret }} + valueFrom: + secretKeyRef: + name: {{ .Values.global.platform.cookie.existingSecret }} + key: {{ .Values.global.platform.cookie.secretKeys.encryptionKey }} + {{- else }} + value: {{ .Values.global.platform.cookie.encryptionKey | default .Values.config.encryption_key | quote }} + {{- end }} +- name: NODE_ENV + value: {{ .Values.config.environment }} +- name: PLATFORM_URL + value: {{ tpl (default (printf "%s://%s" .Values.global.platform.portal.scheme .Values.global.platform.portal.host) .Values.config.platform_url) $ }} +- name: UNSECURE_COOKIES + value: "false" +- name: COOKIE_DOMAIN + value: {{ .Values.global.serviceHost }} +- name: MEMBERSHIP_CLIENT_ID + value: "{{ .Values.global.platform.membership.oauthClient.id }}" +- name: MEMBERSHIP_CLIENT_SECRET + {{- if gt (len .Values.global.platform.membership.oauthClient.existingSecret) 0 }} + valueFrom: + secretKeyRef: + name: {{ .Values.global.platform.membership.oauthClient.existingSecret }} + key: {{ .Values.global.platform.membership.oauthClient.secretKeys.secret }} + {{- else }} + value: {{ .Values.global.platform.membership.oauthClient.secret | quote }} + {{- end }} +- name: MEMBERSHIP_URL_API + value: {{ tpl (printf "%s://%s/api" .Values.global.platform.membership.scheme .Values.global.platform.membership.host) $}} +{{ include "console.otel.env" . }} +{{ include "console.additionalEnv" . }} +{{- end }} + +{{- define "console.otel.env" -}} +- name: OTEL_TRACES + value: "{{ .Values.config.monitoring.traces.enabled }}" +{{- if .Values.config.monitoring.traces.enabled }} +- name: OTEL_TRACES_ENDPOINT + value: "{{ .Values.config.monitoring.traces.url }}" +- name: OTEL_TRACES_EXPORTER + value: "otlp" +- name: OTEL_TRACES_EXPORTER_OTLP_ENDPOINT + value: "{{ .Values.config.monitoring.traces.url }}:{{ .Values.config.monitoring.traces.port }}" +- name: OTEL_TRACES_EXPORTER_OTLP_INSECURE + value: "true" +- name: OTEL_TRACES_EXPORTER_OTLP_MODE + value: "grpc" +- name: OTEL_TRACES_PORT + value: "{{ .Values.config.monitoring.traces.port }}" +- name: OTEL_RESOURCE_ATTRIBUTES + value: "{{ .Values.config.monitoring.traces.attributes }}" +{{- end }} +{{- end }} + + +{{- define "console.additionalEnv" -}} +{{ range $key, $val := .Values.config.additionalEnv }} +- name: {{ $key }} + value: {{ tpl (tpl $val $) $ | quote}} +{{- end }} +{{- end}} \ No newline at end of file diff --git a/charts/console/templates/deployment.yaml b/charts/console/templates/deployment.yaml new file mode 100644 index 0000000..873bc77 --- /dev/null +++ b/charts/console/templates/deployment.yaml @@ -0,0 +1,65 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "core.fullname" . }} + labels: + {{- include "core.labels" . | nindent 4 }} +spec: + replicas: {{ .Values.replicas }} + selector: + matchLabels: + {{- include "core.selectorLabels" . | nindent 6 }} + template: + metadata: + labels: + {{- include "core.selectorLabels" . | nindent 8 }} + spec: + serviceAccountName: {{ include "core.serviceAccountName" . }} + containers: + - name: console + command: + - yarn + - run + - start + resources: + {{- toYaml .Values.resources | nindent 12 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{.Values.image.pullPolicy}} + ports: + - containerPort: 3000 + protocol: TCP + name: http + readinessProbe: + httpGet: + path: /_info + port: 3000 + initialDelaySeconds: {{ .Values.readinessProbe.initialDelaySeconds | default 30}} + periodSeconds: 3 + livenessProbe: + httpGet: + path: /_info + port: 3000 + initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds | default 30}} + periodSeconds: 3 + env: + {{- include "console.env" . | nindent 12 }} + {{ with .Values.volumeMounts }} + volumeMounts: + {{- toYaml . | nindent 12 }} + {{- end }} + {{ with .Values.volumes }} + volumes: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/charts/console/templates/ingress.yaml b/charts/console/templates/ingress.yaml new file mode 100644 index 0000000..37f2294 --- /dev/null +++ b/charts/console/templates/ingress.yaml @@ -0,0 +1 @@ +{{ include "core.ingress" . }} diff --git a/charts/console/templates/service.yaml b/charts/console/templates/service.yaml new file mode 100644 index 0000000..02ecdf2 --- /dev/null +++ b/charts/console/templates/service.yaml @@ -0,0 +1,28 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "core.fullname" . }} + labels: + {{- include "core.labels" . | nindent 4 }} + {{- with .Values.service.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + type: {{ .Values.service.type }} + {{- with .Values.service.clusterIP }} + clusterIP: {{ . }} + {{- end }} + ports: + - name: http + port: {{ .Values.service.ports.http.port }} + {{- if and (or (eq .Values.service.type "NodePort") (eq .Values.service.type "LoadBalancer")) .Values.service.ports.http.nodePort }} + nodePort: {{ .Values.service.ports.http.nodePort }} + {{- end }} + targetPort: http + protocol: TCP + {{- if semverCompare ">=1.20-0" .Capabilities.KubeVersion.GitVersion }} + appProtocol: http + {{- end }} + selector: + {{- include "core.selectorLabels" . | nindent 4 }} \ No newline at end of file diff --git a/charts/console/templates/serviceaccount.yaml b/charts/console/templates/serviceaccount.yaml new file mode 100644 index 0000000..b4c7924 --- /dev/null +++ b/charts/console/templates/serviceaccount.yaml @@ -0,0 +1 @@ +{{- include "core.serviceAccount" . -}} diff --git a/charts/console/values.schema.json b/charts/console/values.schema.json new file mode 100644 index 0000000..d1767bd --- /dev/null +++ b/charts/console/values.schema.json @@ -0,0 +1,262 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "affinity": { + "properties": {}, + "type": "object" + }, + "config": { + "properties": { + "additionalEnv": { + "properties": {}, + "type": "object" + }, + "environment": { + "type": "string" + }, + "monitoring": { + "properties": { + "traces": { + "properties": { + "attributes": { + "type": "string" + }, + "enabled": { + "type": "boolean" + }, + "port": { + "type": "integer" + }, + "url": { + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "global": { + "properties": { + "debug": { + "type": "boolean" + }, + "platform": { + "properties": { + "console": { + "properties": { + "host": { + "type": "string" + }, + "scheme": { + "type": "string" + } + }, + "type": "object" + }, + "cookie": { + "properties": { + "encryptionKey": { + "type": "string" + }, + "existingSecret": { + "type": "string" + }, + "secretKeys": { + "properties": { + "encryptionKey": { + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "membership": { + "properties": { + "host": { + "type": "string" + }, + "oauthClient": { + "properties": { + "existingSecret": { + "type": "string" + }, + "id": { + "type": "string" + }, + "secret": { + "type": "string" + }, + "secretKeys": { + "properties": { + "secret": { + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "scheme": { + "type": "string" + } + }, + "type": "object" + }, + "portal": { + "properties": { + "host": { + "type": "string" + }, + "scheme": { + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "serviceHost": { + "type": "string" + } + }, + "type": "object" + }, + "image": { + "properties": { + "pullPolicy": { + "type": "string" + }, + "repository": { + "type": "string" + }, + "tag": { + "type": "string" + } + }, + "type": "object" + }, + "ingress": { + "properties": { + "annotations": { + "properties": {}, + "type": "object" + }, + "className": { + "type": "string" + }, + "enabled": { + "type": "boolean" + }, + "hosts": { + "items": { + "properties": { + "host": { + "type": "string" + }, + "paths": { + "items": { + "properties": { + "path": { + "type": "string" + }, + "pathType": { + "type": "string" + } + }, + "type": "object" + }, + "type": "array" + } + }, + "type": "object" + }, + "type": "array" + }, + "tls": { + "type": "array" + } + }, + "type": "object" + }, + "livenessProbe": { + "properties": {}, + "type": "object" + }, + "nodeSelector": { + "properties": {}, + "type": "object" + }, + "readinessProbe": { + "properties": {}, + "type": "object" + }, + "replicas": { + "type": "integer" + }, + "resources": { + "properties": {}, + "type": "object" + }, + "service": { + "properties": { + "annotations": { + "properties": {}, + "type": "object" + }, + "clusterIP": { + "type": "string" + }, + "ports": { + "properties": { + "http": { + "properties": { + "port": { + "type": "integer" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "type": { + "type": "string" + } + }, + "type": "object" + }, + "serviceAccount": { + "properties": { + "annotations": { + "properties": {}, + "type": "object" + }, + "create": { + "type": "boolean" + }, + "name": { + "type": "string" + } + }, + "type": "object" + }, + "tolerations": { + "type": "array" + }, + "volumeMounts": { + "type": "array" + }, + "volumes": { + "type": "array" + } + }, + "type": "object" +} diff --git a/charts/console/values.yaml b/charts/console/values.yaml new file mode 100644 index 0000000..85068e8 --- /dev/null +++ b/charts/console/values.yaml @@ -0,0 +1,165 @@ +global: + # -- Enable debug mode + # @section -- Global configuration + debug: false + # -- is the base domain for portal and console + # @section -- Global configuration + serviceHost: "" + + platform: + console: + # -- is the scheme for the console + # @section -- Global configuration + scheme: "https" + # -- is the host for the console + # @section -- Global configuration + host: "console.{{ .Values.global.serviceHost }}" + portal: + # -- is the scheme for the portal + # @section -- Global configuration + scheme: "https" + # -- is the host for the portal + # @section -- Global configuration + host: "portal.{{ .Values.global.serviceHost }}" + cookie: + # -- is used to encrypt a cookie that share authentication between platform services (console, portal, ...),is used to store the current state organizationId-stackId + # @section -- Global configuration + encryptionKey: "changeMe00" + # -- is the name of the secret + # @section -- Global configuration + existingSecret: "" + # -- is the key contained within the secret + # @section -- Global configuration + secretKeys: + encryptionKey: "" + + membership: + # -- is the scheme for the membership + # @section -- Global configuration + scheme: "https" + # -- is the host for the membership + # @section -- Global configuration + host: "membership.{{ .Values.global.serviceHost }}" + + oauthClient: + # -- is the id of the client + # @section -- Global configuration + id: "platform" + # -- is the secret of the client + # @section -- Global configuration + secret: "changeMe1" + # -- is the name of the secret + # @section -- Global configuration + existingSecret: "" + # -- is the key contained within the secret + # @section -- Global configuration + secretKeys: + secret: "" + +# -- Number of replicas +replicas: 1 + +serviceAccount: + # -- Service account creation + create: true + # -- Service account name + name: "" + # -- Service account annotations + annotations: {} + +image: + # -- image repository + repository: ghcr.io/formancehq/console + # -- image pull policy + pullPolicy: IfNotPresent + # -- image tag + tag: 9431e5f4b4b1a03cb8f02ef1676507b9c023f2bb + +# -- Console resources +resources: + {} + # requests: + # cpu: 250m + # memory: 512Mi + # limits: + # cpu: 500m + # memory: 2048Mi + +# -- Console readiness probe +readinessProbe: {} + +# -- Console liveness probe +livenessProbe: {} + +service: + # -- service annotations + annotations: {} + # -- service type + type: ClusterIP + # -- service cluster IP + clusterIP: "" + ports: + # -- service http port + http: + port: 3000 + # -- service node port + # nodePort: + +ingress: + # -- ingress enabled + enabled: true + # -- ingress class name + className: "" + # -- ingress annotations + annotations: {} + hosts: + # -- ingress host + - host: "{{ tpl .Values.global.platform.console.host $ }}" + paths: + - # -- ingress path + path: / + # -- ingress path type + pathType: Prefix + # -- ingress tls + tls: + [] + # -- ingress tls secret name + # - secretName: YOUR_TLS_SECRET_NAME + +# -- Console volume mounts +volumeMounts: [] + +# -- Console volumes +volumes: [] + +# -- Console node selector +nodeSelector: {} + +# -- Console tolerations +tolerations: [] + +# -- Console affinity +affinity: {} + +config: + # -- Console environment + environment: production + # -- Console additional environment variables + additionalEnv: + {} + # -- Console additional environment variables HOST + # HOST: "0.0.0.0" + # -- Console additional environment variables FEATURE_DISABLED + # FEATURE_DISABLED: "" + + # -- Otel collector configuration + monitoring: + traces: + # -- Console monitoring traces enabled + enabled: false + # -- Console monitoring traces url + url: "" + # -- Console monitoring traces port + port: 4317 + # -- Console monitoring traces attributes + attributes: "" diff --git a/charts/core/Chart.yaml b/charts/core/Chart.yaml new file mode 100644 index 0000000..ed70471 --- /dev/null +++ b/charts/core/Chart.yaml @@ -0,0 +1,16 @@ +apiVersion: v2 +name: core +description: Formance Core Library +home: "https://formance.com" + +keywords: + - core + +maintainers: + - name: "Formance Team" + email: "support@formance.com" +icon: "https://avatars.githubusercontent.com/u/84325077?s=200&v=4" + +type: library +version: "v1.0.0-beta.1" +appVersion: "latest" diff --git a/charts/core/Earthfile b/charts/core/Earthfile new file mode 100644 index 0000000..eee5735 --- /dev/null +++ b/charts/core/Earthfile @@ -0,0 +1,39 @@ +VERSION --wildcard-builds --wildcard-copy 0.8 + +IMPORT .. AS charts + +schema: + FROM charts+schema-base + WORKDIR /src + DO --pass-args charts+SCHEMA + +readme: + FROM charts+readme-base + WORKDIR /src/core + COPY --dir (+sources/*) . + DO --pass-args charts+README_GENERATOR + +sources: + DO --pass-args charts+BASE + WORKDIR /src/core + COPY (+schema/*) . + DO --pass-args charts+SOURCES + +validate: + FROM +dependencies + RUN echo "No validation required" + +dependencies: + FROM +sources + WORKDIR /src/core + DO --pass-args charts+DEPENDENCIES + +lint: + FROM +dependencies + WORKDIR /src/core + DO --pass-args charts+LINT + +package: + FROM +lint + WORKDIR /src/core + DO --pass-args charts+PACKAGE diff --git a/charts/core/README.md b/charts/core/README.md new file mode 100644 index 0000000..9de0a01 --- /dev/null +++ b/charts/core/README.md @@ -0,0 +1,14 @@ +# core + +![Version: v1.0.0-beta.1](https://img.shields.io/badge/Version-v1.0.0--beta.1-informational?style=flat-square) ![Type: library](https://img.shields.io/badge/Type-library-informational?style=flat-square) ![AppVersion: latest](https://img.shields.io/badge/AppVersion-latest-informational?style=flat-square) + +Formance Core Library + +**Homepage:** + +## Maintainers + +| Name | Email | Url | +| ---- | ------ | --- | +| Formance Team | | | + diff --git a/charts/core/templates/_aws-iam.tpl b/charts/core/templates/_aws-iam.tpl new file mode 100644 index 0000000..c2be8d4 --- /dev/null +++ b/charts/core/templates/_aws-iam.tpl @@ -0,0 +1,20 @@ +{{/** + This now can be included in every chart folowing: + + global: + aws: + iam: + enabled: true + + Each component who want to use IAM should include this snippet in their deployment.yaml + We could decline it in the future to be more specific to a service + + + **/}} + +{{- define "aws.iam.postgres" }} +{{- if .Values.global.aws.iam }} +- name: POSTGRES_AWS_ENABLE_IAM + value: "true" +{{- end }} +{{- end }} diff --git a/charts/core/templates/_aws-tgb.tpl b/charts/core/templates/_aws-tgb.tpl new file mode 100644 index 0000000..292984c --- /dev/null +++ b/charts/core/templates/_aws-tgb.tpl @@ -0,0 +1,43 @@ +{{/** + This now can be included in every chart folowing: + + global: + aws: + elb: + enabled: true + + aws: + targetGroups: + http: + ipAddressType: ipv4 + targetType: ip + serviceRefName: "{{ include "core.fullname" . }}-http" + serviceRefPort: "{{ .Values.service.port }}" + targetGroup: "" + grpc: + ipAddressType: ipv4 + targetType: ip + +**/}} + + +{{- define "aws.tgb.generics" -}} +{{ $ := index . 0 }} +{{ $data := index . 1 }} +{{- range $k,$v := $data }} +apiVersion: elbv2.k8s.aws/v1beta1 +kind: TargetGroupBinding +metadata: + name: {{ include "core.fullname" $ }}-{{ $k }} + labels: + {{- include "core.labels" $ | nindent 4 }} +spec: + ipAddressType: {{ $v.ipAddressType }} + serviceRef: + name: {{ tpl $v.serviceRef.name $ }} + port: {{ tpl $v.serviceRef.port $ }} + targetGroupARN: {{ $v.targetGroupARN }} + targetType: {{ $v.targetType }} +--- +{{- end -}} +{{- end -}} \ No newline at end of file diff --git a/charts/core/templates/_broker.tpl b/charts/core/templates/_broker.tpl new file mode 100644 index 0000000..b2bafb6 --- /dev/null +++ b/charts/core/templates/_broker.tpl @@ -0,0 +1,31 @@ +{{/** + This now can be included in every chart folowing: + + # Global configuration + global: + nats: + enabled: true + url: "nats://nats:4222" + + # Service specific + nats: + clientID: "publisher" + topicMapping: "topic1:subject1,topic2:subject2" + + # We could integrate with a nats chart like bitami and follow their construction + +**/}} + + +{{- define "core.nats.env" -}} +{{- if .Values.global.nats.enabled }} +- name: PUBLISHER_NATS_ENABLED + value: '{{ .Values.global.nats.enabled }}' +- name: PUBLISHER_NATS_URL + value: "{{ .Values.global.nats.url }}" +- name: PUBLISHER_NATS_CLIENT_ID + value: "{{ .Values.config.nats.clientID}}" +- name: PUBLISHER_TOPIC_MAPPING + value: "{{ .Values.config.nats.topicMapping }}" +{{- end -}} +{{- end -}} \ No newline at end of file diff --git a/charts/core/templates/_helpers.tpl b/charts/core/templates/_helpers.tpl new file mode 100644 index 0000000..b384ea8 --- /dev/null +++ b/charts/core/templates/_helpers.tpl @@ -0,0 +1,45 @@ +{{/* + .Values: Values to search within + .Key: Key to find in the .config. then in . + .Default: default an object where each key is a string +*/}} +{{- define "resolveGlobalOrServiceValue" -}} + {{- $values := .Values -}} + {{- $key := .Key -}} + {{- $default := .Default -}} + + {{- $keys := splitList "." $key -}} + + + {{- $configkeys := splitList "." (print "config." $key) -}} + {{- $subchartValue := $values -}} + {{- $found := true -}} + {{- range $configkeys -}} + {{- if hasKey $subchartValue . -}} + {{- $subchartValue = index $subchartValue . -}} + {{- else -}} + {{- $found = false -}} + {{- break -}} + {{- end -}} + {{- end -}} + + {{- if not $found -}} + {{- $subchartValue = $values.global -}} + {{- $found = true -}} + {{- range $keys -}} + {{- if hasKey $subchartValue . -}} + {{- $subchartValue = index $subchartValue . -}} + {{- else -}} + {{- $subchartValue = $default -}} + {{- $found = false -}} + {{- break -}} + {{- end -}} + {{- end -}} + {{- end -}} + + {{- if not $found -}} + {{- $subchartValue = $default -}} + {{- end -}} + + {{- $subchartValue -}} +{{- end -}} diff --git a/charts/core/templates/_hpa.tpl b/charts/core/templates/_hpa.tpl new file mode 100644 index 0000000..b973fcb --- /dev/null +++ b/charts/core/templates/_hpa.tpl @@ -0,0 +1,34 @@ +{{- define "core.hpa" -}} +{{- if .Values.autoscaling.enabled }} +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "core.fullname" . }} + labels: + {{- include "core.labels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "core.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 }} + {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} + {{- end }} +{{- end }} +{{- end -}} \ No newline at end of file diff --git a/charts/core/templates/_ingress.tpl b/charts/core/templates/_ingress.tpl new file mode 100644 index 0000000..b55fdc2 --- /dev/null +++ b/charts/core/templates/_ingress.tpl @@ -0,0 +1,63 @@ +{{- define "core.ingress" -}} +{{- if .Values.ingress.enabled -}} +{{- $fullName := include "core.fullname" . -}} +{{- $svcPort := .Values.service.ports.http.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 }} +{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1 + {{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1beta1 + {{- else -}} +apiVersion: extensions/v1beta1 + {{- end }} +kind: Ingress +metadata: + name: {{ include "core.fullname" . }} + labels: + {{- include "core.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 }} + - {{ tpl . $ | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ tpl .host $ | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + {{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }} + pathType: {{ .pathType }} + {{- end }} + backend: + {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} + service: + name: {{ $fullName }} + port: + number: {{ $svcPort }} + {{- else }} + serviceName: {{ $fullName }} + servicePort: {{ $svcPort }} + {{- end }} + {{- end }} + {{- end }} +{{- end }} +{{- end -}} \ No newline at end of file diff --git a/charts/core/templates/_labels.tpl b/charts/core/templates/_labels.tpl new file mode 100644 index 0000000..e19f574 --- /dev/null +++ b/charts/core/templates/_labels.tpl @@ -0,0 +1,19 @@ +{{/* +Common labels +*/}} +{{- define "core.labels" -}} +helm.sh/chart: {{ include "core.chart" . }} +{{ include "core.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "core.selectorLabels" -}} +app.kubernetes.io/name: {{ include "core.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} diff --git a/charts/core/templates/_monitoring.tpl b/charts/core/templates/_monitoring.tpl new file mode 100644 index 0000000..57d69be --- /dev/null +++ b/charts/core/templates/_monitoring.tpl @@ -0,0 +1,113 @@ +{{/** + # -- Enable tracing + # -- Endpoint for tracing + # -- Exporter for tracing + # -- Insecure for tracing + # -- Mode + # -- Port + # -- Enable logging + # -- Level for logging + # -- Format for logging + # -- Enable metrics + # -- Endpoint for metrics + # -- Exporter for metrics + # -- Insecure for metrics + # -- Mode for metrics + # -- Port for metrics + global: + monitoring: + traces: + enabled: true + endpoint: "localhost" + exporter: "otlp" + insecure: "true" + mode: "grpc" + port: 4317 + logs: + enabled: true + level: "info" + format: "json" + metrics: + enabled: true + exporter: "otlp" + insecure: "true" + mode: "grpc" + port: 4317 + + # -- Service name for monitoring + config: + monitoring: + serviceName: "" + + Each component who want to use monitoring should include this snippet in their deployment.yaml + +**/}} + + +{{- define "core.monitoring.traces" }} +- name: OTEL_TRACES + value: {{ include "resolveGlobalOrServiceValue" (dict "Values" .Values "Key" "monitoring.traces.enabled" "Default" "") | quote}} +- name: OTEL_TRACES_ENDPOINT + value: {{ include "resolveGlobalOrServiceValue" (dict "Values" .Values "Key" "monitoring.traces.endpoint" "Default" "")| quote }} +- name: OTEL_TRACES_EXPORTER + value: {{ include "resolveGlobalOrServiceValue" (dict "Values" .Values "Key" "monitoring.traces.exporter" "Default" "") | quote }} +- name: OTEL_TRACES_EXPORTER_OTLP_INSECURE + value: {{ include "resolveGlobalOrServiceValue" (dict "Values" .Values "Key" "monitoring.traces.insecure" "Default" "") | quote}} +- name: OTEL_TRACES_EXPORTER_OTLP_MODE + value: {{ include "resolveGlobalOrServiceValue" (dict "Values" .Values "Key" "monitoring.traces.mode" "Default" "") | quote}} +- name: OTEL_TRACES_PORT + value: {{ include "resolveGlobalOrServiceValue" (dict "Values" .Values "Key" "monitoring.traces.port" "Default" "") | quote }} +- name: OTEL_TRACES_EXPORTER_OTLP_ENDPOINT + value: "$(OTEL_TRACES_ENDPOINT):$(OTEL_TRACES_PORT)" +{{- end }} + +{{- define "core.monitoring.metrics" }} +- name: OTEL_METRICS + value: {{ include "resolveGlobalOrServiceValue" (dict "Values" .Values "Key" "monitoring.metrics.enabled" "Default" "") | quote }} +- name: OTEL_METRICS_ENDPOINT + value: {{ include "resolveGlobalOrServiceValue" (dict "Values" .Values "Key" "monitoring.metrics.endpoint" "Default" "") | quote}} +- name: OTEL_METRICS_EXPORTER + value: {{ include "resolveGlobalOrServiceValue" (dict "Values" .Values "Key" "monitoring.metrics.exporter" "Default" "") | quote}} +- name: OTEL_METRICS_EXPORTER_OTLP_INSECURE + value: {{ include "resolveGlobalOrServiceValue" (dict "Values" .Values "Key" "monitoring.metrics.insecure" "Default" "") | quote }} +- name: OTEL_METRICS_EXPORTER_OTLP_MODE + value: {{ include "resolveGlobalOrServiceValue" (dict "Values" .Values "Key" "monitoring.metrics.mode" "Default" "") | quote }} +- name: OTEL_METRICS_PORT + value: {{ include "resolveGlobalOrServiceValue" (dict "Values" .Values "Key" "monitoring.metrics.port" "Default" "") | quote }} +- name: OTEL_METRICS_EXPORTER_OTLP_ENDPOINT + value: "$(OTEL_TRACES_ENDPOINT):$(OTEL_METRICS_PORT)" +{{- end -}} + +{{- define "core.monitoring.logs" }} +- name: LOGS_ENABLED + value: {{ include "resolveGlobalOrServiceValue" (dict "Values" .Values "Key" "monitoring.logs.enabled" "Default" "") | quote }} +- name: LOGS_LEVEL + value: {{ include "resolveGlobalOrServiceValue" (dict "Values" .Values "Key" "monitoring.logs.level" "Default" "")| quote }} +{{- if eq (include "resolveGlobalOrServiceValue" (dict "Values" .Values "Key" "monitoring.logs.format" "Default" "")) "json" }} +- name: JSON_FORMATTING_LOGGER + value: "true" +{{- end -}} +{{- end -}} + + +{{- define "core.monitoring.common" }} +- name: OTEL_SERVICE_NAME + value: "{{ .Chart.Name }}" +{{- end -}} + +{{- define "core.monitoring" -}} +{{- include "core.monitoring.common" . }} +{{- $traces := include "resolveGlobalOrServiceValue" (dict "Values" .Values "Key" "monitoring.traces.enabled" "Default" "") }} +{{- $logs := include "resolveGlobalOrServiceValue" (dict "Values" .Values "Key" "monitoring.logs.enabled" "Default" "") }} +{{- $metrics := include "resolveGlobalOrServiceValue" (dict "Values" .Values "Key" "monitoring.metrics.enabled" "Default" "") }} +{{- if eq $traces "true" }} +{{- include "core.monitoring.traces" . }} +{{- end }} +{{- if eq $logs "true" }} +{{- include "core.monitoring.logs" . }} +{{- end }} +{{- if eq $metrics "true" }} +{{- include "core.monitoring.metrics" . }} +{{- end }} +{{- end -}} + diff --git a/charts/core/templates/_names.tpl b/charts/core/templates/_names.tpl new file mode 100644 index 0000000..3cccc48 --- /dev/null +++ b/charts/core/templates/_names.tpl @@ -0,0 +1,38 @@ +{{- define "core.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "core.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | 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 "core.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 the name of the service account to use +*/}} +{{- define "core.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "core.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} \ No newline at end of file diff --git a/charts/core/templates/_pdp.tpl b/charts/core/templates/_pdp.tpl new file mode 100644 index 0000000..f087d02 --- /dev/null +++ b/charts/core/templates/_pdp.tpl @@ -0,0 +1,20 @@ +{{- define "core.podDisruptionBudget" -}} +{{- if .Values.podDisruptionBudget.enabled }} +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: {{ include "core.fullname" . }} + labels: +{{ include "core.labels" . | indent 4 }} +spec: + {{- with .Values.podDisruptionBudget.minAvailable }} + minAvailable: {{ . }} + {{- end }} + {{- with .Values.podDisruptionBudget.maxUnavailable }} + maxUnavailable: {{ . }} + {{- end }} + selector: + matchLabels: + {{- include "core.selectorLabels" . | nindent 6 }} +{{- end }} +{{- end -}} \ No newline at end of file diff --git a/charts/core/templates/_postgres.tpl b/charts/core/templates/_postgres.tpl new file mode 100644 index 0000000..1ba9f19 --- /dev/null +++ b/charts/core/templates/_postgres.tpl @@ -0,0 +1,42 @@ +{{/** + This now can be included in every chart folowing: + It need to be either integrate as one instance or as multiple seperated instances + + global: + aws: + iam: true + + postgres: + enabled: true + + : + serviceAccount: + annotations: + eks.amazonaws.com/role-arn: + + We could integrate with a postgres chart like bitnami and follow their construction +**/}} + +{{- define "core.postgres.uri" -}} +{{- include "aws.iam.postgres" . }} +{{- if and .Values.global.postgresql.auth.existingSecret (not .Values.config.postgresqlUrl) }} +- name: POSTGRES_USERNAME + value: {{ include "postgresql.v1.username" . }} +- name: POSTGRES_PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "postgresql.v1.secretName" . }} + key: {{ include "postgresql.v1.adminPasswordKey" . }} +{{- else }} +- name: POSTGRES_USERNAME + value: {{ .Values.global.postgresql.auth.username }} +- name: POSTGRES_PASSWORD + value: {{ .Values.global.postgresql.auth.password }} +{{- end }} +- name: POSTGRES_URI +{{- if .Values.config.postgresqlUrl }} + value: "{{ .Values.config.postgresqlUrl }}" +{{- else }} + value: "postgresql://$(POSTGRES_USERNAME):$(POSTGRES_PASSWORD)@{{.Values.global.postgresql.host | default (printf "%s.%s.svc" (include "postgresql.v1.primary.fullname" .Subcharts.postgresql) .Release.Namespace ) }}:{{.Values.global.postgresql.service.ports.postgresql}}/{{.Values.global.postgresql.auth.database}}{{- if .Values.global.postgresql.additionalArgs}}?{{.Values.global.postgresql.additionalArgs}}{{- end -}}" +{{- end }} +{{- end }} diff --git a/charts/core/templates/_service.tpl b/charts/core/templates/_service.tpl new file mode 100644 index 0000000..b067e30 --- /dev/null +++ b/charts/core/templates/_service.tpl @@ -0,0 +1,15 @@ +{{/* + * + This now can be included in every chart folowing: + + global: + debug: true + +**/}} + + + +{{- define "core.env.common" -}} +- name: DEBUG + value: {{ include "resolveGlobalOrServiceValue" (dict "Values" .Values "Key" "debug" "Default" "false") | quote }} +{{- end -}} diff --git a/charts/core/templates/_serviceAccount.tpl b/charts/core/templates/_serviceAccount.tpl new file mode 100644 index 0000000..1597d5e --- /dev/null +++ b/charts/core/templates/_serviceAccount.tpl @@ -0,0 +1,14 @@ +{{- define "core.serviceAccount" -}} +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "core.serviceAccountName" . }} + labels: + {{- include "core.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} +{{- end -}} diff --git a/charts/core/templates/_uris.tpl b/charts/core/templates/_uris.tpl new file mode 100644 index 0000000..f5ee836 --- /dev/null +++ b/charts/core/templates/_uris.tpl @@ -0,0 +1,9 @@ +{{- define "service.url" -}} +{{- $service := .service -}} +{{- $ := .Context -}} +{{- if kindIs "string" $service -}} +#### Wrong values +{{- else -}} +{{- tpl (printf "%s://%s" ($service.scheme) ($service.host)) $ -}} +{{- end -}} +{{- end -}} \ No newline at end of file diff --git a/charts/core/values.schema.json b/charts/core/values.schema.json new file mode 100644 index 0000000..7b7ea91 --- /dev/null +++ b/charts/core/values.schema.json @@ -0,0 +1,5 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": {}, + "type": "object" +} diff --git a/charts/core/values.yaml b/charts/core/values.yaml new file mode 100644 index 0000000..8ee718a --- /dev/null +++ b/charts/core/values.yaml @@ -0,0 +1,109 @@ +{} +# global: +# # -- Enable debug mode +# # @section -- Global configuration +# debug: false + +# # -- is the base domain for portal and console +# # @section -- Global configuration +# serviceHost: "" +# aws: +# # -- Enable AWS IAM across all services, appropriate .serviceAccount.annotations must be set +# # @section -- Global configuration +# iam: false +# # -- Enable AWS ELB across all services, appropriate .aws.targertGroup must be set +# # @section -- Global configuration +# elb: false +# nats: +# # -- Enable NATS +# # @section -- Global configuration +# enabled: false + +# # -- URL for NATS +# # @section -- Global configuration +# url: "" + +# monitoring: +# traces: +# # -- Enable otel tracing +# # @section -- Global configuration +# enabled: true +# # -- Endpoint +# # @section -- Global configuration +# endpoint: "localhost" +# # -- Exporter +# # @section -- Global configuration +# exporter: "otlp" +# # -- Insecure +# # @section -- Global configuration +# insecure: true +# # -- Mode +# # @section -- Global configuration +# mode: "grpc" +# # -- Port +# # @section -- Global configuration +# port: 4317 +# logs: +# # -- Enable logging +# # @section -- Global configuration +# enabled: true +# # -- Level +# # @section -- Global configuration +# level: "info" +# # -- Format +# # @section -- Global configuration +# format: "json" + +# metrics: +# # -- Enable +# # @section -- Global configuration +# enabled: true +# # -- Exporter +# # @section -- Global configuration +# exporter: "otlp" +# # -- Insecure +# # @section -- Global configuration +# insecure: true +# # -- Mode +# # @section -- Global configuration +# mode: "grpc" +# # -- Port +# # @section -- Global configuration +# port: 4317 + +# postgresql: +# # -- Host for PostgreSQL (overrides included postgreql `host`) +# # @section -- Global configuration +# host: "" +# # -- Additional arguments for PostgreSQL Connection URI +# # @section -- Global configuration +# additionalArgs: "sslmode=disable" +# auth: +# # -- Name for a custom user to create (overrides `auth.username`) +# # @section -- Global configuration +# username: formance +# # -- Password for the "postgres" admin user (overrides `auth.postgresPassword`) +# # @section -- Global configuration +# password: formance +# # -- Name for a custom database to create (overrides `auth.database`) +# # @section -- Global configuration +# database: formance +# # -- Password for the custom user to create (overrides `auth.password`) +# # @section -- Global configuration +# postgresPassword: formance +# # -- Name of existing secret to use for PostgreSQL credentials (overrides `auth.existingSecret`). +# # @section -- Global configuration +# existingSecret: "" +# secretKeys: +# # -- Name of key in existing secret to use for PostgreSQL credentials (overrides `auth.secretKeys.adminPasswordKey`). Only used when `global.postgresql.auth.existingSecret` is set. +# # @section -- Global configuration +# adminPasswordKey: "" +# # -- Name of key in existing secret to use for PostgreSQL credentials (overrides `auth.secretKeys.userPasswordKey`). Only used when `global.postgresql.auth.existingSecret` is set. +# # @section -- Global configuration +# userPasswordKey: "" + +# service: +# ports: +# # -- PostgreSQL service port (overrides `service.ports.postgresql`) +# # @section -- Global configuration +# postgresql: 5432 diff --git a/charts/demo/Chart.lock b/charts/demo/Chart.lock new file mode 100644 index 0000000..8260baf --- /dev/null +++ b/charts/demo/Chart.lock @@ -0,0 +1,12 @@ +dependencies: +- name: nats + repository: https://nats-io.github.io/k8s/helm/charts/ + version: 1.1.4 +- name: postgresql + repository: oci://registry-1.docker.io/bitnamicharts + version: 13.2.24 +- name: opensearch + repository: oci://registry-1.docker.io/bitnamicharts + version: 0.6.1 +digest: sha256:0ba507db4977c1b36e846b75cf90e5e9b8018404120a9bd0bb80dd347c80bd5a +generated: "2024-10-09T18:40:07.46388361Z" diff --git a/charts/demo/Chart.yaml b/charts/demo/Chart.yaml index fc5d7be..73ff789 100644 --- a/charts/demo/Chart.yaml +++ b/charts/demo/Chart.yaml @@ -1,6 +1,6 @@ apiVersion: v2 name: demo -description: Formance Private Regions Demo Helm Chart +description: Formance Private Regions Demo home: "https://formance.com" sources: - "https://github.com/formancehq/stack" diff --git a/charts/demo/Earthfile b/charts/demo/Earthfile index 687b6da..b447ce3 100644 --- a/charts/demo/Earthfile +++ b/charts/demo/Earthfile @@ -1,21 +1,39 @@ -VERSION 0.8 +VERSION --wildcard-builds --wildcard-copy 0.8 -IMPORT github.com/formancehq/earthly:tags/v0.15.0 AS core +IMPORT ../ AS charts +IMPORT ../core AS core -sources: - FROM core+base-image - WORKDIR /src - COPY Chart.lock /src/Chart.lock - COPY Chart.yaml /src/Chart.yaml - COPY README.md /src/README.md - COPY values.yaml /src/values.yaml - COPY --dir templates /src/templates - SAVE ARTIFACT /src - -helm-validate: - FROM core+helm-base +schema: + FROM charts+schema-base WORKDIR /src - COPY (+sources/*) /src/ + DO --pass-args charts+SCHEMA + +readme: + FROM charts+readme-base + WORKDIR /src/demo + COPY --dir (+dependencies/*) . + DO --pass-args charts+README_GENERATOR + +sources: + DO --pass-args charts+BASE + WORKDIR /src/demo + COPY (+schema/*) . + DO --pass-args charts+SOURCES + +dependencies: + FROM +sources + WORKDIR /src/demo + DO --pass-args charts+DEPENDENCIES + +validate: + FROM +dependencies + WORKDIR /src/demo + COPY (+readme/*) . + DO --pass-args charts+VALIDATE + +package: + FROM +validate + WORKDIR /src/demo + DO --pass-args charts+PACKAGE + - RUN helm dependencies update - DO --pass-args core+HELM_VALIDATE diff --git a/charts/demo/README.md b/charts/demo/README.md index 3d6ff8d..f1eb25f 100644 --- a/charts/demo/README.md +++ b/charts/demo/README.md @@ -1,8 +1,8 @@ # demo -![Version: 0.1.0](https://img.shields.io/badge/Version-0.1.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: latest](https://img.shields.io/badge/AppVersion-latest-informational?style=flat-square) +![Version: 2.0.0](https://img.shields.io/badge/Version-2.0.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: latest](https://img.shields.io/badge/AppVersion-latest-informational?style=flat-square) -Formance Private Regions Demo Helm Chart +Formance Private Regions Demo **Homepage:** @@ -35,13 +35,13 @@ Formance Private Regions Demo Helm Chart | nats.config.jetstream.fileStore.enabled | bool | `true` | | | nats.config.jetstream.fileStore.pvc.enabled | bool | `false` | | | nats.config.jetstream.fileStore.pvc.size | string | `"20Gi"` | | -| nats.enabled | bool | `true` | | +| nats.enabled | bool | `false` | | | nats.fullnameOverride | string | `"nats"` | | | opensearch.coordinating.replicaCount | int | `0` | | | opensearch.dashboards.enabled | bool | `true` | | | opensearch.data.persistence.enabled | bool | `false` | | | opensearch.data.replicaCount | int | `2` | | -| opensearch.enabled | bool | `true` | | +| opensearch.enabled | bool | `false` | | | opensearch.fullnameOverride | string | `"opensearch"` | | | opensearch.ingest.enabled | bool | `false` | | | opensearch.ingest.replicaCount | int | `0` | | @@ -57,5 +57,3 @@ Formance Private Regions Demo Helm Chart | postgresql.global.postgresql.auth.username | string | `"formance"` | | | postgresql.primary.persistence.enabled | bool | `false` | | ----------------------------------------------- -Autogenerated from chart metadata using [helm-docs v1.11.2](https://github.com/norwoodj/helm-docs/releases/v1.11.2) diff --git a/charts/membership/.helmignore b/charts/membership/.helmignore new file mode 100644 index 0000000..0e8a0eb --- /dev/null +++ b/charts/membership/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/charts/membership/Chart.lock b/charts/membership/Chart.lock new file mode 100644 index 0000000..1049cab --- /dev/null +++ b/charts/membership/Chart.lock @@ -0,0 +1,12 @@ +dependencies: +- name: postgresql + repository: oci://registry-1.docker.io/bitnamicharts + version: 15.5.38 +- name: dex + repository: https://charts.dexidp.io + version: 0.17.1 +- name: core + repository: file://../core + version: v1.0.0-beta.1 +digest: sha256:54a650b634aa35bd266b741b4cfc2cde63cf52cda68d96be83a58c63c68279a4 +generated: "2024-10-02T09:06:20.581671811Z" diff --git a/charts/membership/Chart.yaml b/charts/membership/Chart.yaml new file mode 100644 index 0000000..398e773 --- /dev/null +++ b/charts/membership/Chart.yaml @@ -0,0 +1,41 @@ +apiVersion: v2 +name: membership +description: |- + Formance Membership API. Manage stacks, organizations, regions, invitations, users, roles, and permissions. + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: "v1.0.0-beta.1" + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: "v0.35.3" +kubeVersion: ">=1.14.0-0" +sources: + - https://github.com/formancehq/membership-api + +dependencies: + - name: postgresql + repository: oci://registry-1.docker.io/bitnamicharts + version: 15.5.X + condition: postgresql.enabled + - name: dex + version: 0.17.X + repository: https://charts.dexidp.io + condition: dex.enabled + - name: core + version: "v1.0.0-beta.1" + repository: file://../core diff --git a/charts/membership/Earthfile b/charts/membership/Earthfile new file mode 100644 index 0000000..94713ca --- /dev/null +++ b/charts/membership/Earthfile @@ -0,0 +1,41 @@ +VERSION --wildcard-builds --wildcard-copy 0.8 + +IMPORT ../ AS charts +IMPORT ../core AS core + +schema: + FROM charts+schema-base + WORKDIR /src + DO --pass-args charts+SCHEMA + +readme: + FROM charts+readme-base + WORKDIR /src/core + COPY --dir (core+dependencies/*) . + WORKDIR /src/membership + COPY --dir (+dependencies/*) . + DO --pass-args charts+README_GENERATOR + +sources: + DO --pass-args charts+BASE + WORKDIR /src/membership + COPY (+schema/*) . + DO --pass-args charts+SOURCES + +dependencies: + FROM +sources + WORKDIR /src/core + COPY --dir (core+dependencies/*) . + WORKDIR /src/membership + DO --pass-args charts+DEPENDENCIES + +validate: + FROM +dependencies + WORKDIR /src/membership + COPY (+readme/*) . + DO --pass-args charts+VALIDATE + +package: + FROM +validate + WORKDIR /src/membership + DO --pass-args charts+PACKAGE diff --git a/charts/membership/README.md b/charts/membership/README.md new file mode 100644 index 0000000..3fd5e8c --- /dev/null +++ b/charts/membership/README.md @@ -0,0 +1,155 @@ +# membership + +![Version: v1.0.0-beta.1](https://img.shields.io/badge/Version-v1.0.0--beta.1-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: v0.35.3](https://img.shields.io/badge/AppVersion-v0.35.3-informational?style=flat-square) + +Formance Membership API. Manage stacks, organizations, regions, invitations, users, roles, and permissions. + +## Source Code + +* + +## Requirements + +Kubernetes: `>=1.14.0-0` + +| Repository | Name | Version | +|------------|------|---------| +| file://../core | core | v1.0.0-beta.1 | +| https://charts.dexidp.io | dex | 0.17.X | +| oci://registry-1.docker.io/bitnamicharts | postgresql | 15.5.X | + +## Values + +### Global configuration + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| global.aws.iam | bool | `false` | Enable AWS IAM Authentification | +| global.debug | bool | `false` | Enable debug mode | +| global.monitoring.logs.enabled | bool | `true` | Enable logging | +| global.monitoring.logs.format | string | `"json"` | Format | +| global.monitoring.logs.level | string | `"info"` | Level: Info, Debug, Error | +| global.monitoring.traces.enabled | bool | `false` | Enable otel tracing | +| global.monitoring.traces.endpoint | string | `"localhost"` | Endpoint | +| global.monitoring.traces.exporter | string | `"otlp"` | Exporter | +| global.monitoring.traces.insecure | bool | `true` | Insecure | +| global.monitoring.traces.mode | string | `"grpc"` | Mode | +| global.monitoring.traces.port | int | `4317` | Port | +| global.platform.console.host | string | `"console.{{ .Values.global.serviceHost }}"` | is the host for the console | +| global.platform.console.scheme | string | `"https"` | is the scheme for the console | +| global.platform.enabled | bool | `true` | Enable platform oauth2 client | +| global.platform.membership.host | string | `"membership.{{ .Values.global.serviceHost }}"` | is the host for the membership | +| global.platform.membership.oauthClient.existingSecret | string | `""` | is the name of the secret | +| global.platform.membership.oauthClient.id | string | `"platform"` | is the id of the client | +| global.platform.membership.oauthClient.secret | string | `"changeMe1"` | is the secret of the client | +| global.platform.membership.oauthClient.secretKeys | object | `{"secret":""}` | is the key contained within the secret | +| global.platform.membership.relyingParty.host | string | `"dex.{{ .Values.global.serviceHost }}"` | is the host for the membership | +| global.platform.membership.relyingParty.scheme | string | `"https"` | is the scheme for the membership | +| global.platform.membership.scheme | string | `"https"` | is the scheme for the membership | +| global.platform.portal.host | string | `"portal.{{ .Values.global.serviceHost }}"` | is the host for the portal | +| global.platform.portal.scheme | string | `"https"` | is the scheme for the portal | +| global.postgresql.additionalArgs | string | `"sslmode=disable"` | Additional arguments for PostgreSQL Connection URI | +| global.postgresql.auth.database | string | `"formance"` | Name for a custom database to create (overrides `auth.database`) | +| global.postgresql.auth.existingSecret | string | `""` | Name of existing secret to use for PostgreSQL credentials (overrides `auth.existingSecret`). | +| global.postgresql.auth.password | string | `"formance"` | Password for the "postgres" admin user (overrides `auth.postgresPassword`) | +| global.postgresql.auth.postgresPassword | string | `"formance"` | Password for the custom user to create (overrides `auth.password`) | +| global.postgresql.auth.secretKeys.adminPasswordKey | string | `""` | Name of key in existing secret to use for PostgreSQL credentials (overrides `auth.secretKeys.adminPasswordKey`). Only used when `global.postgresql.auth.existingSecret` is set. | +| global.postgresql.auth.secretKeys.userPasswordKey | string | `""` | Name of key in existing secret to use for PostgreSQL credentials (overrides `auth.secretKeys.userPasswordKey`). Only used when `global.postgresql.auth.existingSecret` is set. | +| global.postgresql.auth.username | string | `"formance"` | Name for a custom user to create (overrides `auth.username`) | +| global.postgresql.host | string | `""` | Host for PostgreSQL (overrides included postgreql `host`) | +| global.postgresql.service.ports.postgresql | int | `5432` | PostgreSQL service port (overrides `service.ports.postgresql`) | +| global.serviceHost | string | `""` | is the base domain for portal and console | + +### Dex configuration + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| dex.configOverrides | object | `{"enablePasswordDB":true,"oauth2":{"responseTypes":["code","token","id_token"],"skipApprovalScreen":true},"staticPasswords":[{"email":"admin@formance.com","hash":"$2a$10$2b2cU8CPhOTaGrs1HRQuAueS7JTT5ZHsHSzYiFPm1leZck7Mc8T4W","userID":"08a8684b-db88-4b73-90a9-3cd1661f5466","username":"admin"}]}` | Config override allow template function. Database is setup on the chart global, make sure that user/password when using kubernetes secret | +| dex.configOverrides.enablePasswordDB | bool | `true` | enable password db | +| dex.configOverrides.oauth2.responseTypes | list | `["code","token","id_token"]` | oauth2 response types | +| dex.configOverrides.oauth2.skipApprovalScreen | bool | `true` | oauth2 skip approval screen | +| dex.configOverrides.staticPasswords[0].email | string | `"admin@formance.com"` | static passwords email | +| dex.configOverrides.staticPasswords[0].hash | string | `"$2a$10$2b2cU8CPhOTaGrs1HRQuAueS7JTT5ZHsHSzYiFPm1leZck7Mc8T4W"` | static passwords hash | +| dex.configOverrides.staticPasswords[0].userID | string | `"08a8684b-db88-4b73-90a9-3cd1661f5466"` | static passwords user id | +| dex.configOverrides.staticPasswords[0].username | string | `"admin"` | static passwords username | +| dex.configSecret.create | bool | `false` | Dex config secret create Default secret provided by the dex chart | +| dex.configSecret.createConfigSecretOverrides | bool | `true` | Dex config secret create config secret overrides Enable secret config overrides provided by the cloudprem chart | +| dex.configSecret.name | string | `"membership-dex-config"` | Dex config secret name | +| dex.enabled | bool | `true` | Enable dex | +| dex.envVars | list | `[]` | Dex additional environment variables | +| dex.image.pullPolicy | string | `"IfNotPresent"` | image pull policy | +| dex.image.repository | string | `"ghcr.io/formancehq/dex"` | image repository | +| dex.image.tag | string | `"v0.33.10"` | image tag | +| dex.ingress.annotations | object | `{}` | Dex ingress annotations | +| dex.ingress.className | string | `""` | Dex ingress class name | +| dex.ingress.enabled | bool | `true` | Dex ingress enabled | +| dex.ingress.hosts[0].host | string | `"{{ tpl .Values.global.platform.membership.relyingParty.host $ }}"` | Dex ingress host | +| dex.ingress.hosts[0].paths[0].path | string | `"/"` | Dex ingress path | +| dex.ingress.hosts[0].paths[0].pathType | string | `"Prefix"` | Dex ingress path type | +| dex.ingress.tls | list | `[]` | Dex ingress tls | +| dex.resources | object | `{}` | Dex resources | + +### Postgresql configuration + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| postgresql.architecture | string | `"standalone"` | Postgresql architecture | +| postgresql.enabled | bool | `true` | Enable postgresql | +| postgresql.fullnameOverride | string | `"postgresql"` | Postgresql fullname override | +| postgresql.primary | object | `{"persistence":{"enabled":false}}` | Postgresql primary persistence enabled | + +### Other Values + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| affinity | object | `{}` | Membership affinity | +| autoscaling | object | `{}` | Membership autoscaling | +| commonLabels | object | `{}` | DEPRECATED Membership service | +| config.additionalOAuthClients | list | `[]` | | +| config.fctl | bool | `true` | Enable Fctl | +| config.migration.annotations | object | `{"helm.sh/hook":"pre-upgrade","helm.sh/hook-delete-policy":"before-hook-creation,hook-succeeded,hook-failed"}` | Membership migration annotations | +| config.migration.annotations."helm.sh/hook" | string | `"pre-upgrade"` | Membership migration helm hook | +| config.migration.annotations."helm.sh/hook-delete-policy" | string | `"before-hook-creation,hook-succeeded,hook-failed"` | Membership migration hook delete policy | +| config.oidc | object | `{"clientId":"membership","clientSecret":"changeMe","existingSecret":"","secretKeys":{"secret":""}}` | DEPRECATED Membership postgresql connection url postgresqlUrl: "postgresql://formance:formance@postgresql.formance-control.svc:5432/formance?sslmode=disable" | +| config.oidc.clientId | string | `"membership"` | Membership oidc client id | +| config.oidc.clientSecret | string | `"changeMe"` | Membership oidc client secret | +| config.oidc.existingSecret | string | `""` | Membership oidc existing secret | +| config.oidc.secretKeys | object | `{"secret":""}` | Membership oidc secret key | +| debug | bool | `false` | Membership debug | +| dev | bool | `false` | Membership dev | +| feature.disableEvents | bool | `true` | Membership feature disable events | +| feature.managedStacks | bool | `true` | Membership feature managed stacks | +| fullnameOverride | string | `""` | Membership fullname override | +| image.pullPolicy | string | `"IfNotPresent"` | Membership image pull policy | +| image.repository | string | `"ghcr.io/formancehq/membership"` | Membership image repository | +| image.tag | string | `""` | Membership image tag | +| imagePullSecrets | list | `[]` | Membership image pull secrets | +| ingress.annotations | object | `{}` | Membership ingress annotations | +| ingress.className | string | `""` | Membership ingress class name | +| ingress.enabled | bool | `true` | Membership ingress enabled | +| ingress.hosts[0] | object | `{"host":"{{ tpl .Values.global.platform.membership.host $ }}","paths":[{"path":"/api","pathType":"Prefix"}]}` | Membership ingress host | +| ingress.hosts[0].paths[0].path | string | `"/api"` | Membership ingress path | +| ingress.hosts[0].paths[0].pathType | string | `"Prefix"` | Membership ingress path type | +| ingress.tls | list | `[]` | Membership ingress tls | +| initContainers | list | `[]` | Membership init containers | +| nameOverride | string | `""` | Membership name override | +| nodeSelector | object | `{}` | Membership node selector | +| podSecurityContext | object | `{}` | Membership pod security context | +| replicaCount | int | `1` | Count of replicas | +| resources | object | `{}` | Membership resources | +| securityContext.capabilities | object | `{"drop":["ALL"]}` | Membership security context capabilities drop | +| securityContext.readOnlyRootFilesystem | bool | `true` | Membership security context read only root filesystem | +| securityContext.runAsNonRoot | bool | `true` | Membership security context run as non root | +| securityContext.runAsUser | int | `1000` | Membership security context run as user | +| service.annotations | object | `{}` | service annotations | +| service.clusterIP | string | `""` | service cluster IP | +| service.ports.grpc | object | `{"port":8082}` | service grpc port | +| service.ports.http | object | `{"port":8080}` | service http port | +| service.type | string | `"ClusterIP"` | service type | +| serviceAccount.annotations | object | `{}` | Service account annotations | +| serviceAccount.create | bool | `true` | Service account creation | +| serviceAccount.name | string | `""` | Service account name | +| tolerations | list | `[]` | Membership tolerations | +| volumeMounts | list | `[]` | Membership volume mounts | +| volumes | list | `[]` | Membership volumes | + diff --git a/charts/membership/templates/_helpers.tpl b/charts/membership/templates/_helpers.tpl new file mode 100644 index 0000000..10f7929 --- /dev/null +++ b/charts/membership/templates/_helpers.tpl @@ -0,0 +1,107 @@ +{{- define "membership.env" -}} +- name: DEBUG + value: "{{.Values.debug}}" +- name: DEV + value: "{{.Values.dev}}" +- name: CONFIG + value: "/config/config.yaml" +- name: RP_ISSUER + value: "{{ tpl (printf "%s://%s" .Values.global.platform.membership.relyingParty.scheme .Values.global.platform.membership.relyingParty.host) $ }}" +- name: RP_CLIENT_ID + value: "{{ tpl .Values.config.oidc.clientId .}}" +- name: RP_CLIENT_SECRET + {{- if gt (len .Values.config.oidc.existingSecret) 0 }} + valueFrom: + secretKeyRef: + name: {{ .Values.config.oidc.existingSecret }} + key: {{ .Values.config.oidc.secretKeys.secret }} + {{- else }} + value: {{ .Values.config.oidc.clientSecret | quote }} + {{- end }} +- name: SERVICE_URL + value: "{{ tpl (printf "%s://%s/api" .Values.global.platform.membership.scheme .Values.global.platform.membership.host) $ }}" +- name: MANAGED_STACKS + value: "{{.Values.feature.managedStacks}}" +- name: DISABLE_EVENTS + value: "{{.Values.feature.disableEvents}}" +{{- if and .Values.global.platform.console.host .Values.global.platform.console.scheme }} +- name: CONSOLE_PUBLIC_BASEURL + value: {{ tpl (default (printf "%s://%s" .Values.global.platform.console.scheme .Values.global.platform.console.host) .Values.config.redirect_url) $ }} +{{- end }} +- name: PLATFORM_OAUTH_CLIENT_SECRET + {{- if gt (len .Values.global.platform.membership.oauthClient.existingSecret) 0 }} + valueFrom: + secretKeyRef: + name: {{ .Values.global.platform.membership.oauthClient.existingSecret }} + key: {{ .Values.global.platform.membership.oauthClient.secretKeys.secret }} + {{- else }} + value: {{ .Values.global.platform.membership.oauthClient.secret | quote }} + {{- end }} +{{- include "core.postgres.uri" . }} +{{- include "core.monitoring" . }} +{{- with .Values.additionalEnv }} +{{- tpl (toYaml .) $ }} +{{- end }} +{{- end }} + +{{ define "render-value" }} + {{- if kindIs "string" .value }} + {{- tpl .value .context }} + {{- else }} + {{- tpl (.value | toYaml) .context }} + {{- end }} +{{- end }} + +{{ define "dex-values" }} +issuer: "{{ tpl (printf "%s://%s" .Values.global.platform.membership.relyingParty.scheme .Values.global.platform.membership.relyingParty.host) $ }}" +logger: + # -- logger format + # @section -- Dex configuration + format: "json" +storage: + # -- storage type + # @section -- Dex configuration + type: postgres + config: + # -- storage config host + # @section -- Dex configuration + host: '{{.Values.global.postgresql.host | default (printf "%s.%s.svc" (include "postgresql.v1.primary.fullname" .Subcharts.postgresql) .Release.Namespace ) }}' + # -- (number) storage config port, cannot be templated from other values + # @section -- Dex configuration + port: {{ .Values.global.postgresql.service.ports.postgresql }} # @schema type:number + # -- storage config database + # @section -- Dex configuration + database: "{{.Values.global.postgresql.auth.database }}" + # -- storage config user + # @section -- Dex configuration + user: formance + # -- storage config password + # @section -- Dex configuration + password: formance + ssl: + # -- storage config ssl mode + # @section -- Dex configuration + mode: disable + +staticClients: + - # -- static clients name + # @section -- Dex configuration + name: "membership" + # -- static clients id + # @section -- Dex configuration + id: "{{ .Values.config.oidc.clientId }}" + {{ if .Values.config.oidc.existingSecret }} + # -- static clients secret env var, do not use secret and secretEnv at the same time + # -- According to dex.envVars + # @section -- Dex configuration + secretEnv: MEMBERSHIP_CLIENT_SECRET + {{ else }} + # -- static clients secret + # @section -- Dex configuration + secret: "{{ tpl .Values.config.oidc.clientSecret $ }}" + {{ end }} + # -- static clients redirect uris + # @section -- Dex configuration + redirectURIs: + - "{{ .Values.global.platform.membership.scheme }}://{{ tpl .Values.global.platform.membership.host $ }}/api/authorize/callback" +{{- end }} \ No newline at end of file diff --git a/charts/membership/templates/configmap.yaml b/charts/membership/templates/configmap.yaml new file mode 100644 index 0000000..61f826f --- /dev/null +++ b/charts/membership/templates/configmap.yaml @@ -0,0 +1,32 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "core.fullname" . }} + labels: + {{- include "core.labels" . | nindent 4 }} +data: + config.yaml: |- + staticClients: + {{- if .Values.config.fctl }} + - id: fctl + public: true + {{- end }} + {{- if .Values.global.platform.enabled }} + - id: "{{ .Values.global.platform.membership.oauthClient.id }}" + secrets: + - "$PLATFORM_OAUTH_CLIENT_SECRET" + redirectUris: + - '{{ tpl (printf "%s://%s" .Values.global.platform.console.scheme .Values.global.platform.console.host) $ }}/auth/login' + - '{{ tpl (printf "%s://%s" .Values.global.platform.portal.scheme .Values.global.platform.portal.host) $ }}/auth/login' + postLogoutRedirectUris: + - '{{ tpl (printf "%s://%s" .Values.global.platform.console.scheme .Values.global.platform.console.host) $ }}/auth/logout' + - '{{ tpl (printf "%s://%s" .Values.global.platform.portal.scheme .Values.global.platform.portal.host) $ }}/auth/logout' + scopes: + - supertoken + - accesses + - remember_me + - keep_refresh_token + {{- end }} + {{- with .Values.config.additionalOAuthClients }} + {{- tpl (toYaml .) $ | nindent 6 }} + {{- end }} diff --git a/charts/membership/templates/deployment.yaml b/charts/membership/templates/deployment.yaml new file mode 100644 index 0000000..5829de5 --- /dev/null +++ b/charts/membership/templates/deployment.yaml @@ -0,0 +1,86 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "core.fullname" . }} + labels: + {{- include "core.labels" . | nindent 4 }} +spec: + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include "core.selectorLabels" . | nindent 6 }} + template: + metadata: + annotations: + checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} + labels: + {{- include "core.selectorLabels" . | nindent 8 }} + spec: + serviceAccountName: {{ include "core.serviceAccountName" . }} + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + volumes: + - name: config + configMap: + name: {{ include "core.fullname" . }} + initContainers: + {{- if gt (len .Values.initContainers) 0 }} + {{ .Values.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 }} + volumeMounts: + - name: config + mountPath: "/config" + readOnly: true + env: + {{- include "membership.env" . | nindent 12 }} + ports: + - name: http + containerPort: 8080 + protocol: TCP + {{- if not .Values.feature.managedStacks }} + - name: grpc + containerPort: 8082 + protocol: TCP + {{- end }} + livenessProbe: + httpGet: + path: /_healthcheck + port: http + readinessProbe: + httpGet: + path: /_healthcheck + port: http + resources: + {{- toYaml .Values.resources | nindent 12 }} + {{ with .Values.volumeMounts }} + volumeMounts: + {{- toYaml . | nindent 12 }} + {{- 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.volumes }} + volumes: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/charts/membership/templates/dex/secret.yaml b/charts/membership/templates/dex/secret.yaml new file mode 100644 index 0000000..875ffcd --- /dev/null +++ b/charts/membership/templates/dex/secret.yaml @@ -0,0 +1,12 @@ +{{- if and .Values.dex.enabled .Values.dex.configSecret.createConfigSecretOverrides }} +{{ $defaultValues := (include "dex-values" . ) | fromYaml }} +apiVersion: v1 +kind: Secret +metadata: + name: membership-dex-config + labels: + {{- include "core.labels" . | nindent 4 }} +stringData: + config.yaml: |- + {{- tpl ((merge .Values.dex.configOverrides $defaultValues)|toYaml) . | nindent 6 }} +{{- end }} \ No newline at end of file diff --git a/charts/membership/templates/ingress.yaml b/charts/membership/templates/ingress.yaml new file mode 100644 index 0000000..6b775d6 --- /dev/null +++ b/charts/membership/templates/ingress.yaml @@ -0,0 +1,61 @@ +{{- if .Values.ingress.enabled -}} +{{- $fullName := include "core.fullname" . -}} +{{- $svcPort := .Values.service.ports.http.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 }} +{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1 +{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1beta1 +{{- else -}} +apiVersion: extensions/v1beta1 +{{- end }} +kind: Ingress +metadata: + name: {{ include "core.fullname" . }} + labels: + {{- include "core.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 }} + - {{ tpl . $ | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ tpl .host $ | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + {{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }} + pathType: {{ .pathType }} + {{- end }} + backend: + {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} + service: + name: {{ $fullName }} + port: + number: {{ $svcPort }} + {{- else }} + serviceName: {{ $fullName }} + servicePort: {{ $svcPort }} + {{- end }} + {{- end }} + {{- end }} + {{- end }} \ No newline at end of file diff --git a/charts/membership/templates/job.yaml b/charts/membership/templates/job.yaml new file mode 100644 index 0000000..9f7a031 --- /dev/null +++ b/charts/membership/templates/job.yaml @@ -0,0 +1,30 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ include "core.fullname" . }} + labels: + {{- include "core.labels" . | nindent 4 }} + {{- with .Values.config.migration.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + template: + metadata: + labels: + {{- include "core.selectorLabels" . | nindent 8 }} + spec: + restartPolicy: OnFailure + containers: + - name: migrate + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + args: + - storage + - migrations + - up + env: + - name: DEBUG + value: "{{.Values.debug}}" + - name: DEV + value: "{{.Values.dev}}" + {{- include "core.postgres.uri" . | nindent 12 }} diff --git a/charts/membership/templates/service.yaml b/charts/membership/templates/service.yaml new file mode 100644 index 0000000..ea376c7 --- /dev/null +++ b/charts/membership/templates/service.yaml @@ -0,0 +1,40 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "core.fullname" . }} + labels: + {{- include "core.labels" . | nindent 4 }} + {{- with .Values.service.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + type: {{ .Values.service.type }} + {{- with .Values.service.clusterIP }} + clusterIP: {{ . }} + {{- end }} + ports: + - name: http + port: {{ .Values.service.ports.http.port }} + {{- if and (or (eq .Values.service.type "NodePort") (eq .Values.service.type "LoadBalancer")) .Values.service.ports.http.nodePort }} + nodePort: {{ .Values.service.ports.http.nodePort }} + {{- end }} + targetPort: http + protocol: TCP + {{- if semverCompare ">=1.20-0" .Capabilities.KubeVersion.GitVersion }} + appProtocol: http + {{- end }} + {{- if not .Values.feature.managedStacks }} + - name: grpc + port: {{ .Values.service.ports.grpc.port }} + {{- if and (or (eq .Values.service.type "NodePort") (eq .Values.service.type "LoadBalancer")) .Values.service.ports.grpc.nodePort }} + nodePort: {{ .Values.service.ports.grpc.nodePort }} + {{- end }} + targetPort: grpc + protocol: TCP + {{- if semverCompare ">=1.20-0" .Capabilities.KubeVersion.GitVersion }} + appProtocol: http + {{- end }} + {{- end }} + selector: + {{- include "core.selectorLabels" . | nindent 4 }} diff --git a/charts/membership/templates/serviceaccount.yaml b/charts/membership/templates/serviceaccount.yaml new file mode 100644 index 0000000..b4c7924 --- /dev/null +++ b/charts/membership/templates/serviceaccount.yaml @@ -0,0 +1 @@ +{{- include "core.serviceAccount" . -}} diff --git a/charts/membership/values.schema.json b/charts/membership/values.schema.json new file mode 100644 index 0000000..a51d764 --- /dev/null +++ b/charts/membership/values.schema.json @@ -0,0 +1,598 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "affinity": { + "properties": {}, + "type": "object" + }, + "autoscaling": { + "properties": {}, + "type": "object" + }, + "commonLabels": { + "properties": {}, + "type": "object" + }, + "config": { + "properties": { + "additionalOAuthClients": { + "type": "array" + }, + "fctl": { + "type": "boolean" + }, + "migration": { + "properties": { + "annotations": { + "properties": { + "helm.sh/hook": { + "type": "string" + }, + "helm.sh/hook-delete-policy": { + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "oidc": { + "properties": { + "clientId": { + "type": "string" + }, + "clientSecret": { + "type": "string" + }, + "existingSecret": { + "type": "string" + }, + "secretKeys": { + "properties": { + "secret": { + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "debug": { + "type": "boolean" + }, + "dev": { + "type": "boolean" + }, + "dex": { + "properties": { + "configOverrides": { + "properties": { + "enablePasswordDB": { + "type": "boolean" + }, + "oauth2": { + "properties": { + "responseTypes": { + "items": { + "type": "string" + }, + "type": "array" + }, + "skipApprovalScreen": { + "type": "boolean" + } + }, + "type": "object" + }, + "staticPasswords": { + "items": { + "properties": { + "email": { + "type": "string" + }, + "hash": { + "type": "string" + }, + "userID": { + "type": "string" + }, + "username": { + "type": "string" + } + }, + "type": "object" + }, + "type": "array" + } + }, + "type": "object" + }, + "configSecret": { + "properties": { + "create": { + "type": "boolean" + }, + "createConfigSecretOverrides": { + "type": "boolean" + }, + "name": { + "type": "string" + } + }, + "type": "object" + }, + "enabled": { + "type": "boolean" + }, + "envVars": { + "type": "array" + }, + "image": { + "properties": { + "pullPolicy": { + "type": "string" + }, + "repository": { + "type": "string" + }, + "tag": { + "type": "string" + } + }, + "type": "object" + }, + "ingress": { + "properties": { + "annotations": { + "properties": {}, + "type": "object" + }, + "className": { + "type": "string" + }, + "enabled": { + "type": "boolean" + }, + "hosts": { + "items": { + "properties": { + "host": { + "type": "string" + }, + "paths": { + "items": { + "properties": { + "path": { + "type": "string" + }, + "pathType": { + "type": "string" + } + }, + "type": "object" + }, + "type": "array" + } + }, + "type": "object" + }, + "type": "array" + }, + "tls": { + "type": "array" + } + }, + "type": "object" + }, + "resources": { + "properties": {}, + "type": "object" + } + }, + "type": "object" + }, + "feature": { + "properties": { + "disableEvents": { + "type": "boolean" + }, + "managedStacks": { + "type": "boolean" + } + }, + "type": "object" + }, + "fullnameOverride": { + "type": "string" + }, + "global": { + "properties": { + "aws": { + "properties": { + "iam": { + "type": "boolean" + } + }, + "type": "object" + }, + "debug": { + "type": "boolean" + }, + "monitoring": { + "properties": { + "logs": { + "properties": { + "enabled": { + "type": "boolean" + }, + "format": { + "type": "string" + }, + "level": { + "type": "string" + } + }, + "type": "object" + }, + "traces": { + "properties": { + "enabled": { + "type": "boolean" + }, + "endpoint": { + "type": "string" + }, + "exporter": { + "type": "string" + }, + "insecure": { + "type": "boolean" + }, + "mode": { + "type": "string" + }, + "port": { + "type": "integer" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "platform": { + "properties": { + "console": { + "properties": { + "host": { + "type": "string" + }, + "scheme": { + "type": "string" + } + }, + "type": "object" + }, + "enabled": { + "type": "boolean" + }, + "membership": { + "properties": { + "host": { + "type": "string" + }, + "oauthClient": { + "properties": { + "existingSecret": { + "type": "string" + }, + "id": { + "type": "string" + }, + "secret": { + "type": "string" + }, + "secretKeys": { + "properties": { + "secret": { + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "relyingParty": { + "properties": { + "host": { + "type": "string" + }, + "scheme": { + "type": "string" + } + }, + "type": "object" + }, + "scheme": { + "type": "string" + } + }, + "type": "object" + }, + "portal": { + "properties": { + "host": { + "type": "string" + }, + "scheme": { + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "postgresql": { + "properties": { + "additionalArgs": { + "type": "string" + }, + "auth": { + "properties": { + "database": { + "type": "string" + }, + "existingSecret": { + "type": "string" + }, + "password": { + "type": "string" + }, + "postgresPassword": { + "type": "string" + }, + "secretKeys": { + "properties": { + "adminPasswordKey": { + "type": "string" + }, + "userPasswordKey": { + "type": "string" + } + }, + "type": "object" + }, + "username": { + "type": "string" + } + }, + "type": "object" + }, + "host": { + "type": "string" + }, + "service": { + "properties": { + "ports": { + "properties": { + "postgresql": { + "type": "integer" + } + }, + "type": "object" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "serviceHost": { + "type": "string" + } + }, + "type": "object" + }, + "image": { + "properties": { + "pullPolicy": { + "type": "string" + }, + "repository": { + "type": "string" + }, + "tag": { + "type": "string" + } + }, + "type": "object" + }, + "imagePullSecrets": { + "type": "array" + }, + "ingress": { + "properties": { + "annotations": { + "properties": {}, + "type": "object" + }, + "className": { + "type": "string" + }, + "enabled": { + "type": "boolean" + }, + "hosts": { + "items": { + "properties": { + "host": { + "type": "string" + }, + "paths": { + "items": { + "properties": { + "path": { + "type": "string" + }, + "pathType": { + "type": "string" + } + }, + "type": "object" + }, + "type": "array" + } + }, + "type": "object" + }, + "type": "array" + }, + "tls": { + "type": "array" + } + }, + "type": "object" + }, + "initContainers": { + "type": "array" + }, + "nameOverride": { + "type": "string" + }, + "nodeSelector": { + "properties": {}, + "type": "object" + }, + "podSecurityContext": { + "properties": {}, + "type": "object" + }, + "postgresql": { + "properties": { + "architecture": { + "type": "string" + }, + "enabled": { + "type": "boolean" + }, + "fullnameOverride": { + "type": "string" + }, + "primary": { + "properties": { + "persistence": { + "properties": { + "enabled": { + "type": "boolean" + } + }, + "type": "object" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "replicaCount": { + "type": "integer" + }, + "resources": { + "properties": {}, + "type": "object" + }, + "securityContext": { + "properties": { + "capabilities": { + "properties": { + "drop": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "readOnlyRootFilesystem": { + "type": "boolean" + }, + "runAsNonRoot": { + "type": "boolean" + }, + "runAsUser": { + "type": "integer" + } + }, + "type": "object" + }, + "service": { + "properties": { + "annotations": { + "properties": {}, + "type": "object" + }, + "clusterIP": { + "type": "string" + }, + "ports": { + "properties": { + "grpc": { + "properties": { + "port": { + "type": "integer" + } + }, + "type": "object" + }, + "http": { + "properties": { + "port": { + "type": "integer" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "type": { + "type": "string" + } + }, + "type": "object" + }, + "serviceAccount": { + "properties": { + "annotations": { + "properties": {}, + "type": "object" + }, + "create": { + "type": "boolean" + }, + "name": { + "type": "string" + } + }, + "type": "object" + }, + "tolerations": { + "type": "array" + }, + "volumeMounts": { + "type": "array" + }, + "volumes": { + "type": "array" + } + }, + "type": "object" +} diff --git a/charts/membership/values.yaml b/charts/membership/values.yaml new file mode 100644 index 0000000..4a23758 --- /dev/null +++ b/charts/membership/values.yaml @@ -0,0 +1,440 @@ +global: + # -- Enable debug mode + # @section -- Global configuration + debug: false + # -- is the base domain for portal and console + # @section -- Global configuration + serviceHost: "" + + aws: + # -- Enable AWS IAM Authentification + # @section -- Global configuration + iam: false + + platform: + # -- Enable platform oauth2 client + # @section -- Global configuration + enabled: true + console: + # -- is the scheme for the console + # @section -- Global configuration + scheme: "https" + # -- is the host for the console + # @section -- Global configuration + host: "console.{{ .Values.global.serviceHost }}" + portal: + # -- is the scheme for the portal + # @section -- Global configuration + scheme: "https" + # -- is the host for the portal + # @section -- Global configuration + host: "portal.{{ .Values.global.serviceHost }}" + membership: + # -- is the scheme for the membership + # @section -- Global configuration + scheme: "https" + # -- is the host for the membership + # @section -- Global configuration + host: "membership.{{ .Values.global.serviceHost }}" + + relyingParty: + # -- is the scheme for the membership + # @section -- Global configuration + scheme: "https" + # -- is the host for the membership + # @section -- Global configuration + host: "dex.{{ .Values.global.serviceHost }}" + + oauthClient: + # -- is the id of the client + # @section -- Global configuration + id: "platform" + # -- is the secret of the client + # @section -- Global configuration + secret: "changeMe1" + # -- is the name of the secret + # @section -- Global configuration + existingSecret: "" + # -- is the key contained within the secret + # @section -- Global configuration + secretKeys: + secret: "" + + monitoring: + traces: + # -- Enable otel tracing + # @section -- Global configuration + enabled: false + # -- Endpoint + # @section -- Global configuration + endpoint: "localhost" + # -- Exporter + # @section -- Global configuration + exporter: "otlp" + # -- Insecure + # @section -- Global configuration + insecure: true + # -- Mode + # @section -- Global configuration + mode: "grpc" + # -- Port + # @section -- Global configuration + port: 4317 + logs: + # -- Enable logging + # @section -- Global configuration + enabled: true + # -- Level: Info, Debug, Error + # @section -- Global configuration + level: "info" + # -- Format + # @section -- Global configuration + format: "json" + + postgresql: + # -- Host for PostgreSQL (overrides included postgreql `host`) + # @section -- Global configuration + host: "" + # -- Additional arguments for PostgreSQL Connection URI + # @section -- Global configuration + additionalArgs: "sslmode=disable" + auth: + # -- Name for a custom user to create (overrides `auth.username`) + # @section -- Global configuration + username: formance + # -- Password for the "postgres" admin user (overrides `auth.postgresPassword`) + # @section -- Global configuration + password: formance + # -- Name for a custom database to create (overrides `auth.database`) + # @section -- Global configuration + database: formance + # -- Password for the custom user to create (overrides `auth.password`) + # @section -- Global configuration + postgresPassword: formance + # -- Name of existing secret to use for PostgreSQL credentials (overrides `auth.existingSecret`). + # @section -- Global configuration + existingSecret: "" + secretKeys: + # -- Name of key in existing secret to use for PostgreSQL credentials (overrides `auth.secretKeys.adminPasswordKey`). Only used when `global.postgresql.auth.existingSecret` is set. + # @section -- Global configuration + adminPasswordKey: "" + # -- Name of key in existing secret to use for PostgreSQL credentials (overrides `auth.secretKeys.userPasswordKey`). Only used when `global.postgresql.auth.existingSecret` is set. + # @section -- Global configuration + userPasswordKey: "" + + service: + ports: + # -- PostgreSQL service port (overrides `service.ports.postgresql`) + # @section -- Global configuration + postgresql: 5432 + +# -- Count of replicas +replicaCount: 1 + +serviceAccount: + # -- Service account creation + create: true + # -- Service account name + name: "" + # -- Service account annotations + annotations: + {} + # eks.amazonaws.com/role-arn: "" + +image: + # -- Membership image repository + repository: ghcr.io/formancehq/membership + # -- Membership image pull policy + pullPolicy: IfNotPresent + # -- Membership image tag + tag: "" + +# -- Membership image pull secrets +imagePullSecrets: [] + +# -- Membership name override +nameOverride: "" +# -- Membership fullname override +fullnameOverride: "" + +feature: + # -- Membership feature managed stacks + managedStacks: true + # -- Membership feature disable events + disableEvents: true + +# -- Membership pod security context +podSecurityContext: {} +# fsGroup: 2000 + +securityContext: + # -- Membership security context capabilities drop + capabilities: + drop: + - ALL + # -- Membership security context read only root filesystem + readOnlyRootFilesystem: true + # -- Membership security context run as non root + runAsNonRoot: true + # -- Membership security context run as user + runAsUser: 1000 + +# -- Membership resources +resources: + {} + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + +# -- Membership autoscaling +autoscaling: + {} + # enabled: false + # minReplicas: 1 + # maxReplicas: 10 + # targetCPUUtilizationPercentage: 80 + # targetMemoryUtilizationPercentage: 80 + +# -- Membership node selector +nodeSelector: {} + +# -- Membership tolerations +tolerations: [] + +# -- Membership affinity +affinity: {} + +# -- DEPRECATED Membership service +commonLabels: {} + +# -- Membership debug +debug: false + +# To disable ssl verification +# -- Membership dev +dev: false + +# -- Membership init containers +initContainers: [] + +# -- Membership volume mounts +volumeMounts: [] + +# -- Membership volumes +volumes: [] + +service: + # -- service annotations + annotations: {} + # -- service type + type: ClusterIP + # -- service cluster IP + clusterIP: "" + ports: + # -- service http port + http: + port: 8080 + # -- service node port + # nodePort: + # -- service grpc port + grpc: + port: 8082 + # -- Membership service node port + # nodePort: + +ingress: + # -- Membership ingress enabled + enabled: true + # -- Membership ingress class name + className: "" + # -- Membership ingress annotations + annotations: {} + hosts: + # -- Membership ingress host + - host: "{{ tpl .Values.global.platform.membership.host $ }}" + paths: + - # -- Membership ingress path + path: /api + # -- Membership ingress path type + pathType: Prefix + # -- Membership ingress tls + tls: + [] + # -- Membership ingress tls secret name + # - secretName: YOUR_TLS_SECRET_NAME + +config: + # -- Enable Fctl + fctl: true + # -- DEPRECATED Membership postgresql connection url + # postgresqlUrl: "postgresql://formance:formance@postgresql.formance-control.svc:5432/formance?sslmode=disable" + oidc: + # -- Membership oidc client id + clientId: "membership" + # -- Membership oidc client secret + clientSecret: "changeMe" + # -- Membership oidc existing secret + existingSecret: "" + # -- Membership oidc secret key + secretKeys: + secret: "" + + migration: + # -- Membership migration annotations + annotations: + # -- Membership migration helm hook + helm.sh/hook: pre-upgrade + # -- Membership migration hook delete policy + helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded,hook-failed + + # Argo CD translate `pre-install,pre-upgrade` to: argocd.argoproj.io/hook: PreSync + + additionalOAuthClients: [] + +dex: + # -- Enable dex + # @section -- Dex configuration + enabled: true + + image: + # -- image repository + # @section -- Dex configuration + repository: ghcr.io/formancehq/dex + # -- image pull policy + # @section -- Dex configuration + pullPolicy: IfNotPresent + # -- image tag + # @section -- Dex configuration + tag: v0.33.10 + + ingress: + # -- Dex ingress enabled + # @section -- Dex configuration + enabled: true + # -- Dex ingress class name + # @section -- Dex configuration + className: "" + # -- Dex ingress annotations + # @section -- Dex configuration + annotations: {} + hosts: + - # -- Dex ingress host + # @section -- Dex configuration + host: "{{ tpl .Values.global.platform.membership.relyingParty.host $ }}" + paths: + - # -- Dex ingress path + # @section -- Dex configuration + path: / + # -- Dex ingress path type + # @section -- Dex configuration + pathType: Prefix + # -- Dex ingress tls + # @section -- Dex configuration + tls: + [] + # -- Dex ingress tls secret name + # @section -- Dex configuration + # - secretName: YOUR_TLS_SECRET_NAME + + # -- Dex resources + # @section -- Dex configuration + resources: + {} + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + + configSecret: + # -- Dex config secret create + # Default secret provided by the dex chart + # @section -- Dex configuration + create: false + # -- Dex config secret create config secret overrides + # @section -- Dex configuration + # Enable secret config overrides provided by the cloudprem chart + createConfigSecretOverrides: true + # -- Dex config secret name + # @section -- Dex configuration + name: "membership-dex-config" + + # -- Dex additional environment variables + # @section -- Dex configuration + envVars: [] + + # Unfortunalty Dex chart does not provide a way to use function within values + # + # # When using + # # - oidc.clientSecret.secretKeys.secret + # # - oidc.clientSecret.existingSecret + # envVars: + # - name: MEMBERSHIP_CLIENT_SECRET + # valueFrom: + # secretKeyRef: + # name: "membership-secrets" + # key: "dex-client-secret" + # + # # When using .global.postgresql.auth.existingSecret + # - name: POSTGRES_PASSWORD + # valueFrom: + # secretKeyRef: + # # name: {{ include "postgresql.v1.secretName" . }} + # # key: {{ include "postgresql.v1.adminPasswordKey" . }} + # + + # -- Config override allow template function. + # Database is setup on the chart global, make sure that user/password when using kubernetes secret + # @section -- Dex configuration + configOverrides: + oauth2: + # -- oauth2 skip approval screen + # @section -- Dex configuration + skipApprovalScreen: true + # -- oauth2 response types + # @section -- Dex configuration + responseTypes: + - code + - token + - id_token + + # -- enable password db + # @section -- Dex configuration + enablePasswordDB: true + # Generate password: https://github.com/dexidp/dex/blob/576f990d257d9dd63e283cf379960e50506e8bcc/examples/config-dev.yaml#L145 + staticPasswords: + - # -- static passwords email + # @section -- Dex configuration + email: admin@formance.com + # -- static passwords hash + # @section -- Dex configuration + hash: "$2a$10$2b2cU8CPhOTaGrs1HRQuAueS7JTT5ZHsHSzYiFPm1leZck7Mc8T4W" # password + # -- static passwords username + # @section -- Dex configuration + username: admin + # -- static passwords user id + # @section -- Dex configuration + userID: 08a8684b-db88-4b73-90a9-3cd1661f5466 + +postgresql: + # -- Enable postgresql + # @section -- Postgresql configuration + enabled: true + + # -- Postgresql fullname override + # @section -- Postgresql configuration + fullnameOverride: postgresql + + # -- Postgresql architecture + # @section -- Postgresql configuration + architecture: standalone + + # -- Postgresql primary persistence enabled + # @section -- Postgresql configuration + primary: + persistence: + enabled: false diff --git a/charts/portal/.helmignore b/charts/portal/.helmignore new file mode 100644 index 0000000..0e8a0eb --- /dev/null +++ b/charts/portal/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/charts/portal/Chart.lock b/charts/portal/Chart.lock new file mode 100644 index 0000000..b7847e3 --- /dev/null +++ b/charts/portal/Chart.lock @@ -0,0 +1,6 @@ +dependencies: +- name: core + repository: file://../core + version: v1.0.0-beta.1 +digest: sha256:476f02fc2e76ae3a272789ded9cf18ab9a286f6f8b6266cc183bb1930f19d9e4 +generated: "2024-09-19T23:19:37.632746+02:00" diff --git a/charts/portal/Chart.yaml b/charts/portal/Chart.yaml new file mode 100644 index 0000000..5bc10d0 --- /dev/null +++ b/charts/portal/Chart.yaml @@ -0,0 +1,41 @@ +apiVersion: v2 +name: portal +description: |- + Formance Portal + +home: "https://formance.com" + +maintainers: + - name: "Formance Team" + email: "support@formance.com" +icon: "https://avatars.githubusercontent.com/u/84325077?s=200&v=4" + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: "v1.0.0-beta.1" + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: "764bb7e199e1d2882e4d5cd205eada0ef0abc283" +kubeVersion: ">=1.14.0-0" + +sources: + - https://github.com/formancehq/platform-ui + +dependencies: + - name: core + version: "v1.0.0-beta.1" + repository: file://../core diff --git a/charts/portal/Earthfile b/charts/portal/Earthfile new file mode 100644 index 0000000..0079139 --- /dev/null +++ b/charts/portal/Earthfile @@ -0,0 +1,41 @@ +VERSION --wildcard-builds --wildcard-copy 0.8 + +IMPORT ../ AS charts +IMPORT ../core AS core + +schema: + FROM charts+schema-base + WORKDIR /src + DO --pass-args charts+SCHEMA + +readme: + FROM charts+readme-base + WORKDIR /src/core + COPY --dir (core+dependencies/*) . + WORKDIR /src/portal + COPY --dir (+dependencies/*) . + DO --pass-args charts+README_GENERATOR + +sources: + DO --pass-args charts+BASE + WORKDIR /src/portal + COPY (+schema/*) . + DO --pass-args charts+SOURCES + +dependencies: + FROM +sources + WORKDIR /src/core + COPY --dir (core+dependencies/*) . + WORKDIR /src/portal + DO --pass-args charts+DEPENDENCIES + +validate: + FROM +dependencies + WORKDIR /src/portal + COPY (+readme/*) . + DO --pass-args charts+VALIDATE + +package: + FROM +validate + WORKDIR /src/portal + DO --pass-args charts+PACKAGE \ No newline at end of file diff --git a/charts/portal/README.md b/charts/portal/README.md new file mode 100644 index 0000000..c2540d7 --- /dev/null +++ b/charts/portal/README.md @@ -0,0 +1,87 @@ +# portal + +![Version: v1.0.0-beta.1](https://img.shields.io/badge/Version-v1.0.0--beta.1-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 764bb7e199e1d2882e4d5cd205eada0ef0abc283](https://img.shields.io/badge/AppVersion-764bb7e199e1d2882e4d5cd205eada0ef0abc283-informational?style=flat-square) + +Formance Portal + +**Homepage:** + +## Maintainers + +| Name | Email | Url | +| ---- | ------ | --- | +| Formance Team | | | + +## Source Code + +* + +## Requirements + +Kubernetes: `>=1.14.0-0` + +| Repository | Name | Version | +|------------|------|---------| +| file://../core | core | v1.0.0-beta.1 | + +## Values + +### Global configuration + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| global.debug | bool | `false` | Enable debug mode | +| global.platform.console.host | string | `"console.{{ .Values.global.serviceHost }}"` | is the host for the console | +| global.platform.console.scheme | string | `"https"` | is the scheme for the console | +| global.platform.cookie.encryptionKey | string | `"changeMe00"` | is used to encrypt a cookie that share authentication between platform services (console, portal, ...),is used to store the current state organizationId-stackId | +| global.platform.cookie.existingSecret | string | `""` | is the name of the secret | +| global.platform.cookie.secretKeys | object | `{"encryptionKey":""}` | is the key contained within the secret | +| global.platform.membership.host | string | `"membership.{{ .Values.global.serviceHost }}"` | is the host for the membership | +| global.platform.membership.oauthClient.existingSecret | string | `""` | is the name of the secret | +| global.platform.membership.oauthClient.id | string | `"platform"` | is the id of the client | +| global.platform.membership.oauthClient.secret | string | `"changeMe1"` | is the secret of the client | +| global.platform.membership.oauthClient.secretKeys | object | `{"secret":""}` | is the key contained within the secret | +| global.platform.membership.scheme | string | `"https"` | is the scheme for the membership | +| global.platform.portal.host | string | `"portal.{{ .Values.global.serviceHost }}"` | is the host for the portal | +| global.platform.portal.scheme | string | `"https"` | is the scheme for the portal | +| global.serviceHost | string | `""` | is the base domain for portal and console | + +### Other Values + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| affinity | object | `{}` | Portal affinity | +| config.additionalEnv | object | `{}` | Additional environment variables | +| config.cookie.existingSecret | string | `""` | Cookie existing secret | +| config.cookie.secret | string | `"changeMe2"` | Cookie secret | +| config.cookie.secretKeys | object | `{"secret":""}` | Cookie secret key | +| config.environment | string | `"production"` | Portal environment | +| image.pullPolicy | string | `"IfNotPresent"` | image pull policy | +| image.repository | string | `"ghcr.io/formancehq/membership-ui"` | image repository | +| image.tag | string | `"764bb7e199e1d2882e4d5cd205eada0ef0abc283"` | image tag | +| ingress.annotations | object | `{}` | ingress annotations | +| ingress.className | string | `""` | ingress class name | +| ingress.enabled | bool | `true` | ingress enabled | +| ingress.hosts[0] | object | `{"host":"{{ tpl .Values.global.platform.portal.host $ }}","paths":[{"path":"/","pathType":"Prefix"}]}` | ingress host | +| ingress.hosts[0].paths[0].path | string | `"/"` | ingress path | +| ingress.hosts[0].paths[0].pathType | string | `"Prefix"` | ingress path type | +| ingress.tls | list | `[]` | ingress tls | +| livenessProbe | object | `{}` | Portal liveness probe | +| nodeSelector | object | `{}` | Portal node selector | +| podDisruptionBudget.enabled | bool | `false` | Enable pod disruption budget | +| podDisruptionBudget.maxUnavailable | int | `0` | Maximum unavailable pods | +| podDisruptionBudget.minAvailable | int | `1` | Minimum available pods | +| readinessProbe | object | `{}` | Portal readiness probe | +| replicas | int | `1` | Number of replicas | +| resources | object | `{}` | Portal resources | +| service.annotations | object | `{}` | service annotations | +| service.clusterIP | string | `""` | service cluster IP | +| service.ports.http | object | `{"port":3000}` | service http port | +| service.type | string | `"ClusterIP"` | service type | +| serviceAccount.annotations | object | `{}` | Service account annotations | +| serviceAccount.create | bool | `true` | Service account creation | +| serviceAccount.name | string | `""` | Service account name | +| tolerations | list | `[]` | Portal tolerations | +| volumeMounts | list | `[]` | Portal volume mounts | +| volumes | list | `[]` | Portal volumes | + diff --git a/charts/portal/templates/_helpers.tpl b/charts/portal/templates/_helpers.tpl new file mode 100644 index 0000000..f2e5f6b --- /dev/null +++ b/charts/portal/templates/_helpers.tpl @@ -0,0 +1,74 @@ +{{/* + + # + # Membership: + # MEMBERSHIP_URL_API is the url to the membership api + # MEMBERSHIP_CLIENT_ID is the client id of the membership api + # MEMBERSHIP_CLIENT_SECRET is the client secret of the membership api + # + # Portal: + # + # NODE_ENV is the environment of the app + # REDIRECT_URI is the url to redirect after login + # + # COOKIE_SECRET is the secret to encrypt the session cookies + # COOKIE_NAME is the name of the cookie + # COOKIE_DOMAIN is the domain of the cookie to set (it could be inferred from the service host) as only available to the portal + # + # Apps: + # APPS_CONSOLE is the url to the console app + # + # Console: + # + # CONSOLE_COOKIE_SECRET is the secret to encrypt the console cookies + # - As console cookie also need to be available to the portal, a common domain must be used + # - Adding a CONSOLE_COOKIE_DOMAIN for cookie distinction + +*/}} + +{{- define "portal.env" -}} +- name: NODE_ENV + value: {{ .Values.config.environment }} +- name: MEMBERSHIP_URL_API + value: {{ (printf "%s/api" (include "service.url" (dict "service" .Values.global.platform.membership "Context" .))) }} +- name: MEMBERSHIP_CLIENT_ID + value: "{{ .Values.global.platform.membership.oauthClient.id }}" +- name: MEMBERSHIP_CLIENT_SECRET + {{- if gt (len .Values.global.platform.membership.oauthClient.existingSecret) 0 }} + valueFrom: + secretKeyRef: + name: {{ .Values.global.platform.membership.oauthClient.existingSecret }} + key: {{ .Values.global.platform.membership.oauthClient.secretKeys.secret }} + {{- else }} + value: {{ .Values.global.platform.membership.oauthClient.secret | quote }} + {{- end }} +- name: REDIRECT_URI + value: {{ include "service.url" (dict "service" .Values.global.platform.portal "Context" .) }} +- name: COOKIE_SECRET + value: {{ .Values.config.cookie.secret }} +- name: COOKIE_NAME + value: __session_platform +- name: COOKIE_DOMAIN + value: {{ .Values.global.serviceHost }} +- name: CONSOLE_COOKIE_SECRET + {{- if gt (len .Values.global.platform.cookie.existingSecret) 0 }} + valueFrom: + secretKeyRef: + name: {{ .Values.global.platform.cookie.existingSecret }} + key: {{ .Values.global.platform.cookie.secretKeys.encryptionKey }} + {{- else }} + value: {{ .Values.global.platform.cookie.encryptionKey | quote }} + {{- end }} +- name: APPS_CONSOLE + value: {{ include "service.url" (dict "service" .Values.global.platform.console "Context" .) }} +- name: DEBUG + value: {{ .Values.global.debug | quote }} +{{ include "portal.additionalEnv" . }} +{{- end }} + +{{- define "portal.additionalEnv" -}} +{{ range $key, $value := $.Values.config.additionalEnv }} +- name: {{ $key }} + value: {{ tpl (. | quote) $ }} +{{- end }} +{{- end }} diff --git a/charts/portal/templates/deployment.yaml b/charts/portal/templates/deployment.yaml new file mode 100644 index 0000000..948e8cd --- /dev/null +++ b/charts/portal/templates/deployment.yaml @@ -0,0 +1,61 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "core.fullname" . }} + labels: + {{- include "core.labels" . | nindent 4 }} +spec: + replicas: {{ .Values.replicas }} + selector: + matchLabels: + {{- include "core.selectorLabels" . | nindent 6 }} + template: + metadata: + labels: + {{- include "core.selectorLabels" . | nindent 8 }} + spec: + serviceAccountName: {{ include "core.serviceAccountName" . }} + containers: + - name: {{ include "core.fullname" . }} + resources: + {{- toYaml .Values.resources | nindent 12 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + imagePullPolicy: {{.Values.image.pullPolicy}} + ports: + - containerPort: 3000 + protocol: TCP + name: http + readinessProbe: + httpGet: + path: /_info + port: 3000 + initialDelaySeconds: {{ .Values.readinessProbe.initialDelaySeconds | default 30}} + periodSeconds: 3 + livenessProbe: + httpGet: + path: /_info + port: 3000 + initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds | default 30}} + periodSeconds: 3 + env: + {{- include "portal.env" . | nindent 12 }} + {{ with .Values.volumeMounts }} + volumeMounts: + {{- toYaml . | nindent 12 }} + {{- end }} + {{ with .Values.volumes }} + volumes: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/charts/portal/templates/ingress.yaml b/charts/portal/templates/ingress.yaml new file mode 100644 index 0000000..d92aa66 --- /dev/null +++ b/charts/portal/templates/ingress.yaml @@ -0,0 +1 @@ +{{- include "core.ingress" . -}} \ No newline at end of file diff --git a/charts/portal/templates/pdp.yaml b/charts/portal/templates/pdp.yaml new file mode 100644 index 0000000..fb60155 --- /dev/null +++ b/charts/portal/templates/pdp.yaml @@ -0,0 +1 @@ +{{ include "core.podDisruptionBudget" . }} \ No newline at end of file diff --git a/charts/portal/templates/service.yaml b/charts/portal/templates/service.yaml new file mode 100644 index 0000000..8d1d3d8 --- /dev/null +++ b/charts/portal/templates/service.yaml @@ -0,0 +1,28 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "core.fullname" . }} + labels: + {{- include "core.labels" . | nindent 4 }} + {{- with .Values.service.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + type: {{ .Values.service.type }} + {{- with .Values.service.clusterIP }} + clusterIP: {{ . }} + {{- end }} + ports: + - name: http + port: {{ .Values.service.ports.http.port }} + {{- if and (or (eq .Values.service.type "NodePort") (eq .Values.service.type "LoadBalancer")) .Values.service.ports.http.nodePort }} + nodePort: {{ .Values.service.ports.http.nodePort }} + {{- end }} + targetPort: http + protocol: TCP + {{- if semverCompare ">=1.20-0" .Capabilities.KubeVersion.GitVersion }} + appProtocol: http + {{- end }} + selector: + {{- include "core.selectorLabels" . | nindent 4 }} diff --git a/charts/portal/templates/serviceaccount.yaml b/charts/portal/templates/serviceaccount.yaml new file mode 100644 index 0000000..b4c7924 --- /dev/null +++ b/charts/portal/templates/serviceaccount.yaml @@ -0,0 +1 @@ +{{- include "core.serviceAccount" . -}} diff --git a/charts/portal/values.schema.json b/charts/portal/values.schema.json new file mode 100644 index 0000000..ce18244 --- /dev/null +++ b/charts/portal/values.schema.json @@ -0,0 +1,273 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "affinity": { + "properties": {}, + "type": "object" + }, + "config": { + "properties": { + "additionalEnv": { + "properties": {}, + "type": "object" + }, + "cookie": { + "properties": { + "existingSecret": { + "type": "string" + }, + "secret": { + "type": "string" + }, + "secretKeys": { + "properties": { + "secret": { + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "environment": { + "type": "string" + } + }, + "type": "object" + }, + "global": { + "properties": { + "debug": { + "type": "boolean" + }, + "platform": { + "properties": { + "console": { + "properties": { + "host": { + "type": "string" + }, + "scheme": { + "type": "string" + } + }, + "type": "object" + }, + "cookie": { + "properties": { + "encryptionKey": { + "type": "string" + }, + "existingSecret": { + "type": "string" + }, + "secretKeys": { + "properties": { + "encryptionKey": { + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "membership": { + "properties": { + "host": { + "type": "string" + }, + "oauthClient": { + "properties": { + "existingSecret": { + "type": "string" + }, + "id": { + "type": "string" + }, + "secret": { + "type": "string" + }, + "secretKeys": { + "properties": { + "secret": { + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "scheme": { + "type": "string" + } + }, + "type": "object" + }, + "portal": { + "properties": { + "host": { + "type": "string" + }, + "scheme": { + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "serviceHost": { + "type": "string" + } + }, + "type": "object" + }, + "image": { + "properties": { + "pullPolicy": { + "type": "string" + }, + "repository": { + "type": "string" + }, + "tag": { + "type": "string" + } + }, + "type": "object" + }, + "ingress": { + "properties": { + "annotations": { + "properties": {}, + "type": "object" + }, + "className": { + "type": "string" + }, + "enabled": { + "type": "boolean" + }, + "hosts": { + "items": { + "properties": { + "host": { + "type": "string" + }, + "paths": { + "items": { + "properties": { + "path": { + "type": "string" + }, + "pathType": { + "type": "string" + } + }, + "type": "object" + }, + "type": "array" + } + }, + "type": "object" + }, + "type": "array" + }, + "tls": { + "type": "array" + } + }, + "type": "object" + }, + "livenessProbe": { + "properties": {}, + "type": "object" + }, + "nodeSelector": { + "properties": {}, + "type": "object" + }, + "podDisruptionBudget": { + "properties": { + "enabled": { + "type": "boolean" + }, + "maxUnavailable": { + "type": "integer" + }, + "minAvailable": { + "type": "integer" + } + }, + "type": "object" + }, + "readinessProbe": { + "properties": {}, + "type": "object" + }, + "replicas": { + "type": "integer" + }, + "resources": { + "properties": {}, + "type": "object" + }, + "service": { + "properties": { + "annotations": { + "properties": {}, + "type": "object" + }, + "clusterIP": { + "type": "string" + }, + "ports": { + "properties": { + "http": { + "properties": { + "port": { + "type": "integer" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "type": { + "type": "string" + } + }, + "type": "object" + }, + "serviceAccount": { + "properties": { + "annotations": { + "properties": {}, + "type": "object" + }, + "create": { + "type": "boolean" + }, + "name": { + "type": "string" + } + }, + "type": "object" + }, + "tolerations": { + "type": "array" + }, + "volumeMounts": { + "type": "array" + }, + "volumes": { + "type": "array" + } + }, + "type": "object" +} diff --git a/charts/portal/values.yaml b/charts/portal/values.yaml new file mode 100644 index 0000000..cc4196d --- /dev/null +++ b/charts/portal/values.yaml @@ -0,0 +1,167 @@ +global: + # -- Enable debug mode + # @section -- Global configuration + debug: false + # -- is the base domain for portal and console + # @section -- Global configuration + serviceHost: "" + + platform: + console: + # -- is the scheme for the console + # @section -- Global configuration + scheme: "https" + # -- is the host for the console + # @section -- Global configuration + host: "console.{{ .Values.global.serviceHost }}" + portal: + # -- is the scheme for the portal + # @section -- Global configuration + scheme: "https" + # -- is the host for the portal + # @section -- Global configuration + host: "portal.{{ .Values.global.serviceHost }}" + cookie: + # -- is used to encrypt a cookie that share authentication between platform services (console, portal, ...),is used to store the current state organizationId-stackId + # @section -- Global configuration + encryptionKey: "changeMe00" + # -- is the name of the secret + # @section -- Global configuration + existingSecret: "" + # -- is the key contained within the secret + # @section -- Global configuration + secretKeys: + encryptionKey: "" + + membership: + # -- is the scheme for the membership + # @section -- Global configuration + scheme: "https" + # -- is the host for the membership + # @section -- Global configuration + host: "membership.{{ .Values.global.serviceHost }}" + + oauthClient: + # -- is the id of the client + # @section -- Global configuration + id: "platform" + # -- is the secret of the client + # @section -- Global configuration + secret: "changeMe1" + # -- is the name of the secret + # @section -- Global configuration + existingSecret: "" + # -- is the key contained within the secret + # @section -- Global configuration + secretKeys: + secret: "" + +# -- Number of replicas +replicas: 1 + +podDisruptionBudget: + # -- Enable pod disruption budget + enabled: false + # -- Minimum available pods + minAvailable: 1 + # -- Maximum unavailable pods + maxUnavailable: 0 + +image: + # -- image repository + repository: ghcr.io/formancehq/membership-ui + # -- image pull policy + pullPolicy: IfNotPresent + # -- image tag + tag: 764bb7e199e1d2882e4d5cd205eada0ef0abc283 + +serviceAccount: + # -- Service account creation + create: true + # -- Service account name + name: "" + # -- Service account annotations + annotations: {} + +ingress: + # -- ingress enabled + enabled: true + # -- ingress class name + className: "" + # -- ingress annotations + annotations: {} + hosts: + # -- ingress host + - host: "{{ tpl .Values.global.platform.portal.host $ }}" + paths: + - # -- ingress path + path: / + # -- ingress path type + pathType: Prefix + # -- ingress tls + tls: + [] + # -- ingress tls secret name + # - secretName: YOUR_TLS_SECRET_NAME + +# Cookie store portal session in a cookie +config: + # -- Portal environment + environment: production + + cookie: + # -- Cookie secret + secret: "changeMe2" + # -- Cookie existing secret + existingSecret: "" + # -- Cookie secret key + secretKeys: + secret: "" + + # -- Additional environment variables + additionalEnv: {} + +service: + # -- service annotations + annotations: {} + # -- service type + type: ClusterIP + # -- service cluster IP + clusterIP: "" + ports: + # -- service http port + http: + port: 3000 + # -- service node port + # nodePort: + +# -- Portal volume mounts +volumeMounts: [] + +# -- Portal volumes +volumes: [] + +# -- Portal resources +resources: + {} + # requests: + # cpu: 250m + # memory: 512Mi + # limits: + # cpu: 500m + # memory: 2048Mi + +# -- Portal readiness probe +readinessProbe: {} + +# -- Portal liveness probe +livenessProbe: {} + +# -- Portal node selector +nodeSelector: {} + +# -- Portal tolerations +tolerations: [] + +# -- Portal affinity +affinity: {} diff --git a/charts/stargate/.helmignore b/charts/stargate/.helmignore new file mode 100644 index 0000000..0e8a0eb --- /dev/null +++ b/charts/stargate/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/charts/stargate/Chart.lock b/charts/stargate/Chart.lock new file mode 100644 index 0000000..e5e48db --- /dev/null +++ b/charts/stargate/Chart.lock @@ -0,0 +1,9 @@ +dependencies: +- name: core + repository: file://../core + version: v1.0.0-beta.1 +- name: postgresql + repository: oci://registry-1.docker.io/bitnamicharts + version: 15.5.38 +digest: sha256:b13cf3231ee262022c340523e848bc6b407d79d3774624d9e89ed97b8541a6aa +generated: "2024-10-02T09:06:21.909404852Z" diff --git a/charts/stargate/Chart.yaml b/charts/stargate/Chart.yaml new file mode 100644 index 0000000..9bc9220 --- /dev/null +++ b/charts/stargate/Chart.yaml @@ -0,0 +1,34 @@ +apiVersion: v2 +name: stargate +description: |- + Formance Stargate gRPC Gateway + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 0.3.0 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: "latest" + +dependencies: + - name: core + version: "v1.0.0-beta.1" + repository: file://../core + - name: postgresql + repository: oci://registry-1.docker.io/bitnamicharts + version: 15.5.X + condition: postgresql.enabled diff --git a/charts/stargate/Earthfile b/charts/stargate/Earthfile new file mode 100644 index 0000000..db87034 --- /dev/null +++ b/charts/stargate/Earthfile @@ -0,0 +1,41 @@ +VERSION --wildcard-builds --wildcard-copy 0.8 + +IMPORT ../ AS charts +IMPORT ../core AS core + +schema: + FROM charts+schema-base + WORKDIR /src + DO --pass-args charts+SCHEMA + +readme: + FROM charts+readme-base + WORKDIR /src/core + COPY --dir (core+dependencies/*) . + WORKDIR /src/stargate + COPY --dir (+dependencies/*) . + DO --pass-args charts+README_GENERATOR + +sources: + DO --pass-args charts+BASE + WORKDIR /src/stargate + COPY (+schema/*) . + DO --pass-args charts+SOURCES + +dependencies: + FROM +sources + WORKDIR /src/core + COPY --dir (core+dependencies/*) . + WORKDIR /src/stargate + DO --pass-args charts+DEPENDENCIES + +validate: + FROM +dependencies + WORKDIR /src/stargate + COPY (+readme/*) . + DO --pass-args charts+VALIDATE + +package: + FROM +validate + WORKDIR /src/stargate + DO --pass-args charts+PACKAGE \ No newline at end of file diff --git a/charts/stargate/README.md b/charts/stargate/README.md new file mode 100644 index 0000000..6b17c40 --- /dev/null +++ b/charts/stargate/README.md @@ -0,0 +1,87 @@ +# stargate + +![Version: 0.3.0](https://img.shields.io/badge/Version-0.3.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: latest](https://img.shields.io/badge/AppVersion-latest-informational?style=flat-square) + +Formance Stargate gRPC Gateway + +## Requirements + +| Repository | Name | Version | +|------------|------|---------| +| file://../core | core | v1.0.0-beta.1 | +| oci://registry-1.docker.io/bitnamicharts | postgresql | 15.5.X | + +## Values + +### Global AWS configuration + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| global.aws.elb | bool | `false` | Enable AWS ELB across all services, appropriate .aws.targertGroup must be set | +| global.aws.iam | bool | `false` | Enable AWS IAM across all services, appropriate .serviceAccount.annotations must be set | + +### Global configuration + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| global.monitoring.logs.enabled | bool | `true` | Enable logging | +| global.monitoring.logs.format | string | `"json"` | Format | +| global.monitoring.logs.level | string | `"info"` | Level: Info, Debug, Error | +| global.monitoring.metrics.enabled | bool | `false` | Enable | +| global.monitoring.metrics.endpoint | string | `""` | Endpoint | +| global.monitoring.metrics.exporter | string | `"otlp"` | Exporter | +| global.monitoring.metrics.insecure | bool | `true` | Insecure | +| global.monitoring.metrics.mode | string | `"grpc"` | Mode | +| global.monitoring.metrics.port | int | `4317` | Port | +| global.monitoring.traces.enabled | bool | `false` | Enable otel tracing | +| global.monitoring.traces.endpoint | string | `"localhost"` | Endpoint | +| global.monitoring.traces.exporter | string | `"otlp"` | Exporter | +| global.monitoring.traces.insecure | bool | `true` | Insecure | +| global.monitoring.traces.mode | string | `"grpc"` | Mode | +| global.monitoring.traces.port | int | `4317` | Port | +| global.postgresql.additionalArgs | string | `"sslmode=disable"` | Additional arguments for PostgreSQL Connection URI | +| global.postgresql.auth.database | string | `"formance"` | Name for a custom database to create (overrides `auth.database`) | +| global.postgresql.auth.existingSecret | string | `""` | Name of existing secret to use for PostgreSQL credentials (overrides `auth.existingSecret`). | +| global.postgresql.auth.password | string | `"formance"` | Password for the "postgres" admin user (overrides `auth.postgresPassword`) | +| global.postgresql.auth.postgresPassword | string | `"formance"` | Password for the custom user to create (overrides `auth.password`) | +| global.postgresql.auth.secretKeys.adminPasswordKey | string | `""` | Name of key in existing secret to use for PostgreSQL credentials (overrides `auth.secretKeys.adminPasswordKey`). Only used when `global.postgresql.auth.existingSecret` is set. | +| global.postgresql.auth.secretKeys.userPasswordKey | string | `""` | Name of key in existing secret to use for PostgreSQL credentials (overrides `auth.secretKeys.userPasswordKey`). Only used when `global.postgresql.auth.existingSecret` is set. | +| global.postgresql.auth.username | string | `"formance"` | Name for a custom user to create (overrides `auth.username`) | +| global.postgresql.host | string | `""` | Host for PostgreSQL (overrides included postgreql `host`) | +| global.postgresql.service.ports.postgresql | int | `5432` | PostgreSQL service port (overrides `service.ports.postgresql`) | + +### Global Nats configuration + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| global.nats.enabled | bool | `false` | Enable NATS | +| global.nats.url | string | `""` | URL for NATS | + +### Other Values + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| global.debug | bool | `false` | Enable debug mode | +| global.serviceHost | string | `""` | is the base domain for portal and console | +| affinity | object | `{}` | Affinity for pod assignment | +| autoscaling | object | `{"enabled":false,"maxReplicas":100,"minReplicas":1,"targetCPUUtilizationPercentage":80}` | Target memory utilization percentage | +| aws | object | `{"targetGroups":{"grpc":{"ipAddressType":"ipv4","serviceRef":{"name":"{{ include \"stargate.fullname\" $ }}","port":"{{ .Values.service.ports.grpc | default 3068 }}"},"targetGroupARN":"","targetType":"ip"}}}` | Target group name | +| config | object | `{"auth_issuer_url":"","monitoring":{"serviceName":"stargate"},"nats":{"clientID":"stargate","topicMapping":"stargate"}}` | Service name for monitoring | +| fullnameOverride | string | `""` | String to fully override stargate.fullname template with a string | +| image | object | `{"pullPolicy":"IfNotPresent","repository":"ghcr.io/formancehq/stargate","tag":""}` | Image tag | +| ingress | object | `{"annotations":{},"className":"","enabled":false,"hosts":[{"host":"stargate.{{ .Values.global.serviceHost }}","paths":[{"path":"/","pathType":"Prefix"}]}],"tls":[]}` | Ingress TLS | +| nameOverride | string | `""` | String to partially override stargate.fullname template with a string (will append the release name) | +| nodeSelector | object | `{}` | Node labels for pod assignment | +| podAnnotations | object | `{}` | Annotations to add to the pod | +| podDisruptionBudget.enabled | bool | `false` | Enable a [pod distruption budget](https://kubernetes.io/docs/tasks/run-application/configure-pdb/) to help dealing with [disruptions](https://kubernetes.io/docs/concepts/workloads/pods/disruptions/). It is **highly recommended** for webhooks as disruptions can prevent launching new pods. | +| podDisruptionBudget.maxUnavailable | int | `0` | | +| podDisruptionBudget.minAvailable | int | `1` | | +| podSecurityContext | object | `{}` | Security context for the pod | +| replicaCount | int | `1` | Number of replicas | +| resources | object | `{}` | Resource limits and requests | +| securityContext | object | `{}` | Security context for the container | +| service | object | `{"ports":{"grpc":{"port":3068},"http":{"port":8080}},"type":"ClusterIP"}` | gRPC port | +| serviceAccount | object | `{"annotations":{},"create":true,"name":""}` | The name of the service account to use. | +| tolerations | list | `[]` | Tolerations for pod assignment | +| topologySpreadConstraints | list | `[]` | Topology spread constraints | + diff --git a/charts/stargate/templates/NOTES.txt b/charts/stargate/templates/NOTES.txt new file mode 100644 index 0000000..22f78ba --- /dev/null +++ b/charts/stargate/templates/NOTES.txt @@ -0,0 +1,16 @@ +1. Get the application URL by running these commands: +{{- if contains "NodePort" .Values.service.type }} + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "core.fullname" . }}) + export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + echo http://$NODE_IP:$NODE_PORT +{{- else if contains "LoadBalancer" .Values.service.type }} + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "core.fullname" . }}' + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "core.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") + echo http://$SERVICE_IP:{{ .Values.service.ports.http }} +{{- else if contains "ClusterIP" .Values.service.type }} + export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "core.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") + export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") + echo "Visit http://127.0.0.1:8080 to use your application" + kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT +{{- end }} diff --git a/charts/stargate/templates/deployment.yaml b/charts/stargate/templates/deployment.yaml new file mode 100644 index 0000000..e5b74ba --- /dev/null +++ b/charts/stargate/templates/deployment.yaml @@ -0,0 +1,89 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "core.fullname" . }} + labels: + {{- include "core.labels" . | nindent 4 }} +spec: + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include "core.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "core.selectorLabels" . | nindent 8 }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "core.serviceAccountName" . }} + {{- with .Values.topologySpreadConstraints }} + topologySpreadConstraints: + {{- range $constraint := . }} + - {{ toYaml $constraint | nindent 10 | trim }} + {{- if not $constraint.labelSelector }} + labelSelector: + matchLabels: + {{- include "core.selectorLabels" $ | nindent 14 }} + {{- end }} + {{- end }} + {{- end }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: {{ include "core.fullname" . }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + args: + - stargate + - server + env: + - name: AUTH_ISSUER_URL + value: "{{ tpl .Values.config.auth_issuer_url . }}" + - name: GRPC_ADDR + value: "0.0.0.0:{{ .Values.service.ports.grpc | default 3068 }}" + - name: HTTP_ADDR + value: "0.0.0.0:{{ .Values.service.ports.http | default 8080 }}" + {{- include "core.postgres.uri" . | nindent 12 }} + {{- include "aws.iam.postgres" . | nindent 12 }} + {{- include "core.nats.env" . | nindent 12 }} + {{- include "core.env.common" . | nindent 12 }} + {{- include "core.monitoring" . | nindent 12 }} + ports: + - name: http + containerPort: {{ default 8080 .Values.service.ports.http.port }} + protocol: TCP + - name: grpc + containerPort: {{ default 3068 .Values.service.ports.grpc.port }} + protocol: TCP + livenessProbe: + httpGet: + path: /_healthcheck + port: http + readinessProbe: + httpGet: + path: /_healthcheck + port: http + resources: + {{- toYaml .Values.resources | nindent 12 }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/charts/stargate/templates/hpa.yaml b/charts/stargate/templates/hpa.yaml new file mode 100644 index 0000000..a137024 --- /dev/null +++ b/charts/stargate/templates/hpa.yaml @@ -0,0 +1 @@ +{{ include "core.hpa" . }} \ No newline at end of file diff --git a/charts/stargate/templates/ingress.yaml b/charts/stargate/templates/ingress.yaml new file mode 100644 index 0000000..cf4a833 --- /dev/null +++ b/charts/stargate/templates/ingress.yaml @@ -0,0 +1 @@ +{{ include "core.ingress" . }} \ No newline at end of file diff --git a/charts/stargate/templates/pdp.yaml b/charts/stargate/templates/pdp.yaml new file mode 100644 index 0000000..fb60155 --- /dev/null +++ b/charts/stargate/templates/pdp.yaml @@ -0,0 +1 @@ +{{ include "core.podDisruptionBudget" . }} \ No newline at end of file diff --git a/charts/stargate/templates/service.yaml b/charts/stargate/templates/service.yaml new file mode 100644 index 0000000..54d65e1 --- /dev/null +++ b/charts/stargate/templates/service.yaml @@ -0,0 +1,19 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "core.fullname" . }} + labels: + {{- include "core.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ default 8080 .Values.service.ports.http.port }} + targetPort: {{ default 8080 .Values.service.ports.http.port }} + protocol: TCP + name: http + - port: {{ default 3068 .Values.service.ports.grpc.port }} + targetPort: {{ default 3068 .Values.service.ports.grpc.port }} + protocol: TCP + name: grpc + selector: + {{- include "core.selectorLabels" . | nindent 4 }} diff --git a/charts/stargate/templates/serviceaccount.yaml b/charts/stargate/templates/serviceaccount.yaml new file mode 100644 index 0000000..9b540c9 --- /dev/null +++ b/charts/stargate/templates/serviceaccount.yaml @@ -0,0 +1 @@ +{{- include "core.serviceAccount" . -}} \ No newline at end of file diff --git a/charts/stargate/values.schema.json b/charts/stargate/values.schema.json new file mode 100644 index 0000000..f531a8d --- /dev/null +++ b/charts/stargate/values.schema.json @@ -0,0 +1,392 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "affinity": { + "properties": {}, + "type": "object" + }, + "autoscaling": { + "properties": { + "enabled": { + "type": "boolean" + }, + "maxReplicas": { + "type": "integer" + }, + "minReplicas": { + "type": "integer" + }, + "targetCPUUtilizationPercentage": { + "type": "integer" + } + }, + "type": "object" + }, + "aws": { + "properties": { + "targetGroups": { + "properties": { + "grpc": { + "properties": { + "ipAddressType": { + "type": "string" + }, + "serviceRef": { + "properties": { + "name": { + "type": "string" + }, + "port": { + "type": "string" + } + }, + "type": "object" + }, + "targetGroupARN": { + "type": "string" + }, + "targetType": { + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "config": { + "properties": { + "auth_issuer_url": { + "type": "string" + }, + "monitoring": { + "properties": { + "serviceName": { + "type": "string" + } + }, + "type": "object" + }, + "nats": { + "properties": { + "clientID": { + "type": "string" + }, + "topicMapping": { + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "fullnameOverride": { + "type": "string" + }, + "global": { + "properties": { + "aws": { + "properties": { + "elb": { + "type": "boolean" + }, + "iam": { + "type": "boolean" + } + }, + "type": "object" + }, + "debug": { + "type": "boolean" + }, + "monitoring": { + "properties": { + "logs": { + "properties": { + "enabled": { + "type": "boolean" + }, + "format": { + "type": "string" + }, + "level": { + "type": "string" + } + }, + "type": "object" + }, + "metrics": { + "properties": { + "enabled": { + "type": "boolean" + }, + "endpoint": { + "type": "string" + }, + "exporter": { + "type": "string" + }, + "insecure": { + "type": "boolean" + }, + "mode": { + "type": "string" + }, + "port": { + "type": "integer" + } + }, + "type": "object" + }, + "traces": { + "properties": { + "enabled": { + "type": "boolean" + }, + "endpoint": { + "type": "string" + }, + "exporter": { + "type": "string" + }, + "insecure": { + "type": "boolean" + }, + "mode": { + "type": "string" + }, + "port": { + "type": "integer" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "nats": { + "properties": { + "enabled": { + "type": "boolean" + }, + "url": { + "type": "string" + } + }, + "type": "object" + }, + "postgresql": { + "properties": { + "additionalArgs": { + "type": "string" + }, + "auth": { + "properties": { + "database": { + "type": "string" + }, + "existingSecret": { + "type": "string" + }, + "password": { + "type": "string" + }, + "postgresPassword": { + "type": "string" + }, + "secretKeys": { + "properties": { + "adminPasswordKey": { + "type": "string" + }, + "userPasswordKey": { + "type": "string" + } + }, + "type": "object" + }, + "username": { + "type": "string" + } + }, + "type": "object" + }, + "host": { + "type": "string" + }, + "service": { + "properties": { + "ports": { + "properties": { + "postgresql": { + "type": "integer" + } + }, + "type": "object" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "serviceHost": { + "type": "string" + } + }, + "type": "object" + }, + "image": { + "properties": { + "pullPolicy": { + "type": "string" + }, + "repository": { + "type": "string" + }, + "tag": { + "type": "string" + } + }, + "type": "object" + }, + "ingress": { + "properties": { + "annotations": { + "properties": {}, + "type": "object" + }, + "className": { + "type": "string" + }, + "enabled": { + "type": "boolean" + }, + "hosts": { + "items": { + "properties": { + "host": { + "type": "string" + }, + "paths": { + "items": { + "properties": { + "path": { + "type": "string" + }, + "pathType": { + "type": "string" + } + }, + "type": "object" + }, + "type": "array" + } + }, + "type": "object" + }, + "type": "array" + }, + "tls": { + "type": "array" + } + }, + "type": "object" + }, + "nameOverride": { + "type": "string" + }, + "nodeSelector": { + "properties": {}, + "type": "object" + }, + "podAnnotations": { + "properties": {}, + "type": "object" + }, + "podDisruptionBudget": { + "properties": { + "enabled": { + "type": "boolean" + }, + "maxUnavailable": { + "type": "integer" + }, + "minAvailable": { + "type": "integer" + } + }, + "type": "object" + }, + "podSecurityContext": { + "properties": {}, + "type": "object" + }, + "replicaCount": { + "type": "integer" + }, + "resources": { + "properties": {}, + "type": "object" + }, + "securityContext": { + "properties": {}, + "type": "object" + }, + "service": { + "properties": { + "ports": { + "properties": { + "grpc": { + "properties": { + "port": { + "type": "integer" + } + }, + "type": "object" + }, + "http": { + "properties": { + "port": { + "type": "integer" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "type": { + "type": "string" + } + }, + "type": "object" + }, + "serviceAccount": { + "properties": { + "annotations": { + "properties": {}, + "type": "object" + }, + "create": { + "type": "boolean" + }, + "name": { + "type": "string" + } + }, + "type": "object" + }, + "tolerations": { + "type": "array" + }, + "topologySpreadConstraints": { + "type": "array" + } + }, + "type": "object" +} diff --git a/charts/stargate/values.yaml b/charts/stargate/values.yaml new file mode 100644 index 0000000..3d8b4b9 --- /dev/null +++ b/charts/stargate/values.yaml @@ -0,0 +1,259 @@ +global: + # -- Enable debug mode + debug: false + # -- is the base domain for portal and console + serviceHost: "" + + monitoring: + traces: + # -- Enable otel tracing + # @section -- Global configuration + enabled: false + # -- Endpoint + # @section -- Global configuration + endpoint: "localhost" + # -- Exporter + # @section -- Global configuration + exporter: "otlp" + # -- Insecure + # @section -- Global configuration + insecure: true + # -- Mode + # @section -- Global configuration + mode: "grpc" + # -- Port + # @section -- Global configuration + port: 4317 + logs: + # -- Enable logging + # @section -- Global configuration + enabled: true + # -- Level: Info, Debug, Error + # @section -- Global configuration + level: "info" + # -- Format + # @section -- Global configuration + format: "json" + metrics: + # -- Enable + # @section -- Global configuration + enabled: false + # -- Mode + # @section -- Global configuration + mode: grpc + # -- Exporter + # @section -- Global configuration + exporter: otlp + # -- Endpoint + # @section -- Global configuration + endpoint: "" + # -- Port + # @section -- Global configuration + port: 4317 + # -- Insecure + # @section -- Global configuration + insecure: true + + nats: + # -- Enable NATS + # @section -- Global Nats configuration + enabled: false + + # -- URL for NATS + # @section -- Global Nats configuration + url: "" + + aws: + # -- Enable AWS ELB across all services, appropriate .aws.targertGroup must be set + # @section -- Global AWS configuration + + elb: false + # -- Enable AWS IAM across all services, appropriate .serviceAccount.annotations must be set + # @section -- Global AWS configuration + iam: false + + postgresql: + # -- Host for PostgreSQL (overrides included postgreql `host`) + # @section -- Global configuration + host: "" + # -- Additional arguments for PostgreSQL Connection URI + # @section -- Global configuration + additionalArgs: "sslmode=disable" + auth: + # -- Name for a custom user to create (overrides `auth.username`) + # @section -- Global configuration + username: formance + # -- Password for the "postgres" admin user (overrides `auth.postgresPassword`) + # @section -- Global configuration + password: formance + # -- Name for a custom database to create (overrides `auth.database`) + # @section -- Global configuration + database: formance + # -- Password for the custom user to create (overrides `auth.password`) + # @section -- Global configuration + postgresPassword: formance + # -- Name of existing secret to use for PostgreSQL credentials (overrides `auth.existingSecret`). + # @section -- Global configuration + existingSecret: "" + secretKeys: + # -- Name of key in existing secret to use for PostgreSQL credentials (overrides `auth.secretKeys.adminPasswordKey`). Only used when `global.postgresql.auth.existingSecret` is set. + # @section -- Global configuration + adminPasswordKey: "" + # -- Name of key in existing secret to use for PostgreSQL credentials (overrides `auth.secretKeys.userPasswordKey`). Only used when `global.postgresql.auth.existingSecret` is set. + # @section -- Global configuration + userPasswordKey: "" + + service: + ports: + # -- PostgreSQL service port (overrides `service.ports.postgresql`) + # @section -- Global configuration + postgresql: 5432 + +podDisruptionBudget: + # -- Enable a [pod distruption budget](https://kubernetes.io/docs/tasks/run-application/configure-pdb/) to help dealing with [disruptions](https://kubernetes.io/docs/concepts/workloads/pods/disruptions/). + # It is **highly recommended** for webhooks as disruptions can prevent launching new pods. + enabled: false + minAvailable: 1 + maxUnavailable: 0 + +# -- Topology spread constraints +topologySpreadConstraints: + [] + # - maxSkew: 1 + # topologyKey: kubernetes.io/hostname + # whenUnsatisfiable: DoNotSchedule + +# -- Number of replicas +replicaCount: 1 + +# -- String to partially override stargate.fullname template with a string (will append the release name) +nameOverride: "" +# -- String to fully override stargate.fullname template with a string +fullnameOverride: "" + +# -- Specifies whether a service account should be created +# -- Annotations to add to the service account +# -- The name of the service account to use. +serviceAccount: + create: true + annotations: {} + name: "" + +# -- Annotations to add to the pod +podAnnotations: {} + +# -- Security context for the pod +podSecurityContext: + {} + # fsGroup: 2000 + +# -- Security context for the container +securityContext: + {} + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 + +# -- Kubernetes service type +# -- HTTP port +# -- gRPC port +service: + type: ClusterIP + ports: + http: + port: 8080 + grpc: + port: 3068 + +# -- Enable ingress +# -- Ingress class name +# -- Ingress annotations +# -- Ingress host +# -- Ingress path +# -- Ingress path type +# -- Ingress TLS +ingress: + enabled: false + className: "" + annotations: + {} + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" + hosts: + - host: "stargate.{{ .Values.global.serviceHost }}" + paths: + - path: / + pathType: Prefix + tls: [] + # - secretName: chart-example-tls + # hosts: + # - chart-example.local + +# -- Resource limits and requests +resources: + {} + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + +# -- Enable autoscaling +# -- Minimum replicas +# -- Maximum replicas +# -- Target CPU utilization percentage +# -- Target memory utilization percentage +autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 100 + targetCPUUtilizationPercentage: 80 + # targetMemoryUtilizationPercentage: 80 + +# -- Node labels for pod assignment +nodeSelector: {} + +# -- Tolerations for pod assignment +tolerations: [] + +# -- Affinity for pod assignment +affinity: {} + +# -- Target group IP address type +# -- Target group target type +# -- Target group service reference name +# -- Target group service reference port +# -- Target group name +aws: + targetGroups: + grpc: + ipAddressType: "ipv4" + targetType: "ip" + targetGroupARN: "" + serviceRef: + name: '{{ include "stargate.fullname" $ }}' + port: "{{ .Values.service.ports.grpc | default 3068 }}" + +# -- Auth issuer URL <-> Membership +# -- NATS client ID +# -- NATS topic mapping +# -- Service name for monitoring +config: + auth_issuer_url: "" + nats: + clientID: "stargate" + topicMapping: "stargate" + monitoring: + serviceName: "stargate" + +# -- Image repository +# -- Image pull policy +# -- Image tag +image: + repository: "ghcr.io/formancehq/stargate" + pullPolicy: "IfNotPresent" + tag: "" diff --git a/test/helm/Earthfile b/test/helm/Earthfile new file mode 100644 index 0000000..cf66035 --- /dev/null +++ b/test/helm/Earthfile @@ -0,0 +1,28 @@ +VERSION --wildcard-builds --wildcard-copy 0.8 + +IMPORT github.com/formancehq/earthly:tags/v0.16.3 AS core +IMPORT ../.. AS root + +sources: + FROM core+base-image + WORKDIR /src + COPY go.* . + COPY --dir suite . + # TODO(david): refactor tests to base test on specific helm package name-version.tgz + # Then we can remove this line, and COPY root+packages/* /build + COPY --dir (root+sources/*/charts --PATH=charts) . + SAVE ARTIFACT /src + +tests: + FROM core+builder-image + RUN apk add helm + WORKDIR /src + COPY (+sources/*) . + + ARG additionalArgs + ENV CHART_DIR /src/charts/ + ENV CGO_ENABLED=1 + RUN --mount=type=cache,id=gomod,target=${GOPATH}/pkg/mod \ + --mount=type=cache,id=gobuild,target=/root/.cache/go-build \ + --mount=type=cache,id=golangci,target=/root/.cache/golangci-lint \ + go test ./... -race $additionalArgs \ No newline at end of file diff --git a/test/helm/go.mod b/test/helm/go.mod new file mode 100644 index 0000000..f71d61a --- /dev/null +++ b/test/helm/go.mod @@ -0,0 +1,89 @@ +module github.com/formancehq/clients/tests/helm + +go 1.22.0 + +require ( + github.com/stretchr/testify v1.9.0 + k8s.io/api v0.28.4 + k8s.io/apimachinery v0.28.4 +) + +require ( + github.com/BurntSushi/toml v1.3.2 // indirect + github.com/aws/aws-sdk-go v1.44.122 // indirect + github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect + github.com/emicklei/go-restful/v3 v3.9.0 // indirect + github.com/ghodss/yaml v1.0.0 // indirect + github.com/go-errors/errors v1.0.2-0.20180813162953-d98b870cc4e0 // indirect + github.com/go-logr/logr v1.2.4 // indirect + github.com/go-openapi/jsonpointer v0.19.6 // indirect + github.com/go-openapi/jsonreference v0.20.2 // indirect + github.com/go-openapi/swag v0.22.3 // indirect + github.com/go-sql-driver/mysql v1.4.1 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/gonvenience/bunt v1.3.5 // indirect + github.com/gonvenience/neat v1.3.12 // indirect + github.com/gonvenience/term v1.0.2 // indirect + github.com/gonvenience/text v1.0.7 // indirect + github.com/gonvenience/wrap v1.1.2 // indirect + github.com/gonvenience/ytbx v1.4.4 // indirect + github.com/google/gnostic-models v0.6.8 // indirect + github.com/google/go-cmp v0.5.9 // indirect + github.com/google/gofuzz v1.2.0 // indirect + github.com/google/uuid v1.3.0 // indirect + github.com/gruntwork-io/go-commons v0.8.0 // indirect + github.com/hashicorp/errwrap v1.0.0 // indirect + github.com/hashicorp/go-multierror v1.1.0 // indirect + github.com/homeport/dyff v1.6.0 // indirect + github.com/imdario/mergo v0.3.11 // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/lucasb-eyer/go-colorful v1.2.0 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/mattn/go-ciede2000 v0.0.0-20170301095244-782e8c62fec3 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect + github.com/mattn/go-zglob v0.0.2-0.20190814121620-e3c945676326 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/go-ps v1.0.0 // indirect + github.com/mitchellh/hashstructure v1.1.0 // indirect + github.com/moby/spdystream v0.2.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/pquerna/otp v1.2.0 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/sergi/go-diff v1.3.1 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/texttheater/golang-levenshtein v1.0.1 // indirect + github.com/urfave/cli v1.22.2 // indirect + github.com/virtuald/go-ordered-json v0.0.0-20170621173500-b18e6e673d74 // indirect + golang.org/x/crypto v0.21.0 // indirect + golang.org/x/net v0.23.0 // indirect + golang.org/x/oauth2 v0.8.0 // indirect + golang.org/x/sync v0.4.0 // indirect + golang.org/x/sys v0.18.0 // indirect + golang.org/x/term v0.18.0 // indirect + golang.org/x/text v0.14.0 // indirect + golang.org/x/time v0.3.0 // indirect + google.golang.org/appengine v1.6.7 // indirect + google.golang.org/protobuf v1.33.0 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + k8s.io/client-go v0.28.4 // indirect + k8s.io/klog/v2 v2.100.1 // indirect + k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 // indirect + k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 // indirect + sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect + sigs.k8s.io/yaml v1.3.0 // indirect +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/gruntwork-io/terratest v0.47.1 + github.com/pmezard/go-difflib v1.0.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/test/helm/go.sum b/test/helm/go.sum new file mode 100644 index 0000000..a234924 --- /dev/null +++ b/test/helm/go.sum @@ -0,0 +1,272 @@ +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= +github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/aws/aws-sdk-go v1.44.122 h1:p6mw01WBaNpbdP2xrisz5tIkcNwzj/HysobNoaAHjgo= +github.com/aws/aws-sdk-go v1.44.122/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI= +github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE= +github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= +github.com/go-errors/errors v1.0.2-0.20180813162953-d98b870cc4e0 h1:skJKxRtNmevLqnayafdLe2AsenqRupVmzZSqrvb5caU= +github.com/go-errors/errors v1.0.2-0.20180813162953-d98b870cc4e0/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= +github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= +github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= +github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= +github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= +github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= +github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= +github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA= +github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/gonvenience/bunt v1.3.5 h1:wSQquifvwEWtzn27k1ngLfeLaStyt0k1b/K6TrlCNAs= +github.com/gonvenience/bunt v1.3.5/go.mod h1:7ApqkVBEWvX04oJ28Q2WeI/BvJM6VtukaJAU/q/pTs8= +github.com/gonvenience/neat v1.3.12 h1:xwIyRbJcG9LgcDYys+HHLH9DqqHeQsUpS5CfBUeskbs= +github.com/gonvenience/neat v1.3.12/go.mod h1:8OljAIgPelN0uPPO94VBqxK+Kz98d6ZFwHDg5o/PfkE= +github.com/gonvenience/term v1.0.2 h1:qKa2RydbWIrabGjR/fegJwpW5m+JvUwFL8mLhHzDXn0= +github.com/gonvenience/term v1.0.2/go.mod h1:wThTR+3MzWtWn7XGVW6qQ65uaVf8GHED98KmwpuEQeo= +github.com/gonvenience/text v1.0.7 h1:YmIqmgTwxnACYCG59DykgMbomwteYyNhAmEUEJtPl14= +github.com/gonvenience/text v1.0.7/go.mod h1:OAjH+mohRszffLY6OjgQcUXiSkbrIavooFpfIt1ZwAs= +github.com/gonvenience/wrap v1.1.2 h1:xPKxNwL1HCguwyM+HlP/1CIuc9LRd7k8RodLwe9YTZA= +github.com/gonvenience/wrap v1.1.2/go.mod h1:GiryBSXoI3BAAhbWD1cZVj7RZmtiu0ERi/6R6eJfslI= +github.com/gonvenience/ytbx v1.4.4 h1:jQopwyaLsVGuwdxSiN4WkXjsEaFNPJ3V4lUj7eyEpzo= +github.com/gonvenience/ytbx v1.4.4/go.mod h1:w37+MKCPcCMY/jpPNmEklD4xKqrOAVBO6kIWW2+uI6M= +github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= +github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20230602150820-91b7bce49751 h1:hR7/MlvK23p6+lIw9SN1TigNLn9ZnF3W4SYRKq2gAHs= +github.com/google/pprof v0.0.0-20230602150820-91b7bce49751/go.mod h1:Jh3hGz2jkYak8qXPD19ryItVnUgpgeqzdkY/D0EaeuA= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gruntwork-io/go-commons v0.8.0 h1:k/yypwrPqSeYHevLlEDmvmgQzcyTwrlZGRaxEM6G0ro= +github.com/gruntwork-io/go-commons v0.8.0/go.mod h1:gtp0yTtIBExIZp7vyIV9I0XQkVwiQZze678hvDXof78= +github.com/gruntwork-io/terratest v0.47.1 h1:qOaxnL7Su5+KpDHYUN/ek1jn8ImvCKtOkaY4OSMS4tI= +github.com/gruntwork-io/terratest v0.47.1/go.mod h1:LnYX8BN5WxUMpDr8rtD39oToSL4CBERWSCusbJ0d/64= +github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI= +github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= +github.com/homeport/dyff v1.6.0 h1:AN+ikld0Fy+qx34YE7655b/bpWuxS6cL9k852pE2GUc= +github.com/homeport/dyff v1.6.0/go.mod h1:FlAOFYzeKvxmU5nTrnG+qrlJVWpsFew7pt8L99p5q8k= +github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA= +github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= +github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattn/go-ciede2000 v0.0.0-20170301095244-782e8c62fec3 h1:BXxTozrOU8zgC5dkpn3J6NTRdoP+hjok/e+ACr4Hibk= +github.com/mattn/go-ciede2000 v0.0.0-20170301095244-782e8c62fec3/go.mod h1:x1uk6vxTiVuNt6S5R2UYgdhpj3oKojXvOXauHZ7dEnI= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-zglob v0.0.1/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo= +github.com/mattn/go-zglob v0.0.2-0.20190814121620-e3c945676326 h1:ofNAzWCcyTALn2Zv40+8XitdzCgXY6e9qvXwN9W0YXg= +github.com/mattn/go-zglob v0.0.2-0.20190814121620-e3c945676326/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= +github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= +github.com/mitchellh/hashstructure v1.1.0 h1:P6P1hdjqAAknpY/M1CGipelZgp+4y9ja9kmUZPXP+H0= +github.com/mitchellh/hashstructure v1.1.0/go.mod h1:xUDAozZz0Wmdiufv0uyhnHkUTN6/6d8ulp4AwfLKrmA= +github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8= +github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4= +github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o= +github.com/onsi/gomega v1.28.0 h1:i2rg/p9n/UqIDAMFUJ6qIUUMcsqOuUHgbpbu235Vr1c= +github.com/onsi/gomega v1.28.0/go.mod h1:A1H2JE76sI14WIP57LMKj7FVfCHx3g3BcZVjJG8bjX8= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pquerna/otp v1.2.0 h1:/A3+Jn+cagqayeR3iHs/L62m5ue7710D35zl1zJ1kok= +github.com/pquerna/otp v1.2.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= +github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/texttheater/golang-levenshtein v1.0.1 h1:+cRNoVrfiwufQPhoMzB6N0Yf/Mqajr6t1lOv8GyGE2U= +github.com/texttheater/golang-levenshtein v1.0.1/go.mod h1:PYAKrbF5sAiq9wd+H82hs7gNaen0CplQ9uvm6+enD/8= +github.com/urfave/cli v1.22.2 h1:gsqYFH8bb9ekPA12kRo0hfjngWQjkJPlN9R0N78BoUo= +github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/virtuald/go-ordered-json v0.0.0-20170621173500-b18e6e673d74 h1:JwtAtbp7r/7QSyGz8mKUbYJBg2+6Cd7OjM8o/GNOcVo= +github.com/virtuald/go-ordered-json v0.0.0-20170621173500-b18e6e673d74/go.mod h1:RmMWU37GKR2s6pgrIEB4ixgpVCt/cf7dnJv3fuH1J1c= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= +golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8= +golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= +golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= +golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= +golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +k8s.io/api v0.28.4 h1:8ZBrLjwosLl/NYgv1P7EQLqoO8MGQApnbgH8tu3BMzY= +k8s.io/api v0.28.4/go.mod h1:axWTGrY88s/5YE+JSt4uUi6NMM+gur1en2REMR7IRj0= +k8s.io/apimachinery v0.28.4 h1:zOSJe1mc+GxuMnFzD4Z/U1wst50X28ZNsn5bhgIIao8= +k8s.io/apimachinery v0.28.4/go.mod h1:wI37ncBvfAoswfq626yPTe6Bz1c22L7uaJ8dho83mgg= +k8s.io/client-go v0.28.4 h1:Np5ocjlZcTrkyRJ3+T3PkXDpe4UpatQxj85+xjaD2wY= +k8s.io/client-go v0.28.4/go.mod h1:0VDZFpgoZfelyP5Wqu0/r/TRYcLYuJ2U1KEeoaPa1N4= +k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg= +k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 h1:LyMgNKD2P8Wn1iAwQU5OhxCKlKJy0sHc+PcDwFB24dQ= +k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9/go.mod h1:wZK2AVp1uHCp4VamDVgBP2COHZjqD1T68Rf0CM3YjSM= +k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 h1:qY1Ad8PODbnymg2pRbkyMT/ylpTrCM8P2RJ0yroCyIk= +k8s.io/utils v0.0.0-20230406110748-d93618cff8a2/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= +sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= +sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= +sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/test/helm/suite/cloudprem_test.go b/test/helm/suite/cloudprem_test.go new file mode 100644 index 0000000..d739ca6 --- /dev/null +++ b/test/helm/suite/cloudprem_test.go @@ -0,0 +1,106 @@ +package suite_test + +import ( + "fmt" + "os" + "path/filepath" + "strconv" + "strings" + "testing" + + "github.com/gruntwork-io/terratest/modules/helm" + "github.com/gruntwork-io/terratest/modules/random" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" +) + +type TemplatePlatfromTest struct { + Base +} + +func TestCloudprem(t *testing.T) { + if chartDirFromEnv := os.Getenv("CHART_DIR"); chartDirFromEnv != "" { + chartDir = chartDirFromEnv + } + + chartPath, err := filepath.Abs(filepath.Join(chartDir, mainChart)) + require.NoError(t, err) + + chartTest := &TemplatePlatfromTest{ + Base: Base{ + ChartPath: chartPath, + ChartDir: chartDir, + Release: releaseName, + Name: "cloudprem", + Namespace: "ccsm-helm-" + strings.ToLower(random.UniqueId()), + setValues: map[string]string{}, + }, + } + + // Update deps + for _, app := range plaformCharts { + chartPath, err := filepath.Abs(filepath.Join(chartDir, app)) + require.NoError(t, err) + + chart := &TemplatePlatfromTest{ + Base: Base{ + ChartPath: chartPath, + ChartDir: chartDir, + Release: releaseName, + Name: app, + Namespace: "ccsm-helm-" + strings.ToLower(random.UniqueId()), + setValues: map[string]string{}, + }, + } + chart.setupSuite(t) + } + + // Then update the main chart deps + chartTest.setupSuite(t) + suite.Run(t, chartTest) +} +func (s *TemplatePlatfromTest) TestAppEnabled() { + t := s.T() + t.Parallel() + + for _, app := range plaformCharts { + appDir := fmt.Sprint(s.ChartDir + app + "/templates") + dirEntries, err := os.ReadDir(appDir) + require.NoError(t, err) + templateNames := []string{} + for _, dirEntry := range dirEntries { + if strings.HasSuffix(dirEntry.Name(), ".yaml") { + templateNames = append(templateNames, dirEntry.Name()) + } + } + + for _, templateName := range templateNames { + for _, enabled := range []bool{true, false} { + t.Run(fmt.Sprintf("app-%s-%s-enabled-%s", app, templateName, strconv.FormatBool(enabled)), func(t *testing.T) { + t.Parallel() + values := make(map[string]string, 0) + switch templateName { + case "hpa.yaml": + values["autoscaling.enabled"] = "true" + case "ingress.yaml": + values["ingress.enabled"] = "true" + case "pdp.yaml": + values["podDisruptionBudget.enabled"] = "true" + } + options := s.Options() + for k, v := range values { + options.SetValues[fmt.Sprintf("%s.%s", app, k)] = v + } + options.SetValues[fmt.Sprintf("%s.enabled", app)] = strconv.FormatBool(enabled) + output, err := helm.RenderTemplateE(t, options, s.ChartPath, s.Release, []string{fmt.Sprintf("charts/%s/templates/%s", app, templateName)}) + if enabled { + require.NoError(t, err) + require.NotEmpty(t, output) + } else { + require.Error(t, err) + } + }) + } + } + } +} diff --git a/test/helm/suite/console_test.go b/test/helm/suite/console_test.go new file mode 100644 index 0000000..216cb84 --- /dev/null +++ b/test/helm/suite/console_test.go @@ -0,0 +1,95 @@ +package suite_test + +import ( + "fmt" + "os" + "path/filepath" + "strconv" + "strings" + "testing" + + "github.com/google/uuid" + "github.com/gruntwork-io/terratest/modules/helm" + "github.com/gruntwork-io/terratest/modules/random" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + v1 "k8s.io/api/apps/v1" + coreV1 "k8s.io/api/core/v1" +) + +type TemplateConsole struct { + Base +} + +func TestConsole(t *testing.T) { + if chartDirFromEnv := os.Getenv("CHART_DIR"); chartDirFromEnv != "" { + chartDir = chartDirFromEnv + } + + chartPath, err := filepath.Abs(filepath.Join(chartDir, "console")) + require.NoError(t, err) + + chartTest := &TemplateConsole{ + Base: Base{ + ChartPath: chartPath, + ChartDir: chartDir, + Release: releaseName, + Name: "console", + Namespace: "ccsm-helm-" + strings.ToLower(random.UniqueId()), + setValues: map[string]string{}, + }, + } + + suite.Run(t, chartTest) +} + +func (s *TemplateConsole) TestCookieEncryptionKey() { + t := s.T() + t.Parallel() + + templateName := "deployment.yaml" + + for _, withEncryptioNKey := range []bool{true, false} { + t.Run(fmt.Sprintf("%s-with-encryption-secret-%s", t.Name(), strconv.FormatBool(withEncryptioNKey)), func(t *testing.T) { + t.Parallel() + var values map[string]string + if withEncryptioNKey { + values = map[string]string{ + "global.platform.cookie.existingSecret": uuid.NewString(), + "global.platform.cookie.secretKeys.encryptionKey": uuid.NewString(), + } + } else { + values = map[string]string{ + "global.platform.cookie.encryptionKey": uuid.NewString(), + } + } + options := s.Options( + WithValues(values), + ) + output, err := helm.RenderTemplateE(t, options, s.ChartPath, s.Release, []string{fmt.Sprintf("templates/%s", templateName)}) + require.NoError(t, err) + r := v1.Deployment{} + helm.UnmarshalK8SYaml(t, output, &r) + if !withEncryptioNKey { + require.Contains(t, r.Spec.Template.Spec.Containers[0].Env, coreV1.EnvVar{ + Name: "ENCRYPTION_KEY", + Value: values["global.platform.cookie.encryptionKey"], + }) + + return + } + require.Contains(t, r.Spec.Template.Spec.Containers[0].Env, coreV1.EnvVar{ + Name: "ENCRYPTION_KEY", + ValueFrom: &coreV1.EnvVarSource{ + SecretKeyRef: &coreV1.SecretKeySelector{ + LocalObjectReference: coreV1.LocalObjectReference{ + Name: values["global.platform.cookie.existingSecret"], + }, + Key: values["global.platform.cookie.secretKeys.encryptionKey"], + }, + }, + }) + }) + } + +} diff --git a/test/helm/suite/global_test.go b/test/helm/suite/global_test.go new file mode 100644 index 0000000..46bc478 --- /dev/null +++ b/test/helm/suite/global_test.go @@ -0,0 +1,379 @@ +package suite_test + +import ( + "fmt" + "os" + "path/filepath" + "strconv" + "strings" + "testing" + + "github.com/gruntwork-io/terratest/modules/helm" + "github.com/gruntwork-io/terratest/modules/random" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + v1 "k8s.io/api/apps/v1" + coreV1 "k8s.io/api/core/v1" + networkingV1 "k8s.io/api/networking/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" +) + +// https://medium.com/@zelldon91/advanced-test-practices-for-helm-charts-587caeeb4cb +type TemplateGoldenTest struct { + Base +} + +func TestCharts(t *testing.T) { + // could be ldflags, but does not seem to work with go test ./... -ldflags "-X tests/helm/suite_test.ChartPath=../../charts/cloudprem" + if chartDirFromEnv := os.Getenv("CHART_DIR"); chartDirFromEnv != "" { + chartDir = chartDirFromEnv + } + + for _, chart := range charts { + chartPath, err := filepath.Abs(chartDir + chart) + require.NoError(t, err) + + chartTest := &TemplateChart{ + TemplateGoldenTest{ + Base{ + ChartPath: chartPath, + Name: chart, + Release: releaseName, + Namespace: "ccsm-helm-" + strings.ToLower(random.UniqueId()), + setValues: map[string]string{}, + }, + }, + } + + t.Run(chart, func(t *testing.T) { + t.Parallel() + require.NoError(t, chartTest.setupSuite(t)) + suite.Run(t, chartTest) + }) + } +} + +func (s *TemplateChart) TestLabelAndNaming() { + t := s.T() + t.Parallel() + + appDir := fmt.Sprint(s.ChartPath + "/templates") + dirEntries, err := os.ReadDir(appDir) + require.NoError(t, err) + templateNames := []string{} + for _, dirEntry := range dirEntries { + if strings.HasSuffix(dirEntry.Name(), ".yaml") { + templateNames = append(templateNames, dirEntry.Name()) + } + } + + for _, templateName := range templateNames { + t.Run(fmt.Sprintf("labels-%s-%s", s.Name, templateName), s.testLabels(templateName)) + t.Run(fmt.Sprintf("names-%s-%s", s.Name, templateName), s.testNames(templateName)) + } + +} + +func (s *TemplateChart) testLabels(templateName string) func(t *testing.T) { + return func(t *testing.T) { + values := make(map[string]string, 0) + switch templateName { + case "hpa.yaml": + values["autoscaling.enabled"] = "true" + case "ingress.yaml": + values["ingress.enabled"] = "true" + case "pdp.yaml": + values["podDisruptionBudget.enabled"] = "true" + } + options := s.Options() + for k, v := range values { + options.SetValues[k] = v + } + output, err := helm.RenderTemplateE(t, options, s.ChartPath, s.Release, []string{fmt.Sprintf("templates/%s", templateName)}) + require.NoError(t, err) + + r := unstructured.Unstructured{} + helm.UnmarshalK8SYaml(t, output, &r) + + labels := r.GetLabels() + require.NotNil(t, labels) + require.Contains(t, labels, "app.kubernetes.io/version") + + require.Contains(t, labels, "app.kubernetes.io/name") + require.Equal(t, labels["app.kubernetes.io/name"], s.Name) + + require.Contains(t, labels, "app.kubernetes.io/instance") + require.Equal(t, labels["app.kubernetes.io/instance"], s.Release) + + require.Contains(t, labels, "app.kubernetes.io/managed-by") + require.Equal(t, labels["app.kubernetes.io/managed-by"], "Helm") + } +} + +func (s *TemplateChart) testNames(templateName string) func(t *testing.T) { + return func(t *testing.T) { + t.Parallel() + values := make(map[string]string, 0) + switch templateName { + case "hpa.yaml": + values["autoscaling.enabled"] = "true" + case "ingress.yaml": + values["ingress.enabled"] = "true" + case "pdp.yaml": + values["podDisruptionBudget.enabled"] = "true" + } + options := s.Options() + for k, v := range values { + options.SetValues[k] = v + } + output, err := helm.RenderTemplateE(t, options, s.ChartPath, s.Release, []string{fmt.Sprintf("templates/%s", templateName)}) + require.NoError(t, err) + r := unstructured.Unstructured{} + helm.UnmarshalK8SYaml(t, output, &r) + switch r.GetKind() { + case "Job": + require.Contains(t, r.GetName(), s.Release) + default: + require.Equal(t, r.GetName(), fmt.Sprintf("%s-%s", s.Release, s.Name)) + } + } +} + +func (s *TemplateChart) TestService() { + t := s.T() + t.Parallel() + + templateName := "service" + + t.Run(fmt.Sprintf("service-%s-%s", s.Name, templateName), func(t *testing.T) { + t.Parallel() + output, err := helm.RenderTemplateE(t, s.Options(), s.ChartPath, s.Release, []string{fmt.Sprintf("templates/%s.yaml", templateName)}) + require.NoError(t, err) + r := coreV1.Service{} + helm.UnmarshalK8SYaml(t, output, &r) + + require.GreaterOrEqual(t, len(r.Spec.Ports), 1) + }) + +} + +func (s *TemplateChart) TestServiceAccount() { + t := s.T() + t.Parallel() + + templateName := "serviceaccount" + values := map[string]string{ + "serviceAccount.annotations.test\\.annotations": "test", + } + t.Run(fmt.Sprintf("serviceaccount-%s-%s", s.Name, templateName), func(t *testing.T) { + t.Parallel() + for _, withServiceAccount := range []bool{true, false} { + t.Run(fmt.Sprintf("serviceaccount-%s-%s-create-%s", s.Name, templateName, strconv.FormatBool(withServiceAccount)), func(t *testing.T) { + options := s.Options() + options.SetValues["serviceAccount.create"] = strconv.FormatBool(withServiceAccount) + for k, v := range values { + options.SetValues[k] = v + } + + output, err := helm.RenderTemplateE(t, options, s.ChartPath, s.Release, []string{fmt.Sprintf("templates/%s.yaml", templateName)}) + if !withServiceAccount { + require.Error(t, err) + return + } + require.NoError(t, err) + r := coreV1.ServiceAccount{} + helm.UnmarshalK8SYaml(t, output, &r) + + require.Equal(t, r.Name, fmt.Sprintf("%s-%s", s.Release, s.Name)) + require.Len(t, r.Annotations, 1) + require.Contains(t, r.Annotations, "test.annotations") + require.Equal(t, r.Annotations["test.annotations"], "test") + }) + } + }) + +} + +func (s *TemplateChart) TestDeploymentServiceAccount() { + t := s.T() + t.Parallel() + + templateName := "deployment" + + t.Run(fmt.Sprintf("deployment-%s-%s", s.Name, templateName), func(t *testing.T) { + t.Parallel() + output, err := helm.RenderTemplateE(t, s.Options(), s.ChartPath, s.Release, []string{fmt.Sprintf("templates/%s.yaml", templateName)}) + require.NoError(t, err) + r := v1.Deployment{} + helm.UnmarshalK8SYaml(t, output, &r) + + require.NotNil(t, r.Spec.Template.Spec.ServiceAccountName) + require.Equal(t, r.Spec.Template.Spec.ServiceAccountName, fmt.Sprintf("%s-%s", s.Release, s.Name)) + }) + +} +func (s *TemplateChart) TestIngress() { + t := s.T() + t.Parallel() + + templateName := "ingress" + serviceHost := "example.com" + options := s.Options() + options.SetValues["global.serviceHost"] = serviceHost + options.SetValues["ingress.enabled"] = "true" + + t.Run(fmt.Sprintf("ingress-%s-%s", s.Name, templateName), func(t *testing.T) { + t.Parallel() + + output, err := helm.RenderTemplateE(t, options, s.ChartPath, s.Release, []string{fmt.Sprintf("templates/%s.yaml", templateName)}) + require.NoError(t, err) + r := networkingV1.Ingress{} + helm.UnmarshalK8SYaml(t, output, &r) + + require.Len(t, r.Spec.Rules, 1) + require.Len(t, r.Spec.Rules[0].HTTP.Paths, 1) + require.NotNil(t, r.Spec.Rules[0].HTTP.Paths[0].Path) + require.Equal(t, *r.Spec.Rules[0].HTTP.Paths[0].PathType, networkingV1.PathTypePrefix) + require.Equal(t, r.Spec.Rules[0].Host, fmt.Sprintf("%s.%s", s.Name, serviceHost)) + }) + +} + +func (s *TemplateGoldenTest) TestLivenessRediness() { + t := s.T() + t.Parallel() + + options := s.Options() + templateName := "deployment" + t.Run(fmt.Sprintf("liveness-readiness-%s-%s", s.Name, templateName), func(t *testing.T) { + output, err := helm.RenderTemplateE(t, options, s.ChartPath, s.Release, []string{fmt.Sprintf("templates/%s.yaml", templateName)}) + require.NoError(t, err) + r := v1.Deployment{} + helm.UnmarshalK8SYaml(t, output, &r) + + require.Len(t, r.Spec.Template.Spec.Containers, 1) + require.NotNil(t, r.Spec.Template.Spec.Containers[0].LivenessProbe) + require.NotNil(t, r.Spec.Template.Spec.Containers[0].ReadinessProbe) + + path := "/_healthcheck" + if s.Name == "console" || s.Name == "portal" { + path = "/_info" + } + require.Equal(t, r.Spec.Template.Spec.Containers[0].LivenessProbe.HTTPGet.Path, path) + require.Equal(t, r.Spec.Template.Spec.Containers[0].ReadinessProbe.HTTPGet.Path, path) + }) + +} + +func (s *TemplateGoldenTest) TestResources() { + t := s.T() + t.Parallel() + templateName := "deployment" + + values := map[string]string{ + "resources.limits.cpu": "100m", + "resources.limits.memory": "128Mi", + "resources.requests.cpu": "100m", + "resources.requests.memory": "128Mi", + } + + for _, withResource := range []bool{true, false} { + t.Run(fmt.Sprintf("resources-%s-%s-with-resources-%s", s.Name, templateName, strconv.FormatBool(withResource)), func(t *testing.T) { + options := s.Options() + if withResource { + for k, v := range values { + options.SetValues[k] = v + } + } + output, err := helm.RenderTemplateE(t, options, s.ChartPath, s.Release, []string{fmt.Sprintf("templates/%s.yaml", templateName)}) + require.NoError(t, err) + r := v1.Deployment{} + helm.UnmarshalK8SYaml(t, output, &r) + + require.Len(t, r.Spec.Template.Spec.Containers, 1) + require.NotNil(t, r.Spec.Template.Spec.Containers[0].Resources) + + if withResource { + require.Equal(t, r.Spec.Template.Spec.Containers[0].Resources.Limits.Cpu().String(), values["resources.limits.cpu"]) + require.Equal(t, r.Spec.Template.Spec.Containers[0].Resources.Requests.Cpu().String(), values["resources.requests.cpu"]) + require.Equal(t, r.Spec.Template.Spec.Containers[0].Resources.Limits.Memory().String(), values["resources.limits.memory"]) + require.Equal(t, r.Spec.Template.Spec.Containers[0].Resources.Requests.Memory().String(), values["resources.requests.memory"]) + } + }) + } + +} + +func (s *TemplateGoldenTest) TestNodeSelector() { + t := s.T() + t.Parallel() + + templateName := "deployment" + + values := map[string]string{ + "nodeSelector.kubernetes\\.io/hostname": "minikube", + } + + for _, withNodeSelector := range []bool{true, false} { + t.Run(fmt.Sprintf("node-selector-%s-%s-with-node-selector-%s", s.Name, templateName, strconv.FormatBool(withNodeSelector)), func(t *testing.T) { + options := s.Options() + if withNodeSelector { + for k, v := range values { + options.SetValues[k] = v + } + } + output, err := helm.RenderTemplateE(t, options, s.ChartPath, s.Release, []string{fmt.Sprintf("templates/%s.yaml", templateName)}) + require.NoError(t, err) + r := v1.Deployment{} + helm.UnmarshalK8SYaml(t, output, &r) + + if withNodeSelector { + require.Len(t, r.Spec.Template.Spec.NodeSelector, len(values)) + require.Contains(t, r.Spec.Template.Spec.NodeSelector, "kubernetes.io/hostname") + require.Equal(t, r.Spec.Template.Spec.NodeSelector["kubernetes.io/hostname"], values["nodeSelector.kubernetes\\.io/hostname"]) + } else { + require.Len(t, r.Spec.Template.Spec.NodeSelector, 0) + } + + }) + } + +} + +func (s *TemplateGoldenTest) TestTolerations() { + t := s.T() + t.Parallel() + templateName := "deployment" + + values := map[string]string{ + "tolerations[0].key": "key", + "tolerations[0].operator": "Exists", + "tolerations[0].effect": "NoSchedule", + } + + for _, withTolerations := range []bool{true, false} { + t.Run(fmt.Sprintf("tolerations-%s-%s-with-tolerations-%s", s.Name, templateName, strconv.FormatBool(withTolerations)), func(t *testing.T) { + options := s.Options() + if withTolerations { + for k, v := range values { + options.SetValues[k] = v + } + } + output, err := helm.RenderTemplateE(t, options, s.ChartPath, s.Release, []string{fmt.Sprintf("templates/%s.yaml", templateName)}) + require.NoError(t, err) + r := v1.Deployment{} + helm.UnmarshalK8SYaml(t, output, &r) + + if withTolerations { + require.Len(t, r.Spec.Template.Spec.Tolerations, 1) + require.Equal(t, r.Spec.Template.Spec.Tolerations[0].Key, values["tolerations[0].key"]) + require.Equal(t, string(r.Spec.Template.Spec.Tolerations[0].Operator), values["tolerations[0].operator"]) + require.Equal(t, string(r.Spec.Template.Spec.Tolerations[0].Effect), values["tolerations[0].effect"]) + } else { + require.Len(t, r.Spec.Template.Spec.Tolerations, 0) + } + + }) + } + +} diff --git a/test/helm/suite/main_test.go b/test/helm/suite/main_test.go new file mode 100644 index 0000000..bacd641 --- /dev/null +++ b/test/helm/suite/main_test.go @@ -0,0 +1,84 @@ +package suite_test + +import ( + "os" + "testing" + + "github.com/gruntwork-io/go-commons/errors" + "github.com/gruntwork-io/terratest/modules/helm" + "github.com/gruntwork-io/terratest/modules/k8s" + "github.com/gruntwork-io/terratest/modules/logger" + "github.com/stretchr/testify/suite" +) + +var ( + releaseName = "helm-basic" + chartDir = "../../../charts/" + mainChart = "cloudprem" + plaformCharts = []string{"portal", "console", "membership"} + charts = []string{"portal", "console", "stargate"} +) + +type Base struct { + suite.Suite + Name string + ChartDir string + ChartPath string + Release string + Namespace string + setValues map[string]string +} + +type helmOpt func(*helm.Options) *helm.Options + +func WithValues(values map[string]string) helmOpt { + return func(opts *helm.Options) *helm.Options { + for k, v := range values { + opts.SetValues[k] = v + } + return opts + } +} + +func WithDefaultValues(base *Base) helmOpt { + return func(opts *helm.Options) *helm.Options { + WithValues(base.setValues)(opts) + return opts + } +} + +func (s *Base) setupSuite(t *testing.T) error { + if os.Getenv("NO_UPDATE") == "true" { + return nil + } + if _, err := helm.RunHelmCommandAndGetStdOutE(t, s.Options(), "dependency", "update", s.ChartPath); err != nil { + return errors.WithStackTrace(err) + } + return nil +} + +func (s *Base) Options(helmOpts ...helmOpt) *helm.Options { + // Find a way to read the Chart.yaml to add the repository + opts := &helm.Options{ + KubectlOptions: k8s.NewKubectlOptions("", "", s.Namespace), + // Need to add all the repositories + // BuildDependencies: true, + SetValues: map[string]string{}, + } + WithDefaultValues(s)(opts) + + for _, opt := range helmOpts { + opts = opt(opts) + } + + opts.Logger = logger.Discard + if testing.Verbose() { + opts.Logger = logger.TestingT + } + + return opts +} + +type TemplateChart struct { + TemplateGoldenTest +} diff --git a/test/helm/suite/membership_test.go b/test/helm/suite/membership_test.go new file mode 100644 index 0000000..c63091d --- /dev/null +++ b/test/helm/suite/membership_test.go @@ -0,0 +1,171 @@ +package suite_test + +import ( + "fmt" + "os" + "path/filepath" + "strconv" + "strings" + "testing" + + "github.com/gruntwork-io/terratest/modules/helm" + "github.com/gruntwork-io/terratest/modules/random" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + v1 "k8s.io/api/apps/v1" + coreV1 "k8s.io/api/core/v1" +) + +type TemplateMembership struct { + TemplateChart +} + +func TestMembership(t *testing.T) { + // could be ldflags, but does not seem to work with go test ./... -ldflags "-X tests/helm/suite_test.ChartPath=../../charts/cloudprem" + if chartDirFromEnv := os.Getenv("CHART_DIR"); chartDirFromEnv != "" { + chartDir = chartDirFromEnv + } + + chartPath, err := filepath.Abs(filepath.Join(chartDir + "membership")) + require.NoError(t, err) + + chartTest := &TemplateMembership{ + TemplateChart{ + TemplateGoldenTest{ + Base{ + ChartPath: chartPath, + Release: releaseName, + Name: "membership", + Namespace: "ccsm-helm-" + strings.ToLower(random.UniqueId()), + setValues: map[string]string{}, + }, + }, + }, + } + + chartTest.setupSuite(t) + suite.Run(t, chartTest) +} + +func (s *TemplateMembership) TestManagedStack() { + t := s.T() + t.Parallel() + templateNames := []string{"service", "deployment"} + + for _, managedStack := range []bool{true, false} { + options := s.Options() + options.SetValues["feature.managedStacks"] = strconv.FormatBool(managedStack) + + for _, templateName := range templateNames { + if managedStack { + switch templateName { + case "service": + t.Run(fmt.Sprintf("%s-managed", templateName), func(t *testing.T) { + output, err := helm.RenderTemplateE(t, options, s.ChartPath, s.Release, []string{fmt.Sprintf("templates/%s.yaml", templateName)}) + require.NoError(t, err) + var r coreV1.Service + helm.UnmarshalK8SYaml(t, output, &r) + require.Len(t, r.Spec.Ports, 1) + require.Equal(t, r.Spec.Ports[0].Name, "http") + }) + case "deployment": + t.Run(fmt.Sprintf("%s-managed", templateName), func(t *testing.T) { + output, err := helm.RenderTemplateE(t, options, s.ChartPath, s.Release, []string{fmt.Sprintf("templates/%s.yaml", templateName)}) + require.NoError(t, err) + var r v1.Deployment + helm.UnmarshalK8SYaml(t, output, &r) + require.Len(t, r.Spec.Template.Spec.Containers, 1) + require.Len(t, r.Spec.Template.Spec.Containers[0].Ports, 1) + require.Equal(t, r.Spec.Template.Spec.Containers[0].Ports[0].Name, "http") + }) + default: + t.Skipf("Skipping test: %s", templateName) + } + } else { + switch templateName { + case "service": + t.Run(fmt.Sprintf("%s-not-managed", templateName), func(t *testing.T) { + output, err := helm.RenderTemplateE(t, options, s.ChartPath, s.Release, []string{fmt.Sprintf("templates/%s.yaml", templateName)}) + require.NoError(t, err) + var r coreV1.Service + helm.UnmarshalK8SYaml(t, output, &r) + require.Len(t, r.Spec.Ports, 2) + require.Equal(t, r.Spec.Ports[0].Name, "http") + require.Equal(t, r.Spec.Ports[1].Name, "grpc") + }) + case "deployment": + t.Run(fmt.Sprintf("%s-not-managed", templateName), func(t *testing.T) { + output, err := helm.RenderTemplateE(t, options, s.ChartPath, s.Release, []string{fmt.Sprintf("templates/%s.yaml", templateName)}) + require.NoError(t, err) + var r v1.Deployment + helm.UnmarshalK8SYaml(t, output, &r) + require.Len(t, r.Spec.Template.Spec.Containers, 1) + require.Len(t, r.Spec.Template.Spec.Containers[0].Ports, 2) + require.Equal(t, r.Spec.Template.Spec.Containers[0].Ports[0].Name, "http") + require.Equal(t, r.Spec.Template.Spec.Containers[0].Ports[1].Name, "grpc") + }) + default: + t.Skipf("Skipping test: %s", templateName) + } + } + } + } + +} + +func (s *TemplateMembership) TestDeploymentConfigMapping() { + t := s.T() + t.Parallel() + templateNames := []string{"configmap", "deployment"} + options := s.Options() + for _, templateName := range templateNames { + t.Run(fmt.Sprintf("membership-%s-config-mapping", templateName), func(t *testing.T) { + output, err := helm.RenderTemplateE(t, options, s.ChartPath, s.Release, []string{fmt.Sprintf("templates/%s.yaml", templateName)}) + require.NoError(t, err) + switch templateName { + case "configmap": + r := coreV1.ConfigMap{} + helm.UnmarshalK8SYaml(t, output, &r) + require.Equal(t, r.Name, fmt.Sprintf("%s-%s", s.Release, s.Name)) + case "deployment": + r := v1.Deployment{} + helm.UnmarshalK8SYaml(t, output, &r) + + require.Len(t, r.Spec.Template.Spec.Volumes, 1) + require.Equal(t, r.Spec.Template.Spec.Volumes[0].ConfigMap.Name, fmt.Sprintf("%s-%s", s.Release, s.Name)) + } + }) + } +} + +func (s *TemplateMembership) TestConsoleBaseUrl() { + t := s.T() + t.Parallel() + for _, withConsole := range []bool{false, true} { + t.Run(fmt.Sprintf("%s-with-%s", t.Name(), strconv.FormatBool(withConsole)), func(t *testing.T) { + t.Parallel() + var values map[string]string + if withConsole { + values = map[string]string{ + "global.platform.console.host": "console.example.com", + "global.platform.console.scheme": "https", + } + } + + options := s.Options( + WithValues(values), + ) + output, err := helm.RenderTemplateE(t, options, s.ChartPath, s.Release, []string{"templates/deployment.yaml"}) + require.NoError(t, err) + r := v1.Deployment{} + helm.UnmarshalK8SYaml(t, output, &r) + if withConsole { + require.Contains(t, r.Spec.Template.Spec.Containers[0].Env, coreV1.EnvVar{ + Name: "CONSOLE_PUBLIC_BASEURL", + Value: fmt.Sprintf("%s://%s", values["global.platform.console.scheme"], values["global.platform.console.host"]), + }) + return + } + }) + } +} diff --git a/tools/readme/Earthfile b/tools/readme/Earthfile new file mode 100644 index 0000000..3fdc505 --- /dev/null +++ b/tools/readme/Earthfile @@ -0,0 +1,12 @@ +VERSION --wildcard-builds --wildcard-copy 0.8 + +IMPORT github.com/formancehq/earthly:tags/v0.16.3 AS core + +sources: + FROM core+base-image + RUN apk add go + WORKDIR /src + COPY --dir go.* *.go assets . + SAVE ARTIFACT /src + + diff --git a/tools/readme/assets/readme.tpl b/tools/readme/assets/readme.tpl new file mode 100644 index 0000000..2940750 --- /dev/null +++ b/tools/readme/assets/readme.tpl @@ -0,0 +1,18 @@ +# Formance Helm charts + +## How to use Helm charts + +| Readme | Chart Version | App Version | Description | Hub | +|--------|---------------|-------------|-------------|-----| +{{- range .Charts }} +| [{{ .Name | title }}](./charts/{{ .Name }}/README.md) | {{ .Version }} |{{ .AppVersion }} | {{ .Description }} | [![Artifact HUB](https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/{{ .Name }})](https://artifacthub.io/packages/search?repo={{ .Name }}) | +{{- end }} + +## How to contribute + +Please refer to the [CONTRIBUTING.md](./CONTRIBUTING.md) file for information on how to contribute to this project. + +## License + +This project is licensed under the MIT License - see the [LICENSE](./LICENSE) file for details. + diff --git a/tools/readme/go.mod b/tools/readme/go.mod new file mode 100644 index 0000000..c22eeab --- /dev/null +++ b/tools/readme/go.mod @@ -0,0 +1,30 @@ +module github.com/formancehq/helm/readme + +go 1.22 + +require ( + github.com/Masterminds/sprig/v3 v3.3.0 + github.com/goccy/go-yaml v1.12.0 + github.com/spf13/cobra v1.8.1 +) + +require ( + dario.cat/mergo v1.0.1 // indirect + github.com/Masterminds/goutils v1.1.1 // indirect + github.com/Masterminds/semver/v3 v3.3.0 // indirect + github.com/fatih/color v1.10.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/huandu/xstrings v1.5.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/mattn/go-colorable v0.1.8 // indirect + github.com/mattn/go-isatty v0.0.12 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/shopspring/decimal v1.4.0 // indirect + github.com/spf13/cast v1.7.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + golang.org/x/crypto v0.26.0 // indirect + golang.org/x/sys v0.23.0 // indirect + golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect +) diff --git a/tools/readme/go.sum b/tools/readme/go.sum new file mode 100644 index 0000000..28f1e38 --- /dev/null +++ b/tools/readme/go.sum @@ -0,0 +1,73 @@ +dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= +dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= +github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0= +github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs= +github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg= +github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= +github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= +github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= +github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= +github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= +github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= +github.com/goccy/go-yaml v1.12.0 h1:/1WHjnMsI1dlIBQutrvSMGZRQufVO3asrHfTwfACoPM= +github.com/goccy/go-yaml v1.12.0/go.mod h1:wKnAMd44+9JAAnGQpWVEgBzGt3YuTaQ4uXoHvE4m7WU= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= +github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= +github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= +github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= +github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= +github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= +github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= +github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= +github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= +golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/tools/readme/main.go b/tools/readme/main.go new file mode 100644 index 0000000..e3a6373 --- /dev/null +++ b/tools/readme/main.go @@ -0,0 +1,134 @@ +package main + +import ( + "context" + "fmt" + "html/template" + "os" + + "github.com/Masterminds/sprig/v3" + "github.com/goccy/go-yaml" + "github.com/spf13/cobra" +) + +var ( + chartDirFlag string = "chart-dir" + assetsPatternFlag string = "assets-dir" + templatefileNameFlag string = "template-file" +) + +type chart struct { + Name string `yaml:"name"` + Description string `yaml:"description"` + AppVersion string `yaml:"appVersion"` + Version string `yaml:"version"` + KubeVersion string `yaml:"kubeVersion"` + Sources []string `yaml:"sources"` + Dependencies []struct { + Name string `yaml:"name"` + Version string `yaml:"version"` + Repository string `yaml:"repository"` + } +} + +type values struct { + Charts []chart +} + +func listCharts(chartDir string) ([]chart, error) { + dir, err := os.ReadDir(chartDir) + if err != nil { + return nil, err + } + + var charts []chart + for _, entry := range dir { + if entry.IsDir() { + var ( + hasChartYaml bool + ct chart + ) + + if _, err := os.Stat(fmt.Sprintf("%s/%s/Chart.yaml", chartDir, entry.Name())); err == nil { + hasChartYaml = true + + b, err := os.ReadFile(fmt.Sprintf("%s/%s/Chart.yaml", chartDir, entry.Name())) + if err != nil { + return nil, err + } + + if err := yaml.Unmarshal(b, &ct); err != nil { + return nil, err + } + + } + if !hasChartYaml { + continue + } + + charts = append(charts, ct) + } + } + + return charts, nil +} + +func runE(cmd *cobra.Command, args []string) error { + + chartDir := cmd.Flag(chartDirFlag).Value.String() + if chartDir == "" { + return fmt.Errorf("chart-dir is required") + } + + assetPattern := cmd.Flag(assetsPatternFlag).Value.String() + if assetPattern == "" { + return fmt.Errorf("assets-pattern is required") + } + + fileName := cmd.Flag(templatefileNameFlag).Value.String() + if fileName == "" { + return fmt.Errorf("template-file-name is required") + } + + charts, err := listCharts(chartDir) + if err != nil { + return err + } + tmpl, err := template. + New(fileName). + Funcs(sprig.FuncMap()). + ParseGlob(assetPattern) + if err != nil { + return err + } + return tmpl.Execute(cmd.OutOrStdout(), &values{ + Charts: charts, + }) +} + +func NewRootCommand() *cobra.Command { + root := &cobra.Command{ + Use: "readme", + Long: "readme is a tool to generate README.md file", + RunE: runE, + } + + root.Flags().String(chartDirFlag, "../../charts", "chart directory") + root.Flags().String(assetsPatternFlag, "./assets/*.tpl", "assets pattern") + root.Flags().String(templatefileNameFlag, "readme.tpl", "template file name") + + return root +} + +func main() { + defer func() { + if r := recover(); r != nil { + fmt.Println("Recovered in f", r) + os.Exit(1) + } + }() + + if err := NewRootCommand().ExecuteContext(context.TODO()); err != nil { + panic(err) + } +}