Reintegrate fork joschi/dive (#570)

* 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 304bc2c7ed

* 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 19714728c9.

* 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 <kianmeng@cpan.org>

* docs: add more Windows installation options to the README (wagoodman/dive#470)

Fixes wagoodman/dive#346

Co-authored-by: Nikolas Grottendieck <git@nikolasgrottendieck.com>

* 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 <c0d3r.nodiru.gaji@gmail.com>

* 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 <tmgross@umich.edu>

* chore: remove repetitive words (wagoodman/dive#515)

Co-authored-by: thirdkeyword <fliterdashen@gmail.com>
Signed-off-by: thirdkeyword <fliterdashen@gmail.com>

* fix: close tmp files (wagoodman/dive#517)

Co-authored-by: guoguangwu <guoguangwug@gmail.com>
Signed-off-by: guoguangwu <guoguangwug@gmail.com>

* chore: fix phony ci-release target (wagoodman/dive#530)

Co-authored-by: Richard Steinmetz <richard@steinmetz.cloud>

* 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 <afsharzadeh8@gmail.com>

* 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 <docwhat@gerf.org>

* fix: line wrap toggle now updates status bar indicator (wagoodman/dive#497)

Fixes wagoodman/dive#496

Co-authored-by: Scott Moore <scott.moore@viavisolutions.com>

* 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 <alexander.yastrebov@zalando.de>

* 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 <context-name>`.

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 <raj25by10@gmail.com>

* 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 <anatoli@rainforce.org>

* 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 <akash19nayak@gmail.com>

* 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 <kaedwen@heinrich.blue>

* 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 <gwendolyngoetz@users.noreply.github.com>
Co-authored-by: Mehmet Ümit Özden <ozdenmehmetumit@gmail.com>

* 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 <jochen@schalanda.name>

* 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 <jochen@schalanda.name>

* 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 e46f931a8a

* chore: remove Renovate configuration

Reverts aa75fbf36f

* Revert "ci: validate Renovate configuration file on build"

This reverts commit 8d938774e2.

---------

Signed-off-by: thirdkeyword <fliterdashen@gmail.com>
Signed-off-by: guoguangwu <guoguangwug@gmail.com>
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 <kianmeng@cpan.org>
Co-authored-by: Nikolas Grottendieck <git@nikolasgrottendieck.com>
Co-authored-by: YóUnǎi <c0d3r.nodiru.gaji@gmail.com>
Co-authored-by: Trevor Gross <tmgross@umich.edu>
Co-authored-by: thirdkeyword <fliterdashen@gmail.com>
Co-authored-by: guoguangwu <guoguangwug@gmail.com>
Co-authored-by: Richard Steinmetz <richard@steinmetz.cloud>
Co-authored-by: Zhang Yuanfeng <71358306+YuanfengZhang@users.noreply.github.com>
Co-authored-by: Ali Afsharzadeh <afsharzadeh8@gmail.com>
Co-authored-by: Christian Höltje <docwhat@gerf.org>
Co-authored-by: Scott Moore <scott.moore@viavisolutions.com>
Co-authored-by: Alexander Yastrebov <alexander.yastrebov@zalando.de>
Co-authored-by: Rajiv Kushwaha <raj25by10@gmail.com>
Co-authored-by: Anatoli Babenia <anatoli@rainforce.org>
Co-authored-by: st-gr <38470677+st-gr@users.noreply.github.com>
Co-authored-by: Akash Nayak <akash19nayak@gmail.com>
Co-authored-by: kaedwen <kaedwen@heinrich.blue>
Co-authored-by: Gwendolyn Goetz <gwendolyngoetz@users.noreply.github.com>
Co-authored-by: Mehmet Ümit Özden <ozdenmehmetumit@gmail.com>
Co-authored-by: steven-halaka <steven.halaka@twosixtech.com>
This commit is contained in:
Jochen Schalanda 2025-03-18 05:15:09 +01:00 committed by GitHub
commit e65b32c4d7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
69 changed files with 5545 additions and 460 deletions

View file

@ -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

2
.data/Dockerfile.minimal Normal file
View file

@ -0,0 +1,2 @@
FROM scratch
COPY README.md /README.md

Binary file not shown.

BIN
.data/test-gzip-image.tar Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
.data/test-zstd-image.tar Normal file

Binary file not shown.

View file

@ -4,7 +4,7 @@ inputs:
go-version:
description: "Go version to install"
required: true
default: "1.20.x"
default: "1.24.x"
use-go-cache:
description: "Restore go cache"
required: true
@ -24,13 +24,13 @@ inputs:
runs:
using: "composite"
steps:
- uses: actions/setup-go@v3
- uses: actions/setup-go@v5
with:
go-version: ${{ inputs.go-version }}
- name: Restore tool cache
id: tool-cache
uses: actions/cache@v3
uses: actions/cache@v4
with:
path: ${{ github.workspace }}/.tmp
key: ${{ inputs.cache-key-prefix }}-${{ runner.os }}-tool-${{ hashFiles('Makefile') }}
@ -40,7 +40,7 @@ runs:
- name: Restore go module cache
id: go-mod-cache
if: inputs.use-go-cache == 'true'
uses: actions/cache@v3
uses: actions/cache@v4
with:
path: |
~/go/pkg/mod
@ -56,7 +56,7 @@ runs:
- name: Restore go build cache
id: go-cache
if: inputs.use-go-cache == 'true'
uses: actions/cache@v3
uses: actions/cache@v4
with:
path: |
~/.cache/go-build

View file

@ -11,7 +11,7 @@ jobs:
environment: release
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Check if tag already exists
# note: this will fail if the tag already exists
@ -20,7 +20,7 @@ jobs:
git tag ${{ github.event.inputs.version }}
- name: Check static analysis results
uses: fountainhead/action-wait-for-check@v1.1.0
uses: fountainhead/action-wait-for-check@v1.2.0
id: static-analysis
with:
token: ${{ secrets.GITHUB_TOKEN }}
@ -29,7 +29,7 @@ jobs:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Check unit test results
uses: fountainhead/action-wait-for-check@v1.1.0
uses: fountainhead/action-wait-for-check@v1.2.0
id: unit
with:
token: ${{ secrets.GITHUB_TOKEN }}
@ -38,7 +38,7 @@ jobs:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Check acceptance test results (linux)
uses: fountainhead/action-wait-for-check@v1.1.0
uses: fountainhead/action-wait-for-check@v1.2.0
id: acceptance-linux
with:
token: ${{ secrets.GITHUB_TOKEN }}
@ -47,7 +47,7 @@ jobs:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Check acceptance test results (mac)
uses: fountainhead/action-wait-for-check@v1.1.0
uses: fountainhead/action-wait-for-check@v1.2.0
id: acceptance-mac
with:
token: ${{ secrets.GITHUB_TOKEN }}
@ -56,7 +56,7 @@ jobs:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Check acceptance test results (windows)
uses: fountainhead/action-wait-for-check@v1.1.0
uses: fountainhead/action-wait-for-check@v1.2.0
id: acceptance-windows
with:
token: ${{ secrets.GITHUB_TOKEN }}
@ -64,7 +64,6 @@ jobs:
checkName: "Acceptance tests (Windows)"
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Quality gate
if: steps.static-analysis.outputs.conclusion != 'success' || steps.unit.outputs.conclusion != 'success' || steps.acceptance-linux.outputs.conclusion != 'success' || steps.acceptance-mac.outputs.conclusion != 'success' || steps.acceptance-windows.outputs.conclusion != 'success'
run: |
@ -82,15 +81,25 @@ jobs:
permissions:
# for tagging
contents: write
# for pushing container images
packages: write
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Bootstrap environment
uses: ./.github/actions/bootstrap
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Tag release
run: |
git tag ${{ github.event.inputs.version }}
@ -98,11 +107,12 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Login to Docker Hub
uses: docker/login-action@v2
- name: Login to container registry
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build & publish release artifacts
run: make ci-release

View file

@ -7,13 +7,12 @@ on:
pull_request:
jobs:
Static-Analysis:
# Note: changing this job name requires making the same update in the .github/workflows/release.yaml pipeline
name: "Static analysis"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Bootstrap environment
uses: ./.github/actions/bootstrap
@ -21,7 +20,6 @@ jobs:
- name: Run static analysis
run: make static-analysis
Unit-Test:
# Note: changing this job name requires making the same update in the .github/workflows/release.yaml pipeline
name: "Unit tests"
@ -29,13 +27,12 @@ jobs:
matrix:
platform:
- ubuntu-latest
# - macos-latest # todo: mac runners are expensive minute-wise
# - windows-latest # todo: support windows
# - macos-latest # todo: mac runners are expensive minute-wise
# - windows-latest # todo: support windows
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Bootstrap environment
uses: ./.github/actions/bootstrap
@ -43,16 +40,21 @@ jobs:
- name: Run unit tests
run: make unit
Build-Snapshot-Artifacts:
name: "Build snapshot artifacts"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Bootstrap environment
uses: ./.github/actions/bootstrap
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build snapshot artifacts
run: make snapshot
@ -65,28 +67,26 @@ jobs:
# why not use actions/upload-artifact? It is very slow (3 minutes to upload ~600MB of data, vs 10 seconds with this approach).
# see https://github.com/actions/upload-artifact/issues/199 for more info
- name: Upload snapshot artifacts
uses: actions/cache/save@v3
uses: actions/cache/save@v4
with:
path: snapshot
key: snapshot-build-${{ github.run_id }}
# ... however the cache trick doesn't work on windows :(
- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@v4
with:
name: windows-artifacts
path: snapshot/dive_windows_amd64_v1/dive.exe
Acceptance-Linux:
name: "Acceptance tests (Linux)"
needs: [ Build-Snapshot-Artifacts ]
needs: [Build-Snapshot-Artifacts]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: Download snapshot build
uses: actions/cache/restore@v3
uses: actions/cache/restore@v4
with:
path: snapshot
key: snapshot-build-${{ github.run_id }}
@ -100,17 +100,15 @@ jobs:
- name: Test RPM package installation
run: make ci-test-rpm-package-install
Acceptance-Mac:
name: "Acceptance tests (Mac)"
needs: [ Build-Snapshot-Artifacts ]
needs: [Build-Snapshot-Artifacts]
runs-on: macos-latest
steps:
- uses: actions/checkout@master
- name: Download snapshot build
uses: actions/cache/restore@v3
uses: actions/cache/restore@v4
with:
path: snapshot
key: snapshot-build-${{ github.run_id }}
@ -118,16 +116,14 @@ jobs:
- name: Test darwin run
run: make ci-test-mac-run
Acceptance-Windows:
name: "Acceptance tests (Windows)"
needs: [ Build-Snapshot-Artifacts ]
needs: [Build-Snapshot-Artifacts]
runs-on: windows-latest
steps:
- uses: actions/checkout@master
- uses: actions/download-artifact@v3
- uses: actions/download-artifact@v4
with:
name: windows-artifacts

View file

@ -67,7 +67,7 @@ linters-settings:
# - prealloc # following this rule isn't consistently a good idea, as it sometimes forces unnecessary allocations that result in less idiomatic code
# - scopelint # deprecated
# - testpackage
# - wsl # this doens't have an auto-fixer yet and is pretty noisy (https://github.com/bombsimon/wsl/issues/90)
# - wsl # this doesn't have an auto-fixer yet and is pretty noisy (https://github.com/bombsimon/wsl/issues/90)
# - varcheck # deprecated (since v1.49.0) due to: The owner seems to have abandoned the linter. Replaced by unused.
# - deadcode # deprecated (since v1.49.0) due to: The owner seems to have abandoned the linter. Replaced by unused.
# - structcheck # deprecated (since v1.49.0) due to: The owner seems to have abandoned the linter. Replaced by unused.

View file

@ -1,3 +1,5 @@
version: 2
release:
# If set to auto, will mark the release as not ready for production in case there is an indicator for this in the
# tag e.g. v1.0.0-rc1 .If set to true, will mark the release as not ready for production.
@ -17,6 +19,7 @@ builds:
goarch:
- amd64
- arm64
- ppc64le
ldflags: -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.buildTime={{.Date}}`.
brews:
@ -43,15 +46,44 @@ nfpms:
- deb
dockers:
-
- id: docker-amd64
ids:
- dive
dockerfile: Dockerfile
# todo: on 1.0 remove 'v' prefix
use: buildx
goarch: amd64
image_templates:
- "wagoodman/dive:latest"
- "wagoodman/dive:{{ .Tag }}"
- "wagoodman/dive:v{{ .Major }}"
- "wagoodman/dive:v{{ .Major }}.{{ .Minor }}"
- '{{ envOrDefault "REGISTRY" "docker.io" }}/wagoodman/dive:latest-amd64'
- '{{ envOrDefault "REGISTRY" "docker.io" }}/wagoodman/dive:{{ .Version }}-amd64'
build_flag_templates:
- "--build-arg=DOCKER_CLI_VERSION={{.Env.DOCKER_CLI_VERSION}}"
- "--label=org.opencontainers.image.created={{.Date}}"
- "--label=org.opencontainers.image.title={{.ProjectName}}"
- "--label=org.opencontainers.image.revision={{.FullCommit}}"
- "--label=org.opencontainers.image.version={{.Version}}"
- "--label=org.opencontainers.image.source={{.GitURL}}"
- "--platform=linux/amd64"
- id: docker-arm64
ids:
- dive
use: buildx
goarch: arm64
image_templates:
- '{{ envOrDefault "REGISTRY" "docker.io" }}/wagoodman/dive:latest-arm64'
- '{{ envOrDefault "REGISTRY" "docker.io" }}/wagoodman/dive:{{ .Version }}-arm64'
build_flag_templates:
- "--build-arg=DOCKER_CLI_VERSION={{.Env.DOCKER_CLI_VERSION}}"
- "--label=org.opencontainers.image.created={{.Date}}"
- "--label=org.opencontainers.image.title={{.ProjectName}}"
- "--label=org.opencontainers.image.revision={{.FullCommit}}"
- "--label=org.opencontainers.image.version={{.Version}}"
- "--label=org.opencontainers.image.source={{.GitURL}}"
- "--platform=linux/arm64/v8"
docker_manifests:
- name_template: '{{ envOrDefault "REGISTRY" "docker.io" }}/wagoodman/dive:latest'
image_templates:
- '{{ envOrDefault "REGISTRY" "docker.io" }}/wagoodman/dive:latest-amd64'
- '{{ envOrDefault "REGISTRY" "docker.io" }}/wagoodman/dive:latest-arm64'
- name_template: '{{ envOrDefault "REGISTRY" "docker.io" }}/wagoodman/dive:{{ .Version }}'
image_templates:
- '{{ envOrDefault "REGISTRY" "docker.io" }}/wagoodman/dive:{{ .Version }}-amd64'
- '{{ envOrDefault "REGISTRY" "docker.io" }}/wagoodman/dive:{{ .Version }}-arm64'

View file

@ -1,10 +1,12 @@
FROM alpine:3.18
FROM alpine:3.21 AS base
ARG DOCKER_CLI_VERSION=${DOCKER_CLI_VERSION}
RUN wget -O- https://download.docker.com/linux/static/stable/$(uname -m)/docker-${DOCKER_CLI_VERSION}.tgz | \
tar -xzf - docker/docker --strip-component=1 && \
mv docker /usr/local/bin
tar -xzf - docker/docker --strip-component=1 -C /usr/local/bin
COPY dive /usr/local/bin/
FROM scratch
COPY --from=base /usr/local/bin /usr/local/bin
ENTRYPOINT ["/usr/local/bin/dive"]

View file

@ -1,24 +1,24 @@
BIN = dive
TEMP_DIR = ./.tmp
PWD := ${CURDIR}
PRODUCTION_REGISTRY = docker.io
REGISTRY ?= docker.io
SHELL = /bin/bash -o pipefail
TEST_IMAGE = busybox:latest
# Tool versions #################################
GOLANG_CI_VERSION = v1.52.2
GOLANG_CI_VERSION = v1.64.5
GOBOUNCER_VERSION = v0.4.0
GORELEASER_VERSION = v1.19.1
GORELEASER_VERSION = v2.4.4
GOSIMPORTS_VERSION = v0.3.8
CHRONICLE_VERSION = v0.6.0
GLOW_VERSION = v1.5.0
DOCKER_CLI_VERSION = 23.0.6
CHRONICLE_VERSION = v0.8.0
GLOW_VERSION = v1.5.1
DOCKER_CLI_VERSION = 28.0.0
# Command templates #################################
LINT_CMD = $(TEMP_DIR)/golangci-lint run --tests=false --timeout=2m --config .golangci.yaml
GOIMPORTS_CMD = $(TEMP_DIR)/gosimports -local github.com/wagoodman
RELEASE_CMD = DOCKER_CLI_VERSION=$(DOCKER_CLI_VERSION) $(TEMP_DIR)/goreleaser release --clean
SNAPSHOT_CMD = $(RELEASE_CMD) --skip-publish --snapshot --skip-sign
SNAPSHOT_CMD = $(RELEASE_CMD) --skip=publish --skip=sign --snapshot
CHRONICLE_CMD = $(TEMP_DIR)/chronicle
GLOW_CMD = $(TEMP_DIR)/glow
@ -34,7 +34,7 @@ SUCCESS := $(BOLD)$(GREEN)
# Test variables #################################
# the quality gate lower threshold for unit test total % coverage (by function statements)
COVERAGE_THRESHOLD := 55
COVERAGE_THRESHOLD := 30
## Build variables #################################
DIST_DIR = dist
@ -86,7 +86,7 @@ bootstrap-tools: $(TEMP_DIR)
curl -sSfL https://raw.githubusercontent.com/anchore/chronicle/main/install.sh | sh -s -- -b $(TEMP_DIR)/ $(CHRONICLE_VERSION)
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(TEMP_DIR)/ $(GOLANG_CI_VERSION)
curl -sSfL https://raw.githubusercontent.com/wagoodman/go-bouncer/master/bouncer.sh | sh -s -- -b $(TEMP_DIR)/ $(GOBOUNCER_VERSION)
GOBIN="$(realpath $(TEMP_DIR))" go install github.com/goreleaser/goreleaser@$(GORELEASER_VERSION)
GOBIN="$(realpath $(TEMP_DIR))" go install github.com/goreleaser/goreleaser/v2@$(GORELEASER_VERSION)
GOBIN="$(realpath $(TEMP_DIR))" go install github.com/rinchsan/gosimports/cmd/gosimports@$(GOSIMPORTS_VERSION)
GOBIN="$(realpath $(TEMP_DIR))" go install github.com/charmbracelet/glow@$(GLOW_VERSION)
@ -125,7 +125,34 @@ bootstrap: bootstrap-go bootstrap-tools ## Download and install all go dependenc
.PHONY: generate-test-data
generate-test-data:
docker build -t dive-test:latest -f .data/Dockerfile.test-image . && docker image save -o .data/test-docker-image.tar dive-test:latest && echo 'Exported test data!'
docker build -t dive-test:latest -f .data/Dockerfile.test-image . && \
docker image save -o .data/test-docker-image.tar dive-test:latest && \
echo 'Exported test data!'
.PHONY: generate-compressed-test-images
generate-compressed-test-images:
for alg in uncompressed gzip estargz zstd; do \
for exporter in docker image; do \
docker buildx build \
-f .data/Dockerfile.minimal \
--tag test-dive-$${exporter}:$${alg} \
--output type=$${exporter},force-compression=true,compression=$${alg} . ; \
done ; \
done && \
echo 'Exported test data!'
.PHONY: generate-compressed-test-data
generate-compressed-test-data:
for alg in uncompressed gzip estargz zstd; \
do \
docker buildx build \
-f .data/Dockerfile.minimal \
--output type=tar,dest=.data/test-$${alg}-image.tar,force-compression=true,compression=$${alg} . ; \
docker buildx build \
-f .data/Dockerfile.minimal \
--output type=oci,dest=.data/test-oci-$${alg}-image.tar,force-compression=true,compression=$${alg} . ; \
done && \
echo 'Exported test data!'
## Static analysis targets #################################
@ -186,7 +213,7 @@ ci-test-docker-image:
--rm \
-t \
-v /var/run/docker.sock:/var/run/docker.sock \
'${PRODUCTION_REGISTRY}/wagoodman/dive:latest' \
'${REGISTRY}/wagoodman/dive:latest-amd64' \
'${TEST_IMAGE}' \
--ci
@ -227,12 +254,18 @@ ci-test-rpm-package-install:
"
.PHONY: ci-test-linux-run
ci-test-linux-run:
ci-test-linux-run: generate-compressed-test-images
ls -la $(SNAPSHOT_DIR)
ls -la $(SNAPSHOT_DIR)/dive_linux_amd64_v1
chmod 755 $(SNAPSHOT_DIR)/dive_linux_amd64_v1/dive && \
$(SNAPSHOT_DIR)/dive_linux_amd64_v1/dive '${TEST_IMAGE}' --ci && \
$(SNAPSHOT_DIR)/dive_linux_amd64_v1/dive --source docker-archive .data/test-kaniko-image.tar --ci --ci-config .data/.dive-ci
for alg in uncompressed gzip estargz zstd; do \
for exporter in docker image; do \
$(SNAPSHOT_DIR)/dive_linux_amd64_v1/dive "test-dive-$${exporter}:$${alg}" --ci ; \
done && \
$(SNAPSHOT_DIR)/dive_linux_amd64_v1/dive --source docker-archive .data/test-oci-$${alg}-image.tar --ci --ci-config .data/.dive-ci; \
done
# we're not attempting to test docker, just our ability to run on these systems. This avoids setting up docker in CI.
.PHONY: ci-test-mac-run
@ -282,7 +315,7 @@ $(CHANGELOG):
release: ## Cut a new release
@.github/scripts/trigger-release.sh
.PHONY: release
.PHONY: ci-release
ci-release: ci-check clean-dist $(CHANGELOG)
$(call title,Publishing release artifacts)
@ -315,9 +348,8 @@ clean-changelog:
rm -f $(CHANGELOG) VERSION
## Halp! #################################
## Help! #################################
.PHONY: help
help:
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "$(BOLD)$(CYAN)%-25s$(RESET)%s\n", $$1, $$2}'

View file

@ -5,7 +5,7 @@
[![License: MIT](https://img.shields.io/badge/License-MIT%202.0-blue.svg)](https://github.com/wagoodman/dive/blob/main/LICENSE)
[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg?style=flat)](https://www.paypal.me/wagoodman)
**A tool for exploring a docker image, layer contents, and discovering ways to shrink the size of your Docker/OCI image.**
**A tool for exploring a Docker image, layer contents, and discovering ways to shrink the size of your Docker/OCI image.**
![Image](.data/demo.gif)
@ -15,9 +15,9 @@ To analyze a Docker image simply run dive with an image tag/id/digest:
dive <your-image-tag>
```
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 <your-image-tag>
# for example
@ -29,7 +29,7 @@ or if you want to build your image then jump straight into analyzing it:
dive build -t <some-tag> .
```
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 <some-tag> .
ghcr.io/wagoodman/dive:latest build -t <some-tag> .
```
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 <dive arguments...>
ghcr.io/wagoodman/dive:latest <dive arguments...>
```
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 <dive arguments...>
ghcr.io/wagoodman/dive:latest <dive arguments...>
```
**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 <dive arguments...>
ghcr.io/wagoodman/dive:latest <dive arguments...>
```
## CI Integration
@ -228,8 +247,11 @@ Key Binding | Description
<kbd>Ctrl + C</kbd> or <kbd>Q</kbd> | Exit
<kbd>Tab</kbd> | Switch between the layer and filetree views
<kbd>Ctrl + F</kbd> | Filter files
<kbd>PageUp</kbd> | Scroll up a page
<kbd>PageDown</kbd> | Scroll down a page
<kbd>ESC</kbd> | Close filter files
<kbd>PageUp</kbd> or <kbd>U</kbd> | Scroll up a page
<kbd>PageDown</kbd> or <kbd>D</kbd> | Scroll down a page
<kbd>Up</kbd> or <kbd>K</kbd> | Move up one line within a page
<kbd>Down</kbd> or <kbd>J</kbd> | Move down one line within a page
<kbd>Ctrl + A</kbd> | Layer view: see aggregated image modifications
<kbd>Ctrl + L</kbd> | Layer view: see current layer modifications
<kbd>Space</kbd> | Filetree view: collapse/uncollapse a directory
@ -239,8 +261,8 @@ Key Binding | Description
<kbd>Ctrl + M</kbd> | Filetree view: show/hide modified files
<kbd>Ctrl + U</kbd> | Filetree view: show/hide unmodified files
<kbd>Ctrl + B</kbd> | Filetree view: show/hide file attributes
<kbd>PageUp</kbd> | Filetree view: scroll up a page
<kbd>PageDown</kbd> | Filetree view: scroll down a page
<kbd>PageUp</kbd> or <kbd>U</kbd> | Filetree view: scroll up a page
<kbd>PageDown</kbd> or <kbd>D</kbd> | 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.

View file

@ -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", "")

View file

@ -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 {

View file

@ -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()

View file

@ -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.

View file

@ -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 {

View file

@ -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)
}
}

View file

@ -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
}

View file

@ -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)
}
}

View file

@ -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():

View file

@ -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")
}

View file

@ -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...)

View file

@ -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"
}

View file

@ -0,0 +1,7 @@
//go:build !windows
package docker
const (
defaultDockerHost = "unix:///var/run/docker.sock"
)

View file

@ -0,0 +1,5 @@
package docker
const (
defaultDockerHost = "npipe:////.pipe/docker_engine"
)

View file

@ -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,14 +107,19 @@ 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 + "'...")
// 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
}
}
readCloser, err := dockerClient.ImageSave(ctx, []string{id})
@ -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
}

View file

@ -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 {
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
}

View file

@ -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...)

View file

@ -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 {

View file

@ -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")
}

View file

@ -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
}

84
go.mod
View file

@ -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
)

321
go.sum
View file

@ -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=

View file

@ -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"

View file

@ -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"

View file

@ -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,
}
}

File diff suppressed because it is too large Load diff

View file

@ -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"`
FileList []filetree.FileInfo `json:"fileList"`
}

View file

@ -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

View file

@ -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"},
},
},

View file

@ -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,11 +77,11 @@ func newApp(gui *gocui.Gui, imageName string, analysis *image.AnalysisResult, ca
Display: "Switch view",
},
{
Key: gocui.KeyArrowRight,
ConfigKeys: []string{"keybinding.right"},
OnAction: controller.NextPane,
},
{
Key: gocui.KeyArrowLeft,
ConfigKeys: []string{"keybinding.left"},
OnAction: controller.PrevPane,
},
{
@ -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
}

View file

@ -15,9 +15,11 @@ import (
type Controller struct {
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
@ -26,6 +28,8 @@ func NewCollection(g *gocui.Gui, imageName string, analysis *image.AnalysisResul
controller := &Controller{
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()

View file

@ -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)

View file

@ -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,

View file

@ -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)

View file

@ -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,22 +160,22 @@ func (v *FileTree) Setup(view, header *gocui.View) error {
OnAction: v.PageDown,
},
{
Key: gocui.KeyArrowDown,
ConfigKeys: []string{"keybinding.down"},
Modifier: gocui.ModNone,
OnAction: v.CursorDown,
},
{
Key: gocui.KeyArrowUp,
ConfigKeys: []string{"keybinding.up"},
Modifier: gocui.ModNone,
OnAction: v.CursorUp,
},
{
Key: gocui.KeyArrowLeft,
ConfigKeys: []string{"keybinding.left"},
Modifier: gocui.ModNone,
OnAction: v.CursorLeft,
},
{
Key: gocui.KeyArrowRight,
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()
}

View file

@ -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)

View file

@ -44,12 +44,12 @@ func (v *ImageDetails) Setup(body, header *gocui.View) error {
var infos = []key.BindingInfo{
{
Key: gocui.KeyArrowDown,
ConfigKeys: []string{"keybinding.down"},
Modifier: gocui.ModNone,
OnAction: v.CursorDown,
},
{
Key: gocui.KeyArrowUp,
ConfigKeys: []string{"keybinding.up"},
Modifier: gocui.ModNone,
OnAction: v.CursorUp,
},

View file

@ -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,12 +116,12 @@ func (v *Layer) Setup(body *gocui.View, header *gocui.View) error {
Display: "Show aggregated changes",
},
{
Key: gocui.KeyArrowDown,
ConfigKeys: []string{"keybinding.down"},
Modifier: gocui.ModNone,
OnAction: v.CursorDown,
},
{
Key: gocui.KeyArrowUp,
ConfigKeys: []string{"keybinding.up"},
Modifier: gocui.ModNone,
OnAction: v.CursorUp,
},
@ -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

View file

@ -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,12 +40,12 @@ func (v *LayerDetails) Setup(body, header *gocui.View) error {
var infos = []key.BindingInfo{
{
Key: gocui.KeyArrowDown,
ConfigKeys: []string{"keybinding.down"},
Modifier: gocui.ModNone,
OnAction: v.CursorDown,
},
{
Key: gocui.KeyArrowUp,
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,

View file

@ -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)

View file

@ -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

View file

@ -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())
}
}

View file

@ -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)
}
})
}
}

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -3,7 +3,7 @@ package utils
import (
"strings"
"github.com/logrusorgru/aurora"
"github.com/logrusorgru/aurora/v4"
)
func TitleFormat(s string) string {