From 073e08ee11cf7c3ed5525ac5e420eaf59e8c2443 Mon Sep 17 00:00:00 2001 From: CrazyMax Date: Thu, 29 Dec 2022 11:06:32 +0100 Subject: [PATCH] Entry metadata field --- docs/faq.md | 1 + docs/notif/script.md | 9 ++++- docs/notif/webhook.md | 13 ++++++- docs/providers/docker.md | 37 +++++++++++++------ docs/providers/dockerfile.md | 2 + docs/providers/file.md | 4 ++ docs/providers/kubernetes.md | 36 ++++++++++++------ docs/providers/nomad.md | 38 +++++++++++++------ docs/providers/swarm.md | 34 +++++++++++------ internal/app/diun.go | 53 +++++++++++++-------------- internal/app/job.go | 1 + internal/model/image.go | 23 ++++++------ internal/model/notif.go | 1 + internal/msg/client.go | 30 +++++++++------ internal/provider/common.go | 51 ++++++++++++++++++++------ internal/provider/docker/container.go | 37 ++++++++++++++++++- internal/provider/dockerfile/image.go | 2 +- internal/provider/kubernetes/pod.go | 15 +++++++- internal/provider/nomad/task.go | 15 +++++++- internal/provider/swarm/service.go | 12 +++++- test/dockerfile1/mount/Dockerfile | 1 + 21 files changed, 296 insertions(+), 119 deletions(-) diff --git a/docs/faq.md b/docs/faq.md index 51616125..ae887ab5 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -82,6 +82,7 @@ Templating is supported with the following fields: | `.Entry.Manifest.Labels` | Image labels | | `.Entry.Manifest.Layers` | Image layers | | `.Entry.Manifest.Platform` | Platform that the image is runs on. e.g. `linux/amd64` | +| `.Entry.Metadata` | Key-value pair of image metadata specific to each provider | ## Authentication against the registry diff --git a/docs/notif/script.md b/docs/notif/script.md index 1f9c93b6..8a2830b6 100644 --- a/docs/notif/script.md +++ b/docs/notif/script.md @@ -3,7 +3,7 @@ You can call a script when a notification occured. Following environment variables will be passed: ``` -DIUN_VERSION=3.0.0 +DIUN_VERSION=4.24.0 DIUN_ENTRY_STATUS=new DIUN_HOSTNAME=myserver DIUN_ENTRY_PROVIDER=file @@ -13,6 +13,13 @@ DIUN_ENTRY_MIMETYPE=application/vnd.docker.distribution.manifest.list.v2+json DIUN_ENTRY_DIGEST=sha256:216e3ae7de4ca8b553eb11ef7abda00651e79e537e85c46108284e5e91673e01 DIUN_ENTRY_CREATED=2020-03-26 12:23:56 +0000 UTC DIUN_ENTRY_PLATFORM=linux/amd64 +DIUN_ENTRY_METADATA_CTN_COMMAND=diun serve +DIUN_ENTRY_METADATA_CTN_CREATEDAT=2022-12-29 10:46:20 +0100 CET +DIUN_ENTRY_METADATA_CTN_ID=7c71187fad11aa06f951dee0ebd6382ee0030a8228929fc7ea2fccc18f940788 +DIUN_ENTRY_METADATA_CTN_NAMES=diun +DIUN_ENTRY_METADATA_CTN_SIZE=0B +DIUN_ENTRY_METADATA_CTN_STATE=running +DIUN_ENTRY_METADATA_CTN_STATUS=Up Less than a second (health: starting) ``` ## Configuration diff --git a/docs/notif/webhook.md b/docs/notif/webhook.md index 97920a9b..31a2b61b 100644 --- a/docs/notif/webhook.md +++ b/docs/notif/webhook.md @@ -35,7 +35,7 @@ The JSON response will look like this: ```json { - "diun_version": "4.0.0", + "diun_version": "4.24.0", "hostname": "myserver", "status": "new", "provider": "file", @@ -44,7 +44,16 @@ The JSON response will look like this: "mime_type": "application/vnd.docker.distribution.manifest.list.v2+json", "digest": "sha256:216e3ae7de4ca8b553eb11ef7abda00651e79e537e85c46108284e5e91673e01", "created": "2020-03-26T12:23:56Z", - "platform": "linux/amd64" + "platform": "linux/amd64", + "metadata": { + "ctn_command": "diun serve", + "ctn_createdat": "2022-12-29 10:22:15 +0100 CET", + "ctn_id": "0dbd10e15b31add2c48856fd34451adabf50d276efa466fe19a8ef5fbd87ad7c", + "ctn_names": "diun", + "ctn_size": "0B", + "ctn_state": "running", + "ctn_status": "Up Less than a second (health: starting)" + } } ``` diff --git a/docs/providers/docker.md b/docs/providers/docker.md index a614f8b5..46e09082 100644 --- a/docs/providers/docker.md +++ b/docs/providers/docker.md @@ -173,15 +173,28 @@ Include created and exited containers too (default `false`). You can configure more finely the way to analyze the image of your container through Docker labels: -| Name | Default | Description | -|---------------------|--------------|---------------------------------------------------------------------------------------------------------------------------------------------------------| -| `diun.enable` | | Set to true to enable image analysis of this container | -| `diun.regopt` | | [Registry options](../config/regopts.md) name to use | -| `diun.watch_repo` | `false` | Watch all tags of this container image ([be careful](../faq.md#docker-hub-rate-limits) with this setting) | -| `diun.notify_on` | `new;update` | Semicolon separated list of status to be notified: `new`, `update` | -| `diun.sort_tags` | `reverse` | [Sort tags method](../faq.md#tags-sorting-when-using-watch_repo) if `diun.watch_repo` enabled. One of `default`, `reverse`, `semver`, `lexicographical` | -| `diun.max_tags` | `0` | Maximum number of tags to watch if `diun.watch_repo` enabled. `0` means all of them | -| `diun.include_tags` | | Semicolon separated list of regular expressions to include tags. Can be useful if you enable `diun.watch_repo` | -| `diun.exclude_tags` | | Semicolon separated list of regular expressions to exclude tags. Can be useful if you enable `diun.watch_repo` | -| `diun.hub_link` | _automatic_ | Set registry hub link for this image | -| `diun.platform` | _automatic_ | Platform to use (e.g. `linux/amd64`) | +| Name | Default | Description | +|---------------------|--------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------| +| `diun.enable` | | Set to true to enable image analysis of this container | +| `diun.regopt` | | [Registry options](../config/regopts.md) name to use | +| `diun.watch_repo` | `false` | Watch all tags of this container image ([be careful](../faq.md#docker-hub-rate-limits) with this setting) | +| `diun.notify_on` | `new;update` | Semicolon separated list of status to be notified: `new`, `update` | +| `diun.sort_tags` | `reverse` | [Sort tags method](../faq.md#tags-sorting-when-using-watch_repo) if `diun.watch_repo` enabled. One of `default`, `reverse`, `semver`, `lexicographical` | +| `diun.max_tags` | `0` | Maximum number of tags to watch if `diun.watch_repo` enabled. `0` means all of them | +| `diun.include_tags` | | Semicolon separated list of regular expressions to include tags. Can be useful if you enable `diun.watch_repo` | +| `diun.exclude_tags` | | Semicolon separated list of regular expressions to exclude tags. Can be useful if you enable `diun.watch_repo` | +| `diun.hub_link` | _automatic_ | Set registry hub link for this image | +| `diun.platform` | _automatic_ | Platform to use (e.g. `linux/amd64`) | +| `diun.metadata.*` | See [below](#default-metadata) | Additional metadata that can be used in [notification template](../faq.md#notification-template) (e.g. `diun.metadata.foo=bar`) | + +## Default metadata + +| Key | Description | +|-------------------------------|-------------------------| +| `diun.metadata.ctn_id` | Container ID | +| `diun.metadata.ctn_names` | Container names | +| `diun.metadata.ctn_command` | Container command | +| `diun.metadata.ctn_createdat` | Container creation date | +| `diun.metadata.ctn_state` | Container state | +| `diun.metadata.ctn_status` | Container status | +| `diun.metadata.ctn_size` | Container size | diff --git a/docs/providers/dockerfile.md b/docs/providers/dockerfile.md index e82164cd..9270813b 100644 --- a/docs/providers/dockerfile.md +++ b/docs/providers/dockerfile.md @@ -37,6 +37,7 @@ providers: # syntax=docker/dockerfile:1.2 # diun.platform=linux/amd64 +# diun.metadata.foo=bar FROM alpine:latest # diun.watch_repo=true @@ -117,3 +118,4 @@ The following annotations can be added as comments before the target instruction | `diun.exclude_tags` | | Semicolon separated list of regular expressions to exclude tags. Can be useful if you enable `diun.watch_repo` | | `diun.hub_link` | _automatic_ | Set registry hub link for this image | | `diun.platform` | _automatic_ | Platform to use (e.g. `linux/amd64`) | +| `diun.metadata.*` | | Additional metadata that can be used in [notification template](../faq.md#notification-template) (e.g. `metadata.foo=bar`) | diff --git a/docs/providers/file.md b/docs/providers/file.md index 2d9e29f1..01873062 100644 --- a/docs/providers/file.md +++ b/docs/providers/file.md @@ -48,7 +48,10 @@ providers: regopt: myregistry # Watch coreos/hyperkube image on quay.io (Quay) and assume latest tag. +# Add foo=bar metadata to be used in notification template. - name: quay.io/coreos/hyperkube + metadata: + foo: bar # Watch crazymax/swarm-cronjob image and assume docker.io registry and latest tag # with registry options named 'docker.io/crazymax' (image selector). @@ -186,3 +189,4 @@ The configuration file(s) defines a slice of images to analyze with the followin | `platform.os` | _automatic_ | Operating system to use as custom platform | | `platform.arch` | _automatic_ | CPU architecture to use as custom platform | | `platform.variant` | _automatic_ | Variant of the CPU to use as custom platform | +| `metadata.*` | | Additional metadata that can be used in [notification template](../faq.md#notification-template) (e.g. `metadata.foo=bar`) | diff --git a/docs/providers/kubernetes.md b/docs/providers/kubernetes.md index df5a951e..124fdf7b 100644 --- a/docs/providers/kubernetes.md +++ b/docs/providers/kubernetes.md @@ -282,15 +282,27 @@ Enable watch by default. If false, pods that don't have `diun.enable: "true"` an You can configure more finely the way to analyze the image of your pods through Kubernetes annotations: -| Name | Default | Description | -|---------------------|--------------|---------------------------------------------------------------------------------------------------------------------------------------------------------| -| `diun.enable` | | Set to true to enable image analysis of this pod | -| `diun.regopt` | | [Registry options](../config/regopts.md) name to use | -| `diun.watch_repo` | `false` | Watch all tags of this pod image ([be careful](../faq.md#docker-hub-rate-limits) with this setting) | -| `diun.notify_on` | `new;update` | Semicolon separated list of status to be notified: `new`, `update`. | -| `diun.sort_tags` | `reverse` | [Sort tags method](../faq.md#tags-sorting-when-using-watch_repo) if `diun.watch_repo` enabled. One of `default`, `reverse`, `semver`, `lexicographical` | -| `diun.max_tags` | `0` | Maximum number of tags to watch if `diun.watch_repo` enabled. `0` means all of them | -| `diun.include_tags` | | Semicolon separated list of regular expressions to include tags. Can be useful if you enable `diun.watch_repo` | -| `diun.exclude_tags` | | Semicolon separated list of regular expressions to exclude tags. Can be useful if you enable `diun.watch_repo` | -| `diun.hub_link` | _automatic_ | Set registry hub link for this image | -| `diun.platform` | _automatic_ | Platform to use (e.g. `linux/amd64`) | +| Name | Default | Description | +|---------------------|--------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------| +| `diun.enable` | | Set to true to enable image analysis of this pod | +| `diun.regopt` | | [Registry options](../config/regopts.md) name to use | +| `diun.watch_repo` | `false` | Watch all tags of this pod image ([be careful](../faq.md#docker-hub-rate-limits) with this setting) | +| `diun.notify_on` | `new;update` | Semicolon separated list of status to be notified: `new`, `update`. | +| `diun.sort_tags` | `reverse` | [Sort tags method](../faq.md#tags-sorting-when-using-watch_repo) if `diun.watch_repo` enabled. One of `default`, `reverse`, `semver`, `lexicographical` | +| `diun.max_tags` | `0` | Maximum number of tags to watch if `diun.watch_repo` enabled. `0` means all of them | +| `diun.include_tags` | | Semicolon separated list of regular expressions to include tags. Can be useful if you enable `diun.watch_repo` | +| `diun.exclude_tags` | | Semicolon separated list of regular expressions to exclude tags. Can be useful if you enable `diun.watch_repo` | +| `diun.hub_link` | _automatic_ | Set registry hub link for this image | +| `diun.platform` | _automatic_ | Platform to use (e.g. `linux/amd64`) | +| `diun.metadata.*` | See [below](#default-metadata) | Additional metadata that can be used in [notification template](../faq.md#notification-template) (e.g. `diun.metadata.foo=bar`) | + +## Default metadata + +| Key | Description | +|-------------------------------|-------------------| +| `diun.metadata.pod_name` | Pod name | +| `diun.metadata.pod_status` | Pod status | +| `diun.metadata.pod_namespace` | Pod namespace | +| `diun.metadata.pod_createdat` | Pod creation date | +| `diun.metadata.ctn_name` | Container name | +| `diun.metadata.ctn_command` | Container command | diff --git a/docs/providers/nomad.md b/docs/providers/nomad.md index 53de4495..327e44c9 100644 --- a/docs/providers/nomad.md +++ b/docs/providers/nomad.md @@ -197,15 +197,29 @@ Enable watch by default. If false, tasks that don't have `diun.enable = true` in You can configure more finely the way to analyze the image of your tasks through Nomad meta attributes or service tags: -| Name | Default | Description | -|---------------------|--------------|---------------------------------------------------------------------------------------------------------------------------------------------------------| -| `diun.enable` | | Set to true to enable image analysis of this task | -| `diun.regopt` | | [Registry options](../config/regopts.md) name to use | -| `diun.watch_repo` | `false` | Watch all tags of this task image ([be careful](../faq.md#docker-hub-rate-limits) with this setting) | -| `diun.notify_on` | `new;update` | Semicolon separated list of status to be notified: `new`, `update`. | -| `diun.sort_tags` | `reverse` | [Sort tags method](../faq.md#tags-sorting-when-using-watch_repo) if `diun.watch_repo` enabled. One of `default`, `reverse`, `semver`, `lexicographical` | -| `diun.max_tags` | `0` | Maximum number of tags to watch if `diun.watch_repo` enabled. `0` means all of them | -| `diun.include_tags` | | Semicolon separated list of regular expressions to include tags. Can be useful if you enable `diun.watch_repo` | -| `diun.exclude_tags` | | Semicolon separated list of regular expressions to exclude tags. Can be useful if you enable `diun.watch_repo` | -| `diun.hub_link` | _automatic_ | Set registry hub link for this image | -| `diun.platform` | _automatic_ | Platform to use (e.g. `linux/amd64`) | +| Name | Default | Description | +|---------------------|--------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------| +| `diun.enable` | | Set to true to enable image analysis of this task | +| `diun.regopt` | | [Registry options](../config/regopts.md) name to use | +| `diun.watch_repo` | `false` | Watch all tags of this task image ([be careful](../faq.md#docker-hub-rate-limits) with this setting) | +| `diun.notify_on` | `new;update` | Semicolon separated list of status to be notified: `new`, `update`. | +| `diun.sort_tags` | `reverse` | [Sort tags method](../faq.md#tags-sorting-when-using-watch_repo) if `diun.watch_repo` enabled. One of `default`, `reverse`, `semver`, `lexicographical` | +| `diun.max_tags` | `0` | Maximum number of tags to watch if `diun.watch_repo` enabled. `0` means all of them | +| `diun.include_tags` | | Semicolon separated list of regular expressions to include tags. Can be useful if you enable `diun.watch_repo` | +| `diun.exclude_tags` | | Semicolon separated list of regular expressions to exclude tags. Can be useful if you enable `diun.watch_repo` | +| `diun.hub_link` | _automatic_ | Set registry hub link for this image | +| `diun.platform` | _automatic_ | Platform to use (e.g. `linux/amd64`) | +| `diun.metadata.*` | See [below](#default-metadata) | Additional metadata that can be used in [notification template](../faq.md#notification-template) (e.g. `diun.metadata.foo=bar`) | + +## Default metadata + +| Key | Description | +|--------------------------------|-----------------| +| `diun.metadata.job_id` | Job ID | +| `diun.metadata.job_name` | Job name | +| `diun.metadata.job_status` | Job status | +| `diun.metadata.job_namespace` | Job namespace | +| `diun.metadata.taskgroup_name` | Task group name | +| `diun.metadata.task_name` | Task name | +| `diun.metadata.task_driver` | Task driver | +| `diun.metadata.task_user` | Task user | diff --git a/docs/providers/swarm.md b/docs/providers/swarm.md index e945e3d8..252852b9 100644 --- a/docs/providers/swarm.md +++ b/docs/providers/swarm.md @@ -175,15 +175,25 @@ Enable watch by default. If false, services that don't have `diun.enable=true` l You can configure more finely the way to analyze the image of your service through Docker labels: -| Name | Default | Description | -|---------------------|--------------|---------------------------------------------------------------------------------------------------------------------------------------------------------| -| `diun.enable` | | Set to true to enable image analysis of this service | -| `diun.regopt` | | [Registry options](../config/regopts.md) name to use | -| `diun.watch_repo` | `false` | Watch all tags of this service image ([be careful](../faq.md#docker-hub-rate-limits) with this setting) | -| `diun.notify_on` | `new;update` | Semicolon separated list of status to be notified: `new`, `update`. | -| `diun.sort_tags` | `reverse` | [Sort tags method](../faq.md#tags-sorting-when-using-watch_repo) if `diun.watch_repo` enabled. One of `default`, `reverse`, `semver`, `lexicographical` | -| `diun.max_tags` | `0` | Maximum number of tags to watch if `diun.watch_repo` enabled. `0` means all of them | -| `diun.include_tags` | | Semicolon separated list of regular expressions to include tags. Can be useful if you enable `diun.watch_repo` | -| `diun.exclude_tags` | | Semicolon separated list of regular expressions to exclude tags. Can be useful if you enable `diun.watch_repo` | -| `diun.hub_link` | _automatic_ | Set registry hub link for this image | -| `diun.platform` | _automatic_ | Platform to use (e.g. `linux/amd64`) | +| Name | Default | Description | +|---------------------|--------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------| +| `diun.enable` | | Set to true to enable image analysis of this service | +| `diun.regopt` | | [Registry options](../config/regopts.md) name to use | +| `diun.watch_repo` | `false` | Watch all tags of this service image ([be careful](../faq.md#docker-hub-rate-limits) with this setting) | +| `diun.notify_on` | `new;update` | Semicolon separated list of status to be notified: `new`, `update`. | +| `diun.sort_tags` | `reverse` | [Sort tags method](../faq.md#tags-sorting-when-using-watch_repo) if `diun.watch_repo` enabled. One of `default`, `reverse`, `semver`, `lexicographical` | +| `diun.max_tags` | `0` | Maximum number of tags to watch if `diun.watch_repo` enabled. `0` means all of them | +| `diun.include_tags` | | Semicolon separated list of regular expressions to include tags. Can be useful if you enable `diun.watch_repo` | +| `diun.exclude_tags` | | Semicolon separated list of regular expressions to exclude tags. Can be useful if you enable `diun.watch_repo` | +| `diun.hub_link` | _automatic_ | Set registry hub link for this image | +| `diun.platform` | _automatic_ | Platform to use (e.g. `linux/amd64`) | +| `diun.metadata.*` | See [below](#default-metadata) | Additional metadata that can be used in [notification template](../faq.md#notification-template) (e.g. `diun.metadata.foo=bar`) | + +## Default metadata + +| Key | Description | +|-------------------------------|-----------------------| +| `diun.metadata.svc_id` | Service ID | +| `diun.metadata.svc_createdat` | Service creation date | +| `diun.metadata.svc_updatedat` | Service update date | +| `diun.metadata.ctn_name` | Container name | diff --git a/internal/app/diun.go b/internal/app/diun.go index 850787d1..f17cc824 100644 --- a/internal/app/diun.go +++ b/internal/app/diun.go @@ -151,37 +151,11 @@ func (di *Diun) Run() { }, ants.WithLogger(new(logging.AntsLogger))) defer di.pool.Release() - // Docker provider - for _, job := range dockerPrd.New(di.cfg.Providers.Docker).ListJob() { + for _, job := range di.providersJobs() { di.createJob(job) } - - // Swarm provider - for _, job := range swarmPrd.New(di.cfg.Providers.Swarm).ListJob() { - di.createJob(job) - } - - // Kubernetes provider - for _, job := range kubernetesPrd.New(di.cfg.Providers.Kubernetes).ListJob() { - di.createJob(job) - } - - // File provider - for _, job := range filePrd.New(di.cfg.Providers.File).ListJob() { - di.createJob(job) - } - - // Dockerfile provider - for _, job := range dockerfilePrd.New(di.cfg.Providers.Dockerfile).ListJob() { - di.createJob(job) - } - - // Nomad provider - for _, job := range nomadPrd.New(di.cfg.Providers.Nomad).ListJob() { - di.createJob(job) - } - di.wg.Wait() + log.Info(). Int("added", entries.CountNew). Int("updated", entries.CountUpdate). @@ -202,3 +176,26 @@ func (di *Diun) Close() { log.Warn().Err(err).Msg("Cannot close database") } } + +func (di *Diun) providersJobs() []model.Job { + var jobs []model.Job + for _, job := range dockerPrd.New(di.cfg.Providers.Docker).ListJob() { + jobs = append(jobs, job) + } + for _, job := range swarmPrd.New(di.cfg.Providers.Swarm).ListJob() { + jobs = append(jobs, job) + } + for _, job := range kubernetesPrd.New(di.cfg.Providers.Kubernetes).ListJob() { + jobs = append(jobs, job) + } + for _, job := range filePrd.New(di.cfg.Providers.File).ListJob() { + jobs = append(jobs, job) + } + for _, job := range dockerfilePrd.New(di.cfg.Providers.Dockerfile).ListJob() { + jobs = append(jobs, job) + } + for _, job := range nomadPrd.New(di.cfg.Providers.Nomad).ListJob() { + jobs = append(jobs, job) + } + return jobs +} diff --git a/internal/app/job.go b/internal/app/job.go index 54bb84ff..0f1f7c11 100644 --- a/internal/app/job.go +++ b/internal/app/job.go @@ -169,6 +169,7 @@ func (di *Diun) runJob(job model.Job) (entry model.NotifEntry) { Status: model.ImageStatusError, Provider: job.Provider, Image: job.RegImage, + Metadata: job.Image.Metadata, } sublog := log.With(). diff --git a/internal/model/image.go b/internal/model/image.go index 7863a2d3..59d44aac 100644 --- a/internal/model/image.go +++ b/internal/model/image.go @@ -4,17 +4,18 @@ import "github.com/crazy-max/diun/v4/pkg/registry" // Image holds image configuration type Image struct { - Name string `yaml:"name,omitempty" json:",omitempty"` - Platform ImagePlatform `yaml:"platform,omitempty" json:",omitempty"` - RegOpt string `yaml:"regopt,omitempty" json:",omitempty"` - WatchRepo bool `yaml:"watch_repo,omitempty" json:",omitempty"` - NotifyOn []NotifyOn `yaml:"notify_on,omitempty" json:",omitempty"` - MaxTags int `yaml:"max_tags,omitempty" json:",omitempty"` - SortTags registry.SortTag `yaml:"sort_tags,omitempty" json:",omitempty"` - IncludeTags []string `yaml:"include_tags,omitempty" json:",omitempty"` - ExcludeTags []string `yaml:"exclude_tags,omitempty" json:",omitempty"` - HubTpl string `yaml:"hub_tpl,omitempty" json:",omitempty"` - HubLink string `yaml:"hub_link,omitempty" json:",omitempty"` + Name string `yaml:"name,omitempty" json:",omitempty"` + Platform ImagePlatform `yaml:"platform,omitempty" json:",omitempty"` + RegOpt string `yaml:"regopt,omitempty" json:",omitempty"` + WatchRepo bool `yaml:"watch_repo,omitempty" json:",omitempty"` + NotifyOn []NotifyOn `yaml:"notify_on,omitempty" json:",omitempty"` + MaxTags int `yaml:"max_tags,omitempty" json:",omitempty"` + SortTags registry.SortTag `yaml:"sort_tags,omitempty" json:",omitempty"` + IncludeTags []string `yaml:"include_tags,omitempty" json:",omitempty"` + ExcludeTags []string `yaml:"exclude_tags,omitempty" json:",omitempty"` + HubTpl string `yaml:"hub_tpl,omitempty" json:",omitempty"` + HubLink string `yaml:"hub_link,omitempty" json:",omitempty"` + Metadata map[string]string `yaml:"metadata,omitempty" json:",omitempty"` } // ImagePlatform holds image platform configuration diff --git a/internal/model/notif.go b/internal/model/notif.go index 85312b67..cca3d68e 100644 --- a/internal/model/notif.go +++ b/internal/model/notif.go @@ -27,6 +27,7 @@ type NotifEntry struct { Provider string `json:"provider,omitempty"` Image registry.Image `json:"image,omitempty"` Manifest registry.Manifest `json:"manifest,omitempty"` + Metadata map[string]string `json:"metadata,omitempty"` } // Notif holds data necessary for notification configuration diff --git a/internal/msg/client.go b/internal/msg/client.go index 0adb77e0..9f82819a 100644 --- a/internal/msg/client.go +++ b/internal/msg/client.go @@ -91,16 +91,17 @@ func (c *Client) RenderHTML() (title []byte, body []byte, err error) { // RenderJSON returns a notification message as JSON func (c *Client) RenderJSON() ([]byte, error) { return json.Marshal(struct { - Version string `json:"diun_version"` - Hostname string `json:"hostname"` - Status string `json:"status"` - Provider string `json:"provider"` - Image string `json:"image"` - HubLink string `json:"hub_link"` - MIMEType string `json:"mime_type"` - Digest digest.Digest `json:"digest"` - Created *time.Time `json:"created"` - Platform string `json:"platform"` + Version string `json:"diun_version"` + Hostname string `json:"hostname"` + Status string `json:"status"` + Provider string `json:"provider"` + Image string `json:"image"` + HubLink string `json:"hub_link"` + MIMEType string `json:"mime_type"` + Digest digest.Digest `json:"digest"` + Created *time.Time `json:"created"` + Platform string `json:"platform"` + Metadata map[string]string `json:"metadata"` }{ Version: c.opts.Meta.Version, Hostname: c.opts.Meta.Hostname, @@ -112,12 +113,17 @@ func (c *Client) RenderJSON() ([]byte, error) { Digest: c.opts.Entry.Manifest.Digest, Created: c.opts.Entry.Manifest.Created, Platform: c.opts.Entry.Manifest.Platform, + Metadata: c.opts.Entry.Metadata, }) } // RenderEnv returns a notification message as environment variables func (c *Client) RenderEnv() []string { - return []string{ + var metadataEnvs []string + for k, v := range c.opts.Entry.Metadata { + metadataEnvs = append(metadataEnvs, fmt.Sprintf("DIUN_ENTRY_METADATA_%s=%s", strings.ToUpper(k), v)) + } + return append([]string{ fmt.Sprintf("DIUN_VERSION=%s", c.opts.Meta.Version), fmt.Sprintf("DIUN_HOSTNAME=%s", c.opts.Meta.Hostname), fmt.Sprintf("DIUN_ENTRY_STATUS=%s", string(c.opts.Entry.Status)), @@ -128,5 +134,5 @@ func (c *Client) RenderEnv() []string { fmt.Sprintf("DIUN_ENTRY_DIGEST=%s", c.opts.Entry.Manifest.Digest), fmt.Sprintf("DIUN_ENTRY_CREATED=%s", c.opts.Entry.Manifest.Created), fmt.Sprintf("DIUN_ENTRY_PLATFORM=%s", c.opts.Entry.Manifest.Platform), - } + }, metadataEnvs...) } diff --git a/internal/provider/common.go b/internal/provider/common.go index fdfeab3c..cdcc5131 100644 --- a/internal/provider/common.go +++ b/internal/provider/common.go @@ -2,21 +2,29 @@ package provider import ( "fmt" + "regexp" "strconv" "strings" "github.com/containerd/containerd/platforms" "github.com/crazy-max/diun/v4/internal/model" "github.com/crazy-max/diun/v4/pkg/registry" + "github.com/pkg/errors" +) + +var ( + metadataKeyChars = `a-zA-Z0-9_` + metadataKeyRegexp = regexp.MustCompile(`^[` + metadataKeyChars + `]+$`) ) // ValidateImage returns a standard image through Docker labels -func ValidateImage(image string, labels map[string]string, watchByDef bool) (img model.Image, err error) { +func ValidateImage(image string, metadata, labels map[string]string, watchByDef bool) (img model.Image, err error) { if i := strings.Index(image, "@sha256:"); i > 0 { image = image[:i] } img = model.Image{ Name: image, + Metadata: metadata, NotifyOn: model.NotifyOnDefaults, SortTags: registry.SortTagReverse, } @@ -34,14 +42,14 @@ func ValidateImage(image string, labels map[string]string, watchByDef bool) (img } for key, value := range labels { - switch key { - case "diun.regopt": + switch { + case key == "diun.regopt": img.RegOpt = value - case "diun.watch_repo": + case key == "diun.watch_repo": if img.WatchRepo, err = strconv.ParseBool(value); err != nil { return img, fmt.Errorf("cannot parse %s value of label %s", value, key) } - case "diun.notify_on": + case key == "diun.notify_on": if len(value) == 0 { break } @@ -53,7 +61,7 @@ func ValidateImage(image string, labels map[string]string, watchByDef bool) (img } img.NotifyOn = append(img.NotifyOn, notifyOn) } - case "diun.sort_tags": + case key == "diun.sort_tags": if value == "" { break } @@ -62,19 +70,19 @@ func ValidateImage(image string, labels map[string]string, watchByDef bool) (img return img, fmt.Errorf("unknown sort tags type %q", value) } img.SortTags = sortTags - case "diun.max_tags": + case key == "diun.max_tags": if img.MaxTags, err = strconv.Atoi(value); err != nil { return img, fmt.Errorf("cannot parse %s value of label %s", value, key) } - case "diun.include_tags": + case key == "diun.include_tags": img.IncludeTags = strings.Split(value, ";") - case "diun.exclude_tags": + case key == "diun.exclude_tags": img.ExcludeTags = strings.Split(value, ";") - case "diun.hub_tpl": + case key == "diun.hub_tpl": img.HubTpl = value - case "diun.hub_link": + case key == "diun.hub_link": img.HubLink = value - case "diun.platform": + case key == "diun.platform": platform, err := platforms.Parse(value) if err != nil { return img, fmt.Errorf("cannot parse %s platform of label %s", value, key) @@ -84,8 +92,27 @@ func ValidateImage(image string, labels map[string]string, watchByDef bool) (img Arch: platform.Architecture, Variant: platform.Variant, } + case strings.HasPrefix(key, "diun.metadata."): + mkey := strings.TrimPrefix(key, "diun.metadata.") + if len(mkey) == 0 || len(value) == 0 { + break + } + if err := validateMetadataKey(mkey); err != nil { + return img, errors.Wrapf(err, "invalid metadata key %q", mkey) + } + if img.Metadata == nil { + img.Metadata = map[string]string{} + } + img.Metadata[mkey] = value } } return img, nil } + +func validateMetadataKey(key string) error { + if !metadataKeyRegexp.MatchString(key) { + return errors.Errorf("only %q are allowed", metadataKeyChars) + } + return nil +} diff --git a/internal/provider/docker/container.go b/internal/provider/docker/container.go index a2bbcea1..54e75b69 100644 --- a/internal/provider/docker/container.go +++ b/internal/provider/docker/container.go @@ -1,12 +1,17 @@ package docker import ( + "fmt" "reflect" + "strings" + "time" "github.com/crazy-max/diun/v4/internal/model" "github.com/crazy-max/diun/v4/internal/provider" "github.com/crazy-max/diun/v4/pkg/docker" + "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/filters" + "github.com/docker/go-units" ) func (c *Client) listContainerImage() []model.Image { @@ -86,7 +91,7 @@ func (c *Client) listContainerImage() []model.Image { Str("ctn_image", imageName). Interface("ctn_labels", ctn.Labels). Msg("Validate image") - image, err := provider.ValidateImage(imageName, ctn.Labels, *c.config.WatchByDefault) + image, err := provider.ValidateImage(imageName, metadata(ctn), ctn.Labels, *c.config.WatchByDefault) if err != nil { c.logger.Error().Err(err). @@ -109,3 +114,33 @@ func (c *Client) listContainerImage() []model.Image { return list } + +func metadata(ctn types.Container) map[string]string { + return map[string]string{ + "ctn_id": ctn.ID, + "ctn_names": formatNames(ctn.Names), + "ctn_command": ctn.Command, + "ctn_createdat": time.Unix(ctn.Created, 0).String(), + "ctn_state": ctn.State, + "ctn_status": ctn.Status, + "ctn_size": formatSize(ctn.SizeRw, ctn.SizeRootFs), + } +} + +func formatNames(names []string) string { + namesStripPrefix := make([]string, len(names)) + for i, s := range names { + namesStripPrefix[i] = s[1:] + } + return strings.Join(namesStripPrefix, ",") +} + +func formatSize(sizeRw, sizeRootFs int64) string { + srw := units.HumanSizeWithPrecision(float64(sizeRw), 3) + sv := units.HumanSizeWithPrecision(float64(sizeRootFs), 3) + sf := srw + if sizeRootFs > 0 { + sf = fmt.Sprintf("%s (virtual %s)", srw, sv) + } + return sf +} diff --git a/internal/provider/dockerfile/image.go b/internal/provider/dockerfile/image.go index 3745e951..3b7d77fa 100644 --- a/internal/provider/dockerfile/image.go +++ b/internal/provider/dockerfile/image.go @@ -32,7 +32,7 @@ func (c *Client) listExtImage() (list []model.Image) { Interface("dfile_comments", fromImage.Comments). Int("dfile_line", fromImage.Line). Msg("Validate image") - image, err := provider.ValidateImage(fromImage.Name, c.extractLabels(fromImage.Comments), true) + image, err := provider.ValidateImage(fromImage.Name, nil, c.extractLabels(fromImage.Comments), true) if err != nil { c.logger.Error().Err(err). Str("dfile_image", fromImage.Name). diff --git a/internal/provider/kubernetes/pod.go b/internal/provider/kubernetes/pod.go index f71df1b7..7b12ce9c 100644 --- a/internal/provider/kubernetes/pod.go +++ b/internal/provider/kubernetes/pod.go @@ -2,10 +2,12 @@ package kubernetes import ( "reflect" + "strings" "github.com/crazy-max/diun/v4/internal/model" "github.com/crazy-max/diun/v4/internal/provider" "github.com/crazy-max/diun/v4/pkg/k8s" + v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -39,7 +41,7 @@ func (c *Client) listPodImage() []model.Image { Str("ctn_image", ctn.Image). Msg("Validate image") - image, err := provider.ValidateImage(ctn.Image, pod.Annotations, *c.config.WatchByDefault) + image, err := provider.ValidateImage(ctn.Image, metadata(pod, ctn), pod.Annotations, *c.config.WatchByDefault) if err != nil { c.logger.Error().Err(err). Str("pod_name", pod.Name). @@ -64,3 +66,14 @@ func (c *Client) listPodImage() []model.Image { return list } + +func metadata(pod v1.Pod, ctn v1.Container) map[string]string { + return map[string]string{ + "pod_name": pod.Name, + "pod_status": pod.Status.String(), + "pod_namespace": pod.Namespace, + "pod_createdat": pod.CreationTimestamp.String(), + "ctn_name": ctn.Name, + "ctn_command": strings.Join(ctn.Command, " "), + } +} diff --git a/internal/provider/nomad/task.go b/internal/provider/nomad/task.go index 4f25925d..22367ee3 100644 --- a/internal/provider/nomad/task.go +++ b/internal/provider/nomad/task.go @@ -96,7 +96,7 @@ func (c *Client) listTaskImages() []model.Image { // Finally, merge task meta values labels = updateMap(labels, task.Meta) - image, err := provider.ValidateImage(imageName, labels, *c.config.WatchByDefault) + image, err := provider.ValidateImage(imageName, metadata(job, taskGroup, task), labels, *c.config.WatchByDefault) if err != nil { c.logger.Error(). Err(err). @@ -124,3 +124,16 @@ func (c *Client) listTaskImages() []model.Image { return list } + +func metadata(job *nomad.JobListStub, taskGroup *nomad.TaskGroup, task *nomad.Task) map[string]string { + return map[string]string{ + "job_id": job.ID, + "job_name": job.Name, + "job_status": job.Status, + "job_namespace": job.Namespace, + "taskgroup_name": *taskGroup.Name, + "task_name": task.Name, + "task_driver": task.Driver, + "task_user": task.User, + } +} diff --git a/internal/provider/swarm/service.go b/internal/provider/swarm/service.go index 078c11de..d10dd95c 100644 --- a/internal/provider/swarm/service.go +++ b/internal/provider/swarm/service.go @@ -7,6 +7,7 @@ import ( "github.com/crazy-max/diun/v4/internal/provider" "github.com/crazy-max/diun/v4/pkg/docker" "github.com/docker/docker/api/types/filters" + "github.com/docker/docker/api/types/swarm" ) func (c *Client) listServiceImage() []model.Image { @@ -36,7 +37,7 @@ func (c *Client) listServiceImage() []model.Image { Str("ctn_image", svc.Spec.TaskTemplate.ContainerSpec.Image). Msg("Validate image") - image, err := provider.ValidateImage(svc.Spec.TaskTemplate.ContainerSpec.Image, svc.Spec.Labels, *c.config.WatchByDefault) + image, err := provider.ValidateImage(svc.Spec.TaskTemplate.ContainerSpec.Image, metadata(svc), svc.Spec.Labels, *c.config.WatchByDefault) if err != nil { c.logger.Error().Err(err). Str("svc_name", svc.Spec.Name). @@ -58,3 +59,12 @@ func (c *Client) listServiceImage() []model.Image { return list } + +func metadata(svc swarm.Service) map[string]string { + return map[string]string{ + "svc_id": svc.ID, + "svc_createdat": svc.CreatedAt.String(), + "svc_updatedat": svc.UpdatedAt.String(), + "ctn_name": svc.Spec.Name, + } +} diff --git a/test/dockerfile1/mount/Dockerfile b/test/dockerfile1/mount/Dockerfile index 4967ad62..f13a3de2 100644 --- a/test/dockerfile1/mount/Dockerfile +++ b/test/dockerfile1/mount/Dockerfile @@ -11,6 +11,7 @@ COPY --from=crazymax/yasu / / # diun.watch_repo=true # diun.include_tags=^\d+\.\d+\.\d+$ # diun.platform=linux/amd64 +# diun.metadata.foo=bar RUN --mount=type=bind,target=.,rw \ --mount=type=bind,from=docker/buildx-bin:0.6.0,source=/buildx,target=/usr/bin/buildx \ yasu --version