github.com/argoproj/argo-cd/v3@v3.2.1/applicationset/controllers/template/template_test.go (about)

     1  package template
     2  
     3  import (
     4  	"errors"
     5  	"maps"
     6  	"testing"
     7  
     8  	"github.com/stretchr/testify/mock"
     9  	"github.com/stretchr/testify/require"
    10  
    11  	log "github.com/sirupsen/logrus"
    12  	"github.com/stretchr/testify/assert"
    13  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    14  	"k8s.io/apimachinery/pkg/runtime"
    15  
    16  	"github.com/argoproj/argo-cd/v3/applicationset/generators"
    17  	genmock "github.com/argoproj/argo-cd/v3/applicationset/generators/mocks"
    18  	"github.com/argoproj/argo-cd/v3/applicationset/utils"
    19  	rendmock "github.com/argoproj/argo-cd/v3/applicationset/utils/mocks"
    20  	"github.com/argoproj/argo-cd/v3/pkg/apis/application"
    21  	"github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
    22  )
    23  
    24  func TestGenerateApplications(t *testing.T) {
    25  	scheme := runtime.NewScheme()
    26  	err := v1alpha1.AddToScheme(scheme)
    27  	require.NoError(t, err)
    28  
    29  	err = v1alpha1.AddToScheme(scheme)
    30  	require.NoError(t, err)
    31  
    32  	for _, c := range []struct {
    33  		name                string
    34  		params              []map[string]any
    35  		template            v1alpha1.ApplicationSetTemplate
    36  		generateParamsError error
    37  		rendererError       error
    38  		expectErr           bool
    39  		expectedReason      v1alpha1.ApplicationSetReasonType
    40  	}{
    41  		{
    42  			name:   "Generate two applications",
    43  			params: []map[string]any{{"name": "app1"}, {"name": "app2"}},
    44  			template: v1alpha1.ApplicationSetTemplate{
    45  				ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{
    46  					Name:      "name",
    47  					Namespace: "namespace",
    48  					Labels:    map[string]string{"label_name": "label_value"},
    49  				},
    50  				Spec: v1alpha1.ApplicationSpec{},
    51  			},
    52  			expectedReason: "",
    53  		},
    54  		{
    55  			name:                "Handles error from the generator",
    56  			generateParamsError: errors.New("error"),
    57  			expectErr:           true,
    58  			expectedReason:      v1alpha1.ApplicationSetReasonApplicationParamsGenerationError,
    59  		},
    60  		{
    61  			name:   "Handles error from the render",
    62  			params: []map[string]any{{"name": "app1"}, {"name": "app2"}},
    63  			template: v1alpha1.ApplicationSetTemplate{
    64  				ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{
    65  					Name:      "name",
    66  					Namespace: "namespace",
    67  					Labels:    map[string]string{"label_name": "label_value"},
    68  				},
    69  				Spec: v1alpha1.ApplicationSpec{},
    70  			},
    71  			rendererError:  errors.New("error"),
    72  			expectErr:      true,
    73  			expectedReason: v1alpha1.ApplicationSetReasonRenderTemplateParamsError,
    74  		},
    75  	} {
    76  		cc := c
    77  		app := v1alpha1.Application{
    78  			ObjectMeta: metav1.ObjectMeta{
    79  				Name:      "test",
    80  				Namespace: "namespace",
    81  			},
    82  			TypeMeta: metav1.TypeMeta{
    83  				Kind:       application.ApplicationKind,
    84  				APIVersion: "argoproj.io/v1alpha1",
    85  			},
    86  		}
    87  
    88  		t.Run(cc.name, func(t *testing.T) {
    89  			generatorMock := genmock.Generator{}
    90  			generator := v1alpha1.ApplicationSetGenerator{
    91  				List: &v1alpha1.ListGenerator{},
    92  			}
    93  
    94  			generatorMock.On("GenerateParams", &generator, mock.AnythingOfType("*v1alpha1.ApplicationSet"), mock.Anything).
    95  				Return(cc.params, cc.generateParamsError)
    96  
    97  			generatorMock.On("GetTemplate", &generator).
    98  				Return(&v1alpha1.ApplicationSetTemplate{})
    99  
   100  			rendererMock := rendmock.Renderer{}
   101  
   102  			var expectedApps []v1alpha1.Application
   103  
   104  			if cc.generateParamsError == nil {
   105  				for _, p := range cc.params {
   106  					if cc.rendererError != nil {
   107  						rendererMock.On("RenderTemplateParams", GetTempApplication(cc.template), mock.AnythingOfType("*v1alpha1.ApplicationSetSyncPolicy"), p, false, []string(nil)).
   108  							Return(nil, cc.rendererError)
   109  					} else {
   110  						rendererMock.On("RenderTemplateParams", GetTempApplication(cc.template), mock.AnythingOfType("*v1alpha1.ApplicationSetSyncPolicy"), p, false, []string(nil)).
   111  							Return(&app, nil)
   112  						expectedApps = append(expectedApps, app)
   113  					}
   114  				}
   115  			}
   116  
   117  			generators := map[string]generators.Generator{
   118  				"List": &generatorMock,
   119  			}
   120  			renderer := &rendererMock
   121  
   122  			got, reason, err := GenerateApplications(log.NewEntry(log.StandardLogger()), v1alpha1.ApplicationSet{
   123  				ObjectMeta: metav1.ObjectMeta{
   124  					Name:      "name",
   125  					Namespace: "namespace",
   126  				},
   127  				Spec: v1alpha1.ApplicationSetSpec{
   128  					Generators: []v1alpha1.ApplicationSetGenerator{generator},
   129  					Template:   cc.template,
   130  				},
   131  			},
   132  				generators,
   133  				renderer,
   134  				nil,
   135  			)
   136  
   137  			if cc.expectErr {
   138  				require.Error(t, err)
   139  			} else {
   140  				require.NoError(t, err)
   141  			}
   142  			assert.Equal(t, expectedApps, got)
   143  			assert.Equal(t, cc.expectedReason, reason)
   144  			generatorMock.AssertNumberOfCalls(t, "GenerateParams", 1)
   145  
   146  			if cc.generateParamsError == nil {
   147  				rendererMock.AssertNumberOfCalls(t, "RenderTemplateParams", len(cc.params))
   148  			}
   149  		})
   150  	}
   151  }
   152  
   153  func TestMergeTemplateApplications(t *testing.T) {
   154  	for _, c := range []struct {
   155  		name             string
   156  		params           []map[string]any
   157  		template         v1alpha1.ApplicationSetTemplate
   158  		overrideTemplate v1alpha1.ApplicationSetTemplate
   159  		expectedMerged   v1alpha1.ApplicationSetTemplate
   160  		expectedApps     []v1alpha1.Application
   161  	}{
   162  		{
   163  			name:   "Generate app",
   164  			params: []map[string]any{{"name": "app1"}},
   165  			template: v1alpha1.ApplicationSetTemplate{
   166  				ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{
   167  					Name:      "name",
   168  					Namespace: "namespace",
   169  					Labels:    map[string]string{"label_name": "label_value"},
   170  				},
   171  				Spec: v1alpha1.ApplicationSpec{},
   172  			},
   173  			overrideTemplate: v1alpha1.ApplicationSetTemplate{
   174  				ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{
   175  					Name:   "test",
   176  					Labels: map[string]string{"foo": "bar"},
   177  				},
   178  				Spec: v1alpha1.ApplicationSpec{},
   179  			},
   180  			expectedMerged: v1alpha1.ApplicationSetTemplate{
   181  				ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{
   182  					Name:      "test",
   183  					Namespace: "namespace",
   184  					Labels:    map[string]string{"label_name": "label_value", "foo": "bar"},
   185  				},
   186  				Spec: v1alpha1.ApplicationSpec{},
   187  			},
   188  			expectedApps: []v1alpha1.Application{
   189  				{
   190  					ObjectMeta: metav1.ObjectMeta{
   191  						Name:      "test",
   192  						Namespace: "test",
   193  						Labels:    map[string]string{"foo": "bar"},
   194  					},
   195  					Spec: v1alpha1.ApplicationSpec{},
   196  				},
   197  			},
   198  		},
   199  	} {
   200  		cc := c
   201  
   202  		t.Run(cc.name, func(t *testing.T) {
   203  			generatorMock := genmock.Generator{}
   204  			generator := v1alpha1.ApplicationSetGenerator{
   205  				List: &v1alpha1.ListGenerator{},
   206  			}
   207  
   208  			generatorMock.On("GenerateParams", &generator, mock.AnythingOfType("*v1alpha1.ApplicationSet"), mock.Anything).
   209  				Return(cc.params, nil)
   210  
   211  			generatorMock.On("GetTemplate", &generator).
   212  				Return(&cc.overrideTemplate)
   213  
   214  			rendererMock := rendmock.Renderer{}
   215  
   216  			rendererMock.On("RenderTemplateParams", GetTempApplication(cc.expectedMerged), mock.AnythingOfType("*v1alpha1.ApplicationSetSyncPolicy"), cc.params[0], false, []string(nil)).
   217  				Return(&cc.expectedApps[0], nil)
   218  
   219  			generators := map[string]generators.Generator{
   220  				"List": &generatorMock,
   221  			}
   222  			renderer := &rendererMock
   223  
   224  			got, _, _ := GenerateApplications(log.NewEntry(log.StandardLogger()), v1alpha1.ApplicationSet{
   225  				ObjectMeta: metav1.ObjectMeta{
   226  					Name:      "name",
   227  					Namespace: "namespace",
   228  				},
   229  				Spec: v1alpha1.ApplicationSetSpec{
   230  					Generators: []v1alpha1.ApplicationSetGenerator{generator},
   231  					Template:   cc.template,
   232  				},
   233  			},
   234  				generators,
   235  				renderer,
   236  				nil,
   237  			)
   238  
   239  			assert.Equal(t, cc.expectedApps, got)
   240  		})
   241  	}
   242  }
   243  
   244  // Test app generation from a go template application set using a pull request generator
   245  func TestGenerateAppsUsingPullRequestGenerator(t *testing.T) {
   246  	for _, cases := range []struct {
   247  		name        string
   248  		params      []map[string]any
   249  		template    v1alpha1.ApplicationSetTemplate
   250  		expectedApp []v1alpha1.Application
   251  	}{
   252  		{
   253  			name: "Generate an application from a go template application set manifest using a pull request generator",
   254  			params: []map[string]any{
   255  				{
   256  					"number":                                "1",
   257  					"title":                                 "title1",
   258  					"branch":                                "branch1",
   259  					"branch_slug":                           "branchSlug1",
   260  					"head_sha":                              "089d92cbf9ff857a39e6feccd32798ca700fb958",
   261  					"head_short_sha":                        "089d92cb",
   262  					"branch_slugify_default":                "feat/a_really+long_pull_request_name_to_test_argo_slugification_and_branch_name_shortening_feature",
   263  					"branch_slugify_smarttruncate_disabled": "feat/areallylongpullrequestnametotestargoslugificationandbranchnameshorteningfeature",
   264  					"branch_slugify_smarttruncate_enabled":  "feat/testwithsmarttruncateenabledramdomlonglistofcharacters",
   265  					"labels":                                []string{"label1"},
   266  				},
   267  			},
   268  			template: v1alpha1.ApplicationSetTemplate{
   269  				ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{
   270  					Name: "AppSet-{{.branch}}-{{.number}}",
   271  					Labels: map[string]string{
   272  						"app1":         "{{index .labels 0}}",
   273  						"branch-test1": "AppSet-{{.branch_slugify_default | slugify }}",
   274  						"branch-test2": "AppSet-{{.branch_slugify_smarttruncate_disabled | slugify 49 false }}",
   275  						"branch-test3": "AppSet-{{.branch_slugify_smarttruncate_enabled | slugify 50 true }}",
   276  					},
   277  				},
   278  				Spec: v1alpha1.ApplicationSpec{
   279  					Source: &v1alpha1.ApplicationSource{
   280  						RepoURL:        "https://testurl/testRepo",
   281  						TargetRevision: "{{.head_short_sha}}",
   282  					},
   283  					Destination: v1alpha1.ApplicationDestination{
   284  						Server:    "https://kubernetes.default.svc",
   285  						Namespace: "AppSet-{{.branch_slug}}-{{.head_sha}}",
   286  					},
   287  				},
   288  			},
   289  			expectedApp: []v1alpha1.Application{
   290  				{
   291  					ObjectMeta: metav1.ObjectMeta{
   292  						Name: "AppSet-branch1-1",
   293  						Labels: map[string]string{
   294  							"app1":         "label1",
   295  							"branch-test1": "AppSet-feat-a-really-long-pull-request-name-to-test-argo",
   296  							"branch-test2": "AppSet-feat-areallylongpullrequestnametotestargoslugific",
   297  							"branch-test3": "AppSet-feat",
   298  						},
   299  					},
   300  					Spec: v1alpha1.ApplicationSpec{
   301  						Source: &v1alpha1.ApplicationSource{
   302  							RepoURL:        "https://testurl/testRepo",
   303  							TargetRevision: "089d92cb",
   304  						},
   305  						Destination: v1alpha1.ApplicationDestination{
   306  							Server:    "https://kubernetes.default.svc",
   307  							Namespace: "AppSet-branchSlug1-089d92cbf9ff857a39e6feccd32798ca700fb958",
   308  						},
   309  					},
   310  				},
   311  			},
   312  		},
   313  	} {
   314  		t.Run(cases.name, func(t *testing.T) {
   315  			generatorMock := genmock.Generator{}
   316  			generator := v1alpha1.ApplicationSetGenerator{
   317  				PullRequest: &v1alpha1.PullRequestGenerator{},
   318  			}
   319  
   320  			generatorMock.On("GenerateParams", &generator, mock.AnythingOfType("*v1alpha1.ApplicationSet"), mock.Anything).
   321  				Return(cases.params, nil)
   322  
   323  			generatorMock.On("GetTemplate", &generator).
   324  				Return(&cases.template, nil)
   325  
   326  			generators := map[string]generators.Generator{
   327  				"PullRequest": &generatorMock,
   328  			}
   329  			renderer := &utils.Render{}
   330  
   331  			gotApp, _, _ := GenerateApplications(log.NewEntry(log.StandardLogger()), v1alpha1.ApplicationSet{
   332  				Spec: v1alpha1.ApplicationSetSpec{
   333  					GoTemplate: true,
   334  					Generators: []v1alpha1.ApplicationSetGenerator{{
   335  						PullRequest: &v1alpha1.PullRequestGenerator{},
   336  					}},
   337  					Template: cases.template,
   338  				},
   339  			},
   340  				generators,
   341  				renderer,
   342  				nil,
   343  			)
   344  			assert.Equal(t, cases.expectedApp[0].Name, gotApp[0].Name)
   345  			assert.Equal(t, cases.expectedApp[0].Spec.Source.TargetRevision, gotApp[0].Spec.Source.TargetRevision)
   346  			assert.Equal(t, cases.expectedApp[0].Spec.Destination.Namespace, gotApp[0].Spec.Destination.Namespace)
   347  			assert.True(t, maps.Equal(cases.expectedApp[0].Labels, gotApp[0].Labels))
   348  		})
   349  	}
   350  }