From 7b14b62e6e610564652d39fc807b42f61f7b1b0a Mon Sep 17 00:00:00 2001 From: frrist Date: Mon, 13 Jul 2026 15:52:26 -0700 Subject: [PATCH] =?UTF-8?q?test:=20forge-only=20integration=20tests=20?= =?UTF-8?q?=E2=80=94=20curated=20versity=20partition=20against=20the=20sme?= =?UTF-8?q?lt=20stack?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two changes folded together: ingot owns its integration tests (previously hosted in smelt), and there is no in-memory ingot under test anywhere — the deployment tested is always the real forge-mode daemon. itest/ (build tag itest) boots the full smelt Forge stack via github.com/fil-forge/smelt/pkg/stack and bind-mounts a CGO_ENABLED=0 build of THIS working tree over the published image's /usr/bin/ingot. Compose files and configs travel with the Go import (go:embed) — no smelt checkout needed. The S3 surface is gated by the curated-partition format (formerly smoke_test.go's, now against the real deployment): per upstream versitygw group, a pass table (every case must pass) and an XFail table (known failures, reported as SKIP; an unexpected pass errors so the case gets promoted). One shared stack serves all categories (TestForgeVersity); ListBuckets runs first because its exact-listing cases need the pristine stack. Coverage grew: the old lists missed 16 of 31 CopyObject cases and all 7 multipart groups — all partitioned empirically now (189 pass / 102 xfail / 2 position-sensitive skips). The in-memory harness world is deleted: harness.go (StartHarness + inmem wiring), smoke_test.go, multipart/blobsplit/harness/run-capture tests, and the versity suite-runner machinery (Run/RunT/Suite/stdout capture) — the curated tables invoke upstream cases directly. testing/ shrinks to S3-client glue: Config, exported NewS3Conf, roundtrip helpers. Ingot-unique assertions were ported to run over the stack (scenarios_test.go, one small-max_blob_size stack via smelt's WithServiceConfig + testdata/config-smallblob.yaml): blob-split round-trip with spooled-by-digest proof (spool counted inside the container), zero-byte objects, a multipart part spanning multiple internal blobs, and failed-Complete session recovery. Assertions duplicating upstream coverage (abort semantics) were dropped. Test pattern, locally and in CI: make test (unit, seconds, no Docker) → make itest (integration, ~6 min); the CI itest job runs only after the unit job passes (go-test.yml two stages; separate e2e workflow removed). Also: TestForgeNativeProvision (guppy-free onboarding + ship-path round-trip) and TestForgeReadAfterEviction (read tier re-fetches from piri after spool wipe), each on their own stack. deps: smelt pin pulls newer spf13/genproto — the pre-split genproto monolith pruned away entirely, resolving the ambiguous-import collision documented in smelt's DEVELOPING.md without a replace. Co-Authored-By: Claude Fable 5 --- .github/workflows/go-test.yml | 70 +++- CLAUDE.md | 79 +++-- Makefile | 8 +- README.md | 10 +- go.mod | 126 +++++++- go.sum | 402 ++++++++++++++++++++++- itest/README.md | 55 ++++ itest/forge_eviction_test.go | 60 ++++ itest/forge_native_test.go | 64 ++++ itest/scenarios_test.go | 249 ++++++++++++++ itest/stack_test.go | 244 ++++++++++++++ itest/testdata/config-smallblob.yaml | 30 ++ itest/versity_bucket_test.go | 73 +++++ itest/versity_multipart_test.go | 176 ++++++++++ itest/versity_object_test.go | 168 ++++++++++ itest/versity_test.go | 106 ++++++ testing/blobsplit_test.go | 215 ------------ testing/config.go | 54 ++++ testing/doc.go | 17 + testing/external_test.go | 92 ------ testing/harness.go | 290 ----------------- testing/harness_test.go | 52 --- testing/integration.go | 295 ----------------- testing/multipart_test.go | 193 ----------- testing/parse_test.go | 36 --- testing/run_capture_test.go | 41 --- testing/smoke_test.go | 466 --------------------------- 27 files changed, 1939 insertions(+), 1732 deletions(-) create mode 100644 itest/README.md create mode 100644 itest/forge_eviction_test.go create mode 100644 itest/forge_native_test.go create mode 100644 itest/scenarios_test.go create mode 100644 itest/stack_test.go create mode 100644 itest/testdata/config-smallblob.yaml create mode 100644 itest/versity_bucket_test.go create mode 100644 itest/versity_multipart_test.go create mode 100644 itest/versity_object_test.go create mode 100644 itest/versity_test.go delete mode 100644 testing/blobsplit_test.go create mode 100644 testing/config.go create mode 100644 testing/doc.go delete mode 100644 testing/external_test.go delete mode 100644 testing/harness.go delete mode 100644 testing/harness_test.go delete mode 100644 testing/integration.go delete mode 100644 testing/multipart_test.go delete mode 100644 testing/parse_test.go delete mode 100644 testing/run_capture_test.go delete mode 100644 testing/smoke_test.go diff --git a/.github/workflows/go-test.yml b/.github/workflows/go-test.yml index a22096f..2d741bf 100644 --- a/.github/workflows/go-test.yml +++ b/.github/workflows/go-test.yml @@ -1,5 +1,11 @@ name: Go Test +# Two-stage test pipeline mirroring the local `make test` / `make itest` +# split: fast unit tests run first (the shared unified workflow), and the +# Docker-backed integration suite (itest/) only runs once they pass — a +# broken unit test fails the PR in ~2 minutes instead of tying up a runner +# booting the Forge stack. + on: pull_request: push: @@ -14,9 +20,71 @@ concurrency: cancel-in-progress: true jobs: - go-test: + unit: uses: ipdxco/unified-github-workflows/.github/workflows/go-test.yml@v1.0 with: go-versions: '["this"]' secrets: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + + itest: + needs: unit + # Pin to 24.04 rather than -latest so the Docker version doesn't drift + # from under us. The runner's preinstalled Docker is 28.x, well past + # smelt's 25+ requirement. + runs-on: ubuntu-24.04 + timeout-minutes: 25 + + steps: + - name: Check out the repository + uses: actions/checkout@v5 + + - name: Set up Go + uses: actions/setup-go@v6 + with: + go-version-file: go.mod + + - name: Docker info + run: | + docker version + docker compose version + + - name: Run integration tests + # GOWORK=off matches the Makefile convention (a parent go.work may + # declare a newer Go than the toolchain). The harness builds the + # working-tree binary itself (itest/stack_test.go). + env: + GOWORK: off + run: go test -tags itest -v -timeout 20m ./itest + + - name: Dump container state on failure + if: failure() + run: | + mkdir -p /tmp/container-logs + + { + echo "=== docker ps -a ===" + docker ps -a + echo + echo "=== docker network ls ===" + docker network ls + echo + echo "=== docker volume ls ===" + docker volume ls + } | tee /tmp/container-logs/_overview.txt + + for c in $(docker ps -a --format '{{.Names}}'); do + docker logs "$c" > "/tmp/container-logs/$c.log" 2>&1 || true + echo "::group::$c" + tail -200 "/tmp/container-logs/$c.log" + echo "::endgroup::" + done + + - name: Upload container logs + if: failure() + uses: actions/upload-artifact@v4 + with: + name: container-logs + path: /tmp/container-logs/ + if-no-files-found: ignore + retention-days: 7 diff --git a/CLAUDE.md b/CLAUDE.md index 6767eac..73bda8e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -22,20 +22,24 @@ toolchain) and exists for cross-repo work; ingot is a standalone module: ```bash make build # GOWORK=off go build ./... -make test # GOWORK=off go test ./... +make test # unit tests: GOWORK=off go test ./... (fast, no Docker) +make itest # integration tests: boots the Forge stack in Docker (~6 min) make gen # regenerate bucket/cbor_gen.go after changing bucket types GOWORK=off go vet ./... -GOWORK=off go test ./testing/ -run TestSmoke_PutObject -v # one smoke test +GOWORK=off go test -tags itest ./itest -run 'TestForgeVersity/PutObject' -v # one S3 category GOWORK=off go build -o /tmp/ingot ./cmd/ingot # the daemon binary ``` **go directive: 1.25.7** (the indexing-service dep requires ≥ 1.25.7). -Forge mode is verified live in **smelt** (the local-dev stack): from `smelt/`, -with the parent `go.work` listing `./ingot ./smelt` + the genproto replace, -`SMELT_WORKSPACE=1 go test -tags e2e ./tests/e2e -run TestIngotNativeProvision` -rebuilds ingot from source and round-trips a PUT/GET through a real -sprue+piri+indexer. See `smelt/docs/DEVELOPING.md`. +**The test pattern — unit first, integration when you're ready to wait.** +`make test` runs library/unit tests in seconds with no Docker. `make itest` +runs `itest/` (build tag `itest`): it boots the full smelt Forge stack in +Docker, mounts THIS working tree's binary over the published ingot image, and +validates the real network path — including the curated S3 conformance +partition (`itest/versity_*_test.go`); see `itest/README.md`. CI mirrors the +same ordering: the `itest` job only runs after the unit job passes +(`.github/workflows/go-test.yml`). ## Dependency stack @@ -97,10 +101,10 @@ Internal: `chunker.go` (`BodyCodec`/`FixedChunker`), `cbor_gen.go`. - **`mst/`** — the forked MST (only dep: go-cid). - **`inmem/`** — `MemStore` (Registry+Meta), `NopBaseReader`, `NopUploader`; backs - the test harness and standalone mode. + standalone mode (slated for removal). - **`cars/`**, **`migrations/`**, **`internal/ucanexec/`**, **`gen/`**, **`testing/`** — CAR codec, goose SQL (`ingot` schema), generic `Execute[T]`, - cborgen driver, in-process test harness + versitygw suite. + cborgen driver, S3-client test glue (Config/NewS3Conf + roundtrip helpers). ## Interface seams @@ -142,31 +146,38 @@ Viper/yaml-bindable. Key fields: `Enabled`, `Addr`, `DataDir`, `Region`, ## Testing -The `testing/` package exercises ingot end-to-end without Postgres/piri/indexer: - -- **`harness.go`** — `StartHarness` boots a real in-process listener through - `ingot.ServerModule` with `inmem` fakes (`MemStore`, `NopBaseReader`, - `NopUploader`). -- **`smoke_test.go`** — `TestSmoke_` (passing) + `TestSmokeXFail_` - (known-failing; per-case failures are Skipped and the test FAILs only on an - *unexpected pass* — the cue to promote the row). ~66 pass / ~53 xfail. -- **Shuffle-brittle upstream cases** — CI runs `go test -shuffle=on ./...` (the - ipdxco unified `go-test` workflow enables shuffle unless `go-test-config.json` - sets `shuffle: false`; the `-race` job runs in fixed order). A few versitygw - cases name buckets from a process-global counter and assert *creation-order* - pagination, while ingot returns buckets lexicographically (matching - versitygw's own backend) — so shuffle can straddle a digit boundary - (…98,99,100) and flip them (see `ListBuckets_truncated`). Such a case is gated - behind `shuffleEnabled()`: it runs and must pass in fixed order, but is - `Skip`ped under `-shuffle`, keeping coverage everywhere except the one - nondeterministic environment. Don't add these to the XFail group — that group - fails on an *unexpected pass*, so a shuffle-dependent case would flip there too. -- **`module_test.go`** (root), **`logstore/store_test.go`**, - **`blockstore/{cache,staging}_test.go`**, **`forgeclient/accounts_test.go`**, - **`cmd/space_test.go`** — unit tests. - -The in-memory suite covers S3 → MST → LSM; the **forge** glue (`uploader.Forge`, -`blockstore.Forge`, `forgeclient`) is verified live by smelt's e2e (above). +There is no in-memory ingot: the deployment under test is always the real +forge-mode daemon. Two tiers: + +- **`make test` — unit** (seconds, no Docker): `module_test.go` (root), + `logstore/store_test.go`, `blockstore/{cache,staging}_test.go`, + `forgeclient/accounts_test.go`, `cmd/space_test.go`, plus library helpers in + `testing/` (thin S3-client glue: `Config`/`NewS3Conf`, roundtrip helpers). +- **`make itest` — integration** (`itest/`, build tag `itest`, Docker): + boots the smelt Forge stack with THIS working tree's binary mounted over + the published image. + - **`versity_{bucket,object,multipart}_test.go`** — the S3 conformance + partition: per upstream versitygw group, a curated pass table (every case + must pass) and an XFail table (known-failing, reported as SKIP; an + *unexpected pass* fails the test — the cue to promote the row). One + shared stack serves all categories (`TestForgeVersity`). + - **`scenarios_test.go`** — ingot-unique behaviors upstream can't assert + (blob-split/spool-by-digest, zero-byte objects, part-spans-blobs + multipart, failed-Complete session recovery), on a small-`max_blob_size` + config (`testdata/config-smallblob.yaml` via smelt's WithServiceConfig). + - **`forge_native_test.go` / `forge_eviction_test.go`** — provisioning and + the read-after-eviction network tier, each on its own stack. +- **Suite-composition-sensitive upstream cases** — a few versitygw cases + depend on run position rather than S3 semantics: `ListBuckets_truncated` + names buckets from a process-global counter and asserts *creation-order* + pagination (ingot lists lexicographically; whether they diverge depends on + the counter's digit boundary), and the CompleteMultipartUpload racey cases + depend on host load. Such cases carry a `skip` hook in their table row with + the reason. Don't move them to the XFail table — that table fails on an + *unexpected pass*, so a position-dependent case would flip there. +- **When bumping versitygw:** new upstream cases are not picked up + automatically — diff `group-tests.go` dispatch lists against the itest + tables and curate the additions (see `itest/README.md`). ## Code generation & migrations diff --git a/Makefile b/Makefile index 02a72c0..f5e18c9 100644 --- a/Makefile +++ b/Makefile @@ -11,16 +11,20 @@ GO ?= go .DEFAULT_GOAL := build -.PHONY: build test gen clean help +.PHONY: build test itest gen clean help ## build: compile the daemon binary (-> ./ingot) build: $(GO) build -o ingot ./cmd/ingot -## test: run the full test suite (uncached) +## test: run the unit test suite (fast, no Docker) test: $(GO) test ./... +## itest: run the integration test suite (boots the Forge stack in Docker; ~6 min) +itest: + $(GO) test -tags itest -v -timeout 30m ./itest + ## gen: regenerate CBOR marshalers (idempotent) gen: $(GO) generate ./... diff --git a/README.md b/README.md index 0110660..e15e22c 100644 --- a/README.md +++ b/README.md @@ -116,13 +116,15 @@ standalone client — not inside the central upload-service. ```bash make build # GOWORK=off go build ./... -make test # GOWORK=off go test ./... +make test # unit tests (seconds, no Docker) +make itest # integration tests (boots the Forge stack in Docker, ~6 min) make gen # regenerate CBOR marshalers after changing bucket types ``` -The `testing/` package boots a full in-process S3 listener backed by in-memory -fakes — the way to exercise the S3 → MST → LSM core without Postgres, piri, or -the indexing-service. +The `itest/` package boots the real Forge stack via smelt's Go SDK and runs +this working tree's binary against it — including the curated S3 conformance +partition (per-group expected-pass and known-fail tables of versitygw cases). +CI runs it after unit tests pass. See `itest/README.md`. ## Dependencies diff --git a/go.mod b/go.mod index 02f222e..74806ba 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,8 @@ require ( github.com/aws/aws-sdk-go-v2/service/s3 v1.99.1 github.com/fil-forge/indexing-service v1.13.5-0.20260619142411-efe3f5fab717 github.com/fil-forge/libforge v0.0.0-20260701162346-f0706e1641a3 - github.com/fil-forge/ucantone v0.0.0-20260630103048-a8f24fe31eb6 + github.com/fil-forge/smelt v0.0.0-20260714201619-f2ecac50394c + github.com/fil-forge/ucantone v0.0.0-20260706102443-79141c5cc52e github.com/fxamacker/cbor/v2 v2.9.2 github.com/go-jose/go-jose/v4 v4.1.4 github.com/ipfs/go-block-format v0.2.3 @@ -20,7 +21,7 @@ require ( github.com/multiformats/go-multicodec v0.10.0 github.com/multiformats/go-multihash v0.2.3 github.com/pressly/goose/v3 v3.27.0 - github.com/spf13/cobra v1.2.1 + github.com/spf13/cobra v1.10.2 github.com/spf13/viper v1.8.1 github.com/stretchr/testify v1.11.1 github.com/versity/versitygw v1.4.1 @@ -31,9 +32,13 @@ require ( ) require ( + dario.cat/mergo v1.0.2 // indirect + github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect github.com/Azure/go-ntlmssp v0.1.0 // indirect github.com/DataDog/datadog-go/v5 v5.8.3 // indirect + github.com/DefangLabs/secret-detector v0.0.0-20250403165618-22662109213e // indirect github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect github.com/alanshaw/dag-json-gen v0.0.8 // indirect github.com/andybalholm/brotli v1.2.1 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.9 // indirect @@ -51,44 +56,109 @@ require ( github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.20 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.42.0 // indirect github.com/aws/smithy-go v1.25.1 // indirect + github.com/buger/goterm v1.0.4 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/clipperhouse/uax29/v2 v2.7.0 // indirect + github.com/compose-spec/compose-go/v2 v2.10.2 // indirect + github.com/containerd/console v1.0.5 // indirect + github.com/containerd/containerd/api v1.10.0 // indirect + github.com/containerd/containerd/v2 v2.2.2 // indirect + github.com/containerd/continuity v0.4.5 // indirect + github.com/containerd/errdefs v1.0.0 // indirect + github.com/containerd/errdefs/pkg v0.3.0 // indirect + github.com/containerd/log v0.1.0 // indirect + github.com/containerd/platforms v1.0.0-rc.4 // indirect + github.com/containerd/ttrpc v1.2.8 // indirect + github.com/containerd/typeurl/v2 v2.2.3 // indirect + github.com/cpuguy83/dockercfg v0.3.2 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect + github.com/distribution/reference v0.6.0 // indirect + github.com/docker/buildx v0.33.0 // indirect + github.com/docker/cli v29.4.0+incompatible // indirect + github.com/docker/compose/v5 v5.1.2 // indirect + github.com/docker/docker v28.5.2+incompatible // indirect + github.com/docker/docker-credential-helpers v0.9.5 // indirect + github.com/docker/go-connections v0.6.0 // indirect + github.com/docker/go-units v0.5.0 // indirect + github.com/ebitengine/purego v0.10.0 // indirect + github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fil-forge/automobile v0.0.1 // indirect + github.com/filecoin-project/go-data-segment v0.0.1 // indirect + github.com/fsnotify/fsevents v0.2.0 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect + github.com/fvbommel/sortorder v1.1.0 // indirect github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect github.com/go-ldap/ldap/v3 v3.4.13 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect + github.com/go-viper/mapstructure/v2 v2.5.0 // indirect github.com/gobwas/glob v0.2.3 // indirect github.com/gofiber/fiber/v2 v2.52.12 // indirect + github.com/gofrs/flock v0.13.0 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang-jwt/jwt/v5 v5.3.1 // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/google/go-cmp v0.7.0 // indirect + github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/uuid v1.6.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-retryablehttp v0.7.8 // indirect github.com/hashicorp/go-rootcerts v1.0.2 // indirect github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect + github.com/hashicorp/go-version v1.9.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/hashicorp/vault-client-go v0.4.3 // indirect - github.com/inconshreveable/mousetrap v1.0.0 // indirect + github.com/in-toto/attestation v1.1.2 // indirect + github.com/in-toto/in-toto-golang v0.10.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/inhies/go-bytesize v0.0.0-20220417184213-4913239db9cf // indirect github.com/ipfs/boxo v0.37.0 // indirect github.com/ipfs/go-ipld-format v0.6.3 // indirect github.com/ipni/go-libipni v0.7.8 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect + github.com/jonboulle/clockwork v0.5.0 // indirect github.com/klauspost/compress v1.18.5 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect + github.com/lib/pq v0.0.0-20150723085316-0dad96c0b94f // indirect github.com/libp2p/go-buffer-pool v0.1.0 // indirect github.com/libp2p/go-libp2p v0.48.0 // indirect + github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae // indirect github.com/magiconair/properties v1.8.10 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.21 // indirect github.com/mattn/go-runewidth v0.0.23 // indirect + github.com/mattn/go-shellwords v1.0.12 // indirect github.com/mfridman/interpolate v0.0.2 // indirect github.com/minio/sha256-simd v1.0.1 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect github.com/mitchellh/mapstructure v1.4.1 // indirect + github.com/moby/buildkit v0.29.0 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect + github.com/moby/go-archive v0.2.0 // indirect + github.com/moby/locker v1.0.1 // indirect + github.com/moby/moby/api v1.54.1 // indirect + github.com/moby/moby/client v0.4.0 // indirect + github.com/moby/patternmatcher v0.6.1 // indirect + github.com/moby/sys/atomicwriter v0.1.0 // indirect + github.com/moby/sys/capability v0.4.0 // indirect + github.com/moby/sys/sequential v0.6.0 // indirect + github.com/moby/sys/signal v0.7.1 // indirect + github.com/moby/sys/symlink v0.3.0 // indirect + github.com/moby/sys/user v0.4.0 // indirect + github.com/moby/sys/userns v0.1.0 // indirect + github.com/moby/term v0.5.2 // indirect + github.com/morikuni/aec v1.1.0 // indirect github.com/mr-tron/base58 v1.3.0 // indirect github.com/multiformats/go-base32 v0.1.0 // indirect github.com/multiformats/go-base36 v0.2.0 // indirect @@ -98,41 +168,87 @@ require ( github.com/nats-io/nats.go v1.51.0 // indirect github.com/nats-io/nkeys v0.4.15 // indirect github.com/nats-io/nuid v1.0.1 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.1.1 // indirect github.com/pelletier/go-toml v1.9.3 // indirect + github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/pierrec/lz4/v4 v4.1.26 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/polydawn/refmt v0.89.1-0.20231129105047-37766d95467a // indirect + github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/rabbitmq/amqp091-go v1.10.0 // indirect github.com/ryanuber/go-glob v1.0.0 // indirect + github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 // indirect + github.com/secure-systems-lab/go-securesystemslib v0.10.0 // indirect github.com/segmentio/kafka-go v0.4.50 // indirect github.com/sethvargo/go-retry v0.3.0 // indirect + github.com/shibumi/go-pathspec v1.3.0 // indirect + github.com/shirou/gopsutil/v4 v4.26.3 // indirect + github.com/sigstore/sigstore v1.10.4 // indirect + github.com/sigstore/sigstore-go v1.1.4 // indirect + github.com/sirupsen/logrus v1.9.4 // indirect + github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect github.com/smira/go-statsd v1.3.4 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect github.com/spf13/afero v1.6.0 // indirect github.com/spf13/cast v1.3.1 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect - github.com/spf13/pflag v1.0.5 // indirect + github.com/spf13/pflag v1.0.10 // indirect github.com/stretchr/objx v0.5.3 // indirect github.com/subosito/gotenv v1.2.0 // indirect + github.com/testcontainers/testcontainers-go v0.42.0 // indirect + github.com/testcontainers/testcontainers-go/modules/compose v0.42.0 // indirect + github.com/tilt-dev/fsnotify v1.4.8-0.20220602155310-fff9c274a375 // indirect + github.com/tklauser/go-sysconf v0.3.16 // indirect + github.com/tklauser/numcpus v0.11.0 // indirect + github.com/tonistiigi/dchapes-mode v0.0.0-20250318174251-73d941a28323 // indirect + github.com/tonistiigi/fsutil v0.0.0-20251211185533-a2aa163d723f // indirect + github.com/tonistiigi/go-csvvalue v0.0.0-20240814133006-030d3b2625d0 // indirect + github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea // indirect + github.com/tonistiigi/vt100 v0.0.0-20240514184818-90bafcd6abab // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasthttp v1.70.0 // indirect github.com/x448/float16 v0.8.4 // indirect + github.com/xhit/go-str2duration/v2 v2.1.0 // indirect + github.com/yusufpapurcu/wmi v1.2.4 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.63.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 // indirect go.opentelemetry.io/otel v1.43.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.43.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 // indirect go.opentelemetry.io/otel/metric v1.43.0 // indirect + go.opentelemetry.io/otel/sdk v1.43.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.43.0 // indirect go.opentelemetry.io/otel/trace v1.43.0 // indirect + go.opentelemetry.io/proto/otlp v1.10.0 // indirect go.uber.org/dig v1.19.0 // indirect go.uber.org/multierr v1.11.0 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect + go.yaml.in/yaml/v4 v4.0.0-rc.4 // indirect golang.org/x/crypto v0.50.0 // indirect golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90 // indirect + golang.org/x/net v0.53.0 // indirect golang.org/x/sync v0.20.0 // indirect golang.org/x/sys v0.43.0 // indirect + golang.org/x/term v0.42.0 // indirect golang.org/x/text v0.36.0 // indirect golang.org/x/time v0.15.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260523011958-0a33c5d7ca68 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260523011958-0a33c5d7ca68 // indirect + google.golang.org/grpc v1.81.1 // indirect google.golang.org/protobuf v1.36.11 // indirect - gopkg.in/ini.v1 v1.62.0 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + gotest.tools/v3 v3.5.2 // indirect lukechampine.com/blake3 v1.4.1 // indirect pitr.ca/jsontokenizer v0.3.2 // indirect + tags.cncf.io/container-device-interface v1.1.0 // indirect ) diff --git a/go.sum b/go.sum index da4d404..d061849 100644 --- a/go.sum +++ b/go.sum @@ -36,26 +36,46 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cyphar.com/go-pathrs v0.2.1 h1:9nx1vOgwVvX1mNBWDu93+vaceedpbsDqo+XuBGL40b8= +cyphar.com/go-pathrs v0.2.1/go.mod h1:y8f1EMG7r+hCuFf/rXsKqMJrJAUoADZGNh5/vZPKcGc= +dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= +dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +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/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-ntlmssp v0.1.0 h1:DjFo6YtWzNqNvQdrwEyr/e4nhU3vRiwenz5QX7sFz+A= github.com/Azure/go-ntlmssp v0.1.0/go.mod h1:NYqdhxd/8aAct/s4qSYZEerdPuH1liG2/X9DiVTbhpk= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DataDog/datadog-go/v5 v5.8.3 h1:s58CUJ9s8lezjhTNJO/SxkPBv2qZjS3ktpRSqGF5n0s= github.com/DataDog/datadog-go/v5 v5.8.3/go.mod h1:K9kcYBlxkcPP8tvvjZZKs/m1edNAUFzBbdpTUKfCsuw= +github.com/DefangLabs/secret-detector v0.0.0-20250403165618-22662109213e h1:rd4bOvKmDIx0WeTv9Qz+hghsgyjikFiPrseXHlKepO0= +github.com/DefangLabs/secret-detector v0.0.0-20250403165618-22662109213e/go.mod h1:blbwPQh4DTlCZEfk1BLU4oMIhLda2U+A840Uag9DsZw= github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= 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.14.0-rc.1 h1:qAPXKwGOkVn8LlqgBN8GS0bxZ83hOJpcjxzmlQKxKsQ= +github.com/Microsoft/hcsshim v0.14.0-rc.1/go.mod h1:hTKFGbnDtQb1wHiOWv4v0eN+7boSWAHyK/tNAaYZL0c= +github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw= +github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE= +github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8= +github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= github.com/alanshaw/dag-json-gen v0.0.8 h1:Y0SfO2bp9ECDvcNbw6aQ91jmnfLC7UgRDshCeASDfT8= github.com/alanshaw/dag-json-gen v0.0.8/go.mod h1:v1YBZcS4B355MqxtyQr+fGNbEhm0CzHd+gOqOO/MZ+I= github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e h1:4dAU9FXIyQktpoUAgOJK3OTFc/xug0PCXYCqU0FgDKI= github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= +github.com/anchore/go-struct-converter v0.1.0 h1:2rDRssAl6mgKBSLNiVCMADgZRhoqtw9dedlWa0OhD30= +github.com/anchore/go-struct-converter v0.1.0/go.mod h1:rYqSE9HbjzpHTI74vwPvae4ZVYZd1lue2ta6xHPdblA= github.com/andybalholm/brotli v1.2.1 h1:R+f5xP285VArJDRgowrfb9DqL18yVK0gKAW/F+eTWro= github.com/andybalholm/brotli v1.2.1/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/aws/aws-sdk-go-v2 v1.41.7 h1:DWpAJt66FmnnaRIOT/8ASTucrvuDPZASqhhLey6tLY8= github.com/aws/aws-sdk-go-v2 v1.41.7/go.mod h1:4LAfZOPHNVNQEckOACQx60Y8pSRjIkNZQz1w92xpMJc= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.9 h1:adBsCIIpLbLmYnkQU+nAChU5yhVTvu5PerROm+/Kq2A= @@ -94,8 +114,18 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.42.0 h1:ks8KBcZPh3PYISr5dAiXCM5/Thcu github.com/aws/aws-sdk-go-v2/service/sts v1.42.0/go.mod h1:pFw33T0WLvXU3rw1WBkpMlkgIn54eCB5FYLhjDc9Foo= github.com/aws/smithy-go v1.25.1 h1:J8ERsGSU7d+aCmdQur5Txg6bVoYelvQJgtZehD12GkI= github.com/aws/smithy-go v1.25.1/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= +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/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= +github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= +github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/buger/goterm v1.0.4 h1:Z9YvGmOih81P0FbVtEYTFF6YsSgxSUKEhf/f9bTMXbY= +github.com/buger/goterm v1.0.4/go.mod h1:HiFWV3xnkolgrBV3mY8m0X0Pumt4zg4QhbdOzQtB8tE= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= +github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= 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= @@ -105,13 +135,59 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk= github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM= +github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8= +github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE= +github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4= +github.com/compose-spec/compose-go/v2 v2.10.2 h1:USa1NUbDcl/cjb8T9iwnuFsnO79H+2ho2L5SjFKz3uI= +github.com/compose-spec/compose-go/v2 v2.10.2/go.mod h1:ZU6zlcweCZKyiB7BVfCizQT9XmkEIMFE+PRZydVcsZg= +github.com/containerd/cgroups/v3 v3.1.3 h1:eUNflyMddm18+yrDmZPn3jI7C5hJ9ahABE5q6dyLYXQ= +github.com/containerd/cgroups/v3 v3.1.3/go.mod h1:PKZ2AcWmSBsY/tJUVhtS/rluX0b1uq1GmPO1ElCmbOw= +github.com/containerd/console v1.0.5 h1:R0ymNeydRqH2DmakFNdmjR2k0t7UPuiOV/N/27/qqsc= +github.com/containerd/console v1.0.5/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= +github.com/containerd/containerd/api v1.10.0 h1:5n0oHYVBwN4VhoX9fFykCV9dF1/BvAXeg2F8W6UYq1o= +github.com/containerd/containerd/api v1.10.0/go.mod h1:NBm1OAk8ZL+LG8R0ceObGxT5hbUYj7CzTmR3xh0DlMM= +github.com/containerd/containerd/v2 v2.2.2 h1:mjVQdtfryzT7lOqs5EYUFZm8ioPVjOpkSoG1GJPxEMY= +github.com/containerd/containerd/v2 v2.2.2/go.mod h1:5Jhevmv6/2J+Iu/A2xXAdUIdI5Ah/hfyO7okJ4AFIdY= +github.com/containerd/continuity v0.4.5 h1:ZRoN1sXq9u7V6QoHMcVWGhOwDFqZ4B9i5H6un1Wh0x4= +github.com/containerd/continuity v0.4.5/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/nydus-snapshotter v0.15.13 h1:z9yCiTPMxVBIZlHxOPinZXhly2MdcIqxk9VXPlHIOJY= +github.com/containerd/nydus-snapshotter v0.15.13/go.mod h1:t95dwCb4I0RE4n1iOk0sJCWosNoACA8daOXmU5A2VHI= +github.com/containerd/platforms v1.0.0-rc.4 h1:M42JrUT4zfZTqtkUwkr0GzmUWbfyO5VO0Q5b3op97T4= +github.com/containerd/platforms v1.0.0-rc.4/go.mod h1:lKlMXyLybmBedS/JJm11uDofzI8L2v0J2ZbYvNsbq1A= +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/stargz-snapshotter v0.18.2 h1:Ev/sxfQUjwzJQ9eqy3XzttcQ3osMIqkQgMYlcET+10M= +github.com/containerd/stargz-snapshotter/estargz v0.18.2 h1:yXkZFYIzz3eoLwlTUZKz2iQ4MrckBxJjkmD16ynUTrw= +github.com/containerd/stargz-snapshotter/estargz v0.18.2/go.mod h1:XyVU5tcJ3PRpkA9XS2T5us6Eg35yM0214Y+wvrZTBrY= +github.com/containerd/ttrpc v1.2.8 h1:xbVu6D4qF2jihdh9rDVOKqUMiFBQk6YctTdo1zk087Y= +github.com/containerd/ttrpc v1.2.8/go.mod h1:wyZW2K79t4Hfcxl+GUvkZqRBzJlqFFvgEeeWXa42tyE= +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-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA= +github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= 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.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= +github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= +github.com/cyberphone/json-canonicalization v0.0.0-20241213102144-19d51d7fe467 h1:uX1JmpONuD549D73r6cgnxyUu18Zb7yHAy5AYU0Pm4Q= +github.com/cyberphone/json-canonicalization v0.0.0-20241213102144-19d51d7fe467/go.mod h1:uzvlm1mxhHkdfqitSA92i7Se+S9ksOn3a3qmv/kyOCw= +github.com/cyphar/filepath-securejoin v0.6.0 h1:BtGB77njd6SVO6VztOHfPxKitJvd/VPT+OFBFMOi1Is= +github.com/cyphar/filepath-securejoin v0.6.0/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc= 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= @@ -120,8 +196,38 @@ github.com/decred/dcrd/crypto/blake256 v1.1.0 h1:zPMNGQCm0g4QTY27fOCorQW7EryeQ/U 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/digitorus/pkcs7 v0.0.0-20230818184609-3a137a874352 h1:ge14PCmCvPjpMQMIAH7uKg0lrtNSOdpYsRXlwk3QbaE= +github.com/digitorus/pkcs7 v0.0.0-20230818184609-3a137a874352/go.mod h1:SKVExuS+vpu2l9IoOc0RwqE7NYnb0JlcFHFnEJkVDzc= +github.com/digitorus/timestamp v0.0.0-20231217203849-220c5c2851b7 h1:lxmTCgmHE1GUYL7P0MlNa00M67axePTq+9nBSGddR8I= +github.com/digitorus/timestamp v0.0.0-20231217203849-220c5c2851b7/go.mod h1:GvWntX9qiTlOud0WkQ6ewFm0LPy5JUR1Xo0Ngbd1w6Y= +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/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= +github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/docker/buildx v0.33.0 h1:xuZeuQe/C/2tvLDgiIA6+Ynq3FFWSfsGNWIHM3q1hD8= +github.com/docker/buildx v0.33.0/go.mod h1:7JVma62htERKE5iy5YD1q64PKiAHUzXuhSBd4oq3I74= +github.com/docker/cli v29.4.0+incompatible h1:+IjXULMetlvWJiuSI0Nbor36lcJ5BTcVpUmB21KBoVM= +github.com/docker/cli v29.4.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/compose/v5 v5.1.2 h1:HxtfGZA0DESw/+hvrNJDjM8VKmCond7OkmgVJCHku48= +github.com/docker/compose/v5 v5.1.2/go.mod h1:JJ2H+lRSugOH50/zxCbkBwN8jU91qDtRCp7gEHWZdgo= +github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= +github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM= +github.com/docker/docker v28.5.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker-credential-helpers v0.9.5 h1:EFNN8DHvaiK8zVqFA2DT6BjXE0GzfLOZ38ggPTKePkY= +github.com/docker/docker-credential-helpers v0.9.5/go.mod h1:v1S+hepowrQXITkEfw6o4+BMbGot02wiKpzWhGUZK6c= +github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94= +github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE= +github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8= +github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= +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.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/ebitengine/purego v0.10.0 h1:QIw4xfpWT6GWTzaW5XEKy3HXoqrJGx1ijYHzTF0/ISU= +github.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203 h1:XBBHcIb256gUJtLmY22n99HaZTz+r2Z51xUPi01m3wg= +github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203/go.mod h1:E1jcSv8FaEny+OP/5k9UxZVw9YFWGj7eI4KR/iOBqCg= 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= @@ -132,6 +238,8 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7 github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fil-forge/automobile v0.0.1 h1:9xB3yc4l5b9EdRJSJcNwudgBFNHoMPEAdcb7GfobLhA= github.com/fil-forge/automobile v0.0.1/go.mod h1:TsO7jlO8ykJZY5tF8j4GsUcu3F02lEzxO7ULoB61hRA= github.com/fil-forge/go-ipni-tools v0.0.0-20260519194815-545b9421aec0 h1:HAfXUPvtPjK9UAzofbDSzW0mSXsbubMtIFCEh0SJ2ow= @@ -142,11 +250,21 @@ github.com/fil-forge/indexing-service v1.13.5-0.20260619142411-efe3f5fab717 h1:W github.com/fil-forge/indexing-service v1.13.5-0.20260619142411-efe3f5fab717/go.mod h1:wFcakLohOqpMRkJzWRdGFGHRFpGB+PpEMCm9wkt2cqU= github.com/fil-forge/libforge v0.0.0-20260701162346-f0706e1641a3 h1:/EDxpbuVeSXH3FLF7MK4G4wrDUFmCO9fLQHL23RZ2uU= github.com/fil-forge/libforge v0.0.0-20260701162346-f0706e1641a3/go.mod h1:0kXihIQ4L2uZ00nR5XrZ/Y8Db7Ht/qQNuiWslwMJ95M= -github.com/fil-forge/ucantone v0.0.0-20260630103048-a8f24fe31eb6 h1:0k57ScNcbxTrveaJhos1veTS7jm4RXt2VBofNK+60ko= -github.com/fil-forge/ucantone v0.0.0-20260630103048-a8f24fe31eb6/go.mod h1:oFY5BfD0bDeodGlbBHh3/nK99MAS93rGXjoQz7s5qgE= +github.com/fil-forge/smelt v0.0.0-20260714201619-f2ecac50394c h1:LqWeuCe44vpkqz/F5kqMjK380FOzvLvYrFlR/I9GVUo= +github.com/fil-forge/smelt v0.0.0-20260714201619-f2ecac50394c/go.mod h1:W6aiYnuM+n0Q7G6fH3Ok4EE2iMfUSpA6f8r3i/+moBs= +github.com/fil-forge/ucantone v0.0.0-20260706102443-79141c5cc52e h1:di/SseJVEO6bznSH/UEnAE2nSwwp/gqgnPLgE9/x2Zg= +github.com/fil-forge/ucantone v0.0.0-20260706102443-79141c5cc52e/go.mod h1:oFY5BfD0bDeodGlbBHh3/nK99MAS93rGXjoQz7s5qgE= +github.com/filecoin-project/go-data-segment v0.0.1 h1:1wmDxOG4ubWQm3ZC1XI5nCon5qgSq7Ra3Rb6Dbu10Gs= +github.com/filecoin-project/go-data-segment v0.0.1/go.mod h1:H0/NKbsRxmRFBcLibmABv+yFNHdmtl5AyplYLnb0Zv4= +github.com/filecoin-project/go-fil-commcid v0.3.1 h1:4EfxpHSlvtkOqa9weG2Yt5kxFmPib2xU7Uc9Lbqk7fs= +github.com/filecoin-project/go-fil-commcid v0.3.1/go.mod h1:z7Ssf8d7kspF9QRAVHDbZ+43JK4mkhbGH5lyph1TnKY= +github.com/fsnotify/fsevents v0.2.0 h1:BRlvlqjvNTfogHfeBOFvSC9N0Ddy+wzQCQukyoD7o/c= +github.com/fsnotify/fsevents v0.2.0/go.mod h1:B3eEk39i4hz8y1zaWS/wPrAP4O6wkIl7HQwKBr1qH/w= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/fvbommel/sortorder v1.1.0 h1:fUmoe+HLsBTctBDoaBwpQo5N+nrCp8g/BjKb/6ZQmYw= +github.com/fvbommel/sortorder v1.1.0/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0= github.com/fxamacker/cbor/v2 v2.9.2 h1:X4Ksno9+x3cz0TZv69ec1hxP/+tymuR8PXQJyDwfh78= github.com/fxamacker/cbor/v2 v2.9.2/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -164,17 +282,71 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/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-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +github.com/go-openapi/analysis v0.24.3 h1:a1hrvMr8X0Xt69KP5uVTu5jH62DscmDifrLzNglAayk= +github.com/go-openapi/analysis v0.24.3/go.mod h1:Nc+dWJ/FxZbhSow5Yh3ozg5CLJioB+XXT6MdLvJUsUw= +github.com/go-openapi/errors v0.22.7 h1:JLFBGC0Apwdzw3484MmBqspjPbwa2SHvpDm0u5aGhUA= +github.com/go-openapi/errors v0.22.7/go.mod h1://QW6SD9OsWtH6gHllUCddOXDL0tk0ZGNYHwsw4sW3w= +github.com/go-openapi/jsonpointer v0.22.5 h1:8on/0Yp4uTb9f4XvTrM2+1CPrV05QPZXu+rvu2o9jcA= +github.com/go-openapi/jsonpointer v0.22.5/go.mod h1:gyUR3sCvGSWchA2sUBJGluYMbe1zazrYWIkWPjjMUY0= +github.com/go-openapi/jsonreference v0.21.5 h1:6uCGVXU/aNF13AQNggxfysJ+5ZcU4nEAe+pJyVWRdiE= +github.com/go-openapi/jsonreference v0.21.5/go.mod h1:u25Bw85sX4E2jzFodh1FOKMTZLcfifd1Q+iKKOUxExw= +github.com/go-openapi/loads v0.23.3 h1:g5Xap1JfwKkUnZdn+S0L3SzBDpcTIYzZ5Qaag0YDkKQ= +github.com/go-openapi/loads v0.23.3/go.mod h1:NOH07zLajXo8y55hom0omlHWDVVvCwBM/S+csCK8LqA= +github.com/go-openapi/runtime v0.29.2 h1:UmwSGWNmWQqKm1c2MGgXVpC2FTGwPDQeUsBMufc5Yj0= +github.com/go-openapi/runtime v0.29.2/go.mod h1:biq5kJXRJKBJxTDJXAa00DOTa/anflQPhT0/wmjuy+0= +github.com/go-openapi/spec v0.22.4 h1:4pxGjipMKu0FzFiu/DPwN3CTBRlVM2yLf/YTWorYfDQ= +github.com/go-openapi/spec v0.22.4/go.mod h1:WQ6Ai0VPWMZgMT4XySjlRIE6GP1bGQOtEThn3gcWLtQ= +github.com/go-openapi/strfmt v0.26.1 h1:7zGCHji7zSYDC2tCXIusoxYQz/48jAf2q+sF6wXTG+c= +github.com/go-openapi/strfmt v0.26.1/go.mod h1:Zslk5VZPOISLwmWTMBIS7oiVFem1o1EI6zULY8Uer7Y= +github.com/go-openapi/swag v0.25.4 h1:OyUPUFYDPDBMkqyxOTkqDYFnrhuhi9NR6QVUvIochMU= +github.com/go-openapi/swag v0.25.4/go.mod h1:zNfJ9WZABGHCFg2RnY0S4IOkAcVTzJ6z2Bi+Q4i6qFQ= +github.com/go-openapi/swag/cmdutils v0.25.4 h1:8rYhB5n6WawR192/BfUu2iVlxqVR9aRgGJP6WaBoW+4= +github.com/go-openapi/swag/cmdutils v0.25.4/go.mod h1:pdae/AFo6WxLl5L0rq87eRzVPm/XRHM3MoYgRMvG4A0= +github.com/go-openapi/swag/conv v0.25.5 h1:wAXBYEXJjoKwE5+vc9YHhpQOFj2JYBMF2DUi+tGu97g= +github.com/go-openapi/swag/conv v0.25.5/go.mod h1:CuJ1eWvh1c4ORKx7unQnFGyvBbNlRKbnRyAvDvzWA4k= +github.com/go-openapi/swag/fileutils v0.25.5 h1:B6JTdOcs2c0dBIs9HnkyTW+5gC+8NIhVBUwERkFhMWk= +github.com/go-openapi/swag/fileutils v0.25.5/go.mod h1:V3cT9UdMQIaH4WiTrUc9EPtVA4txS0TOmRURmhGF4kc= +github.com/go-openapi/swag/jsonname v0.25.5 h1:8p150i44rv/Drip4vWI3kGi9+4W9TdI3US3uUYSFhSo= +github.com/go-openapi/swag/jsonname v0.25.5/go.mod h1:jNqqikyiAK56uS7n8sLkdaNY/uq6+D2m2LANat09pKU= +github.com/go-openapi/swag/jsonutils v0.25.5 h1:XUZF8awQr75MXeC+/iaw5usY/iM7nXPDwdG3Jbl9vYo= +github.com/go-openapi/swag/jsonutils v0.25.5/go.mod h1:48FXUaz8YsDAA9s5AnaUvAmry1UcLcNVWUjY42XkrN4= +github.com/go-openapi/swag/loading v0.25.5 h1:odQ/umlIZ1ZVRteI6ckSrvP6e2w9UTF5qgNdemJHjuU= +github.com/go-openapi/swag/loading v0.25.5/go.mod h1:I8A8RaaQ4DApxhPSWLNYWh9NvmX2YKMoB9nwvv6oW6g= +github.com/go-openapi/swag/mangling v0.25.5 h1:hyrnvbQRS7vKePQPHHDso+k6CGn5ZBs5232UqWZmJZw= +github.com/go-openapi/swag/mangling v0.25.5/go.mod h1:6hadXM/o312N/h98RwByLg088U61TPGiltQn71Iw0NY= +github.com/go-openapi/swag/netutils v0.25.4 h1:Gqe6K71bGRb3ZQLusdI8p/y1KLgV4M/k+/HzVSqT8H0= +github.com/go-openapi/swag/netutils v0.25.4/go.mod h1:m2W8dtdaoX7oj9rEttLyTeEFFEBvnAx9qHd5nJEBzYg= +github.com/go-openapi/swag/stringutils v0.25.5 h1:NVkoDOA8YBgtAR/zvCx5rhJKtZF3IzXcDdwOsYzrB6M= +github.com/go-openapi/swag/stringutils v0.25.5/go.mod h1:PKK8EZdu4QJq8iezt17HM8RXnLAzY7gW0O1KKarrZII= +github.com/go-openapi/swag/typeutils v0.25.5 h1:EFJ+PCga2HfHGdo8s8VJXEVbeXRCYwzzr9u4rJk7L7E= +github.com/go-openapi/swag/typeutils v0.25.5/go.mod h1:itmFmScAYE1bSD8C4rS0W+0InZUBrB2xSPbWt6DLGuc= +github.com/go-openapi/swag/yamlutils v0.25.5 h1:kASCIS+oIeoc55j28T4o8KwlV2S4ZLPT6G0iq2SSbVQ= +github.com/go-openapi/swag/yamlutils v0.25.5/go.mod h1:Gek1/SjjfbYvM+Iq4QGwa/2lEXde9n2j4a3wI3pNuOQ= +github.com/go-openapi/validate v0.25.2 h1:12NsfLAwGegqbGWr2CnvT65X/Q2USJipmJ9b7xDJZz0= +github.com/go-openapi/validate v0.25.2/go.mod h1:Pgl1LpPPGFnZ+ys4/hTlDiRYQdI1ocKypgE+8Q8BLfY= +github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro= +github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= 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.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofiber/fiber/v2 v2.52.12 h1:0LdToKclcPOj8PktUdIKo9BUohjjwfnQl42Dhw8/WUw= github.com/gofiber/fiber/v2 v2.52.12/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw= +github.com/gofrs/flock v0.13.0 h1:95JolYOvGMqeH31+FC7D2+uULf6mG61mEZ/A8dRYMzw= +github.com/gofrs/flock v0.13.0/go.mod h1:jxeyy9R1auM5S6JYDBhDt+E2TCo7DkratH4Pgi8P+Z0= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= +github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= 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/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= @@ -201,8 +373,12 @@ github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.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/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/certificate-transparency-go v1.3.2 h1:9ahSNZF2o7SYMaKaXhAumVEzXB2QaayzII9C8rv7v+A= +github.com/google/certificate-transparency-go v1.3.2/go.mod h1:H5FpMUaGa5Ab2+KCYsxg6sELw3Flkl7pGZzWdBoYLXs= 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= @@ -215,8 +391,11 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 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-containerregistry v0.20.7 h1:24VGNpS0IwrOZ2ms2P1QE3Xa5X9p4phx0aUgzYzHW6I= +github.com/google/go-containerregistry v0.20.7/go.mod h1:Lx5LCZQjLH1QBaMPeGwsME9biPeo1lPx6lbGj/UmzgM= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -233,6 +412,8 @@ github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= 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= @@ -241,10 +422,16 @@ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5m github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g= github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k= +github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= +github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= @@ -253,6 +440,8 @@ github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVH github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVUrx/c8Unxc48= github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= @@ -266,6 +455,8 @@ github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.9.0 h1:CeOIz6k+LoN3qX9Z0tyQrPtiB1DFYRPfCIBtaXPSCnA= +github.com/hashicorp/go-version v1.9.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= @@ -279,8 +470,15 @@ github.com/hashicorp/vault-client-go v0.4.3 h1:zG7STGVgn/VK6rnZc0k8PGbfv2x/sJExR github.com/hashicorp/vault-client-go v0.4.3/go.mod h1:4tDw7Uhq5XOxS1fO+oMtotHL7j4sB9cp0T7U6m4FzDY= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= +github.com/in-toto/attestation v1.1.2 h1:MBFn6lsMq6dptQZJBhalXTcWMb/aJy3V+GX3VYj/V1E= +github.com/in-toto/attestation v1.1.2/go.mod h1:gYFddHMZj3DiQ0b62ltNi1Vj5rC879bTmBbrv9CRHpM= +github.com/in-toto/in-toto-golang v0.10.0 h1:+s2eZQSK3WmWfYV85qXVSBfqgawi/5L02MaqA4o/tpM= +github.com/in-toto/in-toto-golang v0.10.0/go.mod h1:wjT4RiyFlLWCmLUJjwB8oZcjaq7HA390aMJcD3xXgmg= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/inhies/go-bytesize v0.0.0-20220417184213-4913239db9cf h1:FtEj8sfIcaaBfAKrE1Cwb61YDtYq9JxChK1c7AKce7s= +github.com/inhies/go-bytesize v0.0.0-20220417184213-4913239db9cf/go.mod h1:yrqSXGoD/4EKfF26AOGzscPOgTTJcyAwM2rpixWT+t4= github.com/ipfs/boxo v0.37.0 h1:2E3mZvydMI2t5IkAgtkmZ3sGsld0oS7o3I+xyzDk6uI= github.com/ipfs/boxo v0.37.0/go.mod h1:8yyiRn54F2CsW13n0zwXEPrVsZix/gFj9SYIRYMZ6KE= github.com/ipfs/go-block-format v0.2.3 h1:mpCuDaNXJ4wrBJLrtEaGFGXkferrw5eqVvzaHhtFKQk= @@ -321,6 +519,8 @@ github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh6 github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs= github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY= github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= +github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I= +github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= @@ -340,10 +540,14 @@ github.com/kr/pty v1.1.1/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/lib/pq v0.0.0-20150723085316-0dad96c0b94f h1:I1iYfgQavGa2tgdgKn+2Qg1yQhHEETvh/mNSxG3x5c0= +github.com/lib/pq v0.0.0-20150723085316-0dad96c0b94f/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 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-libp2p v0.48.0 h1:h2BrLAgrj7X8bEN05K7qmrjpNHYA+6tnsGRdprjTnvo= github.com/libp2p/go-libp2p v0.48.0/go.mod h1:Q1fBZNdmC2Hf82husCTfkKJVfHm2we5zk+NWmOGEmWk= +github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae h1:dIZY4ULFcto4tAFlj1FYZl8ztUZ13bdq+PLY+NOfbyI= +github.com/lufia/plan9stats v0.0.0-20240513124658-fba389f38bae/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= @@ -357,6 +561,8 @@ github.com/mattn/go-isatty v0.0.21 h1:xYae+lCNBP7QuW4PUnNG61ffM4hVIfm+zUzDuSzYLG github.com/mattn/go-isatty v0.0.21/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4= github.com/mattn/go-runewidth v0.0.23 h1:7ykA0T0jkPpzSvMS5i9uoNn2Xy3R383f9HDx3RybWcw= github.com/mattn/go-runewidth v0.0.23/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= +github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk= +github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/mfridman/interpolate v0.0.2 h1:pnuTK7MQIxxFz1Gr+rjSIx9u7qVjf5VOoM/u6BbAxPY= github.com/mfridman/interpolate v0.0.2/go.mod h1:p+7uk6oE07mpE/Ik1b8EckO0O4ZXiGAfshKBWLUM9Xg= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= @@ -368,14 +574,52 @@ github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4= +github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/moby/buildkit v0.29.0 h1:wxLEFbCOJntEDjSNNN2YWd8zxltZxT5muDQ0LzpbtpU= +github.com/moby/buildkit v0.29.0/go.mod h1:Dmv2FeDe34t75QuzeU87rBoZpAAkcpT5zeu4hXzmASc= +github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= +github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= +github.com/moby/go-archive v0.2.0 h1:zg5QDUM2mi0JIM9fdQZWC7U8+2ZfixfTYoHL7rWUcP8= +github.com/moby/go-archive v0.2.0/go.mod h1:mNeivT14o8xU+5q1YnNrkQVpK+dnNe/K6fHqnTg4qPU= +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/moby/api v1.54.1 h1:TqVzuJkOLsgLDDwNLmYqACUuTehOHRGKiPhvH8V3Nn4= +github.com/moby/moby/api v1.54.1/go.mod h1:+RQ6wluLwtYaTd1WnPLykIDPekkuyD/ROWQClE83pzs= +github.com/moby/moby/client v0.4.0 h1:S+2XegzHQrrvTCvF6s5HFzcrywWQmuVnhOXe2kiWjIw= +github.com/moby/moby/client v0.4.0/go.mod h1:QWPbvWchQbxBNdaLSpoKpCdf5E+WxFAgNHogCWDoa7g= +github.com/moby/patternmatcher v0.6.1 h1:qlhtafmr6kgMIJjKJMDmMWq7WLkKIo23hsrpR3x084U= +github.com/moby/patternmatcher v0.6.1/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= +github.com/moby/policy-helpers v0.0.0-20260324161837-b7c0b994300b h1:lvBBM2ACrsG5/O1G1caEwlh0XeqA89IQK3xq0Sh/5NI= +github.com/moby/policy-helpers v0.0.0-20260324161837-b7c0b994300b/go.mod h1:Cbc1brDwYl1K294MmZB+6WhQR9Tr24hfhgSGND4UlL0= +github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw= +github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs= +github.com/moby/sys/capability v0.4.0 h1:4D4mI6KlNtWMCM1Z/K0i7RV1FkX+DBDHKVJpCndZoHk= +github.com/moby/sys/capability v0.4.0/go.mod h1:4g9IK291rVkms3LKCDOoYlnV8xKwoDTpIrNEE35Wq0I= +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/symlink v0.3.0 h1:GZX89mEZ9u53f97npBy4Rc3vJKj7JBDj/PN2I22GrNU= +github.com/moby/sys/symlink v0.3.0/go.mod h1:3eNdhduHmYPcgsJtZXW1W4XUJdZGBIkttZ8xKqPUJq0= +github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs= +github.com/moby/sys/user v0.4.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/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= +github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/morikuni/aec v1.1.0 h1:vBBl0pUnvi/Je71dsRrhMBtreIqNMYErSAbEeb8jrXQ= +github.com/morikuni/aec v1.1.0/go.mod h1:xDRgiq/iw5l+zkao76YTKzKttOp2cwPEne25HDkJnBw= github.com/mr-tron/base58 v1.3.0 h1:K6Y13R2h+dku0wOqKtecgRnBUBPrZzLZy5aIj8lCcJI= github.com/mr-tron/base58 v1.3.0/go.mod h1:2BuubE67DCSWwVfx37JWNG8emOC0sHEU4/HpcYgCLX8= github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE= @@ -392,6 +636,8 @@ github.com/multiformats/go-multihash v0.2.3 h1:7Lyc8XfX/IY2jWb/gI7JP+o7JEq9hOa7B github.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM= github.com/multiformats/go-varint v0.1.0 h1:i2wqFp4sdl3IcIxfAonHQV9qU5OsZ4Ts9IOoETFs5dI= github.com/multiformats/go-varint v0.1.0/go.mod h1:5KVAVXegtfmNQQm/lCY+ATvDzvJJhSkUlGQV9wgObdI= +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/nats-io/nats.go v1.51.0 h1:ByW84XTz6W03GSSsygsZcA+xgKK8vPGaa/FCAAEHnAI= github.com/nats-io/nats.go v1.51.0/go.mod h1:26HypzazeOkyO3/mqd1zZd53STJN0EjCYF9Uy2ZOBno= github.com/nats-io/nkeys v0.4.15 h1:JACV5jRVO9V856KOapQ7x+EY8Jo3qw1vJt/9Jpwzkk4= @@ -402,25 +648,51 @@ github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOF github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= github.com/neelance/sourcemap v0.0.0-20200213170602-2833bce08e4c/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= +github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= github.com/oklog/ulid/v2 v2.1.1 h1:suPZ4ARWLOJLegGFiZZ1dFAkqzhMjL3J1TzI+5wHz8s= github.com/oklog/ulid/v2 v2.1.1/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ= +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.3.0 h1:YZupQUdctfhpZy3TM39nN9Ika5CBWT5diQ8ibYCRkxg= +github.com/opencontainers/runtime-spec v1.3.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/selinux v1.13.1 h1:A8nNeceYngH9Ow++M+VVEwJVpdFmrlxsN22F+ISDCJE= +github.com/opencontainers/selinux v1.13.1/go.mod h1:S10WXZ/osk2kWOYKy1x2f/eXF5ZHJoUs8UU/2caNRbg= +github.com/package-url/packageurl-go v0.1.1 h1:KTRE0bK3sKbFKAk3yy63DpeskU7Cvs/x/Da5l+RtzyU= +github.com/package-url/packageurl-go v0.1.1/go.mod h1:uQd4a7Rh3ZsVg5j0lNyAfyxIeGde9yrlhjF78GzeW0c= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.9.3 h1:zeC5b1GviRUyKYd6OJPvBU/mcVDVoL1OhT17FCt5dSQ= github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +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/pierrec/lz4/v4 v4.1.26 h1:GrpZw1gZttORinvzBdXPUXATeqlJjqUG/D87TKMnhjY= github.com/pierrec/lz4/v4 v4.1.26/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4= 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/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= 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.1-0.20231129105047-37766d95467a h1:cgqrm0F3zwf9IPzca7xN4w+Zy6MC9ZkPvAC8QEWa/iQ= github.com/polydawn/refmt v0.89.1-0.20231129105047-37766d95467a/go.mod h1:ocZfO/tLSHqfScRDNTJbAJR1by4D1lewauX9OwTaPuY= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/pressly/goose/v3 v3.27.0 h1:/D30gVTuQhu0WsNZYbJi4DMOsx1lNq+6SkLe+Wp59BM= github.com/pressly/goose/v3 v3.27.0/go.mod h1:3ZBeCXqzkgIRvrEMDkYh1guvtoJTU5oMMuDdkutoM78= +github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= +github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.67.4 h1:yR3NqWO1/UyO1w2PhUvXlGQs/PtFmoveVO0KZ4+Lvsc= +github.com/prometheus/common v0.67.4/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= +github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws= +github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= github.com/rabbitmq/amqp091-go v1.10.0 h1:STpn5XsHlHGcecLmMFCtg7mqq0RnD+zFr4uzukfVhBw= github.com/rabbitmq/amqp091-go v1.10.0/go.mod h1:Hy4jKW5kQART1u+JkDTF9YYOQUHXqMuhrgxOEeS7G4o= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= @@ -430,20 +702,45 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= 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/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 h1:PKK9DyHxif4LZo+uQSgXNqs0jj5+xZwwfKHgph2lxBw= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.1/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/secure-systems-lab/go-securesystemslib v0.10.0 h1:l+H5ErcW0PAehBNrBxoGv1jjNpGYdZ9RcheFkB2WI14= +github.com/secure-systems-lab/go-securesystemslib v0.10.0/go.mod h1:MRKONWmRoFzPNQ9USRF9i1mc7MvAVvF1LlW8X5VWDvk= github.com/segmentio/kafka-go v0.4.50 h1:mcyC3tT5WeyWzrFbd6O374t+hmcu1NKt2Pu1L3QaXmc= github.com/segmentio/kafka-go v0.4.50/go.mod h1:Y1gn60kzLEEaW28YshXyk2+VCUKbJ3Qr6DrnT3i4+9E= github.com/sethvargo/go-retry v0.3.0 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah2SE= github.com/sethvargo/go-retry v0.3.0/go.mod h1:mNX17F0C/HguQMyMyJxcnU471gOZGxCLyYaFyAZraas= +github.com/shibumi/go-pathspec v1.3.0 h1:QUyMZhFo0Md5B8zV8x2tesohbb5kfbpTi9rBnKh5dkI= +github.com/shibumi/go-pathspec v1.3.0/go.mod h1:Xutfslp817l2I1cZvgcfeMQJG5QnU2lh5tVaaMCl3jE= +github.com/shirou/gopsutil/v4 v4.26.3 h1:2ESdQt90yU3oXF/CdOlRCJxrP+Am1aBYubTMTfxJ1qc= +github.com/shirou/gopsutil/v4 v4.26.3/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ= github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw= +github.com/sigstore/protobuf-specs v0.5.0 h1:F8YTI65xOHw70NrvPwJ5PhAzsvTnuJMGLkA4FIkofAY= +github.com/sigstore/protobuf-specs v0.5.0/go.mod h1:+gXR+38nIa2oEupqDdzg4qSBT0Os+sP7oYv6alWewWc= +github.com/sigstore/rekor v1.5.0 h1:rL7SghHd5HLCtsCrxw0yQg+NczGvM75EjSPPWuGjaiQ= +github.com/sigstore/rekor v1.5.0/go.mod h1:D7JoVCUkxwQOpPDNYeu+CE8zeBC18Y5uDo6tF8s2rcQ= +github.com/sigstore/rekor-tiles/v2 v2.0.1 h1:1Wfz15oSRNGF5Dzb0lWn5W8+lfO50ork4PGIfEKjZeo= +github.com/sigstore/rekor-tiles/v2 v2.0.1/go.mod h1:Pjsbhzj5hc3MKY8FfVTYHBUHQEnP0ozC4huatu4x7OU= +github.com/sigstore/sigstore v1.10.4 h1:ytOmxMgLdcUed3w1SbbZOgcxqwMG61lh1TmZLN+WeZE= +github.com/sigstore/sigstore v1.10.4/go.mod h1:tDiyrdOref3q6qJxm2G+JHghqfmvifB7hw+EReAfnbI= +github.com/sigstore/sigstore-go v1.1.4 h1:wTTsgCHOfqiEzVyBYA6mDczGtBkN7cM8mPpjJj5QvMg= +github.com/sigstore/sigstore-go v1.1.4/go.mod h1:2U/mQOT9cjjxrtIUeKDVhL+sHBKsnWddn8URlswdBsg= +github.com/sigstore/timestamp-authority/v2 v2.0.3 h1:sRyYNtdED/ttLCMdaYnwpf0zre1A9chvjTnCmWWxN8Y= +github.com/sigstore/timestamp-authority/v2 v2.0.3/go.mod h1:mDaHxkt3HmZYoIlwYj4QWo0RUr7VjYU52aVO5f5Qb3I= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= +github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= +github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA= +github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog= github.com/smarty/assertions v1.15.0 h1:cR//PqUBUiQRakZWqBiFFQ9wb8emQGDb0HeGdqGByCY= github.com/smarty/assertions v1.15.0/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= @@ -454,16 +751,21 @@ github.com/smira/go-statsd v1.3.4 h1:kBYWcLSGT+qC6JVbvfz48kX7mQys32fjDOPrfmsSx2c github.com/smira/go-statsd v1.3.4/go.mod h1:RjdsESPgDODtg1VpVVf9MJrEW2Hw0wtRNbmB1CAhu6A= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spdx/tools-golang v0.5.7 h1:+sWcKGnhwp3vLdMqPcLdA6QK679vd86cK9hQWH3AwCg= +github.com/spdx/tools-golang v0.5.7/go.mod h1:jg7w0LOpoNAw6OxKEzCoqPC2GCTj45LyTlVmXubDsYw= github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v1.2.1 h1:+KmjbUw1hriSNMF55oPrkZcb27aECyrj8V2ytv7kWDw= github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= +github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= +github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= 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 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.8.1 h1:Kq1fyeebqsBfbjZj4EL7gj2IO0mMaiyjYUWcUsl2O44= github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -484,11 +786,40 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/testcontainers/testcontainers-go v0.42.0 h1:He3IhTzTZOygSXLJPMX7n44XtK+qhjat1nI9cneBbUY= +github.com/testcontainers/testcontainers-go v0.42.0/go.mod h1:vZjdY1YmUA1qEForxOIOazfsrdyORJAbhi0bp8plN30= +github.com/testcontainers/testcontainers-go/modules/compose v0.42.0 h1:+t1ZN31TD36cwxmeLqGwe7wIdvblBm0Z+vlj4SX8Mv0= +github.com/testcontainers/testcontainers-go/modules/compose v0.42.0/go.mod h1:CfMpouDHqNTCHC8CijEURU2ZotTV3QhH6pXd48s6ofk= +github.com/theupdateframework/go-tuf v0.7.0 h1:CqbQFrWo1ae3/I0UCblSbczevCCbS31Qvs5LdxRWqRI= +github.com/theupdateframework/go-tuf/v2 v2.4.1 h1:K6ewW064rKZCPkRo1W/CTbTtm/+IB4+coG1iNURAGCw= +github.com/theupdateframework/go-tuf/v2 v2.4.1/go.mod h1:Nex2enPVYDFCklrnbTzl3OVwD7fgIAj0J5++z/rvCj8= +github.com/tilt-dev/fsnotify v1.4.8-0.20220602155310-fff9c274a375 h1:QB54BJwA6x8QU9nHY3xJSZR2kX9bgpZekRKGkLTmEXA= +github.com/tilt-dev/fsnotify v1.4.8-0.20220602155310-fff9c274a375/go.mod h1:xRroudyp5iVtxKqZCrA6n2TLFRBf8bmnjr1UD4x+z7g= +github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA= +github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI= +github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw= +github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ= +github.com/tonistiigi/dchapes-mode v0.0.0-20250318174251-73d941a28323 h1:r0p7fK56l8WPequOaR3i9LBqfPtEdXIQbUTzT55iqT4= +github.com/tonistiigi/dchapes-mode v0.0.0-20250318174251-73d941a28323/go.mod h1:3Iuxbr0P7D3zUzBMAZB+ois3h/et0shEz0qApgHYGpY= +github.com/tonistiigi/fsutil v0.0.0-20251211185533-a2aa163d723f h1:Z4NEQ86qFl1mHuCu9gwcE+EYCwDKfXAYXZbdIXyxmEA= +github.com/tonistiigi/fsutil v0.0.0-20251211185533-a2aa163d723f/go.mod h1:BKdcez7BiVtBvIcef90ZPc6ebqIWr4JWD7+EvLm6J98= +github.com/tonistiigi/go-csvvalue v0.0.0-20240814133006-030d3b2625d0 h1:2f304B10LaZdB8kkVEaoXvAMVan2tl9AiK4G0odjQtE= +github.com/tonistiigi/go-csvvalue v0.0.0-20240814133006-030d3b2625d0/go.mod h1:278M4p8WsNh3n4a1eqiFcV2FGk7wE5fwUpUom9mK9lE= +github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea h1:SXhTLE6pb6eld/v/cCndK0AMpt1wiVFb/YYmqB3/QG0= +github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea/go.mod h1:WPnis/6cRcDZSUvVmezrxJPkiO87ThFYsoUiMwWNDJk= +github.com/tonistiigi/vt100 v0.0.0-20240514184818-90bafcd6abab h1:H6aJ0yKQ0gF49Qb2z5hI1UHxSQt4JMyxebFR15KnApw= +github.com/tonistiigi/vt100 v0.0.0-20240514184818-90bafcd6abab/go.mod h1:ulncasL3N9uLrVann0m+CDlJKWsIAP34MPcOJF6VRvc= +github.com/transparency-dev/formats v0.0.0-20251017110053-404c0d5b696c h1:5a2XDQ2LiAUV+/RjckMyq9sXudfrPSuCY4FuPC1NyAw= +github.com/transparency-dev/formats v0.0.0-20251017110053-404c0d5b696c/go.mod h1:g85IafeFJZLxlzZCDRu4JLpfS7HKzR+Hw9qRh3bVzDI= +github.com/transparency-dev/merkle v0.0.2 h1:Q9nBoQcZcgPamMkGn7ghV8XiTZ/kRxn1yCG81+twTK4= +github.com/transparency-dev/merkle v0.0.2/go.mod h1:pqSy+OXefQ1EDUVmAJ8MUhHB9TXGuzVAT58PqBoHz1A= github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasthttp v1.70.0 h1:LAhMGcWk13QZWm85+eg8ZBNbrq5mnkWFGbHMUJHIdXA= github.com/valyala/fasthttp v1.70.0/go.mod h1:oDZEHHkJ/Buyklg6uURmYs19442zFSnCIfX3j1FY3pE= +github.com/vbatts/tar-split v0.12.2 h1:w/Y6tjxpeiFMR47yzZPlPj/FcPLpXbTUi/9H7d3CPa4= +github.com/vbatts/tar-split v0.12.2/go.mod h1:eF6B6i6ftWQcDqEn3/iGFRFRo8cBIMSJVOpnNdfTMFA= github.com/versity/versitygw v1.4.1 h1:eNh58r3bmcwET0QWdkNmZgoo/QOO3GysT0YGtpnCyYc= github.com/versity/versitygw v1.4.1/go.mod h1:8xbEr+kFj/wT/Tf7AOP/biagykBN3iSB5zMhd35Hi0g= github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0 h1:GDDkbFiaK8jsSDJfjId/PEGEShv6ugrt4kYsC5UIDaQ= @@ -503,6 +834,8 @@ github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY= github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8= github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= +github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc= +github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -511,6 +844,8 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de 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= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b h1:CzigHMRySiX3drau9C6Q5CAbNIApmLdat5jPMqChvDA= gitlab.com/yawning/secp256k1-voi v0.0.0-20230925100816-f2616030848b/go.mod h1:/y/V339mxv2sZmYYR64O07VuCpdNZqCTwO8ZcouTMI8= gitlab.com/yawning/tuplehash v0.0.0-20230713102510-df83abbf9a02 h1:qwDnMxjkyLmAFgcfgTnfJrmYKWhHnci3GjDqcZp1M3Q= @@ -525,14 +860,38 @@ go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +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.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 h1:YH4g8lQroajqUwWbq/tr2QX1JFmEXaDLgG+ew9bLMWo= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0/go.mod h1:fvPi2qXDqFs8M4B4fmJhE92TyQs9Ydjlg3RvfUp+NbQ= +go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.63.0 h1:2pn7OzMewmYRiNtv1doZnLo3gONcnMHlFnmOR8Vgt+8= +go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.63.0/go.mod h1:rjbQTDEPQymPE0YnRQp9/NuPwwtL0sesz/fnqRW/v84= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 h1:OyrsyzuttWTSur2qN/Lm0m2a8yqyIjUVBZcxFPuXq2o= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0/go.mod h1:C2NGBr+kAB4bk3xtMXfZ94gqFDtg/GkI7e9zqGh5Beg= go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0 h1:MdKucPl/HbzckWWEisiNqMPhRrAOQX8r4jTuGr636gk= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0/go.mod h1:RolT8tWtfHcjajEH5wFIZ4Dgh5jpPdFXYV9pTAk/qjc= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.43.0 h1:w1K+pCJoPpQifuVpsKamUdn9U0zM3xUziVOqsGksUrY= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.43.0/go.mod h1:HBy4BjzgVE8139ieRI75oXm3EcDN+6GhD88JT1Kjvxg= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 h1:88Y4s2C8oTui1LGM6bTWkw0ICGcOLCAI5l6zsD1j20k= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0/go.mod h1:Vl1/iaggsuRlrHf/hfPJPvVag77kKyvrLeD10kpMl+A= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0 h1:zWWrB1U6nqhS/k6zYB74CjRpuiitRtLLi68VcgmOEto= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0/go.mod h1:2qXPNBX1OVRC0IwOnfo1ljoid+RD0QK3443EaqVlsOU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 h1:3iZJKlCZufyRzPzlQhUIWVmfltrXuGyfjREgGP3UUjc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0/go.mod h1:/G+nUPfhq2e+qiXMGxMwumDrP5jtzU+mWN7/sjT2rak= go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM= go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY= +go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg= +go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg= +go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw= +go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A= go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= +go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g= +go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/dig v1.19.0 h1:BACLhebsYdpQ7IROQ1AGPjrXcP5dF80U3gKoFzbaq/4= go.uber.org/dig v1.19.0/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE= @@ -540,14 +899,20 @@ go.uber.org/fx v1.24.0 h1:wE8mruvpg2kiiL1Vqd0CC+tr0/24XIB10Iwp2lLWzkg= go.uber.org/fx v1.24.0/go.mod h1:AmDeGyS+ZARGKM4tlH4FY2Jr63VjbEDJHtqXTGP5hbo= 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.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= +go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= 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/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= go.uber.org/zap v1.28.0 h1:IZzaP1Fv73/T/pBMLk4VutPl36uNC+OSUh3JLG3FIjo= go.uber.org/zap v1.28.0/go.mod h1:rDLpOi171uODNm/mxFcuYWxDsqWSAVkFdX4XojSKg/Q= +go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= +go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +go.yaml.in/yaml/v4 v4.0.0-rc.4 h1:UP4+v6fFrBIb1l934bDl//mmnoIZEDK0idg1+AIvX5U= +go.yaml.in/yaml/v4 v4.0.0-rc.4/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/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-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -599,6 +964,8 @@ 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.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI= +golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY= 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-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -678,6 +1045,7 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -700,6 +1068,7 @@ golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7w 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-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -708,12 +1077,16 @@ golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210331175145-43e1dd70ce54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/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-20210616094352-59db8d763f22/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-20220412211240-33da011f77ad/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-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/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.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= @@ -722,6 +1095,8 @@ golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9sn 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.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY= +golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -801,6 +1176,8 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY= golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= +gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= +gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= @@ -871,6 +1248,10 @@ google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto/googleapis/api v0.0.0-20260523011958-0a33c5d7ca68 h1:WVVw1Nl19li0fMX++FJ3ye1z9+S1N35QODDy5qpnaXw= +google.golang.org/genproto/googleapis/api v0.0.0-20260523011958-0a33c5d7ca68/go.mod h1:1dCETSCY2YKZNXQE3h4fun3TYwF5p8jejRKZgfWAgAY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260523011958-0a33c5d7ca68 h1:PvEgGJf9C/1u5CHkInMg7UFYYUoiaQmW2LbtH0pjB78= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260523011958-0a33c5d7ca68/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -891,6 +1272,8 @@ google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAG google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.81.1 h1:VnnIIZ88UzOOKLukQi+ImGz8O1Wdp8nAGGnvOfEIWQQ= +google.golang.org/grpc v1.81.1/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I= 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= @@ -910,8 +1293,9 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 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/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +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.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -921,6 +1305,8 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C 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= +gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= +gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= 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-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -938,8 +1324,12 @@ modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= modernc.org/sqlite v1.46.1 h1:eFJ2ShBLIEnUWlLy12raN0Z1plqmFX9Qe3rjQTKt6sU= modernc.org/sqlite v1.46.1/go.mod h1:CzbrU2lSB1DKUusvwGz7rqEKIq+NUd8GWuBBZDs9/nA= +pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk= +pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= pitr.ca/jsontokenizer v0.3.2 h1:kzsKwfkWPV5XCmAF//hznh3L9IXbiL8aHbI0FswRkCs= pitr.ca/jsontokenizer v0.3.2/go.mod h1:3DJdA2QNOU6cI0XkH6pRKZ4Oe8G5SDRUQ6PFAwaQ3YY= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +tags.cncf.io/container-device-interface v1.1.0 h1:RnxNhxF1JOu6CJUVpetTYvrXHdxw9j9jFYgZpI+anSY= +tags.cncf.io/container-device-interface v1.1.0/go.mod h1:76Oj0Yqp9FwTx/pySDc8Bxjpg+VqXfDb50cKAXVJ34Q= diff --git a/itest/README.md b/itest/README.md new file mode 100644 index 0000000..df06da0 --- /dev/null +++ b/itest/README.md @@ -0,0 +1,55 @@ +# Integration tests (itest) + +Docker-backed integration tests behind the `itest` build tag. Everything here +runs against a **forge-mode ingot deployed in the smelt Forge stack** (sprue, +piri, indexing-service, postgres, ...) with a build of **this working tree** +bind-mounted over the published image — what you just edited is what runs. +There is no in-memory ingot anywhere: the deployment under test is the real +one. No smelt checkout needed; compose files travel with the Go import. + +The pattern: `make test` while iterating (seconds, no Docker), `make itest` +when you're ready to wait for the real thing. CI does the same — unit tests +first, integration only after they pass. + +```bash +make itest # everything (~10 min) +go test -tags itest ./itest -run 'TestForgeVersity/PutObject' -v # one category +go test -tags itest ./itest -run 'TestForgeVersity/PutObject/success' -v # one case +``` + +## The conformance partition — `TestForgeVersity` + +The S3 surface is gated by curated lists of upstream +[versitygw](https://github.com/versity/versitygw) integration cases in +`versity_{bucket,object,multipart}_test.go`: per upstream group, a table of +cases ingot must PASS and a table of known failures (**XFail**, reported as +SKIP). One shared stack serves all categories. + +**The ratchet:** an expected-pass case failing fails the build; an XFail case +*unexpectedly passing* also errors — move its line from the xfail table to +the pass table (that's the promotion). The XFail tables are the precise, +reviewable statement of what ingot's S3 surface doesn't do yet. + +**When bumping the versitygw dependency:** new upstream cases are NOT picked +up automatically — nothing enforces list exhaustiveness at runtime. Diff the +group dispatch lists (`tests/integration/group-tests.go`) against the tables +here and add new cases to the pass table (demote to xfail if they fail). + +## Scenario tests + +| Test | Covers | ~time | +|---|---|---| +| `TestForgeScenarios` | Ingot-unique behaviors upstream can't assert, on a stack whose ingot config lowers `max_blob_size` to 64 KiB (`testdata/config-smallblob.yaml`): coarse blob-split round-trip with spooled-by-digest proof (spool counted inside the container), zero-byte objects, a multipart part spanning multiple internal blobs, and failed-Complete session recovery. | ~3 min | +| `TestForgeNativeProvision` | Guppy-free onboarding: `ingot login` + `space generate --provision-to` in the container, then a PUT/GET round-trip over the real ship path. | ~1.7 min | +| `TestForgeReadAfterEviction` | The appliance read tier: PUT, wipe `/data/spool`, GET must re-fetch body blobs from piri via the local locator + `/content/retrieve`. | ~1.3 min | + +Notes: + +- `GOWORK=off` matches the Makefile convention (a parent `go.work` may declare + a newer Go than the installed toolchain); `make itest` sets it for you. +- **One itest run per Docker host at a time** — the pre-test sweep removes + every `smeltery-*` container, including another suite's live stack. +- The other services run their published `:main` images — `docker pull` them + occasionally; compose won't refresh an existing tag. +- CI runs this suite on every PR after unit tests pass + (`.github/workflows/go-test.yml`, job `itest`). diff --git a/itest/forge_eviction_test.go b/itest/forge_eviction_test.go new file mode 100644 index 0000000..757848e --- /dev/null +++ b/itest/forge_eviction_test.go @@ -0,0 +1,60 @@ +//go:build itest + +package itest + +import ( + "bytes" + "testing" + + ingottest "github.com/fil-forge/ingot/testing" +) + +// TestForgeReadAfterEviction proves the appliance read tier: after the local +// spool is wiped, a GET must re-fetch the object's body blobs from piri by +// resolving their location from the local blob_locations table +// (registry.LocalLocator) and issuing a /content/retrieve — not from +// read-after-write. Body blobs live only in the spool; the manifest/MST live +// in the catalog log and survive the wipe, so only the body read exercises +// the network tier. +// +// go test -tags itest ./itest -run TestForgeReadAfterEviction -v -timeout 900s +func TestForgeReadAfterEviction(t *testing.T) { + ctx := t.Context() + const email = "test@example.com" + + s, ingotEndpoint := forgeStack(t) + + // ingot self-provisions its space on sprue (guppy-free) so uploads succeed. + ingotSelfProvision(t, ctx, s, email) + + cfg := forgeConfig(ingotEndpoint) + const bucket, key = "evict-bucket", "obj" + + // A deterministic body large enough to be a real blob shipped to piri. + data := make([]byte, 512*1024) + for i := range data { + data[i] = byte(i*7 + 3) + } + + if err := ingottest.CreateBucket(ctx, cfg, bucket); err != nil { + t.Fatalf("create bucket: %v", err) + } + if err := ingottest.PutBytes(ctx, cfg, bucket, key, data); err != nil { + t.Fatalf("put object: %v", err) + } + + // Wipe the local spool so the next GET cannot read-after-write — its body + // blobs must be re-fetched from piri. + if out, errOut, err := s.Exec(ctx, "ingot", "sh", "-c", "rm -rf /data/spool"); err != nil { + t.Fatalf("evict spool: %v (stdout=%s stderr=%s)", err, out, errOut) + } + + got, err := ingottest.GetBytes(ctx, cfg, bucket, key) + if err != nil { + t.Fatalf("get after eviction (forge read tier): %v", err) + } + if !bytes.Equal(got, data) { + t.Fatalf("read-after-eviction mismatch: got %d bytes, want %d", len(got), len(data)) + } + t.Logf("read-after-eviction OK: %d bytes re-fetched from piri via the local locator", len(got)) +} diff --git a/itest/forge_native_test.go b/itest/forge_native_test.go new file mode 100644 index 0000000..e505ebc --- /dev/null +++ b/itest/forge_native_test.go @@ -0,0 +1,64 @@ +//go:build itest + +package itest + +import ( + "bytes" + "strings" + "testing" + + ingottest "github.com/fil-forge/ingot/testing" +) + +// TestForgeNativeProvision proves ingot needs no guppy: it drives +// `ingot login` and `ingot space generate --provision-to` inside the ingot +// container (the access-request email confirmed via smtp4dev), then +// round-trips a PUT/GET through the forge-mode listener to confirm the full +// ship path (sprue -> piri -> indexer) works on a space ingot provisioned +// itself. +// +// go test -tags itest ./itest -run TestForgeNativeProvision -v -timeout 900s +func TestForgeNativeProvision(t *testing.T) { + ctx := t.Context() + const email = "test@example.com" + + s, ingotEndpoint := forgeStack(t) + + // 1. ingot logs itself in (no guppy). + ingotLoginViaEmail(t, ctx, s, email) + + // 2. ingot provisions its own space (no guppy): generate reuses the + // daemon's /data/space.key, provisions it to the account on sprue, and + // grants access. Provisioning is server-side, so the running daemon needs + // no restart. + out, errOut, err := s.Exec(ctx, "ingot", + "ingot", "--config", ingotConfigPath, "space", "generate", "--provision-to", email) + if err != nil { + t.Fatalf("ingot space generate: %v (stdout=%s stderr=%s)", err, out, errOut) + } + if !strings.Contains(out+errOut, "Generated space:") { + t.Fatalf("ingot space generate: unexpected output stdout=%q stderr=%q", out, errOut) + } + t.Logf("ingot space generate ok:\nstdout=%s\nstderr=%s", out, errOut) + + // 3. PUT/GET round-trip against the forge-mode ingot — exercises the full + // blob ship path (sprue -> piri -> indexer) on the natively-provisioned + // space. The exhaustive S3 surface is TestForgeVersity's job. + cfg := forgeConfig(ingotEndpoint) + const bucket, key = "native-provision", "obj" + data := patternBytes(512 << 10) + if err := ingottest.CreateBucket(ctx, cfg, bucket); err != nil { + t.Fatalf("create bucket: %v", err) + } + if err := ingottest.PutBytes(ctx, cfg, bucket, key, data); err != nil { + t.Fatalf("put object: %v", err) + } + got, err := ingottest.GetBytes(ctx, cfg, bucket, key) + if err != nil { + t.Fatalf("get object: %v", err) + } + if !bytes.Equal(got, data) { + t.Fatalf("round-trip mismatch: got %d bytes, want %d", len(got), len(data)) + } + t.Logf("self-provisioned round-trip OK: %d bytes through sprue/piri", len(data)) +} diff --git a/itest/scenarios_test.go b/itest/scenarios_test.go new file mode 100644 index 0000000..a782b4f --- /dev/null +++ b/itest/scenarios_test.go @@ -0,0 +1,249 @@ +//go:build itest + +package itest + +import ( + "bytes" + "fmt" + "strings" + "testing" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/s3" + "github.com/aws/aws-sdk-go-v2/service/s3/types" +) + +// TestForgeScenarios covers ingot-unique behaviors the upstream versitygw +// suite cannot assert — internal blob-plane properties and session-state +// recovery — against a forge-mode stack whose ingot config lowers +// max_blob_size to 64 KiB (testdata/config-smallblob.yaml) so small objects +// coarse-split into multiple body blobs. One stack is shared by all +// subtests; spool assertions are delta-based so subtest order doesn't +// matter. +// +// These were ported from the old in-process suite; assertions that merely +// duplicated upstream versitygw coverage (e.g. abort semantics) were dropped +// in the move. +func TestForgeScenarios(t *testing.T) { + const maxBlob = 64 << 10 // must match testdata/config-smallblob.yaml + + s, endpoint := forgeStack(t, withSmallBlobConfig()) + ctx := t.Context() + ingotSelfProvision(t, ctx, s, "test@example.com") + cl := sdkClient(forgeS3Conf(endpoint)) + + // BlobSplitMultiBlobRoundTrip: a PUT several times larger than + // max_blob_size is coarsely split into multiple BlobRefs; the + // whole-object GET, boundary-spanning ranged GETs, and md5 ETag all + // reconstruct the exact bytes, and the bodies land in the spool by + // digest (the data-plane inversion) — not journaled into the log. + t.Run("BlobSplitMultiBlobRoundTrip", func(t *testing.T) { + const bucket = "blob-split" + if _, err := cl.CreateBucket(ctx, &s3.CreateBucketInput{Bucket: aws.String(bucket)}); err != nil { + t.Fatalf("CreateBucket: %v", err) + } + + // 3.5 blobs: blob0..blob2 are full (64 KiB), blob3 is a half blob. + const size = 3*maxBlob + maxBlob/2 + data := patternBytes(size) + + spoolBefore := spoolBlobCount(t, ctx, s) + put, err := cl.PutObject(ctx, &s3.PutObjectInput{ + Bucket: aws.String(bucket), + Key: aws.String("big"), + Body: bytes.NewReader(data), + }) + if err != nil { + t.Fatalf("PutObject: %v", err) + } + if want := quotedMD5(data); aws.ToString(put.ETag) != want { + t.Fatalf("PUT ETag = %s, want %s", aws.ToString(put.ETag), want) + } + if got := spoolBlobCount(t, ctx, s) - spoolBefore; got != 4 { + t.Fatalf("PUT added %d spool blobs, want 4 — bodies must be spooled by digest, not logged", got) + } + + if got := getBody(t, ctx, cl, bucket, "big", ""); !bytes.Equal(got, data) { + t.Fatalf("whole GET mismatch: got %d bytes, want %d", len(got), len(data)) + } + + rangeCases := []struct{ start, end int }{ + {maxBlob - 10, maxBlob + 2000}, // crosses a blob boundary + {maxBlob + 5, maxBlob + 105}, // wholly inside blob1, non-zero in-blob offset + {size - 1, size - 1}, // final byte only + {0, 0}, // first byte only + } + for _, rc := range rangeCases { + hdr := fmt.Sprintf("bytes=%d-%d", rc.start, rc.end) + got := getBody(t, ctx, cl, bucket, "big", hdr) + want := data[rc.start : rc.end+1] + if !bytes.Equal(got, want) { + t.Fatalf("range %s mismatch: got %d bytes, want %d", hdr, len(got), len(want)) + } + } + + head, err := cl.HeadObject(ctx, &s3.HeadObjectInput{Bucket: aws.String(bucket), Key: aws.String("big")}) + if err != nil { + t.Fatalf("HeadObject: %v", err) + } + if aws.ToInt64(head.ContentLength) != size { + t.Fatalf("HEAD ContentLength = %d, want %d", aws.ToInt64(head.ContentLength), size) + } + if want := quotedMD5(data); aws.ToString(head.ETag) != want { + t.Fatalf("HEAD ETag = %s, want %s", aws.ToString(head.ETag), want) + } + }) + + // ZeroByteObject: a 0-byte object stores no blob, round-trips empty, + // and carries the well-known empty-content md5 ETag. + t.Run("ZeroByteObject", func(t *testing.T) { + const bucket = "zero-byte" + if _, err := cl.CreateBucket(ctx, &s3.CreateBucketInput{Bucket: aws.String(bucket)}); err != nil { + t.Fatalf("CreateBucket: %v", err) + } + + const emptyMD5 = `"d41d8cd98f00b204e9800998ecf8427e"` + spoolBefore := spoolBlobCount(t, ctx, s) + put, err := cl.PutObject(ctx, &s3.PutObjectInput{ + Bucket: aws.String(bucket), + Key: aws.String("empty"), + Body: bytes.NewReader(nil), + }) + if err != nil { + t.Fatalf("PutObject: %v", err) + } + if aws.ToString(put.ETag) != emptyMD5 { + t.Fatalf("PUT ETag = %s, want %s", aws.ToString(put.ETag), emptyMD5) + } + if got := spoolBlobCount(t, ctx, s) - spoolBefore; got != 0 { + t.Fatalf("zero-byte PUT added %d spool blobs, want 0", got) + } + + if got := getBody(t, ctx, cl, bucket, "empty", ""); len(got) != 0 { + t.Fatalf("zero-byte GET returned %d bytes, want 0", len(got)) + } + head, err := cl.HeadObject(ctx, &s3.HeadObjectInput{Bucket: aws.String(bucket), Key: aws.String("empty")}) + if err != nil { + t.Fatalf("HeadObject: %v", err) + } + if aws.ToInt64(head.ContentLength) != 0 { + t.Fatalf("HEAD ContentLength = %d, want 0", aws.ToInt64(head.ContentLength)) + } + if aws.ToString(head.ETag) != emptyMD5 { + t.Fatalf("HEAD ETag = %s, want %s", aws.ToString(head.ETag), emptyMD5) + } + }) + + // MultipartPartSpansBlobs: Create → UploadPart×3 → Complete where part 1 + // is larger than max_blob_size, so a single part coarse-splits into + // multiple internal body blobs. Upstream can assert multipart round-trip + // but has no notion of ingot's internal split; the boundary-spanning + // ranged GET proves reassembly across both part and blob boundaries. + t.Run("MultipartPartSpansBlobs", func(t *testing.T) { + const bucket, key = "mpbucket", "big/obj" + if _, err := cl.CreateBucket(ctx, &s3.CreateBucketInput{Bucket: aws.String(bucket)}); err != nil { + t.Fatalf("CreateBucket: %v", err) + } + + create, err := cl.CreateMultipartUpload(ctx, &s3.CreateMultipartUploadInput{ + Bucket: aws.String(bucket), Key: aws.String(key), ContentType: aws.String("text/plain"), + }) + if err != nil { + t.Fatalf("CreateMultipartUpload: %v", err) + } + uploadID := create.UploadId + + // Part 1 (150 KiB) spans three 64 KiB blobs; parts 2 and 3 are small. + partData := [][]byte{patternBytes(150 << 10), patternBytes(40 << 10), patternBytes(9 << 10)} + var completed []types.CompletedPart + var whole []byte + for i, data := range partData { + pn := int32(i + 1) + up, err := cl.UploadPart(ctx, &s3.UploadPartInput{ + Bucket: aws.String(bucket), Key: aws.String(key), UploadId: uploadID, + PartNumber: aws.Int32(pn), Body: bytes.NewReader(data), + }) + if err != nil { + t.Fatalf("UploadPart %d: %v", pn, err) + } + completed = append(completed, types.CompletedPart{PartNumber: aws.Int32(pn), ETag: up.ETag}) + whole = append(whole, data...) + } + + comp, err := cl.CompleteMultipartUpload(ctx, &s3.CompleteMultipartUploadInput{ + Bucket: aws.String(bucket), Key: aws.String(key), UploadId: uploadID, + MultipartUpload: &types.CompletedMultipartUpload{Parts: completed}, + }) + if err != nil { + t.Fatalf("CompleteMultipartUpload: %v", err) + } + if et := strings.Trim(aws.ToString(comp.ETag), `"`); !strings.HasSuffix(et, "-3") { + t.Fatalf("complete ETag = %q, want a multipart -3 suffix", et) + } + + if got := getBody(t, ctx, cl, bucket, key, ""); !bytes.Equal(got, whole) { + t.Fatalf("multipart GET mismatch: got %d bytes, want %d", len(got), len(whole)) + } + head, err := cl.HeadObject(ctx, &s3.HeadObjectInput{Bucket: aws.String(bucket), Key: aws.String(key)}) + if err != nil { + t.Fatalf("HeadObject: %v", err) + } + if aws.ToInt64(head.ContentLength) != int64(len(whole)) { + t.Fatalf("HEAD size = %d, want %d", aws.ToInt64(head.ContentLength), len(whole)) + } + // A ranged GET spanning the part-1→part-2 boundary still reconstructs. + p1 := len(partData[0]) + hdr := fmt.Sprintf("bytes=%d-%d", p1-10, p1+2000) + if got := getBody(t, ctx, cl, bucket, key, hdr); !bytes.Equal(got, whole[p1-10:p1+2001]) { + t.Fatalf("ranged multipart GET across the part boundary mismatch: got %d bytes", len(got)) + } + }) + + // MultipartFailedCompleteStaysAbortable: the zombie-session regression. A + // Complete that fails validation (wrong part ETag) must leave the session + // open — a retry with the correct ETag succeeds and reads back + // byte-exact. Upstream tests re-complete-after-success + // (already_completed) but not recovery from a FAILED complete; the + // session-state revert out of 'completing' is ingot catalog behavior. + t.Run("MultipartFailedCompleteStaysAbortable", func(t *testing.T) { + const bucket, key = "mpretry", "obj" + if _, err := cl.CreateBucket(ctx, &s3.CreateBucketInput{Bucket: aws.String(bucket)}); err != nil { + t.Fatalf("CreateBucket: %v", err) + } + create, err := cl.CreateMultipartUpload(ctx, &s3.CreateMultipartUploadInput{Bucket: aws.String(bucket), Key: aws.String(key)}) + if err != nil { + t.Fatalf("CreateMultipartUpload: %v", err) + } + data := patternBytes(8 << 10) + up, err := cl.UploadPart(ctx, &s3.UploadPartInput{ + Bucket: aws.String(bucket), Key: aws.String(key), UploadId: create.UploadId, + PartNumber: aws.Int32(1), Body: bytes.NewReader(data), + }) + if err != nil { + t.Fatalf("UploadPart: %v", err) + } + + // Complete with a WRONG part ETag → InvalidPart. The session must not zombie. + if _, err := cl.CompleteMultipartUpload(ctx, &s3.CompleteMultipartUploadInput{ + Bucket: aws.String(bucket), Key: aws.String(key), UploadId: create.UploadId, + MultipartUpload: &types.CompletedMultipartUpload{Parts: []types.CompletedPart{ + {PartNumber: aws.Int32(1), ETag: aws.String("\"deadbeefdeadbeefdeadbeefdeadbeef\"")}, + }}, + }); err == nil { + t.Fatal("Complete with a wrong part ETag: want an error, got nil") + } + + // A retry with the correct ETag now succeeds (session was reverted to open). + if _, err := cl.CompleteMultipartUpload(ctx, &s3.CompleteMultipartUploadInput{ + Bucket: aws.String(bucket), Key: aws.String(key), UploadId: create.UploadId, + MultipartUpload: &types.CompletedMultipartUpload{Parts: []types.CompletedPart{ + {PartNumber: aws.Int32(1), ETag: up.ETag}, + }}, + }); err != nil { + t.Fatalf("Complete retry after a failed Complete: %v", err) + } + if got := getBody(t, ctx, cl, bucket, key, ""); !bytes.Equal(got, data) { + t.Fatalf("retried multipart object mismatch: %d bytes", len(got)) + } + }) +} diff --git a/itest/stack_test.go b/itest/stack_test.go new file mode 100644 index 0000000..8d6a6fb --- /dev/null +++ b/itest/stack_test.go @@ -0,0 +1,244 @@ +//go:build itest + +package itest + +import ( + "context" + "crypto/md5" + "encoding/hex" + "fmt" + "io" + "log" + "net/http" + "os" + "os/exec" + "path/filepath" + "runtime" + "strconv" + "strings" + "sync" + "testing" + "time" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/s3" + "github.com/versity/versitygw/tests/integration" + + ingottest "github.com/fil-forge/ingot/testing" + "github.com/fil-forge/smelt/pkg/clients/guppy" + "github.com/fil-forge/smelt/pkg/stack" +) + +// This file is the shared harness for the integration tests: they boot the +// full smelt Forge stack (sprue + piri + indexer + postgres + ...) via +// smelt's Go SDK and mount the WORKING TREE's ingot binary over the published +// image, so every S3 call exercises this checkout's code against the real +// network path. Requires Docker; runs behind the itest build tag: +// +// make itest + +// ingotConfigPath is where smelt's ingot system definition mounts the daemon +// config inside the container. +const ingotConfigPath = "/etc/ingot/config.yaml" + +// forgeCreds are the root S3 credentials from smelt's +// systems/ingot/config/config.yaml. +const ( + forgeAccessKey = "ingot" + forgeSecretKey = "ingotsecret" + forgeRegion = "us-east-1" +) + +// TestMain sweeps containers/volumes leaked by prior crashed itest runs (same +// smeltery- project prefix as any smelt-SDK stack). Best-effort: a missing +// docker only matters once a test actually boots a stack. +func TestMain(m *testing.M) { + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute) + if err := stack.CleanupLeaked(ctx); err != nil { + log.Printf("itest: pre-test sweep warning: %v", err) + } + cancel() + os.Exit(m.Run()) +} + +var ( + buildOnce sync.Once + builtBinary string + buildErr error +) + +// localIngotBinary compiles the working tree's ingot daemon once per test +// run as a static linux binary suitable for bind-mounting over the published +// image's /usr/bin/ingot. GOARCH follows the test host so the binary matches +// the Docker host's container platform; GOWORK=off matches the Makefile +// convention (the parent go.work may declare a newer Go than the toolchain). +func localIngotBinary(t *testing.T) string { + t.Helper() + buildOnce.Do(func() { + dir, err := os.MkdirTemp("", "ingot-itest-bin-") + if err != nil { + buildErr = err + return + } + out := filepath.Join(dir, "ingot") + cmd := exec.Command("go", "build", "-o", out, "./cmd/ingot") + cmd.Dir = ".." // tests run in itest/; build from the module root + cmd.Env = append(os.Environ(), + "CGO_ENABLED=0", + "GOOS=linux", + "GOARCH="+runtime.GOARCH, + "GOWORK=off", + ) + if outb, err := cmd.CombinedOutput(); err != nil { + buildErr = fmt.Errorf("go build ./cmd/ingot: %v\n%s", err, outb) + return + } + builtBinary = out + }) + if buildErr != nil { + t.Fatalf("build local ingot binary: %v", buildErr) + } + return builtBinary +} + +// forgeStack boots the smelt stack with the working tree's ingot injected +// (plus any extra stack options), waits for ingot's S3 listener, and returns +// the stack and ingot's host endpoint. Callers that upload must provision +// first (ingotSelfProvision). The stack lives until the calling test — +// including all of its subtests — completes; share it across categories by +// booting in a parent test and passing the conf into t.Run subtests. +func forgeStack(t *testing.T, extra ...stack.Option) (*stack.Stack, string) { + t.Helper() + t.Logf("booting the smelt Forge stack (~1-2 min; first run also compiles ingot and pulls images)") + opts := append([]stack.Option{ + stack.WithPiriNodes(stack.PiriNodeConfig{}), + stack.WithServiceBinary("ingot", localIngotBinary(t)), + }, extra...) + s := stack.MustNewStack(t, opts...) + endpoint := s.IngotEndpoint() + waitHTTPOK(t, endpoint+"/health", 2*time.Minute) + t.Logf("forge-mode ingot S3 endpoint: %s", endpoint) + return s, endpoint +} + +// withSmallBlobConfig mounts testdata/config-smallblob.yaml over the ingot +// container's config — smelt's default forge config with max_blob_size +// lowered to 64 KiB, for the blob-split scenarios. +func withSmallBlobConfig() stack.Option { + return stack.WithServiceConfig("ingot", "testdata/config-smallblob.yaml") +} + +// forgeConfig is the roundtrip-helper config for a stack-deployed ingot. +func forgeConfig(endpoint string) ingottest.Config { + return ingottest.Config{ + Endpoint: endpoint, + AccessKey: forgeAccessKey, + SecretKey: forgeSecretKey, + Region: forgeRegion, + } +} + +// forgeS3Conf is the upstream-suite config for a stack-deployed ingot, for +// invoking versitygw cases directly. +func forgeS3Conf(endpoint string) *integration.S3Conf { + return ingottest.NewS3Conf(forgeConfig(endpoint)) +} + +// waitHTTPOK polls url until it returns 2xx or the timeout elapses. +func waitHTTPOK(t *testing.T, url string, timeout time.Duration) { + t.Helper() + deadline := time.Now().Add(timeout) + for time.Now().Before(deadline) { + resp, err := http.Get(url) + if err == nil { + resp.Body.Close() + if resp.StatusCode >= 200 && resp.StatusCode < 300 { + return + } + } + time.Sleep(time.Second) + } + t.Fatalf("%s not healthy after %s", url, timeout) +} + +// ingotLoginViaEmail logs the ingot agent in as email — guppy-free: the +// blocking `ingot login` CLI runs in the ingot container and the validation +// link is clicked from inside that same container. +func ingotLoginViaEmail(t *testing.T, ctx context.Context, s *stack.Stack, email string) { + t.Helper() + if err := guppy.LoginViaEmail(ctx, s, "ingot", email, + "ingot", "--config", ingotConfigPath, "login", email); err != nil { + t.Fatalf("ingot login via email: %v", err) + } +} + +// ingotSelfProvision has ingot provision its own space to email on sprue +// (login + `space generate --provision-to`), no guppy involved. +func ingotSelfProvision(t *testing.T, ctx context.Context, s *stack.Stack, email string) { + t.Helper() + ingotLoginViaEmail(t, ctx, s, email) + if out, errOut, err := s.Exec(ctx, "ingot", + "ingot", "--config", ingotConfigPath, "space", "generate", "--provision-to", email); err != nil { + t.Fatalf("ingot space generate: %v (stdout=%s stderr=%s)", err, out, errOut) + } +} + +// spoolBlobCount counts the body blobs in the ingot container's spool, +// ignoring in-progress temp files. Used to prove object bodies are spooled by +// digest (the data-plane inversion), not journaled into the log. +func spoolBlobCount(t *testing.T, ctx context.Context, s *stack.Stack) int { + t.Helper() + out, errOut, err := s.Exec(ctx, "ingot", "sh", "-c", + `find /data/spool -maxdepth 1 -type f ! -name '.tmp*' 2>/dev/null | wc -l`) + if err != nil { + t.Fatalf("count spool blobs: %v (stderr=%s)", err, errOut) + } + n, err := strconv.Atoi(strings.TrimSpace(out)) + if err != nil { + t.Fatalf("parse spool count %q: %v", out, err) + } + return n +} + +// --- raw-SDK helpers for the scenario tests (ported from the old +// in-process suite; same construction the upstream integration cases use) --- + +func sdkClient(conf *integration.S3Conf) *s3.Client { + return conf.GetClient() +} + +func quotedMD5(b []byte) string { + sum := md5.Sum(b) + return `"` + hex.EncodeToString(sum[:]) + `"` +} + +// patternBytes returns n deterministic bytes — a cheap way to make a payload +// whose every byte position is verifiable after a round trip. The (i>>16) term +// mixes in the 64 KiB-blob index so each coarse-split blob has distinct content +// (a purely position-linear pattern would make equal-length blobs identical and +// dedup them in the spool). +func patternBytes(n int) []byte { + b := make([]byte, n) + for i := range b { + b[i] = byte(i*31 + 7 + (i>>16)*101) + } + return b +} + +func getBody(t *testing.T, ctx context.Context, cl *s3.Client, bucket, key, rangeHdr string) []byte { + t.Helper() + in := &s3.GetObjectInput{Bucket: aws.String(bucket), Key: aws.String(key)} + if rangeHdr != "" { + in.Range = aws.String(rangeHdr) + } + out, err := cl.GetObject(ctx, in) + if err != nil { + t.Fatalf("GetObject %s (range %q): %v", key, rangeHdr, err) + } + defer out.Body.Close() + b, err := io.ReadAll(out.Body) + if err != nil { + t.Fatalf("read body %s: %v", key, err) + } + return b +} diff --git a/itest/testdata/config-smallblob.yaml b/itest/testdata/config-smallblob.yaml new file mode 100644 index 0000000..3db7697 --- /dev/null +++ b/itest/testdata/config-smallblob.yaml @@ -0,0 +1,30 @@ +# Ingot daemon config for the blob-split scenario itests — smelt's default +# forge-mode config (systems/ingot/config/config.yaml) with max_blob_size +# lowered to 64 KiB so small objects coarse-split into multiple body blobs. +# Mounted over /etc/ingot/config.yaml via stack.WithServiceConfig. + +mode: forge + +addr: "0.0.0.0:9000" +region: us-east-1 +data_dir: /data + +root_access: ingot +root_secret: ingotsecret + +identity: + key_file: /keys/ingot.pem + +postgres_dsn: "postgres://ingot:ingot@ingot-postgres:5432/ingot?sslmode=disable" + +upload_service_url: "http://upload:80" +upload_service_did: "did:web:upload" +upload_receipts_url: "http://upload:80/receipt" + +indexer_endpoint: "http://indexer:80" +indexer_did: "did:web:indexer" + +token_store_dir: /data + +# The point of this config: force coarse blob-splitting at 64 KiB. +max_blob_size: 65536 diff --git a/itest/versity_bucket_test.go b/itest/versity_bucket_test.go new file mode 100644 index 0000000..3ae9732 --- /dev/null +++ b/itest/versity_bucket_test.go @@ -0,0 +1,73 @@ +//go:build itest + +package itest + +import ( + "github.com/versity/versitygw/tests/integration" +) + +// Bucket-level groups of the S3 conformance partition. Seeded from the +// in-memory suite's curated lists, verified live against the forge-mode +// stack (the two deployments proved case-identical on these groups before +// the in-memory harness was removed). + +var createBucketPass = []forgeCase{ + {name: "invalid_bucket_name", fn: integration.CreateBucket_invalid_bucket_name}, + {name: "invalid_canned_acl", fn: integration.CreateBucket_invalid_canned_acl}, + {name: "invalid_location_constraint", fn: integration.CreateBucket_invalid_location_constraint}, + {name: "invalid_ownership", fn: integration.CreateBucket_invalid_ownership}, + {name: "ownership_with_acl", fn: integration.CreateBucket_ownership_with_acl}, + {name: "success", fn: integration.CreateBucket_success}, +} + +var createBucketXFail = []forgeCase{ + {name: "as_user", fn: integration.CreateBucket_as_user}, + {name: "default_acl", fn: integration.CreateBucket_default_acl}, + {name: "default_object_lock", fn: integration.CreateBucket_default_object_lock}, + {name: "duplicate_keys", fn: integration.CreateBucket_duplicate_keys}, + {name: "existing_bucket", fn: integration.CreateBucket_existing_bucket}, + {name: "invalid_tags", fn: integration.CreateBucket_invalid_tags}, + {name: "long_tags", fn: integration.CreateBucket_long_tags}, + {name: "non_default_acl", fn: integration.CreateBucket_non_default_acl}, + {name: "owned_by_you", fn: integration.CreateBucket_owned_by_you}, + {name: "private_canned_acl", fn: integration.CreateBucket_private_canned_acl}, + {name: "private_canned_acl_bucket_owner_enforced_ownership", fn: integration.CreateBucket_private_canned_acl_bucket_owner_enforced_ownership}, + {name: "tag_count_limit", fn: integration.CreateBucket_tag_count_limit}, +} + +var headBucketPass = []forgeCase{ + {name: "non_existing_bucket", fn: integration.HeadBucket_non_existing_bucket}, + {name: "success", fn: integration.HeadBucket_success}, +} + +var listBucketsPass = []forgeCase{ + {name: "empty_success", fn: integration.ListBuckets_empty_success}, + {name: "invalid_max_buckets", fn: integration.ListBuckets_invalid_max_buckets}, + {name: "success", fn: integration.ListBuckets_success}, + {name: "with_prefix", fn: integration.ListBuckets_with_prefix}, + // truncated is counter-position-sensitive, not S3-semantics-sensitive: + // the upstream case names buckets from a process-global counter + // (test-bucket-) and compares pages position-by-position expecting + // creation order, while ingot returns buckets lexicographically. Whether + // a page straddles a digit boundary (…9, 10 or …99, 100) — and thus + // whether the orders diverge — depends on how far the counter advanced + // before this case runs, which is an artifact of suite composition (it + // deterministically fails at this suite's counter position). Skipped + // rather than curated; the pagination surface itself is exercised by + // ListBuckets_with_prefix and ListBuckets_invalid_max_buckets. + {name: "truncated", fn: integration.ListBuckets_truncated, skip: func() string { + return "creation-order pagination assertion is counter-position-dependent (ingot lists lexicographically)" + }}, +} + +var listBucketsXFail = []forgeCase{ + {name: "as_admin", fn: integration.ListBuckets_as_admin}, + {name: "as_user", fn: integration.ListBuckets_as_user}, +} + +var deleteBucketPass = []forgeCase{ + {name: "incorrect_expected_bucket_owner", fn: integration.DeleteBucket_incorrect_expected_bucket_owner}, + {name: "non_empty_bucket", fn: integration.DeleteBucket_non_empty_bucket}, + {name: "non_existing_bucket", fn: integration.DeleteBucket_non_existing_bucket}, + {name: "success_status_code", fn: integration.DeleteBucket_success_status_code}, +} diff --git a/itest/versity_multipart_test.go b/itest/versity_multipart_test.go new file mode 100644 index 0000000..dae7ce9 --- /dev/null +++ b/itest/versity_multipart_test.go @@ -0,0 +1,176 @@ +//go:build itest + +package itest + +import ( + "github.com/versity/versitygw/tests/integration" +) + +// Multipart groups of the S3 conformance partition. These had no prior +// curated lists — the partition below was created empirically against the +// forge-mode stack (see the curation note in README.md). The broad strokes: +// the Create/UploadPart/Complete/Abort happy paths and most input validation +// pass; part-level checksums, tagging/object-lock/ACL surfaces, and the +// ListParts / ListMultipartUploads / UploadPartCopy groups are unimplemented. + +var createMultipartPass = []forgeCase{ + {name: "non_existing_bucket", fn: integration.CreateMultipartUpload_non_existing_bucket}, + {name: "long_metadata", fn: integration.CreateMultipartUpload_long_metadata}, + {name: "with_object_lock_invalid_retention", fn: integration.CreateMultipartUpload_with_object_lock_invalid_retention}, + {name: "past_retain_until_date", fn: integration.CreateMultipartUpload_past_retain_until_date}, + {name: "invalid_legal_hold", fn: integration.CreateMultipartUpload_invalid_legal_hold}, + {name: "invalid_object_lock_mode", fn: integration.CreateMultipartUpload_invalid_object_lock_mode}, + {name: "invalid_checksum_algorithm", fn: integration.CreateMultipartUpload_invalid_checksum_algorithm}, + {name: "empty_checksum_algorithm_with_checksum_type", fn: integration.CreateMultipartUpload_empty_checksum_algorithm_with_checksum_type}, + {name: "type_algo_mismatch", fn: integration.CreateMultipartUpload_type_algo_mismatch}, + {name: "invalid_checksum_type", fn: integration.CreateMultipartUpload_invalid_checksum_type}, + {name: "valid_algo_type", fn: integration.CreateMultipartUpload_valid_algo_type}, + {name: "success", fn: integration.CreateMultipartUpload_success}, +} + +var createMultipartXFail = []forgeCase{ + {name: "dir_obj", fn: integration.CreateMultipartUpload_dir_obj}, + {name: "with_metadata", fn: integration.CreateMultipartUpload_with_metadata}, + {name: "with_tagging", fn: integration.CreateMultipartUpload_with_tagging}, + {name: "with_object_lock", fn: integration.CreateMultipartUpload_with_object_lock}, + {name: "with_object_lock_not_enabled", fn: integration.CreateMultipartUpload_with_object_lock_not_enabled}, + {name: "object_acl_not_supported", fn: integration.CreateMultipartUpload_object_acl_not_supported}, +} + +var uploadPartPass = []forgeCase{ + {name: "non_existing_bucket", fn: integration.UploadPart_non_existing_bucket}, + {name: "invalid_part_number", fn: integration.UploadPart_invalid_part_number}, + {name: "non_existing_mp_upload", fn: integration.UploadPart_non_existing_mp_upload}, + {name: "multiple_checksum_headers", fn: integration.UploadPart_multiple_checksum_headers}, + {name: "invalid_checksum_header", fn: integration.UploadPart_invalid_checksum_header}, + {name: "checksum_header_and_algo_mismatch", fn: integration.UploadPart_checksum_header_and_algo_mismatch}, + {name: "success", fn: integration.UploadPart_success}, +} + +var uploadPartXFail = []forgeCase{ + {name: "non_existing_key", fn: integration.UploadPart_non_existing_key}, + {name: "checksum_algorithm_mistmatch_on_initialization", fn: integration.UploadPart_checksum_algorithm_mistmatch_on_initialization}, + {name: "checksum_algorithm_mistmatch_on_initialization_with_value", fn: integration.UploadPart_checksum_algorithm_mistmatch_on_initialization_with_value}, + {name: "incorrect_checksums", fn: integration.UploadPart_incorrect_checksums}, + {name: "no_checksum_with_full_object_checksum_type", fn: integration.UploadPart_no_checksum_with_full_object_checksum_type}, + {name: "no_checksum_with_composite_checksum_type", fn: integration.UploadPart_no_checksum_with_composite_checksum_type}, + {name: "should_calculate_checksum_if_only_algorithm_is_provided", fn: integration.UploadPart_should_calculate_checksum_if_only_algorithm_is_provided}, + {name: "with_checksums_success", fn: integration.UploadPart_with_checksums_success}, +} + +var uploadPartCopyPass = []forgeCase{ + {name: "non_existing_bucket", fn: integration.UploadPartCopy_non_existing_bucket}, + {name: "invalid_part_number", fn: integration.UploadPartCopy_invalid_part_number}, + {name: "invalid_copy_source", fn: integration.UploadPartCopy_invalid_copy_source}, +} + +var uploadPartCopyXFail = []forgeCase{ + {name: "incorrect_uploadId", fn: integration.UploadPartCopy_incorrect_uploadId}, + {name: "incorrect_object_key", fn: integration.UploadPartCopy_incorrect_object_key}, + {name: "non_existing_source_bucket", fn: integration.UploadPartCopy_non_existing_source_bucket}, + {name: "non_existing_source_object_key", fn: integration.UploadPartCopy_non_existing_source_object_key}, + {name: "success", fn: integration.UploadPartCopy_success}, + {name: "by_range_invalid_ranges", fn: integration.UploadPartCopy_by_range_invalid_ranges}, + {name: "exceeding_copy_source_range", fn: integration.UploadPartCopy_exceeding_copy_source_range}, + {name: "greater_range_than_obj_size", fn: integration.UploadPartCopy_greater_range_than_obj_size}, + {name: "by_range_success", fn: integration.UploadPartCopy_by_range_success}, + {name: "should_copy_the_checksum", fn: integration.UploadPartCopy_should_copy_the_checksum}, + {name: "should_not_copy_the_checksum", fn: integration.UploadPartCopy_should_not_copy_the_checksum}, + {name: "should_calculate_the_checksum", fn: integration.UploadPartCopy_should_calculate_the_checksum}, + {name: "conditional_reads", fn: integration.UploadPartCopy_conditional_reads}, + {name: "incorrect_source_bucket_expected_owner", fn: integration.UploadPartCopy_incorrect_source_bucket_expected_owner}, +} + +var listPartsPass = []forgeCase{ + {name: "invalid_max_parts", fn: integration.ListParts_invalid_max_parts}, + {name: "invalid_part_number_marker", fn: integration.ListParts_invalid_part_number_marker}, +} + +var listPartsXFail = []forgeCase{ + {name: "incorrect_uploadId", fn: integration.ListParts_incorrect_uploadId}, + {name: "incorrect_object_key", fn: integration.ListParts_incorrect_object_key}, + {name: "default_max_parts", fn: integration.ListParts_default_max_parts}, + {name: "exceeding_max_parts", fn: integration.ListParts_exceeding_max_parts}, + {name: "truncated", fn: integration.ListParts_truncated}, + {name: "with_checksums", fn: integration.ListParts_with_checksums}, + {name: "null_checksums", fn: integration.ListParts_null_checksums}, + {name: "success", fn: integration.ListParts_success}, +} + +var listMultipartUploadsPass = []forgeCase{ + {name: "non_existing_bucket", fn: integration.ListMultipartUploads_non_existing_bucket}, + {name: "invalid_max_uploads", fn: integration.ListMultipartUploads_invalid_max_uploads}, +} + +var listMultipartUploadsXFail = []forgeCase{ + {name: "empty_result", fn: integration.ListMultipartUploads_empty_result}, + {name: "max_uploads", fn: integration.ListMultipartUploads_max_uploads}, + {name: "exceeding_max_uploads", fn: integration.ListMultipartUploads_exceeding_max_uploads}, + {name: "ignore_upload_id_marker", fn: integration.ListMultipartUploads_ignore_upload_id_marker}, + {name: "invalid_uploadId_marker", fn: integration.ListMultipartUploads_invalid_uploadId_marker}, + {name: "keyMarker_not_from_list", fn: integration.ListMultipartUploads_keyMarker_not_from_list}, + {name: "delimiter_truncated", fn: integration.ListMultipartUploads_delimiter_truncated}, + {name: "prefix", fn: integration.ListMultipartUploads_prefix}, + {name: "both_delimiter_and_prefix", fn: integration.ListMultipartUploads_both_delimiter_and_prefix}, + {name: "delimiter_no_matches", fn: integration.ListMultipartUploads_delimiter_no_matches}, + {name: "with_checksums", fn: integration.ListMultipartUploads_with_checksums}, +} + +var abortMultipartPass = []forgeCase{ + {name: "non_existing_bucket", fn: integration.AbortMultipartUpload_non_existing_bucket}, + {name: "incorrect_uploadId", fn: integration.AbortMultipartUpload_incorrect_uploadId}, + {name: "incorrect_object_key", fn: integration.AbortMultipartUpload_incorrect_object_key}, + {name: "success_status_code", fn: integration.AbortMultipartUpload_success_status_code}, +} + +var abortMultipartXFail = []forgeCase{ + // success asserts the aborted upload disappears from ListMultipartUploads, + // which is unimplemented (see listMultipartUploadsXFail); the abort + // lifecycle itself is proven by TestForgeScenarios. + {name: "success", fn: integration.AbortMultipartUpload_success}, + {name: "if_match_initiated_time", fn: integration.AbortMultipartUpload_if_match_initiated_time}, +} + +var completeMultipartPass = []forgeCase{ + // upstream function name carries a typo (CompletedMultipartUpload_...). + {name: "non_existing_bucket", fn: integration.CompletedMultipartUpload_non_existing_bucket}, + {name: "incorrect_part_number", fn: integration.CompleteMultipartUpload_incorrect_part_number}, + {name: "default_content_type", fn: integration.CompleteMultipartUpload_default_content_type}, + {name: "invalid_ETag", fn: integration.CompleteMultipartUpload_invalid_ETag}, + {name: "empty_parts", fn: integration.CompleteMultipartUpload_empty_parts}, + {name: "incorrect_parts_order", fn: integration.CompleteMultipartUpload_incorrect_parts_order}, + {name: "invalid_checksum_type", fn: integration.CompleteMultipartUpload_invalid_checksum_type}, + {name: "multiple_final_checksums", fn: integration.CompleteMultipartUpload_multiple_final_checksums}, + {name: "invalid_final_checksums", fn: integration.CompleteMultipartUpload_invalid_final_checksums}, + {name: "invalid_final_composite_checksum", fn: integration.CompleteMultipartUpload_invalid_final_composite_checksum}, + {name: "with_metadata", fn: integration.CompleteMultipartUpload_with_metadata}, + {name: "success", fn: integration.CompleteMultipartUpload_success}, + // racey_success races ten concurrent 25 MiB multipart uploads of one key + // under a 30s client deadline — on a host simultaneously running the + // smelt stack its outcome depends on load, not S3 semantics, so it is + // skipped rather than curated. + {name: "racey_success", fn: integration.CompleteMultipartUpload_racey_success, skip: func() string { + return "load-sensitive (10 concurrent 25MiB uploads under a 30s deadline); outcome depends on host load, not S3 semantics" + }}, +} + +var completeMultipartXFail = []forgeCase{ + {name: "invalid_part_number", fn: integration.CompleteMultipartUpload_invalid_part_number}, + {name: "small_upload_size", fn: integration.CompleteMultipartUpload_small_upload_size}, + {name: "mpu_object_size", fn: integration.CompleteMultipartUpload_mpu_object_size}, + {name: "conditional_writes", fn: integration.CompleteMultipartUpload_conditional_writes}, + {name: "invalid_checksum_part", fn: integration.CompleteMultipartUpload_invalid_checksum_part}, + {name: "multiple_checksum_part", fn: integration.CompleteMultipartUpload_multiple_checksum_part}, + {name: "incorrect_checksum_part", fn: integration.CompleteMultipartUpload_incorrect_checksum_part}, + {name: "different_checksum_part", fn: integration.CompleteMultipartUpload_different_checksum_part}, + {name: "missing_part_checksum", fn: integration.CompleteMultipartUpload_missing_part_checksum}, + {name: "incorrect_final_checksums", fn: integration.CompleteMultipartUpload_incorrect_final_checksums}, + {name: "should_calculate_the_final_checksum_full_object", fn: integration.CompleteMultipartUpload_should_calculate_the_final_checksum_full_object}, + {name: "should_verify_the_final_checksum", fn: integration.CompleteMultipartUpload_should_verify_the_final_checksum}, + {name: "should_verify_final_composite_checksum", fn: integration.CompleteMultipartUpload_should_verify_final_composite_checksum}, + {name: "checksum_type_mismatch", fn: integration.CompleteMultipartUpload_checksum_type_mismatch}, + {name: "should_ignore_the_final_checksum", fn: integration.CompleteMultipartUpload_should_ignore_the_final_checksum}, + {name: "should_succeed_without_final_checksum_type", fn: integration.CompleteMultipartUpload_should_succeed_without_final_checksum_type}, + {name: "already_completed", fn: integration.CompleteMultipartUpload_already_completed}, + {name: "racey_data_integrity", fn: integration.CompleteMultipartUpload_racey_data_integrity}, +} diff --git a/itest/versity_object_test.go b/itest/versity_object_test.go new file mode 100644 index 0000000..b5d7dc7 --- /dev/null +++ b/itest/versity_object_test.go @@ -0,0 +1,168 @@ +//go:build itest + +package itest + +import ( + "github.com/versity/versitygw/tests/integration" +) + +// Single-object groups of the S3 conformance partition. + +var putObjectPass = []forgeCase{ + {name: "checksum_algorithm_and_header_mismatch", fn: integration.PutObject_checksum_algorithm_and_header_mismatch}, + {name: "checksums_success", fn: integration.PutObject_checksums_success}, + {name: "conditional_writes", fn: integration.PutObject_conditional_writes}, + {name: "default_checksum", fn: integration.PutObject_default_checksum}, + {name: "default_content_type", fn: integration.PutObject_default_content_type}, + {name: "dir_object_default_checksum", fn: integration.PutObject_dir_object_default_checksum}, + {name: "dir_object_checksums_success", fn: integration.PutObject_dir_object_checksums_success}, + {name: "incorrect_checksums", fn: integration.PutObject_incorrect_checksums}, + {name: "invalid_credentials", fn: integration.PutObject_invalid_credentials}, + {name: "false_negative_object_names", fn: integration.PutObject_false_negative_object_names}, + {name: "invalid_checksum_header", fn: integration.PutObject_invalid_checksum_header}, + {name: "invalid_legal_hold", fn: integration.PutObject_invalid_legal_hold}, + {name: "invalid_object_lock_mode", fn: integration.PutObject_invalid_object_lock_mode}, + {name: "invalid_object_names", fn: integration.PutObject_invalid_object_names}, + {name: "invalid_retain_until_date", fn: integration.PutObject_invalid_retain_until_date}, + {name: "long_metadata", fn: integration.PutObject_long_metadata}, + {name: "missing_object_lock_retention_config", fn: integration.PutObject_missing_object_lock_retention_config}, + {name: "multiple_checksum_headers", fn: integration.PutObject_multiple_checksum_headers}, + {name: "non_existing_bucket", fn: integration.PutObject_non_existing_bucket}, + {name: "past_retain_until_date", fn: integration.PutObject_past_retain_until_date}, + {name: "racey_success", fn: integration.PutObject_racey_success}, + {name: "special_chars", fn: integration.PutObject_special_chars}, + {name: "success", fn: integration.PutObject_success}, + {name: "should_combine_metadata", fn: integration.PutObject_should_combine_metadata}, + {name: "with_metadata", fn: integration.PutObject_with_metadata}, +} + +var putObjectXFail = []forgeCase{ + {name: "missing_bucket_lock", fn: integration.PutObject_missing_bucket_lock}, + {name: "object_acl_not_supported", fn: integration.PutObject_object_acl_not_supported}, + {name: "tagging", fn: integration.PutObject_tagging}, + {name: "with_object_lock", fn: integration.PutObject_with_object_lock}, +} + +var getObjectPass = []forgeCase{ + {name: "by_range_resp_status", fn: integration.GetObject_by_range_resp_status}, + {name: "checksums", fn: integration.GetObject_checksums}, + {name: "conditional_reads", fn: integration.GetObject_conditional_reads}, + {name: "dir_object_checksum", fn: integration.GetObject_dir_object_checksum}, + {name: "dir_with_range", fn: integration.GetObject_dir_with_range}, + {name: "directory_object_noslash", fn: integration.GetObject_directory_object_noslash}, + {name: "empty_object_part_number_1", fn: integration.GetObject_empty_object_part_number_1}, + {name: "invalid_parent", fn: integration.GetObject_invalid_parent}, + {name: "invalid_part_number", fn: integration.GetObject_invalid_part_number}, + {name: "mp_part_number_exceeds_parts_count", fn: integration.GetObject_mp_part_number_exceeds_parts_count}, + {name: "mp_part_number_resp_status", fn: integration.GetObject_mp_part_number_resp_status}, + {name: "mp_part_number_success", fn: integration.GetObject_mp_part_number_success}, + {name: "non_mp_part_number_1_success", fn: integration.GetObject_non_mp_part_number_1_success}, + {name: "large_object", fn: integration.GetObject_large_object}, + {name: "non_existing_dir_object", fn: integration.GetObject_non_existing_dir_object}, + {name: "non_existing_key", fn: integration.GetObject_non_existing_key}, + {name: "not_enabled_checksum_mode", fn: integration.GetObject_not_enabled_checksum_mode}, + {name: "overrides_presign_success", fn: integration.GetObject_overrides_presign_success}, + {name: "overrides_success", fn: integration.GetObject_overrides_success}, + {name: "range_and_part_number", fn: integration.GetObject_range_and_part_number}, + {name: "ranged_with_checksum_mode", fn: integration.GetObject_ranged_with_checksum_mode}, + {name: "with_range", fn: integration.GetObject_with_range}, + {name: "zero_len_with_range", fn: integration.GetObject_zero_len_with_range}, +} + +var getObjectXFail = []forgeCase{ + {name: "directory_success", fn: integration.GetObject_directory_success}, + {name: "overrides_fail_public", fn: integration.GetObject_overrides_fail_public}, + {name: "success", fn: integration.GetObject_success}, +} + +var headObjectPass = []forgeCase{ + {name: "checksums", fn: integration.HeadObject_checksums}, + {name: "conditional_reads", fn: integration.HeadObject_conditional_reads}, + {name: "directory_object_noslash", fn: integration.HeadObject_directory_object_noslash}, + {name: "empty_object_part_number_1", fn: integration.HeadObject_empty_object_part_number_1}, + {name: "invalid_parent_dir", fn: integration.HeadObject_invalid_parent_dir}, + {name: "invalid_part_number", fn: integration.HeadObject_invalid_part_number}, + {name: "mp_part_number_exceeds_parts_count", fn: integration.HeadObject_mp_part_number_exceeds_parts_count}, + {name: "mp_part_number_resp_status", fn: integration.HeadObject_mp_part_number_resp_status}, + {name: "mp_part_number_success", fn: integration.HeadObject_mp_part_number_success}, + {name: "non_mp_part_number_1_success", fn: integration.HeadObject_non_mp_part_number_1_success}, + {name: "non_existing_dir_object", fn: integration.HeadObject_non_existing_dir_object}, + {name: "non_existing_object", fn: integration.HeadObject_non_existing_object}, + {name: "not_enabled_checksum_mode", fn: integration.HeadObject_not_enabled_checksum_mode}, + {name: "overrides_presign_success", fn: integration.HeadObject_overrides_presign_success}, + {name: "overrides_success", fn: integration.HeadObject_overrides_success}, + {name: "range_and_part_number", fn: integration.HeadObject_range_and_part_number}, + {name: "by_range_resp_status", fn: integration.HeadObject_by_range_resp_status}, + {name: "dir_with_range", fn: integration.HeadObject_dir_with_range}, + {name: "ranged_with_checksum_mode", fn: integration.HeadObject_ranged_with_checksum_mode}, + {name: "with_range", fn: integration.HeadObject_with_range}, + {name: "zero_len_with_range", fn: integration.HeadObject_zero_len_with_range}, +} + +var headObjectXFail = []forgeCase{ + {name: "overrides_fail_public", fn: integration.HeadObject_overrides_fail_public}, + {name: "success", fn: integration.HeadObject_success}, +} + +var deleteObjectPass = []forgeCase{ + {name: "conditional_writes", fn: integration.DeleteObject_conditional_writes}, + {name: "directory_object", fn: integration.DeleteObject_directory_object}, + {name: "directory_object_noslash", fn: integration.DeleteObject_directory_object_noslash}, + {name: "expected_bucket_owner", fn: integration.DeleteObject_expected_bucket_owner}, + {name: "incorrect_expected_bucket_owner", fn: integration.DeleteObject_incorrect_expected_bucket_owner}, + {name: "non_empty_dir_obj", fn: integration.DeleteObject_non_empty_dir_obj}, + {name: "non_existing_dir_object", fn: integration.DeleteObject_non_existing_dir_object}, + {name: "non_existing_object", fn: integration.DeleteObject_non_existing_object}, + {name: "success", fn: integration.DeleteObject_success}, + {name: "success_status_code", fn: integration.DeleteObject_success_status_code}, +} + +// CopyObject covers the full upstream group (31 cases). The in-memory suite +// only partitioned 16 of them; the remainder were partitioned empirically +// against the forge stack when this table was created. The posix-only +// CopyObject_overwrite_same_file_object (not part of the upstream CopyObject +// group dispatch) is deliberately absent. +var copyObjectPass = []forgeCase{ + {name: "success", fn: integration.CopyObject_success}, + {name: "copy_to_itself", fn: integration.CopyObject_copy_to_itself}, + {name: "copy_to_itself_invalid_directive", fn: integration.CopyObject_copy_to_itself_invalid_directive}, + {name: "copy_source_starting_with_slash", fn: integration.CopyObject_copy_source_starting_with_slash}, + {name: "default_content_type_with_replace_metadata", fn: integration.CopyObject_default_content_type_with_replace_metadata}, + {name: "invalid_copy_source", fn: integration.CopyObject_invalid_copy_source}, + {name: "non_existing_dst_bucket", fn: integration.CopyObject_non_existing_dst_bucket}, + {name: "non_existing_dir_object", fn: integration.CopyObject_non_existing_dir_object}, + {name: "with_metadata", fn: integration.CopyObject_with_metadata}, + {name: "to_itself_with_new_metadata", fn: integration.CopyObject_to_itself_with_new_metadata}, + {name: "conditional_reads", fn: integration.CopyObject_conditional_reads}, + {name: "with_special_characters", fn: integration.CopyObject_with_special_characters}, + {name: "long_metadata", fn: integration.CopyObject_long_metadata}, + {name: "should_copy_meta_props", fn: integration.CopyObject_should_copy_meta_props}, + {name: "should_replace_meta_props", fn: integration.CopyObject_should_replace_meta_props}, + {name: "invalid_tagging_directive", fn: integration.CopyObject_invalid_tagging_directive}, + {name: "invalid_legal_hold", fn: integration.CopyObject_invalid_legal_hold}, + {name: "invalid_object_lock_mode", fn: integration.CopyObject_invalid_object_lock_mode}, + {name: "invalid_checksum_algorithm", fn: integration.CopyObject_invalid_checksum_algorithm}, +} + +// Observed failing against the forge stack: multi-account semantics, tagging, +// object-lock, and checksum-on-copy are unimplemented surface. +var copyObjectXFail = []forgeCase{ + {name: "not_owned_source_bucket", fn: integration.CopyObject_not_owned_source_bucket}, + {name: "should_replace_tagging", fn: integration.CopyObject_should_replace_tagging}, + {name: "should_copy_tagging", fn: integration.CopyObject_should_copy_tagging}, + {name: "missing_bucket_lock", fn: integration.CopyObject_missing_bucket_lock}, + {name: "with_legal_hold", fn: integration.CopyObject_with_legal_hold}, + {name: "with_retention_lock", fn: integration.CopyObject_with_retention_lock}, + {name: "object_acl_not_supported", fn: integration.CopyObject_object_acl_not_supported}, + {name: "create_checksum_on_copy", fn: integration.CopyObject_create_checksum_on_copy}, + {name: "should_copy_the_existing_checksum", fn: integration.CopyObject_should_copy_the_existing_checksum}, + {name: "should_replace_the_existing_checksum", fn: integration.CopyObject_should_replace_the_existing_checksum}, + {name: "to_itself_by_replacing_the_checksum", fn: integration.CopyObject_to_itself_by_replacing_the_checksum}, + {name: "incorrect_source_bucket_expected_owner", fn: integration.CopyObject_incorrect_source_bucket_expected_owner}, +} + +var deleteObjectsPass = []forgeCase{ + {name: "success", fn: integration.DeleteObjects_success}, + {name: "empty_input", fn: integration.DeleteObjects_empty_input}, + {name: "non_existing_objects", fn: integration.DeleteObjects_non_existing_objects}, +} diff --git a/itest/versity_test.go b/itest/versity_test.go new file mode 100644 index 0000000..85a94c4 --- /dev/null +++ b/itest/versity_test.go @@ -0,0 +1,106 @@ +//go:build itest + +package itest + +import ( + "testing" + + "github.com/versity/versitygw/tests/integration" +) + +// forgeCase pairs an upstream versitygw integration case with its subtest +// name, so each category table below renders one runnable row per case. +// skip, when non-nil and returning a non-empty reason, skips the case — +// used for the order-sensitive ListBuckets_truncated (under -shuffle) and +// the load-sensitive CompleteMultipartUpload racey cases. +type forgeCase struct { + name string + fn integration.IntTest + skip func() string +} + +// TestForgeVersity is the S3 conformance partition, run against a forge-mode +// ingot in the smelt stack (this working tree's binary). One stack boot is +// shared by every category — the upstream cases create and tear down their +// own buckets, so categories don't interfere — and the stack lives until this +// test (including all subtests) completes. +// +// Layout mirrors the curated format: per upstream group, a subtest of +// known-passing cases (every case must pass) and a XFail subtest of +// cases ingot fails today (each expected to fail and reported as SKIP; an +// unexpected pass errors so the case can be promoted). +// +// Promoting a case: when a fix lands, run the matching XFail subtest. Cases +// that flip green report "case unexpectedly passed" — move the line from the +// xfail table to the pass table. +// +// Run one category or one case: +// +// go test -tags itest ./itest -run 'TestForgeVersity/PutObject' -v +// go test -tags itest ./itest -run 'TestForgeVersity/PutObject/success' -v +func TestForgeVersity(t *testing.T) { + s, endpoint := forgeStack(t) + ingotSelfProvision(t, t.Context(), s, "test@example.com") + conf := forgeS3Conf(endpoint) + + categories := []struct { + name string + pass []forgeCase + xfail []forgeCase + }{ + // ListBuckets runs FIRST: its empty_success/success cases assert the + // exact bucket listing, which only holds on the pristine stack — + // failed cases in other categories leak their buckets (upstream + // teardown doesn't run on error), and unlike the old per-group + // in-memory harnesses, all categories share this one deployment. + {"ListBuckets", listBucketsPass, listBucketsXFail}, + {"CreateBucket", createBucketPass, createBucketXFail}, + {"HeadBucket", headBucketPass, nil}, + {"DeleteBucket", deleteBucketPass, nil}, + {"PutObject", putObjectPass, putObjectXFail}, + {"GetObject", getObjectPass, getObjectXFail}, + {"HeadObject", headObjectPass, headObjectXFail}, + {"DeleteObject", deleteObjectPass, nil}, + {"CopyObject", copyObjectPass, copyObjectXFail}, + {"DeleteObjects", deleteObjectsPass, nil}, + {"CreateMultipartUpload", createMultipartPass, createMultipartXFail}, + {"UploadPart", uploadPartPass, uploadPartXFail}, + {"UploadPartCopy", uploadPartCopyPass, uploadPartCopyXFail}, + {"ListParts", listPartsPass, listPartsXFail}, + {"ListMultipartUploads", listMultipartUploadsPass, listMultipartUploadsXFail}, + {"AbortMultipartUpload", abortMultipartPass, abortMultipartXFail}, + {"CompleteMultipartUpload", completeMultipartPass, completeMultipartXFail}, + } + + for _, cat := range categories { + t.Run(cat.name, func(t *testing.T) { + for _, tt := range cat.pass { + t.Run(tt.name, func(t *testing.T) { + if tt.skip != nil { + if reason := tt.skip(); reason != "" { + t.Skip(reason) + } + } + if err := tt.fn(conf); err != nil { + t.Fatalf("%v", err) + } + }) + } + }) + if cat.xfail == nil { + continue + } + t.Run(cat.name+"XFail", func(t *testing.T) { + for _, tt := range cat.xfail { + t.Run(tt.name, func(t *testing.T) { + err := tt.fn(conf) + if err == nil { + t.Errorf("case unexpectedly passed; promote it from the %s xfail table to the pass table", cat.name) + return + } + t.Skipf("known-failing: %v", err) + }) + } + }) + } +} diff --git a/testing/blobsplit_test.go b/testing/blobsplit_test.go deleted file mode 100644 index 90752bf..0000000 --- a/testing/blobsplit_test.go +++ /dev/null @@ -1,215 +0,0 @@ -package testing_test - -import ( - "bytes" - "context" - "crypto/md5" - "encoding/hex" - "fmt" - "io" - "os" - "path/filepath" - "strings" - "testing" - "time" - - "github.com/aws/aws-sdk-go-v2/aws" - "github.com/aws/aws-sdk-go-v2/service/s3" - "github.com/versity/versitygw/tests/integration" - "go.uber.org/zap/zaptest" - - mstesting "github.com/fil-forge/ingot/testing" -) - -// blobClient builds an S3 client pointed at the harness — the same -// construction the upstream integration suite uses (path-style, static -// creds). -func blobClient(h *mstesting.Harness) *s3.Client { - return integration.NewS3Conf( - integration.WithEndpoint(h.Endpoint), - integration.WithAccess(h.AccessKey), - integration.WithSecret(h.SecretKey), - integration.WithRegion(h.Region), - ).GetClient() -} - -func quotedMD5(b []byte) string { - sum := md5.Sum(b) - return `"` + hex.EncodeToString(sum[:]) + `"` -} - -// patternBytes returns n deterministic bytes — a cheap way to make a payload -// whose every byte position is verifiable after a round trip. The (i>>16) term -// mixes in the 64 KiB-blob index so each coarse-split blob has distinct content -// (a purely position-linear pattern would make equal-length blobs identical and -// dedup them in the spool). -func patternBytes(n int) []byte { - b := make([]byte, n) - for i := range b { - b[i] = byte(i*31 + 7 + (i>>16)*101) - } - return b -} - -// TestBlobSplit_MultiBlobRoundTrip drives a PUT of an object several -// times larger than max_blob_size so its body is coarsely split into -// multiple BlobRefs, then verifies the whole-object GET, ranged GETs -// (one spanning a blob boundary, one wholly inside a later blob), and -// the md5 ETag all reconstruct the exact bytes. -func TestBlobSplit_MultiBlobRoundTrip(t *testing.T) { - ctx, cancel := context.WithTimeout(t.Context(), 30*time.Second) - defer cancel() - - const maxBlob = 64 << 10 // 64 KiB blob ceiling - h, err := mstesting.StartHarness(ctx, - mstesting.WithLogger(zaptest.NewLogger(t)), - mstesting.WithMaxBlobSize(maxBlob), - ) - if err != nil { - t.Fatalf("StartHarness: %v", err) - } - t.Cleanup(func() { _ = h.Stop(context.Background()) }) - - cl := blobClient(h) - const bucket = "blob-split" - if _, err := cl.CreateBucket(ctx, &s3.CreateBucketInput{Bucket: aws.String(bucket)}); err != nil { - t.Fatalf("CreateBucket: %v", err) - } - - // 3.5 blobs: blob0..blob2 are full (64 KiB), blob3 is a half blob. - const size = 3*maxBlob + maxBlob/2 - data := patternBytes(size) - - put, err := cl.PutObject(ctx, &s3.PutObjectInput{ - Bucket: aws.String(bucket), - Key: aws.String("big"), - Body: bytes.NewReader(data), - }) - if err != nil { - t.Fatalf("PutObject: %v", err) - } - if want := quotedMD5(data); aws.ToString(put.ETag) != want { - t.Fatalf("PUT ETag = %s, want %s", aws.ToString(put.ETag), want) - } - - // The bodies must be spooled to local disk by digest (the data-plane - // inversion), not journaled into the log. A 3.5-blob object spools 4 files. - if got := spoolBlobCount(t, h); got != 4 { - t.Fatalf("spool holds %d blobs, want 4 — bodies must be spooled, not logged", got) - } - - // Whole-object GET. - if got := getBody(t, ctx, cl, bucket, "big", ""); !bytes.Equal(got, data) { - t.Fatalf("whole GET mismatch: got %d bytes, want %d", len(got), len(data)) - } - - // Ranged GET spanning the blob0→blob1 boundary. - rangeCases := []struct{ start, end int }{ - {maxBlob - 10, maxBlob + 2000}, // crosses a blob boundary - {maxBlob + 5, maxBlob + 105}, // wholly inside blob1, non-zero in-blob offset - {size - 1, size - 1}, // final byte only - {0, 0}, // first byte only - } - for _, rc := range rangeCases { - hdr := fmt.Sprintf("bytes=%d-%d", rc.start, rc.end) - got := getBody(t, ctx, cl, bucket, "big", hdr) - want := data[rc.start : rc.end+1] - if !bytes.Equal(got, want) { - t.Fatalf("range %s mismatch: got %d bytes, want %d", hdr, len(got), len(want)) - } - } - - // HEAD reflects the full size and the same ETag. - head, err := cl.HeadObject(ctx, &s3.HeadObjectInput{Bucket: aws.String(bucket), Key: aws.String("big")}) - if err != nil { - t.Fatalf("HeadObject: %v", err) - } - if aws.ToInt64(head.ContentLength) != size { - t.Fatalf("HEAD ContentLength = %d, want %d", aws.ToInt64(head.ContentLength), size) - } - if want := quotedMD5(data); aws.ToString(head.ETag) != want { - t.Fatalf("HEAD ETag = %s, want %s", aws.ToString(head.ETag), want) - } -} - -// TestBlobSplit_ZeroByteObject verifies a 0-byte object stores no blob, -// round-trips empty, and carries the well-known empty-content md5 ETag. -func TestBlobSplit_ZeroByteObject(t *testing.T) { - ctx, cancel := context.WithTimeout(t.Context(), 30*time.Second) - defer cancel() - - h, err := mstesting.StartHarness(ctx, mstesting.WithLogger(zaptest.NewLogger(t))) - if err != nil { - t.Fatalf("StartHarness: %v", err) - } - t.Cleanup(func() { _ = h.Stop(context.Background()) }) - - cl := blobClient(h) - const bucket = "zero-byte" - if _, err := cl.CreateBucket(ctx, &s3.CreateBucketInput{Bucket: aws.String(bucket)}); err != nil { - t.Fatalf("CreateBucket: %v", err) - } - - const emptyMD5 = `"d41d8cd98f00b204e9800998ecf8427e"` - put, err := cl.PutObject(ctx, &s3.PutObjectInput{ - Bucket: aws.String(bucket), - Key: aws.String("empty"), - Body: bytes.NewReader(nil), - }) - if err != nil { - t.Fatalf("PutObject: %v", err) - } - if aws.ToString(put.ETag) != emptyMD5 { - t.Fatalf("PUT ETag = %s, want %s", aws.ToString(put.ETag), emptyMD5) - } - - if got := getBody(t, ctx, cl, bucket, "empty", ""); len(got) != 0 { - t.Fatalf("zero-byte GET returned %d bytes, want 0", len(got)) - } - - head, err := cl.HeadObject(ctx, &s3.HeadObjectInput{Bucket: aws.String(bucket), Key: aws.String("empty")}) - if err != nil { - t.Fatalf("HeadObject: %v", err) - } - if aws.ToInt64(head.ContentLength) != 0 { - t.Fatalf("HEAD ContentLength = %d, want 0", aws.ToInt64(head.ContentLength)) - } - if aws.ToString(head.ETag) != emptyMD5 { - t.Fatalf("HEAD ETag = %s, want %s", aws.ToString(head.ETag), emptyMD5) - } -} - -// spoolBlobCount counts the body blobs on disk in the harness spool, ignoring -// in-progress temp files. Used to prove object bodies are spooled by digest. -func spoolBlobCount(t *testing.T, h *mstesting.Harness) int { - t.Helper() - entries, err := os.ReadDir(filepath.Join(h.DataDir(), "spool")) - if err != nil { - t.Fatalf("read spool dir: %v", err) - } - n := 0 - for _, e := range entries { - if !e.IsDir() && !strings.HasPrefix(e.Name(), ".tmp") { - n++ - } - } - return n -} - -func getBody(t *testing.T, ctx context.Context, cl *s3.Client, bucket, key, rangeHdr string) []byte { - t.Helper() - in := &s3.GetObjectInput{Bucket: aws.String(bucket), Key: aws.String(key)} - if rangeHdr != "" { - in.Range = aws.String(rangeHdr) - } - out, err := cl.GetObject(ctx, in) - if err != nil { - t.Fatalf("GetObject %s (range %q): %v", key, rangeHdr, err) - } - defer out.Body.Close() - b, err := io.ReadAll(out.Body) - if err != nil { - t.Fatalf("read body %s: %v", key, err) - } - return b -} diff --git a/testing/config.go b/testing/config.go new file mode 100644 index 0000000..cfd36d6 --- /dev/null +++ b/testing/config.go @@ -0,0 +1,54 @@ +package testing + +import ( + "github.com/versity/versitygw/tests/integration" +) + +// Config carries the connection details for a running ingot S3 listener. +// Callers own the server lifecycle; this package is only client-side glue. +type Config struct { + Endpoint string + AccessKey string + SecretKey string + + // Region must match the listener's configured region. Empty + // defaults to "us-east-1". + Region string + + // HostStyle uses host-style bucket addressing (bucket.host) + // instead of path-style (host/bucket). + HostStyle bool + + // VersioningEnabled tells the upstream suite the bucket-versioning + // feature is on; case functions branch on this flag. + VersioningEnabled bool + + // SkipTLSVerify accepts self-signed certs. + SkipTLSVerify bool +} + +// NewS3Conf builds the upstream versitygw *integration.S3Conf for the +// listener described by c. Individual upstream cases have signature +// func(*integration.S3Conf) error, so this is the bridge for invoking them +// directly — see ingot's itest package for the curated conformance +// partition that does exactly that. +func NewS3Conf(c Config) *integration.S3Conf { + region := c.Region + if region == "" { + region = "us-east-1" + } + opts := []integration.Option{ + integration.WithEndpoint(c.Endpoint), + integration.WithAccess(c.AccessKey), + integration.WithSecret(c.SecretKey), + integration.WithRegion(region), + integration.WithTLSStatus(c.SkipTLSVerify), + } + if c.HostStyle { + opts = append(opts, integration.WithHostStyle()) + } + if c.VersioningEnabled { + opts = append(opts, integration.WithVersioningEnabled()) + } + return integration.NewS3Conf(opts...) +} diff --git a/testing/doc.go b/testing/doc.go new file mode 100644 index 0000000..9d4435a --- /dev/null +++ b/testing/doc.go @@ -0,0 +1,17 @@ +// Package testing is thin S3-client glue for tests that drive a running +// ingot listener. The listener itself is always provided by the caller — +// there is no in-process harness; the deployment under test is the real +// forge-mode daemon in the smelt stack (see the itest package). +// +// - config.go — Config (connection details) and NewS3Conf, the bridge to +// the upstream versitygw integration suite: upstream case functions have +// signature func(*integration.S3Conf) error and are invoked directly by +// itest's curated conformance partition. +// - roundtrip.go — CreateBucket/PutBytes/GetBytes: minimal single-object +// round-trip helpers for scenario tests (e.g. read-after-eviction). +// +// The S3 conformance partition — the curated lists of upstream cases ingot +// is expected to pass and known to fail — lives in itest/versity_*_test.go +// and runs against the forge-mode deployment: `make itest` (Docker). Unit +// tests (`make test`) cover libraries and pure logic only. +package testing diff --git a/testing/external_test.go b/testing/external_test.go deleted file mode 100644 index 31df260..0000000 --- a/testing/external_test.go +++ /dev/null @@ -1,92 +0,0 @@ -//go:build e2e - -package testing - -import ( - "context" - "os" - "testing" -) - -// TestExternalVersity runs the versitygw integration suite against an -// EXTERNAL, already-running ingot S3 endpoint (e.g. an ingot daemon -// deployed in the smelt Forge stack) rather than the in-process harness. -// -// Configure via env and run with the e2e build tag: -// -// INGOT_E2E_ENDPOINT=http://localhost:15110 \ -// go test -tags e2e ./testing -run TestExternalVersity -v -// -// Optional: INGOT_E2E_ACCESS, INGOT_E2E_SECRET, INGOT_E2E_REGION, -// INGOT_E2E_SUITE (smoke|crud|full; default smoke). -// -// This is the same versitygw Suite ingot's in-process smoke tests use, so -// a forge-mode ingot in smelt should report the same pass/xfail profile as -// the in-memory harness — the S3 semantics are backend-independent. -func TestExternalVersity(t *testing.T) { - endpoint := os.Getenv("INGOT_E2E_ENDPOINT") - if endpoint == "" { - t.Skip("INGOT_E2E_ENDPOINT not set; this test targets an external ingot S3 listener") - } - - cfg := Config{ - Endpoint: endpoint, - AccessKey: getenvDefault("INGOT_E2E_ACCESS", "ingot"), - SecretKey: getenvDefault("INGOT_E2E_SECRET", "ingotsecret"), - Region: getenvDefault("INGOT_E2E_REGION", "us-east-1"), - } - - suiteName := getenvDefault("INGOT_E2E_SUITE", "smoke") - suite, ok := externalSuites[suiteName] - if !ok { - t.Fatalf("unknown INGOT_E2E_SUITE %q (want smoke|crud|full)", suiteName) - } - - res := Run(context.Background(), cfg, suite) - t.Logf("versitygw %q suite vs %s: ran=%d passed=%d failed=%d", suiteName, endpoint, res.Ran, res.Passed, res.Failed) - - // We don't assert Failed==0: ingot is not yet fully S3-conformant (the - // known xfail set). The meaningful signal is that the suite actually - // exercised the external listener and the conformant cases pass. - if res.Ran == 0 { - t.Fatalf("no versitygw cases ran against %s — is the endpoint reachable?", endpoint) - } - if res.Passed == 0 { - t.Fatalf("no versitygw cases passed against %s", endpoint) - } -} - -// TestInProcessVersityBaseline runs the SAME suite against a fresh -// in-process harness, so the external/smelt result can be compared to a -// clean-ingot baseline (the forge-mode S3 semantics should match the -// in-memory ones exactly). -func TestInProcessVersityBaseline(t *testing.T) { - h, err := StartHarness(context.Background()) - if err != nil { - t.Fatalf("start harness: %v", err) - } - t.Cleanup(func() { _ = h.Stop(context.Background()) }) - - suiteName := getenvDefault("INGOT_E2E_SUITE", "smoke") - suite := externalSuites[suiteName] - res := Run(context.Background(), Config{ - Endpoint: h.Endpoint, - AccessKey: h.AccessKey, - SecretKey: h.SecretKey, - Region: h.Region, - }, suite) - t.Logf("IN-PROCESS %q baseline: ran=%d passed=%d failed=%d", suiteName, res.Ran, res.Passed, res.Failed) -} - -var externalSuites = map[string]Suite{ - "smoke": Smoke, - "crud": CRUD, - "full": Full, -} - -func getenvDefault(key, def string) string { - if v := os.Getenv(key); v != "" { - return v - } - return def -} diff --git a/testing/harness.go b/testing/harness.go deleted file mode 100644 index 6a97339..0000000 --- a/testing/harness.go +++ /dev/null @@ -1,290 +0,0 @@ -package testing - -import ( - "context" - "fmt" - "net" - "os" - "time" - - "go.uber.org/fx" - "go.uber.org/fx/fxevent" - "go.uber.org/zap" - - "github.com/fil-forge/ingot" - "github.com/fil-forge/ingot/blockstore" - "github.com/fil-forge/ingot/inmem" - "github.com/fil-forge/ingot/logstore" - "github.com/fil-forge/ingot/registry" - "github.com/fil-forge/ingot/uploader" -) - -// DefaultAccessKey / DefaultSecretKey are the sigv4 credentials a -// freshly started Harness uses unless overridden via WithCredentials. -// They are not secrets — the harness binds to 127.0.0.1 only. -const ( - DefaultAccessKey = "ingot-test-access" - DefaultSecretKey = "ingot-test-secret" -) - -// Harness is an in-process ingot S3 listener backed by in-memory deps. -// No Postgres, no piri, no indexer: a sealed segment's flush is a -// no-op that just advances bookkeeping. Sufficient for driving the -// upstream versitygw integration suite against the listener via -// Run + Suite. -// -// The server is constructed through ingot's exported fx ServerModule — -// the same wiring production uses — with the in-memory fakes supplied -// in place of the Postgres registry / Forge reader / Forge uploader. -type Harness struct { - Endpoint string - AccessKey string - SecretKey string - Region string - - app *fx.App - dataDir string -} - -// HarnessOption customizes StartHarness. Each option mutates a -// HarnessOptions value in place. -type HarnessOption func(*harnessOptions) - -type harnessOptions struct { - logger *zap.Logger - region string - accessKey string - secretKey string - maxBlobSize int64 - sealBytes int64 - sealAge time.Duration - retain int - readyAfter time.Duration -} - -// WithLogger sets the zap logger supplied to the ingot fx module (and -// used for fx's own lifecycle logging). Default nop. -func WithLogger(l *zap.Logger) HarnessOption { - return func(o *harnessOptions) { o.logger = l } -} - -// WithRegion overrides the default "us-east-1" sigv4 region. -func WithRegion(r string) HarnessOption { - return func(o *harnessOptions) { o.region = r } -} - -// WithCredentials overrides DefaultAccessKey / DefaultSecretKey. -func WithCredentials(access, secret string) HarnessOption { - return func(o *harnessOptions) { - o.accessKey = access - o.secretKey = secret - } -} - -// WithMaxBlobSize overrides the per-object blob ceiling. Tests that -// exercise multi-blob (coarse-split) objects set a small value so a -// modest payload spans several blobs. 0 means use bucket.DefaultMaxBlobSize. -func WithMaxBlobSize(n int64) HarnessOption { - return func(o *harnessOptions) { o.maxBlobSize = n } -} - -// WithSealConfig forwards SealBytes / SealAge / Retain to logstore. -// Tests that exercise seal-on-size or seal-on-age behavior use this; -// the default leaves all three zero so logstore picks its own -// defaults. -func WithSealConfig(sealBytes int64, sealAge time.Duration, retain int) HarnessOption { - return func(o *harnessOptions) { - o.sealBytes = sealBytes - o.sealAge = sealAge - o.retain = retain - } -} - -// WithReadyTimeout caps how long StartHarness will dial the listener -// before giving up. Default 5 s. -func WithReadyTimeout(d time.Duration) HarnessOption { - return func(o *harnessOptions) { o.readyAfter = d } -} - -// StartHarness stands up an in-process ingot listener — built through the -// exported ingot.ServerModule with in-memory fakes — bound to a random -// 127.0.0.1 port, and waits for it to accept TCP connections. The caller -// must call Stop to drain the log and remove scratch state. -func StartHarness(ctx context.Context, opts ...HarnessOption) (*Harness, error) { - options := harnessOptions{ - logger: zap.NewNop(), - region: "us-east-1", - accessKey: DefaultAccessKey, - secretKey: DefaultSecretKey, - readyAfter: 5 * time.Second, - } - for _, o := range opts { - o(&options) - } - - addr, err := pickFreeAddr() - if err != nil { - return nil, fmt.Errorf("ingot harness: pick port: %w", err) - } - - dataDir, err := os.MkdirTemp("", "ingot-harness-") - if err != nil { - return nil, fmt.Errorf("ingot harness: tempdir: %w", err) - } - - mem := inmem.NewMemStore() - - // Build the server through ingot's exported ServerModule, exactly as a - // production host would, but supplying the in-memory fakes for the four - // collaborator interfaces instead of the Postgres registry / Forge - // reader / Forge uploader. No PreStartHooks are registered (there are no - // migrations to run against the in-memory store). - app := fx.New( - // Route fx's own lifecycle logging through the supplied zap logger - // (the test logger) instead of fx's default stderr writer. - fx.WithLogger(func() fxevent.Logger { return &fxevent.ZapLogger{Logger: options.logger} }), - fx.Supply(options.logger), - fx.Supply(ingot.ServerConfig{ - Addr: addr, - DataDir: dataDir, - Region: options.region, - RootAccess: options.accessKey, - RootSecret: options.secretKey, - MaxBlobSize: options.maxBlobSize, - // Ship the catalog plane to the nop uploader so the seal → ship → - // retire path stays exercised by the in-memory suite. - SealBytesCatalog: options.sealBytes, - SealAgeCatalog: options.sealAge, - ShipCatalog: true, - RetainCatalog: options.retain, - }), - // MemStore satisfies registry.Registry, registry.IntentStore, and - // logstore.Meta; NopUploader satisfies both upload seams. Expose each - // under every interface the module consumes. - fx.Provide( - fx.Annotate(func() *inmem.MemStore { return mem }, fx.As(new(registry.Registry))), - fx.Annotate(func() *inmem.MemStore { return mem }, fx.As(new(registry.IntentStore))), - fx.Annotate(func() *inmem.MemStore { return mem }, fx.As(new(registry.LocationStore))), - fx.Annotate(func() *inmem.MemStore { return mem }, fx.As(new(registry.BlobRefStore))), - fx.Annotate(func() *inmem.MemStore { return mem }, fx.As(new(registry.GCStore))), - fx.Annotate(func() *inmem.MemStore { return mem }, fx.As(new(registry.MultipartStore))), - fx.Annotate(func() *inmem.MemStore { return mem }, fx.As(new(logstore.Meta))), - fx.Annotate(func() inmem.NopBaseReader { return inmem.NopBaseReader{} }, fx.As(new(blockstore.BlockReader))), - fx.Annotate(func() inmem.NopUploader { return inmem.NopUploader{} }, fx.As(new(uploader.Uploader))), - fx.Annotate(func() inmem.NopUploader { return inmem.NopUploader{} }, fx.As(new(uploader.BodyUploader))), - fx.Annotate(func() inmem.NopUploader { return inmem.NopUploader{} }, fx.As(new(uploader.BlobRemover))), - ), - ingot.ServerModule, - ) - if err := app.Err(); err != nil { - _ = os.RemoveAll(dataDir) - return nil, fmt.Errorf("ingot harness: build app: %w", err) - } - - if err := app.Start(ctx); err != nil { - _ = os.RemoveAll(dataDir) - return nil, fmt.Errorf("ingot harness: start app: %w", err) - } - - if err := waitListening(ctx, addr, options.readyAfter); err != nil { - _ = app.Stop(ctx) - _ = os.RemoveAll(dataDir) - return nil, fmt.Errorf("ingot harness: %w", err) - } - - return &Harness{ - Endpoint: "http://" + addr, - AccessKey: options.accessKey, - SecretKey: options.secretKey, - Region: options.region, - app: app, - dataDir: dataDir, - }, nil -} - -// Stop stops the fx app (which shuts the listener down and drains the -// log via the module's OnStop hook) and removes the scratch data -// directory. Safe to call once; subsequent calls no-op. Errors from -// each step are joined. -func (h *Harness) Stop(ctx context.Context) error { - var errs []error - if h.app != nil { - // Shutdown must run even when the caller's context is already done — - // e.g. a t.Cleanup gets a t.Context() that is canceled just before - // cleanups run, and fx.App.Stop honors a canceled context by refusing - // to run OnStop. Detach from the parent's cancellation but keep a - // bound so a wedged drain can't hang the test. - stopCtx, cancel := context.WithTimeout(context.WithoutCancel(ctx), 30*time.Second) - if err := h.app.Stop(stopCtx); err != nil { - errs = append(errs, err) - } - cancel() - h.app = nil - } - if h.dataDir != "" { - if err := os.RemoveAll(h.dataDir); err != nil { - errs = append(errs, fmt.Errorf("remove dataDir: %w", err)) - } - h.dataDir = "" - } - if len(errs) > 0 { - return fmt.Errorf("ingot harness stop: %v", errs) - } - return nil -} - -// DataDir returns the harness's scratch data directory (where the spool and -// log segments live). Exposed so tests can assert on-disk layout — e.g. that -// object bodies are spooled rather than journaled into the log. -func (h *Harness) DataDir() string { return h.dataDir } - -// Config returns a Config wired against the harness's listener, -// suitable for passing to Run. -func (h *Harness) Config() Config { - return Config{ - Endpoint: h.Endpoint, - AccessKey: h.AccessKey, - SecretKey: h.SecretKey, - Region: h.Region, - } -} - -// pickFreeAddr asks the kernel for a free 127.0.0.1 port by binding -// and immediately closing. There is a small race window between -// close and ingot's rebind, but for serial unit tests it is -// effectively zero. -func pickFreeAddr() (string, error) { - l, err := net.Listen("tcp", "127.0.0.1:0") - if err != nil { - return "", err - } - addr := l.Addr().String() - if err := l.Close(); err != nil { - return "", err - } - return addr, nil -} - -// waitListening polls TCP connect to addr until it succeeds, ctx -// is canceled, or the timeout fires. -func waitListening(ctx context.Context, addr string, timeout time.Duration) error { - deadline := time.Now().Add(timeout) - var d net.Dialer - for { - if !time.Now().Before(deadline) { - return fmt.Errorf("listener not ready at %s after %s", addr, timeout) - } - dialCtx, cancel := context.WithTimeout(ctx, 100*time.Millisecond) - conn, err := d.DialContext(dialCtx, "tcp", addr) - cancel() - if err == nil { - _ = conn.Close() - return nil - } - select { - case <-ctx.Done(): - return ctx.Err() - case <-time.After(20 * time.Millisecond): - } - } -} diff --git a/testing/harness_test.go b/testing/harness_test.go deleted file mode 100644 index 6aac09b..0000000 --- a/testing/harness_test.go +++ /dev/null @@ -1,52 +0,0 @@ -package testing_test - -import ( - "context" - "net/http" - "strings" - "testing" - "time" - - "go.uber.org/zap/zaptest" - - mstesting "github.com/fil-forge/ingot/testing" -) - -func TestHarnessLifecycle(t *testing.T) { - ctx, cancel := context.WithTimeout(t.Context(), 10*time.Second) - defer cancel() - - h, err := mstesting.StartHarness(ctx, mstesting.WithLogger(zaptest.NewLogger(t))) - if err != nil { - t.Fatalf("StartHarness: %v", err) - } - t.Cleanup(func() { - if err := h.Stop(t.Context()); err != nil { - t.Errorf("Stop: %v", err) - } - }) - - if !strings.HasPrefix(h.Endpoint, "http://127.0.0.1:") { - t.Fatalf("unexpected endpoint %q", h.Endpoint) - } - - // /health is wired in buildS3API; hit it to confirm the listener - // is actually serving HTTP, not just accepting TCP. - req, err := http.NewRequestWithContext(ctx, http.MethodGet, h.Endpoint+"/health", nil) - if err != nil { - t.Fatalf("NewRequest: %v", err) - } - resp, err := http.DefaultClient.Do(req) - if err != nil { - t.Fatalf("GET /health: %v", err) - } - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - t.Fatalf("/health status = %d, want 200", resp.StatusCode) - } - - cfg := h.Config() - if cfg.Endpoint != h.Endpoint || cfg.AccessKey != h.AccessKey || cfg.SecretKey != h.SecretKey { - t.Fatalf("Config() mismatch: %+v vs %+v", cfg, h) - } -} diff --git a/testing/integration.go b/testing/integration.go deleted file mode 100644 index ee39ccb..0000000 --- a/testing/integration.go +++ /dev/null @@ -1,295 +0,0 @@ -// Package testing wires the upstream versitygw integration suite -// (github.com/versity/versitygw/tests/integration) against a running -// ingot S3 listener. Callers own server lifecycle and pass connection -// details in via Config; this package only selects which upstream -// group functions to run. Each group prints its own per-test results -// to stdout; Run additionally returns a Result summarizing how many -// cases passed and failed so Go tests can fail a *testing.T when the -// suite reports any failures (see RunT). -package testing - -import ( - "bytes" - "context" - "fmt" - "io" - "os" - "regexp" - "strings" - "sync" - "testing" - - "github.com/versity/versitygw/tests/integration" -) - -// Config addresses the ingot S3 listener under test. -type Config struct { - Endpoint string - AccessKey string - SecretKey string - - // Region must match the listener's configured region. Empty - // defaults to "us-east-1". - Region string - - // Parallel queues each Run-mode test on TestState's worker - // pool instead of running serially. Sync-mode tests still - // run after the parallel batch drains. - Parallel bool - - // HostStyle uses host-style bucket addressing (bucket.host) - // instead of path-style (host/bucket). - HostStyle bool - - // VersioningEnabled tells the suite the bucket-versioning - // feature is on; group functions branch on this flag. - VersioningEnabled bool - - // SkipTLSVerify accepts self-signed certs. - SkipTLSVerify bool -} - -// Suite is an ordered list of upstream group functions. Each takes a -// *integration.TestState and dispatches its individual tests via -// ts.Run / ts.Sync. Compose ad-hoc suites by listing -// integration.TestXxx values directly: -// -// testing.Run(ctx, cfg, testing.Suite{ -// integration.TestCreateBucket, -// integration.TestPutObject, -// }) -type Suite []func(*integration.TestState) - -// Result summarizes a single suite Run. Counts are deltas — the -// versitygw counters are package-level atomics shared across every -// caller in the process, so Run snapshots them on entry and reports -// the difference. -type Result struct { - // Ran is the number of individual case functions that started. - Ran uint32 - // Passed is the number that ended in passF. - Passed uint32 - // Failed is the number that ended in failF. - Failed uint32 - - // PassedCases / FailedCases name the individual cases by their versitygw - // test name (e.g. "PutObject_conditional_writes"), recovered by capturing the - // suite's stdout — the upstream counters expose only totals. They let a caller - // gate per-case (e.g. assert the forge-mode failures are a subset of the - // in-process baseline's, catching forge-specific regressions a count - // comparison would miss). Empty if stdout capture failed. - PassedCases []string - FailedCases []string -} - -// Err returns a non-nil error if any case failed. Use this when the -// caller is not a *testing.T (e.g., a CLI runner). For Go tests, -// prefer RunT which propagates failures into t.Errorf directly. -func (r Result) Err() error { - if r.Failed > 0 { - return fmt.Errorf("integration suite: %d of %d cases failed", r.Failed, r.Ran) - } - return nil -} - -// runMu serializes concurrent Run calls so the global versitygw -// counters can be sampled before/after one Run without interleaving -// with another. Two parallel Run calls in the same process would -// otherwise contaminate each other's deltas. -var runMu sync.Mutex - -// Run drives suite against a fresh TestState bound to ctx and c. -// Blocks until queued (Run-mode) and deferred (Sync-mode) tests -// complete, then returns this run's case counts. -func Run(ctx context.Context, c Config, suite Suite) Result { - runMu.Lock() - defer runMu.Unlock() - - ranBefore := integration.RunCount.Load() - passedBefore := integration.PassCount.Load() - failedBefore := integration.FailCount.Load() - - // The upstream group functions print each case's RUN/PASS/FAIL line to - // os.Stdout and bump package atomics — they expose no structured per-case - // result. Tee os.Stdout through a pipe for the duration of the run so we can - // recover the case names (live output is preserved via the MultiWriter). - // runMu already serializes Run calls, so the global os.Stdout swap is safe. - var captured bytes.Buffer - orig := os.Stdout - pr, pw, pipeErr := os.Pipe() - copyDone := make(chan struct{}) - if pipeErr == nil { - os.Stdout = pw - go func() { - _, _ = io.Copy(io.MultiWriter(orig, &captured), pr) - close(copyDone) - }() - } - - ts := integration.NewTestState(ctx, newS3Conf(c), c.Parallel) - for _, group := range suite { - group(ts) - } - ts.Wait() - - if pipeErr == nil { - os.Stdout = orig - _ = pw.Close() - <-copyDone - _ = pr.Close() - } - - passed, failed := parseCaseLines(captured.String()) - return Result{ - Ran: integration.RunCount.Load() - ranBefore, - Passed: integration.PassCount.Load() - passedBefore, - Failed: integration.FailCount.Load() - failedBefore, - PassedCases: passed, - FailedCases: failed, - } -} - -var ansiRe = regexp.MustCompile(`\x1b\[[0-9;]*m`) - -// parseCaseLines recovers per-case names from captured suite output. The upstream -// printers emit "PASS " and "FAIL : " (wrapped in ANSI color); -// the name is the token up to the first colon. Teardown-failure lines lack the -// "FAIL " prefix and are ignored (they don't increment the fail counter). -func parseCaseLines(output string) (passed, failed []string) { - for _, line := range strings.Split(output, "\n") { - line = strings.TrimSpace(ansiRe.ReplaceAllString(line, "")) - switch { - case strings.HasPrefix(line, "FAIL "): - failed = append(failed, caseName(line[len("FAIL "):])) - case strings.HasPrefix(line, "PASS "): - passed = append(passed, caseName(line[len("PASS "):])) - } - } - return passed, failed -} - -func caseName(s string) string { - if i := strings.IndexByte(s, ':'); i >= 0 { - s = s[:i] - } - return strings.TrimSpace(s) -} - -// RunT is the Go-test-friendly form of Run. On any failure the -// returned Result is also reported via t.Errorf so `go test` exits -// non-zero. The per-case FAIL lines printed by versitygw are -// captured in t's log output, so the test author sees exactly which -// cases failed without RunT having to summarize them. -func RunT(t *testing.T, c Config, suite Suite) Result { - t.Helper() - r := Run(t.Context(), c, suite) - if r.Failed > 0 { - t.Errorf("integration suite: %d of %d cases failed (see test output for per-case details)", r.Failed, r.Ran) - } - return r -} - -func newS3Conf(c Config) *integration.S3Conf { - region := c.Region - if region == "" { - region = "us-east-1" - } - opts := []integration.Option{ - integration.WithEndpoint(c.Endpoint), - integration.WithAccess(c.AccessKey), - integration.WithSecret(c.SecretKey), - integration.WithRegion(region), - integration.WithTLSStatus(c.SkipTLSVerify), - } - if c.HostStyle { - opts = append(opts, integration.WithHostStyle()) - } - if c.VersioningEnabled { - opts = append(opts, integration.WithVersioningEnabled()) - } - return integration.NewS3Conf(opts...) -} - -// Smoke is the minimum subset that should pass on a working listener: -// bucket lifecycle plus single-object CRUD. -var Smoke = Suite{ - integration.TestCreateBucket, - integration.TestHeadBucket, - integration.TestListBuckets, - integration.TestPutObject, - integration.TestGetObject, - integration.TestHeadObject, - integration.TestDeleteObject, - integration.TestDeleteBucket, -} - -// CRUD covers Smoke plus listing, multi-delete, copy, and the -// GetObjectAttributes surface. Stays inside features that don't -// require multipart, versioning, ACL, policy, CORS, lock, or tagging. -var CRUD = Suite{ - integration.TestCreateBucket, - integration.TestHeadBucket, - integration.TestListBuckets, - integration.TestDeleteBucket, - integration.TestPutObject, - integration.TestHeadObject, - integration.TestGetObject, - integration.TestGetObjectAttributes, - integration.TestListObjects, - integration.TestListObjectsV2, - integration.TestCopyObject, - integration.TestDeleteObject, - integration.TestDeleteObjects, -} - -// Multipart covers the multipart-upload group set. -var Multipart = Suite{ - integration.TestCreateMultipartUpload, - integration.TestUploadPart, - integration.TestUploadPartCopy, - integration.TestListParts, - integration.TestListMultipartUploads, - integration.TestAbortMultipartUpload, - integration.TestCompleteMultipartUpload, -} - -// Tagging covers object and bucket tagging APIs. -var Tagging = Suite{ - integration.TestPutBucketTagging, - integration.TestGetBucketTagging, - integration.TestDeleteBucketTagging, - integration.TestPutObjectTagging, - integration.TestGetObjectTagging, - integration.TestDeleteObjectTagging, -} - -// ObjectLock covers retention, legal hold, lock config, and -// WORM-protection groups. -var ObjectLock = Suite{ - integration.TestPutObjectLockConfiguration, - integration.TestGetObjectLockConfiguration, - integration.TestPutObjectRetention, - integration.TestGetObjectRetention, - integration.TestPutObjectLegalHold, - integration.TestGetObjectLegalHold, - integration.TestWORMProtection, -} - -// Versioning runs the version-aware group. Set -// Config.VersioningEnabled = true. -var Versioning = Suite{ - integration.TestVersioning, - integration.TestVersioningDisabled, - integration.TestListObjectVersions_VD, -} - -// Auth runs sigv4 + presigned-URL authentication groups. -var Auth = Suite{ - integration.TestAuthentication, - integration.TestPresignedAuthentication, -} - -// Full is the upstream TestFullFlow rolled-up suite — the -// "how-far-from-full-compatibility" gauge. Expect noisy failures -// until ingot closes the gaps tracked by the focused suites above. -var Full = Suite{integration.TestFullFlow} diff --git a/testing/multipart_test.go b/testing/multipart_test.go deleted file mode 100644 index 938d0a3..0000000 --- a/testing/multipart_test.go +++ /dev/null @@ -1,193 +0,0 @@ -package testing_test - -import ( - "bytes" - "context" - "strings" - "testing" - "time" - - "github.com/aws/aws-sdk-go-v2/aws" - "github.com/aws/aws-sdk-go-v2/service/s3" - "github.com/aws/aws-sdk-go-v2/service/s3/types" - "go.uber.org/zap/zaptest" - - mstesting "github.com/fil-forge/ingot/testing" -) - -// TestMultipart_RoundTrip drives Create → UploadPart×3 → Complete and verifies -// the assembled object reads back byte-exact, with a multipart "-N" ETag. Parts -// are coarse-split (max_blob_size small) so a part spans multiple body blobs. -func TestMultipart_RoundTrip(t *testing.T) { - ctx, cancel := context.WithTimeout(t.Context(), 30*time.Second) - defer cancel() - - const maxBlob = 32 << 10 - h, err := mstesting.StartHarness(ctx, - mstesting.WithLogger(zaptest.NewLogger(t)), - mstesting.WithMaxBlobSize(maxBlob), - ) - if err != nil { - t.Fatalf("StartHarness: %v", err) - } - t.Cleanup(func() { _ = h.Stop(context.Background()) }) - - cl := blobClient(h) - const bucket, key = "mpbucket", "big/obj" - if _, err := cl.CreateBucket(ctx, &s3.CreateBucketInput{Bucket: aws.String(bucket)}); err != nil { - t.Fatalf("CreateBucket: %v", err) - } - - create, err := cl.CreateMultipartUpload(ctx, &s3.CreateMultipartUploadInput{ - Bucket: aws.String(bucket), Key: aws.String(key), ContentType: aws.String("text/plain"), - }) - if err != nil { - t.Fatalf("CreateMultipartUpload: %v", err) - } - uploadID := create.UploadId - - // Three parts of varying sizes; part 1 spans several blobs. - partData := [][]byte{patternBytes(70 << 10), patternBytes(40 << 10), patternBytes(9 << 10)} - var completed []types.CompletedPart - var whole []byte - for i, data := range partData { - pn := int32(i + 1) - up, err := cl.UploadPart(ctx, &s3.UploadPartInput{ - Bucket: aws.String(bucket), Key: aws.String(key), UploadId: uploadID, - PartNumber: aws.Int32(pn), Body: bytes.NewReader(data), - }) - if err != nil { - t.Fatalf("UploadPart %d: %v", pn, err) - } - completed = append(completed, types.CompletedPart{PartNumber: aws.Int32(pn), ETag: up.ETag}) - whole = append(whole, data...) - } - - comp, err := cl.CompleteMultipartUpload(ctx, &s3.CompleteMultipartUploadInput{ - Bucket: aws.String(bucket), Key: aws.String(key), UploadId: uploadID, - MultipartUpload: &types.CompletedMultipartUpload{Parts: completed}, - }) - if err != nil { - t.Fatalf("CompleteMultipartUpload: %v", err) - } - if et := strings.Trim(aws.ToString(comp.ETag), `"`); !strings.HasSuffix(et, "-3") { - t.Fatalf("complete ETag = %q, want a multipart -3 suffix", et) - } - - // The assembled object reads back byte-exact, and HEAD reports the full size. - if got := getBody(t, ctx, cl, bucket, key, ""); !bytes.Equal(got, whole) { - t.Fatalf("multipart GET mismatch: got %d bytes, want %d", len(got), len(whole)) - } - head, err := cl.HeadObject(ctx, &s3.HeadObjectInput{Bucket: aws.String(bucket), Key: aws.String(key)}) - if err != nil { - t.Fatalf("HeadObject: %v", err) - } - if aws.ToInt64(head.ContentLength) != int64(len(whole)) { - t.Fatalf("HEAD size = %d, want %d", aws.ToInt64(head.ContentLength), len(whole)) - } - // A ranged GET spanning the part-1→part-2 boundary still reconstructs. - r := getBody(t, ctx, cl, bucket, key, "bytes=65536-90000") - if !bytes.Equal(r, whole[65536:90001]) { - t.Fatalf("ranged multipart GET mismatch: got %d bytes", len(r)) - } -} - -// TestMultipart_FailedCompleteStaysAbortable is the regression for the -// zombie-session bug: a Complete that fails validation (wrong part ETag) must -// leave the session abortable (and retriable), not stuck in 'completing'. -func TestMultipart_FailedCompleteStaysAbortable(t *testing.T) { - ctx, cancel := context.WithTimeout(t.Context(), 30*time.Second) - defer cancel() - - h, err := mstesting.StartHarness(ctx, mstesting.WithLogger(zaptest.NewLogger(t))) - if err != nil { - t.Fatalf("StartHarness: %v", err) - } - t.Cleanup(func() { _ = h.Stop(context.Background()) }) - - cl := blobClient(h) - const bucket, key = "mpretry", "obj" - if _, err := cl.CreateBucket(ctx, &s3.CreateBucketInput{Bucket: aws.String(bucket)}); err != nil { - t.Fatalf("CreateBucket: %v", err) - } - create, err := cl.CreateMultipartUpload(ctx, &s3.CreateMultipartUploadInput{Bucket: aws.String(bucket), Key: aws.String(key)}) - if err != nil { - t.Fatalf("CreateMultipartUpload: %v", err) - } - data := patternBytes(8 << 10) - up, err := cl.UploadPart(ctx, &s3.UploadPartInput{ - Bucket: aws.String(bucket), Key: aws.String(key), UploadId: create.UploadId, - PartNumber: aws.Int32(1), Body: bytes.NewReader(data), - }) - if err != nil { - t.Fatalf("UploadPart: %v", err) - } - - // Complete with a WRONG part ETag → InvalidPart. The session must not zombie. - if _, err := cl.CompleteMultipartUpload(ctx, &s3.CompleteMultipartUploadInput{ - Bucket: aws.String(bucket), Key: aws.String(key), UploadId: create.UploadId, - MultipartUpload: &types.CompletedMultipartUpload{Parts: []types.CompletedPart{ - {PartNumber: aws.Int32(1), ETag: aws.String("\"deadbeefdeadbeefdeadbeefdeadbeef\"")}, - }}, - }); err == nil { - t.Fatal("Complete with a wrong part ETag: want an error, got nil") - } - - // A retry with the correct ETag now succeeds (session was reverted to open). - if _, err := cl.CompleteMultipartUpload(ctx, &s3.CompleteMultipartUploadInput{ - Bucket: aws.String(bucket), Key: aws.String(key), UploadId: create.UploadId, - MultipartUpload: &types.CompletedMultipartUpload{Parts: []types.CompletedPart{ - {PartNumber: aws.Int32(1), ETag: up.ETag}, - }}, - }); err != nil { - t.Fatalf("Complete retry after a failed Complete: %v", err) - } - if got := getBody(t, ctx, cl, bucket, key, ""); !bytes.Equal(got, data) { - t.Fatalf("retried multipart object mismatch: %d bytes", len(got)) - } -} - -// TestMultipart_Abort verifies an aborted upload leaves no object and the -// upload id becomes unusable. -func TestMultipart_Abort(t *testing.T) { - ctx, cancel := context.WithTimeout(t.Context(), 30*time.Second) - defer cancel() - - h, err := mstesting.StartHarness(ctx, mstesting.WithLogger(zaptest.NewLogger(t))) - if err != nil { - t.Fatalf("StartHarness: %v", err) - } - t.Cleanup(func() { _ = h.Stop(context.Background()) }) - - cl := blobClient(h) - const bucket, key = "mpabort", "obj" - if _, err := cl.CreateBucket(ctx, &s3.CreateBucketInput{Bucket: aws.String(bucket)}); err != nil { - t.Fatalf("CreateBucket: %v", err) - } - create, err := cl.CreateMultipartUpload(ctx, &s3.CreateMultipartUploadInput{Bucket: aws.String(bucket), Key: aws.String(key)}) - if err != nil { - t.Fatalf("CreateMultipartUpload: %v", err) - } - if _, err := cl.UploadPart(ctx, &s3.UploadPartInput{ - Bucket: aws.String(bucket), Key: aws.String(key), UploadId: create.UploadId, - PartNumber: aws.Int32(1), Body: bytes.NewReader(patternBytes(4 << 10)), - }); err != nil { - t.Fatalf("UploadPart: %v", err) - } - if _, err := cl.AbortMultipartUpload(ctx, &s3.AbortMultipartUploadInput{ - Bucket: aws.String(bucket), Key: aws.String(key), UploadId: create.UploadId, - }); err != nil { - t.Fatalf("AbortMultipartUpload: %v", err) - } - - // The object was never created. - if _, err := cl.HeadObject(ctx, &s3.HeadObjectInput{Bucket: aws.String(bucket), Key: aws.String(key)}); err == nil { - t.Fatalf("HeadObject after abort: want an error (no object), got nil") - } - // The upload id is gone — a second abort fails with NoSuchUpload. - if _, err := cl.AbortMultipartUpload(ctx, &s3.AbortMultipartUploadInput{ - Bucket: aws.String(bucket), Key: aws.String(key), UploadId: create.UploadId, - }); err == nil { - t.Fatalf("second AbortMultipartUpload: want NoSuchUpload, got nil") - } -} diff --git a/testing/parse_test.go b/testing/parse_test.go deleted file mode 100644 index 22bdac4..0000000 --- a/testing/parse_test.go +++ /dev/null @@ -1,36 +0,0 @@ -package testing - -import ( - "reflect" - "testing" -) - -// TestParseCaseLines locks the recovery of per-case names from the upstream -// suite's ANSI-colored stdout (PASS/FAIL lines), which is how Result.{Passed, -// Failed}Cases is built. Teardown-failure lines (no "FAIL " prefix) are ignored. -func TestParseCaseLines(t *testing.T) { - const ( - red = "\033[31m" - green = "\033[32m" - cyan = "\033[36m" - reset = "\033[0m" - ) - out := cyan + "RUN " + reset + "PutObject_success\n" + - green + "PASS " + reset + "PutObject_success\n" + - cyan + "RUN " + reset + "PutObject_conditional_writes\n" + - red + "FAIL " + reset + "PutObject_conditional_writes: operation error S3: PutObject, StatusCode: 500\n" + - red + "SomeCase: failed to delete the bucket: boom\n" + // teardown noise, no FAIL prefix - green + "PASS " + reset + "GetObject_success\n" + - red + "FAIL " + reset + "CreateBucket_default_acl: expected grants length to be 1, instead got 0\n" - - passed, failed := parseCaseLines(out) - - wantPassed := []string{"PutObject_success", "GetObject_success"} - wantFailed := []string{"PutObject_conditional_writes", "CreateBucket_default_acl"} - if !reflect.DeepEqual(passed, wantPassed) { - t.Errorf("passed = %v, want %v", passed, wantPassed) - } - if !reflect.DeepEqual(failed, wantFailed) { - t.Errorf("failed = %v, want %v", failed, wantFailed) - } -} diff --git a/testing/run_capture_test.go b/testing/run_capture_test.go deleted file mode 100644 index 7a75e6d..0000000 --- a/testing/run_capture_test.go +++ /dev/null @@ -1,41 +0,0 @@ -package testing - -import ( - "context" - "testing" - - "github.com/versity/versitygw/tests/integration" -) - -// TestRun_CapturesCaseNames validates end-to-end that Run recovers per-case -// names from the suite's stdout (the os.Stdout tee + parseCaseLines) and that -// the recovered names match the upstream pass/fail counters one-for-one. Without -// this, a broken capture would silently return empty case sets — turning the -// forge conformance gate (forge failures ⊆ baseline failures) into a vacuous -// pass. Runs in-process against the harness (no Docker); the CreateBucket group -// has both passing and out-of-scope-failing cases, so both sets are non-empty. -func TestRun_CapturesCaseNames(t *testing.T) { - ctx := context.Background() - h, err := StartHarness(ctx) - if err != nil { - t.Fatalf("start harness: %v", err) - } - t.Cleanup(func() { _ = h.Stop(context.Background()) }) - - res := Run(ctx, Config{ - Endpoint: h.Endpoint, - AccessKey: h.AccessKey, - SecretKey: h.SecretKey, - Region: h.Region, - }, Suite{integration.TestCreateBucket}) - - if len(res.PassedCases) == 0 && len(res.FailedCases) == 0 { - t.Fatal("Run captured no case names — os.Stdout capture is broken (the conformance gate would pass vacuously)") - } - if int(res.Passed) != len(res.PassedCases) { - t.Errorf("passed counter=%d but captured %d names: %v", res.Passed, len(res.PassedCases), res.PassedCases) - } - if int(res.Failed) != len(res.FailedCases) { - t.Errorf("failed counter=%d but captured %d names: %v", res.Failed, len(res.FailedCases), res.FailedCases) - } -} diff --git a/testing/smoke_test.go b/testing/smoke_test.go deleted file mode 100644 index 5befbf5..0000000 --- a/testing/smoke_test.go +++ /dev/null @@ -1,466 +0,0 @@ -package testing - -import ( - "context" - "flag" - "testing" - - "github.com/versity/versitygw/tests/integration" - "go.uber.org/zap/zaptest" -) - -// smokeCase pairs an upstream versitygw integration case with its -// subtest name. Each TestSmoke_* / TestSmokeXFail_* function below -// declares its cases inline as a []smokeCase, so GoLand (and any -// other IDE that parses table-driven Go tests) renders one -// play-icon per row in the gutter. -type smokeCase struct { - name string - fn integration.IntTest -} - -// Layout: one top-level test per S3 group, in two flavors: -// -// TestSmoke_ — known-passing cases (every case must pass) -// TestSmokeXFail_ — cases ingot fails today; each one is -// expected to fail and reported as SKIP. -// An unexpected pass errors so the case -// can be promoted. -// -// Adding a case: when a fix lands, run the matching TestSmokeXFail_*. -// Cases that flip green will report "case unexpectedly passed" — move -// the line from the XFail function to the matching TestSmoke_* one. -// -// Each top-level test boots its own Harness (via smokeHarness), so -// failures in one group can't leak buckets / segments / op-roots -// into another. Cases within a group share one harness because the -// upstream cases create + tear down their own buckets internally. - -// smokeHarness boots a Harness scoped to t and registers cleanup. -func smokeHarness(t *testing.T) *Harness { - t.Helper() - h, err := StartHarness(t.Context(), WithLogger(zaptest.NewLogger(t))) - if err != nil { - t.Fatalf("StartHarness: %v", err) - } - t.Cleanup(func() { _ = h.Stop(context.Background()) }) - return h -} - -// shuffleEnabled reports whether `go test -shuffle` is active. Shuffle is a -// runtime flag (there is no build tag for it), so we inspect it directly. A -// couple of order-sensitive upstream cases key off this to skip only when the -// run order — and thus their process-global bucket-name counter — is -// randomized; in fixed order they are deterministic and run normally. -func shuffleEnabled() bool { - f := flag.Lookup("test.shuffle") - return f != nil && f.Value.String() != "off" -} - -// ============================================================= -// Known-passing cases -// ============================================================= - -func TestSmoke_CreateBucket(t *testing.T) { - tests := []smokeCase{ - {"invalid_bucket_name", integration.CreateBucket_invalid_bucket_name}, - {"invalid_canned_acl", integration.CreateBucket_invalid_canned_acl}, - {"invalid_location_constraint", integration.CreateBucket_invalid_location_constraint}, - {"invalid_ownership", integration.CreateBucket_invalid_ownership}, - {"ownership_with_acl", integration.CreateBucket_ownership_with_acl}, - {"success", integration.CreateBucket_success}, - } - s3conf := newS3Conf(smokeHarness(t).Config()) - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if err := tt.fn(s3conf); err != nil { - t.Fatalf("%v", err) - } - }) - } -} - -func TestSmoke_HeadBucket(t *testing.T) { - tests := []smokeCase{ - {"non_existing_bucket", integration.HeadBucket_non_existing_bucket}, - {"success", integration.HeadBucket_success}, - } - s3conf := newS3Conf(smokeHarness(t).Config()) - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if err := tt.fn(s3conf); err != nil { - t.Fatalf("%v", err) - } - }) - } -} - -func TestSmoke_ListBuckets(t *testing.T) { - tests := []smokeCase{ - {"empty_success", integration.ListBuckets_empty_success}, - {"invalid_max_buckets", integration.ListBuckets_invalid_max_buckets}, - {"success", integration.ListBuckets_success}, - {"with_prefix", integration.ListBuckets_with_prefix}, - } - s3conf := newS3Conf(smokeHarness(t).Config()) - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if err := tt.fn(s3conf); err != nil { - t.Fatalf("%v", err) - } - }) - } - - // truncated exercises paginated ListBuckets and ingot passes it in - // deterministic (fixed) order — so it runs in the normal test run, the - // -race job, IDE runs, etc. But it is brittle under `go test -shuffle`: - // the upstream case names buckets from a process-global counter - // (test-bucket-) and compares pages position-by-position, expecting - // creation order, while ingot returns buckets lexicographically (matching - // versitygw's own backend). Shuffle randomizes how far that counter has - // advanced, and when a page straddles a digit boundary (…98, 99, 100) - // lexical order ("100" < "98") diverges from the expected numeric order. - // So we run it whenever order is deterministic and skip only under - // shuffle. See CLAUDE.md "Testing". - t.Run("truncated", func(t *testing.T) { - if shuffleEnabled() { - t.Skip("ListBuckets_truncated assumes creation-order pagination; brittle under -shuffle (see comment above)") - } - if err := integration.ListBuckets_truncated(s3conf); err != nil { - t.Fatalf("%v", err) - } - }) -} - -func TestSmoke_DeleteBucket(t *testing.T) { - tests := []smokeCase{ - {"incorrect_expected_bucket_owner", integration.DeleteBucket_incorrect_expected_bucket_owner}, - {"non_empty_bucket", integration.DeleteBucket_non_empty_bucket}, - {"non_existing_bucket", integration.DeleteBucket_non_existing_bucket}, - {"success_status_code", integration.DeleteBucket_success_status_code}, - } - s3conf := newS3Conf(smokeHarness(t).Config()) - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if err := tt.fn(s3conf); err != nil { - t.Fatalf("%v", err) - } - }) - } -} - -func TestSmoke_PutObject(t *testing.T) { - tests := []smokeCase{ - {"checksum_algorithm_and_header_mismatch", integration.PutObject_checksum_algorithm_and_header_mismatch}, - {"checksums_success", integration.PutObject_checksums_success}, - {"conditional_writes", integration.PutObject_conditional_writes}, - {"default_checksum", integration.PutObject_default_checksum}, - {"default_content_type", integration.PutObject_default_content_type}, - {"dir_object_default_checksum", integration.PutObject_dir_object_default_checksum}, - {"dir_object_checksums_success", integration.PutObject_dir_object_checksums_success}, - {"incorrect_checksums", integration.PutObject_incorrect_checksums}, - {"invalid_credentials", integration.PutObject_invalid_credentials}, - {"false_negative_object_names", integration.PutObject_false_negative_object_names}, - {"invalid_checksum_header", integration.PutObject_invalid_checksum_header}, - {"invalid_legal_hold", integration.PutObject_invalid_legal_hold}, - {"invalid_object_lock_mode", integration.PutObject_invalid_object_lock_mode}, - {"invalid_object_names", integration.PutObject_invalid_object_names}, - {"invalid_retain_until_date", integration.PutObject_invalid_retain_until_date}, - {"long_metadata", integration.PutObject_long_metadata}, - {"missing_object_lock_retention_config", integration.PutObject_missing_object_lock_retention_config}, - {"multiple_checksum_headers", integration.PutObject_multiple_checksum_headers}, - {"non_existing_bucket", integration.PutObject_non_existing_bucket}, - {"past_retain_until_date", integration.PutObject_past_retain_until_date}, - {"racey_success", integration.PutObject_racey_success}, - {"special_chars", integration.PutObject_special_chars}, - {"success", integration.PutObject_success}, - {"should_combine_metadata", integration.PutObject_should_combine_metadata}, - {"with_metadata", integration.PutObject_with_metadata}, - } - s3conf := newS3Conf(smokeHarness(t).Config()) - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if err := tt.fn(s3conf); err != nil { - t.Fatalf("%v", err) - } - }) - } -} - -func TestSmoke_GetObject(t *testing.T) { - tests := []smokeCase{ - {"by_range_resp_status", integration.GetObject_by_range_resp_status}, - {"checksums", integration.GetObject_checksums}, - {"conditional_reads", integration.GetObject_conditional_reads}, - {"dir_object_checksum", integration.GetObject_dir_object_checksum}, - {"dir_with_range", integration.GetObject_dir_with_range}, - {"directory_object_noslash", integration.GetObject_directory_object_noslash}, - {"empty_object_part_number_1", integration.GetObject_empty_object_part_number_1}, - {"invalid_parent", integration.GetObject_invalid_parent}, - {"invalid_part_number", integration.GetObject_invalid_part_number}, - {"mp_part_number_exceeds_parts_count", integration.GetObject_mp_part_number_exceeds_parts_count}, - {"mp_part_number_resp_status", integration.GetObject_mp_part_number_resp_status}, - {"mp_part_number_success", integration.GetObject_mp_part_number_success}, - {"non_mp_part_number_1_success", integration.GetObject_non_mp_part_number_1_success}, - {"large_object", integration.GetObject_large_object}, - {"non_existing_dir_object", integration.GetObject_non_existing_dir_object}, - {"non_existing_key", integration.GetObject_non_existing_key}, - {"not_enabled_checksum_mode", integration.GetObject_not_enabled_checksum_mode}, - {"overrides_presign_success", integration.GetObject_overrides_presign_success}, - {"overrides_success", integration.GetObject_overrides_success}, - {"range_and_part_number", integration.GetObject_range_and_part_number}, - {"ranged_with_checksum_mode", integration.GetObject_ranged_with_checksum_mode}, - {"with_range", integration.GetObject_with_range}, - {"zero_len_with_range", integration.GetObject_zero_len_with_range}, - } - s3conf := newS3Conf(smokeHarness(t).Config()) - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if err := tt.fn(s3conf); err != nil { - t.Fatalf("%v", err) - } - }) - } -} - -func TestSmoke_HeadObject(t *testing.T) { - tests := []smokeCase{ - {"checksums", integration.HeadObject_checksums}, - {"conditional_reads", integration.HeadObject_conditional_reads}, - {"directory_object_noslash", integration.HeadObject_directory_object_noslash}, - {"empty_object_part_number_1", integration.HeadObject_empty_object_part_number_1}, - {"invalid_parent_dir", integration.HeadObject_invalid_parent_dir}, - {"invalid_part_number", integration.HeadObject_invalid_part_number}, - {"mp_part_number_exceeds_parts_count", integration.HeadObject_mp_part_number_exceeds_parts_count}, - {"mp_part_number_resp_status", integration.HeadObject_mp_part_number_resp_status}, - {"mp_part_number_success", integration.HeadObject_mp_part_number_success}, - {"non_mp_part_number_1_success", integration.HeadObject_non_mp_part_number_1_success}, - {"non_existing_dir_object", integration.HeadObject_non_existing_dir_object}, - {"non_existing_object", integration.HeadObject_non_existing_object}, - {"not_enabled_checksum_mode", integration.HeadObject_not_enabled_checksum_mode}, - {"overrides_presign_success", integration.HeadObject_overrides_presign_success}, - {"overrides_success", integration.HeadObject_overrides_success}, - {"range_and_part_number", integration.HeadObject_range_and_part_number}, - {"by_range_resp_status", integration.HeadObject_by_range_resp_status}, - {"dir_with_range", integration.HeadObject_dir_with_range}, - {"ranged_with_checksum_mode", integration.HeadObject_ranged_with_checksum_mode}, - {"with_range", integration.HeadObject_with_range}, - {"zero_len_with_range", integration.HeadObject_zero_len_with_range}, - } - s3conf := newS3Conf(smokeHarness(t).Config()) - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if err := tt.fn(s3conf); err != nil { - t.Fatalf("%v", err) - } - }) - } -} - -func TestSmoke_DeleteObject(t *testing.T) { - tests := []smokeCase{ - {"conditional_writes", integration.DeleteObject_conditional_writes}, - {"directory_object", integration.DeleteObject_directory_object}, - {"directory_object_noslash", integration.DeleteObject_directory_object_noslash}, - {"expected_bucket_owner", integration.DeleteObject_expected_bucket_owner}, - {"incorrect_expected_bucket_owner", integration.DeleteObject_incorrect_expected_bucket_owner}, - {"non_empty_dir_obj", integration.DeleteObject_non_empty_dir_obj}, - {"non_existing_dir_object", integration.DeleteObject_non_existing_dir_object}, - {"non_existing_object", integration.DeleteObject_non_existing_object}, - {"success", integration.DeleteObject_success}, - {"success_status_code", integration.DeleteObject_success_status_code}, - } - s3conf := newS3Conf(smokeHarness(t).Config()) - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if err := tt.fn(s3conf); err != nil { - t.Fatalf("%v", err) - } - }) - } -} - -// ============================================================= -// Known-failing cases (XFail) -// ============================================================= - -func TestSmokeXFail_CreateBucket(t *testing.T) { - tests := []smokeCase{ - {"as_user", integration.CreateBucket_as_user}, - {"default_acl", integration.CreateBucket_default_acl}, - {"default_object_lock", integration.CreateBucket_default_object_lock}, - {"duplicate_keys", integration.CreateBucket_duplicate_keys}, - {"existing_bucket", integration.CreateBucket_existing_bucket}, - {"invalid_tags", integration.CreateBucket_invalid_tags}, - {"long_tags", integration.CreateBucket_long_tags}, - {"non_default_acl", integration.CreateBucket_non_default_acl}, - {"owned_by_you", integration.CreateBucket_owned_by_you}, - {"private_canned_acl", integration.CreateBucket_private_canned_acl}, - {"private_canned_acl_bucket_owner_enforced_ownership", integration.CreateBucket_private_canned_acl_bucket_owner_enforced_ownership}, - {"tag_count_limit", integration.CreateBucket_tag_count_limit}, - } - s3conf := newS3Conf(smokeHarness(t).Config()) - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := tt.fn(s3conf) - if err == nil { - t.Errorf("case unexpectedly passed; promote it from TestSmokeXFail_CreateBucket to TestSmoke_CreateBucket") - return - } - t.Skipf("known-failing: %v", err) - }) - } -} - -func TestSmokeXFail_ListBuckets(t *testing.T) { - tests := []smokeCase{ - {"as_admin", integration.ListBuckets_as_admin}, - {"as_user", integration.ListBuckets_as_user}, - } - s3conf := newS3Conf(smokeHarness(t).Config()) - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := tt.fn(s3conf) - if err == nil { - t.Errorf("case unexpectedly passed; promote it from TestSmokeXFail_ListBuckets to TestSmoke_ListBuckets") - return - } - t.Skipf("known-failing: %v", err) - }) - } -} - -func TestSmokeXFail_PutObject(t *testing.T) { - tests := []smokeCase{ - {"missing_bucket_lock", integration.PutObject_missing_bucket_lock}, - {"object_acl_not_supported", integration.PutObject_object_acl_not_supported}, - {"tagging", integration.PutObject_tagging}, - {"with_object_lock", integration.PutObject_with_object_lock}, - } - s3conf := newS3Conf(smokeHarness(t).Config()) - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := tt.fn(s3conf) - if err == nil { - t.Errorf("case unexpectedly passed; promote it from TestSmokeXFail_PutObject to TestSmoke_PutObject") - return - } - t.Skipf("known-failing: %v", err) - }) - } -} - -func TestSmokeXFail_GetObject(t *testing.T) { - tests := []smokeCase{ - {"directory_success", integration.GetObject_directory_success}, - {"overrides_fail_public", integration.GetObject_overrides_fail_public}, - {"success", integration.GetObject_success}, - } - s3conf := newS3Conf(smokeHarness(t).Config()) - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := tt.fn(s3conf) - if err == nil { - t.Errorf("case unexpectedly passed; promote it from TestSmokeXFail_GetObject to TestSmoke_GetObject") - return - } - t.Skipf("known-failing: %v", err) - }) - } -} - -func TestSmokeXFail_HeadObject(t *testing.T) { - tests := []smokeCase{ - {"overrides_fail_public", integration.HeadObject_overrides_fail_public}, - {"success", integration.HeadObject_success}, - } - s3conf := newS3Conf(smokeHarness(t).Config()) - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := tt.fn(s3conf) - if err == nil { - t.Errorf("case unexpectedly passed; promote it from TestSmokeXFail_HeadObject to TestSmoke_HeadObject") - return - } - t.Skipf("known-failing: %v", err) - }) - } -} - -func TestSmokeXFail_DeleteObject(t *testing.T) { - tests := []smokeCase{} - s3conf := newS3Conf(smokeHarness(t).Config()) - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := tt.fn(s3conf) - if err == nil { - t.Errorf("case unexpectedly passed; promote it from TestSmokeXFail_DeleteObject to TestSmoke_DeleteObject") - return - } - t.Skipf("known-failing: %v", err) - }) - } -} - -func TestSmoke_CopyObject(t *testing.T) { - tests := []smokeCase{ - {"success", integration.CopyObject_success}, - {"copy_to_itself", integration.CopyObject_copy_to_itself}, - {"copy_to_itself_invalid_directive", integration.CopyObject_copy_to_itself_invalid_directive}, - {"copy_source_starting_with_slash", integration.CopyObject_copy_source_starting_with_slash}, - {"default_content_type_with_replace_metadata", integration.CopyObject_default_content_type_with_replace_metadata}, - {"invalid_copy_source", integration.CopyObject_invalid_copy_source}, - {"non_existing_dst_bucket", integration.CopyObject_non_existing_dst_bucket}, - {"non_existing_dir_object", integration.CopyObject_non_existing_dir_object}, - {"with_metadata", integration.CopyObject_with_metadata}, - {"to_itself_with_new_metadata", integration.CopyObject_to_itself_with_new_metadata}, - {"conditional_reads", integration.CopyObject_conditional_reads}, - {"with_special_characters", integration.CopyObject_with_special_characters}, - {"long_metadata", integration.CopyObject_long_metadata}, - {"should_copy_meta_props", integration.CopyObject_should_copy_meta_props}, - {"should_replace_meta_props", integration.CopyObject_should_replace_meta_props}, - } - s3conf := newS3Conf(smokeHarness(t).Config()) - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if err := tt.fn(s3conf); err != nil { - t.Fatalf("%v", err) - } - }) - } -} - -func TestSmoke_DeleteObjects(t *testing.T) { - tests := []smokeCase{ - {"success", integration.DeleteObjects_success}, - {"empty_input", integration.DeleteObjects_empty_input}, - {"non_existing_objects", integration.DeleteObjects_non_existing_objects}, - } - s3conf := newS3Conf(smokeHarness(t).Config()) - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if err := tt.fn(s3conf); err != nil { - t.Fatalf("%v", err) - } - }) - } -} - -// TestSmokeXFail_CopyObject: cases ingot fails today. overwrite_same_file_object -// expects a posix filesystem error that doesn't apply to ingot's flat keyspace. -func TestSmokeXFail_CopyObject(t *testing.T) { - tests := []smokeCase{ - {"overwrite_same_file_object", integration.CopyObject_overwrite_same_file_object}, - } - s3conf := newS3Conf(smokeHarness(t).Config()) - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := tt.fn(s3conf) - if err == nil { - t.Errorf("case unexpectedly passed; promote it from TestSmokeXFail_CopyObject to TestSmoke_CopyObject") - return - } - t.Skipf("known-failing: %v", err) - }) - } -}