diff --git a/applicationset/controllers/applicationset_controller_test.go b/applicationset/controllers/applicationset_controller_test.go index 6d55dd6bdb1e5..c9e7a4f42db19 100644 --- a/applicationset/controllers/applicationset_controller_test.go +++ b/applicationset/controllers/applicationset_controller_test.go @@ -1894,12 +1894,6 @@ func TestValidateGeneratedApplications(t *testing.T) { err := v1alpha1.AddToScheme(scheme) require.NoError(t, err) - // Valid cluster - myCluster := v1alpha1.Cluster{ - Server: "https://kubernetes.default.svc", - Name: "my-cluster", - } - // Valid project myProject := &v1alpha1.AppProject{ ObjectMeta: metav1.ObjectMeta{Name: "default", Namespace: "namespace"}, @@ -2066,18 +2060,12 @@ func TestValidateGeneratedApplications(t *testing.T) { objects := append([]runtime.Object{}, secret) kubeclientset := kubefake.NewSimpleClientset(objects...) - argoDBMock := dbmocks.ArgoDB{} - argoDBMock.On("GetCluster", mock.Anything, "https://kubernetes.default.svc").Return(&myCluster, nil) - argoDBMock.On("ListClusters", mock.Anything).Return(&v1alpha1.ClusterList{Items: []v1alpha1.Cluster{ - myCluster, - }}, nil) - r := ApplicationSetReconciler{ Client: client, Scheme: scheme, Recorder: record.NewFakeRecorder(1), Generators: map[string]generators.Generator{}, - ArgoDB: &argoDBMock, + ArgoDB: &dbmocks.ArgoDB{}, ArgoCDNamespace: "namespace", KubeClientset: kubeclientset, Metrics: metrics, @@ -2159,17 +2147,9 @@ func TestReconcilerValidationProjectErrorBehaviour(t *testing.T) { } kubeclientset := kubefake.NewSimpleClientset() - argoDBMock := dbmocks.ArgoDB{} client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&appSet, &project).WithStatusSubresource(&appSet).WithIndex(&v1alpha1.Application{}, ".metadata.controller", appControllerIndexer).Build() metrics := appsetmetrics.NewFakeAppsetMetrics(client) - goodCluster := v1alpha1.Cluster{Server: "https://good-cluster", Name: "good-cluster"} - badCluster := v1alpha1.Cluster{Server: "https://bad-cluster", Name: "bad-cluster"} - argoDBMock.On("GetCluster", mock.Anything, "https://good-cluster").Return(&goodCluster, nil) - argoDBMock.On("GetCluster", mock.Anything, "https://bad-cluster").Return(&badCluster, nil) - argoDBMock.On("ListClusters", mock.Anything).Return(&v1alpha1.ClusterList{Items: []v1alpha1.Cluster{ - goodCluster, - }}, nil) r := ApplicationSetReconciler{ Client: client, @@ -2179,7 +2159,7 @@ func TestReconcilerValidationProjectErrorBehaviour(t *testing.T) { Generators: map[string]generators.Generator{ "List": generators.NewListGenerator(), }, - ArgoDB: &argoDBMock, + ArgoDB: &dbmocks.ArgoDB{}, KubeClientset: kubeclientset, Policy: v1alpha1.ApplicationsSyncPolicySync, ArgoCDNamespace: "argocd", @@ -2363,7 +2343,6 @@ func TestSetApplicationSetStatusCondition(t *testing.T) { } kubeclientset := kubefake.NewSimpleClientset([]runtime.Object{}...) - argoDBMock := dbmocks.ArgoDB{} for _, testCase := range testCases { client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&testCase.appset).WithIndex(&v1alpha1.Application{}, ".metadata.controller", appControllerIndexer).WithStatusSubresource(&testCase.appset).Build() @@ -2377,7 +2356,7 @@ func TestSetApplicationSetStatusCondition(t *testing.T) { Generators: map[string]generators.Generator{ "List": generators.NewListGenerator(), }, - ArgoDB: &argoDBMock, + ArgoDB: &dbmocks.ArgoDB{}, KubeClientset: kubeclientset, Metrics: metrics, } @@ -2433,16 +2412,28 @@ func applicationsUpdateSyncPolicyTest(t *testing.T, applicationsSyncPolicy v1alp }, } - kubeclientset := kubefake.NewSimpleClientset() - argoDBMock := dbmocks.ArgoDB{} + secret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-cluster", + Namespace: "argocd", + Labels: map[string]string{ + argocommon.LabelKeySecretType: argocommon.LabelValueSecretTypeCluster, + }, + }, + Data: map[string][]byte{ + // Since this test requires the cluster to be an invalid destination, we + // always return a cluster named 'my-cluster2' (different from app 'my-cluster', above) + "name": []byte("good-cluster"), + "server": []byte("https://good-cluster"), + "config": []byte("{\"username\":\"foo\",\"password\":\"foo\"}"), + }, + } + + objects := append([]runtime.Object{}, secret) + kubeclientset := kubefake.NewSimpleClientset(objects...) client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&appSet, &defaultProject).WithStatusSubresource(&appSet).WithIndex(&v1alpha1.Application{}, ".metadata.controller", appControllerIndexer).Build() metrics := appsetmetrics.NewFakeAppsetMetrics(client) - goodCluster := v1alpha1.Cluster{Server: "https://good-cluster", Name: "good-cluster"} - argoDBMock.On("GetCluster", mock.Anything, "https://good-cluster").Return(&goodCluster, nil) - argoDBMock.On("ListClusters", mock.Anything).Return(&v1alpha1.ClusterList{Items: []v1alpha1.Cluster{ - goodCluster, - }}, nil) r := ApplicationSetReconciler{ Client: client, @@ -2452,7 +2443,7 @@ func applicationsUpdateSyncPolicyTest(t *testing.T, applicationsSyncPolicy v1alp Generators: map[string]generators.Generator{ "List": generators.NewListGenerator(), }, - ArgoDB: &argoDBMock, + ArgoDB: &dbmocks.ArgoDB{}, ArgoCDNamespace: "argocd", KubeClientset: kubeclientset, Policy: v1alpha1.ApplicationsSyncPolicySync, @@ -2595,16 +2586,28 @@ func applicationsDeleteSyncPolicyTest(t *testing.T, applicationsSyncPolicy v1alp }, } - kubeclientset := kubefake.NewSimpleClientset() - argoDBMock := dbmocks.ArgoDB{} + secret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-cluster", + Namespace: "argocd", + Labels: map[string]string{ + argocommon.LabelKeySecretType: argocommon.LabelValueSecretTypeCluster, + }, + }, + Data: map[string][]byte{ + // Since this test requires the cluster to be an invalid destination, we + // always return a cluster named 'my-cluster2' (different from app 'my-cluster', above) + "name": []byte("good-cluster"), + "server": []byte("https://good-cluster"), + "config": []byte("{\"username\":\"foo\",\"password\":\"foo\"}"), + }, + } + + objects := append([]runtime.Object{}, secret) + kubeclientset := kubefake.NewSimpleClientset(objects...) client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&appSet, &defaultProject).WithStatusSubresource(&appSet).WithIndex(&v1alpha1.Application{}, ".metadata.controller", appControllerIndexer).Build() metrics := appsetmetrics.NewFakeAppsetMetrics(client) - goodCluster := v1alpha1.Cluster{Server: "https://good-cluster", Name: "good-cluster"} - argoDBMock.On("GetCluster", mock.Anything, "https://good-cluster").Return(&goodCluster, nil) - argoDBMock.On("ListClusters", mock.Anything).Return(&v1alpha1.ClusterList{Items: []v1alpha1.Cluster{ - goodCluster, - }}, nil) r := ApplicationSetReconciler{ Client: client, @@ -2614,7 +2617,7 @@ func applicationsDeleteSyncPolicyTest(t *testing.T, applicationsSyncPolicy v1alp Generators: map[string]generators.Generator{ "List": generators.NewListGenerator(), }, - ArgoDB: &argoDBMock, + ArgoDB: &dbmocks.ArgoDB{}, ArgoCDNamespace: "argocd", KubeClientset: kubeclientset, Policy: v1alpha1.ApplicationsSyncPolicySync, @@ -2689,7 +2692,7 @@ func TestDeletePerformedWithSyncPolicyCreateDelete(t *testing.T) { apps := applicationsDeleteSyncPolicyTest(t, applicationsSyncPolicy, 3, true) - assert.Empty(t, apps.Items) + assert.NotNil(t, apps.Items[0].DeletionTimestamp) } func TestDeletePerformedWithSyncPolicySync(t *testing.T) { @@ -2697,7 +2700,7 @@ func TestDeletePerformedWithSyncPolicySync(t *testing.T) { apps := applicationsDeleteSyncPolicyTest(t, applicationsSyncPolicy, 3, true) - assert.Empty(t, apps.Items) + assert.NotNil(t, apps.Items[0].DeletionTimestamp) } func TestDeletePerformedWithSyncPolicyCreateOnlyAndAllowPolicyOverrideFalse(t *testing.T) { @@ -2705,7 +2708,7 @@ func TestDeletePerformedWithSyncPolicyCreateOnlyAndAllowPolicyOverrideFalse(t *t apps := applicationsDeleteSyncPolicyTest(t, applicationsSyncPolicy, 3, false) - assert.Empty(t, apps.Items) + assert.NotNil(t, apps.Items[0].DeletionTimestamp) } func TestPolicies(t *testing.T) { @@ -2717,14 +2720,8 @@ func TestPolicies(t *testing.T) { ObjectMeta: metav1.ObjectMeta{Name: "default", Namespace: "argocd"}, Spec: v1alpha1.AppProjectSpec{SourceRepos: []string{"*"}, Destinations: []v1alpha1.ApplicationDestination{{Namespace: "*", Server: "https://kubernetes.default.svc"}}}, } - myCluster := v1alpha1.Cluster{ - Server: "https://kubernetes.default.svc", - Name: "my-cluster", - } kubeclientset := kubefake.NewSimpleClientset() - argoDBMock := dbmocks.ArgoDB{} - argoDBMock.On("GetCluster", mock.Anything, "https://kubernetes.default.svc").Return(&myCluster, nil) for _, c := range []struct { name string @@ -2807,7 +2804,7 @@ func TestPolicies(t *testing.T) { Generators: map[string]generators.Generator{ "List": generators.NewListGenerator(), }, - ArgoDB: &argoDBMock, + ArgoDB: &dbmocks.ArgoDB{}, ArgoCDNamespace: "argocd", KubeClientset: kubeclientset, Policy: policy, @@ -2881,7 +2878,6 @@ func TestSetApplicationSetApplicationStatus(t *testing.T) { require.NoError(t, err) kubeclientset := kubefake.NewSimpleClientset([]runtime.Object{}...) - argoDBMock := dbmocks.ArgoDB{} for _, cc := range []struct { name string @@ -2965,7 +2961,7 @@ func TestSetApplicationSetApplicationStatus(t *testing.T) { Generators: map[string]generators.Generator{ "List": generators.NewListGenerator(), }, - ArgoDB: &argoDBMock, + ArgoDB: &dbmocks.ArgoDB{}, KubeClientset: kubeclientset, Metrics: metrics, } @@ -3714,14 +3710,13 @@ func TestBuildAppDependencyList(t *testing.T) { } { t.Run(cc.name, func(t *testing.T) { kubeclientset := kubefake.NewSimpleClientset([]runtime.Object{}...) - argoDBMock := dbmocks.ArgoDB{} r := ApplicationSetReconciler{ Client: client, Scheme: scheme, Recorder: record.NewFakeRecorder(1), Generators: map[string]generators.Generator{}, - ArgoDB: &argoDBMock, + ArgoDB: &dbmocks.ArgoDB{}, KubeClientset: kubeclientset, Metrics: metrics, } @@ -4381,14 +4376,13 @@ func TestBuildAppSyncMap(t *testing.T) { } { t.Run(cc.name, func(t *testing.T) { kubeclientset := kubefake.NewSimpleClientset([]runtime.Object{}...) - argoDBMock := dbmocks.ArgoDB{} r := ApplicationSetReconciler{ Client: client, Scheme: scheme, Recorder: record.NewFakeRecorder(1), Generators: map[string]generators.Generator{}, - ArgoDB: &argoDBMock, + ArgoDB: &dbmocks.ArgoDB{}, KubeClientset: kubeclientset, Metrics: metrics, } @@ -5326,7 +5320,6 @@ func TestUpdateApplicationSetApplicationStatus(t *testing.T) { } { t.Run(cc.name, func(t *testing.T) { kubeclientset := kubefake.NewSimpleClientset([]runtime.Object{}...) - argoDBMock := dbmocks.ArgoDB{} client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&cc.appSet).WithStatusSubresource(&cc.appSet).Build() metrics := appsetmetrics.NewFakeAppsetMetrics(client) @@ -5336,7 +5329,7 @@ func TestUpdateApplicationSetApplicationStatus(t *testing.T) { Scheme: scheme, Recorder: record.NewFakeRecorder(1), Generators: map[string]generators.Generator{}, - ArgoDB: &argoDBMock, + ArgoDB: &dbmocks.ArgoDB{}, KubeClientset: kubeclientset, Metrics: metrics, } @@ -6075,7 +6068,6 @@ func TestUpdateApplicationSetApplicationStatusProgress(t *testing.T) { } { t.Run(cc.name, func(t *testing.T) { kubeclientset := kubefake.NewSimpleClientset([]runtime.Object{}...) - argoDBMock := dbmocks.ArgoDB{} client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&cc.appSet).WithStatusSubresource(&cc.appSet).Build() metrics := appsetmetrics.NewFakeAppsetMetrics(client) @@ -6085,7 +6077,7 @@ func TestUpdateApplicationSetApplicationStatusProgress(t *testing.T) { Scheme: scheme, Recorder: record.NewFakeRecorder(1), Generators: map[string]generators.Generator{}, - ArgoDB: &argoDBMock, + ArgoDB: &dbmocks.ArgoDB{}, KubeClientset: kubeclientset, Metrics: metrics, } @@ -6286,7 +6278,6 @@ func TestUpdateResourceStatus(t *testing.T) { } { t.Run(cc.name, func(t *testing.T) { kubeclientset := kubefake.NewSimpleClientset([]runtime.Object{}...) - argoDBMock := dbmocks.ArgoDB{} client := fake.NewClientBuilder().WithScheme(scheme).WithStatusSubresource(&cc.appSet).WithObjects(&cc.appSet).Build() metrics := appsetmetrics.NewFakeAppsetMetrics(client) @@ -6296,7 +6287,7 @@ func TestUpdateResourceStatus(t *testing.T) { Scheme: scheme, Recorder: record.NewFakeRecorder(1), Generators: map[string]generators.Generator{}, - ArgoDB: &argoDBMock, + ArgoDB: &dbmocks.ArgoDB{}, KubeClientset: kubeclientset, Metrics: metrics, } @@ -6376,7 +6367,6 @@ func TestResourceStatusAreOrdered(t *testing.T) { } { t.Run(cc.name, func(t *testing.T) { kubeclientset := kubefake.NewSimpleClientset([]runtime.Object{}...) - argoDBMock := dbmocks.ArgoDB{} client := fake.NewClientBuilder().WithScheme(scheme).WithStatusSubresource(&cc.appSet).WithObjects(&cc.appSet).Build() metrics := appsetmetrics.NewFakeAppsetMetrics(client) @@ -6386,7 +6376,7 @@ func TestResourceStatusAreOrdered(t *testing.T) { Scheme: scheme, Recorder: record.NewFakeRecorder(1), Generators: map[string]generators.Generator{}, - ArgoDB: &argoDBMock, + ArgoDB: &dbmocks.ArgoDB{}, KubeClientset: kubeclientset, Metrics: metrics, } diff --git a/applicationset/utils/clusterUtils.go b/applicationset/utils/clusterUtils.go index 8c44dc1246be5..2645ff432cbb9 100644 --- a/applicationset/utils/clusterUtils.go +++ b/applicationset/utils/clusterUtils.go @@ -51,9 +51,12 @@ const ( // if we used destination name we infer the server url // if we used both name and server then we return an invalid spec error func ValidateDestination(ctx context.Context, dest *appv1.ApplicationDestination, clientset kubernetes.Interface, argoCDNamespace string) error { + if dest.IsServerInferred() && dest.IsNameInferred() { + return fmt.Errorf("application destination can't have both name and server inferred: %s %s", dest.Name, dest.Server) + } if dest.Name != "" { if dest.Server == "" { - server, err := getDestinationServer(ctx, dest.Name, clientset, argoCDNamespace) + server, err := getDestinationBy(ctx, dest.Name, clientset, argoCDNamespace, true) if err != nil { return fmt.Errorf("unable to find destination server: %w", err) } @@ -61,14 +64,25 @@ func ValidateDestination(ctx context.Context, dest *appv1.ApplicationDestination return fmt.Errorf("application references destination cluster %s which does not exist", dest.Name) } dest.SetInferredServer(server) - } else if !dest.IsServerInferred() { + } else if !dest.IsServerInferred() && !dest.IsNameInferred() { return fmt.Errorf("application destination can't have both name and server defined: %s %s", dest.Name, dest.Server) } + } else if dest.Server != "" { + if dest.Name == "" { + serverName, err := getDestinationBy(ctx, dest.Server, clientset, argoCDNamespace, false) + if err != nil { + return fmt.Errorf("unable to find destination server: %w", err) + } + if serverName == "" { + return fmt.Errorf("application references destination cluster %s which does not exist", dest.Server) + } + dest.SetInferredName(serverName) + } } return nil } -func getDestinationServer(ctx context.Context, clusterName string, clientset kubernetes.Interface, argoCDNamespace string) (string, error) { +func getDestinationBy(ctx context.Context, cluster string, clientset kubernetes.Interface, argoCDNamespace string, byName bool) (string, error) { // settingsMgr := settings.NewSettingsManager(context.TODO(), clientset, namespace) // argoDB := db.NewDB(namespace, settingsMgr, clientset) // clusterList, err := argoDB.ListClusters(ctx) @@ -78,14 +92,17 @@ func getDestinationServer(ctx context.Context, clusterName string, clientset kub } var servers []string for _, c := range clusterList.Items { - if c.Name == clusterName { + if byName && c.Name == cluster { servers = append(servers, c.Server) } + if !byName && c.Server == cluster { + servers = append(servers, c.Name) + } } if len(servers) > 1 { return "", fmt.Errorf("there are %d clusters with the same name: %v", len(servers), servers) } else if len(servers) == 0 { - return "", fmt.Errorf("there are no clusters with this name: %s", clusterName) + return "", fmt.Errorf("there are no clusters with this name: %s", cluster) } return servers[0], nil } diff --git a/applicationset/utils/clusterUtils_test.go b/applicationset/utils/clusterUtils_test.go index fdc316fbc428c..3161f8ac82864 100644 --- a/applicationset/utils/clusterUtils_test.go +++ b/applicationset/utils/clusterUtils_test.go @@ -93,7 +93,12 @@ func TestValidateDestination(t *testing.T) { Namespace: "default", } - appCond := ValidateDestination(context.Background(), &dest, nil, fakeNamespace) + secret := createClusterSecret("my-secret", "minikube", "https://127.0.0.1:6443") + objects := []runtime.Object{} + objects = append(objects, secret) + kubeclientset := fake.NewSimpleClientset(objects...) + + appCond := ValidateDestination(context.Background(), &dest, kubeclientset, fakeNamespace) require.NoError(t, appCond) assert.False(t, dest.IsServerInferred()) }) diff --git a/pkg/apis/application/v1alpha1/types.go b/pkg/apis/application/v1alpha1/types.go index ef8947885eb66..452914457056a 100644 --- a/pkg/apis/application/v1alpha1/types.go +++ b/pkg/apis/application/v1alpha1/types.go @@ -1005,6 +1005,8 @@ type ApplicationDestination struct { // nolint:govet isServerInferred bool `json:"-"` + // nolint:govet + isNameInferred bool `json:"-"` } // SetIsServerInferred sets the isServerInferred flag. This is used to allow comparison between two destinations where @@ -3054,6 +3056,17 @@ func (dest ApplicationDestination) Equals(other ApplicationDestination) bool { other.Server = "" other.isServerInferred = false } + + if dest.isNameInferred { + dest.Name = "" + dest.isNameInferred = false + } + + if other.isNameInferred { + other.Name = "" + other.isNameInferred = false + } + return reflect.DeepEqual(dest, other) } @@ -3316,6 +3329,12 @@ func (d *ApplicationDestination) SetInferredServer(server string) { d.Server = server } +// SetInferredName sets the Name field of the destination. See IsNameInferred() for details. +func (d *ApplicationDestination) SetInferredName(name string) { + d.isNameInferred = true + d.Name = name +} + // An ApplicationDestination has an 'inferred server' if the ApplicationDestination // contains a Name, but not a Server URL. In this case it is necessary to retrieve // the Server URL by looking up the cluster name. @@ -3326,6 +3345,10 @@ func (d *ApplicationDestination) IsServerInferred() bool { return d.isServerInferred } +func (d *ApplicationDestination) IsNameInferred() bool { + return d.isNameInferred +} + // MarshalJSON marshals an application destination to JSON format func (d *ApplicationDestination) MarshalJSON() ([]byte, error) { type Alias ApplicationDestination @@ -3334,6 +3357,11 @@ func (d *ApplicationDestination) MarshalJSON() ([]byte, error) { dest = dest.DeepCopy() dest.Server = "" } + if d.isNameInferred { + dest = dest.DeepCopy() + dest.Name = "" + } + return json.Marshal(&struct{ *Alias }{Alias: (*Alias)(dest)}) } diff --git a/server/application/application.go b/server/application/application.go index 5c42f9d8f99b9..9fee8369795b1 100644 --- a/server/application/application.go +++ b/server/application/application.go @@ -369,7 +369,13 @@ func (s *Server) Create(ctx context.Context, q *application.ApplicationCreateReq if err != nil { return nil, status.Errorf(codes.Internal, "unable to check existing application details (%s): %v", appNs, err) } - equalSpecs := reflect.DeepEqual(existing.Spec, a.Spec) && + + if err := argo.ValidateDestination(ctx, &existing.Spec.Destination, s.db); err != nil { + return nil, status.Errorf(codes.InvalidArgument, "application destination spec for %s is invalid: %s", existing.Name, err.Error()) + } + + equalSpecs := existing.Spec.Destination.Equals(a.Spec.Destination) && + reflect.DeepEqual(existing.Spec, a.Spec) && reflect.DeepEqual(existing.Labels, a.Labels) && reflect.DeepEqual(existing.Annotations, a.Annotations) && reflect.DeepEqual(existing.Finalizers, a.Finalizers) diff --git a/test/e2e/app_management_ns_test.go b/test/e2e/app_management_ns_test.go index d2d605382892e..328d252f66703 100644 --- a/test/e2e/app_management_ns_test.go +++ b/test/e2e/app_management_ns_test.go @@ -346,7 +346,7 @@ func TestNamespacedAppCreationWithoutForceUpdate(t *testing.T) { }). When(). IgnoreErrors(). - CreateApp(). + CreateApp("--dest-server", KubernetesInternalAPIServerAddr). Then(). Expect(Error("", "existing application spec is different, use upsert flag to force update")) } diff --git a/test/e2e/app_management_test.go b/test/e2e/app_management_test.go index a20e09477ce6b..3b471bc304718 100644 --- a/test/e2e/app_management_test.go +++ b/test/e2e/app_management_test.go @@ -449,7 +449,7 @@ func TestAppCreationWithoutForceUpdate(t *testing.T) { }). When(). IgnoreErrors(). - CreateApp(). + CreateApp("--dest-server", KubernetesInternalAPIServerAddr). Then(). Expect(Error("", "existing application spec is different, use upsert flag to force update")) } diff --git a/test/e2e/fixture/app/actions.go b/test/e2e/fixture/app/actions.go index 5f012e5aa5134..3342521d25f1b 100644 --- a/test/e2e/fixture/app/actions.go +++ b/test/e2e/fixture/app/actions.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "os" + "slices" "strconv" log "github.com/sirupsen/logrus" @@ -226,7 +227,7 @@ func (a *Actions) prepareCreateAppArgs(args []string) []string { "--repo", fixture.RepoURL(a.context.repoURLType), }, args...) - if a.context.destName != "" { + if a.context.destName != "" && a.context.isDestServerInferred && !slices.Contains(args, "--dest-server") { args = append(args, "--dest-name", a.context.destName) } else { args = append(args, "--dest-server", a.context.destServer) diff --git a/test/e2e/fixture/app/context.go b/test/e2e/fixture/app/context.go index 1107ea7e33fed..845c41d60a7d6 100644 --- a/test/e2e/fixture/app/context.go +++ b/test/e2e/fixture/app/context.go @@ -15,7 +15,7 @@ import ( "github.com/argoproj/argo-cd/v2/util/settings" ) -// this implements the "given" part of given/when/then +// Context implements the "given" part of given/when/then type Context struct { t *testing.T path string @@ -27,6 +27,7 @@ type Context struct { appNamespace string destServer string destName string + isDestServerInferred bool env string parameters []string namePrefix string @@ -69,12 +70,13 @@ func GivenWithNamespace(t *testing.T, namespace string) *Context { func GivenWithSameState(t *testing.T) *Context { t.Helper() - // ARGOCE_E2E_DEFAULT_TIMEOUT can be used to override the default timeout + // ARGOCD_E2E_DEFAULT_TIMEOUT can be used to override the default timeout // for any context. timeout := env.ParseNumFromEnv("ARGOCD_E2E_DEFAULT_TIMEOUT", 20, 0, 180) return &Context{ t: t, destServer: v1alpha1.KubernetesInternalAPIServerAddr, + destName: "in-cluster", repoURLType: fixture.RepoURLTypeFile, name: fixture.Name(), timeout: timeout, @@ -263,11 +265,13 @@ func (c *Context) Timeout(timeout int) *Context { func (c *Context) DestServer(destServer string) *Context { c.destServer = destServer + c.isDestServerInferred = false return c } func (c *Context) DestName(destName string) *Context { c.destName = destName + c.isDestServerInferred = true return c } diff --git a/util/argo/argo.go b/util/argo/argo.go index 676a059db4761..a965ebd5cd56d 100644 --- a/util/argo/argo.go +++ b/util/argo/argo.go @@ -485,16 +485,18 @@ func GetRefSources(ctx context.Context, sources argoappv1.ApplicationSources, pr return refSources, nil } -// ValidateDestination sets the 'Server' value of the ApplicationDestination, if it is not set. +// ValidateDestination sets the 'Server' or the `Name` value of the ApplicationDestination, if it is not set. // NOTE: this function WILL write to the object pointed to by the 'dest' parameter. -// // If an ApplicationDestination has a Name field, but has an empty Server (URL) field, // ValidationDestination will look up the cluster by name (to get the server URL), and -// set the corresponding Server field value. +// set the corresponding Server field value. Same goes for the opposite case. // // It also checks: // - If we used both name and server then we return an invalid spec error func ValidateDestination(ctx context.Context, dest *argoappv1.ApplicationDestination, db db.ArgoDB) error { + if dest.IsServerInferred() && dest.IsNameInferred() { + return fmt.Errorf("application destination can't have both name and server inferred: %s %s", dest.Name, dest.Server) + } if dest.Name != "" { if dest.Server == "" { server, err := getDestinationServer(ctx, db, dest.Name) @@ -505,9 +507,20 @@ func ValidateDestination(ctx context.Context, dest *argoappv1.ApplicationDestina return fmt.Errorf("application references destination cluster %s which does not exist", dest.Name) } dest.SetInferredServer(server) - } else if !dest.IsServerInferred() { + } else if !dest.IsServerInferred() && !dest.IsNameInferred() { return fmt.Errorf("application destination can't have both name and server defined: %s %s", dest.Name, dest.Server) } + } else if dest.Server != "" { + if dest.Name == "" { + serverName, err := getDestinationServerName(ctx, db, dest.Server) + if err != nil { + return fmt.Errorf("unable to find destination server: %w", err) + } + if serverName == "" { + return fmt.Errorf("application references destination cluster %s which does not exist", dest.Server) + } + dest.SetInferredName(serverName) + } } return nil } @@ -959,6 +972,22 @@ func getDestinationServer(ctx context.Context, db db.ArgoDB, clusterName string) return servers[0], nil } +func getDestinationServerName(ctx context.Context, db db.ArgoDB, server string) (string, error) { + if db == nil { + return "", fmt.Errorf("there are no clusters registered in the database") + } + + cluster, err := db.GetCluster(ctx, server) + if err != nil { + return "", fmt.Errorf("error getting cluster name by server %q: %w", server, err) + } + + if cluster.Name == "" { + return "", fmt.Errorf("there are no clusters with this URL: %s", server) + } + return cluster.Name, nil +} + func GetGlobalProjects(proj *argoappv1.AppProject, projLister applicationsv1.AppProjectLister, settingsManager *settings.SettingsManager) []*argoappv1.AppProject { gps, err := settingsManager.GetGlobalProjectsSettings() globalProjects := make([]*argoappv1.AppProject, 0) diff --git a/util/argo/argo_test.go b/util/argo/argo_test.go index 74db80a0c198c..d07360665b20f 100644 --- a/util/argo/argo_test.go +++ b/util/argo/argo_test.go @@ -702,7 +702,7 @@ func TestValidatePermissions(t *testing.T) { SourceRepos: []string{"http://some/where/else"}, }, } - cluster := &argoappv1.Cluster{Server: "https://127.0.0.1:6443"} + cluster := &argoappv1.Cluster{Server: "https://127.0.0.1:6443", Name: "test"} db := &dbmocks.ArgoDB{} db.On("GetCluster", context.Background(), spec.Destination.Server).Return(cluster, nil) conditions, err := ValidatePermissions(context.Background(), &spec, &proj, db) @@ -735,7 +735,7 @@ func TestValidatePermissions(t *testing.T) { SourceRepos: []string{"http://some/where"}, }, } - cluster := &argoappv1.Cluster{Server: "https://127.0.0.1:6443"} + cluster := &argoappv1.Cluster{Server: "https://127.0.0.1:6443", Name: "test"} db := &dbmocks.ArgoDB{} db.On("GetCluster", context.Background(), spec.Destination.Server).Return(cluster, nil) conditions, err := ValidatePermissions(context.Background(), &spec, &proj, db) @@ -773,7 +773,7 @@ func TestValidatePermissions(t *testing.T) { conditions, err := ValidatePermissions(context.Background(), &spec, &proj, db) require.NoError(t, err) assert.Len(t, conditions, 1) - assert.Contains(t, conditions[0].Message, "has not been configured") + assert.Contains(t, conditions[0].Message, "unable to find destination server") }) t.Run("Destination cluster name does not exist", func(t *testing.T) { @@ -834,8 +834,10 @@ func TestValidatePermissions(t *testing.T) { } db := &dbmocks.ArgoDB{} db.On("GetCluster", context.Background(), spec.Destination.Server).Return(nil, fmt.Errorf("Unknown error occurred")) - _, err := ValidatePermissions(context.Background(), &spec, &proj, db) - require.Error(t, err) + conditions, err := ValidatePermissions(context.Background(), &spec, &proj, db) + require.NoError(t, err) + assert.Len(t, conditions, 1) + assert.Contains(t, conditions[0].Message, "Unknown error occurred") }) t.Run("Destination cluster name resolves to valid server", func(t *testing.T) { @@ -932,7 +934,7 @@ func TestValidateDestination(t *testing.T) { } appCond := ValidateDestination(context.Background(), &dest, nil) - require.NoError(t, appCond) + require.Error(t, appCond) assert.False(t, dest.IsServerInferred()) }) @@ -1420,7 +1422,7 @@ func TestValidatePermissionsMultipleSources(t *testing.T) { SourceRepos: []string{"http://some/where/else"}, }, } - cluster := &argoappv1.Cluster{Server: "https://127.0.0.1:6443"} + cluster := &argoappv1.Cluster{Server: "https://127.0.0.1:6443", Name: "test"} db := &dbmocks.ArgoDB{} db.On("GetCluster", context.Background(), spec.Destination.Server).Return(cluster, nil) conditions, err := ValidatePermissions(context.Background(), &spec, &proj, db)