mirror of
https://github.com/argoproj/argo-cd.git
synced 2026-03-16 05:18:47 +01:00
Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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")
|
||||
|
||||
@@ -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.10
|
||||
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.10
|
||||
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.10
|
||||
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.10
|
||||
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.10
|
||||
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.10
|
||||
|
||||
@@ -12,7 +12,7 @@ patches:
|
||||
images:
|
||||
- name: quay.io/argoproj/argocd
|
||||
newName: quay.io/argoproj/argocd
|
||||
newTag: v2.7.8
|
||||
newTag: v2.7.10
|
||||
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.10
|
||||
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.10
|
||||
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.10
|
||||
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.10
|
||||
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.10
|
||||
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.10
|
||||
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.10
|
||||
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.10
|
||||
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.10
|
||||
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.10
|
||||
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.10
|
||||
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.10
|
||||
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.10
|
||||
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.10
|
||||
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.10
|
||||
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.10
|
||||
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.10
|
||||
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.10
|
||||
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.10
|
||||
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.10
|
||||
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.10
|
||||
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.10
|
||||
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.10
|
||||
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.10
|
||||
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.10
|
||||
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.10
|
||||
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.10
|
||||
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.10
|
||||
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: '*'
|
||||
@@ -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'>
|
||||
|
||||
@@ -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