diff --git a/.binny.yaml b/.binny.yaml new file mode 100644 index 0000000..b03f4d9 --- /dev/null +++ b/.binny.yaml @@ -0,0 +1,64 @@ +tools: + # we want to use a pinned version of binny to manage the toolchain (so binny manages itself!) + - name: binny + version: + want: v0.9.0 + method: github-release + with: + repo: anchore/binny + + # used for linting + - name: golangci-lint + version: + want: v1.64.8 + method: github-release + with: + repo: golangci/golangci-lint + + # used for showing the changelog at release + - name: glow + version: + want: v2.1.0 + method: github-release + with: + repo: charmbracelet/glow + + # used to release all artifacts + - name: goreleaser + version: + want: v2.8.1 + method: github-release + with: + repo: goreleaser/goreleaser + + # used at release to generate the changelog + - name: chronicle + version: + want: v0.8.0 + method: github-release + with: + repo: anchore/chronicle + + # used during static analysis for license compliance + - name: bouncer + version: + want: v0.4.0 + method: github-release + with: + repo: wagoodman/go-bouncer + + # used for running all local and CI tasks + - name: task + version: + want: v3.42.1 + method: github-release + with: + repo: go-task/task + + # used for triggering a release + - name: gh + version: + want: v2.69.0 + method: github-release + with: + repo: cli/cli diff --git a/.github/actions/bootstrap/action.yaml b/.github/actions/bootstrap/action.yaml index 1b9032b..4c66e26 100644 --- a/.github/actions/bootstrap/action.yaml +++ b/.github/actions/bootstrap/action.yaml @@ -5,18 +5,10 @@ inputs: description: "Go version to install" required: true default: "1.24.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: "" @@ -35,39 +27,15 @@ runs: 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@v4 - 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@v4 - 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 }}- + run: make tools - 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 + if: steps.go-mod-cache.outputs.cache-hit != 'true' + run: go mod download -x - name: Install apt packages if: inputs.bootstrap-apt-packages != '' diff --git a/.github/scripts/ci-check.sh b/.github/scripts/ci-check.sh deleted file mode 100755 index 0ab83a3..0000000 --- a/.github/scripts/ci-check.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/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/go-mod-tidy-check.sh b/.github/scripts/go-mod-tidy-check.sh deleted file mode 100755 index 41bc639..0000000 --- a/.github/scripts/go-mod-tidy-check.sh +++ /dev/null @@ -1,31 +0,0 @@ -#!/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/.gitignore b/.gitignore index f5868db..e80299d 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,9 @@ VERSION /bin /.tool-versions /.tmp +/.tool +/.mise.toml +/.task # builds /dist diff --git a/Makefile b/Makefile index 6cbe167..ab8f94c 100644 --- a/Makefile +++ b/Makefile @@ -1,354 +1,47 @@ -BIN = dive -TEMP_DIR = ./.tmp -PWD := ${CURDIR} -SHELL = /bin/bash -o pipefail -TEST_IMAGE = busybox:latest +OWNER = wagoodman +PROJECT = dive -# Tool versions ################################# -GOLANG_CI_VERSION = v1.64.5 -GOBOUNCER_VERSION = v0.4.0 -GORELEASER_VERSION = v2.4.4 -GOSIMPORTS_VERSION = v0.3.8 -CHRONICLE_VERSION = v0.8.0 -GLOW_VERSION = v1.5.1 -DOCKER_CLI_VERSION = 28.0.0 - -# Command templates ################################# -LINT_CMD = $(TEMP_DIR)/golangci-lint run --tests=false --timeout=2m --config .golangci.yaml -GOIMPORTS_CMD = $(TEMP_DIR)/gosimports -local github.com/wagoodman -RELEASE_CMD = DOCKER_CLI_VERSION=$(DOCKER_CLI_VERSION) $(TEMP_DIR)/goreleaser release --clean -SNAPSHOT_CMD = $(RELEASE_CMD) --skip=publish --skip=sign --snapshot -CHRONICLE_CMD = $(TEMP_DIR)/chronicle -GLOW_CMD = $(TEMP_DIR)/glow - -# 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) - -# Test variables ################################# -# the quality gate lower threshold for unit test total % coverage (by function statements) -COVERAGE_THRESHOLD := 30 - -## 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) - -ifeq "$(strip $(VERSION))" "" - override VERSION = $(shell git describe --always --tags --dirty) -endif - -## Variable assertions - -ifndef TEMP_DIR - $(error TEMP_DIR is not set) -endif - -ifndef DIST_DIR - $(error DIST_DIR is not set) -endif - -ifndef SNAPSHOT_DIR - $(error SNAPSHOT_DIR is not set) -endif - -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) +TOOL_DIR = .tool +BINNY = $(TOOL_DIR)/binny +TASK = $(TOOL_DIR)/task +.DEFAULT_GOAL := make-default ## 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/v2@$(GORELEASER_VERSION) - GOBIN="$(realpath $(TEMP_DIR))" go install github.com/rinchsan/gosimports/cmd/gosimports@$(GOSIMPORTS_VERSION) - GOBIN="$(realpath $(TEMP_DIR))" go install github.com/charmbracelet/glow@$(GLOW_VERSION) +# note: we need to assume that binny and task have not already been installed +$(BINNY): + @mkdir -p $(TOOL_DIR) + @curl -sSfL https://raw.githubusercontent.com/anchore/binny/main/install.sh | sh -s -- -b $(TOOL_DIR) -.PHONY: bootstrap-go -bootstrap-go: - $(call title,Bootstrapping go dependencies) - go mod download +# note: we need to assume that binny and task have not already been installed +.PHONY: task +$(TASK) task: $(BINNY) + @$(BINNY) install task -q -.PHONY: bootstrap -bootstrap: bootstrap-go bootstrap-tools ## Download and install all go dependencies (+ prep tooling in the ./tmp dir) +# this is a bootstrapping catch-all, where if the target doesn't exist, we'll ensure the tools are installed and then try again +%: + @make --silent $(TASK) + @$(TASK) $@ +## Shim targets ################################# -## Development targets ################################### +.PHONY: make-default +make-default: $(TASK) + @# run the default task in the taskfile + @$(TASK) -#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) +# for those of us that can't seem to kick the habit of typing `make ...` lets wrap the superior `task` tool +TASKS := $(shell bash -c "test -f $(TASK) && $(TASK) -l | grep '^\* ' | cut -d' ' -f2 | tr -d ':' | tr '\n' ' '" ) $(shell bash -c "test -f $(TASK) && $(TASK) -l | grep 'aliases:' | cut -d ':' -f 3 | tr '\n' ' ' | tr -d ','") -.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!' +.PHONY: $(TASKS) +$(TASKS): $(TASK) + @$(TASK) $@ -.PHONY: generate-compressed-test-images -generate-compressed-test-images: - for alg in uncompressed gzip estargz zstd; do \ - for exporter in docker image; do \ - docker buildx build \ - -f .data/Dockerfile.minimal \ - --tag test-dive-$${exporter}:$${alg} \ - --output type=$${exporter},force-compression=true,compression=$${alg} . ; \ - done ; \ - done && \ - echo 'Exported test data!' +## actual targets -.PHONY: generate-compressed-test-data -generate-compressed-test-data: - for alg in uncompressed gzip estargz zstd; \ - do \ - docker buildx build \ - -f .data/Dockerfile.minimal \ - --output type=tar,dest=.data/test-$${alg}-image.tar,force-compression=true,compression=$${alg} . ; \ - docker buildx build \ - -f .data/Dockerfile.minimal \ - --output type=oci,dest=.data/test-oci-$${alg}-image.tar,force-compression=true,compression=$${alg} . ; \ - done && \ - echo 'Exported test data!' - - -## Static analysis targets ################################# - -.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 -.PHONY: ci-test-docker-image -ci-test-docker-image: - docker run \ - --rm \ - -t \ - -v /var/run/docker.sock:/var/run/docker.sock \ - 'docker.io/wagoodman/dive:latest-amd64' \ - '${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 \ - ubuntu:latest \ - /bin/bash -x -c "\ - apt update && \ - apt install -y curl && \ - 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 && \ - 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 \ - 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 ./snapshot/dive_*_linux_amd64.rpm -y && \ - dive --version && \ - dive '${TEST_IMAGE}' --ci \ - " - -.PHONY: ci-test-linux-run -ci-test-linux-run: generate-compressed-test-images - ls -la $(SNAPSHOT_DIR) - ls -la $(SNAPSHOT_DIR)/dive_linux_amd64_v1 - chmod 755 $(SNAPSHOT_DIR)/dive_linux_amd64_v1/dive && \ - $(SNAPSHOT_DIR)/dive_linux_amd64_v1/dive '${TEST_IMAGE}' --ci && \ - $(SNAPSHOT_DIR)/dive_linux_amd64_v1/dive --source docker-archive .data/test-kaniko-image.tar --ci --ci-config .data/.dive-ci - for alg in uncompressed gzip estargz zstd; do \ - for exporter in docker image; do \ - $(SNAPSHOT_DIR)/dive_linux_amd64_v1/dive "test-dive-$${exporter}:$${alg}" --ci ; \ - done && \ - $(SNAPSHOT_DIR)/dive_linux_amd64_v1/dive --source docker-archive .data/test-oci-$${alg}-image.tar --ci --ci-config .data/.dive-ci; \ - done - -# we're not attempting to test docker, just our ability to run on these systems. This avoids setting up docker in CI. -.PHONY: ci-test-mac-run -ci-test-mac-run: - 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: - dive.exe --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 ################################# - -.PHONY: build -build: $(SNAPSHOT_DIR) ## Build release snapshot binaries and packages - -$(SNAPSHOT_DIR): ## Build snapshot release binaries and packages - $(call title,Building snapshot artifacts) - - @# create a config with the dist dir overridden - @echo "dist: $(SNAPSHOT_DIR)" > $(TEMP_DIR)/goreleaser.yaml - @cat .goreleaser.yaml >> $(TEMP_DIR)/goreleaser.yaml - - @# build release snapshots - @bash -c "\ - VERSION=$(VERSION:v%=%) \ - $(SNAPSHOT_CMD) --config $(TEMP_DIR)/goreleaser.yaml \ - " - -.PHONY: cli -cli: $(SNAPSHOT_DIR) ## Run CLI tests - chmod 755 "$(SNAPSHOT_BIN)" - $(SNAPSHOT_BIN) version - go test -count=1 -timeout=15m -v ./test/cli - -.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) - -$(CHANGELOG): - $(CHRONICLE_CMD) -vvv > $(CHANGELOG) - -.PHONY: release -release: ## Cut a new release - @.github/scripts/trigger-release.sh - -.PHONY: ci-release -ci-release: ci-check clean-dist $(CHANGELOG) - $(call title,Publishing release artifacts) - - # create a config with the dist dir overridden - echo "dist: $(DIST_DIR)" > $(TEMP_DIR)/goreleaser.yaml - cat .goreleaser.yaml >> $(TEMP_DIR)/goreleaser.yaml - - 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 - - -## Help! ################################# - -.PHONY: help -help: - @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "$(BOLD)$(CYAN)%-25s$(RESET)%s\n", $$1, $$2}' +help: $(TASK) + @$(TASK) -l diff --git a/Taskfile.yaml b/Taskfile.yaml new file mode 100644 index 0000000..19c9f3f --- /dev/null +++ b/Taskfile.yaml @@ -0,0 +1,382 @@ + +version: "3" +vars: + OWNER: wagoodman + PROJECT: dive + + # static file dirs + TOOL_DIR: .tool + TMP_DIR: .tmp + + # TOOLS + BINNY: "{{ .TOOL_DIR }}/binny" + CHRONICLE: "{{ .TOOL_DIR }}/chronicle" + GORELEASER: "{{ .TOOL_DIR }}/goreleaser" + GOLANGCI_LINT: "{{ .TOOL_DIR }}/golangci-lint" + TASK: "{{ .TOOL_DIR }}/task" + BOUNCER: "{{ .TOOL_DIR }}/bouncer" + GLOW: "{{ .TOOL_DIR }}/glow" + + # used for changelog generation + CHANGELOG: CHANGELOG.md + NEXT_VERSION: VERSION + + # note: the snapshot dir must be a relative path starting with ./ + SNAPSHOT_DIR: ./snapshot + SNAPSHOT_CMD: "{{ .GORELEASER }} release --config {{ .TMP_DIR }}/goreleaser.yaml --clean --snapshot --skip=publish --skip=sign" + BUILD_CMD: "{{ .GORELEASER }} build --config {{ .TMP_DIR }}/goreleaser.yaml --clean --snapshot --single-target" + RELEASE_CMD: "{{ .GORELEASER }} release --clean --release-notes {{ .CHANGELOG }}" + VERSION: + sh: git describe --dirty --always --tags + + # used for acceptance tests + TEST_IMAGE: busybox:latest + DOCKER_CLI_VERSION: 28.0.0 + +env: + GNUMAKEFLAGS: '--no-print-directory' + DOCKER_CLI_VERSION: "{{ .DOCKER_CLI_VERSION }}" + +tasks: + + ## High-level tasks ################################# + + default: + desc: Run all validation tasks + aliases: + - pr-validations + - validations + cmds: + - task: static-analysis + - task: test + + static-analysis: + desc: Run all static analysis tasks + cmds: + - task: check-go-mod-tidy + - task: check-licenses + - task: lint + + test: + desc: Run all levels of test + cmds: + - task: unit + + ## Bootstrap tasks ################################# + + binny: + internal: true + # desc: Get the binny tool + generates: + - "{{ .BINNY }}" + status: + - "test -f {{ .BINNY }}" + cmd: "curl -sSfL https://raw.githubusercontent.com/anchore/binny/main/install.sh | sh -s -- -b .tool" + silent: true + + tools: + desc: Install all tools needed for CI and local development + deps: [binny] + aliases: + - bootstrap + generates: + - ".binny.yaml" + - "{{ .TOOL_DIR }}/*" + status: + - "{{ .BINNY }} check -v" + cmd: "{{ .BINNY }} install -v" + silent: true + + update-tools: + desc: Update pinned versions of all tools to their latest available versions + deps: [binny] + generates: + - ".binny.yaml" + - "{{ .TOOL_DIR }}/*" + cmd: "{{ .BINNY }} update -v" + silent: true + + list-tools: + desc: List all tools needed for CI and local development + deps: [binny] + cmd: "{{ .BINNY }} list" + silent: true + + list-tool-updates: + desc: List all tools that are not up to date relative to the binny config + deps: [binny] + cmd: "{{ .BINNY }} list --updates" + silent: true + + tmpdir: + silent: true + generates: + - "{{ .TMP_DIR }}" + cmd: "mkdir -p {{ .TMP_DIR }}" + + ## Static analysis tasks ################################# + + format: + desc: Auto-format all source code + deps: [tools] + cmds: + - gofmt -w -s . + - go mod tidy + + lint-fix: + desc: Auto-format all source code + run golangci lint fixers + deps: [tools] + cmds: + - task: format + - "{{ .GOLANGCI_LINT }} run --tests=false --fix" + + lint: + desc: Run gofmt + golangci lint checks + vars: + BAD_FMT_FILES: + sh: gofmt -l -s . + BAD_FILE_NAMES: + sh: "find . | grep -e ':' || true" + deps: [tools] + cmds: + # ensure there are no go fmt differences + - cmd: 'test -z "{{ .BAD_FMT_FILES }}" || (echo "files with gofmt issues: [{{ .BAD_FMT_FILES }}]"; exit 1)' + silent: true + # ensure there are no files with ":" in it (a known back case in the go ecosystem) + - cmd: 'test -z "{{ .BAD_FILE_NAMES }}" || (echo "files with bad names: [{{ .BAD_FILE_NAMES }}]"; exit 1)' + silent: true + # run linting + - "{{ .GOLANGCI_LINT }} run --tests=false" + + check-licenses: + # desc: Ensure transitive dependencies are compliant with the current license policy + deps: [tools] + cmd: "{{ .BOUNCER }} check ./..." + + check-go-mod-tidy: + # desc: Ensure go.mod and go.sum are up to date + cmds: + - cmd: | + if ! go mod tidy -diff; then + echo "go.mod and/or go.sum need updates. Please run 'go mod tidy'" + exit 1 + fi + silent: true + + + ## Testing tasks ################################# + + unit: + desc: Run unit tests + deps: + - tmpdir + vars: + TEST_PKGS: + sh: "go list ./... | tr '\n' ' '" + + # unit test coverage threshold (in % coverage) + COVERAGE_THRESHOLD: 30 + cmds: + - "go test -coverprofile {{ .TMP_DIR }}/unit-coverage-details.txt {{ .TEST_PKGS }}" + - cmd: ".github/scripts/coverage.py {{ .COVERAGE_THRESHOLD }} {{ .TMP_DIR }}/unit-coverage-details.txt" + silent: true + + + ## Acceptance tests ################################# + + ci-test-linux: + cmds: + - task: ci-test-linux-run + - task: ci-test-docker-image + - task: ci-test-deb-package-install + - task: ci-test-rpm-package-install + + ci-test-docker-image: + desc: Test using the docker image + cmds: + - | + docker run \ + --rm \ + -t \ + -v /var/run/docker.sock:/var/run/docker.sock \ + 'docker.io/wagoodman/dive:latest' \ + '{{ .TEST_IMAGE }}' \ + --ci + + ci-test-deb-package-install: + desc: Test debian package installation + cmds: + - | + docker run \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -v /${PWD}:/src \ + -w /src \ + ubuntu:latest \ + /bin/bash -x -c "\ + apt update && \ + apt install -y curl && \ + 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 && \ + apt install ./snapshot/dive_*_linux_amd64.deb -y && \ + dive --version && \ + dive '{{ .TEST_IMAGE }}' --ci \ + " + + ci-test-rpm-package-install: + desc: Test RPM package installation + cmds: + - | + docker run \ + -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 ./snapshot/dive_*_linux_amd64.rpm -y && \ + dive --version && \ + dive '{{ .TEST_IMAGE }}' --ci \ + " + + generate-compressed-test-images: + desc: Generate compressed test images for testing + cmds: + - | + for alg in uncompressed gzip estargz zstd; do \ + for exporter in docker image; do \ + docker buildx build \ + -f .data/Dockerfile.minimal \ + --tag test-dive-${exporter}:${alg} \ + --output type=${exporter},force-compression=true,compression=${alg} . ; \ + done ; \ + done && \ + echo 'Exported test data!' + + generate-compressed-test-data: + desc: Generate compressed test data for testing + cmds: + - | + for alg in uncompressed gzip estargz zstd; \ + do \ + docker buildx build \ + -f .data/Dockerfile.minimal \ + --output type=tar,dest=.data/test-${alg}-image.tar,force-compression=true,compression=${alg} . ; \ + docker buildx build \ + -f .data/Dockerfile.minimal \ + --output type=oci,dest=.data/test-oci-${alg}-image.tar,force-compression=true,compression=${alg} . ; \ + done && \ + echo 'Exported test data!' + + ci-test-linux-run: + desc: Test Linux binary execution (CI only) + deps: [ci-check, generate-compressed-test-images] + cmds: + - | + ls -la {{ .SNAPSHOT_DIR }} + ls -la {{ .SNAPSHOT_DIR }}/dive_linux_amd64_v1 + chmod 755 {{ .SNAPSHOT_DIR }}/dive_linux_amd64_v1/dive && \ + {{ .SNAPSHOT_DIR }}/dive_linux_amd64_v1/dive '{{ .TEST_IMAGE }}' --ci && \ + {{ .SNAPSHOT_DIR }}/dive_linux_amd64_v1/dive --source docker-archive .data/test-kaniko-image.tar --ci --ci-config .data/.dive-ci + - | + for alg in uncompressed gzip estargz zstd; do \ + for exporter in docker image; do \ + {{ .SNAPSHOT_DIR }}/dive_linux_amd64_v1/dive "test-dive-${exporter}:${alg}" --ci ; \ + done && \ + {{ .SNAPSHOT_DIR }}/dive_linux_amd64_v1/dive --source docker-archive .data/test-oci-${alg}-image.tar --ci --ci-config .data/.dive-ci; \ + done + + ci-test-mac-run: + desc: Test macOS binary execution (CI only) + deps: [ci-check] + cmds: + - | + 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 + + + ## Build-related targets ################################# + + build: + desc: Build the project + deps: [tools, tmpdir] + generates: + - "{{ .PROJECT }}" + cmds: + - silent: true + cmd: | + echo "dist: {{ .SNAPSHOT_DIR }}" > {{ .TMP_DIR }}/goreleaser.yaml + cat .goreleaser.yaml >> {{ .TMP_DIR }}/goreleaser.yaml + + - "{{ .BUILD_CMD }}" + + snapshot: + desc: Create a snapshot release + aliases: + - build + deps: [tools, tmpdir] + sources: + - "**/*.go" + method: checksum + cmds: + - silent: true + cmd: | + echo "dist: {{ .SNAPSHOT_DIR }}" > {{ .TMP_DIR }}/goreleaser.yaml + cat .goreleaser.yaml >> {{ .TMP_DIR }}/goreleaser.yaml + + - "{{ .SNAPSHOT_CMD }}" + + changelog: + desc: Generate a changelog + deps: [tools] + generates: + - "{{ .CHANGELOG }}" + - "{{ .NEXT_VERSION }}" + cmds: + - "{{ .CHRONICLE }} -vv -n --version-file {{ .NEXT_VERSION }} > {{ .CHANGELOG }}" + - "{{ .GLOW }} -w 200 {{ .CHANGELOG }}" + + + ## Release targets ################################# + + release: + desc: Create a release + interactive: true + deps: [tools] + cmds: + - cmd: .github/scripts/trigger-release.sh + silent: true + + + ## CI-only targets ################################# + + ci-check: + preconditions: + - sh: test -n "$CI" + msg: "This step should ONLY be run in CI. Exiting..." + cmds: + - echo "Running in CI environment" + silent: true + internal: true + + ci-release: + # desc: "[CI only] Create a release" + deps: [ci-check, tools] + cmds: + - "{{ .CHRONICLE }} -vvv > CHANGELOG.md" + - cmd: "cat CHANGELOG.md" + silent: true + - "{{ .RELEASE_CMD }}" + + + ## Cleanup targets ################################# + + clean-snapshot: + desc: Remove any snapshot builds + cmds: + - "rm -rf {{ .SNAPSHOT_DIR }}" + - "rm -rf {{ .TMP_DIR }}/goreleaser.yaml"