From 7250f08cc5262be78e0ec43d4a92eb68901b201f Mon Sep 17 00:00:00 2001 From: Paul Payne Date: Thu, 17 Jul 2025 13:38:16 -0700 Subject: [PATCH] Adds memcached and openproject apps. --- .cspell/custom-dictionary-workspace.txt | 2 + apps/README.md | 94 +++++++++- apps/immich/db-init-job.yaml | 2 +- apps/memcached/deployment.yaml | 42 +++++ apps/memcached/kustomization.yaml | 13 ++ apps/memcached/manifest.yaml | 19 ++ apps/memcached/namespace.yaml | 4 + apps/memcached/service.yaml | 12 ++ apps/openproject/configmap_core.yaml | 21 +++ apps/openproject/configmap_memcached.yaml | 9 + apps/openproject/db-init-job.yaml | 51 ++++++ apps/openproject/ingress.yaml | 23 +++ apps/openproject/kustomization.yaml | 21 +++ apps/openproject/manifest.yaml | 32 ++++ apps/openproject/namespace.yaml | 4 + apps/openproject/persistentvolumeclaim.yaml | 12 ++ apps/openproject/seeder-job.yaml | 138 +++++++++++++++ apps/openproject/service.yaml | 16 ++ apps/openproject/serviceaccount.yaml | 7 + apps/openproject/web-deployment.yaml | 181 ++++++++++++++++++++ apps/openproject/worker-deployment.yaml | 151 ++++++++++++++++ bin/wild-app-add | 62 ++++--- bin/wild-app-fetch | 2 +- bin/wild-setup-scaffold | 3 +- scripts/common.sh | 2 +- 25 files changed, 876 insertions(+), 47 deletions(-) create mode 100644 apps/memcached/deployment.yaml create mode 100644 apps/memcached/kustomization.yaml create mode 100644 apps/memcached/manifest.yaml create mode 100644 apps/memcached/namespace.yaml create mode 100644 apps/memcached/service.yaml create mode 100644 apps/openproject/configmap_core.yaml create mode 100644 apps/openproject/configmap_memcached.yaml create mode 100644 apps/openproject/db-init-job.yaml create mode 100644 apps/openproject/ingress.yaml create mode 100644 apps/openproject/kustomization.yaml create mode 100644 apps/openproject/manifest.yaml create mode 100644 apps/openproject/namespace.yaml create mode 100644 apps/openproject/persistentvolumeclaim.yaml create mode 100644 apps/openproject/seeder-job.yaml create mode 100644 apps/openproject/service.yaml create mode 100644 apps/openproject/serviceaccount.yaml create mode 100644 apps/openproject/web-deployment.yaml create mode 100644 apps/openproject/worker-deployment.yaml diff --git a/.cspell/custom-dictionary-workspace.txt b/.cspell/custom-dictionary-workspace.txt index ad3a79f..d08c298 100644 --- a/.cspell/custom-dictionary-workspace.txt +++ b/.cspell/custom-dictionary-workspace.txt @@ -18,6 +18,7 @@ Jellyfin keepalives KUBECONFIG kubernetescrd +kustomization letsencrypt metallb NEXTCLOUD @@ -25,6 +26,7 @@ nftables NXDOMAIN OBJECTSTORE objref +openproject OVERWRITECLIURL OVERWRITEHOST OVERWRITEPROTOCOL diff --git a/apps/README.md b/apps/README.md index 2b4f56a..c454b58 100644 --- a/apps/README.md +++ b/apps/README.md @@ -6,7 +6,7 @@ This repository contains a collection of apps that can be deployed using Wild Cl ## App Structure -Each subdirectory in this directory represents a Wild Cloud app. Each app directory contains a `manifest.yaml` file and other necessary Kustomize files. +Each subdirectory in this directory represents a Wild Cloud app. Each app directory contains an "app manifest" (`manifest.yaml`), a "kustomization" (`kustomization.yaml`), and one or more "configurations" (yaml files containing definitions/configurations of Kubernetes objects/resources). ### App Manifest @@ -49,15 +49,86 @@ Explanation of the fields: - `defaultConfig`: A set of default configuration values for the app. When an app is added using `wild-app-add`, these values will be added to the Wild Cloud `config.yaml` file. - `requiredSecrets`: A list of secrets that must be set in the Wild Cloud `secrets.yaml` file for the app to function properly. These secrets are typically sensitive information like database passwords or API keys. Keys with random values will be generated automatically when the app is added. -### Kustomize Files +### Kustomization + +Each app directory should also contain a `kustomization.yaml` file. This file defines how the app's Kubernetes resources are built and deployed. It can include references to other Kustomize files, patches, and configurations. + +Here is an example `kustomization.yaml` file for the "immich" app: + +```yaml +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +namespace: immich +labels: + - includeSelectors: true + pairs: + app: immich + managedBy: kustomize + partOf: wild-cloud +resources: + - deployment-server.yaml + - deployment-machine-learning.yaml + - deployment-microservices.yaml + - ingress.yaml + - namespace.yaml + - pvc.yaml + - service.yaml + - db-init-job.yaml + ``` + +Kustomization requirements: + +- Every Wild Cloud kustomization should include the Wild Cloud labels in its `kustomization.yaml` file. This allows the Wild Cloud to identify and manage the app correctly. The labels should be defined under the `labels` key, as shown in the example above. +- The `app` label and `namespace` keys should the app's name/directory. + +#### Standard Wild Cloud Labels + +Wild Cloud uses a consistent labeling strategy across all apps: + +```yaml +labels: + - includeSelectors: true + pairs: + app: myapp # The app name (matches directory) + managedBy: kustomize # Managed by Kustomize + partOf: wild-cloud # Part of Wild Cloud ecosystem +``` + +The `includeSelectors: true` setting automatically applies these labels to all resources AND their selectors, which means: + +1. **Resource labels** - All resources get the standard Wild Cloud labels +2. **Selector labels** - All selectors automatically include these labels for robust selection + +This allows individual resources to use simple, component-specific selectors: + +```yaml +selector: + matchLabels: + component: web +``` + +Which Kustomize automatically expands to: + +```yaml +selector: + matchLabels: + app: myapp + component: web + managedBy: kustomize + partOf: wild-cloud +``` + +### Configuration Files Wild Cloud apps use Kustomize as kustomize files are simple, transparent, and easier to manage in a Git repository. -#### Configuration (templates) +#### Templates -The only non-standard feature of Wild Cloud apps is the use of Wild Cloud configuration variables in the Kustomize files, such as `{{ .cloud.domain }}` for the domain name. This allows for dynamic configuration based on the operator's Wild Cloud configuration and secrets. All configuration variables need to exist in the operator's `config.yaml`, so they should be defined in the app's `manifest.yaml` under `defaultConfig`. +For operators, Wild Cloud apps use standard configuration files. This makes modifying the app's configuration straightforward, as operators can customize their app files as needed. They can choose to manage modifications and updates directly on the configuration files using `git` tools, or they can use Kustomize patches or overlays. As a convenience for operators, when adding an app (using `wild-app-add`), the app's configurations will be compiled with the operator's Wild Cloud configuration and secrets. This results in standard Kustomize files being placed in the Wild Cloud home directory, which can then be modified as needed. This means the configuration files in this repository are actually templates, but they will be compiled into standard Kustomize files when the app is added to an operator's Wild Cloud home directory. -When `wild-app-add` is run, the app's Kustomize files will be compiled with the operator's Wild Cloud configuration and secrets resulting in standard Kustomize files being placed in the Wild Cloud home directory. This makes modifying the app's configuration straightforward, as operators can customize their app files as needed. When changes are pulled from upstream, the operator can run `wild-app-add` again to update their local configuration and Kustomize files and then view the changes with `git status` to see what has changed. +To reference operator configuration in the configuration files, use gomplate variables, such as `{{ .cloud.domain }}` for the domain name. All configuration variables you use need to exist in the operator's `config.yaml`, so they should be either standard Wild Cloud operator variables, or be defined in the app's `manifest.yaml` under `defaultConfig`. + +When `wild-app-add` is run, the app's Kustomize files will be compiled with the operator's Wild Cloud configuration and secrets resulting in standard Kustomize files being placed in the Wild Cloud home directory. #### Secrets @@ -91,9 +162,6 @@ If you would like to contribute an app to the Wild Cloud, issue a pull request w ### Converting from Helm Charts - -# Converting Helm Charts to Wild Cloud Kustomize definitions - Wild Cloud apps use Kustomize as kustomize files are simpler, more transparent, and easier to manage in a Git repository than Helm charts. If you have a Helm chart that you want to convert to a Wild Cloud app, the following example steps can simplify the process for you: ```bash @@ -109,5 +177,13 @@ cd base/nginx-ingress kustomize create --autodetect ``` -After running these commands against your own Helm chart, you will have a Kustomize directory structure that can be used as a Wild Cloud app. All you need to do then, usually, is add a `manifest.yaml` file and replace any hardcoded values with Wild Cloud variables, such as `{{ .cloud.domain }}` for the domain name. +After running these commands against your own Helm chart, you will have a Kustomize directory structure that can be used as a Wild Cloud app. All you need to do then, usually, is: +- add an app manifest (a `manifest.yaml` file). +- replace any hardcoded operator values with Wild Cloud operator variables, such as `{{ .cloud.domain }}` for the domain name. +- modify how secrets are referenced in the Kustomize files (see above) +- update labels and selectors to use the Wild Cloud standard: + - Replace complex Helm labels (like `app.kubernetes.io/name`, `app.kubernetes.io/instance`) with simple component labels + - Use `component: web`, `component: worker`, etc. in selectors and pod template labels + - Let Kustomize handle the common labels (`app`, `managedBy`, `partOf`) automatically +- remove any Helm-specific labels from the Kustomize files, as Wild Cloud apps do not use Helm labels. diff --git a/apps/immich/db-init-job.yaml b/apps/immich/db-init-job.yaml index e9a9614..888a3cf 100644 --- a/apps/immich/db-init-job.yaml +++ b/apps/immich/db-init-job.yaml @@ -7,7 +7,7 @@ spec: spec: containers: - name: db-init - image: postgres:15 + image: {{ .apps.postgres.image }} command: ["/bin/bash", "-c"] args: - | diff --git a/apps/memcached/deployment.yaml b/apps/memcached/deployment.yaml new file mode 100644 index 0000000..25db50f --- /dev/null +++ b/apps/memcached/deployment.yaml @@ -0,0 +1,42 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: memcached +spec: + replicas: {{ .apps.memcached.replicas }} + selector: + matchLabels: + component: cache + template: + metadata: + labels: + component: cache + spec: + containers: + - name: memcached + image: {{ .apps.memcached.image }} + ports: + - containerPort: {{ .apps.memcached.port }} + name: memcached + args: + - -m + - {{ .apps.memcached.memoryLimit }} + - -c + - "{{ .apps.memcached.maxConnections }}" + - -p + - "{{ .apps.memcached.port }}" + resources: + requests: + memory: {{ .apps.memcached.resources.requests.memory }} + cpu: {{ .apps.memcached.resources.requests.cpu }} + limits: + memory: {{ .apps.memcached.resources.limits.memory }} + cpu: {{ .apps.memcached.resources.limits.cpu }} + securityContext: + runAsNonRoot: true + runAsUser: 11211 + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true \ No newline at end of file diff --git a/apps/memcached/kustomization.yaml b/apps/memcached/kustomization.yaml new file mode 100644 index 0000000..2d25ded --- /dev/null +++ b/apps/memcached/kustomization.yaml @@ -0,0 +1,13 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +namespace: memcached +labels: + - includeSelectors: true + pairs: + app: memcached + managedBy: kustomize + partOf: wild-cloud +resources: +- namespace.yaml +- deployment.yaml +- service.yaml diff --git a/apps/memcached/manifest.yaml b/apps/memcached/manifest.yaml new file mode 100644 index 0000000..57ed9f9 --- /dev/null +++ b/apps/memcached/manifest.yaml @@ -0,0 +1,19 @@ +name: memcached +description: Memcached is an in-memory key-value store for small chunks of arbitrary data, commonly used as a cache layer. +version: 1.6.32 +icon: https://memcached.org/memcached-logo.png +requires: [] +defaultConfig: + image: memcached:1.6.32-alpine + port: 11211 + memoryLimit: 64m + maxConnections: 1024 + replicas: 1 + resources: + requests: + memory: 64Mi + cpu: 100m + limits: + memory: 128Mi + cpu: 200m +requiredSecrets: [] \ No newline at end of file diff --git a/apps/memcached/namespace.yaml b/apps/memcached/namespace.yaml new file mode 100644 index 0000000..76b7af5 --- /dev/null +++ b/apps/memcached/namespace.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: memcached \ No newline at end of file diff --git a/apps/memcached/service.yaml b/apps/memcached/service.yaml new file mode 100644 index 0000000..883f1b3 --- /dev/null +++ b/apps/memcached/service.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Service +metadata: + name: memcached +spec: + ports: + - port: {{ .apps.memcached.port }} + targetPort: {{ .apps.memcached.port }} + protocol: TCP + name: memcached + selector: + component: cache \ No newline at end of file diff --git a/apps/openproject/configmap_core.yaml b/apps/openproject/configmap_core.yaml new file mode 100644 index 0000000..92ff304 --- /dev/null +++ b/apps/openproject/configmap_core.yaml @@ -0,0 +1,21 @@ +--- +# Source: openproject/templates/secret_core.yaml +apiVersion: "v1" +kind: "ConfigMap" +metadata: + name: "openproject-core" +data: + DATABASE_HOST: "{{ .apps.openproject.dbHostname }}" + DATABASE_PORT: "5432" + DATABASE_URL: "postgresql://{{ .apps.openproject.dbUsername }}@{{ .apps.openproject.dbHostname }}:5432/{{ .apps.openproject.dbName }}" + OPENPROJECT_SEED_ADMIN_USER_PASSWORD_RESET: "{{ .apps.openproject.adminPasswordReset }}" + OPENPROJECT_SEED_ADMIN_USER_NAME: "{{ .apps.openproject.adminUserName }}" + OPENPROJECT_SEED_ADMIN_USER_MAIL: "{{ .apps.openproject.adminUserEmail }}" + OPENPROJECT_HTTPS: "{{ .apps.openproject.https }}" + OPENPROJECT_SEED_LOCALE: "{{ .apps.openproject.seedLocale }}" + OPENPROJECT_HOST__NAME: "{{ .apps.openproject.domain }}" + OPENPROJECT_HSTS: "{{ .apps.openproject.hsts }}" + OPENPROJECT_RAILS__CACHE__STORE: "{{ .apps.openproject.cacheStore }}" + OPENPROJECT_RAILS__RELATIVE__URL__ROOT: "{{ .apps.openproject.railsRelativeUrlRoot }}" + POSTGRES_STATEMENT_TIMEOUT: "{{ .apps.openproject.postgresStatementTimeout }}" +... diff --git a/apps/openproject/configmap_memcached.yaml b/apps/openproject/configmap_memcached.yaml new file mode 100644 index 0000000..8b7a4f7 --- /dev/null +++ b/apps/openproject/configmap_memcached.yaml @@ -0,0 +1,9 @@ +--- +# Source: openproject/templates/secret_memcached.yaml +apiVersion: "v1" +kind: "ConfigMap" +metadata: + name: "openproject-memcached" +data: + OPENPROJECT_CACHE__MEMCACHE__SERVER: "{{ .apps.openproject.memcachedHostname }}:{{ .apps.openproject.memcachedPort }}" +... diff --git a/apps/openproject/db-init-job.yaml b/apps/openproject/db-init-job.yaml new file mode 100644 index 0000000..1475c67 --- /dev/null +++ b/apps/openproject/db-init-job.yaml @@ -0,0 +1,51 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: openproject-db-init + labels: + component: db-init +spec: + template: + metadata: + labels: + component: db-init + spec: + containers: + - name: db-init + image: {{ .apps.postgres.image }} + command: ["/bin/bash", "-c"] + args: + - | + PGPASSWORD=${POSTGRES_ADMIN_PASSWORD} psql -h ${DB_HOSTNAME} -U postgres < "$temp_manifest" + yq eval ".apps.${APP_NAME} = (.apps.${APP_NAME} // {}) * load(\"$temp_manifest\")" -i "${CONFIG_FILE}" + rm "$temp_manifest" + + # Process template variables in the merged config + echo "Processing template variables in app config" + temp_config=$(mktemp) + + # Build gomplate command with config context + gomplate_cmd="gomplate -c .=${CONFIG_FILE}" + + # Add secrets context if secrets.yaml exists + if [ -f "${SECRETS_FILE}" ]; then + gomplate_cmd="${gomplate_cmd} -c secrets=${SECRETS_FILE}" + fi + + # Process the entire config file through gomplate to resolve template variables + ${gomplate_cmd} -f "${CONFIG_FILE}" > "$temp_config" + mv "$temp_config" "${CONFIG_FILE}" + echo "Merged defaultConfig for app '${APP_NAME}'" fi diff --git a/bin/wild-app-fetch b/bin/wild-app-fetch index 9edf34a..c8579f6 100755 --- a/bin/wild-app-fetch +++ b/bin/wild-app-fetch @@ -47,7 +47,7 @@ fi # Initialize Wild Cloud environment if [ -z "${WC_ROOT}" ]; then - print "WC_ROOT is not set." + echo "WC_ROOT is not set." exit 1 else source "${WC_ROOT}/scripts/common.sh" diff --git a/bin/wild-setup-scaffold b/bin/wild-setup-scaffold index 4d593c2..3ff4bc4 100755 --- a/bin/wild-setup-scaffold +++ b/bin/wild-setup-scaffold @@ -51,11 +51,10 @@ done # Initialize Wild Cloud environment if [ -z "${WC_ROOT}" ]; then - print "WC_ROOT is not set." + echo "WC_ROOT is not set." exit 1 else source "${WC_ROOT}/scripts/common.sh" - init_wild_env fi TEMPLATE_DIR="${WC_ROOT}/setup/home-scaffold" diff --git a/scripts/common.sh b/scripts/common.sh index e2bba4a..5f38c5e 100644 --- a/scripts/common.sh +++ b/scripts/common.sh @@ -184,7 +184,7 @@ find_wc_home() { # Call this function at the beginning of scripts init_wild_env() { if [ -z "${WC_ROOT}" ]; then - print "Fail" + echo "ERROR: WC_ROOT is not set." exit 1 else