Repository: argoproj-labs/applicationset
Pull request #37 (2020-09-04 13:04:29)

Improve and document the developer deployment process.
Rather than require developers to change on disk files in the git index,
or create custom patching solutions, introduce a make target that allows
us to build, push, and deploy manifests in a single command.

Adds namespace to the ServiceAccount subject in the ClusterRoleBinding,
as far as I can tell this was invalid yaml.

dgoodwin: PR updated, thanks!

Patch:
diff --git a/Makefile b/Makefile
index 657b2a3..756cc34 100644
--- a/Makefile
+++ b/Makefile
@@ -1,6 +1,5 @@
 VERSION?=$(shell cat VERSION)
-IMAGE_TAG?=v$(VERSION)
-IMAGE_PREFIX?=argoprojlabs
+IMAGE?=argoprojlabs/argocd-applicationset:v$(VERSION)
 DOCKER_PUSH?=true
 
 .PHONY: build
@@ -9,8 +8,13 @@ build:
 
 .PHONY: image
 image:
-	docker build -t $(IMAGE_PREFIX)/argocd-applicationset:$(IMAGE_TAG) .
-	@if [ "$(DOCKER_PUSH)" = "true" ] ; then docker push $(IMAGE_PREFIX)/argocd-applicationset:$(IMAGE_TAG) ; fi
+	docker build -t $(IMAGE) .
+	@if [ "$(DOCKER_PUSH)" = "true" ] ; then docker push $(IMAGE) ; fi
+
+.PHONY: deploy
+deploy:
+	kustomize build manifests/cluster-install | kubectl apply -f -
+	kubectl patch deployment -n argocd argocd-applicationset-controller --type='json' -p='[{"op": "replace", "path": "/spec/template/spec/containers/0/image", "value": "$(IMAGE)"}]'
 
 # Generate manifests e.g. CRD, RBAC etc.
 .PHONY: manifests
diff --git a/README.md b/README.md
index 5c006f5..79a4a62 100644
--- a/README.md
+++ b/README.md
@@ -12,6 +12,22 @@ compatibility are made, at least until merging into Argo CD proper.
 ## Proposal:
 * https://docs.google.com/document/d/1juWGr20FQaJmuuTIS8mBFmWWDU422M_FQMuhp5c1jt4/edit?usp=sharing
 
+## Development Instructions
+
+The following assumes you have: 
+
+ 1. Installed a recent version of [kustomize](https://github.com/kubernetes-sigs/kustomize) (3.x+). 
+ 1. Created a container repository for your development image.
+ 1. Deployed ArgoCD into the "argocd" namespace.
+ 
+To build and push a container with your current code, and deploy Kubernetes manifests for the controller Deployment:
+
+```bash
+IMAGE="username/argocd-applicationset:v0.0.1" make image deploy
+```
+
+The ApplicationSet controller should now be running in the "argocd" namespace.
+
 ## Example Spec:
 
 ```yaml
@@ -40,3 +56,4 @@ spec:
 ```
 
 Additional examples are available in the [examples](./examples) directory.
+
diff --git a/api/v1alpha1/applicationset_types.go b/api/v1alpha1/applicationset_types.go
index dc9fdca..34a8403 100644
--- a/api/v1alpha1/applicationset_types.go
+++ b/api/v1alpha1/applicationset_types.go
@@ -72,6 +72,7 @@ type ClusterGenerator struct {
 type GitGenerator struct {
 	RepoURL     string                      `json:"repoURL"`
 	Directories []GitDirectoryGeneratorItem `json:"directories,omitempty"`
+	Revision    string                      `json:"revision"`
 }
 
 type GitDirectoryGeneratorItem struct {
diff --git a/go.mod b/go.mod
index 38d7d0b..96873a1 100644
--- a/go.mod
+++ b/go.mod
@@ -5,28 +5,20 @@ go 1.13
 require (
 	github.com/Masterminds/semver v1.5.0 // indirect
 	github.com/argoproj/argo-cd v1.5.6
+	github.com/argoproj/gitops-engine v0.1.3
 	github.com/argoproj/pkg v0.0.0-20200424003221-9b858eff18a1 // indirect
-	github.com/ghodss/yaml v1.0.0 // indirect
 	github.com/gogo/protobuf v1.3.1 // indirect
-	github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
-	github.com/grpc-ecosystem/grpc-gateway v1.3.1 // indirect
-	github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 // indirect
 	github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
 	github.com/pkg/errors v0.9.1
-	github.com/sergi/go-diff v1.1.0 // indirect
 	github.com/sirupsen/logrus v1.4.2
 	github.com/stretchr/testify v1.4.0
 	github.com/valyala/fasttemplate v1.1.1
-	github.com/yudai/gojsondiff v1.0.1-0.20180504020246-0525c875b75c // indirect
-	github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect
-	github.com/yudai/pp v2.0.1+incompatible // indirect
+	google.golang.org/grpc v1.23.0
 	gopkg.in/src-d/go-git.v4 v4.13.1 // indirect
-	gopkg.in/yaml.v2 v2.2.8 // indirect
 	k8s.io/api v0.17.2
 	k8s.io/apimachinery v0.17.2
 	k8s.io/client-go v11.0.1-0.20190816222228-6d55c1b1f1ca+incompatible
 	k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a // indirect
-	k8s.io/kubectl v0.16.6 // indirect
 	k8s.io/kubernetes v1.16.6
 	k8s.io/utils v0.0.0-20191114200735-6ca3b61696b6 // indirect
 	sigs.k8s.io/controller-runtime v0.5.0
diff --git a/go.sum b/go.sum
index 4a6c216..644ffca 100644
--- a/go.sum
+++ b/go.sum
@@ -45,6 +45,9 @@ github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo
 github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
 github.com/argoproj/argo-cd v1.5.6 h1:yrzJuSTf/YBFLugB6DwiS8spDbRKFVyGg8Few5SPLfA=
 github.com/argoproj/argo-cd v1.5.6/go.mod h1:UPOPiF6Y1y/oTL3X1KcuLbu7ljD7f4+SIaXvlowBmhE=
+github.com/argoproj/gitops-engine v0.1.3 h1:eQp1bfqaeaATcu4XErlxNb6aVsN4rC7suL/Fqx/9E+k=
+github.com/argoproj/gitops-engine v0.1.3/go.mod h1:UmBGlQLT/MPNiMmbnouZRWhkk3slPuozMsENdXMkIMs=
+github.com/argoproj/pkg v0.0.0-20200102163130-2dd1f3f6b4de/go.mod h1:2EZ44RG/CcgtPTwrRR0apOc7oU6UIw8GjCUJWZ8X3bM=
 github.com/argoproj/pkg v0.0.0-20200424003221-9b858eff18a1 h1:BCuMRvKYHPuPN2Gep3uY+XOScGNCBFbF/pXYlhmyJjg=
 github.com/argoproj/pkg v0.0.0-20200424003221-9b858eff18a1/go.mod h1:2EZ44RG/CcgtPTwrRR0apOc7oU6UIw8GjCUJWZ8X3bM=
 github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
@@ -211,6 +214,7 @@ github.com/go-toolsmith/pkgload v0.0.0-20181119091011-e9e65178eee8/go.mod h1:WoM
 github.com/go-toolsmith/pkgload v1.0.0/go.mod h1:5eFArkbO80v7Z0kdngIxsRXRMTaX4Ilcwuh3clNrQJc=
 github.com/go-toolsmith/strparse v1.0.0/go.mod h1:YI2nUKP9YGZnL/L1/DLFBfixrcjslWct4wyljWhSRy8=
 github.com/go-toolsmith/typep v1.0.0/go.mod h1:JSQCQMUPdRlMZFswiq3TGpNp1GMktqkR2Ns5AIQkATU=
+github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
 github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
 github.com/godbus/dbus v4.1.0+incompatible/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
 github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
@@ -284,6 +288,7 @@ github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3/go.m
 github.com/gostaticanalysis/analysisutil v0.0.3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE=
 github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM=
 github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
+github.com/grpc-ecosystem/go-grpc-middleware v0.0.0-20190222133341-cfaf5686ec79 h1:lR9ssWAqp9qL0bALxqEEkuudiP1eweOdv9jsRK3e7lE=
 github.com/grpc-ecosystem/go-grpc-middleware v0.0.0-20190222133341-cfaf5686ec79/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
 github.com/grpc-ecosystem/grpc-gateway v1.3.0 h1:HJtP6RRwj2EpPCD/mhAWzSvLL/dFTdPm1UrWwanoFos=
@@ -510,6 +515,7 @@ github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jW
 github.com/storageos/go-api v0.0.0-20180912212459-343b3eff91fc/go.mod h1:ZrLn+e0ZuF3Y65PNF6dIwbJPZqfmtCXxFm9ckv0agOY=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
 github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
 github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
diff --git a/main.go b/main.go
index 919ce1c..f2acbaf 100644
--- a/main.go
+++ b/main.go
@@ -1,7 +1,9 @@
 package main
 
 import (
+	"context"
 	"flag"
+	"github.com/argoproj-labs/applicationset/pkg/services"
 	argov1alpha1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
 	"os"
 
@@ -13,6 +15,7 @@ import (
 
 	argoprojiov1alpha1 "github.com/argoproj-labs/applicationset/api/v1alpha1"
 	"github.com/argoproj-labs/applicationset/pkg/controllers"
+	"k8s.io/client-go/kubernetes"
 	// +kubebuilder:scaffold:imports
 )
 
@@ -34,11 +37,15 @@ func main() {
 	var metricsAddr string
 	var probeBindAddr string
 	var enableLeaderElection bool
+	var namespace string
+	var argocdRepoServer string
 	flag.StringVar(&metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.")
 	flag.StringVar(&metricsAddr, "probe-addr", ":8081", "The address the probe endpoint binds to.")
 	flag.BoolVar(&enableLeaderElection, "enable-leader-election", false,
 		"Enable leader election for controller manager. "+
 			"Enabling this will ensure there is only one active controller manager.")
+	flag.StringVar(&namespace, "namespace", "argocd", "Argo CD repo namesapce")
+	flag.StringVar(&argocdRepoServer, "argocd-repo-server", "argocd-repo-server:8081", "Argo CD repo server address")
 	flag.Parse()
 
 	ctrl.SetLogger(zap.New(zap.UseDevMode(true)))
@@ -56,10 +63,13 @@ func main() {
 		os.Exit(1)
 	}
 
+	k8s := kubernetes.NewForConfigOrDie(mgr.GetConfig())
+
 	if err = (&controllers.ApplicationSetReconciler{
-		Client:   mgr.GetClient(),
-		Scheme:   mgr.GetScheme(),
-		Recorder: mgr.GetEventRecorderFor("applicationset-controller"),
+		Client:      mgr.GetClient(),
+		Scheme:      mgr.GetScheme(),
+		Recorder:    mgr.GetEventRecorderFor("applicationset-controller"),
+		AppsService: services.NewArgoCDService(context.Background(), k8s, namespace, argocdRepoServer),
 	}).SetupWithManager(mgr); err != nil {
 		setupLog.Error(err, "unable to create controller", "controller", "ApplicationSet")
 		os.Exit(1)
diff --git a/manifests/base/rbac.yaml b/manifests/base/rbac.yaml
index 31291b7..9cf2616 100644
--- a/manifests/base/rbac.yaml
+++ b/manifests/base/rbac.yaml
@@ -43,7 +43,6 @@ rules:
       - ''
     resources:
       - events
-      - pods
     verbs:
       - create
       - delete
@@ -56,6 +55,7 @@ rules:
       - ''
     resources:
       - secrets
+      - configmaps
     verbs:
       - get
       - list
diff --git a/manifests/cluster-rbac/rbac.yaml b/manifests/cluster-rbac/rbac.yaml
index de86378..c9fcbcd 100644
--- a/manifests/cluster-rbac/rbac.yaml
+++ b/manifests/cluster-rbac/rbac.yaml
@@ -33,7 +33,6 @@ rules:
       - ''
     resources:
       - events
-      - pods
     verbs:
       - create
       - delete
@@ -46,6 +45,7 @@ rules:
       - ''
     resources:
       - secrets
+      - configmaps
     verbs:
       - get
       - list
diff --git a/manifests/crds/applicationset-crd.yaml b/manifests/crds/applicationset-crd.yaml
index eed0966..e087c1d 100644
--- a/manifests/crds/applicationset-crd.yaml
+++ b/manifests/crds/applicationset-crd.yaml
@@ -2,6 +2,9 @@
 apiVersion: apiextensions.k8s.io/v1beta1
 kind: CustomResourceDefinition
 metadata:
+  annotations:
+    controller-gen.kubebuilder.io/version: v0.3.0
+  creationTimestamp: null
   labels:
     app.kubernetes.io/name: applicationsets.argoproj.io
     app.kubernetes.io/part-of: argocd-applicationset
@@ -113,8 +116,11 @@ spec:
                         type: array
                       repoURL:
                         type: string
+                      revision:
+                        type: string
                     required:
-                      - repoURL
+                    - repoURL
+                    - revision
                     type: object
                   list:
                     description: ListGenerator include items info
diff --git a/pkg/controllers/applicationset_controller.go b/pkg/controllers/applicationset_controller.go
index 1fccb1e..344d04e 100644
--- a/pkg/controllers/applicationset_controller.go
+++ b/pkg/controllers/applicationset_controller.go
@@ -20,6 +20,8 @@ import (
 	"context"
 	"fmt"
 	"github.com/argoproj-labs/applicationset/pkg/generators"
+	"github.com/argoproj-labs/applicationset/pkg/services"
+	argov1alpha1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/client-go/tools/record"
@@ -34,14 +36,15 @@ import (
 	"sigs.k8s.io/controller-runtime/pkg/client"
 
 	argoprojiov1alpha1 "github.com/argoproj-labs/applicationset/api/v1alpha1"
-	argov1alpha1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
 )
 
 // ApplicationSetReconciler reconciles a ApplicationSet object
 type ApplicationSetReconciler struct {
 	client.Client
-	Scheme   *runtime.Scheme
-	Recorder record.EventRecorder
+	Scheme         	*runtime.Scheme
+	Recorder       	record.EventRecorder
+	RepoServerAddr 	string
+	AppsService		services.Apps
 }
 
 // +kubebuilder:rbac:groups=argoproj.io,resources=applicationsets,verbs=get;list;watch;create;update;patch;delete
@@ -60,9 +63,11 @@ func (r *ApplicationSetReconciler) Reconcile(req ctrl.Request) (ctrl.Result, err
 
 	listGenerator := generators.NewListGenerator()
 	clusterGenerator := generators.NewClusterGenerator(r.Client)
+	GitGenerator := generators.NewGitGenerator(r.AppsService)
 
 	// desiredApplications is the main list of all expected Applications from all generators in this appset.
 	var desiredApplications []argov1alpha1.Application
+
 	for _, tmpGenerator := range applicationSetInfo.Spec.Generators {
 		var apps []argov1alpha1.Application
 		var err error
@@ -70,11 +75,15 @@ func (r *ApplicationSetReconciler) Reconcile(req ctrl.Request) (ctrl.Result, err
 			apps, err = listGenerator.GenerateApplications(&tmpGenerator, &applicationSetInfo)
 		} else if tmpGenerator.Clusters != nil {
 			apps, err = clusterGenerator.GenerateApplications(&tmpGenerator, &applicationSetInfo)
+		} else if tmpGenerator.Git != nil {
+			apps, err = GitGenerator.GenerateApplications(&tmpGenerator, &applicationSetInfo)
 		}
 		log.Infof("apps from generator: %+v", apps)
 		if err != nil {
 			log.WithError(err).Error("error generating applications")
+			continue
 		}
+		
 		desiredApplications = append(desiredApplications, apps...)
 
 	}
diff --git a/pkg/generators/cluster.go b/pkg/generators/cluster.go
index d525efa..74c7a81 100644
--- a/pkg/generators/cluster.go
+++ b/pkg/generators/cluster.go
@@ -60,6 +60,7 @@ func (g *ClusterGenerator) GenerateApplications(
 	log.Debug("clusters matching labels", "count", len(clusterSecretList.Items))
 
 	var tmplApplication argov1alpha1.Application
+	tmplApplication.Labels = appSet.Spec.Template.Labels
 	tmplApplication.Namespace = appSet.Spec.Template.Namespace
 	tmplApplication.Name = appSet.Spec.Template.Name
 	tmplApplication.Spec = appSet.Spec.Template.Spec
diff --git a/pkg/generators/git.go b/pkg/generators/git.go
index 590abff..0c07c9e 100644
--- a/pkg/generators/git.go
+++ b/pkg/generators/git.go
@@ -1,27 +1,102 @@
 package generators
 
 import (
+	"context"
 	"fmt"
 	argoprojiov1alpha1 "github.com/argoproj-labs/applicationset/api/v1alpha1"
+	"github.com/argoproj-labs/applicationset/pkg/services"
+	"github.com/argoproj-labs/applicationset/pkg/utils"
 	argov1alpha1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
+	log "github.com/sirupsen/logrus"
+	"path"
 )
 
 var _ Generator = (*GitGenerator)(nil)
 
 type GitGenerator struct {
+	repos services.Apps
 }
 
-func NewGitGenerator() Generator {
-	g := &GitGenerator{}
+func NewGitGenerator(repos services.Apps) Generator {
+	g := &GitGenerator{
+		repos: repos,
+	}
 	return g
 }
 
-func (g *GitGenerator) GenerateApplications(
-	appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator,
-	appSet *argoprojiov1alpha1.ApplicationSet) ([]argov1alpha1.Application, error) {
-	if appSet == nil {
+func (g *GitGenerator) GenerateApplications(appSetGenerator *argoprojiov1alpha1.ApplicationSetGenerator, appSet *argoprojiov1alpha1.ApplicationSet) ([]argov1alpha1.Application, error) {
+	if appSetGenerator == nil || appSet == nil {
 		return nil, fmt.Errorf("ApplicationSet is empty")
 	}
 
-	return nil, nil
+	if appSetGenerator.Git == nil {
+		return nil, fmt.Errorf("git variable empty")
+	}
+
+	allApps, err := g.repos.GetApps(context.TODO(), appSetGenerator.Git.RepoURL, appSetGenerator.Git.Revision)
+	if err != nil {
+		return nil, err
+	}
+
+	log.WithFields(log.Fields{
+		"allAps": allApps,
+		"total": len(allApps),
+		"repoURL": appSetGenerator.Git.RepoURL,
+		"revision": appSetGenerator.Git.Revision,
+	}).Info("applications result from the repo service")
+
+	requestedApps := g.filter(appSetGenerator.Git.Directories, allApps)
+
+	res := g.generateApplications(requestedApps, appSet)
+
+	return res, nil
+}
+
+func (g *GitGenerator) filter(Directories []argoprojiov1alpha1.GitDirectoryGeneratorItem, allApps []string) []string {
+	res := []string{}
+	for _, requestedPath := range Directories {
+		for _, appPath := range allApps {
+			match, err := path.Match(requestedPath.Path, appPath)
+			if err != nil {
+				log.WithError(err).WithField("requestedPath", requestedPath).
+					WithField("appPath", appPath).Error("error while matching appPath to requestedPath")
+				continue
+			}
+			if match {
+				res = append(res, appPath)
+			}
+		}
+	}
+	return res
+}
+
+func (g *GitGenerator) generateApplications(requestedApps []string, appSet *argoprojiov1alpha1.ApplicationSet) ([]argov1alpha1.Application) {
+
+	res := make([]argov1alpha1.Application, len(requestedApps))
+	for i, a := range requestedApps {
+		app, err := g.generateApplication(appSet, a)
+		if err != nil {
+			log.WithError(err).WithField("app", a).Error("error while generating app")
+			continue
+		}
+		res[i] = *app
+	}
+
+	return res
+}
+
+
+func (g *GitGenerator) generateApplication(appSet *argoprojiov1alpha1.ApplicationSet, appPath string) (*argov1alpha1.Application, error) {
+	var tmplApplication argov1alpha1.Application
+	tmplApplication.Namespace = appSet.Spec.Template.Namespace
+	tmplApplication.Name = appSet.Spec.Template.Name
+	tmplApplication.Spec = appSet.Spec.Template.Spec
+
+	params := make(map[string]string, 2)
+	params["path"] = appPath
+	params["path.basename"] = path.Base(appPath)
+
+	tmpApplication, err := utils.RenderTemplateParams(&tmplApplication, params)
+
+	return tmpApplication, err
 }
diff --git a/pkg/services/repo_service.go b/pkg/services/repo_service.go
new file mode 100644
index 0000000..cad70dc
--- /dev/null
+++ b/pkg/services/repo_service.go
@@ -0,0 +1,71 @@
+package services
+
+import (
+	"context"
+	"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
+	"github.com/argoproj/argo-cd/reposerver/apiclient"
+	"github.com/argoproj/argo-cd/util/db"
+	"github.com/argoproj/argo-cd/util/settings"
+	"github.com/argoproj/gitops-engine/pkg/utils/io"
+	"github.com/pkg/errors"
+	log "github.com/sirupsen/logrus"
+	"k8s.io/client-go/kubernetes"
+)
+
+// RepositoryDB Is a lean facade for ArgoDB,
+// Using a lean interface makes it more easy to test the functionality the git generator uses
+type RepositoryDB interface {
+	GetRepository(ctx context.Context, url string) (*v1alpha1.Repository, error)
+}
+
+type argoCDService struct {
+	repositoriesDB 		RepositoryDB
+	repoClientset 		apiclient.Clientset
+}
+
+type Apps interface {
+	GetApps(ctx context.Context, repoURL string, revision string) ([]string, error)
+}
+
+func NewArgoCDService(ctx context.Context, clientset kubernetes.Interface, namespace string, repoServerAddress string) Apps {
+	settingsMgr := settings.NewSettingsManager(ctx, clientset, namespace)
+
+	return &argoCDService{
+		repositoriesDB: db.NewDB(namespace, settingsMgr, clientset).(RepositoryDB),
+		repoClientset: apiclient.NewRepoServerClientset(repoServerAddress, 5),
+	}
+}
+
+func (a *argoCDService) GetApps(ctx context.Context, repoURL string, revision string) ([]string, error) {
+	repo, err := a.repositoriesDB.GetRepository(ctx, repoURL)
+	if err != nil {
+
+		return nil, errors.Wrap(err, "Error in GetRepository")
+	}
+	log.Infof("repo - %#v", repo)
+
+	conn, repoClient, err := a.repoClientset.NewRepoServerClient()
+	defer io.Close(conn)
+	if err != nil {
+		return nil, errors.Wrap(err, "Error in creating repo service client")
+	}
+
+	apps, err := repoClient.ListApps(ctx, &apiclient.ListAppsRequest{
+		Repo: repo,
+		Revision: revision,
+	})
+	log.Infof("apps - %#v", apps)
+	if err != nil {
+		return nil, errors.Wrap(err, "Error in ListApps")
+	}
+
+	res := []string{}
+
+	for name, _ := range apps.Apps {
+		res = append(res, name)
+	}
+
+	log.Infof("res - %#v", apps)
+
+	return res, nil
+}


Test patch:
diff --git a/pkg/generators/cluster_test.go b/pkg/generators/cluster_test.go
index c8b5f19..b30d6f9 100644
--- a/pkg/generators/cluster_test.go
+++ b/pkg/generators/cluster_test.go
@@ -16,8 +16,9 @@ import (
 	argoprojiov1alpha1 "github.com/argoproj-labs/applicationset/api/v1alpha1"
 	argov1alpha1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
 
-	"github.com/stretchr/testify/assert"
 	"testing"
+
+	"github.com/stretchr/testify/assert"
 )
 
 type possiblyErroringFakeCtrlRuntimeClient struct {
@@ -27,7 +28,7 @@ type possiblyErroringFakeCtrlRuntimeClient struct {
 
 func (p *possiblyErroringFakeCtrlRuntimeClient) List(ctx context.Context, secretList runtime.Object, opts ...client.ListOption) error {
 	if p.shouldError {
-		return errors.New("Could not list Secrets.")
+		return errors.New("could not list Secrets")
 	}
 	return p.Client.List(ctx, secretList, opts...)
 }
@@ -40,6 +41,9 @@ func getRenderTemplate(appName, clusterName, server, environmentLabel string) *a
 			Finalizers: []string{
 				"resources-finalizer.argocd.argoproj.io",
 			},
+			Labels: map[string]string{
+				"environment": environmentLabel,
+			},
 		},
 		Spec: argov1alpha1.ApplicationSpec{
 			Source: argov1alpha1.ApplicationSource{
@@ -95,7 +99,7 @@ func TestGenerateApplications(t *testing.T) {
 			},
 			Data: map[string][]byte{
 				"config": []byte(base64.StdEncoding.EncodeToString([]byte("foo"))),
-				"name":   []byte(base64.StdEncoding.EncodeToString([]byte("productuion-01"))),
+				"name":   []byte(base64.StdEncoding.EncodeToString([]byte("production-01"))),
 				"server": []byte("https://production-01.example.com"),
 			},
 			Type: corev1.SecretType("Opaque"),
@@ -104,6 +108,9 @@ func TestGenerateApplications(t *testing.T) {
 
 	applicationSetTemplate := argoprojiov1alpha1.ApplicationSetTemplate{
 		ObjectMeta: metav1.ObjectMeta{
+			Labels: map[string]string{
+				"environment": "{{metadata.labels.environment}}",
+			},
 			Name:      "{{name}}-app",
 			Namespace: "namespace",
 		},
@@ -200,7 +207,7 @@ func TestGenerateApplications(t *testing.T) {
 			metav1.LabelSelector{},
 			nil,
 			true,
-			errors.New("Could not list Secrets."),
+			errors.New("could not list Secrets"),
 		},
 	}
 
diff --git a/pkg/generators/git_test.go b/pkg/generators/git_test.go
new file mode 100644
index 0000000..6a43fb3
--- /dev/null
+++ b/pkg/generators/git_test.go
@@ -0,0 +1,177 @@
+package generators
+
+import (
+	"context"
+	"fmt"
+	argoprojiov1alpha1 "github.com/argoproj-labs/applicationset/api/v1alpha1"
+	argov1alpha1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
+	"github.com/argoproj/argo-cd/reposerver/apiclient"
+	"github.com/argoproj/argo-cd/util"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/mock"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"testing"
+)
+
+type clientSet struct {
+	RepoServerServiceClient apiclient.RepoServerServiceClient
+}
+
+func (c *clientSet) NewRepoServerClient() (util.Closer, apiclient.RepoServerServiceClient, error) {
+	return util.NewCloser(func() error { return nil }), c.RepoServerServiceClient, nil
+}
+
+type argoCDServiceMock struct {
+	mock.Mock
+}
+
+func (a argoCDServiceMock) GetApps(ctx context.Context, repoURL string, revision string) ([]string, error) {
+	args := a.Called(ctx, repoURL, revision)
+
+	return args.Get(0).([]string), args.Error(1)
+}
+
+func getGitRenderTemplate(name, path string) *argov1alpha1.Application {
+	return &argov1alpha1.Application{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      name,
+			Namespace: "namespace",
+			Finalizers: []string{
+				"resources-finalizer.argocd.argoproj.io",
+			},
+		},
+		Spec: argov1alpha1.ApplicationSpec{
+			Source: argov1alpha1.ApplicationSource{
+				RepoURL:        "RepoURL",
+				Path:           path,
+				TargetRevision: "HEAD",
+			},
+			Destination: argov1alpha1.ApplicationDestination{
+				Server:    "server",
+				Namespace: "destinationNamespace",
+			},
+			Project: "project",
+		},
+	}
+}
+
+func TestGitGenerateApplications(t *testing.T) {
+
+	appSetTemplate := argoprojiov1alpha1.ApplicationSetTemplate{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "{{path.basename}}",
+			Namespace: "namespace",
+		},
+		Spec: argov1alpha1.ApplicationSpec{
+			Source: argov1alpha1.ApplicationSource{
+				RepoURL:        "RepoURL",
+				Path:           "{{path}}",
+				TargetRevision: "HEAD",
+			},
+			Destination: argov1alpha1.ApplicationDestination{
+				Server:    "server",
+				Namespace: "destinationNamespace",
+			},
+			Project: "project",
+		},
+	}
+
+	cases := []struct {
+		name		  string
+		template      argoprojiov1alpha1.ApplicationSetTemplate
+		directories   []argoprojiov1alpha1.GitDirectoryGeneratorItem
+		repoApps      []string
+		repoError     error
+		expected      []argov1alpha1.Application
+		expectedError error
+	}{
+		{
+			name: "happy flow - created apps",
+			template: appSetTemplate,
+			directories: []argoprojiov1alpha1.GitDirectoryGeneratorItem{{"*"}},
+			repoApps: []string{
+					"app1",
+					"app2",
+					"p1/app3",
+			},
+			repoError: nil,
+			expected: []argov1alpha1.Application{
+				*getGitRenderTemplate("app1", "app1"),
+				*getGitRenderTemplate("app2", "app2"),
+			},
+			expectedError: nil,
+		},
+		{
+			name: "It filters application according to the paths",
+			template: appSetTemplate,
+			directories: []argoprojiov1alpha1.GitDirectoryGeneratorItem{{"p1/*"}, {"p1/*/*"}},
+			repoApps: []string{
+				"app1",
+				"p1/app2",
+				"p1/p2/app3",
+				"p1/p2/p3/app4",
+			},
+			repoError: nil,
+			expected: []argov1alpha1.Application{
+				*getGitRenderTemplate("app2", "p1/app2"),
+				*getGitRenderTemplate("app3", "p1/p2/app3"),
+			},
+			expectedError: nil,
+		},
+		{
+			name: "handles empty response from repo server",
+			template: appSetTemplate,
+			directories: []argoprojiov1alpha1.GitDirectoryGeneratorItem{{"*"}},
+			repoApps: []string{},
+			repoError: nil,
+			expected: []argov1alpha1.Application{},
+			expectedError:nil,
+		},
+		{
+			name: "handles error from repo server",
+			template: appSetTemplate,
+			directories: []argoprojiov1alpha1.GitDirectoryGeneratorItem{{"*"}},
+			repoApps: []string{},
+			repoError: fmt.Errorf("error"),
+			expected: []argov1alpha1.Application{},
+			expectedError: fmt.Errorf("error"),
+		},
+	}
+
+	for _, c := range cases {
+		cc := c
+		t.Run(cc.name, func(t *testing.T) {
+			argoCDServiceMock := argoCDServiceMock{}
+			argoCDServiceMock.On("GetApps", mock.Anything, mock.Anything, mock.Anything).Return(c.repoApps, c.repoError)
+
+			var gitGenerator = NewGitGenerator(argoCDServiceMock)
+			applicationSetInfo := argoprojiov1alpha1.ApplicationSet{
+				ObjectMeta: metav1.ObjectMeta{
+					Name: "set",
+				},
+				Spec: argoprojiov1alpha1.ApplicationSetSpec{
+					Generators: []argoprojiov1alpha1.ApplicationSetGenerator{{
+						Git: &argoprojiov1alpha1.GitGenerator{
+							RepoURL:     "RepoURL",
+							Revision:    "Revision",
+							Directories: c.directories,
+						},
+					}},
+					Template: c.template,
+				},
+			}
+
+			got, err := gitGenerator.GenerateApplications(&applicationSetInfo.Spec.Generators[0], &applicationSetInfo)
+
+			if c.expectedError != nil {
+				assert.EqualError(t, err, c.expectedError.Error())
+			} else {
+				assert.NoError(t, err)
+				assert.Equal(t, c.expected, got)
+			}
+
+			argoCDServiceMock.AssertExpectations(t)
+		})
+	}
+
+}
diff --git a/pkg/services/repo_service_test.go b/pkg/services/repo_service_test.go
new file mode 100644
index 0000000..0efd053
--- /dev/null
+++ b/pkg/services/repo_service_test.go
@@ -0,0 +1,161 @@
+package services
+
+import (
+	"context"
+	"errors"
+	"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
+	"github.com/argoproj/argo-cd/reposerver/apiclient"
+	"github.com/argoproj/argo-cd/util"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/mock"
+	"google.golang.org/grpc"
+	"testing"
+)
+
+type ArgocdRepositoryMock struct {
+	mock.Mock
+}
+
+func (a ArgocdRepositoryMock) GetRepository(ctx context.Context, url string) (*v1alpha1.Repository, error) {
+	args := a.Called(ctx, url)
+
+	return args.Get(0).(*v1alpha1.Repository), args.Error(1)
+
+}
+
+type repoServerClientMock struct {
+	mock.Mock
+}
+
+func (r repoServerClientMock) GenerateManifest(ctx context.Context, in *apiclient.ManifestRequest, opts ...grpc.CallOption) (*apiclient.ManifestResponse, error) {
+	return nil, nil
+}
+func (r repoServerClientMock) ListApps(ctx context.Context, in *apiclient.ListAppsRequest, opts ...grpc.CallOption) (*apiclient.AppList, error) {
+	args := r.Called(ctx, in)
+
+	return args.Get(0).(*apiclient.AppList), args.Error(1)
+}
+func (r repoServerClientMock) GetAppDetails(ctx context.Context, in *apiclient.RepoServerAppDetailsQuery, opts ...grpc.CallOption) (*apiclient.RepoAppDetailsResponse, error) {
+	return nil, nil
+}
+func (r repoServerClientMock) GetRevisionMetadata(ctx context.Context, in *apiclient.RepoServerRevisionMetadataRequest, opts ...grpc.CallOption) (*v1alpha1.RevisionMetadata, error) {
+	return nil, nil
+}
+func (r repoServerClientMock) GetHelmCharts(ctx context.Context, in *apiclient.HelmChartsRequest, opts ...grpc.CallOption) (*apiclient.HelmChartsResponse, error) {
+	return nil, nil
+}
+
+type closer struct {
+	mock.Mock
+}
+
+func (c closer) Close() error{
+	return nil
+}
+
+type repoClientsetMock struct {
+	mock.Mock
+}
+
+func (r repoClientsetMock) NewRepoServerClient() (util.Closer, apiclient.RepoServerServiceClient, error) {
+	args := r.Called()
+
+	return closer{}, args.Get(0).(apiclient.RepoServerServiceClient), args.Error(1)
+}
+
+
+func TestGetApps(t *testing.T) {
+
+	for _, c := range []struct {
+		name			string
+		repoURL     	string
+		revision		string
+		repoRes			*v1alpha1.Repository
+		repoErr			error
+		appRes			*apiclient.AppList
+		appError		error
+		expected   		[]string
+		expectedError	error
+	}{
+		{
+			"Happy Flow",
+			"repoURL",
+			"revision",
+			&v1alpha1.Repository{
+			},
+			nil,
+			&apiclient.AppList {
+				Apps: map[string]string{
+					"app1": "",
+					"app2": "",
+				},
+			},
+			nil,
+			[]string{"app1", "app2"},
+			nil,
+		},
+		{
+			"handles GetRepository error",
+			"repoURL",
+			"revision",
+			&v1alpha1.Repository{
+			},
+			errors.New("error"),
+			&apiclient.AppList {
+				Apps: map[string]string{
+					"app1": "",
+					"app2": "",
+				},
+			},
+			nil,
+			[]string{},
+			errors.New("Error in GetRepository: error"),
+		},
+		{
+			"handles ListApps error",
+			"repoURL",
+			"revision",
+			&v1alpha1.Repository{
+			},
+			nil,
+			&apiclient.AppList {
+				Apps: map[string]string{
+					"app1": "",
+					"app2": "",
+				},
+			},
+			errors.New("error"),
+			[]string{},
+			errors.New("Error in ListApps: error"),
+		},
+	}{
+		cc := c
+		t.Run(cc.name, func(t *testing.T) {
+			argocdRepositoryMock := ArgocdRepositoryMock{}
+			repoServerClientMock := repoServerClientMock{}
+			repoClientsetMock := repoClientsetMock{}
+
+			argocdRepositoryMock.On("GetRepository", mock.Anything, cc.repoURL).Return(cc.repoRes, cc.repoErr)
+
+			repoServerClientMock.On("ListApps", mock.Anything, &apiclient.ListAppsRequest{
+				Repo: cc.repoRes,
+				Revision: cc.revision,
+			}).Return(cc.appRes, cc.appError)
+
+			repoClientsetMock.On("NewRepoServerClient").Return(repoServerClientMock, nil)
+
+			argocd := argoCDService{
+				repositoriesDB: argocdRepositoryMock,
+				repoClientset: repoClientsetMock,
+			}
+			got, err := argocd.GetApps(context.TODO(), cc.repoURL, cc.revision)
+
+			if cc.expectedError != nil {
+				assert.EqualError(t, err, cc.expectedError.Error())
+			} else {
+				assert.Equal(t, got, cc.expected)
+				assert.NoError(t, err)
+			}
+		})
+	}
+}