diff --git a/pkg/appdir/appset_directory.go b/pkg/appdir/appset_directory.go index 93a94ff0..4f7ca118 100644 --- a/pkg/appdir/appset_directory.go +++ b/pkg/appdir/appset_directory.go @@ -204,13 +204,18 @@ func (d *AppSetDirectory) RemoveApp(app v1alpha1.ApplicationSet) { } } +// containsKindApplicationSet checks if the file contains kind: ApplicationSet func containsKindApplicationSet(path string) bool { file, err := os.Open(path) if err != nil { - log.Printf("failed to open file %s: %v", path, err) + log.Error().Err(err).Stack().Msgf("failed to open file %s: %v", path, err) return false } - defer file.Close() + defer func() { + if err := file.Close(); err != nil { + log.Warn().Err(err).Stack().Msgf("failed to close file %s: %v", path, err) + } + }() scanner := bufio.NewScanner(file) for scanner.Scan() { @@ -222,7 +227,7 @@ func containsKindApplicationSet(path string) bool { } if err := scanner.Err(); err != nil { - log.Printf("error reading file %s: %v", path, err) + log.Error().Err(err).Stack().Msgf("error reading file %s: %v", path, err) } return false diff --git a/pkg/appdir/appset_directory_test.go b/pkg/appdir/appset_directory_test.go new file mode 100644 index 00000000..c2fa48c3 --- /dev/null +++ b/pkg/appdir/appset_directory_test.go @@ -0,0 +1,230 @@ +package appdir + +import ( + "os" + "path/filepath" + "testing" + + "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" + "github.com/stretchr/testify/assert" + "github.com/zapier/kubechecks/pkg/git" + v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestAppSetDirectory_ProcessApp(t *testing.T) { + + type args struct { + app v1alpha1.ApplicationSet + } + tests := []struct { + name string + input *AppSetDirectory + args args + expected *AppSetDirectory + }{ + { + name: "normal process, expect to get the appset stored in the map", + input: NewAppSetDirectory(), + args: args{ + app: v1alpha1.ApplicationSet{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-appset", + }, + Spec: v1alpha1.ApplicationSetSpec{ + Template: v1alpha1.ApplicationSetTemplate{ + Spec: v1alpha1.ApplicationSpec{ + Source: &v1alpha1.ApplicationSource{ + Path: "/test1/test2", + Helm: &v1alpha1.ApplicationSourceHelm{ + ValueFiles: []string{"one.yaml", "./two.yaml", "../three.yaml"}, + FileParameters: []v1alpha1.HelmFileParameter{ + {Name: "one", Path: "one.json"}, + {Name: "two", Path: "./two.json"}, + {Name: "three", Path: "../three.json"}, + }, + }, + }, + }, + }, + }, + }, + }, + expected: &AppSetDirectory{ + appSetDirs: map[string][]string{"/test1/test2": {"test-appset"}}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + d := &AppSetDirectory{ + appSetDirs: tt.input.appSetDirs, + appSetFiles: tt.input.appSetFiles, + appSetsMap: tt.input.appSetsMap, + } + d.ProcessApp(tt.args.app) + assert.Equal(t, tt.expected.appSetDirs, d.appSetDirs) + }) + } +} + +func TestAppSetDirectory_FindAppsBasedOnChangeList(t *testing.T) { + + tests := []struct { + name string + changeList []string + targetBranch string + mockFiles map[string]string // Mock file content + expected []v1alpha1.ApplicationSet + }{ + { + name: "Valid ApplicationSet", + changeList: []string{ + "appsets/httpdump/valid-appset.yaml", + }, + targetBranch: "main", + mockFiles: map[string]string{ + "appsets/httpdump/valid-appset.yaml": ` +apiVersion: argoproj.io/v1alpha1 +kind: ApplicationSet +metadata: + name: httpdump + namespace: kubechecks +spec: + generators: + # this is a simple list generator + - list: + elements: + - name: a + url: https://kubernetes.default.svc + template: + metadata: + finalizers: + - resources-finalizer.argocd.argoproj.io + name: "in-cluster-{{ name }}-httpdump" + namespace: kubechecks + labels: + argocd.argoproj.io/application-set-name: "httpdump" + spec: + destination: + namespace: "httpdump-{{ name }}" + server: '{{ url }}' + project: default + source: + repoURL: REPO_URL + targetRevision: HEAD + path: 'apps/httpdump/overlays/{{ name }}/' + syncPolicy: + automated: + prune: true + syncOptions: + - CreateNamespace=true + sources: [] + `, + }, + expected: []v1alpha1.ApplicationSet{ + { + TypeMeta: metav1.TypeMeta{ + APIVersion: "argoproj.io/v1alpha1", + Kind: "ApplicationSet", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "httpdump", + Namespace: "kubechecks", + }, + Spec: v1alpha1.ApplicationSetSpec{ + Template: v1alpha1.ApplicationSetTemplate{ + ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{ + Name: "in-cluster-{{ name }}-httpdump", + Namespace: "kubechecks", + Labels: map[string]string{ + "argocd.argoproj.io/application-set-name": "httpdump", + }, + Finalizers: []string{"resources-finalizer.argocd.argoproj.io"}, + }, + Spec: v1alpha1.ApplicationSpec{ + Source: &v1alpha1.ApplicationSource{ + RepoURL: "REPO_URL", + Path: "apps/httpdump/overlays/{{ name }}/", + TargetRevision: "HEAD", + }, + Destination: v1alpha1.ApplicationDestination{ + Namespace: "httpdump-{{ name }}", + Server: "{{ url }}", + }, + Project: "default", + SyncPolicy: &v1alpha1.SyncPolicy{ + Automated: &v1alpha1.SyncPolicyAutomated{ + Prune: true, + }, + SyncOptions: []string{"CreateNamespace=true"}, + }, + Sources: v1alpha1.ApplicationSources{}, + }, + }, + Generators: []v1alpha1.ApplicationSetGenerator{ + { + List: &v1alpha1.ListGenerator{ + Elements: []v1.JSON{{Raw: []byte("{\"name\":\"a\",\"url\":\"https://kubernetes.default.svc\"}")}}, + Template: v1alpha1.ApplicationSetTemplate{}, + ElementsYaml: "", + }, + }, + }, + }, + }, + }, + }, + { + name: "Invalid YAML File", + changeList: []string{ + "invalid-appset.yaml", + }, + targetBranch: "main", + mockFiles: map[string]string{ + "appsets/httpdump/invalid-appset.yaml": "invalid yaml content", + }, + expected: nil, + }, + // Add more test cases as needed + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create a temporary directory for the test + tempDir := os.TempDir() + var fatalErr error + var cleanUpDirs []string + for fileName, content := range tt.mockFiles { + absPath := filepath.Join(tempDir, fileName) + cleanUpDirs = append(cleanUpDirs, absPath) + err := os.MkdirAll(filepath.Dir(absPath), 0755) + if err != nil { + fatalErr = err + break + } + err = os.WriteFile(absPath, []byte(content), 0644) + if err != nil { + fatalErr = err + break + } + } + defer cleanUpTmpFiles(t, cleanUpDirs) + if fatalErr != nil { + t.Fatalf("failed to create tmp folder %s", fatalErr) + } + d := &AppSetDirectory{} + result := d.FindAppsBasedOnChangeList(tt.changeList, tt.targetBranch, &git.Repo{Directory: tempDir}) + assert.Equal(t, tt.expected, result) + }) + } +} + +// cleanUpTmpFiles removes the temporary directories created for the test +func cleanUpTmpFiles(t *testing.T, cleanUpDirs []string) { + for _, dir := range cleanUpDirs { + if err := os.RemoveAll(filepath.Dir(dir)); err != nil { + t.Fatalf("failed to remove tmp folder %s", err) + } + } +} diff --git a/pkg/appdir/vcstoargomap.go b/pkg/appdir/vcstoargomap.go index 5b216587..0becae94 100644 --- a/pkg/appdir/vcstoargomap.go +++ b/pkg/appdir/vcstoargomap.go @@ -26,6 +26,10 @@ func (v2a VcsToArgoMap) GetMap() map[pkg.RepoURL]*AppDirectory { return v2a.appDirByRepo } +func (v2a VcsToArgoMap) GetAppSetMap() map[pkg.RepoURL]*AppSetDirectory { + return v2a.appSetDirByRepo +} + func (v2a VcsToArgoMap) GetAppsInRepo(repoCloneUrl string) *AppDirectory { repoUrl, _, err := pkg.NormalizeRepoUrl(repoCloneUrl) if err != nil { diff --git a/pkg/appdir/vcstoargomap_test.go b/pkg/appdir/vcstoargomap_test.go new file mode 100644 index 00000000..95a5d088 --- /dev/null +++ b/pkg/appdir/vcstoargomap_test.go @@ -0,0 +1,188 @@ +package appdir + +import ( + "testing" + + "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" + "github.com/stretchr/testify/assert" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// TestAddApp tests the AddApp method from the VcsToArgoMap type. +func TestAddApp(t *testing.T) { + // Setup your mocks and expected calls here. + + v2a := NewVcsToArgoMap("vcs-username") // This would be mocked accordingly. + app1 := &v1alpha1.Application{ + ObjectMeta: metav1.ObjectMeta{Name: "test-app-1", Namespace: "default"}, + Spec: v1alpha1.ApplicationSpec{ + Source: &v1alpha1.ApplicationSource{ + RepoURL: "https://github.com/argoproj/argo-cd.git", + Path: "test-app-1", + }, + }, + } + + v2a.AddApp(app1) + appDir := v2a.GetAppsInRepo("https://github.com/argoproj/argo-cd.git") + + assert.Equal(t, appDir.Count(), 1) + assert.Equal(t, len(appDir.appDirs["test-app-1"]), 1) + + // Assertions to verify the behavior here. + app2 := &v1alpha1.Application{ + ObjectMeta: metav1.ObjectMeta{Name: "test-app-2", Namespace: "default"}, + Spec: v1alpha1.ApplicationSpec{ + Source: &v1alpha1.ApplicationSource{ + RepoURL: "https://github.com/argoproj/argo-cd.git", + Path: "test-app-2", + }, + }, + } + + v2a.AddApp(app2) + assert.Equal(t, appDir.Count(), 2) + assert.Equal(t, len(appDir.appDirs["test-app-2"]), 1) +} + +func TestDeleteApp(t *testing.T) { + // Setup your mocks and expected calls here. + + v2a := NewVcsToArgoMap("vcs-username") // This would be mocked accordingly. + app1 := &v1alpha1.Application{ + ObjectMeta: metav1.ObjectMeta{Name: "test-app-1", Namespace: "default"}, + Spec: v1alpha1.ApplicationSpec{ + Source: &v1alpha1.ApplicationSource{ + RepoURL: "https://github.com/argoproj/argo-cd.git", + Path: "test-app-1", + }, + }, + } + // Assertions to verify the behavior here. + app2 := &v1alpha1.Application{ + ObjectMeta: metav1.ObjectMeta{Name: "test-app-2", Namespace: "default"}, + Spec: v1alpha1.ApplicationSpec{ + Source: &v1alpha1.ApplicationSource{ + RepoURL: "https://github.com/argoproj/argo-cd.git", + Path: "test-app-2", + }, + }, + } + + v2a.AddApp(app1) + v2a.AddApp(app2) + appDir := v2a.GetAppsInRepo("https://github.com/argoproj/argo-cd.git") + + assert.Equal(t, appDir.Count(), 2) + assert.Equal(t, len(appDir.appDirs["test-app-1"]), 1) + assert.Equal(t, len(appDir.appDirs["test-app-2"]), 1) + + v2a.DeleteApp(app2) + assert.Equal(t, appDir.Count(), 1) + assert.Equal(t, len(appDir.appDirs["test-app-2"]), 0) +} + +func TestVcsToArgoMap_AddAppSet(t *testing.T) { + type args struct { + app *v1alpha1.ApplicationSet + } + tests := []struct { + name string + fields VcsToArgoMap + args args + expectedCount int + }{ + { + name: "normal process, expect to get the appset stored in the map", + fields: NewVcsToArgoMap("dummyuser"), + args: args{ + app: &v1alpha1.ApplicationSet{ + Spec: v1alpha1.ApplicationSetSpec{ + Template: v1alpha1.ApplicationSetTemplate{ + ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{}, + Spec: v1alpha1.ApplicationSpec{ + Source: &v1alpha1.ApplicationSource{ + RepoURL: "http://gitnotreal.local/unittest/", + Path: "apps/unittest/{{ values.cluster }}", + }, + }, + }, + }, + }, + }, + expectedCount: 1, + }, + { + name: "invalid appset", + fields: NewVcsToArgoMap("vcs-username"), + args: args{ + app: &v1alpha1.ApplicationSet{ + Spec: v1alpha1.ApplicationSetSpec{ + Template: v1alpha1.ApplicationSetTemplate{ + ApplicationSetTemplateMeta: v1alpha1.ApplicationSetTemplateMeta{}, + Spec: v1alpha1.ApplicationSpec{}, + }, + }, + }, + }, + expectedCount: 0, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + v2a := VcsToArgoMap{ + username: tt.fields.username, + appDirByRepo: tt.fields.appDirByRepo, + appSetDirByRepo: tt.fields.appSetDirByRepo, + } + v2a.AddAppSet(tt.args.app) + assert.Equal(t, tt.expectedCount, len(v2a.appSetDirByRepo)) + }) + } +} + +func TestVcsToArgoMap_DeleteAppSet(t *testing.T) { + // Set up your mocks and expected calls here. + + v2a := NewVcsToArgoMap("vcs-username") // This would be mocked accordingly. + app1 := &v1alpha1.ApplicationSet{ + ObjectMeta: metav1.ObjectMeta{Name: "test-app-1", Namespace: "default"}, + Spec: v1alpha1.ApplicationSetSpec{ + Template: v1alpha1.ApplicationSetTemplate{ + Spec: v1alpha1.ApplicationSpec{ + Source: &v1alpha1.ApplicationSource{ + RepoURL: "https://github.com/argoproj/argo-cd.git", + Path: "test-app-1", + }, + }, + }, + }, + } + // Assertions to verify the behavior here. + app2 := &v1alpha1.ApplicationSet{ + ObjectMeta: metav1.ObjectMeta{Name: "test-app-2", Namespace: "default"}, + Spec: v1alpha1.ApplicationSetSpec{ + Template: v1alpha1.ApplicationSetTemplate{ + Spec: v1alpha1.ApplicationSpec{ + Source: &v1alpha1.ApplicationSource{ + RepoURL: "https://github.com/argoproj/argo-cd.git", + Path: "test-app-2", + }, + }, + }, + }, + } + + v2a.AddAppSet(app1) + v2a.AddAppSet(app2) + appDir := v2a.GetAppSetsInRepo("https://github.com/argoproj/argo-cd.git") + + assert.Equal(t, appDir.Count(), 2) + assert.Equal(t, len(appDir.appSetDirs["test-app-1"]), 1) + assert.Equal(t, len(appDir.appSetDirs["test-app-2"]), 1) + + v2a.DeleteAppSet(app2) + assert.Equal(t, appDir.Count(), 1) + assert.Equal(t, len(appDir.appSetDirs["test-app-2"]), 0) +} diff --git a/pkg/appdir/vcstoargumap_test.go b/pkg/appdir/vcstoargumap_test.go deleted file mode 100644 index 611f8fa1..00000000 --- a/pkg/appdir/vcstoargumap_test.go +++ /dev/null @@ -1,83 +0,0 @@ -package appdir - -import ( - "testing" - - "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" - "github.com/stretchr/testify/assert" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// TestAddApp tests the AddApp method from the VcsToArgoMap type. -func TestAddApp(t *testing.T) { - // Setup your mocks and expected calls here. - - v2a := NewVcsToArgoMap("vcs-username") // This would be mocked accordingly. - app1 := &v1alpha1.Application{ - ObjectMeta: metav1.ObjectMeta{Name: "test-app-1", Namespace: "default"}, - Spec: v1alpha1.ApplicationSpec{ - Source: &v1alpha1.ApplicationSource{ - RepoURL: "https://github.com/argoproj/argo-cd.git", - Path: "test-app-1", - }, - }, - } - - v2a.AddApp(app1) - appDir := v2a.GetAppsInRepo("https://github.com/argoproj/argo-cd.git") - - assert.Equal(t, appDir.Count(), 1) - assert.Equal(t, len(appDir.appDirs["test-app-1"]), 1) - - // Assertions to verify the behavior here. - app2 := &v1alpha1.Application{ - ObjectMeta: metav1.ObjectMeta{Name: "test-app-2", Namespace: "default"}, - Spec: v1alpha1.ApplicationSpec{ - Source: &v1alpha1.ApplicationSource{ - RepoURL: "https://github.com/argoproj/argo-cd.git", - Path: "test-app-2", - }, - }, - } - - v2a.AddApp(app2) - assert.Equal(t, appDir.Count(), 2) - assert.Equal(t, len(appDir.appDirs["test-app-2"]), 1) -} - -func TestDeleteApp(t *testing.T) { - // Setup your mocks and expected calls here. - - v2a := NewVcsToArgoMap("vcs-username") // This would be mocked accordingly. - app1 := &v1alpha1.Application{ - ObjectMeta: metav1.ObjectMeta{Name: "test-app-1", Namespace: "default"}, - Spec: v1alpha1.ApplicationSpec{ - Source: &v1alpha1.ApplicationSource{ - RepoURL: "https://github.com/argoproj/argo-cd.git", - Path: "test-app-1", - }, - }, - } - // Assertions to verify the behavior here. - app2 := &v1alpha1.Application{ - ObjectMeta: metav1.ObjectMeta{Name: "test-app-2", Namespace: "default"}, - Spec: v1alpha1.ApplicationSpec{ - Source: &v1alpha1.ApplicationSource{ - RepoURL: "https://github.com/argoproj/argo-cd.git", - Path: "test-app-2", - }, - }, - } - - v2a.AddApp(app1) - v2a.AddApp(app2) - appDir := v2a.GetAppsInRepo("https://github.com/argoproj/argo-cd.git") - - assert.Equal(t, appDir.Count(), 2) - assert.Equal(t, len(appDir.appDirs["test-app-1"]), 1) - assert.Equal(t, len(appDir.appDirs["test-app-2"]), 1) - - v2a.DeleteApp(app2) - assert.Equal(t, appDir.Count(), 1) - assert.Equal(t, len(appDir.appDirs["test-app-2"]), 0) -}