2018-09-11 21:28:53 +00:00
package controller
import (
2019-01-08 22:53:45 +00:00
"context"
2019-10-28 23:44:23 +00:00
"encoding/json"
2018-09-11 21:28:53 +00:00
"testing"
"time"
2020-05-29 01:42:01 +00:00
"github.com/argoproj/gitops-engine/pkg/cache/mocks"
synccommon "github.com/argoproj/gitops-engine/pkg/sync/common"
2020-05-15 21:39:29 +00:00
"github.com/argoproj/gitops-engine/pkg/utils/kube"
"github.com/argoproj/gitops-engine/pkg/utils/kube/kubetest"
2018-09-11 21:28:53 +00:00
"github.com/ghodss/yaml"
2018-12-03 18:27:43 +00:00
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
2018-11-19 20:25:45 +00:00
corev1 "k8s.io/api/core/v1"
2019-05-30 19:39:54 +00:00
apierr "k8s.io/apimachinery/pkg/api/errors"
2018-09-11 21:28:53 +00:00
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2018-12-03 18:27:43 +00:00
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2018-09-11 21:28:53 +00:00
"k8s.io/apimachinery/pkg/runtime"
2019-06-05 01:17:41 +00:00
"k8s.io/apimachinery/pkg/runtime/schema"
2018-09-11 21:28:53 +00:00
"k8s.io/client-go/kubernetes/fake"
2018-11-19 20:25:45 +00:00
kubetesting "k8s.io/client-go/testing"
2018-11-30 21:50:27 +00:00
"k8s.io/client-go/tools/cache"
2018-09-11 21:28:53 +00:00
2019-05-30 19:39:54 +00:00
"github.com/argoproj/argo-cd/common"
2018-12-03 18:27:43 +00:00
mockstatecache "github.com/argoproj/argo-cd/controller/cache/mocks"
2018-09-11 21:28:53 +00:00
argoappv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
appclientset "github.com/argoproj/argo-cd/pkg/client/clientset/versioned/fake"
2019-07-13 00:17:23 +00:00
"github.com/argoproj/argo-cd/reposerver/apiclient"
mockrepoclient "github.com/argoproj/argo-cd/reposerver/apiclient/mocks"
2018-11-30 21:50:27 +00:00
"github.com/argoproj/argo-cd/test"
2019-10-16 22:46:45 +00:00
cacheutil "github.com/argoproj/argo-cd/util/cache"
appstatecache "github.com/argoproj/argo-cd/util/cache/appstate"
2019-01-08 22:53:45 +00:00
"github.com/argoproj/argo-cd/util/settings"
2018-09-11 21:28:53 +00:00
)
2019-08-19 15:14:48 +00:00
type namespacedResource struct {
argoappv1 . ResourceNode
AppName string
}
2018-12-03 18:27:43 +00:00
type fakeData struct {
2019-08-19 15:14:48 +00:00
apps [ ] runtime . Object
manifestResponse * apiclient . ManifestResponse
managedLiveObjs map [ kube . ResourceKey ] * unstructured . Unstructured
namespacedResources map [ kube . ResourceKey ] namespacedResource
2019-08-20 15:43:29 +00:00
configMapData map [ string ] string
2018-12-03 18:27:43 +00:00
}
func newFakeController ( data * fakeData ) * ApplicationController {
2018-11-19 20:25:45 +00:00
var clust corev1 . Secret
err := yaml . Unmarshal ( [ ] byte ( fakeCluster ) , & clust )
if err != nil {
panic ( err )
}
2018-12-03 18:27:43 +00:00
// Mock out call to GenerateManifest
2019-03-05 22:56:47 +00:00
mockRepoClient := mockrepoclient . RepoServerServiceClient { }
2018-12-03 18:27:43 +00:00
mockRepoClient . On ( "GenerateManifest" , mock . Anything , mock . Anything ) . Return ( data . manifestResponse , nil )
2020-07-02 20:47:56 +00:00
mockRepoClientset := mockrepoclient . Clientset { RepoServerServiceClient : & mockRepoClient }
2018-12-03 18:27:43 +00:00
2018-12-03 23:15:37 +00:00
secret := corev1 . Secret {
ObjectMeta : metav1 . ObjectMeta {
Name : "argocd-secret" ,
Namespace : test . FakeArgoCDNamespace ,
} ,
Data : map [ string ] [ ] byte {
"admin.password" : [ ] byte ( "test" ) ,
"server.secretkey" : [ ] byte ( "test" ) ,
} ,
}
cm := corev1 . ConfigMap {
ObjectMeta : metav1 . ObjectMeta {
Name : "argocd-cm" ,
Namespace : test . FakeArgoCDNamespace ,
2019-07-11 23:00:47 +00:00
Labels : map [ string ] string {
"app.kubernetes.io/part-of" : "argocd" ,
} ,
2018-12-03 23:15:37 +00:00
} ,
2019-08-20 15:43:29 +00:00
Data : data . configMapData ,
2018-12-03 23:15:37 +00:00
}
2019-01-08 22:53:45 +00:00
kubeClient := fake . NewSimpleClientset ( & clust , & cm , & secret )
settingsMgr := settings . NewSettingsManager ( context . Background ( ) , kubeClient , test . FakeArgoCDNamespace )
2019-09-11 23:37:00 +00:00
kubectl := & kubetest . MockKubectlCmd { }
2019-01-08 22:53:45 +00:00
ctrl , err := NewApplicationController (
2018-12-03 18:27:43 +00:00
test . FakeArgoCDNamespace ,
2019-01-08 22:53:45 +00:00
settingsMgr ,
kubeClient ,
2018-12-03 18:27:43 +00:00
appclientset . NewSimpleClientset ( data . apps ... ) ,
& mockRepoClientset ,
2019-10-16 22:46:45 +00:00
appstatecache . NewCache (
cacheutil . NewCache ( cacheutil . NewInMemoryCache ( 1 * time . Minute ) ) ,
1 * time . Minute ,
) ,
2019-09-11 23:37:00 +00:00
kubectl ,
2018-09-11 21:28:53 +00:00
time . Minute ,
2019-07-25 02:26:09 +00:00
time . Minute ,
2019-05-28 18:41:02 +00:00
common . DefaultPortArgoCDMetrics ,
2019-09-10 16:56:48 +00:00
0 ,
2018-09-11 21:28:53 +00:00
)
2019-01-08 22:53:45 +00:00
if err != nil {
panic ( err )
}
2019-03-04 08:56:36 +00:00
cancelProj := test . StartInformer ( ctrl . projInformer )
defer cancelProj ( )
cancelApp := test . StartInformer ( ctrl . appInformer )
defer cancelApp ( )
2020-05-15 17:01:18 +00:00
clusterCacheMock := mocks . ClusterCache { }
clusterCacheMock . On ( "IsNamespaced" , mock . Anything ) . Return ( true , nil )
2019-08-19 15:14:48 +00:00
mockStateCache := mockstatecache . LiveStateCache { }
ctrl . appStateManager . ( * appStateManager ) . liveStateCache = & mockStateCache
ctrl . stateCache = & mockStateCache
mockStateCache . On ( "IsNamespaced" , mock . Anything , mock . Anything ) . Return ( true , nil )
mockStateCache . On ( "GetManagedLiveObjs" , mock . Anything , mock . Anything ) . Return ( data . managedLiveObjs , nil )
2020-03-17 21:20:36 +00:00
mockStateCache . On ( "GetVersionsInfo" , mock . Anything ) . Return ( "v1.2.3" , nil , nil )
2019-08-19 15:14:48 +00:00
response := make ( map [ kube . ResourceKey ] argoappv1 . ResourceNode )
for k , v := range data . namespacedResources {
response [ k ] = v . ResourceNode
2018-12-03 18:27:43 +00:00
}
2019-08-19 15:14:48 +00:00
mockStateCache . On ( "GetNamespaceTopLevelResources" , mock . Anything , mock . Anything ) . Return ( response , nil )
2020-05-15 17:01:18 +00:00
mockStateCache . On ( "GetClusterCache" , mock . Anything ) . Return ( & clusterCacheMock , nil )
2019-08-19 15:14:48 +00:00
mockStateCache . On ( "IterateHierarchy" , mock . Anything , mock . Anything , mock . Anything ) . Run ( func ( args mock . Arguments ) {
key := args [ 1 ] . ( kube . ResourceKey )
action := args [ 2 ] . ( func ( child argoappv1 . ResourceNode , appName string ) )
appName := ""
if res , ok := data . namespacedResources [ key ] ; ok {
appName = res . AppName
}
2020-07-21 23:59:00 +00:00
action ( argoappv1 . ResourceNode { ResourceRef : argoappv1 . ResourceRef { Kind : key . Kind , Group : key . Group , Namespace : key . Namespace , Name : key . Name } } , appName )
2019-08-19 15:14:48 +00:00
} ) . Return ( nil )
2018-12-03 18:27:43 +00:00
return ctrl
2018-09-11 21:28:53 +00:00
}
2018-11-19 20:25:45 +00:00
var fakeCluster = `
apiVersion : v1
data :
# { "bearerToken" : "fake" , "tlsClientConfig" : { "insecure" : true } , "awsAuthConfig" : null }
config : eyJiZWFyZXJUb2tlbiI6ImZha2UiLCJ0bHNDbGllbnRDb25maWciOnsiaW5zZWN1cmUiOnRydWV9LCJhd3NBdXRoQ29uZmlnIjpudWxsfQ ==
# minikube
2020-06-20 23:12:46 +00:00
name : bWluaWt1YmU =
2018-11-19 20:25:45 +00:00
# https : //localhost:6443
2019-03-30 05:09:36 +00:00
server : aHR0cHM6Ly9sb2NhbGhvc3Q6NjQ0Mw ==
2018-11-19 20:25:45 +00:00
kind : Secret
metadata :
labels :
argocd . argoproj . io / secret - type : cluster
2019-03-30 05:09:36 +00:00
name : some - secret
2018-12-03 18:27:43 +00:00
namespace : ` + test.FakeArgoCDNamespace + `
2018-11-19 20:25:45 +00:00
type : Opaque
`
2018-09-11 21:28:53 +00:00
var fakeApp = `
apiVersion : argoproj . io / v1alpha1
kind : Application
metadata :
2019-05-13 18:17:32 +00:00
uid : "123"
2018-09-11 21:28:53 +00:00
name : my - app
2018-12-03 18:27:43 +00:00
namespace : ` + test.FakeArgoCDNamespace + `
2018-09-11 21:28:53 +00:00
spec :
destination :
2018-12-03 18:27:43 +00:00
namespace : ` + test.FakeDestNamespace + `
2018-09-11 21:28:53 +00:00
server : https : //localhost:6443
project : default
source :
path : some / path
repoURL : https : //github.com/argoproj/argocd-example-apps.git
syncPolicy :
automated : { }
status :
2018-09-24 15:52:43 +00:00
operationState :
finishedAt : 2018 - 0 9 - 21 T23 : 50 : 29 Z
message : successfully synced
operation :
sync :
revision : HEAD
phase : Succeeded
startedAt : 2018 - 0 9 - 21 T23 : 50 : 25 Z
syncResult :
resources :
- kind : RoleBinding
message : | -
rolebinding . rbac . authorization . k8s . io / always - outofsync reconciled
rolebinding . rbac . authorization . k8s . io / always - outofsync configured
name : always - outofsync
namespace : default
status : Synced
revision : aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
2019-03-04 08:56:36 +00:00
source :
path : some / path
repoURL : https : //github.com/argoproj/argocd-example-apps.git
2018-09-11 21:28:53 +00:00
`
2020-06-20 23:12:46 +00:00
var fakeAppWithDestName = `
apiVersion : argoproj . io / v1alpha1
kind : Application
metadata :
uid : "123"
name : my - app
namespace : ` + test.FakeArgoCDNamespace + `
spec :
destination :
namespace : ` + test.FakeDestNamespace + `
name : minikube
project : default
source :
path : some / path
repoURL : https : //github.com/argoproj/argocd-example-apps.git
syncPolicy :
automated : { }
`
var fakeAppWithDestMismatch = `
apiVersion : argoproj . io / v1alpha1
kind : Application
metadata :
uid : "123"
name : my - app
namespace : ` + test.FakeArgoCDNamespace + `
spec :
destination :
namespace : ` + test.FakeDestNamespace + `
name : another - cluster
server : https : //localhost:6443
project : default
source :
path : some / path
repoURL : https : //github.com/argoproj/argocd-example-apps.git
syncPolicy :
automated : { }
`
2019-12-09 16:39:20 +00:00
var fakeStrayResource = `
apiVersion : v1
kind : ConfigMap
metadata :
name : test - cm
namespace : invalid
labels :
app . kubernetes . io / instance : my - app
data :
`
2018-09-11 21:28:53 +00:00
func newFakeApp ( ) * argoappv1 . Application {
2020-06-20 23:12:46 +00:00
return createFakeApp ( fakeApp )
}
func newFakeAppWithDestMismatch ( ) * argoappv1 . Application {
return createFakeApp ( fakeAppWithDestMismatch )
}
func newFakeAppWithDestName ( ) * argoappv1 . Application {
return createFakeApp ( fakeAppWithDestName )
}
func createFakeApp ( testApp string ) * argoappv1 . Application {
2018-09-11 21:28:53 +00:00
var app argoappv1 . Application
2020-06-20 23:12:46 +00:00
err := yaml . Unmarshal ( [ ] byte ( testApp ) , & app )
2018-09-11 21:28:53 +00:00
if err != nil {
panic ( err )
}
return & app
}
2019-12-09 16:39:20 +00:00
func newFakeCM ( ) map [ string ] interface { } {
var cm map [ string ] interface { }
err := yaml . Unmarshal ( [ ] byte ( fakeStrayResource ) , & cm )
if err != nil {
panic ( err )
}
return cm
}
2018-09-11 21:28:53 +00:00
func TestAutoSync ( t * testing . T ) {
app := newFakeApp ( )
2018-12-03 18:27:43 +00:00
ctrl := newFakeController ( & fakeData { apps : [ ] runtime . Object { app } } )
2018-12-04 10:52:57 +00:00
syncStatus := argoappv1 . SyncStatus {
2018-12-04 01:39:55 +00:00
Status : argoappv1 . SyncStatusCodeOutOfSync ,
2018-09-11 21:28:53 +00:00
Revision : "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" ,
}
2020-03-18 21:19:54 +00:00
cond := ctrl . autoSync ( app , & syncStatus , [ ] argoappv1 . ResourceStatus { { Name : "guestbook" , Kind : kube . DeploymentKind , Status : argoappv1 . SyncStatusCodeOutOfSync } } )
2018-09-11 21:28:53 +00:00
assert . Nil ( t , cond )
2020-08-05 18:36:40 +00:00
app , err := ctrl . applicationClientset . ArgoprojV1alpha1 ( ) . Applications ( test . FakeArgoCDNamespace ) . Get ( context . Background ( ) , "my-app" , metav1 . GetOptions { } )
2018-09-11 21:28:53 +00:00
assert . NoError ( t , err )
assert . NotNil ( t , app . Operation )
assert . NotNil ( t , app . Operation . Sync )
assert . False ( t , app . Operation . Sync . Prune )
}
func TestSkipAutoSync ( t * testing . T ) {
// Verify we skip when we previously synced to it in our most recent history
// Set current to 'aaaaa', desired to 'aaaa' and mark system OutOfSync
2020-03-18 21:19:54 +00:00
t . Run ( "PreviouslySyncedToRevision" , func ( t * testing . T ) {
2019-03-04 08:56:36 +00:00
app := newFakeApp ( )
ctrl := newFakeController ( & fakeData { apps : [ ] runtime . Object { app } } )
syncStatus := argoappv1 . SyncStatus {
Status : argoappv1 . SyncStatusCodeOutOfSync ,
Revision : "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" ,
}
2019-07-25 02:26:09 +00:00
cond := ctrl . autoSync ( app , & syncStatus , [ ] argoappv1 . ResourceStatus { } )
2019-03-04 08:56:36 +00:00
assert . Nil ( t , cond )
2020-08-05 18:36:40 +00:00
app , err := ctrl . applicationClientset . ArgoprojV1alpha1 ( ) . Applications ( test . FakeArgoCDNamespace ) . Get ( context . Background ( ) , "my-app" , metav1 . GetOptions { } )
2019-03-04 08:56:36 +00:00
assert . NoError ( t , err )
assert . Nil ( t , app . Operation )
2020-03-18 21:19:54 +00:00
} )
2018-09-11 21:28:53 +00:00
// Verify we skip when we are already Synced (even if revision is different)
2020-03-18 21:19:54 +00:00
t . Run ( "AlreadyInSyncedState" , func ( t * testing . T ) {
2019-03-04 08:56:36 +00:00
app := newFakeApp ( )
ctrl := newFakeController ( & fakeData { apps : [ ] runtime . Object { app } } )
syncStatus := argoappv1 . SyncStatus {
Status : argoappv1 . SyncStatusCodeSynced ,
Revision : "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" ,
}
2019-07-25 02:26:09 +00:00
cond := ctrl . autoSync ( app , & syncStatus , [ ] argoappv1 . ResourceStatus { } )
2019-03-04 08:56:36 +00:00
assert . Nil ( t , cond )
2020-08-05 18:36:40 +00:00
app , err := ctrl . applicationClientset . ArgoprojV1alpha1 ( ) . Applications ( test . FakeArgoCDNamespace ) . Get ( context . Background ( ) , "my-app" , metav1 . GetOptions { } )
2019-03-04 08:56:36 +00:00
assert . NoError ( t , err )
assert . Nil ( t , app . Operation )
2020-03-18 21:19:54 +00:00
} )
2018-09-11 21:28:53 +00:00
// Verify we skip when auto-sync is disabled
2020-03-18 21:19:54 +00:00
t . Run ( "AutoSyncIsDisabled" , func ( t * testing . T ) {
2019-03-04 08:56:36 +00:00
app := newFakeApp ( )
app . Spec . SyncPolicy = nil
ctrl := newFakeController ( & fakeData { apps : [ ] runtime . Object { app } } )
syncStatus := argoappv1 . SyncStatus {
Status : argoappv1 . SyncStatusCodeOutOfSync ,
Revision : "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" ,
}
2019-07-25 02:26:09 +00:00
cond := ctrl . autoSync ( app , & syncStatus , [ ] argoappv1 . ResourceStatus { } )
2019-03-04 08:56:36 +00:00
assert . Nil ( t , cond )
2020-08-05 18:36:40 +00:00
app , err := ctrl . applicationClientset . ArgoprojV1alpha1 ( ) . Applications ( test . FakeArgoCDNamespace ) . Get ( context . Background ( ) , "my-app" , metav1 . GetOptions { } )
2019-03-04 08:56:36 +00:00
assert . NoError ( t , err )
assert . Nil ( t , app . Operation )
2020-03-18 21:19:54 +00:00
} )
2019-03-04 08:56:36 +00:00
// Verify we skip when application is marked for deletion
2020-03-18 21:19:54 +00:00
t . Run ( "ApplicationIsMarkedForDeletion" , func ( t * testing . T ) {
2019-03-04 08:56:36 +00:00
app := newFakeApp ( )
now := metav1 . Now ( )
app . DeletionTimestamp = & now
ctrl := newFakeController ( & fakeData { apps : [ ] runtime . Object { app } } )
syncStatus := argoappv1 . SyncStatus {
Status : argoappv1 . SyncStatusCodeOutOfSync ,
Revision : "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" ,
}
2019-07-25 02:26:09 +00:00
cond := ctrl . autoSync ( app , & syncStatus , [ ] argoappv1 . ResourceStatus { } )
2019-03-04 08:56:36 +00:00
assert . Nil ( t , cond )
2020-08-05 18:36:40 +00:00
app , err := ctrl . applicationClientset . ArgoprojV1alpha1 ( ) . Applications ( test . FakeArgoCDNamespace ) . Get ( context . Background ( ) , "my-app" , metav1 . GetOptions { } )
2019-03-04 08:56:36 +00:00
assert . NoError ( t , err )
assert . Nil ( t , app . Operation )
2020-03-18 21:19:54 +00:00
} )
2018-09-11 21:28:53 +00:00
// Verify we skip when previous sync attempt failed and return error condition
// Set current to 'aaaaa', desired to 'bbbbb' and add 'bbbbb' to failure history
2020-03-18 21:19:54 +00:00
t . Run ( "PreviousSyncAttemptFailed" , func ( t * testing . T ) {
2019-03-04 08:56:36 +00:00
app := newFakeApp ( )
app . Status . OperationState = & argoappv1 . OperationState {
Operation : argoappv1 . Operation {
Sync : & argoappv1 . SyncOperation { } ,
} ,
2020-05-15 17:01:18 +00:00
Phase : synccommon . OperationFailed ,
2019-03-04 08:56:36 +00:00
SyncResult : & argoappv1 . SyncOperationResult {
Revision : "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" ,
Source : * app . Spec . Source . DeepCopy ( ) ,
} ,
}
ctrl := newFakeController ( & fakeData { apps : [ ] runtime . Object { app } } )
syncStatus := argoappv1 . SyncStatus {
Status : argoappv1 . SyncStatusCodeOutOfSync ,
2018-09-11 21:28:53 +00:00
Revision : "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" ,
2019-03-04 08:56:36 +00:00
}
2020-03-18 21:19:54 +00:00
cond := ctrl . autoSync ( app , & syncStatus , [ ] argoappv1 . ResourceStatus { { Name : "guestbook" , Kind : kube . DeploymentKind , Status : argoappv1 . SyncStatusCodeOutOfSync } } )
2019-03-04 08:56:36 +00:00
assert . NotNil ( t , cond )
2020-08-05 18:36:40 +00:00
app , err := ctrl . applicationClientset . ArgoprojV1alpha1 ( ) . Applications ( test . FakeArgoCDNamespace ) . Get ( context . Background ( ) , "my-app" , metav1 . GetOptions { } )
2019-03-04 08:56:36 +00:00
assert . NoError ( t , err )
assert . Nil ( t , app . Operation )
2020-03-18 21:19:54 +00:00
} )
t . Run ( "NeedsToPruneResourcesOnlyButAutomatedPruneDisabled" , func ( t * testing . T ) {
app := newFakeApp ( )
ctrl := newFakeController ( & fakeData { apps : [ ] runtime . Object { app } } )
syncStatus := argoappv1 . SyncStatus {
Status : argoappv1 . SyncStatusCodeOutOfSync ,
Revision : "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" ,
}
cond := ctrl . autoSync ( app , & syncStatus , [ ] argoappv1 . ResourceStatus {
{ Name : "guestbook" , Kind : kube . DeploymentKind , Status : argoappv1 . SyncStatusCodeOutOfSync , RequiresPruning : true } ,
} )
assert . Nil ( t , cond )
2020-08-05 18:36:40 +00:00
app , err := ctrl . applicationClientset . ArgoprojV1alpha1 ( ) . Applications ( test . FakeArgoCDNamespace ) . Get ( context . Background ( ) , "my-app" , metav1 . GetOptions { } )
2020-03-18 21:19:54 +00:00
assert . NoError ( t , err )
assert . Nil ( t , app . Operation )
} )
2018-09-11 21:28:53 +00:00
}
2018-09-24 15:52:43 +00:00
// TestAutoSyncIndicateError verifies we skip auto-sync and return error condition if previous sync failed
func TestAutoSyncIndicateError ( t * testing . T ) {
app := newFakeApp ( )
2019-03-04 08:56:36 +00:00
app . Spec . Source . Helm = & argoappv1 . ApplicationSourceHelm {
Parameters : [ ] argoappv1 . HelmParameter {
{
Name : "a" ,
Value : "1" ,
} ,
2018-09-24 15:52:43 +00:00
} ,
}
2018-12-03 18:27:43 +00:00
ctrl := newFakeController ( & fakeData { apps : [ ] runtime . Object { app } } )
2018-12-04 10:52:57 +00:00
syncStatus := argoappv1 . SyncStatus {
2018-12-04 01:39:55 +00:00
Status : argoappv1 . SyncStatusCodeOutOfSync ,
2018-09-24 15:52:43 +00:00
Revision : "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" ,
}
app . Status . OperationState = & argoappv1 . OperationState {
Operation : argoappv1 . Operation {
Sync : & argoappv1 . SyncOperation {
2019-03-04 08:56:36 +00:00
Source : app . Spec . Source . DeepCopy ( ) ,
2018-09-24 15:52:43 +00:00
} ,
} ,
2020-05-15 17:01:18 +00:00
Phase : synccommon . OperationFailed ,
2018-09-24 15:52:43 +00:00
SyncResult : & argoappv1 . SyncOperationResult {
Revision : "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" ,
2019-03-04 08:56:36 +00:00
Source : * app . Spec . Source . DeepCopy ( ) ,
2018-09-24 15:52:43 +00:00
} ,
}
2020-03-18 21:19:54 +00:00
cond := ctrl . autoSync ( app , & syncStatus , [ ] argoappv1 . ResourceStatus { { Name : "guestbook" , Kind : kube . DeploymentKind , Status : argoappv1 . SyncStatusCodeOutOfSync } } )
2018-09-24 15:52:43 +00:00
assert . NotNil ( t , cond )
2020-08-05 18:36:40 +00:00
app , err := ctrl . applicationClientset . ArgoprojV1alpha1 ( ) . Applications ( test . FakeArgoCDNamespace ) . Get ( context . Background ( ) , "my-app" , metav1 . GetOptions { } )
2018-09-24 15:52:43 +00:00
assert . NoError ( t , err )
assert . Nil ( t , app . Operation )
}
// TestAutoSyncParameterOverrides verifies we auto-sync if revision is same but parameter overrides are different
func TestAutoSyncParameterOverrides ( t * testing . T ) {
app := newFakeApp ( )
2019-03-04 08:56:36 +00:00
app . Spec . Source . Helm = & argoappv1 . ApplicationSourceHelm {
Parameters : [ ] argoappv1 . HelmParameter {
{
Name : "a" ,
Value : "1" ,
} ,
2018-09-24 15:52:43 +00:00
} ,
}
2018-12-03 18:27:43 +00:00
ctrl := newFakeController ( & fakeData { apps : [ ] runtime . Object { app } } )
2018-12-04 10:52:57 +00:00
syncStatus := argoappv1 . SyncStatus {
2018-12-04 01:39:55 +00:00
Status : argoappv1 . SyncStatusCodeOutOfSync ,
2018-09-24 15:52:43 +00:00
Revision : "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" ,
}
app . Status . OperationState = & argoappv1 . OperationState {
Operation : argoappv1 . Operation {
Sync : & argoappv1 . SyncOperation {
2019-03-04 08:56:36 +00:00
Source : & argoappv1 . ApplicationSource {
Helm : & argoappv1 . ApplicationSourceHelm {
Parameters : [ ] argoappv1 . HelmParameter {
{
Name : "a" ,
Value : "2" , // this value changed
} ,
} ,
2018-09-24 15:52:43 +00:00
} ,
} ,
} ,
} ,
2020-05-15 17:01:18 +00:00
Phase : synccommon . OperationFailed ,
2018-09-24 15:52:43 +00:00
SyncResult : & argoappv1 . SyncOperationResult {
Revision : "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" ,
} ,
}
2020-03-18 21:19:54 +00:00
cond := ctrl . autoSync ( app , & syncStatus , [ ] argoappv1 . ResourceStatus { { Name : "guestbook" , Kind : kube . DeploymentKind , Status : argoappv1 . SyncStatusCodeOutOfSync } } )
2018-09-24 15:52:43 +00:00
assert . Nil ( t , cond )
2020-08-05 18:36:40 +00:00
app , err := ctrl . applicationClientset . ArgoprojV1alpha1 ( ) . Applications ( test . FakeArgoCDNamespace ) . Get ( context . Background ( ) , "my-app" , metav1 . GetOptions { } )
2018-09-24 15:52:43 +00:00
assert . NoError ( t , err )
assert . NotNil ( t , app . Operation )
}
2018-11-19 20:25:45 +00:00
// TestFinalizeAppDeletion verifies application deletion
func TestFinalizeAppDeletion ( t * testing . T ) {
2020-08-06 00:58:20 +00:00
defaultProj := argoappv1 . AppProject {
ObjectMeta : metav1 . ObjectMeta {
Name : "default" ,
Namespace : test . FakeArgoCDNamespace ,
} ,
Spec : argoappv1 . AppProjectSpec {
SourceRepos : [ ] string { "*" } ,
Destinations : [ ] argoappv1 . ApplicationDestination {
{
Server : "*" ,
Namespace : "*" ,
2019-12-09 16:39:20 +00:00
} ,
} ,
2020-08-06 00:58:20 +00:00
} ,
}
// Ensure app can be deleted cascading
t . Run ( "CascadingDelete" , func ( t * testing . T ) {
2019-12-09 16:39:20 +00:00
app := newFakeApp ( )
app . Spec . Destination . Namespace = test . FakeArgoCDNamespace
appObj := kube . MustToUnstructured ( & app )
ctrl := newFakeController ( & fakeData { apps : [ ] runtime . Object { app , & defaultProj } , managedLiveObjs : map [ kube . ResourceKey ] * unstructured . Unstructured {
kube . GetResourceKey ( appObj ) : appObj ,
} } )
2018-11-19 20:25:45 +00:00
2019-12-09 16:39:20 +00:00
patched := false
fakeAppCs := ctrl . applicationClientset . ( * appclientset . Clientset )
defaultReactor := fakeAppCs . ReactionChain [ 0 ]
fakeAppCs . ReactionChain = nil
fakeAppCs . AddReactor ( "get" , "*" , func ( action kubetesting . Action ) ( handled bool , ret runtime . Object , err error ) {
return defaultReactor . React ( action )
} )
fakeAppCs . AddReactor ( "patch" , "*" , func ( action kubetesting . Action ) ( handled bool , ret runtime . Object , err error ) {
patched = true
return true , nil , nil
} )
_ , err := ctrl . finalizeApplicationDeletion ( app )
assert . NoError ( t , err )
assert . True ( t , patched )
2020-08-06 00:58:20 +00:00
} )
2019-12-09 16:39:20 +00:00
2020-05-27 17:22:13 +00:00
// Ensure any stray resources irregularly labeled with instance label of app are not deleted upon deleting,
2019-12-09 16:39:20 +00:00
// when app project restriction is in place
2020-08-06 00:58:20 +00:00
t . Run ( "ProjectRestrictionEnforced" , func ( * testing . T ) {
2019-12-09 16:39:20 +00:00
restrictedProj := argoappv1 . AppProject {
ObjectMeta : metav1 . ObjectMeta {
Name : "restricted" ,
Namespace : test . FakeArgoCDNamespace ,
} ,
Spec : argoappv1 . AppProjectSpec {
SourceRepos : [ ] string { "*" } ,
Destinations : [ ] argoappv1 . ApplicationDestination {
{
Server : "*" ,
Namespace : "my-app" ,
} ,
} ,
} ,
}
app := newFakeApp ( )
app . Spec . Destination . Namespace = test . FakeArgoCDNamespace
app . Spec . Project = "restricted"
appObj := kube . MustToUnstructured ( & app )
cm := newFakeCM ( )
strayObj := kube . MustToUnstructured ( & cm )
ctrl := newFakeController ( & fakeData {
apps : [ ] runtime . Object { app , & defaultProj , & restrictedProj } ,
managedLiveObjs : map [ kube . ResourceKey ] * unstructured . Unstructured {
kube . GetResourceKey ( appObj ) : appObj ,
kube . GetResourceKey ( strayObj ) : strayObj ,
} ,
} )
patched := false
fakeAppCs := ctrl . applicationClientset . ( * appclientset . Clientset )
defaultReactor := fakeAppCs . ReactionChain [ 0 ]
fakeAppCs . ReactionChain = nil
fakeAppCs . AddReactor ( "get" , "*" , func ( action kubetesting . Action ) ( handled bool , ret runtime . Object , err error ) {
return defaultReactor . React ( action )
} )
fakeAppCs . AddReactor ( "patch" , "*" , func ( action kubetesting . Action ) ( handled bool , ret runtime . Object , err error ) {
patched = true
return true , nil , nil
} )
objs , err := ctrl . finalizeApplicationDeletion ( app )
assert . NoError ( t , err )
assert . True ( t , patched )
objsMap , err := ctrl . stateCache . GetManagedLiveObjs ( app , [ ] * unstructured . Unstructured { } )
if err != nil {
assert . NoError ( t , err )
}
// Managed objects must be empty
assert . Empty ( t , objsMap )
// Loop through all deleted objects, ensure that test-cm is none of them
for _ , o := range objs {
assert . NotEqual ( t , "test-cm" , o . GetName ( ) )
}
2020-08-06 00:58:20 +00:00
} )
t . Run ( "DeleteWithDestinationClusterName" , func ( t * testing . T ) {
app := newFakeApp ( )
app . Spec . Destination . Namespace = test . FakeArgoCDNamespace
app . Spec . Destination . Name = "minikube"
app . Spec . Destination . Server = ""
appObj := kube . MustToUnstructured ( & app )
ctrl := newFakeController ( & fakeData { apps : [ ] runtime . Object { app , & defaultProj } , managedLiveObjs : map [ kube . ResourceKey ] * unstructured . Unstructured {
kube . GetResourceKey ( appObj ) : appObj ,
} } )
patched := false
fakeAppCs := ctrl . applicationClientset . ( * appclientset . Clientset )
defaultReactor := fakeAppCs . ReactionChain [ 0 ]
fakeAppCs . ReactionChain = nil
fakeAppCs . AddReactor ( "get" , "*" , func ( action kubetesting . Action ) ( handled bool , ret runtime . Object , err error ) {
return defaultReactor . React ( action )
} )
fakeAppCs . AddReactor ( "patch" , "*" , func ( action kubetesting . Action ) ( handled bool , ret runtime . Object , err error ) {
patched = true
return true , nil , nil
} )
_ , err := ctrl . finalizeApplicationDeletion ( app )
assert . NoError ( t , err )
assert . True ( t , patched )
} )
2018-11-30 21:50:27 +00:00
}
2018-11-19 20:25:45 +00:00
2018-11-30 21:50:27 +00:00
// TestNormalizeApplication verifies we normalize an application during reconciliation
func TestNormalizeApplication ( t * testing . T ) {
2019-03-04 08:56:36 +00:00
defaultProj := argoappv1 . AppProject {
ObjectMeta : metav1 . ObjectMeta {
Name : "default" ,
Namespace : test . FakeArgoCDNamespace ,
} ,
Spec : argoappv1 . AppProjectSpec {
SourceRepos : [ ] string { "*" } ,
Destinations : [ ] argoappv1 . ApplicationDestination {
{
Server : "*" ,
Namespace : "*" ,
} ,
} ,
} ,
}
2018-11-30 21:50:27 +00:00
app := newFakeApp ( )
app . Spec . Project = ""
2019-03-04 08:56:36 +00:00
app . Spec . Source . Kustomize = & argoappv1 . ApplicationSourceKustomize { NamePrefix : "foo-" }
data := fakeData {
apps : [ ] runtime . Object { app , & defaultProj } ,
2019-07-13 00:17:23 +00:00
manifestResponse : & apiclient . ManifestResponse {
2019-03-04 08:56:36 +00:00
Manifests : [ ] string { } ,
Namespace : test . FakeDestNamespace ,
Server : test . FakeClusterURL ,
Revision : "abc123" ,
} ,
managedLiveObjs : make ( map [ kube . ResourceKey ] * unstructured . Unstructured ) ,
}
2018-11-30 21:50:27 +00:00
2019-03-04 08:56:36 +00:00
{
// Verify we normalize the app because project is missing
ctrl := newFakeController ( & data )
key , _ := cache . MetaNamespaceKeyFunc ( app )
ctrl . appRefreshQueue . Add ( key )
fakeAppCs := ctrl . applicationClientset . ( * appclientset . Clientset )
fakeAppCs . ReactionChain = nil
normalized := false
fakeAppCs . AddReactor ( "patch" , "*" , func ( action kubetesting . Action ) ( handled bool , ret runtime . Object , err error ) {
if patchAction , ok := action . ( kubetesting . PatchAction ) ; ok {
if string ( patchAction . GetPatch ( ) ) == ` { "spec": { "project":"default"}} ` {
normalized = true
}
2018-11-30 21:50:27 +00:00
}
2019-03-04 08:56:36 +00:00
return true , nil , nil
} )
ctrl . processAppRefreshQueueItem ( )
assert . True ( t , normalized )
}
2018-11-30 21:50:27 +00:00
2019-03-04 08:56:36 +00:00
{
// Verify we don't unnecessarily normalize app when project is set
app . Spec . Project = "default"
data . apps [ 0 ] = app
ctrl := newFakeController ( & data )
key , _ := cache . MetaNamespaceKeyFunc ( app )
ctrl . appRefreshQueue . Add ( key )
fakeAppCs := ctrl . applicationClientset . ( * appclientset . Clientset )
fakeAppCs . ReactionChain = nil
normalized := false
fakeAppCs . AddReactor ( "patch" , "*" , func ( action kubetesting . Action ) ( handled bool , ret runtime . Object , err error ) {
if patchAction , ok := action . ( kubetesting . PatchAction ) ; ok {
if string ( patchAction . GetPatch ( ) ) == ` { "spec": { "project":"default"}} ` {
normalized = true
}
2018-11-30 21:50:27 +00:00
}
2019-03-04 08:56:36 +00:00
return true , nil , nil
} )
ctrl . processAppRefreshQueueItem ( )
assert . False ( t , normalized )
}
2018-11-19 20:25:45 +00:00
}
2019-05-01 16:42:45 +00:00
func TestHandleAppUpdated ( t * testing . T ) {
app := newFakeApp ( )
2019-05-06 19:49:29 +00:00
app . Spec . Destination . Namespace = test . FakeArgoCDNamespace
app . Spec . Destination . Server = common . KubernetesInternalAPIServerAddr
2019-05-01 16:42:45 +00:00
ctrl := newFakeController ( & fakeData { apps : [ ] runtime . Object { app } } )
2019-08-19 15:14:48 +00:00
ctrl . handleObjectUpdated ( map [ string ] bool { app . Name : true } , kube . GetObjectRef ( kube . MustToUnstructured ( app ) ) )
2019-07-15 20:34:26 +00:00
isRequested , level := ctrl . isRefreshRequested ( app . Name )
2019-05-01 16:42:45 +00:00
assert . False ( t , isRequested )
2019-07-15 20:34:26 +00:00
assert . Equal ( t , ComparisonWithNothing , level )
2019-05-01 16:42:45 +00:00
2019-08-19 15:14:48 +00:00
ctrl . handleObjectUpdated ( map [ string ] bool { app . Name : true } , corev1 . ObjectReference { UID : "test" , Kind : kube . DeploymentKind , Name : "test" , Namespace : "default" } )
2019-07-15 20:34:26 +00:00
isRequested , level = ctrl . isRefreshRequested ( app . Name )
2019-05-01 16:42:45 +00:00
assert . True ( t , isRequested )
2019-07-15 20:34:26 +00:00
assert . Equal ( t , CompareWithRecent , level )
2019-05-01 16:42:45 +00:00
}
2019-05-30 19:39:54 +00:00
2019-08-19 15:14:48 +00:00
func TestHandleOrphanedResourceUpdated ( t * testing . T ) {
app1 := newFakeApp ( )
app1 . Name = "app1"
app1 . Spec . Destination . Namespace = test . FakeArgoCDNamespace
app1 . Spec . Destination . Server = common . KubernetesInternalAPIServerAddr
app2 := newFakeApp ( )
app2 . Name = "app2"
app2 . Spec . Destination . Namespace = test . FakeArgoCDNamespace
app2 . Spec . Destination . Server = common . KubernetesInternalAPIServerAddr
proj := defaultProj . DeepCopy ( )
proj . Spec . OrphanedResources = & argoappv1 . OrphanedResourcesMonitorSettings { }
ctrl := newFakeController ( & fakeData { apps : [ ] runtime . Object { app1 , app2 , proj } } )
ctrl . handleObjectUpdated ( map [ string ] bool { } , corev1 . ObjectReference { UID : "test" , Kind : kube . DeploymentKind , Name : "test" , Namespace : test . FakeArgoCDNamespace } )
isRequested , level := ctrl . isRefreshRequested ( app1 . Name )
assert . True ( t , isRequested )
assert . Equal ( t , ComparisonWithNothing , level )
isRequested , level = ctrl . isRefreshRequested ( app2 . Name )
assert . True ( t , isRequested )
assert . Equal ( t , ComparisonWithNothing , level )
}
2020-07-21 23:59:00 +00:00
func TestGetResourceTree_HasOrphanedResources ( t * testing . T ) {
app := newFakeApp ( )
proj := defaultProj . DeepCopy ( )
proj . Spec . OrphanedResources = & argoappv1 . OrphanedResourcesMonitorSettings { }
managedDeploy := argoappv1 . ResourceNode {
ResourceRef : argoappv1 . ResourceRef { Group : "apps" , Kind : "Deployment" , Namespace : "default" , Name : "nginx-deployment" , Version : "v1" } ,
}
orphanedDeploy1 := argoappv1 . ResourceNode {
ResourceRef : argoappv1 . ResourceRef { Group : "apps" , Kind : "Deployment" , Namespace : "default" , Name : "deploy1" } ,
}
orphanedDeploy2 := argoappv1 . ResourceNode {
ResourceRef : argoappv1 . ResourceRef { Group : "apps" , Kind : "Deployment" , Namespace : "default" , Name : "deploy2" } ,
}
ctrl := newFakeController ( & fakeData {
apps : [ ] runtime . Object { app , proj } ,
namespacedResources : map [ kube . ResourceKey ] namespacedResource {
kube . NewResourceKey ( "apps" , "Deployment" , "default" , "nginx-deployment" ) : { ResourceNode : managedDeploy } ,
kube . NewResourceKey ( "apps" , "Deployment" , "default" , "deploy1" ) : { ResourceNode : orphanedDeploy1 } ,
kube . NewResourceKey ( "apps" , "Deployment" , "default" , "deploy2" ) : { ResourceNode : orphanedDeploy2 } ,
} ,
} )
tree , err := ctrl . getResourceTree ( app , [ ] * argoappv1 . ResourceDiff { {
Namespace : "default" ,
Name : "nginx-deployment" ,
Kind : "Deployment" ,
Group : "apps" ,
LiveState : "null" ,
TargetState : test . DeploymentManifest ,
} } )
assert . NoError ( t , err )
assert . Equal ( t , tree . Nodes , [ ] argoappv1 . ResourceNode { managedDeploy } )
assert . Equal ( t , tree . OrphanedNodes , [ ] argoappv1 . ResourceNode { orphanedDeploy1 , orphanedDeploy2 } )
}
2019-05-30 19:39:54 +00:00
func TestSetOperationStateOnDeletedApp ( t * testing . T ) {
ctrl := newFakeController ( & fakeData { apps : [ ] runtime . Object { } } )
fakeAppCs := ctrl . applicationClientset . ( * appclientset . Clientset )
fakeAppCs . ReactionChain = nil
patched := false
fakeAppCs . AddReactor ( "patch" , "*" , func ( action kubetesting . Action ) ( handled bool , ret runtime . Object , err error ) {
patched = true
return true , nil , apierr . NewNotFound ( schema . GroupResource { } , "my-app" )
} )
2020-05-15 17:01:18 +00:00
ctrl . setOperationState ( newFakeApp ( ) , & argoappv1 . OperationState { Phase : synccommon . OperationSucceeded } )
2019-05-30 19:39:54 +00:00
assert . True ( t , patched )
}
2019-07-15 20:34:26 +00:00
func TestNeedRefreshAppStatus ( t * testing . T ) {
ctrl := newFakeController ( & fakeData { apps : [ ] runtime . Object { } } )
app := newFakeApp ( )
now := metav1 . Now ( )
app . Status . ReconciledAt = & now
app . Status . Sync = argoappv1 . SyncStatus {
Status : argoappv1 . SyncStatusCodeSynced ,
ComparedTo : argoappv1 . ComparedTo {
Source : app . Spec . Source ,
Destination : app . Spec . Destination ,
} ,
}
// no need to refresh just reconciled application
needRefresh , _ , _ := ctrl . needRefreshAppStatus ( app , 1 * time . Hour )
assert . False ( t , needRefresh )
// refresh app using the 'deepest' requested comparison level
2020-01-08 22:07:36 +00:00
ctrl . requestAppRefresh ( app . Name , CompareWithRecent . Pointer ( ) , nil )
ctrl . requestAppRefresh ( app . Name , ComparisonWithNothing . Pointer ( ) , nil )
2019-07-15 20:34:26 +00:00
needRefresh , refreshType , compareWith := ctrl . needRefreshAppStatus ( app , 1 * time . Hour )
assert . True ( t , needRefresh )
assert . Equal ( t , argoappv1 . RefreshTypeNormal , refreshType )
assert . Equal ( t , CompareWithRecent , compareWith )
// refresh application which status is not reconciled using latest commit
app . Status . Sync = argoappv1 . SyncStatus { Status : argoappv1 . SyncStatusCodeUnknown }
needRefresh , refreshType , compareWith = ctrl . needRefreshAppStatus ( app , 1 * time . Hour )
assert . True ( t , needRefresh )
assert . Equal ( t , argoappv1 . RefreshTypeNormal , refreshType )
assert . Equal ( t , CompareWithLatest , compareWith )
2019-09-23 17:31:59 +00:00
{
// refresh app using the 'latest' level if comparison expired
app := app . DeepCopy ( )
2020-01-08 22:07:36 +00:00
ctrl . requestAppRefresh ( app . Name , CompareWithRecent . Pointer ( ) , nil )
2019-09-23 17:31:59 +00:00
reconciledAt := metav1 . NewTime ( time . Now ( ) . UTC ( ) . Add ( - 1 * time . Hour ) )
app . Status . ReconciledAt = & reconciledAt
needRefresh , refreshType , compareWith = ctrl . needRefreshAppStatus ( app , 1 * time . Minute )
assert . True ( t , needRefresh )
assert . Equal ( t , argoappv1 . RefreshTypeNormal , refreshType )
assert . Equal ( t , CompareWithLatest , compareWith )
2019-07-15 20:34:26 +00:00
}
2019-09-23 17:31:59 +00:00
{
app := app . DeepCopy ( )
// execute hard refresh if app has refresh annotation
reconciledAt := metav1 . NewTime ( time . Now ( ) . UTC ( ) . Add ( - 1 * time . Hour ) )
app . Status . ReconciledAt = & reconciledAt
app . Annotations = map [ string ] string {
common . AnnotationKeyRefresh : string ( argoappv1 . RefreshTypeHard ) ,
}
needRefresh , refreshType , compareWith = ctrl . needRefreshAppStatus ( app , 1 * time . Hour )
assert . True ( t , needRefresh )
assert . Equal ( t , argoappv1 . RefreshTypeHard , refreshType )
assert . Equal ( t , CompareWithLatest , compareWith )
}
2019-10-22 22:23:15 +00:00
{
app := app . DeepCopy ( )
// ensure that CompareWithLatest level is used if application source has changed
2020-01-08 22:07:36 +00:00
ctrl . requestAppRefresh ( app . Name , ComparisonWithNothing . Pointer ( ) , nil )
2019-10-22 22:23:15 +00:00
// sample app source change
app . Spec . Source . Helm = & argoappv1 . ApplicationSourceHelm {
Parameters : [ ] argoappv1 . HelmParameter { {
Name : "foo" ,
Value : "bar" ,
} } ,
}
needRefresh , refreshType , compareWith = ctrl . needRefreshAppStatus ( app , 1 * time . Hour )
assert . True ( t , needRefresh )
assert . Equal ( t , argoappv1 . RefreshTypeNormal , refreshType )
assert . Equal ( t , CompareWithLatest , compareWith )
}
2019-07-15 20:34:26 +00:00
}
2019-10-11 00:26:53 +00:00
func TestRefreshAppConditions ( t * testing . T ) {
defaultProj := argoappv1 . AppProject {
ObjectMeta : metav1 . ObjectMeta {
Name : "default" ,
Namespace : test . FakeArgoCDNamespace ,
} ,
Spec : argoappv1 . AppProjectSpec {
SourceRepos : [ ] string { "*" } ,
Destinations : [ ] argoappv1 . ApplicationDestination {
{
Server : "*" ,
Namespace : "*" ,
} ,
} ,
} ,
}
t . Run ( "NoErrorConditions" , func ( t * testing . T ) {
app := newFakeApp ( )
ctrl := newFakeController ( & fakeData { apps : [ ] runtime . Object { app , & defaultProj } } )
2019-12-26 22:08:14 +00:00
_ , hasErrors := ctrl . refreshAppConditions ( app )
2019-10-11 00:26:53 +00:00
assert . False ( t , hasErrors )
assert . Len ( t , app . Status . Conditions , 0 )
} )
t . Run ( "PreserveExistingWarningCondition" , func ( t * testing . T ) {
app := newFakeApp ( )
app . Status . SetConditions ( [ ] argoappv1 . ApplicationCondition { { Type : argoappv1 . ApplicationConditionExcludedResourceWarning } } , nil )
ctrl := newFakeController ( & fakeData { apps : [ ] runtime . Object { app , & defaultProj } } )
2019-12-26 22:08:14 +00:00
_ , hasErrors := ctrl . refreshAppConditions ( app )
2019-10-11 00:26:53 +00:00
assert . False ( t , hasErrors )
assert . Len ( t , app . Status . Conditions , 1 )
assert . Equal ( t , argoappv1 . ApplicationConditionExcludedResourceWarning , app . Status . Conditions [ 0 ] . Type )
} )
t . Run ( "ReplacesSpecErrorCondition" , func ( t * testing . T ) {
app := newFakeApp ( )
app . Spec . Project = "wrong project"
app . Status . SetConditions ( [ ] argoappv1 . ApplicationCondition { { Type : argoappv1 . ApplicationConditionInvalidSpecError , Message : "old message" } } , nil )
ctrl := newFakeController ( & fakeData { apps : [ ] runtime . Object { app , & defaultProj } } )
2019-12-26 22:08:14 +00:00
_ , hasErrors := ctrl . refreshAppConditions ( app )
2019-10-11 00:26:53 +00:00
assert . True ( t , hasErrors )
assert . Len ( t , app . Status . Conditions , 1 )
assert . Equal ( t , argoappv1 . ApplicationConditionInvalidSpecError , app . Status . Conditions [ 0 ] . Type )
assert . Equal ( t , "Application referencing project wrong project which does not exist" , app . Status . Conditions [ 0 ] . Message )
} )
2020-06-20 23:12:46 +00:00
t . Run ( "NoErrorConditionsWithDestNameOnly" , func ( t * testing . T ) {
app := newFakeAppWithDestName ( )
ctrl := newFakeController ( & fakeData { apps : [ ] runtime . Object { app , & defaultProj } } )
_ , hasErrors := ctrl . refreshAppConditions ( app )
assert . False ( t , hasErrors )
assert . Len ( t , app . Status . Conditions , 0 )
} )
t . Run ( "ErrorOnBothDestNameAndServer" , func ( t * testing . T ) {
app := newFakeAppWithDestMismatch ( )
ctrl := newFakeController ( & fakeData { apps : [ ] runtime . Object { app , & defaultProj } } )
_ , hasErrors := ctrl . refreshAppConditions ( app )
assert . True ( t , hasErrors )
assert . Len ( t , app . Status . Conditions , 1 )
assert . Equal ( t , argoappv1 . ApplicationConditionInvalidSpecError , app . Status . Conditions [ 0 ] . Type )
assert . Equal ( t , "application destination can't have both name and server defined: another-cluster https://localhost:6443" , app . Status . Conditions [ 0 ] . Message )
} )
2019-10-11 00:26:53 +00:00
}
2019-10-28 23:44:23 +00:00
func TestUpdateReconciledAt ( t * testing . T ) {
app := newFakeApp ( )
reconciledAt := metav1 . NewTime ( time . Now ( ) . Add ( - 1 * time . Second ) )
app . Status = argoappv1 . ApplicationStatus { ReconciledAt : & reconciledAt }
app . Status . Sync = argoappv1 . SyncStatus { ComparedTo : argoappv1 . ComparedTo { Source : app . Spec . Source , Destination : app . Spec . Destination } }
ctrl := newFakeController ( & fakeData {
apps : [ ] runtime . Object { app , & defaultProj } ,
manifestResponse : & apiclient . ManifestResponse {
Manifests : [ ] string { } ,
Namespace : test . FakeDestNamespace ,
Server : test . FakeClusterURL ,
Revision : "abc123" ,
} ,
managedLiveObjs : make ( map [ kube . ResourceKey ] * unstructured . Unstructured ) ,
} )
key , _ := cache . MetaNamespaceKeyFunc ( app )
fakeAppCs := ctrl . applicationClientset . ( * appclientset . Clientset )
fakeAppCs . ReactionChain = nil
receivedPatch := map [ string ] interface { } { }
fakeAppCs . AddReactor ( "patch" , "*" , func ( action kubetesting . Action ) ( handled bool , ret runtime . Object , err error ) {
if patchAction , ok := action . ( kubetesting . PatchAction ) ; ok {
assert . NoError ( t , json . Unmarshal ( patchAction . GetPatch ( ) , & receivedPatch ) )
}
return true , nil , nil
} )
t . Run ( "UpdatedOnFullReconciliation" , func ( t * testing . T ) {
receivedPatch = map [ string ] interface { } { }
2020-01-08 22:07:36 +00:00
ctrl . requestAppRefresh ( app . Name , CompareWithLatest . Pointer ( ) , nil )
2019-10-28 23:44:23 +00:00
ctrl . appRefreshQueue . Add ( key )
ctrl . processAppRefreshQueueItem ( )
_ , updated , err := unstructured . NestedString ( receivedPatch , "status" , "reconciledAt" )
assert . NoError ( t , err )
assert . True ( t , updated )
_ , updated , err = unstructured . NestedString ( receivedPatch , "status" , "observedAt" )
assert . NoError ( t , err )
assert . True ( t , updated )
} )
t . Run ( "NotUpdatedOnPartialReconciliation" , func ( t * testing . T ) {
receivedPatch = map [ string ] interface { } { }
ctrl . appRefreshQueue . Add ( key )
2020-01-08 22:07:36 +00:00
ctrl . requestAppRefresh ( app . Name , CompareWithRecent . Pointer ( ) , nil )
2019-10-28 23:44:23 +00:00
ctrl . processAppRefreshQueueItem ( )
_ , updated , err := unstructured . NestedString ( receivedPatch , "status" , "reconciledAt" )
assert . NoError ( t , err )
assert . False ( t , updated )
_ , updated , err = unstructured . NestedString ( receivedPatch , "status" , "observedAt" )
assert . NoError ( t , err )
assert . True ( t , updated )
} )
}
2020-07-22 00:25:41 +00:00
func TestFinalizeProjectDeletion_HasApplications ( t * testing . T ) {
app := newFakeApp ( )
proj := & argoappv1 . AppProject { ObjectMeta : metav1 . ObjectMeta { Name : "default" , Namespace : test . FakeArgoCDNamespace } }
ctrl := newFakeController ( & fakeData { apps : [ ] runtime . Object { app , proj } } )
fakeAppCs := ctrl . applicationClientset . ( * appclientset . Clientset )
patched := false
fakeAppCs . PrependReactor ( "patch" , "*" , func ( action kubetesting . Action ) ( handled bool , ret runtime . Object , err error ) {
patched = true
return true , nil , nil
} )
err := ctrl . finalizeProjectDeletion ( proj )
assert . NoError ( t , err )
assert . False ( t , patched )
}
func TestFinalizeProjectDeletion_DoesNotHaveApplications ( t * testing . T ) {
proj := & argoappv1 . AppProject { ObjectMeta : metav1 . ObjectMeta { Name : "default" , Namespace : test . FakeArgoCDNamespace } }
ctrl := newFakeController ( & fakeData { apps : [ ] runtime . Object { & defaultProj } } )
fakeAppCs := ctrl . applicationClientset . ( * appclientset . Clientset )
receivedPatch := map [ string ] interface { } { }
fakeAppCs . PrependReactor ( "patch" , "*" , func ( action kubetesting . Action ) ( handled bool , ret runtime . Object , err error ) {
if patchAction , ok := action . ( kubetesting . PatchAction ) ; ok {
assert . NoError ( t , json . Unmarshal ( patchAction . GetPatch ( ) , & receivedPatch ) )
}
return true , nil , nil
} )
err := ctrl . finalizeProjectDeletion ( proj )
assert . NoError ( t , err )
assert . Equal ( t , map [ string ] interface { } {
"metadata" : map [ string ] interface { } {
"finalizers" : nil ,
} ,
} , receivedPatch )
}
2020-07-28 17:14:17 +00:00
func TestProcessRequestedAppOperation_FailedNoRetries ( t * testing . T ) {
app := newFakeApp ( )
app . Spec . Project = "invalid-project"
app . Operation = & argoappv1 . Operation {
Sync : & argoappv1 . SyncOperation { } ,
}
ctrl := newFakeController ( & fakeData { apps : [ ] runtime . Object { app } } )
fakeAppCs := ctrl . applicationClientset . ( * appclientset . Clientset )
receivedPatch := map [ string ] interface { } { }
fakeAppCs . PrependReactor ( "patch" , "*" , func ( action kubetesting . Action ) ( handled bool , ret runtime . Object , err error ) {
if patchAction , ok := action . ( kubetesting . PatchAction ) ; ok {
assert . NoError ( t , json . Unmarshal ( patchAction . GetPatch ( ) , & receivedPatch ) )
}
return true , nil , nil
} )
ctrl . processRequestedAppOperation ( app )
phase , _ , _ := unstructured . NestedString ( receivedPatch , "status" , "operationState" , "phase" )
assert . Equal ( t , string ( synccommon . OperationError ) , phase )
}
func TestProcessRequestedAppOperation_FailedHasRetries ( t * testing . T ) {
app := newFakeApp ( )
app . Spec . Project = "invalid-project"
app . Operation = & argoappv1 . Operation {
Sync : & argoappv1 . SyncOperation { } ,
Retry : argoappv1 . RetryStrategy { Limit : 1 } ,
}
ctrl := newFakeController ( & fakeData { apps : [ ] runtime . Object { app } } )
fakeAppCs := ctrl . applicationClientset . ( * appclientset . Clientset )
receivedPatch := map [ string ] interface { } { }
fakeAppCs . PrependReactor ( "patch" , "*" , func ( action kubetesting . Action ) ( handled bool , ret runtime . Object , err error ) {
if patchAction , ok := action . ( kubetesting . PatchAction ) ; ok {
assert . NoError ( t , json . Unmarshal ( patchAction . GetPatch ( ) , & receivedPatch ) )
}
return true , nil , nil
} )
ctrl . processRequestedAppOperation ( app )
phase , _ , _ := unstructured . NestedString ( receivedPatch , "status" , "operationState" , "phase" )
assert . Equal ( t , string ( synccommon . OperationRunning ) , phase )
message , _ , _ := unstructured . NestedString ( receivedPatch , "status" , "operationState" , "message" )
assert . Contains ( t , message , "Retrying attempt #1" )
retryCount , _ , _ := unstructured . NestedFloat64 ( receivedPatch , "status" , "operationState" , "retryCount" )
assert . Equal ( t , float64 ( 1 ) , retryCount )
}
func TestProcessRequestedAppOperation_RunningPreviouslyFailed ( t * testing . T ) {
app := newFakeApp ( )
app . Operation = & argoappv1 . Operation {
Sync : & argoappv1 . SyncOperation { } ,
Retry : argoappv1 . RetryStrategy { Limit : 1 } ,
}
app . Status . OperationState . Phase = synccommon . OperationRunning
app . Status . OperationState . SyncResult . Resources = [ ] * argoappv1 . ResourceResult { {
Name : "guestbook" ,
Kind : "Deployment" ,
Group : "apps" ,
Status : synccommon . ResultCodeSyncFailed ,
} }
data := & fakeData {
apps : [ ] runtime . Object { app , & defaultProj } ,
manifestResponse : & apiclient . ManifestResponse {
Manifests : [ ] string { } ,
Namespace : test . FakeDestNamespace ,
Server : test . FakeClusterURL ,
Revision : "abc123" ,
} ,
}
ctrl := newFakeController ( data )
fakeAppCs := ctrl . applicationClientset . ( * appclientset . Clientset )
receivedPatch := map [ string ] interface { } { }
fakeAppCs . PrependReactor ( "patch" , "*" , func ( action kubetesting . Action ) ( handled bool , ret runtime . Object , err error ) {
if patchAction , ok := action . ( kubetesting . PatchAction ) ; ok {
assert . NoError ( t , json . Unmarshal ( patchAction . GetPatch ( ) , & receivedPatch ) )
}
return true , nil , nil
} )
ctrl . processRequestedAppOperation ( app )
phase , _ , _ := unstructured . NestedString ( receivedPatch , "status" , "operationState" , "phase" )
assert . Equal ( t , string ( synccommon . OperationSucceeded ) , phase )
}
2020-08-06 00:58:59 +00:00
func TestProcessRequestedAppOperation_HasRetriesTerminated ( t * testing . T ) {
app := newFakeApp ( )
app . Operation = & argoappv1 . Operation {
Sync : & argoappv1 . SyncOperation { } ,
Retry : argoappv1 . RetryStrategy { Limit : 10 } ,
}
app . Status . OperationState . Phase = synccommon . OperationTerminating
data := & fakeData {
apps : [ ] runtime . Object { app , & defaultProj } ,
manifestResponse : & apiclient . ManifestResponse {
Manifests : [ ] string { } ,
Namespace : test . FakeDestNamespace ,
Server : test . FakeClusterURL ,
Revision : "abc123" ,
} ,
}
ctrl := newFakeController ( data )
fakeAppCs := ctrl . applicationClientset . ( * appclientset . Clientset )
receivedPatch := map [ string ] interface { } { }
fakeAppCs . PrependReactor ( "patch" , "*" , func ( action kubetesting . Action ) ( handled bool , ret runtime . Object , err error ) {
if patchAction , ok := action . ( kubetesting . PatchAction ) ; ok {
assert . NoError ( t , json . Unmarshal ( patchAction . GetPatch ( ) , & receivedPatch ) )
}
return true , nil , nil
} )
ctrl . processRequestedAppOperation ( app )
phase , _ , _ := unstructured . NestedString ( receivedPatch , "status" , "operationState" , "phase" )
assert . Equal ( t , string ( synccommon . OperationFailed ) , phase )
}