From e0e3d3ba9a813ea3614d24e704bfce8060fcde6a Mon Sep 17 00:00:00 2001
From: xueli <xueli@redhat.com>
Date: Sat, 28 Feb 2026 10:58:54 +0800
Subject: [PATCH 1/2] Standardize Dockerfiles and Makefile for building images

enrich .dockerignore and enhanced Dockerfile and Makefile
---
 .dockerignore |  77 +++++++++-
 Dockerfile    |  51 ++++---
 Makefile      | 393 +++++++++++++++++++++-----------------------------
 3 files changed, 271 insertions(+), 250 deletions(-)

diff --git a/.dockerignore b/.dockerignore
index 81d06cf7..cb9eaea0 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -1,9 +1,76 @@
-.git
-.gitignore
-.github
+# Build artifacts
 bin/
-docs/
-charts/
+*.exe
+*.dll
+*.so
+*.dylib
+
+# IDE and editor files
+.idea/
+.vscode/
+*.swp
+*.swo
+*~
+.DS_Store
+
+# Git
+.git/
+.gitignore
+.github/
+
+# Environment files
 .env
 .env.*
 
+# Local config files
+*.local.yaml
+
+# CI/CD files (not needed in container)
+.gitlab-ci.yml
+.travis.yml
+Jenkinsfile
+
+# Kubernetes and deployment files
+chart/
+charts/
+deployments/
+
+# License and owners
+LICENSE
+OWNERS
+CONTRIBUTING.md
+
+# Temporary files
+tmp/
+temp/
+*.tmp
+
+# Log files
+*.log
+
+# Test and coverage files
+coverage/
+*.out
+*.test
+*.prof
+
+# Linter config (not needed for build)
+.golangci.yml
+.golangciversion
+
+# AI assistant config
+.claude/
+
+# Dockerfile itself
+Dockerfile
+
+# Documentation (not needed in container)
+*.md
+!README.md
+docs/
+
+# Secrets (should never be in container image)
+secrets/
+
+# Test request files
+test-requests/
diff --git a/Dockerfile b/Dockerfile
index fdabcaf8..597a2839 100755
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,44 +1,61 @@
 ARG BASE_IMAGE=gcr.io/distroless/static-debian12:nonroot
 
-# OpenAPI generation stage
-FROM golang:1.25 AS builder
+FROM registry.access.redhat.com/ubi9/go-toolset:1.25 AS builder
 
 ARG GIT_SHA=unknown
 ARG GIT_DIRTY=""
+ARG BUILD_DATE=""
+ARG VERSION=""
 
+# Install make as root (UBI9 go-toolset doesn't include it), then switch back to non-root.
+USER root
+RUN dnf install -y make && dnf clean all
 WORKDIR /build
-
-# Copy go mod files
-COPY go.mod go.sum ./
-RUN go mod download
-
-# Copy source code
-COPY . .
-
-# Build binary
-RUN CGO_ENABLED=0 GOOS=linux make build
+RUN chown 1001:0 /build
+USER 1001
+
+# Install bingo tools (mockgen, oapi-codegen) under /build/.gobin and add to PATH
+# so "go generate" can find them. ENV persists for all subsequent RUN commands.
+ENV GOBIN=/build/.gobin
+RUN mkdir -p $GOBIN
+ENV PATH="${GOBIN}:${PATH}"
+
+COPY --chown=1001:0 go.mod go.sum ./
+RUN --mount=type=cache,target=/opt/app-root/src/go/pkg/mod,uid=1001 \
+    go mod download
+
+COPY --chown=1001:0 . .
+
+# CGO_ENABLED=0 produces a static binary required for distroless runtime.
+# For FIPS-compliant builds (CGO_ENABLED=1 + GOEXPERIMENT=boringcrypto), use a
+# runtime image with glibc (e.g. ubi9-micro) instead of distroless.
+RUN --mount=type=cache,target=/opt/app-root/src/go/pkg/mod,uid=1001 \
+    --mount=type=cache,target=/opt/app-root/src/.cache/go-build,uid=1001 \
+    mkdir -p $GOBIN && \
+    CGO_ENABLED=0 GOOS=linux \
+    GIT_SHA=${GIT_SHA} GIT_DIRTY=${GIT_DIRTY} BUILD_DATE=${BUILD_DATE} VERSION=${VERSION} \
+    make build
 
 # Runtime stage
 FROM ${BASE_IMAGE}
 
 WORKDIR /app
 
-# Copy binary from builder
 COPY --from=builder /build/bin/hyperfleet-api /app/hyperfleet-api
-
-# Copy OpenAPI schema for validation (uses the source spec, not the generated one)
 COPY --from=builder /build/openapi/openapi.yaml /app/openapi/openapi.yaml
 
-# Set default schema path (can be overridden by Helm for provider-specific schemas)
 ENV OPENAPI_SCHEMA_PATH=/app/openapi/openapi.yaml
 
+USER 65532:65532
+
 EXPOSE 8000
 
 ENTRYPOINT ["/app/hyperfleet-api"]
 CMD ["serve"]
 
+ARG VERSION=""
 LABEL name="hyperfleet-api" \
       vendor="Red Hat" \
-      version="0.0.1" \
+      version="${VERSION}" \
       summary="HyperFleet API - Cluster Lifecycle Management Service" \
       description="HyperFleet API for cluster lifecycle management"
diff --git a/Makefile b/Makefile
index 3d34e404..7dd46330 100755
--- a/Makefile
+++ b/Makefile
@@ -7,110 +7,92 @@ include .bingo/Variables.mk
 # Use ?= to allow Dockerfile to override (CGO_ENABLED=0 for Alpine-based dev images)
 CGO_ENABLED ?= 1
 
-# Enable users to override the golang used to accomodate custom installations
 GO ?= go
 
-# Version information for build metadata
-version:=$(shell date +%s)
+# Auto-detect container tool (podman preferred when available)
+CONTAINER_TOOL ?= $(shell command -v podman 2>/dev/null || command -v docker 2>/dev/null)
 
-# a tool for managing containers and images, etc. You can set it as docker
-container_tool ?= podman
+.PHONY: check-container-tool
+check-container-tool:
+ifndef CONTAINER_TOOL
+	@echo "Error: No container tool found (podman or docker)"
+	@echo ""
+	@echo "Please install one of:"
+	@echo "  brew install podman   # macOS"
+	@echo "  brew install docker   # macOS"
+	@echo "  dnf install podman    # Fedora/RHEL"
+	@exit 1
+endif
 
-# Image configuration
+# Version information
+GIT_SHA ?= $(shell git rev-parse --short HEAD 2>/dev/null || echo "unknown")
+GIT_DIRTY ?= $(shell [ -z "$$(git status --porcelain 2>/dev/null)" ] || echo "-modified")
+VERSION ?= $(GIT_SHA)$(GIT_DIRTY)
+BUILD_DATE ?= $(shell date -u +"%Y-%m-%dT%H:%M:%SZ")
+
+# Go build flags
+GOFLAGS ?= -trimpath
+LDFLAGS := -s -w \
+           -X github.com/openshift-hyperfleet/hyperfleet-api/pkg/api.Version=$(VERSION) \
+           -X github.com/openshift-hyperfleet/hyperfleet-api/pkg/api.Commit=$(GIT_SHA) \
+           -X 'github.com/openshift-hyperfleet/hyperfleet-api/pkg/api.BuildTime=$(BUILD_DATE)'
+
+# =============================================================================
+# Image Configuration
+# =============================================================================
 IMAGE_REGISTRY ?= quay.io/openshift-hyperfleet
 IMAGE_NAME ?= hyperfleet-api
-IMAGE_TAG ?= latest
+IMAGE_TAG ?= $(VERSION)
+
+PLATFORM ?= linux/amd64
 
 # Dev image configuration - set QUAY_USER to push to personal registry
 # Usage: QUAY_USER=myuser make image-dev
 QUAY_USER ?=
 DEV_TAG ?= dev-$(GIT_SHA)
+DEV_BASE_IMAGE ?= alpine:3.21
 
-# Database connection details
-db_name:=hyperfleet
-db_host=hyperfleet-db.$(namespace)
-db_port=5432
-db_user:=hyperfleet
-db_password:=foobar-bizz-buzz
-db_password_file=${PWD}/secrets/db.password
-db_sslmode:=disable
-db_image?=docker.io/library/postgres:14.2
+# Encourage consistent tool versions
+OPENAPI_GENERATOR_VERSION := 5.4.0
+GO_VERSION := go1.25.
 
-# Log verbosity level
-glog_v:=10
+# Database connection details
+db_name := hyperfleet
+db_port := 5432
+db_user := hyperfleet
+db_password := foobar-bizz-buzz
+db_password_file := ${PWD}/secrets/db.password
+db_sslmode := disable
+db_image ?= docker.io/library/postgres:14.2
 
-# Location of the JSON web key set used to verify tokens:
-jwks_url:=https://sso.redhat.com/auth/realms/redhat-external/protocol/openid-connect/certs
+# Location of the JSON web key set used to verify tokens
+jwks_url := https://sso.redhat.com/auth/realms/redhat-external/protocol/openid-connect/certs
 
 # Test output files
 unit_test_json_output ?= ${PWD}/unit-test-results.json
 integration_test_json_output ?= ${PWD}/integration-test-results.json
 
-# Prints a list of useful targets.
-help:
-	@echo ""
-	@echo "HyperFleet API - Cluster Lifecycle Management Service"
-	@echo ""
-	@echo "make verify               verify source code"
-	@echo "make lint                 run golangci-lint"
-	@echo "make build                compile binaries to bin/"
-	@echo "make install              compile binaries and install in GOPATH bin"
-	@echo "make secrets              initialize secrets directory with default values"
-	@echo "make run                  run the application"
-	@echo "make run/docs             run swagger and host the api spec"
-	@echo "make test                 run unit tests"
-	@echo "make test-integration     run integration tests"
-	@echo "make generate             generate openapi modules"
-	@echo "make generate-mocks       generate mock implementations for services"
-	@echo "make generate-all         generate all code (openapi + mocks)"
-	@echo "make clean                delete temporary generated files"
-	@echo "make image                build container image"
-	@echo "make image-push           build and push container image"
-	@echo "make image-dev            build and push to personal Quay registry"
-	@echo "$(fake)"
-.PHONY: help
-
-# Encourage consistent tool versions
-OPENAPI_GENERATOR_VERSION:=5.4.0
-GO_VERSION:=go1.24.
-
-### Constants:
-version:=$(shell date +%s)
-
-# Version information for ldflags
-GIT_SHA ?= $(shell git rev-parse --short HEAD 2>/dev/null || echo "unknown")
-GIT_DIRTY ?= $(shell git diff --quiet 2>/dev/null || echo "-modified")
-build_version:=$(GIT_SHA)$(GIT_DIRTY)
-build_time:=$(shell date -u '+%Y-%m-%d %H:%M:%S UTC')
-ldflags=-X github.com/openshift-hyperfleet/hyperfleet-api/pkg/api.Version=$(build_version) -X github.com/openshift-hyperfleet/hyperfleet-api/pkg/api.Commit=$(GIT_SHA) -X 'github.com/openshift-hyperfleet/hyperfleet-api/pkg/api.BuildTime=$(build_time)'
-
-### Envrionment-sourced variables with defaults
-# Can be overriden by setting environment var before running
-# Example:
-#   OCM_ENV=unit_testing make run
-#   export OCM_ENV=testing; make run
-# Set the environment to development by default
+### Environment-sourced variables with defaults
 ifndef OCM_ENV
-	OCM_ENV:=development
+	OCM_ENV := development
 endif
 
 ifndef TEST_SUMMARY_FORMAT
-	TEST_SUMMARY_FORMAT=short-verbose
+	TEST_SUMMARY_FORMAT = short-verbose
 endif
 
 ifndef OCM_BASE_URL
-	OCM_BASE_URL:="https://api.integration.openshift.com"
+	OCM_BASE_URL := "https://api.integration.openshift.com"
 endif
 
-# Checks if a GOPATH is set, or emits an error message
-check-gopath:
-ifndef GOPATH
-	$(error GOPATH is not set)
-endif
-.PHONY: check-gopath
+.PHONY: help
+help: ## Display this help
+	@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n  make \033[36m<target>\033[0m\n"} /^[a-zA-Z_\/-]+:.*?##/ { printf "  \033[36m%-20s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)
 
-# Verifies that source passes standard checks.
-verify: check-gopath
+##@ Code Quality
+
+.PHONY: verify
+verify: ## Verify source passes standard checks
 	${GO} vet \
 		./cmd/... \
 		./pkg/...
@@ -125,200 +107,155 @@ verify: check-gopath
 			echo "* Your go version is not the expected $(GO_VERSION) *" | sed 's/./*/g'; \
 			printf '\033[0m'; \
 		)
-.PHONY: verify
 
-# Runs our linter to verify that everything is following best practices
-# Linter is set to ignore `unused` stuff due to example being incomplete by definition
-lint: $(GOLANGCI_LINT)
-	$(GOLANGCI_LINT) run ./cmd/... ./pkg/... ./test/...
 .PHONY: lint
+lint: $(GOLANGCI_LINT) ## Run golangci-lint
+	$(GOLANGCI_LINT) run ./cmd/... ./pkg/... ./test/...
 
-# Build binaries
-# NOTE it may be necessary to use CGO_ENABLED=0 for backwards compatibility with centos7 if not using centos7
-build: check-gopath generate-all
-	@mkdir -p bin
-	echo "Building version: ${build_version}"
-	CGO_ENABLED=$(CGO_ENABLED) GOEXPERIMENT=boringcrypto ${GO} build -ldflags="$(ldflags)" -o bin/hyperfleet-api ./cmd/hyperfleet-api
-.PHONY: build
-
-# Install
-install: check-gopath generate-all
-	CGO_ENABLED=$(CGO_ENABLED) GOEXPERIMENT=boringcrypto ${GO} install -ldflags="$(ldflags)" ./cmd/hyperfleet-api
-	@ ${GO} version | grep -q "$(GO_VERSION)" || \
-		( \
-			printf '\033[41m\033[97m\n'; \
-			echo "* Your go version is not the expected $(GO_VERSION) *" | sed 's/./*/g'; \
-			echo "* Your go version is not the expected $(GO_VERSION) *"; \
-			echo "* Your go version is not the expected $(GO_VERSION) *" | sed 's/./*/g'; \
-			printf '\033[0m'; \
-		)
-.PHONY: install
-
-# Initialize secrets directory with default values
-secrets:
-	@mkdir -p secrets
-	@printf "localhost" > secrets/db.host
-	@printf "$(db_name)" > secrets/db.name
-	@printf "$(db_password)" > secrets/db.password
-	@printf "$(db_port)" > secrets/db.port
-	@printf "$(db_user)" > secrets/db.user
-	@printf "ocm-hyperfleet-testing" > secrets/ocm-service.clientId
-	@printf "your-client-secret-here" > secrets/ocm-service.clientSecret
-	@printf "your-token-here" > secrets/ocm-service.token
-	@echo "Secrets directory initialized with default values"
-.PHONY: secrets
-
-# Runs the unit tests.
-#
-# Args:
-#   TESTFLAGS: Flags to pass to `go test`. The `-v` argument is always passed.
-#
-# Examples:
-#   make test TESTFLAGS="-run TestSomething"
-test: install secrets $(GOTESTSUM)
-	OCM_ENV=unit_testing $(GOTESTSUM) --format $(TEST_SUMMARY_FORMAT) -- -p 1 -v $(TESTFLAGS) \
-		./pkg/... \
-		./cmd/...
-.PHONY: test
-
-# Runs the unit tests with json output
-#
-# Args:
-#   TESTFLAGS: Flags to pass to `go test`. The `-v` argument is always passed.
-#
-# Examples:
-#   make test-unit-json TESTFLAGS="-run TestSomething"
-ci-test-unit: install secrets $(GOTESTSUM)
-	OCM_ENV=unit_testing $(GOTESTSUM) --jsonfile-timing-events=$(unit_test_json_output) --format $(TEST_SUMMARY_FORMAT) -- -p 1 -v $(TESTFLAGS) \
-		./pkg/... \
-		./cmd/...
-.PHONY: ci-test-unit
-
-# Runs the integration tests.
-#
-# Args:
-#   TESTFLAGS: Flags to pass to `go test`. The `-v` argument is always passed.
-#
-# Example:
-#   make test-integration
-#   make test-integration TESTFLAGS="-run TestAccounts"     acts as TestAccounts* and run TestAccountsGet, TestAccountsPost, etc.
-#   make test-integration TESTFLAGS="-run TestAccountsGet"  runs TestAccountsGet
-#   make test-integration TESTFLAGS="-short"                skips long-run tests
-ci-test-integration: install secrets $(GOTESTSUM)
-	TESTCONTAINERS_RYUK_DISABLED=true OCM_ENV=integration_testing $(GOTESTSUM) --jsonfile-timing-events=$(integration_test_json_output) --format $(TEST_SUMMARY_FORMAT) -- -p 1 -ldflags -s -v -timeout 1h $(TESTFLAGS) \
-			./test/integration
-.PHONY: ci-test-integration
+##@ Code Generation
 
-# Runs the integration tests.
-#
-# Args:
-#   TESTFLAGS: Flags to pass to `go test`. The `-v` argument is always passed.
-#
-# Example:
-#   make test-integration
-#   make test-integration TESTFLAGS="-run TestAccounts"     acts as TestAccounts* and run TestAccountsGet, TestAccountsPost, etc.
-#   make test-integration TESTFLAGS="-run TestAccountsGet"  runs TestAccountsGet
-#   make test-integration TESTFLAGS="-short"                skips long-run tests
-test-integration: install secrets $(GOTESTSUM)
-	TESTCONTAINERS_RYUK_DISABLED=true OCM_ENV=integration_testing $(GOTESTSUM) --format $(TEST_SUMMARY_FORMAT) -- -p 1 -ldflags -s -v -timeout 1h $(TESTFLAGS) \
-			./test/integration
-.PHONY: test-integration
-
-# Regenerate openapi types using oapi-codegen
-generate: $(OAPI_CODEGEN)
+.PHONY: generate
+generate: $(OAPI_CODEGEN) ## Generate OpenAPI types using oapi-codegen
 	rm -rf pkg/api/openapi
 	mkdir -p pkg/api/openapi
 	$(OAPI_CODEGEN) --config openapi/oapi-codegen.yaml openapi/openapi.yaml
-.PHONY: generate
 
-# Generate mock implementations for service interfaces
-generate-mocks: $(MOCKGEN)
-	${GO} generate ./pkg/services/...
 .PHONY: generate-mocks
+generate-mocks: $(MOCKGEN) ## Generate mock implementations for services
+	${GO} generate ./pkg/services/...
 
-# Generate all code (openapi + mocks)
-generate-all: generate generate-mocks
 .PHONY: generate-all
+generate-all: generate generate-mocks ## Generate all code (openapi + mocks)
 
-# generate-vendor is now equivalent to generate (oapi-codegen handles dependencies)
-generate-vendor: generate
 .PHONY: generate-vendor
+generate-vendor: generate
 
-run: build
+##@ Development
+
+.PHONY: build
+build: generate-all ## Build the hyperfleet-api binary
+	@mkdir -p bin
+	@echo "Building version: ${VERSION}"
+	CGO_ENABLED=$(CGO_ENABLED) GOEXPERIMENT=boringcrypto ${GO} build $(GOFLAGS) -ldflags="$(LDFLAGS)" -o bin/hyperfleet-api ./cmd/hyperfleet-api
+
+.PHONY: install
+install: generate-all ## Build and install binary to GOPATH/bin
+	CGO_ENABLED=$(CGO_ENABLED) GOEXPERIMENT=boringcrypto ${GO} install $(GOFLAGS) -ldflags="$(LDFLAGS)" ./cmd/hyperfleet-api
+
+.PHONY: run
+run: build ## Run the application
 	./bin/hyperfleet-api migrate
 	./bin/hyperfleet-api serve
-.PHONY: run
 
-run-no-auth: build
+.PHONY: run-no-auth
+run-no-auth: build ## Run the application without auth
 	./bin/hyperfleet-api migrate
 	./bin/hyperfleet-api serve --enable-authz=false --enable-jwt=false
 
-# Run Swagger nd host the api docs
-run/docs:
-	@echo "Please open http://localhost/"
-	docker run -d -p 80:8080 -e SWAGGER_JSON=/hyperfleet.yaml -v $(PWD)/openapi/hyperfleet.yaml:/hyperfleet.yaml swaggerapi/swagger-ui
 .PHONY: run/docs
-
-# Delete temporary files
-clean:
-	rm -rf \
-		bin \
-		pkg/api/openapi \
-		data/generated/openapi/*.json \
-		secrets \
-.PHONY: clean
+run/docs: check-container-tool ## Run swagger and host the api spec
+	@echo "Please open http://localhost:8081/"
+	# Port 8081 instead of 80: ports <1024 are privileged and fail with rootless Podman.
+	# Port 8080 is avoided since it's used by the health endpoint server.
+	$(CONTAINER_TOOL) run -d -p 8081:8080 -e SWAGGER_JSON=/hyperfleet.yaml -v $(PWD)/openapi/hyperfleet.yaml:/hyperfleet.yaml swaggerapi/swagger-ui
 
 .PHONY: cmds
-cmds:
+cmds: ## Build all binaries under cmd/
 	@mkdir -p bin
 	for cmd in $$(ls cmd); do \
-		CGO_ENABLED=$(CGO_ENABLED) ${GO} build \
-			-ldflags="$(ldflags)" \
+		CGO_ENABLED=$(CGO_ENABLED) GOEXPERIMENT=boringcrypto ${GO} build \
+			$(GOFLAGS) \
+			-ldflags="$(LDFLAGS)" \
 			-o "bin/$${cmd}" \
 			"./cmd/$${cmd}" \
 			|| exit 1; \
 	done
 
+.PHONY: clean
+clean: ## Delete temporary generated files
+	rm -rf \
+		bin \
+		pkg/api/openapi \
+		data/generated/openapi/*.json \
+		secrets \
+
+.PHONY: secrets
+secrets: ## Initialize secrets directory with default values
+	@mkdir -p secrets
+	@printf "localhost" > secrets/db.host
+	@printf "$(db_name)" > secrets/db.name
+	@printf "$(db_password)" > secrets/db.password
+	@printf "$(db_port)" > secrets/db.port
+	@printf "$(db_user)" > secrets/db.user
+	@printf "ocm-hyperfleet-testing" > secrets/ocm-service.clientId
+	@printf "your-client-secret-here" > secrets/ocm-service.clientSecret
+	@printf "your-token-here" > secrets/ocm-service.token
+	@echo "Secrets directory initialized with default values"
+
+##@ Testing
+
+.PHONY: test
+test: install secrets $(GOTESTSUM) ## Run unit tests
+	OCM_ENV=unit_testing $(GOTESTSUM) --format $(TEST_SUMMARY_FORMAT) -- -p 1 -v $(TESTFLAGS) \
+		./pkg/... \
+		./cmd/...
+
+.PHONY: ci-test-unit
+ci-test-unit: install secrets $(GOTESTSUM) ## Run unit tests with JSON output
+	OCM_ENV=unit_testing $(GOTESTSUM) --jsonfile-timing-events=$(unit_test_json_output) --format $(TEST_SUMMARY_FORMAT) -- -p 1 -v $(TESTFLAGS) \
+		./pkg/... \
+		./cmd/...
+
+.PHONY: test-integration
+test-integration: install secrets $(GOTESTSUM) ## Run integration tests
+	TESTCONTAINERS_RYUK_DISABLED=true OCM_ENV=integration_testing $(GOTESTSUM) --format $(TEST_SUMMARY_FORMAT) -- -p 1 -ldflags -s -v -timeout 1h $(TESTFLAGS) \
+			./test/integration
+
+.PHONY: ci-test-integration
+ci-test-integration: install secrets $(GOTESTSUM) ## Run integration tests with JSON output
+	TESTCONTAINERS_RYUK_DISABLED=true OCM_ENV=integration_testing $(GOTESTSUM) --jsonfile-timing-events=$(integration_test_json_output) --format $(TEST_SUMMARY_FORMAT) -- -p 1 -ldflags -s -v -timeout 1h $(TESTFLAGS) \
+			./test/integration
+
+##@ Database
 
 .PHONY: db/setup
-db/setup: secrets
+db/setup: check-container-tool secrets ## Start local PostgreSQL container
 	@echo $(db_password) > $(db_password_file)
-	$(container_tool) run --name psql-hyperfleet -e POSTGRES_DB=$(db_name) -e POSTGRES_USER=$(db_user) -e POSTGRES_PASSWORD=$(db_password) -p $(db_port):5432 -d $(db_image)
+	$(CONTAINER_TOOL) run --name psql-hyperfleet -e POSTGRES_DB=$(db_name) -e POSTGRES_USER=$(db_user) -e POSTGRES_PASSWORD=$(db_password) -p $(db_port):5432 -d $(db_image)
 
 .PHONY: db/login
-db/login:
-	$(container_tool) exec -it psql-hyperfleet bash -c "psql -h localhost -U $(db_user) $(db_name)"
+db/login: check-container-tool ## Login to local PostgreSQL
+	$(CONTAINER_TOOL) exec -it psql-hyperfleet bash -c "psql -h localhost -U $(db_user) $(db_name)"
 
 .PHONY: db/teardown
-db/teardown:
-	$(container_tool) stop psql-hyperfleet
-	$(container_tool) rm psql-hyperfleet
+db/teardown: check-container-tool ## Stop and remove local PostgreSQL container
+	$(CONTAINER_TOOL) stop psql-hyperfleet
+	$(CONTAINER_TOOL) rm psql-hyperfleet
+
+##@ Container Images
 
-# Build container image (multi-stage build, no local binary needed)
 .PHONY: image
-image:
+image: check-container-tool ## Build container image with configurable registry/tag
 	@echo "Building container image $(IMAGE_REGISTRY)/$(IMAGE_NAME):$(IMAGE_TAG)..."
-	# --platform flag requires Docker >= 20.10 or Podman >= 3.4
-	# For older engines: use 'docker buildx build' or omit --platform
-	$(container_tool) build  \
-		--platform linux/amd64 \
+	$(CONTAINER_TOOL) build \
+		--platform $(PLATFORM) \
 		--build-arg GIT_SHA=$(GIT_SHA) \
 		--build-arg GIT_DIRTY=$(GIT_DIRTY) \
+		--build-arg BUILD_DATE=$(BUILD_DATE) \
+		--build-arg VERSION=$(VERSION) \
 		-t $(IMAGE_REGISTRY)/$(IMAGE_NAME):$(IMAGE_TAG) .
-	@echo "✅ Image built: $(IMAGE_REGISTRY)/$(IMAGE_NAME):$(IMAGE_TAG)"
+	@echo "Image built: $(IMAGE_REGISTRY)/$(IMAGE_NAME):$(IMAGE_TAG)"
 
-# Build and push container image to registry
 .PHONY: image-push
-image-push: image
+image-push: image ## Build and push container image
 	@echo "Pushing image $(IMAGE_REGISTRY)/$(IMAGE_NAME):$(IMAGE_TAG)..."
-	$(container_tool) push $(IMAGE_REGISTRY)/$(IMAGE_NAME):$(IMAGE_TAG)
-	@echo "✅ Image pushed: $(IMAGE_REGISTRY)/$(IMAGE_NAME):$(IMAGE_TAG)"
+	$(CONTAINER_TOOL) push $(IMAGE_REGISTRY)/$(IMAGE_NAME):$(IMAGE_TAG)
+	@echo "Image pushed: $(IMAGE_REGISTRY)/$(IMAGE_NAME):$(IMAGE_TAG)"
 
-# Build and push to personal Quay registry (requires QUAY_USER)
 .PHONY: image-dev
-image-dev:
-ifndef QUAY_USER
-	@echo "❌ ERROR: QUAY_USER is not set"
+image-dev: check-container-tool ## Build and push to personal Quay registry (requires QUAY_USER)
+ifeq ($(strip $(QUAY_USER)),)
+	@echo "Error: QUAY_USER is not set"
 	@echo ""
 	@echo "Usage: QUAY_USER=myuser make image-dev"
 	@echo ""
@@ -326,15 +263,15 @@ ifndef QUAY_USER
 	@exit 1
 endif
 	@echo "Building dev image quay.io/$(QUAY_USER)/$(IMAGE_NAME):$(DEV_TAG)..."
-	# --platform flag requires Docker >= 20.10 or Podman >= 3.4
-	# For older engines: use 'docker buildx build' or omit --platform
-	$(container_tool) build \
-		--platform linux/amd64 \
-		--build-arg BASE_IMAGE=alpine:3.21 \
+	$(CONTAINER_TOOL) build \
+		--platform $(PLATFORM) \
+		--build-arg BASE_IMAGE=$(DEV_BASE_IMAGE) \
 		--build-arg GIT_SHA=$(GIT_SHA) \
 		--build-arg GIT_DIRTY=$(GIT_DIRTY) \
+		--build-arg BUILD_DATE=$(BUILD_DATE) \
+		--build-arg VERSION=$(VERSION) \
 		-t quay.io/$(QUAY_USER)/$(IMAGE_NAME):$(DEV_TAG) .
 	@echo "Pushing dev image..."
-	$(container_tool) push quay.io/$(QUAY_USER)/$(IMAGE_NAME):$(DEV_TAG)
+	$(CONTAINER_TOOL) push quay.io/$(QUAY_USER)/$(IMAGE_NAME):$(DEV_TAG)
 	@echo ""
-	@echo "✅ Dev image pushed: quay.io/$(QUAY_USER)/$(IMAGE_NAME):$(DEV_TAG)"
+	@echo "Dev image pushed: quay.io/$(QUAY_USER)/$(IMAGE_NAME):$(DEV_TAG)"

From 1756b78d313ae7ec99b028bd4af1062d6680cfcc Mon Sep 17 00:00:00 2001
From: xueli <xueli@redhat.com>
Date: Tue, 3 Mar 2026 15:01:24 +0800
Subject: [PATCH 2/2] Add Helm chart testing and fix default image registry

- Add `make test-helm` target with 8 configurations: default values,
  external database, autoscaling, PDB, ServiceMonitor, auth disabled,
  custom image, and full adapter config
- Fix image.registry default from CHANGE_ME to quay.io/openshift-hyperfleet

Ref: HYPERFLEET-658
Made-with: Cursor
---
 Makefile           | 85 ++++++++++++++++++++++++++++++++++++++++++++++
 charts/values.yaml |  2 +-
 2 files changed, 86 insertions(+), 1 deletion(-)

diff --git a/Makefile b/Makefile
index 7dd46330..ce9b442e 100755
--- a/Makefile
+++ b/Makefile
@@ -232,6 +232,91 @@ db/teardown: check-container-tool ## Stop and remove local PostgreSQL container
 	$(CONTAINER_TOOL) stop psql-hyperfleet
 	$(CONTAINER_TOOL) rm psql-hyperfleet
 
+##@ Helm
+
+.PHONY: test-helm
+test-helm: ## Test Helm charts (lint, template, validate)
+	@echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
+	@echo "Testing Helm charts..."
+	@echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
+	@if ! command -v helm > /dev/null; then \
+		echo "Error: helm not found. Please install Helm:"; \
+		echo "  brew install helm  # macOS"; \
+		echo "  curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash  # Linux"; \
+		exit 1; \
+	fi
+	@echo "Linting Helm chart..."
+	helm lint charts/ \
+		--set 'adapters.cluster=["validation"]' \
+		--set 'adapters.nodepool=["validation"]'
+	@echo ""
+	@echo "Testing template rendering with default values..."
+	helm template test-release charts/ \
+		--set 'adapters.cluster=["validation"]' \
+		--set 'adapters.nodepool=["validation"]' > /dev/null
+	@echo "Default values template OK"
+	@echo ""
+	@echo "Testing template with external database..."
+	helm template test-release charts/ \
+		--set 'adapters.cluster=["validation"]' \
+		--set 'adapters.nodepool=["validation"]' \
+		--set database.postgresql.enabled=false \
+		--set database.external.enabled=true \
+		--set database.external.secretName=my-db-secret > /dev/null
+	@echo "External database config template OK"
+	@echo ""
+	@echo "Testing template with autoscaling..."
+	helm template test-release charts/ \
+		--set 'adapters.cluster=["validation"]' \
+		--set 'adapters.nodepool=["validation"]' \
+		--set autoscaling.enabled=true \
+		--set autoscaling.minReplicas=2 \
+		--set autoscaling.maxReplicas=5 > /dev/null
+	@echo "Autoscaling config template OK"
+	@echo ""
+	@echo "Testing template with PDB enabled..."
+	helm template test-release charts/ \
+		--set 'adapters.cluster=["validation"]' \
+		--set 'adapters.nodepool=["validation"]' \
+		--set podDisruptionBudget.enabled=true \
+		--set podDisruptionBudget.minAvailable=1 > /dev/null
+	@echo "PDB config template OK"
+	@echo ""
+	@echo "Testing template with ServiceMonitor enabled..."
+	helm template test-release charts/ \
+		--set 'adapters.cluster=["validation"]' \
+		--set 'adapters.nodepool=["validation"]' \
+		--set serviceMonitor.enabled=true \
+		--set serviceMonitor.interval=15s > /dev/null
+	@echo "ServiceMonitor config template OK"
+	@echo ""
+	@echo "Testing template with auth disabled..."
+	helm template test-release charts/ \
+		--set 'adapters.cluster=["validation"]' \
+		--set 'adapters.nodepool=["validation"]' \
+		--set auth.enableJwt=false \
+		--set auth.enableAuthz=false > /dev/null
+	@echo "Auth disabled config template OK"
+	@echo ""
+	@echo "Testing template with custom image..."
+	helm template test-release charts/ \
+		--set 'adapters.cluster=["validation"]' \
+		--set 'adapters.nodepool=["validation"]' \
+		--set image.registry=quay.io/myorg \
+		--set image.repository=hyperfleet-api \
+		--set image.tag=v1.0.0 > /dev/null
+	@echo "Custom image config template OK"
+	@echo ""
+	@echo "Testing template with full adapter config..."
+	helm template test-release charts/ \
+		--set-json 'adapters.cluster=["validation","dns","pullsecret","hypershift"]' \
+		--set-json 'adapters.nodepool=["validation","hypershift"]' > /dev/null
+	@echo "Full adapter config template OK"
+	@echo ""
+	@echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
+	@echo "All Helm chart tests passed!"
+	@echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
+
 ##@ Container Images
 
 .PHONY: image
diff --git a/charts/values.yaml b/charts/values.yaml
index e75ebd33..0b1717cb 100644
--- a/charts/values.yaml
+++ b/charts/values.yaml
@@ -4,7 +4,7 @@
 replicaCount: 1
 
 image:
-  registry: CHANGE_ME
+  registry: quay.io/openshift-hyperfleet
   repository: hyperfleet-api
   pullPolicy: Always
   tag: "latest"
