From d5e8a9296882f113e3990daca5e6b16109f512b8 Mon Sep 17 00:00:00 2001 From: Alex Goodman Date: Thu, 6 Jul 2023 22:01:46 -0400 Subject: [PATCH] Rework CI validation workflow and makefile (#460) * rework CI validation workflow and makefile * enable push * fix job names * fix license check * fix snapshot builds * fix acceptance tests * fix linting * disable pull request event * rework windows runner caching * disable release pipeline and add issue templates --- .bouncer.yaml | 12 + .circleci/config.yml | 59 ---- .github/FUNDING.yml | 1 - .github/ISSUE_TEMPLATE/bug_report.md | 20 ++ .github/ISSUE_TEMPLATE/feature_request.md | 15 + .github/actions/bootstrap/action.yaml | 76 ++++ .github/scripts/ci-check.sh | 11 + .github/scripts/coverage.py | 36 ++ .github/scripts/go-mod-tidy-check.sh | 31 ++ .github/scripts/trigger-release.sh | 50 +++ .github/workflows/pipeline.yml | 180 ---------- .github/workflows/release.yaml | 114 ++++++ .github/workflows/validations.yaml | 135 ++++++++ .gitignore | 24 +- .golangci.yaml | 74 ++++ .goreleaser.yml => .goreleaser.yaml | 12 +- .scripts/tag.sh | 37 -- .scripts/test-coverage.sh | 55 --- Makefile | 324 ++++++++++++++---- README.md | 4 +- cmd/analyze.go | 8 +- cmd/build.go | 1 + cmd/ci.go | 1 - cmd/root.go | 6 +- dive/filetree/comparer.go | 7 +- dive/filetree/efficiency.go | 1 - dive/filetree/file_info.go | 6 +- dive/filetree/file_node.go | 3 +- dive/filetree/file_tree.go | 1 - dive/get_image_resolver.go | 6 +- dive/image/docker/archive_resolver.go | 3 +- dive/image/docker/cli.go | 3 +- dive/image/docker/config.go | 1 + dive/image/docker/engine_resolver.go | 4 +- dive/image/docker/image_archive.go | 4 - dive/image/docker/layer.go | 2 +- dive/image/docker/manifest.go | 1 + dive/image/docker/testing.go | 3 +- dive/image/image.go | 1 - dive/image/layer.go | 1 + dive/image/podman/build.go | 1 + dive/image/podman/cli.go | 4 +- dive/image/podman/resolver.go | 2 + dive/image/podman/resolver_unsupported.go | 2 + runtime/ci/evaluator.go | 11 +- runtime/ci/evaluator_test.go | 3 +- runtime/ci/rule.go | 6 +- runtime/export/export.go | 1 + runtime/export/export_test.go | 6 +- runtime/options.go | 1 + runtime/run.go | 7 +- runtime/run_test.go | 6 +- runtime/ui/app.go | 22 +- runtime/ui/controller.go | 6 +- runtime/ui/format/format.go | 21 +- runtime/ui/job_control_other.go | 13 + runtime/ui/job_control_unix.go | 19 + runtime/ui/key/binding.go | 1 + .../layout/compound/layer_details_column.go | 1 + runtime/ui/layout/manager.go | 2 - runtime/ui/layout/manager_test.go | 3 +- runtime/ui/view/cursor.go | 1 + runtime/ui/view/debug.go | 1 + runtime/ui/view/filetree.go | 7 +- runtime/ui/view/filter.go | 1 + runtime/ui/view/image_details.go | 6 +- runtime/ui/view/layer.go | 3 +- runtime/ui/view/layer_details.go | 4 +- runtime/ui/view/status.go | 4 +- runtime/ui/view/views.go | 1 + runtime/ui/viewmodel/filetree.go | 3 +- runtime/ui/viewmodel/filetree_test.go | 5 +- utils/format.go | 3 +- 73 files changed, 999 insertions(+), 511 deletions(-) create mode 100644 .bouncer.yaml delete mode 100644 .circleci/config.yml create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/actions/bootstrap/action.yaml create mode 100755 .github/scripts/ci-check.sh create mode 100755 .github/scripts/coverage.py create mode 100755 .github/scripts/go-mod-tidy-check.sh create mode 100755 .github/scripts/trigger-release.sh delete mode 100644 .github/workflows/pipeline.yml create mode 100644 .github/workflows/release.yaml create mode 100644 .github/workflows/validations.yaml create mode 100644 .golangci.yaml rename .goreleaser.yml => .goreleaser.yaml (65%) delete mode 100755 .scripts/tag.sh delete mode 100755 .scripts/test-coverage.sh create mode 100644 runtime/ui/job_control_other.go create mode 100644 runtime/ui/job_control_unix.go diff --git a/.bouncer.yaml b/.bouncer.yaml new file mode 100644 index 0000000..9ee3121 --- /dev/null +++ b/.bouncer.yaml @@ -0,0 +1,12 @@ +permit: + - BSD.* + - MIT.* + - Apache.* + - MPL.* + - ISC + - WTFPL + +ignore-packages: + # crypto/internal/boring is released under the openSSL license as a part of the Golang Standard Library + - crypto/internal/boring + diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 35375e8..0000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,59 +0,0 @@ -version: 2.1 - -jobs: - run-static-analyses: - parameters: - version: - type: string - working_directory: /home/circleci/app - docker: - - image: cimg/go:<< parameters.version >> - environment: - GO111MODULE: "on" - steps: - - checkout - - restore_cache: - keys: - - golang-<< parameters.version >>-{{ checksum "go.sum" }} - - run: make ci-install-go-tools - - save_cache: - key: golang-<< parameters.version >>-{{ checksum "go.sum" }} - paths: - - "/go/pkg/mod" - - run: - name: run static analysis - command: make ci-static-analysis - - run-tests: - parameters: - version: - type: string - working_directory: /home/circleci/app - docker: - - image: cimg/go:<< parameters.version >> - environment: - GO111MODULE: "on" - steps: - - checkout - - restore_cache: - keys: - - golang-<< parameters.version >>-{{ checksum "go.sum" }} - - run: make ci-install-go-tools - - save_cache: - key: golang-<< parameters.version >>-{{ checksum "go.sum" }} - paths: - - "/go/pkg/mod" - - run: - name: run unit tests - command: make ci-unit-test - - -workflows: - commit: - jobs: - - run-static-analyses: - version: "1.19" - - run-tests: - version: "1.19" - - run-tests: - version: "1.19" diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 0cfb4a1..51d9b59 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,2 +1 @@ github: ['wagoodman'] -custom: ['https://www.paypal.me/wagoodman'] diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..6879ffd --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,20 @@ +--- +name: Bug report +about: Something isn't working as expected +title: '' +labels: bug +assignees: '' + +--- + +**What happened**: + +**What you expected to happen**: + +**How to reproduce it (as minimally and precisely as possible)**: + +**Anything else we need to know?**: + +**Environment**: +- OS version +- Docker version (if applicable) diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..437f1aa --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,15 @@ +--- +name: Feature request +about: Got an idea for a new feature? Let us know! +title: '' +labels: enhancement +assignees: '' + +--- + +**What would you like to be added**: + +**Why is this needed**: + +**Additional context**: + diff --git a/.github/actions/bootstrap/action.yaml b/.github/actions/bootstrap/action.yaml new file mode 100644 index 0000000..df71a17 --- /dev/null +++ b/.github/actions/bootstrap/action.yaml @@ -0,0 +1,76 @@ +name: "Bootstrap" +description: "Bootstrap all tools and dependencies" +inputs: + go-version: + description: "Go version to install" + required: true + default: "1.20.x" + use-go-cache: + description: "Restore go cache" + required: true + default: "true" + cache-key-prefix: + description: "Prefix all cache keys with this value" + required: true + default: "efa04b89c1b1" + build-cache-key-prefix: + description: "Prefix build cache key with this value" + required: true + default: "f8b6d31dea" + bootstrap-apt-packages: + description: "Space delimited list of tools to install via apt" + default: "" + +runs: + using: "composite" + steps: + - uses: actions/setup-go@v3 + with: + go-version: ${{ inputs.go-version }} + + - name: Restore tool cache + id: tool-cache + uses: actions/cache@v3 + with: + path: ${{ github.workspace }}/.tmp + key: ${{ inputs.cache-key-prefix }}-${{ runner.os }}-tool-${{ hashFiles('Makefile') }} + + # note: we need to keep restoring the go mod cache before bootstrapping tools since `go install` is used in + # some installations of project tools. + - name: Restore go module cache + id: go-mod-cache + if: inputs.use-go-cache == 'true' + uses: actions/cache@v3 + with: + path: | + ~/go/pkg/mod + key: ${{ inputs.cache-key-prefix }}-${{ runner.os }}-go-${{ inputs.go-version }}-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ inputs.cache-key-prefix }}-${{ runner.os }}-go-${{ inputs.go-version }}- + + - name: (cache-miss) Bootstrap project tools + shell: bash + if: steps.tool-cache.outputs.cache-hit != 'true' + run: make bootstrap-tools + + - name: Restore go build cache + id: go-cache + if: inputs.use-go-cache == 'true' + uses: actions/cache@v3 + with: + path: | + ~/.cache/go-build + key: ${{ inputs.cache-key-prefix }}-${{ inputs.build-cache-key-prefix }}-${{ runner.os }}-go-${{ inputs.go-version }}-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ inputs.cache-key-prefix }}-${{ inputs.build-cache-key-prefix }}-${{ runner.os }}-go-${{ inputs.go-version }}- + + - name: (cache-miss) Bootstrap go dependencies + shell: bash + if: steps.go-mod-cache.outputs.cache-hit != 'true' && inputs.use-go-cache == 'true' + run: make bootstrap-go + + - name: Install apt packages + if: inputs.bootstrap-apt-packages != '' + shell: bash + run: | + DEBIAN_FRONTEND=noninteractive sudo apt update && sudo -E apt install -y ${{ inputs.bootstrap-apt-packages }} diff --git a/.github/scripts/ci-check.sh b/.github/scripts/ci-check.sh new file mode 100755 index 0000000..0ab83a3 --- /dev/null +++ b/.github/scripts/ci-check.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +red=$(tput setaf 1) +bold=$(tput bold) +normal=$(tput sgr0) + +# assert we are running in CI (or die!) +if [[ -z "$CI" ]]; then + echo "${bold}${red}This step should ONLY be run in CI. Exiting...${normal}" + exit 1 +fi diff --git a/.github/scripts/coverage.py b/.github/scripts/coverage.py new file mode 100755 index 0000000..db14135 --- /dev/null +++ b/.github/scripts/coverage.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 +import subprocess +import sys +import shlex + + +class bcolors: + HEADER = '\033[95m' + OKBLUE = '\033[94m' + OKCYAN = '\033[96m' + OKGREEN = '\033[92m' + WARNING = '\033[93m' + FAIL = '\033[91m' + ENDC = '\033[0m' + BOLD = '\033[1m' + UNDERLINE = '\033[4m' + + +if len(sys.argv) < 3: + print("Usage: coverage.py [threshold] [go-coverage-report]") + sys.exit(1) + + +threshold = float(sys.argv[1]) +report = sys.argv[2] + + +args = shlex.split(f"go tool cover -func {report}") +p = subprocess.run(args, capture_output=True, text=True) + +percent_coverage = float(p.stdout.splitlines()[-1].split()[-1].replace("%", "")) +print(f"{bcolors.BOLD}Coverage: {percent_coverage}%{bcolors.ENDC}") + +if percent_coverage < threshold: + print(f"{bcolors.BOLD}{bcolors.FAIL}Coverage below threshold of {threshold}%{bcolors.ENDC}") + sys.exit(1) diff --git a/.github/scripts/go-mod-tidy-check.sh b/.github/scripts/go-mod-tidy-check.sh new file mode 100755 index 0000000..41bc639 --- /dev/null +++ b/.github/scripts/go-mod-tidy-check.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +set -eu + +ORIGINAL_STATE_DIR=$(mktemp -d "TEMP-original-state-XXXXXXXXX") +TIDY_STATE_DIR=$(mktemp -d "TEMP-tidy-state-XXXXXXXXX") + +trap "cp -v ${ORIGINAL_STATE_DIR}/* ./ && rm -fR ${ORIGINAL_STATE_DIR} ${TIDY_STATE_DIR}" EXIT + +echo "Capturing original state of files..." +cp -v go.mod go.sum "${ORIGINAL_STATE_DIR}" + +echo "Capturing state of go.mod and go.sum after running go mod tidy..." +go mod tidy +cp -v go.mod go.sum "${TIDY_STATE_DIR}" +echo "" + +set +e + +# Detect difference between the git HEAD state and the go mod tidy state +DIFF_MOD=$(diff -u "${ORIGINAL_STATE_DIR}/go.mod" "${TIDY_STATE_DIR}/go.mod") +DIFF_SUM=$(diff -u "${ORIGINAL_STATE_DIR}/go.sum" "${TIDY_STATE_DIR}/go.sum") + +if [[ -n "${DIFF_MOD}" || -n "${DIFF_SUM}" ]]; then + echo "go.mod diff:" + echo "${DIFF_MOD}" + echo "go.sum diff:" + echo "${DIFF_SUM}" + echo "" + printf "FAILED! go.mod and/or go.sum are NOT tidy; please run 'go mod tidy'.\n\n" + exit 1 +fi diff --git a/.github/scripts/trigger-release.sh b/.github/scripts/trigger-release.sh new file mode 100755 index 0000000..c1a5432 --- /dev/null +++ b/.github/scripts/trigger-release.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env bash +set -eu + +bold=$(tput bold) +normal=$(tput sgr0) + +if ! [ -x "$(command -v gh)" ]; then + echo "The GitHub CLI could not be found. To continue follow the instructions at https://github.com/cli/cli#installation" + exit 1 +fi + +gh auth status + +# we need all of the git state to determine the next version. Since tagging is done by +# the release pipeline it is possible to not have all of the tags from previous releases. +git fetch --tags + +# populates the CHANGELOG.md and VERSION files +echo "${bold}Generating changelog...${normal}" +make changelog 2> /dev/null + +NEXT_VERSION=$(cat VERSION) + +if [[ "$NEXT_VERSION" == "" || "${NEXT_VERSION}" == "(Unreleased)" ]]; then + echo "Could not determine the next version to release. Exiting..." + exit 1 +fi + +while true; do + read -p "${bold}Do you want to trigger a release for version '${NEXT_VERSION}'?${normal} [y/n] " yn + case $yn in + [Yy]* ) echo; break;; + [Nn]* ) echo; echo "Cancelling release..."; exit;; + * ) echo "Please answer yes or no.";; + esac +done + +echo "${bold}Kicking off release for ${NEXT_VERSION}${normal}..." +echo +gh workflow run release.yaml -f version=${NEXT_VERSION} + +echo +echo "${bold}Waiting for release to start...${normal}" +sleep 10 + +set +e + +echo "${bold}Head to the release workflow to monitor the release:${normal} $(gh run list --workflow=release.yaml --limit=1 --json url --jq '.[].url')" +id=$(gh run list --workflow=release.yaml --limit=1 --json databaseId --jq '.[].databaseId') +gh run watch $id --exit-status || (echo ; echo "${bold}Logs of failed step:${normal}" && GH_PAGER="" gh run view $id --log-failed) diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml deleted file mode 100644 index 7778ef9..0000000 --- a/.github/workflows/pipeline.yml +++ /dev/null @@ -1,180 +0,0 @@ -name: 'app-pipeline' -on: - push: - pull_request: - types: [ opened, reopened ] -env: - DOCKER_CLI_VERSION: "19.03.1" -jobs: - unit-test: - strategy: - matrix: - go-version: [1.19.x] - # todo: support windows - platform: [ubuntu-latest, macos-latest] - # platform: [ubuntu-latest, macos-latest, windows-latest] - runs-on: ${{ matrix.platform }} - steps: - - - uses: actions/setup-go@v1 - with: - go-version: ${{ matrix.go-version }} - - - uses: actions/checkout@v1 - - - name: Cache go dependencies - id: unit-cache-go-dependencies - uses: actions/cache@v1 - with: - path: ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ matrix.go-version }}-${{ hashFiles('**/go.sum') }} - restore-keys: ${{ runner.os }}-go-${{ matrix.go-version }}- - - - name: Install go dependencies - if: steps.unit-cache-go-dependencies.outputs.cache-hit != 'true' - run: go get ./... - - - name: Test - run: make ci-unit-test - - build-artifacts: - runs-on: ubuntu-latest - steps: - - uses: actions/setup-go@v1 - with: - go-version: '1.19.x' - - - uses: actions/checkout@v1 - - - name: Install tooling - run: | - make ci-install-go-tools - make ci-install-ci-tools - - - name: Cache go dependencies - id: package-cache-go-dependencies - uses: actions/cache@v1 - with: - path: ~/go/pkg/mod - key: ${{ runner.os }}-go-prod-${{ hashFiles('**/go.sum') }} - restore-keys: ${{ runner.os }}-go-prod- - - - name: Install dependencies - if: steps.package-cache-go-dependencies.outputs.cache-hit != 'true' - run: go get ./... - - - name: Linting, formatting, and other static code analyses - run: make ci-static-analysis - - - name: Build snapshot artifacts - run: make ci-build-snapshot-packages - - - run: docker images wagoodman/dive - - # todo: compare against known json output in shared volume - - name: Test production image - run: make ci-test-production-image - - - uses: actions/upload-artifact@master - with: - name: artifacts - path: dist - - - test-linux-artifacts: - needs: [ build-artifacts ] - runs-on: ubuntu-latest - steps: - - - uses: actions/checkout@master - - - uses: actions/download-artifact@master - with: - name: artifacts - path: dist - - - name: Test linux run - run: make ci-test-linux-run - - - name: Test DEB package installation - run: make ci-test-deb-package-install - - - name: Test RPM package installation - run: make ci-test-rpm-package-install - - - test-mac-artifacts: - needs: [ build-artifacts ] - runs-on: macos-latest - steps: - - - uses: actions/checkout@master - - - uses: actions/download-artifact@master - with: - name: artifacts - path: dist - - - name: Test darwin run - run: make ci-test-mac-run - - - test-windows-artifacts: - needs: [ build-artifacts ] - runs-on: windows-latest - steps: - - - uses: actions/checkout@master - - - uses: actions/download-artifact@master - with: - name: artifacts - path: dist - - - name: Test windows run - run: make ci-test-windows-run - - - release: - needs: [ unit-test, build-artifacts, test-linux-artifacts, test-mac-artifacts, test-windows-artifacts ] - runs-on: ubuntu-latest - if: startsWith(github.ref, 'refs/tags/v') - steps: - - - uses: actions/setup-go@v1 - with: - go-version: '1.19.x' - - - uses: actions/checkout@v1 - - - name: Install tooling - run: make ci-install-ci-tools - - - name: Cache go dependencies - id: release-cache-go-dependencies - uses: actions/cache@v1 - with: - path: ~/go/pkg/mod - key: ${{ runner.os }}-go-prod-${{ hashFiles('**/go.sum') }} - restore-keys: ${{ runner.os }}-go-prod- - - - name: Install dependencies - if: steps.release-cache-go-dependencies.outputs.cache-hit != 'true' - run: go get ./... - - - name: Docker login - run: make ci-docker-login - env: - DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} - DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} - - - name: Publish GitHub release - run: make ci-release - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Docker logout - run: make ci-docker-logout - - - name: Smoke test published image - run: make ci-test-production-image diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000..be3310f --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,114 @@ +name: "Release" +on: + workflow_dispatch: + inputs: + version: + description: tag the latest commit on main with the given version (prefixed with v) + required: true + +jobs: + quality-gate: + environment: release + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Check if tag already exists + # note: this will fail if the tag already exists + run: | + [[ "${{ github.event.inputs.version }}" == v* ]] || (echo "version '${{ github.event.inputs.version }}' does not have a 'v' prefix" && exit 1) + git tag ${{ github.event.inputs.version }} + + - name: Check static analysis results + uses: fountainhead/action-wait-for-check@v1.1.0 + id: static-analysis + with: + token: ${{ secrets.GITHUB_TOKEN }} + # This check name is defined as the github action job name (in .github/workflows/validations.yaml) + checkName: "Static analysis" + ref: ${{ github.event.pull_request.head.sha || github.sha }} + + - name: Check unit test results + uses: fountainhead/action-wait-for-check@v1.1.0 + id: unit + with: + token: ${{ secrets.GITHUB_TOKEN }} + # This check name is defined as the github action job name (in .github/workflows/validations.yaml) + checkName: "Unit tests" + ref: ${{ github.event.pull_request.head.sha || github.sha }} + + - name: Check acceptance test results (linux) + uses: fountainhead/action-wait-for-check@v1.1.0 + id: acceptance-linux + with: + token: ${{ secrets.GITHUB_TOKEN }} + # This check name is defined as the github action job name (in .github/workflows/validations.yaml) + checkName: "Acceptance tests (Linux)" + ref: ${{ github.event.pull_request.head.sha || github.sha }} + + - name: Check acceptance test results (mac) + uses: fountainhead/action-wait-for-check@v1.1.0 + id: acceptance-mac + with: + token: ${{ secrets.GITHUB_TOKEN }} + # This check name is defined as the github action job name (in .github/workflows/validations.yaml) + checkName: "Acceptance tests (Mac)" + ref: ${{ github.event.pull_request.head.sha || github.sha }} + + - name: Check acceptance test results (windows) + uses: fountainhead/action-wait-for-check@v1.1.0 + id: acceptance-windows + with: + token: ${{ secrets.GITHUB_TOKEN }} + # This check name is defined as the github action job name (in .github/workflows/validations.yaml) + 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: | + echo "Static Analysis Status: ${{ steps.static-analysis.conclusion }}" + echo "Unit Test Status: ${{ steps.unit.outputs.conclusion }}" + echo "Acceptance Test (Linux) Status: ${{ steps.acceptance-linux.outputs.conclusion }}" + echo "Acceptance Test (Mac) Status: ${{ steps.acceptance-mac.outputs.conclusion }}" + echo "Acceptance Test (Windows) Status: ${{ steps.acceptance-windows.outputs.conclusion }}" + + false + +# TODO: uncomment this when we have a release process tested and ready to go +# release: +# needs: [quality-gate] +# runs-on: ubuntu-latest +# permissions: +# # for tagging +# contents: write +# steps: +# +# - uses: actions/checkout@v3 +# with: +# fetch-depth: 0 +# +# - name: Bootstrap environment +# uses: ./.github/actions/bootstrap +# +# - name: Tag release +# run: | +# git tag ${{ github.event.inputs.version }} +# git push origin --tags +# env: +# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} +# +# - name: Login to Docker Hub +# uses: docker/login-action@v2 +# with: +# username: ${{ secrets.DOCKER_USERNAME }} +# password: ${{ secrets.DOCKER_PASSWORD }} +# +# - name: Build & publish release artifacts +# run: make ci-release +# env: +# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} +# +# - name: Smoke test published image +# run: make ci-test-docker-image diff --git a/.github/workflows/validations.yaml b/.github/workflows/validations.yaml new file mode 100644 index 0000000..6525705 --- /dev/null +++ b/.github/workflows/validations.yaml @@ -0,0 +1,135 @@ +name: "Validations" +on: + workflow_dispatch: + push: + branches: + - main + 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 + + - name: Bootstrap environment + uses: ./.github/actions/bootstrap + + - 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 + + runs-on: ${{ matrix.platform }} + steps: + + - uses: actions/checkout@v3 + + - name: Bootstrap environment + uses: ./.github/actions/bootstrap + + - name: Run unit tests + run: make unit + + + Build-Snapshot-Artifacts: + name: "Build snapshot artifacts" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Bootstrap environment + uses: ./.github/actions/bootstrap + + - name: Build snapshot artifacts + run: make snapshot + + - run: docker images wagoodman/dive + + # todo: compare against known json output in shared volume + - name: Test production image + run: make ci-test-docker-image + + # 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 + with: + path: snapshot + key: snapshot-build-${{ github.run_id }} + + # ... however the cache trick doesn't work on windows :( + - uses: actions/upload-artifact@v3 + with: + name: windows-artifacts + path: snapshot/dive_windows_amd64_v1/dive.exe + + + Acceptance-Linux: + name: "Acceptance tests (Linux)" + needs: [ Build-Snapshot-Artifacts ] + runs-on: ubuntu-latest + steps: + + - uses: actions/checkout@master + + - name: Download snapshot build + uses: actions/cache/restore@v3 + with: + path: snapshot + key: snapshot-build-${{ github.run_id }} + + - name: Test linux run + run: make ci-test-linux-run + + - name: Test DEB package installation + run: make ci-test-deb-package-install + + - name: Test RPM package installation + run: make ci-test-rpm-package-install + + + Acceptance-Mac: + name: "Acceptance tests (Mac)" + needs: [ Build-Snapshot-Artifacts ] + runs-on: macos-latest + steps: + + - uses: actions/checkout@master + + - name: Download snapshot build + uses: actions/cache/restore@v3 + with: + path: snapshot + key: snapshot-build-${{ github.run_id }} + + - name: Test darwin run + run: make ci-test-mac-run + + + Acceptance-Windows: + name: "Acceptance tests (Windows)" + needs: [ Build-Snapshot-Artifacts ] + runs-on: windows-latest + steps: + + - uses: actions/checkout@master + + - uses: actions/download-artifact@v3 + with: + name: windows-artifacts + + - name: Test windows run + run: make ci-test-windows-run diff --git a/.gitignore b/.gitignore index ee32cf7..f5868db 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,25 @@ +# misc +/.image +*.log +CHANGELOG.md +VERSION + +# IDEs /.idea +/.vscode + +# tooling /bin /.tool-versions +/.tmp + +# builds +/dist +/snapshot + +# testing +.cover +coverage.txt # Binaries for programs and plugins *.exe @@ -18,8 +37,3 @@ /build /_vendor* /vendor -/.image -*.log -/dist -.cover -coverage.txt diff --git a/.golangci.yaml b/.golangci.yaml new file mode 100644 index 0000000..ed76ead --- /dev/null +++ b/.golangci.yaml @@ -0,0 +1,74 @@ +# TODO: enable this when we have coverage on docstring comments +#issues: +# # The list of ids of default excludes to include or disable. +# include: +# - EXC0002 # disable excluding of issues about comments from golint + +linters-settings: + funlen: + # Checks the number of lines in a function. + # If lower than 0, disable the check. + # Default: 60 + # TODO: drop this down over time... + lines: 110 + # Checks the number of statements in a function. + # If lower than 0, disable the check. + # Default: 40 + statements: 60 + +# TODO: use the default linters for now, but include these over time +#linters: +# # inverted configuration with `enable-all` and `disable` is not scalable during updates of golangci-lint +# disable-all: true +# enable: +# - asciicheck +# - bodyclose +# - depguard +# - dogsled +# - dupl +# - errcheck +# - exportloopref +# - funlen +# - gocognit +# - goconst +# - gocritic +# - gocyclo +# - gofmt +# - goimports +# - goprintffuncname +# - gosec +# - gosimple +# - govet +# - ineffassign +# - misspell +# - nakedret +# - nolintlint +# - revive +# - staticcheck +# - stylecheck +# - typecheck +# - unconvert +# - unparam +# - unused +# - whitespace + +# do not enable... +# - gochecknoglobals +# - gochecknoinits # this is too aggressive +# - godot +# - godox +# - goerr113 +# - golint # deprecated +# - gomnd # this is too aggressive +# - interfacer # this is a good idea, but is no longer supported and is prone to false positives +# - lll # without a way to specify per-line exception cases, this is not usable +# - maligned # this is an excellent linter, but tricky to optimize and we are not sensitive to memory layout optimizations +# - nestif +# - 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) +# - 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 diff --git a/.goreleaser.yml b/.goreleaser.yaml similarity index 65% rename from .goreleaser.yml rename to .goreleaser.yaml index b83bbef..3de4c14 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yaml @@ -1,5 +1,10 @@ release: - prerelease: false + # 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. + prerelease: auto + + # If set to true, will not auto-publish the release. This is done to allow us to review the changelog before publishing. + draft: false builds: - binary: dive @@ -11,6 +16,7 @@ builds: - linux goarch: - amd64 + - arm64 ldflags: -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.buildTime={{.Date}}`. brews: @@ -18,7 +24,7 @@ brews: owner: wagoodman name: homebrew-dive homepage: "https://github.com/wagoodman/dive/" - description: "A tool for exploring each layer in a docker image" + description: "A tool for exploring layers in a docker image" archives: - format: tar.gz @@ -30,7 +36,7 @@ nfpms: - license: MIT maintainer: Alex Goodman homepage: https://github.com/wagoodman/dive/ - description: "A tool for exploring each layer in a docker image" + description: "A tool for exploring layers in a docker image" formats: - rpm - deb diff --git a/.scripts/tag.sh b/.scripts/tag.sh deleted file mode 100755 index 2e28f9f..0000000 --- a/.scripts/tag.sh +++ /dev/null @@ -1,37 +0,0 @@ -#!/bin/bash -set -u - -BOLD=$(tput bold) -NORMAL=$(tput sgr0) - -echo "${BOLD}Tagging${NORMAL}" - -#get highest tag number -VERSION=`git describe --abbrev=0 --tags` - -#replace . with space so can split into an array -VERSION_BITS=(${VERSION//./ }) - -#get number parts and increase last one by 1 -VNUM1=${VERSION_BITS[0]} -VNUM2=${VERSION_BITS[1]} -VNUM3=${VERSION_BITS[2]} -VNUM3=$((VNUM3+1)) - -#create new tag -NEW_TAG="$VNUM1.$VNUM2.$VNUM3" - -echo "Updating $VERSION to $NEW_TAG" - -#get current hash and see if it already has a tag -GIT_COMMIT=`git rev-parse HEAD` -NEEDS_TAG=`git describe --contains $GIT_COMMIT` - -#only tag if no tag already (would be better if the git describe command above could have a silent option) -if [ -z "$NEEDS_TAG" ]; then - echo "Tagged with $NEW_TAG (Ignoring fatal:cannot describe - this means commit is untagged) " - git tag $NEW_TAG - git push --tags -else - echo "Already a tag on this commit" -fi \ No newline at end of file diff --git a/.scripts/test-coverage.sh b/.scripts/test-coverage.sh deleted file mode 100755 index 36db3a7..0000000 --- a/.scripts/test-coverage.sh +++ /dev/null @@ -1,55 +0,0 @@ -#!/bin/sh -# Generate test coverage statistics for Go packages. -# -# Works around the fact that `go test -coverprofile` currently does not work -# with multiple packages, see https://code.google.com/p/go/issues/detail?id=6909 -# -# Usage: script/coverage [--html|--coveralls] -# -# --html Additionally create HTML report and open it in browser -# --coveralls Push coverage statistics to coveralls.io -# -# Source: https://github.com/mlafeldt/chef-runner/blob/v0.7.0/script/coverage - -set -e - -workdir=.cover -profile="$workdir/cover.out" -mode=count - -generate_cover_data() { - rm -rf "$workdir" - mkdir "$workdir" - - for pkg in "$@"; do - f="$workdir/$(echo $pkg | tr / -).cover" - go test -v -covermode="$mode" -coverprofile="$f" "$pkg" - done - - echo "mode: $mode" >"$profile" - grep -h -v "^mode:" "$workdir"/*.cover >>"$profile" -} - -show_cover_report() { - go tool cover -${1}="$profile" -} - -push_to_coveralls() { - echo "Pushing coverage statistics to coveralls.io" - goveralls -coverprofile="$profile" -} - -generate_cover_data $(go list ./...) -case "$1" in -"") - show_cover_report func - ;; ---html) - show_cover_report html - ;; ---coveralls) - push_to_coveralls - ;; -*) - echo >&2 "error: invalid option: $1"; exit 1 ;; -esac diff --git a/Makefile b/Makefile index b5cb57f..8759e2a 100644 --- a/Makefile +++ b/Makefile @@ -1,65 +1,201 @@ BIN = dive -BUILD_DIR = ./dist/dive_linux_amd64 -BUILD_PATH = $(BUILD_DIR)/$(BIN) +TEMP_DIR = ./.tmp PWD := ${CURDIR} PRODUCTION_REGISTRY = docker.io SHELL = /bin/bash -o pipefail TEST_IMAGE = busybox:latest -all: gofmt clean build +# Tool versions ################################# +GOLANG_CI_VERSION = v1.52.2 +GOBOUNCER_VERSION = v0.4.0 +GORELEASER_VERSION = v1.19.1 +GOSIMPORTS_VERSION = v0.3.8 +CHRONICLE_VERSION = v0.6.0 +GLOW_VERSION = v1.5.0 +DOCKER_CLI_VERSION = 23.0.6 -## For CI +# 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 +CHRONICLE_CMD = $(TEMP_DIR)/chronicle +GLOW_CMD = $(TEMP_DIR)/glow -ci-unit-test: - go test -cover -v -race ./... +# Formatting variables ################################# +BOLD := $(shell tput -T linux bold) +PURPLE := $(shell tput -T linux setaf 5) +GREEN := $(shell tput -T linux setaf 2) +CYAN := $(shell tput -T linux setaf 6) +RED := $(shell tput -T linux setaf 1) +RESET := $(shell tput -T linux sgr0) +TITLE := $(BOLD)$(PURPLE) +SUCCESS := $(BOLD)$(GREEN) -ci-static-analysis: - grep -R 'const allowTestDataCapture = false' runtime/ui/viewmodel - go vet ./... - gofmt -s -l . 2>&1 | grep -vE '^\.git/' | grep -vE '^\.cache/' - golangci-lint run +# Test variables ################################# +# the quality gate lower threshold for unit test total % coverage (by function statements) +COVERAGE_THRESHOLD := 55 -ci-install-go-tools: - mkdir -p ${HOME}/.local/bin - curl -sfL https://goreleaser.com/static/run > ${HOME}/.local/bin/goreleaser - chmod +x ${HOME}/.local/bin/goreleaser +## Build variables ################################# +DIST_DIR = dist +SNAPSHOT_DIR = snapshot +OS=$(shell uname | tr '[:upper:]' '[:lower:]') +SNAPSHOT_BIN=$(realpath $(shell pwd)/$(SNAPSHOT_DIR)/$(OS)-build_$(OS)_amd64_v1/$(BIN)) +CHANGELOG := CHANGELOG.md +VERSION=$(shell git describe --dirty --always --tags) -ci-install-ci-tools: - curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b ${HOME}/.local/bin/ +ifeq "$(strip $(VERSION))" "" + override VERSION = $(shell git describe --always --tags --dirty) +endif -ci-docker-login: - echo '${DOCKER_PASSWORD}' | docker login -u '${DOCKER_USERNAME}' --password-stdin '${PRODUCTION_REGISTRY}' +## Variable assertions -ci-docker-logout: - docker logout '${PRODUCTION_REGISTRY}' +ifndef TEMP_DIR + $(error TEMP_DIR is not set) +endif -ci-publish-release: - goreleaser --clean +ifndef DIST_DIR + $(error DIST_DIR is not set) +endif -ci-build-snapshot-packages: - goreleaser \ - --snapshot \ - --skip-publish \ - --clean +ifndef SNAPSHOT_DIR + $(error SNAPSHOT_DIR is not set) +endif -ci-release: - goreleaser release --clean +define title + @printf '$(TITLE)$(1)$(RESET)\n' +endef + + +.PHONY: all +all: clean static-analysis test ## Run all static analysis and tests + @printf '$(SUCCESS)All checks pass!$(RESET)\n' + +.PHONY: test +test: unit ## Run all tests (currently unit and cli tests) + +$(TEMP_DIR): + mkdir -p $(TEMP_DIR) + + +## Bootstrapping targets ################################# + +.PHONY: bootstrap-tools +bootstrap-tools: $(TEMP_DIR) + $(call title,Bootstrapping tools) + 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/rinchsan/gosimports/cmd/gosimports@$(GOSIMPORTS_VERSION) + GOBIN="$(realpath $(TEMP_DIR))" go install github.com/charmbracelet/glow@$(GLOW_VERSION) + +.PHONY: bootstrap-go +bootstrap-go: + $(call title,Bootstrapping go dependencies) + go mod download + +.PHONY: bootstrap +bootstrap: bootstrap-go bootstrap-tools ## Download and install all go dependencies (+ prep tooling in the ./tmp dir) + + +## Development targets ################################### + +#run: build +# $(BUILD_PATH) build -t dive-example:latest -f .data/Dockerfile.example . +# +#run-large: build +# $(BUILD_PATH) amir20/clashleaders:latest +# +#run-podman: build +# podman build -t dive-example:latest -f .data/Dockerfile.example . +# $(BUILD_PATH) localhost/dive-example:latest --engine podman +# +#run-podman-large: build +# $(BUILD_PATH) docker.io/amir20/clashleaders:latest --engine podman +# +#run-ci: build +# CI=true $(BUILD_PATH) dive-example:latest --ci-config .data/.dive-ci +# +#dev: +# docker run -ti --rm -v $(PWD):/app -w /app -v dive-pkg:/go/pkg/ golang:1.13 bash +# +#build: gofmt +# go build -o $(BUILD_PATH) + +.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!' + + +## Static analysis targets ################################# + +.PHONY: static-analysis +static-analysis: lint check-go-mod-tidy check-licenses + +.PHONY: lint +lint: ## Run gofmt + golangci lint checks + $(call title,Running linters) + # ensure there are no go fmt differences + @printf "files with gofmt issues: [$(shell gofmt -l -s .)]\n" + @test -z "$(shell gofmt -l -s .)" + + # run all golangci-lint rules + $(LINT_CMD) + @[ -z "$(shell $(GOIMPORTS_CMD) -d .)" ] || (echo "goimports needs to be fixed" && false) + + # go tooling does not play well with certain filename characters, ensure the common cases don't result in future "go get" failures + $(eval MALFORMED_FILENAMES := $(shell find . | grep -e ':')) + @bash -c "[[ '$(MALFORMED_FILENAMES)' == '' ]] || (printf '\nfound unsupported filename characters:\n$(MALFORMED_FILENAMES)\n\n' && false)" + +.PHONY: format +format: ## Auto-format all source code + $(call title,Running formatters) + gofmt -w -s . + $(GOIMPORTS_CMD) -w . + go mod tidy + +.PHONY: lint-fix +lint-fix: format ## Auto-format all source code + run golangci lint fixers + $(call title,Running lint fixers) + $(LINT_CMD) --fix + +.PHONY: check-licenses +check-licenses: + $(TEMP_DIR)/bouncer check ./... + +check-go-mod-tidy: + @ .github/scripts/go-mod-tidy-check.sh && echo "go.mod and go.sum are tidy!" + + +## Testing targets ################################# + +.PHONY: unit +unit: $(TEMP_DIR) ## Run unit tests (with coverage) + $(call title,Running unit tests) + go test -race -coverprofile $(TEMP_DIR)/unit-coverage-details.txt ./... + @.github/scripts/coverage.py $(COVERAGE_THRESHOLD) $(TEMP_DIR)/unit-coverage-details.txt + + +## Acceptance testing targets (CI only) ################################# # todo: add --pull=never when supported by host box -ci-test-production-image: +.PHONY: ci-test-docker-image +ci-test-docker-image: docker run \ --rm \ -t \ - -v //var/run/docker.sock://var/run/docker.sock \ + -v /var/run/docker.sock:/var/run/docker.sock \ '${PRODUCTION_REGISTRY}/wagoodman/dive:latest' \ '${TEST_IMAGE}' \ --ci +.PHONY: ci-test-deb-package-install ci-test-deb-package-install: docker run \ - -v //var/run/docker.sock://var/run/docker.sock \ - -v /${PWD}://src \ - -w //src \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -v /${PWD}:/src \ + -w /src \ ubuntu:latest \ /bin/bash -x -c "\ apt update && \ @@ -68,78 +204,120 @@ ci-test-deb-package-install: tar -vxzf - docker/docker --strip-component=1 && \ mv docker /usr/local/bin/ &&\ docker version && \ - apt install ./dist/dive_*_linux_amd64.deb -y && \ + apt install ./snapshot/dive_*_linux_amd64.deb -y && \ dive --version && \ dive '${TEST_IMAGE}' --ci \ " +.PHONY: ci-test-deb-package-install ci-test-rpm-package-install: docker run \ - -v //var/run/docker.sock://var/run/docker.sock \ - -v /${PWD}://src \ - -w //src \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -v /${PWD}:/src \ + -w /src \ fedora:latest \ /bin/bash -x -c "\ curl -L 'https://download.docker.com/linux/static/stable/x86_64/docker-${DOCKER_CLI_VERSION}.tgz' | \ tar -vxzf - docker/docker --strip-component=1 && \ mv docker /usr/local/bin/ &&\ docker version && \ - dnf install ./dist/dive_*_linux_amd64.rpm -y && \ + dnf install ./snapshot/dive_*_linux_amd64.rpm -y && \ dive --version && \ dive '${TEST_IMAGE}' --ci \ " +.PHONY: ci-test-linux-run ci-test-linux-run: - ls -la ./dist - ls -la ./dist/dive_linux_amd64_v1 - chmod 755 ./dist/dive_linux_amd64_v1/dive && \ - ./dist/dive_linux_amd64_v1/dive '${TEST_IMAGE}' --ci && \ - ./dist/dive_linux_amd64_v1/dive --source docker-archive .data/test-kaniko-image.tar --ci --ci-config .data/.dive-ci + 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 # 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 ci-test-mac-run: - chmod 755 ./dist/dive_darwin_amd64_v1/dive && \ - ./dist/dive_darwin_amd64_v1/dive --source docker-archive .data/test-docker-image.tar --ci --ci-config .data/.dive-ci + chmod 755 $(SNAPSHOT_DIR)/dive_darwin_amd64_v1/dive && \ + $(SNAPSHOT_DIR)/dive_darwin_amd64_v1/dive --source docker-archive .data/test-docker-image.tar --ci --ci-config .data/.dive-ci # 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-windows-run ci-test-windows-run: - ./dist/dive_windows_amd64_v1/dive --source docker-archive .data/test-docker-image.tar --ci --ci-config .data/.dive-ci + dive.exe --source docker-archive .data/test-docker-image.tar --ci --ci-config .data/.dive-ci +## Build-related targets ################################# -## For development +.PHONY: build +build: $(SNAPSHOT_DIR) ## Build release snapshot binaries and packages -run: build - $(BUILD_PATH) build -t dive-example:latest -f .data/Dockerfile.example . +$(SNAPSHOT_DIR): ## Build snapshot release binaries and packages + $(call title,Building snapshot artifacts) -run-large: build - $(BUILD_PATH) amir20/clashleaders:latest + @# create a config with the dist dir overridden + @echo "dist: $(SNAPSHOT_DIR)" > $(TEMP_DIR)/goreleaser.yaml + @cat .goreleaser.yaml >> $(TEMP_DIR)/goreleaser.yaml -run-podman: build - podman build -t dive-example:latest -f .data/Dockerfile.example . - $(BUILD_PATH) localhost/dive-example:latest --engine podman + @# build release snapshots + @bash -c "\ + VERSION=$(VERSION:v%=%) \ + $(SNAPSHOT_CMD) --config $(TEMP_DIR)/goreleaser.yaml \ + " -run-podman-large: build - $(BUILD_PATH) docker.io/amir20/clashleaders:latest --engine podman +.PHONY: cli +cli: $(SNAPSHOT_DIR) ## Run CLI tests + chmod 755 "$(SNAPSHOT_BIN)" + $(SNAPSHOT_BIN) version + go test -count=1 -timeout=15m -v ./test/cli -run-ci: build - CI=true $(BUILD_PATH) dive-example:latest --ci-config .data/.dive-ci +.PHONY: changelog +changelog: clean-changelog ## Generate and show the changelog for the current unreleased version + $(CHRONICLE_CMD) -vvv -n --version-file VERSION > $(CHANGELOG) + @$(GLOW_CMD) $(CHANGELOG) -build: gofmt - go build -o $(BUILD_PATH) +$(CHANGELOG): + $(CHRONICLE_CMD) -vvv > $(CHANGELOG) -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!' +.PHONY: release +release: ## Cut a new release + @.github/scripts/trigger-release.sh -test: gofmt - ./.scripts/test-coverage.sh +.PHONY: release +ci-release: ci-check clean-dist $(CHANGELOG) + $(call title,Publishing release artifacts) -dev: - docker run -ti --rm -v $(PWD):/app -w /app -v dive-pkg:/go/pkg/ golang:1.13 bash + # create a config with the dist dir overridden + echo "dist: $(DIST_DIR)" > $(TEMP_DIR)/goreleaser.yaml + cat .goreleaser.yaml >> $(TEMP_DIR)/goreleaser.yaml -clean: - rm -rf dist - go clean + bash -c "$(RELEASE_CMD) --release-notes <(cat CHANGELOG.md) --config $(TEMP_DIR)/goreleaser.yaml" + +.PHONY: ci-check +ci-check: + @.github/scripts/ci-check.sh + + +## Cleanup targets ################################# + +.PHONY: clean +clean: clean-dist clean-snapshot ## Remove previous builds, result reports, and test cache + +.PHONY: clean-snapshot +clean-snapshot: + rm -rf $(SNAPSHOT_DIR) $(TEMP_DIR)/goreleaser.yaml + +.PHONY: clean-dist +clean-dist: clean-changelog + rm -rf $(DIST_DIR) $(TEMP_DIR)/goreleaser.yaml + +.PHONY: clean-changelog +clean-changelog: + rm -f $(CHANGELOG) VERSION + + +## Halp! ################################# + +.PHONY: help +help: + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "$(BOLD)$(CYAN)%-25s$(RESET)%s\n", $$1, $$2}' -gofmt: - go fmt -x ./... diff --git a/README.md b/README.md index 3027500..cc2b8f0 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # dive +[![GitHub release](https://img.shields.io/github/release/wagoodman/dive.svg)](https://github.com/wagoodman/dive/releases/latest) +[![Validations](https://github.com/wagoodman/dive/actions/workflows/validations.yaml/badge.svg)](https://github.com/wagoodman/dive/actions/workflows/validations.yaml) [![Go Report Card](https://goreportcard.com/badge/github.com/wagoodman/dive)](https://goreportcard.com/report/github.com/wagoodman/dive) -[![Pipeline Status](https://circleci.com/gh/wagoodman/dive.svg?style=svg)](https://circleci.com/gh/wagoodman/dive) +[![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.** diff --git a/cmd/analyze.go b/cmd/analyze.go index 49883d6..31a56f8 100644 --- a/cmd/analyze.go +++ b/cmd/analyze.go @@ -2,19 +2,19 @@ package cmd import ( "fmt" - "github.com/sirupsen/logrus" - "github.com/spf13/viper" - "github.com/wagoodman/dive/dive" "os" + "github.com/sirupsen/logrus" "github.com/spf13/cobra" + "github.com/spf13/viper" + + "github.com/wagoodman/dive/dive" "github.com/wagoodman/dive/runtime" ) // doAnalyzeCmd takes a docker image tag, digest, or id and displays the // image analysis to the screen func doAnalyzeCmd(cmd *cobra.Command, args []string) { - if len(args) == 0 { printVersionFlag, err := cmd.PersistentFlags().GetBool("version") if err == nil && printVersionFlag { diff --git a/cmd/build.go b/cmd/build.go index 9c65fab..84b029e 100644 --- a/cmd/build.go +++ b/cmd/build.go @@ -3,6 +3,7 @@ package cmd import ( "github.com/spf13/cobra" "github.com/spf13/viper" + "github.com/wagoodman/dive/dive" "github.com/wagoodman/dive/runtime" ) diff --git a/cmd/ci.go b/cmd/ci.go index 07938f6..bf87a04 100644 --- a/cmd/ci.go +++ b/cmd/ci.go @@ -10,7 +10,6 @@ import ( ) func configureCi() (bool, *viper.Viper, error) { - isCiFromEnv, _ := strconv.ParseBool(os.Getenv("CI")) isCi = isCi || isCiFromEnv diff --git a/cmd/root.go b/cmd/root.go index 5c90429..27412d9 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -7,13 +7,13 @@ import ( "path" "strings" - "github.com/wagoodman/dive/dive" - "github.com/wagoodman/dive/dive/filetree" - "github.com/mitchellh/go-homedir" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" "github.com/spf13/viper" + + "github.com/wagoodman/dive/dive" + "github.com/wagoodman/dive/dive/filetree" ) var cfgFile string diff --git a/dive/filetree/comparer.go b/dive/filetree/comparer.go index 1df52a7..3e0ea25 100644 --- a/dive/filetree/comparer.go +++ b/dive/filetree/comparer.go @@ -2,6 +2,7 @@ package filetree import ( "fmt" + "github.com/sirupsen/logrus" ) @@ -52,8 +53,8 @@ func (cmp *Comparer) GetPathErrors(key TreeIndexKey) ([]PathError, error) { } func (cmp *Comparer) GetTree(key TreeIndexKey) (*FileTree, error) { - //func (cmp *Comparer) GetTree(bottomTreeStart, bottomTreeStop, topTreeStart, topTreeStop int) (*FileTree, []PathError, error) { - //key := TreeIndexKey{bottomTreeStart, bottomTreeStop, topTreeStart, topTreeStop} + // func (cmp *Comparer) GetTree(bottomTreeStart, bottomTreeStop, topTreeStart, topTreeStop int) (*FileTree, []PathError, error) { + // key := TreeIndexKey{bottomTreeStart, bottomTreeStop, topTreeStart, topTreeStop} if value, exists := cmp.trees[key]; exists { return value, nil @@ -114,7 +115,6 @@ func (cmp *Comparer) NaturalIndexes() <-chan TreeIndexKey { } }() return indexes - } // case 2: aggregated compare (bottom tree is ENTIRELY fixed, top tree SIZE changes) @@ -146,7 +146,6 @@ func (cmp *Comparer) AggregatedIndexes() <-chan TreeIndexKey { } }() return indexes - } func (cmp *Comparer) BuildCache() (errors []error) { diff --git a/dive/filetree/efficiency.go b/dive/filetree/efficiency.go index cdb6b4f..f45d281 100644 --- a/dive/filetree/efficiency.go +++ b/dive/filetree/efficiency.go @@ -85,7 +85,6 @@ func Efficiency(trees []*FileTree) (float64, EfficiencySlice) { return err } } - } else { sizeBytes = node.Data.FileInfo.Size } diff --git a/dive/filetree/file_info.go b/dive/filetree/file_info.go index 788ac3c..bc5f302 100644 --- a/dive/filetree/file_info.go +++ b/dive/filetree/file_info.go @@ -2,10 +2,11 @@ package filetree import ( "archive/tar" - "github.com/cespare/xxhash" - "github.com/sirupsen/logrus" "io" "os" + + "github.com/cespare/xxhash" + "github.com/sirupsen/logrus" ) // FileInfo contains tar metadata for a specific FileNode @@ -56,7 +57,6 @@ func NewFileInfo(realPath, path string, info os.FileInfo) FileInfo { if err != nil { logrus.Panic("unable to read link:", realPath, err) } - } else if info.IsDir() { fileType = tar.TypeDir } else { diff --git a/dive/filetree/file_node.go b/dive/filetree/file_node.go index e003119..f99bc32 100644 --- a/dive/filetree/file_node.go +++ b/dive/filetree/file_node.go @@ -6,11 +6,10 @@ import ( "sort" "strings" - "github.com/sirupsen/logrus" - "github.com/dustin/go-humanize" "github.com/fatih/color" "github.com/phayes/permbits" + "github.com/sirupsen/logrus" ) const ( diff --git a/dive/filetree/file_tree.go b/dive/filetree/file_tree.go index 4333e25..ede52b9 100644 --- a/dive/filetree/file_tree.go +++ b/dive/filetree/file_tree.go @@ -277,7 +277,6 @@ func (tree *FileTree) AddPath(filepath string, data FileInfo) (*FileNode, []*Fil if idx == len(nodeNames)-1 { node.Data.FileInfo = data } - } return node, addedNodes, nil } diff --git a/dive/get_image_resolver.go b/dive/get_image_resolver.go index fa79c16..05d95ab 100644 --- a/dive/get_image_resolver.go +++ b/dive/get_image_resolver.go @@ -2,11 +2,12 @@ package dive import ( "fmt" + "net/url" + "strings" + "github.com/wagoodman/dive/dive/image" "github.com/wagoodman/dive/dive/image/docker" "github.com/wagoodman/dive/dive/image/podman" - "net/url" - "strings" ) const ( @@ -56,7 +57,6 @@ func DeriveImageSource(image string) (ImageSource, string) { return SourceDockerArchive, imageSource case "docker-tar": return SourceDockerArchive, imageSource - } return SourceUnknown, "" } diff --git a/dive/image/docker/archive_resolver.go b/dive/image/docker/archive_resolver.go index 5cdc923..8baf4ac 100644 --- a/dive/image/docker/archive_resolver.go +++ b/dive/image/docker/archive_resolver.go @@ -2,8 +2,9 @@ package docker import ( "fmt" - "github.com/wagoodman/dive/dive/image" "os" + + "github.com/wagoodman/dive/dive/image" ) type archiveResolver struct{} diff --git a/dive/image/docker/cli.go b/dive/image/docker/cli.go index 6c13696..a1d3689 100644 --- a/dive/image/docker/cli.go +++ b/dive/image/docker/cli.go @@ -2,9 +2,10 @@ package docker import ( "fmt" - "github.com/wagoodman/dive/utils" "os" "os/exec" + + "github.com/wagoodman/dive/utils" ) // runDockerCmd runs a given Docker command in the current tty diff --git a/dive/image/docker/config.go b/dive/image/docker/config.go index 0efcb57..c4ef902 100644 --- a/dive/image/docker/config.go +++ b/dive/image/docker/config.go @@ -2,6 +2,7 @@ package docker import ( "encoding/json" + "github.com/sirupsen/logrus" ) diff --git a/dive/image/docker/engine_resolver.go b/dive/image/docker/engine_resolver.go index 3f91fe0..cf8b109 100644 --- a/dive/image/docker/engine_resolver.go +++ b/dive/image/docker/engine_resolver.go @@ -2,7 +2,6 @@ package docker import ( "fmt" - "github.com/wagoodman/dive/dive/image" "io" "net/http" "os" @@ -11,6 +10,8 @@ import ( "github.com/docker/cli/cli/connhelper" "github.com/docker/docker/client" "golang.org/x/net/context" + + "github.com/wagoodman/dive/dive/image" ) type engineResolver struct{} @@ -20,7 +21,6 @@ func NewResolverFromEngine() *engineResolver { } func (r *engineResolver) Fetch(id string) (*image.Image, error) { - reader, err := r.fetchArchive(id) if err != nil { return nil, err diff --git a/dive/image/docker/image_archive.go b/dive/image/docker/image_archive.go index 69d362f..3a81873 100644 --- a/dive/image/docker/image_archive.go +++ b/dive/image/docker/image_archive.go @@ -46,7 +46,6 @@ func NewImageArchive(tarFile io.ReadCloser) (*ImageArchive, error) { // some layer tars can be relative layer symlinks to other layer tars if header.Typeflag == tar.TypeSymlink || header.Typeflag == tar.TypeReg { - if strings.HasSuffix(name, ".tar") { currentLayer++ layerReader := tar.NewReader(tarReader) @@ -57,7 +56,6 @@ func NewImageArchive(tarFile io.ReadCloser) (*ImageArchive, error) { // add the layer to the image img.layerMap[tree.Name] = tree - } else if strings.HasSuffix(name, ".tar.gz") || strings.HasSuffix(name, "tgz") { currentLayer++ @@ -78,7 +76,6 @@ func NewImageArchive(tarFile io.ReadCloser) (*ImageArchive, error) { // add the layer to the image img.layerMap[tree.Name] = tree - } else if strings.HasSuffix(name, ".json") || strings.HasPrefix(name, "sha256:") { fileBuffer, err := io.ReadAll(tarReader) if err != nil { @@ -206,5 +203,4 @@ func (img *ImageArchive) ToImage() (*image.Image, error) { Trees: trees, Layers: layers, }, nil - } diff --git a/dive/image/docker/layer.go b/dive/image/docker/layer.go index 408fea1..7222735 100644 --- a/dive/image/docker/layer.go +++ b/dive/image/docker/layer.go @@ -1,10 +1,10 @@ package docker import ( - "github.com/wagoodman/dive/dive/image" "strings" "github.com/wagoodman/dive/dive/filetree" + "github.com/wagoodman/dive/dive/image" ) // Layer represents a Docker image layer and metadata diff --git a/dive/image/docker/manifest.go b/dive/image/docker/manifest.go index b08ee05..3a4e9af 100644 --- a/dive/image/docker/manifest.go +++ b/dive/image/docker/manifest.go @@ -2,6 +2,7 @@ package docker import ( "encoding/json" + "github.com/sirupsen/logrus" ) diff --git a/dive/image/docker/testing.go b/dive/image/docker/testing.go index ac70d7c..f8d8817 100644 --- a/dive/image/docker/testing.go +++ b/dive/image/docker/testing.go @@ -1,9 +1,10 @@ package docker import ( - "github.com/wagoodman/dive/dive/image" "os" "testing" + + "github.com/wagoodman/dive/dive/image" ) func TestLoadArchive(tarPath string) (*ImageArchive, error) { diff --git a/dive/image/image.go b/dive/image/image.go index de72c7b..04a1be2 100644 --- a/dive/image/image.go +++ b/dive/image/image.go @@ -10,7 +10,6 @@ type Image struct { } func (img *Image) Analyze() (*AnalysisResult, error) { - efficiency, inefficiencies := filetree.Efficiency(img.Trees) var sizeBytes, userSizeBytes uint64 diff --git a/dive/image/layer.go b/dive/image/layer.go index 8c7fa6a..66448f2 100644 --- a/dive/image/layer.go +++ b/dive/image/layer.go @@ -5,6 +5,7 @@ import ( "strings" "github.com/dustin/go-humanize" + "github.com/wagoodman/dive/dive/filetree" ) diff --git a/dive/image/podman/build.go b/dive/image/podman/build.go index 0c12d25..9909c88 100644 --- a/dive/image/podman/build.go +++ b/dive/image/podman/build.go @@ -1,3 +1,4 @@ +//go:build linux || darwin // +build linux darwin package podman diff --git a/dive/image/podman/cli.go b/dive/image/podman/cli.go index 249a0eb..73d1e0a 100644 --- a/dive/image/podman/cli.go +++ b/dive/image/podman/cli.go @@ -1,13 +1,15 @@ +//go:build linux || darwin // +build linux darwin package podman import ( "fmt" - "github.com/wagoodman/dive/utils" "io" "os" "os/exec" + + "github.com/wagoodman/dive/utils" ) // runPodmanCmd runs a given Podman command in the current tty diff --git a/dive/image/podman/resolver.go b/dive/image/podman/resolver.go index 3ead634..e34efd6 100644 --- a/dive/image/podman/resolver.go +++ b/dive/image/podman/resolver.go @@ -1,3 +1,4 @@ +//go:build linux || darwin // +build linux darwin package podman @@ -5,6 +6,7 @@ package podman import ( "fmt" "io" + "github.com/wagoodman/dive/dive/image" "github.com/wagoodman/dive/dive/image/docker" ) diff --git a/dive/image/podman/resolver_unsupported.go b/dive/image/podman/resolver_unsupported.go index c49c522..4834e1d 100644 --- a/dive/image/podman/resolver_unsupported.go +++ b/dive/image/podman/resolver_unsupported.go @@ -1,9 +1,11 @@ +//go:build !linux && !darwin // +build !linux,!darwin package podman import ( "fmt" + "github.com/wagoodman/dive/dive/image" ) diff --git a/runtime/ci/evaluator.go b/runtime/ci/evaluator.go index f8baeec..152bb21 100644 --- a/runtime/ci/evaluator.go +++ b/runtime/ci/evaluator.go @@ -2,16 +2,16 @@ package ci import ( "fmt" - "github.com/dustin/go-humanize" - "github.com/wagoodman/dive/dive/image" - "github.com/wagoodman/dive/utils" "sort" "strconv" "strings" + "github.com/dustin/go-humanize" + "github.com/logrusorgru/aurora" "github.com/spf13/viper" - "github.com/logrusorgru/aurora" + "github.com/wagoodman/dive/dive/image" + "github.com/wagoodman/dive/utils" ) type CiEvaluator struct { @@ -67,7 +67,6 @@ func (ci *CiEvaluator) Evaluate(analysis *image.AnalysisResult) bool { message: "test", } } - } if !canEvaluate { @@ -111,7 +110,6 @@ func (ci *CiEvaluator) Evaluate(analysis *image.AnalysisResult) bool { status: status, message: message, } - } ci.Tally.Total = len(ci.Results) @@ -174,7 +172,6 @@ func (ci *CiEvaluator) Report() string { if ci.Misconfigured { fmt.Fprintln(&sb, aurora.Red("CI Misconfigured")) - } else { summary := fmt.Sprintf("Result:%s [Total:%d] [Passed:%d] [Failed:%d] [Warn:%d] [Skipped:%d]", status, ci.Tally.Total, ci.Tally.Pass, ci.Tally.Fail, ci.Tally.Warn, ci.Tally.Skip) if ci.Pass { diff --git a/runtime/ci/evaluator_test.go b/runtime/ci/evaluator_test.go index d929452..c513adf 100644 --- a/runtime/ci/evaluator_test.go +++ b/runtime/ci/evaluator_test.go @@ -1,11 +1,12 @@ package ci import ( - "github.com/wagoodman/dive/dive/image/docker" "strings" "testing" "github.com/spf13/viper" + + "github.com/wagoodman/dive/dive/image/docker" ) func Test_Evaluator(t *testing.T) { diff --git a/runtime/ci/rule.go b/runtime/ci/rule.go index 60b350d..84ccede 100644 --- a/runtime/ci/rule.go +++ b/runtime/ci/rule.go @@ -2,13 +2,13 @@ package ci import ( "fmt" - "github.com/wagoodman/dive/dive/image" "strconv" - "github.com/spf13/viper" - "github.com/dustin/go-humanize" "github.com/logrusorgru/aurora" + "github.com/spf13/viper" + + "github.com/wagoodman/dive/dive/image" ) const ( diff --git a/runtime/export/export.go b/runtime/export/export.go index 8085afc..e4c8308 100644 --- a/runtime/export/export.go +++ b/runtime/export/export.go @@ -2,6 +2,7 @@ package export import ( "encoding/json" + diveImage "github.com/wagoodman/dive/dive/image" ) diff --git a/runtime/export/export_test.go b/runtime/export/export_test.go index 3d6d1d2..35133e6 100644 --- a/runtime/export/export_test.go +++ b/runtime/export/export_test.go @@ -1,9 +1,11 @@ package export import ( - "github.com/sergi/go-diff/diffmatchpatch" - "github.com/wagoodman/dive/dive/image/docker" "testing" + + "github.com/sergi/go-diff/diffmatchpatch" + + "github.com/wagoodman/dive/dive/image/docker" ) func Test_Export(t *testing.T) { diff --git a/runtime/options.go b/runtime/options.go index e0a3676..c9c5a1e 100644 --- a/runtime/options.go +++ b/runtime/options.go @@ -2,6 +2,7 @@ package runtime import ( "github.com/spf13/viper" + "github.com/wagoodman/dive/dive" ) diff --git a/runtime/run.go b/runtime/run.go index d11082d..37c8dd2 100644 --- a/runtime/run.go +++ b/runtime/run.go @@ -2,9 +2,13 @@ package runtime import ( "fmt" + "os" + "time" + "github.com/dustin/go-humanize" "github.com/sirupsen/logrus" "github.com/spf13/afero" + "github.com/wagoodman/dive/dive" "github.com/wagoodman/dive/dive/filetree" "github.com/wagoodman/dive/dive/image" @@ -12,8 +16,6 @@ import ( "github.com/wagoodman/dive/runtime/export" "github.com/wagoodman/dive/runtime/ui" "github.com/wagoodman/dive/utils" - "os" - "time" ) func run(enableUi bool, options Options, imageResolver image.Resolver, events eventChannel, filesystem afero.Fs) { @@ -84,7 +86,6 @@ func run(enableUi bool, options Options, imageResolver image.Resolver, events ev } return - } else { events.message(utils.TitleFormat("Building cache...")) treeStack := filetree.NewComparer(analysis.RefTrees) diff --git a/runtime/run_test.go b/runtime/run_test.go index faca379..1565ab5 100644 --- a/runtime/run_test.go +++ b/runtime/run_test.go @@ -2,14 +2,16 @@ package runtime import ( "fmt" + "os" + "testing" + "github.com/lunixbochs/vtclean" "github.com/spf13/afero" "github.com/spf13/viper" + "github.com/wagoodman/dive/dive" "github.com/wagoodman/dive/dive/image" "github.com/wagoodman/dive/dive/image/docker" - "os" - "testing" ) type defaultResolver struct{} diff --git a/runtime/ui/app.go b/runtime/ui/app.go index faf908f..608a664 100644 --- a/runtime/ui/app.go +++ b/runtime/ui/app.go @@ -2,16 +2,15 @@ package ui import ( "sync" - "syscall" + "github.com/awesome-gocui/gocui" + "github.com/sirupsen/logrus" + + "github.com/wagoodman/dive/dive/filetree" "github.com/wagoodman/dive/dive/image" "github.com/wagoodman/dive/runtime/ui/key" "github.com/wagoodman/dive/runtime/ui/layout" "github.com/wagoodman/dive/runtime/ui/layout/compound" - - "github.com/awesome-gocui/gocui" - "github.com/sirupsen/logrus" - "github.com/wagoodman/dive/dive/filetree" ) const debug = false @@ -51,7 +50,7 @@ func newApp(gui *gocui.Gui, imageName string, analysis *image.AnalysisResult, ca lm.Add(controller.views.Debug, layout.LocationColumn) } gui.Cursor = false - //g.Mouse = true + // g.Mouse = true gui.SetManagerFunc(lm.Layout) // var profileObj = profile.Start(profile.CPUProfile, profile.ProfilePath("."), profile.NoShutdownHook) @@ -105,7 +104,6 @@ func newApp(gui *gocui.Gui, imageName string, analysis *image.AnalysisResult, ca if err != nil { return } - }) return appSingleton, err @@ -129,22 +127,12 @@ func newApp(gui *gocui.Gui, imageName string, analysis *image.AnalysisResult, ca // quit is the gocui callback invoked when the user hits Ctrl+C func (a *app) quit() error { - // profileObj.Stop() // onExit() return gocui.ErrQuit } -// handle ctrl+z -func handle_ctrl_z(g *gocui.Gui, v *gocui.View) error { - gocui.Suspend() - if err := syscall.Kill(syscall.Getpid(), syscall.SIGSTOP); err != nil { - return err - } - return gocui.Resume() -} - // Run is the UI entrypoint. func Run(imageName string, analysis *image.AnalysisResult, treeStack filetree.Comparer) error { var err error diff --git a/runtime/ui/controller.go b/runtime/ui/controller.go index bc76423..031955d 100644 --- a/runtime/ui/controller.go +++ b/runtime/ui/controller.go @@ -1,13 +1,15 @@ package ui import ( + "regexp" + "github.com/awesome-gocui/gocui" "github.com/sirupsen/logrus" + "github.com/wagoodman/dive/dive/filetree" "github.com/wagoodman/dive/dive/image" "github.com/wagoodman/dive/runtime/ui/view" "github.com/wagoodman/dive/runtime/ui/viewmodel" - "regexp" ) type Controller struct { @@ -141,6 +143,7 @@ func (c *Controller) Render() error { return nil } +//nolint:dupl func (c *Controller) NextPane() (err error) { v := c.gui.CurrentView() if v == nil { @@ -165,6 +168,7 @@ func (c *Controller) NextPane() (err error) { return c.UpdateAndRender() } +//nolint:dupl func (c *Controller) PrevPane() (err error) { v := c.gui.CurrentView() if v == nil { diff --git a/runtime/ui/format/format.go b/runtime/ui/format/format.go index 6a7c671..e0b804f 100644 --- a/runtime/ui/format/format.go +++ b/runtime/ui/format/format.go @@ -2,23 +2,24 @@ package format import ( "fmt" + "strings" + "github.com/fatih/color" "github.com/lunixbochs/vtclean" - "strings" ) const ( - //selectedLeftBracketStr = " " - //selectedRightBracketStr = " " - //selectedFillStr = " " + // selectedLeftBracketStr = " " + // selectedRightBracketStr = " " + // selectedFillStr = " " // //leftBracketStr = "▏" //rightBracketStr = "▕" //fillStr = "─" - //selectedLeftBracketStr = " " - //selectedRightBracketStr = " " - //selectedFillStr = "━" + // selectedLeftBracketStr = " " + // selectedRightBracketStr = " " + // selectedFillStr = "━" // //leftBracketStr = "▏" //rightBracketStr = "▕" @@ -33,7 +34,7 @@ const ( fillStr = "─" selectStr = " ● " - //selectStr = " " + // selectStr = " " ) var ( @@ -74,8 +75,8 @@ func RenderHeader(title string, width int, selected bool) string { repeatCount = 0 } return fmt.Sprintf("%s%s%s%s\n", selectedLeftBracketStr, body, selectedRightBracketStr, strings.Repeat(selectedFillStr, repeatCount)) - //return fmt.Sprintf("%s%s%s%s\n", Selected(selectedLeftBracketStr), body, Selected(selectedRightBracketStr), Selected(strings.Repeat(selectedFillStr, width-bodyLen-2))) - //return fmt.Sprintf("%s%s%s%s\n", Selected(selectedLeftBracketStr), body, Selected(selectedRightBracketStr), strings.Repeat(selectedFillStr, width-bodyLen-2)) + // return fmt.Sprintf("%s%s%s%s\n", Selected(selectedLeftBracketStr), body, Selected(selectedRightBracketStr), Selected(strings.Repeat(selectedFillStr, width-bodyLen-2))) + // return fmt.Sprintf("%s%s%s%s\n", Selected(selectedLeftBracketStr), body, Selected(selectedRightBracketStr), strings.Repeat(selectedFillStr, width-bodyLen-2)) } body := Header(fmt.Sprintf(" %s ", title)) bodyLen := len(vtclean.Clean(body, false)) diff --git a/runtime/ui/job_control_other.go b/runtime/ui/job_control_other.go new file mode 100644 index 0000000..420ad1e --- /dev/null +++ b/runtime/ui/job_control_other.go @@ -0,0 +1,13 @@ +//go:build windows +// +build windows + +package ui + +import ( + "github.com/awesome-gocui/gocui" +) + +// handle ctrl+z not supported on windows +func handle_ctrl_z(_ *gocui.Gui, _ *gocui.View) error { + return nil +} diff --git a/runtime/ui/job_control_unix.go b/runtime/ui/job_control_unix.go new file mode 100644 index 0000000..f3953c8 --- /dev/null +++ b/runtime/ui/job_control_unix.go @@ -0,0 +1,19 @@ +//go:build !windows +// +build !windows + +package ui + +import ( + "syscall" + + "github.com/awesome-gocui/gocui" +) + +// handle ctrl+z +func handle_ctrl_z(g *gocui.Gui, v *gocui.View) error { + gocui.Suspend() + if err := syscall.Kill(syscall.Getpid(), syscall.SIGSTOP); err != nil { + return err + } + return gocui.Resume() +} diff --git a/runtime/ui/key/binding.go b/runtime/ui/key/binding.go index 14566a7..be77f6b 100644 --- a/runtime/ui/key/binding.go +++ b/runtime/ui/key/binding.go @@ -7,6 +7,7 @@ import ( "github.com/awesome-gocui/keybinding" "github.com/sirupsen/logrus" "github.com/spf13/viper" + "github.com/wagoodman/dive/runtime/ui/format" ) diff --git a/runtime/ui/layout/compound/layer_details_column.go b/runtime/ui/layout/compound/layer_details_column.go index 8c73b2a..7126fc0 100644 --- a/runtime/ui/layout/compound/layer_details_column.go +++ b/runtime/ui/layout/compound/layer_details_column.go @@ -3,6 +3,7 @@ package compound import ( "github.com/awesome-gocui/gocui" "github.com/sirupsen/logrus" + "github.com/wagoodman/dive/runtime/ui/view" "github.com/wagoodman/dive/utils" ) diff --git a/runtime/ui/layout/manager.go b/runtime/ui/layout/manager.go index 2f679cd..91ce7c6 100644 --- a/runtime/ui/layout/manager.go +++ b/runtime/ui/layout/manager.go @@ -51,7 +51,6 @@ func (lm *Manager) planAndLayoutHeaders(g *gocui.Gui, area Area) (Area, error) { // restrict the available screen real estate area.minY += height - } } return area, nil @@ -141,7 +140,6 @@ func (lm *Manager) planAndLayoutColumns(g *gocui.Gui, area Area) (Area, error) { // move left to right, scratching off real estate as it is taken area.minX += width - } } return area, nil diff --git a/runtime/ui/layout/manager_test.go b/runtime/ui/layout/manager_test.go index a3e6962..6199fd4 100644 --- a/runtime/ui/layout/manager_test.go +++ b/runtime/ui/layout/manager_test.go @@ -1,8 +1,9 @@ package layout import ( - "github.com/awesome-gocui/gocui" "testing" + + "github.com/awesome-gocui/gocui" ) type testElement struct { diff --git a/runtime/ui/view/cursor.go b/runtime/ui/view/cursor.go index a2b6b89..c6ab1c2 100644 --- a/runtime/ui/view/cursor.go +++ b/runtime/ui/view/cursor.go @@ -2,6 +2,7 @@ package view import ( "errors" + "github.com/awesome-gocui/gocui" ) diff --git a/runtime/ui/view/debug.go b/runtime/ui/view/debug.go index cb45676..e4cc8aa 100644 --- a/runtime/ui/view/debug.go +++ b/runtime/ui/view/debug.go @@ -5,6 +5,7 @@ import ( "github.com/awesome-gocui/gocui" "github.com/sirupsen/logrus" + "github.com/wagoodman/dive/runtime/ui/format" "github.com/wagoodman/dive/utils" ) diff --git a/runtime/ui/view/filetree.go b/runtime/ui/view/filetree.go index e90f0e4..62439e9 100644 --- a/runtime/ui/view/filetree.go +++ b/runtime/ui/view/filetree.go @@ -7,6 +7,7 @@ import ( "github.com/awesome-gocui/gocui" "github.com/sirupsen/logrus" "github.com/spf13/viper" + "github.com/wagoodman/dive/dive/filetree" "github.com/wagoodman/dive/runtime/ui/format" "github.com/wagoodman/dive/runtime/ui/key" @@ -406,7 +407,7 @@ func (v *FileTree) Layout(g *gocui.Gui, minX, minY, maxX, maxY int) error { logrus.Tracef("view.Layout(minX: %d, minY: %d, maxX: %d, maxY: %d) %s", minX, minY, maxX, maxY, v.Name()) attributeRowSize := 0 - // make the layout responsive to the available realestate. Make more room for the main content by hiding auxillary + // make the layout responsive to the available realestate. Make more room for the main content by hiding auxiliary // content when there is not enough room if maxX-minX < 60 { v.vm.ConstrainLayout() @@ -436,7 +437,7 @@ func (v *FileTree) Layout(g *gocui.Gui, minX, minY, maxX, maxY int) error { } func (v *FileTree) RequestedSize(available int) *int { - //var requestedWidth = int(float64(available) * (1.0 - v.requestedWidthRatio)) - //return &requestedWidth + // var requestedWidth = int(float64(available) * (1.0 - v.requestedWidthRatio)) + // return &requestedWidth return nil } diff --git a/runtime/ui/view/filter.go b/runtime/ui/view/filter.go index 83978e5..a9617c7 100644 --- a/runtime/ui/view/filter.go +++ b/runtime/ui/view/filter.go @@ -6,6 +6,7 @@ import ( "github.com/awesome-gocui/gocui" "github.com/sirupsen/logrus" + "github.com/wagoodman/dive/runtime/ui/format" "github.com/wagoodman/dive/utils" ) diff --git a/runtime/ui/view/image_details.go b/runtime/ui/view/image_details.go index 6a31b55..08098ab 100644 --- a/runtime/ui/view/image_details.go +++ b/runtime/ui/view/image_details.go @@ -2,14 +2,16 @@ package view import ( "fmt" + "strconv" + "strings" + "github.com/awesome-gocui/gocui" "github.com/dustin/go-humanize" "github.com/sirupsen/logrus" + "github.com/wagoodman/dive/dive/filetree" "github.com/wagoodman/dive/runtime/ui/format" "github.com/wagoodman/dive/runtime/ui/key" - "strconv" - "strings" ) type ImageDetails struct { diff --git a/runtime/ui/view/layer.go b/runtime/ui/view/layer.go index caa3d7b..ce6954a 100644 --- a/runtime/ui/view/layer.go +++ b/runtime/ui/view/layer.go @@ -6,6 +6,7 @@ import ( "github.com/awesome-gocui/gocui" "github.com/sirupsen/logrus" "github.com/spf13/viper" + "github.com/wagoodman/dive/dive/image" "github.com/wagoodman/dive/runtime/ui/format" "github.com/wagoodman/dive/runtime/ui/key" @@ -319,7 +320,6 @@ func (v *Layer) Render() error { // update contents v.body.Clear() for idx, layer := range v.vm.Layers { - var layerStr string if v.constrainedRealEstate { layerStr = fmt.Sprintf("%-4d", layer.Index) @@ -339,7 +339,6 @@ func (v *Layer) Render() error { logrus.Debug("unable to write to buffer: ", err) return err } - } return nil }) diff --git a/runtime/ui/view/layer_details.go b/runtime/ui/view/layer_details.go index b4c6f68..bfcb425 100644 --- a/runtime/ui/view/layer_details.go +++ b/runtime/ui/view/layer_details.go @@ -2,12 +2,14 @@ package view import ( "fmt" + "strings" + "github.com/awesome-gocui/gocui" "github.com/sirupsen/logrus" + "github.com/wagoodman/dive/dive/image" "github.com/wagoodman/dive/runtime/ui/format" "github.com/wagoodman/dive/runtime/ui/key" - "strings" ) type LayerDetails struct { diff --git a/runtime/ui/view/status.go b/runtime/ui/view/status.go index 9706280..f7b130f 100644 --- a/runtime/ui/view/status.go +++ b/runtime/ui/view/status.go @@ -4,12 +4,12 @@ import ( "fmt" "strings" + "github.com/awesome-gocui/gocui" "github.com/sirupsen/logrus" + "github.com/wagoodman/dive/runtime/ui/format" "github.com/wagoodman/dive/runtime/ui/key" "github.com/wagoodman/dive/utils" - - "github.com/awesome-gocui/gocui" ) // Status holds the UI objects and data models for populating the bottom-most pane. Specifically the panel diff --git a/runtime/ui/view/views.go b/runtime/ui/view/views.go index 0178c36..785f5d3 100644 --- a/runtime/ui/view/views.go +++ b/runtime/ui/view/views.go @@ -2,6 +2,7 @@ package view import ( "github.com/awesome-gocui/gocui" + "github.com/wagoodman/dive/dive/filetree" "github.com/wagoodman/dive/dive/image" ) diff --git a/runtime/ui/viewmodel/filetree.go b/runtime/ui/viewmodel/filetree.go index 2240ab3..d0172d4 100644 --- a/runtime/ui/viewmodel/filetree.go +++ b/runtime/ui/viewmodel/filetree.go @@ -3,14 +3,15 @@ package viewmodel import ( "bytes" "fmt" - "github.com/wagoodman/dive/runtime/ui/format" "regexp" "strings" "github.com/lunixbochs/vtclean" "github.com/sirupsen/logrus" "github.com/spf13/viper" + "github.com/wagoodman/dive/dive/filetree" + "github.com/wagoodman/dive/runtime/ui/format" ) // FileTreeViewModel holds the UI objects and data models for populating the right pane. Specifically the pane that diff --git a/runtime/ui/viewmodel/filetree_test.go b/runtime/ui/viewmodel/filetree_test.go index cf321a0..668eb1e 100644 --- a/runtime/ui/viewmodel/filetree_test.go +++ b/runtime/ui/viewmodel/filetree_test.go @@ -2,8 +2,6 @@ package viewmodel import ( "bytes" - "github.com/wagoodman/dive/dive/image/docker" - "github.com/wagoodman/dive/runtime/ui/format" "os" "path/filepath" "regexp" @@ -11,7 +9,10 @@ import ( "github.com/fatih/color" "github.com/sergi/go-diff/diffmatchpatch" + "github.com/wagoodman/dive/dive/filetree" + "github.com/wagoodman/dive/dive/image/docker" + "github.com/wagoodman/dive/runtime/ui/format" ) const allowTestDataCapture = false diff --git a/utils/format.go b/utils/format.go index 77a9d59..cabdbc1 100644 --- a/utils/format.go +++ b/utils/format.go @@ -1,8 +1,9 @@ package utils import ( - "github.com/logrusorgru/aurora" "strings" + + "github.com/logrusorgru/aurora" ) func TitleFormat(s string) string {