``` ├── .github/ ├── ISSUE_TEMPLATE/ ├── bug-report.yaml ├── config.yml ├── feature-request.yaml ├── dependabot.yaml ├── workflows/ ├── artifacthub.yaml ├── e2e.yaml ├── go.yaml ├── helm.yaml ├── release.yaml ├── .gitignore ├── .golangci.yaml ├── .goreleaser.yaml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── charts/ ├── spegel/ ├── .helmignore ├── Chart.yaml ├── README.md ├── README.md.gotmpl ├── artifacthub-repo.yml ├── monitoring/ ├── grafana-dashboard.json ├── templates/ ├── _helpers.tpl ├── daemonset.yaml ├── grafana-dashboard.yaml ├── post-delete-hook.yaml ├── rbac.yaml ├── service.yaml ├── servicemonitor.yaml ├── verticalpodautoscaler.yaml ├── values.yaml ├── go.mod ├── go.sum ├── internal/ ├── channel/ ├── channel.go ├── cleanup/ ├── cleanup.go ├── cleanup_test.go ├── mux/ ├── mux.go ├── mux_test.go ├── response.go ├── response_test.go ├── web/ ├── templates/ ├── index.html ├── measure.html ├── stats.html ├── web.go ├── web_test.go ├── main.go ├── pkg/ ├── metrics/ ├── metrics.go ├── metrics_test.go ├── oci/ ├── containerd.go ├── containerd_test.go ``` ## /.github/ISSUE_TEMPLATE/bug-report.yaml ```yaml path="/.github/ISSUE_TEMPLATE/bug-report.yaml" name: Bug Report description: Create a report to help improve Spegel labels: ["bug"] body: - type: markdown attributes: value: | Thank you for taking the time to fill ot this bug report! Please read the [FAQ](https://spegel.dev/docs/faq/) and check existing issues before submitting a new issue. - type: input attributes: label: Spegel version placeholder: eg. v0.0.16 validations: required: true - type: input attributes: label: Kubernetes distribution placeholder: eg. AKS, EKS, K3S, Kubeadm... validations: required: true - type: input attributes: label: Kubernetes version placeholder: eg. v1.29.0 validations: required: true - type: input attributes: label: CNI placeholder: eg. Calico, Cilium, Azure CNI... validations: required: true - type: textarea attributes: label: Describe the bug description: A clear description of what the bug is. validations: required: true ``` ## /.github/ISSUE_TEMPLATE/config.yml ```yml path="/.github/ISSUE_TEMPLATE/config.yml" blank_issues_enabled: true ``` ## /.github/ISSUE_TEMPLATE/feature-request.yaml ```yaml path="/.github/ISSUE_TEMPLATE/feature-request.yaml" name: Feature Request description: Suggest a new feature for Spegel labels: ["enhancement"] body: - type: markdown attributes: value: | Thank you for creating a feature request! Please check existing issues before submitting. - type: textarea attributes: label: Describe the problem to be solved description: A clear description of the problem that needs to be addressed by this feature request. validations: required: true - type: textarea attributes: label: Proposed solution to the problem description: A clear description of the solution or multiple possible solutions to implement this feature request. validations: required: false ``` ## /.github/dependabot.yaml ```yaml path="/.github/dependabot.yaml" version: 2 updates: - package-ecosystem: "github-actions" directory: "/" schedule: interval: "daily" open-pull-requests-limit: 15 - package-ecosystem: "gomod" directory: "/" schedule: interval: "daily" open-pull-requests-limit: 15 groups: k8s: patterns: - "k8s.io/*" ``` ## /.github/workflows/artifacthub.yaml ```yaml path="/.github/workflows/artifacthub.yaml" name: artifacthub on: push: branches: ["main"] paths: - "charts/spegel/artifacthub-repo.yml" permissions: contents: read packages: write defaults: run: shell: bash jobs: release: runs-on: ubuntu-latest steps: - name: Clone repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2 with: submodules: true - name: Login to GitHub Container Registry uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 #v3.4.0 with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - name: Setup ORAS uses: oras-project/setup-oras@5c0b487ce3fe0ce3ab0d034e63669e426e294e4d #v1.2.2 - name: Push Artifact Hub metadata run: oras push ghcr.io/spegel-org/helm-charts/spegel:artifacthub.io --config /dev/null:application/vnd.cncf.artifacthub.config.v1+yaml charts/spegel/artifacthub-repo.yml:application/vnd.cncf.artifacthub.repository-metadata.layer.v1.yaml ``` ## /.github/workflows/e2e.yaml ```yaml path="/.github/workflows/e2e.yaml" name: e2e on: pull_request: permissions: contents: read defaults: run: shell: bash jobs: test: runs-on: ubuntu-latest strategy: matrix: include: - proxy-mode: iptables ip-family: ipv4 - proxy-mode: iptables ip-family: ipv6 - proxy-mode: ipvs ip-family: ipv4 steps: - name: Checkout uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2 - name: Setup Go uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b #v5.4.0 with: go-version-file: go.mod - name: Setup GoReleaser uses: goreleaser/goreleaser-action@v6 with: install-only: true - name: Setup Kind uses: helm/kind-action@a1b0e391336a6ee6713a0583f8c6240d70863de3 #v1.12.0 with: version: v0.27.0 install_only: true - name: Run e2e run: make test-e2e E2E_PROXY_MODE=${{ matrix.proxy-mode }} E2E_IP_FAMILY=${{ matrix.ip-family }} ``` ## /.github/workflows/go.yaml ```yaml path="/.github/workflows/go.yaml" name: go on: pull_request: permissions: contents: read defaults: run: shell: bash jobs: lint: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2 - name: Setup Go uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b #v5.4.0 with: go-version-file: go.mod - name: Setup golangci-lint uses: golangci/golangci-lint-action@1481404843c368bc19ca9406f87d6e0fc97bdcfd #v7.0.0 unit: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2 - name: Setup Go uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b #v5.4.0 with: go-version-file: go.mod - name: Run tests run: go test -race -coverprofile=coverage.txt -covermode=atomic ./... - name: Upload coverage reports to Codecov uses: codecov/codecov-action@ad3126e916f78f00edff4ed0317cf185271ccc2d #v5.4.2 with: token: ${{ secrets.CODECOV_TOKEN }} ``` ## /.github/workflows/helm.yaml ```yaml path="/.github/workflows/helm.yaml" name: helm on: pull_request: permissions: contents: read defaults: run: shell: bash jobs: docs: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2 - name: Setup Go uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b #v5.4.0 with: go-version-file: go.mod - name: Run helm-docs run: make helm-docs - name: Check if working tree is dirty run: | if [[ $(git diff --stat) != '' ]]; then git diff echo 'run make helm-docs and commit changes' exit 1 fi ``` ## /.github/workflows/release.yaml ```yaml path="/.github/workflows/release.yaml" name: release on: push: tags: - 'v*' permissions: contents: write packages: write id-token: write defaults: run: shell: bash jobs: release: runs-on: ubuntu-latest steps: - name: Clone repo uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2 - name: Setup Cosign uses: sigstore/cosign-installer@3454372f43399081ed03b604cb2d021dabca52bb #v3.8.2 - name: Setup Helm uses: azure/setup-helm@b9e51907a09c216f16ebe8536097933489208112 #v4.3.0 with: version: v3.17.3 - name: Setup Docker Buildx id: buildx uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 #v3.10.0 - name: Setup yq uses: frenck/action-setup-yq@c4b5be8b4a215c536a41d436757d9feb92836d4f #v1.0.2 - name: Login to GitHub Container Registry uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 #v3.4.0 with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - name: Prepare version id: prep run: | VERSION=sha-${GITHUB_SHA::8} if [[ $GITHUB_REF == refs/tags/* ]]; then VERSION=${GITHUB_REF/refs\/tags\//} fi echo "Refer to the [Changelog](https://github.com/spegel-org/spegel/blob/main/CHANGELOG.md#${VERSION//.}) for list of changes." > ${{ runner.temp }}/NOTES.txt echo "VERSION=${VERSION}" >> $GITHUB_OUTPUT - name: Run GoReleaser uses: goreleaser/goreleaser-action@v6 with: args: release --clean --release-notes ${{ runner.temp }}/NOTES.txt env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Generate images meta id: meta uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 #v5.7.0 with: images: ghcr.io/${{ github.repository_owner }}/spegel tags: type=raw,value=${{ steps.prep.outputs.VERSION }} - name: Publish multi-arch image uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 #v6.16.0 id: build with: push: true builder: ${{ steps.buildx.outputs.name }} context: . file: ./Dockerfile platforms: linux/amd64,linux/arm/v7,linux/arm64 tags: ghcr.io/${{ github.repository_owner }}/spegel:${{ steps.prep.outputs.VERSION }} labels: ${{ steps.meta.outputs.labels }} - name: Sign the image with Cosign run: | cosign sign --yes ghcr.io/${{ github.repository_owner }}/spegel@${{ steps.build.outputs.DIGEST }} - name: Publish Helm chart to GHCR id: helm run: | HELM_VERSION=${{ steps.prep.outputs.VERSION }} HELM_VERSION=${HELM_VERSION#v} rm charts/spegel/artifacthub-repo.yml yq -i '.image.digest = "${{ steps.build.outputs.DIGEST }}"' charts/spegel/values.yaml helm package --app-version ${{ steps.prep.outputs.VERSION }} --version ${HELM_VERSION} charts/spegel helm push spegel-${HELM_VERSION}.tgz oci://ghcr.io/${{ github.repository_owner }}/helm-charts 2> .digest DIGEST=$(cat .digest | awk -F "[, ]+" '/Digest/{print $NF}') echo "DIGEST=${DIGEST}" >> $GITHUB_OUTPUT - name: Sign the Helm chart with Cosign run: | cosign sign --yes ghcr.io/${{ github.repository_owner }}/helm-charts/spegel@${{ steps.helm.outputs.DIGEST }} ``` ## /.gitignore ```gitignore path="/.gitignore" # If you prefer the allow list template instead of the deny list, see community template: # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore # # Binaries for programs and plugins *.exe *.exe~ *.dll *.so *.dylib # Test binary, built with `go test -c` *.test # Output of the go coverage tool, specifically when used with LiteIDE *.out # Dependency directories (remove the comment below to include it) # vendor/ # Go workspace file go.work # Added by goreleaser init: dist/ ``` ## /.golangci.yaml ```yaml path="/.golangci.yaml" version: "2" linters: default: none enable: - bodyclose - errcheck - gocritic - govet - importas - ineffassign - ireturn - misspell - nolintlint - paralleltest - perfsprint - staticcheck - testifylint - unused settings: errcheck: disable-default-exclusions: true check-type-assertions: true check-blank: true gocritic: enable-all: true disabled-checks: - importShadow - hugeParam - rangeValCopy - whyNoLint - unnamedResult - httpNoBody govet: disable: - shadow enable-all: true importas: alias: - pkg: io/fs alias: iofs - pkg: github.com/go-logr/logr/testing alias: tlog - pkg: github.com/pelletier/go-toml/v2/unstable alias: tomlu - pkg: github.com/multiformats/go-multiaddr/net alias: manet - pkg: github.com/multiformats/go-multiaddr alias: ma - pkg: github.com/multiformats/go-multicodec alias: mc - pkg: github.com/multiformats/go-multihash alias: mh - pkg: github.com/ipfs/go-cid alias: cid - pkg: github.com/libp2p/go-libp2p-kad-dht alias: dht - pkg: github.com/libp2p/go-libp2p/p2p/net/mock alias: mocknet - pkg: go.etcd.io/bbolt alias: bolt - pkg: k8s.io/cri-api/pkg/apis/runtime/v1 alias: runtimeapi - pkg: github.com/containerd/containerd/api/events alias: eventtypes - pkg: github.com/opencontainers/go-digest alias: digest - pkg: github.com/opencontainers/image-spec/specs-go/v1 alias: ocispec - pkg: k8s.io/apimachinery/pkg/util/version alias: utilversion no-extra-aliases: true nolintlint: require-explanation: true require-specific: true perfsprint: strconcat: false testifylint: enable-all: true exclusions: generated: lax presets: - comments - common-false-positives - legacy - std-error-handling paths: - third_party$ - builtin$ - examples$ formatters: enable: - goimports exclusions: generated: lax paths: - third_party$ - builtin$ - examples$ ``` ## /.goreleaser.yaml ```yaml path="/.goreleaser.yaml" version: 2 project_name: spegel before: hooks: - go mod tidy builds: - goos: - linux goarch: - amd64 - arm - arm64 goarm: - 7 env: - CGO_ENABLED=0 flags: - -trimpath - -a no_unique_dist_dir: true binary: "{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}/{{ .ProjectName }}" archives: - formats: [tar.gz] name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}" files: - none* ``` ## /CHANGELOG.md # Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## Unreleased ### Added ### Changed - [#852](https://github.com/spegel-org/spegel/pull/852) Remove use of Afero in Containerd config. - [#854](https://github.com/spegel-org/spegel/pull/854) Implement unit tests for cleanup logic . ### Deprecated ### Removed ### Fixed ### Security ## v0.2.0 ### Added - [#832](https://github.com/spegel-org/spegel/pull/832) Add delete hook to cleanup configuration from host when chart is uninstalled. - [#846](https://github.com/spegel-org/spegel/pull/846) Build binaries as part of the release process. - [#848](https://github.com/spegel-org/spegel/pull/848) Add support for a static bootstrapper. - [#850](https://github.com/spegel-org/spegel/pull/850) Persist libp2p key to disk when data directory is set. ### Changed - [#812](https://github.com/spegel-org/spegel/pull/812) Upgrade to Go 1.24.1 and switch to use go tool for helm docs. - [#725](https://github.com/spegel-org/spegel/pull/725) Remove use of httputil reverse proxy. - [#820](https://github.com/spegel-org/spegel/pull/820) Switch to using new test context. - [#827](https://github.com/spegel-org/spegel/pull/827) Add p2p options to router for optional configuration. - [#835](https://github.com/spegel-org/spegel/pull/835) Refactor registry config to align with router config. - [#847](https://github.com/spegel-org/spegel/pull/847) Set default values for address arguments. ### Removed - [#831](https://github.com/spegel-org/spegel/pull/831) Remove local address check when resolving peers. ### Fixed - [#824](https://github.com/spegel-org/spegel/pull/824) Fix improper image string formatting and expand tests. - [#825](https://github.com/spegel-org/spegel/pull/825) Fix gopls modernize warnings. - [#826](https://github.com/spegel-org/spegel/pull/826) Standardize router channel naming. - [#844](https://github.com/spegel-org/spegel/pull/844) Fix p2p option naming to conform with the standard. - [#849](https://github.com/spegel-org/spegel/pull/849) Fix libp2p options so field is exported in configuration. ## v0.1.1 ### Fixed - [#807](https://github.com/spegel-org/spegel/pull/807) Update golangci lint and fix new issues. - [#810](https://github.com/spegel-org/spegel/pull/810) Increase timeout to avoid flakiness in conformance tests. - [#806](https://github.com/spegel-org/spegel/pull/806) Fix verification of Containerd configuration with suffixes. ## v0.1.0 ### Added - [#717](https://github.com/spegel-org/spegel/pull/717) Extend tests for distribution. - [#753](https://github.com/spegel-org/spegel/pull/753) Set GOMAXPROCS and GOMEMLIMIT when limits are set. - [#792](https://github.com/spegel-org/spegel/pull/792) Add dev deploy recipe to simplify local development. - [#791](https://github.com/spegel-org/spegel/pull/791) Add debug view to help validating Spegel. ### Changed - [#747](https://github.com/spegel-org/spegel/pull/747) Update Go to 1.23.6. - [#750](https://github.com/spegel-org/spegel/pull/750) Rename append mirrors to prepend existing. - [#373](https://github.com/spegel-org/spegel/pull/373) Apply mirror configuration on all registires by default. - [#762](https://github.com/spegel-org/spegel/pull/762) Set appropriate buckets for response size - [#778](https://github.com/spegel-org/spegel/pull/778) Replace interface{} with any alias. - [#784](https://github.com/spegel-org/spegel/pull/784) Refactor distribution and move to OCI package. - [#787](https://github.com/spegel-org/spegel/pull/787) Refactor OCI image to allow parsing without digest. - [#794](https://github.com/spegel-org/spegel/pull/794) Set default memory request and limit in Helm chart. ### Removed - [#796](https://github.com/spegel-org/spegel/pull/796) Remove name from OCI image struct. - [#799](https://github.com/spegel-org/spegel/pull/799) Remove Kubernetes bootstrapper. ### Fixed - [#743](https://github.com/spegel-org/spegel/pull/743) Remove metrics label from bootstrap service in Helm chart. - [#748](https://github.com/spegel-org/spegel/pull/748) Fix topology annotation. - [#785](https://github.com/spegel-org/spegel/pull/785) Fix verification of digests when parsing distribution path. - [#798](https://github.com/spegel-org/spegel/pull/798) Restart Spegel if Containerd event subscription is disconnected. - [#800](https://github.com/spegel-org/spegel/pull/800) Fix so that host is closed even when a bootstrap error occurs. - [#801](https://github.com/spegel-org/spegel/pull/801) Fix helm values naming for additionalMirrorTargets and mirroredRegistries. ## v0.0.30 ### Changed - [#694](https://github.com/spegel-org/spegel/pull/694) Replace IP in multi address with manet. - [#693](https://github.com/spegel-org/spegel/pull/693) Add commonLabels for pods. - [#699](https://github.com/spegel-org/spegel/pull/699) Remove as mismatch error and replace with errors as. - [#701](https://github.com/spegel-org/spegel/pull/701) Rewrite e2e tests in Go. - [#704](https://github.com/spegel-org/spegel/pull/704) Update Containerd client to v2. ### Fixed - [#689](https://github.com/spegel-org/spegel/pull/689) Make cluster domain configurable. - [#696](https://github.com/spegel-org/spegel/pull/696) Fix DNS bootstrap self check. - [#702](https://github.com/spegel-org/spegel/pull/702) Refactor and add tests for p2p ready. - [#703](https://github.com/spegel-org/spegel/pull/703) Fix p2p router close panic and add tests. ## v0.0.29 ### Added - [#678](https://github.com/spegel-org/spegel/pull/678) Add support for setting common labels in Helm chart. - [#681](https://github.com/spegel-org/spegel/pull/681) Add import as linter. ### Changed - [#683](https://github.com/spegel-org/spegel/pull/683) Change bootstrapper to allow returning multiple peers. - [#684](https://github.com/spegel-org/spegel/pull/684) Allow bootstrappers to return multiaddress only containing IP. - [#680](https://github.com/spegel-org/spegel/pull/680) Switch to using headless service for bootstrapping. ## v0.0.28 ### Added - [#576](https://github.com/spegel-org/spegel/pull/576) Add support for range requests for blobs. - [#621](https://github.com/spegel-org/spegel/pull/621) Added Mermaid diagrams documentation to help explain Spegel's inner workings. - [#629](https://github.com/spegel-org/spegel/pull/629) Document how to use multiple Spegel deployments in the same cluster. - [#661](https://github.com/spegel-org/spegel/pull/661) Add allocs to pprof endpoints. ### Changed - [#608](https://github.com/spegel-org/spegel/pull/608) Use custom proxy transport and increase idle connections per host. ### Fixed - [#651](https://github.com/spegel-org/spegel/pull/651) Fix Containerd CRI config verification. - [#660](https://github.com/spegel-org/spegel/pull/660) Add accept ranges header to blob HEAD request. ## v0.0.27 ### Fixed - [#603](https://github.com/spegel-org/spegel/pull/603) Fix append to backup always happening. - [#604](https://github.com/spegel-org/spegel/pull/604) Create empty backup directory when mirror directory is empty. ## v0.0.26 ### Removed - [#596](https://github.com/spegel-org/spegel/pull/596) Remove throttling from blobs. ### Fixed - [#601](https://github.com/spegel-org/spegel/pull/601) Fix Containerd host mirror ordering. ## v0.0.25 ### Added - [#578](https://github.com/spegel-org/spegel/pull/578) Add possibility to override environment variable NODE_IP. ### Changed - [#575](https://github.com/spegel-org/spegel/pull/575) Update to Go v1.23.2. ### Fixed - [#581](https://github.com/spegel-org/spegel/pull/581) Skip status response verification for containerd v2 ## v0.0.24 ### Added - [#538](https://github.com/spegel-org/spegel/pull/538) Replace mock OCI client with in memory client. - [#552](https://github.com/spegel-org/spegel/pull/552) Add support for VerticalPodAutoscaler in the Helm chart. - [#556](https://github.com/spegel-org/spegel/pull/556) Add configuration for revisionHistoryLimit in the Helm Chart. - [#573](https://github.com/spegel-org/spegel/pull/573) Use buffer pool for proxy copying data. ### Changed - [#518](https://github.com/spegel-org/spegel/pull/518) Extend tests for image. - [#519](https://github.com/spegel-org/spegel/pull/519) Extend tests for containerd. - [#520](https://github.com/spegel-org/spegel/pull/520) Add tests for metrics. - [#536](https://github.com/spegel-org/spegel/pull/536) Update Go version to 1.22.5. - [#547](https://github.com/spegel-org/spegel/pull/547) Set blob content type to disable detection. - [#553](https://github.com/spegel-org/spegel/pull/553) Re-use resources value for initContainer in the Helm Chart. ### Deprecated ### Removed - [#517](https://github.com/spegel-org/spegel/pull/517) Remove deprecated CopyLayer function. ### Fixed - [#535](https://github.com/spegel-org/spegel/pull/535) Fix Docker build casing checks. ### Security ## v0.0.23 ### Added - [#388](https://github.com/spegel-org/spegel/pull/388) Add support for deploying the Grafana dashboard with the Helm chart. ### Changed - [#475](https://github.com/spegel-org/spegel/pull/475) Move resolving ref to digest to manifest handler. - [#477](https://github.com/spegel-org/spegel/pull/477) Refactor distribution ref to simplify registry routing. - [#479](https://github.com/spegel-org/spegel/pull/479) Enable goimports linter and fix errors. - [#480](https://github.com/spegel-org/spegel/pull/480) Enable ireturn linter and fix errors. - [#481](https://github.com/spegel-org/spegel/pull/481) Enable perfsprint linter and fix errors. - [#482](https://github.com/spegel-org/spegel/pull/482) Enable gocritic linter and fix errors. - [#483](https://github.com/spegel-org/spegel/pull/483) Update errcheck linter configuration and fix errors. - [#487](https://github.com/spegel-org/spegel/pull/487) Move mirror metrics code to mirror handler. - [#488](https://github.com/spegel-org/spegel/pull/488) Update existing registry errors and add more detail. - [#495](https://github.com/spegel-org/spegel/pull/495) Modify e2e tests to allow reusing the same kind cluster. - [#498](https://github.com/spegel-org/spegel/pull/498) Update to Go 1.22. - [#499](https://github.com/spegel-org/spegel/pull/499) Add paralleltest linter and set all unit tests to run in parallel. - [#501](https://github.com/spegel-org/spegel/pull/501) Rename mock router to memory router and add tests. - [#507](https://github.com/spegel-org/spegel/pull/507) Change default resolve timeout to 20ms. ### Fixed - [#460](https://github.com/spegel-org/spegel/pull/460) Fix environment variable for http-bootstrap-addr flag. - [#471](https://github.com/spegel-org/spegel/pull/471) Fix handler key in request logging. - [#490](https://github.com/spegel-org/spegel/pull/490) Close immediate channel after writing to it to close wait group in merge logic. - [#491](https://github.com/spegel-org/spegel/pull/491) Fix so that resolve timeout does not cancel mirroring attempts. - [#496](https://github.com/spegel-org/spegel/pull/496) Fix p2p bootstrap to run on failed readiness check. ## v0.0.22 ### Added - [#435](https://github.com/spegel-org/spegel/pull/435) Add pprof endpoints to enable profiling. - [#434](https://github.com/spegel-org/spegel/pull/434) Add optional Containerd local content store to increase serve performance. - [#438](https://github.com/spegel-org/spegel/pull/438) Set host path type for Containerd socket. - [#449](https://github.com/spegel-org/spegel/pull/449) Replace zapr with slog and add log level configuration. ### Changed - [#439](https://github.com/spegel-org/spegel/pull/439) Update Go version and fix toolchain version. ### Fixed - [#452](https://github.com/spegel-org/spegel/pull/452) Fix Containerd Subscribe returning on any error. ### Security - [#451](https://github.com/spegel-org/spegel/pull/451) Bump golang.org/x/net from 0.21.0 to 0.23.0. ## v0.0.21 ### Added - [#421](https://github.com/spegel-org/spegel/pull/421) Add conformance tests to e2e test. - [#424](https://github.com/spegel-org/spegel/pull/424) Add option to append mirror configuration instead of overwriting. - [#429](https://github.com/spegel-org/spegel/pull/429) Add metrics to measure duration to resolve peers. ### Changed - [#395](https://github.com/spegel-org/spegel/pull/395) Replace Gin with standard lib HTTP handler. - [#430](https://github.com/spegel-org/spegel/pull/430) Replace XenitAB Kubernetes pkg with internal package. ### Fixed - [#431](https://github.com/spegel-org/spegel/pull/431) Fix import error caused by invalid file name. ## v0.0.20 ### Added - [#416](https://github.com/spegel-org/spegel/pull/416) Add image and Helm chart signing with Cosign. ### Changed - [#411](https://github.com/spegel-org/spegel/pull/411) Replace XenitAB pkg with internal package. ### Fixed - [#412](https://github.com/spegel-org/spegel/pull/412) Fix http bootstrapper arg annotation. ### Security - [#409](https://github.com/spegel-org/spegel/pull/409) Bump protobuf to fix CVE-2024-24786. - [#415](https://github.com/spegel-org/spegel/pull/415) Bump Libp2p to fix CVE-2024-22189. ## v0.0.19 > [!IMPORTANT] > The Spegel repository has been moved from XenitAB to a new GitHub organization. > Make sure to update the organization in the image and chart references. ### Added - [#335](https://github.com/spegel-org/spegel/pull/335) Add k3s to compatibility guide. - [#359](https://github.com/spegel-org/spegel/pull/359) Extend OCI client tests. - [#365](https://github.com/spegel-org/spegel/pull/365) Add support for throttling blob write speed. - [#386](https://github.com/spegel-org/spegel/pull/386) Add contributing guide. - [#391](https://github.com/spegel-org/spegel/pull/391) Add documentation for EKS specific Containerd configuration. - [#393](https://github.com/spegel-org/spegel/pull/393) Add environment variable configuration support. - [#394](https://github.com/spegel-org/spegel/pull/394) Add `cgr.dev` to default registry mirrors in the Helm chart. - [#398](https://github.com/spegel-org/spegel/pull/398) Document DigitalOcean incompatibility. ### Changed - [#355](https://github.com/spegel-org/spegel/pull/355) Rename OCI function names. - [#356](https://github.com/spegel-org/spegel/pull/356) Refactor OCI client test to simplify testing multiple implementations. - [#357](https://github.com/spegel-org/spegel/pull/357) Replace mock Containerd store with real upstream store. - [#367](https://github.com/spegel-org/spegel/pull/367) Update Go image to 1.21.7. - [#376](https://github.com/spegel-org/spegel/pull/376) Change go directive to 1.21. - [#383](https://github.com/spegel-org/spegel/pull/383) Bump libp2p to v0.33.0, replace deprecated Pretty function - [#397](https://github.com/spegel-org/spegel/pull/397) Replace CopyLayer with GetBlob. - [#400](https://github.com/spegel-org/spegel/pull/400) Update org imports from xenitab to spegel-org. - [#402](https://github.com/spegel-org/spegel/pull/402) Update tests to use spegel-org instead of xenitab. ### Deprecated ### Removed - [#371](https://github.com/spegel-org/spegel/pull/371) Remove state track retry on error. ### Fixed - [#396](https://github.com/spegel-org/spegel/pull/396) Fix missing metrics when registering. - [#408](https://github.com/spegel-org/spegel/pull/408) Fix int overflow for ARM builds. ### Security ## v0.0.18 ### Added - [#331](https://github.com/spegel-org/spegel/pull/331) Document possible modifications required for k8s-digester. - [#337](https://github.com/spegel-org/spegel/pull/337) Add HTTP bootstrapper. - [#340](https://github.com/spegel-org/spegel/pull/340) Add Talos to compatibility. - [#343](https://github.com/spegel-org/spegel/pull/343) Implement image event and add support for delete events. - [#344](https://github.com/spegel-org/spegel/pull/344) Add support for multi arch images. - [#347](https://github.com/spegel-org/spegel/pull/347) Add support for a custom http transport when proxying requests. - [#352](https://github.com/spegel-org/spegel/pull/352) Add lip2p options to router. ### Changed - [#319](https://github.com/spegel-org/spegel/pull/319) Move metrics definitions to separate package. - [#322](https://github.com/spegel-org/spegel/pull/322) Refactor type of router resolve. - [#325](https://github.com/spegel-org/spegel/pull/325) Refactor bootstrap to exit on error. - [#326](https://github.com/spegel-org/spegel/pull/326) Clean up routing interface. - [#328](https://github.com/spegel-org/spegel/pull/328) Move remaining packages to pkg. - [#342](https://github.com/spegel-org/spegel/pull/342) Defer Containerd client creation until first accessed. - [#348](https://github.com/spegel-org/spegel/pull/348) Change registry configuration to options. - [#349](https://github.com/spegel-org/spegel/pull/349) Adjust router peer channel buffer size. ### Removed - [#327](https://github.com/spegel-org/spegel/pull/327) Remove ConfigMap from RBAC. ## v0.0.17 ### Added - [#299](https://github.com/spegel-org/spegel/pull/299) Add update strategy configuration to Helm chart. ### Changed - [#291](https://github.com/spegel-org/spegel/pull/291) Move OCI package to pkg. - [#306](https://github.com/spegel-org/spegel/pull/306) Realign the structs in attempt to minimise memory usage. ### Fixed - [#309](https://github.com/spegel-org/spegel/pull/309) Fix label selectors on service monitors and metrics service. - [#279](https://github.com/spegel-org/spegel/pull/279) Fix broken default value for additional mirror registries. - [#284](https://github.com/spegel-org/spegel/pull/284) Fix Spegel support for ipv6. ## v0.0.16 ### Fixed - [#276](https://github.com/spegel-org/spegel/pull/276) Fix Golang image digest to use manifest list instead of AMD64. ## v0.0.15 ### Added - [#270](https://github.com/spegel-org/spegel/pull/270) Add tests for local and external service port. - [#262](https://github.com/spegel-org/spegel/pull/262) Enable misspell linter and fix spelling mistakes. - [#263](https://github.com/spegel-org/spegel/pull/263) Enable testifylint linter and fix errors. - [#269](https://github.com/spegel-org/spegel/pull/269) Set Go image version with digest in Dockerfile. ### Changed - [#253](https://github.com/spegel-org/spegel/pull/253) Set klog logger to standardize output format. ### Fixed - [#271](https://github.com/spegel-org/spegel/pull/271) Fix Spegel running on IPVS cluster. ## v0.0.14 ### Added - [#237](https://github.com/spegel-org/spegel/pull/237) Verify discard unpacked layers setting. ### Fixed - [#241](https://github.com/spegel-org/spegel/pull/241) Fix missing return on resolve error. - [#223](https://github.com/spegel-org/spegel/pull/223) Propagate closing channel before resolve timeout. ### Security - [#249](https://github.com/spegel-org/spegel/pull/249) Bump google.golang.org/grpc from 1.55.0 to 1.56.3 ## v0.0.13 ### Added - [#195](https://github.com/spegel-org/spegel/pull/195) Fix daemonset argument namespace to use helper-defined namespace value. ### Changed - [#164](https://github.com/spegel-org/spegel/pull/164) Update Go to 1.21. - [#211](https://github.com/spegel-org/spegel/pull/211) Replace factory with adress filter to remove loopback addresses. - [#219](https://github.com/spegel-org/spegel/pull/219) Use release name instead of namespace for name of leader election configmap. - [#215](https://github.com/spegel-org/spegel/pull/215) Support for servicemonitor labels, interval and timeout in helm chart. ### Fixed - [#233](https://github.com/spegel-org/spegel/pull/233) Fix address filtering to remove localhost from host. ### Security - [#235](https://github.com/spegel-org/spegel/pull/235) Bump golang.org/x/net from 0.14.0 to 0.17.0. ## v0.0.12 ### Added - [#182](https://github.com/spegel-org/spegel/pull/182) Add lscr.io as default registry. ### Fixed - [#181](https://github.com/spegel-org/spegel/pull/181) Fix mirroring images using index files without a media type. - [#191](https://github.com/spegel-org/spegel/pull/191) Fix Containerd config path verification. ### Security - [#184](https://github.com/spegel-org/spegel/pull/184) Bump github.com/libp2p/go-libp2p from 0.27.7 to 0.30.0. ## v0.0.11 ### Added - [#170](https://github.com/spegel-org/spegel/pull/170) Backup existing Containerd mirror configuration. - [#171](https://github.com/spegel-org/spegel/pull/171) Add option to disable resolve. ### Changed - [#174](https://github.com/spegel-org/spegel/pull/174) Modify error handling in state tracking to avoid exiting. ## v0.0.10 ### Added - [#145](https://github.com/spegel-org/spegel/pull/145) Add new field to override Helm chart namespace. - [#153](https://github.com/spegel-org/spegel/pull/153) Add option to disable resolving latest tags. - [#156](https://github.com/spegel-org/spegel/pull/156) Add validation of mirror configuration on start. ### Changed - [#151](https://github.com/spegel-org/spegel/pull/151) Refactor containerd mirror tests and remove utils package. ### Removed - [#160](https://github.com/spegel-org/spegel/pull/160) Remove X-Spegel-Registry header. - [#161](https://github.com/spegel-org/spegel/pull/161) Remove X-Spegel-Mirror header. - [#162](https://github.com/spegel-org/spegel/pull/162) Remove X-Spegel-External header. ### Fixed - [#152](https://github.com/spegel-org/spegel/pull/152) Fix image parsing to allow only passing digest through image reference. - [#158](https://github.com/spegel-org/spegel/pull/158) Fix Containerd verify with check for empty configuration path. - [#163](https://github.com/spegel-org/spegel/pull/163) Remove unneeded namespace in role binding. ## v0.0.9 ### Changed - [#138](https://github.com/spegel-org/spegel/pull/138) Set image digest in Helm chart. ### Fixed - [#141](https://github.com/spegel-org/spegel/pull/141) Fix platform matching and add tests for getting image digests. ## v0.0.8 ### Added - [#125](https://github.com/spegel-org/spegel/pull/125) Add retry mirroring to new peer if current peer fails. - [#127](https://github.com/spegel-org/spegel/pull/127) Add configuration for resolve retry and timeout. ### Changed - [#107](https://github.com/spegel-org/spegel/pull/107) Refactor image references with generic implementation. - [#114](https://github.com/spegel-org/spegel/pull/114) Move mirror configuration to specific OCI implementation. - [#117](https://github.com/spegel-org/spegel/pull/117) Update Containerd client to 1.7. - [#126](https://github.com/spegel-org/spegel/pull/126) Refactor registry implementation to not require separate handler. - [#132](https://github.com/spegel-org/spegel/pull/132) Extend tests to validate single node and mirror fallback. - [#133](https://github.com/spegel-org/spegel/pull/133) Use routing table size for readiness check. ### Removed - [#113](https://github.com/spegel-org/spegel/pull/113) Remove image filter configuration. ## v0.0.7 ### Changed - [#82](https://github.com/spegel-org/spegel/pull/82) Filter out localhost from advertised IPs. - [#89](https://github.com/spegel-org/spegel/pull/89) Remove p2p route table check on startup. - [#91](https://github.com/spegel-org/spegel/pull/91) Adjust tolerations and node selector. ## v0.0.6 ### Changed - [#42](https://github.com/spegel-org/spegel/pull/42) Only use bootstrap function for initial peer discovery. - [#66](https://github.com/spegel-org/spegel/pull/66) Move mirror configuration logic to run as an init container. ### Fixed - [#71](https://github.com/spegel-org/spegel/pull/71) Fix priority class name. ## v0.0.5 ### Added - [#29](https://github.com/spegel-org/spegel/pull/29) Make priority class name configurable and set a default value. - [#49](https://github.com/spegel-org/spegel/pull/49) Add registry.k8s.io to registry mirror list. - [#56](https://github.com/spegel-org/spegel/pull/56) Add gcr.io and k8s.gcr.io registries to default list. ### Changed - [#32](https://github.com/spegel-org/spegel/pull/32) Update Go to 1.20. - [#33](https://github.com/spegel-org/spegel/pull/33) Remove containerd info call when handling manifest request. - [#48](https://github.com/spegel-org/spegel/pull/48) Replace multierr with stdlib errors join. - [#54](https://github.com/spegel-org/spegel/pull/54) Refactor metrics and add documentation. ### Fixed - [#51](https://github.com/spegel-org/spegel/pull/51) Filter tracked images to only included mirrored registries. - [#52](https://github.com/spegel-org/spegel/pull/52) Return error when image reference is not valid. - [#55](https://github.com/spegel-org/spegel/pull/55) Fix filters by merging them into a single statement. - [#53](https://github.com/spegel-org/spegel/pull/53) Include error from defer in returned error. ## v0.0.4 ### Fixed - [#26](https://github.com/spegel-org/spegel/pull/26) Replace topology keys with optional topology aware hints. ## v0.0.3 ### Added - [#18](https://github.com/spegel-org/spegel/pull/18) Add support to use Spegel instance on another node. ### Changed - [#21](https://github.com/spegel-org/spegel/pull/21) Allow external mirror request to resolve to mirror instance. ## /CONTRIBUTING.md # Contributing Thank you for considering contributing to Spegel, hopefully this document will make this process easier. ## Running tests The following tools are required to run the tests properly. * go * [golangci-lint](https://github.com/golangci/golangci-lint) * [kind](https://github.com/kubernetes-sigs/kind) * [goreleaser](https://github.com/goreleaser/goreleaser) * [docker](https://docs.docker.com/get-started/get-docker/) * [helm](https://github.com/helm/helm) * [kubectl](https://kubernetes.io/docs/tasks/tools/#kubectl) Run the linter and the unit tests to quickly validate changes. ```shell make lint test ``` Run the e2e tests which take a bit more time. ```shell make test-e2e ``` There are e2e tests for the different CNIs iptables, iptables-v6, and ipvs. ```shell make test-e2e E2E_CNI=ipvs ``` ## Building Build the Docker image locally. ```shell make build-image ``` It is possible to specify a different image name and tag. ```shell make build-image IMG=example.com/spegel TAG=feature ``` ### Local debugging Run the `dev-deploy` recipe which will create a Kind cluster with the proper configuration and deploy Spegel into it. If you run this command a second time the cluster will be kept but Spegel will be updated. ```shell make dev-deploy ``` After the command has run you can get a kubeconfig file to access the cluster and do any debugging. ```shell kind get kubeconfig --name spegel-dev > kubeconfig export KUBECOONFIG=$(pwd)/kubeconfig kubectl -n spegel get pods ``` ## Generate Helm documentation Changes to the Helm chart values will require the documentation to be regenerated. ```shell make helm-docs ``` ## Acceptance policy Pull requests need to fulfill the following requirements to be accepted. * New code has tests where applicable. * The change has been added to the [changelog](./CHANGELOG.md). * Documentation has been generated if applicable. * The unit tests pass. * Linter does not report any errors. * All end to end tests pass. ## /Dockerfile ``` path="/Dockerfile" FROM gcr.io/distroless/static:nonroot ARG TARGETOS ARG TARGETARCH COPY ./dist/spegel_${TARGETOS}_${TARGETARCH}/spegel / USER root:root ENTRYPOINT ["/spegel"] ``` ## /LICENSE ``` path="/LICENSE" MIT License Copyright (c) 2024 The Spegel Authors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ``` ## /Makefile ``` path="/Makefile" TAG = $$(git rev-parse --short HEAD) IMG_NAME ?= ghcr.io/spegel-org/spegel IMG_REF = $(IMG_NAME):$(TAG) E2E_PROXY_MODE ?= iptables E2E_IP_FAMILY ?= ipv4 lint: golangci-lint run ./... build: goreleaser build --snapshot --clean --single-target --skip before build-image: build docker build -t ${IMG_REF} . test-unit: go test ./... test-e2e: build-image IMG_REF=${IMG_REF} \ E2E_PROXY_MODE=${E2E_PROXY_MODE} \ E2E_IP_FAMILY=${E2E_IP_FAMILY} \ go test ./test/e2e -v -timeout 200s -tags e2e -count 1 -run TestE2E dev-deploy: build-image IMG_REF=${IMG_REF} go test ./test/e2e -v -timeout 200s -tags e2e -count 1 -run TestDevDeploy tools: GO111MODULE=on go install github.com/norwoodj/helm-docs/cmd/helm-docs helm-docs: tools cd ./charts/spegel && helm-docs ``` ## /README.md > [!NOTE] > We’ve started hosting community meetings every Tuesday at 17:00 CET. Find out how to participate at https://spegel.dev/project/community/#meeting. # Spegel Spegel, mirror in Swedish, is a stateless cluster local OCI registry mirror.

## Features Spegel is for you if you are looking to do any of the following. * Locally cache images from external registries with no explicit configuration. * Avoid cluster failure during external registry downtime. * Improve image pull speed and pod startup time by pulling images from the local cache first. * Avoid rate-limiting when pulling images from external registries (e.g. Docker Hub). * Decrease egressing traffic outside of the clusters network. * Increase image pull efficiency in edge node deployments. ## Getting Started Read the [getting started](https://spegel.dev/docs/getting-started/) guide to deploy Spegel. ## Contributing Read [contribution guidelines](./CONTRIBUTING.md) for instructions on how to build and test Spegel. ## Acknowledgements Spegel was initially developed at [Xenit AB](https://xenit.se/). ## License This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. ## /charts/spegel/.helmignore ```helmignore path="/charts/spegel/.helmignore" # Patterns to ignore when building packages. # This supports shell glob matching, relative path matching, and # negation (prefixed with !). Only one pattern per line. .DS_Store # Common VCS dirs .git/ .gitignore .bzr/ .bzrignore .hg/ .hgignore .svn/ # Common backup files *.swp *.bak *.tmp *.orig *~ # Various IDEs .project .idea/ *.tmproj .vscode/ ``` ## /charts/spegel/Chart.yaml ```yaml path="/charts/spegel/Chart.yaml" apiVersion: v2 name: spegel description: Stateless cluster local OCI registry mirror. type: application version: v0.0.1 appVersion: v0.0.1 annotations: artifacthub.io/category: "integration-delivery" artifacthub.io/license: "MIT" artifacthub.io/operator: "false" artifacthub.io/prerelease: "false" ``` ## /charts/spegel/README.md # Spegel Stateless cluster local OCI registry mirror. Read the [getting started](https://spegel.dev/docs/getting-started/) guide to deploy Spegel. ## Values | Key | Type | Default | Description | |-----|------|---------|-------------| | affinity | object | `{}` | Affinity settings for pod assignment. | | basicAuthSecretName | string | `""` | Name of secret containing basic authentication credentials for registry. | | clusterDomain | string | `"cluster.local."` | Domain configured for service domain names. | | commonLabels | object | `{}` | Common labels to apply to all rendered resources. | | fullnameOverride | string | `""` | Overrides the full name of the chart. | | grafanaDashboard.annotations | object | `{}` | Annotations that ConfigMaps can have to get configured in Grafana, See: sidecar.dashboards.folderAnnotation for specifying the dashboard folder. https://github.com/grafana/helm-charts/tree/main/charts/grafana | | grafanaDashboard.enabled | bool | `false` | If true creates a Grafana dashboard. | | grafanaDashboard.sidecarLabel | string | `"grafana_dashboard"` | Label that ConfigMaps should have to be loaded as dashboards. | | grafanaDashboard.sidecarLabelValue | string | `"1"` | Label value that ConfigMaps should have to be loaded as dashboards. | | image.digest | string | `""` | Image digest. | | image.pullPolicy | string | `"IfNotPresent"` | Image Pull Policy. | | image.repository | string | `"ghcr.io/spegel-org/spegel"` | Image repository. | | image.tag | string | `""` | Overrides the image tag whose default is the chart appVersion. | | imagePullSecrets | list | `[]` | Image Pull Secrets | | nameOverride | string | `""` | Overrides the name of the chart. | | namespaceOverride | string | `""` | Overrides the namespace where spegel resources are installed. | | nodeSelector | object | `{"kubernetes.io/os":"linux"}` | Node selector for pod assignment. | | podAnnotations | object | `{}` | Annotations to add to the pod. | | podSecurityContext | object | `{}` | Security context for the pod. | | priorityClassName | string | `"system-node-critical"` | Priority class name to use for the pod. | | resources | object | `{"limits":{"memory":"128Mi"},"requests":{"memory":"128Mi"}}` | Resource requests and limits for the Spegel container. | | revisionHistoryLimit | int | `10` | The number of old history to retain to allow rollback. | | securityContext | object | `{}` | Security context for the Spegel container. | | service.cleanup.port | int | `8080` | Port to expose cleanup probe on. | | service.metrics.port | int | `9090` | Port to expose the metrics via the service. | | service.registry.hostPort | int | `30020` | Local host port to expose the registry. | | service.registry.nodeIp | string | `""` | Override the NODE_ID environment variable. It defaults to the field status.hostIP | | service.registry.nodePort | int | `30021` | Node port to expose the registry via the service. | | service.registry.port | int | `5000` | Port to expose the registry via the service. | | service.registry.topologyAwareHintsEnabled | bool | `true` | If true adds topology aware hints annotation to node port service. | | service.router.port | int | `5001` | Port to expose the router via the service. | | serviceAccount.annotations | object | `{}` | Annotations to add to the service account | | serviceAccount.name | string | `""` | The name of the service account to use. If not set and create is true, a name is generated using the fullname template. | | serviceMonitor.enabled | bool | `false` | If true creates a Prometheus Service Monitor. | | serviceMonitor.interval | string | `"60s"` | Prometheus scrape interval. | | serviceMonitor.labels | object | `{}` | Service monitor specific labels for prometheus to discover servicemonitor. | | serviceMonitor.metricRelabelings | list | `[]` | List of relabeling rules to apply to the samples before ingestion. | | serviceMonitor.relabelings | list | `[]` | List of relabeling rules to apply the target’s metadata labels. | | serviceMonitor.scrapeTimeout | string | `"30s"` | Prometheus scrape interval timeout. | | spegel.additionalMirrorTargets | list | `[]` | Additional target mirror registries other than Spegel. | | spegel.containerdContentPath | string | `"/var/lib/containerd/io.containerd.content.v1.content"` | Path to Containerd content store.. | | spegel.containerdMirrorAdd | bool | `true` | If true Spegel will add mirror configuration to the node. | | spegel.containerdNamespace | string | `"k8s.io"` | Containerd namespace where images are stored. | | spegel.containerdRegistryConfigPath | string | `"/etc/containerd/certs.d"` | Path to Containerd mirror configuration. | | spegel.containerdSock | string | `"/run/containerd/containerd.sock"` | Path to Containerd socket. | | spegel.debugWebEnabled | bool | `false` | When true enables debug web page. | | spegel.logLevel | string | `"INFO"` | Minimum log level to output. Value should be DEBUG, INFO, WARN, or ERROR. | | spegel.mirrorResolveRetries | int | `3` | Max amount of mirrors to attempt. | | spegel.mirrorResolveTimeout | string | `"20ms"` | Max duration spent finding a mirror. | | spegel.mirroredRegistries | list | `[]` | Registries for which mirror configuration will be created. Empty means all registires will be mirrored. | | spegel.prependExisting | bool | `false` | When true existing mirror configuration will be kept and Spegel will prepend it's configuration. | | spegel.resolveLatestTag | bool | `true` | When true latest tags will be resolved to digests. | | spegel.resolveTags | bool | `true` | When true Spegel will resolve tags to digests. | | tolerations | list | `[{"key":"CriticalAddonsOnly","operator":"Exists"},{"effect":"NoExecute","operator":"Exists"},{"effect":"NoSchedule","operator":"Exists"}]` | Tolerations for pod assignment. | | updateStrategy | object | `{}` | An update strategy to replace existing pods with new pods. | | verticalPodAutoscaler.controlledResources | list | `[]` | List of resources that the vertical pod autoscaler can control. Defaults to cpu and memory | | verticalPodAutoscaler.controlledValues | string | `"RequestsAndLimits"` | Specifies which resource values should be controlled: RequestsOnly or RequestsAndLimits. | | verticalPodAutoscaler.enabled | bool | `false` | If true creates a Vertical Pod Autoscaler. | | verticalPodAutoscaler.maxAllowed | object | `{}` | Define the max allowed resources for the pod | | verticalPodAutoscaler.minAllowed | object | `{}` | Define the min allowed resources for the pod | | verticalPodAutoscaler.recommenders | list | `[]` | Recommender responsible for generating recommendation for the object. List should be empty (then the default recommender will generate the recommendation) or contain exactly one recommender. | | verticalPodAutoscaler.updatePolicy.minReplicas | int | `2` | Specifies minimal number of replicas which need to be alive for VPA Updater to attempt pod eviction | | verticalPodAutoscaler.updatePolicy.updateMode | string | `"Auto"` | Specifies whether recommended updates are applied when a Pod is started and whether recommended updates are applied during the life of a Pod. Possible values are "Off", "Initial", "Recreate", and "Auto". | ## /charts/spegel/README.md.gotmpl ```gotmpl path="/charts/spegel/README.md.gotmpl" # Spegel {{ template "chart.description" . }} Read the [getting started](https://spegel.dev/docs/getting-started/) guide to deploy Spegel. {{ template "chart.valuesSection" . }} ``` ## /charts/spegel/artifacthub-repo.yml ```yml path="/charts/spegel/artifacthub-repo.yml" repositoryID: 8122016b-c465-4eaf-be87-f51423aa76f1 owners: - name: Philip Laine email: philip.laine@gmail.com ``` ## /charts/spegel/monitoring/grafana-dashboard.json ```json path="/charts/spegel/monitoring/grafana-dashboard.json" { "annotations": { "list": [ { "builtIn": 1, "datasource": { "type": "grafana", "uid": "-- Grafana --" }, "enable": true, "hide": true, "iconColor": "rgba(0, 211, 255, 1)", "name": "Annotations & Alerts", "target": { "limit": 100, "matchAny": false, "tags": [], "type": "dashboard" }, "type": "dashboard" } ] }, "description": "Spegel is a pull only OCI registry which runs locally on every Node in the Kubernetes cluster. Containerd is configured to use the local registry as a mirror, which would serve the image from within the cluster or from the source registry.", "editable": true, "fiscalYearStartMonth": 0, "graphTooltip": 0, "id": null, "links": [], "panels": [ { "collapsed": false, "gridPos": { "h": 1, "w": 24, "x": 0, "y": 0 }, "id": 24, "panels": [], "title": "", "type": "row" }, { "datasource": { "type": "prometheus", "uid": "${datasource}" }, "description": "", "fieldConfig": { "defaults": { "color": { "mode": "thresholds" }, "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { "color": "transparent", "value": null } ] } }, "overrides": [] }, "gridPos": { "h": 4, "w": 3, "x": 0, "y": 1 }, "id": 11, "options": { "colorMode": "none", "graphMode": "area", "justifyMode": "auto", "orientation": "auto", "percentChangeColorMode": "standard", "reduceOptions": { "calcs": [ "lastNotNull" ], "fields": "", "values": false }, "showPercentChange": false, "textMode": "auto", "wideLayout": true }, "pluginVersion": "11.3.0", "targets": [ { "datasource": { "type": "prometheus", "uid": "${datasource}" }, "editorMode": "code", "expr": "count(spegel_advertised_keys{instance=~\"$instance\"})", "hide": false, "legendFormat": "__auto", "range": true, "refId": "A" } ], "title": "Registry", "transparent": true, "type": "stat" }, { "datasource": { "type": "prometheus", "uid": "${datasource}" }, "fieldConfig": { "defaults": { "color": { "mode": "thresholds" }, "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { "color": "transparent", "value": null } ] } }, "overrides": [] }, "gridPos": { "h": 4, "w": 3, "x": 3, "y": 1 }, "id": 29, "options": { "colorMode": "none", "graphMode": "area", "justifyMode": "auto", "orientation": "auto", "percentChangeColorMode": "standard", "reduceOptions": { "calcs": [ "lastNotNull" ], "fields": "", "values": false }, "showPercentChange": false, "textMode": "auto", "wideLayout": true }, "pluginVersion": "11.3.0", "targets": [ { "datasource": { "type": "prometheus", "uid": "${datasource}" }, "editorMode": "code", "expr": "sum(kubelet_node_name{job=\"kubelet\"})", "legendFormat": "__auto", "range": true, "refId": "A" } ], "title": "Running Nodes", "transparent": true, "type": "stat" }, { "datasource": { "type": "prometheus", "uid": "$datasource" }, "fieldConfig": { "defaults": { "links": [], "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { "color": "transparent", "value": null } ] }, "unit": "none" }, "overrides": [] }, "gridPos": { "h": 4, "w": 3, "x": 6, "y": 1 }, "id": 22, "options": { "colorMode": "none", "graphMode": "area", "justifyMode": "auto", "orientation": "auto", "percentChangeColorMode": "standard", "reduceOptions": { "calcs": [ "lastNotNull" ], "fields": "", "values": false }, "showPercentChange": false, "textMode": "auto", "wideLayout": true }, "pluginVersion": "11.3.0", "targets": [ { "datasource": { "uid": "$datasource" }, "editorMode": "code", "expr": "sum(kubelet_running_containers)", "format": "time_series", "intervalFactor": 2, "legendFormat": "{{instance}}", "range": true, "refId": "A" } ], "title": "Running Containers", "transparent": true, "type": "stat" }, { "datasource": { "type": "prometheus", "uid": "${datasource}" }, "fieldConfig": { "defaults": { "color": { "mode": "thresholds" }, "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { "color": "transparent", "value": null } ] } }, "overrides": [] }, "gridPos": { "h": 4, "w": 3, "x": 9, "y": 1 }, "id": 20, "options": { "colorMode": "none", "graphMode": "area", "justifyMode": "auto", "orientation": "auto", "percentChangeColorMode": "standard", "reduceOptions": { "calcs": [ "lastNotNull" ], "fields": "", "values": false }, "showPercentChange": false, "textMode": "auto", "wideLayout": true }, "pluginVersion": "11.3.0", "targets": [ { "datasource": { "type": "prometheus", "uid": "${datasource}" }, "editorMode": "code", "expr": "sum(kubelet_running_pods)", "legendFormat": "__auto", "range": true, "refId": "A" } ], "title": "Running Pods", "transparent": true, "type": "stat" }, { "datasource": { "type": "prometheus", "uid": "${datasource}" }, "description": "", "fieldConfig": { "defaults": { "color": { "fixedColor": "green", "mode": "fixed" }, "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { "color": "green", "value": null }, { "color": "red", "value": 80 } ] }, "unit": "ms" }, "overrides": [] }, "gridPos": { "h": 4, "w": 3, "x": 12, "y": 1 }, "id": 12, "options": { "colorMode": "value", "graphMode": "area", "justifyMode": "auto", "orientation": "auto", "percentChangeColorMode": "standard", "reduceOptions": { "calcs": [ "lastNotNull" ], "fields": "", "values": false }, "showPercentChange": false, "textMode": "auto", "wideLayout": true }, "pluginVersion": "11.3.0", "targets": [ { "datasource": { "type": "prometheus", "uid": "${datasource}" }, "editorMode": "code", "exemplar": false, "expr": "max(rate(http_request_duration_seconds_bucket{job=\"spegel\"}[$__interval]))", "format": "table", "instant": true, "legendFormat": "__auto", "range": false, "refId": "A" } ], "title": "Max Request Duration", "transparent": true, "type": "stat" }, { "fieldConfig": { "defaults": {}, "overrides": [] }, "gridPos": { "h": 4, "w": 3, "x": 21, "y": 1 }, "id": 2, "options": { "code": { "language": "plaintext", "showLineNumbers": false, "showMiniMap": false }, "content": "
\n
Spegel at GitHub
\n\n", "mode": "html" }, "pluginVersion": "11.3.0", "title": "Github link", "transparent": true, "type": "text" }, { "collapsed": false, "gridPos": { "h": 1, "w": 24, "x": 0, "y": 5 }, "id": 9, "panels": [], "repeat": "datasource", "title": "", "type": "row" }, { "datasource": { "type": "prometheus", "uid": "${datasource}" }, "fieldConfig": { "defaults": { "color": { "mode": "thresholds" }, "custom": { "align": "center", "cellOptions": { "type": "auto" }, "inspect": false }, "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { "color": "green", "value": null }, { "color": "red", "value": 80 } ] } }, "overrides": [ { "matcher": { "id": "byName", "options": "instance" }, "properties": [ { "id": "custom.width", "value": 226 } ] }, { "matcher": { "id": "byName", "options": "prometheus" }, "properties": [ { "id": "custom.width", "value": 296 } ] } ] }, "gridPos": { "h": 7, "w": 12, "x": 0, "y": 6 }, "id": 16, "options": { "cellHeight": "sm", "footer": { "countRows": false, "enablePagination": false, "fields": "", "reducer": [ "sum" ], "show": false }, "frameIndex": 0, "showHeader": true, "sortBy": [] }, "pluginVersion": "11.3.0", "targets": [ { "datasource": { "type": "prometheus", "uid": "${datasource}" }, "editorMode": "code", "exemplar": false, "expr": "spegel_advertised_images{job=~\"spegel\",instance=~\"$instance\"}", "format": "table", "instant": true, "legendFormat": "__auto", "range": false, "refId": "A" }, { "datasource": { "type": "prometheus", "uid": "${datasource}" }, "editorMode": "code", "exemplar": false, "expr": "spegel_advertised_keys{job=~\"spegel\",instance=~\"$instance\"} ", "format": "table", "hide": false, "instant": true, "legendFormat": "__auto", "range": false, "refId": "B" } ], "title": "", "transformations": [ { "id": "filterFieldsByName", "options": { "include": { "names": [ "pod", "Value #A", "Value #B" ] } } }, { "id": "merge", "options": {} }, { "id": "renameByRegex", "options": { "regex": "Value #A", "renamePattern": "Container cache" } }, { "id": "renameByRegex", "options": { "regex": "Value #B", "renamePattern": "Layers cache" } } ], "transparent": true, "type": "table" }, { "datasource": { "type": "prometheus", "uid": "${datasource}" }, "description": "", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "series", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "barWidthFactor": 0.6, "drawStyle": "line", "fillOpacity": 10, "gradientMode": "opacity", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "insertNulls": false, "lineInterpolation": "smooth", "lineStyle": { "fill": "solid" }, "lineWidth": 2, "pointSize": 1, "scaleDistribution": { "type": "linear" }, "showPoints": "auto", "spanNulls": false, "stacking": { "group": "A", "mode": "none" }, "thresholdsStyle": { "mode": "off" } }, "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { "color": "green", "value": null }, { "color": "red", "value": 80 } ] } }, "overrides": [] }, "gridPos": { "h": 7, "w": 12, "x": 12, "y": 6 }, "id": 6, "options": { "legend": { "calcs": [], "displayMode": "list", "placement": "bottom", "showLegend": true }, "tooltip": { "mode": "single", "sort": "none" } }, "pluginVersion": "11.3.0", "targets": [ { "datasource": { "type": "prometheus", "uid": "${datasource}" }, "editorMode": "code", "exemplar": false, "expr": "spegel_advertised_images{job=~\"spegel\",instance=~\"$instance\"}", "format": "time_series", "instant": false, "legendFormat": "{{ instance }}", "range": true, "refId": "A" } ], "title": "Container Images Advertised ", "transparent": true, "type": "timeseries" }, { "datasource": { "type": "prometheus", "uid": "$datasource" }, "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "series", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "barWidthFactor": 0.6, "drawStyle": "line", "fillOpacity": 36, "gradientMode": "opacity", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, "scaleDistribution": { "type": "linear" }, "showPoints": "never", "spanNulls": false, "stacking": { "group": "A", "mode": "normal" }, "thresholdsStyle": { "mode": "off" } }, "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { "color": "green", "value": null }, { "color": "red", "value": 80 } ] } }, "overrides": [ { "matcher": { "id": "byName", "options": "requests" }, "properties": [ { "id": "color", "value": { "fixedColor": "#F2495C", "mode": "fixed" } }, { "id": "custom.fillOpacity", "value": 0 }, { "id": "custom.lineWidth", "value": 2 }, { "id": "custom.stacking", "value": { "group": false, "mode": "normal" } } ] }, { "matcher": { "id": "byName", "options": "limits" }, "properties": [ { "id": "color", "value": { "fixedColor": "#FF9830", "mode": "fixed" } }, { "id": "custom.fillOpacity", "value": 0 }, { "id": "custom.lineWidth", "value": 2 }, { "id": "custom.stacking", "value": { "group": false, "mode": "normal" } } ] } ] }, "gridPos": { "h": 7, "w": 12, "x": 0, "y": 13 }, "id": 28, "interval": "1m", "options": { "legend": { "calcs": [], "displayMode": "list", "placement": "bottom", "showLegend": false }, "tooltip": { "mode": "multi", "sort": "none" } }, "pluginVersion": "11.3.0", "targets": [ { "datasource": { "uid": "$datasource" }, "editorMode": "code", "exemplar": false, "expr": "sum(node_namespace_pod_container:container_cpu_usage_seconds_total:sum_irate{container=\"$container\"}) by (pod)", "format": "time_series", "instant": false, "intervalFactor": 2, "legendFormat": "{{ container }}", "range": true, "refId": "A", "step": 10 } ], "title": "CPU Usage", "transparent": true, "type": "timeseries" }, { "datasource": { "type": "prometheus", "uid": "${datasource}" }, "description": "", "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "series", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "barWidthFactor": 0.6, "drawStyle": "line", "fillOpacity": 10, "gradientMode": "opacity", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "insertNulls": false, "lineInterpolation": "smooth", "lineWidth": 2, "pointSize": 1, "scaleDistribution": { "type": "linear" }, "showPoints": "auto", "spanNulls": false, "stacking": { "group": "A", "mode": "none" }, "thresholdsStyle": { "mode": "off" } }, "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { "color": "green", "value": null }, { "color": "red", "value": 80 } ] } }, "overrides": [] }, "gridPos": { "h": 7, "w": 12, "x": 12, "y": 13 }, "id": 7, "options": { "legend": { "calcs": [], "displayMode": "list", "placement": "bottom", "showLegend": true }, "tooltip": { "mode": "single", "sort": "none" } }, "pluginVersion": "11.3.0", "targets": [ { "datasource": { "type": "prometheus", "uid": "${datasource}" }, "editorMode": "code", "exemplar": false, "expr": "spegel_advertised_keys{job=~\"spegel\",instance=~\"$instance\"} ", "format": "time_series", "instant": false, "legendFormat": "{{ instance }}", "range": true, "refId": "A" } ], "title": "Images Layer Advertised ", "transparent": true, "type": "timeseries" }, { "collapsed": false, "gridPos": { "h": 1, "w": 24, "x": 0, "y": 20 }, "id": 35, "panels": [], "title": "Kubelet", "type": "row" }, { "datasource": { "type": "prometheus", "uid": "${datasource}" }, "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "barWidthFactor": 0.6, "drawStyle": "line", "fillOpacity": 6, "gradientMode": "none", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, "scaleDistribution": { "type": "linear" }, "showPoints": "never", "spanNulls": false, "stacking": { "group": "A", "mode": "none" }, "thresholdsStyle": { "mode": "off" } }, "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { "color": "green", "value": null }, { "color": "red", "value": 80 } ] }, "unit": "ops" }, "overrides": [] }, "gridPos": { "h": 10, "w": 12, "x": 0, "y": 21 }, "id": 31, "options": { "legend": { "calcs": [], "displayMode": "table", "placement": "right", "showLegend": true }, "tooltip": { "mode": "single", "sort": "none" } }, "pluginVersion": "11.3.0", "targets": [ { "datasource": { "type": "prometheus", "uid": "${datasource}" }, "editorMode": "code", "expr": "sum(rate(kubelet_runtime_operations_total{job=\"kubelet\", metrics_path=\"/metrics\", operation_type=~\"list_images|pull_image|start_container|list_images|list_containers|version|exec_sync|create_container\"}[$__rate_interval])) by (operation_type)", "legendFormat": "__auto", "range": true, "refId": "A" } ], "title": "Kubelet Operation Rate", "transparent": true, "type": "timeseries" }, { "datasource": { "type": "prometheus", "uid": "$datasource" }, "fieldConfig": { "defaults": { "color": { "mode": "palette-classic" }, "custom": { "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, "barWidthFactor": 0.6, "drawStyle": "line", "fillOpacity": 10, "gradientMode": "none", "hideFrom": { "legend": false, "tooltip": false, "viz": false }, "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, "scaleDistribution": { "type": "linear" }, "showPoints": "never", "spanNulls": false, "stacking": { "group": "A", "mode": "none" }, "thresholdsStyle": { "mode": "off" } }, "mappings": [], "thresholds": { "mode": "absolute", "steps": [ { "color": "green", "value": null }, { "color": "red", "value": 80 } ] }, "unit": "ops" }, "overrides": [] }, "gridPos": { "h": 10, "w": 12, "x": 12, "y": 21 }, "id": 33, "options": { "legend": { "calcs": [], "displayMode": "list", "placement": "bottom", "showLegend": true }, "tooltip": { "mode": "multi", "sort": "none" } }, "pluginVersion": "11.3.0", "targets": [ { "datasource": { "uid": "$datasource" }, "editorMode": "code", "expr": "sum(rate(kubelet_runtime_operations_errors_total{job=\"kubelet\", metrics_path=\"/metrics\",operation_type=~\"list_images|pull_image|start_container|list_images|list_containers|version|exec_sync|create_container\"}[$__rate_interval])) by (operation_type)", "format": "time_series", "intervalFactor": 2, "legendFormat": "{{instance}} {{operation_type}}", "range": true, "refId": "A" } ], "title": "Kubelet Operation Error Rate", "transparent": true, "type": "timeseries" } ], "preload": false, "refresh": "30s", "schemaVersion": 40, "tags": [], "templating": { "list": [ { "current": null, "includeAll": false, "label": "Data Source", "name": "datasource", "options": [], "query": "prometheus", "refresh": 1, "regex": "", "type": "datasource" }, { "current": { "text": "registry", "value": "registry" }, "description": "", "hide": 2, "includeAll": false, "label": "container", "name": "container", "options": [ { "selected": true, "text": "registry", "value": "registry" } ], "query": "registry", "type": "custom" }, { "current": {}, "datasource": { "type": "prometheus", "uid": "${datasource}" }, "definition": "label_values(spegel_advertised_images,pod)", "description": "", "hide": 2, "includeAll": false, "label": "pod", "name": "pod", "options": [], "query": { "query": "label_values(spegel_advertised_images,pod)", "refId": "StandardVariableQuery" }, "refresh": 1, "regex": "", "type": "query" }, { "current": {}, "datasource": { "type": "prometheus", "uid": "${datasource}" }, "definition": "label_values(spegel_advertised_keys{job=\"spegel\"},instance)", "includeAll": true, "label": "instance", "multi": true, "name": "instance", "options": [], "query": { "qryType": 1, "query": "label_values(spegel_advertised_keys{job=\"spegel\"},instance)", "refId": "PrometheusVariableQueryEditor-VariableQuery" }, "refresh": 2, "regex": "", "type": "query" } ] }, "time": { "from": "now-3h", "to": "now" }, "timepicker": {}, "timezone": "", "title": "Spegel stateless cluster local OCI registry mirror", "uid": "1iY4QMJVk-psee", "version": 1, "weekStart": "", "gnetId": 18089 } ``` ## /charts/spegel/templates/_helpers.tpl ```tpl path="/charts/spegel/templates/_helpers.tpl" {{/* Expand the name of the chart. */}} {{- define "spegel.name" -}} {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} {{- end }} {{/* Create a default fully qualified app name. We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). If release name contains chart name it will be used as a full name. */}} {{- define "spegel.fullname" -}} {{- if .Values.fullnameOverride }} {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} {{- else }} {{- $name := default .Chart.Name .Values.nameOverride }} {{- if contains $name .Release.Name }} {{- .Release.Name | trunc 63 | trimSuffix "-" }} {{- else }} {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} {{- end }} {{- end }} {{- end }} {{/* Creates the namespace for the chart. Defaults to the Release namespace unless the namespaceOverride is defined. */}} {{- define "spegel.namespace" -}} {{- if .Values.namespaceOverride }} {{- printf "%s" .Values.namespaceOverride -}} {{- else }} {{- printf "%s" .Release.Namespace -}} {{- end }} {{- end }} {{/* Create chart name and version as used by the chart label. */}} {{- define "spegel.chart" -}} {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} {{- end }} {{/* Common labels */}} {{- define "spegel.labels" -}} helm.sh/chart: {{ include "spegel.chart" . }} {{ include "spegel.selectorLabels" . }} {{- if .Chart.AppVersion }} app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} {{- end }} app.kubernetes.io/managed-by: {{ .Release.Service }} {{- with .Values.commonLabels }} {{ toYaml . }} {{- end }} {{- end }} {{/* {{- end }} {{- end }} {{/* Selector labels */}} {{- define "spegel.selectorLabels" -}} app.kubernetes.io/name: {{ include "spegel.name" . }} app.kubernetes.io/instance: {{ .Release.Name }} {{- end }} {{/* Create the name of the service account to use */}} {{- define "spegel.serviceAccountName" -}} {{- default (include "spegel.fullname" .) .Values.serviceAccount.name }} {{- end }} {{/* Image reference */}} {{- define "spegel.image" -}} {{- if .Values.image.digest }} {{- .Values.image.repository }}@{{ .Values.image.digest }} {{- else }} {{- .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }} {{- end }} {{- end }} {{/* Host networking */}} {{- define "networking.nodeIp" -}} {{- if .Values.service.registry.nodeIp -}} value: {{ .Values.service.registry.nodeIp }} {{- else -}} valueFrom: fieldRef: fieldPath: status.hostIP {{- end -}} {{- end -}} ``` ## /charts/spegel/templates/daemonset.yaml ```yaml path="/charts/spegel/templates/daemonset.yaml" apiVersion: apps/v1 kind: DaemonSet metadata: name: {{ include "spegel.fullname" . }} namespace: {{ include "spegel.namespace" . }} labels: {{- include "spegel.labels" . | nindent 4 }} spec: revisionHistoryLimit: {{ .Values.revisionHistoryLimit }} updateStrategy: {{- toYaml .Values.updateStrategy | nindent 4 }} selector: matchLabels: {{- include "spegel.selectorLabels" . | nindent 6 }} template: metadata: {{- with .Values.podAnnotations }} annotations: {{- toYaml . | nindent 8 }} {{- end }} labels: {{- include "spegel.selectorLabels" . | nindent 8 }} {{- with .Values.commonLabels }} {{- toYaml . | nindent 8 }} {{- end }} spec: {{- with .Values.imagePullSecrets }} imagePullSecrets: {{- toYaml . | nindent 8 }} {{- end }} serviceAccountName: {{ include "spegel.serviceAccountName" . }} securityContext: {{- toYaml .Values.podSecurityContext | nindent 8 }} priorityClassName: {{ .Values.priorityClassName }} {{- if .Values.spegel.containerdMirrorAdd }} initContainers: - name: configuration image: "{{ include "spegel.image" . }}" imagePullPolicy: {{ .Values.image.pullPolicy }} securityContext: {{- toYaml .Values.securityContext | nindent 12 }} args: - configuration - --log-level={{ .Values.spegel.logLevel }} - --containerd-registry-config-path={{ .Values.spegel.containerdRegistryConfigPath }} {{- with .Values.spegel.mirroredRegistries }} - --mirrored-registries {{- range . }} - {{ . | quote }} {{- end }} {{- end }} - --mirror-targets - http://$(NODE_IP):{{ .Values.service.registry.hostPort }} - http://$(NODE_IP):{{ .Values.service.registry.nodePort }} {{- with .Values.spegel.additionalMirrorTargets }} {{- range . }} - {{ . | quote }} {{- end }} {{- end }} - --resolve-tags={{ .Values.spegel.resolveTags }} - --prepend-existing={{ .Values.spegel.prependExisting }} env: - name: NODE_IP {{- include "networking.nodeIp" . | nindent 10 }} resources: {{- toYaml .Values.resources | nindent 10 }} volumeMounts: - name: containerd-config mountPath: {{ .Values.spegel.containerdRegistryConfigPath }} {{- if .Values.basicAuthSecretName }} - name: basic-auth mountPath: "/etc/secrets/basic-auth" readOnly: true {{- end }} {{- end }} containers: - name: registry image: "{{ include "spegel.image" . }}" imagePullPolicy: {{ .Values.image.pullPolicy }} securityContext: {{- toYaml .Values.securityContext | nindent 12 }} args: - registry - --log-level={{ .Values.spegel.logLevel }} - --mirror-resolve-retries={{ .Values.spegel.mirrorResolveRetries }} - --mirror-resolve-timeout={{ .Values.spegel.mirrorResolveTimeout }} - --registry-addr=:{{ .Values.service.registry.port }} - --router-addr=:{{ .Values.service.router.port }} - --metrics-addr=:{{ .Values.service.metrics.port }} {{- with .Values.spegel.mirroredRegistries }} - --mirrored-registries {{- range . }} - {{ . | quote }} {{- end }} {{- end }} - --containerd-sock={{ .Values.spegel.containerdSock }} - --containerd-namespace={{ .Values.spegel.containerdNamespace }} - --containerd-registry-config-path={{ .Values.spegel.containerdRegistryConfigPath }} - --bootstrap-kind=dns - --dns-bootstrap-domain={{ include "spegel.fullname" . }}-bootstrap.{{ include "spegel.namespace" . }}.svc.{{ .Values.clusterDomain }} - --resolve-latest-tag={{ .Values.spegel.resolveLatestTag }} {{- with .Values.spegel.containerdContentPath }} - --containerd-content-path={{ . }} {{- end }} - --debug-web-enabled={{ .Values.spegel.debugWebEnabled }} env: {{- if ((.Values.resources).limits).cpu }} - name: GOMAXPROCS valueFrom: resourceFieldRef: resource: limits.cpu divisor: 1 {{- end }} {{- if ((.Values.resources).limits).memory }} - name: GOMEMLIMIT valueFrom: resourceFieldRef: resource: limits.memory divisor: 1 {{- end }} - name: NODE_IP {{- include "networking.nodeIp" . | nindent 10 }} ports: - name: registry containerPort: {{ .Values.service.registry.port }} hostPort: {{ .Values.service.registry.hostPort }} protocol: TCP - name: router containerPort: {{ .Values.service.router.port }} protocol: TCP - name: metrics containerPort: {{ .Values.service.metrics.port }} protocol: TCP # Startup may take a bit longer on bootsrap as Pods need to find each other. # This is why the startup proben is a bit more forgiving, while hitting the endpoint more often. startupProbe: periodSeconds: 3 failureThreshold: 60 httpGet: path: /healthz port: registry readinessProbe: httpGet: path: /healthz port: registry volumeMounts: {{- if .Values.basicAuthSecretName }} - name: basic-auth mountPath: "/etc/secrets/basic-auth" readOnly: true {{- end }} - name: containerd-sock mountPath: {{ .Values.spegel.containerdSock }} {{- with .Values.spegel.containerdContentPath }} - name: containerd-content mountPath: {{ . }} readOnly: true {{- end }} resources: {{- toYaml .Values.resources | nindent 10 }} volumes: {{- with .Values.basicAuthSecretName }} - name: basic-auth secret: secretName: {{ . }} {{- end }} - name: containerd-sock hostPath: path: {{ .Values.spegel.containerdSock }} type: Socket {{- with .Values.spegel.containerdContentPath }} - name: containerd-content hostPath: path: {{ . }} type: Directory {{- end }} {{- if .Values.spegel.containerdMirrorAdd }} - name: containerd-config hostPath: path: {{ .Values.spegel.containerdRegistryConfigPath }} type: DirectoryOrCreate {{- end }} {{- with .Values.nodeSelector }} nodeSelector: {{- toYaml . | nindent 8 }} {{- end }} {{- with .Values.affinity }} affinity: {{- toYaml . | nindent 8 }} {{- end }} {{- with .Values.tolerations }} tolerations: {{- toYaml . | nindent 8 }} {{- end }} ``` ## /charts/spegel/templates/grafana-dashboard.yaml ```yaml path="/charts/spegel/templates/grafana-dashboard.yaml" {{- if .Values.grafanaDashboard.enabled }} apiVersion: v1 kind: ConfigMap metadata: name: {{ include "spegel.fullname" . }}-dashboard namespace: {{ include "spegel.namespace" . }} labels: {{ .Values.grafanaDashboard.sidecarLabel }}: {{ .Values.grafanaDashboard.sidecarLabelValue | quote }} {{- include "spegel.labels" . | nindent 4 }} {{- with .Values.grafanaDashboard.annotations }} annotations: {{- toYaml . | nindent 4 }} {{- end }} data: spegel.json: |- {{ .Files.Get "monitoring/grafana-dashboard.json" | indent 6 }} {{- end }} ``` ## /charts/spegel/templates/post-delete-hook.yaml ```yaml path="/charts/spegel/templates/post-delete-hook.yaml" {{- if .Values.spegel.containerdMirrorAdd }} apiVersion: apps/v1 kind: DaemonSet metadata: name: {{ include "spegel.fullname" . }}-cleanup namespace: {{ include "spegel.namespace" . }} labels: app.kubernetes.io/component: cleanup {{- include "spegel.labels" . | nindent 4 }} annotations: helm.sh/hook: "post-delete" helm.sh/hook-delete-policy: "before-hook-creation, hook-succeeded" helm.sh/hook-weight: "0" spec: selector: matchLabels: app.kubernetes.io/component: cleanup {{- include "spegel.selectorLabels" . | nindent 6 }} template: metadata: labels: app.kubernetes.io/component: cleanup {{- include "spegel.selectorLabels" . | nindent 8 }} spec: {{- with .Values.imagePullSecrets }} imagePullSecrets: {{- toYaml . | nindent 8 }} {{- end }} securityContext: {{- toYaml .Values.podSecurityContext | nindent 8 }} priorityClassName: {{ .Values.priorityClassName }} containers: - name: cleanup image: "{{ include "spegel.image" . }}" imagePullPolicy: {{ .Values.image.pullPolicy }} args: - cleanup - --containerd-registry-config-path={{ .Values.spegel.containerdRegistryConfigPath }} - --addr=:{{ .Values.service.cleanup.port }} readinessProbe: httpGet: path: /healthz port: readiness ports: - name: readiness containerPort: {{ .Values.service.cleanup.port }} protocol: TCP volumeMounts: - name: containerd-config mountPath: {{ .Values.spegel.containerdRegistryConfigPath }} volumes: - name: containerd-config hostPath: path: {{ .Values.spegel.containerdRegistryConfigPath }} type: DirectoryOrCreate {{- with .Values.tolerations }} tolerations: {{- toYaml . | nindent 8 }} {{- end }} --- apiVersion: v1 kind: Service metadata: name: {{ include "spegel.fullname" . }}-cleanup namespace: {{ include "spegel.namespace" . }} labels: app.kubernetes.io/component: cleanup {{- include "spegel.labels" . | nindent 4 }} annotations: helm.sh/hook: "post-delete" helm.sh/hook-delete-policy: "before-hook-creation, hook-succeeded" helm.sh/hook-weight: "0" spec: selector: app.kubernetes.io/component: cleanup {{- include "spegel.selectorLabels" . | nindent 4 }} clusterIP: None publishNotReadyAddresses: true ports: - name: readiness port: {{ .Values.service.cleanup.port }} protocol: TCP --- apiVersion: v1 kind: Pod metadata: name: {{ include "spegel.fullname" . }}-cleanup-wait namespace: {{ include "spegel.namespace" . }} labels: app.kubernetes.io/component: cleanup-wait {{- include "spegel.labels" . | nindent 4 }} annotations: helm.sh/hook: "post-delete" helm.sh/hook-delete-policy: "before-hook-creation, hook-succeeded" helm.sh/hook-weight: "1" spec: containers: - name: cleanup-wait image: "{{ include "spegel.image" . }}" imagePullPolicy: {{ .Values.image.pullPolicy }} args: - cleanup-wait - --probe-endpoint={{ include "spegel.fullname" . }}-cleanup.{{ include "spegel.namespace" . }}.svc.{{ .Values.clusterDomain }}:{{ .Values.service.cleanup.port }} restartPolicy: Never terminationGracePeriodSeconds: 0 {{- end }} ``` ## /charts/spegel/templates/rbac.yaml ```yaml path="/charts/spegel/templates/rbac.yaml" apiVersion: v1 kind: ServiceAccount metadata: name: {{ include "spegel.serviceAccountName" . }} namespace: {{ include "spegel.namespace" . }} labels: {{- include "spegel.labels" . | nindent 4 }} {{- with .Values.serviceAccount.annotations }} annotations: {{- toYaml . | nindent 4 }} {{- end }} ``` ## /charts/spegel/templates/service.yaml ```yaml path="/charts/spegel/templates/service.yaml" apiVersion: v1 kind: Service metadata: name: {{ include "spegel.fullname" . }} namespace: {{ include "spegel.namespace" . }} labels: app.kubernetes.io/component: metrics {{- include "spegel.labels" . | nindent 4 }} spec: selector: {{- include "spegel.selectorLabels" . | nindent 4 }} ports: - name: metrics port: {{ .Values.service.metrics.port }} targetPort: metrics protocol: TCP --- apiVersion: v1 kind: Service metadata: name: {{ include "spegel.fullname" . }}-registry namespace: {{ include "spegel.namespace" . }} labels: {{- include "spegel.labels" . | nindent 4 }} {{- if .Values.service.registry.topologyAwareHintsEnabled }} annotations: service.kubernetes.io/topology-mode: "auto" {{- end }} spec: type: NodePort selector: {{- include "spegel.selectorLabels" . | nindent 4 }} ports: - name: registry port: {{ .Values.service.registry.port }} targetPort: registry nodePort: {{ .Values.service.registry.nodePort }} protocol: TCP --- apiVersion: v1 kind: Service metadata: name: {{ include "spegel.fullname" . }}-bootstrap namespace: {{ include "spegel.namespace" . }} labels: {{- include "spegel.labels" . | nindent 4 }} spec: selector: {{- include "spegel.selectorLabels" . | nindent 4 }} clusterIP: None publishNotReadyAddresses: true ports: - name: router port: {{ .Values.service.router.port }} protocol: TCP ``` ## /charts/spegel/templates/servicemonitor.yaml ```yaml path="/charts/spegel/templates/servicemonitor.yaml" {{- if .Values.serviceMonitor.enabled }} apiVersion: monitoring.coreos.com/v1 kind: ServiceMonitor metadata: name: {{ include "spegel.fullname" . }} namespace: {{ include "spegel.namespace" . }} labels: {{- include "spegel.labels" . | nindent 4 }} {{- if .Values.serviceMonitor.labels -}} {{ toYaml .Values.serviceMonitor.labels | nindent 4}} {{- end }} spec: selector: matchLabels: app.kubernetes.io/component: metrics {{- include "spegel.selectorLabels" . | nindent 6 }} endpoints: - port: metrics interval: {{ .Values.serviceMonitor.interval }} scrapeTimeout: {{ .Values.serviceMonitor.scrapeTimeout }} {{- with .Values.serviceMonitor.relabelings }} relabelings: {{- toYaml . | nindent 8 }} {{- end }} {{- with .Values.serviceMonitor.metricRelabelings }} metricRelabelings: {{- toYaml . | nindent 8 }} {{- end }} {{- end }} ``` ## /charts/spegel/templates/verticalpodautoscaler.yaml ```yaml path="/charts/spegel/templates/verticalpodautoscaler.yaml" {{- if and (.Capabilities.APIVersions.Has "autoscaling.k8s.io/v1") (.Values.verticalPodAutoscaler.enabled) }} apiVersion: autoscaling.k8s.io/v1 kind: VerticalPodAutoscaler metadata: name: {{ include "spegel.fullname" . }} namespace: {{ include "spegel.namespace" . }} labels: {{- include "spegel.labels" . | nindent 4 }} spec: {{- with .Values.verticalPodAutoscaler.recommenders }} recommenders: {{- toYaml . | nindent 4 }} {{- end }} resourcePolicy: containerPolicies: - containerName: registry {{- with .Values.verticalPodAutoscaler.controlledResources }} controlledResources: {{- toYaml . | nindent 8 }} {{- end }} {{- if .Values.verticalPodAutoscaler.controlledValues }} controlledValues: {{ .Values.verticalPodAutoscaler.controlledValues }} {{- end }} {{- if .Values.verticalPodAutoscaler.maxAllowed }} maxAllowed: {{- toYaml .Values.verticalPodAutoscaler.maxAllowed | nindent 8 }} {{- end }} {{- if .Values.verticalPodAutoscaler.minAllowed }} minAllowed: {{- toYaml .Values.verticalPodAutoscaler.minAllowed | nindent 8 }} {{- end }} targetRef: apiVersion: apps/v1 kind: DaemonSet name: {{ include "spegel.fullname" . }} {{- with .Values.verticalPodAutoscaler.updatePolicy }} updatePolicy: {{- toYaml . | nindent 4 }} {{- end }} {{- end }} ``` ## /charts/spegel/values.yaml ```yaml path="/charts/spegel/values.yaml" image: # -- Image repository. repository: ghcr.io/spegel-org/spegel # -- Image Pull Policy. pullPolicy: IfNotPresent # -- Overrides the image tag whose default is the chart appVersion. tag: "" # -- Image digest. digest: "" # -- Image Pull Secrets imagePullSecrets: [] # -- Overrides the name of the chart. nameOverride: "" # -- Overrides the full name of the chart. fullnameOverride: "" # -- Overrides the namespace where spegel resources are installed. namespaceOverride: "" serviceAccount: # -- Annotations to add to the service account annotations: {} # -- The name of the service account to use. # If not set and create is true, a name is generated using the fullname template. name: "" # -- Annotations to add to the pod. podAnnotations: {} # -- Security context for the pod. podSecurityContext: {} # fsGroup: 2000 # -- The number of old history to retain to allow rollback. revisionHistoryLimit: 10 # -- Security context for the Spegel container. securityContext: {} # capabilities: # drop: # - ALL # readOnlyRootFilesystem: true # runAsNonRoot: true # runAsUser: 1000 service: registry: # -- Override the NODE_ID environment variable. It defaults to the field status.hostIP nodeIp: "" # -- Port to expose the registry via the service. port: 5000 # -- Node port to expose the registry via the service. nodePort: 30021 # -- Local host port to expose the registry. hostPort: 30020 # -- If true adds topology aware hints annotation to node port service. topologyAwareHintsEnabled: true router: # -- Port to expose the router via the service. port: 5001 metrics: # -- Port to expose the metrics via the service. port: 9090 cleanup: # -- Port to expose cleanup probe on. port: 8080 # -- Resource requests and limits for the Spegel container. resources: requests: memory: 128Mi limits: memory: 128Mi # -- Node selector for pod assignment. nodeSelector: kubernetes.io/os: linux # -- An update strategy to replace existing pods with new pods. updateStrategy: {} # type: RollingUpdate # rollingUpdate: # maxSurge: 0 # maxUnavailable: 1 # -- Tolerations for pod assignment. tolerations: - key: CriticalAddonsOnly operator: Exists - effect: NoExecute operator: Exists - effect: NoSchedule operator: Exists # -- Affinity settings for pod assignment. affinity: {} # -- Common labels to apply to all rendered resources. commonLabels: {} # -- Domain configured for service domain names. clusterDomain: cluster.local. serviceMonitor: # -- If true creates a Prometheus Service Monitor. enabled: false # -- Prometheus scrape interval. interval: 60s # -- Prometheus scrape interval timeout. scrapeTimeout: 30s # -- Service monitor specific labels for prometheus to discover servicemonitor. labels: {} # -- List of relabeling rules to apply the target’s metadata labels. relabelings: [] # -- List of relabeling rules to apply to the samples before ingestion. metricRelabelings: [] grafanaDashboard: # -- If true creates a Grafana dashboard. enabled: false # -- Label that ConfigMaps should have to be loaded as dashboards. sidecarLabel: "grafana_dashboard" # -- Label value that ConfigMaps should have to be loaded as dashboards. sidecarLabelValue: "1" # -- Annotations that ConfigMaps can have to get configured in Grafana, # See: sidecar.dashboards.folderAnnotation for specifying the dashboard folder. # https://github.com/grafana/helm-charts/tree/main/charts/grafana annotations: {} # -- Priority class name to use for the pod. priorityClassName: system-node-critical # -- Name of secret containing basic authentication credentials for registry. basicAuthSecretName: "" spegel: # -- Minimum log level to output. Value should be DEBUG, INFO, WARN, or ERROR. logLevel: "INFO" # -- Registries for which mirror configuration will be created. Empty means all registires will be mirrored. mirroredRegistries: [] # - https://docker.io # - https://ghcr.io # -- Additional target mirror registries other than Spegel. additionalMirrorTargets: [] # -- Max amount of mirrors to attempt. mirrorResolveRetries: 3 # -- Max duration spent finding a mirror. mirrorResolveTimeout: "20ms" # -- Path to Containerd socket. containerdSock: "/run/containerd/containerd.sock" # -- Containerd namespace where images are stored. containerdNamespace: "k8s.io" # -- Path to Containerd mirror configuration. containerdRegistryConfigPath: "/etc/containerd/certs.d" # -- Path to Containerd content store.. containerdContentPath: "/var/lib/containerd/io.containerd.content.v1.content" # -- If true Spegel will add mirror configuration to the node. containerdMirrorAdd: true # -- When true Spegel will resolve tags to digests. resolveTags: true # -- When true latest tags will be resolved to digests. resolveLatestTag: true # -- When true existing mirror configuration will be kept and Spegel will prepend it's configuration. prependExisting: false # -- When true enables debug web page. debugWebEnabled: false verticalPodAutoscaler: # -- If true creates a Vertical Pod Autoscaler. enabled: false # -- Recommender responsible for generating recommendation for the object. # List should be empty (then the default recommender will generate the recommendation) # or contain exactly one recommender. recommenders: [] # - name: custom-recommender-performance # -- List of resources that the vertical pod autoscaler can control. Defaults to cpu and memory controlledResources: [] # -- Specifies which resource values should be controlled: RequestsOnly or RequestsAndLimits. controlledValues: RequestsAndLimits # -- Define the max allowed resources for the pod maxAllowed: {} # cpu: 100m # memory: 128Mi # -- Define the min allowed resources for the pod minAllowed: {} # cpu: 100m # memory: 128Mi updatePolicy: # -- Specifies minimal number of replicas which need to be alive for VPA Updater to attempt pod eviction minReplicas: 2 # -- Specifies whether recommended updates are applied when a Pod is started and whether recommended updates # are applied during the life of a Pod. Possible values are "Off", "Initial", "Recreate", and "Auto". updateMode: Auto ``` ## /go.mod ```mod path="/go.mod" module github.com/spegel-org/spegel go 1.24.1 require ( github.com/alexflint/go-arg v1.5.1 github.com/containerd/containerd/api v1.8.0 github.com/containerd/containerd/v2 v2.0.5 github.com/containerd/errdefs v1.0.0 github.com/containerd/typeurl/v2 v2.2.3 github.com/go-logr/logr v1.4.2 github.com/ipfs/go-cid v0.5.0 github.com/libp2p/go-libp2p v0.41.1 github.com/libp2p/go-libp2p-kad-dht v0.31.0 github.com/multiformats/go-multiaddr v0.15.0 github.com/multiformats/go-multicodec v0.9.0 github.com/multiformats/go-multihash v0.2.3 github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/image-spec v1.1.1 github.com/pelletier/go-toml/v2 v2.2.4 github.com/prometheus/client_golang v1.22.0 github.com/prometheus/common v0.63.0 github.com/stretchr/testify v1.10.0 go.etcd.io/bbolt v1.4.0 golang.org/x/sync v0.13.0 google.golang.org/grpc v1.72.0 k8s.io/apimachinery v0.33.0 k8s.io/cri-api v0.33.0 k8s.io/klog/v2 v2.130.1 ) require ( dario.cat/mergo v1.0.1 // indirect github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 // indirect github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20231105174938-2b5cbb29f3e2 // indirect github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver/v3 v3.3.1 // indirect github.com/Masterminds/sprig/v3 v3.3.0 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/Microsoft/hcsshim v0.12.9 // indirect github.com/alexflint/go-scalar v1.2.0 // indirect github.com/benbjohnson/clock v1.3.5 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/containerd/cgroups v1.1.0 // indirect github.com/containerd/cgroups/v3 v3.0.3 // indirect github.com/containerd/continuity v0.4.4 // indirect github.com/containerd/errdefs/pkg v0.3.0 // indirect github.com/containerd/fifo v1.1.0 // indirect github.com/containerd/log v0.1.0 // indirect github.com/containerd/platforms v1.0.0-rc.1 // indirect github.com/containerd/plugin v1.0.0 // indirect github.com/containerd/ttrpc v1.2.7 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect github.com/distribution/reference v0.6.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/elastic/gosigar v0.14.3 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/flynn/noise v1.1.0 // indirect github.com/francoispqt/gojay v1.2.13 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/gobwas/glob v0.2.3 // indirect github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/gopacket v1.1.19 // indirect github.com/google/pprof v0.0.0-20250208200701-d0013a598941 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/websocket v1.5.3 // indirect github.com/hashicorp/golang-lru v1.0.2 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/huandu/xstrings v1.5.0 // indirect github.com/huin/goupnp v1.3.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/ipfs/boxo v0.29.1 // indirect github.com/ipfs/go-datastore v0.8.2 // indirect github.com/ipfs/go-log v1.0.5 // indirect github.com/ipfs/go-log/v2 v2.5.1 // indirect github.com/ipld/go-ipld-prime v0.21.0 // indirect github.com/jackpal/go-nat-pmp v1.0.2 // indirect github.com/jbenet/go-temp-err-catcher v0.1.0 // indirect github.com/klauspost/compress v1.18.0 // indirect github.com/klauspost/cpuid/v2 v2.2.10 // indirect github.com/koron/go-ssdp v0.0.5 // indirect github.com/libp2p/go-buffer-pool v0.1.0 // indirect github.com/libp2p/go-cidranger v1.1.0 // indirect github.com/libp2p/go-flow-metrics v0.2.0 // indirect github.com/libp2p/go-libp2p-asn-util v0.4.1 // indirect github.com/libp2p/go-libp2p-kbucket v0.7.0 // indirect github.com/libp2p/go-libp2p-record v0.3.1 // indirect github.com/libp2p/go-libp2p-routing-helpers v0.7.5 // indirect github.com/libp2p/go-msgio v0.3.0 // indirect github.com/libp2p/go-netroute v0.2.2 // indirect github.com/libp2p/go-reuseport v0.4.0 // indirect github.com/libp2p/go-yamux/v5 v5.0.0 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/miekg/dns v1.1.63 // indirect github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b // indirect github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc // indirect github.com/minio/sha256-simd v1.0.1 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/moby/locker v1.0.1 // indirect github.com/moby/sys/mountinfo v0.7.2 // indirect github.com/moby/sys/sequential v0.6.0 // indirect github.com/moby/sys/signal v0.7.1 // indirect github.com/moby/sys/user v0.3.0 // indirect github.com/moby/sys/userns v0.1.0 // indirect github.com/mr-tron/base58 v1.2.0 // indirect github.com/multiformats/go-base32 v0.1.0 // indirect github.com/multiformats/go-base36 v0.2.0 // indirect github.com/multiformats/go-multiaddr-dns v0.4.1 // indirect github.com/multiformats/go-multiaddr-fmt v0.1.0 // indirect github.com/multiformats/go-multibase v0.2.0 // indirect github.com/multiformats/go-multistream v0.6.0 // indirect github.com/multiformats/go-varint v0.0.7 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/norwoodj/helm-docs v1.14.2 // indirect github.com/onsi/ginkgo/v2 v2.22.2 // indirect github.com/opencontainers/runtime-spec v1.2.0 // indirect github.com/opencontainers/selinux v1.11.1 // indirect github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect github.com/pion/datachannel v1.5.10 // indirect github.com/pion/dtls/v2 v2.2.12 // indirect github.com/pion/dtls/v3 v3.0.4 // indirect github.com/pion/ice/v4 v4.0.8 // indirect github.com/pion/interceptor v0.1.37 // indirect github.com/pion/logging v0.2.3 // indirect github.com/pion/mdns/v2 v2.0.7 // indirect github.com/pion/randutil v0.1.0 // indirect github.com/pion/rtcp v1.2.15 // indirect github.com/pion/rtp v1.8.11 // indirect github.com/pion/sctp v1.8.37 // indirect github.com/pion/sdp/v3 v3.0.10 // indirect github.com/pion/srtp/v3 v3.0.4 // indirect github.com/pion/stun v0.6.1 // indirect github.com/pion/stun/v3 v3.0.0 // indirect github.com/pion/transport/v2 v2.2.10 // indirect github.com/pion/transport/v3 v3.0.7 // indirect github.com/pion/turn/v4 v4.0.0 // indirect github.com/pion/webrtc/v4 v4.0.10 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/polydawn/refmt v0.89.0 // indirect github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/procfs v0.15.1 // indirect github.com/quic-go/qpack v0.5.1 // indirect github.com/quic-go/quic-go v0.50.1 // indirect github.com/quic-go/webtransport-go v0.8.1-0.20241018022711-4ac2c9250e66 // indirect github.com/raulk/go-watchdog v1.3.0 // indirect github.com/shopspring/decimal v1.4.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect github.com/spf13/afero v1.14.0 // indirect github.com/spf13/cast v1.7.0 // indirect github.com/spf13/cobra v1.8.1 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.6 // indirect github.com/spf13/viper v1.16.0 // indirect github.com/subosito/gotenv v1.4.2 // indirect github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 // indirect github.com/wlynxg/anet v0.0.5 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 // indirect go.opentelemetry.io/otel v1.34.0 // indirect go.opentelemetry.io/otel/metric v1.34.0 // indirect go.opentelemetry.io/otel/trace v1.34.0 // indirect go.uber.org/dig v1.18.0 // indirect go.uber.org/fx v1.23.0 // indirect go.uber.org/mock v0.5.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect golang.org/x/crypto v0.36.0 // indirect golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect golang.org/x/mod v0.24.0 // indirect golang.org/x/net v0.38.0 // indirect golang.org/x/sys v0.31.0 // indirect golang.org/x/text v0.23.0 // indirect golang.org/x/tools v0.31.0 // indirect gonum.org/v1/gonum v0.15.1 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a // indirect google.golang.org/protobuf v1.36.6 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect helm.sh/helm/v3 v3.17.3 // indirect lukechampine.com/blake3 v1.4.0 // indirect ) tool github.com/norwoodj/helm-docs/cmd/helm-docs ``` ## /go.sum ```sum path="/go.sum" cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo= dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU= dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU= dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4= dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU= git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20231105174938-2b5cbb29f3e2 h1:dIScnXFlF784X79oi7MzVT6GWqr/W1uUt0pB5CsDs9M= github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20231105174938-2b5cbb29f3e2/go.mod h1:gCLVsLfv1egrcZu+GoJATN5ts75F2s62ih/457eWzOw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4= github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs= github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/Microsoft/hcsshim v0.12.9 h1:2zJy5KA+l0loz1HzEGqyNnjd3fyZA31ZBCGKacp6lLg= github.com/Microsoft/hcsshim v0.12.9/go.mod h1:fJ0gkFAna6ukt0bLdKB8djt4XIJhF/vEPuoIWYVvZ8Y= github.com/alexflint/go-arg v1.5.1 h1:nBuWUCpuRy0snAG+uIJ6N0UvYxpxA0/ghA/AaHxlT8Y= github.com/alexflint/go-arg v1.5.1/go.mod h1:A7vTJzvjoaSTypg4biM5uYNTkJ27SkNTArtYXnlqVO8= github.com/alexflint/go-scalar v1.2.0 h1:WR7JPKkeNpnYIOfHRa7ivM21aWAdHD0gEWHCx+WQBRw= github.com/alexflint/go-scalar v1.2.0/go.mod h1:LoFvNMqS1CPrMVltza4LvnGKhaSpc3oyLEBUZVhhS2o= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o= github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/containerd/cgroups v0.0.0-20201119153540-4cbc285b3327/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE= github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM= github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw= github.com/containerd/cgroups/v3 v3.0.3 h1:S5ByHZ/h9PMe5IOQoN7E+nMc2UcLEM/V48DGDJ9kip0= github.com/containerd/cgroups/v3 v3.0.3/go.mod h1:8HBe7V3aWGLFPd/k03swSIsGjZhHI2WzJmticMgVuz0= github.com/containerd/containerd/api v1.8.0 h1:hVTNJKR8fMc/2Tiw60ZRijntNMd1U+JVMyTRdsD2bS0= github.com/containerd/containerd/api v1.8.0/go.mod h1:dFv4lt6S20wTu/hMcP4350RL87qPWLVa/OHOwmmdnYc= github.com/containerd/containerd/v2 v2.0.5 h1:2vg/TjUXnaohAxiHnthQg8K06L9I4gdYEMcOLiMc8BQ= github.com/containerd/containerd/v2 v2.0.5/go.mod h1:Qqo0UN43i2fX1FLkrSTCg6zcHNfjN7gEnx3NPRZI+N0= github.com/containerd/continuity v0.4.4 h1:/fNVfTJ7wIl/YPMHjf+5H32uFhl63JucB34PlCpMKII= github.com/containerd/continuity v0.4.4/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE= github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= github.com/containerd/fifo v1.1.0 h1:4I2mbh5stb1u6ycIABlBw9zgtlK8viPI9QkQNRQEEmY= github.com/containerd/fifo v1.1.0/go.mod h1:bmC4NWMbXlt2EZ0Hc7Fx7QzTFxgPID13eH0Qu+MAb2o= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/containerd/platforms v1.0.0-rc.1 h1:83KIq4yy1erSRgOVHNk1HYdPvzdJ5CnsWaRoJX4C41E= github.com/containerd/platforms v1.0.0-rc.1/go.mod h1:J71L7B+aiM5SdIEqmd9wp6THLVRzJGXfNuWCZCllLA4= github.com/containerd/plugin v1.0.0 h1:c8Kf1TNl6+e2TtMHZt+39yAPDbouRH9WAToRjex483Y= github.com/containerd/plugin v1.0.0/go.mod h1:hQfJe5nmWfImiqT1q8Si3jLv3ynMUIBB47bQ+KexvO8= github.com/containerd/ttrpc v1.2.7 h1:qIrroQvuOL9HQ1X6KHe2ohc7p+HP/0VE6XPU7elJRqQ= github.com/containerd/ttrpc v1.2.7/go.mod h1:YCXHsb32f+Sq5/72xHubdiJRQY9inL4a4ZQrAbN1q9o= github.com/containerd/typeurl/v2 v2.2.3 h1:yNA/94zxWdvYACdYO8zofhrTVuQY73fFU1y++dYSw40= github.com/containerd/typeurl/v2 v2.2.3/go.mod h1:95ljDnPfD3bAbDJRugOiShd/DlAAsxGtUBhJxIn7SCk= github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c h1:pFUpOrbxDR6AkioZ1ySsx5yxlDQZ8stG2b88gTPxgJU= github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c/go.mod h1:6UhI8N9EjYm1c2odKpFpAYeR8dsBeM7PtzQhRgxRr9U= github.com/decred/dcrd/crypto/blake256 v1.1.0 h1:zPMNGQCm0g4QTY27fOCorQW7EryeQ/U0x++OzVrdms8= github.com/decred/dcrd/crypto/blake256 v1.1.0/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/elastic/gosigar v0.12.0/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0WKyPWoMs= github.com/elastic/gosigar v0.14.3 h1:xwkKwPia+hSfg9GqrCUKYdId102m9qTJIIr7egmK/uo= github.com/elastic/gosigar v0.14.3/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0WKyPWoMs= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/flynn/noise v1.1.0 h1:KjPQoQCEFdZDiP03phOvGi11+SVVhBG2wOWAorLsstg= github.com/flynn/noise v1.1.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwUTag= github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk= github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20250208200701-d0013a598941 h1:43XjGa6toxLpeksjcxs1jIoIyr+vUfOqY2c6HB4bpoc= github.com/google/pprof v0.0.0-20250208200701-d0013a598941/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c h1:7lF+Vz0LqiRidnzC1Oq86fpX1q/iEv2KJdrCtttYjT4= github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc= github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/ipfs/boxo v0.29.1 h1:z61ZT4YDfTHLjXTsu/+3wvJ8aJlExthDSOCpx6Nh8xc= github.com/ipfs/boxo v0.29.1/go.mod h1:MkDJStXiJS9U99cbAijHdcmwNfVn5DKYBmQCOgjY2NU= github.com/ipfs/go-block-format v0.2.0 h1:ZqrkxBA2ICbDRbK8KJs/u0O3dlp6gmAuuXUJNiW1Ycs= github.com/ipfs/go-block-format v0.2.0/go.mod h1:+jpL11nFx5A/SPpsoBn6Bzkra/zaArfSmsknbPMYgzM= github.com/ipfs/go-cid v0.5.0 h1:goEKKhaGm0ul11IHA7I6p1GmKz8kEYniqFopaB5Otwg= github.com/ipfs/go-cid v0.5.0/go.mod h1:0L7vmeNXpQpUS9vt+yEARkJ8rOg43DF3iPgn4GIN0mk= github.com/ipfs/go-datastore v0.8.2 h1:Jy3wjqQR6sg/LhyY0NIePZC3Vux19nLtg7dx0TVqr6U= github.com/ipfs/go-datastore v0.8.2/go.mod h1:W+pI1NsUsz3tcsAACMtfC+IZdnQTnC/7VfPoJBQuts0= github.com/ipfs/go-detect-race v0.0.1 h1:qX/xay2W3E4Q1U7d9lNs1sU9nvguX0a7319XbyQ6cOk= github.com/ipfs/go-detect-race v0.0.1/go.mod h1:8BNT7shDZPo99Q74BpGMK+4D8Mn4j46UU0LZ723meps= github.com/ipfs/go-ipfs-util v0.0.3 h1:2RFdGez6bu2ZlZdI+rWfIdbQb1KudQp3VGwPtdNCmE0= github.com/ipfs/go-ipfs-util v0.0.3/go.mod h1:LHzG1a0Ig4G+iZ26UUOMjHd+lfM84LZCrn17xAKWBvs= github.com/ipfs/go-log v1.0.5 h1:2dOuUCB1Z7uoczMWgAyDck5JLb72zHzrMnGnCNNbvY8= github.com/ipfs/go-log v1.0.5/go.mod h1:j0b8ZoR+7+R99LD9jZ6+AJsrzkPbSXbZfGakb5JPtIo= github.com/ipfs/go-log/v2 v2.1.3/go.mod h1:/8d0SH3Su5Ooc31QlL1WysJhvyOTDCjcCZ9Axpmri6g= github.com/ipfs/go-log/v2 v2.5.1 h1:1XdUzF7048prq4aBjDQQ4SL5RxftpRGdXhNRwKSAlcY= github.com/ipfs/go-log/v2 v2.5.1/go.mod h1:prSpmC1Gpllc9UYWxDiZDreBYw7zp4Iqp1kOLU9U5UI= github.com/ipfs/go-test v0.2.1 h1:/D/a8xZ2JzkYqcVcV/7HYlCnc7bv/pKHQiX5TdClkPE= github.com/ipfs/go-test v0.2.1/go.mod h1:dzu+KB9cmWjuJnXFDYJwC25T3j1GcN57byN+ixmK39M= github.com/ipld/go-ipld-prime v0.21.0 h1:n4JmcpOlPDIxBcY037SVfpd1G+Sj1nKZah0m6QH9C2E= github.com/ipld/go-ipld-prime v0.21.0/go.mod h1:3RLqy//ERg/y5oShXXdx5YIp50cFGOanyMctpPjsvxQ= github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= github.com/jbenet/go-temp-err-catcher v0.1.0 h1:zpb3ZH6wIE8Shj2sKS+khgRvf7T7RABoLk/+KKHggpk= github.com/jbenet/go-temp-err-catcher v0.1.0/go.mod h1:0kJRvmDZXNMIiJirNPEYfhpPwbGVtZVWC34vc5WLsDk= github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/koron/go-ssdp v0.0.5 h1:E1iSMxIs4WqxTbIBLtmNBeOOC+1sCIXQeqTWVnpmwhk= github.com/koron/go-ssdp v0.0.5/go.mod h1:Qm59B7hpKpDqfyRNWRNr00jGwLdXjDyZh6y7rH6VS0w= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.3/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8= github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg= github.com/libp2p/go-cidranger v1.1.0 h1:ewPN8EZ0dd1LSnrtuwd4709PXVcITVeuwbag38yPW7c= github.com/libp2p/go-cidranger v1.1.0/go.mod h1:KWZTfSr+r9qEo9OkI9/SIEeAtw+NNoU0dXIXt15Okic= github.com/libp2p/go-flow-metrics v0.2.0 h1:EIZzjmeOE6c8Dav0sNv35vhZxATIXWZg6j/C08XmmDw= github.com/libp2p/go-flow-metrics v0.2.0/go.mod h1:st3qqfu8+pMfh+9Mzqb2GTiwrAGjIPszEjZmtksN8Jc= github.com/libp2p/go-libp2p v0.41.1 h1:8ecNQVT5ev/jqALTvisSJeVNvXYJyK4NhQx1nNRXQZE= github.com/libp2p/go-libp2p v0.41.1/go.mod h1:DcGTovJzQl/I7HMrby5ZRjeD0kQkGiy+9w6aEkSZpRI= github.com/libp2p/go-libp2p-asn-util v0.4.1 h1:xqL7++IKD9TBFMgnLPZR6/6iYhawHKHl950SO9L6n94= github.com/libp2p/go-libp2p-asn-util v0.4.1/go.mod h1:d/NI6XZ9qxw67b4e+NgpQexCIiFYJjErASrYW4PFDN8= github.com/libp2p/go-libp2p-kad-dht v0.31.0 h1:C+YmAGqA+EfyVbqQWv4O67BYao77sAc2SwV4jlj6Fnw= github.com/libp2p/go-libp2p-kad-dht v0.31.0/go.mod h1:6rsxd1mte52S5warH6vfNOKVWTF9i+sTHL2re1/Cc4U= github.com/libp2p/go-libp2p-kbucket v0.7.0 h1:vYDvRjkyJPeWunQXqcW2Z6E93Ywx7fX0jgzb/dGOKCs= github.com/libp2p/go-libp2p-kbucket v0.7.0/go.mod h1:blOINGIj1yiPYlVEX0Rj9QwEkmVnz3EP8LK1dRKBC6g= github.com/libp2p/go-libp2p-record v0.3.1 h1:cly48Xi5GjNw5Wq+7gmjfBiG9HCzQVkiZOUZ8kUl+Fg= github.com/libp2p/go-libp2p-record v0.3.1/go.mod h1:T8itUkLcWQLCYMqtX7Th6r7SexyUJpIyPgks757td/E= github.com/libp2p/go-libp2p-routing-helpers v0.7.5 h1:HdwZj9NKovMx0vqq6YNPTh6aaNzey5zHD7HeLJtq6fI= github.com/libp2p/go-libp2p-routing-helpers v0.7.5/go.mod h1:3YaxrwP0OBPDD7my3D0KxfR89FlcX/IEbxDEDfAmj98= github.com/libp2p/go-libp2p-testing v0.12.0 h1:EPvBb4kKMWO29qP4mZGyhVzUyR25dvfUIK5WDu6iPUA= github.com/libp2p/go-libp2p-testing v0.12.0/go.mod h1:KcGDRXyN7sQCllucn1cOOS+Dmm7ujhfEyXQL5lvkcPg= github.com/libp2p/go-msgio v0.3.0 h1:mf3Z8B1xcFN314sWX+2vOTShIE0Mmn2TXn3YCUQGNj0= github.com/libp2p/go-msgio v0.3.0/go.mod h1:nyRM819GmVaF9LX3l03RMh10QdOroF++NBbxAb0mmDM= github.com/libp2p/go-netroute v0.2.2 h1:Dejd8cQ47Qx2kRABg6lPwknU7+nBnFRpko45/fFPuZ8= github.com/libp2p/go-netroute v0.2.2/go.mod h1:Rntq6jUAH0l9Gg17w5bFGhcC9a+vk4KNXs6s7IljKYE= github.com/libp2p/go-reuseport v0.4.0 h1:nR5KU7hD0WxXCJbmw7r2rhRYruNRl2koHw8fQscQm2s= github.com/libp2p/go-reuseport v0.4.0/go.mod h1:ZtI03j/wO5hZVDFo2jKywN6bYKWLOy8Se6DrI2E1cLU= github.com/libp2p/go-yamux/v5 v5.0.0 h1:2djUh96d3Jiac/JpGkKs4TO49YhsfLopAoryfPmf+Po= github.com/libp2p/go-yamux/v5 v5.0.0/go.mod h1:en+3cdX51U0ZslwRdRLrvQsdayFt3TSUKvBGErzpWbU= github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd h1:br0buuQ854V8u83wA0rVZ8ttrq5CpaPZdvrK0LP2lOk= github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd/go.mod h1:QuCEs1Nt24+FYQEqAAncTDPJIuGs+LxK1MCiFL25pMU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY= github.com/miekg/dns v1.1.63/go.mod h1:6NGHfjhpmr5lt3XPLuyfDJi5AXbNIPM9PY6H6sF1Nfs= github.com/mikioh/tcp v0.0.0-20190314235350-803a9b46060c h1:bzE/A84HN25pxAuk9Eej1Kz9OUelF97nAc82bDquQI8= github.com/mikioh/tcp v0.0.0-20190314235350-803a9b46060c/go.mod h1:0SQS9kMwD2VsyFEB++InYyBJroV/FRmBgcydeSUcJms= github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b h1:z78hV3sbSMAUoyUMM0I83AUIT6Hu17AWfgjzIbtrYFc= github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b/go.mod h1:lxPUiZwKoFL8DUUmalo2yJJUCxbPKtm8OKfqr2/FTNU= github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc h1:PTfri+PuQmWDqERdnNMiD9ZejrlswWrCpBEZgWOiTrc= github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc/go.mod h1:cGKTAVKx4SxOuR/czcZ/E2RSJ3sfHs8FpHhQ5CWMf9s= github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ= github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= github.com/moby/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9KouLrg= github.com/moby/sys/mountinfo v0.7.2/go.mod h1:1YOa8w8Ih7uW0wALDUgT1dTTSBrZ+HiBLGws92L2RU4= github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko= github.com/moby/sys/signal v0.7.1 h1:PrQxdvxcGijdo6UXXo/lU/TvHUWyPhj7UOpSo8tuvk0= github.com/moby/sys/signal v0.7.1/go.mod h1:Se1VGehYokAkrSQwL4tDzHvETwUZlnY7S5XtQ50mQp8= github.com/moby/sys/user v0.3.0 h1:9ni5DlcW5an3SvRSx4MouotOygvzaXbaSrc/wGDFWPo= github.com/moby/sys/user v0.3.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mr-tron/base58 v1.1.2/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE= github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI= github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0= github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4= github.com/multiformats/go-multiaddr v0.1.1/go.mod h1:aMKBKNEYmzmDmxfX88/vz+J5IU55txyt0p4aiWVohjo= github.com/multiformats/go-multiaddr v0.15.0 h1:zB/HeaI/apcZiTDwhY5YqMvNVl/oQYvs3XySU+qeAVo= github.com/multiformats/go-multiaddr v0.15.0/go.mod h1:JSVUmXDjsVFiW7RjIFMP7+Ev+h1DTbiJgVeTV/tcmP0= github.com/multiformats/go-multiaddr-dns v0.4.1 h1:whi/uCLbDS3mSEUMb1MsoT4uzUeZB0N32yzufqS0i5M= github.com/multiformats/go-multiaddr-dns v0.4.1/go.mod h1:7hfthtB4E4pQwirrz+J0CcDUfbWzTqEzVyYKKIKpgkc= github.com/multiformats/go-multiaddr-fmt v0.1.0 h1:WLEFClPycPkp4fnIzoFoV9FVd49/eQsuaL3/CWe167E= github.com/multiformats/go-multiaddr-fmt v0.1.0/go.mod h1:hGtDIW4PU4BqJ50gW2quDuPVjyWNZxToGUh/HwTZYJo= github.com/multiformats/go-multibase v0.2.0 h1:isdYCVLvksgWlMW9OZRYJEa9pZETFivncJHmHnnd87g= github.com/multiformats/go-multibase v0.2.0/go.mod h1:bFBZX4lKCA/2lyOFSAoKH5SS6oPyjtnzK/XTFDPkNuk= github.com/multiformats/go-multicodec v0.9.0 h1:pb/dlPnzee/Sxv/j4PmkDRxCOi3hXTz3IbPKOXWJkmg= github.com/multiformats/go-multicodec v0.9.0/go.mod h1:L3QTQvMIaVBkXOXXtVmYE+LI16i14xuaojr/H7Ai54k= github.com/multiformats/go-multihash v0.0.8/go.mod h1:YSLudS+Pi8NHE7o6tb3D8vrpKa63epEDmG8nTduyAew= github.com/multiformats/go-multihash v0.2.3 h1:7Lyc8XfX/IY2jWb/gI7JP+o7JEq9hOa7BFvVU9RSh+U= github.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM= github.com/multiformats/go-multistream v0.6.0 h1:ZaHKbsL404720283o4c/IHQXiS6gb8qAN5EIJ4PN5EA= github.com/multiformats/go-multistream v0.6.0/go.mod h1:MOyoG5otO24cHIg8kf9QW2/NozURlkP/rvi2FQJyCPg= github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8= github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= github.com/norwoodj/helm-docs v1.14.2 h1:Ew3bCq1hZqMnnTopkk66Uy2mGwu/jAclAx+3JAVp1To= github.com/norwoodj/helm-docs v1.14.2/go.mod h1:qdo76rorOkPDme8nsV5e0JBAYrs56kzvZMYW83k1kgc= github.com/onsi/ginkgo/v2 v2.22.2 h1:/3X8Panh8/WwhU/3Ssa6rCKqPLuAkVY2I0RoyDLySlU= github.com/onsi/ginkgo/v2 v2.22.2/go.mod h1:oeMosUL+8LtarXBHu/c0bx2D/K9zyQ6uX3cTyztHwsk= github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8= github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.2.0 h1:z97+pHb3uELt/yiAWD691HNHQIF07bE7dzrbT927iTk= github.com/opencontainers/runtime-spec v1.2.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/selinux v1.11.1 h1:nHFvthhM0qY8/m+vfhJylliSshm8G1jJ2jDMcgULaH8= github.com/opencontainers/selinux v1.11.1/go.mod h1:E5dMC3VPuVvVHDYmi78qvhJp8+M586T4DlDRYpFkyec= github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0= github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pion/datachannel v1.5.10 h1:ly0Q26K1i6ZkGf42W7D4hQYR90pZwzFOjTq5AuCKk4o= github.com/pion/datachannel v1.5.10/go.mod h1:p/jJfC9arb29W7WrxyKbepTU20CFgyx5oLo8Rs4Py/M= github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s= github.com/pion/dtls/v2 v2.2.12 h1:KP7H5/c1EiVAAKUmXyCzPiQe5+bCJrpOeKg/L05dunk= github.com/pion/dtls/v2 v2.2.12/go.mod h1:d9SYc9fch0CqK90mRk1dC7AkzzpwJj6u2GU3u+9pqFE= github.com/pion/dtls/v3 v3.0.4 h1:44CZekewMzfrn9pmGrj5BNnTMDCFwr+6sLH+cCuLM7U= github.com/pion/dtls/v3 v3.0.4/go.mod h1:R373CsjxWqNPf6MEkfdy3aSe9niZvL/JaKlGeFphtMg= github.com/pion/ice/v4 v4.0.8 h1:ajNx0idNG+S+v9Phu4LSn2cs8JEfTsA1/tEjkkAVpFY= github.com/pion/ice/v4 v4.0.8/go.mod h1:y3M18aPhIxLlcO/4dn9X8LzLLSma84cx6emMSu14FGw= github.com/pion/interceptor v0.1.37 h1:aRA8Zpab/wE7/c0O3fh1PqY0AJI3fCSEM5lRWJVorwI= github.com/pion/interceptor v0.1.37/go.mod h1:JzxbJ4umVTlZAf+/utHzNesY8tmRkM2lVmkS82TTj8Y= github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms= github.com/pion/logging v0.2.3 h1:gHuf0zpoh1GW67Nr6Gj4cv5Z9ZscU7g/EaoC/Ke/igI= github.com/pion/logging v0.2.3/go.mod h1:z8YfknkquMe1csOrxK5kc+5/ZPAzMxbKLX5aXpbpC90= github.com/pion/mdns/v2 v2.0.7 h1:c9kM8ewCgjslaAmicYMFQIde2H9/lrZpjBkN8VwoVtM= github.com/pion/mdns/v2 v2.0.7/go.mod h1:vAdSYNAT0Jy3Ru0zl2YiW3Rm/fJCwIeM0nToenfOJKA= github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA= github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8= github.com/pion/rtcp v1.2.15 h1:LZQi2JbdipLOj4eBjK4wlVoQWfrZbh3Q6eHtWtJBZBo= github.com/pion/rtcp v1.2.15/go.mod h1:jlGuAjHMEXwMUHK78RgX0UmEJFV4zUKOFHR7OP+D3D0= github.com/pion/rtp v1.8.11 h1:17xjnY5WO5hgO6SD3/NTIUPvSFw/PbLsIJyz1r1yNIk= github.com/pion/rtp v1.8.11/go.mod h1:8uMBJj32Pa1wwx8Fuv/AsFhn8jsgw+3rUC2PfoBZ8p4= github.com/pion/sctp v1.8.37 h1:ZDmGPtRPX9mKCiVXtMbTWybFw3z/hVKAZgU81wcOrqs= github.com/pion/sctp v1.8.37/go.mod h1:cNiLdchXra8fHQwmIoqw0MbLLMs+f7uQ+dGMG2gWebE= github.com/pion/sdp/v3 v3.0.10 h1:6MChLE/1xYB+CjumMw+gZ9ufp2DPApuVSnDT8t5MIgA= github.com/pion/sdp/v3 v3.0.10/go.mod h1:88GMahN5xnScv1hIMTqLdu/cOcUkj6a9ytbncwMCq2E= github.com/pion/srtp/v3 v3.0.4 h1:2Z6vDVxzrX3UHEgrUyIGM4rRouoC7v+NiF1IHtp9B5M= github.com/pion/srtp/v3 v3.0.4/go.mod h1:1Jx3FwDoxpRaTh1oRV8A/6G1BnFL+QI82eK4ms8EEJQ= github.com/pion/stun v0.6.1 h1:8lp6YejULeHBF8NmV8e2787BogQhduZugh5PdhDyyN4= github.com/pion/stun v0.6.1/go.mod h1:/hO7APkX4hZKu/D0f2lHzNyvdkTGtIy3NDmLR7kSz/8= github.com/pion/stun/v3 v3.0.0 h1:4h1gwhWLWuZWOJIJR9s2ferRO+W3zA/b6ijOI6mKzUw= github.com/pion/stun/v3 v3.0.0/go.mod h1:HvCN8txt8mwi4FBvS3EmDghW6aQJ24T+y+1TKjB5jyU= github.com/pion/transport/v2 v2.2.1/go.mod h1:cXXWavvCnFF6McHTft3DWS9iic2Mftcz1Aq29pGcU5g= github.com/pion/transport/v2 v2.2.4/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0= github.com/pion/transport/v2 v2.2.10 h1:ucLBLE8nuxiHfvkFKnkDQRYWYfp8ejf4YBOPfaQpw6Q= github.com/pion/transport/v2 v2.2.10/go.mod h1:sq1kSLWs+cHW9E+2fJP95QudkzbK7wscs8yYgQToO5E= github.com/pion/transport/v3 v3.0.7 h1:iRbMH05BzSNwhILHoBoAPxoB9xQgOaJk+591KC9P1o0= github.com/pion/transport/v3 v3.0.7/go.mod h1:YleKiTZ4vqNxVwh77Z0zytYi7rXHl7j6uPLGhhz9rwo= github.com/pion/turn/v4 v4.0.0 h1:qxplo3Rxa9Yg1xXDxxH8xaqcyGUtbHYw4QSCvmFWvhM= github.com/pion/turn/v4 v4.0.0/go.mod h1:MuPDkm15nYSklKpN8vWJ9W2M0PlyQZqYt1McGuxG7mA= github.com/pion/webrtc/v4 v4.0.10 h1:Hq/JLjhqLxi+NmCtE8lnRPDr8H4LcNvwg8OxVcdv56Q= github.com/pion/webrtc/v4 v4.0.10/go.mod h1:ViHLVaNpiuvaH8pdiuQxuA9awuE6KVzAXx3vVWilOck= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/polydawn/refmt v0.89.0 h1:ADJTApkvkeBZsN0tBTx8QjpD9JkmxbKp0cxfr9qszm4= github.com/polydawn/refmt v0.89.0/go.mod h1:/zvteZs/GwLtCgZ4BL6CBsk9IKIlexP43ObX9AxTqTw= github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.63.0 h1:YR/EIY1o3mEFP/kZCD7iDMnLPlGyuU2Gb3HIcXnA98k= github.com/prometheus/common v0.63.0/go.mod h1:VVFF/fBIoToEnWRVkYoXEkq3R3paCoxG9PXP74SnV18= github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= github.com/quic-go/quic-go v0.50.1 h1:unsgjFIUqW8a2oopkY7YNONpV1gYND6Nt9hnt1PN94Q= github.com/quic-go/quic-go v0.50.1/go.mod h1:Vim6OmUvlYdwBhXP9ZVrtGmCMWa3wEqhq3NgYrI8b4E= github.com/quic-go/webtransport-go v0.8.1-0.20241018022711-4ac2c9250e66 h1:4WFk6u3sOT6pLa1kQ50ZVdm8BQFgJNA117cepZxtLIg= github.com/quic-go/webtransport-go v0.8.1-0.20241018022711-4ac2c9250e66/go.mod h1:Vp72IJajgeOL6ddqrAhmp7IM9zbTcgkQxD/YdxrVwMw= github.com/raulk/go-watchdog v1.3.0 h1:oUmdlHxdkXRJlwfG0O9omj8ukerm8MEQavSiDTEtBsk= github.com/raulk/go-watchdog v1.3.0/go.mod h1:fIvOnLbF0b0ZwkB9YU4mOW9Did//4vPZtDqv66NfsMU= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY= github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM= github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0= github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= github.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw= github.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI= github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU= github.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag= github.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg= github.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw= github.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y= github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= github.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q= github.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ= github.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I= github.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b5uSkrEVM1jQUspwbixRBhaIjIzL2xazXp6kntxYle0= github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ= github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk= github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4= github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs= github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg= github.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3Pg9vgXWeJpQFMM= github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE= github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA= github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo= github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc= github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 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.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU= github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM= github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0 h1:GDDkbFiaK8jsSDJfjId/PEGEShv6ugrt4kYsC5UIDaQ= github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw= github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 h1:EKhdznlJHPMoKr0XTrX+IlJs1LH3lyx2nfr1dOlZ79k= github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1/go.mod h1:8UvriyWtv5Q5EOgjHaSseUEdkQfvwFv1I/In/O2M9gc= github.com/wlynxg/anet v0.0.3/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA= github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU= github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.etcd.io/bbolt v1.4.0 h1:TU77id3TnN/zKr7CO/uk+fBCwF2jGcMuw2B/FMAzYIk= go.etcd.io/bbolt v1.4.0/go.mod h1:AsD+OCi/qPN1giOX1aiLAha3o1U8rAz65bvN4j0sRuk= go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 h1:UP6IpuHFkUgOQL9FFQFrZ+5LiwhhYRbi7VZSIx6Nj5s= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0/go.mod h1:qxuZLtbq5QDtdeSHsS7bcf6EH6uO6jUAgk764zd3rhM= go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/dig v1.18.0 h1:imUL1UiY0Mg4bqbFfsRQO5G4CGRBec/ZujWTvSVp3pw= go.uber.org/dig v1.18.0/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE= go.uber.org/fx v1.23.0 h1:lIr/gYWQGfTwGcSXWXu4vP5Ws6iqnNEIY+F/aFzCKTg= go.uber.org/fx v1.23.0/go.mod h1:o/D9n+2mLP6v1EG+qsdT1O8wKopYAsqZasju97SDFCU= go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw= golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200602180216-279210d13fed/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw= golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM= golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181029044818-c44066c5c816/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= 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= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180810173357-98c5dad5d1a0/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-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= 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= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU= golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.15.1 h1:FNy7N6OUZVUaWG9pTiD+jlhdQ3lMP+/LcTpJ6+a8sQ0= gonum.org/v1/gonum v0.15.1/go.mod h1:eZTZuRFrzu5pcyjN5wJhcIhnUdNijYxX1T2IcrOGY0o= google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg= google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a h1:51aaUVRocpvUOSQKM6Q7VuoaktNIaMCLuhZB6DKksq4= google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a/go.mod h1:uRxBH1mhmO8PGhU89cMcHaXKZqO+OfakD8QQO0oYwlQ= google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.72.0 h1:S7UkcVa60b5AAQTaO6ZKamFp1zMZSU0fGDK2WZLbBnM= google.golang.org/grpc v1.72.0/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= 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= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= 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= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= helm.sh/helm/v3 v3.17.3 h1:3n5rW3D0ArjFl0p4/oWO8IbY/HKaNNwJtOQFdH2AZHg= helm.sh/helm/v3 v3.17.3/go.mod h1:+uJKMH/UiMzZQOALR3XUf3BLIoczI2RKKD6bMhPh4G8= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= k8s.io/apimachinery v0.33.0 h1:1a6kHrJxb2hs4t8EE5wuR/WxKDwGN1FKH3JvDtA0CIQ= k8s.io/apimachinery v0.33.0/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= k8s.io/cri-api v0.33.0 h1:YyGNgWmuSREqFPlP3XCstlHLilYdW898KwtKoaTYwBs= k8s.io/cri-api v0.33.0/go.mod h1:OLQvT45OpIA+tv91ZrpuFIGY+Y2Ho23poS7n115Aocs= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= lukechampine.com/blake3 v1.4.0 h1:xDbKOZCVbnZsfzM6mHSYcGRHZ3YrLDzqz8XnV4uaD5w= lukechampine.com/blake3 v1.4.0/go.mod h1:MQJNQCTnR+kwOP/JEZSxj3MaQjp80FOFSNMMHXcSeX0= sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck= sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0= ``` ## /internal/channel/channel.go ```go path="/internal/channel/channel.go" package channel import ( "sync" ) func Merge[T any](cs ...<-chan T) <-chan T { var wg sync.WaitGroup out := make(chan T) output := func(c <-chan T) { for n := range c { out <- n } wg.Done() } wg.Add(len(cs)) for _, c := range cs { go output(c) } go func() { wg.Wait() close(out) }() return out } ``` ## /internal/cleanup/cleanup.go ```go path="/internal/cleanup/cleanup.go" package cleanup import ( "context" "errors" "fmt" "io" "net" "net/http" "net/url" "time" "github.com/go-logr/logr" "golang.org/x/sync/errgroup" "github.com/spegel-org/spegel/internal/channel" "github.com/spegel-org/spegel/pkg/oci" ) func Run(ctx context.Context, addr, configPath string) error { log := logr.FromContextOrDiscard(ctx) err := oci.CleanupMirrorConfiguration(ctx, configPath) if err != nil { return err } g, gCtx := errgroup.WithContext(ctx) mux := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { if req.Method != http.MethodGet && req.URL.Path != "/healthz" { log.Error(errors.New("unknown request"), "unsupported probe request", "path", req.URL.Path, "method", req.Method) rw.WriteHeader(http.StatusNotFound) return } rw.WriteHeader(http.StatusOK) }) srv := &http.Server{ Addr: addr, Handler: mux, } g.Go(func() error { if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { return err } return nil }) g.Go(func() error { <-gCtx.Done() shutdownCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() return srv.Shutdown(shutdownCtx) }) log.Info("waiting to be shutdown") err = g.Wait() if err != nil { return err } return nil } func Wait(ctx context.Context, probeEndpoint string, period time.Duration, threshold int) error { log := logr.FromContextOrDiscard(ctx) resolver := &net.Resolver{} client := &http.Client{} addr, port, err := net.SplitHostPort(probeEndpoint) if err != nil { return err } immediateCh := make(chan time.Time, 1) immediateCh <- time.Now() close(immediateCh) ticker := time.NewTicker(period) defer ticker.Stop() tickerCh := channel.Merge(immediateCh, ticker.C) thresholdCount := 0 for { select { case <-ctx.Done(): return ctx.Err() case <-tickerCh: start := time.Now() log.Info("running probe lookup", "host", addr) ips, err := resolver.LookupIPAddr(ctx, addr) if err != nil { log.Error(err, "cleanup probe lookup failed") thresholdCount = 0 continue } log.Info("running probe request", "endpoints", len(ips)) err = probeIPs(ctx, client, ips, port) if err != nil { log.Error(err, "cleanup probe request failed") thresholdCount = 0 continue } thresholdCount += 1 log.Info("probe ran successfully", "threshold", thresholdCount, "duration", time.Since(start).String()) if thresholdCount == threshold { log.Info("probe threshold reached") return nil } } } } func probeIPs(ctx context.Context, client *http.Client, ips []net.IPAddr, port string) error { g, gCtx := errgroup.WithContext(ctx) g.SetLimit(10) for _, ip := range ips { g.Go(func() error { u := url.URL{ Scheme: "http", Host: net.JoinHostPort(ip.String(), port), Path: "/healthz", } reqCtx, cancel := context.WithTimeout(gCtx, 1*time.Second) defer cancel() req, err := http.NewRequestWithContext(reqCtx, http.MethodGet, u.String(), nil) if err != nil { return err } resp, err := client.Do(req) if err != nil { return err } defer resp.Body.Close() _, err = io.Copy(io.Discard, resp.Body) if err != nil { return err } if resp.StatusCode != http.StatusOK { return fmt.Errorf("unexpected status code %s", resp.Status) } return nil }) } err := g.Wait() if err != nil { return err } return nil } ``` ## /internal/cleanup/cleanup_test.go ```go path="/internal/cleanup/cleanup_test.go" package cleanup import ( "context" "net" "net/http" "net/http/httptest" "net/url" "testing" "time" "github.com/stretchr/testify/require" "golang.org/x/sync/errgroup" ) func TestCleanupFail(t *testing.T) { t.Parallel() srv := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { rw.WriteHeader(http.StatusInternalServerError) })) defer srv.Close() u, err := url.Parse(srv.URL) require.NoError(t, err) timeoutCtx, timeoutCancel := context.WithTimeout(t.Context(), 1*time.Second) defer timeoutCancel() err = Wait(timeoutCtx, u.Host, 100*time.Millisecond, 3) require.EqualError(t, err, "context deadline exceeded") } func TestCleanupSucceed(t *testing.T) { t.Parallel() listener, err := net.Listen("tcp", ":0") if err != nil { panic(err) } addr := listener.Addr().String() err = listener.Close() require.NoError(t, err) timeoutCtx, timeoutCancel := context.WithTimeout(t.Context(), 1*time.Second) defer timeoutCancel() g, gCtx := errgroup.WithContext(timeoutCtx) g.Go(func() error { err := Run(gCtx, addr, t.TempDir()) if err != nil { return err } return nil }) g.Go(func() error { err := Wait(gCtx, addr, 100*time.Microsecond, 3) if err != nil { return err } return nil }) err = g.Wait() require.NoError(t, err) } ``` ## /internal/mux/mux.go ```go path="/internal/mux/mux.go" package mux import ( "errors" "net/http" ) type Handler func(rw ResponseWriter, req *http.Request) type ServeMux struct { h Handler } func NewServeMux(h Handler) (*ServeMux, error) { if h == nil { return nil, errors.New("handler cannot be nil") } return &ServeMux{h: h}, nil } func (s *ServeMux) ServeHTTP(rw http.ResponseWriter, req *http.Request) { s.h(&response{ResponseWriter: rw}, req) } ``` ## /internal/mux/mux_test.go ```go path="/internal/mux/mux_test.go" package mux import ( "net/http" "testing" "github.com/stretchr/testify/require" ) func TestServeMux(t *testing.T) { t.Parallel() m, err := NewServeMux(nil) require.Nil(t, m) require.EqualError(t, err, "handler cannot be nil") handlerCalled := false h := func(rw ResponseWriter, req *http.Request) { handlerCalled = true } m, err = NewServeMux(h) require.NoError(t, err) m.ServeHTTP(nil, nil) require.True(t, handlerCalled) } ``` ## /internal/mux/response.go ```go path="/internal/mux/response.go" package mux import ( "bufio" "io" "net" "net/http" ) type ResponseWriter interface { http.ResponseWriter WriteError(statusCode int, err error) Error() error Status() int Size() int64 } var ( _ http.ResponseWriter = &response{} _ http.Flusher = &response{} _ http.Hijacker = &response{} _ io.ReaderFrom = &response{} ) type response struct { http.ResponseWriter error error status int size int64 writtenHeader bool } func (r *response) WriteHeader(statusCode int) { if !r.writtenHeader { r.writtenHeader = true r.status = statusCode } r.ResponseWriter.WriteHeader(statusCode) } func (r *response) Write(b []byte) (int, error) { r.writtenHeader = true n, err := r.ResponseWriter.Write(b) r.size += int64(n) return n, err } func (r *response) WriteError(statusCode int, err error) { r.error = err r.WriteHeader(statusCode) } func (r *response) Flush() { r.writtenHeader = true //nolint: errcheck // No method to throw the error. flusher := r.ResponseWriter.(http.Flusher) flusher.Flush() } func (r *response) Hijack() (net.Conn, *bufio.ReadWriter, error) { //nolint: errcheck // No method to throw the error. hijacker := r.ResponseWriter.(http.Hijacker) return hijacker.Hijack() } func (r *response) ReadFrom(rd io.Reader) (int64, error) { n, err := io.Copy(r.ResponseWriter, rd) r.size += n return n, err } func (r *response) Unwrap() http.ResponseWriter { return r.ResponseWriter } func (r *response) Status() int { if r.status == 0 { return http.StatusOK } return r.status } func (r *response) Error() error { return r.error } func (r *response) Size() int64 { return r.size } ``` ## /internal/mux/response_test.go ```go path="/internal/mux/response_test.go" package mux import ( "errors" "io" "net/http" "net/http/httptest" "strings" "testing" "github.com/stretchr/testify/require" ) func TestResponseWriter(t *testing.T) { t.Parallel() var httpRw http.ResponseWriter = &response{} _, ok := httpRw.(io.ReaderFrom) require.True(t, ok) httpRw = httptest.NewRecorder() rw := &response{ ResponseWriter: httpRw, } require.Equal(t, httpRw, rw.Unwrap()) require.NoError(t, rw.Error()) require.Equal(t, int64(0), rw.Size()) require.Equal(t, http.StatusOK, rw.Status()) rw = &response{ ResponseWriter: httptest.NewRecorder(), } rw.WriteHeader(http.StatusNotFound) require.True(t, rw.writtenHeader) require.Equal(t, http.StatusNotFound, rw.Status()) rw.WriteHeader(http.StatusBadGateway) require.Equal(t, http.StatusNotFound, rw.Status()) _, err := rw.Write([]byte("foo")) require.NoError(t, err) require.Equal(t, http.StatusNotFound, rw.Status()) rw = &response{ ResponseWriter: httptest.NewRecorder(), } err = errors.New("some server error") rw.WriteError(http.StatusInternalServerError, err) require.Equal(t, err, rw.Error()) require.Equal(t, http.StatusInternalServerError, rw.Status()) rw = &response{ ResponseWriter: httptest.NewRecorder(), } first := "hello world" n, err := rw.Write([]byte(first)) require.Equal(t, http.StatusOK, rw.Status()) require.NoError(t, err) require.Equal(t, len(first), n) require.Equal(t, int64(len(first)), rw.Size()) second := "foo bar" n, err = rw.Write([]byte(second)) require.NoError(t, err) require.Equal(t, len(second), n) require.Equal(t, int64(len(first)+len(second)), rw.Size()) rw = &response{ ResponseWriter: httptest.NewRecorder(), } r := strings.NewReader("reader") readFromN, err := rw.ReadFrom(r) require.NoError(t, err) require.Equal(t, r.Size(), readFromN) require.Equal(t, r.Size(), rw.Size()) } ``` ## /internal/web/templates/index.html ```html path="/internal/web/templates/index.html" Spegel Debug

Spegel

Measure Image Pull

``` ## /internal/web/templates/measure.html ```html path="/internal/web/templates/measure.html" {{ if .PeerResults }}

Resolved Peers

{{ range .PeerResults }} {{ end }}
Peer Duration
{{ .Peer.Addr }} {{ .Duration }}

Result

{{ range .PullResults }} {{ end }}
Identifier Type Size Duration
{{ .Identifier }} {{ .ContentType }} {{ .ContentLength }} {{ .Duration }}
{{ else }}

No peers found for image

{{ end }} ``` ## /internal/web/templates/stats.html ```html path="/internal/web/templates/stats.html"
Images
{{ .ImageCount }}
Layers
{{ .LayerCount }}
``` ## /internal/web/web.go ```go path="/internal/web/web.go" package web import ( "embed" "encoding/json" "errors" "fmt" "html/template" "io" "net" "net/http" "net/netip" "net/url" "runtime" "strconv" "time" "github.com/containerd/containerd/v2/core/images" "github.com/go-logr/logr" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/prometheus/common/expfmt" "github.com/spegel-org/spegel/pkg/oci" "github.com/spegel-org/spegel/pkg/routing" ) //go:embed templates/* var templatesFS embed.FS type Web struct { router routing.Router client *http.Client tmpls *template.Template } func NewWeb(router routing.Router) (*Web, error) { tmpls, err := template.New("").ParseFS(templatesFS, "templates/*") if err != nil { return nil, err } return &Web{ router: router, client: &http.Client{}, tmpls: tmpls, }, nil } func (w *Web) Handler(log logr.Logger) http.Handler { log = log.WithName("web") handlers := map[string]func(*http.Request) (string, any, error){ "/debug/web/": func(r *http.Request) (string, any, error) { return "index", nil, nil }, "/debug/web/stats": w.stats, "/debug/web/measure": w.measure, } return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { h, ok := handlers[req.URL.Path] if !ok { rw.WriteHeader(http.StatusNotFound) return } t, data, err := h(req) if err != nil { log.Error(err, "error when running handler", "path", req.URL.Path) rw.WriteHeader(http.StatusInternalServerError) return } err = w.tmpls.ExecuteTemplate(rw, t+".html", data) if err != nil { log.Error(err, "error rendering page", "path", req.URL.Path) rw.WriteHeader(http.StatusInternalServerError) return } }) } func (w *Web) stats(req *http.Request) (string, any, error) { //nolint: errcheck // Ignore error. srvAddr := req.Context().Value(http.LocalAddrContextKey).(net.Addr) resp, err := http.Get(fmt.Sprintf("http://%s/metrics", srvAddr.String())) if err != nil { return "", nil, err } defer resp.Body.Close() parser := expfmt.TextParser{} metricFamilies, err := parser.TextToMetricFamilies(resp.Body) if err != nil { return "", nil, err } data := struct { ImageCount int64 LayerCount int64 }{} for _, metric := range metricFamilies["spegel_advertised_images"].Metric { data.ImageCount += int64(*metric.Gauge.Value) } for _, metric := range metricFamilies["spegel_advertised_keys"].Metric { data.LayerCount += int64(*metric.Gauge.Value) } return "stats", data, nil } type measureResult struct { PeerResults []peerResult PullResults []pullResult } type peerResult struct { Peer netip.AddrPort Duration time.Duration } type pullResult struct { Identifier string ContentType string ContentLength string Duration time.Duration } func (w *Web) measure(req *http.Request) (string, any, error) { // Parse image name. imgName := req.URL.Query().Get("image") if imgName == "" { return "", nil, errors.New("image name cannot be empty") } img, err := oci.ParseImage(imgName) if err != nil { return "", nil, err } res := measureResult{} // Resolve peers for the given image. resolveStart := time.Now() peerCh, err := w.router.Resolve(req.Context(), imgName, 0) if err != nil { return "", nil, err } for peer := range peerCh { res.PeerResults = append(res.PeerResults, peerResult{ Peer: peer, Duration: time.Since(resolveStart), }) } if len(res.PeerResults) == 0 { return "measure", res, nil } // Pull the image and measure performance. pullResults, err := measureImagePull(w.client, "http://localhost:5000", img) if err != nil { return "", nil, err } res.PullResults = pullResults return "measure", res, nil } func measureImagePull(client *http.Client, regURL string, img oci.Image) ([]pullResult, error) { pullResults := []pullResult{} queue := []oci.DistributionPath{ { Kind: oci.DistributionKindManifest, Name: img.Repository, Digest: img.Digest, Tag: img.Tag, Registry: img.Registry, }, } for { //nolint: staticcheck // Ignore until we have proper tests. if len(queue) == 0 { break } pr, dists, err := fetchDistributionPath(client, regURL, queue[0]) if err != nil { return nil, err } queue = queue[1:] queue = append(queue, dists...) pullResults = append(pullResults, pr) } return pullResults, nil } func fetchDistributionPath(client *http.Client, regURL string, dist oci.DistributionPath) (pullResult, []oci.DistributionPath, error) { regU, err := url.Parse(regURL) if err != nil { return pullResult{}, nil, err } u := dist.URL() u.Scheme = regU.Scheme u.Host = regU.Host pullStart := time.Now() pullReq, err := http.NewRequest(http.MethodGet, u.String(), nil) if err != nil { return pullResult{}, nil, err } pullResp, err := client.Do(pullReq) if err != nil { return pullResult{}, nil, err } defer pullResp.Body.Close() if pullResp.StatusCode != http.StatusOK { _, err = io.Copy(io.Discard, pullResp.Body) if err != nil { return pullResult{}, nil, err } return pullResult{}, nil, fmt.Errorf("request returned unexpected status code %s", pullResp.Status) } queue := []oci.DistributionPath{} ct := pullResp.Header.Get("Content-Type") switch dist.Kind { case oci.DistributionKindBlob: _, err = io.Copy(io.Discard, pullResp.Body) if err != nil { return pullResult{}, nil, err } ct = "Layer" case oci.DistributionKindManifest: b, err := io.ReadAll(pullResp.Body) if err != nil { return pullResult{}, nil, err } switch ct { case images.MediaTypeDockerSchema2ManifestList, ocispec.MediaTypeImageIndex: var idx ocispec.Index if err := json.Unmarshal(b, &idx); err != nil { return pullResult{}, nil, err } for _, m := range idx.Manifests { //nolint: staticcheck // Simplify in the future. if !(m.Platform.OS == runtime.GOOS && m.Platform.Architecture == runtime.GOARCH) { continue } queue = append(queue, oci.DistributionPath{ Kind: oci.DistributionKindManifest, Name: dist.Name, Digest: m.Digest, Registry: dist.Registry, }) } ct = "Index" case images.MediaTypeDockerSchema2Manifest, ocispec.MediaTypeImageManifest: var manifest ocispec.Manifest err := json.Unmarshal(b, &manifest) if err != nil { return pullResult{}, nil, err } queue = append(queue, oci.DistributionPath{ Kind: oci.DistributionKindManifest, Name: dist.Name, Digest: manifest.Config.Digest, Registry: dist.Registry, }) for _, layer := range manifest.Layers { queue = append(queue, oci.DistributionPath{ Kind: oci.DistributionKindBlob, Name: dist.Name, Digest: layer.Digest, Registry: dist.Registry, }) } ct = "Manifest" case ocispec.MediaTypeImageConfig: ct = "Config" } } pullResp.Body.Close() i, err := strconv.ParseInt(pullResp.Header.Get("Content-Length"), 10, 0) if err != nil { return pullResult{}, nil, err } return pullResult{ Identifier: dist.Reference(), ContentType: ct, ContentLength: formatByteSize(i), Duration: time.Since(pullStart), }, queue, nil } func formatByteSize(size int64) string { const unit = 1000 if size < unit { return fmt.Sprintf("%d B", size) } div, exp := int64(unit), 0 for n := size / unit; n >= unit; n /= unit { div *= unit exp++ } return fmt.Sprintf("%.1f %cB", float64(size)/float64(div), "kMGTPE"[exp]) } ``` ## /internal/web/web_test.go ```go path="/internal/web/web_test.go" package web import ( "encoding/json" "net/http" "net/http/httptest" "runtime" "strconv" "testing" "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/stretchr/testify/require" "github.com/spegel-org/spegel/pkg/oci" ) func TestMeasureImagePull(t *testing.T) { t.Parallel() srv := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { switch req.URL.Path { case "/v2/test/image/manifests/index": rw.Header().Set("Content-Type", ocispec.MediaTypeImageIndex) idx := ocispec.Index{ Manifests: []ocispec.Descriptor{ { Digest: digest.Digest("manifest"), Platform: &ocispec.Platform{ OS: runtime.GOOS, Architecture: runtime.GOARCH, }, }, }, } b, err := json.Marshal(&idx) if err != nil { rw.WriteHeader(http.StatusInternalServerError) return } //nolint: errcheck // Ignore error. rw.Write(b) case "/v2/test/image/manifests/manifest": rw.Header().Set("Content-Type", ocispec.MediaTypeImageManifest) manifest := ocispec.Manifest{ Config: ocispec.Descriptor{ Digest: digest.Digest("config"), }, Layers: []ocispec.Descriptor{ { Digest: digest.Digest("layer"), }, }, } b, err := json.Marshal(&manifest) if err != nil { rw.WriteHeader(http.StatusInternalServerError) return } //nolint: errcheck // Ignore error. rw.Write(b) case "/v2/test/image/manifests/config": rw.Header().Set("Content-Type", ocispec.MediaTypeImageConfig) config := ocispec.ImageConfig{ User: "root", } b, err := json.Marshal(&config) if err != nil { rw.WriteHeader(http.StatusInternalServerError) return } //nolint: errcheck // Ignore error. rw.Write(b) case "/v2/test/image/blobs/layer": //nolint: errcheck // Ignore error. rw.Write([]byte("Hello World")) default: rw.WriteHeader(http.StatusNotFound) } })) t.Cleanup(func() { srv.Close() }) img := oci.Image{ Repository: "test/image", Digest: digest.Digest("index"), Registry: "example.com", } pullResults, err := measureImagePull(srv.Client(), srv.URL, img) require.NoError(t, err) require.NotEmpty(t, pullResults) } func TestFormatByteSize(t *testing.T) { t.Parallel() tests := []struct { expected string size int64 }{ { size: 1, expected: "1 B", }, { size: 18954, expected: "19.0 kB", }, { size: 1000000000, expected: "1.0 GB", }, } for _, tt := range tests { t.Run(strconv.FormatInt(tt.size, 10), func(t *testing.T) { t.Parallel() result := formatByteSize(tt.size) require.Equal(t, tt.expected, result) }) } } ``` ## /main.go ```go path="/main.go" package main import ( "context" "errors" "fmt" "log/slog" "net" "net/http" "net/http/pprof" "net/url" "os" "os/signal" "path/filepath" "syscall" "time" "github.com/alexflint/go-arg" "github.com/go-logr/logr" "github.com/prometheus/client_golang/prometheus/promhttp" "golang.org/x/sync/errgroup" "k8s.io/klog/v2" "github.com/spegel-org/spegel/internal/cleanup" "github.com/spegel-org/spegel/internal/web" "github.com/spegel-org/spegel/pkg/metrics" "github.com/spegel-org/spegel/pkg/oci" "github.com/spegel-org/spegel/pkg/registry" "github.com/spegel-org/spegel/pkg/routing" "github.com/spegel-org/spegel/pkg/state" ) type ConfigurationCmd struct { ContainerdRegistryConfigPath string `arg:"--containerd-registry-config-path,env:CONTAINERD_REGISTRY_CONFIG_PATH" default:"/etc/containerd/certs.d" help:"Directory where mirror configuration is written."` MirroredRegistries []url.URL `arg:"--mirrored-registries,env:MIRRORED_REGISTRIES" help:"Registries that are configured to be mirrored, if slice is empty all registires are mirrored."` MirrorTargets []url.URL `arg:"--mirror-targets,env:MIRROR_TARGETS,required" help:"registries that are configured to act as mirrors."` ResolveTags bool `arg:"--resolve-tags,env:RESOLVE_TAGS" default:"true" help:"When true Spegel will resolve tags to digests."` PrependExisting bool `arg:"--prepend-existing,env:PREPEND_EXISTING" default:"false" help:"When true existing mirror configuration will be kept and Spegel will prepend it's configuration."` } type BootstrapConfig struct { BootstrapKind string `arg:"--bootstrap-kind,env:BOOTSTRAP_KIND" help:"Kind of bootsrapper to use."` DNSBootstrapDomain string `arg:"--dns-bootstrap-domain,env:DNS_BOOTSTRAP_DOMAIN" help:"Domain to use when bootstrapping using DNS."` HTTPBootstrapAddr string `arg:"--http-bootstrap-addr,env:HTTP_BOOTSTRAP_ADDR" help:"Address to serve for HTTP bootstrap."` HTTPBootstrapPeer string `arg:"--http-bootstrap-peer,env:HTTP_BOOTSTRAP_PEER" help:"Peer to HTTP bootstrap with."` StaticBootstrapPeers []string `arg:"--static-bootstrap-peers,env:STATIC_BOOTSTRAP_PEERS" help:"Static list of peers to bootstrap with."` } type RegistryCmd struct { BootstrapConfig ContainerdRegistryConfigPath string `arg:"--containerd-registry-config-path,env:CONTAINERD_REGISTRY_CONFIG_PATH" default:"/etc/containerd/certs.d" help:"Directory where mirror configuration is written."` MetricsAddr string `arg:"--metrics-addr,env:METRICS_ADDR" default:":9090" help:"address to serve metrics."` ContainerdSock string `arg:"--containerd-sock,env:CONTAINERD_SOCK" default:"/run/containerd/containerd.sock" help:"Endpoint of containerd service."` ContainerdNamespace string `arg:"--containerd-namespace,env:CONTAINERD_NAMESPACE" default:"k8s.io" help:"Containerd namespace to fetch images from."` ContainerdContentPath string `arg:"--containerd-content-path,env:CONTAINERD_CONTENT_PATH" default:"/var/lib/containerd/io.containerd.content.v1.content" help:"Path to Containerd content store"` DataDir string `arg:"--data-dir,env:DATA_DIR" default:"/var/lib/spegel" help:"Directory where Spegel persists data."` RouterAddr string `arg:"--router-addr,env:ROUTER_ADDR" default:":5001" help:"address to serve router."` RegistryAddr string `arg:"--registry-addr,env:REGISTRY_ADDR" default:":5000" help:"address to server image registry."` MirroredRegistries []url.URL `arg:"--mirrored-registries,env:MIRRORED_REGISTRIES" help:"Registries that are configured to be mirrored, if slice is empty all registires are mirrored."` MirrorResolveTimeout time.Duration `arg:"--mirror-resolve-timeout,env:MIRROR_RESOLVE_TIMEOUT" default:"20ms" help:"Max duration spent finding a mirror."` MirrorResolveRetries int `arg:"--mirror-resolve-retries,env:MIRROR_RESOLVE_RETRIES" default:"3" help:"Max amount of mirrors to attempt."` ResolveLatestTag bool `arg:"--resolve-latest-tag,env:RESOLVE_LATEST_TAG" default:"true" help:"When true latest tags will be resolved to digests."` DebugWebEnabled bool `arg:"--debug-web-enabled,env:DEBUG_WEB_ENABLED" default:"false" help:"When true enables debug web page."` } type CleanupCmd struct { Addr string `arg:"--addr,required,env:ADDR" help:"address to run readiness probe on."` ContainerdRegistryConfigPath string `arg:"--containerd-registry-config-path,env:CONTAINERD_REGISTRY_CONFIG_PATH" default:"/etc/containerd/certs.d" help:"Directory where mirror configuration is written."` } type CleanupWaitCmd struct { ProbeEndpoint string `arg:"--probe-endpoint,required,env:PROBE_ENDPOINT" help:"endpoint to probe cleanup jobs from."` Threshold int `arg:"--threshold,env:THRESHOLD" default:"3" help:"amount of consecutive successful probes to consider cleanup done."` Period time.Duration `arg:"--period,env:PERIOD" default:"2s" help:"address to run readiness probe on."` } type Arguments struct { Configuration *ConfigurationCmd `arg:"subcommand:configuration"` Registry *RegistryCmd `arg:"subcommand:registry"` Cleanup *CleanupCmd `arg:"subcommand:cleanup"` CleanupWait *CleanupWaitCmd `arg:"subcommand:cleanup-wait"` LogLevel slog.Level `arg:"--log-level,env:LOG_LEVEL" default:"INFO" help:"Minimum log level to output. Value should be DEBUG, INFO, WARN, or ERROR."` } func main() { args := &Arguments{} arg.MustParse(args) opts := slog.HandlerOptions{ AddSource: true, Level: args.LogLevel, } handler := slog.NewJSONHandler(os.Stderr, &opts) log := logr.FromSlogHandler(handler) klog.SetLogger(log) ctx := logr.NewContext(context.Background(), log) err := run(ctx, args) if err != nil { log.Error(err, "run exit with error") os.Exit(1) } log.Info("gracefully shutdown") } func run(ctx context.Context, args *Arguments) error { ctx, cancel := signal.NotifyContext(ctx, syscall.SIGTERM) defer cancel() switch { case args.Configuration != nil: return configurationCommand(ctx, args.Configuration) case args.Registry != nil: return registryCommand(ctx, args.Registry) case args.Cleanup != nil: return cleanupCommand(ctx, args.Cleanup) case args.CleanupWait != nil: return cleanupWaitCommand(ctx, args.CleanupWait) default: return errors.New("unknown subcommand") } } func configurationCommand(ctx context.Context, args *ConfigurationCmd) error { username, password, err := loadBasicAuth() if err != nil { return err } err = oci.AddMirrorConfiguration(ctx, args.ContainerdRegistryConfigPath, args.MirroredRegistries, args.MirrorTargets, args.ResolveTags, args.PrependExisting, username, password) if err != nil { return err } return nil } func registryCommand(ctx context.Context, args *RegistryCmd) (err error) { log := logr.FromContextOrDiscard(ctx) g, ctx := errgroup.WithContext(ctx) username, password, err := loadBasicAuth() if err != nil { return err } // OCI Client ociClient, err := oci.NewContainerd(args.ContainerdSock, args.ContainerdNamespace, args.ContainerdRegistryConfigPath, args.MirroredRegistries, oci.WithContentPath(args.ContainerdContentPath)) if err != nil { return err } err = ociClient.Verify(ctx) if err != nil { return err } // Router _, registryPort, err := net.SplitHostPort(args.RegistryAddr) if err != nil { return err } bootstrapper, err := getBootstrapper(args.BootstrapConfig) if err != nil { return err } routerOpts := []routing.P2PRouterOption{ routing.WithDataDir(args.DataDir), } router, err := routing.NewP2PRouter(ctx, args.RouterAddr, bootstrapper, registryPort, routerOpts...) if err != nil { return err } g.Go(func() error { return router.Run(ctx) }) // State tracking g.Go(func() error { err := state.Track(ctx, ociClient, router, args.ResolveLatestTag) if err != nil { return err } return nil }) // Registry registryOpts := []registry.RegistryOption{ registry.WithResolveLatestTag(args.ResolveLatestTag), registry.WithResolveRetries(args.MirrorResolveRetries), registry.WithResolveTimeout(args.MirrorResolveTimeout), registry.WithLogger(log), registry.WithBasicAuth(username, password), } reg, err := registry.NewRegistry(ociClient, router, registryOpts...) if err != nil { return err } regSrv, err := reg.Server(args.RegistryAddr) if err != nil { return err } g.Go(func() error { if err := regSrv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { return err } return nil }) g.Go(func() error { <-ctx.Done() shutdownCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() return regSrv.Shutdown(shutdownCtx) }) // Metrics metrics.Register() mux := http.NewServeMux() mux.Handle("/metrics", promhttp.HandlerFor(metrics.DefaultGatherer, promhttp.HandlerOpts{})) mux.Handle("/debug/pprof/", http.HandlerFunc(pprof.Index)) mux.Handle("/debug/pprof/profile", http.HandlerFunc(pprof.Profile)) mux.Handle("/debug/pprof/trace", http.HandlerFunc(pprof.Trace)) mux.Handle("/debug/pprof/symbol", http.HandlerFunc(pprof.Symbol)) mux.Handle("/debug/pprof/heap", pprof.Handler("heap")) mux.Handle("/debug/pprof/allocs", pprof.Handler("allocs")) mux.Handle("/debug/pprof/goroutine", pprof.Handler("goroutine")) mux.Handle("/debug/pprof/threadcreate", pprof.Handler("threadcreate")) mux.Handle("/debug/pprof/block", pprof.Handler("block")) mux.Handle("/debug/pprof/mutex", pprof.Handler("mutex")) if args.DebugWebEnabled { web, err := web.NewWeb(router) if err != nil { return err } mux.Handle("/debug/web/", web.Handler(log)) } metricsSrv := &http.Server{ Addr: args.MetricsAddr, Handler: mux, } g.Go(func() error { if err := metricsSrv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { return err } return nil }) g.Go(func() error { <-ctx.Done() shutdownCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() return metricsSrv.Shutdown(shutdownCtx) }) log.Info("running Spegel", "registry", args.RegistryAddr, "router", args.RouterAddr) err = g.Wait() if err != nil { return err } return nil } func cleanupCommand(ctx context.Context, args *CleanupCmd) error { err := cleanup.Run(ctx, args.Addr, args.ContainerdRegistryConfigPath) if err != nil { return err } return nil } func cleanupWaitCommand(ctx context.Context, args *CleanupWaitCmd) error { err := cleanup.Wait(ctx, args.ProbeEndpoint, args.Period, args.Threshold) if err != nil { return err } return nil } func getBootstrapper(cfg BootstrapConfig) (routing.Bootstrapper, error) { //nolint: ireturn // Return type can be different structs. switch cfg.BootstrapKind { case "dns": return routing.NewDNSBootstrapper(cfg.DNSBootstrapDomain, 10), nil case "http": return routing.NewHTTPBootstrapper(cfg.HTTPBootstrapAddr, cfg.HTTPBootstrapPeer), nil case "static": return routing.NewStaticBootstrapperFromStrings(cfg.StaticBootstrapPeers) default: return nil, fmt.Errorf("unknown bootstrap kind %s", cfg.BootstrapKind) } } func loadBasicAuth() (string, string, error) { dirPath := "/etc/secrets/basic-auth" username, err := os.ReadFile(filepath.Join(dirPath, "username")) if err != nil && !errors.Is(err, os.ErrNotExist) { return "", "", err } password, err := os.ReadFile(filepath.Join(dirPath, "password")) if err != nil && !errors.Is(err, os.ErrNotExist) { return "", "", err } return string(username), string(password), nil } ``` ## /pkg/metrics/metrics.go ```go path="/pkg/metrics/metrics.go" package metrics import ( "github.com/prometheus/client_golang/prometheus" ) var ( // DefaultRegisterer and DefaultGatherer are the implementations of the // prometheus Registerer and Gatherer interfaces that all metrics operations // will use. They are variables so that packages that embed this library can // replace them at runtime, instead of having to pass around specific // registries. DefaultRegisterer = prometheus.DefaultRegisterer DefaultGatherer = prometheus.DefaultGatherer ) var ( MirrorRequestsTotal = prometheus.NewCounterVec(prometheus.CounterOpts{ Name: "spegel_mirror_requests_total", Help: "Total number of mirror requests.", }, []string{"registry", "cache"}) ResolveDurHistogram = prometheus.NewHistogramVec(prometheus.HistogramOpts{ Name: "spegel_resolve_duration_seconds", Help: "The duration for router to resolve a peer.", }, []string{"router"}) AdvertisedImages = prometheus.NewGaugeVec(prometheus.GaugeOpts{ Name: "spegel_advertised_images", Help: "Number of images advertised to be available.", }, []string{"registry"}) AdvertisedImageTags = prometheus.NewGaugeVec(prometheus.GaugeOpts{ Name: "spegel_advertised_image_tags", Help: "Number of image tags advertised to be available.", }, []string{"registry"}) AdvertisedImageDigests = prometheus.NewGaugeVec(prometheus.GaugeOpts{ Name: "spegel_advertised_image_digests", Help: "Number of image digests advertised to be available.", }, []string{"registry"}) AdvertisedKeys = prometheus.NewGaugeVec(prometheus.GaugeOpts{ Name: "spegel_advertised_keys", Help: "Number of keys advertised to be available.", }, []string{"registry"}) HttpRequestDurHistogram = prometheus.NewHistogramVec(prometheus.HistogramOpts{ Subsystem: "http", Name: "request_duration_seconds", Help: "The latency of the HTTP requests.", }, []string{"handler", "method", "code"}) HttpResponseSizeHistogram = prometheus.NewHistogramVec(prometheus.HistogramOpts{ Subsystem: "http", Name: "response_size_bytes", Help: "The size of the HTTP responses.", // 1kB up to 2GB Buckets: prometheus.ExponentialBuckets(1024, 5, 10), }, []string{"handler", "method", "code"}) HttpRequestsInflight = prometheus.NewGaugeVec(prometheus.GaugeOpts{ Subsystem: "http", Name: "requests_inflight", Help: "The number of inflight requests being handled at the same time.", }, []string{"handler"}) ) func Register() { DefaultRegisterer.MustRegister(MirrorRequestsTotal) DefaultRegisterer.MustRegister(ResolveDurHistogram) DefaultRegisterer.MustRegister(AdvertisedImages) DefaultRegisterer.MustRegister(AdvertisedImageTags) DefaultRegisterer.MustRegister(AdvertisedImageDigests) DefaultRegisterer.MustRegister(AdvertisedKeys) DefaultRegisterer.MustRegister(HttpRequestDurHistogram) DefaultRegisterer.MustRegister(HttpResponseSizeHistogram) DefaultRegisterer.MustRegister(HttpRequestsInflight) } ``` ## /pkg/metrics/metrics_test.go ```go path="/pkg/metrics/metrics_test.go" package metrics import "testing" func TestRegister(t *testing.T) { t.Parallel() Register() } ``` ## /pkg/oci/containerd.go ```go path="/pkg/oci/containerd.go" package oci import ( "bytes" "context" "encoding/base64" "encoding/json" "errors" "fmt" "io" "net/url" "os" "path" "path/filepath" "strings" "text/template" eventtypes "github.com/containerd/containerd/api/events" "github.com/containerd/containerd/v2/client" "github.com/containerd/containerd/v2/core/content" "github.com/containerd/errdefs" "github.com/containerd/typeurl/v2" "github.com/go-logr/logr" "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pelletier/go-toml/v2" tomlu "github.com/pelletier/go-toml/v2/unstable" "google.golang.org/grpc" utilversion "k8s.io/apimachinery/pkg/util/version" runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1" ) const ( backupDir = "_backup" ) var _ Client = &Containerd{} type Containerd struct { contentPath string client *client.Client clientGetter func() (*client.Client, error) listFilter string eventFilter string registryConfigPath string } type Option func(*Containerd) func WithContentPath(path string) Option { return func(c *Containerd) { c.contentPath = path } } func NewContainerd(sock, namespace, registryConfigPath string, mirroredRegistries []url.URL, opts ...Option) (*Containerd, error) { listFilter, eventFilter := createFilters(mirroredRegistries) c := &Containerd{ clientGetter: func() (*client.Client, error) { return client.New(sock, client.WithDefaultNamespace(namespace)) }, listFilter: listFilter, eventFilter: eventFilter, registryConfigPath: registryConfigPath, } for _, opt := range opts { opt(c) } return c, nil } func (c *Containerd) Client() (*client.Client, error) { var err error if c.client == nil { c.client, err = c.clientGetter() } return c.client, err } func (c *Containerd) Name() string { return "containerd" } func (c *Containerd) Verify(ctx context.Context) error { log := logr.FromContextOrDiscard(ctx) client, err := c.Client() if err != nil { return err } ok, err := client.IsServing(ctx) if err != nil { return err } if !ok { return errors.New("could not reach Containerd service") } grpcConn, ok := client.Conn().(*grpc.ClientConn) if !ok { return errors.New("client connection is not grpc") } srv := runtimeapi.NewRuntimeServiceClient(grpcConn) versionResp, err := srv.Version(ctx, &runtimeapi.VersionRequest{}) if err != nil { return err } ok, err = canVerifyContainerdConfiguration(versionResp.RuntimeVersion) if err != nil { return fmt.Errorf("could not check Containerd version %s: %w", versionResp.RuntimeVersion, err) } if !ok { log.Info("skipping verification of Containerd configuration", "version", versionResp.RuntimeVersion) return nil } statusResp, err := srv.Status(ctx, &runtimeapi.StatusRequest{Verbose: true}) if err != nil { return err } err = verifyStatusResponse(statusResp, c.registryConfigPath) if err != nil { return err } return nil } func canVerifyContainerdConfiguration(version string) (bool, error) { v, err := utilversion.Parse(version) if err != nil { return false, err } return v.LessThan(utilversion.MustParse("2.0")), nil } func verifyStatusResponse(resp *runtimeapi.StatusResponse, configPath string) error { str, ok := resp.Info["config"] if !ok { return errors.New("could not get config data from info response") } cfg := &struct { Registry struct { ConfigPath *string `json:"configPath"` } `json:"registry"` Containerd struct { DiscardUnpackedLayers *bool `json:"discardUnpackedLayers"` } `json:"containerd"` }{} err := json.Unmarshal([]byte(str), cfg) if err != nil { return err } if cfg.Containerd.DiscardUnpackedLayers == nil { return errors.New("field containerd.discardUnpackedLayers missing from config") } if *cfg.Containerd.DiscardUnpackedLayers { return errors.New("Containerd discard unpacked layers cannot be enabled") } if cfg.Registry.ConfigPath == nil { return errors.New("field registry.configPath missing from config") } if *cfg.Registry.ConfigPath == "" { return errors.New("Containerd registry config path needs to be set for mirror configuration to take effect") } paths := filepath.SplitList(*cfg.Registry.ConfigPath) for _, path := range paths { if path != configPath { continue } return nil } return fmt.Errorf("Containerd registry config path is %s but needs to contain path %s for mirror configuration to take effect", *cfg.Registry.ConfigPath, configPath) } func (c *Containerd) Subscribe(ctx context.Context) (<-chan ImageEvent, <-chan error, error) { imgCh := make(chan ImageEvent) errCh := make(chan error) client, err := c.Client() if err != nil { return nil, nil, err } envelopeCh, cErrCh := client.EventService().Subscribe(ctx, c.eventFilter) go func() { for envelope := range envelopeCh { var img Image imageName, eventType, err := getEventImage(envelope.Event) if err != nil { errCh <- err continue } switch eventType { case CreateEvent, UpdateEvent: cImg, err := client.GetImage(ctx, imageName) if err != nil { errCh <- err continue } img, err = ParseImageRequireDigest(cImg.Name(), cImg.Target().Digest) if err != nil { errCh <- err continue } case DeleteEvent: img, err = ParseImageRequireDigest(imageName, "") if err != nil { errCh <- err continue } } imgCh <- ImageEvent{Image: img, Type: eventType} } close(imgCh) }() go func() { for err := range cErrCh { errCh <- err } close(errCh) }() return imgCh, errCh, nil } func (c *Containerd) ListImages(ctx context.Context) ([]Image, error) { client, err := c.Client() if err != nil { return nil, err } cImgs, err := client.ListImages(ctx, c.listFilter) if err != nil { return nil, err } imgs := []Image{} for _, cImg := range cImgs { img, err := ParseImageRequireDigest(cImg.Name(), cImg.Target().Digest) if err != nil { return nil, err } imgs = append(imgs, img) } return imgs, nil } func (c *Containerd) Resolve(ctx context.Context, ref string) (digest.Digest, error) { client, err := c.Client() if err != nil { return "", err } cImg, err := client.GetImage(ctx, ref) if err != nil { return "", err } return cImg.Target().Digest, nil } func (c *Containerd) Size(ctx context.Context, dgst digest.Digest) (int64, error) { client, err := c.Client() if err != nil { return 0, err } info, err := client.ContentStore().Info(ctx, dgst) if errors.Is(err, errdefs.ErrNotFound) { return 0, errors.Join(ErrNotFound, err) } if err != nil { return 0, err } return info.Size, nil } func (c *Containerd) GetManifest(ctx context.Context, dgst digest.Digest) ([]byte, string, error) { client, err := c.Client() if err != nil { return nil, "", err } b, err := content.ReadBlob(ctx, client.ContentStore(), ocispec.Descriptor{Digest: dgst}) if errors.Is(err, errdefs.ErrNotFound) { return nil, "", errors.Join(ErrNotFound, err) } if err != nil { return nil, "", err } mt, err := DetermineMediaType(b) if err != nil { return nil, "", err } return b, mt, nil } func (c *Containerd) GetBlob(ctx context.Context, dgst digest.Digest) (io.ReadSeekCloser, error) { if c.contentPath != "" { path := filepath.Join(c.contentPath, "blobs", dgst.Algorithm().String(), dgst.Encoded()) file, err := os.Open(path) if errors.Is(err, os.ErrNotExist) { return nil, errors.Join(ErrNotFound, err) } if err != nil { return nil, err } return file, nil } client, err := c.Client() if err != nil { return nil, err } ra, err := client.ContentStore().ReaderAt(ctx, ocispec.Descriptor{Digest: dgst}) if errors.Is(err, errdefs.ErrNotFound) { return nil, errors.Join(ErrNotFound, err) } if err != nil { return nil, err } return struct { io.ReadSeeker io.Closer }{ ReadSeeker: io.NewSectionReader(ra, 0, ra.Size()), Closer: ra, }, nil } func getEventImage(e typeurl.Any) (string, EventType, error) { if e == nil { return "", "", errors.New("any cannot be nil") } evt, err := typeurl.UnmarshalAny(e) if err != nil { return "", "", fmt.Errorf("failed to unmarshal any: %w", err) } switch e := evt.(type) { case *eventtypes.ImageCreate: return e.Name, CreateEvent, nil case *eventtypes.ImageUpdate: return e.Name, UpdateEvent, nil case *eventtypes.ImageDelete: return e.Name, DeleteEvent, nil default: return "", "", errors.New("unsupported event type") } } func createFilters(mirroredRegistries []url.URL) (string, string) { registryHosts := []string{} for _, registry := range mirroredRegistries { registryHosts = append(registryHosts, strings.ReplaceAll(registry.Host, `.`, `\\.`)) } listFilter := fmt.Sprintf(`name~="^(%s)/"`, strings.Join(registryHosts, "|")) if len(registryHosts) == 0 { // Filter images that do not have a registry in it's reference, // as we cant mirror images without registries. listFilter = `name~="^.+/"` } eventFilter := fmt.Sprintf(`topic~="/images/create|/images/update|/images/delete",event.%s`, listFilter) return listFilter, eventFilter } // Refer to containerd registry configuration documentation for more information about required configuration. // https://github.com/containerd/containerd/blob/main/docs/cri/config.md#registry-configuration // https://github.com/containerd/containerd/blob/main/docs/hosts.md#registry-configuration---examples func AddMirrorConfiguration(ctx context.Context, configPath string, mirroredRegistries, mirrorTargets []url.URL, resolveTags, prependExisting bool, username, password string) error { log := logr.FromContextOrDiscard(ctx) err := validateRegistries(mirroredRegistries) if err != nil { return err } err = os.MkdirAll(configPath, 0o755) if err != nil { return err } err = backupConfig(log, configPath) if err != nil { return err } err = clearConfig(configPath) if err != nil { return err } capabilities := []string{"pull"} if resolveTags { capabilities = append(capabilities, "resolve") } if len(mirroredRegistries) == 0 { mirroredRegistries = append(mirroredRegistries, url.URL{}) } // Write mirror configuration for _, mirroredRegistry := range mirroredRegistries { templatedHosts, err := templateHosts(mirroredRegistry, mirrorTargets, capabilities, username, password) if err != nil { return err } if prependExisting { existingHosts, err := existingHosts(configPath, mirroredRegistry) if err != nil { return err } if existingHosts != "" { templatedHosts = templatedHosts + "\n\n" + existingHosts } log.Info("prepending to existing Containerd mirror configuration", "registry", mirroredRegistry.String()) } hostComp := mirroredRegistry.Host if hostComp == "" { hostComp = "_default" } fp := path.Join(configPath, hostComp, "hosts.toml") err = os.MkdirAll(filepath.Dir(fp), 0o755) if err != nil { return err } err = os.WriteFile(fp, []byte(templatedHosts), 0o644) if err != nil { return err } log.Info("added Containerd mirror configuration", "registry", mirroredRegistry.String(), "path", fp) } return nil } func CleanupMirrorConfiguration(ctx context.Context, configPath string) error { log := logr.FromContextOrDiscard(ctx) // If backup directory does not exist it means mirrors was never configured or cleanup has already run. backupDirPath := path.Join(configPath, backupDir) ok, err := dirExists(backupDirPath) if err != nil { return err } if !ok { log.Info("skipping cleanup because backup directory does not exist") return nil } // Remove everything except _backup err = clearConfig(configPath) if err != nil { return err } // Move content from backup directory files, err := os.ReadDir(backupDirPath) if err != nil { return err } for _, fi := range files { oldPath := path.Join(backupDirPath, fi.Name()) newPath := path.Join(configPath, fi.Name()) err := os.Rename(oldPath, newPath) if err != nil { return err } log.Info("recovering Containerd host configuration", "path", oldPath) } // Remove backup directory to indicate that cleanup has been run. err = os.RemoveAll(backupDirPath) if err != nil { return err } return nil } func validateRegistries(urls []url.URL) error { errs := []error{} for _, u := range urls { if u.Scheme != "http" && u.Scheme != "https" { errs = append(errs, fmt.Errorf("invalid registry url scheme must be http or https: %s", u.String())) } if u.Path != "" { errs = append(errs, fmt.Errorf("invalid registry url path has to be empty: %s", u.String())) } if len(u.Query()) != 0 { errs = append(errs, fmt.Errorf("invalid registry url query has to be empty: %s", u.String())) } if u.User != nil { errs = append(errs, fmt.Errorf("invalid registry url user has to be empty: %s", u.String())) } } return errors.Join(errs...) } func backupConfig(log logr.Logger, configPath string) error { backupDirPath := path.Join(configPath, backupDir) ok, err := dirExists(backupDirPath) if err != nil { return err } if ok { return nil } files, err := os.ReadDir(configPath) if err != nil { return err } err = os.MkdirAll(backupDirPath, 0o755) if err != nil { return err } for _, fi := range files { oldPath := path.Join(configPath, fi.Name()) newPath := path.Join(backupDirPath, fi.Name()) err := os.Rename(oldPath, newPath) if err != nil { return err } log.Info("backing up Containerd host configuration", "path", oldPath) } return nil } func clearConfig(configPath string) error { files, err := os.ReadDir(configPath) if err != nil { return err } for _, fi := range files { if fi.Name() == backupDir { continue } filePath := path.Join(configPath, fi.Name()) err := os.RemoveAll(filePath) if err != nil { return err } } return nil } func templateHosts(mirroredRegistry url.URL, mirrorTargets []url.URL, capabilities []string, username, password string) (string, error) { server := mirroredRegistry.String() if mirroredRegistry.String() == "https://docker.io" { server = "https://registry-1.docker.io" } authorization := "" if username != "" || password != "" { authorization = username + ":" + password authorization = base64.StdEncoding.EncodeToString([]byte(authorization)) authorization = "Basic " + authorization } hc := struct { Authorization string Server string Capabilities string MirrorTargets []url.URL }{ Server: server, Capabilities: fmt.Sprintf("['%s']", strings.Join(capabilities, "', '")), MirrorTargets: mirrorTargets, Authorization: authorization, } tmpl, err := template.New("").Parse(`{{- with .Server }}server = '{{ . }}'{{ end }} {{- $authorization := .Authorization }} {{ range .MirrorTargets }} [host.'{{ .String }}'] capabilities = {{ $.Capabilities }} {{- if $authorization }} [host.'{{ .String }}'.header] Authorization = '{{ $authorization }}' {{- end }} {{ end }}`) if err != nil { return "", err } buf := bytes.NewBuffer(nil) err = tmpl.Execute(buf, hc) if err != nil { return "", err } return strings.TrimSpace(buf.String()), nil } type hostFile struct { Hosts map[string]any `toml:"host"` } func existingHosts(configPath string, mirroredRegistry url.URL) (string, error) { fp := path.Join(configPath, backupDir, mirroredRegistry.Host, "hosts.toml") b, err := os.ReadFile(fp) if errors.Is(err, os.ErrNotExist) { return "", nil } if err != nil { return "", err } var hf hostFile err = toml.Unmarshal(b, &hf) if err != nil { return "", err } if len(hf.Hosts) == 0 { return "", nil } hosts := []string{} parser := tomlu.Parser{} parser.Reset(b) for parser.NextExpression() { err := parser.Error() if err != nil { return "", err } e := parser.Expression() if e.Kind != tomlu.Table { continue } ki := e.Key() if ki.Next() && string(ki.Node().Data) == "host" && ki.Next() && ki.IsLast() { hosts = append(hosts, string(ki.Node().Data)) } } ehs := []string{} for _, h := range hosts { data := hostFile{ Hosts: map[string]any{ h: hf.Hosts[h], }, } b, err := toml.Marshal(data) if err != nil { return "", err } eh := strings.TrimPrefix(string(b), "[host]\n") ehs = append(ehs, eh) } return strings.TrimSpace(strings.Join(ehs, "\n")), nil } func dirExists(path string) (bool, error) { info, err := os.Stat(path) if err != nil { if os.IsNotExist(err) { return false, nil } return false, err } return info.IsDir(), nil } ``` The content has been capped at 50000 tokens, and files over NaN bytes have been omitted. The user could consider applying other filters to refine the result. The better and more specific the context, the better the LLM can follow instructions. If the context seems verbose, the user can refine the filter using uithub. Thank you for using https://uithub.com - Perfect LLM context for any GitHub repo.