Skip to content
This repository has been archived by the owner on Oct 12, 2023. It is now read-only.

add sync status #48

Merged
merged 2 commits into from
Sep 21, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
15 changes: 13 additions & 2 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ func main() {
var enableLeaderElection bool
var namespace string
var argocdRepoServer string
var policy string
var debugLog bool
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.")
Expand All @@ -52,14 +53,23 @@ func main() {
"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.StringVar(&policy, "policy", "sync", "Modify how application is sync between the generator and the cluster. Default is sync (create & update & delete), options: create-only, create-update (no deletion)")
flag.BoolVar(&debugLog, "debug", false, "print debug logs")
flag.Parse()



ctrl.SetLogger(zap.New(zap.UseDevMode(true)))

if debugLog {
log.SetLevel(log.DebugLevel)
policyObj, exists := utils.Policies[policy]
if !exists {
setupLog.Info("Policy value can be: sync, create-only, create-update")
os.Exit(1)
}

if debugLog {
log.SetLevel(log.DebugLevel)
}

// Determine the namespace we're running in. Normally injected into the pod as an env
// var via the Kube downward API configured in the Deployment.
Expand Down Expand Up @@ -100,6 +110,7 @@ func main() {
Scheme: mgr.GetScheme(),
Recorder: mgr.GetEventRecorderFor("applicationset-controller"),
Renderer: &utils.Render{},
Policy: policyObj,
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "ApplicationSet")
os.Exit(1)
Expand Down
73 changes: 63 additions & 10 deletions pkg/controllers/applicationset_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ type ApplicationSetReconciler struct {
Scheme *runtime.Scheme
Recorder record.EventRecorder
Generators []generators.Generator
utils.Policy
utils.Renderer
}

Expand All @@ -69,13 +70,23 @@ func (r *ApplicationSetReconciler) Reconcile(req ctrl.Request) (ctrl.Result, err
return ctrl.Result{}, err
}

err = r.createOrUpdateInCluster(ctx, applicationSetInfo, desiredApplications)
if err != nil {
return ctrl.Result{}, err
if r.Policy.Update() {
err = r.createOrUpdateInCluster(ctx, applicationSetInfo, desiredApplications)
if err != nil {
return ctrl.Result{}, err
}
} else {
err = r.createInCluster(ctx, applicationSetInfo, desiredApplications)
if err != nil {
return ctrl.Result{}, err
}
}
err = r.deleteInCluster(ctx, applicationSetInfo, desiredApplications)
if err != nil {
return ctrl.Result{}, err

if r.Policy.Delete() {
err = r.deleteInCluster(ctx, applicationSetInfo, desiredApplications)
if err != nil {
return ctrl.Result{}, err
}
}

return ctrl.Result{}, nil
Expand Down Expand Up @@ -180,7 +191,7 @@ func (r *ApplicationSetReconciler) createOrUpdateInCluster(ctx context.Context,
})

if err != nil {
appLog.WithError(err).WithField("action", action).Error("failed to create/update Application")
appLog.WithError(err).WithField("action", action).Errorf("failed to %s Application", action)
if firstError == nil {
firstError = err
}
Expand All @@ -193,13 +204,55 @@ func (r *ApplicationSetReconciler) createOrUpdateInCluster(ctx context.Context,
return firstError
}


// createInCluster will filter from the desiredApplications only the application that needs to be created
// Then it will call createOrUpdateInCluster to do the actual create
func (r *ApplicationSetReconciler) createInCluster(ctx context.Context, applicationSet argoprojiov1alpha1.ApplicationSet, desiredApplications []argov1alpha1.Application) error {

var createApps []argov1alpha1.Application
current, err := r.getCurrentApplications(ctx,applicationSet)
if err != nil {
return err
}

m := make(map[string]bool) // Will holds the app names that are current in the cluster

for _, app := range current {
m[app.Name] = true
}

// filter applications that are not in m[string]bool (new to the cluster)
for _, app := range desiredApplications {
_, exists := m[app.Name]

if !exists {
createApps = append(createApps, app)
}
}

return r.createOrUpdateInCluster(ctx, applicationSet, createApps)
}

func (r *ApplicationSetReconciler) getCurrentApplications(ctx context.Context, applicationSet argoprojiov1alpha1.ApplicationSet) ([]argov1alpha1.Application, error){
var current argov1alpha1.ApplicationList
err := r.Client.List(context.Background(), &current, client.MatchingFields{".metadata.controller": applicationSet.Name})

if err != nil {
return nil, err
}

return current.Items, nil
}

// deleteInCluster will delete application that are current in the cluster but not in appList.
// The function must be called after all generators had been called and generated applications
func (r *ApplicationSetReconciler) deleteInCluster(ctx context.Context, applicationSet argoprojiov1alpha1.ApplicationSet, desiredApplications []argov1alpha1.Application) error {

// Save current applications to be able to delete the ones that are not in appList
var current argov1alpha1.ApplicationList
_ = r.Client.List(context.Background(), &current, client.MatchingFields{".metadata.controller": applicationSet.Name})
current,err := r.getCurrentApplications(ctx,applicationSet)
if err != nil {
return err
}

m := make(map[string]bool) // Will holds the app names in appList for the deletion process

Expand All @@ -209,7 +262,7 @@ func (r *ApplicationSetReconciler) deleteInCluster(ctx context.Context, applicat

// Delete apps that are not in m[string]bool
var firstError error
for _, app := range current.Items {
for _, app := range current {
appLog := log.WithFields(log.Fields{"app": app.Name, "appSet": applicationSet.Name})
_, exists := m[app.Name]

Expand Down
187 changes: 187 additions & 0 deletions pkg/controllers/applicationset_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,193 @@ func TestCreateOrUpdateInCluster(t *testing.T) {

}

func TestCreateApplications(t *testing.T) {

scheme := runtime.NewScheme()
argoprojiov1alpha1.AddToScheme(scheme)
argov1alpha1.AddToScheme(scheme)

for _, c := range []struct {
appSet argoprojiov1alpha1.ApplicationSet
existsApps []argov1alpha1.Application
apps []argov1alpha1.Application
expected []argov1alpha1.Application
}{
{
appSet: argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "name",
Namespace: "namespace",
},
},
existsApps: nil,
apps: []argov1alpha1.Application{
{
ObjectMeta: metav1.ObjectMeta{
Name: "app1",
},
},
},
expected: []argov1alpha1.Application{
{
TypeMeta: metav1.TypeMeta{
Kind: "Application",
APIVersion: "argoproj.io/v1alpha1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "app1",
Namespace: "namespace",
ResourceVersion: "1",
},
},
},
},
{
appSet: argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "name",
Namespace: "namespace",
},
Spec: argoprojiov1alpha1.ApplicationSetSpec{
Template: argoprojiov1alpha1.ApplicationSetTemplate{
Spec: argov1alpha1.ApplicationSpec{
Project: "project",
},
},
},
},
existsApps: []argov1alpha1.Application{
argov1alpha1.Application{
TypeMeta: metav1.TypeMeta{
Kind: "Application",
APIVersion: "argoproj.io/v1alpha1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "app1",
Namespace: "namespace",
ResourceVersion: "2",
},
Spec: argov1alpha1.ApplicationSpec{
Project: "test",
},
},
},
apps: []argov1alpha1.Application{
{
ObjectMeta: metav1.ObjectMeta{
Name: "app1",
},
Spec: argov1alpha1.ApplicationSpec{
Project: "project",
},
},
},
expected: []argov1alpha1.Application{
{
TypeMeta: metav1.TypeMeta{
Kind: "Application",
APIVersion: "argoproj.io/v1alpha1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "app1",
Namespace: "namespace",
ResourceVersion: "2",
},
Spec: argov1alpha1.ApplicationSpec{
Project: "test",
},
},
},
},
{
appSet: argoprojiov1alpha1.ApplicationSet{
ObjectMeta: metav1.ObjectMeta{
Name: "name",
Namespace: "namespace",
},
Spec: argoprojiov1alpha1.ApplicationSetSpec{
Template: argoprojiov1alpha1.ApplicationSetTemplate{
Spec: argov1alpha1.ApplicationSpec{
Project: "project",
},
},
},
},
existsApps: []argov1alpha1.Application{
argov1alpha1.Application{
TypeMeta: metav1.TypeMeta{
Kind: "Application",
APIVersion: "argoproj.io/v1alpha1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "app1",
Namespace: "namespace",
ResourceVersion: "2",
},
Spec: argov1alpha1.ApplicationSpec{
Project: "test",
},
},
},
apps: []argov1alpha1.Application{
{
ObjectMeta: metav1.ObjectMeta{
Name: "app2",
},
Spec: argov1alpha1.ApplicationSpec{
Project: "project",
},
},
},
expected: []argov1alpha1.Application{
{
TypeMeta: metav1.TypeMeta{
Kind: "Application",
APIVersion: "argoproj.io/v1alpha1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "app2",
Namespace: "namespace",
ResourceVersion: "1",
},
Spec: argov1alpha1.ApplicationSpec{
Project: "project",
},
},
},
},
} {
initObjs := []runtime.Object{&c.appSet}
for _, a := range c.existsApps {
controllerutil.SetControllerReference(&c.appSet, &a, scheme)
initObjs = append(initObjs, &a)
}

client := fake.NewFakeClientWithScheme(scheme, initObjs...)

r := ApplicationSetReconciler{
Client: client,
Scheme: scheme,
Recorder: record.NewFakeRecorder(len(initObjs) + len(c.expected)),
}

r.createInCluster(context.TODO(), c.appSet, c.apps)

for _, obj := range c.expected {
got := &argov1alpha1.Application{}
_ = client.Get(context.Background(), crtclient.ObjectKey{
Namespace: obj.Namespace,
Name: obj.Name,
}, got)

controllerutil.SetControllerReference(&c.appSet, &obj, r.Scheme)

assert.Equal(t, obj, *got)
}
}

}

func TestDeleteInCluster(t *testing.T) {

scheme := runtime.NewScheme()
Expand Down
45 changes: 45 additions & 0 deletions pkg/utils/policy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package utils

// Policy allows to apply different rules to a set of changes.
type Policy interface {
Update() bool
Delete() bool
}

// Policies is a registry of available policies.
var Policies = map[string]Policy{
"sync": &SyncPolicy{},
"create-only": &CreateOnlyPolicy{},
"create-update": &CreateUpdatePolicy{},
}

type SyncPolicy struct{}

func (p *SyncPolicy) Update() bool {
return true
}

func (p *SyncPolicy) Delete() bool {
return true
}

type CreateUpdatePolicy struct{}

func (p *CreateUpdatePolicy) Update() bool {
return true
}

func (p *CreateUpdatePolicy) Delete() bool {
return false
}

type CreateOnlyPolicy struct{}

func (p *CreateOnlyPolicy) Update() bool {
return false
}

func (p *CreateOnlyPolicy) Delete() bool {
return false
}