From 913fd78ae2984b0bf68a613bca1c91bfba46d133 Mon Sep 17 00:00:00 2001 From: Stavros Kois <47820033+stavros-k@users.noreply.github.com> Date: Fri, 8 Dec 2023 16:44:11 +0200 Subject: [PATCH] feat(ingress) refactor + tests (#621) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Description** ⚒️ Fixes # Partially solves https://github.com/truecharts/charts/issues/11205 Partially solves https://github.com/truecharts/charts/issues/14160 Partially solves https://github.com/truecharts/charts/issues/8080 Partially solves https://github.com/truecharts/charts/issues/9635 All the above are "partially" because changes need to be performed on the chart it self Eg. Set the targetSelector for the "other" ingress to point to the "other" service **⚙️ Type of change** - [x] ⚙️ Feature/App addition - [x] 🪛 Bugfix - [ ] ⚠️ Breaking change (fix or feature that would cause existing functionality to not work as expected) - [x] 🔃 Refactor of current code **🧪 How Has This Been Tested?** **📃 Notes:** **✔️ Checklist:** - [x] ⚖️ My code follows the style guidelines of this project - [x] 👀 I have performed a self-review of my own code - [x] #️⃣ I have commented my code, particularly in hard-to-understand areas - [ ] 📄 I have made corresponding changes to the documentation - [ ] ⚠️ My changes generate no new warnings - [x] 🧪 I have added tests to this description that prove my fix is effective or that my feature works - [x] ⬆️ I increased versions for any altered app according to semantic versioning **➕ App addition** If this PR is an app addition please make sure you have done the following. - [ ] 🪞 I have opened a PR on [truecharts/containers](https://github.com/truecharts/containers) adding the container to TrueCharts mirror repo. - [ ] 🖼️ I have added an icon in the Chart's root directory called `icon.png` --- _Please don't blindly check all the boxes. Read them and only check those that apply. Those checkboxes are there for the reviewer to see what is this all about and the status of this PR with a quick glance._ --- library/common-test/Chart.yaml | 2 +- library/common-test/ci/ingress-values.yaml | 3 - .../tests/ingress/cert_manager_test.yaml | 115 ++++ .../tests/ingress/homepage_test.yaml | 152 +++++ .../tests/ingress/metadata_test.yaml | 274 ++++---- .../common-test/tests/ingress/name_test.yaml | 67 ++ .../tests/ingress/presence_test.yaml | 93 --- .../common-test/tests/ingress/rules_test.yaml | 199 ++++++ .../tests/ingress/service_reference_test.yaml | 79 --- .../common-test/tests/ingress/tls_test.yaml | 260 +++++--- .../tests/ingress/traefik_test.yaml | 466 ++++++++++++++ .../tests/ingress/validation_test.yaml | 607 ++++++++++++++++++ .../tests/ingress/values_test.yaml | 143 ----- .../tests/service/metadata_test.yaml | 8 +- library/common/Chart.yaml | 2 +- library/common/templates/class/_ingress.tpl | 196 ++---- .../common/templates/class/_priorityClass.tpl | 2 +- .../common/templates/class/cnpg/_cluster.tpl | 2 +- .../common/templates/lib/_tc_capabilities.tpl | 5 - .../lib/container/_securityContext.tpl | 2 +- .../templates/lib/ingress/_targetSelector.tpl | 85 +++ .../templates/lib/ingress/_validation.tpl | 172 ++++- .../lib/ingress/integrations/_certManager.tpl | 29 + .../lib/ingress/integrations/_homepage.tpl | 100 ++- .../lib/ingress/integrations/_traefik.tpl | 115 ++++ .../lib/service/_additionalAnnotations.tpl | 12 +- .../templates/lib/util/_primary_service.tpl | 4 +- library/common/templates/loader/_init.tpl | 2 +- library/common/templates/spawner/_ingress.tpl | 121 ++-- .../templates/spawner/_priorityClass.tpl | 2 +- library/common/templates/spawner/_service.tpl | 2 +- library/common/values.yaml | 155 ++--- 32 files changed, 2626 insertions(+), 850 deletions(-) create mode 100644 library/common-test/tests/ingress/cert_manager_test.yaml create mode 100644 library/common-test/tests/ingress/homepage_test.yaml create mode 100644 library/common-test/tests/ingress/name_test.yaml delete mode 100644 library/common-test/tests/ingress/presence_test.yaml create mode 100644 library/common-test/tests/ingress/rules_test.yaml delete mode 100644 library/common-test/tests/ingress/service_reference_test.yaml create mode 100644 library/common-test/tests/ingress/traefik_test.yaml create mode 100644 library/common-test/tests/ingress/validation_test.yaml delete mode 100644 library/common-test/tests/ingress/values_test.yaml create mode 100644 library/common/templates/lib/ingress/_targetSelector.tpl create mode 100644 library/common/templates/lib/ingress/integrations/_certManager.tpl create mode 100644 library/common/templates/lib/ingress/integrations/_traefik.tpl diff --git a/library/common-test/Chart.yaml b/library/common-test/Chart.yaml index 34a2e909..31dc8d5e 100644 --- a/library/common-test/Chart.yaml +++ b/library/common-test/Chart.yaml @@ -3,7 +3,7 @@ appVersion: "" dependencies: - name: common repository: file://../common - version: ~16.0.0 + version: ~16.1.0 deprecated: false description: Helper chart to test different use cases of the common library home: https://github.com/truecharts/apps/tree/master/charts/library/common-test diff --git a/library/common-test/ci/ingress-values.yaml b/library/common-test/ci/ingress-values.yaml index ac6f17ef..2cb68375 100644 --- a/library/common-test/ci/ingress-values.yaml +++ b/library/common-test/ci/ingress-values.yaml @@ -53,9 +53,6 @@ ingress: paths: - path: / pathType: Prefix - service: - name: - port: tls: [] scalecert: diff --git a/library/common-test/tests/ingress/cert_manager_test.yaml b/library/common-test/tests/ingress/cert_manager_test.yaml new file mode 100644 index 00000000..62c8b52f --- /dev/null +++ b/library/common-test/tests/ingress/cert_manager_test.yaml @@ -0,0 +1,115 @@ +suite: ingress - cert manager metadata test +templates: + - common.yaml +chart: + appVersion: &appVer v9.9.9 +release: + name: test-release-name + namespace: test-release-namespace +tests: + - it: should pass with ingress created with annotations from cert manager + set: + operator: &operator + verify: + enabled: false + service: &service + my-service: + enabled: true + primary: true + ports: + main: + enabled: true + primary: true + port: 80 + ingress: + my-ingress: + enabled: true + primary: true + integrations: + traefik: &traefik + enabled: false + certManager: + enabled: true + certificateIssuer: some-issuer + hosts: &hosts + - host: test-host + paths: + - path: /test-path + asserts: + - documentIndex: &ingressDoc 1 + isKind: + of: Ingress + - documentIndex: *ingressDoc + equal: + path: metadata.name + value: test-release-name-common-test + - documentIndex: *ingressDoc + equal: + path: metadata.annotations + value: + cert-manager.io/cluster-issuer: some-issuer + cert-manager.io/private-key-rotation-policy: Always + - documentIndex: *ingressDoc + equal: + path: metadata.namespace + value: test-release-namespace + + - it: should pass with ingress created without cert manager annotations when cert manager false + set: + operator: *operator + service: *service + ingress: + my-ingress: + enabled: true + primary: true + integrations: + traefik: *traefik + certManager: + enabled: false + hosts: *hosts + asserts: + - documentIndex: *ingressDoc + isKind: + of: Ingress + - documentIndex: *ingressDoc + equal: + path: metadata.name + value: test-release-name-common-test + - documentIndex: *ingressDoc + isNull: + path: metadata.annotations + + # Failures + - it: should fail with missing certificateIssuer + set: + operator: *operator + service: *service + ingress: + my-ingress1: + enabled: true + primary: true + integrations: + certManager: + enabled: true + hosts: *hosts + asserts: + - failedTemplate: + errorMessage: Ingress - Expected a non-empty [integrations.certManager.certificateIssuer] + + - it: should fail with certificateIssuer not a string + set: + operator: *operator + service: *service + ingress: + my-ingress1: + enabled: true + primary: true + integrations: + certManager: + enabled: true + certificateIssuer: + - some-issuer + hosts: *hosts + asserts: + - failedTemplate: + errorMessage: Ingress - Expected [integrations.certManager.certificateIssuer] to be a [string], but got [slice] diff --git a/library/common-test/tests/ingress/homepage_test.yaml b/library/common-test/tests/ingress/homepage_test.yaml new file mode 100644 index 00000000..1ad45c69 --- /dev/null +++ b/library/common-test/tests/ingress/homepage_test.yaml @@ -0,0 +1,152 @@ +suite: ingress - homepage metadata test +templates: + - common.yaml +chart: + appVersion: &appVer v9.9.9 +release: + name: test-release-name + namespace: test-release-namespace +tests: + - it: should pass with ingress created with annotations from homepage enabled + set: + k1: some-name + k2: some-desc + k3: some-icon + k4: some-url + k5: some-type + k6: some-group + operator: &operator + verify: + enabled: false + service: &service + my-service: + enabled: true + primary: true + ports: + main: + enabled: true + primary: true + port: 80 + someHost: test-host + somePath: /test-path + ingress: + my-ingress1: + enabled: true + primary: true + integrations: + traefik: &traefik + enabled: false + homepage: + enabled: true + hosts: + - host: "{{ .Values.someHost }}" + paths: + - path: "{{ .Values.somePath }}" + my-ingress2: + enabled: true + integrations: + traefik: *traefik + homepage: + enabled: true + name: "{{ .Values.k1 }}" + description: "{{ .Values.k2 }}" + icon: "{{ .Values.k3 }}" + group: "{{ .Values.k6 }}" + weight: 1 + podSelector: + - main + - other + widget: + url: "{{ .Values.k4 }}" + type: "{{ .Values.k5 }}" + custom: + some-key: some-value + some-other-key: some-other-value + hosts: &hosts + - host: test-host + paths: + - path: /test-path + asserts: + - documentIndex: &ingressDoc 1 + isKind: + of: Ingress + - documentIndex: *ingressDoc + equal: + path: metadata.annotations + value: + gethomepage.dev/enabled: "true" + gethomepage.dev/name: CommonTest + gethomepage.dev/description: Helper chart to test different use cases of the common library + gethomepage.dev/icon: https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png + gethomepage.dev/widget.type: common-test + gethomepage.dev/widget.url: "https://test-host/test-path" + - documentIndex: *ingressDoc + equal: + path: metadata.name + value: test-release-name-common-test + - documentIndex: *ingressDoc + equal: + path: metadata.namespace + value: test-release-namespace + - documentIndex: &otherIngressDoc 2 + isKind: + of: Ingress + - documentIndex: *otherIngressDoc + equal: + path: metadata.annotations + value: + gethomepage.dev/enabled: "true" + gethomepage.dev/name: some-name + gethomepage.dev/description: some-desc + gethomepage.dev/group: some-group + gethomepage.dev/icon: some-icon + gethomepage.dev/widget.url: some-url + gethomepage.dev/weight: "1" + gethomepage.dev/widget.type: some-type + gethomepage.dev/pod-selector: pod.name in (main,other) + gethomepage.dev/widget.some-key: some-value + gethomepage.dev/widget.some-other-key: some-other-value + - documentIndex: *otherIngressDoc + equal: + path: metadata.name + value: test-release-name-common-test-my-ingress2 + - documentIndex: *otherIngressDoc + equal: + path: metadata.namespace + value: test-release-namespace + + # Failures + - it: should fail with podSelector not a slice + set: + operator: *operator + service: *service + ingress: + my-ingress1: + enabled: true + primary: true + integrations: + homepage: + enabled: true + podSelector: main + hosts: *hosts + asserts: + - failedTemplate: + errorMessage: Ingress - Expected [integrations.homepage.podSelector] to be a [slice], but got [string] + + - it: should fail with custom widget not a map + set: + operator: *operator + service: *service + ingress: + my-ingress1: + enabled: true + primary: true + integrations: + homepage: + enabled: true + widget: + custom: "not a map" + hosts: *hosts + asserts: + - failedTemplate: + errorMessage: Ingress - Expected [integrations.homepage.widget.custom] to be a [map], but got [string] diff --git a/library/common-test/tests/ingress/metadata_test.yaml b/library/common-test/tests/ingress/metadata_test.yaml index 5c4cfeae..ad3fc5f7 100644 --- a/library/common-test/tests/ingress/metadata_test.yaml +++ b/library/common-test/tests/ingress/metadata_test.yaml @@ -1,4 +1,4 @@ -suite: ingress metadata +suite: ingress metadata test templates: - common.yaml chart: @@ -7,135 +7,66 @@ release: name: test-release-name namespace: test-release-namespace tests: - - it: default metadata should pass + - it: should pass with ingress created with labels and annotations set: - operator: + label1: label1 + label2: global_label2 + annotation1: annotation1 + annotation2: global_annotation2 + global: + labels: + g_label1: global_label1 + g_label2: "{{ .Values.label2 }}" + annotations: + g_annotation1: global_annotation1 + g_annotation2: "{{ .Values.annotation2 }}" + operator: &operator verify: enabled: false - ingress.main.enabled: true - service: - main: + service: &service + my-service: enabled: true + primary: true ports: main: enabled: true primary: true - port: 12345 - workload: - my-workload: - enabled: true - primary: true - type: Deployment - podSpec: {} - asserts: - - documentIndex: &ingressDocument 2 - isKind: - of: Ingress - - documentIndex: *ingressDocument - equal: - path: metadata.annotations - value: - traefik.ingress.kubernetes.io/router.entrypoints: websecure - traefik.ingress.kubernetes.io/router.middlewares: tc-system-chain-basic@kubernetescrd - - - documentIndex: *ingressDocument - equal: - path: metadata.labels - value: - app: common-test-1.0.0 - release: test-release-name - helm-revision: "0" - helm.sh/chart: common-test-1.0.0 - app.kubernetes.io/name: common-test - app.kubernetes.io/instance: test-release-name - app.kubernetes.io/managed-by: Helm - app.kubernetes.io/version: *appVer - - - it: cors metadata should pass - set: - operator: - verify: - enabled: false + port: 80 ingress: - main: - enabled: true - allowCors: true - service: - main: - enabled: true - ports: - main: - enabled: true - primary: true - port: 12345 - workload: - my-workload: + my-ingress1: enabled: true primary: true - type: Deployment - podSpec: {} - asserts: - - documentIndex: &ingressDocument 2 - isKind: - of: Ingress - - documentIndex: *ingressDocument - equal: - path: metadata.annotations - value: - traefik.ingress.kubernetes.io/router.entrypoints: websecure - traefik.ingress.kubernetes.io/router.middlewares: tc-system-tc-opencors-chain@kubernetescrd - - - documentIndex: *ingressDocument - equal: - path: metadata.labels - value: - app: common-test-1.0.0 - release: test-release-name - helm-revision: "0" - helm.sh/chart: common-test-1.0.0 - app.kubernetes.io/name: common-test - app.kubernetes.io/instance: test-release-name - app.kubernetes.io/managed-by: Helm - app.kubernetes.io/version: *appVer - - - it: custom metadata should pass - set: - operator: - verify: - enabled: false - service: - main: - enabled: true - ports: - main: - enabled: true - primary: true - port: 12345 - workload: - my-workload: - enabled: true - primary: true - type: Deployment - podSpec: {} - ingress: - main: - enabled: true - annotations: - test_annotation: test labels: - test_label: test + label1: "{{ .Values.label1 }}" + label2: label2 + annotations: + annotation1: "{{ .Values.annotation1 }}" + annotation2: annotation2 + integrations: &integrations + traefik: + enabled: false + hosts: &hosts + - host: test-host + paths: + - path: /test-path + my-ingress2: + enabled: true + primary: false + integrations: *integrations + hosts: *hosts asserts: - - documentIndex: &ingressDocument 2 + - documentIndex: &ingressDoc 1 isKind: of: Ingress - - documentIndex: *ingressDocument + - documentIndex: *ingressDoc equal: path: metadata.annotations value: - test_annotation: test - traefik.ingress.kubernetes.io/router.entrypoints: websecure - traefik.ingress.kubernetes.io/router.middlewares: tc-system-chain-basic@kubernetescrd - - documentIndex: *ingressDocument + annotation1: annotation1 + annotation2: annotation2 + g_annotation1: global_annotation1 + g_annotation2: global_annotation2 + - documentIndex: *ingressDoc equal: path: metadata.labels value: @@ -143,8 +74,121 @@ tests: release: test-release-name helm-revision: "0" helm.sh/chart: common-test-1.0.0 - app.kubernetes.io/name: common-test - app.kubernetes.io/instance: test-release-name app.kubernetes.io/managed-by: Helm app.kubernetes.io/version: *appVer - test_label: test + app.kubernetes.io/instance: test-release-name + app.kubernetes.io/name: common-test + g_label1: global_label1 + g_label2: global_label2 + label1: label1 + label2: label2 + - documentIndex: *ingressDoc + equal: + path: metadata.name + value: test-release-name-common-test + - documentIndex: *ingressDoc + equal: + path: metadata.namespace + value: test-release-namespace + - documentIndex: &otherIngressDoc 2 + isKind: + of: Ingress + - documentIndex: *otherIngressDoc + equal: + path: metadata.labels + value: + app: common-test-1.0.0 + app.kubernetes.io/instance: test-release-name + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: common-test + app.kubernetes.io/version: *appVer + g_label1: global_label1 + g_label2: global_label2 + helm-revision: "0" + helm.sh/chart: common-test-1.0.0 + release: test-release-name + - documentIndex: *otherIngressDoc + equal: + path: metadata.name + value: test-release-name-common-test-my-ingress2 + - documentIndex: *otherIngressDoc + equal: + path: metadata.namespace + value: test-release-namespace + + - it: should pass with ingress created with object namespace from tpl + set: + key: some-namespace + operator: *operator + service: *service + ingress: + my-ingress1: + enabled: true + primary: true + namespace: "{{ .Values.key }}" + integrations: *integrations + hosts: *hosts + asserts: + - documentIndex: *ingressDoc + equal: + path: metadata.namespace + value: some-namespace + + - it: should pass with ingress created with global namespace from tpl + set: + key: global-namespace + global: + namespace: "{{ .Values.key }}" + operator: *operator + service: *service + ingress: + my-ingress1: + enabled: true + primary: true + integrations: *integrations + hosts: *hosts + asserts: + - documentIndex: *ingressDoc + equal: + path: metadata.namespace + value: global-namespace + + - it: should pass with ingress created with root namespace from tpl + set: + key: local-namespace + namespace: "{{ .Values.key }}" + global: + namespace: global-namespace + operator: *operator + service: *service + ingress: + my-ingress1: + enabled: true + primary: true + integrations: *integrations + hosts: *hosts + asserts: + - documentIndex: *ingressDoc + equal: + path: metadata.namespace + value: local-namespace + + - it: should pass with ingress created with namespace in TrueNAS SCALE + set: + global: + ixChartContext: + iAmNotEmpty: true + namespace: ix-namespace + operator: *operator + service: *service + ingress: + my-ingress1: + enabled: true + primary: true + integrations: *integrations + hosts: *hosts + asserts: + - documentIndex: *ingressDoc + equal: + path: metadata.namespace + value: ix-namespace diff --git a/library/common-test/tests/ingress/name_test.yaml b/library/common-test/tests/ingress/name_test.yaml new file mode 100644 index 00000000..6f36ded8 --- /dev/null +++ b/library/common-test/tests/ingress/name_test.yaml @@ -0,0 +1,67 @@ +suite: ingress name test +templates: + - common.yaml +release: + name: test-release-name + namespace: test-release-namespace +tests: + - it: should generate correct name + set: + operator: + verify: + enabled: false + service: + my-service: + enabled: true + primary: true + ports: + main: + enabled: true + primary: true + port: 80 + ingress: + my-ingress1: + enabled: true + primary: true + hosts: &hosts + - host: test-host + paths: + - path: /test-path + my-ingress2: + enabled: true + hosts: *hosts + my-ingress3: + enabled: true + expandObjectName: false + hosts: *hosts + asserts: + - documentIndex: &ingressDoc 1 + isKind: + of: Ingress + - documentIndex: *ingressDoc + isAPIVersion: + of: networking.k8s.io/v1 + - documentIndex: *ingressDoc + equal: + path: metadata.name + value: test-release-name-common-test + - documentIndex: &otherIngressDoc 2 + isKind: + of: Ingress + - documentIndex: *otherIngressDoc + isAPIVersion: + of: networking.k8s.io/v1 + - documentIndex: *otherIngressDoc + equal: + path: metadata.name + value: test-release-name-common-test-my-ingress2 + - documentIndex: &thirdIngressDoc 3 + isKind: + of: Ingress + - documentIndex: *thirdIngressDoc + isAPIVersion: + of: networking.k8s.io/v1 + - documentIndex: *thirdIngressDoc + equal: + path: metadata.name + value: my-ingress3 diff --git a/library/common-test/tests/ingress/presence_test.yaml b/library/common-test/tests/ingress/presence_test.yaml deleted file mode 100644 index 43328c63..00000000 --- a/library/common-test/tests/ingress/presence_test.yaml +++ /dev/null @@ -1,93 +0,0 @@ -suite: ingress presence -templates: - - common.yaml -release: - name: test-release-name - namespace: test-release-namespace -tests: - - it: default should pass - asserts: - - hasDocuments: - count: 0 - - - - it: explicitly disabled should pass - set: - ingress.main.enabled: false - asserts: - - hasDocuments: - count: 0 - - - it: explicitly enabled should pass - set: - operator: - verify: - enabled: false - ingress.main.enabled: true - service: - main: - enabled: true - ports: - main: - enabled: true - primary: true - port: 12345 - workload: - my-workload: - enabled: true - primary: true - type: Deployment - podSpec: {} - asserts: - - hasDocuments: - count: 3 - - documentIndex: 0 - not: true - isKind: - of: Ingress - - documentIndex: 1 - not: true - isKind: - of: Ingress - - documentIndex: 2 - isKind: - of: Ingress - - - it: multiple enabled should pass - set: - operator: - verify: - enabled: false - ingress.main.enabled: true - ingress.test.enabled: true - service: - main: - enabled: true - ports: - main: - enabled: true - primary: true - port: 12345 - workload: - my-workload: - enabled: true - primary: true - type: Deployment - podSpec: {} - asserts: - - hasDocuments: - count: 4 - - documentIndex: 0 - not: true - isKind: - of: Ingress - - documentIndex: 1 - not: true - isKind: - of: Ingress - - documentIndex: 2 - isKind: - of: Ingress - - documentIndex: 3 - isKind: - of: Ingress diff --git a/library/common-test/tests/ingress/rules_test.yaml b/library/common-test/tests/ingress/rules_test.yaml new file mode 100644 index 00000000..eda68c2e --- /dev/null +++ b/library/common-test/tests/ingress/rules_test.yaml @@ -0,0 +1,199 @@ +suite: ingress - rules test +templates: + - common.yaml +chart: + appVersion: &appVer v9.9.9 +release: + name: test-release-name + namespace: test-release-namespace +tests: + - it: should pass with ingress created with ingressClassName + set: + operator: &operator + verify: + enabled: false + service: + my-service: + enabled: true + primary: true + ports: + main: + enabled: true + primary: true + port: 80 + ingress: + my-ingress: + enabled: true + primary: true + ingressClassName: some-class + hosts: + - host: test-host + paths: + - path: /test-path + integrations: &integrations + traefik: + enabled: false + asserts: + - documentIndex: &ingressDoc 1 + isKind: + of: Ingress + - documentIndex: *ingressDoc + equal: + path: metadata.name + value: test-release-name-common-test + - documentIndex: *ingressDoc + equal: + path: spec.ingressClassName + value: some-class + + - it: should pass with ingress created with rules + set: + operator: &operator + verify: + enabled: false + service: + my-service: + enabled: true + primary: true + ports: + main: + enabled: true + primary: true + port: 80 + someHost: test-host + somePath: /test-path + someType: Exact + ingress: + my-ingress: + enabled: true + primary: true + hosts: &hosts + - host: "{{ .Values.someHost }}" + paths: + - path: "{{ .Values.somePath }}" + pathType: "{{ .Values.someType }}" + - host: test-other-host + paths: + - path: /test-other-path + - path: /test-other-path2 + overrideService: + name: other-service + port: 8080 + integrations: *integrations + asserts: + - documentIndex: &ingressDoc 1 + isKind: + of: Ingress + - documentIndex: *ingressDoc + equal: + path: metadata.name + value: test-release-name-common-test + - documentIndex: *ingressDoc + equal: + path: spec.rules + value: + - host: test-host + http: + paths: + - path: /test-path + pathType: Exact + backend: + service: + name: test-release-name-common-test + port: + number: 80 + - host: test-other-host + http: + paths: + - path: /test-other-path + pathType: Prefix + backend: + service: + name: test-release-name-common-test + port: + number: 80 + - path: /test-other-path2 + pathType: Prefix + backend: + service: + name: other-service + port: + number: 8080 + + - it: should pass with ingress created with rules with targetSelector + set: + operator: &operator + verify: + enabled: false + service: + my-service: + enabled: true + primary: true + ports: + main: + enabled: true + primary: true + port: 80 + my-service2: + enabled: true + ports: + my-port2: + enabled: true + port: 9000 + ingress: + my-ingress: + enabled: true + primary: true + targetSelector: + my-service2: my-port2 + hosts: &hosts + - host: test-host + paths: + - path: /test-path + - host: test-other-host + paths: + - path: /test-other-path + - path: /test-other-path2 + overrideService: + name: other-service + port: 8080 + integrations: *integrations + asserts: + - documentIndex: &ingressDoc 2 + isKind: + of: Ingress + - documentIndex: *ingressDoc + equal: + path: metadata.name + value: test-release-name-common-test + - documentIndex: *ingressDoc + equal: + path: spec.rules + value: + - host: test-host + http: + paths: + - path: /test-path + pathType: Prefix + backend: + service: + name: test-release-name-common-test-my-service2 + port: + number: 9000 + - host: test-other-host + http: + paths: + - path: /test-other-path + pathType: Prefix + backend: + service: + name: test-release-name-common-test-my-service2 + port: + number: 9000 + - path: /test-other-path2 + pathType: Prefix + backend: + service: + name: other-service + port: + number: 8080 diff --git a/library/common-test/tests/ingress/service_reference_test.yaml b/library/common-test/tests/ingress/service_reference_test.yaml deleted file mode 100644 index 0fe2042f..00000000 --- a/library/common-test/tests/ingress/service_reference_test.yaml +++ /dev/null @@ -1,79 +0,0 @@ -suite: ingress service reference -templates: - - common.yaml -release: - name: test-release-name - namespace: test-release-namespace -tests: - - it: default should pass - set: - operator: - verify: - enabled: false - ingress.main.enabled: true - service: - main: - enabled: true - ports: - main: - enabled: true - primary: true - port: 12345 - workload: - my-workload: - enabled: true - primary: true - type: Deployment - podSpec: {} - asserts: - - documentIndex: &ingressDocument 2 - isKind: - of: Ingress - - documentIndex: *ingressDocument - equal: - path: spec.rules[0].http.paths[0].backend.service - value: - name: test-release-name-common-test - port: - number: 12345 - - - - it: custom service reference should pass - set: - operator: - verify: - enabled: false - service: - main: - enabled: true - ports: - main: - enabled: true - primary: true - port: 12345 - workload: - my-workload: - enabled: true - primary: true - type: Deployment - podSpec: {} - ingress.main: - enabled: true - hosts: - - host: chart-test.local - paths: - - path: / - service: - name: pathService - port: 1234 - asserts: - - documentIndex: &ingressDocument 2 - isKind: - of: Ingress - - documentIndex: *ingressDocument - equal: - path: spec.rules[0].http.paths[0].backend.service - value: - name: pathService - port: - number: 1234 diff --git a/library/common-test/tests/ingress/tls_test.yaml b/library/common-test/tests/ingress/tls_test.yaml index d07b8bbc..089ef69f 100644 --- a/library/common-test/tests/ingress/tls_test.yaml +++ b/library/common-test/tests/ingress/tls_test.yaml @@ -1,143 +1,195 @@ -suite: ingress tls +suite: ingress - tls test templates: - common.yaml +chart: + appVersion: &appVer v9.9.9 release: name: test-release-name namespace: test-release-namespace tests: - - it: default should pass + - it: should pass with ingress created with certManager integration set: - operator: + operator: &operator verify: enabled: false - service: - main: + service: &service + my-service: enabled: true + primary: true ports: main: enabled: true primary: true - port: 12345 - workload: - my-workload: + port: 80 + ingress: + my-ingress: enabled: true primary: true - type: Deployment - podSpec: {} - ingress.main.enabled: true + hosts: + - host: test-host + paths: + - path: /test-path + - host: other-test-host + paths: + - path: /other-test-path + integrations: &integrations + traefik: + enabled: false + certManager: + enabled: true + certificateIssuer: some-issuer asserts: - - documentIndex: &ingressDocument 2 + - documentIndex: &ingressDoc 1 isKind: of: Ingress - - documentIndex: *ingressDocument - isNull: + - documentIndex: *ingressDoc + equal: + path: metadata.name + value: test-release-name-common-test + - documentIndex: *ingressDoc + equal: path: spec.tls + value: + - hosts: + - test-host + secretName: test-release-name-common-test-tls-0 + - hosts: + - other-test-host + secretName: test-release-name-common-test-tls-1 - - it: tls enabled should pass + - it: should pass with ingress created with tls with scaleCert set: - operator: - verify: - enabled: false - service: - main: - enabled: true - ports: - main: - enabled: true - primary: true - port: 12345 - workload: - my-workload: + operator: *operator + service: *service + namespace: ix-test-namespace + global: + ixChartContext: + imNotEmpty: true + ixCertificates: + "1": + certificate: some_cert + privatekey: some_key + "2": + certificate: some_other_cert + privatekey: some_other_key + ingress: + my-ingress: enabled: true primary: true - type: Deployment - podSpec: {} - ingress.main: - enabled: true - tls: - - secretName: test - hosts: - - hostname + hosts: + - host: test-host + paths: + - path: /test-path + - host: other-test-host + paths: + - path: /other-test-path + tls: + - hosts: + - test-host + - other-test-host + scaleCert: "1" + - hosts: + - some-other-test-host + scaleCert: "2" + integrations: &integrations + traefik: + enabled: false asserts: - - documentIndex: &ingressDocument 2 + - documentIndex: &secretDoc 2 + isKind: + of: Secret + - documentIndex: *secretDoc + equal: + path: metadata.name + value: test-release-name-common-test-scale-tls-0 + - documentIndex: *secretDoc + equal: + path: data + value: + tls.crt: c29tZV9jZXJ0 + tls.key: c29tZV9rZXk= + - documentIndex: *secretDoc + equal: + path: type + value: kubernetes.io/tls + - documentIndex: &secretDoc 3 + isKind: + of: Secret + - documentIndex: *secretDoc + equal: + path: metadata.name + value: test-release-name-common-test-scale-tls-1 + - documentIndex: *secretDoc + equal: + path: data + value: + tls.crt: c29tZV9vdGhlcl9jZXJ0 + tls.key: c29tZV9vdGhlcl9rZXk= + - documentIndex: *secretDoc + equal: + path: type + value: kubernetes.io/tls + - documentIndex: &ingressDoc 1 isKind: of: Ingress - - documentIndex: *ingressDocument + - documentIndex: *ingressDoc equal: - path: spec.tls[0] + path: metadata.name + value: test-release-name-common-test + - documentIndex: *ingressDoc + equal: + path: spec.tls value: - secretName: test - hosts: - - hostname + - hosts: + - test-host + - other-test-host + secretName: test-release-name-common-test-scale-tls-0 + - hosts: + - some-other-test-host + secretName: test-release-name-common-test-scale-tls-1 - - it: tls enabled without secret should pass + - it: should pass with ingress created with tls with certificateIssuer set: - operator: - verify: - enabled: false - service: - main: - enabled: true - ports: - main: - enabled: true - primary: true - port: 12345 - workload: - my-workload: + operator: *operator + service: *service + ingress: + my-ingress: enabled: true primary: true - type: Deployment - podSpec: {} - ingress.main: - enabled: true - tls: - - hosts: - - hostname + hosts: + - host: test-host + paths: + - path: /test-path + - host: other-test-host + paths: + - path: /other-test-path + tls: + - hosts: + - test-host + - other-test-host + certificateIssuer: some-issuer + - hosts: + - some-other-test-host + certificateIssuer: some-other-issuer + integrations: &integrations + traefik: + enabled: false asserts: - - documentIndex: &ingressDocument 2 + - documentIndex: &ingressDoc 1 isKind: of: Ingress - - documentIndex: *ingressDocument + - documentIndex: *ingressDoc equal: - path: spec.tls[0] - value: - hosts: - - hostname - - - it: tls enabled with secret template should pass - set: - operator: - verify: - enabled: false - service: - main: - enabled: true - ports: - main: - enabled: true - primary: true - port: 12345 - workload: - my-workload: - enabled: true - primary: true - type: Deployment - podSpec: {} - ingress.main: - enabled: true - tls: - - secretName: "{{ .Release.Name }}-secret" - hosts: - - hostname - asserts: - - documentIndex: &ingressDocument 2 - isKind: - of: Ingress - - documentIndex: *ingressDocument + path: metadata.name + value: test-release-name-common-test + - documentIndex: *ingressDoc equal: - path: spec.tls[0] + path: spec.tls value: - secretName: test-release-name-secret - hosts: - - hostname + - hosts: + - test-host + - other-test-host + secretName: test-release-name-common-test-tls-0 + - hosts: + - some-other-test-host + secretName: test-release-name-common-test-tls-1 diff --git a/library/common-test/tests/ingress/traefik_test.yaml b/library/common-test/tests/ingress/traefik_test.yaml new file mode 100644 index 00000000..2eb69a5f --- /dev/null +++ b/library/common-test/tests/ingress/traefik_test.yaml @@ -0,0 +1,466 @@ +suite: ingress - traefik metadata test +templates: + - common.yaml +chart: + appVersion: &appVer v9.9.9 +release: + name: test-release-name + namespace: test-release-namespace +tests: + - it: should pass with ingress created with annotations from traefik + set: + operator: &operator + verify: + enabled: false + service: &service + my-service: + enabled: true + primary: true + ports: + main: + enabled: true + primary: true + port: 80 + ingress: + my-ingress: + enabled: true + primary: true + hosts: &hosts + - host: test-host + paths: + - path: /test-path + asserts: + - documentIndex: &ingressDoc 1 + isKind: + of: Ingress + - documentIndex: *ingressDoc + equal: + path: metadata.name + value: test-release-name-common-test + - documentIndex: *ingressDoc + equal: + path: metadata.annotations + value: + traefik.ingress.kubernetes.io/router.entrypoints: websecure + traefik.ingress.kubernetes.io/router.middlewares: chain-basic-tc-system@kubernetescrd + - documentIndex: *ingressDoc + equal: + path: metadata.namespace + value: test-release-namespace + + - it: should pass with ingress created without traefik annotations when traefik false + set: + operator: *operator + service: *service + ingress: + my-ingress: + enabled: true + primary: true + integrations: + traefik: + enabled: false + hosts: *hosts + asserts: + - documentIndex: *ingressDoc + isKind: + of: Ingress + - documentIndex: *ingressDoc + equal: + path: metadata.name + value: test-release-name-common-test + - documentIndex: *ingressDoc + isNull: + path: metadata.annotations + + - it: should replace local fixedMiddlewares when allowCors true + set: + operator: *operator + service: *service + ingress: + my-ingress: + enabled: true + primary: true + integrations: + traefik: + enabled: true + allowCors: true + fixedMiddlewares: + - some-fixed-middleware + hosts: *hosts + asserts: + - documentIndex: *ingressDoc + isKind: + of: Ingress + - documentIndex: *ingressDoc + equal: + path: metadata.name + value: test-release-name-common-test + - documentIndex: *ingressDoc + equal: + path: metadata.annotations + value: + traefik.ingress.kubernetes.io/router.entrypoints: websecure + traefik.ingress.kubernetes.io/router.middlewares: tc-opencors-chain-tc-system@kubernetescrd + + - it: should replace global fixedMiddlewares when allowCors true + set: + operator: *operator + service: *service + ingress: + my-ingress: + enabled: true + primary: true + integrations: + traefik: + enabled: true + allowCors: true + hosts: *hosts + asserts: + - documentIndex: *ingressDoc + isKind: + of: Ingress + - documentIndex: *ingressDoc + equal: + path: metadata.name + value: test-release-name-common-test + - documentIndex: *ingressDoc + equal: + path: metadata.annotations + value: + traefik.ingress.kubernetes.io/router.entrypoints: websecure + traefik.ingress.kubernetes.io/router.middlewares: tc-opencors-chain-tc-system@kubernetescrd + + - it: should replace global fixedMiddlewares when local fixedMiddlewares is defined + set: + operator: *operator + service: *service + ingress: + my-ingress: + enabled: true + primary: true + integrations: + traefik: + enabled: true + fixedMiddlewares: + - some-fixed-middleware + hosts: *hosts + asserts: + - documentIndex: *ingressDoc + isKind: + of: Ingress + - documentIndex: *ingressDoc + equal: + path: metadata.name + value: test-release-name-common-test + - documentIndex: *ingressDoc + equal: + path: metadata.annotations + value: + traefik.ingress.kubernetes.io/router.entrypoints: websecure + traefik.ingress.kubernetes.io/router.middlewares: some-fixed-middleware-tc-system@kubernetescrd + + - it: should override default entrypoint(s) + set: + operator: *operator + service: *service + ingress: + my-ingress: + enabled: true + primary: true + integrations: + traefik: + enabled: true + entrypoints: + - web + - websecure + hosts: *hosts + asserts: + - documentIndex: *ingressDoc + isKind: + of: Ingress + - documentIndex: *ingressDoc + equal: + path: metadata.name + value: test-release-name-common-test + - documentIndex: *ingressDoc + equal: + path: metadata.annotations + value: + traefik.ingress.kubernetes.io/router.entrypoints: web,websecure + traefik.ingress.kubernetes.io/router.middlewares: chain-basic-tc-system@kubernetescrd + + - it: should not contain fixed middlewares when global is disabled + set: + operator: *operator + service: *service + global: + traefik: + enableFixedMiddlewares: false + ingress: + my-ingress: + enabled: true + primary: true + integrations: + traefik: + enabled: true + hosts: *hosts + asserts: + - documentIndex: *ingressDoc + isKind: + of: Ingress + - documentIndex: *ingressDoc + equal: + path: metadata.annotations + value: + traefik.ingress.kubernetes.io/router.entrypoints: websecure + + - it: should not contain fixed middlewares when local is disabled + set: + operator: *operator + service: *service + global: + traefik: + enableFixedMiddlewares: true + ingress: + my-ingress: + enabled: true + primary: true + integrations: + traefik: + enabled: true + enableFixedMiddlewares: false + hosts: *hosts + asserts: + - documentIndex: *ingressDoc + isKind: + of: Ingress + - documentIndex: *ingressDoc + equal: + path: metadata.annotations + value: + traefik.ingress.kubernetes.io/router.entrypoints: websecure + + - it: should set correct namespace when operator is registered + set: + operator: + verify: + enabled: false + traefik: + namespace: some-ns + service: *service + ingress: + my-ingress: + enabled: true + primary: true + integrations: + traefik: + enabled: true + entrypoints: + - web + - websecure + hosts: *hosts + asserts: + - documentIndex: *ingressDoc + isKind: + of: Ingress + - documentIndex: *ingressDoc + equal: + path: metadata.name + value: test-release-name-common-test + - documentIndex: *ingressDoc + equal: + path: metadata.annotations + value: + traefik.ingress.kubernetes.io/router.entrypoints: web,websecure + traefik.ingress.kubernetes.io/router.middlewares: chain-basic-some-ns@kubernetescrd + + - it: should set correct namespace when ingressClassName is defined regardless of operator + set: + operator: + verify: + enabled: false + traefik: + namespace: some-ns + someclass: some-class + service: *service + ingress: + my-ingress: + enabled: true + primary: true + integrations: + traefik: + enabled: true + ingressClassName: "{{ .Values.someclass }}" + hosts: *hosts + asserts: + - documentIndex: *ingressDoc + isKind: + of: Ingress + - documentIndex: *ingressDoc + equal: + path: metadata.name + value: test-release-name-common-test + - documentIndex: *ingressDoc + equal: + path: metadata.annotations + value: + traefik.ingress.kubernetes.io/router.entrypoints: websecure + traefik.ingress.kubernetes.io/router.middlewares: chain-basic-some-class@kubernetescrd + + - it: should set correct namespace when ingressClassName is defined in SCALE regardless of operator + set: + operator: + verify: + enabled: false + traefik: + namespace: some-ns + service: *service + global: + ixChartContext: + imNotEmpty: true + namespace: ix-namespace + ingress: + my-ingress: + enabled: true + primary: true + integrations: + traefik: + enabled: true + ingressClassName: some-class + hosts: *hosts + asserts: + - documentIndex: *ingressDoc + isKind: + of: Ingress + - documentIndex: *ingressDoc + equal: + path: metadata.name + value: test-release-name-common-test + - documentIndex: *ingressDoc + equal: + path: metadata.annotations + value: + traefik.ingress.kubernetes.io/router.entrypoints: websecure + traefik.ingress.kubernetes.io/router.middlewares: chain-basic-ix-some-class@kubernetescrd + + - it: should add the defined middlewares to the ingress + set: + operator: *operator + service: *service + ingress: + my-ingress: + enabled: true + primary: true + integrations: + traefik: + enabled: true + middlewares: + - some-middleware + - some-other-middleware + hosts: *hosts + asserts: + - documentIndex: *ingressDoc + isKind: + of: Ingress + - documentIndex: *ingressDoc + isKind: + of: Ingress + - documentIndex: *ingressDoc + equal: + path: metadata.name + value: test-release-name-common-test + - documentIndex: *ingressDoc + equal: + path: metadata.annotations + value: + traefik.ingress.kubernetes.io/router.entrypoints: websecure + traefik.ingress.kubernetes.io/router.middlewares: chain-basic-tc-system@kubernetescrd,some-middleware-tc-system@kubernetescrd,some-other-middleware-tc-system@kubernetescrd + + # Failures + - it: should fail with entrypoint not a slice + set: + operator: *operator + service: *service + ingress: + my-ingress1: + enabled: true + primary: true + integrations: + traefik: + enabled: true + entrypoints: "not a string" + hosts: *hosts + asserts: + - failedTemplate: + errorMessage: Ingress - Expected [integrations.traefik.entrypoints] to be a [slice], but got [string] + + - it: should fail with middlewares not a slice + set: + operator: *operator + service: *service + ingress: + my-ingress1: + enabled: true + primary: true + integrations: + traefik: + enabled: true + middlewares: "not a slice" + hosts: *hosts + asserts: + - failedTemplate: + errorMessage: Ingress - Expected [integrations.traefik.middlewares] to be a [slice], but got [string] + + - it: should fail with fixedMiddlewares not a slice + set: + operator: *operator + service: *service + ingress: + my-ingress1: + enabled: true + primary: true + integrations: + traefik: + enabled: true + fixedMiddlewares: "not a slice" + hosts: *hosts + asserts: + - failedTemplate: + errorMessage: Ingress - Expected [integrations.traefik.fixedMiddlewares] to be a [slice], but got [string] + + - it: should fail with duplicate middlewares + set: + operator: *operator + service: *service + ingress: + my-ingress1: + enabled: true + primary: true + integrations: + traefik: + enabled: true + middlewares: + - chain-basic + hosts: *hosts + asserts: + - failedTemplate: + errorMessage: Ingress - Combined traefik middlewares contain duplicates [chain-basic, chain-basic] + + - it: should fail with duplicate entrypoints + set: + operator: *operator + service: *service + ingress: + my-ingress1: + enabled: true + primary: true + integrations: + traefik: + enabled: true + entrypoints: + - websecure + - websecure + hosts: *hosts + asserts: + - failedTemplate: + errorMessage: Ingress - Combined traefik entrypoints contain duplicates [websecure, websecure] diff --git a/library/common-test/tests/ingress/validation_test.yaml b/library/common-test/tests/ingress/validation_test.yaml new file mode 100644 index 00000000..bd7dd62c --- /dev/null +++ b/library/common-test/tests/ingress/validation_test.yaml @@ -0,0 +1,607 @@ +suite: ingress validation test +templates: + - common.yaml +release: + name: test-release-name + namespace: test-release-namespace +tests: + - it: should fail if required is true but enabled is false + set: + operator: &operator + verify: + enabled: false + service: &service + my-service: + enabled: true + primary: true + ports: + main: + enabled: true + primary: true + port: 80 + ingress: + my-ingress: + enabled: false + required: true + asserts: + - failedTemplate: + errorMessage: Ingress - Expected ingress [my-ingress] to be enabled. This chart is designed to work only with ingress enabled. + + - it: should fail with name longer than 253 characters + set: + operator: *operator + service: *service + ingress: + my-ing: + enabled: true + primary: true + hosts: &hosts + - host: test-host + paths: + - path: /test-path + my-ingress-super-long-name-that-is-longer-than-253-characters-my-ingress-super-long-name-that-is-longer-than-253-characters-my-ingress-super-long-name-that-is-longer-than-253-characters-my-ingress-super-long-long-long-long-long-long-long-long-name: + enabled: true + asserts: + - failedTemplate: + errorMessage: Name [test-release-name-common-test-my-ingress-super-long-name-that-is-longer-than-253-characters-my-ingress-super-long-name-that-is-longer-than-253-characters-my-ingress-super-long-name-that-is-longer-than-253-characters-my-ingress-super-long-long-long-long-long-long-long-long-name] is not valid. Must start and end with an alphanumeric lowercase character. It can contain '-'. And must be at most 253 characters. + + - it: should fail with name starting with underscore + set: + operator: *operator + service: *service + ingress: + my-ing: + enabled: true + primary: true + hosts: *hosts + _my-ingress: + enabled: true + asserts: + - failedTemplate: + errorMessage: Name [test-release-name-common-test-_my-ingress] is not valid. Must start and end with an alphanumeric lowercase character. It can contain '-'. And must be at most 253 characters. + + - it: should fail with namespace longer than 63 characters + set: + operator: *operator + service: *service + ingress: + my-ingress: + enabled: true + primary: true + hosts: *hosts + namespace: my-extra-super-duper-long-name-that-is-longer-than-63-characters + asserts: + - failedTemplate: + errorMessage: Ingress - Namespace [my-extra-super-duper-long-name-that-is-longer-than-63-characters] is not valid. Must start and end with an alphanumeric lowercase character. It can contain '-'. And must be at most 63 characters. + + - it: should fail with namespace not starting with [ix-] in TrueNAS SCALE + set: + global: + ixChartContext: + iAmNotEmpty: true + operator: *operator + service: + my-service: + enabled: true + primary: true + namespace: ix-namespace + ports: + main: + enabled: true + primary: true + port: 80 + ingress: + my-ingress: + enabled: true + primary: true + hosts: *hosts + namespace: my-namespace + asserts: + - failedTemplate: + errorMessage: Ingress - Namespace [my-namespace] expected to have [ix-] prefix when installed in TrueNAS SCALE + + - it: should fail with labels not a dict + set: + operator: *operator + ingress: + my-ingress: + enabled: true + primary: true + labels: "not a dict" + asserts: + - failedTemplate: + errorMessage: Ingress - Expected [labels] to be a dictionary, but got [string] + + - it: should fail with annotations not a dict + set: + operator: *operator + ingress: + my-ingress: + enabled: true + primary: true + annotations: "not a dict" + asserts: + - failedTemplate: + errorMessage: Ingress - Expected [annotations] to be a dictionary, but got [string] + + - it: should fail with empty enabled + set: + operator: *operator + ingress: + my-ingress: + enabled: + asserts: + - failedTemplate: + errorMessage: Ingress - Expected the defined key [enabled] in [ingress.my-ingress] to not be empty + + - it: should fail with targetSelector not a map + set: + operator: *operator + ingress: + my-ingress: + enabled: true + primary: true + targetSelector: "not a map" + asserts: + - failedTemplate: + errorMessage: Ingress - Expected [targetSelector] to be a [map], but got [string] + + - it: should fail with targetSelector having more than one key + set: + operator: *operator + ingress: + my-ingress: + enabled: true + primary: true + targetSelector: + main: main + other: other + asserts: + - failedTemplate: + errorMessage: Ingress - Expected [targetSelector] to have exactly one key, but got [2] + + - it: should fail with targetSelector key missing value + set: + operator: *operator + ingress: + my-ingress: + enabled: true + primary: true + targetSelector: + main: + asserts: + - failedTemplate: + errorMessage: Ingress - Expected [targetSelector.main] to have a value + + - it: should fail with targetSelector key having non-string value + set: + operator: *operator + ingress: + my-ingress: + enabled: true + primary: true + targetSelector: + main: + - not a string + asserts: + - failedTemplate: + errorMessage: Ingress - Expected [targetSelector.main] to be a [string], but got [slice] + + - it: should fail with no hosts + set: + operator: *operator + ingress: + my-ingress: + enabled: true + primary: true + hosts: [] + asserts: + - failedTemplate: + errorMessage: Ingress - Expected non-empty [hosts] + + - it: should fail with hosts not a slice + set: + operator: *operator + ingress: + my-ingress: + enabled: true + primary: true + hosts: not-a-slice + asserts: + - failedTemplate: + errorMessage: Ingress - Expected [hosts] to be a [slice], but got [string] + + - it: should fail with no hosts.host + set: + operator: *operator + ingress: + my-ingress: + enabled: true + primary: true + hosts: + - paths: + - path: /test-path + asserts: + - failedTemplate: + errorMessage: Ingress - Expected non-empty [hosts.host] + + - it: should fail with host starting with https:// + set: + operator: *operator + ingress: + my-ingress: + enabled: true + primary: true + hosts: + - host: https://test-host + paths: + - path: /test-path + asserts: + - failedTemplate: + errorMessage: Ingress - Expected [hosts.host] to not start with [https://], but got [https://test-host] + + - it: should fail with host starting with http:// + set: + operator: *operator + ingress: + my-ingress: + enabled: true + primary: true + hosts: + - host: http://test-host + paths: + - path: /test-path + asserts: + - failedTemplate: + errorMessage: Ingress - Expected [hosts.host] to not start with [http://], but got [http://test-host] + + - it: should fail with host contain ":" + set: + operator: *operator + ingress: + my-ingress: + enabled: true + primary: true + hosts: + - host: test-host:123 + paths: + - path: /test-path + asserts: + - failedTemplate: + errorMessage: Ingress - Expected [hosts.host] to not contain [:], but got [test-host:123] + + - it: should fail without paths + set: + operator: *operator + ingress: + my-ingress: + enabled: true + primary: true + hosts: + - host: test-host + asserts: + - failedTemplate: + errorMessage: Ingress - Expected non-empty [hosts.paths] + + - it: should fail with paths not a slice + set: + operator: *operator + ingress: + my-ingress: + enabled: true + primary: true + hosts: + - host: test-host + paths: not-a-slice + asserts: + - failedTemplate: + errorMessage: Ingress - Expected [hosts.paths] to be a [slice], but got [string] + + - it: should fail with invalid pathType + set: + operator: *operator + ingress: + my-ingress: + enabled: true + primary: true + hosts: + - host: test-host + paths: + - path: /test-path + pathType: not-a-valid-path-type + asserts: + - failedTemplate: + errorMessage: Ingress - Expected [hosts.paths.pathType] to be one of [Prefix, Exact, ImplementationSpecific], but got [not-a-valid-path-type] + + - it: should fail with path not starting with / (only on pathType Exact and Prefix) + set: + operator: *operator + ingress: + my-ingress: + enabled: true + primary: true + hosts: + - host: test-host + paths: + - path: test-path + pathType: Prefix + asserts: + - failedTemplate: + errorMessage: Ingress - Expected [hosts.paths.path] to start with [/], but got [test-path] + + - it: should fail if name in the overrideService is missing + set: + operator: *operator + ingress: + my-ingress: + enabled: true + primary: true + hosts: + - host: test-host + paths: + - path: /test-path + overrideService: + port: 80 + asserts: + - failedTemplate: + errorMessage: Ingress - Expected non-empty [hosts.paths.overrideService.name] + + - it: should fail if port in the overrideService is missing + set: + operator: *operator + ingress: + my-ingress: + enabled: true + primary: true + hosts: + - host: test-host + paths: + - path: /test-path + overrideService: + name: test-service + asserts: + - failedTemplate: + errorMessage: Ingress - Expected non-empty [hosts.paths.overrideService.port] + + - it: should fail if targeted service does not exist + set: + operator: *operator + ingress: + my-ingress: + enabled: true + primary: true + targetSelector: + my-service: my-port + hosts: + - host: test-host + paths: + - path: /test-path + asserts: + - failedTemplate: + errorMessage: Ingress - Expected targeted service [my-service] to exist + + - it: should fail if targeted service is not enabled + set: + operator: *operator + service: + my-service: + enabled: false + primary: true + ports: + my-port: + enabled: true + primary: true + port: 80 + ingress: + my-ingress: + enabled: true + primary: true + targetSelector: + my-service: my-port + hosts: + - host: test-host + paths: + - path: /test-path + asserts: + - failedTemplate: + errorMessage: Ingress - Expected targeted service [my-service] to be enabled + + - it: should fail without primary service or a targetSelector + set: + operator: *operator + ingress: + my-ingress: + enabled: true + primary: true + hosts: + - host: test-host + paths: + - path: /test-path + asserts: + - failedTemplate: + errorMessage: Ingress - Expected [targetSelector] or a primary service to exist + + - it: should fail if the targeted service does not have the targeted port + set: + operator: *operator + service: + my-service: + enabled: true + primary: true + ports: + my-other-port: + enabled: true + primary: true + port: 80 + ingress: + my-ingress: + enabled: true + primary: true + targetSelector: + my-service: my-port + hosts: + - host: test-host + paths: + - path: /test-path + asserts: + - failedTemplate: + errorMessage: Ingress - Expected targeted service [my-service] to have port [my-port] + + - it: should fail if the targeted service port is not enabled + set: + operator: *operator + service: + my-service: + enabled: true + primary: true + ports: + my-port: + enabled: false + primary: true + port: 80 + my-other-port: + enabled: true + primary: true + port: 80 + ingress: + my-ingress: + enabled: true + primary: true + targetSelector: + my-service: my-port + hosts: + - host: test-host + paths: + - path: /test-path + asserts: + - failedTemplate: + errorMessage: Ingress - Expected targeted service port [my-port] to be enabled + + - it: should fail if tls.hosts are empty + set: + operator: *operator + service: *service + ingress: + my-ingress: + enabled: true + primary: true + hosts: *hosts + tls: + - hosts: [] + asserts: + - failedTemplate: + errorMessage: Ingress - Expected non-empty [tls.hosts] + + - it: should fail if tls.hosts is not a slice + set: + operator: *operator + service: *service + ingress: + my-ingress: + enabled: true + primary: true + hosts: *hosts + tls: + - hosts: not-a-slice + asserts: + - failedTemplate: + errorMessage: Ingress - Expected [tls.hosts] to be a [slice], but got [string] + + - it: should fail if tls.hosts.host is empty + set: + operator: *operator + service: *service + ingress: + my-ingress: + enabled: true + primary: true + hosts: *hosts + tls: + - hosts: + - "" + asserts: + - failedTemplate: + errorMessage: Ingress - Expected non-empty entry in [tls.hosts] + + - it: should fail if tls.hosts.host starts with https:// + set: + operator: *operator + service: *service + ingress: + my-ingress: + enabled: true + primary: true + hosts: *hosts + tls: + - hosts: + - https://test-host + asserts: + - failedTemplate: + errorMessage: Ingress - Expected entry in [tls.hosts] to not start with [https://], but got [https://test-host] + + - it: should fail if tls.hosts.host starts with http:// + set: + operator: *operator + service: *service + ingress: + my-ingress: + enabled: true + primary: true + hosts: *hosts + tls: + - hosts: + - http://test-host + asserts: + - failedTemplate: + errorMessage: Ingress - Expected entry in [tls.hosts] to not start with [http://], but got [http://test-host] + + - it: should fail if tls.hosts.host contains ":" + set: + operator: *operator + service: *service + ingress: + my-ingress: + enabled: true + primary: true + hosts: *hosts + tls: + - hosts: + - test-host:123 + asserts: + - failedTemplate: + errorMessage: Ingress - Expected entry in [tls.hosts] to not contain [:], but got [test-host:123] + + - it: should fail if more than 1 cert option is set under tls + set: + operator: *operator + service: *service + ingress: + my-ingress: + enabled: true + primary: true + hosts: *hosts + tls: + - hosts: + - test-host + secretName: test-secret + scaleCert: "1" + asserts: + - failedTemplate: + errorMessage: Ingress - Expected only one of [scaleCert, secretName] to be set, but got [scaleCert, secretName] + + - it: should fail with scaleCert outside of SCALE + set: + operator: *operator + service: *service + ingress: + my-ingress: + enabled: true + primary: true + hosts: *hosts + tls: + - hosts: + - test-host + scaleCert: "1" + asserts: + - failedTemplate: + errorMessage: Ingress - [tls.scalecert] can only be used in TrueNAS SCALE diff --git a/library/common-test/tests/ingress/values_test.yaml b/library/common-test/tests/ingress/values_test.yaml deleted file mode 100644 index ef06584e..00000000 --- a/library/common-test/tests/ingress/values_test.yaml +++ /dev/null @@ -1,143 +0,0 @@ -suite: ingress values -templates: - - common.yaml -release: - name: test-release-name - namespace: test-release-namespace -tests: - - it: default should pass - set: - operator: - verify: - enabled: false - service: - main: - enabled: true - ports: - main: - enabled: true - primary: true - port: 12345 - workload: - my-workload: - enabled: true - primary: true - type: Deployment - podSpec: {} - ingress.main.enabled: true - asserts: - - documentIndex: &ingressDocument 2 - isKind: - of: Ingress - - documentIndex: *ingressDocument - equal: - path: spec.rules[0].host - value: chart-example.local - - documentIndex: *ingressDocument - equal: - path: spec.rules[0].http.paths[0].path - value: "/" - - - it: custom host and path should pass - set: - operator: - verify: - enabled: false - service: - main: - enabled: true - ports: - main: - enabled: true - primary: true - port: 12345 - workload: - my-workload: - enabled: true - primary: true - type: Deployment - podSpec: {} - ingress.main: - enabled: true - hosts: - - host: chart-test.local - paths: - - path: /test - asserts: - - documentIndex: &ingressDocument 2 - isKind: - of: Ingress - - documentIndex: *ingressDocument - equal: - path: spec.rules[0].host - value: chart-test.local - - documentIndex: *ingressDocument - equal: - path: spec.rules[0].http.paths[0].path - value: "/test" - - - it: host with template should pass - set: - operator: - verify: - enabled: false - service: - main: - enabled: true - ports: - main: - enabled: true - primary: true - port: 12345 - workload: - my-workload: - enabled: true - primary: true - type: Deployment - podSpec: {} - ingress.main: - enabled: true - hosts: - - host: "{{ .Release.Name }}.hostname" - asserts: - - documentIndex: &ingressDocument 2 - isKind: - of: Ingress - - documentIndex: *ingressDocument - equal: - path: spec.rules[0].host - value: test-release-name.hostname - - - it: path with template should pass - set: - operator: - verify: - enabled: false - service: - main: - enabled: true - ports: - main: - enabled: true - primary: true - port: 12345 - workload: - my-workload: - enabled: true - primary: true - type: Deployment - podSpec: {} - ingress.main: - enabled: true - hosts: - - host: chart-test.local - paths: - - path: "/{{ .Release.Name }}.path" - asserts: - - documentIndex: &ingressDocument 2 - isKind: - of: Ingress - - documentIndex: *ingressDocument - equal: - path: spec.rules[0].http.paths[0].path - value: "/test-release-name.path" diff --git a/library/common-test/tests/service/metadata_test.yaml b/library/common-test/tests/service/metadata_test.yaml index b3af6915..681e57c7 100644 --- a/library/common-test/tests/service/metadata_test.yaml +++ b/library/common-test/tests/service/metadata_test.yaml @@ -113,11 +113,13 @@ tests: path: metadata.namespace value: test-release-namespace - - it: should pass with service type LoadBalancer, with https port and addMetalLBAnnotations/Traefik true + - it: should pass with service type LoadBalancer, with https port and metallb.addServiceAnnotations/Traefik true set: global: - addMetalLBAnnotations: true - addTraefikAnnotations: true + metallb: + addServiceAnnotations: true + traefik: + addServiceAnnotations: true proto: https service: my-service1: diff --git a/library/common/Chart.yaml b/library/common/Chart.yaml index c49add2c..377e2622 100644 --- a/library/common/Chart.yaml +++ b/library/common/Chart.yaml @@ -15,4 +15,4 @@ maintainers: name: common sources: null type: library -version: 16.0.0 +version: 16.1.0 diff --git a/library/common/templates/class/_ingress.tpl b/library/common/templates/class/_ingress.tpl index f85fe63b..6e925721 100644 --- a/library/common/templates/class/_ingress.tpl +++ b/library/common/templates/class/_ingress.tpl @@ -1,157 +1,93 @@ -{{/* -This template serves as a blueprint for all Ingress objects that are created -within the common library. +{{/* Ingress Class */}} +{{/* Call this template: +{{ include "tc.v1.common.class.ingress" (dict "rootCtx" $ "objectData" $objectData) }} + +rootCtx: The root context of the chart. +objectData: The object data to be used to render the Ingress. */}} + {{- define "tc.v1.common.class.ingress" -}} - {{- $fullName := include "tc.v1.common.lib.chart.names.fullname" . -}} - {{- $ingressName := $fullName -}} - {{- $values := .Values.ingress -}} - {{- if hasKey . "ObjectValues" -}} - {{- with .ObjectValues.ingress -}} - {{- $values = . -}} - {{- end -}} + {{- $rootCtx := .rootCtx -}} + {{- $objectData := .objectData -}} + + {{- $svcData := (include "tc.v1.common.lib.ingress.targetSelector" (dict "rootCtx" $rootCtx "objectData" $objectData) | fromYaml) -}} + + {{- if not (hasKey $objectData "integrations") -}} + {{- $_ := set $objectData "integrations" dict -}} {{- end -}} - {{- $ingressLabels := $values.labels -}} - {{- $ingressAnnotations := $values.annotations -}} - - {{- $ingressName = $values.name -}} - - {{/* Get the name of the primary service, if any */}} - {{- $primaryServiceName := (include "tc.v1.common.lib.util.service.primary" (dict "services" .Values.service "root" .)) -}} - {{/* Get service values of the primary service, if any */}} - {{- $primaryService := get .Values.service $primaryServiceName -}} - {{- $defaultServiceName := $fullName -}} - - {{- if and (hasKey $primaryService "nameOverride") $primaryService.nameOverride -}} - {{- $defaultServiceName = printf "%v-%v" $defaultServiceName $primaryService.nameOverride -}} - {{- end -}} - {{- $defaultServicePort := get $primaryService.ports (include "tc.v1.common.lib.util.service.ports.primary" (dict "svcValues" $primaryService "svcName" $primaryServiceName )) -}} - - {{- $mddwrNamespace := "tc-system" -}} - {{- if $.Values.operator.traefik -}} - {{- if $.Values.operator.traefik.namespace -}} - {{- $mddwrNamespace = $.Values.operator.traefik.namespace -}} - {{- end -}} + {{- if not (hasKey $objectData "annotations") -}} + {{- $_ := set $objectData "annotations" dict -}} {{- end -}} - {{- if $values.ingressClassName -}} - - {{- if $.Values.global.ixChartContext -}} - {{- $mddwrNamespace = (printf "ix-%s" $values.ingressClassName) -}} - {{- else -}} - {{- $mddwrNamespace = $values.ingressClassName -}} - {{- end -}} - {{- end -}} - - {{- $fixedMiddlewares := "" -}} - {{- if $values.enableFixedMiddlewares -}} - - {{/* If cors is enabled, replace the default fixedMiddleware with the opencors chain */}} - {{- if $values.allowCors -}} - {{- $corsMiddlewares := list "tc-opencors-chain" }} - {{- $_ := set $values "fixedMiddlewares" $corsMiddlewares -}} - {{- end -}} - - {{- range $index, $fixedMiddleware := $values.fixedMiddlewares -}} - {{- if $index -}} - {{- $fixedMiddlewares = ( printf "%v, %v-%v@%v" $fixedMiddlewares $mddwrNamespace $fixedMiddleware "kubernetescrd" ) -}} - {{- else -}} - {{- $fixedMiddlewares = ( printf "%v-%v@%v" $mddwrNamespace $fixedMiddleware "kubernetescrd" ) -}} - {{- end -}} - {{- end -}} - {{- end -}} - - {{- $middlewares := "" -}} - {{- range $index, $middleware := $values.middlewares -}} - {{- if $index -}} - {{- $middlewares = ( printf "%v, %v-%v@%v" $middlewares $mddwrNamespace $middleware "kubernetescrd" ) -}} - {{- else -}} - {{- $middlewares = ( printf "%v-%v@%v" $mddwrNamespace $middleware "kubernetescrd" ) -}} - {{- end -}} - {{ end }} - - {{- if and ( $fixedMiddlewares ) ( $middlewares ) -}} - {{- $middlewares = ( printf "%v, %v" $fixedMiddlewares $middlewares ) -}} - {{- else if $fixedMiddlewares -}} - {{- $middlewares = ( printf "%s" $fixedMiddlewares ) -}} - {{- end }} + {{- include "tc.v1.common.lib.ingress.integration.certManager" (dict "rootCtx" $rootCtx "objectData" $objectData) -}} + {{- include "tc.v1.common.lib.ingress.integration.traefik" (dict "rootCtx" $rootCtx "objectData" $objectData) -}} + {{- include "tc.v1.common.lib.ingress.integration.homepage" (dict "rootCtx" $rootCtx "objectData" $objectData) }} --- -apiVersion: {{ include "tc.v1.common.capabilities.ingress.apiVersion" $ }} +apiVersion: networking.k8s.io/v1 kind: Ingress metadata: - name: {{ $ingressName }} - namespace: {{ $.Values.namespace | default $.Values.global.namespace | default $.Release.Namespace }} - {{- $labels := (mustMerge ($ingressLabels | default dict) (include "tc.v1.common.lib.metadata.allLabels" $ | fromYaml)) -}} - {{- with (include "tc.v1.common.lib.metadata.render" (dict "rootCtx" $ "labels" $labels) | trim) }} + name: {{ $objectData.name }} + namespace: {{ include "tc.v1.common.lib.metadata.namespace" (dict "rootCtx" $rootCtx "objectData" $objectData "caller" "Ingress") }} + {{- $labels := (mustMerge ($objectData.labels | default dict) (include "tc.v1.common.lib.metadata.allLabels" $rootCtx | fromYaml)) -}} + {{- with (include "tc.v1.common.lib.metadata.render" (dict "rootCtx" $rootCtx "labels" $labels) | trim) }} labels: {{- . | nindent 4 }} {{- end -}} - {{- $annotations := (mustMerge ($ingressAnnotations | default dict) (include "tc.v1.common.lib.metadata.allAnnotations" $ | fromYaml) (include "tc.v1.common.lib.ingress.integration.homepage" (dict "objectData" $values "rootCtx" $) | fromYaml)) }} + {{- $annotations := (mustMerge ($objectData.annotations | default dict) (include "tc.v1.common.lib.metadata.allAnnotations" $rootCtx | fromYaml)) -}} + {{- with (include "tc.v1.common.lib.metadata.render" (dict "rootCtx" $rootCtx "annotations" $annotations) | trim) }} annotations: - {{- with $values.certificateIssuer }} - cert-manager.io/cluster-issuer: {{ tpl ( toYaml . ) $ }} - cert-manager.io/private-key-rotation-policy: Always - {{- end }} - "traefik.ingress.kubernetes.io/router.entrypoints": {{ $values.entrypoint | default "websecure" }} - "traefik.ingress.kubernetes.io/router.middlewares": {{ $middlewares | quote }} - {{- with (include "tc.v1.common.lib.metadata.render" (dict "rootCtx" $ "annotations" $annotations) | trim) }} {{- . | nindent 4 }} {{- end }} spec: - {{- if $values.ingressClassName }} - ingressClassName: {{ $values.ingressClassName }} - {{- end -}} - {{- if $values.certificateIssuer }} - tls: - {{- range $index, $hostsValues := $values.hosts }} - - hosts: - - {{ tpl $hostsValues.host $ | quote }} - secretName: {{ ( printf "%v-%v-%v" $ingressName "tls" $index ) }} - {{- end -}} - {{- else if $values.tls }} - tls: - {{- range $index, $tlsValues := $values.tls }} - {{- $tlsName := ( printf "%v-%v" "tls" $index ) }} - - hosts: - {{- range $tlsValues.hosts }} - - {{ tpl . $ | quote }} - {{- end -}} - {{- if $tlsValues.certificateIssuer }} - secretName: {{ printf "%v-%v" $ingressName $tlsName }} - {{- else if and ($tlsValues.scaleCert) ($.Values.global.ixChartContext) -}} - {{- $cert := dict }} - {{- $_ := set $cert "id" $tlsValues.scaleCert }} - {{- $_ := set $cert "nameOverride" $tlsName }} - secretName: {{ printf "%s-tls-%v" (include "tc.v1.common.lib.chart.names.fullname" $) $index }} - {{- else if .clusterCertificate }} - secretName: clusterissuer-templated-{{ tpl .clusterCertificate $ }} - {{- else if .secretName }} - secretName: {{ tpl .secretName $ | quote }} - {{- end -}} - {{- end -}} + {{- if $objectData.ingressClassName }} + ingressClassName: {{ tpl $objectData.ingressClassName $rootCtx }} {{- end }} rules: - {{- range $values.hosts }} - - host: {{ tpl .host $ | quote }} + {{- range $h := $objectData.hosts }} + - host: {{ tpl $h.host $rootCtx }} http: paths: - {{- range .paths -}} - {{- $service := $defaultServiceName -}} - {{- $port := $defaultServicePort.port -}} - {{- if .service -}} - {{- $service = default $service .service.name -}} - {{- $port = default $port .service.port -}} + {{- range $p := $h.paths -}} + {{- with $p.overrideService -}} + {{- $svcData = (dict "name" .name "port" .port) -}} {{- end }} - - path: {{ tpl .path $ | quote }} - pathType: {{ default "Prefix" .pathType }} + - path: {{ tpl $p.path $rootCtx }} + pathType: {{ tpl ($p.pathType | default "Prefix") $rootCtx }} backend: service: - name: {{ $service }} + name: {{ $svcData.name }} port: - number: {{ $port }} + number: {{ $svcData.port }} {{- end -}} + {{- end -}} + {{/* If a certificateIssuer is defined in the whole ingress, use that */}} + {{- if and $objectData.integrations.certManager $objectData.integrations.certManager.enabled }} + tls: + {{- range $idx, $h := $objectData.hosts }} + - secretName: {{ printf "%s-tls-%d" $objectData.name ($idx | int) }} + hosts: + - {{ tpl $h.host $rootCtx }} + {{- end -}} + {{/* else if a tls section is defined use the configuration from there */}} + {{- else if $objectData.tls }} + tls: + {{- range $idx, $t := $objectData.tls -}} + {{- $secretName := "" -}} + {{- if $t.secretName -}} + {{- $secretName = tpl $t.secretName $rootCtx -}} + {{- else if $t.scaleCert -}} + {{- $secretName = printf "%s-scale-tls-%d" $objectData.name ($idx | int) -}} + {{- else if $t.certificateIssuer -}} + {{- $secretName = printf "%s-tls-%d" $objectData.name ($idx | int) -}} + {{- else if $t.clusterCertificate -}} + {{/* TODO: Needs the refactor of Certificate object */}} + {{- end }} + - secretName: {{ $secretName }} + hosts: + {{- range $h := $t.hosts }} + - {{ tpl $h $rootCtx }} + {{- end -}} + {{- end -}} {{- end -}} - - {{- end -}} diff --git a/library/common/templates/class/_priorityClass.tpl b/library/common/templates/class/_priorityClass.tpl index 4789f9b8..3b4b8450 100644 --- a/library/common/templates/class/_priorityClass.tpl +++ b/library/common/templates/class/_priorityClass.tpl @@ -22,7 +22,7 @@ apiVersion: scheduling.k8s.io/v1 kind: PriorityClass metadata: name: {{ $objectData.name }} - namespace: {{ include "tc.v1.common.lib.metadata.namespace" (dict "rootCtx" $rootCtx "objectData" $objectData "caller" "priorityclass") }} + namespace: {{ include "tc.v1.common.lib.metadata.namespace" (dict "rootCtx" $rootCtx "objectData" $objectData "caller" "Priority Class") }} {{- $labels := (mustMerge ($objectData.labels | default dict) (include "tc.v1.common.lib.metadata.allLabels" $rootCtx | fromYaml)) -}} {{- with (include "tc.v1.common.lib.metadata.render" (dict "rootCtx" $rootCtx "labels" $labels) | trim) }} labels: diff --git a/library/common/templates/class/cnpg/_cluster.tpl b/library/common/templates/class/cnpg/_cluster.tpl index 7be41c44..a0bf8834 100644 --- a/library/common/templates/class/cnpg/_cluster.tpl +++ b/library/common/templates/class/cnpg/_cluster.tpl @@ -158,7 +158,7 @@ spec: {{- end -}} {{- with $preloadLibraries }} shared_preload_libraries: - {{- range $lib := (. | uniq) }} + {{- range $lib := (. | mustUniq) }} - {{ $lib | quote }} {{- end -}} {{- end -}} diff --git a/library/common/templates/lib/_tc_capabilities.tpl b/library/common/templates/lib/_tc_capabilities.tpl index e4187638..4d911dd8 100644 --- a/library/common/templates/lib/_tc_capabilities.tpl +++ b/library/common/templates/lib/_tc_capabilities.tpl @@ -13,11 +13,6 @@ {{- print "monitoring.coreos.com/v1" -}} {{- end -}} -{{/* Return the appropriate apiVersion for Ingress */}} -{{- define "tc.v1.common.capabilities.ingress.apiVersion" -}} - {{- print "networking.k8s.io/v1" -}} -{{- end -}} - {{/* Return the appropriate apiVersion for NetworkPolicy*/}} {{- define "tc.v1.common.capabilities.networkpolicy.apiVersion" -}} {{- print "networking.k8s.io/v1" -}} diff --git a/library/common/templates/lib/container/_securityContext.tpl b/library/common/templates/lib/container/_securityContext.tpl index 4736a2f5..3552a2f8 100644 --- a/library/common/templates/lib/container/_securityContext.tpl +++ b/library/common/templates/lib/container/_securityContext.tpl @@ -172,7 +172,7 @@ objectData: The object data to be used to render the container. {{- end -}} {{- end -}} - {{- if not (deepEqual (uniq $item) $item) -}} + {{- if not (deepEqual (mustUniq $item) $item) -}} {{- fail (printf "Container - Expected items of [securityContext.capabilities.%s] to be unique, but got [%s]" $key (join ", " $item)) -}} {{- end -}} {{- end -}} diff --git a/library/common/templates/lib/ingress/_targetSelector.tpl b/library/common/templates/lib/ingress/_targetSelector.tpl new file mode 100644 index 00000000..17115631 --- /dev/null +++ b/library/common/templates/lib/ingress/_targetSelector.tpl @@ -0,0 +1,85 @@ +{{/* Returns the selected service or fallback to primary */}} +{{- define "tc.v1.common.lib.ingress.targetSelector" -}} + {{- $rootCtx := .rootCtx -}} + {{- $objectData := .objectData -}} + + {{- $selectedService := (dict "name" "" "port" 0) -}} + {{- $svcData := dict -}} + {{- $portData := dict -}} + {{- $svcName := "" -}} + {{- $portName := "" -}} + + {{- if $objectData.targetSelector -}} + {{/* We have validation that only 1 key is allowed */}} + {{- $svcName = ($objectData.targetSelector | keys | mustFirst) -}} + {{- $portName = (get $objectData.targetSelector $svcName) -}} + {{- $svcData = (get $rootCtx.Values.service $svcName) -}} + + {{- if not $svcData -}} + {{- fail (printf "Ingress - Expected targeted service [%s] to exist" $svcName) -}} + {{- end -}} + + {{- $enabled := (include "tc.v1.common.lib.util.enabled" (dict + "rootCtx" $rootCtx "objectData" $svcData + "name" $svcName "caller" "Ingress" + "key" "ingress")) -}} + + {{- if ne $enabled "true" -}} + {{- fail (printf "Ingress - Expected targeted service [%s] to be enabled" $svcName) -}} + {{- end -}} + + {{- else -}} + {{/* Find the primary service */}} + {{- range $name, $service := $rootCtx.Values.service -}} + + {{- $enabled := (include "tc.v1.common.lib.util.enabled" (dict + "rootCtx" $rootCtx "objectData" $service + "name" $name "caller" "Ingress" + "key" "ingress")) -}} + + {{/* Check if its enabled */}} + {{- if eq $enabled "true" -}} + + {{- if $service.primary -}} + {{- $svcName = $name -}} + {{- $svcData = $service -}} + + {{/* Find the primary port */}} + {{- range $name, $port := $svcData.ports -}} + {{- if $port.primary -}} + {{- $portName = $name -}} + {{- end -}} + {{- end -}} + {{- end -}} + {{- end -}} + {{- end -}} + + {{- if not $svcData -}} + {{- fail "Ingress - Expected [targetSelector] or a primary service to exist" -}} + {{- end -}} + + {{- end -}} + + {{- $portData = (get $svcData.ports $portName) -}} + {{- if not $portData -}} + {{- fail (printf "Ingress - Expected targeted service [%s] to have port [%s]" $svcName $portName) -}} + {{- end -}} + + {{- $enabled := (include "tc.v1.common.lib.util.enabled" (dict + "rootCtx" $rootCtx "objectData" $portData + "name" $portName "caller" "Ingress" + "key" "ingress")) -}} + + {{- if ne $enabled "true" -}} + {{- fail (printf "Ingress - Expected targeted service port [%s] to be enabled" $portName) -}} + {{- end -}} + + {{- $expandedSvcName := include "tc.v1.common.lib.chart.names.fullname" $rootCtx -}} + {{- if not $svcData.primary -}} + {{- $expandedSvcName = printf "%s-%s" $expandedSvcName $svcName -}} + {{- end -}} + + {{- $selectedService = (dict "name" $expandedSvcName "port" (tpl ($portData.port | toString) $rootCtx)) -}} + + {{- $selectedService | toYaml -}} +{{- end -}} diff --git a/library/common/templates/lib/ingress/_validation.tpl b/library/common/templates/lib/ingress/_validation.tpl index a63bc583..216ef5e9 100644 --- a/library/common/templates/lib/ingress/_validation.tpl +++ b/library/common/templates/lib/ingress/_validation.tpl @@ -1,14 +1,182 @@ {{/* Ingress Validation */}} {{/* Call this template: -{{ include "tc.v1.common.lib.ingress.validation" (dict "objectData" $objectData) -}} +{{ include "tc.v1.common.lib.ingress.validation" (dict "rootCtx" $ "objectData" $objectData) -}} objectData: rootCtx: The root context of the chart. - objectData: The ingress object. + objectData: The Ingress object. */}} {{- define "tc.v1.common.lib.ingress.validation" -}} {{- $rootCtx := .rootCtx -}} {{- $objectData := .objectData -}} + {{- if $objectData.targetSelector -}} + {{- if not (kindIs "map" $objectData.targetSelector) -}} + {{- fail (printf "Ingress - Expected [targetSelector] to be a [map], but got [%s]" (kindOf $objectData.targetSelector)) -}} + {{- end -}} + + {{- $selectors := $objectData.targetSelector | keys | len -}} + {{- if (gt $selectors 1) -}} + {{ fail (printf "Ingress - Expected [targetSelector] to have exactly one key, but got [%d]" $selectors) -}} + {{- end -}} + + {{- range $k, $v := $objectData.targetSelector -}} + {{- if not $v -}} + {{- fail (printf "Ingress - Expected [targetSelector.%s] to have a value" $k) -}} + {{- end -}} + + {{- if not (kindIs "string" $v) -}} + {{- fail (printf "Ingress - Expected [targetSelector.%s] to be a [string], but got [%s]" $k (kindOf $v)) -}} + {{- end -}} + {{- end -}} + {{- end -}} + + {{- if not $objectData.hosts -}} + {{- fail "Ingress - Expected non-empty [hosts]" -}} + {{- end -}} + + {{- if not (kindIs "slice" $objectData.hosts) -}} + {{- fail (printf "Ingress - Expected [hosts] to be a [slice], but got [%s]" (kindOf $objectData.hosts)) -}} + {{- end -}} + + {{- range $h := $objectData.hosts -}} + {{- if not $h.host -}} + {{- fail "Ingress - Expected non-empty [hosts.host]" -}} + {{- end -}} + + {{- $host := tpl $h.host $rootCtx -}} + {{- if (hasPrefix "http://" $host) -}} + {{- fail (printf "Ingress - Expected [hosts.host] to not start with [http://], but got [%s]" $host) -}} + {{- end -}} + {{- if (hasPrefix "https://" $host) -}} + {{- fail (printf "Ingress - Expected [hosts.host] to not start with [https://], but got [%s]" $host) -}} + {{- end -}} + {{- if (contains ":" $host) -}} + {{- fail (printf "Ingress - Expected [hosts.host] to not contain [:], but got [%s]" $host) -}} + {{- end -}} + + {{- if not $h.paths -}} + {{- fail "Ingress - Expected non-empty [hosts.paths]" -}} + {{- end -}} + + {{- if not (kindIs "slice" $h.paths) -}} + {{- fail (printf "Ingress - Expected [hosts.paths] to be a [slice], but got [%s]" (kindOf $h.paths)) -}} + {{- end -}} + + {{- range $p := $h.paths -}} + {{- $pathType := "Prefix" -}} + {{- if $p.pathType -}} + {{- $pathType = tpl $p.pathType $rootCtx -}} + {{- end -}} + + {{- $validPathTypes := (list "Prefix" "Exact" "ImplementationSpecific") -}} + {{- if not (mustHas $pathType $validPathTypes) -}} + {{- fail (printf "Ingress - Expected [hosts.paths.pathType] to be one of [%s], but got [%s]" (join ", " $validPathTypes) $pathType) -}} + {{- end -}} + + {{- $path := tpl $p.path $rootCtx -}} + {{- $prefixSlashTypes := (list "Prefix" "Exact") -}} + {{- if (mustHas $pathType $prefixSlashTypes) -}} + {{- if not (hasPrefix "/" $path) -}} + {{- fail (printf "Ingress - Expected [hosts.paths.path] to start with [/], but got [%s]" $path) -}} + {{- end -}} + {{- end -}} + + {{/* If at least one thing in overrideService is defined... */}} + {{- with $p.overrideService -}} + {{- if not .name -}} + {{- fail "Ingress - Expected non-empty [hosts.paths.overrideService.name]" -}} + {{- end -}} + {{- if not .port -}} + {{- fail "Ingress - Expected non-empty [hosts.paths.overrideService.port]" -}} + {{- end -}} + {{- end -}} + + {{- end -}} + {{- end -}} + + {{- range $t := $objectData.tls -}} + {{- if not $t.hosts -}} + {{- fail "Ingress - Expected non-empty [tls.hosts]" -}} + {{- end -}} + + {{- if not (kindIs "slice" $t.hosts) -}} + {{- fail (printf "Ingress - Expected [tls.hosts] to be a [slice], but got [%s]" (kindOf $t.hosts)) -}} + {{- end -}} + + {{- range $h := $t.hosts -}} + {{- if not $h -}} + {{- fail "Ingress - Expected non-empty entry in [tls.hosts]" -}} + {{- end -}} + + {{- $host := tpl $h $rootCtx -}} + {{- if (hasPrefix "http://" $host) -}} + {{- fail (printf "Ingress - Expected entry in [tls.hosts] to not start with [http://], but got [%s]" $host) -}} + {{- end -}} + {{- if (hasPrefix "https://" $host) -}} + {{- fail (printf "Ingress - Expected entry in [tls.hosts] to not start with [https://], but got [%s]" $host) -}} + {{- end -}} + {{- if (contains ":" $host) -}} + {{- fail (printf "Ingress - Expected entry in [tls.hosts] to not contain [:], but got [%s]" $host) -}} + {{- end -}} + {{- end -}} + + {{/* TODO: Add the rest of the options?! */}} + {{- $certOptions := (list "scaleCert" "secretName") -}} + {{- $optsSet := list -}} + {{- range $opt := $certOptions -}} + {{- if (get $t $opt) -}} + {{- $optsSet = mustAppend $optsSet $opt -}} + {{- end -}} + {{- end -}} + + {{- if gt ($optsSet | len) 1 -}} + {{- fail (printf "Ingress - Expected only one of [%s] to be set, but got [%s]" (join ", " $certOptions) (join ", " $optsSet)) -}} + {{- end -}} + + {{- end -}} + +{{- end -}} + +{{/* Ingress Primary Validation */}} +{{/* Call this template: +{{ include "tc.v1.common.lib.ingress.primaryValidation" $ -}} +*/}} + +{{- define "tc.v1.common.lib.ingress.primaryValidation" -}} + + {{/* Initialize values */}} + {{- $hasPrimary := false -}} + {{- $hasEnabled := false -}} + + {{- range $name, $ingress := $.Values.ingress -}} + + {{- $enabled := (include "tc.v1.common.lib.util.enabled" (dict + "rootCtx" $ "objectData" $ingress + "name" $name "caller" "Ingress" + "key" "ingress")) -}} + + {{/* If ingress is enabled */}} + {{- if eq $enabled "true" -}} + {{- $hasEnabled = true -}} + + {{/* And ingress is primary */}} + {{- if and (hasKey $ingress "primary") ($ingress.primary) -}} + {{/* Fail if there is already a primary ingress */}} + {{- if $hasPrimary -}} + {{- fail "Ingress - Only one ingress can be primary" -}} + {{- end -}} + + {{- $hasPrimary = true -}} + + {{- end -}} + + {{- end -}} + {{- end -}} + + {{/* Require at least one primary ingress, if any enabled */}} + {{- if and $hasEnabled (not $hasPrimary) -}} + {{- fail "Ingress - At least one enabled ingress must be primary" -}} + {{- end -}} {{- end -}} diff --git a/library/common/templates/lib/ingress/integrations/_certManager.tpl b/library/common/templates/lib/ingress/integrations/_certManager.tpl new file mode 100644 index 00000000..2df0cdbf --- /dev/null +++ b/library/common/templates/lib/ingress/integrations/_certManager.tpl @@ -0,0 +1,29 @@ +{{- define "tc.v1.common.lib.ingress.integration.certManager" -}} + {{- $objectData := .objectData -}} + {{- $rootCtx := .rootCtx -}} + + {{- $certManager := $objectData.integrations.certManager -}} + + {{- if $certManager.enabled -}} + {{- include "tc.v1.common.lib.ingress.integration.certManager.validate" (dict "objectData" $objectData) -}} + + {{- $_ := set $objectData.annotations "cert-manager.io/cluster-issuer" $certManager.certificateIssuer -}} + {{- $_ := set $objectData.annotations "cert-manager.io/private-key-rotation-policy" "Always" -}} + + {{- end -}} +{{- end -}} + +{{- define "tc.v1.common.lib.ingress.integration.certManager.validate" -}} + {{- $objectData := .objectData -}} + + {{- $certManager := $objectData.integrations.certManager -}} + + {{- if not $certManager.certificateIssuer -}} + {{- fail "Ingress - Expected a non-empty [integrations.certManager.certificateIssuer]" -}} + {{- end -}} + + {{- if not (kindIs "string" $certManager.certificateIssuer) -}} + {{- fail (printf "Ingress - Expected [integrations.certManager.certificateIssuer] to be a [string], but got [%s]" (kindOf $certManager.certificateIssuer)) -}} + {{- end -}} + +{{- end -}} diff --git a/library/common/templates/lib/ingress/integrations/_homepage.tpl b/library/common/templates/lib/ingress/integrations/_homepage.tpl index 73692bb5..94863efb 100644 --- a/library/common/templates/lib/ingress/integrations/_homepage.tpl +++ b/library/common/templates/lib/ingress/integrations/_homepage.tpl @@ -1,35 +1,75 @@ -{{/* Ingress Homepage Integration */}} -{{/* Call this template: -{{ include "tc.v1.common.lib.ingress.integration.homepage" (dict "rootCtx" $rootCtx "objectData" $objectData) -}} -objectData: - rootCtx: The root context of the chart. - objectData: The ingress object. -*/}} - {{- define "tc.v1.common.lib.ingress.integration.homepage" -}} + {{- $objectData := .objectData -}} {{- $rootCtx := .rootCtx -}} + + {{- $homepage := $objectData.integrations.homepage -}} + {{- if and $homepage $homepage.enabled -}} + {{- if not (hasKey $homepage "widget") -}} + {{- $_ := set $objectData.integrations.homepage "widget" dict -}} + {{- end -}} + + {{- include "tc.v1.common.lib.ingress.integration.homepage.validation" (dict "objectData" $objectData) -}} + + {{- $name := $homepage.name | default ($rootCtx.Chart.Name | camelcase) -}} + {{- $desc := $homepage.description | default $rootCtx.Chart.Description -}} + {{- $icon := $homepage.icon | default $rootCtx.Chart.Icon -}} + {{- $type := $homepage.widget.type | default $rootCtx.Chart.Name -}} + {{- $url := $homepage.widget.url -}} + + {{- if not $url -}} + {{- $fHost := $objectData.hosts | mustFirst -}} + {{- $fPath := $fHost.paths | mustFirst -}} + {{- $host := tpl $fHost.host $rootCtx -}} + {{- $path := tpl $fPath.path $rootCtx -}} + + {{- $url = printf "https://%s/%s" $host ($path | trimPrefix "/") -}} + {{- end -}} + + {{- $_ := set $objectData.annotations "gethomepage.dev/enabled" "true" -}} + {{- $_ := set $objectData.annotations "gethomepage.dev/name" (tpl $name $rootCtx) -}} + {{- $_ := set $objectData.annotations "gethomepage.dev/description" (tpl $desc $rootCtx) -}} + {{- $_ := set $objectData.annotations "gethomepage.dev/icon" (tpl $icon $rootCtx) -}} + {{- $_ := set $objectData.annotations "gethomepage.dev/widget.type" (tpl $type $rootCtx) -}} + {{- with $homepage.group -}} + {{- $_ := set $objectData.annotations "gethomepage.dev/group" (tpl . $rootCtx) -}} + {{- end -}} + + {{- with $homepage.weight -}} + {{- $_ := set $objectData.annotations "gethomepage.dev/weight" (. | toString) -}} + {{- end -}} + + {{- with $url -}} + {{- $_ := set $objectData.annotations "gethomepage.dev/widget.url" (tpl $url $rootCtx) -}} + {{- end -}} + + {{- if $homepage.widget.custom -}} + {{- range $k, $v := $homepage.widget.custom -}} + {{- $_ := set $objectData.annotations (printf "gethomepage.dev/widget.%s" $k) (tpl $v $rootCtx | toString) -}} + {{- end -}} + {{- end -}} + + {{- with $homepage.podSelector -}} + {{- $selector := (printf "pod.name in (%s)" (join "," .)) -}} + {{- $_ := set $objectData.annotations "gethomepage.dev/pod-selector" $selector -}} + {{- end -}} + + {{- end -}} +{{- end -}} + +{{- define "tc.v1.common.lib.ingress.integration.homepage.validation" -}} {{- $objectData := .objectData -}} -{{- if and $objectData.integration $objectData.integration.homepage $objectData.integration.homepage.enabled -}} -gethomepage.dev/enabled: "true" -gethomepage.dev/name: {{ $objectData.integration.homepage.name | default ( camelcase $rootCtx.Chart.Name ) }} -gethomepage.dev/description: {{ $objectData.integration.homepage.description | default $rootCtx.Chart.Description }} -gethomepage.dev/group: {{ $objectData.integration.homepage.group | default "default" }} -gethomepage.dev/icon: {{ $objectData.integration.homepage.icon | default $rootCtx.Chart.Icon }} -{{- if $objectData.integration.homepage.podSelector -}} -gethomepage.dev/pod-selector: {{ . }} -{{- else -}} -gethomepage.dev/pod-selector: "" -{{- end -}} -{{- with $objectData.integration.homepage.weight -}} -gethomepage.dev/weight: {{ . }} -{{- end -}} -gethomepage.dev/widget.type: {{ $objectData.integration.homepage.widget.type | default $rootCtx.Chart.Name }} -{{- with (index $objectData.hosts 0) -}} -gethomepage.dev/widget.url: {{ $objectData.integration.homepage.widget.url | default (printf "%v%v" .host ( .path | default "/")) }} -{{- end -}} -{{- range $objectData.integration.homepage.widget.custom -}} -gethomepage.dev/widget.{{ .name }}: {{ .value }} -{{- end -}} -{{- end -}} + {{- $homepage := $objectData.integrations.homepage -}} + + {{- with $homepage.podSelector -}} + {{- if not (kindIs "slice" .) -}} + {{- fail (printf "Ingress - Expected [integrations.homepage.podSelector] to be a [slice], but got [%s]" (kindOf .)) -}} + {{- end -}} + {{- end -}} + + {{- if $homepage.widget.custom -}} + {{- if not (kindIs "map" $homepage.widget.custom) -}} + {{- fail (printf "Ingress - Expected [integrations.homepage.widget.custom] to be a [map], but got [%s]" (kindOf $homepage.widget.custom)) -}} + {{- end -}} + {{- end -}} {{- end -}} diff --git a/library/common/templates/lib/ingress/integrations/_traefik.tpl b/library/common/templates/lib/ingress/integrations/_traefik.tpl new file mode 100644 index 00000000..4dcef8dd --- /dev/null +++ b/library/common/templates/lib/ingress/integrations/_traefik.tpl @@ -0,0 +1,115 @@ +{{- define "tc.v1.common.lib.ingress.integration.traefik" -}} + {{- $objectData := .objectData -}} + {{- $rootCtx := .rootCtx -}} + + {{- $traefik := $objectData.integrations.traefik -}} + + {{- $enabled := true -}} + {{- if and $traefik (hasKey $traefik "enabled") (kindIs "bool" $traefik.enabled) -}} + {{- $enabled = $traefik.enabled -}} + {{- end -}} + + {{- if $enabled -}} + {{- include "tc.v1.common.lib.ingress.integration.traefik.validate" (dict "objectData" $objectData) -}} + + {{- $fixedMiddlewares := list -}} + {{- $enableFixed := false -}} + {{- if (hasKey $rootCtx.Values.global "traefik") -}} + {{- $fixedMiddlewares = $rootCtx.Values.global.traefik.fixedMiddlewares -}} + {{- $enableFixed = $rootCtx.Values.global.traefik.enableFixedMiddlewares -}} + {{- end -}} + + {{/* Override global (enable)fixedMiddlewares with local */}} + {{- if $traefik.fixedMiddlewares -}} + {{- $fixedMiddlewares = $traefik.fixedMiddlewares -}} + {{- end -}} + + {{/* Replace global fixed with local fixed */}} + {{- if and (hasKey $traefik "enableFixedMiddlewares") (kindIs "bool" $traefik.enableFixedMiddlewares) -}} + {{- $enableFixed = $traefik.enableFixedMiddlewares -}} + {{- end -}} + + {{/* Replace global and local fixed middlewares with the opencors-chain */}} + {{- if $traefik.allowCors -}} + {{- $fixedMiddlewares = list "tc-opencors-chain" -}} + {{- end -}} + + {{- $entrypoints := $traefik.entrypoints | default (list "websecure") -}} + {{- $middlewares := list -}} + + {{/* Add the fixedMiddlewares */}} + {{- if and $enableFixed $fixedMiddlewares -}} + {{- $middlewares = concat $middlewares $fixedMiddlewares -}} + {{- end -}} + + {{/* Add the user middlewares */}} + {{- if $traefik.middlewares -}} + {{- $middlewares = concat $middlewares $traefik.middlewares -}} + {{- end -}} + + {{/* Make sure we dont have dupes */}} + {{- if $middlewares -}} + {{- if not (deepEqual (mustUniq $middlewares) $middlewares) -}} + {{- fail (printf "Ingress - Combined traefik middlewares contain duplicates [%s]" (join ", " $middlewares)) -}} + {{- end -}} + {{- end -}} + + {{- if not (deepEqual (mustUniq $entrypoints) $entrypoints) -}} + {{- fail (printf "Ingress - Combined traefik entrypoints contain duplicates [%s]" (join ", " $entrypoints)) -}} + {{- end -}} + + {{- $midNamespace := "tc-system" -}} + {{/* If our hook has set operator.traefik.namespace, use that */}} + {{- if (hasKey $rootCtx.Values.operator "traefik") -}} + {{- if $rootCtx.Values.operator.traefik.namespace -}} + {{- $midNamespace = $rootCtx.Values.operator.traefik.namespace -}} + {{- end -}} + {{- end -}} + + {{- if $traefik.ingressClassName -}} + {{- $midNamespace = tpl $traefik.ingressClassName $rootCtx -}} + + {{/* On SCALE prepend with ix- */}} + {{- if $rootCtx.Values.global.ixChartContext -}} + {{- $midNamespace = (printf "ix-%s" $midNamespace) -}} + {{- end -}} + {{- end -}} + + {{/* Format middlewares */}} + {{- $formMiddlewares := list -}} + {{- range $mid := $middlewares -}} + {{- $formMiddlewares = mustAppend $formMiddlewares (printf "%s-%s@kubernetescrd" $mid $midNamespace) -}} + {{- end -}} + + {{- $_ := set $objectData.annotations "traefik.ingress.kubernetes.io/router.entrypoints" (join "," $entrypoints) -}} + {{- if $formMiddlewares -}} + {{- $_ := set $objectData.annotations "traefik.ingress.kubernetes.io/router.middlewares" (join "," $formMiddlewares) -}} + {{- end -}} + + {{- end -}} +{{- end -}} + +{{- define "tc.v1.common.lib.ingress.integration.traefik.validate" -}} + {{- $objectData := .objectData -}} + + {{- $traefik := $objectData.integrations.traefik -}} + + {{- if $traefik.entrypoints -}} + {{- if not (kindIs "slice" $traefik.entrypoints) -}} + {{- fail (printf "Ingress - Expected [integrations.traefik.entrypoints] to be a [slice], but got [%s]" (kindOf $traefik.entrypoints)) -}} + {{- end -}} + {{- end -}} + + {{- if $traefik.middlewares -}} + {{- if not (kindIs "slice" $traefik.middlewares) -}} + {{- fail (printf "Ingress - Expected [integrations.traefik.middlewares] to be a [slice], but got [%s]" (kindOf $traefik.middlewares)) -}} + {{- end -}} + {{- end -}} + + {{- if $traefik.fixedMiddlewares -}} + {{- if not (kindIs "slice" $traefik.fixedMiddlewares) -}} + {{- fail (printf "Ingress - Expected [integrations.traefik.fixedMiddlewares] to be a [slice], but got [%s]" (kindOf $traefik.fixedMiddlewares)) -}} + {{- end -}} + {{- end -}} + +{{- end -}} diff --git a/library/common/templates/lib/service/_additionalAnnotations.tpl b/library/common/templates/lib/service/_additionalAnnotations.tpl index ab4e8dcc..e314083c 100644 --- a/library/common/templates/lib/service/_additionalAnnotations.tpl +++ b/library/common/templates/lib/service/_additionalAnnotations.tpl @@ -18,8 +18,10 @@ annotations: The annotations variable reference, to append the MetalLB annotatio {{- $sharedKey = tpl . $rootCtx -}} {{- end -}} - {{- if $rootCtx.Values.global.addMetalLBAnnotations -}} - {{- $_ := set $annotations "metallb.universe.tf/allow-shared-ip" $sharedKey -}} + {{- if (hasKey $rootCtx.Values.global "metallb") -}} + {{- if $rootCtx.Values.global.metallb.addServiceAnnotations -}} + {{- $_ := set $annotations "metallb.universe.tf/allow-shared-ip" $sharedKey -}} + {{- end -}} {{- end -}} {{- end -}} @@ -34,7 +36,9 @@ annotations: The annotations variable reference, to append the Traefik annotatio {{- $rootCtx := .rootCtx -}} {{- $annotations := .annotations -}} - {{- if $rootCtx.Values.global.addTraefikAnnotations -}} - {{- $_ := set $annotations "traefik.ingress.kubernetes.io/service.serversscheme" "https" -}} + {{- if (hasKey $rootCtx.Values.global "traefik") -}} + {{- if $rootCtx.Values.global.traefik.addServiceAnnotations -}} + {{- $_ := set $annotations "traefik.ingress.kubernetes.io/service.serversscheme" "https" -}} + {{- end -}} {{- end -}} {{- end -}} diff --git a/library/common/templates/lib/util/_primary_service.tpl b/library/common/templates/lib/util/_primary_service.tpl index b661a250..75f57631 100644 --- a/library/common/templates/lib/util/_primary_service.tpl +++ b/library/common/templates/lib/util/_primary_service.tpl @@ -32,8 +32,8 @@ {{- end -}} {{- if $result -}} - {{- $result -}} + {{- $result -}} {{- else -}} - {{- fail "No primary and enabled service found" -}} + {{- fail "No primary and enabled service found" -}} {{- end -}} {{- end -}} diff --git a/library/common/templates/loader/_init.tpl b/library/common/templates/loader/_init.tpl index aff423a4..13111e53 100644 --- a/library/common/templates/loader/_init.tpl +++ b/library/common/templates/loader/_init.tpl @@ -8,7 +8,7 @@ {{- include "tc.v1.common.loader.lists" . -}} {{/* Ensure TrueCharts chart context information is available */}} - {{- include "tc.v1.common.lib.util.chartcontext" . -}} + {{- /* include "tc.v1.common.lib.util.chartcontext" . */ -}} {{/* Autogenerate postgresql passwords if needed */}} {{- include "tc.v1.common.spawner.cnpg" . }} diff --git a/library/common/templates/spawner/_ingress.tpl b/library/common/templates/spawner/_ingress.tpl index 07353144..cadf12ca 100644 --- a/library/common/templates/spawner/_ingress.tpl +++ b/library/common/templates/spawner/_ingress.tpl @@ -1,62 +1,95 @@ -{{/* Renders the Ingress objects required by the chart */}} +{{/* Ingress Spawwner */}} +{{/* Call this template: +{{ include "tc.v1.common.spawner.ingress" $ -}} +*/}} + {{- define "tc.v1.common.spawner.ingress" -}} {{- $fullname := include "tc.v1.common.lib.chart.names.fullname" $ -}} - {{/* Generate named ingresses as required */}} + {{/* Validate that only 1 primary exists */}} + {{- include "tc.v1.common.lib.ingress.primaryValidation" $ -}} + {{- range $name, $ingress := .Values.ingress -}} - {{- if $ingress.enabled -}} - {{- $ingressValues := $ingress -}} - {{- $ingressName := $fullname -}} - {{/* set defaults */}} - {{- if and (not $ingressValues.nameOverride) (ne $name (include "tc.v1.common.lib.util.ingress.primary" $)) -}} - {{- $_ := set $ingressValues "nameOverride" $name -}} + {{- $enabled := (include "tc.v1.common.lib.util.enabled" (dict + "rootCtx" $ "objectData" $ingress + "name" $name "caller" "Ingress" + "key" "ingress")) -}} + + {{- if and (eq $enabled "false") ($ingress.required) -}} + {{- fail (printf "Ingress - Expected ingress [%s] to be enabled. This chart is designed to work only with ingress enabled." $name) -}} + {{- end -}} + + {{- if eq $enabled "true" -}} + + {{/* Create a copy of the ingress */}} + {{- $objectData := (mustDeepCopy $ingress) -}} + + {{/* Init object name */}} + {{- $objectName := $name -}} + + {{- $expandName := (include "tc.v1.common.lib.util.expandName" (dict + "rootCtx" $ "objectData" $objectData + "name" $name "caller" "Ingress" + "key" "ingress")) -}} + + {{- if eq $expandName "true" -}} + {{/* Expand the name of the service if expandName resolves to true */}} + {{- $objectName = $fullname -}} {{- end -}} - {{- if $ingressValues.nameOverride -}} - {{- $ingressName = printf "%v-%v" $ingressName $ingressValues.nameOverride -}} + {{- if and (eq $expandName "true") (not $objectData.primary) -}} + {{/* If the ingress is not primary append its name to fullname */}} + {{- $objectName = (printf "%s-%s" $fullname $name) -}} {{- end -}} - {{- $_ := set $ingressValues "name" $ingressName -}} + {{/* Perform validations */}} + {{- include "tc.v1.common.lib.chart.names.validation" (dict "name" $objectName "length" 253) -}} + {{- include "tc.v1.common.lib.metadata.validation" (dict "objectData" $objectData "caller" "Ingress") -}} + {{- include "tc.v1.common.lib.ingress.validation" (dict "rootCtx" $ "objectData" $objectData) -}} - {{- $_ := set $ "ObjectValues" (dict "ingress" $ingressValues) -}} - {{- include "tc.v1.common.class.ingress" $ -}} - {{- if and ( $ingressValues.tls ) ( not $ingressValues.clusterIssuer ) -}} - {{- range $index, $tlsValues := $ingressValues.tls -}} - {{- $tlsName := ( printf "%v-%v" "tls" $index ) -}} - {{- if $tlsValues.certificateIssuer -}} - {{- include "tc.v1.common.class.certificate" (dict "root" $ "name" ( printf "%v-%v" $ingressName $tlsName ) "certificateIssuer" $tlsValues.certificateIssuer "hosts" $tlsValues.hosts ) -}} - {{- else if and ( $tlsValues.scaleCert ) ( $.Values.global.ixChartContext ) -}} + {{/* Set the name of the ingress */}} + {{- $_ := set $objectData "name" $objectName -}} + {{- $_ := set $objectData "shortName" $name -}} - {{/* Create certificate object and use it to construct a secret */}} - {{- $objectData := dict -}} - {{- $_ := set $objectData "id" .scaleCert -}} - - {{- $objectName := (printf "%s-%s" $fullname $tlsName) -}} - {{/* Perform validations */}} - {{- include "tc.v1.common.lib.chart.names.validation" (dict "name" $objectName) -}} - {{- include "tc.v1.common.lib.scaleCertificate.validation" (dict "objectData" $objectData) -}} - {{- include "tc.v1.common.lib.metadata.validation" (dict "objectData" $objectData "caller" "Certificate") -}} - - {{/* Prepare data */}} - {{- $data := fromJson (include "tc.v1.common.lib.scaleCertificate.getData" (dict "rootCtx" $ "objectData" $objectData)) -}} - {{- $_ := set $objectData "data" $data -}} - - {{/* Set the type to certificate */}} - {{- $_ := set $objectData "type" "certificate" -}} - - {{/* Set the name of the certificate */}} - {{- $_ := set $objectData "name" $objectName -}} - {{- $_ := set $objectData "shortName" $name -}} - - {{/* Call class to create the object */}} - {{- include "tc.v1.common.class.secret" (dict "rootCtx" $ "objectData" $objectData) -}} + {{/* Call class to create the object */}} + {{- include "tc.v1.common.class.ingress" (dict "rootCtx" $ "objectData" $objectData) -}} + {{/* TODO: range over TLS and do stuff */}} + {{- $hasCertIssuer := false -}} + {{- if $objectData.integrations -}} + {{- if and $objectData.integrations.certManager $objectData.integrations.certManager.enabled -}} + {{- $hasCertIssuer = true -}} {{- end -}} {{- end -}} + + {{- if not $hasCertIssuer -}} + {{- range $idx, $tlsData := $objectData.tls -}} + {{- if $tlsData.scaleCert -}} + {{- if not $.Values.global.ixChartContext -}} + {{- fail "Ingress - [tls.scalecert] can only be used in TrueNAS SCALE" -}} + {{- end -}} + + {{- $certData := (include "tc.v1.common.lib.scaleCertificate.getData" (dict "rootCtx" $ "objectData" (dict "id" $tlsData.scaleCert)) | fromJson) -}} + {{- $certName := printf "%s-scale-tls-%d" $objectData.name ($idx | int) -}} + + {{- $certObjData := (dict + "id" $tlsData.scaleCert "type" "certificate" + "name" $certName "shortName" $name + "data" $certData + ) -}} + + {{- include "tc.v1.common.lib.chart.names.validation" (dict "name" $certName) -}} + {{- include "tc.v1.common.lib.scaleCertificate.validation" (dict "objectData" $certObjData) -}} + {{- include "tc.v1.common.lib.metadata.validation" (dict "objectData" $certObjData "caller" "Ingress") -}} + + {{/* Create the secret with the certData */}} + {{- include "tc.v1.common.class.secret" (dict "rootCtx" $ "objectData" $certObjData) -}} + {{- else if $tlsData.clusterCertificate -}} + {{/* TODO: Needs the refactor of Certificate object */}} + {{- end -}} + {{- end -}} {{- end -}} - {{- else if $ingress.required -}} - {{- fail (printf "Ingress - [ingress.%s] is set to be [required] and cannot be disabled" $name) -}} {{- end -}} {{- end -}} {{- end -}} diff --git a/library/common/templates/spawner/_priorityClass.tpl b/library/common/templates/spawner/_priorityClass.tpl index a7391d5d..07ecb992 100644 --- a/library/common/templates/spawner/_priorityClass.tpl +++ b/library/common/templates/spawner/_priorityClass.tpl @@ -1,4 +1,4 @@ -{{/* priorityclass Spawwner */}} +{{/* Priority Class Spawner */}} {{/* Call this template: {{ include "tc.v1.common.spawner.priorityclass" $ -}} */}} diff --git a/library/common/templates/spawner/_service.tpl b/library/common/templates/spawner/_service.tpl index 86d40841..57081af9 100644 --- a/library/common/templates/spawner/_service.tpl +++ b/library/common/templates/spawner/_service.tpl @@ -43,7 +43,7 @@ {{- include "tc.v1.common.lib.metadata.validation" (dict "objectData" $objectData "caller" "Service") -}} {{- include "tc.v1.common.lib.service.validation" (dict "rootCtx" $ "objectData" $objectData) -}} - {{/* Set the name of the service account */}} + {{/* Set the name of the service */}} {{- $_ := set $objectData "name" $objectName -}} {{- $_ := set $objectData "shortName" $name -}} diff --git a/library/common/values.yaml b/library/common/values.yaml index 0123a962..ffa44111 100644 --- a/library/common/values.yaml +++ b/library/common/values.yaml @@ -7,10 +7,19 @@ global: # -- Set a global namespace # TODO: Currently some objects do not support this namespace: "" - # -- Adds metalLB annotations to services - addMetalLBAnnotations: true - # -- Adds traefik annotations to services - addTraefikAnnotations: true + metallb: + # -- Adds metalLB annotations to services + addServiceAnnotations: true + traefik: + # -- Adds traefik annotations to services (when needed) + addServiceAnnotations: true + # Enables or disables the fixed middlewares on all ingresses + # Can be overruled per ingress + enableFixedMiddlewares: true + # Applies middleware to all ingresses + # Can be overruled per ingress + fixedMiddlewares: + - chain-basic # -- Minimum nodePort value minNodePort: 9000 # -- Enable to stop most pods and containers including cnpg @@ -515,7 +524,6 @@ wireguardImage: # -- Specify the WireGuard image pull policy pullPolicy: IfNotPresent - # -- Configure the ingresses for the chart here. # Additional ingresses can be added by adding a dictionary key similar to the 'main' ingress. # @default -- See below @@ -523,104 +531,81 @@ ingress: main: # -- Enables or disables the ingress enabled: false - - # -- Adds integrations to ingress -# integration: -# homepage: -# enabled: true -# # Default: chart name -# name: somename -# # Default: chart description -# description: some description -# group: somegroup -# # Default: chart icon -# icon: icon.png -# pod-selector: "" -# widget: -# # Default: chartname -# type: "sometype" -# # Default: host-path of first ingress -# url: "https://example.com" -# custom: -# - somesetting: some value - # -- Make this the primary ingress (used in probes, notes, etc...). # If there is more than 1 ingress, make sure that only 1 ingress is marked as primary. primary: true - # -- Ensure this ingress is always enabled. required: false - - # -- Override the name suffix that is used for this ingress. - nameOverride: - - # -- Autolink the ingress to a service and port, both with the same name as the ingress. - autoLink: false - - # -- disable to ignore any default middlwares - enableFixedMiddlewares: true - - # -- set the Cert-Manager clusterissuer for this ingress - clusterIssuer: "" - - # -- List of middlewares in the traefikmiddlewares k8s namespace to add automatically - # Creates an annotation with the middlewares and appends k8s and traefik namespaces to the middleware names - # Primarily used for TrueNAS SCALE to add additional (seperate) middlewares without exposing them to the end-user - fixedMiddlewares: - - chain-basic - - # -- Additional List of middlewares in the traefikmiddlewares k8s namespace to add automatically - # Creates an annotation with the middlewares and appends k8s and traefik namespaces to the middleware names - middlewares: [] - annotationsList: [] - # - name: somename - # value: somevalue - # -- Provide additional annotations which may be required. - annotations: {} - # kubernetes.io/ingress.class: nginx - # kubernetes.io/tls-acme: "true" - - labelsList: [] - # - name: somename - # value: somevalue - # -- Set labels on the deployment/statefulset/daemonset - # -- Provide additional labels which may be required. + expandObjectName: false # -- Provide additional labels which may be required. labels: {} - + # -- Provide additional annotations which may be required. + annotations: {} # -- Set the ingressClass that is used for this ingress. # Requires Kubernetes >=1.19 - ingressClassName: # "nginx" - - # Enable or disable CORS Requests to the ingress - allowCors: false - + ingressClassName: "" + # Defaults to primary service and primary port + # targetSelector: + # # service: port + # main: main ## Configure the hosts for the ingress - hosts: - - # -- Host address. Helm template can be passed. - host: chart-example.local - ## Configure the paths for the host - paths: - - # -- Path. Helm template can be passed. - path: / - # -- Ignored if not kubeVersion >= 1.14-0 - pathType: Prefix - service: - # -- Overrides the service name reference for this path - name: - # -- Overrides the service port reference for this path - port: - + hosts: [] + # - # -- Host address. Helm template can be passed. + # host: chart-example.local + # ## Configure the paths for the host + # paths: + # - # -- Path. Helm template can be passed. + # path: / + # # -- Ignored if not kubeVersion >= 1.14-0 + # pathType: Prefix + # # -- Overrides the service reference for this path, by default the selector is honored + # overrideService: + # # -- Overrides the service name reference for this path + # name: + # # -- Overrides the service port reference for this path + # port: # -- Configure TLS for the ingress. Both secretName and hosts can process a Helm template. # Gets ignored when clusterIssuer is filled tls: [] # - secretName: chart-example-tls # # Cannot be combined with scaleCert - # clusterIssuer: "" - # # Cannot be combined with clusterIssuer + # certificateIssuer: "" + # # Cannot be combined with certificateIssuer # scaleCert: "" # hosts: # - chart-example.local + integrations: + certManager: + enabled: false + certificateIssuer: "" + traefik: + enabled: true + # Default to websecure + entrypoints: + - websecure + enableFixedMiddlewares: true + # Drops both global and local fixedMiddlewares when enabled + allowCors: false + # fixedMiddlewares: + # - chain-basic + middlewares: [] + homepage: + enabled: false + # Default: chart name + name: somename + # Default: chart description + description: some description + # Default: no group + group: somegroup + # Default: chart icon + icon: icon.png + widget: + # Default: chartname + type: "sometype" + # Default to ingress host 0 + url: "https://example.com" + custom: + - somesetting: some value # -- BETA: Configure the gateway routes for the chart here. # Additional routes can be added by adding a dictionary key similar to the 'main' route.