Skip to content

Commit 6a3c5e8

Browse files
committed
ssa: introduce support for migrating API version
Signed-off-by: Matheus Pimenta <matheuscscp@gmail.com>
1 parent ad86bcd commit 6a3c5e8

3 files changed

Lines changed: 804 additions & 5 deletions

File tree

ssa/manager_apply.go

Lines changed: 67 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,11 @@ type ApplyOptions struct {
6868
// CustomStageKinds defines a set of Kubernetes resource types that should be applied
6969
// in a separate stage after CRDs and before namespaced objects.
7070
CustomStageKinds map[schema.GroupKind]struct{} `json:"customStageKinds,omitempty"`
71+
72+
// MigrateAPIVersion configures the engine to migrate the API version
73+
// in managed fields to the API version of the applied object when they
74+
// differ.
75+
MigrateAPIVersion bool `json:"migrateAPIVersion,omitempty"`
7176
}
7277

7378
// ApplyCleanupOptions defines which metadata entries are to be removed before applying objects.
@@ -108,6 +113,15 @@ func (m *ResourceManager) Apply(ctx context.Context, object *unstructured.Unstru
108113
return m.changeSetEntry(object, SkippedAction), nil
109114
}
110115

116+
var patched bool
117+
if opts.MigrateAPIVersion && getError == nil {
118+
var err error
119+
patched, err = m.migrateAPIVersion(ctx, existingObject, object.GetAPIVersion())
120+
if err != nil {
121+
return nil, fmt.Errorf("%s failed to migrate API version: %w", utils.FmtUnstructured(existingObject), err)
122+
}
123+
}
124+
111125
dryRunObject := object.DeepCopy()
112126
if err := m.dryRunApply(ctx, dryRunObject); err != nil {
113127
if !errors.IsNotFound(getError) && m.shouldForceApply(object, existingObject, opts, err) {
@@ -121,11 +135,12 @@ func (m *ResourceManager) Apply(ctx context.Context, object *unstructured.Unstru
121135
return nil, ssaerrors.NewDryRunErr(err, dryRunObject)
122136
}
123137

124-
patched, err := m.cleanupMetadata(ctx, object, existingObject, opts.Cleanup)
138+
patchedCleanupMetadata, err := m.cleanupMetadata(ctx, object, existingObject, opts.Cleanup)
125139
if err != nil {
126140
return nil, fmt.Errorf("%s metadata.managedFields cleanup failed: %w",
127141
utils.FmtUnstructured(existingObject), err)
128142
}
143+
patched = patched || patchedCleanupMetadata
129144

130145
// do not apply objects that have not drifted to avoid bumping the resource version
131146
if !patched && !m.hasDrifted(existingObject, dryRunObject) {
@@ -172,6 +187,15 @@ func (m *ResourceManager) ApplyAll(ctx context.Context, objects []*unstructured.
172187
return nil
173188
}
174189

190+
var patched bool
191+
if opts.MigrateAPIVersion && getError == nil {
192+
var err error
193+
patched, err = m.migrateAPIVersion(ctx, existingObject, object.GetAPIVersion())
194+
if err != nil {
195+
return fmt.Errorf("%s failed to migrate API version: %w", utils.FmtUnstructured(existingObject), err)
196+
}
197+
}
198+
175199
dryRunObject := object.DeepCopy()
176200
if err := m.dryRunApply(ctx, dryRunObject); err != nil {
177201
// We cannot have an immutable error (and therefore shouldn't force-apply) if the resource doesn't
@@ -207,11 +231,12 @@ func (m *ResourceManager) ApplyAll(ctx context.Context, objects []*unstructured.
207231
}
208232
}
209233

210-
patched, err := m.cleanupMetadata(ctx, object, existingObject, opts.Cleanup)
234+
patchedCleanupMetadata, err := m.cleanupMetadata(ctx, object, existingObject, opts.Cleanup)
211235
if err != nil {
212236
return fmt.Errorf("%s metadata.managedFields cleanup failed: %w",
213237
utils.FmtUnstructured(existingObject), err)
214238
}
239+
patched = patched || patchedCleanupMetadata
215240

216241
if patched || m.hasDrifted(existingObject, dryRunObject) {
217242
toApply[i] = object
@@ -345,6 +370,46 @@ func (m *ResourceManager) apply(ctx context.Context, object *unstructured.Unstru
345370
return m.client.Patch(ctx, object, client.Apply, opts...)
346371
}
347372

373+
// migrateAPIVersion updates the API version in managed field entries
374+
// whose API version differs from the desired API version using a raw
375+
// patch. This is important for making the API server validate the
376+
// managed fields against the schema of the desired API version on
377+
// dry-run. If this operation succeeds, existingObject will be updated
378+
// with the content returned by the API server. The function returns a
379+
// boolean indicating whether a patch was applied. All managed field
380+
// entries owned by this ResourceManager's field owner are migrated,
381+
// including entries on subresources like status — leaving any of our
382+
// own entries at an older API version can cause dry-run failures on
383+
// subsequent applies, and a stale main-resource entry can even trigger
384+
// failures on a status subresource apply (and vice versa). Entries
385+
// owned by other field managers are left untouched.
386+
func (m *ResourceManager) migrateAPIVersion(ctx context.Context,
387+
existingObject *unstructured.Unstructured,
388+
desiredAPIVersion string) (bool, error) {
389+
390+
// Build patch.
391+
patch, err := PatchMigrateToVersion(existingObject, desiredAPIVersion,
392+
WithMigrateManager(m.owner.Field))
393+
if err != nil {
394+
return false, fmt.Errorf("failed to create patch for migrating managed fields API version: %w", err)
395+
}
396+
if len(patch) == 0 {
397+
return false, nil
398+
}
399+
400+
// Apply patch.
401+
patchBytes, err := json.Marshal(patch)
402+
if err != nil {
403+
return false, fmt.Errorf("failed to marshal patch for migrating managed fields API version: %w", err)
404+
}
405+
rawPatch := client.RawPatch(types.JSONPatchType, patchBytes)
406+
if err := m.client.Patch(ctx, existingObject, rawPatch, client.FieldOwner(m.owner.Field)); err != nil {
407+
return false, fmt.Errorf("failed to migrate managed fields API version to %s: %w", desiredAPIVersion, err)
408+
}
409+
410+
return true, nil
411+
}
412+
348413
// cleanupMetadata performs an HTTP PATCH request to remove entries from metadata annotations, labels and managedFields.
349414
func (m *ResourceManager) cleanupMetadata(ctx context.Context,
350415
desiredObject *unstructured.Unstructured,

0 commit comments

Comments
 (0)