summaryrefslogtreecommitdiff
path: root/pkg
diff options
context:
space:
mode:
authorMiguel Ángel García <magmax@users.noreply.github.com>2021-03-26 11:13:46 +0100
committerGitHub <noreply@github.com>2021-03-26 11:13:46 +0100
commit274d19c7a06a8413db07672d9983c0821f488ba5 (patch)
tree85029d70045bac81846b89fbcdb381320ce3ab6d /pkg
parent21df300598eed7ca52a015a15de9e4ccc4d75107 (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.go43
-rw-r--r--pkg/kube/kubernetes.go43
-rw-r--r--pkg/kube/kubernetes_test.go30
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)
+ })
+}