From 1a9f8a6c638e1518f226f8faf130eebda0d3fb32 Mon Sep 17 00:00:00 2001 From: Nacho Gonzalez-Garilleti Date: Fri, 12 May 2023 16:19:22 +0200 Subject: [PATCH] Adding files for Helm Chart 4.51.0 --- .azuredevops/pull_request_template.md | 18 + .azuredevops/release-pipeline.yaml | 248 +++++++ .gitignore | 39 ++ .helmignore | 23 + Chart.lock | 6 + Chart.yaml | 13 + README.md | 194 +++++- charts/redis-15.7.6.tgz | Bin 0 -> 80315 bytes scripts/dbinit-generate-atlas.sh | 45 ++ scripts/dbinit-generate.sh | 33 + templates/NOTES.txt | 14 + templates/_helpers.tpl | 234 +++++++ templates/backend/backend-configmap.yaml | 14 + templates/backend/backend-deployment.yaml | 97 +++ templates/backend/backend-ingress.yaml | 50 ++ templates/backend/backend-service.yaml | 22 + .../dbinit-generate/dbinit-generate.yaml | 167 +++++ templates/frontend/frontend-configmap.yaml | 22 + templates/frontend/frontend-deployment.yaml | 71 ++ templates/frontend/frontend-ingress.yaml | 52 ++ templates/frontend/frontend-service.yaml | 24 + templates/pod-monitors/backend-monitor.yaml | 17 + templates/pod-monitors/frontend-monitor.yaml | 17 + .../secrets/agent-assist-backend-api-key.yaml | 11 + .../secrets/agent-assist-redis-password.yaml | 16 + templates/secrets/cognigy-agent-assist.yaml | 17 + ...igy-service-endpoint-api-access-token.yaml | 11 + ...igy-service-handover-api-access-token.yaml | 11 + .../test-api-bridge-configmap.yaml | 17 + .../test-api-bridge-deployment.yaml | 55 ++ .../test-api-bridge-ingress.yaml | 52 ++ .../test-api-bridge-service.yaml | 24 + values.schema.json | 629 ++++++++++++++++++ values.yaml | 264 ++++++++ values_dev.yaml | 155 +++++ values_prod.yaml | 124 ++++ 36 files changed, 2805 insertions(+), 1 deletion(-) create mode 100644 .azuredevops/pull_request_template.md create mode 100644 .azuredevops/release-pipeline.yaml create mode 100644 .gitignore create mode 100644 .helmignore create mode 100644 Chart.lock create mode 100644 Chart.yaml create mode 100644 charts/redis-15.7.6.tgz create mode 100755 scripts/dbinit-generate-atlas.sh create mode 100755 scripts/dbinit-generate.sh create mode 100644 templates/NOTES.txt create mode 100644 templates/_helpers.tpl create mode 100644 templates/backend/backend-configmap.yaml create mode 100644 templates/backend/backend-deployment.yaml create mode 100644 templates/backend/backend-ingress.yaml create mode 100644 templates/backend/backend-service.yaml create mode 100644 templates/dbinit-generate/dbinit-generate.yaml create mode 100644 templates/frontend/frontend-configmap.yaml create mode 100644 templates/frontend/frontend-deployment.yaml create mode 100644 templates/frontend/frontend-ingress.yaml create mode 100644 templates/frontend/frontend-service.yaml create mode 100644 templates/pod-monitors/backend-monitor.yaml create mode 100644 templates/pod-monitors/frontend-monitor.yaml create mode 100644 templates/secrets/agent-assist-backend-api-key.yaml create mode 100644 templates/secrets/agent-assist-redis-password.yaml create mode 100644 templates/secrets/cognigy-agent-assist.yaml create mode 100644 templates/secrets/cognigy-service-endpoint-api-access-token.yaml create mode 100644 templates/secrets/cognigy-service-handover-api-access-token.yaml create mode 100644 templates/test-api-bridge/test-api-bridge-configmap.yaml create mode 100644 templates/test-api-bridge/test-api-bridge-deployment.yaml create mode 100644 templates/test-api-bridge/test-api-bridge-ingress.yaml create mode 100644 templates/test-api-bridge/test-api-bridge-service.yaml create mode 100644 values.schema.json create mode 100644 values.yaml create mode 100644 values_dev.yaml create mode 100644 values_prod.yaml diff --git a/.azuredevops/pull_request_template.md b/.azuredevops/pull_request_template.md new file mode 100644 index 0000000..7dadf31 --- /dev/null +++ b/.azuredevops/pull_request_template.md @@ -0,0 +1,18 @@ +# Changelog: +In order to help the product-team to compose better changelogs for our customers, please take your time and try to compose a changelog item. Try to describe your change from a customer perspective. Thanks! + +- [ ] Improved by ... +- [ ] [internal] Improved by ... +- [ ] Fixed a bug where... +- [ ] [internal] Fixed a bug where ... +- [ ] Fixed security vulnerabilities in ... + +# How to test +Please describe the individual steps on how a peer can test your change. + +1. A +2. B +3. C + +# Additional considerations +- [ ] This PR might have performance implications \ No newline at end of file diff --git a/.azuredevops/release-pipeline.yaml b/.azuredevops/release-pipeline.yaml new file mode 100644 index 0000000..f2f211e --- /dev/null +++ b/.azuredevops/release-pipeline.yaml @@ -0,0 +1,248 @@ +trigger: + branches: + include: + - release/* + +resources: + repositories: + - repository: AgentAssistBackend + type: git + name: agent-assist-backend + - repository: AgentAssistFrontend + type: git + name: agent-assist-frontend + - repository: FluxFleetNonProd + type: git + name: Cognigy.AI/flux-fleet-non-prod + # - repository: GitHubLiveAgent + # type: github + # endpoint: github.com_mayrbenjamin92 + # name: Cognigy/cognigy-live-agent-helm-chart + +variables: + - group: acr-prod + - name: DOCKER_BUILDKIT + value: 1 + +name: agent-assist-app-cd-$(BuildId) + +pool: + vmImage: ubuntu-20.04 + +stages: + - stage: createOrUpdateRelease + displayName: Create or Update Release + jobs: + - job: setOrUpdateReleaseVersion + displayName: Set or Update Release Version + steps: + - checkout: self + persistCredentials: "true" + clean: "true" + + - bash: | + version="$(echo "$(Build.SourceBranch)" | sed "s/^.*release\/\(.*\)$/\1/")" + + echo "Setting as release version: $version" + + git config --global user.email "live-agent-ci@cognigy.com" + git config --global user.name "Live Agent CI" + + git fetch origin release/$version + + git checkout release/$version + + # set version with buildId to make Flux detect the change + versionWithBuildId="$version-$(Build.BuildId)" + chartFile="./Chart.yaml" + + echo "Setting as app version: $versionWithBuildId and version: $versionWithBuildId" + + yq e "(.version = \"$versionWithBuildId\") | (.appVersion = \"$versionWithBuildId\")" $chartFile > Chart-updated.yaml + diff -U0 -w -b --ignore-blank-lines $chartFile Chart-updated.yaml > Chart.diff + patch $chartFile < Chart.diff + rm Chart.diff Chart-updated.yaml + + git add . + git commit -m "[skip ci] Update Chart.yaml - appVersion: $versionWithBuildId | version: $versionWithBuildId" + + git -c http.extraheader="AUTHORIZATION: bearer $(System.AccessToken)" push -u origin HEAD + + echo "##vso[task.setvariable variable=releaseVersion;isOutput=true]$version" + name: setReleaseVersionAndUpdateChart + displayName: "Set Release Version And Update Chart.yaml" + + - job: createAgentAssistBackendReleaseBranch + displayName: Create agent-assist-backend Release Branch + dependsOn: setOrUpdateReleaseVersion + variables: + releaseVersion: $[ dependencies.setOrUpdateReleaseVersion.outputs['setReleaseVersionAndUpdateChart.releaseVersion'] ] + steps: + - checkout: AgentAssistBackend + persistCredentials: "true" + clean: "true" + + - bash: | + git config --global user.email "live-agent-ci@cognigy.com" + git config --global user.name "Live Agent CI" + + releaseBranch="release/$(releaseVersion)" + + git fetch --all + + git checkout "$releaseBranch" 2>/dev/null || (git checkout -b "$releaseBranch" && git push -u origin $releaseBranch) + displayName: "Create Release Branch" + + - job: createAgentAssistFrontendReleaseBranch + displayName: Create agent-assist-frontend Release Branch + dependsOn: setOrUpdateReleaseVersion + variables: + releaseVersion: $[ dependencies.setOrUpdateReleaseVersion.outputs['setReleaseVersionAndUpdateChart.releaseVersion'] ] + steps: + - checkout: AgentAssistFrontend + persistCredentials: "true" + clean: "true" + + - bash: | + git config --global user.email "live-agent-ci@cognigy.com" + git config --global user.name "Live Agent CI" + + releaseBranch="release/$(releaseVersion)" + + git fetch --all + + git checkout "$releaseBranch" 2>/dev/null || (git checkout -b "$releaseBranch" && git push -u origin $releaseBranch) + displayName: "Create Release Branch" + - stage: stagingDeployment + displayName: Staging Deployment + dependsOn: + - createOrUpdateRelease + variables: + releaseVersion: $[stageDependencies.createOrUpdateRelease.setOrUpdateReleaseVersion.outputs['setReleaseVersionAndUpdateChart.releaseVersion']] + jobs: + - job: updateFluxFleetNonProdStagingV4 + displayName: Update flux-fleet-non-prod staging-v4 + steps: + - checkout: FluxFleetNonProd + persistCredentials: "true" + clean: "true" + + - bash: | + git config --global user.email "live-agent-ci@cognigy.com" + git config --global user.name "Live Agent CI" + + git fetch --all + + git checkout master + + echo "release: $(releaseVersion)" + + agentAssistSyncPath="./aws/eu-central-1/staging-v4/agent-assist" + + yq e -i 'select(.kind == "GitRepository").spec.ref.branch |= "release/$(releaseVersion)"' $agentAssistSyncPath/sync.yaml + + git add . + + if [[ `git status --porcelain` ]]; then + commitMessage="Updated staging-v4 agent-assist to release $(releaseVersion)" + + echo "$commitMessage" + + git commit -m "$commitMessage" + + git -c http.extraheader="AUTHORIZATION: bearer $(System.AccessToken)" push + else + echo "staging-v4 agent-assist already references this release, no changes pushed" + fi + displayName: "Update release branch reference" + + - stage: publishRelease + displayName: Publish Release + dependsOn: + - createOrUpdateRelease + - stagingDeployment + variables: + releaseVersion: $[stageDependencies.createOrUpdateRelease.setOrUpdateReleaseVersion.outputs['setReleaseVersionAndUpdateChart.releaseVersion']] + jobs: + - deployment: performPublishRelease + displayName: Perform Publish Release + environment: production + variables: + ACRPath: helm + chartName: "agent-assist" + strategy: + runOnce: + deploy: + steps: + - checkout: self + persistCredentials: "true" + clean: "true" + + # - checkout: GitHubLiveAgent + # persistCredentials: "true" + # clean: "true" + + - task: HelmInstaller@0 + displayName: Install Helm 3.8.2 + inputs: + helmVersion: 3.8.2 + checkLatestHelmVersion: false + + - task: Docker@2 + displayName: Login into container registry + inputs: + command: "login" + containerRegistry: $(containerProductionRegistryReference) + + - bash: | + git config --global user.email "live-agent-ci@cognigy.com" + git config --global user.name "Live Agent CI" + + git fetch --all + + git checkout release/$(releaseVersion) && git pull + + chartFile="./Chart.yaml" + + echo "Setting as version: $(releaseVersion)" + + # Remove buildId from version as it is not necessary for Trial and App deployments + yq e '.version = "$(releaseVersion)"' $chartFile > Chart-updated.yaml + diff -U0 -w -b --ignore-blank-lines $chartFile Chart-updated.yaml > Chart.diff + patch $chartFile < Chart.diff + rm Chart.diff Chart-updated.yaml + + git add . + git commit -m "[skip ci] remove buildId from Chart.yaml version: $(releaseVersion)" + + # Create new release tag + git tag -a "v$(releaseVersion)" -m "version $(releaseVersion)" + + git -c http.extraheader="AUTHORIZATION: bearer $(System.AccessToken)" push --follow-tags -u origin HEAD + + helm version + helmChartVersion="$(yq e '.version' Chart.yaml)" + + echo "Helm Chart" $(chartName) "version:" $helmChartVersion + + helm package . + helm push ./$(chartName)-$helmChartVersion.tgz oci://$(containerProductionRegistry)/$(ACRPath) + + # COMMENTED OUT FOR NOW AS WE ARE NOT PUBLISHING THE HELM CHART TO GITHUB + # # Change to the GitHub repository for Cognigy Live Agent Helm charts + # cd ../../cognigy-live-agent-helm-chart + + # git checkout main + + # # Copy Helm chart package and add it to the root folder + # mv ../live-agent-app/cognigy-live-agent/$(chartName)-$helmChartVersion.tgz ./ + + # git add . + + # git commit -m "$(releaseVersion) Helm chart" + + # # Create new release tag + # git tag -a "v$(releaseVersion)" -m "$(releaseVersion) Helm chart" + + # git -c http.extraheader="AUTHORIZATION: bearer $(System.AccessToken)" push --follow-tags -u origin HEAD + displayName: "Create release tag and publish Helm chart" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0234be0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,39 @@ +*agent-assist*.tgz + +# OSX leaves these everywhere on SMB shares +._* + +# OSX trash +.DS_Store + +# Files generated by JetBrains IDEs, e.g. IntelliJ IDEA +.idea/ +*.iml + +# Vscode files +.vscode + +# Emacs save files +*~ +\#*\# +.\#* + +# Vim-related files +[._]*.s[a-w][a-z] +[._]s[a-w][a-z] +*.un~ +Session.vim +.netrwhist + +# Chart dependencies +**/charts/*.tgz +values-local.yaml +values-local.out.yaml +Chart.lock + +.history + +# ignore files we might create for local development +ui.test/ +minica-key.pem +minica.pem diff --git a/.helmignore b/.helmignore new file mode 100644 index 0000000..0e8a0eb --- /dev/null +++ b/.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/Chart.lock b/Chart.lock new file mode 100644 index 0000000..8e1d31a --- /dev/null +++ b/Chart.lock @@ -0,0 +1,6 @@ +dependencies: +- name: redis + repository: https://charts.bitnami.com/bitnami + version: 15.7.6 +digest: sha256:9844c7f437a25a400c8c4b9b76bd84a950820eb15c432f52f805767bc55e1cc5 +generated: "2022-10-09T14:05:10.390477+02:00" diff --git a/Chart.yaml b/Chart.yaml new file mode 100644 index 0000000..ccf6e9f --- /dev/null +++ b/Chart.yaml @@ -0,0 +1,13 @@ +apiVersion: v2 +name: agent-assist +description: A Helm chart to deploy Cognigy Agent Assist + +type: application +version: "4.51.0-82336" +appVersion: "4.51.0-82336" +icon: https://liveagent-trial.cognigy.ai/apple-icon-180x180.png +dependencies: + - name: redis + version: "15.7.6" + repository: https://charts.bitnami.com/bitnami + condition: redis.enabled diff --git a/README.md b/README.md index aab5bc1..5328eec 100644 --- a/README.md +++ b/README.md @@ -1 +1,193 @@ -# cognigy-agent-assist-helm-chart \ No newline at end of file +# Cognigy Agent Assist Helm Chart + +Cognigy Agent Assist offers a variety of advanced features that empower agents to provide faster and more accurate customer support. + +## Prerequisites + +- Kubernetes v1.19-1.24 running on either: + - AWS EKS + - Azure AKS + - "generic" on-premises kubernetes platform. Running Cognigy Agent Assist on-premises will require additional manual steps, we recommend to use public clouds (AWS or Azure) instead. +- kubectl utility connected to the kubernetes cluster +- Helm 3.8.0+ +- MongoDB Helm chart installed in the same cluster. Here is the [setup guide](https://github.com/Cognigy/cognigy-mongodb-helm-chart) +- Cognigy.AI Helm chart installed in the same cluster. Here is the [setup guide](https://github.com/Cognigy/cognigy-ai-helm-chart) + +## Configuration + +To deploy a new Cognigy Agent Assist setup you need to create a separate file with Helm release values. You can use `values_prod.yaml` as a baseline, we recommend to start with it: + +1. Make a copy of `values_prod.yaml` into a new file and name it accordingly, we refer to it as `YOUR_VALUES_FILE.yaml` later in this document. +2. **Do not make** a copy of default `values.yaml` file as it contains hardcoded docker images references for all microservices, and in this case you will need to change all of them manually during upgrades. However, you can add some variables from default `values.yaml` file into your customized `YOUR_VALUES_FILE.yaml` later on, e.g. for tweaking CPU/RAM resources of Cognigy Agent Assist microservices. We describe this process later in the document. + +### Setting Essential Parameters + +You need to set at least following parameters in `YOUR_VALUES_FILE.yaml`: + +1. Cognigy Image repository credentials: set `imageCredentials.username` and `imageCredentials.password` accordingly. +2. MongoDB root credentials: set `mongodb.auth` `username` and `password` to `rootUser` and `rootPassword` values of MongoDB helm release created before. + +### Installing the Chart + +1. Download chart dependencies: + +```bash +helm dependency update +``` + +2. Install Cognigy Agent Assist Helm release: + +- Installing from Cognigy Container Registry (recommended), specify proper `HELM_CHART_VERSION` and `YOUR_VALUES_FILE.yaml`: + - Login into Cognigy helm registry (provide your Cognigy Container Registry credentials): + ``` + helm registry login cognigy.azurecr.io \ + --username \ + --password + ``` + - Install Helm Chart into a separate `agent-assist` namespace: + ``` + helm upgrade --install --namespace agent-assist cognigy-agent-assist oci://cognigy.azurecr.io/helm/agent-assist --version HELM_CHART_VERSION --values YOUR_VALUES_FILE.yaml --create-namespace + ``` +- Alternatively you can install it from the local chart (not recommended): + ``` + helm upgrade --install --namespace agent-assist --values YOUR_VALUES_FILE.yaml cognigy-agent-assist . + ``` + +3. Verify that all pods are in a ready state: + +``` +kubectl get pods --namespace agent-assist +``` + +Try to access the Agent Assist Workspace frontend URL, it should be available at `AGENT_ASSIST_WORKSPACE_FRONTEND_URL_WITH_PROTOCOL` value defined in `YOUR_VALUES_FILE.yaml`. + +The URL needs to provide fake parameters for `sessionId`, `userId`, `URLToken`, `organisationId`, `projectId` and `configId` query parameters, e.g.: + +`https:///?sessionId=test&userId=test&URLToken=test&organisationId=test&projectId=test&configId=test` + +### Upgrading Helm Release + +To upgrade Cognigy Agent Assist platform to a newer version, you need to upgrade the existing Helm release to a particular `HELM_CHART_VERSION`, for this execute: + +```bash +helm upgrade --namespace agent-assist cognigy-agent-assist oci://cognigy.azurecr.io/helm/agent-assist --version HELM_CHART_VERSION --values YOUR_VALUES_FILE.yaml +``` + +### Modifying Resources + +Default resources for Cognigy Agent Assist microservices specified in `values.yaml` are tailored to provide consistent performance for typical production use-cases. However, to meet particular demands, you can modify RAM/CPU resources or number of replicas for separate microservices in your Cognigy Agent Assist installation. For this you need to copy specific variables from default `values.yaml` into `YOUR_VALUES_FILE.yaml` for a particular microservice and adjust the `Request/Limits` and `replica` values accordingly. + +**IMPORTANT:** Do not copy `image` value as you will need to modify it manually during upgrades! + +For example, for `backend` microservice copy from `values.yaml` and adjust in `YOUR_VALUES_FILE.yaml` following variables: + +``` +backend: + replica: 1 + resources: + requests: + cpu: '0.4' + memory: 400M + limits: + cpu: '0.4' + memory: 500M +``` + +### Cognigy.AI integration + +#### Cognigy.AI Endpoint URL + +The `COGNIGY_AI_ENDPOINT_URL_WITH_PROTOCOL` value needs to point to the `cognigy-ai` service-endpoint ingress URL. This value is used by the Agent Assist Workspace backend to connect to Cognigy.AI. The value can be checked in the `cognigy-ai` Helm chart deployment file. + +#### Cognigy.AI Endpoint Access Token Secret + +The `cognigyServiceEndpointApiAccessToken.existingSecret` value is for generating a secret that is used to authenticate the Agent Assist Workspace API with Cognigy.AI. This secret is the Cognigy.AI service-endpoint `SERVICE_ENDPOINT_API_ACCESS_TOKEN` environment variable value. The environment variable used by the Agent Assist Workspace backend is `COGNIGY_AI_ENDPOINT_ACCESS_TOKEN` that can be checked in its deployment file. + +```yaml +# custom-values.yaml +cognigyServiceEndpointApiAccessToken: + existingSecret: cognigy-service-endpoint-api-access-token +``` + +You can use the following command to get the value of the `SERVICE_ENDPOINT_API_ACCESS_TOKEN` environment variable: + +```bash +kubectl get secret --namespace cognigy-ai cognigy-service-endpoint-api-access-token -o jsonpath="{.data.api-access-token}" | base64 -d +``` + +#### Cognigy.AI Handover Access Token Secret + +The `cognigyServiceHandoverApiAccessToken.existingSecret` value is for generating a secret that is used to authenticate the Agent Assist Workspace API with Cognigy.AI. This secret is the Cognigy.AI service-handover `SERVICE_HANDOVER_API_ACCESS_TOKEN` environment variable value. The environment variable used by the Agent Assist Workspace backend is `COGNIGY_AI_HANDOVER_ACCESS_TOKEN` that can be checked in its deployment file. + +```yaml +# custom-values.yaml +cognigyServiceHandoverApiAccessToken: + existingSecret: cognigy-service-handover-api-access-token +``` + +You can use the following command to get the value of the `SERVICE_HANDOVER_API_ACCESS_TOKEN` environment variable: + +```bash +kubectl get secret --namespace cognigy-ai cognigy-service-handover-api-access-token -o jsonpath="{.data.api-access-token}" | base64 -d +``` + +#### Cognigy.AI Helm chart Modifications + +The following values are required to be filled in order to integrate with Cognigy.AI Helm chart values: + +```yaml +# custom-values.yaml +cognigyEnv: + AGENT_ASSIST_WORKSPACE_FRONTEND_URL_WITH_PROTOCOL: https://agent-assist.test + AGENT_ASSIST_WORKSPACE_API_BASE_URL_WITH_PROTOCOL: https://api-agent-assist.test +``` + +#### Agent Assist API Access Token for Cognigy.AI services + +Assuming that Agent Assist is deployed on a production cluster with the `values.yaml` existing secrets filled, there is a need to create a new secret in the `cognigy-ai` namespace containing the `apiKey.existingSecret` value defined in the Cognigy Agent Assist Helm chart `values.yaml` file. Once the secret is created, it needs to be added into the cognigy-ai helm chart values for the services like this: + +```yaml +serviceAi: + # ... + extraEnvVars: + # ... + - name: AGENT_ASSIST_WORKSPACE_API_ACCESS_TOKEN + valueFrom: + secretKeyRef: + name: cognigy-agent-assist-workspace-credentials + key: api-access-token + +serviceEndpoint: + # ... + extraEnvVars: + # ... + - name: AGENT_ASSIST_WORKSPACE_API_ACCESS_TOKEN + valueFrom: + secretKeyRef: + name: cognigy-agent-assist-workspace-credentials + key: api-access-token + +serviceHandover: + # ... + extraEnvVars: + # ... + - name: AGENT_ASSIST_WORKSPACE_API_ACCESS_TOKEN + valueFrom: + secretKeyRef: + name: cognigy-agent-assist-workspace-credentials + key: api-access-token +``` + +### Do testing without installing the Helm chart + +#### Testing linting + +Run the following command to test the linting: + + `helm lint ./` + +#### Testing templates generation + +Run the following command to test the templates generation: + + `helm template ./` diff --git a/charts/redis-15.7.6.tgz b/charts/redis-15.7.6.tgz new file mode 100644 index 0000000000000000000000000000000000000000..37cde427b54e55ac0ac74d8765616e3141e4fd61 GIT binary patch literal 80315 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

{LT`x0}jK~4* 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 /tmp/dbinit-scripts/$file_name.js <<-EOF +if (db.getSiblingDB("$DB").getUser("$USER") == null) { + db.getSiblingDB("$DB").createUser({ + user: "$USER", + pwd: "$PASSWORD", + roles: [ + { role: "readWrite", db: "$DB" } + ] + }); +} +EOF +done + +MONGODB_PRIMARY_HOST=`mongo -u $MONGODB_USERNAME -p $MONGODB_PASSWORD --authenticationDatabase admin $MONGODB_HOSTS --eval "rs.status().members.find(r=>r.state===1).name" --quiet` +echo "MongoDB primay host: $MONGODB_PRIMARY_HOST" +mongo -u $MONGODB_USERNAME -p $MONGODB_PASSWORD --authenticationDatabase admin $MONGODB_PRIMARY_HOST /tmp/dbinit-scripts/* \ No newline at end of file diff --git a/templates/NOTES.txt b/templates/NOTES.txt new file mode 100644 index 0000000..da4250f --- /dev/null +++ b/templates/NOTES.txt @@ -0,0 +1,14 @@ +Thank you for installing {{ .Chart.Name }}. + +Your release is named {{ .Release.Name }} with version {{ .Release.version }}. + +To learn more about the release, try: + + $ helm status {{ .Release.Name }} + $ helm get all {{ .Release.Name }} + +NOTE: It may take a few minutes for the LoadBalancer IP to be available. +You can watch the status by running +'kubectl get svc -w {{ template "agent-assist.fullname" . }}' + +To learn more about the chart upgrade, head over to https://docs.cognigy.com/agent-assist/installation/deployment/installation-updates/ \ No newline at end of file diff --git a/templates/_helpers.tpl b/templates/_helpers.tpl new file mode 100644 index 0000000..295782a --- /dev/null +++ b/templates/_helpers.tpl @@ -0,0 +1,234 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "agent-assist.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "agent-assist.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 }} + +{{/* +Backend full name +*/}} +{{- define "agent-assist-backend.fullname" -}} +{{ include "agent-assist.fullname" . }}-backend +{{- end }} + +{{/* +Frontend full name +*/}} +{{- define "agent-assist-frontend.fullname" -}} +{{ include "agent-assist.fullname" . }}-frontend +{{- end }} + +{{/* +Test API Bridge full name +*/}} +{{- define "agent-assist-test-api-bridge.fullname" -}} +{{ include "agent-assist.fullname" . }}-test-api-bridge +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "agent-assist.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "agent-assist.labels" -}} +helm.sh/chart: {{ include "agent-assist.chart" . }} +{{ include "agent-assist.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "agent-assist.selectorLabels" -}} +app.kubernetes.io/name: {{ include "agent-assist.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Return the appropriate apiVersion for ingress. +*/}} +{{- define "grafana.ingress.apiVersion" -}} + {{- if and (.Capabilities.APIVersions.Has "networking.k8s.io/v1") (semverCompare ">= 1.19-0" .Capabilities.KubeVersion.Version) -}} + {{- print "networking.k8s.io/v1" -}} + {{- else if .Capabilities.APIVersions.Has "networking.k8s.io/v1beta1" -}} + {{- print "networking.k8s.io/v1beta1" -}} + {{- else -}} + {{- print "extensions/v1beta1" -}} + {{- end -}} +{{- end -}} + +{{/* +Return if ingress is stable. +*/}} +{{- define "grafana.ingress.isStable" -}} + {{- eq (include "grafana.ingress.apiVersion" .) "networking.k8s.io/v1" -}} +{{- end -}} + +{{/* +Return if ingress supports pathType. +*/}} +{{- define "grafana.ingress.supportsPathType" -}} + {{- or (eq (include "grafana.ingress.isStable" .) "true") (and (eq (include "grafana.ingress.apiVersion" .) "networking.k8s.io/v1beta1") (semverCompare ">= 1.18-0" .Capabilities.KubeVersion.Version)) -}} +{{- end -}} + +{{/* +Return the proper mongodb credentials Secret Name +*/}} +{{- define "mongodbCredentials.secretName.render" -}} + {{- $mongodbCredentialsSecretName := "" -}} + + {{- if .Values.mongodb.auth.existingSecret -}} + {{- $mongodbCredentialsSecretName = .Values.mongodb.auth.existingSecret -}} + {{- else -}} + {{- $mongodbCredentialsSecretName = "mongodb-connection-creds" -}} + {{- end -}} + + {{- printf "%s" (tpl $mongodbCredentialsSecretName $) -}} +{{- end -}} + +{{- define "connectionString.mongodb" -}} +{{- .mongodbScheme }}://{{- .serviceName -}}:{{- .pw -}}@{{- .mongodbHosts -}}/{{- .serviceName -}}{{- .mongodbParams }} +{{- end }} + +{{/* +Return the proper mongodb Atlas credentials Secret Name +*/}} +{{- define "mongodbAtlasCredentials.secretName.render" -}} + {{- $mongodbAtlasCredentialsSecretName := "" -}} + + {{- if .Values.mongodb.auth.atlas.existingSecret -}} + {{- $mongodbAtlasCredentialsSecretName = .Values.mongodb.auth.atlas.existingSecret -}} + {{- else -}} + {{- $mongodbAtlasCredentialsSecretName = "mongodb-atlas-creds" -}} + {{- end -}} + + {{- printf "%s" (tpl $mongodbAtlasCredentialsSecretName $) -}} +{{- end -}} + + +{{/* +Set redis fullname +*/}} +{{- define "agent-assist.redis.fullname" -}} +{{- if .Values.redis.fullnameOverride -}} +{{- .Values.redis.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default .Chart.Name .Values.redis.nameOverride -}} +{{- if contains $name .Release.Name -}} +{{- .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s" .Release.Name "redis" | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Set redis port +*/}} +{{- define "agent-assist.redis.port" -}} +{{- if .Values.redis.enabled -}} + 6379 +{{- else -}} +{{- default 6379 .Values.redis.port -}} +{{- end -}} +{{- end -}} + +{{/* +Set redis host +*/}} +{{- define "agent-assist.redis.host" -}} +{{- if and (.Values.redis.enabled) (.Values.redis.sentinel.enabled) -}} +{{- template "agent-assist.redis.fullname" . -}} +{{- else if .Values.redis.enabled -}} +{{- template "agent-assist.redis.fullname" . -}}-master +{{- else -}} +{{- .Values.redis.host -}} +{{- end -}} +{{- end -}} + +{{/* +Set the backend api key secret +*/}} +{{- define "agent-assist.backend.apiKey" -}} +{{- if .Values.backend.apiKey.existingSecret -}} + {{- .Values.backend.apiKey.existingSecret | quote -}} +{{- else -}} + "agent-assist-api-key" +{{- end -}} +{{- end -}} + +{{/* +Set the service-endpoint api key secret +*/}} +{{- define "agent-assist.backend.cognigyServiceEndpointApiAccessToken" -}} +{{- if .Values.backend.cognigyServiceEndpointApiAccessToken.existingSecret -}} + {{- .Values.backend.cognigyServiceEndpointApiAccessToken.existingSecret | quote -}} +{{- else -}} + "cognigy-service-endpoint-api-access-token" +{{- end -}} +{{- end -}} + +{{/* +Set the service-handover api key secret +*/}} +{{- define "agent-assist.backend.cognigyServiceHandoverApiAccessToken" -}} +{{- if .Values.backend.cognigyServiceHandoverApiAccessToken.existingSecret -}} + {{- .Values.backend.cognigyServiceHandoverApiAccessToken.existingSecret | quote -}} +{{- else -}} + "cognigy-service-handover-api-access-token" +{{- end -}} +{{- end -}} + +{{/* +Return the proper Docker Image Registry Secret Names +*/}} +{{- define "image.pullSecrets" -}} + {{- $pullSecrets := list -}} + + {{- if and (.Values.imageCredentials.registry) (.Values.imageCredentials.username) (.Values.imageCredentials.password) -}} + {{- $pullSecrets = append $pullSecrets "cognigy-registry-token" -}} + {{- else if .Values.imageCredentials.pullSecrets -}} + {{- range .Values.imageCredentials.pullSecrets -}} + {{- $pullSecrets = append $pullSecrets . -}} + {{- end -}} + {{- else -}} + {{ required "A valid value for .Values.imageCredentials is required!" .Values.imageCredentials.registry }} + {{ required "A valid value for .Values.imageCredentials is required!" .Values.imageCredentials.username }} + {{ required "A valid value for .Values.imageCredentials is required!" .Values.imageCredentials.password }} + {{ required "A valid value for .Values.imageCredentials is required!" .Values.imageCredentials.pullSecrets }} + {{- end -}} + + {{- if (not (empty $pullSecrets)) -}} +imagePullSecrets: + {{- range $pullSecrets }} + - name: {{ . }} + {{- end -}} + {{- end -}} +{{- end -}} \ No newline at end of file diff --git a/templates/backend/backend-configmap.yaml b/templates/backend/backend-configmap.yaml new file mode 100644 index 0000000..f8236df --- /dev/null +++ b/templates/backend/backend-configmap.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + creationTimestamp: null + name: {{ template "agent-assist-backend.fullname" . }} + labels: + {{- include "agent-assist.labels" . | nindent 4 }} + +data: + NODE_ENV: 'production' + SERVER_HOST: '0.0.0.0' + SERVER_PORT: {{ .Values.backend.service.internalPort | quote }} + REDIS_HOST: {{template "agent-assist.redis.host" . }} +{{ toYaml .Values.backend.configmap | nindent 2 }} diff --git a/templates/backend/backend-deployment.yaml b/templates/backend/backend-deployment.yaml new file mode 100644 index 0000000..118aba1 --- /dev/null +++ b/templates/backend/backend-deployment.yaml @@ -0,0 +1,97 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "agent-assist-backend.fullname" . }} + labels: + {{- include "agent-assist.labels" . | nindent 4 }} +spec: + replicas: {{ int .Values.backend.replica }} + selector: + matchLabels: + {{- include "agent-assist.selectorLabels" . | nindent 6 }} + role: backend + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "agent-assist.selectorLabels" . | nindent 8 }} + role: backend + spec: + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- if .Values.nodeSelector }} + nodeSelector: {{- include "common.tplvalues.render" (dict "value" .Values.nodeSelector "context" $) | nindent 8 }} + {{- end }} + {{- include "image.pullSecrets" $ | nindent 6 }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - envFrom: + - configMapRef: + name: {{ template "agent-assist-backend.fullname" . }} + env: + - name: ACCESS_TOKEN_SECRET + valueFrom: + secretKeyRef: + name: {{ template "agent-assist.backend.apiKey" . }} + key: api-key + - name: COGNIGY_AI_ENDPOINT_ACCESS_TOKEN + valueFrom: + secretKeyRef: + name: {{ template "agent-assist.backend.cognigyServiceEndpointApiAccessToken" . }} + key: api-access-token + - name: COGNIGY_AI_HANDOVER_ACCESS_TOKEN + valueFrom: + secretKeyRef: + name: {{ template "agent-assist.backend.cognigyServiceHandoverApiAccessToken" . }} + key: api-access-token + image: "{{ .Values.backend.image.repository }}:{{ .Values.backend.image.tag | default .Chart.AppVersion }}" + name: {{ .Chart.Name }}-app + ports: + - name: app + containerPort: {{ int .Values.backend.service.internalPort }} + - name: metrics + containerPort: {{ int .Values.backend.metrics.port | default 8002 }} + - name: health + containerPort: {{ int .Values.backend.health.port | default 8001 }} + imagePullPolicy: {{ .Values.backend.image.pullPolicy }} + {{- with .Values.backend.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} + startupProbe: + httpGet: + path: /health/startup + port: {{ int .Values.backend.health.port | default 8001 }} + livenessProbe: + httpGet: + path: /health/liveness + port: {{ int .Values.backend.health.port | default 8001 }} + failureThreshold: 3 + initialDelaySeconds: 90 + periodSeconds: 60 + timeoutSeconds: 10 + volumeMounts: + - name: mongodb-connection-string + mountPath: /var/run/secrets/mongodbConnectionString + subPath: mongodbConnectionString + - name: redis-password + mountPath: /var/run/secrets/redis-password.conf + subPath: redis-password.conf + volumes: + - name: mongodb-connection-string + secret: + secretName: cognigy-agent-assist + items: + - key: connection-string + path: mongodbConnectionString + - name: redis-password + secret: + secretName: agent-assist-redis-password + +status: {} diff --git a/templates/backend/backend-ingress.yaml b/templates/backend/backend-ingress.yaml new file mode 100644 index 0000000..aaf4432 --- /dev/null +++ b/templates/backend/backend-ingress.yaml @@ -0,0 +1,50 @@ +{{- if .Values.backend.ingress.enabled -}} +{{- $ingressApiIsStable := eq (include "grafana.ingress.isStable" .) "true" -}} +{{- $ingressSupportsPathType := eq (include "grafana.ingress.supportsPathType" .) "true" -}} +apiVersion: {{ include "grafana.ingress.apiVersion" . }} +kind: Ingress +metadata: + name: {{ include "agent-assist-backend.fullname" . }} + labels: + {{- include "agent-assist.selectorLabels" . | nindent 4 }} + {{- if .Values.backend.ingress.annotations }} + annotations: + {{- range $key, $value := .Values.backend.ingress.annotations }} + {{ $key }}: {{ tpl $value $ | quote }} + {{- end }} + {{- end }} +spec: + ingressClassName: traefik + {{- if .Values.backend.ingress.tls }} + tls: + {{- range .Values.backend.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- range .Values.backend.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + {{- if $ingressSupportsPathType }} + pathType: {{ .pathType }} + {{- end }} + backend: + {{- if $ingressApiIsStable }} + service: + name: {{ .backend.service.name }} + port: + number: {{ int .backend.service.port.number }} + {{- else }} + serviceName: {{ .backend.service.name }} + servicePort: {{ int .backend.service.port.number }} + {{- end }} + {{- end }} + {{- end }} + {{- end }} \ No newline at end of file diff --git a/templates/backend/backend-service.yaml b/templates/backend/backend-service.yaml new file mode 100644 index 0000000..858c894 --- /dev/null +++ b/templates/backend/backend-service.yaml @@ -0,0 +1,22 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ template "agent-assist-backend.fullname" . }} + creationTimestamp: null + labels: + {{- include "agent-assist.selectorLabels" . | nindent 8 }} + {{- with .Values.backend.service.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + ports: + - name: {{ .Values.backend.service.name | quote}} + port: {{ int .Values.backend.service.internalPort }} + targetPort: {{ int .Values.backend.service.targetPort }} + type: {{ .Values.backend.service.type }} + selector: + {{- include "agent-assist.selectorLabels" . | nindent 4 }} + role: backend +status: + loadBalancer: {} \ No newline at end of file diff --git a/templates/dbinit-generate/dbinit-generate.yaml b/templates/dbinit-generate/dbinit-generate.yaml new file mode 100644 index 0000000..8ececd3 --- /dev/null +++ b/templates/dbinit-generate/dbinit-generate.yaml @@ -0,0 +1,167 @@ +{{- if .Values.mongodb.enabled }} +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: cognigy-agent-assist-db-init + namespace: {{ $.Release.Namespace | quote }} + annotations: + # This is what defines this resource as a hook. Without these lines, the + # resource is considered part of the release. + "helm.sh/hook": pre-install,pre-upgrade + "helm.sh/hook-weight": "-3" + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded +spec: + template: + metadata: + name: cognigy-agent-assist-db-init + spec: + restartPolicy: Never + {{- include "image.pullSecrets" $ | nindent 6 }} + {{- if .Values.mongodb.dbinit.securityContext }} + securityContext: + {{- toYaml .Values.mongodb.dbinit.securityContext | nindent 8 }} + {{- end }} + containers: + - name: cognigy-agent-assist-db-init + image: {{ .Values.mongodb.dbinit.image }} + command: ["/bin/bash"] + {{- if eq .Values.mongodb.scheme "mongodb+srv" }} + args: ["/cognigy-ai-dbinit/dbinit-generate/dbinit-generate-atlas.sh"] + {{- else }} + args: ["/cognigy-ai-dbinit/dbinit-generate/dbinit-generate.sh"] + {{- end }} + volumeMounts: + - name: dbinit-generate + mountPath: /cognigy-ai-dbinit/dbinit-generate + - name: mongodb-connection-strings + mountPath: "/cognigy-ai-dbinit/mongodb-connection-strings" + readOnly: true + env: + {{- if eq .Values.mongodb.scheme "mongodb+srv" }} + - name: MONGODB_ATLAS_PUBLIC_API_KEY + valueFrom: + secretKeyRef: + name: {{- include "mongodbAtlasCredentials.secretName.render" $ | indent 1 }} + key: apikeypublic + - name: MONGODB_ATLAS_PRIVATE_API_KEY + valueFrom: + secretKeyRef: + name: {{- include "mongodbAtlasCredentials.secretName.render" $ | indent 1 }} + key: apikeyprivate + - name: MONGODB_ATLAS_PROJECT_ID + valueFrom: + secretKeyRef: + name: {{- include "mongodbAtlasCredentials.secretName.render" $ | indent 1 }} + key: projectid + - name: MONGODB_ATLAS_CLUSTER_NAME + valueFrom: + secretKeyRef: + name: {{- include "mongodbAtlasCredentials.secretName.render" $ | indent 1 }} + key: clustername + {{- else }} + - name: MONGODB_USERNAME + valueFrom: + secretKeyRef: + name: {{- include "mongodbCredentials.secretName.render" $ | indent 1 }} + key: username + - name: MONGODB_PASSWORD + valueFrom: + secretKeyRef: + name: {{- include "mongodbCredentials.secretName.render" $ | indent 1 }} + key: password + - name: MONGODB_HOSTS + valueFrom: + secretKeyRef: + name: mongodb-connection-hosts + key: hosts + {{- end}} + volumes: + - name: dbinit-generate + configMap: + name: dbinit-generate + + - name: mongodb-connection-strings + projected: + sources: + - secret: + name: cognigy-agent-assist + items: + - key: connection-string + path: cognigy-agent-assist +{{- end }} +{{- if and (.Values.mongodb.enabled) (eq .Values.mongodb.scheme "mongodb") (not .Values.mongodb.auth.existingSecret) }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: mongodb-connection-creds + namespace: {{ $.Release.Namespace | quote }} + annotations: + # This is what defines this resource as a hook. Without these lines, the + # resource is considered part of the release. + "helm.sh/hook": pre-install,pre-upgrade + "helm.sh/hook-weight": "-4" + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded,hook-failed +type: Opaque +data: + username: "{{- .Values.mongodb.auth.rootUser | b64enc }}" + password: "{{- .Values.mongodb.auth.rootPassword | b64enc }}" +{{- end }} +{{- if and (.Values.mongodb.enabled) (eq .Values.mongodb.scheme "mongodb+srv") (not .Values.mongodb.auth.atlas.existingSecret) }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: mongodb-atlas-creds + namespace: {{ $.Release.Namespace | quote }} + annotations: + # This is what defines this resource as a hook. Without these lines, the + # resource is considered part of the release. + "helm.sh/hook": pre-install,pre-upgrade + "helm.sh/hook-weight": "-4" + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded,hook-failed +type: Opaque +data: + apikeypublic: "{{- .Values.mongodb.auth.atlas.publicAPIKey | b64enc }}" + apikeyprivate: "{{- .Values.mongodb.auth.atlas.privateAPIKey | b64enc }}" + projectid: "{{- .Values.mongodb.auth.atlas.projectId | b64enc }}" + clustername: "{{- .Values.mongodb.auth.atlas.clusterName | b64enc }}" +{{- end }} +{{- if .Values.mongodb.enabled }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: mongodb-connection-hosts + namespace: {{ $.Release.Namespace | quote }} + annotations: + # This is what defines this resource as a hook. Without these lines, the + # resource is considered part of the release. + "helm.sh/hook": pre-install,pre-upgrade + "helm.sh/hook-weight": "-4" + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded,hook-failed +type: Opaque +data: + hosts: "{{- .Values.mongodb.hosts | b64enc }}" +{{- end }} +{{- if .Values.mongodb.enabled }} +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: dbinit-generate + namespace: {{ $.Release.Namespace | quote }} + annotations: + # This is what defines this resource as a hook. Without these lines, the + # resource is considered part of the release. + "helm.sh/hook": pre-install,pre-upgrade + "helm.sh/hook-weight": "-4" + "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded,hook-failed +data: +{{- if eq .Values.mongodb.scheme "mongodb+srv" }} +{{ (.Files.Glob "scripts/dbinit-generate-atlas.sh").AsConfig | indent 2 }} +{{- else }} +{{ (.Files.Glob "scripts/dbinit-generate.sh").AsConfig | indent 2 }} +{{- end }} +{{- end }} \ No newline at end of file diff --git a/templates/frontend/frontend-configmap.yaml b/templates/frontend/frontend-configmap.yaml new file mode 100644 index 0000000..211caac --- /dev/null +++ b/templates/frontend/frontend-configmap.yaml @@ -0,0 +1,22 @@ +{{- if .Values.frontend.enabled -}} +apiVersion: v1 +kind: ConfigMap +metadata: + creationTimestamp: null + name: {{ template "agent-assist-frontend.fullname" . }} + labels: + {{- include "agent-assist.labels" . | nindent 4 }} + +data: + NODE_ENV: 'production' + SERVER_HOST: '0.0.0.0' + SERVER_PORT: {{ .Values.frontend.service.internalPort | quote }} + METRICS_PORT: {{ .Values.frontend.metrics.port | quote }} + HEALTH_PORT: {{ .Values.frontend.health.port | quote }} + {{- with (first .Values.backend.ingress.hosts) }} + APP_AGENT_ASSIST_API_URL: "https://{{ .host }}" + {{- end }} +{{ if .Values.frontend.configmap }} +{{ toYaml .Values.frontend.configmap | nindent 2 }} +{{ end }} +{{- end }} diff --git a/templates/frontend/frontend-deployment.yaml b/templates/frontend/frontend-deployment.yaml new file mode 100644 index 0000000..bd4f303 --- /dev/null +++ b/templates/frontend/frontend-deployment.yaml @@ -0,0 +1,71 @@ +{{- if .Values.frontend.enabled -}} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "agent-assist-frontend.fullname" . }} + labels: + {{- include "agent-assist.labels" . | nindent 4 }} +spec: + replicas: {{ int .Values.frontend.replica }} + selector: + matchLabels: + {{- include "agent-assist.selectorLabels" . | nindent 6 }} + role: frontend + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "agent-assist.selectorLabels" . | nindent 8 }} + role: frontend + spec: + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- if .Values.nodeSelector }} + nodeSelector: {{- include "common.tplvalues.render" (dict "value" .Values.nodeSelector "context" $) | nindent 8 }} + {{- end }} + {{- include "image.pullSecrets" $ | nindent 6 }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - envFrom: + - configMapRef: + name: {{ template "agent-assist-frontend.fullname" . }} + env: + - name: APP_ACCESS_TOKEN_SECRET + valueFrom: + secretKeyRef: + name: {{ template "agent-assist.backend.apiKey" . }} + key: api-key + image: "{{ .Values.frontend.image.repository }}:{{ .Values.frontend.image.tag | default .Chart.AppVersion }}" + name: {{ .Chart.Name }}-app + ports: + - name: app + containerPort: {{ int .Values.frontend.service.internalPort }} + - name: metrics + containerPort: {{ int .Values.frontend.metrics.port | default 8002 }} + - name: health + containerPort: {{ int .Values.frontend.health.port | default 8001 }} + imagePullPolicy: {{ .Values.frontend.image.pullPolicy }} + {{- with .Values.frontend.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} + startupProbe: + httpGet: + path: /health/startup + port: {{ int .Values.frontend.health.port | default 8001 }} + livenessProbe: + httpGet: + path: /health/liveness + port: {{ int .Values.frontend.health.port | default 8001 }} + failureThreshold: 3 + initialDelaySeconds: 90 + periodSeconds: 60 + timeoutSeconds: 10 +status: {} +{{- end }} diff --git a/templates/frontend/frontend-ingress.yaml b/templates/frontend/frontend-ingress.yaml new file mode 100644 index 0000000..565ea8e --- /dev/null +++ b/templates/frontend/frontend-ingress.yaml @@ -0,0 +1,52 @@ +{{- if .Values.frontend.enabled -}} +{{- if .Values.frontend.ingress.enabled -}} +{{- $ingressApiIsStable := eq (include "grafana.ingress.isStable" .) "true" -}} +{{- $ingressSupportsPathType := eq (include "grafana.ingress.supportsPathType" .) "true" -}} +apiVersion: {{ include "grafana.ingress.apiVersion" . }} +kind: Ingress +metadata: + name: {{ include "agent-assist-frontend.fullname" . }} + labels: + {{- include "agent-assist.selectorLabels" . | nindent 4 }} + {{- if .Values.frontend.ingress.annotations }} + annotations: + {{- range $key, $value := .Values.frontend.ingress.annotations }} + {{ $key }}: {{ tpl $value $ | quote }} + {{- end }} + {{- end }} +spec: + ingressClassName: traefik + {{- if .Values.frontend.ingress.tls }} + tls: + {{- range .Values.frontend.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- range .Values.frontend.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + {{- if $ingressSupportsPathType }} + pathType: {{ .pathType }} + {{- end }} + backend: + {{- if $ingressApiIsStable }} + service: + name: {{ .backend.service.name }} + port: + number: {{ int .backend.service.port.number }} + {{- else }} + serviceName: {{ .backend.service.name }} + servicePort: {{ int .backend.service.port.number }} + {{- end }} + {{- end }} + {{- end }} + {{- end }} +{{- end }} \ No newline at end of file diff --git a/templates/frontend/frontend-service.yaml b/templates/frontend/frontend-service.yaml new file mode 100644 index 0000000..80c296d --- /dev/null +++ b/templates/frontend/frontend-service.yaml @@ -0,0 +1,24 @@ +{{- if .Values.frontend.enabled -}} +apiVersion: v1 +kind: Service +metadata: + name: {{ template "agent-assist-frontend.fullname" . }} + creationTimestamp: null + labels: + {{- include "agent-assist.selectorLabels" . | nindent 8 }} + {{- with .Values.frontend.service.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + ports: + - name: {{ .Values.frontend.service.name | quote}} + port: {{ int .Values.frontend.service.internalPort }} + targetPort: {{ int .Values.frontend.service.targetPort }} + type: {{ .Values.frontend.service.type }} + selector: + {{- include "agent-assist.selectorLabels" . | nindent 4 }} + role: frontend +status: + loadBalancer: {} +{{- end }} \ No newline at end of file diff --git a/templates/pod-monitors/backend-monitor.yaml b/templates/pod-monitors/backend-monitor.yaml new file mode 100644 index 0000000..43f34ee --- /dev/null +++ b/templates/pod-monitors/backend-monitor.yaml @@ -0,0 +1,17 @@ +{{- if .Values.monitoring.enabled -}} +apiVersion: monitoring.coreos.com/v1 +kind: PodMonitor +metadata: + name: "prom-monitor-{{ template "agent-assist.fullname" . }}-backend" + namespace: monitoring +spec: + namespaceSelector: + matchNames: + - {{ .Release.Namespace | quote }} + podMetricsEndpoints: + - path: /metrics + port: metrics + selector: + matchLabels: + role: backend +{{- end }} \ No newline at end of file diff --git a/templates/pod-monitors/frontend-monitor.yaml b/templates/pod-monitors/frontend-monitor.yaml new file mode 100644 index 0000000..8aa5878 --- /dev/null +++ b/templates/pod-monitors/frontend-monitor.yaml @@ -0,0 +1,17 @@ +{{- if .Values.monitoring.enabled -}} +apiVersion: monitoring.coreos.com/v1 +kind: PodMonitor +metadata: + name: "prom-monitor-{{ template "agent-assist.fullname" . }}-frontend" + namespace: monitoring +spec: + namespaceSelector: + matchNames: + - {{ .Release.Namespace | quote }} + podMetricsEndpoints: + - path: /metrics + port: metrics + selector: + matchLabels: + role: frontend +{{- end }} \ No newline at end of file diff --git a/templates/secrets/agent-assist-backend-api-key.yaml b/templates/secrets/agent-assist-backend-api-key.yaml new file mode 100644 index 0000000..8252556 --- /dev/null +++ b/templates/secrets/agent-assist-backend-api-key.yaml @@ -0,0 +1,11 @@ +{{- if (not .Values.backend.apiKey.existingSecret) }} +apiVersion: v1 +kind: Secret +metadata: + name: agent-assist-api-key + namespace: {{ $.Release.Namespace | quote }} +type: Opaque +data: + # this should contain a valid api key for the Agent Assist API + api-key: {{ .Values.backend.apiKey.value | b64enc | quote }} +{{- end }} diff --git a/templates/secrets/agent-assist-redis-password.yaml b/templates/secrets/agent-assist-redis-password.yaml new file mode 100644 index 0000000..2b68725 --- /dev/null +++ b/templates/secrets/agent-assist-redis-password.yaml @@ -0,0 +1,16 @@ +{{- $redis_password := printf "%x" (randAlphaNum 64) -}} + +{{ if not (lookup "v1" "Secret" $.Release.Namespace "agent-assist-redis-password") }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: agent-assist-redis-password + namespace: {{ $.Release.Namespace | quote }} + annotations: + "helm.sh/resource-policy": "keep" +type: Opaque +data: + redis-password.conf: "{{ printf "requirepass %s" $redis_password | b64enc }}" + REDIS_PASSWORD: "{{ $redis_password | b64enc }}" +{{- end }} \ No newline at end of file diff --git a/templates/secrets/cognigy-agent-assist.yaml b/templates/secrets/cognigy-agent-assist.yaml new file mode 100644 index 0000000..affa512 --- /dev/null +++ b/templates/secrets/cognigy-agent-assist.yaml @@ -0,0 +1,17 @@ +{{ if not (lookup "v1" "Secret" $.Release.Namespace "cognigy-agent-assist") }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: cognigy-agent-assist + namespace: {{ $.Release.Namespace | quote }} + annotations: + # This is what defines this resource as a hook. Without these lines, the + # resource is considered part of the release. + "helm.sh/hook": pre-install + "helm.sh/hook-weight": "-4" + "helm.sh/resource-policy": "keep" +type: Opaque +data: + connection-string: "{{- include "connectionString.mongodb" (dict "pw" (randAlphaNum 24) "serviceName" "agent-assist" "mongodbScheme" .Values.mongodb.scheme "mongodbHosts" .Values.mongodb.hosts "mongodbParams" .Values.mongodb.params) | b64enc }}" +{{- end }} \ No newline at end of file diff --git a/templates/secrets/cognigy-service-endpoint-api-access-token.yaml b/templates/secrets/cognigy-service-endpoint-api-access-token.yaml new file mode 100644 index 0000000..41163f8 --- /dev/null +++ b/templates/secrets/cognigy-service-endpoint-api-access-token.yaml @@ -0,0 +1,11 @@ +{{- if (not .Values.backend.cognigyServiceEndpointApiAccessToken.existingSecret) }} +apiVersion: v1 +kind: Secret +metadata: + name: cognigy-service-endpoint-api-access-token + namespace: {{ $.Release.Namespace | quote }} +type: Opaque +data: + # this should contain a valid api key for the Agent Assist API + api-access-token: {{ .Values.backend.cognigyServiceEndpointApiAccessToken.value | b64enc | quote }} +{{- end }} diff --git a/templates/secrets/cognigy-service-handover-api-access-token.yaml b/templates/secrets/cognigy-service-handover-api-access-token.yaml new file mode 100644 index 0000000..41c5f27 --- /dev/null +++ b/templates/secrets/cognigy-service-handover-api-access-token.yaml @@ -0,0 +1,11 @@ +{{- if (not .Values.backend.cognigyServiceHandoverApiAccessToken.existingSecret) }} +apiVersion: v1 +kind: Secret +metadata: + name: cognigy-service-handover-api-access-token + namespace: {{ $.Release.Namespace | quote }} +type: Opaque +data: + # this should contain a valid api key for the Agent Assist API + api-access-token: {{ .Values.backend.cognigyServiceHandoverApiAccessToken.value | b64enc | quote }} +{{- end }} diff --git a/templates/test-api-bridge/test-api-bridge-configmap.yaml b/templates/test-api-bridge/test-api-bridge-configmap.yaml new file mode 100644 index 0000000..44fa22f --- /dev/null +++ b/templates/test-api-bridge/test-api-bridge-configmap.yaml @@ -0,0 +1,17 @@ +{{- if .Values.loadTestingMode.enabled -}} +apiVersion: v1 +kind: ConfigMap +metadata: + creationTimestamp: null + name: {{ template "agent-assist-test-api-bridge.fullname" . }} + labels: + {{- include "agent-assist.labels" . | nindent 4 }} + +data: + NODE_ENV: 'production' + SERVER_HOST: '0.0.0.0' + SERVER_PORT: {{ .Values.loadTestingMode.testApiBridge.service.internalPort | quote }} +{{ if .Values.loadTestingMode.testApiBridge.configmap }} +{{ toYaml .Values.loadTestingMode.testApiBridge.configmap | nindent 2 }} +{{ end }} +{{- end }} diff --git a/templates/test-api-bridge/test-api-bridge-deployment.yaml b/templates/test-api-bridge/test-api-bridge-deployment.yaml new file mode 100644 index 0000000..c075307 --- /dev/null +++ b/templates/test-api-bridge/test-api-bridge-deployment.yaml @@ -0,0 +1,55 @@ +{{- if .Values.loadTestingMode.enabled -}} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "agent-assist-test-api-bridge.fullname" . }} + labels: + {{- include "agent-assist.labels" . | nindent 4 }} +spec: + replicas: {{ int .Values.loadTestingMode.testApiBridge.replica }} + selector: + matchLabels: + {{- include "agent-assist.selectorLabels" . | nindent 6 }} + role: test-api-bridge + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "agent-assist.selectorLabels" . | nindent 8 }} + role: test-api-bridge + spec: + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- if .Values.nodeSelector }} + nodeSelector: {{- include "common.tplvalues.render" (dict "value" .Values.nodeSelector "context" $) | nindent 8 }} + {{- end }} + {{- include "image.pullSecrets" $ | nindent 6 }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - envFrom: + - configMapRef: + name: {{ template "agent-assist-test-api-bridge.fullname" . }} + env: + - name: AGENT_ASSIST_SOCKET_API_PASSPHRASE + valueFrom: + secretKeyRef: + name: {{ template "agent-assist.backend.apiKey" . }} + key: api-key + image: "{{ .Values.loadTestingMode.testApiBridge.image.repository }}:{{ .Values.loadTestingMode.testApiBridge.image.tag | default .Chart.AppVersion }}" + name: {{ .Chart.Name }}-app + ports: + - name: app + containerPort: {{ int .Values.loadTestingMode.testApiBridge.service.internalPort }} + imagePullPolicy: {{ .Values.loadTestingMode.testApiBridge.image.pullPolicy }} + {{- with .Values.loadTestingMode.testApiBridge.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} +status: {} +{{- end }} \ No newline at end of file diff --git a/templates/test-api-bridge/test-api-bridge-ingress.yaml b/templates/test-api-bridge/test-api-bridge-ingress.yaml new file mode 100644 index 0000000..43ef894 --- /dev/null +++ b/templates/test-api-bridge/test-api-bridge-ingress.yaml @@ -0,0 +1,52 @@ +{{- if .Values.loadTestingMode.enabled -}} +{{- if .Values.loadTestingMode.testApiBridge.ingress.enabled -}} +{{- $ingressApiIsStable := eq (include "grafana.ingress.isStable" .) "true" -}} +{{- $ingressSupportsPathType := eq (include "grafana.ingress.supportsPathType" .) "true" -}} +apiVersion: {{ include "grafana.ingress.apiVersion" . }} +kind: Ingress +metadata: + name: {{ include "agent-assist-test-api-bridge.fullname" . }} + labels: + {{- include "agent-assist.selectorLabels" . | nindent 4 }} + {{- if .Values.loadTestingMode.testApiBridge.ingress.annotations }} + annotations: + {{- range $key, $value := .Values.loadTestingMode.testApiBridge.ingress.annotations }} + {{ $key }}: {{ tpl $value $ | quote }} + {{- end }} + {{- end }} +spec: + ingressClassName: traefik + {{- if .Values.loadTestingMode.testApiBridge.ingress.tls }} + tls: + {{- range .Values.loadTestingMode.testApiBridge.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- range .Values.loadTestingMode.testApiBridge.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + {{- if $ingressSupportsPathType }} + pathType: {{ .pathType }} + {{- end }} + backend: + {{- if $ingressApiIsStable }} + service: + name: {{ .backend.service.name }} + port: + number: {{ int .backend.service.port.number }} + {{- else }} + serviceName: {{ .backend.service.name }} + servicePort: {{ int .backend.service.port.number }} + {{- end }} + {{- end }} + {{- end }} + {{- end }} +{{- end }} \ No newline at end of file diff --git a/templates/test-api-bridge/test-api-bridge-service.yaml b/templates/test-api-bridge/test-api-bridge-service.yaml new file mode 100644 index 0000000..40ce30b --- /dev/null +++ b/templates/test-api-bridge/test-api-bridge-service.yaml @@ -0,0 +1,24 @@ +{{- if .Values.loadTestingMode.enabled -}} +apiVersion: v1 +kind: Service +metadata: + name: {{ template "agent-assist-test-api-bridge.fullname" . }} + creationTimestamp: null + labels: + {{- include "agent-assist.selectorLabels" . | nindent 8 }} + {{- with .Values.loadTestingMode.testApiBridge.service.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + ports: + - name: {{ .Values.loadTestingMode.testApiBridge.service.name | quote}} + port: {{ int .Values.loadTestingMode.testApiBridge.service.internalPort }} + targetPort: {{ int .Values.loadTestingMode.testApiBridge.service.targetPort }} + type: {{ .Values.loadTestingMode.testApiBridge.service.type }} + selector: + {{- include "agent-assist.selectorLabels" . | nindent 4 }} + role: test-api-bridge +status: + loadBalancer: {} +{{- end }} \ No newline at end of file diff --git a/values.schema.json b/values.schema.json new file mode 100644 index 0000000..e413e3c --- /dev/null +++ b/values.schema.json @@ -0,0 +1,629 @@ +{ + "$schema": "http://json-schema.org/schema#", + "type": "object", + "required": [ + "frontend", + "backend", + "podAnnotations", + "podSecurityContext", + "securityContext", + "loadTestingMode" + ], + "properties": { + "imageCredentials": { + "description": "Docker registry credentials", + "type": "object", + "properties": { + "registry": { + "description": "URL for the registry", + "type": "string" + }, + "username": { + "description": "Username for the registry", + "type": "string" + }, + "password": { + "description": "Password for the registry", + "type": "string" + }, + "pullSecrets": { + "description": "Existing secrets for the registry login", + "type": "array" + } + }, + "anyOf": [ + { + "required": ["registry", "username", "password"] + }, + { + "required": ["pullSecrets"] + } + ] + }, + "backend": { + "type": "object", + "required": [ + "replica", + "image", + "service", + "ingress", + "resources", + "configmap" + ], + "properties": { + "image": { + "type": "object", + "required": ["repository", "pullPolicy", "tag"], + "properties": { + "repository": { + "type": "string", + "pattern": "^[a-z0-9-_/.]+$" + }, + "pullPolicy": { + "type": "string", + "pattern": "^(Always|Never|IfNotPresent)$" + }, + "tag": { + "type": "string" + } + } + }, + "replica": { + "type": "number" + }, + "service": { + "type": "object", + "required": [ + "name", + "internalPort", + "targetPort", + "type", + "annotations" + ], + "properties": { + "name": { + "type": "string" + }, + "internalPort": { + "type": "number" + }, + "targetPort": { + "type": "number" + }, + "type": { + "type": "string", + "enum": ["ExternalName", "ClusterIP", "NodePort", "LoadBalancer"] + }, + "annotations": { + "type": "object" + } + } + }, + "ingress": { + "type": "object", + "required": ["enabled", "hosts"], + "properties": { + "enabled": { + "type": "boolean" + }, + "annotations": { + "type": "object" + }, + "hosts": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": ["host", "paths"], + "properties": { + "host": { + "type": "string" + }, + "paths": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": ["path", "pathType"], + "properties": { + "path": { + "type": "string" + }, + "pathType": { + "type": "string", + "enum": ["ImplementationSpecific", "Prefix", "Exact"] + } + } + } + } + } + } + }, + "tls": { + "type": "array", + "items": { + "type": "object", + "required": ["secretName"], + "properties": { + "secretName": { + "type": "string" + }, + "hosts": { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1 + } + } + } + } + } + }, + "resources": { + "type": "object", + "properties": { + "limits": { + "type": "object", + "properties": { + "cpu": { + "type": "string" + }, + "memory": { + "type": "string" + } + } + }, + "requests": { + "type": "object", + "properties": { + "cpu": { + "type": "string" + }, + "memory": { + "type": "string" + } + } + } + } + }, + "health": { + "type": "object", + "properties": { + "port": { + "type": "number" + } + } + }, + "metrics": { + "type": "object", + "properties": { + "port": { + "type": "number" + } + } + }, + "apiKey": { + "type": "object", + "properties": { + "existingSecret": { + "type": "string" + }, + "value": { + "type": "string" + } + } + }, + "configmap": { + "type": "object", + "properties": { + "FRONTEND_URL": { + "type": "string" + } + } + } + } + }, + "frontend": { + "type": "object", + "required": ["enabled"], + "properties": { + "enabled": { + "type": "boolean" + }, + "image": { + "type": "object", + "required": ["repository", "pullPolicy", "tag"], + "properties": { + "repository": { + "type": "string", + "pattern": "^[a-z0-9-_/.]+$" + }, + "pullPolicy": { + "type": "string", + "pattern": "^(Always|Never|IfNotPresent)$" + }, + "tag": { + "type": "string" + } + } + }, + "replica": { + "type": "number" + }, + "service": { + "type": "object", + "required": [ + "name", + "internalPort", + "targetPort", + "type", + "annotations" + ], + "properties": { + "name": { + "type": "string" + }, + "internalPort": { + "type": "number" + }, + "targetPort": { + "type": "number" + }, + "type": { + "type": "string", + "enum": ["ExternalName", "ClusterIP", "NodePort", "LoadBalancer"] + }, + "annotations": { + "type": "object" + } + } + }, + "ingress": { + "type": "object", + "required": ["enabled", "hosts"], + "properties": { + "enabled": { + "type": "boolean" + }, + "annotations": { + "type": "object" + }, + "hosts": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": ["host", "paths"], + "properties": { + "host": { + "type": "string" + }, + "paths": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": ["path", "pathType"], + "properties": { + "path": { + "type": "string" + }, + "pathType": { + "type": "string", + "enum": ["ImplementationSpecific", "Prefix", "Exact"] + } + } + } + } + } + } + }, + "tls": { + "type": "array", + "items": { + "type": "object", + "required": ["secretName"], + "properties": { + "secretName": { + "type": "string" + }, + "hosts": { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1 + } + } + } + } + } + }, + "resources": { + "type": "object", + "properties": { + "limits": { + "type": "object", + "properties": { + "cpu": { + "type": "string" + }, + "memory": { + "type": "string" + } + } + }, + "requests": { + "type": "object", + "properties": { + "cpu": { + "type": "string" + }, + "memory": { + "type": "string" + } + } + } + } + }, + "configmap": { + "type": "object", + "properties": {} + } + } + }, + "loadTestingMode": { + "type": "object", + "required": ["enabled"], + "properties": { + "enabled": { + "type": "boolean" + }, + "testApiBridge": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "image": { + "type": "object", + "required": ["repository", "pullPolicy", "tag"], + "properties": { + "repository": { + "type": "string", + "pattern": "^[a-z0-9-_/.]+$" + }, + "pullPolicy": { + "type": "string", + "pattern": "^(Always|Never|IfNotPresent)$" + }, + "tag": { + "type": "string" + } + } + }, + "replica": { + "type": "number" + }, + "service": { + "type": "object", + "required": [ + "name", + "internalPort", + "targetPort", + "type", + "annotations" + ], + "properties": { + "name": { + "type": "string" + }, + "internalPort": { + "type": "number" + }, + "targetPort": { + "type": "number" + }, + "type": { + "type": "string", + "enum": [ + "ExternalName", + "ClusterIP", + "NodePort", + "LoadBalancer" + ] + }, + "annotations": { + "type": "object" + } + } + }, + "ingress": { + "type": "object", + "required": ["enabled", "hosts"], + "properties": { + "enabled": { + "type": "boolean" + }, + "annotations": { + "type": "object" + }, + "hosts": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": ["host", "paths"], + "properties": { + "host": { + "type": "string" + }, + "paths": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": ["path", "pathType"], + "properties": { + "path": { + "type": "string" + }, + "pathType": { + "type": "string", + "enum": [ + "ImplementationSpecific", + "Prefix", + "Exact" + ] + } + } + } + } + } + } + }, + "tls": { + "type": "array", + "items": { + "type": "object", + "required": ["secretName"], + "properties": { + "secretName": { + "type": "string" + }, + "hosts": { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1 + } + } + } + } + } + }, + "resources": { + "type": "object", + "properties": { + "limits": { + "type": "object", + "properties": { + "cpu": { + "type": "string" + }, + "memory": { + "type": "string" + } + } + }, + "requests": { + "type": "object", + "properties": { + "cpu": { + "type": "string" + }, + "memory": { + "type": "string" + } + } + } + } + }, + "configmap": { + "type": "object", + "properties": {} + } + } + } + } + }, + "nameOverride": { + "type": "string" + }, + "fullnameOverride": { + "type": "string" + }, + "podAnnotations": { + "type": "object", + "properties": { + "create": { + "type": "boolean" + }, + "annotations": { + "type": "object" + }, + "name": { + "type": "string" + } + } + }, + "podSecurityContext": { + "type": "object", + "properties": { + "fsGroup": { + "type": "number" + } + } + }, + "securityContext": { + "properties": { + "allowPrivilegeEscalation": { + "type": "boolean" + }, + "capabilities": { + "type": "object" + }, + "privileged": { + "type": "boolean" + }, + "readOnlyRootFilesystem": { + "type": "boolean" + }, + "runAsGroup": { + "type": "integer" + }, + "runAsNonRoot": { + "type": "boolean" + }, + "runAsUser": { + "type": "integer" + } + } + }, + "nodeSelector": { + "type": "object", + "properties": { + "kubernetes.io/hostname": { + "type": "string" + } + } + }, + "tolerations": { + "type": "array", + "items": { + "type": "object", + "properties": { + "key": { + "type": "string" + }, + "operator": { + "type": "string", + "enum": ["Equal", "Exists", "GreaterThan", "LessThan"] + }, + "value": { + "type": "string" + }, + "effect": { + "type": "string", + "enum": ["NoSchedule", "Prevent", "Allow"] + }, + "tolerationSeconds": { + "type": "integer" + } + } + } + } + } +} diff --git a/values.yaml b/values.yaml new file mode 100644 index 0000000..8c74847 --- /dev/null +++ b/values.yaml @@ -0,0 +1,264 @@ +# Default values for the Cognigy Agent Assist Helm chart. This is a YAML-formatted file. +# --- IMPORTANT --- +# Do not edit this file directly, create a new one instead with +# the values that need to be overridden. For more information, head over to +# https://docs.cognigy.com/live-agent/installation/deployment/installation-using-helm/ +# --- + +# Override the chart name +nameOverride: "" +# Override the full app name +fullnameOverride: "" + +podAnnotations: {} +podSecurityContext: + {} + # fsGroup: 2000 + +securityContext: + {} + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 + +## Credentials for pulling image from private image registry. +## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ +## NOTE 1: Either clear text credentials (registry, username and password) or pullSecrets must be provided. +imageCredentials: + ## Alternatively specify the username, password and the url of the private registry. + ## A kubernetes.io/dockerconfigjson type secret named "cognigy-registry-token" will be created based on these information. + registry: "cognigy.azurecr.io" + username: "" + password: "" + + ## Alternatively specify an array of imagePullSecrets. + ## Secrets must be manually created in the proper namespace beforehand. + ## Example: + ## pullSecrets: + ## - cognigyRegistrySecretName + ## + ## NOTE: When registry, username and password all are set, the pullSecrets are ignored. + pullSecrets: [] + +## If neither Cognigy MongoDB Helm Chart (https://github.com/Cognigy/cognigy-mongodb-helm-chart) nor MongoDB Atlas is used +## set "mongodb.enabled": false. It will skip user initialization tasks. However, you will need to create users/passwords manually +mongodb: + enabled: true + ## MongoDB connection scheme, for Cognigy MongoDB Helm Chart use scheme: "mongodb", for MongoDB Atlas use scheme: "mongodb+srv" + scheme: "mongodb" + ## Parameters for MongoDB connection, leave empty for Cognigy MongoDB Helm Chart + ## For MongoDB Atlas set params: "?retryWrites=true&w=majority" + params: "" + ## This MongoDB user and password must have the permission to create users and databases, so normally it is admin or root + ## It does NOT have to be root user. We use these key names to be compatible with Bitnami MongoDB Helm Chert + auth: + rootUser: root + rootPassword: "" + ## Optionally provide the name of an existing secret with MongoDB credentials. Mandatory keys: `username` and `password`, that contains the value + ## of "rootUser" and "rootPassword" + ## NOTE: When it's set the previous parameters "rootUser" and "rootPassword" are ignored. + ## + existingSecret: "" + ## MongoDB Atlas cluster parameters + atlas: + # Specify MongoDB Atlas projectId and clusterName + projectId: "" + clusterName: "" + # Specify publicAPIKey and privateAPIKey with "Project Owner" permissions for the Project in which MongoDB Cluster is located + ## For more information refer to MongoDB Atlas documentation: https://www.mongodb.com/docs/atlas/configure-api-access/#grant-programmatic-access-to-service + publicAPIKey: "" + privateAPIKey: "" + ## Optionally provide the name of an existing secret with MongoDB Atlas credentials. Mandatory keys: `projectid`, `clustername`, `apikeypublic` and `apikeyprivate` + ## that contains the value of MongoDB Atlas projectId, clusterName, publicAPIKey and privateAPIKey respectively. + ## NOTE: When it's set the previous parameters "projectId", "clusterName", "publicAPIKey" and "privateAPIKey" are ignored. + ## + existingSecret: "" + # Connection string for MongoDB replica set deployed with Cognigy MongoDB Helm Chart into 3 availability zones: + # hosts: mongodb-0.mongodb-headless.mongodb.svc.cluster.local:27017,mongodb-1.mongodb-headless.mongodb.svc.cluster.local:27017,mongodb-2.mongodb-headless.mongodb.svc.cluster.local:27017 + # Connection string for development and testing with a single replica MongoDB deployed with Cognigy MongoDB Helm Chart: + # hosts: "mongodb-0.mongodb-headless.mongodb.svc.cluster.local:27017" + # For MongoDB Atlas use the connection string of your MongoDB Atlas Cluster + hosts: mongodb-0.mongodb-headless.mongodb.svc.cluster.local:27017,mongodb-1.mongodb-headless.mongodb.svc.cluster.local:27017,mongodb-2.mongodb-headless.mongodb.svc.cluster.local:27017 + ## db-init generator image + ## + dbinit: + image: cognigy.azurecr.io/mongodb:5.0.16-debian-11-r3 + securityContext: {} + +redis: + enabled: true + nameOverride: redis + auth: + existingSecret: "agent-assist-redis-password" + existingSecretPasswordKey: "REDIS_PASSWORD" + master: + persistence: + enabled: true + +backend: + image: + repository: cognigy.azurecr.io/agent-assist-backend + pullPolicy: IfNotPresent + tag: "b30aab6e6d1e0048dc7670129689323b23c10221-1683280180" + + replica: 3 + + service: + name: cognigy-agent-assist-backend + internalPort: 8000 + targetPort: 8000 + type: ClusterIP + annotations: + {} + # For example + # service.beta.kubernetes.io/aws-load-balancer-type: external + # service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip + # service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing + + ingress: + enabled: true + hosts: + - host: "" + paths: + - path: / + pathType: Prefix + backend: + service: + # Same value as service.name above + name: cognigy-agent-assist-backend + port: + number: 8000 + # tls: + # - secretName: chart-example-tls + # hosts: + # - chart-example.local + + # Health configuration + health: + port: 8001 + + # Metrics configuration + metrics: + port: 8002 + + # The API access token to use for authenticating the Backend with the Cognigy.AI endpoint + cognigyServiceEndpointApiAccessToken: + # Existing secret must have `api-access-token` as secret key + existingSecret: "" + # Optionally create the secret by setting the api key here (not recommended) + # value: "" + + # The API access token to use for authenticating the Backend with the Cognigy.AI handover endpoint + cognigyServiceHandoverApiAccessToken: + # Existing secret must have `api-access-token` as secret key + existingSecret: "" + # Optionally create the secret by setting the api key here (not recommended) + # value: "" + + # The API key to use for authenticating the Frontend with the Backend + apiKey: + # Existing secret must have `api-key` as secret key + existingSecret: "" + # Optionally create the secret by setting the api key here (not recommended) + # value: "" + + # Config map env variables + configmap: + # Allowed origins for CORS + ALLOWED_ORIGINS: "" + # URL of the Agent Assist Frontend + FRONTEND_URL: "" + # Redis reconnect configuration + REDIS_ENABLE_RECONNECT: "true" + # Cognigy.AI integration (same for service-handover/service-endpoint) + COGNIGY_AI_ENDPOINT_URL_WITH_PROTOCOL: "" + resources: + {} + # We usually recommend not to specify default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + # limits: + # cpu: 1000m + # memory: 600Mi + # requests: + # cpu: 1000m + # memory: 400Mi + +frontend: + # Possibility to use the BE in headless mode + enabled: true + image: + repository: cognigy.azurecr.io/agent-assist-frontend + pullPolicy: IfNotPresent + tag: "b742161378ff644cf1bd9470e894ba4ded97c59b-1683894008" + + replica: 3 + + service: + name: cognigy-agent-assist-frontend + internalPort: 8000 + targetPort: 8000 + type: ClusterIP + annotations: + {} + # For example + # service.beta.kubernetes.io/aws-load-balancer-type: external + # service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip + # service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing + + ingress: + enabled: true + hosts: + - host: "" + paths: + - path: / + pathType: Prefix + backend: + service: + # Same value as service.name above + name: cognigy-agent-assist-frontend + port: + number: 8000 + # tls: + # - secretName: chart-example-tls + # hosts: + # - chart-example.local + + # Health configuration + health: + port: 8001 + + # Metrics configuration + metrics: + port: 8002 + resources: + {} + # We usually recommend not to specify default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + # limits: + # cpu: 1000m + # memory: 600Mi + # requests: + # cpu: 1000m + # memory: 400Mi + + configmap: + # Google maps api token to have maps available for locations + # GOOGLE_MAPS_API_TOKEN: "" + +monitoring: + enabled: false + +# For testing purposes only +loadTestingMode: + enabled: false + +nodeSelector: {} + +tolerations: [] diff --git a/values_dev.yaml b/values_dev.yaml new file mode 100644 index 0000000..6521153 --- /dev/null +++ b/values_dev.yaml @@ -0,0 +1,155 @@ +podAnnotations: {} + +podSecurityContext: + {} + # fsGroup: 2000 + +securityContext: + {} + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 + +imageCredentials: + registry: "cognigy.azurecr.io" + username: "" + password: "" + +mongodb: + enabled: true + auth: + rootUser: root + rootPassword: "" + hosts: mongodb-0.mongodb-headless.mongodb.svc.cluster.local:27017 + +backend: + image: + repository: cognigydevelopment.azurecr.io/agent-assist-backend + pullPolicy: IfNotPresent + tag: "abcdf680c478e56d0a1cd58f0d9af2833010ca33-1662397710" + replica: 1 + + service: + name: cognigy-agent-assist-backend + internalPort: 8000 + targetPort: 8000 + type: ClusterIP + annotations: + {} + # For example + # service.beta.kubernetes.io/aws-load-balancer-type: external + # service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip + # service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing + + ingress: + enabled: true + hosts: + - host: "agent-assist-api" + paths: + - path: / + pathType: Prefix + backend: + service: + # Same value as service.name above + name: cognigy-agent-assist-backend + port: + number: 8000 + # tls: + # - secretName: chart-example-tls + # hosts: + # - chart-example.local + + # The API access token to use for authenticating the Backend with the Cognigy.AI endpoint + cognigyServiceEndpointApiAccessToken: + # Existing secret must have `api-access-token` as secret key + # existingSecret: "" + # Optionally create the secret by setting the api key here (not recommended) + value: "" + + cognigyServiceHandoverApiAccessToken: + # Existing secret must have `api-access-token` as secret key + # existingSecret: "" + # Optionally create the secret by setting the api key here (not recommended) + value: "" + + apiKey: + existingSecret: cognigy-agent-assist-api-key + # Optionally create the secret by setting the api key here (not recommended) + # value: '' + + # Config map env variables + configmap: + FRONTEND_URL: "http://agent-assist.test" + resources: + {} + # We usually recommend not to specify default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + # limits: + # cpu: 1000m + # memory: 600Mi + # requests: + # cpu: 1000m + # memory: 400Mi + +frontend: + image: + repository: cognigydevelopment.azurecr.io/agent-assist-frontend + pullPolicy: IfNotPresent + tag: "d6563f67dc8bf9b4ae082c0f50e5b78c62c07e4b-1662492536" + + replica: 1 + + service: + name: cognigy-agent-assist-frontend + internalPort: 8000 + targetPort: 8000 + type: ClusterIP + annotations: + {} + # For example + # service.beta.kubernetes.io/aws-load-balancer-type: external + # service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip + # service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing + + ingress: + enabled: true + hosts: + - host: "agent-assist-ui.test" + paths: + - path: / + pathType: Prefix + backend: + service: + # Same value as service.name above + name: cognigy-agent-assist-frontend + port: + number: 8000 + # tls: + # - secretName: chart-example-tls + # hosts: + # - chart-example.local + + # Config map env variables + configmap: {} + + resources: + {} + # We usually recommend not to specify default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + # limits: + # cpu: 1000m + # memory: 600Mi + # requests: + # cpu: 1000m + # memory: 400Mi + +nodeSelector: {} + +tolerations: [] diff --git a/values_prod.yaml b/values_prod.yaml new file mode 100644 index 0000000..e92b0e4 --- /dev/null +++ b/values_prod.yaml @@ -0,0 +1,124 @@ +fullnameOverride: "cognigy-agent-assist" +mongodb: + enabled: true + auth: + rootUser: root + rootPassword: "" + # Connection string for MongoDB replica set deployed with Cognigy MongoDB Helm Chart into 3 availability zones: + hosts: mongodb-0.mongodb-headless.mongodb.svc.cluster.local:27017,mongodb-1.mongodb-headless.mongodb.svc.cluster.local:27017,mongodb-2.mongodb-headless.mongodb.svc.cluster.local:27017 +imageCredentials: + registry: "cognigy.azurecr.io" + username: "" + password: "" +redis: + enabled: true + master: + resources: + limits: + cpu: 500m + memory: 512Mi + requests: + cpu: 200m + memory: 100Mi + persistence: + enabled: true + size: 1Gi + replica: + replicaCount: 1 + resources: + limits: + cpu: 500m + memory: 512Mi + requests: + cpu: 200m + memory: 100Mi + persistence: + enabled: true + size: 1Gi + metrics: + enabled: true + serviceMonitor: + enabled: true +backend: + image: + pullPolicy: IfNotPresent + service: + name: cognigy-agent-assist-backend + internalPort: 8000 + targetPort: 8000 + type: ClusterIP + annotations: {} + ingress: + enabled: true + hosts: + - host: "" + paths: + - path: / + pathType: Prefix + backend: + service: + # Same value as service.name above + name: cognigy-agent-assist-backend + port: + number: 8000 + tls: + - hosts: + - "" + secretName: cognigy-traefik + cognigyServiceEndpointApiAccessToken: + existingSecret: cognigy-service-endpoint-api-access-token-aa + cognigyServiceHandoverApiAccessToken: + existingSecret: cognigy-service-handover-api-access-token-aa + apiKey: + existingSecret: agent-assist-api-key + configmap: + FRONTEND_URL: "" + ALLOWED_ORIGINS: "" + COGNIGY_AI_ENDPOINT_URL_WITH_PROTOCOL: "" + resources: + limits: + cpu: 1000m + memory: 600Mi + requests: + cpu: 800m + memory: 400Mi +frontend: + enabled: true + image: + pullPolicy: IfNotPresent + service: + name: cognigy-agent-assist-frontend + internalPort: 8000 + targetPort: 8000 + type: ClusterIP + annotations: {} + ingress: + enabled: true + hosts: + - host: "" + paths: + - path: / + pathType: Prefix + backend: + service: + # Same value as service.name above + name: cognigy-agent-assist-frontend + port: + number: 8000 + tls: + # Careful with the indentation + # For more information, see https://helm.sh/docs/chart_template_guide/yaml_techniques/#strings-in-yaml + - hosts: + - "" + secretName: cognigy-traefik + configmap: + GOOGLE_MAPS_API_TOKEN: "" + resources: + limits: + cpu: 600m + memory: 600Mi + requests: + cpu: 400m + memory: 400Mi +monitoring: + enabled: true