Skip to content

Commit 78d26a6

Browse files
chore(ci): Add test to check if user changes are preserved
Add a test to ensure that OLM is not reverting user changes like kubectl rollout restart. Assisted-by: Cursor/Claude
1 parent 25704ad commit 78d26a6

File tree

3 files changed

+455
-0
lines changed

3 files changed

+455
-0
lines changed

internal/operator-controller/applier/boxcutter_test.go

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1890,3 +1890,144 @@ func (s *statusWriterMock) Apply(ctx context.Context, obj runtime.ApplyConfigura
18901890
// helps ensure tests fail if this assumption changes
18911891
return fmt.Errorf("unexpected call to StatusWriter.Apply() - this method is not expected to be used in these tests")
18921892
}
1893+
1894+
func Test_SimpleRevisionGenerator_PodTemplateAnnotationSanitization(t *testing.T) {
1895+
tests := []struct {
1896+
name string
1897+
podTemplateAnnotations map[string]string
1898+
expectAnnotationsInRevision bool
1899+
}{
1900+
{
1901+
name: "deployment with non-empty pod template annotations preserves them",
1902+
podTemplateAnnotations: map[string]string{
1903+
"kubectl.kubernetes.io/default-container": "main",
1904+
"prometheus.io/scrape": "true",
1905+
},
1906+
expectAnnotationsInRevision: true,
1907+
},
1908+
{
1909+
name: "deployment with empty pod template annotations removes them",
1910+
podTemplateAnnotations: map[string]string{},
1911+
expectAnnotationsInRevision: false,
1912+
},
1913+
{
1914+
name: "deployment with nil pod template annotations has none in revision",
1915+
podTemplateAnnotations: nil,
1916+
expectAnnotationsInRevision: false,
1917+
},
1918+
}
1919+
1920+
for _, tt := range tests {
1921+
t.Run(tt.name, func(t *testing.T) {
1922+
// Create a deployment with specified pod template annotations
1923+
deployment := &appsv1.Deployment{
1924+
ObjectMeta: metav1.ObjectMeta{
1925+
Name: "test-deployment",
1926+
},
1927+
Spec: appsv1.DeploymentSpec{
1928+
Replicas: ptr.To(int32(1)),
1929+
Selector: &metav1.LabelSelector{
1930+
MatchLabels: map[string]string{"app": "test"},
1931+
},
1932+
Template: corev1.PodTemplateSpec{
1933+
ObjectMeta: metav1.ObjectMeta{
1934+
Labels: map[string]string{"app": "test"},
1935+
Annotations: tt.podTemplateAnnotations,
1936+
},
1937+
Spec: corev1.PodSpec{
1938+
Containers: []corev1.Container{
1939+
{
1940+
Name: "main",
1941+
Image: "nginx:latest",
1942+
},
1943+
},
1944+
},
1945+
},
1946+
},
1947+
}
1948+
1949+
// Create revision generator with fake manifest provider
1950+
scheme := runtime.NewScheme()
1951+
require.NoError(t, k8scheme.AddToScheme(scheme))
1952+
require.NoError(t, ocv1.AddToScheme(scheme))
1953+
1954+
ext := &ocv1.ClusterExtension{
1955+
ObjectMeta: metav1.ObjectMeta{
1956+
Name: "test-extension",
1957+
},
1958+
}
1959+
1960+
manifestProvider := &FakeManifestProvider{
1961+
GetFn: func(_ fs.FS, _ *ocv1.ClusterExtension) ([]client.Object, error) {
1962+
return []client.Object{deployment}, nil
1963+
},
1964+
}
1965+
1966+
rg := &applier.SimpleRevisionGenerator{
1967+
Scheme: scheme,
1968+
ManifestProvider: manifestProvider,
1969+
}
1970+
1971+
// Create a valid bundle FS
1972+
bundleFS := bundlefs.Builder().
1973+
WithPackageName("test-package").
1974+
WithCSV(clusterserviceversion.Builder().WithName("test-csv").Build()).
1975+
Build()
1976+
1977+
// Generate revision
1978+
revision, err := rg.GenerateRevision(
1979+
context.Background(),
1980+
bundleFS,
1981+
ext,
1982+
map[string]string{"test": "label"},
1983+
nil,
1984+
)
1985+
require.NoError(t, err)
1986+
require.NotNil(t, revision)
1987+
1988+
// Find the deployment in the revision
1989+
var deploymentFound bool
1990+
for _, obj := range revision.Spec.Phases[0].Objects {
1991+
if obj.Object.GetKind() == "Deployment" {
1992+
deploymentFound = true
1993+
1994+
// Check pod template annotations
1995+
spec, ok := obj.Object.Object["spec"].(map[string]any)
1996+
require.True(t, ok, "deployment should have spec")
1997+
1998+
template, ok := spec["template"].(map[string]any)
1999+
require.True(t, ok, "deployment spec should have template")
2000+
2001+
templateMeta, ok := template["metadata"].(map[string]any)
2002+
require.True(t, ok, "pod template should have metadata")
2003+
2004+
annotations, hasAnnotations := templateMeta["annotations"]
2005+
if tt.expectAnnotationsInRevision {
2006+
assert.True(t, hasAnnotations, "expected annotations to be present in revision")
2007+
annotationsMap, ok := annotations.(map[string]any)
2008+
require.True(t, ok, "annotations should be a map")
2009+
2010+
for key, expectedValue := range tt.podTemplateAnnotations {
2011+
actualValue, exists := annotationsMap[key]
2012+
assert.True(t, exists, "expected annotation key %s to exist", key)
2013+
assert.Equal(t, expectedValue, actualValue, "annotation value mismatch for key %s", key)
2014+
}
2015+
assert.Len(t, annotationsMap, len(tt.podTemplateAnnotations), "annotation count mismatch")
2016+
} else {
2017+
assert.False(t, hasAnnotations, "expected annotations to be removed from revision")
2018+
}
2019+
2020+
// Labels should always be preserved
2021+
labels, hasLabels := templateMeta["labels"]
2022+
assert.True(t, hasLabels, "labels should always be preserved")
2023+
labelsMap, ok := labels.(map[string]any)
2024+
require.True(t, ok, "labels should be a map")
2025+
assert.Equal(t, "test", labelsMap["app"], "label app should be preserved")
2026+
2027+
break
2028+
}
2029+
}
2030+
assert.True(t, deploymentFound, "deployment should be found in revision")
2031+
})
2032+
}
2033+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
Feature: Rollout Restart User Changes
2+
# Verifies that user-added pod template annotations persist after OLM reconciliation.
3+
# Fixes: https://github.com/operator-framework/operator-lifecycle-manager/issues/3392
4+
5+
Background:
6+
Given OLM is available
7+
And ClusterCatalog "test" serves bundles
8+
And ServiceAccount "olm-sa" with needed permissions is available in ${TEST_NAMESPACE}
9+
10+
// TODO(Fix Boxcutter): It does not work with boxcutter
11+
// but to properly fix we need SSA first. So, this one is blocked
12+
// by https://github.com/operator-framework/operator-controller/pull/2515
13+
Scenario: User-initiated deployment changes persist after OLM reconciliation
14+
When ClusterExtension is applied
15+
"""
16+
apiVersion: olm.operatorframework.io/v1
17+
kind: ClusterExtension
18+
metadata:
19+
name: ${NAME}
20+
spec:
21+
namespace: ${TEST_NAMESPACE}
22+
serviceAccount:
23+
name: olm-sa
24+
source:
25+
sourceType: Catalog
26+
catalog:
27+
packageName: test
28+
selector:
29+
matchLabels:
30+
"olm.operatorframework.io/metadata.name": test-catalog
31+
"""
32+
Then ClusterExtension is rolled out
33+
And ClusterExtension is available
34+
And resource "deployment/test-operator" is installed
35+
And deployment "test-operator" is ready
36+
When user performs rollout restart on "deployment/test-operator"
37+
Then deployment "test-operator" rollout completes successfully
38+
And resource "deployment/test-operator" has restart annotation
39+
# Wait for OLM reconciliation cycle (runs every 10 seconds)
40+
And I wait for "30" seconds
41+
# Verify user changes persisted after OLM reconciliation
42+
Then deployment "test-operator" rollout is still successful
43+
And resource "deployment/test-operator" has restart annotation
44+
And deployment "test-operator" has expected number of ready replicas

0 commit comments

Comments
 (0)