diff --git a/.github/ct.yaml b/.github/ct.yaml new file mode 100644 index 0000000..7e33cf3 --- /dev/null +++ b/.github/ct.yaml @@ -0,0 +1,29 @@ +# Configuration for chart-testing (ct), used by .github/workflows/lint-and-test.yaml +# Full reference: https://github.com/helm/chart-testing + +chart-dirs: + - charts + +target-branch: main + +# Chart.yaml does not declare a maintainers list today; enabling this would +# block linting. Revisit if a maintainers list is added. +validate-maintainers: false + +# Defaults below are made explicit to document the chart's CI policy +# rather than relying on ct's built-in defaults. +check-version-increment: true +validate-chart-schema: true +validate-yaml: true + +# In-place upgrade testing (disabled). ct's `--upgrade` doesn't exercise +# `helm upgrade` against an existing deployed release the way users would +# expect; it installs the previous revision into an ephemeral namespace, +# upgrades to current, then tears down. Combined with the ~2x CI runtime +# cost, not worth enabling right now. Revisit if ct's upgrade semantics +# change or maintainers want to opt in. +upgrade: false + +# Passed through to `helm install` during `ct install`. 15-minute timeout +# accommodates Graylog's startup time on minimal-resource CI runners. +helm-extra-args: --timeout=900s diff --git a/.github/workflows/lint-and-test.yaml b/.github/workflows/lint-and-test.yaml index a79a3eb..5f3d714 100644 --- a/.github/workflows/lint-and-test.yaml +++ b/.github/workflows/lint-and-test.yaml @@ -7,8 +7,122 @@ on: branches: ["main"] jobs: - lint: + helm-ct-lint: runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + helm-version: + - v3.16.4 + - v4.2.0 steps: - - uses: actions/checkout@v3 - - run: helm lint charts/graylog + - name: Checkout + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Set up Helm + uses: azure/setup-helm@v5 + with: + version: ${{ matrix.helm-version }} + + - name: Set up chart-testing + uses: helm/chart-testing-action@v2.8.0 + + - name: Run ct lint + run: ct lint --config .github/ct.yaml --all + + helm-unittest: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + helm-version: + - v3.16.4 + - v4.2.0 + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Set up Helm + uses: azure/setup-helm@v5 + with: + version: ${{ matrix.helm-version }} + + - name: Install helm-unittest plugin + # `helm plugin install --version` does NOT pin to a git tag — it + # clones the default branch. Clone the v1.0.3 tag explicitly and + # install from local path. v1.0.3 is the latest helm-unittest + # release whose plugin.yaml does NOT use the `platformHooks` field; + # v1.1.0+ adds that field, which Helm 3.16.4 rejects during + # plugin.yaml parsing. Pinning to v1.0.3 keeps both matrix variants + # (Helm 3 and Helm 4) compatible. + # Local-path install also sidesteps Helm 4's signature verification. + run: | + git clone --depth 1 --branch v1.0.3 https://github.com/helm-unittest/helm-unittest /tmp/helm-unittest + helm plugin install /tmp/helm-unittest + + - name: Run helm unittest + run: helm unittest charts/graylog + + helm-ct-install: + runs-on: ubuntu-latest + needs: [helm-ct-lint, helm-unittest] + strategy: + fail-fast: false + # Asymmetric K8s × Helm matrix: full K8s coverage on Helm 3 (the chart's + # stated minimum per docs/TESTING.md), with Helm 4 added as bonus coverage + # on the most recent K8s version. Expand by adding more `include:` entries. + matrix: + include: + - helm-version: v3.16.4 + k8s-version: v1.32.11 + - helm-version: v3.16.4 + k8s-version: v1.33.7 + - helm-version: v3.16.4 + k8s-version: v1.34.3 + - helm-version: v4.2.0 + k8s-version: v1.34.3 + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Set up Helm + uses: azure/setup-helm@v5 + with: + version: ${{ matrix.helm-version }} + + - name: Set up chart-testing + uses: helm/chart-testing-action@v2.8.0 + + - name: Create kind cluster + uses: helm/kind-action@v1.14.0 + with: + node_image: kindest/node:${{ matrix.k8s-version }} + + - name: Install MongoDB Kubernetes Operator + run: | + helm upgrade --install mongodb-kubernetes-operator mongodb-kubernetes \ + --repo https://mongodb.github.io/helm-charts \ + --version "1.6.1" \ + --set operator.watchNamespace="*" \ + --namespace operators \ + --create-namespace \ + --wait \ + --timeout 5m + + - name: Generate ephemeral CI rootPassword and mask in workflow logs + id: ci-root-password + run: | + password=$(openssl rand -hex 16) + echo "::add-mask::${password}" + echo "value=${password}" >> "$GITHUB_OUTPUT" + + - name: Run ct install + run: | + ct install --config .github/ct.yaml --all \ + --helm-extra-set-args "--set graylog.config.rootPassword=${{ steps.ci-root-password.outputs.value }}" diff --git a/charts/graylog/Chart.yaml b/charts/graylog/Chart.yaml index 2f301d3..052f68d 100644 --- a/charts/graylog/Chart.yaml +++ b/charts/graylog/Chart.yaml @@ -65,4 +65,4 @@ annotations: # This is the chart version. version: 1.0.0 # This is the version number of the Graylog application bundled with this chart. -appVersion: "7.0" \ No newline at end of file +appVersion: "7.0" diff --git a/charts/graylog/ci/ci-values.yaml b/charts/graylog/ci/ci-values.yaml new file mode 100644 index 0000000..303ebf8 --- /dev/null +++ b/charts/graylog/ci/ci-values.yaml @@ -0,0 +1,33 @@ +# CI overlay used by `ct install` (auto-discovered via charts/graylog/ci/*-values.yaml). +# Minimal-resource configuration tuned to fit a default GitHub-hosted runner +# (~7 GB RAM, 4 vCPU). Not intended for production-like validation. + +graylog: + replicas: 1 + config: + serverJavaOpts: "-Xms512m -Xmx768m" + resources: + requests: + cpu: "250m" + memory: "768Mi" + limits: + cpu: "1" + memory: "1500Mi" + +datanode: + replicas: 1 + config: + opensearchHeap: "1g" + javaOpts: "-Xms512m -Xmx512m" + resources: + requests: + cpu: "250m" + memory: "1Gi" + limits: + cpu: "1" + memory: "2Gi" + +mongodb: + replicas: 1 + arbiters: 0 + version: "8.0.23" diff --git a/charts/graylog/tests/graylog_statefulset_test.yaml b/charts/graylog/tests/graylog_statefulset_test.yaml new file mode 100644 index 0000000..7eb2b14 --- /dev/null +++ b/charts/graylog/tests/graylog_statefulset_test.yaml @@ -0,0 +1,79 @@ +suite: graylog StatefulSet +templates: + # The StatefulSet's pod-spec annotations reference two checksum helpers that + # `include` other templates by path. helm-unittest's `templates:` filter must + # list those dependencies so the includes resolve. Assertions below use the + # `template:` field to scope each assert to the StatefulSet specifically. + - workload/statefulsets/graylog.yaml + - config/graylog.yaml # checksum/config dependency + - config/secret/secrets.yaml # checksum/secret dependency +release: + name: graylog + namespace: graylog +tests: + - it: respects graylog.replicas value + set: + graylog.replicas: 3 + asserts: + - equal: + path: spec.replicas + value: 3 + template: workload/statefulsets/graylog.yaml + + - it: mounts tls-creds volume when graylog.config.tls.enabled is true + set: + graylog.config.tls.enabled: true + graylog.config.tls.secretName: my-tls-secret + asserts: + - contains: + path: spec.template.spec.volumes + content: + name: tls-creds + secret: + secretName: "my-tls-secret" + template: workload/statefulsets/graylog.yaml + + - it: mounts tls-creds at /usr/share/graylog/tls when TLS enabled + set: + graylog.config.tls.enabled: true + graylog.config.tls.secretName: my-tls-secret + asserts: + - contains: + path: spec.template.spec.containers[0].volumeMounts + content: + name: tls-creds + mountPath: /usr/share/graylog/tls + template: workload/statefulsets/graylog.yaml + + - it: does NOT mount tls-creds when graylog.config.tls.enabled is false + set: + graylog.config.tls.enabled: false + asserts: + - notContains: + path: spec.template.spec.containers[0].volumeMounts + content: + name: tls-creds + mountPath: /usr/share/graylog/tls + template: workload/statefulsets/graylog.yaml + + - it: adds init-plugins emptyDir volume when graylog.config.plugins.enabled is true + set: + graylog.config.plugins.enabled: true + asserts: + - contains: + path: spec.template.spec.volumes + content: + name: init-plugins + emptyDir: {} + template: workload/statefulsets/graylog.yaml + + - it: does NOT add init-plugins volume when graylog.config.plugins.enabled is false + set: + graylog.config.plugins.enabled: false + asserts: + - notContains: + path: spec.template.spec.volumes + content: + name: init-plugins + emptyDir: {} + template: workload/statefulsets/graylog.yaml diff --git a/charts/graylog/tests/ingress_test.yaml b/charts/graylog/tests/ingress_test.yaml new file mode 100644 index 0000000..20f9a85 --- /dev/null +++ b/charts/graylog/tests/ingress_test.yaml @@ -0,0 +1,112 @@ +suite: graylog web Ingress +templates: + - service/ingress/graylog.yaml +release: + name: graylog + namespace: graylog +tests: + - it: does not render when ingress.enabled is false + set: + ingress.enabled: false + asserts: + - hasDocuments: + count: 0 + + - it: does not render when ingress.web.enabled is false + set: + ingress.enabled: true + ingress.web.enabled: false + asserts: + - hasDocuments: + count: 0 + + - it: renders when ingress.enabled and ingress.web.enabled are both true + set: + ingress.enabled: true + ingress.web.enabled: true + asserts: + - hasDocuments: + count: 1 + - equal: + path: kind + value: Ingress + - equal: + path: apiVersion + value: networking.k8s.io/v1 + + - it: sets ingressClassName from values + set: + ingress.enabled: true + ingress.web.enabled: true + ingress.web.className: nginx + asserts: + - equal: + path: spec.ingressClassName + value: nginx + + - it: renders TLS section when ingress.web.tls is provided + set: + ingress.enabled: true + ingress.web.enabled: true + ingress.web.tls: + - secretName: graylog-tls + hosts: + - graylog.example.com + asserts: + - equal: + path: spec.tls[0].secretName + value: graylog-tls + - equal: + path: spec.tls[0].hosts[0] + value: graylog.example.com + + - it: omits TLS section when ingress.web.tls is empty + set: + ingress.enabled: true + ingress.web.enabled: true + ingress.web.tls: [] + asserts: + - notExists: + path: spec.tls + + - it: adds cert-manager cluster-issuer annotation when configured + set: + ingress.enabled: true + ingress.web.enabled: true + ingress.web.tls: + - secretName: graylog-tls + hosts: + - graylog.example.com + ingress.config.tls.clusterIssuer.existingName: letsencrypt-prod + asserts: + - equal: + path: metadata.annotations["cert-manager.io/cluster-issuer"] + value: letsencrypt-prod + + - it: adds cert-manager issuer annotation when existingName is configured + set: + ingress.enabled: true + ingress.web.enabled: true + ingress.web.tls: + - secretName: graylog-tls + hosts: + - graylog.example.com + ingress.config.tls.issuer.existingName: my-issuer + asserts: + - equal: + path: metadata.annotations["cert-manager.io/issuer"] + value: my-issuer + + - it: does NOT add cert-manager annotation when no issuer is configured + set: + ingress.enabled: true + ingress.web.enabled: true + ingress.web.tls: + - secretName: graylog-tls + hosts: + - graylog.example.com + asserts: + - notExists: + path: metadata.annotations["cert-manager.io/cluster-issuer"] + - notExists: + path: metadata.annotations["cert-manager.io/issuer"] diff --git a/charts/graylog/tests/init_graylog_test.yaml b/charts/graylog/tests/init_graylog_test.yaml new file mode 100644 index 0000000..02b88cf --- /dev/null +++ b/charts/graylog/tests/init_graylog_test.yaml @@ -0,0 +1,66 @@ +suite: graylog init-script ConfigMap +templates: + - config/init-graylog.yaml +release: + name: graylog + namespace: graylog +tests: + - it: does not render when graylog.enabled is false + set: + graylog.enabled: false + asserts: + - hasDocuments: + count: 0 + + - it: renders the init-script.sh data key + asserts: + - exists: + path: data["init-script.sh"] + + - it: always includes the MongoDB credentials check + asserts: + - matchRegex: + path: data["init-script.sh"] + pattern: 'env \| grep GRAYLOG_MONGODB_URI \| grep -q "@"' + + - it: includes truststore-import logic when TLS is enabled with updateKeyStore + set: + graylog.config.tls.enabled: true + graylog.config.tls.updateKeyStore: true + asserts: + - matchRegex: + path: data["init-script.sh"] + pattern: 'keytool -importcert' + + - it: skips truststore-import logic when TLS is enabled but updateKeyStore is false + set: + graylog.config.tls.enabled: true + graylog.config.tls.updateKeyStore: false + asserts: + - notMatchRegex: + path: data["init-script.sh"] + pattern: 'keytool -importcert' + + - it: skips TLS setup entirely when TLS is disabled + set: + graylog.config.tls.enabled: false + asserts: + - notMatchRegex: + path: data["init-script.sh"] + pattern: '/mnt/tls/tls.crt' + + - it: includes plugin-copy logic when plugins are enabled + set: + graylog.config.plugins.enabled: true + asserts: + - matchRegex: + path: data["init-script.sh"] + pattern: 'find /mnt/shared/plugins/' + + - it: skips plugin logic when plugins are disabled + set: + graylog.config.plugins.enabled: false + asserts: + - notMatchRegex: + path: data["init-script.sh"] + pattern: 'find /mnt/shared/plugins/' diff --git a/charts/graylog/tests/mongodb_test.yaml b/charts/graylog/tests/mongodb_test.yaml new file mode 100644 index 0000000..85fca9c --- /dev/null +++ b/charts/graylog/tests/mongodb_test.yaml @@ -0,0 +1,86 @@ +suite: MongoDB Community Custom Resource +templates: + - custom/mongo-rs.yaml +release: + name: graylog + namespace: graylog +tests: + - it: renders the MongoDBCommunity CR when communityResource.enabled is true + set: + mongodb.communityResource.enabled: true + asserts: + - hasDocuments: + count: 1 + - equal: + path: kind + value: MongoDBCommunity + - equal: + path: apiVersion + value: mongodbcommunity.mongodb.com/v1 + + - it: does NOT render the MongoDBCommunity CR when communityResource.enabled is false + set: + mongodb.communityResource.enabled: false + asserts: + - hasDocuments: + count: 0 + + - it: uses configured mongodb.version + set: + mongodb.communityResource.enabled: true + mongodb.version: "8.0.23" + asserts: + - equal: + path: spec.version + value: "8.0.23" + + - it: configures members count from mongodb.replicas + set: + mongodb.communityResource.enabled: true + mongodb.replicas: 3 + mongodb.arbiters: 0 + asserts: + - equal: + path: spec.members + value: 3 + + - it: includes arbiters when mongodb.arbiters > 0 + set: + mongodb.communityResource.enabled: true + mongodb.replicas: 3 + mongodb.arbiters: 1 + asserts: + - equal: + path: spec.arbiters + value: 1 + + - it: omits arbiters key when mongodb.arbiters is 0 + set: + mongodb.communityResource.enabled: true + mongodb.replicas: 3 + mongodb.arbiters: 0 + asserts: + - notExists: + path: spec.arbiters + + - it: fails when arbiters >= replicas + set: + mongodb.communityResource.enabled: true + mongodb.replicas: 2 + mongodb.arbiters: 2 + asserts: + - failedTemplate: + errorPattern: "Number of MongoDB arbiters must be less than the number of replicas" + + - it: sets persistence size from values + set: + mongodb.communityResource.enabled: true + mongodb.persistence.size.data: "20G" + mongodb.persistence.size.logs: "5G" + asserts: + - equal: + path: spec.statefulSet.spec.volumeClaimTemplates[0].spec.resources.requests.storage + value: "20G" + - equal: + path: spec.statefulSet.spec.volumeClaimTemplates[1].spec.resources.requests.storage + value: "5G" diff --git a/charts/graylog/tests/service_test.yaml b/charts/graylog/tests/service_test.yaml new file mode 100644 index 0000000..fc63371 --- /dev/null +++ b/charts/graylog/tests/service_test.yaml @@ -0,0 +1,89 @@ +suite: graylog Service +templates: + - service/graylog.yaml +release: + name: graylog + namespace: graylog +tests: + - it: does not render when graylog.enabled is false + set: + graylog.enabled: false + asserts: + - hasDocuments: + count: 0 + + - it: defaults to ClusterIP service type + asserts: + - equal: + path: spec.type + value: ClusterIP + + - it: respects graylog.service.type override + set: + graylog.service.type: LoadBalancer + asserts: + - equal: + path: spec.type + value: LoadBalancer + + - it: includes metrics port when graylog.service.metrics.enabled is true + set: + graylog.service.metrics.enabled: true + graylog.service.ports.metrics: 9833 + asserts: + - contains: + path: spec.ports + content: + name: metrics + port: 9833 + targetPort: metrics + protocol: TCP + + - it: skips metrics port when graylog.service.metrics.enabled is false + set: + graylog.service.metrics.enabled: false + asserts: + - notContains: + path: spec.ports + content: + name: metrics + port: 9833 + targetPort: metrics + protocol: TCP + + - it: renders one port per graylog.inputs entry + set: + graylog.inputs: + - name: input-gelf + port: 12201 + targetPort: 12201 + protocol: TCP + - name: input-syslog + port: 514 + targetPort: 514 + protocol: UDP + asserts: + - contains: + path: spec.ports + content: + name: input-gelf + port: 12201 + targetPort: 12201 + protocol: TCP + - contains: + path: spec.ports + content: + name: input-syslog + port: 514 + targetPort: 514 + protocol: UDP + + - it: always includes input-fwd-conf port + asserts: + - contains: + path: spec.ports + content: + name: input-fwd-conf + port: 13302 + targetPort: 13302 + protocol: TCP diff --git a/charts/graylog/values.yaml b/charts/graylog/values.yaml index 958eb5a..ea388b4 100644 --- a/charts/graylog/values.yaml +++ b/charts/graylog/values.yaml @@ -341,11 +341,11 @@ mongodb: role: create: true rules: - - apiGroups: [ "" ] - resources: [ "secrets" ] - resourceNames: [ ] - verbs: [ "get" ] - - apiGroups: [ "" ] - resources: [ "pods" ] - resourceNames: [ ] - verbs: [ "get", "patch", "delete" ] \ No newline at end of file + - apiGroups: [""] + resources: ["secrets"] + resourceNames: [] + verbs: ["get"] + - apiGroups: [""] + resources: ["pods"] + resourceNames: [] + verbs: ["get", "patch", "delete"]