2018-07-07 07:54:06 +00:00
package controller
import (
2018-10-05 17:18:12 +00:00
"context"
2018-09-10 17:14:14 +00:00
"fmt"
"sort"
2018-07-07 07:54:06 +00:00
"testing"
2018-10-05 17:18:12 +00:00
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/watch"
2018-09-20 16:48:54 +00:00
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/util/kube"
2018-07-07 07:54:06 +00:00
log "github.com/sirupsen/logrus"
2018-09-10 17:14:14 +00:00
"github.com/stretchr/testify/assert"
2018-09-20 16:48:54 +00:00
2018-09-10 17:14:14 +00:00
apiv1 "k8s.io/api/core/v1"
2018-09-20 16:48:54 +00:00
"k8s.io/apimachinery/pkg/apis/meta/v1"
2018-09-10 17:14:14 +00:00
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2018-09-20 16:48:54 +00:00
fakedisco "k8s.io/client-go/discovery/fake"
2018-07-07 07:54:06 +00:00
"k8s.io/client-go/rest"
2018-09-20 16:48:54 +00:00
testcore "k8s.io/client-go/testing"
2018-07-07 07:54:06 +00:00
)
2018-09-10 17:14:14 +00:00
type kubectlOutput struct {
output string
err error
}
type mockKubectlCmd struct {
commands map [ string ] kubectlOutput
2018-10-05 17:18:12 +00:00
events chan watch . Event
}
func ( k mockKubectlCmd ) WatchResources (
ctx context . Context , config * rest . Config , namespace string , selector func ( kind schema . GroupVersionKind ) v1 . ListOptions ) ( chan watch . Event , error ) {
return k . events , nil
2018-09-10 17:14:14 +00:00
}
func ( k mockKubectlCmd ) DeleteResource ( config * rest . Config , obj * unstructured . Unstructured , namespace string ) error {
command , ok := k . commands [ obj . GetName ( ) ]
if ! ok {
return nil
}
return command . err
}
func ( k mockKubectlCmd ) ApplyResource ( config * rest . Config , obj * unstructured . Unstructured , namespace string , dryRun , force bool ) ( string , error ) {
command , ok := k . commands [ obj . GetName ( ) ]
if ! ok {
return "" , nil
}
return command . output , command . err
}
// ConvertToVersion converts an unstructured object into the specified group/version
func ( k mockKubectlCmd ) ConvertToVersion ( obj * unstructured . Unstructured , group , version string ) ( * unstructured . Unstructured , error ) {
return obj , nil
}
2018-09-20 16:48:54 +00:00
func newTestSyncCtx ( resources ... * v1 . APIResourceList ) * syncContext {
fakeDisco := & fakedisco . FakeDiscovery { Fake : & testcore . Fake { } }
fakeDisco . Resources = append ( resources , & v1 . APIResourceList {
APIResources : [ ] v1 . APIResource {
{ Kind : "pod" , Namespaced : true } ,
{ Kind : "deployment" , Namespaced : true } ,
{ Kind : "service" , Namespaced : true } ,
} ,
} )
kube . FlushServerResourcesCache ( )
2018-07-07 07:54:06 +00:00
return & syncContext {
comparison : & v1alpha1 . ComparisonResult { } ,
config : & rest . Config { } ,
namespace : "test-namespace" ,
2018-09-10 17:14:14 +00:00
syncRes : & v1alpha1 . SyncOperationResult { } ,
syncOp : & v1alpha1 . SyncOperation {
Prune : true ,
SyncStrategy : & v1alpha1 . SyncStrategy {
Apply : & v1alpha1 . SyncStrategyApply { } ,
} ,
} ,
2018-09-20 16:48:54 +00:00
proj : & v1alpha1 . AppProject {
ObjectMeta : v1 . ObjectMeta {
Name : "test" ,
} ,
Spec : v1alpha1 . AppProjectSpec {
ClusterResourceWhitelist : [ ] v1 . GroupKind {
{ Group : "*" , Kind : "*" } ,
} ,
} ,
} ,
2018-09-10 17:14:14 +00:00
opState : & v1alpha1 . OperationState { } ,
2018-09-20 16:48:54 +00:00
disco : fakeDisco ,
2018-09-10 17:14:14 +00:00
log : log . WithFields ( log . Fields { "application" : "fake-app" } ) ,
2018-07-07 07:54:06 +00:00
}
}
2018-09-10 17:14:14 +00:00
func TestSyncCreateInSortedOrder ( t * testing . T ) {
syncCtx := newTestSyncCtx ( )
syncCtx . kubectl = mockKubectlCmd { }
2018-11-17 01:10:04 +00:00
syncCtx . resources = [ ] v1alpha1 . ResourceState { {
LiveState : "" ,
TargetState : "{\"kind\":\"pod\"}" ,
} , {
LiveState : "" ,
TargetState : "{\"kind\":\"service\"}" ,
} }
2018-09-10 17:14:14 +00:00
syncCtx . sync ( )
assert . Len ( t , syncCtx . syncRes . Resources , 2 )
for i := range syncCtx . syncRes . Resources {
if syncCtx . syncRes . Resources [ i ] . Kind == "pod" {
assert . Equal ( t , v1alpha1 . ResourceDetailsSynced , syncCtx . syncRes . Resources [ i ] . Status )
} else if syncCtx . syncRes . Resources [ i ] . Kind == "service" {
assert . Equal ( t , v1alpha1 . ResourceDetailsSynced , syncCtx . syncRes . Resources [ i ] . Status )
} else {
t . Error ( "Resource isn't a pod or a service" )
}
}
syncCtx . sync ( )
assert . Equal ( t , syncCtx . opState . Phase , v1alpha1 . OperationSucceeded )
}
2018-09-20 16:48:54 +00:00
func TestSyncCreateNotWhitelistedClusterResources ( t * testing . T ) {
syncCtx := newTestSyncCtx ( & v1 . APIResourceList {
GroupVersion : v1alpha1 . SchemeGroupVersion . String ( ) ,
APIResources : [ ] v1 . APIResource {
{ Name : "workflows" , Namespaced : false , Kind : "Workflow" , Group : "argoproj.io" } ,
{ Name : "application" , Namespaced : false , Kind : "Application" , Group : "argoproj.io" } ,
} ,
} , & v1 . APIResourceList {
GroupVersion : "rbac.authorization.k8s.io/v1" ,
APIResources : [ ] v1 . APIResource {
{ Name : "clusterroles" , Namespaced : false , Kind : "ClusterRole" , Group : "rbac.authorization.k8s.io" } ,
} ,
} )
syncCtx . proj . Spec . ClusterResourceWhitelist = [ ] v1 . GroupKind {
{ Group : "argoproj.io" , Kind : "*" } ,
}
syncCtx . kubectl = mockKubectlCmd { }
2018-11-17 01:10:04 +00:00
syncCtx . resources = [ ] v1alpha1 . ResourceState { {
LiveState : "" ,
TargetState : ` { "apiVersion": "rbac.authorization.k8s.io/v1", "kind": "ClusterRole", "metadata": { "name": "argo-ui-cluster-role" }} ` ,
} }
2018-09-20 16:48:54 +00:00
syncCtx . sync ( )
assert . Len ( t , syncCtx . syncRes . Resources , 1 )
assert . Equal ( t , v1alpha1 . ResourceDetailsSyncFailed , syncCtx . syncRes . Resources [ 0 ] . Status )
assert . Contains ( t , syncCtx . syncRes . Resources [ 0 ] . Message , "not permitted in project" )
}
func TestSyncBlacklistedNamespacedResources ( t * testing . T ) {
syncCtx := newTestSyncCtx ( )
syncCtx . proj . Spec . NamespaceResourceBlacklist = [ ] v1 . GroupKind {
{ Group : "*" , Kind : "deployment" } ,
}
syncCtx . kubectl = mockKubectlCmd { }
2018-11-17 01:10:04 +00:00
syncCtx . resources = [ ] v1alpha1 . ResourceState { {
LiveState : "" ,
TargetState : "{\"kind\":\"deployment\"}" ,
} }
2018-09-20 16:48:54 +00:00
syncCtx . sync ( )
assert . Len ( t , syncCtx . syncRes . Resources , 1 )
assert . Equal ( t , v1alpha1 . ResourceDetailsSyncFailed , syncCtx . syncRes . Resources [ 0 ] . Status )
assert . Contains ( t , syncCtx . syncRes . Resources [ 0 ] . Message , "not permitted in project" )
}
2018-09-10 17:14:14 +00:00
func TestSyncSuccessfully ( t * testing . T ) {
syncCtx := newTestSyncCtx ( )
syncCtx . kubectl = mockKubectlCmd { }
2018-11-17 01:10:04 +00:00
syncCtx . resources = [ ] v1alpha1 . ResourceState { {
LiveState : "" ,
TargetState : "{\"kind\":\"service\"}" ,
} , {
LiveState : "{\"kind\":\"pod\"}" ,
TargetState : "" ,
} }
2018-09-10 17:14:14 +00:00
syncCtx . sync ( )
assert . Len ( t , syncCtx . syncRes . Resources , 2 )
for i := range syncCtx . syncRes . Resources {
if syncCtx . syncRes . Resources [ i ] . Kind == "pod" {
assert . Equal ( t , v1alpha1 . ResourceDetailsSyncedAndPruned , syncCtx . syncRes . Resources [ i ] . Status )
} else if syncCtx . syncRes . Resources [ i ] . Kind == "service" {
assert . Equal ( t , v1alpha1 . ResourceDetailsSynced , syncCtx . syncRes . Resources [ i ] . Status )
} else {
t . Error ( "Resource isn't a pod or a service" )
}
}
syncCtx . sync ( )
assert . Equal ( t , syncCtx . opState . Phase , v1alpha1 . OperationSucceeded )
}
func TestSyncDeleteSuccessfully ( t * testing . T ) {
syncCtx := newTestSyncCtx ( )
syncCtx . kubectl = mockKubectlCmd { }
2018-11-17 01:10:04 +00:00
syncCtx . resources = [ ] v1alpha1 . ResourceState { {
LiveState : "{\"kind\":\"service\"}" ,
TargetState : "" ,
} , {
LiveState : "{\"kind\":\"pod\"}" ,
TargetState : "" ,
} }
2018-09-10 17:14:14 +00:00
syncCtx . sync ( )
for i := range syncCtx . syncRes . Resources {
if syncCtx . syncRes . Resources [ i ] . Kind == "pod" {
assert . Equal ( t , v1alpha1 . ResourceDetailsSyncedAndPruned , syncCtx . syncRes . Resources [ i ] . Status )
} else if syncCtx . syncRes . Resources [ i ] . Kind == "service" {
assert . Equal ( t , v1alpha1 . ResourceDetailsSyncedAndPruned , syncCtx . syncRes . Resources [ i ] . Status )
} else {
t . Error ( "Resource isn't a pod or a service" )
}
}
syncCtx . sync ( )
assert . Equal ( t , syncCtx . opState . Phase , v1alpha1 . OperationSucceeded )
}
func TestSyncCreateFailure ( t * testing . T ) {
syncCtx := newTestSyncCtx ( )
syncCtx . kubectl = mockKubectlCmd {
commands : map [ string ] kubectlOutput {
"test-service" : {
output : "" ,
err : fmt . Errorf ( "error: error validating \"test.yaml\": error validating data: apiVersion not set; if you choose to ignore these errors, turn validation off with --validate=false" ) ,
} ,
} ,
}
2018-11-17 01:10:04 +00:00
syncCtx . resources = [ ] v1alpha1 . ResourceState { {
LiveState : "" ,
TargetState : "{\"kind\":\"service\", \"metadata\":{\"name\":\"test-service\"}}" ,
} }
2018-09-10 17:14:14 +00:00
syncCtx . sync ( )
assert . Len ( t , syncCtx . syncRes . Resources , 1 )
assert . Equal ( t , v1alpha1 . ResourceDetailsSyncFailed , syncCtx . syncRes . Resources [ 0 ] . Status )
}
func TestSyncPruneFailure ( t * testing . T ) {
syncCtx := newTestSyncCtx ( )
syncCtx . kubectl = mockKubectlCmd {
commands : map [ string ] kubectlOutput {
"test-service" : {
output : "" ,
err : fmt . Errorf ( " error: timed out waiting for \"test-service\" to be synced" ) ,
} ,
} ,
}
2018-11-17 01:10:04 +00:00
syncCtx . resources = [ ] v1alpha1 . ResourceState { {
LiveState : "{\"kind\":\"service\", \"metadata\":{\"name\":\"test-service\"}}" ,
TargetState : "" ,
} }
2018-09-10 17:14:14 +00:00
syncCtx . sync ( )
assert . Len ( t , syncCtx . syncRes . Resources , 1 )
assert . Equal ( t , v1alpha1 . ResourceDetailsSyncFailed , syncCtx . syncRes . Resources [ 0 ] . Status )
}
2018-07-07 07:54:06 +00:00
func TestRunWorkflows ( t * testing . T ) {
// syncCtx := newTestSyncCtx()
// syncCtx.doWorkflowSync(nil, nil)
}
2018-09-10 17:14:14 +00:00
func unsortedManifest ( ) [ ] syncTask {
return [ ] syncTask {
{
targetObj : & unstructured . Unstructured {
Object : map [ string ] interface { } {
"GroupVersion" : apiv1 . SchemeGroupVersion . String ( ) ,
"kind" : "Pod" ,
} ,
} ,
} ,
{
targetObj : & unstructured . Unstructured {
Object : map [ string ] interface { } {
"GroupVersion" : apiv1 . SchemeGroupVersion . String ( ) ,
"kind" : "Service" ,
} ,
} ,
} ,
{
targetObj : & unstructured . Unstructured {
Object : map [ string ] interface { } {
"GroupVersion" : apiv1 . SchemeGroupVersion . String ( ) ,
"kind" : "PersistentVolume" ,
} ,
} ,
} ,
{
targetObj : & unstructured . Unstructured {
Object : map [ string ] interface { } {
"GroupVersion" : apiv1 . SchemeGroupVersion . String ( ) ,
} ,
} ,
} ,
{
targetObj : & unstructured . Unstructured {
Object : map [ string ] interface { } {
"GroupVersion" : apiv1 . SchemeGroupVersion . String ( ) ,
"kind" : "ConfigMap" ,
} ,
} ,
} ,
}
}
func sortedManifest ( ) [ ] syncTask {
return [ ] syncTask {
{
targetObj : & unstructured . Unstructured {
Object : map [ string ] interface { } {
"GroupVersion" : apiv1 . SchemeGroupVersion . String ( ) ,
"kind" : "ConfigMap" ,
} ,
} ,
} ,
{
targetObj : & unstructured . Unstructured {
Object : map [ string ] interface { } {
"GroupVersion" : apiv1 . SchemeGroupVersion . String ( ) ,
"kind" : "PersistentVolume" ,
} ,
} ,
} ,
{
targetObj : & unstructured . Unstructured {
Object : map [ string ] interface { } {
"GroupVersion" : apiv1 . SchemeGroupVersion . String ( ) ,
"kind" : "Service" ,
} ,
} ,
} ,
{
targetObj : & unstructured . Unstructured {
Object : map [ string ] interface { } {
"GroupVersion" : apiv1 . SchemeGroupVersion . String ( ) ,
"kind" : "Pod" ,
} ,
} ,
} ,
{
targetObj : & unstructured . Unstructured {
Object : map [ string ] interface { } {
"GroupVersion" : apiv1 . SchemeGroupVersion . String ( ) ,
} ,
} ,
} ,
}
}
func TestSortKubernetesResourcesSuccessfully ( t * testing . T ) {
unsorted := unsortedManifest ( )
ks := newKindSorter ( unsorted , resourceOrder )
sort . Sort ( ks )
expectedOrder := sortedManifest ( )
assert . Equal ( t , len ( unsorted ) , len ( expectedOrder ) )
for i , sorted := range unsorted {
assert . Equal ( t , expectedOrder [ i ] , sorted )
}
}
func TestSortManifestHandleNil ( t * testing . T ) {
task := syncTask {
targetObj : & unstructured . Unstructured {
Object : map [ string ] interface { } {
"GroupVersion" : apiv1 . SchemeGroupVersion . String ( ) ,
"kind" : "Service" ,
} ,
} ,
}
manifest := [ ] syncTask {
{ } ,
task ,
}
ks := newKindSorter ( manifest , resourceOrder )
sort . Sort ( ks )
assert . Equal ( t , task , manifest [ 0 ] )
assert . Nil ( t , manifest [ 1 ] . targetObj )
}