diff --git a/.github/workflows/build-test.yaml b/.github/workflows/build-test.yaml index b2b3bd23..7d46e6aa 100644 --- a/.github/workflows/build-test.yaml +++ b/.github/workflows/build-test.yaml @@ -45,18 +45,16 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v4 # v3.5.3 + uses: actions/checkout@v4.2.2 + with: + fetch-depth: 0 - name: Setup Go uses: actions/setup-go@v5 with: go-version-file: go.mod - - name: Download and required packages - run: | - make deps - - name: Run all unit tests - run: make test + run: make test-unit - name: check test coverage uses: vladopajic/go-test-coverage@v2 diff --git a/.github/workflows/govulncheck.yaml b/.github/workflows/govulncheck.yaml new file mode 100644 index 00000000..882e6863 --- /dev/null +++ b/.github/workflows/govulncheck.yaml @@ -0,0 +1,35 @@ +# THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT. +# Edit https://github.com/cert-manager/makefile-modules/blob/main/modules/go/base/.github/workflows/govulncheck.yaml instead. + +# Run govulncheck at midnight every night on the main branch, +# to alert us to recent vulnerabilities which affect the Go code in this +# project. +name: govulncheck +on: + workflow_dispatch: {} + schedule: + - cron: '0 0 * * *' + +permissions: + contents: read + +jobs: + govulncheck: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + # Adding `fetch-depth: 0` makes sure tags are also fetched. We need + # the tags so `git describe` returns a valid version. + # see https://github.com/actions/checkout/issues/701 for extra info about this option + with: { fetch-depth: 0 } + + - id: go-version + run: | + make print-go-version >> "$GITHUB_OUTPUT" + + - uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0 + with: + go-version: ${{ steps.go-version.outputs.result }} + + - run: make verify-govulncheck diff --git a/.gitignore b/.gitignore index 5931a6c0..6de93b30 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ /bin coverage.out .debug +_bin +.vscode diff --git a/.golangci.yaml b/.golangci.yaml new file mode 100644 index 00000000..39538626 --- /dev/null +++ b/.golangci.yaml @@ -0,0 +1,85 @@ +version: "2" +linters: + default: none + exclusions: + generated: lax + presets: [comments, common-false-positives, legacy, std-error-handling] + rules: + - linters: + - gomoddirectives + - promlinter + text: .* + paths: [third_party$, builtin$, examples$] + warn-unused: true + settings: + staticcheck: + checks: ["all", "-ST1000", "-ST1001", "-ST1003", "-ST1005", "-ST1012", "-ST1016", "-ST1020", "-ST1021", "-ST1022", "-QF1001", "-QF1003", "-QF1008"] + enable: + - asasalint + - asciicheck + - bidichk + - bodyclose + - canonicalheader + - contextcheck + - copyloopvar + - decorder + - dogsled + - dupword + - durationcheck + - errcheck + - errchkjson + - errname + - exhaustive + - exptostd + - forbidigo + - ginkgolinter + - gocheckcompilerdirectives + - gochecksumtype + - gocritic + - goheader + - goprintffuncname + - gosec + - gosmopolitan + - govet + - grouper + - importas + - ineffassign + - interfacebloat + - intrange + - loggercheck + - makezero + - mirror + - misspell + - musttag + - nakedret + - nilerr + - nilnil + - noctx + - nosprintfhostport + - predeclared + - promlinter + - protogetter + - reassign + - sloglint + - staticcheck + - tagalign + - testableexamples + - unconvert + - unparam + - unused + - usestdlibvars + - usetesting + - wastedassign +formatters: + enable: [gci, gofmt] + settings: + gci: + sections: + - standard # Standard section: captures all standard packages. + - default # Default section: contains all imports that could not be matched to another section type. + - prefix(github.com/jetstack/version-checker) # Custom section: groups all imports with the specified Prefix. + - blank # Blank section: contains all blank imports. This section is not present unless explicitly enabled. + - dot # Dot section: contains all dot imports. This section is not present unless explicitly enabled. + exclusions: + generated: lax + paths: [third_party$, builtin$, examples$] diff --git a/Makefile b/Makefile index 4aa48283..6a1652d4 100644 --- a/Makefile +++ b/Makefile @@ -1,31 +1,116 @@ -BINDIR ?= $(CURDIR)/bin -ARCH ?= amd64 +# Copyright 2023 The cert-manager Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. -help: ## display this help - @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n\nTargets:\n"} /^[a-zA-Z0-9_-]+:.*?##/ { printf " \033[36m%-20s\033[0m %s\n", $$1, $$2 }' $(MAKEFILE_LIST) +# THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT. +# Edit https://github.com/cert-manager/makefile-modules/blob/main/modules/repository-base/base/Makefile instead. -.PHONY: help build image all clean +# NOTE FOR DEVELOPERS: "How do the Makefiles work and how can I extend them?" +# +# Shared Makefile logic lives in the make/_shared/ directory. The source of truth for these files +# lies outside of this repository, eg. in the cert-manager/makefile-modules repository. +# +# Logic specific to this repository must be defined in the make/00_mod.mk and make/02_mod.mk files: +# - The make/00_mod.mk file is included first and contains variable definitions needed by +# the shared Makefile logic. +# - The make/02_mod.mk file is included later, it can make use of most of the shared targets +# defined in the make/_shared/ directory (all targets defined in 00_mod.mk and 01_mod.mk). +# This file should be used to define targets specific to this repository. -deps: ## Download all Dependencies - go mod download +################################## -test: deps ## test version-checker - go test ./... -coverprofile=coverage.out +# Some modules build their dependencies from variables, we want these to be +# evaluated at the last possible moment. For this we use second expansion to +# re-evaluate the generate and verify targets a second time. +# +# See https://www.gnu.org/software/make/manual/html_node/Secondary-Expansion.html +.SECONDEXPANSION: -$(BINDIR): - mkdir -p $(BINDIR) +# For details on some of these "prelude" settings, see: +# https://clarkgrubb.com/makefile-style-guide +MAKEFLAGS += --warn-undefined-variables --no-builtin-rules +SHELL := /usr/bin/env bash +.SHELLFLAGS := -uo pipefail -c +.DEFAULT_GOAL := help +.DELETE_ON_ERROR: +.SUFFIXES: +FORCE: -build: deps $(BINDIR) ## build version-checker - CGO_ENABLED=0 go build -o ./bin/version-checker ./cmd/. +noop: # do nothing -verify: test build ## tests and builds version-checker +# Set empty value for MAKECMDGOALS to prevent the "warning: undefined variable 'MAKECMDGOALS'" +# warning from happening when running make without arguments +MAKECMDGOALS ?= -image: ## build docker image - GOARCH=$(ARCH) GOOS=linux CGO_ENABLED=0 go build -o ./bin/version-checker-linux ./cmd/. - docker build -t quay.io/jetstack/version-checker:v0.9.0 . +################################## +# Host OS and architecture setup # +################################## -clean: ## clean up created files - rm -rf \ - $(BINDIR) +# The reason we don't use "go env GOOS" or "go env GOARCH" is that the "go" +# binary may not be available in the PATH yet when the Makefiles are +# evaluated. HOST_OS and HOST_ARCH only support Linux, *BSD and macOS (M1 +# and Intel). +host_os := $(shell uname -s | tr A-Z a-z) +host_arch := $(shell uname -m) +HOST_OS ?= $(host_os) +HOST_ARCH ?= $(host_arch) -all: test build image ## runs test, build and image +ifeq (x86_64, $(HOST_ARCH)) + HOST_ARCH = amd64 +else ifeq (aarch64, $(HOST_ARCH)) + # linux reports the arm64 arch as aarch64 + HOST_ARCH = arm64 +endif + +################################## +# Git and versioning information # +################################## + +git_version := $(shell git describe --tags --always --match='v*' --abbrev=14 --dirty) +VERSION ?= $(git_version) +IS_PRERELEASE := $(shell git describe --tags --always --match='v*' --abbrev=0 | grep -q '-' && echo true || echo false) +GITCOMMIT := $(shell git rev-parse HEAD) +GITEPOCH := $(shell git show -s --format=%ct HEAD) + +################################## +# Global variables and dirs # +################################## + +bin_dir := _bin + +# The ARTIFACTS environment variable is set by the CI system to a directory +# where artifacts should be placed. These artifacts are then uploaded to a +# storage bucket by the CI system (https://docs.prow.k8s.io/docs/components/pod-utilities/). +# An example of such an artifact is a jUnit XML file containing test results. +# If the ARTIFACTS environment variable is not set, we default to a local +# directory in the _bin directory. +ARTIFACTS ?= $(bin_dir)/artifacts + +$(bin_dir) $(ARTIFACTS) $(bin_dir)/scratch: + mkdir -p $@ + +.PHONY: clean +## Clean all temporary files +## @category [shared] Tools +clean: + rm -rf $(bin_dir) + +################################## +# Include all the Makefiles # +################################## + +-include make/00_mod.mk +-include make/_shared/*/00_mod.mk +-include make/_shared/*/01_mod.mk +-include make/02_mod.mk +-include make/_shared/*/02_mod.mk diff --git a/deploy/charts/version-checker/templates/deployment.yaml b/deploy/charts/version-checker/templates/deployment.yaml index f483a0f8..6509335e 100644 --- a/deploy/charts/version-checker/templates/deployment.yaml +++ b/deploy/charts/version-checker/templates/deployment.yaml @@ -42,7 +42,6 @@ spec: ports: - name: metrics containerPort: 8080 - command: ["version-checker"] args: {{- include "version-checker.pod.args" . | nindent 8 }} resources: diff --git a/deploy/charts/version-checker/values.linter.exceptions b/deploy/charts/version-checker/values.linter.exceptions new file mode 100644 index 00000000..e69de29b diff --git a/go.mod b/go.mod index c9253b09..7afb8cee 100644 --- a/go.mod +++ b/go.mod @@ -40,11 +40,18 @@ require ( github.com/google/go-github/v70 v70.0.0 github.com/hashicorp/go-cleanhttp v0.5.2 github.com/jarcoal/httpmock v1.3.1 + github.com/onsi/ginkgo/v2 v2.22.0 + github.com/onsi/gomega v1.36.1 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/stretchr/testify v1.10.0 sigs.k8s.io/controller-runtime v0.20.4 ) +require ( + github.com/itchyny/timefmt-go v0.1.6 // indirect + golang.org/x/tools v0.29.0 // indirect +) + require ( github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect @@ -79,6 +86,7 @@ require ( github.com/go-openapi/jsonpointer v0.21.1 // indirect github.com/go-openapi/jsonreference v0.21.0 // indirect github.com/go-openapi/swag v0.23.1 // indirect + github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v4 v4.5.2 // indirect github.com/golang/protobuf v1.5.4 // indirect @@ -86,10 +94,12 @@ require ( github.com/google/gnostic-models v0.6.9 // indirect github.com/google/go-querystring v1.1.0 // indirect github.com/google/gofuzz v1.2.0 // indirect + github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/itchyny/gojq v0.12.16 github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.18.0 // indirect diff --git a/go.sum b/go.sum index 985fee26..fe38ec6a 100644 --- a/go.sum +++ b/go.sum @@ -148,6 +148,10 @@ github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISH github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/itchyny/gojq v0.12.16 h1:yLfgLxhIr/6sJNVmYfQjTIv0jGctu6/DgDoivmxTr7g= +github.com/itchyny/gojq v0.12.16/go.mod h1:6abHbdC2uB9ogMS38XsErnfqJ94UlngIJGlRAIj4jTM= +github.com/itchyny/timefmt-go v0.1.6 h1:ia3s54iciXDdzWzwaVKXZPbiXzxxnv1SPGFfM/myJ5Q= +github.com/itchyny/timefmt-go v0.1.6/go.mod h1:RRDZYC5s9ErkjQvTvvU7keJjxUYzIISJGxm9/mAERQg= github.com/jarcoal/httpmock v1.3.1 h1:iUx3whfZWVf3jT01hQTO/Eo5sAYtB2/rqaUuOtpInww= github.com/jarcoal/httpmock v1.3.1/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= diff --git a/klone.yaml b/klone.yaml new file mode 100644 index 00000000..b3cacda9 --- /dev/null +++ b/klone.yaml @@ -0,0 +1,59 @@ +# This klone.yaml file describes the Makefile modules and versions that are +# cloned into the "make/_shared" folder. These modules are dynamically imported +# by the root Makefile. The "make upgrade-klone" target can be used to pull +# the latest version from the upstream repositories (using the repo_ref value). +# +# More info can be found here: https://github.com/cert-manager/makefile-modules + +targets: + make/_shared: + - folder_name: generate-verify + repo_url: https://github.com/cert-manager/makefile-modules.git + repo_ref: main + repo_hash: 9c9f116938e082201a6bae0aedc7c7cf525fd8bf + repo_path: modules/generate-verify + - folder_name: go + repo_url: https://github.com/cert-manager/makefile-modules.git + repo_ref: main + repo_hash: 9c9f116938e082201a6bae0aedc7c7cf525fd8bf + repo_path: modules/go + - folder_name: helm + repo_url: https://github.com/cert-manager/makefile-modules.git + repo_ref: main + repo_hash: 9c9f116938e082201a6bae0aedc7c7cf525fd8bf + repo_path: modules/helm + - folder_name: help + repo_url: https://github.com/cert-manager/makefile-modules.git + repo_ref: main + repo_hash: 9c9f116938e082201a6bae0aedc7c7cf525fd8bf + repo_path: modules/help + - folder_name: kind + repo_url: https://github.com/cert-manager/makefile-modules.git + repo_ref: main + repo_hash: 9c9f116938e082201a6bae0aedc7c7cf525fd8bf + repo_path: modules/kind + - folder_name: klone + repo_url: https://github.com/cert-manager/makefile-modules.git + repo_ref: main + repo_hash: 9c9f116938e082201a6bae0aedc7c7cf525fd8bf + repo_path: modules/klone + - folder_name: oci-build + repo_url: https://github.com/cert-manager/makefile-modules.git + repo_ref: main + repo_hash: 9c9f116938e082201a6bae0aedc7c7cf525fd8bf + repo_path: modules/oci-build + - folder_name: oci-publish + repo_url: https://github.com/cert-manager/makefile-modules.git + repo_ref: main + repo_hash: 9c9f116938e082201a6bae0aedc7c7cf525fd8bf + repo_path: modules/oci-publish + - folder_name: repository-base + repo_url: https://github.com/cert-manager/makefile-modules.git + repo_ref: main + repo_hash: 9c9f116938e082201a6bae0aedc7c7cf525fd8bf + repo_path: modules/repository-base + - folder_name: tools + repo_url: https://github.com/cert-manager/makefile-modules.git + repo_ref: main + repo_hash: 9c9f116938e082201a6bae0aedc7c7cf525fd8bf + repo_path: modules/tools diff --git a/make/00_mod.mk b/make/00_mod.mk new file mode 100644 index 00000000..ce05887d --- /dev/null +++ b/make/00_mod.mk @@ -0,0 +1,58 @@ +# Copyright 2023 The cert-manager Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +repo_name := github.com/jetstack/version-checker + +kind_cluster_name := version-checker +kind_cluster_config := $(bin_dir)/scratch/kind_cluster.yaml + +build_names := manager + +go_manager_main_dir := ./cmd +go_manager_mod_dir := . +go_manager_ldflags := -X $(repo_name)/internal/version.AppVersion=$(VERSION) -X $(repo_name)/internal/version.GitCommit=$(GITCOMMIT) +oci_manager_base_image_flavor := static +oci_manager_image_name := quay.io/jetstack/version-checker +oci_manager_image_tag := $(VERSION) +oci_manager_image_name_development := version-checker.local/version-checker +oci_platforms := linux/amd64,linux/arm/v7,linux/arm64,linux/ppc64le,linux/s390x + +deploy_name := version-checker +deploy_namespace := version-checker + +helm_chart_source_dir := deploy/charts/version-checker +helm_chart_image_name := version-checker +helm_chart_version := $(VERSION) +helm_labels_template_name := version-checker.labels +helm_docs_use_helm_tool := 1 +helm_generate_schema := 1 +helm_verify_values := 1 + +golangci_lint_config := .golangci.yaml + +define helm_values_mutation_function +$(YQ) \ + '( .image.repository = "$(oci_manager_image_name)" ) | \ + ( .image.tag = "$(oci_manager_image_tag)" )' \ + $1 --inplace +endef + +images_amd64 ?= +images_arm64 ?= + +images_amd64 += docker.io/kong/httpbin:0.1.0@sha256:9d65a5b1955d2466762f53ea50eebae76be9dc7e277217cd8fb9a24b004154f4 +images_arm64 += docker.io/kong/httpbin:0.1.0@sha256:c546c8b06c542b615f053b577707cb72ddc875a0731d56d0ffaf840f767322ad + +images_amd64 += quay.io/curl/curl:8.5.0@sha256:e40a76dcfa9405678336774130411ca35beba85db426d5755b3cdd7b99d09a7a +images_arm64 += quay.io/curl/curl:8.5.0@sha256:038b0290c9e4a371aed4f9d6993e3548fcfa32b96e9a170bfc73f5da4ec2354d diff --git a/make/02_mod.mk b/make/02_mod.mk new file mode 100644 index 00000000..f13aa687 --- /dev/null +++ b/make/02_mod.mk @@ -0,0 +1,25 @@ +# Copyright 2023 The cert-manager Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +$(kind_cluster_config): make/config/kind/cluster.yaml | $(bin_dir)/scratch + cat $< | \ + sed -e 's|{{KIND_IMAGES}}|$(CURDIR)/$(images_tar_dir)|g' -e 's|{{KANIKO_DIR}}|$(CURDIR)/make/config/kaniko|g' \ + > $@ + +# cat $< | \ +# sed -e 's|{{KANIKO_DIR}}|$(CURDIR)/make/config/kaniko|g' \ +# > $@ + +include make/test-e2e.mk +include make/test-unit.mk diff --git a/make/_shared/generate-verify/00_mod.mk b/make/_shared/generate-verify/00_mod.mk new file mode 100644 index 00000000..43555138 --- /dev/null +++ b/make/_shared/generate-verify/00_mod.mk @@ -0,0 +1,18 @@ +# Copyright 2023 The cert-manager Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +shared_generate_targets ?= +shared_generate_targets_dirty ?= +shared_verify_targets ?= +shared_verify_targets_dirty ?= diff --git a/make/_shared/generate-verify/02_mod.mk b/make/_shared/generate-verify/02_mod.mk new file mode 100644 index 00000000..f0677298 --- /dev/null +++ b/make/_shared/generate-verify/02_mod.mk @@ -0,0 +1,39 @@ +# Copyright 2023 The cert-manager Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +.PHONY: generate +## Generate all generate targets. +## @category [shared] Generate/ Verify +generate: $$(shared_generate_targets) + @echo "The following targets cannot be run simultaneously with each other or other generate scripts:" + $(foreach TARGET,$(shared_generate_targets_dirty), $(MAKE) $(TARGET)) + +verify_script := $(dir $(lastword $(MAKEFILE_LIST)))/util/verify.sh + +# Run the supplied make target argument in a temporary workspace and diff the results. +verify-%: FORCE + +$(verify_script) $(MAKE) $* + +verify_generated_targets = $(shared_generate_targets:%=verify-%) +verify_generated_targets_dirty = $(shared_generate_targets_dirty:%=verify-%) + +verify_targets = $(sort $(verify_generated_targets) $(shared_verify_targets)) +verify_targets_dirty = $(sort $(verify_generated_targets_dirty) $(shared_verify_targets_dirty)) + +.PHONY: verify +## Verify code and generate targets. +## @category [shared] Generate/ Verify +verify: $$(verify_targets) + @echo "The following targets create temporary files in the current directory, that is why they have to be run last:" + $(foreach TARGET,$(verify_targets_dirty), $(MAKE) $(TARGET)) diff --git a/make/_shared/generate-verify/util/verify.sh b/make/_shared/generate-verify/util/verify.sh new file mode 100755 index 00000000..feb53bfe --- /dev/null +++ b/make/_shared/generate-verify/util/verify.sh @@ -0,0 +1,73 @@ +#!/usr/bin/env bash + +# Copyright 2023 The cert-manager Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Verify that the supplied command does not make any changes to the repository. +# +# This is called from the Makefile to verify that all code generation scripts +# have been run and that their changes have been committed to the repository. +# +# Runs any of the scripts or Make targets in this repository, after making a +# copy of the repository, then reports any changes to the files in the copy. + +# For example: +# +# make verify-helm-chart-update || \ +# make helm-chart-update +# +set -o errexit +set -o nounset +set -o pipefail + +projectdir="$( cd "$( dirname "${BASH_SOURCE[0]}" )/../../../.." && pwd )" + +cd "${projectdir}" + +# Use short form arguments here to support BSD/macOS. `-d` instructs +# it to make a directory, `-t` provides a prefix to use for the directory name. +tmp="$(mktemp -d /tmp/verify.sh.XXXXXXXX)" + +cleanup() { + rm -rf "${tmp}" +} +trap "cleanup" EXIT SIGINT + +# Why not just "cp" to the tmp dir? +# A dumb "cp" will fail sometimes since _bin can get changed while it's being copied if targets are run in parallel, +# and cp doesn't have some universal "exclude" option to ignore "_bin" +# +# We previously used "rsync" here, but: +# 1. That's another tool we need to depend on +# 2. rsync on macOS 15.4 and newer is actually openrsync, which has different permissions and throws errors when copying git objects +# +# So, we use find to list all files except _bin, and then copy each in turn +find . -maxdepth 1 -not \( -path "./_bin" \) -not \( -path "." \) | xargs -I% cp -af "${projectdir}/%" "${tmp}/" + +pushd "${tmp}" >/dev/null + +"$@" + +popd >/dev/null + +if ! diff \ + --exclude=".git" \ + --exclude="_bin" \ + --new-file --unified --show-c-function --recursive "${projectdir}" "${tmp}" +then + echo + echo "Project '${projectdir}' is out of date." + echo "Please run '${*}' or apply the above diffs" + exit 1 +fi diff --git a/make/_shared/go/.golangci.override.yaml b/make/_shared/go/.golangci.override.yaml new file mode 100644 index 00000000..f787d6a4 --- /dev/null +++ b/make/_shared/go/.golangci.override.yaml @@ -0,0 +1,80 @@ +version: "2" +linters: + default: none + exclusions: + generated: lax + presets: [ comments, common-false-positives, legacy, std-error-handling ] + paths: [ third_party$, builtin$, examples$ ] + warn-unused: true + settings: + staticcheck: + checks: [ "all", "-ST1000", "-ST1001", "-ST1003", "-ST1005", "-ST1012", "-ST1016", "-ST1020", "-ST1021", "-ST1022", "-QF1001", "-QF1003", "-QF1008" ] + enable: + - asasalint + - asciicheck + - bidichk + - bodyclose + - canonicalheader + - contextcheck + - copyloopvar + - decorder + - dogsled + - dupword + - durationcheck + - errcheck + - errchkjson + - errname + - exhaustive + - exptostd + - forbidigo + - ginkgolinter + - gocheckcompilerdirectives + - gochecksumtype + - gocritic + - goheader + - goprintffuncname + - gosec + - gosmopolitan + - govet + - grouper + - importas + - ineffassign + - interfacebloat + - intrange + - loggercheck + - makezero + - mirror + - misspell + - musttag + - nakedret + - nilerr + - nilnil + - noctx + - nosprintfhostport + - predeclared + - promlinter + - protogetter + - reassign + - sloglint + - staticcheck + - tagalign + - testableexamples + - unconvert + - unparam + - unused + - usestdlibvars + - usetesting + - wastedassign +formatters: + enable: [ gci, gofmt ] + settings: + gci: + sections: + - standard # Standard section: captures all standard packages. + - default # Default section: contains all imports that could not be matched to another section type. + - prefix({{REPO-NAME}}) # Custom section: groups all imports with the specified Prefix. + - blank # Blank section: contains all blank imports. This section is not present unless explicitly enabled. + - dot # Dot section: contains all dot imports. This section is not present unless explicitly enabled. + exclusions: + generated: lax + paths: [ third_party$, builtin$, examples$ ] diff --git a/make/_shared/go/01_mod.mk b/make/_shared/go/01_mod.mk new file mode 100644 index 00000000..81681ddd --- /dev/null +++ b/make/_shared/go/01_mod.mk @@ -0,0 +1,150 @@ +# Copyright 2023 The cert-manager Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +ifndef bin_dir +$(error bin_dir is not set) +endif + +ifndef repo_name +$(error repo_name is not set) +endif + +golangci_lint_override := $(dir $(lastword $(MAKEFILE_LIST)))/.golangci.override.yaml + +.PHONY: go-workspace +go-workspace: export GOWORK?=$(abspath go.work) +## Create a go.work file in the repository root (or GOWORK) +## +## @category Development +go-workspace: | $(NEEDS_GO) + @rm -f $(GOWORK) + $(GO) work init + @find . -name go.mod -not \( -path "./$(bin_dir)/*" -or -path "./make/_shared/*" \) \ + | while read d; do \ + target=$$(dirname $${d}); \ + $(GO) work use "$${target}"; \ + done + +.PHONY: go-tidy +## Alias for `make generate-go-mod-tidy` +## @category [shared] Generate/ Verify +go-tidy: generate-go-mod-tidy + +.PHONY: generate-go-mod-tidy +## Run `go mod tidy` on all Go modules +## @category [shared] Generate/ Verify +generate-go-mod-tidy: | $(NEEDS_GO) + @find . -name go.mod -not \( -path "./$(bin_dir)/*" -or -path "./make/_shared/*" \) \ + | while read d; do \ + target=$$(dirname $${d}); \ + echo "Running 'go mod tidy' in directory '$${target}'"; \ + pushd "$${target}" >/dev/null; \ + $(GO) mod tidy || exit; \ + popd >/dev/null; \ + echo ""; \ + done + +shared_generate_targets += generate-go-mod-tidy + +default_govulncheck_generate_base_dir := $(dir $(lastword $(MAKEFILE_LIST)))/base/ +# The base directory used to copy the govulncheck GH action from. This can be +# overwritten with an action with extra authentication or with a totally different +# pipeline (eg. a GitLab pipeline). +govulncheck_generate_base_dir ?= $(default_govulncheck_generate_base_dir) + +.PHONY: generate-govulncheck +## Generate base files in the repository +## @category [shared] Generate/ Verify +generate-govulncheck: + cp -r $(govulncheck_generate_base_dir)/. ./ + +shared_generate_targets += generate-govulncheck + +.PHONY: verify-govulncheck +## Verify all Go modules for vulnerabilities using govulncheck +## @category [shared] Generate/ Verify +# +# Runs `govulncheck` on all Go modules related to the project. +# Ignores Go modules among the temporary build artifacts in _bin, to avoid +# scanning the code of the vendored Go, after running make vendor-go. +# Ignores Go modules in make/_shared, because those will be checked in centrally +# in the makefile_modules repository. +# +# `verify-govulncheck` not added to the `shared_verify_targets` variable and is +# not run by `make verify`, because `make verify` is run for each PR, and we do +# not want new vulnerabilities in existing code to block the merging of PRs. +# Instead `make verify-govulncheck` is intended to be run periodically by a CI job. +verify-govulncheck: | $(NEEDS_GOVULNCHECK) + @find . -name go.mod -not \( -path "./$(bin_dir)/*" -or -path "./make/_shared/*" \) \ + | while read d; do \ + target=$$(dirname $${d}); \ + echo "Running 'GOTOOLCHAIN=go$(VENDORED_GO_VERSION) $(bin_dir)/tools/govulncheck ./...' in directory '$${target}'"; \ + pushd "$${target}" >/dev/null; \ + GOTOOLCHAIN=go$(VENDORED_GO_VERSION) $(GOVULNCHECK) ./... || exit; \ + popd >/dev/null; \ + echo ""; \ + done + +ifdef golangci_lint_config + +.PHONY: generate-golangci-lint-config +## Generate a golangci-lint configuration file +## @category [shared] Generate/ Verify +generate-golangci-lint-config: | $(NEEDS_GOLANGCI-LINT) $(NEEDS_YQ) $(bin_dir)/scratch + if [ "$$($(YQ) eval 'has("version") | not' $(golangci_lint_config))" == "true" ]; then \ + $(GOLANGCI-LINT) migrate -c $(golangci_lint_config); \ + rm $(basename $(golangci_lint_config)).bck$(suffix $(golangci_lint_config)); \ + fi + + cp $(golangci_lint_config) $(bin_dir)/scratch/golangci-lint.yaml.tmp + $(YQ) -i 'del(.linters.enable)' $(bin_dir)/scratch/golangci-lint.yaml.tmp + $(YQ) eval-all -i '. as $$item ireduce ({}; . * $$item)' $(bin_dir)/scratch/golangci-lint.yaml.tmp $(golangci_lint_override) + $(YQ) -i '(.. | select(tag == "!!str")) |= sub("{{REPO-NAME}}", "$(repo_name)")' $(bin_dir)/scratch/golangci-lint.yaml.tmp + mv $(bin_dir)/scratch/golangci-lint.yaml.tmp $(golangci_lint_config) + +shared_generate_targets += generate-golangci-lint-config + +golangci_lint_timeout ?= 10m + +.PHONY: verify-golangci-lint +## Verify all Go modules using golangci-lint +## @category [shared] Generate/ Verify +verify-golangci-lint: | $(NEEDS_GO) $(NEEDS_GOLANGCI-LINT) $(NEEDS_YQ) $(bin_dir)/scratch + @find . -name go.mod -not \( -path "./$(bin_dir)/*" -or -path "./make/_shared/*" \) \ + | while read d; do \ + target=$$(dirname $${d}); \ + echo "Running 'GOVERSION=$(VENDORED_GO_VERSION) $(bin_dir)/tools/golangci-lint run -c $(CURDIR)/$(golangci_lint_config) --timeout $(golangci_lint_timeout)' in directory '$${target}'"; \ + pushd "$${target}" >/dev/null; \ + GOVERSION=$(VENDORED_GO_VERSION) $(GOLANGCI-LINT) run -c $(CURDIR)/$(golangci_lint_config) --timeout $(golangci_lint_timeout) || exit; \ + popd >/dev/null; \ + echo ""; \ + done + +shared_verify_targets_dirty += verify-golangci-lint + +.PHONY: fix-golangci-lint +## Fix all Go modules using golangci-lint +## @category [shared] Generate/ Verify +fix-golangci-lint: | $(NEEDS_GOLANGCI-LINT) $(NEEDS_YQ) $(NEEDS_GCI) $(bin_dir)/scratch + @find . -name go.mod -not \( -path "./$(bin_dir)/*" -or -path "./make/_shared/*" \) \ + | while read d; do \ + target=$$(dirname $${d}); \ + echo "Running 'GOVERSION=$(VENDORED_GO_VERSION) $(bin_dir)/tools/golangci-lint fmt -c $(CURDIR)/$(golangci_lint_config)' in directory '$${target}'"; \ + pushd "$${target}" >/dev/null; \ + GOVERSION=$(VENDORED_GO_VERSION) $(GOLANGCI-LINT) fmt -c $(CURDIR)/$(golangci_lint_config) || exit; \ + popd >/dev/null; \ + echo ""; \ + done + +endif diff --git a/make/_shared/go/README.md b/make/_shared/go/README.md new file mode 100644 index 00000000..ad1962ba --- /dev/null +++ b/make/_shared/go/README.md @@ -0,0 +1,3 @@ +# README + +A module for various Go static checks. diff --git a/make/_shared/go/base/.github/workflows/govulncheck.yaml b/make/_shared/go/base/.github/workflows/govulncheck.yaml new file mode 100644 index 00000000..882e6863 --- /dev/null +++ b/make/_shared/go/base/.github/workflows/govulncheck.yaml @@ -0,0 +1,35 @@ +# THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT. +# Edit https://github.com/cert-manager/makefile-modules/blob/main/modules/go/base/.github/workflows/govulncheck.yaml instead. + +# Run govulncheck at midnight every night on the main branch, +# to alert us to recent vulnerabilities which affect the Go code in this +# project. +name: govulncheck +on: + workflow_dispatch: {} + schedule: + - cron: '0 0 * * *' + +permissions: + contents: read + +jobs: + govulncheck: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + # Adding `fetch-depth: 0` makes sure tags are also fetched. We need + # the tags so `git describe` returns a valid version. + # see https://github.com/actions/checkout/issues/701 for extra info about this option + with: { fetch-depth: 0 } + + - id: go-version + run: | + make print-go-version >> "$GITHUB_OUTPUT" + + - uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0 + with: + go-version: ${{ steps.go-version.outputs.result }} + + - run: make verify-govulncheck diff --git a/make/_shared/helm/01_mod.mk b/make/_shared/helm/01_mod.mk new file mode 100644 index 00000000..45ed301b --- /dev/null +++ b/make/_shared/helm/01_mod.mk @@ -0,0 +1,20 @@ +# Copyright 2023 The cert-manager Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +ifndef helm_dont_include_crds +include $(dir $(lastword $(MAKEFILE_LIST)))/crds.mk +endif + +include $(dir $(lastword $(MAKEFILE_LIST)))/helm.mk +include $(dir $(lastword $(MAKEFILE_LIST)))/deploy.mk diff --git a/make/_shared/helm/crd.template.footer.yaml b/make/_shared/helm/crd.template.footer.yaml new file mode 100644 index 00000000..0a67617f --- /dev/null +++ b/make/_shared/helm/crd.template.footer.yaml @@ -0,0 +1 @@ +{{- end }} \ No newline at end of file diff --git a/make/_shared/helm/crd.template.header.yaml b/make/_shared/helm/crd.template.header.yaml new file mode 100644 index 00000000..663d7126 --- /dev/null +++ b/make/_shared/helm/crd.template.header.yaml @@ -0,0 +1,11 @@ +{{- if .Values.crds.enabled }} +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: "REPLACE_CRD_NAME" + {{- if .Values.crds.keep }} + annotations: + helm.sh/resource-policy: keep + {{- end }} + labels: + {{- include "REPLACE_LABELS_TEMPLATE" . | nindent 4 }} \ No newline at end of file diff --git a/make/_shared/helm/crds.mk b/make/_shared/helm/crds.mk new file mode 100644 index 00000000..247e3619 --- /dev/null +++ b/make/_shared/helm/crds.mk @@ -0,0 +1,74 @@ +# Copyright 2023 The cert-manager Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +################ +# Check Inputs # +################ + +ifndef helm_chart_source_dir +$(error helm_chart_source_dir is not set) +endif + +ifndef helm_labels_template_name +$(error helm_labels_template_name is not set) +endif + +################ +# Add targets # +################ + +crd_template_header := $(dir $(lastword $(MAKEFILE_LIST)))/crd.template.header.yaml +crd_template_footer := $(dir $(lastword $(MAKEFILE_LIST)))/crd.template.footer.yaml + +# see https://stackoverflow.com/a/53408233 +sed_inplace := sed -i'' +ifeq ($(HOST_OS),darwin) + sed_inplace := sed -i '' +endif + +crds_dir ?= deploy/crds +crds_dir_readme := $(dir $(lastword $(MAKEFILE_LIST)))/crds_dir.README.md + +.PHONY: generate-crds +## Generate CRD manifests. +## @category [shared] Generate/ Verify +generate-crds: | $(NEEDS_CONTROLLER-GEN) $(NEEDS_YQ) + $(eval crds_gen_temp := $(bin_dir)/scratch/crds) + $(eval directories := $(shell ls -d */ | grep -v -e 'make' $(shell git check-ignore -- * | sed 's/^/-e /'))) + + rm -rf $(crds_gen_temp) + mkdir -p $(crds_gen_temp) + + $(CONTROLLER-GEN) crd \ + $(directories:%=paths=./%...) \ + output:crd:artifacts:config=$(crds_gen_temp) + + @echo "Updating CRDs with helm templating, writing to $(helm_chart_source_dir)/templates" + + @for i in $$(ls $(crds_gen_temp)); do \ + crd_name=$$($(YQ) eval '.metadata.name' $(crds_gen_temp)/$$i); \ + cat $(crd_template_header) > $(helm_chart_source_dir)/templates/crd-$$i; \ + echo "" >> $(helm_chart_source_dir)/templates/crd-$$i; \ + $(sed_inplace) "s/REPLACE_CRD_NAME/$$crd_name/g" $(helm_chart_source_dir)/templates/crd-$$i; \ + $(sed_inplace) "s/REPLACE_LABELS_TEMPLATE/$(helm_labels_template_name)/g" $(helm_chart_source_dir)/templates/crd-$$i; \ + $(YQ) -I2 '{"spec": .spec}' $(crds_gen_temp)/$$i >> $(helm_chart_source_dir)/templates/crd-$$i; \ + cat $(crd_template_footer) >> $(helm_chart_source_dir)/templates/crd-$$i; \ + done + + @if [ -n "$$(ls $(crds_gen_temp) 2>/dev/null)" ]; then \ + cp $(crds_gen_temp)/* $(crds_dir)/ ; \ + cp $(crds_dir_readme) $(crds_dir)/README.md ; \ + fi + +shared_generate_targets += generate-crds diff --git a/make/_shared/helm/crds_dir.README.md b/make/_shared/helm/crds_dir.README.md new file mode 100644 index 00000000..fba79fed --- /dev/null +++ b/make/_shared/helm/crds_dir.README.md @@ -0,0 +1,8 @@ +# CRDs source directory + +> **WARNING**: if you are an end-user, you probably should NOT need to use the +> files in this directory. These files are for **reference, development and testing purposes only**. + +This directory contains 'source code' used to build our CustomResourceDefinition +resources consumed by our officially supported deployment methods (e.g. the Helm chart). +The CRDs in this directory might be incomplete, and should **NOT** be used to provision the operator. \ No newline at end of file diff --git a/make/_shared/helm/deploy.mk b/make/_shared/helm/deploy.mk new file mode 100644 index 00000000..8bc6ebb4 --- /dev/null +++ b/make/_shared/helm/deploy.mk @@ -0,0 +1,54 @@ +# Copyright 2023 The cert-manager Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +ifndef deploy_name +$(error deploy_name is not set) +endif + +ifndef deploy_namespace +$(error deploy_namespace is not set) +endif + +# Install options allows the user configuration of extra flags +INSTALL_OPTIONS ?= + +########################################## + +.PHONY: install +## Install controller helm chart on the current active K8S cluster. +## @category [shared] Deployment +install: $(helm_chart_archive) | $(NEEDS_HELM) + $(HELM) upgrade $(deploy_name) $(helm_chart_archive) \ + --wait \ + --install \ + --create-namespace \ + $(INSTALL_OPTIONS) \ + --namespace $(deploy_namespace) + +.PHONY: uninstall +## Uninstall controller helm chart from the current active K8S cluster. +## @category [shared] Deployment +uninstall: | $(NEEDS_HELM) + $(HELM) uninstall $(deploy_name) \ + --wait \ + --namespace $(deploy_namespace) + +.PHONY: template +## Template the helm chart. +## @category [shared] Deployment +template: $(helm_chart_archive) | $(NEEDS_HELM) + @$(HELM) template $(deploy_name) $(helm_chart_archive) \ + --create-namespace \ + $(INSTALL_OPTIONS) \ + --namespace $(deploy_namespace) diff --git a/make/_shared/helm/helm.mk b/make/_shared/helm/helm.mk new file mode 100644 index 00000000..1e9dd0a1 --- /dev/null +++ b/make/_shared/helm/helm.mk @@ -0,0 +1,180 @@ +# Copyright 2023 The cert-manager Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +ifndef bin_dir +$(error bin_dir is not set) +endif + +ifndef repo_name +$(error repo_name is not set) +endif + +ifndef helm_chart_source_dir +$(error helm_chart_source_dir is not set) +endif + +ifndef helm_chart_image_name +$(error helm_chart_image_name is not set) +endif + +ifndef helm_chart_version +$(error helm_chart_version is not set) +endif +ifneq ($(helm_chart_version:v%=v),v) +$(error helm_chart_version "$(helm_chart_version)" should start with a "v") +endif + +ifndef helm_values_mutation_function +$(error helm_values_mutation_function is not set) +endif + +########################################## + +helm_chart_name := $(notdir $(helm_chart_image_name)) +helm_chart_image_registry := $(dir $(helm_chart_image_name)) +helm_chart_image_tag := $(helm_chart_version) +helm_chart_sources := $(shell find $(helm_chart_source_dir) -maxdepth 1 -type f) $(shell find $(helm_chart_source_dir)/templates -type f) +helm_chart_archive := $(bin_dir)/scratch/helm/$(helm_chart_name)-$(helm_chart_version).tgz +helm_digest_path := $(bin_dir)/scratch/helm/$(helm_chart_name)-$(helm_chart_version).digests +helm_digest = $(shell head -1 $(helm_digest_path) 2> /dev/null) + +$(bin_dir)/scratch/helm: + @mkdir -p $@ + +$(helm_chart_archive): $(helm_chart_sources) | $(NEEDS_HELM) $(NEEDS_YQ) $(bin_dir)/scratch/helm + $(eval helm_chart_source_dir_versioned := $@.tmp) + rm -rf $(helm_chart_source_dir_versioned) + mkdir -p $(dir $(helm_chart_source_dir_versioned)) + cp -a $(helm_chart_source_dir) $(helm_chart_source_dir_versioned) + + $(call helm_values_mutation_function,$(helm_chart_source_dir_versioned)/values.yaml) + + @if ! $(YQ) -oy '.name' $(helm_chart_source_dir_versioned)/Chart.yaml | grep -q '^$(helm_chart_name)$$'; then \ + echo "Chart name does not match the name in the helm_chart_name variable"; \ + exit 1; \ + fi + + $(YQ) '.annotations."artifacthub.io/prerelease" = "$(IS_PRERELEASE)"' \ + --inplace $(helm_chart_source_dir_versioned)/Chart.yaml + + mkdir -p $(dir $@) + $(HELM) package $(helm_chart_source_dir_versioned) \ + --app-version $(helm_chart_version) \ + --version $(helm_chart_version) \ + --destination $(dir $@) + +.PHONY: helm-chart-oci-push +## Create and push Helm chart to OCI registry. +## Will also create a non-v-prefixed tag for the OCI image. +## @category [shared] Publish +helm-chart-oci-push: $(helm_chart_archive) | $(NEEDS_HELM) $(NEEDS_CRANE) + $(HELM) push "$(helm_chart_archive)" "oci://$(helm_chart_image_registry)" 2>&1 \ + | tee >(grep -o "sha256:.\+" | tee $(helm_digest_path)) + + @# $(helm_chart_image_tag:v%=%) removes the v prefix from the value stored in helm_chart_image_tag. + @# See https://www.gnu.org/software/make/manual/html_node/Substitution-Refs.html for the manual on the syntax. + helm_digest=$$(cat $(helm_digest_path)) && \ + $(CRANE) copy "$(helm_chart_image_name)@$$helm_digest" "$(helm_chart_image_name):$(helm_chart_image_tag:v%=%)" + +.PHONY: helm-chart +## Create a helm chart +## @category [shared] Helm Chart +helm-chart: $(helm_chart_archive) + +helm_tool_header_search ?= ^ +helm_tool_footer_search ?= ^ + +.PHONY: generate-helm-docs +## Generate Helm chart documentation. +## @category [shared] Generate/ Verify +generate-helm-docs: | $(NEEDS_HELM-TOOL) + $(HELM-TOOL) inject -i $(helm_chart_source_dir)/values.yaml -o $(helm_chart_source_dir)/README.md --header-search "$(helm_tool_header_search)" --footer-search "$(helm_tool_footer_search)" + +shared_generate_targets += generate-helm-docs + +.PHONY: generate-helm-schema +## Generate Helm chart schema. +## @category [shared] Generate/ Verify +generate-helm-schema: | $(NEEDS_HELM-TOOL) $(NEEDS_GOJQ) + $(HELM-TOOL) schema -i $(helm_chart_source_dir)/values.yaml | $(GOJQ) > $(helm_chart_source_dir)/values.schema.json + +shared_generate_targets += generate-helm-schema + +.PHONY: verify-helm-values +## Verify Helm chart values using helm-tool. +## @category [shared] Generate/ Verify +verify-helm-values: | $(NEEDS_HELM-TOOL) $(NEEDS_GOJQ) + $(HELM-TOOL) lint -i $(helm_chart_source_dir)/values.yaml -d $(helm_chart_source_dir)/templates -e $(helm_chart_source_dir)/values.linter.exceptions + +shared_verify_targets += verify-helm-values + +$(bin_dir)/scratch/kyverno: + @mkdir -p $@ + +$(bin_dir)/scratch/kyverno/pod-security-policy.yaml: | $(NEEDS_KUSTOMIZE) $(bin_dir)/scratch/kyverno + @$(KUSTOMIZE) build https://github.com/kyverno/policies/pod-security/enforce > $@ + +# Extra arguments for kyverno apply. +kyverno_apply_extra_args := +# Allows known policy violations to be skipped by supplying Kyverno policy +# exceptions as a Kyverno YAML resource, e.g.: +# apiVersion: kyverno.io/v2 +# kind: PolicyException +# metadata: +# name: pod-security-exceptions +# spec: +# exceptions: +# - policyName: disallow-privilege-escalation +# ruleNames: +# - autogen-privilege-escalation +# - policyName: restrict-seccomp-strict +# ruleNames: +# - autogen-check-seccomp-strict +# match: +# any: +# - resources: +# kinds: +# - Deployment +# namespaces: +# - mynamespace +# names: +# - my-deployment +ifneq ("$(wildcard make/verify-pod-security-standards-exceptions.yaml)","") + kyverno_apply_extra_args += --exceptions make/verify-pod-security-standards-exceptions.yaml +endif + +.PHONY: verify-pod-security-standards +## Verify that the Helm chart complies with the pod security standards. +## +## You can add Kyverno policy exceptions to +## `make/verify-pod-security-standards-exceptions.yaml`, to skip some of the pod +## security policy rules. +## +## @category [shared] Generate/ Verify +verify-pod-security-standards: $(helm_chart_archive) $(bin_dir)/scratch/kyverno/pod-security-policy.yaml | $(NEEDS_KYVERNO) $(NEEDS_HELM) + $(HELM) template $(helm_chart_archive) $(INSTALL_OPTIONS) \ + | $(KYVERNO) apply $(bin_dir)/scratch/kyverno/pod-security-policy.yaml \ + $(kyverno_apply_extra_args) \ + --resource - \ + --table + +shared_verify_targets_dirty += verify-pod-security-standards + +.PHONY: verify-helm-lint +## Verify that the Helm chart is linted. +## @category [shared] Generate/ Verify +verify-helm-lint: $(helm_chart_archive) | $(NEEDS_HELM) + $(HELM) lint $(helm_chart_archive) + +shared_verify_targets_dirty += verify-helm-lint \ No newline at end of file diff --git a/make/_shared/help/01_mod.mk b/make/_shared/help/01_mod.mk new file mode 100644 index 00000000..1a6a3b48 --- /dev/null +++ b/make/_shared/help/01_mod.mk @@ -0,0 +1,22 @@ +# Copyright 2023 The cert-manager Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +help_sh := $(dir $(lastword $(MAKEFILE_LIST)))/help.sh + +.PHONY: help +help: + @MAKEFILE_LIST="$(MAKEFILE_LIST)" \ + MAKE="$(MAKE)" \ + $(help_sh) diff --git a/make/_shared/help/help.sh b/make/_shared/help/help.sh new file mode 100755 index 00000000..400aab3d --- /dev/null +++ b/make/_shared/help/help.sh @@ -0,0 +1,115 @@ +#!/usr/bin/env bash + +# Copyright 2023 The cert-manager Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -o errexit +set -o nounset +set -o pipefail + +## 1. Build set of extracted line items + +EMPTYLINE_REGEX="^[[:space:]]*$" +DOCBLOCK_REGEX="^##[[:space:]]*(.*)$" +CATEGORY_REGEX="^##[[:space:]]*@category[[:space:]]*(.*)$" +TARGET_REGEX="^(([a-zA-Z0-9\_\/\%\$\(\)]|-)+):.*$" + +EMPTY_ITEM="" + +# shellcheck disable=SC2086 +raw_lines=$(cat ${MAKEFILE_LIST} | tr '\t' ' ' | grep -E "($TARGET_REGEX|$DOCBLOCK_REGEX|$EMPTYLINE_REGEX)") +extracted_lines="" +extracted_current="$EMPTY_ITEM" +max_target_length=0 + +## Extract all the commented targets from the Makefile +while read -r line; do + if [[ $line =~ $EMPTYLINE_REGEX ]]; then + # Reset current item. + extracted_current="$EMPTY_ITEM" + elif [[ $line =~ $CATEGORY_REGEX ]]; then + extracted_current=${extracted_current///${BASH_REMATCH[1]}} + elif [[ $line =~ $TARGET_REGEX ]]; then + # only keep the target if there is a comment + if [[ $extracted_current != *""* ]]; then + max_target_length=$(( ${#BASH_REMATCH[1]} > max_target_length ? ${#BASH_REMATCH[1]} : max_target_length )) + extracted_current=${extracted_current///${BASH_REMATCH[1]}} + extracted_lines="$extracted_lines\n$extracted_current" + fi + + extracted_current="$EMPTY_ITEM" + elif [[ $line =~ $DOCBLOCK_REGEX ]]; then + extracted_current=${extracted_current///${BASH_REMATCH[1]}} + fi +done <<< "$raw_lines" + +## 2. Build mapping for expanding targets + +ASSIGNMENT_REGEX="^(([a-zA-Z0-9\_\/\%\$\(\)]|-)+)[[:space:]]*:=[[:space:]]*(.*)$" + +raw_expansions=$(${MAKE} --dry-run --print-data-base noop | tr '\t' ' ' | grep -E "$ASSIGNMENT_REGEX") +extracted_expansions="" + +while read -r line; do + if [[ $line =~ $ASSIGNMENT_REGEX ]]; then + target=${BASH_REMATCH[1]} + expansion=${BASH_REMATCH[3]// /, } + extracted_expansions="$extracted_expansions\n$target$expansion" + fi +done <<< "$raw_expansions" + +## 3. Sort and print the extracted line items + +RULE_COLOR="$(TERM=xterm tput setaf 6)" +CATEGORY_COLOR="$(TERM=xterm tput setaf 3)" +CLEAR_STYLE="$(TERM=xterm tput sgr0)" +PURPLE=$(TERM=xterm tput setaf 125) + +extracted_lines=$(echo -e "$extracted_lines" | LC_ALL=C sort -r) +current_category="" + +## Print the help +echo "Usage: make [target1] [target2] ..." + +IFS=$'\n'; for line in $extracted_lines; do + category=$([[ $line =~ \(.*)\ ]] && echo "${BASH_REMATCH[1]}") + target=$([[ $line =~ \(.*)\ ]] && echo "${BASH_REMATCH[1]}") + comment=$([[ $line =~ \(.*)\ ]] && echo -e "${BASH_REMATCH[1]///\\n}") + + # Print the category header if it's changed + if [[ "$current_category" != "$category" ]]; then + current_category=$category + echo -e "\n${CATEGORY_COLOR}${current_category}${CLEAR_STYLE}" + fi + + # replace any $(...) with the actual value + if [[ $target =~ \$\((.*)\) ]]; then + new_target=$(echo -e "$extracted_expansions" | grep "${BASH_REMATCH[1]}" || true) + if [[ -n "$new_target" ]]; then + target=$([[ $new_target =~ \(.*)\ ]] && echo -e "${BASH_REMATCH[1]}") + fi + fi + + # Print the target and its multiline comment + is_first_line=true + while read -r comment_line; do + if [[ "$is_first_line" == true ]]; then + is_first_line=false + padding=$(( max_target_length - ${#target} )) + printf " %s%${padding}s ${PURPLE}>${CLEAR_STYLE} %s\n" "${RULE_COLOR}${target}${CLEAR_STYLE}" "" "${comment_line}" + else + printf " %${max_target_length}s %s\n" "" "${comment_line}" + fi + done <<< "$comment" +done diff --git a/make/_shared/kind/00_kind_image_versions.mk b/make/_shared/kind/00_kind_image_versions.mk new file mode 100755 index 00000000..987a4aa2 --- /dev/null +++ b/make/_shared/kind/00_kind_image_versions.mk @@ -0,0 +1,32 @@ +# Copyright 2024 The cert-manager Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This file is auto-generated by the learn_kind_images.sh script in the makefile-modules repo. +# Do not edit manually. + +kind_image_kindversion := v0.27.0 + +kind_image_kube_1.29_amd64 := docker.io/kindest/node:v1.29.14@sha256:e7858e6394f5e834802ce573ab340a0584d8314f909cb0717e14b57f2dd97257 +kind_image_kube_1.29_arm64 := docker.io/kindest/node:v1.29.14@sha256:6eed9bfd0313cc3574c4613adeb7f53832cb8d9c0ca9ffa8b8221716fd96dc18 +kind_image_kube_1.30_amd64 := docker.io/kindest/node:v1.30.10@sha256:e382f9b891474f1c4b0b5cfcf27f8e471f1bdc1f285afe38adeec1bd5b856cfe +kind_image_kube_1.30_arm64 := docker.io/kindest/node:v1.30.10@sha256:ca8e16c04ee9ebaeb9a4dd85abbe188f3893fb39bd658d6d3e639d16cf46e3da +kind_image_kube_1.31_amd64 := docker.io/kindest/node:v1.31.6@sha256:37d52dc19f59394f9347b00547c3ed2d73eb301a60294b9b05fbe56fb6196517 +kind_image_kube_1.31_arm64 := docker.io/kindest/node:v1.31.6@sha256:4e6223faa19178922d30e7b62546c5464fdf9bc66a3df64073424a51ab44f2ab +kind_image_kube_1.32_amd64 := docker.io/kindest/node:v1.32.2@sha256:a37b679ad8c1cfa7c64aca1734cc4299dc833258d6c131ed0204c8cd2bd56ff7 +kind_image_kube_1.32_arm64 := docker.io/kindest/node:v1.32.2@sha256:4d0e1b60f1da0d1349996a9778f8bace905189af5e05e04618eae0a155dd9f9c +kind_image_kube_1.33_amd64 := docker.io/kindest/node:v1.33.0@sha256:c9ec7bf998c310c5a6c903d66c2e595fb3e2eb53fb626cd53d07a3a5499de412 +kind_image_kube_1.33_arm64 := docker.io/kindest/node:v1.33.0@sha256:96ae3b980f87769e0117c2a89ec74fc660b84eedb573432abd2a682af3eccc02 + +kind_image_latest_amd64 := $(kind_image_kube_1.33_amd64) +kind_image_latest_arm64 := $(kind_image_kube_1.33_arm64) diff --git a/make/_shared/kind/00_mod.mk b/make/_shared/kind/00_mod.mk new file mode 100644 index 00000000..a4489159 --- /dev/null +++ b/make/_shared/kind/00_mod.mk @@ -0,0 +1,21 @@ +# Copyright 2023 The cert-manager Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +include $(dir $(lastword $(MAKEFILE_LIST)))/00_kind_image_versions.mk + +images_amd64 ?= +images_arm64 ?= + +images_amd64 += $(kind_image_latest_amd64) +images_arm64 += $(kind_image_latest_arm64) diff --git a/make/_shared/kind/01_mod.mk b/make/_shared/kind/01_mod.mk new file mode 100644 index 00000000..a7eb1b2b --- /dev/null +++ b/make/_shared/kind/01_mod.mk @@ -0,0 +1,16 @@ +# Copyright 2023 The cert-manager Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +include $(dir $(lastword $(MAKEFILE_LIST)))/kind.mk +include $(dir $(lastword $(MAKEFILE_LIST)))/kind-image-preload.mk diff --git a/make/_shared/kind/kind-image-preload.mk b/make/_shared/kind/kind-image-preload.mk new file mode 100644 index 00000000..88471e26 --- /dev/null +++ b/make/_shared/kind/kind-image-preload.mk @@ -0,0 +1,71 @@ +# Copyright 2023 The cert-manager Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +ifndef bin_dir +$(error bin_dir is not set) +endif + +ifndef images_amd64 +$(error images_amd64 is not set) +endif + +ifndef images_arm64 +$(error images_arm64 is not set) +endif + +########################################## + +images := $(images_$(HOST_ARCH)) +images_files := $(foreach image,$(images),$(subst :,+,$(image))) + +images_tar_dir := $(bin_dir)/downloaded/containers/$(HOST_ARCH) +images_tars := $(images_files:%=$(images_tar_dir)/%.tar) + +# Download the images as tarballs. After downloading the image using +# its digest, we untar the image and modify the .[0].RepoTags[0] value in +# the manifest.json file to have the correct tag (instead of "i-was-a-digest" +# which is set when the image is pulled using its digest). This tag is used +# to reference the image after it has been imported using docker or kind. Otherwise, +# the image would be imported with the tag "i-was-a-digest" which is not very useful. +# We would have to use digests to reference the image everywhere which might +# not always be possible and does not match the default behavior of eg. our helm charts. +# Untarring and modifying manifest.json is a hack and we hope that crane adds an option +# in the future that allows setting the tag on images that are pulled by digest. +# NOTE: the tag is fully determined based on the input, we fully allow the remote +# tag to point to a different digest. This prevents CI from breaking due to upstream +# changes. However, it also means that we can incorrectly combine digests with tags, +# hence caution is advised. +$(images_tars): $(images_tar_dir)/%.tar: | $(NEEDS_IMAGE-TOOL) $(NEEDS_CRANE) $(NEEDS_GOJQ) + @$(eval full_image=$(subst +,:,$*)) + @$(eval bare_image=$(word 1,$(subst :, ,$(full_image)))) + @$(eval digest=$(word 2,$(subst @, ,$(full_image)))) + @$(eval tag=$(word 2,$(subst :, ,$(word 1,$(subst @, ,$(full_image)))))) + @mkdir -p $(dir $@) + $(CRANE) pull "$(bare_image)@$(digest)" $@ --platform=linux/$(HOST_ARCH) + $(IMAGE-TOOL) tag-docker-tar $@ "$(bare_image):$(tag)" + +images_tar_envs := $(images_files:%=env-%) + +.PHONY: $(images_tar_envs) +$(images_tar_envs): env-%: $(images_tar_dir)/%.tar | $(NEEDS_GOJQ) + @$(eval image_without_tag=$(shell cut -d+ -f1 <<<"$*")) + @$(eval $(image_without_tag).TAR="$(images_tar_dir)/$*.tar") + @$(eval $(image_without_tag).REPO=$(shell tar xfO "$(images_tar_dir)/$*.tar" manifest.json | $(GOJQ) '.[0].RepoTags[0]' -r | cut -d: -f1)) + @$(eval $(image_without_tag).TAG=$(shell tar xfO "$(images_tar_dir)/$*.tar" manifest.json | $(GOJQ) '.[0].RepoTags[0]' -r | cut -d: -f2)) + @$(eval $(image_without_tag).FULL=$($(image_without_tag).REPO):$($(image_without_tag).TAG)) + +.PHONY: images-preload +## Preload images. +## @category [shared] Kind cluster +images-preload: | $(images_tar_envs) diff --git a/make/_shared/kind/kind.mk b/make/_shared/kind/kind.mk new file mode 100644 index 00000000..c768c8d1 --- /dev/null +++ b/make/_shared/kind/kind.mk @@ -0,0 +1,86 @@ +# Copyright 2023 The cert-manager Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +ifndef bin_dir +$(error bin_dir is not set) +endif + +ifndef kind_cluster_name +$(error kind_cluster_name is not set) +endif + +ifndef kind_cluster_config +$(error kind_cluster_config is not set) +endif + +########################################## + +kind_kubeconfig := $(bin_dir)/scratch/kube.config +absolute_kubeconfig := $(CURDIR)/$(kind_kubeconfig) + +$(bin_dir)/scratch/cluster-check: FORCE | $(NEEDS_KIND) $(bin_dir)/scratch + @if ! $(KIND) get clusters -q | grep -q "^$(kind_cluster_name)\$$"; then \ + echo "❌ cluster $(kind_cluster_name) not found. Starting ..."; \ + echo "trigger" > $@; \ + else \ + echo "✅ existing cluster $(kind_cluster_name) found"; \ + fi + $(eval export KUBECONFIG=$(absolute_kubeconfig)) + +kind_post_create_hook ?= +$(kind_kubeconfig): $(kind_cluster_config) $(bin_dir)/scratch/cluster-check | images-preload $(bin_dir)/scratch $(NEEDS_KIND) $(NEEDS_KUBECTL) $(NEEDS_CTR) + @[ -f "$(bin_dir)/scratch/cluster-check" ] && ( \ + $(KIND) delete cluster --name $(kind_cluster_name); \ + $(CTR) load -i $(docker.io/kindest/node.TAR); \ + $(KIND) create cluster \ + --image $(docker.io/kindest/node.FULL) \ + --name $(kind_cluster_name) \ + --config "$<"; \ + $(CTR) exec $(kind_cluster_name)-control-plane find /mounted_images/ -name "*.tar" -exec echo {} \; -exec ctr --namespace=k8s.io images import --all-platforms --no-unpack --digests {} \; ; \ + $(MAKE) --no-print-directory noop $(kind_post_create_hook); \ + $(KUBECTL) config use-context kind-$(kind_cluster_name); \ + ) || true + + $(KIND) get kubeconfig --name $(kind_cluster_name) > $@ + +.PHONY: kind-cluster +kind-cluster: $(kind_kubeconfig) + +.PHONY: kind-cluster-load +## Create Kind cluster and wait for nodes to be ready +## Load the kubeconfig into the default location so that +## it can be easily queried by kubectl. This target is +## meant to be used directly, NOT as a dependency. +## Use `kind-cluster` as a dependency instead. +## @category [shared] Kind cluster +kind-cluster-load: kind-cluster | $(NEEDS_KUBECTL) + mkdir -p ~/.kube + KUBECONFIG=~/.kube/config:$(kind_kubeconfig) $(KUBECTL) config view --flatten > ~/.kube/config + $(KUBECTL) config use-context kind-$(kind_cluster_name) + +.PHONY: kind-cluster-clean +## Delete the Kind cluster +## @category [shared] Kind cluster +kind-cluster-clean: $(NEEDS_KIND) + $(KIND) delete cluster --name $(kind_cluster_name) + rm -rf $(kind_kubeconfig) + $(MAKE) --no-print-directory noop $(kind_post_create_hook) + +.PHONY: kind-logs +## Get the Kind cluster +## @category [shared] Kind cluster +kind-logs: | kind-cluster $(NEEDS_KIND) $(bin_dir)/artifacts + rm -rf $(bin_dir)/artifacts/e2e-logs + mkdir -p $(bin_dir)/artifacts/e2e-logs + $(KIND) export logs $(bin_dir)/artifacts/e2e-logs --name=$(kind_cluster_name) diff --git a/make/_shared/klone/01_mod.mk b/make/_shared/klone/01_mod.mk new file mode 100644 index 00000000..a3d07dd2 --- /dev/null +++ b/make/_shared/klone/01_mod.mk @@ -0,0 +1,27 @@ +# Copyright 2023 The cert-manager Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +.PHONY: generate-klone +## Generate klone shared Makefiles +## @category [shared] Generate/ Verify +generate-klone: | $(NEEDS_KLONE) + $(KLONE) sync + +shared_generate_targets += generate-klone + +.PHONY: upgrade-klone +## Upgrade klone Makefile modules to latest version +## @category [shared] Self-upgrade +upgrade-klone: | $(NEEDS_KLONE) + $(KLONE) upgrade diff --git a/make/_shared/oci-build/00_mod.mk b/make/_shared/oci-build/00_mod.mk new file mode 100644 index 00000000..c94c1f6a --- /dev/null +++ b/make/_shared/oci-build/00_mod.mk @@ -0,0 +1,134 @@ +# Copyright 2023 The cert-manager Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +oci_platforms ?= linux/amd64,linux/arm/v7,linux/arm64,linux/ppc64le + +# Use distroless as minimal base image to package the manager binary +# To get latest SHA run "crane digest quay.io/jetstack/base-static:latest" +base_image_static := quay.io/jetstack/base-static@sha256:16a5a64b918592f5c38fa73721a87f8585a3a501d261087e7b953f8b59279cd0 + +# Use custom apko-built image as minimal base image to package the manager binary +# To get latest SHA run "crane digest quay.io/jetstack/base-static-csi:latest" +base_image_csi-static := quay.io/jetstack/base-static-csi@sha256:fb97fc098aabdfb5b9b01475d3531b688a9c2219f4bbc143816d3e47a267be6d + +# Utility functions +fatal_if_undefined = $(if $(findstring undefined,$(origin $1)),$(error $1 is not set)) +fatal_if_deprecated_defined = $(if $(findstring undefined,$(origin $1)),,$(error $1 is deprecated, use $2 instead)) + +# Validate globals that are required +$(call fatal_if_undefined,bin_dir) +$(call fatal_if_undefined,build_names) + +# Set default config values +CGO_ENABLED ?= 0 +GOEXPERIMENT ?= # empty by default + +# Default variables per build_names entry +# +# $1 - build_name +define default_per_build_variables +go_$1_cgo_enabled ?= $(CGO_ENABLED) +go_$1_goexperiment ?= $(GOEXPERIMENT) +go_$1_flags ?= -tags= +oci_$1_additional_layers ?= +oci_$1_linux_capabilities ?= +oci_$1_build_args ?= +endef + +$(foreach build_name,$(build_names),$(eval $(call default_per_build_variables,$(build_name)))) + +# Validate variables per build_names entry +# +# $1 - build_name +define check_per_build_variables +# Validate deprecated variables +$(call fatal_if_deprecated_defined,cgo_enabled_$1,go_$1_cgo_enabled) +$(call fatal_if_deprecated_defined,goexperiment_$1,go_$1_goexperiment) +$(call fatal_if_deprecated_defined,oci_additional_layers_$1,oci_$1_additional_layers) + +# Validate required config exists +$(call fatal_if_undefined,go_$1_ldflags) +$(call fatal_if_undefined,go_$1_main_dir) +$(call fatal_if_undefined,go_$1_mod_dir) +$(call fatal_if_undefined,oci_$1_base_image_flavor) +$(call fatal_if_undefined,oci_$1_image_name_development) + +# Validate we have valid base image config +ifeq ($(oci_$1_base_image_flavor),static) + oci_$1_base_image := $(base_image_static) +else ifeq ($(oci_$1_base_image_flavor),csi-static) + oci_$1_base_image := $(base_image_csi-static) +else ifeq ($(oci_$1_base_image_flavor),custom) + $$(call fatal_if_undefined,oci_$1_base_image) +else + $$(error oci_$1_base_image_flavor has unknown value "$(oci_$1_base_image_flavor)") +endif + +# Validate the config required to build the golang based images +ifneq ($(go_$1_main_dir:.%=.),.) +$$(error go_$1_main_dir "$(go_$1_main_dir)" should be a directory path that DOES start with ".") +endif +ifeq ($(go_$1_main_dir:%/=/),/) +$$(error go_$1_main_dir "$(go_$1_main_dir)" should be a directory path that DOES NOT end with "/") +endif +ifeq ($(go_$1_main_dir:%.go=.go),.go) +$$(error go_$1_main_dir "$(go_$1_main_dir)" should be a directory path that DOES NOT end with ".go") +endif +ifneq ($(go_$1_mod_dir:.%=.),.) +$$(error go_$1_mod_dir "$(go_$1_mod_dir)" should be a directory path that DOES start with ".") +endif +ifeq ($(go_$1_mod_dir:%/=/),/) +$$(error go_$1_mod_dir "$(go_$1_mod_dir)" should be a directory path that DOES NOT end with "/") +endif +ifeq ($(go_$1_mod_dir:%.go=.go),.go) +$$(error go_$1_mod_dir "$(go_$1_mod_dir)" should be a directory path that DOES NOT end with ".go") +endif +ifeq ($(wildcard $(go_$1_mod_dir)/go.mod),) +$$(error go_$1_mod_dir "$(go_$1_mod_dir)" does not contain a go.mod file) +endif +ifeq ($(wildcard $(go_$1_mod_dir)/$(go_$1_main_dir)/main.go),) +$$(error go_$1_main_dir "$(go_$1_mod_dir)" does not contain a main.go file) +endif + +# Validate the config required to build OCI images +ifneq ($(words $(oci_$1_image_name_development)),1) +$$(error oci_$1_image_name_development "$(oci_$1_image_name_development)" should be a single image name) +endif + +endef + +$(foreach build_name,$(build_names),$(eval $(call check_per_build_variables,$(build_name)))) + +# Create variables holding targets +# +# We create the following targets for each $(build_names) +# - oci-build-$(build_name) = build the oci directory +# - oci-load-$(build_name) = load the image into docker using the oci_$(build_name)_image_name_development variable +# - docker-tarball-$(build_name) = build a "docker load" compatible tarball of the image +# - ko-config-$(build_name) = generate "ko" config for a given build +oci_build_targets := $(build_names:%=oci-build-%) +oci_load_targets := $(build_names:%=oci-load-%) +docker_tarball_targets := $(build_names:%=docker-tarball-%) +ko_config_targets := $(build_names:%=ko-config-%) + +# Derive config based on user config +# +# - oci_layout_path_$(build_name) = path that the OCI image will be saved in OCI layout directory format +# - oci_digest_path_$(build_name) = path to the file that will contain the digests +# - ko_config_path_$(build_name) = path to the ko config file +# - docker_tarball_path_$(build_name) = path that the docker tarball that the docker-tarball-$(build_name) will produce +$(foreach build_name,$(build_names),$(eval oci_layout_path_$(build_name) := $(bin_dir)/scratch/image/oci-layout-$(build_name).$(oci_$(build_name)_image_tag))) +$(foreach build_name,$(build_names),$(eval oci_digest_path_$(build_name) := $(CURDIR)/$(oci_layout_path_$(build_name)).digests)) +$(foreach build_name,$(build_names),$(eval ko_config_path_$(build_name) := $(CURDIR)/$(oci_layout_path_$(build_name)).ko_config.yaml)) +$(foreach build_name,$(build_names),$(eval docker_tarball_path_$(build_name) := $(CURDIR)/$(oci_layout_path_$(build_name)).docker.tar)) diff --git a/make/_shared/oci-build/01_mod.mk b/make/_shared/oci-build/01_mod.mk new file mode 100644 index 00000000..726ad13c --- /dev/null +++ b/make/_shared/oci-build/01_mod.mk @@ -0,0 +1,85 @@ +# Copyright 2023 The cert-manager Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +$(bin_dir)/scratch/image: + @mkdir -p $@ + +define ko_config_target +.PHONY: $(ko_config_path_$1:$(CURDIR)/%=%) +$(ko_config_path_$1:$(CURDIR)/%=%): | $(NEEDS_YQ) $(bin_dir)/scratch/image + echo '{}' | \ + $(YQ) '.defaultBaseImage = "$(oci_$1_base_image)"' | \ + $(YQ) '.builds[0].id = "$1"' | \ + $(YQ) '.builds[0].dir = "$(go_$1_mod_dir)"' | \ + $(YQ) '.builds[0].main = "$(go_$1_main_dir)"' | \ + $(YQ) '.builds[0].env[0] = "CGO_ENABLED=$(go_$1_cgo_enabled)"' | \ + $(YQ) '.builds[0].env[1] = "GOEXPERIMENT=$(go_$1_goexperiment)"' | \ + $(YQ) '.builds[0].ldflags[0] = "-s"' | \ + $(YQ) '.builds[0].ldflags[1] = "-w"' | \ + $(YQ) '.builds[0].ldflags[2] = "{{.Env.LDFLAGS}}"' | \ + $(YQ) '.builds[0].flags[0] = "$(go_$1_flags)"' | \ + $(YQ) '.builds[0].linux_capabilities = "$(oci_$1_linux_capabilities)"' \ + > $(CURDIR)/$(oci_layout_path_$1).ko_config.yaml + +ko-config-$1: $(ko_config_path_$1:$(CURDIR)/%=%) +endef + +.PHONY: $(ko_config_targets) +$(foreach build_name,$(build_names),$(eval $(call ko_config_target,$(build_name)))) + +.PHONY: $(oci_build_targets) +## Build the OCI image. +## @category [shared] Build +$(oci_build_targets): oci-build-%: ko-config-% | $(NEEDS_KO) $(NEEDS_GO) $(NEEDS_YQ) $(NEEDS_IMAGE-TOOL) $(bin_dir)/scratch/image + rm -rf $(CURDIR)/$(oci_layout_path_$*) + GOWORK=off \ + KO_DOCKER_REPO=$(oci_$*_image_name_development) \ + KOCACHE=$(CURDIR)/$(bin_dir)/scratch/image/ko_cache \ + KO_CONFIG_PATH=$(ko_config_path_$*) \ + SOURCE_DATE_EPOCH=$(GITEPOCH) \ + KO_GO_PATH=$(GO) \ + LDFLAGS="$(go_$*_ldflags)" \ + $(KO) build $(go_$*_mod_dir)/$(go_$*_main_dir) \ + --platform=$(oci_platforms) \ + $(oci_$*_build_args) \ + --oci-layout-path=$(oci_layout_path_$*) \ + --sbom-dir=$(CURDIR)/$(oci_layout_path_$*).sbom \ + --sbom=spdx \ + --push=false \ + --bare + + $(IMAGE-TOOL) append-layers \ + $(CURDIR)/$(oci_layout_path_$*) \ + $(oci_$*_additional_layers) + + $(IMAGE-TOOL) list-digests \ + $(CURDIR)/$(oci_layout_path_$*) \ + > $(oci_digest_path_$*) + +# Only include the oci-load target if kind is provided by the kind makefile-module +ifdef kind_cluster_name +.PHONY: $(oci_load_targets) +## Build OCI image for the local architecture and load +## it into the $(kind_cluster_name) kind cluster. +## @category [shared] Build +$(oci_load_targets): oci-load-%: docker-tarball-% | kind-cluster $(NEEDS_KIND) + $(KIND) load image-archive --name $(kind_cluster_name) $(docker_tarball_path_$*) +endif + +## Build Docker tarball image for the local architecture +## @category [shared] Build +.PHONY: $(docker_tarball_targets) +$(docker_tarball_targets): oci_platforms := "linux/$(HOST_ARCH)" +$(docker_tarball_targets): docker-tarball-%: oci-build-% | $(NEEDS_GO) $(NEEDS_IMAGE-TOOL) + $(IMAGE-TOOL) convert-to-docker-tar $(CURDIR)/$(oci_layout_path_$*) $(docker_tarball_path_$*) $(oci_$*_image_name_development):$(oci_$*_image_tag) diff --git a/make/_shared/oci-publish/00_mod.mk b/make/_shared/oci-publish/00_mod.mk new file mode 100644 index 00000000..099bff46 --- /dev/null +++ b/make/_shared/oci-publish/00_mod.mk @@ -0,0 +1,58 @@ +# Copyright 2023 The cert-manager Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Push names is equivalent to build_names, additional names can be added for +# pushing images that are not build with the oci-build module +push_names ?= +push_names += $(build_names) + +# Sometimes we need to push to one registry, but pull from another. This allows +# that. +# +# The lines should be in the format a=b +# +# The value on the left is the domain you include in your oci__image_name +# variable, the one on the right is the domain that is actually pushed to. +# +# For example, if we set up a vanity domain for the current quay: +# +# oci_controller_image_name = registry.cert-manager.io/cert-manager-controller` +# image_registry_rewrite += registry.cert-manager.io=quay.io/jetstack +# +# This would push to quay.io/jetstack/cert-manager-controller. +# +# The general idea is oci__image_name contains the final image name, after replication, after vanity domains etc. + +image_registry_rewrite ?= + +# Utilities for extracting the key and value from a foo=bar style line +kv_key = $(word 1,$(subst =, ,$1)) +kv_value = $(word 2,$(subst =, ,$1)) + +# Apply the image_registry_rewrite rules, if no rules match an image then the +# image name is not changed. Any rules that match will be applied. +# +# For example, if there was a rule vanity-domain.com=real-registry.com/foo +# then any references to vanity-domain.com/image would be rewritten to +# real-registry.com/foo/image +image_registry_rewrite_rules_for_image = $(strip $(sort $(foreach rule,$(image_registry_rewrite),$(if $(findstring $(call kv_key,$(rule)),$1),$(rule))))) +apply_image_registry_rewrite_rules_to_image = $(if $(call image_registry_rewrite_rules_for_image,$1),\ + $(foreach rule,$(call image_registry_rewrite_rules_for_image,$1),$(subst $(call kv_key,$(rule)),$(call kv_value,$(rule)),$1)),\ + $1) +apply_image_registry_rewrite_rules = $(foreach image_name,$1,$(call apply_image_registry_rewrite_rules_to_image,$(image_name))) + +# This is a helper function to return the image names for a given build_name. +# It will apply all rewrite rules to the image names +oci_image_names_for = $(call apply_image_registry_rewrite_rules,$(oci_$1_image_name)) +oci_image_tag_for = $(oci_$1_image_tag) \ No newline at end of file diff --git a/make/_shared/oci-publish/01_mod.mk b/make/_shared/oci-publish/01_mod.mk new file mode 100644 index 00000000..348490c9 --- /dev/null +++ b/make/_shared/oci-publish/01_mod.mk @@ -0,0 +1,127 @@ +# Copyright 2023 The cert-manager Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Utility functions +fatal_if_undefined = $(if $(findstring undefined,$(origin $1)),$(error $1 is not set)) +oci_digest = $(shell head -1 $(oci_digest_path_$1) 2> /dev/null) +sanitize_target = $(subst :,-,$1) +registry_for = $(firstword $(subst /, ,$1)) + +# Utility variables +current_makefile_directory := $(dir $(lastword $(MAKEFILE_LIST))) +image_exists_script := $(current_makefile_directory)/image-exists.sh + +# Validate globals that are required +$(call fatal_if_undefined,bin_dir) +$(call fatal_if_undefined,push_names) + +# Set default config values +RELEASE_DRYRUN ?= false +CRANE_FLAGS ?= # empty by default +COSIGN_FLAGS ?= # empty by default +OCI_SIGN_ON_PUSH ?= true + +# Default variables per push_names entry +# +# $1 - build_name +define default_per_build_variables +release_dryrun_$1 ?= $(RELEASE_DRYRUN) +crane_flags_$1 ?= $(CRANE_FLAGS) +cosign_flags_$1 ?= $(COSIGN_FLAGS) +oci_sign_on_push_$1 ?= $(OCI_SIGN_ON_PUSH) +endef + +$(foreach build_name,$(push_names),$(eval $(call default_per_build_variables,$(build_name)))) + +# Validate variables per push_names entry +# +# $1 - build_name +define check_per_build_variables +$(call fatal_if_undefined,oci_digest_path_$1) +$(call fatal_if_undefined,oci_layout_path_$1) +$(call fatal_if_undefined,oci_$1_image_name) +$(call fatal_if_undefined,oci_$1_image_tag) +endef + +$(foreach build_name,$(push_names),$(eval $(call check_per_build_variables,$(build_name)))) + +# Create variables holding targets +# +# We create the following targets for each $(push_names) +# - oci-build-$(build_name) = build the oci directory +# - oci-load-$(build_name) = load the image into docker using the oci_$(build_name)_image_name_development variable +# - docker-tarball-$(build_name) = build a "docker load" compatible tarball of the image +# - ko-config-$(build_name) = generate "ko" config for a given build +oci_push_targets := $(push_names:%=oci-push-%) +oci_sign_targets := $(push_names:%=oci-sign-%) +oci_maybe_push_targets := $(push_names:%=oci-maybe-push-%) + +# Define push target +# $1 - build_name +# $2 - image_name +define oci_push_target +.PHONY: $(call sanitize_target,oci-push-$2) +$(call sanitize_target,oci-push-$2): oci-build-$1 | $(NEEDS_CRANE) + $$(CRANE) $(crane_flags_$1) push "$(oci_layout_path_$1)" "$2:$(call oci_image_tag_for,$1)" + $(if $(filter true,$(oci_sign_on_push_$1)),$(MAKE) $(call sanitize_target,oci-sign-$2)) + +.PHONY: $(call sanitize_target,oci-maybe-push-$2) +$(call sanitize_target,oci-maybe-push-$2): oci-build-$1 | $(NEEDS_CRANE) + CRANE="$$(CRANE) $(crane_flags_$1)" \ + source $(image_exists_script) $2:$(call oci_image_tag_for,$1); \ + $$(CRANE) $(crane_flags_$1) push "$(oci_layout_path_$1)" "$2:$(call oci_image_tag_for,$1)"; \ + $(if $(filter true,$(oci_sign_on_push_$1)),$(MAKE) $(call sanitize_target,oci-sign-$2)) + +oci-push-$1: $(call sanitize_target,oci-push-$2) +oci-maybe-push-$1: $(call sanitize_target,oci-maybe-push-$2) +endef + +oci_push_target_per_image = $(foreach image_name,$2,$(eval $(call oci_push_target,$1,$(image_name)))) +$(foreach build_name,$(push_names),$(eval $(call oci_push_target_per_image,$(build_name),$(call oci_image_names_for,$(build_name))))) + +.PHONY: $(oci_push_targets) +## Build and push OCI image. +## If the tag already exists, this target will overwrite it. +## If an identical image was already built before, we will add a new tag to it, but we will not sign it again. +## Expected pushed images: +## - :v1.2.3, @sha256:0000001 +## - :v1.2.3.sig, :sha256-0000001.sig +## @category [shared] Publish +$(oci_push_targets): + +.PHONY: $(oci_maybe_push_targets) +## Push image if tag does not already exist in registry. +## @category [shared] Publish +$(oci_maybe_push_targets): + +# Define sign target +# $1 - build_name +# $2 - image_name +define oci_sign_target +.PHONY: $(call sanitize_target,oci-sign-$2) +$(call sanitize_target,oci-sign-$2): $(oci_digest_path_$1) | $(NEEDS_CRANE) $(NEEDS_COSIGN) + $$(CRANE) $(crane_flags_$1) manifest $2:$$(subst :,-,$$(call oci_digest,$1)).sig > /dev/null 2>&1 || \ + $$(COSIGN) sign --yes=true $(cosign_flags_$1) "$2@$$(call oci_digest,$1)" + +oci-sign-$1: $(call sanitize_target,oci-sign-$2) +endef + +oci_sign_target_per_image = $(foreach image_name,$2,$(eval $(call oci_sign_target,$1,$(image_name)))) +$(foreach build_name,$(push_names),$(eval $(call oci_sign_target_per_image,$(build_name),$(call oci_image_names_for,$(build_name))))) + +.PHONY: $(oci_sign_targets) +## Sign an OCI image. +## If a signature already exists, this will not overwrite it. +## @category [shared] Publish +$(oci_sign_targets): \ No newline at end of file diff --git a/make/_shared/oci-publish/image-exists.sh b/make/_shared/oci-publish/image-exists.sh new file mode 100755 index 00000000..9ecbb61a --- /dev/null +++ b/make/_shared/oci-publish/image-exists.sh @@ -0,0 +1,70 @@ +#!/usr/bin/env bash + +# Copyright 2022 The cert-manager Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -o errexit +set -o nounset +set -o pipefail + +# This script checks if a given image exists in the upstream registry, and if it +# does, whether it contains all the expected architectures. + +crane=${CRANE:-} + +FULL_IMAGE=${1:-} + +function print_usage() { + echo "usage: $0 [commands...]" +} + +if [[ -z $FULL_IMAGE ]]; then + print_usage + echo "Missing full-image" + exit 1 +fi + +if [[ -z $crane ]]; then + echo "CRANE environment variable must be set to the path of the crane binary" + exit 1 +fi + +shift 1 + +manifest=$(mktemp) +trap 'rm -f "$manifest"' EXIT SIGINT + +manifest_error=$(mktemp) +trap 'rm -f "$manifest_error"' EXIT SIGINT + +echo "+++ searching for $FULL_IMAGE in upstream registry" + +set +o errexit +$crane manifest "$FULL_IMAGE" > "$manifest" 2> "$manifest_error" +exit_code=$? +set -o errexit + +manifest_error_data=$(cat "$manifest_error") +if [[ $exit_code -eq 0 ]]; then + echo "+++ upstream registry appears to contain $FULL_IMAGE, exiting" + exit 0 + +elif [[ "$manifest_error_data" == *"MANIFEST_UNKNOWN"* ]]; then + echo "+++ upstream registry does not contain $FULL_IMAGE, will build and push" + # fall through to run the commands passed to this script + +else + echo "FATAL: upstream registry returned an unexpected error: $manifest_error_data, exiting" + exit 1 +fi diff --git a/make/_shared/repository-base/01_mod.mk b/make/_shared/repository-base/01_mod.mk new file mode 100644 index 00000000..aa6b7ee2 --- /dev/null +++ b/make/_shared/repository-base/01_mod.mk @@ -0,0 +1,33 @@ +# Copyright 2023 The cert-manager Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +base_dir := $(dir $(lastword $(MAKEFILE_LIST)))/base/ +base_dependabot_dir := $(dir $(lastword $(MAKEFILE_LIST)))/base-dependabot/ + +ifdef repository_base_no_dependabot +.PHONY: generate-base +## Generate base files in the repository +## @category [shared] Generate/ Verify +generate-base: + cp -r $(base_dir)/. ./ +else +.PHONY: generate-base +## Generate base files in the repository +## @category [shared] Generate/ Verify +generate-base: + cp -r $(base_dir)/. ./ + cp -r $(base_dependabot_dir)/. ./ +endif + +shared_generate_targets += generate-base diff --git a/make/_shared/repository-base/base-dependabot/.github/dependabot.yaml b/make/_shared/repository-base/base-dependabot/.github/dependabot.yaml new file mode 100644 index 00000000..d950a83e --- /dev/null +++ b/make/_shared/repository-base/base-dependabot/.github/dependabot.yaml @@ -0,0 +1,20 @@ +# THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT. +# Edit https://github.com/cert-manager/makefile-modules/blob/main/modules/repository-base/base-dependabot/.github/dependabot.yaml instead. + +# Update Go dependencies and GitHub Actions dependencies daily. +version: 2 +updates: +- package-ecosystem: gomod + directory: / + schedule: + interval: daily + groups: + all: + patterns: ["*"] +- package-ecosystem: github-actions + directory: / + schedule: + interval: daily + groups: + all: + patterns: ["*"] diff --git a/make/_shared/repository-base/base/.github/workflows/make-self-upgrade.yaml b/make/_shared/repository-base/base/.github/workflows/make-self-upgrade.yaml new file mode 100644 index 00000000..1097272e --- /dev/null +++ b/make/_shared/repository-base/base/.github/workflows/make-self-upgrade.yaml @@ -0,0 +1,105 @@ +# THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT. +# Edit https://github.com/cert-manager/makefile-modules/blob/main/modules/repository-base/base/.github/workflows/make-self-upgrade.yaml instead. + +name: make-self-upgrade +concurrency: make-self-upgrade +on: + workflow_dispatch: {} + schedule: + - cron: '0 0 * * *' + +permissions: + contents: read + +jobs: + self_upgrade: + runs-on: ubuntu-latest + + if: github.repository_owner == 'cert-manager' + + permissions: + contents: write + pull-requests: write + + env: + SOURCE_BRANCH: "${{ github.ref_name }}" + SELF_UPGRADE_BRANCH: "self-upgrade-${{ github.ref_name }}" + + steps: + - name: Fail if branch is not head of branch. + if: ${{ !startsWith(github.ref, 'refs/heads/') && env.SOURCE_BRANCH != '' && env.SELF_UPGRADE_BRANCH != '' }} + run: | + echo "This workflow should not be run on a non-branch-head." + exit 1 + + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + # Adding `fetch-depth: 0` makes sure tags are also fetched. We need + # the tags so `git describe` returns a valid version. + # see https://github.com/actions/checkout/issues/701 for extra info about this option + with: { fetch-depth: 0 } + + - id: go-version + run: | + make print-go-version >> "$GITHUB_OUTPUT" + + - uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5.4.0 + with: + go-version: ${{ steps.go-version.outputs.result }} + + - run: | + git checkout -B "$SELF_UPGRADE_BRANCH" + + - run: | + make -j upgrade-klone + make -j generate + + - id: is-up-to-date + shell: bash + run: | + git_status=$(git status -s) + is_up_to_date="true" + if [ -n "$git_status" ]; then + is_up_to_date="false" + echo "The following changes will be committed:" + echo "$git_status" + fi + echo "result=$is_up_to_date" >> "$GITHUB_OUTPUT" + + - if: ${{ steps.is-up-to-date.outputs.result != 'true' }} + run: | + git config --global user.name "cert-manager-bot" + git config --global user.email "cert-manager-bot@users.noreply.github.com" + git add -A && git commit -m "BOT: run 'make upgrade-klone' and 'make generate'" --signoff + git push -f origin "$SELF_UPGRADE_BRANCH" + + - if: ${{ steps.is-up-to-date.outputs.result != 'true' }} + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + with: + script: | + const { repo, owner } = context.repo; + const pulls = await github.rest.pulls.list({ + owner: owner, + repo: repo, + head: owner + ':' + process.env.SELF_UPGRADE_BRANCH, + base: process.env.SOURCE_BRANCH, + state: 'open', + }); + + if (pulls.data.length < 1) { + const result = await github.rest.pulls.create({ + title: '[CI] Merge ' + process.env.SELF_UPGRADE_BRANCH + ' into ' + process.env.SOURCE_BRANCH, + owner: owner, + repo: repo, + head: process.env.SELF_UPGRADE_BRANCH, + base: process.env.SOURCE_BRANCH, + body: [ + 'This PR is auto-generated to bump the Makefile modules.', + ].join('\n'), + }); + await github.rest.issues.addLabels({ + owner, + repo, + issue_number: result.data.number, + labels: ['skip-review'] + }); + } diff --git a/make/_shared/repository-base/base/LICENSE b/make/_shared/repository-base/base/LICENSE new file mode 100644 index 00000000..261eeb9e --- /dev/null +++ b/make/_shared/repository-base/base/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/make/_shared/repository-base/base/Makefile b/make/_shared/repository-base/base/Makefile new file mode 100644 index 00000000..6a1652d4 --- /dev/null +++ b/make/_shared/repository-base/base/Makefile @@ -0,0 +1,116 @@ +# Copyright 2023 The cert-manager Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT. +# Edit https://github.com/cert-manager/makefile-modules/blob/main/modules/repository-base/base/Makefile instead. + +# NOTE FOR DEVELOPERS: "How do the Makefiles work and how can I extend them?" +# +# Shared Makefile logic lives in the make/_shared/ directory. The source of truth for these files +# lies outside of this repository, eg. in the cert-manager/makefile-modules repository. +# +# Logic specific to this repository must be defined in the make/00_mod.mk and make/02_mod.mk files: +# - The make/00_mod.mk file is included first and contains variable definitions needed by +# the shared Makefile logic. +# - The make/02_mod.mk file is included later, it can make use of most of the shared targets +# defined in the make/_shared/ directory (all targets defined in 00_mod.mk and 01_mod.mk). +# This file should be used to define targets specific to this repository. + +################################## + +# Some modules build their dependencies from variables, we want these to be +# evaluated at the last possible moment. For this we use second expansion to +# re-evaluate the generate and verify targets a second time. +# +# See https://www.gnu.org/software/make/manual/html_node/Secondary-Expansion.html +.SECONDEXPANSION: + +# For details on some of these "prelude" settings, see: +# https://clarkgrubb.com/makefile-style-guide +MAKEFLAGS += --warn-undefined-variables --no-builtin-rules +SHELL := /usr/bin/env bash +.SHELLFLAGS := -uo pipefail -c +.DEFAULT_GOAL := help +.DELETE_ON_ERROR: +.SUFFIXES: +FORCE: + +noop: # do nothing + +# Set empty value for MAKECMDGOALS to prevent the "warning: undefined variable 'MAKECMDGOALS'" +# warning from happening when running make without arguments +MAKECMDGOALS ?= + +################################## +# Host OS and architecture setup # +################################## + +# The reason we don't use "go env GOOS" or "go env GOARCH" is that the "go" +# binary may not be available in the PATH yet when the Makefiles are +# evaluated. HOST_OS and HOST_ARCH only support Linux, *BSD and macOS (M1 +# and Intel). +host_os := $(shell uname -s | tr A-Z a-z) +host_arch := $(shell uname -m) +HOST_OS ?= $(host_os) +HOST_ARCH ?= $(host_arch) + +ifeq (x86_64, $(HOST_ARCH)) + HOST_ARCH = amd64 +else ifeq (aarch64, $(HOST_ARCH)) + # linux reports the arm64 arch as aarch64 + HOST_ARCH = arm64 +endif + +################################## +# Git and versioning information # +################################## + +git_version := $(shell git describe --tags --always --match='v*' --abbrev=14 --dirty) +VERSION ?= $(git_version) +IS_PRERELEASE := $(shell git describe --tags --always --match='v*' --abbrev=0 | grep -q '-' && echo true || echo false) +GITCOMMIT := $(shell git rev-parse HEAD) +GITEPOCH := $(shell git show -s --format=%ct HEAD) + +################################## +# Global variables and dirs # +################################## + +bin_dir := _bin + +# The ARTIFACTS environment variable is set by the CI system to a directory +# where artifacts should be placed. These artifacts are then uploaded to a +# storage bucket by the CI system (https://docs.prow.k8s.io/docs/components/pod-utilities/). +# An example of such an artifact is a jUnit XML file containing test results. +# If the ARTIFACTS environment variable is not set, we default to a local +# directory in the _bin directory. +ARTIFACTS ?= $(bin_dir)/artifacts + +$(bin_dir) $(ARTIFACTS) $(bin_dir)/scratch: + mkdir -p $@ + +.PHONY: clean +## Clean all temporary files +## @category [shared] Tools +clean: + rm -rf $(bin_dir) + +################################## +# Include all the Makefiles # +################################## + +-include make/00_mod.mk +-include make/_shared/*/00_mod.mk +-include make/_shared/*/01_mod.mk +-include make/02_mod.mk +-include make/_shared/*/02_mod.mk diff --git a/make/_shared/repository-base/base/OWNERS_ALIASES b/make/_shared/repository-base/base/OWNERS_ALIASES new file mode 100644 index 00000000..672704c9 --- /dev/null +++ b/make/_shared/repository-base/base/OWNERS_ALIASES @@ -0,0 +1,14 @@ +# THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT. +# Edit https://github.com/cert-manager/makefile-modules/blob/main/modules/repository-base/base/OWNERS_ALIASES instead. + +aliases: + cm-maintainers: + - munnerz + - joshvanl + - wallrj + - jakexks + - maelvls + - sgtcodfish + - inteon + - thatsmrtalbot + - erikgb diff --git a/make/_shared/tools/00_mod.mk b/make/_shared/tools/00_mod.mk new file mode 100644 index 00000000..56a6ee7a --- /dev/null +++ b/make/_shared/tools/00_mod.mk @@ -0,0 +1,665 @@ +# Copyright 2023 The cert-manager Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +ifndef bin_dir +$(error bin_dir is not set) +endif + +########################################## + +export DOWNLOAD_DIR ?= $(CURDIR)/$(bin_dir)/downloaded +export GOVENDOR_DIR ?= $(CURDIR)/$(bin_dir)/go_vendor + +$(bin_dir)/tools $(DOWNLOAD_DIR)/tools: + @mkdir -p $@ + +checkhash_script := $(dir $(lastword $(MAKEFILE_LIST)))/util/checkhash.sh +lock_script := $(dir $(lastword $(MAKEFILE_LIST)))/util/lock.sh + +# $outfile is a variable in the lock script +outfile := $$outfile + +for_each_kv = $(foreach item,$2,$(eval $(call $1,$(word 1,$(subst =, ,$(item))),$(word 2,$(subst =, ,$(item)))))) + +# To make sure we use the right version of each tool, we put symlink in +# $(bin_dir)/tools, and the actual binaries are in $(bin_dir)/downloaded. When bumping +# the version of the tools, this symlink gets updated. + +# Let's have $(bin_dir)/tools in front of the PATH so that we don't inadvertently +# pick up the wrong binary somewhere. Watch out, $(shell echo $$PATH) will +# still print the original PATH, since GNU make does not honor exported +# variables: https://stackoverflow.com/questions/54726457 +export PATH := $(CURDIR)/$(bin_dir)/tools:$(PATH) + +CTR ?= docker +.PHONY: __require-ctr +ifneq ($(shell command -v $(CTR) >/dev/null || echo notfound),) +__require-ctr: + @:$(error "$(CTR) (or set CTR to a docker-compatible tool)") +endif +NEEDS_CTR = __require-ctr + +tools := +# https://github.com/helm/helm/releases +tools += helm=v3.17.2 +# https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl +tools += kubectl=v1.32.3 +# https://github.com/kubernetes-sigs/kind/releases +tools += kind=v0.27.0 +# https://www.vaultproject.io/downloads +tools += vault=1.19.1 +# https://github.com/Azure/azure-workload-identity/releases +tools += azwi=v1.4.1 +# https://github.com/kyverno/kyverno/releases +tools += kyverno=v1.13.4 +# https://github.com/mikefarah/yq/releases +tools += yq=v4.45.1 +# https://github.com/ko-build/ko/releases +tools += ko=0.17.1 +# https://github.com/protocolbuffers/protobuf/releases +tools += protoc=30.2 +# https://github.com/aquasecurity/trivy/releases +tools += trivy=v0.61.0 +# https://github.com/vmware-tanzu/carvel-ytt/releases +tools += ytt=v0.51.2 +# https://github.com/rclone/rclone/releases +tools += rclone=v1.69.1 +# https://github.com/istio/istio/releases +tools += istioctl=1.25.1 + +### go packages +# https://pkg.go.dev/sigs.k8s.io/controller-tools/cmd/controller-gen?tab=versions +tools += controller-gen=v0.17.3 +# https://pkg.go.dev/golang.org/x/tools/cmd/goimports?tab=versions +tools += goimports=v0.31.0 +# https://pkg.go.dev/github.com/google/go-licenses/v2?tab=versions +tools += go-licenses=v2.0.0-alpha.1 +# https://pkg.go.dev/gotest.tools/gotestsum?tab=versions +tools += gotestsum=v1.12.1 +# https://pkg.go.dev/sigs.k8s.io/kustomize/kustomize/v4?tab=versions +tools += kustomize=v4.5.7 +# https://pkg.go.dev/github.com/itchyny/gojq?tab=versions +tools += gojq=v0.12.17 +# https://pkg.go.dev/github.com/google/go-containerregistry/pkg/crane?tab=versions +tools += crane=v0.20.3 +# https://pkg.go.dev/google.golang.org/protobuf/cmd/protoc-gen-go?tab=versions +tools += protoc-gen-go=v1.36.6 +# https://pkg.go.dev/github.com/sigstore/cosign/v2/cmd/cosign?tab=versions +tools += cosign=v2.4.3 +# https://pkg.go.dev/github.com/cert-manager/boilersuite?tab=versions +tools += boilersuite=v0.1.0 +# https://pkg.go.dev/github.com/princjef/gomarkdoc/cmd/gomarkdoc?tab=versions +tools += gomarkdoc=v1.1.0 +# https://pkg.go.dev/oras.land/oras/cmd/oras?tab=versions +tools += oras=v1.2.2 +# https://pkg.go.dev/github.com/onsi/ginkgo/v2/ginkgo?tab=versions +# The gingko version should be kept in sync with the version used in code. +# If there is no go.mod file (which is only the case for the makefile-modules +# repo), then we default to a version that we know exists. We have to do this +# because otherwise the awk failure renders the whole makefile unusable. +detected_ginkgo_version := $(shell [[ -f go.mod ]] && awk '/ginkgo\/v2/ {print $$2}' go.mod || echo "v2.13.2") +tools += ginkgo=$(detected_ginkgo_version) +# https://pkg.go.dev/github.com/cert-manager/klone?tab=versions +tools += klone=v0.2.0 +# https://pkg.go.dev/github.com/goreleaser/goreleaser?tab=versions +tools += goreleaser=v1.26.2 +# https://pkg.go.dev/github.com/anchore/syft/cmd/syft?tab=versions +tools += syft=v1.22.0 +# https://github.com/cert-manager/helm-tool/releases +tools += helm-tool=v0.5.3 +# https://github.com/cert-manager/image-tool/releases +tools += image-tool=v0.0.2 +# https://github.com/cert-manager/cmctl/releases +tools += cmctl=v2.1.1 +# https://pkg.go.dev/github.com/cert-manager/release/cmd/cmrel?tab=versions +tools += cmrel=e3cbe5171488deda000145003e22567bdce622ea +# https://pkg.go.dev/github.com/golangci/golangci-lint/v2/cmd/golangci-lint?tab=versions +tools += golangci-lint=v2.1.2 +# https://pkg.go.dev/golang.org/x/vuln?tab=versions +tools += govulncheck=v1.1.4 +# https://pkg.go.dev/github.com/operator-framework/operator-sdk/cmd/operator-sdk?tab=versions +tools += operator-sdk=v1.39.2 +# https://pkg.go.dev/github.com/cli/cli/v2?tab=versions +tools += gh=v2.69.0 +# https://github.com/redhat-openshift-ecosystem/openshift-preflight/releases +tools += preflight=1.12.1 +# https://github.com/daixiang0/gci/releases +tools += gci=v0.13.6 +# https://github.com/google/yamlfmt/releases +tools += yamlfmt=v0.16.0 + +# https://pkg.go.dev/k8s.io/code-generator/cmd?tab=versions +K8S_CODEGEN_VERSION := v0.32.3 +tools += client-gen=$(K8S_CODEGEN_VERSION) +tools += deepcopy-gen=$(K8S_CODEGEN_VERSION) +tools += informer-gen=$(K8S_CODEGEN_VERSION) +tools += lister-gen=$(K8S_CODEGEN_VERSION) +tools += applyconfiguration-gen=$(K8S_CODEGEN_VERSION) +tools += defaulter-gen=$(K8S_CODEGEN_VERSION) +tools += conversion-gen=$(K8S_CODEGEN_VERSION) +# https://github.com/kubernetes/kube-openapi +tools += openapi-gen=c8a335a9a2ffc5aff16dfef74896a1ee34eb235d + +# https://raw.githubusercontent.com/kubernetes-sigs/controller-tools/master/envtest-releases.yaml +KUBEBUILDER_ASSETS_VERSION := v1.32.0 +tools += etcd=$(KUBEBUILDER_ASSETS_VERSION) +tools += kube-apiserver=$(KUBEBUILDER_ASSETS_VERSION) + +# Additional tools can be defined to reuse the tooling in this file +ADDITIONAL_TOOLS ?= +tools += $(ADDITIONAL_TOOLS) + +# https://go.dev/dl/ +VENDORED_GO_VERSION := 1.24.2 + +# Print the go version which can be used in GH actions +.PHONY: print-go-version +print-go-version: + @echo result=$(VENDORED_GO_VERSION) + +# When switching branches which use different versions of the tools, we +# need a way to re-trigger the symlinking from $(bin_dir)/downloaded to $(bin_dir)/tools. +$(bin_dir)/scratch/%_VERSION: FORCE | $(bin_dir)/scratch + @test "$($*_VERSION)" == "$(shell cat $@ 2>/dev/null)" || echo $($*_VERSION) > $@ + +# --silent = don't print output like progress meters +# --show-error = but do print errors when they happen +# --fail = exit with a nonzero error code without the response from the server when there's an HTTP error +# --location = follow redirects from the server +# --retry = the number of times to retry a failed attempt to connect +# --retry-connrefused = retry even if the initial connection was refused +CURL := curl --silent --show-error --fail --location --retry 10 --retry-connrefused + +# LN is expected to be an atomic action, meaning that two Make processes +# can run the "link $(DOWNLOAD_DIR)/tools/xxx@$(XXX_VERSION)_$(HOST_OS)_$(HOST_ARCH) +# to $(bin_dir)/tools/xxx" operation simultaneously without issues (both +# will perform the action and the second time the link will be overwritten). +# +# -s = Create a symbolic link +# -f = Force the creation of the link (replace existing links) +# -n = If destination already exists, replace it, don't use it as a directory to create a new link inside +LN := ln -fsn + +upper_map := a:A b:B c:C d:D e:E f:F g:G h:H i:I j:J k:K l:L m:M n:N o:O p:P q:Q r:R s:S t:T u:U v:V w:W x:X y:Y z:Z +uc = $(strip \ + $(eval __upper := $1) \ + $(foreach p,$(upper_map), \ + $(eval __upper := $(subst $(word 1,$(subst :, ,$p)),$(word 2,$(subst :, ,$p)),$(__upper))) \ + ) \ + )$(__upper) + +tool_names := + +# for each item `xxx` in the tools variable: +# - a $(XXX_VERSION) variable is generated +# -> this variable contains the version of the tool +# - a $(NEEDS_XXX) variable is generated +# -> this variable contains the target name for the tool, +# which is the relative path of the binary, this target +# should be used when adding the tool as a dependency to +# your target, you can't use $(XXX) as a dependency because +# make does not support an absolute path as a dependency +# - a $(XXX) variable is generated +# -> this variable contains the absolute path of the binary, +# the absolute path should be used when executing the binary +# in targets or in scripts, because it is agnostic to the +# working directory +# - an unversioned target $(bin_dir)/tools/xxx is generated that +# creates a link to the corresponding versioned target: +# $(DOWNLOAD_DIR)/tools/xxx@$(XXX_VERSION)_$(HOST_OS)_$(HOST_ARCH) +define tool_defs +tool_names += $1 + +$(call uc,$1)_VERSION ?= $2 +NEEDS_$(call uc,$1) := $$(bin_dir)/tools/$1 +$(call uc,$1) := $$(CURDIR)/$$(bin_dir)/tools/$1 + +$$(bin_dir)/tools/$1: $$(bin_dir)/scratch/$(call uc,$1)_VERSION | $$(DOWNLOAD_DIR)/tools/$1@$$($(call uc,$1)_VERSION)_$$(HOST_OS)_$$(HOST_ARCH) $$(bin_dir)/tools + @cd $$(dir $$@) && $$(LN) $$(patsubst $$(bin_dir)/%,../%,$$(word 1,$$|)) $$(notdir $$@) + @touch $$@ # making sure the target of the symlink is newer than *_VERSION +endef + +$(foreach tool,$(tools),$(eval $(call tool_defs,$(word 1,$(subst =, ,$(tool))),$(word 2,$(subst =, ,$(tool)))))) + +###### +# Go # +###### + +# $(NEEDS_GO) is a target that is set as an order-only prerequisite in +# any target that calls $(GO), e.g.: +# +# $(bin_dir)/tools/crane: $(NEEDS_GO) +# $(GO) build -o $(bin_dir)/tools/crane +# +# $(NEEDS_GO) is empty most of the time, except when running "make vendor-go" +# or when "make vendor-go" was previously run, in which case $(NEEDS_GO) is set +# to $(bin_dir)/tools/go, since $(bin_dir)/tools/go is a prerequisite of +# any target depending on Go when "make vendor-go" was run. + +detected_vendoring := $(findstring vendor-go,$(MAKECMDGOALS))$(shell [ -f $(bin_dir)/tools/go ] && echo yes) +export VENDOR_GO ?= $(detected_vendoring) + +ifeq ($(VENDOR_GO),) +.PHONY: __require-go +ifneq ($(shell command -v go >/dev/null || echo notfound),) +__require-go: + @:$(error "$(GO) (or run 'make vendor-go')") +endif +GO := go +NEEDS_GO = __require-go +else +export GOROOT := $(CURDIR)/$(bin_dir)/tools/goroot +export PATH := $(CURDIR)/$(bin_dir)/tools/goroot/bin:$(PATH) +GO := $(CURDIR)/$(bin_dir)/tools/go +NEEDS_GO := $(bin_dir)/tools/go +MAKE := $(MAKE) vendor-go +endif + +.PHONY: vendor-go +## By default, this Makefile uses the system's Go. You can use a "vendored" +## version of Go that will get downloaded by running this command once. To +## disable vendoring, run "make unvendor-go". When vendoring is enabled, +## you will want to set the following: +## +## export PATH="$PWD/$(bin_dir)/tools:$PATH" +## export GOROOT="$PWD/$(bin_dir)/tools/goroot" +## @category [shared] Tools +vendor-go: $(bin_dir)/tools/go + +.PHONY: unvendor-go +unvendor-go: $(bin_dir)/tools/go + rm -rf $(bin_dir)/tools/go $(bin_dir)/tools/goroot + +.PHONY: which-go +## Print the version and path of go which will be used for building and +## testing in Makefile commands. Vendored go will have a path in ./bin +## @category [shared] Tools +which-go: | $(NEEDS_GO) + @$(GO) version + @echo "go binary used for above version information: $(GO)" + +$(bin_dir)/tools/go: $(bin_dir)/scratch/VENDORED_GO_VERSION | $(bin_dir)/tools/goroot $(bin_dir)/tools + @cd $(dir $@) && $(LN) ./goroot/bin/go $(notdir $@) + @touch $@ # making sure the target of the symlink is newer than *_VERSION + +# The "_" in "_bin" prevents "go mod tidy" from trying to tidy the vendored goroot. +$(bin_dir)/tools/goroot: $(bin_dir)/scratch/VENDORED_GO_VERSION | $(GOVENDOR_DIR)/go@$(VENDORED_GO_VERSION)_$(HOST_OS)_$(HOST_ARCH)/goroot $(bin_dir)/tools + @cd $(dir $@) && $(LN) $(patsubst $(bin_dir)/%,../%,$(word 1,$|)) $(notdir $@) + @touch $@ # making sure the target of the symlink is newer than *_VERSION + +# Extract the tar to the $(GOVENDOR_DIR) directory, this directory is not cached across CI runs. +$(GOVENDOR_DIR)/go@$(VENDORED_GO_VERSION)_$(HOST_OS)_$(HOST_ARCH)/goroot: | $(DOWNLOAD_DIR)/tools/go@$(VENDORED_GO_VERSION)_$(HOST_OS)_$(HOST_ARCH).tar.gz + @source $(lock_script) $@; \ + mkdir -p $(outfile).dir; \ + tar xzf $| -C $(outfile).dir; \ + mv $(outfile).dir/go $(outfile); \ + rm -rf $(outfile).dir + +################### +# go dependencies # +################### + +go_dependencies := +go_dependencies += ginkgo=github.com/onsi/ginkgo/v2/ginkgo +go_dependencies += controller-gen=sigs.k8s.io/controller-tools/cmd/controller-gen +go_dependencies += goimports=golang.org/x/tools/cmd/goimports +go_dependencies += go-licenses=github.com/google/go-licenses/v2 +go_dependencies += gotestsum=gotest.tools/gotestsum +go_dependencies += kustomize=sigs.k8s.io/kustomize/kustomize/v4 +go_dependencies += gojq=github.com/itchyny/gojq/cmd/gojq +go_dependencies += crane=github.com/google/go-containerregistry/cmd/crane +go_dependencies += protoc-gen-go=google.golang.org/protobuf/cmd/protoc-gen-go +go_dependencies += cosign=github.com/sigstore/cosign/v2/cmd/cosign +go_dependencies += boilersuite=github.com/cert-manager/boilersuite +go_dependencies += gomarkdoc=github.com/princjef/gomarkdoc/cmd/gomarkdoc +go_dependencies += oras=oras.land/oras/cmd/oras +go_dependencies += klone=github.com/cert-manager/klone +go_dependencies += goreleaser=github.com/goreleaser/goreleaser +go_dependencies += syft=github.com/anchore/syft/cmd/syft +go_dependencies += client-gen=k8s.io/code-generator/cmd/client-gen +go_dependencies += deepcopy-gen=k8s.io/code-generator/cmd/deepcopy-gen +go_dependencies += informer-gen=k8s.io/code-generator/cmd/informer-gen +go_dependencies += lister-gen=k8s.io/code-generator/cmd/lister-gen +go_dependencies += applyconfiguration-gen=k8s.io/code-generator/cmd/applyconfiguration-gen +go_dependencies += defaulter-gen=k8s.io/code-generator/cmd/defaulter-gen +go_dependencies += conversion-gen=k8s.io/code-generator/cmd/conversion-gen +go_dependencies += openapi-gen=k8s.io/kube-openapi/cmd/openapi-gen +go_dependencies += helm-tool=github.com/cert-manager/helm-tool +go_dependencies += image-tool=github.com/cert-manager/image-tool +go_dependencies += cmctl=github.com/cert-manager/cmctl/v2 +go_dependencies += cmrel=github.com/cert-manager/release/cmd/cmrel +go_dependencies += golangci-lint=github.com/golangci/golangci-lint/v2/cmd/golangci-lint +go_dependencies += govulncheck=golang.org/x/vuln/cmd/govulncheck +go_dependencies += operator-sdk=github.com/operator-framework/operator-sdk/cmd/operator-sdk +go_dependencies += gh=github.com/cli/cli/v2/cmd/gh +go_dependencies += gci=github.com/daixiang0/gci +go_dependencies += yamlfmt=github.com/google/yamlfmt/cmd/yamlfmt + +################# +# go build tags # +################# + +go_tags := + +# Additional Go dependencies can be defined to re-use the tooling in this file +ADDITIONAL_GO_DEPENDENCIES ?= +ADDITIONAL_GO_TAGS ?= +go_dependencies += $(ADDITIONAL_GO_DEPENDENCIES) +go_tags += $(ADDITIONAL_GO_TAGS) + +go_tags_init = go_tags_$1 := +$(call for_each_kv,go_tags_init,$(go_dependencies)) + +go_tags_defs = go_tags_$1 += $2 +$(call for_each_kv,go_tags_defs,$(go_tags)) + +go_tool_names := + +define go_dependency +go_tool_names += $1 +$$(DOWNLOAD_DIR)/tools/$1@$($(call uc,$1)_VERSION)_$(HOST_OS)_$(HOST_ARCH): | $$(NEEDS_GO) $$(DOWNLOAD_DIR)/tools + @source $$(lock_script) $$@; \ + mkdir -p $$(outfile).dir; \ + GOWORK=off GOBIN=$$(outfile).dir $$(GO) install --tags "$(strip $(go_tags_$1))" $2@$($(call uc,$1)_VERSION); \ + mv $$(outfile).dir/$1 $$(outfile); \ + rm -rf $$(outfile).dir +endef +$(call for_each_kv,go_dependency,$(go_dependencies)) + +################## +# File downloads # +################## + +go_linux_amd64_SHA256SUM=68097bd680839cbc9d464a0edce4f7c333975e27a90246890e9f1078c7e702ad +go_linux_arm64_SHA256SUM=756274ea4b68fa5535eb9fe2559889287d725a8da63c6aae4d5f23778c229f4b +go_darwin_amd64_SHA256SUM=238d9c065d09ff6af229d2e3b8b5e85e688318d69f4006fb85a96e41c216ea83 +go_darwin_arm64_SHA256SUM=b70f8b3c5b4ccb0ad4ffa5ee91cd38075df20fdbd953a1daedd47f50fbcff47a + +.PRECIOUS: $(DOWNLOAD_DIR)/tools/go@$(VENDORED_GO_VERSION)_$(HOST_OS)_$(HOST_ARCH).tar.gz +$(DOWNLOAD_DIR)/tools/go@$(VENDORED_GO_VERSION)_$(HOST_OS)_$(HOST_ARCH).tar.gz: | $(DOWNLOAD_DIR)/tools + @source $(lock_script) $@; \ + $(CURL) https://go.dev/dl/go$(VENDORED_GO_VERSION).$(HOST_OS)-$(HOST_ARCH).tar.gz -o $(outfile); \ + $(checkhash_script) $(outfile) $(go_$(HOST_OS)_$(HOST_ARCH)_SHA256SUM) + +helm_linux_amd64_SHA256SUM=90c28792a1eb5fb0b50028e39ebf826531ebfcf73f599050dbd79bab2f277241 +helm_linux_arm64_SHA256SUM=d78d76ec7625a94991e887ac049d93f44bd70e4876200b945f813c9e1ed1df7c +helm_darwin_amd64_SHA256SUM=3e240238c7a3a10efd37b8e16615b28e94ba5db5957247bb42009ba6d52f76e9 +helm_darwin_arm64_SHA256SUM=b843cebcbebc9eccb1e43aba9cca7693d32e9f2c4a35344990e3b7b381933948 + +.PRECIOUS: $(DOWNLOAD_DIR)/tools/helm@$(HELM_VERSION)_$(HOST_OS)_$(HOST_ARCH) +$(DOWNLOAD_DIR)/tools/helm@$(HELM_VERSION)_$(HOST_OS)_$(HOST_ARCH): | $(DOWNLOAD_DIR)/tools + @source $(lock_script) $@; \ + $(CURL) https://get.helm.sh/helm-$(HELM_VERSION)-$(HOST_OS)-$(HOST_ARCH).tar.gz -o $(outfile).tar.gz; \ + $(checkhash_script) $(outfile).tar.gz $(helm_$(HOST_OS)_$(HOST_ARCH)_SHA256SUM); \ + tar xfO $(outfile).tar.gz $(HOST_OS)-$(HOST_ARCH)/helm > $(outfile); \ + chmod +x $(outfile); \ + rm -f $(outfile).tar.gz + +kubectl_linux_amd64_SHA256SUM=ab209d0c5134b61486a0486585604a616a5bb2fc07df46d304b3c95817b2d79f +kubectl_linux_arm64_SHA256SUM=6c2c91e760efbf3fa111a5f0b99ba8975fb1c58bb3974eca88b6134bcf3717e2 +kubectl_darwin_amd64_SHA256SUM=b814c523071cd09e27c88d8c87c0e9b054ca0cf5c2b93baf3127750a4f194d5b +kubectl_darwin_arm64_SHA256SUM=a110af64fc31e2360dd0f18e4110430e6eedda1a64f96e9d89059740a7685bbd + +.PRECIOUS: $(DOWNLOAD_DIR)/tools/kubectl@$(KUBECTL_VERSION)_$(HOST_OS)_$(HOST_ARCH) +$(DOWNLOAD_DIR)/tools/kubectl@$(KUBECTL_VERSION)_$(HOST_OS)_$(HOST_ARCH): | $(DOWNLOAD_DIR)/tools + @source $(lock_script) $@; \ + $(CURL) https://dl.k8s.io/release/$(KUBECTL_VERSION)/bin/$(HOST_OS)/$(HOST_ARCH)/kubectl -o $(outfile); \ + $(checkhash_script) $(outfile) $(kubectl_$(HOST_OS)_$(HOST_ARCH)_SHA256SUM); \ + chmod +x $(outfile) + +kind_linux_amd64_SHA256SUM=a6875aaea358acf0ac07786b1a6755d08fd640f4c79b7a2e46681cc13f49a04b +kind_linux_arm64_SHA256SUM=5e4507a41c69679562610b1be82ba4f80693a7826f4e9c6e39236169a3e4f9d0 +kind_darwin_amd64_SHA256SUM=3435134325b6b9406ccfec417b13bb46a808fc74e9a2ebb0ca31b379c8293863 +kind_darwin_arm64_SHA256SUM=5240ca1acb587e1d0386532dd8c3373d81f5173b5af322919fc56f0cdd646596 + +.PRECIOUS: $(DOWNLOAD_DIR)/tools/kind@$(KIND_VERSION)_$(HOST_OS)_$(HOST_ARCH) +$(DOWNLOAD_DIR)/tools/kind@$(KIND_VERSION)_$(HOST_OS)_$(HOST_ARCH): | $(DOWNLOAD_DIR)/tools + @source $(lock_script) $@; \ + $(CURL) https://github.com/kubernetes-sigs/kind/releases/download/$(KIND_VERSION)/kind-$(HOST_OS)-$(HOST_ARCH) -o $(outfile); \ + $(checkhash_script) $(outfile) $(kind_$(HOST_OS)_$(HOST_ARCH)_SHA256SUM); \ + chmod +x $(outfile) + +vault_linux_amd64_SHA256SUM=a673933f5b02236b5e241e153c0d2fed15b47b48ad640ae886f8b3b567087a05 +vault_linux_arm64_SHA256SUM=27561edfbc3a59936c9a892d6a130ada5a224c91862523c1aa596bfd30cd45e3 +vault_darwin_amd64_SHA256SUM=3cb0eddebbe82622a20f5256890d71fcc1a4b0ff56561f9d68b29bb0e8b99ab6 +vault_darwin_arm64_SHA256SUM=392df64ce576fcc61508755b842160058e79fe438b30ac4b7fb64dd71f2ca781 + +.PRECIOUS: $(DOWNLOAD_DIR)/tools/vault@$(VAULT_VERSION)_$(HOST_OS)_$(HOST_ARCH) +$(DOWNLOAD_DIR)/tools/vault@$(VAULT_VERSION)_$(HOST_OS)_$(HOST_ARCH): | $(DOWNLOAD_DIR)/tools + @source $(lock_script) $@; \ + $(CURL) https://releases.hashicorp.com/vault/$(VAULT_VERSION)/vault_$(VAULT_VERSION)_$(HOST_OS)_$(HOST_ARCH).zip -o $(outfile).zip; \ + $(checkhash_script) $(outfile).zip $(vault_$(HOST_OS)_$(HOST_ARCH)_SHA256SUM); \ + unzip -qq -c $(outfile).zip > $(outfile); \ + chmod +x $(outfile); \ + rm -f $(outfile).zip + +azwi_linux_amd64_SHA256SUM=1824d5c0ff700e6aff38f99812670f0dbf828407da0e977cd6c2342e40a32ee6 +azwi_linux_arm64_SHA256SUM=80a5028c27168cea36c34baf893ba6431cc5bcfc5023c1bc8790bf6d8f984f3d +azwi_darwin_amd64_SHA256SUM=18b459c1d82cc92142485720ab797e98706cfaa7280c0308a5cd2d8220f9798b +azwi_darwin_arm64_SHA256SUM=09e8eb961e020ed0e9bfb93ddc30f06d2e3f99203e01f863be131528722d687c + +.PRECIOUS: $(DOWNLOAD_DIR)/tools/azwi@$(AZWI_VERSION)_$(HOST_OS)_$(HOST_ARCH) +$(DOWNLOAD_DIR)/tools/azwi@$(AZWI_VERSION)_$(HOST_OS)_$(HOST_ARCH): | $(DOWNLOAD_DIR)/tools + @source $(lock_script) $@; \ + $(CURL) https://github.com/Azure/azure-workload-identity/releases/download/$(AZWI_VERSION)/azwi-$(AZWI_VERSION)-$(HOST_OS)-$(HOST_ARCH).tar.gz -o $(outfile).tar.gz; \ + $(checkhash_script) $(outfile).tar.gz $(azwi_$(HOST_OS)_$(HOST_ARCH)_SHA256SUM); \ + tar xfO $(outfile).tar.gz azwi > $(outfile) && chmod 775 $(outfile); \ + rm -f $(outfile).tar.gz + +kubebuilder_tools_linux_amd64_SHA256SUM=2f8252f327e53f6a3ecb92280cc7eb373ca18fd9305a151a1a2d8f769b30feba +kubebuilder_tools_linux_arm64_SHA256SUM=b817a5e7c2a25d84c4c979b37a4797f93c4d316d9059c064f991e5f2fe869164 +kubebuilder_tools_darwin_amd64_SHA256SUM=a6c9005d55ef51d1266f74cf10333892b7c9514231b9a489efc4efb23ac76f9e +kubebuilder_tools_darwin_arm64_SHA256SUM=9108ab4e970aff81fd5ad8272a841e472a772f0ec347318a69f1925f1e8a7a54 + +.PRECIOUS: $(DOWNLOAD_DIR)/tools/kubebuilder_tools_$(KUBEBUILDER_ASSETS_VERSION)_$(HOST_OS)_$(HOST_ARCH).tar.gz +$(DOWNLOAD_DIR)/tools/kubebuilder_tools_$(KUBEBUILDER_ASSETS_VERSION)_$(HOST_OS)_$(HOST_ARCH).tar.gz: | $(DOWNLOAD_DIR)/tools + @source $(lock_script) $@; \ + $(CURL) https://github.com/kubernetes-sigs/controller-tools/releases/download/envtest-$(KUBEBUILDER_ASSETS_VERSION)/envtest-$(KUBEBUILDER_ASSETS_VERSION)-$(HOST_OS)-$(HOST_ARCH).tar.gz -o $(outfile); \ + $(checkhash_script) $(outfile) $(kubebuilder_tools_$(HOST_OS)_$(HOST_ARCH)_SHA256SUM) + +$(DOWNLOAD_DIR)/tools/etcd@$(KUBEBUILDER_ASSETS_VERSION)_$(HOST_OS)_$(HOST_ARCH): $(DOWNLOAD_DIR)/tools/kubebuilder_tools_$(KUBEBUILDER_ASSETS_VERSION)_$(HOST_OS)_$(HOST_ARCH).tar.gz | $(DOWNLOAD_DIR)/tools + @source $(lock_script) $@; \ + tar xfO $< controller-tools/envtest/etcd > $(outfile) && chmod 775 $(outfile) + +$(DOWNLOAD_DIR)/tools/kube-apiserver@$(KUBEBUILDER_ASSETS_VERSION)_$(HOST_OS)_$(HOST_ARCH): $(DOWNLOAD_DIR)/tools/kubebuilder_tools_$(KUBEBUILDER_ASSETS_VERSION)_$(HOST_OS)_$(HOST_ARCH).tar.gz | $(DOWNLOAD_DIR)/tools + @source $(lock_script) $@; \ + tar xfO $< controller-tools/envtest/kube-apiserver > $(outfile) && chmod 775 $(outfile) + +kyverno_linux_amd64_SHA256SUM=abd318dbb971ab6de2bbe3b7226f4a03230d5c9c651df8a29b6b5e085a55aeeb +kyverno_linux_arm64_SHA256SUM=33ccb628b939f075bb8b7f35f5c6ce672cb6733d5748f4df196fa0ce1c67b4d2 +kyverno_darwin_amd64_SHA256SUM=ade0f72c5e93a906396b82f2007226b507d2ff1e06e6b548756ec62a86efc941 +kyverno_darwin_arm64_SHA256SUM=af61da03d44c4e213e05c11981e80b511725c65911a09dc12f0371e06d190766 + +.PRECIOUS: $(DOWNLOAD_DIR)/tools/kyverno@$(KYVERNO_VERSION)_$(HOST_OS)_$(HOST_ARCH) +$(DOWNLOAD_DIR)/tools/kyverno@$(KYVERNO_VERSION)_$(HOST_OS)_$(HOST_ARCH): | $(DOWNLOAD_DIR)/tools + $(eval ARCH := $(subst amd64,x86_64,$(HOST_ARCH))) + + @source $(lock_script) $@; \ + $(CURL) https://github.com/kyverno/kyverno/releases/download/$(KYVERNO_VERSION)/kyverno-cli_$(KYVERNO_VERSION)_$(HOST_OS)_$(ARCH).tar.gz -o $(outfile).tar.gz; \ + $(checkhash_script) $(outfile).tar.gz $(kyverno_$(HOST_OS)_$(HOST_ARCH)_SHA256SUM); \ + tar xfO $(outfile).tar.gz kyverno > $(outfile); \ + chmod +x $(outfile); \ + rm -f $(outfile).tar.gz + +yq_linux_amd64_SHA256SUM=654d2943ca1d3be2024089eb4f270f4070f491a0610481d128509b2834870049 +yq_linux_arm64_SHA256SUM=ceea73d4c86f2e5c91926ee0639157121f5360da42beeb8357783d79c2cc6a1d +yq_darwin_amd64_SHA256SUM=cee787479550f0c94662e45251e7bb80f70e7071840bd19ce24542e9bcb4157a +yq_darwin_arm64_SHA256SUM=83edb55e254993f9043d01a1515205b54ffc2c7ce815a780573da64afaf2c71b + +.PRECIOUS: $(DOWNLOAD_DIR)/tools/yq@$(YQ_VERSION)_$(HOST_OS)_$(HOST_ARCH) +$(DOWNLOAD_DIR)/tools/yq@$(YQ_VERSION)_$(HOST_OS)_$(HOST_ARCH): | $(DOWNLOAD_DIR)/tools + @source $(lock_script) $@; \ + $(CURL) https://github.com/mikefarah/yq/releases/download/$(YQ_VERSION)/yq_$(HOST_OS)_$(HOST_ARCH) -o $(outfile); \ + $(checkhash_script) $(outfile) $(yq_$(HOST_OS)_$(HOST_ARCH)_SHA256SUM); \ + chmod +x $(outfile) + +ko_linux_amd64_SHA256SUM=4f0b979b59880b3232f47d79c940f2279165aaad15a11d7614e8a2c9e5c78c29 +ko_linux_arm64_SHA256SUM=9421ebe2a611bac846844bd34fed5c75fba7b36c8cb1d113ad8680c48f6106df +ko_darwin_amd64_SHA256SUM=888656c3f0028d4211654a9df57b003fe26f874b092776c83acace7aca8a73a4 +ko_darwin_arm64_SHA256SUM=d0b6bcc4f86c8d775688d1c21d416985ee557a85ad557c4a7d0e2d82b7cdbd92 + +.PRECIOUS: $(DOWNLOAD_DIR)/tools/ko@$(KO_VERSION)_$(HOST_OS)_$(HOST_ARCH) +$(DOWNLOAD_DIR)/tools/ko@$(KO_VERSION)_$(HOST_OS)_$(HOST_ARCH): | $(DOWNLOAD_DIR)/tools + $(eval OS := $(subst linux,Linux,$(subst darwin,Darwin,$(HOST_OS)))) + $(eval ARCH := $(subst amd64,x86_64,$(HOST_ARCH))) + + @source $(lock_script) $@; \ + $(CURL) https://github.com/ko-build/ko/releases/download/v$(KO_VERSION)/ko_$(KO_VERSION)_$(OS)_$(ARCH).tar.gz -o $(outfile).tar.gz; \ + $(checkhash_script) $(outfile).tar.gz $(ko_$(HOST_OS)_$(HOST_ARCH)_SHA256SUM); \ + tar xfO $(outfile).tar.gz ko > $(outfile); \ + chmod +x $(outfile); \ + rm -f $(outfile).tar.gz + +protoc_linux_amd64_SHA256SUM=327e9397c6fb3ea2a542513a3221334c6f76f7aa524a7d2561142b67b312a01f +protoc_linux_arm64_SHA256SUM=a3173ea338ef91b1605b88c4f8120d6c8ccf36f744d9081991d595d0d4352996 +protoc_darwin_amd64_SHA256SUM=65675c3bb874a2d5f0c941e61bce6175090be25fe466f0ec2d4a6f5978333624 +protoc_darwin_arm64_SHA256SUM=92728c650f6cf2b6c37891ae04ef5bc2d4b5f32c5fbbd101eda623f90bb95f63 + +.PRECIOUS: $(DOWNLOAD_DIR)/tools/protoc@$(PROTOC_VERSION)_$(HOST_OS)_$(HOST_ARCH) +$(DOWNLOAD_DIR)/tools/protoc@$(PROTOC_VERSION)_$(HOST_OS)_$(HOST_ARCH): | $(DOWNLOAD_DIR)/tools + $(eval OS := $(subst darwin,osx,$(HOST_OS))) + $(eval ARCH := $(subst arm64,aarch_64,$(subst amd64,x86_64,$(HOST_ARCH)))) + + @source $(lock_script) $@; \ + $(CURL) https://github.com/protocolbuffers/protobuf/releases/download/v$(PROTOC_VERSION)/protoc-$(PROTOC_VERSION)-$(OS)-$(ARCH).zip -o $(outfile).zip; \ + $(checkhash_script) $(outfile).zip $(protoc_$(HOST_OS)_$(HOST_ARCH)_SHA256SUM); \ + unzip -qq -c $(outfile).zip bin/protoc > $(outfile); \ + chmod +x $(outfile); \ + rm -f $(outfile).zip + +trivy_linux_amd64_SHA256SUM=31af7049380abcdc422094638cc33364593f0ccc89c955dd69d27aca288ae79c +trivy_linux_arm64_SHA256SUM=e3ff876fd6fa95919de02c38258acdb26b8f71be1b89c5cb7831f6ec29719ca5 +trivy_darwin_amd64_SHA256SUM=7454cd0d31dec55498baa2fbec9c4034c23ab52df45bb256c29297f2099129f8 +trivy_darwin_arm64_SHA256SUM=9ad04f68b7823109b93d3c6b4e069d932348bf2847e4ccd197787f87f346138e + +.PRECIOUS: $(DOWNLOAD_DIR)/tools/trivy@$(TRIVY_VERSION)_$(HOST_OS)_$(HOST_ARCH) +$(DOWNLOAD_DIR)/tools/trivy@$(TRIVY_VERSION)_$(HOST_OS)_$(HOST_ARCH): | $(DOWNLOAD_DIR)/tools + $(eval OS := $(subst linux,Linux,$(subst darwin,macOS,$(HOST_OS)))) + $(eval ARCH := $(subst amd64,64bit,$(subst arm64,ARM64,$(HOST_ARCH)))) + + @source $(lock_script) $@; \ + $(CURL) https://github.com/aquasecurity/trivy/releases/download/$(TRIVY_VERSION)/trivy_$(patsubst v%,%,$(TRIVY_VERSION))_$(OS)-$(ARCH).tar.gz -o $(outfile).tar.gz; \ + $(checkhash_script) $(outfile).tar.gz $(trivy_$(HOST_OS)_$(HOST_ARCH)_SHA256SUM); \ + tar xfO $(outfile).tar.gz trivy > $(outfile); \ + chmod +x $(outfile); \ + rm $(outfile).tar.gz + +ytt_linux_amd64_SHA256SUM=61ad01f1df9cc8344c786e93acb1f5707ded9e4b52e4ec55a0f6637f2af53bae +ytt_linux_arm64_SHA256SUM=ae0bdc3aca64e71276f59679ea9253be5f88fc6880937ae1de3dd42a00492a8c +ytt_darwin_amd64_SHA256SUM=a25dd1c8b74f276a6ef2b4c2d0b493f8aaf87839e90762aa3c444e0b7eec95c8 +ytt_darwin_arm64_SHA256SUM=4fa87a81af4634099c3a1c7396d4d0f0b6fee9f4854b37a6a547d55bfca897c5 + +.PRECIOUS: $(DOWNLOAD_DIR)/tools/ytt@$(YTT_VERSION)_$(HOST_OS)_$(HOST_ARCH) +$(DOWNLOAD_DIR)/tools/ytt@$(YTT_VERSION)_$(HOST_OS)_$(HOST_ARCH): | $(DOWNLOAD_DIR)/tools + @source $(lock_script) $@; \ + $(CURL) -sSfL https://github.com/vmware-tanzu/carvel-ytt/releases/download/$(YTT_VERSION)/ytt-$(HOST_OS)-$(HOST_ARCH) -o $(outfile); \ + $(checkhash_script) $(outfile) $(ytt_$(HOST_OS)_$(HOST_ARCH)_SHA256SUM); \ + chmod +x $(outfile) + +rclone_linux_amd64_SHA256SUM=231841f8d8029ae6cfca932b601b3b50d0e2c3c2cb9da3166293f1c3eae7d79c +rclone_linux_arm64_SHA256SUM=a03de8f700fcda7a1aef6b568f88d44218b698fb4e1637596c024d341bb24124 +rclone_darwin_amd64_SHA256SUM=ebe1d5e13b0255605becfafbfa7c1809bc985272bcea0b342675c7e29c57629b +rclone_darwin_arm64_SHA256SUM=09b42295c30ba6b41a0d9c6741e4b5769de9ddecf5069f93c33f01bb46caa228 + +.PRECIOUS: $(DOWNLOAD_DIR)/tools/rclone@$(RCLONE_VERSION)_$(HOST_OS)_$(HOST_ARCH) +$(DOWNLOAD_DIR)/tools/rclone@$(RCLONE_VERSION)_$(HOST_OS)_$(HOST_ARCH): | $(DOWNLOAD_DIR)/tools + $(eval OS := $(subst darwin,osx,$(HOST_OS))) + + @source $(lock_script) $@; \ + $(CURL) https://github.com/rclone/rclone/releases/download/$(RCLONE_VERSION)/rclone-$(RCLONE_VERSION)-$(OS)-$(HOST_ARCH).zip -o $(outfile).zip; \ + $(checkhash_script) $(outfile).zip $(rclone_$(HOST_OS)_$(HOST_ARCH)_SHA256SUM); \ + unzip -p $(outfile).zip rclone-$(RCLONE_VERSION)-$(OS)-$(HOST_ARCH)/rclone > $(outfile); \ + chmod +x $(outfile); \ + rm -f $(outfile).zip + +istioctl_linux_amd64_SHA256SUM=dcdd18d94e398b49221c33d723a2d0bf2d022e795655dd7ce22b8b98a8982a8c +istioctl_linux_arm64_SHA256SUM=aec291d524239822779abc1ec53f141740d693b5a125599e8d6a92c0d443559f +istioctl_darwin_amd64_SHA256SUM=fc2424008654bc2172ebe7646d5af68fd511b0a049f92216b1859d8a0b62d36d +istioctl_darwin_arm64_SHA256SUM=503e3af5d9d713b464dd33ca3308f52843e835804e55a2da8b30f1959b5bb45c + +.PRECIOUS: $(DOWNLOAD_DIR)/tools/istioctl@$(ISTIOCTL_VERSION)_$(HOST_OS)_$(HOST_ARCH) +$(DOWNLOAD_DIR)/tools/istioctl@$(ISTIOCTL_VERSION)_$(HOST_OS)_$(HOST_ARCH): | $(DOWNLOAD_DIR)/tools + $(eval OS := $(subst darwin,osx,$(HOST_OS))) + + @source $(lock_script) $@; \ + $(CURL) https://github.com/istio/istio/releases/download/$(ISTIOCTL_VERSION)/istio-$(ISTIOCTL_VERSION)-$(OS)-$(HOST_ARCH).tar.gz -o $(outfile).tar.gz; \ + $(checkhash_script) $(outfile).tar.gz $(istioctl_$(HOST_OS)_$(HOST_ARCH)_SHA256SUM); \ + tar xfO $(outfile).tar.gz istio-$(ISTIOCTL_VERSION)/bin/istioctl > $(outfile); \ + chmod +x $(outfile); \ + rm $(outfile).tar.gz + +preflight_linux_amd64_SHA256SUM=ee92573f38929be67c7bda91dad614ac1b7d1dd81fa8bd15dfe01e385a540856 +preflight_linux_arm64_SHA256SUM=1f4d199386e5152e59b36acb42fb870ffe7a70b4fe70b49b19f8f73c0f6382ce + +# Currently there are no official releases for darwin, you cannot submit results +# on non-official binaries, but we can still run tests. +# +# Once https://github.com/redhat-openshift-ecosystem/openshift-preflight/pull/942 is merged +# we can remove this darwin specific hack +.PRECIOUS: $(DOWNLOAD_DIR)/tools/preflight@$(PREFLIGHT_VERSION)_darwin_$(HOST_ARCH) +$(DOWNLOAD_DIR)/tools/preflight@$(PREFLIGHT_VERSION)_darwin_$(HOST_ARCH): | $(DOWNLOAD_DIR)/tools + @source $(lock_script) $@; \ + mkdir -p $(outfile).dir; \ + GOWORK=off GOBIN=$(outfile).dir $(GO) install github.com/redhat-openshift-ecosystem/openshift-preflight/cmd/preflight@$(PREFLIGHT_VERSION); \ + mv $(outfile).dir/preflight $(outfile); \ + rm -rf $(outfile).dir + +.PRECIOUS: $(DOWNLOAD_DIR)/tools/preflight@$(PREFLIGHT_VERSION)_linux_$(HOST_ARCH) +$(DOWNLOAD_DIR)/tools/preflight@$(PREFLIGHT_VERSION)_linux_$(HOST_ARCH): | $(DOWNLOAD_DIR)/tools + @source $(lock_script) $@; \ + $(CURL) https://github.com/redhat-openshift-ecosystem/openshift-preflight/releases/download/$(PREFLIGHT_VERSION)/preflight-linux-$(HOST_ARCH) -o $(outfile); \ + $(checkhash_script) $(outfile) $(preflight_linux_$(HOST_ARCH)_SHA256SUM); \ + chmod +x $(outfile) + +################# +# Other Targets # +################# + +# Although we "vendor" most tools in $(bin_dir)/tools, we still require some binaries +# to be available on the system. The vendor-go MAKECMDGOALS trick prevents the +# check for the presence of Go when 'make vendor-go' is run. + +# Gotcha warning: MAKECMDGOALS only contains what the _top level_ make invocation used, and doesn't look at target dependencies +# i.e. if we have a target "abc: vendor-go test" and run "make abc", we'll get an error +# about go being missing even though abc itself depends on vendor-go! +# That means we need to pass vendor-go at the top level if go is not installed (i.e. "make vendor-go abc") + +missing=$(shell (command -v curl >/dev/null || echo curl) \ + && (command -v sha256sum >/dev/null || command -v shasum >/dev/null || echo sha256sum) \ + && (command -v git >/dev/null || echo git) \ + && (command -v xargs >/dev/null || echo xargs) \ + && (command -v bash >/dev/null || echo bash)) +ifneq ($(missing),) +$(error Missing required tools: $(missing)) +endif + +non_go_tool_names := $(filter-out $(go_tool_names),$(tool_names)) + +.PHONY: non-go-tools +## Download and setup all Go tools +## @category [shared] Tools +non-go-tools: $(non_go_tool_names:%=$(bin_dir)/tools/%) + +.PHONY: go-tools +## Download and setup all Non-Go tools +## NOTE: this target is also used to learn the shas of +## these tools (see scripts/learn_tools_shas.sh in the +## Makefile modules repo) +## @category [shared] Tools +go-tools: $(go_tool_names:%=$(bin_dir)/tools/%) + +.PHONY: tools +## Download and setup all tools +## @category [shared] Tools +tools: non-go-tools go-tools diff --git a/make/_shared/tools/util/checkhash.sh b/make/_shared/tools/util/checkhash.sh new file mode 100755 index 00000000..62e5489b --- /dev/null +++ b/make/_shared/tools/util/checkhash.sh @@ -0,0 +1,58 @@ +#!/usr/bin/env bash + +# Copyright 2023 The cert-manager Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -o errexit +set -o nounset +set -o pipefail + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" + +# This script takes the hash of its first argument and verifies it against the +# hex hash given in its second argument + +function usage_and_exit() { + echo "usage: $0 " + echo "or: LEARN_FILE= $0 " + exit 1 +} + +HASH_TARGET=${1:-} +EXPECTED_HASH=${2:-} + +if [[ -z $HASH_TARGET ]]; then + usage_and_exit +fi + +if [[ -z $EXPECTED_HASH ]]; then + usage_and_exit +fi + +SHASUM=$("${SCRIPT_DIR}/hash.sh" "$HASH_TARGET") + +if [[ "$SHASUM" == "$EXPECTED_HASH" ]]; then + exit 0 +fi + +# When running 'make learn-sha-tools', we don't want this script to fail. +# Instead we log what sha values are wrong, so the make.mk file can be updated. + +if [ "${LEARN_FILE:-}" != "" ]; then + echo "s/$EXPECTED_HASH/$SHASUM/g" >> "${LEARN_FILE:-}" + exit 0 +fi + +echo "invalid checksum for \"$HASH_TARGET\": wanted \"$EXPECTED_HASH\" but got \"$SHASUM\"" +exit 1 diff --git a/make/_shared/tools/util/hash.sh b/make/_shared/tools/util/hash.sh new file mode 100755 index 00000000..21d006fc --- /dev/null +++ b/make/_shared/tools/util/hash.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash + +# Copyright 2023 The cert-manager Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -o errexit +set -o nounset +set -o pipefail + +# This script is a wrapper for outputting purely the sha256 hash of the input file, +# ideally in a portable way. + +case "$(uname -s)" in + Darwin*) shasum -a 256 "$1";; + *) sha256sum "$1" +esac | cut -d" " -f1 \ No newline at end of file diff --git a/make/_shared/tools/util/lock.sh b/make/_shared/tools/util/lock.sh new file mode 100755 index 00000000..0b89fda7 --- /dev/null +++ b/make/_shared/tools/util/lock.sh @@ -0,0 +1,70 @@ +#!/usr/bin/env bash + +# Copyright 2023 The cert-manager Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -o errexit +set -o nounset +set -o pipefail + +# This script is used to lock a file while it is being downloaded. It prevents +# multiple processes from downloading the same file at the same time or from reading +# a half-downloaded file. +# We need this solution because we have recursive $(MAKE) calls in our makefile +# which each will try to download a set of tools. To prevent them from all downloading +# the same files, we re-use the same downloads folder for all $(MAKE) invocations and +# use this script to deduplicate the download processes. + +finalfile="$1" +lockfile="$finalfile.lock" + +# On macOS, flock is not installed, we just skip locking in that case, +# this means that running verify in parallel without downloading all +# tools first will not work. +flock_installed=$(command -v flock >/dev/null && echo "yes" || echo "no") + +if [[ "$flock_installed" == "yes" ]]; then + mkdir -p "$(dirname "$lockfile")" + touch "$lockfile" + exec {FD}<>"$lockfile" + + # wait for the file to be unlocked + if ! flock -x $FD; then + echo "Failed to obtain a lock for $lockfile" + exit 1 + fi +fi + +# now that we have the lock, check if file is already there +if [[ -e "$finalfile" ]]; then + exit 0 +fi + +# use a temporary file to prevent Make from thinking the file is ready +# while in reality is is only a partial download +# shellcheck disable=SC2034 +outfile="$finalfile.tmp" + +finish() { + rv=$? + if [[ $rv -eq 0 ]]; then + mv "$outfile" "$finalfile" + echo "[info]: downloaded $finalfile" + else + rm -rf "$outfile" || true + rm -rf "$finalfile" || true + fi + rm -rf "$lockfile" || true +} +trap finish EXIT SIGINT diff --git a/make/config/kaniko/dockerfile b/make/config/kaniko/dockerfile new file mode 100644 index 00000000..5aaf0a28 --- /dev/null +++ b/make/config/kaniko/dockerfile @@ -0,0 +1 @@ +FROM hello-world diff --git a/make/config/kind/cluster.yaml b/make/config/kind/cluster.yaml new file mode 100644 index 00000000..f2da42ce --- /dev/null +++ b/make/config/kind/cluster.yaml @@ -0,0 +1,34 @@ +apiVersion: kind.x-k8s.io/v1alpha4 +kind: Cluster +kubeadmConfigPatches: + - | + kind: ClusterConfiguration + metadata: + name: config + etcd: + local: + extraArgs: + unsafe-no-fsync: "true" + networking: + serviceSubnet: 10.0.0.0/16 +nodes: +- role: control-plane + + extraPortMappings: + - containerPort: 30443 + hostPort: 30443 + listenAddress: "0.0.0.0" + protocol: tcp + + extraMounts: + - hostPath: {{KIND_IMAGES}} + containerPath: /mounted_images + - hostPath: {{KANIKO_DIR}} + containerPath: /workspace + +containerdConfigPatches: +- |- + [plugins."io.containerd.grpc.v1.cri".registry.mirrors."docker-registry.registry.svc.cluster.local:5000"] + endpoint = ["http://localhost:30443"] + [plugins."io.containerd.grpc.v1.cri".registry.configs."docker-registry.registry.svc.cluster.local:5000".tls] + insecure_skip_verify = true diff --git a/make/config/registry/docker-registry-values.yaml b/make/config/registry/docker-registry-values.yaml new file mode 100644 index 00000000..e175502d --- /dev/null +++ b/make/config/registry/docker-registry-values.yaml @@ -0,0 +1,24 @@ +configData: + version: 0.1 + log: + fields: + service: registry + storage: + cache: + blobdescriptor: inmemory + http: + addr: :5000 + headers: + X-Content-Type-Options: [nosniff] + debug: + addr: :5001 + prometheus: + enabled: false + path: /metrics + health: + storagedriver: + enabled: true + interval: 10s + threshold: 3 + secrets: + htpasswd: "user:$2y$05$zDST8320bM1EcRhcj2nsheN7conl3kkOWxPXESn.HW/1lnThDmoBG" diff --git a/make/config/version-checker-values.yaml b/make/config/version-checker-values.yaml new file mode 100644 index 00000000..5f99bde6 --- /dev/null +++ b/make/config/version-checker-values.yaml @@ -0,0 +1,7 @@ +versionChecker: + testAllContainers: false +selfhosted: + - name: registry + host: http://docker-registry.registry.svc.cluster.local:5000 + username: user + password: password diff --git a/make/test-e2e.mk b/make/test-e2e.mk new file mode 100644 index 00000000..9a84f810 --- /dev/null +++ b/make/test-e2e.mk @@ -0,0 +1,67 @@ +# Copyright 2023 The cert-manager Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +.PHONY: e2e-setup-docker-registry +e2e-setup-docker-registry: | kind-cluster $(NEEDS_HELM) $(NEEDS_KUBECTL) + $(HELM) repo add twuni https://helm.twun.io + $(HELM) upgrade \ + --install \ + --create-namespace \ + -n registry \ + --wait \ + --set service.type=NodePort \ + --set service.nodePort=30443 \ + -f ./make/config/registry/docker-registry-values.yaml \ + docker-registry twuni/docker-registry >/dev/null + + +INSTALL_OPTIONS += --set image.repository=$(oci_manager_image_name_development) +# INSTALL_OPTIONS += -f ./make/config/version-checker-values.yaml + +# .PHONY: e2e-setup-deps +# e2e-setup-deps: | kind-cluster $(NEEDS_KUBECTL) +# $(KUBECTL) apply -f test/e2e/manifests/docker-credentials.yaml +# $(KUBECTL) apply -f test/e2e/manifests/kaniko.yaml +# $(KUBECTL) wait pod -lapp=e2e-kaniko --timeout=30s --for=jsonpath='{.status.containerStatuses[*].state.terminated.reason}'=Completed + +is_e2e_test= + +# The "install" target can be run on its own with any currently active cluster, +# we can't use any other cluster then a target containing "test-e2e" is run. +# When a "test-e2e*" target is run, the currently active cluster must be the kind +# cluster created by the "kind-cluster" target. +ifeq ($(findstring test-e2e,$(MAKECMDGOALS)),test-e2e) +is_e2e_test = yes +endif + + +ifdef is_e2e_test +install: kind-cluster oci-load-manager +endif + +# test-e2e-deps: e2e-setup-docker-registry +# test-e2e-deps: e2e-setup-deps +test-e2e-deps: INSTALL_OPTIONS += -f ./make/config/version-checker-values.yaml +test-e2e-deps: install + + + +.PHONY: test-e2e +## e2e end-to-end tests +## @category Testing +test-e2e: test-e2e-deps | kind-cluster $(NEEDS_GINKGO) $(NEEDS_KUBECTL) + $(GINKGO) \ + -v \ + --no-color \ + ./test/e2e/ diff --git a/make/test-unit.mk b/make/test-unit.mk new file mode 100644 index 00000000..61695fa2 --- /dev/null +++ b/make/test-unit.mk @@ -0,0 +1,29 @@ +# Copyright 2023 The cert-manager Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +.PHONY: test-unit +## Unit tests +## @category Testing +test-unit: | $(NEEDS_GOTESTSUM) $(NEEDS_GO) $(NEEDS_ETCD) $(NEEDS_KUBE-APISERVER) $(NEEDS_KUBECTL) $(ARTIFACTS) + KUBEBUILDER_ASSETS=$(CURDIR)/$(bin_dir)/tools \ + $(GOTESTSUM) \ + --junitfile=$(ARTIFACTS)/junit-go-e2e.xml \ + -- \ + -coverprofile=$(ARTIFACTS)/filtered.cov \ + ./cmd/... ./pkg/... \ + -- \ + -ldflags $(go_manager_ldflags) \ + -test.timeout 2m \ + + $(GO) tool cover -html=$(ARTIFACTS)/filtered.cov -o=$(ARTIFACTS)/filtered.html diff --git a/pkg/client/selfhosted/selfhosted.go b/pkg/client/selfhosted/selfhosted.go index 47982684..f393d601 100644 --- a/pkg/client/selfhosted/selfhosted.go +++ b/pkg/client/selfhosted/selfhosted.go @@ -34,6 +34,7 @@ const ( // HTTP headers to request API version dockerAPIv1Header = "application/vnd.docker.distribution.manifest.v1+json" + ociV1Header = "application/vnd.oci.image.manifest.v1+json" dockerAPIv2Header = "application/vnd.docker.distribution.manifest.v2+json" ) @@ -237,6 +238,7 @@ func (c *Client) doRequest(ctx context.Context, url, header string, obj interfac if len(header) > 0 { req.Header.Set("Accept", header) + req.Header.Add("Accept", ociV1Header) } resp, err := c.Do(req) diff --git a/test/e2e/e2e_test.go b/test/e2e/e2e_test.go new file mode 100644 index 00000000..5842c74c --- /dev/null +++ b/test/e2e/e2e_test.go @@ -0,0 +1,15 @@ +package e2e + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + _ "github.com/jetstack/version-checker/test/e2e/suite" +) + +func TestVersionChecker(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "VersionChecker Suite") +} diff --git a/test/e2e/manifests/docker-credentials.yaml b/test/e2e/manifests/docker-credentials.yaml new file mode 100644 index 00000000..835b79b6 --- /dev/null +++ b/test/e2e/manifests/docker-credentials.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: Secret +metadata: + name: regcred +type: kubernetes.io/dockerconfigjson +data: + .dockerconfigjson: eyJhdXRocyI6eyJkb2NrZXItcmVnaXN0cnkucmVnaXN0cnkuc3ZjLmNsdXN0ZXIubG9jYWw6NTAwMCI6eyJ1c2VybmFtZSI6InVzZXIiLCJwYXNzd29yZCI6InBhc3N3b3JkIiwiYXV0aCI6ImRYTmxjanB3WVhOemQyOXlaQT09In19fQ== diff --git a/test/e2e/manifests/docker-registry-values.yaml b/test/e2e/manifests/docker-registry-values.yaml new file mode 100644 index 00000000..e175502d --- /dev/null +++ b/test/e2e/manifests/docker-registry-values.yaml @@ -0,0 +1,24 @@ +configData: + version: 0.1 + log: + fields: + service: registry + storage: + cache: + blobdescriptor: inmemory + http: + addr: :5000 + headers: + X-Content-Type-Options: [nosniff] + debug: + addr: :5001 + prometheus: + enabled: false + path: /metrics + health: + storagedriver: + enabled: true + interval: 10s + threshold: 3 + secrets: + htpasswd: "user:$2y$05$zDST8320bM1EcRhcj2nsheN7conl3kkOWxPXESn.HW/1lnThDmoBG" diff --git a/test/e2e/manifests/image-from-reg.yaml b/test/e2e/manifests/image-from-reg.yaml new file mode 100644 index 00000000..d3e95800 --- /dev/null +++ b/test/e2e/manifests/image-from-reg.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: Pod +metadata: + name: image-from-reg + annotations: + enable.version-checker.io/test: "true" +spec: + containers: + - name: test + image: docker-registry.registry.svc.cluster.local:5000/my-app:0.0.1 + imagePullPolicy: Always + imagePullSecrets: + - name: regcred diff --git a/test/e2e/manifests/kaniko.yaml b/test/e2e/manifests/kaniko.yaml new file mode 100644 index 00000000..29014893 --- /dev/null +++ b/test/e2e/manifests/kaniko.yaml @@ -0,0 +1,65 @@ +apiVersion: v1 +kind: Pod +metadata: + name: kaniko-0 + labels: + app: e2e-kaniko +spec: + containers: + - name: kaniko + image: gcr.io/kaniko-project/executor:latest + args: ["--dockerfile=/workspace/dockerfile", + "--context=dir://workspace", + "--insecure=true", + "--destination=docker-registry.registry.svc.cluster.local:5000/my-app:0.0.1", + "--insecure-registry=docker-registry.registry.svc.cluster.local:5000", + "--push-retry=10"] + volumeMounts: + - name: docker-secret + mountPath: /kaniko/.docker + - name: dockerfile-storage + mountPath: /workspace + restartPolicy: Never + volumes: + - name: docker-secret + secret: + secretName: regcred + items: + - key: .dockerconfigjson + path: config.json + - name: dockerfile-storage + hostPath: + path: /workspace +--- +apiVersion: v1 +kind: Pod +metadata: + name: kaniko-1 + labels: + app: e2e-kaniko +spec: + containers: + - name: kaniko + image: gcr.io/kaniko-project/executor:latest + args: ["--dockerfile=/workspace/dockerfile", + "--context=dir://workspace", + "--insecure=true", + "--destination=docker-registry.registry.svc.cluster.local:5000/my-app:0.0.2", + "--insecure-registry=docker-registry.registry.svc.cluster.local:5000", + "--push-retry=10"] + volumeMounts: + - name: docker-secret + mountPath: /kaniko/.docker + - name: dockerfile-storage + mountPath: /workspace + restartPolicy: Never + volumes: + - name: docker-secret + secret: + secretName: regcred + items: + - key: .dockerconfigjson + path: config.json + - name: dockerfile-storage + hostPath: + path: /workspace \ No newline at end of file diff --git a/test/e2e/manifests/pod.yaml b/test/e2e/manifests/pod.yaml new file mode 100644 index 00000000..e1e55570 --- /dev/null +++ b/test/e2e/manifests/pod.yaml @@ -0,0 +1,27 @@ +apiVersion: v1 +kind: Pod +metadata: + name: kaniko +spec: + containers: + - name: kaniko + image: gcr.io/kaniko-project/executor:latest + args: ["--dockerfile=/workspace/dockerfile", + "--context=dir://workspace", + "--destination=docker-registry.registry.svc.cluster.local/version-checker:0.0.1"] + volumeMounts: + - name: kaniko-secret + mountPath: /kaniko/.docker + - name: dockerfile-storage + mountPath: /workspace + restartPolicy: Never + volumes: + - name: kaniko-secret + secret: + secretName: regcred + items: + - key: .dockerconfigjson + path: config.json + - name: dockerfile-storage + persistentVolumeClaim: + claimName: dockerfile-claim diff --git a/test/e2e/manifests/prom2json.yaml b/test/e2e/manifests/prom2json.yaml new file mode 100644 index 00000000..51655107 --- /dev/null +++ b/test/e2e/manifests/prom2json.yaml @@ -0,0 +1,14 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: prom2json +spec: + template: + spec: + containers: + - command: + - prom2json + - http://version-checker.version-checker:8080/metrics + image: prom/prom2json + name: prom2json + restartPolicy: Never \ No newline at end of file diff --git a/test/e2e/suite/doc.go b/test/e2e/suite/doc.go new file mode 100644 index 00000000..98fd94f3 --- /dev/null +++ b/test/e2e/suite/doc.go @@ -0,0 +1,21 @@ +/* +Copyright 2021 The cert-manager Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package suite + +import ( + _ "github.com/jetstack/version-checker/test/e2e/suite/selfhosted" +) diff --git a/test/e2e/suite/selfhosted/selfhosted.go b/test/e2e/suite/selfhosted/selfhosted.go new file mode 100644 index 00000000..c2b460e5 --- /dev/null +++ b/test/e2e/suite/selfhosted/selfhosted.go @@ -0,0 +1,153 @@ +package selfhosted + +import ( + "bytes" + "encoding/json" + "log" + "os/exec" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "github.com/itchyny/gojq" +) + +var _ = BeforeSuite(func() { + cmd := exec.Command("helm", "repo", "add", "twuni", "https://helm.twun.io") + cmd.Stdout = GinkgoWriter + cmd.Stderr = GinkgoWriter + Expect(cmd.Run()).NotTo(HaveOccurred()) + cmd = exec.Command("helm", "upgrade", "--install", "--create-namespace", "-n", "registry", "--wait", "--set", "service.type=NodePort", "--set", "service.nodePort=30443", "-f", "./manifests/docker-registry-values.yaml", "docker-registry", "twuni/docker-registry") + cmd.Stdout = GinkgoWriter + cmd.Stderr = GinkgoWriter + Expect(cmd.Run()).NotTo(HaveOccurred()) +}) + +var _ = AfterSuite(func() { + cmd := exec.Command("helm", "uninstall", "-n", "registry", "--wait", "docker-registry") + cmd.Stdout = GinkgoWriter + cmd.Stderr = GinkgoWriter + Expect(cmd.Run()).NotTo(HaveOccurred()) +}) + +var _ = Describe("version-checker selfhosted", func() { + BeforeEach(func() { + cmd := exec.Command("kubectl", "apply", "-f", "./manifests/kaniko.yaml", "-f", "./manifests/docker-credentials.yaml") + cmd.Stdout = GinkgoWriter + cmd.Stderr = GinkgoWriter + Expect(cmd.Run()).NotTo(HaveOccurred()) + cmd = exec.Command("kubectl", "wait", "pod", "-lapp=e2e-kaniko", "--timeout=30s", "--for=jsonpath={.status.containerStatuses[*].state.terminated.reason}=Completed") + cmd.Stdout = GinkgoWriter + cmd.Stderr = GinkgoWriter + Expect(cmd.Run()).NotTo(HaveOccurred()) + }) + AfterEach(func() { + cmd := exec.Command("kubectl", "delete", "-f", "./manifests/kaniko.yaml") + cmd.Stdout = GinkgoWriter + cmd.Stderr = GinkgoWriter + Expect(cmd.Run()).NotTo(HaveOccurred()) + }) + + JustBeforeEach(func() { + cmd := exec.Command("kubectl", "apply", "-f", "./manifests/prom2json.yaml") + cmd.Stdout = GinkgoWriter + cmd.Stderr = GinkgoWriter + Expect(cmd.Run()).NotTo(HaveOccurred()) + cmd = exec.Command("kubectl", "wait", "--for=condition=Complete", "--timeout=30s", "job", "-ljob-name=prom2json") + cmd.Stdout = GinkgoWriter + cmd.Stderr = GinkgoWriter + Expect(cmd.Run()).NotTo(HaveOccurred()) + }) + AfterEach(func() { + cmd := exec.Command("kubectl", "delete", "-f", "./manifests/prom2json.yaml") + cmd.Stdout = GinkgoWriter + cmd.Stderr = GinkgoWriter + Expect(cmd.Run()).NotTo(HaveOccurred()) + }) + + When("a Pod is deployed", func() { + BeforeEach(func() { + cmd := exec.Command("kubectl", "apply", "-f", "./manifests/image-from-reg.yaml") + cmd.Stdout = GinkgoWriter + cmd.Stderr = GinkgoWriter + Expect(cmd.Run()).NotTo(HaveOccurred()) + + }) + AfterEach(func() { + cmd := exec.Command("kubectl", "delete", "-f", "./manifests/image-from-reg.yaml") + cmd.Stdout = GinkgoWriter + cmd.Stderr = GinkgoWriter + Expect(cmd.Run()).NotTo(HaveOccurred()) + }) + + It("it should get the current version", func() { + buf := new(bytes.Buffer) + cmd := exec.Command("kubectl", "logs", "-ljob-name=prom2json") + cmd.Stdout = buf + cmd.Stderr = GinkgoWriter + Expect(cmd.Run()).NotTo(HaveOccurred()) + + //k logs -ljob-name=prom2json | jq '.[]|select(.name=="version_checker_is_latest_version")| .metrics[] | select(.labels.image=="docker-registry.registry.svc.cluster.local:5000/my-app") | .labels.current_version' + //k logs -ljob-name=prom2json | jq '.[]|select(.name=="version_checker_is_latest_version")| .metrics[] | select(.labels.image=="docker-registry.registry.svc.cluster.local:5000/my-app") | .labels.latest_version' + //k logs -ljob-name=prom2json | jq '.[]|select(.name=="version_checker_is_latest_version")| .metrics[] | select(.labels.image=="docker-registry.registry.svc.cluster.local:5000/my-app") | .value' + query, err := gojq.Parse(".[]|select(.name==\"version_checker_is_latest_version\")| .metrics[] | select(.labels.image==\"docker-registry.registry.svc.cluster.local:5000/my-app\") | .labels.current_version") + if err != nil { + log.Fatalln(err) + } + var result []interface{} + err = json.Unmarshal(buf.Bytes(), &result) + if err != nil { + log.Fatalln(err) + } + iter := query.Run(result) + for { + v, ok := iter.Next() + if !ok { + break + } + if err, ok := v.(error); ok { + if err, ok := err.(*gojq.HaltError); ok && err.Value() == nil { + break + } + log.Fatalln(err) + } + Expect(v).To(Equal("0.0.1")) + } + }) + + It("it should find a newer version", func() { + buf := new(bytes.Buffer) + cmd := exec.Command("kubectl", "logs", "-ljob-name=prom2json") + cmd.Stdout = buf + cmd.Stderr = GinkgoWriter + Expect(cmd.Run()).NotTo(HaveOccurred()) + + //k logs -ljob-name=prom2json | jq '.[]|select(.name=="version_checker_is_latest_version")| .metrics[] | select(.labels.image=="docker-registry.registry.svc.cluster.local:5000/my-app") | .labels.current_version' + //k logs -ljob-name=prom2json | jq '.[]|select(.name=="version_checker_is_latest_version")| .metrics[] | select(.labels.image=="docker-registry.registry.svc.cluster.local:5000/my-app") | .labels.latest_version' + //k logs -ljob-name=prom2json | jq '.[]|select(.name=="version_checker_is_latest_version")| .metrics[] | select(.labels.image=="docker-registry.registry.svc.cluster.local:5000/my-app") | .value' + query, err := gojq.Parse(".[]|select(.name==\"version_checker_is_latest_version\")| .metrics[] | select(.labels.image==\"docker-registry.registry.svc.cluster.local:5000/my-app\") | .labels.latest_version") + if err != nil { + log.Fatalln(err) + } + var result []interface{} + err = json.Unmarshal(buf.Bytes(), &result) + if err != nil { + log.Fatalln(err) + } + iter := query.Run(result) + for { + v, ok := iter.Next() + if !ok { + break + } + if err, ok := v.(error); ok { + if err, ok := err.(*gojq.HaltError); ok && err.Value() == nil { + break + } + log.Fatalln(err) + } + Expect(v).To(Equal("0.0.2")) + } + }) + }) +})