Skip to content
Merged
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
12 changes: 12 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# More info: https://docs.docker.com/engine/reference/builder/#dockerignore-file
# Ignore build and test binaries.
bin/


.git
.gitignore

.pre-commit-config.yaml
.golangci.yml

coverage.out
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,4 @@ Thumbs.db
dist/
build/
secrets/
bin/
49 changes: 49 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
ARG BASE_IMAGE=registry.access.redhat.com/ubi9-micro:latest
Comment thread
ma-hill marked this conversation as resolved.


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

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
WORKDIR /build
RUN chown 1001:0 /build
USER 1001

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 . .
Comment thread
coderabbitai[bot] marked this conversation as resolved.

# 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

# Runtime stage
FROM ${BASE_IMAGE}

WORKDIR /app

# ubi9-micro doesn't include CA certificates; copy from builder for TLS (e.g. Google Pub/Sub)
COPY --from=builder /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem
COPY --from=builder /build/bin/hyperfleet-applier /app/hyperfleet-applier
COPY --from=builder /build/LICENSE /licenses/LICENSE

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}" \
summary="HyperFleet Applier" \
description="Controller responsible for Hyperfleet ReadDesires, ApplyDesires and DeleteDesires"
123 changes: 115 additions & 8 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ GOFMT ?= gofmt

ENVTEST_K8S_VERSION ?= 1.36.2

BIN_DIR := bin
BINARY_NAME := $(BIN_DIR)/hyperfleet-applier
BINARY_PATH ?= bin/hyperfleet-applier

BUILD_DATE ?= $(shell date -u +"%Y-%m-%dT%H:%M:%SZ")
GIT_SHA ?= $(shell git rev-parse --short HEAD 2>/dev/null || echo "unknown")
Expand All @@ -18,6 +17,10 @@ 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)

Expand All @@ -34,12 +37,11 @@ help: ## Display this help

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

.PHONY: run
run: build ## Run the applier service
./$(BINARY_NAME) serve \
./$(BINARY_PATH) serve \
--config "$(CONFIG)" \
--kubernetes-kube-config-path "$(KUBE_CONFIG_PATH)"

Expand All @@ -49,9 +51,9 @@ test: ## Run unit tests

.PHONY: test-envtest
test-envtest: ## Run envtest-backed integration tests against a real kube-apiserver
@assets=$$($(call gotool,setup-envtest) use -i -p path $(ENVTEST_K8S_VERSION)); \
@assets=$$($(call gotool,setup-envtest) use -p path $(ENVTEST_K8S_VERSION)); \
if [ -z "$$assets" ]; then \
echo "setup-envtest: failed to resolve installed assets for $(ENVTEST_K8S_VERSION)"; \
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
Expand Down Expand Up @@ -84,7 +86,7 @@ lint: ## Run golangci-lint
$(call gotool,golangci-lint) run

.PHONY: verify
verify: fmt-check vet ## Run all verification checks
verify: fmt-check vet helm-verify ## Run all verification checks

.PHONY: lint-check
lint-check: fmt-check vet ## Run static code analysis (alias for verify, follows architecture naming)
Expand All @@ -106,3 +108,108 @@ verify-tools: tools ## Fail in CI if tool module drifted
.PHONY: download
download: ## Download dependencies
$(GO) mod download


##@ Container Images

# =============================================================================
# Image Configuration
# =============================================================================
IMAGE_REGISTRY ?= quay.io/openshift-hyperfleet
IMAGE_NAME ?= hyperfleet-applier
IMAGE_TAG ?= $(APP_VERSION)
IMG ?= $(IMAGE_REGISTRY)/$(IMAGE_NAME):$(IMAGE_TAG)
PLATFORM ?= linux/amd64


BASE_IMAGE ?= registry.access.redhat.com/ubi9-micro:latest
# Auto-detect container tool (podman preferred when available)
CONTAINER_TOOL ?= $(shell command -v podman 2>/dev/null || command -v docker 2>/dev/null)

.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

# Build container image (multi-stage build, no local binary needed)
.PHONY: image
image: check-container-tool ## Build container image with configurable registry/tag
@echo "Building container image $(IMG)..."
$(CONTAINER_TOOL) build \
--platform $(PLATFORM) \
--build-arg BASE_IMAGE=$(BASE_IMAGE) \
--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)"


# 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: IMAGE_TAG = $(DEV_TAG)
image-dev: BASE_IMAGE = $(DEV_BASE_IMAGE)
image-dev: check-container-tool image image-push
Comment thread
coderabbitai[bot] marked this conversation as resolved.

##@ Helm

HELM ?= helm
CHART_DIR := charts
CHART_VALUES_FILE := charts/values.yaml

# Test values for helm template rendering
HELM_TEST_VALUES := \
--set image.registry=quay.io \
--set image.repository=openshift-hyperfleet/hyperfleet-applier \
--set image.tag=test \
--set applier.managementCluster=test-cluster \
--set applier.pollInterval=5s \
--set redis.address=redis:6379

.PHONY: helm-lint
helm-lint: ## Lint the Helm chart
@echo "Linting Helm chart..."
$(HELM) lint $(CHART_DIR)

.PHONY: helm-template
helm-template: ## Render Helm chart templates with test values
@echo "Rendering Helm chart templates..."
$(HELM) template hyperfleet-applier $(CHART_DIR) $(HELM_TEST_VALUES)

.PHONY: helm-template-check
helm-template-check: ## Verify Helm chart templates can be rendered
@echo "Verifying Helm chart templates can be rendered..."
@$(HELM) template hyperfleet-applier $(CHART_DIR) $(HELM_TEST_VALUES) > /dev/null
@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"

.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

.PHONY: verify-helm-docs
verify-helm-docs: ## Verify chart README is up to date
Comment thread
kuudori marked this conversation as resolved.
$(call gotool,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)

23 changes: 23 additions & 0 deletions charts/.helmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*.orig
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/
13 changes: 13 additions & 0 deletions charts/Chart.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
apiVersion: v2
name: hyperfleet-applier
description: HyperFleet Applier - Kubernetes controller for reconciling ApplyDesire and DeleteDesire resources
type: application
version: 0.1.0
appVersion: "0.0.0-dev"
keywords:
- hyperfleet
- applier
- desire
home: https://github.com/openshift-hyperfleet/hyperfleet-applier
maintainers:
- name: HyperFleet Team
40 changes: 40 additions & 0 deletions charts/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# hyperfleet-applier

![Version: 0.1.0](https://img.shields.io/badge/Version-0.1.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 0.0.0-dev](https://img.shields.io/badge/AppVersion-0.0.0--dev-informational?style=flat-square)

HyperFleet Applier - Kubernetes controller for reconciling ApplyDesire and DeleteDesire resources

**Homepage:** <https://github.com/openshift-hyperfleet/hyperfleet-applier>

## Maintainers

| Name | Email | Url |
| ---- | ------ | --- |
| HyperFleet Team | | |

## Values

| Key | Type | Default | Description |
|-----|------|---------|-------------|
| imagePullSecrets | list | `[]` | List of image pull secrets to use for pulling container images |
| nameOverride | string | `""` | Override the chart name |
| fullnameOverride | string | `""` | Override the full release name |
| serviceAccount.create | bool | `true` | Create a service account for the controller |
| 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 |
| 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 |
| securityContext | object | `{"allowPrivilegeEscalation":false,"capabilities":{"drop":["ALL"]},"readOnlyRootFilesystem":true,"runAsNonRoot":true}` | Container-level security context |
| resources | object | `{"limits":{"cpu":"500m","memory":"512Mi"},"requests":{"cpu":"100m","memory":"128Mi"}}` | Resource requests and limits for the controller container |
| replicaCount | int | `1` | Number of controller replicas to run |
| image.registry | string | `""` | Container image registry (required) |
| image.repository | string | `""` | Container image repository (required) |
| image.tag | string | `""` | Container image tag (required) |
| image.pullPolicy | string | `"IfNotPresent"` | Image pull policy |
| applier.managementCluster | string | `""` | Management cluster identifier - must match the partition this applier instance manages (required) |
| applier.pollInterval | string | `""` | Polling interval for reconciliation loops (e.g., "5s", "1m") (required) |
| redis.address | string | `""` | Redis server address in format "host:port" (required) |

Loading