Compare commits

...

20 Commits

Author SHA1 Message Date
argo-bot
a48bca03c7 Bump version to 2.4.6 2022-07-12 22:19:23 +00:00
argo-bot
6930ceb414 Bump version to 2.4.6 2022-07-12 22:19:14 +00:00
CI
01b7a73922 chore: fix build error
Signed-off-by: CI <michael@crenshaw.dev>
2022-07-12 16:46:39 -04:00
taksenov
ff2d9b918d docs: fix typo in Generators-Git.md (#9949)
`ApplictionSet` --> `ApplicationSet`
Signed-off-by: CI <michael@crenshaw.dev>
2022-07-12 16:29:03 -04:00
Jake
cea91ce935 docs: add terminal documentation (#9948)
Signed-off-by: notfromstatefarm <86763948+notfromstatefarm@users.noreply.github.com>
2022-07-12 16:28:24 -04:00
Michael Crenshaw
51b73096f5 fix: 'unexpected reserved bits' breaking web terminal (#9605) (#9895)
* fix: 'unexpected reserved bits' breaking web terminal (#9605)

Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>

* make things more like they were originally, since the mutex fixes the problem

Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>

* fix typo, don't pass around a pointer when it isn't necessary

Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>

* apply suggestions

Signed-off-by: Michael Crenshaw <michael@crenshaw.dev>
2022-07-12 16:28:01 -04:00
Yuan Tang
2c166dac97 feat: Treat connection reset as a retryable error (#9739)
Signed-off-by: Yuan Tang <terrytangyuan@gmail.com>
2022-07-12 16:25:08 -04:00
jannfis
16c015f0cb test: Use dedicated multi-arch workloads in e2e tests (#9921)
* test: Use dedicated multi-arch workloads in e2e tests

Signed-off-by: jannfis <jann@mistrust.net>

* Use correct tag

Signed-off-by: jannfis <jann@mistrust.net>
2022-07-12 16:24:41 -04:00
Xiao Yang
7ca60cb957 fix: argocd login just hangs on 2.4.0 #9679 (#9935)
Signed-off-by: Xiao Yang <muma.378@163.com>

Co-authored-by: Michael Crenshaw <michael@crenshaw.dev>
Signed-off-by: CI <michael@crenshaw.dev>
2022-07-12 16:24:23 -04:00
Jake
d0c80ee0bb fix: CMP manifest generation fails with ENHANCE_YOUR_CALM if over 40s (#9922)
* fix: CMP manifest generation fails with ENHANCE_YOUR_CALM if over 40s

Signed-off-by: notfromstatefarm <86763948+notfromstatefarm@users.noreply.github.com>

* fix timeouts across all gRPC servers

Signed-off-by: notfromstatefarm <86763948+notfromstatefarm@users.noreply.github.com>

* use common consts

Signed-off-by: notfromstatefarm <86763948+notfromstatefarm@users.noreply.github.com>
2022-07-12 16:24:00 -04:00
yongguangl
9f1a33865a fix: NotAfter is not set when ValidFor is set (#9911)
Signed-off-by: yongguangl <1363186473@qq.com>
2022-07-12 16:21:21 -04:00
Hyeonmin Park
f09c84f30a fix: add missing download CLI tool link for ppc64le, s390x (#9649)
Signed-off-by: Hyeonmin Park <hyeonmin.park@kennysoft.kr>
2022-07-12 16:21:03 -04:00
jannfis
f7f7493e9f fix: Check tracking annotation for being self-referencing (#9791)
* fix: Check tracking annotation for being self-referencing

Signed-off-by: jannfis <jann@mistrust.net>

* Tweak isManagedLiveObj() logic

Signed-off-by: jannfis <jann@mistrust.net>

* Rename isManagedLiveResource to isSelfReferencedObj

Signed-off-by: jannfis <jann@mistrust.net>

* Add e2e test

Signed-off-by: jannfis <jann@mistrust.net>
2022-07-12 16:20:21 -04:00
jannfis
c1ddb53d29 fix: Make change of tracking method work at runtime (#9820)
* fix: Make change of tracking method work at runtime

Signed-off-by: jannfis <jann@mistrust.net>

* GetAppName() will figure tracking label or annotation on its own

Signed-off-by: jannfis <jann@mistrust.net>

* Correct test comments and add another test

Signed-off-by: jannfis <jann@mistrust.net>

* Add a read lock before getting cache settings

Signed-off-by: jannfis <jann@mistrust.net>
2022-07-12 16:19:41 -04:00
Shunki
e04288482d fix: argo-cd git submodule is using SSH auth instead of HTTPs (#3118) (#9821)
* fix: argo-cd git submodule is using SSH auth instead of HTTPs (#3118)

Signed-off-by: shunki-fujita <shunki-fujita@cybozu.co.jp>

* Add submodule functions and unit tests
Signed-off-by: shunki-fujita <shunki-fujita@cybozu.co.jp>
2022-07-12 16:16:48 -04:00
YaytayAtWork
1f3e1ec803 #9429: Adding blank line so list is formatted correctly. (#9880)
Signed-off-by: CI <michael@crenshaw.dev>
2022-07-12 16:14:05 -04:00
Jake
c3423e8df3 docs: small fix for plugin stream filtering (#9871)
Signed-off-by: notfromstatefarm <86763948+notfromstatefarm@users.noreply.github.com>
2022-07-12 16:13:45 -04:00
Didrik Finnøy
5ef48c1123 docs: Document the possibility of rendering Helm charts with Kustomize (#9841)
* Update kustomize.md

Resolves  #7835.

Signed-off-by: Didrik Finnøy <djfinnoy@protonmail.com>

* Removed unnecessary command flag from example. Minor text edits.

Signed-off-by: Didrik Finnøy <djfinnoy@protonmail.com>

* spelling

Signed-off-by: Didrik Finnøy <djfinnoy@protonmail.com>
2022-07-12 16:13:22 -04:00
YaytayAtWork
e3ae286e2d docs: getting started notes on self-signed cert (#9429) (#9784)
* Fix #9429: A couple of notes in the docs to explain that the default certificate is insecure.

Signed-off-by: Jim Talbut <jim.talbut@groupgti.com>

* Fixes #9429: More verbose, but complete, text for Getting Started.

Signed-off-by: Jim Talbut <jim.talbut@groupgti.com>
2022-07-12 16:12:41 -04:00
Michael Crenshaw
7abf6713b4 test: check for error messages from CI env (#9953)
test: check for error messages from CI env (#9953)

Signed-off-by: CI <michael@crenshaw.dev>
2022-07-12 15:31:44 -04:00
72 changed files with 922 additions and 139 deletions

View File

@@ -1 +1 @@
2.4.5
2.4.6

View File

@@ -68,7 +68,8 @@ argocd login cd.argoproj.io --core`,
server = "kubernetes"
} else {
server = args[0]
tlsTestResult, err := grpc_util.TestTLS(server)
dialTime := 30 * time.Second
tlsTestResult, err := grpc_util.TestTLS(server, dialTime)
errors.CheckError(err)
if !tlsTestResult.TLS {
if !globalClientOpts.PlainText {

View File

@@ -2,12 +2,13 @@ package cmpserver
import (
"fmt"
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
"net"
"os"
"os/signal"
"syscall"
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
grpc_logrus "github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus"
grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
@@ -24,6 +25,7 @@ import (
"github.com/argoproj/argo-cd/v2/server/version"
"github.com/argoproj/argo-cd/v2/util/errors"
grpc_util "github.com/argoproj/argo-cd/v2/util/grpc"
"google.golang.org/grpc/keepalive"
)
// ArgoCDCMPServer is the config management plugin server implementation
@@ -61,6 +63,11 @@ func NewServer(initConstants plugin.CMPServerInitConstants) (*ArgoCDCMPServer, e
grpc.StreamInterceptor(grpc_middleware.ChainStreamServer(streamInterceptors...)),
grpc.MaxRecvMsgSize(apiclient.MaxGRPCMessageSize),
grpc.MaxSendMsgSize(apiclient.MaxGRPCMessageSize),
grpc.KeepaliveEnforcementPolicy(
keepalive.EnforcementPolicy{
MinTime: common.GRPCKeepAliveEnforcementMinimum,
},
),
}
return &ArgoCDCMPServer{

View File

@@ -286,3 +286,10 @@ const (
// AnnotationApplicationRefresh is an annotation that is added when an ApplicationSet is requested to be refreshed by a webhook. The ApplicationSet controller will remove this annotation at the end of reconcilation.
AnnotationApplicationSetRefresh = "argocd.argoproj.io/application-set-refresh"
)
// gRPC settings
const (
GRPCKeepAliveEnforcementMinimum = 10 * time.Second
// Keep alive is 2x enforcement minimum to ensure network jitter does not introduce ENHANCE_YOUR_CALM errors
GRPCKeepAliveTime = 2 * GRPCKeepAliveEnforcementMinimum
)

View File

@@ -176,6 +176,7 @@ func NewLiveStateCache(
type cacheSettings struct {
clusterSettings clustercache.Settings
appInstanceLabelKey string
trackingMethod appv1.TrackingMethod
}
type liveStateCache struct {
@@ -210,7 +211,7 @@ func (c *liveStateCache) loadCacheSettings() (*cacheSettings, error) {
ResourceHealthOverride: lua.ResourceHealthOverrides(resourceOverrides),
ResourcesFilter: resourcesFilter,
}
return &cacheSettings{clusterSettings, appInstanceLabelKey}, nil
return &cacheSettings{clusterSettings, appInstanceLabelKey, argo.GetTrackingMethod(c.settingsMgr)}, nil
}
func asResourceNode(r *clustercache.Resource) appv1.ResourceNode {
@@ -354,7 +355,8 @@ func isTransientNetworkErr(err error) bool {
}
if strings.Contains(errorString, "net/http: TLS handshake timeout") ||
strings.Contains(errorString, "i/o timeout") ||
strings.Contains(errorString, "connection timed out") {
strings.Contains(errorString, "connection timed out") ||
strings.Contains(errorString, "connection reset by peer") {
return true
}
return false
@@ -387,7 +389,6 @@ func (c *liveStateCache) getCluster(server string) (clustercache.ClusterCache, e
return nil, fmt.Errorf("controller is configured to ignore cluster %s", cluster.Server)
}
trackingMethod := argo.GetTrackingMethod(c.settingsMgr)
clusterCacheOpts := []clustercache.UpdateSettingsFunc{
clustercache.SetListSemaphore(semaphore.NewWeighted(clusterCacheListSemaphoreSize)),
clustercache.SetListPageSize(clusterCacheListPageSize),
@@ -400,9 +401,12 @@ func (c *liveStateCache) getCluster(server string) (clustercache.ClusterCache, e
clustercache.SetPopulateResourceInfoHandler(func(un *unstructured.Unstructured, isRoot bool) (interface{}, bool) {
res := &ResourceInfo{}
populateNodeInfo(un, res)
c.lock.RLock()
cacheSettings := c.cacheSettings
c.lock.RUnlock()
res.Health, _ = health.GetResourceHealth(un, cacheSettings.clusterSettings.ResourceHealthOverride)
appName := c.resourceTracking.GetAppName(un, cacheSettings.appInstanceLabelKey, trackingMethod)
appName := c.resourceTracking.GetAppName(un, cacheSettings.appInstanceLabelKey, cacheSettings.trackingMethod)
if isRoot && appName != "" {
res.AppName = appName
}

View File

@@ -111,6 +111,7 @@ func TestIsRetryableError(t *testing.T) {
tlsHandshakeTimeoutErr net.Error = netError("net/http: TLS handshake timeout")
ioTimeoutErr net.Error = netError("i/o timeout")
connectionTimedout net.Error = netError("connection timed out")
connectionReset net.Error = netError("connection reset by peer")
)
t.Run("Nil", func(t *testing.T) {
assert.False(t, isRetryableError(nil))
@@ -148,4 +149,7 @@ func TestIsRetryableError(t *testing.T) {
t.Run("ConnectionTimeout", func(t *testing.T) {
assert.True(t, isRetryableError(connectionTimedout))
})
t.Run("ConnectionReset", func(t *testing.T) {
assert.True(t, isRetryableError(connectionReset))
})
}

View File

@@ -494,6 +494,8 @@ func (m *appStateManager) CompareAppState(app *v1alpha1.Application, project *ap
}
gvk := obj.GroupVersionKind()
isSelfReferencedObj := m.isSelfReferencedObj(liveObj, appLabelKey, trackingMethod)
resState := v1alpha1.ResourceStatus{
Namespace: obj.GetNamespace(),
Name: obj.GetName(),
@@ -501,7 +503,7 @@ func (m *appStateManager) CompareAppState(app *v1alpha1.Application, project *ap
Version: gvk.Version,
Group: gvk.Group,
Hook: hookutil.IsHook(obj),
RequiresPruning: targetObj == nil && liveObj != nil,
RequiresPruning: targetObj == nil && liveObj != nil && isSelfReferencedObj,
}
var diffResult diff.DiffResult
@@ -510,8 +512,11 @@ func (m *appStateManager) CompareAppState(app *v1alpha1.Application, project *ap
} else {
diffResult = diff.DiffResult{Modified: false, NormalizedLive: []byte("{}"), PredictedLive: []byte("{}")}
}
if resState.Hook || ignore.Ignore(obj) || (targetObj != nil && hookutil.Skip(targetObj)) {
// For resource hooks or skipped resources, don't store sync status, and do not affect overall sync status
if resState.Hook || ignore.Ignore(obj) || (targetObj != nil && hookutil.Skip(targetObj)) || !isSelfReferencedObj {
// For resource hooks, skipped resources or objects that may have
// been created by another controller with annotations copied from
// the source object, don't store sync status, and do not affect
// overall sync status
} else if diffResult.Modified || targetObj == nil || liveObj == nil {
// Set resource state to OutOfSync since one of the following is true:
// * target and live resource are different
@@ -667,3 +672,33 @@ func NewAppStateManager(
resourceTracking: resourceTracking,
}
}
// isSelfReferencedObj returns whether the given obj is managed by the application
// according to the values in the tracking annotation. It returns true when all
// of the properties in the annotation (name, namespace, group and kind) match
// the properties of the inspected object, or if the tracking method used does
// not provide the required properties for matching.
func (m *appStateManager) isSelfReferencedObj(obj *unstructured.Unstructured, appLabelKey string, trackingMethod v1alpha1.TrackingMethod) bool {
if obj == nil {
return true
}
// If tracking method doesn't contain required metadata for this check,
// we are not able to determine and just assume the object to be managed.
if trackingMethod == argo.TrackingMethodLabel {
return true
}
// In order for us to assume obj to be managed by this application, the
// values from the annotation have to match the properties from the live
// object.
appInstance := m.resourceTracking.GetAppInstance(obj, appLabelKey, trackingMethod)
if appInstance != nil {
return obj.GetNamespace() == appInstance.Namespace &&
obj.GetName() == appInstance.Name &&
obj.GetObjectKind().GroupVersionKind().Group == appInstance.Group &&
obj.GetObjectKind().GroupVersionKind().Kind == appInstance.Kind
}
return true
}

View File

@@ -13,6 +13,7 @@ import (
. "github.com/argoproj/gitops-engine/pkg/utils/testing"
"github.com/stretchr/testify/assert"
v1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
@@ -21,6 +22,7 @@ import (
argoappv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/v2/reposerver/apiclient"
"github.com/argoproj/argo-cd/v2/test"
"github.com/argoproj/argo-cd/v2/util/argo"
)
// TestCompareAppStateEmpty tests comparison when both git and live have no objects
@@ -770,3 +772,128 @@ func TestComparisonResult_GetSyncStatus(t *testing.T) {
assert.Equal(t, status, res.GetSyncStatus())
}
func TestIsLiveResourceManaged(t *testing.T) {
managedObj := kube.MustToUnstructured(&corev1.ConfigMap{
TypeMeta: metav1.TypeMeta{
APIVersion: "v1",
Kind: "ConfigMap",
},
ObjectMeta: metav1.ObjectMeta{
Name: "configmap1",
Namespace: "default",
Annotations: map[string]string{
common.AnnotationKeyAppInstance: "guestbook:/ConfigMap:default/configmap1",
},
},
})
managedObjWithLabel := kube.MustToUnstructured(&corev1.ConfigMap{
TypeMeta: metav1.TypeMeta{
APIVersion: "v1",
Kind: "ConfigMap",
},
ObjectMeta: metav1.ObjectMeta{
Name: "configmap1",
Namespace: "default",
Labels: map[string]string{
common.LabelKeyAppInstance: "guestbook",
},
},
})
unmanagedObjWrongName := kube.MustToUnstructured(&corev1.ConfigMap{
TypeMeta: metav1.TypeMeta{
APIVersion: "v1",
Kind: "ConfigMap",
},
ObjectMeta: metav1.ObjectMeta{
Name: "configmap2",
Namespace: "default",
Annotations: map[string]string{
common.AnnotationKeyAppInstance: "guestbook:/ConfigMap:default/configmap1",
},
},
})
unmanagedObjWrongKind := kube.MustToUnstructured(&corev1.ConfigMap{
TypeMeta: metav1.TypeMeta{
APIVersion: "v1",
Kind: "ConfigMap",
},
ObjectMeta: metav1.ObjectMeta{
Name: "configmap2",
Namespace: "default",
Annotations: map[string]string{
common.AnnotationKeyAppInstance: "guestbook:/Service:default/configmap2",
},
},
})
unmanagedObjWrongGroup := kube.MustToUnstructured(&corev1.ConfigMap{
TypeMeta: metav1.TypeMeta{
APIVersion: "v1",
Kind: "ConfigMap",
},
ObjectMeta: metav1.ObjectMeta{
Name: "configmap2",
Namespace: "default",
Annotations: map[string]string{
common.AnnotationKeyAppInstance: "guestbook:apps/ConfigMap:default/configmap2",
},
},
})
unmanagedObjWrongNamespace := kube.MustToUnstructured(&corev1.ConfigMap{
TypeMeta: metav1.TypeMeta{
APIVersion: "v1",
Kind: "ConfigMap",
},
ObjectMeta: metav1.ObjectMeta{
Name: "configmap2",
Namespace: "default",
Annotations: map[string]string{
common.AnnotationKeyAppInstance: "guestbook:/ConfigMap:fakens/configmap2",
},
},
})
ctrl := newFakeController(&fakeData{
apps: []runtime.Object{app, &defaultProj},
manifestResponse: &apiclient.ManifestResponse{
Manifests: []string{},
Namespace: test.FakeDestNamespace,
Server: test.FakeClusterURL,
Revision: "abc123",
},
managedLiveObjs: map[kube.ResourceKey]*unstructured.Unstructured{
kube.GetResourceKey(managedObj): managedObj,
kube.GetResourceKey(unmanagedObjWrongName): unmanagedObjWrongName,
kube.GetResourceKey(unmanagedObjWrongKind): unmanagedObjWrongKind,
kube.GetResourceKey(unmanagedObjWrongGroup): unmanagedObjWrongGroup,
kube.GetResourceKey(unmanagedObjWrongNamespace): unmanagedObjWrongNamespace,
},
})
manager := ctrl.appStateManager.(*appStateManager)
// Managed resource w/ annotations
assert.True(t, manager.isSelfReferencedObj(managedObj, common.AnnotationKeyAppInstance, argo.TrackingMethodLabel))
assert.True(t, manager.isSelfReferencedObj(managedObj, common.AnnotationKeyAppInstance, argo.TrackingMethodAnnotation))
// Managed resource w/ label
assert.True(t, manager.isSelfReferencedObj(managedObjWithLabel, common.AnnotationKeyAppInstance, argo.TrackingMethodLabel))
// Wrong resource name
assert.True(t, manager.isSelfReferencedObj(unmanagedObjWrongName, common.AnnotationKeyAppInstance, argo.TrackingMethodLabel))
assert.False(t, manager.isSelfReferencedObj(unmanagedObjWrongName, common.AnnotationKeyAppInstance, argo.TrackingMethodAnnotation))
// Wrong resource group
assert.True(t, manager.isSelfReferencedObj(unmanagedObjWrongGroup, common.AnnotationKeyAppInstance, argo.TrackingMethodLabel))
assert.False(t, manager.isSelfReferencedObj(unmanagedObjWrongGroup, common.AnnotationKeyAppInstance, argo.TrackingMethodAnnotation))
// Wrong resource kind
assert.True(t, manager.isSelfReferencedObj(unmanagedObjWrongKind, common.AnnotationKeyAppInstance, argo.TrackingMethodLabel))
assert.False(t, manager.isSelfReferencedObj(unmanagedObjWrongKind, common.AnnotationKeyAppInstance, argo.TrackingMethodAnnotation))
// Wrong resource namespace
assert.True(t, manager.isSelfReferencedObj(unmanagedObjWrongNamespace, common.AnnotationKeyAppInstance, argo.TrackingMethodLabel))
assert.False(t, manager.isSelfReferencedObj(unmanagedObjWrongNamespace, common.AnnotationKeyAppInstance, argo.TrackingMethodAnnotationAndLabel))
// Nil resource
assert.True(t, manager.isSelfReferencedObj(nil, common.AnnotationKeyAppInstance, argo.TrackingMethodAnnotation))
}

BIN
docs/assets/terminal.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

View File

@@ -174,7 +174,9 @@ argocd ... --grpc-web
## Why Am I Getting `x509: certificate signed by unknown authority` When Using The CLI?
Your not running your server with correct certs.
The certificate created by default by Argo CD is not automatically recognised by the Argo CD CLI, in order
to create a secure system you must follow the instructions to [install a certificate](/operator-manual/tls/)
and configure your client OS to trust that certificate.
If you're not running in a production system (e.g. you're testing Argo CD out), try the `--insecure` flag:

View File

@@ -29,6 +29,13 @@ kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/core-install.yaml
```
This default installation will have a self-signed certificate and cannot be accessed without a bit of extra work.
Do one of:
* Follow the [instructions to configure a certificate](./operator-manual/tls) (and ensure that the client OS trusts it).
* Configure the client OS to trust the self signed certificate.
* Use the --insecure flag on all Argo CD CLI operations in this guide.
Use `argocd login --core` to [configure](./user-guide/commands/argocd_login.md) CLI access and skip steps 3-5.
## 2. Download Argo CD CLI

View File

@@ -107,7 +107,7 @@ spec:
```
(*The full example can be found [here](https://github.com/argoproj/argo-cd/tree/master/examples/applicationset/git-generator-directory/excludes).*)
This example excludes the `exclude-helm-guestbook` directory from the list of directories scanned for this `ApplictionSet` resource.
This example excludes the `exclude-helm-guestbook` directory from the list of directories scanned for this `ApplicationSet` resource.
!!! note "Exclude rules have higher priority than include rules"

View File

@@ -39,6 +39,8 @@ data:
# The URLs to download additional ArgoCD binaries (besides the Linux amd64 binary included by default)
# for different OS architectures. If provided, additional download buttons will be displayed on the help page.
help.download.linux-arm64: "path-or-url-to-download"
help.download.linux-ppc64le: "path-or-url-to-download"
help.download.linux-s390x: "path-or-url-to-download"
help.download.darwin-amd64: "path-or-url-to-download"
help.download.darwin-arm64: "path-or-url-to-download"
help.download.windows-amd64: "path-or-url-to-download"
@@ -231,6 +233,14 @@ data:
# If omitted, Argo CD injects the app name into the label: 'app.kubernetes.io/instance'
application.instanceLabelKey: mycompany.com/appname
# You can change the resource tracking method Argo CD uses by changing the
# setting application.resourceTrackingMethod to the desired method.
# The following methods are available:
# - label : Uses the application.instanceLabelKey label for tracking
# - annotation : Uses an annotation with additional metadata for tracking instead of the label
# - annotation+label : Also uses an annotation for tracking, but additionally labels the resource with the application name
application.resourceTrackingMethod: annotation
# disables admin user. Admin is enabled by default
admin.enabled: "false"
# add an additional local user with apiKey and login capabilities
@@ -289,6 +299,9 @@ data:
# exec.enabled indicates whether the UI exec feature is enabled. It is disabled by default.
exec.enabled: "false"
# exec.shells restricts which shells are allowed for `exec`, and in which order they are attempted
exec.shells: "bash,sh,powershell,cmd"
# oidc.tls.insecure.skip.verify determines whether certificate verification is skipped when verifying tokens with the
# configured OIDC provider (either external or the bundled Dex instance). Setting this to "true" will cause JWT
# token verification to pass despite the OIDC provider having an invalid certificate. Only set to "true" if you

View File

@@ -59,22 +59,7 @@ also use glob patterns in the action path: `action/*` (or regex patterns if you
`exec` is a special resource. When enabled with the `create` action, this privilege allows a user to `exec` into Pods via
the Argo CD UI. The functionality is similar to `kubectl exec`.
`exec` is a powerful privilege. It allows the user to run arbitrary code on any Pod managed by an Application for which
they have `create` privileges. If the Pod mounts a ServiceAccount token (which is the default behavior of Kubernetes),
then the user effectively has the same privileges as that ServiceAccount.
The exec feature is disabled entirely by default. To enable it, set the `exec.enabled` key to "true" on the argocd-cm
ConfigMap. You will also need to add the following to the argocd-api-server Role (if you're using Argo CD in namespaced
mode) or ClusterRole (if you're using Argo CD in cluster mode).
```yaml
- apiGroups:
- ""
resources:
- pods/exec
verbs:
- create
```
See [Web-based Terminal](web_based_terminal.md) for more info.
## Tying It All Together

View File

@@ -0,0 +1,45 @@
# Web-based Terminal
![Argo CD Terminal](../assets/terminal.png)
Since v2.4, Argo CD has a web-based terminal that allows you to get a shell inside a running pod just like you would with
`kubectl exec`. It's basically SSH from your browser, full ANSI color support and all! However, for security this feature
is disabled by default.
This is a powerful privilege. It allows the user to run arbitrary code on any Pod managed by an Application for which
they have the `exec/create` privilege. If the Pod mounts a ServiceAccount token (which is the default behavior of
Kubernetes), then the user effectively has the same privileges as that ServiceAccount.
## Enabling the terminal
1. Set the `exec.enabled` key to `true` on the `argocd-cm` ConfigMap.
2. Patch the `argocd-server` Role (if using namespaced Argo) or ClusterRole (if using clustered Argo) to allow `argocd-server`
to exec into pods
```yaml
- apiGroups:
- ""
resources:
- pods/exec
verbs:
- create
```
3. Add RBAC rules to allow your users to `create` the `exec` resource, i.e.
```
p, role:myrole, exec, create, */*, allow
```
See [RBAC Configuration](rbac.md#exec-resource) for more info.
## Changing allowed shells
By default, Argo CD attempts to execute shells in this order:
1. bash
2. sh
3. powershell
4. cmd
If none of the shells are found, the terminal session will fail. To add to or change the allowed shells, change the
`exec.shells` key in the `argocd-cm` ConfigMap, separating them with commas.

View File

@@ -235,16 +235,17 @@ If you don't need to set any environment variables, you can set an empty plugin
is 90s. So if you increase the repo server timeout greater than 90s, be sure to set `ARGOCD_EXEC_TIMEOUT` on the
sidecar.
## Tarball stream filtering
## Plugin tar stream exclusions
In order to increase the speed of manifest generation, certain files and folders can be excluded from being sent to your
plugin. We recommend excluding your `.git` folder if it isn't necessary. Use Go's
[filepatch.Match](https://pkg.go.dev/path/filepath#Match) syntax.
You can set it one of three ways:
1. The `--plugin-tar-exclude` argument on the repo server.
2. The `reposerver.plugin.tar.exclusions` key if you are using `argocd-cmd-params-cm`
3. Directly setting 'ARGOCD_REPO_SERVER_PLUGIN_TAR_EXCLUSIONS' environment variable on the repo server.
3. Directly setting `ARGOCD_REPO_SERVER_PLUGIN_TAR_EXCLUSIONS` environment variable on the repo server.
For option 1, the flag can be repeated multiple times. For option 2 and 3, you can specify multiple globs by separating
them with semicolons.

View File

@@ -85,3 +85,22 @@ argocd app set <appName> --kustomize-version v3.5.4
## Build Environment
Kustomize does not support parameters and therefore cannot support the standard [build environment](build-environment.md).
## Kustomizing Helm charts
It's possible to [render Helm charts with Kustomize](https://github.com/kubernetes-sigs/kustomize/blob/master/examples/chart.md).
Doing so requires that you pass the `--enable-helm` flag to the `kustomize build` command.
This flag is not part of the Kustomize options within Argo CD.
If you would like to render Helm charts through Kustomize in an Argo CD application, you have two options:
You can either create a [custom plugin](https://argo-cd.readthedocs.io/en/stable/user-guide/config-management-plugins/), or modify the `argocd-cm` ConfigMap to include the `--enable-helm` flag globally for all Kustomize applications:
```
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-cm
namespace: argocd
data:
kustomize.buildOptions: --enable-helm
```

View File

@@ -5,7 +5,7 @@ kind: Kustomization
images:
- name: quay.io/argoproj/argocd
newName: quay.io/argoproj/argocd
newTag: v2.4.5
newTag: v2.4.6
resources:
- ./application-controller
- ./dex

View File

@@ -9385,7 +9385,7 @@ spec:
valueFrom:
fieldRef:
fieldPath: metadata.namespace
image: quay.io/argoproj/argocd:v2.4.5
image: quay.io/argoproj/argocd:v2.4.6
imagePullPolicy: Always
name: argocd-applicationset-controller
ports:
@@ -9615,7 +9615,7 @@ spec:
value: /helm-working-dir
- name: HELM_DATA_HOME
value: /helm-working-dir
image: quay.io/argoproj/argocd:v2.4.5
image: quay.io/argoproj/argocd:v2.4.6
imagePullPolicy: Always
livenessProbe:
failureThreshold: 3
@@ -9664,7 +9664,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /var/run/argocd/argocd-cmp-server
image: quay.io/argoproj/argocd:v2.4.5
image: quay.io/argoproj/argocd:v2.4.6
name: copyutil
securityContext:
allowPrivilegeEscalation: false
@@ -9851,7 +9851,7 @@ spec:
key: otlp.address
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.4.5
image: quay.io/argoproj/argocd:v2.4.6
imagePullPolicy: Always
livenessProbe:
httpGet:

View File

@@ -12,4 +12,4 @@ resources:
images:
- name: quay.io/argoproj/argocd
newName: quay.io/argoproj/argocd
newTag: v2.4.5
newTag: v2.4.6

View File

@@ -11,7 +11,7 @@ patchesStrategicMerge:
images:
- name: quay.io/argoproj/argocd
newName: quay.io/argoproj/argocd
newTag: v2.4.5
newTag: v2.4.6
resources:
- ../../base/application-controller
- ../../base/applicationset-controller

View File

@@ -10320,7 +10320,7 @@ spec:
valueFrom:
fieldRef:
fieldPath: metadata.namespace
image: quay.io/argoproj/argocd:v2.4.5
image: quay.io/argoproj/argocd:v2.4.6
imagePullPolicy: Always
name: argocd-applicationset-controller
ports:
@@ -10417,7 +10417,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /shared/argocd-dex
image: quay.io/argoproj/argocd:v2.4.5
image: quay.io/argoproj/argocd:v2.4.6
imagePullPolicy: Always
name: copyutil
securityContext:
@@ -10457,7 +10457,7 @@ spec:
containers:
- command:
- argocd-notifications
image: quay.io/argoproj/argocd:v2.4.5
image: quay.io/argoproj/argocd:v2.4.6
imagePullPolicy: Always
livenessProbe:
tcpSocket:
@@ -10714,7 +10714,7 @@ spec:
value: /helm-working-dir
- name: HELM_DATA_HOME
value: /helm-working-dir
image: quay.io/argoproj/argocd:v2.4.5
image: quay.io/argoproj/argocd:v2.4.6
imagePullPolicy: Always
livenessProbe:
failureThreshold: 3
@@ -10763,7 +10763,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /var/run/argocd/argocd-cmp-server
image: quay.io/argoproj/argocd:v2.4.5
image: quay.io/argoproj/argocd:v2.4.6
name: copyutil
securityContext:
allowPrivilegeEscalation: false
@@ -11010,7 +11010,7 @@ spec:
key: otlp.address
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.4.5
image: quay.io/argoproj/argocd:v2.4.6
imagePullPolicy: Always
livenessProbe:
httpGet:
@@ -11218,7 +11218,7 @@ spec:
key: otlp.address
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.4.5
image: quay.io/argoproj/argocd:v2.4.6
imagePullPolicy: Always
livenessProbe:
httpGet:

View File

@@ -1244,7 +1244,7 @@ spec:
valueFrom:
fieldRef:
fieldPath: metadata.namespace
image: quay.io/argoproj/argocd:v2.4.5
image: quay.io/argoproj/argocd:v2.4.6
imagePullPolicy: Always
name: argocd-applicationset-controller
ports:
@@ -1341,7 +1341,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /shared/argocd-dex
image: quay.io/argoproj/argocd:v2.4.5
image: quay.io/argoproj/argocd:v2.4.6
imagePullPolicy: Always
name: copyutil
securityContext:
@@ -1381,7 +1381,7 @@ spec:
containers:
- command:
- argocd-notifications
image: quay.io/argoproj/argocd:v2.4.5
image: quay.io/argoproj/argocd:v2.4.6
imagePullPolicy: Always
livenessProbe:
tcpSocket:
@@ -1638,7 +1638,7 @@ spec:
value: /helm-working-dir
- name: HELM_DATA_HOME
value: /helm-working-dir
image: quay.io/argoproj/argocd:v2.4.5
image: quay.io/argoproj/argocd:v2.4.6
imagePullPolicy: Always
livenessProbe:
failureThreshold: 3
@@ -1687,7 +1687,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /var/run/argocd/argocd-cmp-server
image: quay.io/argoproj/argocd:v2.4.5
image: quay.io/argoproj/argocd:v2.4.6
name: copyutil
securityContext:
allowPrivilegeEscalation: false
@@ -1934,7 +1934,7 @@ spec:
key: otlp.address
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.4.5
image: quay.io/argoproj/argocd:v2.4.6
imagePullPolicy: Always
livenessProbe:
httpGet:
@@ -2142,7 +2142,7 @@ spec:
key: otlp.address
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.4.5
image: quay.io/argoproj/argocd:v2.4.6
imagePullPolicy: Always
livenessProbe:
httpGet:

View File

@@ -9692,7 +9692,7 @@ spec:
valueFrom:
fieldRef:
fieldPath: metadata.namespace
image: quay.io/argoproj/argocd:v2.4.5
image: quay.io/argoproj/argocd:v2.4.6
imagePullPolicy: Always
name: argocd-applicationset-controller
ports:
@@ -9789,7 +9789,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /shared/argocd-dex
image: quay.io/argoproj/argocd:v2.4.5
image: quay.io/argoproj/argocd:v2.4.6
imagePullPolicy: Always
name: copyutil
securityContext:
@@ -9829,7 +9829,7 @@ spec:
containers:
- command:
- argocd-notifications
image: quay.io/argoproj/argocd:v2.4.5
image: quay.io/argoproj/argocd:v2.4.6
imagePullPolicy: Always
livenessProbe:
tcpSocket:
@@ -10054,7 +10054,7 @@ spec:
value: /helm-working-dir
- name: HELM_DATA_HOME
value: /helm-working-dir
image: quay.io/argoproj/argocd:v2.4.5
image: quay.io/argoproj/argocd:v2.4.6
imagePullPolicy: Always
livenessProbe:
failureThreshold: 3
@@ -10103,7 +10103,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /var/run/argocd/argocd-cmp-server
image: quay.io/argoproj/argocd:v2.4.5
image: quay.io/argoproj/argocd:v2.4.6
name: copyutil
securityContext:
allowPrivilegeEscalation: false
@@ -10346,7 +10346,7 @@ spec:
key: otlp.address
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.4.5
image: quay.io/argoproj/argocd:v2.4.6
imagePullPolicy: Always
livenessProbe:
httpGet:
@@ -10548,7 +10548,7 @@ spec:
key: otlp.address
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.4.5
image: quay.io/argoproj/argocd:v2.4.6
imagePullPolicy: Always
livenessProbe:
httpGet:

View File

@@ -616,7 +616,7 @@ spec:
valueFrom:
fieldRef:
fieldPath: metadata.namespace
image: quay.io/argoproj/argocd:v2.4.5
image: quay.io/argoproj/argocd:v2.4.6
imagePullPolicy: Always
name: argocd-applicationset-controller
ports:
@@ -713,7 +713,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /shared/argocd-dex
image: quay.io/argoproj/argocd:v2.4.5
image: quay.io/argoproj/argocd:v2.4.6
imagePullPolicy: Always
name: copyutil
securityContext:
@@ -753,7 +753,7 @@ spec:
containers:
- command:
- argocd-notifications
image: quay.io/argoproj/argocd:v2.4.5
image: quay.io/argoproj/argocd:v2.4.6
imagePullPolicy: Always
livenessProbe:
tcpSocket:
@@ -978,7 +978,7 @@ spec:
value: /helm-working-dir
- name: HELM_DATA_HOME
value: /helm-working-dir
image: quay.io/argoproj/argocd:v2.4.5
image: quay.io/argoproj/argocd:v2.4.6
imagePullPolicy: Always
livenessProbe:
failureThreshold: 3
@@ -1027,7 +1027,7 @@ spec:
- -n
- /usr/local/bin/argocd
- /var/run/argocd/argocd-cmp-server
image: quay.io/argoproj/argocd:v2.4.5
image: quay.io/argoproj/argocd:v2.4.6
name: copyutil
securityContext:
allowPrivilegeEscalation: false
@@ -1270,7 +1270,7 @@ spec:
key: otlp.address
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.4.5
image: quay.io/argoproj/argocd:v2.4.6
imagePullPolicy: Always
livenessProbe:
httpGet:
@@ -1472,7 +1472,7 @@ spec:
key: otlp.address
name: argocd-cmd-params-cm
optional: true
image: quay.io/argoproj/argocd:v2.4.5
image: quay.io/argoproj/argocd:v2.4.6
imagePullPolicy: Always
livenessProbe:
httpGet:

View File

@@ -44,6 +44,7 @@ nav:
- operator-manual/custom_tools.md
- operator-manual/custom-styles.md
- operator-manual/metrics.md
- operator-manual/web_based_terminal.md
- Notification:
- Overview: operator-manual/notifications/index.md
- operator-manual/notifications/triggers.md

View File

@@ -3,10 +3,11 @@ package reposerver
import (
"crypto/tls"
"fmt"
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
"os"
"path/filepath"
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
grpc_logrus "github.com/grpc-ecosystem/go-grpc-middleware/logging/logrus"
grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
@@ -15,6 +16,7 @@ import (
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/health"
"google.golang.org/grpc/health/grpc_health_v1"
"google.golang.org/grpc/keepalive"
"google.golang.org/grpc/reflection"
"github.com/argoproj/argo-cd/v2/common"
@@ -86,6 +88,11 @@ func NewServer(metricsServer *metrics.MetricsServer, cache *reposervercache.Cach
grpc.StreamInterceptor(grpc_middleware.ChainStreamServer(streamInterceptors...)),
grpc.MaxRecvMsgSize(apiclient.MaxGRPCMessageSize),
grpc.MaxSendMsgSize(apiclient.MaxGRPCMessageSize),
grpc.KeepaliveEnforcementPolicy(
keepalive.EnforcementPolicy{
MinTime: common.GRPCKeepAliveEnforcementMinimum,
},
),
}
// We do allow for non-TLS servers to be created, in case of mTLS will be

View File

@@ -33,17 +33,19 @@ type terminalHandler struct {
enf *rbac.Enforcer
cache *servercache.Cache
appResourceTreeFn func(ctx context.Context, app *appv1.Application) (*appv1.ApplicationTree, error)
allowedShells []string
}
// NewHandler returns a new terminal handler.
func NewHandler(appLister applisters.ApplicationNamespaceLister, db db.ArgoDB, enf *rbac.Enforcer, cache *servercache.Cache,
appResourceTree AppResourceTreeFn) *terminalHandler {
appResourceTree AppResourceTreeFn, allowedShells []string) *terminalHandler {
return &terminalHandler{
appLister: appLister,
db: db,
enf: enf,
cache: cache,
appResourceTreeFn: appResourceTree,
allowedShells: allowedShells,
}
}
@@ -123,7 +125,7 @@ func (s *terminalHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
http.Error(w, "Namespace name is not valid", http.StatusBadRequest)
return
}
shell := q.Get("shell") // No need to validate. Will only buse used if it's in the allow-list.
shell := q.Get("shell") // No need to validate. Will only be used if it's in the allow-list.
ctx := r.Context()
@@ -216,14 +218,12 @@ func (s *terminalHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
defer session.Done()
validShells := []string{"bash", "sh", "powershell", "cmd"}
if isValidShell(validShells, shell) {
if isValidShell(s.allowedShells, shell) {
cmd := []string{shell}
err = startProcess(kubeClientset, config, namespace, podName, container, cmd, session)
} else {
// No shell given or it was not valid: try some shells until one succeeds or all fail
// FIXME: if the first shell fails then the first keyboard event is lost
for _, testShell := range validShells {
// No shell given or the given shell was not allowed: try the configured shells until one succeeds or all fail.
for _, testShell := range s.allowedShells {
cmd := []string{testShell}
if err = startProcess(kubeClientset, config, namespace, podName, container, cmd, session); err == nil {
break

View File

@@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"
"net/http"
"sync"
"time"
"github.com/gorilla/websocket"
@@ -26,6 +27,7 @@ type terminalSession struct {
sizeChan chan remotecommand.TerminalSize
doneChan chan struct{}
tty bool
readLock sync.Mutex
}
// newTerminalSession create terminalSession
@@ -60,7 +62,9 @@ func (t *terminalSession) Next() *remotecommand.TerminalSize {
// Read called in a loop from remotecommand as long as the process is running
func (t *terminalSession) Read(p []byte) (int, error) {
t.readLock.Lock()
_, message, err := t.wsConn.ReadMessage()
t.readLock.Unlock()
if err != nil {
log.Errorf("read message err: %v", err)
return copy(p, EndOfTransmission), err

View File

@@ -22,6 +22,8 @@ import (
// nolint:staticcheck
golang_proto "github.com/golang/protobuf/proto"
netCtx "context"
"github.com/argoproj/pkg/sync"
"github.com/go-redis/redis/v8"
"github.com/golang-jwt/jwt/v4"
@@ -35,11 +37,11 @@ import (
log "github.com/sirupsen/logrus"
"github.com/soheilhy/cmux"
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
netCtx "golang.org/x/net/context"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/keepalive"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/reflection"
"google.golang.org/grpc/status"
@@ -607,6 +609,11 @@ func (a *ArgoCDServer) newGRPCServer() (*grpc.Server, application.AppResourceTre
grpc.MaxRecvMsgSize(apiclient.MaxGRPCMessageSize),
grpc.MaxSendMsgSize(apiclient.MaxGRPCMessageSize),
grpc.ConnectionTimeout(300 * time.Second),
grpc.KeepaliveEnforcementPolicy(
keepalive.EnforcementPolicy{
MinTime: common.GRPCKeepAliveEnforcementMinimum,
},
),
}
sensitiveMethods := map[string]bool{
"/cluster.ClusterService/Create": true,
@@ -801,7 +808,7 @@ func (a *ArgoCDServer) newHTTPServer(ctx context.Context, port int, grpcWebHandl
}
mux.Handle("/api/", handler)
terminalHandler := application.NewHandler(a.appLister, a.db, a.enf, a.Cache, appResourceTreeFn)
terminalHandler := application.NewHandler(a.appLister, a.db, a.enf, a.Cache, appResourceTreeFn, a.settings.ExecShells)
mux.HandleFunc("/terminal", func(writer http.ResponseWriter, request *http.Request) {
argocdSettings, err := a.settingsMgr.GetSettings()
if err != nil {

View File

@@ -38,6 +38,7 @@ import (
projectFixture "github.com/argoproj/argo-cd/v2/test/e2e/fixture/project"
repoFixture "github.com/argoproj/argo-cd/v2/test/e2e/fixture/repos"
"github.com/argoproj/argo-cd/v2/test/e2e/testdata"
"github.com/argoproj/argo-cd/v2/util/argo"
. "github.com/argoproj/argo-cd/v2/util/argo"
. "github.com/argoproj/argo-cd/v2/util/errors"
"github.com/argoproj/argo-cd/v2/util/io"
@@ -947,7 +948,7 @@ func TestLocalManifestSync(t *testing.T) {
And(func(app *Application) {
res, _ := RunCli("app", "manifests", app.Name)
assert.Contains(t, res, "containerPort: 80")
assert.Contains(t, res, "image: gcr.io/heptio-images/ks-guestbook-demo:0.2")
assert.Contains(t, res, "image: quay.io/argoprojlabs/argocd-e2e-container:0.2")
}).
Given().
LocalPath(guestbookPathLocal).
@@ -958,7 +959,7 @@ func TestLocalManifestSync(t *testing.T) {
And(func(app *Application) {
res, _ := RunCli("app", "manifests", app.Name)
assert.Contains(t, res, "containerPort: 81")
assert.Contains(t, res, "image: gcr.io/heptio-images/ks-guestbook-demo:0.3")
assert.Contains(t, res, "image: quay.io/argoprojlabs/argocd-e2e-container:0.3")
}).
Given().
LocalPath("").
@@ -969,7 +970,7 @@ func TestLocalManifestSync(t *testing.T) {
And(func(app *Application) {
res, _ := RunCli("app", "manifests", app.Name)
assert.Contains(t, res, "containerPort: 80")
assert.Contains(t, res, "image: gcr.io/heptio-images/ks-guestbook-demo:0.2")
assert.Contains(t, res, "image: quay.io/argoprojlabs/argocd-e2e-container:0.2")
})
}
@@ -1190,7 +1191,8 @@ func TestPermissionWithScopedRepo(t *testing.T) {
Name(projName).
Destination("*,*").
When().
Create()
Create().
AddSource("*")
repoFixture.Given(t, true).
When().
@@ -2063,3 +2065,241 @@ func TestDisableManifestGeneration(t *testing.T) {
assert.Equal(t, app.Status.SourceType, ApplicationSourceTypeDirectory)
})
}
func TestSwitchTrackingMethod(t *testing.T) {
ctx := Given(t)
ctx.
SetTrackingMethod(string(argo.TrackingMethodAnnotation)).
Path("deployment").
When().
CreateApp().
Sync().
Refresh(RefreshTypeNormal).
Then().
Expect(OperationPhaseIs(OperationSucceeded)).
Expect(SyncStatusIs(SyncStatusCodeSynced)).
Expect(HealthIs(health.HealthStatusHealthy)).
When().
And(func() {
// Add resource with tracking annotation. This should put the
// application OutOfSync.
FailOnErr(KubeClientset.CoreV1().ConfigMaps(DeploymentNamespace()).Create(context.Background(), &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "other-configmap",
Annotations: map[string]string{
common.AnnotationKeyAppInstance: fmt.Sprintf("%s:/ConfigMap:%s/other-configmap", Name(), DeploymentNamespace()),
},
},
}, metav1.CreateOptions{}))
}).
Then().
Expect(OperationPhaseIs(OperationSucceeded)).
Expect(SyncStatusIs(SyncStatusCodeOutOfSync)).
Expect(HealthIs(health.HealthStatusHealthy)).
When().
And(func() {
// Delete resource to bring application back in sync
FailOnErr(nil, KubeClientset.CoreV1().ConfigMaps(DeploymentNamespace()).Delete(context.Background(), "other-configmap", metav1.DeleteOptions{}))
}).
Then().
Expect(OperationPhaseIs(OperationSucceeded)).
Expect(SyncStatusIs(SyncStatusCodeSynced)).
Expect(HealthIs(health.HealthStatusHealthy)).
When().
SetTrackingMethod(string(argo.TrackingMethodLabel)).
Sync().
Then().
Expect(OperationPhaseIs(OperationSucceeded)).
Expect(SyncStatusIs(SyncStatusCodeSynced)).
Expect(HealthIs(health.HealthStatusHealthy)).
When().
And(func() {
// Add a resource with a tracking annotation. This should not
// affect the application, because we now use the tracking method
// "label".
FailOnErr(KubeClientset.CoreV1().ConfigMaps(DeploymentNamespace()).Create(context.Background(), &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "other-configmap",
Annotations: map[string]string{
common.AnnotationKeyAppInstance: fmt.Sprintf("%s:/ConfigMap:%s/other-configmap", Name(), DeploymentNamespace()),
},
},
}, metav1.CreateOptions{}))
}).
Then().
Expect(OperationPhaseIs(OperationSucceeded)).
Expect(SyncStatusIs(SyncStatusCodeSynced)).
Expect(HealthIs(health.HealthStatusHealthy)).
When().
And(func() {
// Add a resource with the tracking label. The app should become
// OutOfSync.
FailOnErr(KubeClientset.CoreV1().ConfigMaps(DeploymentNamespace()).Create(context.Background(), &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "extra-configmap",
Labels: map[string]string{
common.LabelKeyAppInstance: Name(),
},
},
}, metav1.CreateOptions{}))
}).
Then().
Expect(OperationPhaseIs(OperationSucceeded)).
Expect(SyncStatusIs(SyncStatusCodeOutOfSync)).
Expect(HealthIs(health.HealthStatusHealthy)).
When().
And(func() {
// Delete resource to bring application back in sync
FailOnErr(nil, KubeClientset.CoreV1().ConfigMaps(DeploymentNamespace()).Delete(context.Background(), "extra-configmap", metav1.DeleteOptions{}))
}).
Then().
Expect(OperationPhaseIs(OperationSucceeded)).
Expect(SyncStatusIs(SyncStatusCodeSynced)).
Expect(HealthIs(health.HealthStatusHealthy))
}
func TestSwitchTrackingLabel(t *testing.T) {
ctx := Given(t)
ctx.
Path("deployment").
When().
CreateApp().
Sync().
Refresh(RefreshTypeNormal).
Then().
Expect(OperationPhaseIs(OperationSucceeded)).
Expect(SyncStatusIs(SyncStatusCodeSynced)).
Expect(HealthIs(health.HealthStatusHealthy)).
When().
And(func() {
// Add extra resource that carries the default tracking label
// We expect the app to go out of sync.
FailOnErr(KubeClientset.CoreV1().ConfigMaps(DeploymentNamespace()).Create(context.Background(), &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "other-configmap",
Labels: map[string]string{
common.LabelKeyAppInstance: Name(),
},
},
}, metav1.CreateOptions{}))
}).
Then().
Expect(OperationPhaseIs(OperationSucceeded)).
Expect(SyncStatusIs(SyncStatusCodeOutOfSync)).
Expect(HealthIs(health.HealthStatusHealthy)).
When().
And(func() {
// Delete resource to bring application back in sync
FailOnErr(nil, KubeClientset.CoreV1().ConfigMaps(DeploymentNamespace()).Delete(context.Background(), "other-configmap", metav1.DeleteOptions{}))
}).
Then().
Expect(OperationPhaseIs(OperationSucceeded)).
Expect(SyncStatusIs(SyncStatusCodeSynced)).
Expect(HealthIs(health.HealthStatusHealthy)).
When().
// Change tracking label
SetTrackingLabel("argocd.tracking").
Sync().
Then().
Expect(OperationPhaseIs(OperationSucceeded)).
Expect(SyncStatusIs(SyncStatusCodeSynced)).
Expect(HealthIs(health.HealthStatusHealthy)).
When().
And(func() {
// Create resource with the new tracking label, the application
// is expected to go out of sync
FailOnErr(KubeClientset.CoreV1().ConfigMaps(DeploymentNamespace()).Create(context.Background(), &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "other-configmap",
Labels: map[string]string{
"argocd.tracking": Name(),
},
},
}, metav1.CreateOptions{}))
}).
Then().
Expect(OperationPhaseIs(OperationSucceeded)).
Expect(SyncStatusIs(SyncStatusCodeOutOfSync)).
Expect(HealthIs(health.HealthStatusHealthy)).
When().
And(func() {
// Delete resource to bring application back in sync
FailOnErr(nil, KubeClientset.CoreV1().ConfigMaps(DeploymentNamespace()).Delete(context.Background(), "other-configmap", metav1.DeleteOptions{}))
}).
Then().
Expect(OperationPhaseIs(OperationSucceeded)).
Expect(SyncStatusIs(SyncStatusCodeSynced)).
Expect(HealthIs(health.HealthStatusHealthy)).
When().
And(func() {
// Add extra resource that carries the default tracking label
// We expect the app to stay in sync, because the configured
// label is different.
FailOnErr(KubeClientset.CoreV1().ConfigMaps(DeploymentNamespace()).Create(context.Background(), &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "other-configmap",
Labels: map[string]string{
common.LabelKeyAppInstance: Name(),
},
},
}, metav1.CreateOptions{}))
}).
Then().
Expect(OperationPhaseIs(OperationSucceeded)).
Expect(SyncStatusIs(SyncStatusCodeSynced)).
Expect(HealthIs(health.HealthStatusHealthy))
}
func TestAnnotationTrackingExtraResources(t *testing.T) {
ctx := Given(t)
SetTrackingMethod(string(argo.TrackingMethodAnnotation))
ctx.
Path("deployment").
When().
CreateApp().
Sync().
Refresh(RefreshTypeNormal).
Then().
Expect(OperationPhaseIs(OperationSucceeded)).
Expect(SyncStatusIs(SyncStatusCodeSynced)).
Expect(HealthIs(health.HealthStatusHealthy)).
When().
And(func() {
// Add a resource with an annotation that is not referencing the
// resource.
FailOnErr(KubeClientset.CoreV1().ConfigMaps(DeploymentNamespace()).Create(context.Background(), &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "extra-configmap",
Annotations: map[string]string{
common.AnnotationKeyAppInstance: fmt.Sprintf("%s:apps/Deployment:%s/guestbook-cm", Name(), DeploymentNamespace()),
},
},
}, metav1.CreateOptions{}))
}).
Refresh(RefreshTypeNormal).
Then().
Expect(OperationPhaseIs(OperationSucceeded)).
Expect(SyncStatusIs(SyncStatusCodeSynced)).
Expect(HealthIs(health.HealthStatusHealthy)).
When().
And(func() {
// Add a resource with an annotation that is self-referencing the
// resource.
FailOnErr(KubeClientset.CoreV1().ConfigMaps(DeploymentNamespace()).Create(context.Background(), &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "other-configmap",
Annotations: map[string]string{
common.AnnotationKeyAppInstance: fmt.Sprintf("%s:/ConfigMap:%s/other-configmap", Name(), DeploymentNamespace()),
},
},
}, metav1.CreateOptions{}))
}).
Refresh(RefreshTypeNormal).
Then().
Expect(OperationPhaseIs(OperationSucceeded)).
Expect(SyncStatusIs(SyncStatusCodeOutOfSync)).
Expect(HealthIs(health.HealthStatusHealthy))
}

View File

@@ -22,7 +22,7 @@ metadata:
spec:
containers:
- name: main
image: alpine:3.10.2
image: quay.io/argoprojlabs/argocd-e2e-container:0.1
imagePullPolicy: IfNotPresent
command:
- "true"
@@ -43,7 +43,7 @@ metadata:
spec:
containers:
- name: main
image: alpine:3.10.2
image: quay.io/argoprojlabs/argocd-e2e-container:0.1
imagePullPolicy: IfNotPresent
command:
- "true"

View File

@@ -341,3 +341,13 @@ func (a *Actions) verifyAction() {
a.Then().Expect(Success(""))
}
}
func (a *Actions) SetTrackingMethod(trackingMethod string) *Actions {
fixture.SetTrackingMethod(trackingMethod)
return a
}
func (a *Actions) SetTrackingLabel(trackingLabel string) *Actions {
fixture.SetTrackingLabel(trackingLabel)
return a
}

View File

@@ -308,3 +308,8 @@ func (c *Context) HelmSkipCrds() *Context {
c.helmSkipCrds = true
return c
}
func (c *Context) SetTrackingMethod(trackingMethod string) *Context {
fixture.SetTrackingMethod(trackingMethod)
return c
}

View File

@@ -160,7 +160,8 @@ func init() {
adminUsername = GetEnvWithDefault(EnvAdminUsername, defaultAdminUsername)
AdminPassword = GetEnvWithDefault(EnvAdminPassword, defaultAdminPassword)
tlsTestResult, err := grpcutil.TestTLS(apiServerAddress)
dialTime := 30 * time.Second
tlsTestResult, err := grpcutil.TestTLS(apiServerAddress, dialTime)
CheckError(err)
ArgoCDClientset, err = apiclient.NewClient(&apiclient.ClientOptions{Insecure: true, ServerAddr: apiServerAddress, PlainText: !tlsTestResult.TLS})
@@ -357,6 +358,13 @@ func SetTrackingMethod(trackingMethod string) {
})
}
func SetTrackingLabel(trackingLabel string) {
updateSettingConfigMap(func(cm *corev1.ConfigMap) error {
cm.Data["application.instanceLabelKey"] = trackingLabel
return nil
})
}
func SetResourceOverridesSplitKeys(overrides map[string]v1alpha1.ResourceOverride) {
updateSettingConfigMap(func(cm *corev1.ConfigMap) error {
for k, v := range overrides {

View File

@@ -15,6 +15,7 @@ type Context struct {
timeout int
name string
destination string
repos []string
}
func Given(t *testing.T) *Context {
@@ -43,6 +44,11 @@ func (c *Context) Destination(destination string) *Context {
return c
}
func (c *Context) SourceRepositories(repos []string) *Context {
c.repos = repos
return c
}
func (c *Context) And(block func()) *Context {
block()
return c

View File

@@ -171,7 +171,7 @@ spec:
containers:
- command:
- "true"
image: "alpine:latest"
image: "quay.io/argoprojlabs/argocd-e2e-container:0.1"
imagePullPolicy: IfNotPresent
name: main
restartPolicy: Never
@@ -202,7 +202,7 @@ spec:
containers:
- command:
- "true"
image: "alpine:latest"
image: "quay.io/argoprojlabs/argocd-e2e-container:0.1"
imagePullPolicy: IfNotPresent
name: main
restartPolicy: Never
@@ -218,7 +218,7 @@ spec:
containers:
- command:
- "false"
image: "alpine:latest"
image: "quay.io/argoprojlabs/argocd-e2e-container:0.1"
imagePullPolicy: IfNotPresent
name: main
restartPolicy: Never

View File

@@ -0,0 +1,2 @@
FROM docker.io/library/busybox
CMD exec sh -c "trap : TERM INT; echo 'Hi' && tail -f /dev/null"

View File

@@ -0,0 +1,11 @@
#!/bin/sh
set -x
VERSIONS="0.1 0.2 0.3"
PLATFORMS="linux/amd64,linux/arm64,linux/s390x,linux/ppc64le"
for version in $VERSIONS; do
docker buildx build \
-t "quay.io/argoprojlabs/argocd-e2e-container:${version}" \
--platform "${PLATFORMS}" \
--push \
.
done

View File

@@ -5,7 +5,7 @@ metadata:
spec:
containers:
- name: main
image: alpine:3.10.2
image: quay.io/argoprojlabs/argocd-e2e-container:0.1
imagePullPolicy: IfNotPresent
command:
- "true"

View File

@@ -16,7 +16,7 @@ spec:
spec:
containers:
- name: nginx
image: nginx:1.17.4-alpine
image: quay.io/argoprojlabs/argocd-e2e-container:0.1
ports:
- containerPort: "80"
imagePullPolicy: IfNotPresent

View File

@@ -16,6 +16,6 @@ spec:
spec:
containers:
- name: nginx
image: nginx:1.17.4-alpine
image: quay.io/argoprojlabs/argocd-e2e-container:0.1
ports:
- containerPort: 80

View File

@@ -16,7 +16,7 @@ spec:
spec:
containers:
- name: extensions-deployment
image: "gcr.io/heptio-images/ks-guestbook-demo:0.2"
image: quay.io/argoprojlabs/argocd-e2e-container:0.1
imagePullPolicy: IfNotPresent
ports:
- name: http

View File

@@ -5,7 +5,7 @@ metadata:
spec:
containers:
- name: main
image: alpine:3.10.2
image: quay.io/argoprojlabs/argocd-e2e-container:0.1
imagePullPolicy: IfNotPresent
command:
- "true"

View File

@@ -14,13 +14,8 @@ spec:
app: guestbook-ui
spec:
containers:
- image: busybox
- image: quay.io/argoprojlabs/argocd-e2e-container:0.1
imagePullPolicy: IfNotPresent
name: guestbook-ui
command:
- sh
args:
- -c
- "echo \"Hi\" && tail -f /dev/null"
ports:
- containerPort: 80

View File

@@ -15,7 +15,7 @@ spec:
app: guestbook-ui
spec:
containers:
- image: gcr.io/heptio-images/ks-guestbook-demo:0.2
- image: quay.io/argoprojlabs/argocd-e2e-container:0.1
imagePullPolicy: IfNotPresent
name: guestbook-ui
ports:

View File

@@ -14,7 +14,7 @@ spec:
app: guestbook-ui
spec:
containers:
- image: gcr.io/heptio-images/ks-guestbook-demo:0.2
- image: quay.io/argoprojlabs/argocd-e2e-container:0.2
imagePullPolicy: IfNotPresent
name: guestbook-ui
ports:

View File

@@ -14,7 +14,7 @@ spec:
app: guestbook-ui
spec:
containers:
- image: gcr.io/heptio-images/ks-guestbook-demo:0.2
- image: quay.io/argoprojlabs/argocd-e2e-container:0.2
imagePullPolicy: IfNotPresent
name: guestbook-ui
ports:

View File

@@ -14,7 +14,7 @@ spec:
app: guestbook-ui
spec:
containers:
- image: gcr.io/heptio-images/ks-guestbook-demo:0.3
- image: quay.io/argoprojlabs/argocd-e2e-container:0.3
name: guestbook-ui
ports:
- containerPort: 81

View File

@@ -14,7 +14,7 @@ spec:
spec:
containers:
- name: main
image: nginx:1.17.4-alpine
image: quay.io/argoprojlabs/argocd-e2e-container:0.1
imagePullPolicy: IfNotPresent
readinessProbe:
failureThreshold: 3

View File

@@ -10,7 +10,7 @@ spec:
containers:
- command:
- "true"
image: "alpine:3.10.2"
image: quay.io/argoprojlabs/argocd-e2e-container:0.1
imagePullPolicy: IfNotPresent
name: main
restartPolicy: Never

View File

@@ -8,7 +8,7 @@ spec:
containers:
- command:
- "true"
image: "alpine:latest"
image: quay.io/argoprojlabs/argocd-e2e-container:0.1
imagePullPolicy: IfNotPresent
name: main
restartPolicy: Never

View File

@@ -5,7 +5,7 @@ metadata:
spec:
containers:
- name: main
image: alpine:latest
image: quay.io/argoprojlabs/argocd-e2e-container:0.1
imagePullPolicy: IfNotPresent
command:
- "true"

View File

@@ -5,7 +5,7 @@ metadata:
spec:
containers:
- name: main
image: alpine:3.10.2
image: quay.io/argoprojlabs/argocd-e2e-container:0.1
imagePullPolicy: IfNotPresent
command:
- "true"

View File

@@ -22,7 +22,7 @@ spec:
spec:
containers:
- name: nginx
image: nginx:1.17.4-alpine
image: quay.io/argoprojlabs/argocd-e2e-container:0.1
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
@@ -39,7 +39,7 @@ spec:
restartPolicy: Never
containers:
- name: main
image: alpine:3.10.2
image: quay.io/argoprojlabs/argocd-e2e-container:0.1
imagePullPolicy: IfNotPresent
command: [sh, -c, "sleep 10"]
@@ -56,6 +56,6 @@ spec:
restartPolicy: Never
containers:
- name: main
image: alpine:3.10.2
image: quay.io/argoprojlabs/argocd-e2e-container:0.1
imagePullPolicy: IfNotPresent
command: [sh, -c, "sleep 10"]

View File

@@ -17,7 +17,7 @@ spec:
spec:
containers:
- name: helm-guestbook
image: "gcr.io/heptio-images/ks-guestbook-demo:0.2"
image: quay.io/argoprojlabs/argocd-e2e-container:0.2
imagePullPolicy: IfNotPresent
ports:
- name: http
@@ -30,4 +30,4 @@ spec:
readinessProbe:
httpGet:
path: /
port: http
port: http

View File

@@ -16,7 +16,7 @@ spec:
spec:
containers:
- name: helm-guestbook
image: "gcr.io/heptio-images/ks-guestbook-demo:0.2"
image: quay.io/argoprojlabs/argocd-e2e-container:0.2
imagePullPolicy: IfNotPresent
ports:
- name: http

View File

@@ -14,7 +14,7 @@ spec:
app: guestbook-ui
spec:
containers:
- image: gcr.io/heptio-images/ks-guestbook-demo:0.2
- image: quay.io/argoprojlabs/argocd-e2e-container:0.2
name: guestbook-ui
ports:
- containerPort: 80

View File

@@ -16,6 +16,6 @@ spec:
containers:
- name: main
command: ["sleep", "999"]
image: alpine:3.10.2
image: quay.io/argoprojlabs/argocd-e2e-container:0.1
imagePullPolicy: IfNotPresent

View File

@@ -5,7 +5,7 @@ metadata:
spec:
containers:
- name: main
image: alpine:3.10.2
image: quay.io/argoprojlabs/argocd-e2e-container:0.1
imagePullPolicy: IfNotPresent
command:
- "true"

View File

@@ -5,7 +5,7 @@ metadata:
spec:
containers:
- name: main
image: alpine:3.10.2
image: quay.io/argoprojlabs/argocd-e2e-container:0.1
imagePullPolicy: IfNotPresent
command:
- "true"

View File

@@ -43,7 +43,7 @@ export const Help = () => {
&nbsp;
{Object.keys(binaryUrls || {}).map(binaryName => {
const url = binaryUrls[binaryName];
const match = binaryName.match(/.*(darwin|windows|linux)-(amd64|arm64)/);
const match = binaryName.match(/.*(darwin|windows|linux)-(amd64|arm64|ppc64le|s390x)/);
const [platform, arch] = match ? match.slice(1) : ['', ''];
return (
<>

View File

@@ -29,6 +29,7 @@ var LabelMaxLength = 63
// ResourceTracking defines methods which allow setup and retrieve tracking information to resource
type ResourceTracking interface {
GetAppName(un *unstructured.Unstructured, key string, trackingMethod v1alpha1.TrackingMethod) string
GetAppInstance(un *unstructured.Unstructured, key string, trackingMethod v1alpha1.TrackingMethod) *AppInstanceValue
SetAppInstance(un *unstructured.Unstructured, key, val, namespace string, trackingMethod v1alpha1.TrackingMethod) error
BuildAppInstanceValue(value AppInstanceValue) string
ParseAppInstanceValue(value string) (*AppInstanceValue, error)
@@ -54,7 +55,7 @@ func NewResourceTracking() ResourceTracking {
// GetTrackingMethod retrieve tracking method from settings
func GetTrackingMethod(settingsMgr *settings.SettingsManager) v1alpha1.TrackingMethod {
tm, err := settingsMgr.GetTrackingMethod()
if err != nil {
if err != nil || tm == "" {
return TrackingMethodLabel
}
return v1alpha1.TrackingMethod(tm)
@@ -64,15 +65,23 @@ func IsOldTrackingMethod(trackingMethod string) bool {
return trackingMethod == "" || trackingMethod == string(TrackingMethodLabel)
}
func (rt *resourceTracking) getAppInstanceValue(un *unstructured.Unstructured, key string, trackingMethod v1alpha1.TrackingMethod) *AppInstanceValue {
appInstanceAnnotation := argokube.GetAppInstanceAnnotation(un, common.AnnotationKeyAppInstance)
value, err := rt.ParseAppInstanceValue(appInstanceAnnotation)
if err != nil {
return nil
}
return value
}
// GetAppName retrieve application name base on tracking method
func (rt *resourceTracking) GetAppName(un *unstructured.Unstructured, key string, trackingMethod v1alpha1.TrackingMethod) string {
retrieveAppInstanceValue := func() string {
appInstanceAnnotation := argokube.GetAppInstanceAnnotation(un, common.AnnotationKeyAppInstance)
value, err := rt.ParseAppInstanceValue(appInstanceAnnotation)
if err != nil {
return ""
value := rt.getAppInstanceValue(un, key, trackingMethod)
if value != nil {
return value.ApplicationName
}
return value.ApplicationName
return ""
}
switch trackingMethod {
case TrackingMethodLabel:
@@ -86,6 +95,18 @@ func (rt *resourceTracking) GetAppName(un *unstructured.Unstructured, key string
}
}
// GetAppInstance returns the representation of the app instance annotation.
// If the tracking method does not support metadata, or the annotation could
// not be parsed, it returns nil.
func (rt *resourceTracking) GetAppInstance(un *unstructured.Unstructured, key string, trackingMethod v1alpha1.TrackingMethod) *AppInstanceValue {
switch trackingMethod {
case TrackingMethodAnnotation, TrackingMethodAnnotationAndLabel:
return rt.getAppInstanceValue(un, key, trackingMethod)
default:
return nil
}
}
// SetAppInstance set label/annotation base on tracking method
func (rt *resourceTracking) SetAppInstance(un *unstructured.Unstructured, key, val, namespace string, trackingMethod v1alpha1.TrackingMethod) error {
setAppInstanceAnnotation := func() error {

View File

@@ -58,6 +58,7 @@ type Client interface {
Root() string
Init() error
Fetch(revision string) error
Submodule() error
Checkout(revision string, submoduleEnabled bool) error
LsRefs() (*Refs, error)
LsRemote(revision string) (string, error)
@@ -392,6 +393,17 @@ func (m *nativeGitClient) LsLargeFiles() ([]string, error) {
return ss, nil
}
// Submodule embed other repositories into this repository
func (m *nativeGitClient) Submodule() error {
if err := m.runCredentialedCmd("git", "submodule", "sync", "--recursive"); err != nil {
return err
}
if err := m.runCredentialedCmd("git", "submodule", "update", "--init", "--recursive"); err != nil {
return err
}
return nil
}
// Checkout checkout specified revision
func (m *nativeGitClient) Checkout(revision string, submoduleEnabled bool) error {
if revision == "" || revision == "HEAD" {
@@ -415,7 +427,7 @@ func (m *nativeGitClient) Checkout(revision string, submoduleEnabled bool) error
}
if _, err := os.Stat(m.root + "/.gitmodules"); !os.IsNotExist(err) {
if submoduleEnabled {
if err := m.runCredentialedCmd("git", "submodule", "update", "--init", "--recursive"); err != nil {
if err := m.Submodule(); err != nil {
return err
}
}

View File

@@ -4,6 +4,7 @@ import (
"fmt"
"os"
"os/exec"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
@@ -92,3 +93,117 @@ func Test_nativeGitClient_Fetch_Prune(t *testing.T) {
err = client.Fetch("")
assert.NoError(t, err)
}
func Test_nativeGitClient_Submodule(t *testing.T) {
tempDir, err := os.MkdirTemp("", "")
require.NoError(t, err)
foo := filepath.Join(tempDir, "foo")
err = os.Mkdir(foo, 0755)
require.NoError(t, err)
cmd := exec.Command("git", "init")
cmd.Dir = foo
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err = cmd.Run()
require.NoError(t, err)
bar := filepath.Join(tempDir, "bar")
err = os.Mkdir(bar, 0755)
require.NoError(t, err)
cmd = exec.Command("git", "init")
cmd.Dir = bar
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err = cmd.Run()
require.NoError(t, err)
cmd = exec.Command("git", "commit", "-m", "Initial commit", "--allow-empty")
cmd.Dir = bar
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err = cmd.Run()
require.NoError(t, err)
// Embed repository bar into repository foo
cmd = exec.Command("git", "submodule", "add", bar)
cmd.Dir = foo
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err = cmd.Run()
require.NoError(t, err)
cmd = exec.Command("git", "commit", "-m", "Initial commit")
cmd.Dir = foo
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err = cmd.Run()
require.NoError(t, err)
tempDir, err = os.MkdirTemp("", "")
require.NoError(t, err)
// Clone foo
cmd = exec.Command("git", "clone", foo)
cmd.Dir = tempDir
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err = cmd.Run()
require.NoError(t, err)
client, err := NewClient(fmt.Sprintf("file://%s", foo), NopCreds{}, true, false, "")
require.NoError(t, err)
err = client.Init()
require.NoError(t, err)
err = client.Fetch("")
assert.NoError(t, err)
commitSHA, err := client.LsRemote("HEAD")
assert.NoError(t, err)
// Call Checkout() with submoduleEnabled=false.
err = client.Checkout(commitSHA, false)
assert.NoError(t, err)
// Check if submodule url does not exist in .git/config
cmd = exec.Command("git", "config", "submodule.bar.url")
cmd.Dir = client.Root()
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err = cmd.Run()
assert.Error(t, err)
// Call Submodule() via Checkout() with submoduleEnabled=true.
err = client.Checkout(commitSHA, true)
assert.NoError(t, err)
// Check if the .gitmodule URL is reflected in .git/config
cmd = exec.Command("git", "config", "submodule.bar.url")
cmd.Dir = client.Root()
result, err := cmd.Output()
assert.NoError(t, err)
assert.Equal(t, bar+"\n", string(result))
// Change URL of submodule bar
cmd = exec.Command("git", "config", "--file=.gitmodules", "submodule.bar.url", bar+"baz")
cmd.Dir = client.Root()
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err = cmd.Run()
require.NoError(t, err)
// Call Submodule()
err = client.Submodule()
assert.NoError(t, err)
// Check if the URL change in .gitmodule is reflected in .git/config
cmd = exec.Command("git", "config", "submodule.bar.url")
cmd.Dir = client.Root()
result, err = cmd.Output()
assert.NoError(t, err)
assert.Equal(t, bar+"baz\n", string(result))
}

View File

@@ -3,9 +3,8 @@
package mocks
import (
mock "github.com/stretchr/testify/mock"
git "github.com/argoproj/argo-cd/v2/util/git"
mock "github.com/stretchr/testify/mock"
)
// Client is an autogenerated mock type for the Client type
@@ -203,6 +202,20 @@ func (_m *Client) Root() string {
return r0
}
// Submodule provides a mock function with given fields:
func (_m *Client) Submodule() error {
ret := _m.Called()
var r0 error
if rf, ok := ret.Get(0).(func() error); ok {
r0 = rf()
} else {
r0 = ret.Error(0)
}
return r0
}
// VerifyCommitSignature provides a mock function with given fields: _a0
func (_m *Client) VerifyCommitSignature(_a0 string) (string, error) {
ret := _m.Called(_a0)

View File

@@ -7,6 +7,7 @@ import (
"strings"
"time"
"github.com/argoproj/argo-cd/v2/common"
"github.com/sirupsen/logrus"
"golang.org/x/net/context"
"google.golang.org/grpc"
@@ -87,7 +88,7 @@ func BlockingDial(ctx context.Context, network, address string, creds credential
grpc.FailOnNonTempDialError(true),
grpc.WithContextDialer(dialer),
grpc.WithTransportCredentials(insecure.NewCredentials()), // we are handling TLS, so tell grpc not to
grpc.WithKeepaliveParams(keepalive.ClientParameters{Time: 10 * time.Second}),
grpc.WithKeepaliveParams(keepalive.ClientParameters{Time: common.GRPCKeepAliveTime}),
)
conn, err := grpc.DialContext(ctx, address, opts...)
var res interface{}
@@ -115,7 +116,7 @@ type TLSTestResult struct {
InsecureErr error
}
func TestTLS(address string) (*TLSTestResult, error) {
func TestTLS(address string, dialTime time.Duration) (*TLSTestResult, error) {
if parts := strings.Split(address, ":"); len(parts) == 1 {
// If port is unspecified, assume the most likely port
address += ":443"
@@ -124,12 +125,21 @@ func TestTLS(address string) (*TLSTestResult, error) {
var tlsConfig tls.Config
tlsConfig.InsecureSkipVerify = true
creds := credentials.NewTLS(&tlsConfig)
conn, err := BlockingDial(context.Background(), "tcp", address, creds)
// Set timeout when dialing to the server
// fix: https://github.com/argoproj/argo-cd/issues/9679
ctx, cancel := context.WithTimeout(context.Background(), dialTime)
defer cancel()
conn, err := BlockingDial(ctx, "tcp", address, creds)
if err == nil {
_ = conn.Close()
testResult.TLS = true
creds := credentials.NewTLS(&tls.Config{})
conn, err := BlockingDial(context.Background(), "tcp", address, creds)
ctx, cancel := context.WithTimeout(context.Background(), dialTime)
defer cancel()
conn, err := BlockingDial(ctx, "tcp", address, creds)
if err == nil {
_ = conn.Close()
} else {
@@ -142,7 +152,9 @@ func TestTLS(address string) (*TLSTestResult, error) {
// If we get here, we were unable to connect via TLS (even with InsecureSkipVerify: true)
// It may be because server is running without TLS, or because of real issues (e.g. connection
// refused). Test if server accepts plain-text connections
conn, err = BlockingDial(context.Background(), "tcp", address, nil)
ctx, cancel = context.WithTimeout(context.Background(), dialTime)
defer cancel()
conn, err = BlockingDial(ctx, "tcp", address, nil)
if err == nil {
_ = conn.Close()
testResult.TLS = false

View File

@@ -9,6 +9,7 @@ import (
"net/http"
"net/http/httptest"
"net/url"
"strings"
"testing"
gooidc "github.com/coreos/go-oidc"
@@ -133,7 +134,9 @@ requestedScopes: ["oidc"]`, oidcTestServer.URL),
app.HandleLogin(w, req)
assert.Contains(t, w.Body.String(), "certificate is not trusted")
if !strings.Contains(w.Body.String(), "certificate signed by unknown authority") && !strings.Contains(w.Body.String(), "certificate is not trusted") {
t.Fatal("did not receive expected certificate verification failure error")
}
cdSettings.OIDCTLSInsecureSkipVerify = true
@@ -145,6 +148,7 @@ requestedScopes: ["oidc"]`, oidcTestServer.URL),
app.HandleLogin(w, req)
assert.NotContains(t, w.Body.String(), "certificate is not trusted")
assert.NotContains(t, w.Body.String(), "certificate signed by unknown authority")
})
t.Run("dex certificate checking during login should toggle on config", func(t *testing.T) {
@@ -170,7 +174,9 @@ requestedScopes: ["oidc"]`, oidcTestServer.URL),
app.HandleLogin(w, req)
assert.Contains(t, w.Body.String(), "certificate signed by unknown authority")
if !strings.Contains(w.Body.String(), "certificate signed by unknown authority") && !strings.Contains(w.Body.String(), "certificate is not trusted") {
t.Fatal("did not receive expected certificate verification failure error")
}
cdSettings.OIDCTLSInsecureSkipVerify = true
@@ -181,6 +187,7 @@ requestedScopes: ["oidc"]`, oidcTestServer.URL),
app.HandleLogin(w, req)
assert.NotContains(t, w.Body.String(), "certificate is not trusted")
assert.NotContains(t, w.Body.String(), "certificate signed by unknown authority")
})
}
@@ -211,7 +218,9 @@ requestedScopes: ["oidc"]`, oidcTestServer.URL),
app.HandleCallback(w, req)
assert.Contains(t, w.Body.String(), "certificate is not trusted")
if !strings.Contains(w.Body.String(), "certificate signed by unknown authority") && !strings.Contains(w.Body.String(), "certificate is not trusted") {
t.Fatal("did not receive expected certificate verification failure error")
}
cdSettings.OIDCTLSInsecureSkipVerify = true
@@ -223,6 +232,7 @@ requestedScopes: ["oidc"]`, oidcTestServer.URL),
app.HandleCallback(w, req)
assert.NotContains(t, w.Body.String(), "certificate is not trusted")
assert.NotContains(t, w.Body.String(), "certificate signed by unknown authority")
})
t.Run("dex certificate checking during oidc callback should toggle on config", func(t *testing.T) {
@@ -248,7 +258,9 @@ requestedScopes: ["oidc"]`, oidcTestServer.URL),
app.HandleCallback(w, req)
assert.Contains(t, w.Body.String(), "certificate signed by unknown authority")
if !strings.Contains(w.Body.String(), "certificate signed by unknown authority") && !strings.Contains(w.Body.String(), "certificate is not trusted") {
t.Fatal("did not receive expected certificate verification failure error")
}
cdSettings.OIDCTLSInsecureSkipVerify = true
@@ -259,6 +271,7 @@ requestedScopes: ["oidc"]`, oidcTestServer.URL),
app.HandleCallback(w, req)
assert.NotContains(t, w.Body.String(), "certificate is not trusted")
assert.NotContains(t, w.Body.String(), "certificate signed by unknown authority")
})
}

View File

@@ -550,8 +550,10 @@ rootCA: |
require.NoError(t, err)
_, _, err = mgr.VerifyToken(tokenString)
// If the root CA is being respected, we won't get this error.
// If the root CA is being respected, we won't get this error. The error message is environment-dependent, so
// we check for either of the error messages associated with a failed cert check.
assert.NotContains(t, err.Error(), "certificate is not trusted")
assert.NotContains(t, err.Error(), "certificate signed by unknown authority")
})
t.Run("OIDC provider is Dex, TLS is configured", func(t *testing.T) {
@@ -585,7 +587,10 @@ rootCA: |
require.NoError(t, err)
_, _, err = mgr.VerifyToken(tokenString)
assert.ErrorContains(t, err, "certificate signed by unknown authority")
require.Error(t, err)
if !strings.Contains(err.Error(), "certificate signed by unknown authority") && !strings.Contains(err.Error(), "certificate is not trusted") {
t.Fatal("did not receive expected certificate verification failure error")
}
})
t.Run("OIDC provider is external, TLS is configured", func(t *testing.T) {
@@ -619,7 +624,10 @@ requestedScopes: ["oidc"]`, oidcTestServer.URL),
require.NoError(t, err)
_, _, err = mgr.VerifyToken(tokenString)
assert.ErrorContains(t, err, "certificate is not trusted")
require.Error(t, err)
if !strings.Contains(err.Error(), "certificate signed by unknown authority") && !strings.Contains(err.Error(), "certificate is not trusted") {
t.Fatal("did not receive expected certificate verification failure error")
}
})
t.Run("OIDC provider is Dex, TLS is configured", func(t *testing.T) {
@@ -653,7 +661,10 @@ requestedScopes: ["oidc"]`, oidcTestServer.URL),
require.NoError(t, err)
_, _, err = mgr.VerifyToken(tokenString)
assert.ErrorContains(t, err, "certificate signed by unknown authority")
require.Error(t, err)
if !strings.Contains(err.Error(), "certificate signed by unknown authority") && !strings.Contains(err.Error(), "certificate is not trusted") {
t.Fatal("did not receive expected certificate verification failure error")
}
})
t.Run("OIDC provider is external, TLS is configured, OIDCTLSInsecureSkipVerify is true", func(t *testing.T) {
@@ -688,6 +699,7 @@ requestedScopes: ["oidc"]`, oidcTestServer.URL),
require.NoError(t, err)
_, _, err = mgr.VerifyToken(tokenString)
assert.NotContains(t, err.Error(), "certificate is not trusted")
assert.NotContains(t, err.Error(), "certificate signed by unknown authority")
})
@@ -718,5 +730,6 @@ requestedScopes: ["oidc"]`, oidcTestServer.URL),
_, _, err = mgr.VerifyToken(tokenString)
// This is the error thrown when the test server's certificate _is_ being verified.
assert.NotContains(t, err.Error(), "certificate is not trusted")
assert.NotContains(t, err.Error(), "certificate signed by unknown authority")
})
}

View File

@@ -99,11 +99,15 @@ type ArgoCDSettings struct {
ServerRBACLogEnforceEnable bool `json:"serverRBACLogEnforceEnable"`
// ExecEnabled indicates whether the UI exec feature is enabled
ExecEnabled bool `json:"execEnabled"`
// ExecShells restricts which shells are allowed for `exec` and in which order they are tried
ExecShells []string `json:"execShells"`
// OIDCTLSInsecureSkipVerify determines whether certificate verification is skipped when verifying tokens with the
// configured OIDC provider (either external or the bundled Dex instance). Setting this to `true` will cause JWT
// token verification to pass despite the OIDC provider having an invalid certificate. Only set to `true` if you
// understand the risks.
OIDCTLSInsecureSkipVerify bool `json:"oidcTLSInsecureSkipVerify"`
// TrackingMethod defines the resource tracking method to be used
TrackingMethod string `json:"application.resourceTrackingMethod,omitempty"`
}
type GoogleAnalytics struct {
@@ -373,7 +377,7 @@ const (
kustomizePathPrefixKey = "kustomize.path"
// anonymousUserEnabledKey is the key which enables or disables anonymous user
anonymousUserEnabledKey = "users.anonymous.enabled"
// anonymousUserEnabledKey is the key which specifies token expiration duration
// userSessionDurationKey is the key which specifies token expiration duration
userSessionDurationKey = "users.session.duration"
// diffOptions is the key where diff options are configured
resourceCompareOptionsKey = "resource.compareoptions"
@@ -411,6 +415,8 @@ const (
helmValuesFileSchemesKey = "helm.valuesFileSchemes"
// execEnabledKey is the key to configure whether the UI exec feature is enabled
execEnabledKey = "exec.enabled"
// execShellsKey is the key to configure which shells are allowed for `exec` and in what order they are tried
execShellsKey = "exec.shells"
// oidcTLSInsecureSkipVerifyKey is the key to configure whether TLS cert verification is skipped for OIDC connections
oidcTLSInsecureSkipVerifyKey = "oidc.tls.insecure.skip.verify"
)
@@ -1278,7 +1284,15 @@ func updateSettingsFromConfigMap(settings *ArgoCDSettings, argoCDCM *apiv1.Confi
}
settings.InClusterEnabled = argoCDCM.Data[inClusterEnabledKey] != "false"
settings.ExecEnabled = argoCDCM.Data[execEnabledKey] == "true"
execShells := argoCDCM.Data[execShellsKey]
if execShells != "" {
settings.ExecShells = strings.Split(execShells, ",")
} else {
// Fall back to default. If you change this list, also change docs/operator-manual/argocd-cm.yaml.
settings.ExecShells = []string{"bash", "sh", "powershell", "cmd"}
}
settings.OIDCTLSInsecureSkipVerify = argoCDCM.Data[oidcTLSInsecureSkipVerifyKey] == "true"
settings.TrackingMethod = argoCDCM.Data[settingsResourceTrackingMethodKey]
}
// validateExternalURL ensures the external URL that is set on the configmap is valid

View File

@@ -248,6 +248,8 @@ func generate(opts CertOptions) ([]byte, crypto.PrivateKey, error) {
var validFor time.Duration
if opts.ValidFor == 0 {
validFor = 365 * 24 * time.Hour
} else {
validFor = opts.ValidFor
}
notAfter := notBefore.Add(validFor)

View File

@@ -252,6 +252,21 @@ func TestGenerate(t *testing.T) {
assert.NotNil(t, cert)
assert.GreaterOrEqual(t, (time.Now().Unix())+int64(1*time.Hour), cert.NotBefore.Unix())
})
for _, year := range []int{1, 2, 3, 10} {
t.Run(fmt.Sprintf("Create certificate with specified ValidFor %d year", year), func(t *testing.T) {
validFrom, validFor := time.Now(), 365*24*time.Hour*time.Duration(year)
opts := CertOptions{Hosts: []string{"localhost"}, Organization: "Acme", ValidFrom: validFrom, ValidFor: validFor}
certBytes, privKey, err := generate(opts)
assert.NoError(t, err)
assert.NotNil(t, privKey)
cert, err := x509.ParseCertificate(certBytes)
assert.NoError(t, err)
assert.NotNil(t, cert)
t.Logf("certificate expiration time %s", cert.NotAfter)
assert.Equal(t, validFrom.Unix()+int64(validFor.Seconds()), cert.NotAfter.Unix())
})
}
}
func TestGeneratePEM(t *testing.T) {