diff --git a/.gitignore b/.gitignore index 4b220dae..7caad1e2 100644 --- a/.gitignore +++ b/.gitignore @@ -31,6 +31,21 @@ Thumbs.db *.pyd __pycache__/ +# Terraform +*.tfvars +*.terraform +*.tfplan +*.tfstate +*.tfstate.backup +*.tfvars.json +*.tfvars.json.lock + +# Misc +.env + +# Cursor +.cursor/ + # Mlflow specific applications/mlflow/tests/.venv/ **/charts/.rendered-templates/ diff --git a/applications/n8n/.terraform.lock.hcl b/applications/n8n/.terraform.lock.hcl new file mode 100644 index 00000000..fbd5e99b --- /dev/null +++ b/applications/n8n/.terraform.lock.hcl @@ -0,0 +1,60 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/helm" { + version = "2.17.0" + hashes = [ + "h1:kQMkcPVvHOguOqnxoEU2sm1ND9vCHiT8TvZ2x6v/Rsw=", + "zh:06fb4e9932f0afc1904d2279e6e99353c2ddac0d765305ce90519af410706bd4", + "zh:104eccfc781fc868da3c7fec4385ad14ed183eb985c96331a1a937ac79c2d1a7", + "zh:129345c82359837bb3f0070ce4891ec232697052f7d5ccf61d43d818912cf5f3", + "zh:3956187ec239f4045975b35e8c30741f701aa494c386aaa04ebabffe7749f81c", + "zh:66a9686d92a6b3ec43de3ca3fde60ef3d89fb76259ed3313ca4eb9bb8c13b7dd", + "zh:88644260090aa621e7e8083585c468c8dd5e09a3c01a432fb05da5c4623af940", + "zh:a248f650d174a883b32c5b94f9e725f4057e623b00f171936dcdcc840fad0b3e", + "zh:aa498c1f1ab93be5c8fbf6d48af51dc6ef0f10b2ea88d67bcb9f02d1d80d3930", + "zh:bf01e0f2ec2468c53596e027d376532a2d30feb72b0b5b810334d043109ae32f", + "zh:c46fa84cc8388e5ca87eb575a534ebcf68819c5a5724142998b487cb11246654", + "zh:d0c0f15ffc115c0965cbfe5c81f18c2e114113e7a1e6829f6bfd879ce5744fbb", + "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", + ] +} + +provider "registry.terraform.io/hashicorp/kubernetes" { + version = "2.25.2" + constraints = "~> 2.25.2" + hashes = [ + "h1:T1WAQt40cAk721H0AM/eZ5YuodJaIfS8r3Tu7rKCJJE=", + "zh:044788ac936e0e8ece8f78a2e4e366ecd435ea8235388eaf2cbc8e7975d9d970", + "zh:24f5ff01df91f51f00ee7ff39430adeb63bb2ca4ea0042e68f06d6b65808c02f", + "zh:49984aa0aa1faa8c4f01e8faa039322f1e6fdaeab0b7e32f5c6e96edfde36a38", + "zh:4eeceaff56bac9fc782e7e33f157fa2c7e9a47b2c3c3d12da2642c312ace73f6", + "zh:4f49b6419345960d5af475e0200c243af4c9c140b0ee64799fe1fc9b023c49ea", + "zh:7958414d516867a2263a978792a24843f80023fb233cf051ff4095adc9803d85", + "zh:c633a755fc95e9ff0cd73656f052947afd85883a0987dde5198113aa48474156", + "zh:cbfe958d119795004ce1e8001449d01c056fa2a062b51d07843d98be216337d7", + "zh:cfb85392e18768578d4c943438897083895719be678227fd90efbe3500702a56", + "zh:d705a661ed5da425dd236a48645bec39fe78a67d2e70e8460b720417cbf260ac", + "zh:ddd7a01263da3793df4f3b5af65f166307eed5acf525e51e058cda59009cc856", + "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", + ] +} + +provider "registry.terraform.io/hashicorp/random" { + version = "3.7.1" + hashes = [ + "h1:t152MY0tQH4a8fLzTtEWx70ITd3azVOrFDn/pQblbto=", + "zh:3193b89b43bf5805493e290374cdda5132578de6535f8009547c8b5d7a351585", + "zh:3218320de4be943e5812ed3de995946056db86eb8d03aa3f074e0c7316599bef", + "zh:419861805a37fa443e7d63b69fb3279926ccf98a79d256c422d5d82f0f387d1d", + "zh:4df9bd9d839b8fc11a3b8098a604b9b46e2235eb65ef15f4432bde0e175f9ca6", + "zh:5814be3f9c9cc39d2955d6f083bae793050d75c572e70ca11ccceb5517ced6b1", + "zh:63c6548a06de1231c8ee5570e42ca09c4b3db336578ded39b938f2156f06dd2e", + "zh:697e434c6bdee0502cc3deb098263b8dcd63948e8a96d61722811628dce2eba1", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:a0b8e44927e6327852bbfdc9d408d802569367f1e22a95bcdd7181b1c3b07601", + "zh:b7d3af018683ef22794eea9c218bc72d7c35a2b3ede9233b69653b3c782ee436", + "zh:d63b911d618a6fe446c65bfc21e793a7663e934b2fef833d42d3ccd38dd8d68d", + "zh:fa985cd0b11e6d651f47cff3055f0a9fd085ec190b6dbe99bf5448174434cdea", + ] +} diff --git a/applications/n8n/charts/n8n-values.yaml b/applications/n8n/charts/n8n-values.yaml new file mode 100644 index 00000000..1e143f0d --- /dev/null +++ b/applications/n8n/charts/n8n-values.yaml @@ -0,0 +1,67 @@ +#Prod like set up with CloudNativePG and nginx-ingress +main: + config: + db: + type: postgresdb + postgresdb: + host: db-rw + user: n8n + # password: password is read from cnpg db-app secretKeyRef + pool: + size: 10 + ssl: + enabled: true + reject_Unauthorized: true + ca_file: "/home/ssl/certs/postgresql/ca.crt" + # comment out as we are experiment with Terraform template + # secret: + # n8n: + # encryption_key: "thisistheway" + + extraEnv: + DB_POSTGRESDB_PASSWORD: + valueFrom: + secretKeyRef: + name: db-app + key: password + # Mount the CNPG CA Cert into N8N container + extraVolumeMounts: + - name: db-ca-cert + mountPath: /home/ssl/certs/postgresql + readOnly: true + extraVolumes: + - name: db-ca-cert + secret: + secretName: db-ca + items: + - key: ca.crt + path: ca.crt + resources: + limits: + memory: 2048Mi + requests: + memory: 512Mi +ingress: + enabled: false +# cnpg DB cluster request +extraManifests: + - apiVersion: postgresql.cnpg.io/v1 + kind: Cluster + metadata: + name: db + spec: + instances: 1 + bootstrap: + initdb: + database: n8n + owner: n8n + postgresql: + parameters: + shared_buffers: "64MB" + resources: + requests: + memory: "512Mi" + limits: + memory: "512Mi" + storage: + size: 1Gi diff --git a/applications/n8n/charts/n8n/Chart.lock b/applications/n8n/charts/n8n/Chart.lock new file mode 100644 index 00000000..c94d98b1 --- /dev/null +++ b/applications/n8n/charts/n8n/Chart.lock @@ -0,0 +1,9 @@ +dependencies: +- name: valkey + repository: oci://registry-1.docker.io/bitnamicharts + version: 2.4.7 +- name: replicated + repository: oci://registry.replicated.com/library + version: 1.5.0 +digest: sha256:1eb6a06e563152b56016188710d829b5caab37ba6da16470b0779007779b326f +generated: "2025-04-10T09:43:54.914664+10:00" diff --git a/applications/n8n/charts/n8n/Chart.yaml b/applications/n8n/charts/n8n/Chart.yaml new file mode 100644 index 00000000..6b7b0306 --- /dev/null +++ b/applications/n8n/charts/n8n/Chart.yaml @@ -0,0 +1,40 @@ +annotations: + artifacthub.io/changes: | + - kind: fixed + description: "fix https://github.com/8gears/n8n-helm-chart/issues/176" + artifacthub.io/prerelease: "false" +apiVersion: v2 +appVersion: 1.85.1 +dependencies: +- condition: valkey.enabled + name: valkey + repository: oci://registry-1.docker.io/bitnamicharts + version: 2.4.7 +- name: replicated + repository: oci://registry.replicated.com/library + version: 1.5.0 +description: Helm Chart for deploying n8n on Kubernetes, a fair-code workflow automation + platform with native AI capabilities for technical teams. Easily automate tasks + across different services. +home: https://github.com/8gears/n8n-helm-chart +icon: https://avatars1.githubusercontent.com/u/45487711?s=200&v=4 +keywords: +- Workflow Automation +- Workflow +- iPaaS +- integration-framework +- low-code-plattform +- low-code +maintainers: +- email: contact@8gears.com + name: 8gears + url: https://github.com/8gears +- email: _@8gears.com + name: n8n + url: https://github.com/n8n-io +name: n8n +sources: +- https://github.com/n8n-io/n8n +- https://n8n.io/ +type: application +version: 1.0.6 diff --git a/applications/n8n/charts/n8n/LICENSE b/applications/n8n/charts/n8n/LICENSE new file mode 100644 index 00000000..261eeb9e --- /dev/null +++ b/applications/n8n/charts/n8n/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/applications/n8n/charts/n8n/README.md b/applications/n8n/charts/n8n/README.md new file mode 100644 index 00000000..a0e8946e --- /dev/null +++ b/applications/n8n/charts/n8n/README.md @@ -0,0 +1,835 @@ +[![Artifact HUB](https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/n8n)](https://artifacthub.io/packages/helm/open-8gears/n8n) + +> [!NOTE] +> The n8n Helm chart is growing in popularity. +> We're looking for additional passionate maintainers and contributors +> to improve and maintain this chart, governance, development, documentation and CI/CD workflows. +> If you're interested in making a difference, +> [join the discussion](https://github.com/8gears/n8n-helm-chart/discussions/90). + +> [!WARNING] +> Version 1.0.0 of this Chart includes breaking changes and is not backwards compatible with previous versions. +> Please review the migration guide below before upgrading. +> + + +# n8n Helm Chart for Kubernetes + +[n8n](https://github.com/n8n-io/n8n) is an extendable workflow automation tool. + + + +The Helm chart source code location is [github.com/8gears/n8n-helm-chart](https://github.com/8gears/n8n-helm-chart) + +## Requirements + +Before you start, make sure you have the following tools ready: + +- Helm >= 3.8 +- external Postgres DB or embedded SQLite (SQLite is bundled with n8n) +- Helmfile (Optional) + +## Overview + +The `values.yaml` file is divided into a multiple n8n and Kubernetes specific sections. + +1. Global and chart wide values, like the image repository, image tag, etc. +2. Ingress, (default is nginx, but you can change it to your own ingress controller) +3. Main n8n app configuration + Kubernetes specific settings +4. Worker related settings + Kubernetes specific settings +5. Webhook related settings + Kubernetes specific settings +6. Raw Resources to pass through your own manifests like GatewayAPI, ServiceMonitor etc. +7. Redis related settings + Kubernetes specific settings + +## Setting Configuration Values and Environment Variables + +These n8n specific settings should be added to `main.config:` or `main.secret:` in the `values.yaml` file. + +See the [example](#examples) section and other example in the `/examples` directory of this repo. + +> [!IMPORTANT] +> The YAML nodes `config` and `secret` in the values.yaml are transformed 1:1 into ENV variables. + +```yaml +main: + config: + n8n: + encryption_key: "my_secret" # ==> turns into ENV: N8N_ENCRYPTION_KEY=my_secret + db: + type: postgresdb # ==> turns into ENV: DB_TYPE=postgresdb + postgresdb: + host: 192.168.0.52 # ==> turns into ENV: DB_POSTGRESDB_HOST=192.168.0.52 + node: + function_allow_builtin: "*" # ==> turns into ENV: NODE_FUNCTION_ALLOW_BUILTIN="*" +``` + +Consult the [n8n Environment Variables Documentation]( https://docs.n8n.io/hosting/configuration/environment-variables/) + +You decide what should go into `secret` and what should be a `config`. +There is no restriction, mix and match as you like. + +# Installation + +Install chart + +```shell +helm install my-n8n oci://8gears.container-registry.com/library/n8n --version 1.0.0 +``` + +# Examples + +A typical example of a config in combination with a secret. +You can find various other examples in the `examples` directory of this repository. + +```yaml +#small deployment with nodeport for local testing or small deployments +main: + config: + n8n: + hide_usage_page: true + secret: + n8n: + encryption_key: "" + resources: + limits: + memory: 2048Mi + requests: + memory: 512Mi + service: + type: NodePort + port: 5678 +``` + +# Values File + +## N8N Specific Config Section + +Every possible n8n config value can be set, +even if it is not mentioned in the excerpt below. +Treat the n8n provided configuration documentation as the source of truth, +this Charts just forwards everything down to the n8n pods. + +```yaml + +image: + repository: n8nio/n8n + pullPolicy: IfNotPresent + # Overrides the image tag whose default is the chart appVersion. + tag: "" +imagePullSecrets: [] + +# The Name to use for the chart. Will be the prefix of all resources aka. The Chart.Name (default is 'n8n') +nameOverride: +# Override the full name of the deployment. When empty, the name will be "{release-name}-{chart-name}" or the value of nameOverride if specified +fullnameOverride: + +# Add entries to a pod's /etc/hosts file, mapping custom IP addresses to hostnames. +hostAliases: [] + #- ip: 8.8.8.8 + # hostnames: +# - service-example.local +# +# Ingress +# +ingress: + enabled: false + annotations: {} + # define a custom ingress class Name, like "traefik" or "nginx" + className: "" + hosts: + - host: workflow.example.com + paths: [] + tls: + - hosts: + - workflow.example.com + secretName: host-domain-cert + +# the main (n8n) application related configuration + Kubernetes specific settings +# The config: {} dictionary is converted to environmental variables in the ConfigMap. +main: + # See https://docs.n8n.io/hosting/configuration/environment-variables/ for all values. + config: {} + # n8n: + # db: + # type: postgresdb + # postgresdb: + # host: 192.168.0.52 + + # Dictionary for secrets, unlike config:, the values here will end up in the secret file. + # The YAML entry db.postgresdb.password: my_secret is transformed DB_POSTGRESDB_password=bXlfc2VjcmV0 + # See https://docs.n8n.io/hosting/configuration/environment-variables/ + secret: {} + # n8n: + # if you run n8n stateless, you should provide an encryption key here. + # encryption_key: + # + # database: + # postgresdb: + # password: 'big secret' + + # Extra environmental variables, so you can reference other configmaps and secrets into n8n as env vars. + extraEnv: + # N8N_DB_POSTGRESDB_NAME: + # valueFrom: + # secretKeyRef: + # name: db-app + # key: dbname + # + # N8n Kubernetes specific settings + # + persistence: + # If true, use a Persistent Volume Claim, If false, use emptyDir + enabled: false + # what type volume, possible options are [existing, emptyDir, dynamic] dynamic for Dynamic Volume Provisioning, existing for using an existing Claim + type: emptyDir + # Persistent Volume Storage Class + # If defined, storageClassName: + # If set to "-", storageClassName: "", which disables dynamic provisioning + # If undefined (the default) or set to null, no storageClassName spec is + # set, choosing the default provisioner. (gp2 on AWS, standard on + # GKE, AWS & OpenStack) + # + # storageClass: "-" + # PVC annotations + # + # If you need this annotation include it under `values.yml` file and pvc.yml template will add it. + # This is not maintained at Helm v3 anymore. + # https://github.com/8gears/n8n-helm-chart/issues/8 + # + # annotations: + # helm.sh/resource-policy: keep + # Persistent Volume Access Mode + # + accessModes: + - ReadWriteOnce + # Persistent Volume size + size: 1Gi + # Use an existing PVC + # existingClaim: + + extraVolumes: [] + # - name: db-ca-cert + # secret: + # secretName: db-ca + # items: + # - key: ca.crt + # path: ca.crt + + extraVolumeMounts: [] + # - name: db-ca-cert + # mountPath: /etc/ssl/certs/postgresql + # readOnly: true + + + # Number of desired pods. More than one pod is supported in n8n enterprise. + replicaCount: 1 + + # here you can specify the deployment strategy as Recreate or RollingUpdate with optional maxSurge and maxUnavailable + # If these options are not set, default values are 25% + # deploymentStrategy: + # type: Recreate | RollingUpdate + # maxSurge: "50%" + # maxUnavailable: "50%" + + deploymentStrategy: + type: "Recreate" + # maxSurge: "50%" + # maxUnavailable: "50%" + + serviceAccount: + # Specifies whether a service account should be created + create: true + # Annotations to add to the service account + annotations: {} + # The name of the service account to use. + # If not set and create is true, a name is generated using the fullname template + name: "" + + # Annotations to be implemented on the main service deployment + deploymentAnnotations: {} + # Labels to be implemented on the main service deployment + deploymentLabels: {} + # Annotations to be implemented on the main service pod + podAnnotations: {} + # Labels to be implemented on the main service pod + podLabels: {} + + podSecurityContext: + runAsNonRoot: true + runAsUser: 1000 + runAsGroup: 1000 + fsGroup: 1000 + + securityContext: {} + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 + + # here you can specify lifecycle hooks - it can be used e.g., to easily add packages to the container without building + # your own docker image + # see https://github.com/8gears/n8n-helm-chart/pull/30 + lifecycle: {} + + # here's the sample configuration to add mysql-client to the container + # lifecycle: + # postStart: + # exec: + # command: ["/bin/sh", "-c", "apk add mysql-client"] + + # here you can override a command for main container + # it may be used to override a starting script (e.g., to resolve issues like https://github.com/n8n-io/n8n/issues/6412) or run additional preparation steps (e.g., installing additional software) + command: [] + + # sample configuration that overrides starting script and solves above issue (also it runs n8n as root, so be careful): + # command: + # - tini + # - -- + # - /bin/sh + # - -c + # - chmod o+rx /root; chown -R node /root/.n8n || true; chown -R node /root/.n8n; ln -s /root/.n8n /home/node; chown -R node /home/node || true; node /usr/local/bin/n8n + + # here you can override the livenessProbe for the main container + # it may be used to increase the timeout for the livenessProbe (e.g., to resolve issues like + + livenessProbe: + httpGet: + path: /healthz + port: http + # initialDelaySeconds: 30 + # periodSeconds: 10 + # timeoutSeconds: 5 + # failureThreshold: 6 + # successThreshold: 1 + + # here you can override the readinessProbe for the main container + # it may be used to increase the timeout for the readinessProbe (e.g., to resolve issues like + + readinessProbe: + httpGet: + path: /healthz + port: http + # initialDelaySeconds: 30 + # periodSeconds: 10 + # timeoutSeconds: 5 + # failureThreshold: 6 + # successThreshold: 1 + + # List of initialization containers belonging to the pod. Init containers are executed in order prior to containers being started. + # See https://kubernetes.io/docs/concepts/workloads/pods/init-containers/ + initContainers: [] + # - name: init-data-dir + # image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + # command: [ "/bin/sh", "-c", "mkdir -p /home/node/.n8n/" ] + # volumeMounts: + # - name: data + # mountPath: /home/node/.n8n + + + service: + annotations: {} + # -- Service types allow you to specify what kind of Service you want. + # E.g., ClusterIP, NodePort, LoadBalancer, ExternalName + type: ClusterIP + # -- Service port + port: 80 + + resources: {} + # We usually recommend not specifying default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + + autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 100 + targetCPUUtilizationPercentage: 80 + # targetMemoryUtilizationPercentage: 80 + + nodeSelector: {} + tolerations: [] + affinity: {} + +# # # # # # # # # # # # # # # # +# +# Worker related settings +# +worker: + enabled: false + + # additional (to main) config for worker + config: {} + + # additional (to main) config for worker + secret: {} + + # Extra environmental variables, so you can reference other configmaps and secrets into n8n as env vars. + extraEnv: {} + + # Define the number of jobs a worker can run in parallel by using the concurrency flag. Default is 10 + concurrency: 10 + + # + # Worker Kubernetes specific settings + # + persistence: + # If true, use a Persistent Volume Claim, If false, use emptyDir + enabled: false + # what type volume, possible options are [existing, emptyDir, dynamic] dynamic for Dynamic Volume Provisioning, existing for using an existing Claim + type: emptyDir + # Persistent Volume Storage Class + # If defined, storageClassName: + # If set to "-", storageClassName: "", which disables dynamic provisioning + # If undefined (the default) or set to null, no storageClassName spec is + # set, choosing the default provisioner. (gp2 on AWS, standard on + # GKE, AWS & OpenStack) + # + # storageClass: "-" + # PVC annotations + # + # If you need this annotation include it under `values.yml` file and pvc.yml template will add it. + # This is not maintained at Helm v3 anymore. + # https://github.com/8gears/n8n-helm-chart/issues/8 + # + # annotations: + # helm.sh/resource-policy: keep + # Persistent Volume Access Mode + accessModes: + - ReadWriteOnce + # Persistent Volume size + size: 1Gi + # Use an existing PVC + # existingClaim: + + # Number of desired pods. + replicaCount: 1 + + # here you can specify the deployment strategy as Recreate or RollingUpdate with optional maxSurge and maxUnavailable + # If these options are not set, default values are 25% + # deploymentStrategy: + # type: RollingUpdate + # maxSurge: "50%" + # maxUnavailable: "50%" + + deploymentStrategy: + type: "Recreate" + # maxSurge: "50%" + # maxUnavailable: "50%" + + serviceAccount: + # Specifies whether a service account should be created + create: true + # Annotations to add to the service account + annotations: {} + # The name of the service account to use. + # If not set and create is true, a name is generated using the fullname template + name: "" + + # Annotations to be implemented on the worker deployment + deploymentAnnotations: {} + # Labels to be implemented on the worker deployment + deploymentLabels: {} + # Annotations to be implemented on the worker pod + podAnnotations: {} + # Labels to be implemented on the worker pod + podLabels: {} + + podSecurityContext: + runAsNonRoot: true + runAsUser: 1000 + runAsGroup: 1000 + fsGroup: 1000 + + securityContext: {} + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 + + # here you can specify lifecycle hooks - it can be used e.g., to easily add packages to the container without building + # your own docker image + # see https://github.com/8gears/n8n-helm-chart/pull/30 + lifecycle: {} + + # here's the sample configuration to add mysql-client to the container + # lifecycle: + # postStart: + # exec: + # command: ["/bin/sh", "-c", "apk add mysql-client"] + + # here you can override a command for worker container + # it may be used to override a starting script (e.g., to resolve issues like https://github.com/n8n-io/n8n/issues/6412) or + # run additional preparation steps (e.g., installing additional software) + command: [] + + # sample configuration that overrides starting script and solves above issue (also it runs n8n as root, so be careful): + # command: + # - tini + # - -- + # - /bin/sh + # - -c + # - chmod o+rx /root; chown -R node /root/.n8n || true; chown -R node /root/.n8n; ln -s /root/.n8n /home/node; chown -R node /home/node || true; node /usr/local/bin/n8n + + # command args + commandArgs: [] + + # here you can override the livenessProbe for the main container + # it may be used to increase the timeout for the livenessProbe (e.g., to resolve issues like + livenessProbe: + httpGet: + path: /healthz + port: http + # initialDelaySeconds: 30 + # periodSeconds: 10 + # timeoutSeconds: 5 + # failureThreshold: 6 + # successThreshold: 1 + + # here you can override the readinessProbe for the main container + # it may be used to increase the timeout for the readinessProbe (e.g., to resolve issues like + + readinessProbe: + httpGet: + path: /healthz + port: http + # initialDelaySeconds: 30 + # periodSeconds: 10 + # timeoutSeconds: 5 + # failureThreshold: 6 + # successThreshold: 1 + + # List of initialization containers belonging to the pod. Init containers are executed in order prior to containers being started. + # See https://kubernetes.io/docs/concepts/workloads/pods/init-containers/ + initContainers: [] + + service: + annotations: {} + # -- Service types allow you to specify what kind of Service you want. + # E.g., ClusterIP, NodePort, LoadBalancer, ExternalName + type: ClusterIP + # -- Service port + port: 80 + + resources: {} + # We usually recommend not specifying default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + + autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 100 + targetCPUUtilizationPercentage: 80 + # targetMemoryUtilizationPercentage: 80 + + nodeSelector: {} + tolerations: [] + affinity: {} + +# Webhook related settings +# With .Values.scaling.webhook.enabled=true you disable Webhooks from the main process, but you enable the processing on a different Webhook instance. +# See https://github.com/8gears/n8n-helm-chart/issues/39#issuecomment-1579991754 for the full explanation. +# Webhook processes rely on Valkey/Redis too. +webhook: + enabled: false + # additional (to main) config for webhook + config: {} + # additional (to main) config for webhook + secret: {} + + # Extra environmental variables, so you can reference other configmaps and secrets into n8n as env vars. + extraEnv: {} + # WEBHOOK_URL: + # value: "http://webhook.domain.tld" + + + # + # Webhook Kubernetes specific settings + # + persistence: + # If true, use a Persistent Volume Claim, If false, use emptyDir + enabled: false + # what type volume, possible options are [existing, emptyDir, dynamic] dynamic for Dynamic Volume Provisioning, existing for using an existing Claim + type: emptyDir + # Persistent Volume Storage Class + # If defined, storageClassName: + # If set to "-", storageClassName: "", which disables dynamic provisioning + # If undefined (the default) or set to null, no storageClassName spec is + # set, choosing the default provisioner. (gp2 on AWS, standard on + # GKE, AWS & OpenStack) + # + # storageClass: "-" + # PVC annotations + # + # If you need this annotation include it under `values.yml` file and pvc.yml template will add it. + # This is not maintained at Helm v3 anymore. + # https://github.com/8gears/n8n-helm-chart/issues/8 + # + # annotations: + # helm.sh/resource-policy: keep + # Persistent Volume Access Mode + # + accessModes: + - ReadWriteOnce + # Persistent Volume size + # + size: 1Gi + # Use an existing PVC + # + # existingClaim: + + # Number of desired pods. + replicaCount: 1 + + # here you can specify the deployment strategy as Recreate or RollingUpdate with optional maxSurge and maxUnavailable + # If these options are not set, default values are 25% + # deploymentStrategy: + # type: RollingUpdate + # maxSurge: "50%" + # maxUnavailable: "50%" + + deploymentStrategy: + type: "Recreate" + + nameOverride: "" + fullnameOverride: "" + + serviceAccount: + # Specifies whether a service account should be created + create: true + # Annotations to add to the service account + annotations: {} + # The name of the service account to use. + # If not set and create is true, a name is generated using the fullname template + name: "" + + # Annotations to be implemented on the webhook deployment + deploymentAnnotations: {} + # Labels to be implemented on the webhook deployment + deploymentLabels: {} + # Annotations to be implemented on the webhook pod + podAnnotations: {} + # Labels to be implemented on the webhook pod + podLabels: {} + + podSecurityContext: + runAsNonRoot: true + runAsUser: 1000 + runAsGroup: 1000 + fsGroup: 1000 + + securityContext: {} + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 + + # here you can specify lifecycle hooks - it can be used e.g., to easily add packages to the container without building + # your own docker image + # see https://github.com/8gears/n8n-helm-chart/pull/30 + lifecycle: {} + + # here's the sample configuration to add mysql-client to the container + # lifecycle: + # postStart: + # exec: + # command: ["/bin/sh", "-c", "apk add mysql-client"] + + # here you can override a command for main container + # it may be used to override a starting script (e.g., to resolve issues like https://github.com/n8n-io/n8n/issues/6412) or + # run additional preparation steps (e.g., installing additional software) + command: [] + + # sample configuration that overrides starting script and solves above issue (also it runs n8n as root, so be careful): + # command: + # - tini + # - -- + # - /bin/sh + # - -c + # - chmod o+rx /root; chown -R node /root/.n8n || true; chown -R node /root/.n8n; ln -s /root/.n8n /home/node; chown -R node /home/node || true; node /usr/local/bin/n8n + # Command Arguments + commandArgs: [] + + # here you can override the livenessProbe for the main container + # it may be used to increase the timeout for the livenessProbe (e.g., to resolve issues like + + livenessProbe: + httpGet: + path: /healthz + port: http + # initialDelaySeconds: 30 + # periodSeconds: 10 + # timeoutSeconds: 5 + # failureThreshold: 6 + # successThreshold: 1 + + # here you can override the readinessProbe for the main container + # it may be used to increase the timeout for the readinessProbe (e.g., to resolve issues like + + readinessProbe: + httpGet: + path: /healthz + port: http + # initialDelaySeconds: 30 + # periodSeconds: 10 + # timeoutSeconds: 5 + # failureThreshold: 6 + # successThreshold: 1 + + # List of initialization containers belonging to the pod. Init containers are executed in order prior to containers being started. + # See https://kubernetes.io/docs/concepts/workloads/pods/init-containers/ + initContainers: [] + + service: + annotations: {} + # -- Service types allow you to specify what kind of Service you want. + # E.g., ClusterIP, NodePort, LoadBalancer, ExternalName + type: ClusterIP + # -- Service port + port: 80 + + resources: {} + # We usually recommend not specifying default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 100 + targetCPUUtilizationPercentage: 80 + # targetMemoryUtilizationPercentage: 80 + nodeSelector: {} + tolerations: [] + affinity: {} + +# +# User defined supplementary K8s manifests +# + +# Takes a list of Kubernetes manifests and merges each resource with a default metadata.labels map and +# installs the result. +# Use this to add any arbitrary Kubernetes manifests alongside this chart instead of kubectl and scripts. +extraManifests: [] +# - apiVersion: v1 +# kind: ConfigMap +# metadata: +# name: example-config +# data: +# example.property.1: "value1" +# example.property.2: "value2" +# As an alternative to the above, you can also use a string as the value of the data field. +# - | +# apiVersion: v1 +# kind: ConfigMap +# metadata: +# name: example-config-string +# data: +# example.property.1: "value1" +# example.property.2: "value2" + +# String extraManifests supports using variables directly within a string manifest. +# Templates are rendered using the context defined in the values.yaml file, enabling dynamic and flexible content customization. +extraTemplateManifests: [] +# - | +# apiVersion: v1 +# kind: ConfigMap +# metadata: +# name: my-config +# stringData: +# image_name: {{ .Values.image.repository }} + +# Bitnami Valkey configuration +# https://artifacthub.io/packages/helm/bitnami/valkey +valkey: + enabled: false + #architecture: standalone + # + #primary: + # persistence: + # enabled: false + # existingClaim: "" + # size: 2Gi +``` +## Migration Guide to Version 1.0.0 + +This version includes a complete redesign of the chart to better accommodate n8n configuration options. +Key changes include: +- Values restructured under `.Values.main`, `.Values.worker`, and `.Values.webhook` +- Updated deployment configurations +- New Redis integration requirements + + +## Scaling and Advanced Configuration Options + +n8n provides a **queue-mode**, where the workload is shared between multiple +instances of the same n8n installation. +This provides a shared load over multiple instances and limited high +availability, because the controller instance remains as Single-Point-Of-Failure. + +With the help of an internal/external redis server and by using the excellent +BullMQ, the tasks can be shared over different instances, which also can run on +different hosts. + +[See docs about this Queue-Mode](https://docs.n8n.io/hosting/scaling/queue-mode/) + +To enable this mode within this helm chart, you simply should +set `scaling.enable` to true. +This chart is configured to spawn two worker instances. + +```yaml +scaling: + enabled: true +``` + +You can define to spawn more workers, by set scaling.worker.replicaCount to a higher +number. +Also, it is possible to define your own external redis server. + +```yaml +scaling: + enabled: true + redis: + host: "redis-hostname" + password: "redis-password-if-set" +``` + +If you want to use the internal redis server, set `redis.enable = true`. By +default, no redis server is spawned. + +At last scaling option is it possible to create dedicated webhook instances, +which only process the webhooks. +If you set `scaling.webhook.enabled=true`, then webhook processing on the main +instance is disabled and by default a single webhook instance is started. + +## Chart Release Workflow + +1. Update the `Chart.yaml` with the new version numbers for the chart and/or app. +2. In `Chart.yaml`update/replace the content of the `artifacthub.io/changes` section. See Artifacthub [annotation referene](https://artifacthub.io/docs/topics/annotations/helm/) +3. In GitHub create a new release with the the chart version number as the tag and a title. diff --git a/applications/n8n/charts/n8n/templates/NOTES.txt b/applications/n8n/charts/n8n/templates/NOTES.txt new file mode 100644 index 00000000..106b2f42 --- /dev/null +++ b/applications/n8n/charts/n8n/templates/NOTES.txt @@ -0,0 +1,22 @@ +1. Get the application URL by running these commands: +{{- if .Values.ingress.enabled }} +{{- range $host := .Values.ingress.hosts }} + {{- range .paths }} + http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ . }} + {{- end }} +{{- end }} +{{- else if contains "NodePort" (default "ClusterIP" .Values.main.service.type) }} + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "n8n.fullname" . }}) + export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + echo http://$NODE_IP:$NODE_PORT +{{- else if contains "LoadBalancer" (default "ClusterIP" .Values.main.service.type) }} + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "n8n.fullname" . }}' + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "n8n.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") + echo http://$SERVICE_IP:{{ .Values.main.service.port }} +{{- else if contains "ClusterIP" (default "ClusterIP" .Values.main.service.type) }} + export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "n8n.name" . }},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 }} diff --git a/applications/n8n/charts/n8n/templates/_helpers.tpl b/applications/n8n/charts/n8n/templates/_helpers.tpl new file mode 100644 index 00000000..c1cd45f7 --- /dev/null +++ b/applications/n8n/charts/n8n/templates/_helpers.tpl @@ -0,0 +1,105 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "n8n.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 "n8n.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 "n8n.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "n8n.labels" -}} +helm.sh/chart: {{ include "n8n.chart" . }} +{{ include "n8n.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "n8n.selectorLabels" -}} +app.kubernetes.io/name: {{ include "n8n.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + + +{{/* Create the name of the service account to use */}} +{{- define "n8n.serviceAccountName" -}} +{{- if .Values.main.serviceAccount.create }} +{{- default (include "n8n.fullname" .) .Values.main.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.main.serviceAccount.name }} +{{- end }} +{{- end }} + +{{/* PVC existing, emptyDir, Dynamic */}} +{{- define "n8n.pvc" -}} +{{- if or (not .Values.main.persistence.enabled) (eq .Values.main.persistence.type "emptyDir") -}} + emptyDir: {} +{{- else if and .Values.main.persistence.enabled .Values.main.persistence.existingClaim -}} + persistentVolumeClaim: + claimName: {{ .Values.main.persistence.existingClaim }} +{{- else if and .Values.main.persistence.enabled (eq .Values.main.persistence.type "dynamic") -}} + persistentVolumeClaim: + claimName: {{ include "n8n.fullname" . }} +{{- end }} +{{- end }} + + +{{/* Create environment variables from yaml tree */}} +{{- define "toEnvVars" -}} + {{- $prefix := "" }} + {{- if .prefix }} + {{- $prefix = printf "%s_" .prefix }} + {{- end }} + {{- range $key, $value := .values }} + {{- if kindIs "map" $value -}} + {{- dict "values" $value "prefix" (printf "%s%s" $prefix ($key | upper)) "isSecret" $.isSecret | include "toEnvVars" -}} + {{- else -}} + {{- if $.isSecret -}} +{{ $prefix }}{{ $key | upper }}: {{ $value | toString | b64enc }}{{ "\n" }} + {{- else -}} +{{ $prefix }}{{ $key | upper }}: {{ $value | toString | quote }}{{ "\n" }} + {{- end -}} + {{- end -}} + {{- end -}} +{{- end }} + + +{{/* Validate Valkey/Redis configuration when webhooks are enabled*/}} +{{- define "n8n.validateValkey" -}} +{{- $envVars := fromYaml (include "toEnvVars" (dict "values" .Values.main.config "prefix" "")) -}} +{{- if and .Values.webhook.enabled (not $envVars.QUEUE_BULL_REDIS_HOST) -}} +{{- fail "Webhook processes rely on Valkey. Please set a Redis/Valkey host when webhook.enabled=true" -}} +{{- end -}} +{{- end -}} + + diff --git a/applications/n8n/charts/n8n/templates/configmap.webhook.yaml b/applications/n8n/charts/n8n/templates/configmap.webhook.yaml new file mode 100644 index 00000000..af1d745b --- /dev/null +++ b/applications/n8n/charts/n8n/templates/configmap.webhook.yaml @@ -0,0 +1,10 @@ +{{- if .Values.webhook.config }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "n8n.fullname" . }}-webhook-config + labels: + {{- include "n8n.labels" . | nindent 4 }} +data: + {{- include "toEnvVars" (dict "values" .Values.webhook.config "prefix" "") | nindent 2 }} +{{- end }} diff --git a/applications/n8n/charts/n8n/templates/configmap.worker.yaml b/applications/n8n/charts/n8n/templates/configmap.worker.yaml new file mode 100644 index 00000000..370ff71f --- /dev/null +++ b/applications/n8n/charts/n8n/templates/configmap.worker.yaml @@ -0,0 +1,10 @@ +{{- if .Values.worker.config }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "n8n.fullname" . }}-worker-config + labels: + {{- include "n8n.labels" . | nindent 4 }} +data: + {{- include "toEnvVars" (dict "values" .Values.worker.config "prefix" "") | nindent 2 }} +{{- end }} diff --git a/applications/n8n/charts/n8n/templates/configmap.yaml b/applications/n8n/charts/n8n/templates/configmap.yaml new file mode 100644 index 00000000..05bc5bcc --- /dev/null +++ b/applications/n8n/charts/n8n/templates/configmap.yaml @@ -0,0 +1,10 @@ +{{- if .Values.main.config }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "n8n.fullname" . }}-app-config + labels: + {{- include "n8n.labels" . | nindent 4 }} +data: + {{- include "toEnvVars" (dict "values" .Values.main.config "prefix" "") | nindent 2 }} +{{- end }} diff --git a/applications/n8n/charts/n8n/templates/deployment.webhook.yaml b/applications/n8n/charts/n8n/templates/deployment.webhook.yaml new file mode 100644 index 00000000..ef56ab33 --- /dev/null +++ b/applications/n8n/charts/n8n/templates/deployment.webhook.yaml @@ -0,0 +1,143 @@ +{{- if .Values.webhook.enabled }} +{{- /* Validate Valkey/Redis configuration */}} +{{- include "n8n.validateValkey" . }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "n8n.fullname" . }}-webhook + labels: + {{- include "n8n.labels" . | nindent 4 }} + {{- if .Values.webhook.deploymentLabels }} + {{- toYaml .Values.webhook.deploymentLabels | nindent 4 }} + {{- end }} + {{- if .Values.webhook.deploymentAnnotations }} + annotations: + {{- toYaml .Values.webhook.deploymentAnnotations | nindent 4 }} + {{- end }} +spec: + {{- if not .Values.webhook.autoscaling.enabled }} + replicas: {{ .Values.webhook.replicaCount }} + {{- end }} + strategy: + type: {{ .Values.webhook.deploymentStrategy.type }} + {{- if eq .Values.webhook.deploymentStrategy.type "RollingUpdate" }} + rollingUpdate: + maxSurge: {{ default "25%" .Values.webhook.deploymentStrategy.maxSurge }} + maxUnavailable: {{ default "25%" .Values.webhook.deploymentStrategy.maxUnavailable }} + {{- end }} + selector: + matchLabels: + {{- include "n8n.selectorLabels" . | nindent 6 }} + app.kubernetes.io/type: webhook + template: + metadata: + annotations: + checksum/config: {{ print .Values.webhook .Values.main | sha256sum }} + {{- with .Values.webhook.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "n8n.selectorLabels" . | nindent 8 }} + app.kubernetes.io/type: webhook + {{- if .Values.webhook.podLabels }} + {{ toYaml .Values.webhook.podLabels | nindent 8 }} + {{- end }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "n8n.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.webhook.podSecurityContext | nindent 8 }} + {{- if .Values.webhook.initContainers }} + initContainers: + {{ tpl (toYaml .Values.webhook.initContainers) . | nindent 8 }} + {{- end }} + containers: + - name: {{ .Chart.Name }}-webhook + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + {{- if .Values.webhook.command }} + command: + {{- toYaml .Values.webhook.command | nindent 12 }} + {{- else }} + command: ["n8n"] + {{- end }} + {{- if .Values.webhook.commandArgs }} + args: + {{- toYaml .Values.webhook.commandArgs | nindent 12 }} + {{- else }} + args: + - "webhook" + {{- end }} + ports: + - name: http + containerPort: {{ get (default (dict) .Values.webhook.config) "port" | default 5678 }} + protocol: TCP + {{- with .Values.webhook.livenessProbe }} + livenessProbe: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.webhook.readinessProbe }} + readinessProbe: + {{- toYaml . | nindent 12 }} + {{- end }} + envFrom: + {{- if .Values.main.config }} + - configMapRef: + name: {{ include "n8n.fullname" . }}-app-config + {{- end }} + {{- if .Values.main.secret }} + - secretRef: + name: {{ include "n8n.fullname" . }}-app-secret + {{- end }} + {{- if .Values.webhook.config }} + - configMapRef: + name: {{ include "n8n.fullname" . }}-webhook-config + {{- end }} + {{- if .Values.webhook.secret }} + - secretRef: + name: {{ include "n8n.fullname" . }}-webhook-secret + {{- end }} + + env: {{ not (empty .Values.webhook.extraEnv) | ternary nil "[]" }} + {{- range $key, $value := .Values.webhook.extraEnv }} + - name: {{ $key }} + {{- toYaml $value | nindent 14 }} + {{- end }} + lifecycle: + {{- toYaml .Values.webhook.lifecycle | nindent 12 }} + securityContext: + {{- toYaml .Values.webhook.securityContext | nindent 12 }} + resources: + {{- toYaml .Values.webhook.resources | nindent 12 }} + volumeMounts: + - name: data + mountPath: /home/node/.n8n + {{- if .Values.webhook.extraVolumeMounts }} + {{- toYaml .Values.webhook.extraVolumeMounts | nindent 12 }} + {{- end }} + {{- with .Values.hostAliases }} + hostAliases: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.webhook.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.webhook.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.webhook.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + volumes: + - name: "data" + {{ include "n8n.pvc" . }} + {{- if .Values.webhook.extraVolumes }} + {{- toYaml .Values.webhook.extraVolumes | nindent 8 }} + {{- end }} +{{- end }} diff --git a/applications/n8n/charts/n8n/templates/deployment.worker.yaml b/applications/n8n/charts/n8n/templates/deployment.worker.yaml new file mode 100644 index 00000000..b6dbb60b --- /dev/null +++ b/applications/n8n/charts/n8n/templates/deployment.worker.yaml @@ -0,0 +1,139 @@ +{{- if .Values.worker.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "n8n.fullname" . }}-worker + labels: + {{- include "n8n.labels" . | nindent 4 }} + {{- if .Values.worker.deploymentLabels }} + {{- toYaml .Values.worker.deploymentLabels | nindent 4 }} + {{- end }} + {{- if .Values.worker.deploymentAnnotations }} + annotations: + {{- toYaml .Values.worker.deploymentAnnotations | nindent 4 }} + {{- end }} +spec: + {{- if not .Values.worker.autoscaling.enabled }} + replicas: {{ .Values.worker.replicaCount }} + {{- end }} + strategy: + type: {{ .Values.worker.deploymentStrategy.type }} + {{- if eq .Values.worker.deploymentStrategy.type "RollingUpdate" }} + rollingUpdate: + maxSurge: {{ default "25%" .Values.worker.deploymentStrategy.maxSurge }} + maxUnavailable: {{ default "25%" .Values.worker.deploymentStrategy.maxUnavailable }} + {{- end }} + selector: + matchLabels: + {{- include "n8n.selectorLabels" . | nindent 6 }} + app.kubernetes.io/type: worker + template: + metadata: + annotations: + checksum/config: {{ print .Values | sha256sum }} + {{- with .Values.worker.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "n8n.selectorLabels" . | nindent 8 }} + app.kubernetes.io/type: worker + {{- if .Values.worker.podLabels }} + {{ toYaml .Values.worker.podLabels | nindent 8 }} + {{- end }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "n8n.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.worker.podSecurityContext | nindent 8 }} + {{- if .Values.worker.initContainers }} + initContainers: + {{ tpl (toYaml .Values.worker.initContainers) . | nindent 8 }} + {{- end }} + containers: + - name: {{ .Chart.Name }}-worker + securityContext: + {{- toYaml .Values.worker.securityContext | nindent 12 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + envFrom: + {{- if .Values.main.config }} + - configMapRef: + name: {{ include "n8n.fullname" . }}-app-config + {{- end }} + {{- if .Values.main.secret }} + - secretRef: + name: {{ include "n8n.fullname" . }}-app-secret + {{- end }} + {{- if .Values.worker.config }} + - configMapRef: + name: {{ include "n8n.fullname" . }}-worker-config + {{- end }} + {{- if .Values.worker.secret }} + - secretRef: + name: {{ include "n8n.fullname" . }}-worker-secret + {{- end }} + env: {{ not (empty .Values.worker.extraEnv) | ternary nil "[]" }} + {{- range $key, $value := .Values.worker.extraEnv }} + - name: {{ $key }} + {{- toYaml $value | nindent 14 }} + {{- end }} + {{- if .Values.worker.command }} + command: + {{- toYaml .Values.worker.command | nindent 12 }} + {{- else }} + command: ["n8n"] + {{- end }} + {{- if .Values.worker.commandArgs }} + args: + {{- toYaml .Values.worker.commandArgs | nindent 12 }} + {{- else }} + args: + - "worker" + - "--concurrency={{ .Values.worker.concurrency }}" + {{- end }} + ports: + - name: http + containerPort: {{ get (default (dict) .Values.worker.config) "port" | default 5678 }} + protocol: TCP + {{- with .Values.main.livenessProbe }} + livenessProbe: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.main.readinessProbe }} + readinessProbe: + {{- toYaml . | nindent 12 }} + {{- end }} + resources: + {{- toYaml .Values.worker.resources | nindent 12 }} + volumeMounts: + - name: data + mountPath: /home/node/.n8n + {{- if .Values.worker.extraVolumeMounts }} + {{- toYaml .Values.worker.extraVolumeMounts | nindent 12 }} + {{- end }} + {{- with .Values.hostAliases }} + hostAliases: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.worker.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.worker.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.worker.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + volumes: + - name: "data" + {{ include "n8n.pvc" . }} + {{- if .Values.worker.extraVolumes }} + {{- toYaml .Values.worker.extraVolumes | nindent 8 }} + {{- end }} +{{- end }} diff --git a/applications/n8n/charts/n8n/templates/deployment.yaml b/applications/n8n/charts/n8n/templates/deployment.yaml new file mode 100644 index 00000000..d354b5bf --- /dev/null +++ b/applications/n8n/charts/n8n/templates/deployment.yaml @@ -0,0 +1,123 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "n8n.fullname" . }} + labels: + {{- include "n8n.labels" . | nindent 4 }} + {{- if .Values.main.deploymentLabels }} + {{- toYaml .Values.main.deploymentLabels | nindent 4 }} + {{- end }} + {{- if .Values.main.deploymentAnnotations }} + annotations: + {{- toYaml .Values.main.deploymentAnnotations | nindent 4 }} + {{- end }} +spec: + {{- if not .Values.main.autoscaling.enabled }} + replicas: {{ .Values.main.replicaCount }} + {{- end }} + strategy: + type: {{ .Values.main.deploymentStrategy.type }} + {{- if eq .Values.main.deploymentStrategy.type "RollingUpdate" }} + rollingUpdate: + maxSurge: {{ default "25%" .Values.main.deploymentStrategy.maxSurge }} + maxUnavailable: {{ default "25%" .Values.main.deploymentStrategy.maxUnavailable }} + {{- end }} + selector: + matchLabels: + {{- include "n8n.selectorLabels" . | nindent 6 }} + app.kubernetes.io/type: master + template: + metadata: + annotations: + checksum/config: {{ print .Values.main | sha256sum }} + {{- with .Values.main.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "n8n.selectorLabels" . | nindent 8 }} + app.kubernetes.io/type: master + {{- if .Values.main.podLabels }} + {{ toYaml .Values.main.podLabels | nindent 8 }} + {{- end }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "n8n.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.main.podSecurityContext | nindent 8 }} + {{- if or .Values.main.initContainers }} + initContainers: + {{- if .Values.main.initContainers }} + {{ tpl (toYaml .Values.main.initContainers) . | nindent 10 }} + {{- end }} + {{- end }} + containers: + - name: {{ .Chart.Name }} + {{- with .Values.main.command }} + command: + {{- toYaml . | nindent 12 }} + {{- end }} + securityContext: + {{- toYaml .Values.main.securityContext | nindent 12 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + envFrom: + {{- if .Values.main.config }} + - configMapRef: + name: {{ include "n8n.fullname" . }}-app-config + {{- end }} + {{- if .Values.main.secret }} + - secretRef: + name: {{ include "n8n.fullname" . }}-app-secret + {{- end }} + env: {{ not (empty .Values.main.extraEnv) | ternary nil "[]" }} + {{- range $key, $value := .Values.main.extraEnv }} + - name: {{ $key }} + {{- toYaml $value | nindent 14 }} + {{- end }} + lifecycle: + {{- toYaml .Values.main.lifecycle | nindent 12 }} + ports: + - name: http + containerPort: {{ get (default (dict) .Values.main.config) "port" | default 5678 }} + protocol: TCP + {{- with .Values.main.livenessProbe }} + livenessProbe: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.main.readinessProbe }} + readinessProbe: + {{- toYaml . | nindent 12 }} + {{- end }} + resources: + {{- toYaml .Values.main.resources | nindent 12 }} + volumeMounts: + - name: data + mountPath: /home/node/.n8n + {{- if .Values.main.extraVolumeMounts }} + {{- toYaml .Values.main.extraVolumeMounts | nindent 12 }} + {{- end }} + {{- with .Values.hostAliases }} + hostAliases: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.main.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.main.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.main.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + volumes: + - name: "data" + {{ include "n8n.pvc" . }} + {{- if .Values.main.extraVolumes }} + {{- toYaml .Values.main.extraVolumes | nindent 8 }} + {{- end }} diff --git a/applications/n8n/charts/n8n/templates/extraManifests.yaml b/applications/n8n/charts/n8n/templates/extraManifests.yaml new file mode 100644 index 00000000..2fd589be --- /dev/null +++ b/applications/n8n/charts/n8n/templates/extraManifests.yaml @@ -0,0 +1,49 @@ +{{/* +extraManifests.yaml creates Kubernetes resources from values provided in +.Values.extraManifests and .Values.extraTemplateManifests +*/}} + +{{- define "extraManifests.metadata" -}} +metadata: + labels: + app: {{ template "n8n.name" . }} + chart: {{ template "n8n.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +{{- end }} + +{{- $metadata := fromYaml (include "extraManifests.metadata" .) -}} + +{{- $resources := list -}} +{{- range .Values.extraManifests }} + {{- if typeIs "string" . }} + {{/* Handle multi-part YAML documents */}} + {{- range . | splitList "---\n" }} + {{- with . | fromYaml }} + {{- $resources = append $resources . }} + {{- end }} + {{- end }} + {{- else }} + {{- $resources = append $resources . }} + {{- end }} +{{- end }} + +{{- $templates := list -}} +{{- range .Values.extraTemplateManifests }} + {{/* Handle multi-part YAML documents */}} + {{- range . | splitList "---\n" }} + {{- if . | fromYaml }} + {{- $templates = append $templates . }} + {{- end }} + {{- end }} +{{- end }} + +{{- range $resources }} +--- +{{ merge . $metadata | toYaml }} +{{- end }} + +{{- range $templates }} +--- +{{ merge (tpl . $ | fromYaml) $metadata | toYaml }} +{{- end }} diff --git a/applications/n8n/charts/n8n/templates/hpa.yaml b/applications/n8n/charts/n8n/templates/hpa.yaml new file mode 100644 index 00000000..346ecf21 --- /dev/null +++ b/applications/n8n/charts/n8n/templates/hpa.yaml @@ -0,0 +1,32 @@ +{{- if .Values.main.autoscaling.enabled }} +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "n8n.fullname" . }} + labels: + {{- include "n8n.labels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "n8n.fullname" . }} + minReplicas: {{ .Values.main.autoscaling.minReplicas }} + maxReplicas: {{ .Values.main.autoscaling.maxReplicas }} + metrics: + {{- if .Values.main.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: {{ .Values.main.autoscaling.targetCPUUtilizationPercentage }} + {{- end }} + {{- if .Values.main.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: {{ .Values.main.autoscaling.targetMemoryUtilizationPercentage }} + {{- end }} +{{- end }} diff --git a/applications/n8n/charts/n8n/templates/ingress.yaml b/applications/n8n/charts/n8n/templates/ingress.yaml new file mode 100644 index 00000000..4b2dcd4e --- /dev/null +++ b/applications/n8n/charts/n8n/templates/ingress.yaml @@ -0,0 +1,73 @@ +{{- if .Values.ingress.enabled -}} +{{- $fullName := include "n8n.fullname" . -}} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ $fullName }} + labels: + {{- include "n8n.labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if .Values.ingress.className }} + ingressClassName: {{ .Values.ingress.className }} + {{- end }} + {{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ . }} + pathType: Prefix + backend: + service: + name: {{ $fullName }} + port: + number: {{ $.Values.main.service.port | default 80 }} + {{- if $.Values.webhook.enabled }} + - path: {{ . }}webhook/ + pathType: Prefix + backend: + service: + name: {{ $fullName }}-webhook + port: + number: {{ $.Values.webhook.service.port | default 80 }} + # Note: The default URL for manual workflow executions is /webhook-test/*. Make sure that these URLs route to your main process. + - path: {{ . }}webhook-test/ + pathType: Prefix + backend: + service: + name: {{ $fullName }} + port: + number: {{ $.Values.main.service.port | default 80 }} + - path: {{ . }}webhook-waiting/ + pathType: Prefix + backend: + service: + name: {{ $fullName }}-webhook + port: + number: {{ $.Values.webhook.service.port | default 80 }} + - path: {{ . }}form/ + pathType: Prefix + backend: + service: + name: {{ $fullName }}-webhook + port: + number: {{ $.Values.webhook.service.port | default 80 }} + {{- end }} + {{- end }} + {{- end }} + {{- end }} diff --git a/applications/n8n/charts/n8n/templates/pvc.yaml b/applications/n8n/charts/n8n/templates/pvc.yaml new file mode 100644 index 00000000..deff860f --- /dev/null +++ b/applications/n8n/charts/n8n/templates/pvc.yaml @@ -0,0 +1,29 @@ +{{- if and .Values.main.persistence.enabled (not .Values.main.persistence.existingClaim) }} +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ include "n8n.fullname" . }} + {{- with .Values.main.persistence.annotations }} + annotations: + {{- range $key, $value := . }} + {{ $key }}: {{ $value }} + {{- end }} + {{- end }} + labels: + {{- include "n8n.labels" . | nindent 4 }} +spec: + accessModes: + {{- range .Values.main.persistence.accessModes }} + - {{ . | quote }} + {{- end }} + resources: + requests: + storage: {{ .Values.main.persistence.size }} + {{- if .Values.main.persistence.storageClass }} + {{- if eq "-" .Values.main.persistence.storageClass }} + storageClassName: "" + {{- else }} + storageClassName: {{ .Values.main.persistence.storageClass }} + {{- end }} + {{- end }} +{{- end }} diff --git a/applications/n8n/charts/n8n/templates/secret.webhook.yaml b/applications/n8n/charts/n8n/templates/secret.webhook.yaml new file mode 100644 index 00000000..21a20949 --- /dev/null +++ b/applications/n8n/charts/n8n/templates/secret.webhook.yaml @@ -0,0 +1,12 @@ +{{- if .Values.webhook.secret }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "n8n.fullname" . }}-webhook-secret + labels: + {{- include "n8n.labels" . | nindent 4 }} +type: Opaque +data: + {{- include "toEnvVars" (dict "values" .Values.webhook.secret "prefix" "" "isSecret" true) | nindent 4 }} + +{{- end }} diff --git a/applications/n8n/charts/n8n/templates/secret.worker.yaml b/applications/n8n/charts/n8n/templates/secret.worker.yaml new file mode 100644 index 00000000..91c8f1a8 --- /dev/null +++ b/applications/n8n/charts/n8n/templates/secret.worker.yaml @@ -0,0 +1,12 @@ +{{- if .Values.worker.secret }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "n8n.fullname" . }}-worker-secret + labels: + {{- include "n8n.labels" . | nindent 4 }} +type: Opaque +data: + {{- include "toEnvVars" (dict "values" .Values.worker.secret "prefix" "" "isSecret" true) | nindent 4 }} + +{{- end }} diff --git a/applications/n8n/charts/n8n/templates/secret.yaml b/applications/n8n/charts/n8n/templates/secret.yaml new file mode 100644 index 00000000..6366a65a --- /dev/null +++ b/applications/n8n/charts/n8n/templates/secret.yaml @@ -0,0 +1,12 @@ +{{- if .Values.main.secret }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "n8n.fullname" . }}-app-secret + labels: + {{- include "n8n.labels" . | nindent 4 }} +type: Opaque +data: + {{- include "toEnvVars" (dict "values" .Values.main.secret "prefix" "" "isSecret" true) | nindent 4 }} + +{{- end }} diff --git a/applications/n8n/charts/n8n/templates/service.webhook.yaml b/applications/n8n/charts/n8n/templates/service.webhook.yaml new file mode 100644 index 00000000..1d2d384f --- /dev/null +++ b/applications/n8n/charts/n8n/templates/service.webhook.yaml @@ -0,0 +1,22 @@ +{{- if .Values.webhook.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "n8n.fullname" . }}-webhook + labels: + {{- include "n8n.labels" . | nindent 4 }} + {{- with .Values.webhook.service.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + type: {{ .Values.webhook.service.type | default "ClusterIP" }} + ports: + - port: {{ .Values.webhook.service.port | default 80 }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "n8n.selectorLabels" . | nindent 4 }} + app.kubernetes.io/type: webhook +{{- end }} diff --git a/applications/n8n/charts/n8n/templates/service.yaml b/applications/n8n/charts/n8n/templates/service.yaml new file mode 100644 index 00000000..e2c93cd6 --- /dev/null +++ b/applications/n8n/charts/n8n/templates/service.yaml @@ -0,0 +1,20 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "n8n.fullname" . }} + labels: + {{- include "n8n.labels" . | nindent 4 }} + {{- with .Values.main.service.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + type: {{ default "ClusterIP_" .Values.main.service.type }} + ports: + - port: {{ default 80 .Values.main.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "n8n.selectorLabels" . | nindent 4 }} + app.kubernetes.io/type: master diff --git a/applications/n8n/charts/n8n/templates/serviceaccount.yaml b/applications/n8n/charts/n8n/templates/serviceaccount.yaml new file mode 100644 index 00000000..137ea35a --- /dev/null +++ b/applications/n8n/charts/n8n/templates/serviceaccount.yaml @@ -0,0 +1,12 @@ +{{- if .Values.main.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "n8n.serviceAccountName" . }} + labels: + {{- include "n8n.labels" . | nindent 4 }} + {{- with .Values.main.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/applications/n8n/charts/n8n/templates/tests/test-connection.yaml b/applications/n8n/charts/n8n/templates/tests/test-connection.yaml new file mode 100644 index 00000000..5a0d6558 --- /dev/null +++ b/applications/n8n/charts/n8n/templates/tests/test-connection.yaml @@ -0,0 +1,19 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "{{ include "n8n.fullname" . }}-test-connection" + labels: + {{- include "n8n.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": test +spec: + containers: + - name: wget + image: alpine + command: ['sh', '-c'] + args: + - | + echo "Testing connection to {{ include "n8n.fullname" . }}:{{ default 80 .Values.main.service.port }}"; + nslookup {{ include "n8n.fullname" . }}; + wget {{ include "n8n.fullname" . }}:{{ default 80 .Values.main.service.port }}; + restartPolicy: Never diff --git a/applications/n8n/charts/n8n/values.yaml b/applications/n8n/charts/n8n/values.yaml new file mode 100644 index 00000000..7437567a --- /dev/null +++ b/applications/n8n/charts/n8n/values.yaml @@ -0,0 +1,679 @@ +# README +# High level values structure, overview and explanation of the values.yaml file. +# 1. Global and chart wide values, like the image repository, image tag, etc. +# 2. Ingress, (default is nginx, but you can change it to your own ingress controller) +# 3. Main n8n app configuration + kubernetes specific settings +# 4. Worker related settings + kubernetes specific settings +# 5. Webhook related settings + kubernetes specific settings +# 6. Raw Resources to pass through your own manifests like GatewayAPI, ServiceMonitor etc. +# 7. Valkey/Redis related settings and kubernetes specific settings + +# +# Global common config for this entire n8n deployment +# + +image: + repository: n8nio/n8n + pullPolicy: IfNotPresent + # Overrides the image tag whose default is the chart appVersion. + tag: "" +imagePullSecrets: [] + +# The Name to use for the chart. Will be the prefix of all resources aka. The Chart.Name (default is 'n8n') +nameOverride: +# Override the full name of the deployment. When empty, the name will be "{release-name}-{chart-name}" or the value of nameOverride if specified +fullnameOverride: + +# Add entries to a pod's /etc/hosts file, mapping custom IP addresses to hostnames. +hostAliases: [] + # - ip: 8.8.8.8 + # hostnames: + # - service-example.local +# +# Ingress +# +ingress: + enabled: false + annotations: {} + # define a custom ingress class Name, like "traefik" or "nginx" + className: "" + hosts: + - host: workflow.example.com + paths: [] + tls: + - hosts: + - workflow.example.com + secretName: host-domain-cert + +# the main (n8n) application related configuration + Kubernetes specific settings +# The config: {} dictionary is converted to environmental variables in the ConfigMap. +main: + # See https://docs.n8n.io/hosting/configuration/environment-variables/ for all values. + config: {} + # n8n: + # db: + # type: postgresdb + # postgresdb: + # host: 192.168.0.52 + + # Dictionary for secrets, unlike config:, the values here will end up in the secret file. + # The YAML entry db.postgresdb.password: my_secret is transformed DB_POSTGRESDB_password=bXlfc2VjcmV0 + # See https://docs.n8n.io/hosting/configuration/environment-variables/ + secret: {} + # n8n: + # if you run n8n stateless, you should provide an encryption key here. + # encryption_key: + # + # db: + # postgresdb: + # password: 'big secret' + + # Extra environmental variables, so you can reference other configmaps and secrets into n8n as env vars. + extraEnv: + # N8N_DB_POSTGRESDB_NAME: + # valueFrom: + # secretKeyRef: + # name: db-app + # key: dbname + # + # N8n Kubernetes specific settings + # + persistence: + # If true, use a Persistent Volume Claim, If false, use emptyDir + enabled: false + # what type volume, possible options are [existing, emptyDir, dynamic] dynamic for Dynamic Volume Provisioning, existing for using an existing Claim + type: emptyDir + # Persistent Volume Storage Class + # If defined, storageClassName: + # If set to "-", storageClassName: "", which disables dynamic provisioning + # If undefined (the default) or set to null, no storageClassName spec is + # set, choosing the default provisioner. (gp2 on AWS, standard on + # GKE, AWS & OpenStack) + # + # storageClass: "-" + # PVC annotations + # + # If you need this annotation include it under `values.yml` file and pvc.yml template will add it. + # This is not maintained at Helm v3 anymore. + # https://github.com/8gears/n8n-helm-chart/issues/8 + # + # annotations: + # helm.sh/resource-policy: keep + # Persistent Volume Access Mode + # + accessModes: + - ReadWriteOnce + # Persistent Volume size + size: 1Gi + # Use an existing PVC + # existingClaim: + + extraVolumes: [] + # - name: db-ca-cert + # secret: + # secretName: db-ca + # items: + # - key: ca.crt + # path: ca.crt + + extraVolumeMounts: [] + # - name: db-ca-cert + # mountPath: /etc/ssl/certs/postgresql + # readOnly: true + + + # Number of desired pods. More than one pod is supported in n8n enterprise. + replicaCount: 1 + + # here you can specify the deployment strategy as Recreate or RollingUpdate with optional maxSurge and maxUnavailable + # If these options are not set, default values are 25% + # deploymentStrategy: + # type: Recreate | RollingUpdate + # maxSurge: "50%" + # maxUnavailable: "50%" + + deploymentStrategy: + type: "Recreate" + # maxSurge: "50%" + # maxUnavailable: "50%" + + serviceAccount: + # Specifies whether a service account should be created + create: true + # Annotations to add to the service account + annotations: {} + # The name of the service account to use. + # If not set and create is true, a name is generated using the fullname template + name: "" + + # Annotations to be implemented on the main service deployment + deploymentAnnotations: {} + # Labels to be implemented on the main service deployment + deploymentLabels: {} + # Annotations to be implemented on the main service pod + podAnnotations: {} + # Labels to be implemented on the main service pod + podLabels: {} + + podSecurityContext: + runAsNonRoot: true + runAsUser: 1000 + runAsGroup: 1000 + fsGroup: 1000 + + securityContext: {} + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 + + # here you can specify lifecycle hooks - it can be used e.g., to easily add packages to the container without building + # your own docker image + # see https://github.com/8gears/n8n-helm-chart/pull/30 + lifecycle: {} + + # here's the sample configuration to add mysql-client to the container + # lifecycle: + # postStart: + # exec: + # command: ["/bin/sh", "-c", "apk add mysql-client"] + + # here you can override a command for main container + # it may be used to override a starting script (e.g., to resolve issues like https://github.com/n8n-io/n8n/issues/6412) or run additional preparation steps (e.g., installing additional software) + command: [] + + # sample configuration that overrides starting script and solves above issue (also it runs n8n as root, so be careful): + # command: + # - tini + # - -- + # - /bin/sh + # - -c + # - chmod o+rx /root; chown -R node /root/.n8n || true; chown -R node /root/.n8n; ln -s /root/.n8n /home/node; chown -R node /home/node || true; node /usr/local/bin/n8n + + # here you can override the livenessProbe for the main container + # it may be used to increase the timeout for the livenessProbe (e.g., to resolve issues like + + livenessProbe: + httpGet: + path: /healthz + port: http + # initialDelaySeconds: 30 + # periodSeconds: 10 + # timeoutSeconds: 5 + # failureThreshold: 6 + # successThreshold: 1 + + # here you can override the readinessProbe for the main container + # it may be used to increase the timeout for the readinessProbe (e.g., to resolve issues like + + readinessProbe: + httpGet: + path: /healthz + port: http + # initialDelaySeconds: 30 + # periodSeconds: 10 + # timeoutSeconds: 5 + # failureThreshold: 6 + # successThreshold: 1 + + # List of initialization containers belonging to the pod. Init containers are executed in order prior to containers being started. + # See https://kubernetes.io/docs/concepts/workloads/pods/init-containers/ + initContainers: [] + # - name: init-data-dir + # image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + # command: [ "/bin/sh", "-c", "mkdir -p /home/node/.n8n/" ] + # volumeMounts: + # - name: data + # mountPath: /home/node/.n8n + + + service: + annotations: {} + # -- Service types allow you to specify what kind of Service you want. + # E.g., ClusterIP, NodePort, LoadBalancer, ExternalName + type: ClusterIP + # -- Service port + port: 80 + + resources: {} + # We usually recommend not specifying default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + + autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 100 + targetCPUUtilizationPercentage: 80 + # targetMemoryUtilizationPercentage: 80 + + nodeSelector: {} + tolerations: [] + affinity: {} + +# # # # # # # # # # # # # # # # +# +# Worker related settings +# +worker: + enabled: false + + # additional (to main) config for worker + config: {} + + # additional (to main) config for worker + secret: {} + + # Extra environmental variables, so you can reference other configmaps and secrets into n8n as env vars. + extraEnv: {} + + # Define the number of jobs a worker can run in parallel by using the concurrency flag. Default is 10 + concurrency: 10 + + # + # Worker Kubernetes specific settings + # + persistence: + # If true, use a Persistent Volume Claim, If false, use emptyDir + enabled: false + # what type volume, possible options are [existing, emptyDir, dynamic] dynamic for Dynamic Volume Provisioning, existing for using an existing Claim + type: emptyDir + # Persistent Volume Storage Class + # If defined, storageClassName: + # If set to "-", storageClassName: "", which disables dynamic provisioning + # If undefined (the default) or set to null, no storageClassName spec is + # set, choosing the default provisioner. (gp2 on AWS, standard on + # GKE, AWS & OpenStack) + # + # storageClass: "-" + # PVC annotations + # + # If you need this annotation include it under `values.yml` file and pvc.yml template will add it. + # This is not maintained at Helm v3 anymore. + # https://github.com/8gears/n8n-helm-chart/issues/8 + # + # annotations: + # helm.sh/resource-policy: keep + # Persistent Volume Access Mode + accessModes: + - ReadWriteOnce + # Persistent Volume size + size: 1Gi + # Use an existing PVC + # existingClaim: + + # Number of desired pods. + replicaCount: 1 + + # here you can specify the deployment strategy as Recreate or RollingUpdate with optional maxSurge and maxUnavailable + # If these options are not set, default values are 25% + # deploymentStrategy: + # type: RollingUpdate + # maxSurge: "50%" + # maxUnavailable: "50%" + + deploymentStrategy: + type: "Recreate" + # maxSurge: "50%" + # maxUnavailable: "50%" + + serviceAccount: + # Specifies whether a service account should be created + create: true + # Annotations to add to the service account + annotations: {} + # The name of the service account to use. + # If not set and create is true, a name is generated using the fullname template + name: "" + + # Annotations to be implemented on the worker deployment + deploymentAnnotations: {} + # Labels to be implemented on the worker deployment + deploymentLabels: {} + # Annotations to be implemented on the worker pod + podAnnotations: {} + # Labels to be implemented on the worker pod + podLabels: {} + + podSecurityContext: + runAsNonRoot: true + runAsUser: 1000 + runAsGroup: 1000 + fsGroup: 1000 + + securityContext: {} + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 + + # here you can specify lifecycle hooks - it can be used e.g., to easily add packages to the container without building + # your own docker image + # see https://github.com/8gears/n8n-helm-chart/pull/30 + lifecycle: {} + + # here's the sample configuration to add mysql-client to the container + # lifecycle: + # postStart: + # exec: + # command: ["/bin/sh", "-c", "apk add mysql-client"] + + # here you can override a command for worker container + # it may be used to override a starting script (e.g., to resolve issues like https://github.com/n8n-io/n8n/issues/6412) or + # run additional preparation steps (e.g., installing additional software) + command: [] + + # sample configuration that overrides starting script and solves above issue (also it runs n8n as root, so be careful): + # command: + # - tini + # - -- + # - /bin/sh + # - -c + # - chmod o+rx /root; chown -R node /root/.n8n || true; chown -R node /root/.n8n; ln -s /root/.n8n /home/node; chown -R node /home/node || true; node /usr/local/bin/n8n + + # command args + commandArgs: [] + + # here you can override the livenessProbe for the main container + # it may be used to increase the timeout for the livenessProbe (e.g., to resolve issues like + livenessProbe: + httpGet: + path: /healthz + port: http + # initialDelaySeconds: 30 + # periodSeconds: 10 + # timeoutSeconds: 5 + # failureThreshold: 6 + # successThreshold: 1 + + # here you can override the readinessProbe for the main container + # it may be used to increase the timeout for the readinessProbe (e.g., to resolve issues like + + readinessProbe: + httpGet: + path: /healthz + port: http + # initialDelaySeconds: 30 + # periodSeconds: 10 + # timeoutSeconds: 5 + # failureThreshold: 6 + # successThreshold: 1 + + # List of initialization containers belonging to the pod. Init containers are executed in order prior to containers being started. + # See https://kubernetes.io/docs/concepts/workloads/pods/init-containers/ + initContainers: [] + + service: + annotations: {} + # -- Service types allow you to specify what kind of Service you want. + # E.g., ClusterIP, NodePort, LoadBalancer, ExternalName + type: ClusterIP + # -- Service port + port: 80 + + resources: {} + # We usually recommend not specifying default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + + autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 100 + targetCPUUtilizationPercentage: 80 + # targetMemoryUtilizationPercentage: 80 + + nodeSelector: {} + tolerations: [] + affinity: {} + +# Webhook related settings +# With .Values.scaling.webhook.enabled=true you disable Webhooks from the main process, but you enable the processing on a different Webhook instance. +# See https://github.com/8gears/n8n-helm-chart/issues/39#issuecomment-1579991754 for the full explanation. +# Webhook processes rely on Valkey/Redis too. +webhook: + enabled: false + # additional (to main) config for webhook + config: {} + # additional (to main) config for webhook + secret: {} + + # Extra environmental variables, so you can reference other configmaps and secrets into n8n as env vars. + extraEnv: {} + # WEBHOOK_URL: + # value: "http://webhook.domain.tld" + + + # + # Webhook Kubernetes specific settings + # + persistence: + # If true, use a Persistent Volume Claim, If false, use emptyDir + enabled: false + # what type volume, possible options are [existing, emptyDir, dynamic] dynamic for Dynamic Volume Provisioning, existing for using an existing Claim + type: emptyDir + # Persistent Volume Storage Class + # If defined, storageClassName: + # If set to "-", storageClassName: "", which disables dynamic provisioning + # If undefined (the default) or set to null, no storageClassName spec is + # set, choosing the default provisioner. (gp2 on AWS, standard on + # GKE, AWS & OpenStack) + # + # storageClass: "-" + # PVC annotations + # + # If you need this annotation include it under `values.yml` file and pvc.yml template will add it. + # This is not maintained at Helm v3 anymore. + # https://github.com/8gears/n8n-helm-chart/issues/8 + # + # annotations: + # helm.sh/resource-policy: keep + # Persistent Volume Access Mode + # + accessModes: + - ReadWriteOnce + # Persistent Volume size + # + size: 1Gi + # Use an existing PVC + # + # existingClaim: + + # Number of desired pods. + replicaCount: 1 + + # here you can specify the deployment strategy as Recreate or RollingUpdate with optional maxSurge and maxUnavailable + # If these options are not set, default values are 25% + # deploymentStrategy: + # type: RollingUpdate + # maxSurge: "50%" + # maxUnavailable: "50%" + + deploymentStrategy: + type: "Recreate" + + nameOverride: "" + fullnameOverride: "" + + serviceAccount: + # Specifies whether a service account should be created + create: true + # Annotations to add to the service account + annotations: {} + # The name of the service account to use. + # If not set and create is true, a name is generated using the fullname template + name: "" + + # Annotations to be implemented on the webhook deployment + deploymentAnnotations: {} + # Labels to be implemented on the webhook deployment + deploymentLabels: {} + # Annotations to be implemented on the webhook pod + podAnnotations: {} + # Labels to be implemented on the webhook pod + podLabels: {} + + podSecurityContext: + runAsNonRoot: true + runAsUser: 1000 + runAsGroup: 1000 + fsGroup: 1000 + + securityContext: {} + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 + + # here you can specify lifecycle hooks - it can be used e.g., to easily add packages to the container without building + # your own docker image + # see https://github.com/8gears/n8n-helm-chart/pull/30 + lifecycle: {} + + # here's the sample configuration to add mysql-client to the container + # lifecycle: + # postStart: + # exec: + # command: ["/bin/sh", "-c", "apk add mysql-client"] + + # here you can override a command for main container + # it may be used to override a starting script (e.g., to resolve issues like https://github.com/n8n-io/n8n/issues/6412) or + # run additional preparation steps (e.g., installing additional software) + command: [] + + # sample configuration that overrides starting script and solves above issue (also it runs n8n as root, so be careful): + # command: + # - tini + # - -- + # - /bin/sh + # - -c + # - chmod o+rx /root; chown -R node /root/.n8n || true; chown -R node /root/.n8n; ln -s /root/.n8n /home/node; chown -R node /home/node || true; node /usr/local/bin/n8n + # Command Arguments + commandArgs: [] + + # here you can override the livenessProbe for the main container + # it may be used to increase the timeout for the livenessProbe (e.g., to resolve issues like + + livenessProbe: + httpGet: + path: /healthz + port: http + # initialDelaySeconds: 30 + # periodSeconds: 10 + # timeoutSeconds: 5 + # failureThreshold: 6 + # successThreshold: 1 + + # here you can override the readinessProbe for the main container + # it may be used to increase the timeout for the readinessProbe (e.g., to resolve issues like + + readinessProbe: + httpGet: + path: /healthz + port: http + # initialDelaySeconds: 30 + # periodSeconds: 10 + # timeoutSeconds: 5 + # failureThreshold: 6 + # successThreshold: 1 + + # List of initialization containers belonging to the pod. Init containers are executed in order prior to containers being started. + # See https://kubernetes.io/docs/concepts/workloads/pods/init-containers/ + initContainers: [] + + service: + annotations: {} + # -- Service types allow you to specify what kind of Service you want. + # E.g., ClusterIP, NodePort, LoadBalancer, ExternalName + type: ClusterIP + # -- Service port + port: 80 + + resources: {} + # We usually recommend not specifying default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 100 + targetCPUUtilizationPercentage: 80 + # targetMemoryUtilizationPercentage: 80 + nodeSelector: {} + tolerations: [] + affinity: {} + +# +# User defined supplementary K8s manifests +# + +# Takes a list of Kubernetes manifests and merges each resource with a default metadata.labels map and +# installs the result. +# Use this to add any arbitrary Kubernetes manifests alongside this chart instead of kubectl and scripts. +extraManifests: [] +# - apiVersion: v1 +# kind: ConfigMap +# metadata: +# name: example-config +# data: +# example.property.1: "value1" +# example.property.2: "value2" +# As an alternative to the above, you can also use a string as the value of the data field. +# - | +# apiVersion: v1 +# kind: ConfigMap +# metadata: +# name: example-config-string +# data: +# example.property.1: "value1" +# example.property.2: "value2" + +# String extraManifests supports using variables directly within a string manifest. +# Templates are rendered using the context defined in the values.yaml file, enabling dynamic and flexible content customization. +extraTemplateManifests: [] +# - | +# apiVersion: v1 +# kind: ConfigMap +# metadata: +# name: my-config +# stringData: +# image_name: {{ .Values.image.repository }} + +# Bitnami Valkey configuration +# https://artifacthub.io/packages/helm/bitnami/valkey +valkey: + enabled: false + # architecture: standalone + # + # primary: + # persistence: + # enabled: false + # existingClaim: "" + # size: 2Gi diff --git a/applications/n8n/dagger.json b/applications/n8n/dagger.json new file mode 100644 index 00000000..e63627c1 --- /dev/null +++ b/applications/n8n/dagger.json @@ -0,0 +1,20 @@ +{ + "name": "n8n", + "engineVersion": "v0.18.1", + "sdk": { + "source": "go" + }, + "dependencies": [ + { + "name": "helm", + "source": "github.com/sagikazarmark/daggerverse/helm@helm/v0.14.0", + "pin": "4981f49ead356d64a860ef85caec9b0622933ad7" + }, + { + "name": "replicated", + "source": "github.com/replicatedhq/daggerverse/replicated@1b8faf528b3c00b6b400bc7555ea4b5de5224ef8", + "pin": "1b8faf528b3c00b6b400bc7555ea4b5de5224ef8" + } + ], + "source": "dagger" +} diff --git a/applications/n8n/dagger/.gitattributes b/applications/n8n/dagger/.gitattributes new file mode 100644 index 00000000..3a454933 --- /dev/null +++ b/applications/n8n/dagger/.gitattributes @@ -0,0 +1,4 @@ +/dagger.gen.go linguist-generated +/internal/dagger/** linguist-generated +/internal/querybuilder/** linguist-generated +/internal/telemetry/** linguist-generated diff --git a/applications/n8n/dagger/.gitignore b/applications/n8n/dagger/.gitignore new file mode 100644 index 00000000..7ebabcc1 --- /dev/null +++ b/applications/n8n/dagger/.gitignore @@ -0,0 +1,4 @@ +/dagger.gen.go +/internal/dagger +/internal/querybuilder +/internal/telemetry diff --git a/applications/n8n/dagger/go.mod b/applications/n8n/dagger/go.mod new file mode 100644 index 00000000..ef0637fa --- /dev/null +++ b/applications/n8n/dagger/go.mod @@ -0,0 +1,50 @@ +module dagger/n-8-n + +go 1.23.6 + +require ( + github.com/99designs/gqlgen v0.17.70 + github.com/Khan/genqlient v0.8.0 + github.com/vektah/gqlparser/v2 v2.5.23 + go.opentelemetry.io/otel v1.34.0 + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0 + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0 + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0 + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.32.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.32.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0 + go.opentelemetry.io/otel/log v0.8.0 + go.opentelemetry.io/otel/metric v1.34.0 + go.opentelemetry.io/otel/sdk v1.34.0 + go.opentelemetry.io/otel/sdk/log v0.8.0 + go.opentelemetry.io/otel/sdk/metric v1.34.0 + go.opentelemetry.io/otel/trace v1.34.0 + go.opentelemetry.io/proto/otlp v1.3.1 + golang.org/x/sync v0.12.0 + google.golang.org/grpc v1.71.0 +) + +require ( + github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0 // indirect + github.com/sosodev/duration v1.3.1 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0 // indirect + golang.org/x/net v0.38.0 // indirect + golang.org/x/sys v0.31.0 // indirect + golang.org/x/text v0.23.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect + google.golang.org/protobuf v1.36.6 // indirect +) + +replace go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc => go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0 + +replace go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp => go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0 + +replace go.opentelemetry.io/otel/log => go.opentelemetry.io/otel/log v0.8.0 + +replace go.opentelemetry.io/otel/sdk/log => go.opentelemetry.io/otel/sdk/log v0.8.0 diff --git a/applications/n8n/dagger/go.sum b/applications/n8n/dagger/go.sum new file mode 100644 index 00000000..2d8a69fe --- /dev/null +++ b/applications/n8n/dagger/go.sum @@ -0,0 +1,85 @@ +github.com/99designs/gqlgen v0.17.70 h1:xgLIgQuG+Q2L/AE9cW595CT7xCWCe/bpPIFGSfsGSGs= +github.com/99designs/gqlgen v0.17.70/go.mod h1:fvCiqQAu2VLhKXez2xFvLmE47QgAPf/KTPN5XQ4rsHQ= +github.com/Khan/genqlient v0.8.0 h1:Hd1a+E1CQHYbMEKakIkvBH3zW0PWEeiX6Hp1i2kP2WE= +github.com/Khan/genqlient v0.8.0/go.mod h1:hn70SpYjWteRGvxTwo0kfaqg4wxvndECGkfa1fdDdYI= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0 h1:ad0vkEBuk23VJzZR9nkLVG0YAoN9coASF1GusYX6AlU= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0/go.mod h1:igFoXX2ELCW06bol23DWPB5BEWfZISOzSP5K2sbLea0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= +github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= +github.com/sosodev/duration v1.3.1 h1:qtHBDMQ6lvMQsL15g4aopM4HEfOaYuhWBw3NPTtlqq4= +github.com/sosodev/duration v1.3.1/go.mod h1:RQIBBX0+fMLc/D9+Jb/fwvVmo0eZvDDEERAikUR6SDg= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/vektah/gqlparser/v2 v2.5.23 h1:PurJ9wpgEVB7tty1seRUwkIDa/QH5RzkzraiKIjKLfA= +github.com/vektah/gqlparser/v2 v2.5.23/go.mod h1:D1/VCZtV3LPnQrcPBeR/q5jkSQIPti0uYCP/RI0gIeo= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= +go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0 h1:WzNab7hOOLzdDF/EoWCt4glhrbMPVMOO5JYTmpz36Ls= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0/go.mod h1:hKvJwTzJdp90Vh7p6q/9PAOd55dI6WA6sWj62a/JvSs= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0 h1:S+LdBGiQXtJdowoJoQPEtI52syEP/JYBUpjO49EQhV8= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0/go.mod h1:5KXybFvPGds3QinJWQT7pmXf+TN5YIa7CNYObWRkj50= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0 h1:j7ZSD+5yn+lo3sGV69nW04rRR0jhYnBwjuX3r0HvnK0= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0/go.mod h1:WXbYJTUaZXAbYd8lbgGuvih0yuCfOFC5RJoYnoLcGz8= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.32.0 h1:t/Qur3vKSkUCcDVaSumWF2PKHt85pc7fRvFuoVT8qFU= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.32.0/go.mod h1:Rl61tySSdcOJWoEgYZVtmnKdA0GeKrSqkHC1t+91CH8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0 h1:IJFEoHiytixx8cMiVAO+GmHR6Frwu+u5Ur8njpFO6Ac= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0/go.mod h1:3rHrKNtLIoS0oZwkY2vxi+oJcwFRWdtUyRII+so45p8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.32.0 h1:9kV11HXBHZAvuPUZxmMWrH8hZn/6UnHX4K0mu36vNsU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.32.0/go.mod h1:JyA0FHXe22E1NeNiHmVp7kFHglnexDQ7uRWDiiJ1hKQ= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0 h1:cMyu9O88joYEaI47CnQkxO1XZdpoTF9fEnW2duIddhw= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0/go.mod h1:6Am3rn7P9TVVeXYG+wtcGE7IE1tsQ+bP3AuWcKt/gOI= +go.opentelemetry.io/otel/log v0.8.0 h1:egZ8vV5atrUWUbnSsHn6vB8R21G2wrKqNiDt3iWertk= +go.opentelemetry.io/otel/log v0.8.0/go.mod h1:M9qvDdUTRCopJcGRKg57+JSQ9LgLBrwwfC32epk5NX8= +go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= +go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= +go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= +go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= +go.opentelemetry.io/otel/sdk/log v0.8.0 h1:zg7GUYXqxk1jnGF/dTdLPrK06xJdrXgqgFLnI4Crxvs= +go.opentelemetry.io/otel/sdk/log v0.8.0/go.mod h1:50iXr0UVwQrYS45KbruFrEt4LvAdCaWWgIrsN3ZQggo= +go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= +go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= +go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= +go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= +go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= +go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= +golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= +golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 h1:GVIKPyP/kLIyVOgOnTwFOrvQaQUzOzGMCxgFUOEmm24= +google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422/go.mod h1:b6h1vNKhxaSoEI+5jc3PJUCustfli/mRab7295pY7rw= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50= +google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg= +google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/applications/n8n/dagger/main.go b/applications/n8n/dagger/main.go new file mode 100644 index 00000000..1b2a65bc --- /dev/null +++ b/applications/n8n/dagger/main.go @@ -0,0 +1,124 @@ +// A generated module for N8N functions +// +// This module has been generated via dagger init and serves as a reference to +// basic module structure as you get started with Dagger. +// +// Two functions have been pre-created. You can modify, delete, or add to them, +// as needed. They demonstrate usage of arguments and return types using simple +// echo and grep commands. The functions can be called from the dagger CLI or +// from one of the SDKs. +// +// The first line in this comment block is a short description line and the +// rest is a long description with more detail on the module's purpose or usage, +// if appropriate. All modules should have a short description. + +package main + +import ( + "context" + "dagger/n-8-n/internal/dagger" + "fmt" + "log" + "time" +) + +type N8N struct { + // +private + Source *dagger.Directory +} + +const ( + REPLICATED_APP = "gerard-n8n" + CHART_DIR = "charts/n8n" +) + +func New( + // Project source directory + // + // +defaultPath="." + source *dagger.Directory) *N8N { + return &N8N{ + Source: source, + } +} + +func (m *N8N) CreateReplicatedRelease(ctx context.Context, token *dagger.Secret, version, channel string) (string, error) { + versionStr := m.generateVersion(ctx, version) + packagedDir := m.PrepareReplicatedRelease(ctx, versionStr) + return dag.Container(). + From("replicated/vendor-cli:latest"). + WithDirectory("/src", packagedDir). + WithEnvVariable("REPLICATED_APP", REPLICATED_APP). + WithSecretVariable("REPLICATED_API_TOKEN", token). + WithExec([]string{"/replicated", "release", "create", "--yaml-dir", "/src", "--promote", channel, "--version", version, "--ensure-channel"}). + Stdout(ctx) +} + +func (m *N8N) DownloadLicense(ctx context.Context, token *dagger.Secret, channel string) *dagger.File { + // create customer and download license + customerName := fmt.Sprintf("%s-customer", channel) + return dag.Container(). + From("replicated/vendor-cli:latest"). + WithEnvVariable("REPLICATED_APP", REPLICATED_APP). + WithSecretVariable("REPLICATED_API_TOKEN", token). + WithExec([]string{"/replicated", "customer", "create", "--name", customerName, "--channel", channel}). + WithExec([]string{"/replicated", "customer", "download-license", "--customer", customerName, "--output", "license.yaml"}). + File("license.yaml") +} + +func (m *N8N) PrepareReplicatedRelease(ctx context.Context, version string) *dagger.Directory { + releaseDir := m.Source.Directory("replicated") + + // lint chart + _, err := m.Lint(ctx) + if err != nil { + log.Fatalf("Failed to lint Helm chart: %v", err) + } + + // package Helm chart + appHelmChart := m.Package(ctx, version) + appHelmChartName, _ := appHelmChart.Name(ctx) + + // update KOTS HelmChart CR version + baseContainer := m.Base() + return baseContainer. + WithDirectory("/src", releaseDir). + WithWorkdir("/src"). + WithExec([]string{"apk", "add", "yq"}). + WithExec([]string{"yq", "-i", fmt.Sprintf(".spec.chart.chartVersion=\"%s\"", version), "kots-chart.yaml"}). + WithFile(appHelmChartName, appHelmChart). + Directory("/src") +} + +func (m *N8N) Base() *dagger.Container { + return dag.Container().From("alpine:latest") +} + +// Lint the Helm chart +func (m *N8N) Lint(ctx context.Context) (string, error) { + chart := m.chart() + return chart.Lint().Stdout(ctx) +} + +// Package the Helm chart +func (m *N8N) Package(ctx context.Context, version string) *dagger.File { + chart := m.chart() + return chart.Package(dagger.HelmChartPackageOpts{ + Version: version, + AppVersion: version, + DependencyUpdate: true, + }).File() +} + +func (m *N8N) chart() *dagger.HelmChart { + chart := m.Source.Directory(CHART_DIR) + return dag.Helm(dagger.HelmOpts{ + Version: "3.17.1", + }).Chart(chart) +} + +// Generates a version string based on the current date, branch name, and commit hash +func (m *N8N) generateVersion(ctx context.Context, version string) string { + date := time.Now().Format("20060102-150405") + return fmt.Sprintf("%s-%s", version, date) +} diff --git a/applications/n8n/docs/workflow.md b/applications/n8n/docs/workflow.md new file mode 100644 index 00000000..ae76b6a3 --- /dev/null +++ b/applications/n8n/docs/workflow.md @@ -0,0 +1,118 @@ +# Helm Chart Development Workflow + +## Objective + +Provide engineers with a clear, reproducible workflow for iterative Helm chart development, emphasizing fast feedback loops and easy configuration management. The workflow leverages Terraform, Helm CLI, and automation via `just` and Dagger. + +--- + +## Components + +- **n8n** *(local, custom Helm chart)* +- **CloudNativePG (Postgres)** *(remote Helm chart)* +- **Traefik ingress** *(remote Helm chart)* + +--- + +## Key Principles + +- **Progressive Complexity** – Start simple, add complexity incrementally. +- **Fast Feedback** – Immediate validation of changes. +- **Reproducibility** – Simple to recreate locally. +- **Modular Configuration** – Clear separation of values per component. +- **Automation** – Minimize repetitive tasks. + +--- + +## Project Repository Structure + +``` +. +├── charts +│ ├── n8n/ # Custom local Helm chart +│ │ ├── charts/ # Helm chart dependencies +│ └── n8n-values.yaml # Environment-specific Helm values for n8n for local dev +├── dagger # Automation logic (chart packaging/releases) +│ ├── main.go # Main dagger code +├── docs # Workflow documentation +│ └── workflow.md # Workflow guide (this document) +├── memory_bank # Tracking files for workflow tasks +├── justfile # Automation tasks (developer-friendly commands) +├── main.tf # Terraform config for Helm chart releases +``` + +--- + +## Prerequisites + +- Kubernetes cluster (**k3d recommended**; any valid Kubernetes cluster is acceptable) +- Helm CLI installed +- Terraform CLI installed +- Dagger CLI installed +- `just` command-line runner installed + +--- + +## Workflow Steps + +``` +just setup +just install +just destroy +``` + +### just setup +This command will create a Replicated DEV customer, so that we can test integration with Replicated SDK. The newly created customer id + license id will be persisted into `.env` file to be used by other tasks. +It will then run `helm dependency update` to download subcharts used by `n8n` chart. +Finally, it will run `terraform init` to initialize the Terraform providers (Helm) used to install charts + +### just install +This command will inject the `license id` created earlier into the `n8n` values so that the Replicated SDK installation will be successful. +It will then run `terraform apply` to install all the charts specified in the Terraform code. + +### just destroy +This command will delete all installed charts as well as delete the test customer. + +## Evaluation + +**What Worked Well:** + +* **Consistency/Reproducibility:** Strongest point via Dagger + Terraform. +* **Simple Interface:** `just` provides a low-friction CLI. +* **Automation:** Reduces repetitive commands. +* **Separation of Concerns:** Clear roles for Terraform, Dagger, `just`. +* **Pipeline as Code:** Version-controlled dev/test/deploy logic via Dagger. + +**Friction Points:** + +* **Learning Curve:** Terraform and Dagger concepts require learning investment. +* **Initial Setup:** Installation effort for tools. +* **Dagger Complexity:** Can become complex for intricate pipelines; debugging challenges. +* **Cold Starts:** Initial runs take longer (image pulls, provisioning). +* **Terraform `helm_release` Template Change Detection:** Terraform's `helm_release` may not detect *template-only* changes in local charts without a version bump or value change, potentially requiring manual intervention or preferring the Dagger/`helm upgrade` approach for rapid template iteration. +* **State Management:** Handling `kubeconfig` between Terraform/Dagger; Helm state lives outside Terraform unless `helm_release` is used. + +**Not Yet Implemented/Tested (Examples):** + +* Complex CI/CD integration details. +* Handling inter-chart dependencies locally. +* Advanced validation suites. +* Secure secret management locally. + +## Suitability + +**This approach is well-suited for:** + +* Teams already comfortable with IaC (Terraform) and containerization (Docker). +* Projects where consistency between local development and CI/CD is highly valued. +* Developing complex Helm charts where robust linting, testing, and validation are crucial. +* Teams looking to standardize development workflows across multiple developers or projects. +* Situations where developers need isolated, ephemeral Kubernetes environments frequently. +* Teams wanting to bundle Kubernetes cluster creation and application (Helm chart) installation into a single, automated setup process. This workflow supports this directly using Terraform (managing both cluster resources and `helm_release` resources) or via orchestration through `justfile` (e.g., `just setup` runs Terraform for the cluster, followed by `just deploy` running Dagger for the chart). +* **Is particularly effective for iterating on Helm chart configurations (`values.yaml`)** due to the ease of creating clean environments and reliably testing different value sets. + +**It might be overkill for:** + +* Very simple Helm charts with minimal testing needs. +* Teams unfamiliar or unwilling to adopt Terraform and/or Dagger. +* Projects where developers prefer direct, manual `helm` and `kubectl` interaction without abstraction layers. diff --git a/applications/n8n/justfile b/applications/n8n/justfile new file mode 100644 index 00000000..0fd1c001 --- /dev/null +++ b/applications/n8n/justfile @@ -0,0 +1,71 @@ +set dotenv-load + +customer_name := "dev-customer" +channel_name := "workflow-experiment" + +setup: + @echo "Create DEV customer..." + replicated customer create --name {{customer_name}} --channel {{channel_name}} + @echo "CUSTOMER_ID=$(replicated customer inspect --customer {{customer_name}} --output json | jq -r '.id')" >> .env + @echo "LICENSE_ID=$(replicated customer inspect --customer {{customer_name}} --output json | jq -r '.installationId')" >> .env + + @echo "Update sub-charts dependencies..." + just helm-dep-update + + @echo "Setting up Terraform..." + terraform init + +install: + @echo "Add DEV customer to n8n-values.yaml..." + just set-replicated-values + @echo "Installing all charts..." + terraform apply --auto-approve + +destroy: + @echo "Destroying Terraform..." + terraform destroy --auto-approve + @echo "Remove global/replicated block from n8n-values.yaml if any..." + yq -i 'del(.global)' ./charts/n8n-values.yaml + yq -i 'del(.replicated)' ./charts/n8n-values.yaml + @echo "Delete DEV customer..." + replicated customer archive "$CUSTOMER_ID" + @echo "Remove env CUSTOMER_ID and LICENSE_ID..." + gsed -i '/^CUSTOMER_ID=/d' .env || true + gsed -i '/^LICENSE_ID=/d' .env || true + +helm-dep-update: + @echo "Updating Helm dependencies for n8n chart..." + cd charts/n8n && helm dependency update + +helm-template: + @echo "Running helm template to verify current values..." + helm template n8n ./charts/n8n -f ./charts/n8n-values.yaml --debug + +helm-lint: + @echo "Running helm lint to verify current values..." + helm lint ./charts/n8n -f ./charts/n8n-values.yaml --debug + +helm-diff: + @echo "Running helm diff to verify current values..." + helm diff upgrade n8n -n n8n ./charts/n8n -f ./charts/n8n-values.yaml --debug + +get-manifests: + @echo "Getting manifests for n8n chart..." + terraform output n8n_template | jq -r '. | fromjson' | yq -P + +create-replicated-release version channel: + @echo "Creating replicated release..." + dagger call create-replicated-release --token=env://REPLICATED_API_TOKEN --version={{version}} --channel={{channel}} + +show-replicated-values: + @echo "Logging into replicated registry with license ID..." + helm registry login registry.replicated.com --username $LICENSE_ID --password $LICENSE_ID + @echo "Showing values from replicated Helm chart..." + helm show values oci://registry.replicated.com/library/replicated --version 1.5.0 | yq + @echo "Logging out from replicated registry..." + helm registry logout registry.replicated.com + +set-replicated-values: + @echo "Setting replicated values in n8n-values.yaml..." + yq eval -i '.replicated.integration.licenseID = env(LICENSE_ID)' ./charts/n8n-values.yaml + yq eval -i '.replicated.integration.enabled = true' ./charts/n8n-values.yaml diff --git a/applications/n8n/main.tf b/applications/n8n/main.tf new file mode 100644 index 00000000..c9a95a18 --- /dev/null +++ b/applications/n8n/main.tf @@ -0,0 +1,101 @@ +terraform { + required_providers { + helm = { + source = "hashicorp/helm" + } + kubernetes = { + source = "hashicorp/kubernetes" + version = "~> 2.25.2" + } + } + required_version = ">= 1.0.0" +} + +provider "helm" { + experiments { + manifest = true + } + kubernetes { + config_path = "~/.kube/config" + } +} + +provider "kubernetes" { + config_path = "~/.kube/config" +} + +# Create n8n namespace +resource "kubernetes_namespace" "n8n" { + metadata { + name = "n8n" + } +} + +# Install CloudNativePG Operator +resource "helm_release" "cloudnativepg" { + name = "cloudnativepg" + repository = "https://cloudnative-pg.github.io/charts" + chart = "cloudnative-pg" + version = "0.23.2" + namespace = kubernetes_namespace.n8n.metadata[0].name + + # Wait for the CloudNativePG operator to be ready + wait = true +} + +# Install Traefik Ingress Controller +resource "helm_release" "traefik" { + name = "traefik" + repository = "https://traefik.github.io/charts" + chart = "traefik" + version = "35.0.0" + namespace = "traefik-system" + create_namespace = true + + # Set values for Traefik (customize as needed) + set { + name = "ingressClass.enabled" + value = "true" + } + + set { + name = "ingressClass.isDefaultClass" + value = "true" + } +} + +# Install n8n chart (local chart) +resource "helm_release" "n8n" { + name = "n8n" + chart = "${path.module}/charts/n8n" + namespace = kubernetes_namespace.n8n.metadata[0].name + dependency_update = true + + # Load values from the n8n-values.yaml file + values = [ + file("${path.module}/charts/n8n-values.yaml") + ] + + # Set the encryption key using the random_password resource + set_sensitive { + name = "main.secret.n8n.encryption_key" + value = random_password.encryption_key.result + } + + # Dependencies - wait for both remote charts to be installed first + depends_on = [ + helm_release.cloudnativepg, + helm_release.traefik + ] +} + +# Generate random passwords for n8n +resource "random_password" "encryption_key" { + length = 32 + special = false +} + +output "n8n_template" { + sensitive = true + value = helm_release.n8n.manifest +} diff --git a/applications/n8n/replicated/kots-application.yaml b/applications/n8n/replicated/kots-application.yaml new file mode 100644 index 00000000..6cb09796 --- /dev/null +++ b/applications/n8n/replicated/kots-application.yaml @@ -0,0 +1,18 @@ +apiVersion: kots.io/v1beta1 +kind: Application +metadata: + name: n8n +spec: + title: n8n + icon: https://www.shareicon.net/data/256x256/2016/07/26/802134_workflow_512x512.png + allowRollback: true + additionalNamespaces: + - "traefik-system" + - "n8n" + - "*" + ports: + - serviceName: n8n/n8n + servicePort: 5678 + applicationUrl: "http://n8n" + statusInformers: + - n8n/deployment/n8n \ No newline at end of file diff --git a/applications/n8n/replicated/kots-chart.yaml b/applications/n8n/replicated/kots-chart.yaml new file mode 100644 index 00000000..459e1b52 --- /dev/null +++ b/applications/n8n/replicated/kots-chart.yaml @@ -0,0 +1,11 @@ +apiVersion: kots.io/v1beta2 +kind: HelmChart +metadata: + name: n8n +spec: + chart: + name: n8n + chartVersion: 0.0.1 + helmUpgradeFlags: + - --debug + values: \ No newline at end of file diff --git a/applications/n8n/replicated/kots-config.yaml b/applications/n8n/replicated/kots-config.yaml new file mode 100644 index 00000000..f64d8a17 --- /dev/null +++ b/applications/n8n/replicated/kots-config.yaml @@ -0,0 +1,4 @@ +apiVersion: kots.io/v1beta1 +kind: Config +metadata: + name: n8n \ No newline at end of file diff --git a/applications/n8n/replicated/kots-ec.yaml b/applications/n8n/replicated/kots-ec.yaml new file mode 100644 index 00000000..264d9035 --- /dev/null +++ b/applications/n8n/replicated/kots-ec.yaml @@ -0,0 +1,20 @@ +apiVersion: embeddedcluster.replicated.com/v1beta1 +kind: Config +spec: + version: 2.3.1+k8s-1.30 + roles: + controller: + name: management + labels: + management: "true" + custom: + - name: app + labels: + app: "true" + unsupportedOverrides: + k0s: | + config: + spec: + api: + extraArgs: + service-node-port-range: 80-32767 \ No newline at end of file