diff --git a/Dockerfile.ci b/Dockerfile.ci new file mode 100644 index 0000000..aa36bc1 --- /dev/null +++ b/Dockerfile.ci @@ -0,0 +1,16 @@ +FROM src +WORKDIR /go/src/github.com/openshift-hyperfleet/hyperfleet-infra +USER root +RUN yum -y install --setopt=skip_missing_names_on_install=False \ + shellcheck \ + jq \ + unzip \ + && yum clean all +RUN curl -fsSL -o /tmp/get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 \ + && chmod +x /tmp/get_helm.sh && /tmp/get_helm.sh && rm /tmp/get_helm.sh +RUN TERRAFORM_VERSION=1.9.8 \ + && curl -fsSL -o /tmp/terraform.zip "https://releases.hashicorp.com/terraform/${TERRAFORM_VERSION}/terraform_${TERRAFORM_VERSION}_linux_amd64.zip" \ + && unzip -o /tmp/terraform.zip -d /usr/local/bin/ && rm /tmp/terraform.zip +RUN curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" \ + && chmod 755 kubectl && mv kubectl /usr/local/bin/ +RUN helm plugin install https://github.com/aslafy-z/helm-git diff --git a/Makefile b/Makefile index c1562b2..5fcc1a1 100644 --- a/Makefile +++ b/Makefile @@ -16,6 +16,11 @@ REGISTRY ?= quay.io/openshift-hyperfleet API_IMAGE_TAG ?= v0.1.1 SENTINEL_IMAGE_TAG ?= v0.1.1 ADAPTER_IMAGE_TAG ?= v0.1.1 +DRY_RUN ?= +AUTO_APPROVE ?= +# Derived flags from boolean variables +DRY_RUN_FLAG := $(if $(DRY_RUN),--dry-run) +AUTO_APPROVE_FLAG := $(if $(AUTO_APPROVE),-auto-approve) # Chart source configuration (helm-git plugin) # Chart refs are independent of image tags so that overriding an image tag @@ -109,7 +114,7 @@ install-maestro: check-helm check-kubectl check-maestro-namespace ## Install Mae helm dependency update $(HELM_DIR)/maestro @echo "Installing Maestro..." - if ! helm upgrade --install $(MAESTRO_NS)-maestro $(HELM_DIR)/maestro \ + if ! helm upgrade --install $(DRY_RUN_FLAG) $(MAESTRO_NS)-maestro $(HELM_DIR)/maestro \ --namespace $(MAESTRO_NS) \ --kubeconfig $(KUBECONFIG) \ --set agent.messageBroker.mqtt.host=maestro-mqtt.$(MAESTRO_NS) \ @@ -147,7 +152,7 @@ endef install-api: check-helm check-kubectl check-namespace ## Install HyperFleet API $(call set-chart-ref,$(HELM_DIR)/api,$(API_CHART_REF)) helm dependency update $(HELM_DIR)/api - helm upgrade --install $(NAMESPACE)-api $(HELM_DIR)/api \ + helm upgrade --install $(DRY_RUN_FLAG) $(NAMESPACE)-api $(HELM_DIR)/api \ --namespace $(NAMESPACE) \ --kubeconfig $(KUBECONFIG) \ $(if $(REGISTRY),--set hyperfleet-api.image.registry=$(REGISTRY)) \ @@ -157,7 +162,7 @@ install-api: check-helm check-kubectl check-namespace ## Install HyperFleet API install-sentinel-clusters: check-helm check-kubectl check-namespace ## Install Sentinel for clusters $(call set-chart-ref,$(HELM_DIR)/sentinel-clusters,$(SENTINEL_CHART_REF)) helm dependency update $(HELM_DIR)/sentinel-clusters - helm upgrade --install $(NAMESPACE)-sentinel-clusters $(HELM_DIR)/sentinel-clusters \ + helm upgrade --install $(DRY_RUN_FLAG) $(NAMESPACE)-sentinel-clusters $(HELM_DIR)/sentinel-clusters \ --namespace $(NAMESPACE) \ --kubeconfig $(KUBECONFIG) \ --set sentinel.broker.type=$(BROKER_TYPE) \ @@ -169,7 +174,7 @@ install-sentinel-clusters: check-helm check-kubectl check-namespace ## Install S install-sentinel-nodepools: check-helm check-kubectl check-namespace ## Install Sentinel for nodepools $(call set-chart-ref,$(HELM_DIR)/sentinel-nodepools,$(SENTINEL_CHART_REF)) helm dependency update $(HELM_DIR)/sentinel-nodepools - helm upgrade --install $(NAMESPACE)-sentinel-nodepools $(HELM_DIR)/sentinel-nodepools \ + helm upgrade --install $(DRY_RUN_FLAG) $(NAMESPACE)-sentinel-nodepools $(HELM_DIR)/sentinel-nodepools \ --namespace $(NAMESPACE) \ --kubeconfig $(KUBECONFIG) \ --set sentinel.broker.type=$(BROKER_TYPE) \ @@ -181,7 +186,7 @@ install-sentinel-nodepools: check-helm check-kubectl check-namespace ## Install install-adapter1: check-helm check-kubectl check-namespace ## Install adapter1 $(call set-chart-ref,$(HELM_DIR)/adapter1,$(ADAPTER_CHART_REF)) helm dependency update $(HELM_DIR)/adapter1 - helm upgrade --install $(NAMESPACE)-adapter1 $(HELM_DIR)/adapter1 \ + helm upgrade --install $(DRY_RUN_FLAG) $(NAMESPACE)-adapter1 $(HELM_DIR)/adapter1 \ --namespace $(NAMESPACE) \ --kubeconfig $(KUBECONFIG) \ --set hyperfleet-adapter.broker.type=$(BROKER_TYPE) \ @@ -195,7 +200,7 @@ install-adapter1: check-helm check-kubectl check-namespace ## Install adapter1 install-adapter2: check-helm check-kubectl check-namespace ## Install adapter2 $(call set-chart-ref,$(HELM_DIR)/adapter2,$(ADAPTER_CHART_REF)) helm dependency update $(HELM_DIR)/adapter2 - helm upgrade --install $(NAMESPACE)-adapter2 $(HELM_DIR)/adapter2 \ + helm upgrade --install $(DRY_RUN_FLAG) $(NAMESPACE)-adapter2 $(HELM_DIR)/adapter2 \ --namespace $(NAMESPACE) \ --kubeconfig $(KUBECONFIG) \ --set hyperfleet-adapter.broker.type=$(BROKER_TYPE) \ @@ -209,7 +214,7 @@ install-adapter2: check-helm check-kubectl check-namespace ## Install adapter2 install-adapter3: check-helm check-kubectl check-namespace ## Install adapter3 $(call set-chart-ref,$(HELM_DIR)/adapter3,$(ADAPTER_CHART_REF)) helm dependency update $(HELM_DIR)/adapter3 - helm upgrade --install $(NAMESPACE)-adapter3 $(HELM_DIR)/adapter3 \ + helm upgrade --install $(DRY_RUN_FLAG) $(NAMESPACE)-adapter3 $(HELM_DIR)/adapter3 \ --namespace $(NAMESPACE) \ --kubeconfig $(KUBECONFIG) \ --set hyperfleet-adapter.broker.type=$(BROKER_TYPE) \ @@ -222,7 +227,7 @@ install-adapter3: check-helm check-kubectl check-namespace ## Install adapter3 .PHONY: install-terraform install-terraform: check-terraform check-tf-files ## Run Terraform init and apply cd $(TF_DIR) && terraform init -backend-config=$(TF_BACKEND) - cd $(TF_DIR) && terraform apply -var-file=$(TF_VARS) + cd $(TF_DIR) && terraform apply -var-file=$(TF_VARS) $(AUTO_APPROVE_FLAG) # ────────────────────────────────────────────── # Aggregate install targets @@ -244,6 +249,125 @@ install-all: install-terraform get-credentials tf-helm-values install-maestro cr install-all-rabbitmq: BROKER_TYPE = rabbitmq install-all-rabbitmq: install-rabbitmq tf-helm-values install-hyperfleet install-maestro create-maestro-consumer ## Full RabbitMQ install (rabbitmq + hyperfleet + maestro, no terraform) +# ────────────────────────────────────────────── +# CI validation targets +# ────────────────────────────────────────────── + +# --- Layer 1: Static validation --- + +.PHONY: validate-terraform +validate-terraform: check-terraform ## Validate Terraform syntax and formatting + cd $(TF_DIR) && \ + terraform init -backend=false && \ + terraform fmt -check -recursive -diff && \ + terraform validate + +.PHONY: lint-helm +lint-helm: check-helm deps ## Lint all Helm charts + @for chart in $(HELM_DIR)/*/; do \ + echo "Linting $$chart..."; \ + helm lint "$$chart" || exit 1; \ + done + +.PHONY: lint-shellcheck +lint-shellcheck: ## Validate shell scripts with shellcheck + @if command -v shellcheck >/dev/null 2>&1; then \ + find . -name '*.sh' -not -path './.terraform/*' -not -path './.git/*' -exec shellcheck {} +; \ + elif [ -n "$$CI" ]; then \ + echo "ERROR: shellcheck is required in CI but not installed"; exit 1; \ + else \ + echo "WARN: shellcheck not installed, skipping"; \ + fi + +.PHONY: ci-validate +ci-validate: validate-terraform lint-helm lint-shellcheck ## Layer 1: Static validation + +# --- Layer 2: Dry-run validation --- + +.PHONY: plan-terraform +plan-terraform: check-terraform check-tf-files ## Run terraform plan (preview only, no apply) + cd $(TF_DIR) && terraform init -backend-config=$(TF_BACKEND) + cd $(TF_DIR) && terraform plan -var-file=$(TF_VARS) + +# validate-chart: validate a single Helm chart with helm template +# Usage: $(call validate-chart,,,) +define validate-chart + $(call set-chart-ref,$(HELM_DIR)/$(1),$(2)) + helm dependency update $(HELM_DIR)/$(1) + @echo "Validating $(1) chart..." + helm template $(NAMESPACE)-$(1) $(HELM_DIR)/$(1) $(3) > /dev/null +endef + +.PHONY: validate-helm-charts +validate-helm-charts: check-helm ## Render all Helm charts with helm template (no cluster required) + $(call validate-chart,api,$(API_CHART_REF),\ + $(if $(REGISTRY),--set hyperfleet-api.image.registry=$(REGISTRY)) \ + --set hyperfleet-api.image.tag=$(API_IMAGE_TAG)) + + $(call validate-chart,sentinel-clusters,$(SENTINEL_CHART_REF),\ + --set sentinel.broker.type=$(BROKER_TYPE) \ + $(if $(REGISTRY),--set sentinel.image.registry=$(REGISTRY)) \ + --set sentinel.image.tag=$(SENTINEL_IMAGE_TAG)) + + $(call validate-chart,sentinel-nodepools,$(SENTINEL_CHART_REF),\ + --set sentinel.broker.type=$(BROKER_TYPE) \ + $(if $(REGISTRY),--set sentinel.image.registry=$(REGISTRY)) \ + --set sentinel.image.tag=$(SENTINEL_IMAGE_TAG)) + + $(call validate-chart,adapter1,$(ADAPTER_CHART_REF),\ + --set hyperfleet-adapter.broker.type=$(BROKER_TYPE) \ + $(if $(REGISTRY),--set hyperfleet-adapter.image.registry=$(REGISTRY)) \ + --set hyperfleet-adapter.image.tag=$(ADAPTER_IMAGE_TAG) \ + --set-file hyperfleet-adapter.adapterConfig.yaml=$(HELM_DIR)/adapter1/adapter-config.yaml \ + --set-file hyperfleet-adapter.adapterTaskConfig.yaml=$(HELM_DIR)/adapter1/adapter-task-config.yaml) + + $(call validate-chart,adapter2,$(ADAPTER_CHART_REF),\ + --set hyperfleet-adapter.broker.type=$(BROKER_TYPE) \ + $(if $(REGISTRY),--set hyperfleet-adapter.image.registry=$(REGISTRY)) \ + --set hyperfleet-adapter.image.tag=$(ADAPTER_IMAGE_TAG) \ + --set-file hyperfleet-adapter.adapterConfig.yaml=$(HELM_DIR)/adapter2/adapter-config.yaml \ + --set-file hyperfleet-adapter.adapterTaskConfig.yaml=$(HELM_DIR)/adapter2/adapter-task-config.yaml) + + $(call validate-chart,adapter3,$(ADAPTER_CHART_REF),\ + --set hyperfleet-adapter.broker.type=$(BROKER_TYPE) \ + $(if $(REGISTRY),--set hyperfleet-adapter.image.registry=$(REGISTRY)) \ + --set hyperfleet-adapter.image.tag=$(ADAPTER_IMAGE_TAG) \ + --set-file hyperfleet-adapter.adapterConfig.yaml=$(HELM_DIR)/adapter3/adapter-config.yaml \ + --set-file hyperfleet-adapter.adapterTaskConfig.yaml=$(HELM_DIR)/adapter3/adapter-task-config.yaml) + + helm dependency update $(HELM_DIR)/maestro + @echo "Validating maestro chart..." + helm template $(MAESTRO_NS)-maestro $(HELM_DIR)/maestro \ + --set agent.messageBroker.mqtt.host=maestro-mqtt.$(MAESTRO_NS) > /dev/null + @echo "OK: all Helm charts rendered successfully" + +.PHONY: ci-dry-run +ci-dry-run: ci-validate ## Layer 2: Static + dry-run validation (no credentials required) + $(MAKE) validate-helm-charts BROKER_TYPE=rabbitmq + $(MAKE) validate-helm-charts BROKER_TYPE=googlepubsub + +# --- Layer 3: Full integration test --- + +.PHONY: health-check +health-check: check-kubectl ## Verify all HyperFleet components are healthy + @echo "Checking HyperFleet components..." + @kubectl wait --for=condition=ready pods --all --namespace $(NAMESPACE) --kubeconfig $(KUBECONFIG) --timeout=300s + @echo "Checking Maestro components..." + @kubectl wait --for=condition=ready pods --all --namespace $(MAESTRO_NS) --kubeconfig $(KUBECONFIG) --timeout=300s + @echo "OK: all components healthy" + +.PHONY: destroy-terraform +destroy-terraform: check-terraform check-tf-files ## Destroy Terraform-managed infrastructure + cd $(TF_DIR) && terraform init -backend-config=$(TF_BACKEND) + # Always use -auto-approve to prevent CI cleanup from hanging on interactive prompt + cd $(TF_DIR) && terraform destroy -var-file=$(TF_VARS) -auto-approve + +.PHONY: ci-test +ci-test: install-all health-check ## Layer 3: Full integration test + +.PHONY: ci-cleanup +ci-cleanup: uninstall-all destroy-terraform ## Layer 3: Cleanup after integration test + # ────────────────────────────────────────────── # Uninstall targets # ────────────────────────────────────────────── @@ -328,6 +452,8 @@ help: ## Print available targets @echo " SENTINEL_CHART_REF Git ref for sentinel helm chart source (default: SENTINEL_IMAGE_TAG)" @echo " ADAPTER_CHART_REF Git ref for adapter helm chart source (default: ADAPTER_IMAGE_TAG)" @echo " MAESTRO_CONSUMER Maestro consumer name (default: cluster1)" + @echo " DRY_RUN Set to any value (e.g., true) for Helm dry-run mode (default: empty)" + @echo " AUTO_APPROVE Set to any value (e.g., true) for non-interactive Terraform (default: empty)" @echo "" @echo "Targets:" @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-28s\033[0m %s\n", $$1, $$2}' diff --git a/OWNERS b/OWNERS new file mode 100644 index 0000000..4f27c40 --- /dev/null +++ b/OWNERS @@ -0,0 +1,35 @@ +approvers: +- "86254860" +- aredenba-rh +- ciaranRoche +- crizzo71 +- jsell-rh +- mbrudnoy +- Mischulee +- pnguyen44 +- rafabene +- rh-amarin +- tirthct +- tzhou5 +- vkareh +- xueli181114 +- yasun1 +- yingzhanredhat + +reviewers: +- "86254860" +- aredenba-rh +- ciaranRoche +- crizzo71 +- jsell-rh +- mbrudnoy +- Mischulee +- pnguyen44 +- rafabene +- rh-amarin +- tirthct +- tzhou5 +- vkareh +- xueli181114 +- yasun1 +- yingzhanredhat