Skip to content

Commit a03da8d

Browse files
committed
ssa: address review feedback on API version migration
- Remove API version migration from Diff() - Diff should not alter objects - Refactor migrateAPIVersion to not require getError parameter - Check object existence conditionally before calling migrateAPIVersion - Remove inadequate TestApply_APIVersionMigration test Fixes: fluxcd/flux2#5715 Signed-off-by: mehrdadbn9 <mehrdadbiukian@gmail.com>
1 parent f48460a commit a03da8d

4 files changed

Lines changed: 45 additions & 95 deletions

File tree

ssa/manager_apply.go

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -108,9 +108,11 @@ func (m *ResourceManager) Apply(ctx context.Context, object *unstructured.Unstru
108108
return m.changeSetEntry(object, SkippedAction), nil
109109
}
110110

111-
// Migrate managed fields API version if needed before dry-run
112-
if err := m.migrateAPIVersion(ctx, object, existingObject, getError); err != nil {
113-
return nil, fmt.Errorf("%s failed to migrate API version: %w", utils.FmtUnstructured(object), err)
111+
// Migrate managed fields API version if the object exists and has a different API version
112+
if getError == nil && existingObject.GetUID() != "" {
113+
if err := m.migrateAPIVersion(ctx, existingObject, object.GetAPIVersion()); err != nil {
114+
return nil, fmt.Errorf("%s failed to migrate API version: %w", utils.FmtUnstructured(object), err)
115+
}
114116
}
115117

116118
dryRunObject := object.DeepCopy()
@@ -177,9 +179,11 @@ func (m *ResourceManager) ApplyAll(ctx context.Context, objects []*unstructured.
177179
return nil
178180
}
179181

180-
// Migrate managed fields API version if needed before dry-run
181-
if err := m.migrateAPIVersion(ctx, object, existingObject, getError); err != nil {
182-
return fmt.Errorf("%s failed to migrate API version: %w", utils.FmtUnstructured(object), err)
182+
// Migrate managed fields API version if the object exists and has a different API version
183+
if getError == nil && existingObject.GetUID() != "" {
184+
if err := m.migrateAPIVersion(ctx, existingObject, object.GetAPIVersion()); err != nil {
185+
return fmt.Errorf("%s failed to migrate API version: %w", utils.FmtUnstructured(object), err)
186+
}
183187
}
184188

185189
dryRunObject := object.DeepCopy()
@@ -355,18 +359,12 @@ func (m *ResourceManager) apply(ctx context.Context, object *unstructured.Unstru
355359
return m.client.Patch(ctx, object, client.Apply, opts...)
356360
}
357361

358-
// migrateAPIVersion updates the managed fields API version when the desired object
359-
// has a different API version than the existing object. This is necessary because
362+
// migrateAPIVersion updates the managed fields API version when the existing object
363+
// has a different API version than the desired API version. This is necessary because
360364
// Kubernetes server-side apply validates managed fields against the schema of the
361365
// API version they reference, and when upgrading CRD versions, the old API version
362366
// may have fields that don't exist in the new schema, causing dry-run to fail.
363-
func (m *ResourceManager) migrateAPIVersion(ctx context.Context, object *unstructured.Unstructured, existingObject *unstructured.Unstructured, getError error) error {
364-
// Skip if object doesn't exist or there's an error getting it
365-
if getError != nil || existingObject == nil || existingObject.GetUID() == "" {
366-
return nil
367-
}
368-
369-
desiredAPIVersion := object.GetAPIVersion()
367+
func (m *ResourceManager) migrateAPIVersion(ctx context.Context, existingObject *unstructured.Unstructured, desiredAPIVersion string) error {
370368
existingAPIVersion := existingObject.GetAPIVersion()
371369

372370
// Skip if API versions are the same

ssa/manager_apply_test.go

Lines changed: 0 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1661,80 +1661,6 @@ func TestApplyAllStaged_AppliesRoleAndRoleBinding(t *testing.T) {
16611661
})
16621662
}
16631663

1664-
func TestApply_APIVersionMigration(t *testing.T) {
1665-
timeout := 10 * time.Second
1666-
ctx, cancel := context.WithTimeout(context.Background(), timeout)
1667-
defer cancel()
1668-
1669-
id := generateName("api-version-migrate")
1670-
objects, err := readManifest("testdata/test1.yaml", id)
1671-
if err != nil {
1672-
t.Fatal(err)
1673-
}
1674-
1675-
manager.SetOwnerLabels(objects, "app1", "default")
1676-
1677-
// Get the configmap object
1678-
_, configMap := getFirstObject(objects, "ConfigMap", id)
1679-
1680-
t.Run("creates object with initial API version", func(t *testing.T) {
1681-
changeSet, err := manager.ApplyAllStaged(ctx, objects, DefaultApplyOptions())
1682-
if err != nil {
1683-
t.Fatal(err)
1684-
}
1685-
1686-
for _, entry := range changeSet.Entries {
1687-
if entry.Action != CreatedAction {
1688-
t.Errorf("Expected %s, got %s for %s", CreatedAction, entry.Action, entry.Subject)
1689-
}
1690-
}
1691-
})
1692-
1693-
t.Run("patches managed fields with new API version", func(t *testing.T) {
1694-
// Simulate an API version migration by updating the managed fields
1695-
// In a real scenario, this would happen when a CRD version is deprecated
1696-
// and the user updates their manifests to use the new version
1697-
1698-
// Get the existing object
1699-
existingConfigMap := configMap.DeepCopy()
1700-
err := manager.client.Get(ctx, client.ObjectKeyFromObject(existingConfigMap), existingConfigMap)
1701-
if err != nil {
1702-
t.Fatal(err)
1703-
}
1704-
1705-
// Verify the object was created with proper managed fields
1706-
managedFields := existingConfigMap.GetManagedFields()
1707-
if len(managedFields) == 0 {
1708-
t.Fatal("Expected managed fields to be present")
1709-
}
1710-
1711-
// Verify that the manager's field owner is in managed fields
1712-
foundManager := false
1713-
for _, field := range managedFields {
1714-
if field.Manager == manager.owner.Field {
1715-
foundManager = true
1716-
break
1717-
}
1718-
}
1719-
if !foundManager {
1720-
t.Errorf("Expected to find manager %s in managed fields", manager.owner.Field)
1721-
}
1722-
})
1723-
1724-
t.Run("re-applies object without changes", func(t *testing.T) {
1725-
changeSet, err := manager.ApplyAllStaged(ctx, objects, DefaultApplyOptions())
1726-
if err != nil {
1727-
t.Fatal(err)
1728-
}
1729-
1730-
for _, entry := range changeSet.Entries {
1731-
if entry.Action != UnchangedAction {
1732-
t.Errorf("Expected %s, got %s for %s", UnchangedAction, entry.Action, entry.Subject)
1733-
}
1734-
}
1735-
})
1736-
}
1737-
17381664
func TestPatchMigrateToVersion(t *testing.T) {
17391665
tests := []struct {
17401666
name string

ssa/manager_diff.go

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -65,17 +65,12 @@ func (m *ResourceManager) Diff(ctx context.Context, object *unstructured.Unstruc
6565
utils.RemoveCABundleFromCRD(object)
6666
existingObject := &unstructured.Unstructured{}
6767
existingObject.SetGroupVersionKind(object.GroupVersionKind())
68-
getError := m.client.Get(ctx, client.ObjectKeyFromObject(object), existingObject)
68+
_ = m.client.Get(ctx, client.ObjectKeyFromObject(object), existingObject)
6969

7070
if m.shouldSkipDiff(object, existingObject, opts) {
7171
return m.changeSetEntry(existingObject, SkippedAction), nil, nil, nil
7272
}
7373

74-
// Migrate managed fields API version if needed before dry-run
75-
if err := m.migrateAPIVersion(ctx, object, existingObject, getError); err != nil {
76-
return nil, nil, nil, err
77-
}
78-
7974
dryRunObject := object.DeepCopy()
8075
if err := m.dryRunApply(ctx, dryRunObject); err != nil {
8176
if m.shouldForceApply(object, existingObject, ApplyOptions{

ssa/testdata/test15-crd.yaml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
---
2+
apiVersion: v1
3+
kind: Namespace
4+
metadata:
5+
name: "%[1]s"
6+
---
7+
apiVersion: apiextensions.k8s.io/v1
8+
kind: CustomResourceDefinition
9+
metadata:
10+
name: "tests.apimigration.example.com"
11+
spec:
12+
group: apimigration.example.com
13+
names:
14+
kind: Test
15+
listKind: TestList
16+
plural: tests
17+
singular: test
18+
scope: Namespaced
19+
versions:
20+
- name: v1alpha1
21+
served: true
22+
storage: true
23+
schema:
24+
openAPIV3Schema:
25+
type: object
26+
properties:
27+
spec:
28+
type: object
29+
properties:
30+
value:
31+
type: string

0 commit comments

Comments
 (0)