2018-07-07 07:54:06 +00:00
package controller
import (
2024-10-03 14:37:50 +00:00
"strconv"
2018-07-07 07:54:06 +00:00
"testing"
2026-02-12 14:29:40 +00:00
"github.com/argoproj/argo-cd/gitops-engine/pkg/sync"
synccommon "github.com/argoproj/argo-cd/gitops-engine/pkg/sync/common"
"github.com/argoproj/argo-cd/gitops-engine/pkg/utils/kube"
2018-09-10 17:14:14 +00:00
"github.com/stretchr/testify/assert"
2022-01-13 21:00:17 +00:00
"github.com/stretchr/testify/require"
2024-10-03 14:37:50 +00:00
corev1 "k8s.io/api/core/v1"
2024-12-31 08:34:11 +00:00
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2018-09-10 17:14:14 +00:00
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2019-01-04 18:16:41 +00:00
"k8s.io/apimachinery/pkg/runtime"
2018-11-21 19:58:08 +00:00
2025-07-02 19:00:57 +00:00
"github.com/argoproj/argo-cd/v3/common"
2025-01-10 21:14:00 +00:00
"github.com/argoproj/argo-cd/v3/controller/testdata"
"github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/v3/reposerver/apiclient"
"github.com/argoproj/argo-cd/v3/test"
"github.com/argoproj/argo-cd/v3/util/argo/diff"
"github.com/argoproj/argo-cd/v3/util/argo/normalizers"
2018-07-07 07:54:06 +00:00
)
2019-01-04 18:16:41 +00:00
func TestPersistRevisionHistory ( t * testing . T ) {
app := newFakeApp ( )
2019-03-04 08:56:36 +00:00
app . Status . OperationState = nil
app . Status . History = nil
2019-01-04 18:16:41 +00:00
defaultProject := & v1alpha1 . AppProject {
2024-12-31 08:34:11 +00:00
ObjectMeta : metav1 . ObjectMeta {
2019-01-04 18:16:41 +00:00
Namespace : test . FakeArgoCDNamespace ,
Name : "default" ,
} ,
}
data := fakeData {
apps : [ ] runtime . Object { app , defaultProject } ,
2019-07-13 00:17:23 +00:00
manifestResponse : & apiclient . ManifestResponse {
2019-01-04 18:16:41 +00:00
Manifests : [ ] string { } ,
Namespace : test . FakeDestNamespace ,
Server : test . FakeClusterURL ,
Revision : "abc123" ,
} ,
managedLiveObjs : make ( map [ kube . ResourceKey ] * unstructured . Unstructured ) ,
}
2025-11-02 12:39:24 +00:00
ctrl := newFakeController ( t . Context ( ) , & data , nil )
2019-01-04 18:16:41 +00:00
2019-03-04 08:56:36 +00:00
// Sync with source unspecified
opState := & v1alpha1 . OperationState { Operation : v1alpha1 . Operation {
2019-01-04 18:16:41 +00:00
Sync : & v1alpha1 . SyncOperation { } ,
2019-03-04 08:56:36 +00:00
} }
2025-07-03 17:16:58 +00:00
ctrl . appStateManager . SyncAppState ( app , defaultProject , opState )
2019-03-04 08:56:36 +00:00
// Ensure we record spec.source into sync result
2022-12-16 20:47:08 +00:00
assert . Equal ( t , app . Spec . GetSource ( ) , opState . SyncResult . Source )
2019-01-04 18:16:41 +00:00
2025-03-10 14:14:41 +00:00
updatedApp , err := ctrl . applicationClientset . ArgoprojV1alpha1 ( ) . Applications ( app . Namespace ) . Get ( t . Context ( ) , app . Name , metav1 . GetOptions { } )
2024-06-17 13:05:27 +00:00
require . NoError ( t , err )
2024-12-16 21:59:09 +00:00
require . Len ( t , updatedApp . Status . History , 1 )
2022-12-16 20:47:08 +00:00
assert . Equal ( t , app . Spec . GetSource ( ) , updatedApp . Status . History [ 0 ] . Source )
2019-01-04 18:16:41 +00:00
assert . Equal ( t , "abc123" , updatedApp . Status . History [ 0 ] . Revision )
2019-03-04 08:56:36 +00:00
}
2023-05-19 13:55:08 +00:00
func TestPersistManagedNamespaceMetadataState ( t * testing . T ) {
app := newFakeApp ( )
app . Status . OperationState = nil
app . Status . History = nil
app . Spec . SyncPolicy . ManagedNamespaceMetadata = & v1alpha1 . ManagedNamespaceMetadata {
Labels : map [ string ] string {
"foo" : "bar" ,
} ,
Annotations : map [ string ] string {
"foo" : "bar" ,
} ,
}
defaultProject := & v1alpha1 . AppProject {
2024-12-31 08:34:11 +00:00
ObjectMeta : metav1 . ObjectMeta {
2023-05-19 13:55:08 +00:00
Namespace : test . FakeArgoCDNamespace ,
Name : "default" ,
} ,
}
data := fakeData {
apps : [ ] runtime . Object { app , defaultProject } ,
manifestResponse : & apiclient . ManifestResponse {
Manifests : [ ] string { } ,
Namespace : test . FakeDestNamespace ,
Server : test . FakeClusterURL ,
Revision : "abc123" ,
} ,
managedLiveObjs : make ( map [ kube . ResourceKey ] * unstructured . Unstructured ) ,
}
2025-11-02 12:39:24 +00:00
ctrl := newFakeController ( t . Context ( ) , & data , nil )
2023-05-19 13:55:08 +00:00
// Sync with source unspecified
opState := & v1alpha1 . OperationState { Operation : v1alpha1 . Operation {
Sync : & v1alpha1 . SyncOperation { } ,
} }
2025-07-03 17:16:58 +00:00
ctrl . appStateManager . SyncAppState ( app , defaultProject , opState )
2023-05-19 13:55:08 +00:00
// Ensure we record spec.syncPolicy.managedNamespaceMetadata into sync result
assert . Equal ( t , app . Spec . SyncPolicy . ManagedNamespaceMetadata , opState . SyncResult . ManagedNamespaceMetadata )
}
2019-03-04 08:56:36 +00:00
func TestPersistRevisionHistoryRollback ( t * testing . T ) {
app := newFakeApp ( )
app . Status . OperationState = nil
app . Status . History = nil
defaultProject := & v1alpha1 . AppProject {
2024-12-31 08:34:11 +00:00
ObjectMeta : metav1 . ObjectMeta {
2019-03-04 08:56:36 +00:00
Namespace : test . FakeArgoCDNamespace ,
Name : "default" ,
} ,
}
data := fakeData {
apps : [ ] runtime . Object { app , defaultProject } ,
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 ) ,
}
2025-11-02 12:39:24 +00:00
ctrl := newFakeController ( t . Context ( ) , & data , nil )
2019-01-04 18:16:41 +00:00
2019-03-04 08:56:36 +00:00
// Sync with source specified
source := v1alpha1 . ApplicationSource {
Helm : & v1alpha1 . ApplicationSourceHelm {
Parameters : [ ] v1alpha1 . HelmParameter {
{
Name : "test" ,
Value : "123" ,
} ,
} ,
} ,
}
opState := & v1alpha1 . OperationState { Operation : v1alpha1 . Operation {
2019-01-04 18:16:41 +00:00
Sync : & v1alpha1 . SyncOperation {
2019-03-04 08:56:36 +00:00
Source : & source ,
2019-01-04 18:16:41 +00:00
} ,
2019-03-04 08:56:36 +00:00
} }
2025-07-03 17:16:58 +00:00
ctrl . appStateManager . SyncAppState ( app , defaultProject , opState )
2019-03-04 08:56:36 +00:00
// Ensure we record opState's source into sync result
assert . Equal ( t , source , opState . SyncResult . Source )
2019-01-04 18:16:41 +00:00
2025-03-10 14:14:41 +00:00
updatedApp , err := ctrl . applicationClientset . ArgoprojV1alpha1 ( ) . Applications ( app . Namespace ) . Get ( t . Context ( ) , app . Name , metav1 . GetOptions { } )
2024-06-17 13:05:27 +00:00
require . NoError ( t , err )
2024-06-07 19:12:50 +00:00
assert . Len ( t , updatedApp . Status . History , 1 )
2019-03-04 08:56:36 +00:00
assert . Equal ( t , source , updatedApp . Status . History [ 0 ] . Source )
2019-01-04 18:16:41 +00:00
assert . Equal ( t , "abc123" , updatedApp . Status . History [ 0 ] . Revision )
}
2020-06-22 21:09:22 +00:00
func TestSyncComparisonError ( t * testing . T ) {
app := newFakeApp ( )
app . Status . OperationState = nil
app . Status . History = nil
defaultProject := & v1alpha1 . AppProject {
2024-12-31 08:34:11 +00:00
ObjectMeta : metav1 . ObjectMeta {
2020-06-22 21:09:22 +00:00
Namespace : test . FakeArgoCDNamespace ,
Name : "default" ,
} ,
Spec : v1alpha1 . AppProjectSpec {
SignatureKeys : [ ] v1alpha1 . SignatureKey { { KeyID : "test" } } ,
} ,
}
data := fakeData {
apps : [ ] runtime . Object { app , defaultProject } ,
manifestResponse : & apiclient . ManifestResponse {
Manifests : [ ] string { } ,
Namespace : test . FakeDestNamespace ,
Server : test . FakeClusterURL ,
Revision : "abc123" ,
VerifyResult : "something went wrong" ,
} ,
managedLiveObjs : make ( map [ kube . ResourceKey ] * unstructured . Unstructured ) ,
}
2025-11-02 12:39:24 +00:00
ctrl := newFakeController ( t . Context ( ) , & data , nil )
2020-06-22 21:09:22 +00:00
// Sync with source unspecified
opState := & v1alpha1 . OperationState { Operation : v1alpha1 . Operation {
Sync : & v1alpha1 . SyncOperation { } ,
} }
2023-07-06 23:50:08 +00:00
t . Setenv ( "ARGOCD_GPG_ENABLED" , "true" )
2025-07-03 17:16:58 +00:00
ctrl . appStateManager . SyncAppState ( app , defaultProject , opState )
2020-06-22 21:09:22 +00:00
conditions := app . Status . GetConditions ( map [ v1alpha1 . ApplicationConditionType ] bool { v1alpha1 . ApplicationConditionComparisonError : true } )
assert . NotEmpty ( t , conditions )
assert . Equal ( t , "abc123" , opState . SyncResult . Revision )
}
2021-10-20 15:06:41 +00:00
func TestAppStateManager_SyncAppState ( t * testing . T ) {
2025-06-08 13:10:55 +00:00
t . Parallel ( )
2021-10-20 15:06:41 +00:00
type fixture struct {
application * v1alpha1 . Application
2025-07-03 17:16:58 +00:00
project * v1alpha1 . AppProject
2021-10-20 15:06:41 +00:00
controller * ApplicationController
}
2025-07-02 19:00:57 +00:00
setup := func ( liveObjects map [ kube . ResourceKey ] * unstructured . Unstructured ) * fixture {
2021-10-20 15:06:41 +00:00
app := newFakeApp ( )
app . Status . OperationState = nil
app . Status . History = nil
2025-07-02 19:00:57 +00:00
if liveObjects == nil {
liveObjects = make ( map [ kube . ResourceKey ] * unstructured . Unstructured )
}
2021-10-20 15:06:41 +00:00
project := & v1alpha1 . AppProject {
2024-12-31 08:34:11 +00:00
ObjectMeta : metav1 . ObjectMeta {
2021-10-20 15:06:41 +00:00
Namespace : test . FakeArgoCDNamespace ,
Name : "default" ,
} ,
Spec : v1alpha1 . AppProjectSpec {
SignatureKeys : [ ] v1alpha1 . SignatureKey { { KeyID : "test" } } ,
2025-07-02 19:00:57 +00:00
Destinations : [ ] v1alpha1 . ApplicationDestination {
{
Namespace : "*" ,
Server : "*" ,
} ,
} ,
2021-10-20 15:06:41 +00:00
} ,
}
data := fakeData {
apps : [ ] runtime . Object { app , project } ,
manifestResponse : & apiclient . ManifestResponse {
Manifests : [ ] string { } ,
Namespace : test . FakeDestNamespace ,
Server : test . FakeClusterURL ,
Revision : "abc123" ,
} ,
2025-07-02 19:00:57 +00:00
managedLiveObjs : liveObjects ,
2021-10-20 15:06:41 +00:00
}
2025-11-02 12:39:24 +00:00
ctrl := newFakeController ( t . Context ( ) , & data , nil )
2021-10-20 15:06:41 +00:00
return & fixture {
application : app ,
2025-07-03 17:16:58 +00:00
project : project ,
2021-10-20 15:06:41 +00:00
controller : ctrl ,
}
}
t . Run ( "will fail the sync if finds shared resources" , func ( t * testing . T ) {
// given
t . Parallel ( )
2025-07-02 19:00:57 +00:00
sharedObject := kube . MustToUnstructured ( & corev1 . ConfigMap {
TypeMeta : metav1 . TypeMeta {
APIVersion : "v1" ,
Kind : "ConfigMap" ,
} ,
ObjectMeta : metav1 . ObjectMeta {
Name : "configmap1" ,
Namespace : "default" ,
Annotations : map [ string ] string {
common . AnnotationKeyAppInstance : "guestbook:/ConfigMap:default/configmap1" ,
} ,
} ,
} )
liveObjects := make ( map [ kube . ResourceKey ] * unstructured . Unstructured )
liveObjects [ kube . GetResourceKey ( sharedObject ) ] = sharedObject
f := setup ( liveObjects )
2021-10-20 15:06:41 +00:00
// Sync with source unspecified
opState := & v1alpha1 . OperationState { Operation : v1alpha1 . Operation {
Sync : & v1alpha1 . SyncOperation {
Source : & v1alpha1 . ApplicationSource { } ,
SyncOptions : [ ] string { "FailOnSharedResource=true" } ,
} ,
} }
// when
2025-07-03 17:16:58 +00:00
f . controller . appStateManager . SyncAppState ( f . application , f . project , opState )
2021-10-20 15:06:41 +00:00
// then
2025-07-02 19:00:57 +00:00
assert . Equal ( t , synccommon . OperationFailed , opState . Phase )
assert . Contains ( t , opState . Message , "ConfigMap/configmap1 is part of applications fake-argocd-ns/my-app and guestbook" )
2021-10-20 15:06:41 +00:00
} )
}
2022-01-13 21:00:17 +00:00
2024-03-26 12:45:35 +00:00
func TestSyncWindowDeniesSync ( t * testing . T ) {
2025-06-08 13:10:55 +00:00
t . Parallel ( )
2024-03-26 12:45:35 +00:00
type fixture struct {
application * v1alpha1 . Application
2025-07-03 17:16:58 +00:00
project * v1alpha1 . AppProject
2024-03-26 12:45:35 +00:00
controller * ApplicationController
}
setup := func ( ) * fixture {
app := newFakeApp ( )
app . Status . OperationState = nil
app . Status . History = nil
project := & v1alpha1 . AppProject {
2024-12-31 08:34:11 +00:00
ObjectMeta : metav1 . ObjectMeta {
2024-03-26 12:45:35 +00:00
Namespace : test . FakeArgoCDNamespace ,
Name : "default" ,
} ,
Spec : v1alpha1 . AppProjectSpec {
SyncWindows : v1alpha1 . SyncWindows { {
Kind : "deny" ,
Schedule : "0 0 * * *" ,
Duration : "24h" ,
Clusters : [ ] string { "*" } ,
Namespaces : [ ] string { "*" } ,
Applications : [ ] string { "*" } ,
} } ,
} ,
}
data := fakeData {
apps : [ ] runtime . Object { app , project } ,
manifestResponse : & apiclient . ManifestResponse {
Manifests : [ ] string { } ,
Namespace : test . FakeDestNamespace ,
Server : test . FakeClusterURL ,
Revision : "abc123" ,
} ,
managedLiveObjs : make ( map [ kube . ResourceKey ] * unstructured . Unstructured ) ,
}
2025-11-02 12:39:24 +00:00
ctrl := newFakeController ( t . Context ( ) , & data , nil )
2024-03-26 12:45:35 +00:00
return & fixture {
application : app ,
2025-07-03 17:16:58 +00:00
project : project ,
2024-03-26 12:45:35 +00:00
controller : ctrl ,
}
}
t . Run ( "will keep the sync progressing if a sync window prevents the sync" , func ( t * testing . T ) {
// given a project with an active deny sync window and an operation in progress
t . Parallel ( )
f := setup ( )
opMessage := "Sync operation blocked by sync window"
2024-06-11 15:41:55 +00:00
opState := & v1alpha1 . OperationState {
Operation : v1alpha1 . Operation {
Sync : & v1alpha1 . SyncOperation {
Source : & v1alpha1 . ApplicationSource { } ,
} ,
} ,
2025-07-02 19:00:57 +00:00
Phase : synccommon . OperationRunning ,
2024-03-26 12:45:35 +00:00
}
// when
2025-07-03 17:16:58 +00:00
f . controller . appStateManager . SyncAppState ( f . application , f . project , opState )
2024-03-26 12:45:35 +00:00
2024-06-11 15:41:55 +00:00
// then
2025-07-02 19:00:57 +00:00
assert . Equal ( t , synccommon . OperationRunning , opState . Phase )
2024-03-26 12:45:35 +00:00
assert . Contains ( t , opState . Message , opMessage )
} )
}
2022-01-13 21:00:17 +00:00
func TestNormalizeTargetResources ( t * testing . T ) {
type fixture struct {
comparisonResult * comparisonResult
}
setup := func ( t * testing . T , ignores [ ] v1alpha1 . ResourceIgnoreDifferences ) * fixture {
t . Helper ( )
dc , err := diff . NewDiffConfigBuilder ( ) .
2024-04-26 09:24:02 +00:00
WithDiffSettings ( ignores , nil , true , normalizers . IgnoreNormalizerOpts { } ) .
2022-01-13 21:00:17 +00:00
WithNoCache ( ) .
Build ( )
require . NoError ( t , err )
live := test . YamlToUnstructured ( testdata . LiveDeploymentYaml )
target := test . YamlToUnstructured ( testdata . TargetDeploymentYaml )
return & fixture {
& comparisonResult {
reconciliationResult : sync . ReconciliationResult {
Live : [ ] * unstructured . Unstructured { live } ,
Target : [ ] * unstructured . Unstructured { target } ,
} ,
diffConfig : dc ,
} ,
}
}
t . Run ( "will modify target resource adding live state in fields it should ignore" , func ( t * testing . T ) {
// given
ignore := v1alpha1 . ResourceIgnoreDifferences {
Group : "*" ,
Kind : "*" ,
ManagedFieldsManagers : [ ] string { "janitor" } ,
}
ignores := [ ] v1alpha1 . ResourceIgnoreDifferences { ignore }
f := setup ( t , ignores )
// when
2025-11-17 19:21:44 +00:00
targets , err := normalizeTargetResources ( f . comparisonResult )
2022-01-13 21:00:17 +00:00
// then
require . NoError ( t , err )
2024-06-07 19:12:50 +00:00
require . Len ( t , targets , 1 )
2022-01-13 21:00:17 +00:00
iksmVersion := targets [ 0 ] . GetAnnotations ( ) [ "iksm-version" ]
assert . Equal ( t , "2.0" , iksmVersion )
} )
t . Run ( "will not modify target resource if ignore difference is not configured" , func ( t * testing . T ) {
// given
f := setup ( t , [ ] v1alpha1 . ResourceIgnoreDifferences { } )
// when
2025-11-17 19:21:44 +00:00
targets , err := normalizeTargetResources ( f . comparisonResult )
2022-01-13 21:00:17 +00:00
// then
require . NoError ( t , err )
2024-06-07 19:12:50 +00:00
require . Len ( t , targets , 1 )
2022-01-13 21:00:17 +00:00
iksmVersion := targets [ 0 ] . GetAnnotations ( ) [ "iksm-version" ]
assert . Equal ( t , "1.0" , iksmVersion )
} )
t . Run ( "will remove fields from target if not present in live" , func ( t * testing . T ) {
ignore := v1alpha1 . ResourceIgnoreDifferences {
Group : "apps" ,
Kind : "Deployment" ,
JSONPointers : [ ] string { "/metadata/annotations/iksm-version" } ,
}
ignores := [ ] v1alpha1 . ResourceIgnoreDifferences { ignore }
f := setup ( t , ignores )
live := f . comparisonResult . reconciliationResult . Live [ 0 ]
unstructured . RemoveNestedField ( live . Object , "metadata" , "annotations" , "iksm-version" )
// when
2025-11-17 19:21:44 +00:00
targets , err := normalizeTargetResources ( f . comparisonResult )
2022-01-13 21:00:17 +00:00
// then
require . NoError ( t , err )
2024-06-07 19:12:50 +00:00
require . Len ( t , targets , 1 )
2022-01-13 21:00:17 +00:00
_ , ok := targets [ 0 ] . GetAnnotations ( ) [ "iksm-version" ]
assert . False ( t , ok )
} )
t . Run ( "will correctly normalize with multiple ignore configurations" , func ( t * testing . T ) {
// given
ignores := [ ] v1alpha1 . ResourceIgnoreDifferences {
{
Group : "apps" ,
Kind : "Deployment" ,
JSONPointers : [ ] string { "/spec/replicas" } ,
} ,
{
Group : "*" ,
Kind : "*" ,
ManagedFieldsManagers : [ ] string { "janitor" } ,
} ,
}
f := setup ( t , ignores )
// when
2025-11-17 19:21:44 +00:00
targets , err := normalizeTargetResources ( f . comparisonResult )
2022-01-13 21:00:17 +00:00
// then
require . NoError ( t , err )
2024-06-07 19:12:50 +00:00
require . Len ( t , targets , 1 )
2022-01-13 21:00:17 +00:00
normalized := targets [ 0 ]
iksmVersion , ok := normalized . GetAnnotations ( ) [ "iksm-version" ]
require . True ( t , ok )
assert . Equal ( t , "2.0" , iksmVersion )
replicas , ok , err := unstructured . NestedInt64 ( normalized . Object , "spec" , "replicas" )
require . NoError ( t , err )
require . True ( t , ok )
assert . Equal ( t , int64 ( 4 ) , replicas )
} )
t . Run ( "will keep new array entries not found in live state if not ignored" , func ( t * testing . T ) {
2025-11-17 19:21:44 +00:00
t . Skip ( "limitation in the current implementation" )
2022-01-13 21:00:17 +00:00
// given
ignores := [ ] v1alpha1 . ResourceIgnoreDifferences {
{
Group : "apps" ,
Kind : "Deployment" ,
JQPathExpressions : [ ] string { ".spec.template.spec.containers[] | select(.name == \"guestbook-ui\")" } ,
} ,
}
f := setup ( t , ignores )
target := test . YamlToUnstructured ( testdata . TargetDeploymentNewEntries )
f . comparisonResult . reconciliationResult . Target = [ ] * unstructured . Unstructured { target }
// when
2025-11-17 19:21:44 +00:00
targets , err := normalizeTargetResources ( f . comparisonResult )
2022-01-13 21:00:17 +00:00
// then
require . NoError ( t , err )
2024-06-07 19:12:50 +00:00
require . Len ( t , targets , 1 )
2022-01-13 21:00:17 +00:00
containers , ok , err := unstructured . NestedSlice ( targets [ 0 ] . Object , "spec" , "template" , "spec" , "containers" )
require . NoError ( t , err )
require . True ( t , ok )
2024-06-07 19:12:50 +00:00
assert . Len ( t , containers , 2 )
2022-01-13 21:00:17 +00:00
} )
}
2024-04-03 22:56:59 +00:00
func TestNormalizeTargetResourcesWithList ( t * testing . T ) {
type fixture struct {
comparisonResult * comparisonResult
}
2025-02-18 16:02:38 +00:00
setupHTTPProxy := func ( t * testing . T , ignores [ ] v1alpha1 . ResourceIgnoreDifferences ) * fixture {
2024-04-03 22:56:59 +00:00
t . Helper ( )
dc , err := diff . NewDiffConfigBuilder ( ) .
2024-04-26 09:24:02 +00:00
WithDiffSettings ( ignores , nil , true , normalizers . IgnoreNormalizerOpts { } ) .
2024-04-03 22:56:59 +00:00
WithNoCache ( ) .
Build ( )
require . NoError ( t , err )
live := test . YamlToUnstructured ( testdata . LiveHTTPProxy )
target := test . YamlToUnstructured ( testdata . TargetHTTPProxy )
return & fixture {
& comparisonResult {
reconciliationResult : sync . ReconciliationResult {
Live : [ ] * unstructured . Unstructured { live } ,
Target : [ ] * unstructured . Unstructured { target } ,
} ,
diffConfig : dc ,
} ,
}
}
t . Run ( "will properly ignore nested fields within arrays" , func ( t * testing . T ) {
// given
ignores := [ ] v1alpha1 . ResourceIgnoreDifferences {
{
Group : "projectcontour.io" ,
Kind : "HTTPProxy" ,
JQPathExpressions : [ ] string { ".spec.routes[]" } ,
2024-06-11 15:41:55 +00:00
// JSONPointers: []string{"/spec/routes"},
2024-04-03 22:56:59 +00:00
} ,
}
2025-02-18 16:02:38 +00:00
f := setupHTTPProxy ( t , ignores )
2024-04-03 22:56:59 +00:00
target := test . YamlToUnstructured ( testdata . TargetHTTPProxy )
f . comparisonResult . reconciliationResult . Target = [ ] * unstructured . Unstructured { target }
// when
2025-11-17 19:21:44 +00:00
patchedTargets , err := normalizeTargetResources ( f . comparisonResult )
2024-04-03 22:56:59 +00:00
// then
require . NoError ( t , err )
2024-06-07 19:12:50 +00:00
require . Len ( t , f . comparisonResult . reconciliationResult . Live , 1 )
require . Len ( t , f . comparisonResult . reconciliationResult . Target , 1 )
require . Len ( t , patchedTargets , 1 )
2024-04-03 22:56:59 +00:00
// live should have 1 entry
2025-06-16 15:02:32 +00:00
require . Len ( t , dig ( f . comparisonResult . reconciliationResult . Live [ 0 ] . Object , "spec" , "routes" , 0 , "rateLimitPolicy" , "global" , "descriptors" ) , 1 )
2024-04-03 22:56:59 +00:00
// assert some arbitrary field to show `entries[0]` is not an empty object
2025-06-16 15:02:32 +00:00
require . Equal ( t , "sample-header" , dig ( f . comparisonResult . reconciliationResult . Live [ 0 ] . Object , "spec" , "routes" , 0 , "rateLimitPolicy" , "global" , "descriptors" , 0 , "entries" , 0 , "requestHeader" , "headerName" ) )
2024-04-03 22:56:59 +00:00
// target has 2 entries
2025-06-16 15:02:32 +00:00
require . Len ( t , dig ( f . comparisonResult . reconciliationResult . Target [ 0 ] . Object , "spec" , "routes" , 0 , "rateLimitPolicy" , "global" , "descriptors" , 0 , "entries" ) , 2 )
2024-04-03 22:56:59 +00:00
// assert some arbitrary field to show `entries[0]` is not an empty object
2025-06-16 15:02:32 +00:00
require . Equal ( t , "sample-header" , dig ( f . comparisonResult . reconciliationResult . Target [ 0 ] . Object , "spec" , "routes" , 0 , "rateLimitPolicy" , "global" , "descriptors" , 0 , "entries" , 0 , "requestHeaderValueMatch" , "headers" , 0 , "name" ) )
2024-04-03 22:56:59 +00:00
// It should be *1* entries in the array
2025-06-16 15:02:32 +00:00
require . Len ( t , dig ( patchedTargets [ 0 ] . Object , "spec" , "routes" , 0 , "rateLimitPolicy" , "global" , "descriptors" ) , 1 )
2024-04-03 22:56:59 +00:00
// and it should NOT equal an empty object
2025-06-16 15:02:32 +00:00
require . Len ( t , dig ( patchedTargets [ 0 ] . Object , "spec" , "routes" , 0 , "rateLimitPolicy" , "global" , "descriptors" , 0 , "entries" , 0 ) , 1 )
2024-04-03 22:56:59 +00:00
} )
t . Run ( "will correctly set array entries if new entries have been added" , func ( t * testing . T ) {
// given
ignores := [ ] v1alpha1 . ResourceIgnoreDifferences {
{
Group : "apps" ,
Kind : "Deployment" ,
JQPathExpressions : [ ] string { ".spec.template.spec.containers[].env[] | select(.name == \"SOME_ENV_VAR\")" } ,
} ,
}
2025-02-18 16:02:38 +00:00
f := setupHTTPProxy ( t , ignores )
2024-04-03 22:56:59 +00:00
live := test . YamlToUnstructured ( testdata . LiveDeploymentEnvVarsYaml )
target := test . YamlToUnstructured ( testdata . TargetDeploymentEnvVarsYaml )
f . comparisonResult . reconciliationResult . Live = [ ] * unstructured . Unstructured { live }
f . comparisonResult . reconciliationResult . Target = [ ] * unstructured . Unstructured { target }
// when
2025-11-17 19:21:44 +00:00
targets , err := normalizeTargetResources ( f . comparisonResult )
2024-04-03 22:56:59 +00:00
// then
require . NoError ( t , err )
2024-06-07 19:12:50 +00:00
require . Len ( t , targets , 1 )
2024-04-03 22:56:59 +00:00
containers , ok , err := unstructured . NestedSlice ( targets [ 0 ] . Object , "spec" , "template" , "spec" , "containers" )
require . NoError ( t , err )
require . True ( t , ok )
2024-06-07 19:12:50 +00:00
assert . Len ( t , containers , 1 )
2024-04-03 22:56:59 +00:00
2025-01-02 23:26:59 +00:00
ports := containers [ 0 ] . ( map [ string ] any ) [ "ports" ] . ( [ ] any )
2024-06-07 19:12:50 +00:00
assert . Len ( t , ports , 1 )
2024-04-03 22:56:59 +00:00
2025-01-02 23:26:59 +00:00
env := containers [ 0 ] . ( map [ string ] any ) [ "env" ] . ( [ ] any )
2024-06-07 19:12:50 +00:00
assert . Len ( t , env , 3 )
2024-04-03 22:56:59 +00:00
first := env [ 0 ]
second := env [ 1 ]
third := env [ 2 ]
// Currently the defined order at this time is the insertion order of the target manifest.
2025-01-02 23:26:59 +00:00
assert . Equal ( t , "SOME_ENV_VAR" , first . ( map [ string ] any ) [ "name" ] )
assert . Equal ( t , "some_value" , first . ( map [ string ] any ) [ "value" ] )
2024-04-03 22:56:59 +00:00
2025-01-02 23:26:59 +00:00
assert . Equal ( t , "SOME_OTHER_ENV_VAR" , second . ( map [ string ] any ) [ "name" ] )
assert . Equal ( t , "some_other_value" , second . ( map [ string ] any ) [ "value" ] )
2024-04-03 22:56:59 +00:00
2025-01-02 23:26:59 +00:00
assert . Equal ( t , "YET_ANOTHER_ENV_VAR" , third . ( map [ string ] any ) [ "name" ] )
assert . Equal ( t , "yet_another_value" , third . ( map [ string ] any ) [ "value" ] )
2024-04-03 22:56:59 +00:00
} )
t . Run ( "ignore-deployment-image-replicas-changes-additive" , func ( t * testing . T ) {
// given
ignores := [ ] v1alpha1 . ResourceIgnoreDifferences {
{
Group : "apps" ,
Kind : "Deployment" ,
JSONPointers : [ ] string { "/spec/replicas" } ,
} , {
Group : "apps" ,
Kind : "Deployment" ,
JQPathExpressions : [ ] string { ".spec.template.spec.containers[].image" } ,
} ,
}
2025-02-18 16:02:38 +00:00
f := setupHTTPProxy ( t , ignores )
2024-04-03 22:56:59 +00:00
live := test . YamlToUnstructured ( testdata . MinimalImageReplicaDeploymentYaml )
target := test . YamlToUnstructured ( testdata . AdditionalImageReplicaDeploymentYaml )
f . comparisonResult . reconciliationResult . Live = [ ] * unstructured . Unstructured { live }
f . comparisonResult . reconciliationResult . Target = [ ] * unstructured . Unstructured { target }
// when
2025-11-17 19:21:44 +00:00
targets , err := normalizeTargetResources ( f . comparisonResult )
2024-04-03 22:56:59 +00:00
// then
require . NoError ( t , err )
2024-06-07 19:12:50 +00:00
require . Len ( t , targets , 1 )
2024-04-03 22:56:59 +00:00
metadata , ok , err := unstructured . NestedMap ( targets [ 0 ] . Object , "metadata" )
require . NoError ( t , err )
require . True ( t , ok )
2025-01-02 23:26:59 +00:00
labels , ok := metadata [ "labels" ] . ( map [ string ] any )
2024-04-03 22:56:59 +00:00
require . True ( t , ok )
2024-06-07 19:12:50 +00:00
assert . Len ( t , labels , 2 )
2024-04-03 22:56:59 +00:00
assert . Equal ( t , "web" , labels [ "appProcess" ] )
spec , ok , err := unstructured . NestedMap ( targets [ 0 ] . Object , "spec" )
require . NoError ( t , err )
require . True ( t , ok )
assert . Equal ( t , int64 ( 1 ) , spec [ "replicas" ] )
2025-01-02 23:26:59 +00:00
template , ok := spec [ "template" ] . ( map [ string ] any )
2024-04-03 22:56:59 +00:00
require . True ( t , ok )
2025-01-02 23:26:59 +00:00
tMetadata , ok := template [ "metadata" ] . ( map [ string ] any )
2024-04-03 22:56:59 +00:00
require . True ( t , ok )
2025-01-02 23:26:59 +00:00
tLabels , ok := tMetadata [ "labels" ] . ( map [ string ] any )
2024-04-03 22:56:59 +00:00
require . True ( t , ok )
2024-06-07 19:12:50 +00:00
assert . Len ( t , tLabels , 2 )
2024-04-03 22:56:59 +00:00
assert . Equal ( t , "web" , tLabels [ "appProcess" ] )
2025-01-02 23:26:59 +00:00
tSpec , ok := template [ "spec" ] . ( map [ string ] any )
2024-04-03 22:56:59 +00:00
require . True ( t , ok )
containers , ok , err := unstructured . NestedSlice ( tSpec , "containers" )
require . NoError ( t , err )
require . True ( t , ok )
2024-06-07 19:12:50 +00:00
assert . Len ( t , containers , 1 )
2024-04-03 22:56:59 +00:00
2025-01-02 23:26:59 +00:00
first := containers [ 0 ] . ( map [ string ] any )
2024-04-03 22:56:59 +00:00
assert . Equal ( t , "alpine:3" , first [ "image" ] )
2025-01-02 23:26:59 +00:00
resources , ok := first [ "resources" ] . ( map [ string ] any )
2024-04-03 22:56:59 +00:00
require . True ( t , ok )
2025-01-02 23:26:59 +00:00
requests , ok := resources [ "requests" ] . ( map [ string ] any )
2024-04-03 22:56:59 +00:00
require . True ( t , ok )
assert . Equal ( t , "400m" , requests [ "cpu" ] )
env , ok , err := unstructured . NestedSlice ( first , "env" )
require . NoError ( t , err )
require . True ( t , ok )
2024-06-07 19:12:50 +00:00
assert . Len ( t , env , 1 )
2024-04-03 22:56:59 +00:00
2025-01-02 23:26:59 +00:00
env0 := env [ 0 ] . ( map [ string ] any )
2024-04-03 22:56:59 +00:00
assert . Equal ( t , "EV" , env0 [ "name" ] )
assert . Equal ( t , "here" , env0 [ "value" ] )
} )
}
2024-10-03 14:37:50 +00:00
func TestDeriveServiceAccountMatchingNamespaces ( t * testing . T ) {
2025-06-08 13:10:55 +00:00
t . Parallel ( )
2024-10-03 14:37:50 +00:00
type fixture struct {
project * v1alpha1 . AppProject
application * v1alpha1 . Application
2025-06-23 01:32:19 +00:00
cluster * v1alpha1 . Cluster
2024-10-03 14:37:50 +00:00
}
setup := func ( destinationServiceAccounts [ ] v1alpha1 . ApplicationDestinationServiceAccount , destinationNamespace , destinationServerURL , applicationNamespace string ) * fixture {
project := & v1alpha1 . AppProject {
2024-12-31 08:34:11 +00:00
ObjectMeta : metav1 . ObjectMeta {
2024-10-03 14:37:50 +00:00
Namespace : "argocd-ns" ,
Name : "testProj" ,
} ,
Spec : v1alpha1 . AppProjectSpec {
DestinationServiceAccounts : destinationServiceAccounts ,
} ,
}
app := & v1alpha1 . Application {
2024-12-31 08:34:11 +00:00
ObjectMeta : metav1 . ObjectMeta {
2024-10-03 14:37:50 +00:00
Namespace : applicationNamespace ,
Name : "testApp" ,
} ,
Spec : v1alpha1 . ApplicationSpec {
Project : "testProj" ,
Destination : v1alpha1 . ApplicationDestination {
Server : destinationServerURL ,
Namespace : destinationNamespace ,
} ,
} ,
}
2025-06-23 01:32:19 +00:00
cluster := & v1alpha1 . Cluster {
Server : "https://kubernetes.svc.local" ,
Name : "test-cluster" ,
}
2024-10-03 14:37:50 +00:00
return & fixture {
project : project ,
application : app ,
2025-06-23 01:32:19 +00:00
cluster : cluster ,
2024-10-03 14:37:50 +00:00
}
}
t . Run ( "empty destination service accounts" , func ( t * testing . T ) {
// given an application referring a project with no destination service accounts
t . Parallel ( )
destinationServiceAccounts := [ ] v1alpha1 . ApplicationDestinationServiceAccount { }
destinationNamespace := "testns"
destinationServerURL := "https://kubernetes.svc.local"
applicationNamespace := "argocd-ns"
expectedSA := ""
expectedErrMsg := "no matching service account found for destination server https://kubernetes.svc.local and namespace testns"
f := setup ( destinationServiceAccounts , destinationNamespace , destinationServerURL , applicationNamespace )
// when
2025-06-23 01:32:19 +00:00
sa , err := deriveServiceAccountToImpersonate ( f . project , f . application , f . cluster )
2024-10-03 14:37:50 +00:00
assert . Equal ( t , expectedSA , sa )
// then, there should be an error saying no valid match was found
assert . EqualError ( t , err , expectedErrMsg )
} )
t . Run ( "exact match of destination namespace" , func ( t * testing . T ) {
// given an application referring a project with exactly one destination service account that matches the application destination,
t . Parallel ( )
destinationServiceAccounts := [ ] v1alpha1 . ApplicationDestinationServiceAccount {
{
Server : "https://kubernetes.svc.local" ,
Namespace : "testns" ,
DefaultServiceAccount : "test-sa" ,
} ,
}
destinationNamespace := "testns"
destinationServerURL := "https://kubernetes.svc.local"
applicationNamespace := "argocd-ns"
expectedSA := "system:serviceaccount:testns:test-sa"
f := setup ( destinationServiceAccounts , destinationNamespace , destinationServerURL , applicationNamespace )
// when
2025-06-23 01:32:19 +00:00
sa , err := deriveServiceAccountToImpersonate ( f . project , f . application , f . cluster )
2024-10-03 14:37:50 +00:00
// then, there should be no error and should use the right service account for impersonation
require . NoError ( t , err )
assert . Equal ( t , expectedSA , sa )
} )
t . Run ( "exact one match with multiple destination service accounts" , func ( t * testing . T ) {
// given an application referring a project with multiple destination service accounts having one exact match for application destination
t . Parallel ( )
destinationServiceAccounts := [ ] v1alpha1 . ApplicationDestinationServiceAccount {
{
Server : "https://kubernetes.svc.local" ,
Namespace : "guestbook" ,
DefaultServiceAccount : "guestbook-sa" ,
} ,
{
Server : "https://kubernetes.svc.local" ,
Namespace : "guestbook-test" ,
DefaultServiceAccount : "guestbook-test-sa" ,
} ,
{
Server : "https://kubernetes.svc.local" ,
Namespace : "default" ,
DefaultServiceAccount : "default-sa" ,
} ,
{
Server : "https://kubernetes.svc.local" ,
Namespace : "testns" ,
DefaultServiceAccount : "test-sa" ,
} ,
}
destinationNamespace := "testns"
destinationServerURL := "https://kubernetes.svc.local"
applicationNamespace := "argocd-ns"
expectedSA := "system:serviceaccount:testns:test-sa"
f := setup ( destinationServiceAccounts , destinationNamespace , destinationServerURL , applicationNamespace )
// when
2025-06-23 01:32:19 +00:00
sa , err := deriveServiceAccountToImpersonate ( f . project , f . application , f . cluster )
2024-10-03 14:37:50 +00:00
// then, there should be no error and should use the right service account for impersonation
require . NoError ( t , err )
assert . Equal ( t , expectedSA , sa )
} )
t . Run ( "first match to be used when multiple matches are available" , func ( t * testing . T ) {
// given an application referring a project with multiple destination service accounts having multiple match for application destination
t . Parallel ( )
destinationServiceAccounts := [ ] v1alpha1 . ApplicationDestinationServiceAccount {
{
Server : "https://kubernetes.svc.local" ,
Namespace : "testns" ,
DefaultServiceAccount : "test-sa" ,
} ,
{
Server : "https://kubernetes.svc.local" ,
Namespace : "testns" ,
DefaultServiceAccount : "test-sa-2" ,
} ,
{
Server : "https://kubernetes.svc.local" ,
Namespace : "testns" ,
DefaultServiceAccount : "test-sa-3" ,
} ,
{
Server : "https://kubernetes.svc.local" ,
Namespace : "guestbook" ,
DefaultServiceAccount : "guestbook-sa" ,
} ,
}
destinationNamespace := "testns"
destinationServerURL := "https://kubernetes.svc.local"
applicationNamespace := "argocd-ns"
expectedSA := "system:serviceaccount:testns:test-sa"
f := setup ( destinationServiceAccounts , destinationNamespace , destinationServerURL , applicationNamespace )
// when
2025-06-23 01:32:19 +00:00
sa , err := deriveServiceAccountToImpersonate ( f . project , f . application , f . cluster )
2024-10-03 14:37:50 +00:00
// then, there should be no error and it should use the first matching service account for impersonation
require . NoError ( t , err )
assert . Equal ( t , expectedSA , sa )
} )
t . Run ( "first match to be used when glob pattern is used" , func ( t * testing . T ) {
// given an application referring a project with multiple destination service accounts with glob patterns matching the application destination
t . Parallel ( )
destinationServiceAccounts := [ ] v1alpha1 . ApplicationDestinationServiceAccount {
{
Server : "https://kubernetes.svc.local" ,
Namespace : "test*" ,
DefaultServiceAccount : "test-sa" ,
} ,
{
Server : "https://kubernetes.svc.local" ,
Namespace : "testns" ,
DefaultServiceAccount : "test-sa-2" ,
} ,
{
Server : "https://kubernetes.svc.local" ,
Namespace : "default" ,
DefaultServiceAccount : "default-sa" ,
} ,
}
destinationNamespace := "testns"
destinationServerURL := "https://kubernetes.svc.local"
applicationNamespace := "argocd-ns"
expectedSA := "system:serviceaccount:testns:test-sa"
f := setup ( destinationServiceAccounts , destinationNamespace , destinationServerURL , applicationNamespace )
// when
2025-06-23 01:32:19 +00:00
sa , err := deriveServiceAccountToImpersonate ( f . project , f . application , f . cluster )
2024-10-03 14:37:50 +00:00
// then, there should not be any error and should use the first matching glob pattern service account for impersonation
require . NoError ( t , err )
assert . Equal ( t , expectedSA , sa )
} )
t . Run ( "no match among a valid list" , func ( t * testing . T ) {
// given an application referring a project with multiple destination service accounts with no matches for application destination
t . Parallel ( )
destinationServiceAccounts := [ ] v1alpha1 . ApplicationDestinationServiceAccount {
{
Server : "https://kubernetes.svc.local" ,
Namespace : "test1" ,
DefaultServiceAccount : "test-sa" ,
} ,
{
Server : "https://kubernetes.svc.local" ,
Namespace : "test2" ,
DefaultServiceAccount : "test-sa-2" ,
} ,
{
Server : "https://kubernetes.svc.local" ,
Namespace : "default" ,
DefaultServiceAccount : "default-sa" ,
} ,
}
destinationNamespace := "testns"
destinationServerURL := "https://kubernetes.svc.local"
applicationNamespace := "argocd-ns"
expectedSA := ""
expectedErrMsg := "no matching service account found for destination server https://kubernetes.svc.local and namespace testns"
f := setup ( destinationServiceAccounts , destinationNamespace , destinationServerURL , applicationNamespace )
// when
2025-06-23 01:32:19 +00:00
sa , err := deriveServiceAccountToImpersonate ( f . project , f . application , f . cluster )
2024-10-03 14:37:50 +00:00
// then, there should be an error saying no match was found
require . EqualError ( t , err , expectedErrMsg )
assert . Equal ( t , expectedSA , sa )
} )
t . Run ( "app destination namespace is empty" , func ( t * testing . T ) {
// given an application referring a project with multiple destination service accounts with empty application destination namespace
t . Parallel ( )
destinationServiceAccounts := [ ] v1alpha1 . ApplicationDestinationServiceAccount {
{
Server : "https://kubernetes.svc.local" ,
DefaultServiceAccount : "test-sa" ,
} ,
{
Server : "https://kubernetes.svc.local" ,
Namespace : "*" ,
DefaultServiceAccount : "test-sa-2" ,
} ,
}
destinationNamespace := ""
destinationServerURL := "https://kubernetes.svc.local"
applicationNamespace := "argocd-ns"
expectedSA := "system:serviceaccount:argocd-ns:test-sa"
f := setup ( destinationServiceAccounts , destinationNamespace , destinationServerURL , applicationNamespace )
// when
2025-06-23 01:32:19 +00:00
sa , err := deriveServiceAccountToImpersonate ( f . project , f . application , f . cluster )
2024-10-03 14:37:50 +00:00
// then, there should not be any error and the service account configured for with empty namespace should be used.
require . NoError ( t , err )
assert . Equal ( t , expectedSA , sa )
} )
t . Run ( "match done via catch all glob pattern" , func ( t * testing . T ) {
// given an application referring a project with multiple destination service accounts having a catch all glob pattern
t . Parallel ( )
destinationServiceAccounts := [ ] v1alpha1 . ApplicationDestinationServiceAccount {
{
Server : "https://kubernetes.svc.local" ,
Namespace : "testns1" ,
DefaultServiceAccount : "test-sa-2" ,
} ,
{
Server : "https://kubernetes.svc.local" ,
Namespace : "default" ,
DefaultServiceAccount : "default-sa" ,
} ,
{
Server : "https://kubernetes.svc.local" ,
Namespace : "*" ,
DefaultServiceAccount : "test-sa" ,
} ,
}
destinationNamespace := "testns"
destinationServerURL := "https://kubernetes.svc.local"
applicationNamespace := "argocd-ns"
expectedSA := "system:serviceaccount:testns:test-sa"
f := setup ( destinationServiceAccounts , destinationNamespace , destinationServerURL , applicationNamespace )
// when
2025-06-23 01:32:19 +00:00
sa , err := deriveServiceAccountToImpersonate ( f . project , f . application , f . cluster )
2024-10-03 14:37:50 +00:00
// then, there should not be any error and the catch all service account should be returned
require . NoError ( t , err )
assert . Equal ( t , expectedSA , sa )
} )
t . Run ( "match done via invalid glob pattern" , func ( t * testing . T ) {
// given an application referring a project with a destination service account having an invalid glob pattern for namespace
t . Parallel ( )
destinationServiceAccounts := [ ] v1alpha1 . ApplicationDestinationServiceAccount {
{
Server : "https://kubernetes.svc.local" ,
Namespace : "e[[a*" ,
DefaultServiceAccount : "test-sa" ,
} ,
}
destinationNamespace := "testns"
destinationServerURL := "https://kubernetes.svc.local"
applicationNamespace := "argocd-ns"
expectedSA := ""
f := setup ( destinationServiceAccounts , destinationNamespace , destinationServerURL , applicationNamespace )
// when
2025-06-23 01:32:19 +00:00
sa , err := deriveServiceAccountToImpersonate ( f . project , f . application , f . cluster )
2024-10-03 14:37:50 +00:00
// then, there must be an error as the glob pattern is invalid.
require . ErrorContains ( t , err , "invalid glob pattern for destination namespace" )
assert . Equal ( t , expectedSA , sa )
} )
t . Run ( "sa specified with a namespace" , func ( t * testing . T ) {
// given an application referring a project with multiple destination service accounts having a matching service account specified with its namespace
t . Parallel ( )
destinationServiceAccounts := [ ] v1alpha1 . ApplicationDestinationServiceAccount {
{
Server : "https://kubernetes.svc.local" ,
Namespace : "testns" ,
DefaultServiceAccount : "myns:test-sa" ,
} ,
{
Server : "https://kubernetes.svc.local" ,
Namespace : "default" ,
DefaultServiceAccount : "default-sa" ,
} ,
{
Server : "https://kubernetes.svc.local" ,
Namespace : "*" ,
DefaultServiceAccount : "test-sa" ,
} ,
}
destinationNamespace := "testns"
destinationServerURL := "https://kubernetes.svc.local"
applicationNamespace := "argocd-ns"
expectedSA := "system:serviceaccount:myns:test-sa"
f := setup ( destinationServiceAccounts , destinationNamespace , destinationServerURL , applicationNamespace )
// when
2025-06-23 01:32:19 +00:00
sa , err := deriveServiceAccountToImpersonate ( f . project , f . application , f . cluster )
assert . Equal ( t , expectedSA , sa )
// then, there should not be any error and the service account with its namespace should be returned.
require . NoError ( t , err )
} )
t . Run ( "app destination name instead of server URL" , func ( t * testing . T ) {
t . Parallel ( )
destinationServiceAccounts := [ ] v1alpha1 . ApplicationDestinationServiceAccount {
{
Server : "https://kubernetes.svc.local" ,
Namespace : "*" ,
DefaultServiceAccount : "test-sa" ,
} ,
}
destinationNamespace := "testns"
destinationServerURL := "https://kubernetes.svc.local"
applicationNamespace := "argocd-ns"
expectedSA := "system:serviceaccount:testns:test-sa"
f := setup ( destinationServiceAccounts , destinationNamespace , destinationServerURL , applicationNamespace )
// Use destination name instead of server URL
f . application . Spec . Destination . Server = ""
f . application . Spec . Destination . Name = f . cluster . Name
// when
sa , err := deriveServiceAccountToImpersonate ( f . project , f . application , f . cluster )
2024-10-03 14:37:50 +00:00
assert . Equal ( t , expectedSA , sa )
// then, there should not be any error and the service account with its namespace should be returned.
require . NoError ( t , err )
} )
}
func TestDeriveServiceAccountMatchingServers ( t * testing . T ) {
2025-06-08 13:10:55 +00:00
t . Parallel ( )
2024-10-03 14:37:50 +00:00
type fixture struct {
project * v1alpha1 . AppProject
application * v1alpha1 . Application
2025-06-23 01:32:19 +00:00
cluster * v1alpha1 . Cluster
2024-10-03 14:37:50 +00:00
}
setup := func ( destinationServiceAccounts [ ] v1alpha1 . ApplicationDestinationServiceAccount , destinationNamespace , destinationServerURL , applicationNamespace string ) * fixture {
project := & v1alpha1 . AppProject {
2024-12-31 08:34:11 +00:00
ObjectMeta : metav1 . ObjectMeta {
2024-10-03 14:37:50 +00:00
Namespace : "argocd-ns" ,
Name : "testProj" ,
} ,
Spec : v1alpha1 . AppProjectSpec {
DestinationServiceAccounts : destinationServiceAccounts ,
} ,
}
app := & v1alpha1 . Application {
2024-12-31 08:34:11 +00:00
ObjectMeta : metav1 . ObjectMeta {
2024-10-03 14:37:50 +00:00
Namespace : applicationNamespace ,
Name : "testApp" ,
} ,
Spec : v1alpha1 . ApplicationSpec {
Project : "testProj" ,
Destination : v1alpha1 . ApplicationDestination {
Server : destinationServerURL ,
Namespace : destinationNamespace ,
} ,
} ,
}
2025-06-23 01:32:19 +00:00
cluster := & v1alpha1 . Cluster {
Server : "https://kubernetes.svc.local" ,
Name : "test-cluster" ,
}
2024-10-03 14:37:50 +00:00
return & fixture {
project : project ,
application : app ,
2025-06-23 01:32:19 +00:00
cluster : cluster ,
2024-10-03 14:37:50 +00:00
}
}
t . Run ( "exact one match with multiple destination service accounts" , func ( t * testing . T ) {
// given an application referring a project with multiple destination service accounts and one exact match for application destination
t . Parallel ( )
destinationServiceAccounts := [ ] v1alpha1 . ApplicationDestinationServiceAccount {
{
Server : "https://kubernetes.svc.local" ,
Namespace : "guestbook" ,
DefaultServiceAccount : "guestbook-sa" ,
} ,
{
Server : "https://abc.svc.local" ,
Namespace : "guestbook" ,
DefaultServiceAccount : "guestbook-test-sa" ,
} ,
{
Server : "https://cde.svc.local" ,
Namespace : "guestbook" ,
DefaultServiceAccount : "default-sa" ,
} ,
{
Server : "https://kubernetes.svc.local" ,
Namespace : "testns" ,
DefaultServiceAccount : "test-sa" ,
} ,
}
destinationNamespace := "testns"
destinationServerURL := "https://kubernetes.svc.local"
applicationNamespace := "argocd-ns"
expectedSA := "system:serviceaccount:testns:test-sa"
f := setup ( destinationServiceAccounts , destinationNamespace , destinationServerURL , applicationNamespace )
// when
2025-06-23 01:32:19 +00:00
sa , err := deriveServiceAccountToImpersonate ( f . project , f . application , f . cluster )
2024-10-03 14:37:50 +00:00
// then, there should not be any error and the right service account must be returned.
require . NoError ( t , err )
assert . Equal ( t , expectedSA , sa )
} )
t . Run ( "first match to be used when multiple matches are available" , func ( t * testing . T ) {
// given an application referring a project with multiple destination service accounts and multiple matches for application destination
t . Parallel ( )
destinationServiceAccounts := [ ] v1alpha1 . ApplicationDestinationServiceAccount {
{
Server : "https://kubernetes.svc.local" ,
Namespace : "testns" ,
DefaultServiceAccount : "test-sa" ,
} ,
{
Server : "https://kubernetes.svc.local" ,
Namespace : "testns" ,
DefaultServiceAccount : "test-sa-2" ,
} ,
{
Server : "https://kubernetes.svc.local" ,
Namespace : "default" ,
DefaultServiceAccount : "default-sa" ,
} ,
{
Server : "https://kubernetes.svc.local" ,
Namespace : "guestbook" ,
DefaultServiceAccount : "guestbook-sa" ,
} ,
}
destinationNamespace := "testns"
destinationServerURL := "https://kubernetes.svc.local"
applicationNamespace := "argocd-ns"
expectedSA := "system:serviceaccount:testns:test-sa"
f := setup ( destinationServiceAccounts , destinationNamespace , destinationServerURL , applicationNamespace )
// when
2025-06-23 01:32:19 +00:00
sa , err := deriveServiceAccountToImpersonate ( f . project , f . application , f . cluster )
2024-10-03 14:37:50 +00:00
// then, there should not be any error and first matching service account should be used
require . NoError ( t , err )
assert . Equal ( t , expectedSA , sa )
} )
t . Run ( "first match to be used when glob pattern is used" , func ( t * testing . T ) {
// given an application referring a project with multiple destination service accounts with a matching glob pattern and exact match
t . Parallel ( )
destinationServiceAccounts := [ ] v1alpha1 . ApplicationDestinationServiceAccount {
{
Server : "https://kubernetes.svc.local" ,
Namespace : "test*" ,
DefaultServiceAccount : "test-sa" ,
} ,
{
Server : "https://kubernetes.svc.local" ,
Namespace : "testns" ,
DefaultServiceAccount : "test-sa-2" ,
} ,
{
Server : "https://kubernetes.svc.local" ,
Namespace : "default" ,
DefaultServiceAccount : "default-sa" ,
} ,
}
destinationNamespace := "testns"
destinationServerURL := "https://kubernetes.svc.local"
applicationNamespace := "argocd-ns"
expectedSA := "system:serviceaccount:testns:test-sa"
f := setup ( destinationServiceAccounts , destinationNamespace , destinationServerURL , applicationNamespace )
// when
2025-06-23 01:32:19 +00:00
sa , err := deriveServiceAccountToImpersonate ( f . project , f . application , f . cluster )
2024-10-03 14:37:50 +00:00
assert . Equal ( t , expectedSA , sa )
// then, there should not be any error and the service account of the glob pattern, being the first match should be returned.
require . NoError ( t , err )
} )
t . Run ( "no match among a valid list" , func ( t * testing . T ) {
// given an application referring a project with multiple destination service accounts with no match
t . Parallel ( )
destinationServiceAccounts := [ ] v1alpha1 . ApplicationDestinationServiceAccount {
{
Server : "https://kubernetes.svc.local" ,
Namespace : "testns" ,
DefaultServiceAccount : "test-sa" ,
} ,
{
Server : "https://abc.svc.local" ,
Namespace : "testns" ,
DefaultServiceAccount : "test-sa-2" ,
} ,
{
Server : "https://cde.svc.local" ,
Namespace : "default" ,
DefaultServiceAccount : "default-sa" ,
} ,
}
destinationNamespace := "testns"
destinationServerURL := "https://xyz.svc.local"
applicationNamespace := "argocd-ns"
expectedSA := ""
expectedErr := "no matching service account found for destination server https://xyz.svc.local and namespace testns"
f := setup ( destinationServiceAccounts , destinationNamespace , destinationServerURL , applicationNamespace )
// when
2025-06-23 01:32:19 +00:00
sa , err := deriveServiceAccountToImpersonate ( f . project , f . application , & v1alpha1 . Cluster { Server : destinationServerURL } )
2024-10-03 14:37:50 +00:00
// then, there an error with appropriate message must be returned
require . EqualError ( t , err , expectedErr )
assert . Equal ( t , expectedSA , sa )
} )
t . Run ( "match done via catch all glob pattern" , func ( t * testing . T ) {
// given an application referring a project with multiple destination service accounts with matching catch all glob pattern
t . Parallel ( )
destinationServiceAccounts := [ ] v1alpha1 . ApplicationDestinationServiceAccount {
{
Server : "https://kubernetes.svc.local" ,
Namespace : "testns1" ,
DefaultServiceAccount : "test-sa-2" ,
} ,
{
Server : "https://kubernetes.svc.local" ,
Namespace : "default" ,
DefaultServiceAccount : "default-sa" ,
} ,
{
Server : "*" ,
Namespace : "*" ,
DefaultServiceAccount : "test-sa" ,
} ,
}
destinationNamespace := "testns"
destinationServerURL := "https://localhost:6443"
applicationNamespace := "argocd-ns"
expectedSA := "system:serviceaccount:testns:test-sa"
f := setup ( destinationServiceAccounts , destinationNamespace , destinationServerURL , applicationNamespace )
// when
2025-06-23 01:32:19 +00:00
sa , err := deriveServiceAccountToImpersonate ( f . project , f . application , f . cluster )
2024-10-03 14:37:50 +00:00
// then, there should not be any error and the service account of the glob pattern match must be returned.
require . NoError ( t , err )
assert . Equal ( t , expectedSA , sa )
} )
t . Run ( "match done via invalid glob pattern" , func ( t * testing . T ) {
// given an application referring a project with a destination service account having an invalid glob pattern for server
t . Parallel ( )
destinationServiceAccounts := [ ] v1alpha1 . ApplicationDestinationServiceAccount {
{
Server : "e[[a*" ,
Namespace : "test-ns" ,
DefaultServiceAccount : "test-sa" ,
} ,
}
destinationNamespace := "testns"
destinationServerURL := "https://kubernetes.svc.local"
applicationNamespace := "argocd-ns"
expectedSA := ""
f := setup ( destinationServiceAccounts , destinationNamespace , destinationServerURL , applicationNamespace )
// when
2025-06-23 01:32:19 +00:00
sa , err := deriveServiceAccountToImpersonate ( f . project , f . application , f . cluster )
2024-10-03 14:37:50 +00:00
// then, there must be an error as the glob pattern is invalid.
require . ErrorContains ( t , err , "invalid glob pattern for destination server" )
assert . Equal ( t , expectedSA , sa )
} )
t . Run ( "sa specified with a namespace" , func ( t * testing . T ) {
// given app sync impersonation feature is enabled and matching service account is prefixed with a namespace
t . Parallel ( )
destinationServiceAccounts := [ ] v1alpha1 . ApplicationDestinationServiceAccount {
{
Server : "https://abc.svc.local" ,
Namespace : "testns" ,
DefaultServiceAccount : "myns:test-sa" ,
} ,
{
Server : "https://kubernetes.svc.local" ,
Namespace : "default" ,
DefaultServiceAccount : "default-sa" ,
} ,
{
Server : "*" ,
Namespace : "*" ,
DefaultServiceAccount : "test-sa" ,
} ,
}
destinationNamespace := "testns"
destinationServerURL := "https://abc.svc.local"
applicationNamespace := "argocd-ns"
expectedSA := "system:serviceaccount:myns:test-sa"
f := setup ( destinationServiceAccounts , destinationNamespace , destinationServerURL , applicationNamespace )
// when
2025-06-23 01:32:19 +00:00
sa , err := deriveServiceAccountToImpersonate ( f . project , f . application , & v1alpha1 . Cluster { Server : destinationServerURL } )
2024-10-03 14:37:50 +00:00
// then, there should not be any error and the service account with the given namespace prefix must be returned.
require . NoError ( t , err )
assert . Equal ( t , expectedSA , sa )
} )
2025-06-23 01:32:19 +00:00
t . Run ( "app destination name instead of server URL" , func ( t * testing . T ) {
t . Parallel ( )
destinationServiceAccounts := [ ] v1alpha1 . ApplicationDestinationServiceAccount {
{
Server : "https://kubernetes.svc.local" ,
Namespace : "*" ,
DefaultServiceAccount : "test-sa" ,
} ,
}
destinationNamespace := "testns"
destinationServerURL := "https://kubernetes.svc.local"
applicationNamespace := "argocd-ns"
expectedSA := "system:serviceaccount:testns:test-sa"
f := setup ( destinationServiceAccounts , destinationNamespace , destinationServerURL , applicationNamespace )
// Use destination name instead of server URL
f . application . Spec . Destination . Server = ""
f . application . Spec . Destination . Name = f . cluster . Name
// when
sa , err := deriveServiceAccountToImpersonate ( f . project , f . application , f . cluster )
assert . Equal ( t , expectedSA , sa )
// then, there should not be any error and the service account with its namespace should be returned.
require . NoError ( t , err )
} )
2024-10-03 14:37:50 +00:00
}
func TestSyncWithImpersonate ( t * testing . T ) {
type fixture struct {
application * v1alpha1 . Application
2025-07-03 17:16:58 +00:00
project * v1alpha1 . AppProject
2024-10-03 14:37:50 +00:00
controller * ApplicationController
}
setup := func ( impersonationEnabled bool , destinationNamespace , serviceAccountName string ) * fixture {
app := newFakeApp ( )
app . Status . OperationState = nil
app . Status . History = nil
project := & v1alpha1 . AppProject {
2024-12-31 08:34:11 +00:00
ObjectMeta : metav1 . ObjectMeta {
2024-10-03 14:37:50 +00:00
Namespace : test . FakeArgoCDNamespace ,
Name : "default" ,
} ,
Spec : v1alpha1 . AppProjectSpec {
2025-06-16 15:02:32 +00:00
DestinationServiceAccounts : [ ] v1alpha1 . ApplicationDestinationServiceAccount {
2024-10-03 14:37:50 +00:00
{
Server : "https://localhost:6443" ,
Namespace : destinationNamespace ,
DefaultServiceAccount : serviceAccountName ,
} ,
} ,
} ,
}
additionalObjs := [ ] runtime . Object { }
if serviceAccountName != "" {
syncServiceAccount := & corev1 . ServiceAccount {
2024-12-31 08:34:11 +00:00
ObjectMeta : metav1 . ObjectMeta {
2024-10-03 14:37:50 +00:00
Name : serviceAccountName ,
Namespace : test . FakeDestNamespace ,
} ,
}
additionalObjs = append ( additionalObjs , syncServiceAccount )
}
data := fakeData {
apps : [ ] runtime . Object { app , project } ,
manifestResponse : & apiclient . ManifestResponse {
Manifests : [ ] string { } ,
Namespace : test . FakeDestNamespace ,
Server : "https://localhost:6443" ,
Revision : "abc123" ,
} ,
managedLiveObjs : map [ kube . ResourceKey ] * unstructured . Unstructured { } ,
configMapData : map [ string ] string {
"application.sync.impersonation.enabled" : strconv . FormatBool ( impersonationEnabled ) ,
} ,
additionalObjs : additionalObjs ,
}
2025-11-02 12:39:24 +00:00
ctrl := newFakeController ( t . Context ( ) , & data , nil )
2024-10-03 14:37:50 +00:00
return & fixture {
application : app ,
2025-07-03 17:16:58 +00:00
project : project ,
2024-10-03 14:37:50 +00:00
controller : ctrl ,
}
}
t . Run ( "sync with impersonation and no matching service account" , func ( t * testing . T ) {
// given app sync impersonation feature is enabled with an application referring a project no matching service account
f := setup ( true , test . FakeArgoCDNamespace , "" )
opMessage := "failed to find a matching service account to impersonate: no matching service account found for destination server https://localhost:6443 and namespace fake-dest-ns"
opState := & v1alpha1 . OperationState {
Operation : v1alpha1 . Operation {
Sync : & v1alpha1 . SyncOperation {
Source : & v1alpha1 . ApplicationSource { } ,
} ,
} ,
2025-07-02 19:00:57 +00:00
Phase : synccommon . OperationRunning ,
2024-10-03 14:37:50 +00:00
}
// when
2025-07-03 17:16:58 +00:00
f . controller . appStateManager . SyncAppState ( f . application , f . project , opState )
2024-10-03 14:37:50 +00:00
// then, app sync should fail with expected error message in operation state
2025-07-02 19:00:57 +00:00
assert . Equal ( t , synccommon . OperationError , opState . Phase )
2024-10-03 14:37:50 +00:00
assert . Contains ( t , opState . Message , opMessage )
} )
t . Run ( "sync with impersonation and empty service account match" , func ( t * testing . T ) {
// given app sync impersonation feature is enabled with an application referring a project matching service account that is an empty string
f := setup ( true , test . FakeDestNamespace , "" )
opMessage := "failed to find a matching service account to impersonate: default service account contains invalid chars ''"
opState := & v1alpha1 . OperationState {
Operation : v1alpha1 . Operation {
Sync : & v1alpha1 . SyncOperation {
Source : & v1alpha1 . ApplicationSource { } ,
} ,
} ,
2025-07-02 19:00:57 +00:00
Phase : synccommon . OperationRunning ,
2024-10-03 14:37:50 +00:00
}
// when
2025-07-03 17:16:58 +00:00
f . controller . appStateManager . SyncAppState ( f . application , f . project , opState )
2024-10-03 14:37:50 +00:00
// then app sync should fail with expected error message in operation state
2025-07-02 19:00:57 +00:00
assert . Equal ( t , synccommon . OperationError , opState . Phase )
2024-10-03 14:37:50 +00:00
assert . Contains ( t , opState . Message , opMessage )
} )
t . Run ( "sync with impersonation and matching sa" , func ( t * testing . T ) {
// given app sync impersonation feature is enabled with an application referring a project matching service account
f := setup ( true , test . FakeDestNamespace , "test-sa" )
opMessage := "successfully synced (no more tasks)"
opState := & v1alpha1 . OperationState {
Operation : v1alpha1 . Operation {
Sync : & v1alpha1 . SyncOperation {
Source : & v1alpha1 . ApplicationSource { } ,
} ,
} ,
2025-07-02 19:00:57 +00:00
Phase : synccommon . OperationRunning ,
2024-10-03 14:37:50 +00:00
}
// when
2025-07-03 17:16:58 +00:00
f . controller . appStateManager . SyncAppState ( f . application , f . project , opState )
2024-10-03 14:37:50 +00:00
// then app sync should not fail
2025-07-02 19:00:57 +00:00
assert . Equal ( t , synccommon . OperationSucceeded , opState . Phase )
2024-10-03 14:37:50 +00:00
assert . Contains ( t , opState . Message , opMessage )
} )
t . Run ( "sync without impersonation" , func ( t * testing . T ) {
// given app sync impersonation feature is disabled with an application referring a project matching service account
f := setup ( false , test . FakeDestNamespace , "" )
opMessage := "successfully synced (no more tasks)"
opState := & v1alpha1 . OperationState {
Operation : v1alpha1 . Operation {
Sync : & v1alpha1 . SyncOperation {
Source : & v1alpha1 . ApplicationSource { } ,
} ,
} ,
2025-07-02 19:00:57 +00:00
Phase : synccommon . OperationRunning ,
2024-10-03 14:37:50 +00:00
}
// when
2025-07-03 17:16:58 +00:00
f . controller . appStateManager . SyncAppState ( f . application , f . project , opState )
2024-10-03 14:37:50 +00:00
// then application sync should pass using the control plane service account
2025-07-02 19:00:57 +00:00
assert . Equal ( t , synccommon . OperationSucceeded , opState . Phase )
2024-10-03 14:37:50 +00:00
assert . Contains ( t , opState . Message , opMessage )
2025-06-23 01:32:19 +00:00
} )
t . Run ( "app destination name instead of server URL" , func ( t * testing . T ) {
// given app sync impersonation feature is enabled with an application referring a project matching service account
f := setup ( true , test . FakeDestNamespace , "test-sa" )
opMessage := "successfully synced (no more tasks)"
opState := & v1alpha1 . OperationState {
Operation : v1alpha1 . Operation {
Sync : & v1alpha1 . SyncOperation {
Source : & v1alpha1 . ApplicationSource { } ,
} ,
} ,
2025-07-02 19:00:57 +00:00
Phase : synccommon . OperationRunning ,
2025-06-23 01:32:19 +00:00
}
f . application . Spec . Destination . Server = ""
f . application . Spec . Destination . Name = "minikube"
// when
2025-07-03 17:16:58 +00:00
f . controller . appStateManager . SyncAppState ( f . application , f . project , opState )
2025-06-23 01:32:19 +00:00
// then app sync should not fail
2025-07-02 19:00:57 +00:00
assert . Equal ( t , synccommon . OperationSucceeded , opState . Phase )
2025-06-23 01:32:19 +00:00
assert . Contains ( t , opState . Message , opMessage )
2024-10-03 14:37:50 +00:00
} )
}
2025-06-13 21:58:07 +00:00
func TestClientSideApplyMigration ( t * testing . T ) {
t . Parallel ( )
type fixture struct {
application * v1alpha1 . Application
2025-07-03 17:16:58 +00:00
project * v1alpha1 . AppProject
2025-06-13 21:58:07 +00:00
controller * ApplicationController
}
setup := func ( disableMigration bool , customManager string ) * fixture {
app := newFakeApp ( )
app . Status . OperationState = nil
app . Status . History = nil
// Add sync options
if disableMigration {
app . Spec . SyncPolicy . SyncOptions = append ( app . Spec . SyncPolicy . SyncOptions , "DisableClientSideApplyMigration=true" )
}
// Add custom manager annotation if specified
if customManager != "" {
app . Annotations = map [ string ] string {
"argocd.argoproj.io/client-side-apply-migration-manager" : customManager ,
}
}
project := & v1alpha1 . AppProject {
ObjectMeta : metav1 . ObjectMeta {
Namespace : test . FakeArgoCDNamespace ,
Name : "default" ,
} ,
}
data := fakeData {
apps : [ ] runtime . Object { app , project } ,
manifestResponse : & apiclient . ManifestResponse {
Manifests : [ ] string { } ,
Namespace : test . FakeDestNamespace ,
Server : test . FakeClusterURL ,
Revision : "abc123" ,
} ,
managedLiveObjs : make ( map [ kube . ResourceKey ] * unstructured . Unstructured ) ,
}
2025-11-02 12:39:24 +00:00
ctrl := newFakeController ( t . Context ( ) , & data , nil )
2025-06-13 21:58:07 +00:00
return & fixture {
application : app ,
2025-07-03 17:16:58 +00:00
project : project ,
2025-06-13 21:58:07 +00:00
controller : ctrl ,
}
}
t . Run ( "client-side apply migration enabled by default" , func ( t * testing . T ) {
// given
t . Parallel ( )
f := setup ( false , "" )
// when
opState := & v1alpha1 . OperationState { Operation : v1alpha1 . Operation {
Sync : & v1alpha1 . SyncOperation {
Source : & v1alpha1 . ApplicationSource { } ,
} ,
} }
2025-07-03 17:16:58 +00:00
f . controller . appStateManager . SyncAppState ( f . application , f . project , opState )
2025-06-13 21:58:07 +00:00
// then
2025-07-02 19:00:57 +00:00
assert . Equal ( t , synccommon . OperationSucceeded , opState . Phase )
2025-06-13 21:58:07 +00:00
assert . Contains ( t , opState . Message , "successfully synced" )
} )
t . Run ( "client-side apply migration disabled" , func ( t * testing . T ) {
// given
t . Parallel ( )
f := setup ( true , "" )
// when
opState := & v1alpha1 . OperationState { Operation : v1alpha1 . Operation {
Sync : & v1alpha1 . SyncOperation {
Source : & v1alpha1 . ApplicationSource { } ,
} ,
} }
2025-07-03 17:16:58 +00:00
f . controller . appStateManager . SyncAppState ( f . application , f . project , opState )
2025-06-13 21:58:07 +00:00
// then
2025-07-02 19:00:57 +00:00
assert . Equal ( t , synccommon . OperationSucceeded , opState . Phase )
2025-06-13 21:58:07 +00:00
assert . Contains ( t , opState . Message , "successfully synced" )
} )
t . Run ( "client-side apply migration with custom manager" , func ( t * testing . T ) {
// given
t . Parallel ( )
f := setup ( false , "my-custom-manager" )
// when
opState := & v1alpha1 . OperationState { Operation : v1alpha1 . Operation {
Sync : & v1alpha1 . SyncOperation {
Source : & v1alpha1 . ApplicationSource { } ,
} ,
} }
2025-07-03 17:16:58 +00:00
f . controller . appStateManager . SyncAppState ( f . application , f . project , opState )
2025-06-13 21:58:07 +00:00
// then
2025-07-02 19:00:57 +00:00
assert . Equal ( t , synccommon . OperationSucceeded , opState . Phase )
2025-06-13 21:58:07 +00:00
assert . Contains ( t , opState . Message , "successfully synced" )
} )
}
2025-06-16 15:02:32 +00:00
func dig ( obj any , path ... any ) any {
2024-04-03 22:56:59 +00:00
i := obj
for _ , segment := range path {
2025-06-16 15:02:32 +00:00
switch segment := segment . ( type ) {
2024-04-03 22:56:59 +00:00
case int :
2025-06-16 15:02:32 +00:00
i = i . ( [ ] any ) [ segment ]
2024-04-03 22:56:59 +00:00
case string :
2025-06-16 15:02:32 +00:00
i = i . ( map [ string ] any ) [ segment ]
2024-04-03 22:56:59 +00:00
default :
panic ( "invalid path for object" )
}
}
2025-06-16 15:02:32 +00:00
return i
2024-04-03 22:56:59 +00:00
}