Skip to content

Commit 0bdcc70

Browse files
authored
Merge branch 'master' into 1.19.0-sync-ginkgo-tests-from-argocd-operator-
2 parents 5ae5e3b + eaaf361 commit 0bdcc70

File tree

8 files changed

+591
-15
lines changed

8 files changed

+591
-15
lines changed

OWNERS

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ approvers:
99
- anandf
1010
- varshab1210
1111
- svghadi
12+
- adamsaleh
13+
- trdoyle81
1214

1315
reviewers:
1416
- wtam2018
@@ -17,7 +19,9 @@ reviewers:
1719
- jannfis
1820
- jgwest
1921
- anandrkskd
20-
- varshab1210
2122
- trdoyle81
2223
- svghadi
2324
- varshab1210
25+
- adamsaleh
26+
- trdoyle81
27+
- Naveena-058

controllers/consoleplugin.go

Lines changed: 83 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"os"
77
"reflect"
8+
"sort"
89

910
argocommon "github.com/argoproj-labs/argocd-operator/common"
1011
argocdutil "github.com/argoproj-labs/argocd-operator/controllers/argoutil"
@@ -16,6 +17,7 @@ import (
1617
corev1 "k8s.io/api/core/v1"
1718

1819
"github.com/redhat-developer/gitops-operator/common"
20+
"k8s.io/apimachinery/pkg/api/equality"
1921
"k8s.io/apimachinery/pkg/api/errors"
2022
resourcev1 "k8s.io/apimachinery/pkg/api/resource"
2123
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -267,6 +269,74 @@ func pluginConfigMap() *corev1.ConfigMap {
267269
return cm
268270
}
269271

272+
// normalizeContainerDefaults sets Kubernetes default values for container fields that are
273+
// automatically populated by the API server. This ensures consistent comparison between
274+
// existing containers (from etcd) and new containers (from operator).
275+
func normalizeContainerDefaults(container *corev1.Container) {
276+
if container.TerminationMessagePath == "" {
277+
container.TerminationMessagePath = "/dev/termination-log"
278+
}
279+
if container.TerminationMessagePolicy == "" {
280+
container.TerminationMessagePolicy = corev1.TerminationMessageReadFile
281+
}
282+
}
283+
284+
// sortContainers creates a sorted copy of containers by name, and nested fields
285+
// (Env, Ports, VolumeMounts) to handle non-deterministic ordering from etcd
286+
func sortContainers(containers []corev1.Container) []corev1.Container {
287+
if len(containers) == 0 {
288+
return containers
289+
}
290+
sorted := make([]corev1.Container, len(containers))
291+
for i := range containers {
292+
sorted[i] = *containers[i].DeepCopy()
293+
normalizeContainerDefaults(&sorted[i])
294+
sort.Slice(sorted[i].Env, func(a, b int) bool {
295+
return sorted[i].Env[a].Name < sorted[i].Env[b].Name
296+
})
297+
sort.Slice(sorted[i].Ports, func(a, b int) bool {
298+
if sorted[i].Ports[a].ContainerPort != sorted[i].Ports[b].ContainerPort {
299+
return sorted[i].Ports[a].ContainerPort < sorted[i].Ports[b].ContainerPort
300+
}
301+
return sorted[i].Ports[a].Name < sorted[i].Ports[b].Name
302+
})
303+
sort.Slice(sorted[i].VolumeMounts, func(a, b int) bool {
304+
return sorted[i].VolumeMounts[a].Name < sorted[i].VolumeMounts[b].Name
305+
})
306+
}
307+
sort.Slice(sorted, func(i, j int) bool {
308+
return sorted[i].Name < sorted[j].Name
309+
})
310+
return sorted
311+
}
312+
313+
// sortVolumes creates a sorted copy of volumes by name
314+
func sortVolumes(volumes []corev1.Volume) []corev1.Volume {
315+
sorted := make([]corev1.Volume, len(volumes))
316+
copy(sorted, volumes)
317+
sort.Slice(sorted, func(i, j int) bool {
318+
return sorted[i].Name < sorted[j].Name
319+
})
320+
return sorted
321+
}
322+
323+
// sortTolerations creates a sorted copy of tolerations by key, operator, and effect
324+
func sortTolerations(tolerations []corev1.Toleration) []corev1.Toleration {
325+
sorted := make([]corev1.Toleration, len(tolerations))
326+
copy(sorted, tolerations)
327+
sort.Slice(sorted, func(i, j int) bool {
328+
a, b := sorted[i], sorted[j]
329+
if a.Key != b.Key {
330+
return a.Key < b.Key
331+
}
332+
if a.Operator != b.Operator {
333+
return string(a.Operator) < string(b.Operator)
334+
}
335+
return string(a.Effect) < string(b.Effect)
336+
})
337+
return sorted
338+
}
339+
270340
func (r *ReconcileGitopsService) reconcileDeployment(cr *pipelinesv1alpha1.GitopsService, request reconcile.Request) (reconcile.Result, error) {
271341
reqLogger := logs.WithValues("Request.Namespace", request.Namespace, "Request.Name", request.Name)
272342
newPluginDeployment := pluginDeployment(cr.Spec.ImagePullPolicy)
@@ -309,17 +379,19 @@ func (r *ReconcileGitopsService) reconcileDeployment(cr *pipelinesv1alpha1.Gitop
309379
} else {
310380
existingSpecTemplate := &existingPluginDeployment.Spec.Template
311381
newSpecTemplate := newPluginDeployment.Spec.Template
312-
changed := !reflect.DeepEqual(existingPluginDeployment.Labels, newPluginDeployment.Labels) ||
313-
!reflect.DeepEqual(existingPluginDeployment.Spec.Replicas, newPluginDeployment.Spec.Replicas) ||
314-
!reflect.DeepEqual(existingPluginDeployment.Spec.Selector, newPluginDeployment.Spec.Selector) ||
315-
!reflect.DeepEqual(existingSpecTemplate.Labels, newSpecTemplate.Labels) ||
316-
!reflect.DeepEqual(existingSpecTemplate.Spec.Containers, newSpecTemplate.Spec.Containers) ||
317-
!reflect.DeepEqual(existingSpecTemplate.Spec.Volumes, newSpecTemplate.Spec.Volumes) ||
318-
!reflect.DeepEqual(existingSpecTemplate.Spec.RestartPolicy, newSpecTemplate.Spec.RestartPolicy) ||
319-
!reflect.DeepEqual(existingSpecTemplate.Spec.DNSPolicy, newSpecTemplate.Spec.DNSPolicy) ||
320-
!reflect.DeepEqual(existingPluginDeployment.Spec.Template.Spec.NodeSelector, newPluginDeployment.Spec.Template.Spec.NodeSelector) ||
321-
!reflect.DeepEqual(existingPluginDeployment.Spec.Template.Spec.Tolerations, newPluginDeployment.Spec.Template.Spec.Tolerations) ||
322-
!reflect.DeepEqual(existingSpecTemplate.Spec.SecurityContext, existingSpecTemplate.Spec.SecurityContext) || !reflect.DeepEqual(existingSpecTemplate.Spec.Containers[0].Resources, newSpecTemplate.Spec.Containers[0].Resources)
382+
// Sort list fields before comparing to handle non-deterministic ordering
383+
changed := !equality.Semantic.DeepEqual(existingPluginDeployment.Labels, newPluginDeployment.Labels) ||
384+
!equality.Semantic.DeepEqual(existingPluginDeployment.Spec.Replicas, newPluginDeployment.Spec.Replicas) ||
385+
!equality.Semantic.DeepEqual(existingPluginDeployment.Spec.Selector, newPluginDeployment.Spec.Selector) ||
386+
!equality.Semantic.DeepEqual(existingSpecTemplate.Labels, newSpecTemplate.Labels) ||
387+
!equality.Semantic.DeepEqual(sortContainers(existingSpecTemplate.Spec.Containers), sortContainers(newSpecTemplate.Spec.Containers)) ||
388+
!equality.Semantic.DeepEqual(sortVolumes(existingSpecTemplate.Spec.Volumes), sortVolumes(newSpecTemplate.Spec.Volumes)) ||
389+
!equality.Semantic.DeepEqual(existingSpecTemplate.Spec.RestartPolicy, newSpecTemplate.Spec.RestartPolicy) ||
390+
!equality.Semantic.DeepEqual(existingSpecTemplate.Spec.DNSPolicy, newSpecTemplate.Spec.DNSPolicy) ||
391+
!equality.Semantic.DeepEqual(existingPluginDeployment.Spec.Template.Spec.NodeSelector, newPluginDeployment.Spec.Template.Spec.NodeSelector) ||
392+
!equality.Semantic.DeepEqual(sortTolerations(existingPluginDeployment.Spec.Template.Spec.Tolerations), sortTolerations(newPluginDeployment.Spec.Template.Spec.Tolerations)) ||
393+
!equality.Semantic.DeepEqual(existingSpecTemplate.Spec.SecurityContext, newSpecTemplate.Spec.SecurityContext) ||
394+
!equality.Semantic.DeepEqual(existingSpecTemplate.Spec.Containers[0].Resources, newSpecTemplate.Spec.Containers[0].Resources)
323395

324396
if changed {
325397
reqLogger.Info("Reconciling plugin deployment", "Namespace", existingPluginDeployment.Namespace, "Name", existingPluginDeployment.Name)

controllers/consoleplugin_test.go

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1656,3 +1656,207 @@ func TestPlug_reconcileConfigMap_changedLabels(t *testing.T) {
16561656
})
16571657
}
16581658
}
1659+
1660+
// Tests that reconciliation does NOT trigger an update when containers are returned in different order from etcd.
1661+
func TestReconcileDeployment_NoUpdateWhenContainersOrderDiffers(t *testing.T) {
1662+
s := scheme.Scheme
1663+
addKnownTypesToScheme(s)
1664+
1665+
fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(newGitopsService()).Build()
1666+
reconciler := newReconcileGitOpsService(fakeClient, s)
1667+
instance := &pipelinesv1alpha1.GitopsService{}
1668+
1669+
// Create deployment
1670+
_, err := reconciler.reconcileDeployment(instance, newRequest(serviceNamespace, gitopsPluginName))
1671+
assertNoError(t, err)
1672+
1673+
// Get the deployment and capture initial ResourceVersion and Generation
1674+
deployment := &appsv1.Deployment{}
1675+
err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: gitopsPluginName, Namespace: serviceNamespace}, deployment)
1676+
assertNoError(t, err)
1677+
initialRV := deployment.ResourceVersion
1678+
initialGen := deployment.Generation
1679+
1680+
// Simulate etcd returning containers in different order
1681+
if len(deployment.Spec.Template.Spec.Containers) >= 2 {
1682+
containers := deployment.Spec.Template.Spec.Containers
1683+
reversedContainers := make([]corev1.Container, len(containers))
1684+
for i := range containers {
1685+
reversedContainers[len(containers)-1-i] = containers[i]
1686+
}
1687+
deployment.Spec.Template.Spec.Containers = reversedContainers
1688+
err = fakeClient.Update(context.TODO(), deployment)
1689+
assertNoError(t, err)
1690+
}
1691+
1692+
// Reconcile again - should NOT trigger an update
1693+
_, err = reconciler.reconcileDeployment(instance, newRequest(serviceNamespace, gitopsPluginName))
1694+
assertNoError(t, err)
1695+
1696+
// Verify no update was triggered
1697+
err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: gitopsPluginName, Namespace: serviceNamespace}, deployment)
1698+
assertNoError(t, err)
1699+
1700+
assert.Equal(t, deployment.ResourceVersion, initialRV,
1701+
"ResourceVersion should NOT change when only container order differs")
1702+
assert.Equal(t, deployment.Generation, initialGen,
1703+
"Generation should NOT change when only container order differs")
1704+
}
1705+
1706+
// Tests that reconciliation does NOT trigger an update when volumes are returned in different order from etcd.
1707+
func TestReconcileDeployment_NoUpdateWhenVolumesOrderDiffers(t *testing.T) {
1708+
s := scheme.Scheme
1709+
addKnownTypesToScheme(s)
1710+
1711+
fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(newGitopsService()).Build()
1712+
reconciler := newReconcileGitOpsService(fakeClient, s)
1713+
instance := &pipelinesv1alpha1.GitopsService{}
1714+
1715+
// Create deployment
1716+
_, err := reconciler.reconcileDeployment(instance, newRequest(serviceNamespace, gitopsPluginName))
1717+
assertNoError(t, err)
1718+
1719+
// Get the deployment
1720+
deployment := &appsv1.Deployment{}
1721+
err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: gitopsPluginName, Namespace: serviceNamespace}, deployment)
1722+
assertNoError(t, err)
1723+
1724+
// Simulate etcd returning volumes in different order
1725+
if len(deployment.Spec.Template.Spec.Volumes) >= 2 {
1726+
volumes := deployment.Spec.Template.Spec.Volumes
1727+
reversedVolumes := make([]corev1.Volume, len(volumes))
1728+
for i := range volumes {
1729+
reversedVolumes[len(volumes)-1-i] = volumes[i]
1730+
}
1731+
deployment.Spec.Template.Spec.Volumes = reversedVolumes
1732+
err = fakeClient.Update(context.TODO(), deployment)
1733+
assertNoError(t, err)
1734+
1735+
err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: gitopsPluginName, Namespace: serviceNamespace}, deployment)
1736+
assertNoError(t, err)
1737+
rvAfterManualUpdate := deployment.ResourceVersion
1738+
genAfterManualUpdate := deployment.Generation
1739+
1740+
// Reconcile again - should NOT trigger an update
1741+
_, err = reconciler.reconcileDeployment(instance, newRequest(serviceNamespace, gitopsPluginName))
1742+
assertNoError(t, err)
1743+
1744+
// Verify no update was triggered
1745+
err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: gitopsPluginName, Namespace: serviceNamespace}, deployment)
1746+
assertNoError(t, err)
1747+
1748+
assert.Equal(t, deployment.ResourceVersion, rvAfterManualUpdate,
1749+
"ResourceVersion should NOT change when only volume order differs")
1750+
assert.Equal(t, deployment.Generation, genAfterManualUpdate,
1751+
"Generation should NOT change when only volume order differs")
1752+
} else {
1753+
t.Skip("Skipping test: deployment has less than 2 volumes, cannot test order difference")
1754+
}
1755+
}
1756+
1757+
// Tests that reconciliation does NOT trigger an update when tolerations are returned in different order from etcd.
1758+
func TestReconcileDeployment_NoUpdateWhenTolerationsOrderDiffers(t *testing.T) {
1759+
s := scheme.Scheme
1760+
addKnownTypesToScheme(s)
1761+
1762+
// Create GitopsService with tolerations
1763+
gitopsService := &pipelinesv1alpha1.GitopsService{
1764+
ObjectMeta: metav1.ObjectMeta{
1765+
Name: serviceName,
1766+
},
1767+
Spec: pipelinesv1alpha1.GitopsServiceSpec{
1768+
Tolerations: []corev1.Toleration{
1769+
{
1770+
Key: "key-a",
1771+
Operator: corev1.TolerationOpEqual,
1772+
Effect: corev1.TaintEffectNoSchedule,
1773+
},
1774+
{
1775+
Key: "key-b",
1776+
Operator: corev1.TolerationOpEqual,
1777+
Effect: corev1.TaintEffectNoSchedule,
1778+
},
1779+
},
1780+
},
1781+
}
1782+
1783+
fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(gitopsService).Build()
1784+
reconciler := newReconcileGitOpsService(fakeClient, s)
1785+
1786+
// Create deployment
1787+
_, err := reconciler.reconcileDeployment(gitopsService, newRequest(serviceNamespace, gitopsPluginName))
1788+
assertNoError(t, err)
1789+
1790+
// Get the deployment
1791+
deployment := &appsv1.Deployment{}
1792+
err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: gitopsPluginName, Namespace: serviceNamespace}, deployment)
1793+
assertNoError(t, err)
1794+
1795+
// Simulate etcd returning tolerations in different order
1796+
if len(deployment.Spec.Template.Spec.Tolerations) >= 2 {
1797+
tolerations := deployment.Spec.Template.Spec.Tolerations
1798+
reversedTolerations := make([]corev1.Toleration, len(tolerations))
1799+
for i := range tolerations {
1800+
reversedTolerations[len(tolerations)-1-i] = tolerations[i]
1801+
}
1802+
deployment.Spec.Template.Spec.Tolerations = reversedTolerations
1803+
err = fakeClient.Update(context.TODO(), deployment)
1804+
assertNoError(t, err)
1805+
1806+
err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: gitopsPluginName, Namespace: serviceNamespace}, deployment)
1807+
assertNoError(t, err)
1808+
rvAfterManualUpdate := deployment.ResourceVersion
1809+
genAfterManualUpdate := deployment.Generation
1810+
1811+
// Reconcile again - should NOT trigger an update
1812+
_, err = reconciler.reconcileDeployment(gitopsService, newRequest(serviceNamespace, gitopsPluginName))
1813+
assertNoError(t, err)
1814+
1815+
// Verify no update was triggered
1816+
err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: gitopsPluginName, Namespace: serviceNamespace}, deployment)
1817+
assertNoError(t, err)
1818+
1819+
assert.Equal(t, deployment.ResourceVersion, rvAfterManualUpdate,
1820+
"ResourceVersion should NOT change when only toleration order differs")
1821+
assert.Equal(t, deployment.Generation, genAfterManualUpdate,
1822+
"Generation should NOT change when only toleration order differs")
1823+
} else {
1824+
t.Skip("Skipping test: deployment has less than 2 tolerations, cannot test order difference")
1825+
}
1826+
}
1827+
1828+
// Tests that legitimate changes do trigger updates.
1829+
func TestReconcileDeployment_UpdateWhenActualChange(t *testing.T) {
1830+
s := scheme.Scheme
1831+
addKnownTypesToScheme(s)
1832+
1833+
fakeClient := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(newGitopsService()).Build()
1834+
reconciler := newReconcileGitOpsService(fakeClient, s)
1835+
instance := &pipelinesv1alpha1.GitopsService{}
1836+
1837+
// Create deployment
1838+
_, err := reconciler.reconcileDeployment(instance, newRequest(serviceNamespace, gitopsPluginName))
1839+
assertNoError(t, err)
1840+
1841+
// Get the deployment and capture initial ResourceVersion and Generation
1842+
deployment := &appsv1.Deployment{}
1843+
err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: gitopsPluginName, Namespace: serviceNamespace}, deployment)
1844+
assertNoError(t, err)
1845+
initialRV := deployment.ResourceVersion
1846+
1847+
// Make an actual change
1848+
deployment.Spec.Template.Spec.Containers[0].Image = "different-image:tag"
1849+
err = fakeClient.Update(context.TODO(), deployment)
1850+
assertNoError(t, err)
1851+
1852+
// Reconcile again - should trigger an update
1853+
_, err = reconciler.reconcileDeployment(instance, newRequest(serviceNamespace, gitopsPluginName))
1854+
assertNoError(t, err)
1855+
1856+
// Verify update was triggered
1857+
err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: gitopsPluginName, Namespace: serviceNamespace}, deployment)
1858+
assertNoError(t, err)
1859+
1860+
assert.Assert(t, deployment.ResourceVersion != initialRV,
1861+
"ResourceVersion SHOULD change when actual change is made")
1862+
}

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ require (
7171
github.com/go-errors/errors v1.5.1 // indirect
7272
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
7373
github.com/go-git/go-billy/v5 v5.6.2 // indirect
74-
github.com/go-git/go-git/v5 v5.16.2 // indirect
74+
github.com/go-git/go-git/v5 v5.16.5 // indirect
7575
github.com/go-logr/zapr v1.3.0 // indirect
7676
github.com/go-openapi/jsonpointer v0.21.1 // indirect
7777
github.com/go-openapi/jsonreference v0.21.0 // indirect

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,8 +124,8 @@ github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UN
124124
github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU=
125125
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
126126
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
127-
github.com/go-git/go-git/v5 v5.16.2 h1:fT6ZIOjE5iEnkzKyxTHK1W4HGAsPhqEqiSAssSO77hM=
128-
github.com/go-git/go-git/v5 v5.16.2/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8=
127+
github.com/go-git/go-git/v5 v5.16.5 h1:mdkuqblwr57kVfXri5TTH+nMFLNUxIj9Z7F5ykFbw5s=
128+
github.com/go-git/go-git/v5 v5.16.5/go.mod h1:QOMLpNf1qxuSY4StA/ArOdfFR2TrKEjJiye2kel2m+M=
129129
github.com/go-jose/go-jose/v4 v4.1.2 h1:TK/7NqRQZfgAh+Td8AlsrvtPoUyiHh0LqVvokh+1vHI=
130130
github.com/go-jose/go-jose/v4 v4.1.2/go.mod h1:22cg9HWM1pOlnRiY+9cQYJ9XHmya1bYW8OeDM6Ku6Oo=
131131
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=

openshift-ci/build-root/OWNERS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ approvers:
66
- anandf
77
- varshab1210
88
- adamsaleh
9+
- trdoyle81
10+
- Naveena-058
911

1012
reviewers:
1113
- wtam2018
@@ -14,3 +16,5 @@ reviewers:
1416
- anandf
1517
- varshab1210
1618
- adamsaleh
19+
- trdoyle81
20+
- Naveena-058

0 commit comments

Comments
 (0)