mirror of
https://github.com/argoproj/argo-cd.git
synced 2026-03-12 03:18:48 +01:00
Compare commits
51 Commits
v2.6.13
...
release-2.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3f170be710 | ||
|
|
63f0cbbe65 | ||
|
|
8b88abd951 | ||
|
|
394211c425 | ||
|
|
348d45cc6a | ||
|
|
975f78a126 | ||
|
|
4bf8e7fa66 | ||
|
|
869e722af2 | ||
|
|
38fbe0225a | ||
|
|
e274c3ab93 | ||
|
|
b763d194f4 | ||
|
|
8152092918 | ||
|
|
2f7922be9c | ||
|
|
f30a20a116 | ||
|
|
3b50226e76 | ||
|
|
0c6cfa306d | ||
|
|
1fb3d3fb11 | ||
|
|
d9ce38f357 | ||
|
|
d16bc2610b | ||
|
|
a443ab06e0 | ||
|
|
d5ba03b5f2 | ||
|
|
71d7bb67a4 | ||
|
|
6f0b7cb1a4 | ||
|
|
2b15397479 | ||
|
|
999132d9e8 | ||
|
|
eb34149e7a | ||
|
|
970329f99b | ||
|
|
fbf49f72e7 | ||
|
|
cc56e70718 | ||
|
|
8d758733ad | ||
|
|
0432c40a85 | ||
|
|
09d1a1356d | ||
|
|
343cc02339 | ||
|
|
84f935a615 | ||
|
|
3aa3b5c778 | ||
|
|
201513d1c9 | ||
|
|
cea6f80266 | ||
|
|
deddacf6cc | ||
|
|
bd7bd4b18c | ||
|
|
95587118a1 | ||
|
|
0d93b20796 | ||
|
|
1f6174eebb | ||
|
|
4261d08b29 | ||
|
|
966919792f | ||
|
|
2bb1587333 | ||
|
|
f685ee61e1 | ||
|
|
2883620bb0 | ||
|
|
261e665bd9 | ||
|
|
b05386d8b7 | ||
|
|
ce87bf951e | ||
|
|
6ecb081ac8 |
4
.github/workflows/ci-build.yaml
vendored
4
.github/workflows/ci-build.yaml
vendored
@@ -13,7 +13,7 @@ on:
|
||||
|
||||
env:
|
||||
# Golang version to use across CI steps
|
||||
GOLANG_VERSION: '1.19.10'
|
||||
GOLANG_VERSION: '1.20.10'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
@@ -427,7 +427,7 @@ jobs:
|
||||
run: |
|
||||
docker pull ghcr.io/dexidp/dex:v2.37.0
|
||||
docker pull argoproj/argo-cd-ci-builder:v1.0.0
|
||||
docker pull redis:7.0.11-alpine
|
||||
docker pull redis:7.0.14-alpine
|
||||
- name: Create target directory for binaries in the build-process
|
||||
run: |
|
||||
mkdir -p dist
|
||||
|
||||
2
.github/workflows/image.yaml
vendored
2
.github/workflows/image.yaml
vendored
@@ -10,7 +10,7 @@ on:
|
||||
types: [ labeled, unlabeled, opened, synchronize, reopened ]
|
||||
|
||||
env:
|
||||
GOLANG_VERSION: '1.19.10'
|
||||
GOLANG_VERSION: '1.20.10'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
|
||||
10
.github/workflows/release.yaml
vendored
10
.github/workflows/release.yaml
vendored
@@ -12,7 +12,7 @@ on:
|
||||
- "!release-v0*"
|
||||
|
||||
env:
|
||||
GOLANG_VERSION: '1.19.10'
|
||||
GOLANG_VERSION: '1.20.10'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -266,6 +266,14 @@ jobs:
|
||||
body: ${{ steps.release-notes.outputs.content }}
|
||||
if: ${{ env.DRY_RUN == 'true' }}
|
||||
|
||||
# Based on this suggestion: https://github.com/actions/runner-images/issues/2840#issuecomment-1284059930
|
||||
- name: Free Up Disk Space
|
||||
id: free-up-disk-space
|
||||
run: |
|
||||
df -h
|
||||
sudo rm -rf /usr/share/dotnet
|
||||
df -h
|
||||
|
||||
- name: Generate SBOM (spdx)
|
||||
id: spdx-builder
|
||||
env:
|
||||
|
||||
@@ -4,7 +4,7 @@ ARG BASE_IMAGE=docker.io/library/ubuntu:22.04
|
||||
# Initial stage which pulls prepares build dependencies and CLI tooling we need for our final image
|
||||
# Also used as the image in CI jobs so needs all dependencies
|
||||
####################################################################################################
|
||||
FROM docker.io/library/golang:1.19.10@sha256:83f9f840072d05ad4d90ce4ac7cb2427632d6b89d5ffc558f18f9577ec8188c0 AS builder
|
||||
FROM docker.io/library/golang:1.20.10@sha256:077ff85b374b23916b4b41835e242e5a3ddad9fc537ea7e980f230431747d245 AS builder
|
||||
|
||||
RUN echo 'deb http://deb.debian.org/debian buster-backports main' >> /etc/apt/sources.list
|
||||
|
||||
@@ -99,7 +99,7 @@ RUN HOST_ARCH=$TARGETARCH NODE_ENV='production' NODE_ONLINE_ENV='online' NODE_OP
|
||||
####################################################################################################
|
||||
# Argo CD Build stage which performs the actual build of Argo CD binaries
|
||||
####################################################################################################
|
||||
FROM --platform=$BUILDPLATFORM docker.io/library/golang:1.19.10@sha256:83f9f840072d05ad4d90ce4ac7cb2427632d6b89d5ffc558f18f9577ec8188c0 AS argocd-build
|
||||
FROM --platform=$BUILDPLATFORM docker.io/library/golang:1.20.10@sha256:077ff85b374b23916b4b41835e242e5a3ddad9fc537ea7e980f230431747d245 AS argocd-build
|
||||
|
||||
WORKDIR /go/src/github.com/argoproj/argo-cd
|
||||
|
||||
|
||||
@@ -35,9 +35,7 @@ impact on Argo CD before opening an issue at least roughly.
|
||||
|
||||
## Supported Versions
|
||||
|
||||
We currently support the most recent release (`N`, e.g. `1.8`) and the release
|
||||
previous to the most recent one (`N-1`, e.g. `1.7`). With the release of
|
||||
`N+1`, `N-1` drops out of support and `N` becomes `N-1`.
|
||||
We currently support the last 3 minor versions of Argo CD with security and bug fixes.
|
||||
|
||||
We regularly perform patch releases (e.g. `1.8.5` and `1.7.12`) for the
|
||||
supported versions, which will contain fixes for security vulnerabilities and
|
||||
|
||||
1
USERS.md
1
USERS.md
@@ -175,6 +175,7 @@ Currently, the following organizations are **officially** using Argo CD:
|
||||
1. [Patreon](https://www.patreon.com/)
|
||||
1. [PayPay](https://paypay.ne.jp/)
|
||||
1. [Peloton Interactive](https://www.onepeloton.com/)
|
||||
1. [PGS](https://www.pgs.com)
|
||||
1. [Pigment](https://www.gopigment.com/)
|
||||
1. [Pipefy](https://www.pipefy.com/)
|
||||
1. [Pismo](https://pismo.io/)
|
||||
|
||||
@@ -267,7 +267,10 @@ func (r *Render) Replace(tmpl string, replaceMap map[string]interface{}, useGoTe
|
||||
return tmpl, nil
|
||||
}
|
||||
|
||||
fstTmpl := fasttemplate.New(tmpl, "{{", "}}")
|
||||
fstTmpl, err := fasttemplate.NewTemplate(tmpl, "{{", "}}")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("invalid template: %w", err)
|
||||
}
|
||||
replacedTmpl := fstTmpl.ExecuteFuncString(func(w io.Writer, tag string) (int, error) {
|
||||
trimmedTag := strings.TrimSpace(tag)
|
||||
replacement, ok := replaceMap[trimmedTag].(string)
|
||||
|
||||
@@ -464,6 +464,14 @@ func TestRenderTemplateParamsGoTemplate(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Render_Replace_no_panic_on_missing_closing_brace(t *testing.T) {
|
||||
r := &Render{}
|
||||
assert.NotPanics(t, func() {
|
||||
_, err := r.Replace("{{properly.closed}} {{improperly.closed}", nil, false)
|
||||
assert.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestRenderTemplateKeys(t *testing.T) {
|
||||
t.Run("fasttemplate", func(t *testing.T) {
|
||||
application := &argoappsv1.Application{
|
||||
|
||||
@@ -55,6 +55,7 @@ func NewCommand() *cobra.Command {
|
||||
argocdRepoServerStrictTLS bool
|
||||
configMapName string
|
||||
secretName string
|
||||
applicationNamespaces []string
|
||||
)
|
||||
var command = cobra.Command{
|
||||
Use: "controller",
|
||||
@@ -138,7 +139,7 @@ func NewCommand() *cobra.Command {
|
||||
log.Infof("serving metrics on port %d", metricsPort)
|
||||
log.Infof("loading configuration %d", metricsPort)
|
||||
|
||||
ctrl := notificationscontroller.NewController(k8sClient, dynamicClient, argocdService, namespace, appLabelSelector, registry, secretName, configMapName)
|
||||
ctrl := notificationscontroller.NewController(k8sClient, dynamicClient, argocdService, namespace, applicationNamespaces, appLabelSelector, registry, secretName, configMapName)
|
||||
err = ctrl.Init(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -161,5 +162,6 @@ func NewCommand() *cobra.Command {
|
||||
command.Flags().BoolVar(&argocdRepoServerStrictTLS, "argocd-repo-server-strict-tls", false, "Perform strict validation of TLS certificates when connecting to repo server")
|
||||
command.Flags().StringVar(&configMapName, "config-map-name", "argocd-notifications-cm", "Set notifications ConfigMap name")
|
||||
command.Flags().StringVar(&secretName, "secret-name", "argocd-notifications-secret", "Set notifications Secret name")
|
||||
command.Flags().StringSliceVar(&applicationNamespaces, "application-namespaces", env.StringsFromEnv("ARGOCD_APPLICATION_NAMESPACES", []string{}, ","), "List of additional namespaces that this controller should send notifications for")
|
||||
return &command
|
||||
}
|
||||
|
||||
@@ -84,6 +84,8 @@ func NewCommand() *cobra.Command {
|
||||
allowOutOfBoundsSymlinks bool
|
||||
streamedManifestMaxTarSize string
|
||||
streamedManifestMaxExtractedSize string
|
||||
helmManifestMaxExtractedSize string
|
||||
disableManifestMaxExtractedSize bool
|
||||
)
|
||||
var command = cobra.Command{
|
||||
Use: cliName,
|
||||
@@ -122,6 +124,9 @@ func NewCommand() *cobra.Command {
|
||||
streamedManifestMaxExtractedSizeQuantity, err := resource.ParseQuantity(streamedManifestMaxExtractedSize)
|
||||
errors.CheckError(err)
|
||||
|
||||
helmManifestMaxExtractedSizeQuantity, err := resource.ParseQuantity(helmManifestMaxExtractedSize)
|
||||
errors.CheckError(err)
|
||||
|
||||
askPassServer := askpass.NewServer()
|
||||
metricsServer := metrics.NewMetricsServer()
|
||||
cacheutil.CollectMetrics(redisClient, metricsServer)
|
||||
@@ -136,6 +141,7 @@ func NewCommand() *cobra.Command {
|
||||
AllowOutOfBoundsSymlinks: allowOutOfBoundsSymlinks,
|
||||
StreamedManifestMaxExtractedSize: streamedManifestMaxExtractedSizeQuantity.ToDec().Value(),
|
||||
StreamedManifestMaxTarSize: streamedManifestMaxTarSizeQuantity.ToDec().Value(),
|
||||
HelmManifestMaxExtractedSize: helmManifestMaxExtractedSizeQuantity.ToDec().Value(),
|
||||
}, askPassServer)
|
||||
errors.CheckError(err)
|
||||
|
||||
@@ -216,6 +222,8 @@ func NewCommand() *cobra.Command {
|
||||
command.Flags().BoolVar(&allowOutOfBoundsSymlinks, "allow-oob-symlinks", env.ParseBoolFromEnv("ARGOCD_REPO_SERVER_ALLOW_OUT_OF_BOUNDS_SYMLINKS", false), "Allow out-of-bounds symlinks in repositories (not recommended)")
|
||||
command.Flags().StringVar(&streamedManifestMaxTarSize, "streamed-manifest-max-tar-size", env.StringFromEnv("ARGOCD_REPO_SERVER_STREAMED_MANIFEST_MAX_TAR_SIZE", "100M"), "Maximum size of streamed manifest archives")
|
||||
command.Flags().StringVar(&streamedManifestMaxExtractedSize, "streamed-manifest-max-extracted-size", env.StringFromEnv("ARGOCD_REPO_SERVER_STREAMED_MANIFEST_MAX_EXTRACTED_SIZE", "1G"), "Maximum size of streamed manifest archives when extracted")
|
||||
command.Flags().StringVar(&helmManifestMaxExtractedSize, "helm-manifest-max-extracted-size", env.StringFromEnv("ARGOCD_REPO_SERVER_HELM_MANIFEST_MAX_EXTRACTED_SIZE", "1G"), "Maximum size of helm manifest archives when extracted")
|
||||
command.Flags().BoolVar(&disableManifestMaxExtractedSize, "disable-helm-manifest-max-extracted-size", env.ParseBoolFromEnv("ARGOCD_REPO_SERVER_DISABLE_HELM_MANIFEST_MAX_EXTRACTED_SIZE", false), "Disable maximum size of helm manifest archives when extracted")
|
||||
tlsConfigCustomizerSrc = tls.AddTLSFlagsToCmd(&command)
|
||||
cacheSrc = reposervercache.AddCacheFlagsToCmd(&command, func(client *redis.Client) {
|
||||
redisClient = client
|
||||
|
||||
@@ -97,6 +97,14 @@ func runCommand(ctx context.Context, command Command, path string, env []string)
|
||||
<-ctx.Done()
|
||||
// Kill by group ID to make sure child processes are killed. The - tells `kill` that it's a group ID.
|
||||
// Since we didn't set Pgid in SysProcAttr, the group ID is the same as the process ID. https://pkg.go.dev/syscall#SysProcAttr
|
||||
|
||||
// Sending a TERM signal first to allow any potential cleanup if needed, and then sending a KILL signal
|
||||
_ = sysCallTerm(-cmd.Process.Pid)
|
||||
|
||||
// modify cleanup timeout to allow process to cleanup
|
||||
cleanupTimeout := 5 * time.Second
|
||||
time.Sleep(cleanupTimeout)
|
||||
|
||||
_ = sysCallKill(-cmd.Process.Pid)
|
||||
}()
|
||||
|
||||
|
||||
@@ -369,6 +369,28 @@ func TestRunCommandEmptyCommand(t *testing.T) {
|
||||
assert.ErrorContains(t, err, "Command is empty")
|
||||
}
|
||||
|
||||
// TestRunCommandContextTimeoutWithGracefulTermination makes sure that the process is given enough time to cleanup before sending SIGKILL.
|
||||
func TestRunCommandContextTimeoutWithCleanup(t *testing.T) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 900*time.Millisecond)
|
||||
defer cancel()
|
||||
|
||||
// Use a subshell so there's a child command.
|
||||
// This command sleeps for 4 seconds which is currently less than the 5 second delay between SIGTERM and SIGKILL signal and then exits successfully.
|
||||
command := Command{
|
||||
Command: []string{"sh", "-c"},
|
||||
Args: []string{`(trap 'echo "cleanup completed"; exit' TERM; sleep 4)`},
|
||||
}
|
||||
|
||||
before := time.Now()
|
||||
output, err := runCommand(ctx, command, "", []string{})
|
||||
after := time.Now()
|
||||
|
||||
assert.Error(t, err) // The command should time out, causing an error.
|
||||
assert.Less(t, after.Sub(before), 1*time.Second)
|
||||
// The command should still have completed the cleanup after termination.
|
||||
assert.Contains(t, output, "cleanup completed")
|
||||
}
|
||||
|
||||
func Test_getParametersAnnouncement_empty_command(t *testing.T) {
|
||||
staticYAML := `
|
||||
- name: static-a
|
||||
|
||||
@@ -14,3 +14,7 @@ func newSysProcAttr(setpgid bool) *syscall.SysProcAttr {
|
||||
func sysCallKill(pid int) error {
|
||||
return syscall.Kill(pid, syscall.SIGKILL)
|
||||
}
|
||||
|
||||
func sysCallTerm(pid int) error {
|
||||
return syscall.Kill(pid, syscall.SIGTERM)
|
||||
}
|
||||
|
||||
@@ -14,3 +14,7 @@ func newSysProcAttr(setpgid bool) *syscall.SysProcAttr {
|
||||
func sysCallKill(pid int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func sysCallTerm(pid int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
6
controller/cache/cache.go
vendored
6
controller/cache/cache.go
vendored
@@ -702,12 +702,14 @@ func (c *liveStateCache) handleModEvent(oldCluster *appv1.Cluster, newCluster *a
|
||||
}
|
||||
|
||||
func (c *liveStateCache) handleDeleteEvent(clusterServer string) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
c.lock.RLock()
|
||||
cluster, ok := c.clusters[clusterServer]
|
||||
c.lock.RUnlock()
|
||||
if ok {
|
||||
cluster.Invalidate()
|
||||
c.lock.Lock()
|
||||
delete(c.clusters, clusterServer)
|
||||
c.lock.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
97
controller/cache/cache_test.go
vendored
97
controller/cache/cache_test.go
vendored
@@ -1,10 +1,13 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net"
|
||||
"net/url"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
apierr "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
@@ -14,8 +17,10 @@ import (
|
||||
"github.com/argoproj/gitops-engine/pkg/cache"
|
||||
"github.com/argoproj/gitops-engine/pkg/cache/mocks"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
|
||||
appv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||
argosettings "github.com/argoproj/argo-cd/v2/util/settings"
|
||||
)
|
||||
|
||||
type netError string
|
||||
@@ -106,6 +111,98 @@ func TestHandleAddEvent_ClusterExcluded(t *testing.T) {
|
||||
assert.Len(t, clustersCache.clusters, 0)
|
||||
}
|
||||
|
||||
func TestHandleDeleteEvent_CacheDeadlock(t *testing.T) {
|
||||
testCluster := &appv1.Cluster{
|
||||
Server: "https://mycluster",
|
||||
Config: appv1.ClusterConfig{Username: "bar"},
|
||||
}
|
||||
fakeClient := fake.NewSimpleClientset()
|
||||
settingsMgr := argosettings.NewSettingsManager(context.TODO(), fakeClient, "argocd")
|
||||
externalLockRef := sync.RWMutex{}
|
||||
gitopsEngineClusterCache := &mocks.ClusterCache{}
|
||||
clustersCache := liveStateCache{
|
||||
clusters: map[string]cache.ClusterCache{
|
||||
testCluster.Server: gitopsEngineClusterCache,
|
||||
},
|
||||
clusterFilter: func(cluster *appv1.Cluster) bool {
|
||||
return true
|
||||
},
|
||||
settingsMgr: settingsMgr,
|
||||
// Set the lock here so we can reference it later
|
||||
// nolint We need to overwrite here to have access to the lock
|
||||
lock: externalLockRef,
|
||||
}
|
||||
channel := make(chan string)
|
||||
// Mocked lock held by the gitops-engine cluster cache
|
||||
mockMutex := sync.RWMutex{}
|
||||
// Locks to force trigger condition during test
|
||||
// Condition order:
|
||||
// EnsuredSynced -> Locks gitops-engine
|
||||
// handleDeleteEvent -> Locks liveStateCache
|
||||
// EnsureSynced via sync, newResource, populateResourceInfoHandler -> attempts to Lock liveStateCache
|
||||
// handleDeleteEvent via cluster.Invalidate -> attempts to Lock gitops-engine
|
||||
handleDeleteWasCalled := sync.Mutex{}
|
||||
engineHoldsLock := sync.Mutex{}
|
||||
handleDeleteWasCalled.Lock()
|
||||
engineHoldsLock.Lock()
|
||||
gitopsEngineClusterCache.On("EnsureSynced").Run(func(args mock.Arguments) {
|
||||
// Held by EnsureSync calling into sync and watchEvents
|
||||
mockMutex.Lock()
|
||||
defer mockMutex.Unlock()
|
||||
// Continue Execution of timer func
|
||||
engineHoldsLock.Unlock()
|
||||
// Wait for handleDeleteEvent to be called triggering the lock
|
||||
// on the liveStateCache
|
||||
handleDeleteWasCalled.Lock()
|
||||
t.Logf("handleDelete was called, EnsureSynced continuing...")
|
||||
handleDeleteWasCalled.Unlock()
|
||||
// Try and obtain the lock on the liveStateCache
|
||||
alreadyFailed := !externalLockRef.TryLock()
|
||||
if alreadyFailed {
|
||||
channel <- "DEADLOCKED -- EnsureSynced could not obtain lock on liveStateCache"
|
||||
return
|
||||
}
|
||||
externalLockRef.Lock()
|
||||
t.Logf("EnsureSynce was able to lock liveStateCache")
|
||||
externalLockRef.Unlock()
|
||||
}).Return(nil).Once()
|
||||
gitopsEngineClusterCache.On("Invalidate").Run(func(args mock.Arguments) {
|
||||
// If deadlock is fixed should be able to acquire lock here
|
||||
alreadyFailed := !mockMutex.TryLock()
|
||||
if alreadyFailed {
|
||||
channel <- "DEADLOCKED -- Invalidate could not obtain lock on gitops-engine"
|
||||
return
|
||||
}
|
||||
mockMutex.Lock()
|
||||
t.Logf("Invalidate was able to lock gitops-engine cache")
|
||||
mockMutex.Unlock()
|
||||
}).Return()
|
||||
go func() {
|
||||
// Start the gitops-engine lock holds
|
||||
go func() {
|
||||
err := gitopsEngineClusterCache.EnsureSynced()
|
||||
if err != nil {
|
||||
assert.Fail(t, err.Error())
|
||||
}
|
||||
}()
|
||||
// Wait for EnsureSynced to grab the lock for gitops-engine
|
||||
engineHoldsLock.Lock()
|
||||
t.Log("EnsureSynced has obtained lock on gitops-engine")
|
||||
engineHoldsLock.Unlock()
|
||||
// Run in background
|
||||
go clustersCache.handleDeleteEvent(testCluster.Server)
|
||||
// Allow execution to continue on clusters cache call to trigger lock
|
||||
handleDeleteWasCalled.Unlock()
|
||||
channel <- "PASSED"
|
||||
}()
|
||||
select {
|
||||
case str := <-channel:
|
||||
assert.Equal(t, "PASSED", str, str)
|
||||
case <-time.After(5 * time.Second):
|
||||
assert.Fail(t, "Ended up in deadlock")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsRetryableError(t *testing.T) {
|
||||
var (
|
||||
tlsHandshakeTimeoutErr net.Error = netError("net/http: TLS handshake timeout")
|
||||
|
||||
@@ -71,6 +71,8 @@ We supply a `ClusterRole` and `ClusterRoleBinding` suitable for this purpose in
|
||||
kubectl apply -f examples/k8s-rbac/argocd-server-applications/
|
||||
```
|
||||
|
||||
`argocd-notifications-controller-rbac-clusterrole.yaml` and `argocd-notifications-controller-rbac-clusterrolebinding.yaml` are used to support notifications controller to notify apps in all namespaces.
|
||||
|
||||
!!! note
|
||||
At some later point in time, we may make this cluster role part of the default installation manifests.
|
||||
|
||||
|
||||
@@ -166,6 +166,7 @@ spec:
|
||||
- CreateNamespace=true # Namespace Auto-Creation ensures that namespace specified as the application destination exists in the destination cluster.
|
||||
- PrunePropagationPolicy=foreground # Supported policies are background, foreground and orphan.
|
||||
- PruneLast=true # Allow the ability for resource pruning to happen as a final, implicit wave of a sync operation
|
||||
- RespectIgnoreDifferences=true # When syncing changes, respect fields ignored by the ignoreDifferences configuration
|
||||
managedNamespaceMetadata: # Sets the metadata for the application namespace. Only valid if CreateNamespace=true (see above), otherwise it's a no-op.
|
||||
labels: # The labels to set on the application namespace
|
||||
any: label
|
||||
@@ -184,18 +185,24 @@ spec:
|
||||
maxDuration: 3m # the maximum amount of time allowed for the backoff strategy
|
||||
|
||||
# Will ignore differences between live and desired states during the diff. Note that these configurations are not
|
||||
# used during the sync process.
|
||||
# used during the sync process unless the `RespectIgnoreDifferences=true` sync option is enabled.
|
||||
ignoreDifferences:
|
||||
# for the specified json pointers
|
||||
- group: apps
|
||||
kind: Deployment
|
||||
jsonPointers:
|
||||
- /spec/replicas
|
||||
- kind: ConfigMap
|
||||
jqPathExpressions:
|
||||
- '.data["config.yaml"].auth'
|
||||
# for the specified managedFields managers
|
||||
- group: "*"
|
||||
kind: "*"
|
||||
managedFieldsManagers:
|
||||
- kube-controller-manager
|
||||
# Name and namespace are optional. If specified, they must match exactly, these are not glob patterns.
|
||||
name: my-deployment
|
||||
namespace: my-namespace
|
||||
|
||||
# RevisionHistoryLimit limits the number of items kept in the application's revision history, which is used for
|
||||
# informational purposes as well as for rollbacks to previous versions. This should only be changed in exceptional
|
||||
|
||||
@@ -153,7 +153,7 @@ Or, a shorter way (using [path.Match](https://golang.org/pkg/path/#Match) syntax
|
||||
|
||||
```yaml
|
||||
- path: /d/*
|
||||
- path: /d/[f|g]
|
||||
- path: /d/[fg]
|
||||
exclude: true
|
||||
```
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ Generators are primarily based on the data source that they use to generate the
|
||||
|
||||
As of this writing there are eight generators:
|
||||
|
||||
- [List generator](Generators-List.md): The List generator allows you to target Argo CD Applications to clusters based on a fixed list of cluster name/URL values.
|
||||
- [List generator](Generators-List.md): The List generator allows you to target Argo CD Applications to clusters based on a fixed list of any chosen key/value element pairs.
|
||||
- [Cluster generator](Generators-Cluster.md): The Cluster generator allows you to target Argo CD Applications to clusters, based on the list of clusters defined within (and managed by) Argo CD (which includes automatically responding to cluster addition/removal events from Argo CD).
|
||||
- [Git generator](Generators-Git.md): The Git generator allows you to create Applications based on files within a Git repository, or based on the directory structure of a Git repository.
|
||||
- [Matrix generator](Generators-Matrix.md): The Matrix generator may be used to combine the generated parameters of two separate generators.
|
||||
|
||||
@@ -15,7 +15,7 @@ As an experimental feature, progressive syncs must be explicitly enabled, in one
|
||||
|
||||
1. Pass `--enable-progressive-syncs` to the ApplicationSet controller args.
|
||||
1. Set `ARGOCD_APPLICATIONSET_CONTROLLER_ENABLE_PROGRESSIVE_SYNCS=true` in the ApplicationSet controller environment variables.
|
||||
1. Set `applicationsetcontroller.enable.progressive.syncs: true` in the Argo CD ConfigMap.
|
||||
1. Set `applicationsetcontroller.enable.progressive.syncs: true` in the Argo CD `argocd-cmd-params-cm` ConfigMap.
|
||||
|
||||
## Strategies
|
||||
|
||||
|
||||
@@ -98,7 +98,7 @@ The AppProject CRD is the Kubernetes resource object representing a logical grou
|
||||
It is defined by the following key pieces of information:
|
||||
|
||||
* `sourceRepos` reference to the repositories that applications within the project can pull manifests from.
|
||||
* `destinations` reference to clusters and namespaces that applications within the project can deploy into (don't use the `name` field, only the `server` field is matched).
|
||||
* `destinations` reference to clusters and namespaces that applications within the project can deploy into.
|
||||
* `roles` list of entities with definitions of their access to resources within the project.
|
||||
|
||||
!!!warning "Projects which can deploy to the Argo CD namespace grant admin access"
|
||||
|
||||
@@ -17,8 +17,9 @@ kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/st
|
||||
* Add Email username and password token to `argocd-notifications-secret` secret
|
||||
|
||||
```bash
|
||||
export EMAIL_USER=<your-username>
|
||||
export PASSWORD=<your-password>
|
||||
EMAIL_USER=<your-username>
|
||||
PASSWORD=<your-password>
|
||||
|
||||
kubectl apply -n argocd -f - << EOF
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
|
||||
@@ -58,15 +58,19 @@ metadata:
|
||||
|
||||

|
||||
|
||||
If the message is set to 140 characters or more, it will be truncate.
|
||||
|
||||
```yaml
|
||||
template.app-deployed: |
|
||||
message: |
|
||||
Application {{.app.metadata.name}} is now running new version of deployments manifests.
|
||||
github:
|
||||
repoURLPath: "{{.app.spec.source.repoURL}}"
|
||||
revisionPath: "{{.app.status.operationState.syncResult.revision}}"
|
||||
status:
|
||||
state: success
|
||||
label: "continuous-delivery/{{.app.metadata.name}}"
|
||||
targetURL: "{{.context.argocdUrl}}/applications/{{.app.metadata.name}}?operation=true"
|
||||
```
|
||||
|
||||
**Notes**:
|
||||
- If the message is set to 140 characters or more, it will be truncated.
|
||||
- If `github.repoURLPath` and `github.revisionPath` are same as above, they can be omitted.
|
||||
|
||||
@@ -86,3 +86,13 @@ spec:
|
||||
clusters:
|
||||
- in-cluster
|
||||
- cluster1
|
||||
|
||||
# By default, apps may sync to any cluster specified under the `destinations` field, even if they are not
|
||||
# scoped to this project. Set the following field to `true` to restrict apps in this cluster to only clusters
|
||||
# scoped to this project.
|
||||
permitOnlyProjectScopedClusters: false
|
||||
|
||||
# When using Applications-in-any-namespace, this field determines which namespaces this AppProject permits
|
||||
# Applications to reside in. Details: https://argo-cd.readthedocs.io/en/stable/operator-manual/app-any-namespace/
|
||||
sourceNamespaces:
|
||||
- "argocd-apps-*"
|
||||
|
||||
@@ -55,6 +55,13 @@ corresponds to the `action` path `action/extensions/DaemonSet/restart`. You can
|
||||
also use glob patterns in the action path: `action/*` (or regex patterns if you have
|
||||
[enabled the `regex` match mode](https://github.com/argoproj/argo-cd/blob/master/docs/operator-manual/argocd-rbac-cm.yaml)).
|
||||
|
||||
If the resource is not under a group (for examples, Pods or ConfigMaps), then omit the group name from your RBAC
|
||||
configuration:
|
||||
|
||||
```csv
|
||||
p, example-user, applications, action//Pod/maintenance-off, default/*, allow
|
||||
```
|
||||
|
||||
#### The `exec` resource
|
||||
|
||||
`exec` is a special resource. When enabled with the `create` action, this privilege allows a user to `exec` into Pods via
|
||||
|
||||
@@ -15,7 +15,9 @@ argocd-repo-server [flags]
|
||||
```
|
||||
--allow-oob-symlinks Allow out-of-bounds symlinks in repositories (not recommended)
|
||||
--default-cache-expiration duration Cache expiration default (default 24h0m0s)
|
||||
--disable-helm-manifest-max-extracted-size Disable maximum size of helm manifest archives when extracted
|
||||
--disable-tls Disable TLS on the gRPC endpoint
|
||||
--helm-manifest-max-extracted-size string Maximum size of helm manifest archives when extracted (default "1G")
|
||||
-h, --help help for argocd-repo-server
|
||||
--logformat string Set the logging format. One of: text|json (default "text")
|
||||
--loglevel string Set the logging level. One of: debug|info|warn|error (default "info")
|
||||
|
||||
26
docs/user-guide/annotations-and-labels.md
Normal file
26
docs/user-guide/annotations-and-labels.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# Annotations and Labels used by Argo CD
|
||||
|
||||
## Annotations
|
||||
|
||||
| Annotation key | Target resource(es) | Possible values | Description |
|
||||
|--------------------------------------------|---------------------|---------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| argocd.argoproj.io/application-set-refresh | ApplicationSet | `"true"` | Added when an ApplicationSet is requested to be refreshed by a webhook. The ApplicationSet controller will remove this annotation at the end of reconciliation. |
|
||||
| argocd.argoproj.io/compare-options | any | [see compare options docs](compare-options.md) | Configures how an app's current state is compared to its desired state. |
|
||||
| argocd.argoproj.io/hook | any | [see resource hooks docs](resource_hooks.md) | Used to configure [resource hooks](resource_hooks.md). |
|
||||
| argocd.argoproj.io/hook-delete-policy | any | [see resource hooks docs](resource_hooks.md#hook-deletion-policies) | Used to set a [resource hook's deletion policy](resource_hooks.md#hook-deletion-policies). |
|
||||
| argocd.argoproj.io/manifest-generate-paths | Application | [see scaling docs](../operator-manual/high_availability.md#webhook-and-manifest-paths-annotation) | Used to avoid unnecessary Application refreshes, especially in mono-repos. |
|
||||
| argocd.argoproj.io/refresh | Application | `normal`, `hard` | Indicates that app needs to be refreshed. Removed by application controller after app is refreshed. Value `"hard"` means manifest cache and target cluster state cache should be invalidated before refresh. |
|
||||
| argocd.argoproj.io/skip-reconcile | Application | `"true"` | Indicates to the Argo CD application controller that the Application should not be reconciled. See the [skip reconcile documentation](skip_reconcile.md) for use cases. |
|
||||
| argocd.argoproj.io/sync-options | any | [see sync options docs](sync-options.md) | Provides a variety of settings to determine how an Application's resources are synced. |
|
||||
| argocd.argoproj.io/sync-wave | any | [see sync waves docs](sync-waves.md) | |
|
||||
| argocd.argoproj.io/tracking-id | any | any | Used by Argo CD to track resources it manages. See [resource tracking docs](resource_tracking.md) for details. |
|
||||
| link.argocd.argoproj.io/{some link name} | any | An http(s) URL | Adds a link to the Argo CD UI for the resource. See [external URL docs](external-url.md) for details. |
|
||||
| pref.argocd.argoproj.io/default-pod-sort | Application | [see UI customization docs](../operator-manual/ui-customization.md) | Sets the Application's default grouping mechanism. |
|
||||
| pref.argocd.argoproj.io/default-view | Application | [see UI customization docs](../operator-manual/ui-customization.md) | Sets the Application's default view mode (e.g. "tree" or "list") |
|
||||
|
||||
## Labels
|
||||
|
||||
| Label key | Target resource(es) | Possible values | Description |
|
||||
|--------------------------------|---------------------|---------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| argocd.argoproj.io/instance | Application | any | Recommended tracking label to [avoid conflicts with other tools which use `app.kubernetes.io/instance`](../faq.md#why-is-my-app-out-of-sync-even-after-syncing. |
|
||||
| argocd.argoproj.io/secret-type | Secret | `cluster`, `repository`, `repo-creds` | Identifies certain types of Secrets used by Argo CD. See the [Declarative Setup docs](../operator-manual/declarative-setup.md) for details. |
|
||||
@@ -68,6 +68,15 @@ spec:
|
||||
|
||||
The above configuration will ignore differences from all fields owned by `kube-controller-manager` for all resources belonging to this application.
|
||||
|
||||
If you have a slash `/` in your pointer path, you can use the `~1` character. For example:
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
ignoreDifferences:
|
||||
- kind: Node
|
||||
jsonPointers: /metadata/labels/node-role.kubernetes.io~1worker
|
||||
```
|
||||
|
||||
## System-Level Configuration
|
||||
|
||||
The comparison of resources with well-known issues can be customized at a system level. Ignored differences can be configured for a specified group and kind
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: argocd-notifications-controller-cluster-apps
|
||||
app.kubernetes.io/part-of: argocd
|
||||
app.kubernetes.io/component: notifications-controller
|
||||
name: argocd-notifications-controller-cluster-apps
|
||||
rules:
|
||||
- apiGroups:
|
||||
- "argoproj.io"
|
||||
resources:
|
||||
- "applications"
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- update
|
||||
- patch
|
||||
@@ -0,0 +1,16 @@
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: argocd-notifications-controller-cluster-apps
|
||||
app.kubernetes.io/part-of: argocd
|
||||
app.kubernetes.io/component: notifications-controller
|
||||
name: argocd-notifications-controller-cluster-apps
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
name: argocd-notifications-controller-cluster-apps
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: argocd-notifications-controller
|
||||
namespace: argocd
|
||||
2
go.mod
2
go.mod
@@ -1,6 +1,6 @@
|
||||
module github.com/argoproj/argo-cd/v2
|
||||
|
||||
go 1.18
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
code.gitea.io/sdk/gitea v0.15.1
|
||||
|
||||
@@ -5,7 +5,7 @@ kind: Kustomization
|
||||
images:
|
||||
- name: quay.io/argoproj/argocd
|
||||
newName: quay.io/argoproj/argocd
|
||||
newTag: v2.6.13
|
||||
newTag: v2.6.15
|
||||
resources:
|
||||
- ./application-controller
|
||||
- ./dex
|
||||
|
||||
@@ -31,6 +31,13 @@ spec:
|
||||
containers:
|
||||
- command:
|
||||
- argocd-notifications
|
||||
env:
|
||||
- name: ARGOCD_APPLICATION_NAMESPACES
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
key: application.namespaces
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
workingDir: /app
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
|
||||
@@ -23,7 +23,7 @@ spec:
|
||||
serviceAccountName: argocd-redis
|
||||
containers:
|
||||
- name: redis
|
||||
image: redis:7.0.11-alpine
|
||||
image: redis:7.0.14-alpine
|
||||
imagePullPolicy: Always
|
||||
args:
|
||||
- "--save"
|
||||
|
||||
@@ -137,6 +137,18 @@ spec:
|
||||
key: reposerver.streamed.manifest.max.extracted.size
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
- name: ARGOCD_REPO_SERVER_HELM_MANIFEST_MAX_EXTRACTED_SIZE
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
key: reposerver.helm.manifest.max.extracted.size
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
- name: ARGOCD_REPO_SERVER_DISABLE_HELM_MANIFEST_MAX_EXTRACTED_SIZE
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: argocd-cmd-params-cm
|
||||
key: reposerver.disable.helm.manifest.max.extracted.size
|
||||
optional: true
|
||||
- name: ARGOCD_GIT_MODULES_ENABLED
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
|
||||
@@ -15557,7 +15557,7 @@ spec:
|
||||
key: applicationsetcontroller.enable.progressive.syncs
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.6.13
|
||||
image: quay.io/argoproj/argocd:v2.6.15
|
||||
imagePullPolicy: Always
|
||||
name: argocd-applicationset-controller
|
||||
ports:
|
||||
@@ -15639,7 +15639,7 @@ spec:
|
||||
- ""
|
||||
- --appendonly
|
||||
- "no"
|
||||
image: redis:7.0.11-alpine
|
||||
image: redis:7.0.14-alpine
|
||||
imagePullPolicy: Always
|
||||
name: redis
|
||||
ports:
|
||||
@@ -15809,6 +15809,18 @@ spec:
|
||||
key: reposerver.streamed.manifest.max.extracted.size
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
- name: ARGOCD_REPO_SERVER_HELM_MANIFEST_MAX_EXTRACTED_SIZE
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
key: reposerver.helm.manifest.max.extracted.size
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
- name: ARGOCD_REPO_SERVER_DISABLE_HELM_MANIFEST_MAX_EXTRACTED_SIZE
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
key: reposerver.disable.helm.manifest.max.extracted.size
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
- name: ARGOCD_GIT_MODULES_ENABLED
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
@@ -15821,7 +15833,7 @@ spec:
|
||||
value: /helm-working-dir
|
||||
- name: HELM_DATA_HOME
|
||||
value: /helm-working-dir
|
||||
image: quay.io/argoproj/argocd:v2.6.13
|
||||
image: quay.io/argoproj/argocd:v2.6.15
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -15873,7 +15885,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /var/run/argocd/argocd-cmp-server
|
||||
image: quay.io/argoproj/argocd:v2.6.13
|
||||
image: quay.io/argoproj/argocd:v2.6.15
|
||||
name: copyutil
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
@@ -16080,7 +16092,7 @@ spec:
|
||||
key: application.namespaces
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.6.13
|
||||
image: quay.io/argoproj/argocd:v2.6.15
|
||||
imagePullPolicy: Always
|
||||
name: argocd-application-controller
|
||||
ports:
|
||||
|
||||
@@ -12,4 +12,4 @@ resources:
|
||||
images:
|
||||
- name: quay.io/argoproj/argocd
|
||||
newName: quay.io/argoproj/argocd
|
||||
newTag: v2.6.13
|
||||
newTag: v2.6.15
|
||||
|
||||
@@ -11,7 +11,7 @@ patchesStrategicMerge:
|
||||
images:
|
||||
- name: quay.io/argoproj/argocd
|
||||
newName: quay.io/argoproj/argocd
|
||||
newTag: v2.6.13
|
||||
newTag: v2.6.15
|
||||
resources:
|
||||
- ../../base/application-controller
|
||||
- ../../base/applicationset-controller
|
||||
|
||||
@@ -1080,7 +1080,13 @@ spec:
|
||||
args:
|
||||
- /readonly/haproxy_init.sh
|
||||
securityContext:
|
||||
null
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
runAsNonRoot: true
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
volumeMounts:
|
||||
- name: config-volume
|
||||
mountPath: /readonly
|
||||
@@ -1092,7 +1098,13 @@ spec:
|
||||
image: haproxy:2.6.14-alpine
|
||||
imagePullPolicy: IfNotPresent
|
||||
securityContext:
|
||||
null
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
runAsNonRoot: true
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /healthz
|
||||
@@ -1179,7 +1191,7 @@ spec:
|
||||
automountServiceAccountToken: false
|
||||
initContainers:
|
||||
- name: config-init
|
||||
image: redis:7.0.11-alpine
|
||||
image: redis:7.0.14-alpine
|
||||
imagePullPolicy: IfNotPresent
|
||||
resources:
|
||||
{}
|
||||
@@ -1188,7 +1200,14 @@ spec:
|
||||
args:
|
||||
- /readonly-config/init.sh
|
||||
securityContext:
|
||||
null
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
runAsNonRoot: true
|
||||
runAsUser: 1000
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
env:
|
||||
- name: SENTINEL_ID_0
|
||||
value: 3c0d9c0320bb34888c2df5757c718ce6ca992ce6
|
||||
@@ -1206,14 +1225,21 @@ spec:
|
||||
|
||||
containers:
|
||||
- name: redis
|
||||
image: redis:7.0.11-alpine
|
||||
image: redis:7.0.14-alpine
|
||||
imagePullPolicy: IfNotPresent
|
||||
command:
|
||||
- redis-server
|
||||
args:
|
||||
- /data/conf/redis.conf
|
||||
securityContext:
|
||||
null
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
runAsNonRoot: true
|
||||
runAsUser: 1000
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
livenessProbe:
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 15
|
||||
@@ -1256,14 +1282,21 @@ spec:
|
||||
- /bin/sh
|
||||
- /readonly-config/trigger-failover-if-master.sh
|
||||
- name: sentinel
|
||||
image: redis:7.0.11-alpine
|
||||
image: redis:7.0.14-alpine
|
||||
imagePullPolicy: IfNotPresent
|
||||
command:
|
||||
- redis-sentinel
|
||||
args:
|
||||
- /data/conf/sentinel.conf
|
||||
securityContext:
|
||||
null
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
runAsNonRoot: true
|
||||
runAsUser: 1000
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
livenessProbe:
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 15
|
||||
@@ -1300,14 +1333,21 @@ spec:
|
||||
{}
|
||||
|
||||
- name: split-brain-fix
|
||||
image: redis:7.0.11-alpine
|
||||
image: redis:7.0.14-alpine
|
||||
imagePullPolicy: IfNotPresent
|
||||
command:
|
||||
- sh
|
||||
args:
|
||||
- /readonly-config/fix-split-brain.sh
|
||||
securityContext:
|
||||
null
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
runAsNonRoot: true
|
||||
runAsUser: 1000
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
env:
|
||||
- name: SENTINEL_ID_0
|
||||
value: 3c0d9c0320bb34888c2df5757c718ce6ca992ce6
|
||||
|
||||
@@ -18,7 +18,7 @@ redis-ha:
|
||||
client: 6m
|
||||
checkInterval: 3s
|
||||
image:
|
||||
tag: 7.0.11-alpine
|
||||
tag: 7.0.14-alpine
|
||||
containerSecurityContext: null
|
||||
sentinel:
|
||||
bind: "0.0.0.0"
|
||||
|
||||
@@ -16758,7 +16758,7 @@ spec:
|
||||
key: applicationsetcontroller.enable.progressive.syncs
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.6.13
|
||||
image: quay.io/argoproj/argocd:v2.6.15
|
||||
imagePullPolicy: Always
|
||||
name: argocd-applicationset-controller
|
||||
ports:
|
||||
@@ -16868,7 +16868,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /shared/argocd-dex
|
||||
image: quay.io/argoproj/argocd:v2.6.13
|
||||
image: quay.io/argoproj/argocd:v2.6.15
|
||||
imagePullPolicy: Always
|
||||
name: copyutil
|
||||
securityContext:
|
||||
@@ -16921,7 +16921,14 @@ spec:
|
||||
containers:
|
||||
- command:
|
||||
- argocd-notifications
|
||||
image: quay.io/argoproj/argocd:v2.6.13
|
||||
env:
|
||||
- name: ARGOCD_APPLICATION_NAMESPACES
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
key: application.namespaces
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.6.15
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
@@ -17212,6 +17219,18 @@ spec:
|
||||
key: reposerver.streamed.manifest.max.extracted.size
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
- name: ARGOCD_REPO_SERVER_HELM_MANIFEST_MAX_EXTRACTED_SIZE
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
key: reposerver.helm.manifest.max.extracted.size
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
- name: ARGOCD_REPO_SERVER_DISABLE_HELM_MANIFEST_MAX_EXTRACTED_SIZE
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
key: reposerver.disable.helm.manifest.max.extracted.size
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
- name: ARGOCD_GIT_MODULES_ENABLED
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
@@ -17224,7 +17243,7 @@ spec:
|
||||
value: /helm-working-dir
|
||||
- name: HELM_DATA_HOME
|
||||
value: /helm-working-dir
|
||||
image: quay.io/argoproj/argocd:v2.6.13
|
||||
image: quay.io/argoproj/argocd:v2.6.15
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -17276,7 +17295,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /var/run/argocd/argocd-cmp-server
|
||||
image: quay.io/argoproj/argocd:v2.6.13
|
||||
image: quay.io/argoproj/argocd:v2.6.15
|
||||
name: copyutil
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
@@ -17555,7 +17574,7 @@ spec:
|
||||
key: server.enable.proxy.extension
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.6.13
|
||||
image: quay.io/argoproj/argocd:v2.6.15
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
@@ -17791,7 +17810,7 @@ spec:
|
||||
key: application.namespaces
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.6.13
|
||||
image: quay.io/argoproj/argocd:v2.6.15
|
||||
imagePullPolicy: Always
|
||||
name: argocd-application-controller
|
||||
ports:
|
||||
@@ -17868,7 +17887,7 @@ spec:
|
||||
- /data/conf/redis.conf
|
||||
command:
|
||||
- redis-server
|
||||
image: redis:7.0.11-alpine
|
||||
image: redis:7.0.14-alpine
|
||||
imagePullPolicy: IfNotPresent
|
||||
lifecycle:
|
||||
preStop:
|
||||
@@ -17921,7 +17940,7 @@ spec:
|
||||
- /data/conf/sentinel.conf
|
||||
command:
|
||||
- redis-sentinel
|
||||
image: redis:7.0.11-alpine
|
||||
image: redis:7.0.14-alpine
|
||||
imagePullPolicy: IfNotPresent
|
||||
lifecycle: {}
|
||||
livenessProbe:
|
||||
@@ -17973,7 +17992,7 @@ spec:
|
||||
value: 40000915ab58c3fa8fd888fb8b24711944e6cbb4
|
||||
- name: SENTINEL_ID_2
|
||||
value: 2bbec7894d954a8af3bb54d13eaec53cb024e2ca
|
||||
image: redis:7.0.11-alpine
|
||||
image: redis:7.0.14-alpine
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: split-brain-fix
|
||||
resources: {}
|
||||
@@ -18002,7 +18021,7 @@ spec:
|
||||
value: 40000915ab58c3fa8fd888fb8b24711944e6cbb4
|
||||
- name: SENTINEL_ID_2
|
||||
value: 2bbec7894d954a8af3bb54d13eaec53cb024e2ca
|
||||
image: redis:7.0.11-alpine
|
||||
image: redis:7.0.14-alpine
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: config-init
|
||||
securityContext:
|
||||
|
||||
@@ -1562,7 +1562,7 @@ spec:
|
||||
key: applicationsetcontroller.enable.progressive.syncs
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.6.13
|
||||
image: quay.io/argoproj/argocd:v2.6.15
|
||||
imagePullPolicy: Always
|
||||
name: argocd-applicationset-controller
|
||||
ports:
|
||||
@@ -1672,7 +1672,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /shared/argocd-dex
|
||||
image: quay.io/argoproj/argocd:v2.6.13
|
||||
image: quay.io/argoproj/argocd:v2.6.15
|
||||
imagePullPolicy: Always
|
||||
name: copyutil
|
||||
securityContext:
|
||||
@@ -1725,7 +1725,14 @@ spec:
|
||||
containers:
|
||||
- command:
|
||||
- argocd-notifications
|
||||
image: quay.io/argoproj/argocd:v2.6.13
|
||||
env:
|
||||
- name: ARGOCD_APPLICATION_NAMESPACES
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
key: application.namespaces
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.6.15
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
@@ -2016,6 +2023,18 @@ spec:
|
||||
key: reposerver.streamed.manifest.max.extracted.size
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
- name: ARGOCD_REPO_SERVER_HELM_MANIFEST_MAX_EXTRACTED_SIZE
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
key: reposerver.helm.manifest.max.extracted.size
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
- name: ARGOCD_REPO_SERVER_DISABLE_HELM_MANIFEST_MAX_EXTRACTED_SIZE
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
key: reposerver.disable.helm.manifest.max.extracted.size
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
- name: ARGOCD_GIT_MODULES_ENABLED
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
@@ -2028,7 +2047,7 @@ spec:
|
||||
value: /helm-working-dir
|
||||
- name: HELM_DATA_HOME
|
||||
value: /helm-working-dir
|
||||
image: quay.io/argoproj/argocd:v2.6.13
|
||||
image: quay.io/argoproj/argocd:v2.6.15
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -2080,7 +2099,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /var/run/argocd/argocd-cmp-server
|
||||
image: quay.io/argoproj/argocd:v2.6.13
|
||||
image: quay.io/argoproj/argocd:v2.6.15
|
||||
name: copyutil
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
@@ -2359,7 +2378,7 @@ spec:
|
||||
key: server.enable.proxy.extension
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.6.13
|
||||
image: quay.io/argoproj/argocd:v2.6.15
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
@@ -2595,7 +2614,7 @@ spec:
|
||||
key: application.namespaces
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.6.13
|
||||
image: quay.io/argoproj/argocd:v2.6.15
|
||||
imagePullPolicy: Always
|
||||
name: argocd-application-controller
|
||||
ports:
|
||||
@@ -2672,7 +2691,7 @@ spec:
|
||||
- /data/conf/redis.conf
|
||||
command:
|
||||
- redis-server
|
||||
image: redis:7.0.11-alpine
|
||||
image: redis:7.0.14-alpine
|
||||
imagePullPolicy: IfNotPresent
|
||||
lifecycle:
|
||||
preStop:
|
||||
@@ -2725,7 +2744,7 @@ spec:
|
||||
- /data/conf/sentinel.conf
|
||||
command:
|
||||
- redis-sentinel
|
||||
image: redis:7.0.11-alpine
|
||||
image: redis:7.0.14-alpine
|
||||
imagePullPolicy: IfNotPresent
|
||||
lifecycle: {}
|
||||
livenessProbe:
|
||||
@@ -2777,7 +2796,7 @@ spec:
|
||||
value: 40000915ab58c3fa8fd888fb8b24711944e6cbb4
|
||||
- name: SENTINEL_ID_2
|
||||
value: 2bbec7894d954a8af3bb54d13eaec53cb024e2ca
|
||||
image: redis:7.0.11-alpine
|
||||
image: redis:7.0.14-alpine
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: split-brain-fix
|
||||
resources: {}
|
||||
@@ -2806,7 +2825,7 @@ spec:
|
||||
value: 40000915ab58c3fa8fd888fb8b24711944e6cbb4
|
||||
- name: SENTINEL_ID_2
|
||||
value: 2bbec7894d954a8af3bb54d13eaec53cb024e2ca
|
||||
image: redis:7.0.11-alpine
|
||||
image: redis:7.0.14-alpine
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: config-init
|
||||
securityContext:
|
||||
|
||||
@@ -15877,7 +15877,7 @@ spec:
|
||||
key: applicationsetcontroller.enable.progressive.syncs
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.6.13
|
||||
image: quay.io/argoproj/argocd:v2.6.15
|
||||
imagePullPolicy: Always
|
||||
name: argocd-applicationset-controller
|
||||
ports:
|
||||
@@ -15987,7 +15987,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /shared/argocd-dex
|
||||
image: quay.io/argoproj/argocd:v2.6.13
|
||||
image: quay.io/argoproj/argocd:v2.6.15
|
||||
imagePullPolicy: Always
|
||||
name: copyutil
|
||||
securityContext:
|
||||
@@ -16040,7 +16040,14 @@ spec:
|
||||
containers:
|
||||
- command:
|
||||
- argocd-notifications
|
||||
image: quay.io/argoproj/argocd:v2.6.13
|
||||
env:
|
||||
- name: ARGOCD_APPLICATION_NAMESPACES
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
key: application.namespaces
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.6.15
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
@@ -16117,7 +16124,7 @@ spec:
|
||||
- ""
|
||||
- --appendonly
|
||||
- "no"
|
||||
image: redis:7.0.11-alpine
|
||||
image: redis:7.0.14-alpine
|
||||
imagePullPolicy: Always
|
||||
name: redis
|
||||
ports:
|
||||
@@ -16287,6 +16294,18 @@ spec:
|
||||
key: reposerver.streamed.manifest.max.extracted.size
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
- name: ARGOCD_REPO_SERVER_HELM_MANIFEST_MAX_EXTRACTED_SIZE
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
key: reposerver.helm.manifest.max.extracted.size
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
- name: ARGOCD_REPO_SERVER_DISABLE_HELM_MANIFEST_MAX_EXTRACTED_SIZE
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
key: reposerver.disable.helm.manifest.max.extracted.size
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
- name: ARGOCD_GIT_MODULES_ENABLED
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
@@ -16299,7 +16318,7 @@ spec:
|
||||
value: /helm-working-dir
|
||||
- name: HELM_DATA_HOME
|
||||
value: /helm-working-dir
|
||||
image: quay.io/argoproj/argocd:v2.6.13
|
||||
image: quay.io/argoproj/argocd:v2.6.15
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -16351,7 +16370,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /var/run/argocd/argocd-cmp-server
|
||||
image: quay.io/argoproj/argocd:v2.6.13
|
||||
image: quay.io/argoproj/argocd:v2.6.15
|
||||
name: copyutil
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
@@ -16626,7 +16645,7 @@ spec:
|
||||
key: server.enable.proxy.extension
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.6.13
|
||||
image: quay.io/argoproj/argocd:v2.6.15
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
@@ -16860,7 +16879,7 @@ spec:
|
||||
key: application.namespaces
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.6.13
|
||||
image: quay.io/argoproj/argocd:v2.6.15
|
||||
imagePullPolicy: Always
|
||||
name: argocd-application-controller
|
||||
ports:
|
||||
|
||||
@@ -681,7 +681,7 @@ spec:
|
||||
key: applicationsetcontroller.enable.progressive.syncs
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.6.13
|
||||
image: quay.io/argoproj/argocd:v2.6.15
|
||||
imagePullPolicy: Always
|
||||
name: argocd-applicationset-controller
|
||||
ports:
|
||||
@@ -791,7 +791,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /shared/argocd-dex
|
||||
image: quay.io/argoproj/argocd:v2.6.13
|
||||
image: quay.io/argoproj/argocd:v2.6.15
|
||||
imagePullPolicy: Always
|
||||
name: copyutil
|
||||
securityContext:
|
||||
@@ -844,7 +844,14 @@ spec:
|
||||
containers:
|
||||
- command:
|
||||
- argocd-notifications
|
||||
image: quay.io/argoproj/argocd:v2.6.13
|
||||
env:
|
||||
- name: ARGOCD_APPLICATION_NAMESPACES
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
key: application.namespaces
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.6.15
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
@@ -921,7 +928,7 @@ spec:
|
||||
- ""
|
||||
- --appendonly
|
||||
- "no"
|
||||
image: redis:7.0.11-alpine
|
||||
image: redis:7.0.14-alpine
|
||||
imagePullPolicy: Always
|
||||
name: redis
|
||||
ports:
|
||||
@@ -1091,6 +1098,18 @@ spec:
|
||||
key: reposerver.streamed.manifest.max.extracted.size
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
- name: ARGOCD_REPO_SERVER_HELM_MANIFEST_MAX_EXTRACTED_SIZE
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
key: reposerver.helm.manifest.max.extracted.size
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
- name: ARGOCD_REPO_SERVER_DISABLE_HELM_MANIFEST_MAX_EXTRACTED_SIZE
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
key: reposerver.disable.helm.manifest.max.extracted.size
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
- name: ARGOCD_GIT_MODULES_ENABLED
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
@@ -1103,7 +1122,7 @@ spec:
|
||||
value: /helm-working-dir
|
||||
- name: HELM_DATA_HOME
|
||||
value: /helm-working-dir
|
||||
image: quay.io/argoproj/argocd:v2.6.13
|
||||
image: quay.io/argoproj/argocd:v2.6.15
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -1155,7 +1174,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /var/run/argocd/argocd-cmp-server
|
||||
image: quay.io/argoproj/argocd:v2.6.13
|
||||
image: quay.io/argoproj/argocd:v2.6.15
|
||||
name: copyutil
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
@@ -1430,7 +1449,7 @@ spec:
|
||||
key: server.enable.proxy.extension
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.6.13
|
||||
image: quay.io/argoproj/argocd:v2.6.15
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
@@ -1664,7 +1683,7 @@ spec:
|
||||
key: application.namespaces
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.6.13
|
||||
image: quay.io/argoproj/argocd:v2.6.15
|
||||
imagePullPolicy: Always
|
||||
name: argocd-application-controller
|
||||
ports:
|
||||
|
||||
@@ -163,6 +163,7 @@ nav:
|
||||
- user-guide/external-url.md
|
||||
- user-guide/extra_info.md
|
||||
- Notification subscriptions: user-guide/subscriptions.md
|
||||
- user-guide/annotations-and-labels.md
|
||||
- Command Reference: user-guide/commands/argocd.md
|
||||
- Developer Guide:
|
||||
- developer-guide/index.md
|
||||
|
||||
@@ -6,6 +6,8 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/util/glob"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/util/notification/k8s"
|
||||
|
||||
service "github.com/argoproj/argo-cd/v2/util/notification/argocd"
|
||||
@@ -52,14 +54,21 @@ func NewController(
|
||||
client dynamic.Interface,
|
||||
argocdService service.Service,
|
||||
namespace string,
|
||||
applicationNamespaces []string,
|
||||
appLabelSelector string,
|
||||
registry *controller.MetricsRegistry,
|
||||
secretName string,
|
||||
configMapName string,
|
||||
) *notificationController {
|
||||
appClient := client.Resource(applications)
|
||||
appInformer := newInformer(appClient.Namespace(namespace), appLabelSelector)
|
||||
appProjInformer := newInformer(newAppProjClient(client, namespace), "")
|
||||
var appClient dynamic.ResourceInterface
|
||||
namespaceableAppClient := client.Resource(applications)
|
||||
appClient = namespaceableAppClient
|
||||
if len(applicationNamespaces) == 0 {
|
||||
appClient = namespaceableAppClient.Namespace(namespace)
|
||||
}
|
||||
|
||||
appInformer := newInformer(appClient, namespace, applicationNamespaces, appLabelSelector)
|
||||
appProjInformer := newInformer(newAppProjClient(client, namespace), namespace, []string{namespace}, "")
|
||||
secretInformer := k8s.NewSecretInformer(k8sClient, namespace, secretName)
|
||||
configMapInformer := k8s.NewConfigMapInformer(k8sClient, namespace, configMapName)
|
||||
apiFactory := api.NewFactory(settings.GetFactorySettings(argocdService, secretName, configMapName), namespace, secretInformer, configMapInformer)
|
||||
@@ -70,12 +79,15 @@ func NewController(
|
||||
appInformer: appInformer,
|
||||
appProjInformer: appProjInformer,
|
||||
apiFactory: apiFactory}
|
||||
res.ctrl = controller.NewController(appClient, appInformer, apiFactory,
|
||||
res.ctrl = controller.NewController(namespaceableAppClient, appInformer, apiFactory,
|
||||
controller.WithSkipProcessing(func(obj v1.Object) (bool, string) {
|
||||
app, ok := (obj).(*unstructured.Unstructured)
|
||||
if !ok {
|
||||
return false, ""
|
||||
}
|
||||
if checkAppNotInAdditionalNamespaces(app, namespace, applicationNamespaces) {
|
||||
return true, "app is not in one of the application-namespaces, nor the notification controller namespace"
|
||||
}
|
||||
return !isAppSyncStatusRefreshed(app, log.WithField("app", obj.GetName())), "sync status out of date"
|
||||
}),
|
||||
controller.WithMetricsRegistry(registry),
|
||||
@@ -83,6 +95,11 @@ func NewController(
|
||||
return res
|
||||
}
|
||||
|
||||
// Check if app is not in the namespace where the controller is in, and also app is not in one of the applicationNamespaces
|
||||
func checkAppNotInAdditionalNamespaces(app *unstructured.Unstructured, namespace string, applicationNamespaces []string) bool {
|
||||
return namespace != app.GetNamespace() && !glob.MatchStringInList(applicationNamespaces, app.GetNamespace(), false)
|
||||
}
|
||||
|
||||
func (c *notificationController) alterDestinations(obj v1.Object, destinations services.Destinations, cfg api.Config) services.Destinations {
|
||||
app, ok := (obj).(*unstructured.Unstructured)
|
||||
if !ok {
|
||||
@@ -96,21 +113,38 @@ func (c *notificationController) alterDestinations(obj v1.Object, destinations s
|
||||
return destinations
|
||||
}
|
||||
|
||||
func newInformer(resClient dynamic.ResourceInterface, selector string) cache.SharedIndexInformer {
|
||||
func newInformer(resClient dynamic.ResourceInterface, controllerNamespace string, applicationNamespaces []string, selector string) cache.SharedIndexInformer {
|
||||
informer := cache.NewSharedIndexInformer(
|
||||
&cache.ListWatch{
|
||||
ListFunc: func(options v1.ListOptions) (object runtime.Object, err error) {
|
||||
ListFunc: func(options v1.ListOptions) (runtime.Object, error) {
|
||||
// We are only interested in apps that exist in namespaces the
|
||||
// user wants to be enabled.
|
||||
options.LabelSelector = selector
|
||||
return resClient.List(context.Background(), options)
|
||||
appList, err := resClient.List(context.TODO(), options)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to list applications: %w", err)
|
||||
}
|
||||
newItems := []unstructured.Unstructured{}
|
||||
for _, res := range appList.Items {
|
||||
if controllerNamespace == res.GetNamespace() || glob.MatchStringInList(applicationNamespaces, res.GetNamespace(), false) {
|
||||
newItems = append(newItems, res)
|
||||
}
|
||||
}
|
||||
appList.Items = newItems
|
||||
return appList, nil
|
||||
},
|
||||
WatchFunc: func(options v1.ListOptions) (watch.Interface, error) {
|
||||
options.LabelSelector = selector
|
||||
return resClient.Watch(context.Background(), options)
|
||||
return resClient.Watch(context.TODO(), options)
|
||||
},
|
||||
},
|
||||
&unstructured.Unstructured{},
|
||||
resyncPeriod,
|
||||
cache.Indexers{},
|
||||
cache.Indexers{
|
||||
cache.NamespaceIndex: func(obj interface{}) ([]string, error) {
|
||||
return cache.MetaNamespaceIndexFunc(obj)
|
||||
},
|
||||
},
|
||||
)
|
||||
return informer
|
||||
}
|
||||
|
||||
32
notification_controller/controller/controller_test.go
Normal file
32
notification_controller/controller/controller_test.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
)
|
||||
|
||||
func TestCheckAppNotInAdditionalNamespaces(t *testing.T) {
|
||||
app := &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"spec": map[string]interface{}{},
|
||||
},
|
||||
}
|
||||
namespace := "argocd"
|
||||
var applicationNamespaces []string
|
||||
applicationNamespaces = append(applicationNamespaces, "namespace1")
|
||||
applicationNamespaces = append(applicationNamespaces, "namespace2")
|
||||
|
||||
// app is in same namespace as controller's namespace
|
||||
app.SetNamespace(namespace)
|
||||
assert.False(t, checkAppNotInAdditionalNamespaces(app, namespace, applicationNamespaces))
|
||||
|
||||
// app is not in the namespace as controller's namespace, but it is in one of the applicationNamespaces
|
||||
app.SetNamespace("namespace2")
|
||||
assert.False(t, checkAppNotInAdditionalNamespaces(app, "", applicationNamespaces))
|
||||
|
||||
// app is not in the namespace as controller's namespace, and it is not in any of the applicationNamespaces
|
||||
app.SetNamespace("namespace3")
|
||||
assert.True(t, checkAppNotInAdditionalNamespaces(app, "", applicationNamespaces))
|
||||
}
|
||||
@@ -109,6 +109,8 @@ type RepoServerInitConstants struct {
|
||||
AllowOutOfBoundsSymlinks bool
|
||||
StreamedManifestMaxExtractedSize int64
|
||||
StreamedManifestMaxTarSize int64
|
||||
HelmManifestMaxExtractedSize int64
|
||||
DisableHelmManifestMaxExtractedSize bool
|
||||
}
|
||||
|
||||
// NewService returns a new instance of the Manifest service
|
||||
@@ -348,7 +350,7 @@ func (s *Service) runRepoOperation(
|
||||
if source.Helm != nil {
|
||||
helmPassCredentials = source.Helm.PassCredentials
|
||||
}
|
||||
chartPath, closer, err := helmClient.ExtractChart(source.Chart, revision, helmPassCredentials)
|
||||
chartPath, closer, err := helmClient.ExtractChart(source.Chart, revision, helmPassCredentials, s.initConstants.HelmManifestMaxExtractedSize, s.initConstants.DisableHelmManifestMaxExtractedSize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -402,9 +404,15 @@ func (s *Service) runRepoOperation(
|
||||
}
|
||||
}
|
||||
|
||||
commitSHA, err := gitClient.CommitSHA()
|
||||
if err != nil {
|
||||
return err
|
||||
var commitSHA string
|
||||
if hasMultipleSources {
|
||||
commitSHA = revision
|
||||
} else {
|
||||
commit, err := gitClient.CommitSHA()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get commit SHA: %w", err)
|
||||
}
|
||||
commitSHA = commit
|
||||
}
|
||||
|
||||
// double-check locking
|
||||
@@ -945,11 +953,13 @@ func getHelmRepos(appPath string, repositories []*v1alpha1.Repository, helmRepoC
|
||||
|
||||
repos := make([]helm.HelmRepository, 0)
|
||||
for _, dep := range dependencies {
|
||||
// find matching repo credentials by URL or name
|
||||
repo, ok := reposByUrl[dep.Repo]
|
||||
if !ok && dep.Name != "" {
|
||||
repo, ok = reposByName[dep.Name]
|
||||
}
|
||||
if !ok {
|
||||
// if no matching repo credentials found, use the repo creds from the credential list
|
||||
repo = &v1alpha1.Repository{Repo: dep.Repo, Name: dep.Name, EnableOCI: dep.EnableOCI}
|
||||
if repositoryCredential := getRepoCredential(helmRepoCreds, dep.Repo); repositoryCredential != nil {
|
||||
repo.EnableOCI = repositoryCredential.EnableOCI
|
||||
@@ -958,6 +968,16 @@ func getHelmRepos(appPath string, repositories []*v1alpha1.Repository, helmRepoC
|
||||
repo.SSHPrivateKey = repositoryCredential.SSHPrivateKey
|
||||
repo.TLSClientCertData = repositoryCredential.TLSClientCertData
|
||||
repo.TLSClientCertKey = repositoryCredential.TLSClientCertKey
|
||||
} else if repo.EnableOCI {
|
||||
// finally if repo is OCI and no credentials found, use the first OCI credential matching by hostname
|
||||
// see https://github.com/argoproj/argo-cd/issues/14636
|
||||
for _, cred := range repositories {
|
||||
if depURL, err := url.Parse("oci://" + dep.Repo); err == nil && cred.EnableOCI && depURL.Host == cred.Repo {
|
||||
repo.Username = cred.Username
|
||||
repo.Password = cred.Password
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
repos = append(repos, helm.HelmRepository{Name: repo.Name, Repo: repo.Repo, Creds: repo.GetHelmCreds(), EnableOci: repo.EnableOCI})
|
||||
|
||||
@@ -1825,6 +1825,44 @@ func TestGenerateManifestWithAnnotatedAndRegularGitTagHashes(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateManifestWithAnnotatedTagsAndMultiSourceApp(t *testing.T) {
|
||||
annotatedGitTaghash := "95249be61b028d566c29d47b19e65c5603388a41"
|
||||
|
||||
service := newServiceWithCommitSHA(".", annotatedGitTaghash)
|
||||
|
||||
refSources := map[string]*argoappv1.RefTarget{}
|
||||
|
||||
refSources["$global"] = &argoappv1.RefTarget{
|
||||
TargetRevision: annotatedGitTaghash,
|
||||
}
|
||||
|
||||
refSources["$default"] = &argoappv1.RefTarget{
|
||||
TargetRevision: annotatedGitTaghash,
|
||||
}
|
||||
|
||||
manifestRequest := &apiclient.ManifestRequest{
|
||||
Repo: &argoappv1.Repository{},
|
||||
ApplicationSource: &argoappv1.ApplicationSource{
|
||||
TargetRevision: annotatedGitTaghash,
|
||||
Helm: &argoappv1.ApplicationSourceHelm{
|
||||
ValueFiles: []string{"$global/values.yaml", "$default/secrets.yaml"},
|
||||
},
|
||||
},
|
||||
HasMultipleSources: true,
|
||||
NoCache: true,
|
||||
RefSources: refSources,
|
||||
}
|
||||
|
||||
response, err := service.GenerateManifest(context.Background(), manifestRequest)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected %s", err)
|
||||
}
|
||||
|
||||
if response.Revision != annotatedGitTaghash {
|
||||
t.Errorf("returned SHA %s is different from expected annotated tag %s", response.Revision, annotatedGitTaghash)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindResources(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
@@ -2616,7 +2654,7 @@ func TestGetHelmRepos_OCIDependencies(t *testing.T) {
|
||||
assert.Equal(t, len(helmRepos), 1)
|
||||
assert.Equal(t, helmRepos[0].Username, "test")
|
||||
assert.Equal(t, helmRepos[0].EnableOci, true)
|
||||
assert.Equal(t, helmRepos[0].Repo, "example.com")
|
||||
assert.Equal(t, helmRepos[0].Repo, "example.com/myrepo")
|
||||
}
|
||||
|
||||
func TestGetHelmRepo_NamedRepos(t *testing.T) {
|
||||
|
||||
@@ -2,5 +2,5 @@ name: my-chart
|
||||
version: 1.1.0
|
||||
dependencies:
|
||||
- name: my-dependency
|
||||
repository: oci://example.com
|
||||
repository: oci://example.com/myrepo
|
||||
version: '*'
|
||||
@@ -1,7 +1,7 @@
|
||||
health_status = {}
|
||||
|
||||
if obj.status ~= nil and obj.status.reconciliationStatus ~= nil then
|
||||
if obj.status.reconciliationStatus.success then
|
||||
if obj.status.reconciliationStatus.success or obj.status.reconciliationStatus.state == "DEPLOYED" then
|
||||
health_status.status = "Healthy"
|
||||
return health_status
|
||||
end
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
tests:
|
||||
- healthStatus:
|
||||
status: Healthy
|
||||
inputPath: testdata/healthy_running.yaml
|
||||
inputPath: testdata/healthy_running_v0.1.x.yaml
|
||||
- healthStatus:
|
||||
status: Healthy
|
||||
inputPath: testdata/healthy_suspended.yaml
|
||||
inputPath: testdata/healthy_running_v1.x.yaml
|
||||
- healthStatus:
|
||||
status: Healthy
|
||||
inputPath: testdata/healthy_suspended_v0.1.x.yaml
|
||||
- healthStatus:
|
||||
status: Healthy
|
||||
inputPath: testdata/healthy_suspended_v1.x.yaml
|
||||
- healthStatus:
|
||||
status: Progressing
|
||||
message: Waiting for deploying
|
||||
|
||||
11
resource_customizations/flink.apache.org/FlinkDeployment/testdata/healthy_running_v1.x.yaml
vendored
Normal file
11
resource_customizations/flink.apache.org/FlinkDeployment/testdata/healthy_running_v1.x.yaml
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
apiVersion: flink.apache.org/v1alpha1
|
||||
kind: FlinkDeployment
|
||||
spec:
|
||||
job:
|
||||
state: running
|
||||
status:
|
||||
jobManagerDeploymentStatus: READY
|
||||
jobStatus:
|
||||
state: RUNNING
|
||||
reconciliationStatus:
|
||||
state: DEPLOYED
|
||||
11
resource_customizations/flink.apache.org/FlinkDeployment/testdata/healthy_suspended_v1.x.yaml
vendored
Normal file
11
resource_customizations/flink.apache.org/FlinkDeployment/testdata/healthy_suspended_v1.x.yaml
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
apiVersion: flink.apache.org/v1alpha1
|
||||
kind: FlinkDeployment
|
||||
spec:
|
||||
job:
|
||||
state: suspended
|
||||
status:
|
||||
jobManagerDeploymentStatus: MISSING
|
||||
jobStatus:
|
||||
state: SUSPENDED
|
||||
reconciliationStatus:
|
||||
state: DEPLOYED
|
||||
@@ -1325,9 +1325,10 @@ func (s *Server) WatchResourceTree(q *application.ResourcesQuery, ws application
|
||||
return err
|
||||
}
|
||||
|
||||
return s.cache.OnAppResourcesTreeChanged(ws.Context(), q.GetApplicationName(), func() error {
|
||||
cacheKey := argo.AppInstanceName(q.GetApplicationName(), q.GetAppNamespace(), s.ns)
|
||||
return s.cache.OnAppResourcesTreeChanged(ws.Context(), cacheKey, func() error {
|
||||
var tree appv1.ApplicationTree
|
||||
err := s.cache.GetAppResourcesTree(q.GetApplicationName(), &tree)
|
||||
err := s.cache.GetAppResourcesTree(cacheKey, &tree)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting app resource tree: %w", err)
|
||||
}
|
||||
@@ -1387,7 +1388,7 @@ func (s *Server) ManagedResources(ctx context.Context, q *application.ResourcesQ
|
||||
res := &application.ManagedResourcesResponse{}
|
||||
for i := range items {
|
||||
item := items[i]
|
||||
if isMatchingResource(q, kube.ResourceKey{Name: item.Name, Namespace: item.Namespace, Kind: item.Kind, Group: item.Group}) {
|
||||
if !item.Hook && isMatchingResource(q, kube.ResourceKey{Name: item.Name, Namespace: item.Namespace, Kind: item.Kind, Group: item.Group}) {
|
||||
res.Items = append(res.Items, item)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
util_session "github.com/argoproj/argo-cd/v2/util/session"
|
||||
"github.com/argoproj/gitops-engine/pkg/utils/kube"
|
||||
log "github.com/sirupsen/logrus"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
@@ -37,11 +38,12 @@ type terminalHandler struct {
|
||||
allowedShells []string
|
||||
namespace string
|
||||
enabledNamespaces []string
|
||||
sessionManager util_session.SessionManager
|
||||
}
|
||||
|
||||
// NewHandler returns a new terminal handler.
|
||||
func NewHandler(appLister applisters.ApplicationLister, namespace string, enabledNamespaces []string, db db.ArgoDB, enf *rbac.Enforcer, cache *servercache.Cache,
|
||||
appResourceTree AppResourceTreeFn, allowedShells []string) *terminalHandler {
|
||||
appResourceTree AppResourceTreeFn, allowedShells []string, sessionManager util_session.SessionManager) *terminalHandler {
|
||||
return &terminalHandler{
|
||||
appLister: appLister,
|
||||
db: db,
|
||||
@@ -51,6 +53,7 @@ func NewHandler(appLister applisters.ApplicationLister, namespace string, enable
|
||||
allowedShells: allowedShells,
|
||||
namespace: namespace,
|
||||
enabledNamespaces: enabledNamespaces,
|
||||
sessionManager: sessionManager,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -253,7 +256,7 @@ func (s *terminalHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
fieldLog.Info("terminal session starting")
|
||||
|
||||
session, err := newTerminalSession(w, r, nil)
|
||||
session, err := newTerminalSession(w, r, nil, s.sessionManager)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to start terminal session", http.StatusBadRequest)
|
||||
return
|
||||
@@ -309,6 +312,11 @@ type TerminalMessage struct {
|
||||
Cols uint16 `json:"cols"`
|
||||
}
|
||||
|
||||
// TerminalCommand is the struct for websocket commands,For example you need ask client to reconnect
|
||||
type TerminalCommand struct {
|
||||
Code int
|
||||
}
|
||||
|
||||
// startProcess executes specified commands in the container and connects it up with the ptyHandler (a session)
|
||||
func startProcess(k8sClient kubernetes.Interface, cfg *rest.Config, namespace, podName, containerName string, cmd []string, ptyHandler PtyHandler) error {
|
||||
req := k8sClient.CoreV1().RESTClient().Post().
|
||||
|
||||
@@ -3,6 +3,9 @@ package application
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/argoproj/argo-cd/v2/common"
|
||||
httputil "github.com/argoproj/argo-cd/v2/util/http"
|
||||
util_session "github.com/argoproj/argo-cd/v2/util/session"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -12,6 +15,11 @@ import (
|
||||
"k8s.io/client-go/tools/remotecommand"
|
||||
)
|
||||
|
||||
const (
|
||||
ReconnectCode = 1
|
||||
ReconnectMessage = "\nReconnect because the token was refreshed...\n"
|
||||
)
|
||||
|
||||
var upgrader = func() websocket.Upgrader {
|
||||
upgrader := websocket.Upgrader{}
|
||||
upgrader.HandshakeTimeout = time.Second * 2
|
||||
@@ -23,25 +31,40 @@ var upgrader = func() websocket.Upgrader {
|
||||
|
||||
// terminalSession implements PtyHandler
|
||||
type terminalSession struct {
|
||||
wsConn *websocket.Conn
|
||||
sizeChan chan remotecommand.TerminalSize
|
||||
doneChan chan struct{}
|
||||
tty bool
|
||||
readLock sync.Mutex
|
||||
writeLock sync.Mutex
|
||||
wsConn *websocket.Conn
|
||||
sizeChan chan remotecommand.TerminalSize
|
||||
doneChan chan struct{}
|
||||
tty bool
|
||||
readLock sync.Mutex
|
||||
writeLock sync.Mutex
|
||||
sessionManager util_session.SessionManager
|
||||
token *string
|
||||
}
|
||||
|
||||
// getToken get auth token from web socket request
|
||||
func getToken(r *http.Request) (string, error) {
|
||||
cookies := r.Cookies()
|
||||
return httputil.JoinCookies(common.AuthCookieName, cookies)
|
||||
}
|
||||
|
||||
// newTerminalSession create terminalSession
|
||||
func newTerminalSession(w http.ResponseWriter, r *http.Request, responseHeader http.Header) (*terminalSession, error) {
|
||||
func newTerminalSession(w http.ResponseWriter, r *http.Request, responseHeader http.Header, sessionManager util_session.SessionManager) (*terminalSession, error) {
|
||||
token, err := getToken(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
conn, err := upgrader.Upgrade(w, r, responseHeader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
session := &terminalSession{
|
||||
wsConn: conn,
|
||||
tty: true,
|
||||
sizeChan: make(chan remotecommand.TerminalSize),
|
||||
doneChan: make(chan struct{}),
|
||||
wsConn: conn,
|
||||
tty: true,
|
||||
sizeChan: make(chan remotecommand.TerminalSize),
|
||||
doneChan: make(chan struct{}),
|
||||
sessionManager: sessionManager,
|
||||
token: &token,
|
||||
}
|
||||
return session, nil
|
||||
}
|
||||
@@ -61,8 +84,40 @@ func (t *terminalSession) Next() *remotecommand.TerminalSize {
|
||||
}
|
||||
}
|
||||
|
||||
// reconnect send reconnect code to client and ask them init new ws session
|
||||
func (t *terminalSession) reconnect() (int, error) {
|
||||
reconnectCommand, _ := json.Marshal(TerminalCommand{
|
||||
Code: ReconnectCode,
|
||||
})
|
||||
reconnectMessage, _ := json.Marshal(TerminalMessage{
|
||||
Operation: "stdout",
|
||||
Data: ReconnectMessage,
|
||||
})
|
||||
t.writeLock.Lock()
|
||||
err := t.wsConn.WriteMessage(websocket.TextMessage, reconnectMessage)
|
||||
if err != nil {
|
||||
log.Errorf("write message err: %v", err)
|
||||
return 0, err
|
||||
}
|
||||
err = t.wsConn.WriteMessage(websocket.TextMessage, reconnectCommand)
|
||||
if err != nil {
|
||||
log.Errorf("write message err: %v", err)
|
||||
return 0, err
|
||||
}
|
||||
t.writeLock.Unlock()
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// Read called in a loop from remotecommand as long as the process is running
|
||||
func (t *terminalSession) Read(p []byte) (int, error) {
|
||||
// check if token still valid
|
||||
_, newToken, err := t.sessionManager.VerifyToken(*t.token)
|
||||
// err in case if token is revoked, newToken in case if refresh happened
|
||||
if err != nil || newToken != "" {
|
||||
// need to send reconnect code in case if token was refreshed
|
||||
return t.reconnect()
|
||||
}
|
||||
|
||||
t.readLock.Lock()
|
||||
_, message, err := t.wsConn.ReadMessage()
|
||||
t.readLock.Unlock()
|
||||
|
||||
46
server/application/websocket_test.go
Normal file
46
server/application/websocket_test.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func reconnect(w http.ResponseWriter, r *http.Request) {
|
||||
var upgrader = websocket.Upgrader{}
|
||||
c, err := upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
ts := terminalSession{wsConn: c}
|
||||
_, _ = ts.reconnect()
|
||||
}
|
||||
|
||||
func TestReconnect(t *testing.T) {
|
||||
|
||||
s := httptest.NewServer(http.HandlerFunc(reconnect))
|
||||
defer s.Close()
|
||||
|
||||
u := "ws" + strings.TrimPrefix(s.URL, "http")
|
||||
|
||||
// Connect to the server
|
||||
ws, _, err := websocket.DefaultDialer.Dial(u, nil)
|
||||
assert.NoError(t, err)
|
||||
|
||||
defer ws.Close()
|
||||
|
||||
_, p, _ := ws.ReadMessage()
|
||||
|
||||
var message TerminalMessage
|
||||
|
||||
err = json.Unmarshal(p, &message)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, message.Data, ReconnectMessage)
|
||||
|
||||
}
|
||||
@@ -24,6 +24,8 @@ import (
|
||||
|
||||
// nolint:staticcheck
|
||||
golang_proto "github.com/golang/protobuf/proto"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/selection"
|
||||
|
||||
"github.com/argoproj/notifications-engine/pkg/api"
|
||||
"github.com/argoproj/pkg/sync"
|
||||
@@ -291,7 +293,9 @@ func NewServer(ctx context.Context, opts ArgoCDServerOpts) *ArgoCDServer {
|
||||
|
||||
apiFactory := api.NewFactory(settings_notif.GetFactorySettings(argocdService, "argocd-notifications-secret", "argocd-notifications-cm"), opts.Namespace, secretInformer, configMapInformer)
|
||||
|
||||
return &ArgoCDServer{
|
||||
dbInstance := db.NewDB(opts.Namespace, settingsMgr, opts.KubeClientset)
|
||||
|
||||
a := &ArgoCDServer{
|
||||
ArgoCDServerOpts: opts,
|
||||
log: log.NewEntry(log.StandardLogger()),
|
||||
settings: settings,
|
||||
@@ -307,11 +311,19 @@ func NewServer(ctx context.Context, opts ArgoCDServerOpts) *ArgoCDServer {
|
||||
policyEnforcer: policyEnf,
|
||||
userStateStorage: userStateStorage,
|
||||
staticAssets: http.FS(staticFS),
|
||||
db: db.NewDB(opts.Namespace, settingsMgr, opts.KubeClientset),
|
||||
db: dbInstance,
|
||||
apiFactory: apiFactory,
|
||||
secretInformer: secretInformer,
|
||||
configMapInformer: configMapInformer,
|
||||
}
|
||||
|
||||
err = a.logInClusterWarnings()
|
||||
if err != nil {
|
||||
// Just log. It's not critical.
|
||||
log.Warnf("Failed to log in-cluster warnings: %v", err)
|
||||
}
|
||||
|
||||
return a
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -358,6 +370,47 @@ func (l *Listeners) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// logInClusterWarnings checks the in-cluster configuration and prints out any warnings.
|
||||
func (a *ArgoCDServer) logInClusterWarnings() error {
|
||||
labelSelector := labels.NewSelector()
|
||||
req, err := labels.NewRequirement(common.LabelKeySecretType, selection.Equals, []string{common.LabelValueSecretTypeCluster})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to construct cluster-type label selector: %w", err)
|
||||
}
|
||||
labelSelector = labelSelector.Add(*req)
|
||||
secretsLister, err := a.settingsMgr.GetSecretsLister()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get secrets lister: %w", err)
|
||||
}
|
||||
clusterSecrets, err := secretsLister.Secrets(a.ArgoCDServerOpts.Namespace).List(labelSelector)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list cluster secrets: %w", err)
|
||||
}
|
||||
var inClusterSecrets []string
|
||||
for _, clusterSecret := range clusterSecrets {
|
||||
cluster, err := db.SecretToCluster(clusterSecret)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not unmarshal cluster secret %q: %w", clusterSecret.Name, err)
|
||||
}
|
||||
if cluster.Server == v1alpha1.KubernetesInternalAPIServerAddr {
|
||||
inClusterSecrets = append(inClusterSecrets, clusterSecret.Name)
|
||||
}
|
||||
}
|
||||
if len(inClusterSecrets) > 0 {
|
||||
// Don't make this call unless we actually have in-cluster secrets, to save time.
|
||||
dbSettings, err := a.settingsMgr.GetSettings()
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not get DB settings: %w", err)
|
||||
}
|
||||
if !dbSettings.InClusterEnabled {
|
||||
for _, clusterName := range inClusterSecrets {
|
||||
log.Warnf("cluster %q uses in-cluster server address but it's disabled in Argo CD settings", clusterName)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func startListener(host string, port int) (net.Listener, error) {
|
||||
var conn net.Listener
|
||||
var realErr error
|
||||
@@ -460,12 +513,12 @@ func (a *ArgoCDServer) Run(ctx context.Context, listeners *Listeners) {
|
||||
var httpL net.Listener
|
||||
var httpsL net.Listener
|
||||
if !a.useTLS() {
|
||||
httpL = tcpm.Match(cmux.HTTP1Fast())
|
||||
httpL = tcpm.Match(cmux.HTTP1Fast("PATCH"))
|
||||
grpcL = tcpm.MatchWithWriters(cmux.HTTP2MatchHeaderFieldSendSettings("content-type", "application/grpc"))
|
||||
|
||||
} else {
|
||||
// We first match on HTTP 1.1 methods.
|
||||
httpL = tcpm.Match(cmux.HTTP1Fast())
|
||||
httpL = tcpm.Match(cmux.HTTP1Fast("PATCH"))
|
||||
|
||||
// If not matched, we assume that its TLS.
|
||||
tlsl := tcpm.Match(cmux.Any())
|
||||
@@ -480,7 +533,7 @@ func (a *ArgoCDServer) Run(ctx context.Context, listeners *Listeners) {
|
||||
|
||||
// Now, we build another mux recursively to match HTTPS and gRPC.
|
||||
tlsm = cmux.New(tlsl)
|
||||
httpsL = tlsm.Match(cmux.HTTP1Fast())
|
||||
httpsL = tlsm.Match(cmux.HTTP1Fast("PATCH"))
|
||||
grpcL = tlsm.MatchWithWriters(cmux.HTTP2MatchHeaderFieldSendSettings("content-type", "application/grpc"))
|
||||
}
|
||||
|
||||
@@ -910,7 +963,7 @@ func (a *ArgoCDServer) newHTTPServer(ctx context.Context, port int, grpcWebHandl
|
||||
}
|
||||
mux.Handle("/api/", handler)
|
||||
|
||||
terminal := application.NewHandler(a.appLister, a.Namespace, a.ApplicationNamespaces, a.db, a.enf, a.Cache, appResourceTreeFn, a.settings.ExecShells).
|
||||
terminal := application.NewHandler(a.appLister, a.Namespace, a.ApplicationNamespaces, a.db, a.enf, a.Cache, appResourceTreeFn, a.settings.ExecShells, *a.sessionMgr).
|
||||
WithFeatureFlagMiddleware(a.settingsMgr.GetSettings)
|
||||
th := util_session.WithAuthMiddleware(a.DisableAuth, a.sessionMgr, terminal)
|
||||
mux.Handle("/terminal", th)
|
||||
|
||||
@@ -7,7 +7,7 @@ RUN ln -s /usr/lib/$(uname -m)-linux-gnu /usr/lib/linux-gnu
|
||||
|
||||
FROM docker.io/library/node:12.18.4-buster as node
|
||||
|
||||
FROM docker.io/library/golang:1.18 as golang
|
||||
FROM docker.io/library/golang:1.20.10@sha256:077ff85b374b23916b4b41835e242e5a3ddad9fc537ea7e980f230431747d245 as golang
|
||||
|
||||
FROM docker.io/library/registry:2.8 as registry
|
||||
|
||||
|
||||
@@ -472,6 +472,24 @@ func TestDeleteAppResource(t *testing.T) {
|
||||
Expect(HealthIs(health.HealthStatusMissing))
|
||||
}
|
||||
|
||||
// Fix for issue #2677, support PATCH in HTTP service
|
||||
func TestPatchHttp(t *testing.T) {
|
||||
ctx := Given(t)
|
||||
|
||||
ctx.
|
||||
Path(guestbookPath).
|
||||
When().
|
||||
CreateApp().
|
||||
Sync().
|
||||
PatchAppHttp(`{"metadata": {"labels": { "test": "patch" }, "annotations": { "test": "patch" }}}`).
|
||||
Then().
|
||||
And(func(app *Application) {
|
||||
assert.Equal(t, "patch", app.Labels["test"])
|
||||
assert.Equal(t, "patch", app.Annotations["test"])
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
// demonstrate that we cannot use a standard sync when an immutable field is changed, we must use "force"
|
||||
func TestImmutableChange(t *testing.T) {
|
||||
SkipOnEnv(t, "OPENSHIFT")
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
client "github.com/argoproj/argo-cd/v2/pkg/apiclient/application"
|
||||
. "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||
"github.com/argoproj/argo-cd/v2/test/e2e/fixture"
|
||||
"github.com/argoproj/argo-cd/v2/util/errors"
|
||||
@@ -289,6 +291,28 @@ func (a *Actions) PatchApp(patch string) *Actions {
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *Actions) PatchAppHttp(patch string) *Actions {
|
||||
a.context.t.Helper()
|
||||
var application Application
|
||||
var patchType = "merge"
|
||||
var appName = a.context.AppQualifiedName()
|
||||
var appNamespace = a.context.AppNamespace()
|
||||
patchRequest := &client.ApplicationPatchRequest{
|
||||
Name: &appName,
|
||||
PatchType: &patchType,
|
||||
Patch: &patch,
|
||||
AppNamespace: &appNamespace,
|
||||
}
|
||||
jsonBytes, err := json.MarshalIndent(patchRequest, "", " ")
|
||||
errors.CheckError(err)
|
||||
err = fixture.DoHttpJsonRequest("PATCH",
|
||||
fmt.Sprintf("/api/v1/applications/%v", appName),
|
||||
&application,
|
||||
jsonBytes...)
|
||||
errors.CheckError(err)
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *Actions) AppSet(flags ...string) *Actions {
|
||||
a.context.t.Helper()
|
||||
args := []string{"app", "set", a.context.AppQualifiedName()}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
ARG BASE_IMAGE=docker.io/library/ubuntu:22.04
|
||||
|
||||
FROM golang:1.18 AS go
|
||||
FROM docker.io/library/golang:1.20.10@sha256:077ff85b374b23916b4b41835e242e5a3ddad9fc537ea7e980f230431747d245 AS go
|
||||
|
||||
RUN go install github.com/mattn/goreman@latest && \
|
||||
go install github.com/kisielk/godepgraph@latest
|
||||
|
||||
@@ -108,15 +108,24 @@ export const ApplicationCreatePanel = (props: {
|
||||
const [explicitPathType, setExplicitPathType] = React.useState<{path: string; type: models.AppSourceType}>(null);
|
||||
const [destFormat, setDestFormat] = React.useState('URL');
|
||||
const [retry, setRetry] = React.useState(false);
|
||||
const app = deepMerge(DEFAULT_APP, props.app || {});
|
||||
|
||||
React.useEffect(() => {
|
||||
if (app?.spec?.destination?.name && app.spec.destination.name !== '') {
|
||||
setDestFormat('NAME');
|
||||
} else {
|
||||
setDestFormat('URL');
|
||||
}
|
||||
}, []);
|
||||
|
||||
function normalizeTypeFields(formApi: FormApi, type: models.AppSourceType) {
|
||||
const app = formApi.getFormState().values;
|
||||
const appToNormalize = formApi.getFormState().values;
|
||||
for (const item of appTypes) {
|
||||
if (item.type !== type) {
|
||||
delete app.spec.source[item.field];
|
||||
delete appToNormalize.spec.source[item.field];
|
||||
}
|
||||
}
|
||||
formApi.setAllValues(app);
|
||||
formApi.setAllValues(appToNormalize);
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -132,7 +141,6 @@ export const ApplicationCreatePanel = (props: {
|
||||
}>
|
||||
{({projects, clusters, reposInfo}) => {
|
||||
const repos = reposInfo.map(info => info.repo).sort();
|
||||
const app = deepMerge(DEFAULT_APP, props.app || {});
|
||||
const repoInfo = reposInfo.find(info => info.repo === app.spec.source.repoURL);
|
||||
if (repoInfo) {
|
||||
normalizeAppSource(app, repoInfo.type || 'git');
|
||||
|
||||
@@ -72,7 +72,13 @@ export const PodTerminalViewer: React.FC<PodTerminalViewerProps> = ({
|
||||
|
||||
const onConnectionMessage = (e: MessageEvent) => {
|
||||
const msg = JSON.parse(e.data);
|
||||
connSubject.next(msg);
|
||||
if (!msg?.Code) {
|
||||
connSubject.next(msg);
|
||||
} else {
|
||||
// Do reconnect due to refresh token event
|
||||
onConnectionClose();
|
||||
setupConnection();
|
||||
}
|
||||
};
|
||||
|
||||
const onConnectionOpen = () => {
|
||||
|
||||
@@ -105,7 +105,7 @@ export class VersionPanel extends React.Component<VersionPanelProps, {copyState:
|
||||
}
|
||||
|
||||
private async onCopy(version: VersionMessage): Promise<void> {
|
||||
const stringifiedVersion = JSON.stringify(version, undefined, 4);
|
||||
const stringifiedVersion = JSON.stringify(version, undefined, 4) + '\n';
|
||||
try {
|
||||
await navigator.clipboard.writeText(stringifiedVersion);
|
||||
this.setState({copyState: 'success'});
|
||||
|
||||
@@ -154,7 +154,7 @@ func (rt *resourceTracking) BuildAppInstanceValue(value AppInstanceValue) string
|
||||
//ParseAppInstanceValue parse resource tracking id from format <application-name>:<group>/<kind>:<namespace>/<name> to struct
|
||||
func (rt *resourceTracking) ParseAppInstanceValue(value string) (*AppInstanceValue, error) {
|
||||
var appInstanceValue AppInstanceValue
|
||||
parts := strings.Split(value, ":")
|
||||
parts := strings.SplitN(value, ":", 3)
|
||||
appInstanceValue.ApplicationName = parts[0]
|
||||
if len(parts) != 3 {
|
||||
return nil, WrongResourceTrackingFormat
|
||||
|
||||
@@ -89,6 +89,19 @@ func TestParseAppInstanceValue(t *testing.T) {
|
||||
assert.Equal(t, appInstanceValue.Name, "<name>")
|
||||
}
|
||||
|
||||
func TestParseAppInstanceValueColon(t *testing.T) {
|
||||
resourceTracking := NewResourceTracking()
|
||||
appInstanceValue, err := resourceTracking.ParseAppInstanceValue("app:<group>/<kind>:<namespace>/<name>:<colon>")
|
||||
if !assert.NoError(t, err) {
|
||||
t.Fatal()
|
||||
}
|
||||
assert.Equal(t, appInstanceValue.ApplicationName, "app")
|
||||
assert.Equal(t, appInstanceValue.Group, "<group>")
|
||||
assert.Equal(t, appInstanceValue.Kind, "<kind>")
|
||||
assert.Equal(t, appInstanceValue.Namespace, "<namespace>")
|
||||
assert.Equal(t, appInstanceValue.Name, "<name>:<colon>")
|
||||
}
|
||||
|
||||
func TestParseAppInstanceValueWrongFormat1(t *testing.T) {
|
||||
resourceTracking := NewResourceTracking()
|
||||
_, err := resourceTracking.ParseAppInstanceValue("app")
|
||||
|
||||
@@ -68,7 +68,7 @@ func (db *db) ListClusters(ctx context.Context) (*appv1.ClusterList, error) {
|
||||
inClusterEnabled := settings.InClusterEnabled
|
||||
hasInClusterCredentials := false
|
||||
for _, clusterSecret := range clusterSecrets {
|
||||
cluster, err := secretToCluster(clusterSecret)
|
||||
cluster, err := SecretToCluster(clusterSecret)
|
||||
if err != nil {
|
||||
log.Errorf("could not unmarshal cluster secret %s", clusterSecret.Name)
|
||||
continue
|
||||
@@ -77,8 +77,6 @@ func (db *db) ListClusters(ctx context.Context) (*appv1.ClusterList, error) {
|
||||
if inClusterEnabled {
|
||||
hasInClusterCredentials = true
|
||||
clusterList.Items = append(clusterList.Items, *cluster)
|
||||
} else {
|
||||
log.Errorf("failed to add cluster %q to cluster list: in-cluster server address is disabled in Argo CD settings", cluster.Name)
|
||||
}
|
||||
} else {
|
||||
clusterList.Items = append(clusterList.Items, *cluster)
|
||||
@@ -122,7 +120,7 @@ func (db *db) CreateCluster(ctx context.Context, c *appv1.Cluster) (*appv1.Clust
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cluster, err := secretToCluster(clusterSecret)
|
||||
cluster, err := SecretToCluster(clusterSecret)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "could not unmarshal cluster secret %s", clusterSecret.Name)
|
||||
}
|
||||
@@ -150,7 +148,7 @@ func (db *db) WatchClusters(ctx context.Context,
|
||||
common.LabelValueSecretTypeCluster,
|
||||
|
||||
func(secret *apiv1.Secret) {
|
||||
cluster, err := secretToCluster(secret)
|
||||
cluster, err := SecretToCluster(secret)
|
||||
if err != nil {
|
||||
log.Errorf("could not unmarshal cluster secret %s", secret.Name)
|
||||
return
|
||||
@@ -165,12 +163,12 @@ func (db *db) WatchClusters(ctx context.Context,
|
||||
},
|
||||
|
||||
func(oldSecret *apiv1.Secret, newSecret *apiv1.Secret) {
|
||||
oldCluster, err := secretToCluster(oldSecret)
|
||||
oldCluster, err := SecretToCluster(oldSecret)
|
||||
if err != nil {
|
||||
log.Errorf("could not unmarshal cluster secret %s", oldSecret.Name)
|
||||
return
|
||||
}
|
||||
newCluster, err := secretToCluster(newSecret)
|
||||
newCluster, err := SecretToCluster(newSecret)
|
||||
if err != nil {
|
||||
log.Errorf("could not unmarshal cluster secret %s", newSecret.Name)
|
||||
return
|
||||
@@ -220,7 +218,7 @@ func (db *db) GetCluster(_ context.Context, server string) (*appv1.Cluster, erro
|
||||
return nil, err
|
||||
}
|
||||
if len(res) > 0 {
|
||||
return secretToCluster(res[0].(*apiv1.Secret))
|
||||
return SecretToCluster(res[0].(*apiv1.Secret))
|
||||
}
|
||||
if server == appv1.KubernetesInternalAPIServerAddr {
|
||||
return db.getLocalCluster(), nil
|
||||
@@ -241,7 +239,7 @@ func (db *db) GetProjectClusters(ctx context.Context, project string) ([]*appv1.
|
||||
}
|
||||
var res []*appv1.Cluster
|
||||
for i := range secrets {
|
||||
cluster, err := secretToCluster(secrets[i].(*apiv1.Secret))
|
||||
cluster, err := SecretToCluster(secrets[i].(*apiv1.Secret))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -295,7 +293,7 @@ func (db *db) UpdateCluster(ctx context.Context, c *appv1.Cluster) (*appv1.Clust
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cluster, err := secretToCluster(clusterSecret)
|
||||
cluster, err := SecretToCluster(clusterSecret)
|
||||
if err != nil {
|
||||
log.Errorf("could not unmarshal cluster secret %s", clusterSecret.Name)
|
||||
return nil, err
|
||||
@@ -347,6 +345,9 @@ func clusterToSecret(c *appv1.Cluster, secret *apiv1.Secret) error {
|
||||
secret.Data = data
|
||||
|
||||
secret.Labels = c.Labels
|
||||
if c.Annotations != nil && c.Annotations[apiv1.LastAppliedConfigAnnotation] != "" {
|
||||
return status.Errorf(codes.InvalidArgument, "annotation %s cannot be set", apiv1.LastAppliedConfigAnnotation)
|
||||
}
|
||||
secret.Annotations = c.Annotations
|
||||
|
||||
if secret.Annotations == nil {
|
||||
@@ -362,8 +363,8 @@ func clusterToSecret(c *appv1.Cluster, secret *apiv1.Secret) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// secretToCluster converts a secret into a Cluster object
|
||||
func secretToCluster(s *apiv1.Secret) (*appv1.Cluster, error) {
|
||||
// SecretToCluster converts a secret into a Cluster object
|
||||
func SecretToCluster(s *apiv1.Secret) (*appv1.Cluster, error) {
|
||||
var config appv1.ClusterConfig
|
||||
if len(s.Data["config"]) > 0 {
|
||||
err := json.Unmarshal(s.Data["config"], &config)
|
||||
@@ -405,6 +406,8 @@ func secretToCluster(s *apiv1.Secret) (*appv1.Cluster, error) {
|
||||
annotations := map[string]string{}
|
||||
if s.Annotations != nil {
|
||||
annotations = collections.CopyStringMap(s.Annotations)
|
||||
// delete system annotations
|
||||
delete(annotations, apiv1.LastAppliedConfigAnnotation)
|
||||
delete(annotations, common.AnnotationKeyManagedBy)
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
@@ -43,7 +45,7 @@ func Test_secretToCluster(t *testing.T) {
|
||||
"config": []byte("{\"username\":\"foo\"}"),
|
||||
},
|
||||
}
|
||||
cluster, err := secretToCluster(secret)
|
||||
cluster, err := SecretToCluster(secret)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, *cluster, v1alpha1.Cluster{
|
||||
Name: "test",
|
||||
@@ -56,6 +58,24 @@ func Test_secretToCluster(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func Test_secretToCluster_LastAppliedConfigurationDropped(t *testing.T) {
|
||||
secret := &v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "mycluster",
|
||||
Namespace: fakeNamespace,
|
||||
Annotations: map[string]string{v1.LastAppliedConfigAnnotation: "val2"},
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"name": []byte("test"),
|
||||
"server": []byte("http://mycluster"),
|
||||
"config": []byte("{\"username\":\"foo\"}"),
|
||||
},
|
||||
}
|
||||
cluster, err := SecretToCluster(secret)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, cluster.Annotations, 0)
|
||||
}
|
||||
|
||||
func TestClusterToSecret(t *testing.T) {
|
||||
cluster := &appv1.Cluster{
|
||||
Server: "server",
|
||||
@@ -78,6 +98,21 @@ func TestClusterToSecret(t *testing.T) {
|
||||
assert.Equal(t, cluster.Labels, s.Labels)
|
||||
}
|
||||
|
||||
func TestClusterToSecret_LastAppliedConfigurationRejected(t *testing.T) {
|
||||
cluster := &appv1.Cluster{
|
||||
Server: "server",
|
||||
Annotations: map[string]string{v1.LastAppliedConfigAnnotation: "val2"},
|
||||
Name: "test",
|
||||
Config: v1alpha1.ClusterConfig{},
|
||||
Project: "project",
|
||||
Namespaces: []string{"default"},
|
||||
}
|
||||
s := &v1.Secret{}
|
||||
err := clusterToSecret(cluster, s)
|
||||
require.Error(t, err)
|
||||
require.Equal(t, codes.InvalidArgument, status.Code(err))
|
||||
}
|
||||
|
||||
func Test_secretToCluster_NoConfig(t *testing.T) {
|
||||
secret := &v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
@@ -89,7 +124,7 @@ func Test_secretToCluster_NoConfig(t *testing.T) {
|
||||
"server": []byte("http://mycluster"),
|
||||
},
|
||||
}
|
||||
cluster, err := secretToCluster(secret)
|
||||
cluster, err := SecretToCluster(secret)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, *cluster, v1alpha1.Cluster{
|
||||
Name: "test",
|
||||
@@ -111,7 +146,7 @@ func Test_secretToCluster_InvalidConfig(t *testing.T) {
|
||||
"config": []byte("{'tlsClientConfig':{'insecure':false}}"),
|
||||
},
|
||||
}
|
||||
cluster, err := secretToCluster(secret)
|
||||
cluster, err := SecretToCluster(secret)
|
||||
require.Error(t, err)
|
||||
assert.Nil(t, cluster)
|
||||
}
|
||||
|
||||
@@ -151,7 +151,7 @@ func replaceListSecrets(obj []interface{}, secretValues map[string]string) []int
|
||||
// https://dexidp.io/docs/connectors/
|
||||
func needsRedirectURI(connectorType string) bool {
|
||||
switch connectorType {
|
||||
case "oidc", "saml", "microsoft", "linkedin", "gitlab", "github", "bitbucket-cloud", "openshift":
|
||||
case "oidc", "saml", "microsoft", "linkedin", "gitlab", "github", "bitbucket-cloud", "openshift", "gitea", "google", "oauth":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
||||
@@ -270,7 +270,7 @@ func Test_GenerateDexConfig(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("Redirect config", func(t *testing.T) {
|
||||
types := []string{"oidc", "saml", "microsoft", "linkedin", "gitlab", "github", "bitbucket-cloud"}
|
||||
types := []string{"oidc", "saml", "microsoft", "linkedin", "gitlab", "github", "bitbucket-cloud", "openshift", "gitea", "google", "oauth"}
|
||||
for _, c := range types {
|
||||
assert.True(t, needsRedirectURI(c))
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
executil "github.com/argoproj/argo-cd/v2/util/exec"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
@@ -23,7 +24,6 @@ import (
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/common"
|
||||
"github.com/argoproj/argo-cd/v2/util/cache"
|
||||
executil "github.com/argoproj/argo-cd/v2/util/exec"
|
||||
argoio "github.com/argoproj/argo-cd/v2/util/io"
|
||||
"github.com/argoproj/argo-cd/v2/util/io/files"
|
||||
"github.com/argoproj/argo-cd/v2/util/proxy"
|
||||
@@ -51,7 +51,7 @@ type indexCache interface {
|
||||
|
||||
type Client interface {
|
||||
CleanChartCache(chart string, version string) error
|
||||
ExtractChart(chart string, version string, passCredentials bool) (string, argoio.Closer, error)
|
||||
ExtractChart(chart string, version string, passCredentials bool, manifestMaxExtractedSize int64, disableManifestMaxExtractedSize bool) (string, argoio.Closer, error)
|
||||
GetIndex(noCache bool) (*Index, error)
|
||||
GetTags(chart string, noCache bool) (*TagsList, error)
|
||||
TestHelmOCI() (bool, error)
|
||||
@@ -121,7 +121,21 @@ func (c *nativeHelmChart) CleanChartCache(chart string, version string) error {
|
||||
return os.RemoveAll(cachePath)
|
||||
}
|
||||
|
||||
func (c *nativeHelmChart) ExtractChart(chart string, version string, passCredentials bool) (string, argoio.Closer, error) {
|
||||
func untarChart(tempDir string, cachedChartPath string, manifestMaxExtractedSize int64, disableManifestMaxExtractedSize bool) error {
|
||||
if disableManifestMaxExtractedSize {
|
||||
cmd := exec.Command("tar", "-zxvf", cachedChartPath)
|
||||
cmd.Dir = tempDir
|
||||
_, err := executil.Run(cmd)
|
||||
return err
|
||||
}
|
||||
reader, err := os.Open(cachedChartPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return files.Untgz(tempDir, reader, manifestMaxExtractedSize)
|
||||
}
|
||||
|
||||
func (c *nativeHelmChart) ExtractChart(chart string, version string, passCredentials bool, manifestMaxExtractedSize int64, disableManifestMaxExtractedSize bool) (string, argoio.Closer, error) {
|
||||
// always use Helm V3 since we don't have chart content to determine correct Helm version
|
||||
helmCmd, err := NewCmdWithVersion("", HelmV3, c.enableOci, c.proxy)
|
||||
|
||||
@@ -195,15 +209,14 @@ func (c *nativeHelmChart) ExtractChart(chart string, version string, passCredent
|
||||
if len(infos) != 1 {
|
||||
return "", nil, fmt.Errorf("expected 1 file, found %v", len(infos))
|
||||
}
|
||||
|
||||
err = os.Rename(filepath.Join(tempDest, infos[0].Name()), cachedChartPath)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
}
|
||||
|
||||
cmd := exec.Command("tar", "-zxvf", cachedChartPath)
|
||||
cmd.Dir = tempDir
|
||||
_, err = executil.Run(cmd)
|
||||
err = untarChart(tempDir, cachedChartPath, manifestMaxExtractedSize, disableManifestMaxExtractedSize)
|
||||
if err != nil {
|
||||
_ = os.RemoveAll(tempDir)
|
||||
return "", nil, err
|
||||
@@ -301,8 +314,9 @@ func (c *nativeHelmChart) loadRepoIndex() ([]byte, error) {
|
||||
}
|
||||
|
||||
tr := &http.Transport{
|
||||
Proxy: proxy.GetCallback(c.proxy),
|
||||
TLSClientConfig: tlsConf,
|
||||
Proxy: proxy.GetCallback(c.proxy),
|
||||
TLSClientConfig: tlsConf,
|
||||
DisableKeepAlives: true,
|
||||
}
|
||||
client := http.Client{Transport: tr}
|
||||
resp, err := client.Do(req)
|
||||
@@ -469,8 +483,9 @@ func (c *nativeHelmChart) getTagsFromUrl(tagsURL string) ([]byte, string, error)
|
||||
}
|
||||
|
||||
tr := &http.Transport{
|
||||
Proxy: proxy.GetCallback(c.proxy),
|
||||
TLSClientConfig: tlsConf,
|
||||
Proxy: proxy.GetCallback(c.proxy),
|
||||
TLSClientConfig: tlsConf,
|
||||
DisableKeepAlives: true,
|
||||
}
|
||||
client := http.Client{Transport: tr}
|
||||
resp, err := client.Do(req)
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -72,7 +73,7 @@ func TestIndex(t *testing.T) {
|
||||
|
||||
func Test_nativeHelmChart_ExtractChart(t *testing.T) {
|
||||
client := NewClient("https://argoproj.github.io/argo-helm", Creds{}, false, "")
|
||||
path, closer, err := client.ExtractChart("argo-cd", "0.7.1", false)
|
||||
path, closer, err := client.ExtractChart("argo-cd", "0.7.1", false, math.MaxInt64, true)
|
||||
assert.NoError(t, err)
|
||||
defer io.Close(closer)
|
||||
info, err := os.Stat(path)
|
||||
@@ -80,9 +81,15 @@ func Test_nativeHelmChart_ExtractChart(t *testing.T) {
|
||||
assert.True(t, info.IsDir())
|
||||
}
|
||||
|
||||
func Test_nativeHelmChart_ExtractChartWithLimiter(t *testing.T) {
|
||||
client := NewClient("https://argoproj.github.io/argo-helm", Creds{}, false, "")
|
||||
_, _, err := client.ExtractChart("argo-cd", "0.7.1", false, 100, false)
|
||||
assert.Error(t, err, "error while iterating on tar reader: unexpected EOF")
|
||||
}
|
||||
|
||||
func Test_nativeHelmChart_ExtractChart_insecure(t *testing.T) {
|
||||
client := NewClient("https://argoproj.github.io/argo-helm", Creds{InsecureSkipVerify: true}, false, "")
|
||||
path, closer, err := client.ExtractChart("argo-cd", "0.7.1", false)
|
||||
path, closer, err := client.ExtractChart("argo-cd", "0.7.1", false, math.MaxInt64, true)
|
||||
assert.NoError(t, err)
|
||||
defer io.Close(closer)
|
||||
info, err := os.Stat(path)
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
@@ -264,6 +265,10 @@ var (
|
||||
)
|
||||
|
||||
func cleanSetParameters(val string) string {
|
||||
// `{}` equal helm list parameters format, so don't escape `,`.
|
||||
if strings.HasPrefix(val, `{`) && strings.HasSuffix(val, `}`) {
|
||||
return val
|
||||
}
|
||||
return re.ReplaceAllString(val, `$1\,`)
|
||||
}
|
||||
|
||||
|
||||
@@ -165,6 +165,7 @@ func TestHelmArgCleaner(t *testing.T) {
|
||||
`bar`: `bar`,
|
||||
`not, clean`: `not\, clean`,
|
||||
`a\,b,c`: `a\,b\,c`,
|
||||
`{a,b,c}`: `{a,b,c}`,
|
||||
} {
|
||||
cleaned := cleanSetParameters(input)
|
||||
assert.Equal(t, expected, cleaned)
|
||||
|
||||
@@ -29,7 +29,7 @@ func (_m *Client) CleanChartCache(chart string, version string) error {
|
||||
}
|
||||
|
||||
// ExtractChart provides a mock function with given fields: chart, version
|
||||
func (_m *Client) ExtractChart(chart string, version string, passCredentials bool) (string, io.Closer, error) {
|
||||
func (_m *Client) ExtractChart(chart string, version string, passCredentials bool, manifestMaxExtractedSize int64, disableManifestMaxExtractedSize bool) (string, io.Closer, error) {
|
||||
ret := _m.Called(chart, version)
|
||||
|
||||
var r0 string
|
||||
|
||||
@@ -264,7 +264,8 @@ func (a *ArgoCDWebhookHandler) HandleEvent(payload interface{}) {
|
||||
for _, source := range app.Spec.GetSources() {
|
||||
if sourceRevisionHasChanged(source, revision, touchedHead) && sourceUsesURL(source, webURL, repoRegexp) {
|
||||
if appFilesHaveChanged(&app, changedFiles) {
|
||||
_, err = argo.RefreshApp(appIf, app.ObjectMeta.Name, v1alpha1.RefreshTypeNormal)
|
||||
namespacedAppInterface := a.appClientset.ArgoprojV1alpha1().Applications(app.ObjectMeta.Namespace)
|
||||
_, err = argo.RefreshApp(namespacedAppInterface, app.ObjectMeta.Name, v1alpha1.RefreshTypeNormal)
|
||||
if err != nil {
|
||||
log.Warnf("Failed to refresh app '%s' for controller reprocessing: %v", app.ObjectMeta.Name, err)
|
||||
continue
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
@@ -149,10 +150,10 @@ func TestGitHubCommitEvent_MultiSource_Refresh(t *testing.T) {
|
||||
func TestGitHubCommitEvent_AppsInOtherNamespaces(t *testing.T) {
|
||||
hook := test.NewGlobal()
|
||||
|
||||
patchedApps := make([]string, 0, 3)
|
||||
patchedApps := make([]types.NamespacedName, 0, 3)
|
||||
reaction := func(action kubetesting.Action) (handled bool, ret runtime.Object, err error) {
|
||||
patchAction := action.(kubetesting.PatchAction)
|
||||
patchedApps = append(patchedApps, patchAction.GetName())
|
||||
patchedApps = append(patchedApps, types.NamespacedName{Name: patchAction.GetName(), Namespace: patchAction.GetNamespace()})
|
||||
return true, nil, nil
|
||||
}
|
||||
|
||||
@@ -231,10 +232,10 @@ func TestGitHubCommitEvent_AppsInOtherNamespaces(t *testing.T) {
|
||||
assert.Contains(t, logMessages, "Requested app 'app-to-refresh-in-globbed-namespace' refresh")
|
||||
assert.NotContains(t, logMessages, "Requested app 'app-to-ignore' refresh")
|
||||
|
||||
assert.Contains(t, patchedApps, "app-to-refresh-in-default-namespace")
|
||||
assert.Contains(t, patchedApps, "app-to-refresh-in-exact-match-namespace")
|
||||
assert.Contains(t, patchedApps, "app-to-refresh-in-globbed-namespace")
|
||||
assert.NotContains(t, patchedApps, "app-to-ignore")
|
||||
assert.Contains(t, patchedApps, types.NamespacedName{Name: "app-to-refresh-in-default-namespace", Namespace: "argocd"})
|
||||
assert.Contains(t, patchedApps, types.NamespacedName{Name: "app-to-refresh-in-exact-match-namespace", Namespace: "end-to-end-tests"})
|
||||
assert.Contains(t, patchedApps, types.NamespacedName{Name: "app-to-refresh-in-globbed-namespace", Namespace: "app-team-two"})
|
||||
assert.NotContains(t, patchedApps, types.NamespacedName{Name: "app-to-ignore", Namespace: "kube-system"})
|
||||
assert.Len(t, patchedApps, 3)
|
||||
|
||||
hook.Reset()
|
||||
|
||||
Reference in New Issue
Block a user