mirror of
https://github.com/argoproj/argo-cd
synced 2026-04-21 17:07:16 +00:00
2204 lines
72 KiB
Go
2204 lines
72 KiB
Go
package argo
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/argoproj/argo-cd/gitops-engine/pkg/utils/kube"
|
|
"github.com/argoproj/argo-cd/gitops-engine/pkg/utils/kube/kubetest"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/mock"
|
|
"github.com/stretchr/testify/require"
|
|
"google.golang.org/grpc/codes"
|
|
"google.golang.org/grpc/status"
|
|
corev1 "k8s.io/api/core/v1"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
"k8s.io/client-go/kubernetes/fake"
|
|
"k8s.io/client-go/tools/cache"
|
|
|
|
"github.com/argoproj/argo-cd/gitops-engine/pkg/sync/common"
|
|
|
|
argoappv1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
|
|
appclientset "github.com/argoproj/argo-cd/v3/pkg/client/clientset/versioned/fake"
|
|
"github.com/argoproj/argo-cd/v3/pkg/client/informers/externalversions/application/v1alpha1"
|
|
applisters "github.com/argoproj/argo-cd/v3/pkg/client/listers/application/v1alpha1"
|
|
"github.com/argoproj/argo-cd/v3/reposerver/apiclient"
|
|
"github.com/argoproj/argo-cd/v3/reposerver/apiclient/mocks"
|
|
"github.com/argoproj/argo-cd/v3/test"
|
|
"github.com/argoproj/argo-cd/v3/util/db"
|
|
dbmocks "github.com/argoproj/argo-cd/v3/util/db/mocks"
|
|
"github.com/argoproj/argo-cd/v3/util/settings"
|
|
)
|
|
|
|
func TestRefreshApp(t *testing.T) {
|
|
var testApp argoappv1.Application
|
|
testApp.Name = "test-app"
|
|
testApp.Namespace = "default"
|
|
appClientset := appclientset.NewSimpleClientset(&testApp)
|
|
appIf := appClientset.ArgoprojV1alpha1().Applications("default")
|
|
_, err := RefreshApp(appIf, "test-app", argoappv1.RefreshTypeNormal, true)
|
|
require.NoError(t, err)
|
|
// For some reason, the fake Application interface doesn't reflect the patch status after Patch(),
|
|
// so can't verify it was set in unit tests.
|
|
// _, ok := newApp.Annotations[common.AnnotationKeyRefresh]
|
|
// assert.True(t, ok)
|
|
}
|
|
|
|
func TestGetAppProjectWithNoProjDefined(t *testing.T) {
|
|
projName := "default"
|
|
namespace := "default"
|
|
|
|
cm := corev1.ConfigMap{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "argocd-cm",
|
|
Namespace: test.FakeArgoCDNamespace,
|
|
Labels: map[string]string{
|
|
"app.kubernetes.io/part-of": "argocd",
|
|
},
|
|
},
|
|
}
|
|
|
|
testProj := &argoappv1.AppProject{
|
|
ObjectMeta: metav1.ObjectMeta{Name: projName, Namespace: namespace},
|
|
}
|
|
|
|
var testApp argoappv1.Application
|
|
testApp.Name = "test-app"
|
|
testApp.Namespace = namespace
|
|
appClientset := appclientset.NewSimpleClientset(testProj)
|
|
ctx, cancel := context.WithCancel(t.Context())
|
|
defer cancel()
|
|
indexers := cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}
|
|
informer := v1alpha1.NewAppProjectInformer(appClientset, namespace, 0, indexers)
|
|
go informer.Run(ctx.Done())
|
|
cache.WaitForCacheSync(ctx.Done(), informer.HasSynced)
|
|
|
|
kubeClient := fake.NewClientset(&cm)
|
|
settingsMgr := settings.NewSettingsManager(t.Context(), kubeClient, test.FakeArgoCDNamespace)
|
|
argoDB := db.NewDB("default", settingsMgr, kubeClient)
|
|
proj, err := GetAppProject(ctx, &testApp, applisters.NewAppProjectLister(informer.GetIndexer()), namespace, settingsMgr, argoDB)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, proj.Name, projName)
|
|
}
|
|
|
|
func TestIncludeResource(t *testing.T) {
|
|
// Resource filters format - GROUP:KIND:NAMESPACE/NAME or GROUP:KIND:NAME
|
|
var (
|
|
blankValues = argoappv1.SyncOperationResource{Group: "", Kind: "", Name: "", Namespace: "", Exclude: false}
|
|
// *:*:*
|
|
includeAllResources = argoappv1.SyncOperationResource{Group: "*", Kind: "*", Name: "*", Namespace: "", Exclude: false}
|
|
// !*:*:*
|
|
excludeAllResources = argoappv1.SyncOperationResource{Group: "*", Kind: "*", Name: "*", Namespace: "", Exclude: true}
|
|
// *:Service:*
|
|
includeAllServiceResources = argoappv1.SyncOperationResource{Group: "*", Kind: "Service", Name: "*", Namespace: "", Exclude: false}
|
|
// !*:Service:*
|
|
excludeAllServiceResources = argoappv1.SyncOperationResource{Group: "*", Kind: "Service", Name: "*", Namespace: "", Exclude: true}
|
|
// apps:ReplicaSet:backend
|
|
includeAllReplicaSetResource = argoappv1.SyncOperationResource{Group: "apps", Kind: "ReplicaSet", Name: "*", Namespace: "", Exclude: false}
|
|
// apps:ReplicaSet:backend
|
|
includeReplicaSetResource = argoappv1.SyncOperationResource{Group: "apps", Kind: "ReplicaSet", Name: "backend", Namespace: "", Exclude: false}
|
|
// !apps:ReplicaSet:backend
|
|
excludeReplicaSetResource = argoappv1.SyncOperationResource{Group: "apps", Kind: "ReplicaSet", Name: "backend", Namespace: "", Exclude: true}
|
|
)
|
|
tests := []struct {
|
|
testName string
|
|
name string
|
|
namespace string
|
|
gvk schema.GroupVersionKind
|
|
syncOperationResource []*argoappv1.SyncOperationResource
|
|
expectedResult bool
|
|
}{
|
|
//--resource apps:ReplicaSet:backend --resource *:Service:*
|
|
{
|
|
testName: "Include ReplicaSet backend resource and all service resources",
|
|
name: "backend",
|
|
namespace: "default",
|
|
gvk: schema.GroupVersionKind{Group: "apps", Kind: "ReplicaSet"},
|
|
syncOperationResource: []*argoappv1.SyncOperationResource{&includeAllServiceResources, &includeReplicaSetResource},
|
|
expectedResult: true,
|
|
},
|
|
//--resource apps:ReplicaSet:backend --resource *:Service:*
|
|
{
|
|
testName: "Include ReplicaSet backend resource and all service resources",
|
|
name: "main-page-down",
|
|
namespace: "default",
|
|
gvk: schema.GroupVersionKind{Group: "batch", Kind: "Job"},
|
|
syncOperationResource: []*argoappv1.SyncOperationResource{&includeAllServiceResources, &includeReplicaSetResource},
|
|
expectedResult: false,
|
|
},
|
|
//--resource apps:ReplicaSet:backend --resource !*:Service:*
|
|
{
|
|
testName: "Include ReplicaSet backend resource and exclude all service resources",
|
|
name: "main-page-down",
|
|
namespace: "default",
|
|
gvk: schema.GroupVersionKind{Group: "batch", Kind: "Job"},
|
|
syncOperationResource: []*argoappv1.SyncOperationResource{&excludeAllServiceResources, &includeReplicaSetResource},
|
|
expectedResult: false,
|
|
},
|
|
// --resource !apps:ReplicaSet:backend --resource !*:Service:*
|
|
{
|
|
testName: "Exclude ReplicaSet backend resource and all service resources",
|
|
name: "main-page-down",
|
|
namespace: "default",
|
|
gvk: schema.GroupVersionKind{Group: "batch", Kind: "Job"},
|
|
syncOperationResource: []*argoappv1.SyncOperationResource{&excludeReplicaSetResource, &excludeAllServiceResources},
|
|
expectedResult: true,
|
|
},
|
|
// --resource !apps:ReplicaSet:backend
|
|
{
|
|
testName: "Exclude ReplicaSet backend resource",
|
|
name: "backend",
|
|
namespace: "default",
|
|
gvk: schema.GroupVersionKind{Group: "apps", Kind: "ReplicaSet"},
|
|
syncOperationResource: []*argoappv1.SyncOperationResource{&excludeReplicaSetResource},
|
|
expectedResult: false,
|
|
},
|
|
// --resource !apps:ReplicaSet:backend --resource !*:Service:*
|
|
{
|
|
testName: "Exclude ReplicaSet backend resource and all service resources(dummy condition)",
|
|
name: "backend",
|
|
namespace: "default",
|
|
gvk: schema.GroupVersionKind{Group: "apps", Kind: "ReplicaSet"},
|
|
syncOperationResource: []*argoappv1.SyncOperationResource{&excludeReplicaSetResource, &excludeAllServiceResources},
|
|
expectedResult: false,
|
|
},
|
|
// --resource apps:ReplicaSet:backend
|
|
{
|
|
testName: "Include ReplicaSet backend resource",
|
|
name: "backend",
|
|
namespace: "default",
|
|
gvk: schema.GroupVersionKind{Group: "apps", Kind: "ReplicaSet"},
|
|
syncOperationResource: []*argoappv1.SyncOperationResource{&includeReplicaSetResource},
|
|
expectedResult: true,
|
|
},
|
|
// --resource !*:Service:*
|
|
{
|
|
testName: "Exclude Service resources",
|
|
name: "backend",
|
|
namespace: "default",
|
|
gvk: schema.GroupVersionKind{Group: "", Kind: "Service"},
|
|
syncOperationResource: []*argoappv1.SyncOperationResource{&excludeAllServiceResources},
|
|
expectedResult: false,
|
|
},
|
|
// --resource *:Service:*
|
|
{
|
|
testName: "Include Service resources",
|
|
name: "backend",
|
|
namespace: "default",
|
|
gvk: schema.GroupVersionKind{Group: "", Kind: "Service"},
|
|
syncOperationResource: []*argoappv1.SyncOperationResource{&includeAllServiceResources},
|
|
expectedResult: true,
|
|
},
|
|
// --resource apps:ReplicaSet:* --resource !apps:ReplicaSet:backend
|
|
{
|
|
testName: "Include & Exclude ReplicaSet resources",
|
|
name: "backend",
|
|
namespace: "default",
|
|
gvk: schema.GroupVersionKind{Group: "apps", Kind: "ReplicaSet"},
|
|
syncOperationResource: []*argoappv1.SyncOperationResource{&includeAllReplicaSetResource, &excludeReplicaSetResource},
|
|
expectedResult: false,
|
|
},
|
|
// --resource !*:*:*
|
|
{
|
|
testName: "Exclude all resources",
|
|
name: "backend",
|
|
namespace: "default",
|
|
gvk: schema.GroupVersionKind{Group: "", Kind: "Service"},
|
|
syncOperationResource: []*argoappv1.SyncOperationResource{&excludeAllResources},
|
|
expectedResult: false,
|
|
},
|
|
// --resource *:*:*
|
|
{
|
|
testName: "Include all resources",
|
|
name: "backend",
|
|
namespace: "default",
|
|
gvk: schema.GroupVersionKind{Group: "", Kind: "Service"},
|
|
syncOperationResource: []*argoappv1.SyncOperationResource{&includeAllResources},
|
|
expectedResult: true,
|
|
},
|
|
{
|
|
testName: "No Filters",
|
|
name: "backend",
|
|
namespace: "default",
|
|
gvk: schema.GroupVersionKind{Group: "", Kind: "Service"},
|
|
syncOperationResource: []*argoappv1.SyncOperationResource{&blankValues},
|
|
expectedResult: false,
|
|
},
|
|
{
|
|
testName: "Default values",
|
|
expectedResult: true,
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.testName, func(t *testing.T) {
|
|
isResourceIncluded := IncludeResource(test.name, test.namespace, test.gvk, test.syncOperationResource)
|
|
assert.Equal(t, test.expectedResult, isResourceIncluded)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestContainsSyncResource(t *testing.T) {
|
|
var (
|
|
blankUnstructured unstructured.Unstructured
|
|
blankResource argoappv1.SyncOperationResource
|
|
helloResource = argoappv1.SyncOperationResource{Name: "hello"}
|
|
)
|
|
tables := []struct {
|
|
u *unstructured.Unstructured
|
|
rr []argoappv1.SyncOperationResource
|
|
expected bool
|
|
}{
|
|
{&blankUnstructured, []argoappv1.SyncOperationResource{}, false},
|
|
{&blankUnstructured, []argoappv1.SyncOperationResource{blankResource}, true},
|
|
{&blankUnstructured, []argoappv1.SyncOperationResource{helloResource}, false},
|
|
}
|
|
|
|
for _, table := range tables {
|
|
out := ContainsSyncResource(table.u.GetName(), table.u.GetNamespace(), table.u.GroupVersionKind(), table.rr)
|
|
assert.Equal(t, table.expected, out, "Expected %t for slice %+v contains resource %+v; instead got %t", table.expected, table.rr, table.u, out)
|
|
}
|
|
}
|
|
|
|
// TestNilOutZerValueAppSources verifies we will nil out app source specs when they are their zero-value
|
|
func TestNilOutZerValueAppSources(t *testing.T) {
|
|
var spec *argoappv1.ApplicationSpec
|
|
spec = NormalizeApplicationSpec(&argoappv1.ApplicationSpec{Source: &argoappv1.ApplicationSource{Kustomize: &argoappv1.ApplicationSourceKustomize{NamePrefix: "foo"}}})
|
|
assert.NotNil(t, spec.GetSource().Kustomize)
|
|
spec = NormalizeApplicationSpec(&argoappv1.ApplicationSpec{Source: &argoappv1.ApplicationSource{Kustomize: &argoappv1.ApplicationSourceKustomize{NamePrefix: ""}}})
|
|
source := spec.GetSource()
|
|
assert.Nil(t, source.Kustomize)
|
|
|
|
spec = NormalizeApplicationSpec(&argoappv1.ApplicationSpec{Source: &argoappv1.ApplicationSource{Kustomize: &argoappv1.ApplicationSourceKustomize{NameSuffix: "foo"}}})
|
|
assert.NotNil(t, spec.GetSource().Kustomize)
|
|
spec = NormalizeApplicationSpec(&argoappv1.ApplicationSpec{Source: &argoappv1.ApplicationSource{Kustomize: &argoappv1.ApplicationSourceKustomize{NameSuffix: ""}}})
|
|
source = spec.GetSource()
|
|
assert.Nil(t, source.Kustomize)
|
|
|
|
spec = NormalizeApplicationSpec(&argoappv1.ApplicationSpec{Source: &argoappv1.ApplicationSource{Helm: &argoappv1.ApplicationSourceHelm{ValueFiles: []string{"values.yaml"}}}})
|
|
assert.NotNil(t, spec.GetSource().Helm)
|
|
spec = NormalizeApplicationSpec(&argoappv1.ApplicationSpec{Source: &argoappv1.ApplicationSource{Helm: &argoappv1.ApplicationSourceHelm{ValueFiles: []string{}}}})
|
|
assert.Nil(t, spec.GetSource().Helm)
|
|
|
|
spec = NormalizeApplicationSpec(&argoappv1.ApplicationSpec{Source: &argoappv1.ApplicationSource{Directory: &argoappv1.ApplicationSourceDirectory{Recurse: true}}})
|
|
assert.NotNil(t, spec.GetSource().Directory)
|
|
spec = NormalizeApplicationSpec(&argoappv1.ApplicationSpec{Source: &argoappv1.ApplicationSource{Directory: &argoappv1.ApplicationSourceDirectory{Recurse: false}}})
|
|
assert.Nil(t, spec.GetSource().Directory)
|
|
}
|
|
|
|
func TestValidatePermissionsEmptyDestination(t *testing.T) {
|
|
conditions, err := ValidatePermissions(t.Context(), &argoappv1.ApplicationSpec{
|
|
Source: &argoappv1.ApplicationSource{RepoURL: "https://github.com/argoproj/argo-cd", Path: "."},
|
|
}, &argoappv1.AppProject{
|
|
Spec: argoappv1.AppProjectSpec{
|
|
SourceRepos: []string{"*"},
|
|
Destinations: []argoappv1.ApplicationDestination{{Server: "*", Namespace: "*"}},
|
|
},
|
|
}, nil)
|
|
require.NoError(t, err)
|
|
assert.ElementsMatch(t, conditions, []argoappv1.ApplicationCondition{{Type: argoappv1.ApplicationConditionInvalidSpecError, Message: "Destination server missing from app spec"}})
|
|
}
|
|
|
|
func TestValidateChartWithoutRevision(t *testing.T) {
|
|
appSpec := &argoappv1.ApplicationSpec{
|
|
Source: &argoappv1.ApplicationSource{RepoURL: "https://charts.helm.sh/incubator/", Chart: "myChart", TargetRevision: ""},
|
|
Destination: argoappv1.ApplicationDestination{
|
|
Server: "https://kubernetes.default.svc", Namespace: "default",
|
|
},
|
|
}
|
|
cluster := &argoappv1.Cluster{Server: "https://kubernetes.default.svc"}
|
|
db := &dbmocks.ArgoDB{}
|
|
ctx := t.Context()
|
|
db.EXPECT().GetCluster(ctx, appSpec.Destination.Server).Return(cluster, nil).Maybe()
|
|
|
|
conditions, err := ValidatePermissions(ctx, appSpec, &argoappv1.AppProject{
|
|
Spec: argoappv1.AppProjectSpec{
|
|
SourceRepos: []string{"*"},
|
|
Destinations: []argoappv1.ApplicationDestination{{Server: "*", Namespace: "*"}},
|
|
},
|
|
}, db)
|
|
require.NoError(t, err)
|
|
assert.Len(t, conditions, 1)
|
|
assert.Equal(t, argoappv1.ApplicationConditionInvalidSpecError, conditions[0].Type)
|
|
assert.Equal(t, "spec.source.targetRevision is required if the manifest source is a helm chart", conditions[0].Message)
|
|
}
|
|
|
|
func TestAPIResourcesToStrings(t *testing.T) {
|
|
resources := []kube.APIResourceInfo{{
|
|
GroupVersionResource: schema.GroupVersionResource{Group: "apps", Version: "v1beta1"},
|
|
GroupKind: schema.GroupKind{Kind: "Deployment"},
|
|
}, {
|
|
GroupVersionResource: schema.GroupVersionResource{Group: "apps", Version: "v1beta2"},
|
|
GroupKind: schema.GroupKind{Kind: "Deployment"},
|
|
}, {
|
|
GroupVersionResource: schema.GroupVersionResource{Group: "extensions", Version: "v1beta1"},
|
|
GroupKind: schema.GroupKind{Kind: "Deployment"},
|
|
}}
|
|
|
|
assert.ElementsMatch(t, []string{"apps/v1beta1", "apps/v1beta2", "extensions/v1beta1"}, APIResourcesToStrings(resources, false))
|
|
assert.ElementsMatch(t, []string{
|
|
"apps/v1beta1", "apps/v1beta1/Deployment", "apps/v1beta2", "apps/v1beta2/Deployment", "extensions/v1beta1", "extensions/v1beta1/Deployment",
|
|
},
|
|
APIResourcesToStrings(resources, true))
|
|
}
|
|
|
|
func TestValidateRepo(t *testing.T) {
|
|
repoPath, err := filepath.Abs("./../..")
|
|
require.NoError(t, err)
|
|
|
|
apiResources := []kube.APIResourceInfo{{
|
|
GroupVersionResource: schema.GroupVersionResource{Group: "apps", Version: "v1beta1"},
|
|
GroupKind: schema.GroupKind{Kind: "Deployment"},
|
|
}, {
|
|
GroupVersionResource: schema.GroupVersionResource{Group: "apps", Version: "v1beta2"},
|
|
GroupKind: schema.GroupKind{Kind: "Deployment"},
|
|
}}
|
|
kubeVersion := "v1.16"
|
|
kustomizeOptions := &argoappv1.KustomizeOptions{BuildOptions: ""}
|
|
repo := &argoappv1.Repository{Repo: "file://" + repoPath}
|
|
cluster := &argoappv1.Cluster{Server: "sample server"}
|
|
app := &argoappv1.Application{
|
|
Spec: argoappv1.ApplicationSpec{
|
|
Source: &argoappv1.ApplicationSource{
|
|
RepoURL: repo.Repo,
|
|
},
|
|
Destination: argoappv1.ApplicationDestination{
|
|
Server: cluster.Server,
|
|
Namespace: "default",
|
|
},
|
|
},
|
|
}
|
|
|
|
proj := &argoappv1.AppProject{
|
|
Spec: argoappv1.AppProjectSpec{
|
|
SourceRepos: []string{"*"},
|
|
},
|
|
}
|
|
|
|
helmRepos := []*argoappv1.Repository{{Repo: "sample helm repo"}}
|
|
|
|
repoClient := &mocks.RepoServerServiceClient{}
|
|
source := app.Spec.GetSource()
|
|
repoClient.EXPECT().GetAppDetails(mock.Anything, &apiclient.RepoServerAppDetailsQuery{
|
|
Repo: repo,
|
|
Source: &source,
|
|
Repos: helmRepos,
|
|
KustomizeOptions: kustomizeOptions,
|
|
HelmOptions: &argoappv1.HelmOptions{ValuesFileSchemes: []string{"https", "http"}},
|
|
NoRevisionCache: true,
|
|
}).Return(&apiclient.RepoAppDetailsResponse{}, nil).Maybe()
|
|
|
|
repo.Type = "git"
|
|
repoClient.EXPECT().TestRepository(mock.Anything, &apiclient.TestRepositoryRequest{
|
|
Repo: repo,
|
|
}).Return(&apiclient.TestRepositoryResponse{
|
|
VerifiedRepository: true,
|
|
}, nil).Maybe()
|
|
|
|
repoClientSet := &mocks.Clientset{RepoServerServiceClient: repoClient}
|
|
|
|
db := &dbmocks.ArgoDB{}
|
|
|
|
db.EXPECT().GetRepository(mock.Anything, app.Spec.Source.RepoURL, "").Return(repo, nil).Maybe()
|
|
db.EXPECT().ListHelmRepositories(mock.Anything).Return(helmRepos, nil).Maybe()
|
|
db.EXPECT().ListOCIRepositories(mock.Anything).Return([]*argoappv1.Repository{}, nil).Maybe()
|
|
db.EXPECT().GetCluster(mock.Anything, app.Spec.Destination.Server).Return(cluster, nil).Maybe()
|
|
db.EXPECT().GetAllHelmRepositoryCredentials(mock.Anything).Return(nil, nil).Maybe()
|
|
db.EXPECT().GetAllOCIRepositoryCredentials(mock.Anything).Return([]*argoappv1.RepoCreds{}, nil).Maybe()
|
|
|
|
var receivedRequest *apiclient.ManifestRequest
|
|
|
|
repoClient.EXPECT().GenerateManifest(mock.Anything, mock.MatchedBy(func(req *apiclient.ManifestRequest) bool {
|
|
receivedRequest = req
|
|
return true
|
|
})).Return(nil, nil).Maybe()
|
|
|
|
cm := corev1.ConfigMap{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "argocd-cm",
|
|
Namespace: test.FakeArgoCDNamespace,
|
|
Labels: map[string]string{
|
|
"app.kubernetes.io/part-of": "argocd",
|
|
},
|
|
},
|
|
Data: map[string]string{
|
|
"globalProjects": `
|
|
- projectName: default-x
|
|
labelSelector:
|
|
matchExpressions:
|
|
- key: is-x
|
|
operator: Exists
|
|
- projectName: default-non-x
|
|
labelSelector:
|
|
matchExpressions:
|
|
- key: is-x
|
|
operator: DoesNotExist
|
|
`,
|
|
},
|
|
}
|
|
|
|
kubeClient := fake.NewClientset(&cm)
|
|
settingsMgr := settings.NewSettingsManager(t.Context(), kubeClient, test.FakeArgoCDNamespace)
|
|
|
|
conditions, err := ValidateRepo(t.Context(), app, repoClientSet, db, &kubetest.MockKubectlCmd{Version: kubeVersion, APIResources: apiResources}, proj, settingsMgr)
|
|
|
|
require.NoError(t, err)
|
|
assert.Empty(t, conditions)
|
|
assert.ElementsMatch(t, []string{"apps/v1beta1", "apps/v1beta1/Deployment", "apps/v1beta2", "apps/v1beta2/Deployment"}, receivedRequest.ApiVersions)
|
|
assert.Equal(t, kubeVersion, receivedRequest.KubeVersion)
|
|
assert.Equal(t, app.Spec.Destination.Namespace, receivedRequest.Namespace)
|
|
assert.Equal(t, &source, receivedRequest.ApplicationSource)
|
|
assert.Equal(t, kustomizeOptions, receivedRequest.KustomizeOptions)
|
|
}
|
|
|
|
func TestValidateRepo_SourceHydrator(t *testing.T) {
|
|
repoPath, err := filepath.Abs("./../..")
|
|
require.NoError(t, err)
|
|
|
|
apiResources := []kube.APIResourceInfo{{
|
|
GroupVersionResource: schema.GroupVersionResource{Group: "apps", Version: "v1beta1"},
|
|
GroupKind: schema.GroupKind{Kind: "Deployment"},
|
|
}, {
|
|
GroupVersionResource: schema.GroupVersionResource{Group: "apps", Version: "v1beta2"},
|
|
GroupKind: schema.GroupKind{Kind: "Deployment"},
|
|
}}
|
|
kubeVersion := "v1.16"
|
|
|
|
repoURL := "file://" + repoPath
|
|
repo := &argoappv1.Repository{Repo: repoURL, Type: "git"}
|
|
cluster := &argoappv1.Cluster{Server: "sample server"}
|
|
|
|
app := &argoappv1.Application{
|
|
Spec: argoappv1.ApplicationSpec{
|
|
Destination: argoappv1.ApplicationDestination{
|
|
Server: cluster.Server,
|
|
Namespace: "default",
|
|
},
|
|
SourceHydrator: &argoappv1.SourceHydrator{
|
|
DrySource: argoappv1.DrySource{
|
|
RepoURL: repoURL,
|
|
TargetRevision: "HEAD",
|
|
Path: "guestbook",
|
|
},
|
|
SyncSource: argoappv1.SyncSource{
|
|
TargetBranch: "env/test",
|
|
Path: "guestbook",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
proj := &argoappv1.AppProject{
|
|
Spec: argoappv1.AppProjectSpec{
|
|
SourceRepos: []string{"*"},
|
|
},
|
|
}
|
|
|
|
repoClient := &mocks.RepoServerServiceClient{}
|
|
|
|
syncSource := app.Spec.GetSource()
|
|
repoClient.EXPECT().TestRepository(mock.Anything, mock.MatchedBy(func(req *apiclient.TestRepositoryRequest) bool {
|
|
return req.Repo.Repo == syncSource.RepoURL
|
|
})).Return(&apiclient.TestRepositoryResponse{VerifiedRepository: true}, nil)
|
|
|
|
var receivedRequest *apiclient.ManifestRequest
|
|
repoClient.EXPECT().GenerateManifest(mock.Anything, mock.MatchedBy(func(req *apiclient.ManifestRequest) bool {
|
|
receivedRequest = req
|
|
return true
|
|
})).Return(nil, nil)
|
|
|
|
repoClientSet := &mocks.Clientset{RepoServerServiceClient: repoClient}
|
|
|
|
db := &dbmocks.ArgoDB{}
|
|
db.EXPECT().GetRepository(mock.Anything, repoURL, "").Return(repo, nil).Maybe()
|
|
db.EXPECT().ListHelmRepositories(mock.Anything).Return(nil, nil)
|
|
db.EXPECT().ListOCIRepositories(mock.Anything).Return(nil, nil)
|
|
db.EXPECT().GetCluster(mock.Anything, cluster.Server).Return(cluster, nil)
|
|
db.EXPECT().GetAllHelmRepositoryCredentials(mock.Anything).Return(nil, nil)
|
|
db.EXPECT().GetAllOCIRepositoryCredentials(mock.Anything).Return(nil, nil)
|
|
|
|
cm := corev1.ConfigMap{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "argocd-cm",
|
|
Namespace: test.FakeArgoCDNamespace,
|
|
Labels: map[string]string{"app.kubernetes.io/part-of": "argocd"},
|
|
},
|
|
}
|
|
kubeClient := fake.NewClientset(&cm)
|
|
settingsMgr := settings.NewSettingsManager(t.Context(), kubeClient, test.FakeArgoCDNamespace)
|
|
|
|
conditions, err := ValidateRepo(t.Context(), app, repoClientSet, db, &kubetest.MockKubectlCmd{Version: kubeVersion, APIResources: apiResources}, proj, settingsMgr)
|
|
|
|
require.NoError(t, err)
|
|
assert.Empty(t, conditions)
|
|
require.NotNil(t, receivedRequest)
|
|
|
|
drySource := app.Spec.SourceHydrator.GetDrySource()
|
|
assert.Equal(t, &drySource, receivedRequest.ApplicationSource)
|
|
assert.Equal(t, "HEAD", receivedRequest.Revision)
|
|
assert.Empty(t, receivedRequest.KubeVersion, "KubeVersion must be empty for dry sources to match hydrator cache keys")
|
|
assert.Nil(t, receivedRequest.ApiVersions, "ApiVersions must be nil for dry sources to match hydrator cache keys")
|
|
}
|
|
|
|
func TestFormatAppConditions(t *testing.T) {
|
|
conditions := []argoappv1.ApplicationCondition{
|
|
{
|
|
Type: EventReasonOperationCompleted,
|
|
Message: "Foo",
|
|
},
|
|
{
|
|
Type: EventReasonResourceCreated,
|
|
Message: "Bar",
|
|
},
|
|
}
|
|
|
|
t.Run("Single Condition", func(t *testing.T) {
|
|
res := FormatAppConditions(conditions[0:1])
|
|
assert.NotEmpty(t, res)
|
|
assert.Equal(t, EventReasonOperationCompleted+": Foo", res)
|
|
})
|
|
|
|
t.Run("Multiple Conditions", func(t *testing.T) {
|
|
res := FormatAppConditions(conditions)
|
|
assert.NotEmpty(t, res)
|
|
assert.Equal(t, fmt.Sprintf("%s: Foo;%s: Bar", EventReasonOperationCompleted, EventReasonResourceCreated), res)
|
|
})
|
|
|
|
t.Run("Empty Conditions", func(t *testing.T) {
|
|
res := FormatAppConditions([]argoappv1.ApplicationCondition{})
|
|
assert.Empty(t, res)
|
|
})
|
|
}
|
|
|
|
func TestFilterByProjects(t *testing.T) {
|
|
apps := []argoappv1.Application{
|
|
{
|
|
Spec: argoappv1.ApplicationSpec{
|
|
Project: "fooproj",
|
|
},
|
|
},
|
|
{
|
|
Spec: argoappv1.ApplicationSpec{
|
|
Project: "barproj",
|
|
},
|
|
},
|
|
}
|
|
|
|
t.Run("No apps in single project", func(t *testing.T) {
|
|
res := FilterByProjects(apps, []string{"foobarproj"})
|
|
assert.Empty(t, res)
|
|
})
|
|
|
|
t.Run("Single app in single project", func(t *testing.T) {
|
|
res := FilterByProjects(apps, []string{"fooproj"})
|
|
assert.Len(t, res, 1)
|
|
})
|
|
|
|
t.Run("Single app in multiple project", func(t *testing.T) {
|
|
res := FilterByProjects(apps, []string{"fooproj", "foobarproj"})
|
|
assert.Len(t, res, 1)
|
|
})
|
|
|
|
t.Run("Multiple apps in multiple project", func(t *testing.T) {
|
|
res := FilterByProjects(apps, []string{"fooproj", "barproj"})
|
|
assert.Len(t, res, 2)
|
|
})
|
|
}
|
|
|
|
func TestFilterByProjectsP(t *testing.T) {
|
|
apps := []*argoappv1.Application{
|
|
{
|
|
Spec: argoappv1.ApplicationSpec{
|
|
Project: "fooproj",
|
|
},
|
|
},
|
|
{
|
|
Spec: argoappv1.ApplicationSpec{
|
|
Project: "barproj",
|
|
},
|
|
},
|
|
}
|
|
|
|
t.Run("No apps in single project", func(t *testing.T) {
|
|
res := FilterByProjectsP(apps, []string{"foobarproj"})
|
|
assert.Empty(t, res)
|
|
})
|
|
|
|
t.Run("Single app in single project", func(t *testing.T) {
|
|
res := FilterByProjectsP(apps, []string{"fooproj"})
|
|
assert.Len(t, res, 1)
|
|
})
|
|
|
|
t.Run("Single app in multiple project", func(t *testing.T) {
|
|
res := FilterByProjectsP(apps, []string{"fooproj", "foobarproj"})
|
|
assert.Len(t, res, 1)
|
|
})
|
|
|
|
t.Run("Multiple apps in multiple project", func(t *testing.T) {
|
|
res := FilterByProjectsP(apps, []string{"fooproj", "barproj"})
|
|
assert.Len(t, res, 2)
|
|
})
|
|
}
|
|
|
|
func TestFilterAppSetsByProjects(t *testing.T) {
|
|
appsets := []argoappv1.ApplicationSet{
|
|
{
|
|
Spec: argoappv1.ApplicationSetSpec{
|
|
Template: argoappv1.ApplicationSetTemplate{
|
|
Spec: argoappv1.ApplicationSpec{
|
|
Project: "fooproj",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Spec: argoappv1.ApplicationSetSpec{
|
|
Template: argoappv1.ApplicationSetTemplate{
|
|
Spec: argoappv1.ApplicationSpec{
|
|
Project: "barproj",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
t.Run("No apps in single project", func(t *testing.T) {
|
|
res := FilterAppSetsByProjects(appsets, []string{"foobarproj"})
|
|
assert.Empty(t, res)
|
|
})
|
|
|
|
t.Run("Single app in single project", func(t *testing.T) {
|
|
res := FilterAppSetsByProjects(appsets, []string{"fooproj"})
|
|
assert.Len(t, res, 1)
|
|
})
|
|
|
|
t.Run("Single app in multiple project", func(t *testing.T) {
|
|
res := FilterAppSetsByProjects(appsets, []string{"fooproj", "foobarproj"})
|
|
assert.Len(t, res, 1)
|
|
})
|
|
|
|
t.Run("Multiple apps in multiple project", func(t *testing.T) {
|
|
res := FilterAppSetsByProjects(appsets, []string{"fooproj", "barproj"})
|
|
assert.Len(t, res, 2)
|
|
})
|
|
}
|
|
|
|
func TestFilterByRepo(t *testing.T) {
|
|
apps := []argoappv1.Application{
|
|
{
|
|
Spec: argoappv1.ApplicationSpec{
|
|
Source: &argoappv1.ApplicationSource{
|
|
RepoURL: "git@github.com:owner/repo.git",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Spec: argoappv1.ApplicationSpec{
|
|
Source: &argoappv1.ApplicationSource{
|
|
RepoURL: "git@github.com:owner/otherrepo.git",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
t.Run("Empty filter", func(t *testing.T) {
|
|
res := FilterByRepo(apps, "")
|
|
assert.Len(t, res, 2)
|
|
})
|
|
|
|
t.Run("Match", func(t *testing.T) {
|
|
res := FilterByRepo(apps, "git@github.com:owner/repo.git")
|
|
assert.Len(t, res, 1)
|
|
})
|
|
|
|
t.Run("No match", func(t *testing.T) {
|
|
res := FilterByRepo(apps, "git@github.com:owner/willnotmatch.git")
|
|
assert.Empty(t, res)
|
|
})
|
|
}
|
|
|
|
func TestFilterByRepoP(t *testing.T) {
|
|
apps := []*argoappv1.Application{
|
|
{
|
|
Spec: argoappv1.ApplicationSpec{
|
|
Source: &argoappv1.ApplicationSource{
|
|
RepoURL: "git@github.com:owner/repo.git",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Spec: argoappv1.ApplicationSpec{
|
|
Source: &argoappv1.ApplicationSource{
|
|
RepoURL: "git@github.com:owner/otherrepo.git",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
t.Run("Empty filter", func(t *testing.T) {
|
|
res := FilterByRepoP(apps, "")
|
|
assert.Len(t, res, 2)
|
|
})
|
|
|
|
t.Run("Match", func(t *testing.T) {
|
|
res := FilterByRepoP(apps, "git@github.com:owner/repo.git")
|
|
assert.Len(t, res, 1)
|
|
})
|
|
|
|
t.Run("No match", func(t *testing.T) {
|
|
res := FilterByRepoP(apps, "git@github.com:owner/willnotmatch.git")
|
|
assert.Empty(t, res)
|
|
})
|
|
}
|
|
|
|
func TestFilterByPath(t *testing.T) {
|
|
apps := []argoappv1.Application{
|
|
{
|
|
Spec: argoappv1.ApplicationSpec{
|
|
Source: &argoappv1.ApplicationSource{
|
|
Path: "example/app/foo",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Spec: argoappv1.ApplicationSpec{
|
|
Source: &argoappv1.ApplicationSource{
|
|
Path: "example/app/existent",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
t.Run("Empty filter", func(t *testing.T) {
|
|
res := FilterByPath(apps, "")
|
|
assert.Len(t, res, 2)
|
|
})
|
|
|
|
t.Run("Found one match", func(t *testing.T) {
|
|
res := FilterByPath(apps, "example/app/foo")
|
|
assert.Len(t, res, 1)
|
|
})
|
|
|
|
t.Run("No match found", func(t *testing.T) {
|
|
res := FilterByPath(apps, "example/app/non-existent")
|
|
assert.Empty(t, res)
|
|
})
|
|
}
|
|
|
|
func TestValidatePermissions(t *testing.T) {
|
|
t.Run("Empty Repo URL result in condition", func(t *testing.T) {
|
|
spec := argoappv1.ApplicationSpec{
|
|
Source: &argoappv1.ApplicationSource{
|
|
RepoURL: "",
|
|
},
|
|
}
|
|
proj := argoappv1.AppProject{}
|
|
db := &dbmocks.ArgoDB{}
|
|
conditions, err := ValidatePermissions(t.Context(), &spec, &proj, db)
|
|
require.NoError(t, err)
|
|
assert.Len(t, conditions, 1)
|
|
assert.Equal(t, argoappv1.ApplicationConditionInvalidSpecError, conditions[0].Type)
|
|
assert.Contains(t, conditions[0].Message, "are required")
|
|
})
|
|
|
|
t.Run("Incomplete Path/Chart combo result in condition", func(t *testing.T) {
|
|
spec := argoappv1.ApplicationSpec{
|
|
Source: &argoappv1.ApplicationSource{
|
|
RepoURL: "http://some/where",
|
|
Path: "",
|
|
Chart: "",
|
|
},
|
|
}
|
|
proj := argoappv1.AppProject{}
|
|
db := &dbmocks.ArgoDB{}
|
|
conditions, err := ValidatePermissions(t.Context(), &spec, &proj, db)
|
|
require.NoError(t, err)
|
|
assert.Len(t, conditions, 1)
|
|
assert.Equal(t, argoappv1.ApplicationConditionInvalidSpecError, conditions[0].Type)
|
|
assert.Contains(t, conditions[0].Message, "are required")
|
|
})
|
|
|
|
t.Run("Helm chart requires targetRevision", func(t *testing.T) {
|
|
spec := argoappv1.ApplicationSpec{
|
|
Source: &argoappv1.ApplicationSource{
|
|
RepoURL: "http://some/where",
|
|
Path: "",
|
|
Chart: "somechart",
|
|
},
|
|
}
|
|
proj := argoappv1.AppProject{}
|
|
db := &dbmocks.ArgoDB{}
|
|
conditions, err := ValidatePermissions(t.Context(), &spec, &proj, db)
|
|
require.NoError(t, err)
|
|
assert.Len(t, conditions, 1)
|
|
assert.Equal(t, argoappv1.ApplicationConditionInvalidSpecError, conditions[0].Type)
|
|
assert.Contains(t, conditions[0].Message, "is required if the manifest source is a helm chart")
|
|
})
|
|
|
|
t.Run("Application source is not permitted in project", func(t *testing.T) {
|
|
spec := argoappv1.ApplicationSpec{
|
|
Source: &argoappv1.ApplicationSource{
|
|
RepoURL: "http://some/where",
|
|
Path: "",
|
|
Chart: "somechart",
|
|
TargetRevision: "1.4.1",
|
|
},
|
|
Destination: argoappv1.ApplicationDestination{
|
|
Server: "https://127.0.0.1:6443",
|
|
Namespace: "testns",
|
|
},
|
|
}
|
|
proj := argoappv1.AppProject{
|
|
Spec: argoappv1.AppProjectSpec{
|
|
Destinations: []argoappv1.ApplicationDestination{
|
|
{
|
|
Server: "*",
|
|
Namespace: "*",
|
|
},
|
|
},
|
|
SourceRepos: []string{"http://some/where/else"},
|
|
},
|
|
}
|
|
cluster := &argoappv1.Cluster{Server: "https://127.0.0.1:6443", Name: "test"}
|
|
db := &dbmocks.ArgoDB{}
|
|
db.EXPECT().GetCluster(mock.Anything, spec.Destination.Server).Return(cluster, nil).Maybe()
|
|
conditions, err := ValidatePermissions(t.Context(), &spec, &proj, db)
|
|
require.NoError(t, err)
|
|
assert.Len(t, conditions, 1)
|
|
assert.Contains(t, conditions[0].Message, "application repo http://some/where is not permitted")
|
|
})
|
|
|
|
t.Run("Application destination is not permitted in project", func(t *testing.T) {
|
|
spec := argoappv1.ApplicationSpec{
|
|
Source: &argoappv1.ApplicationSource{
|
|
RepoURL: "http://some/where",
|
|
Path: "",
|
|
Chart: "somechart",
|
|
TargetRevision: "1.4.1",
|
|
},
|
|
Destination: argoappv1.ApplicationDestination{
|
|
Server: "https://127.0.0.1:6443",
|
|
Namespace: "testns",
|
|
},
|
|
}
|
|
proj := argoappv1.AppProject{
|
|
Spec: argoappv1.AppProjectSpec{
|
|
Destinations: []argoappv1.ApplicationDestination{
|
|
{
|
|
Server: "*",
|
|
Namespace: "default",
|
|
},
|
|
},
|
|
SourceRepos: []string{"http://some/where"},
|
|
},
|
|
}
|
|
cluster := &argoappv1.Cluster{Server: "https://127.0.0.1:6443", Name: "test"}
|
|
db := &dbmocks.ArgoDB{}
|
|
db.EXPECT().GetCluster(mock.Anything, spec.Destination.Server).Return(cluster, nil).Maybe()
|
|
conditions, err := ValidatePermissions(t.Context(), &spec, &proj, db)
|
|
require.NoError(t, err)
|
|
assert.Len(t, conditions, 1)
|
|
assert.Contains(t, conditions[0].Message, "application destination")
|
|
})
|
|
|
|
t.Run("Destination cluster does not exist", func(t *testing.T) {
|
|
spec := argoappv1.ApplicationSpec{
|
|
Source: &argoappv1.ApplicationSource{
|
|
RepoURL: "http://some/where",
|
|
Path: "",
|
|
Chart: "somechart",
|
|
TargetRevision: "1.4.1",
|
|
},
|
|
Destination: argoappv1.ApplicationDestination{
|
|
Server: "https://127.0.0.1:6443",
|
|
Namespace: "default",
|
|
},
|
|
}
|
|
proj := argoappv1.AppProject{
|
|
Spec: argoappv1.AppProjectSpec{
|
|
Destinations: []argoappv1.ApplicationDestination{
|
|
{
|
|
Server: "*",
|
|
Namespace: "default",
|
|
},
|
|
},
|
|
SourceRepos: []string{"http://some/where"},
|
|
},
|
|
}
|
|
db := &dbmocks.ArgoDB{}
|
|
db.EXPECT().GetCluster(mock.Anything, spec.Destination.Server).Return(nil, status.Errorf(codes.NotFound, "Cluster does not exist")).Maybe()
|
|
conditions, err := ValidatePermissions(t.Context(), &spec, &proj, db)
|
|
require.NoError(t, err)
|
|
assert.Len(t, conditions, 1)
|
|
assert.Contains(t, conditions[0].Message, "Cluster does not exist")
|
|
})
|
|
|
|
t.Run("Destination cluster name does not exist", func(t *testing.T) {
|
|
spec := argoappv1.ApplicationSpec{
|
|
Source: &argoappv1.ApplicationSource{
|
|
RepoURL: "http://some/where",
|
|
Path: "",
|
|
Chart: "somechart",
|
|
TargetRevision: "1.4.1",
|
|
},
|
|
Destination: argoappv1.ApplicationDestination{
|
|
Name: "does-not-exist",
|
|
Namespace: "default",
|
|
},
|
|
}
|
|
proj := argoappv1.AppProject{
|
|
Spec: argoappv1.AppProjectSpec{
|
|
Destinations: []argoappv1.ApplicationDestination{
|
|
{
|
|
Server: "*",
|
|
Namespace: "default",
|
|
},
|
|
},
|
|
SourceRepos: []string{"http://some/where"},
|
|
},
|
|
}
|
|
db := &dbmocks.ArgoDB{}
|
|
db.EXPECT().GetClusterServersByName(mock.Anything, "does-not-exist").Return(nil, nil).Maybe()
|
|
conditions, err := ValidatePermissions(t.Context(), &spec, &proj, db)
|
|
require.NoError(t, err)
|
|
assert.Len(t, conditions, 1)
|
|
assert.Contains(t, conditions[0].Message, "there are no clusters with this name: does-not-exist")
|
|
})
|
|
|
|
t.Run("Cannot get cluster info from DB", func(t *testing.T) {
|
|
spec := argoappv1.ApplicationSpec{
|
|
Source: &argoappv1.ApplicationSource{
|
|
RepoURL: "http://some/where",
|
|
Path: "",
|
|
Chart: "somechart",
|
|
TargetRevision: "1.4.1",
|
|
},
|
|
Destination: argoappv1.ApplicationDestination{
|
|
Server: "https://127.0.0.1:6443",
|
|
Namespace: "default",
|
|
},
|
|
}
|
|
proj := argoappv1.AppProject{
|
|
Spec: argoappv1.AppProjectSpec{
|
|
Destinations: []argoappv1.ApplicationDestination{
|
|
{
|
|
Server: "*",
|
|
Namespace: "default",
|
|
},
|
|
},
|
|
SourceRepos: []string{"http://some/where"},
|
|
},
|
|
}
|
|
db := &dbmocks.ArgoDB{}
|
|
db.EXPECT().GetCluster(mock.Anything, spec.Destination.Server).Return(nil, errors.New("Unknown error occurred")).Maybe()
|
|
conditions, err := ValidatePermissions(t.Context(), &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) {
|
|
spec := argoappv1.ApplicationSpec{
|
|
Source: &argoappv1.ApplicationSource{
|
|
RepoURL: "http://some/where",
|
|
Path: "",
|
|
Chart: "somechart",
|
|
TargetRevision: "1.4.1",
|
|
},
|
|
Destination: argoappv1.ApplicationDestination{
|
|
Name: "does-exist",
|
|
Namespace: "default",
|
|
},
|
|
}
|
|
proj := argoappv1.AppProject{
|
|
Spec: argoappv1.AppProjectSpec{
|
|
Destinations: []argoappv1.ApplicationDestination{
|
|
{
|
|
Server: "*",
|
|
Namespace: "default",
|
|
},
|
|
},
|
|
SourceRepos: []string{"http://some/where"},
|
|
},
|
|
}
|
|
db := &dbmocks.ArgoDB{}
|
|
cluster := argoappv1.Cluster{
|
|
Name: "does-exist",
|
|
Server: "https://127.0.0.1:6443",
|
|
}
|
|
db.EXPECT().GetClusterServersByName(mock.Anything, "does-exist").Return([]string{"https://127.0.0.1:6443"}, nil).Maybe()
|
|
db.EXPECT().GetCluster(mock.Anything, "https://127.0.0.1:6443").Return(&cluster, nil).Maybe()
|
|
conditions, err := ValidatePermissions(t.Context(), &spec, &proj, db)
|
|
require.NoError(t, err)
|
|
assert.Empty(t, conditions)
|
|
})
|
|
}
|
|
|
|
func TestSetAppOperations(t *testing.T) {
|
|
t.Run("Application not existing", func(t *testing.T) {
|
|
appIf := appclientset.NewSimpleClientset().ArgoprojV1alpha1().Applications("default")
|
|
app, err := SetAppOperation(appIf, "someapp", &argoappv1.Operation{Sync: &argoappv1.SyncOperation{Revision: "aaa"}})
|
|
require.Error(t, err)
|
|
assert.Nil(t, app)
|
|
})
|
|
|
|
t.Run("Operation already in progress", func(t *testing.T) {
|
|
a := argoappv1.Application{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "someapp",
|
|
Namespace: "default",
|
|
},
|
|
Operation: &argoappv1.Operation{Sync: &argoappv1.SyncOperation{Revision: "aaa"}},
|
|
}
|
|
appIf := appclientset.NewSimpleClientset(&a).ArgoprojV1alpha1().Applications("default")
|
|
app, err := SetAppOperation(appIf, "someapp", &argoappv1.Operation{Sync: &argoappv1.SyncOperation{Revision: "aaa"}})
|
|
require.ErrorContains(t, err, "operation is already in progress")
|
|
assert.Nil(t, app)
|
|
})
|
|
|
|
t.Run("Operation unspecified", func(t *testing.T) {
|
|
a := argoappv1.Application{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "someapp",
|
|
Namespace: "default",
|
|
},
|
|
}
|
|
appIf := appclientset.NewSimpleClientset(&a).ArgoprojV1alpha1().Applications("default")
|
|
app, err := SetAppOperation(appIf, "someapp", &argoappv1.Operation{Sync: nil})
|
|
require.ErrorContains(t, err, "Operation unspecified")
|
|
assert.Nil(t, app)
|
|
})
|
|
|
|
t.Run("Success", func(t *testing.T) {
|
|
a := argoappv1.Application{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "someapp",
|
|
Namespace: "default",
|
|
},
|
|
}
|
|
appIf := appclientset.NewSimpleClientset(&a).ArgoprojV1alpha1().Applications("default")
|
|
app, err := SetAppOperation(appIf, "someapp", &argoappv1.Operation{Sync: &argoappv1.SyncOperation{Revision: "aaa"}})
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, app)
|
|
})
|
|
}
|
|
|
|
func TestGetDestinationCluster(t *testing.T) {
|
|
t.Run("Validate destination with server url", func(t *testing.T) {
|
|
dest := argoappv1.ApplicationDestination{
|
|
Server: "https://127.0.0.1:6443",
|
|
Namespace: "default",
|
|
}
|
|
|
|
expectedCluster := &argoappv1.Cluster{Server: "https://127.0.0.1:6443"}
|
|
db := &dbmocks.ArgoDB{}
|
|
db.EXPECT().GetCluster(mock.Anything, "https://127.0.0.1:6443").Return(expectedCluster, nil).Maybe()
|
|
|
|
destCluster, err := GetDestinationCluster(t.Context(), dest, db)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, expectedCluster)
|
|
assert.Equal(t, expectedCluster, destCluster)
|
|
})
|
|
|
|
t.Run("Validate destination with server name", func(t *testing.T) {
|
|
dest := argoappv1.ApplicationDestination{
|
|
Name: "minikube",
|
|
}
|
|
|
|
db := &dbmocks.ArgoDB{}
|
|
db.EXPECT().GetClusterServersByName(mock.Anything, "minikube").Return([]string{"https://127.0.0.1:6443"}, nil).Maybe()
|
|
db.EXPECT().GetCluster(mock.Anything, "https://127.0.0.1:6443").Return(&argoappv1.Cluster{Server: "https://127.0.0.1:6443", Name: "minikube"}, nil).Maybe()
|
|
|
|
destCluster, err := GetDestinationCluster(t.Context(), dest, db)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "https://127.0.0.1:6443", destCluster.Server)
|
|
})
|
|
|
|
t.Run("Error when having both server url and name", func(t *testing.T) {
|
|
dest := argoappv1.ApplicationDestination{
|
|
Server: "https://127.0.0.1:6443",
|
|
Name: "minikube",
|
|
Namespace: "default",
|
|
}
|
|
|
|
_, err := GetDestinationCluster(t.Context(), dest, nil)
|
|
assert.EqualError(t, err, "application destination can't have both name and server defined: minikube https://127.0.0.1:6443")
|
|
})
|
|
|
|
t.Run("GetClusterServersByName fails", func(t *testing.T) {
|
|
dest := argoappv1.ApplicationDestination{
|
|
Name: "minikube",
|
|
}
|
|
|
|
db := &dbmocks.ArgoDB{}
|
|
db.EXPECT().GetClusterServersByName(mock.Anything, "minikube").Return(nil, errors.New("an error occurred"))
|
|
|
|
_, err := GetDestinationCluster(t.Context(), dest, db)
|
|
require.ErrorContains(t, err, "an error occurred")
|
|
})
|
|
|
|
t.Run("Destination cluster does not exist", func(t *testing.T) {
|
|
dest := argoappv1.ApplicationDestination{
|
|
Name: "minikube",
|
|
}
|
|
|
|
db := &dbmocks.ArgoDB{}
|
|
db.EXPECT().GetClusterServersByName(mock.Anything, "minikube").Return(nil, nil)
|
|
|
|
_, err := GetDestinationCluster(t.Context(), dest, db)
|
|
assert.EqualError(t, err, "there are no clusters with this name: minikube")
|
|
})
|
|
|
|
t.Run("Validate too many clusters with the same name", func(t *testing.T) {
|
|
dest := argoappv1.ApplicationDestination{
|
|
Name: "dind",
|
|
}
|
|
|
|
db := &dbmocks.ArgoDB{}
|
|
db.EXPECT().GetClusterServersByName(mock.Anything, "dind").Return([]string{"https://127.0.0.1:2443", "https://127.0.0.1:8443"}, nil)
|
|
|
|
_, err := GetDestinationCluster(t.Context(), dest, db)
|
|
assert.EqualError(t, err, "there are 2 clusters with the same name: [https://127.0.0.1:2443 https://127.0.0.1:8443]")
|
|
})
|
|
}
|
|
|
|
func TestFilterByName(t *testing.T) {
|
|
apps := []argoappv1.Application{
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "foo",
|
|
},
|
|
Spec: argoappv1.ApplicationSpec{
|
|
Project: "fooproj",
|
|
},
|
|
},
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "bar",
|
|
},
|
|
Spec: argoappv1.ApplicationSpec{
|
|
Project: "barproj",
|
|
},
|
|
},
|
|
}
|
|
|
|
t.Run("Name is empty string", func(t *testing.T) {
|
|
res, err := FilterByName(apps, "")
|
|
require.NoError(t, err)
|
|
assert.Len(t, res, 2)
|
|
})
|
|
|
|
t.Run("Single app by name", func(t *testing.T) {
|
|
res, err := FilterByName(apps, "foo")
|
|
require.NoError(t, err)
|
|
assert.Len(t, res, 1)
|
|
})
|
|
|
|
t.Run("No such app", func(t *testing.T) {
|
|
res, err := FilterByName(apps, "foobar")
|
|
require.Error(t, err)
|
|
assert.Empty(t, res)
|
|
})
|
|
}
|
|
|
|
func TestFilterByNameP(t *testing.T) {
|
|
apps := []*argoappv1.Application{
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "foo",
|
|
},
|
|
Spec: argoappv1.ApplicationSpec{
|
|
Project: "fooproj",
|
|
},
|
|
},
|
|
{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "bar",
|
|
},
|
|
Spec: argoappv1.ApplicationSpec{
|
|
Project: "barproj",
|
|
},
|
|
},
|
|
}
|
|
|
|
t.Run("Name is empty string", func(t *testing.T) {
|
|
res := FilterByNameP(apps, "")
|
|
assert.Len(t, res, 2)
|
|
})
|
|
|
|
t.Run("Single app by name", func(t *testing.T) {
|
|
res := FilterByNameP(apps, "foo")
|
|
assert.Len(t, res, 1)
|
|
})
|
|
|
|
t.Run("No such app", func(t *testing.T) {
|
|
res := FilterByNameP(apps, "foobar")
|
|
assert.Empty(t, res)
|
|
})
|
|
}
|
|
|
|
func TestFilterByCluster(t *testing.T) {
|
|
apps := []argoappv1.Application{
|
|
{
|
|
Spec: argoappv1.ApplicationSpec{
|
|
Destination: argoappv1.ApplicationDestination{
|
|
Server: "https://cluster-1.example.com",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Spec: argoappv1.ApplicationSpec{
|
|
Destination: argoappv1.ApplicationDestination{
|
|
Name: "in-cluster",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
cluster string
|
|
expected int
|
|
}{
|
|
{
|
|
name: "Empty filter returns all",
|
|
cluster: "",
|
|
expected: 2,
|
|
},
|
|
{
|
|
name: "Match by Server",
|
|
cluster: "https://cluster-1.example.com",
|
|
expected: 1,
|
|
},
|
|
{
|
|
name: "Match by Name",
|
|
cluster: "in-cluster",
|
|
expected: 1,
|
|
},
|
|
{
|
|
name: "No match found",
|
|
cluster: "https://cluster-2.example.com",
|
|
expected: 0,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
res := FilterByCluster(apps, tt.cluster)
|
|
assert.Len(t, res, tt.expected)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetGlobalProjects(t *testing.T) {
|
|
t.Run("Multiple global projects", func(t *testing.T) {
|
|
namespace := "default"
|
|
|
|
cm := corev1.ConfigMap{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "argocd-cm",
|
|
Namespace: test.FakeArgoCDNamespace,
|
|
Labels: map[string]string{
|
|
"app.kubernetes.io/part-of": "argocd",
|
|
},
|
|
},
|
|
Data: map[string]string{
|
|
"globalProjects": `
|
|
- projectName: default-x
|
|
labelSelector:
|
|
matchExpressions:
|
|
- key: is-x
|
|
operator: Exists
|
|
- projectName: default-non-x
|
|
labelSelector:
|
|
matchExpressions:
|
|
- key: is-x
|
|
operator: DoesNotExist
|
|
`,
|
|
},
|
|
}
|
|
|
|
defaultX := &argoappv1.AppProject{
|
|
ObjectMeta: metav1.ObjectMeta{Name: "default-x", Namespace: namespace},
|
|
Spec: argoappv1.AppProjectSpec{
|
|
ClusterResourceWhitelist: []argoappv1.ClusterResourceRestrictionItem{
|
|
{Group: "*", Kind: "*"},
|
|
},
|
|
ClusterResourceBlacklist: []argoappv1.ClusterResourceRestrictionItem{
|
|
{Kind: "Volume"},
|
|
},
|
|
},
|
|
}
|
|
|
|
defaultNonX := &argoappv1.AppProject{
|
|
ObjectMeta: metav1.ObjectMeta{Name: "default-non-x", Namespace: namespace},
|
|
Spec: argoappv1.AppProjectSpec{
|
|
ClusterResourceBlacklist: []argoappv1.ClusterResourceRestrictionItem{
|
|
{Group: "*", Kind: "*"},
|
|
},
|
|
},
|
|
}
|
|
|
|
isX := &argoappv1.AppProject{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "is-x",
|
|
Namespace: namespace,
|
|
Labels: map[string]string{
|
|
"is-x": "yep",
|
|
},
|
|
},
|
|
}
|
|
|
|
isNoX := &argoappv1.AppProject{
|
|
ObjectMeta: metav1.ObjectMeta{Name: "is-no-x", Namespace: namespace},
|
|
}
|
|
|
|
projClientset := appclientset.NewSimpleClientset(defaultX, defaultNonX, isX, isNoX)
|
|
ctx, cancel := context.WithCancel(t.Context())
|
|
defer cancel()
|
|
indexers := cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}
|
|
informer := v1alpha1.NewAppProjectInformer(projClientset, namespace, 0, indexers)
|
|
go informer.Run(ctx.Done())
|
|
cache.WaitForCacheSync(ctx.Done(), informer.HasSynced)
|
|
|
|
kubeClient := fake.NewSimpleClientset(&cm)
|
|
settingsMgr := settings.NewSettingsManager(t.Context(), kubeClient, test.FakeArgoCDNamespace)
|
|
|
|
projLister := applisters.NewAppProjectLister(informer.GetIndexer())
|
|
|
|
xGlobalProjects := GetGlobalProjects(isX, projLister, settingsMgr)
|
|
assert.Len(t, xGlobalProjects, 1)
|
|
assert.Equal(t, "default-x", xGlobalProjects[0].Name)
|
|
|
|
nonXGlobalProjects := GetGlobalProjects(isNoX, projLister, settingsMgr)
|
|
assert.Len(t, nonXGlobalProjects, 1)
|
|
assert.Equal(t, "default-non-x", nonXGlobalProjects[0].Name)
|
|
})
|
|
}
|
|
|
|
func Test_GetDifferentPathsBetweenStructs(t *testing.T) {
|
|
r1 := argoappv1.Repository{}
|
|
r2 := argoappv1.Repository{
|
|
Name: "SomeName",
|
|
}
|
|
|
|
difference, _ := GetDifferentPathsBetweenStructs(r1, r2)
|
|
assert.Equal(t, []string{"Name"}, difference)
|
|
}
|
|
|
|
func Test_GenerateSpecIsDifferentErrorMessageWithNoDiff(t *testing.T) {
|
|
r1 := argoappv1.Repository{}
|
|
r2 := argoappv1.Repository{}
|
|
|
|
msg := GenerateSpecIsDifferentErrorMessage("application", r1, r2)
|
|
assert.Equal(t, "existing application spec is different; use upsert flag to force update", msg)
|
|
}
|
|
|
|
func Test_GenerateSpecIsDifferentErrorMessageWithDiff(t *testing.T) {
|
|
r1 := argoappv1.Repository{}
|
|
r2 := argoappv1.Repository{
|
|
Name: "test",
|
|
}
|
|
|
|
msg := GenerateSpecIsDifferentErrorMessage("repo", r1, r2)
|
|
assert.Equal(t, "existing repo spec is different; use upsert flag to force update; difference in keys \"Name\"", msg)
|
|
}
|
|
|
|
func Test_ParseAppQualifiedName(t *testing.T) {
|
|
testcases := []struct {
|
|
name string
|
|
input string
|
|
implicitNs string
|
|
appName string
|
|
appNs string
|
|
}{
|
|
{"Full qualified without implicit NS", "namespace/name", "", "name", "namespace"},
|
|
{"Non qualified without implicit NS", "name", "", "name", ""},
|
|
{"Full qualified with implicit NS", "namespace/name", "namespace2", "name", "namespace"},
|
|
{"Non qualified with implicit NS", "name", "namespace2", "name", "namespace2"},
|
|
{"Invalid without implicit NS", "namespace_name", "", "namespace_name", ""},
|
|
}
|
|
|
|
for _, tt := range testcases {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
appName, appNs := ParseFromQualifiedName(tt.input, tt.implicitNs)
|
|
assert.Equal(t, tt.appName, appName)
|
|
assert.Equal(t, tt.appNs, appNs)
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_ParseAppInstanceName(t *testing.T) {
|
|
testcases := []struct {
|
|
name string
|
|
input string
|
|
implicitNs string
|
|
appName string
|
|
appNs string
|
|
}{
|
|
{"Full qualified without implicit NS", "namespace_name", "", "name", "namespace"},
|
|
{"Non qualified without implicit NS", "name", "", "name", ""},
|
|
{"Full qualified with implicit NS", "namespace_name", "namespace2", "name", "namespace"},
|
|
{"Non qualified with implicit NS", "name", "namespace2", "name", "namespace2"},
|
|
{"Invalid without implicit NS", "namespace/name", "", "namespace/name", ""},
|
|
}
|
|
|
|
for _, tt := range testcases {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
appName, appNs := ParseInstanceName(tt.input, tt.implicitNs)
|
|
assert.Equal(t, tt.appName, appName)
|
|
assert.Equal(t, tt.appNs, appNs)
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_AppInstanceName(t *testing.T) {
|
|
testcases := []struct {
|
|
name string
|
|
appName string
|
|
appNamespace string
|
|
defaultNs string
|
|
result string
|
|
}{
|
|
{"defaultns different as appns", "appname", "appns", "defaultns", "appns_appname"},
|
|
{"defaultns same as appns", "appname", "appns", "appns", "appname"},
|
|
{"defaultns set and appns not given", "appname", "", "appns", "appname"},
|
|
{"neither defaultns nor appns set", "appname", "", "appns", "appname"},
|
|
}
|
|
|
|
for _, tt := range testcases {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := AppInstanceName(tt.appName, tt.appNamespace, tt.defaultNs)
|
|
assert.Equal(t, tt.result, result)
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_AppInstanceNameFromQualified(t *testing.T) {
|
|
testcases := []struct {
|
|
name string
|
|
appName string
|
|
defaultNs string
|
|
result string
|
|
}{
|
|
{"Qualified name with namespace not being defaultns", "appns/appname", "defaultns", "appns_appname"},
|
|
{"Qualified name with namespace being defaultns", "defaultns/appname", "defaultns", "appname"},
|
|
{"Qualified name without namespace", "appname", "defaultns", "appname"},
|
|
{"Qualified name without namespace and defaultns", "appname", "", "appname"},
|
|
}
|
|
|
|
for _, tt := range testcases {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result := InstanceNameFromQualified(tt.appName, tt.defaultNs)
|
|
assert.Equal(t, tt.result, result)
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_GetRefSources(t *testing.T) {
|
|
repoPath, err := filepath.Abs("./../..")
|
|
require.NoError(t, err)
|
|
|
|
getMultiSourceAppSpec := func(sources argoappv1.ApplicationSources) *argoappv1.ApplicationSpec {
|
|
return &argoappv1.ApplicationSpec{
|
|
Sources: sources,
|
|
}
|
|
}
|
|
|
|
repo := &argoappv1.Repository{Repo: "file://" + repoPath}
|
|
|
|
t.Run("target ref exists", func(t *testing.T) {
|
|
argoSpec := getMultiSourceAppSpec(argoappv1.ApplicationSources{
|
|
{RepoURL: "file://" + repoPath, Ref: "source-1_2"},
|
|
{RepoURL: "file://" + repoPath},
|
|
})
|
|
|
|
refSources, err := GetRefSources(t.Context(), argoSpec.Sources, argoSpec.Project, func(_ context.Context, _ string, _ string) (*argoappv1.Repository, error) {
|
|
return repo, nil
|
|
}, []string{})
|
|
|
|
expectedRefSource := argoappv1.RefTargetRevisionMapping{
|
|
"$source-1_2": &argoappv1.RefTarget{
|
|
Repo: *repo,
|
|
},
|
|
}
|
|
require.NoError(t, err)
|
|
assert.Len(t, refSources, 1)
|
|
assert.Equal(t, expectedRefSource, refSources)
|
|
})
|
|
|
|
t.Run("target ref does not exist", func(t *testing.T) {
|
|
argoSpec := getMultiSourceAppSpec(argoappv1.ApplicationSources{
|
|
{RepoURL: "file://does-not-exist", Ref: "source1"},
|
|
{RepoURL: "file://" + repoPath},
|
|
})
|
|
|
|
refSources, err := GetRefSources(t.Context(), argoSpec.Sources, argoSpec.Project, func(_ context.Context, _ string, _ string) (*argoappv1.Repository, error) {
|
|
return nil, errors.New("repo does not exist")
|
|
}, []string{})
|
|
|
|
require.Error(t, err)
|
|
assert.Empty(t, refSources)
|
|
})
|
|
|
|
t.Run("invalid ref", func(t *testing.T) {
|
|
argoSpec := getMultiSourceAppSpec(argoappv1.ApplicationSources{
|
|
{RepoURL: "file://does-not-exist", Ref: "%invalid-name%"},
|
|
{RepoURL: "file://" + repoPath},
|
|
})
|
|
|
|
refSources, err := GetRefSources(t.Context(), argoSpec.Sources, argoSpec.Project, func(_ context.Context, _ string, _ string) (*argoappv1.Repository, error) {
|
|
return nil, err
|
|
}, []string{})
|
|
|
|
require.Error(t, err)
|
|
assert.Empty(t, refSources)
|
|
})
|
|
|
|
t.Run("duplicate ref keys", func(t *testing.T) {
|
|
argoSpec := getMultiSourceAppSpec(argoappv1.ApplicationSources{
|
|
{RepoURL: "file://does-not-exist", Ref: "source1"},
|
|
{RepoURL: "file://does-not-exist", Ref: "source1"},
|
|
})
|
|
|
|
refSources, err := GetRefSources(t.Context(), argoSpec.Sources, argoSpec.Project, func(_ context.Context, _ string, _ string) (*argoappv1.Repository, error) {
|
|
return nil, err
|
|
}, []string{})
|
|
|
|
require.Error(t, err)
|
|
assert.Empty(t, refSources)
|
|
})
|
|
}
|
|
|
|
func TestValidatePermissionsMultipleSources(t *testing.T) {
|
|
t.Run("Empty Repo URL result in condition", func(t *testing.T) {
|
|
spec := argoappv1.ApplicationSpec{
|
|
Sources: argoappv1.ApplicationSources{
|
|
{RepoURL: ""},
|
|
},
|
|
}
|
|
|
|
proj := argoappv1.AppProject{}
|
|
db := &dbmocks.ArgoDB{}
|
|
conditions, err := ValidatePermissions(t.Context(), &spec, &proj, db)
|
|
require.NoError(t, err)
|
|
assert.Len(t, conditions, 1)
|
|
assert.Equal(t, argoappv1.ApplicationConditionInvalidSpecError, conditions[0].Type)
|
|
assert.Contains(t, conditions[0].Message, "are required")
|
|
})
|
|
|
|
t.Run("Incomplete Path/Chart/Ref combo result in condition", func(t *testing.T) {
|
|
spec := argoappv1.ApplicationSpec{
|
|
Sources: argoappv1.ApplicationSources{
|
|
{
|
|
RepoURL: "http://some/where",
|
|
Path: "",
|
|
Chart: "",
|
|
Ref: "",
|
|
},
|
|
},
|
|
}
|
|
proj := argoappv1.AppProject{}
|
|
db := &dbmocks.ArgoDB{}
|
|
conditions, err := ValidatePermissions(t.Context(), &spec, &proj, db)
|
|
require.NoError(t, err)
|
|
assert.Len(t, conditions, 1)
|
|
assert.Equal(t, argoappv1.ApplicationConditionInvalidSpecError, conditions[0].Type)
|
|
assert.Contains(t, conditions[0].Message, "are required")
|
|
})
|
|
|
|
t.Run("One of the Application sources is not permitted in project", func(t *testing.T) {
|
|
spec := argoappv1.ApplicationSpec{
|
|
Sources: argoappv1.ApplicationSources{
|
|
{
|
|
RepoURL: "http://some/where",
|
|
Path: "",
|
|
Chart: "somechart",
|
|
TargetRevision: "1.4.1",
|
|
},
|
|
},
|
|
Destination: argoappv1.ApplicationDestination{
|
|
Server: "https://127.0.0.1:6443",
|
|
Namespace: "testns",
|
|
},
|
|
}
|
|
proj := argoappv1.AppProject{
|
|
Spec: argoappv1.AppProjectSpec{
|
|
Destinations: []argoappv1.ApplicationDestination{
|
|
{
|
|
Server: "*",
|
|
Namespace: "*",
|
|
},
|
|
},
|
|
SourceRepos: []string{"http://some/where/else"},
|
|
},
|
|
}
|
|
cluster := &argoappv1.Cluster{Server: "https://127.0.0.1:6443", Name: "test"}
|
|
db := &dbmocks.ArgoDB{}
|
|
db.EXPECT().GetCluster(mock.Anything, spec.Destination.Server).Return(cluster, nil)
|
|
conditions, err := ValidatePermissions(t.Context(), &spec, &proj, db)
|
|
require.NoError(t, err)
|
|
assert.Len(t, conditions, 1)
|
|
assert.Contains(t, conditions[0].Message, "application repo http://some/where is not permitted")
|
|
})
|
|
|
|
t.Run("Source with a Ref field and missing Path/Chart field", func(t *testing.T) {
|
|
spec := argoappv1.ApplicationSpec{
|
|
Sources: argoappv1.ApplicationSources{
|
|
{
|
|
RepoURL: "http://some/where",
|
|
Path: "",
|
|
Chart: "",
|
|
Ref: "somechart",
|
|
},
|
|
},
|
|
Destination: argoappv1.ApplicationDestination{
|
|
Name: "does-exist",
|
|
Namespace: "default",
|
|
},
|
|
}
|
|
proj := argoappv1.AppProject{
|
|
Spec: argoappv1.AppProjectSpec{
|
|
Destinations: []argoappv1.ApplicationDestination{
|
|
{
|
|
Server: "*",
|
|
Namespace: "default",
|
|
},
|
|
},
|
|
SourceRepos: []string{"http://some/where"},
|
|
},
|
|
}
|
|
db := &dbmocks.ArgoDB{}
|
|
cluster := argoappv1.Cluster{
|
|
Name: "does-exist",
|
|
Server: "https://127.0.0.1:6443",
|
|
}
|
|
db.EXPECT().GetClusterServersByName(mock.Anything, "does-exist").Return([]string{"https://127.0.0.1:6443"}, nil).Maybe()
|
|
db.EXPECT().GetCluster(mock.Anything, "https://127.0.0.1:6443").Return(&cluster, nil).Maybe()
|
|
conditions, err := ValidatePermissions(t.Context(), &spec, &proj, db)
|
|
require.NoError(t, err)
|
|
assert.Empty(t, conditions)
|
|
})
|
|
}
|
|
|
|
func TestAugmentSyncMsg(t *testing.T) {
|
|
mockAPIResourcesFn := func() ([]kube.APIResourceInfo, error) {
|
|
return []kube.APIResourceInfo{
|
|
{
|
|
GroupKind: schema.GroupKind{
|
|
Group: "apps",
|
|
Kind: "Deployment",
|
|
},
|
|
GroupVersionResource: schema.GroupVersionResource{
|
|
Group: "apps",
|
|
Version: "v1",
|
|
},
|
|
},
|
|
{
|
|
GroupKind: schema.GroupKind{
|
|
Group: "networking.k8s.io",
|
|
Kind: "Ingress",
|
|
},
|
|
GroupVersionResource: schema.GroupVersionResource{
|
|
Group: "networking.k8s.io",
|
|
Version: "v1",
|
|
},
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
testcases := []struct {
|
|
name string
|
|
msg string
|
|
expectedMessage string
|
|
res common.ResourceSyncResult
|
|
mockFn func() ([]kube.APIResourceInfo, error)
|
|
errMsg string
|
|
}{
|
|
{
|
|
name: "match specific k8s error",
|
|
msg: "the server could not find the requested resource",
|
|
res: common.ResourceSyncResult{
|
|
ResourceKey: kube.ResourceKey{
|
|
Name: "deployment-resource",
|
|
Namespace: "test-namespace",
|
|
Kind: "Deployment",
|
|
Group: "apps",
|
|
},
|
|
Version: "v1beta1",
|
|
},
|
|
expectedMessage: "The Kubernetes API could not find version \"v1beta1\" of apps/Deployment for requested resource test-namespace/deployment-resource. Version \"v1\" of apps/Deployment is installed on the destination cluster.",
|
|
mockFn: mockAPIResourcesFn,
|
|
},
|
|
{
|
|
name: "any random k8s msg",
|
|
msg: "random message from k8s",
|
|
res: common.ResourceSyncResult{
|
|
ResourceKey: kube.ResourceKey{
|
|
Name: "deployment-resource",
|
|
Namespace: "test-namespace",
|
|
Kind: "Deployment",
|
|
Group: "apps",
|
|
},
|
|
Version: "v1beta1",
|
|
},
|
|
expectedMessage: "random message from k8s",
|
|
mockFn: mockAPIResourcesFn,
|
|
},
|
|
{
|
|
name: "resource doesn't exist in the target cluster",
|
|
res: common.ResourceSyncResult{
|
|
ResourceKey: kube.ResourceKey{
|
|
Name: "persistent-volume-resource",
|
|
Namespace: "test-namespace",
|
|
Kind: "PersistentVolume",
|
|
Group: "",
|
|
},
|
|
Version: "v1",
|
|
},
|
|
msg: "the server could not find the requested resource",
|
|
expectedMessage: "The Kubernetes API could not find /PersistentVolume for requested resource test-namespace/persistent-volume-resource. Make sure the \"PersistentVolume\" CRD is installed on the destination cluster.",
|
|
mockFn: mockAPIResourcesFn,
|
|
},
|
|
{
|
|
name: "API Resource returns error",
|
|
res: common.ResourceSyncResult{
|
|
ResourceKey: kube.ResourceKey{
|
|
Name: "persistent-volume-resource",
|
|
Namespace: "test-namespace",
|
|
Kind: "PersistentVolume",
|
|
Group: "",
|
|
},
|
|
Version: "v1",
|
|
},
|
|
msg: "the server could not find the requested resource",
|
|
expectedMessage: "the server could not find the requested resource",
|
|
mockFn: func() ([]kube.APIResourceInfo, error) {
|
|
return nil, errors.New("failed to fetch resource of given kind %s from the target cluster")
|
|
},
|
|
errMsg: "failed to get API resource info for group \"\" and kind \"PersistentVolume\": failed to get API resource info: failed to fetch resource of given kind %s from the target cluster",
|
|
},
|
|
{
|
|
name: "old Ingress type returns error suggesting new Ingress type",
|
|
res: common.ResourceSyncResult{
|
|
ResourceKey: kube.ResourceKey{
|
|
Name: "ingress-resource",
|
|
Namespace: "test-namespace",
|
|
Kind: "Ingress",
|
|
Group: "extensions",
|
|
},
|
|
Version: "v1beta1",
|
|
},
|
|
msg: "the server could not find the requested resource",
|
|
expectedMessage: "The Kubernetes API could not find version \"v1beta1\" of extensions/Ingress for requested resource test-namespace/ingress-resource. Version \"v1\" of networking.k8s.io/Ingress is installed on the destination cluster.",
|
|
mockFn: mockAPIResourcesFn,
|
|
},
|
|
}
|
|
|
|
for _, tt := range testcases {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
tt.res.Message = tt.msg
|
|
msg, err := AugmentSyncMsg(tt.res, tt.mockFn)
|
|
if tt.errMsg != "" {
|
|
assert.EqualError(t, err, tt.errMsg)
|
|
} else {
|
|
require.NoError(t, err)
|
|
assert.Equal(t, tt.expectedMessage, msg)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetAppEventLabels(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
cmInEventLabelKeys string
|
|
cmExEventLabelKeys string
|
|
appLabels map[string]string
|
|
projLabels map[string]string
|
|
expectedEventLabels map[string]string
|
|
}{
|
|
{
|
|
name: "no label keys in cm - no event labels",
|
|
cmInEventLabelKeys: "",
|
|
appLabels: map[string]string{"team": "A", "tier": "frontend"},
|
|
projLabels: map[string]string{"environment": "dev"},
|
|
expectedEventLabels: nil,
|
|
},
|
|
{
|
|
name: "label keys in cm, no labels on app & proj - no event labels",
|
|
cmInEventLabelKeys: "team, environment",
|
|
appLabels: nil,
|
|
projLabels: nil,
|
|
expectedEventLabels: nil,
|
|
},
|
|
{
|
|
name: "labels on app, no labels on proj - event labels matched on app only",
|
|
cmInEventLabelKeys: "team, environment",
|
|
appLabels: map[string]string{"team": "A", "tier": "frontend"},
|
|
projLabels: nil,
|
|
expectedEventLabels: map[string]string{"team": "A"},
|
|
},
|
|
{
|
|
name: "no labels on app, labels on proj - event labels matched on proj only",
|
|
cmInEventLabelKeys: "team, environment",
|
|
appLabels: nil,
|
|
projLabels: map[string]string{"environment": "dev"},
|
|
expectedEventLabels: map[string]string{"environment": "dev"},
|
|
},
|
|
{
|
|
name: "labels on app & proj with conflicts - event labels matched on both app & proj and app labels prioritized on conflict",
|
|
cmInEventLabelKeys: "team, environment",
|
|
appLabels: map[string]string{"team": "A", "environment": "stage", "tier": "frontend"},
|
|
projLabels: map[string]string{"environment": "dev"},
|
|
expectedEventLabels: map[string]string{"team": "A", "environment": "stage"},
|
|
},
|
|
{
|
|
name: "wildcard support - matched all labels",
|
|
cmInEventLabelKeys: "*",
|
|
appLabels: map[string]string{"team": "A", "tier": "frontend"},
|
|
projLabels: map[string]string{"environment": "dev"},
|
|
expectedEventLabels: map[string]string{"team": "A", "tier": "frontend", "environment": "dev"},
|
|
},
|
|
{
|
|
name: "exclude event labels",
|
|
cmInEventLabelKeys: "example.com/team,tier,env*",
|
|
cmExEventLabelKeys: "tie*",
|
|
appLabels: map[string]string{"example.com/team": "A", "tier": "frontend"},
|
|
projLabels: map[string]string{"environment": "dev"},
|
|
expectedEventLabels: map[string]string{"example.com/team": "A", "environment": "dev"},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
cm := corev1.ConfigMap{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "argocd-cm",
|
|
Namespace: test.FakeArgoCDNamespace,
|
|
Labels: map[string]string{
|
|
"app.kubernetes.io/part-of": "argocd",
|
|
},
|
|
},
|
|
Data: map[string]string{
|
|
"resource.includeEventLabelKeys": tt.cmInEventLabelKeys,
|
|
"resource.excludeEventLabelKeys": tt.cmExEventLabelKeys,
|
|
},
|
|
}
|
|
|
|
proj := &argoappv1.AppProject{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "default",
|
|
Namespace: test.FakeArgoCDNamespace,
|
|
Labels: tt.projLabels,
|
|
},
|
|
}
|
|
|
|
var app argoappv1.Application
|
|
app.Name = "test-app"
|
|
app.Namespace = test.FakeArgoCDNamespace
|
|
app.Labels = tt.appLabels
|
|
appClientset := appclientset.NewSimpleClientset(proj)
|
|
ctx, cancel := context.WithCancel(t.Context())
|
|
defer cancel()
|
|
indexers := cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}
|
|
informer := v1alpha1.NewAppProjectInformer(appClientset, test.FakeArgoCDNamespace, 0, indexers)
|
|
go informer.Run(ctx.Done())
|
|
cache.WaitForCacheSync(ctx.Done(), informer.HasSynced)
|
|
|
|
kubeClient := fake.NewSimpleClientset(&cm)
|
|
settingsMgr := settings.NewSettingsManager(t.Context(), kubeClient, test.FakeArgoCDNamespace)
|
|
argoDB := db.NewDB("default", settingsMgr, kubeClient)
|
|
|
|
eventLabels := GetAppEventLabels(ctx, &app, applisters.NewAppProjectLister(informer.GetIndexer()), test.FakeArgoCDNamespace, settingsMgr, argoDB)
|
|
assert.Len(t, eventLabels, len(tt.expectedEventLabels))
|
|
for ek, ev := range tt.expectedEventLabels {
|
|
v, found := eventLabels[ek]
|
|
assert.True(t, found)
|
|
assert.Equal(t, ev, v)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValidateManagedByURL(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
app *argoappv1.Application
|
|
wantErr bool
|
|
wantErrMsg string
|
|
}{
|
|
{
|
|
name: "Valid HTTPS URL",
|
|
app: &argoappv1.Application{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Annotations: map[string]string{
|
|
argoappv1.AnnotationKeyManagedByURL: "https://argocd.example.com",
|
|
},
|
|
},
|
|
},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "Valid HTTP URL",
|
|
app: &argoappv1.Application{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Annotations: map[string]string{
|
|
argoappv1.AnnotationKeyManagedByURL: "http://argocd.example.com",
|
|
},
|
|
},
|
|
},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "Valid localhost HTTPS URL",
|
|
app: &argoappv1.Application{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Annotations: map[string]string{
|
|
argoappv1.AnnotationKeyManagedByURL: "https://localhost:8081",
|
|
},
|
|
},
|
|
},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "Valid localhost HTTP URL",
|
|
app: &argoappv1.Application{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Annotations: map[string]string{
|
|
argoappv1.AnnotationKeyManagedByURL: "http://localhost:8081",
|
|
},
|
|
},
|
|
},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "Valid 127.0.0.1 URL",
|
|
app: &argoappv1.Application{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Annotations: map[string]string{
|
|
argoappv1.AnnotationKeyManagedByURL: "http://127.0.0.1:8081",
|
|
},
|
|
},
|
|
},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "No annotations",
|
|
app: &argoappv1.Application{
|
|
ObjectMeta: metav1.ObjectMeta{},
|
|
},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "Empty managed-by-url",
|
|
app: &argoappv1.Application{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Annotations: map[string]string{
|
|
argoappv1.AnnotationKeyManagedByURL: "",
|
|
},
|
|
},
|
|
},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "Missing managed-by-url annotation",
|
|
app: &argoappv1.Application{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Annotations: map[string]string{
|
|
"other.annotation": "value",
|
|
},
|
|
},
|
|
},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "Invalid protocol - javascript",
|
|
app: &argoappv1.Application{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Annotations: map[string]string{
|
|
argoappv1.AnnotationKeyManagedByURL: "javascript:alert('xss')",
|
|
},
|
|
},
|
|
},
|
|
wantErr: true,
|
|
wantErrMsg: "invalid managed-by URL: URL must include http or https protocol",
|
|
},
|
|
{
|
|
name: "Invalid protocol - data",
|
|
app: &argoappv1.Application{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Annotations: map[string]string{
|
|
argoappv1.AnnotationKeyManagedByURL: "data:text/html,<script>alert('xss')</script>",
|
|
},
|
|
},
|
|
},
|
|
wantErr: true,
|
|
wantErrMsg: "invalid managed-by URL: URL must include http or https protocol",
|
|
},
|
|
{
|
|
name: "Invalid protocol - file",
|
|
app: &argoappv1.Application{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Annotations: map[string]string{
|
|
argoappv1.AnnotationKeyManagedByURL: "file:///etc/passwd",
|
|
},
|
|
},
|
|
},
|
|
wantErr: true,
|
|
wantErrMsg: "invalid managed-by URL: URL must include http or https protocol",
|
|
},
|
|
{
|
|
name: "Invalid URL format",
|
|
app: &argoappv1.Application{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Annotations: map[string]string{
|
|
argoappv1.AnnotationKeyManagedByURL: "not-a-url",
|
|
},
|
|
},
|
|
},
|
|
wantErr: true,
|
|
wantErrMsg: "invalid managed-by URL: URL must include http or https protocol",
|
|
},
|
|
{
|
|
name: "URL with path and query",
|
|
app: &argoappv1.Application{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Annotations: map[string]string{
|
|
argoappv1.AnnotationKeyManagedByURL: "https://argocd.example.com/applications?namespace=default",
|
|
},
|
|
},
|
|
},
|
|
wantErr: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
conditions := ValidateManagedByURL(tt.app)
|
|
|
|
if tt.wantErr {
|
|
require.Len(t, conditions, 1, "Expected exactly one validation condition")
|
|
assert.Equal(t, argoappv1.ApplicationConditionInvalidSpecError, conditions[0].Type)
|
|
assert.Contains(t, conditions[0].Message, tt.wantErrMsg)
|
|
} else {
|
|
assert.Empty(t, conditions, "Expected no validation conditions for valid URL")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_GetSyncedRefSources(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
refSources argoappv1.RefTargetRevisionMapping
|
|
sources argoappv1.ApplicationSources
|
|
syncedRevisions []string
|
|
result argoappv1.RefTargetRevisionMapping
|
|
}{
|
|
{
|
|
name: "multi ref sources",
|
|
refSources: argoappv1.RefTargetRevisionMapping{
|
|
"$values": &argoappv1.RefTarget{
|
|
Repo: argoappv1.Repository{Repo: "https://github.com/argocd"},
|
|
TargetRevision: "main-1",
|
|
Chart: "chart",
|
|
},
|
|
"$values_1": &argoappv1.RefTarget{
|
|
Repo: argoappv1.Repository{Repo: "https://github.com/argocd-1"},
|
|
TargetRevision: "main-2",
|
|
Chart: "chart",
|
|
},
|
|
},
|
|
sources: argoappv1.ApplicationSources{
|
|
{RepoURL: "https://helm.registry", TargetRevision: "0.0.1", Chart: "my-chart", Helm: &argoappv1.ApplicationSourceHelm{ValueFiles: []string{"$values/path"}}},
|
|
{RepoURL: "https://github.com/argocd", TargetRevision: "main-1", Ref: "values"},
|
|
{RepoURL: "https://github.com/argocd-1", TargetRevision: "main-2", Ref: "values_1"},
|
|
},
|
|
syncedRevisions: []string{"0.0.1", "resolved-main-1", "resolved-main-2"},
|
|
result: argoappv1.RefTargetRevisionMapping{
|
|
"$values": &argoappv1.RefTarget{
|
|
Repo: argoappv1.Repository{Repo: "https://github.com/argocd"},
|
|
TargetRevision: "resolved-main-1",
|
|
Chart: "chart",
|
|
},
|
|
"$values_1": &argoappv1.RefTarget{
|
|
Repo: argoappv1.Repository{Repo: "https://github.com/argocd-1"},
|
|
TargetRevision: "resolved-main-2",
|
|
Chart: "chart",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "ref source",
|
|
refSources: argoappv1.RefTargetRevisionMapping{
|
|
"$values": &argoappv1.RefTarget{
|
|
Repo: argoappv1.Repository{Repo: "https://github.com/argocd"},
|
|
TargetRevision: "main-1",
|
|
Chart: "chart",
|
|
},
|
|
},
|
|
sources: argoappv1.ApplicationSources{
|
|
{RepoURL: "https://helm.registry", TargetRevision: "0.0.1", Chart: "my-chart", Helm: &argoappv1.ApplicationSourceHelm{ValueFiles: []string{"$values/path"}}},
|
|
{RepoURL: "https://github.com/argocd", TargetRevision: "main-1", Ref: "values"},
|
|
},
|
|
syncedRevisions: []string{"0.0.1", "resolved-main-1"},
|
|
result: argoappv1.RefTargetRevisionMapping{
|
|
"$values": &argoappv1.RefTarget{
|
|
Repo: argoappv1.Repository{Repo: "https://github.com/argocd"},
|
|
TargetRevision: "resolved-main-1",
|
|
Chart: "chart",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "empty ref source",
|
|
refSources: argoappv1.RefTargetRevisionMapping{},
|
|
sources: argoappv1.ApplicationSources{
|
|
{RepoURL: "https://helm.registry", TargetRevision: "0.0.1", Chart: "my-chart"},
|
|
},
|
|
syncedRevisions: []string{"0.0.1"},
|
|
result: argoappv1.RefTargetRevisionMapping{},
|
|
},
|
|
{
|
|
name: "empty sources",
|
|
refSources: argoappv1.RefTargetRevisionMapping{},
|
|
sources: argoappv1.ApplicationSources{},
|
|
syncedRevisions: []string{},
|
|
result: argoappv1.RefTargetRevisionMapping{},
|
|
},
|
|
{
|
|
name: "no synced revisions",
|
|
refSources: argoappv1.RefTargetRevisionMapping{
|
|
"$values": &argoappv1.RefTarget{
|
|
Repo: argoappv1.Repository{Repo: "https://github.com/argocd"},
|
|
TargetRevision: "main-1",
|
|
Chart: "chart",
|
|
},
|
|
},
|
|
sources: argoappv1.ApplicationSources{
|
|
{RepoURL: "https://helm.registry", TargetRevision: "0.0.1", Chart: "my-chart", Helm: &argoappv1.ApplicationSourceHelm{ValueFiles: []string{"$values/path"}}},
|
|
{RepoURL: "https://github.com/argocd", TargetRevision: "main-1", Ref: "values"},
|
|
},
|
|
syncedRevisions: []string{},
|
|
result: argoappv1.RefTargetRevisionMapping{
|
|
"$values": &argoappv1.RefTarget{
|
|
Repo: argoappv1.Repository{Repo: "https://github.com/argocd"},
|
|
TargetRevision: "",
|
|
Chart: "chart",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
syncedRefSources := GetSyncedRefSources(tt.refSources, tt.sources, tt.syncedRevisions)
|
|
assert.Equal(t, tt.result, syncedRefSources)
|
|
})
|
|
}
|
|
}
|