diff --git a/charts/aconext/.gitignore b/charts/aconext/.gitignore new file mode 100644 index 0000000..19e9e18 --- /dev/null +++ b/charts/aconext/.gitignore @@ -0,0 +1 @@ +test-values.yaml \ No newline at end of file diff --git a/charts/aconext/.helmignore b/charts/aconext/.helmignore new file mode 100644 index 0000000..0e8a0eb --- /dev/null +++ b/charts/aconext/.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/aconext/Chart.lock b/charts/aconext/Chart.lock new file mode 100644 index 0000000..1fbb3c7 --- /dev/null +++ b/charts/aconext/Chart.lock @@ -0,0 +1,12 @@ +dependencies: +- name: postgresql + repository: https://charts.bitnami.com/bitnami + version: 18.1.13 +- name: redis + repository: https://charts.bitnami.com/bitnami + version: 24.0.0 +- name: rabbitmq + repository: https://charts.bitnami.com/bitnami + version: 16.0.14 +digest: sha256:a6dacb3ae4b421677920042468a45a83db2632a5dc4e2ff8fce26738c28ace94 +generated: "2025-12-02T21:17:35.634297+02:00" diff --git a/charts/aconext/Chart.yaml b/charts/aconext/Chart.yaml new file mode 100644 index 0000000..7ad5f59 --- /dev/null +++ b/charts/aconext/Chart.yaml @@ -0,0 +1,22 @@ +apiVersion: v2 +name: aconext +description: A Helm chart for Acontext deployment on Kubernetes + +type: application + +version: 0.0.1 +appVersion: "0.0.1" + +dependencies: + - name: postgresql + repository: https://charts.bitnami.com/bitnami + version: 18.1.13 + condition: postgresql.enabled + - name: redis + repository: https://charts.bitnami.com/bitnami + version: 24.0.0 + condition: redis.enabled + - name: rabbitmq + repository: https://charts.bitnami.com/bitnami + version: 16.0.14 + condition: rabbitmq.enabled diff --git a/charts/aconext/charts/postgresql-18.1.13.tgz b/charts/aconext/charts/postgresql-18.1.13.tgz new file mode 100644 index 0000000..fa0ba84 Binary files /dev/null and b/charts/aconext/charts/postgresql-18.1.13.tgz differ diff --git a/charts/aconext/charts/rabbitmq-16.0.14.tgz b/charts/aconext/charts/rabbitmq-16.0.14.tgz new file mode 100644 index 0000000..c223603 Binary files /dev/null and b/charts/aconext/charts/rabbitmq-16.0.14.tgz differ diff --git a/charts/aconext/charts/redis-24.0.0.tgz b/charts/aconext/charts/redis-24.0.0.tgz new file mode 100644 index 0000000..3890f57 Binary files /dev/null and b/charts/aconext/charts/redis-24.0.0.tgz differ diff --git a/charts/aconext/templates/NOTES.txt b/charts/aconext/templates/NOTES.txt new file mode 100644 index 0000000..cf1001c --- /dev/null +++ b/charts/aconext/templates/NOTES.txt @@ -0,0 +1,62 @@ +1. Get the application URL by running these commands: +{{- if contains "NodePort" .Values.api.service.type }} + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "aconext.api.name" . }}) + export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + echo "API URL: http://$NODE_IP:$NODE_PORT" +{{- else if contains "LoadBalancer" .Values.api.service.type }} + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + You can watch its status by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "aconext.api.name" . }}' + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "aconext.api.name" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") + echo "API URL: http://$SERVICE_IP:{{ .Values.api.service.port }}" +{{- else if contains "ClusterIP" .Values.api.service.type }} + export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "aconext.name" . }}-api,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 }} + +2. Check service status: + kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/instance={{ .Release.Name }}" + kubectl get svc --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/instance={{ .Release.Name }}" + +3. View logs: + kubectl logs --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "aconext.name" . }}-api" --tail=100 + kubectl logs --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "aconext.name" . }}-core" --tail=100 + +4. Service endpoints: +{{- if .Values.core.enabled }} + Core Service: {{ include "aconext.core.name" . }}:{{ .Values.core.service.port }} +{{- end }} +{{- if .Values.api.enabled }} + API Service: {{ include "aconext.api.name" . }}:{{ .Values.api.service.port }} +{{- end }} + +5. Dependencies status: +{{- if .Values.postgresql.enabled }} + PostgreSQL: Enabled (internal) +{{- else }} + PostgreSQL: External ({{ .Values.external.postgresql.host }}:{{ .Values.external.postgresql.port }}) +{{- end }} +{{- if .Values.redis.enabled }} + Redis: Enabled (internal) +{{- else }} + Redis: External ({{ .Values.external.redis.host }}:{{ .Values.external.redis.port }}) +{{- end }} +{{- if .Values.rabbitmq.enabled }} + RabbitMQ: Enabled (internal) +{{- else }} + RabbitMQ: External ({{ .Values.external.rabbitmq.host }}:{{ .Values.external.rabbitmq.port }}) +{{- end }} + +{{- if and .Values.httpRoute.enabled .Values.httpRoute.gateway.name }} +{{- if .Values.httpRoute.hostnames }} +6. API Health Check: +{{- range .Values.httpRoute.hostnames }} + Health Check: http://{{ . }}/health +{{- end }} +{{- else }} +6. API Health Check: + Health Check: http:///health + (Note: Configure httpRoute.hostnames to get the full URL) +{{- end }} +{{- end }} diff --git a/charts/aconext/templates/_helpers.tpl b/charts/aconext/templates/_helpers.tpl new file mode 100644 index 0000000..4db756b --- /dev/null +++ b/charts/aconext/templates/_helpers.tpl @@ -0,0 +1,227 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "aconext.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 "aconext.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "aconext.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "aconext.labels" -}} +helm.sh/chart: {{ include "aconext.chart" . }} +{{ include "aconext.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "aconext.selectorLabels" -}} +app.kubernetes.io/name: {{ include "aconext.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the core service account to use +*/}} +{{- define "aconext.core.serviceAccountName" -}} +{{- if .Values.core.serviceAccount.create }} +{{- default (include "aconext.core.name" .) .Values.core.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.core.serviceAccount.name }} +{{- end }} +{{- end }} + +{{/* +Create the name of the api service account to use +*/}} +{{- define "aconext.api.serviceAccountName" -}} +{{- if .Values.api.serviceAccount.create }} +{{- default (include "aconext.api.name" .) .Values.api.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.api.serviceAccount.name }} +{{- end }} +{{- end }} + +{{/* +Core service name +*/}} +{{- define "aconext.core.name" -}} +{{- printf "%s-core" (include "aconext.fullname" .) | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +API service name +*/}} +{{- define "aconext.api.name" -}} +{{- printf "%s-api" (include "aconext.fullname" .) | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Core selector labels +*/}} +{{- define "aconext.core.selectorLabels" -}} +app.kubernetes.io/name: {{ include "aconext.name" . }}-core +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +API selector labels +*/}} +{{- define "aconext.api.selectorLabels" -}} +app.kubernetes.io/name: {{ include "aconext.name" . }}-api +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Get PostgreSQL host +*/}} +{{- define "aconext.postgresql.host" -}} +{{- if .Values.postgresql.enabled }} +{{- printf "%s-postgresql" .Release.Name }} +{{- else }} +{{- .Values.external.postgresql.host }} +{{- end }} +{{- end }} + +{{/* +Get PostgreSQL port +*/}} +{{- define "aconext.postgresql.port" -}} +{{- if .Values.postgresql.enabled }} +{{- "5432" }} +{{- else }} +{{- .Values.external.postgresql.port | default "5432" | quote }} +{{- end }} +{{- end }} + +{{/* +Get Redis host +*/}} +{{- define "aconext.redis.host" -}} +{{- if .Values.redis.enabled }} +{{- printf "%s-redis-master" .Release.Name }} +{{- else }} +{{- .Values.external.redis.host }} +{{- end }} +{{- end }} + +{{/* +Get Redis port +*/}} +{{- define "aconext.redis.port" -}} +{{- if .Values.redis.enabled }} +{{- "6379" }} +{{- else }} +{{- .Values.external.redis.port | default "6379" | quote }} +{{- end }} +{{- end }} + +{{/* +Get RabbitMQ host +*/}} +{{- define "aconext.rabbitmq.host" -}} +{{- if .Values.rabbitmq.enabled }} +{{- printf "%s-rabbitmq" .Release.Name }} +{{- else }} +{{- .Values.external.rabbitmq.host }} +{{- end }} +{{- end }} + +{{/* +Get RabbitMQ port +*/}} +{{- define "aconext.rabbitmq.port" -}} +{{- if .Values.rabbitmq.enabled }} +{{- "5672" }} +{{- else }} +{{- .Values.external.rabbitmq.port | default "5672" | quote }} +{{- end }} +{{- end }} + +{{/* +Get PostgreSQL database URL +*/}} +{{- define "aconext.postgresql.url" -}} +{{- $host := include "aconext.postgresql.host" . }} +{{- $port := include "aconext.postgresql.port" . }} +{{- $user := .Values.postgresql.auth.username | default .Values.external.postgresql.username }} +{{- $password := .Values.postgresql.auth.password | default .Values.external.postgresql.password }} +{{- $database := .Values.postgresql.auth.database | default .Values.external.postgresql.database }} +{{- printf "postgresql://%s:%s@%s:%s/%s" $user $password $host $port $database }} +{{- end }} + +{{/* +Get Redis URL +*/}} +{{- define "aconext.redis.url" -}} +{{- $host := include "aconext.redis.host" . }} +{{- $port := include "aconext.redis.port" . }} +{{- $password := .Values.redis.auth.password | default .Values.external.redis.password }} +{{- if $password }} +{{- printf "redis://:%s@%s:%s" $password $host $port }} +{{- else }} +{{- printf "redis://%s:%s" $host $port }} +{{- end }} +{{- end }} + +{{/* +Get RabbitMQ URL +*/}} +{{- define "aconext.rabbitmq.url" -}} +{{- $host := include "aconext.rabbitmq.host" . }} +{{- $port := include "aconext.rabbitmq.port" . }} +{{- $user := .Values.rabbitmq.auth.username | default .Values.external.rabbitmq.username }} +{{- $password := .Values.rabbitmq.auth.password | default .Values.external.rabbitmq.password }} +{{- $vhost := .Values.rabbitmq.auth.vhost | default .Values.external.rabbitmq.vhost | default "/" }} +{{- printf "amqp://%s:%s@%s:%s/%s" $user $password $host $port $vhost }} +{{- end }} + +{{/* +Generate env entries from core.env +*/}} +{{- define "aconext.core.env" -}} +{{- range $key, $value := .Values.core.env }} +- name: {{ $key }} + value: {{ $value | quote }} +{{- end }} +{{- end }} + +{{/* +Generate env entries from api.env +*/}} +{{- define "aconext.api.env" -}} +{{- range $key, $value := .Values.api.env }} +- name: {{ $key }} + value: {{ $value | quote }} +{{- end }} +{{- end }} diff --git a/charts/aconext/templates/api-deployment.yaml b/charts/aconext/templates/api-deployment.yaml new file mode 100644 index 0000000..d30ff00 --- /dev/null +++ b/charts/aconext/templates/api-deployment.yaml @@ -0,0 +1,153 @@ +{{- if .Values.api.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "aconext.api.name" . }} + labels: + {{- include "aconext.labels" . | nindent 4 }} + app.kubernetes.io/component: api +spec: + replicas: {{ .Values.api.replicaCount }} + selector: + matchLabels: + {{- include "aconext.api.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.api.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "aconext.api.selectorLabels" . | nindent 8 }} + {{- with .Values.api.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "aconext.api.serviceAccountName" . }} + {{- with .Values.api.podSecurityContext }} + securityContext: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: api + {{- with .Values.api.securityContext }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} + image: "{{ .Values.api.image.repository }}:{{ .Values.api.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.api.image.pullPolicy }} + ports: + - name: http + containerPort: {{ .Values.api.service.port }} + protocol: TCP + env: + {{- if .Values.secrets.api.rootBearerToken }} + - name: ROOT_API_BEARER_TOKEN + valueFrom: + secretKeyRef: + name: {{ include "aconext.api.name" . }}-secret + key: ROOT_API_BEARER_TOKEN + {{- end }} + - name: DATABASE_HOST + value: {{ include "aconext.postgresql.host" . | quote }} + - name: DATABASE_EXPORT_PORT + value: {{ include "aconext.postgresql.port" . | quote }} + - name: DATABASE_USER + value: {{ if .Values.postgresql.enabled }}{{ .Values.postgresql.auth.username | quote }}{{ else }}{{ .Values.external.postgresql.username | quote }}{{ end }} + {{- if .Values.postgresql.enabled }} + - name: DATABASE_PASSWORD + value: {{ .Values.postgresql.auth.password | quote }} + {{- else }} + - name: DATABASE_PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "aconext.fullname" . }}-postgresql-secret + key: password + {{- end }} + - name: DATABASE_NAME + value: {{ if .Values.postgresql.enabled }}{{ .Values.postgresql.auth.database | quote }}{{ else }}{{ .Values.external.postgresql.database | quote }}{{ end }} + - name: REDIS_HOST + value: {{ include "aconext.redis.host" . | quote }} + - name: REDIS_EXPORT_PORT + value: {{ include "aconext.redis.port" . | quote }} + {{- if .Values.redis.enabled }} + - name: REDIS_PASSWORD + value: {{ .Values.redis.auth.password | quote }} + {{- else if .Values.secrets.redis.password }} + - name: REDIS_PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "aconext.fullname" . }}-redis-secret + key: password + {{- end }} + - name: RABBITMQ_HOST + value: {{ include "aconext.rabbitmq.host" . | quote }} + - name: RABBITMQ_EXPORT_PORT + value: {{ include "aconext.rabbitmq.port" . | quote }} + - name: RABBITMQ_USER + value: {{ if .Values.rabbitmq.enabled }}{{ .Values.rabbitmq.auth.username | quote }}{{ else }}{{ .Values.external.rabbitmq.username | quote }}{{ end }} + {{- if .Values.rabbitmq.enabled }} + - name: RABBITMQ_PASSWORD + value: {{ .Values.rabbitmq.auth.password | quote }} + {{- else }} + - name: RABBITMQ_PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "aconext.fullname" . }}-rabbitmq-secret + key: password + {{- end }} + - name: RABBITMQ_VHOST + value: {{ if .Values.rabbitmq.enabled }}{{ .Values.rabbitmq.auth.vhost | default "/" | quote }}{{ else }}{{ .Values.external.rabbitmq.vhost | default "/" | quote }}{{ end }} + - name: RABBITMQ_VHOST_ENCODED + value: {{ if .Values.rabbitmq.enabled }}{{ .Values.rabbitmq.auth.vhost | default "/" | urlquery | quote }}{{ else }}{{ .Values.external.rabbitmq.vhost | default "/" | urlquery | quote }}{{ end }} + {{- if .Values.secrets.s3.accessKey }} + - name: S3_ACCESS_KEY + valueFrom: + secretKeyRef: + name: {{ include "aconext.fullname" . }}-s3-secret + key: S3_ACCESS_KEY + {{- end }} + {{- if .Values.secrets.s3.secretKey }} + - name: S3_SECRET_KEY + valueFrom: + secretKeyRef: + name: {{ include "aconext.fullname" . }}-s3-secret + key: S3_SECRET_KEY + {{- end }} + - name: CORE_BASE_URL + value: http://{{ include "aconext.core.name" . }}:{{ .Values.core.service.port }} + {{- include "aconext.api.env" . | nindent 12 }} + {{- if and .Values.api.env.S3_ENDPOINT (not .Values.api.env.S3_INTERNAL_ENDPOINT) }} + - name: S3_INTERNAL_ENDPOINT + value: {{ .Values.api.env.S3_ENDPOINT | quote }} + {{- end }} + {{- with .Values.api.livenessProbe }} + livenessProbe: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.api.readinessProbe }} + readinessProbe: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.api.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.api.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.api.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.api.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} +{{- end }} + diff --git a/charts/aconext/templates/api-service.yaml b/charts/aconext/templates/api-service.yaml new file mode 100644 index 0000000..5253b97 --- /dev/null +++ b/charts/aconext/templates/api-service.yaml @@ -0,0 +1,19 @@ +{{- if .Values.api.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "aconext.api.name" . }} + labels: + {{- include "aconext.labels" . | nindent 4 }} + app.kubernetes.io/component: api +spec: + type: {{ .Values.api.service.type }} + ports: + - port: {{ .Values.api.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "aconext.api.selectorLabels" . | nindent 4 }} +{{- end }} + diff --git a/charts/aconext/templates/api-serviceaccount.yaml b/charts/aconext/templates/api-serviceaccount.yaml new file mode 100644 index 0000000..347f0cc --- /dev/null +++ b/charts/aconext/templates/api-serviceaccount.yaml @@ -0,0 +1,15 @@ +{{- if and .Values.api.enabled .Values.api.serviceAccount.create }} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "aconext.api.serviceAccountName" . }} + labels: + {{- include "aconext.labels" . | nindent 4 }} + app.kubernetes.io/component: api + {{- with .Values.api.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +automountServiceAccountToken: {{ .Values.api.serviceAccount.automount }} +{{- end }} + diff --git a/charts/aconext/templates/core-deployment.yaml b/charts/aconext/templates/core-deployment.yaml new file mode 100644 index 0000000..c2d9bca --- /dev/null +++ b/charts/aconext/templates/core-deployment.yaml @@ -0,0 +1,138 @@ +{{- if .Values.core.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "aconext.core.name" . }} + labels: + {{- include "aconext.labels" . | nindent 4 }} + app.kubernetes.io/component: core +spec: + replicas: {{ .Values.core.replicaCount }} + selector: + matchLabels: + {{- include "aconext.core.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.core.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "aconext.core.selectorLabels" . | nindent 8 }} + {{- with .Values.core.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "aconext.core.serviceAccountName" . }} + {{- with .Values.core.podSecurityContext }} + securityContext: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- if and .Values.postgresql.enabled .Values.postgresql.auth.postgresPassword }} + initContainers: + - name: init-vector-extension + image: {{ .Values.postgresql.image.registry }}/{{ .Values.postgresql.image.repository }}:{{ .Values.postgresql.image.tag }} + imagePullPolicy: {{ .Values.postgresql.image.pullPolicy }} + command: + - /bin/bash + - -ec + - | + export PGHOST={{ include "aconext.postgresql.host" . }} + export PGPORT={{ include "aconext.postgresql.port" . }} + export PGUSER=postgres + export PGPASSWORD=$POSTGRES_PASSWORD + export PGDATABASE={{ .Values.postgresql.auth.database | quote }} + + echo "Waiting for PostgreSQL to be ready..." + until PGPASSWORD="$POSTGRES_PASSWORD" psql -h "$PGHOST" -p "$PGPORT" -U "$PGUSER" -d postgres -c '\q' 2>/dev/null; do + echo "PostgreSQL is unavailable - sleeping" + sleep 2 + done + + echo "Creating vector extension in database $PGDATABASE..." + PGPASSWORD="$POSTGRES_PASSWORD" psql -h "$PGHOST" -p "$PGPORT" -U "$PGUSER" -d "$PGDATABASE" -c "CREATE EXTENSION IF NOT EXISTS vector;" || echo "Extension may already exist or failed to create" + + echo "Vector extension initialization completed!" + env: + - name: PGHOST + value: {{ include "aconext.postgresql.host" . | quote }} + - name: PGPORT + value: {{ include "aconext.postgresql.port" . | quote }} + - name: PGUSER + value: "postgres" + - name: POSTGRES_PASSWORD + value: {{ .Values.postgresql.auth.postgresPassword | quote }} + - name: PGDATABASE + value: {{ .Values.postgresql.auth.database | quote }} + {{- end }} + containers: + - name: core + {{- with .Values.core.securityContext }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} + image: "{{ .Values.core.image.repository }}:{{ .Values.core.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.core.image.pullPolicy }} + ports: + - name: http + containerPort: {{ .Values.core.service.port }} + protocol: TCP + env: + - name: DATABASE_URL + value: {{ include "aconext.postgresql.url" . | quote }} + - name: REDIS_URL + value: {{ include "aconext.redis.url" . | quote }} + - name: MQ_URL + value: {{ include "aconext.rabbitmq.url" . | quote }} + {{- if .Values.secrets.llm.apiKey }} + - name: LLM_API_KEY + valueFrom: + secretKeyRef: + name: {{ include "aconext.core.name" . }}-secret + key: LLM_API_KEY + {{- end }} + {{- if .Values.secrets.s3.accessKey }} + - name: S3_ACCESS_KEY + valueFrom: + secretKeyRef: + name: {{ include "aconext.fullname" . }}-s3-secret + key: S3_ACCESS_KEY + {{- end }} + {{- if .Values.secrets.s3.secretKey }} + - name: S3_SECRET_KEY + valueFrom: + secretKeyRef: + name: {{ include "aconext.fullname" . }}-s3-secret + key: S3_SECRET_KEY + {{- end }} + {{- include "aconext.core.env" . | nindent 12 }} + {{- with .Values.core.livenessProbe }} + livenessProbe: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.core.readinessProbe }} + readinessProbe: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.core.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.core.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.core.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.core.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} +{{- end }} + diff --git a/charts/aconext/templates/core-service.yaml b/charts/aconext/templates/core-service.yaml new file mode 100644 index 0000000..c475188 --- /dev/null +++ b/charts/aconext/templates/core-service.yaml @@ -0,0 +1,19 @@ +{{- if .Values.core.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "aconext.core.name" . }} + labels: + {{- include "aconext.labels" . | nindent 4 }} + app.kubernetes.io/component: core +spec: + type: {{ .Values.core.service.type }} + ports: + - port: {{ .Values.core.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "aconext.core.selectorLabels" . | nindent 4 }} +{{- end }} + diff --git a/charts/aconext/templates/core-serviceaccount.yaml b/charts/aconext/templates/core-serviceaccount.yaml new file mode 100644 index 0000000..963b14d --- /dev/null +++ b/charts/aconext/templates/core-serviceaccount.yaml @@ -0,0 +1,15 @@ +{{- if and .Values.core.enabled .Values.core.serviceAccount.create }} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "aconext.core.serviceAccountName" . }} + labels: + {{- include "aconext.labels" . | nindent 4 }} + app.kubernetes.io/component: core + {{- with .Values.core.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +automountServiceAccountToken: {{ .Values.core.serviceAccount.automount }} +{{- end }} + diff --git a/charts/aconext/templates/httproute.yaml b/charts/aconext/templates/httproute.yaml new file mode 100644 index 0000000..6363b5c --- /dev/null +++ b/charts/aconext/templates/httproute.yaml @@ -0,0 +1,77 @@ +{{- if and .Values.httpRoute.enabled .Values.httpRoute.gateway.name }} +{{- $gatewayNamespace := .Values.httpRoute.gateway.namespace | default .Release.Namespace }} +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: {{ include "aconext.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "aconext.labels" . | nindent 4 }} + app.kubernetes.io/component: httproute +spec: + parentRefs: + - name: {{ .Values.httpRoute.gateway.name }} + namespace: {{ $gatewayNamespace }} + sectionName: http +{{- if .Values.httpRoute.hostnames }} + hostnames: + {{- toYaml .Values.httpRoute.hostnames | nindent 4 }} +{{- end }} + rules: +{{- if .Values.httpRoute.tls.enabled }} + # Redirect HTTP to HTTPS when TLS is enabled + - matches: + - path: + type: PathPrefix + value: "/" + filters: + - type: RequestRedirect + requestRedirect: + scheme: https + statusCode: 301 +{{- else if and .Values.httpRoute.routes.api.enabled .Values.api.enabled }} + # Route HTTP traffic to API service (when TLS is disabled) + - matches: + - path: + type: PathPrefix + value: {{ .Values.httpRoute.routes.api.path | quote }} + backendRefs: + - name: {{ include "aconext.api.name" . }} + namespace: {{ .Release.Namespace }} + port: {{ .Values.api.service.port }} + weight: 100 +{{- end }} +{{- if .Values.httpRoute.tls.enabled }} +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: {{ include "aconext.fullname" . }}-https + namespace: {{ .Release.Namespace }} + labels: + {{- include "aconext.labels" . | nindent 4 }} + app.kubernetes.io/component: httproute-https +spec: + parentRefs: + - name: {{ .Values.httpRoute.gateway.name }} + namespace: {{ $gatewayNamespace }} + sectionName: https +{{- if .Values.httpRoute.hostnames }} + hostnames: + {{- toYaml .Values.httpRoute.hostnames | nindent 4 }} +{{- end }} + rules: +{{- if and .Values.httpRoute.routes.api.enabled .Values.api.enabled }} + - matches: + - path: + type: PathPrefix + value: {{ .Values.httpRoute.routes.api.path | quote }} + backendRefs: + - name: {{ include "aconext.api.name" . }} + namespace: {{ .Release.Namespace }} + port: {{ .Values.api.service.port }} + weight: 100 +{{- end }} +{{- end }} +{{- end }} diff --git a/charts/aconext/templates/secret.yaml b/charts/aconext/templates/secret.yaml new file mode 100644 index 0000000..9fcbf33 --- /dev/null +++ b/charts/aconext/templates/secret.yaml @@ -0,0 +1,99 @@ +{{- if .Values.core.enabled }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "aconext.core.name" . }}-secret + labels: + {{- include "aconext.labels" . | nindent 4 }} + app.kubernetes.io/component: core +type: Opaque +data: + {{- if .Values.secrets.llm.apiKey }} + LLM_API_KEY: {{ .Values.secrets.llm.apiKey | b64enc }} + {{- end }} +{{- end }} +--- +{{- if .Values.api.enabled }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "aconext.api.name" . }}-secret + labels: + {{- include "aconext.labels" . | nindent 4 }} + app.kubernetes.io/component: api +type: Opaque +data: + {{- if .Values.secrets.api.rootBearerToken }} + ROOT_API_BEARER_TOKEN: {{ .Values.secrets.api.rootBearerToken | b64enc }} + {{- end }} +{{- end }} +--- +{{- if not .Values.postgresql.enabled }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "aconext.fullname" . }}-postgresql-secret + labels: + {{- include "aconext.labels" . | nindent 4 }} + app.kubernetes.io/component: postgresql +type: Opaque +data: + {{- if .Values.secrets.postgresql.password }} + password: {{ .Values.secrets.postgresql.password | b64enc }} + {{- end }} + {{- if .Values.secrets.postgresql.postgresPassword }} + postgres-password: {{ .Values.secrets.postgresql.postgresPassword | b64enc }} + {{- end }} +{{- end }} +--- +{{- if not .Values.redis.enabled }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "aconext.fullname" . }}-redis-secret + labels: + {{- include "aconext.labels" . | nindent 4 }} + app.kubernetes.io/component: redis +type: Opaque +data: + {{- if .Values.secrets.redis.password }} + password: {{ .Values.secrets.redis.password | b64enc }} + {{- end }} +{{- end }} +--- +{{- if not .Values.rabbitmq.enabled }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "aconext.fullname" . }}-rabbitmq-secret + labels: + {{- include "aconext.labels" . | nindent 4 }} + app.kubernetes.io/component: rabbitmq +type: Opaque +data: + {{- if .Values.secrets.rabbitmq.password }} + password: {{ .Values.secrets.rabbitmq.password | b64enc }} + {{- end }} + {{- if .Values.secrets.rabbitmq.erlangCookie }} + erlang-cookie: {{ .Values.secrets.rabbitmq.erlangCookie | b64enc }} + {{- end }} +{{- end }} +--- +{{- if or .Values.core.enabled .Values.api.enabled }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "aconext.fullname" . }}-s3-secret + labels: + {{- include "aconext.labels" . | nindent 4 }} + app.kubernetes.io/component: s3 +type: Opaque +data: + {{- if .Values.secrets.s3.accessKey }} + S3_ACCESS_KEY: {{ .Values.secrets.s3.accessKey | b64enc }} + {{- end }} + {{- if .Values.secrets.s3.secretKey }} + S3_SECRET_KEY: {{ .Values.secrets.s3.secretKey | b64enc }} + {{- end }} +{{- end }} + diff --git a/charts/aconext/values.yaml b/charts/aconext/values.yaml new file mode 100644 index 0000000..613be71 --- /dev/null +++ b/charts/aconext/values.yaml @@ -0,0 +1,276 @@ +# Global settings +nameOverride: "" +fullnameOverride: "" + +# Image pull secrets +imagePullSecrets: [] + +# Core service configuration +core: + enabled: true + replicaCount: 1 + serviceAccount: + create: true + automount: true + annotations: {} + name: "" # If not set, will use {{ release-name }}-core + image: + repository: ghcr.io/memodb-io/acontext-core + tag: "latest" + pullPolicy: Always + + service: + type: ClusterIP + port: 8000 + + env: + # LLM Configuration + LLM_BASE_URL: "" # Optional: Custom LLM base URL + LLM_SIMPLE_MODEL: "gpt-4.1" # Default LLM model + LLM_SDK: "openai" # LLM SDK: "openai" or "anthropic" + + # S3 Configuration (AWS S3) + S3_ENDPOINT: "" # AWS S3 endpoint, e.g., https://s3.amazonaws.com or https://s3.us-east-1.amazonaws.com + S3_REGION: "us-east-1" # AWS S3 region + S3_BUCKET: "acontext-assets" # S3 bucket name + + resources: + limits: + cpu: 1000m + memory: 2Gi + requests: + cpu: 500m + memory: 1Gi + + livenessProbe: + httpGet: + path: /health + port: 8000 + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + + readinessProbe: + httpGet: + path: /health + port: 8000 + initialDelaySeconds: 10 + periodSeconds: 5 + timeoutSeconds: 3 + failureThreshold: 3 + + # Pod annotations and labels + podAnnotations: {} + podLabels: {} + + # Security contexts + podSecurityContext: {} + securityContext: {} + + # Node selector, tolerations, affinity + nodeSelector: {} + tolerations: [] + affinity: {} + +# API service configuration +api: + enabled: true + replicaCount: 1 + serviceAccount: + create: true + automount: true + annotations: {} + name: "" # If not set, will use {{ release-name }}-api + image: + repository: ghcr.io/memodb-io/acontext-api + tag: "latest" + pullPolicy: Always + + service: + type: ClusterIP + port: 8029 + + env: + # API Configuration + API_EXPORT_PORT: "8029" + + # S3 Configuration (AWS S3) + S3_ENDPOINT: "" # AWS S3 endpoint + S3_REGION: "us-east-2" # AWS region + S3_BUCKET: "acontext-assets" # S3 bucket name + + resources: + limits: + cpu: 1000m + memory: 2Gi + requests: + cpu: 500m + memory: 1Gi + + livenessProbe: + httpGet: + path: /health + port: 8029 + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + + readinessProbe: + httpGet: + path: /health + port: 8029 + initialDelaySeconds: 10 + periodSeconds: 5 + timeoutSeconds: 3 + failureThreshold: 3 + + # Pod annotations and labels + podAnnotations: {} + podLabels: {} + + # Security contexts + podSecurityContext: {} + securityContext: {} + + # Node selector, tolerations, affinity + nodeSelector: {} + tolerations: [] + affinity: {} + +# PostgreSQL configuration (Bitnami chart) +# Set enabled: false to use external PostgreSQL +postgresql: + enabled: true + image: + registry: "docker.io" + repository: "bitnamilegacy/postgresql" + tag: "17.6.0-debian-12-r4" + pullPolicy: Always + auth: + username: "acontext" + password: "acontext" + database: "acontext" + postgresPassword: "postgres" + persistence: + enabled: true + # Fix for read-only filesystem error: disable readOnlyRootFilesystem + # PostgreSQL needs to create lock files in /var/run/postgresql/ + primary: + containerSecurityContext: + readOnlyRootFilesystem: false + +# Redis configuration (Bitnami chart) +# Set enabled: false to use external Redis +redis: + enabled: true + image: + registry: "docker.io" + repository: "bitnamilegacy/redis" + tag: "8.2.1-debian-12-r0" + pullPolicy: Always + auth: + enabled: true + password: "acontext" + persistence: + enabled: true + +# RabbitMQ configuration (Bitnami chart) +# Set enabled: false to use external RabbitMQ +rabbitmq: + enabled: true + image: + registry: "docker.io" + repository: "bitnamilegacy/rabbitmq" + tag: "4.1.3-debian-12-r1" + pullPolicy: Always + debug: true + auth: + username: "acontext" + password: "acontext" + persistence: + enabled: true + volumePermissions: + enabled: false + image: + repository: "bitnamilegacy/os-shell" + tag: "12-debian-12-r50" + pullPolicy: IfNotPresent + +# External service configuration (when not using internal services) +# When using external services, set the corresponding internal service (postgresql/redis/rabbitmq) enabled: false +# and configure the external service details below. +# Note: For production, use external-secrets operator or similar tools to inject passwords securely. +external: + postgresql: + host: "" # External PostgreSQL host (e.g., rds-instance.region.rds.amazonaws.com) + port: 5432 + username: "acontext" + database: "acontext" + + redis: + host: "" # External Redis host (e.g., redis-cluster.cache.amazonaws.com) + port: 6379 + + rabbitmq: + host: "" # External RabbitMQ host + port: 5672 + username: "acontext" + vhost: "/" # RabbitMQ virtual host + +# Secrets configuration +# For production, use external-secrets operator or similar tools to inject these values securely +# TODO: use external-secrets operator +secrets: + # PostgreSQL secrets (used when using external PostgreSQL) + # These are also used to override internal PostgreSQL passwords if needed + postgresql: + password: "" + postgresPassword: "" + + # Redis secrets (used when using external Redis) + # Also used to override internal Redis password if needed + redis: + password: "" + + # RabbitMQ secrets (used when using external RabbitMQ) + # Also used to override internal RabbitMQ credentials if needed + rabbitmq: + password: "" + erlangCookie: "" + + # Application secrets + llm: + apiKey: "" # LLM API key (OpenAI or Anthropic) + + api: + rootBearerToken: "" # Root API bearer token for admin operations + + s3: + accessKey: "" # AWS S3 access key + secretKey: "" # AWS S3 secret key + + +# TOOD: opentelemetry collector + +# HTTPRoute configuration (Gateway API) +# HTTPRoute is used to define routing rules for the Gateway API +httpRoute: + enabled: false + # Gateway name and namespace where the HTTPRoute will be attached + gateway: + name: "" # Gateway name (e.g., "main-gateway") + namespace: "" # Gateway namespace (defaults to release namespace if empty) + # Hostnames for the routes + hostnames: [] # e.g., ["api.example.com"] + # Routes configuration + routes: + # API routes + api: + enabled: true + path: "/" # Path prefix for API routes (matches / and all subpaths) + # TLS configuration + tls: + # Rememer to create correct Gateway resource with HTTPS listener, https://gateway-api.sigs.k8s.io/guides/http-redirect-rewrite/#http-to-https-redirects + enabled: false