diff --git a/Makefile b/Makefile index 57748a7ed..e880c708c 100644 --- a/Makefile +++ b/Makefile @@ -587,6 +587,17 @@ generate-modules-ci: generate-modules exit 1; \ fi +.PHONY: generate-readme-usage +generate-readme-usage: ## Update the README usage block from the main binary +ifeq ($(BUILD_IN_CONTAINER),true) + docker run --rm \ + -v $(shell go env GOPATH)/pkg:/go/pkg$(MOUNT_FLAGS) \ + -v $(shell pwd):/src/cluster-api-provider-$(INFRA_PROVIDER)$(MOUNT_FLAGS) \ + $(BUILDER_IMAGE):$(BUILDER_IMAGE_VERSION) $@; +else + ./hack/update-readme-usage.sh +endif + generate-manifests: ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects. ifeq ($(BUILD_IN_CONTAINER),true) docker run --rm \ @@ -753,7 +764,7 @@ else endif .PHONY: generate -generate: generate-manifests generate-go-deepcopy generate-boilerplate generate-modules generate-mocks ## Generate Files +generate: generate-manifests generate-go-deepcopy generate-boilerplate generate-modules generate-mocks generate-readme-usage ## Generate Files ALL_VERIFY_CHECKS = boilerplate shellcheck starlark manifests capi-version .PHONY: verify diff --git a/README.md b/README.md index 0dd08932f..cbd0ef8ad 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ - [What is CAPH](#-what-is-the-cluster-api-provider-hetzner) - [Documentation](#-documentation) - [Getting Started](#-getting-started) +- [CLI Usage](#-cli-usage) - [Version Compatibility](#%EF%B8%8F-compatibility-with-cluster-api-and-kubernetes-versions) - [Node Images](#-operating-system-images) - [Contributing](#-getting-involved-and-contributing) @@ -67,6 +68,41 @@ Additional resources from the documentation: In addition to the pure creation and operation of Kubernetes clusters, this provider can also validate and approve certificate signing requests. This increases security as the kubelets of the nodes can be operated with signed certificates, and enables the metrics-server to run securely. [Click here](https://syself.com/docs/caph/topics/advanced/csr-controller) to read more about the CSR controller. +## 🧭 CLI Usage + +The main binary prints its usage text before trying to connect to a Kubernetes cluster. The block below is updated from the binary itself. + + +```console +$ cluster-api-provider-hetzner --help +Usage: + cluster-api-provider-hetzner [flags] + +Flags: + --baremetal-image-url-command string Command to run (in rescue-system) to provision an baremetal machine. Docs: https://syself.com/docs/caph/developers/image-url-command + --baremetal-ssh-after-install-image Connect to the baremetal machine after install-image and ensure it is provisioned. Current default is true, but we might change that to false. Background: Users might not want the controller to be able to ssh onto the servers (default true) + --debug-hcloud-api-calls Debug all calls to the hcloud API. + --disable-csr-approval Disables builtin workload cluster CSR validation and approval. + --hcloudmachine-concurrency int Number of HcloudMachines to process simultaneously (default 1) + --health-probe-bind-address string The address the probe endpoint binds to. (default ":9440") + --hetznerbaremetalhost-concurrency int Number of HetznerBareMetalHosts to process simultaneously (default 1) + --hetznerbaremetalmachine-concurrency int Number of HetznerBareMetalMachines to process simultaneously (default 1) + --hetznercluster-concurrency int Number of HetznerClusters to process simultaneously (default 1) + --kubeconfig string Paths to a kubeconfig. Only required if out-of-cluster. + --leader-elect Enable leader election for controller manager. Enabling this will ensure there is only one active controller manager. (default true) + --leader-elect-namespace string Namespace that the controller performs leader election in. If unspecified, the controller will discover which namespace it is running in. + --log-level string Specifies log level. Options are 'debug', 'info' and 'error' (default "info") + --metrics-bind-address string The address the metric endpoint binds to. (default "localhost:8080") + --namespace string Namespace that the controller watches to reconcile cluster-api objects. If unspecified, the controller watches for cluster-api objects across all namespaces. + --pre-provision-command string Command to run (in rescue-system) before installing the image on bare metal servers. You can use that to check if the machine is healthy before installing the image. If the exit value is non-zero, the machine is considered unhealthy. This command must be accessible by the controller pod. You can use an initContainer to copy the command to a shared emptyDir. + --rate-limit duration The rate limiting for HCloud controller (e.g. 5m) (default 5m0s) + --skip-crd-migration-phases strings List of CRD migration phases to skip. Valid values are: StorageVersionMigration, CleanupManagedFields. + --skip-webhooks Skip setting up webhooks. Together with --leader-elect=false, this lets you run CAPH in a cluster connected via KUBECONFIG. You should scale down the deployed CAPH controller to 0 before doing that. This is only for testing. + --sync-period duration The minimum interval at which watched resources are reconciled (e.g. 3m) (default 3m0s) + --watch-filter string Label value that the controller watches to reconcile cluster-api objects. Label key is always cluster.x-k8s.io/watch-filter. If unspecified, the controller watches for all cluster-api objects. +``` + + ## šŸ–‡ļø Compatibility with Cluster API and Kubernetes Versions [Compatibility Table](./docs/caph/01-getting-started/01-introduction.md#compatibility-with-cluster-api-and-kubernetes-versions) diff --git a/hack/update-readme-usage.sh b/hack/update-readme-usage.sh new file mode 100755 index 000000000..390810f13 --- /dev/null +++ b/hack/update-readme-usage.sh @@ -0,0 +1,95 @@ +#!/usr/bin/env bash + +# Copyright 2026 The Kubernetes 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. + +# Regenerate the README CLI usage block from the current binary's --help output. +# Run this after changing CLI flags or help text. + +# Bash Strict Mode: https://github.com/guettli/bash-strict-mode +trap 'echo -e "\n🤷 🚨 šŸ”„ Warning: A command has failed. Exiting the script. Line was ($0:$LINENO): $(sed -n "${LINENO}p" "$0" 2>/dev/null || true) šŸ”„ 🚨 🤷 "; exit 3' ERR +set -Eeuo pipefail + +readonly REPO_ROOT="$(git rev-parse --show-toplevel)" +readonly README_FILE="$REPO_ROOT/README.md" +readonly START_MARKER="" +readonly END_MARKER="" + +cd "$REPO_ROOT" + +if ! grep -Fq "$START_MARKER" "$README_FILE"; then + echo "Missing README marker: $START_MARKER" + exit 1 +fi + +if ! grep -Fq "$END_MARKER" "$README_FILE"; then + echo "Missing README marker: $END_MARKER" + exit 1 +fi + +tmp_dir="$(mktemp -d)" +cleanup() { + rm -rf "$tmp_dir" +} +trap cleanup EXIT + +binary_path="$tmp_dir/cluster-api-provider-hetzner" +usage_output_file="$tmp_dir/usage.txt" +replacement_file="$tmp_dir/replacement.txt" +updated_readme_file="$tmp_dir/README.md" + +go build -o "$binary_path" . +"$binary_path" --help >"$usage_output_file" + +{ + echo "$START_MARKER" + echo '```console' + echo '$ cluster-api-provider-hetzner --help' + cat "$usage_output_file" + echo '```' + echo "$END_MARKER" +} >"$replacement_file" + +awk \ + -v start="$START_MARKER" \ + -v end="$END_MARKER" \ + -v replacement="$replacement_file" \ + ' +BEGIN { + while ((getline line < replacement) > 0) { + replacement_text = replacement_text line ORS + } +} +$0 == start { + printf "%s", replacement_text + in_block = 1 + found_start = 1 + next +} +$0 == end { + in_block = 0 + found_end = 1 + next +} +!in_block { + print +} +END { + if (!found_start || !found_end) { + exit 1 + } +} +' "$README_FILE" >"$updated_readme_file" + +mv "$updated_readme_file" "$README_FILE" diff --git a/main.go b/main.go index 794693b42..772cf49ca 100644 --- a/main.go +++ b/main.go @@ -22,6 +22,7 @@ import ( "errors" "flag" "fmt" + "io" "os" "path/filepath" "regexp" @@ -66,6 +67,8 @@ var ( commandRegex = regexp.MustCompile(`^[a-z][a-z0-9_.-]+[a-z0-9]$`) ) +const binaryName = "cluster-api-provider-hetzner" + func init() { utilruntime.Must(clientgoscheme.AddToScheme(scheme)) utilruntime.Must(apiextensionsv1.AddToScheme(scheme)) @@ -109,8 +112,7 @@ func (m *strictManager) GetClient() client.Client { return m.strictClient } -func main() { - fs := pflag.CommandLine +func registerFlags(fs *pflag.FlagSet) { fs.StringVar(&metricsAddr, "metrics-bind-address", "localhost:8080", "The address the metric endpoint binds to.") fs.StringVar(&probeAddr, "health-probe-bind-address", ":9440", "The address the probe endpoint binds to.") fs.BoolVar(&enableLeaderElection, "leader-elect", true, "Enable leader election for controller manager. Enabling this will ensure there is only one active controller manager.") @@ -127,11 +129,35 @@ func main() { fs.DurationVar(&rateLimitWaitTime, "rate-limit", 5*time.Minute, "The rate limiting for HCloud controller (e.g. 5m)") fs.BoolVar(&hcloudclient.DebugAPICalls, "debug-hcloud-api-calls", false, "Debug all calls to the hcloud API.") fs.StringVar(&preProvisionCommand, "pre-provision-command", "", "Command to run (in rescue-system) before installing the image on bare metal servers. You can use that to check if the machine is healthy before installing the image. If the exit value is non-zero, the machine is considered unhealthy. This command must be accessible by the controller pod. You can use an initContainer to copy the command to a shared emptyDir.") - fs.BoolVar(&skipWebhooks, "skip-webhooks", false, "Skip setting up of webhooks. Together with --leader-elect=false, you can use `go run main.go` to run CAPH in a cluster connected via KUBECONFIG. You should scale down the caph deployment to 0 before doing that. This is only for testing!") + fs.BoolVar(&skipWebhooks, "skip-webhooks", false, "Skip setting up webhooks. Together with --leader-elect=false, this lets you run CAPH in a cluster connected via KUBECONFIG. You should scale down the deployed CAPH controller to 0 before doing that. This is only for testing.") fs.StringSliceVar(&skipCRDMigrationPhases, "skip-crd-migration-phases", []string{}, "List of CRD migration phases to skip. Valid values are: StorageVersionMigration, CleanupManagedFields.") - pflag.CommandLine.AddGoFlagSet(flag.CommandLine) - pflag.Parse() + fs.AddGoFlagSet(flag.CommandLine) +} + +func printUsage(fs *pflag.FlagSet, out io.Writer) { + fs.SetOutput(out) + defer fs.SetOutput(io.Discard) + + _, _ = fmt.Fprintf(out, "Usage:\n %s [flags]\n\nFlags:\n", binaryName) + fs.PrintDefaults() +} + +func main() { + fs := pflag.NewFlagSet(binaryName, pflag.ContinueOnError) + fs.SetOutput(io.Discard) + registerFlags(fs) + + if err := fs.Parse(os.Args[1:]); err != nil { + if errors.Is(err, pflag.ErrHelp) { + printUsage(fs, os.Stdout) + return + } + + _, _ = fmt.Fprintf(os.Stderr, "Error: %v\n\n", err) + printUsage(fs, os.Stderr) + os.Exit(2) + } ctrl.SetLogger(utils.GetDefaultLogger(logLevel))