Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
ARG BASE_IMAGE=registry.access.redhat.com/ubi9-micro:latest


FROM registry.access.redhat.com/ubi9/go-toolset:9.8-1787774815 AS builder

ARG GIT_SHA=unknown
ARG GIT_DIRTY=""
ARG BUILD_DATE=""
ARG APP_VERSION="0.0.0-dev"

# 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
Expand All @@ -21,10 +24,11 @@ RUN --mount=type=cache,target=/opt/app-root/src/go/pkg/mod,uid=1001 \

COPY --chown=1001:0 . .

# For FIPS-compliant builds, use CGO_ENABLED=1 + GOEXPERIMENT=boringcrypto.
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 \
GOOS=linux make build
GOOS=linux CGO_ENABLED=0 \
GIT_SHA=${GIT_SHA} BUILD_DATE=${BUILD_DATE} APP_VERSION=${APP_VERSION} \
make build

# Runtime stage
FROM ${BASE_IMAGE}
Expand All @@ -41,7 +45,6 @@ USER 65532:65532
ENTRYPOINT ["/app/hyperfleet-applier"]

ARG APP_VERSION="0.0.0-dev"

LABEL name="hyperfleet-applier" \
vendor="Red Hat, Inc." \
version="${APP_VERSION}" \
Expand Down
80 changes: 55 additions & 25 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -17,27 +17,39 @@ LDFLAGS := -s -w \
-X main.commit=$(GIT_SHA) \
-X main.date=$(BUILD_DATE)

# Version information
CGO_ENABLED ?= 1
GOEXPERIMENT ?= boringcrypto

CONFIG ?= configs/applier.yaml
KUBE_CONFIG_PATH ?= $(if $(KUBECONFIG),$(KUBECONFIG),$(HOME)/.kube/config)

# Go tool info

LOCALBIN ?= $(shell pwd)/bin
$(LOCALBIN):
mkdir -p $(LOCALBIN)


# Invoke a pinned tool: $(call gotool,name)
# All tools share tools/go.mod with Go 1.24+ tool directives.
TOOL_MOD := tools/go.mod
gotool = "$(GO)" tool -modfile="$(TOOL_MOD)" $(1)

# Tool shortcuts
HELM_DOCS := $(call gotool,helm-docs)
GOLANGCI_LINT := $(call gotool,golangci-lint)
SETUP_ENVTEST := $(call gotool,setup-envtest)


.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%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)

##@ Development

CGO_ENABLED ?= 1

.PHONY: build
build: ## Build the applier binary
CGO_ENABLED=$(CGO_ENABLED) GOEXPERIMENT=$(GOEXPERIMENT) $(GO) build $(GOFLAGS) -ldflags "$(LDFLAGS)" -o $(BINARY_PATH) ./cmd
build: ## Build the applier binary
CGO_ENABLED=$(CGO_ENABLED) $(GO) build $(GOFLAGS) -ldflags "$(LDFLAGS)" -o $(BINARY_PATH) ./cmd

.PHONY: run
run: build ## Run the applier service
Expand All @@ -49,14 +61,16 @@ run: build ## Run the applier service
test: ## Run unit tests
$(GO) test -v -race -coverprofile=coverage.out ./...

.PHONY: test-envtest
test-envtest: ## Run envtest-backed integration tests against a real kube-apiserver
@assets=$$($(call gotool,setup-envtest) use -p path $(ENVTEST_K8S_VERSION)); \
if [ -z "$$assets" ]; then \
echo "setup-envtest: failed to resolve assets for $(ENVTEST_K8S_VERSION)"; \
exit 1; \
fi; \
KUBEBUILDER_ASSETS="$$assets" $(GO) test -race -tags envtest ./... -run Envtest -v
# Removal of -i to the setup-envtest command is intentional
# If we run this envtest in CI, we want to load them from the $(PWD)/bin since we don't have access to the home directory.
# Without --bin-dir and -i setup-envtest installs the binaries in ~/.local/share/kubebuilder-envtest/
.PHONY: setup-envtest
setup-envtest: $(LOCALBIN) ## Download the envtest binaries (etcd, kube-apiserver) into the local bin directory.
$(SETUP_ENVTEST) use '$(ENVTEST_K8S_VERSION)' --bin-dir $(LOCALBIN) -p path

.PHONY: envtest
envtest: fmt vet setup-envtest ## Run envtest-backed integration tests against a real kube-apiserver
KUBEBUILDER_ASSETS="$$($(SETUP_ENVTEST) use '$(ENVTEST_K8S_VERSION)' --bin-dir $(LOCALBIN) -p path)" go test -race -tags envtest ./... -run Envtest -v

.PHONY: fmt
fmt: ## Format Go code
Expand All @@ -83,7 +97,7 @@ go-vet: vet ## Alias for vet

.PHONY: lint
lint: ## Run golangci-lint
$(call gotool,golangci-lint) run
$(GOLANGCI_LINT) run

.PHONY: verify
verify: fmt-check vet helm-verify ## Run all verification checks
Expand Down Expand Up @@ -115,7 +129,8 @@ download: ## Download dependencies
# =============================================================================
# Image Configuration
# =============================================================================
IMAGE_REGISTRY ?= quay.io/openshift-hyperfleet
QUAY_REPO := openshift-hyperfleet
IMAGE_REGISTRY ?= quay.io/$(QUAY_REPO)
IMAGE_NAME ?= hyperfleet-applier
IMAGE_TAG ?= $(APP_VERSION)
IMG ?= $(IMAGE_REGISTRY)/$(IMAGE_NAME):$(IMAGE_TAG)
Expand Down Expand Up @@ -144,35 +159,46 @@ image: check-container-tool ## Build container image with configurable registry/
@echo "Building container image $(IMG)..."
$(CONTAINER_TOOL) build \
--platform $(PLATFORM) \
--build-arg GIT_SHA=$(GIT_SHA) \
--build-arg BUILD_DATE=$(BUILD_DATE) \
Comment thread
coderabbitai[bot] marked this conversation as resolved.
--build-arg BASE_IMAGE=$(BASE_IMAGE) \
Comment thread
ma-hill marked this conversation as resolved.
--build-arg APP_VERSION=$(APP_VERSION) \
-t $(IMG) .
@echo "Image built: $(IMG)"
@echo "$(IMG)"

.PHONY: image-push
image-push: check-container-tool ## Push container image to registry
@echo "Pushing image $(IMG)..."
$(CONTAINER_TOOL) push $(IMG)
@echo "Image pushed: $(IMG)"

.PHONY: check-quay-user
check-quay-user:
ifeq ($(strip $(QUAY_USER)),)
@echo "Error: QUAY_USER is not set"
@echo ""
@echo "Usage: QUAY_USER=myuser make image-dev"
@exit 1
endif

# Usage: QUAY_USER=myuser make image-dev
# Dev image configuration - set QUAY_USER to push to personal registry
DEV_TAG ?= dev-$(GIT_SHA)
QUAY_USER ?=
DEV_BASE_IMAGE ?= registry.access.redhat.com/ubi9/ubi-minimal:latest

.PHONY: image-dev
image-dev: IMAGE_REGISTRY = quay.io/$(QUAY_USER)
image-dev: QUAY_REPO = $(QUAY_USER)
image-dev: IMAGE_TAG = $(DEV_TAG)
image-dev: BASE_IMAGE = $(DEV_BASE_IMAGE)
image-dev: check-container-tool image image-push
image-dev: check-quay-user image image-push ## Build and push dev image to dev Quay registry (requires QUAY_USER)
Comment thread
ma-hill marked this conversation as resolved.

##@ Helm

HELM ?= helm
##@ Helm Targets

HELM := helm
HELM_CHECK := $(shell command -v $(HELM) 2>/dev/null)
CHART_DIR := charts
CHART_VALUES_FILE := charts/values.yaml

# Test values for helm template rendering
HELM_TEST_VALUES := \
Expand Down Expand Up @@ -200,16 +226,20 @@ helm-template-check: ## Verify Helm chart templates can be rendered
@echo "✓ Helm chart templates rendered successfully"

.PHONY: helm-verify
helm-verify: helm-lint helm-template-check verify-helm-docs ## Run all Helm chart verification checks
@echo "✓ All Helm chart checks passed"
helm-verify: ## Helm checks (helm lint, helm template, verify helm-docs)
@if [ -z "$(HELM_CHECK)" ]; then \
echo "WARNING: helm not installed. Please install it to verify Helm chart documentation." ; \
else \
$(MAKE) helm-lint helm-template-check verify-helm-docs; \
fi

.PHONY: helm-docs
helm-docs: ## Generate Helm chart README from values.yaml annotations
$(call gotool,helm-docs) --chart-search-root=charts --sort-values-order=file
$(HELM_DOCS) --chart-search-root=charts --sort-values-order=file

.PHONY: verify-helm-docs
verify-helm-docs: ## Verify chart README is up to date
$(call gotool,helm-docs) --chart-search-root=charts --sort-values-order=file
$(HELM_DOCS) --chart-search-root=charts --sort-values-order=file
@git diff --exit-code charts/README.md > /dev/null 2>&1 || \
(echo "ERROR: charts/README.md is out of date. Run 'make helm-docs' and commit the result." && exit 1)

10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,16 @@ go get github.com/openshift-hyperfleet/hyperfleet-applier
Credentials are stored scoped to the applier's own partition by convention.
Cryptographic enforcement of that scoping arrives with the production backends.

### FIPS Compliance:

As part of https://redhat.atlassian.net/browse/HYPERFLEET-1601 - decided to defer FIPS compliant images until compliance requirement comes in.
If want to build a FIPS compliant applier image -- add these Go variables to the `Dockerfile` and Makefile
```bash
CGO_ENABLED=1 and GOEXPERIMENT=strictfipsruntime go ...
```

Reference on this: https://developers.redhat.com/articles/2025/01/23/fips-mode-red-hat-go-toolset

## Contributing

1. Verify you're a member of the `openshift-hyperfleet` organization
Expand Down
2 changes: 2 additions & 0 deletions charts/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,5 @@ keywords:
home: https://github.com/openshift-hyperfleet/hyperfleet-applier
maintainers:
- name: HyperFleet Team
email: hyperfleet-team@redhat.com
url: https://github.com/openshift-hyperfleet
4 changes: 2 additions & 2 deletions charts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ HyperFleet Applier - Kubernetes controller for reconciling ApplyDesire and Delet

| Name | Email | Url |
| ---- | ------ | --- |
| HyperFleet Team | | |
| HyperFleet Team | <hyperfleet-team@redhat.com> | <https://github.com/openshift-hyperfleet> |

## Values

Expand All @@ -23,7 +23,7 @@ HyperFleet Applier - Kubernetes controller for reconciling ApplyDesire and Delet
| serviceAccount.annotations | object | `{}` | Annotations to add to the service account |
| serviceAccount.name | string | `""` | Override the service account name |
| rbac.create | bool | `true` | Create RBAC resources (ClusterRole, ClusterRoleBinding) |
| rbac.rules | list | `[{"apiGroups":["*"],"resources":["*"],"verbs":["get","list","watch","create","update","patch","delete"]}]` | ClusterRole rules - the controller needs broad permissions to apply any resource type WARNING: These broad permissions allow the applier to manage any Kubernetes resource. Future change to reduce the permissions |
| rbac.rules | list | `[{"apiGroups":["*"],"resources":["*"],"verbs":["get","list","watch","create","patch","delete"]}]` | ClusterRole rules - the controller needs broad permissions to apply any resource type. Wildcards are required because the applier dynamically manages arbitrary resource types (including CRDs) determined at runtime by desires in the Redis store. Verb breakdown by controller: ApplyDesire: create, patch (server-side apply) DeleteDesire: get, delete ReadDesire: get, list, watch (dynamic informers) "update" is intentionally excluded — the applier uses SSA (patch), never full PUT. |
| podAnnotations | object | `{}` | Annotations to add to controller pods |
| podLabels | object | `{}` | Labels to add to controller pods |
| podSecurityContext | object | `{"runAsNonRoot":true,"seccompProfile":{"type":"RuntimeDefault"}}` | Pod-level security context |
Expand Down
13 changes: 9 additions & 4 deletions charts/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,18 @@ serviceAccount:
rbac:
# -- Create RBAC resources (ClusterRole, ClusterRoleBinding)
create: true
# -- ClusterRole rules - the controller needs broad permissions to apply any resource type
# WARNING: These broad permissions allow the applier to manage any Kubernetes resource.
# Future change to reduce the permissions
# -- ClusterRole rules - the controller needs broad permissions to apply any resource type.
# Wildcards are required because the applier dynamically manages arbitrary resource types
# (including CRDs) determined at runtime by desires in the Redis store.
# Verb breakdown by controller:
# ApplyDesire: create, patch (server-side apply)
# DeleteDesire: get, delete
# ReadDesire: get, list, watch (dynamic informers)
# "update" is intentionally excluded — the applier uses SSA (patch), never full PUT.
rules:
- apiGroups: ["*"]
resources: ["*"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
verbs: ["get", "list", "watch", "create", "patch", "delete"]
Comment thread
ma-hill marked this conversation as resolved.

# -- Annotations to add to controller pods
podAnnotations: {}
Expand Down