Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refresh argocd apps on exit #341

Merged
merged 1 commit into from
Jul 25, 2024
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 111 additions & 0 deletions pkg/controllers/localbuild/argo_test.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,42 @@
package localbuild

import (
"context"
"testing"

argov1alpha1 "github.com/cnoe-io/argocd-api/api/argo/application/v1alpha1"
"github.com/cnoe-io/idpbuilder/api/v1alpha1"
"github.com/cnoe-io/idpbuilder/pkg/k8s"
"github.com/cnoe-io/idpbuilder/pkg/util"
"github.com/stretchr/testify/mock"
"gotest.tools/v3/assert"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"sigs.k8s.io/controller-runtime/pkg/client"
)

type fakeKubeClient struct {
mock.Mock
client.Client
}

func (f *fakeKubeClient) List(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error {
args := f.Called(ctx, list, opts)
return args.Error(0)
}

func (f *fakeKubeClient) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.PatchOption) error {
args := f.Called(ctx, obj, patch, opts)
return args.Error(0)
}

type testCase struct {
err error
listApps []argov1alpha1.Application
annotations []map[string]string
}

func TestGetRawInstallResources(t *testing.T) {
e := EmbeddedInstallation{
resourceFS: installArgoFS,
Expand Down Expand Up @@ -53,3 +83,84 @@ func TestGetK8sInstallResources(t *testing.T) {
t.Fatalf("Expected 58 Argo Install Resources, got: %d", len(objs))
}
}

func TestArgoCDAppAnnotation(t *testing.T) {
ctx := context.Background()

cases := []testCase{
{
err: nil,
listApps: []argov1alpha1.Application{
{
TypeMeta: metav1.TypeMeta{
Kind: argov1alpha1.ApplicationSchemaGroupVersionKind.Kind,
APIVersion: argov1alpha1.ApplicationSchemaGroupVersionKind.GroupVersion().String(),
},
ObjectMeta: metav1.ObjectMeta{
Name: "nil-annotation",
Namespace: "argocd",
},
},
},
annotations: []map[string]string{
{
argoCDApplicationAnnotationKeyRefresh: argoCDApplicationAnnotationValueRefreshNormal,
},
},
},
{
err: nil,
listApps: []argov1alpha1.Application{
{
TypeMeta: metav1.TypeMeta{
Kind: argov1alpha1.ApplicationSchemaGroupVersionKind.Kind,
APIVersion: argov1alpha1.ApplicationSchemaGroupVersionKind.GroupVersion().String(),
},
ObjectMeta: metav1.ObjectMeta{
Name: "existing-annotation",
Namespace: "argocd",
Annotations: map[string]string{
"test": "value",
},
},
},
},
annotations: []map[string]string{
{
"test": "value",
argoCDApplicationAnnotationKeyRefresh: argoCDApplicationAnnotationValueRefreshNormal,
},
},
},
}

for i := range cases {
c := cases[i]
fClient := new(fakeKubeClient)
fClient.On("List", ctx, mock.Anything, []client.ListOption{client.InNamespace(argocdNamespace)}).
Run(func(args mock.Arguments) {
apps := args.Get(1).(*argov1alpha1.ApplicationList)
apps.Items = c.listApps
}).Return(c.err)
for j := range c.annotations {
app := c.listApps[j]
u := makeUnstructured(app.Name, app.Namespace, app.GroupVersionKind(), c.annotations[j])
fClient.On("Patch", ctx, u, client.Apply, []client.PatchOption{client.FieldOwner(v1alpha1.FieldManager)}).Return(nil)
}
rec := LocalbuildReconciler{
Client: fClient,
}
err := rec.requestArgoCDAppRefresh(ctx)
fClient.AssertExpectations(t)
assert.NilError(t, err)
}
}

func makeUnstructured(name, namespace string, gvk schema.GroupVersionKind, annotations map[string]string) *unstructured.Unstructured {
u := &unstructured.Unstructured{}
u.SetAnnotations(annotations)
u.SetName(name)
u.SetNamespace(namespace)
u.SetGroupVersionKind(gvk)
return u
}
78 changes: 74 additions & 4 deletions pkg/controllers/localbuild/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ const (
defaultArgoCDProjectName string = "default"
defaultRequeueTime = time.Second * 15
errRequeueTime = time.Second * 5

argoCDApplicationAnnotationKeyRefresh = "argocd.argoproj.io/refresh"
argoCDApplicationAnnotationValueRefreshNormal = "normal"
argoCDApplicationSetAnnotationKeyRefresh = "argocd.argoproj.io/application-set-refresh"
argoCDApplicationSetAnnotationKeyRefreshTrue = "true"
)

type LocalbuildReconciler struct {
Expand Down Expand Up @@ -122,18 +127,26 @@ func (r *LocalbuildReconciler) installCorePackages(ctx context.Context, req ctrl

// Responsible to updating ObservedGeneration in status
func (r *LocalbuildReconciler) postProcessReconcile(ctx context.Context, req ctrl.Request, resource *v1alpha1.Localbuild) {
log := log.FromContext(ctx)
logger := log.FromContext(ctx)

log.Info("Checking if we should shutdown")
logger.Info("Checking if we should shutdown")
if r.shouldShutdown {
log.Info("Shutting Down")
logger.Info("Shutting Down")
err := r.requestArgoCDAppRefresh(ctx)
if err != nil {
logger.V(1).Info("failed requesting argocd application refresh", "error", err)
}
err = r.requestArgoCDAppSetRefresh(ctx)
if err != nil {
logger.V(1).Info("failed requesting argocd application set refresh", "error", err)
}
r.CancelFunc()
return
}

resource.Status.ObservedGeneration = resource.GetGeneration()
if err := r.Status().Update(ctx, resource); err != nil {
log.Error(err, "Failed to update resource status after reconcile")
logger.Error(err, "Failed to update resource status after reconcile")
}
}

Expand Down Expand Up @@ -527,6 +540,63 @@ func (r *LocalbuildReconciler) reconcileGitRepo(ctx context.Context, resource *v
return repo, err
}

func (r *LocalbuildReconciler) requestArgoCDAppRefresh(ctx context.Context) error {
apps := &argov1alpha1.ApplicationList{}
err := r.Client.List(ctx, apps, client.InNamespace(argocdNamespace))
if err != nil {
return fmt.Errorf("listing argocd apps for refresh: %w", err)
}

for i := range apps.Items {
app := apps.Items[i]
aErr := r.applyArgoCDAnnotation(ctx, &app, argocdapp.ApplicationKind, argoCDApplicationAnnotationKeyRefresh, argoCDApplicationAnnotationValueRefreshNormal)
if aErr != nil {
return aErr
}
}
return nil
}

func (r *LocalbuildReconciler) requestArgoCDAppSetRefresh(ctx context.Context) error {
appsets := &argov1alpha1.ApplicationSetList{}
err := r.Client.List(ctx, appsets, client.InNamespace(argocdNamespace))
if err != nil {
return fmt.Errorf("listing argocd apps for refresh: %w", err)
}

for i := range appsets.Items {
appset := appsets.Items[i]
aErr := r.applyArgoCDAnnotation(ctx, &appset, argocdapp.ApplicationSetKind, argoCDApplicationSetAnnotationKeyRefresh, argoCDApplicationSetAnnotationKeyRefreshTrue)
if aErr != nil {
return aErr
}
}
return nil
}

func (r *LocalbuildReconciler) applyArgoCDAnnotation(ctx context.Context, obj client.Object, argoCDType, annotationKey, annotationValue string) error {
annotations := obj.GetAnnotations()
if annotations != nil {
_, ok := annotations[annotationKey]
if !ok {
annotations[annotationKey] = annotationValue
err := util.ApplyAnnotation(ctx, r.Client, obj, annotations, client.FieldOwner(v1alpha1.FieldManager))
if err != nil {
return fmt.Errorf("applying %s refresh annotation for %s: %w", argoCDType, obj.GetName(), err)
}
}
} else {
a := map[string]string{
annotationKey: annotationValue,
}
err := util.ApplyAnnotation(ctx, r.Client, obj, a, client.FieldOwner(v1alpha1.FieldManager))
if err != nil {
return fmt.Errorf("applying %s refresh annotation for %s: %w", argoCDType, obj.GetName(), err)
}
}
return nil
}

func getCustomPackageName(fileName, appName string) string {
s := strings.Split(fileName, ".")
return fmt.Sprintf("%s-%s", strings.ToLower(s[0]), appName)
Expand Down
8 changes: 6 additions & 2 deletions pkg/util/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,14 +65,18 @@ func UpdateSyncAnnotation(ctx context.Context, kubeClient client.Client, obj cli
}
annotations := make(map[string]string, 1)
SetLastObservedSyncTimeAnnotationValue(annotations, timeStamp)

return ApplyAnnotation(ctx, kubeClient, obj, annotations, client.ForceOwnership, client.FieldOwner(v1alpha1.FieldManager))
}

func ApplyAnnotation(ctx context.Context, kubeClient client.Client, obj client.Object, annotations map[string]string, opts ...client.PatchOption) error {
// MUST be unstructured to avoid managing fields we do not care about.
u := unstructured.Unstructured{}
u.SetAnnotations(annotations)
u.SetName(obj.GetName())
u.SetNamespace(obj.GetNamespace())
u.SetGroupVersionKind(obj.GetObjectKind().GroupVersionKind())

return kubeClient.Patch(ctx, &u, client.Apply, client.ForceOwnership, client.FieldOwner(v1alpha1.FieldManager))
return kubeClient.Patch(ctx, &u, client.Apply, opts...)
}

func GeneratePassword() (string, error) {
Expand Down
Loading