diff --git a/.github/actions/cleanup-files/action.yaml b/.github/actions/cleanup-files/action.yaml index d466daf3a8..1818d6094f 100644 --- a/.github/actions/cleanup-files/action.yaml +++ b/.github/actions/cleanup-files/action.yaml @@ -15,7 +15,6 @@ runs: echo "removing zarf sboms, packages, cache" sudo rm -rf zarf-sbom /tmp/zarf-* - sudo env "PATH=$PATH" CI=true make delete-packages sudo build/zarf tools clear-cache lsblk -f diff --git a/.github/actions/golang/action.yaml b/.github/actions/golang/action.yaml deleted file mode 100644 index 890c4bb3cd..0000000000 --- a/.github/actions/golang/action.yaml +++ /dev/null @@ -1,10 +0,0 @@ -name: setup-go -description: "Setup Go binary and caching" - -runs: - using: composite - steps: - - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 - with: - go-version-file: 'go.mod' - cache: true diff --git a/.github/workflows/check-go-mod.yml b/.github/workflows/check-go-mod.yml index 8efdd95beb..4cc64058c8 100644 --- a/.github/workflows/check-go-mod.yml +++ b/.github/workflows/check-go-mod.yml @@ -14,7 +14,9 @@ jobs: uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - name: Setup golang - uses: ./.github/actions/golang + uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 + with: + go-version-file: go.mod - name: Test go mod run: | diff --git a/.github/workflows/nightly-ecr.yml b/.github/workflows/nightly-ecr.yml index 4be6e1ebcb..7b72b9eee3 100644 --- a/.github/workflows/nightly-ecr.yml +++ b/.github/workflows/nightly-ecr.yml @@ -22,7 +22,9 @@ jobs: uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - name: Setup golang - uses: ./.github/actions/golang + uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 + with: + go-version-file: go.mod - name: Build the Zarf binary run: make build-cli-linux-amd diff --git a/.github/workflows/nightly-eks.yml b/.github/workflows/nightly-eks.yml index fa347c2a8c..239f3cbbff 100644 --- a/.github/workflows/nightly-eks.yml +++ b/.github/workflows/nightly-eks.yml @@ -30,7 +30,9 @@ jobs: uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - name: Setup golang - uses: ./.github/actions/golang + uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 + with: + go-version-file: go.mod - name: Build binary and zarf packages uses: ./.github/actions/packages diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3ec2f6839c..38bb15883d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -22,7 +22,9 @@ jobs: fetch-depth: 0 - name: Setup golang - uses: ./.github/actions/golang + uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 + with: + go-version-file: go.mod - name: Install tools uses: ./.github/actions/install-tools @@ -107,7 +109,9 @@ jobs: path: build/ - name: Setup golang - uses: ./.github/actions/golang + uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 + with: + go-version-file: go.mod - name: Make Zarf executable run: | @@ -140,7 +144,9 @@ jobs: fetch-depth: 0 - name: Setup golang - uses: ./.github/actions/golang + uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 + with: + go-version-file: go.mod - name: Install tools uses: ./.github/actions/install-tools diff --git a/.github/workflows/scan-codeql.yml b/.github/workflows/scan-codeql.yml index 58e3596fea..7cf799c2e8 100644 --- a/.github/workflows/scan-codeql.yml +++ b/.github/workflows/scan-codeql.yml @@ -39,11 +39,13 @@ jobs: uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - name: Setup golang - uses: ./.github/actions/golang + uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 + with: + go-version-file: go.mod # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@c36620d31ac7c881962c3d9dd939c40ec9434f2b # v3.26.12 + uses: github/codeql-action/init@f779452ac5af1c261dce0346a8f964149f49322b # v3.26.13 with: languages: ${{ matrix.language }} config-file: ./.github/codeql.yaml @@ -52,6 +54,6 @@ jobs: run: make build-cli-linux-amd - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@c36620d31ac7c881962c3d9dd939c40ec9434f2b # v3.26.12 + uses: github/codeql-action/analyze@f779452ac5af1c261dce0346a8f964149f49322b # v3.26.13 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/scan-docs-and-schema.yml b/.github/workflows/scan-docs-and-schema.yml index d1228c2fd4..69cb020a28 100644 --- a/.github/workflows/scan-docs-and-schema.yml +++ b/.github/workflows/scan-docs-and-schema.yml @@ -14,7 +14,9 @@ jobs: uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - name: Setup golang - uses: ./.github/actions/golang + uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 + with: + go-version-file: go.mod - name: Docs and schemas run: "make test-docs-and-schema" diff --git a/.github/workflows/scorecard.yaml b/.github/workflows/scorecard.yaml index 4a8ab4470b..98f0a328d8 100644 --- a/.github/workflows/scorecard.yaml +++ b/.github/workflows/scorecard.yaml @@ -44,6 +44,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@c36620d31ac7c881962c3d9dd939c40ec9434f2b # v3.26.12 + uses: github/codeql-action/upload-sarif@f779452ac5af1c261dce0346a8f964149f49322b # v3.26.13 with: sarif_file: results.sarif diff --git a/.github/workflows/test-e2e.yml b/.github/workflows/test-e2e.yml index ae4d719f1f..e3e7ffef6d 100644 --- a/.github/workflows/test-e2e.yml +++ b/.github/workflows/test-e2e.yml @@ -38,7 +38,9 @@ jobs: uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - name: Setup golang - uses: ./.github/actions/golang + uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 + with: + go-version-file: go.mod - name: Build binary and zarf packages uses: ./.github/actions/packages @@ -67,7 +69,9 @@ jobs: path: build/ - name: Setup golang - uses: ./.github/actions/golang + uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 + with: + go-version-file: go.mod - name: Make Zarf executable run: | @@ -102,7 +106,9 @@ jobs: path: build/ - name: Setup golang - uses: ./.github/actions/golang + uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 + with: + go-version-file: go.mod - name: Setup K3d uses: ./.github/actions/k3d @@ -144,7 +150,9 @@ jobs: path: build/ - name: Setup golang - uses: ./.github/actions/golang + uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 + with: + go-version-file: go.mod - name: Make Zarf executable run: | @@ -182,7 +190,9 @@ jobs: path: build/ - name: Setup golang - uses: ./.github/actions/golang + uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 + with: + go-version-file: go.mod - name: Setup Kind run: | @@ -226,7 +236,9 @@ jobs: path: build/ - name: Setup golang - uses: ./.github/actions/golang + uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 + with: + go-version-file: go.mod - name: Setup Minikube run: minikube start --driver=docker diff --git a/.github/workflows/test-external.yml b/.github/workflows/test-external.yml index 1dc1b5b641..10f4c75d7f 100644 --- a/.github/workflows/test-external.yml +++ b/.github/workflows/test-external.yml @@ -37,7 +37,9 @@ jobs: uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - name: Setup golang - uses: ./.github/actions/golang + uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 + with: + go-version-file: go.mod - name: Build binary and zarf packages uses: ./.github/actions/packages diff --git a/.github/workflows/test-import.yaml b/.github/workflows/test-import.yaml index 201eb2f7db..44831ca1a9 100644 --- a/.github/workflows/test-import.yaml +++ b/.github/workflows/test-import.yaml @@ -20,8 +20,7 @@ jobs: - name: Setup Go uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 with: - go-version-file: 'go.mod' - cache: true + go-version-file: go.mod - name: Run test Go program that imports Zarf run: | diff --git a/.github/workflows/test-package-create.yml b/.github/workflows/test-package-create.yml new file mode 100644 index 0000000000..2c4e7e40cb --- /dev/null +++ b/.github/workflows/test-package-create.yml @@ -0,0 +1,48 @@ +name: Test Package Create Checksums + +on: + pull_request: + merge_group: + +permissions: + contents: read + +concurrency: + group: package-create-${{ github.ref }} + cancel-in-progress: true + +jobs: + test-checksums: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 + + - uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 + with: + go-version-file: go.mod + + - name: Build Zarf + run: make build + + - name: Build examples + run: make build-examples ARCH=amd64 + + - name: Compare checksums + run: | + set -e + + for f in hack/examples-checksums/*.txt + do + NAME=$(basename $f .txt) + CHECKSUM=$(tar Oxf build/$NAME.tar.zst checksums.txt | grep -v sboms.tar) + EXPECTED_CHECKSUM=$(cat $f | grep -v sboms.tar) + if [[ "$CHECKSUM" != "$EXPECTED_CHECKSUM" ]] + then + echo "Package $f does not have expected checksum." + echo "$CHECKSUM" + echo "-----" + echo "$EXPECTED_CHECKSUM" + exit 1 + fi + done diff --git a/.github/workflows/test-unit.yml b/.github/workflows/test-unit.yml index cec2d23eda..cc7278db9d 100644 --- a/.github/workflows/test-unit.yml +++ b/.github/workflows/test-unit.yml @@ -41,7 +41,9 @@ jobs: uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - name: Setup golang - uses: ./.github/actions/golang + uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 + with: + go-version-file: go.mod - name: Run unit tests run: make test-unit diff --git a/.github/workflows/test-upgrade.yml b/.github/workflows/test-upgrade.yml index 8922a88cb0..f41e961b83 100644 --- a/.github/workflows/test-upgrade.yml +++ b/.github/workflows/test-upgrade.yml @@ -37,7 +37,9 @@ jobs: uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 - name: Setup golang - uses: ./.github/actions/golang + uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 + with: + go-version-file: go.mod - name: Build PR binary and zarf init package uses: ./.github/actions/packages @@ -66,7 +68,9 @@ jobs: path: build/ - name: Setup golang - uses: ./.github/actions/golang + uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 + with: + go-version-file: go.mod - name: Make Zarf executable run: | diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index 21cca72af2..13a4f43dea 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -36,6 +36,11 @@ jobs: - name: Checkout uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 + - name: Setup golang + uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 + with: + go-version-file: go.mod + - name: Run Windows unit tests run: make test-unit shell: pwsh @@ -46,6 +51,11 @@ jobs: - name: Checkout uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1 + - name: Setup golang + uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 + with: + go-version-file: go.mod + - name: Build Windows binary and zarf packages uses: ./.github/actions/packages with: diff --git a/Makefile b/Makefile index 860fdf2333..9c3ac5ea18 100644 --- a/Makefile +++ b/Makefile @@ -72,9 +72,6 @@ destroy: ## Run `zarf destroy` on the current cluster $(ZARF_BIN) destroy --confirm --remove-components rm -fr build -delete-packages: ## Delete all Zarf package tarballs in the project recursively - find . -type f -name 'zarf-package-*' -not -path '*/testdata/*' -print -delete - # Note: the path to the main.go file is not used due to https://github.com/golang/go/issues/51831#issuecomment-1074188363 .PHONY: build build: ## Build the Zarf CLI for the machines OS and architecture diff --git a/examples/dos-games/zarf.yaml b/examples/dos-games/zarf.yaml index 87042ca58d..f3319f8ea7 100644 --- a/examples/dos-games/zarf.yaml +++ b/examples/dos-games/zarf.yaml @@ -15,15 +15,6 @@ components: - manifests/service.yaml images: - ghcr.io/zarf-dev/doom-game:0.0.1 - actions: - onDeploy: - after: - - wait: - cluster: - kind: deployment - name: game - namespace: dos-games - condition: available # YAML keys starting with `x-` are custom keys that are ignored by the Zarf CLI # The `x-mdx` key is used to render the markdown content for https://docs.zarf.dev/ref/examples diff --git a/examples/helm-charts/zarf.yaml b/examples/helm-charts/zarf.yaml index e5e3757717..dbb2648204 100644 --- a/examples/helm-charts/zarf.yaml +++ b/examples/helm-charts/zarf.yaml @@ -56,33 +56,6 @@ components: - ghcr.io/stefanprodan/podinfo:6.4.0 # This is the cosign signature for the podinfo image for image signature verification - ghcr.io/stefanprodan/podinfo:sha256-57a654ace69ec02ba8973093b6a786faa15640575fbf0dbb603db55aca2ccec8.sig - actions: - onDeploy: - after: - - wait: - cluster: - kind: deployment - name: podinfo-local - namespace: podinfo-from-local-chart - condition: available - - wait: - cluster: - kind: deployment - name: podinfo-oci - namespace: podinfo-from-oci - condition: available - - wait: - cluster: - kind: deployment - name: podinfo-git - namespace: podinfo-from-git - condition: available - - wait: - cluster: - kind: deployment - name: cool-release-name-podinfo - namespace: podinfo-from-repo - condition: available # YAML keys starting with `x-` are custom keys that are ignored by the Zarf CLI # The `x-mdx` key is used to render the markdown content for https://docs.zarf.dev/ref/examples diff --git a/examples/manifests/zarf.yaml b/examples/manifests/zarf.yaml index 092f6ca684..7855f0a02b 100644 --- a/examples/manifests/zarf.yaml +++ b/examples/manifests/zarf.yaml @@ -12,17 +12,6 @@ components: files: # local manifests are specified relative to the `zarf.yaml` that uses them: - httpd-deployment.yaml - actions: - onDeploy: - # the following checks were computed by viewing the success state of the package deployment - # and creating `wait` actions that match - after: - - wait: - cluster: - kind: deployment - name: httpd-deployment - namespace: httpd - condition: "{.status.readyReplicas}=2" # image discovery is supported in all manifests and charts using: # zarf prepare find-images images: @@ -38,17 +27,6 @@ components: - https://k8s.io/examples/application/deployment.yaml@c57f73449b26eae02ca2a549c388807d49ef6d3f2dc040a9bbb1290128d97157 # this sha256 can be discovered using: # zarf prepare sha256sum https://k8s.io/examples/application/deployment.yaml - actions: - onDeploy: - # the following checks were computed by viewing the success state of the package deployment - # and creating `wait` actions that match - after: - - wait: - cluster: - kind: deployment - name: nginx-deployment - namespace: nginx - condition: available # image discovery is supported in all manifests and charts using: # zarf prepare find-images images: @@ -64,17 +42,6 @@ components: - github.com/stefanprodan/podinfo//kustomize?ref=6.4.0 # while ?ref= is not a requirement, it is recommended to use a specific commit hash / git tag to # ensure that the kustomization is not changed in a way that breaks your deployment. - actions: - onDeploy: - # the following checks were computed by viewing the success state of the package deployment - # and creating `wait` actions that match - after: - - wait: - cluster: - kind: deployment - name: podinfo - namespace: podinfo - condition: available # image discovery is supported in all manifests and charts using: # zarf prepare find-images images: diff --git a/examples/podinfo-flux/git/podinfo-kustomization.yaml b/examples/podinfo-flux/git/podinfo-kustomization.yaml index aa251f98ce..bc72ee0aa0 100644 --- a/examples/podinfo-flux/git/podinfo-kustomization.yaml +++ b/examples/podinfo-flux/git/podinfo-kustomization.yaml @@ -12,3 +12,4 @@ spec: kind: GitRepository name: podinfo targetNamespace: podinfo-git + wait: true diff --git a/examples/podinfo-flux/oci/podinfo-kustomization.yaml b/examples/podinfo-flux/oci/podinfo-kustomization.yaml index 57f290e7b6..7acd41f327 100644 --- a/examples/podinfo-flux/oci/podinfo-kustomization.yaml +++ b/examples/podinfo-flux/oci/podinfo-kustomization.yaml @@ -12,3 +12,4 @@ spec: kind: OCIRepository name: podinfo targetNamespace: podinfo-oci + wait: true diff --git a/examples/podinfo-flux/zarf.yaml b/examples/podinfo-flux/zarf.yaml index 6e94a715d0..16b25e3e59 100644 --- a/examples/podinfo-flux/zarf.yaml +++ b/examples/podinfo-flux/zarf.yaml @@ -33,16 +33,6 @@ components: - https://github.com/stefanprodan/podinfo.git images: - ghcr.io/stefanprodan/podinfo:6.4.0 - actions: - onDeploy: - after: - - description: Podinfo pods to be ready via wait action - wait: - cluster: - kind: pod - name: app=podinfo - namespace: podinfo-git - condition: ready - name: podinfo-via-flux-helm description: Example deployment via flux (helm oci) using the famous podinfo example @@ -57,16 +47,6 @@ components: - ghcr.io/stefanprodan/podinfo:6.4.0 # Note: this is a helm OCI artifact rather than a container image - ghcr.io/stefanprodan/charts/podinfo:6.4.0 - actions: - onDeploy: - after: - - description: Podinfo pods to be ready via wait action - wait: - cluster: - kind: pod - name: app.kubernetes.io/name=podinfo - namespace: podinfo-helm - condition: ready - name: podinfo-via-flux-oci description: Example deployment via flux (native oci) using the famous podinfo example @@ -81,17 +61,6 @@ components: - ghcr.io/stefanprodan/podinfo:6.4.0 # Note: this is a flux kustomize OCI artifact rather than a container image - ghcr.io/stefanprodan/manifests/podinfo:6.4.0 - actions: - onDeploy: - after: - # This will use a wait action to wait for the podinfo pod to be ready - - description: Podinfo pods to be ready via wait action - wait: - cluster: - kind: pod - name: app=podinfo - namespace: podinfo-oci - condition: ready # YAML keys starting with `x-` are custom keys that are ignored by the Zarf CLI # The `x-mdx` key is used to render the markdown content for https://docs.zarf.dev/ref/examples diff --git a/examples/variables/zarf.yaml b/examples/variables/zarf.yaml index df2873b1c4..e180acdaa8 100644 --- a/examples/variables/zarf.yaml +++ b/examples/variables/zarf.yaml @@ -81,15 +81,6 @@ components: - nginx-configmap.yaml - nginx-deployment.yaml - nginx-service.yaml - actions: - onDeploy: - after: - - wait: - cluster: - kind: pod - namespace: nginx - name: app=nginx - condition: Ready # YAML keys starting with `x-` are custom keys that are ignored by the Zarf CLI # The `x-mdx` key is used to render the markdown content for https://docs.zarf.dev/ref/examples diff --git a/examples/yolo/zarf.yaml b/examples/yolo/zarf.yaml index dfa4cf65f1..71a3f7aa95 100644 --- a/examples/yolo/zarf.yaml +++ b/examples/yolo/zarf.yaml @@ -13,15 +13,6 @@ components: files: - ../dos-games/manifests/deployment.yaml - ../dos-games/manifests/service.yaml - actions: - onDeploy: - after: - - wait: - cluster: - kind: deployment - name: game - namespace: zarf-yolo-example - condition: available # YAML keys starting with `x-` are custom keys that are ignored by the Zarf CLI # The `x-mdx` key is used to render the markdown content for https://docs.zarf.dev/ref/examples diff --git a/go.mod b/go.mod index 75c52d5f4f..487438bc8c 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,6 @@ require ( github.com/anchore/syft v1.14.0 github.com/avast/retry-go/v4 v4.6.0 github.com/defenseunicorns/pkg/helpers/v2 v2.0.1 - github.com/defenseunicorns/pkg/kubernetes v0.3.0 github.com/defenseunicorns/pkg/oci v1.0.2 github.com/derailed/k9s v0.32.5 github.com/distribution/distribution/v3 v3.0.0-beta.1 @@ -556,7 +555,7 @@ require ( modernc.org/memory v1.8.0 // indirect modernc.org/sqlite v1.33.1 // indirect oras.land/oras-go v1.2.5 // indirect - sigs.k8s.io/controller-runtime v0.19.0 // indirect + sigs.k8s.io/controller-runtime v0.19.0 sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/kustomize/kustomize/v5 v5.4.2 // indirect sigs.k8s.io/release-utils v0.8.4 // indirect diff --git a/go.sum b/go.sum index f1f84685d0..73cfde145c 100644 --- a/go.sum +++ b/go.sum @@ -621,8 +621,6 @@ github.com/defenseunicorns/gojsonschema v0.0.0-20231116163348-e00f069122d6 h1:gw github.com/defenseunicorns/gojsonschema v0.0.0-20231116163348-e00f069122d6/go.mod h1:StKLYMmPj1R5yIs6CK49EkcW1TvUYuw5Vri+LRk7Dy8= github.com/defenseunicorns/pkg/helpers/v2 v2.0.1 h1:j08rz9vhyD9Bs+yKiyQMY2tSSejXRMxTqEObZ5M1Wbk= github.com/defenseunicorns/pkg/helpers/v2 v2.0.1/go.mod h1:u1PAqOICZyiGIVA2v28g55bQH1GiAt0Bc4U9/rnWQvQ= -github.com/defenseunicorns/pkg/kubernetes v0.3.0 h1:f4VSIaUdvn87/dhiZvRbUfHhcHa8bKia6aU0WcvPbYg= -github.com/defenseunicorns/pkg/kubernetes v0.3.0/go.mod h1:FsuKQGpPZOnZWifBse7v787+avtIu2lte5LTsaojDkY= github.com/defenseunicorns/pkg/oci v1.0.2 h1:JRdFbKnJQiGVsMUWmcmm0ZS8aBmmAORXLGSAGkIGhBQ= github.com/defenseunicorns/pkg/oci v1.0.2/go.mod h1:z11UFenAd4HQRucaEp0uhoccor/6zbQiXEQq+Z7vtI0= github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da h1:ZOjWpVsFZ06eIhnh4mkaceTiVoktdU67+M7KDHJ268M= diff --git a/hack/examples-checksums/zarf-package-component-actions-amd64.txt b/hack/examples-checksums/zarf-package-component-actions-amd64.txt new file mode 100644 index 0000000000..24913a26dd --- /dev/null +++ b/hack/examples-checksums/zarf-package-component-actions-amd64.txt @@ -0,0 +1,3 @@ +cfd67a2aeebf13c632207183e1d5cd57146a28aa22b5ddf8896c6ce593c627c2 components/on-deploy-with-wait-action.tar +d26e18c92ff9f93b296fad6be153f02d4a746f0ec44dbcf4c526f1e121ffe4ec components/on-remove.tar +fbfcd7215652819a3b03084d2a12ca7594d8c610ac6e2ebed2f229a0019a52e2 components/on-deploy-with-template-use-of-variable.tar diff --git a/hack/examples-checksums/zarf-package-component-choice-amd64.txt b/hack/examples-checksums/zarf-package-component-choice-amd64.txt new file mode 100644 index 0000000000..ecce58903a --- /dev/null +++ b/hack/examples-checksums/zarf-package-component-choice-amd64.txt @@ -0,0 +1,2 @@ +fa862d90928e52ccc9faa69b5eae73bb97fa5acb76407ba6a177eb338f7d692d components/first-choice.tar +fb99083f2881d87f556c1a7a163876aeaaf1e6094526ff50b2775fc85f8858a3 components/second-choice.tar diff --git a/hack/examples-checksums/zarf-package-dos-games-amd64-1.1.0.txt b/hack/examples-checksums/zarf-package-dos-games-amd64-1.1.0.txt new file mode 100644 index 0000000000..e0a3ce3493 --- /dev/null +++ b/hack/examples-checksums/zarf-package-dos-games-amd64-1.1.0.txt @@ -0,0 +1,8 @@ +0a44b759e219d9d6f3c7cbbf40c57ede71a1f9bf54da65767c4137be74727662 images/blobs/sha256/0a44b759e219d9d6f3c7cbbf40c57ede71a1f9bf54da65767c4137be74727662 +4752b809555b8767401dfd39638f256c2373763a1f2cc421012301bb48481e6d images/blobs/sha256/4752b809555b8767401dfd39638f256c2373763a1f2cc421012301bb48481e6d +49f63464352d7b53ceea3b60b9cc764c1a810b28217ee9f13ab7f974bb3ed968 components/baseline.tar +4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1 images/blobs/sha256/4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1 +b66dbb27a73334db6ac9c030475837bd7f4472d835c72b2360534b203edce6cb images/oci-layout +c7ee390ce7fc9b525429312617f5fbff5fe195544c1d95b44b72330bff15a615 images/index.json +cc1421ef2ded4a559feaefe8dc266488d60675fcc995db7e51f0b0a6d893e010 images/blobs/sha256/cc1421ef2ded4a559feaefe8dc266488d60675fcc995db7e51f0b0a6d893e010 +e6886dc0b01f09d19480a3270fd9e7c4b261346cee9490b881b36edf21c7e722 images/blobs/sha256/e6886dc0b01f09d19480a3270fd9e7c4b261346cee9490b881b36edf21c7e722 diff --git a/hack/examples-checksums/zarf-package-manifests-amd64-0.0.1.txt b/hack/examples-checksums/zarf-package-manifests-amd64-0.0.1.txt new file mode 100644 index 0000000000..3553f82efa --- /dev/null +++ b/hack/examples-checksums/zarf-package-manifests-amd64-0.0.1.txt @@ -0,0 +1,27 @@ +0f23e58bd0b7c74311703e20c21c690a6847e62240ed456f8821f4c067d3659b images/blobs/sha256/0f23e58bd0b7c74311703e20c21c690a6847e62240ed456f8821f4c067d3659b +12cba3a8e34081029e840e7ac5454c080835cbc5a7adc1620482e939283a3a49 images/blobs/sha256/12cba3a8e34081029e840e7ac5454c080835cbc5a7adc1620482e939283a3a49 +27833a3ba0a545deda33bb01eaf95a14d05d43bf30bce9267d92d17f069fe897 images/blobs/sha256/27833a3ba0a545deda33bb01eaf95a14d05d43bf30bce9267d92d17f069fe897 +27e17b7ec145d38d0be7b5837639a1206f2f3902f7831a6060d0b897f144decd images/index.json +295c7be079025306c4f1d65997fcf7adb411c88f139ad1d34b537164aa060369 images/blobs/sha256/295c7be079025306c4f1d65997fcf7adb411c88f139ad1d34b537164aa060369 +3a96ca29c7fb133e78765557b2bf29a257467f679c43e4153ad05bcde8a1ce3d images/blobs/sha256/3a96ca29c7fb133e78765557b2bf29a257467f679c43e4153ad05bcde8a1ce3d +45ef08258efc940f6336384ae1f35224b5bdf89a3b7abbb5effcbb6c5d62cabe components/nginx-remote.tar +489db2792d7fc3ed75b6970b2e0e73f782bd5c0ed2462ddd683ae92cce04cdb6 images/blobs/sha256/489db2792d7fc3ed75b6970b2e0e73f782bd5c0ed2462ddd683ae92cce04cdb6 +4db1b89c0bd13344176ddce2d093b9da2ae58336823ffed2009a7ea4b62d2a95 images/blobs/sha256/4db1b89c0bd13344176ddce2d093b9da2ae58336823ffed2009a7ea4b62d2a95 +4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1 images/blobs/sha256/4f4fb700ef54461cfa02571ae0db9a0dc1e0cdb5577484a6d75e68dc38e8acc1 +706446e9c6667c0880d5da3f39c09a6c7d2114f5a5d6b74a2fafd24ae30d2078 images/blobs/sha256/706446e9c6667c0880d5da3f39c09a6c7d2114f5a5d6b74a2fafd24ae30d2078 +8ca774778e858d3f97d9ec1bec1de879ac5e10096856dc22ed325a3ad944f78a images/blobs/sha256/8ca774778e858d3f97d9ec1bec1de879ac5e10096856dc22ed325a3ad944f78a +92974acd1b7d5aec7654a2df3a310f97c56b7449fc5d042ba8442dbace9a0da6 images/blobs/sha256/92974acd1b7d5aec7654a2df3a310f97c56b7449fc5d042ba8442dbace9a0da6 +9926d2e1a82b13f28b6b9c720bb6947b34c8eabc943de113a3fbc8fabee82d94 images/blobs/sha256/9926d2e1a82b13f28b6b9c720bb6947b34c8eabc943de113a3fbc8fabee82d94 +9b61d3667e8d8d1d8f14ebb413c1ac3fe62373fd69af6aafb281b7a8733f50aa images/blobs/sha256/9b61d3667e8d8d1d8f14ebb413c1ac3fe62373fd69af6aafb281b7a8733f50aa +ae8092b154d705e09bc77523083da3e93200a476ae3aa2b7a5e1747b1cbb8fef images/blobs/sha256/ae8092b154d705e09bc77523083da3e93200a476ae3aa2b7a5e1747b1cbb8fef +b4cd0df67c961ba7f49c86c2e1e6e89d2fd1b8c40ad6fe59508db060dfac51fe images/blobs/sha256/b4cd0df67c961ba7f49c86c2e1e6e89d2fd1b8c40ad6fe59508db060dfac51fe +b66dbb27a73334db6ac9c030475837bd7f4472d835c72b2360534b203edce6cb images/oci-layout +b9c1296647242c2c9c7ffe8cc3a1b9ecde558e8748969ad6a64428ab5922769a images/blobs/sha256/b9c1296647242c2c9c7ffe8cc3a1b9ecde558e8748969ad6a64428ab5922769a +c398742ba22c44f9bbc08dcbbdf0c978b20928fde49dceacded095bc09a46b84 images/blobs/sha256/c398742ba22c44f9bbc08dcbbdf0c978b20928fde49dceacded095bc09a46b84 +c926b61bad3b94ae7351bafd0c184c159ebf0643b085f7ef1d47ecdc7316833c images/blobs/sha256/c926b61bad3b94ae7351bafd0c184c159ebf0643b085f7ef1d47ecdc7316833c +cadc8652ff5abccc918746eb742e7b9165a48428b2c8cc6a48eb6ce782ce5405 images/blobs/sha256/cadc8652ff5abccc918746eb742e7b9165a48428b2c8cc6a48eb6ce782ce5405 +d37d27b92cce4fb1383d5fbe32540382ea3d9662c7be3555f5a0f6a044099e1b images/blobs/sha256/d37d27b92cce4fb1383d5fbe32540382ea3d9662c7be3555f5a0f6a044099e1b +d8173b5b3d825c1c19acf91cb66599f453187705ca9cdb4608d7be5482768cba images/blobs/sha256/d8173b5b3d825c1c19acf91cb66599f453187705ca9cdb4608d7be5482768cba +d95fa8da986254bcd64c1251b695fe91875383dac1ed1780480fdf70f02cea3b images/blobs/sha256/d95fa8da986254bcd64c1251b695fe91875383dac1ed1780480fdf70f02cea3b +f55cf5db16c790710ce2cd7b3d4fa00db89bdeea9d516aa83a596e910de103b2 components/podinfo-kustomize.tar +f59dcac0742ce66d707aed956c25cd0fc20d162ecaca308637197eac1cef13fc components/httpd-local.tar diff --git a/hack/examples-checksums/zarf-package-variables-amd64.txt b/hack/examples-checksums/zarf-package-variables-amd64.txt new file mode 100644 index 0000000000..a0fdb11006 --- /dev/null +++ b/hack/examples-checksums/zarf-package-variables-amd64.txt @@ -0,0 +1,12 @@ +1ff0f94a80076ab49af75159e23f062a30a75d333a8e9c021bf39669230afcfe images/blobs/sha256/1ff0f94a80076ab49af75159e23f062a30a75d333a8e9c021bf39669230afcfe +291f5d3c8c1742164379dfd09b17eeec4f70bcb165773d65d450dec5ef94d907 images/index.json +4b2a24be75c4766f2d20892ddb84841e3773d0e26249ee57eed530da19c07bb2 components/variables-with-nginx.tar +557c9ede65655e5a70e4a32f1651638ea3bfb0802edd982810884602f700ba25 images/blobs/sha256/557c9ede65655e5a70e4a32f1651638ea3bfb0802edd982810884602f700ba25 +84181e80d10e844350789d3324e848cf728df4f3d0f6c978789dd489f493934a images/blobs/sha256/84181e80d10e844350789d3324e848cf728df4f3d0f6c978789dd489f493934a +a8a737eacb28af35791c2a444d8095ca3d493ba31eca78cd57a6fe3cced79154 components/variables-with-terraform.tar +ac232364af842735579e922641ae2f67d5b8ea97df33a207c5ea05f60c63a92d images/blobs/sha256/ac232364af842735579e922641ae2f67d5b8ea97df33a207c5ea05f60c63a92d +b66dbb27a73334db6ac9c030475837bd7f4472d835c72b2360534b203edce6cb images/oci-layout +d4ceccbfc2696101c94fbf2149036e4ff815e4723e518721ff85105ce5aa8afc images/blobs/sha256/d4ceccbfc2696101c94fbf2149036e4ff815e4723e518721ff85105ce5aa8afc +d776269cad101c9f8e33e2baa0a05993ed0786604d86ea525f62d5d7ae7b9540 images/blobs/sha256/d776269cad101c9f8e33e2baa0a05993ed0786604d86ea525f62d5d7ae7b9540 +e9427fcfa8642f8ddf5106f742a75eca0dbac676cf8145598623d04fa45dd74e images/blobs/sha256/e9427fcfa8642f8ddf5106f742a75eca0dbac676cf8145598623d04fa45dd74e +f1f26f5702560b7e591bef5c4d840f76a232bf13fd5aefc4e22077a1ae4440c7 images/blobs/sha256/f1f26f5702560b7e591bef5c4d840f76a232bf13fd5aefc4e22077a1ae4440c7 diff --git a/hack/examples-checksums/zarf-package-yolo-amd64.txt b/hack/examples-checksums/zarf-package-yolo-amd64.txt new file mode 100644 index 0000000000..e925a4d3d9 --- /dev/null +++ b/hack/examples-checksums/zarf-package-yolo-amd64.txt @@ -0,0 +1 @@ +b31cd4195a94c235f6560274fac9efb9934c517381d16593592d02f212e1cd70 components/yolo-games.tar diff --git a/packages/gitea/zarf.yaml b/packages/gitea/zarf.yaml index 2f59bebdba..6a51afe161 100644 --- a/packages/gitea/zarf.yaml +++ b/packages/gitea/zarf.yaml @@ -71,12 +71,6 @@ components: - name: GIT_SERVER_CREATE_PVC mute: true after: - - wait: - cluster: - kind: pod - namespace: zarf - name: app=gitea - condition: Ready - cmd: ./zarf internal create-read-only-gitea-user --no-progress maxRetries: 3 maxTotalSeconds: 60 diff --git a/packages/zarf-agent/zarf.yaml b/packages/zarf-agent/zarf.yaml index 65ee63170f..1e1dac492f 100644 --- a/packages/zarf-agent/zarf.yaml +++ b/packages/zarf-agent/zarf.yaml @@ -40,11 +40,3 @@ components: windows: pwsh dir: ../.. description: Build the local agent image (if 'AGENT_IMAGE_TAG' was specified as 'local') - onDeploy: - after: - - wait: - cluster: - kind: pod - namespace: zarf - name: app=agent-hook - condition: Ready diff --git a/packages/zarf-registry/chart/templates/deployment.yaml b/packages/zarf-registry/chart/templates/deployment.yaml index e0e878eb82..f4263ca731 100644 --- a/packages/zarf-registry/chart/templates/deployment.yaml +++ b/packages/zarf-registry/chart/templates/deployment.yaml @@ -33,8 +33,11 @@ spec: {{- end }} priorityClassName: system-node-critical securityContext: - fsGroup: 1000 runAsUser: 1000 + fsGroup: 2000 + runAsGroup: 2000 + seccompProfile: + type: "RuntimeDefault" containers: - name: {{ .Chart.Name }} image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" @@ -53,6 +56,12 @@ spec: httpGet: path: / port: 5000 + securityContext: + readOnlyRootFilesystem: true + allowPrivilegeEscalation: false + runAsNonRoot: true + capabilities: + drop: ["ALL"] resources: {{ toYaml .Values.resources | indent 12 }} env: diff --git a/packages/zarf-registry/zarf.yaml b/packages/zarf-registry/zarf.yaml index 190eeba4c0..2a5b60dbed 100644 --- a/packages/zarf-registry/zarf.yaml +++ b/packages/zarf-registry/zarf.yaml @@ -171,12 +171,3 @@ components: images: # This image (or images) must match that used for injection (see zarf-config.toml) - "###ZARF_PKG_TMPL_REGISTRY_IMAGE_DOMAIN######ZARF_PKG_TMPL_REGISTRY_IMAGE###:###ZARF_PKG_TMPL_REGISTRY_IMAGE_TAG###" - actions: - onDeploy: - after: - - wait: - cluster: - kind: deployment - namespace: zarf - name: app=docker-registry - condition: Available diff --git a/site/src/content/docs/ref/init-package.mdx b/site/src/content/docs/ref/init-package.mdx index db35cd2330..a155549e9a 100644 --- a/site/src/content/docs/ref/init-package.mdx +++ b/site/src/content/docs/ref/init-package.mdx @@ -32,8 +32,8 @@ View all init options w/ [`zarf init --help`](/commands/zarf_init/). An 'init' package requires a series of specially named, and configured components to ensure the cluster is correctly initialized. These components are: -- [`zarf-injector`](#zarf-injector) -- [`zarf-seed-registry`](#zarf-seed-registry) +- [`zarf-injector`](#zarf-injector-and-zarf-seed-registry) +- [`zarf-seed-registry`](#zarf-injector-and-zarf-seed-registry) - [`zarf-registry`](#zarf-registry) - [`zarf-agent`](#zarf-agent) diff --git a/src/internal/healthchecks/healthchecks.go b/src/internal/healthchecks/healthchecks.go index 7957953378..447ddb81cf 100644 --- a/src/internal/healthchecks/healthchecks.go +++ b/src/internal/healthchecks/healthchecks.go @@ -6,15 +6,21 @@ package healthchecks import ( "context" + "errors" + "fmt" - pkgkubernetes "github.com/defenseunicorns/pkg/kubernetes" "github.com/zarf-dev/zarf/src/api/v1alpha1" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/cli-utils/pkg/kstatus/polling/aggregator" + "sigs.k8s.io/cli-utils/pkg/kstatus/polling/collector" + "sigs.k8s.io/cli-utils/pkg/kstatus/polling/event" + "sigs.k8s.io/cli-utils/pkg/kstatus/status" "sigs.k8s.io/cli-utils/pkg/kstatus/watcher" "sigs.k8s.io/cli-utils/pkg/object" ) -// Run waits for a list of objects to be reconciled +// Run waits for a list of Zarf healthchecks to reach a ready state. func Run(ctx context.Context, watcher watcher.StatusWatcher, healthChecks []v1alpha1.NamespacedObjectKindReference) error { objs := []object.ObjMetadata{} for _, hc := range healthChecks { @@ -32,9 +38,99 @@ func Run(ctx context.Context, watcher watcher.StatusWatcher, healthChecks []v1al } objs = append(objs, obj) } - err := pkgkubernetes.WaitForReady(ctx, watcher, objs) + err := WaitForReady(ctx, watcher, objs) if err != nil { return err } return nil } + +// WaitForReadyRuntime waits for all of the objects to reach a ready state. +func WaitForReadyRuntime(ctx context.Context, sw watcher.StatusWatcher, robjs []runtime.Object) error { + objs := []object.ObjMetadata{} + for _, robj := range robjs { + obj, err := object.RuntimeToObjMeta(robj) + if err != nil { + return err + } + objs = append(objs, obj) + } + return WaitForReady(ctx, sw, objs) +} + +// WaitForReady waits for all of the objects to reach a ready state. +func WaitForReady(ctx context.Context, sw watcher.StatusWatcher, objs []object.ObjMetadata) error { + cancelCtx, cancel := context.WithCancel(ctx) + defer cancel() + + eventCh := sw.Watch(cancelCtx, objs, watcher.Options{}) + statusCollector := collector.NewResourceStatusCollector(objs) + done := statusCollector.ListenWithObserver(eventCh, collector.ObserverFunc( + func(statusCollector *collector.ResourceStatusCollector, _ event.Event) { + rss := []*event.ResourceStatus{} + for _, rs := range statusCollector.ResourceStatuses { + if rs == nil { + continue + } + rss = append(rss, rs) + } + desired := status.CurrentStatus + if aggregator.AggregateStatus(rss, desired) == desired { + cancel() + return + } + }), + ) + <-done + + if statusCollector.Error != nil { + return statusCollector.Error + } + + // Only check parent context error, otherwise we would error when desired status is achieved. + if ctx.Err() != nil { + errs := []error{} + for _, id := range objs { + rs := statusCollector.ResourceStatuses[id] + switch rs.Status { + case status.CurrentStatus: + case status.NotFoundStatus: + errs = append(errs, fmt.Errorf("%s: %s not found", rs.Identifier.Name, rs.Identifier.GroupKind.Kind)) + default: + errs = append(errs, fmt.Errorf("%s: %s not ready", rs.Identifier.Name, rs.Identifier.GroupKind.Kind)) + } + } + errs = append(errs, ctx.Err()) + return errors.Join(errs...) + } + + return nil +} + +// ImmediateWatcher should only be used for testing and returns the set status immediately. +type ImmediateWatcher struct { + status status.Status +} + +// NewImmediateWatcher returns a ImmediateWatcher. +func NewImmediateWatcher(status status.Status) *ImmediateWatcher { + return &ImmediateWatcher{ + status: status, + } +} + +// Watch watches the given objects and immediately returns the configured status. +func (w *ImmediateWatcher) Watch(_ context.Context, objs object.ObjMetadataSet, _ watcher.Options) <-chan event.Event { + eventCh := make(chan event.Event, len(objs)) + for _, obj := range objs { + eventCh <- event.Event{ + Type: event.ResourceUpdateEvent, + Resource: &event.ResourceStatus{ + Identifier: obj, + Status: w.status, + }, + } + } + close(eventCh) + return eventCh +} diff --git a/src/internal/healthchecks/healthchecks_test.go b/src/internal/healthchecks/healthchecks_test.go index 9761f7ba84..d52d0d2ff9 100644 --- a/src/internal/healthchecks/healthchecks_test.go +++ b/src/internal/healthchecks/healthchecks_test.go @@ -6,6 +6,7 @@ package healthchecks import ( "context" + "errors" "testing" "time" @@ -45,19 +46,19 @@ metadata: func TestRunHealthChecks(t *testing.T) { t.Parallel() tests := []struct { - name string - podYaml string - expectErr error + name string + podYamls []string + expectErrs []error }{ { - name: "Pod is running", - podYaml: podCurrentYaml, - expectErr: nil, + name: "Pod is ready", + podYamls: []string{podCurrentYaml}, + expectErrs: nil, }, { - name: "Pod is never ready", - podYaml: podYaml, - expectErr: context.DeadlineExceeded, + name: "One pod is never ready", + podYamls: []string{podYaml, podCurrentYaml}, + expectErrs: []error{errors.New("in-progress-pod: Pod not ready"), context.DeadlineExceeded}, }, } @@ -70,24 +71,27 @@ func TestRunHealthChecks(t *testing.T) { ) ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond) defer cancel() - m := make(map[string]interface{}) - err := yaml.Unmarshal([]byte(tt.podYaml), &m) - require.NoError(t, err) - pod := &unstructured.Unstructured{Object: m} statusWatcher := watcher.NewDefaultStatusWatcher(fakeClient, fakeMapper) - podGVR := schema.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"} - require.NoError(t, fakeClient.Tracker().Create(podGVR, pod, pod.GetNamespace())) - objs := []v1alpha1.NamespacedObjectKindReference{ - { + objs := []v1alpha1.NamespacedObjectKindReference{} + for _, podYaml := range tt.podYamls { + m := make(map[string]interface{}) + err := yaml.Unmarshal([]byte(podYaml), &m) + require.NoError(t, err) + pod := &unstructured.Unstructured{Object: m} + podGVR := schema.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"} + err = fakeClient.Tracker().Create(podGVR, pod, pod.GetNamespace()) + require.NoError(t, err) + objs = append(objs, v1alpha1.NamespacedObjectKindReference{ APIVersion: pod.GetAPIVersion(), Kind: pod.GetKind(), Namespace: pod.GetNamespace(), Name: pod.GetName(), - }, + }) } - err = Run(ctx, statusWatcher, objs) - if tt.expectErr != nil { - require.ErrorIs(t, err, tt.expectErr) + + err := Run(ctx, statusWatcher, objs) + if tt.expectErrs != nil { + require.EqualError(t, err, errors.Join(tt.expectErrs...).Error()) return } require.NoError(t, err) diff --git a/src/internal/packager/helm/chart.go b/src/internal/packager/helm/chart.go index 6009d5f9bc..954b1d6144 100644 --- a/src/internal/packager/helm/chart.go +++ b/src/internal/packager/helm/chart.go @@ -23,9 +23,9 @@ import ( "helm.sh/helm/v3/pkg/releaseutil" "helm.sh/helm/v3/pkg/storage/driver" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" "sigs.k8s.io/yaml" - "github.com/zarf-dev/zarf/src/api/v1alpha1" "github.com/zarf-dev/zarf/src/config" "github.com/zarf-dev/zarf/src/internal/healthchecks" "github.com/zarf-dev/zarf/src/pkg/message" @@ -129,20 +129,14 @@ func (h *Helm) InstallOrUpgradeChart(ctx context.Context) (types.ConnectStrings, return nil, "", fmt.Errorf("unable to build the resource list: %w", err) } - healthChecks := []v1alpha1.NamespacedObjectKindReference{} + runtimeObjs := []runtime.Object{} for _, resource := range resourceList { - apiVersion, kind := resource.Object.GetObjectKind().GroupVersionKind().ToAPIVersionAndKind() - healthChecks = append(healthChecks, v1alpha1.NamespacedObjectKindReference{ - APIVersion: apiVersion, - Kind: kind, - Name: resource.Name, - Namespace: resource.Namespace, - }) + runtimeObjs = append(runtimeObjs, resource.Object) } if !h.chart.NoWait { // Ensure we don't go past the timeout by using a context initialized with the helm timeout spinner.Updatef("Running health checks") - if err := healthchecks.Run(helmCtx, h.cluster.Watcher, healthChecks); err != nil { + if err := healthchecks.WaitForReadyRuntime(helmCtx, h.cluster.Watcher, runtimeObjs); err != nil { return nil, "", err } } diff --git a/src/internal/packager/helm/zarf.go b/src/internal/packager/helm/zarf.go index 03e4db2ed0..b6945a6e8c 100644 --- a/src/internal/packager/helm/zarf.go +++ b/src/internal/packager/helm/zarf.go @@ -13,11 +13,10 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "sigs.k8s.io/cli-utils/pkg/object" - pkgkubernetes "github.com/defenseunicorns/pkg/kubernetes" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/zarf-dev/zarf/src/api/v1alpha1" + "github.com/zarf-dev/zarf/src/internal/healthchecks" "github.com/zarf-dev/zarf/src/internal/packager/template" "github.com/zarf-dev/zarf/src/pkg/cluster" "github.com/zarf-dev/zarf/src/pkg/message" @@ -61,7 +60,7 @@ func (h *Helm) UpdateZarfRegistryValues(ctx context.Context) error { } waitCtx, waitCancel := context.WithTimeout(ctx, 60*time.Second) defer waitCancel() - err = pkgkubernetes.WaitForReady(waitCtx, h.cluster.Watcher, objs) + err = healthchecks.WaitForReady(waitCtx, h.cluster.Watcher, objs) if err != nil { return err } @@ -157,7 +156,7 @@ func (h *Helm) UpdateZarfAgentValues(ctx context.Context) error { } waitCtx, waitCancel := context.WithTimeout(ctx, 60*time.Second) defer waitCancel() - err = pkgkubernetes.WaitForReady(waitCtx, h.cluster.Watcher, objs) + err = healthchecks.WaitForReady(waitCtx, h.cluster.Watcher, objs) if err != nil { return err } diff --git a/src/internal/packager/images/common.go b/src/internal/packager/images/common.go index 285c541edb..3d5137a995 100644 --- a/src/internal/packager/images/common.go +++ b/src/internal/packager/images/common.go @@ -13,7 +13,6 @@ import ( "github.com/google/go-containerregistry/pkg/crane" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/zarf-dev/zarf/src/config" - "github.com/zarf-dev/zarf/src/pkg/message" "github.com/zarf-dev/zarf/src/pkg/transform" "github.com/zarf-dev/zarf/src/types" ) @@ -98,18 +97,18 @@ func WithPushAuth(ri types.RegistryInfo) crane.Option { return WithBasicAuth(ri.PushUsername, ri.PushPassword) } -func createPushOpts(cfg PushConfig, pb *message.ProgressBar) []crane.Option { +func createPushOpts(cfg PushConfig) []crane.Option { opts := CommonOpts(cfg.Arch) opts = append(opts, WithPushAuth(cfg.RegInfo)) - transport := http.DefaultTransport.(*http.Transport).Clone() - transport.TLSClientConfig.InsecureSkipVerify = config.CommonOptions.InsecureSkipTLSVerify + defaultTransport := http.DefaultTransport.(*http.Transport).Clone() + defaultTransport.TLSClientConfig.InsecureSkipVerify = config.CommonOptions.InsecureSkipTLSVerify // TODO (@WSTARR) This is set to match the TLSHandshakeTimeout to potentially mitigate effects of https://github.com/zarf-dev/zarf/issues/1444 - transport.ResponseHeaderTimeout = 10 * time.Second + defaultTransport.ResponseHeaderTimeout = 10 * time.Second - transportWithProgressBar := helpers.NewTransport(transport, pb) + transport := helpers.NewTransport(defaultTransport, nil) - opts = append(opts, crane.WithTransport(transportWithProgressBar)) + opts = append(opts, crane.WithTransport(transport)) return opts } diff --git a/src/internal/packager/images/push.go b/src/internal/packager/images/push.go index f8cbf90c4e..e04c628023 100644 --- a/src/internal/packager/images/push.go +++ b/src/internal/packager/images/push.go @@ -6,11 +6,9 @@ package images import ( "context" - "fmt" "time" "github.com/avast/retry-go/v4" - "github.com/defenseunicorns/pkg/helpers/v2" "github.com/google/go-containerregistry/pkg/crane" "github.com/google/go-containerregistry/pkg/logs" v1 "github.com/google/go-containerregistry/pkg/v1" @@ -27,7 +25,6 @@ func Push(ctx context.Context, cfg PushConfig) error { logs.Progress.SetOutput(&message.DebugWriter{}) toPush := map[transform.Image]v1.Image{} - var totalSize int64 // Build an image list from the references for _, refInfo := range cfg.ImageList { img, err := utils.LoadOCIImage(cfg.SourceDirectory, refInfo) @@ -35,16 +32,6 @@ func Push(ctx context.Context, cfg PushConfig) error { return err } toPush[refInfo] = img - imgSize, err := calcImgSize(img) - if err != nil { - return err - } - totalSize += imgSize - } - - // If this is not a no checksum image push we will be pushing two images (the second will go faster as it checks the same layers) - if !cfg.NoChecksum { - totalSize = totalSize * 2 } var ( @@ -52,7 +39,6 @@ func Push(ctx context.Context, cfg PushConfig) error { tunnel *cluster.Tunnel registryURL = cfg.RegInfo.Address ) - err = retry.Do(func() error { c, _ := cluster.NewCluster() if c != nil { @@ -64,16 +50,12 @@ func Push(ctx context.Context, cfg PushConfig) error { defer tunnel.Close() } } - - progress := message.NewProgressBar(totalSize, fmt.Sprintf("Pushing %d images", len(toPush))) - defer progress.Close() - pushOptions := createPushOpts(cfg, progress) + pushOptions := createPushOpts(cfg) pushImage := func(img v1.Image, name string) error { if tunnel != nil { return tunnel.Wrap(func() error { return crane.Push(img, name, pushOptions...) }) } - return crane.Push(img, name, pushOptions...) } @@ -84,14 +66,7 @@ func Push(ctx context.Context, cfg PushConfig) error { } }() for refInfo, img := range toPush { - refTruncated := helpers.Truncate(refInfo.Reference, 55, true) - progress.Updatef(fmt.Sprintf("Pushing %s", refTruncated)) - - size, err := calcImgSize(img) - if err != nil { - return err - } - + message.Infof("Pushing %s", refInfo.Reference) // If this is not a no checksum image push it for use with the Zarf agent if !cfg.NoChecksum { offlineNameCRC, err := transform.ImageTransformHost(registryURL, refInfo.Reference) @@ -102,8 +77,6 @@ func Push(ctx context.Context, cfg PushConfig) error { if err = pushImage(img, offlineNameCRC); err != nil { return err } - - totalSize -= size } // To allow for other non-zarf workloads to easily see the images upload a non-checksum version @@ -113,16 +86,12 @@ func Push(ctx context.Context, cfg PushConfig) error { return err } - message.Debugf("push %s -> %s)", refInfo.Reference, offlineName) - if err = pushImage(img, offlineName); err != nil { return err } pushed = append(pushed, refInfo) - totalSize -= size } - progress.Successf("Pushed %d images", len(cfg.ImageList)) return nil }, retry.Context(ctx), retry.Attempts(uint(cfg.Retries)), retry.Delay(500*time.Millisecond)) if err != nil { @@ -131,25 +100,3 @@ func Push(ctx context.Context, cfg PushConfig) error { return nil } - -func calcImgSize(img v1.Image) (int64, error) { - size, err := img.Size() - if err != nil { - return size, err - } - - layers, err := img.Layers() - if err != nil { - return size, err - } - - for _, layer := range layers { - ls, err := layer.Size() - if err != nil { - return size, err - } - size += ls - } - - return size, nil -} diff --git a/src/internal/packager2/mirror.go b/src/internal/packager2/mirror.go index 8532a8c4e6..1b0131dd21 100644 --- a/src/internal/packager2/mirror.go +++ b/src/internal/packager2/mirror.go @@ -85,15 +85,15 @@ func pushImagesToRegistry(ctx context.Context, c *cluster.Cluster, pkgLayout *la return nil } - transport := http.DefaultTransport.(*http.Transport).Clone() - transport.TLSClientConfig.InsecureSkipVerify = config.CommonOptions.InsecureSkipTLSVerify + defaultTransport := http.DefaultTransport.(*http.Transport).Clone() + defaultTransport.TLSClientConfig.InsecureSkipVerify = config.CommonOptions.InsecureSkipTLSVerify // TODO (@WSTARR) This is set to match the TLSHandshakeTimeout to potentially mitigate effects of https://github.com/zarf-dev/zarf/issues/1444 - transport.ResponseHeaderTimeout = 10 * time.Second - transportWithProgressBar := helpers.NewTransport(transport, nil) + defaultTransport.ResponseHeaderTimeout = 10 * time.Second + transport := helpers.NewTransport(defaultTransport, nil) pushOptions := []crane.Option{ crane.WithPlatform(&v1.Platform{OS: "linux", Architecture: pkgLayout.Pkg.Build.Architecture}), - crane.WithTransport(transportWithProgressBar), + crane.WithTransport(transport), crane.WithAuth(authn.FromConfig(authn.AuthConfig{ Username: regInfo.PushUsername, Password: regInfo.PushPassword, diff --git a/src/pkg/cluster/cluster.go b/src/pkg/cluster/cluster.go index 5db77e0c9c..a97b3066bc 100644 --- a/src/pkg/cluster/cluster.go +++ b/src/pkg/cluster/cluster.go @@ -17,9 +17,11 @@ import ( "sigs.k8s.io/cli-utils/pkg/kstatus/watcher" "github.com/avast/retry-go/v4" - pkgkubernetes "github.com/defenseunicorns/pkg/kubernetes" "github.com/zarf-dev/zarf/src/pkg/message" + "k8s.io/client-go/dynamic" + "k8s.io/client-go/tools/clientcmd" + "sigs.k8s.io/controller-runtime/pkg/client/apiutil" ) const ( @@ -76,11 +78,11 @@ func NewClusterWithWait(ctx context.Context) (*Cluster, error) { // NewCluster creates a new Cluster instance and validates connection to the cluster by fetching the Kubernetes version. func NewCluster() (*Cluster, error) { clusterErr := errors.New("unable to connect to the cluster") - clientset, config, err := pkgkubernetes.ClientAndConfig() + clientset, config, err := ClientAndConfig() if err != nil { return nil, errors.Join(clusterErr, err) } - watcher, err := pkgkubernetes.WatcherForConfig(config) + watcher, err := WatcherForConfig(config) if err != nil { return nil, errors.Join(clusterErr, err) } @@ -96,3 +98,36 @@ func NewCluster() (*Cluster, error) { } return c, nil } + +// ClientAndConfig returns a Kubernetes client and the rest config used to configure the client. +func ClientAndConfig() (kubernetes.Interface, *rest.Config, error) { + loader := clientcmd.NewDefaultClientConfigLoadingRules() + clientCfg := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loader, nil) + cfg, err := clientCfg.ClientConfig() + if err != nil { + return nil, nil, err + } + clientset, err := kubernetes.NewForConfig(cfg) + if err != nil { + return nil, nil, err + } + return clientset, cfg, nil +} + +// WatcherForConfig returns a status watcher for the give Kubernetes configuration. +func WatcherForConfig(cfg *rest.Config) (watcher.StatusWatcher, error) { + dynamicClient, err := dynamic.NewForConfig(cfg) + if err != nil { + return nil, err + } + httpClient, err := rest.HTTPClientFor(cfg) + if err != nil { + return nil, err + } + restMapper, err := apiutil.NewDynamicRESTMapper(cfg, httpClient) + if err != nil { + return nil, err + } + sw := watcher.NewDefaultStatusWatcher(dynamicClient, restMapper) + return sw, nil +} diff --git a/src/pkg/cluster/injector.go b/src/pkg/cluster/injector.go index 48552ac5e1..8586934710 100644 --- a/src/pkg/cluster/injector.go +++ b/src/pkg/cluster/injector.go @@ -24,9 +24,9 @@ import ( "k8s.io/apimachinery/pkg/util/wait" "github.com/defenseunicorns/pkg/helpers/v2" - pkgkubernetes "github.com/defenseunicorns/pkg/kubernetes" "github.com/zarf-dev/zarf/src/config" + "github.com/zarf-dev/zarf/src/internal/healthchecks" "github.com/zarf-dev/zarf/src/pkg/message" "github.com/zarf-dev/zarf/src/pkg/transform" "github.com/zarf-dev/zarf/src/pkg/utils" @@ -117,7 +117,7 @@ func (c *Cluster) StartInjection(ctx context.Context, tmpDir, imagesDir string, waitCtx, waitCancel := context.WithTimeout(ctx, 60*time.Second) defer waitCancel() - err = pkgkubernetes.WaitForReadyRuntime(waitCtx, c.Watcher, []runtime.Object{pod}) + err = healthchecks.WaitForReadyRuntime(waitCtx, c.Watcher, []runtime.Object{pod}) if err != nil { return err } @@ -319,6 +319,9 @@ func hasBlockingTaints(taints []corev1.Taint) bool { func buildInjectionPod(nodeName, image string, payloadCmNames []string, shasum string, resReq corev1.ResourceRequirements) *corev1.Pod { executeMode := int32(0777) + userID := int64(1000) + groupID := int64(2000) + fsGroupID := int64(2000) pod := &corev1.Pod{ TypeMeta: metav1.TypeMeta{ @@ -337,6 +340,12 @@ func buildInjectionPod(nodeName, image string, payloadCmNames []string, shasum s NodeName: nodeName, // Do not try to restart the pod as it will be deleted/re-created instead. RestartPolicy: corev1.RestartPolicyNever, + SecurityContext: &corev1.PodSecurityContext{ + RunAsUser: &userID, + RunAsGroup: &groupID, + FSGroup: &fsGroupID, + SeccompProfile: &corev1.SeccompProfile{Type: corev1.SeccompProfileTypeRuntimeDefault}, + }, Containers: []corev1.Container{ { Name: "injector", @@ -366,6 +375,14 @@ func buildInjectionPod(nodeName, image string, payloadCmNames []string, shasum s }, }, }, + SecurityContext: &corev1.SecurityContext{ + ReadOnlyRootFilesystem: helpers.BoolPtr(true), + AllowPrivilegeEscalation: helpers.BoolPtr(false), + RunAsNonRoot: helpers.BoolPtr(true), + Capabilities: &corev1.Capabilities{ + Drop: []corev1.Capability{"ALL"}, + }, + }, Resources: resReq, }, }, diff --git a/src/pkg/cluster/injector_test.go b/src/pkg/cluster/injector_test.go index 67dff422f2..e5acdf3227 100644 --- a/src/pkg/cluster/injector_test.go +++ b/src/pkg/cluster/injector_test.go @@ -15,6 +15,7 @@ import ( "github.com/google/go-containerregistry/pkg/v1/layout" "github.com/google/go-containerregistry/pkg/v1/random" "github.com/stretchr/testify/require" + "github.com/zarf-dev/zarf/src/internal/healthchecks" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -22,8 +23,6 @@ import ( "k8s.io/client-go/kubernetes/fake" k8stesting "k8s.io/client-go/testing" "sigs.k8s.io/cli-utils/pkg/kstatus/status" - - pkgkubernetes "github.com/defenseunicorns/pkg/kubernetes" ) func TestInjector(t *testing.T) { @@ -31,7 +30,7 @@ func TestInjector(t *testing.T) { cs := fake.NewSimpleClientset() c := &Cluster{ Clientset: cs, - Watcher: pkgkubernetes.NewImmediateWatcher(status.CurrentStatus), + Watcher: healthchecks.NewImmediateWatcher(status.CurrentStatus), } cs.PrependReactor("delete-collection", "configmaps", func(action k8stesting.Action) (bool, runtime.Object, error) { delAction, ok := action.(k8stesting.DeleteCollectionActionImpl) diff --git a/src/pkg/cluster/testdata/expected-injection-pod.json b/src/pkg/cluster/testdata/expected-injection-pod.json index 30f2e5b1f1..297a5e28bc 100644 --- a/src/pkg/cluster/testdata/expected-injection-pod.json +++ b/src/pkg/cluster/testdata/expected-injection-pod.json @@ -1 +1 @@ -{"kind":"Pod","apiVersion":"v1","metadata":{"name":"injector","namespace":"zarf","creationTimestamp":null,"labels":{"app":"zarf-injector","zarf.dev/agent":"ignore"}},"spec":{"volumes":[{"name":"init","configMap":{"name":"rust-binary","defaultMode":511}},{"name":"seed","emptyDir":{}},{"name":"foo","configMap":{"name":"foo"}},{"name":"bar","configMap":{"name":"bar"}}],"containers":[{"name":"injector","image":"docker.io/library/ubuntu:latest","command":["/zarf-init/zarf-injector","shasum"],"workingDir":"/zarf-init","resources":{"limits":{"cpu":"1","memory":"256Mi"},"requests":{"cpu":"500m","memory":"64Mi"}},"volumeMounts":[{"name":"init","mountPath":"/zarf-init/zarf-injector","subPath":"zarf-injector"},{"name":"seed","mountPath":"/zarf-seed"},{"name":"foo","mountPath":"/zarf-init/foo","subPath":"foo"},{"name":"bar","mountPath":"/zarf-init/bar","subPath":"bar"}],"readinessProbe":{"httpGet":{"path":"/v2/","port":5000},"periodSeconds":2,"successThreshold":1,"failureThreshold":10},"imagePullPolicy":"IfNotPresent"}],"restartPolicy":"Never","nodeName":"injection-node"},"status":{}} +{"kind":"Pod","apiVersion":"v1","metadata":{"name":"injector","namespace":"zarf","creationTimestamp":null,"labels":{"app":"zarf-injector","zarf.dev/agent":"ignore"}},"spec":{"volumes":[{"name":"init","configMap":{"name":"rust-binary","defaultMode":511}},{"name":"seed","emptyDir":{}},{"name":"foo","configMap":{"name":"foo"}},{"name":"bar","configMap":{"name":"bar"}}],"containers":[{"name":"injector","image":"docker.io/library/ubuntu:latest","command":["/zarf-init/zarf-injector","shasum"],"workingDir":"/zarf-init","resources":{"limits":{"cpu":"1","memory":"256Mi"},"requests":{"cpu":"500m","memory":"64Mi"}},"volumeMounts":[{"name":"init","mountPath":"/zarf-init/zarf-injector","subPath":"zarf-injector"},{"name":"seed","mountPath":"/zarf-seed"},{"name":"foo","mountPath":"/zarf-init/foo","subPath":"foo"},{"name":"bar","mountPath":"/zarf-init/bar","subPath":"bar"}],"readinessProbe":{"httpGet":{"path":"/v2/","port":5000},"periodSeconds":2,"successThreshold":1,"failureThreshold":10},"imagePullPolicy":"IfNotPresent","securityContext":{"capabilities":{"drop":["ALL"]},"runAsNonRoot":true,"readOnlyRootFilesystem":true,"allowPrivilegeEscalation":false}}],"restartPolicy":"Never","nodeName":"injection-node","securityContext":{"runAsUser":1000,"runAsGroup":2000,"fsGroup":2000,"seccompProfile":{"type":"RuntimeDefault"}}},"status":{}} diff --git a/src/pkg/logger/logger.go b/src/pkg/logger/logger.go new file mode 100644 index 0000000000..41400666d8 --- /dev/null +++ b/src/pkg/logger/logger.go @@ -0,0 +1,159 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2021-Present The Zarf Authors + +// Package logger implements a log/slog based logger in Zarf. +package logger + +import ( + "fmt" + "io" + "log/slog" + "os" + "strings" + "sync/atomic" +) + +var defaultLogger atomic.Pointer[slog.Logger] + +// init sets a logger with default config when the package is initialized. +func init() { + l, _ := New(ConfigDefault()) //nolint:errcheck + SetDefault(l) +} + +// Level declares each supported log level. These are 1:1 what log/slog supports by default. Info is the default level. +type Level int + +// Store names for Levels +var ( + Debug = Level(slog.LevelDebug) // -4 + Info = Level(slog.LevelInfo) // 0 + Warn = Level(slog.LevelWarn) // 4 + Error = Level(slog.LevelError) // 8 +) + +// validLevels is a set that provides an ergonomic way to check if a level is a member of the set. +var validLevels = map[Level]bool{ + Debug: true, + Info: true, + Warn: true, + Error: true, +} + +// strLevels maps a string to its Level. +var strLevels = map[string]Level{ + "debug": Debug, + "info": Info, + "warn": Warn, + "error": Error, +} + +// ParseLevel takes a string representation of a Level, ensure it exists, and then converts it into a Level. +func ParseLevel(s string) (Level, error) { + k := strings.ToLower(s) + l, ok := strLevels[k] + if !ok { + return 0, fmt.Errorf("invalid log level: %s", k) + } + return l, nil +} + +// Format declares the kind of logging handler to use. An empty Format defaults to text. +type Format string + +// ToLower takes a Format string and converts it to lowercase for case-agnostic validation. Users shouldn't have to care +// about "json" vs. "JSON" for example - they should both work. +func (f Format) ToLower() Format { + return Format(strings.ToLower(string(f))) +} + +// TODO(mkcp): Add dev format +var ( + // FormatText uses the standard slog TextHandler + FormatText Format = "text" + // FormatJSON uses the standard slog JSONHandler + FormatJSON Format = "json" + // FormatNone sends log writes to DestinationNone / io.Discard + FormatNone Format = "none" +) + +// More printers would be great, like dev format https://github.com/golang-cz/devslog +// and a pretty console slog https://github.com/phsym/console-slog + +// Destination declares an io.Writer to send logs to. +type Destination io.Writer + +var ( + // DestinationDefault points to Stderr + DestinationDefault Destination = os.Stderr + // DestinationNone discards logs as they are received + DestinationNone Destination = io.Discard +) + +// Config is configuration for a logger. +type Config struct { + // Level sets the log level. An empty value corresponds to Info aka 0. + Level + Format + Destination +} + +// ConfigDefault returns a Config with defaults like Text formatting at Info level writing to Stderr. +func ConfigDefault() Config { + return Config{ + Level: Info, + Format: FormatText, + Destination: DestinationDefault, // Stderr + } +} + +// New takes a Config and returns a validated logger. +func New(cfg Config) (*slog.Logger, error) { + var handler slog.Handler + opts := slog.HandlerOptions{} + + // Use default destination if none + if cfg.Destination == nil { + cfg.Destination = DestinationDefault + } + + // Check that we have a valid log level. + if !validLevels[cfg.Level] { + return nil, fmt.Errorf("unsupported log level: %d", cfg.Level) + } + opts.Level = slog.Level(cfg.Level) + + switch cfg.Format.ToLower() { + // Use Text handler if no format provided + case "", FormatText: + handler = slog.NewTextHandler(cfg.Destination, &opts) + case FormatJSON: + handler = slog.NewJSONHandler(cfg.Destination, &opts) + // TODO(mkcp): Add dev format + // case FormatDev: + // handler = slog.NewTextHandler(DestinationNone, &slog.HandlerOptions{ + // AddSource: true, + // }) + case FormatNone: + handler = slog.NewTextHandler(DestinationNone, &slog.HandlerOptions{}) + // Format not found, let's error out + default: + return nil, fmt.Errorf("unsupported log format: %s", cfg.Format) + } + + log := slog.New(handler) + return log, nil +} + +// Default retrieves a logger from the package default. This is intended as a fallback when a logger cannot easily be +// passed in as a dependency, like when developing a new function. Use it like you would use context.TODO(). +func Default() *slog.Logger { + return defaultLogger.Load() +} + +// SetDefault takes a logger and atomically stores it as the package default. This is intended to be called when the +// application starts to override the default config with application-specific config. See Default() for more usage +// details. +func SetDefault(l *slog.Logger) { + defaultLogger.Store(l) +} diff --git a/src/pkg/logger/logger_test.go b/src/pkg/logger/logger_test.go new file mode 100644 index 0000000000..db8851e25c --- /dev/null +++ b/src/pkg/logger/logger_test.go @@ -0,0 +1,216 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2021-Present The Zarf Authors + +// Package logger implements a log/slog based logger in Zarf. +package logger + +import ( + "os" + "testing" + + "github.com/stretchr/testify/require" +) + +func Test_New(t *testing.T) { + t.Parallel() + + tt := []struct { + name string + cfg Config + }{ + { + name: "Empty level, format, and destination are ok", + cfg: Config{}, + }, + { + name: "Default config is ok", + cfg: ConfigDefault(), + }, + { + name: "Debug logs are ok", + cfg: Config{ + Level: Debug, + }, + }, + { + name: "Info logs are ok", + cfg: Config{ + Level: Info, + }, + }, + { + name: "Warn logs are ok", + cfg: Config{ + Level: Warn, + }, + }, + { + name: "Error logs are ok", + cfg: Config{ + Level: Error, + }, + }, + { + name: "Text format is supported", + cfg: Config{ + Format: FormatText, + }, + }, + { + name: "JSON format is supported", + cfg: Config{ + Format: FormatJSON, + }, + }, + { + name: "FormatNone is supported to disable logs", + cfg: Config{ + Format: FormatNone, + }, + }, + { + name: "DestinationNone is supported to disable logs", + cfg: Config{ + Destination: DestinationNone, + }, + }, + { + name: "users can send logs to any io.Writer", + cfg: Config{ + Destination: os.Stdout, + }, + }, + } + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + res, err := New(tc.cfg) + require.NoError(t, err) + require.NotNil(t, res) + }) + } +} + +func Test_NewErrors(t *testing.T) { + t.Parallel() + + tt := []struct { + name string + cfg Config + }{ + { + name: "unsupported log level errors", + cfg: Config{ + Level: 3, + }, + }, + { + name: "wildly unsupported log level errors", + cfg: Config{ + Level: 42389412389213489, + }, + }, + { + name: "unsupported format errors", + cfg: Config{ + Format: "foobar", + }, + }, + { + name: "wildly unsupported format errors", + cfg: Config{ + Format: "^\\w+([-+.']\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*$ lorem ipsum dolor sit amet 243897 )*&($#", + }, + }, + } + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + res, err := New(tc.cfg) + require.Error(t, err) + require.Nil(t, res) + }) + } +} + +func Test_ParseLevel(t *testing.T) { + t.Parallel() + + tt := []struct { + name string + s string + expect Level + }{ + { + name: "can parse debug", + s: "debug", + expect: Debug, + }, + { + name: "can parse info", + s: "Info", + expect: Info, + }, + { + name: "can parse warn", + s: "warn", + expect: Warn, + }, + { + name: "can parse error", + s: "error", + expect: Error, + }, + { + name: "can handle uppercase", + s: "ERROR", + expect: Error, + }, + { + name: "can handle inconsistent uppercase", + s: "errOR", + expect: Error, + }, + } + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + res, err := ParseLevel(tc.s) + require.NoError(t, err) + require.Equal(t, tc.expect, res) + }) + } +} + +func Test_ParseLevelErrors(t *testing.T) { + t.Parallel() + + tt := []struct { + name string + s string + }{ + { + name: "errors out on unknown level", + s: "SUPER-DEBUG-10x-supremE", + }, + { + name: "is precise about character variations", + s: "érrør", + }, + { + name: "does not partial match level", + s: "error-info", + }, + { + name: "does not partial match level 2", + s: "info-error", + }, + { + name: "does not partial match level 3", + s: "info2", + }, + } + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + _, err := ParseLevel(tc.s) + require.Error(t, err) + }) + } +} diff --git a/src/pkg/packager/creator/normal.go b/src/pkg/packager/creator/normal.go index 099fda5e2b..3b7d4a7698 100644 --- a/src/pkg/packager/creator/normal.go +++ b/src/pkg/packager/creator/normal.go @@ -206,6 +206,12 @@ func (pc *PackageCreator) Assemble(ctx context.Context, dst *layout.PackagePaths sbomImageList = append(sbomImageList, info) } } + + // Sort images index to make build reproducible. + err = utils.SortImagesIndex(dst.Images.Base) + if err != nil { + return err + } } // Ignore SBOM creation if the flag is set. diff --git a/src/pkg/utils/image.go b/src/pkg/utils/image.go index 3756a759e1..cd62128159 100644 --- a/src/pkg/utils/image.go +++ b/src/pkg/utils/image.go @@ -9,6 +9,8 @@ import ( "fmt" "os" "path/filepath" + "slices" + "strings" "github.com/defenseunicorns/pkg/helpers/v2" v1 "github.com/google/go-containerregistry/pkg/v1" @@ -102,3 +104,25 @@ func OnlyHasImageLayers(img v1.Image) (bool, error) { } return true, nil } + +// SortImagesIndex sorts the index.json by digest. +func SortImagesIndex(ociPath string) error { + indexPath := filepath.Join(ociPath, "index.json") + b, err := os.ReadFile(indexPath) + if err != nil { + return err + } + var index ocispec.Index + err = json.Unmarshal(b, &index) + if err != nil { + return err + } + slices.SortFunc(index.Manifests, func(a, b ocispec.Descriptor) int { + return strings.Compare(string(a.Digest), string(b.Digest)) + }) + b, err = json.Marshal(index) + if err != nil { + return err + } + return os.WriteFile(indexPath, b, helpers.ReadWriteUser) +} diff --git a/src/test/external/ext_in_cluster_test.go b/src/test/external/ext_in_cluster_test.go index 81102c0b14..1b4f668d8c 100644 --- a/src/test/external/ext_in_cluster_test.go +++ b/src/test/external/ext_in_cluster_test.go @@ -14,9 +14,9 @@ import ( "testing" "time" - pkgkubernetes "github.com/defenseunicorns/pkg/kubernetes" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" + "github.com/zarf-dev/zarf/src/internal/healthchecks" "github.com/zarf-dev/zarf/src/pkg/cluster" "github.com/zarf-dev/zarf/src/pkg/utils/exec" "github.com/zarf-dev/zarf/src/test/testutil" @@ -88,7 +88,7 @@ func (suite *ExtInClusterTestSuite) SetupSuite() { } waitCtx, waitCancel := context.WithTimeout(context.Background(), 60*time.Second) defer waitCancel() - err = pkgkubernetes.WaitForReady(waitCtx, c.Watcher, objs) + err = healthchecks.WaitForReady(waitCtx, c.Watcher, objs) suite.NoError(err) } @@ -199,7 +199,7 @@ func (suite *ExtInClusterTestSuite) Test_1_Deploy() { } waitCtx, waitCancel := context.WithTimeout(context.Background(), 60*time.Second) defer waitCancel() - err = pkgkubernetes.WaitForReady(waitCtx, c.Watcher, objs) + err = healthchecks.WaitForReady(waitCtx, c.Watcher, objs) suite.NoError(err) _, _, err = exec.CmdWithTesting(suite.T(), exec.PrintCfg(), zarfBinPath, "destroy", "--confirm")