mirror of
https://github.com/argoproj/argo-cd.git
synced 2026-03-18 22:38:48 +01:00
Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ec195adad8 | ||
|
|
743334ec1d | ||
|
|
97ddc4b245 | ||
|
|
89c30d8d34 | ||
|
|
c56d7e7bc6 | ||
|
|
9b6892c467 | ||
|
|
469f25753b | ||
|
|
057a39d962 | ||
|
|
ad006440f3 | ||
|
|
1960da7e8f | ||
|
|
284c16f838 | ||
|
|
3e65ad2893 | ||
|
|
84e2f77520 | ||
|
|
316be4eb27 | ||
|
|
0157f414f3 | ||
|
|
fbe7f8f8d7 | ||
|
|
0ee33e52dd | ||
|
|
ba44ddb9a1 | ||
|
|
8249eddf75 | ||
|
|
0d24330298 | ||
|
|
53eeed06b0 | ||
|
|
f1bfa8c655 |
@@ -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
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -1240,40 +1240,44 @@ func (ctrl *ApplicationController) processRequestedAppOperation(app *appv1.Appli
|
||||
}
|
||||
|
||||
func (ctrl *ApplicationController) setOperationState(app *appv1.Application, state *appv1.OperationState) {
|
||||
kube.RetryUntilSucceed(context.Background(), updateOperationStateTimeout, "Update application operation state", logutils.NewLogrusLogger(logutils.NewWithCurrentConfig()), func() error {
|
||||
if state.Phase == "" {
|
||||
// expose any bugs where we neglect to set phase
|
||||
panic("no phase was set")
|
||||
}
|
||||
if state.Phase.Completed() {
|
||||
now := metav1.Now()
|
||||
state.FinishedAt = &now
|
||||
}
|
||||
patch := map[string]interface{}{
|
||||
"status": map[string]interface{}{
|
||||
"operationState": state,
|
||||
},
|
||||
}
|
||||
if state.Phase.Completed() {
|
||||
// If operation is completed, clear the operation field to indicate no operation is
|
||||
// in progress.
|
||||
patch["operation"] = nil
|
||||
}
|
||||
if reflect.DeepEqual(app.Status.OperationState, state) {
|
||||
log.Infof("No operation updates necessary to '%s'. Skipping patch", app.QualifiedName())
|
||||
return nil
|
||||
}
|
||||
patchJSON, err := json.Marshal(patch)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error marshaling json: %w", err)
|
||||
}
|
||||
if app.Status.OperationState != nil && app.Status.OperationState.FinishedAt != nil && state.FinishedAt == nil {
|
||||
patchJSON, err = jsonpatch.MergeMergePatches(patchJSON, []byte(`{"status": {"operationState": {"finishedAt": null}}}`))
|
||||
if err != nil {
|
||||
return fmt.Errorf("error merging operation state patch: %w", err)
|
||||
}
|
||||
}
|
||||
logCtx := log.WithFields(log.Fields{"application": app.Name, "appNamespace": app.Namespace, "project": app.Spec.Project})
|
||||
|
||||
if state.Phase == "" {
|
||||
// expose any bugs where we neglect to set phase
|
||||
panic("no phase was set")
|
||||
}
|
||||
if state.Phase.Completed() {
|
||||
now := metav1.Now()
|
||||
state.FinishedAt = &now
|
||||
}
|
||||
patch := map[string]interface{}{
|
||||
"status": map[string]interface{}{
|
||||
"operationState": state,
|
||||
},
|
||||
}
|
||||
if state.Phase.Completed() {
|
||||
// If operation is completed, clear the operation field to indicate no operation is
|
||||
// in progress.
|
||||
patch["operation"] = nil
|
||||
}
|
||||
if reflect.DeepEqual(app.Status.OperationState, state) {
|
||||
logCtx.Infof("No operation updates necessary to '%s'. Skipping patch", app.QualifiedName())
|
||||
return
|
||||
}
|
||||
patchJSON, err := json.Marshal(patch)
|
||||
if err != nil {
|
||||
logCtx.Errorf("error marshaling json: %v", err)
|
||||
return
|
||||
}
|
||||
if app.Status.OperationState != nil && app.Status.OperationState.FinishedAt != nil && state.FinishedAt == nil {
|
||||
patchJSON, err = jsonpatch.MergeMergePatches(patchJSON, []byte(`{"status": {"operationState": {"finishedAt": null}}}`))
|
||||
if err != nil {
|
||||
logCtx.Errorf("error merging operation state patch: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
kube.RetryUntilSucceed(context.Background(), updateOperationStateTimeout, "Update application operation state", logutils.NewLogrusLogger(logutils.NewWithCurrentConfig()), func() error {
|
||||
appClient := ctrl.applicationClientset.ArgoprojV1alpha1().Applications(app.Namespace)
|
||||
_, err = appClient.Patch(context.Background(), app.Name, types.MergePatchType, patchJSON, metav1.PatchOptions{})
|
||||
if err != nil {
|
||||
@@ -1281,32 +1285,36 @@ func (ctrl *ApplicationController) setOperationState(app *appv1.Application, sta
|
||||
if apierr.IsNotFound(err) {
|
||||
return nil
|
||||
}
|
||||
// kube.RetryUntilSucceed logs failed attempts at "debug" level, but we want to know if this fails. Log a
|
||||
// warning.
|
||||
logCtx.Warnf("error patching application with operation state: %v", err)
|
||||
return fmt.Errorf("error patching application with operation state: %w", err)
|
||||
}
|
||||
log.Infof("updated '%s' operation (phase: %s)", app.QualifiedName(), state.Phase)
|
||||
if state.Phase.Completed() {
|
||||
eventInfo := argo.EventInfo{Reason: argo.EventReasonOperationCompleted}
|
||||
var messages []string
|
||||
if state.Operation.Sync != nil && len(state.Operation.Sync.Resources) > 0 {
|
||||
messages = []string{"Partial sync operation"}
|
||||
} else {
|
||||
messages = []string{"Sync operation"}
|
||||
}
|
||||
if state.SyncResult != nil {
|
||||
messages = append(messages, "to", state.SyncResult.Revision)
|
||||
}
|
||||
if state.Phase.Successful() {
|
||||
eventInfo.Type = v1.EventTypeNormal
|
||||
messages = append(messages, "succeeded")
|
||||
} else {
|
||||
eventInfo.Type = v1.EventTypeWarning
|
||||
messages = append(messages, "failed:", state.Message)
|
||||
}
|
||||
ctrl.auditLogger.LogAppEvent(app, eventInfo, strings.Join(messages, " "), "")
|
||||
ctrl.metricsServer.IncSync(app, state)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
logCtx.Infof("updated '%s' operation (phase: %s)", app.QualifiedName(), state.Phase)
|
||||
if state.Phase.Completed() {
|
||||
eventInfo := argo.EventInfo{Reason: argo.EventReasonOperationCompleted}
|
||||
var messages []string
|
||||
if state.Operation.Sync != nil && len(state.Operation.Sync.Resources) > 0 {
|
||||
messages = []string{"Partial sync operation"}
|
||||
} else {
|
||||
messages = []string{"Sync operation"}
|
||||
}
|
||||
if state.SyncResult != nil {
|
||||
messages = append(messages, "to", state.SyncResult.Revision)
|
||||
}
|
||||
if state.Phase.Successful() {
|
||||
eventInfo.Type = v1.EventTypeNormal
|
||||
messages = append(messages, "succeeded")
|
||||
} else {
|
||||
eventInfo.Type = v1.EventTypeWarning
|
||||
messages = append(messages, "failed:", state.Message)
|
||||
}
|
||||
ctrl.auditLogger.LogAppEvent(app, eventInfo, strings.Join(messages, " "), "")
|
||||
ctrl.metricsServer.IncSync(app, state)
|
||||
}
|
||||
}
|
||||
|
||||
func (ctrl *ApplicationController) processAppRefreshQueueItem() (processNext bool) {
|
||||
|
||||
@@ -3,9 +3,11 @@ package controller
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
|
||||
clustercache "github.com/argoproj/gitops-engine/pkg/cache"
|
||||
@@ -927,6 +929,41 @@ func TestSetOperationStateOnDeletedApp(t *testing.T) {
|
||||
assert.True(t, patched)
|
||||
}
|
||||
|
||||
type logHook struct {
|
||||
entries []logrus.Entry
|
||||
}
|
||||
|
||||
func (h *logHook) Levels() []logrus.Level {
|
||||
return []logrus.Level{logrus.WarnLevel}
|
||||
}
|
||||
|
||||
func (h *logHook) Fire(entry *logrus.Entry) error {
|
||||
h.entries = append(h.entries, *entry)
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestSetOperationStateLogRetries(t *testing.T) {
|
||||
hook := logHook{}
|
||||
logrus.AddHook(&hook)
|
||||
t.Cleanup(func() {
|
||||
logrus.StandardLogger().ReplaceHooks(logrus.LevelHooks{})
|
||||
})
|
||||
ctrl := newFakeController(&fakeData{apps: []runtime.Object{}})
|
||||
fakeAppCs := ctrl.applicationClientset.(*appclientset.Clientset)
|
||||
fakeAppCs.ReactionChain = nil
|
||||
patched := false
|
||||
fakeAppCs.AddReactor("patch", "*", func(action kubetesting.Action) (handled bool, ret runtime.Object, err error) {
|
||||
if !patched {
|
||||
patched = true
|
||||
return true, nil, errors.New("fake error")
|
||||
}
|
||||
return true, nil, nil
|
||||
})
|
||||
ctrl.setOperationState(newFakeApp(), &v1alpha1.OperationState{Phase: synccommon.OperationSucceeded})
|
||||
assert.True(t, patched)
|
||||
assert.Contains(t, hook.entries[0].Message, "fake error")
|
||||
}
|
||||
|
||||
func TestNeedRefreshAppStatus(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
99
controller/cache/cache_test.go
vendored
99
controller/cache/cache_test.go
vendored
@@ -1,13 +1,16 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net"
|
||||
"net/url"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"k8s.io/api/core/v1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
apierr "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
@@ -15,8 +18,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
|
||||
@@ -107,6 +112,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")
|
||||
|
||||
@@ -172,6 +172,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
|
||||
@@ -190,7 +191,7 @@ 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
|
||||
@@ -202,6 +203,9 @@ spec:
|
||||
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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -118,7 +118,7 @@ spec:
|
||||
# static parameter announcements list.
|
||||
command: [echo, '[{"name": "example-param", "string": "default-string-value"}]']
|
||||
|
||||
# If set to then the plugin receives repository files with original file mode. Dangerous since the repository
|
||||
# If set to `true` then the plugin receives repository files with original file mode. Dangerous since the repository
|
||||
# might have executable files. Set to true only if you trust the CMP plugin authors.
|
||||
preserveFileMode: false
|
||||
```
|
||||
|
||||
@@ -21,7 +21,7 @@ Each link in the list has five subfields:
|
||||
|
||||
1. `title`: title/tag that will be displayed in the UI corresponding to that link
|
||||
2. `url`: the actual URL where the deep link will redirect to, this field can be templated to use data from the
|
||||
corresponding application, project or resource objects (depending on where it is located). This uses [text/template](pkg.go.dev/text/template) pkg for templating
|
||||
corresponding application, project or resource objects (depending on where it is located). This uses [text/template](https://pkg.go.dev/text/template) pkg for templating
|
||||
3. `description` (optional): a description for what the deep link is about
|
||||
4. `icon.class` (optional): a font-awesome icon class to be used when displaying the links in dropdown menus
|
||||
5. `if` (optional): a conditional statement that results in either `true` or `false`, it also has access to the same
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -5,7 +5,7 @@ kind: Kustomization
|
||||
images:
|
||||
- name: quay.io/argoproj/argocd
|
||||
newName: quay.io/argoproj/argocd
|
||||
newTag: v2.7.8
|
||||
newTag: v2.7.11
|
||||
resources:
|
||||
- ./application-controller
|
||||
- ./dex
|
||||
|
||||
@@ -16706,7 +16706,7 @@ spec:
|
||||
key: applicationsetcontroller.enable.progressive.syncs
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.7.8
|
||||
image: quay.io/argoproj/argocd:v2.7.11
|
||||
imagePullPolicy: Always
|
||||
name: argocd-applicationset-controller
|
||||
ports:
|
||||
@@ -16968,7 +16968,7 @@ spec:
|
||||
value: /helm-working-dir
|
||||
- name: HELM_DATA_HOME
|
||||
value: /helm-working-dir
|
||||
image: quay.io/argoproj/argocd:v2.7.8
|
||||
image: quay.io/argoproj/argocd:v2.7.11
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -17020,7 +17020,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /var/run/argocd/argocd-cmp-server
|
||||
image: quay.io/argoproj/argocd:v2.7.8
|
||||
image: quay.io/argoproj/argocd:v2.7.11
|
||||
name: copyutil
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
@@ -17233,7 +17233,7 @@ spec:
|
||||
key: controller.kubectl.parallelism.limit
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.7.8
|
||||
image: quay.io/argoproj/argocd:v2.7.11
|
||||
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.7.8
|
||||
newTag: v2.7.11
|
||||
|
||||
@@ -12,7 +12,7 @@ patches:
|
||||
images:
|
||||
- name: quay.io/argoproj/argocd
|
||||
newName: quay.io/argoproj/argocd
|
||||
newTag: v2.7.8
|
||||
newTag: v2.7.11
|
||||
resources:
|
||||
- ../../base/application-controller
|
||||
- ../../base/applicationset-controller
|
||||
|
||||
@@ -17927,7 +17927,7 @@ spec:
|
||||
key: applicationsetcontroller.enable.progressive.syncs
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.7.8
|
||||
image: quay.io/argoproj/argocd:v2.7.11
|
||||
imagePullPolicy: Always
|
||||
name: argocd-applicationset-controller
|
||||
ports:
|
||||
@@ -18037,7 +18037,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /shared/argocd-dex
|
||||
image: quay.io/argoproj/argocd:v2.7.8
|
||||
image: quay.io/argoproj/argocd:v2.7.11
|
||||
imagePullPolicy: Always
|
||||
name: copyutil
|
||||
securityContext:
|
||||
@@ -18094,7 +18094,7 @@ spec:
|
||||
containers:
|
||||
- args:
|
||||
- /usr/local/bin/argocd-notifications
|
||||
image: quay.io/argoproj/argocd:v2.7.8
|
||||
image: quay.io/argoproj/argocd:v2.7.11
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
@@ -18399,7 +18399,7 @@ spec:
|
||||
value: /helm-working-dir
|
||||
- name: HELM_DATA_HOME
|
||||
value: /helm-working-dir
|
||||
image: quay.io/argoproj/argocd:v2.7.8
|
||||
image: quay.io/argoproj/argocd:v2.7.11
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -18451,7 +18451,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /var/run/argocd/argocd-cmp-server
|
||||
image: quay.io/argoproj/argocd:v2.7.8
|
||||
image: quay.io/argoproj/argocd:v2.7.11
|
||||
name: copyutil
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
@@ -18733,7 +18733,7 @@ spec:
|
||||
key: server.enable.proxy.extension
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.7.8
|
||||
image: quay.io/argoproj/argocd:v2.7.11
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
@@ -18978,7 +18978,7 @@ spec:
|
||||
key: controller.kubectl.parallelism.limit
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.7.8
|
||||
image: quay.io/argoproj/argocd:v2.7.11
|
||||
imagePullPolicy: Always
|
||||
name: argocd-application-controller
|
||||
ports:
|
||||
|
||||
@@ -1587,7 +1587,7 @@ spec:
|
||||
key: applicationsetcontroller.enable.progressive.syncs
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.7.8
|
||||
image: quay.io/argoproj/argocd:v2.7.11
|
||||
imagePullPolicy: Always
|
||||
name: argocd-applicationset-controller
|
||||
ports:
|
||||
@@ -1697,7 +1697,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /shared/argocd-dex
|
||||
image: quay.io/argoproj/argocd:v2.7.8
|
||||
image: quay.io/argoproj/argocd:v2.7.11
|
||||
imagePullPolicy: Always
|
||||
name: copyutil
|
||||
securityContext:
|
||||
@@ -1754,7 +1754,7 @@ spec:
|
||||
containers:
|
||||
- args:
|
||||
- /usr/local/bin/argocd-notifications
|
||||
image: quay.io/argoproj/argocd:v2.7.8
|
||||
image: quay.io/argoproj/argocd:v2.7.11
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
@@ -2059,7 +2059,7 @@ spec:
|
||||
value: /helm-working-dir
|
||||
- name: HELM_DATA_HOME
|
||||
value: /helm-working-dir
|
||||
image: quay.io/argoproj/argocd:v2.7.8
|
||||
image: quay.io/argoproj/argocd:v2.7.11
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -2111,7 +2111,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /var/run/argocd/argocd-cmp-server
|
||||
image: quay.io/argoproj/argocd:v2.7.8
|
||||
image: quay.io/argoproj/argocd:v2.7.11
|
||||
name: copyutil
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
@@ -2393,7 +2393,7 @@ spec:
|
||||
key: server.enable.proxy.extension
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.7.8
|
||||
image: quay.io/argoproj/argocd:v2.7.11
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
@@ -2638,7 +2638,7 @@ spec:
|
||||
key: controller.kubectl.parallelism.limit
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.7.8
|
||||
image: quay.io/argoproj/argocd:v2.7.11
|
||||
imagePullPolicy: Always
|
||||
name: argocd-application-controller
|
||||
ports:
|
||||
|
||||
@@ -17044,7 +17044,7 @@ spec:
|
||||
key: applicationsetcontroller.enable.progressive.syncs
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.7.8
|
||||
image: quay.io/argoproj/argocd:v2.7.11
|
||||
imagePullPolicy: Always
|
||||
name: argocd-applicationset-controller
|
||||
ports:
|
||||
@@ -17154,7 +17154,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /shared/argocd-dex
|
||||
image: quay.io/argoproj/argocd:v2.7.8
|
||||
image: quay.io/argoproj/argocd:v2.7.11
|
||||
imagePullPolicy: Always
|
||||
name: copyutil
|
||||
securityContext:
|
||||
@@ -17211,7 +17211,7 @@ spec:
|
||||
containers:
|
||||
- args:
|
||||
- /usr/local/bin/argocd-notifications
|
||||
image: quay.io/argoproj/argocd:v2.7.8
|
||||
image: quay.io/argoproj/argocd:v2.7.11
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
@@ -17468,7 +17468,7 @@ spec:
|
||||
value: /helm-working-dir
|
||||
- name: HELM_DATA_HOME
|
||||
value: /helm-working-dir
|
||||
image: quay.io/argoproj/argocd:v2.7.8
|
||||
image: quay.io/argoproj/argocd:v2.7.11
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -17520,7 +17520,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /var/run/argocd/argocd-cmp-server
|
||||
image: quay.io/argoproj/argocd:v2.7.8
|
||||
image: quay.io/argoproj/argocd:v2.7.11
|
||||
name: copyutil
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
@@ -17795,7 +17795,7 @@ spec:
|
||||
key: server.enable.proxy.extension
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.7.8
|
||||
image: quay.io/argoproj/argocd:v2.7.11
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
@@ -18035,7 +18035,7 @@ spec:
|
||||
key: controller.kubectl.parallelism.limit
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.7.8
|
||||
image: quay.io/argoproj/argocd:v2.7.11
|
||||
imagePullPolicy: Always
|
||||
name: argocd-application-controller
|
||||
ports:
|
||||
|
||||
@@ -704,7 +704,7 @@ spec:
|
||||
key: applicationsetcontroller.enable.progressive.syncs
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.7.8
|
||||
image: quay.io/argoproj/argocd:v2.7.11
|
||||
imagePullPolicy: Always
|
||||
name: argocd-applicationset-controller
|
||||
ports:
|
||||
@@ -814,7 +814,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /shared/argocd-dex
|
||||
image: quay.io/argoproj/argocd:v2.7.8
|
||||
image: quay.io/argoproj/argocd:v2.7.11
|
||||
imagePullPolicy: Always
|
||||
name: copyutil
|
||||
securityContext:
|
||||
@@ -871,7 +871,7 @@ spec:
|
||||
containers:
|
||||
- args:
|
||||
- /usr/local/bin/argocd-notifications
|
||||
image: quay.io/argoproj/argocd:v2.7.8
|
||||
image: quay.io/argoproj/argocd:v2.7.11
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
@@ -1128,7 +1128,7 @@ spec:
|
||||
value: /helm-working-dir
|
||||
- name: HELM_DATA_HOME
|
||||
value: /helm-working-dir
|
||||
image: quay.io/argoproj/argocd:v2.7.8
|
||||
image: quay.io/argoproj/argocd:v2.7.11
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -1180,7 +1180,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /var/run/argocd/argocd-cmp-server
|
||||
image: quay.io/argoproj/argocd:v2.7.8
|
||||
image: quay.io/argoproj/argocd:v2.7.11
|
||||
name: copyutil
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
@@ -1455,7 +1455,7 @@ spec:
|
||||
key: server.enable.proxy.extension
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.7.8
|
||||
image: quay.io/argoproj/argocd:v2.7.11
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
@@ -1695,7 +1695,7 @@ spec:
|
||||
key: controller.kubectl.parallelism.limit
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.7.8
|
||||
image: quay.io/argoproj/argocd:v2.7.11
|
||||
imagePullPolicy: Always
|
||||
name: argocd-application-controller
|
||||
ports:
|
||||
|
||||
@@ -945,11 +945,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 +960,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})
|
||||
|
||||
@@ -2616,7 +2616,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: '*'
|
||||
@@ -1389,7 +1389,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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,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"
|
||||
@@ -290,7 +292,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,
|
||||
@@ -306,11 +310,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 (
|
||||
@@ -357,6 +369,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
|
||||
@@ -459,12 +512,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())
|
||||
@@ -479,7 +532,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"))
|
||||
}
|
||||
|
||||
|
||||
@@ -475,6 +475,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()}
|
||||
|
||||
@@ -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,16 +141,10 @@ 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');
|
||||
}
|
||||
if (app?.spec?.destination?.name && app.spec.destination.name !== '') {
|
||||
setDestFormat('NAME');
|
||||
} else {
|
||||
setDestFormat('URL');
|
||||
}
|
||||
return (
|
||||
<div className='application-create-panel'>
|
||||
{(yamlMode && (
|
||||
|
||||
@@ -24,7 +24,7 @@ export const ApplicationResourceList = ({
|
||||
<div className='columns small-1 xxxlarge-1' />
|
||||
<div className='columns small-2 xxxlarge-2'>NAME</div>
|
||||
<div className='columns small-2 xxxlarge-2'>GROUP/KIND</div>
|
||||
<div className='columns small-1 xxxlarge-2'>SYNC ORDER</div>
|
||||
<div className='columns small-1 xxxlarge-1'>SYNC ORDER</div>
|
||||
<div className='columns small-2 xxxlarge-2'>NAMESPACE</div>
|
||||
<div className='columns small-2 xxxlarge-2'>CREATED AT</div>
|
||||
<div className='columns small-2 xxxlarge-2'>STATUS</div>
|
||||
@@ -62,7 +62,7 @@ export const ApplicationResourceList = ({
|
||||
)}
|
||||
</div>
|
||||
<div className='columns small-2 xxxlarge-2'>{[res.group, res.kind].filter(item => !!item).join('/')}</div>
|
||||
<div className='columns small-1 xxxlarge-2'>{res.syncWave || '-'}</div>
|
||||
<div className='columns small-1 xxxlarge-1'>{res.syncWave || '-'}</div>
|
||||
<div className='columns small-2 xxxlarge-2'>{res.namespace}</div>
|
||||
<div className='columns small-2 xxxlarge-2'>{res.createdAt}</div>
|
||||
<div className='columns small-2 xxxlarge-2'>
|
||||
|
||||
@@ -4,7 +4,14 @@ import {Tooltip} from 'argo-ui';
|
||||
// SinceSelector is a component that renders a dropdown menu of time ranges
|
||||
export const SinceSecondsSelector = ({sinceSeconds, setSinceSeconds}: {sinceSeconds: number; setSinceSeconds: (value: number) => void}) => (
|
||||
<Tooltip content='Show logs since a given time'>
|
||||
<select className='argo-field' style={{marginRight: '1em'}} value={sinceSeconds} onChange={e => setSinceSeconds(parseInt(e.target.value, 10))}>
|
||||
<select
|
||||
className='argo-field'
|
||||
style={{marginRight: '1em'}}
|
||||
value={sinceSeconds}
|
||||
onChange={e => {
|
||||
const v = parseInt(e.target.value, 10);
|
||||
setSinceSeconds(!isNaN(v) ? v : null);
|
||||
}}>
|
||||
<option value='60'>1m ago</option>
|
||||
<option value='300'>5m ago</option>
|
||||
<option value='600'>10m ago</option>
|
||||
|
||||
@@ -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, fmt.Errorf("failed to convert secret to cluster: %w", 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
|
||||
@@ -362,8 +360,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)
|
||||
|
||||
@@ -43,7 +43,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",
|
||||
@@ -89,7 +89,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 +111,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))
|
||||
}
|
||||
|
||||
@@ -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