diff --git a/charts/galust-ai-layer/.helmignore b/charts/galust-ai-layer/.helmignore new file mode 100644 index 0000000..414bb6e --- /dev/null +++ b/charts/galust-ai-layer/.helmignore @@ -0,0 +1,18 @@ +# Patterns to ignore when building packages. +.DS_Store +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +*.swp +*.bak +*.tmp +*.orig +*~ +.project +.idea/ +*.tmproj +.vscode/ diff --git a/charts/galust-ai-layer/Chart.lock b/charts/galust-ai-layer/Chart.lock new file mode 100644 index 0000000..84099ba --- /dev/null +++ b/charts/galust-ai-layer/Chart.lock @@ -0,0 +1,15 @@ +dependencies: +- name: base + repository: https://dasmeta.github.io/helm + version: 0.3.29 +- name: base + repository: https://dasmeta.github.io/helm + version: 0.3.29 +- name: base + repository: https://dasmeta.github.io/helm + version: 0.3.29 +- name: base + repository: https://dasmeta.github.io/helm + version: 0.3.29 +digest: sha256:0f058458a7e18d9bb5c9b227c8a7d29712f572efa84f5fad9fa19e314d1d6ab5 +generated: "2026-05-12T09:59:51.72808+04:00" diff --git a/charts/galust-ai-layer/Chart.yaml b/charts/galust-ai-layer/Chart.yaml new file mode 100644 index 0000000..2ec1f71 --- /dev/null +++ b/charts/galust-ai-layer/Chart.yaml @@ -0,0 +1,28 @@ +apiVersion: v2 +name: galust-ai-layer +description: Galust AI layer umbrella chart for Kubernetes clusters +type: application +version: 0.1.0 +appVersion: "0.1.0" + +dependencies: + - name: base + alias: backend + version: 0.3.29 + repository: https://dasmeta.github.io/helm + condition: backend.enabled + - name: base + alias: mcp + version: 0.3.29 + repository: https://dasmeta.github.io/helm + condition: mcp.enabled + - name: base + alias: mcpUseCase + version: 0.3.29 + repository: https://dasmeta.github.io/helm + condition: mcpUseCase.enabled + - name: base + alias: orchestrator + version: 0.3.29 + repository: https://dasmeta.github.io/helm + condition: orchestrator.enabled diff --git a/charts/galust-ai-layer/README.md b/charts/galust-ai-layer/README.md new file mode 100644 index 0000000..68b0071 --- /dev/null +++ b/charts/galust-ai-layer/README.md @@ -0,0 +1,377 @@ +# Galust AI Layer + +Umbrella Helm chart for deploying the Galust AI layer stack into a Kubernetes or EKS cluster. + +## What This Chart Deploys + +This chart is an umbrella chart for the Galust AI layer services. It wraps the published `dasmeta/base` chart once per component and deploys: + +- Strapi backend +- MCP +- MCP use-case service +- Orchestrator + +The chart manages Kubernetes workload configuration for these services. It does not provision cloud infrastructure, databases, DNS records, TLS issuers, IAM roles, ECR policies, or external secrets. + +## Components + +The chart wraps the published `dasmeta/base` chart with one alias per deployable component: + +| Component | Values key | Default | +| --- | --- | --- | +| Strapi backend | `backend.enabled` | `true` | +| MCP | `mcp.enabled` | `true` | +| MCP use-case service | `mcpUseCase.enabled` | `true` | +| Orchestrator | `orchestrator.enabled` | `true` | + +Each component can be disabled independently: + +```bash +helm upgrade --install galust-ai-layer ./charts/galust-ai-layer \ + -n ai-layer \ + --create-namespace \ + --set mcpUseCase.enabled=false +``` + +## Prerequisites + +Before deploying, confirm the target cluster has: + +- Kubernetes access through a working `kubectl` context. +- Helm 3 installed locally. +- Access to the `dasmeta` Helm repository, because this chart depends on `dasmeta/base`. +- AWS access to the target account, usually through an AWS SSO permission set and account assignment managed outside this chart. +- Namespace access for `ai-layer`, or permission to create it. +- Image pull access for the private ECR images. +- ECR read access for the private repositories used by the backend, MCP, MCP use-case, and orchestrator images. +- Required application secrets already created in the namespace. +- Database connectivity for the backend. +- A PVC or storage class suitable for backend uploads. +- Ingress controller installed if ingress is enabled. +- DNS records pointing the public hosts to the ingress/load balancer. +- TLS/cert-manager setup if the default TLS annotations and secrets are used. + +If AWS access is managed through the Terraform SSO/RBAC modules, create or assign an AWS SSO permission set before deploying this chart. The permission set should allow the operator or automation role to: + +- Read private ECR repositories and get ECR authorization tokens. +- Access the target EKS cluster and update Kubernetes resources in the `ai-layer` namespace. +- Create or update Kubernetes Secrets used by the chart, including `ecr-secret`, `ai-layer-strapi`, `db-ai-layer-strapi`, `ai-layer-mcp`, `ai-layer-mcp-use-case`, and `ai-layer-orchestrator`. +- If `ecrCredentialsRefresh.enabled=true`, provide an AWS identity for the refresh job with `ecr:GetAuthorizationToken`. + +Required default Kubernetes objects: + +| Object | Default name | Used by | +| --- | --- | --- | +| Docker registry pull secret | `ecr-secret` | all components | +| Backend app secret | `ai-layer-strapi` | backend | +| Backend DB host secret | `db-ai-layer-strapi`, key `host` | backend | +| Backend DB port | `backend.config.DATABASE_PORT`, default `5432` | backend | +| Backend DB password secret | `db-ai-layer-strapi`, key `password` | backend | +| Backend uploads PVC | `ai-layer-strapi-uploads` | backend | +| MCP secret | `ai-layer-mcp` | MCP | +| MCP use-case secret | `ai-layer-mcp-use-case` | MCP use-case | +| Orchestrator secret | `ai-layer-orchestrator` | orchestrator | + +External dependencies such as Redis, Qdrant, Langfuse, OpenAI credentials, database provisioning, External Secrets, IAM trust, and DNS are handled outside this chart. + +## Orchestrator Secret + +The orchestrator reads sensitive values from a Kubernetes Secret through `envFrom.secret`. + +Default values: + +```yaml +orchestrator: + envFrom: + secret: ai-layer-orchestrator +``` + +This means every key in the `ai-layer-orchestrator` Secret is exposed to the orchestrator container as an environment variable. The default secret should contain: + +| Secret key | Purpose | +| --- | --- | +| `OPENAI_API_KEY` | OpenAI API access | +| `LANGFUSE_PUBLIC_KEY` | Langfuse public key | +| `LANGFUSE_SECRET_KEY` | Langfuse secret key | +| `CLOUDBROWSER_API_TOKEN` | Cloud browser access | +| `FIRECRAWL_API_KEY` | Firecrawl API access | +| `SENTRY_DSN` | Sentry project DSN | +| `AI_LAYER_BACKEND_API_TOKEN` | Backend API token | +| `MCP_USE_CASE_AUTH_TOKEN` | MCP use-case auth token | +| `SUPPORT_CHAT_CORE_PRODUCT_UID` | Support chat core product UID | + +Create or update the secret before deploying: + +```bash +kubectl create secret generic ai-layer-orchestrator \ + -n ai-layer \ + --from-literal=OPENAI_API_KEY='' \ + --from-literal=LANGFUSE_PUBLIC_KEY='' \ + --from-literal=LANGFUSE_SECRET_KEY='' \ + --from-literal=CLOUDBROWSER_API_TOKEN='' \ + --from-literal=FIRECRAWL_API_KEY='' \ + --from-literal=SENTRY_DSN='' \ + --from-literal=AI_LAYER_BACKEND_API_TOKEN='' \ + --from-literal=MCP_USE_CASE_AUTH_TOKEN='' \ + --from-literal=SUPPORT_CHAT_CORE_PRODUCT_UID='' \ + --dry-run=client -o yaml | kubectl apply -f - +``` + +Use a different secret name if needed: + +```bash +helm upgrade --install galust-ai-layer charts/galust-ai-layer \ + -n ai-layer \ + --create-namespace \ + --set orchestrator.envFrom.secret=my-orchestrator-secret +``` + +## Configure Public URLs + +Public URLs are defined near the top of `values.yaml`: + +```yaml +global: + API_URL: &apiUrl https://api.galust.ai + API_HOST: &apiHost api.galust.ai + APP_URL: &appUrl https://app.galust.ai + MCP_URL: &mcpUrl https://mcp.galust.ai + MCP_HOST: &mcpHost mcp.galust.ai + ADMIN_URL: &adminUrl https://api.galust.ai/admin + OPENAPI_BASE_URL: &openapiBaseUrl https://api.galust.ai/api + OPENAPI_SPEC_URL: &openapiSpecUrl https://api.galust.ai/documentation/openapi.json + ORCHESTRATOR_ENDPOINT: &orchestratorEndpoint https://api.galust.ai/orchestrator/api/galust/ask +``` + +The rest of `values.yaml` reuses these values with YAML anchors, for example: + +```yaml +backend: + config: + ADMIN_URL: *adminUrl + BACKEND_URL: *apiUrl + ingress: + hosts: + - host: *apiHost +``` + +Important: YAML anchors are resolved before Helm merges extra values files. If you deploy with `-f examples/galust-ai-layer/values.test.yaml` and only override `global`, Helm will not recalculate aliases already resolved in `values.yaml`. To change domains for another environment, either edit the full values file or provide a values file that also overrides the component `config` and `ingress` fields. + +## Image Pull Access + +The default values expect an existing Kubernetes docker registry secret named `ecr-secret`. The secret name is anchored once and reused by every component: + +```yaml +imagePullSecret: + name: &imagePullSecretName ecr-secret + +imagePullSecrets: + - name: *imagePullSecretName +``` + +The chart can also render the secret when `imagePullSecret.create=true`: + +```bash +helm upgrade --install galust-ai-layer ./charts/galust-ai-layer \ + -n ai-layer \ + --create-namespace \ + --set imagePullSecret.create=true \ + --set-file imagePullSecret.dockerConfigJson=./dockerconfig.json +``` + +AWS IAM trust, ECR repository policies, role assumption, and External Secrets setup are intentionally outside this chart. Provide the resulting Kubernetes pull secret name through `imagePullSecret.name` and each component's `imagePullSecrets` override. + +ECR authorization tokens expire. For long-running environments, enable the optional refresh CronJob or use another cluster-managed renewal mechanism such as External Secrets, a registry credential controller, or a platform-owned refresh job. This chart can reference an existing pull secret, render one from provided docker config JSON, or refresh the secret through the optional CronJob. + +Example secret creation: + +```bash +kubectl create namespace ai-layer + +kubectl create secret docker-registry ecr-secret \ + -n ai-layer \ + --docker-server=565580475168.dkr.ecr.eu-central-1.amazonaws.com \ + --docker-username=AWS \ + --docker-password='' +``` + +## ECR Credentials Refresh + +The chart can render an optional CronJob that refreshes the ECR docker registry secret before the token expires: + +```yaml +ecrCredentialsRefresh: + enabled: true + schedule: "0 */6 * * *" + registry: 565580475168.dkr.ecr.eu-central-1.amazonaws.com + region: eu-central-1 + secretName: ecr-secret +``` + +The refresh job updates the same `kubernetes.io/dockerconfigjson` Secret referenced by component `imagePullSecrets`. It creates a namespaced `Role`, `RoleBinding`, `ServiceAccount`, and `CronJob`. + +Use an existing Kubernetes Secret with AWS credentials: + +```bash +kubectl create secret generic ecr-refresh-aws-credentials \ + -n ai-layer \ + --from-literal=AWS_ACCESS_KEY_ID='' \ + --from-literal=AWS_SECRET_ACCESS_KEY='' \ + --dry-run=client -o yaml | kubectl apply -f - +``` + +Then enable the refresher: + +```bash +helm upgrade --install galust-ai-layer charts/galust-ai-layer \ + -n ai-layer \ + --create-namespace \ + --set ecrCredentialsRefresh.enabled=true \ + --set ecrCredentialsRefresh.awsCredentialsSecret.name=ecr-refresh-aws-credentials +``` + +If the cluster uses IRSA or EKS Pod Identity, leave `awsCredentialsSecret.name` empty and annotate the refresh service account: + +```yaml +ecrCredentialsRefresh: + enabled: true + serviceAccount: + annotations: + eks.amazonaws.com/role-arn: arn:aws:iam:::role/ +``` + +The AWS identity used by the refresh job needs permission to call `ecr:GetAuthorizationToken`. The refresh container uses `alpine` and installs `aws-cli`, `curl`, `ca-certificates`, and `coreutils` at runtime, so the namespace must allow outbound package download access or you must override `ecrCredentialsRefresh.image` with an internal image that already contains the required tools. + +After installing, trigger the first refresh before private-image workloads need to pull: + +```bash +kubectl create job \ + -n ai-layer \ + --from=cronjob/galust-ai-layer-ecr-refresh \ + galust-ai-layer-ecr-refresh-manual +``` + +## Backend Notes + +The backend values use the existing Galust Strapi image and runtime defaults from `ai-layer/backend/helm/strapi.yaml`, but this umbrella chart does not provision AWS IAM or a managed database. Database secrets are created outside this chart. By default the backend expects: + +- an `ai-layer-strapi` secret for Strapi application secrets +- `db-ai-layer-strapi` with key `host` +- `backend.config.DATABASE_PORT`, defaulting to `5432` +- `db-ai-layer-strapi` with key `password` +- a PVC named `ai-layer-strapi-uploads` for `/opt/app/public/uploads` + +Override those names for environment-specific infrastructure. + +If the database host and password are stored in one Kubernetes Secret, override the `extraEnv` references: + +```yaml +backend: + extraEnv: + DATABASE_HOST: + secretKeyRef: + name: my-database-secret + key: host + DATABASE_PASSWORD: + secretKeyRef: + name: my-database-secret + key: password +``` + +If the database port is also stored in the same database secret, override `DATABASE_PORT` from `config` with an `extraEnv` secret reference: + +```yaml +backend: + config: + DATABASE_PORT: null + extraEnv: + DATABASE_HOST: + secretKeyRef: + name: db-ai-layer-strapi + key: host + DATABASE_PORT: + secretKeyRef: + name: db-ai-layer-strapi + key: port +``` + +## Deploy + +Run commands from the `helm` repository directory: + +```bash +cd /Users/juliaaghamyan/Desktop/dasmeta/helm +helm dependency update charts/galust-ai-layer +``` + +Deploy with default `values.yaml`: + +```bash +helm upgrade --install galust-ai-layer charts/galust-ai-layer \ + -n ai-layer \ + --create-namespace +``` + +Deploy with the test values example: + +```bash +helm upgrade --install galust-ai-layer charts/galust-ai-layer \ + -n ai-layer \ + --create-namespace \ + -f examples/galust-ai-layer/values.test.yaml +``` + +Disable a component: + +```bash +helm upgrade --install galust-ai-layer charts/galust-ai-layer \ + -n ai-layer \ + --create-namespace \ + --set mcpUseCase.enabled=false +``` + +## Post-Deploy Checks + +```bash +kubectl get pods -n ai-layer +kubectl get svc -n ai-layer +kubectl get ingress -n ai-layer +kubectl describe pod -n ai-layer -l app=ai-layer-orchestrator +kubectl logs -n ai-layer deploy/ai-layer-orchestrator +``` + +Expected default service names: + +- `ai-layer-strapi` +- `ai-layer-mcp` +- `ai-layer-mcp-use-case` +- `ai-layer-orchestrator` + +Expected public hosts when ingress is enabled: + +- `api.galust.ai` +- `mcp.galust.ai` + +## Local Validation + +```bash +helm dependency update charts/galust-ai-layer +helm lint charts/galust-ai-layer +helm template galust-ai-layer charts/galust-ai-layer -n ai-layer +helm template galust-ai-layer charts/galust-ai-layer -n ai-layer --set backend.enabled=false +helm template galust-ai-layer charts/galust-ai-layer -n ai-layer -f examples/galust-ai-layer/values.test.yaml +``` + +## Troubleshooting + +If pods are stuck in `ImagePullBackOff`, check the `ecr-secret` secret and ECR access. + +If the backend fails to start, check the `ai-layer-strapi` and `db-ai-layer-strapi` secrets, plus database reachability from the namespace. + +If ingress does not work, confirm the ingress controller, DNS records, TLS secret or cert-manager issuer, and rendered ingress hosts. + +If URL overrides do not appear in rendered manifests, remember that YAML anchors are not dynamic Helm templates. Render locally with: + +```bash +helm template galust-ai-layer charts/galust-ai-layer -n ai-layer -f examples/galust-ai-layer/values.test.yaml +``` diff --git a/charts/galust-ai-layer/charts/base-0.3.29.tgz b/charts/galust-ai-layer/charts/base-0.3.29.tgz new file mode 100644 index 0000000..e96111f Binary files /dev/null and b/charts/galust-ai-layer/charts/base-0.3.29.tgz differ diff --git a/charts/galust-ai-layer/templates/NOTES.txt b/charts/galust-ai-layer/templates/NOTES.txt new file mode 100644 index 0000000..f7b3194 --- /dev/null +++ b/charts/galust-ai-layer/templates/NOTES.txt @@ -0,0 +1,21 @@ +Galust AI layer chart rendered for namespace {{ .Release.Namespace }}. + +Enabled components: +{{- if .Values.backend.enabled }} +- backend: {{ .Values.backend.fullnameOverride | default "backend" }} +{{- end }} +{{- if .Values.mcp.enabled }} +- mcp: {{ .Values.mcp.fullnameOverride | default "mcp" }} +{{- end }} +{{- if .Values.mcpUseCase.enabled }} +- mcp-use-case: {{ .Values.mcpUseCase.fullnameOverride | default "mcp-use-case" }} +{{- end }} +{{- if .Values.orchestrator.enabled }} +- orchestrator: {{ .Values.orchestrator.fullnameOverride | default "orchestrator" }} +{{- end }} + +Image pull access: +- By default the chart references an existing imagePullSecret named {{ include "galust-ai-layer.imagePullSecretName" . }}. +- Set imagePullSecret.create=true with dockerConfigJson or dockerConfigJsonBase64 to render that Secret from this chart. +- Set ecrCredentialsRefresh.enabled=true to render a CronJob that refreshes the ECR docker registry secret. +- AWS IAM trust, ECR repository policy, and external secret provisioning are outside this chart. diff --git a/charts/galust-ai-layer/templates/_helpers.tpl b/charts/galust-ai-layer/templates/_helpers.tpl new file mode 100644 index 0000000..e4d07bc --- /dev/null +++ b/charts/galust-ai-layer/templates/_helpers.tpl @@ -0,0 +1,54 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "galust-ai-layer.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +*/}} +{{- define "galust-ai-layer.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := include "galust-ai-layer.name" . }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Common labels. +*/}} +{{- define "galust-ai-layer.labels" -}} +helm.sh/chart: {{ printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +app.kubernetes.io/name: {{ include "galust-ai-layer.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Image pull secret name used by the optional generated dockerconfigjson Secret. +*/}} +{{- define "galust-ai-layer.imagePullSecretName" -}} +{{- default "ecr-secret" .Values.imagePullSecret.name | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Name used for the optional ECR credentials refresh resources. +*/}} +{{- define "galust-ai-layer.ecrCredentialsRefreshName" -}} +{{- printf "%s-ecr-refresh" (include "galust-ai-layer.fullname" .) | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Service account name used by the optional ECR credentials refresh job. +*/}} +{{- define "galust-ai-layer.ecrCredentialsRefreshServiceAccountName" -}} +{{- default (include "galust-ai-layer.ecrCredentialsRefreshName" .) .Values.ecrCredentialsRefresh.serviceAccount.name | trunc 63 | trimSuffix "-" }} +{{- end }} diff --git a/charts/galust-ai-layer/templates/ecr-credentials-refresh.yaml b/charts/galust-ai-layer/templates/ecr-credentials-refresh.yaml new file mode 100644 index 0000000..1774674 --- /dev/null +++ b/charts/galust-ai-layer/templates/ecr-credentials-refresh.yaml @@ -0,0 +1,143 @@ +{{- if .Values.ecrCredentialsRefresh.enabled -}} +{{- $name := include "galust-ai-layer.ecrCredentialsRefreshName" . -}} +{{- $serviceAccountName := include "galust-ai-layer.ecrCredentialsRefreshServiceAccountName" . -}} +{{- $secretName := required "ecrCredentialsRefresh.secretName is required when ecrCredentialsRefresh.enabled=true" .Values.ecrCredentialsRefresh.secretName -}} +{{- $registry := required "ecrCredentialsRefresh.registry is required when ecrCredentialsRefresh.enabled=true" .Values.ecrCredentialsRefresh.registry -}} +{{- $region := required "ecrCredentialsRefresh.region is required when ecrCredentialsRefresh.enabled=true" .Values.ecrCredentialsRefresh.region -}} +{{- if .Values.ecrCredentialsRefresh.serviceAccount.create }} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ $serviceAccountName }} + labels: + {{- include "galust-ai-layer.labels" . | nindent 4 }} + {{- with .Values.ecrCredentialsRefresh.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +--- +{{- end }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ $name }} + labels: + {{- include "galust-ai-layer.labels" . | nindent 4 }} +rules: + - apiGroups: + - "" + resources: + - secrets + verbs: + - get + - create + - patch + - update +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ $name }} + labels: + {{- include "galust-ai-layer.labels" . | nindent 4 }} +subjects: + - kind: ServiceAccount + name: {{ $serviceAccountName }} + namespace: {{ .Release.Namespace }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ $name }} +--- +apiVersion: batch/v1 +kind: CronJob +metadata: + name: {{ $name }} + labels: + {{- include "galust-ai-layer.labels" . | nindent 4 }} +spec: + schedule: {{ .Values.ecrCredentialsRefresh.schedule | quote }} + concurrencyPolicy: {{ .Values.ecrCredentialsRefresh.concurrencyPolicy }} + successfulJobsHistoryLimit: {{ .Values.ecrCredentialsRefresh.successfulJobsHistoryLimit }} + failedJobsHistoryLimit: {{ .Values.ecrCredentialsRefresh.failedJobsHistoryLimit }} + jobTemplate: + spec: + template: + metadata: + labels: + {{- include "galust-ai-layer.labels" . | nindent 12 }} + spec: + serviceAccountName: {{ $serviceAccountName }} + restartPolicy: OnFailure + containers: + - name: refresh + image: "{{ .Values.ecrCredentialsRefresh.image.repository }}:{{ .Values.ecrCredentialsRefresh.image.tag }}" + imagePullPolicy: {{ .Values.ecrCredentialsRefresh.image.pullPolicy }} + env: + - name: AWS_REGION + value: {{ $region | quote }} + - name: ECR_REGISTRY + value: {{ $registry | quote }} + - name: DOCKER_SECRET_NAME + value: {{ $secretName | quote }} + - name: TARGET_NAMESPACE + value: {{ .Release.Namespace | quote }} + {{- with .Values.ecrCredentialsRefresh.awsCredentialsSecret.name }} + envFrom: + - secretRef: + name: {{ . }} + {{- end }} + command: + - /bin/sh + - -ec + args: + - | + apk add --no-cache aws-cli curl ca-certificates coreutils + + ECR_PASSWORD="$(aws ecr get-login-password --region "${AWS_REGION}")" + AUTH="$(printf 'AWS:%s' "${ECR_PASSWORD}" | base64 | tr -d '\n')" + DOCKER_CONFIG_JSON="$(printf '{"auths":{"%s":{"username":"AWS","password":"%s","auth":"%s"}}}' "${ECR_REGISTRY}" "${ECR_PASSWORD}" "${AUTH}")" + DOCKER_CONFIG_B64="$(printf '%s' "${DOCKER_CONFIG_JSON}" | base64 | tr -d '\n')" + + cat >/tmp/secret-create.json </tmp/secret-patch.json <