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
This commit is contained in:
Alex Goodman 2023-07-06 22:01:46 -04:00 committed by GitHub
parent 42925c1b05
commit d5e8a92968
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
73 changed files with 999 additions and 511 deletions

12
.bouncer.yaml Normal file
View file

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

View file

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

1
.github/FUNDING.yml vendored
View file

@ -1,2 +1 @@
github: ['wagoodman']
custom: ['https://www.paypal.me/wagoodman']

20
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View file

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

View file

@ -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**:
<!-- Add any other context or screenshots about the feature request here. -->

76
.github/actions/bootstrap/action.yaml vendored Normal file
View file

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

11
.github/scripts/ci-check.sh vendored Executable file
View file

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

36
.github/scripts/coverage.py vendored Executable file
View file

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

31
.github/scripts/go-mod-tidy-check.sh vendored Executable file
View file

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

50
.github/scripts/trigger-release.sh vendored Executable file
View file

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

View file

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

114
.github/workflows/release.yaml vendored Normal file
View file

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

135
.github/workflows/validations.yaml vendored Normal file
View file

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

24
.gitignore vendored
View file

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

74
.golangci.yaml Normal file
View file

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

View file

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

View file

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

View file

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

324
Makefile
View file

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

View file

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

View file

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

View file

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

View file

@ -10,7 +10,6 @@ import (
)
func configureCi() (bool, *viper.Viper, error) {
isCiFromEnv, _ := strconv.ParseBool(os.Getenv("CI"))
isCi = isCi || isCiFromEnv

View file

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

View file

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

View file

@ -85,7 +85,6 @@ func Efficiency(trees []*FileTree) (float64, EfficiencySlice) {
return err
}
}
} else {
sizeBytes = node.Data.FileInfo.Size
}

View file

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

View file

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

View file

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

View file

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

View file

@ -2,8 +2,9 @@ package docker
import (
"fmt"
"github.com/wagoodman/dive/dive/image"
"os"
"github.com/wagoodman/dive/dive/image"
)
type archiveResolver struct{}

View file

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

View file

@ -2,6 +2,7 @@ package docker
import (
"encoding/json"
"github.com/sirupsen/logrus"
)

View file

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

View file

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

View file

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

View file

@ -2,6 +2,7 @@ package docker
import (
"encoding/json"
"github.com/sirupsen/logrus"
)

View file

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

View file

@ -10,7 +10,6 @@ type Image struct {
}
func (img *Image) Analyze() (*AnalysisResult, error) {
efficiency, inefficiencies := filetree.Efficiency(img.Trees)
var sizeBytes, userSizeBytes uint64

View file

@ -5,6 +5,7 @@ import (
"strings"
"github.com/dustin/go-humanize"
"github.com/wagoodman/dive/dive/filetree"
)

View file

@ -1,3 +1,4 @@
//go:build linux || darwin
// +build linux darwin
package podman

View file

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

View file

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

View file

@ -1,9 +1,11 @@
//go:build !linux && !darwin
// +build !linux,!darwin
package podman
import (
"fmt"
"github.com/wagoodman/dive/dive/image"
)

View file

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

View file

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

View file

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

View file

@ -2,6 +2,7 @@ package export
import (
"encoding/json"
diveImage "github.com/wagoodman/dive/dive/image"
)

View file

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

View file

@ -2,6 +2,7 @@ package runtime
import (
"github.com/spf13/viper"
"github.com/wagoodman/dive/dive"
)

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,8 +1,9 @@
package layout
import (
"github.com/awesome-gocui/gocui"
"testing"
"github.com/awesome-gocui/gocui"
)
type testElement struct {

View file

@ -2,6 +2,7 @@ package view
import (
"errors"
"github.com/awesome-gocui/gocui"
)

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -2,6 +2,7 @@ package view
import (
"github.com/awesome-gocui/gocui"
"github.com/wagoodman/dive/dive/filetree"
"github.com/wagoodman/dive/dive/image"
)

View file

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

View file

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

View file

@ -1,8 +1,9 @@
package utils
import (
"github.com/logrusorgru/aurora"
"strings"
"github.com/logrusorgru/aurora"
)
func TitleFormat(s string) string {