summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmd/main.go21
-rw-r--r--manifests/base/deployment/argocd-image-updater-deployment.yaml6
-rw-r--r--manifests/base/rbac/argocd-image-updater-role.yaml7
-rw-r--r--manifests/install.yaml12
-rw-r--r--pkg/argocd/update.go43
-rw-r--r--pkg/kube/kubernetes.go43
-rw-r--r--pkg/kube/kubernetes_test.go30
7 files changed, 143 insertions, 19 deletions
diff --git a/cmd/main.go b/cmd/main.go
index 6e4f992..9d5e62f 100644
--- a/cmd/main.go
+++ b/cmd/main.go
@@ -51,6 +51,7 @@ type ImageUpdaterConfig struct {
AppNamePatterns []string
GitCommitUser string
GitCommitMail string
+ DisableKubeEvents bool
}
// warmupImageCache performs a cache warm-up, which is basically one cycle of
@@ -145,13 +146,14 @@ func runImageUpdater(cfg *ImageUpdaterConfig, warmUp bool) (argocd.ImageUpdaterR
defer sem.Release(1)
log.Debugf("Processing application %s", app)
upconf := &argocd.UpdateConfiguration{
- NewRegFN: registry.NewClient,
- ArgoClient: cfg.ArgoClient,
- KubeClient: cfg.KubeClient,
- UpdateApp: &curApplication,
- DryRun: dryRun,
- GitCommitUser: cfg.GitCommitUser,
- GitCommitEmail: cfg.GitCommitMail,
+ NewRegFN: registry.NewClient,
+ ArgoClient: cfg.ArgoClient,
+ KubeClient: cfg.KubeClient,
+ UpdateApp: &curApplication,
+ DryRun: dryRun,
+ GitCommitUser: cfg.GitCommitUser,
+ GitCommitEmail: cfg.GitCommitMail,
+ DisableKubeEvents: cfg.DisableKubeEvents,
}
res := argocd.UpdateApplication(upconf)
result.NumApplicationsProcessed += 1
@@ -238,6 +240,7 @@ func newTestCommand() *cobra.Command {
kubeConfig string
disableKubernetes bool
ignoreTags []string
+ disableKubeEvents bool
)
var runCmd = &cobra.Command{
Use: "test IMAGE",
@@ -251,7 +254,7 @@ to using the given parametrization. Command line switches can be used as a
way to supply the required parameters.
`,
Example: `
-# In the most simple form, check for the latest available (semver) version of
+# In the most simple form, check for the latest available (semver) version of
# an image in the registry
argocd-image-updater test nginx
@@ -372,6 +375,7 @@ argocd-image-updater test nginx --allow-tags '^1.19.\d+(\-.*)*$' --update-strate
runCmd.Flags().BoolVar(&disableKubernetes, "disable-kubernetes", false, "whether to disable the Kubernetes client")
runCmd.Flags().StringVar(&kubeConfig, "kubeconfig", "", "path to your Kubernetes client configuration")
runCmd.Flags().StringVar(&credentials, "credentials", "", "the credentials definition for the test (overrides registry config)")
+ runCmd.Flags().BoolVar(&disableKubeEvents, "disable-kubernetes-events", false, "Disable kubernetes events")
return runCmd
}
@@ -542,6 +546,7 @@ func newRunCommand() *cobra.Command {
runCmd.Flags().BoolVar(&warmUpCache, "warmup-cache", true, "whether to perform a cache warm-up on startup")
runCmd.Flags().StringVar(&cfg.GitCommitUser, "git-commit-user", env.GetStringVal("GIT_COMMIT_USER", "argocd-image-updater"), "Username to use for Git commits")
runCmd.Flags().StringVar(&cfg.GitCommitMail, "git-commit-email", env.GetStringVal("GIT_COMMIT_EMAIL", "noreply@argoproj.io"), "E-Mail address to use for Git commits")
+ runCmd.Flags().BoolVar(&cfg.DisableKubeEvents, "disable-kube-events", env.GetBoolVal("IMAGE_UPDATER_KUBE_EVENTS", false), "Disable kubernetes events")
return runCmd
}
diff --git a/manifests/base/deployment/argocd-image-updater-deployment.yaml b/manifests/base/deployment/argocd-image-updater-deployment.yaml
index c79dc1a..cba1326 100644
--- a/manifests/base/deployment/argocd-image-updater-deployment.yaml
+++ b/manifests/base/deployment/argocd-image-updater-deployment.yaml
@@ -76,6 +76,12 @@ spec:
name: argocd-image-updater-config
key: git.email
optional: true
+ - name: IMAGE_UPDATER_KUBE_EVENTS
+ valueFrom:
+ configMapKeyRef:
+ name: argocd-image-updater-config
+ key: kube.events
+ optional: true
livenessProbe:
httpGet:
path: /healthz
diff --git a/manifests/base/rbac/argocd-image-updater-role.yaml b/manifests/base/rbac/argocd-image-updater-role.yaml
index aa7cd02..60266f2 100644
--- a/manifests/base/rbac/argocd-image-updater-role.yaml
+++ b/manifests/base/rbac/argocd-image-updater-role.yaml
@@ -25,3 +25,10 @@ rules:
- list
- update
- patch
+ - apiGroups:
+ - ""
+ resources:
+ - events
+ verbs:
+ - create
+
diff --git a/manifests/install.yaml b/manifests/install.yaml
index 50091f7..0531861 100644
--- a/manifests/install.yaml
+++ b/manifests/install.yaml
@@ -34,6 +34,12 @@ rules:
- list
- update
- patch
+- apiGroups:
+ - ""
+ resources:
+ - events
+ verbs:
+ - create
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
@@ -143,6 +149,12 @@ spec:
key: git.email
name: argocd-image-updater-config
optional: true
+ - name: IMAGE_UPDATER_KUBE_EVENTS
+ valueFrom:
+ configMapKeyRef:
+ key: kube.events
+ name: argocd-image-updater-config
+ optional: true
image: argoprojlabs/argocd-image-updater:latest
imagePullPolicy: Always
livenessProbe:
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)
+ })
+}