From edde8c38392b70a855c15a450d363a08a4a637ec Mon Sep 17 00:00:00 2001 From: Stavros Kois <47820033+stavros-k@users.noreply.github.com> Date: Wed, 5 Jul 2023 18:42:37 +0300 Subject: [PATCH] NAS-122507 / 23.10 / Add `immich` to `community` train (#1271) * initial commit * add some todo * extend config * remove typesense url * add microservices * add proxy and typesense * ML * fix configmap * fixup config * change range * one more * add some inits * ts dont need to wait for server * wait web * add redis * fix redis * wrong space * type * add redis service * conditional services * fix redis... * add caps * fix capabilities * fix service * add tests * fix conditional pods * fix upgrade_strategy * fix config * lint * whops * update strategy * make robust * cleaner * fix regex * Take a copy of the resources before we start doing modifications * regen commontgz * bump versions * add pullPolicy * regen common * bump version * bump * bump helm * regen common * Update library/ix-dev/community/immich/values.yaml * bump * update resources validation --- .github/workflows/charts_tests.yaml | 4 +- .github/workflows/common_library_tests.yaml | 6 +- library/common/Chart.yaml | 2 +- .../common/templates/app_functions/_redis.tpl | 93 ++++ .../templates/lib/container/_resources.tpl | 2 +- library/common/values.yaml | 5 + library/ix-dev/community/immich/Chart.lock | 6 + library/ix-dev/community/immich/Chart.yaml | 26 ++ library/ix-dev/community/immich/README.md | 3 + library/ix-dev/community/immich/app-readme.md | 3 + .../community/immich/charts/common-1.0.10.tgz | Bin 0 -> 56386 bytes .../community/immich/ci/basic-values.yaml | 22 + .../community/immich/ci/no-extra-values.yaml | 26 ++ .../community/immich/ci/no-ml-values.yaml | 25 ++ .../immich/ci/no-typesenes-values.yaml | 25 ++ library/ix-dev/community/immich/item.yaml | 7 + library/ix-dev/community/immich/metadata.yaml | 14 + .../ix-dev/community/immich/questions.yaml | 410 ++++++++++++++++++ .../community/immich/templates/NOTES.txt | 1 + .../immich/templates/_configuration.tpl | 125 ++++++ .../templates/_immich-machinelearning.tpl | 41 ++ .../templates/_immich-microservices.tpl | 57 +++ .../immich/templates/_immich-proxy.tpl | 48 ++ .../immich/templates/_immich-server.tpl | 51 +++ .../immich/templates/_immich-typesense.tpl | 42 ++ .../immich/templates/_immich-web.tpl | 45 ++ .../immich/templates/_persistence.tpl | 137 ++++++ .../community/immich/templates/_portal.tpl | 12 + .../community/immich/templates/_postgres.tpl | 7 + .../community/immich/templates/_redis.tpl | 6 + .../community/immich/templates/_service.tpl | 105 +++++ .../community/immich/templates/_waitURL.tpl | 17 + .../community/immich/templates/common.yaml | 23 + .../ix-dev/community/immich/upgrade_info.json | 4 + .../ix-dev/community/immich/upgrade_strategy | 58 +++ library/ix-dev/community/immich/values.yaml | 70 +++ 36 files changed, 1521 insertions(+), 7 deletions(-) create mode 100644 library/common/templates/app_functions/_redis.tpl create mode 100644 library/ix-dev/community/immich/Chart.lock create mode 100644 library/ix-dev/community/immich/Chart.yaml create mode 100644 library/ix-dev/community/immich/README.md create mode 100644 library/ix-dev/community/immich/app-readme.md create mode 100644 library/ix-dev/community/immich/charts/common-1.0.10.tgz create mode 100644 library/ix-dev/community/immich/ci/basic-values.yaml create mode 100644 library/ix-dev/community/immich/ci/no-extra-values.yaml create mode 100644 library/ix-dev/community/immich/ci/no-ml-values.yaml create mode 100644 library/ix-dev/community/immich/ci/no-typesenes-values.yaml create mode 100644 library/ix-dev/community/immich/item.yaml create mode 100644 library/ix-dev/community/immich/metadata.yaml create mode 100644 library/ix-dev/community/immich/questions.yaml create mode 100644 library/ix-dev/community/immich/templates/NOTES.txt create mode 100644 library/ix-dev/community/immich/templates/_configuration.tpl create mode 100644 library/ix-dev/community/immich/templates/_immich-machinelearning.tpl create mode 100644 library/ix-dev/community/immich/templates/_immich-microservices.tpl create mode 100644 library/ix-dev/community/immich/templates/_immich-proxy.tpl create mode 100644 library/ix-dev/community/immich/templates/_immich-server.tpl create mode 100644 library/ix-dev/community/immich/templates/_immich-typesense.tpl create mode 100644 library/ix-dev/community/immich/templates/_immich-web.tpl create mode 100644 library/ix-dev/community/immich/templates/_persistence.tpl create mode 100644 library/ix-dev/community/immich/templates/_portal.tpl create mode 100644 library/ix-dev/community/immich/templates/_postgres.tpl create mode 100644 library/ix-dev/community/immich/templates/_redis.tpl create mode 100644 library/ix-dev/community/immich/templates/_service.tpl create mode 100644 library/ix-dev/community/immich/templates/_waitURL.tpl create mode 100644 library/ix-dev/community/immich/templates/common.yaml create mode 100644 library/ix-dev/community/immich/upgrade_info.json create mode 100755 library/ix-dev/community/immich/upgrade_strategy create mode 100644 library/ix-dev/community/immich/values.yaml diff --git a/.github/workflows/charts_tests.yaml b/.github/workflows/charts_tests.yaml index 640fc768f4..3383a842a1 100644 --- a/.github/workflows/charts_tests.yaml +++ b/.github/workflows/charts_tests.yaml @@ -20,7 +20,7 @@ jobs: helm-version: - v3.9.4 - v3.10.3 - - v3.12.0 + - v3.12.1 steps: - name: Checkout uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # tag=v3 @@ -87,7 +87,7 @@ jobs: # We run tests on Helm version of latest SCALE release, SCALE nightly and manually defined "latest" helm-version: - v3.9.4 - - v3.12.0 + - v3.12.1 # We run tests on k3s version of latest SCALE release, SCALE nightly and manually defined "latest" k3s-version: - v1.25.3+k3s1 diff --git a/.github/workflows/common_library_tests.yaml b/.github/workflows/common_library_tests.yaml index cde33cb063..5843d01166 100644 --- a/.github/workflows/common_library_tests.yaml +++ b/.github/workflows/common_library_tests.yaml @@ -19,7 +19,7 @@ jobs: helm-version: - v3.9.4 - v3.10.3 - - v3.12.0 + - v3.12.1 steps: - name: Checkout uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # tag=v3 @@ -57,7 +57,7 @@ jobs: helm-version: - v3.9.4 - v3.10.3 - - v3.12.0 + - v3.12.1 steps: - name: Checkout uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # tag=v3 @@ -97,7 +97,7 @@ jobs: - v1.25.3+k3s1 # We run tests on Helm version of latest SCALE release, SCALE nightly and manually defined "latest" helm-version: - - v3.12.0 + - v3.12.1 values: - basic-values.yaml - configmap-values.yaml diff --git a/library/common/Chart.yaml b/library/common/Chart.yaml index e5113d9da7..7adb20e369 100644 --- a/library/common/Chart.yaml +++ b/library/common/Chart.yaml @@ -2,7 +2,7 @@ apiVersion: v2 name: common description: A library chart for iX Official Catalog type: library -version: 1.0.9 +version: 1.0.10 appVersion: v1 annotations: title: Common Library Chart diff --git a/library/common/templates/app_functions/_redis.tpl b/library/common/templates/app_functions/_redis.tpl new file mode 100644 index 0000000000..06a6bec9b8 --- /dev/null +++ b/library/common/templates/app_functions/_redis.tpl @@ -0,0 +1,93 @@ +{{/* Returns a redis pod with init container for fixing permissions +and a pre-upgrade job to backup the database */}} +{{/* Call this template: +{{ include "ix.v1.common.app.redis" (dict "name" "redis" "secretName" "redis-creds" "resources" .Values.resources) }} + +name (optional): Name of the redis pod/container (default: redis) +secretName (required): Name of the secret containing the redis credentials +resources (required): Resources for the redis container +*/}} +{{- define "ix.v1.common.app.redis" -}} + {{- $name := .name | default "redis" -}} + {{- $secretName := (required "Redis - Secret Name is required" .secretName) -}} + {{- $resources := (required "Redis - Resources are required" .resources) }} +{{ $name }}: + enabled: true + type: Deployment + podSpec: + securityContext: + fsGroup: 1001 + containers: + {{ $name }}: + enabled: true + primary: true + imageSelector: redisImage + securityContext: + runAsUser: 1001 + runAsGroup: 0 + runAsNonRoot: false + readOnlyRootFilesystem: false + resources: + limits: + cpu: {{ $resources.limits.cpu }} + memory: {{ $resources.limits.memory }} + envFrom: + - secretRef: + name: {{ $secretName }} + probes: + liveness: + enabled: true + type: exec + command: + - /bin/sh + - -c + - redis-cli -a "$REDIS_PASSWORD" -p ${REDIS_PORT_NUMBER:-6379} ping | grep -q PONG + readiness: + enabled: true + type: exec + command: + - /bin/sh + - -c + - redis-cli -a "$REDIS_PASSWORD" -p ${REDIS_PORT_NUMBER:-6379} ping | grep -q PONG + startup: + enabled: true + type: exec + command: + - /bin/sh + - -c + - redis-cli -a "$REDIS_PASSWORD" -p ${REDIS_PORT_NUMBER:-6379} ping | grep -q PONG +{{- end -}} + +{{/* Returns a redis-wait container for waiting for redis to be ready */}} +{{/* Call this template: +{{ include "ix.v1.common.app.redisWait" (dict "name" "redis-wait" "secretName" "redis-creds") }} + +name (optional): Name of the redis-wait container (default: redis-wait) +secretName (required): Name of the secret containing the redis credentials +*/}} + +{{- define "ix.v1.common.app.redisWait" -}} + {{- $name := .name | default "redis-wait" -}} + {{- $secretName := (required "Redis-Wait - Secret Name is required" .secretName) }} +{{ $name }}: + enabled: true + type: init + imageSelector: redisImage + envFrom: + - secretRef: + name: {{ $secretName }} + resources: + limits: + cpu: 500m + memory: 256Mi + command: bash + args: + - -c + - | + echo "Waiting for redis to be ready" + until redis-cli -h "$REDIS_HOST" -a "$REDIS_PASSWORD" -p ${REDIS_PORT_NUMBER:-6379} ping | grep -q PONG; do + echo "Waiting for redis to be ready. Sleeping 2 seconds..." + sleep 2 + done + echo "Redis is ready!" +{{- end -}} diff --git a/library/common/templates/lib/container/_resources.tpl b/library/common/templates/lib/container/_resources.tpl index 9cd47285e3..3021101fcc 100644 --- a/library/common/templates/lib/container/_resources.tpl +++ b/library/common/templates/lib/container/_resources.tpl @@ -8,7 +8,7 @@ objectData: The object data to be used to render the container. {{- $rootCtx := .rootCtx -}} {{- $objectData := .objectData -}} - {{- $resources := $rootCtx.Values.resources -}} + {{- $resources := mustDeepCopy $rootCtx.Values.resources -}} {{- if $objectData.resources -}} {{- $resources = mustMergeOverwrite $resources $objectData.resources -}} diff --git a/library/common/values.yaml b/library/common/values.yaml index 9b11deb4c5..f01965e437 100644 --- a/library/common/values.yaml +++ b/library/common/values.yaml @@ -51,6 +51,11 @@ mariadbImage: tag: "10.6.14" pullPolicy: IfNotPresent +redisImage: + repository: bitnami/redis + tag: "7.0.11" + pullPolicy: IfNotPresent + # -- (docs/README.md) securityContext: container: diff --git a/library/ix-dev/community/immich/Chart.lock b/library/ix-dev/community/immich/Chart.lock new file mode 100644 index 0000000000..c9acb307f3 --- /dev/null +++ b/library/ix-dev/community/immich/Chart.lock @@ -0,0 +1,6 @@ +dependencies: +- name: common + repository: file://../../../common + version: 1.0.10 +digest: sha256:06709bd6d9a8dbb721eec437a52f8c24aeecefbbc57e6f8617e53b29570d5512 +generated: "2023-06-20T18:37:14.983325697+03:00" diff --git a/library/ix-dev/community/immich/Chart.yaml b/library/ix-dev/community/immich/Chart.yaml new file mode 100644 index 0000000000..41cc814cd9 --- /dev/null +++ b/library/ix-dev/community/immich/Chart.yaml @@ -0,0 +1,26 @@ +name: immich +description: Immich +annotations: + title: Immich +type: application +version: 1.0.0 +apiVersion: v2 +appVersion: '1.61.1' +kubeVersion: '>=1.16.0-0' +maintainers: + - name: truenas + url: https://www.truenas.com/ + email: dev@ixsystems.com +dependencies: + - name: common + repository: file://../../../common + version: 1.0.10 +home: https://immich.app +icon: https://github.com/immich-app/immich/raw/main/design/immich-logo.svg +sources: + - https://immich.app + - https://github.com/truenas/charts/tree/master/community/immich + - https://github.com/immich-app/immich +keywords: + - photos + - backup diff --git a/library/ix-dev/community/immich/README.md b/library/ix-dev/community/immich/README.md new file mode 100644 index 0000000000..88f8be3f9c --- /dev/null +++ b/library/ix-dev/community/immich/README.md @@ -0,0 +1,3 @@ +# Immich + +[Immich](https://immich.app) - Self-hosted backup solution for photos and videos on mobile device diff --git a/library/ix-dev/community/immich/app-readme.md b/library/ix-dev/community/immich/app-readme.md new file mode 100644 index 0000000000..88f8be3f9c --- /dev/null +++ b/library/ix-dev/community/immich/app-readme.md @@ -0,0 +1,3 @@ +# Immich + +[Immich](https://immich.app) - Self-hosted backup solution for photos and videos on mobile device diff --git a/library/ix-dev/community/immich/charts/common-1.0.10.tgz b/library/ix-dev/community/immich/charts/common-1.0.10.tgz new file mode 100644 index 0000000000000000000000000000000000000000..cc6c1096b09aab05ebaf282426ecddb3d4759cad GIT binary patch literal 56386 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 + This ranges from 0-3 with 3 being the most precise
+ - 3 - Cities > 500 population: ~200MB RAM
+ - 2 - Cities > 1000 population: ~150MB RAM
+ - 1 - Cities > 5000 population: ~80MB RAM
+ - 0 - Cities > 15000 population: ~40MB RAM + schema: + type: int + default: 3 + min: 0 + max: 3 + required: true + - variable: publicLoginMessage + label: Public Login Message + description: | + The message that will be displayed on the login page. + schema: + type: string + default: "" + - variable: enableML + label: Enable Machine Learning + description: | + Enable Machine Learning + schema: + type: boolean + default: true + - variable: enableTypesense + label: Enable Typesense + description: | + Enable Typesense + schema: + type: boolean + default: true + + - variable: immichNetwork + label: "" + group: Network Configuration + schema: + type: dict + attrs: + - variable: webuiPort + label: Web Port + description: The port for the Immich WebUI. + schema: + type: int + default: 30041 + min: 9000 + max: 65535 + required: true + + - variable: immichStorage + label: "" + group: Storage Configuration + schema: + type: dict + attrs: + - variable: uploads + label: Immich Uploads Storage + description: The path to store Immich uploads. + schema: + type: dict + attrs: + - variable: type + label: Type + description: | + ixVolume: Is dataset created automatically by the system.
+ Host Path: Is a path that already exists on the system. + schema: + type: string + required: true + default: ixVolume + enum: + - value: hostPath + description: Host Path (Path that already exists on the system) + - value: ixVolume + description: ixVolume (Dataset created automatically by the system) + - variable: datasetName + label: Dataset Name + schema: + type: string + show_if: [["type", "=", "ixVolume"]] + required: true + hidden: true + immutable: true + default: uploads + $ref: + - "normalize/ixVolume" + - variable: hostPath + label: Host Path + schema: + type: hostpath + show_if: [["type", "=", "hostPath"]] + immutable: true + required: true + - variable: library + label: Immich Library Storage + description: The path to store Immich library. + schema: + type: dict + attrs: + - variable: type + label: Type + description: | + ixVolume: Is dataset created automatically by the system.
+ Host Path: Is a path that already exists on the system. + schema: + type: string + required: true + default: ixVolume + enum: + - value: hostPath + description: Host Path (Path that already exists on the system) + - value: ixVolume + description: ixVolume (Dataset created automatically by the system) + - variable: datasetName + label: Dataset Name + schema: + type: string + show_if: [["type", "=", "ixVolume"]] + required: true + hidden: true + immutable: true + default: library + $ref: + - "normalize/ixVolume" + - variable: hostPath + label: Host Path + schema: + type: hostpath + show_if: [["type", "=", "hostPath"]] + immutable: true + required: true + - variable: thumbs + label: Immich Thumbs Storage + description: The path to store Immich Thumbs. + schema: + type: dict + attrs: + - variable: type + label: Type + description: | + ixVolume: Is dataset created automatically by the system.
+ Host Path: Is a path that already exists on the system. + schema: + type: string + required: true + default: ixVolume + enum: + - value: hostPath + description: Host Path (Path that already exists on the system) + - value: ixVolume + description: ixVolume (Dataset created automatically by the system) + - variable: datasetName + label: Dataset Name + schema: + type: string + show_if: [["type", "=", "ixVolume"]] + required: true + hidden: true + immutable: true + default: thumbs + $ref: + - "normalize/ixVolume" + - variable: hostPath + label: Host Path + schema: + type: hostpath + show_if: [["type", "=", "hostPath"]] + immutable: true + required: true + - variable: profile + label: Immich Profile Storage + description: The path to store Immich Profile. + schema: + type: dict + attrs: + - variable: type + label: Type + description: | + ixVolume: Is dataset created automatically by the system.
+ Host Path: Is a path that already exists on the system. + schema: + type: string + required: true + default: ixVolume + enum: + - value: hostPath + description: Host Path (Path that already exists on the system) + - value: ixVolume + description: ixVolume (Dataset created automatically by the system) + - variable: datasetName + label: Dataset Name + schema: + type: string + show_if: [["type", "=", "ixVolume"]] + required: true + hidden: true + immutable: true + default: profile + $ref: + - "normalize/ixVolume" + - variable: hostPath + label: Host Path + schema: + type: hostpath + show_if: [["type", "=", "hostPath"]] + immutable: true + required: true + - variable: video + label: Immich Video Storage + description: The path to store Immich Video. + schema: + type: dict + attrs: + - variable: type + label: Type + description: | + ixVolume: Is dataset created automatically by the system.
+ Host Path: Is a path that already exists on the system. + schema: + type: string + required: true + default: ixVolume + enum: + - value: hostPath + description: Host Path (Path that already exists on the system) + - value: ixVolume + description: ixVolume (Dataset created automatically by the system) + - variable: datasetName + label: Dataset Name + schema: + type: string + show_if: [["type", "=", "ixVolume"]] + required: true + hidden: true + immutable: true + default: video + $ref: + - "normalize/ixVolume" + - variable: hostPath + label: Host Path + schema: + type: hostpath + show_if: [["type", "=", "hostPath"]] + immutable: true + required: true + - variable: pgData + label: Immich Postgres Data Storage + description: The path to store Immich Postgres Data. + schema: + type: dict + attrs: + - variable: type + label: Type + description: | + ixVolume: Is dataset created automatically by the system.
+ Host Path: Is a path that already exists on the system. + schema: + type: string + required: true + default: ixVolume + enum: + - value: hostPath + description: Host Path (Path that already exists on the system) + - value: ixVolume + description: ixVolume (Dataset created automatically by the system) + - variable: datasetName + label: Dataset Name + schema: + type: string + show_if: [["type", "=", "ixVolume"]] + required: true + hidden: true + immutable: true + default: pgData + $ref: + - "normalize/ixVolume" + - variable: hostPath + label: Host Path + schema: + type: hostpath + show_if: [["type", "=", "hostPath"]] + immutable: true + required: true + - variable: pgBackup + label: Immich Postgres Backup Storage + description: The path to store Immich Postgres Backup. + schema: + type: dict + attrs: + - variable: type + label: Type + description: | + ixVolume: Is dataset created automatically by the system.
+ Host Path: Is a path that already exists on the system. + schema: + type: string + required: true + default: ixVolume + enum: + - value: hostPath + description: Host Path (Path that already exists on the system) + - value: ixVolume + description: ixVolume (Dataset created automatically by the system) + - variable: datasetName + label: Dataset Name + schema: + type: string + show_if: [["type", "=", "ixVolume"]] + required: true + hidden: true + immutable: true + default: pgBackup + $ref: + - "normalize/ixVolume" + - variable: hostPath + label: Host Path + schema: + type: hostpath + show_if: [["type", "=", "hostPath"]] + immutable: true + required: true + + - variable: resources + label: "" + group: Resources Configuration + schema: + type: dict + attrs: + - variable: limits + label: Limits + schema: + type: dict + attrs: + - variable: cpu + label: CPU + description: CPU limit for Immich. + schema: + type: string + max_length: 6 + valid_chars: '^(0\.[1-9]|[1-9][0-9]*)(\.[0-9]|m?)$' + valid_chars_error: | + Valid CPU limit formats are
+ - Plain Integer - eg. 1
+ - Float - eg. 0.5
+ - Milicpu - eg. 500m + default: "4000m" + required: true + - variable: memory + label: Memory + description: Memory limit for Immich. + schema: + type: string + max_length: 12 + valid_chars: '^[1-9][0-9]*([EPTGMK]i?|e[0-9]+)?$' + valid_chars_error: | + Valid Memory limit formats are
+ - Suffixed with E/P/T/G/M/K - eg. 1G
+ - Suffixed with Ei/Pi/Ti/Gi/Mi/Ki - eg. 1Gi
+ - Plain Integer in bytes - eg. 1024
+ - Exponent - eg. 134e6 + default: "8Gi" + required: true diff --git a/library/ix-dev/community/immich/templates/NOTES.txt b/library/ix-dev/community/immich/templates/NOTES.txt new file mode 100644 index 0000000000..ba4e01146c --- /dev/null +++ b/library/ix-dev/community/immich/templates/NOTES.txt @@ -0,0 +1 @@ +{{ include "ix.v1.common.lib.chart.notes" $ }} diff --git a/library/ix-dev/community/immich/templates/_configuration.tpl b/library/ix-dev/community/immich/templates/_configuration.tpl new file mode 100644 index 0000000000..1434df7b72 --- /dev/null +++ b/library/ix-dev/community/immich/templates/_configuration.tpl @@ -0,0 +1,125 @@ +{{- define "immich.configuration" -}} + {{- $fullname := (include "ix.v1.common.lib.chart.names.fullname" $) -}} + + {{- $dbHost := (printf "%s-postgres" $fullname) -}} + {{- $dbUser := "immich" -}} + {{- $dbName := "immich" -}} + + {{- $dbPass := randAlphaNum 32 -}} + {{- with (lookup "v1" "Secret" .Release.Namespace (printf "%s-postgres-creds" $fullname)) -}} + {{- $dbPass = ((index .data "POSTGRES_PASSWORD") | b64dec) -}} + {{- end -}} + + {{- $redisHost := (printf "%s-redis" $fullname) -}} + + {{- $redisPass := randAlphaNum 32 -}} + {{- with (lookup "v1" "Secret" .Release.Namespace (printf "%s-redis-creds" $fullname)) -}} + {{- $redisPass = ((index .data "REDIS_PASSWORD") | b64dec) -}} + {{- end -}} + + {{- $dbURL := (printf "postgres://%s:%s@%s:5432/%s?sslmode=disable" $dbUser $dbPass $dbHost $dbName) -}} + + {{- $typesenseKey := randAlphaNum 32 -}} + {{- with (lookup "v1" "Secret" .Release.Namespace (printf "%s-immich-creds" $fullname)) -}} + {{- $typesenseKey = ((index .data "TYPESENSE_API_KEY") | b64dec) -}} + {{- end -}} + + {{- $mlURL := "false" -}} + {{- if .Values.immichConfig.enableML -}} + {{- $mlURL = printf "http://%v-machinelearning:%v" $fullname .Values.immichNetwork.microservicesPort -}} + {{- end }} + +secret: + postgres-creds: + enabled: true + data: + POSTGRES_USER: {{ $dbUser }} + POSTGRES_DB: {{ $dbName }} + POSTGRES_PASSWORD: {{ $dbPass }} + POSTGRES_HOST: {{ $dbHost }} + POSTGRES_URL: {{ $dbURL }} + + redis-creds: + enabled: true + data: + ALLOW_EMPTY_PASSWORD: "no" + REDIS_PASSWORD: {{ $redisPass }} + REDIS_HOST: {{ $redisHost }} + + {{/* Server & Microservices */}} + immich-creds: + enabled: true + data: + IMMICH_MACHINE_LEARNING_URL: {{ $mlURL | quote }} + TYPESENSE_ENABLED: {{ .Values.immichConfig.enableTypesense | quote }} + TYPESENSE_API_KEY: {{ $typesenseKey }} + {{- if .Values.immichConfig.enableTypesense }} + TYPESENSE_PROTOCOL: http + TYPESENSE_HOST: {{ printf "%v-typesense" $fullname }} + TYPESENSE_PORT: {{ .Values.immichNetwork.typesensePort | quote }} + {{- end }} + DB_USERNAME: {{ $dbUser }} + DB_PASSWORD: {{ $dbPass }} + DB_HOSTNAME: {{ $dbHost }} + DB_DATABASE_NAME: {{ $dbName }} + DB_PORT: "5432" + REDIS_HOSTNAME: {{ $redisHost }} + REDIS_PASSWORD: {{ $redisPass }} + REDIS_PORT: "6379" + REDIS_DBINDEX: "0" + + {{- if .Values.immichConfig.enableTypesense }} + typesense-creds: + enabled: true + data: + TYPESENSE_API_KEY: {{ $typesenseKey }} + TYPESENSE_DATA_DIR: /typesense-data + {{- end }} + +configmap: + server-config: + enabled: true + data: + LOG_LEVEL: log + NODE_ENV: production + SERVER_PORT: {{ .Values.immichNetwork.serverPort | quote }} + + micro-config: + enabled: true + data: + LOG_LEVEL: log + NODE_ENV: production + MICROSERVICES_PORT: {{ .Values.immichNetwork.microservicesPort | quote }} + DISABLE_REVERSE_GEOCODING: {{ .Values.immichConfig.disableReverseGeocoding | quote }} + {{- if not .Values.immichConfig.disableReverseGeocoding }} + REVERSE_GEOCODING_PRECISION: {{ .Values.immichConfig.reverseGeocodingPrecision | quote }} + {{- end }} + REVERSE_GEOCODING_DUMP_DIRECTORY: /microcache + + web-config: + enabled: true + data: + NODE_ENV: production + PORT: {{ .Values.immichNetwork.webPort | quote }} + IMMICH_SERVER_URL: {{ printf "http://%v-server:%v" $fullname .Values.immichNetwork.serverPort }} + PUBLIC_IMMICH_SERVER_URL: {{ printf "http://%v-server:%v" $fullname .Values.immichNetwork.serverPort }} + {{- with .Values.immichConfig.publicLoginMessage }} + PUBLIC_LOGIN_PAGE_MESSAGE: {{ . | quote }} + {{- end }} + + proxy-config: + enabled: true + data: + IMMICH_WEB_URL: {{ printf "http://%v-web:%v" $fullname .Values.immichNetwork.webPort }} + IMMICH_SERVER_URL: {{ printf "http://%v-server:%v" $fullname .Values.immichNetwork.serverPort }} + + {{- if .Values.immichConfig.enableML }} + ml-config: + enabled: true + data: + NODE_ENV: production + MACHINE_LEARNING_PORT: {{ .Values.immichNetwork.machinelearningPort | quote }} + MACHINE_LEARNING_CACHE_FOLDER: /mlcache + TRANSFORMERS_CACHE: /mlcache + {{- end }} +{{- end -}} diff --git a/library/ix-dev/community/immich/templates/_immich-machinelearning.tpl b/library/ix-dev/community/immich/templates/_immich-machinelearning.tpl new file mode 100644 index 0000000000..1f6479b9be --- /dev/null +++ b/library/ix-dev/community/immich/templates/_immich-machinelearning.tpl @@ -0,0 +1,41 @@ +{{- define "immich.machinelearning.workload" -}} +{{- $fullname := (include "ix.v1.common.lib.chart.names.fullname" $) -}} +{{- $url := printf "http://%v-server:%v/server-info/ping" $fullname .Values.immichNetwork.serverPort }} +workload: + machinelearning: + enabled: true + type: Deployment + podSpec: + hostNetwork: false + containers: + machinelearning: + enabled: true + primary: true + imageSelector: mlImage + securityContext: + runAsUser: 0 + runAsGroup: 0 + runAsNonRoot: false + readOnlyRootFilesystem: false + envFrom: + - configMapRef: + name: ml-config + probes: + liveness: + enabled: true + type: http + port: {{ .Values.immichNetwork.machinelearningPort }} + path: /ping + readiness: + enabled: true + type: http + port: {{ .Values.immichNetwork.machinelearningPort }} + path: /ping + startup: + enabled: true + type: http + port: {{ .Values.immichNetwork.machinelearningPort }} + path: /ping + initContainers: + {{- include "immich.wait.init" (dict "url" $url) | indent 8 }} +{{- end -}} diff --git a/library/ix-dev/community/immich/templates/_immich-microservices.tpl b/library/ix-dev/community/immich/templates/_immich-microservices.tpl new file mode 100644 index 0000000000..9eb88034b7 --- /dev/null +++ b/library/ix-dev/community/immich/templates/_immich-microservices.tpl @@ -0,0 +1,57 @@ +{{- define "immich.microservices.workload" -}} +{{- $fullname := (include "ix.v1.common.lib.chart.names.fullname" $) -}} +{{- $url := printf "http://%v-server:%v/server-info/ping" $fullname .Values.immichNetwork.serverPort }} +workload: + microservices: + enabled: true + type: Deployment + podSpec: + hostNetwork: false + containers: + microservices: + enabled: true + primary: true + imageSelector: image + securityContext: + runAsUser: 0 + runAsGroup: 0 + runAsNonRoot: false + readOnlyRootFilesystem: false + args: start-microservices.sh + envFrom: + - secretRef: + name: immich-creds + - configMapRef: + name: micro-config + probes: + liveness: + enabled: true + type: exec + command: + - /bin/sh + - -c + - | + ps -a | grep -v grep | grep -q microservices + readiness: + enabled: true + type: exec + command: + - /bin/sh + - -c + - | + ps -a | grep -v grep | grep -q microservices + startup: + enabled: true + type: exec + command: + - /bin/sh + - -c + - | + ps -a | grep -v grep | grep -q microservices + initContainers: + {{- include "ix.v1.common.app.postgresWait" (dict "name" "postgres-wait" + "secretName" "postgres-creds") | nindent 8 }} + {{- include "ix.v1.common.app.redisWait" (dict "name" "redis-wait" + "secretName" "redis-creds") | nindent 8 }} + {{- include "immich.wait.init" (dict "url" $url) | indent 8 }} +{{- end -}} diff --git a/library/ix-dev/community/immich/templates/_immich-proxy.tpl b/library/ix-dev/community/immich/templates/_immich-proxy.tpl new file mode 100644 index 0000000000..95feed8cd7 --- /dev/null +++ b/library/ix-dev/community/immich/templates/_immich-proxy.tpl @@ -0,0 +1,48 @@ +{{- define "immich.proxy.workload" -}} +{{- $fullname := (include "ix.v1.common.lib.chart.names.fullname" $) -}} +{{- $serverUrl := printf "http://%v-server:%v/server-info/ping" $fullname .Values.immichNetwork.serverPort -}} +{{- $webUrl := printf "http://%v-web:%v/robots.txt" $fullname .Values.immichNetwork.webPort }} +workload: + proxy: + enabled: true + type: Deployment + podSpec: + hostNetwork: false + containers: + proxy: + enabled: true + primary: true + imageSelector: proxyImage + securityContext: + runAsUser: 0 + runAsGroup: 0 + runAsNonRoot: false + readOnlyRootFilesystem: false + capabilities: + add: + - CHOWN + - SETUID + - SETGID + envFrom: + - configMapRef: + name: proxy-config + probes: + liveness: + enabled: true + type: http + path: /api/server-info/ping + port: 8080 + readiness: + enabled: true + type: http + path: /api/server-info/ping + port: 8080 + startup: + enabled: true + type: http + path: /api/server-info/ping + port: 8080 + initContainers: + {{- include "immich.wait.init" (dict "url" $serverUrl) | indent 8 }} + {{- include "immich.wait.init" (dict "url" $webUrl) | indent 8 }} +{{- end -}} diff --git a/library/ix-dev/community/immich/templates/_immich-server.tpl b/library/ix-dev/community/immich/templates/_immich-server.tpl new file mode 100644 index 0000000000..41c1377ed8 --- /dev/null +++ b/library/ix-dev/community/immich/templates/_immich-server.tpl @@ -0,0 +1,51 @@ +{{- define "immich.server.workload" -}} +{{- $fullname := (include "ix.v1.common.lib.chart.names.fullname" $) -}} +{{- $typesenseUrl := printf "http://%v-typesense:%v/health" $fullname .Values.immichNetwork.typesensePort }} +workload: + server: + enabled: true + primary: true + type: Deployment + podSpec: + hostNetwork: false + containers: + server: + enabled: true + primary: true + imageSelector: image + securityContext: + runAsUser: 0 + runAsGroup: 0 + runAsNonRoot: false + readOnlyRootFilesystem: false + args: start-server.sh + envFrom: + - secretRef: + name: immich-creds + - configMapRef: + name: server-config + probes: + liveness: + enabled: true + type: http + path: /server-info/ping + port: {{ .Values.immichNetwork.serverPort }} + readiness: + enabled: true + type: http + path: /server-info/ping + port: {{ .Values.immichNetwork.serverPort }} + startup: + enabled: true + type: http + path: /server-info/ping + port: {{ .Values.immichNetwork.serverPort }} + initContainers: + {{- include "ix.v1.common.app.postgresWait" (dict "name" "postgres-wait" + "secretName" "postgres-creds") | nindent 8 }} + {{- include "ix.v1.common.app.redisWait" (dict "name" "redis-wait" + "secretName" "redis-creds") | nindent 8 }} + {{- if .Values.immichConfig.enableTypesense }} + {{- include "immich.wait.init" (dict "url" $typesenseUrl) | indent 8 }} + {{- end }} +{{- end -}} diff --git a/library/ix-dev/community/immich/templates/_immich-typesense.tpl b/library/ix-dev/community/immich/templates/_immich-typesense.tpl new file mode 100644 index 0000000000..53dd8958a4 --- /dev/null +++ b/library/ix-dev/community/immich/templates/_immich-typesense.tpl @@ -0,0 +1,42 @@ +{{- define "immich.typesense.workload" -}} +{{- $fullname := (include "ix.v1.common.lib.chart.names.fullname" $) -}} +{{- $url := printf "http://%v-server:%v/server-info/ping" $fullname .Values.immichNetwork.serverPort }} +workload: + typesense: + enabled: true + type: Deployment + podSpec: + hostNetwork: false + containers: + typesense: + enabled: true + primary: true + imageSelector: typesenseImage + args: + - --api-port + - {{ .Values.immichNetwork.typesensePort | quote }} + securityContext: + runAsUser: 0 + runAsGroup: 0 + runAsNonRoot: false + readOnlyRootFilesystem: false + envFrom: + - secretRef: + name: typesense-creds + probes: + liveness: + enabled: true + type: http + path: /health + port: {{ .Values.immichNetwork.typesensePort }} + readiness: + enabled: true + type: http + path: /health + port: {{ .Values.immichNetwork.typesensePort }} + startup: + enabled: true + type: http + path: /health + port: {{ .Values.immichNetwork.typesensePort }} +{{- end -}} diff --git a/library/ix-dev/community/immich/templates/_immich-web.tpl b/library/ix-dev/community/immich/templates/_immich-web.tpl new file mode 100644 index 0000000000..a932895c32 --- /dev/null +++ b/library/ix-dev/community/immich/templates/_immich-web.tpl @@ -0,0 +1,45 @@ +{{- define "immich.web.workload" -}} +{{- $fullname := (include "ix.v1.common.lib.chart.names.fullname" $) -}} +{{- $url := printf "http://%v-server:%v/server-info/ping" $fullname .Values.immichNetwork.serverPort }} +workload: + web: + enabled: true + type: Deployment + podSpec: + hostNetwork: false + containers: + web: + enabled: true + primary: true + imageSelector: webImage + securityContext: + runAsUser: 0 + runAsGroup: 0 + runAsNonRoot: false + readOnlyRootFilesystem: false + capabilities: + add: + - SETUID + - SETGID + envFrom: + - configMapRef: + name: web-config + probes: + liveness: + enabled: true + type: http + path: /robots.txt + port: {{ .Values.immichNetwork.webPort }} + readiness: + enabled: true + type: http + path: /robots.txt + port: {{ .Values.immichNetwork.webPort }} + startup: + enabled: true + type: http + path: /robots.txt + port: {{ .Values.immichNetwork.webPort }} + initContainers: + {{- include "immich.wait.init" (dict "url" $url) | indent 8 }} +{{- end -}} diff --git a/library/ix-dev/community/immich/templates/_persistence.tpl b/library/ix-dev/community/immich/templates/_persistence.tpl new file mode 100644 index 0000000000..e9c2bff8df --- /dev/null +++ b/library/ix-dev/community/immich/templates/_persistence.tpl @@ -0,0 +1,137 @@ +{{- define "immich.persistence" -}} +persistence: + {{/* Data */}} + library: + enabled: true + type: {{ .Values.immichStorage.library.type }} + datasetName: {{ .Values.immichStorage.library.datasetName | default "" }} + hostPath: {{ .Values.immichStorage.library.hostPath | default "" }} + targetSelector: + server: + server: + mountPath: /usr/src/app/upload/library + microservices: + microservices: + mountPath: /usr/src/app/upload/library + uploads: + enabled: true + type: {{ .Values.immichStorage.uploads.type }} + datasetName: {{ .Values.immichStorage.uploads.datasetName | default "" }} + hostPath: {{ .Values.immichStorage.uploads.hostPath | default "" }} + targetSelector: + server: + server: + mountPath: /usr/src/app/upload/upload + microservices: + microservices: + mountPath: /usr/src/app/upload/upload + thumbs: + enabled: true + type: {{ .Values.immichStorage.thumbs.type }} + datasetName: {{ .Values.immichStorage.thumbs.datasetName | default "" }} + hostPath: {{ .Values.immichStorage.thumbs.hostPath | default "" }} + targetSelector: + server: + server: + mountPath: /usr/src/app/upload/thumbs + microservices: + microservices: + mountPath: /usr/src/app/upload/thumbs + profile: + enabled: true + type: {{ .Values.immichStorage.profile.type }} + datasetName: {{ .Values.immichStorage.profile.datasetName | default "" }} + hostPath: {{ .Values.immichStorage.profile.hostPath | default "" }} + targetSelector: + server: + server: + mountPath: /usr/src/app/upload/profile + microservices: + microservices: + mountPath: /usr/src/app/upload/profile + video: + enabled: true + type: {{ .Values.immichStorage.video.type }} + datasetName: {{ .Values.immichStorage.video.datasetName | default "" }} + hostPath: {{ .Values.immichStorage.video.hostPath | default "" }} + targetSelector: + server: + server: + mountPath: /usr/src/app/upload/encoded-video + microservices: + microservices: + mountPath: /usr/src/app/upload/encoded-video + + {{/* Caches */}} + microcache: + enabled: true + type: emptyDir + targetSelector: + microservices: + microservices: + mountPath: /microcache + {{- if .Values.immichConfig.enableTypesense }} + typsense: + enabled: true + type: emptyDir + targetSelector: + typesense: + typesense: + mountPath: /typesense-data + {{- end -}} + {{- if .Values.immichConfig.enableML }} + mlcache: + enabled: true + type: emptyDir + targetSelector: + machinelearning: + machinelearning: + mountPath: /mlcache + {{- end }} + redis: + enabled: true + type: emptyDir + targetSelector: + redis: + redis: + mountPath: /bitnami/redis/data + tmp: + enabled: true + type: emptyDir + targetSelector: + redis: + redis: + mountPath: /tmp + + {{/* Database */}} + postgresdata: + enabled: true + type: {{ .Values.immichStorage.pgData.type }} + datasetName: {{ .Values.immichStorage.pgData.datasetName | default "" }} + hostPath: {{ .Values.immichStorage.pgData.hostPath | default "" }} + targetSelector: + # Postgres pod + postgres: + # Postgres container + postgres: + mountPath: /var/lib/postgresql/data + # Postgres - Permissions container + # Different than the 01-permissions + permissions: + mountPath: /mnt/directories/postgres_data + postgresbackup: + enabled: true + type: {{ .Values.immichStorage.pgBackup.type }} + datasetName: {{ .Values.immichStorage.pgBackup.datasetName | default "" }} + hostPath: {{ .Values.immichStorage.pgBackup.hostPath | default "" }} + targetSelector: + # Postgres backup pod + postgresbackup: + # Postgres backup container + postgresbackup: + mountPath: /postgres_backup + # Postgres - Permissions container + # Different than the 01-permissions + permissions: + mountPath: /mnt/directories/postgres_backup +{{- end -}} diff --git a/library/ix-dev/community/immich/templates/_portal.tpl b/library/ix-dev/community/immich/templates/_portal.tpl new file mode 100644 index 0000000000..acc267a33e --- /dev/null +++ b/library/ix-dev/community/immich/templates/_portal.tpl @@ -0,0 +1,12 @@ +{{- define "immich.portal" -}} +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: portal +data: + path: / + port: {{ .Values.immichNetwork.webuiPort | quote }} + protocol: http + host: $node_ip +{{- end -}} diff --git a/library/ix-dev/community/immich/templates/_postgres.tpl b/library/ix-dev/community/immich/templates/_postgres.tpl new file mode 100644 index 0000000000..0e7ac05894 --- /dev/null +++ b/library/ix-dev/community/immich/templates/_postgres.tpl @@ -0,0 +1,7 @@ +{{- define "postgres.workload" -}} +workload: +{{- include "ix.v1.common.app.postgres" (dict "secretName" "postgres-creds" + "resources" .Values.resources + "ixChartContext" .Values.ixChartContext) | nindent 2 }} + +{{- end -}} diff --git a/library/ix-dev/community/immich/templates/_redis.tpl b/library/ix-dev/community/immich/templates/_redis.tpl new file mode 100644 index 0000000000..c90b61b2be --- /dev/null +++ b/library/ix-dev/community/immich/templates/_redis.tpl @@ -0,0 +1,6 @@ +{{- define "redis.workload" -}} +workload: +{{- include "ix.v1.common.app.redis" (dict "secretName" "redis-creds" + "resources" .Values.resources) | nindent 2 }} + +{{- end -}} diff --git a/library/ix-dev/community/immich/templates/_service.tpl b/library/ix-dev/community/immich/templates/_service.tpl new file mode 100644 index 0000000000..5c8333143f --- /dev/null +++ b/library/ix-dev/community/immich/templates/_service.tpl @@ -0,0 +1,105 @@ +{{- define "immich.service" -}} +service: + proxy: + enabled: true + primary: true + type: NodePort + targetSelector: proxy + ports: + proxy: + enabled: true + primary: true + port: {{ .Values.immichNetwork.webuiPort }} + nodePort: {{ .Values.immichNetwork.webuiPort }} + protocol: http + targetPort: 8080 + targetSelector: proxy + + server: + enabled: true + type: ClusterIP + targetSelector: server + ports: + server: + enabled: true + primary: true + port: {{ .Values.immichNetwork.serverPort }} + protocol: http + targetSelector: server + + web: + enabled: true + type: ClusterIP + targetSelector: web + ports: + web: + enabled: true + primary: true + port: {{ .Values.immichNetwork.webPort }} + protocol: http + targetSelector: web + + microservices: + enabled: true + type: ClusterIP + targetSelector: microservices + ports: + microservices: + enabled: true + primary: true + port: {{ .Values.immichNetwork.microservicesPort }} + protocol: http + targetSelector: microservices + + {{- if .Values.immichConfig.enableML }} + machinelearning: + enabled: true + type: ClusterIP + targetSelector: machinelearning + ports: + machinelearning: + enabled: true + primary: true + port: {{ .Values.immichNetwork.machinelearningPort }} + protocol: http + targetSelector: machinelearning + {{- end -}} + + {{- if .Values.immichConfig.enableTypesense }} + typesense: + enabled: true + type: ClusterIP + targetSelector: typesense + ports: + typesense: + enabled: true + primary: true + port: {{ .Values.immichNetwork.typesensePort }} + protocol: http + targetSelector: typesense + {{- end }} + + redis: + enabled: true + type: ClusterIP + targetSelector: redis + ports: + redis: + enabled: true + primary: true + port: 6379 + targetPort: 6379 + targetSelector: redis + + postgres: + enabled: true + type: ClusterIP + targetSelector: postgres + ports: + postgres: + enabled: true + primary: true + port: 5432 + targetPort: 5432 + targetSelector: postgres +{{- end -}} diff --git a/library/ix-dev/community/immich/templates/_waitURL.tpl b/library/ix-dev/community/immich/templates/_waitURL.tpl new file mode 100644 index 0000000000..7c109c0000 --- /dev/null +++ b/library/ix-dev/community/immich/templates/_waitURL.tpl @@ -0,0 +1,17 @@ +{{- define "immich.wait.init" -}} +{{- $url := .url }} +wait-url: + enabled: true + type: init + imageSelector: bashImage + command: + - /bin/ash + - -c + - | + echo "Pinging [{{ $url }}] until it is ready..." + until wget --spider --quiet "{{ $url }}"; do + echo "Waiting for [{{ $url }}] to be ready..." + sleep 2 + done + echo "URL [{{ $url }}] is ready!" +{{- end -}} diff --git a/library/ix-dev/community/immich/templates/common.yaml b/library/ix-dev/community/immich/templates/common.yaml new file mode 100644 index 0000000000..3a4b911709 --- /dev/null +++ b/library/ix-dev/community/immich/templates/common.yaml @@ -0,0 +1,23 @@ +{{- include "ix.v1.common.loader.init" . -}} + +{{/* Merge the templates with Values */}} +{{- $_ := mustMergeOverwrite .Values (include "immich.configuration" $ | fromYaml) -}} +{{- $_ := mustMergeOverwrite .Values (include "immich.server.workload" $ | fromYaml) -}} +{{- $_ := mustMergeOverwrite .Values (include "immich.microservices.workload" $ | fromYaml) -}} +{{- if .Values.immichConfig.enableML -}} + {{- $_ := mustMergeOverwrite .Values (include "immich.machinelearning.workload" $ | fromYaml) -}} +{{- end -}} +{{- $_ := mustMergeOverwrite .Values (include "immich.web.workload" $ | fromYaml) -}} +{{- $_ := mustMergeOverwrite .Values (include "immich.proxy.workload" $ | fromYaml) -}} +{{- if .Values.immichConfig.enableTypesense -}} + {{- $_ := mustMergeOverwrite .Values (include "immich.typesense.workload" $ | fromYaml) -}} +{{- end -}} +{{- $_ := mustMergeOverwrite .Values (include "immich.persistence" $ | fromYaml) -}} +{{- $_ := mustMergeOverwrite .Values (include "immich.service" $ | fromYaml) -}} +{{- $_ := mustMergeOverwrite .Values (include "postgres.workload" $ | fromYaml) -}} +{{- $_ := mustMergeOverwrite .Values (include "redis.workload" $ | fromYaml) -}} + +{{/* Create the configmap for portal manually*/}} +{{- include "immich.portal" $ -}} + +{{- include "ix.v1.common.loader.apply" . -}} diff --git a/library/ix-dev/community/immich/upgrade_info.json b/library/ix-dev/community/immich/upgrade_info.json new file mode 100644 index 0000000000..dc39e91f41 --- /dev/null +++ b/library/ix-dev/community/immich/upgrade_info.json @@ -0,0 +1,4 @@ +{ + "filename": "values.yaml", + "keys": ["image", "webImage", "proxyImage", "mlImage", "typesenseImage"] +} diff --git a/library/ix-dev/community/immich/upgrade_strategy b/library/ix-dev/community/immich/upgrade_strategy new file mode 100755 index 0000000000..17637327d7 --- /dev/null +++ b/library/ix-dev/community/immich/upgrade_strategy @@ -0,0 +1,58 @@ +#!/usr/bin/python3 +import json +import re +import sys + +from catalog_update.upgrade_strategy import semantic_versioning + + +RE_STABLE_VERSION_BASE = r'\d+\.\d+\.\d+' +ENUMS = { + 'typesenseImage': { + 'RE_STABLE_VERSION': re.compile(RE_STABLE_VERSION_BASE), + 'STRIP_TEXT': '' + }, + 'default': { + 'RE_STABLE_VERSION': re.compile(rf'v{RE_STABLE_VERSION_BASE}'), + 'STRIP_TEXT': 'v' + } +} + + +def newer_mapping(image_tags): + + output = { + "tags": {}, + "app_version": "" + } + + for key in image_tags.keys(): + STRIP_TEXT = ENUMS[key].get('STRIP_TEXT', None) \ + if key in ENUMS else ENUMS.get('default', None).get('STRIP_TEXT', None) + RE_STABLE_VERSION = ENUMS[key].get('RE_STABLE_VERSION') \ + if key in ENUMS else ENUMS.get('default', None).get('RE_STABLE_VERSION', None) + + if (STRIP_TEXT is None) or (RE_STABLE_VERSION is None): + continue + + tags = {t.strip(STRIP_TEXT): t for t in image_tags[key] if RE_STABLE_VERSION.fullmatch(t)} + version = semantic_versioning(list(tags)) + + if not version: + continue + + if key == 'image': + output['app_version'] = version + + output['tags'][key] = tags[version] + + return output + + +if __name__ == '__main__': + try: + versions_json = json.loads(sys.stdin.read()) + except ValueError: + raise ValueError('Invalid json specified') + + print(json.dumps(newer_mapping(versions_json))) diff --git a/library/ix-dev/community/immich/values.yaml b/library/ix-dev/community/immich/values.yaml new file mode 100644 index 0000000000..4787fa1058 --- /dev/null +++ b/library/ix-dev/community/immich/values.yaml @@ -0,0 +1,70 @@ +image: + repository: altran1502/immich-server + pullPolicy: IfNotPresent + tag: v1.66.1 + +webImage: + repository: altran1502/immich-web + pullPolicy: IfNotPresent + tag: v1.66.1 + +proxyImage: + repository: altran1502/immich-proxy + pullPolicy: IfNotPresent + tag: v1.66.1 + +mlImage: + repository: altran1502/immich-machine-learning + pullPolicy: IfNotPresent + tag: v1.66.1 + +typesenseImage: + repository: typesense/typesense + pullPolicy: IfNotPresent + tag: 0.24.1 + +resources: + limits: + cpu: 4000m + memory: 8Gi + +immichConfig: + disableReverseGeocoding: false + reverseGeocodingPrecision: 3 + publicLoginMessage: '' + enableML: true + enableTypesense: true + +immichNetwork: + webuiPort: 30041 + # Not user configurable, will be hidden on UI + # Putting it here in case it needs to be configurable + # in the future. + serverPort: 32000 + webPort: 32001 + machinelearningPort: 32002 + microservicesPort: 32003 + typesensePort: 32004 + +immichStorage: + uploads: + type: ixVolume + datasetName: uploads + library: + type: ixVolume + datasetName: library + thumbs: + type: ixVolume + datasetName: thumbs + profile: + type: ixVolume + datasetName: profile + video: + type: ixVolume + datasetName: video + pgData: + type: ixVolume + datasetName: pgData + pgBackup: + type: ixVolume + datasetName: pgBackup