From c0b58b2bbddc1e694c0d2b730caabb706d70cf57 Mon Sep 17 00:00:00 2001 From: schristoff <28318173+schristoff@users.noreply.github.com> Date: Wed, 15 May 2024 15:13:08 -0600 Subject: [PATCH] bug: fix rust injector Signed-off-by: schristoff <28318173+schristoff@users.noreply.github.com> --- .github/CONTRIBUTING.md | 4 +- .github/SECURITY.md | 21 + .github/actions/install-tools/action.yaml | 6 +- .github/actions/k3d/action.yaml | 4 +- .github/codeql.yaml | 2 +- .github/workflows/build-rust-injector.yml | 3 +- .github/workflows/compare-cves.yml | 33 + .github/workflows/scan-codeql.yml | 2 - .github/workflows/scan-cves.yml | 6 - .github/workflows/scan-lint.yml | 11 +- .golangci.yaml | 62 + .grype.yaml | 14 - .pre-commit-config.yaml | 18 +- CODEOWNERS | 32 +- CODE_OF_CONDUCT.md | 132 +++ Makefile | 14 +- README.md | 3 +- SECURITY.md | 9 - SUPPORT.md | 47 + go.mod | 77 +- go.sum | 162 +-- hack/check-vulnerabilities.sh | 30 + hack/check-zarf-docs-and-schema.sh | 4 +- hack/compare.tmpl | 7 + hack/create-zarf-schema.sh | 17 +- hack/empty-config.toml | 3 + hack/{.templates => }/grype.tmpl | 0 hack/lint-all-zarf-packages.sh | 8 +- .../chart/templates/_helpers.tpl | 42 + .../chart/templates/deployment.yaml | 11 + .../zarf-registry/chart/templates/hpa.yaml | 5 + .../chart/templates/serviceaccount.yaml | 13 + packages/zarf-registry/chart/values.yaml | 12 + packages/zarf-registry/registry-values.yaml | 19 +- packages/zarf-registry/zarf.yaml | 35 + renovate.json | 10 + revive.toml | 31 - site/astro.config.ts | 7 +- site/package-lock.json | 9 + site/package.json | 1 + .../src/components/SchemaItemProperties.astro | 2 +- .../docs/commands/zarf_package_inspect.md | 1 + .../docs/commands/zarf_tools_yq_eval.md | 10 +- site/src/content/docs/faq.mdx | 28 - site/src/content/docs/ref/create.mdx | 44 +- site/src/content/docs/ref/deploy.mdx | 40 +- site/src/content/docs/ref/init-package.mdx | 311 +++-- site/src/content/docs/ref/packages.mdx | 12 +- site/src/styles/custom.css | 73 +- src/cmd/common/utils.go | 13 + src/cmd/connect.go | 15 +- src/cmd/destroy.go | 18 +- src/cmd/dev.go | 35 +- src/cmd/initialize.go | 9 +- src/cmd/internal.go | 40 +- src/cmd/package.go | 71 +- src/cmd/root.go | 5 + src/cmd/tools/archiver.go | 5 +- src/cmd/tools/crane.go | 57 +- src/cmd/tools/helm/load_plugins.go | 11 +- src/cmd/tools/helm/repo_add.go | 16 +- src/cmd/tools/helm/repo_index.go | 6 +- src/cmd/tools/yq.go | 5 +- src/cmd/tools/zarf.go | 23 +- src/config/config.go | 43 - src/config/lang/english.go | 21 +- src/extensions/bigbang/bigbang_test.go | 3 + src/extensions/bigbang/test/bigbang_test.go | 167 +-- src/injector/.cargo/config.toml | 5 + src/injector/Cargo.lock | 1033 ++++++++--------- src/injector/Cargo.toml | 12 +- src/injector/Makefile | 31 +- src/injector/README.md | 123 +- src/injector/src/main.rs | 247 ++-- src/internal/packager/git/gitea.go | 25 +- src/internal/packager/helm/chart.go | 25 +- src/internal/packager/helm/images.go | 3 + src/internal/packager/helm/post-render.go | 26 +- src/internal/packager/helm/zarf.go | 26 +- src/internal/packager/images/common.go | 100 +- src/internal/packager/images/pull.go | 475 ++++---- src/internal/packager/images/push.go | 128 +- src/internal/packager/validate/validate.go | 369 ------ src/pkg/cluster/common.go | 17 +- src/pkg/cluster/data.go | 7 +- src/pkg/cluster/injector.go | 112 +- src/pkg/cluster/namespace.go | 4 +- src/pkg/cluster/secrets.go | 17 +- src/pkg/cluster/state.go | 27 +- src/pkg/cluster/tunnel.go | 27 +- src/pkg/cluster/zarf.go | 81 +- src/pkg/k8s/common.go | 48 +- src/pkg/k8s/configmap.go | 19 +- src/pkg/k8s/dynamic.go | 14 +- src/pkg/k8s/hpa.go | 16 +- src/pkg/k8s/info.go | 11 +- src/pkg/k8s/namespace.go | 34 +- src/pkg/k8s/nodes.go | 8 +- src/pkg/k8s/pods.go | 177 +-- src/pkg/k8s/sa.go | 44 +- src/pkg/k8s/secrets.go | 24 +- src/pkg/k8s/services.go | 34 +- src/pkg/k8s/tunnel.go | 49 +- src/pkg/layout/package_test.go | 4 +- src/pkg/packager/common.go | 25 +- src/pkg/packager/common_test.go | 6 +- src/pkg/packager/composer/list.go | 3 +- src/pkg/packager/create.go | 3 +- src/pkg/packager/creator/differential.go | 48 - src/pkg/packager/creator/normal.go | 45 +- src/pkg/packager/deploy.go | 99 +- src/pkg/packager/dev.go | 10 +- src/pkg/packager/filters/deploy.go | 4 +- src/pkg/packager/filters/diff.go | 63 + src/pkg/packager/filters/diff_test.go | 70 ++ src/pkg/packager/filters/os_test.go | 5 +- src/pkg/packager/filters/utils.go | 13 - src/pkg/packager/generate.go | 3 +- src/pkg/packager/inspect.go | 17 +- src/pkg/packager/mirror.go | 11 +- src/pkg/packager/prepare.go | 4 +- src/pkg/packager/remove.go | 25 +- src/pkg/packager/sources/cluster.go | 14 +- src/pkg/transform/types.go | 3 + src/pkg/utils/bytes.go | 1 + src/pkg/utils/cosign.go | 3 +- src/pkg/utils/image.go | 18 +- src/pkg/variables/types.go | 35 + src/pkg/zoci/push.go | 2 +- src/test/e2e/00_use_cli_test.go | 28 +- src/test/e2e/06_create_sbom_test.go | 4 + src/test/e2e/12_lint_test.go | 3 + src/test/e2e/13_find_images_test.go | 3 + src/test/e2e/14_create_sha_index_test.go | 41 + src/test/e2e/21_connect_creds_test.go | 15 +- src/test/e2e/22_git_and_gitops_test.go | 17 +- src/test/e2e/23_data_injection_test.go | 10 +- src/test/e2e/25_helm_test.go | 13 +- src/test/e2e/26_simple_packages_test.go | 3 +- src/test/e2e/30_config_file_test.go | 12 +- src/test/e2e/33_component_webhooks_test.go | 4 +- src/test/e2e/36_custom_retries_test.go | 8 +- src/test/e2e/99_yolo_test.go | 3 +- src/test/external/configure-gitea.sh | 1 + src/test/external/docker-registry-values.yaml | 2 +- src/test/external/ext_in_cluster_test.go | 6 +- src/test/external/ext_out_cluster_test.go | 6 +- .../14-index-sha/image-index/zarf.yaml | 9 + .../14-index-sha/manifest-list/zarf.yaml | 9 + src/types/component.go | 9 + src/types/runtime.go | 8 +- src/types/validate.go | 335 ++++++ zarf-config.toml | 7 +- zarf.schema.json | 362 +++--- 154 files changed, 3972 insertions(+), 2888 deletions(-) create mode 100644 .github/SECURITY.md create mode 100644 .github/workflows/compare-cves.yml create mode 100644 .golangci.yaml create mode 100644 CODE_OF_CONDUCT.md delete mode 100644 SECURITY.md create mode 100644 SUPPORT.md create mode 100755 hack/check-vulnerabilities.sh create mode 100644 hack/compare.tmpl rename hack/{.templates => }/grype.tmpl (100%) create mode 100644 packages/zarf-registry/chart/templates/serviceaccount.yaml delete mode 100644 revive.toml create mode 100644 src/injector/.cargo/config.toml delete mode 100644 src/internal/packager/validate/validate.go create mode 100644 src/pkg/packager/filters/diff.go create mode 100644 src/pkg/packager/filters/diff_test.go create mode 100644 src/test/e2e/14_create_sha_index_test.go create mode 100644 src/test/packages/14-index-sha/image-index/zarf.yaml create mode 100644 src/test/packages/14-index-sha/manifest-list/zarf.yaml create mode 100644 src/types/validate.go diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 9137fa6b9b..4dda9e1668 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -23,8 +23,8 @@ We use [pre-commit](https://pre-commit.com/) to manage our pre-commit hooks. Thi # install hooks pre-commit install -# install goimports -go install golang.org/x/tools/cmd/goimports@latest +# install golang-ci-lint +go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest ``` Now every time you commit, the hooks will run and format your code, linting can be called via `make lint-go`. diff --git a/.github/SECURITY.md b/.github/SECURITY.md new file mode 100644 index 0000000000..6fd559327f --- /dev/null +++ b/.github/SECURITY.md @@ -0,0 +1,21 @@ +# Reporting Security Issues + +To report a security issue or vulnerability in Zarf, please use the confidential GitHub Security Advisory ["Report a Vulnerability"](https://github.com/defenseunicorns/zarf/security/advisories) tab. The Zarf team will send a response indicating the next steps in handling your report. After the initial reply to your report, the team will keep you informed of the progress towards a fix and full announcement, and may ask for additional information or guidance. + +### When Should I Report a Vulnerability? + +* You found a vulnerability in the Zarf code. +* You found a vulnerability in one of the Zarf dependencies that affects the project that has not been patched yet. + +### When Should I NOT Report a Vulnerability? + +* You found a bug or malfunction in the Zarf code (not security related). +* You want to add a feature to Zarf. + +## Supported Versions + +As Zarf has not yet reached v1.0.0, only the current latest minor release is supported. + +## Contacting Us + +To discuss security related issues, please email the maintainers at zarf-dev-private@googlegroups.com. diff --git a/.github/actions/install-tools/action.yaml b/.github/actions/install-tools/action.yaml index e34f24c746..e8e052b640 100644 --- a/.github/actions/install-tools/action.yaml +++ b/.github/actions/install-tools/action.yaml @@ -8,7 +8,11 @@ runs: - uses: anchore/sbom-action/download-syft@b6a39da80722a2cb0ef5d197531764a89b5d48c3 # v0.15.8 - - run: "curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin --tag v0.64.2" + - name: install grype + env: + # renovate: datasource=github-tags depName=anchore/grype versioning=semver + VERSION: v0.74.6 + run: "curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin $VERSION" shell: bash - uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0 diff --git a/.github/actions/k3d/action.yaml b/.github/actions/k3d/action.yaml index c6bfb5e92a..951caf7756 100644 --- a/.github/actions/k3d/action.yaml +++ b/.github/actions/k3d/action.yaml @@ -7,5 +7,5 @@ runs: - run: "curl -s https://raw.githubusercontent.com/k3d-io/k3d/main/install.sh | bash" shell: bash - - run: k3d cluster delete && k3d cluster create --k3s-arg="--disable=traefik@server:0" --image="rancher/k3s:v1.28.4-k3s2" - shell: bash + - run: k3d cluster delete && k3d cluster create --k3s-arg="--disable=traefik@server:0" + shell: bash \ No newline at end of file diff --git a/.github/codeql.yaml b/.github/codeql.yaml index 3d376c23c1..a57c624727 100644 --- a/.github/codeql.yaml +++ b/.github/codeql.yaml @@ -2,7 +2,7 @@ paths-ignore: - src/pkg/packager/network.go - src/pkg/utils/network.go - src/pkg/utils/credentials.go - - docs-website/** + - site/** - build/** query-filters: diff --git a/.github/workflows/build-rust-injector.yml b/.github/workflows/build-rust-injector.yml index 375dc62bb3..270a091fcf 100644 --- a/.github/workflows/build-rust-injector.yml +++ b/.github/workflows/build-rust-injector.yml @@ -27,8 +27,9 @@ jobs: - name: "Build Rust Binary for x86_64 and arm64" run: | + cd src/injector make build-injector-linux - cd src/injector/target + cd target mkdir -p ../dist cp x86_64-unknown-linux-musl/release/zarf-injector ../dist/zarf-injector-amd64 cp aarch64-unknown-linux-musl/release/zarf-injector ../dist/zarf-injector-arm64 diff --git a/.github/workflows/compare-cves.yml b/.github/workflows/compare-cves.yml new file mode 100644 index 0000000000..f4c8500d88 --- /dev/null +++ b/.github/workflows/compare-cves.yml @@ -0,0 +1,33 @@ +name: Compare CVEs to main + +permissions: + contents: read + +on: + pull_request: + paths: + - "go.mod" + - "go.sum" + - "cargo.toml" + - "cargo.lock" + +jobs: + validate: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + with: + ref: ${{ github.head_ref || github.ref_name }} + + - name: fetch main + run: git fetch origin main --depth 1 + + - name: Setup golang + uses: ./.github/actions/golang + + - name: Install tools + uses: ./.github/actions/install-tools + + - name: Check for CVEs in Dependencies + run: "hack/check-vulnerabilities.sh" diff --git a/.github/workflows/scan-codeql.yml b/.github/workflows/scan-codeql.yml index 5fbbd757bb..7184bf1778 100644 --- a/.github/workflows/scan-codeql.yml +++ b/.github/workflows/scan-codeql.yml @@ -44,8 +44,6 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@e8893c57a1f3a2b659b6b55564fdfdbbd2982911 # v3.24.0 - env: - CODEQL_EXTRACTOR_GO_BUILD_TRACING: on with: languages: ${{ matrix.language }} config-file: ./.github/codeql.yaml diff --git a/.github/workflows/scan-cves.yml b/.github/workflows/scan-cves.yml index 26c05a08c9..2851849bf7 100644 --- a/.github/workflows/scan-cves.yml +++ b/.github/workflows/scan-cves.yml @@ -6,12 +6,6 @@ permissions: on: schedule: - cron: "0 10 * * *" - pull_request: - paths: - - "go.mod" - - "go.sum" - - "cargo.toml" - - "cargo.lock" jobs: validate: diff --git a/.github/workflows/scan-lint.yml b/.github/workflows/scan-lint.yml index 123c7cfeeb..e35d4f7330 100644 --- a/.github/workflows/scan-lint.yml +++ b/.github/workflows/scan-lint.yml @@ -10,12 +10,5 @@ jobs: steps: - name: Checkout uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - - - name: Run Revive Action by pulling pre-built image - uses: docker://morphy/revive-action:v2.5.7@sha256:087d4e61077087755711ab7e9fae3cc899b7bb07ff8f6a30c3dfb240b1620ae8 - with: - config: revive.toml - # Exclude patterns, separated by semicolons (optional) - exclude: "src/cmd/viper.go" - # Path pattern (default: ./...) - path: "./src/..." + - name: Run golangci-lint + uses: golangci/golangci-lint-action@a4f60bb28d35aeee14e6880718e0c85ff1882e64 # v6.0.1 diff --git a/.golangci.yaml b/.golangci.yaml new file mode 100644 index 0000000000..274226cc10 --- /dev/null +++ b/.golangci.yaml @@ -0,0 +1,62 @@ +run: + timeout: 5m +linters: + disable-all: true + enable: + - gosimple + - govet + - staticcheck + - unused + - revive + - goheader + - goimports + - nolintlint +linters-settings: + govet: + enable-all: true + disable: + - shadow + - fieldalignment + - unusedwrite + nolintlint: + require-specific: true + goheader: + template: |- + SPDX-License-Identifier: Apache-2.0 + SPDX-FileCopyrightText: 2021-Present The Zarf Authors + revive: + rules: + - name: blank-imports + - name: context-as-argument + - name: context-keys-type + - name: dot-imports + - name: error-return + - name: error-strings + - name: error-naming + - name: exported + - name: if-return + - name: increment-decrement + - name: var-naming + - name: var-declaration + - name: package-comments + - name: range + - name: receiver-naming + - name: time-naming + - name: unexported-return + - name: indent-error-flow + - name: errorf + - name: empty-block + - name: superfluous-else + - name: unused-parameter + - name: unreachable-code + - name: redefines-builtin-id +issues: + # Revive rules that are disabled by default. + include: + - EXC0012 + - EXC0013 + - EXC0014 + - EXC0015 + # Exclude linting code copied from Helm. + exclude-dirs: + - "src/cmd/tools/helm" diff --git a/.grype.yaml b/.grype.yaml index ec5662d9d9..dcc070020c 100644 --- a/.grype.yaml +++ b/.grype.yaml @@ -1,20 +1,6 @@ -# Ignore file for false positives from protobuf, see the following for more information: -# https://github.com/anchore/grype/issues/558 ignore: - # This vulnerability does not affect Zarf as we do not instantiate a rekor client - - vulnerability: GHSA-2h5h-59f5-c5x9 - - # This vulnerability does not affect Zarf as we do not instantiate a rekor client - - vulnerability: GHSA-frqx-jfcm-6jjr - # From rouille - The Zarf injector does not expose endpoints that use multipart form data - vulnerability: GHSA-mc8h-8q98-g5hr - # From semver - This comes through nodemon which is only used for development - - vulnerability: GHSA-c2qf-rxjj-qqgw - - # From k8s.io/apiserver - This is a false positive due to the difference in versioning between the library / binary k8s versioning - - vulnerability: GHSA-82hx-w2r5-c2wq - # From helm - This behavior was introduced intentionally, and cannot be removed without breaking backwards compatibility (some users may be relying on these values). - vulnerability: GHSA-jw44-4f3j-q396 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7b3bcc66b4..93759bdb25 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -9,10 +9,13 @@ repos: args: - "--allow-missing-credentials" - id: detect-private-key + exclude: "src/test/e2e/30_config_file_test.go" - id: end-of-file-fixer + exclude: site/src/content/docs/commands/.* - id: fix-byte-order-marker - id: trailing-whitespace args: [--markdown-linebreak-ext=md] + exclude: site/src/content/docs/commands/.* - repo: https://github.com/sirosen/texthooks rev: 0.6.4 hooks: @@ -27,13 +30,16 @@ repos: language: script - id: goimports name: goimports - entry: goimports - files: .go$ - args: - - -l - - -w - language: system + entry: golangci-lint run --enable-only goimports --fix + types: [go] + language: golang pass_filenames: true + - id: lint + name: golangci-lint go lint + entry: golangci-lint run + types: [go] + language: golang + pass_filenames: false - repo: https://github.com/python-jsonschema/check-jsonschema rev: 0.14.0 hooks: diff --git a/CODEOWNERS b/CODEOWNERS index 4789538bef..c713375a0d 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1,35 +1,5 @@ -* @defenseunicorns/zarf +* @defenseunicorns/zarf @dgershman -# Docs & examples -/adr/ @jeff-mccoy @Racer159 @Noxsios @lucasrod16 @AustinAbro321 -/docs/ @jeff-mccoy @Racer159 @Noxsios @lucasrod16 @AustinAbro321 -/examples/ @jeff-mccoy @Racer159 @Noxsios @lucasrod16 @AustinAbro321 -*.md @jeff-mccoy @Racer159 @Noxsios @lucasrod16 @AustinAbro321 - -# Core code -/src/ @jeff-mccoy @Racer159 @Noxsios @lucasrod16 @AustinAbro321 -/go.* @jeff-mccoy @Racer159 @Noxsios @lucasrod16 @AustinAbro321 -main.go @jeff-mccoy @Racer159 @Noxsios @lucasrod16 @AustinAbro321 - -# Init package -/packages/ @jeff-mccoy @Racer159 @Noxsios @lucasrod16 @AustinAbro321 -/zarf.yaml @jeff-mccoy @Racer159 @Noxsios @lucasrod16 @AustinAbro321 - -# Docs Website -/docs-website/ @Racer159 @Noxsios @jeff-mccoy @lucasrod16 @AustinAbro321 - -# Privileged pipeline files -/.github/ @jeff-mccoy @Racer159 @Noxsios @lucasrod16 @AustinAbro321 -/hack/ @jeff-mccoy @Racer159 @Noxsios @lucasrod16 @AustinAbro321 -/.gitignore @jeff-mccoy @Racer159 @Noxsios @lucasrod16 @AustinAbro321 -/.golangci.yml @jeff-mccoy @Racer159 @Noxsios @lucasrod16 @AustinAbro321 -/.goreleaser.yml @jeff-mccoy @Racer159 @Noxsios @lucasrod16 @AustinAbro321 -/.grype.yaml @jeff-mccoy @Racer159 @Noxsios @lucasrod16 @AustinAbro321 -/Dockerfile @jeff-mccoy @Racer159 @Noxsios @lucasrod16 @AustinAbro321 -/renovate.json @jeff-mccoy @Racer159 @Noxsios @lucasrod16 @AustinAbro321 -/Makefile @jeff-mccoy @Racer159 @Noxsios @lucasrod16 @AustinAbro321 - -# Additional privileged files /CODEOWNERS @jeff-mccoy @austenbryan /cosign.pub @jeff-mccoy @austenbryan /LICENSE @jeff-mccoy @austenbryan diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000000..a7d8c48f7e --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,132 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, caste, color, religion, or sexual +identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the overall + community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or advances of + any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email address, + without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official email address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +zarf-dev-private@googlegroups.com. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of +actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or permanent +ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the +community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.1, available at +[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. + +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. + +For answers to common questions about this code of conduct, see the FAQ at +[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at +[https://www.contributor-covenant.org/translations][translations]. + +[homepage]: https://www.contributor-covenant.org +[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html +[Mozilla CoC]: https://github.com/mozilla/diversity +[FAQ]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations \ No newline at end of file diff --git a/Makefile b/Makefile index 08de34a14d..5683962afc 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,6 @@ # Provide a default value for the operating system architecture used in tests, e.g. " APPLIANCE_MODE=true|false make test-e2e ARCH=arm64" ARCH ?= amd64 -KEY ?= "" ###################################################################################### # Figure out which Zarf binary we should use based on the operating system we are on @@ -79,6 +78,7 @@ delete-packages: ## Delete all Zarf package tarballs in the project recursively # 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 + go mod tidy $(MAKE) $(BUILD_CLI_FOR_SYSTEM) build-cli-linux-amd: ## Build the Zarf CLI for Linux on AMD64 @@ -108,7 +108,7 @@ docs-and-schema: ## Generate the Zarf Documentation and Schema ZARF_CONFIG=hack/empty-config.toml hack/create-zarf-schema.sh lint-packages-and-examples: build ## Recursively lint all zarf.yaml files in the repo except for those dedicated to tests - hack/lint-all-zarf-packages.sh $(ZARF_BIN) + hack/lint-all-zarf-packages.sh $(ZARF_BIN) false # INTERNAL: a shim used to build the agent image only if needed on Windows using the `test` command init-package-local-agent: @@ -172,7 +172,7 @@ build-examples: ## Build all of the example packages @test -s ./build/zarf-package-component-webhooks-$(ARCH)-0.0.1.tar.zst || $(ZARF_BIN) package create examples/component-webhooks -o build -a $(ARCH) --confirm build-injector-linux: ## Build the Zarf injector for AMD64 and ARM64 - docker run --rm --user "$(id -u)":"$(id -g)" -v $$PWD/src/injector:/usr/src/zarf-injector -w /usr/src/zarf-injector rust:1.71.0-bookworm make build-injector-linux + docker run --rm --user "$(id -u)":"$(id -g)" -v $$PWD/src/injector:/usr/src/zarf-injector -w /usr/src/zarf-injector rust:1.71.0-bookworm make build-injector-linux list-sizes ## NOTE: Requires an existing cluster or the env var APPLIANCE_MODE=true .PHONY: test-e2e @@ -219,11 +219,11 @@ test-docs-and-schema: # INTERNAL: used to test for new CVEs that may have been introduced test-cves: - go run main.go tools sbom scan . -o json --exclude './docs-website' --exclude './examples' | grype --fail-on low + go run main.go tools sbom scan . -o json --exclude './site' --exclude './examples' | grype --fail-on low cve-report: ## Create a CVE report for the current project (must `brew install grype` first) @test -d ./build || mkdir ./build - go run main.go tools sbom scan . -o json --exclude './docs-website' --exclude './examples' | grype -o template -t hack/.templates/grype.tmpl > build/zarf-known-cves.csv + go run main.go tools sbom scan . -o json --exclude './site' --exclude './examples' | grype -o template -t hack/grype.tmpl > build/zarf-known-cves.csv -lint-go: ## Run revive to lint the go code (must `brew install revive` first) - revive -config revive.toml -exclude src/cmd/viper.go -formatter stylish ./src/... +lint-go: ## Run golang-ci-lint to lint the go code (must `brew install golang-ci-lint` first) + golangci-lint run diff --git a/README.md b/README.md index 4593158b2c..7ea6cf63c0 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![Go version](https://img.shields.io/github/go-mod/go-version/defenseunicorns/zarf?filename=go.mod)](https://go.dev/) [![Build Status](https://img.shields.io/github/actions/workflow/status/defenseunicorns/zarf/release.yml)](https://github.com/defenseunicorns/zarf/actions/workflows/release.yml) [![Zarf Documentation Status](https://api.netlify.com/api/v1/badges/fe846ae4-25fb-4274-9968-90782640ee9f/deploy-status)](https://app.netlify.com/sites/zarf-docs/deploys) -[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/defenseunicorns/zarf/badge)](https://api.securityscorecards.dev/projects/github.com/defenseunicorns/zarf) +[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/defenseunicorns/zarf/badge)](https://securityscorecards.dev/viewer/?uri=github.com/defenseunicorns/zarf) zarf logo @@ -12,6 +12,7 @@ [![Zarf Documentation](https://img.shields.io/badge/docs-docs.zarf.dev-775ba1)](https://docs.zarf.dev/) [![Zarf Slack Channel](https://img.shields.io/badge/k8s%20slack-zarf-40a3dd)](https://kubernetes.slack.com/archives/C03B6BJAUJ3) [![Community Meetups](https://img.shields.io/badge/community-meetups-22aebb)](https://github.com/defenseunicorns/zarf/issues/2202) +[![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg)](CODE_OF_CONDUCT.md) Zarf eliminates the [complexity of air gap software delivery](https://www.itopstimes.com/contain/air-gap-kubernetes-considerations-for-running-cloud-native-applications-without-the-cloud/) for Kubernetes clusters and cloud-native workloads using a declarative packaging strategy to support DevSecOps in offline and semi-connected environments. diff --git a/SECURITY.md b/SECURITY.md deleted file mode 100644 index ff723278fe..0000000000 --- a/SECURITY.md +++ /dev/null @@ -1,9 +0,0 @@ -# Security Policy - -## Supported Versions - -As Zarf has not yet reached v1.0.0, only the current latest minor release is supported. - -## Reporting a Vulnerability - -Please email `security-notice [at] defenseunicorns.com` to report a vulnerability. If you are unable to disclose details via email, please let us know and we can coordinate alternate communications. diff --git a/SUPPORT.md b/SUPPORT.md new file mode 100644 index 0000000000..690b7d8402 --- /dev/null +++ b/SUPPORT.md @@ -0,0 +1,47 @@ +# Support Guidelines + +We strive to create clear guidelines on communication to the Zarf team to provide a good community experience. + +## Questions +For guidance on using Zarf, [the documentation](https://docs.zarf.dev/) should cover most use cases. +For all questions documentation may not cover, we suggest utilizing [Github Discussions](https://github.com/defenseunicorns/zarf/discussions). + +## Standard Process +All code issues should be a [Github Issue](https://github.com/defenseunicorns/zarf/issues/new/choose) that follows the issue template. + +Following the templates provides the Zarf community a foundation of understanding to be able to assist quickly. +After an issue is made, this issue can be brought into other chanels such as the [Kubernetes Slack #Zarf](https://zarf.dev/slack) channel or the [bi-weekly Zarf Community Meeting](https://docs.zarf.dev/contribute/contributor-guide/). + + Github Issue + / \ + Zarf Slack Channel Zarf Community Call + + + +## Sensitive Information Process +For issues from those who are unable to post on Github, you may send an email using the following issue template filled out to [zarf-dev-private@googlegroups.com](zarf-dev-private@googlegroups.com) + +The response time to emails may be delayed as they are not able to receive community help, so we encourage participation into Github Issues as much as possible. + +``` +### Environment +Device and OS: +App version: +Kubernetes distro being used: +Other: + +### Steps to reproduce +1. + +### Expected result + +### Actual Result + +### Visual Proof (screenshots, videos, text, etc) + +### Severity/Priority + +### Additional Context +Add any other context or screenshots about the technical debt here. + +``` \ No newline at end of file diff --git a/go.mod b/go.mod index e09830fca2..9fe6c7a968 100644 --- a/go.mod +++ b/go.mod @@ -10,11 +10,10 @@ require ( github.com/AlecAivazis/survey/v2 v2.3.7 github.com/Masterminds/semver/v3 v3.2.1 github.com/agnivade/levenshtein v1.1.1 - github.com/alecthomas/jsonschema v0.0.0-20220216202328-9eeeec9d044b - github.com/anchore/clio v0.0.0-20240307182142-fb5fc4c9db3c + github.com/anchore/clio v0.0.0-20240408173007-3c4abf89e72f github.com/anchore/stereoscope v0.0.1 github.com/anchore/syft v0.100.0 - github.com/defenseunicorns/pkg/helpers v1.0.0 + github.com/defenseunicorns/pkg/helpers v1.1.1 github.com/defenseunicorns/pkg/oci v0.0.1 github.com/derailed/k9s v0.31.7 github.com/distribution/reference v0.5.0 @@ -28,6 +27,7 @@ require ( github.com/gofrs/flock v0.8.1 github.com/google/go-containerregistry v0.19.0 github.com/gosuri/uitable v0.0.4 + github.com/invopop/jsonschema v0.12.0 github.com/mholt/archiver/v3 v3.5.1 github.com/moby/moby v24.0.9+incompatible github.com/opencontainers/image-spec v1.1.0 @@ -45,8 +45,9 @@ require ( github.com/spf13/viper v1.18.2 github.com/stretchr/testify v1.9.0 github.com/xeipuuv/gojsonschema v1.2.0 - golang.org/x/crypto v0.21.0 - golang.org/x/term v0.18.0 + golang.org/x/crypto v0.23.0 + golang.org/x/sync v0.7.0 + golang.org/x/term v0.20.0 helm.sh/helm/v3 v3.14.2 k8s.io/api v0.29.1 k8s.io/apimachinery v0.29.1 @@ -64,12 +65,14 @@ require ( atomicgo.dev/cursor v0.2.0 // indirect atomicgo.dev/keyboard v0.2.9 // indirect atomicgo.dev/schedule v0.1.0 // indirect - cloud.google.com/go v0.112.1 // indirect - cloud.google.com/go/compute v1.24.0 // indirect - cloud.google.com/go/compute/metadata v0.2.3 // indirect - cloud.google.com/go/iam v1.1.6 // indirect - cloud.google.com/go/kms v1.15.7 // indirect - cloud.google.com/go/storage v1.38.0 // indirect + cloud.google.com/go v0.113.0 // indirect + cloud.google.com/go/auth v0.4.1 // indirect + cloud.google.com/go/auth/oauth2adapt v0.2.2 // indirect + cloud.google.com/go/compute/metadata v0.3.0 // indirect + cloud.google.com/go/iam v1.1.8 // indirect + cloud.google.com/go/kms v1.16.0 // indirect + cloud.google.com/go/longrunning v0.5.7 // indirect + cloud.google.com/go/storage v1.41.0 // indirect cuelabs.dev/go/oci/ociregistry v0.0.0-20231103182354-93e78c079a13 // indirect dario.cat/mergo v1.0.0 // indirect filippo.io/edwards25519 v1.1.0 // indirect @@ -135,7 +138,7 @@ require ( github.com/aquasecurity/go-version v0.0.0-20210121072130-637058cfe492 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/atotto/clipboard v0.1.4 // indirect - github.com/aws/aws-sdk-go v1.50.0 // indirect + github.com/aws/aws-sdk-go v1.53.1 // indirect github.com/aws/aws-sdk-go-v2 v1.24.1 // indirect github.com/aws/aws-sdk-go-v2/config v1.26.6 // indirect github.com/aws/aws-sdk-go-v2/credentials v1.16.16 // indirect @@ -154,6 +157,7 @@ require ( github.com/aws/smithy-go v1.19.0 // indirect github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20231024185945-8841054dbdb8 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect + github.com/bahlo/generic-list-go v0.2.0 // indirect github.com/becheran/wildmatch-go v1.0.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect @@ -161,6 +165,7 @@ require ( github.com/blang/semver/v4 v4.0.0 // indirect github.com/bmatcuk/doublestar/v2 v2.0.4 // indirect github.com/bmatcuk/doublestar/v4 v4.6.1 // indirect + github.com/buger/jsonparser v1.1.1 // indirect github.com/buildkite/agent/v3 v3.62.0 // indirect github.com/buildkite/go-pipeline v0.3.2 // indirect github.com/buildkite/interpolate v0.0.0-20200526001904-07f35b4ae251 // indirect @@ -257,7 +262,7 @@ require ( github.com/golang-jwt/jwt/v4 v4.5.0 // indirect github.com/golang-jwt/jwt/v5 v5.2.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/golang/protobuf v1.5.3 // indirect + github.com/golang/protobuf v1.5.4 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/google/btree v1.1.2 // indirect github.com/google/certificate-transparency-go v1.1.7 // indirect @@ -272,7 +277,7 @@ require ( github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/uuid v1.6.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect - github.com/googleapis/gax-go/v2 v2.12.1 // indirect + github.com/googleapis/gax-go/v2 v2.12.4 // indirect github.com/gookit/color v1.5.4 // indirect github.com/gorilla/mux v1.8.1 // indirect github.com/gorilla/websocket v1.5.0 // indirect @@ -280,7 +285,7 @@ require ( github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect - github.com/hashicorp/go-getter v1.7.3 // indirect + github.com/hashicorp/go-getter v1.7.4 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-retryablehttp v0.7.5 // indirect github.com/hashicorp/go-rootcerts v1.0.2 // indirect @@ -292,7 +297,6 @@ require ( github.com/hashicorp/hcl v1.0.1-vault-5 // indirect github.com/hashicorp/vault/api v1.10.0 // indirect github.com/huandu/xstrings v1.4.0 // indirect - github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0 // indirect github.com/iancoleman/strcase v0.3.0 // indirect github.com/imdario/mergo v0.3.16 // indirect github.com/in-toto/in-toto-golang v0.9.0 // indirect @@ -311,7 +315,7 @@ require ( github.com/kastenhq/goversion v0.0.0-20230811215019-93b2f8823953 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect - github.com/klauspost/compress v1.17.4 // indirect + github.com/klauspost/compress v1.17.8 // indirect github.com/klauspost/cpuid/v2 v2.2.5 // indirect github.com/klauspost/pgzip v1.2.6 // indirect github.com/knqyf263/go-apk-version v0.0.0-20200609155635-041fdbb8563f // indirect @@ -376,7 +380,7 @@ require ( github.com/opencontainers/selinux v1.11.0 // indirect github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/openvex/go-vex v0.2.5 // indirect - github.com/otiai10/copy v1.14.0 // indirect + github.com/otiai10/copy v1.14.0 github.com/owenrumney/go-sarif v1.1.2-0.20231003122901-1000f5e05554 // indirect github.com/package-url/packageurl-go v0.1.1 // indirect github.com/pborman/indent v1.2.1 // indirect @@ -436,13 +440,14 @@ require ( github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 // indirect github.com/tjfoc/gmsm v1.4.1 // indirect github.com/transparency-dev/merkle v0.0.2 // indirect - github.com/ulikunitz/xz v0.5.11 // indirect + github.com/ulikunitz/xz v0.5.12 // indirect github.com/vbatts/go-mtree v0.5.3 // indirect github.com/vbatts/tar-split v0.11.5 // indirect github.com/vifraa/gopom v1.0.0 // indirect github.com/wagoodman/go-partybus v0.0.0-20230516145632-8ccac152c651 // indirect github.com/wagoodman/go-presenter v0.0.0-20211015174752-f9c01afc824b // indirect github.com/wagoodman/go-progress v0.0.0-20230925121702-07e42b3cdba0 // indirect + github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect github.com/xanzy/go-gitlab v0.96.0 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect @@ -458,33 +463,31 @@ require ( go.mongodb.org/mongo-driver v1.13.1 // indirect go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352 // indirect go.opencensus.io v0.24.0 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.48.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.48.0 // indirect - go.opentelemetry.io/otel v1.23.0 // indirect - go.opentelemetry.io/otel/metric v1.23.0 // indirect - go.opentelemetry.io/otel/sdk v1.22.0 // indirect - go.opentelemetry.io/otel/trace v1.23.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.51.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0 // indirect + go.opentelemetry.io/otel v1.26.0 // indirect + go.opentelemetry.io/otel/metric v1.26.0 // indirect + go.opentelemetry.io/otel/sdk v1.26.0 // indirect + go.opentelemetry.io/otel/trace v1.26.0 // indirect go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect go.step.sm/crypto v0.42.1 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.26.0 // indirect golang.org/x/exp v0.0.0-20231108232855-2478ac86f678 // indirect golang.org/x/mod v0.14.0 // indirect - golang.org/x/net v0.22.0 // indirect - golang.org/x/oauth2 v0.17.0 // indirect - golang.org/x/sync v0.6.0 // indirect - golang.org/x/sys v0.18.0 // indirect - golang.org/x/text v0.14.0 // indirect + golang.org/x/net v0.25.0 // indirect + golang.org/x/oauth2 v0.20.0 // indirect + golang.org/x/sys v0.20.0 // indirect + golang.org/x/text v0.15.0 // indirect golang.org/x/time v0.5.0 // indirect golang.org/x/tools v0.16.1 // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect - google.golang.org/api v0.166.0 // indirect - google.golang.org/appengine v1.6.8 // indirect - google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240221002015-b0ce06bbee7c // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240221002015-b0ce06bbee7c // indirect - google.golang.org/grpc v1.61.1 // indirect - google.golang.org/protobuf v1.33.0 // indirect + google.golang.org/api v0.180.0 // indirect + google.golang.org/genproto v0.0.0-20240513163218-0867130af1f8 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240513163218-0867130af1f8 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240513163218-0867130af1f8 // indirect + google.golang.org/grpc v1.63.2 // indirect + google.golang.org/protobuf v1.34.1 // indirect gopkg.in/evanphx/json-patch.v5 v5.6.0 // indirect gopkg.in/go-jose/go-jose.v2 v2.6.3 // indirect gopkg.in/inf.v0 v0.9.1 // indirect diff --git a/go.sum b/go.sum index 7fe2a516c2..cb92210e44 100644 --- a/go.sum +++ b/go.sum @@ -39,8 +39,8 @@ cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w9 cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc= cloud.google.com/go v0.102.1/go.mod h1:XZ77E9qnTEnrgEOvr4xzfdX5TRo7fB4T2F4O6+34hIU= cloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRYtA= -cloud.google.com/go v0.112.1 h1:uJSeirPke5UNZHIb4SxfZklVSiWWVqW4oXlETwZziwM= -cloud.google.com/go v0.112.1/go.mod h1:+Vbu+Y1UU+I1rjmzeMOb/8RfkKJK2Gyxi1X6jJCZLo4= +cloud.google.com/go v0.113.0 h1:g3C70mn3lWfckKBiCVsAshabrDg01pQ0pnX1MNtnMkA= +cloud.google.com/go v0.113.0/go.mod h1:glEqlogERKYeePz6ZdkcLJ28Q2I6aERgDDErBg9GzO8= cloud.google.com/go/aiplatform v1.22.0/go.mod h1:ig5Nct50bZlzV6NvKaTwmplLLddFx0YReh9WfTO5jKw= cloud.google.com/go/aiplatform v1.24.0/go.mod h1:67UUvRBKG6GTayHKV8DBv2RtR1t93YRu5B1P3x99mYY= cloud.google.com/go/analytics v0.11.0/go.mod h1:DjEWCu41bVbYcKyvlws9Er60YE4a//bK6mnhWvQeFNI= @@ -55,6 +55,10 @@ cloud.google.com/go/asset v1.8.0/go.mod h1:mUNGKhiqIdbr8X7KNayoYvyc4HbbFO9URsjby cloud.google.com/go/assuredworkloads v1.5.0/go.mod h1:n8HOZ6pff6re5KYfBXcFvSViQjDwxFkAkmUFffJRbbY= cloud.google.com/go/assuredworkloads v1.6.0/go.mod h1:yo2YOk37Yc89Rsd5QMVECvjaMKymF9OP+QXWlKXUkXw= cloud.google.com/go/assuredworkloads v1.7.0/go.mod h1:z/736/oNmtGAyU47reJgGN+KVoYoxeLBoj4XkKYscNI= +cloud.google.com/go/auth v0.4.1 h1:Z7YNIhlWRtrnKlZke7z3GMqzvuYzdc2z98F9D1NV5Hg= +cloud.google.com/go/auth v0.4.1/go.mod h1:QVBuVEKpCn4Zp58hzRGvL0tjRGU0YqdRTdCHM1IHnro= +cloud.google.com/go/auth/oauth2adapt v0.2.2 h1:+TTV8aXpjeChS9M+aTtN/TjdQnzJvmzKFt//oWu7HX4= +cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q= cloud.google.com/go/automl v1.5.0/go.mod h1:34EjfoFGMZ5sgJ9EoLsRtdPSNZLcfflJR39VbVNS2M0= cloud.google.com/go/automl v1.6.0/go.mod h1:ugf8a6Fx+zP0D59WLhqgTDsQI9w07o64uf/Is3Nh5p8= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= @@ -77,10 +81,8 @@ cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU= cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U= cloud.google.com/go/compute v1.10.0/go.mod h1:ER5CLbMxl90o2jtNbGSbtfOpQKR0t15FOtRsugnLrlU= -cloud.google.com/go/compute v1.24.0 h1:phWcR2eWzRJaL/kOiJwfFsPs4BaKq1j6vnpZrc1YlVg= -cloud.google.com/go/compute v1.24.0/go.mod h1:kw1/T+h/+tK2LJK0wiPPx1intgdAM3j/g3hFDlscY40= -cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= -cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= +cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc= +cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= cloud.google.com/go/containeranalysis v0.5.1/go.mod h1:1D92jd8gRR/c0fGMlymRgxWD3Qw9C1ff6/T7mLgVL8I= cloud.google.com/go/containeranalysis v0.6.0/go.mod h1:HEJoiEIu+lEXM+k7+qLCci0h33lX3ZqoYFdmPcoO7s4= cloud.google.com/go/datacatalog v1.3.0/go.mod h1:g9svFY6tuR+j+hrTw3J2dNcmI0dzmSiyOzm8kpLq0a0= @@ -119,14 +121,16 @@ cloud.google.com/go/gkehub v0.10.0/go.mod h1:UIPwxI0DsrpsVoWpLB0stwKCP+WFVG9+y97 cloud.google.com/go/grafeas v0.2.0/go.mod h1:KhxgtF2hb0P191HlY5besjYm6MqTSTj3LSI+M+ByZHc= cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY= cloud.google.com/go/iam v0.5.0/go.mod h1:wPU9Vt0P4UmCux7mqtRu6jcpPAb74cP1fh50J3QpkUc= -cloud.google.com/go/iam v1.1.6 h1:bEa06k05IO4f4uJonbB5iAgKTPpABy1ayxaIZV/GHVc= -cloud.google.com/go/iam v1.1.6/go.mod h1:O0zxdPeGBoFdWW3HWmBxJsk0pfvNM/p/qa82rWOGTwI= -cloud.google.com/go/kms v1.15.7 h1:7caV9K3yIxvlQPAcaFffhlT7d1qpxjB1wHBtjWa13SM= -cloud.google.com/go/kms v1.15.7/go.mod h1:ub54lbsa6tDkUwnu4W7Yt1aAIFLnspgh0kPGToDukeI= +cloud.google.com/go/iam v1.1.8 h1:r7umDwhj+BQyz0ScZMp4QrGXjSTI3ZINnpgU2nlB/K0= +cloud.google.com/go/iam v1.1.8/go.mod h1:GvE6lyMmfxXauzNq8NbgJbeVQNspG+tcdL/W8QO1+zE= +cloud.google.com/go/kms v1.16.0 h1:1yZsRPhmargZOmY+fVAh8IKiR9HzCb0U1zsxb5g2nRY= +cloud.google.com/go/kms v1.16.0/go.mod h1:olQUXy2Xud+1GzYfiBO9N0RhjsJk5IJLU6n/ethLXVc= cloud.google.com/go/language v1.4.0/go.mod h1:F9dRpNFQmJbkaop6g0JhSBXCNlO90e1KWx5iDdxbWic= cloud.google.com/go/language v1.6.0/go.mod h1:6dJ8t3B+lUYfStgls25GusK04NLh3eDLQnWM3mdEbhI= cloud.google.com/go/lifesciences v0.5.0/go.mod h1:3oIKy8ycWGPUyZDR/8RNnTOYevhaMLqh5vLUXs9zvT8= cloud.google.com/go/lifesciences v0.6.0/go.mod h1:ddj6tSX/7BOnhxCSd3ZcETvtNr8NZ6t/iPhY2Tyfu08= +cloud.google.com/go/longrunning v0.5.7 h1:WLbHekDbjK1fVFD3ibpFFVoyizlLRl73I7YKuAKilhU= +cloud.google.com/go/longrunning v0.5.7/go.mod h1:8GClkudohy1Fxm3owmBGid8W0pSgodEMwEAztp38Xng= cloud.google.com/go/mediatranslation v0.5.0/go.mod h1:jGPUhGTybqsPQn91pNXw0xVHfuJ3leR1wj37oU3y1f4= cloud.google.com/go/mediatranslation v0.6.0/go.mod h1:hHdBCTYNigsBxshbznuIMFNe5QXEowAuNmmC7h8pu5w= cloud.google.com/go/memcache v1.4.0/go.mod h1:rTOfiGZtJX1AaFUrOgsMHX5kAzaTQ8azHiuDoTPzNsE= @@ -183,8 +187,8 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9 cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y= cloud.google.com/go/storage v1.23.0/go.mod h1:vOEEDNFnciUMhBeT6hsJIn3ieU5cFRmzeLgDvXzfIXc= cloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi80i/iqR2s= -cloud.google.com/go/storage v1.38.0 h1:Az68ZRGlnNTpIBbLjSMIV2BDcwwXYlRlQzis0llkpJg= -cloud.google.com/go/storage v1.38.0/go.mod h1:tlUADB0mAb9BgYls9lq+8MGkfzOXuLrnHXlpHmvFJoY= +cloud.google.com/go/storage v1.41.0 h1:RusiwatSu6lHeEXe3kglxakAmAbfV+rhtPqA6i8RBx0= +cloud.google.com/go/storage v1.41.0/go.mod h1:J1WCa/Z2FcgdEDuPUY8DxT5I+d9mFKsCepp5vR6Sq80= cloud.google.com/go/talent v1.1.0/go.mod h1:Vl4pt9jiHKvOgF9KoZo6Kob9oV4lwd/ZD5Cto54zDRw= cloud.google.com/go/talent v1.2.0/go.mod h1:MoNF9bhFQbiJ6eFD3uSsg0uBALw4n4gaCaEjBw9zo8g= cloud.google.com/go/videointelligence v1.6.0/go.mod h1:w0DIDlVRKtwPCn/C4iwZIJdvC69yInhW0cfi+p546uU= @@ -314,8 +318,6 @@ github.com/agnivade/levenshtein v1.1.1 h1:QY8M92nrzkmr798gCo3kmMyqXFzdQVpxLlGPRB github.com/agnivade/levenshtein v1.1.1/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo= github.com/alecthomas/assert/v2 v2.3.0 h1:mAsH2wmvjsuvyBvAmCtm7zFsBlb8mIHx5ySLVdDZXL0= github.com/alecthomas/assert/v2 v2.3.0/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ= -github.com/alecthomas/jsonschema v0.0.0-20220216202328-9eeeec9d044b h1:doCpXjVwui6HUN+xgNsNS3SZ0/jUZ68Eb+mJRNOZfog= -github.com/alecthomas/jsonschema v0.0.0-20220216202328-9eeeec9d044b/go.mod h1:/n6+1/DWPltRLWL/VKyUxg6tzsl5kHUCcraimt4vr60= github.com/alecthomas/participle/v2 v2.1.1 h1:hrjKESvSqGHzRb4yW1ciisFJ4p3MGYih6icjJvbsmV8= github.com/alecthomas/participle/v2 v2.1.1/go.mod h1:Y1+hAs8DHPmc3YUFzqllV+eSQ9ljPTk0ZkPMtEdAx2c= github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= @@ -370,8 +372,8 @@ github.com/aliyun/credentials-go v1.3.1 h1:uq/0v7kWrxmoLGpqjx7vtQ/s03f0zR//0br/x github.com/aliyun/credentials-go v1.3.1/go.mod h1:8jKYhQuDawt8x2+fusqa1Y6mPxemTsBEN04dgcAcYz0= github.com/anchore/bubbly v0.0.0-20231115134915-def0aba654a9 h1:p0ZIe0htYOX284Y4axJaGBvXHU0VCCzLN5Wf5XbKStU= github.com/anchore/bubbly v0.0.0-20231115134915-def0aba654a9/go.mod h1:3ZsFB9tzW3vl4gEiUeuSOMDnwroWxIxJelOOHUp8dSw= -github.com/anchore/clio v0.0.0-20240307182142-fb5fc4c9db3c h1:adhR3PBgGwEmcfLUt/IC9k3RPkyHcwcu6DDQjI0OPG0= -github.com/anchore/clio v0.0.0-20240307182142-fb5fc4c9db3c/go.mod h1:XqIExxQkDvte2P1pgdcFVtw4p8TkJML+bZmKgEE8XXU= +github.com/anchore/clio v0.0.0-20240408173007-3c4abf89e72f h1:2xJPf4KWzxFDwZK/yax+h8QHP2Gnn4fgXUCCeizhbyU= +github.com/anchore/clio v0.0.0-20240408173007-3c4abf89e72f/go.mod h1:1k7cwq2CeVH9dgAl0X/JBBfJ4y/E8h2R9KqByDkrCA4= github.com/anchore/fangs v0.0.0-20231201140849-5075d28d6d8b h1:L/djgY7ZbZ/38+wUtdkk398W3PIBJLkt1N8nU/7e47A= github.com/anchore/fangs v0.0.0-20231201140849-5075d28d6d8b/go.mod h1:TLcE0RE5+8oIx2/NPWem/dq1DeaMoC+fPEH7hoSzPLo= github.com/anchore/go-logger v0.0.0-20230725134548-c21dafa1ec5a h1:nJ2G8zWKASyVClGVgG7sfM5mwoZlZ2zYpIzN2OhjWkw= @@ -420,8 +422,8 @@ github.com/atomicgo/cursor v0.0.1/go.mod h1:cBON2QmmrysudxNBFthvMtN32r3jxVRIvzkU github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= github.com/aws/aws-sdk-go v1.44.122/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= -github.com/aws/aws-sdk-go v1.50.0 h1:HBtrLeO+QyDKnc3t1+5DR1RxodOHCGr8ZcrHudpv7jI= -github.com/aws/aws-sdk-go v1.50.0/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= +github.com/aws/aws-sdk-go v1.53.1 h1:15/i0m9rE8r1q3P4ooHCfZTJtkxwG2Dwqp9JhPaVbs0= +github.com/aws/aws-sdk-go v1.53.1/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= github.com/aws/aws-sdk-go-v2 v1.21.2/go.mod h1:ErQhvNuEMhJjweavOYhxVkn2RUx7kQXVATHrjKtxIpM= github.com/aws/aws-sdk-go-v2 v1.24.1 h1:xAojnj+ktS95YZlDf0zxWBkbFtymPeDP+rvUQIH3uAU= github.com/aws/aws-sdk-go-v2 v1.24.1/go.mod h1:LNh45Br1YAkEKaAqvmE1m8FUx6a5b/V0oAKV7of29b4= @@ -462,6 +464,8 @@ github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20231024185945- github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20231024185945-8841054dbdb8/go.mod h1:2JF49jcDOrLStIXN/j/K1EKRq8a8R2qRnlZA6/o/c7c= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= +github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= +github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/becheran/wildmatch-go v1.0.0 h1:mE3dGGkTmpKtT4Z+88t8RStG40yN9T+kFEGj2PZFSzA= github.com/becheran/wildmatch-go v1.0.0/go.mod h1:gbMvj0NtVdJ15Mg/mH9uxk2R1QCistMyU7d9KFzroX4= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -483,6 +487,8 @@ github.com/bradleyjkemp/cupaloy/v2 v2.8.0 h1:any4BmKE+jGIaMpnU8YgH/I2LPiLBufr6oM github.com/bradleyjkemp/cupaloy/v2 v2.8.0/go.mod h1:bm7JXdkRd4BHJk9HpwqAI8BoAY1lps46Enkdqw6aRX0= github.com/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuPGoOVeF2fE4Og9otCc70= github.com/bshuster-repo/logrus-logstash-hook v1.0.0/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= +github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= +github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/buildkite/agent/v3 v3.62.0 h1:yvzSjI8Lgifw883I8m9u8/L/Thxt4cLFd5aWPn3gg70= github.com/buildkite/agent/v3 v3.62.0/go.mod h1:jN6SokGXrVNNIpI0BGQ+j5aWeI3gin8F+3zwA5Q6gqM= github.com/buildkite/go-pipeline v0.3.2 h1:SW4EaXNwfjow7xDRPGgX0Rcx+dPj5C1kV9LKCLjWGtM= @@ -539,8 +545,6 @@ github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20231109132714-523115ebc101 h1:7To3pQ+pZo0i3dsWEbinPNFs5gPSBOsJtx3wTT94VBY= -github.com/cncf/xds/go v0.0.0-20231109132714-523115ebc101/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cockroachdb/apd/v3 v3.2.1 h1:U+8j7t0axsIgvQUqthuNm82HIrYXodOV2iWLWtEaIwg= github.com/cockroachdb/apd/v3 v3.2.1/go.mod h1:klXJcjp+FffLTHlhIG69tezTDvdP065naDsHzKhYSqc= github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE= @@ -593,8 +597,8 @@ github.com/daviddengcn/go-colortext v1.0.0 h1:ANqDyC0ys6qCSvuEK7l3g5RaehL/Xck9EX github.com/daviddengcn/go-colortext v1.0.0/go.mod h1:zDqEI5NVUop5QPpVJUxE9UO10hRnmkD5G4Pmri9+m4c= github.com/defenseunicorns/gojsonschema v0.0.0-20231116163348-e00f069122d6 h1:gwevOZ0fxT2nzM9hrtdPbsiOHjFqDRIYMzJHba3/G6Q= github.com/defenseunicorns/gojsonschema v0.0.0-20231116163348-e00f069122d6/go.mod h1:StKLYMmPj1R5yIs6CK49EkcW1TvUYuw5Vri+LRk7Dy8= -github.com/defenseunicorns/pkg/helpers v1.0.0 h1:0o3Rs+J/g0UemZHcENBS1Z2Qw2y4FIUUrGs75iEyPb4= -github.com/defenseunicorns/pkg/helpers v1.0.0/go.mod h1:F4S5VZLDrlNWQKklzv4v9tFWjjZNhxJ1gT79j4XiLwk= +github.com/defenseunicorns/pkg/helpers v1.1.1 h1:p3pKeK5SeFaoZUJZIX9sEsJqX1CGGMS8OpQMPgJtSqM= +github.com/defenseunicorns/pkg/helpers v1.1.1/go.mod h1:F4S5VZLDrlNWQKklzv4v9tFWjjZNhxJ1gT79j4XiLwk= github.com/defenseunicorns/pkg/oci v0.0.1 h1:EFRp3NeiwzhOWKpQ6mAxi0l9chnrAvDcIgjMr0o0fkM= github.com/defenseunicorns/pkg/oci v0.0.1/go.mod h1:zVBgRjckEAhfdvbnQrnfOP/3M/GYJkIgWtJtY7pjYdo= github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da h1:ZOjWpVsFZ06eIhnh4mkaceTiVoktdU67+M7KDHJ268M= @@ -678,8 +682,6 @@ github.com/envoyproxy/go-control-plane v0.10.1/go.mod h1:AY7fTTXNdv/aJ2O5jwpxAPO github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E9/baC+qXE/TeeyBRzgJDws= -github.com/envoyproxy/protoc-gen-validate v1.0.2 h1:QkIBuU5k+x7/QXPvPPnWXWlCdaBFApVqftFV6k087DA= -github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE= github.com/evanphx/json-patch v5.7.0+incompatible h1:vgGkfT/9f8zE6tvSCe74nfpAVDQ2tG6yudJd8LBksgI= github.com/evanphx/json-patch v5.7.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwCFad8crR9dcMQWvV9Hvulu6hwUh4tWPJnM= @@ -847,8 +849,8 @@ github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w github.com/golang-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1Jw= github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v1.1.2 h1:DVjP2PbBOzHyzA+dn3WhHIq4NdVu3Q+pvivFICf/7fo= -github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ= +github.com/golang/glog v1.2.0 h1:uCdmnmatrKCgMBlM4rMuJZWOkPDqdbZPnrMXDY4gI68= +github.com/golang/glog v1.2.0/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -880,8 +882,8 @@ github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= @@ -935,8 +937,8 @@ github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXi github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= -github.com/google/martian/v3 v3.3.2 h1:IqNFLAmvJOgVlpdEBiQbDc2EwKW77amAycfTuWKdfvw= -github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= +github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc= +github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= @@ -984,8 +986,8 @@ github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99 github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c= github.com/googleapis/gax-go/v2 v2.5.1/go.mod h1:h6B0KMMFNtI2ddbGJn3T3ZbwkeT6yqEF02fYlzkUCyo= github.com/googleapis/gax-go/v2 v2.6.0/go.mod h1:1mjbznJAPHFpesgE5ucqfYEscaz5kMdcIDwU/6+DDoY= -github.com/googleapis/gax-go/v2 v2.12.1 h1:9F8GV9r9ztXyAi00gsMQHNoF51xPZm8uj1dpYt2ZETM= -github.com/googleapis/gax-go/v2 v2.12.1/go.mod h1:61M8vcyyXR2kqKFxKrfA22jaA8JGF7Dc8App1U3H6jc= +github.com/googleapis/gax-go/v2 v2.12.4 h1:9gWcmF85Wvq4ryPFvGFaOgPIs1AQX0d0bcbGw4Z96qg= +github.com/googleapis/gax-go/v2 v2.12.4/go.mod h1:KYEYLorsnIGDi/rPC8b5TdlB9kbKoFubselGIoBMCwI= github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= github.com/gookit/color v1.2.5/go.mod h1:AhIE+pS6D4Ql0SQWbBeXPHw7gY0/sjHoA4s/n1KB7xg= github.com/gookit/color v1.4.2/go.mod h1:fqRyamkC1W8uxl+lxCQxOT09l/vYfZ+QeiX3rKQHCoQ= @@ -1020,8 +1022,8 @@ github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtng github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= -github.com/hashicorp/go-getter v1.7.3 h1:bN2+Fw9XPFvOCjB0UOevFIMICZ7G2XSQHzfvLUyOM5E= -github.com/hashicorp/go-getter v1.7.3/go.mod h1:W7TalhMmbPmsSMdNjD0ZskARur/9GJ17cfHTRtXV744= +github.com/hashicorp/go-getter v1.7.4 h1:3yQjWuxICvSpYwqSayAdKRFcvBl1y/vogCxczWSmix0= +github.com/hashicorp/go-getter v1.7.4/go.mod h1:W7TalhMmbPmsSMdNjD0ZskARur/9GJ17cfHTRtXV744= github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-hclog v1.0.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= @@ -1085,8 +1087,6 @@ github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpO github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU= github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= -github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0 h1:i462o439ZjprVSFSZLZxcsoAe592sZB1rci2Z8j4wdk= -github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0/go.mod h1:N0Wam8K1arqPXNWjMo21EXnBPOPp36vB07FNRdD2geA= github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI= github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= @@ -1101,6 +1101,8 @@ github.com/in-toto/in-toto-golang v0.9.0/go.mod h1:xsBVrVsHNsB61++S6Dy2vWosKhuA3 github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/invopop/jsonschema v0.12.0 h1:6ovsNSuvn9wEQVOyc72aycBMVQFKz7cPdMJn10CvzRI= +github.com/invopop/jsonschema v0.12.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jedisct1/go-minisign v0.0.0-20230811132847-661be99b8267 h1:TMtDYDHKYY15rFihtRfck/bfFqNfvcabqvXAFQfAUpY= @@ -1151,8 +1153,8 @@ github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYs github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= -github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= -github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU= +github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.10/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= @@ -1599,7 +1601,6 @@ github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.3.1-0.20190311161405-34c6fa2dc709/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -1650,8 +1651,8 @@ github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqri github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= -github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8= -github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc= +github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= github.com/vbatts/go-mtree v0.5.3 h1:S/jYlfG8rZ+a0bhZd+RANXejy7M4Js8fq9U+XoWTd5w= github.com/vbatts/go-mtree v0.5.3/go.mod h1:eXsdoPMdL2jcJx6HweWi9lYQxBsTp4lNhqqAjgkZUg8= @@ -1667,6 +1668,8 @@ github.com/wagoodman/go-presenter v0.0.0-20211015174752-f9c01afc824b h1:uWNQ0khA github.com/wagoodman/go-presenter v0.0.0-20211015174752-f9c01afc824b/go.mod h1:ewlIKbKV8l+jCj8rkdXIs361ocR5x3qGyoCSca47Gx8= github.com/wagoodman/go-progress v0.0.0-20230925121702-07e42b3cdba0 h1:0KGbf+0SMg+UFy4e1A/CPVvXn21f1qtWdeJwxZFoQG8= github.com/wagoodman/go-progress v0.0.0-20230925121702-07e42b3cdba0/go.mod h1:jLXFoL31zFaHKAAyZUh+sxiTDFe1L1ZHrcK2T1itVKA= +github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc= +github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= github.com/xanzy/go-gitlab v0.96.0 h1:LGkZ+wSNMRtHIBaYE4Hq3dZVjprwHv3Y1+rhKU3WETs= github.com/xanzy/go-gitlab v0.96.0/go.mod h1:ETg8tcj4OhrB84UEgeE8dSuV/0h4BBL1uOV/qK0vlyI= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= @@ -1733,12 +1736,12 @@ go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/contrib/exporters/autoexport v0.46.1 h1:ysCfPZB9AjUlMa1UHYup3c9dAOCMQX/6sxSfPBUoxHw= go.opentelemetry.io/contrib/exporters/autoexport v0.46.1/go.mod h1:ha0aiYm+DOPsLHjh0zoQ8W8sLT+LJ58J3j47lGpSLrU= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.48.0 h1:P+/g8GpuJGYbOp2tAdKrIPUX9JO02q8Q0YNlHolpibA= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.48.0/go.mod h1:tIKj3DbO8N9Y2xo52og3irLsPI4GW02DSMtrVgNMgxg= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.48.0 h1:doUP+ExOpH3spVTLS0FcWGLnQrPct/hD/bCPbDRUEAU= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.48.0/go.mod h1:rdENBZMT2OE6Ne/KLwpiXudnAsbdrdBaqBvTN8M8BgA= -go.opentelemetry.io/otel v1.23.0 h1:Df0pqjqExIywbMCMTxkAwzjLZtRf+bBKLbUcpxO2C9E= -go.opentelemetry.io/otel v1.23.0/go.mod h1:YCycw9ZeKhcJFrb34iVSkyT0iczq/zYDtZYFufObyB0= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.51.0 h1:A3SayB3rNyt+1S6qpI9mHPkeHTZbD7XILEqWnYZb2l0= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.51.0/go.mod h1:27iA5uvhuRNmalO+iEUdVn5ZMj2qy10Mm+XRIpRmyuU= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0 h1:Xs2Ncz0gNihqu9iosIZ5SkBbWo5T8JhhLJFMQL1qmLI= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0/go.mod h1:vy+2G/6NvVMpwGX/NyLqcC41fxepnuKHk16E6IZUcJc= +go.opentelemetry.io/otel v1.26.0 h1:LQwgL5s/1W7YiiRwxf03QGnWLb2HW4pLiAhaA5cZXBs= +go.opentelemetry.io/otel v1.26.0/go.mod h1:UmLkJHUAidDval2EICqBMbnAd0/m2vmpf/dAM+fvFs4= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.44.0 h1:jd0+5t/YynESZqsSyPz+7PAFdEop0dlN0+PkyHYo8oI= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.44.0/go.mod h1:U707O40ee1FpQGyhvqnzmCJm1Wh6OX6GGBVn0E6Uyyk= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.44.0 h1:bflGWrfYyuulcdxf14V6n9+CoQcu5SAAdHmDPAJnlps= @@ -1755,14 +1758,14 @@ go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v0.44.0 h1:dEZWPjVN22urgY go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v0.44.0/go.mod h1:sTt30Evb7hJB/gEk27qLb1+l9n4Tb8HvHkR0Wx3S6CU= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.21.0 h1:VhlEQAPp9R1ktYfrPk5SOryw1e9LDDTZCbIPFrho0ec= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.21.0/go.mod h1:kB3ufRbfU+CQ4MlUcqtW8Z7YEOBeK2DJ6CmR5rYYF3E= -go.opentelemetry.io/otel/metric v1.23.0 h1:pazkx7ss4LFVVYSxYew7L5I6qvLXHA0Ap2pwV+9Cnpo= -go.opentelemetry.io/otel/metric v1.23.0/go.mod h1:MqUW2X2a6Q8RN96E2/nqNoT+z9BSms20Jb7Bbp+HiTo= -go.opentelemetry.io/otel/sdk v1.22.0 h1:6coWHw9xw7EfClIC/+O31R8IY3/+EiRFHevmHafB2Gw= -go.opentelemetry.io/otel/sdk v1.22.0/go.mod h1:iu7luyVGYovrRpe2fmj3CVKouQNdTOkxtLzPvPz1DOc= +go.opentelemetry.io/otel/metric v1.26.0 h1:7S39CLuY5Jgg9CrnA9HHiEjGMF/X2VHvoXGgSllRz30= +go.opentelemetry.io/otel/metric v1.26.0/go.mod h1:SY+rHOI4cEawI9a7N1A4nIg/nTQXe1ccCNWYOJUrpX4= +go.opentelemetry.io/otel/sdk v1.26.0 h1:Y7bumHf5tAiDlRYFmGqetNcLaVUZmh4iYfmGxtmz7F8= +go.opentelemetry.io/otel/sdk v1.26.0/go.mod h1:0p8MXpqLeJ0pzcszQQN4F0S5FVjBLgypeGSngLsmirs= go.opentelemetry.io/otel/sdk/metric v1.21.0 h1:smhI5oD714d6jHE6Tie36fPx4WDFIg+Y6RfAY4ICcR0= go.opentelemetry.io/otel/sdk/metric v1.21.0/go.mod h1:FJ8RAsoPGv/wYMgBdUJXOm+6pzFY3YdljnXtv1SBE8Q= -go.opentelemetry.io/otel/trace v1.23.0 h1:37Ik5Ib7xfYVb4V1UtnT97T1jI+AoIYkJyPkuL4iJgI= -go.opentelemetry.io/otel/trace v1.23.0/go.mod h1:GSGTbIClEsuZrGIzoEHqsVfxgn5UkggkflQwDScNUsk= +go.opentelemetry.io/otel/trace v1.26.0 h1:1ieeAUb4y0TE26jUFrCIXKpTuVK7uJGN9/Z/2LP5sQA= +go.opentelemetry.io/otel/trace v1.26.0/go.mod h1:4iDxvGDQuUkHve82hJJ8UqrwswHYsZuWCBllGV2U2y0= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= @@ -1804,8 +1807,8 @@ golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58 golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= -golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= -golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -1913,8 +1916,8 @@ golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ= -golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= -golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1941,8 +1944,8 @@ golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= golang.org/x/oauth2 v0.1.0/go.mod h1:G9FE4dLTsbXUu90h/Pf85g4w1D+SSAgR+q46nJZ8M4A= -golang.org/x/oauth2 v0.17.0 h1:6m3ZPmLEFdVxKKWnKq4VqZ60gutO35zm+zrAHVmHyDQ= -golang.org/x/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA= +golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo= +golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1958,8 +1961,8 @@ golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= -golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -2062,8 +2065,8 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -2077,8 +2080,8 @@ golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= -golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= -golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= +golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -2095,8 +2098,9 @@ golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -2224,8 +2228,8 @@ google.golang.org/api v0.96.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ google.golang.org/api v0.97.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= google.golang.org/api v0.98.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= google.golang.org/api v0.100.0/go.mod h1:ZE3Z2+ZOr87Rx7dqFsdRQkRBk36kDtp/h+QpHbB7a70= -google.golang.org/api v0.166.0 h1:6m4NUwrZYhAaVIHZWxaKjw1L1vNAjtMwORmKRyEEo24= -google.golang.org/api v0.166.0/go.mod h1:4FcBc686KFi7QI/U51/2GKKevfZMpM17sCdibqe/bSA= +google.golang.org/api v0.180.0 h1:M2D87Yo0rGBPWpo1orwfCLehUUL6E7/TYe5gvMQWDh4= +google.golang.org/api v0.180.0/go.mod h1:51AiyoEg1MJPSZ9zvklA8VnRILPXxn1iVen9v25XHAE= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -2233,8 +2237,6 @@ google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= -google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -2340,12 +2342,12 @@ google.golang.org/genproto v0.0.0-20221010155953-15ba04fc1c0e/go.mod h1:3526vdqw google.golang.org/genproto v0.0.0-20221014173430-6e2ab493f96b/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= google.golang.org/genproto v0.0.0-20221025140454-527a21cfbd71/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= -google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 h1:9+tzLLstTlPTRyJTh+ah5wIMsBW5c4tQwGTN3thOW9Y= -google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:mqHbVIp48Muh7Ywss/AD6I5kNVKZMmAa/QEW58Gxp2s= -google.golang.org/genproto/googleapis/api v0.0.0-20240221002015-b0ce06bbee7c h1:9g7erC9qu44ks7UK4gDNlnk4kOxZG707xKm4jVniy6o= -google.golang.org/genproto/googleapis/api v0.0.0-20240221002015-b0ce06bbee7c/go.mod h1:5iCWqnniDlqZHrd3neWVTOwvh/v6s3232omMecelax8= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240221002015-b0ce06bbee7c h1:NUsgEN92SQQqzfA+YtqYNqYmB3DMMYLlIwUZAQFVFbo= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240221002015-b0ce06bbee7c/go.mod h1:H4O17MA/PE9BsGx3w+a+W2VOLLD1Qf7oJneAoU6WktY= +google.golang.org/genproto v0.0.0-20240513163218-0867130af1f8 h1:XpH03M6PDRKTo1oGfZBXu2SzwcbfxUokgobVinuUZoU= +google.golang.org/genproto v0.0.0-20240513163218-0867130af1f8/go.mod h1:OLh2Ylz+WlYAJaSBRpJIJLP8iQP+8da+fpxbwNEAV/o= +google.golang.org/genproto/googleapis/api v0.0.0-20240513163218-0867130af1f8 h1:W5Xj/70xIA4x60O/IFyXivR5MGqblAb8R3w26pnD6No= +google.golang.org/genproto/googleapis/api v0.0.0-20240513163218-0867130af1f8/go.mod h1:vPrPUTsDCYxXWjP7clS81mZ6/803D8K4iM9Ma27VKas= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240513163218-0867130af1f8 h1:mxSlqyb8ZAHsYDCfiXN1EDdNTdvjUJSLY+OnAUtYNYA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240513163218-0867130af1f8/go.mod h1:I7Y+G38R2bu5j1aLzfFmQfTcU/WnFuqDwLZAbvKTKpM= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -2382,8 +2384,8 @@ google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACu google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= -google.golang.org/grpc v1.61.1 h1:kLAiWrZs7YeDM6MumDe7m3y4aM6wacLzM1Y/wiLP9XY= -google.golang.org/grpc v1.61.1/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= +google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM= +google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= @@ -2400,8 +2402,8 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= +google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/hack/check-vulnerabilities.sh b/hack/check-vulnerabilities.sh new file mode 100755 index 0000000000..903e59a01a --- /dev/null +++ b/hack/check-vulnerabilities.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash + +set -euo pipefail + +MAIN_BRANCH="main" +TARGET_BRANCH=$(git rev-parse --abbrev-ref HEAD) +echo "target branch is $TARGET_BRANCH" + +mkdir -p build + +git checkout $MAIN_BRANCH +go run main.go tools sbom scan . -o json --exclude './site' --exclude './examples' > build/main-syft.json + +git checkout $TARGET_BRANCH +cat build/main-syft.json | grype -o template -t hack/compare.tmpl > build/main.json +go run main.go tools sbom scan . -o json --exclude './site' --exclude './examples' | grype -o template -t hack/compare.tmpl > build/target.json + + +result=$(jq --slurp '.[0] - .[1]' build/target.json build/main.json | jq '[.[] | select(.severity != "Low" and .severity != "Medium")]') + +echo "CVEs on $MAIN_BRANCH are $(cat build/main.json | jq )" +echo "CVEs on $TARGET_BRANCH are $(cat build/target.json | jq)" + +if [[ "$result" == "[]" ]]; then + echo "no new vulnerabilities on $TARGET_BRANCH" + exit 0 +else + echo "new CVEs have been added with IDs $result" + exit 1 +fi diff --git a/hack/check-zarf-docs-and-schema.sh b/hack/check-zarf-docs-and-schema.sh index 216d64b0ea..95fee01c1f 100755 --- a/hack/check-zarf-docs-and-schema.sh +++ b/hack/check-zarf-docs-and-schema.sh @@ -1,4 +1,6 @@ -#!/usr/bin/env sh +#!/usr/bin/env bash + +set -euo pipefail if [ -z "$(git status -s ./site/src/content/docs/commands/ ./zarf.schema.json)" ]; then echo "Success!" diff --git a/hack/compare.tmpl b/hack/compare.tmpl new file mode 100644 index 0000000000..469720459f --- /dev/null +++ b/hack/compare.tmpl @@ -0,0 +1,7 @@ +[ + {{- $length := len .Matches -}} + {{- range $index, $match := .Matches -}} + { "id": "{{$match.Vulnerability.ID}}", "severity": "{{$match.Vulnerability.Severity}}" } + {{ if lt (add $index 1) $length }},{{ end }} + {{- end -}} +] diff --git a/hack/create-zarf-schema.sh b/hack/create-zarf-schema.sh index aa9a8fc79c..91fa767bf5 100755 --- a/hack/create-zarf-schema.sh +++ b/hack/create-zarf-schema.sh @@ -1,8 +1,21 @@ -#!/usr/bin/env sh +#!/usr/bin/env bash + +set -euo pipefail # Create the json schema for the zarf.yaml go run main.go internal gen-config-schema > zarf.schema.json # Adds pattern properties to all definitions to allow for yaml extensions -jq '.definitions |= map_values(. + {"patternProperties": {"^x-": {}}})' zarf.schema.json > temp_zarf.schema.json +jq ' + def addPatternProperties: + . + + if has("properties") then + {"patternProperties": {"^x-": {}}} + else + {} + end; + + walk(if type == "object" then addPatternProperties else . end) +' zarf.schema.json > temp_zarf.schema.json + mv temp_zarf.schema.json zarf.schema.json diff --git a/hack/empty-config.toml b/hack/empty-config.toml index e69de29bb2..65c006efc8 100644 --- a/hack/empty-config.toml +++ b/hack/empty-config.toml @@ -0,0 +1,3 @@ +# This is here so it can be used during certain Zarf commands +# such as `internal gen-cli-docs` where we prefer an empty config +# as opposed to the init package config at the base of the repo diff --git a/hack/.templates/grype.tmpl b/hack/grype.tmpl similarity index 100% rename from hack/.templates/grype.tmpl rename to hack/grype.tmpl diff --git a/hack/lint-all-zarf-packages.sh b/hack/lint-all-zarf-packages.sh index 5f41d3d887..85b734460a 100755 --- a/hack/lint-all-zarf-packages.sh +++ b/hack/lint-all-zarf-packages.sh @@ -1,4 +1,6 @@ -#!/bin/bash +#!/usr/bin/env bash + +set -euo pipefail ZARF_BIN=$1 LINT_SRC_TEST=$2 @@ -11,7 +13,7 @@ find "." -type f -name 'zarf.yaml' | while read -r yaml_file; do if [[ "$dir" == *src/test/* ]] && [ "$LINT_SRC_TEST" != true ]; then continue fi - echo "Running 'zarf prepare lint' in directory: $dir" - $ZARF_BIN prepare lint "$dir" + echo "Running 'zarf dev lint' in directory: $dir" + $ZARF_BIN dev lint "$dir" echo "---" done diff --git a/packages/zarf-registry/chart/templates/_helpers.tpl b/packages/zarf-registry/chart/templates/_helpers.tpl index 3e570d3078..76cd228c13 100644 --- a/packages/zarf-registry/chart/templates/_helpers.tpl +++ b/packages/zarf-registry/chart/templates/_helpers.tpl @@ -23,6 +23,37 @@ We truncate at 63 chars because some Kubernetes name fields are limited to this {{- end -}} {{- end -}} +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "docker-registry.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Selector labels +*/}} +{{- define "docker-registry.selectorLabels" -}} +app.kubernetes.io/name: {{ include "docker-registry.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end -}} + +{{/* +Common labels +*/}} +{{- define "docker-registry.labels" -}} +{{ include "docker-registry.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/part-of: {{ .Release.Name }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +helm.sh/chart: {{ include "docker-registry.chart" . }} +{{- with .Values.customLabels }} +{{ toYaml . }} +{{- end }} +{{- end -}} + {{/* Merge all configmaps */}} @@ -34,3 +65,14 @@ Merge all configmaps {{ .Values.caBundle | indent 6 }} {{- end }} {{- end -}} + +{{/* +Create the name of the service account to use +*/}} +{{- define "docker-registry.serviceAccountName" -}} +{{- if .Values.serviceAccount.create -}} + {{ default (include "docker-registry.fullname" .) .Values.serviceAccount.name }} +{{- else -}} + {{ default "default" .Values.serviceAccount.name }} +{{- end -}} +{{- end -}} diff --git a/packages/zarf-registry/chart/templates/deployment.yaml b/packages/zarf-registry/chart/templates/deployment.yaml index ce8126f60a..e0e878eb82 100644 --- a/packages/zarf-registry/chart/templates/deployment.yaml +++ b/packages/zarf-registry/chart/templates/deployment.yaml @@ -26,6 +26,7 @@ spec: annotations: checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }} spec: + serviceAccountName: {{ include "docker-registry.serviceAccountName" . }} {{- if .Values.imagePullSecrets }} imagePullSecrets: {{ toYaml .Values.imagePullSecrets | indent 8 }} @@ -83,7 +84,11 @@ spec: subPath: ca-certificates.crt readOnly: true {{- end }} +{{- if .Values.affinity.enabled }} affinity: +{{- if .Values.affinity.custom }} +{{ toYaml .Values.affinity.custom | indent 8 }} +{{- else }} {{- if (eq "ReadWriteMany" .Values.persistence.accessMode) }} podAntiAffinity: {{- else }} @@ -99,6 +104,12 @@ spec: values: - {{ template "docker-registry.name" . }} topologyKey: kubernetes.io/hostname +{{- end }} +{{- end }} +{{- if .Values.tolerations}} + tolerations: +{{ toYaml .Values.tolerations | indent 8 }} +{{- end }} volumes: - name: config secret: diff --git a/packages/zarf-registry/chart/templates/hpa.yaml b/packages/zarf-registry/chart/templates/hpa.yaml index cdb4468872..7045a709d3 100644 --- a/packages/zarf-registry/chart/templates/hpa.yaml +++ b/packages/zarf-registry/chart/templates/hpa.yaml @@ -13,8 +13,13 @@ spec: apiVersion: apps/v1 kind: Deployment name: {{ template "docker-registry.fullname" . }} +{{- if .Values.autoscaling.mapReplicasToNodes }} + minReplicas: {{ len (lookup "v1" "Node" "" "") }} + maxReplicas: {{ add (len (lookup "v1" "Node" "" "")) 4 }} +{{- else }} minReplicas: {{ .Values.autoscaling.minReplicas }} maxReplicas: {{ .Values.autoscaling.maxReplicas }} +{{- end }} metrics: - type: Resource resource: diff --git a/packages/zarf-registry/chart/templates/serviceaccount.yaml b/packages/zarf-registry/chart/templates/serviceaccount.yaml new file mode 100644 index 0000000000..7103f59fd9 --- /dev/null +++ b/packages/zarf-registry/chart/templates/serviceaccount.yaml @@ -0,0 +1,13 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + namespace: {{ .Values.namespace | default .Release.Namespace }} + name: {{ template "docker-registry.serviceAccountName" . }} + labels: + {{- include "docker-registry.labels" . | nindent 4 }} +{{- if .Values.serviceAccount.annotations }} + annotations: +{{ toYaml .Values.serviceAccount.annotations | indent 4 }} +{{- end }} +{{- end -}} diff --git a/packages/zarf-registry/chart/values.yaml b/packages/zarf-registry/chart/values.yaml index 527578eca0..77a88650e1 100644 --- a/packages/zarf-registry/chart/values.yaml +++ b/packages/zarf-registry/chart/values.yaml @@ -48,8 +48,15 @@ secrets: podDisruptionBudget: minAvailable: 1 +affinity: + enabled: true + custom: {} + +tolerations: [] + autoscaling: enabled: true + mapReplicasToNodes: false minReplicas: 1 maxReplicas: 5 targetCPUUtilizationPercentage: 80 @@ -75,3 +82,8 @@ extraEnvVars: [] ## Additional ENV variables to set # - name: REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY # value: "/var/lib/example" + +serviceAccount: + create: false + name: "" + annotations: {} diff --git a/packages/zarf-registry/registry-values.yaml b/packages/zarf-registry/registry-values.yaml index 891cb9a891..98a02f38fc 100644 --- a/packages/zarf-registry/registry-values.yaml +++ b/packages/zarf-registry/registry-values.yaml @@ -34,14 +34,31 @@ fullnameOverride: "zarf-docker-registry" podLabels: zarf.dev/agent: "ignore" +affinity: + enabled: ###ZARF_VAR_REGISTRY_AFFINITY_ENABLE### + custom: + ###ZARF_VAR_REGISTRY_AFFINITY_CUSTOM### + +tolerations: + ###ZARF_VAR_REGISTRY_TOLERATIONS### + autoscaling: enabled: ###ZARF_VAR_REGISTRY_HPA_ENABLE### + mapReplicasToNodes: ###ZARF_VAR_REGISTRY_HPA_AUTO_SIZE### minReplicas: "###ZARF_VAR_REGISTRY_HPA_MIN###" maxReplicas: "###ZARF_VAR_REGISTRY_HPA_MAX###" - targetCPUUtilizationPercentage: 80 + targetCPUUtilizationPercentage: ###ZARF_VAR_REGISTRY_HPA_TARGET_CPU### caBundle: | ###ZARF_VAR_REGISTRY_CA_BUNDLE### extraEnvVars: ###ZARF_VAR_REGISTRY_EXTRA_ENVS### + +serviceAccount: + # Specifies whether a service account should be created + create: ###ZARF_VAR_REGISTRY_CREATE_SERVICE_ACCOUNT### + # The name of the service account to use. If name not set and create is true, a name is generated using fullname template + name: "###ZARF_VAR_REGISTRY_SERVICE_ACCOUNT_NAME###" + annotations: + ###ZARF_VAR_REGISTRY_SERVICE_ACCOUNT_ANNOTATIONS### diff --git a/packages/zarf-registry/zarf.yaml b/packages/zarf-registry/zarf.yaml index 38a13006c1..2da03ccda6 100644 --- a/packages/zarf-registry/zarf.yaml +++ b/packages/zarf-registry/zarf.yaml @@ -58,6 +58,41 @@ variables: default: "" autoIndent: true + - name: REGISTRY_CREATE_SERVICE_ACCOUNT + description: Toggle the creation of a new service account for the registry + default: "false" + + - name: REGISTRY_SERVICE_ACCOUNT_NAME + description: The name of the service account to use. If not set and create is true, a name is generated using fullname template + default: "" + + - name: REGISTRY_SERVICE_ACCOUNT_ANNOTATIONS + description: Map of annotations to add to the created service account + default: "" + autoIndent: true + + - name: REGISTRY_AFFINITY_ENABLE + description: Enable pod affinity for the registry + default: "true" + + - name: REGISTRY_AFFINITY_CUSTOM + description: Custom pod affinity yaml block for the registry + default: "" + autoIndent: true + + - name: REGISTRY_TOLERATIONS + description: Custom tolerations array for the registry + default: "" + autoIndent: true + + - name: REGISTRY_HPA_AUTO_SIZE + description: Enable to set min and max replicas based on amount of nodes + default: "false" + + - name: REGISTRY_HPA_TARGET_CPU + description: The target CPU utilization percentage for the registry + default: "80" + constants: - name: REGISTRY_IMAGE value: "###ZARF_PKG_TMPL_REGISTRY_IMAGE###" diff --git a/renovate.json b/renovate.json index d8f6f4bb6b..91e8dd55b0 100644 --- a/renovate.json +++ b/renovate.json @@ -82,6 +82,16 @@ "https:\\/\\/github.com\\/(?[\\w\\/\\-\\.\\+\\%]+?)\\/releases\\/download\\/(?[\\w\\/\\-\\.\\+\\%]+?)\\/" ], "datasourceTemplate": "github-releases" + }, + { + "fileMatch": [ + "\\.*\\.ya?ml$" + ], + "matchStrings": [ + "# renovate: datasource=github-tags depName=anchore/grype versioning=semver\n\\s*VERSION: (?v[\\d.]+)" + ], + "datasourceTemplate": "github-tags", + "depNameTemplate": "anchore/grype" } ] } diff --git a/revive.toml b/revive.toml deleted file mode 100644 index ebda8f5cb3..0000000000 --- a/revive.toml +++ /dev/null @@ -1,31 +0,0 @@ -ignoreGeneratedHeader = false -severity = "warning" -confidence = 0.8 -errorCode = 0 -warningCode = 0 -formatter = "stylish" - -[rule.blank-imports] -[rule.context-as-argument] -[rule.context-keys-type] -[rule.dot-imports] -[rule.error-return] -[rule.error-strings] -[rule.error-naming] -[rule.exported] -[rule.if-return] -[rule.increment-decrement] -[rule.var-naming] -[rule.var-declaration] -[rule.package-comments] -[rule.range] -[rule.receiver-naming] -[rule.time-naming] -[rule.unexported-return] -[rule.indent-error-flow] -[rule.errorf] -[rule.empty-block] -[rule.superfluous-else] -[rule.unused-parameter] -[rule.unreachable-code] -[rule.redefines-builtin-id] diff --git a/site/astro.config.ts b/site/astro.config.ts index fb53176741..6e54f621a0 100644 --- a/site/astro.config.ts +++ b/site/astro.config.ts @@ -7,7 +7,7 @@ import remarkGemoji from "remark-gemoji"; // https://astro.build/config export default defineConfig({ redirects: { - '/docs/zarf-overview': '/' + "/docs/zarf-overview": "/", }, markdown: { remarkPlugins: [remarkGemoji], @@ -37,7 +37,10 @@ export default defineConfig({ src: "./src/assets/zarf-logo-header.svg", replacesTitle: true, }, - customCss: ["./src/styles/custom.css"], + customCss: [ + "./src/styles/custom.css", + "@fontsource/source-code-pro/400.css", + ], lastUpdated: true, sidebar: [ { diff --git a/site/package-lock.json b/site/package-lock.json index a9ccc6756c..faa4544f51 100644 --- a/site/package-lock.json +++ b/site/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@astrojs/check": "^0.5.10", "@astrojs/starlight": "^0.21.2", + "@fontsource/source-code-pro": "^5.0.17", "astro": "^4.5.12", "mermaid": "^10.9.0", "rehype-autolink-headings": "^7.1.0", @@ -20,6 +21,9 @@ "markdownlint-cli2": "^0.12.1", "remark-gemoji": "^8.0.0", "yaml": "^2.4.1" + }, + "engines": { + "node": ">=20.11.1" } }, "node_modules/@ampproject/remapping": { @@ -1351,6 +1355,11 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/@fontsource/source-code-pro": { + "version": "5.0.17", + "resolved": "https://registry.npmjs.org/@fontsource/source-code-pro/-/source-code-pro-5.0.17.tgz", + "integrity": "sha512-Q5GfthInOTW+Ek5k97/LH6FWLmD+IwHwBEEXF+KStvsyPwSZz+0ssdWJfz/ZRaTZxOk7FwlRgysXAWo41fq6bQ==" + }, "node_modules/@img/sharp-darwin-arm64": { "version": "0.33.3", "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.3.tgz", diff --git a/site/package.json b/site/package.json index 82f61d39c9..22dafe180d 100644 --- a/site/package.json +++ b/site/package.json @@ -17,6 +17,7 @@ "dependencies": { "@astrojs/check": "^0.5.10", "@astrojs/starlight": "^0.21.2", + "@fontsource/source-code-pro": "^5.0.17", "astro": "^4.5.12", "mermaid": "^10.9.0", "rehype-autolink-headings": "^7.1.0", diff --git a/site/src/components/SchemaItemProperties.astro b/site/src/components/SchemaItemProperties.astro index 0b62b83ecd..d6bb1d8409 100644 --- a/site/src/components/SchemaItemProperties.astro +++ b/site/src/components/SchemaItemProperties.astro @@ -14,7 +14,7 @@ const includesElement = (key: string) => { const json = await import("../assets/zarf.schema.json"); // @ts-expect-error - We don't import a TS type for the schema, but we know it's structured correctly -const itemSchema = json.definitions[item]; +const itemSchema = json["$defs"][item]; if (unwrap) { unwrap.forEach((wrapped: string) => { diff --git a/site/src/content/docs/commands/zarf_package_inspect.md b/site/src/content/docs/commands/zarf_package_inspect.md index def3845bb1..7a27daff9f 100644 --- a/site/src/content/docs/commands/zarf_package_inspect.md +++ b/site/src/content/docs/commands/zarf_package_inspect.md @@ -22,6 +22,7 @@ zarf package inspect [ PACKAGE_SOURCE ] [flags] ``` -h, --help help for inspect + --list-images List images in the package (prints to stdout) -s, --sbom View SBOM contents while inspecting the package --sbom-out string Specify an output directory for the SBOMs from the inspected Zarf package ``` diff --git a/site/src/content/docs/commands/zarf_tools_yq_eval.md b/site/src/content/docs/commands/zarf_tools_yq_eval.md index 6a6f57b8e1..215184cf00 100644 --- a/site/src/content/docs/commands/zarf_tools_yq_eval.md +++ b/site/src/content/docs/commands/zarf_tools_yq_eval.md @@ -28,10 +28,10 @@ zarf tools yq eval [expression] [yaml_file1]... [flags] ``` # Reads field under the given path for each file -zarf tools yq e '.a.b' f1.yml f2.yml +zarf tools yq e '.a.b' f1.yml f2.yml # Prints out the file -zarf tools yq e sample.yaml +zarf tools yq e sample.yaml # Pipe from STDIN ## use '-' as a filename to pipe from STDIN @@ -39,10 +39,10 @@ cat file2.yml | zarf tools yq e '.a.b' file1.yml - file3.yml # Creates a new yaml document ## Note that editing an empty file does not work. -zarf tools yq e -n '.a.b.c = "cat"' +zarf tools yq e -n '.a.b.c = "cat"' -# Update a file inplace -zarf tools yq e '.a.b = "cool"' -i file.yaml +# Update a file in place +zarf tools yq e '.a.b = "cool"' -i file.yaml ``` diff --git a/site/src/content/docs/faq.mdx b/site/src/content/docs/faq.mdx index 1e662a262a..e4bca91bba 100644 --- a/site/src/content/docs/faq.mdx +++ b/site/src/content/docs/faq.mdx @@ -24,34 +24,6 @@ No, the Zarf binary and init package can be downloaded from the [Releases Page]( Zarf is statically compiled and written in [Go](https://golang.org/) and [Rust](https://www.rust-lang.org/), so it has no external dependencies. For Linux, Zarf can bring a Kubernetes cluster using [K3s](https://k3s.io/). For Mac and Windows, Zarf can leverage any available local or remote cluster the user has access to. Currently, the K3s installation Zarf performs does require a [Systemd](https://en.wikipedia.org/wiki/Systemd) based system and `root` (not just `sudo`) access. -## What is the Zarf Agent? - -The Zarf Agent is a [Kubernetes Mutating Webhook](https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#mutatingadmissionwebhook) that is installed into the cluster during `zarf init`. The Agent is responsible for modifying [Kubernetes PodSpec](https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#PodSpec) objects [Image](https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#Container.Image) fields to point to the Zarf Registry. This allows the cluster to pull images from the Zarf Registry instead of the internet without having to modify the original image references. The Agent also modifies [Flux GitRepository](https://fluxcd.io/docs/components/source/gitrepositories/) objects to point to the local Git Server. - -## Why doesn't the Zarf Agent create secrets it needs in the cluster? - -During early discussions and [subsequent decision](https://github.com/defenseunicorns/zarf/blob/main/adr/0005-mutating-webhook.md) to use a Mutating Webhook, we decided to not have the Agent create any secrets in the cluster. This is to avoid the Agent having to have more privileges than it needs as well as to avoid collisions with Helm. The Agent today simply responds to requests to patch PodSpec and GitRepository objects. - -The Agent does not need to create any secrets in the cluster. Instead, during `zarf init` and `zarf package deploy`, secrets are automatically created as [Helm Postrender Hook](https://helm.sh/docs/topics/advanced/#post-rendering) for any namespaces Zarf sees. If you have resources managed by [Flux](https://fluxcd.io/) that are not in a namespace managed by Zarf, you can either create the secrets manually or include a manifest to create the namespace in your package and let Zarf create the secrets for you. - -## How can a Kubernetes resource be excluded from the Zarf Agent? - -Resources can be excluded at the namespace or resources level by adding the `zarf.dev/agent: ignore` label. - -## What happens to resources that exist in the cluster before `zarf init`? - -During the [`zarf init`](/commands/zarf_init) operation, the Zarf Agent will patch any existing namespaces with the `zarf.dev/agent: ignore` label to prevent the Agent from modifying any resources in that namespace. This is done because there is no way to guarantee the images used by pods in existing namespaces are available in the Zarf Registry. - -If you would like to adopt pre-existing resources into a Zarf deployment you can use the `--adopt-existing-resources` flag on [`zarf package deploy`](/commands/zarf_package_deploy/) to adopt those resources into the Helm Releases that Zarf manages (including namespaces). This will add the requisite annotations and labels to those resources and drop the `zarf.dev/agent: ignore` label from any namespaces specified by those resources. - -:::note - -Zarf will refuse to adopt the Kubernetes [initial namespaces](https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/#initial-namespaces). It is recommended that you do not deploy resources into the `default` or `kube-*` namespaces with Zarf. - -Additionally, when adopting resources, you should ensure that the namespaces you are adopting are dedicated to Zarf, or that you go back and manually add the `zarf.dev/agent: ignore` label to any non-Zarf managed resources in those namespaces (and ensure that updates to those resources do not strip that label) otherwise you may see [ImagePullBackOff](https://kubernetes.io/docs/concepts/containers/images/#imagepullbackoff) errors. - -::: - ## How can I improve the speed of loading large images from Docker on `zarf package create`? Due to some limitations with how Docker provides access to local image layers, `zarf package create` has to rely on `docker save` under the hood which is [very slow overall](https://github.com/defenseunicorns/zarf/issues/1214) and also takes a long time to report progress. We experimented with many ways to improve this, but for now recommend leveraging a local docker registry to speed up the process. diff --git a/site/src/content/docs/ref/create.mdx b/site/src/content/docs/ref/create.mdx index 422a887035..8ce19e4e95 100644 --- a/site/src/content/docs/ref/create.mdx +++ b/site/src/content/docs/ref/create.mdx @@ -27,7 +27,7 @@ graph TD A11 -->|Yes| A12(add seed image)-->A13 A11 -->|No| A13 - subgraph + subgraph "" A13(add each component)-->A13 A13 --> A14(run each '.actions.onCreate.before'):::action-->A14 A14 --> A15(load '.charts')-->A16 @@ -61,44 +61,10 @@ graph TD `}/> -## Types of Zarf Packages -There are two types of Zarf Packages, the `ZarfInitConfig` and the `ZarfPackageConfig`, which are distinguished by the `kind:` field and specified in the `zarf.yaml` file. +## Package Templates -Throughout the rest of the documentation, we will refer to the `ZarfInitConfig` as an `init config` package or `init` package, and to the `ZarfPackageConfig` as simply a "package". - -### ZarfInitConfig - -The init package is used to initialize a cluster, making it ready for deployment of other Zarf Packages. It must be executed once on each cluster that you want to deploy another package onto, even if multiple clusters share the same host. For additional information on the init package, we provide detailed documentation on the Zarf ['init' package page](/ref/init-package/). - -If there is no running cluster, the init package can be used to create one. It has a deployable K3s cluster component that can be optionally deployed on your machine. Usually, an init package is the first Zarf Package to be deployed on a cluster as other packages often depend on the services installed or configured by the init package. If you want to install a K8s cluster with Zarf, but you don't want to use K3s as your cluster, you will need to create or find another Zarf Package that will stand up your cluster before you run the zarf init command. - -:::note - -To clarify, in most cases, the first Zarf Package you deploy onto a cluster should be the init package since other packages often depend on the services it installs onto your cluster. However, if you don't want to use the K3s distribution included in the init package or if you already have a preferred K8s distribution, you can deploy the distribution package first, followed by the init package, and then any other packages you want. This only applies if you don't have a K8s cluster yet. - -::: - -During the initialization process, Zarf will seed your cluster with a container registry to store images that other packages may require. Additionally, the init package has the option to deploy other features to your cluster, such as a Git server to manage your repositories or a PLG logging stack that allows you to monitor the applications running on your cluster. - -#### Using the init-package - -To initialize your cluster, you need to run the command `zarf init`. This command will search for a file with the specific naming convention: `zarf-init-{ARCHITECTURE}-{VERSION}.tar.zst`. The architecture must match that of the cluster you are deploying to. If you are deploying to a cluster with a different architecture, you will need to specify the name of the architecture you are deploying on with the `-a` flag. For example, if you are on an arm64 machine but are deploying on an amd64 machine, you will run `zarf init -a amd64`. - -Init packages can also be run with `zarf package deploy zarf-init-{ARCHITECTURE}-{VERSION}.tar.zst`. - -You do not need to create init configs by yourself unless you want to customize how your cluster is installed/configured. For example, if you want to use the init process to install a specifically configured K3s cluster onto your host machine, you can create a specific package to do that before running the init package. - -### ZarfPackageConfig - -`ZarfPackageConfig` refers to any package that is not an init package and is used to define specific capabilities that you want to deploy onto your initialized cluster. - -To deploy a Zarf Package, you can use the command `zarf package deploy`. This will prompt you to select from all of the files in your current directory that match the name `zarf-package-*.tar.zst`. Alternatively, if you already know which package you want to deploy, you can simply use the command `zarf package deploy {PACKAGE_NAME}`. - -During the deployment process, Zarf will leverage the infrastructure created during the 'init' process (such as the Docker registry and Git server) to push all the necessary images and repositories required for the package to operate. - - -### Package Templates +Package configuration templates can be used during `zarf package create` to configure the `zarf.yaml` file. Templates are baked into the Zarf package so they cannot be changed post create. :::caution @@ -106,8 +72,9 @@ During the deployment process, Zarf will leverage the infrastructure created dur ::: -You can also specify `zarf.yaml` package configuration templates at package create time by including `###_ZARF_PKG_TMPL_*###` as the value for any string-type data in your package definition. These values are discovered during `zarf package create` and will always be prompted for if not using `--confirm` or `--set`. An example of this is below: +You can specify `zarf.yaml` templates at package create time by including `###_ZARF_PKG_TMPL_*###` as the value for any string-type data in your package definition. Template values can be defined using [config files](/ref/config-files/) or the `--set` flag. If a template is undefined during `zarf package create` if will always be prompted for unless `--confirm` is used. +An example of templates is below: ```yaml kind: ZarfPackageConfig metadata: @@ -138,4 +105,3 @@ You can only template string values in this way as non-string values will not ma Additionally, you cannot template the component import path using package configuration templates ::: - diff --git a/site/src/content/docs/ref/deploy.mdx b/site/src/content/docs/ref/deploy.mdx index 2a19268c4c..3b92ee1070 100644 --- a/site/src/content/docs/ref/deploy.mdx +++ b/site/src/content/docs/ref/deploy.mdx @@ -50,7 +50,7 @@ graph TD B12(prompt to confirm components)-->B13 B13(prompt to choose components in '.group')-->B14 - subgraph + subgraph "" B14(deploy each component)-->B14 B14 --> B15(run each '.actions.onDeploy.before'):::action-->B15 B15 --> B16(copy '.files')-->B17 @@ -120,3 +120,41 @@ $ zarf connect [service name] You can also specify a package locally, or via oci such as `zarf package deploy oci://defenseunicorns/dos-games:1.0.0-$(uname -m) --key=https://zarf.dev/cosign.pub` ::: + +## Installing, Upgrading, and Rolling Back with Helm + +Zarf deploys resources in Kubernetes using [Helm's Go SDK](https://helm.sh/docs/topics/advanced/#go-sdk), and converts manifests into Helm charts for installation. + +If no existing Helm releases match a given chart in the cluster, Zarf executes a `helm install`. + +Should matching releases exist, a `helm upgrade` is performed. + +### Handling CustomResourceDefinitions (CRDs) + + - CRDs are _included_ during `helm install` to support Kubernetes Operator deployments + - CRDs are _excluded_ during `helm upgrade` due to [Helm's lack of support for upgrading CRDs](https://helm.sh/docs/chart_best_practices/custom_resource_definitions/#some-caveats-and-explanations) + +### Waiting for Resource Readiness + +By default, Zarf waits for all resources to deploy successfully during install, upgrade, and rollback operations. + +You can override this behavior during install and upgrade by setting the `noWait: true` key under the `charts` and `manifests` fields. + +### Timeout Settings + +The default timeout for Helm operations in Zarf is 15 minutes. + +Use the `--timeout` flag with `zarf init` and `zarf package deploy` to modify the timeout duration. + +### Retry Policy + +Zarf retries install and upgrade operations up to three times by default if an error occurs. + +Use the `--retries` flag with `zarf init` and `zarf package deploy` to change the number of retry attempts. + +### Rollback Process + +If attempts to upgrade a chart fail, Zarf tries to roll the chart back to its last successful release. During this rollback process: + + - Any resources created during the failed upgrade attempt are deleted (`helm rollback --cleanup-on-fail`) + - Resource updates are forced through delete and recreate if needed (`helm rollback --force`) diff --git a/site/src/content/docs/ref/init-package.mdx b/site/src/content/docs/ref/init-package.mdx index 62954eb6f5..c2f98e1e6e 100644 --- a/site/src/content/docs/ref/init-package.mdx +++ b/site/src/content/docs/ref/init-package.mdx @@ -5,66 +5,187 @@ sidebar: --- import Mermaid from "@components/Mermaid.astro"; +import Details from "@components/Details.astro"; -The 'init' package is a special Zarf Package (denoted by `kind: ZarfInitConfig` in its `zarf.yaml`) that initializes a cluster with the requisite air gap services when running `zarf init`. This allows future Zarf Packages to store any required resources (i.e. container images and git repositories) so that they can be retrieved later. +In a traditional Kubernetes deployment, clusters pull resources (e.g. cluster images, OCI artifacts, Git repos) from external sources. -The default 'init' package that Zarf ships is defined in the `zarf.yaml` that lives at the [root of the Zarf repository](https://github.com/defenseunicorns/zarf/blob/main/zarf.yaml), and is constructed from composed components that provide a foundation for customization. If you would like to change the behavior of the 'init' package you can do so by modifying this `zarf.yaml` or any of the composed components that it references and running `zarf package create` at the root of the repository. You can learn more about creating a custom init package in the [Creating a Custom 'init' Package Tutorial](/tutorials/8-custom-init-packages). +However, in an air-gapped environment, these external providers are not available, or exist at different locations to their references within Kubernetes manifests. -Upon deployment, the init package creates a `zarf` namespace within your K8s cluster and deploys pods, services, and secrets to that namespace based on the components selected for deployment. +Zarf solves this problem with its 'init' package, a special Zarf Package (traditionally deployed first) that provides the necessary mechanisms to enable air-gapped Kubernetes, and deliver DevSecOps across air-gaps. -## Required Component +:::note[tldr;] -Zarf's mutation capabilities require that the [`zarf-agent`](/faq#what-is-the-zarf-agent) component of the init package is deployed and active within the cluster, meaning that it cannot be disabled and is always running. This component intercepts requests to create resources and uses the `zarf-state` secret to mutate them to point to their air gap equivalents. It is automatically deployed whenever a `zarf init` command is executed. +Don't care about the details and just want to get deploying as quickly as possible? Run the following after connecting to a cluster: -| Component | Description | -| ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | -| zarf-agent | A Kubernetes mutating webhook installed during `zarf init` that converts Pod specs and Flux GitRepository objects to match their air gap equivalents. | +```bash +zarf tools download-init +zarf init --confirm +``` -:::note +Want to see a guided `zarf init`? Check out the [Zarf Init tutorial](/tutorials/1-initializing-a-k8s-cluster/). -The `zarf-agent` will mutate any resources it sees that have not already been patched and don't have the `zarf.dev/agent: ignore` label applied. This label is automatically applied to all namespaces that exist prior to `zarf init`, and will prevent the `zarf-agent` from mutating system resources. You can manually apply this label to any additional namespaces or resources after the fact to prevent the `zarf-agent` from acting upon them. See the FAQ entry to learn more about [what happens to resources that were deployed prior to `zarf init`](/faq#what-happens-to-resources-that-exist-in-the-cluster-before-zarf-init). +View all init options w/ [`zarf init --help`](/commands/zarf_init/). ::: ## Core Components -In addition to the required `zarf-agent` component, Zarf also offers components that provide additional functionality and can be enabled as needed based on your desired end-state. +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-registry`](#zarf-registry) +- [`zarf-agent`](#zarf-agent) + +### `zarf-injector` and `zarf-seed-registry` + +One of the most challenging aspects of deploying into an air-gapped environment is the initial bootstrapping of the cluster. + +A cluster needs a registry to pull images from, but spinning up a registry requires an image to be pulled from a registry - chicken, meet egg. + +To ensure that our approach is distro-agnostic, the Zarf team developed a unique solution to seed the container registry into the cluster, populate said registry, and redirect cluster resources to use the air-gapped registry. + +Shoving random data into a cluster is generally a bad idea, and an antipattern overall to containerization. However in the case of Zarf, and air-gapped environments, certain liberties must be taken. + +While there is no distro-agnostic method to inject images into a cluster, every cluster has support for `configmaps`. However, the size of a `configmap` is limited to 1MB (technically only limited by whatever is configured in `etcd`, the default is 1MB), and the `registry:2` image is around 10MB (as of this writing). So we split the `registry:2` image into chunks and inject them into the cluster as `configmaps`. -In most scenarios, Zarf will also deploy an internal registry using the three components described below. However, Zarf can be configured to use an already existing registry with the `--registry-*` flags when running `zarf init` (detailed information on all `zarf init` command flags can be found in the [zarf init CLI](/commands/zarf_init/) section). This option skips the injector and seed process, and will not deploy a registry inside of the cluster. Instead, it uploads any images to the externally configured registry. +But then we have another problem of how to reassemble the image on the other side, as we don't have any consistent image that exists in the cluster that would have such utilities. This is where the `zarf-injector` Rust binary comes in. -| Components | Description | -| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------- | -| zarf-injector | Adds a Rust binary to the working directory to be injected into the cluster during registry bootstrapping. | -| zarf-seed-registry | Adds a temporary container registry so Zarf can bootstrap itself into the cluster. | -| zarf-registry | Adds a long-lived container registry service—[docker registry](https://docs.docker.com/registry/)—into the cluster. | +> For compiling the `zarf-injector` binary, refer to its [README.md](https://github.com/defenseunicorns/zarf/tree/main/src/injector/README.md). + +The `zarf-injector` binary is statically compiled and injected into the cluster as a `configmap` along with the chunks of the `registry:2` image. During the `zarf-seed-registry`'s deployment, the `zarf-injector` binary is run in a pod that mounts the `configmaps` and reassembles the `registry:2` image. It then hosts a temporary, pull-only Docker registry implemented in Rust so that a real registry can be deployed into the cluster from the hosted `registry:2` image. + +> While the `zarf-injector` component *must* be defined and deployed *before* the `zarf-seed-registry` component, the magic doesn't start until `zarf-seed-registry` is deployed. The `zarf-injector` component for the most part just copies the `zarf-injector` binary to `###ZARF_TEMP###/zarf-injector`. + +When `zarf init` deploys the `zarf-seed-registry` component, the following happens: + +1. Zarf injects the `zarf-injector` binary and the `registry:2` image chunks into the cluster. +2. Zarf connects to the cluster and grabs a pod that is running an image that is already present in the cluster. +3. Zarf spins up a pod using the existing image, mounts the `configmaps` that contain the `zarf-injector` binary and the `registry:2` image chunks and runs the `zarf-injector` binary. :::note -Given the registry is a core part of any Kubernetes deployment you MUST either specify an external registry with the `--registry-*` flags or use the injected registry which is why it cannot be selected with `--components` like the components below. +Doing this keeps Zarf cluster agnostic, however does require that the kubelet be able to reach out to a cluster NodePort service, which may require changes to firewall configurations like allowing UDP traffic between nodes if using something like VXLAN tunneling. ::: +4. The `zarf-injector` binary reassembles the `registry:2` image and hosts a temporary registry that the cluster can pull from. +5. The `docker-registry` chart in the `zarf-seed-registry` component is then deployed, with its `image.repository` set to the temporary registry that the `zarf-injector` binary is hosting (consumed as the `###ZARF_SEED_REGISTRY###` built-in variable set at runtime). +6. Once the `docker-registry` chart is deployed, the `zarf-seed-registry` component is marked as complete and the `zarf-injector` pod is removed from the cluster. +7. Deployment proceeds to the `zarf-registry` component. + :::note -The Zarf Registry is initially injected as a series of config maps that bootstraps itself into the cluster and then binds to a NodePort to allow the kubelet to pull images and setup the final registry deployment. Doing this keeps Zarf cluster agnostic however does require that the kubelet be able to reach out to a cluster NodePort service which may require changes to firewall configurations like allowing UDP traffic between nodes if using something like VXLAN tunneling. +The `registry:2` image and the Zarf Agent image can be configured with a custom init package using the `registry_image_*` and `agent_image_*` templates defined in the Zarf repo's [zarf-config.toml](https://github.com/defenseunicorns/zarf/blob/main/zarf-config.toml). This allows you to swap them for enterprise provided / hardened versions if desired such as those provided by [Iron Bank](https://repo1.dso.mil/dsop/opensource/defenseunicorns/zarf/zarf-agent). ::: +### `zarf-registry` + +The `zarf-registry` component is a long-lived container registry service that is deployed into the cluster. + +It leverages the same `docker-registry` chart used in `zarf-seed-registry` but with a few key differences: + +1. The `image.repository` is set to the value of the built-in variable `###ZARF_REGISTRY###` which is set at runtime to the registry hosted by `zarf-seed-registry`. +{/* you know, why DO we do this? if we kept the repository the same, and kept running the injector, couldnt this handle cluster full-deaths??? (ofc would need to tweak the zarf-agent to not mutate the image.repository for the docker-registry chart under the zarf namespace, but i think it might be doable) */} +2. A `connect` manifest for running [`zarf connect registry`](/commands/zarf_connect_registry/) to tunnel to the Zarf Registry. +3. A configmap to satisfy [KEP-1755](https://github.com/kubernetes/enhancements/tree/master/keps/sig-cluster-lifecycle/generic/1755-communicating-a-local-registry) + :::tip You can further customize how the registry behaves by setting variables such as `REGISTRY_PVC_SIZE` with a [config file](/ref/config-files/) or `--set` on `zarf init`. -To see a full list of `variables` you can view the [zarf.yaml that defines the registry](https://github.com/defenseunicorns/zarf/blob/main/packages/zarf-registry/zarf.yaml). +To see a full list of `variables` you can view the [`zarf.yaml` that defines the registry](https://github.com/defenseunicorns/zarf/blob/main/packages/zarf-registry/zarf.yaml). ::: -Beyond the registry, their are also fully-optional components available for the init package. Many of these also have external configurations you can set with `zarf init` (such as `--git-*`), but these components provide an easy way to get started in environments where these core services are needed and may not already exist. +#### Using External Registries + +Zarf can be configured to use an already existing registry with the `--registry-*` flags when running [`zarf init`](/commands/zarf_init/). + +This option skips the injector and seed process, and will not deploy a registry inside of the cluster. Instead, it pushes any images contained in the package to the externally configured registry. + +:::note + +Given the registry is a core part of any Kubernetes deployment you MUST either specify an external registry with the `--registry-*` flags or use the injected registry. + +::: + +#### Making the Registry Highly-Available + +By default, the registry included in the init package creates a `ReadWriteOnce` PVC and is only scheduled to run on one node at a time. + +This setup is usually enough for smaller and simpler deployments. However, for larger deployments or those where nodes are frequently restarted or updated, you may want to make the registry highly-available. + +This approach requires certain prerequisites, such as a storage class that supports `ReadWriteMany`, or being in an environment that allows you to configure the registry to use an S3-compatible backend. + +Additionally, you must provide custom configuration to the registry to ensure it is distributed across all nodes and has the appropriate number of replicas. Below is an example [configuration file](/ref/config-files/) using a ReadWriteMany storage class: + +```yaml +# zarf-config.yaml +package: + deploy: + set: + REGISTRY_PVC_ENABLED: "true" + REGISTRY_PVC_ACCESS_MODE: "ReadWriteMany" + REGISTRY_HPA_AUTO_SIZE: "true" + REGISTRY_AFFINITY_CUSTOM: | + podAntiAffinity: + preferredDuringSchedulingIgnoredDuringExecution: + - weight: 100 + podAffinityTerm: + labelSelector: + matchExpressions: + - key: app + operator: In + values: + - docker-registry + topologyKey: kubernetes.io/hostname +``` + +Notably, the `REGISTRY_AFFINITY_CUSTOM` variable overrides the default pod anti-affinity, and `REGISTRY_HPA_AUTO_SIZE` automatically adjusts the minimum and maximum replicas for the registry based on the number of nodes in the cluster. If you prefer to manually set the minimum and maximum replicas, you can use `REGISTRY_HPA_MIN` and `REGISTRY_HPA_MAX` to specify the desired values. + +### `zarf-agent` + +{/* TODO: document and flesh out how the mutations operate for the agent */} + +The `zarf-agent` is a [Kubernetes Mutating Webhook](https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#mutatingadmissionwebhook) that intercepts requests to create resources and uses the `zarf-state` secret to mutate them to point to their air-gapped equivalents. + +The `zarf-agent` is responsible for modifying [Kubernetes PodSpec](https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#PodSpec) objects [Image](https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#Container.Image) fields to point to the Zarf Registry. + +This allows the cluster to pull images from the Zarf Registry instead of the internet without having to modify the original image references. + +The `zarf-agent` also modifies [Flux GitRepository](https://fluxcd.io/docs/components/source/gitrepositories/) objects to point to the local Git Server. + +> Support for mutating `Application` and `Repository` objects in ArgoCD is in [`beta`](/roadmap#beta) and should tested on non-production clusters before being deployed to production clusters. + +:::note + +During the [`zarf init`](/commands/zarf_init) operation, the Zarf Agent will add the `zarf.dev/agent: ignore` label to prevent the Agent from modifying any resources in that namespace. This is done because there is no way to guarantee the images used by pods in existing namespaces are available in the Zarf Registry. + +If you would like to adopt pre-existing resources into a Zarf deployment you can use the `--adopt-existing-resources` flag on [`zarf package deploy`](/commands/zarf_package_deploy/) to adopt those resources into the Helm Releases that Zarf manages (including namespaces). This will add the requisite annotations and labels to those resources and drop the `zarf.dev/agent: ignore` label from any namespaces specified by those resources. + +::: + +#### Excluding Resources from `zarf-agent` + +Resources can be excluded at the namespace or resources level by adding the `zarf.dev/agent: ignore` label. + +Zarf will refuse to adopt the Kubernetes [initial namespaces](https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/#initial-namespaces) (`default`, `kube-*`, etc...). This is because these namespaces are critical to the operation of the cluster and should not be managed by Zarf. + +Additionally, when adopting resources, ensure that the namespaces specified are dedicated to Zarf, or add the `zarf.dev/agent: ignore` label to any non-Zarf managed resources in those namespaces (and ensure that updates to those resources do not strip that label) otherwise [ImagePullBackOff](https://kubernetes.io/docs/concepts/containers/images/#imagepullbackoff) errors may occur. + +The Agent does not need to create any secrets in the cluster. Instead, during `zarf init` and `zarf package deploy`, secrets are automatically created in a [Helm Postrender Hook](https://helm.sh/docs/topics/advanced/#post-rendering) for any namespaces Zarf sees. If you have resources managed by [Flux](https://fluxcd.io/) that are not in a namespace managed by Zarf, you can either create the secrets manually or include a manifest to create the namespace in your package and let Zarf create the secrets for you. + +## Optional Components + +The Zarf team maintains some optional components in the default 'init' package. | Components | Description | | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| k3s | REQUIRES ROOT (not sudo). Installs a lightweight Kubernetes Cluster on the local host—[K3s](https://k3s.io/)—and configures it to start up on boot. | -| logging | Adds a log monitoring stack—[promtail/loki/grafana (aka PLG)](https://github.com/grafana/loki)—into the cluster. | -| git-server | Adds a [GitOps](https://about.gitlab.com/topics/gitops/)-compatible source control service—[Gitea](https://gitea.io/en-us/)—into the cluster. | +| k3s | REQUIRES ROOT (not sudo). Installs a lightweight Kubernetes Cluster on the local host [K3s](https://k3s.io/) and configures it to start up on boot. | +| logging | Adds a log monitoring stack [promtail/loki/grafana (aka PLG)](https://github.com/grafana/loki) into the cluster. | +| git-server | Adds a [GitOps](https://about.gitlab.com/topics/gitops/)-compatible source control service [Gitea](https://gitea.io/en-us/) into the cluster. | There are two ways to deploy these optional components. First, you can provide a comma-separated list of components to the `--components` flag, such as `zarf init --components k3s,git-server --confirm`, or, you can choose to exclude the `--components` and `--confirm` flags and respond with a yes (`y`) or no (`n`) for each optional component when interactively prompted. @@ -76,7 +197,7 @@ There are two ways to deploy these optional components. First, you can provide a :::tip -The `k3s` component included in Zarf differs from the default `k3s` install in that it disables the installation of `traefik` out of the box. This was done so that people could more intentionally choose if they wanted `traefik` or another ingress provider (or no ingress at all) depending on their needs. If you would like to return `k3s` to its defaults, you can set the `K3S_ARGS` zarf variable to an empty string: +The `k3s` component included differs from the default `k3s` install in that it disables the installation of `traefik` out of the box. This was done so that people could more intentionally choose if they wanted `traefik` or another ingress provider (or no ingress at all) depending on their needs. If you would like to return `k3s` to its defaults, you can set the `K3S_ARGS` Zarf variable to an empty string: ```text root@machine ~ # zarf init --components k3s --set K3S_ARGS="" --confirm @@ -92,70 +213,96 @@ To see a full list of `variables` you can view the [zarf.yaml that defines the g ::: -## What Makes the Init Package Special +## Putting it All Together -Deploying into air gapped environments is a hard problem, particularly when the K8s environment doesn't have a container registry for you to store images in already. This results in a dilemma where the container registry image must be introduced to the cluster, but there is no container registry to push it to as the image is not yet in the cluster - chicken, meet egg. To ensure that our approach is distro-agnostic, we developed a unique solution to seed the container registry into the cluster. +The package definition 'init' is similar to writing any other Zarf Package, but with a few key differences: -This is done with the `zarf-injector` [component](https://github.com/defenseunicorns/zarf/blob/main/packages/zarf-injector/zarf.yaml) which injects a single rust binary (statically compiled) and a series of configmap chunks of a `registry:2` image into an ephemeral pod that is based on an existing image in the cluster. This gives us a running registry to bootstrap from and deploy the rest of the 'init' package and any other packages down the line. +Starting with `kind` and `metadata`: -:::note +```yaml {3, 6, 9} +# zarf.yaml +# kind must be ZarfInitConfig +kind: ZarfInitConfig +metadata: + # name *can* be anything, but it is generally recommended to end with 'init' + name: init + # version should be empty as it will be set by the Zarf CLI + # (this is ONLY for the 'init' package) + # version: 0.1.0 +... +``` -The `registry:2` image and the Zarf Agent image can be configured with a custom init package using the `registry_image_*` and `agent_image_*` templates defined in the Zarf repo's [zarf-config.toml](https://github.com/defenseunicorns/zarf/blob/main/zarf-config.toml). This allows you to swap them for enterprise provided / hardened versions if desired such as those provided by [Iron Bank](https://repo1.dso.mil/dsop/opensource/defenseunicorns/zarf/zarf-agent). +In order for Zarf to operate correctly, the following `components`: -::: +- must be defined, ordered, and named **exactly** as shown below +- must have the `required` field set to `true` -## The `zarf init` Lifecycle - -The `zarf init` lifecycle is _very similar_ to the [`zarf package deploy` lifecycle](/ref/deploy/) except that it sets up resources specific to Zarf such as the `zarf-state` and performs special actions such as the injection procedure. - -B2 - B2(handle multipart package)-->B3 - B3(extract archive to temp dir)-->B4 - B4(validate package checksums and signature)-->B5 - B5(filter components by architecture & OS)-->B6 - B6(save SBOM files to current dir)-->B7 - B7(handle deprecations and breaking changes)-->B9 - B9(confirm package deploy):::prompt-->B10 - B10(process deploy-time variables)-->B11 - B11(prompt for missing variables)-->B12 - B12(prompt to confirm components)-->B13 - B13(prompt to choose components in '.group')-->B14 - - subgraph - B52 --> |Yes|B14(deploy each component)-->B14 - B14 --> B15{Component is \n zarf-seed-registry} - B15 --> |Yes|B51(initialize zarf-state secret):::action - B51 --> B52{External \n registry configured} - B52 --> |No|B53(run injection process):::action-->B16 - B15 --> |No|B16(run each '.actions.onDeploy.before'):::action-->B16 - B16 --> B17(copy '.files')-->B18 - B18(load Zarf State)-->B19 - B19(push '.images')-->B20 - B20(push '.repos')-->B21 - B21(process '.dataInjections')-->B22 - B22(install '.charts')-->B23 - B23(apply '.manifests')-->B24 - B24(run each '.actions.onDeploy.after'):::action-->B24 - B24-->B25{Success?} - B25-->|Yes|B26(run each\n'.actions.onDeploy.success'):::action-->B26 - B25-->|No|B27(run each\n'.actions.onDeploy.failure'):::action-->B27-->B999 - - B999[Abort]:::fail - end - - B26-->B28(print Zarf connect table) - B28-->B29(save package data to cluster) - - - classDef prompt fill:#4adede,color:#000000 - classDef action fill:#bd93f9,color:#000000 - classDef fail fill:#aa0000 -`}/> +```yaml {3, 7, 11, 15} +# zarf.yaml +components: + # components (like k3s) that spin up a cluster... -:::tip + - name: zarf-injector + required: true + ... + + - name: zarf-seed-registry + required: true + ... + + - name: zarf-registry + required: true + ... + + - name: zarf-agent + required: true + ... + + # optional components that need a cluster ... +``` -Given that these flows are so similar you actually can `zarf package deploy` the init package. This is similar to accepting the defaults for any init-specific command flags and is useful when trying to deploy packages in a more generic way. +:::note + +In order to reproduce / build the following example, you will need to have the Zarf repository cloned locally. + +```bash +git clone https://github.com/defenseunicorns/zarf.git +cd zarf +mv zarf.yaml zarf.yaml.bak +``` + +You can learn more about creating a custom init package in the [Creating a Custom 'init' Package Tutorial](/tutorials/8-custom-init-packages). ::: + +A minimal `zarf.yaml` for the 'init' package looks something like: + +```yaml +# zarf.yaml +kind: ZarfInitConfig +metadata: + name: init + +components: + - name: zarf-injector + required: true + import: + path: packages/zarf-registry + + - name: zarf-seed-registry + required: true + import: + path: packages/zarf-registry + + - name: zarf-registry + required: true + import: + path: packages/zarf-registry + + - name: zarf-agent + required: true + import: + path: packages/zarf-agent +``` + +{/* technically the most minimal you can go is just `zarf-agent` and using an external registry / git server but idk if it's worth documenting that */} diff --git a/site/src/content/docs/ref/packages.mdx b/site/src/content/docs/ref/packages.mdx index 913e125f3a..4cb1e4434c 100644 --- a/site/src/content/docs/ref/packages.mdx +++ b/site/src/content/docs/ref/packages.mdx @@ -20,17 +20,19 @@ Throughout the rest of the documentation, we will refer to the `ZarfInitConfig` ### `ZarfInitConfig` -The init package is used to initialize a cluster, making it ready for deployment of other Zarf packages. It must be executed once on each cluster that you want to deploy another package onto, even if multiple clusters share the same host. For additional information on the init package, we provide detailed documentation on the Zarf ['init' package page](/ref/init-package/). +The init package is used to initialize a cluster, making it ready for deployment of other Zarf packages. It must be executed once on each cluster that you want to deploy another package onto, even if multiple clusters share the same host. -If there is no running cluster, the init package can be used to create one. It has a deployable K3s cluster component that can be optionally deployed on your machine. Usually, an init package is the first Zarf Package to be deployed on a cluster as other packages often depend on the services installed or configured by the init package. If you want to install a K8s cluster with Zarf, but you don't want to use K3s as your cluster, you will need to create or find another Zarf Package that will stand up your cluster before you run the zarf init command. +If there is no running cluster, the init package can be used to create one. It has a deployable K3s cluster component that can be optionally deployed on your machine. To install a K8s distribution other than K3s, finding or creating a custom Zarf Package is necessary. -:::note +Typically, an init package is the first Zarf Package to be deployed on a cluster as other packages often rely on the services it installs or configures, however Zarf may be used to package and install the cluster distribution itself. In this scenario, the distribution package would be deployed first, followed by the init package, and then any other packages planned for deployment. + +:::tip -To clarify, in most cases, the first Zarf Package you deploy onto a cluster should be the init package since other packages often depend on the services it installs onto your cluster. However, if you don't want to use the K3s distribution included in the init package or if you already have a preferred K8s distribution, you can deploy the distribution package first, followed by the init package, and then any other packages you want. This only applies if you don't have a K8s cluster yet. +Check out our [K3s cluster package](https://github.com/defenseunicorns/zarf/blob/main/packages/distros/k3s/zarf.yaml) to see an example of a Zarf Package that installs a Kubernetes distribution ::: -During the initialization process, Zarf will seed your cluster with a container registry to store images that other packages may require. Additionally, the init package has the option to deploy other features to your cluster, such as a Git server to manage your repositories or a PLG logging stack that allows you to monitor the applications running on your cluster. +During the initialization process, Zarf will seed the cluster with a container registry to store images that other packages may require. The init package has the option to deploy other features to your cluster, such as a Git server to manage your repositories or a PLG logging stack that allows you to monitor the applications running on your cluster. For additional information on the init package, we provide detailed documentation on the Zarf ['init' package page](/ref/init-package/). #### Using the init-package diff --git a/site/src/styles/custom.css b/site/src/styles/custom.css index 9389191655..d16bc02119 100644 --- a/site/src/styles/custom.css +++ b/site/src/styles/custom.css @@ -1,58 +1,61 @@ -/* +/* Generated from https://starlight.astro.build/guides/css-and-tailwind/#theming -Accent- H: 220.909 C: 0.0.87 +Accent- H: 220.909 C: 0.0.87 Gray- H: 273.019 C: 0.064 */ /* Dark mode colors. */ :root { - --sl-color-accent-low: #13272e; - --sl-color-accent: #1d758d; - --sl-color-accent-high: #b5cdd6; - --sl-color-white: #ffffff; - --sl-color-gray-1: #e8edff; - --sl-color-gray-2: #bbc1d7; - --sl-color-gray-3: #7e89b3; - --sl-color-gray-4: #4c567c; - --sl-color-gray-5: #2d3559; - --sl-color-gray-6: #1d2446; - --sl-color-black: #131727; + --sl-color-accent-low: #13282f; + --sl-color-accent: #00bbff; + --sl-color-accent-high: #70d9ff; + --sl-color-white: #ffffff; + --sl-color-gray-1: #e8edff; + /* --sl-color-gray-2: #bbc1d7; */ + --sl-color-gray-2: var(--sl-color-gray-1); + --sl-color-gray-3: #7e89b3; + --sl-color-gray-4: #4c567c; + --sl-color-gray-5: #2d3559; + --sl-color-gray-6: #1d2446; + --sl-color-black: #131727; } /* Light mode colors. */ -:root[data-theme='light'] { - --sl-color-accent-low: #c8dbe1; - --sl-color-accent: #20778f; - --sl-color-accent-high: #153742; - --sl-color-white: #131727; - --sl-color-gray-1: #1d2446; - --sl-color-gray-2: #2d3559; - --sl-color-gray-3: #4c567c; - --sl-color-gray-4: #7e89b3; - --sl-color-gray-5: #bbc1d7; - --sl-color-gray-6: #e8edff; - --sl-color-gray-7: #f3f6ff; - --sl-color-black: #ffffff; +:root[data-theme="light"] { + --sl-color-accent-low: #c8dbe1; + --sl-color-accent: #007099; + --sl-color-accent-high: #153742; + --sl-color-white: #131727; + --sl-color-gray-1: #1d2446; + --sl-color-gray-2: #2d3559; + --sl-color-gray-3: #4c567c; + --sl-color-gray-4: #7e89b3; + --sl-color-gray-5: #bbc1d7; + --sl-color-gray-6: #e8edff; + --sl-color-gray-7: #f3f6ff; + --sl-color-black: #ffffff; } :root { - --transition-speed: 0.2s; + --transition-speed: 0.2s; + --sl-font-mono: "Source Code Pro", monospace; + --sl-content-width: 52rem; } /* Heading link styling */ .heading-link::after { - content: '#'; - padding-inline-start: 0.25em; - opacity: 0; - transition: var(--transition-speed); + content: "#"; + padding-inline-start: 0.25em; + opacity: 0; + transition: var(--transition-speed); } .heading-link:hover::after { - color: var(--sl-color-text-accent); - opacity: 1; + color: var(--sl-color-text-accent); + opacity: 1; } .heading-link { - text-decoration: none; - color: var(--sl-color-white) !important; + text-decoration: none; + color: var(--sl-color-white) !important; } diff --git a/src/cmd/common/utils.go b/src/cmd/common/utils.go index 54113ee90f..4fe6fa5043 100644 --- a/src/cmd/common/utils.go +++ b/src/cmd/common/utils.go @@ -5,11 +5,13 @@ package common import ( + "context" "os" "os/signal" "syscall" "github.com/defenseunicorns/zarf/src/config/lang" + "github.com/defenseunicorns/zarf/src/pkg/cluster" "github.com/defenseunicorns/zarf/src/pkg/message" ) @@ -35,3 +37,14 @@ func ExitOnInterrupt() { } }() } + +// NewClusterOrDie creates a new Cluster instance and waits for the cluster to be ready or throws a fatal error. +func NewClusterOrDie(ctx context.Context) *cluster.Cluster { + timeoutCtx, cancel := context.WithTimeout(ctx, cluster.DefaultTimeout) + defer cancel() + c, err := cluster.NewClusterWithWait(timeoutCtx) + if err != nil { + message.Fatalf(err, "Failed to connect to cluster") + } + return c +} diff --git a/src/cmd/connect.go b/src/cmd/connect.go index 39b382cad3..19422df967 100644 --- a/src/cmd/connect.go +++ b/src/cmd/connect.go @@ -32,7 +32,7 @@ var ( Aliases: []string{"c"}, Short: lang.CmdConnectShort, Long: lang.CmdConnectLong, - Run: func(_ *cobra.Command, args []string) { + Run: func(cmd *cobra.Command, args []string) { var target string if len(args) > 0 { target = args[0] @@ -43,12 +43,14 @@ var ( spinner.Fatalf(err, lang.CmdConnectErrCluster, err.Error()) } + ctx := cmd.Context() + var tunnel *k8s.Tunnel if connectResourceName != "" { zt := cluster.NewTunnelInfo(connectNamespace, connectResourceType, connectResourceName, "", connectLocalPort, connectRemotePort) - tunnel, err = c.ConnectTunnelInfo(zt) + tunnel, err = c.ConnectTunnelInfo(ctx, zt) } else { - tunnel, err = c.Connect(target) + tunnel, err = c.Connect(ctx, target) } if err != nil { spinner.Fatalf(err, lang.CmdConnectErrService, err.Error()) @@ -90,8 +92,11 @@ var ( Use: "list", Aliases: []string{"l"}, Short: lang.CmdConnectListShort, - Run: func(_ *cobra.Command, _ []string) { - cluster.NewClusterOrDie().PrintConnectTable() + Run: func(cmd *cobra.Command, _ []string) { + ctx := cmd.Context() + if err := common.NewClusterOrDie(ctx).PrintConnectTable(ctx); err != nil { + message.Fatal(err, err.Error()) + } }, } ) diff --git a/src/cmd/destroy.go b/src/cmd/destroy.go index bc9dbe7d61..429aec2c16 100644 --- a/src/cmd/destroy.go +++ b/src/cmd/destroy.go @@ -10,10 +10,10 @@ import ( "regexp" "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/zarf/src/cmd/common" "github.com/defenseunicorns/zarf/src/config" "github.com/defenseunicorns/zarf/src/config/lang" "github.com/defenseunicorns/zarf/src/internal/packager/helm" - "github.com/defenseunicorns/zarf/src/pkg/cluster" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/defenseunicorns/zarf/src/pkg/utils/exec" @@ -28,16 +28,14 @@ var destroyCmd = &cobra.Command{ Aliases: []string{"d"}, Short: lang.CmdDestroyShort, Long: lang.CmdDestroyLong, - Run: func(_ *cobra.Command, _ []string) { - c, err := cluster.NewClusterWithWait(cluster.DefaultTimeout) - if err != nil { - message.Fatalf(err, lang.ErrNoClusterConnection) - } + Run: func(cmd *cobra.Command, _ []string) { + ctx := cmd.Context() + c := common.NewClusterOrDie(ctx) // NOTE: If 'zarf init' failed to deploy the k3s component (or if we're looking at the wrong kubeconfig) // there will be no zarf-state to load and the struct will be empty. In these cases, if we can find // the scripts to remove k3s, we will still try to remove a locally installed k3s cluster - state, err := c.LoadZarfState() + state, err := c.LoadZarfState(ctx) if err != nil { message.WarnErr(err, lang.ErrLoadState) } @@ -74,10 +72,12 @@ var destroyCmd = &cobra.Command{ helm.Destroy(removeComponents) // If Zarf didn't deploy the cluster, only delete the ZarfNamespace - c.DeleteZarfNamespace() + if err := c.DeleteZarfNamespace(ctx); err != nil { + message.Fatal(err, err.Error()) + } // Remove zarf agent labels and secrets from namespaces Zarf doesn't manage - c.StripZarfLabelsAndSecretsFromNamespaces() + c.StripZarfLabelsAndSecretsFromNamespaces(ctx) } }, } diff --git a/src/cmd/dev.go b/src/cmd/dev.go index 068565f7c7..17962176a4 100644 --- a/src/cmd/dev.go +++ b/src/cmd/dev.go @@ -40,7 +40,7 @@ var devDeployCmd = &cobra.Command{ Args: cobra.MaximumNArgs(1), Short: lang.CmdDevDeployShort, Long: lang.CmdDevDeployLong, - Run: func(_ *cobra.Command, args []string) { + Run: func(cmd *cobra.Command, args []string) { pkgConfig.CreateOpts.BaseDir = common.SetBaseDirectory(args) v := common.GetViper() @@ -50,12 +50,10 @@ var devDeployCmd = &cobra.Command{ pkgConfig.PkgOpts.SetVariables = helpers.TransformAndMergeMap( v.GetStringMapString(common.VPkgDeploySet), pkgConfig.PkgOpts.SetVariables, strings.ToUpper) - // Configure the packager pkgClient := packager.NewOrDie(&pkgConfig) defer pkgClient.ClearTempPaths() - // Create the package - if err := pkgClient.DevDeploy(); err != nil { + if err := pkgClient.DevDeploy(cmd.Context()); err != nil { message.Fatalf(err, lang.CmdDevDeployErr, err.Error()) } }, @@ -209,19 +207,15 @@ var devFindImagesCmd = &cobra.Command{ Run: func(_ *cobra.Command, args []string) { pkgConfig.CreateOpts.BaseDir = common.SetBaseDirectory(args) - // Ensure uppercase keys from viper v := common.GetViper() pkgConfig.CreateOpts.SetVariables = helpers.TransformAndMergeMap( v.GetStringMapString(common.VPkgCreateSet), pkgConfig.CreateOpts.SetVariables, strings.ToUpper) pkgConfig.PkgOpts.SetVariables = helpers.TransformAndMergeMap( v.GetStringMapString(common.VPkgDeploySet), pkgConfig.PkgOpts.SetVariables, strings.ToUpper) - - // Configure the packager pkgClient := packager.NewOrDie(&pkgConfig) defer pkgClient.ClearTempPaths() - // Find all the images the package might need if _, err := pkgClient.FindImages(); err != nil { message.Fatalf(err, lang.CmdDevFindImagesErr, err.Error()) } @@ -292,8 +286,14 @@ func init() { // use the package create config for this and reset it here to avoid overwriting the config.CreateOptions.SetVariables devFindImagesCmd.Flags().StringToStringVar(&pkgConfig.CreateOpts.SetVariables, "set", v.GetStringMapString(common.VPkgCreateSet), lang.CmdDevFlagSet) - devFindImagesCmd.Flags().MarkDeprecated("set", "this field is replaced by create-set") - devFindImagesCmd.Flags().MarkHidden("set") + err := devFindImagesCmd.Flags().MarkDeprecated("set", "this field is replaced by create-set") + if err != nil { + message.Fatal(err, err.Error()) + } + err = devFindImagesCmd.Flags().MarkHidden("set") + if err != nil { + message.Fatal(err, err.Error()) + } devFindImagesCmd.Flags().StringVarP(&pkgConfig.CreateOpts.Flavor, "flavor", "f", v.GetString(common.VPkgCreateFlavor), lang.CmdPackageCreateFlagFlavor) devFindImagesCmd.Flags().StringToStringVar(&pkgConfig.CreateOpts.SetVariables, "create-set", v.GetStringMapString(common.VPkgCreateSet), lang.CmdDevFlagSet) devFindImagesCmd.Flags().StringToStringVar(&pkgConfig.PkgOpts.SetVariables, "deploy-set", v.GetStringMapString(common.VPkgDeploySet), lang.CmdPackageDeployFlagSet) @@ -341,7 +341,16 @@ func bindDevGenerateFlags(_ *viper.Viper) { generateFlags.StringVar(&pkgConfig.GenerateOpts.Output, "output-directory", "", "Output directory for the generated zarf.yaml") generateFlags.StringVar(&pkgConfig.FindImagesOpts.KubeVersionOverride, "kube-version", "", lang.CmdDevFlagKubeVersion) - devGenerateCmd.MarkFlagRequired("url") - devGenerateCmd.MarkFlagRequired("version") - devGenerateCmd.MarkFlagRequired("output-directory") + err := devGenerateCmd.MarkFlagRequired("url") + if err != nil { + message.Fatal(err, err.Error()) + } + err = devGenerateCmd.MarkFlagRequired("version") + if err != nil { + message.Fatal(err, err.Error()) + } + err = devGenerateCmd.MarkFlagRequired("output-directory") + if err != nil { + message.Fatal(err, err.Error()) + } } diff --git a/src/cmd/initialize.go b/src/cmd/initialize.go index 0f8a1ee322..b41c3ca8d9 100644 --- a/src/cmd/initialize.go +++ b/src/cmd/initialize.go @@ -35,7 +35,7 @@ var initCmd = &cobra.Command{ Short: lang.CmdInitShort, Long: lang.CmdInitLong, Example: lang.CmdInitExample, - Run: func(_ *cobra.Command, _ []string) { + Run: func(cmd *cobra.Command, _ []string) { zarfLogo := message.GetLogo() _, _ = fmt.Fprintln(os.Stderr, zarfLogo) @@ -58,17 +58,16 @@ var initCmd = &cobra.Command{ message.Fatal(err, err.Error()) } - // Ensure uppercase keys from viper v := common.GetViper() pkgConfig.PkgOpts.SetVariables = helpers.TransformAndMergeMap( v.GetStringMapString(common.VPkgDeploySet), pkgConfig.PkgOpts.SetVariables, strings.ToUpper) - // Configure the packager pkgClient := packager.NewOrDie(&pkgConfig, packager.WithSource(src)) defer pkgClient.ClearTempPaths() - // Deploy everything - err = pkgClient.Deploy() + ctx := cmd.Context() + + err = pkgClient.Deploy(ctx) if err != nil { message.Fatal(err, err.Error()) } diff --git a/src/cmd/internal.go b/src/cmd/internal.go index deda9ae16d..97263d0b08 100644 --- a/src/cmd/internal.go +++ b/src/cmd/internal.go @@ -11,15 +11,14 @@ import ( "path/filepath" "strings" - "github.com/alecthomas/jsonschema" "github.com/defenseunicorns/pkg/helpers" "github.com/defenseunicorns/zarf/src/cmd/common" "github.com/defenseunicorns/zarf/src/config/lang" "github.com/defenseunicorns/zarf/src/internal/agent" "github.com/defenseunicorns/zarf/src/internal/packager/git" - "github.com/defenseunicorns/zarf/src/pkg/cluster" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/defenseunicorns/zarf/src/types" + "github.com/invopop/jsonschema" "github.com/spf13/cobra" "github.com/spf13/cobra/doc" "github.com/spf13/pflag" @@ -102,7 +101,7 @@ var genCLIDocs = &cobra.Command{ if toolCmd.Use == "monitor" { resetStringFlags(toolCmd) } - + if toolCmd.Use == "yq" { for _, subCmd := range toolCmd.Commands() { if subCmd.Name() == "shell-completion" { @@ -160,7 +159,8 @@ var genConfigSchemaCmd = &cobra.Command{ Aliases: []string{"gc"}, Short: lang.CmdInternalConfigSchemaShort, Run: func(_ *cobra.Command, _ []string) { - schema := jsonschema.Reflect(&types.ZarfPackage{}) + reflector := jsonschema.Reflector(jsonschema.Reflector{ExpandedStruct: true}) + schema := reflector.Reflect(&types.ZarfPackage{}) output, err := json.MarshalIndent(schema, "", " ") if err != nil { message.Fatal(err, lang.CmdInternalConfigSchemaErr) @@ -193,15 +193,17 @@ var createReadOnlyGiteaUser = &cobra.Command{ Use: "create-read-only-gitea-user", Short: lang.CmdInternalCreateReadOnlyGiteaUserShort, Long: lang.CmdInternalCreateReadOnlyGiteaUserLong, - Run: func(_ *cobra.Command, _ []string) { + Run: func(cmd *cobra.Command, _ []string) { + ctx := cmd.Context() + // Load the state so we can get the credentials for the admin git user - state, err := cluster.NewClusterOrDie().LoadZarfState() + state, err := common.NewClusterOrDie(ctx).LoadZarfState(ctx) if err != nil { message.WarnErr(err, lang.ErrLoadState) } // Create the non-admin user - if err = git.New(state.GitServer).CreateReadOnlyUser(); err != nil { + if err = git.New(state.GitServer).CreateReadOnlyUser(ctx); err != nil { message.WarnErr(err, lang.CmdInternalCreateReadOnlyGiteaUserErr) } }, @@ -211,24 +213,26 @@ var createPackageRegistryToken = &cobra.Command{ Use: "create-artifact-registry-token", Short: lang.CmdInternalArtifactRegistryGiteaTokenShort, Long: lang.CmdInternalArtifactRegistryGiteaTokenLong, - Run: func(_ *cobra.Command, _ []string) { - // Load the state so we can get the credentials for the admin git user - c := cluster.NewClusterOrDie() - state, err := c.LoadZarfState() + Run: func(cmd *cobra.Command, _ []string) { + ctx := cmd.Context() + c := common.NewClusterOrDie(ctx) + state, err := c.LoadZarfState(ctx) if err != nil { message.WarnErr(err, lang.ErrLoadState) } // If we are setup to use an internal artifact server, create the artifact registry token if state.ArtifactServer.InternalServer { - token, err := git.New(state.GitServer).CreatePackageRegistryToken() + token, err := git.New(state.GitServer).CreatePackageRegistryToken(ctx) if err != nil { message.WarnErr(err, lang.CmdInternalArtifactRegistryGiteaTokenErr) } state.ArtifactServer.PushToken = token.Sha1 - c.SaveZarfState(state) + if err := c.SaveZarfState(ctx, state); err != nil { + message.Fatal(err, err.Error()) + } } }, } @@ -237,10 +241,11 @@ var updateGiteaPVC = &cobra.Command{ Use: "update-gitea-pvc", Short: lang.CmdInternalUpdateGiteaPVCShort, Long: lang.CmdInternalUpdateGiteaPVCLong, - Run: func(_ *cobra.Command, _ []string) { + Run: func(cmd *cobra.Command, _ []string) { + ctx := cmd.Context() // There is a possibility that the pvc does not yet exist and Gitea helm chart should create it - helmShouldCreate, err := git.UpdateGiteaPVC(rollback) + helmShouldCreate, err := git.UpdateGiteaPVC(ctx, rollback) if err != nil { message.WarnErr(err, lang.CmdInternalUpdateGiteaPVCErr) } @@ -293,6 +298,9 @@ func addHiddenDummyFlag(cmd *cobra.Command, flagDummy string) { if cmd.PersistentFlags().Lookup(flagDummy) == nil { var dummyStr string cmd.PersistentFlags().StringVar(&dummyStr, flagDummy, "", "") - cmd.PersistentFlags().MarkHidden(flagDummy) + err := cmd.PersistentFlags().MarkHidden(flagDummy) + if err != nil { + message.Fatal(err, err.Error()) + } } } diff --git a/src/cmd/package.go b/src/cmd/package.go index e3fb00b48a..e8817bf60b 100644 --- a/src/cmd/package.go +++ b/src/cmd/package.go @@ -48,16 +48,13 @@ var packageCreateCmd = &cobra.Command{ config.CommonOptions.CachePath = config.ZarfDefaultCachePath } - // Ensure uppercase keys from viper v := common.GetViper() pkgConfig.CreateOpts.SetVariables = helpers.TransformAndMergeMap( v.GetStringMapString(common.VPkgCreateSet), pkgConfig.CreateOpts.SetVariables, strings.ToUpper) - // Configure the packager pkgClient := packager.NewOrDie(&pkgConfig) defer pkgClient.ClearTempPaths() - // Create the package if err := pkgClient.Create(); err != nil { message.Fatalf(err, lang.CmdPackageCreateErr, err.Error()) } @@ -70,22 +67,19 @@ var packageDeployCmd = &cobra.Command{ Short: lang.CmdPackageDeployShort, Long: lang.CmdPackageDeployLong, Args: cobra.MaximumNArgs(1), - Run: func(_ *cobra.Command, args []string) { + Run: func(cmd *cobra.Command, args []string) { pkgConfig.PkgOpts.PackageSource = choosePackage(args) - // Ensure uppercase keys from viper and CLI --set v := common.GetViper() - - // Merge the viper config file variables and provided CLI flag variables (CLI takes precedence)) pkgConfig.PkgOpts.SetVariables = helpers.TransformAndMergeMap( v.GetStringMapString(common.VPkgDeploySet), pkgConfig.PkgOpts.SetVariables, strings.ToUpper) - // Configure the packager pkgClient := packager.NewOrDie(&pkgConfig) defer pkgClient.ClearTempPaths() - // Deploy the package - if err := pkgClient.Deploy(); err != nil { + ctx := cmd.Context() + + if err := pkgClient.Deploy(ctx); err != nil { message.Fatalf(err, lang.CmdPackageDeployErr, err.Error()) } }, @@ -98,15 +92,15 @@ var packageMirrorCmd = &cobra.Command{ Long: lang.CmdPackageMirrorLong, Example: lang.CmdPackageMirrorExample, Args: cobra.MaximumNArgs(1), - Run: func(_ *cobra.Command, args []string) { + Run: func(cmd *cobra.Command, args []string) { pkgConfig.PkgOpts.PackageSource = choosePackage(args) - // Configure the packager pkgClient := packager.NewOrDie(&pkgConfig) defer pkgClient.ClearTempPaths() - // Deploy the package - if err := pkgClient.Mirror(); err != nil { + ctx := cmd.Context() + + if err := pkgClient.Mirror(ctx); err != nil { message.Fatalf(err, lang.CmdPackageDeployErr, err.Error()) } }, @@ -123,11 +117,9 @@ var packageInspectCmd = &cobra.Command{ src := identifyAndFallbackToClusterSource() - // Configure the packager pkgClient := packager.NewOrDie(&pkgConfig, packager.WithSource(src)) defer pkgClient.ClearTempPaths() - // Inspect the package if err := pkgClient.Inspect(); err != nil { message.Fatalf(err, lang.CmdPackageInspectErr, err.Error()) } @@ -139,9 +131,9 @@ var packageListCmd = &cobra.Command{ Use: "list", Aliases: []string{"l", "ls"}, Short: lang.CmdPackageListShort, - Run: func(_ *cobra.Command, _ []string) { - // Get all the deployed packages - deployedZarfPackages, errs := cluster.NewClusterOrDie().GetDeployedZarfPackages() + Run: func(cmd *cobra.Command, _ []string) { + ctx := cmd.Context() + deployedZarfPackages, errs := common.NewClusterOrDie(ctx).GetDeployedZarfPackages(ctx) if len(errs) > 0 && len(deployedZarfPackages) == 0 { message.Fatalf(errs, lang.CmdPackageListNoPackageWarn) } @@ -161,7 +153,6 @@ var packageListCmd = &cobra.Command{ }) } - // Print out the table for the user header := []string{"Package", "Version", "Components"} message.Table(header, packageData) @@ -177,15 +168,17 @@ var packageRemoveCmd = &cobra.Command{ Aliases: []string{"u", "rm"}, Args: cobra.MaximumNArgs(1), Short: lang.CmdPackageRemoveShort, - Run: func(_ *cobra.Command, args []string) { + Run: func(cmd *cobra.Command, args []string) { pkgConfig.PkgOpts.PackageSource = choosePackage(args) src := identifyAndFallbackToClusterSource() - // Configure the packager + pkgClient := packager.NewOrDie(&pkgConfig, packager.WithSource(src)) defer pkgClient.ClearTempPaths() - if err := pkgClient.Remove(); err != nil { + ctx := cmd.Context() + + if err := pkgClient.Remove(ctx); err != nil { message.Fatalf(err, lang.CmdPackageRemoveErr, err.Error()) } }, @@ -220,11 +213,9 @@ var packagePublishCmd = &cobra.Command{ pkgConfig.PublishOpts.PackageDestination = ref.String() - // Configure the packager pkgClient := packager.NewOrDie(&pkgConfig) defer pkgClient.ClearTempPaths() - // Publish the package if err := pkgClient.Publish(); err != nil { message.Fatalf(err, lang.CmdPackagePublishErr, err.Error()) } @@ -239,11 +230,9 @@ var packagePullCmd = &cobra.Command{ Run: func(_ *cobra.Command, args []string) { pkgConfig.PkgOpts.PackageSource = args[0] - // Configure the packager pkgClient := packager.NewOrDie(&pkgConfig) defer pkgClient.ClearTempPaths() - // Pull the package if err := pkgClient.Pull(); err != nil { message.Fatalf(err, lang.CmdPackagePullErr, err.Error()) } @@ -288,7 +277,7 @@ func identifyAndFallbackToClusterSource() (src sources.PackageSource) { return src } -func getPackageCompletionArgs(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) { +func getPackageCompletionArgs(cmd *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) { var pkgCandidates []string c, err := cluster.NewCluster() @@ -296,8 +285,9 @@ func getPackageCompletionArgs(_ *cobra.Command, _ []string, _ string) ([]string, return pkgCandidates, cobra.ShellCompDirectiveDefault } - // Get all the deployed packages - deployedZarfPackages, _ := c.GetDeployedZarfPackages() + ctx := cmd.Context() + + deployedZarfPackages, _ := c.GetDeployedZarfPackages(ctx) // Populate list of package names for _, pkg := range deployedZarfPackages { pkgCandidates = append(pkgCandidates, pkg.Name) @@ -366,9 +356,18 @@ func bindCreateFlags(v *viper.Viper) { createFlags.IntVar(&pkgConfig.PkgOpts.Retries, "retries", v.GetInt(common.VPkgRetries), lang.CmdPackageFlagRetries) - createFlags.MarkHidden("output-directory") - createFlags.MarkHidden("key") - createFlags.MarkHidden("key-pass") + err := createFlags.MarkHidden("output-directory") + if err != nil { + message.Fatal(err, err.Error()) + } + err = createFlags.MarkHidden("key") + if err != nil { + message.Fatal(err, err.Error()) + } + err = createFlags.MarkHidden("key-pass") + if err != nil { + message.Fatal(err, err.Error()) + } } func bindDeployFlags(v *viper.Viper) { @@ -388,7 +387,10 @@ func bindDeployFlags(v *viper.Viper) { deployFlags.StringVar(&pkgConfig.PkgOpts.Shasum, "shasum", v.GetString(common.VPkgDeployShasum), lang.CmdPackageDeployFlagShasum) deployFlags.StringVar(&pkgConfig.PkgOpts.SGetKeyPath, "sget", v.GetString(common.VPkgDeploySget), lang.CmdPackageDeployFlagSget) - deployFlags.MarkHidden("sget") + err := deployFlags.MarkHidden("sget") + if err != nil { + message.Fatal(err, err.Error()) + } } func bindMirrorFlags(v *viper.Viper) { @@ -422,6 +424,7 @@ func bindInspectFlags(_ *viper.Viper) { inspectFlags := packageInspectCmd.Flags() inspectFlags.BoolVarP(&pkgConfig.InspectOpts.ViewSBOM, "sbom", "s", false, lang.CmdPackageInspectFlagSbom) inspectFlags.StringVar(&pkgConfig.InspectOpts.SBOMOutputDir, "sbom-out", "", lang.CmdPackageInspectFlagSbomOut) + inspectFlags.BoolVar(&pkgConfig.InspectOpts.ListImages, "list-images", false, lang.CmdPackageInspectFlagListImages) } func bindRemoveFlags(v *viper.Viper) { diff --git a/src/cmd/root.go b/src/cmd/root.go index 9563f382d3..83fb28e61d 100644 --- a/src/cmd/root.go +++ b/src/cmd/root.go @@ -5,6 +5,7 @@ package cmd import ( + "context" "fmt" "os" "strings" @@ -37,6 +38,10 @@ var rootCmd = &cobra.Command{ config.SkipLogFile = true } + // Set the global context for the root command and all child commands + ctx := context.Background() + cmd.SetContext(ctx) + common.SetupCLI() }, Short: lang.RootCmdShort, diff --git a/src/cmd/tools/archiver.go b/src/cmd/tools/archiver.go index 9a1ee1c022..344cc7398c 100644 --- a/src/cmd/tools/archiver.go +++ b/src/cmd/tools/archiver.go @@ -93,5 +93,8 @@ func init() { archiverDecompressCmd.Flags().BoolVar(&unarchiveAll, "decompress-all", false, "Decompress all tarballs in the archive") archiverDecompressCmd.Flags().BoolVar(&unarchiveAll, "unarchive-all", false, "Unarchive all tarballs in the archive") archiverDecompressCmd.MarkFlagsMutuallyExclusive("decompress-all", "unarchive-all") - archiverDecompressCmd.Flags().MarkHidden("decompress-all") + err := archiverDecompressCmd.Flags().MarkHidden("decompress-all") + if err != nil { + message.Fatal(err, err.Error()) + } } diff --git a/src/cmd/tools/crane.go b/src/cmd/tools/crane.go index b6fd47e5a0..b501aeac7a 100644 --- a/src/cmd/tools/crane.go +++ b/src/cmd/tools/crane.go @@ -13,6 +13,7 @@ import ( "github.com/defenseunicorns/zarf/src/cmd/common" "github.com/defenseunicorns/zarf/src/config" "github.com/defenseunicorns/zarf/src/config/lang" + "github.com/defenseunicorns/zarf/src/internal/packager/images" "github.com/defenseunicorns/zarf/src/pkg/cluster" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/defenseunicorns/zarf/src/pkg/transform" @@ -85,12 +86,12 @@ func init() { craneCopy := craneCmd.NewCmdCopy(&craneOptions) registryCmd.AddCommand(craneCopy) - registryCmd.AddCommand(zarfCraneCatalog(&craneOptions)) - registryCmd.AddCommand(zarfCraneInternalWrapper(craneCmd.NewCmdList, &craneOptions, lang.CmdToolsRegistryListExample, 0)) - registryCmd.AddCommand(zarfCraneInternalWrapper(craneCmd.NewCmdPush, &craneOptions, lang.CmdToolsRegistryPushExample, 1)) - registryCmd.AddCommand(zarfCraneInternalWrapper(craneCmd.NewCmdPull, &craneOptions, lang.CmdToolsRegistryPullExample, 0)) - registryCmd.AddCommand(zarfCraneInternalWrapper(craneCmd.NewCmdDelete, &craneOptions, lang.CmdToolsRegistryDeleteExample, 0)) - registryCmd.AddCommand(zarfCraneInternalWrapper(craneCmd.NewCmdDigest, &craneOptions, lang.CmdToolsRegistryDigestExample, 0)) + registryCmd.AddCommand(zarfCraneCatalog(craneOptions)) + registryCmd.AddCommand(zarfCraneInternalWrapper(craneCmd.NewCmdList, craneOptions, lang.CmdToolsRegistryListExample, 0)) + registryCmd.AddCommand(zarfCraneInternalWrapper(craneCmd.NewCmdPush, craneOptions, lang.CmdToolsRegistryPushExample, 1)) + registryCmd.AddCommand(zarfCraneInternalWrapper(craneCmd.NewCmdPull, craneOptions, lang.CmdToolsRegistryPullExample, 0)) + registryCmd.AddCommand(zarfCraneInternalWrapper(craneCmd.NewCmdDelete, craneOptions, lang.CmdToolsRegistryDeleteExample, 0)) + registryCmd.AddCommand(zarfCraneInternalWrapper(craneCmd.NewCmdDigest, craneOptions, lang.CmdToolsRegistryDigestExample, 0)) registryCmd.AddCommand(pruneCmd) registryCmd.AddCommand(craneCmd.NewCmdVersion()) @@ -103,8 +104,8 @@ func init() { } // Wrap the original crane catalog with a zarf specific version -func zarfCraneCatalog(cranePlatformOptions *[]crane.Option) *cobra.Command { - craneCatalog := craneCmd.NewCmdCatalog(cranePlatformOptions) +func zarfCraneCatalog(cranePlatformOptions []crane.Option) *cobra.Command { + craneCatalog := craneCmd.NewCmdCatalog(&cranePlatformOptions) craneCatalog.Example = lang.CmdToolsRegistryCatalogExample craneCatalog.Args = nil @@ -123,20 +124,21 @@ func zarfCraneCatalog(cranePlatformOptions *[]crane.Option) *cobra.Command { return err } - // Load Zarf state - zarfState, err := c.LoadZarfState() + ctx := cmd.Context() + + zarfState, err := c.LoadZarfState(ctx) if err != nil { return err } - registryEndpoint, tunnel, err := c.ConnectToZarfRegistryEndpoint(zarfState.RegistryInfo) + registryEndpoint, tunnel, err := c.ConnectToZarfRegistryEndpoint(ctx, zarfState.RegistryInfo) if err != nil { return err } // Add the correct authentication to the crane command options - authOption := config.GetCraneAuthOption(zarfState.RegistryInfo.PullUsername, zarfState.RegistryInfo.PullPassword) - *cranePlatformOptions = append(*cranePlatformOptions, authOption) + authOption := images.WithPullAuth(zarfState.RegistryInfo) + cranePlatformOptions = append(cranePlatformOptions, authOption) if tunnel != nil { message.Notef(lang.CmdToolsRegistryTunnel, registryEndpoint, zarfState.RegistryInfo.Address) @@ -151,8 +153,8 @@ func zarfCraneCatalog(cranePlatformOptions *[]crane.Option) *cobra.Command { } // Wrap the original crane list with a zarf specific version -func zarfCraneInternalWrapper(commandToWrap func(*[]crane.Option) *cobra.Command, cranePlatformOptions *[]crane.Option, exampleText string, imageNameArgumentIndex int) *cobra.Command { - wrappedCommand := commandToWrap(cranePlatformOptions) +func zarfCraneInternalWrapper(commandToWrap func(*[]crane.Option) *cobra.Command, cranePlatformOptions []crane.Option, exampleText string, imageNameArgumentIndex int) *cobra.Command { + wrappedCommand := commandToWrap(&cranePlatformOptions) wrappedCommand.Example = exampleText wrappedCommand.Args = nil @@ -172,8 +174,9 @@ func zarfCraneInternalWrapper(commandToWrap func(*[]crane.Option) *cobra.Command message.Note(lang.CmdToolsRegistryZarfState) - // Load the state (if able) - zarfState, err := c.LoadZarfState() + ctx := cmd.Context() + + zarfState, err := c.LoadZarfState(ctx) if err != nil { message.Warnf(lang.CmdToolsCraneConnectedButBadStateErr, err.Error()) return originalListFn(cmd, args) @@ -184,14 +187,14 @@ func zarfCraneInternalWrapper(commandToWrap func(*[]crane.Option) *cobra.Command return originalListFn(cmd, args) } - _, tunnel, err := c.ConnectToZarfRegistryEndpoint(zarfState.RegistryInfo) + _, tunnel, err := c.ConnectToZarfRegistryEndpoint(ctx, zarfState.RegistryInfo) if err != nil { return err } // Add the correct authentication to the crane command options - authOption := config.GetCraneAuthOption(zarfState.RegistryInfo.PushUsername, zarfState.RegistryInfo.PushPassword) - *cranePlatformOptions = append(*cranePlatformOptions, authOption) + authOption := images.WithPushAuth(zarfState.RegistryInfo) + cranePlatformOptions = append(cranePlatformOptions, authOption) if tunnel != nil { message.Notef(lang.CmdToolsRegistryTunnel, tunnel.Endpoint(), zarfState.RegistryInfo.Address) @@ -210,27 +213,27 @@ func zarfCraneInternalWrapper(commandToWrap func(*[]crane.Option) *cobra.Command return wrappedCommand } -func pruneImages(_ *cobra.Command, _ []string) error { +func pruneImages(cmd *cobra.Command, _ []string) error { // Try to connect to a Zarf initialized cluster c, err := cluster.NewCluster() if err != nil { return err } - // Load the state - zarfState, err := c.LoadZarfState() + ctx := cmd.Context() + + zarfState, err := c.LoadZarfState(ctx) if err != nil { return err } - // Load the currently deployed packages - zarfPackages, errs := c.GetDeployedZarfPackages() + zarfPackages, errs := c.GetDeployedZarfPackages(ctx) if len(errs) > 0 { return lang.ErrUnableToGetPackages } // Set up a tunnel to the registry if applicable - registryEndpoint, tunnel, err := c.ConnectToZarfRegistryEndpoint(zarfState.RegistryInfo) + registryEndpoint, tunnel, err := c.ConnectToZarfRegistryEndpoint(ctx, zarfState.RegistryInfo) if err != nil { return err } @@ -245,7 +248,7 @@ func pruneImages(_ *cobra.Command, _ []string) error { } func doPruneImagesForPackages(zarfState *types.ZarfState, zarfPackages []types.DeployedPackage, registryEndpoint string) error { - authOption := config.GetCraneAuthOption(zarfState.RegistryInfo.PushUsername, zarfState.RegistryInfo.PushPassword) + authOption := images.WithPushAuth(zarfState.RegistryInfo) spinner := message.NewProgressSpinner(lang.CmdToolsRegistryPruneLookup) defer spinner.Stop() diff --git a/src/cmd/tools/helm/load_plugins.go b/src/cmd/tools/helm/load_plugins.go index 28ea155030..f4d2800137 100644 --- a/src/cmd/tools/helm/load_plugins.go +++ b/src/cmd/tools/helm/load_plugins.go @@ -32,6 +32,7 @@ import ( "strings" "syscall" + "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/pkg/errors" "github.com/spf13/cobra" "sigs.k8s.io/yaml" @@ -216,7 +217,10 @@ func loadCompletionForPlugin(pluginCmd *cobra.Command, plugin *plugin.Plugin) { if err != nil { // The file could be missing or invalid. No static completion for this plugin. if settings.Debug { - log.Output(2, fmt.Sprintf("[info] %s\n", err.Error())) + err := log.Output(2, fmt.Sprintf("[info] %s\n", err.Error())) + if err != nil { + message.Fatal(err, err.Error()) + } } // Continue to setup dynamic completion. cmds = &pluginCommand{} @@ -238,7 +242,10 @@ func addPluginCommands(plugin *plugin.Plugin, baseCmd *cobra.Command, cmds *plug if len(cmds.Name) == 0 { // Missing name for a command if settings.Debug { - log.Output(2, fmt.Sprintf("[info] sub-command name field missing for %s", baseCmd.CommandPath())) + err := log.Output(2, fmt.Sprintf("[info] sub-command name field missing for %s", baseCmd.CommandPath())) + if err != nil { + message.Fatal(err, err.Error()) + } } return } diff --git a/src/cmd/tools/helm/repo_add.go b/src/cmd/tools/helm/repo_add.go index d053689000..114cd020f5 100644 --- a/src/cmd/tools/helm/repo_add.go +++ b/src/cmd/tools/helm/repo_add.go @@ -31,6 +31,8 @@ import ( "time" "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/zarf/src/pkg/cluster" + "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/gofrs/flock" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -77,13 +79,13 @@ func newRepoAddCmd(out io.Writer) *cobra.Command { Use: "add [NAME] [URL]", Short: "add a chart repository", Args: require.ExactArgs(2), - RunE: func(_ *cobra.Command, args []string) error { + RunE: func(cmd *cobra.Command, args []string) error { o.name = args[0] o.url = args[1] o.repoFile = settings.RepositoryConfig o.repoCache = settings.RepositoryCache - return o.run(out) + return o.run(cmd.Context(), out) }, } @@ -103,7 +105,7 @@ func newRepoAddCmd(out io.Writer) *cobra.Command { return cmd } -func (o *repoAddOptions) run(out io.Writer) error { +func (o *repoAddOptions) run(ctx context.Context, out io.Writer) error { // Block deprecated repos if !o.allowDeprecatedRepos { for oldURL, newURL := range deprecatedRepos { @@ -128,11 +130,15 @@ func (o *repoAddOptions) run(out io.Writer) error { lockPath = o.repoFile + ".lock" } fileLock := flock.New(lockPath) - lockCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + lockCtx, cancel := context.WithTimeout(ctx, cluster.DefaultTimeout) defer cancel() locked, err := fileLock.TryLockContext(lockCtx, time.Second) if err == nil && locked { - defer fileLock.Unlock() + defer func() { + if err := fileLock.Unlock(); err != nil { + message.Fatal(err, err.Error()) + } + }() } if err != nil { return err diff --git a/src/cmd/tools/helm/repo_index.go b/src/cmd/tools/helm/repo_index.go index a84a3af74c..1d6182e85e 100644 --- a/src/cmd/tools/helm/repo_index.go +++ b/src/cmd/tools/helm/repo_index.go @@ -27,6 +27,7 @@ import ( "path/filepath" "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -101,7 +102,10 @@ func index(dir, url, mergeTo string) error { var i2 *repo.IndexFile if _, err := os.Stat(mergeTo); os.IsNotExist(err) { i2 = repo.NewIndexFile() - i2.WriteFile(mergeTo, helpers.ReadAllWriteUser) + err := i2.WriteFile(mergeTo, helpers.ReadAllWriteUser) + if err != nil { + message.Fatal(err, err.Error()) + } } else { i2, err = repo.LoadIndexFile(mergeTo) if err != nil { diff --git a/src/cmd/tools/yq.go b/src/cmd/tools/yq.go index e1cebd2b74..4dbf43ddff 100644 --- a/src/cmd/tools/yq.go +++ b/src/cmd/tools/yq.go @@ -7,7 +7,6 @@ package tools import ( "github.com/defenseunicorns/zarf/src/config/lang" yq "github.com/mikefarah/yq/v4/cmd" - ) func init() { @@ -16,10 +15,10 @@ func init() { yqCmd.Example = lang.CmdToolsYqExample yqCmd.Use = "yq" for _, subCmd := range yqCmd.Commands() { - if subCmd.Name() == "eval" { + if subCmd.Name() == "eval" { subCmd.Example = lang.CmdToolsYqEvalExample } - if subCmd.Name() == "eval-all" { + if subCmd.Name() == "eval-all" { subCmd.Example = lang.CmdToolsYqEvalAllExample } } diff --git a/src/cmd/tools/zarf.go b/src/cmd/tools/zarf.go index 363fe87c63..20655089bc 100644 --- a/src/cmd/tools/zarf.go +++ b/src/cmd/tools/zarf.go @@ -19,7 +19,6 @@ import ( "github.com/defenseunicorns/zarf/src/internal/packager/git" "github.com/defenseunicorns/zarf/src/internal/packager/helm" "github.com/defenseunicorns/zarf/src/internal/packager/template" - "github.com/defenseunicorns/zarf/src/pkg/cluster" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/defenseunicorns/zarf/src/pkg/packager/sources" "github.com/defenseunicorns/zarf/src/pkg/pki" @@ -51,8 +50,9 @@ var getCredsCmd = &cobra.Command{ Example: lang.CmdToolsGetCredsExample, Aliases: []string{"gc"}, Args: cobra.MaximumNArgs(1), - Run: func(_ *cobra.Command, args []string) { - state, err := cluster.NewClusterOrDie().LoadZarfState() + Run: func(cmd *cobra.Command, args []string) { + ctx := cmd.Context() + state, err := common.NewClusterOrDie(ctx).LoadZarfState(ctx) if err != nil || state.Distro == "" { // If no distro the zarf secret did not load properly message.Fatalf(nil, lang.ErrLoadState) @@ -85,8 +85,9 @@ var updateCredsCmd = &cobra.Command{ } } - c := cluster.NewClusterOrDie() - oldState, err := c.LoadZarfState() + ctx := cmd.Context() + c := common.NewClusterOrDie(ctx) + oldState, err := c.LoadZarfState(ctx) if err != nil || oldState.Distro == "" { // If no distro the zarf secret did not load properly message.Fatalf(nil, lang.ErrLoadState) @@ -114,16 +115,16 @@ var updateCredsCmd = &cobra.Command{ if confirm { // Update registry and git pull secrets if slices.Contains(args, message.RegistryKey) { - c.UpdateZarfManagedImageSecrets(newState) + c.UpdateZarfManagedImageSecrets(ctx, newState) } if slices.Contains(args, message.GitKey) { - c.UpdateZarfManagedGitSecrets(newState) + c.UpdateZarfManagedGitSecrets(ctx, newState) } // Update artifact token (if internal) if slices.Contains(args, message.ArtifactKey) && newState.ArtifactServer.PushToken == "" && newState.ArtifactServer.InternalServer { g := git.New(oldState.GitServer) - tokenResponse, err := g.CreatePackageRegistryToken() + tokenResponse, err := g.CreatePackageRegistryToken(ctx) if err != nil { // Warn if we couldn't actually update the git server (it might not be installed and we should try to continue) message.Warnf(lang.CmdToolsUpdateCredsUnableCreateToken, err.Error()) @@ -133,7 +134,7 @@ var updateCredsCmd = &cobra.Command{ } // Save the final Zarf State - err = c.SaveZarfState(newState) + err = c.SaveZarfState(ctx, newState) if err != nil { message.Fatalf(err, lang.ErrSaveState) } @@ -150,14 +151,14 @@ var updateCredsCmd = &cobra.Command{ } if slices.Contains(args, message.GitKey) && newState.GitServer.InternalServer { g := git.New(newState.GitServer) - err = g.UpdateZarfGiteaUsers(oldState) + err = g.UpdateZarfGiteaUsers(ctx, oldState) if err != nil { // Warn if we couldn't actually update the git server (it might not be installed and we should try to continue) message.Warnf(lang.CmdToolsUpdateCredsUnableUpdateGit, err.Error()) } } if slices.Contains(args, message.AgentKey) { - err = h.UpdateZarfAgentValues() + err = h.UpdateZarfAgentValues(ctx) if err != nil { // Warn if we couldn't actually update the agent (it might not be installed and we should try to continue) message.Warnf(lang.CmdToolsUpdateCredsUnableUpdateAgent, err.Error()) diff --git a/src/config/config.go b/src/config/config.go index b8e3e2d386..8aedce6abb 100644 --- a/src/config/config.go +++ b/src/config/config.go @@ -5,10 +5,8 @@ package config import ( - "crypto/tls" "embed" "fmt" - "net/http" "os" "path/filepath" "runtime" @@ -16,9 +14,6 @@ import ( "time" "github.com/defenseunicorns/zarf/src/types" - "github.com/google/go-containerregistry/pkg/authn" - "github.com/google/go-containerregistry/pkg/crane" - v1 "github.com/google/go-containerregistry/pkg/v1" ) // Zarf Global Configuration Constants. @@ -121,44 +116,6 @@ func GetDataInjectionMarker() string { return fmt.Sprintf(dataInjectionMarker, operationStartTime) } -// GetCraneOptions returns a crane option object with the correct options & platform. -func GetCraneOptions(insecure bool, archs ...string) []crane.Option { - var options []crane.Option - - // Handle insecure registry option - if insecure { - roundTripper := http.DefaultTransport.(*http.Transport).Clone() - roundTripper.TLSClientConfig = &tls.Config{ - InsecureSkipVerify: true, - } - options = append(options, crane.Insecure, crane.WithTransport(roundTripper)) - } - - // Add the image platform info - options = append(options, - crane.WithPlatform(&v1.Platform{ - OS: "linux", - Architecture: GetArch(archs...), - }), - crane.WithUserAgent("zarf"), - crane.WithNoClobber(true), - // TODO: (@WSTARR) this is set to limit pushes to registry pods and reduce the likelihood that crane will get stuck. - // We should investigate this further in the future to dig into more of what is happening (see https://github.com/defenseunicorns/zarf/issues/1568) - crane.WithJobs(1), - ) - - return options -} - -// GetCraneAuthOption returns a crane auth option with the provided credentials. -func GetCraneAuthOption(username string, secret string) crane.Option { - return crane.WithAuth( - authn.FromConfig(authn.AuthConfig{ - Username: username, - Password: secret, - })) -} - // GetAbsCachePath gets the absolute cache path for images and git repos. func GetAbsCachePath() string { return GetAbsHomePath(CommonOptions.CachePath) diff --git a/src/config/lang/english.go b/src/config/lang/english.go index 658ebab539..b72ffa1461 100644 --- a/src/config/lang/english.go +++ b/src/config/lang/english.go @@ -1,8 +1,8 @@ -//go:build !alt_language - // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2021-Present The Zarf Authors +//go:build !alt_language + // Package lang contains the language strings for english used by Zarf // Alternative languages can be created by duplicating this file and changing the build tag to "//go:build alt_language && ". package lang @@ -308,9 +308,10 @@ $ zarf package mirror-resources \ CmdPackageMirrorFlagComponents = "Comma-separated list of components to mirror. This list will be respected regardless of a component's 'required' or 'default' status. Globbing component names with '*' and deselecting components with a leading '-' are also supported." CmdPackageMirrorFlagNoChecksum = "Turns off the addition of a checksum to image tags (as would be used by the Zarf Agent) while mirroring images." - CmdPackageInspectFlagSbom = "View SBOM contents while inspecting the package" - CmdPackageInspectFlagSbomOut = "Specify an output directory for the SBOMs from the inspected Zarf package" - CmdPackageInspectErr = "Failed to inspect package: %s" + CmdPackageInspectFlagSbom = "View SBOM contents while inspecting the package" + CmdPackageInspectFlagSbomOut = "Specify an output directory for the SBOMs from the inspected Zarf package" + CmdPackageInspectFlagListImages = "List images in the package (prints to stdout)" + CmdPackageInspectErr = "Failed to inspect package: %s" CmdPackageRemoveShort = "Removes a Zarf package that has been deployed already (runs offline)" CmdPackageRemoveFlagConfirm = "REQUIRED. Confirm the removal action to prevent accidental deletions" @@ -506,10 +507,10 @@ cat file2.yml | zarf tools yq ea '.a.b' file1.yml - file3.yml ` CmdToolsYqEvalExample = ` # Reads field under the given path for each file -zarf tools yq e '.a.b' f1.yml f2.yml +zarf tools yq e '.a.b' f1.yml f2.yml # Prints out the file -zarf tools yq e sample.yaml +zarf tools yq e sample.yaml # Pipe from STDIN ## use '-' as a filename to pipe from STDIN @@ -517,10 +518,10 @@ cat file2.yml | zarf tools yq e '.a.b' file1.yml - file3.yml # Creates a new yaml document ## Note that editing an empty file does not work. -zarf tools yq e -n '.a.b.c = "cat"' +zarf tools yq e -n '.a.b.c = "cat"' -# Update a file inplace -zarf tools yq e '.a.b = "cool"' -i file.yaml +# Update a file in place +zarf tools yq e '.a.b = "cool"' -i file.yaml ` CmdToolsMonitorShort = "Launches a terminal UI to monitor the connected cluster using K9s." diff --git a/src/extensions/bigbang/bigbang_test.go b/src/extensions/bigbang/bigbang_test.go index b9807ff73b..21d09939f4 100644 --- a/src/extensions/bigbang/bigbang_test.go +++ b/src/extensions/bigbang/bigbang_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2021-Present The Zarf Authors + package bigbang import ( diff --git a/src/extensions/bigbang/test/bigbang_test.go b/src/extensions/bigbang/test/bigbang_test.go index 468a498b5b..4e016c6a6f 100644 --- a/src/extensions/bigbang/test/bigbang_test.go +++ b/src/extensions/bigbang/test/bigbang_test.go @@ -1,10 +1,16 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2021-Present The Zarf Authors + package main import ( "context" + "encoding/json" "fmt" + "io" "net/http" "os" + "regexp" "strings" "testing" @@ -14,35 +20,28 @@ import ( "github.com/stretchr/testify/require" ) -// Code related to fetching the last two Big Bang versions -// and using them to set the BB_VERSION and BB_MAJOR variables -// has been commented out due to a bug in how Zarf clones and checks out git repos. -// -// https://github.com/defenseunicorns/zarf/actions/runs/8529925302/job/23403205495?pr=2411#step:9:897 -// -// The versions are currently hardcoded to the last two known working versions. -// TODO: fix the git clone/checkout bug and update this test to not be hardcoded. - // The Big Bang project ID on Repo1 -// const bbProjID = "2872" +const bbProjID = "2872" var ( - zarf string - // previous string - // latest string + zarf string + previous string + latest string ) func TestMain(m *testing.M) { + var err error + // Change to the build dir if err := os.Chdir("../../../../build/"); err != nil { panic(err) } - // // Get the latest and previous releases - // latest, previous, err = getReleases() - // if err != nil { - // panic(err) - // } + // Get the latest and previous releases + latest, previous, err = getReleases() + if err != nil { + panic(err) + } // Get the Zarf CLI path zarf = fmt.Sprintf("./%s", test.GetCLIName()) @@ -61,45 +60,47 @@ func TestReleases(t *testing.T) { zarfCache = fmt.Sprintf("--zarf-cache=%s", CIMount) } + ctx := context.Background() + // Initialize the cluster with the Git server and AMD64 architecture arch := "amd64" - stdOut, stdErr, err := zarfExec("init", "--components", "git-server", "--architecture", arch, tmpdir, "--confirm", zarfCache) + stdOut, stdErr, err := zarfExec(ctx, "init", "--components", "git-server", "--architecture", arch, tmpdir, "--confirm", zarfCache) require.NoError(t, err, stdOut, stdErr) // Remove the init package to free up disk space on the test runner - err = os.RemoveAll(fmt.Sprintf("zarf-init-%s-%s.tar.zst", arch, getZarfVersion(t))) + err = os.RemoveAll(fmt.Sprintf("zarf-init-%s-%s.tar.zst", arch, getZarfVersion(ctx, t))) require.NoError(t, err) // Build the previous version - bbVersion := "--set=BB_VERSION=2.22.0" - bbMajor := "--set=BB_MAJOR=2" - stdOut, stdErr, err = zarfExec("package", "create", "../src/extensions/bigbang/test/package", bbVersion, bbMajor, tmpdir, "--confirm") + bbVersion := fmt.Sprintf("--set=BB_VERSION=%s", previous) + bbMajor := fmt.Sprintf("--set=BB_MAJOR=%s", previous[0:1]) + stdOut, stdErr, err = zarfExec(ctx, "package", "create", "../src/extensions/bigbang/test/package", bbVersion, bbMajor, tmpdir, "--confirm") require.NoError(t, err, stdOut, stdErr) // Clean up zarf cache to reduce disk pressure - stdOut, stdErr, err = zarfExec("tools", "clear-cache") + stdOut, stdErr, err = zarfExec(ctx, "tools", "clear-cache") require.NoError(t, err, stdOut, stdErr) // Deploy the previous version - pkgPath := fmt.Sprintf("zarf-package-big-bang-test-%s-2.22.0.tar.zst", arch) - stdOut, stdErr, err = zarfExec("package", "deploy", pkgPath, tmpdir, "--confirm") + pkgPath := fmt.Sprintf("zarf-package-big-bang-test-%s-%s.tar.zst", arch, previous) + stdOut, stdErr, err = zarfExec(ctx, "package", "deploy", pkgPath, tmpdir, "--confirm") require.NoError(t, err, stdOut, stdErr) // HACK: scale down the flux deployments due to very-low CPU in the test runner fluxControllers := []string{"helm-controller", "source-controller", "kustomize-controller", "notification-controller"} for _, deployment := range fluxControllers { - stdOut, stdErr, err = zarfExec("tools", "kubectl", "-n", "flux-system", "scale", "deployment", deployment, "--replicas=0") + stdOut, stdErr, err = zarfExec(ctx, "tools", "kubectl", "-n", "flux-system", "scale", "deployment", deployment, "--replicas=0") require.NoError(t, err, stdOut, stdErr) } // Cluster info - stdOut, stdErr, err = zarfExec("tools", "kubectl", "describe", "nodes") + stdOut, stdErr, err = zarfExec(ctx, "tools", "kubectl", "describe", "nodes") require.NoError(t, err, stdOut, stdErr) // Build the latest version - bbVersion = "--set=BB_VERSION=2.23.0" - bbMajor = "--set=BB_MAJOR=2" - stdOut, stdErr, err = zarfExec("package", "create", "../src/extensions/bigbang/test/package", bbVersion, bbMajor, "--differential", pkgPath, tmpdir, "--confirm") + bbVersion = fmt.Sprintf("--set=BB_VERSION=%s", latest) + bbMajor = fmt.Sprintf("--set=BB_MAJOR=%s", latest[0:1]) + stdOut, stdErr, err = zarfExec(ctx, "package", "create", "../src/extensions/bigbang/test/package", bbVersion, bbMajor, "--differential", pkgPath, tmpdir, "--confirm") require.NoError(t, err, stdOut, stdErr) // Remove the previous version package @@ -107,23 +108,23 @@ func TestReleases(t *testing.T) { require.NoError(t, err) // Clean up zarf cache to reduce disk pressure - stdOut, stdErr, err = zarfExec("tools", "clear-cache") + stdOut, stdErr, err = zarfExec(ctx, "tools", "clear-cache") require.NoError(t, err, stdOut, stdErr) // Deploy the latest version - pkgPath = fmt.Sprintf("zarf-package-big-bang-test-%s-2.22.0-differential-2.23.0.tar.zst", arch) - stdOut, stdErr, err = zarfExec("package", "deploy", pkgPath, tmpdir, "--confirm") + pkgPath = fmt.Sprintf("zarf-package-big-bang-test-%s-%s-differential-%s.tar.zst", arch, previous, latest) + stdOut, stdErr, err = zarfExec(ctx, "package", "deploy", pkgPath, tmpdir, "--confirm") require.NoError(t, err, stdOut, stdErr) // Cluster info - stdOut, stdErr, err = zarfExec("tools", "kubectl", "describe", "nodes") + stdOut, stdErr, err = zarfExec(ctx, "tools", "kubectl", "describe", "nodes") require.NoError(t, err, stdOut, stdErr) // Test connectivity to Twistlock - testConnection(t) + testConnection(ctx, t) } -func testConnection(t *testing.T) { +func testConnection(ctx context.Context, t *testing.T) { // Establish the tunnel config c, err := cluster.NewCluster() require.NoError(t, err) @@ -131,7 +132,7 @@ func testConnection(t *testing.T) { require.NoError(t, err) // Establish the tunnel connection - _, err = tunnel.Connect() + _, err = tunnel.Connect(ctx) require.NoError(t, err) defer tunnel.Close() @@ -141,56 +142,56 @@ func testConnection(t *testing.T) { require.Equal(t, 200, resp.StatusCode) } -func zarfExec(args ...string) (string, string, error) { - return exec.CmdWithContext(context.TODO(), exec.PrintCfg(), zarf, args...) +func zarfExec(ctx context.Context, args ...string) (string, string, error) { + return exec.CmdWithContext(ctx, exec.PrintCfg(), zarf, args...) } // getZarfVersion returns the current build/zarf version -func getZarfVersion(t *testing.T) string { +func getZarfVersion(ctx context.Context, t *testing.T) string { // Get the version of the CLI - stdOut, stdErr, err := zarfExec("version") + stdOut, stdErr, err := zarfExec(ctx, "version") require.NoError(t, err, stdOut, stdErr) return strings.Trim(stdOut, "\n") } -// func getReleases() (latest, previous string, err error) { -// // Create the URL for the API endpoint -// url := fmt.Sprintf("https://repo1.dso.mil/api/v4/projects/%s/repository/tags", bbProjID) - -// // Send an HTTP GET request to the API endpoint -// resp, err := http.Get(url) -// if err != nil { -// return latest, previous, err -// } -// defer resp.Body.Close() - -// // Read the response body -// body, err := io.ReadAll(resp.Body) -// if err != nil { -// return latest, previous, err -// } - -// // Parse the response body as a JSON array of objects -// var data []map[string]interface{} -// err = json.Unmarshal(body, &data) -// if err != nil { -// return latest, previous, err -// } - -// // Compile the regular expression for filtering tags that don't contain a hyphen -// re := regexp.MustCompile("^[^-]+$") - -// // Create a slice to store the tag names that match the regular expression -// var releases []string - -// // Iterate over the tags returned by the API, and filter out tags that don't match the regular expression -// for _, tag := range data { -// name := tag["name"].(string) -// if re.MatchString(name) { -// releases = append(releases, name) -// } -// } - -// // Set the latest and previous release variables to the first two releases -// return releases[0], releases[1], nil -// } +func getReleases() (latest, previous string, err error) { + // Create the URL for the API endpoint + url := fmt.Sprintf("https://repo1.dso.mil/api/v4/projects/%s/repository/tags", bbProjID) + + // Send an HTTP GET request to the API endpoint + resp, err := http.Get(url) + if err != nil { + return latest, previous, err + } + defer resp.Body.Close() + + // Read the response body + body, err := io.ReadAll(resp.Body) + if err != nil { + return latest, previous, err + } + + // Parse the response body as a JSON array of objects + var data []map[string]interface{} + err = json.Unmarshal(body, &data) + if err != nil { + return latest, previous, err + } + + // Compile the regular expression for filtering tags that don't contain a hyphen + re := regexp.MustCompile("^[^-]+$") + + // Create a slice to store the tag names that match the regular expression + var releases []string + + // Iterate over the tags returned by the API, and filter out tags that don't match the regular expression + for _, tag := range data { + name := tag["name"].(string) + if re.MatchString(name) { + releases = append(releases, name) + } + } + + // Set the latest and previous release variables to the first two releases + return releases[0], releases[1], nil +} diff --git a/src/injector/.cargo/config.toml b/src/injector/.cargo/config.toml new file mode 100644 index 0000000000..d5bd8214f3 --- /dev/null +++ b/src/injector/.cargo/config.toml @@ -0,0 +1,5 @@ +[target.x86_64-unknown-linux-musl] +rustflags = ["-C", "relocation-model=static", "-C", "strip=symbols"] + +[target.aarch64-unknown-linux-musl] +rustflags = ["-C", "relocation-model=static", "-C", "strip=symbols"] diff --git a/src/injector/Cargo.lock b/src/injector/Cargo.lock index 04aa2408f3..4915654980 100644 --- a/src/injector/Cargo.lock +++ b/src/injector/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + [[package]] name = "adler" version = "1.0.2" @@ -9,53 +18,86 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] -name = "adler32" -version = "1.2.0" +name = "async-trait" +version = "0.1.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" +checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] -name = "alloc-no-stdlib" -version = "2.0.4" +name = "axum" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" +checksum = "3a6c9af12842a67734c9a2e355436e5d03b22383ed60cf13cd0c18fbfe3dcbcf" +dependencies = [ + "async-trait", + "axum-core", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper 1.0.1", + "tokio", + "tower", + "tower-layer", + "tower-service", + "tracing", +] [[package]] -name = "alloc-stdlib" -version = "0.2.2" +name = "axum-core" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +checksum = "a15c63fd72d41492dc4f497196f5da1fb04fb7529e631d73630d1b491e47a2e3" dependencies = [ - "alloc-no-stdlib", + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper 0.1.2", + "tower-layer", + "tower-service", + "tracing", ] [[package]] -name = "android_system_properties" -version = "0.1.5" +name = "backtrace" +version = "0.3.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" dependencies = [ + "addr2line", + "cc", + "cfg-if", "libc", + "miniz_oxide", + "object", + "rustc-demangle", ] -[[package]] -name = "ascii" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" - -[[package]] -name = "autocfg" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" - -[[package]] -name = "base64" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" - [[package]] name = "bitflags" version = "1.3.2" @@ -63,56 +105,31 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] -name = "block-buffer" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" -dependencies = [ - "generic-array", -] - -[[package]] -name = "brotli" -version = "3.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1a0b1dbcc8ae29329621f8d4f0d835787c1c38bb1401979b49d13b0b305ff68" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", - "brotli-decompressor", -] - -[[package]] -name = "brotli-decompressor" -version = "2.3.2" +name = "bitflags" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ad2d4653bf5ca36ae797b1f4bb4dbddb60ce49ca4aed8a2ce4829f60425b80" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", -] +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" [[package]] -name = "buf_redux" -version = "0.8.4" +name = "block-buffer" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b953a6887648bb07a535631f2bc00fbdb2a2216f135552cb3f534ed136b9c07f" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ - "memchr", - "safemem", + "generic-array", ] [[package]] -name = "bumpalo" -version = "3.11.1" +name = "bytes" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" [[package]] name = "cc" -version = "1.0.73" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" +checksum = "d32a725bc159af97c3e629873bb9f88fb8cf8a4867175f76dc987815ea07c83b" [[package]] name = "cfg-if" @@ -120,122 +137,34 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "chrono" -version = "0.4.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1" -dependencies = [ - "iana-time-zone", - "num-integer", - "num-traits", - "winapi", -] - -[[package]] -name = "chunked_transfer" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fff857943da45f546682664a79488be82e69e43c1a7a2307679ab9afb3a66d2e" - -[[package]] -name = "codespan-reporting" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" -dependencies = [ - "termcolor", - "unicode-width", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" - [[package]] name = "cpufeatures" -version = "0.2.1" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" dependencies = [ "libc", ] [[package]] name = "crc32fast" -version = "1.3.2" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" dependencies = [ "cfg-if", ] [[package]] name = "crypto-common" -version = "0.1.3" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", "typenum", ] -[[package]] -name = "cxx" -version = "1.0.80" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b7d4e43b25d3c994662706a1d4fcfc32aaa6afd287502c111b237093bb23f3a" -dependencies = [ - "cc", - "cxxbridge-flags", - "cxxbridge-macro", - "link-cplusplus", -] - -[[package]] -name = "cxx-build" -version = "1.0.80" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84f8829ddc213e2c1368e51a2564c552b65a8cb6a28f31e576270ac81d5e5827" -dependencies = [ - "cc", - "codespan-reporting", - "once_cell", - "proc-macro2", - "quote", - "scratch", - "syn 1.0.103", -] - -[[package]] -name = "cxxbridge-flags" -version = "1.0.80" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e72537424b474af1460806647c41d4b6d35d09ef7fe031c5c2fa5766047cc56a" - -[[package]] -name = "cxxbridge-macro" -version = "1.0.80" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "309e4fb93eed90e1e14bea0da16b209f81813ba9fc7830c20ed151dd7bc0a4d7" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.103", -] - -[[package]] -name = "deflate" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c86f7e25f518f4b81808a2cf1c50996a61f5c2eb394b2393bd87f2a4780a432f" -dependencies = [ - "adler32", - "gzip-header", -] - [[package]] name = "digest" version = "0.10.7" @@ -247,24 +176,25 @@ dependencies = [ ] [[package]] -name = "fastrand" -version = "1.8.0" +name = "errno" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" dependencies = [ - "instant", + "libc", + "windows-sys 0.52.0", ] [[package]] name = "filetime" -version = "0.2.15" +version = "0.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "975ccf83d8d9d0d84682850a38c8169027be83368805971cc4f238c2b245bc98" +checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd" dependencies = [ "cfg-if", "libc", "redox_syscall", - "winapi", + "windows-sys 0.52.0", ] [[package]] @@ -277,404 +207,373 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "form_urlencoded" -version = "1.1.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ "percent-encoding", ] [[package]] -name = "generic-array" -version = "0.14.5" +name = "futures-channel" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" dependencies = [ - "typenum", - "version_check", + "futures-core", ] [[package]] -name = "getrandom" -version = "0.2.8" +name = "futures-core" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" [[package]] -name = "glob" -version = "0.3.1" +name = "futures-sink" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" [[package]] -name = "gzip-header" -version = "1.0.0" +name = "futures-task" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95cc527b92e6029a62960ad99aa8a6660faa4555fe5f731aab13aa6a921795a2" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ - "crc32fast", + "futures-core", + "futures-task", + "pin-project-lite", + "pin-utils", ] [[package]] -name = "hermit-abi" -version = "0.1.19" +name = "generic-array" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ - "libc", + "typenum", + "version_check", ] [[package]] -name = "hex" -version = "0.4.3" +name = "gimli" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] -name = "httparse" -version = "1.8.0" +name = "glob" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] -name = "httpdate" -version = "1.0.2" +name = "hex" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] -name = "iana-time-zone" -version = "0.1.51" +name = "http" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5a6ef98976b22b3b7f2f3a806f858cb862044cfa66805aa3ad84cb3d3b785ed" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "wasm-bindgen", - "winapi", + "bytes", + "fnv", + "itoa", ] [[package]] -name = "iana-time-zone-haiku" -version = "0.1.1" +name = "http-body" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" +checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" dependencies = [ - "cxx", - "cxx-build", + "bytes", + "http", ] [[package]] -name = "idna" -version = "0.3.0" +name = "http-body-util" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d" dependencies = [ - "unicode-bidi", - "unicode-normalization", + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", ] [[package]] -name = "instant" -version = "0.1.12" +name = "httparse" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" -dependencies = [ - "cfg-if", -] +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" [[package]] -name = "itoa" -version = "1.0.4" +name = "httpdate" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] -name = "js-sys" -version = "0.3.60" +name = "hyper" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" +checksum = "fe575dd17d0862a9a33781c8c4696a55c320909004a67a00fb286ba8b1bc496d" dependencies = [ - "wasm-bindgen", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", ] [[package]] -name = "libc" -version = "0.2.148" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" - -[[package]] -name = "link-cplusplus" -version = "1.0.7" +name = "hyper-util" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9272ab7b96c9046fbc5bc56c06c117cb639fe2d509df0c421cad82d2915cf369" +checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa" dependencies = [ - "cc", + "bytes", + "futures-util", + "http", + "http-body", + "hyper", + "pin-project-lite", + "socket2", + "tokio", ] [[package]] -name = "log" -version = "0.4.17" +name = "itoa" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if", -] +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] -name = "memchr" -version = "2.5.0" +name = "libc" +version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" [[package]] -name = "mime" -version = "0.3.16" +name = "linux-raw-sys" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" [[package]] -name = "mime_guess" -version = "2.0.4" +name = "log" +version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" -dependencies = [ - "mime", - "unicase", -] +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" [[package]] -name = "miniz_oxide" -version = "0.7.1" +name = "matchit" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" -dependencies = [ - "adler", -] +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" [[package]] -name = "multipart" -version = "0.18.0" +name = "memchr" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00dec633863867f29cb39df64a397cdf4a6354708ddd7759f70c7fb51c5f9182" -dependencies = [ - "buf_redux", - "httparse", - "log", - "mime", - "mime_guess", - "quick-error", - "rand", - "safemem", - "tempfile", - "twoway", -] +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" [[package]] -name = "num-integer" -version = "0.1.45" +name = "mime" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" -dependencies = [ - "autocfg", - "num-traits", -] +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] -name = "num-traits" -version = "0.2.15" +name = "miniz_oxide" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" dependencies = [ - "autocfg", + "adler", ] [[package]] -name = "num_cpus" -version = "1.13.1" +name = "mio" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ - "hermit-abi", "libc", + "wasi", + "windows-sys 0.48.0", ] [[package]] -name = "num_threads" -version = "0.1.6" +name = "object" +version = "0.32.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" dependencies = [ - "libc", + "memchr", ] [[package]] name = "once_cell" -version = "1.15.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "percent-encoding" -version = "2.2.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] -name = "ppv-lite86" -version = "0.2.16" +name = "pin-project" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +dependencies = [ + "pin-project-internal", +] [[package]] -name = "proc-macro2" -version = "1.0.78" +name = "pin-project-internal" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ - "unicode-ident", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "quick-error" -version = "1.2.3" +name = "pin-project-lite" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" [[package]] -name = "quote" -version = "1.0.35" +name = "pin-utils" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" -dependencies = [ - "proc-macro2", -] +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] -name = "rand" -version = "0.8.5" +name = "proc-macro2" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" dependencies = [ - "libc", - "rand_chacha", - "rand_core", + "unicode-ident", ] [[package]] -name = "rand_chacha" -version = "0.3.1" +name = "quote" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ - "ppv-lite86", - "rand_core", + "proc-macro2", ] [[package]] -name = "rand_core" -version = "0.6.4" +name = "redox_syscall" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" dependencies = [ - "getrandom", + "bitflags 1.3.2", ] [[package]] -name = "redox_syscall" -version = "0.2.10" +name = "regex-lite" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" -dependencies = [ - "bitflags", -] +checksum = "30b661b2f27137bdbc16f00eda72866a92bb28af1753ffbd56744fb6e2e9cd8e" [[package]] -name = "remove_dir_all" -version = "0.5.3" +name = "rustc-demangle" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" -dependencies = [ - "winapi", -] +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] -name = "rouille" -version = "3.6.2" +name = "rustix" +version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3716fbf57fc1084d7a706adf4e445298d123e4a44294c4e8213caf1b85fcc921" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ - "base64", - "brotli", - "chrono", - "deflate", - "filetime", - "multipart", - "percent-encoding", - "rand", - "serde", - "serde_derive", - "serde_json", - "sha1_smol", - "threadpool", - "time", - "tiny_http", - "url", + "bitflags 2.5.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", ] [[package]] -name = "ryu" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" - -[[package]] -name = "safemem" -version = "0.3.3" +name = "rustversion" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" +checksum = "80af6f9131f277a45a3fba6ce8e2258037bb0477a67e610d3c1fe046ab31de47" [[package]] -name = "scratch" -version = "1.0.2" +name = "ryu" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898" +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" [[package]] name = "serde" -version = "1.0.196" +version = "1.0.198" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32" +checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.196" +version = "1.0.198" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" +checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn", ] [[package]] name = "serde_json" -version = "1.0.113" +version = "1.0.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79" +checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" dependencies = [ "itoa", "ryu", @@ -682,10 +581,26 @@ dependencies = [ ] [[package]] -name = "sha1_smol" -version = "1.0.0" +name = "serde_path_to_error" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6" +dependencies = [ + "itoa", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] [[package]] name = "sha2" @@ -699,27 +614,44 @@ dependencies = [ ] [[package]] -name = "syn" -version = "1.0.103" +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "socket2" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" +checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", + "libc", + "windows-sys 0.52.0", ] [[package]] name = "syn" -version = "2.0.48" +version = "2.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "sync_wrapper" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" + [[package]] name = "tar" version = "0.4.40" @@ -732,259 +664,278 @@ dependencies = [ ] [[package]] -name = "tempfile" -version = "3.3.0" +name = "tokio" +version = "1.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" dependencies = [ - "cfg-if", - "fastrand", + "backtrace", "libc", - "redox_syscall", - "remove_dir_all", - "winapi", + "mio", + "pin-project-lite", + "socket2", + "tokio-macros", + "windows-sys 0.48.0", ] [[package]] -name = "termcolor" -version = "1.1.3" +name = "tokio-macros" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ - "winapi-util", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "threadpool" -version = "1.8.1" +name = "tokio-util" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" +checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" dependencies = [ - "num_cpus", + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", ] [[package]] -name = "time" -version = "0.3.16" +name = "tower" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fab5c8b9980850e06d92ddbe3ab839c062c801f3927c0fb8abd6fc8e918fbca" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" dependencies = [ - "libc", - "num_threads", - "serde", - "time-core", + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite", + "tokio", + "tower-layer", + "tower-service", + "tracing", ] [[package]] -name = "time-core" -version = "0.1.0" +name = "tower-layer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" + +[[package]] +name = "tower-service" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] -name = "tiny_http" -version = "0.12.0" +name = "tracing" +version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389915df6413a2e74fb181895f933386023c71110878cd0825588928e64cdc82" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ - "ascii", - "chunked_transfer", - "httpdate", "log", + "pin-project-lite", + "tracing-core", ] [[package]] -name = "tinyvec" -version = "1.6.0" +name = "tracing-core" +version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ - "tinyvec_macros", + "once_cell", ] [[package]] -name = "tinyvec_macros" -version = "0.1.0" +name = "typenum" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] -name = "twoway" -version = "0.1.8" +name = "unicode-ident" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59b11b2b5241ba34be09c3cc85a36e56e48f9888862e19cedf23336d35316ed1" -dependencies = [ - "memchr", -] +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] -name = "typenum" -version = "1.15.0" +name = "version_check" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] -name = "unicase" -version = "2.6.0" +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "version_check", + "windows-targets 0.48.5", ] [[package]] -name = "unicode-bidi" -version = "0.3.8" +name = "windows-sys" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.5", +] [[package]] -name = "unicode-ident" -version = "1.0.5" +name = "windows-targets" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] [[package]] -name = "unicode-normalization" -version = "0.1.22" +name = "windows-targets" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" dependencies = [ - "tinyvec", + "windows_aarch64_gnullvm 0.52.5", + "windows_aarch64_msvc 0.52.5", + "windows_i686_gnu 0.52.5", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.5", + "windows_x86_64_gnu 0.52.5", + "windows_x86_64_gnullvm 0.52.5", + "windows_x86_64_msvc 0.52.5", ] [[package]] -name = "unicode-width" -version = "0.1.10" +name = "windows_aarch64_gnullvm" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] -name = "url" -version = "2.3.1" +name = "windows_aarch64_gnullvm" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", -] +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" [[package]] -name = "version_check" -version = "0.9.4" +name = "windows_aarch64_msvc" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +name = "windows_aarch64_msvc" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" [[package]] -name = "wasm-bindgen" -version = "0.2.83" +name = "windows_i686_gnu" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" -dependencies = [ - "cfg-if", - "wasm-bindgen-macro", -] +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] -name = "wasm-bindgen-backend" -version = "0.2.83" +name = "windows_i686_gnu" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" -dependencies = [ - "bumpalo", - "log", - "once_cell", - "proc-macro2", - "quote", - "syn 1.0.103", - "wasm-bindgen-shared", -] +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" [[package]] -name = "wasm-bindgen-macro" -version = "0.2.83" +name = "windows_i686_gnullvm" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" [[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.83" +name = "windows_i686_msvc" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.103", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] -name = "wasm-bindgen-shared" -version = "0.2.83" +name = "windows_i686_msvc" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" [[package]] -name = "winapi" -version = "0.3.9" +name = "windows_x86_64_gnu" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" +name = "windows_x86_64_gnu" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" [[package]] -name = "winapi-util" -version = "0.1.5" +name = "windows_x86_64_gnullvm" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" -dependencies = [ - "winapi", -] +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" [[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" +name = "windows_x86_64_msvc" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" [[package]] name = "xattr" -version = "1.0.1" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4686009f71ff3e5c4dbcf1a282d0a44db3f021ba69350cd42086b3e5f1c6985" +checksum = "8da84f1a25939b27f6820d92aed108f83ff920fdf11a7b19366c27c4cda81d4f" dependencies = [ "libc", + "linux-raw-sys", + "rustix", ] [[package]] name = "zarf-injector" version = "0.5.0" dependencies = [ + "axum", "flate2", "glob", "hex", - "rouille", + "regex-lite", "serde_json", "sha2", "tar", + "tokio", + "tokio-util", ] diff --git a/src/injector/Cargo.toml b/src/injector/Cargo.toml index fb671ca2d5..050f6e314a 100644 --- a/src/injector/Cargo.toml +++ b/src/injector/Cargo.toml @@ -1,6 +1,7 @@ # SPDX-License-Identifier: Apache-2.0 # SPDX-FileCopyrightText: 2021-Present The Zarf Authors + [profile.release] opt-level = "z" # Optimize for size. lto = true @@ -8,6 +9,8 @@ codegen-units = 1 panic = "abort" strip = true + + [package] name = "zarf-injector" version = "0.5.0" @@ -20,6 +23,9 @@ glob = "0.3.1" flate2 = "1.0.28" tar = "0.4.40" sha2 = "0.10.8" -hex = "0.4.3" -rouille = "3.6.2" -serde_json = "1.0.113" +hex = {version = "0.4.3", default-features = false} +serde_json = { version = "1.0.113", default-features = false, features = ["alloc"] } +axum = {version = "0.7.5", features = ["tokio"]} +tokio = { version = "1.35.0", features = ["fs", "rt"] } +tokio-util = { version = "0.7.10", features = ["io"]} +regex-lite = "0.1.5" diff --git a/src/injector/Makefile b/src/injector/Makefile index 8f40fbadbf..19e47d5869 100644 --- a/src/injector/Makefile +++ b/src/injector/Makefile @@ -10,6 +10,29 @@ help: ## Display this help information clean: ## Clean the build directory rm -rf target +cross-injector-linux: cross-injector-amd cross-injector-arm + +cross-injector-amd: + rustup target add x86_64-unknown-linux-musl + test -s x86_64-linux-musl-cross || curl https://zarf-public.s3-us-gov-west-1.amazonaws.com/pipelines/x86_64-linux-musl-cross.tgz | tar -xz + export PATH="$$PWD/x86_64-linux-musl-cross/bin:$$PATH" + export CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_LINKER=x86_64-linux-musl-cc + + cargo install cross --git https://github.com/cross-rs/cross + + cross build --target x86_64-unknown-linux-musl --release + + +cross-injector-arm: + rustup target add aarch64-unknown-linux-musl + test -s aarch64-linux-musl-cross || curl https://zarf-public.s3-us-gov-west-1.amazonaws.com/pipelines/aarch64-linux-musl-cross.tgz | tar -xz + export PATH="$$PWD/aarch64-linux-musl-cross/bin:$$PATH" + export CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_LINKER=aarch64-linux-musl-cc + + cross build --target aarch64-unknown-linux-musl --release + + + build-injector-linux: build-injector-linux-amd build-injector-linux-arm ## Build the Zarf injector for AMD64 and ARM64 build-injector-linux-amd: ## Build the Zarf injector for AMD64 @@ -24,8 +47,6 @@ build-injector-linux-amd: ## Build the Zarf injector for AMD64 cargo build --target x86_64-unknown-linux-musl --release; \ fi - du --si target/x86_64-unknown-linux-musl/release/zarf-injector - build-injector-linux-arm: ## Build the Zarf injector for ARM64 rustup target add aarch64-unknown-linux-musl @@ -38,4 +59,10 @@ build-injector-linux-arm: ## Build the Zarf injector for ARM64 cargo build --target aarch64-unknown-linux-musl --release; \ fi +list-sizes: ## List the sizes of the Zarf injector binaries + @echo '\n\033[0;36mSize of Zarf injector binaries:\033[0m\n'; \ + du --si target/x86_64-unknown-linux-musl/release/zarf-injector; \ du --si target/aarch64-unknown-linux-musl/release/zarf-injector + +build-with-docker: ## Build the Zarf injector using Docker + docker run --rm --user "$(id -u)":"$(id -g)" -v $$PWD:/usr/src/zarf-injector -w /usr/src/zarf-injector rust:1.71.0-bookworm make build-injector-linux diff --git a/src/injector/README.md b/src/injector/README.md index 699be652ed..d92aa85712 100644 --- a/src/injector/README.md +++ b/src/injector/README.md @@ -1,33 +1,124 @@ + # zarf-injector -A tiny (<1MiB) binary statically-linked with musl in order to fit as a configmap +> If using VSCode w/ the official Rust extension, make sure to open a new window in the `src/injector` directory to make `rust-analyzer` happy. +> +> ```bash +> code src/injector +> ``` + +A tiny (<1MiB) binary statically-linked with musl in order to fit as a configmap. + +See how it gets used during the [`zarf-init`](https://docs.zarf.dev/commands/zarf_init/) process in the ['init' package reference documentation](https://docs.zarf.dev/ref/init-package/). + +## What does it do? + +```sh +zarf-injector +``` + +The `zarf-injector` binary serves 2 purposes during 'init'. + +1. It re-assembles a multi-part tarball that was split into multiple ConfigMap entries (located at `./zarf-payload-*`) back into `payload.tar.gz`, then extracts it to the `/zarf-seed` directory. It also checks that the SHA256 hash of the re-assembled tarball matches the first (and only) argument provided to the binary. +2. It runs a pull-only, insecure, HTTP OCI compliant registry server on port 5000 that serves the contents of the `/zarf-seed` directory (which is of the OCI layout format). + +This enables a distro-agnostic way to inject real `registry:2` image into a running cluster, thereby enabling air-gapped deployments. + +## Building in Docker (recommended) + +```bash +make build-with-docker +``` + +## Building on Debian-based Systems -## Building on Ubuntu +Install [Rust](https://rustup.rs/) and `build-essential`. ```bash -# install rust -curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --no-modify-path -source $HOME/.cargo/env +make build-injector-linux list-sizes +``` + +## Building on Apple Silicon + +* Install Cross +* Install Docker & have it running +* Rust must be installed via Rustup (Check `which rustc` if you're unsure) -# install build-essential -sudo apt install build-essential -y +``` +cargo install cross --git https://github.com/cross-rs/cross +``` + +Whichever arch. of `musl` used, add to toolchain +``` +rustup toolchain install --force-non-host stable-x86_64-unknown-linux-musl +``` +``` +cross build --target x86_64-unknown-linux-musl --release -# build w/ musl -rustup target add x86_64-unknown-linux-musl -cargo build --target x86_64-unknown-linux-musl --release +cross build --target aarch64-unknown-linux-musl --release ``` +This will build into `target/*--unknown-linux-musl/release` + + + ## Checking Binary Size Due to the ConfigMap size limit (1MiB for binary data), we need to make sure the binary is small enough to fit. ```bash -cargo build --target x86_64-unknown-linux-musl --release +make list-sizes +``` + +```sh +$ make build-with-docker +... + +Size of Zarf injector binaries: + +840k target/x86_64-unknown-linux-musl/release/zarf-injector +713k target/aarch64-unknown-linux-musl/release/zarf-injector +``` + +## Testing your injector + +Build your injector by following the steps above, or running one of the following: +``` +make build-injector-linux + +## OR +## works on apple silicon +make cross-injector-linux + +``` + +Point the [zarf-registry/zarf.yaml](../../packages/zarf-registry/zarf.yaml) to +the locally built injector image. -cargo build --target aarch64-unknown-linux-musl --release +``` + files: + # Rust Injector Binary + - source: ../../src/injector/target/x86_64-unknown-linux-musl/release/zarf-injector + target: "###ZARF_TEMP###/zarf-injector" + + executable: true -size_linux=$(du --si target/x86_64-unknown-linux-musl/release/zarf-injector | cut -f1) -echo "Linux binary size: $size_linux" -size_aarch64=$(du --si target/aarch64-unknown-linux-musl/release/zarf-injector | cut -f1) -echo "aarch64 binary size: $size_aarch64" + files: + # Rust Injector Binary + - source: ../../src/injector/target/aarch64-unknown-linux-musl/release/zarf-injector + target: "###ZARF_TEMP###/zarf-injector" + + executable: true ``` + +In Zarf Root Directory, run: +``` +zarf tools clear-cache +make clean +make && make init-package +``` + +If you are running on an Apple Silicon, add the `ARCH` flag: `make init-package ARCH=arm64` + +This builds all artifacts within the `/build` directory. Running `zarf init` would look like: +`.build/zarf-mac-apple init --components git-server -l trace` diff --git a/src/injector/src/main.rs b/src/injector/src/main.rs index ee9d0fad4f..e1b9cc3dc5 100644 --- a/src/injector/src/main.rs +++ b/src/injector/src/main.rs @@ -7,17 +7,25 @@ use std::fs::File; use std::io; use std::io::Read; use std::io::Write; -use std::path::{Path, PathBuf}; - +use std::path::PathBuf; + +use axum::{ + body::Body, + extract::Path, + http::StatusCode, + response::{IntoResponse, Response}, + routing::get, + Router, +}; use flate2::read::GzDecoder; use glob::glob; use hex::ToHex; -use rouille::{accept, router, Request, Response}; +use regex_lite::Regex; use serde_json::Value; use sha2::{Digest, Sha256}; use tar::Archive; - -const DOCKER_MIME_TYPE: &str = "application/vnd.docker.distribution.manifest.v2+json"; +use tokio_util::io::ReaderStream; +const OCI_MIME_TYPE: &str = "application/vnd.oci.image.manifest.v1+json"; // Reads the binary contents of a file fn get_file(path: &PathBuf) -> io::Result> { @@ -93,83 +101,77 @@ fn unpack(sha_sum: &String) { /// index.json - the image index /// blobs/sha256/ - the image layers /// oci-layout - the OCI image layout -fn start_seed_registry() { - let root = PathBuf::from("/zarf-seed"); - println!("Starting seed registry at {} on port 5000", root.display()); - rouille::start_server("0.0.0.0:5000", move |request| { - rouille::log(request, io::stdout(), || { - router!(request, - (GET) (/v2/) => { - // returns empty json w/ Docker-Distribution-Api-Version header set - Response::text("{}") - .with_unique_header("Content-Type", "application/json; charset=utf-8") - .with_additional_header("Docker-Distribution-Api-Version", "registry/2.0") - .with_additional_header("X-Content-Type-Options", "nosniff") - }, - - _ => { - handle_request(&root, &request) - } - ) - }) - }); +fn start_seed_registry() -> Router { + // The name and reference parameter identify the image + // The reference may include a tag or digest. + Router::new() + .route("/v2/*path", get(handler)) + .route( + "/v2/", + get(|| async { + Response::builder() + .status(StatusCode::OK) + .header("Content-Type", "application/json; charset=utf-8") + .header("Docker-Distribution-Api-Version", "registry/2.0") + .header("X-Content-Type-Options", "nosniff") + .body(Body::empty()) + .unwrap() + }), + ) + .route( + "/v2", + get(|| async { + Response::builder() + .status(StatusCode::OK) + .header("Content-Type", "application/json; charset=utf-8") + .header("Docker-Distribution-Api-Version", "registry/2.0") + .header("X-Content-Type-Options", "nosniff") + .body(Body::empty()) + .unwrap() + }), + ) } -fn handle_request(root: &Path, request: &Request) -> Response { - let url = request.url(); - let url_segments: Vec<_> = url.split("/").collect(); - let url_seg_len = url_segments.len(); - - if url_seg_len >= 4 && url_segments[1] == "v2" { - let tag_index = url_seg_len - 1; - let object_index = url_seg_len - 2; - - let object_type = url_segments[object_index]; - - if object_type == "manifests" { - let tag_or_digest = url_segments[tag_index].to_owned(); - - let namespaced_name = url_segments[2..object_index].join("/"); - - // this route handles (GET) (/v2/**/manifests/) - if request.method() == "GET" { - return handle_get_manifest(&root, &namespaced_name, &tag_or_digest); - // this route handles (HEAD) (/v2/**/manifests/) - } else if request.method() == "HEAD" { - // a normal HEAD response has an empty body, but due to rouille not allowing for an override - // on Content-Length, we respond the same as a GET - return accept!( - request, - DOCKER_MIME_TYPE => { - handle_get_manifest(&root, &namespaced_name, &tag_or_digest) - }, - "*/*" => Response::empty_406() - ); - } - // this route handles (GET) (/v2/**/blobs/) - } else if object_type == "blobs" && request.method() == "GET" { - let digest = url_segments[tag_index].to_owned(); - return handle_get_digest(&root, &digest); - } +async fn handler(Path(path): Path) -> Response { + println!("request: {}", path); + let path = &path; + let manifest = Regex::new("(.+)/manifests/(.+)").unwrap(); + let blob = Regex::new(".+/([^/]+)").unwrap(); + + if manifest.is_match(path) { + let caps = manifest.captures(path).unwrap(); + let name = caps.get(1).unwrap().as_str().to_string(); + let reference = caps.get(2).unwrap().as_str().to_string(); + handle_get_manifest(name, reference).await + } else if blob.is_match(&path) { + let caps = blob.captures(path).unwrap(); + let tag = caps.get(1).unwrap().as_str().to_string(); + handle_get_digest(tag).await + } else { + Response::builder() + .status(StatusCode::NOT_FOUND) + .body(format!("Not Found")) + .unwrap() + .into_response() } - - Response::empty_404() } /// Handles the GET request for the manifest (only returns a OCI manifest regardless of Accept header) -fn handle_get_manifest(root: &Path, name: &String, tag: &String) -> Response { - let index = fs::read_to_string(root.join("index.json")).expect("read index.json"); +async fn handle_get_manifest(name: String, reference: String) -> Response { + let index = fs::read_to_string(PathBuf::from("/zarf-seed").join("index.json")) + .expect("index.json is read"); let json: Value = serde_json::from_str(&index).expect("unable to parse index.json"); - let mut sha_manifest = "".to_owned(); - if tag.starts_with("sha256:") { - sha_manifest = tag.strip_prefix("sha256:").unwrap().to_owned(); + let mut sha_manifest: String = "".to_owned(); + + if reference.starts_with("sha256:") { + sha_manifest = reference.strip_prefix("sha256:").unwrap().to_owned(); } else { for manifest in json["manifests"].as_array().unwrap() { let image_base_name = manifest["annotations"]["org.opencontainers.image.base.name"] .as_str() .unwrap(); - let requested_reference = name.to_owned() + ":" + tag; + let requested_reference = name.to_owned() + ":" + &reference; if requested_reference == image_base_name { sha_manifest = manifest["digest"] .as_str() @@ -180,46 +182,89 @@ fn handle_get_manifest(root: &Path, name: &String, tag: &String) -> Response { } } } - - if sha_manifest != "" { - let file = File::open(&root.join("blobs").join("sha256").join(&sha_manifest)).unwrap(); - Response::from_file(DOCKER_MIME_TYPE, file) - .with_additional_header( - "Docker-Content-Digest", - format!("sha256:{}", sha_manifest.to_owned()), - ) - .with_additional_header("Etag", format!("sha256:{}", sha_manifest)) - .with_additional_header("Docker-Distribution-Api-Version", "registry/2.0") + if sha_manifest.is_empty() { + Response::builder() + .status(StatusCode::NOT_FOUND) + .body(format!("Not Found")) + .unwrap() + .into_response() } else { - Response::empty_404() + let file_path = PathBuf::from("/zarf-seed") + .to_owned() + .join("blobs") + .join("sha256") + .join(&sha_manifest); + match tokio::fs::File::open(&file_path).await { + Ok(file) => { + let metadata = match file.metadata().await { + Ok(meta) => meta, + Err(_) => { + return Response::builder() + .status(StatusCode::INTERNAL_SERVER_ERROR) + .body("Failed to get file metadata".into()) + .unwrap() + } + }; + let stream = ReaderStream::new(file); + Response::builder() + .status(StatusCode::OK) + .header("Content-Type", OCI_MIME_TYPE) + .header("Content-Length", metadata.len()) + .header( + "Docker-Content-Digest", + format!("sha256:{}", sha_manifest.clone()), + ) + .header("Etag", format!("sha256:{}", sha_manifest)) + .header("Docker-Distribution-Api-Version", "registry/2.0") + .body(Body::from_stream(stream)) + .unwrap() + } + Err(err) => Response::builder() + .status(StatusCode::NOT_FOUND) + .body(format!("File not found: {}", err)) + .unwrap() + .into_response(), + } } } /// Handles the GET request for a blob -fn handle_get_digest(root: &Path, digest: &String) -> Response { - let blob_root = root.join("blobs").join("sha256"); - let path = blob_root.join(digest.strip_prefix("sha256:").unwrap()); - - let file = File::open(&path).unwrap(); - Response::from_file("application/octet-stream", file) - .with_additional_header("Docker-Content-Digest", digest.to_owned()) - .with_additional_header("Etag", digest.to_owned()) - .with_additional_header("Docker-Distribution-Api-Version", "registry/2.0") - .with_additional_header("Cache-Control", "max-age=31536000") +async fn handle_get_digest(tag: String) -> Response { + let blob_root = PathBuf::from("/zarf-seed").join("blobs").join("sha256"); + let path = blob_root.join(tag.strip_prefix("sha256:").unwrap()); + + match tokio::fs::File::open(&path).await { + Ok(file) => { + let stream = ReaderStream::new(file); + Response::builder() + .status(StatusCode::OK) + .header("Content-Type", "application/octet-stream") + .header("Docker-Content-Digest", tag.to_owned()) + .header("Etag", tag.to_owned()) + .header("Docker-Distribution-Api-Version", "registry/2.0") + .header("Cache-Control", "max-age=31536000") + .body(Body::from_stream(stream)) + .unwrap() + } + Err(err) => Response::builder() + .status(StatusCode::NOT_FOUND) + .body(format!("File not found: {}", err)) + .unwrap() + .into_response(), + } } -fn main() { +#[tokio::main(flavor = "current_thread")] +async fn main() { let args: Vec = env::args().collect(); - match args.len() { - 2 => { - let payload_sha = &args[1]; - unpack(payload_sha); + println!("unpacking: {}", args[1]); + let payload_sha = &args[1]; - start_seed_registry(); - } - _ => { - println!("Usage: {} ", args[0]); - } - } + unpack(payload_sha); + + let listener = tokio::net::TcpListener::bind("0.0.0.0:5000").await.unwrap(); + println!("listening on {}", listener.local_addr().unwrap()); + axum::serve(listener, start_seed_registry()).await.unwrap(); + println!("Usage: {} ", args[1]); } diff --git a/src/internal/packager/git/gitea.go b/src/internal/packager/git/gitea.go index f7bd86b00c..d243337d3c 100644 --- a/src/internal/packager/git/gitea.go +++ b/src/internal/packager/git/gitea.go @@ -6,6 +6,7 @@ package git import ( "bytes" + "context" "encoding/json" "fmt" "io" @@ -31,7 +32,7 @@ type CreateTokenResponse struct { } // CreateReadOnlyUser uses the Gitea API to create a non-admin Zarf user. -func (g *Git) CreateReadOnlyUser() error { +func (g *Git) CreateReadOnlyUser(ctx context.Context) error { message.Debugf("git.CreateReadOnlyUser()") c, err := cluster.NewCluster() @@ -44,7 +45,7 @@ func (g *Git) CreateReadOnlyUser() error { if err != nil { return err } - _, err = tunnel.Connect() + _, err = tunnel.Connect(ctx) if err != nil { return err } @@ -102,16 +103,16 @@ func (g *Git) CreateReadOnlyUser() error { } // UpdateZarfGiteaUsers updates Zarf gitea users -func (g *Git) UpdateZarfGiteaUsers(oldState *types.ZarfState) error { +func (g *Git) UpdateZarfGiteaUsers(ctx context.Context, oldState *types.ZarfState) error { //Update git read only user password - err := g.UpdateGitUser(oldState.GitServer.PushPassword, g.Server.PullUsername, g.Server.PullPassword) + err := g.UpdateGitUser(ctx, oldState.GitServer.PushPassword, g.Server.PullUsername, g.Server.PullPassword) if err != nil { return fmt.Errorf("unable to update gitea read only user password: %w", err) } // Update Git admin password - err = g.UpdateGitUser(oldState.GitServer.PushPassword, g.Server.PushUsername, g.Server.PushPassword) + err = g.UpdateGitUser(ctx, oldState.GitServer.PushPassword, g.Server.PushUsername, g.Server.PushPassword) if err != nil { return fmt.Errorf("unable to update gitea admin user password: %w", err) } @@ -119,7 +120,7 @@ func (g *Git) UpdateZarfGiteaUsers(oldState *types.ZarfState) error { } // UpdateGitUser updates Zarf git server users -func (g *Git) UpdateGitUser(oldAdminPass string, username string, userpass string) error { +func (g *Git) UpdateGitUser(ctx context.Context, oldAdminPass string, username string, userpass string) error { message.Debugf("git.UpdateGitUser()") c, err := cluster.NewCluster() @@ -131,7 +132,7 @@ func (g *Git) UpdateGitUser(oldAdminPass string, username string, userpass strin if err != nil { return err } - _, err = tunnel.Connect() + _, err = tunnel.Connect(ctx) if err != nil { return err } @@ -157,7 +158,7 @@ func (g *Git) UpdateGitUser(oldAdminPass string, username string, userpass strin } // CreatePackageRegistryToken uses the Gitea API to create a package registry token. -func (g *Git) CreatePackageRegistryToken() (CreateTokenResponse, error) { +func (g *Git) CreatePackageRegistryToken(ctx context.Context) (CreateTokenResponse, error) { message.Debugf("git.CreatePackageRegistryToken()") c, err := cluster.NewCluster() @@ -170,7 +171,7 @@ func (g *Git) CreatePackageRegistryToken() (CreateTokenResponse, error) { if err != nil { return CreateTokenResponse{}, err } - _, err = tunnel.Connect() + _, err = tunnel.Connect(ctx) if err != nil { return CreateTokenResponse{}, err } @@ -245,7 +246,7 @@ func (g *Git) CreatePackageRegistryToken() (CreateTokenResponse, error) { } // UpdateGiteaPVC updates the existing Gitea persistent volume claim and tells Gitea whether to create or not. -func UpdateGiteaPVC(shouldRollBack bool) (string, error) { +func UpdateGiteaPVC(ctx context.Context, shouldRollBack bool) (string, error) { c, err := cluster.NewCluster() if err != nil { return "false", err @@ -260,12 +261,12 @@ func UpdateGiteaPVC(shouldRollBack bool) (string, error) { annotations := map[string]string{"meta.helm.sh/release-name": "zarf-gitea", "meta.helm.sh/release-namespace": "zarf"} if shouldRollBack { - err = c.K8s.RemoveLabelsAndAnnotations(cluster.ZarfNamespaceName, pvcName, groupKind, labels, annotations) + err = c.K8s.RemoveLabelsAndAnnotations(ctx, cluster.ZarfNamespaceName, pvcName, groupKind, labels, annotations) return "false", err } if pvcName == "data-zarf-gitea-0" { - err = c.K8s.AddLabelsAndAnnotations(cluster.ZarfNamespaceName, pvcName, groupKind, labels, annotations) + err = c.K8s.AddLabelsAndAnnotations(ctx, cluster.ZarfNamespaceName, pvcName, groupKind, labels, annotations) return "true", err } diff --git a/src/internal/packager/helm/chart.go b/src/internal/packager/helm/chart.go index 72b80d65f9..9252bf40d4 100644 --- a/src/internal/packager/helm/chart.go +++ b/src/internal/packager/helm/chart.go @@ -82,7 +82,7 @@ func (h *Helm) InstallOrUpgradeChart() (types.ConnectStrings, string, error) { } if err != nil { - return fmt.Errorf("unable to complete the helm chart install/upgrade: %w", err) + return err } message.Debug(output.Info.Description) @@ -92,7 +92,6 @@ func (h *Helm) InstallOrUpgradeChart() (types.ConnectStrings, string, error) { err = helpers.Retry(tryHelm, h.retries, 5*time.Second, message.Warnf) if err != nil { - // Try to rollback any deployed releases releases, _ := histClient.Run(h.chart.ReleaseName) previouslyDeployedVersion := 0 @@ -103,25 +102,21 @@ func (h *Helm) InstallOrUpgradeChart() (types.ConnectStrings, string, error) { } } - // On total failure try to rollback (if there was a previously deployed version) or uninstall. - if previouslyDeployedVersion > 0 { - spinner.Updatef("Performing chart rollback") - - err = h.rollbackChart(h.chart.ReleaseName, previouslyDeployedVersion) - if err != nil { - return nil, "", fmt.Errorf("unable to upgrade chart after %d attempts and unable to rollback: %w", h.retries, err) - } + removeMsg := "if you need to remove the failed chart, use `zarf package remove`" - return nil, "", fmt.Errorf("unable to upgrade chart after %d attempts", h.retries) + // No prior releases means this was an initial install. + if previouslyDeployedVersion == 0 { + return nil, "", fmt.Errorf("unable to install chart after %d attempts: %s", h.retries, removeMsg) } - spinner.Updatef("Performing chart uninstall") - _, err = h.uninstallChart(h.chart.ReleaseName) + // Attempt to rollback on a failed upgrade. + spinner.Updatef("Performing chart rollback") + err = h.rollbackChart(h.chart.ReleaseName, previouslyDeployedVersion) if err != nil { - return nil, "", fmt.Errorf("unable to install chart after %d attempts and unable to uninstall: %w", h.retries, err) + return nil, "", fmt.Errorf("unable to upgrade chart after %d attempts and unable to rollback: %s", h.retries, removeMsg) } - return nil, "", fmt.Errorf("unable to install chart after %d attempts", h.retries) + return nil, "", fmt.Errorf("unable to upgrade chart after %d attempts: %s", h.retries, removeMsg) } // return any collected connect strings for zarf connect. diff --git a/src/internal/packager/helm/images.go b/src/internal/packager/helm/images.go index 14c3c65e07..dbe4d051ac 100644 --- a/src/internal/packager/helm/images.go +++ b/src/internal/packager/helm/images.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2021-Present The Zarf Authors + package helm import ( diff --git a/src/internal/packager/helm/post-render.go b/src/internal/packager/helm/post-render.go index 7cee486d99..8b99cd14f0 100644 --- a/src/internal/packager/helm/post-render.go +++ b/src/internal/packager/helm/post-render.go @@ -6,6 +6,7 @@ package helm import ( "bytes" + "context" "fmt" "os" "path/filepath" @@ -80,13 +81,14 @@ func (r *renderer) Run(renderedManifests *bytes.Buffer) (*bytes.Buffer, error) { finalManifestsOutput := bytes.NewBuffer(nil) - // Otherwise, loop over the resources, if r.cluster != nil { - if err := r.editHelmResources(resources, finalManifestsOutput); err != nil { + ctx := context.Background() + + if err := r.editHelmResources(ctx, resources, finalManifestsOutput); err != nil { return nil, err } - if err := r.adoptAndUpdateNamespaces(); err != nil { + if err := r.adoptAndUpdateNamespaces(ctx); err != nil { return nil, err } } else { @@ -99,9 +101,9 @@ func (r *renderer) Run(renderedManifests *bytes.Buffer) (*bytes.Buffer, error) { return finalManifestsOutput, nil } -func (r *renderer) adoptAndUpdateNamespaces() error { +func (r *renderer) adoptAndUpdateNamespaces(ctx context.Context) error { c := r.cluster - existingNamespaces, _ := c.GetNamespaces() + existingNamespaces, _ := c.GetNamespaces(ctx) for name, namespace := range r.namespaces { // Check to see if this namespace already exists @@ -114,7 +116,7 @@ func (r *renderer) adoptAndUpdateNamespaces() error { if !existingNamespace { // This is a new namespace, add it - if _, err := c.CreateNamespace(namespace); err != nil { + if _, err := c.CreateNamespace(ctx, namespace); err != nil { return fmt.Errorf("unable to create the missing namespace %s", name) } } else if r.cfg.DeployOpts.AdoptExistingResources { @@ -123,7 +125,7 @@ func (r *renderer) adoptAndUpdateNamespaces() error { message.Warnf("Refusing to adopt the initial namespace: %s", name) } else { // This is an existing namespace to adopt - if _, err := c.UpdateNamespace(namespace); err != nil { + if _, err := c.UpdateNamespace(ctx, namespace); err != nil { return fmt.Errorf("unable to adopt the existing namespace %s", name) } } @@ -138,10 +140,10 @@ func (r *renderer) adoptAndUpdateNamespaces() error { validRegistrySecret := c.GenerateRegistryPullCreds(name, config.ZarfImagePullSecretName, r.state.RegistryInfo) // Try to get a valid existing secret - currentRegistrySecret, _ := c.GetSecret(name, config.ZarfImagePullSecretName) + currentRegistrySecret, _ := c.GetSecret(ctx, name, config.ZarfImagePullSecretName) if currentRegistrySecret.Name != config.ZarfImagePullSecretName || !reflect.DeepEqual(currentRegistrySecret.Data, validRegistrySecret.Data) { // Create or update the zarf registry secret - if _, err := c.CreateOrUpdateSecret(validRegistrySecret); err != nil { + if _, err := c.CreateOrUpdateSecret(ctx, validRegistrySecret); err != nil { message.WarnErrf(err, "Problem creating registry secret for the %s namespace", name) } @@ -149,7 +151,7 @@ func (r *renderer) adoptAndUpdateNamespaces() error { gitServerSecret := c.GenerateGitPullCreds(name, config.ZarfGitServerSecretName, r.state.GitServer) // Create or update the zarf git server secret - if _, err := c.CreateOrUpdateSecret(gitServerSecret); err != nil { + if _, err := c.CreateOrUpdateSecret(ctx, gitServerSecret); err != nil { message.WarnErrf(err, "Problem creating git server secret for the %s namespace", name) } } @@ -157,7 +159,7 @@ func (r *renderer) adoptAndUpdateNamespaces() error { return nil } -func (r *renderer) editHelmResources(resources []releaseutil.Manifest, finalManifestsOutput *bytes.Buffer) error { +func (r *renderer) editHelmResources(ctx context.Context, resources []releaseutil.Manifest, finalManifestsOutput *bytes.Buffer) error { for _, resource := range resources { // parse to unstructured to have access to more data than just the name rawData := &unstructured.Unstructured{} @@ -223,7 +225,7 @@ func (r *renderer) editHelmResources(resources []releaseutil.Manifest, finalMani "meta.helm.sh/release-namespace": r.chart.Namespace, } - if err := r.cluster.AddLabelsAndAnnotations(deployedNamespace, rawData.GetName(), rawData.GroupVersionKind().GroupKind(), helmLabels, helmAnnotations); err != nil { + if err := r.cluster.AddLabelsAndAnnotations(ctx, deployedNamespace, rawData.GetName(), rawData.GroupVersionKind().GroupKind(), helmLabels, helmAnnotations); err != nil { // Print a debug message since this could just be because the resource doesn't exist message.Debugf("Unable to adopt resource %s: %s", rawData.GetName(), err.Error()) } diff --git a/src/internal/packager/helm/zarf.go b/src/internal/packager/helm/zarf.go index c6db1299eb..94996b4173 100644 --- a/src/internal/packager/helm/zarf.go +++ b/src/internal/packager/helm/zarf.go @@ -5,6 +5,7 @@ package helm import ( + "context" "fmt" "github.com/defenseunicorns/zarf/src/internal/packager/template" @@ -50,7 +51,7 @@ func (h *Helm) UpdateZarfRegistryValues() error { } // UpdateZarfAgentValues updates the Zarf agent deployment with the new state values -func (h *Helm) UpdateZarfAgentValues() error { +func (h *Helm) UpdateZarfAgentValues(ctx context.Context) error { spinner := message.NewProgressSpinner("Gathering information to update Zarf Agent TLS") defer spinner.Stop() @@ -60,10 +61,14 @@ func (h *Helm) UpdateZarfAgentValues() error { } // Get the current agent image from one of its pods. - pods := h.cluster.WaitForPodsAndContainers(k8s.PodLookup{ - Namespace: cluster.ZarfNamespaceName, - Selector: "app=agent-hook", - }, nil) + pods := h.cluster.WaitForPodsAndContainers( + ctx, + k8s.PodLookup{ + Namespace: cluster.ZarfNamespaceName, + Selector: "app=agent-hook", + }, + nil, + ) var currentAgentImage transform.Image if len(pods) > 0 && len(pods[0].Spec.Containers) > 0 { @@ -119,10 +124,13 @@ func (h *Helm) UpdateZarfAgentValues() error { defer spinner.Stop() // Force pods to be recreated to get the updated secret. - err = h.cluster.DeletePods(k8s.PodLookup{ - Namespace: cluster.ZarfNamespaceName, - Selector: "app=agent-hook", - }) + err = h.cluster.DeletePods( + ctx, + k8s.PodLookup{ + Namespace: cluster.ZarfNamespaceName, + Selector: "app=agent-hook", + }, + ) if err != nil { return fmt.Errorf("error recycling pods for the Zarf Agent: %w", err) } diff --git a/src/internal/packager/images/common.go b/src/internal/packager/images/common.go index 9a27edddbc..9fca5c0a0c 100644 --- a/src/internal/packager/images/common.go +++ b/src/internal/packager/images/common.go @@ -5,13 +5,35 @@ package images import ( + "net/http" + "time" + + "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/zarf/src/config" + "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/defenseunicorns/zarf/src/pkg/transform" "github.com/defenseunicorns/zarf/src/types" + "github.com/google/go-containerregistry/pkg/authn" + "github.com/google/go-containerregistry/pkg/crane" + v1 "github.com/google/go-containerregistry/pkg/v1" ) -// ImageConfig is the main struct for managing container images. -type ImageConfig struct { - ImagesPath string +// PullConfig is the configuration for pulling images. +type PullConfig struct { + DestinationDirectory string + + ImageList []transform.Image + + Arch string + + RegistryOverrides map[string]string + + CacheDirectory string +} + +// PushConfig is the configuration for pushing images. +type PushConfig struct { + SourceDirectory string ImageList []transform.Image @@ -19,9 +41,75 @@ type ImageConfig struct { NoChecksum bool - Insecure bool + Arch string + + Retries int +} - Architectures []string +// NoopOpt is a no-op option for crane. +func NoopOpt(*crane.Options) {} - RegistryOverrides map[string]string +// WithGlobalInsecureFlag returns an option for crane that configures insecure +// based upon Zarf's global --insecure flag. +func WithGlobalInsecureFlag() []crane.Option { + if config.CommonOptions.Insecure { + return []crane.Option{crane.Insecure} + } + // passing a nil option will cause panic + return []crane.Option{NoopOpt} +} + +// WithArchitecture sets the platform option for crane. +// +// This option is actually a slight mis-use of the platform option, as it is +// setting the architecture only and hard coding the OS to linux. +func WithArchitecture(arch string) crane.Option { + return crane.WithPlatform(&v1.Platform{OS: "linux", Architecture: arch}) +} + +// CommonOpts returns a set of common options for crane under Zarf. +func CommonOpts(arch string) []crane.Option { + opts := WithGlobalInsecureFlag() + opts = append(opts, WithArchitecture(arch)) + + opts = append(opts, + crane.WithUserAgent("zarf"), + crane.WithNoClobber(true), + crane.WithJobs(1), + ) + return opts +} + +// WithBasicAuth returns an option for crane that sets basic auth. +func WithBasicAuth(username, password string) crane.Option { + return crane.WithAuth(authn.FromConfig(authn.AuthConfig{ + Username: username, + Password: password, + })) +} + +// WithPullAuth returns an option for crane that sets pull auth from a given registry info. +func WithPullAuth(ri types.RegistryInfo) crane.Option { + return WithBasicAuth(ri.PullUsername, ri.PullPassword) +} + +// WithPushAuth returns an option for crane that sets push auth from a given registry info. +func WithPushAuth(ri types.RegistryInfo) crane.Option { + return WithBasicAuth(ri.PushUsername, ri.PushPassword) +} + +func createPushOpts(cfg PushConfig, pb *message.ProgressBar) []crane.Option { + opts := CommonOpts(cfg.Arch) + opts = append(opts, WithPushAuth(cfg.RegInfo)) + + transport := http.DefaultTransport.(*http.Transport).Clone() + transport.TLSClientConfig.InsecureSkipVerify = config.CommonOptions.Insecure + // TODO (@WSTARR) This is set to match the TLSHandshakeTimeout to potentially mitigate effects of https://github.com/defenseunicorns/zarf/issues/1444 + transport.ResponseHeaderTimeout = 10 * time.Second + + transportWithProgressBar := helpers.NewTransport(transport, pb) + + opts = append(opts, crane.WithTransport(transportWithProgressBar)) + + return opts } diff --git a/src/internal/packager/images/pull.go b/src/internal/packager/images/pull.go index df839e4f07..c440f8d33d 100644 --- a/src/internal/packager/images/pull.go +++ b/src/internal/packager/images/pull.go @@ -6,9 +6,17 @@ package images import ( "context" + "encoding/json" + "errors" "fmt" + "io/fs" + "maps" + "os" "path/filepath" "strings" + "sync" + "sync/atomic" + "time" "github.com/defenseunicorns/pkg/helpers" "github.com/defenseunicorns/zarf/src/config" @@ -25,31 +33,17 @@ import ( "github.com/google/go-containerregistry/pkg/v1/empty" clayout "github.com/google/go-containerregistry/pkg/v1/layout" "github.com/google/go-containerregistry/pkg/v1/partial" + "github.com/google/go-containerregistry/pkg/v1/remote" + "github.com/google/go-containerregistry/pkg/v1/types" "github.com/moby/moby/client" + ocispec "github.com/opencontainers/image-spec/specs-go/v1" + "golang.org/x/sync/errgroup" ) -// ImgInfo wraps references/information about an image -type ImgInfo struct { - RefInfo transform.Image - Img v1.Image - HasImageLayers bool -} - -// PullAll pulls all of the images in the provided tag map. -func (i *ImageConfig) PullAll() ([]ImgInfo, error) { - var ( - longer string - imageCount = len(i.ImageList) - refInfoToImage = map[transform.Image]v1.Image{} - referenceToDigest = make(map[string]string) - imgInfoList []ImgInfo - ) - - type digestInfo struct { - refInfo transform.Image - digest string - } - +// Pull pulls all of the images from the given config. +func Pull(ctx context.Context, cfg PullConfig) (map[transform.Image]v1.Image, error) { + var longer string + imageCount := len(cfg.ImageList) // Give some additional user feedback on larger image sets if imageCount > 15 { longer = "This step may take a couple of minutes to complete." @@ -57,275 +51,298 @@ func (i *ImageConfig) PullAll() ([]ImgInfo, error) { longer = "This step may take several seconds to complete." } - spinner := message.NewProgressSpinner("Loading metadata for %d images. %s", imageCount, longer) + if err := helpers.CreateDirectory(cfg.DestinationDirectory, helpers.ReadExecuteAllWriteUser); err != nil { + return nil, fmt.Errorf("failed to create image path %s: %w", cfg.DestinationDirectory, err) + } + + cranePath, err := clayout.Write(cfg.DestinationDirectory, empty.Index) + if err != nil { + return nil, err + } + + spinner := message.NewProgressSpinner("Fetching info for %d images. %s", imageCount, longer) defer spinner.Stop() logs.Warn.SetOutput(&message.DebugWriter{}) logs.Progress.SetOutput(&message.DebugWriter{}) - metadataImageConcurrency := helpers.NewConcurrencyTools[ImgInfo, error](len(i.ImageList)) + eg, ectx := errgroup.WithContext(ctx) + eg.SetLimit(10) - defer metadataImageConcurrency.Cancel() + var shaLock sync.Mutex + shas := map[string]bool{} + opts := CommonOpts(cfg.Arch) - spinner.Updatef("Fetching image metadata (0 of %d)", len(i.ImageList)) + fetched := map[transform.Image]v1.Image{} - // Spawn a goroutine for each image to load its metadata - for _, refInfo := range i.ImageList { - // Create a closure so that we can pass the src into the goroutine - refInfo := refInfo - go func() { + var counter, totalBytes atomic.Int64 - if metadataImageConcurrency.IsDone() { - return - } + for _, refInfo := range cfg.ImageList { + refInfo := refInfo + eg.Go(func() error { + idx := counter.Add(1) + spinner.Updatef("Fetching image info (%d of %d)", idx, imageCount) - actualSrc := refInfo.Reference - for k, v := range i.RegistryOverrides { + ref := refInfo.Reference + for k, v := range cfg.RegistryOverrides { if strings.HasPrefix(refInfo.Reference, k) { - actualSrc = strings.Replace(refInfo.Reference, k, v, 1) + ref = strings.Replace(refInfo.Reference, k, v, 1) } } - if metadataImageConcurrency.IsDone() { - return - } - - img, hasImageLayers, err := i.PullImage(actualSrc, spinner) - if err != nil { - metadataImageConcurrency.ErrorChan <- fmt.Errorf("failed to pull %s: %w", actualSrc, err) - return - } + var img v1.Image + var desc *remote.Descriptor - if metadataImageConcurrency.IsDone() { - return + // load from local fs if it's a tarball + if strings.HasSuffix(ref, ".tar") || strings.HasSuffix(ref, ".tar.gz") || strings.HasSuffix(ref, ".tgz") { + img, err = crane.Load(ref, opts...) + if err != nil { + return fmt.Errorf("unable to load %s: %w", refInfo.Reference, err) + } + } else { + reference, err := name.ParseReference(ref) + if err != nil { + return fmt.Errorf("failed to parse reference: %w", err) + } + desc, err = crane.Get(ref, opts...) + if err != nil { + if strings.Contains(err.Error(), "unexpected status code 429 Too Many Requests") { + return fmt.Errorf("rate limited by registry: %w", err) + } + + message.Warnf("Falling back to local 'docker', failed to find the manifest on a remote: %s", err.Error()) + + // Attempt to connect to the local docker daemon. + cli, err := client.NewClientWithOpts(client.FromEnv) + if err != nil { + return fmt.Errorf("docker not available: %w", err) + } + cli.NegotiateAPIVersion(ectx) + + // Inspect the image to get the size. + rawImg, _, err := cli.ImageInspectWithRaw(ectx, ref) + if err != nil { + return err + } + + // Warn the user if the image is large. + if rawImg.Size > 750*1000*1000 { + message.Warnf("%s is %s and may take a very long time to load via docker. "+ + "See https://docs.zarf.dev/faq for suggestions on how to improve large local image loading operations.", + ref, utils.ByteFormat(float64(rawImg.Size), 2)) + } + + // Use unbuffered opener to avoid OOM Kill issues https://github.com/defenseunicorns/zarf/issues/1214. + // This will also take forever to load large images. + img, err = daemon.Image(reference, daemon.WithUnbufferedOpener()) + if err != nil { + return fmt.Errorf("failed to load from docker daemon: %w", err) + } + } else { + img, err = crane.Pull(ref, opts...) + if err != nil { + return fmt.Errorf("unable to pull image %s: %w", refInfo.Reference, err) + } + } } - metadataImageConcurrency.ProgressChan <- ImgInfo{RefInfo: refInfo, Img: img, HasImageLayers: hasImageLayers} - }() - } + if refInfo.Digest != "" && desc != nil && types.MediaType(desc.MediaType).IsIndex() { + message.Warn("Zarf does not currently support direct consumption of OCI image indexes or Docker manifest lists") - onMetadataProgress := func(finishedImage ImgInfo, iteration int) { - spinner.Updatef("Fetching image metadata (%d of %d): %s", iteration+1, len(i.ImageList), finishedImage.RefInfo.Reference) - refInfoToImage[finishedImage.RefInfo] = finishedImage.Img - imgInfoList = append(imgInfoList, finishedImage) - } - - onMetadataError := func(err error) error { - return err - } + var idx v1.IndexManifest + if err := json.Unmarshal(desc.Manifest, &idx); err != nil { + return fmt.Errorf("unable to unmarshal index manifest: %w", err) + } + lines := []string{"The following images are available in the index:"} + name := refInfo.Name + if refInfo.Tag != "" { + name += ":" + refInfo.Tag + } + for _, desc := range idx.Manifests { + lines = append(lines, fmt.Sprintf("\n(%s) %s@%s", desc.Platform, name, desc.Digest)) + } + message.Warn(strings.Join(lines, "\n")) + return fmt.Errorf("%s resolved to an index, please select a specific platform to use", refInfo.Reference) + } - if err := metadataImageConcurrency.WaitWithProgress(onMetadataProgress, onMetadataError); err != nil { - return nil, err - } + img = cache.Image(img, cache.NewFilesystemCache(cfg.CacheDirectory)) - // Create the ImagePath directory - if err := helpers.CreateDirectory(i.ImagesPath, helpers.ReadExecuteAllWriteUser); err != nil { - return nil, fmt.Errorf("failed to create image path %s: %w", i.ImagesPath, err) - } + manifest, err := img.Manifest() + if err != nil { + return fmt.Errorf("unable to get manifest for %s: %w", refInfo.Reference, err) + } + totalBytes.Add(manifest.Config.Size) - totalBytes := int64(0) - processedLayers := make(map[string]v1.Layer) - for refInfo, img := range refInfoToImage { - // Get the byte size for this image - layers, err := img.Layers() - if err != nil { - return nil, fmt.Errorf("unable to get layers for image %s: %w", refInfo.Reference, err) - } - for _, layer := range layers { - layerDigest, err := layer.Digest() + layers, err := img.Layers() if err != nil { - return nil, fmt.Errorf("unable to get digest for image layer: %w", err) + return fmt.Errorf("unable to get layers for %s: %w", refInfo.Reference, err) } - // Only calculate this layer size if we haven't already looked at it - if _, ok := processedLayers[layerDigest.Hex]; !ok { - size, err := layer.Size() + shaLock.Lock() + defer shaLock.Unlock() + for _, layer := range layers { + digest, err := layer.Digest() if err != nil { - return nil, fmt.Errorf("unable to get size of layer: %w", err) + return fmt.Errorf("unable to get digest for image layer: %w", err) + } + + if _, ok := shas[digest.Hex]; !ok { + shas[digest.Hex] = true + size, err := layer.Size() + if err != nil { + return fmt.Errorf("unable to get size for image layer: %w", err) + } + totalBytes.Add(size) } - totalBytes += size - processedLayers[layerDigest.Hex] = layer } - } - } - spinner.Updatef("Preparing image sources and cache for image pulling") + if img == nil { + return fmt.Errorf("failed to fetch image %s", refInfo.Reference) + } - // Create special sauce crane Path object - // If it already exists use it - cranePath, err := clayout.FromPath(i.ImagesPath) - // Use crane pattern for creating OCI layout if it doesn't exist - if err != nil { - // If it doesn't exist create it - cranePath, err = clayout.Write(i.ImagesPath, empty.Index) - if err != nil { - return nil, err - } + fetched[refInfo] = img + + return nil + }) } - for refInfo, img := range refInfoToImage { - imgDigest, err := img.Digest() - if err != nil { - return nil, fmt.Errorf("unable to get digest for image %s: %w", refInfo.Reference, err) - } - referenceToDigest[refInfo.Reference] = imgDigest.String() + if err := eg.Wait(); err != nil { + return nil, err } - spinner.Success() + spinner.Successf("Fetched info for %d images", imageCount) - // Create a thread to update a progress bar as we save the image files to disk doneSaving := make(chan error) updateText := fmt.Sprintf("Pulling %d images", imageCount) - go utils.RenderProgressBarForLocalDirWrite(i.ImagesPath, totalBytes, doneSaving, updateText, updateText) - - imageSavingConcurrency := helpers.NewConcurrencyTools[digestInfo, error](len(refInfoToImage)) - - defer imageSavingConcurrency.Cancel() - - // Spawn a goroutine for each image to write it's config and manifest to disk using crane - for refInfo, img := range refInfoToImage { - // Create a closure so that we can pass the refInfo and img into the goroutine - refInfo, img := refInfo, img - go func() { - if err := cranePath.WriteImage(img); err != nil { - // Check if the cache has been invalidated, and warn the user if so - if strings.HasPrefix(err.Error(), "error writing layer: expected blob size") { - message.Warnf("Potential image cache corruption: %s - try clearing cache with \"zarf tools clear-cache\"", err.Error()) - } - imageSavingConcurrency.ErrorChan <- fmt.Errorf("error when trying to save the img (%s): %w", refInfo.Reference, err) - return - } - - if imageSavingConcurrency.IsDone() { - return - } - - // Get the image digest so we can set an annotation in the image.json later - imgDigest, err := img.Digest() - if err != nil { - imageSavingConcurrency.ErrorChan <- err - return - } - - if imageSavingConcurrency.IsDone() { - return - } - - imageSavingConcurrency.ProgressChan <- digestInfo{digest: imgDigest.String(), refInfo: refInfo} - }() - } + go utils.RenderProgressBarForLocalDirWrite(cfg.DestinationDirectory, totalBytes.Load(), doneSaving, updateText, updateText) - onImageSavingProgress := func(finishedImage digestInfo, _ int) { - referenceToDigest[finishedImage.refInfo.Reference] = finishedImage.digest - } + toPull := maps.Clone(fetched) - onImageSavingError := func(err error) error { - // Send a signal to the progress bar that we're done and wait for the thread to finish - doneSaving <- err - <-doneSaving - message.WarnErr(err, "Failed to write image config or manifest, trying again up to 3 times...") + sc := func() error { + saved, err := SaveConcurrent(ctx, cranePath, toPull) + for k := range saved { + delete(toPull, k) + } return err } - if err := imageSavingConcurrency.WaitWithProgress(onImageSavingProgress, onImageSavingError); err != nil { - return nil, err - } - - // for every image sequentially append OCI descriptor - for refInfo, img := range refInfoToImage { - desc, err := partial.Descriptor(img) - if err != nil { - return nil, err + ss := func() error { + saved, err := SaveSequential(ctx, cranePath, toPull) + for k := range saved { + delete(toPull, k) } + return err + } - if err := cranePath.AppendDescriptor(*desc); err != nil { - return nil, err - } + if err := helpers.Retry(sc, 2, 5*time.Second, message.Warnf); err != nil { + message.Warnf("Failed to save images in parallel, falling back to sequential save: %s", err.Error()) - imgDigest, err := img.Digest() - if err != nil { + if err := helpers.Retry(ss, 2, 5*time.Second, message.Warnf); err != nil { return nil, err } - - referenceToDigest[refInfo.Reference] = imgDigest.String() - } - - if err := utils.AddImageNameAnnotation(i.ImagesPath, referenceToDigest); err != nil { - return nil, fmt.Errorf("unable to format OCI layout: %w", err) } // Send a signal to the progress bar that we're done and wait for the thread to finish doneSaving <- nil <-doneSaving - return imgInfoList, nil + return fetched, nil } -// PullImage returns a v1.Image either by loading a local tarball or pulling from the wider internet. -func (i *ImageConfig) PullImage(src string, spinner *message.Spinner) (img v1.Image, hasImageLayers bool, err error) { - cacheImage := false - // Load image tarballs from the local filesystem. - if strings.HasSuffix(src, ".tar") || strings.HasSuffix(src, ".tar.gz") || strings.HasSuffix(src, ".tgz") { - spinner.Updatef("Reading image tarball: %s", src) - img, err = crane.Load(src, config.GetCraneOptions(true, i.Architectures...)...) - if err != nil { - return nil, false, err +// CleanupInProgressLayers removes incomplete layers from the cache. +func CleanupInProgressLayers(ctx context.Context, img v1.Image) error { + layers, err := img.Layers() + if err != nil { + return err + } + eg, _ := errgroup.WithContext(ctx) + for _, layer := range layers { + layer := layer + eg.Go(func() error { + digest, err := layer.Digest() + if err != nil { + return err + } + size, err := layer.Size() + if err != nil { + return err + } + cacheDir := filepath.Join(config.GetAbsCachePath(), layout.ImagesDir) + location := filepath.Join(cacheDir, digest.String()) + info, err := os.Stat(location) + if errors.Is(err, fs.ErrNotExist) { + return nil + } + if err != nil { + return err + } + if info.Size() != size { + if err := os.Remove(location); err != nil { + return fmt.Errorf("failed to remove incomplete layer %s: %w", digest.Hex, err) + } + } + return nil + }) + } + return eg.Wait() +} + +// SaveSequential saves images sequentially. +func SaveSequential(ctx context.Context, cl clayout.Path, m map[transform.Image]v1.Image) (map[transform.Image]v1.Image, error) { + saved := map[transform.Image]v1.Image{} + for info, img := range m { + annotations := map[string]string{ + ocispec.AnnotationBaseImageName: info.Reference, } - } else if _, err := crane.Manifest(src, config.GetCraneOptions(i.Insecure, i.Architectures...)...); err != nil { - // If crane is unable to pull the image, try to load it from the local docker daemon. - message.Notef("Falling back to local 'docker' images, failed to find the manifest on a remote: %s", err.Error()) - - // Parse the image reference to get the image name. - reference, err := name.ParseReference(src) - if err != nil { - return nil, false, fmt.Errorf("failed to parse image reference: %w", err) + if err := cl.AppendImage(img, clayout.WithAnnotations(annotations)); err != nil { + if err := CleanupInProgressLayers(ctx, img); err != nil { + message.WarnErr(err, "failed to clean up in-progress layers, please run `zarf tools clear-cache`") + } + return saved, err } + saved[info] = img + } + return saved, nil +} - // Attempt to connect to the local docker daemon. - ctx := context.TODO() - cli, err := client.NewClientWithOpts(client.FromEnv) - if err != nil { - return nil, false, fmt.Errorf("docker not available: %w", err) - } - cli.NegotiateAPIVersion(ctx) +// SaveConcurrent saves images in a concurrent, bounded manner. +func SaveConcurrent(ctx context.Context, cl clayout.Path, m map[transform.Image]v1.Image) (map[transform.Image]v1.Image, error) { + saved := map[transform.Image]v1.Image{} - // Inspect the image to get the size. - rawImg, _, err := cli.ImageInspectWithRaw(ctx, src) - if err != nil { - return nil, false, fmt.Errorf("failed to inspect image via docker: %w", err) - } + var mu sync.Mutex - // Warn the user if the image is large. - if rawImg.Size > 750*1000*1000 { - message.Warnf("%s is %s and may take a very long time to load via docker. "+ - "See https://docs.zarf.dev/docs/faq for suggestions on how to improve large local image loading operations.", - src, utils.ByteFormat(float64(rawImg.Size), 2)) - } + eg, ectx := errgroup.WithContext(ctx) + eg.SetLimit(10) - // Use unbuffered opener to avoid OOM Kill issues https://github.com/defenseunicorns/zarf/issues/1214. - // This will also take for ever to load large images. - if img, err = daemon.Image(reference, daemon.WithUnbufferedOpener()); err != nil { - return nil, false, fmt.Errorf("failed to load image from docker daemon: %w", err) - } - } else { - // Manifest was found, so use crane to pull the image. - if img, err = crane.Pull(src, config.GetCraneOptions(i.Insecure, i.Architectures...)...); err != nil { - return nil, false, fmt.Errorf("failed to pull image: %w", err) - } - cacheImage = true - } + for info, img := range m { + info, img := info, img + eg.Go(func() error { + desc, err := partial.Descriptor(img) + if err != nil { + return err + } - hasImageLayers, err = utils.HasImageLayers(img) - if err != nil { - return nil, false, fmt.Errorf("failed to check image layer mediatype: %w", err) - } + if err := cl.WriteImage(img); err != nil { + if err := CleanupInProgressLayers(ectx, img); err != nil { + message.WarnErr(err, "failed to clean up in-progress layers, please run `zarf tools clear-cache`") + } + return err + } - if hasImageLayers && cacheImage { - spinner.Updatef("Preparing image %s", src) - imageCachePath := filepath.Join(config.GetAbsCachePath(), layout.ImagesDir) - img = cache.Image(img, cache.NewFilesystemCache(imageCachePath)) - } + mu.Lock() + defer mu.Unlock() + annotations := map[string]string{ + ocispec.AnnotationBaseImageName: info.Reference, + } + desc.Annotations = annotations + if err := cl.AppendDescriptor(*desc); err != nil { + return err + } - return img, hasImageLayers, nil + saved[info] = img + return nil + }) + } + return saved, eg.Wait() } diff --git a/src/internal/packager/images/push.go b/src/internal/packager/images/push.go index 900b925651..e9a3645335 100644 --- a/src/internal/packager/images/push.go +++ b/src/internal/packager/images/push.go @@ -5,12 +5,11 @@ package images import ( + "context" "fmt" - "net/http" "time" "github.com/defenseunicorns/pkg/helpers" - "github.com/defenseunicorns/zarf/src/config" "github.com/defenseunicorns/zarf/src/pkg/cluster" "github.com/defenseunicorns/zarf/src/pkg/k8s" "github.com/defenseunicorns/zarf/src/pkg/message" @@ -21,23 +20,20 @@ import ( v1 "github.com/google/go-containerregistry/pkg/v1" ) -// PushToZarfRegistry pushes a provided image into the configured Zarf registry -// This function will optionally shorten the image name while appending a checksum of the original image name. -func (i *ImageConfig) PushToZarfRegistry() error { - message.Debug("images.PushToZarfRegistry()") - +// Push pushes images to a registry. +func Push(ctx context.Context, cfg PushConfig) error { logs.Warn.SetOutput(&message.DebugWriter{}) logs.Progress.SetOutput(&message.DebugWriter{}) - refInfoToImage := map[transform.Image]v1.Image{} + toPush := map[transform.Image]v1.Image{} var totalSize int64 // Build an image list from the references - for _, refInfo := range i.ImageList { - img, err := utils.LoadOCIImage(i.ImagesPath, refInfo) + for _, refInfo := range cfg.ImageList { + img, err := utils.LoadOCIImage(cfg.SourceDirectory, refInfo) if err != nil { return err } - refInfoToImage[refInfo] = img + toPush[refInfo] = img imgSize, err := calcImgSize(img) if err != nil { return err @@ -46,85 +42,95 @@ func (i *ImageConfig) PushToZarfRegistry() error { } // 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 !i.NoChecksum { + if !cfg.NoChecksum { totalSize = totalSize * 2 } - httpTransport := http.DefaultTransport.(*http.Transport).Clone() - httpTransport.TLSClientConfig.InsecureSkipVerify = i.Insecure - // TODO (@WSTARR) This is set to match the TLSHandshakeTimeout to potentially mitigate effects of https://github.com/defenseunicorns/zarf/issues/1444 - httpTransport.ResponseHeaderTimeout = 10 * time.Second - progressBar := message.NewProgressBar(totalSize, fmt.Sprintf("Pushing %d images to the zarf registry", len(i.ImageList))) - defer progressBar.Stop() - craneTransport := helpers.NewTransport(httpTransport, progressBar) - - pushOptions := config.GetCraneOptions(i.Insecure, i.Architectures...) - pushOptions = append(pushOptions, config.GetCraneAuthOption(i.RegInfo.PushUsername, i.RegInfo.PushPassword)) - pushOptions = append(pushOptions, crane.WithTransport(craneTransport)) - var ( err error tunnel *k8s.Tunnel - registryURL string + registryURL = cfg.RegInfo.Address ) - registryURL = i.RegInfo.Address + progress := message.NewProgressBar(totalSize, fmt.Sprintf("Pushing %d images", len(toPush))) + defer progress.Stop() - c, _ := cluster.NewCluster() - if c != nil { - registryURL, tunnel, err = c.ConnectToZarfRegistryEndpoint(i.RegInfo) - if err != nil { - return err + if err := helpers.Retry(func() error { + c, _ := cluster.NewCluster() + if c != nil { + registryURL, tunnel, err = c.ConnectToZarfRegistryEndpoint(ctx, cfg.RegInfo) + if err != nil { + return err + } + if tunnel != nil { + defer tunnel.Close() + } } - } - if tunnel != nil { - defer tunnel.Close() - } + progress = message.NewProgressBar(totalSize, fmt.Sprintf("Pushing %d images", len(toPush))) + pushOptions := createPushOpts(cfg, progress) - pushImage := func(img v1.Image, name string) error { - if tunnel != nil { - return tunnel.Wrap(func() error { return crane.Push(img, name, pushOptions...) }) - } + 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...) - } + return crane.Push(img, name, pushOptions...) + } - for refInfo, img := range refInfoToImage { - refTruncated := helpers.Truncate(refInfo.Reference, 55, true) - progressBar.UpdateTitle(fmt.Sprintf("Pushing %s", refTruncated)) + pushed := []transform.Image{} + defer func() { + for _, refInfo := range pushed { + delete(toPush, refInfo) + } + }() + for refInfo, img := range toPush { + refTruncated := helpers.Truncate(refInfo.Reference, 55, true) + progress.UpdateTitle(fmt.Sprintf("Pushing %s", refTruncated)) - // If this is not a no checksum image push it for use with the Zarf agent - if !i.NoChecksum { - offlineNameCRC, err := transform.ImageTransformHost(registryURL, refInfo.Reference) + size, err := calcImgSize(img) if err != nil { return err } - message.Debugf("crane.Push() %s:%s -> %s)", i.ImagesPath, refInfo.Reference, offlineNameCRC) + // 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) + if err != nil { + return err + } + + message.Debugf("push %s -> %s)", refInfo.Reference, offlineNameCRC) + + if err = pushImage(img, offlineNameCRC); err != nil { + return err + } - err = pushImage(img, offlineNameCRC) + totalSize -= size + } + + // To allow for other non-zarf workloads to easily see the images upload a non-checksum version + // (this may result in collisions but this is acceptable for this use case) + offlineName, err := transform.ImageTransformHostWithoutChecksum(registryURL, refInfo.Reference) if err != nil { return err } - } - // To allow for other non-zarf workloads to easily see the images upload a non-checksum version - // (this may result in collisions but this is acceptable for this use case) - offlineName, err := transform.ImageTransformHostWithoutChecksum(registryURL, refInfo.Reference) - if err != nil { - return err - } + message.Debugf("push %s -> %s)", refInfo.Reference, offlineName) - message.Debugf("crane.Push() %s:%s -> %s)", i.ImagesPath, refInfo.Reference, offlineName) + if err = pushImage(img, offlineName); err != nil { + return err + } - err = pushImage(img, offlineName) - if err != nil { - return err + pushed = append(pushed, refInfo) + totalSize -= size } + return nil + }, cfg.Retries, 5*time.Second, message.Warnf); err != nil { + return err } - progressBar.Successf("Pushed %d images to the zarf registry", len(i.ImageList)) + progress.Successf("Pushed %d images", len(cfg.ImageList)) return nil } diff --git a/src/internal/packager/validate/validate.go b/src/internal/packager/validate/validate.go deleted file mode 100644 index 48452929a1..0000000000 --- a/src/internal/packager/validate/validate.go +++ /dev/null @@ -1,369 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// SPDX-FileCopyrightText: 2021-Present The Zarf Authors - -// Package validate provides Zarf package validation functions. -package validate - -import ( - "fmt" - "path/filepath" - "regexp" - "slices" - - "github.com/defenseunicorns/pkg/helpers" - "github.com/defenseunicorns/zarf/src/config" - "github.com/defenseunicorns/zarf/src/config/lang" - "github.com/defenseunicorns/zarf/src/pkg/variables" - "github.com/defenseunicorns/zarf/src/types" -) - -var ( - // IsLowercaseNumberHyphenNoStartHyphen is a regex for lowercase, numbers and hyphens that cannot start with a hyphen. - // https://regex101.com/r/FLdG9G/2 - IsLowercaseNumberHyphenNoStartHyphen = regexp.MustCompile(`^[a-z0-9][a-z0-9\-]*$`).MatchString - // IsUppercaseNumberUnderscore is a regex for uppercase, numbers and underscores. - // https://regex101.com/r/tfsEuZ/1 - IsUppercaseNumberUnderscore = regexp.MustCompile(`^[A-Z0-9_]+$`).MatchString - // Define allowed OS, an empty string means it is allowed on all operating systems - // same as enums on ZarfComponentOnlyTarget - supportedOS = []string{"linux", "darwin", "windows", ""} -) - -// SupportedOS returns the supported operating systems. -// -// The supported operating systems are: linux, darwin, windows. -// -// An empty string signifies no OS restrictions. -func SupportedOS() []string { - return supportedOS -} - -// Run performs config validations. -func Run(pkg types.ZarfPackage) error { - if pkg.Kind == types.ZarfInitConfig && pkg.Metadata.YOLO { - return fmt.Errorf(lang.PkgValidateErrInitNoYOLO) - } - - if err := validatePackageName(pkg.Metadata.Name); err != nil { - return fmt.Errorf(lang.PkgValidateErrName, err) - } - - for _, variable := range pkg.Variables { - if err := validatePackageVariable(variable); err != nil { - return fmt.Errorf(lang.PkgValidateErrVariable, err) - } - } - - for _, constant := range pkg.Constants { - if err := validatePackageConstant(constant); err != nil { - return fmt.Errorf(lang.PkgValidateErrConstant, err) - } - } - - uniqueComponentNames := make(map[string]bool) - groupDefault := make(map[string]string) - groupedComponents := make(map[string][]string) - - for _, component := range pkg.Components { - // ensure component name is unique - if _, ok := uniqueComponentNames[component.Name]; ok { - return fmt.Errorf(lang.PkgValidateErrComponentNameNotUnique, component.Name) - } - uniqueComponentNames[component.Name] = true - - if err := validateComponent(pkg, component); err != nil { - return fmt.Errorf(lang.PkgValidateErrComponent, component.Name, err) - } - - // ensure groups don't have multiple defaults or only one component - if component.DeprecatedGroup != "" { - if component.Default { - if _, ok := groupDefault[component.DeprecatedGroup]; ok { - return fmt.Errorf(lang.PkgValidateErrGroupMultipleDefaults, component.DeprecatedGroup, groupDefault[component.DeprecatedGroup], component.Name) - } - groupDefault[component.DeprecatedGroup] = component.Name - } - groupedComponents[component.DeprecatedGroup] = append(groupedComponents[component.DeprecatedGroup], component.Name) - } - } - - for groupKey, componentNames := range groupedComponents { - if len(componentNames) == 1 { - return fmt.Errorf(lang.PkgValidateErrGroupOneComponent, groupKey, componentNames[0]) - } - } - - return nil -} - -// ImportDefinition validates the component trying to be imported. -func ImportDefinition(component *types.ZarfComponent) error { - path := component.Import.Path - url := component.Import.URL - - // ensure path or url is provided - if path == "" && url == "" { - return fmt.Errorf(lang.PkgValidateErrImportDefinition, component.Name, "neither a path nor a URL was provided") - } - - // ensure path and url are not both provided - if path != "" && url != "" { - return fmt.Errorf(lang.PkgValidateErrImportDefinition, component.Name, "both a path and a URL were provided") - } - - // validation for path - if url == "" && path != "" { - // ensure path is not an absolute path - if filepath.IsAbs(path) { - return fmt.Errorf(lang.PkgValidateErrImportDefinition, component.Name, "path cannot be an absolute path") - } - } - - // validation for url - if url != "" && path == "" { - ok := helpers.IsOCIURL(url) - if !ok { - return fmt.Errorf(lang.PkgValidateErrImportDefinition, component.Name, "URL is not a valid OCI URL") - } - } - - return nil -} - -func oneIfNotEmpty(testString string) int { - if testString == "" { - return 0 - } - - return 1 -} - -func validateComponent(pkg types.ZarfPackage, component types.ZarfComponent) error { - if !IsLowercaseNumberHyphenNoStartHyphen(component.Name) { - return fmt.Errorf(lang.PkgValidateErrComponentName, component.Name) - } - - if !slices.Contains(supportedOS, component.Only.LocalOS) { - return fmt.Errorf(lang.PkgValidateErrComponentLocalOS, component.Name, component.Only.LocalOS, supportedOS) - } - - if component.Required != nil && *component.Required { - if component.Default { - return fmt.Errorf(lang.PkgValidateErrComponentReqDefault, component.Name) - } - if component.DeprecatedGroup != "" { - return fmt.Errorf(lang.PkgValidateErrComponentReqGrouped, component.Name) - } - } - - uniqueChartNames := make(map[string]bool) - for _, chart := range component.Charts { - // ensure chart name is unique - if _, ok := uniqueChartNames[chart.Name]; ok { - return fmt.Errorf(lang.PkgValidateErrChartNameNotUnique, chart.Name) - } - uniqueChartNames[chart.Name] = true - - if err := validateChart(chart); err != nil { - return fmt.Errorf(lang.PkgValidateErrChart, err) - } - } - - uniqueManifestNames := make(map[string]bool) - for _, manifest := range component.Manifests { - // ensure manifest name is unique - if _, ok := uniqueManifestNames[manifest.Name]; ok { - return fmt.Errorf(lang.PkgValidateErrManifestNameNotUnique, manifest.Name) - } - uniqueManifestNames[manifest.Name] = true - - if err := validateManifest(manifest); err != nil { - return fmt.Errorf(lang.PkgValidateErrManifest, err) - } - } - - if pkg.Metadata.YOLO { - if err := validateYOLO(component); err != nil { - return fmt.Errorf(lang.PkgValidateErrComponentYOLO, component.Name, err) - } - } - - if containsVariables, err := validateActionset(component.Actions.OnCreate); err != nil { - return fmt.Errorf(lang.PkgValidateErrAction, err) - } else if containsVariables { - return fmt.Errorf(lang.PkgValidateErrActionVariables, component.Name) - } - - if _, err := validateActionset(component.Actions.OnDeploy); err != nil { - return fmt.Errorf(lang.PkgValidateErrAction, err) - } - - if containsVariables, err := validateActionset(component.Actions.OnRemove); err != nil { - return fmt.Errorf(lang.PkgValidateErrAction, err) - } else if containsVariables { - return fmt.Errorf(lang.PkgValidateErrActionVariables, component.Name) - } - - return nil -} - -func validateActionset(actions types.ZarfComponentActionSet) (bool, error) { - containsVariables := false - - validate := func(actions []types.ZarfComponentAction) error { - for _, action := range actions { - if cv, err := validateAction(action); err != nil { - return err - } else if cv { - containsVariables = true - } - } - - return nil - } - - if err := validate(actions.Before); err != nil { - return containsVariables, err - } - if err := validate(actions.After); err != nil { - return containsVariables, err - } - if err := validate(actions.OnSuccess); err != nil { - return containsVariables, err - } - if err := validate(actions.OnFailure); err != nil { - return containsVariables, err - } - - return containsVariables, nil -} - -func validateAction(action types.ZarfComponentAction) (bool, error) { - containsVariables := false - - // Validate SetVariable - for _, variable := range action.SetVariables { - if !IsUppercaseNumberUnderscore(variable.Name) { - return containsVariables, fmt.Errorf(lang.PkgValidateMustBeUppercase, variable.Name) - } - containsVariables = true - } - - if action.Wait != nil { - // Validate only cmd or wait, not both - if action.Cmd != "" { - return containsVariables, fmt.Errorf(lang.PkgValidateErrActionCmdWait, action.Cmd) - } - - // Validate only cluster or network, not both - if action.Wait.Cluster != nil && action.Wait.Network != nil { - return containsVariables, fmt.Errorf(lang.PkgValidateErrActionClusterNetwork) - } - - // Validate at least one of cluster or network - if action.Wait.Cluster == nil && action.Wait.Network == nil { - return containsVariables, fmt.Errorf(lang.PkgValidateErrActionClusterNetwork) - } - } - - return containsVariables, nil -} - -func validateYOLO(component types.ZarfComponent) error { - if len(component.Images) > 0 { - return fmt.Errorf(lang.PkgValidateErrYOLONoOCI) - } - - if len(component.Repos) > 0 { - return fmt.Errorf(lang.PkgValidateErrYOLONoGit) - } - - if component.Only.Cluster.Architecture != "" { - return fmt.Errorf(lang.PkgValidateErrYOLONoArch) - } - - if len(component.Only.Cluster.Distros) > 0 { - return fmt.Errorf(lang.PkgValidateErrYOLONoDistro) - } - - return nil -} - -func validatePackageName(subject string) error { - if !IsLowercaseNumberHyphenNoStartHyphen(subject) { - return fmt.Errorf(lang.PkgValidateErrPkgName, subject) - } - - return nil -} - -func validatePackageVariable(subject variables.InteractiveVariable) error { - // ensure the variable name is only capitals and underscores - if !IsUppercaseNumberUnderscore(subject.Name) { - return fmt.Errorf(lang.PkgValidateMustBeUppercase, subject.Name) - } - - return nil -} - -func validatePackageConstant(subject variables.Constant) error { - // ensure the constant name is only capitals and underscores - if !IsUppercaseNumberUnderscore(subject.Name) { - return fmt.Errorf(lang.PkgValidateErrPkgConstantName, subject.Name) - } - - if !regexp.MustCompile(subject.Pattern).MatchString(subject.Value) { - return fmt.Errorf(lang.PkgValidateErrPkgConstantPattern, subject.Name, subject.Pattern) - } - - return nil -} - -func validateChart(chart types.ZarfChart) error { - // Don't allow empty names - if chart.Name == "" { - return fmt.Errorf(lang.PkgValidateErrChartNameMissing, chart.Name) - } - - // Helm max release name - if len(chart.Name) > config.ZarfMaxChartNameLength { - return fmt.Errorf(lang.PkgValidateErrChartName, chart.Name, config.ZarfMaxChartNameLength) - } - - // Must have a namespace - if chart.Namespace == "" { - return fmt.Errorf(lang.PkgValidateErrChartNamespaceMissing, chart.Name) - } - - // Must have a url or localPath (and not both) - count := oneIfNotEmpty(chart.URL) + oneIfNotEmpty(chart.LocalPath) - if count != 1 { - return fmt.Errorf(lang.PkgValidateErrChartURLOrPath, chart.Name) - } - - // Must have a version - if chart.Version == "" { - return fmt.Errorf(lang.PkgValidateErrChartVersion, chart.Name) - } - - return nil -} - -func validateManifest(manifest types.ZarfManifest) error { - // Don't allow empty names - if manifest.Name == "" { - return fmt.Errorf(lang.PkgValidateErrManifestNameMissing, manifest.Name) - } - - // Helm max release name - if len(manifest.Name) > config.ZarfMaxChartNameLength { - return fmt.Errorf(lang.PkgValidateErrManifestNameLength, manifest.Name, config.ZarfMaxChartNameLength) - } - - // Require files in manifest - if len(manifest.Files) < 1 && len(manifest.Kustomizations) < 1 { - return fmt.Errorf(lang.PkgValidateErrManifestFileOrKustomize, manifest.Name) - } - - return nil -} diff --git a/src/pkg/cluster/common.go b/src/pkg/cluster/common.go index a84a06fee3..8b2153999b 100644 --- a/src/pkg/cluster/common.go +++ b/src/pkg/cluster/common.go @@ -5,6 +5,7 @@ package cluster import ( + "context" "time" "github.com/defenseunicorns/zarf/src/config" @@ -27,19 +28,9 @@ var labels = k8s.Labels{ config.ZarfManagedByLabel: "zarf", } -// NewClusterOrDie creates a new Cluster instance and waits up to 30 seconds for the cluster to be ready or throws a fatal error. -func NewClusterOrDie() *Cluster { - c, err := NewClusterWithWait(DefaultTimeout) - if err != nil { - message.Fatalf(err, "Failed to connect to cluster") - } - - return c -} - // NewClusterWithWait creates a new Cluster instance and waits for the given timeout for the cluster to be ready. -func NewClusterWithWait(timeout time.Duration) (*Cluster, error) { - spinner := message.NewProgressSpinner("Waiting for cluster connection (%s timeout)", timeout.String()) +func NewClusterWithWait(ctx context.Context) (*Cluster, error) { + spinner := message.NewProgressSpinner("Waiting for cluster connection") defer spinner.Stop() c := &Cluster{} @@ -50,7 +41,7 @@ func NewClusterWithWait(timeout time.Duration) (*Cluster, error) { return nil, err } - err = c.WaitForHealthyCluster(timeout) + err = c.WaitForHealthyCluster(ctx) if err != nil { return nil, err } diff --git a/src/pkg/cluster/data.go b/src/pkg/cluster/data.go index d2724c0d1f..0c5e526536 100644 --- a/src/pkg/cluster/data.go +++ b/src/pkg/cluster/data.go @@ -5,6 +5,7 @@ package cluster import ( + "context" "fmt" "os" "path/filepath" @@ -25,7 +26,7 @@ import ( // HandleDataInjection waits for the target pod(s) to come up and inject the data into them // todo: this currently requires kubectl but we should have enough k8s work to make this native now. -func (c *Cluster) HandleDataInjection(wg *sync.WaitGroup, data types.ZarfDataInjection, componentPath *layout.ComponentPaths, dataIdx int) { +func (c *Cluster) HandleDataInjection(ctx context.Context, wg *sync.WaitGroup, data types.ZarfDataInjection, componentPath *layout.ComponentPaths, dataIdx int) { defer wg.Done() injectionCompletionMarker := filepath.Join(componentPath.DataInjections, config.GetDataInjectionMarker()) @@ -74,7 +75,7 @@ iterator: } // Wait until the pod we are injecting data into becomes available - pods := c.WaitForPodsAndContainers(target, podFilterByInitContainer) + pods := c.WaitForPodsAndContainers(ctx, target, podFilterByInitContainer) if len(pods) < 1 { continue } @@ -139,7 +140,7 @@ iterator: // Block one final time to make sure at least one pod has come up and injected the data // Using only the pod as the final selector because we don't know what the container name will be // Still using the init container filter to make sure we have the right running pod - _ = c.WaitForPodsAndContainers(podOnlyTarget, podFilterByInitContainer) + _ = c.WaitForPodsAndContainers(ctx, podOnlyTarget, podFilterByInitContainer) // Cleanup now to reduce disk pressure _ = os.RemoveAll(source) diff --git a/src/pkg/cluster/injector.go b/src/pkg/cluster/injector.go index 716a260b96..264a39648e 100644 --- a/src/pkg/cluster/injector.go +++ b/src/pkg/cluster/injector.go @@ -5,6 +5,7 @@ package cluster import ( + "context" "fmt" "net/http" "os" @@ -41,7 +42,7 @@ var ( type imageNodeMap map[string][]string // StartInjectionMadness initializes a Zarf injection into the cluster. -func (c *Cluster) StartInjectionMadness(tmpDir string, imagesDir string, injectorSeedSrcs []string) { +func (c *Cluster) StartInjectionMadness(ctx context.Context, tmpDir string, imagesDir string, injectorSeedSrcs []string) { spinner := message.NewProgressSpinner("Attempting to bootstrap the seed image into the cluster") defer spinner.Stop() @@ -64,19 +65,20 @@ func (c *Cluster) StartInjectionMadness(tmpDir string, imagesDir string, injecto var seedImages []transform.Image // Get all the images from the cluster - timeout := 5 * time.Minute - spinner.Updatef("Getting the list of existing cluster images (%s timeout)", timeout.String()) - if images, err = c.getImagesAndNodesForInjection(timeout); err != nil { + spinner.Updatef("Getting the list of existing cluster images") + findImagesCtx, cancel := context.WithTimeout(ctx, 5*time.Minute) + defer cancel() + if images, err = c.getImagesAndNodesForInjection(findImagesCtx); err != nil { spinner.Fatalf(err, "Unable to generate a list of candidate images to perform the registry injection") } spinner.Updatef("Creating the injector configmap") - if err = c.createInjectorConfigmap(tmp.InjectionBinary); err != nil { + if err = c.createInjectorConfigmap(ctx, tmp.InjectionBinary); err != nil { spinner.Fatalf(err, "Unable to create the injector configmap") } spinner.Updatef("Creating the injector service") - if service, err := c.createService(); err != nil { + if service, err := c.createService(ctx); err != nil { spinner.Fatalf(err, "Unable to create the injector service") } else { config.ZarfSeedPort = fmt.Sprintf("%d", service.Spec.Ports[0].NodePort) @@ -88,7 +90,7 @@ func (c *Cluster) StartInjectionMadness(tmpDir string, imagesDir string, injecto } spinner.Updatef("Loading the seed registry configmaps") - if payloadConfigmaps, sha256sum, err = c.createPayloadConfigmaps(tmp.SeedImagesDir, tmp.InjectorPayloadTarGz, spinner); err != nil { + if payloadConfigmaps, sha256sum, err = c.createPayloadConfigmaps(ctx, tmp.SeedImagesDir, tmp.InjectorPayloadTarGz, spinner); err != nil { spinner.Fatalf(err, "Unable to generate the injector payload configmaps") } @@ -105,26 +107,30 @@ func (c *Cluster) StartInjectionMadness(tmpDir string, imagesDir string, injecto spinner.Updatef("Attempting to bootstrap with the %s/%s", node, image) // Make sure the pod is not there first - _ = c.DeletePod(ZarfNamespaceName, "injector") + err = c.DeletePod(ctx, ZarfNamespaceName, "injector") + if err != nil { + message.Debug("could not delete pod injector:", err) + } // Update the podspec image path and use the first node found + pod, err := c.buildInjectionPod(node[0], image, payloadConfigmaps, sha256sum) if err != nil { // Just debug log the output because failures just result in trying the next image - message.Debug(err) + message.Debug("error making injection pod:", err) continue } // Create the pod in the cluster - pod, err = c.CreatePod(pod) + pod, err = c.CreatePod(ctx, pod) if err != nil { // Just debug log the output because failures just result in trying the next image - message.Debug(pod, err) + message.Debug("error creating pod in cluster:", pod, err) continue } // if no error, try and wait for a seed image to be present, return if successful - if c.injectorIsReady(seedImages, spinner) { + if c.injectorIsReady(ctx, seedImages, spinner) { spinner.Success() return } @@ -137,20 +143,20 @@ func (c *Cluster) StartInjectionMadness(tmpDir string, imagesDir string, injecto } // StopInjectionMadness handles cleanup once the seed registry is up. -func (c *Cluster) StopInjectionMadness() error { +func (c *Cluster) StopInjectionMadness(ctx context.Context) error { // Try to kill the injector pod now - if err := c.DeletePod(ZarfNamespaceName, "injector"); err != nil { + if err := c.DeletePod(ctx, ZarfNamespaceName, "injector"); err != nil { return err } // Remove the configmaps labelMatch := map[string]string{"zarf-injector": "payload"} - if err := c.DeleteConfigMapsByLabel(ZarfNamespaceName, labelMatch); err != nil { + if err := c.DeleteConfigMapsByLabel(ctx, ZarfNamespaceName, labelMatch); err != nil { return err } // Remove the injector service - return c.DeleteService(ZarfNamespaceName, "zarf-injector") + return c.DeleteService(ctx, ZarfNamespaceName, "zarf-injector") } func (c *Cluster) loadSeedImages(imagesDir, seedImagesDir string, injectorSeedSrcs []string, spinner *message.Spinner) ([]transform.Image, error) { @@ -162,34 +168,36 @@ func (c *Cluster) loadSeedImages(imagesDir, seedImagesDir string, injectorSeedSr spinner.Updatef("Loading the seed image '%s' from the package", src) ref, err := transform.ParseImageRef(src) if err != nil { - return seedImages, fmt.Errorf("failed to create ref for image %s: %w", src, err) + return nil, fmt.Errorf("failed to create ref for image %s: %w", src, err) } img, err := utils.LoadOCIImage(imagesDir, ref) if err != nil { - return seedImages, err + return nil, err } - crane.SaveOCI(img, seedImagesDir) + if err := crane.SaveOCI(img, seedImagesDir); err != nil { + return nil, err + } seedImages = append(seedImages, ref) // Get the image digest so we can set an annotation in the image.json later imgDigest, err := img.Digest() if err != nil { - return seedImages, err + return nil, err } // This is done _without_ the domain (different from pull.go) since the injector only handles local images localReferenceToDigest[ref.Path+ref.TagOrDigest] = imgDigest.String() } if err := utils.AddImageNameAnnotation(seedImagesDir, localReferenceToDigest); err != nil { - return seedImages, fmt.Errorf("unable to format OCI layout: %w", err) + return nil, fmt.Errorf("unable to format OCI layout: %w", err) } return seedImages, nil } -func (c *Cluster) createPayloadConfigmaps(seedImagesDir, tarPath string, spinner *message.Spinner) ([]string, string, error) { +func (c *Cluster) createPayloadConfigmaps(ctx context.Context, seedImagesDir, tarPath string, spinner *message.Spinner) ([]string, string, error) { var configMaps []string // Chunk size has to accommodate base64 encoding & etcd 1MB limit @@ -226,7 +234,7 @@ func (c *Cluster) createPayloadConfigmaps(seedImagesDir, tarPath string, spinner spinner.Updatef("Adding archive binary configmap %d of %d to the cluster", idx+1, chunkCount) // Attempt to create the configmap in the cluster - if _, err = c.ReplaceConfigmap(ZarfNamespaceName, fileName, configData); err != nil { + if _, err = c.ReplaceConfigmap(ctx, ZarfNamespaceName, fileName, configData); err != nil { return configMaps, "", err } @@ -241,13 +249,13 @@ func (c *Cluster) createPayloadConfigmaps(seedImagesDir, tarPath string, spinner } // Test for pod readiness and seed image presence. -func (c *Cluster) injectorIsReady(seedImages []transform.Image, spinner *message.Spinner) bool { +func (c *Cluster) injectorIsReady(ctx context.Context, seedImages []transform.Image, spinner *message.Spinner) bool { tunnel, err := c.NewTunnel(ZarfNamespaceName, k8s.SvcResource, ZarfInjectorName, "", 0, ZarfInjectorPort) if err != nil { return false } - _, err = tunnel.Connect() + _, err = tunnel.Connect(ctx) if err != nil { return false } @@ -261,6 +269,7 @@ func (c *Cluster) injectorIsReady(seedImages []transform.Image, spinner *message var resp *http.Response var err error err = tunnel.Wrap(func() error { + message.Debug("getting seed registry %v", seedRegistry) resp, err = http.Get(seedRegistry) return err }) @@ -276,7 +285,7 @@ func (c *Cluster) injectorIsReady(seedImages []transform.Image, spinner *message return true } -func (c *Cluster) createInjectorConfigmap(binaryPath string) error { +func (c *Cluster) createInjectorConfigmap(ctx context.Context, binaryPath string) error { var err error configData := make(map[string][]byte) @@ -286,17 +295,17 @@ func (c *Cluster) createInjectorConfigmap(binaryPath string) error { } // Try to delete configmap silently - _ = c.DeleteConfigmap(ZarfNamespaceName, "rust-binary") + _ = c.DeleteConfigmap(ctx, ZarfNamespaceName, "rust-binary") // Attempt to create the configmap in the cluster - if _, err = c.CreateConfigmap(ZarfNamespaceName, "rust-binary", configData); err != nil { + if _, err = c.CreateConfigmap(ctx, ZarfNamespaceName, "rust-binary", configData); err != nil { return err } return nil } -func (c *Cluster) createService() (*corev1.Service, error) { +func (c *Cluster) createService(ctx context.Context) (*corev1.Service, error) { service := c.GenerateService(ZarfNamespaceName, "zarf-injector") service.Spec.Type = corev1.ServiceTypeNodePort @@ -308,9 +317,9 @@ func (c *Cluster) createService() (*corev1.Service, error) { } // Attempt to purse the service silently - _ = c.DeleteService(ZarfNamespaceName, "zarf-injector") + _ = c.DeleteService(ctx, ZarfNamespaceName, "zarf-injector") - return c.CreateService(service) + return c.CreateService(ctx, service) } // buildInjectionPod return a pod for injection with the appropriate containers to perform the injection. @@ -432,66 +441,61 @@ func (c *Cluster) buildInjectionPod(node, image string, payloadConfigmaps []stri return pod, nil } -// GetImagesFromAvailableNodes checks for images on schedulable nodes within a cluster and returns -func (c *Cluster) getImagesAndNodesForInjection(timeoutDuration time.Duration) (imageNodeMap, error) { - timeout := time.After(timeoutDuration) +// getImagesAndNodesForInjection checks for images on schedulable nodes within a cluster. +func (c *Cluster) getImagesAndNodesForInjection(ctx context.Context) (imageNodeMap, error) { result := make(imageNodeMap) + timer := time.NewTimer(0) + defer timer.Stop() + for { select { - - // On timeout abort - case <-timeout: - return nil, fmt.Errorf("get image list timed-out") - - // After delay, try running - default: - pods, err := c.GetPods(corev1.NamespaceAll, metav1.ListOptions{ + case <-ctx.Done(): + return nil, fmt.Errorf("get image list timed-out: %w", ctx.Err()) + case <-timer.C: + pods, err := c.GetPods(ctx, corev1.NamespaceAll, metav1.ListOptions{ FieldSelector: fmt.Sprintf("status.phase=%s", corev1.PodRunning), }) if err != nil { return nil, fmt.Errorf("unable to get the list of %q pods in the cluster: %w", corev1.PodRunning, err) } - findImages: for _, pod := range pods.Items { nodeName := pod.Spec.NodeName - nodeDetails, err := c.GetNode(nodeName) + nodeDetails, err := c.GetNode(ctx, nodeName) if err != nil { return nil, fmt.Errorf("unable to get the node %q: %w", nodeName, err) } if nodeDetails.Status.Allocatable.Cpu().Cmp(injectorRequestedCPU) < 0 || nodeDetails.Status.Allocatable.Memory().Cmp(injectorRequestedMemory) < 0 { - continue findImages + continue } for _, taint := range nodeDetails.Spec.Taints { if taint.Effect == corev1.TaintEffectNoSchedule || taint.Effect == corev1.TaintEffectNoExecute { - continue findImages + continue } } for _, container := range pod.Spec.InitContainers { result[container.Image] = append(result[container.Image], nodeName) } - for _, container := range pod.Spec.Containers { result[container.Image] = append(result[container.Image], nodeName) } - for _, container := range pod.Spec.EphemeralContainers { result[container.Image] = append(result[container.Image], nodeName) } } - } - if len(result) < 1 { - c.Log("no images found: %w") - time.Sleep(2 * time.Second) - } else { - return result, nil + if len(result) > 0 { + return result, nil + } + + c.Log("No images found on any node. Retrying...") + timer.Reset(2 * time.Second) } } } diff --git a/src/pkg/cluster/namespace.go b/src/pkg/cluster/namespace.go index 82b4277901..a7209936b3 100644 --- a/src/pkg/cluster/namespace.go +++ b/src/pkg/cluster/namespace.go @@ -11,9 +11,9 @@ import ( ) // DeleteZarfNamespace deletes the Zarf namespace from the connected cluster. -func (c *Cluster) DeleteZarfNamespace() { +func (c *Cluster) DeleteZarfNamespace(ctx context.Context) error { spinner := message.NewProgressSpinner("Deleting the zarf namespace from this cluster") defer spinner.Stop() - c.DeleteNamespace(context.TODO(), ZarfNamespaceName) + return c.DeleteNamespace(ctx, ZarfNamespaceName) } diff --git a/src/pkg/cluster/secrets.go b/src/pkg/cluster/secrets.go index 2cdb1a20d1..17183d5e62 100644 --- a/src/pkg/cluster/secrets.go +++ b/src/pkg/cluster/secrets.go @@ -5,6 +5,7 @@ package cluster import ( + "context" "encoding/base64" "encoding/json" "reflect" @@ -73,16 +74,16 @@ func (c *Cluster) GenerateGitPullCreds(namespace, name string, gitServerInfo typ } // UpdateZarfManagedImageSecrets updates all Zarf-managed image secrets in all namespaces based on state -func (c *Cluster) UpdateZarfManagedImageSecrets(state *types.ZarfState) { +func (c *Cluster) UpdateZarfManagedImageSecrets(ctx context.Context, state *types.ZarfState) { spinner := message.NewProgressSpinner("Updating existing Zarf-managed image secrets") defer spinner.Stop() - if namespaces, err := c.GetNamespaces(); err != nil { + if namespaces, err := c.GetNamespaces(ctx); err != nil { spinner.Errorf(err, "Unable to get k8s namespaces") } else { // Update all image pull secrets for _, namespace := range namespaces.Items { - currentRegistrySecret, err := c.GetSecret(namespace.Name, config.ZarfImagePullSecretName) + currentRegistrySecret, err := c.GetSecret(ctx, namespace.Name, config.ZarfImagePullSecretName) if err != nil { continue } @@ -96,7 +97,7 @@ func (c *Cluster) UpdateZarfManagedImageSecrets(state *types.ZarfState) { newRegistrySecret := c.GenerateRegistryPullCreds(namespace.Name, config.ZarfImagePullSecretName, state.RegistryInfo) if !reflect.DeepEqual(currentRegistrySecret.Data, newRegistrySecret.Data) { // Create or update the zarf registry secret - if _, err := c.CreateOrUpdateSecret(newRegistrySecret); err != nil { + if _, err := c.CreateOrUpdateSecret(ctx, newRegistrySecret); err != nil { message.WarnErrf(err, "Problem creating registry secret for the %s namespace", namespace.Name) } } @@ -107,16 +108,16 @@ func (c *Cluster) UpdateZarfManagedImageSecrets(state *types.ZarfState) { } // UpdateZarfManagedGitSecrets updates all Zarf-managed git secrets in all namespaces based on state -func (c *Cluster) UpdateZarfManagedGitSecrets(state *types.ZarfState) { +func (c *Cluster) UpdateZarfManagedGitSecrets(ctx context.Context, state *types.ZarfState) { spinner := message.NewProgressSpinner("Updating existing Zarf-managed git secrets") defer spinner.Stop() - if namespaces, err := c.GetNamespaces(); err != nil { + if namespaces, err := c.GetNamespaces(ctx); err != nil { spinner.Errorf(err, "Unable to get k8s namespaces") } else { // Update all git pull secrets for _, namespace := range namespaces.Items { - currentGitSecret, err := c.GetSecret(namespace.Name, config.ZarfGitServerSecretName) + currentGitSecret, err := c.GetSecret(ctx, namespace.Name, config.ZarfGitServerSecretName) if err != nil { continue } @@ -130,7 +131,7 @@ func (c *Cluster) UpdateZarfManagedGitSecrets(state *types.ZarfState) { newGitSecret := c.GenerateGitPullCreds(namespace.Name, config.ZarfGitServerSecretName, state.GitServer) if !reflect.DeepEqual(currentGitSecret.StringData, newGitSecret.StringData) { // Create or update the zarf git secret - if _, err := c.CreateOrUpdateSecret(newGitSecret); err != nil { + if _, err := c.CreateOrUpdateSecret(ctx, newGitSecret); err != nil { message.WarnErrf(err, "Problem creating git server secret for the %s namespace", namespace.Name) } } diff --git a/src/pkg/cluster/state.go b/src/pkg/cluster/state.go index ebac18fbd3..a2585c4361 100644 --- a/src/pkg/cluster/state.go +++ b/src/pkg/cluster/state.go @@ -5,6 +5,7 @@ package cluster import ( + "context" "encoding/json" "fmt" "time" @@ -34,7 +35,7 @@ const ( ) // InitZarfState initializes the Zarf state with the given temporary directory and init configs. -func (c *Cluster) InitZarfState(initOptions types.ZarfInitOptions) error { +func (c *Cluster) InitZarfState(ctx context.Context, initOptions types.ZarfInitOptions) error { var ( distro string err error @@ -46,7 +47,7 @@ func (c *Cluster) InitZarfState(initOptions types.ZarfInitOptions) error { // Attempt to load an existing state prior to init. // NOTE: We are ignoring the error here because we don't really expect a state to exist yet. spinner.Updatef("Checking cluster for existing Zarf deployment") - state, _ := c.LoadZarfState() + state, _ := c.LoadZarfState(ctx) // If state is nil, this is a new cluster. if state == nil { @@ -59,7 +60,7 @@ func (c *Cluster) InitZarfState(initOptions types.ZarfInitOptions) error { state.ZarfAppliance = true } else { // Otherwise, trying to detect the K8s distro type. - distro, err = c.DetectDistro() + distro, err = c.DetectDistro(ctx) if err != nil { // This is a basic failure right now but likely could be polished to provide user guidance to resolve. return fmt.Errorf("unable to connect to the cluster to verify the distro: %w", err) @@ -79,7 +80,7 @@ func (c *Cluster) InitZarfState(initOptions types.ZarfInitOptions) error { // Setup zarf agent PKI state.AgentTLS = pki.GeneratePKI(config.ZarfAgentHost) - namespaces, err := c.GetNamespaces() + namespaces, err := c.GetNamespaces(ctx) if err != nil { return fmt.Errorf("unable to get the Kubernetes namespaces: %w", err) } @@ -93,7 +94,7 @@ func (c *Cluster) InitZarfState(initOptions types.ZarfInitOptions) error { // This label will tell the Zarf Agent to ignore this namespace. namespace.Labels[agentLabel] = "ignore" namespaceCopy := namespace - if _, err = c.UpdateNamespace(&namespaceCopy); err != nil { + if _, err = c.UpdateNamespace(ctx, &namespaceCopy); err != nil { // This is not a hard failure, but we should log it. message.WarnErrf(err, "Unable to mark the namespace %s as ignored by Zarf Agent", namespace.Name) } @@ -102,14 +103,16 @@ func (c *Cluster) InitZarfState(initOptions types.ZarfInitOptions) error { // Try to create the zarf namespace. spinner.Updatef("Creating the Zarf namespace") zarfNamespace := c.NewZarfManagedNamespace(ZarfNamespaceName) - if _, err := c.CreateNamespace(zarfNamespace); err != nil { + if _, err := c.CreateNamespace(ctx, zarfNamespace); err != nil { return fmt.Errorf("unable to create the zarf namespace: %w", err) } // Wait up to 2 minutes for the default service account to be created. // Some clusters seem to take a while to create this, see https://github.com/kubernetes/kubernetes/issues/66689. // The default SA is required for pods to start properly. - if _, err := c.WaitForServiceAccount(ZarfNamespaceName, "default", 2*time.Minute); err != nil { + saCtx, cancel := context.WithTimeout(ctx, 2*time.Minute) + defer cancel() + if _, err := c.WaitForServiceAccount(saCtx, ZarfNamespaceName, "default"); err != nil { return fmt.Errorf("unable get default Zarf service account: %w", err) } @@ -158,7 +161,7 @@ func (c *Cluster) InitZarfState(initOptions types.ZarfInitOptions) error { spinner.Success() // Save the state back to K8s - if err := c.SaveZarfState(state); err != nil { + if err := c.SaveZarfState(ctx, state); err != nil { return fmt.Errorf("unable to save the Zarf state: %w", err) } @@ -166,9 +169,9 @@ func (c *Cluster) InitZarfState(initOptions types.ZarfInitOptions) error { } // LoadZarfState returns the current zarf/zarf-state secret data or an empty ZarfState. -func (c *Cluster) LoadZarfState() (state *types.ZarfState, err error) { +func (c *Cluster) LoadZarfState(ctx context.Context) (state *types.ZarfState, err error) { // Set up the API connection - secret, err := c.GetSecret(ZarfNamespaceName, ZarfStateSecretName) + secret, err := c.GetSecret(ctx, ZarfNamespaceName, ZarfStateSecretName) if err != nil { return nil, fmt.Errorf("%w. %s", err, message.ColorWrap("Did you remember to zarf init?", color.Bold)) } @@ -218,7 +221,7 @@ func (c *Cluster) debugPrintZarfState(state *types.ZarfState) { } // SaveZarfState takes a given state and persists it to the Zarf/zarf-state secret. -func (c *Cluster) SaveZarfState(state *types.ZarfState) error { +func (c *Cluster) SaveZarfState(ctx context.Context, state *types.ZarfState) error { c.debugPrintZarfState(state) // Convert the data back to JSON. @@ -249,7 +252,7 @@ func (c *Cluster) SaveZarfState(state *types.ZarfState) error { } // Attempt to create or update the secret and return. - if _, err := c.CreateOrUpdateSecret(secret); err != nil { + if _, err := c.CreateOrUpdateSecret(ctx, secret); err != nil { return fmt.Errorf("unable to create the zarf state secret") } diff --git a/src/pkg/cluster/tunnel.go b/src/pkg/cluster/tunnel.go index d52c606ecc..c960fb1e8e 100644 --- a/src/pkg/cluster/tunnel.go +++ b/src/pkg/cluster/tunnel.go @@ -5,6 +5,7 @@ package cluster import ( + "context" "fmt" "strings" @@ -54,8 +55,8 @@ func NewTunnelInfo(namespace, resourceType, resourceName, urlSuffix string, loca } // PrintConnectTable will print a table of all Zarf connect matches found in the cluster. -func (c *Cluster) PrintConnectTable() error { - list, err := c.GetServicesByLabelExists(v1.NamespaceAll, config.ZarfConnectLabelName) +func (c *Cluster) PrintConnectTable(ctx context.Context) error { + list, err := c.GetServicesByLabelExists(ctx, v1.NamespaceAll, config.ZarfConnectLabelName) if err != nil { return err } @@ -78,7 +79,7 @@ func (c *Cluster) PrintConnectTable() error { } // Connect will establish a tunnel to the specified target. -func (c *Cluster) Connect(target string) (*k8s.Tunnel, error) { +func (c *Cluster) Connect(ctx context.Context, target string) (*k8s.Tunnel, error) { var err error zt := TunnelInfo{ namespace: ZarfNamespaceName, @@ -107,7 +108,7 @@ func (c *Cluster) Connect(target string) (*k8s.Tunnel, error) { default: if target != "" { - if zt, err = c.checkForZarfConnectLabel(target); err != nil { + if zt, err = c.checkForZarfConnectLabel(ctx, target); err != nil { return nil, fmt.Errorf("problem looking for a zarf connect label in the cluster: %s", err.Error()) } } @@ -120,17 +121,17 @@ func (c *Cluster) Connect(target string) (*k8s.Tunnel, error) { } } - return c.ConnectTunnelInfo(zt) + return c.ConnectTunnelInfo(ctx, zt) } // ConnectTunnelInfo connects to the cluster with the provided TunnelInfo -func (c *Cluster) ConnectTunnelInfo(zt TunnelInfo) (*k8s.Tunnel, error) { +func (c *Cluster) ConnectTunnelInfo(ctx context.Context, zt TunnelInfo) (*k8s.Tunnel, error) { tunnel, err := c.NewTunnel(zt.namespace, zt.resourceType, zt.resourceName, zt.urlSuffix, zt.localPort, zt.remotePort) if err != nil { return nil, err } - _, err = tunnel.Connect() + _, err = tunnel.Connect(ctx) if err != nil { return nil, err } @@ -139,7 +140,7 @@ func (c *Cluster) ConnectTunnelInfo(zt TunnelInfo) (*k8s.Tunnel, error) { } // ConnectToZarfRegistryEndpoint determines if a registry endpoint is in cluster, and if so opens a tunnel to connect to it -func (c *Cluster) ConnectToZarfRegistryEndpoint(registryInfo types.RegistryInfo) (string, *k8s.Tunnel, error) { +func (c *Cluster) ConnectToZarfRegistryEndpoint(ctx context.Context, registryInfo types.RegistryInfo) (string, *k8s.Tunnel, error) { registryEndpoint := registryInfo.Address var err error @@ -150,7 +151,7 @@ func (c *Cluster) ConnectToZarfRegistryEndpoint(registryInfo types.RegistryInfo) return "", tunnel, err } } else { - svcInfo, err := c.ServiceInfoFromNodePortURL(registryInfo.Address) + svcInfo, err := c.ServiceInfoFromNodePortURL(ctx, registryInfo.Address) // If this is a service (no error getting svcInfo), create a port-forward tunnel to that resource if err == nil { @@ -161,7 +162,7 @@ func (c *Cluster) ConnectToZarfRegistryEndpoint(registryInfo types.RegistryInfo) } if tunnel != nil { - _, err = tunnel.Connect() + _, err = tunnel.Connect(ctx) if err != nil { return "", tunnel, err } @@ -172,13 +173,13 @@ func (c *Cluster) ConnectToZarfRegistryEndpoint(registryInfo types.RegistryInfo) } // checkForZarfConnectLabel looks in the cluster for a connect name that matches the target -func (c *Cluster) checkForZarfConnectLabel(name string) (TunnelInfo, error) { +func (c *Cluster) checkForZarfConnectLabel(ctx context.Context, name string) (TunnelInfo, error) { var err error var zt TunnelInfo message.Debugf("Looking for a Zarf Connect Label in the cluster") - matches, err := c.GetServicesByLabel("", config.ZarfConnectLabelName, name) + matches, err := c.GetServicesByLabel(ctx, "", config.ZarfConnectLabelName, name) if err != nil { return zt, fmt.Errorf("unable to lookup the service: %w", err) } @@ -195,7 +196,7 @@ func (c *Cluster) checkForZarfConnectLabel(name string) (TunnelInfo, error) { zt.remotePort = svc.Spec.Ports[0].TargetPort.IntValue() // if targetPort == 0, look for Port (which is required) if zt.remotePort == 0 { - zt.remotePort = c.FindPodContainerPort(svc) + zt.remotePort = c.FindPodContainerPort(ctx, svc) } // Add the url suffix too. diff --git a/src/pkg/cluster/zarf.go b/src/pkg/cluster/zarf.go index afef2362b3..3522dde6b5 100644 --- a/src/pkg/cluster/zarf.go +++ b/src/pkg/cluster/zarf.go @@ -7,7 +7,6 @@ package cluster import ( "context" "encoding/json" - "errors" "fmt" "strings" "time" @@ -23,11 +22,11 @@ import ( // GetDeployedZarfPackages gets metadata information about packages that have been deployed to the cluster. // We determine what packages have been deployed to the cluster by looking for specific secrets in the Zarf namespace. // Returns a list of DeployedPackage structs and a list of errors. -func (c *Cluster) GetDeployedZarfPackages() ([]types.DeployedPackage, []error) { +func (c *Cluster) GetDeployedZarfPackages(ctx context.Context) ([]types.DeployedPackage, []error) { var deployedPackages = []types.DeployedPackage{} var errorList []error // Get the secrets that describe the deployed packages - secrets, err := c.GetSecretsWithLabel(ZarfNamespaceName, ZarfPackageInfoLabel) + secrets, err := c.GetSecretsWithLabel(ctx, ZarfNamespaceName, ZarfPackageInfoLabel) if err != nil { return deployedPackages, append(errorList, err) } @@ -52,9 +51,9 @@ func (c *Cluster) GetDeployedZarfPackages() ([]types.DeployedPackage, []error) { // GetDeployedPackage gets the metadata information about the package name provided (if it exists in the cluster). // We determine what packages have been deployed to the cluster by looking for specific secrets in the Zarf namespace. -func (c *Cluster) GetDeployedPackage(packageName string) (deployedPackage *types.DeployedPackage, err error) { +func (c *Cluster) GetDeployedPackage(ctx context.Context, packageName string) (deployedPackage *types.DeployedPackage, err error) { // Get the secret that describes the deployed package - secret, err := c.GetSecret(ZarfNamespaceName, config.ZarfPackagePrefix+packageName) + secret, err := c.GetSecret(ctx, ZarfNamespaceName, config.ZarfPackagePrefix+packageName) if err != nil { return deployedPackage, err } @@ -63,7 +62,7 @@ func (c *Cluster) GetDeployedPackage(packageName string) (deployedPackage *types } // StripZarfLabelsAndSecretsFromNamespaces removes metadata and secrets from existing namespaces no longer manged by Zarf. -func (c *Cluster) StripZarfLabelsAndSecretsFromNamespaces() { +func (c *Cluster) StripZarfLabelsAndSecretsFromNamespaces(ctx context.Context) { spinner := message.NewProgressSpinner("Removing zarf metadata & secrets from existing namespaces not managed by Zarf") defer spinner.Stop() @@ -72,7 +71,7 @@ func (c *Cluster) StripZarfLabelsAndSecretsFromNamespaces() { LabelSelector: config.ZarfManagedByLabel + "=zarf", } - if namespaces, err := c.GetNamespaces(); err != nil { + if namespaces, err := c.GetNamespaces(ctx); err != nil { spinner.Errorf(err, "Unable to get k8s namespaces") } else { for _, namespace := range namespaces.Items { @@ -80,7 +79,7 @@ func (c *Cluster) StripZarfLabelsAndSecretsFromNamespaces() { spinner.Updatef("Removing Zarf Agent label for namespace %s", namespace.Name) delete(namespace.Labels, agentLabel) namespaceCopy := namespace - if _, err = c.UpdateNamespace(&namespaceCopy); err != nil { + if _, err = c.UpdateNamespace(ctx, &namespaceCopy); err != nil { // This is not a hard failure, but we should log it spinner.Errorf(err, "Unable to update the namespace labels for %s", namespace.Name) } @@ -89,7 +88,7 @@ func (c *Cluster) StripZarfLabelsAndSecretsFromNamespaces() { spinner.Updatef("Removing Zarf secrets for namespace %s", namespace.Name) err := c.Clientset.CoreV1(). Secrets(namespace.Name). - DeleteCollection(context.TODO(), deleteOptions, listOptions) + DeleteCollection(ctx, deleteOptions, listOptions) if err != nil { spinner.Errorf(err, "Unable to delete secrets from namespace %s", namespace.Name) } @@ -125,9 +124,8 @@ func (c *Cluster) PackageSecretNeedsWait(deployedPackage *types.DeployedPackage, } // RecordPackageDeploymentAndWait records the deployment of a package to the cluster and waits for any webhooks to complete. -func (c *Cluster) RecordPackageDeploymentAndWait(pkg types.ZarfPackage, components []types.DeployedComponent, connectStrings types.ConnectStrings, generation int, component types.ZarfComponent, skipWebhooks bool) (deployedPackage *types.DeployedPackage, err error) { - - deployedPackage, err = c.RecordPackageDeployment(pkg, components, connectStrings, generation) +func (c *Cluster) RecordPackageDeploymentAndWait(ctx context.Context, pkg types.ZarfPackage, components []types.DeployedComponent, connectStrings types.ConnectStrings, generation int, component types.ZarfComponent, skipWebhooks bool) (deployedPackage *types.DeployedPackage, err error) { + deployedPackage, err = c.RecordPackageDeployment(ctx, pkg, components, connectStrings, generation) if err != nil { return nil, err } @@ -135,41 +133,46 @@ func (c *Cluster) RecordPackageDeploymentAndWait(pkg types.ZarfPackage, componen packageNeedsWait, waitSeconds, hookName := c.PackageSecretNeedsWait(deployedPackage, component, skipWebhooks) // If no webhooks need to complete, we can return immediately. if !packageNeedsWait { - return nil, nil + return deployedPackage, nil } - // Timebox the amount of time we wait for a webhook to complete before erroring waitDuration := types.DefaultWebhookWaitDuration if waitSeconds > 0 { waitDuration = time.Duration(waitSeconds) * time.Second } - timeout := time.After(waitDuration) - // We need to wait for this package to finish having webhooks run, create a spinner and keep checking until it's ready - spinner := message.NewProgressSpinner("Waiting for webhook '%s' to complete for component '%s'", hookName, component.Name) + waitCtx, cancel := context.WithTimeout(ctx, waitDuration) + defer cancel() + + spinner := message.NewProgressSpinner("Waiting for webhook %q to complete for component %q", hookName, component.Name) defer spinner.Stop() - for packageNeedsWait { + + timer := time.NewTimer(0) + defer timer.Stop() + + for { select { - // On timeout, abort and return an error. - case <-timeout: - return nil, errors.New("timed out waiting for package deployment to complete") - default: - // Wait for 1 second before checking the secret again - time.Sleep(1 * time.Second) - deployedPackage, err = c.GetDeployedPackage(deployedPackage.Name) + case <-waitCtx.Done(): + return nil, fmt.Errorf("error waiting for webhook %q to complete for component %q: %w", hookName, component.Name, waitCtx.Err()) + case <-timer.C: + deployedPackage, err = c.GetDeployedPackage(ctx, deployedPackage.Name) if err != nil { return nil, err } + packageNeedsWait, _, _ = c.PackageSecretNeedsWait(deployedPackage, component, skipWebhooks) + if !packageNeedsWait { + spinner.Success() + return deployedPackage, nil + } + + timer.Reset(1 * time.Second) } } - - spinner.Success() - return deployedPackage, nil } // RecordPackageDeployment saves metadata about a package that has been deployed to the cluster. -func (c *Cluster) RecordPackageDeployment(pkg types.ZarfPackage, components []types.DeployedComponent, connectStrings types.ConnectStrings, generation int) (deployedPackage *types.DeployedPackage, err error) { +func (c *Cluster) RecordPackageDeployment(ctx context.Context, pkg types.ZarfPackage, components []types.DeployedComponent, connectStrings types.ConnectStrings, generation int) (deployedPackage *types.DeployedPackage, err error) { packageName := pkg.Metadata.Name // Generate a secret that describes the package that is being deployed @@ -179,7 +182,7 @@ func (c *Cluster) RecordPackageDeployment(pkg types.ZarfPackage, components []ty // Attempt to load information about webhooks for the package var componentWebhooks map[string]map[string]types.Webhook - existingPackageSecret, err := c.GetDeployedPackage(packageName) + existingPackageSecret, err := c.GetDeployedPackage(ctx, packageName) if err != nil { message.Debugf("Unable to fetch existing secret for package '%s': %s", packageName, err.Error()) } @@ -205,7 +208,7 @@ func (c *Cluster) RecordPackageDeployment(pkg types.ZarfPackage, components []ty // Update the package secret deployedPackageSecret.Data = map[string][]byte{"data": packageData} var updatedSecret *corev1.Secret - if updatedSecret, err = c.CreateOrUpdateSecret(deployedPackageSecret); err != nil { + if updatedSecret, err = c.CreateOrUpdateSecret(ctx, deployedPackageSecret); err != nil { return nil, fmt.Errorf("failed to record package deployment in secret '%s'", deployedPackageSecret.Name) } @@ -217,8 +220,8 @@ func (c *Cluster) RecordPackageDeployment(pkg types.ZarfPackage, components []ty } // EnableRegHPAScaleDown enables the HPA scale down for the Zarf Registry. -func (c *Cluster) EnableRegHPAScaleDown() error { - hpa, err := c.GetHPA(ZarfNamespaceName, "zarf-docker-registry") +func (c *Cluster) EnableRegHPAScaleDown(ctx context.Context) error { + hpa, err := c.GetHPA(ctx, ZarfNamespaceName, "zarf-docker-registry") if err != nil { return err } @@ -228,7 +231,7 @@ func (c *Cluster) EnableRegHPAScaleDown() error { hpa.Spec.Behavior.ScaleDown.SelectPolicy = &policy // Save the HPA changes. - if _, err = c.UpdateHPA(hpa); err != nil { + if _, err = c.UpdateHPA(ctx, hpa); err != nil { return err } @@ -236,8 +239,8 @@ func (c *Cluster) EnableRegHPAScaleDown() error { } // DisableRegHPAScaleDown disables the HPA scale down for the Zarf Registry. -func (c *Cluster) DisableRegHPAScaleDown() error { - hpa, err := c.GetHPA(ZarfNamespaceName, "zarf-docker-registry") +func (c *Cluster) DisableRegHPAScaleDown(ctx context.Context) error { + hpa, err := c.GetHPA(ctx, ZarfNamespaceName, "zarf-docker-registry") if err != nil { return err } @@ -247,7 +250,7 @@ func (c *Cluster) DisableRegHPAScaleDown() error { hpa.Spec.Behavior.ScaleDown.SelectPolicy = &policy // Save the HPA changes. - if _, err = c.UpdateHPA(hpa); err != nil { + if _, err = c.UpdateHPA(ctx, hpa); err != nil { return err } @@ -255,8 +258,8 @@ func (c *Cluster) DisableRegHPAScaleDown() error { } // GetInstalledChartsForComponent returns any installed Helm Charts for the provided package component. -func (c *Cluster) GetInstalledChartsForComponent(packageName string, component types.ZarfComponent) (installedCharts []types.InstalledChart, err error) { - deployedPackage, err := c.GetDeployedPackage(packageName) +func (c *Cluster) GetInstalledChartsForComponent(ctx context.Context, packageName string, component types.ZarfComponent) (installedCharts []types.InstalledChart, err error) { + deployedPackage, err := c.GetDeployedPackage(ctx, packageName) if err != nil { return installedCharts, err } diff --git a/src/pkg/k8s/common.go b/src/pkg/k8s/common.go index e3472d8962..44027f4492 100644 --- a/src/pkg/k8s/common.go +++ b/src/pkg/k8s/common.go @@ -5,6 +5,7 @@ package k8s import ( + "context" "fmt" "time" @@ -42,35 +43,29 @@ func New(logger Log, defaultLabels Labels) (*K8s, error) { }, nil } -// NewWithWait is a convenience function that creates a new K8s client and waits for the cluster to be healthy. -func NewWithWait(logger Log, defaultLabels Labels, timeout time.Duration) (*K8s, error) { - k, err := New(logger, defaultLabels) - if err != nil { - return nil, err - } +// WaitForHealthyCluster checks for an available K8s cluster every second until timeout. +func (k *K8s) WaitForHealthyCluster(ctx context.Context) error { + var ( + err error + nodes *v1.NodeList + pods *v1.PodList + ) - return k, k.WaitForHealthyCluster(timeout) -} + const waitDuration = 1 * time.Second -// WaitForHealthyCluster checks for an available K8s cluster every second until timeout. -func (k *K8s) WaitForHealthyCluster(timeout time.Duration) error { - var err error - var nodes *v1.NodeList - var pods *v1.PodList - expired := time.After(timeout) + timer := time.NewTimer(0) + defer timer.Stop() for { select { - // on timeout abort - case <-expired: - return fmt.Errorf("timed out waiting for cluster to report healthy") - - // after delay, try running - default: + case <-ctx.Done(): + return fmt.Errorf("error waiting for cluster to report healthy: %w", ctx.Err()) + case <-timer.C: if k.RestConfig == nil || k.Clientset == nil { config, clientset, err := connect() if err != nil { k.Log("Cluster connection not available yet: %w", err) + timer.Reset(waitDuration) continue } @@ -79,31 +74,30 @@ func (k *K8s) WaitForHealthyCluster(timeout time.Duration) error { } // Make sure there is at least one running Node - nodes, err = k.GetNodes() + nodes, err = k.GetNodes(ctx) if err != nil || len(nodes.Items) < 1 { - k.Log("No nodes reporting healthy yet: %#v\n", err) + k.Log("No nodes reporting healthy yet: %v\n", err) + timer.Reset(waitDuration) continue } // Get the cluster pod list - if pods, err = k.GetAllPods(); err != nil { + if pods, err = k.GetAllPods(ctx); err != nil { k.Log("Could not get the pod list: %w", err) + timer.Reset(waitDuration) continue } // Check that at least one pod is in the 'succeeded' or 'running' state for _, pod := range pods.Items { - // If a valid pod is found, return no error if pod.Status.Phase == v1.PodSucceeded || pod.Status.Phase == v1.PodRunning { return nil } } k.Log("No pods reported 'succeeded' or 'running' state yet.") + timer.Reset(waitDuration) } - - // delay check 1 seconds - time.Sleep(1 * time.Second) } } diff --git a/src/pkg/k8s/configmap.go b/src/pkg/k8s/configmap.go index 93777f6b3f..57a72c65ae 100644 --- a/src/pkg/k8s/configmap.go +++ b/src/pkg/k8s/configmap.go @@ -14,16 +14,15 @@ import ( ) // ReplaceConfigmap deletes and recreates a configmap. -func (k *K8s) ReplaceConfigmap(namespace, name string, data map[string][]byte) (*corev1.ConfigMap, error) { - if err := k.DeleteConfigmap(namespace, name); err != nil { +func (k *K8s) ReplaceConfigmap(ctx context.Context, namespace, name string, data map[string][]byte) (*corev1.ConfigMap, error) { + if err := k.DeleteConfigmap(ctx, namespace, name); err != nil { return nil, err } - - return k.CreateConfigmap(namespace, name, data) + return k.CreateConfigmap(ctx, namespace, name, data) } // CreateConfigmap applies a configmap to the cluster. -func (k *K8s) CreateConfigmap(namespace, name string, data map[string][]byte) (*corev1.ConfigMap, error) { +func (k *K8s) CreateConfigmap(ctx context.Context, namespace, name string, data map[string][]byte) (*corev1.ConfigMap, error) { configMap := &corev1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ Name: name, @@ -34,14 +33,14 @@ func (k *K8s) CreateConfigmap(namespace, name string, data map[string][]byte) (* } createOptions := metav1.CreateOptions{} - return k.Clientset.CoreV1().ConfigMaps(namespace).Create(context.TODO(), configMap, createOptions) + return k.Clientset.CoreV1().ConfigMaps(namespace).Create(ctx, configMap, createOptions) } // DeleteConfigmap deletes a configmap by name. -func (k *K8s) DeleteConfigmap(namespace, name string) error { +func (k *K8s) DeleteConfigmap(ctx context.Context, namespace, name string) error { namespaceConfigmap := k.Clientset.CoreV1().ConfigMaps(namespace) - err := namespaceConfigmap.Delete(context.TODO(), name, metav1.DeleteOptions{}) + err := namespaceConfigmap.Delete(ctx, name, metav1.DeleteOptions{}) if err != nil && !errors.IsNotFound(err) { return fmt.Errorf("error deleting the configmap: %w", err) } @@ -50,7 +49,7 @@ func (k *K8s) DeleteConfigmap(namespace, name string) error { } // DeleteConfigMapsByLabel deletes a configmap by label(s). -func (k *K8s) DeleteConfigMapsByLabel(namespace string, labels Labels) error { +func (k *K8s) DeleteConfigMapsByLabel(ctx context.Context, namespace string, labels Labels) error { labelSelector, _ := metav1.LabelSelectorAsSelector(&metav1.LabelSelector{ MatchLabels: labels, }) @@ -59,5 +58,5 @@ func (k *K8s) DeleteConfigMapsByLabel(namespace string, labels Labels) error { LabelSelector: labelSelector.String(), } - return k.Clientset.CoreV1().ConfigMaps(namespace).DeleteCollection(context.TODO(), metaOptions, listOptions) + return k.Clientset.CoreV1().ConfigMaps(namespace).DeleteCollection(ctx, metaOptions, listOptions) } diff --git a/src/pkg/k8s/dynamic.go b/src/pkg/k8s/dynamic.go index daf87c7a1a..59f295f26b 100644 --- a/src/pkg/k8s/dynamic.go +++ b/src/pkg/k8s/dynamic.go @@ -15,17 +15,17 @@ import ( ) // AddLabelsAndAnnotations adds the provided labels and annotations to the specified K8s resource -func (k *K8s) AddLabelsAndAnnotations(resourceNamespace string, resourceName string, groupKind schema.GroupKind, labels map[string]string, annotations map[string]string) error { - return k.updateLabelsAndAnnotations(resourceNamespace, resourceName, groupKind, labels, annotations, false) +func (k *K8s) AddLabelsAndAnnotations(ctx context.Context, resourceNamespace, resourceName string, groupKind schema.GroupKind, labels, annotations map[string]string) error { + return k.updateLabelsAndAnnotations(ctx, resourceNamespace, resourceName, groupKind, labels, annotations, false) } // RemoveLabelsAndAnnotations removes the provided labels and annotations to the specified K8s resource -func (k *K8s) RemoveLabelsAndAnnotations(resourceNamespace string, resourceName string, groupKind schema.GroupKind, labels map[string]string, annotations map[string]string) error { - return k.updateLabelsAndAnnotations(resourceNamespace, resourceName, groupKind, labels, annotations, true) +func (k *K8s) RemoveLabelsAndAnnotations(ctx context.Context, resourceNamespace, resourceName string, groupKind schema.GroupKind, labels, annotations map[string]string) error { + return k.updateLabelsAndAnnotations(ctx, resourceNamespace, resourceName, groupKind, labels, annotations, true) } // updateLabelsAndAnnotations updates the provided labels and annotations to the specified K8s resource -func (k *K8s) updateLabelsAndAnnotations(resourceNamespace string, resourceName string, groupKind schema.GroupKind, labels map[string]string, annotations map[string]string, isRemove bool) error { +func (k *K8s) updateLabelsAndAnnotations(ctx context.Context, resourceNamespace, resourceName string, groupKind schema.GroupKind, labels, annotations map[string]string, isRemove bool) error { dynamicClient := dynamic.NewForConfigOrDie(k.RestConfig) discoveryClient := discovery.NewDiscoveryClientForConfigOrDie(k.RestConfig) @@ -41,7 +41,7 @@ func (k *K8s) updateLabelsAndAnnotations(resourceNamespace string, resourceName return err } - deployedResource, err := dynamicClient.Resource(mapping.Resource).Namespace(resourceNamespace).Get(context.TODO(), resourceName, metav1.GetOptions{}) + deployedResource, err := dynamicClient.Resource(mapping.Resource).Namespace(resourceNamespace).Get(ctx, resourceName, metav1.GetOptions{}) if err != nil { return err } @@ -78,6 +78,6 @@ func (k *K8s) updateLabelsAndAnnotations(resourceNamespace string, resourceName deployedResource.SetAnnotations(deployedAnnotations) - _, err = dynamicClient.Resource(mapping.Resource).Namespace(resourceNamespace).Update(context.TODO(), deployedResource, metav1.UpdateOptions{}) + _, err = dynamicClient.Resource(mapping.Resource).Namespace(resourceNamespace).Update(ctx, deployedResource, metav1.UpdateOptions{}) return err } diff --git a/src/pkg/k8s/hpa.go b/src/pkg/k8s/hpa.go index a159823f67..902f5b1d29 100644 --- a/src/pkg/k8s/hpa.go +++ b/src/pkg/k8s/hpa.go @@ -13,24 +13,24 @@ import ( ) // GetAllHPAs returns a list of horizontal pod autoscalers for all namespaces. -func (k *K8s) GetAllHPAs() (*autoscalingV2.HorizontalPodAutoscalerList, error) { - return k.GetHPAs(corev1.NamespaceAll) +func (k *K8s) GetAllHPAs(ctx context.Context) (*autoscalingV2.HorizontalPodAutoscalerList, error) { + return k.GetHPAs(ctx, corev1.NamespaceAll) } // GetHPAs returns a list of horizontal pod autoscalers in a given namespace. -func (k *K8s) GetHPAs(namespace string) (*autoscalingV2.HorizontalPodAutoscalerList, error) { +func (k *K8s) GetHPAs(ctx context.Context, namespace string) (*autoscalingV2.HorizontalPodAutoscalerList, error) { metaOptions := metav1.ListOptions{} - return k.Clientset.AutoscalingV2().HorizontalPodAutoscalers(namespace).List(context.TODO(), metaOptions) + return k.Clientset.AutoscalingV2().HorizontalPodAutoscalers(namespace).List(ctx, metaOptions) } // GetHPA returns a single horizontal pod autoscaler by namespace and name. -func (k *K8s) GetHPA(namespace, name string) (*autoscalingV2.HorizontalPodAutoscaler, error) { +func (k *K8s) GetHPA(ctx context.Context, namespace, name string) (*autoscalingV2.HorizontalPodAutoscaler, error) { metaOptions := metav1.GetOptions{} - return k.Clientset.AutoscalingV2().HorizontalPodAutoscalers(namespace).Get(context.TODO(), name, metaOptions) + return k.Clientset.AutoscalingV2().HorizontalPodAutoscalers(namespace).Get(ctx, name, metaOptions) } // UpdateHPA updates the given horizontal pod autoscaler in the cluster. -func (k *K8s) UpdateHPA(hpa *autoscalingV2.HorizontalPodAutoscaler) (*autoscalingV2.HorizontalPodAutoscaler, error) { +func (k *K8s) UpdateHPA(ctx context.Context, hpa *autoscalingV2.HorizontalPodAutoscaler) (*autoscalingV2.HorizontalPodAutoscaler, error) { metaOptions := metav1.UpdateOptions{} - return k.Clientset.AutoscalingV2().HorizontalPodAutoscalers(hpa.Namespace).Update(context.TODO(), hpa, metaOptions) + return k.Clientset.AutoscalingV2().HorizontalPodAutoscalers(hpa.Namespace).Update(ctx, hpa, metaOptions) } diff --git a/src/pkg/k8s/info.go b/src/pkg/k8s/info.go index 61effabdaa..edb655b964 100644 --- a/src/pkg/k8s/info.go +++ b/src/pkg/k8s/info.go @@ -5,6 +5,7 @@ package k8s import ( + "context" "errors" "fmt" "regexp" @@ -29,7 +30,7 @@ const ( ) // DetectDistro returns the matching distro or unknown if not found. -func (k *K8s) DetectDistro() (string, error) { +func (k *K8s) DetectDistro(ctx context.Context) (string, error) { kindNodeRegex := regexp.MustCompile(`^kind://`) k3dNodeRegex := regexp.MustCompile(`^k3s://k3d-`) eksNodeRegex := regexp.MustCompile(`^aws:///`) @@ -38,7 +39,7 @@ func (k *K8s) DetectDistro() (string, error) { rke2Regex := regexp.MustCompile(`^rancher/rancher-agent:v2`) tkgRegex := regexp.MustCompile(`^projects\.registry\.vmware\.com/tkg/tanzu_core/`) - nodes, err := k.GetNodes() + nodes, err := k.GetNodes(ctx) if err != nil { return DistroIsUnknown, errors.New("error getting cluster nodes") } @@ -99,7 +100,7 @@ func (k *K8s) DetectDistro() (string, error) { } } - namespaces, err := k.GetNamespaces() + namespaces, err := k.GetNamespaces(ctx) if err != nil { return DistroIsUnknown, errors.New("error getting namespace list") } @@ -115,8 +116,8 @@ func (k *K8s) DetectDistro() (string, error) { } // GetArchitectures returns the cluster system architectures if found. -func (k *K8s) GetArchitectures() ([]string, error) { - nodes, err := k.GetNodes() +func (k *K8s) GetArchitectures(ctx context.Context) ([]string, error) { + nodes, err := k.GetNodes(ctx) if err != nil { return nil, err } diff --git a/src/pkg/k8s/namespace.go b/src/pkg/k8s/namespace.go index 2862731e90..3a63b1ac52 100644 --- a/src/pkg/k8s/namespace.go +++ b/src/pkg/k8s/namespace.go @@ -15,26 +15,26 @@ import ( ) // GetNamespaces returns a list of namespaces in the cluster. -func (k *K8s) GetNamespaces() (*corev1.NamespaceList, error) { +func (k *K8s) GetNamespaces(ctx context.Context) (*corev1.NamespaceList, error) { metaOptions := metav1.ListOptions{} - return k.Clientset.CoreV1().Namespaces().List(context.TODO(), metaOptions) + return k.Clientset.CoreV1().Namespaces().List(ctx, metaOptions) } // UpdateNamespace updates the given namespace in the cluster. -func (k *K8s) UpdateNamespace(namespace *corev1.Namespace) (*corev1.Namespace, error) { +func (k *K8s) UpdateNamespace(ctx context.Context, namespace *corev1.Namespace) (*corev1.Namespace, error) { updateOptions := metav1.UpdateOptions{} - return k.Clientset.CoreV1().Namespaces().Update(context.TODO(), namespace, updateOptions) + return k.Clientset.CoreV1().Namespaces().Update(ctx, namespace, updateOptions) } // CreateNamespace creates the given namespace or returns it if it already exists in the cluster. -func (k *K8s) CreateNamespace(namespace *corev1.Namespace) (*corev1.Namespace, error) { +func (k *K8s) CreateNamespace(ctx context.Context, namespace *corev1.Namespace) (*corev1.Namespace, error) { metaOptions := metav1.GetOptions{} createOptions := metav1.CreateOptions{} - match, err := k.Clientset.CoreV1().Namespaces().Get(context.TODO(), namespace.Name, metaOptions) + match, err := k.Clientset.CoreV1().Namespaces().Get(ctx, namespace.Name, metaOptions) if err != nil || match.Name != namespace.Name { - return k.Clientset.CoreV1().Namespaces().Create(context.TODO(), namespace, createOptions) + return k.Clientset.CoreV1().Namespaces().Create(ctx, namespace, createOptions) } return match, err @@ -45,19 +45,25 @@ func (k *K8s) DeleteNamespace(ctx context.Context, name string) error { // Attempt to delete the namespace immediately gracePeriod := int64(0) err := k.Clientset.CoreV1().Namespaces().Delete(ctx, name, metav1.DeleteOptions{GracePeriodSeconds: &gracePeriod}) - // If an error besides "not found" is returned, return it if err != nil && !errors.IsNotFound(err) { return err } - // Indefinitely wait for the namespace to be deleted, use context.WithTimeout to limit this + timer := time.NewTimer(0) + defer timer.Stop() + for { - // Keep checking for the namespace to be deleted - _, err := k.Clientset.CoreV1().Namespaces().Get(ctx, name, metav1.GetOptions{}) - if errors.IsNotFound(err) { - return nil + select { + case <-ctx.Done(): + return ctx.Err() + case <-timer.C: + _, err := k.Clientset.CoreV1().Namespaces().Get(ctx, name, metav1.GetOptions{}) + if errors.IsNotFound(err) { + return nil + } + + timer.Reset(1 * time.Second) } - time.Sleep(1 * time.Second) } } diff --git a/src/pkg/k8s/nodes.go b/src/pkg/k8s/nodes.go index c2348e06f4..134c00b140 100644 --- a/src/pkg/k8s/nodes.go +++ b/src/pkg/k8s/nodes.go @@ -12,12 +12,12 @@ import ( ) // GetNodes returns a list of nodes from the k8s cluster. -func (k *K8s) GetNodes() (*corev1.NodeList, error) { +func (k *K8s) GetNodes(ctx context.Context) (*corev1.NodeList, error) { metaOptions := metav1.ListOptions{} - return k.Clientset.CoreV1().Nodes().List(context.TODO(), metaOptions) + return k.Clientset.CoreV1().Nodes().List(ctx, metaOptions) } // GetNode returns a node from the k8s cluster. -func (k *K8s) GetNode(nodeName string) (*corev1.Node, error) { - return k.Clientset.CoreV1().Nodes().Get(context.TODO(), nodeName, metav1.GetOptions{}) +func (k *K8s) GetNode(ctx context.Context, nodeName string) (*corev1.Node, error) { + return k.Clientset.CoreV1().Nodes().Get(ctx, nodeName, metav1.GetOptions{}) } diff --git a/src/pkg/k8s/pods.go b/src/pkg/k8s/pods.go index bf29291587..be9c72bec4 100644 --- a/src/pkg/k8s/pods.go +++ b/src/pkg/k8s/pods.go @@ -14,8 +14,6 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -const waitLimit = 30 - // GeneratePod creates a new pod without adding it to the k8s cluster. func (k *K8s) GeneratePod(name, namespace string) *corev1.Pod { pod := &corev1.Pod{ @@ -34,33 +32,42 @@ func (k *K8s) GeneratePod(name, namespace string) *corev1.Pod { } // DeletePod removes a pod from the cluster by namespace & name. -func (k *K8s) DeletePod(namespace string, name string) error { +func (k *K8s) DeletePod(ctx context.Context, namespace string, name string) error { deleteGracePeriod := int64(0) deletePolicy := metav1.DeletePropagationForeground - err := k.Clientset.CoreV1().Pods(namespace).Delete(context.TODO(), name, metav1.DeleteOptions{ + + err := k.Clientset.CoreV1().Pods(namespace).Delete(ctx, name, metav1.DeleteOptions{ GracePeriodSeconds: &deleteGracePeriod, PropagationPolicy: &deletePolicy, }) - if err != nil { return err } + timer := time.NewTimer(0) + defer timer.Stop() + for { - // Keep checking for the pod to be deleted - _, err := k.Clientset.CoreV1().Pods(namespace).Get(context.TODO(), name, metav1.GetOptions{}) - if errors.IsNotFound(err) { - return nil + select { + case <-ctx.Done(): + return ctx.Err() + case <-timer.C: + _, err := k.Clientset.CoreV1().Pods(namespace).Get(ctx, name, metav1.GetOptions{}) + if errors.IsNotFound(err) { + return nil + } + + timer.Reset(1 * time.Second) } - time.Sleep(1 * time.Second) } } // DeletePods removes a collection of pods from the cluster by pod lookup. -func (k *K8s) DeletePods(target PodLookup) error { +func (k *K8s) DeletePods(ctx context.Context, target PodLookup) error { deleteGracePeriod := int64(0) deletePolicy := metav1.DeletePropagationForeground - return k.Clientset.CoreV1().Pods(target.Namespace).DeleteCollection(context.TODO(), + return k.Clientset.CoreV1().Pods(target.Namespace).DeleteCollection( + ctx, metav1.DeleteOptions{ GracePeriodSeconds: &deleteGracePeriod, PropagationPolicy: &deletePolicy, @@ -72,111 +79,115 @@ func (k *K8s) DeletePods(target PodLookup) error { } // CreatePod inserts the given pod into the cluster. -func (k *K8s) CreatePod(pod *corev1.Pod) (*corev1.Pod, error) { +func (k *K8s) CreatePod(ctx context.Context, pod *corev1.Pod) (*corev1.Pod, error) { createOptions := metav1.CreateOptions{} - return k.Clientset.CoreV1().Pods(pod.Namespace).Create(context.TODO(), pod, createOptions) + return k.Clientset.CoreV1().Pods(pod.Namespace).Create(ctx, pod, createOptions) } // GetAllPods returns a list of pods from the cluster for all namespaces. -func (k *K8s) GetAllPods() (*corev1.PodList, error) { - return k.GetPods(corev1.NamespaceAll, metav1.ListOptions{}) +func (k *K8s) GetAllPods(ctx context.Context) (*corev1.PodList, error) { + return k.GetPods(ctx, corev1.NamespaceAll, metav1.ListOptions{}) } // GetPods returns a list of pods from the cluster by namespace. -func (k *K8s) GetPods(namespace string, listOpts metav1.ListOptions) (*corev1.PodList, error) { - return k.Clientset.CoreV1().Pods(namespace).List(context.TODO(), listOpts) +func (k *K8s) GetPods(ctx context.Context, namespace string, listOpts metav1.ListOptions) (*corev1.PodList, error) { + return k.Clientset.CoreV1().Pods(namespace).List(ctx, listOpts) } // WaitForPodsAndContainers attempts to find pods matching the given selector and optional inclusion filter // It will wait up to 90 seconds for the pods to be found and will return a list of matching pod names // If the timeout is reached, an empty list will be returned. -func (k *K8s) WaitForPodsAndContainers(target PodLookup, include PodFilter) []corev1.Pod { - for count := 0; count < waitLimit; count++ { +func (k *K8s) WaitForPodsAndContainers(ctx context.Context, target PodLookup, include PodFilter) []corev1.Pod { + waitCtx, cancel := context.WithTimeout(ctx, 90*time.Second) + defer cancel() - pods, err := k.Clientset.CoreV1().Pods(target.Namespace).List(context.TODO(), metav1.ListOptions{ - LabelSelector: target.Selector, - }) - if err != nil { - k.Log("Unable to find matching pods: %w", err) - break - } + timer := time.NewTimer(0) + defer timer.Stop() - k.Log("Found %d pods for target %#v", len(pods.Items), target) + for { + select { + case <-waitCtx.Done(): + k.Log("Pod lookup failed: %v", ctx.Err()) + return nil + case <-timer.C: + pods, err := k.GetPods(ctx, target.Namespace, metav1.ListOptions{ + LabelSelector: target.Selector, + }) + if err != nil { + k.Log("Unable to find matching pods: %w", err) + return nil + } - var readyPods = []corev1.Pod{} + k.Log("Found %d pods for target %#v", len(pods.Items), target) - // Sort the pods from newest to oldest - sort.Slice(pods.Items, func(i, j int) bool { - return pods.Items[i].CreationTimestamp.After(pods.Items[j].CreationTimestamp.Time) - }) + var readyPods = []corev1.Pod{} - for _, pod := range pods.Items { - k.Log("Testing pod %q", pod.Name) + // Sort the pods from newest to oldest + sort.Slice(pods.Items, func(i, j int) bool { + return pods.Items[i].CreationTimestamp.After(pods.Items[j].CreationTimestamp.Time) + }) - // If an include function is provided, only keep pods that return true - if include != nil && !include(pod) { - continue - } + for _, pod := range pods.Items { + k.Log("Testing pod %q", pod.Name) - // Handle container targeting - if target.Container != "" { - k.Log("Testing pod %q for container %q", pod.Name, target.Container) - var matchesInitContainer bool - - // Check the status of initContainers for a running match - for _, initContainer := range pod.Status.InitContainerStatuses { - isRunning := initContainer.State.Running != nil - if isRunning && initContainer.Name == target.Container { - // On running match in initContainer break this loop - matchesInitContainer = true - readyPods = append(readyPods, pod) - break - } - } - - // Don't check any further if there's already a match - if matchesInitContainer { + // If an include function is provided, only keep pods that return true + if include != nil && !include(pod) { continue } - // Check the status of regular containers for a running match - for _, container := range pod.Status.ContainerStatuses { - isRunning := container.State.Running != nil - if isRunning && container.Name == target.Container { + // Handle container targeting + if target.Container != "" { + k.Log("Testing pod %q for container %q", pod.Name, target.Container) + + // Check the status of initContainers for a running match + for _, initContainer := range pod.Status.InitContainerStatuses { + isRunning := initContainer.State.Running != nil + if initContainer.Name == target.Container && isRunning { + // On running match in initContainer break this loop + readyPods = append(readyPods, pod) + break + } + } + + // Check the status of regular containers for a running match + for _, container := range pod.Status.ContainerStatuses { + isRunning := container.State.Running != nil + if container.Name == target.Container && isRunning { + readyPods = append(readyPods, pod) + break + } + } + } else { + status := pod.Status.Phase + k.Log("Testing pod %q phase, want (%q) got (%q)", pod.Name, corev1.PodRunning, status) + // Regular status checking without a container + if status == corev1.PodRunning { readyPods = append(readyPods, pod) + break } } - } else { - status := pod.Status.Phase - k.Log("Testing pod %q phase, want (%q) got (%q)", pod.Name, corev1.PodRunning, status) - // Regular status checking without a container - if status == corev1.PodRunning { - readyPods = append(readyPods, pod) - } } + if len(readyPods) > 0 { + return readyPods + } + timer.Reset(3 * time.Second) } - - if len(readyPods) > 0 { - return readyPods - } - - time.Sleep(3 * time.Second) } - - k.Log("Pod lookup timeout exceeded") - - return []corev1.Pod{} } // FindPodContainerPort will find a pod's container port from a service and return it. // // Returns 0 if no port is found. -func (k *K8s) FindPodContainerPort(svc corev1.Service) int { +func (k *K8s) FindPodContainerPort(ctx context.Context, svc corev1.Service) int { selectorLabelsOfPods := MakeLabels(svc.Spec.Selector) - pods := k.WaitForPodsAndContainers(PodLookup{ - Namespace: svc.Namespace, - Selector: selectorLabelsOfPods, - }, nil) + pods := k.WaitForPodsAndContainers( + ctx, + PodLookup{ + Namespace: svc.Namespace, + Selector: selectorLabelsOfPods, + }, + nil, + ) for _, pod := range pods { // Find the matching name on the port in the pod diff --git a/src/pkg/k8s/sa.go b/src/pkg/k8s/sa.go index 26e48d134d..38b7624130 100644 --- a/src/pkg/k8s/sa.go +++ b/src/pkg/k8s/sa.go @@ -15,48 +15,50 @@ import ( ) // GetAllServiceAccounts returns a list of services accounts for all namespaces. -func (k *K8s) GetAllServiceAccounts() (*corev1.ServiceAccountList, error) { - return k.GetServiceAccounts(corev1.NamespaceAll) +func (k *K8s) GetAllServiceAccounts(ctx context.Context) (*corev1.ServiceAccountList, error) { + return k.GetServiceAccounts(ctx, corev1.NamespaceAll) } // GetServiceAccounts returns a list of service accounts in a given namespace. -func (k *K8s) GetServiceAccounts(namespace string) (*corev1.ServiceAccountList, error) { +func (k *K8s) GetServiceAccounts(ctx context.Context, namespace string) (*corev1.ServiceAccountList, error) { metaOptions := metav1.ListOptions{} - return k.Clientset.CoreV1().ServiceAccounts(namespace).List(context.TODO(), metaOptions) + return k.Clientset.CoreV1().ServiceAccounts(namespace).List(ctx, metaOptions) } // GetServiceAccount returns a single service account by namespace and name. -func (k *K8s) GetServiceAccount(namespace, name string) (*corev1.ServiceAccount, error) { +func (k *K8s) GetServiceAccount(ctx context.Context, namespace, name string) (*corev1.ServiceAccount, error) { metaOptions := metav1.GetOptions{} - return k.Clientset.CoreV1().ServiceAccounts(namespace).Get(context.TODO(), name, metaOptions) + return k.Clientset.CoreV1().ServiceAccounts(namespace).Get(ctx, name, metaOptions) } // UpdateServiceAccount updates the given service account in the cluster. -func (k *K8s) UpdateServiceAccount(svcAccount *corev1.ServiceAccount) (*corev1.ServiceAccount, error) { +func (k *K8s) UpdateServiceAccount(ctx context.Context, svcAccount *corev1.ServiceAccount) (*corev1.ServiceAccount, error) { metaOptions := metav1.UpdateOptions{} - return k.Clientset.CoreV1().ServiceAccounts(svcAccount.Namespace).Update(context.TODO(), svcAccount, metaOptions) + return k.Clientset.CoreV1().ServiceAccounts(svcAccount.Namespace).Update(ctx, svcAccount, metaOptions) } // WaitForServiceAccount waits for a service account to be created in the cluster. -func (k *K8s) WaitForServiceAccount(ns, name string, timeout time.Duration) (*corev1.ServiceAccount, error) { - expired := time.After(timeout) +func (k *K8s) WaitForServiceAccount(ctx context.Context, ns, name string) (*corev1.ServiceAccount, error) { + timer := time.NewTimer(0) + defer timer.Stop() for { select { - case <-expired: - return nil, fmt.Errorf("timed out waiting for service account %s/%s to exist", ns, name) - - default: - sa, err := k.Clientset.CoreV1().ServiceAccounts(ns).Get(context.TODO(), name, metav1.GetOptions{}) - if err != nil { - if errors.IsNotFound(err) { - time.Sleep(1 * time.Second) - continue - } + case <-ctx.Done(): + return nil, fmt.Errorf("failed to get service account %s/%s: %w", ns, name, ctx.Err()) + case <-timer.C: + sa, err := k.Clientset.CoreV1().ServiceAccounts(ns).Get(ctx, name, metav1.GetOptions{}) + if err == nil { + return sa, nil + } + + if errors.IsNotFound(err) { + k.Log("Service account %s/%s not found, retrying...", ns, name) + } else { return nil, fmt.Errorf("error getting service account %s/%s: %w", ns, name, err) } - return sa, nil + timer.Reset(1 * time.Second) } } } diff --git a/src/pkg/k8s/secrets.go b/src/pkg/k8s/secrets.go index f92882b8d0..d391b97771 100644 --- a/src/pkg/k8s/secrets.go +++ b/src/pkg/k8s/secrets.go @@ -15,14 +15,14 @@ import ( ) // GetSecret returns a Kubernetes secret. -func (k *K8s) GetSecret(namespace, name string) (*corev1.Secret, error) { - return k.Clientset.CoreV1().Secrets(namespace).Get(context.TODO(), name, metav1.GetOptions{}) +func (k *K8s) GetSecret(ctx context.Context, namespace, name string) (*corev1.Secret, error) { + return k.Clientset.CoreV1().Secrets(namespace).Get(ctx, name, metav1.GetOptions{}) } // GetSecretsWithLabel returns a list of Kubernetes secrets with the given label. -func (k *K8s) GetSecretsWithLabel(namespace, labelSelector string) (*corev1.SecretList, error) { +func (k *K8s) GetSecretsWithLabel(ctx context.Context, namespace, labelSelector string) (*corev1.SecretList, error) { listOptions := metav1.ListOptions{LabelSelector: labelSelector} - return k.Clientset.CoreV1().Secrets(namespace).List(context.TODO(), listOptions) + return k.Clientset.CoreV1().Secrets(namespace).List(ctx, listOptions) } // GenerateSecret returns a Kubernetes secret object without applying it to the cluster. @@ -58,20 +58,20 @@ func (k *K8s) GenerateTLSSecret(namespace, name string, conf GeneratedPKI) (*cor } // CreateOrUpdateTLSSecret creates or updates a Kubernetes secret with a new TLS secret. -func (k *K8s) CreateOrUpdateTLSSecret(namespace, name string, conf GeneratedPKI) (*corev1.Secret, error) { +func (k *K8s) CreateOrUpdateTLSSecret(ctx context.Context, namespace, name string, conf GeneratedPKI) (*corev1.Secret, error) { secret, err := k.GenerateTLSSecret(namespace, name, conf) if err != nil { return secret, err } - return k.CreateOrUpdateSecret(secret) + return k.CreateOrUpdateSecret(ctx, secret) } // DeleteSecret deletes a Kubernetes secret. -func (k *K8s) DeleteSecret(secret *corev1.Secret) error { +func (k *K8s) DeleteSecret(ctx context.Context, secret *corev1.Secret) error { namespaceSecrets := k.Clientset.CoreV1().Secrets(secret.Namespace) - err := namespaceSecrets.Delete(context.TODO(), secret.Name, metav1.DeleteOptions{}) + err := namespaceSecrets.Delete(ctx, secret.Name, metav1.DeleteOptions{}) if err != nil && !errors.IsNotFound(err) { return fmt.Errorf("error deleting the secret: %w", err) } @@ -80,18 +80,18 @@ func (k *K8s) DeleteSecret(secret *corev1.Secret) error { } // CreateOrUpdateSecret creates or updates a Kubernetes secret. -func (k *K8s) CreateOrUpdateSecret(secret *corev1.Secret) (createdSecret *corev1.Secret, err error) { +func (k *K8s) CreateOrUpdateSecret(ctx context.Context, secret *corev1.Secret) (createdSecret *corev1.Secret, err error) { namespaceSecrets := k.Clientset.CoreV1().Secrets(secret.Namespace) - if _, err = k.GetSecret(secret.Namespace, secret.Name); err != nil { + if _, err = k.GetSecret(ctx, secret.Namespace, secret.Name); err != nil { // create the given secret - if createdSecret, err = namespaceSecrets.Create(context.TODO(), secret, metav1.CreateOptions{}); err != nil { + if createdSecret, err = namespaceSecrets.Create(ctx, secret, metav1.CreateOptions{}); err != nil { return createdSecret, fmt.Errorf("unable to create the secret: %w", err) } } else { // update the given secret - if createdSecret, err = namespaceSecrets.Update(context.TODO(), secret, metav1.UpdateOptions{}); err != nil { + if createdSecret, err = namespaceSecrets.Update(ctx, secret, metav1.UpdateOptions{}); err != nil { return createdSecret, fmt.Errorf("unable to update the secret: %w", err) } } diff --git a/src/pkg/k8s/services.go b/src/pkg/k8s/services.go index 725fc2788b..63b847d413 100644 --- a/src/pkg/k8s/services.go +++ b/src/pkg/k8s/services.go @@ -27,12 +27,12 @@ type ServiceInfo struct { } // ReplaceService deletes and re-creates a service. -func (k *K8s) ReplaceService(service *corev1.Service) (*corev1.Service, error) { - if err := k.DeleteService(service.Namespace, service.Name); err != nil { +func (k *K8s) ReplaceService(ctx context.Context, service *corev1.Service) (*corev1.Service, error) { + if err := k.DeleteService(ctx, service.Namespace, service.Name); err != nil { return nil, err } - return k.CreateService(service) + return k.CreateService(ctx, service) } // GenerateService returns a K8s service struct without writing to the cluster. @@ -54,28 +54,28 @@ func (k *K8s) GenerateService(namespace, name string) *corev1.Service { } // DeleteService removes a service from the cluster by namespace and name. -func (k *K8s) DeleteService(namespace, name string) error { - return k.Clientset.CoreV1().Services(namespace).Delete(context.TODO(), name, metav1.DeleteOptions{}) +func (k *K8s) DeleteService(ctx context.Context, namespace, name string) error { + return k.Clientset.CoreV1().Services(namespace).Delete(ctx, name, metav1.DeleteOptions{}) } // CreateService creates the given service in the cluster. -func (k *K8s) CreateService(service *corev1.Service) (*corev1.Service, error) { +func (k *K8s) CreateService(ctx context.Context, service *corev1.Service) (*corev1.Service, error) { createOptions := metav1.CreateOptions{} - return k.Clientset.CoreV1().Services(service.Namespace).Create(context.TODO(), service, createOptions) + return k.Clientset.CoreV1().Services(service.Namespace).Create(ctx, service, createOptions) } // GetService returns a Kubernetes service resource in the provided namespace with the given name. -func (k *K8s) GetService(namespace, serviceName string) (*corev1.Service, error) { - return k.Clientset.CoreV1().Services(namespace).Get(context.TODO(), serviceName, metav1.GetOptions{}) +func (k *K8s) GetService(ctx context.Context, namespace, serviceName string) (*corev1.Service, error) { + return k.Clientset.CoreV1().Services(namespace).Get(ctx, serviceName, metav1.GetOptions{}) } // GetServices returns a list of services in the provided namespace. To search all namespaces, pass "" in the namespace arg. -func (k *K8s) GetServices(namespace string) (*corev1.ServiceList, error) { - return k.Clientset.CoreV1().Services(namespace).List(context.TODO(), metav1.ListOptions{}) +func (k *K8s) GetServices(ctx context.Context, namespace string) (*corev1.ServiceList, error) { + return k.Clientset.CoreV1().Services(namespace).List(ctx, metav1.ListOptions{}) } // GetServicesByLabel returns a list of matched services given a label and value. To search all namespaces, pass "" in the namespace arg. -func (k *K8s) GetServicesByLabel(namespace, label, value string) (*corev1.ServiceList, error) { +func (k *K8s) GetServicesByLabel(ctx context.Context, namespace, label, value string) (*corev1.ServiceList, error) { // Create the selector and add the requirement labelSelector, _ := metav1.LabelSelectorAsSelector(&metav1.LabelSelector{ MatchLabels: Labels{ @@ -84,11 +84,11 @@ func (k *K8s) GetServicesByLabel(namespace, label, value string) (*corev1.Servic }) // Run the query with the selector and return as a ServiceList - return k.Clientset.CoreV1().Services(namespace).List(context.TODO(), metav1.ListOptions{LabelSelector: labelSelector.String()}) + return k.Clientset.CoreV1().Services(namespace).List(ctx, metav1.ListOptions{LabelSelector: labelSelector.String()}) } // GetServicesByLabelExists returns a list of matched services given a label. To search all namespaces, pass "" in the namespace arg. -func (k *K8s) GetServicesByLabelExists(namespace, label string) (*corev1.ServiceList, error) { +func (k *K8s) GetServicesByLabelExists(ctx context.Context, namespace, label string) (*corev1.ServiceList, error) { // Create the selector and add the requirement labelSelector, _ := metav1.LabelSelectorAsSelector(&metav1.LabelSelector{ MatchExpressions: []metav1.LabelSelectorRequirement{{ @@ -98,12 +98,12 @@ func (k *K8s) GetServicesByLabelExists(namespace, label string) (*corev1.Service }) // Run the query with the selector and return as a ServiceList - return k.Clientset.CoreV1().Services(namespace).List(context.TODO(), metav1.ListOptions{LabelSelector: labelSelector.String()}) + return k.Clientset.CoreV1().Services(namespace).List(ctx, metav1.ListOptions{LabelSelector: labelSelector.String()}) } // ServiceInfoFromNodePortURL takes a nodePortURL and parses it to find the service info for connecting to the cluster. The string is expected to follow the following format: // Example nodePortURL: 127.0.0.1:{PORT}. -func (k *K8s) ServiceInfoFromNodePortURL(nodePortURL string) (*ServiceInfo, error) { +func (k *K8s) ServiceInfoFromNodePortURL(ctx context.Context, nodePortURL string) (*ServiceInfo, error) { // Attempt to parse as normal, if this fails add a scheme to the URL (docker registries don't use schemes) parsedURL, err := url.Parse(nodePortURL) if err != nil { @@ -128,7 +128,7 @@ func (k *K8s) ServiceInfoFromNodePortURL(nodePortURL string) (*ServiceInfo, erro return nil, fmt.Errorf("node port services should use the port range 30000-32767") } - services, err := k.GetServices("") + services, err := k.GetServices(ctx, "") if err != nil { return nil, err } diff --git a/src/pkg/k8s/tunnel.go b/src/pkg/k8s/tunnel.go index a4b910fccf..6c4f46e0cf 100644 --- a/src/pkg/k8s/tunnel.go +++ b/src/pkg/k8s/tunnel.go @@ -7,6 +7,7 @@ package k8s // Forked from https://github.com/gruntwork-io/terratest/blob/v0.38.8/modules/k8s/tunnel.go import ( + "context" "fmt" "io" "net/http" @@ -79,24 +80,36 @@ func (tunnel *Tunnel) Wrap(function func() error) error { } // Connect will establish a tunnel to the specified target. -func (tunnel *Tunnel) Connect() (string, error) { - url, err := tunnel.establish() +func (tunnel *Tunnel) Connect(ctx context.Context) (string, error) { + url, err := tunnel.establish(ctx) // Try to establish the tunnel up to 3 times. if err != nil { tunnel.attempt++ + // If we have exceeded the number of attempts, exit with an error. if tunnel.attempt > 3 { return "", fmt.Errorf("unable to establish tunnel after 3 attempts: %w", err) } + // Otherwise, retry the connection but delay increasing intervals between attempts. delay := tunnel.attempt * 10 tunnel.kube.Log("%s", err.Error()) tunnel.kube.Log("Delay creating tunnel, waiting %d seconds...", delay) - time.Sleep(time.Duration(delay) * time.Second) - url, err = tunnel.Connect() - if err != nil { - return "", err + + timer := time.NewTimer(0) + defer timer.Stop() + + select { + case <-ctx.Done(): + return "", ctx.Err() + case <-timer.C: + url, err = tunnel.Connect(ctx) + if err != nil { + return "", err + } + + timer.Reset(time.Duration(delay) * time.Second) } } @@ -129,7 +142,7 @@ func (tunnel *Tunnel) Close() { } // establish opens a tunnel to a kubernetes resource, as specified by the provided tunnel struct. -func (tunnel *Tunnel) establish() (string, error) { +func (tunnel *Tunnel) establish(ctx context.Context) (string, error) { var err error // Track this locally as we may need to retry if the tunnel fails. @@ -163,7 +176,7 @@ func (tunnel *Tunnel) establish() (string, error) { tunnel.kube.Log(message) // Find the pod to port forward to - podName, err := tunnel.getAttachablePodForResource() + podName, err := tunnel.getAttachablePodForResource(ctx) if err != nil { return "", fmt.Errorf("unable to find pod attached to given resource: %w", err) } @@ -222,29 +235,33 @@ func (tunnel *Tunnel) establish() (string, error) { // getAttachablePodForResource will find a pod that can be port forwarded to the provided resource type and return // the name. -func (tunnel *Tunnel) getAttachablePodForResource() (string, error) { +func (tunnel *Tunnel) getAttachablePodForResource(ctx context.Context) (string, error) { switch tunnel.resourceType { case PodResource: return tunnel.resourceName, nil case SvcResource: - return tunnel.getAttachablePodForService() + return tunnel.getAttachablePodForService(ctx) default: return "", fmt.Errorf("unknown resource type: %s", tunnel.resourceType) } } // getAttachablePodForService will find an active pod associated with the Service and return the pod name. -func (tunnel *Tunnel) getAttachablePodForService() (string, error) { - service, err := tunnel.kube.GetService(tunnel.namespace, tunnel.resourceName) +func (tunnel *Tunnel) getAttachablePodForService(ctx context.Context) (string, error) { + service, err := tunnel.kube.GetService(ctx, tunnel.namespace, tunnel.resourceName) if err != nil { return "", fmt.Errorf("unable to find the service: %w", err) } selectorLabelsOfPods := MakeLabels(service.Spec.Selector) - servicePods := tunnel.kube.WaitForPodsAndContainers(PodLookup{ - Namespace: tunnel.namespace, - Selector: selectorLabelsOfPods, - }, nil) + servicePods := tunnel.kube.WaitForPodsAndContainers( + ctx, + PodLookup{ + Namespace: tunnel.namespace, + Selector: selectorLabelsOfPods, + }, + nil, + ) if len(servicePods) < 1 { return "", fmt.Errorf("no pods found for service %s", tunnel.resourceName) diff --git a/src/pkg/layout/package_test.go b/src/pkg/layout/package_test.go index 8e7036f066..3c73c1b9b9 100644 --- a/src/pkg/layout/package_test.go +++ b/src/pkg/layout/package_test.go @@ -90,13 +90,13 @@ func TestPackageFiles(t *testing.T) { "checksums.txt": normalizePath("test/checksums.txt"), } require.Equal(t, expected, files) - + pp.SBOMs.Path = normalizePath("test/sboms.tar") files = pp.Files() expected = map[string]string{ "zarf.yaml": normalizePath("test/zarf.yaml"), "checksums.txt": normalizePath("test/checksums.txt"), - "sboms.tar": normalizePath("test/sboms.tar"), + "sboms.tar": normalizePath("test/sboms.tar"), } require.Equal(t, expected, files) }) diff --git a/src/pkg/packager/common.go b/src/pkg/packager/common.go index 319874d26b..f432b8d423 100644 --- a/src/pkg/packager/common.go +++ b/src/pkg/packager/common.go @@ -5,11 +5,11 @@ package packager import ( + "context" "errors" "fmt" "os" "strings" - "time" "slices" @@ -153,18 +153,23 @@ func (p *Packager) ClearTempPaths() { _ = os.RemoveAll(layout.SBOMDir) } +// GetVariableConfig returns the variable configuration for the packager. +func (p *Packager) GetVariableConfig() *variables.VariableConfig { + return p.variableConfig +} + // connectToCluster attempts to connect to a cluster if a connection is not already established -func (p *Packager) connectToCluster(timeout time.Duration) (err error) { +func (p *Packager) connectToCluster(ctx context.Context) (err error) { if p.isConnectedToCluster() { return nil } - p.cluster, err = cluster.NewClusterWithWait(timeout) + p.cluster, err = cluster.NewClusterWithWait(ctx) if err != nil { return err } - return p.attemptClusterChecks() + return p.attemptClusterChecks(ctx) } // isConnectedToCluster returns whether the current packager instance is connected to a cluster @@ -184,19 +189,19 @@ func (p *Packager) hasImages() bool { // attemptClusterChecks attempts to connect to the cluster and check for useful metadata and config mismatches. // NOTE: attemptClusterChecks should only return an error if there is a problem significant enough to halt a deployment, otherwise it should return nil and print a warning message. -func (p *Packager) attemptClusterChecks() (err error) { +func (p *Packager) attemptClusterChecks(ctx context.Context) (err error) { spinner := message.NewProgressSpinner("Gathering additional cluster information (if available)") defer spinner.Stop() // Check if the package has already been deployed and get its generation - if existingDeployedPackage, _ := p.cluster.GetDeployedPackage(p.cfg.Pkg.Metadata.Name); existingDeployedPackage != nil { + if existingDeployedPackage, _ := p.cluster.GetDeployedPackage(ctx, p.cfg.Pkg.Metadata.Name); existingDeployedPackage != nil { // If this package has been deployed before, increment the package generation within the secret p.generation = existingDeployedPackage.Generation + 1 } // Check the clusters architecture matches the package spec - if err := p.validatePackageArchitecture(); err != nil { + if err := p.validatePackageArchitecture(ctx); err != nil { if errors.Is(err, lang.ErrUnableToCheckArch) { message.Warnf("Unable to validate package architecture: %s", err.Error()) } else { @@ -205,7 +210,7 @@ func (p *Packager) attemptClusterChecks() (err error) { } // Check for any breaking changes between the initialized Zarf version and this CLI - if existingInitPackage, _ := p.cluster.GetDeployedPackage("init"); existingInitPackage != nil { + if existingInitPackage, _ := p.cluster.GetDeployedPackage(ctx, "init"); existingInitPackage != nil { // Use the build version instead of the metadata since this will support older Zarf versions deprecated.PrintBreakingChanges(existingInitPackage.Data.Build.Version) } @@ -216,13 +221,13 @@ func (p *Packager) attemptClusterChecks() (err error) { } // validatePackageArchitecture validates that the package architecture matches the target cluster architecture. -func (p *Packager) validatePackageArchitecture() error { +func (p *Packager) validatePackageArchitecture(ctx context.Context) error { // Ignore this check if we don't have a cluster connection, or the package contains no images if !p.isConnectedToCluster() || !p.hasImages() { return nil } - clusterArchitectures, err := p.cluster.GetArchitectures() + clusterArchitectures, err := p.cluster.GetArchitectures(ctx) if err != nil { return lang.ErrUnableToCheckArch } diff --git a/src/pkg/packager/common_test.go b/src/pkg/packager/common_test.go index 1f497415be..3ead7f9b4b 100644 --- a/src/pkg/packager/common_test.go +++ b/src/pkg/packager/common_test.go @@ -1,6 +1,10 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2021-Present The Zarf Authors + package packager import ( + "context" "errors" "fmt" "testing" @@ -129,7 +133,7 @@ func TestValidatePackageArchitecture(t *testing.T) { return true, nodeList, nil }) - err := p.validatePackageArchitecture() + err := p.validatePackageArchitecture(context.TODO()) require.Equal(t, testCase.expectedError, err) }) diff --git a/src/pkg/packager/composer/list.go b/src/pkg/packager/composer/list.go index dced62b0f1..3642ec562f 100644 --- a/src/pkg/packager/composer/list.go +++ b/src/pkg/packager/composer/list.go @@ -11,7 +11,6 @@ import ( "strings" "github.com/defenseunicorns/pkg/helpers" - "github.com/defenseunicorns/zarf/src/internal/packager/validate" "github.com/defenseunicorns/zarf/src/pkg/layout" "github.com/defenseunicorns/zarf/src/pkg/packager/deprecated" "github.com/defenseunicorns/zarf/src/pkg/utils" @@ -142,7 +141,7 @@ func NewImportChain(head types.ZarfComponent, index int, originalPackageName, ar } // TODO: stuff like this should also happen in linting - if err := validate.ImportDefinition(&node.ZarfComponent); err != nil { + if err := node.ZarfComponent.ValidateImportDefinition(); err != nil { return ic, err } diff --git a/src/pkg/packager/create.go b/src/pkg/packager/create.go index b03664ae13..bc10fe8d73 100755 --- a/src/pkg/packager/create.go +++ b/src/pkg/packager/create.go @@ -10,7 +10,6 @@ import ( "github.com/defenseunicorns/pkg/helpers" "github.com/defenseunicorns/zarf/src/config" - "github.com/defenseunicorns/zarf/src/internal/packager/validate" "github.com/defenseunicorns/zarf/src/pkg/layout" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/defenseunicorns/zarf/src/pkg/packager/creator" @@ -41,7 +40,7 @@ func (p *Packager) Create() (err error) { } // Perform early package validation. - if err := validate.Run(p.cfg.Pkg); err != nil { + if err := p.cfg.Pkg.Validate(); err != nil { return fmt.Errorf("unable to validate package: %w", err) } diff --git a/src/pkg/packager/creator/differential.go b/src/pkg/packager/creator/differential.go index 59915eead1..ee373836d0 100644 --- a/src/pkg/packager/creator/differential.go +++ b/src/pkg/packager/creator/differential.go @@ -5,17 +5,13 @@ package creator import ( - "fmt" "os" "github.com/defenseunicorns/zarf/src/config" - "github.com/defenseunicorns/zarf/src/internal/packager/git" "github.com/defenseunicorns/zarf/src/pkg/layout" "github.com/defenseunicorns/zarf/src/pkg/packager/sources" - "github.com/defenseunicorns/zarf/src/pkg/transform" "github.com/defenseunicorns/zarf/src/pkg/utils" "github.com/defenseunicorns/zarf/src/types" - "github.com/go-git/go-git/v5/plumbing" ) // loadDifferentialData sets any images and repos from the existing reference package in the DifferentialData and returns it. @@ -58,47 +54,3 @@ func loadDifferentialData(diffPkgPath string) (diffData *types.DifferentialData, DifferentialPackageVersion: diffPkg.Metadata.Version, }, nil } - -// removeCopiesFromComponents removes any images and repos already present in the reference package components. -func removeCopiesFromComponents(components []types.ZarfComponent, loadedDiffData *types.DifferentialData) (diffComponents []types.ZarfComponent, err error) { - for _, component := range components { - newImageList := []string{} - newRepoList := []string{} - - for _, img := range component.Images { - imgRef, err := transform.ParseImageRef(img) - if err != nil { - return nil, fmt.Errorf("unable to parse image ref %s: %s", img, err.Error()) - } - - imgTag := imgRef.TagOrDigest - includeImage := imgTag == ":latest" || imgTag == ":stable" || imgTag == ":nightly" - if includeImage || !loadedDiffData.DifferentialImages[img] { - newImageList = append(newImageList, img) - } - } - - for _, repoURL := range component.Repos { - _, refPlain, err := transform.GitURLSplitRef(repoURL) - if err != nil { - return nil, err - } - - var ref plumbing.ReferenceName - if refPlain != "" { - ref = git.ParseRef(refPlain) - } - - includeRepo := ref == "" || (!ref.IsTag() && !plumbing.IsHash(refPlain)) - if includeRepo || !loadedDiffData.DifferentialRepos[repoURL] { - newRepoList = append(newRepoList, repoURL) - } - } - - component.Images = newImageList - component.Repos = newRepoList - diffComponents = append(diffComponents, component) - } - - return diffComponents, nil -} diff --git a/src/pkg/packager/creator/normal.go b/src/pkg/packager/creator/normal.go index 9e742d6cee..0cb5c7d921 100644 --- a/src/pkg/packager/creator/normal.go +++ b/src/pkg/packager/creator/normal.go @@ -8,6 +8,7 @@ import ( "context" "errors" "fmt" + "math/rand" "os" "path/filepath" "strconv" @@ -27,6 +28,7 @@ import ( "github.com/defenseunicorns/zarf/src/pkg/layout" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/defenseunicorns/zarf/src/pkg/packager/actions" + "github.com/defenseunicorns/zarf/src/pkg/packager/filters" "github.com/defenseunicorns/zarf/src/pkg/packager/sources" "github.com/defenseunicorns/zarf/src/pkg/transform" "github.com/defenseunicorns/zarf/src/pkg/utils" @@ -110,7 +112,8 @@ func (pc *PackageCreator) LoadPackageDefinition(dst *layout.PackagePaths) (pkg t return types.ZarfPackage{}, nil, errors.New(lang.PkgCreateErrDifferentialNoVersion) } - pkg.Components, err = removeCopiesFromComponents(pkg.Components, diffData) + filter := filters.ByDifferentialData(diffData) + pkg.Components, err = filter.Apply(pkg) if err != nil { return types.ZarfPackage{}, nil, err } @@ -166,6 +169,9 @@ func (pc *PackageCreator) Assemble(dst *layout.PackagePaths, components []types. } imageList = helpers.Unique(imageList) + rs := rand.NewSource(time.Now().UnixNano()) + rnd := rand.New(rs) + rnd.Shuffle(len(imageList), func(i, j int) { imageList[i], imageList[j] = imageList[j], imageList[i] }) var sbomImageList []transform.Image // Images are handled separately from other component assets. @@ -174,32 +180,31 @@ func (pc *PackageCreator) Assemble(dst *layout.PackagePaths, components []types. dst.AddImages() - var pulled []images.ImgInfo - var err error - - doPull := func() error { - imgConfig := images.ImageConfig{ - ImagesPath: dst.Images.Base, - ImageList: imageList, - Insecure: config.CommonOptions.Insecure, - Architectures: []string{arch}, - RegistryOverrides: pc.createOpts.RegistryOverrides, - } + ctx := context.TODO() - pulled, err = imgConfig.PullAll() - return err + pullCfg := images.PullConfig{ + DestinationDirectory: dst.Images.Base, + ImageList: imageList, + Arch: arch, + RegistryOverrides: pc.createOpts.RegistryOverrides, + CacheDirectory: filepath.Join(config.GetAbsCachePath(), layout.ImagesDir), } - if err := helpers.Retry(doPull, 3, 5*time.Second, message.Warnf); err != nil { - return fmt.Errorf("unable to pull images after 3 attempts: %w", err) + pulled, err := images.Pull(ctx, pullCfg) + if err != nil { + return err } - for _, imgInfo := range pulled { - if err := dst.Images.AddV1Image(imgInfo.Img); err != nil { + for info, img := range pulled { + if err := dst.Images.AddV1Image(img); err != nil { return err } - if imgInfo.HasImageLayers { - sbomImageList = append(sbomImageList, imgInfo.RefInfo) + ok, err := utils.HasImageLayers(img) + if err != nil { + return fmt.Errorf("failed to validate %s is an image and not an artifact: %w", info, err) + } + if ok { + sbomImageList = append(sbomImageList, info) } } } diff --git a/src/pkg/packager/deploy.go b/src/pkg/packager/deploy.go index ef4495be0a..039a63cb61 100644 --- a/src/pkg/packager/deploy.go +++ b/src/pkg/packager/deploy.go @@ -5,6 +5,7 @@ package packager import ( + "context" "fmt" "os" "path/filepath" @@ -32,16 +33,16 @@ import ( corev1 "k8s.io/api/core/v1" ) -func (p *Packager) resetRegistryHPA() { +func (p *Packager) resetRegistryHPA(ctx context.Context) { if p.isConnectedToCluster() && p.hpaModified { - if err := p.cluster.EnableRegHPAScaleDown(); err != nil { + if err := p.cluster.EnableRegHPAScaleDown(ctx); err != nil { message.Debugf("unable to reenable the registry HPA scale down: %s", err.Error()) } } } // Deploy attempts to deploy the given PackageConfig. -func (p *Packager) Deploy() (err error) { +func (p *Packager) Deploy(ctx context.Context) (err error) { isInteractive := !config.CommonOptions.Confirm @@ -100,10 +101,10 @@ func (p *Packager) Deploy() (err error) { p.hpaModified = false p.connectStrings = make(types.ConnectStrings) // Reset registry HPA scale down whether an error occurs or not - defer p.resetRegistryHPA() + defer p.resetRegistryHPA(ctx) // Get a list of all the components we are deploying and actually deploy them - deployedComponents, err := p.deployComponents() + deployedComponents, err := p.deployComponents(ctx) if err != nil { return err } @@ -114,14 +115,13 @@ func (p *Packager) Deploy() (err error) { // Notify all the things about the successful deployment message.Successf("Zarf deployment complete") - p.printTablesForDeployment(deployedComponents) + p.printTablesForDeployment(ctx, deployedComponents) return nil } // deployComponents loops through a list of ZarfComponents and deploys them. -func (p *Packager) deployComponents() (deployedComponents []types.DeployedComponent, err error) { - +func (p *Packager) deployComponents(ctx context.Context) (deployedComponents []types.DeployedComponent, err error) { // Check if this package has been deployed before and grab relevant information about already deployed components if p.generation == 0 { p.generation = 1 // If this is the first deployment, set the generation to 1 @@ -142,15 +142,16 @@ func (p *Packager) deployComponents() (deployedComponents []types.DeployedCompon if p.cfg.Pkg.IsInitConfig() { timeout = 5 * time.Minute } - - if err := p.connectToCluster(timeout); err != nil { + connectCtx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + if err := p.connectToCluster(connectCtx); err != nil { return deployedComponents, fmt.Errorf("unable to connect to the Kubernetes cluster: %w", err) } } // Ensure we don't overwrite any installedCharts data when updating the package secret if p.isConnectedToCluster() { - deployedComponent.InstalledCharts, err = p.cluster.GetInstalledChartsForComponent(p.cfg.Pkg.Metadata.Name, component) + deployedComponent.InstalledCharts, err = p.cluster.GetInstalledChartsForComponent(ctx, p.cfg.Pkg.Metadata.Name, component) if err != nil { message.Debugf("Unable to fetch installed Helm charts for component '%s': %s", component.Name, err.Error()) } @@ -161,7 +162,7 @@ func (p *Packager) deployComponents() (deployedComponents []types.DeployedCompon // Update the package secret to indicate that we are attempting to deploy this component if p.isConnectedToCluster() { - if _, err := p.cluster.RecordPackageDeploymentAndWait(p.cfg.Pkg, deployedComponents, p.connectStrings, p.generation, component, p.cfg.DeployOpts.SkipWebhooks); err != nil { + if _, err := p.cluster.RecordPackageDeploymentAndWait(ctx, p.cfg.Pkg, deployedComponents, p.connectStrings, p.generation, component, p.cfg.DeployOpts.SkipWebhooks); err != nil { message.Debugf("Unable to record package deployment for component %s: this will affect features like `zarf package remove`: %s", component.Name, err.Error()) } } @@ -170,9 +171,9 @@ func (p *Packager) deployComponents() (deployedComponents []types.DeployedCompon var charts []types.InstalledChart var deployErr error if p.cfg.Pkg.IsInitConfig() { - charts, deployErr = p.deployInitComponent(component) + charts, deployErr = p.deployInitComponent(ctx, component) } else { - charts, deployErr = p.deployComponent(component, false /* keep img checksum */, false /* always push images */) + charts, deployErr = p.deployComponent(ctx, component, false /* keep img checksum */, false /* always push images */) } onDeploy := component.Actions.OnDeploy @@ -189,7 +190,7 @@ func (p *Packager) deployComponents() (deployedComponents []types.DeployedCompon // Update the package secret to indicate that we failed to deploy this component deployedComponents[idx].Status = types.ComponentStatusFailed if p.isConnectedToCluster() { - if _, err := p.cluster.RecordPackageDeploymentAndWait(p.cfg.Pkg, deployedComponents, p.connectStrings, p.generation, component, p.cfg.DeployOpts.SkipWebhooks); err != nil { + if _, err := p.cluster.RecordPackageDeploymentAndWait(ctx, p.cfg.Pkg, deployedComponents, p.connectStrings, p.generation, component, p.cfg.DeployOpts.SkipWebhooks); err != nil { message.Debugf("Unable to record package deployment for component %q: this will affect features like `zarf package remove`: %s", component.Name, err.Error()) } } @@ -201,7 +202,7 @@ func (p *Packager) deployComponents() (deployedComponents []types.DeployedCompon deployedComponents[idx].InstalledCharts = charts deployedComponents[idx].Status = types.ComponentStatusSucceeded if p.isConnectedToCluster() { - if _, err := p.cluster.RecordPackageDeploymentAndWait(p.cfg.Pkg, deployedComponents, p.connectStrings, p.generation, component, p.cfg.DeployOpts.SkipWebhooks); err != nil { + if _, err := p.cluster.RecordPackageDeploymentAndWait(ctx, p.cfg.Pkg, deployedComponents, p.connectStrings, p.generation, component, p.cfg.DeployOpts.SkipWebhooks); err != nil { message.Debugf("Unable to record package deployment for component %q: this will affect features like `zarf package remove`: %s", component.Name, err.Error()) } } @@ -215,7 +216,7 @@ func (p *Packager) deployComponents() (deployedComponents []types.DeployedCompon return deployedComponents, nil } -func (p *Packager) deployInitComponent(component types.ZarfComponent) (charts []types.InstalledChart, err error) { +func (p *Packager) deployInitComponent(ctx context.Context, component types.ZarfComponent) (charts []types.InstalledChart, err error) { hasExternalRegistry := p.cfg.InitOpts.RegistryInfo.Address != "" isSeedRegistry := component.Name == "zarf-seed-registry" isRegistry := component.Name == "zarf-registry" @@ -229,7 +230,7 @@ func (p *Packager) deployInitComponent(component types.ZarfComponent) (charts [] // Always init the state before the first component that requires the cluster (on most deployments, the zarf-seed-registry) if component.RequiresCluster() && p.state == nil { - err = p.cluster.InitZarfState(p.cfg.InitOpts) + err = p.cluster.InitZarfState(ctx, p.cfg.InitOpts) if err != nil { return charts, fmt.Errorf("unable to initialize Zarf state: %w", err) } @@ -247,17 +248,17 @@ func (p *Packager) deployInitComponent(component types.ZarfComponent) (charts [] // Before deploying the seed registry, start the injector if isSeedRegistry { - p.cluster.StartInjectionMadness(p.layout.Base, p.layout.Images.Base, component.Images) + p.cluster.StartInjectionMadness(ctx, p.layout.Base, p.layout.Images.Base, component.Images) } - charts, err = p.deployComponent(component, isAgent /* skip img checksum if isAgent */, isSeedRegistry /* skip image push if isSeedRegistry */) + charts, err = p.deployComponent(ctx, component, isAgent /* skip img checksum if isAgent */, isSeedRegistry /* skip image push if isSeedRegistry */) if err != nil { return charts, err } // Do cleanup for when we inject the seed registry during initialization if isSeedRegistry { - if err := p.cluster.StopInjectionMadness(); err != nil { + if err := p.cluster.StopInjectionMadness(ctx); err != nil { return charts, fmt.Errorf("unable to seed the Zarf Registry: %w", err) } } @@ -266,7 +267,7 @@ func (p *Packager) deployInitComponent(component types.ZarfComponent) (charts [] } // Deploy a Zarf Component. -func (p *Packager) deployComponent(component types.ZarfComponent, noImgChecksum bool, noImgPush bool) (charts []types.InstalledChart, err error) { +func (p *Packager) deployComponent(ctx context.Context, component types.ZarfComponent, noImgChecksum bool, noImgPush bool) (charts []types.InstalledChart, err error) { // Toggles for general deploy operations componentPath := p.layout.Components.Dirs[component.Name] @@ -285,7 +286,7 @@ func (p *Packager) deployComponent(component types.ZarfComponent, noImgChecksum if component.RequiresCluster() { // Setup the state in the config if p.state == nil { - err = p.setupState() + err = p.setupState(ctx) if err != nil { return charts, err } @@ -293,7 +294,7 @@ func (p *Packager) deployComponent(component types.ZarfComponent, noImgChecksum // Disable the registry HPA scale down if we are deploying images and it is not already disabled if hasImages && !p.hpaModified && p.state.RegistryInfo.InternalRegistry { - if err := p.cluster.DisableRegHPAScaleDown(); err != nil { + if err := p.cluster.DisableRegHPAScaleDown(ctx); err != nil { message.Debugf("unable to disable the registry HPA scale down: %s", err.Error()) } else { p.hpaModified = true @@ -317,13 +318,13 @@ func (p *Packager) deployComponent(component types.ZarfComponent, noImgChecksum } if hasImages { - if err := p.pushImagesToRegistry(component.Images, noImgChecksum); err != nil { + if err := p.pushImagesToRegistry(ctx, component.Images, noImgChecksum); err != nil { return charts, fmt.Errorf("unable to push images to the registry: %w", err) } } if hasRepos { - if err = p.pushReposToRepository(componentPath.Repos, component.Repos); err != nil { + if err = p.pushReposToRepository(ctx, componentPath.Repos, component.Repos); err != nil { return charts, fmt.Errorf("unable to push the repos to the repository: %w", err) } } @@ -334,13 +335,13 @@ func (p *Packager) deployComponent(component types.ZarfComponent, noImgChecksum for idx, data := range component.DataInjections { waitGroup.Add(1) - go p.cluster.HandleDataInjection(&waitGroup, data, componentPath, idx) + go p.cluster.HandleDataInjection(ctx, &waitGroup, data, componentPath, idx) } } if hasCharts || hasManifests { if charts, err = p.installChartAndManifests(componentPath, component); err != nil { - return charts, fmt.Errorf("unable to install helm chart(s): %w", err) + return charts, err } } @@ -431,12 +432,12 @@ func (p *Packager) processComponentFiles(component types.ZarfComponent, pkgLocat } // setupState fetches the current ZarfState from the k8s cluster and sets the packager to use it -func (p *Packager) setupState() (err error) { +func (p *Packager) setupState(ctx context.Context) (err error) { // If we are touching K8s, make sure we can talk to it once per deployment spinner := message.NewProgressSpinner("Loading the Zarf State from the Kubernetes cluster") defer spinner.Stop() - state, err := p.cluster.LoadZarfState() + state, err := p.cluster.LoadZarfState(ctx) // Return on error if we are not in YOLO mode if err != nil && !p.cfg.Pkg.Metadata.YOLO { return fmt.Errorf("%s %w", lang.ErrLoadState, err) @@ -448,7 +449,7 @@ func (p *Packager) setupState() (err error) { // Try to create the zarf namespace spinner.Updatef("Creating the Zarf namespace") zarfNamespace := p.cluster.NewZarfManagedNamespace(cluster.ZarfNamespaceName) - if _, err := p.cluster.CreateNamespace(zarfNamespace); err != nil { + if _, err := p.cluster.CreateNamespace(ctx, zarfNamespace); err != nil { spinner.Fatalf(err, "Unable to create the zarf namespace") } } @@ -480,11 +481,7 @@ func (p *Packager) populatePackageVariableConfig() error { } // Push all of the components images to the configured container registry. -func (p *Packager) pushImagesToRegistry(componentImages []string, noImgChecksum bool) error { - if len(componentImages) == 0 { - return nil - } - +func (p *Packager) pushImagesToRegistry(ctx context.Context, componentImages []string, noImgChecksum bool) error { var combinedImageList []transform.Image for _, src := range componentImages { ref, err := transform.ParseImageRef(src) @@ -496,22 +493,20 @@ func (p *Packager) pushImagesToRegistry(componentImages []string, noImgChecksum imageList := helpers.Unique(combinedImageList) - imgConfig := images.ImageConfig{ - ImagesPath: p.layout.Images.Base, - ImageList: imageList, - NoChecksum: noImgChecksum, - RegInfo: p.state.RegistryInfo, - Insecure: config.CommonOptions.Insecure, - Architectures: []string{p.cfg.Pkg.Build.Architecture}, + pushCfg := images.PushConfig{ + SourceDirectory: p.layout.Images.Base, + ImageList: imageList, + RegInfo: p.state.RegistryInfo, + NoChecksum: noImgChecksum, + Arch: p.cfg.Pkg.Build.Architecture, + Retries: p.cfg.PkgOpts.Retries, } - return helpers.Retry(func() error { - return imgConfig.PushToZarfRegistry() - }, p.cfg.PkgOpts.Retries, 5*time.Second, message.Warnf) + return images.Push(ctx, pushCfg) } // Push all of the components git repos to the configured git server. -func (p *Packager) pushReposToRepository(reposPath string, repos []string) error { +func (p *Packager) pushReposToRepository(ctx context.Context, reposPath string, repos []string) error { for _, repoURL := range repos { // Create an anonymous function to push the repo to the Zarf git server tryPush := func() error { @@ -524,7 +519,9 @@ func (p *Packager) pushReposToRepository(reposPath string, repos []string) error // If this is a service (svcInfo is not nil), create a port-forward tunnel to that resource if svcInfo != nil { if !p.isConnectedToCluster() { - err := p.connectToCluster(5 * time.Second) + connectCtx, cancel := context.WithTimeout(ctx, 5*time.Second) + defer cancel() + err := p.connectToCluster(connectCtx) if err != nil { return err } @@ -535,7 +532,7 @@ func (p *Packager) pushReposToRepository(reposPath string, repos []string) error return err } - _, err = tunnel.Connect() + _, err = tunnel.Connect(ctx) if err != nil { return err } @@ -690,7 +687,7 @@ func (p *Packager) installChartAndManifests(componentPaths *layout.ComponentPath return installedCharts, nil } -func (p *Packager) printTablesForDeployment(componentsToDeploy []types.DeployedComponent) { +func (p *Packager) printTablesForDeployment(ctx context.Context, componentsToDeploy []types.DeployedComponent) { // If not init config, print the application connection table if !p.cfg.Pkg.IsInitConfig() { @@ -698,7 +695,7 @@ func (p *Packager) printTablesForDeployment(componentsToDeploy []types.DeployedC } else { if p.cluster != nil { // Grab a fresh copy of the state (if we are able) to print the most up-to-date version of the creds - freshState, err := p.cluster.LoadZarfState() + freshState, err := p.cluster.LoadZarfState(ctx) if err != nil { freshState = p.state } diff --git a/src/pkg/packager/dev.go b/src/pkg/packager/dev.go index 454d62f6cc..187f74c70c 100644 --- a/src/pkg/packager/dev.go +++ b/src/pkg/packager/dev.go @@ -5,13 +5,13 @@ package packager import ( + "context" "fmt" "os" "runtime" "github.com/defenseunicorns/pkg/helpers" "github.com/defenseunicorns/zarf/src/config" - "github.com/defenseunicorns/zarf/src/internal/packager/validate" "github.com/defenseunicorns/zarf/src/pkg/layout" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/defenseunicorns/zarf/src/pkg/packager/creator" @@ -20,7 +20,7 @@ import ( ) // DevDeploy creates + deploys a package in one shot -func (p *Packager) DevDeploy() error { +func (p *Packager) DevDeploy(ctx context.Context) error { config.CommonOptions.Confirm = true p.cfg.CreateOpts.SkipSBOM = !p.cfg.CreateOpts.NoYOLO @@ -53,7 +53,7 @@ func (p *Packager) DevDeploy() error { return err } - if err := validate.Run(p.cfg.Pkg); err != nil { + if err := p.cfg.Pkg.Validate(); err != nil { return fmt.Errorf("unable to validate package: %w", err) } @@ -82,11 +82,11 @@ func (p *Packager) DevDeploy() error { } else { p.hpaModified = false // Reset registry HPA scale down whether an error occurs or not - defer p.resetRegistryHPA() + defer p.resetRegistryHPA(ctx) } // Get a list of all the components we are deploying and actually deploy them - deployedComponents, err := p.deployComponents() + deployedComponents, err := p.deployComponents(ctx) if err != nil { return err } diff --git a/src/pkg/packager/filters/deploy.go b/src/pkg/packager/filters/deploy.go index d98b9c93f2..622f481229 100644 --- a/src/pkg/packager/filters/deploy.go +++ b/src/pkg/packager/filters/deploy.go @@ -75,7 +75,7 @@ func (f *deploymentFilter) Apply(pkg types.ZarfPackage) ([]types.ZarfComponent, selectState, matchedRequest := includedOrExcluded(component.Name, f.requestedComponents) - if !isRequired(component) { + if !component.IsRequired() { if selectState == excluded { // If the component was explicitly excluded, record the match and continue matchedRequests[matchedRequest] = true @@ -161,7 +161,7 @@ func (f *deploymentFilter) Apply(pkg types.ZarfPackage) ([]types.ZarfComponent, } else { component := groupedComponents[groupKey][0] - if isRequired(component) { + if component.IsRequired() { selectedComponents = append(selectedComponents, component) continue } diff --git a/src/pkg/packager/filters/diff.go b/src/pkg/packager/filters/diff.go new file mode 100644 index 0000000000..fe722e9741 --- /dev/null +++ b/src/pkg/packager/filters/diff.go @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2021-Present The Zarf Authors + +package filters + +import ( + "fmt" + + "github.com/defenseunicorns/zarf/src/internal/packager/git" + "github.com/defenseunicorns/zarf/src/pkg/transform" + "github.com/defenseunicorns/zarf/src/types" + "github.com/go-git/go-git/v5/plumbing" +) + +// ByDifferentialData filters any images and repos already present in the reference package components. +func ByDifferentialData(diffData *types.DifferentialData) ComponentFilterStrategy { + return &differentialDataFilter{ + diffData: diffData, + } +} + +type differentialDataFilter struct { + diffData *types.DifferentialData +} + +func (f *differentialDataFilter) Apply(pkg types.ZarfPackage) ([]types.ZarfComponent, error) { + diffComponents := []types.ZarfComponent{} + for _, component := range pkg.Components { + filteredImages := []string{} + for _, img := range component.Images { + imgRef, err := transform.ParseImageRef(img) + if err != nil { + return nil, fmt.Errorf("unable to parse image ref %s: %w", img, err) + } + imgTag := imgRef.TagOrDigest + includeImage := imgTag == ":latest" || imgTag == ":stable" || imgTag == ":nightly" + if includeImage || !f.diffData.DifferentialImages[img] { + filteredImages = append(filteredImages, img) + } + } + component.Images = filteredImages + + filteredRepos := []string{} + for _, repoURL := range component.Repos { + _, refPlain, err := transform.GitURLSplitRef(repoURL) + if err != nil { + return nil, err + } + var ref plumbing.ReferenceName + if refPlain != "" { + ref = git.ParseRef(refPlain) + } + includeRepo := ref == "" || (!ref.IsTag() && !plumbing.IsHash(refPlain)) + if includeRepo || !f.diffData.DifferentialRepos[repoURL] { + filteredRepos = append(filteredRepos, repoURL) + } + } + component.Repos = filteredRepos + + diffComponents = append(diffComponents, component) + } + return diffComponents, nil +} diff --git a/src/pkg/packager/filters/diff_test.go b/src/pkg/packager/filters/diff_test.go new file mode 100644 index 0000000000..8ee64fab84 --- /dev/null +++ b/src/pkg/packager/filters/diff_test.go @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2021-Present The Zarf Authors + +package filters + +import ( + "testing" + + "github.com/defenseunicorns/zarf/src/types" + "github.com/stretchr/testify/require" +) + +func TestCopyFilter(t *testing.T) { + pkg := types.ZarfPackage{ + Components: []types.ZarfComponent{ + { + Images: []string{ + "example.com/include-image-tag:latest", + "example.com/image-with-tag:v1", + "example.com/diff-image-with-tag:v1", + "example.com/image-with-digest@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "example.com/diff-image-with-digest@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "example.com/image-with-tag-and-digest:v1@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "example.com/diff-image-with-tag-and-digest:v1@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + }, + Repos: []string{ + "https://example.com/no-ref.git", + "https://example.com/branch.git@refs/heads/main", + "https://example.com/tag.git@v1", + "https://example.com/diff-tag.git@v1", + "https://example.com/commit.git@524980951ff16e19dc25232e9aea8fd693989ba6", + "https://example.com/diff-commit.git@524980951ff16e19dc25232e9aea8fd693989ba6", + }, + }, + }, + } + loadedDiffData := types.DifferentialData{ + DifferentialImages: map[string]bool{ + "example.com/include-image-tag:latest": true, + "example.com/diff-image-with-tag:v1": true, + "example.com/diff-image-with-digest@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855": true, + "example.com/diff-image-with-tag-and-digest:v1@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855": true, + }, + DifferentialRepos: map[string]bool{ + "https://example.com/no-ref.git": true, + "https://example.com/branch.git@refs/heads/main": true, + "https://example.com/diff-tag.git@v1": true, + "https://example.com/diff-commit.git@524980951ff16e19dc25232e9aea8fd693989ba6": true, + }, + } + + filter := ByDifferentialData(&loadedDiffData) + diffComponents, err := filter.Apply(pkg) + require.NoError(t, err) + + expectedImages := []string{ + "example.com/include-image-tag:latest", + "example.com/image-with-tag:v1", + "example.com/image-with-digest@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "example.com/image-with-tag-and-digest:v1@sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + } + require.ElementsMatch(t, expectedImages, diffComponents[0].Images) + expectedRepos := []string{ + "https://example.com/no-ref.git", + "https://example.com/branch.git@refs/heads/main", + "https://example.com/tag.git@v1", + "https://example.com/commit.git@524980951ff16e19dc25232e9aea8fd693989ba6", + } + require.ElementsMatch(t, expectedRepos, diffComponents[0].Repos) +} diff --git a/src/pkg/packager/filters/os_test.go b/src/pkg/packager/filters/os_test.go index 9e6b9e7722..7d54beecd3 100644 --- a/src/pkg/packager/filters/os_test.go +++ b/src/pkg/packager/filters/os_test.go @@ -7,7 +7,6 @@ package filters import ( "testing" - "github.com/defenseunicorns/zarf/src/internal/packager/validate" "github.com/defenseunicorns/zarf/src/types" "github.com/stretchr/testify/require" ) @@ -15,7 +14,7 @@ import ( func TestLocalOSFilter(t *testing.T) { pkg := types.ZarfPackage{} - for _, os := range validate.SupportedOS() { + for _, os := range types.SupportedOS() { pkg.Components = append(pkg.Components, types.ZarfComponent{ Only: types.ZarfComponentOnlyTarget{ LocalOS: os, @@ -23,7 +22,7 @@ func TestLocalOSFilter(t *testing.T) { }) } - for _, os := range validate.SupportedOS() { + for _, os := range types.SupportedOS() { filter := ByLocalOS(os) result, err := filter.Apply(pkg) if os == "" { diff --git a/src/pkg/packager/filters/utils.go b/src/pkg/packager/filters/utils.go index 30e1b7cced..3b33f9b594 100644 --- a/src/pkg/packager/filters/utils.go +++ b/src/pkg/packager/filters/utils.go @@ -7,8 +7,6 @@ package filters import ( "path" "strings" - - "github.com/defenseunicorns/zarf/src/types" ) type selectState int @@ -42,14 +40,3 @@ func includedOrExcluded(componentName string, requestedComponentNames []string) // All other cases we don't know if we should include or exclude yet return unknown, "" } - -// isRequired returns if the component is required or not. -func isRequired(c types.ZarfComponent) bool { - requiredExists := c.Required != nil - required := requiredExists && *c.Required - - if requiredExists { - return required - } - return false -} diff --git a/src/pkg/packager/generate.go b/src/pkg/packager/generate.go index 072c1f566b..64bb824285 100644 --- a/src/pkg/packager/generate.go +++ b/src/pkg/packager/generate.go @@ -12,7 +12,6 @@ import ( "github.com/defenseunicorns/pkg/helpers" "github.com/defenseunicorns/zarf/src/config" - "github.com/defenseunicorns/zarf/src/internal/packager/validate" "github.com/defenseunicorns/zarf/src/pkg/layout" "github.com/defenseunicorns/zarf/src/pkg/message" "github.com/defenseunicorns/zarf/src/types" @@ -73,7 +72,7 @@ func (p *Packager) Generate() (err error) { p.cfg.Pkg.Components[i].Images = images[name] } - if err := validate.Run(p.cfg.Pkg); err != nil { + if err := p.cfg.Pkg.Validate(); err != nil { return err } diff --git a/src/pkg/packager/inspect.go b/src/pkg/packager/inspect.go index 6a6cc7fac9..56645e2d54 100644 --- a/src/pkg/packager/inspect.go +++ b/src/pkg/packager/inspect.go @@ -5,6 +5,10 @@ package packager import ( + "fmt" + "os" + + "github.com/defenseunicorns/pkg/helpers" "github.com/defenseunicorns/zarf/src/internal/packager/sbom" "github.com/defenseunicorns/zarf/src/pkg/utils" ) @@ -18,7 +22,18 @@ func (p *Packager) Inspect() (err error) { return err } - utils.ColorPrintYAML(p.cfg.Pkg, nil, false) + if p.cfg.InspectOpts.ListImages { + imageList := []string{} + for _, component := range p.cfg.Pkg.Components { + imageList = append(imageList, component.Images...) + } + imageList = helpers.Unique(imageList) + for _, image := range imageList { + fmt.Fprintln(os.Stdout, "-", image) + } + } else { + utils.ColorPrintYAML(p.cfg.Pkg, nil, false) + } sbomDir := p.layout.SBOMs.Path diff --git a/src/pkg/packager/mirror.go b/src/pkg/packager/mirror.go index 0afec2e480..658967560d 100644 --- a/src/pkg/packager/mirror.go +++ b/src/pkg/packager/mirror.go @@ -5,6 +5,7 @@ package packager import ( + "context" "fmt" "runtime" "strings" @@ -16,7 +17,7 @@ import ( ) // Mirror pulls resources from a package (images, git repositories, etc) and pushes them to remotes in the air gap without deploying them -func (p *Packager) Mirror() (err error) { +func (p *Packager) Mirror(ctx context.Context) (err error) { filter := filters.Combine( filters.ByLocalOS(runtime.GOOS), filters.BySelectState(p.cfg.PkgOpts.OptionalComponents), @@ -46,7 +47,7 @@ func (p *Packager) Mirror() (err error) { } for _, component := range p.cfg.Pkg.Components { - if err := p.mirrorComponent(component); err != nil { + if err := p.mirrorComponent(ctx, component); err != nil { return err } } @@ -54,7 +55,7 @@ func (p *Packager) Mirror() (err error) { } // mirrorComponent mirrors a Zarf Component. -func (p *Packager) mirrorComponent(component types.ZarfComponent) error { +func (p *Packager) mirrorComponent(ctx context.Context, component types.ZarfComponent) error { componentPaths := p.layout.Components.Dirs[component.Name] // All components now require a name @@ -64,13 +65,13 @@ func (p *Packager) mirrorComponent(component types.ZarfComponent) error { hasRepos := len(component.Repos) > 0 if hasImages { - if err := p.pushImagesToRegistry(component.Images, p.cfg.MirrorOpts.NoImgChecksum); err != nil { + if err := p.pushImagesToRegistry(ctx, component.Images, p.cfg.MirrorOpts.NoImgChecksum); err != nil { return fmt.Errorf("unable to push images to the registry: %w", err) } } if hasRepos { - if err := p.pushReposToRepository(componentPaths.Repos, component.Repos); err != nil { + if err := p.pushReposToRepository(ctx, componentPaths.Repos, component.Repos); err != nil { return fmt.Errorf("unable to push the repos to the repository: %w", err) } } diff --git a/src/pkg/packager/prepare.go b/src/pkg/packager/prepare.go index 6f5cbf0432..54c9158d5e 100644 --- a/src/pkg/packager/prepare.go +++ b/src/pkg/packager/prepare.go @@ -15,9 +15,9 @@ import ( "github.com/goccy/go-yaml" "github.com/defenseunicorns/pkg/helpers" - "github.com/defenseunicorns/zarf/src/config" "github.com/defenseunicorns/zarf/src/config/lang" "github.com/defenseunicorns/zarf/src/internal/packager/helm" + "github.com/defenseunicorns/zarf/src/internal/packager/images" "github.com/defenseunicorns/zarf/src/internal/packager/kustomize" "github.com/defenseunicorns/zarf/src/pkg/layout" "github.com/defenseunicorns/zarf/src/pkg/message" @@ -294,7 +294,7 @@ func (p *Packager) findImages() (imgMap map[string][]string, err error) { if sortedImages := sortImages(maybeImages, matchedImages); len(sortedImages) > 0 { var validImages []string for _, image := range sortedImages { - if descriptor, err := crane.Head(image, config.GetCraneOptions(config.CommonOptions.Insecure)...); err != nil { + if descriptor, err := crane.Head(image, images.WithGlobalInsecureFlag()...); err != nil { // Test if this is a real image, if not just quiet log to debug, this is normal message.Debugf("Suspected image does not appear to be valid: %#v", err) } else { diff --git a/src/pkg/packager/remove.go b/src/pkg/packager/remove.go index c0329ce55d..a107147714 100644 --- a/src/pkg/packager/remove.go +++ b/src/pkg/packager/remove.go @@ -5,6 +5,7 @@ package packager import ( + "context" "encoding/json" "errors" "fmt" @@ -26,7 +27,7 @@ import ( ) // Remove removes a package that was already deployed onto a cluster, uninstalling all installed helm charts. -func (p *Packager) Remove() (err error) { +func (p *Packager) Remove(ctx context.Context) (err error) { _, isClusterSource := p.source.(*sources.ClusterSource) if isClusterSource { p.cluster = p.source.(*sources.ClusterSource).Cluster @@ -70,11 +71,13 @@ func (p *Packager) Remove() (err error) { deployedPackage := &types.DeployedPackage{} if packageRequiresCluster { - err = p.connectToCluster(cluster.DefaultTimeout) + connectCtx, cancel := context.WithTimeout(ctx, cluster.DefaultTimeout) + defer cancel() + err = p.connectToCluster(connectCtx) if err != nil { return err } - deployedPackage, err = p.cluster.GetDeployedPackage(packageName) + deployedPackage, err = p.cluster.GetDeployedPackage(ctx, packageName) if err != nil { return fmt.Errorf("unable to load the secret for the package we are attempting to remove: %s", err.Error()) } @@ -93,7 +96,7 @@ func (p *Packager) Remove() (err error) { continue } - if deployedPackage, err = p.removeComponent(deployedPackage, dc, spinner); err != nil { + if deployedPackage, err = p.removeComponent(ctx, deployedPackage, dc, spinner); err != nil { return fmt.Errorf("unable to remove the component '%s': %w", dc.Name, err) } } @@ -101,7 +104,7 @@ func (p *Packager) Remove() (err error) { return nil } -func (p *Packager) updatePackageSecret(deployedPackage types.DeployedPackage) { +func (p *Packager) updatePackageSecret(ctx context.Context, deployedPackage types.DeployedPackage) { // Only attempt to update the package secret if we are actually connected to a cluster if p.cluster != nil { secretName := config.ZarfPackagePrefix + deployedPackage.Name @@ -113,7 +116,7 @@ func (p *Packager) updatePackageSecret(deployedPackage types.DeployedPackage) { newPackageSecretData, _ := json.Marshal(deployedPackage) newPackageSecret.Data["data"] = newPackageSecretData - _, err := p.cluster.CreateOrUpdateSecret(newPackageSecret) + _, err := p.cluster.CreateOrUpdateSecret(ctx, newPackageSecret) // We warn and ignore errors because we may have removed the cluster that this package was inside of if err != nil { @@ -122,7 +125,7 @@ func (p *Packager) updatePackageSecret(deployedPackage types.DeployedPackage) { } } -func (p *Packager) removeComponent(deployedPackage *types.DeployedPackage, deployedComponent types.DeployedComponent, spinner *message.Spinner) (*types.DeployedPackage, error) { +func (p *Packager) removeComponent(ctx context.Context, deployedPackage *types.DeployedPackage, deployedComponent types.DeployedComponent, spinner *message.Spinner) (*types.DeployedPackage, error) { components := deployedPackage.Data.Components c := helpers.Find(components, func(t types.ZarfComponent) bool { @@ -162,7 +165,7 @@ func (p *Packager) removeComponent(deployedPackage *types.DeployedPackage, deplo deployedComponent.InstalledCharts = helpers.RemoveMatches(deployedComponent.InstalledCharts, func(t types.InstalledChart) bool { return t.ChartName == chart.ChartName }) - p.updatePackageSecret(*deployedPackage) + p.updatePackageSecret(ctx, *deployedPackage) } if err := actions.Run(onRemove.Defaults, onRemove.After, nil); err != nil { @@ -184,19 +187,19 @@ func (p *Packager) removeComponent(deployedPackage *types.DeployedPackage, deplo secretName := config.ZarfPackagePrefix + deployedPackage.Name // All the installed components were deleted, therefore this package is no longer actually deployed - packageSecret, err := p.cluster.GetSecret(cluster.ZarfNamespaceName, secretName) + packageSecret, err := p.cluster.GetSecret(ctx, cluster.ZarfNamespaceName, secretName) // We warn and ignore errors because we may have removed the cluster that this package was inside of if err != nil { message.Warnf("Unable to delete the '%s' package secret: '%s' (this may be normal if the cluster was removed)", secretName, err.Error()) } else { - err = p.cluster.DeleteSecret(packageSecret) + err = p.cluster.DeleteSecret(ctx, packageSecret) if err != nil { message.Warnf("Unable to delete the '%s' package secret: '%s' (this may be normal if the cluster was removed)", secretName, err.Error()) } } } else { - p.updatePackageSecret(*deployedPackage) + p.updatePackageSecret(ctx, *deployedPackage) } return deployedPackage, nil diff --git a/src/pkg/packager/sources/cluster.go b/src/pkg/packager/sources/cluster.go index 1d506c6937..c1478cb3da 100644 --- a/src/pkg/packager/sources/cluster.go +++ b/src/pkg/packager/sources/cluster.go @@ -5,10 +5,10 @@ package sources import ( + "context" "fmt" "github.com/defenseunicorns/pkg/helpers" - "github.com/defenseunicorns/zarf/src/internal/packager/validate" "github.com/defenseunicorns/zarf/src/pkg/cluster" "github.com/defenseunicorns/zarf/src/pkg/layout" "github.com/defenseunicorns/zarf/src/pkg/packager/filters" @@ -23,10 +23,14 @@ var ( // NewClusterSource creates a new cluster source. func NewClusterSource(pkgOpts *types.ZarfPackageOptions) (PackageSource, error) { - if !validate.IsLowercaseNumberHyphenNoStartHyphen(pkgOpts.PackageSource) { + if !types.IsLowercaseNumberHyphenNoStartHyphen(pkgOpts.PackageSource) { return nil, fmt.Errorf("invalid package name %q", pkgOpts.PackageSource) } - cluster, err := cluster.NewClusterWithWait(cluster.DefaultTimeout) + + ctx, cancel := context.WithTimeout(context.Background(), cluster.DefaultTimeout) + defer cancel() + + cluster, err := cluster.NewClusterWithWait(ctx) if err != nil { return nil, err } @@ -55,7 +59,9 @@ func (s *ClusterSource) Collect(_ string) (string, error) { // LoadPackageMetadata loads package metadata from a cluster. func (s *ClusterSource) LoadPackageMetadata(dst *layout.PackagePaths, _ bool, _ bool) (types.ZarfPackage, []string, error) { - dpkg, err := s.GetDeployedPackage(s.PackageSource) + ctx := context.Background() + + dpkg, err := s.GetDeployedPackage(ctx, s.PackageSource) if err != nil { return types.ZarfPackage{}, nil, err } diff --git a/src/pkg/transform/types.go b/src/pkg/transform/types.go index 7b19eaaee2..dbf4922a0f 100644 --- a/src/pkg/transform/types.go +++ b/src/pkg/transform/types.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2021-Present The Zarf Authors + package transform // Log is a function that logs a message. diff --git a/src/pkg/utils/bytes.go b/src/pkg/utils/bytes.go index d5fd24e5c6..7ef3ef5c8c 100644 --- a/src/pkg/utils/bytes.go +++ b/src/pkg/utils/bytes.go @@ -1,5 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2021-Present The Zarf Authors + // forked from https://www.socketloop.com/tutorials/golang-byte-format-example // Package utils provides generic utility functions. diff --git a/src/pkg/utils/cosign.go b/src/pkg/utils/cosign.go index 5883495fd0..d9f017afce 100644 --- a/src/pkg/utils/cosign.go +++ b/src/pkg/utils/cosign.go @@ -1,4 +1,3 @@ -// Forked from https://github.com/sigstore/cosign/blob/v1.7.1/pkg/sget/sget.go // SPDX-License-Identifier: Apache-2.0 // SPDX-FileCopyrightText: 2021-Present The Zarf Authors @@ -37,6 +36,8 @@ import ( ) // Sget performs a cosign signature verification on a given image using the specified public key. +// +// Forked from https://github.com/sigstore/cosign/blob/v1.7.1/pkg/sget/sget.go func Sget(ctx context.Context, image, key string, out io.Writer) error { message.Warnf(lang.WarnSGetDeprecation) diff --git a/src/pkg/utils/image.go b/src/pkg/utils/image.go index 5a05d987df..0074ea9036 100644 --- a/src/pkg/utils/image.go +++ b/src/pkg/utils/image.go @@ -48,13 +48,12 @@ func LoadOCIImage(imgPath string, refInfo transform.Image) (v1.Image, error) { func AddImageNameAnnotation(ociPath string, referenceToDigest map[string]string) error { indexPath := filepath.Join(ociPath, "index.json") - // Read the file contents and turn it into a usable struct that we can manipulate var index ocispec.Index - byteValue, err := os.ReadFile(indexPath) + b, err := os.ReadFile(indexPath) if err != nil { return fmt.Errorf("unable to read the contents of the file (%s) so we can add an annotation: %w", indexPath, err) } - if err = json.Unmarshal(byteValue, &index); err != nil { + if err = json.Unmarshal(b, &index); err != nil { return fmt.Errorf("unable to process the contents of the file (%s): %w", indexPath, err) } @@ -80,14 +79,14 @@ func AddImageNameAnnotation(ociPath string, referenceToDigest map[string]string) } // Write the file back to the package - indexJSONBytes, err := json.Marshal(index) + b, err = json.Marshal(index) if err != nil { return err } - return os.WriteFile(indexPath, indexJSONBytes, helpers.ReadWriteUser) + return os.WriteFile(indexPath, b, helpers.ReadWriteUser) } -// HasImageLayers checks if any layers in the v1.Image are known image layers. +// HasImageLayers checks if all layers in the v1.Image are known image layers. func HasImageLayers(img v1.Image) (bool, error) { layers, err := img.Layers() if err != nil { @@ -98,10 +97,9 @@ func HasImageLayers(img v1.Image) (bool, error) { if err != nil { return false, err } - // Check if mediatype is a known image layer - if mediatype.IsLayer() { - return true, nil + if !mediatype.IsLayer() { + return false, nil } } - return false, nil + return true, nil } diff --git a/src/pkg/variables/types.go b/src/pkg/variables/types.go index 5b56fe3eeb..f6f51a6917 100644 --- a/src/pkg/variables/types.go +++ b/src/pkg/variables/types.go @@ -4,6 +4,13 @@ // Package variables contains functions for interacting with variables package variables +import ( + "fmt" + "regexp" + + "github.com/defenseunicorns/zarf/src/config/lang" +) + // VariableType represents a type of a Zarf package variable type VariableType string @@ -14,6 +21,12 @@ const ( FileVariableType VariableType = "file" ) +var ( + // IsUppercaseNumberUnderscore is a regex for uppercase, numbers and underscores. + // https://regex101.com/r/tfsEuZ/1 + IsUppercaseNumberUnderscore = regexp.MustCompile(`^[A-Z0-9_]+$`).MatchString +) + // Variable represents a variable that has a value set programmatically type Variable struct { Name string `json:"name" jsonschema:"description=The name to be used for the variable,pattern=^[A-Z0-9_]+$"` @@ -46,3 +59,25 @@ type SetVariable struct { Variable `json:",inline"` Value string `json:"value" jsonschema:"description=The value the variable is currently set with"` } + +// Validate runs all validation checks on a package variable. +func (v Variable) Validate() error { + if !IsUppercaseNumberUnderscore(v.Name) { + return fmt.Errorf(lang.PkgValidateMustBeUppercase, v.Name) + } + return nil +} + +// Validate runs all validation checks on a package constant. +func (c Constant) Validate() error { + // ensure the constant name is only capitals and underscores + if !IsUppercaseNumberUnderscore(c.Name) { + return fmt.Errorf(lang.PkgValidateErrPkgConstantName, c.Name) + } + + if !regexp.MustCompile(c.Pattern).MatchString(c.Value) { + return fmt.Errorf(lang.PkgValidateErrPkgConstantPattern, c.Name, c.Pattern) + } + + return nil +} diff --git a/src/pkg/zoci/push.go b/src/pkg/zoci/push.go index 3f1bc2b265..380750574e 100644 --- a/src/pkg/zoci/push.go +++ b/src/pkg/zoci/push.go @@ -33,7 +33,7 @@ func (r *Remote) PublishPackage(ctx context.Context, pkg *types.ZarfPackage, pat // Get all of the layers in the package var descs []ocispec.Descriptor for name, path := range paths.Files() { - spinner.Updatef("Preparing layer %s", helpers.First30last30(name)) + spinner.Updatef("Preparing layer %s", helpers.First30Last30(name)) mediaType := ZarfLayerMediaTypeBlob diff --git a/src/test/e2e/00_use_cli_test.go b/src/test/e2e/00_use_cli_test.go index eec380619e..6b5a0e387c 100644 --- a/src/test/e2e/00_use_cli_test.go +++ b/src/test/e2e/00_use_cli_test.go @@ -13,6 +13,7 @@ import ( "testing" "github.com/defenseunicorns/pkg/helpers" + "github.com/otiai10/copy" "github.com/stretchr/testify/require" ) @@ -216,21 +217,32 @@ func TestUseCLI(t *testing.T) { t.Run("zarf tools yq should function appropriately across different uses", func(t *testing.T) { t.Parallel() - file := "src/test/packages/00-yq-checks/file1.yaml" - otherFile := "src/test/packages/00-yq-checks/file2.yaml" + tmpdir := t.TempDir() + originalPath := "src/test/packages/00-yq-checks" + + originalFile := filepath.Join(originalPath, "file1.yaml") + originalOtherFile := filepath.Join(originalPath, "file2.yaml") + + file := filepath.Join(tmpdir, "file1.yaml") + otherFile := filepath.Join(tmpdir, "file2.yaml") + + err := copy.Copy(originalFile, file) + require.NoError(t, err) + err = copy.Copy(originalOtherFile, otherFile) + require.NoError(t, err) // Test that yq can eval properly _, stdErr, err := e2e.Zarf("tools", "yq", "eval", "-i", `.items[1].name = "renamed-item"`, file) require.NoError(t, err, stdErr) - stdOut, stdErr, err := e2e.Zarf("tools", "yq", ".items[1].name", file) + stdOut, _, err := e2e.Zarf("tools", "yq", ".items[1].name", file) + require.NoError(t, err) require.Contains(t, stdOut, "renamed-item") // Test that yq ea can be used properly - _, stdErr, err = e2e.Zarf("tools", "yq", "eval-all", "-i", `. as $doc ireduce ({}; .items += $doc.items)`, file, otherFile) - require.NoError(t, err, stdErr) - stdOut, stdErr, err = e2e.Zarf("tools", "yq", "e", ".items | length", file) + _, _, err = e2e.Zarf("tools", "yq", "eval-all", "-i", `. as $doc ireduce ({}; .items += $doc.items)`, file, otherFile) + require.NoError(t, err) + stdOut, _, err = e2e.Zarf("tools", "yq", "e", ".items | length", file) + require.NoError(t, err) require.Equal(t, "4\n", stdOut) - }) } - diff --git a/src/test/e2e/06_create_sbom_test.go b/src/test/e2e/06_create_sbom_test.go index 9a6027bb14..d040ba2e05 100644 --- a/src/test/e2e/06_create_sbom_test.go +++ b/src/test/e2e/06_create_sbom_test.go @@ -41,6 +41,10 @@ func TestCreateSBOM(t *testing.T) { _, err = os.ReadFile(filepath.Join(sbomPath, "dos-games", "docker.io_defenseunicorns_zarf-game_multi-tile-dark.json")) require.NoError(t, err) + stdOut, _, err = e2e.Zarf("package", "inspect", pkgName, "--list-images") + require.NoError(t, err) + require.Equal(t, "- defenseunicorns/zarf-game:multi-tile-dark\n", stdOut) + // Pull the current zarf binary version to find the corresponding init package version, stdErr, err := e2e.Zarf("version") require.NoError(t, err, version, stdErr) diff --git a/src/test/e2e/12_lint_test.go b/src/test/e2e/12_lint_test.go index 62b6964e88..cc6e513129 100644 --- a/src/test/e2e/12_lint_test.go +++ b/src/test/e2e/12_lint_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2021-Present The Zarf Authors + package test import ( diff --git a/src/test/e2e/13_find_images_test.go b/src/test/e2e/13_find_images_test.go index 3235d519f1..fbb0185320 100644 --- a/src/test/e2e/13_find_images_test.go +++ b/src/test/e2e/13_find_images_test.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2021-Present The Zarf Authors + package test import ( diff --git a/src/test/e2e/14_create_sha_index_test.go b/src/test/e2e/14_create_sha_index_test.go new file mode 100644 index 0000000000..9dee622c85 --- /dev/null +++ b/src/test/e2e/14_create_sha_index_test.go @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2021-Present The Zarf Authors + +// Package test provides e2e tests for Zarf. +package test + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestCreateIndexShaErrors(t *testing.T) { + t.Log("E2E: CreateIndexShaErrors") + + testCases := []struct { + name string + packagePath string + expectedImageInStderr string + }{ + { + name: "Image Index", + packagePath: "src/test/packages/14-index-sha/image-index", + expectedImageInStderr: "ghcr.io/defenseunicorns/zarf/agent:v0.32.6@sha256:b3fabdc7d4ecd0f396016ef78da19002c39e3ace352ea0ae4baa2ce9d5958376", + }, + { + name: "Manifest List", + packagePath: "src/test/packages/14-index-sha/manifest-list", + expectedImageInStderr: "docker.io/defenseunicorns/zarf-game@sha256:f78e442f0f3eb3e9459b5ae6b1a8fda62f8dfe818112e7d130a4e8ae72b3cbff", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + _, stderr, err := e2e.Zarf("package", "create", tc.packagePath, "--confirm") + require.Error(t, err) + require.Contains(t, stderr, tc.expectedImageInStderr) + }) + } + +} diff --git a/src/test/e2e/21_connect_creds_test.go b/src/test/e2e/21_connect_creds_test.go index 4fe560f6a8..a66d390f18 100644 --- a/src/test/e2e/21_connect_creds_test.go +++ b/src/test/e2e/21_connect_creds_test.go @@ -5,6 +5,7 @@ package test import ( + "context" "crypto/tls" "fmt" "io" @@ -27,7 +28,9 @@ func TestConnectAndCreds(t *testing.T) { prevAgentSecretData, _, err := e2e.Kubectl("get", "secret", "agent-hook-tls", "-n", "zarf", "-o", "jsonpath={.data}") require.NoError(t, err) - connectToZarfServices(t) + ctx := context.Background() + + connectToZarfServices(ctx, t) stdOut, stdErr, err := e2e.Zarf("tools", "update-creds", "--confirm") require.NoError(t, err, stdOut, stdErr) @@ -36,7 +39,7 @@ func TestConnectAndCreds(t *testing.T) { require.NoError(t, err) require.NotEqual(t, prevAgentSecretData, newAgentSecretData, "agent secrets should not be the same") - connectToZarfServices(t) + connectToZarfServices(ctx, t) stdOut, stdErr, err = e2e.Zarf("package", "remove", "init", "--components=logging", "--confirm") require.NoError(t, err, stdOut, stdErr) @@ -68,7 +71,7 @@ func TestMetrics(t *testing.T) { tunnel, err := c.NewTunnel("zarf", "svc", "agent-hook", "", 8888, 8443) require.NoError(t, err) - _, err = tunnel.Connect() + _, err = tunnel.Connect(context.Background()) require.NoError(t, err) defer tunnel.Close() @@ -98,7 +101,7 @@ func TestMetrics(t *testing.T) { require.Equal(t, 200, resp.StatusCode) } -func connectToZarfServices(t *testing.T) { +func connectToZarfServices(ctx context.Context, t *testing.T) { // Make the Registry contains the images we expect stdOut, stdErr, err := e2e.Zarf("tools", "registry", "catalog") require.NoError(t, err, stdOut, stdErr) @@ -129,7 +132,7 @@ func connectToZarfServices(t *testing.T) { // Connect to Gitea c, err := cluster.NewCluster() require.NoError(t, err) - tunnelGit, err := c.Connect(cluster.ZarfGit) + tunnelGit, err := c.Connect(ctx, cluster.ZarfGit) require.NoError(t, err) defer tunnelGit.Close() @@ -150,7 +153,7 @@ func connectToZarfServices(t *testing.T) { // Connect to the Logging Stack c, err = cluster.NewCluster() require.NoError(t, err) - tunnelLog, err := c.Connect(cluster.ZarfLogging) + tunnelLog, err := c.Connect(ctx, cluster.ZarfLogging) require.NoError(t, err) defer tunnelLog.Close() diff --git a/src/test/e2e/22_git_and_gitops_test.go b/src/test/e2e/22_git_and_gitops_test.go index f83bd6ba05..7dc712e71a 100644 --- a/src/test/e2e/22_git_and_gitops_test.go +++ b/src/test/e2e/22_git_and_gitops_test.go @@ -5,6 +5,7 @@ package test import ( + "context" "encoding/base64" "encoding/json" "fmt" @@ -12,6 +13,7 @@ import ( "path/filepath" "testing" + "github.com/defenseunicorns/zarf/src/cmd/common" "github.com/defenseunicorns/zarf/src/internal/packager/git" "github.com/defenseunicorns/zarf/src/pkg/cluster" "github.com/defenseunicorns/zarf/src/types" @@ -35,13 +37,14 @@ func TestGit(t *testing.T) { c, err := cluster.NewCluster() require.NoError(t, err) - tunnelGit, err := c.Connect(cluster.ZarfGit) + ctx := context.Background() + tunnelGit, err := c.Connect(ctx, cluster.ZarfGit) require.NoError(t, err) defer tunnelGit.Close() testGitServerConnect(t, tunnelGit.HTTPEndpoint()) - testGitServerReadOnly(t, tunnelGit.HTTPEndpoint()) - testGitServerTagAndHash(t, tunnelGit.HTTPEndpoint()) + testGitServerReadOnly(ctx, t, tunnelGit.HTTPEndpoint()) + testGitServerTagAndHash(ctx, t, tunnelGit.HTTPEndpoint()) } func TestGitOpsFlux(t *testing.T) { @@ -65,9 +68,9 @@ func testGitServerConnect(t *testing.T, gitURL string) { require.Equal(t, 200, resp.StatusCode) } -func testGitServerReadOnly(t *testing.T, gitURL string) { +func testGitServerReadOnly(ctx context.Context, t *testing.T, gitURL string) { // Init the state variable - state, err := cluster.NewClusterOrDie().LoadZarfState() + state, err := common.NewClusterOrDie(ctx).LoadZarfState(ctx) require.NoError(t, err) gitCfg := git.New(state.GitServer) @@ -88,9 +91,9 @@ func testGitServerReadOnly(t *testing.T, gitURL string) { require.True(t, permissionsMap["pull"].(bool)) } -func testGitServerTagAndHash(t *testing.T, gitURL string) { +func testGitServerTagAndHash(ctx context.Context, t *testing.T, gitURL string) { // Init the state variable - state, err := cluster.NewClusterOrDie().LoadZarfState() + state, err := common.NewClusterOrDie(ctx).LoadZarfState(ctx) require.NoError(t, err, "Failed to load Zarf state") repoName := "zarf-public-test-2469062884" diff --git a/src/test/e2e/23_data_injection_test.go b/src/test/e2e/23_data_injection_test.go index ee65c49c0a..efbce9bc13 100644 --- a/src/test/e2e/23_data_injection_test.go +++ b/src/test/e2e/23_data_injection_test.go @@ -21,6 +21,8 @@ func TestDataInjection(t *testing.T) { t.Log("E2E: Data injection") e2e.SetupWithCluster(t) + ctx := context.Background() + path := fmt.Sprintf("build/zarf-package-kiwix-%s-3.5.0.tar", e2e.Arch) tmpdir := t.TempDir() @@ -28,7 +30,7 @@ func TestDataInjection(t *testing.T) { // Repeat the injection action 3 times to ensure the data injection is idempotent and doesn't fail to perform an upgrade for i := 0; i < 3; i++ { - runDataInjection(t, path) + runDataInjection(ctx, t, path) } // Verify the file and injection marker were created @@ -42,7 +44,7 @@ func TestDataInjection(t *testing.T) { // need target to equal svc that we are trying to connect to call checkForZarfConnectLabel c, err := cluster.NewCluster() require.NoError(t, err) - tunnel, err := c.Connect("kiwix") + tunnel, err := c.Connect(ctx, "kiwix") require.NoError(t, err) defer tunnel.Close() @@ -64,9 +66,9 @@ func TestDataInjection(t *testing.T) { require.FileExists(t, filepath.Join(sbomPath, "kiwix", "zarf-component-kiwix-serve.json"), "The data-injection component should have an SBOM json") } -func runDataInjection(t *testing.T, path string) { +func runDataInjection(ctx context.Context, t *testing.T, path string) { // Limit this deploy to 5 minutes - ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Minute) + ctx, cancel := context.WithTimeout(ctx, 5*time.Minute) defer cancel() // Deploy the data injection example diff --git a/src/test/e2e/25_helm_test.go b/src/test/e2e/25_helm_test.go index 1926785c9a..e09d6cf47f 100644 --- a/src/test/e2e/25_helm_test.go +++ b/src/test/e2e/25_helm_test.go @@ -130,16 +130,19 @@ func testHelmUninstallRollback(t *testing.T) { // This package contains SBOMable things but was created with --skip-sbom require.Contains(t, string(stdErr), "This package does NOT contain an SBOM.") - // Ensure that this does not leave behind a dos-games chart + // Ensure this leaves behind a dos-games chart. + // We do not want to uninstall charts that had failed installs/upgrades + // to prevent unintentional deletion and/or data loss in production environments. + // https://github.com/defenseunicorns/zarf/issues/2455 helmOut, err := exec.Command("helm", "list", "-n", "dos-games").Output() require.NoError(t, err) - require.NotContains(t, string(helmOut), "zarf-f53a99d4a4dd9a3575bedf59cd42d48d751ae866") + require.Contains(t, string(helmOut), "zarf-f53a99d4a4dd9a3575bedf59cd42d48d751ae866") // Deploy the good package. stdOut, stdErr, err = e2e.Zarf("package", "deploy", goodPath, "--confirm") require.NoError(t, err, stdOut, stdErr) - // Ensure that this does create a dos-games chart + // Ensure this upgrades/fixes the dos-games chart. helmOut, err = exec.Command("helm", "list", "-n", "dos-games").Output() require.NoError(t, err) require.Contains(t, string(helmOut), "zarf-f53a99d4a4dd9a3575bedf59cd42d48d751ae866") @@ -151,7 +154,7 @@ func testHelmUninstallRollback(t *testing.T) { // Ensure that we rollback properly helmOut, err = exec.Command("helm", "history", "-n", "dos-games", "zarf-f53a99d4a4dd9a3575bedf59cd42d48d751ae866", "--max", "1").Output() require.NoError(t, err) - require.Contains(t, string(helmOut), "Rollback to 1") + require.Contains(t, string(helmOut), "Rollback to 4") // Deploy the evil package (again to ensure we check full history) stdOut, stdErr, err = e2e.Zarf("package", "deploy", evilPath, "--timeout", "10s", "--confirm") @@ -160,7 +163,7 @@ func testHelmUninstallRollback(t *testing.T) { // Ensure that we rollback properly helmOut, err = exec.Command("helm", "history", "-n", "dos-games", "zarf-f53a99d4a4dd9a3575bedf59cd42d48d751ae866", "--max", "1").Output() require.NoError(t, err) - require.Contains(t, string(helmOut), "Rollback to 5") + require.Contains(t, string(helmOut), "Rollback to 8") // Remove the package. stdOut, stdErr, err = e2e.Zarf("package", "remove", "dos-games", "--confirm") diff --git a/src/test/e2e/26_simple_packages_test.go b/src/test/e2e/26_simple_packages_test.go index 996982d383..c7a96e80d6 100644 --- a/src/test/e2e/26_simple_packages_test.go +++ b/src/test/e2e/26_simple_packages_test.go @@ -5,6 +5,7 @@ package test import ( + "context" "fmt" "net/http" "path/filepath" @@ -26,7 +27,7 @@ func TestDosGames(t *testing.T) { c, err := cluster.NewCluster() require.NoError(t, err) - tunnel, err := c.Connect("doom") + tunnel, err := c.Connect(context.Background(), "doom") require.NoError(t, err) defer tunnel.Close() diff --git a/src/test/e2e/30_config_file_test.go b/src/test/e2e/30_config_file_test.go index a03844fa5a..5095efa91f 100644 --- a/src/test/e2e/30_config_file_test.go +++ b/src/test/e2e/30_config_file_test.go @@ -26,9 +26,9 @@ func TestConfigFile(t *testing.T) { e2e.CleanFiles(path) // Test the config file environment variable - os.Setenv("ZARF_CONFIG", filepath.Join(dir, config)) + t.Setenv("ZARF_CONFIG", filepath.Join(dir, config)) + defer os.Unsetenv("ZARF_CONFIG") configFileTests(t, dir, path) - os.Unsetenv("ZARF_CONFIG") configFileDefaultTests(t) @@ -39,6 +39,8 @@ func TestConfigFile(t *testing.T) { } func configFileTests(t *testing.T, dir, path string) { + t.Helper() + _, stdErr, err := e2e.Zarf("package", "create", dir, "--confirm") require.NoError(t, err) require.Contains(t, string(stdErr), "This is a zebra and they have stripes") @@ -94,6 +96,7 @@ H4RxbE+FpmsMAUCpdrzvFkc= } func configFileDefaultTests(t *testing.T) { + t.Helper() globalFlags := []string{ "architecture: 509a38f0", @@ -136,7 +139,8 @@ func configFileDefaultTests(t *testing.T) { } // Test remaining default initializers - os.Setenv("ZARF_CONFIG", filepath.Join("src", "test", "zarf-config-test.toml")) + t.Setenv("ZARF_CONFIG", filepath.Join("src", "test", "zarf-config-test.toml")) + defer os.Unsetenv("ZARF_CONFIG") // Test global flags stdOut, _, _ := e2e.Zarf("--help") @@ -161,6 +165,4 @@ func configFileDefaultTests(t *testing.T) { for _, test := range packageDeployFlags { require.Contains(t, string(stdOut), test) } - - os.Unsetenv("ZARF_CONFIG") } diff --git a/src/test/e2e/33_component_webhooks_test.go b/src/test/e2e/33_component_webhooks_test.go index 349e0d0770..cbb8321b08 100644 --- a/src/test/e2e/33_component_webhooks_test.go +++ b/src/test/e2e/33_component_webhooks_test.go @@ -27,12 +27,12 @@ func TestComponentWebhooks(t *testing.T) { gamesPath := fmt.Sprintf("build/zarf-package-dos-games-%s-1.0.0.tar.zst", e2e.Arch) stdOut, stdErr, err = e2e.Zarf("package", "deploy", gamesPath, "--confirm") require.NoError(t, err, stdOut, stdErr) - require.Contains(t, stdErr, "Waiting for webhook 'test-webhook' to complete for component 'baseline'") + require.Contains(t, stdErr, "Waiting for webhook \"test-webhook\" to complete for component \"baseline\"") // Ensure package deployments with the '--skip-webhooks' flag do not wait on webhooks to complete. stdOut, stdErr, err = e2e.Zarf("package", "deploy", gamesPath, "--skip-webhooks", "--confirm") require.NoError(t, err, stdOut, stdErr) - require.NotContains(t, stdErr, "Waiting for webhook 'test-webhook' to complete for component 'baseline'") + require.NotContains(t, stdErr, "Waiting for webhook \"test-webhook\" to complete for component \"baseline\"") // Remove the Pepr webhook package. stdOut, stdErr, err = e2e.Zarf("package", "remove", "component-webhooks", "--confirm") diff --git a/src/test/e2e/36_custom_retries_test.go b/src/test/e2e/36_custom_retries_test.go index e66a6ff435..cbe5428a25 100644 --- a/src/test/e2e/36_custom_retries_test.go +++ b/src/test/e2e/36_custom_retries_test.go @@ -27,7 +27,9 @@ func TestRetries(t *testing.T) { stdOut, stdErr, err = e2e.Zarf("package", "deploy", path.Join(tmpDir, pkgName), "--retries", "2", "--timeout", "3s", "--tmpdir", tmpDir, "--confirm") require.Error(t, err, stdOut, stdErr) - require.Contains(t, stdErr, "Retrying (1/2) in 5s:") - require.Contains(t, stdErr, "Retrying (2/2) in 10s:") - require.Contains(t, stdErr, "unable to install chart after 2 attempts") + require.Contains(t, stdErr, "Retrying in 5s") + require.Contains(t, e2e.StripMessageFormatting(stdErr), "unable to install chart after 2 attempts") + + _, _, err = e2e.Zarf("package", "remove", "dos-games", "--confirm") + require.NoError(t, err) } diff --git a/src/test/e2e/99_yolo_test.go b/src/test/e2e/99_yolo_test.go index a4044c53a9..bce9ab1450 100644 --- a/src/test/e2e/99_yolo_test.go +++ b/src/test/e2e/99_yolo_test.go @@ -5,6 +5,7 @@ package test import ( + "context" "fmt" "net/http" "testing" @@ -35,7 +36,7 @@ func TestYOLOMode(t *testing.T) { c, err := cluster.NewCluster() require.NoError(t, err) - tunnel, err := c.Connect("doom") + tunnel, err := c.Connect(context.Background(), "doom") require.NoError(t, err) defer tunnel.Close() diff --git a/src/test/external/configure-gitea.sh b/src/test/external/configure-gitea.sh index 4a2f1759d8..adbb8da780 100755 --- a/src/test/external/configure-gitea.sh +++ b/src/test/external/configure-gitea.sh @@ -1,4 +1,5 @@ #!/usr/bin/env bash + set -euo pipefail # Retry gitea migrate until the db is ready diff --git a/src/test/external/docker-registry-values.yaml b/src/test/external/docker-registry-values.yaml index 7de626eabc..65b8a1722c 100644 --- a/src/test/external/docker-registry-values.yaml +++ b/src/test/external/docker-registry-values.yaml @@ -3,7 +3,7 @@ image: tag: 2.8.3 pullPolicy: IfNotPresent imagePullSecrets: - - name: private-registry + - name: private-registry service: name: registry diff --git a/src/test/external/ext_in_cluster_test.go b/src/test/external/ext_in_cluster_test.go index a0a4aae7c4..3338b5474f 100644 --- a/src/test/external/ext_in_cluster_test.go +++ b/src/test/external/ext_in_cluster_test.go @@ -81,10 +81,12 @@ func (suite *ExtInClusterTestSuite) Test_0_Mirror() { c, err := cluster.NewCluster() suite.NoError(err) + ctx := context.TODO() + // Check that the registry contains the images we want tunnelReg, err := c.NewTunnel("external-registry", "svc", "external-registry-docker-registry", "", 0, 5000) suite.NoError(err) - _, err = tunnelReg.Connect() + _, err = tunnelReg.Connect(ctx) suite.NoError(err) defer tunnelReg.Close() @@ -101,7 +103,7 @@ func (suite *ExtInClusterTestSuite) Test_0_Mirror() { tunnelGit, err := c.NewTunnel("git-server", "svc", "gitea-http", "", 0, 3000) suite.NoError(err) - _, err = tunnelGit.Connect() + _, err = tunnelGit.Connect(ctx) suite.NoError(err) defer tunnelGit.Close() diff --git a/src/test/external/ext_out_cluster_test.go b/src/test/external/ext_out_cluster_test.go index 7d086bc137..2fd4178cf3 100644 --- a/src/test/external/ext_out_cluster_test.go +++ b/src/test/external/ext_out_cluster_test.go @@ -174,7 +174,8 @@ func (suite *ExtOutClusterTestSuite) Test_2_AuthToPrivateHelmChart() { URL: chartURL, } repoFile.Add(entry) - utils.WriteYaml(repoPath, repoFile, helpers.ReadWriteUser) + err = utils.WriteYaml(repoPath, repoFile, helpers.ReadWriteUser) + suite.NoError(err) err = exec.CmdWithPrint(zarfBinPath, findImageArgs...) suite.NoError(err, "Unable to find images, helm auth likely failed") @@ -192,7 +193,8 @@ func (suite *ExtOutClusterTestSuite) createHelmChartInGitea(baseURL string, user podinfoTarballPath := filepath.Join(tempDir, fmt.Sprintf("podinfo-%s.tgz", podInfoVersion)) suite.NoError(err, "Unable to package chart") - utils.DownloadToFile(fmt.Sprintf("https://stefanprodan.github.io/podinfo/podinfo-%s.tgz", podInfoVersion), podinfoTarballPath, "") + err = utils.DownloadToFile(fmt.Sprintf("https://stefanprodan.github.io/podinfo/podinfo-%s.tgz", podInfoVersion), podinfoTarballPath, "") + suite.NoError(err) url := fmt.Sprintf("%s/api/packages/%s/helm/api/charts", baseURL, username) file, err := os.Open(podinfoTarballPath) diff --git a/src/test/packages/14-index-sha/image-index/zarf.yaml b/src/test/packages/14-index-sha/image-index/zarf.yaml new file mode 100644 index 0000000000..a29c4380b9 --- /dev/null +++ b/src/test/packages/14-index-sha/image-index/zarf.yaml @@ -0,0 +1,9 @@ +kind: ZarfPackageConfig +metadata: + name: image-index + +components: + - name: baseline + required: true + images: + - ghcr.io/defenseunicorns/zarf/agent:v0.32.6@sha256:05a82656df5466ce17c3e364c16792ae21ce68438bfe06eeab309d0520c16b48 diff --git a/src/test/packages/14-index-sha/manifest-list/zarf.yaml b/src/test/packages/14-index-sha/manifest-list/zarf.yaml new file mode 100644 index 0000000000..9d7ad76b20 --- /dev/null +++ b/src/test/packages/14-index-sha/manifest-list/zarf.yaml @@ -0,0 +1,9 @@ +kind: ZarfPackageConfig +metadata: + name: manifest-list + +components: + - name: baseline + required: true + images: + - defenseunicorns/zarf-game@sha256:0b694ca1c33afae97b7471488e07968599f1d2470c629f76af67145ca64428af diff --git a/src/types/component.go b/src/types/component.go index caee8ee680..3c4ffa21c4 100644 --- a/src/types/component.go +++ b/src/types/component.go @@ -81,6 +81,15 @@ func (c ZarfComponent) RequiresCluster() bool { return false } +// IsRequired returns if the component is required or not. +func (c ZarfComponent) IsRequired() bool { + if c.Required != nil { + return *c.Required + } + + return false +} + // ZarfComponentOnlyTarget filters a component to only show it for a given local OS and cluster. type ZarfComponentOnlyTarget struct { LocalOS string `json:"localOS,omitempty" jsonschema:"description=Only deploy component to specified OS,enum=linux,enum=darwin,enum=windows"` diff --git a/src/types/runtime.go b/src/types/runtime.go index 3d9ea5d09a..24a466926e 100644 --- a/src/types/runtime.go +++ b/src/types/runtime.go @@ -38,8 +38,12 @@ type ZarfPackageOptions struct { // ZarfInspectOptions tracks the user-defined preferences during a package inspection. type ZarfInspectOptions struct { - ViewSBOM bool `json:"sbom" jsonschema:"description=View SBOM contents while inspecting the package"` - SBOMOutputDir string `json:"sbomOutput" jsonschema:"description=Location to output an SBOM into after package inspection"` + // View SBOM contents while inspecting the package + ViewSBOM bool + // Location to output an SBOM into after package inspection + SBOMOutputDir string + // ListImages will list the images in the package + ListImages bool } // ZarfFindImagesOptions tracks the user-defined preferences during a prepare find-images search. diff --git a/src/types/validate.go b/src/types/validate.go new file mode 100644 index 0000000000..ae6236227f --- /dev/null +++ b/src/types/validate.go @@ -0,0 +1,335 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: 2021-Present The Zarf Authors + +// Package types contains all the types used by Zarf. +package types + +import ( + "fmt" + "path/filepath" + "regexp" + "slices" + + "github.com/defenseunicorns/pkg/helpers" + "github.com/defenseunicorns/zarf/src/config/lang" +) + +const ( + // ZarfMaxChartNameLength limits helm chart name size to account for K8s/helm limits and zarf prefix + ZarfMaxChartNameLength = 40 +) + +var ( + // IsLowercaseNumberHyphenNoStartHyphen is a regex for lowercase, numbers and hyphens that cannot start with a hyphen. + // https://regex101.com/r/FLdG9G/2 + IsLowercaseNumberHyphenNoStartHyphen = regexp.MustCompile(`^[a-z0-9][a-z0-9\-]*$`).MatchString + // Define allowed OS, an empty string means it is allowed on all operating systems + // same as enums on ZarfComponentOnlyTarget + supportedOS = []string{"linux", "darwin", "windows", ""} +) + +// SupportedOS returns the supported operating systems. +// +// The supported operating systems are: linux, darwin, windows. +// +// An empty string signifies no OS restrictions. +func SupportedOS() []string { + return supportedOS +} + +// Validate runs all validation checks on the package. +func (pkg ZarfPackage) Validate() error { + if pkg.Kind == ZarfInitConfig && pkg.Metadata.YOLO { + return fmt.Errorf(lang.PkgValidateErrInitNoYOLO) + } + + if !IsLowercaseNumberHyphenNoStartHyphen(pkg.Metadata.Name) { + return fmt.Errorf(lang.PkgValidateErrPkgName, pkg.Metadata.Name) + } + + for _, variable := range pkg.Variables { + if err := variable.Validate(); err != nil { + return fmt.Errorf(lang.PkgValidateErrVariable, err) + } + } + + for _, constant := range pkg.Constants { + if err := constant.Validate(); err != nil { + return fmt.Errorf(lang.PkgValidateErrConstant, err) + } + } + + uniqueComponentNames := make(map[string]bool) + groupDefault := make(map[string]string) + groupedComponents := make(map[string][]string) + + if pkg.Metadata.YOLO { + for _, component := range pkg.Components { + if len(component.Images) > 0 { + return fmt.Errorf(lang.PkgValidateErrYOLONoOCI) + } + + if len(component.Repos) > 0 { + return fmt.Errorf(lang.PkgValidateErrYOLONoGit) + } + + if component.Only.Cluster.Architecture != "" { + return fmt.Errorf(lang.PkgValidateErrYOLONoArch) + } + + if len(component.Only.Cluster.Distros) > 0 { + return fmt.Errorf(lang.PkgValidateErrYOLONoDistro) + } + } + } + + for _, component := range pkg.Components { + // ensure component name is unique + if _, ok := uniqueComponentNames[component.Name]; ok { + return fmt.Errorf(lang.PkgValidateErrComponentNameNotUnique, component.Name) + } + uniqueComponentNames[component.Name] = true + + if !IsLowercaseNumberHyphenNoStartHyphen(component.Name) { + return fmt.Errorf(lang.PkgValidateErrComponentName, component.Name) + } + + if !slices.Contains(supportedOS, component.Only.LocalOS) { + return fmt.Errorf(lang.PkgValidateErrComponentLocalOS, component.Name, component.Only.LocalOS, supportedOS) + } + + if component.IsRequired() { + if component.Default { + return fmt.Errorf(lang.PkgValidateErrComponentReqDefault, component.Name) + } + if component.DeprecatedGroup != "" { + return fmt.Errorf(lang.PkgValidateErrComponentReqGrouped, component.Name) + } + } + + uniqueChartNames := make(map[string]bool) + for _, chart := range component.Charts { + // ensure chart name is unique + if _, ok := uniqueChartNames[chart.Name]; ok { + return fmt.Errorf(lang.PkgValidateErrChartNameNotUnique, chart.Name) + } + uniqueChartNames[chart.Name] = true + + if err := chart.Validate(); err != nil { + return fmt.Errorf(lang.PkgValidateErrChart, err) + } + } + + uniqueManifestNames := make(map[string]bool) + for _, manifest := range component.Manifests { + // ensure manifest name is unique + if _, ok := uniqueManifestNames[manifest.Name]; ok { + return fmt.Errorf(lang.PkgValidateErrManifestNameNotUnique, manifest.Name) + } + uniqueManifestNames[manifest.Name] = true + + if err := manifest.Validate(); err != nil { + return fmt.Errorf(lang.PkgValidateErrManifest, err) + } + } + + if err := component.Actions.Validate(); err != nil { + return fmt.Errorf("%q: %w", component.Name, err) + } + + // ensure groups don't have multiple defaults or only one component + if component.DeprecatedGroup != "" { + if component.Default { + if _, ok := groupDefault[component.DeprecatedGroup]; ok { + return fmt.Errorf(lang.PkgValidateErrGroupMultipleDefaults, component.DeprecatedGroup, groupDefault[component.DeprecatedGroup], component.Name) + } + groupDefault[component.DeprecatedGroup] = component.Name + } + groupedComponents[component.DeprecatedGroup] = append(groupedComponents[component.DeprecatedGroup], component.Name) + } + } + + for groupKey, componentNames := range groupedComponents { + if len(componentNames) == 1 { + return fmt.Errorf(lang.PkgValidateErrGroupOneComponent, groupKey, componentNames[0]) + } + } + + return nil +} + +// Validate runs all validation checks on component actions. +func (a ZarfComponentActions) Validate() error { + if err := a.OnCreate.Validate(); err != nil { + return fmt.Errorf(lang.PkgValidateErrAction, err) + } + + if a.OnCreate.HasSetVariables() { + return fmt.Errorf("cannot contain setVariables outside of onDeploy in actions") + } + + if err := a.OnDeploy.Validate(); err != nil { + return fmt.Errorf(lang.PkgValidateErrAction, err) + } + + if a.OnRemove.HasSetVariables() { + return fmt.Errorf("cannot contain setVariables outside of onDeploy in actions") + } + + return nil +} + +// ValidateImportDefinition validates the component trying to be imported. +func (c ZarfComponent) ValidateImportDefinition() error { + path := c.Import.Path + url := c.Import.URL + + // ensure path or url is provided + if path == "" && url == "" { + return fmt.Errorf(lang.PkgValidateErrImportDefinition, c.Name, "neither a path nor a URL was provided") + } + + // ensure path and url are not both provided + if path != "" && url != "" { + return fmt.Errorf(lang.PkgValidateErrImportDefinition, c.Name, "both a path and a URL were provided") + } + + // validation for path + if url == "" && path != "" { + // ensure path is not an absolute path + if filepath.IsAbs(path) { + return fmt.Errorf(lang.PkgValidateErrImportDefinition, c.Name, "path cannot be an absolute path") + } + } + + // validation for url + if url != "" && path == "" { + ok := helpers.IsOCIURL(url) + if !ok { + return fmt.Errorf(lang.PkgValidateErrImportDefinition, c.Name, "URL is not a valid OCI URL") + } + } + + return nil +} + +// HasSetVariables returns true if any of the actions contain setVariables. +func (as ZarfComponentActionSet) HasSetVariables() bool { + check := func(actions []ZarfComponentAction) bool { + for _, action := range actions { + if len(action.SetVariables) > 0 { + return true + } + } + return false + } + + return check(as.Before) || check(as.After) || check(as.OnSuccess) || check(as.OnFailure) +} + +// Validate runs all validation checks on component action sets. +func (as ZarfComponentActionSet) Validate() error { + validate := func(actions []ZarfComponentAction) error { + for _, action := range actions { + if err := action.Validate(); err != nil { + return err + } + } + return nil + } + + if err := validate(as.Before); err != nil { + return err + } + if err := validate(as.After); err != nil { + return err + } + if err := validate(as.OnSuccess); err != nil { + return err + } + return validate(as.OnFailure) +} + +// Validate runs all validation checks on an action. +func (action ZarfComponentAction) Validate() error { + // Validate SetVariable + for _, variable := range action.SetVariables { + if err := variable.Validate(); err != nil { + return err + } + } + + if action.Wait != nil { + // Validate only cmd or wait, not both + if action.Cmd != "" { + return fmt.Errorf(lang.PkgValidateErrActionCmdWait, action.Cmd) + } + + // Validate only cluster or network, not both + if action.Wait.Cluster != nil && action.Wait.Network != nil { + return fmt.Errorf(lang.PkgValidateErrActionClusterNetwork) + } + + // Validate at least one of cluster or network + if action.Wait.Cluster == nil && action.Wait.Network == nil { + return fmt.Errorf(lang.PkgValidateErrActionClusterNetwork) + } + } + + return nil +} + +// Validate runs all validation checks on a chart. +func (chart ZarfChart) Validate() error { + // Don't allow empty names + if chart.Name == "" { + return fmt.Errorf(lang.PkgValidateErrChartNameMissing, chart.Name) + } + + // Helm max release name + if len(chart.Name) > ZarfMaxChartNameLength { + return fmt.Errorf(lang.PkgValidateErrChartName, chart.Name, ZarfMaxChartNameLength) + } + + // Must have a namespace + if chart.Namespace == "" { + return fmt.Errorf(lang.PkgValidateErrChartNamespaceMissing, chart.Name) + } + + // Must have a url or localPath (and not both) + if chart.URL != "" && chart.LocalPath != "" { + return fmt.Errorf(lang.PkgValidateErrChartURLOrPath, chart.Name) + } + + // Must have a url or localPath (and not both) + if chart.URL == "" && chart.LocalPath == "" { + return fmt.Errorf(lang.PkgValidateErrChartURLOrPath, chart.Name) + } + + // Must have a version + if chart.Version == "" { + return fmt.Errorf(lang.PkgValidateErrChartVersion, chart.Name) + } + + return nil +} + +// Validate runs all validation checks on a manifest. +func (manifest ZarfManifest) Validate() error { + // Don't allow empty names + if manifest.Name == "" { + return fmt.Errorf(lang.PkgValidateErrManifestNameMissing, manifest.Name) + } + + // Helm max release name + if len(manifest.Name) > ZarfMaxChartNameLength { + return fmt.Errorf(lang.PkgValidateErrManifestNameLength, manifest.Name, ZarfMaxChartNameLength) + } + + // Require files in manifest + if len(manifest.Files) < 1 && len(manifest.Kustomizations) < 1 { + return fmt.Errorf(lang.PkgValidateErrManifestFileOrKustomize, manifest.Name) + } + + return nil +} diff --git a/zarf-config.toml b/zarf-config.toml index 8440f1538e..eb549dac54 100644 --- a/zarf-config.toml +++ b/zarf-config.toml @@ -5,9 +5,10 @@ agent_image = 'defenseunicorns/zarf/agent' agent_image_tag = 'local' # Tag for the zarf injector binary to use -injector_version = '2023-08-02' -injector_amd64_shasum = '91de0768855ee2606a4f85a92bb480ff3a14ca205fd8d05eb397c18e15aa0247' -injector_arm64_shasum = '663df681deea957b0ec53538eab221691a83de8e95d86b8a29008af711934bee' +injector_version = '2024-05-15' +injector_amd64_shasum = '1b34519ac30daf0e5a4a2f0a0766dbcd0852c0b5364b35576eea4ac9e22d9e82' +injector_arm64_shasum = 'ca20f427f9cf91ff42646a785c4772be5892a6752fa14924c5085b2d0109b008' + # The image reference to use for the registry that Zarf deploys into the cluster registry_image_domain = '' diff --git a/zarf.schema.json b/zarf.schema.json index 633e73110e..27a8a89a4c 100644 --- a/zarf.schema.json +++ b/zarf.schema.json @@ -1,11 +1,8 @@ { - "$schema": "http://json-schema.org/draft-04/schema#", - "$ref": "#/definitions/ZarfPackage", - "definitions": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/defenseunicorns/zarf/src/types/zarf-package", + "$defs": { "BigBang": { - "required": [ - "version" - ], "properties": { "version": { "type": "string", @@ -36,19 +33,18 @@ }, "additionalProperties": false, "type": "object", + "required": [ + "version" + ], "patternProperties": { "^x-": {} } }, "Constant": { - "required": [ - "name", - "value" - ], "properties": { "name": { - "pattern": "^[A-Z0-9_]+$", "type": "string", + "pattern": "^[A-Z0-9_]+$", "description": "The name to be used for the constant" }, "value": { @@ -70,6 +66,10 @@ }, "additionalProperties": false, "type": "object", + "required": [ + "name", + "value" + ], "patternProperties": { "^x-": {} } @@ -117,12 +117,31 @@ } }, "InteractiveVariable": { - "required": [ - "Variable" - ], "properties": { - "Variable": { - "$ref": "#/definitions/Variable" + "name": { + "type": "string", + "pattern": "^[A-Z0-9_]+$", + "description": "The name to be used for the variable" + }, + "sensitive": { + "type": "boolean", + "description": "Whether to mark this variable as sensitive to not print it in the log" + }, + "autoIndent": { + "type": "boolean", + "description": "Whether to automatically indent the variable's value (if multiline) when templating. Based on the number of chars before the start of ###ZARF_VAR_." + }, + "pattern": { + "type": "string", + "description": "An optional regex pattern that a variable value must match before a package deployment can continue." + }, + "type": { + "type": "string", + "enum": [ + "raw", + "file" + ], + "description": "Changes the handling of a variable to load contents differently (i.e. from a file rather than as a raw variable - templated files should be kept below 1 MiB)" }, "description": { "type": "string", @@ -139,6 +158,9 @@ }, "additionalProperties": false, "type": "object", + "required": [ + "name" + ], "patternProperties": { "^x-": {} } @@ -187,13 +209,10 @@ } }, "Variable": { - "required": [ - "name" - ], "properties": { "name": { - "pattern": "^[A-Z0-9_]+$", "type": "string", + "pattern": "^[A-Z0-9_]+$", "description": "The name to be used for the variable" }, "sensitive": { @@ -209,28 +228,24 @@ "description": "An optional regex pattern that a variable value must match before a package deployment can continue." }, "type": { + "type": "string", "enum": [ "raw", "file" ], - "type": "string", "description": "Changes the handling of a variable to load contents differently (i.e. from a file rather than as a raw variable - templated files should be kept below 1 MiB)" } }, "additionalProperties": false, "type": "object", + "required": [ + "name" + ], "patternProperties": { "^x-": {} } }, "ZarfBuildData": { - "required": [ - "terminal", - "user", - "architecture", - "timestamp", - "version" - ], "properties": { "terminal": { "type": "string", @@ -260,10 +275,8 @@ "description": "Any migrations that have been run on this package" }, "registryOverrides": { - "patternProperties": { - ".*": { - "type": "string" - } + "additionalProperties": { + "type": "string" }, "type": "object", "description": "Any registry domains that were overridden on package create when pulling images" @@ -294,15 +307,18 @@ }, "additionalProperties": false, "type": "object", + "required": [ + "terminal", + "user", + "architecture", + "timestamp", + "version" + ], "patternProperties": { "^x-": {} } }, "ZarfChart": { - "required": [ - "name", - "namespace" - ], "properties": { "name": { "type": "string", @@ -357,8 +373,7 @@ }, "variables": { "items": { - "$schema": "http://json-schema.org/draft-04/schema#", - "$ref": "#/definitions/ZarfChartVariable" + "$ref": "#/$defs/ZarfChartVariable" }, "type": "array", "description": "[alpha] List of variables to set in the Helm chart" @@ -366,20 +381,19 @@ }, "additionalProperties": false, "type": "object", + "required": [ + "name", + "namespace" + ], "patternProperties": { "^x-": {} } }, "ZarfChartVariable": { - "required": [ - "name", - "description", - "path" - ], "properties": { "name": { - "pattern": "^[A-Z0-9_]+$", "type": "string", + "pattern": "^[A-Z0-9_]+$", "description": "The name of the variable" }, "description": { @@ -393,18 +407,20 @@ }, "additionalProperties": false, "type": "object", + "required": [ + "name", + "description", + "path" + ], "patternProperties": { "^x-": {} } }, "ZarfComponent": { - "required": [ - "name" - ], "properties": { "name": { - "pattern": "^[a-z0-9\\-]*[a-z0-9]$", "type": "string", + "pattern": "^[a-z0-9\\-]*[a-z0-9]$", "description": "The name of the component" }, "description": { @@ -420,8 +436,7 @@ "description": "Do not prompt user to install this component" }, "only": { - "$schema": "http://json-schema.org/draft-04/schema#", - "$ref": "#/definitions/ZarfComponentOnlyTarget", + "$ref": "#/$defs/ZarfComponentOnlyTarget", "description": "Filter when this component is included in package creation or deployment" }, "group": { @@ -433,38 +448,33 @@ "description": "[Deprecated] Specify a path to a public key to validate signed online resources. This will be removed in Zarf v1.0.0." }, "import": { - "$schema": "http://json-schema.org/draft-04/schema#", - "$ref": "#/definitions/ZarfComponentImport", + "$ref": "#/$defs/ZarfComponentImport", "description": "Import a component from another Zarf package" }, "manifests": { "items": { - "$schema": "http://json-schema.org/draft-04/schema#", - "$ref": "#/definitions/ZarfManifest" + "$ref": "#/$defs/ZarfManifest" }, "type": "array", "description": "Kubernetes manifests to be included in a generated Helm chart on package deploy" }, "charts": { "items": { - "$schema": "http://json-schema.org/draft-04/schema#", - "$ref": "#/definitions/ZarfChart" + "$ref": "#/$defs/ZarfChart" }, "type": "array", "description": "Helm charts to install during package deploy" }, "dataInjections": { "items": { - "$schema": "http://json-schema.org/draft-04/schema#", - "$ref": "#/definitions/ZarfDataInjection" + "$ref": "#/$defs/ZarfDataInjection" }, "type": "array", "description": "Datasets to inject into a container in the target cluster" }, "files": { "items": { - "$schema": "http://json-schema.org/draft-04/schema#", - "$ref": "#/definitions/ZarfFile" + "$ref": "#/$defs/ZarfFile" }, "type": "array", "description": "Files or folders to place on disk during package deployment" @@ -484,23 +494,23 @@ "description": "List of git repos to include in the package" }, "extensions": { - "$schema": "http://json-schema.org/draft-04/schema#", - "$ref": "#/definitions/ZarfComponentExtensions", + "$ref": "#/$defs/ZarfComponentExtensions", "description": "Extend component functionality with additional features" }, "scripts": { - "$schema": "http://json-schema.org/draft-04/schema#", - "$ref": "#/definitions/DeprecatedZarfComponentScripts", + "$ref": "#/$defs/DeprecatedZarfComponentScripts", "description": "[Deprecated] (replaced by actions) Custom commands to run before or after package deployment. This will be removed in Zarf v1.0.0." }, "actions": { - "$schema": "http://json-schema.org/draft-04/schema#", - "$ref": "#/definitions/ZarfComponentActions", + "$ref": "#/$defs/ZarfComponentActions", "description": "Custom commands to run at various stages of a package lifecycle" } }, "additionalProperties": false, "type": "object", + "required": [ + "name" + ], "patternProperties": { "^x-": {} } @@ -535,18 +545,17 @@ "description": "The command to run. Must specify either cmd or wait for the action to do anything." }, "shell": { - "$ref": "#/definitions/Shell", + "$ref": "#/$defs/Shell", "description": "(cmd only) Indicates a preference for a shell for the provided cmd to be executed in on supported operating systems" }, "setVariable": { - "pattern": "^[A-Z0-9_]+$", "type": "string", + "pattern": "^[A-Z0-9_]+$", "description": "[Deprecated] (replaced by setVariables) (onDeploy/cmd only) The name of a variable to update with the output of the command. This variable will be available to all remaining actions and components in the package. This will be removed in Zarf v1.0.0" }, "setVariables": { "items": { - "$schema": "http://json-schema.org/draft-04/schema#", - "$ref": "#/definitions/Variable" + "$ref": "#/$defs/Variable" }, "type": "array", "description": "(onDeploy/cmd only) An array of variables to update with the output of the command. These variables will be available to all remaining actions and components in the package." @@ -556,8 +565,7 @@ "description": "Description of the action to be displayed during package execution instead of the command" }, "wait": { - "$schema": "http://json-schema.org/draft-04/schema#", - "$ref": "#/definitions/ZarfComponentActionWait", + "$ref": "#/$defs/ZarfComponentActionWait", "description": "Wait for a condition to be met before continuing. Must specify either cmd or wait for the action. See the 'zarf tools wait-for' command for more info." } }, @@ -593,8 +601,7 @@ "description": "Additional environment variables for commands" }, "shell": { - "$schema": "http://json-schema.org/draft-04/schema#", - "$ref": "#/definitions/Shell", + "$ref": "#/$defs/Shell", "description": "(cmd only) Indicates a preference for a shell for the provided cmd to be executed in on supported operating systems" } }, @@ -607,35 +614,33 @@ "ZarfComponentActionSet": { "properties": { "defaults": { - "$schema": "http://json-schema.org/draft-04/schema#", - "$ref": "#/definitions/ZarfComponentActionDefaults", + "$ref": "#/$defs/ZarfComponentActionDefaults", "description": "Default configuration for all actions in this set" }, "before": { "items": { - "$schema": "http://json-schema.org/draft-04/schema#", - "$ref": "#/definitions/ZarfComponentAction" + "$ref": "#/$defs/ZarfComponentAction" }, "type": "array", "description": "Actions to run at the start of an operation" }, "after": { "items": { - "$ref": "#/definitions/ZarfComponentAction" + "$ref": "#/$defs/ZarfComponentAction" }, "type": "array", "description": "Actions to run at the end of an operation" }, "onSuccess": { "items": { - "$ref": "#/definitions/ZarfComponentAction" + "$ref": "#/$defs/ZarfComponentAction" }, "type": "array", "description": "Actions to run if all operations succeed" }, "onFailure": { "items": { - "$ref": "#/definitions/ZarfComponentAction" + "$ref": "#/$defs/ZarfComponentAction" }, "type": "array", "description": "Actions to run if all operations fail" @@ -650,13 +655,11 @@ "ZarfComponentActionWait": { "properties": { "cluster": { - "$schema": "http://json-schema.org/draft-04/schema#", - "$ref": "#/definitions/ZarfComponentActionWaitCluster", + "$ref": "#/$defs/ZarfComponentActionWaitCluster", "description": "Wait for a condition to be met in the cluster before continuing. Only one of cluster or network can be specified." }, "network": { - "$schema": "http://json-schema.org/draft-04/schema#", - "$ref": "#/definitions/ZarfComponentActionWaitNetwork", + "$ref": "#/$defs/ZarfComponentActionWaitNetwork", "description": "Wait for a condition to be met on the network before continuing. Only one of cluster or network can be specified." } }, @@ -667,10 +670,6 @@ } }, "ZarfComponentActionWaitCluster": { - "required": [ - "kind", - "name" - ], "properties": { "kind": { "type": "string", @@ -703,23 +702,23 @@ }, "additionalProperties": false, "type": "object", + "required": [ + "kind", + "name" + ], "patternProperties": { "^x-": {} } }, "ZarfComponentActionWaitNetwork": { - "required": [ - "protocol", - "address" - ], "properties": { "protocol": { + "type": "string", "enum": [ "tcp", "http", "https" ], - "type": "string", "description": "The protocol to wait for" }, "address": { @@ -741,6 +740,10 @@ }, "additionalProperties": false, "type": "object", + "required": [ + "protocol", + "address" + ], "patternProperties": { "^x-": {} } @@ -748,16 +751,15 @@ "ZarfComponentActions": { "properties": { "onCreate": { - "$schema": "http://json-schema.org/draft-04/schema#", - "$ref": "#/definitions/ZarfComponentActionSet", + "$ref": "#/$defs/ZarfComponentActionSet", "description": "Actions to run during package creation" }, "onDeploy": { - "$ref": "#/definitions/ZarfComponentActionSet", + "$ref": "#/$defs/ZarfComponentActionSet", "description": "Actions to run during package deployment" }, "onRemove": { - "$ref": "#/definitions/ZarfComponentActionSet", + "$ref": "#/$defs/ZarfComponentActionSet", "description": "Actions to run during package removal" } }, @@ -770,8 +772,7 @@ "ZarfComponentExtensions": { "properties": { "bigbang": { - "$schema": "http://json-schema.org/draft-04/schema#", - "$ref": "#/definitions/BigBang", + "$ref": "#/$defs/BigBang", "description": "Configurations for installing Big Bang and Flux in the cluster" } }, @@ -792,8 +793,8 @@ "description": "The relative path to a directory containing a zarf.yaml to import from" }, "url": { - "pattern": "^oci://.*$", "type": "string", + "pattern": "^oci://.*$", "description": "[beta] The URL to a Zarf package to import via OCI" } }, @@ -806,16 +807,20 @@ "ZarfComponentOnlyCluster": { "properties": { "architecture": { + "type": "string", "enum": [ "amd64", "arm64" ], - "type": "string", "description": "Only create and deploy to clusters of the given architecture" }, "distros": { "items": { - "type": "string" + "type": "string", + "examples": [ + "k3s", + "eks" + ] }, "type": "array", "description": "A list of kubernetes distros this package works with (Reserved for future use)" @@ -830,17 +835,16 @@ "ZarfComponentOnlyTarget": { "properties": { "localOS": { + "type": "string", "enum": [ "linux", "darwin", "windows" ], - "type": "string", "description": "Only deploy component to specified OS" }, "cluster": { - "$schema": "http://json-schema.org/draft-04/schema#", - "$ref": "#/definitions/ZarfComponentOnlyCluster", + "$ref": "#/$defs/ZarfComponentOnlyCluster", "description": "Only deploy component to specified clusters" }, "flavor": { @@ -855,12 +859,6 @@ } }, "ZarfContainerTarget": { - "required": [ - "namespace", - "selector", - "container", - "path" - ], "properties": { "namespace": { "type": "string", @@ -884,23 +882,24 @@ }, "additionalProperties": false, "type": "object", + "required": [ + "namespace", + "selector", + "container", + "path" + ], "patternProperties": { "^x-": {} } }, "ZarfDataInjection": { - "required": [ - "source", - "target" - ], "properties": { "source": { "type": "string", "description": "Either a path to a local folder/file or a remote URL of a file to inject into the given target pod + container" }, "target": { - "$schema": "http://json-schema.org/draft-04/schema#", - "$ref": "#/definitions/ZarfContainerTarget", + "$ref": "#/$defs/ZarfContainerTarget", "description": "The target pod + container to inject the data into" }, "compress": { @@ -910,15 +909,15 @@ }, "additionalProperties": false, "type": "object", + "required": [ + "source", + "target" + ], "patternProperties": { "^x-": {} } }, "ZarfFile": { - "required": [ - "source", - "target" - ], "properties": { "source": { "type": "string", @@ -950,14 +949,15 @@ }, "additionalProperties": false, "type": "object", + "required": [ + "source", + "target" + ], "patternProperties": { "^x-": {} } }, "ZarfManifest": { - "required": [ - "name" - ], "properties": { "name": { "type": "string", @@ -992,18 +992,18 @@ }, "additionalProperties": false, "type": "object", + "required": [ + "name" + ], "patternProperties": { "^x-": {} } }, "ZarfMetadata": { - "required": [ - "name" - ], "properties": { "name": { - "pattern": "^[a-z0-9\\-]*[a-z0-9]$", "type": "string", + "pattern": "^[a-z0-9\\-]*[a-z0-9]$", "description": "Name to identify this Zarf package" }, "description": { @@ -1064,65 +1064,61 @@ }, "additionalProperties": false, "type": "object", - "patternProperties": { - "^x-": {} - } - }, - "ZarfPackage": { "required": [ - "kind", - "components" + "name" ], - "properties": { - "kind": { - "enum": [ - "ZarfInitConfig", - "ZarfPackageConfig" - ], - "type": "string", - "description": "The kind of Zarf package", - "default": "ZarfPackageConfig" - }, - "metadata": { - "$schema": "http://json-schema.org/draft-04/schema#", - "$ref": "#/definitions/ZarfMetadata", - "description": "Package metadata" - }, - "build": { - "$schema": "http://json-schema.org/draft-04/schema#", - "$ref": "#/definitions/ZarfBuildData", - "description": "Zarf-generated package build data" - }, - "components": { - "items": { - "$schema": "http://json-schema.org/draft-04/schema#", - "$ref": "#/definitions/ZarfComponent" - }, - "type": "array", - "description": "List of components to deploy in this package" - }, - "constants": { - "items": { - "$schema": "http://json-schema.org/draft-04/schema#", - "$ref": "#/definitions/Constant" - }, - "type": "array", - "description": "Constant template values applied on deploy for K8s resources" - }, - "variables": { - "items": { - "$schema": "http://json-schema.org/draft-04/schema#", - "$ref": "#/definitions/InteractiveVariable" - }, - "type": "array", - "description": "Variable template values applied on deploy for K8s resources" - } - }, - "additionalProperties": false, - "type": "object", "patternProperties": { "^x-": {} } } + }, + "properties": { + "kind": { + "type": "string", + "enum": [ + "ZarfInitConfig", + "ZarfPackageConfig" + ], + "description": "The kind of Zarf package", + "default": "ZarfPackageConfig" + }, + "metadata": { + "$ref": "#/$defs/ZarfMetadata", + "description": "Package metadata" + }, + "build": { + "$ref": "#/$defs/ZarfBuildData", + "description": "Zarf-generated package build data" + }, + "components": { + "items": { + "$ref": "#/$defs/ZarfComponent" + }, + "type": "array", + "description": "List of components to deploy in this package" + }, + "constants": { + "items": { + "$ref": "#/$defs/Constant" + }, + "type": "array", + "description": "Constant template values applied on deploy for K8s resources" + }, + "variables": { + "items": { + "$ref": "#/$defs/InteractiveVariable" + }, + "type": "array", + "description": "Variable template values applied on deploy for K8s resources" + } + }, + "additionalProperties": false, + "type": "object", + "required": [ + "kind", + "components" + ], + "patternProperties": { + "^x-": {} } }