Skip to content

Commit 9dcb6a0

Browse files
Add in-place update hooks to API
Signed-off-by: Alexandr Demicev <[email protected]> Co-authored-by: Stefan Büringer <[email protected]>
1 parent cd8a2c8 commit 9dcb6a0

File tree

9 files changed

+1229
-17
lines changed

9 files changed

+1229
-17
lines changed

.golangci-kal.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,11 @@ linters:
105105
## Excludes for current clusterctl v1alpha3 and Runtime Hooks v1alpha1 apiVersions (can be fixed once we bump their apiVersion).
106106
# Note: The types in api/runtime/hooks/v1alpha1 are not CRDs, so e.g. SSA markers don't make sense there.
107107
- path: "cmd/clusterctl/api/v1alpha3|api/runtime/hooks/v1alpha1"
108-
text: "optionalfields|requiredfields|maxlength|ssatags"
108+
text: "maxlength|ssatags"
109+
linters:
110+
- kubeapilinter
111+
- path: "cmd/clusterctl/api/v1alpha3|api/runtime/hooks/v1alpha1/(common_types.go|discovery_types.go|lifecyclehooks_types.go|topologymutation_types.go|topologymutation_variable_types.go)"
112+
text: "optionalfields|requiredfields"
109113
linters:
110114
- kubeapilinter
111115

api/runtime/hooks/v1alpha1/common_types.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,3 +134,15 @@ func (r *CommonRetryResponse) GetRetryAfterSeconds() int32 {
134134
func (r *CommonRetryResponse) SetRetryAfterSeconds(retryAfterSeconds int32) {
135135
r.RetryAfterSeconds = retryAfterSeconds
136136
}
137+
138+
// PatchType defines the supported patch types.
139+
// +kubebuilder:validation:Enum=JSONPatch;JSONMergePatch
140+
type PatchType string
141+
142+
const (
143+
// JSONPatchType identifies a https://datatracker.ietf.org/doc/html/rfc6902 JSON patch.
144+
JSONPatchType PatchType = "JSONPatch"
145+
146+
// JSONMergePatchType identifies a https://datatracker.ietf.org/doc/html/rfc7386 JSON merge patch.
147+
JSONMergePatchType PatchType = "JSONMergePatch"
148+
)
Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
/*
2+
Copyright 2025 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package v1alpha1
18+
19+
import (
20+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
21+
"k8s.io/apimachinery/pkg/runtime"
22+
23+
clusterv1 "sigs.k8s.io/cluster-api/api/core/v1beta2"
24+
runtimecatalog "sigs.k8s.io/cluster-api/exp/runtime/catalog"
25+
)
26+
27+
// CanUpdateMachineRequest is the request of the CanUpdateMachine hook.
28+
// +kubebuilder:object:root=true
29+
type CanUpdateMachineRequest struct {
30+
metav1.TypeMeta `json:",inline"`
31+
32+
// CommonRequest contains fields common to all request types.
33+
CommonRequest `json:",inline"`
34+
35+
// current contains the current state of the Machine and related objects.
36+
// +required
37+
Current CanUpdateMachineRequestObjects `json:"current,omitempty,omitzero"`
38+
39+
// desired contains the desired state of the Machine and related objects.
40+
// +required
41+
Desired CanUpdateMachineRequestObjects `json:"desired,omitempty,omitzero"`
42+
}
43+
44+
// CanUpdateMachineRequestObjects groups objects for CanUpdateMachineRequest.
45+
type CanUpdateMachineRequestObjects struct {
46+
// machine is the full Machine object.
47+
// +required
48+
Machine clusterv1.Machine `json:"machine,omitempty,omitzero"`
49+
50+
// infrastructureMachine is the infra Machine object.
51+
// +required
52+
InfrastructureMachine runtime.RawExtension `json:"infrastructureMachine,omitempty,omitzero"`
53+
54+
// bootstrapConfig is the bootstrap config object.
55+
// +optional
56+
BootstrapConfig runtime.RawExtension `json:"bootstrapConfig,omitempty,omitzero"`
57+
}
58+
59+
var _ ResponseObject = &CanUpdateMachineResponse{}
60+
61+
// CanUpdateMachineResponse is the response of the CanUpdateMachine hook.
62+
// +kubebuilder:object:root=true
63+
type CanUpdateMachineResponse struct {
64+
metav1.TypeMeta `json:",inline"`
65+
66+
// CommonResponse contains Status and Message fields common to all response types.
67+
CommonResponse `json:",inline"`
68+
69+
// machinePatch when applied to the current Machine spec, indicates changes handled in-place.
70+
// +optional
71+
MachinePatch Patch `json:"machinePatch,omitempty,omitzero"`
72+
73+
// infrastructureMachinePatch indicates infra Machine spec changes handled in-place.
74+
// +optional
75+
InfrastructureMachinePatch Patch `json:"infrastructureMachinePatch,omitempty,omitzero"`
76+
77+
// bootstrapConfigPatch indicates bootstrap config spec changes handled in-place.
78+
// +optional
79+
BootstrapConfigPatch Patch `json:"bootstrapConfigPatch,omitempty,omitzero"`
80+
}
81+
82+
// Patch is a single patch (JSONPatch or JSONMergePatch) which can include multiple operations.
83+
type Patch struct {
84+
// patchType JSONPatch or JSONMergePatch.
85+
// +required
86+
PatchType PatchType `json:"patchType,omitempty"`
87+
88+
// patch data for the target object.
89+
// +required
90+
Patch []byte `json:"patch,omitempty"`
91+
}
92+
93+
// CanUpdateMachine is the hook that will be called to determine if an extension
94+
// can handle specific machine changes for in-place updates.
95+
func CanUpdateMachine(*CanUpdateMachineRequest, *CanUpdateMachineResponse) {}
96+
97+
// CanUpdateMachineSetRequest is the request of the CanUpdateMachineSet hook.
98+
// +kubebuilder:object:root=true
99+
type CanUpdateMachineSetRequest struct {
100+
metav1.TypeMeta `json:",inline"`
101+
102+
// CommonRequest contains fields common to all request types.
103+
CommonRequest `json:",inline"`
104+
105+
// current contains the current state of the MachineSet and related objects.
106+
// +required
107+
Current CanUpdateMachineSetRequestObjects `json:"current,omitempty,omitzero"`
108+
109+
// desired contains the desired state of the MachineSet and related objects.
110+
// +required
111+
Desired CanUpdateMachineSetRequestObjects `json:"desired,omitempty,omitzero"`
112+
}
113+
114+
// CanUpdateMachineSetRequestObjects groups objects for CanUpdateMachineSetRequest.
115+
type CanUpdateMachineSetRequestObjects struct {
116+
// machineSet is the full MachineSet object.
117+
// +required
118+
MachineSet clusterv1.MachineSet `json:"machineSet,omitempty,omitzero"`
119+
120+
// infrastructureMachineTemplate is the provider-specific InfrastructureMachineTemplate object.
121+
// +required
122+
InfrastructureMachineTemplate runtime.RawExtension `json:"infrastructureMachineTemplate,omitempty,omitzero"`
123+
124+
// bootstrapConfigTemplate is the provider-specific BootstrapConfigTemplate object.
125+
// +optional
126+
BootstrapConfigTemplate runtime.RawExtension `json:"bootstrapConfigTemplate,omitempty,omitzero"`
127+
}
128+
129+
var _ ResponseObject = &CanUpdateMachineSetResponse{}
130+
131+
// CanUpdateMachineSetResponse is the response of the CanUpdateMachineSet hook.
132+
// +kubebuilder:object:root=true
133+
type CanUpdateMachineSetResponse struct {
134+
metav1.TypeMeta `json:",inline"`
135+
136+
// CommonResponse contains Status and Message fields common to all response types.
137+
CommonResponse `json:",inline"`
138+
139+
// machineSetPatch when applied to the current MachineSet spec, indicates changes handled in-place.
140+
// +optional
141+
MachineSetPatch Patch `json:"machineSetPatch,omitempty,omitzero"`
142+
143+
// infrastructureMachineTemplatePatch indicates infra template spec changes handled in-place.
144+
// +optional
145+
InfrastructureMachineTemplatePatch Patch `json:"infrastructureMachineTemplatePatch,omitempty,omitzero"`
146+
147+
// bootstrapConfigTemplatePatch indicates bootstrap template spec changes handled in-place.
148+
// +optional
149+
BootstrapConfigTemplatePatch Patch `json:"bootstrapConfigTemplatePatch,omitempty,omitzero"`
150+
}
151+
152+
// CanUpdateMachineSet is the hook that will be called to determine if an extension
153+
// can handle specific MachineSet changes for in-place updates.
154+
func CanUpdateMachineSet(*CanUpdateMachineSetRequest, *CanUpdateMachineSetResponse) {}
155+
156+
// UpdateMachineRequest is the request of the UpdateMachine hook.
157+
// +kubebuilder:object:root=true
158+
type UpdateMachineRequest struct {
159+
metav1.TypeMeta `json:",inline"`
160+
161+
// CommonRequest contains fields common to all request types.
162+
CommonRequest `json:",inline"`
163+
164+
// desired contains the desired state of the Machine and related objects.
165+
// +required
166+
Desired UpdateMachineRequestObjects `json:"desired,omitempty,omitzero"`
167+
}
168+
169+
// UpdateMachineRequestObjects groups objects for UpdateMachineRequest.
170+
type UpdateMachineRequestObjects struct {
171+
// machine is the full Machine object.
172+
// +required
173+
Machine clusterv1.Machine `json:"machine,omitempty,omitzero"`
174+
175+
// infrastructureMachine is the infra Machine object.
176+
// +required
177+
InfrastructureMachine runtime.RawExtension `json:"infrastructureMachine,omitempty,omitzero"`
178+
179+
// bootstrapConfig is the bootstrap config object.
180+
// +optional
181+
BootstrapConfig runtime.RawExtension `json:"bootstrapConfig,omitempty,omitzero"`
182+
}
183+
184+
var _ RetryResponseObject = &UpdateMachineResponse{}
185+
186+
// UpdateMachineResponse is the response of the UpdateMachine hook.
187+
// The status of the update operation is determined by the CommonRetryResponse fields:
188+
// - Status=Success + RetryAfterSeconds > 0: update is in progress
189+
// - Status=Success + RetryAfterSeconds = 0: update completed successfully
190+
// - Status=Failure: update failed
191+
// +kubebuilder:object:root=true
192+
type UpdateMachineResponse struct {
193+
metav1.TypeMeta `json:",inline"`
194+
195+
// CommonRetryResponse contains Status, Message and RetryAfterSeconds fields.
196+
CommonRetryResponse `json:",inline"`
197+
}
198+
199+
// UpdateMachine is the hook that will be called to perform in-place updates on a machine.
200+
// This hook should be idempotent and can be called multiple times for the same machine
201+
// until it reports Done or Failed status.
202+
func UpdateMachine(*UpdateMachineRequest, *UpdateMachineResponse) {}
203+
204+
func init() {
205+
catalogBuilder.RegisterHook(CanUpdateMachine, &runtimecatalog.HookMeta{
206+
Tags: []string{"In-Place Update Hooks"},
207+
Summary: "Cluster API Runtime will call this hook to determine if an extension can handle specific Machine changes",
208+
Description: "Called during update planning to determine if an extension can handle Machine changes. " +
209+
"The request contains current and desired state for Machine, InfraMachine and optionally BootstrapConfig. " +
210+
"Extensions should return per-object patches to be applied on current objects to indicate which changes they can handle in-place.\n" +
211+
"\n" +
212+
"Notes:\n" +
213+
"- This hook is called during the planning phase of updates\n" +
214+
"- Only spec is provided, status fields are not included\n" +
215+
"- If no extension can cover the required changes, CAPI will fallback to rolling updates\n",
216+
})
217+
218+
catalogBuilder.RegisterHook(CanUpdateMachineSet, &runtimecatalog.HookMeta{
219+
Tags: []string{"In-Place Update Hooks"},
220+
Summary: "Cluster API Runtime will call this hook to determine if an extension can handle specific MachineSet changes",
221+
Description: "Called during update planning to determine if an extension can handle MachineSet changes. " +
222+
"The request contains current and desired state for MachineSet, InfraMachineTemplate and optionally BootstrapConfigTemplate. " +
223+
"Extensions should return per-object patches to be applied on current objects to indicate which changes they can handle in-place.\n" +
224+
"\n" +
225+
"Notes:\n" +
226+
"- This hook is called during the planning phase of updates\n" +
227+
"- Only spec is provided, status fields are not included\n" +
228+
"- If no extension can cover the required changes, CAPI will fallback to rolling updates\n",
229+
})
230+
231+
catalogBuilder.RegisterHook(UpdateMachine, &runtimecatalog.HookMeta{
232+
Tags: []string{"In-Place Update Hooks"},
233+
Summary: "Cluster API Runtime will call this hook to perform in-place updates on a Machine",
234+
Description: "Cluster API Runtime will call this hook to perform the actual in-place update on a Machine. " +
235+
"The request contains the desired state for Machine, InfraMachine and optionally BootstrapConfig. " +
236+
"The hook will be called repeatedly until it reports Done or Failed status.\n" +
237+
"\n" +
238+
"Notes:\n" +
239+
"- This hook must be idempotent - it can be called multiple times for the same Machine\n",
240+
})
241+
}

api/runtime/hooks/v1alpha1/topologymutation_types.go

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -101,18 +101,6 @@ type GeneratePatchesResponseItem struct {
101101
Patch []byte `json:"patch"`
102102
}
103103

104-
// PatchType defines the supported patch types.
105-
// +enum
106-
type PatchType string
107-
108-
const (
109-
// JSONPatchType identifies a https://datatracker.ietf.org/doc/html/rfc6902 JSON patch.
110-
JSONPatchType PatchType = "JSONPatch"
111-
112-
// JSONMergePatchType identifies a https://datatracker.ietf.org/doc/html/rfc7386 JSON merge patch.
113-
JSONMergePatchType PatchType = "JSONMergePatch"
114-
)
115-
116104
// GeneratePatches generates patches during topology reconciliation for the entire Cluster topology.
117105
func GeneratePatches(*GeneratePatchesRequest, *GeneratePatchesResponse) {}
118106

0 commit comments

Comments
 (0)