diff --git a/.circleci/config.yml b/.circleci/config.yml index 1947021f9..68adeb6c7 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -3,6 +3,11 @@ executors: docker: - image: cimg/go:1.15 +runOnAllTags: &runOnAllTags + filters: + tags: + only: /.*/ + version: 2.1 jobs: @@ -31,15 +36,32 @@ jobs: command: | make test + build: + executor: custom + steps: + - checkout + + - run: + name: Build binaries + command: | + make build + + - run: + name: Ensure `kube-linter version` returns the expected value. + command: | + version="$(.gobin/kube-linter version)" + expected_version="$(./get-tag)" + echo "Version from kube-linter: ${version}. Expected version: ${expected_version}" + [[ "${version}" == "${expected_version}" ]] + + workflows: version: 2 build: jobs: - lint: - filters: - tags: - only: /.*/ + <<: *runOnAllTags - test: - filters: - tags: - only: /.*/ + <<: *runOnAllTags + - build: + <<: *runOnAllTags diff --git a/Makefile b/Makefile index b943a5cb2..0cb17e777 100644 --- a/Makefile +++ b/Makefile @@ -10,10 +10,12 @@ deps: go.mod @go mod verify @touch deps +UNAME_S := $(shell uname -s) +HOST_OS := linux +ifeq ($(UNAME_S),Darwin) + HOST_OS := darwin +endif -##################################################################### -###### Binaries we depend on ############ -##################################################################### GOBIN := $(CURDIR)/.gobin PATH := $(GOBIN):$(PATH) @@ -23,6 +25,10 @@ PATH := $(GOBIN):$(PATH) # See https://stackoverflow.com/a/36226784/3690207 SHELL := env PATH=$(PATH) /bin/bash +######################################## +###### Binaries we depend on ########### +######################################## + GOLANGCILINT_BIN := $(GOBIN)/golangci-lint $(GOLANGCILINT_BIN): deps @echo "+ $@" @@ -66,8 +72,8 @@ lint: golangci-lint staticcheck .PHONY: generated-docs generated-docs: build - ./bin/kube-linter templates list --format markdown > docs/generated/templates.md - ./bin/kube-linter checks list --format markdown > docs/generated/checks.md + kube-linter templates list --format markdown > docs/generated/templates.md + kube-linter checks list --format markdown > docs/generated/checks.md .PHONY: packr packr: $(PACKR_BIN) @@ -80,7 +86,12 @@ packr: $(PACKR_BIN) .PHONY: build build: packr - go build -o ./bin/kube-linter ./cmd/kube-linter + @CGO_ENABLED=0 GOOS=darwin scripts/go-build.sh ./cmd/kube-linter + @CGO_ENABLED=0 GOOS=linux scripts/go-build.sh ./cmd/kube-linter + @CGO_ENABLED=0 GOOS=windows scripts/go-build.sh ./cmd/kube-linter + @mkdir -p "$(GOBIN)" + @cp "bin/$(HOST_OS)/kube-linter" "$(GOBIN)/kube-linter" + @chmod u+w "$(GOBIN)/kube-linter" ########## ## Test ## diff --git a/get-tag b/get-tag new file mode 100755 index 000000000..711761e39 --- /dev/null +++ b/get-tag @@ -0,0 +1,7 @@ +#!/bin/sh + +if [ -n "${CIRCLE_TAG}" ]; then + echo "${CIRCLE_TAG}" +else + git describe --long --tags --abbrev=10 --dirty +fi diff --git a/internal/command/root/command.go b/internal/command/root/command.go index 7e6287a10..e758d2380 100644 --- a/internal/command/root/command.go +++ b/internal/command/root/command.go @@ -7,6 +7,7 @@ import ( "golang.stackrox.io/kube-linter/internal/command/checks" "golang.stackrox.io/kube-linter/internal/command/lint" "golang.stackrox.io/kube-linter/internal/command/templates" + "golang.stackrox.io/kube-linter/internal/command/version" ) // Command is the root command. @@ -20,6 +21,7 @@ func Command() *cobra.Command { checks.Command(), lint.Command(), templates.Command(), + version.Command(), ) return c } diff --git a/internal/command/version/command.go b/internal/command/version/command.go new file mode 100644 index 000000000..2f18395b8 --- /dev/null +++ b/internal/command/version/command.go @@ -0,0 +1,21 @@ +package version + +import ( + "fmt" + + "github.com/spf13/cobra" + "golang.stackrox.io/kube-linter/internal/version" +) + +// Command defines the version command +func Command() *cobra.Command { + c := &cobra.Command{ + Use: "version", + Short: "print version and exit", + Args: cobra.NoArgs, + Run: func(cmd *cobra.Command, _ []string) { + fmt.Println(version.Get()) + }, + } + return c +} diff --git a/internal/version/version.go b/internal/version/version.go new file mode 100644 index 000000000..a6aa2aa6a --- /dev/null +++ b/internal/version/version.go @@ -0,0 +1,14 @@ +package version + +import ( + "golang.stackrox.io/kube-linter/internal/stringutils" +) + +var ( + version string //XDef:VERSION +) + +// Get returns the version. +func Get() string { + return stringutils.OrDefault(version, "development") +} diff --git a/scripts/go-build.sh b/scripts/go-build.sh new file mode 100755 index 000000000..5c51428dd --- /dev/null +++ b/scripts/go-build.sh @@ -0,0 +1,59 @@ +#!/usr/bin/env bash + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +source "${SCRIPT_DIR}/utils.sh" + +set -eo pipefail + +main_srcdir="$1" +[[ -n "${main_srcdir}" ]] || die "Usage: $0 " + +x_defs=() +x_def_errors=() + +while read -r line || [[ -n "$line" ]]; do + if [[ "$line" =~ ^[[:space:]]*$ ]]; then + continue + elif [[ "$line" =~ ^([^[:space:]]+)[[:space:]]+(.*)[[:space:]]*$ ]]; then + var="${BASH_REMATCH[1]}" + def="${BASH_REMATCH[2]}" + eval "stamp_${var}=$(printf '%q' "$def")" + else + die "Malformed variable_stamps.sh output line ${line}" + fi +done < <("${SCRIPT_DIR}/variable_stamps.sh") + +while read -r line || [[ -n "$line" ]]; do + if [[ "$line" =~ ^[[:space:]]*$ ]]; then + continue + elif [[ "$line" =~ ^([^:]+):([[:digit:]]+):[[:space:]]*(var[[:space:]]+)?([^[:space:]]+)[[:space:]].*//XDef:([^[:space:]]+)[[:space:]]*$ ]]; then + go_file="${BASH_REMATCH[1]}" + go_line="${BASH_REMATCH[2]}" + go_var="${BASH_REMATCH[4]}" + stamp_var="${BASH_REMATCH[5]}" + + varname="stamp_${stamp_var}" + [[ -n "${!varname}" ]] || x_def_errors+=( + "Variable ${go_var} defined in ${go_file}:${go_line} references status var ${stamp_var} that is not part of the variable_stamps.sh output" + ) + go_package="$(cd "${SCRIPT_DIR}/.."; go list -e "./$(dirname "$go_file")")" + + x_defs+=(-X "\"${go_package}.${go_var}=${!varname}\"") + fi +done < <(git -C "${SCRIPT_DIR}/.." grep -n '//XDef:' -- '*.go') +if [[ "${#x_def_errors[@]}" -gt 0 ]]; then + printf >&2 "%s\n" "${x_def_errors[@]}" + exit 1 +fi + +ldflags=(-s -w "${x_defs[@]}") + +[[ -n "${GOOS}" ]] || die "GOOS must be set" +bin_name="$(basename "$main_srcdir")" +output_file="bin/${GOOS}/${bin_name}" +if [[ "$GOOS" == "windows" ]]; then + output_file="${output_file}.exe" +fi +mkdir -p "$(dirname "$output_file")" +echo >&2 "Compiling Go source in ${main_srcdir} to ${output_file}" +go build -ldflags="${ldflags[*]}" -o "${output_file}" "${main_srcdir}" diff --git a/scripts/utils.sh b/scripts/utils.sh new file mode 100644 index 000000000..baa9ce10b --- /dev/null +++ b/scripts/utils.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash + +function safe_tput() { + tput "$@" 2>/dev/null || true +} + +bold="$(safe_tput bold)" +reset="$(safe_tput sgr0)" +green="$(safe_tput setaf 2)" +yellow="$(safe_tput setaf 3)" +red="$(safe_tput setaf 1)" +black="$(safe_tput setaf 0; safe_tput setab 7)" + +function eecho() { + echo >&2 "$@" +} + +function einfo() { + eecho -en "${bold}${green}[INFO]${black} " + eecho -n "$@" + eecho -e "$reset" +} + +function ewarn() { + eecho -en "${bold}${yellow}[WARN]${black} " + eecho -n "$@" + eecho -e "$reset" +} + +function eerror() { + eecho -en "${bold}${red}[ERROR]${black} " + eecho -n "$@" + eecho -e "$reset" +} + +function efatal() { + eecho -en "${bold}${red}[FATAL]${black} " + eecho -n "$@" + eecho -e "$reset" +} + +function die() { + efatal "$@" + exit 1 +} diff --git a/scripts/variable_stamps.sh b/scripts/variable_stamps.sh new file mode 100755 index 000000000..0c198de7c --- /dev/null +++ b/scripts/variable_stamps.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +source "${SCRIPT_DIR}/utils.sh" + +set -euo pipefail + +gitroot="$(git rev-parse --show-toplevel)" + +[[ -n "${gitroot}" ]] || die "Could not determine git root" + +echo "VERSION $("${gitroot}/get-tag")"