diff --git a/api/runtime/hooks/v1alpha1/upgrade_plan_types.go b/api/runtime/hooks/v1alpha1/upgrade_plan_types.go new file mode 100644 index 000000000000..f5a267ef7368 --- /dev/null +++ b/api/runtime/hooks/v1alpha1/upgrade_plan_types.go @@ -0,0 +1,110 @@ +/* +Copyright 2025 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. +*/ + +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + clusterv1beta1 "sigs.k8s.io/cluster-api/api/core/v1beta1" + runtimecatalog "sigs.k8s.io/cluster-api/exp/runtime/catalog" +) + +// GenerateUpgradePlanRequest is the request of the GenerateUpgradePlan hook. +// +kubebuilder:object:root=true +type GenerateUpgradePlanRequest struct { + metav1.TypeMeta `json:",inline"` + + // CommonRequest contains fields common to all request types. + CommonRequest `json:",inline"` + + // cluster is the cluster ofject the lifecycle hook correspods to. + // +required + Cluster clusterv1beta1.Cluster `json:"cluster,omitempty,omitzero"` + + // fromControlPlaneKubernetesVersion is the current Kubernetes version of the control plane. + // +required + // +kubebuilder:validation:MinLength=1 + FromControlPlaneKubernetesVersion string `json:"fromControlPlaneKubernetesVersion,omitempty"` + + // fromWorkersKubernetesVersion is the current Kubernetes version of the workers. + // +optional + // +kubebuilder:validation:MinLength=1 + FromWorkersKubernetesVersion string `json:"fromWorkersKubernetesVersion,omitempty"` + + // toKubernetesVersion is the target Kubernetes version for the upgrade. + // +required + // +kubebuilder:validation:MinLength=1 + ToKubernetesVersion string `json:"toKubernetesVersion,omitempty"` +} + +var _ ResponseObject = &GenerateUpgradePlanResponse{} + +// GenerateUpgradePlanResponse is the response of the GenerateUpgradePlan hook. +// +kubebuilder:object:root=true +type GenerateUpgradePlanResponse struct { + metav1.TypeMeta `json:",inline"` + + // CommonResponse contains Status and Message fields common to all response types. + CommonResponse `json:",inline"` + + // controlPlaneUpgrades is the list of version upgrade steps for the control plane. + // Each entry represents an intermediate version that must be applied in sequence. + // +optional + // +listType=atomic + // +kubebuilder:validation:MinItems=1 + // +kubebuilder:validation:MaxItems=1000 + ControlPlaneUpgrades []UpgradeStep `json:"controlPlaneUpgrades,omitempty"` + + // workersUpgrades is the list of version upgrade steps for the workers. + // Each entry represents an intermediate version that must be applied in sequence. + // +optional + // +listType=atomic + // +kubebuilder:validation:MinItems=1 + // +kubebuilder:validation:MaxItems=1000 + WorkersUpgrades []UpgradeStep `json:"workersUpgrades,omitempty"` +} + +// UpgradeStep represents a single version upgrade step. +type UpgradeStep struct { + // version is the Kubernetes version for this upgrade step. + // +required + // +kubebuilder:validation:MinLength=1 + Version string `json:"version,omitempty"` +} + +// GenerateUpgradePlan is the hook that will be called to generate an upgrade plan +// for a cluster. This hook allows runtime extensions to specify intermediate +// Kubernetes versions that must be applied during an upgrade from the current +// version to the target version. +func GenerateUpgradePlan(*GenerateUpgradePlanRequest, *GenerateUpgradePlanResponse) {} + +func init() { + catalogBuilder.RegisterHook(GenerateUpgradePlan, &runtimecatalog.HookMeta{ + Tags: []string{"Chained Upgrade Hook"}, + Summary: "Cluster API Runtime will call this hook to generate an upgrade plan for a cluster", + Description: "Cluster API Runtime will call this hook to generate an upgrade plan for a cluster. " + + "Runtime Extension implementers can use this hook to specify intermediate Kubernetes versions " + + "that must be applied during an upgrade from the current version to the target version.\n" + + "\n" + + "For example, if upgrading from v1.29.0 to v1.33.0 requires intermediate versions v1.30.0, " + + "v1.31.0, and v1.32.0, the hook should return these intermediate versions in the response.\n" + + "\n" + + "Notes:\n" + + "- The response may include separate upgrade paths for control plane and workers\n" + + "- Each upgrade step represents a version that must be applied in sequence", + }) +} diff --git a/api/runtime/hooks/v1alpha1/zz_generated.deepcopy.go b/api/runtime/hooks/v1alpha1/zz_generated.deepcopy.go index 5d941e540d03..8ea3e72b3523 100644 --- a/api/runtime/hooks/v1alpha1/zz_generated.deepcopy.go +++ b/api/runtime/hooks/v1alpha1/zz_generated.deepcopy.go @@ -971,6 +971,67 @@ func (in *GeneratePatchesResponseItem) DeepCopy() *GeneratePatchesResponseItem { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GenerateUpgradePlanRequest) DeepCopyInto(out *GenerateUpgradePlanRequest) { + *out = *in + out.TypeMeta = in.TypeMeta + in.CommonRequest.DeepCopyInto(&out.CommonRequest) + in.Cluster.DeepCopyInto(&out.Cluster) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GenerateUpgradePlanRequest. +func (in *GenerateUpgradePlanRequest) DeepCopy() *GenerateUpgradePlanRequest { + if in == nil { + return nil + } + out := new(GenerateUpgradePlanRequest) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *GenerateUpgradePlanRequest) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GenerateUpgradePlanResponse) DeepCopyInto(out *GenerateUpgradePlanResponse) { + *out = *in + out.TypeMeta = in.TypeMeta + out.CommonResponse = in.CommonResponse + if in.ControlPlaneUpgrades != nil { + in, out := &in.ControlPlaneUpgrades, &out.ControlPlaneUpgrades + *out = make([]UpgradeStep, len(*in)) + copy(*out, *in) + } + if in.WorkersUpgrades != nil { + in, out := &in.WorkersUpgrades, &out.WorkersUpgrades + *out = make([]UpgradeStep, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GenerateUpgradePlanResponse. +func (in *GenerateUpgradePlanResponse) DeepCopy() *GenerateUpgradePlanResponse { + if in == nil { + return nil + } + out := new(GenerateUpgradePlanResponse) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *GenerateUpgradePlanResponse) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *GroupVersionHook) DeepCopyInto(out *GroupVersionHook) { *out = *in @@ -1210,6 +1271,21 @@ func (in *UpdateMachineResponse) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *UpgradeStep) DeepCopyInto(out *UpgradeStep) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UpgradeStep. +func (in *UpgradeStep) DeepCopy() *UpgradeStep { + if in == nil { + return nil + } + out := new(UpgradeStep) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ValidateTopologyRequest) DeepCopyInto(out *ValidateTopologyRequest) { *out = *in diff --git a/api/runtime/hooks/v1alpha1/zz_generated.openapi.go b/api/runtime/hooks/v1alpha1/zz_generated.openapi.go index 61844d63121a..b3c1a0675c1b 100644 --- a/api/runtime/hooks/v1alpha1/zz_generated.openapi.go +++ b/api/runtime/hooks/v1alpha1/zz_generated.openapi.go @@ -66,6 +66,8 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "sigs.k8s.io/cluster-api/api/runtime/hooks/v1alpha1.GeneratePatchesRequestItem": schema_api_runtime_hooks_v1alpha1_GeneratePatchesRequestItem(ref), "sigs.k8s.io/cluster-api/api/runtime/hooks/v1alpha1.GeneratePatchesResponse": schema_api_runtime_hooks_v1alpha1_GeneratePatchesResponse(ref), "sigs.k8s.io/cluster-api/api/runtime/hooks/v1alpha1.GeneratePatchesResponseItem": schema_api_runtime_hooks_v1alpha1_GeneratePatchesResponseItem(ref), + "sigs.k8s.io/cluster-api/api/runtime/hooks/v1alpha1.GenerateUpgradePlanRequest": schema_api_runtime_hooks_v1alpha1_GenerateUpgradePlanRequest(ref), + "sigs.k8s.io/cluster-api/api/runtime/hooks/v1alpha1.GenerateUpgradePlanResponse": schema_api_runtime_hooks_v1alpha1_GenerateUpgradePlanResponse(ref), "sigs.k8s.io/cluster-api/api/runtime/hooks/v1alpha1.GroupVersionHook": schema_api_runtime_hooks_v1alpha1_GroupVersionHook(ref), "sigs.k8s.io/cluster-api/api/runtime/hooks/v1alpha1.HolderReference": schema_api_runtime_hooks_v1alpha1_HolderReference(ref), "sigs.k8s.io/cluster-api/api/runtime/hooks/v1alpha1.MachineBootstrapBuiltins": schema_api_runtime_hooks_v1alpha1_MachineBootstrapBuiltins(ref), @@ -77,6 +79,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "sigs.k8s.io/cluster-api/api/runtime/hooks/v1alpha1.UpdateMachineRequest": schema_api_runtime_hooks_v1alpha1_UpdateMachineRequest(ref), "sigs.k8s.io/cluster-api/api/runtime/hooks/v1alpha1.UpdateMachineRequestObjects": schema_api_runtime_hooks_v1alpha1_UpdateMachineRequestObjects(ref), "sigs.k8s.io/cluster-api/api/runtime/hooks/v1alpha1.UpdateMachineResponse": schema_api_runtime_hooks_v1alpha1_UpdateMachineResponse(ref), + "sigs.k8s.io/cluster-api/api/runtime/hooks/v1alpha1.UpgradeStep": schema_api_runtime_hooks_v1alpha1_UpgradeStep(ref), "sigs.k8s.io/cluster-api/api/runtime/hooks/v1alpha1.ValidateTopologyRequest": schema_api_runtime_hooks_v1alpha1_ValidateTopologyRequest(ref), "sigs.k8s.io/cluster-api/api/runtime/hooks/v1alpha1.ValidateTopologyRequestItem": schema_api_runtime_hooks_v1alpha1_ValidateTopologyRequestItem(ref), "sigs.k8s.io/cluster-api/api/runtime/hooks/v1alpha1.ValidateTopologyResponse": schema_api_runtime_hooks_v1alpha1_ValidateTopologyResponse(ref), @@ -1923,6 +1926,164 @@ func schema_api_runtime_hooks_v1alpha1_GeneratePatchesResponseItem(ref common.Re } } +func schema_api_runtime_hooks_v1alpha1_GenerateUpgradePlanRequest(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "GenerateUpgradePlanRequest is the request of the GenerateUpgradePlan hook.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "settings": { + SchemaProps: spec.SchemaProps{ + Description: "settings defines key value pairs to be passed to the call.", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "cluster": { + SchemaProps: spec.SchemaProps{ + Description: "cluster is the cluster ofject the lifecycle hook correspods to.", + Default: map[string]interface{}{}, + Ref: ref("sigs.k8s.io/cluster-api/api/core/v1beta1.Cluster"), + }, + }, + "fromControlPlaneKubernetesVersion": { + SchemaProps: spec.SchemaProps{ + Description: "fromControlPlaneKubernetesVersion is the current Kubernetes version of the control plane.", + Type: []string{"string"}, + Format: "", + }, + }, + "fromWorkersKubernetesVersion": { + SchemaProps: spec.SchemaProps{ + Description: "fromWorkersKubernetesVersion is the current Kubernetes version of the workers.", + Type: []string{"string"}, + Format: "", + }, + }, + "toKubernetesVersion": { + SchemaProps: spec.SchemaProps{ + Description: "toKubernetesVersion is the target Kubernetes version for the upgrade.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"cluster", "fromControlPlaneKubernetesVersion", "toKubernetesVersion"}, + }, + }, + Dependencies: []string{ + "sigs.k8s.io/cluster-api/api/core/v1beta1.Cluster"}, + } +} + +func schema_api_runtime_hooks_v1alpha1_GenerateUpgradePlanResponse(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "GenerateUpgradePlanResponse is the response of the GenerateUpgradePlan hook.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "status": { + SchemaProps: spec.SchemaProps{ + Description: "status of the call. One of \"Success\" or \"Failure\".\n\nPossible enum values:\n - `\"Failure\"` represents a failure response.\n - `\"Success\"` represents a success response.", + Default: "", + Type: []string{"string"}, + Format: "", + Enum: []interface{}{"Failure", "Success"}, + }, + }, + "message": { + SchemaProps: spec.SchemaProps{ + Description: "message is a human-readable description of the status of the call.", + Type: []string{"string"}, + Format: "", + }, + }, + "controlPlaneUpgrades": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "controlPlaneUpgrades is the list of version upgrade steps for the control plane. Each entry represents an intermediate version that must be applied in sequence.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("sigs.k8s.io/cluster-api/api/runtime/hooks/v1alpha1.UpgradeStep"), + }, + }, + }, + }, + }, + "workersUpgrades": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "workersUpgrades is the list of version upgrade steps for the workers. Each entry represents an intermediate version that must be applied in sequence.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("sigs.k8s.io/cluster-api/api/runtime/hooks/v1alpha1.UpgradeStep"), + }, + }, + }, + }, + }, + }, + Required: []string{"status"}, + }, + }, + Dependencies: []string{ + "sigs.k8s.io/cluster-api/api/runtime/hooks/v1alpha1.UpgradeStep"}, + } +} + func schema_api_runtime_hooks_v1alpha1_GroupVersionHook(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -2372,6 +2533,27 @@ func schema_api_runtime_hooks_v1alpha1_UpdateMachineResponse(ref common.Referenc } } +func schema_api_runtime_hooks_v1alpha1_UpgradeStep(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "UpgradeStep represents a single version upgrade step.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "version": { + SchemaProps: spec.SchemaProps{ + Description: "version is the Kubernetes version for this upgrade step.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"version"}, + }, + }, + } +} + func schema_api_runtime_hooks_v1alpha1_ValidateTopologyRequest(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{