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