Compare commits

..

9 Commits

Author SHA1 Message Date
argo-cd-cherry-pick-bot[bot]
8550f60a05 fix: force attempt http2 with custom tls config (#26975) (cherry-pick #26976 for 3.4) (#27073)
Signed-off-by: Max Verbeek <m4xv3rb33k@gmail.com>
Co-authored-by: Max Verbeek <m4xv3rb33k@gmail.com>
2026-03-30 05:37:06 -10:00
github-actions[bot]
d29ec76295 Bump version to 3.4.0-rc4 on release-3.4 branch (#27046)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: crenshaw-dev <350466+crenshaw-dev@users.noreply.github.com>
2026-03-27 10:07:01 -04:00
argo-cd-cherry-pick-bot[bot]
249b91d75b fix: wrong installation id returned from cache (cherry-pick #26969 for 3.4) (#27028)
Signed-off-by: Zach Aller <zach_aller@intuit.com>
Co-authored-by: Zach Aller <zachaller@users.noreply.github.com>
2026-03-27 09:24:20 -04:00
argo-cd-cherry-pick-bot[bot]
ed4c63ba83 fix: controller incorrectly detecting diff during app normalization (cherry-pick #27002 for 3.4) (#27014)
Signed-off-by: Alexander Matyushentsev <alexander@akuity.io>
Co-authored-by: Alexander Matyushentsev <AMatyushentsev@gmail.com>
2026-03-25 14:13:02 -07:00
github-actions[bot]
cbdc3f1397 Bump version to 3.4.0-rc3 on release-3.4 branch (#27006)
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: reggie-k <19544836+reggie-k@users.noreply.github.com>
2026-03-25 15:46:13 +02:00
argo-cd-cherry-pick-bot[bot]
b66dea4282 fix: Hook resources not created at PostSync when configured with PreDelete PostDelete hooks (cherry-pick #26996 for 3.4) (#26998)
Signed-off-by: reggie-k <regina.voloshin@codefresh.io>
Co-authored-by: Regina Voloshin <regina.voloshin@codefresh.io>
2026-03-25 13:23:57 +02:00
argo-cd-cherry-pick-bot[bot]
aced2b1b36 fix(ui): Improve message on self-healing disabling panel (#26977) (cherry-pick #26978 for 3.4) (#26980)
Signed-off-by: Alberto Chiusole <chiusole@seqera.io>
Co-authored-by: Alberto Chiusole <1922124+bebosudo@users.noreply.github.com>
2026-03-24 17:57:32 +02:00
argo-cd-cherry-pick-bot[bot]
ea71adbae5 chore(deps): bump google.golang.org/grpc from 1.79.2 to 1.79.3 (cherry-pick #26886 for 3.4) (#26952)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Blake Pettersson <blake.pettersson@gmail.com>
2026-03-22 15:46:39 +02:00
argo-cd-cherry-pick-bot[bot]
5ed403cf60 fix(server): fix find container logic for terminal (cherry-pick #26858 for 3.4) (#26933)
Signed-off-by: linghaoSu <linghao.su@daocloud.io>
Co-authored-by: Linghao Su <linghao.su@daocloud.io>
2026-03-20 13:00:53 +01:00
31 changed files with 485 additions and 126 deletions

View File

@@ -1 +1 @@
3.4.0-rc2
3.4.0-rc4

View File

@@ -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))

View File

@@ -76,6 +76,21 @@ func isPostDeleteHook(obj *unstructured.Unstructured) bool {
return isHookOfType(obj, PostDeleteHookType)
}
// hasGitOpsEngineSyncPhaseHook is true when gitops-engine would run the resource during a sync
// phase (PreSync, Sync, PostSync, SyncFail). PreDelete/PostDelete are not sync phases;
// without this check, state reconciliation drops such resources
// entirely because isPreDeleteHook/isPostDeleteHook match any comma-separated value.
// HookTypeSkip is omitted as it is not a sync phase.
func hasGitOpsEngineSyncPhaseHook(obj *unstructured.Unstructured) bool {
for _, t := range hook.Types(obj) {
switch t {
case common.HookTypePreSync, common.HookTypeSync, common.HookTypePostSync, common.HookTypeSyncFail:
return true
}
}
return false
}
// executeHooks is a generic function to execute hooks of a specified type
func (ctrl *ApplicationController) executeHooks(hookType HookType, app *appv1.Application, proj *appv1.AppProject, liveObjs map[kube.ResourceKey]*unstructured.Unstructured, config *rest.Config, logCtx *log.Entry) (bool, error) {
appLabelKey, err := ctrl.settingsMgr.GetAppInstanceLabelKey()

View File

@@ -192,6 +192,92 @@ func TestIsPostDeleteHook(t *testing.T) {
}
}
// TestPartitionTargetObjsForSync covers partitionTargetObjsForSync in state.go.
func TestPartitionTargetObjsForSync(t *testing.T) {
newObj := func(name string, annot map[string]string) *unstructured.Unstructured {
u := &unstructured.Unstructured{}
u.SetName(name)
u.SetAnnotations(annot)
return u
}
tests := []struct {
name string
in []*unstructured.Unstructured
wantNames []string
wantPreDelete bool
wantPostDelete bool
}{
{
name: "PostSync with PreDelete and PostDelete in same annotation stays in sync set",
in: []*unstructured.Unstructured{
newObj("combined", map[string]string{"argocd.argoproj.io/hook": "PostSync,PreDelete,PostDelete"}),
},
wantNames: []string{"combined"},
wantPreDelete: true,
wantPostDelete: true,
},
{
name: "PreDelete-only manifest excluded from sync",
in: []*unstructured.Unstructured{
newObj("pre-del", map[string]string{"argocd.argoproj.io/hook": "PreDelete"}),
},
wantNames: nil,
wantPreDelete: true,
wantPostDelete: false,
},
{
name: "PostDelete-only manifest excluded from sync",
in: []*unstructured.Unstructured{
newObj("post-del", map[string]string{"argocd.argoproj.io/hook": "PostDelete"}),
},
wantNames: nil,
wantPreDelete: false,
wantPostDelete: true,
},
{
name: "Helm pre-delete only excluded from sync",
in: []*unstructured.Unstructured{
newObj("helm-pre-del", map[string]string{"helm.sh/hook": "pre-delete"}),
},
wantNames: nil,
wantPreDelete: true,
wantPostDelete: false,
},
{
name: "Helm pre-install with pre-delete stays in sync (sync-phase hook wins)",
in: []*unstructured.Unstructured{
newObj("helm-mixed", map[string]string{"helm.sh/hook": "pre-install,pre-delete"}),
},
wantNames: []string{"helm-mixed"},
wantPreDelete: true,
wantPostDelete: false,
},
{
name: "Non-hook resource unchanged",
in: []*unstructured.Unstructured{
newObj("pod", map[string]string{"app": "x"}),
},
wantNames: []string{"pod"},
wantPreDelete: false,
wantPostDelete: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, hasPre, hasPost := partitionTargetObjsForSync(tt.in)
var names []string
for _, o := range got {
names = append(names, o.GetName())
}
assert.Equal(t, tt.wantNames, names)
assert.Equal(t, tt.wantPreDelete, hasPre, "hasPreDeleteHooks")
assert.Equal(t, tt.wantPostDelete, hasPost, "hasPostDeleteHooks")
})
}
}
func TestMultiHookOfType(t *testing.T) {
tests := []struct {
name string

View File

@@ -543,6 +543,28 @@ func isManagedNamespace(ns *unstructured.Unstructured, app *v1alpha1.Application
return ns != nil && ns.GetKind() == kubeutil.NamespaceKind && ns.GetName() == app.Spec.Destination.Namespace && app.Spec.SyncPolicy != nil && app.Spec.SyncPolicy.ManagedNamespaceMetadata != nil
}
// partitionTargetObjsForSync returns the manifest subset passed to gitops-engine sync, and whether
// the full manifest set declared PreDelete and/or PostDelete hooks (for finalizer handling).
// Uses isPreDeleteHook / isPostDeleteHook / hasGitOpsEngineSyncPhaseHook from hook.go.
func partitionTargetObjsForSync(targetObjs []*unstructured.Unstructured) (syncObjs []*unstructured.Unstructured, hasPreDeleteHooks, hasPostDeleteHooks bool) {
for _, obj := range targetObjs {
if isPreDeleteHook(obj) {
hasPreDeleteHooks = true
if !hasGitOpsEngineSyncPhaseHook(obj) {
continue
}
}
if isPostDeleteHook(obj) {
hasPostDeleteHooks = true
if !hasGitOpsEngineSyncPhaseHook(obj) {
continue
}
}
syncObjs = append(syncObjs, obj)
}
return syncObjs, hasPreDeleteHooks, hasPostDeleteHooks
}
// CompareAppState compares application git state to the live app state, using the specified
// revision and supplied source. If revision or overrides are empty, then compares against
// revision and overrides in the app spec.
@@ -770,24 +792,7 @@ func (m *appStateManager) CompareAppState(app *v1alpha1.Application, project *v1
}
}
}
hasPreDeleteHooks := false
hasPostDeleteHooks := false
// Filter out PreDelete and PostDelete hooks from targetObjs since they should not be synced
// as regular resources. They are only executed during deletion.
var targetObjsForSync []*unstructured.Unstructured
for _, obj := range targetObjs {
if isPreDeleteHook(obj) {
hasPreDeleteHooks = true
// Skip PreDelete hooks - they are not synced, only executed during deletion
continue
}
if isPostDeleteHook(obj) {
hasPostDeleteHooks = true
// Skip PostDelete hooks - they are not synced, only executed after deletion
continue
}
targetObjsForSync = append(targetObjsForSync, obj)
}
targetObjsForSync, hasPreDeleteHooks, hasPostDeleteHooks := partitionTargetObjsForSync(targetObjs)
reconciliation := sync.Reconcile(targetObjsForSync, liveObjByKey, app.Spec.Destination.Namespace, infoProvider)
ts.AddCheckpoint("live_ms")

View File

@@ -1,5 +1,5 @@
| Argo CD version | Kubernetes versions |
|-----------------|---------------------|
| 3.4 | v1.35, v1.34, v1.33, v1.32 |
| 3.3 | v1.34, v1.33, v1.32, v1.31 |
| 3.3 | v1.35, v1.34, v1.33, v1.32 |
| 3.2 | v1.34, v1.33, v1.32, v1.31 |

2
go.mod
View File

@@ -102,7 +102,7 @@ require (
golang.org/x/term v0.41.0
golang.org/x/time v0.15.0
google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57
google.golang.org/grpc v1.79.2
google.golang.org/grpc v1.79.3
google.golang.org/protobuf v1.36.11
gopkg.in/yaml.v2 v2.4.0
gopkg.in/yaml.v3 v3.0.1

4
go.sum
View File

@@ -1404,8 +1404,8 @@ google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
google.golang.org/grpc v1.79.2 h1:fRMD94s2tITpyJGtBBn7MkMseNpOZU8ZxgC3MMBaXRU=
google.golang.org/grpc v1.79.2/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=

View File

@@ -12,4 +12,4 @@ resources:
images:
- name: quay.io/argoproj/argocd
newName: quay.io/argoproj/argocd
newTag: v3.4.0-rc2
newTag: v3.4.0-rc4

View File

@@ -5,7 +5,7 @@ kind: Kustomization
images:
- name: quay.io/argoproj/argocd
newName: quay.io/argoproj/argocd
newTag: v3.4.0-rc2
newTag: v3.4.0-rc4
resources:
- ./application-controller
- ./dex

View File

@@ -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-rc2
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-rc2
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-rc2
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-rc2
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-rc2
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-rc2
image: quay.io/argoproj/argocd:v3.4.0-rc4
imagePullPolicy: Always
name: argocd-application-controller
ports:

View File

@@ -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-rc2
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-rc2
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-rc2
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-rc2
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-rc2
image: quay.io/argoproj/argocd:v3.4.0-rc4
imagePullPolicy: Always
name: argocd-application-controller
ports:

View File

@@ -12,4 +12,4 @@ resources:
images:
- name: quay.io/argoproj/argocd
newName: quay.io/argoproj/argocd
newTag: v3.4.0-rc2
newTag: v3.4.0-rc4

View File

@@ -12,7 +12,7 @@ patches:
images:
- name: quay.io/argoproj/argocd
newName: quay.io/argoproj/argocd
newTag: v3.4.0-rc2
newTag: v3.4.0-rc4
resources:
- ../../base/application-controller
- ../../base/applicationset-controller

View File

@@ -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-rc2
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-rc2
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-rc2
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-rc2
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-rc2
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-rc2
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-rc2
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-rc2
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-rc2
image: quay.io/argoproj/argocd:v3.4.0-rc4
imagePullPolicy: Always
name: argocd-application-controller
ports:

View File

@@ -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-rc2
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-rc2
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-rc2
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-rc2
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-rc2
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-rc2
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-rc2
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-rc2
image: quay.io/argoproj/argocd:v3.4.0-rc4
imagePullPolicy: Always
name: argocd-application-controller
ports:

View File

@@ -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-rc2
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-rc2
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-rc2
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-rc2
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-rc2
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-rc2
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-rc2
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-rc2
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-rc2
image: quay.io/argoproj/argocd:v3.4.0-rc4
imagePullPolicy: Always
name: argocd-application-controller
ports:

View File

@@ -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-rc2
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-rc2
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-rc2
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-rc2
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-rc2
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-rc2
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-rc2
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-rc2
image: quay.io/argoproj/argocd:v3.4.0-rc4
imagePullPolicy: Always
name: argocd-application-controller
ports:

View File

@@ -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-rc2
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-rc2
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-rc2
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-rc2
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-rc2
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-rc2
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-rc2
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-rc2
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-rc2
image: quay.io/argoproj/argocd:v3.4.0-rc4
imagePullPolicy: Always
name: argocd-application-controller
ports:

16
manifests/install.yaml generated
View File

@@ -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-rc2
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-rc2
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-rc2
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-rc2
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-rc2
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-rc2
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-rc2
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-rc2
image: quay.io/argoproj/argocd:v3.4.0-rc4
imagePullPolicy: Always
name: argocd-application-controller
ports:

View File

@@ -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-rc2
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-rc2
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-rc2
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-rc2
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-rc2
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-rc2
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-rc2
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-rc2
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-rc2
image: quay.io/argoproj/argocd:v3.4.0-rc4
imagePullPolicy: Always
name: argocd-application-controller
ports:

View File

@@ -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-rc2
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-rc2
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-rc2
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-rc2
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-rc2
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-rc2
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-rc2
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-rc2
image: quay.io/argoproj/argocd:v3.4.0-rc4
imagePullPolicy: Always
name: argocd-application-controller
ports:

View File

@@ -210,21 +210,9 @@ func (s *terminalHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return
}
if pod.Status.Phase != corev1.PodRunning {
http.Error(w, "Pod not running", http.StatusBadRequest)
return
}
var findContainer bool
for _, c := range pod.Spec.Containers {
if container == c.Name {
findContainer = true
break
}
}
if !findContainer {
fieldLog.Warn("terminal container not found")
http.Error(w, "Cannot find container", http.StatusBadRequest)
if !containerRunning(pod, container) {
fieldLog.Warn("terminal container not running")
http.Error(w, "container find running", http.StatusBadRequest)
return
}
@@ -273,6 +261,20 @@ func podExists(treeNodes []appv1.ResourceNode, podName, namespace string) bool {
return false
}
func containerRunning(pod *corev1.Pod, containerName string) bool {
return containerStatusRunning(pod.Status.ContainerStatuses, containerName) ||
containerStatusRunning(pod.Status.InitContainerStatuses, containerName)
}
func containerStatusRunning(statuses []corev1.ContainerStatus, containerName string) bool {
for i := range statuses {
if statuses[i].Name == containerName {
return statuses[i].State.Running != nil
}
}
return false
}
const EndOfTransmission = "\u0004"
// PtyHandler is what remotecommand expects from a pty

View File

@@ -5,9 +5,12 @@ import (
"net/http/httptest"
"strings"
"testing"
"time"
"github.com/argoproj/argo-cd/gitops-engine/pkg/utils/kube"
"github.com/stretchr/testify/assert"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
appv1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/v3/util/argo"
@@ -79,6 +82,115 @@ func TestPodExists(t *testing.T) {
}
}
func TestContainerRunning(t *testing.T) {
for _, tcase := range []struct {
name string
pod *corev1.Pod
containerName string
expectedResult bool
}{
{
name: "empty container",
pod: &corev1.Pod{},
containerName: "",
expectedResult: false,
},
{
name: "container not found",
pod: &corev1.Pod{},
containerName: "not-found",
expectedResult: false,
},
{
name: "container running",
pod: &corev1.Pod{
Status: corev1.PodStatus{
ContainerStatuses: []corev1.ContainerStatus{
{
Name: "test",
State: corev1.ContainerState{
Running: &corev1.ContainerStateRunning{
StartedAt: metav1.NewTime(time.Now()),
},
},
},
},
},
},
containerName: "test",
expectedResult: true,
},
{
name: "init container running",
pod: &corev1.Pod{
Status: corev1.PodStatus{
ContainerStatuses: []corev1.ContainerStatus{
{
Name: "test",
State: corev1.ContainerState{
Running: &corev1.ContainerStateRunning{
StartedAt: metav1.NewTime(time.Now()),
},
},
},
},
InitContainerStatuses: []corev1.ContainerStatus{
{
Name: "test-init",
State: corev1.ContainerState{
Running: &corev1.ContainerStateRunning{
StartedAt: metav1.NewTime(time.Now()),
},
},
},
},
},
},
containerName: "test-init",
expectedResult: true,
},
{
name: "container not running",
pod: &corev1.Pod{
Status: corev1.PodStatus{
ContainerStatuses: []corev1.ContainerStatus{
{
Name: "test",
State: corev1.ContainerState{
Running: nil,
},
},
},
},
},
containerName: "test",
expectedResult: false,
},
{
name: "init container not running",
pod: &corev1.Pod{
Status: corev1.PodStatus{
InitContainerStatuses: []corev1.ContainerStatus{
{
Name: "test-init",
State: corev1.ContainerState{
Running: nil,
},
},
},
},
},
containerName: "test-init",
expectedResult: false,
},
} {
t.Run(tcase.name, func(t *testing.T) {
result := containerRunning(tcase.pod, tcase.containerName)
assert.Equalf(t, tcase.expectedResult, result, "Expected result %v, but got %v", tcase.expectedResult, result)
})
}
}
func TestIsValidPodName(t *testing.T) {
for _, tcase := range []struct {
name string

View File

@@ -721,7 +721,7 @@ export const ApplicationSummary = (props: ApplicationSummaryProps) => {
selfHeal ? 'Enable Self Heal?' : 'Disable Self Heal?',
selfHeal
? 'If checked, application will automatically sync when changes are detected'
: 'Are you sure you want to enable automated self healing?',
: 'If unchecked, application will not automatically sync when changes are detected',
automated.prune,
selfHeal,
automated.enabled

View File

@@ -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)
}
}

View File

@@ -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) {

View File

@@ -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

View File

@@ -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

View File

@@ -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 {

View File

@@ -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) }
},