Skip to content

Commit

Permalink
feat: remove argocd api dependency
Browse files Browse the repository at this point in the history
Signed-off-by: Mike Ng <[email protected]>
  • Loading branch information
mikeshng committed Nov 14, 2024
1 parent 6950841 commit 46da8b8
Show file tree
Hide file tree
Showing 13 changed files with 524 additions and 1,874 deletions.
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Build the manager binary
FROM golang:1.19 as builder
FROM golang:1.22 AS builder
ARG TARGETOS
ARG TARGETARCH

Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ vet: ## Run go vet against code.
go vet ./...

.PHONY: test
test: manifests generate fmt vet envtest ## Run tests.
test: manifests fmt vet envtest ## Run tests.
KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" go test ./... -coverprofile cover.out

##@ Build
Expand Down
51 changes: 33 additions & 18 deletions controllers/application/application_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,16 @@ import (
"context"

"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/event"
"sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/predicate"

argov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
clusterv1 "open-cluster-management.io/api/cluster/v1"
workv1 "open-cluster-management.io/api/work/v1"
)
Expand All @@ -50,6 +51,8 @@ const (
LabelKeyAppSet = "apps.open-cluster-management.io/application-set"
// Application and ManifestWork label that enables the pull controller to wrap the Application in ManifestWork payload
LabelKeyPull = "apps.open-cluster-management.io/pull-to-ocm-managed-cluster"
// ResourcesFinalizerName is the finalizer value which we inject to finalize deletion of an application
ResourcesFinalizerName string = "resources-finalizer.argocd.argoproj.io"
)

// ApplicationReconciler reconciles a Application object
Expand All @@ -65,25 +68,32 @@ type ApplicationReconciler struct {
// ApplicationPredicateFunctions defines which Application this controller should wrap inside ManifestWork's payload
var ApplicationPredicateFunctions = predicate.Funcs{
UpdateFunc: func(e event.UpdateEvent) bool {
newApp := e.ObjectNew.(*argov1alpha1.Application)
return containsValidPullLabel(newApp.Labels) && containsValidPullAnnotation(*newApp)

newObj := e.ObjectNew.(*unstructured.Unstructured)
return containsValidPullLabel(newObj.GetLabels()) &&
containsValidPullAnnotation(newObj.GetAnnotations())
},
CreateFunc: func(e event.CreateEvent) bool {
app := e.Object.(*argov1alpha1.Application)
return containsValidPullLabel(app.Labels) && containsValidPullAnnotation(*app)
obj := e.Object.(*unstructured.Unstructured)
return containsValidPullLabel(obj.GetLabels()) &&
containsValidPullAnnotation(obj.GetAnnotations())
},

DeleteFunc: func(e event.DeleteEvent) bool {
app := e.Object.(*argov1alpha1.Application)
return containsValidPullLabel(app.Labels) && containsValidPullAnnotation(*app)
obj := e.Object.(*unstructured.Unstructured)
return containsValidPullLabel(obj.GetLabels()) &&
containsValidPullAnnotation(obj.GetAnnotations())
},
}

// SetupWithManager sets up the controller with the Manager.
func (r *ApplicationReconciler) SetupWithManager(mgr ctrl.Manager) error {
applicationGVK := &unstructured.Unstructured{}
applicationGVK.SetGroupVersionKind(schema.GroupVersionKind{
Group: "argoproj.io",
Version: "v1alpha1",
Kind: "Application",
})
return ctrl.NewControllerManagedBy(mgr).
For(&argov1alpha1.Application{}).
For(applicationGVK).
WithEventFilter(ApplicationPredicateFunctions).
Complete(r)
}
Expand All @@ -93,22 +103,27 @@ func (r *ApplicationReconciler) Reconcile(ctx context.Context, req ctrl.Request)
log := log.FromContext(ctx)
log.Info("reconciling Application...")

var application argov1alpha1.Application
if err := r.Get(ctx, req.NamespacedName, &application); err != nil {
application := &unstructured.Unstructured{}
application.SetGroupVersionKind(schema.GroupVersionKind{
Group: "argoproj.io",
Version: "v1alpha1",
Kind: "Application",
})
if err := r.Get(ctx, req.NamespacedName, application); err != nil {
log.Error(err, "unable to fetch Application")
return ctrl.Result{}, client.IgnoreNotFound(err)
}

managedClusterName := application.GetAnnotations()[AnnotationKeyOCMManagedCluster]
mwName := generateManifestWorkName(application)
mwName := generateManifestWorkName(application.GetName(), application.GetUID())

// the Application is being deleted, find the ManifestWork and delete that as well
if application.ObjectMeta.DeletionTimestamp != nil {
if application.GetDeletionTimestamp() != nil {
// remove finalizer from Application but do not 'commit' yet
if len(application.Finalizers) != 0 {
if len(application.GetFinalizers()) != 0 {
f := application.GetFinalizers()
for i := 0; i < len(f); i++ {
if f[i] == argov1alpha1.ResourcesFinalizerName {
if f[i] == ResourcesFinalizerName {
f = append(f[:i], f[i+1:]...)
i--
}
Expand All @@ -121,7 +136,7 @@ func (r *ApplicationReconciler) Reconcile(ctx context.Context, req ctrl.Request)
err := r.Get(ctx, types.NamespacedName{Name: mwName, Namespace: managedClusterName}, &work)
if errors.IsNotFound(err) {
// already deleted ManifestWork, commit the Application finalizer removal
if err = r.Update(ctx, &application); err != nil {
if err = r.Update(ctx, application); err != nil {
log.Error(err, "unable to update Application")
return ctrl.Result{}, err
}
Expand All @@ -136,7 +151,7 @@ func (r *ApplicationReconciler) Reconcile(ctx context.Context, req ctrl.Request)
}

// deleted ManifestWork, commit the Application finalizer removal
if err := r.Update(ctx, &application); err != nil {
if err := r.Update(ctx, application); err != nil {
log.Error(err, "unable to update Application")
return ctrl.Result{}, err
}
Expand Down
98 changes: 57 additions & 41 deletions controllers/application/application_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,10 @@ import (
. "github.com/onsi/gomega"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"

argov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
corev1 "k8s.io/api/core/v1"
clusterv1 "open-cluster-management.io/api/cluster/v1"
workv1 "open-cluster-management.io/api/work/v1"
Expand All @@ -48,23 +49,31 @@ var _ = Describe("Application Pull controller", func() {
Context("When Application without OCM pull label is created", func() {
It("Should not create ManifestWork", func() {
By("Creating the Application without OCM pull label")
app1 := argov1alpha1.Application{
ObjectMeta: metav1.ObjectMeta{
Name: appName,
Namespace: appNamespace,
Annotations: map[string]string{AnnotationKeyOCMManagedCluster: clusterName},
},
Spec: argov1alpha1.ApplicationSpec{
Source: &argov1alpha1.ApplicationSource{
RepoURL: "default",
},
},
}
Expect(k8sClient.Create(ctx, &app1)).Should(Succeed())
app1 = argov1alpha1.Application{}
Expect(k8sClient.Get(ctx, appKey, &app1)).Should(Succeed())

mwKey := types.NamespacedName{Name: generateManifestWorkName(app1), Namespace: clusterName}
app1 := &unstructured.Unstructured{}
app1.SetGroupVersionKind(schema.GroupVersionKind{
Group: "argoproj.io",
Version: "v1alpha1",
Kind: "Application",
})
app1.SetName(appName)
app1.SetNamespace(appNamespace)
app1.SetAnnotations(map[string]string{AnnotationKeyOCMManagedCluster: clusterName})

// Set required spec fields
_ = unstructured.SetNestedField(app1.Object, "default", "spec", "project")
_ = unstructured.SetNestedField(app1.Object, "default", "spec", "source", "repoURL")
_ = unstructured.SetNestedMap(app1.Object, map[string]interface{}{"server": KubernetesInternalAPIServerAddr}, "spec", "destination")

Expect(k8sClient.Create(ctx, app1)).Should(Succeed())
app1 = &unstructured.Unstructured{}
app1.SetGroupVersionKind(schema.GroupVersionKind{
Group: "argoproj.io",
Version: "v1alpha1",
Kind: "Application",
})
Expect(k8sClient.Get(ctx, appKey, app1)).Should(Succeed())

mwKey := types.NamespacedName{Name: generateManifestWorkName(app1.GetName(), app1.GetUID()), Namespace: clusterName}
mw := workv1.ManifestWork{}
Consistently(func() bool {
if err := k8sClient.Get(ctx, mwKey, &mw); err != nil {
Expand Down Expand Up @@ -94,26 +103,33 @@ var _ = Describe("Application Pull controller", func() {
Expect(k8sClient.Create(ctx, &managedClusterNs)).Should(Succeed())

By("Creating the Application with OCM pull label")
app2 := argov1alpha1.Application{
ObjectMeta: metav1.ObjectMeta{
Name: appName2,
Namespace: appNamespace,
Labels: map[string]string{LabelKeyPull: strconv.FormatBool(true)},
Annotations: map[string]string{AnnotationKeyOCMManagedCluster: clusterName},
Finalizers: []string{argov1alpha1.ResourcesFinalizerName},
},
Spec: argov1alpha1.ApplicationSpec{
Project: "default",
Source: &argov1alpha1.ApplicationSource{
RepoURL: "default",
},
},
}
Expect(k8sClient.Create(ctx, &app2)).Should(Succeed())
app2 = argov1alpha1.Application{}
Expect(k8sClient.Get(ctx, appKey2, &app2)).Should(Succeed())

mwKey := types.NamespacedName{Name: generateManifestWorkName(app2), Namespace: clusterName}
app2 := &unstructured.Unstructured{}
app2.SetGroupVersionKind(schema.GroupVersionKind{
Group: "argoproj.io",
Version: "v1alpha1",
Kind: "Application",
})
app2.SetName(appName2)
app2.SetNamespace(appNamespace)
app2.SetLabels(map[string]string{LabelKeyPull: strconv.FormatBool(true)})
app2.SetAnnotations(map[string]string{AnnotationKeyOCMManagedCluster: clusterName})
app2.SetFinalizers([]string{ResourcesFinalizerName})

// Set required spec fields
_ = unstructured.SetNestedField(app2.Object, "default", "spec", "project")
_ = unstructured.SetNestedField(app2.Object, "default", "spec", "source", "repoURL")
_ = unstructured.SetNestedMap(app2.Object, map[string]interface{}{"server": KubernetesInternalAPIServerAddr}, "spec", "destination")

Expect(k8sClient.Create(ctx, app2)).Should(Succeed())
app2 = &unstructured.Unstructured{}
app2.SetGroupVersionKind(schema.GroupVersionKind{
Group: "argoproj.io",
Version: "v1alpha1",
Kind: "Application",
})
Expect(k8sClient.Get(ctx, appKey2, app2)).Should(Succeed())

mwKey := types.NamespacedName{Name: generateManifestWorkName(app2.GetName(), app2.GetUID()), Namespace: clusterName}
mw := workv1.ManifestWork{}
Eventually(func() bool {
if err := k8sClient.Get(ctx, mwKey, &mw); err != nil {
Expand All @@ -124,8 +140,8 @@ var _ = Describe("Application Pull controller", func() {

By("Updating the Application")
oldRv := mw.GetResourceVersion()
app2.Spec.Project = "somethingelse"
Expect(k8sClient.Update(ctx, &app2)).Should(Succeed())
_ = unstructured.SetNestedField(app2.Object, "somethingelse", "spec", "project")
Expect(k8sClient.Update(ctx, app2)).Should(Succeed())
Eventually(func() bool {
if err := k8sClient.Get(ctx, mwKey, &mw); err != nil {
return false
Expand All @@ -134,7 +150,7 @@ var _ = Describe("Application Pull controller", func() {
}).Should(BeTrue())

By("Deleting the Application")
Expect(k8sClient.Delete(ctx, &app2)).Should(Succeed())
Expect(k8sClient.Delete(ctx, app2)).Should(Succeed())
Eventually(func() bool {
if err := k8sClient.Get(ctx, mwKey, &mw); err != nil {
return true
Expand Down
51 changes: 33 additions & 18 deletions controllers/application/application_status_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,16 @@ package application
import (
"context"

"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/event"
"sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/predicate"

argov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"github.com/argoproj/gitops-engine/pkg/health"
workv1 "open-cluster-management.io/api/work/v1"
)

Expand Down Expand Up @@ -66,6 +66,7 @@ func (re *ApplicationStatusReconciler) SetupWithManager(mgr ctrl.Manager) error
Complete(re)
}

// Reconcile populates the Application status based on the associated ManifestWork's status feedback
// Reconcile populates the Application status based on the associated ManifestWork's status feedback
func (r *ApplicationStatusReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
log := log.FromContext(ctx)
Expand All @@ -78,48 +79,62 @@ func (r *ApplicationStatusReconciler) Reconcile(ctx context.Context, req ctrl.Re
return ctrl.Result{}, client.IgnoreNotFound(err)
}

// Check if the ManifestWork is being deleted
if manifestWork.ObjectMeta.DeletionTimestamp != nil {
return ctrl.Result{}, nil
}

// Extract status feedbacks for healthStatus and syncStatus
resourceManifests := manifestWork.Status.ResourceStatus.Manifests

healthStatus := ""
syncStatus := ""
if len(resourceManifests) > 0 {
statusFeedbacks := resourceManifests[0].StatusFeedbacks.Values
if len(statusFeedbacks) > 0 {
for _, statusFeedback := range statusFeedbacks {
if statusFeedback.Name == "healthStatus" {
healthStatus = *statusFeedback.Value.String
}
if statusFeedback.Name == "syncStatus" {
syncStatus = *statusFeedback.Value.String
}
for _, statusFeedback := range statusFeedbacks {
if statusFeedback.Name == "healthStatus" {
healthStatus = *statusFeedback.Value.String
}
if statusFeedback.Name == "syncStatus" {
syncStatus = *statusFeedback.Value.String
}
}
}

// If status values are missing, log and return
if len(healthStatus) == 0 || len(syncStatus) == 0 {
log.Info("healthStatus and syncStatus are both not in ManifestWork status feedback yet")
return ctrl.Result{}, nil
}

// Retrieve Application reference from annotations
applicationNamespace := manifestWork.Annotations[AnnotationKeyHubApplicationNamespace]
applicationName := manifestWork.Annotations[AnnotationKeyHubApplicationName]

application := argov1alpha1.Application{}
if err := r.Get(ctx, types.NamespacedName{Namespace: applicationNamespace, Name: applicationName}, &application); err != nil {
// Fetch the Application as an unstructured object
application := &unstructured.Unstructured{}
application.SetGroupVersionKind(schema.GroupVersionKind{
Group: "argoproj.io",
Version: "v1alpha1",
Kind: "Application",
})

if err := r.Get(ctx, types.NamespacedName{Namespace: applicationNamespace, Name: applicationName}, application); err != nil {
log.Error(err, "unable to fetch Application")
return ctrl.Result{}, err
}

application.Status.Sync.Status = argov1alpha1.SyncStatusCode(syncStatus)
application.Status.Health.Status = health.HealthStatusCode(healthStatus)
log.Info("updating Application status with ManifestWork status feedbacks")
// Update the Application's status fields dynamically
if err := unstructured.SetNestedField(application.Object, healthStatus, "status", "health", "status"); err != nil {
log.Error(err, "unable to set healthStatus in Application status")
return ctrl.Result{}, err
}
if err := unstructured.SetNestedField(application.Object, syncStatus, "status", "sync", "status"); err != nil {
log.Error(err, "unable to set syncStatus in Application status")
return ctrl.Result{}, err
}

err := r.Client.Update(ctx, &application)
if err != nil {
log.Info("updating Application status with ManifestWork status feedbacks")
if err := r.Client.Update(ctx, application); err != nil {
log.Error(err, "unable to update Application")
return ctrl.Result{}, err
}
Expand Down
Loading

0 comments on commit 46da8b8

Please sign in to comment.