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 }