diff options
| author | Miguel Ángel García <magmax@users.noreply.github.com> | 2021-03-26 11:13:46 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-03-26 11:13:46 +0100 |
| commit | 274d19c7a06a8413db07672d9983c0821f488ba5 (patch) | |
| tree | 85029d70045bac81846b89fbcdb381320ce3ab6d /pkg | |
| parent | 21df300598eed7ca52a015a15de9e4ccc4d75107 (diff) | |
feat: #164 Send events on image change (#167)
* Send events on image change
* fix lints
* fix the rest of lints
* Addressing feedback: parameterize kubeEvents
* small fixes
* Running kustomize
* try to fix the problem after running make manifests
* Move the message to the correct place
* reduce annotation size and randomness
* Provide full image names as well
* goimports/lint
Diffstat (limited to 'pkg')
| -rw-r--r-- | pkg/argocd/update.go | 43 | ||||
| -rw-r--r-- | pkg/kube/kubernetes.go | 43 | ||||
| -rw-r--r-- | pkg/kube/kubernetes_test.go | 30 |
3 files changed, 105 insertions, 11 deletions
diff --git a/pkg/argocd/update.go b/pkg/argocd/update.go index 2cfcf9e..61939fc 100644 --- a/pkg/argocd/update.go +++ b/pkg/argocd/update.go @@ -14,6 +14,7 @@ import ( "github.com/argoproj-labs/argocd-image-updater/pkg/kube" "github.com/argoproj-labs/argocd-image-updater/pkg/log" "github.com/argoproj-labs/argocd-image-updater/pkg/registry" + "github.com/argoproj-labs/argocd-image-updater/pkg/tag" "gopkg.in/yaml.v2" @@ -32,13 +33,14 @@ type ImageUpdaterResult struct { } type UpdateConfiguration struct { - NewRegFN registry.NewRegistryClient - ArgoClient ArgoCD - KubeClient *kube.KubernetesClient - UpdateApp *ApplicationImages - DryRun bool - GitCommitUser string - GitCommitEmail string + NewRegFN registry.NewRegistryClient + ArgoClient ArgoCD + KubeClient *kube.KubernetesClient + UpdateApp *ApplicationImages + DryRun bool + GitCommitUser string + GitCommitEmail string + DisableKubeEvents bool } type GitCredsSource func(app *v1alpha1.Application) (git.Creds, error) @@ -79,12 +81,19 @@ type helmOverride struct { Helm helmParameters `json:"helm"` } +type change struct { + image *image.ContainerImage + oldTag *tag.ImageTag + newTag *tag.ImageTag +} + // UpdateApplication update all images of a single application. Will run in a goroutine. func UpdateApplication(updateConf *UpdateConfiguration) ImageUpdaterResult { var needUpdate bool = false result := ImageUpdaterResult{} app := updateConf.UpdateApp.Application.GetName() + changeList := make([]change, 0) // Get all images that are deployed with the current application applicationImages := GetImagesFromApplication(&updateConf.UpdateApp.Application) @@ -203,8 +212,9 @@ func UpdateApplication(updateConf *UpdateConfiguration) ImageUpdaterResult { result.NumErrors += 1 continue } else { - imgCtx.Infof("Successfully updated image '%s' to '%s', but pending spec update (dry run=%v)", updateableImage.GetFullNameWithTag(), updateableImage.WithTag(latest).GetFullNameWithTag(), updateConf.DryRun) - result.NumImagesUpdated += 1 + containerImageNew := updateableImage.WithTag(latest) + imgCtx.Infof("Successfully updated image '%s' to '%s', but pending spec update (dry run=%v)", updateableImage.GetFullNameWithTag(), containerImageNew.GetFullNameWithTag(), updateConf.DryRun) + changeList = append(changeList, change{containerImageNew, updateableImage.ImageTag, containerImageNew.ImageTag}) } } else { // We need to explicitly set the up-to-date images in the spec too, so @@ -244,6 +254,21 @@ func UpdateApplication(updateConf *UpdateConfiguration) ImageUpdaterResult { result.NumImagesUpdated = 0 } else { logCtx.Infof("Successfully updated the live application spec") + result.NumImagesUpdated += 1 + if !updateConf.DisableKubeEvents && updateConf.KubeClient != nil { + annotations := map[string]string{} + for i, c := range changeList { + annotations[fmt.Sprintf("argocd-image-updater.image-%d/full-image-name", i)] = c.image.GetFullNameWithoutTag() + annotations[fmt.Sprintf("argocd-image-updater.image-%d/image-name", i)] = c.image.ImageName + annotations[fmt.Sprintf("argocd-image-updater.image-%d/old-tag", i)] = c.oldTag.TagName + annotations[fmt.Sprintf("argocd-image-updater.image-%d/new-tag", i)] = c.newTag.TagName + } + message := fmt.Sprintf("Successfully updated application '%s'", app) + _, err = updateConf.KubeClient.CreateApplicationEvent(&updateConf.UpdateApp.Application, "ImagesUpdated", message, annotations) + if err != nil { + logCtx.Warnf("Event could not be sent: %v", err) + } + } } } else { logCtx.Infof("Dry run - not commiting %d changes to application", result.NumImagesUpdated) diff --git a/pkg/kube/kubernetes.go b/pkg/kube/kubernetes.go index a46efb1..bfe7dde 100644 --- a/pkg/kube/kubernetes.go +++ b/pkg/kube/kubernetes.go @@ -6,11 +6,14 @@ import ( "context" "fmt" "os" + "time" "github.com/argoproj-labs/argocd-image-updater/pkg/metrics" + appv1alpha1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1" "github.com/argoproj/argo-cd/pkg/client/clientset/versioned" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/clientcmd" ) @@ -68,7 +71,7 @@ func NewKubernetesClientFromConfig(ctx context.Context, namespace string, kubeco // GetSecretData returns the raw data from named K8s secret in given namespace func (client *KubernetesClient) GetSecretData(namespace string, secretName string) (map[string][]byte, error) { - secret, err := client.Clientset.CoreV1().Secrets(namespace).Get(client.Context, secretName, v1.GetOptions{}) + secret, err := client.Clientset.CoreV1().Secrets(namespace).Get(client.Context, secretName, metav1.GetOptions{}) metrics.Clients().IncreaseK8sClientRequest(1) if err != nil { metrics.Clients().IncreaseK8sClientRequest(1) @@ -91,3 +94,39 @@ func (client *KubernetesClient) GetSecretField(namespace string, secretName stri return string(data), nil } } + +// CreateApplicationevent creates a kubernetes event with a custom reason and message for an application. +func (client *KubernetesClient) CreateApplicationEvent(app *appv1alpha1.Application, reason string, message string, annotations map[string]string) (*v1.Event, error) { + t := metav1.Time{Time: time.Now()} + + event := v1.Event{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("%v.%x", app.ObjectMeta.Name, t.UnixNano()), + Namespace: client.Namespace, + Annotations: annotations, + }, + Source: v1.EventSource{ + Component: "ArgocdImageUpdater", + }, + InvolvedObject: v1.ObjectReference{ + Kind: app.Kind, + APIVersion: app.APIVersion, + Name: app.ObjectMeta.Name, + Namespace: app.ObjectMeta.Namespace, + ResourceVersion: app.ObjectMeta.ResourceVersion, + UID: app.ObjectMeta.UID, + }, + FirstTimestamp: t, + LastTimestamp: t, + Count: 1, + Message: message, + Type: v1.EventTypeNormal, + Reason: reason, + } + + result, err := client.Clientset.CoreV1().Events(client.Namespace).Create(client.Context, &event, metav1.CreateOptions{}) + if err != nil { + return nil, err + } + return result, nil +} diff --git a/pkg/kube/kubernetes_test.go b/pkg/kube/kubernetes_test.go index ef719d2..3fe6453 100644 --- a/pkg/kube/kubernetes_test.go +++ b/pkg/kube/kubernetes_test.go @@ -4,6 +4,9 @@ import ( "context" "testing" + appv1alpha1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/argoproj-labs/argocd-image-updater/test/fake" "github.com/argoproj-labs/argocd-image-updater/test/fixture" @@ -66,3 +69,30 @@ func Test_GetDataFromSecrets(t *testing.T) { require.Empty(t, data) }) } + +func Test_CreateApplicationEvent(t *testing.T) { + t.Run("Create Event", func(t *testing.T) { + application := &appv1alpha1.Application{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-app", + Namespace: "argocd", + }, + Spec: appv1alpha1.ApplicationSpec{}, + Status: appv1alpha1.ApplicationStatus{ + Summary: appv1alpha1.ApplicationSummary{ + Images: []string{"nginx:1.12.2", "that/image", "quay.io/dexidp/dex:v1.23.0"}, + }, + }, + } + annotations := map[string]string{ + "origin": "nginx:1.12.2", + } + clientset := fake.NewFakeClientsetWithResources() + client := &KubernetesClient{Clientset: clientset, Namespace: "default"} + event, err := client.CreateApplicationEvent(application, "TestEvent", "test-message", annotations) + require.NoError(t, err) + require.NotNil(t, event) + assert.Equal(t, "ArgocdImageUpdater", event.Source.Component) + assert.Equal(t, "default", client.Namespace) + }) +} |
