mirror of
https://github.com/argoproj/argo-cd.git
synced 2026-04-02 06:48:48 +02:00
Compare commits
7 Commits
v3.4.0-rc3
...
release-3.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a244f7cb7a | ||
|
|
f4e7a6e604 | ||
|
|
dfa079b5e3 | ||
|
|
8550f60a05 | ||
|
|
d29ec76295 | ||
|
|
249b91d75b | ||
|
|
ed4c63ba83 |
@@ -1851,7 +1851,7 @@ func (ctrl *ApplicationController) processAppRefreshQueueItem() (processNext boo
|
||||
logCtx = logCtx.WithField(k, v.Milliseconds())
|
||||
}
|
||||
|
||||
ctrl.normalizeApplication(origApp, app)
|
||||
ctrl.normalizeApplication(app)
|
||||
ts.AddCheckpoint("normalize_application_ms")
|
||||
|
||||
tree, err := ctrl.setAppManagedResources(destCluster, app, compareResult)
|
||||
@@ -2090,7 +2090,8 @@ func (ctrl *ApplicationController) refreshAppConditions(app *appv1.Application)
|
||||
}
|
||||
|
||||
// normalizeApplication normalizes an application.spec and additionally persists updates if it changed
|
||||
func (ctrl *ApplicationController) normalizeApplication(orig, app *appv1.Application) {
|
||||
func (ctrl *ApplicationController) normalizeApplication(app *appv1.Application) {
|
||||
orig := app.DeepCopy()
|
||||
app.Spec = *argo.NormalizeApplicationSpec(&app.Spec)
|
||||
logCtx := log.WithFields(applog.GetAppLogFields(app))
|
||||
|
||||
|
||||
110
gitops-engine/pkg/cache/cluster.go
vendored
110
gitops-engine/pkg/cache/cluster.go
vendored
@@ -220,7 +220,7 @@ func NewClusterCache(config *rest.Config, opts ...UpdateSettingsFunc) *clusterCa
|
||||
listRetryLimit: 1,
|
||||
listRetryUseBackoff: false,
|
||||
listRetryFunc: ListRetryFuncNever,
|
||||
parentUIDToChildren: make(map[types.UID][]kube.ResourceKey),
|
||||
parentUIDToChildren: make(map[types.UID]map[kube.ResourceKey]struct{}),
|
||||
}
|
||||
for i := range opts {
|
||||
opts[i](cache)
|
||||
@@ -280,10 +280,11 @@ type clusterCache struct {
|
||||
|
||||
respectRBAC int
|
||||
|
||||
// Parent-to-children index for O(1) hierarchy traversal
|
||||
// Maps any resource's UID to its direct children's ResourceKeys
|
||||
// Eliminates need for O(n) graph building during hierarchy traversal
|
||||
parentUIDToChildren map[types.UID][]kube.ResourceKey
|
||||
// Parent-to-children index for O(1) child lookup during hierarchy traversal
|
||||
// Maps any resource's UID to a set of its direct children's ResourceKeys
|
||||
// Using a set eliminates O(k) duplicate checking on insertions
|
||||
// Used for cross-namespace hierarchy traversal; namespaced traversal still builds a graph
|
||||
parentUIDToChildren map[types.UID]map[kube.ResourceKey]struct{}
|
||||
}
|
||||
|
||||
type clusterCacheSync struct {
|
||||
@@ -504,27 +505,35 @@ func (c *clusterCache) setNode(n *Resource) {
|
||||
for k, v := range ns {
|
||||
// update child resource owner references
|
||||
if n.isInferredParentOf != nil && mightHaveInferredOwner(v) {
|
||||
v.setOwnerRef(n.toOwnerRef(), n.isInferredParentOf(k))
|
||||
shouldBeParent := n.isInferredParentOf(k)
|
||||
v.setOwnerRef(n.toOwnerRef(), shouldBeParent)
|
||||
// Update index inline for inferred ref changes.
|
||||
// Note: The removal case (shouldBeParent=false) is currently unreachable for
|
||||
// StatefulSet→PVC relationships because Kubernetes makes volumeClaimTemplates
|
||||
// immutable. We include it for defensive correctness and future-proofing.
|
||||
if n.Ref.UID != "" {
|
||||
if shouldBeParent {
|
||||
c.addToParentUIDToChildren(n.Ref.UID, k)
|
||||
} else {
|
||||
c.removeFromParentUIDToChildren(n.Ref.UID, k)
|
||||
}
|
||||
}
|
||||
}
|
||||
if mightHaveInferredOwner(n) && v.isInferredParentOf != nil {
|
||||
n.setOwnerRef(v.toOwnerRef(), v.isInferredParentOf(n.ResourceKey()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// rebuildParentToChildrenIndex rebuilds the parent-to-children index after a full sync
|
||||
// This is called after initial sync to ensure all parent-child relationships are tracked
|
||||
func (c *clusterCache) rebuildParentToChildrenIndex() {
|
||||
// Clear existing index
|
||||
c.parentUIDToChildren = make(map[types.UID][]kube.ResourceKey)
|
||||
|
||||
// Rebuild parent-to-children index from all resources with owner refs
|
||||
for _, resource := range c.resources {
|
||||
key := resource.ResourceKey()
|
||||
for _, ownerRef := range resource.OwnerRefs {
|
||||
if ownerRef.UID != "" {
|
||||
c.addToParentUIDToChildren(ownerRef.UID, key)
|
||||
childKey := n.ResourceKey()
|
||||
shouldBeParent := v.isInferredParentOf(childKey)
|
||||
n.setOwnerRef(v.toOwnerRef(), shouldBeParent)
|
||||
// Update index inline for inferred ref changes.
|
||||
// Note: The removal case (shouldBeParent=false) is currently unreachable for
|
||||
// StatefulSet→PVC relationships because Kubernetes makes volumeClaimTemplates
|
||||
// immutable. We include it for defensive correctness and future-proofing.
|
||||
if v.Ref.UID != "" {
|
||||
if shouldBeParent {
|
||||
c.addToParentUIDToChildren(v.Ref.UID, childKey)
|
||||
} else {
|
||||
c.removeFromParentUIDToChildren(v.Ref.UID, childKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -533,31 +542,29 @@ func (c *clusterCache) rebuildParentToChildrenIndex() {
|
||||
|
||||
// addToParentUIDToChildren adds a child to the parent-to-children index
|
||||
func (c *clusterCache) addToParentUIDToChildren(parentUID types.UID, childKey kube.ResourceKey) {
|
||||
// Check if child is already in the list to avoid duplicates
|
||||
children := c.parentUIDToChildren[parentUID]
|
||||
for _, existing := range children {
|
||||
if existing == childKey {
|
||||
return // Already exists, no need to add
|
||||
}
|
||||
// Get or create the set for this parent
|
||||
childrenSet := c.parentUIDToChildren[parentUID]
|
||||
if childrenSet == nil {
|
||||
childrenSet = make(map[kube.ResourceKey]struct{})
|
||||
c.parentUIDToChildren[parentUID] = childrenSet
|
||||
}
|
||||
c.parentUIDToChildren[parentUID] = append(children, childKey)
|
||||
// Add child to set (O(1) operation, automatically handles duplicates)
|
||||
childrenSet[childKey] = struct{}{}
|
||||
}
|
||||
|
||||
// removeFromParentUIDToChildren removes a child from the parent-to-children index
|
||||
func (c *clusterCache) removeFromParentUIDToChildren(parentUID types.UID, childKey kube.ResourceKey) {
|
||||
children := c.parentUIDToChildren[parentUID]
|
||||
for i, existing := range children {
|
||||
if existing == childKey {
|
||||
// Remove by swapping with last element and truncating
|
||||
children[i] = children[len(children)-1]
|
||||
c.parentUIDToChildren[parentUID] = children[:len(children)-1]
|
||||
childrenSet := c.parentUIDToChildren[parentUID]
|
||||
if childrenSet == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Clean up empty entries
|
||||
if len(c.parentUIDToChildren[parentUID]) == 0 {
|
||||
delete(c.parentUIDToChildren, parentUID)
|
||||
}
|
||||
return
|
||||
}
|
||||
// Remove child from set (O(1) operation)
|
||||
delete(childrenSet, childKey)
|
||||
|
||||
// Clean up empty sets to avoid memory leaks
|
||||
if len(childrenSet) == 0 {
|
||||
delete(c.parentUIDToChildren, parentUID)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1014,7 +1021,7 @@ func (c *clusterCache) sync() error {
|
||||
c.apisMeta = make(map[schema.GroupKind]*apiMeta)
|
||||
c.resources = make(map[kube.ResourceKey]*Resource)
|
||||
c.namespacedResources = make(map[schema.GroupKind]bool)
|
||||
c.parentUIDToChildren = make(map[types.UID][]kube.ResourceKey)
|
||||
c.parentUIDToChildren = make(map[types.UID]map[kube.ResourceKey]struct{})
|
||||
config := c.config
|
||||
version, err := c.kubectl.GetServerVersion(config)
|
||||
if err != nil {
|
||||
@@ -1113,9 +1120,6 @@ func (c *clusterCache) sync() error {
|
||||
return fmt.Errorf("failed to sync cluster %s: %w", c.config.Host, err)
|
||||
}
|
||||
|
||||
// Rebuild orphaned children index after all resources are loaded
|
||||
c.rebuildParentToChildrenIndex()
|
||||
|
||||
c.log.Info("Cluster successfully synced")
|
||||
return nil
|
||||
}
|
||||
@@ -1256,8 +1260,8 @@ func (c *clusterCache) processCrossNamespaceChildren(
|
||||
}
|
||||
|
||||
// Use parent-to-children index for O(1) lookup of direct children
|
||||
childKeys := c.parentUIDToChildren[clusterResource.Ref.UID]
|
||||
for _, childKey := range childKeys {
|
||||
childrenSet := c.parentUIDToChildren[clusterResource.Ref.UID]
|
||||
for childKey := range childrenSet {
|
||||
child := c.resources[childKey]
|
||||
if child == nil {
|
||||
continue
|
||||
@@ -1310,8 +1314,8 @@ func (c *clusterCache) iterateChildrenUsingIndex(
|
||||
action func(resource *Resource, namespaceResources map[kube.ResourceKey]*Resource) bool,
|
||||
) {
|
||||
// Look up direct children of this parent using the index
|
||||
childKeys := c.parentUIDToChildren[parent.Ref.UID]
|
||||
for _, childKey := range childKeys {
|
||||
childrenSet := c.parentUIDToChildren[parent.Ref.UID]
|
||||
for childKey := range childrenSet {
|
||||
if actionCallState[childKey] != notCalled {
|
||||
continue // action() already called or in progress
|
||||
}
|
||||
@@ -1631,6 +1635,10 @@ func (c *clusterCache) onNodeRemoved(key kube.ResourceKey) {
|
||||
for k, v := range ns {
|
||||
if mightHaveInferredOwner(v) && existing.isInferredParentOf(k) {
|
||||
v.setOwnerRef(existing.toOwnerRef(), false)
|
||||
// Update index inline when removing inferred ref
|
||||
if existing.Ref.UID != "" {
|
||||
c.removeFromParentUIDToChildren(existing.Ref.UID, k)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
345
gitops-engine/pkg/cache/cluster_test.go
vendored
345
gitops-engine/pkg/cache/cluster_test.go
vendored
@@ -416,6 +416,128 @@ func TestStatefulSetOwnershipInferred(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestStatefulSetPVC_ParentToChildrenIndex verifies that inferred StatefulSet → PVC
|
||||
// relationships are correctly captured in the parentUIDToChildren index during initial sync.
|
||||
//
|
||||
// The index is updated inline when inferred owner refs are added in setNode()
|
||||
// (see the inferred parent handling section in clusterCache.setNode).
|
||||
func TestStatefulSetPVC_ParentToChildrenIndex(t *testing.T) {
|
||||
stsUID := types.UID("sts-uid-123")
|
||||
|
||||
// StatefulSet with volumeClaimTemplate named "data"
|
||||
sts := &appsv1.StatefulSet{
|
||||
TypeMeta: metav1.TypeMeta{APIVersion: "apps/v1", Kind: kube.StatefulSetKind},
|
||||
ObjectMeta: metav1.ObjectMeta{UID: stsUID, Name: "web", Namespace: "default"},
|
||||
Spec: appsv1.StatefulSetSpec{
|
||||
VolumeClaimTemplates: []corev1.PersistentVolumeClaim{{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "data"},
|
||||
}},
|
||||
},
|
||||
}
|
||||
|
||||
// PVCs that match the StatefulSet's volumeClaimTemplate pattern: <template>-<sts>-<ordinal>
|
||||
// These have NO explicit owner references - the relationship is INFERRED
|
||||
pvc0 := &corev1.PersistentVolumeClaim{
|
||||
TypeMeta: metav1.TypeMeta{APIVersion: "v1", Kind: kube.PersistentVolumeClaimKind},
|
||||
ObjectMeta: metav1.ObjectMeta{UID: "pvc-0-uid", Name: "data-web-0", Namespace: "default"},
|
||||
}
|
||||
pvc1 := &corev1.PersistentVolumeClaim{
|
||||
TypeMeta: metav1.TypeMeta{APIVersion: "v1", Kind: kube.PersistentVolumeClaimKind},
|
||||
ObjectMeta: metav1.ObjectMeta{UID: "pvc-1-uid", Name: "data-web-1", Namespace: "default"},
|
||||
}
|
||||
|
||||
// Create cluster with all resources
|
||||
// Must add PersistentVolumeClaim to API resources since it's not in the default set
|
||||
cluster := newCluster(t, sts, pvc0, pvc1).WithAPIResources([]kube.APIResourceInfo{{
|
||||
GroupKind: schema.GroupKind{Group: "", Kind: kube.PersistentVolumeClaimKind},
|
||||
GroupVersionResource: schema.GroupVersionResource{Group: "", Version: "v1", Resource: "persistentvolumeclaims"},
|
||||
Meta: metav1.APIResource{Namespaced: true},
|
||||
}})
|
||||
err := cluster.EnsureSynced()
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify the parentUIDToChildren index contains the inferred relationships
|
||||
cluster.lock.RLock()
|
||||
defer cluster.lock.RUnlock()
|
||||
|
||||
pvc0Key := kube.ResourceKey{Group: "", Kind: kube.PersistentVolumeClaimKind, Namespace: "default", Name: "data-web-0"}
|
||||
pvc1Key := kube.ResourceKey{Group: "", Kind: kube.PersistentVolumeClaimKind, Namespace: "default", Name: "data-web-1"}
|
||||
|
||||
children, ok := cluster.parentUIDToChildren[stsUID]
|
||||
require.True(t, ok, "StatefulSet should have entry in parentUIDToChildren index")
|
||||
require.Contains(t, children, pvc0Key, "PVC data-web-0 should be in StatefulSet's children (inferred relationship)")
|
||||
require.Contains(t, children, pvc1Key, "PVC data-web-1 should be in StatefulSet's children (inferred relationship)")
|
||||
|
||||
// Also verify the OwnerRefs were set correctly on the PVCs
|
||||
pvc0Resource := cluster.resources[pvc0Key]
|
||||
require.NotNil(t, pvc0Resource)
|
||||
require.Len(t, pvc0Resource.OwnerRefs, 1, "PVC0 should have inferred owner ref")
|
||||
require.Equal(t, stsUID, pvc0Resource.OwnerRefs[0].UID, "PVC0 owner should be the StatefulSet")
|
||||
|
||||
pvc1Resource := cluster.resources[pvc1Key]
|
||||
require.NotNil(t, pvc1Resource)
|
||||
require.Len(t, pvc1Resource.OwnerRefs, 1, "PVC1 should have inferred owner ref")
|
||||
require.Equal(t, stsUID, pvc1Resource.OwnerRefs[0].UID, "PVC1 owner should be the StatefulSet")
|
||||
}
|
||||
|
||||
// TestStatefulSetPVC_WatchEvent_IndexUpdated verifies that when a PVC is added
|
||||
// via watch event (after initial sync), both the inferred owner reference AND
|
||||
// the parentUIDToChildren index are updated correctly.
|
||||
//
|
||||
// This tests the inline index update logic in setNode() which updates the index
|
||||
// immediately when inferred owner refs are added.
|
||||
func TestStatefulSetPVC_WatchEvent_IndexUpdated(t *testing.T) {
|
||||
stsUID := types.UID("sts-uid-456")
|
||||
|
||||
// StatefulSet with volumeClaimTemplate
|
||||
sts := &appsv1.StatefulSet{
|
||||
TypeMeta: metav1.TypeMeta{APIVersion: "apps/v1", Kind: kube.StatefulSetKind},
|
||||
ObjectMeta: metav1.ObjectMeta{UID: stsUID, Name: "db", Namespace: "default"},
|
||||
Spec: appsv1.StatefulSetSpec{
|
||||
VolumeClaimTemplates: []corev1.PersistentVolumeClaim{{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "storage"},
|
||||
}},
|
||||
},
|
||||
}
|
||||
|
||||
// Create cluster with ONLY the StatefulSet - PVC will be added via watch event
|
||||
cluster := newCluster(t, sts).WithAPIResources([]kube.APIResourceInfo{{
|
||||
GroupKind: schema.GroupKind{Group: "", Kind: kube.PersistentVolumeClaimKind},
|
||||
GroupVersionResource: schema.GroupVersionResource{Group: "", Version: "v1", Resource: "persistentvolumeclaims"},
|
||||
Meta: metav1.APIResource{Namespaced: true},
|
||||
}})
|
||||
err := cluster.EnsureSynced()
|
||||
require.NoError(t, err)
|
||||
|
||||
// PVC that matches the StatefulSet's volumeClaimTemplate pattern
|
||||
// Added via watch event AFTER initial sync
|
||||
pvc := &corev1.PersistentVolumeClaim{
|
||||
TypeMeta: metav1.TypeMeta{APIVersion: "v1", Kind: kube.PersistentVolumeClaimKind},
|
||||
ObjectMeta: metav1.ObjectMeta{UID: "pvc-watch-uid", Name: "storage-db-0", Namespace: "default"},
|
||||
}
|
||||
|
||||
// Simulate watch event adding the PVC
|
||||
cluster.lock.Lock()
|
||||
cluster.setNode(cluster.newResource(mustToUnstructured(pvc)))
|
||||
cluster.lock.Unlock()
|
||||
|
||||
cluster.lock.RLock()
|
||||
defer cluster.lock.RUnlock()
|
||||
|
||||
pvcKey := kube.ResourceKey{Group: "", Kind: kube.PersistentVolumeClaimKind, Namespace: "default", Name: "storage-db-0"}
|
||||
|
||||
// Verify the OwnerRef IS correctly set
|
||||
pvcResource := cluster.resources[pvcKey]
|
||||
require.NotNil(t, pvcResource, "PVC should exist in cache")
|
||||
require.Len(t, pvcResource.OwnerRefs, 1, "PVC should have inferred owner ref from StatefulSet")
|
||||
require.Equal(t, stsUID, pvcResource.OwnerRefs[0].UID, "Owner should be the StatefulSet")
|
||||
|
||||
// Verify the index IS updated for inferred refs via watch events
|
||||
children, indexUpdated := cluster.parentUIDToChildren[stsUID]
|
||||
require.True(t, indexUpdated, "Index should be updated when inferred refs are added via watch events")
|
||||
require.Contains(t, children, pvcKey, "PVC should be in StatefulSet's children (inferred relationship)")
|
||||
}
|
||||
|
||||
func TestEnsureSyncedSingleNamespace(t *testing.T) {
|
||||
obj1 := &appsv1.Deployment{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
@@ -2298,3 +2420,226 @@ func TestIterateHierarchyV2_CircularOwnerChain_NoStackOverflow(t *testing.T) {
|
||||
assert.Equal(t, 1, visitCount["resource-a"], "resource-a should be visited exactly once")
|
||||
assert.Equal(t, 1, visitCount["resource-b"], "resource-b should be visited exactly once")
|
||||
}
|
||||
|
||||
// BenchmarkSync_ParentToChildrenIndex measures the overhead of parent-to-children index
|
||||
// operations during sync. This benchmark was created to investigate performance regression
|
||||
// reported in https://github.com/argoproj/argo-cd/issues/26863
|
||||
//
|
||||
// The index is now maintained with O(1) operations (set-based) and updated inline
|
||||
// in setNode() for both explicit and inferred owner refs. No rebuild is needed.
|
||||
//
|
||||
// This benchmark measures sync performance with resources that have owner references
|
||||
// to quantify the index-building overhead at different scales.
|
||||
func BenchmarkSync_ParentToChildrenIndex(b *testing.B) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
totalResources int
|
||||
pctWithOwnerRefs int // Percentage of resources with owner references
|
||||
}{
|
||||
// Baseline: no owner refs (index operations are no-ops)
|
||||
{"1000res_0pctOwnerRefs", 1000, 0},
|
||||
{"5000res_0pctOwnerRefs", 5000, 0},
|
||||
{"10000res_0pctOwnerRefs", 10000, 0},
|
||||
|
||||
// Typical case: ~80% of resources have owner refs (pods owned by RS, RS owned by Deployment)
|
||||
{"1000res_80pctOwnerRefs", 1000, 80},
|
||||
{"5000res_80pctOwnerRefs", 5000, 80},
|
||||
{"10000res_80pctOwnerRefs", 10000, 80},
|
||||
|
||||
// Heavy case: all resources have owner refs
|
||||
{"1000res_100pctOwnerRefs", 1000, 100},
|
||||
{"5000res_100pctOwnerRefs", 5000, 100},
|
||||
{"10000res_100pctOwnerRefs", 10000, 100},
|
||||
|
||||
// Stress test: larger scale
|
||||
{"20000res_80pctOwnerRefs", 20000, 80},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
b.Run(tc.name, func(b *testing.B) {
|
||||
resources := make([]runtime.Object, 0, tc.totalResources)
|
||||
|
||||
// Create parent resources (deployments) - these won't have owner refs
|
||||
numParents := tc.totalResources / 10 // 10% are parents
|
||||
if numParents < 1 {
|
||||
numParents = 1
|
||||
}
|
||||
parentUIDs := make([]types.UID, numParents)
|
||||
for i := 0; i < numParents; i++ {
|
||||
uid := types.UID(fmt.Sprintf("deploy-uid-%d", i))
|
||||
parentUIDs[i] = uid
|
||||
resources = append(resources, &appsv1.Deployment{
|
||||
TypeMeta: metav1.TypeMeta{APIVersion: "apps/v1", Kind: "Deployment"},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: fmt.Sprintf("deploy-%d", i),
|
||||
Namespace: "default",
|
||||
UID: uid,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// Create child resources (pods) - some with owner refs
|
||||
numChildren := tc.totalResources - numParents
|
||||
numWithOwnerRefs := (numChildren * tc.pctWithOwnerRefs) / 100
|
||||
|
||||
for i := 0; i < numChildren; i++ {
|
||||
pod := &corev1.Pod{
|
||||
TypeMeta: metav1.TypeMeta{APIVersion: "v1", Kind: "Pod"},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: fmt.Sprintf("pod-%d", i),
|
||||
Namespace: "default",
|
||||
UID: types.UID(fmt.Sprintf("pod-uid-%d", i)),
|
||||
},
|
||||
}
|
||||
|
||||
// Add owner refs to the first numWithOwnerRefs pods
|
||||
if i < numWithOwnerRefs {
|
||||
parentIdx := i % numParents
|
||||
pod.OwnerReferences = []metav1.OwnerReference{{
|
||||
APIVersion: "apps/v1",
|
||||
Kind: "Deployment",
|
||||
Name: fmt.Sprintf("deploy-%d", parentIdx),
|
||||
UID: parentUIDs[parentIdx],
|
||||
}}
|
||||
}
|
||||
|
||||
resources = append(resources, pod)
|
||||
}
|
||||
|
||||
cluster := newCluster(b, resources...)
|
||||
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
|
||||
for n := 0; n < b.N; n++ {
|
||||
// sync() reinitializes resources, parentUIDToChildren, etc. at the start,
|
||||
// so no manual reset is needed here.
|
||||
err := cluster.sync()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkUpdateParentUIDToChildren measures the cost of incremental index updates
|
||||
// during setNode. This is called for EVERY resource during sync. The index uses
|
||||
// set-based storage so add/remove operations are O(1) regardless of children count.
|
||||
func BenchmarkUpdateParentUIDToChildren(b *testing.B) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
childrenPerParent int
|
||||
}{
|
||||
{"10children", 10},
|
||||
{"50children", 50},
|
||||
{"100children", 100},
|
||||
{"500children", 500},
|
||||
{"1000children", 1000},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
b.Run(tc.name, func(b *testing.B) {
|
||||
cluster := newCluster(b)
|
||||
err := cluster.EnsureSynced()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
parentUID := types.UID("parent-uid")
|
||||
|
||||
// Pre-populate with existing children
|
||||
childrenSet := make(map[kube.ResourceKey]struct{})
|
||||
for i := 0; i < tc.childrenPerParent; i++ {
|
||||
childKey := kube.ResourceKey{
|
||||
Group: "",
|
||||
Kind: "Pod",
|
||||
Namespace: "default",
|
||||
Name: fmt.Sprintf("existing-child-%d", i),
|
||||
}
|
||||
childrenSet[childKey] = struct{}{}
|
||||
}
|
||||
cluster.parentUIDToChildren[parentUID] = childrenSet
|
||||
|
||||
// Create a new child key to add
|
||||
newChildKey := kube.ResourceKey{
|
||||
Group: "",
|
||||
Kind: "Pod",
|
||||
Namespace: "default",
|
||||
Name: "new-child",
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
|
||||
for n := 0; n < b.N; n++ {
|
||||
// Simulate adding a new child - O(1) set insertion
|
||||
cluster.addToParentUIDToChildren(parentUID, newChildKey)
|
||||
// Remove it so we can add it again in the next iteration
|
||||
cluster.removeFromParentUIDToChildren(parentUID, newChildKey)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkIncrementalIndexBuild measures the cost of incremental index updates
|
||||
// via addToParentUIDToChildren during sync. The index uses O(1) set-based operations.
|
||||
//
|
||||
// This benchmark was created to investigate issue #26863 and verify the fix.
|
||||
func BenchmarkIncrementalIndexBuild(b *testing.B) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
numParents int
|
||||
childrenPerParent int
|
||||
}{
|
||||
{"100parents_10children", 100, 10},
|
||||
{"100parents_50children", 100, 50},
|
||||
{"100parents_100children", 100, 100},
|
||||
{"1000parents_10children", 1000, 10},
|
||||
{"1000parents_100children", 1000, 100},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
// Benchmark incremental approach (what happens during setNode)
|
||||
b.Run(tc.name+"_incremental", func(b *testing.B) {
|
||||
cluster := newCluster(b)
|
||||
err := cluster.EnsureSynced()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
// Prepare parent UIDs and child keys
|
||||
type childInfo struct {
|
||||
parentUID types.UID
|
||||
childKey kube.ResourceKey
|
||||
}
|
||||
children := make([]childInfo, 0, tc.numParents*tc.childrenPerParent)
|
||||
for p := 0; p < tc.numParents; p++ {
|
||||
parentUID := types.UID(fmt.Sprintf("parent-%d", p))
|
||||
for c := 0; c < tc.childrenPerParent; c++ {
|
||||
children = append(children, childInfo{
|
||||
parentUID: parentUID,
|
||||
childKey: kube.ResourceKey{
|
||||
Kind: "Pod",
|
||||
Namespace: "default",
|
||||
Name: fmt.Sprintf("child-%d-%d", p, c),
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
|
||||
for n := 0; n < b.N; n++ {
|
||||
// Clear the index
|
||||
cluster.parentUIDToChildren = make(map[types.UID]map[kube.ResourceKey]struct{})
|
||||
|
||||
// Simulate incremental adds (O(1) set insertions)
|
||||
for _, child := range children {
|
||||
cluster.addToParentUIDToChildren(child.parentUID, child.childKey)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,4 +12,4 @@ resources:
|
||||
images:
|
||||
- name: quay.io/argoproj/argocd
|
||||
newName: quay.io/argoproj/argocd
|
||||
newTag: v3.4.0-rc3
|
||||
newTag: v3.4.0-rc4
|
||||
|
||||
@@ -5,7 +5,7 @@ kind: Kustomization
|
||||
images:
|
||||
- name: quay.io/argoproj/argocd
|
||||
newName: quay.io/argoproj/argocd
|
||||
newTag: v3.4.0-rc3
|
||||
newTag: v3.4.0-rc4
|
||||
resources:
|
||||
- ./application-controller
|
||||
- ./dex
|
||||
|
||||
12
manifests/core-install-with-hydrator.yaml
generated
12
manifests/core-install-with-hydrator.yaml
generated
@@ -31332,7 +31332,7 @@ spec:
|
||||
key: applicationsetcontroller.status.max.resources.count
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc3
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc4
|
||||
imagePullPolicy: Always
|
||||
name: argocd-applicationset-controller
|
||||
ports:
|
||||
@@ -31473,7 +31473,7 @@ spec:
|
||||
key: log.format.timestamp
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc3
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc4
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -31601,7 +31601,7 @@ spec:
|
||||
- argocd
|
||||
- admin
|
||||
- redis-initial-password
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc3
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc4
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: secret-init
|
||||
securityContext:
|
||||
@@ -31910,7 +31910,7 @@ spec:
|
||||
value: /helm-working-dir
|
||||
- name: HELM_DATA_HOME
|
||||
value: /helm-working-dir
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc3
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc4
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -31963,7 +31963,7 @@ spec:
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc3
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc4
|
||||
name: copyutil
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
@@ -32366,7 +32366,7 @@ spec:
|
||||
optional: true
|
||||
- name: KUBECACHEDIR
|
||||
value: /tmp/kubecache
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc3
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc4
|
||||
imagePullPolicy: Always
|
||||
name: argocd-application-controller
|
||||
ports:
|
||||
|
||||
10
manifests/core-install.yaml
generated
10
manifests/core-install.yaml
generated
@@ -31300,7 +31300,7 @@ spec:
|
||||
key: applicationsetcontroller.status.max.resources.count
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc3
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc4
|
||||
imagePullPolicy: Always
|
||||
name: argocd-applicationset-controller
|
||||
ports:
|
||||
@@ -31429,7 +31429,7 @@ spec:
|
||||
- argocd
|
||||
- admin
|
||||
- redis-initial-password
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc3
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc4
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: secret-init
|
||||
securityContext:
|
||||
@@ -31738,7 +31738,7 @@ spec:
|
||||
value: /helm-working-dir
|
||||
- name: HELM_DATA_HOME
|
||||
value: /helm-working-dir
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc3
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc4
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -31791,7 +31791,7 @@ spec:
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc3
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc4
|
||||
name: copyutil
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
@@ -32194,7 +32194,7 @@ spec:
|
||||
optional: true
|
||||
- name: KUBECACHEDIR
|
||||
value: /tmp/kubecache
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc3
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc4
|
||||
imagePullPolicy: Always
|
||||
name: argocd-application-controller
|
||||
ports:
|
||||
|
||||
@@ -12,4 +12,4 @@ resources:
|
||||
images:
|
||||
- name: quay.io/argoproj/argocd
|
||||
newName: quay.io/argoproj/argocd
|
||||
newTag: v3.4.0-rc3
|
||||
newTag: v3.4.0-rc4
|
||||
|
||||
@@ -12,7 +12,7 @@ patches:
|
||||
images:
|
||||
- name: quay.io/argoproj/argocd
|
||||
newName: quay.io/argoproj/argocd
|
||||
newTag: v3.4.0-rc3
|
||||
newTag: v3.4.0-rc4
|
||||
resources:
|
||||
- ../../base/application-controller
|
||||
- ../../base/applicationset-controller
|
||||
|
||||
18
manifests/ha/install-with-hydrator.yaml
generated
18
manifests/ha/install-with-hydrator.yaml
generated
@@ -32758,7 +32758,7 @@ spec:
|
||||
key: applicationsetcontroller.status.max.resources.count
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc3
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc4
|
||||
imagePullPolicy: Always
|
||||
name: argocd-applicationset-controller
|
||||
ports:
|
||||
@@ -32899,7 +32899,7 @@ spec:
|
||||
key: log.format.timestamp
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc3
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc4
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -33057,7 +33057,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /shared/argocd-dex
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc3
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc4
|
||||
imagePullPolicy: Always
|
||||
name: copyutil
|
||||
securityContext:
|
||||
@@ -33159,7 +33159,7 @@ spec:
|
||||
key: notificationscontroller.repo.server.plaintext
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc3
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc4
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
@@ -33283,7 +33283,7 @@ spec:
|
||||
- argocd
|
||||
- admin
|
||||
- redis-initial-password
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc3
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc4
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: secret-init
|
||||
securityContext:
|
||||
@@ -33618,7 +33618,7 @@ spec:
|
||||
value: /helm-working-dir
|
||||
- name: HELM_DATA_HOME
|
||||
value: /helm-working-dir
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc3
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc4
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -33671,7 +33671,7 @@ spec:
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc3
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc4
|
||||
name: copyutil
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
@@ -34100,7 +34100,7 @@ spec:
|
||||
key: server.sync.replace.allowed
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc3
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc4
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
@@ -34532,7 +34532,7 @@ spec:
|
||||
optional: true
|
||||
- name: KUBECACHEDIR
|
||||
value: /tmp/kubecache
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc3
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc4
|
||||
imagePullPolicy: Always
|
||||
name: argocd-application-controller
|
||||
ports:
|
||||
|
||||
16
manifests/ha/install.yaml
generated
16
manifests/ha/install.yaml
generated
@@ -32728,7 +32728,7 @@ spec:
|
||||
key: applicationsetcontroller.status.max.resources.count
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc3
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc4
|
||||
imagePullPolicy: Always
|
||||
name: argocd-applicationset-controller
|
||||
ports:
|
||||
@@ -32887,7 +32887,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /shared/argocd-dex
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc3
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc4
|
||||
imagePullPolicy: Always
|
||||
name: copyutil
|
||||
securityContext:
|
||||
@@ -32989,7 +32989,7 @@ spec:
|
||||
key: notificationscontroller.repo.server.plaintext
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc3
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc4
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
@@ -33113,7 +33113,7 @@ spec:
|
||||
- argocd
|
||||
- admin
|
||||
- redis-initial-password
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc3
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc4
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: secret-init
|
||||
securityContext:
|
||||
@@ -33448,7 +33448,7 @@ spec:
|
||||
value: /helm-working-dir
|
||||
- name: HELM_DATA_HOME
|
||||
value: /helm-working-dir
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc3
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc4
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -33501,7 +33501,7 @@ spec:
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc3
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc4
|
||||
name: copyutil
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
@@ -33930,7 +33930,7 @@ spec:
|
||||
key: server.sync.replace.allowed
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc3
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc4
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
@@ -34362,7 +34362,7 @@ spec:
|
||||
optional: true
|
||||
- name: KUBECACHEDIR
|
||||
value: /tmp/kubecache
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc3
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc4
|
||||
imagePullPolicy: Always
|
||||
name: argocd-application-controller
|
||||
ports:
|
||||
|
||||
18
manifests/ha/namespace-install-with-hydrator.yaml
generated
18
manifests/ha/namespace-install-with-hydrator.yaml
generated
@@ -2005,7 +2005,7 @@ spec:
|
||||
key: applicationsetcontroller.status.max.resources.count
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc3
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc4
|
||||
imagePullPolicy: Always
|
||||
name: argocd-applicationset-controller
|
||||
ports:
|
||||
@@ -2146,7 +2146,7 @@ spec:
|
||||
key: log.format.timestamp
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc3
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc4
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -2304,7 +2304,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /shared/argocd-dex
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc3
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc4
|
||||
imagePullPolicy: Always
|
||||
name: copyutil
|
||||
securityContext:
|
||||
@@ -2406,7 +2406,7 @@ spec:
|
||||
key: notificationscontroller.repo.server.plaintext
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc3
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc4
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
@@ -2530,7 +2530,7 @@ spec:
|
||||
- argocd
|
||||
- admin
|
||||
- redis-initial-password
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc3
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc4
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: secret-init
|
||||
securityContext:
|
||||
@@ -2865,7 +2865,7 @@ spec:
|
||||
value: /helm-working-dir
|
||||
- name: HELM_DATA_HOME
|
||||
value: /helm-working-dir
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc3
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc4
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -2918,7 +2918,7 @@ spec:
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc3
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc4
|
||||
name: copyutil
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
@@ -3347,7 +3347,7 @@ spec:
|
||||
key: server.sync.replace.allowed
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc3
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc4
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
@@ -3779,7 +3779,7 @@ spec:
|
||||
optional: true
|
||||
- name: KUBECACHEDIR
|
||||
value: /tmp/kubecache
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc3
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc4
|
||||
imagePullPolicy: Always
|
||||
name: argocd-application-controller
|
||||
ports:
|
||||
|
||||
16
manifests/ha/namespace-install.yaml
generated
16
manifests/ha/namespace-install.yaml
generated
@@ -1975,7 +1975,7 @@ spec:
|
||||
key: applicationsetcontroller.status.max.resources.count
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc3
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc4
|
||||
imagePullPolicy: Always
|
||||
name: argocd-applicationset-controller
|
||||
ports:
|
||||
@@ -2134,7 +2134,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /shared/argocd-dex
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc3
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc4
|
||||
imagePullPolicy: Always
|
||||
name: copyutil
|
||||
securityContext:
|
||||
@@ -2236,7 +2236,7 @@ spec:
|
||||
key: notificationscontroller.repo.server.plaintext
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc3
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc4
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
@@ -2360,7 +2360,7 @@ spec:
|
||||
- argocd
|
||||
- admin
|
||||
- redis-initial-password
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc3
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc4
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: secret-init
|
||||
securityContext:
|
||||
@@ -2695,7 +2695,7 @@ spec:
|
||||
value: /helm-working-dir
|
||||
- name: HELM_DATA_HOME
|
||||
value: /helm-working-dir
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc3
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc4
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -2748,7 +2748,7 @@ spec:
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc3
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc4
|
||||
name: copyutil
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
@@ -3177,7 +3177,7 @@ spec:
|
||||
key: server.sync.replace.allowed
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc3
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc4
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
@@ -3609,7 +3609,7 @@ spec:
|
||||
optional: true
|
||||
- name: KUBECACHEDIR
|
||||
value: /tmp/kubecache
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc3
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc4
|
||||
imagePullPolicy: Always
|
||||
name: argocd-application-controller
|
||||
ports:
|
||||
|
||||
18
manifests/install-with-hydrator.yaml
generated
18
manifests/install-with-hydrator.yaml
generated
@@ -31776,7 +31776,7 @@ spec:
|
||||
key: applicationsetcontroller.status.max.resources.count
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc3
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc4
|
||||
imagePullPolicy: Always
|
||||
name: argocd-applicationset-controller
|
||||
ports:
|
||||
@@ -31917,7 +31917,7 @@ spec:
|
||||
key: log.format.timestamp
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc3
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc4
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -32075,7 +32075,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /shared/argocd-dex
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc3
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc4
|
||||
imagePullPolicy: Always
|
||||
name: copyutil
|
||||
securityContext:
|
||||
@@ -32177,7 +32177,7 @@ spec:
|
||||
key: notificationscontroller.repo.server.plaintext
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc3
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc4
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
@@ -32279,7 +32279,7 @@ spec:
|
||||
- argocd
|
||||
- admin
|
||||
- redis-initial-password
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc3
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc4
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: secret-init
|
||||
securityContext:
|
||||
@@ -32588,7 +32588,7 @@ spec:
|
||||
value: /helm-working-dir
|
||||
- name: HELM_DATA_HOME
|
||||
value: /helm-working-dir
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc3
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc4
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -32641,7 +32641,7 @@ spec:
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc3
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc4
|
||||
name: copyutil
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
@@ -33068,7 +33068,7 @@ spec:
|
||||
key: server.sync.replace.allowed
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc3
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc4
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
@@ -33500,7 +33500,7 @@ spec:
|
||||
optional: true
|
||||
- name: KUBECACHEDIR
|
||||
value: /tmp/kubecache
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc3
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc4
|
||||
imagePullPolicy: Always
|
||||
name: argocd-application-controller
|
||||
ports:
|
||||
|
||||
16
manifests/install.yaml
generated
16
manifests/install.yaml
generated
@@ -31744,7 +31744,7 @@ spec:
|
||||
key: applicationsetcontroller.status.max.resources.count
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc3
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc4
|
||||
imagePullPolicy: Always
|
||||
name: argocd-applicationset-controller
|
||||
ports:
|
||||
@@ -31903,7 +31903,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /shared/argocd-dex
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc3
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc4
|
||||
imagePullPolicy: Always
|
||||
name: copyutil
|
||||
securityContext:
|
||||
@@ -32005,7 +32005,7 @@ spec:
|
||||
key: notificationscontroller.repo.server.plaintext
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc3
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc4
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
@@ -32107,7 +32107,7 @@ spec:
|
||||
- argocd
|
||||
- admin
|
||||
- redis-initial-password
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc3
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc4
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: secret-init
|
||||
securityContext:
|
||||
@@ -32416,7 +32416,7 @@ spec:
|
||||
value: /helm-working-dir
|
||||
- name: HELM_DATA_HOME
|
||||
value: /helm-working-dir
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc3
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc4
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -32469,7 +32469,7 @@ spec:
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc3
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc4
|
||||
name: copyutil
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
@@ -32896,7 +32896,7 @@ spec:
|
||||
key: server.sync.replace.allowed
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc3
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc4
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
@@ -33328,7 +33328,7 @@ spec:
|
||||
optional: true
|
||||
- name: KUBECACHEDIR
|
||||
value: /tmp/kubecache
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc3
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc4
|
||||
imagePullPolicy: Always
|
||||
name: argocd-application-controller
|
||||
ports:
|
||||
|
||||
18
manifests/namespace-install-with-hydrator.yaml
generated
18
manifests/namespace-install-with-hydrator.yaml
generated
@@ -1023,7 +1023,7 @@ spec:
|
||||
key: applicationsetcontroller.status.max.resources.count
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc3
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc4
|
||||
imagePullPolicy: Always
|
||||
name: argocd-applicationset-controller
|
||||
ports:
|
||||
@@ -1164,7 +1164,7 @@ spec:
|
||||
key: log.format.timestamp
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc3
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc4
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -1322,7 +1322,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /shared/argocd-dex
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc3
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc4
|
||||
imagePullPolicy: Always
|
||||
name: copyutil
|
||||
securityContext:
|
||||
@@ -1424,7 +1424,7 @@ spec:
|
||||
key: notificationscontroller.repo.server.plaintext
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc3
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc4
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
@@ -1526,7 +1526,7 @@ spec:
|
||||
- argocd
|
||||
- admin
|
||||
- redis-initial-password
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc3
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc4
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: secret-init
|
||||
securityContext:
|
||||
@@ -1835,7 +1835,7 @@ spec:
|
||||
value: /helm-working-dir
|
||||
- name: HELM_DATA_HOME
|
||||
value: /helm-working-dir
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc3
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc4
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -1888,7 +1888,7 @@ spec:
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc3
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc4
|
||||
name: copyutil
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
@@ -2315,7 +2315,7 @@ spec:
|
||||
key: server.sync.replace.allowed
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc3
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc4
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
@@ -2747,7 +2747,7 @@ spec:
|
||||
optional: true
|
||||
- name: KUBECACHEDIR
|
||||
value: /tmp/kubecache
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc3
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc4
|
||||
imagePullPolicy: Always
|
||||
name: argocd-application-controller
|
||||
ports:
|
||||
|
||||
16
manifests/namespace-install.yaml
generated
16
manifests/namespace-install.yaml
generated
@@ -991,7 +991,7 @@ spec:
|
||||
key: applicationsetcontroller.status.max.resources.count
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc3
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc4
|
||||
imagePullPolicy: Always
|
||||
name: argocd-applicationset-controller
|
||||
ports:
|
||||
@@ -1150,7 +1150,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /shared/argocd-dex
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc3
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc4
|
||||
imagePullPolicy: Always
|
||||
name: copyutil
|
||||
securityContext:
|
||||
@@ -1252,7 +1252,7 @@ spec:
|
||||
key: notificationscontroller.repo.server.plaintext
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc3
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc4
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
@@ -1354,7 +1354,7 @@ spec:
|
||||
- argocd
|
||||
- admin
|
||||
- redis-initial-password
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc3
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc4
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: secret-init
|
||||
securityContext:
|
||||
@@ -1663,7 +1663,7 @@ spec:
|
||||
value: /helm-working-dir
|
||||
- name: HELM_DATA_HOME
|
||||
value: /helm-working-dir
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc3
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc4
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -1716,7 +1716,7 @@ spec:
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc3
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc4
|
||||
name: copyutil
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
@@ -2143,7 +2143,7 @@ spec:
|
||||
key: server.sync.replace.allowed
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc3
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc4
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
@@ -2575,7 +2575,7 @@ spec:
|
||||
optional: true
|
||||
- name: KUBECACHEDIR
|
||||
value: /tmp/kubecache
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc3
|
||||
image: quay.io/argoproj/argocd:v3.4.0-rc4
|
||||
imagePullPolicy: Always
|
||||
name: argocd-application-controller
|
||||
ports:
|
||||
|
||||
@@ -1309,7 +1309,7 @@ func helmTemplate(appPath string, repoRoot string, env *v1alpha1.Env, q *apiclie
|
||||
return nil, "", fmt.Errorf("error getting helm repos: %w", err)
|
||||
}
|
||||
|
||||
h, err := helm.NewHelmApp(appPath, helmRepos, isLocal, version, proxy, q.Repo.NoProxy, passCredentials)
|
||||
h, err := helm.NewHelmApp(appPath, helmRepos, isLocal, version, proxy, q.Repo.NoProxy, passCredentials, q.Repo.Insecure)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("error initializing helm app object: %w", err)
|
||||
}
|
||||
@@ -2419,7 +2419,7 @@ func (s *Service) populateHelmAppDetails(res *apiclient.RepoAppDetailsResponse,
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
h, err := helm.NewHelmApp(appPath, helmRepos, false, version, q.Repo.Proxy, q.Repo.NoProxy, passCredentials)
|
||||
h, err := helm.NewHelmApp(appPath, helmRepos, false, version, q.Repo.Proxy, q.Repo.NoProxy, passCredentials, q.Repo.Insecure)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -334,8 +334,6 @@ func NewServer(ctx context.Context, opts ArgoCDServerOpts, appsetOpts Applicatio
|
||||
appsetLister := appFactory.Argoproj().V1alpha1().ApplicationSets().Lister()
|
||||
|
||||
userStateStorage := util_session.NewUserStateStorage(opts.RedisClient)
|
||||
ssoClientApp, err := oidc.NewClientApp(settings, opts.DexServerAddr, opts.DexTLSConfig, opts.BaseHRef, cacheutil.NewRedisCache(opts.RedisClient, settings.UserInfoCacheExpiration(), cacheutil.RedisCompressionNone))
|
||||
errorsutil.CheckError(err)
|
||||
sessionMgr := util_session.NewSessionManager(settingsMgr, projLister, opts.DexServerAddr, opts.DexTLSConfig, userStateStorage)
|
||||
enf := rbac.NewEnforcer(opts.KubeClientset, opts.Namespace, common.ArgoCDRBACConfigMapName, nil)
|
||||
enf.EnableEnforce(!opts.DisableAuth)
|
||||
@@ -383,7 +381,6 @@ func NewServer(ctx context.Context, opts ArgoCDServerOpts, appsetOpts Applicatio
|
||||
a := &ArgoCDServer{
|
||||
ArgoCDServerOpts: opts,
|
||||
ApplicationSetOpts: appsetOpts,
|
||||
ssoClientApp: ssoClientApp,
|
||||
log: logger,
|
||||
settings: settings,
|
||||
sessionMgr: sessionMgr,
|
||||
@@ -586,6 +583,10 @@ func (server *ArgoCDServer) Run(ctx context.Context, listeners *Listeners) {
|
||||
if server.RedisClient != nil {
|
||||
cacheutil.CollectMetrics(server.RedisClient, metricsServ, server.userStateStorage.GetLockObject())
|
||||
}
|
||||
// OIDC config needs to be refreshed at each server restart
|
||||
ssoClientApp, err := oidc.NewClientApp(server.settings, server.DexServerAddr, server.DexTLSConfig, server.BaseHRef, cacheutil.NewRedisCache(server.RedisClient, server.settings.UserInfoCacheExpiration(), cacheutil.RedisCompressionNone))
|
||||
errorsutil.CheckError(err)
|
||||
server.ssoClientApp = ssoClientApp
|
||||
|
||||
// Don't init storage until after CollectMetrics. CollectMetrics adds hooks to the Redis client, and Init
|
||||
// reads those hooks. If this is called first, there may be a data race.
|
||||
|
||||
@@ -488,6 +488,100 @@ func TestGracefulShutdown(t *testing.T) {
|
||||
assert.True(t, shutdown)
|
||||
}
|
||||
|
||||
func TestOIDCRefresh(t *testing.T) {
|
||||
port, err := test.GetFreePort()
|
||||
require.NoError(t, err)
|
||||
mockRepoClient := &mocks.Clientset{RepoServerServiceClient: &mocks.RepoServerServiceClient{}}
|
||||
cm := test.NewFakeConfigMap()
|
||||
cm.Data["oidc.config"] = `
|
||||
name: Test OIDC
|
||||
issuer: $oidc.myoidc.issuer
|
||||
clientID: $oidc.myoidc.clientId
|
||||
clientSecret: $oidc.myoidc.clientSecret
|
||||
`
|
||||
secret := test.NewFakeSecret()
|
||||
issuerURL := "http://oidc.127.0.0.1.nip.io"
|
||||
updatedIssuerURL := "http://newoidc.127.0.0.1.nip.io"
|
||||
secret.Data["oidc.myoidc.issuer"] = []byte(issuerURL)
|
||||
secret.Data["oidc.myoidc.clientId"] = []byte("myClientId")
|
||||
secret.Data["oidc.myoidc.clientSecret"] = []byte("myClientSecret")
|
||||
|
||||
kubeclientset := fake.NewSimpleClientset(cm, secret)
|
||||
redis, redisCloser := test.NewInMemoryRedis()
|
||||
defer redisCloser()
|
||||
s := NewServer(
|
||||
t.Context(),
|
||||
ArgoCDServerOpts{
|
||||
ListenPort: port,
|
||||
Namespace: test.FakeArgoCDNamespace,
|
||||
KubeClientset: kubeclientset,
|
||||
AppClientset: apps.NewSimpleClientset(),
|
||||
RepoClientset: mockRepoClient,
|
||||
RedisClient: redis,
|
||||
},
|
||||
ApplicationSetOpts{},
|
||||
)
|
||||
projInformerCancel := test.StartInformer(s.projInformer)
|
||||
defer projInformerCancel()
|
||||
appInformerCancel := test.StartInformer(s.appInformer)
|
||||
defer appInformerCancel()
|
||||
appsetInformerCancel := test.StartInformer(s.appsetInformer)
|
||||
defer appsetInformerCancel()
|
||||
clusterInformerCancel := test.StartInformer(s.clusterInformer)
|
||||
defer clusterInformerCancel()
|
||||
|
||||
shutdown := false
|
||||
|
||||
lns, err := s.Listen()
|
||||
require.NoError(t, err)
|
||||
runCtx := t.Context()
|
||||
|
||||
var wg gosync.WaitGroup
|
||||
wg.Add(1)
|
||||
go func(shutdown *bool) {
|
||||
defer wg.Done()
|
||||
s.Run(runCtx, lns)
|
||||
*shutdown = true
|
||||
}(&shutdown)
|
||||
|
||||
for !s.available.Load() {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
assert.True(t, s.available.Load())
|
||||
assert.Equal(t, issuerURL, s.ssoClientApp.IssuerURL())
|
||||
|
||||
// Update oidc config
|
||||
secret.Data["oidc.myoidc.issuer"] = []byte(updatedIssuerURL)
|
||||
secret.ResourceVersion = "12345"
|
||||
_, err = kubeclientset.CoreV1().Secrets(test.FakeArgoCDNamespace).Update(runCtx, secret, metav1.UpdateOptions{})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Wait for graceful shutdown
|
||||
wg.Wait()
|
||||
for s.available.Load() {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
|
||||
assert.False(t, s.available.Load())
|
||||
|
||||
shutdown = false
|
||||
wg.Add(1)
|
||||
go func(shutdown *bool) {
|
||||
defer wg.Done()
|
||||
s.Run(runCtx, lns)
|
||||
*shutdown = true
|
||||
}(&shutdown)
|
||||
|
||||
for !s.available.Load() {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
assert.True(t, s.available.Load())
|
||||
assert.Equal(t, updatedIssuerURL, s.ssoClientApp.IssuerURL())
|
||||
|
||||
s.stopCh <- syscall.SIGINT
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestAuthenticate(t *testing.T) {
|
||||
type testData struct {
|
||||
test string
|
||||
|
||||
@@ -685,10 +685,11 @@ func DiscoverGitHubAppInstallationID(ctx context.Context, appId int64, privateKe
|
||||
opts.Page = resp.NextPage
|
||||
}
|
||||
|
||||
// Cache all installation IDs
|
||||
// Cache each installation under its account's key so multiple orgs do not overwrite each other.
|
||||
for _, installation := range allInstallations {
|
||||
if installation.Account != nil && installation.Account.Login != nil && installation.ID != nil {
|
||||
githubInstallationIdCache.Set(cacheKey, *installation.ID, gocache.DefaultExpiration)
|
||||
instKey := fmt.Sprintf("%s:%s:%d", strings.ToLower(*installation.Account.Login), domain, appId)
|
||||
githubInstallationIdCache.Set(instKey, *installation.ID, gocache.DefaultExpiration)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -600,6 +600,35 @@ func TestDiscoverGitHubAppInstallationID(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(98765), actualId)
|
||||
})
|
||||
|
||||
t.Run("returns correct installation ID when app is installed on multiple orgs", func(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if strings.HasSuffix(r.URL.Path, "/app/installations") {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
//nolint:errcheck
|
||||
json.NewEncoder(w).Encode([]map[string]any{
|
||||
{"id": 11111, "account": map[string]any{"login": "org-alpha"}},
|
||||
{"id": 22222, "account": map[string]any{"login": "target-org"}},
|
||||
{"id": 33333, "account": map[string]any{"login": "org-gamma"}},
|
||||
})
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
t.Cleanup(func() {
|
||||
domain, _ := domainFromBaseURL(server.URL)
|
||||
for _, org := range []string{"org-alpha", "target-org", "org-gamma"} {
|
||||
githubInstallationIdCache.Delete(fmt.Sprintf("%s:%s:%d", org, domain, 12345))
|
||||
}
|
||||
})
|
||||
|
||||
ctx := context.Background()
|
||||
actualId, err := DiscoverGitHubAppInstallationID(ctx, 12345, fakeGitHubAppPrivateKey, server.URL, "target-org")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(22222), actualId, "should return the installation ID for the requested org, not the last one in the list")
|
||||
})
|
||||
}
|
||||
|
||||
func TestExtractOrgFromRepoURL(t *testing.T) {
|
||||
|
||||
@@ -373,6 +373,7 @@ func (c *nativeHelmChart) loadRepoIndex(ctx context.Context, maxIndexSize int64)
|
||||
Proxy: proxy.GetCallback(c.proxy, c.noProxy),
|
||||
TLSClientConfig: tlsConf,
|
||||
DisableKeepAlives: true,
|
||||
ForceAttemptHTTP2: true,
|
||||
}
|
||||
client := http.Client{Transport: tr}
|
||||
resp, err := client.Do(req)
|
||||
@@ -492,6 +493,7 @@ func (c *nativeHelmChart) GetTags(chart string, noCache bool) ([]string, error)
|
||||
Proxy: proxy.GetCallback(c.proxy, c.noProxy),
|
||||
TLSClientConfig: tlsConf,
|
||||
DisableKeepAlives: true,
|
||||
ForceAttemptHTTP2: true,
|
||||
}
|
||||
|
||||
// Wrap transport to add User-Agent header to all requests
|
||||
|
||||
@@ -2,6 +2,7 @@ package helm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
@@ -10,6 +11,7 @@ import (
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -574,6 +576,68 @@ func TestGetTagsCaching(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetTagsUsesHTTP2(t *testing.T) {
|
||||
t.Run("should negotiate HTTP/2 when TLS is configured", func(t *testing.T) {
|
||||
var requestProtos []string
|
||||
server := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
requestProtos = append(requestProtos, r.Proto)
|
||||
t.Logf("called %s with proto %s", r.URL.Path, r.Proto)
|
||||
|
||||
responseTags := fakeTagsList{
|
||||
Tags: []string{"1.0.0"},
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
require.NoError(t, json.NewEncoder(w).Encode(responseTags))
|
||||
}))
|
||||
// httptest.NewTLSServer only advertises http/1.1 in ALPN, so we must
|
||||
// configure the server to also offer h2 for HTTP/2 negotiation to work.
|
||||
server.TLS = &tls.Config{NextProtos: []string{"h2", "http/1.1"}}
|
||||
server.StartTLS()
|
||||
t.Cleanup(server.Close)
|
||||
|
||||
client := NewClient(server.URL, HelmCreds{InsecureSkipVerify: true}, true, "", "")
|
||||
|
||||
tags, err := client.GetTags("mychart", true)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []string{"1.0.0"}, tags)
|
||||
|
||||
// Verify that at least one request used HTTP/2. When ForceAttemptHTTP2 is
|
||||
// not set on the Transport, Go's TLS stack won't negotiate h2 even though
|
||||
// the server supports it, because a custom TLSClientConfig disables the
|
||||
// automatic HTTP/2 setup.
|
||||
require.NotEmpty(t, requestProtos, "expected at least one request to the server")
|
||||
hasHTTP2 := slices.Contains(requestProtos, "HTTP/2.0")
|
||||
assert.True(t, hasHTTP2, "expected at least one HTTP/2 request, but got protocols: %v", requestProtos)
|
||||
})
|
||||
}
|
||||
|
||||
func TestLoadRepoIndexUsesHTTP2(t *testing.T) {
|
||||
t.Run("should negotiate HTTP/2 when fetching index", func(t *testing.T) {
|
||||
var requestProto string
|
||||
server := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
requestProto = r.Proto
|
||||
t.Logf("called %s with proto %s", r.URL.Path, r.Proto)
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write([]byte(`apiVersion: v1
|
||||
entries: {}
|
||||
`))
|
||||
}))
|
||||
server.TLS = &tls.Config{NextProtos: []string{"h2", "http/1.1"}}
|
||||
server.StartTLS()
|
||||
t.Cleanup(server.Close)
|
||||
|
||||
client := NewClient(server.URL, HelmCreds{InsecureSkipVerify: true}, false, "", "")
|
||||
|
||||
_, err := client.GetIndex(false, 10000)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "HTTP/2.0", requestProto, "expected HTTP/2 request for index fetch, but got %s", requestProto)
|
||||
})
|
||||
}
|
||||
|
||||
func TestUserAgentIsSet(t *testing.T) {
|
||||
t.Run("Default User-Agent for traditional Helm repo", func(t *testing.T) {
|
||||
// Create a test server that captures the User-Agent header
|
||||
|
||||
@@ -327,8 +327,12 @@ func (c *Cmd) PullOCI(repo string, chart string, version string, destination str
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *Cmd) dependencyBuild() (string, error) {
|
||||
out, _, err := c.run(context.Background(), "dependency", "build")
|
||||
func (c *Cmd) dependencyBuild(insecure bool) (string, error) {
|
||||
args := []string{"dependency", "build"}
|
||||
if insecure {
|
||||
args = append(args, "--insecure-skip-tls-verify")
|
||||
}
|
||||
out, _, err := c.run(context.Background(), args...)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to build dependencies: %w", err)
|
||||
}
|
||||
|
||||
@@ -135,6 +135,36 @@ func TestRegistryLogin(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDependencyBuild(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
insecure bool
|
||||
expectedOut string
|
||||
}{
|
||||
{
|
||||
name: "without insecure",
|
||||
insecure: false,
|
||||
expectedOut: "helm dependency build",
|
||||
},
|
||||
{
|
||||
name: "with insecure",
|
||||
insecure: true,
|
||||
expectedOut: "helm dependency build --insecure-skip-tls-verify",
|
||||
},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
c, err := newCmdWithVersion(".", false, "", "", func(cmd *exec.Cmd, _ func(_ string) string) (string, error) {
|
||||
return strings.Join(cmd.Args, " "), nil
|
||||
})
|
||||
require.NoError(t, err)
|
||||
out, err := c.dependencyBuild(tc.insecure)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tc.expectedOut, out)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegistryLogout(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
||||
@@ -43,20 +43,21 @@ type Helm interface {
|
||||
}
|
||||
|
||||
// NewHelmApp create a new wrapper to run commands on the `helm` command-line tool.
|
||||
func NewHelmApp(workDir string, repos []HelmRepository, isLocal bool, version string, proxy string, noProxy string, passCredentials bool) (Helm, error) {
|
||||
func NewHelmApp(workDir string, repos []HelmRepository, isLocal bool, version string, proxy string, noProxy string, passCredentials bool, insecure bool) (Helm, error) {
|
||||
cmd, err := NewCmd(workDir, version, proxy, noProxy)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create new helm command: %w", err)
|
||||
}
|
||||
cmd.IsLocal = isLocal
|
||||
|
||||
return &helm{repos: repos, cmd: *cmd, passCredentials: passCredentials}, nil
|
||||
return &helm{repos: repos, cmd: *cmd, passCredentials: passCredentials, insecure: insecure}, nil
|
||||
}
|
||||
|
||||
type helm struct {
|
||||
cmd Cmd
|
||||
repos []HelmRepository
|
||||
passCredentials bool
|
||||
insecure bool
|
||||
}
|
||||
|
||||
var _ Helm = &helm{}
|
||||
@@ -108,7 +109,7 @@ func (h *helm) DependencyBuild() error {
|
||||
}
|
||||
}
|
||||
h.repos = nil
|
||||
_, err := h.cmd.dependencyBuild()
|
||||
_, err := h.cmd.dependencyBuild(h.insecure)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to build helm dependencies: %w", err)
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ func template(h Helm, opts *TemplateOpts) ([]*unstructured.Unstructured, error)
|
||||
}
|
||||
|
||||
func TestHelmTemplateParams(t *testing.T) {
|
||||
h, err := NewHelmApp("./testdata/minio", []HelmRepository{}, false, "", "", "", false)
|
||||
h, err := NewHelmApp("./testdata/minio", []HelmRepository{}, false, "", "", "", false, false)
|
||||
require.NoError(t, err)
|
||||
opts := TemplateOpts{
|
||||
Name: "test",
|
||||
@@ -58,7 +58,7 @@ func TestHelmTemplateValues(t *testing.T) {
|
||||
repoRoot := "./testdata/redis"
|
||||
repoRootAbs, err := filepath.Abs(repoRoot)
|
||||
require.NoError(t, err)
|
||||
h, err := NewHelmApp(repoRootAbs, []HelmRepository{}, false, "", "", "", false)
|
||||
h, err := NewHelmApp(repoRootAbs, []HelmRepository{}, false, "", "", "", false, false)
|
||||
require.NoError(t, err)
|
||||
valuesPath, _, err := path.ResolveValueFilePathOrUrl(repoRootAbs, repoRootAbs, "values-production.yaml", nil)
|
||||
require.NoError(t, err)
|
||||
@@ -84,7 +84,7 @@ func TestHelmGetParams(t *testing.T) {
|
||||
repoRoot := "./testdata/redis"
|
||||
repoRootAbs, err := filepath.Abs(repoRoot)
|
||||
require.NoError(t, err)
|
||||
h, err := NewHelmApp(repoRootAbs, nil, false, "", "", "", false)
|
||||
h, err := NewHelmApp(repoRootAbs, nil, false, "", "", "", false, false)
|
||||
require.NoError(t, err)
|
||||
params, err := h.GetParameters(nil, repoRootAbs, repoRootAbs)
|
||||
require.NoError(t, err)
|
||||
@@ -97,7 +97,7 @@ func TestHelmGetParamsValueFiles(t *testing.T) {
|
||||
repoRoot := "./testdata/redis"
|
||||
repoRootAbs, err := filepath.Abs(repoRoot)
|
||||
require.NoError(t, err)
|
||||
h, err := NewHelmApp(repoRootAbs, nil, false, "", "", "", false)
|
||||
h, err := NewHelmApp(repoRootAbs, nil, false, "", "", "", false, false)
|
||||
require.NoError(t, err)
|
||||
valuesPath, _, err := path.ResolveValueFilePathOrUrl(repoRootAbs, repoRootAbs, "values-production.yaml", nil)
|
||||
require.NoError(t, err)
|
||||
@@ -112,7 +112,7 @@ func TestHelmGetParamsValueFilesThatExist(t *testing.T) {
|
||||
repoRoot := "./testdata/redis"
|
||||
repoRootAbs, err := filepath.Abs(repoRoot)
|
||||
require.NoError(t, err)
|
||||
h, err := NewHelmApp(repoRootAbs, nil, false, "", "", "", false)
|
||||
h, err := NewHelmApp(repoRootAbs, nil, false, "", "", "", false, false)
|
||||
require.NoError(t, err)
|
||||
valuesMissingPath, _, err := path.ResolveValueFilePathOrUrl(repoRootAbs, repoRootAbs, "values-missing.yaml", nil)
|
||||
require.NoError(t, err)
|
||||
@@ -126,7 +126,7 @@ func TestHelmGetParamsValueFilesThatExist(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestHelmTemplateReleaseNameOverwrite(t *testing.T) {
|
||||
h, err := NewHelmApp("./testdata/redis", nil, false, "", "", "", false)
|
||||
h, err := NewHelmApp("./testdata/redis", nil, false, "", "", "", false, false)
|
||||
require.NoError(t, err)
|
||||
|
||||
objs, err := template(h, &TemplateOpts{Name: "my-release"})
|
||||
@@ -144,7 +144,7 @@ func TestHelmTemplateReleaseNameOverwrite(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestHelmTemplateReleaseName(t *testing.T) {
|
||||
h, err := NewHelmApp("./testdata/redis", nil, false, "", "", "", false)
|
||||
h, err := NewHelmApp("./testdata/redis", nil, false, "", "", "", false, false)
|
||||
require.NoError(t, err)
|
||||
objs, err := template(h, &TemplateOpts{Name: "test"})
|
||||
require.NoError(t, err)
|
||||
@@ -206,7 +206,7 @@ func Test_flatVals(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAPIVersions(t *testing.T) {
|
||||
h, err := NewHelmApp("./testdata/api-versions", nil, false, "", "", "", false)
|
||||
h, err := NewHelmApp("./testdata/api-versions", nil, false, "", "", "", false, false)
|
||||
require.NoError(t, err)
|
||||
|
||||
objs, err := template(h, &TemplateOpts{})
|
||||
@@ -221,7 +221,7 @@ func TestAPIVersions(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestKubeVersionWithSymbol(t *testing.T) {
|
||||
h, err := NewHelmApp("./testdata/tests", nil, false, "", "", "", false)
|
||||
h, err := NewHelmApp("./testdata/tests", nil, false, "", "", "", false, false)
|
||||
require.NoError(t, err)
|
||||
|
||||
objs, err := template(h, &TemplateOpts{KubeVersion: "1.30.11+IKS"})
|
||||
@@ -244,7 +244,7 @@ func TestKubeVersionWithSymbol(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSkipCrds(t *testing.T) {
|
||||
h, err := NewHelmApp("./testdata/crds", nil, false, "", "", "", false)
|
||||
h, err := NewHelmApp("./testdata/crds", nil, false, "", "", "", false, false)
|
||||
require.NoError(t, err)
|
||||
|
||||
objs, err := template(h, &TemplateOpts{SkipCrds: false})
|
||||
@@ -261,7 +261,7 @@ func TestSkipCrds(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSkipTests(t *testing.T) {
|
||||
h, err := NewHelmApp("./testdata/tests", nil, false, "", "", "", false)
|
||||
h, err := NewHelmApp("./testdata/tests", nil, false, "", "", "", false, false)
|
||||
require.NoError(t, err)
|
||||
|
||||
objs, err := template(h, &TemplateOpts{SkipTests: false})
|
||||
|
||||
@@ -143,6 +143,7 @@ func NewClientWithLock(repoURL string, creds Creds, repoLock sync.KeyLock, proxy
|
||||
Proxy: proxy.GetCallback(proxyURL, noProxy),
|
||||
TLSClientConfig: tlsConf,
|
||||
DisableKeepAlives: true,
|
||||
ForceAttemptHTTP2: true,
|
||||
},
|
||||
/*
|
||||
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||
|
||||
@@ -5,16 +5,22 @@ import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"github.com/opencontainers/go-digest"
|
||||
"github.com/opencontainers/image-spec/specs-go"
|
||||
imagev1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"oras.land/oras-go/v2"
|
||||
"oras.land/oras-go/v2/content"
|
||||
@@ -761,6 +767,38 @@ func Test_nativeOCIClient_ResolveRevision(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewClientUsesHTTP2(t *testing.T) {
|
||||
t.Run("should negotiate HTTP/2 when TLS is configured", func(t *testing.T) {
|
||||
var requestProtos []string
|
||||
server := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
requestProtos = append(requestProtos, r.Proto)
|
||||
t.Logf("called %s with proto %s", r.URL.Path, r.Proto)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
// httptest.NewTLSServer only advertises http/1.1 in ALPN, so we must
|
||||
// configure the server to also offer h2 for HTTP/2 negotiation to work.
|
||||
server.TLS = &tls.Config{NextProtos: []string{"h2", "http/1.1"}}
|
||||
server.StartTLS()
|
||||
t.Cleanup(server.Close)
|
||||
|
||||
serverURL, err := url.Parse(server.URL)
|
||||
require.NoError(t, err)
|
||||
|
||||
// NewClient expects oci://host/path format.
|
||||
repoURL := "oci://" + serverURL.Host + "/myorg/myrepo"
|
||||
client, err := NewClient(repoURL, Creds{InsecureSkipVerify: true}, "", "", nil,
|
||||
WithEventHandlers(fakeEventHandlers(t, serverURL.Host+"/myorg/myrepo")))
|
||||
require.NoError(t, err)
|
||||
|
||||
// TestRepo pings the registry's /v2/ endpoint, exercising the transport.
|
||||
_, _ = client.TestRepo(t.Context())
|
||||
|
||||
require.NotEmpty(t, requestProtos, "expected at least one request to the server")
|
||||
hasHTTP2 := slices.Contains(requestProtos, "HTTP/2.0")
|
||||
assert.True(t, hasHTTP2, "expected at least one HTTP/2 request, but got protocols: %v", requestProtos)
|
||||
})
|
||||
}
|
||||
|
||||
func fakeEventHandlers(t *testing.T, repoURL string) EventHandlers {
|
||||
t.Helper()
|
||||
return EventHandlers{
|
||||
@@ -772,6 +810,9 @@ func fakeEventHandlers(t *testing.T, repoURL string) EventHandlers {
|
||||
OnGetTagsFail: func(repo string) func() {
|
||||
return func() { require.Equal(t, repoURL, repo) }
|
||||
},
|
||||
OnTestRepoFail: func(repo string) func() {
|
||||
return func() { require.Equal(t, repoURL, repo) }
|
||||
},
|
||||
OnExtractFail: func(repo string) func(revision string) {
|
||||
return func(_ string) { require.Equal(t, repoURL, repo) }
|
||||
},
|
||||
|
||||
@@ -1069,3 +1069,7 @@ func FormatAccessTokenCacheKey(sub string) string {
|
||||
func formatOidcTokenCacheKey(sub string, sid string) string {
|
||||
return fmt.Sprintf("%s_%s_%s", OidcTokenCachePrefix, sub, sid)
|
||||
}
|
||||
|
||||
func (a *ClientApp) IssuerURL() string {
|
||||
return a.issuerURL
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user