preflight

package
v0.35.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 7, 2025 License: Apache-2.0 Imports: 13 Imported by: 0

README

Preflight Checks Framework

The preflight checks framework is a validating admission webhook that runs a series of checks before a Cluster resource is created. It helps ensure that a cluster's configuration is valid, and that the underlying infrastructure is ready, preventing common issues before they occur.

The framework is designed to be extensible, allowing different sets of checks to be grouped into logical units called Checkers.

Core Concepts

The framework is built around a few key interfaces and structs:

  • preflight.WebhookHandler: The main entry point for the webhook. It receives admission requests, decodes the Cluster object, and orchestrates the execution of all registered Checkers.

  • preflight.Checker: A collection of checks, logically related to some external API, and sharing dependencies, such as a client for the external API. Each Checker is responsible for initializing and returning a slice of Checks to be executed. At the time of this writing, we have two checkers:

    • generic.Checker: For checks that are not specific to any infrastructure provider.
    • nutanix.Checker: For checks specific to the Nutanix infrastructure. All the checks share a Prism Central API client.
  • preflight.Check: Represents a single, atomic validation. Each check must implement two methods:

    • Name() string: Returns a unique name for the check. This name is used for identification, and for skipping checks.
    • Run(ctx context.Context) CheckResult: Executes the validation logic.
  • preflight.CheckResult: The outcome of a Check. It indicates if the check was Allowed, if an InternalError occurred, and provides a list of Causes for failure and any Warnings.

Create a Checker
Implement a new Go package

Create a new package for your checker under the preflight directory. For example, pkg/webhook/preflight/myprovider/.

Create a checker.go file to define your Checker. This checker will initialize all the checks for your provider. A common pattern is to have a configuration pseudo-check that runs first, parses provider-specific configuration, initializes an API client, and then initialize checks with the configuration and client.

package myprovider

import (
    "context"

    clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    ctrl "sigs.k8s.io/controller-runtime"
    ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"

    "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/webhook/preflight"
)

// Expose the checker as a package variable.
var Checker = &myChecker{
    // Use factories to create checks.
}

type myChecker struct {
    // factories for creating checks
}

// checkDependencies holds shared data for all checks.
type checkDependencies struct {
    // provider-specific config, clients, etc.
}

func (m *myChecker) Init(
    ctx context.Context,
    client ctrlclient.Client,
    cluster *clusterv1.Cluster,
) []preflight.Check {
    log := ctrl.LoggerFrom(ctx).WithName("preflight/myprovider")

    cd := &checkDependencies{
        // initialize dependencies
    }

    checks := []preflight.Check{
        // It's good practice to have a configuration check run first.
        newConfigurationCheck(cd),
    }

    // Add other checks
    checks = append(checks, &myCheck{})

    return checks
}

The generic.Checker and nutanix.Checker serve as excellent reference implementations. The nutanix.Checker demonstrates a more complex setup with multiple dependent checks.

Register the Checker

Finally, add your new Checker to the list of checkers in main.go.

// ...existing code...
import (
 preflightgeneric "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/webhook/preflight/generic"
 preflightnutanix "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/webhook/preflight/nutanix"
 preflightmyprovider "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/webhook/preflight/myprovider"
)
// ...existing code...
 if err := mgr.Add(preflight.New(
  mgr.GetClient(),
  mgr.GetWebhookServer().
   GetAdmissionDecoder(),
  []preflight.Checker{
   // Add your preflight checkers here.
   preflightgeneric.Checker,
   preflightnutanix.Checker,
   preflightmyprovider.Checker,
  }...,
 )); err != nil {
// ...existing code...

Create a Check

Create a struct that implements the preflight.Check interface.

The Name method should return a concise, unique name that is a combination of the Checker and Check name, e.g. NutanixVMImage. Checks in the generic Checker only use the Check name, e.g. `Registry. The name is used to identify,and skip checks.

The Run method should return a CheckResult that indicates whether the check passed (Allowed), whether an internal error occurred (InternalError), and one or more Causeses, each including a Message that explains why the check failed, and a Field that points the user to the configuration that should be examined and possibly changed.

If a check runs to completion, then InternalError should be false. It should be true only in case of an unexpected error, such as a malformed response from some API.

If the check passes, then Allowed should be true.

The Message should include context to help the user understand the problem, and the most common ways to help them resolve the problem. Even so, the message should be concise, as it will be displayed in the CLI and UI clients.

The Field should be a valid JSONPath expression that identifies the most relevant part of the Cluster configuration. Look at existing checkers for examples.

package myprovider

import (
    "context"
    "fmt"

    "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/webhook/preflight"
)

type myCheck struct {
    // any dependencies the check needs
}

func (c *myCheck) Name() string {
    return "MyProviderCheck"
}

func (c *myCheck) Run(ctx context.Context) preflight.CheckResult {
    // Your validation logic here.
    // For example, check a specific condition.
    isValid := true // replace with real logic
    if !isValid {
        return preflight.CheckResult{
            Allowed: false,
            Causes: []preflight.Cause{
                {
                    Message: "My custom check failed because of a specific reason.",
                    Field:   "$.spec.topology.variables[?@.name=='myProviderConfig']",
                },
            },
        }
    }

    return preflight.CheckResult{Allowed: true}
}

Documentation

Overview

Copyright 2025 Nutanix. All rights reserved. SPDX-License-Identifier: Apache-2.0

Copyright 2025 Nutanix. All rights reserved. SPDX-License-Identifier: Apache-2.0

Index

Constants

View Source
const (
	// Timeout is the duration, in seconds, that the preflight checks handler has to respond.
	// IMPORTANT Keep in sync timeoutSeconds in the kubebuilder:webhook marker defined in this package.
	Timeout = 30 * time.Second
)

Variables

This section is empty.

Functions

This section is empty.

Types

type Cause

type Cause struct {
	// Message is a human-readable message describing the cause of the failure.
	Message string

	// Field is an optional field that the cause relates to.
	// It is used to indicate which part of the cluster configuration the cause relates to.
	// It is a JSONPath expression that points to the field in the cluster configuration.
	// For example, "spec.topology.variables[.name=clusterConfig].value.imageRegistries[0]".
	Field string
}

Cause represents a cause of a check failure. It contains a message and an optional field that the cause relates to. The field is used to indicate which part of the cluster configuration the cause relates to.

type Check

type Check interface {
	// Name returns the name of the check.
	// The name should be unique across all checks, and should be used to identify the check
	// in the CheckResult.
	// It is also used to skip the check if the cluster has skipped it.
	Name() string

	// Run executes the check and returns a CheckResult.
	Run(ctx context.Context) CheckResult
}

Check represents a single preflight check that can be run against a cluster. It has a Name method that returns the name of the check, and a Run method executes the check, and returns a CheckResult. The Name method is used to identify the check if Run fails to return a result, for example if it panics.

type CheckResult

type CheckResult struct {
	// Allowed indicates whether the check passed.
	Allowed bool

	// InternalError indicates whether there was an internal error running the check.
	// This should be false for most check failures. It can be true in case of an unexpected
	// error, like a network error, an API rate-limit error, etc.
	InternalError bool

	// Causes contains a list of causes for the failure. Each cause has a message and an
	// optional field that the cause relates to. The field is used to indicate which part of
	// the cluster configuration the cause relates to.
	Causes []Cause

	// Warnings contains a list of warnings returned by the check.
	// For example, a check should return a warning when the cluster uses configuration
	// not yet supported by the check.
	Warnings []string
}

CheckResult represents the result of a check. It contains the name of the check, a boolean indicating whether the check passed, an error boolean indicating whether there was an internal error running the check, and a list of causes for the failure. It also contains a list of warnings that were generated during the check.

type Checker

type Checker interface {
	// Init returns the checks that should run for the cluster.
	Init(ctx context.Context, client ctrlclient.Client, cluster *clusterv1.Cluster) []Check
}

Checker returns a set of checks that have been initialized with common dependencies, such as an infrastructure API client.

type WebhookHandler

type WebhookHandler struct {
	// contains filtered or unexported fields
}

func New

func New(client ctrlclient.Client, decoder admission.Decoder, checkers ...Checker) *WebhookHandler

func (*WebhookHandler) Handle

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL