From e65b32c4d7c72b12e96783064aa3c224a5dd7aba Mon Sep 17 00:00:00 2001 From: Jochen Schalanda Date: Tue, 18 Mar 2025 05:15:09 +0100 Subject: [PATCH] Reintegrate fork joschi/dive (#570) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: configure Renovate (#1) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * chore(deps): update github artifact actions to v4 (#20) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * chore(deps): update docker/login-action action to v3 (#19) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * chore(deps): update actions/setup-go action to v5 (#18) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * chore(deps): update actions/checkout action to v4 (#17) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * chore(deps): update actions/cache action to v4 (#16) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * build: use Go 1.23.x to build project * chore(deps): update build tools - golangci-lint v1.16.0: https://github.com/golangci/golangci-lint/releases/tag/v1.61.0 - GoReleaser v1.61.0: https://github.com/goreleaser/goreleaser/releases/tag/v1.26.2 - Chronicle v0.8.0: https://github.com/anchore/chronicle/releases/tag/v0.8.0 - Glow v1.5.1: https://github.com/charmbracelet/glow/releases/tag/v1.5.1 * chore: temporarily lower coverage threshold to 30% Old coverage: ``` go test -race -coverprofile ./.tmp/unit-coverage-details.txt ./... ? github.com/wagoodman/dive [no test files] ? github.com/wagoodman/dive/cmd [no test files] ? github.com/wagoodman/dive/dive [no test files] ? github.com/wagoodman/dive/dive/image [no test files] ok github.com/wagoodman/dive/dive/filetree 0.032s coverage: 58.0% of statements ? github.com/wagoodman/dive/dive/image/podman [no test files] ok github.com/wagoodman/dive/dive/image/docker 0.076s coverage: 43.6% of statements ? github.com/wagoodman/dive/runtime/ui [no test files] ? github.com/wagoodman/dive/runtime/ui/format [no test files] ? github.com/wagoodman/dive/runtime/ui/key [no test files] ok github.com/wagoodman/dive/runtime 0.531s coverage: 53.3% of statements ok github.com/wagoodman/dive/runtime/ci 0.087s coverage: 62.1% of statements ok github.com/wagoodman/dive/runtime/export 0.096s coverage: 100.0% of statements ? github.com/wagoodman/dive/runtime/ui/layout/compound [no test files] ? github.com/wagoodman/dive/runtime/ui/view [no test files] ok github.com/wagoodman/dive/runtime/ui/layout 0.021s coverage: 82.6% of statements ? github.com/wagoodman/dive/utils [no test files] ok github.com/wagoodman/dive/runtime/ui/viewmodel 1.202s coverage: 55.3% of statements Coverage: 57.5% ``` New coverage: ``` go test -race -coverprofile ./.tmp/unit-coverage-details.txt ./... github.com/wagoodman/dive/dive coverage: 0.0% of statements github.com/wagoodman/dive/cmd coverage: 0.0% of statements github.com/wagoodman/dive coverage: 0.0% of statements github.com/wagoodman/dive/dive/image coverage: 0.0% of statements github.com/wagoodman/dive/dive/image/podman coverage: 0.0% of statements ok github.com/wagoodman/dive/dive/filetree 1.027s coverage: 58.0% of statements ok github.com/wagoodman/dive/dive/image/docker 1.064s coverage: 43.6% of statements github.com/wagoodman/dive/runtime/ui coverage: 0.0% of statements github.com/wagoodman/dive/runtime/ui/format coverage: 0.0% of statements github.com/wagoodman/dive/runtime/ui/key coverage: 0.0% of statements github.com/wagoodman/dive/runtime/ui/layout/compound coverage: 0.0% of statements github.com/wagoodman/dive/runtime/ui/view coverage: 0.0% of statements ok github.com/wagoodman/dive/runtime 1.382s coverage: 53.3% of statements ok github.com/wagoodman/dive/runtime/ci 1.055s coverage: 62.1% of statements github.com/wagoodman/dive/utils coverage: 0.0% of statements ok github.com/wagoodman/dive/runtime/export 1.048s coverage: 100.0% of statements ok github.com/wagoodman/dive/runtime/ui/layout 1.012s coverage: 82.6% of statements ok github.com/wagoodman/dive/runtime/ui/viewmodel 2.202s coverage: 55.3% of statements Coverage: 31.8% Coverage below threshold of 55.0% ``` * chore(deps): update fountainhead/action-wait-for-check action to v1.2.0 (#25) https://github.com/fountainhead/action-wait-for-check/releases/tag/v1.2.0 * ci: validate Renovate configuration file on build * ci: add gomodTidy and gomodUpdateImportPaths post update options to Renovate * fix(deps): update module github.com/dustin/go-humanize to v1.0.1 (#5) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * chore(deps): update alpine docker tag to v3.20 (#7) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * fix(deps): update github.com/awesome-gocui/keybinding digest to 8602903 (#2) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * fix(deps): update module github.com/spf13/cobra to v0.0.7 (#6) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * fix(deps): update module github.com/fatih/color to v1.18.0 (#9) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * fix(deps): update module github.com/google/uuid to v1.6.0 (#10) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * fix(deps): update module github.com/sergi/go-diff to v1.3.1 (#11) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * fix(deps): update module github.com/cespare/xxhash to v2 (#21) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * fix(deps): update module github.com/docker/docker to v24.0.9+incompatible (#3) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * fix(deps): update module github.com/spf13/viper to v1.19.0 (#14) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * fix(deps): update module github.com/sirupsen/logrus to v1.9.3 (#12) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * fix(deps): update module golang.org/x/net to v0.30.0 (#15) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * fix(deps): update module github.com/spf13/cobra to v1 (#23) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * fix(deps): update module github.com/docker/docker to v27 (#22) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * fix(deps): update module github.com/docker/cli to v27.3.1+incompatible (#27) * ci: run Linux acceptance tests in parallel (#28) * chore(deps): update module logrusorgru/aurora to v4 (major) (#29) github.com/logrusorgru/aurora changed the license from the WTFPL to the Unlicense due to pkg.go.dev restrictions. Refs https://github.com/logrusorgru/aurora/commit/304bc2c7ed50ec832069fe4a88f35c4bc128eb45 * build: squash Docker image layers (#30) Refs wagoodman/dive#535 * chore(deps): update Docker CLI version to 27.3.1 (#31) * ci: update GoReleaser configuration (#33) * chore(deps): update GoReleaser to v2.4.4 (major) (#32) * revert: run Linux acceptance tests in parallel This reverts commit 19714728c90082c6922a0e098a64ae08c1ef94f0. * ci: fix image name, actions/checkout@v4, docker/login-action name * fix: create `~/.docker` directory in Docker images (#34) Error: ``` ❯ docker run -ti --rm -v /var/run/docker.sock:/var/run/docker.sock joschi/dive:0.13.0-alpha.1 busybox:latest Image Source: docker://busybox:latest Extracting image from docker-engine... (this can take a while for large images) > could not determine docker host: stat /root/.docker: no such file or directory cannot fetch image unable to parse docker host `` ``` * build: added ppc64le support (wagoodman/dive#551) Co-authored-by: Pooja Shah <53046887+pooja0805@users.noreply.github.com> * fix: fix OCI format, GZIP file can be <1024 bytes (wagoodman/dive#511) Fixes wagoodman/dive#507 Fixes wagoodman/dive#510 Fixes wagoodman/dive#526 Fixes wagoodman/dive#534 Co-authored-by: Maddog2050 <17902029+Maddog2050@users.noreply.github.com> * docs: fix typos (wagoodman/dive#531) Found via `codespell -L ot,te` and `typos --hidden --format brief` Closes wagoodman/dive#464 Co-authored-by: Kian-Meng Ang * docs: add more Windows installation options to the README (wagoodman/dive#470) Fixes wagoodman/dive#346 Co-authored-by: Nikolas Grottendieck * docs: Update README.md (wagoodman/dive#506) Made copy and paste easy for zsh users - auto escape character issue Co-authored-by: YóUnǎi * docs: update the install command to the latest Go version (wagoodman/dive#509) `go get` no longer works with recent versions of `go`. Co-authored-by: Trevor Gross * chore: remove repetitive words (wagoodman/dive#515) Co-authored-by: thirdkeyword Signed-off-by: thirdkeyword * fix: close tmp files (wagoodman/dive#517) Co-authored-by: guoguangwu Signed-off-by: guoguangwu * chore: fix phony ci-release target (wagoodman/dive#530) Co-authored-by: Richard Steinmetz * docs: warning message for Snap approach on Ubuntu/Debian (wagoodman/dive#552) https://github.com/wagoodman/dive/issues/546 demonstrates the trouble it may cause. Co-authored-by: Zhang Yuanfeng <71358306+YuanfengZhang@users.noreply.github.com> * docs: update curl commands in README (wagoodman/dive#533) Co-authored-by: Ali Afsharzadeh * feat: improve "Fetching" message (wagoodman/dive#482) The Fetching... message was confusing. This replaces it with a clearer messages to avoid confusion. Additional fix: show original error unless image is not found Only try doing a pull if the image isn't found. Everything else should just generate the error so the user can fix it. Fixes wagoodman/dive#360 Co-authored-by: Christian Höltje * fix: line wrap toggle now updates status bar indicator (wagoodman/dive#497) Fixes wagoodman/dive#496 Co-authored-by: Scott Moore * feat: show setuid, setgid and sticky attributes (wagoodman/dive#524) See https://en.wikipedia.org/wiki/File-system_permissions#Notation_of_traditional_Unix_permissions Co-authored-by: Alexander Yastrebov * feat(docker): Honor the host specified in current docker context (wagoodman/dive#490) This patch adds support for detecting the "docker host" to connect to which is set in the current docker context. One can have multiple such contexts and one of them can be activated via `docker context use `. Fixes wagoodman/dive#397 Fixes wagoodman/dive#408 Fixes wagoodman/dive#412 Fixes wagoodman/dive#463 Fixes wagoodman/dive#495 Co-authored-by: Rajiv Kushwaha * fix: can't inspect ubuntu:24.04 with Podman (wagoodman/dive#476) The problem was caused by `net/url.Parse()`: ``` panic: parse "podman://ubuntu:24.04": invalid port ":24.04" after host ``` Failure: ``` $ ./dive podman://ubuntu:24.04 Image Source: docker://podman://ubuntu:24.04 Fetching image... (this can take a while for large images) Handler not available locally. Trying to pull 'podman://ubuntu:24.04'... cannot fetch image cannot find docker client executable ``` Success: ``` $ ./dive podman://ubuntu:24.04 Image Source: podman://ubuntu:24.04 ... $ ./dive ubuntu:24.04 --source podman Image Source: podman://ubuntu:24.04 ... $ ./dive podman://ubuntu:24.04 --source docker Image Source: podman://ubuntu:24.04 ... ``` Fixes wagoodman/dive#475 Co-authored-by: Anatoli Babenia * fix: enable layer scrolling (wagoodman/dive#521) Fixes wagoodman/dive#469 Fixes wagoodman/dive#494 Fixes wagoodman/dive#540 Refs wagoodman/dive#473 Refs wagoodman/dive#478 Refs wagoodman/dive#520 Co-authored-by: st-gr <38470677+st-gr@users.noreply.github.com> * feat: add layer-wise filesystem information to the analysis json file (wagoodman/dive#458) Add layer-wise filesystem information to the analysis which is written to a JSON file when running dive with `-j` or `--json` flag. Co-authored-by: Akash Nayak * feat: add size to Layer Details view (wagoodman/dive#522) Refs https://github.com/wagoodman/dive/issues/469#issuecomment-1685322270 Co-authored-by: st-gr <38470677+st-gr@users.noreply.github.com> * feat: add CTRL+e for extracting current focused file Refs wagoodman/dive#224 Co-authored-by: kaedwen * feat: vim-like arrow, scroll, and close filtering motions (wagoodman/dive#501) * Adding configurable keybindings for up/down arrows (`k` and `j` vim motions can be used as alternative to up/down arrows). Thanks to @gwendolyngoetz for implementing this feature [Adding configurable keybindings for up/down arrows #499](https://github.com/wagoodman/dive/pull/499) * Add configurable keybindings for left/right arrows (`h` and `l` vim motions can be used as alternative to left/right arrows) * Add `u` and `d` keys for page up/down alternatives (I didn't want to replace default `ctrl+u` toggle-unmodified-files keybinding so I used`u` and `d` like `Vimium` extension ) * Add `esc` key to close filtering (Implemented a new method by utilizing the existing toggle filter method, without touching its current behavior) Refs wagoodman/dive#129 Refs wagoodman/dive#415 Refs wagoodman/dive#499 Co-authored-by: Gwendolyn Goetz Co-authored-by: Mehmet Ümit Özden * fix: gracefully check for Docker configuration (#37) Refs https://github.com/jesseduffield/lazydocker/pull/489 * refactor!: migrate Go module from wagoodman/dive to joschi/dive (#36) * refactor!: migrate Go module from wagoodman/dive to joschi/dive * fix: bring back :latest Docker image * feat: create multi-arch container images with AMD64 and ARM64 (#38) * feat: create multi-arch container images with AMD64 and ARM64 * fix: use joschi/dive:latest-amd64 in CI * ci: use correct container registry in `ci-test-docker-image` (#39) * docs: fix Homebrew instructions in README * fix(deps): update module golang.org/x/net to v0.31.0 (#40) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * chore(deps): update suzuki-shunsuke/github-action-renovate-config-validator action to v1.1.1 (#43) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * fix(deps): update module golang.org/x/net to v0.32.0 (#44) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * chore(deps): update alpine docker tag to v3.21 (#45) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * fix(deps): update module github.com/docker/cli to v27.4.1+incompatible (#46) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * fix(deps): update module golang.org/x/net to v0.33.0 (#48) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * fix(deps): update module github.com/docker/docker to v27.4.1+incompatible (#47) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * fix(deps): update module golang.org/x/net to v0.34.0 (#49) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * fix(deps): update module github.com/spf13/afero to v1.12.0 (#50) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * fix(deps): update module github.com/docker/cli to v27.5.0+incompatible (#51) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * fix(deps): update module github.com/docker/docker to v27.5.0+incompatible (#52) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * fix(deps): update module github.com/docker/cli to v27.5.1+incompatible (#54) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * fix(deps): update module github.com/docker/docker to v27.5.1+incompatible (#55) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * feat: support oci zstd compression (#53) Co-authored-by: Jochen Schalanda * fix(deps): update module github.com/klauspost/compress to v1.17.11 (#57) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * fix(deps): update module golang.org/x/net to v0.35.0 (#59) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * fix(deps): update module github.com/spf13/cobra to v1.9.0 (#60) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * fix(deps): update module github.com/spf13/cobra to v1.9.1 (#62) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * fix(deps): update module github.com/klauspost/compress to v1.18.0 (#63) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * fix(deps): update module github.com/docker/cli to v28 (#64) * fix(deps): update module github.com/docker/cli to v28 * Replace deprecated function in engineResolver.fetchArchive() --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Jochen Schalanda * build: bump Go toolchain from 1.23.x to 1.24.x (#61) * build: bump Go toolchain from 1.23.x to 1.24.x * chore(deps): bump golangci-lint from v1.61.0 to v1.64.5 * fix: non-constant format string in call to (*testing.common).Errorf * test: add layer_set_state_test * fix(deps): update module github.com/docker/cli to v28.0.1+incompatible (#66) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * fix(deps): update module github.com/docker/docker to v28.0.1+incompatible (#65) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * fix(deps): update module golang.org/x/net to v0.36.0 (#73) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * fix(deps): update module golang.org/x/net to v0.37.0 (#74) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * fix(deps): update module github.com/spf13/viper to v1.20.0 (#76) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * fix(deps): update module github.com/spf13/afero to v1.14.0 (#75) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * refactor!: revert module name back to github.com/wagoodman/dive Partially reverts e46f931a8ab3e245051e6e201afd3dfe3499c6c0 * chore: remove Renovate configuration Reverts aa75fbf36f1664b8605a2573faabe4e3429bde6c * Revert "ci: validate Renovate configuration file on build" This reverts commit 8d938774e2a8c298fe41301e9be9f047d4c190d8. --------- Signed-off-by: thirdkeyword Signed-off-by: guoguangwu Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Pooja Shah <53046887+pooja0805@users.noreply.github.com> Co-authored-by: Maddog2050 <17902029+Maddog2050@users.noreply.github.com> Co-authored-by: Kian-Meng Ang Co-authored-by: Nikolas Grottendieck Co-authored-by: YóUnǎi Co-authored-by: Trevor Gross Co-authored-by: thirdkeyword Co-authored-by: guoguangwu Co-authored-by: Richard Steinmetz Co-authored-by: Zhang Yuanfeng <71358306+YuanfengZhang@users.noreply.github.com> Co-authored-by: Ali Afsharzadeh Co-authored-by: Christian Höltje Co-authored-by: Scott Moore Co-authored-by: Alexander Yastrebov Co-authored-by: Rajiv Kushwaha Co-authored-by: Anatoli Babenia Co-authored-by: st-gr <38470677+st-gr@users.noreply.github.com> Co-authored-by: Akash Nayak Co-authored-by: kaedwen Co-authored-by: Gwendolyn Goetz Co-authored-by: Mehmet Ümit Özden Co-authored-by: steven-halaka --- .bouncer.yaml | 1 + .data/Dockerfile.minimal | 2 + .data/test-estargz-image.tar | Bin 0 -> 12800 bytes .data/test-gzip-image.tar | Bin 0 -> 12800 bytes .data/test-oci-estargz-image.tar | Bin 0 -> 11776 bytes .data/test-oci-gzip-image.tar | Bin 0 -> 11264 bytes .data/test-oci-uncompressed-image.tar | Bin 0 -> 19456 bytes .data/test-oci-zstd-image.tar | Bin 0 -> 11264 bytes .data/test-uncompressed-image.tar | Bin 0 -> 12800 bytes .data/test-zstd-image.tar | Bin 0 -> 12800 bytes .github/actions/bootstrap/action.yaml | 10 +- .github/workflows/release.yaml | 36 +- .github/workflows/validations.yaml | 44 +- .golangci.yaml | 4 +- .goreleaser.yaml | 46 +- Dockerfile | 8 +- Makefile | 66 +- README.md | 81 +- cmd/root.go | 10 +- dive/filetree/efficiency.go | 2 +- dive/filetree/efficiency_test.go | 4 +- dive/filetree/file_info.go | 20 +- dive/filetree/file_node.go | 26 +- dive/filetree/file_tree.go | 2 +- dive/filetree/node_data.go | 2 +- dive/filetree/node_data_test.go | 4 +- dive/get_image_resolver.go | 10 +- dive/image/docker/archive_resolver.go | 9 + dive/image/docker/build.go | 1 + dive/image/docker/config.go | 9 + dive/image/docker/docker_host_unix.go | 7 + dive/image/docker/docker_host_windows.go | 5 + dive/image/docker/engine_resolver.go | 109 +- dive/image/docker/image_archive.go | 155 +- dive/image/podman/build.go | 1 + dive/image/podman/resolver.go | 20 + dive/image/podman/resolver_unsupported.go | 8 + dive/image/resolver.go | 2 + go.mod | 84 +- go.sum | 321 +- runtime/ci/evaluator.go | 2 +- runtime/ci/rule.go | 2 +- runtime/export/export.go | 14 + runtime/export/export_test.go | 4567 ++++++++++++++++- runtime/export/layer.go | 15 +- runtime/run.go | 4 +- runtime/run_test.go | 30 +- runtime/ui/app.go | 20 +- runtime/ui/controller.go | 30 +- runtime/ui/key/binding.go | 2 +- .../layout/compound/layer_details_column.go | 2 +- runtime/ui/view/debug.go | 2 +- runtime/ui/view/filetree.go | 67 +- runtime/ui/view/filter.go | 2 +- runtime/ui/view/image_details.go | 12 +- runtime/ui/view/layer.go | 31 +- runtime/ui/view/layer_details.go | 17 +- runtime/ui/view/status.go | 2 +- runtime/ui/viewmodel/filetree.go | 7 +- runtime/ui/viewmodel/filetree_test.go | 2 +- runtime/ui/viewmodel/layer_set_state_test.go | 52 + .../testdata/TestFileShowAggregateChanges.txt | 2 +- .../testdata/TestFileTreeDirCollapse.txt | 2 +- .../testdata/TestFileTreeDirCollapseAll.txt | 2 +- .../testdata/TestFileTreeDirCursorRight.txt | 2 +- .../viewmodel/testdata/TestFileTreeGoCase.txt | 2 +- .../TestFileTreeHideAddedRemovedModified.txt | 2 +- .../testdata/TestFileTreeSelectLayer.txt | 2 +- utils/format.go | 2 +- 69 files changed, 5546 insertions(+), 461 deletions(-) create mode 100644 .data/Dockerfile.minimal create mode 100644 .data/test-estargz-image.tar create mode 100644 .data/test-gzip-image.tar create mode 100644 .data/test-oci-estargz-image.tar create mode 100644 .data/test-oci-gzip-image.tar create mode 100644 .data/test-oci-uncompressed-image.tar create mode 100644 .data/test-oci-zstd-image.tar create mode 100644 .data/test-uncompressed-image.tar create mode 100644 .data/test-zstd-image.tar create mode 100644 dive/image/docker/docker_host_unix.go create mode 100644 dive/image/docker/docker_host_windows.go create mode 100644 runtime/ui/viewmodel/layer_set_state_test.go diff --git a/.bouncer.yaml b/.bouncer.yaml index 9ee3121..659945a 100644 --- a/.bouncer.yaml +++ b/.bouncer.yaml @@ -5,6 +5,7 @@ permit: - MPL.* - ISC - WTFPL + - Unlicense ignore-packages: # crypto/internal/boring is released under the openSSL license as a part of the Golang Standard Library diff --git a/.data/Dockerfile.minimal b/.data/Dockerfile.minimal new file mode 100644 index 0000000..25e48f0 --- /dev/null +++ b/.data/Dockerfile.minimal @@ -0,0 +1,2 @@ +FROM scratch +COPY README.md /README.md diff --git a/.data/test-estargz-image.tar b/.data/test-estargz-image.tar new file mode 100644 index 0000000000000000000000000000000000000000..b67281ed4a5cd00e4941bab14e86724df03fcd01 GIT binary patch literal 12800 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 literal 0 HcmV?d00001 diff --git a/.data/test-oci-uncompressed-image.tar b/.data/test-oci-uncompressed-image.tar new file mode 100644 index 0000000000000000000000000000000000000000..8a73c1f98d26ddcba1e1e4e2c0e3c668e0bce366 GIT binary patch literal 19456 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 hMWBoTnr*k`BDB37jJr{H6u6_n9R=u; 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 S^1v?-{PMsr5By*E!2bYixoiUf literal 0 HcmV?d00001 diff --git a/.data/test-uncompressed-image.tar b/.data/test-uncompressed-image.tar new file mode 100644 index 0000000000000000000000000000000000000000..b67281ed4a5cd00e4941bab14e86724df03fcd01 GIT binary patch literal 12800 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 Tza{+pg}-y)cMkkt ``` -or you can dive with docker command directly +or you can dive with Docker directly: ``` -alias dive="docker run -ti --rm -v /var/run/docker.sock:/var/run/docker.sock wagoodman/dive" +alias dive="docker run -ti --rm -v /var/run/docker.sock:/var/run/docker.sock ghcr.io/wagoodman/dive" dive # for example @@ -29,7 +29,7 @@ or if you want to build your image then jump straight into analyzing it: dive build -t . ``` -Building on Macbook (supporting only the Docker container engine) +Building on macOS (supporting only the Docker container engine): ```bash docker run --rm -it \ @@ -37,7 +37,7 @@ docker run --rm -it \ -v "$(pwd)":"$(pwd)" \ -w "$(pwd)" \ -v "$HOME/.dive.yaml":"$HOME/.dive.yaml" \ - wagoodman/dive:latest build -t . + ghcr.io/wagoodman/dive:latest build -t . ``` Additionally you can run this in your CI pipeline to ensure you're keeping wasted space to a minimum (this skips the UI): @@ -98,7 +98,7 @@ With valid `source` options as such: Using debs: ```bash DIVE_VERSION=$(curl -sL "https://api.github.com/repos/wagoodman/dive/releases/latest" | grep '"tag_name":' | sed -E 's/.*"v([^"]+)".*/\1/') -curl -OL https://github.com/wagoodman/dive/releases/download/v${DIVE_VERSION}/dive_${DIVE_VERSION}_linux_amd64.deb +curl -fOL "https://github.com/wagoodman/dive/releases/download/v${DIVE_VERSION}/dive_${DIVE_VERSION}_linux_amd64.deb" sudo apt install ./dive_${DIVE_VERSION}_linux_amd64.deb ``` @@ -110,10 +110,16 @@ sudo snap connect dive:docker-executables docker:docker-executables sudo snap connect dive:docker-daemon docker:docker-daemon ``` +> [!CAUTION] +> The Snap method is not recommended if you installed Docker via `apt-get`, since it might break your existing Docker daemon. +> +> See also: https://github.com/wagoodman/dive/issues/546 + + **RHEL/Centos** ```bash DIVE_VERSION=$(curl -sL "https://api.github.com/repos/wagoodman/dive/releases/latest" | grep '"tag_name":' | sed -E 's/.*"v([^"]+)".*/\1/') -curl -OL https://github.com/wagoodman/dive/releases/download/v${DIVE_VERSION}/dive_${DIVE_VERSION}_linux_amd64.rpm +curl -fOL "https://github.com/wagoodman/dive/releases/download/v${DIVE_VERSION}/dive_${DIVE_VERSION}_linux_amd64.rpm" rpm -i dive_${DIVE_VERSION}_linux_amd64.rpm ``` @@ -130,7 +136,8 @@ pacman -S dive If you use [Homebrew](https://brew.sh): ```bash -brew install dive +brew tap wagoodman/dive +brew install wagoodman/dive/dive ``` If you use [MacPorts](https://www.macports.org): @@ -143,13 +150,31 @@ Or download the latest Darwin build from the [releases page](https://github.com/ **Windows** -Download the [latest release](https://github.com/wagoodman/dive/releases/latest). +If you use [Chocolatey](https://chocolatey.org) + +```powershell +choco install dive +``` + +If you use [scoop](https://scoop.sh/) + +```powershell +scoop install main/dive +``` + +If you use [winget](https://learn.microsoft.com/en-gb/windows/package-manager/): + +```powershell +winget install --id wagoodman.dive +``` + +Or download the latest Windows build from the [releases page](https://github.com/wagoodman/dive/releases/latest). **Go tools** Requires Go version 1.10 or higher. ```bash -go get github.com/wagoodman/dive +go install github.com/wagoodman/dive@latest ``` *Note*: installing in this way you will not see a proper version when running `dive -v`. @@ -166,27 +191,21 @@ nix-env -iA nixpkgs.dive **Docker** ```bash -docker pull wagoodman/dive +docker pull ghcr.io/wagoodman/dive ``` -or - -```bash -docker pull quay.io/wagoodman/dive -``` - -When running you'll need to include the docker socket file: +When running you'll need to include the Docker socket file: ```bash docker run --rm -it \ -v /var/run/docker.sock:/var/run/docker.sock \ - wagoodman/dive:latest + ghcr.io/wagoodman/dive:latest ``` Docker for Windows (showing PowerShell compatible line breaks; collapse to a single line for Command Prompt compatibility) ```bash docker run --rm -it ` -v /var/run/docker.sock:/var/run/docker.sock ` - wagoodman/dive:latest + ghcr.io/wagoodman/dive:latest ``` **Note:** depending on the version of docker you are running locally you may need to specify the docker API version as an environment variable: @@ -198,7 +217,7 @@ or if you are running with a docker image: docker run --rm -it \ -v /var/run/docker.sock:/var/run/docker.sock \ -e DOCKER_API_VERSION=1.37 \ - wagoodman/dive:latest + ghcr.io/wagoodman/dive:latest ``` ## CI Integration @@ -228,8 +247,11 @@ Key Binding | Description Ctrl + C or Q | Exit Tab | Switch between the layer and filetree views Ctrl + F | Filter files -PageUp | Scroll up a page -PageDown | Scroll down a page +ESC | Close filter files +PageUp or U | Scroll up a page +PageDown or D | Scroll down a page +Up or K | Move up one line within a page +Down or J | Move down one line within a page Ctrl + A | Layer view: see aggregated image modifications Ctrl + L | Layer view: see current layer modifications Space | Filetree view: collapse/uncollapse a directory @@ -239,8 +261,8 @@ Key Binding | Description Ctrl + M | Filetree view: show/hide modified files Ctrl + U | Filetree view: show/hide unmodified files Ctrl + B | Filetree view: show/hide file attributes -PageUp | Filetree view: scroll up a page -PageDown | Filetree view: scroll down a page +PageUp or U | Filetree view: scroll up a page +PageDown or D | Filetree view: scroll down a page ## UI Configuration @@ -262,6 +284,11 @@ keybinding: quit: ctrl+c toggle-view: tab filter-files: ctrl+f, ctrl+slash + close-filter-files: esc + up: up,k + down: down,j + left: left,h + right: right,l # Layer view specific bindings compare-all: ctrl+a @@ -275,8 +302,8 @@ keybinding: toggle-modified-files: ctrl+m toggle-unmodified-files: ctrl+u toggle-filetree-attributes: ctrl+b - page-up: pgup - page-down: pgdn + page-up: pgup,u + page-down: pgdn,d diff: # You can change the default files shown in the filetree (right pane). All diff types are shown by default. diff --git a/cmd/root.go b/cmd/root.go index 5074bbe..ad9f10b 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -80,6 +80,7 @@ func initConfig() { viper.SetDefault("keybinding.quit", "ctrl+c,q") viper.SetDefault("keybinding.toggle-view", "tab") viper.SetDefault("keybinding.filter-files", "ctrl+f, ctrl+slash") + viper.SetDefault("keybinding.close-filter-files", "esc") // keybindings: layer view viper.SetDefault("keybinding.compare-all", "ctrl+a") viper.SetDefault("keybinding.compare-layer", "ctrl+l") @@ -93,8 +94,13 @@ func initConfig() { viper.SetDefault("keybinding.toggle-modified-files", "ctrl+m") viper.SetDefault("keybinding.toggle-unmodified-files", "ctrl+u") viper.SetDefault("keybinding.toggle-wrap-tree", "ctrl+p") - viper.SetDefault("keybinding.page-up", "pgup") - viper.SetDefault("keybinding.page-down", "pgdn") + viper.SetDefault("keybinding.extract-file", "ctrl+e") + viper.SetDefault("keybinding.page-up", "pgup,u") + viper.SetDefault("keybinding.page-down", "pgdn,d") + viper.SetDefault("keybinding.up", "up,k") + viper.SetDefault("keybinding.down", "down,j") + viper.SetDefault("keybinding.left", "left,h") + viper.SetDefault("keybinding.right", "right,l") viper.SetDefault("diff.hide", "") diff --git a/dive/filetree/efficiency.go b/dive/filetree/efficiency.go index d1244d1..7713b1e 100644 --- a/dive/filetree/efficiency.go +++ b/dive/filetree/efficiency.go @@ -65,7 +65,7 @@ func Efficiency(trees []*FileTree) (float64, EfficiencySlice) { stackedTree, failedPaths, err := StackTreeRange(trees, 0, currentTree-1) if len(failedPaths) > 0 { for _, path := range failedPaths { - logrus.Errorf(path.String()) + logrus.Errorf("%s", path.String()) } } if err != nil { diff --git a/dive/filetree/efficiency_test.go b/dive/filetree/efficiency_test.go index 831ceb5..1662efa 100644 --- a/dive/filetree/efficiency_test.go +++ b/dive/filetree/efficiency_test.go @@ -10,7 +10,7 @@ func checkError(t *testing.T, err error, message string) { } } -func TestEfficency(t *testing.T) { +func TestEfficiency(t *testing.T) { trees := make([]*FileTree, 3) for idx := range trees { trees[idx] = NewFileTree() @@ -56,7 +56,7 @@ func TestEfficency(t *testing.T) { } } -func TestEfficency_ScratchImage(t *testing.T) { +func TestEfficiency_ScratchImage(t *testing.T) { trees := make([]*FileTree, 3) for idx := range trees { trees[idx] = NewFileTree() diff --git a/dive/filetree/file_info.go b/dive/filetree/file_info.go index bc5f302..3fc8786 100644 --- a/dive/filetree/file_info.go +++ b/dive/filetree/file_info.go @@ -5,21 +5,21 @@ import ( "io" "os" - "github.com/cespare/xxhash" + "github.com/cespare/xxhash/v2" "github.com/sirupsen/logrus" ) // FileInfo contains tar metadata for a specific FileNode type FileInfo struct { - Path string - TypeFlag byte - Linkname string - hash uint64 - Size int64 - Mode os.FileMode - Uid int - Gid int - IsDir bool + Path string `json:"path"` + TypeFlag byte `json:"typeFlag"` + Linkname string `json:"linkName"` + hash uint64 //`json:"hash"` + Size int64 `json:"size"` + Mode os.FileMode `json:"fileMode"` + Uid int `json:"uid"` + Gid int `json:"gid"` + IsDir bool `json:"isDir"` } // NewFileInfoFromTarHeader extracts the metadata from a tar header and file contents and generates a new FileInfo object. diff --git a/dive/filetree/file_node.go b/dive/filetree/file_node.go index 2cb1bb7..88ff29d 100644 --- a/dive/filetree/file_node.go +++ b/dive/filetree/file_node.go @@ -141,11 +141,33 @@ func (node *FileNode) MetadataString() string { return "" } - fileMode := permbits.FileMode(node.Data.FileInfo.Mode).String() dir := "-" if node.Data.FileInfo.IsDir { dir = "d" } + + fm := permbits.FileMode(node.Data.FileInfo.Mode) + var fileMode strings.Builder + fileMode.Grow(9) + cond := func(c bool, x, y byte) byte { + if c { + return x + } else { + return y + } + } + fileMode.WriteByte(cond(fm.UserRead(), 'r', '-')) + fileMode.WriteByte(cond(fm.UserWrite(), 'w', '-')) + fileMode.WriteByte(cond(fm.UserExecute(), cond(fm.Setuid(), 's', 'x'), cond(fm.Setuid(), 'S', '-'))) + + fileMode.WriteByte(cond(fm.GroupRead(), 'r', '-')) + fileMode.WriteByte(cond(fm.GroupWrite(), 'w', '-')) + fileMode.WriteByte(cond(fm.GroupExecute(), cond(fm.Setgid(), 's', 'x'), cond(fm.Setgid(), 'S', '-'))) + + fileMode.WriteByte(cond(fm.OtherRead(), 'r', '-')) + fileMode.WriteByte(cond(fm.OtherWrite(), 'w', '-')) + fileMode.WriteByte(cond(fm.OtherExecute(), cond(fm.Sticky(), 't', 'x'), cond(fm.Sticky(), 'T', '-'))) + user := node.Data.FileInfo.Uid group := node.Data.FileInfo.Gid userGroup := fmt.Sprintf("%d:%d", user, group) @@ -156,7 +178,7 @@ func (node *FileNode) MetadataString() string { size := humanize.Bytes(uint64(sizeBytes)) - return diffTypeColor[node.Data.DiffType].Sprint(fmt.Sprintf(AttributeFormat, dir, fileMode, userGroup, size)) + return diffTypeColor[node.Data.DiffType].Sprint(fmt.Sprintf(AttributeFormat, dir, fileMode.String(), userGroup, size)) } func (node *FileNode) GetSize() int64 { diff --git a/dive/filetree/file_tree.go b/dive/filetree/file_tree.go index b6b0668..d74a350 100644 --- a/dive/filetree/file_tree.go +++ b/dive/filetree/file_tree.go @@ -269,7 +269,7 @@ func (tree *FileTree) AddPath(filepath string, data FileInfo) (*FileNode, []*Fil if node == nil { // the child could not be added - return node, addedNodes, fmt.Errorf(fmt.Sprintf("could not add child node: '%s' (path:'%s')", name, filepath)) + return node, addedNodes, fmt.Errorf("could not add child node: '%s' (path:'%s')", name, filepath) } } diff --git a/dive/filetree/node_data.go b/dive/filetree/node_data.go index 9e12980..e1175e1 100644 --- a/dive/filetree/node_data.go +++ b/dive/filetree/node_data.go @@ -5,7 +5,7 @@ var GlobalFileTreeCollapse bool // NodeData is the payload for a FileNode type NodeData struct { ViewInfo ViewInfo - FileInfo FileInfo + FileInfo FileInfo `json:"fileInfo"` DiffType DiffType } diff --git a/dive/filetree/node_data_test.go b/dive/filetree/node_data_test.go index 351d6df..94e6b08 100644 --- a/dive/filetree/node_data_test.go +++ b/dive/filetree/node_data_test.go @@ -21,13 +21,13 @@ func TestMergeDiffTypes(t *testing.T) { b := Unmodified merged := a.merge(b) if merged != Unmodified { - t.Errorf("Expected Unchaged (0) but got %v", merged) + t.Errorf("Expected Unchanged (0) but got %v", merged) } a = Modified b = Unmodified merged = a.merge(b) if merged != Modified { - t.Errorf("Expected Unchaged (0) but got %v", merged) + t.Errorf("Expected Unchanged (0) but got %v", merged) } } diff --git a/dive/get_image_resolver.go b/dive/get_image_resolver.go index 05d95ab..4d53ed9 100644 --- a/dive/get_image_resolver.go +++ b/dive/get_image_resolver.go @@ -2,7 +2,6 @@ package dive import ( "fmt" - "net/url" "strings" "github.com/wagoodman/dive/dive/image" @@ -41,14 +40,13 @@ func ParseImageSource(r string) ImageSource { } func DeriveImageSource(image string) (ImageSource, string) { - u, err := url.Parse(image) - if err != nil { + s := strings.SplitN(image, "://", 2) + if len(s) < 2 { return SourceUnknown, "" } + scheme, imageSource := s[0], s[1] - imageSource := strings.TrimPrefix(image, u.Scheme+"://") - - switch u.Scheme { + switch scheme { case SourceDockerEngine.String(): return SourceDockerEngine, imageSource case SourcePodmanEngine.String(): diff --git a/dive/image/docker/archive_resolver.go b/dive/image/docker/archive_resolver.go index 8baf4ac..5404837 100644 --- a/dive/image/docker/archive_resolver.go +++ b/dive/image/docker/archive_resolver.go @@ -13,6 +13,11 @@ func NewResolverFromArchive() *archiveResolver { return &archiveResolver{} } +// Name returns the name of the resolver to display to the user. +func (r *archiveResolver) Name() string { + return "docker-archive" +} + func (r *archiveResolver) Fetch(path string) (*image.Image, error) { reader, err := os.Open(path) if err != nil { @@ -30,3 +35,7 @@ func (r *archiveResolver) Fetch(path string) (*image.Image, error) { func (r *archiveResolver) Build(args []string) (*image.Image, error) { return nil, fmt.Errorf("build option not supported for docker archive resolver") } + +func (r *archiveResolver) Extract(id string, l string, p string) error { + return fmt.Errorf("not implemented") +} diff --git a/dive/image/docker/build.go b/dive/image/docker/build.go index 56d0bd8..935029f 100644 --- a/dive/image/docker/build.go +++ b/dive/image/docker/build.go @@ -10,6 +10,7 @@ func buildImageFromCli(buildArgs []string) (string, error) { return "", err } defer os.Remove(iidfile.Name()) + defer iidfile.Close() allArgs := append([]string{"--iidfile", iidfile.Name()}, buildArgs...) err = runDockerCmd("build", allArgs...) diff --git a/dive/image/docker/config.go b/dive/image/docker/config.go index c4ef902..6167a21 100644 --- a/dive/image/docker/config.go +++ b/dive/image/docker/config.go @@ -44,3 +44,12 @@ func newConfig(configBytes []byte) config { return imageConfig } + +func isConfig(configBytes []byte) bool { + var imageConfig config + err := json.Unmarshal(configBytes, &imageConfig) + if err != nil { + return false + } + return imageConfig.RootFs.Type == "layers" +} diff --git a/dive/image/docker/docker_host_unix.go b/dive/image/docker/docker_host_unix.go new file mode 100644 index 0000000..52dae70 --- /dev/null +++ b/dive/image/docker/docker_host_unix.go @@ -0,0 +1,7 @@ +//go:build !windows + +package docker + +const ( + defaultDockerHost = "unix:///var/run/docker.sock" +) diff --git a/dive/image/docker/docker_host_windows.go b/dive/image/docker/docker_host_windows.go new file mode 100644 index 0000000..3174a9d --- /dev/null +++ b/dive/image/docker/docker_host_windows.go @@ -0,0 +1,5 @@ +package docker + +const ( + defaultDockerHost = "npipe:////.pipe/docker_engine" +) diff --git a/dive/image/docker/engine_resolver.go b/dive/image/docker/engine_resolver.go index cf8b109..af47b98 100644 --- a/dive/image/docker/engine_resolver.go +++ b/dive/image/docker/engine_resolver.go @@ -7,7 +7,10 @@ import ( "os" "strings" + cliconfig "github.com/docker/cli/cli/config" "github.com/docker/cli/cli/connhelper" + ddocker "github.com/docker/cli/cli/context/docker" + ctxstore "github.com/docker/cli/cli/context/store" "github.com/docker/docker/client" "golang.org/x/net/context" @@ -20,6 +23,11 @@ func NewResolverFromEngine() *engineResolver { return &engineResolver{} } +// Name returns the name of the resolver to display to the user. +func (r *engineResolver) Name() string { + return "docker-engine" +} + func (r *engineResolver) Fetch(id string) (*image.Image, error) { reader, err := r.fetchArchive(id) if err != nil { @@ -42,6 +50,19 @@ func (r *engineResolver) Build(args []string) (*image.Image, error) { return r.Fetch(id) } +func (r *engineResolver) Extract(id string, l string, p string) error { + reader, err := r.fetchArchive(id) + if err != nil { + return err + } + + if err := ExtractFromImage(io.NopCloser(reader), l, p); err == nil { + return nil + } + + return fmt.Errorf("unable to extract from image '%s': %+v", id, err) +} + func (r *engineResolver) fetchArchive(id string) (io.ReadCloser, error) { var err error var dockerClient *client.Client @@ -49,8 +70,12 @@ func (r *engineResolver) fetchArchive(id string) (io.ReadCloser, error) { // pull the engineResolver if it does not exist ctx := context.Background() - host := os.Getenv("DOCKER_HOST") - var clientOpts []client.Opt + host, err := determineDockerHost() + if err != nil { + fmt.Printf("> could not determine docker host: %v\n", err) + } + clientOpts := []client.Opt{client.FromEnv} + clientOpts = append(clientOpts, client.WithHost(host)) switch strings.Split(host, ":")[0] { case "ssh": @@ -66,7 +91,8 @@ func (r *engineResolver) fetchArchive(id string) (io.ReadCloser, error) { } return client.WithHTTPClient(httpClient)(c) }) - clientOpts = append(clientOpts, client.WithHost(helper.Host)) + + clientOpts = append(clientOpts, client.WithHost(host)) clientOpts = append(clientOpts, client.WithDialContext(helper.Dialer)) default: @@ -74,8 +100,6 @@ func (r *engineResolver) fetchArchive(id string) (io.ReadCloser, error) { if os.Getenv("DOCKER_TLS_VERIFY") != "" && os.Getenv("DOCKER_CERT_PATH") == "" { os.Setenv("DOCKER_CERT_PATH", "~/.docker") } - - clientOpts = append(clientOpts, client.FromEnv) } clientOpts = append(clientOpts, client.WithAPIVersionNegotiation()) @@ -83,12 +107,17 @@ func (r *engineResolver) fetchArchive(id string) (io.ReadCloser, error) { if err != nil { return nil, err } - _, _, err = dockerClient.ImageInspectWithRaw(ctx, id) + _, err = dockerClient.ImageInspect(ctx, id) if err != nil { - // don't use the API, the CLI has more informative output - fmt.Println("Handler not available locally. Trying to pull '" + id + "'...") - err = runDockerCmd("pull", id) - if err != nil { + // check if the error is due to the image not existing locally + if client.IsErrNotFound(err) { + fmt.Println("The image is not available locally. Trying to pull '" + id + "'...") + err = runDockerCmd("pull", id) + if err != nil { + return nil, err + } + } else { + // Some other error occurred, return it return nil, err } } @@ -100,3 +129,63 @@ func (r *engineResolver) fetchArchive(id string) (io.ReadCloser, error) { return readCloser, nil } + +// determineDockerHost tries to the determine the docker host that we should connect to +// in the following order of decreasing precedence: +// - value of "DOCKER_HOST" environment variable +// - host retrieved from the current context (specified via DOCKER_CONTEXT) +// - "default docker host" for the host operating system, otherwise +func determineDockerHost() (string, error) { + // If the docker host is explicitly set via the "DOCKER_HOST" environment variable, + // then its a no-brainer :shrug: + if os.Getenv("DOCKER_HOST") != "" { + return os.Getenv("DOCKER_HOST"), nil + } + + currentContext := os.Getenv("DOCKER_CONTEXT") + if currentContext == "" { + cf, err := cliconfig.Load(cliconfig.Dir()) + if err != nil { + return "", err + } + currentContext = cf.CurrentContext + } + + if currentContext == "" { + // If a docker context is neither specified via the "DOCKER_CONTEXT" environment variable nor via the + // $HOME/.docker/config file, then we fall back to connecting to the "default docker host" meant for + // the host operating system. + return defaultDockerHost, nil + } + + storeConfig := ctxstore.NewConfig( + func() interface{} { return &ddocker.EndpointMeta{} }, + ctxstore.EndpointTypeGetter(ddocker.DockerEndpoint, func() interface{} { return &ddocker.EndpointMeta{} }), + ) + + st := ctxstore.New(cliconfig.ContextStoreDir(), storeConfig) + md, err := st.GetMetadata(currentContext) + if err != nil { + return "", err + } + dockerEP, ok := md.Endpoints[ddocker.DockerEndpoint] + if !ok { + return "", err + } + dockerEPMeta, ok := dockerEP.(ddocker.EndpointMeta) + if !ok { + return "", fmt.Errorf("expected docker.EndpointMeta, got %T", dockerEP) + } + + if dockerEPMeta.Host != "" { + return dockerEPMeta.Host, nil + } + + // We might end up here, if the context was created with the `host` set to an empty value (i.e. ''). + // For example: + // ```sh + // docker context create foo --docker "host=" + // ``` + // In such scenario, we mimic the `docker` cli and try to connect to the "default docker host". + return defaultDockerHost, nil +} diff --git a/dive/image/docker/image_archive.go b/dive/image/docker/image_archive.go index 827b58b..f2c018b 100644 --- a/dive/image/docker/image_archive.go +++ b/dive/image/docker/image_archive.go @@ -9,8 +9,11 @@ import ( "io" "os" "path" + "path/filepath" "strings" + "github.com/klauspost/compress/zstd" + "github.com/wagoodman/dive/dive/filetree" "github.com/wagoodman/dive/dive/image" ) @@ -93,25 +96,21 @@ func NewImageArchive(tarFile io.ReadCloser) (*ImageArchive, error) { // never consume more bytes than this buffer contains so we can start again. // 512 bytes ought to be enough (as that's the size of a TAR entry header), - // but play it safe with 1024 bytes. This should also include very small layers - // (unless they've also been gzipped, but Docker does not appear to do it) + // but play it safe with 1024 bytes. This should also include very small layers. buffer := make([]byte, 1024) n, err := io.ReadFull(tarReader, buffer) if err != nil && err != io.ErrUnexpectedEOF { return img, err } - // Only try reading a TAR if file is "big enough" - if n == cap(buffer) { - var unwrappedReader io.Reader - unwrappedReader, err = gzip.NewReader(io.MultiReader(bytes.NewReader(buffer[:n]), tarReader)) - if err != nil { - // Not a gzipped entry - unwrappedReader = io.MultiReader(bytes.NewReader(buffer[:n]), tarReader) - } + originalReader := func() io.Reader { + return io.MultiReader(bytes.NewReader(buffer[:n]), tarReader) + } - // Try reading a TAR - layerReader := tar.NewReader(unwrappedReader) + // Try reading a gzip/estargz compressed layer + gzipReader, err := gzip.NewReader(originalReader()) + if err == nil { + layerReader := tar.NewReader(gzipReader) tree, err := processLayerTar(name, layerReader) if err == nil { currentLayer++ @@ -121,13 +120,36 @@ func NewImageArchive(tarFile io.ReadCloser) (*ImageArchive, error) { } } - // Not a TAR (or smaller than our buffer), might be a JSON file + // Try reading a zstd compressed layer + zstdReader, err := zstd.NewReader(originalReader()) + if err == nil { + layerReader := tar.NewReader(zstdReader) + tree, err := processLayerTar(name, layerReader) + if err == nil { + currentLayer++ + // add the layer to the image + img.layerMap[tree.Name] = tree + continue + } + } + + // Try reading a plain tar layer + layerReader := tar.NewReader(originalReader()) + tree, err := processLayerTar(name, layerReader) + if err == nil { + currentLayer++ + // add the layer to the image + img.layerMap[tree.Name] = tree + continue + } + + // Not a TAR/GZIP/ZSTD, might be a JSON file decoder := json.NewDecoder(bytes.NewReader(buffer[:n])) token, err := decoder.Token() if _, ok := token.(json.Delim); err == nil && ok { // Looks like a JSON object (or array) // XXX: should we add a header.Size check too? - fileBuffer, err := io.ReadAll(io.MultiReader(bytes.NewReader(buffer[:n]), tarReader)) + fileBuffer, err := io.ReadAll(originalReader()) if err != nil { return img, err } @@ -139,11 +161,31 @@ func NewImageArchive(tarFile io.ReadCloser) (*ImageArchive, error) { } manifestContent, exists := jsonFiles["manifest.json"] - if !exists { - return img, fmt.Errorf("could not find image manifest") - } + if exists { + img.manifest = newManifest(manifestContent) + } else { + // manifest.json is not part of the OCI spec, docker includes it for compatibility + // Provide compatibility by finding the config and using our layerMap + var configPath string + for path, content := range jsonFiles { + if isConfig(content) { + configPath = path + break + } + } + if len(configPath) == 0 { + return img, fmt.Errorf("could not find image manifest") + } - img.manifest = newManifest(manifestContent) + var layerPaths []string + for k := range img.layerMap { + layerPaths = append(layerPaths, k) + } + img.manifest = manifest{ + ConfigPath: configPath, + LayerTarPaths: layerPaths, + } + } configContent, exists := jsonFiles[img.manifest.ConfigPath] if !exists { @@ -256,3 +298,80 @@ func (img *ImageArchive) ToImage() (*image.Image, error) { Layers: layers, }, nil } + +func ExtractFromImage(tarFile io.ReadCloser, l string, p string) error { + tarReader := tar.NewReader(tarFile) + + for { + header, err := tarReader.Next() + + if err == io.EOF { + break + } + + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + name := header.Name + + switch header.Typeflag { + case tar.TypeReg: + if name == l { + err = extractInner(tar.NewReader(tarReader), p) + if err != nil { + return err + } + return nil + } + default: + continue + } + } + + return nil +} + +func extractInner(reader *tar.Reader, p string) error { + target := strings.TrimPrefix(p, "/") + + for { + header, err := reader.Next() + + if err == io.EOF { + break + } + + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + name := header.Name + + switch header.Typeflag { + case tar.TypeReg: + if strings.HasPrefix(name, target) { + err := os.MkdirAll(filepath.Dir(name), 0755) + if err != nil { + return err + } + + out, err := os.Create(name) + if err != nil { + return err + } + + _, err = io.Copy(out, reader) + if err != nil { + return err + } + } + default: + continue + } + } + + return nil +} diff --git a/dive/image/podman/build.go b/dive/image/podman/build.go index 9909c88..17c188a 100644 --- a/dive/image/podman/build.go +++ b/dive/image/podman/build.go @@ -13,6 +13,7 @@ func buildImageFromCli(buildArgs []string) (string, error) { return "", err } defer os.Remove(iidfile.Name()) + defer iidfile.Close() allArgs := append([]string{"--iidfile", iidfile.Name()}, buildArgs...) err = runPodmanCmd("build", allArgs...) diff --git a/dive/image/podman/resolver.go b/dive/image/podman/resolver.go index e34efd6..80852a6 100644 --- a/dive/image/podman/resolver.go +++ b/dive/image/podman/resolver.go @@ -17,6 +17,11 @@ func NewResolverFromEngine() *resolver { return &resolver{} } +// Name returns the name of the resolver to display to the user. +func (r *resolver) Name() string { + return "podman" +} + func (r *resolver) Build(args []string) (*image.Image, error) { id, err := buildImageFromCli(args) if err != nil { @@ -36,6 +41,21 @@ func (r *resolver) Fetch(id string) (*image.Image, error) { return nil, fmt.Errorf("unable to resolve image '%s': %+v", id, err) } +func (r *resolver) Extract(id string, l string, p string) error { + // todo: add podman fetch attempt via varlink first... + + err, reader := streamPodmanCmd("image", "save", id) + if err != nil { + return err + } + + if err := docker.ExtractFromImage(io.NopCloser(reader), l, p); err == nil { + return nil + } + + return fmt.Errorf("unable to extract from image '%s': %+v", id, err) +} + func (r *resolver) resolveFromDockerArchive(id string) (*image.Image, error) { err, reader := streamPodmanCmd("image", "save", id) if err != nil { diff --git a/dive/image/podman/resolver_unsupported.go b/dive/image/podman/resolver_unsupported.go index 4834e1d..120f62e 100644 --- a/dive/image/podman/resolver_unsupported.go +++ b/dive/image/podman/resolver_unsupported.go @@ -15,6 +15,10 @@ func NewResolverFromEngine() *resolver { return &resolver{} } +// Name returns the name of the resolver to display to the user. +func (r *resolver) Name() string { + return "podman" +} func (r *resolver) Build(args []string) (*image.Image, error) { return nil, fmt.Errorf("unsupported platform") } @@ -22,3 +26,7 @@ func (r *resolver) Build(args []string) (*image.Image, error) { func (r *resolver) Fetch(id string) (*image.Image, error) { return nil, fmt.Errorf("unsupported platform") } + +func (r *resolver) Extract(id string, l string, p string) error { + return fmt.Errorf("unsupported platform") +} diff --git a/dive/image/resolver.go b/dive/image/resolver.go index aaa2407..f3999b9 100644 --- a/dive/image/resolver.go +++ b/dive/image/resolver.go @@ -1,6 +1,8 @@ package image type Resolver interface { + Name() string Fetch(id string) (*Image, error) Build(options []string) (*Image, error) + Extract(id string, layer string, path string) error } diff --git a/go.mod b/go.mod index a3fa807..9538308 100644 --- a/go.mod +++ b/go.mod @@ -1,61 +1,75 @@ module github.com/wagoodman/dive -go 1.19 +go 1.24 require ( github.com/awesome-gocui/gocui v1.1.0 - github.com/awesome-gocui/keybinding v1.0.1-0.20190805183143-864552bd36b7 - github.com/cespare/xxhash v1.1.0 - github.com/docker/cli v0.0.0-20190906153656-016a3232168d - github.com/docker/docker v24.0.7+incompatible - github.com/dustin/go-humanize v1.0.0 - github.com/fatih/color v1.7.0 - github.com/google/uuid v1.1.1 - github.com/logrusorgru/aurora v0.0.0-20190803045625-94edacc10f9b + github.com/awesome-gocui/keybinding v1.0.1-0.20211011072933-86029037a63f + github.com/cespare/xxhash/v2 v2.3.0 + github.com/docker/cli v28.0.1+incompatible + github.com/docker/docker v28.0.1+incompatible + github.com/dustin/go-humanize v1.0.1 + github.com/fatih/color v1.18.0 + github.com/google/uuid v1.6.0 + github.com/klauspost/compress v1.18.0 + github.com/logrusorgru/aurora/v4 v4.0.0 github.com/lunixbochs/vtclean v1.0.0 github.com/mitchellh/go-homedir v1.1.0 github.com/phayes/permbits v0.0.0-20190612203442-39d7c581d2ee - github.com/sergi/go-diff v1.0.0 - github.com/sirupsen/logrus v1.4.2 - github.com/spf13/afero v1.2.2 - github.com/spf13/cobra v0.0.5 - github.com/spf13/viper v1.4.0 - golang.org/x/net v0.17.0 + github.com/sergi/go-diff v1.3.1 + github.com/sirupsen/logrus v1.9.3 + github.com/spf13/afero v1.14.0 + github.com/spf13/cobra v1.9.1 + github.com/spf13/viper v1.20.0 + golang.org/x/net v0.37.0 ) require ( github.com/Microsoft/go-winio v0.4.14 // indirect - github.com/docker/distribution v2.8.2+incompatible // indirect + github.com/containerd/log v0.1.0 // indirect + github.com/distribution/reference v0.6.0 // indirect + github.com/docker/docker-credential-helpers v0.8.2 // indirect github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-units v0.4.0 // indirect - github.com/fsnotify/fsnotify v1.4.7 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/fsnotify/fsnotify v1.8.0 // indirect + github.com/fvbommel/sortorder v1.1.0 // indirect github.com/gdamore/encoding v1.0.0 // indirect github.com/gdamore/tcell/v2 v2.4.0 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-viper/mapstructure/v2 v2.2.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/hashicorp/hcl v1.0.0 // indirect - github.com/inconshreveable/mousetrap v1.0.0 // indirect - github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/lucasb-eyer/go-colorful v1.0.3 // indirect - github.com/magiconair/properties v1.8.1 // indirect - github.com/mattn/go-colorable v0.1.2 // indirect - github.com/mattn/go-isatty v0.0.9 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.10 // indirect - github.com/mitchellh/mapstructure v1.1.2 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/term v0.5.0 // indirect github.com/morikuni/aec v1.0.0 // indirect - github.com/opencontainers/go-digest v1.0.0-rc1 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.0.2 // indirect - github.com/pelletier/go-toml v1.4.0 // indirect + github.com/pelletier/go-toml/v2 v2.2.3 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/rivo/uniseg v0.1.0 // indirect - github.com/spf13/cast v1.3.0 // indirect - github.com/spf13/jwalterweatherman v1.1.0 // indirect - github.com/spf13/pflag v1.0.5 // indirect - github.com/stretchr/testify v1.4.0 // indirect - golang.org/x/sys v0.13.0 // indirect - golang.org/x/term v0.13.0 // indirect - golang.org/x/text v0.13.0 // indirect - gopkg.in/yaml.v2 v2.2.8 // indirect - gotest.tools v2.2.0+incompatible // indirect + github.com/sagikazarmark/locafero v0.7.0 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spf13/cast v1.7.1 // indirect + github.com/spf13/pflag v1.0.6 // indirect + github.com/subosito/gotenv v1.6.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect + go.opentelemetry.io/otel v1.31.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0 // indirect + go.opentelemetry.io/otel/metric v1.31.0 // indirect + go.opentelemetry.io/otel/sdk v1.31.0 // indirect + go.opentelemetry.io/otel/trace v1.31.0 // indirect + go.uber.org/atomic v1.9.0 // indirect + go.uber.org/multierr v1.9.0 // indirect + golang.org/x/sys v0.31.0 // indirect + golang.org/x/term v0.30.0 // indirect + golang.org/x/text v0.23.0 // indirect + golang.org/x/time v0.11.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect gotest.tools/v3 v3.5.0 // indirect ) diff --git a/go.sum b/go.sum index 729cb9e..1bfa31d 100644 --- a/go.sum +++ b/go.sum @@ -1,251 +1,207 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= -github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU= github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= -github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/awesome-gocui/gocui v0.5.0/go.mod h1:1QikxFaPhe2frKeKvEwZEIGia3haiOxOUXKinrv17mA= github.com/awesome-gocui/gocui v1.1.0 h1:db2j7yFEoHZjpQFeE2xqiatS8bm1lO3THeLwE6MzOII= github.com/awesome-gocui/gocui v1.1.0/go.mod h1:M2BXkrp7PR97CKnPRT7Rk0+rtswChPtksw/vRAESGpg= -github.com/awesome-gocui/keybinding v1.0.1-0.20190805183143-864552bd36b7 h1:DDdWoFOtXWySkgCiGGn80TM/E2FS2T1qJBJJxup9+Vo= -github.com/awesome-gocui/keybinding v1.0.1-0.20190805183143-864552bd36b7/go.mod h1:z0TyCwIhaT97yU+becTse8Dqh2CvYT0FLw0R0uTk0ag= +github.com/awesome-gocui/keybinding v1.0.1-0.20211011072933-86029037a63f h1:u5xQfLwWC98BFToYDifqEcgK2ht2FFlbvRlzRnMb0cQ= +github.com/awesome-gocui/keybinding v1.0.1-0.20211011072933-86029037a63f/go.mod h1:z0TyCwIhaT97yU+becTse8Dqh2CvYT0FLw0R0uTk0ag= github.com/awesome-gocui/termbox-go v0.0.0-20190427202837-c0aef3d18bcc/go.mod h1:tOy3o5Nf1bA17mnK4W41gD7PS3u4Cv0P0pqFcoWMy8s= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= -github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= -github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= -github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +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/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= +github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= -github.com/docker/cli v0.0.0-20190906153656-016a3232168d h1:gwX/88xJZfxZV1yjhhuQpWTmEgJis7/XGCVu3iDIZYU= -github.com/docker/cli v0.0.0-20190906153656-016a3232168d/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= -github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v24.0.7+incompatible h1:Wo6l37AuwP3JaMnZa226lzVXGA3F9Ig1seQen0cKYlM= -github.com/docker/docker v24.0.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= +github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/docker/cli v28.0.1+incompatible h1:g0h5NQNda3/CxIsaZfH4Tyf6vpxFth7PYl3hgCPOKzs= +github.com/docker/cli v28.0.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/docker v28.0.1+incompatible h1:FCHjSRdXhNRFjlHMTv4jUNlIBbTeRjrWfeFuJp7jpo0= +github.com/docker/docker v28.0.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo= +github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= -github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= -github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +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/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= +github.com/fsnotify/fsnotify v1.8.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/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko= github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= github.com/gdamore/tcell/v2 v2.4.0 h1:W6dxJEmaxYvhICFoTY3WrLLEXsQ11SaFnKGVEXW57KM= github.com/gdamore/tcell/v2 v2.4.0/go.mod h1:cTTuF84Dlj/RqmaCIV5p4w8uG1zWdk0SF6oBpwHp4fU= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= +github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= -github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= -github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/logrusorgru/aurora v0.0.0-20190803045625-94edacc10f9b h1:PMbSa9CgaiQR9NLlUTwKi+7aeLl3GG5JX5ERJxfQ3IE= -github.com/logrusorgru/aurora v0.0.0-20190803045625-94edacc10f9b/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/logrusorgru/aurora/v4 v4.0.0 h1:sRjfPpun/63iADiSvGGjgA1cAYegEWMPCJdUpJYn9JA= +github.com/logrusorgru/aurora/v4 v4.0.0/go.mod h1:lP0iIa2nrnT/qoFXcOZSrZQpJ1o6n2CUf/hyHi2Q4ZQ= github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac= github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/lunixbochs/vtclean v1.0.0 h1:xu2sLAri4lGiovBDQKxl5mrXyESr3gUr5m5SM5+LVb8= github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= -github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= -github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU= -github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.9 h1:d5US/mDsogSGW37IV293h//ZFaeajb69h+EHFsv2xGg= -github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.10 h1:CoZ3S2P7pvtP45xOtBw+/mDL2z0RKI576gSkzRRpdGg= github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +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/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= -github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ= -github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +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.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/pelletier/go-toml v1.4.0 h1:u3Z1r+oOXJIkxqw34zVhyPgjBsm6X2wn21NWs/HfSeg= -github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo= +github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= +github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= github.com/phayes/permbits v0.0.0-20190612203442-39d7c581d2ee h1:P6U24L02WMfj9ymZTxl7CxS73JC99x3ukk+DBkgQGQs= github.com/phayes/permbits v0.0.0-20190612203442-39d7c581d2ee/go.mod h1:3uODdxMgOaPYeWU7RzZLxVtJHZ/x1f/iHkBZuKJDzuY= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= -github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= -github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= -github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo= +github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k= +github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= +github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= -github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc= -github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= -github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= -github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s= -github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= -github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -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.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -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/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= -github.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU= -github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA= +github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo= +github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= +github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= +github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= +github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= +github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.20.0 h1:zrxIyR3RQIOsarIrgL8+sAvALXul9jeEPa06Y0Ph6vY= +github.com/spf13/viper v1.20.0/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= -github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= -github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= -github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8= +go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= +go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0 h1:K0XaT3DwHAcV4nKLzcQvwAgSyisUghWoY20I7huthMk= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0/go.mod h1:B5Ki776z/MBnVha1Nzwp5arlzBbE3+1jk+pGmaP5HME= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0 h1:lUsI2TYsQw2r1IASwoROaCnjdj2cvC2+Jbxvk6nHnWU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0/go.mod h1:2HpZxxQurfGxJlJDblybejHB6RX6pmExPNe517hREw4= +go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= +go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= +go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= +go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= +go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= +go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= +go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= +go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= +go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= +go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= +golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= -golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= +golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= +golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= +golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= @@ -253,22 +209,21 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 h1:T6rh4haD3GVYsgEfWExoCZA2o2FmbNyKpTuAxbEFPTg= +google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:wp2WsuBYj6j8wUdo3ToZsdxxixbvQNAHqVJrTgi5E5M= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 h1:QCqS/PdaHTSWGvupk2F/ehwHtGc0/GYkT+3GAcR1CCc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= +google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= +google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= -gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= -gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.5.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/runtime/ci/evaluator.go b/runtime/ci/evaluator.go index 152bb21..c48f6e6 100644 --- a/runtime/ci/evaluator.go +++ b/runtime/ci/evaluator.go @@ -7,7 +7,7 @@ import ( "strings" "github.com/dustin/go-humanize" - "github.com/logrusorgru/aurora" + "github.com/logrusorgru/aurora/v4" "github.com/spf13/viper" "github.com/wagoodman/dive/dive/image" diff --git a/runtime/ci/rule.go b/runtime/ci/rule.go index 84ccede..f9e348d 100644 --- a/runtime/ci/rule.go +++ b/runtime/ci/rule.go @@ -5,7 +5,7 @@ import ( "strconv" "github.com/dustin/go-humanize" - "github.com/logrusorgru/aurora" + "github.com/logrusorgru/aurora/v4" "github.com/spf13/viper" "github.com/wagoodman/dive/dive/image" diff --git a/runtime/export/export.go b/runtime/export/export.go index e4c8308..a9a3660 100644 --- a/runtime/export/export.go +++ b/runtime/export/export.go @@ -3,6 +3,9 @@ package export import ( "encoding/json" + "github.com/sirupsen/logrus" + + "github.com/wagoodman/dive/dive/filetree" diveImage "github.com/wagoodman/dive/dive/image" ) @@ -11,6 +14,7 @@ type export struct { Image image `json:"image"` } +// NewExport exports the analysis to a JSON func NewExport(analysis *diveImage.AnalysisResult) *export { data := export{ Layer: make([]layer, len(analysis.Layers)), @@ -24,12 +28,22 @@ func NewExport(analysis *diveImage.AnalysisResult) *export { // export layers in order for idx, curLayer := range analysis.Layers { + layerFileList := make([]filetree.FileInfo, 0) + visitor := func(node *filetree.FileNode) error { + layerFileList = append(layerFileList, node.Data.FileInfo) + return nil + } + err := curLayer.Tree.VisitDepthChildFirst(visitor, nil) + if err != nil { + logrus.Errorf("Unable to propagate layer tree: %+v", err) + } data.Layer[idx] = layer{ Index: curLayer.Index, ID: curLayer.Id, DigestID: curLayer.Digest, SizeBytes: curLayer.Size, Command: curLayer.Command, + FileList: layerFileList, } } diff --git a/runtime/export/export_test.go b/runtime/export/export_test.go index 35133e6..d32e574 100644 --- a/runtime/export/export_test.go +++ b/runtime/export/export_test.go @@ -24,98 +24,4636 @@ func Test_Export(t *testing.T) { "id": "28cfe03618aa2e914e81fdd90345245c15f4478e35252c06ca52d238fd3cc694", "digestId": "sha256:23bc2b70b2014dec0ac22f27bb93e9babd08cdd6f1115d0c955b9ff22b382f5a", "sizeBytes": 1154361, - "command": "#(nop) ADD file:ce026b62356eec3ad1214f92be2c9dc063fe205bd5e600be3492c4dfb17148bd in / " + "command": "#(nop) ADD file:ce026b62356eec3ad1214f92be2c9dc063fe205bd5e600be3492c4dfb17148bd in / ", + "fileList": [ + { + "path": "bin/[", + "typeFlag": 48, + "linkName": "", + "size": 1075464, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/[[", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/acpid", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/add-shell", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/addgroup", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/adduser", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/adjtimex", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/ar", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/arch", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/arp", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/arping", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/ash", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/awk", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/base64", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/basename", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/beep", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/blkdiscard", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/blkid", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/blockdev", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/bootchartd", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/brctl", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/bunzip2", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/busybox", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/bzcat", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/bzip2", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/cal", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/cat", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/chat", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/chattr", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/chgrp", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/chmod", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/chown", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/chpasswd", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/chpst", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/chroot", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/chrt", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/chvt", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/cksum", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/clear", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/cmp", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/comm", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/conspy", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/cp", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/cpio", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/crond", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/crontab", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/cryptpw", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/cttyhack", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/cut", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/date", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/dc", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/dd", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/deallocvt", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/delgroup", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/deluser", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/depmod", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/devmem", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/df", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/dhcprelay", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/diff", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/dirname", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/dmesg", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/dnsd", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/dnsdomainname", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/dos2unix", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/dpkg", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/dpkg-deb", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/du", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/dumpkmap", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/dumpleases", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/echo", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/ed", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/egrep", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/eject", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/env", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/envdir", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/envuidgid", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/ether-wake", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/expand", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/expr", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/factor", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/fakeidentd", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/fallocate", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/false", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/fatattr", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/fbset", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/fbsplash", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/fdflush", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/fdformat", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/fdisk", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/fgconsole", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/fgrep", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/find", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/findfs", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/flock", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/fold", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/free", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/freeramdisk", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/fsck", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/fsck.minix", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/fsfreeze", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/fstrim", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/fsync", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/ftpd", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/ftpget", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/ftpput", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/fuser", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/getconf", + "typeFlag": 48, + "linkName": "", + "size": 77880, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/getopt", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/getty", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/grep", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/groups", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/gunzip", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/gzip", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/halt", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/hd", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/hdparm", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/head", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/hexdump", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/hexedit", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/hostid", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/hostname", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/httpd", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/hush", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/hwclock", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/i2cdetect", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/i2cdump", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/i2cget", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/i2cset", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/id", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/ifconfig", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/ifdown", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/ifenslave", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/ifplugd", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/ifup", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/inetd", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/init", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/insmod", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/install", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/ionice", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/iostat", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/ip", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/ipaddr", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/ipcalc", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/ipcrm", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/ipcs", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/iplink", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/ipneigh", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/iproute", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/iprule", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/iptunnel", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/kbd_mode", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/kill", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/killall", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/killall5", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/klogd", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/last", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/less", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/link", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/linux32", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/linux64", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/linuxrc", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/ln", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/loadfont", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/loadkmap", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/logger", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/login", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/logname", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/logread", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/losetup", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/lpd", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/lpq", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/lpr", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/ls", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/lsattr", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/lsmod", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/lsof", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/lspci", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/lsscsi", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/lsusb", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/lzcat", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/lzma", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/lzop", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/makedevs", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/makemime", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/man", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/md5sum", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/mdev", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/mesg", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/microcom", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/mkdir", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/mkdosfs", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/mke2fs", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/mkfifo", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/mkfs.ext2", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/mkfs.minix", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/mkfs.vfat", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/mknod", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/mkpasswd", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/mkswap", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/mktemp", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/modinfo", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/modprobe", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/more", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/mount", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/mountpoint", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/mpstat", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/mt", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/mv", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/nameif", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/nanddump", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/nandwrite", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/nbd-client", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/nc", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/netstat", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/nice", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/nl", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/nmeter", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/nohup", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/nproc", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/nsenter", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/nslookup", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/ntpd", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/nuke", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/od", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/openvt", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/partprobe", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/passwd", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/paste", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/patch", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/pgrep", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/pidof", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/ping", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/ping6", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/pipe_progress", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/pivot_root", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/pkill", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/pmap", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/popmaildir", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/poweroff", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/powertop", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/printenv", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/printf", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/ps", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/pscan", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/pstree", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/pwd", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/pwdx", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/raidautorun", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/rdate", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/rdev", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/readahead", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/readlink", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/readprofile", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/realpath", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/reboot", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/reformime", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/remove-shell", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/renice", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/reset", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/resize", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/resume", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/rev", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/rm", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/rmdir", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/rmmod", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/route", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/rpm", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/rpm2cpio", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/rtcwake", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/run-init", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/run-parts", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/runlevel", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/runsv", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/runsvdir", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/rx", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/script", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/scriptreplay", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/sed", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/sendmail", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/seq", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/setarch", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/setconsole", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/setfattr", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/setfont", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/setkeycodes", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/setlogcons", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/setpriv", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/setserial", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/setsid", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/setuidgid", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/sh", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/sha1sum", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/sha256sum", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/sha3sum", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/sha512sum", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/showkey", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/shred", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/shuf", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/slattach", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/sleep", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/smemcap", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/softlimit", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/sort", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/split", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/ssl_client", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/start-stop-daemon", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/stat", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/strings", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/stty", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/su", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/sulogin", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/sum", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/sv", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/svc", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/svlogd", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/svok", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/swapoff", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/swapon", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/switch_root", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/sync", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/sysctl", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/syslogd", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/tac", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/tail", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/tar", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/taskset", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/tc", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/tcpsvd", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/tee", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/telnet", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/telnetd", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/test", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/tftp", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/tftpd", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/time", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/timeout", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/top", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/touch", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/tr", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/traceroute", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/traceroute6", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/true", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/truncate", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/tty", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/ttysize", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/tunctl", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/ubiattach", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/ubidetach", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/ubimkvol", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/ubirename", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/ubirmvol", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/ubirsvol", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/ubiupdatevol", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/udhcpc", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/udhcpd", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/udpsvd", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/uevent", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/umount", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/uname", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/unexpand", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/uniq", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/unix2dos", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/unlink", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/unlzma", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/unshare", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/unxz", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/unzip", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/uptime", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/users", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/usleep", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/uudecode", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/uuencode", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/vconfig", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/vi", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/vlock", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/volname", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/w", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/wall", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/watch", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/watchdog", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/wc", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/wget", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/which", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/who", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/whoami", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/whois", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/xargs", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/xxd", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/xz", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/xzcat", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/yes", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/zcat", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin/zcip", + "typeFlag": 49, + "linkName": "bin/[", + "size": 0, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "bin", + "typeFlag": 53, + "linkName": "", + "size": 0, + "fileMode": 2147484141, + "uid": 0, + "gid": 0, + "isDir": true + }, + { + "path": "dev", + "typeFlag": 53, + "linkName": "", + "size": 0, + "fileMode": 2147484141, + "uid": 0, + "gid": 0, + "isDir": true + }, + { + "path": "etc/group", + "typeFlag": 48, + "linkName": "", + "size": 307, + "fileMode": 436, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "etc/localtime", + "typeFlag": 48, + "linkName": "", + "size": 127, + "fileMode": 420, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "etc/network/if-down.d", + "typeFlag": 53, + "linkName": "", + "size": 0, + "fileMode": 2147484141, + "uid": 0, + "gid": 0, + "isDir": true + }, + { + "path": "etc/network/if-post-down.d", + "typeFlag": 53, + "linkName": "", + "size": 0, + "fileMode": 2147484141, + "uid": 0, + "gid": 0, + "isDir": true + }, + { + "path": "etc/network/if-pre-up.d", + "typeFlag": 53, + "linkName": "", + "size": 0, + "fileMode": 2147484141, + "uid": 0, + "gid": 0, + "isDir": true + }, + { + "path": "etc/network/if-up.d", + "typeFlag": 53, + "linkName": "", + "size": 0, + "fileMode": 2147484141, + "uid": 0, + "gid": 0, + "isDir": true + }, + { + "path": "etc/network", + "typeFlag": 53, + "linkName": "", + "size": 0, + "fileMode": 2147484141, + "uid": 0, + "gid": 0, + "isDir": true + }, + { + "path": "etc/passwd", + "typeFlag": 48, + "linkName": "", + "size": 340, + "fileMode": 420, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "etc/shadow", + "typeFlag": 48, + "linkName": "", + "size": 243, + "fileMode": 384, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "etc", + "typeFlag": 53, + "linkName": "", + "size": 0, + "fileMode": 2147484141, + "uid": 0, + "gid": 0, + "isDir": true + }, + { + "path": "home", + "typeFlag": 53, + "linkName": "", + "size": 0, + "fileMode": 2147484141, + "uid": 65534, + "gid": 65534, + "isDir": true + }, + { + "path": "root", + "typeFlag": 53, + "linkName": "", + "size": 0, + "fileMode": 2147484096, + "uid": 0, + "gid": 0, + "isDir": true + }, + { + "path": "tmp", + "typeFlag": 53, + "linkName": "", + "size": 0, + "fileMode": 2148532735, + "uid": 0, + "gid": 0, + "isDir": true + }, + { + "path": "usr/sbin", + "typeFlag": 53, + "linkName": "", + "size": 0, + "fileMode": 2147484141, + "uid": 1, + "gid": 1, + "isDir": true + }, + { + "path": "usr", + "typeFlag": 53, + "linkName": "", + "size": 0, + "fileMode": 2147484141, + "uid": 0, + "gid": 0, + "isDir": true + }, + { + "path": "var/spool/mail", + "typeFlag": 53, + "linkName": "", + "size": 0, + "fileMode": 2147484141, + "uid": 8, + "gid": 8, + "isDir": true + }, + { + "path": "var/spool", + "typeFlag": 53, + "linkName": "", + "size": 0, + "fileMode": 2147484141, + "uid": 0, + "gid": 0, + "isDir": true + }, + { + "path": "var/www", + "typeFlag": 53, + "linkName": "", + "size": 0, + "fileMode": 2147484141, + "uid": 0, + "gid": 0, + "isDir": true + }, + { + "path": "var", + "typeFlag": 53, + "linkName": "", + "size": 0, + "fileMode": 2147484141, + "uid": 0, + "gid": 0, + "isDir": true + } + ] }, { "index": 1, "id": "1871059774abe6914075e4a919b778fa1561f577d620ae52438a9635e6241936", "digestId": "sha256:a65b7d7ac139a0e4337bc3c73ce511f937d6140ef61a0108f7d4b8aab8d67274", "sizeBytes": 6405, - "command": "#(nop) ADD file:139c3708fb6261126453e34483abd8bf7b26ed16d952fd976994d68e72d93be2 in /somefile.txt " + "command": "#(nop) ADD file:139c3708fb6261126453e34483abd8bf7b26ed16d952fd976994d68e72d93be2 in /somefile.txt ", + "fileList": [ + { + "path": "somefile.txt", + "typeFlag": 48, + "linkName": "", + "size": 6405, + "fileMode": 436, + "uid": 0, + "gid": 0, + "isDir": false + } + ] }, { "index": 2, "id": "49fe2a475548bfa4d493fc796fce41f30704e3d4cbff3e45dd3e06f463236d1d", "digestId": "sha256:93e208d471756ffbac88cf9c25feb442007f221d3bd73231e27b747a0a68927c", "sizeBytes": 0, - "command": "mkdir -p /root/example/really/nested" + "command": "mkdir -p /root/example/really/nested", + "fileList": [ + { + "path": "root/example/really/nested", + "typeFlag": 53, + "linkName": "", + "size": 0, + "fileMode": 2147484141, + "uid": 0, + "gid": 0, + "isDir": true + }, + { + "path": "root/example/really", + "typeFlag": 53, + "linkName": "", + "size": 0, + "fileMode": 2147484141, + "uid": 0, + "gid": 0, + "isDir": true + }, + { + "path": "root/example", + "typeFlag": 53, + "linkName": "", + "size": 0, + "fileMode": 2147484141, + "uid": 0, + "gid": 0, + "isDir": true + }, + { + "path": "root", + "typeFlag": 53, + "linkName": "", + "size": 0, + "fileMode": 2147484096, + "uid": 0, + "gid": 0, + "isDir": true + } + ] }, { "index": 3, "id": "80cd2ca1ffc89962b9349c80280c2bc551acbd11e09b16badb0669f8e2369020", "digestId": "sha256:4abad3abe3cb99ad7a492a9d9f6b3d66287c1646843c74128bbbec4f7be5aa9e", "sizeBytes": 6405, - "command": "cp /somefile.txt /root/example/somefile1.txt" + "command": "cp /somefile.txt /root/example/somefile1.txt", + "fileList": [ + { + "path": "root/example/somefile1.txt", + "typeFlag": 48, + "linkName": "", + "size": 6405, + "fileMode": 420, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "root/example", + "typeFlag": 53, + "linkName": "", + "size": 0, + "fileMode": 2147484141, + "uid": 0, + "gid": 0, + "isDir": true + }, + { + "path": "root", + "typeFlag": 53, + "linkName": "", + "size": 0, + "fileMode": 2147484096, + "uid": 0, + "gid": 0, + "isDir": true + } + ] }, { "index": 4, "id": "c99e2f8d3f6282668f0d30dc1db5e67a51d7a1dcd7ff6ddfa0f90760836778ec", "digestId": "sha256:14c9a6ffcb6a0f32d1035f97373b19608e2d307961d8be156321c3f1c1504cbf", "sizeBytes": 6405, - "command": "chmod 444 /root/example/somefile1.txt" + "command": "chmod 444 /root/example/somefile1.txt", + "fileList": [ + { + "path": "root/example/somefile1.txt", + "typeFlag": 48, + "linkName": "", + "size": 6405, + "fileMode": 292, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "root/example", + "typeFlag": 53, + "linkName": "", + "size": 0, + "fileMode": 2147484141, + "uid": 0, + "gid": 0, + "isDir": true + }, + { + "path": "root", + "typeFlag": 53, + "linkName": "", + "size": 0, + "fileMode": 2147484096, + "uid": 0, + "gid": 0, + "isDir": true + } + ] }, { "index": 5, "id": "5eca617bdc3bc06134fe957a30da4c57adb7c340a6d749c8edc4c15861c928d7", "digestId": "sha256:778fb5770ef466f314e79cc9dc418eba76bfc0a64491ce7b167b76aa52c736c4", "sizeBytes": 6405, - "command": "cp /somefile.txt /root/example/somefile2.txt" + "command": "cp /somefile.txt /root/example/somefile2.txt", + "fileList": [ + { + "path": "root/example/somefile2.txt", + "typeFlag": 48, + "linkName": "", + "size": 6405, + "fileMode": 420, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "root/example", + "typeFlag": 53, + "linkName": "", + "size": 0, + "fileMode": 2147484141, + "uid": 0, + "gid": 0, + "isDir": true + }, + { + "path": "root", + "typeFlag": 53, + "linkName": "", + "size": 0, + "fileMode": 2147484096, + "uid": 0, + "gid": 0, + "isDir": true + } + ] }, { "index": 6, "id": "f07c3eb887572395408f8e11a07af945e4da5f02b3188bb06b93fad713ca0b99", "digestId": "sha256:f275b8a31a71deb521cc048e6021e2ff6fa52bedb25c9b7bbe129a0195ddca5f", "sizeBytes": 6405, - "command": "cp /somefile.txt /root/example/somefile3.txt" + "command": "cp /somefile.txt /root/example/somefile3.txt", + "fileList": [ + { + "path": "root/example/somefile3.txt", + "typeFlag": 48, + "linkName": "", + "size": 6405, + "fileMode": 420, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "root/example", + "typeFlag": 53, + "linkName": "", + "size": 0, + "fileMode": 2147484141, + "uid": 0, + "gid": 0, + "isDir": true + }, + { + "path": "root", + "typeFlag": 53, + "linkName": "", + "size": 0, + "fileMode": 2147484096, + "uid": 0, + "gid": 0, + "isDir": true + } + ] }, { "index": 7, "id": "461885fc22589158dee3c5b9f01cc41c87805439f58b4399d733b51aa305cbf9", "digestId": "sha256:dd1effc5eb19894c3e9b57411c98dd1cf30fa1de4253c7fae53c9cea67267d83", "sizeBytes": 6405, - "command": "mv /root/example/somefile3.txt /root/saved.txt" + "command": "mv /root/example/somefile3.txt /root/saved.txt", + "fileList": [ + { + "path": "root/example/.wh.somefile3.txt", + "typeFlag": 48, + "linkName": "", + "size": 0, + "fileMode": 0, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "root/example", + "typeFlag": 53, + "linkName": "", + "size": 0, + "fileMode": 2147484141, + "uid": 0, + "gid": 0, + "isDir": true + }, + { + "path": "root/saved.txt", + "typeFlag": 48, + "linkName": "", + "size": 6405, + "fileMode": 420, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "root", + "typeFlag": 53, + "linkName": "", + "size": 0, + "fileMode": 2147484096, + "uid": 0, + "gid": 0, + "isDir": true + } + ] }, { "index": 8, "id": "a10327f68ffed4afcba78919052809a8f774978a6b87fc117d39c53c4842f72c", "digestId": "sha256:8d1869a0a066cdd12e48d648222866e77b5e2814f773bb3bd8774ab4052f0f1d", "sizeBytes": 6405, - "command": "cp /root/saved.txt /root/.saved.txt" + "command": "cp /root/saved.txt /root/.saved.txt", + "fileList": [ + { + "path": "root/.saved.txt", + "typeFlag": 48, + "linkName": "", + "size": 6405, + "fileMode": 420, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "root", + "typeFlag": 53, + "linkName": "", + "size": 0, + "fileMode": 2147484096, + "uid": 0, + "gid": 0, + "isDir": true + } + ] }, { "index": 9, "id": "f2fc54e25cb7966dc9732ec671a77a1c5c104e732bd15ad44a2dc1ac42368f84", "digestId": "sha256:bc2e36423fa31a97223fd421f22c35466220fa160769abf697b8eb58c896b468", "sizeBytes": 0, - "command": "rm -rf /root/example/" + "command": "rm -rf /root/example/", + "fileList": [ + { + "path": "root/.wh.example", + "typeFlag": 48, + "linkName": "", + "size": 0, + "fileMode": 0, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "root", + "typeFlag": 53, + "linkName": "", + "size": 0, + "fileMode": 2147484096, + "uid": 0, + "gid": 0, + "isDir": true + } + ] }, { "index": 10, "id": "aad36d0b05e71c7e6d4dfe0ca9ed6be89e2e0d8995dafe83438299a314e91071", "digestId": "sha256:7f648d45ee7b6de2292162fba498b66cbaaf181da9004fcceef824c72dbae445", "sizeBytes": 2187, - "command": "#(nop) ADD dir:7ec14b81316baa1a31c38c97686a8f030c98cba2035c968412749e33e0c4427e in /root/.data/ " + "command": "#(nop) ADD dir:7ec14b81316baa1a31c38c97686a8f030c98cba2035c968412749e33e0c4427e in /root/.data/ ", + "fileList": [ + { + "path": "root/.data/tag.sh", + "typeFlag": 48, + "linkName": "", + "size": 917, + "fileMode": 509, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "root/.data/test.sh", + "typeFlag": 48, + "linkName": "", + "size": 1270, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "root/.data", + "typeFlag": 53, + "linkName": "", + "size": 0, + "fileMode": 2147484141, + "uid": 0, + "gid": 0, + "isDir": true + }, + { + "path": "root", + "typeFlag": 53, + "linkName": "", + "size": 0, + "fileMode": 2147484096, + "uid": 0, + "gid": 0, + "isDir": true + } + ] }, { "index": 11, "id": "3d4ad907517a021d86a4102d2764ad2161e4818bbd144e41d019bfc955434181", "digestId": "sha256:a4b8f95f266d5c063c9a9473c45f2f85ddc183e37941b5e6b6b9d3c00e8e0457", "sizeBytes": 6405, - "command": "cp /root/saved.txt /tmp/saved.again1.txt" + "command": "cp /root/saved.txt /tmp/saved.again1.txt", + "fileList": [ + { + "path": "tmp/saved.again1.txt", + "typeFlag": 48, + "linkName": "", + "size": 6405, + "fileMode": 420, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "tmp", + "typeFlag": 53, + "linkName": "", + "size": 0, + "fileMode": 2148532735, + "uid": 0, + "gid": 0, + "isDir": true + } + ] }, { "index": 12, "id": "81b1b002d4b4c1325a9cad9990b5277e7f29f79e0f24582344c0891178f95905", "digestId": "sha256:22a44d45780a541e593a8862d80f3e14cb80b6bf76aa42ce68dc207a35bf3a4a", "sizeBytes": 6405, - "command": "cp /root/saved.txt /root/.data/saved.again2.txt" + "command": "cp /root/saved.txt /root/.data/saved.again2.txt", + "fileList": [ + { + "path": "root/.data/saved.again2.txt", + "typeFlag": 48, + "linkName": "", + "size": 6405, + "fileMode": 420, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "root/.data", + "typeFlag": 53, + "linkName": "", + "size": 0, + "fileMode": 2147484141, + "uid": 0, + "gid": 0, + "isDir": true + }, + { + "path": "root", + "typeFlag": 53, + "linkName": "", + "size": 0, + "fileMode": 2147484096, + "uid": 0, + "gid": 0, + "isDir": true + } + ] }, { "index": 13, "id": "cfb35bb5c127d848739be5ca726057e6e2c77b2849f588e7aebb642c0d3d4b7b", "digestId": "sha256:ba689cac6a98c92d121fa5c9716a1bab526b8bb1fd6d43625c575b79e97300c5", "sizeBytes": 6405, - "command": "chmod +x /root/saved.txt" + "command": "chmod +x /root/saved.txt", + "fileList": [ + { + "path": "root/saved.txt", + "typeFlag": 48, + "linkName": "", + "size": 6405, + "fileMode": 493, + "uid": 0, + "gid": 0, + "isDir": false + }, + { + "path": "root", + "typeFlag": 53, + "linkName": "", + "size": 0, + "fileMode": 2147484096, + "uid": 0, + "gid": 0, + "isDir": true + } + ] } ], "image": { @@ -141,6 +4679,7 @@ func Test_Export(t *testing.T) { ] } }` + actualResult := string(payload) if expectedResult != actualResult { dmp := diffmatchpatch.New() diff --git a/runtime/export/layer.go b/runtime/export/layer.go index cdbba5b..85d5f57 100644 --- a/runtime/export/layer.go +++ b/runtime/export/layer.go @@ -1,9 +1,14 @@ package export +import ( + "github.com/wagoodman/dive/dive/filetree" +) + type layer struct { - Index int `json:"index"` - ID string `json:"id"` - DigestID string `json:"digestId"` - SizeBytes uint64 `json:"sizeBytes"` - Command string `json:"command"` + Index int `json:"index"` + ID string `json:"id"` + DigestID string `json:"digestId"` + SizeBytes uint64 `json:"sizeBytes"` + Command string `json:"command"` + FileList []filetree.FileInfo `json:"fileList"` } diff --git a/runtime/run.go b/runtime/run.go index 37c8dd2..7e4814c 100644 --- a/runtime/run.go +++ b/runtime/run.go @@ -35,7 +35,7 @@ func run(enableUi bool, options Options, imageResolver image.Resolver, events ev } } else { events.message(utils.TitleFormat("Image Source: ") + options.Source.String() + "://" + options.Image) - events.message(utils.TitleFormat("Fetching image...") + " (this can take a while for large images)") + events.message(utils.TitleFormat("Extracting image from "+imageResolver.Name()+"...") + " (this can take a while for large images)") img, err = imageResolver.Fetch(options.Image) if err != nil { events.exitWithErrorMessage("cannot fetch image", err) @@ -108,7 +108,7 @@ func run(enableUi bool, options Options, imageResolver image.Resolver, events ev // enough sleep will prevent this behavior (todo: remove this hack) time.Sleep(100 * time.Millisecond) - err = ui.Run(options.Image, analysis, treeStack) + err = ui.Run(options.Image, imageResolver, analysis, treeStack) if err != nil { events.exitWithError(err) return diff --git a/runtime/run_test.go b/runtime/run_test.go index 1565ab5..6362ea7 100644 --- a/runtime/run_test.go +++ b/runtime/run_test.go @@ -16,6 +16,14 @@ import ( type defaultResolver struct{} +func (r *defaultResolver) Name() string { + return "default-resolver" +} + +func (r *defaultResolver) Extract(id string, l string, p string) error { + return nil +} + func (r *defaultResolver) Fetch(id string) (*image.Image, error) { archive, err := docker.TestLoadArchive("../.data/test-docker-image.tar") if err != nil { @@ -30,6 +38,14 @@ func (r *defaultResolver) Build(args []string) (*image.Image, error) { type failedBuildResolver struct{} +func (r *failedBuildResolver) Name() string { + return "failed-build-resolver" +} + +func (r *failedBuildResolver) Extract(id string, l string, p string) error { + return fmt.Errorf("some extract failure") +} + func (r *failedBuildResolver) Fetch(id string) (*image.Image, error) { archive, err := docker.TestLoadArchive("../.data/test-docker-image.tar") if err != nil { @@ -44,6 +60,14 @@ func (r *failedBuildResolver) Build(args []string) (*image.Image, error) { type failedFetchResolver struct{} +func (r *failedFetchResolver) Name() string { + return "failed-fetch-resolver" +} + +func (r *failedFetchResolver) Extract(id string, l string, p string) error { + return fmt.Errorf("some extract failure") +} + func (r *failedFetchResolver) Fetch(id string) (*image.Image, error) { return nil, fmt.Errorf("some fetch failure") } @@ -108,7 +132,7 @@ func TestRun(t *testing.T) { }, events: []testEvent{ {stdout: "Image Source: docker://dive-example", stderr: "", errorOnExit: false, errMessage: ""}, - {stdout: "Fetching image... (this can take a while for large images)", stderr: "", errorOnExit: false, errMessage: ""}, + {stdout: "Extracting image from default-resolver... (this can take a while for large images)", stderr: "", errorOnExit: false, errMessage: ""}, {stdout: "Analyzing image...", stderr: "", errorOnExit: false, errMessage: ""}, {stdout: "Building cache...", stderr: "", errorOnExit: false, errMessage: ""}, }, @@ -126,7 +150,7 @@ func TestRun(t *testing.T) { }, events: []testEvent{ {stdout: "Image Source: docker://dive-example", stderr: "", errorOnExit: false, errMessage: ""}, - {stdout: "Fetching image... (this can take a while for large images)", stderr: "", errorOnExit: false, errMessage: ""}, + {stdout: "Extracting image from default-resolver... (this can take a while for large images)", stderr: "", errorOnExit: false, errMessage: ""}, {stdout: "Analyzing image...", stderr: "", errorOnExit: false, errMessage: ""}, {stdout: "Building cache...", stderr: "", errorOnExit: false, errMessage: ""}, }, @@ -159,7 +183,7 @@ func TestRun(t *testing.T) { }, events: []testEvent{ {stdout: "Image Source: docker://dive-example", stderr: "", errorOnExit: false, errMessage: ""}, - {stdout: "Fetching image... (this can take a while for large images)", stderr: "", errorOnExit: false, errMessage: ""}, + {stdout: "Extracting image from failed-fetch-resolver... (this can take a while for large images)", stderr: "", errorOnExit: false, errMessage: ""}, {stdout: "", stderr: "cannot fetch image", errorOnExit: true, errMessage: "some fetch failure"}, }, }, diff --git a/runtime/ui/app.go b/runtime/ui/app.go index 608a664..ac92249 100644 --- a/runtime/ui/app.go +++ b/runtime/ui/app.go @@ -27,13 +27,13 @@ var ( appSingleton *app ) -func newApp(gui *gocui.Gui, imageName string, analysis *image.AnalysisResult, cache filetree.Comparer) (*app, error) { +func newApp(gui *gocui.Gui, imageName string, resolver image.Resolver, analysis *image.AnalysisResult, cache filetree.Comparer) (*app, error) { var err error once.Do(func() { var controller *Controller var globalHelpKeys []*key.Binding - controller, err = NewCollection(gui, imageName, analysis, cache) + controller, err = NewCollection(gui, imageName, resolver, analysis, cache) if err != nil { return } @@ -77,12 +77,12 @@ func newApp(gui *gocui.Gui, imageName string, analysis *image.AnalysisResult, ca Display: "Switch view", }, { - Key: gocui.KeyArrowRight, - OnAction: controller.NextPane, + ConfigKeys: []string{"keybinding.right"}, + OnAction: controller.NextPane, }, { - Key: gocui.KeyArrowLeft, - OnAction: controller.PrevPane, + ConfigKeys: []string{"keybinding.left"}, + OnAction: controller.PrevPane, }, { ConfigKeys: []string{"keybinding.filter-files"}, @@ -90,6 +90,10 @@ func newApp(gui *gocui.Gui, imageName string, analysis *image.AnalysisResult, ca IsSelected: controller.views.Filter.IsVisible, Display: "Filter", }, + { + ConfigKeys: []string{"keybinding.close-filter-files"}, + OnAction: controller.CloseFilterView, + }, } globalHelpKeys, err = key.GenerateBindings(gui, "", infos) @@ -134,7 +138,7 @@ func (a *app) quit() error { } // Run is the UI entrypoint. -func Run(imageName string, analysis *image.AnalysisResult, treeStack filetree.Comparer) error { +func Run(imageName string, resolver image.Resolver, analysis *image.AnalysisResult, treeStack filetree.Comparer) error { var err error g, err := gocui.NewGui(gocui.OutputNormal, true) @@ -143,7 +147,7 @@ func Run(imageName string, analysis *image.AnalysisResult, treeStack filetree.Co } defer g.Close() - _, err = newApp(g, imageName, analysis, treeStack) + _, err = newApp(g, imageName, resolver, analysis, treeStack) if err != nil { return err } diff --git a/runtime/ui/controller.go b/runtime/ui/controller.go index 031955d..fd40398 100644 --- a/runtime/ui/controller.go +++ b/runtime/ui/controller.go @@ -13,19 +13,23 @@ import ( ) type Controller struct { - gui *gocui.Gui - views *view.Views + gui *gocui.Gui + views *view.Views + resolver image.Resolver + imageName string } -func NewCollection(g *gocui.Gui, imageName string, analysis *image.AnalysisResult, cache filetree.Comparer) (*Controller, error) { +func NewCollection(g *gocui.Gui, imageName string, resolver image.Resolver, analysis *image.AnalysisResult, cache filetree.Comparer) (*Controller, error) { views, err := view.NewViews(g, imageName, analysis, cache) if err != nil { return nil, err } controller := &Controller{ - gui: g, - views: views, + gui: g, + views: views, + resolver: resolver, + imageName: imageName, } // layer view cursor down event should trigger an update in the file tree @@ -34,6 +38,9 @@ func NewCollection(g *gocui.Gui, imageName string, analysis *image.AnalysisResul // update the status pane when a filetree option is changed by the user controller.views.Tree.AddViewOptionChangeListener(controller.onFileTreeViewOptionChange) + // update the status pane when a filetree option is changed by the user + controller.views.Tree.AddViewExtractListener(controller.onFileTreeViewExtract) + // update the tree view while the user types into the filter view controller.views.Filter.AddFilterEditListener(controller.onFilterEdit) @@ -53,6 +60,10 @@ func NewCollection(g *gocui.Gui, imageName string, analysis *image.AnalysisResul return controller, nil } +func (c *Controller) onFileTreeViewExtract(p string) error { + return c.resolver.Extract(c.imageName, c.views.LayerDetails.CurrentLayer.Id, p) +} + func (c *Controller) onFileTreeViewOptionChange() error { err := c.views.Status.Update() if err != nil { @@ -212,6 +223,15 @@ func (c *Controller) ToggleView() (err error) { return c.UpdateAndRender() } +func (c *Controller) CloseFilterView() error { + // filter view needs to be visible + if c.views.Filter.IsVisible() { + // toggle filter view + return c.ToggleFilterView() + } + return nil +} + func (c *Controller) ToggleFilterView() error { // delete all user input from the tree view err := c.views.Filter.ToggleVisible() diff --git a/runtime/ui/key/binding.go b/runtime/ui/key/binding.go index be77f6b..ff7c1a7 100644 --- a/runtime/ui/key/binding.go +++ b/runtime/ui/key/binding.go @@ -33,7 +33,7 @@ func GenerateBindings(gui *gocui.Gui, influence string, infos []BindingInfo) ([] var err error var binding *Binding - if info.ConfigKeys != nil && len(info.ConfigKeys) > 0 { + if len(info.ConfigKeys) > 0 { binding, err = NewBindingFromConfig(gui, influence, info.ConfigKeys, info.Display, info.OnAction) } else { binding, err = NewBinding(gui, influence, info.Key, info.Modifier, info.Display, info.OnAction) diff --git a/runtime/ui/layout/compound/layer_details_column.go b/runtime/ui/layout/compound/layer_details_column.go index 7126fc0..92363fb 100644 --- a/runtime/ui/layout/compound/layer_details_column.go +++ b/runtime/ui/layout/compound/layer_details_column.go @@ -72,7 +72,7 @@ func (cl *LayerDetailsCompoundLayout) layoutRow(g *gocui.Gui, minX, minY, maxX, } func (cl *LayerDetailsCompoundLayout) Layout(g *gocui.Gui, minX, minY, maxX, maxY int) error { - logrus.Tracef("LayerDetailsCompountLayout.Layout(minX: %d, minY: %d, maxX: %d, maxY: %d) %s", minX, minY, maxX, maxY, cl.Name()) + logrus.Tracef("LayerDetailsCompoundLayout.Layout(minX: %d, minY: %d, maxX: %d, maxY: %d) %s", minX, minY, maxX, maxY, cl.Name()) layouts := []view.IView{ cl.layer, diff --git a/runtime/ui/view/debug.go b/runtime/ui/view/debug.go index e4cc8aa..3478631 100644 --- a/runtime/ui/view/debug.go +++ b/runtime/ui/view/debug.go @@ -20,7 +20,7 @@ type Debug struct { selectedView Helper } -// newDebugView creates a new view object attached the the global [gocui] screen object. +// newDebugView creates a new view object attached the global [gocui] screen object. func newDebugView(gui *gocui.Gui) (controller *Debug) { controller = new(Debug) diff --git a/runtime/ui/view/filetree.go b/runtime/ui/view/filetree.go index e92be37..923f7a9 100644 --- a/runtime/ui/view/filetree.go +++ b/runtime/ui/view/filetree.go @@ -17,6 +17,8 @@ import ( type ViewOptionChangeListener func() error +type ViewExtractListener func(string) error + // FileTree holds the UI objects and data models for populating the right pane. Specifically the pane that // shows selected layer or aggregate file ASCII tree. type FileTree struct { @@ -29,11 +31,12 @@ type FileTree struct { filterRegex *regexp.Regexp listeners []ViewOptionChangeListener + extractListeners []ViewExtractListener helpKeys []*key.Binding requestedWidthRatio float64 } -// newFileTreeView creates a new view object attached the the global [gocui] screen object. +// newFileTreeView creates a new view object attached the global [gocui] screen object. func newFileTreeView(gui *gocui.Gui, tree *filetree.FileTree, refTrees []*filetree.FileTree, cache filetree.Comparer) (controller *FileTree, err error) { controller = new(FileTree) controller.listeners = make([]ViewOptionChangeListener, 0) @@ -60,6 +63,10 @@ func (v *FileTree) AddViewOptionChangeListener(listener ...ViewOptionChangeListe v.listeners = append(v.listeners, listener...) } +func (v *FileTree) AddViewExtractListener(listener ...ViewExtractListener) { + v.extractListeners = append(v.extractListeners, listener...) +} + func (v *FileTree) SetTitle(title string) { v.title = title } @@ -103,6 +110,11 @@ func (v *FileTree) Setup(view, header *gocui.View) error { OnAction: v.toggleSortOrder, Display: "Toggle sort order", }, + { + ConfigKeys: []string{"keybinding.extract-file"}, + OnAction: v.extractFile, + Display: "Extract File", + }, { ConfigKeys: []string{"keybinding.toggle-added-files"}, OnAction: func() error { return v.toggleShowDiffType(filetree.Added) }, @@ -148,24 +160,24 @@ func (v *FileTree) Setup(view, header *gocui.View) error { OnAction: v.PageDown, }, { - Key: gocui.KeyArrowDown, - Modifier: gocui.ModNone, - OnAction: v.CursorDown, + ConfigKeys: []string{"keybinding.down"}, + Modifier: gocui.ModNone, + OnAction: v.CursorDown, }, { - Key: gocui.KeyArrowUp, - Modifier: gocui.ModNone, - OnAction: v.CursorUp, + ConfigKeys: []string{"keybinding.up"}, + Modifier: gocui.ModNone, + OnAction: v.CursorUp, }, { - Key: gocui.KeyArrowLeft, - Modifier: gocui.ModNone, - OnAction: v.CursorLeft, + ConfigKeys: []string{"keybinding.left"}, + Modifier: gocui.ModNone, + OnAction: v.CursorLeft, }, { - Key: gocui.KeyArrowRight, - Modifier: gocui.ModNone, - OnAction: v.CursorRight, + ConfigKeys: []string{"keybinding.right"}, + Modifier: gocui.ModNone, + OnAction: v.CursorRight, }, } @@ -303,9 +315,32 @@ func (v *FileTree) toggleSortOrder() error { return v.Render() } +func (v *FileTree) extractFile() error { + node := v.vm.CurrentNode(v.filterRegex) + for _, listener := range v.extractListeners { + err := listener(node.Path()) + if err != nil { + return err + } + } + + return nil +} + func (v *FileTree) toggleWrapTree() error { v.view.Wrap = !v.view.Wrap - return nil + + err := v.Update() + if err != nil { + return err + } + err = v.Render() + if err != nil { + return err + } + + // we need to render the changes to the status pane as well (not just this contoller/view) + return v.notifyOnViewOptionChangeListeners() } func (v *FileTree) notifyOnViewOptionChangeListeners() error { @@ -335,7 +370,7 @@ func (v *FileTree) toggleAttributes() error { return err } - // we need to render the changes to the status pane as well (not just this contoller/view) + // we need to render the changes to the status pane as well (not just this controller/view) return v.notifyOnViewOptionChangeListeners() } @@ -352,7 +387,7 @@ func (v *FileTree) toggleShowDiffType(diffType filetree.DiffType) error { return err } - // we need to render the changes to the status pane as well (not just this contoller/view) + // we need to render the changes to the status pane as well (not just this controller/view) return v.notifyOnViewOptionChangeListeners() } diff --git a/runtime/ui/view/filter.go b/runtime/ui/view/filter.go index a9617c7..ccbff27 100644 --- a/runtime/ui/view/filter.go +++ b/runtime/ui/view/filter.go @@ -27,7 +27,7 @@ type Filter struct { filterEditListeners []FilterEditListener } -// newFilterView creates a new view object attached the the global [gocui] screen object. +// newFilterView creates a new view object attached the global [gocui] screen object. func newFilterView(gui *gocui.Gui) (controller *Filter) { controller = new(Filter) diff --git a/runtime/ui/view/image_details.go b/runtime/ui/view/image_details.go index 08098ab..977a72c 100644 --- a/runtime/ui/view/image_details.go +++ b/runtime/ui/view/image_details.go @@ -44,14 +44,14 @@ func (v *ImageDetails) Setup(body, header *gocui.View) error { var infos = []key.BindingInfo{ { - Key: gocui.KeyArrowDown, - Modifier: gocui.ModNone, - OnAction: v.CursorDown, + ConfigKeys: []string{"keybinding.down"}, + Modifier: gocui.ModNone, + OnAction: v.CursorDown, }, { - Key: gocui.KeyArrowUp, - Modifier: gocui.ModNone, - OnAction: v.CursorUp, + ConfigKeys: []string{"keybinding.up"}, + Modifier: gocui.ModNone, + OnAction: v.CursorUp, }, { ConfigKeys: []string{"keybinding.page-up"}, diff --git a/runtime/ui/view/layer.go b/runtime/ui/view/layer.go index ce6954a..9aba279 100644 --- a/runtime/ui/view/layer.go +++ b/runtime/ui/view/layer.go @@ -28,7 +28,7 @@ type Layer struct { helpKeys []*key.Binding } -// newLayerView creates a new view object attached the the global [gocui] screen object. +// newLayerView creates a new view object attached the global [gocui] screen object. func newLayerView(gui *gocui.Gui, layers []*image.Layer) (controller *Layer, err error) { controller = new(Layer) @@ -116,14 +116,14 @@ func (v *Layer) Setup(body *gocui.View, header *gocui.View) error { Display: "Show aggregated changes", }, { - Key: gocui.KeyArrowDown, - Modifier: gocui.ModNone, - OnAction: v.CursorDown, + ConfigKeys: []string{"keybinding.down"}, + Modifier: gocui.ModNone, + OnAction: v.CursorDown, }, { - Key: gocui.KeyArrowUp, - Modifier: gocui.ModNone, - OnAction: v.CursorUp, + ConfigKeys: []string{"keybinding.up"}, + Modifier: gocui.ModNone, + OnAction: v.CursorUp, }, { ConfigKeys: []string{"keybinding.page-up"}, @@ -221,6 +221,14 @@ func (v *Layer) CursorUp() error { return nil } +// SetOrigin updates the origin of the layer view pane. +func (v *Layer) SetOrigin(x, y int) error { + if err := v.body.SetOrigin(x, y); err != nil { + return err + } + return nil +} + // SetCursor resets the cursor and orients the file tree view based on the given layer index. func (v *Layer) SetCursor(layer int) error { v.vm.LayerIndex = layer @@ -340,6 +348,15 @@ func (v *Layer) Render() error { return err } } + + // Adjust origin, if necessary + maxBodyDisplayHeight := int(v.height()) + if v.vm.LayerIndex > maxBodyDisplayHeight { + if err := v.SetOrigin(0, v.vm.LayerIndex-maxBodyDisplayHeight); err != nil { + return err + } + } + return nil }) return nil diff --git a/runtime/ui/view/layer_details.go b/runtime/ui/view/layer_details.go index bfcb425..0334fb2 100644 --- a/runtime/ui/view/layer_details.go +++ b/runtime/ui/view/layer_details.go @@ -5,6 +5,7 @@ import ( "strings" "github.com/awesome-gocui/gocui" + "github.com/dustin/go-humanize" "github.com/sirupsen/logrus" "github.com/wagoodman/dive/dive/image" @@ -39,14 +40,14 @@ func (v *LayerDetails) Setup(body, header *gocui.View) error { var infos = []key.BindingInfo{ { - Key: gocui.KeyArrowDown, - Modifier: gocui.ModNone, - OnAction: v.CursorDown, + ConfigKeys: []string{"keybinding.down"}, + Modifier: gocui.ModNone, + OnAction: v.CursorDown, }, { - Key: gocui.KeyArrowUp, - Modifier: gocui.ModNone, - OnAction: v.CursorUp, + ConfigKeys: []string{"keybinding.up"}, + Modifier: gocui.ModNone, + OnAction: v.CursorUp, }, } @@ -79,12 +80,14 @@ func (v *LayerDetails) Render() error { var lines = make([]string, 0) tags := "(none)" - if v.CurrentLayer.Names != nil && len(v.CurrentLayer.Names) > 0 { + if len(v.CurrentLayer.Names) > 0 { tags = strings.Join(v.CurrentLayer.Names, ", ") } + lines = append(lines, []string{ format.Header("Tags: ") + tags, format.Header("Id: ") + v.CurrentLayer.Id, + format.Header("Size: ") + humanize.Bytes(v.CurrentLayer.Size), format.Header("Digest: ") + v.CurrentLayer.Digest, format.Header("Command:"), v.CurrentLayer.Command, diff --git a/runtime/ui/view/status.go b/runtime/ui/view/status.go index f7b130f..648ce2c 100644 --- a/runtime/ui/view/status.go +++ b/runtime/ui/view/status.go @@ -25,7 +25,7 @@ type Status struct { helpKeys []*key.Binding } -// newStatusView creates a new view object attached the the global [gocui] screen object. +// newStatusView creates a new view object attached the global [gocui] screen object. func newStatusView(gui *gocui.Gui) (controller *Status) { controller = new(Status) diff --git a/runtime/ui/viewmodel/filetree.go b/runtime/ui/viewmodel/filetree.go index 8734eaf..a5f87d0 100644 --- a/runtime/ui/viewmodel/filetree.go +++ b/runtime/ui/viewmodel/filetree.go @@ -38,7 +38,7 @@ type FileTreeViewModel struct { Buffer bytes.Buffer } -// NewFileTreeViewModel creates a new view object attached the the global [gocui] screen object. +// NewFileTreeViewModel creates a new view object attached the global [gocui] screen object. func NewFileTreeViewModel(tree *filetree.FileTree, refTrees []*filetree.FileTree, cache filetree.Comparer) (treeViewModel *FileTreeViewModel, err error) { treeViewModel = new(FileTreeViewModel) @@ -161,6 +161,11 @@ func (vm *FileTreeViewModel) CursorDown() bool { return true } +// CursorLeft moves the cursor up until we reach the Parent Node or top of the tree +func (vm *FileTreeViewModel) CurrentNode(filterRegex *regexp.Regexp) *filetree.FileNode { + return vm.getAbsPositionNode(filterRegex) +} + // CursorLeft moves the cursor up until we reach the Parent Node or top of the tree func (vm *FileTreeViewModel) CursorLeft(filterRegex *regexp.Regexp) error { var visitor func(*filetree.FileNode) error diff --git a/runtime/ui/viewmodel/filetree_test.go b/runtime/ui/viewmodel/filetree_test.go index f315b41..3d53b0f 100644 --- a/runtime/ui/viewmodel/filetree_test.go +++ b/runtime/ui/viewmodel/filetree_test.go @@ -55,7 +55,7 @@ func helperCheckDiff(t *testing.T, expected, actual []byte) { if !bytes.Equal(expected, actual) { dmp := diffmatchpatch.New() diffs := dmp.DiffMain(string(expected), string(actual), true) - t.Errorf(dmp.DiffPrettyText(diffs)) + t.Errorf("%s", dmp.DiffPrettyText(diffs)) t.Errorf("%s: bytes mismatch", t.Name()) } } diff --git a/runtime/ui/viewmodel/layer_set_state_test.go b/runtime/ui/viewmodel/layer_set_state_test.go new file mode 100644 index 0000000..67ba1bc --- /dev/null +++ b/runtime/ui/viewmodel/layer_set_state_test.go @@ -0,0 +1,52 @@ +package viewmodel + +import ( + "testing" +) + +func TestGetCompareIndexes(t *testing.T) { + tests := []struct { + name string + layerIndex int + compareMode LayerCompareMode + compareStartIndex int + expected [4]int + }{ + { + name: "LayerIndex equals CompareStartIndex", + layerIndex: 2, + compareMode: CompareSingleLayer, + compareStartIndex: 2, + expected: [4]int{2, 2, 2, 2}, + }, + { + name: "CompareMode is CompareSingleLayer", + layerIndex: 3, + compareMode: CompareSingleLayer, + compareStartIndex: 1, + expected: [4]int{1, 2, 3, 3}, + }, + { + name: "Default CompareMode", + layerIndex: 4, + compareMode: CompareAllLayers, + compareStartIndex: 1, + expected: [4]int{1, 1, 2, 4}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + state := &LayerSetState{ + LayerIndex: tt.layerIndex, + CompareMode: tt.compareMode, + CompareStartIndex: tt.compareStartIndex, + } + bottomTreeStart, bottomTreeStop, topTreeStart, topTreeStop := state.GetCompareIndexes() + actual := [4]int{bottomTreeStart, bottomTreeStop, topTreeStart, topTreeStop} + if actual != tt.expected { + t.Errorf("expected %v, got %v", tt.expected, actual) + } + }) + } +} diff --git a/runtime/ui/viewmodel/testdata/TestFileShowAggregateChanges.txt b/runtime/ui/viewmodel/testdata/TestFileShowAggregateChanges.txt index 5fdc49c..4cce4ee 100644 --- a/runtime/ui/viewmodel/testdata/TestFileShowAggregateChanges.txt +++ b/runtime/ui/viewmodel/testdata/TestFileShowAggregateChanges.txt @@ -25,7 +25,7 @@ drwxr-xr-x 0:0 0 B │ │ │ └── nested -rw-r--r-- 0:0 6.4 kB │ │ └── somefile3.txt -rwxr-xr-x 0:0 6.4 kB │ └── saved.txt -rw-rw-r-- 0:0 6.4 kB ├── somefile.txt -drwxrwxrwx 0:0 6.4 kB ├── tmp +drwxrwxrwt 0:0 6.4 kB ├── tmp -rw-r--r-- 0:0 6.4 kB │ └── saved.again1.txt drwxr-xr-x 0:0 0 B ├── usr drwxr-xr-x 1:1 0 B │ └── sbin diff --git a/runtime/ui/viewmodel/testdata/TestFileTreeDirCollapse.txt b/runtime/ui/viewmodel/testdata/TestFileTreeDirCollapse.txt index 09b1f31..23c5246 100644 --- a/runtime/ui/viewmodel/testdata/TestFileTreeDirCollapse.txt +++ b/runtime/ui/viewmodel/testdata/TestFileTreeDirCollapse.txt @@ -3,7 +3,7 @@ drwxr-xr-x 0:0 0 B ├── dev drwxr-xr-x 0:0 1.0 kB ├─⊕ etc drwxr-xr-x 65534:65534 0 B ├── home drwx------ 0:0 0 B ├── root -drwxrwxrwx 0:0 0 B ├── tmp +drwxrwxrwt 0:0 0 B ├── tmp drwxr-xr-x 0:0 0 B ├── usr drwxr-xr-x 1:1 0 B │ └── sbin drwxr-xr-x 0:0 0 B └── var diff --git a/runtime/ui/viewmodel/testdata/TestFileTreeDirCollapseAll.txt b/runtime/ui/viewmodel/testdata/TestFileTreeDirCollapseAll.txt index 581ba48..89d27dd 100644 --- a/runtime/ui/viewmodel/testdata/TestFileTreeDirCollapseAll.txt +++ b/runtime/ui/viewmodel/testdata/TestFileTreeDirCollapseAll.txt @@ -3,7 +3,7 @@ drwxr-xr-x 0:0 0 B ├── dev drwxr-xr-x 0:0 1.0 kB ├─⊕ etc drwxr-xr-x 65534:65534 0 B ├── home drwx------ 0:0 0 B ├── root -drwxrwxrwx 0:0 0 B ├── tmp +drwxrwxrwt 0:0 0 B ├── tmp drwxr-xr-x 0:0 0 B ├─⊕ usr drwxr-xr-x 0:0 0 B └─⊕ var diff --git a/runtime/ui/viewmodel/testdata/TestFileTreeDirCursorRight.txt b/runtime/ui/viewmodel/testdata/TestFileTreeDirCursorRight.txt index c4126a9..bab317d 100644 --- a/runtime/ui/viewmodel/testdata/TestFileTreeDirCursorRight.txt +++ b/runtime/ui/viewmodel/testdata/TestFileTreeDirCursorRight.txt @@ -12,7 +12,7 @@ drwxr-xr-x 0:0 0 B │ │ └── if-up.d -rw------- 0:0 243 B │ └── shadow drwxr-xr-x 65534:65534 0 B ├── home drwx------ 0:0 0 B ├── root -drwxrwxrwx 0:0 0 B ├── tmp +drwxrwxrwt 0:0 0 B ├── tmp drwxr-xr-x 0:0 0 B ├── usr drwxr-xr-x 1:1 0 B │ └── sbin drwxr-xr-x 0:0 0 B └── var diff --git a/runtime/ui/viewmodel/testdata/TestFileTreeGoCase.txt b/runtime/ui/viewmodel/testdata/TestFileTreeGoCase.txt index e86415c..1c1e43c 100644 --- a/runtime/ui/viewmodel/testdata/TestFileTreeGoCase.txt +++ b/runtime/ui/viewmodel/testdata/TestFileTreeGoCase.txt @@ -406,7 +406,7 @@ drwxr-xr-x 0:0 0 B │ │ └── if-up.d -rw------- 0:0 243 B │ └── shadow drwxr-xr-x 65534:65534 0 B ├── home drwx------ 0:0 0 B ├── root -drwxrwxrwx 0:0 0 B ├── tmp +drwxrwxrwt 0:0 0 B ├── tmp drwxr-xr-x 0:0 0 B ├── usr drwxr-xr-x 1:1 0 B │ └── sbin drwxr-xr-x 0:0 0 B └── var diff --git a/runtime/ui/viewmodel/testdata/TestFileTreeHideAddedRemovedModified.txt b/runtime/ui/viewmodel/testdata/TestFileTreeHideAddedRemovedModified.txt index a34de17..d0e35ef 100644 --- a/runtime/ui/viewmodel/testdata/TestFileTreeHideAddedRemovedModified.txt +++ b/runtime/ui/viewmodel/testdata/TestFileTreeHideAddedRemovedModified.txt @@ -11,7 +11,7 @@ drwxr-xr-x 0:0 0 B │ │ └── if-up.d -rw-r--r-- 0:0 340 B │ ├── passwd -rw------- 0:0 243 B │ └── shadow drwxr-xr-x 65534:65534 0 B ├── home -drwxrwxrwx 0:0 0 B ├── tmp +drwxrwxrwt 0:0 0 B ├── tmp drwxr-xr-x 0:0 0 B ├── usr drwxr-xr-x 1:1 0 B │ └── sbin drwxr-xr-x 0:0 0 B └── var diff --git a/runtime/ui/viewmodel/testdata/TestFileTreeSelectLayer.txt b/runtime/ui/viewmodel/testdata/TestFileTreeSelectLayer.txt index 360737b..a5ade1c 100644 --- a/runtime/ui/viewmodel/testdata/TestFileTreeSelectLayer.txt +++ b/runtime/ui/viewmodel/testdata/TestFileTreeSelectLayer.txt @@ -13,7 +13,7 @@ drwxr-xr-x 0:0 0 B │ │ └── if-up.d drwxr-xr-x 65534:65534 0 B ├── home drwx------ 0:0 0 B ├── root -rw-rw-r-- 0:0 6.4 kB ├── somefile.txt -drwxrwxrwx 0:0 0 B ├── tmp +drwxrwxrwt 0:0 0 B ├── tmp drwxr-xr-x 0:0 0 B ├── usr drwxr-xr-x 1:1 0 B │ └── sbin drwxr-xr-x 0:0 0 B └── var diff --git a/utils/format.go b/utils/format.go index cabdbc1..4b1fd31 100644 --- a/utils/format.go +++ b/utils/format.go @@ -3,7 +3,7 @@ package utils import ( "strings" - "github.com/logrusorgru/aurora" + "github.com/logrusorgru/aurora/v4" ) func TitleFormat(s string) string {