diff --git a/.github/workflows/build-push-artifacts.yaml b/.github/workflows/build-push-artifacts.yaml index c3cff879..3028fea7 100644 --- a/.github/workflows/build-push-artifacts.yaml +++ b/.github/workflows/build-push-artifacts.yaml @@ -67,7 +67,6 @@ jobs: push: true tags: ${{ steps.image-meta.outputs.tags }} labels: ${{ steps.image-meta.outputs.labels }} - github-token: ${{ secrets.GITHUB_TOKEN }} build_push_chart: name: Build and push Helm chart diff --git a/.github/workflows/helm-lint.yaml b/.github/workflows/helm-lint.yaml new file mode 100644 index 00000000..d9d57d90 --- /dev/null +++ b/.github/workflows/helm-lint.yaml @@ -0,0 +1,48 @@ +# NOTE: This workflow can be run locally using https://github.com/nektos/act with: +# act -W .github/workflows/helm-lint.yaml workflow_call -s GITHUB_TOKEN=$(gh auth token) +name: Helm Lint +on: + workflow_call: + inputs: + ref: + type: string + description: The Git ref under test. + required: true + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: ${{ inputs.ref }} + fetch-depth: 0 + + - name: Set up Helm + uses: azure/setup-helm@v4 + with: + version: v3.15.3 + + - name: Set up chart-testing + uses: helm/chart-testing-action@v2 + + - name: Run chart-testing (lint) + run: |- + ct lint \ + --target-branch ${{ github.event.repository.default_branch }} \ + --charts chart/ \ + --validate-maintainers=false + + # Dependency build required for reloader + # Additional schemas required for cert-manager + - name: Run template validation + run: |- + helm dependency build && helm template foo chart \ + | docker run -i --rm ghcr.io/yannh/kubeconform:latest \ + -schema-location default \ + -schema-location 'https://raw.githubusercontent.com/datreeio/CRDs-catalog/main/{{.Group}}/{{.ResourceKind}}_{{.ResourceAPIVersion}}.json' \ + --strict --summary + + - name: Run manifest snapshot test + run: docker run -i --rm -v $(pwd):/apps helmunittest/helm-unittest chart diff --git a/.github/workflows/test-pr.yaml b/.github/workflows/test-pr.yaml index 40dc999a..5cfb423c 100644 --- a/.github/workflows/test-pr.yaml +++ b/.github/workflows/test-pr.yaml @@ -24,6 +24,12 @@ jobs: with: ref: ${{ github.event.pull_request.head.sha }} + # Run the chart linting on every PR, even from external repos + lint: + uses: ./.github/workflows/lint.yaml + with: + ref: ${{ github.event.pull_request.head.sha }} + # This job exists so that PRs from outside the main repo are rejected fail_on_remote: runs-on: ubuntu-latest @@ -68,7 +74,7 @@ jobs: - name: Run Azimuth tests uses: azimuth-cloud/azimuth-config/.github/actions/test@devel - # Tear down the environment + # Tear down the environment - name: Destroy Azimuth uses: azimuth-cloud/azimuth-config/.github/actions/destroy@devel if: ${{ always() }} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..169ed692 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,22 @@ +# Contributing + +We welcome contributions and suggestions for improvements to this code base. +Please check for relevant issues and PRs before opening a new one of your own. + +## Making a contribution + +### Helm template snapshots + +The CI in this repository uses the Helm +[unittest](https://github.com/helm-unittest/helm-unittest) plugin's +snapshotting functionality to check PRs for changes to the templated manifests. +Therefore, if your PR makes changes to the manifest templates or values, you +will need to update the saved snapshots to allow your changes to pass the +automated tests. The easiest way to do this is to run the helm unittest command +inside a docker container from the repo root. + +``` +docker run -i --rm -v $(pwd):/apps helmunittest/helm-unittest chart -u +``` + +where the `-u` option is used to update the existing snapshots. diff --git a/Dockerfile b/Dockerfile index 1836fd18..a27d6ee0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:jammy as helm +FROM ubuntu:jammy AS helm RUN apt-get update && \ apt-get install -y curl && \ @@ -66,22 +66,22 @@ RUN /venv/bin/pip install -e /app FROM ubuntu:jammy # Don't buffer stdout and stderr as it breaks realtime logging -ENV PYTHONUNBUFFERED 1 +ENV PYTHONUNBUFFERED=1 # Make httpx use the system trust roots # By default, this means we use the CAs from the ca-certificates package -ENV SSL_CERT_FILE /etc/ssl/certs/ca-certificates.crt +ENV SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt # Tell Helm to use /tmp for mutable data -ENV HELM_CACHE_HOME /tmp/helm/cache -ENV HELM_CONFIG_HOME /tmp/helm/config -ENV HELM_DATA_HOME /tmp/helm/data +ENV HELM_CACHE_HOME=/tmp/helm/cache +ENV HELM_CONFIG_HOME=/tmp/helm/config +ENV HELM_DATA_HOME=/tmp/helm/data # Create the user that will be used to run the app -ENV APP_UID 1001 -ENV APP_GID 1001 -ENV APP_USER app -ENV APP_GROUP app +ENV APP_UID=1001 +ENV APP_GID=1001 +ENV APP_USER=app +ENV APP_GROUP=app RUN groupadd --gid $APP_GID $APP_GROUP && \ useradd \ --no-create-home \ diff --git a/chart/.helmignore b/chart/.helmignore index 0e8a0eb3..31bcbc27 100644 --- a/chart/.helmignore +++ b/chart/.helmignore @@ -21,3 +21,5 @@ .idea/ *.tmproj .vscode/ +# Helm unit-test files +tests/ diff --git a/chart/tests/__snapshot__/snapshot_test.yaml.snap b/chart/tests/__snapshot__/snapshot_test.yaml.snap new file mode 100755 index 00000000..952669d4 --- /dev/null +++ b/chart/tests/__snapshot__/snapshot_test.yaml.snap @@ -0,0 +1,664 @@ +templated manifests should match snapshot: + 1: | + raw: | + - For a `Deployment` called `foo` have a `ConfigMap` called `foo-configmap`. Then add this annotation to main metadata of your `Deployment` + configmap.reloader.stakater.com/reload: "foo-configmap" + + - For a `Deployment` called `foo` have a `Secret` called `foo-secret`. Then add this annotation to main metadata of your `Deployment` + secret.reloader.stakater.com/reload: "foo-secret" + + - After successful installation, your pods will get rolling updates when a change in data of configmap or secret will happen. + 2: | + apiVersion: apps/v1 + kind: Deployment + metadata: + annotations: + meta.helm.sh/release-name: RELEASE-NAME + meta.helm.sh/release-namespace: NAMESPACE + labels: + app: RELEASE-NAME-reloader + app.kubernetes.io/managed-by: Helm + chart: reloader-2.1.4 + group: com.stakater.platform + heritage: Helm + provider: stakater + release: RELEASE-NAME + version: v1.4.4 + name: RELEASE-NAME-reloader + namespace: NAMESPACE + spec: + replicas: 1 + revisionHistoryLimit: 2 + selector: + matchLabels: + app: RELEASE-NAME-reloader + release: RELEASE-NAME + template: + metadata: + labels: + app: RELEASE-NAME-reloader + app.kubernetes.io/managed-by: Helm + chart: reloader-2.1.4 + group: com.stakater.platform + heritage: Helm + provider: stakater + release: RELEASE-NAME + version: v1.4.4 + spec: + containers: + - args: + - --log-level=info + - --reload-on-create=true + - --sync-after-restart=true + env: + - name: GOMAXPROCS + valueFrom: + resourceFieldRef: + divisor: "1" + resource: limits.cpu + - name: GOMEMLIMIT + valueFrom: + resourceFieldRef: + divisor: "1" + resource: limits.memory + - name: KUBERNETES_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + image: ghcr.io/stakater/reloader:v1.4.4 + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 5 + httpGet: + path: /live + port: http + initialDelaySeconds: 10 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 5 + name: RELEASE-NAME-reloader + ports: + - containerPort: 9090 + name: http + readinessProbe: + failureThreshold: 5 + httpGet: + path: /metrics + port: http + initialDelaySeconds: 10 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 5 + securityContext: {} + securityContext: + runAsNonRoot: true + runAsUser: 65534 + seccompProfile: + type: RuntimeDefault + serviceAccountName: RELEASE-NAME-reloader + 3: | + apiVersion: rbac.authorization.k8s.io/v1beta1 + kind: Role + metadata: + annotations: + meta.helm.sh/release-name: RELEASE-NAME + meta.helm.sh/release-namespace: NAMESPACE + labels: + app: RELEASE-NAME-reloader + app.kubernetes.io/managed-by: Helm + chart: reloader-2.1.4 + heritage: Helm + release: RELEASE-NAME + name: RELEASE-NAME-reloader-role + namespace: NAMESPACE + rules: + - apiGroups: + - "" + resources: + - secrets + - configmaps + verbs: + - list + - get + - watch + - apiGroups: + - apps + resources: + - deployments + - daemonsets + - statefulsets + verbs: + - list + - get + - update + - patch + - apiGroups: + - batch + resources: + - cronjobs + verbs: + - list + - get + - apiGroups: + - batch + resources: + - jobs + verbs: + - create + - delete + - list + - get + - apiGroups: + - "" + resources: + - events + verbs: + - create + - patch + 4: | + apiVersion: rbac.authorization.k8s.io/v1beta1 + kind: RoleBinding + metadata: + annotations: + meta.helm.sh/release-name: RELEASE-NAME + meta.helm.sh/release-namespace: NAMESPACE + labels: + app: RELEASE-NAME-reloader + app.kubernetes.io/managed-by: Helm + chart: reloader-2.1.4 + heritage: Helm + release: RELEASE-NAME + name: RELEASE-NAME-reloader-role-binding + namespace: NAMESPACE + roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: RELEASE-NAME-reloader-role + subjects: + - kind: ServiceAccount + name: RELEASE-NAME-reloader + namespace: NAMESPACE + 5: | + apiVersion: v1 + kind: ServiceAccount + metadata: + annotations: + meta.helm.sh/release-name: RELEASE-NAME + meta.helm.sh/release-namespace: NAMESPACE + labels: + app: RELEASE-NAME-reloader + app.kubernetes.io/managed-by: Helm + chart: reloader-2.1.4 + heritage: Helm + release: RELEASE-NAME + name: RELEASE-NAME-reloader + namespace: NAMESPACE + 6: | + apiVersion: cert-manager.io/v1 + kind: Certificate + metadata: + labels: + app.kubernetes.io/instance: RELEASE-NAME + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: azimuth-capi-operator + app.kubernetes.io/version: main + helm.sh/chart: azimuth-capi-operator-0.1.0 + name: release-name-azimuth-capi-operator + spec: + dnsNames: + - release-name-azimuth-capi-operator.NAMESPACE.svc + - release-name-azimuth-capi-operator.NAMESPACE.svc.cluster.local + issuerRef: + kind: Issuer + name: release-name-azimuth-capi-operator + secretName: release-name-azimuth-capi-operator-cert + 7: | + apiVersion: rbac.authorization.k8s.io/v1 + kind: ClusterRole + metadata: + labels: + app.kubernetes.io/instance: RELEASE-NAME + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: azimuth-capi-operator + app.kubernetes.io/version: main + helm.sh/chart: azimuth-capi-operator-0.1.0 + name: release-name-azimuth-capi-operator + rules: + - apiGroups: + - apiextensions.k8s.io + resources: + - customresourcedefinitions + verbs: + - list + - get + - watch + - create + - apiGroups: + - apiextensions.k8s.io + resourceNames: + - clustertemplates.azimuth.stackhpc.com + - clusters.azimuth.stackhpc.com + - apptemplates.azimuth.stackhpc.com + resources: + - customresourcedefinitions + verbs: + - update + - patch + - apiGroups: + - "" + resources: + - namespaces + verbs: + - list + - watch + - apiGroups: + - "" + - events.k8s.io + resources: + - events + verbs: + - create + - apiGroups: + - azimuth.stackhpc.com + resources: + - '*' + verbs: + - '*' + - apiGroups: + - "" + resources: + - serviceaccounts + verbs: + - '*' + - apiGroups: + - rbac.authorization.k8s.io + resources: + - roles + - rolebindings + verbs: + - '*' + - apiGroups: + - "" + resources: + - secrets + verbs: + - '*' + - apiGroups: + - cluster.x-k8s.io + resources: + - clusters + - machinedeployments + - machines + - machinehealthchecks + verbs: + - '*' + - apiGroups: + - bootstrap.cluster.x-k8s.io + resources: + - kubeadmconfigtemplates + verbs: + - '*' + - apiGroups: + - controlplane.cluster.x-k8s.io + resources: + - kubeadmcontrolplanes + verbs: + - '*' + - apiGroups: + - infrastructure.cluster.x-k8s.io + resources: + - openstackclusters + - openstackmachinetemplates + verbs: + - '*' + - apiGroups: + - addons.stackhpc.com + resources: + - '*' + verbs: + - '*' + - apiGroups: + - apps + resources: + - deployments + verbs: + - '*' + - apiGroups: + - batch + resources: + - jobs + verbs: + - '*' + - apiGroups: + - infrastructure.cluster.x-k8s.io + resources: + - openstackmachines + verbs: + - list + - get + - watch + - apiGroups: + - identity.azimuth.stackhpc.com + resources: + - realms + verbs: + - list + - get + - watch + - apiGroups: + - identity.azimuth.stackhpc.com + resources: + - oidcclients + - platforms + verbs: + - '*' + - apiGroups: + - scheduling.azimuth.stackhpc.com + resources: + - leases + verbs: + - list + - get + - watch + - patch + - apiGroups: + - notification.toolkit.fluxcd.io + resources: + - alerts + - providers + - receivers + verbs: + - get + - list + - watch + - patch + - update + - create + - delete + - apiGroups: + - source.toolkit.fluxcd.io + resources: + - buckets + - gitrepositories + - helmcharts + - ocirepositories + - helmrepositories + verbs: + - get + - list + - watch + - patch + - update + - create + - delete + - apiGroups: + - helm.toolkit.fluxcd.io + resources: + - helmreleases + verbs: + - get + - list + - watch + - patch + - update + - create + - delete + - apiGroups: + - kustomize.toolkit.fluxcd.io + resources: + - kustomizations + verbs: + - get + - list + - watch + - patch + - update + - create + - delete + - apiGroups: + - image.toolkit.fluxcd.io + resources: + - imagepolicies + - imagerepositories + - imageupdateautomations + verbs: + - get + - list + - watch + - patch + - update + - create + - delete + 8: | + apiVersion: rbac.authorization.k8s.io/v1 + kind: ClusterRoleBinding + metadata: + labels: + app.kubernetes.io/instance: RELEASE-NAME + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: azimuth-capi-operator + app.kubernetes.io/version: main + helm.sh/chart: azimuth-capi-operator-0.1.0 + name: release-name-azimuth-capi-operator + roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: release-name-azimuth-capi-operator + subjects: + - kind: ServiceAccount + name: release-name-azimuth-capi-operator + namespace: NAMESPACE + 9: | + apiVersion: apps/v1 + kind: Deployment + metadata: + annotations: + secret.reloader.stakater.com/reload: release-name-azimuth-capi-operator-cert + labels: + app.kubernetes.io/instance: RELEASE-NAME + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: azimuth-capi-operator + app.kubernetes.io/version: main + helm.sh/chart: azimuth-capi-operator-0.1.0 + name: release-name-azimuth-capi-operator + spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/instance: RELEASE-NAME + app.kubernetes.io/name: azimuth-capi-operator + strategy: + type: Recreate + template: + metadata: + annotations: + azimuth.stackhpc.com/config-hash: e00d6ac405419fe1efdde055a73eab4bea2215fa6d87eb0b35dc30cc3eb04389 + labels: + app.kubernetes.io/instance: RELEASE-NAME + app.kubernetes.io/name: azimuth-capi-operator + spec: + containers: + - image: ghcr.io/azimuth-cloud/azimuth-capi-operator:main + imagePullPolicy: IfNotPresent + name: operator + ports: + - containerPort: 8080 + name: metrics + protocol: TCP + - containerPort: 8443 + name: webhook + protocol: TCP + readinessProbe: + failureThreshold: 1 + periodSeconds: 5 + tcpSocket: + port: webhook + resources: {} + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + volumeMounts: + - mountPath: /etc/azimuth + name: etc-azimuth + readOnly: true + - mountPath: /etc/azimuth/cert + name: etc-azimuth-cert + readOnly: true + - mountPath: /tmp + name: tmp + securityContext: + runAsNonRoot: true + serviceAccountName: release-name-azimuth-capi-operator + volumes: + - name: etc-azimuth + secret: + secretName: release-name-azimuth-capi-operator + - name: etc-azimuth-cert + secret: + secretName: release-name-azimuth-capi-operator-cert + - emptyDir: {} + name: tmp + 10: | + apiVersion: cert-manager.io/v1 + kind: Issuer + metadata: + labels: + app.kubernetes.io/instance: RELEASE-NAME + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: azimuth-capi-operator + app.kubernetes.io/version: main + helm.sh/chart: azimuth-capi-operator-0.1.0 + name: release-name-azimuth-capi-operator + spec: + selfSigned: {} + 11: | + apiVersion: v1 + kind: Secret + metadata: + labels: + app.kubernetes.io/instance: RELEASE-NAME + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: azimuth-capi-operator + app.kubernetes.io/version: main + helm.sh/chart: azimuth-capi-operator-0.1.0 + name: release-name-azimuth-capi-operator + stringData: + capi-operator.yaml: | + !include "/etc/azimuth/webhook.yaml,/etc/azimuth/user-config.yaml" + user-config.yaml: | + identity: + defaultUserClusterRole: + rules: + - apiGroups: + - '*' + resources: + - '*' + verbs: + - '*' + - nonResourceURLs: + - '*' + verbs: + - '*' + webhook.yaml: | + webhook: + managed: false + certfile: /etc/azimuth/cert/tls.crt + keyfile: /etc/azimuth/cert/tls.key + 12: | + apiVersion: v1 + kind: Service + metadata: + labels: + app.kubernetes.io/instance: RELEASE-NAME + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: azimuth-capi-operator + app.kubernetes.io/version: main + helm.sh/chart: azimuth-capi-operator-0.1.0 + name: release-name-azimuth-capi-operator + spec: + ports: + - name: metrics + port: 8080 + protocol: TCP + targetPort: metrics + - name: webhook + port: 443 + protocol: TCP + targetPort: webhook + selector: + app.kubernetes.io/instance: RELEASE-NAME + app.kubernetes.io/name: azimuth-capi-operator + type: ClusterIP + 13: | + apiVersion: v1 + kind: ServiceAccount + metadata: + labels: + app.kubernetes.io/instance: RELEASE-NAME + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: azimuth-capi-operator + app.kubernetes.io/version: main + helm.sh/chart: azimuth-capi-operator-0.1.0 + name: release-name-azimuth-capi-operator + 14: | + apiVersion: admissionregistration.k8s.io/v1 + kind: ValidatingWebhookConfiguration + metadata: + annotations: + cert-manager.io/inject-ca-from: NAMESPACE/release-name-azimuth-capi-operator + labels: + app.kubernetes.io/instance: RELEASE-NAME + app.kubernetes.io/managed-by: Helm + app.kubernetes.io/name: azimuth-capi-operator + app.kubernetes.io/version: main + helm.sh/chart: azimuth-capi-operator-0.1.0 + name: release-name-azimuth-capi-operator + webhooks: + - admissionReviewVersions: + - v1 + - v1beta1 + clientConfig: + service: + name: release-name-azimuth-capi-operator + namespace: NAMESPACE + path: /validate-cluster-template + failurePolicy: Fail + matchPolicy: Equivalent + name: validate-cluster-template.webhook.azimuth.stackhpc.com + rules: + - apiGroups: + - azimuth.stackhpc.com + apiVersions: + - v1alpha1 + operations: + - CREATE + - UPDATE + - DELETE + resources: + - clustertemplates + sideEffects: None + - admissionReviewVersions: + - v1 + - v1beta1 + clientConfig: + service: + name: release-name-azimuth-capi-operator + namespace: NAMESPACE + path: /validate-cluster + failurePolicy: Fail + matchPolicy: Equivalent + name: validate-cluster.webhook.azimuth.stackhpc.com + rules: + - apiGroups: + - azimuth.stackhpc.com + apiVersions: + - v1alpha1 + operations: + - CREATE + - UPDATE + resources: + - clusters + sideEffects: None diff --git a/chart/tests/snapshot_test.yaml b/chart/tests/snapshot_test.yaml new file mode 100644 index 00000000..262562e6 --- /dev/null +++ b/chart/tests/snapshot_test.yaml @@ -0,0 +1,7 @@ +# To update manifest snapshots run helm unittest plugin with -u option: +# docker run -i --rm -v $(pwd):/apps helmunittest/helm-unittest -u chart +suite: Manifest snapshot tests +tests: + - it: templated manifests should match snapshot + asserts: + - matchSnapshot: {}