mirror of
https://github.com/crazy-max/diun.git
synced 2025-12-21 13:23:09 +01:00
Dockerfile provider (#329)
Co-authored-by: CrazyMax <crazy-max@users.noreply.github.com>
This commit is contained in:
@@ -216,6 +216,7 @@ Can be transposed to:
|
|||||||
* [regopts](regopts.md)
|
* [regopts](regopts.md)
|
||||||
* providers
|
* providers
|
||||||
* [docker](../providers/docker.md)
|
* [docker](../providers/docker.md)
|
||||||
* [file](../providers/file.md)
|
|
||||||
* [kubernetes](../providers/kubernetes.md)
|
* [kubernetes](../providers/kubernetes.md)
|
||||||
* [swarm](../providers/swarm.md)
|
* [swarm](../providers/swarm.md)
|
||||||
|
* [dockerfile](../providers/dockerfile.md)
|
||||||
|
* [file](../providers/file.md)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
# Providers configuration
|
# Providers configuration
|
||||||
|
|
||||||
* [`docker`](../providers/docker.md)
|
* [`docker`](../providers/docker.md)
|
||||||
* [`file`](../providers/file.md)
|
|
||||||
* [`kubernetes`](../providers/kubernetes.md)
|
* [`kubernetes`](../providers/kubernetes.md)
|
||||||
* [`swarm`](../providers/swarm.md)
|
* [`swarm`](../providers/swarm.md)
|
||||||
|
* [`dockerfile`](../providers/dockerfile.md)
|
||||||
|
* [`file`](../providers/file.md)
|
||||||
|
|||||||
@@ -34,7 +34,8 @@ be selected if not referenced as a `regopt` name.
|
|||||||
|
|
||||||
Unique name for registry options. This name can be used through `diun.regopt`
|
Unique name for registry options. This name can be used through `diun.regopt`
|
||||||
[Docker](../providers/docker.md#docker-labels) / [Swarm](../providers/swarm.md#docker-labels) label
|
[Docker](../providers/docker.md#docker-labels) / [Swarm](../providers/swarm.md#docker-labels) label
|
||||||
or [Kubernetes annotation](../providers/kubernetes.md#kubernetes-annotations) and also as `regopt` for the [file provider](../providers/file.md).
|
or [Kubernetes annotation](../providers/kubernetes.md#kubernetes-annotations) and also as `regopt` for the
|
||||||
|
[Dockerfile](../providers/dockerfile.md) and [File](../providers/file.md) providers.
|
||||||
|
|
||||||
!!! warning
|
!!! warning
|
||||||
* **Required**
|
* **Required**
|
||||||
|
|||||||
@@ -31,8 +31,8 @@ This support includes Linux, macOS, and Windows, on architectures like amd64, i3
|
|||||||
* Internal cron implementation through go routines
|
* Internal cron implementation through go routines
|
||||||
* Worker pool to parallelize analyses
|
* Worker pool to parallelize analyses
|
||||||
* Allow overriding image os and architecture
|
* Allow overriding image os and architecture
|
||||||
* [Docker](providers/docker.md), [Swarm](providers/swarm.md), [Kubernetes](providers/kubernetes.md)
|
* [Docker](providers/docker.md), [Swarm](providers/swarm.md), [Kubernetes](providers/kubernetes.md),
|
||||||
and [File](providers/file.md) providers available
|
[Dockerfile](providers/dockerfile.md) and [File](providers/file.md) providers available
|
||||||
* Get notified through Gotify, Mail, Slack, Telegram and [more](config/index.md#reference)
|
* Get notified through Gotify, Mail, Slack, Telegram and [more](config/index.md#reference)
|
||||||
* [Healthchecks support](config/watch.md#healthchecks) to monitor Diun watcher
|
* [Healthchecks support](config/watch.md#healthchecks) to monitor Diun watcher
|
||||||
* Enhanced logging
|
* Enhanced logging
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ Allow to send notifications to your Discord channel.
|
|||||||
|
|
||||||
!!! abstract "Environment variables"
|
!!! abstract "Environment variables"
|
||||||
* `DIUN_NOTIF_DISCORD_WEBHOOKURL`
|
* `DIUN_NOTIF_DISCORD_WEBHOOKURL`
|
||||||
* `DIUN_NOTIF_DISCORD_MENTIONS`
|
* `DIUN_NOTIF_DISCORD_MENTIONS` (comma separated)
|
||||||
* `DIUN_NOTIF_DISCORD_TIMEOUT`
|
* `DIUN_NOTIF_DISCORD_TIMEOUT`
|
||||||
|
|
||||||
## Sample
|
## Sample
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ DIUN_ENTRY_PLATFORM=linux/amd64
|
|||||||
|
|
||||||
!!! abstract "Environment variables"
|
!!! abstract "Environment variables"
|
||||||
* `DIUN_NOTIF_SCRIPT_CMD`
|
* `DIUN_NOTIF_SCRIPT_CMD`
|
||||||
* `DIUN_NOTIF_SCRIPT_ARGS`
|
* `DIUN_NOTIF_SCRIPT_ARGS` (comma separated)
|
||||||
* `DIUN_NOTIF_SCRIPT_DIR`
|
* `DIUN_NOTIF_SCRIPT_DIR`
|
||||||
|
|
||||||
[^1]: Value required
|
[^1]: Value required
|
||||||
|
|||||||
@@ -177,3 +177,4 @@ You can configure more finely the way to analyze the image of your container thr
|
|||||||
| `diun.max_tags` | `0` | Maximum number of tags to watch if `diun.watch_repo` enabled. `0` means all of them |
|
| `diun.max_tags` | `0` | Maximum number of tags to watch if `diun.watch_repo` enabled. `0` means all of them |
|
||||||
| `diun.include_tags` | | Semi-colon separated list of regular expressions to include tags. Can be useful if you enable `diun.watch_repo` |
|
| `diun.include_tags` | | Semi-colon separated list of regular expressions to include tags. Can be useful if you enable `diun.watch_repo` |
|
||||||
| `diun.exclude_tags` | | Semi-colon separated list of regular expressions to exclude tags. Can be useful if you enable `diun.watch_repo` |
|
| `diun.exclude_tags` | | Semi-colon separated list of regular expressions to exclude tags. Can be useful if you enable `diun.watch_repo` |
|
||||||
|
| `diun.platform` | _automatic_ | Platform to use (e.g. `linux/amd64`) |
|
||||||
|
|||||||
116
docs/providers/dockerfile.md
Normal file
116
docs/providers/dockerfile.md
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
# Dockerfile provider
|
||||||
|
|
||||||
|
## About
|
||||||
|
|
||||||
|
The Dockerfile provider allows to parse a [Dockerfile](https://docs.docker.com/engine/reference/builder/) and extract
|
||||||
|
images for the following instructions:
|
||||||
|
|
||||||
|
* [`FROM <image>`](https://docs.docker.com/engine/reference/builder/#from)
|
||||||
|
* [`COPY --from=<image>`](https://docs.docker.com/engine/reference/builder/#copy)
|
||||||
|
* [`RUN --mount=type=bind,from=<image>`](https://github.com/moby/buildkit/blob/master/frontend/dockerfile/docs/syntax.md#run---mounttypebind-the-default-mount-type)
|
||||||
|
|
||||||
|
## Quick start
|
||||||
|
|
||||||
|
First you have to register the dockerfile provider:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
db:
|
||||||
|
path: diun.db
|
||||||
|
|
||||||
|
watch:
|
||||||
|
workers: 20
|
||||||
|
schedule: "0 */6 * * *"
|
||||||
|
|
||||||
|
regopts:
|
||||||
|
- name: "docker.io"
|
||||||
|
selector: image
|
||||||
|
username: foo
|
||||||
|
password: bar
|
||||||
|
|
||||||
|
providers:
|
||||||
|
dockerfile:
|
||||||
|
patterns:
|
||||||
|
- ./Dockerfile
|
||||||
|
```
|
||||||
|
|
||||||
|
```Dockerfile
|
||||||
|
# syntax=docker/dockerfile:1.2
|
||||||
|
|
||||||
|
# diun.platform=linux/amd64
|
||||||
|
FROM alpine:latest
|
||||||
|
|
||||||
|
# diun.watch_repo=true
|
||||||
|
# diun.max_tags=10
|
||||||
|
# diun.platform=linux/amd64
|
||||||
|
COPY --from=crazymax/yasu / /
|
||||||
|
|
||||||
|
# diun.watch_repo=true
|
||||||
|
# diun.include_tags=^\d+\.\d+\.\d+$
|
||||||
|
# diun.platform=linux/amd64
|
||||||
|
RUN --mount=type=bind,target=.,rw \
|
||||||
|
--mount=type=bind,from=crazymax/docker:20.10.6,source=/usr/local/bin/docker,target=/usr/bin/docker \
|
||||||
|
yasu --version
|
||||||
|
```
|
||||||
|
|
||||||
|
With this Dockerfile the following images will be analyzed:
|
||||||
|
|
||||||
|
* `alpine:latest` tag (`linux/amd64` platform)
|
||||||
|
* Most recent 10 tags for `crazymax/yasu` image (`linux/amd64` platform)
|
||||||
|
* `crazymax/docker` tags matching `^\d+\.\d+\.\d+$` (`linux/amd64` platform)
|
||||||
|
|
||||||
|
Now let's start Diun:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ diun --config /etc/diun/diun.yml
|
||||||
|
Thu, 29 Apr 2021 14:41:55 CEST INF Starting Diun version=4.16.0
|
||||||
|
Thu, 29 Apr 2021 14:41:55 CEST INF Configuration loaded from file: /etc/diun/diun.yml
|
||||||
|
Thu, 29 Apr 2021 14:41:55 CEST WRN No notifier available
|
||||||
|
Thu, 29 Apr 2021 14:41:55 CEST INF Cron triggered
|
||||||
|
Thu, 29 Apr 2021 14:41:55 CEST INF Found 3 image(s) to analyze provider=dockerfile
|
||||||
|
Thu, 29 Apr 2021 14:41:59 CEST INF New image found image=docker.io/library/alpine:latest provider=dockerfile
|
||||||
|
Thu, 29 Apr 2021 14:41:59 CEST INF New image found image=docker.io/crazymax/yasu:latest provider=dockerfile
|
||||||
|
Thu, 29 Apr 2021 14:42:00 CEST INF New image found image=docker.io/crazymax/yasu:1.14.1 provider=dockerfile
|
||||||
|
Thu, 29 Apr 2021 14:42:00 CEST INF New image found image=docker.io/crazymax/docker:20.10.6 provider=dockerfile
|
||||||
|
Thu, 29 Apr 2021 14:42:00 CEST INF New image found image=docker.io/crazymax/yasu:edge provider=dockerfile
|
||||||
|
Thu, 29 Apr 2021 14:42:01 CEST INF New image found image=docker.io/crazymax/yasu:1.14.0 provider=dockerfile
|
||||||
|
Thu, 29 Apr 2021 14:42:02 CEST INF New image found image=docker.io/crazymax/docker:20.10.5 provider=dockerfile
|
||||||
|
Thu, 29 Apr 2021 14:42:02 CEST INF New image found image=docker.io/crazymax/docker:20.10.4 provider=dockerfile
|
||||||
|
Thu, 29 Apr 2021 14:42:02 CEST INF New image found image=docker.io/crazymax/docker:20.10.3 provider=dockerfile
|
||||||
|
Thu, 29 Apr 2021 14:42:02 CEST INF New image found image=docker.io/crazymax/docker:20.10.2 provider=dockerfile
|
||||||
|
Thu, 29 Apr 2021 14:42:03 CEST INF New image found image=docker.io/crazymax/docker:20.10.1 provider=dockerfile
|
||||||
|
Thu, 29 Apr 2021 14:42:03 CEST INF New image found image=docker.io/crazymax/docker:19.03.15 provider=dockerfile
|
||||||
|
Thu, 29 Apr 2021 14:42:04 CEST INF New image found image=docker.io/crazymax/docker:19.03.14 provider=dockerfile
|
||||||
|
Thu, 29 Apr 2021 14:42:04 CEST INF Jobs completed added=13 failed=0 skipped=0 unchanged=0 updated=0
|
||||||
|
Thu, 29 Apr 2021 14:42:05 CEST INF Cron initialized with schedule 0 */6 * * *
|
||||||
|
Thu, 29 Apr 2021 14:42:05 CEST INF Next run in 3 hours (2021-04-29 18:00:00 +0200 CEST)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
### `patterns`
|
||||||
|
|
||||||
|
List of path patterns with [matching and globbing supporting patterns](https://github.com/bmatcuk/doublestar/tree/v3).
|
||||||
|
|
||||||
|
!!! example "File"
|
||||||
|
```yaml
|
||||||
|
providers:
|
||||||
|
dockerfile:
|
||||||
|
patterns:
|
||||||
|
- "**/Dockerfile*"
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! abstract "Environment variables"
|
||||||
|
* `DIUN_PROVIDERS_DOCKERFILE_PATTERNS` (comma separated)
|
||||||
|
|
||||||
|
## Annotations
|
||||||
|
|
||||||
|
The following annotations can be added as comments before the target instruction to customize the image analysis:
|
||||||
|
|
||||||
|
| Name | Default | Description |
|
||||||
|
|-------------------------------|---------------|---------------|
|
||||||
|
| `diun.regopt` | | [Registry options](../config/regopts.md) name to use |
|
||||||
|
| `diun.watch_repo` | `false` | Watch all tags of this image |
|
||||||
|
| `diun.max_tags` | `0` | Maximum number of tags to watch if `watch_repo` enabled. `0` means all of them |
|
||||||
|
| `diun.include_tags` | | Semi-colon separated list of regular expressions to include tags. Can be useful if you enable `diun.watch_repo` |
|
||||||
|
| `diun.exclude_tags` | | Semi-colon separated list of regular expressions to exclude tags. Can be useful if you enable `diun.watch_repo` |
|
||||||
|
| `diun.platform` | _automatic_ | Platform to use (e.g. `linux/amd64`) |
|
||||||
@@ -60,12 +60,12 @@ providers:
|
|||||||
|
|
||||||
# Watch portainer/portainer image on docker.io (DockerHub) and assume latest tag
|
# Watch portainer/portainer image on docker.io (DockerHub) and assume latest tag
|
||||||
# with registry options named 'docker.io' (image selector).
|
# with registry options named 'docker.io' (image selector).
|
||||||
# Only watch latest 10 tags and include tags matching regexp ^(0|[1-9]\d*)\..*
|
# Only watch latest 10 tags and include tags matching regexp ^\d+\.\d+\..*
|
||||||
- name: docker.io/portainer/portainer
|
- name: docker.io/portainer/portainer
|
||||||
watch_repo: true
|
watch_repo: true
|
||||||
max_tags: 10
|
max_tags: 10
|
||||||
include_tags:
|
include_tags:
|
||||||
- ^(0|[1-9]\d*)\..*
|
- ^\d+\.\d+\..*
|
||||||
|
|
||||||
# Watch alpine image (library) and assume docker.io registry and latest tag
|
# Watch alpine image (library) and assume docker.io registry and latest tag
|
||||||
# with registry options named 'docker.io' (image selector).
|
# with registry options named 'docker.io' (image selector).
|
||||||
@@ -178,6 +178,6 @@ The configuration file(s) defines a slice of images to analyze with the followin
|
|||||||
| `max_tags` | `0` | Maximum number of tags to watch if `watch_repo` enabled. `0` means all of them |
|
| `max_tags` | `0` | Maximum number of tags to watch if `watch_repo` enabled. `0` means all of them |
|
||||||
| `include_tags` | | List of regular expressions to include tags. Can be useful if you enable `watch_repo` |
|
| `include_tags` | | List of regular expressions to include tags. Can be useful if you enable `watch_repo` |
|
||||||
| `exclude_tags` | | List of regular expressions to exclude tags. Can be useful if you enable `watch_repo` |
|
| `exclude_tags` | | List of regular expressions to exclude tags. Can be useful if you enable `watch_repo` |
|
||||||
| `platform.os` | dynamic based on your OS specs | Operating system to use as custom platform |
|
| `platform.os` | _automatic_ | Operating system to use as custom platform |
|
||||||
| `platform.arch` | dynamic based on your OS specs | CPU architecture to use as custom platform |
|
| `platform.arch` | _automatic_ | CPU architecture to use as custom platform |
|
||||||
| `platform.variant` | dynamic based on your OS specs | Variant of the CPU to use as custom platform |
|
| `platform.variant` | _automatic_ | Variant of the CPU to use as custom platform |
|
||||||
|
|||||||
@@ -288,3 +288,4 @@ You can configure more finely the way to analyze the image of your pods through
|
|||||||
| `diun.max_tags` | `0` | Maximum number of tags to watch if `diun.watch_repo` enabled. `0` means all of them |
|
| `diun.max_tags` | `0` | Maximum number of tags to watch if `diun.watch_repo` enabled. `0` means all of them |
|
||||||
| `diun.include_tags` | | Semi-colon separated list of regular expressions to include tags. Can be useful if you enable `diun.watch_repo` |
|
| `diun.include_tags` | | Semi-colon separated list of regular expressions to include tags. Can be useful if you enable `diun.watch_repo` |
|
||||||
| `diun.exclude_tags` | | Semi-colon separated list of regular expressions to exclude tags. Can be useful if you enable `diun.watch_repo` |
|
| `diun.exclude_tags` | | Semi-colon separated list of regular expressions to exclude tags. Can be useful if you enable `diun.watch_repo` |
|
||||||
|
| `diun.platform` | _automatic_ | Platform to use (e.g. `linux/amd64`) |
|
||||||
|
|||||||
@@ -182,3 +182,4 @@ You can configure more finely the way to analyze the image of your service throu
|
|||||||
| `diun.max_tags` | `0` | Maximum number of tags to watch if `diun.watch_repo` enabled. `0` means all of them |
|
| `diun.max_tags` | `0` | Maximum number of tags to watch if `diun.watch_repo` enabled. `0` means all of them |
|
||||||
| `diun.include_tags` | | Semi-colon separated list of regular expressions to include tags. Can be useful if you enable `diun.watch_repo` |
|
| `diun.include_tags` | | Semi-colon separated list of regular expressions to include tags. Can be useful if you enable `diun.watch_repo` |
|
||||||
| `diun.exclude_tags` | | Semi-colon separated list of regular expressions to exclude tags. Can be useful if you enable `diun.watch_repo` |
|
| `diun.exclude_tags` | | Semi-colon separated list of regular expressions to exclude tags. Can be useful if you enable `diun.watch_repo` |
|
||||||
|
| `diun.platform` | _automatic_ | Platform to use (e.g. `linux/amd64`) |
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ services:
|
|||||||
- name: traefik
|
- name: traefik
|
||||||
watch_repo: true
|
watch_repo: true
|
||||||
include_tags:
|
include_tags:
|
||||||
- ^(0|[1-9]\d*)\..*
|
- ^\d+\.\d+\..*
|
||||||
```
|
```
|
||||||
|
|
||||||
Here we use a minimal configuration to analyze **all running containers** (watch by default enabled) of
|
Here we use a minimal configuration to analyze **all running containers** (watch by default enabled) of
|
||||||
|
|||||||
3
go.mod
3
go.mod
@@ -4,6 +4,8 @@ go 1.15
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/alecthomas/kong v0.2.16
|
github.com/alecthomas/kong v0.2.16
|
||||||
|
github.com/bmatcuk/doublestar/v3 v3.0.0
|
||||||
|
github.com/containerd/containerd v1.5.0-beta.4
|
||||||
github.com/containers/image/v5 v5.11.1
|
github.com/containers/image/v5 v5.11.1
|
||||||
github.com/crazy-max/gohealthchecks v0.3.0
|
github.com/crazy-max/gohealthchecks v0.3.0
|
||||||
github.com/crazy-max/gonfig v0.4.0
|
github.com/crazy-max/gonfig v0.4.0
|
||||||
@@ -19,6 +21,7 @@ require (
|
|||||||
github.com/matcornic/hermes/v2 v2.1.0
|
github.com/matcornic/hermes/v2 v2.1.0
|
||||||
github.com/matrix-org/gomatrix v0.0.0-20200501121722-e5578b12c752
|
github.com/matrix-org/gomatrix v0.0.0-20200501121722-e5578b12c752
|
||||||
github.com/microcosm-cc/bluemonday v1.0.9
|
github.com/microcosm-cc/bluemonday v1.0.9
|
||||||
|
github.com/moby/buildkit v0.8.2
|
||||||
github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 // indirect
|
github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 // indirect
|
||||||
github.com/nlopes/slack v0.6.0
|
github.com/nlopes/slack v0.6.0
|
||||||
github.com/opencontainers/go-digest v1.0.0
|
github.com/opencontainers/go-digest v1.0.0
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
"github.com/crazy-max/diun/v4/internal/model"
|
"github.com/crazy-max/diun/v4/internal/model"
|
||||||
"github.com/crazy-max/diun/v4/internal/notif"
|
"github.com/crazy-max/diun/v4/internal/notif"
|
||||||
dockerPrd "github.com/crazy-max/diun/v4/internal/provider/docker"
|
dockerPrd "github.com/crazy-max/diun/v4/internal/provider/docker"
|
||||||
|
dockerfilePrd "github.com/crazy-max/diun/v4/internal/provider/dockerfile"
|
||||||
filePrd "github.com/crazy-max/diun/v4/internal/provider/file"
|
filePrd "github.com/crazy-max/diun/v4/internal/provider/file"
|
||||||
kubernetesPrd "github.com/crazy-max/diun/v4/internal/provider/kubernetes"
|
kubernetesPrd "github.com/crazy-max/diun/v4/internal/provider/kubernetes"
|
||||||
swarmPrd "github.com/crazy-max/diun/v4/internal/provider/swarm"
|
swarmPrd "github.com/crazy-max/diun/v4/internal/provider/swarm"
|
||||||
@@ -156,6 +157,11 @@ func (di *Diun) Run() {
|
|||||||
di.createJob(job)
|
di.createJob(job)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Dokcerfile provider
|
||||||
|
for _, job := range dockerfilePrd.New(di.cfg.Providers.Dockerfile).ListJob() {
|
||||||
|
di.createJob(job)
|
||||||
|
}
|
||||||
|
|
||||||
di.wg.Wait()
|
di.wg.Wait()
|
||||||
log.Info().
|
log.Info().
|
||||||
Int("added", entries.CountNew).
|
Int("added", entries.CountNew).
|
||||||
|
|||||||
16
internal/model/provider_dockerfile.go
Normal file
16
internal/model/provider_dockerfile.go
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
// PrdDockerfile holds dockerfile provider configuration
|
||||||
|
type PrdDockerfile struct {
|
||||||
|
Patterns []string `yaml:"patterns,omitempty" json:"patterns,omitempty" validate:"omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDefaults gets the default values
|
||||||
|
func (s *PrdDockerfile) GetDefaults() *PrdDockerfile {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDefaults sets the default values
|
||||||
|
func (s *PrdDockerfile) SetDefaults() {
|
||||||
|
// noop
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ type Providers struct {
|
|||||||
Swarm *PrdSwarm `yaml:"swarm,omitempty" json:"swarm,omitempty" label:"allowEmpty" file:"allowEmpty"`
|
Swarm *PrdSwarm `yaml:"swarm,omitempty" json:"swarm,omitempty" label:"allowEmpty" file:"allowEmpty"`
|
||||||
Kubernetes *PrdKubernetes `yaml:"kubernetes,omitempty" json:"kubernetes,omitempty" label:"allowEmpty" file:"allowEmpty"`
|
Kubernetes *PrdKubernetes `yaml:"kubernetes,omitempty" json:"kubernetes,omitempty" label:"allowEmpty" file:"allowEmpty"`
|
||||||
File *PrdFile `yaml:"file,omitempty" json:"file,omitempty"`
|
File *PrdFile `yaml:"file,omitempty" json:"file,omitempty"`
|
||||||
|
Dockerfile *PrdDockerfile `yaml:"dockerfile,omitempty" json:"dockerfile,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetDefaults gets the default values
|
// GetDefaults gets the default values
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/containerd/containerd/platforms"
|
||||||
"github.com/crazy-max/diun/v4/internal/model"
|
"github.com/crazy-max/diun/v4/internal/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -47,6 +48,16 @@ func ValidateImage(image string, labels map[string]string, watchByDef bool) (img
|
|||||||
img.ExcludeTags = strings.Split(value, ";")
|
img.ExcludeTags = strings.Split(value, ";")
|
||||||
case "diun.hub_tpl":
|
case "diun.hub_tpl":
|
||||||
img.HubTpl = value
|
img.HubTpl = value
|
||||||
|
case "diun.platform":
|
||||||
|
platform, err := platforms.Parse(value)
|
||||||
|
if err != nil {
|
||||||
|
return img, fmt.Errorf("cannot parse %s platform of label %s", value, key)
|
||||||
|
}
|
||||||
|
img.Platform = model.ImagePlatform{
|
||||||
|
Os: platform.OS,
|
||||||
|
Arch: platform.Architecture,
|
||||||
|
Variant: platform.Variant,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
49
internal/provider/dockerfile/dockerfile.go
Normal file
49
internal/provider/dockerfile/dockerfile.go
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
package dockerfile
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/crazy-max/diun/v4/internal/model"
|
||||||
|
"github.com/crazy-max/diun/v4/internal/provider"
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Client represents an active dockerfile provider object
|
||||||
|
type Client struct {
|
||||||
|
*provider.Client
|
||||||
|
config *model.PrdDockerfile
|
||||||
|
logger zerolog.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates new dockerfile provider instance
|
||||||
|
func New(config *model.PrdDockerfile) *provider.Client {
|
||||||
|
return &provider.Client{
|
||||||
|
Handler: &Client{
|
||||||
|
config: config,
|
||||||
|
logger: log.With().Str("provider", "dockerfile").Logger(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListJob returns job list to process
|
||||||
|
func (c *Client) ListJob() []model.Job {
|
||||||
|
if c.config == nil {
|
||||||
|
return []model.Job{}
|
||||||
|
}
|
||||||
|
|
||||||
|
images := c.listExtImage()
|
||||||
|
if len(images) == 0 {
|
||||||
|
log.Warn().Msg("No image found")
|
||||||
|
return []model.Job{}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.logger.Info().Msgf("Found %d image(s) to analyze", len(images))
|
||||||
|
var list []model.Job
|
||||||
|
for _, image := range images {
|
||||||
|
list = append(list, model.Job{
|
||||||
|
Provider: "dockerfile",
|
||||||
|
Image: image,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return list
|
||||||
|
}
|
||||||
94
internal/provider/dockerfile/image.go
Normal file
94
internal/provider/dockerfile/image.go
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
package dockerfile
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/bmatcuk/doublestar/v3"
|
||||||
|
"github.com/crazy-max/diun/v4/internal/model"
|
||||||
|
"github.com/crazy-max/diun/v4/internal/provider"
|
||||||
|
"github.com/crazy-max/diun/v4/pkg/dockerfile"
|
||||||
|
"github.com/crazy-max/diun/v4/pkg/utl"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *Client) listExtImage() (list []model.Image) {
|
||||||
|
for _, filename := range c.listDockerfiles(c.config.Patterns) {
|
||||||
|
dfile, err := dockerfile.New(dockerfile.Options{
|
||||||
|
Filename: filename,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
c.logger.Warn().Err(err).Msg("Cannot create dockerfile client")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fromImages, err := dfile.FromImages()
|
||||||
|
if err != nil {
|
||||||
|
c.logger.Warn().Err(err).Msg("Cannot extract images")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, fromImage := range fromImages {
|
||||||
|
c.logger.Debug().
|
||||||
|
Str("dfile_image", fromImage.Name).
|
||||||
|
Str("dfile_code", fromImage.Code).
|
||||||
|
Interface("dfile_comments", fromImage.Comments).
|
||||||
|
Int("dfile_line", fromImage.Line).
|
||||||
|
Msg("Validate image")
|
||||||
|
image, err := provider.ValidateImage(fromImage.Name, c.extractLabels(fromImage.Comments), true)
|
||||||
|
if err != nil {
|
||||||
|
c.logger.Error().Err(err).
|
||||||
|
Str("dfile_image", fromImage.Name).
|
||||||
|
Str("dfile_code", fromImage.Code).
|
||||||
|
Interface("dfile_comments", fromImage.Comments).
|
||||||
|
Int("dfile_line", fromImage.Line).
|
||||||
|
Msg("Invalid image")
|
||||||
|
continue
|
||||||
|
} else if reflect.DeepEqual(image, model.Image{}) {
|
||||||
|
c.logger.Debug().
|
||||||
|
Str("dfile_image", fromImage.Name).
|
||||||
|
Str("dfile_code", fromImage.Code).
|
||||||
|
Interface("dfile_comments", fromImage.Comments).
|
||||||
|
Int("dfile_line", fromImage.Line).
|
||||||
|
Msg("Watch disabled")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
list = append(list, image)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) listDockerfiles(patterns []string) (dfiles []string) {
|
||||||
|
if len(patterns) == 0 {
|
||||||
|
patterns = []string{"./Dockerfile"}
|
||||||
|
}
|
||||||
|
for _, pattern := range patterns {
|
||||||
|
matches, err := doublestar.Glob(pattern)
|
||||||
|
if err != nil {
|
||||||
|
c.logger.Warn().Err(err).Msgf("No Dockerfile found for %s", pattern)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, dfile := range matches {
|
||||||
|
if utl.Contains(dfiles, dfile) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
dfiles = append(dfiles, dfile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) extractLabels(comments []string) map[string]string {
|
||||||
|
labels := map[string]string{}
|
||||||
|
if len(comments) == 0 {
|
||||||
|
return labels
|
||||||
|
}
|
||||||
|
for _, comment := range comments {
|
||||||
|
if !strings.HasPrefix(comment, "diun.") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
kvp := strings.SplitN(comment, "=", 2)
|
||||||
|
if len(kvp) == 2 {
|
||||||
|
labels[kvp[0]] = kvp[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return labels
|
||||||
|
}
|
||||||
@@ -114,9 +114,10 @@ nav:
|
|||||||
- Webhook: notif/webhook.md
|
- Webhook: notif/webhook.md
|
||||||
- Providers:
|
- Providers:
|
||||||
- Docker: providers/docker.md
|
- Docker: providers/docker.md
|
||||||
- File: providers/file.md
|
|
||||||
- Kubernetes: providers/kubernetes.md
|
- Kubernetes: providers/kubernetes.md
|
||||||
- Swarm: providers/swarm.md
|
- Swarm: providers/swarm.md
|
||||||
|
- Dockerfile: providers/dockerfile.md
|
||||||
|
- File: providers/file.md
|
||||||
- User guides:
|
- User guides:
|
||||||
- Docker + File providers: user-guides/docker-file-providers.md
|
- Docker + File providers: user-guides/docker-file-providers.md
|
||||||
- FAQ: faq.md
|
- FAQ: faq.md
|
||||||
|
|||||||
77
pkg/dockerfile/client.go
Normal file
77
pkg/dockerfile/client.go
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
package dockerfile
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io/ioutil"
|
||||||
|
|
||||||
|
"github.com/moby/buildkit/frontend/dockerfile/instructions"
|
||||||
|
"github.com/moby/buildkit/frontend/dockerfile/parser"
|
||||||
|
"github.com/moby/buildkit/frontend/dockerfile/shell"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Client represents an active dockerfile object
|
||||||
|
type Client struct {
|
||||||
|
ast *parser.Node
|
||||||
|
stages []instructions.Stage
|
||||||
|
metaArgs []instructions.KeyValuePairOptional
|
||||||
|
shlex *shell.Lex
|
||||||
|
}
|
||||||
|
|
||||||
|
// Options holds dockerfile client object options
|
||||||
|
type Options struct {
|
||||||
|
Filename string
|
||||||
|
}
|
||||||
|
|
||||||
|
// New initializes a new dockerfile client
|
||||||
|
func New(opts Options) (*Client, error) {
|
||||||
|
b, err := ioutil.ReadFile(opts.Filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "Cannot read Dockerfile %s", opts.Filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
parsed, err := parser.Parse(bytes.NewReader(b))
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "Cannot parse Dockerfile %s", opts.Filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
stages, metaArgs, err := instructions.Parse(parsed.AST)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "Cannot parse stages for Dockerfile %s", opts.Filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
var kvpoArgs []instructions.KeyValuePairOptional
|
||||||
|
shlex := shell.NewLex(parsed.EscapeToken)
|
||||||
|
for _, cmd := range metaArgs {
|
||||||
|
for _, metaArg := range cmd.Args {
|
||||||
|
if metaArg.Value != nil {
|
||||||
|
*metaArg.Value, _ = shlex.ProcessWordWithMap(*metaArg.Value, metaArgsToMap(kvpoArgs))
|
||||||
|
}
|
||||||
|
kvpoArgs = append(kvpoArgs, metaArg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Client{
|
||||||
|
ast: parsed.AST,
|
||||||
|
stages: stages,
|
||||||
|
metaArgs: kvpoArgs,
|
||||||
|
shlex: shlex,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) isStageName(name string) bool {
|
||||||
|
for _, stage := range c.stages {
|
||||||
|
if stage.Name == name {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func metaArgsToMap(metaArgs []instructions.KeyValuePairOptional) map[string]string {
|
||||||
|
m := map[string]string{}
|
||||||
|
for _, arg := range metaArgs {
|
||||||
|
m[arg.Key] = arg.ValueString()
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
106
pkg/dockerfile/image.go
Normal file
106
pkg/dockerfile/image.go
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
package dockerfile
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/moby/buildkit/frontend/dockerfile/command"
|
||||||
|
"github.com/moby/buildkit/frontend/dockerfile/instructions"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Image struct {
|
||||||
|
Name string
|
||||||
|
Code string
|
||||||
|
Comments []string
|
||||||
|
Line int
|
||||||
|
}
|
||||||
|
|
||||||
|
type Images []Image
|
||||||
|
|
||||||
|
func (is *Images) has(image Image) bool {
|
||||||
|
for _, i := range *is {
|
||||||
|
if i.Line == image.Line {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromImages returns external images found in Dockerfile
|
||||||
|
func (c *Client) FromImages() (Images, error) {
|
||||||
|
images := Images{}
|
||||||
|
|
||||||
|
for _, node := range c.ast.Children {
|
||||||
|
switch node.Value {
|
||||||
|
case command.From:
|
||||||
|
ins, err := instructions.ParseInstruction(node)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "Cannot parse instruction")
|
||||||
|
}
|
||||||
|
if baseName := ins.(*instructions.Stage).BaseName; baseName != "scratch" {
|
||||||
|
name, err := c.shlex.ProcessWordWithMap(baseName, metaArgsToMap(c.metaArgs))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
image := Image{
|
||||||
|
Name: name,
|
||||||
|
Code: node.Original,
|
||||||
|
Comments: node.PrevComment,
|
||||||
|
Line: node.StartLine,
|
||||||
|
}
|
||||||
|
if c.isStageName(name) || images.has(image) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
images = append(images, image)
|
||||||
|
}
|
||||||
|
case command.Copy:
|
||||||
|
cmd, err := instructions.ParseCommand(node)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "Cannot parse command")
|
||||||
|
}
|
||||||
|
if copyFrom := cmd.(*instructions.CopyCommand).From; copyFrom != "null" {
|
||||||
|
name, err := c.shlex.ProcessWordWithMap(copyFrom, metaArgsToMap(c.metaArgs))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
image := Image{
|
||||||
|
Name: name,
|
||||||
|
Code: node.Original,
|
||||||
|
Comments: node.PrevComment,
|
||||||
|
Line: node.StartLine,
|
||||||
|
}
|
||||||
|
if c.isStageName(name) || images.has(image) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
images = append(images, image)
|
||||||
|
}
|
||||||
|
case command.Run:
|
||||||
|
cmd, err := instructions.ParseCommand(node)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "Cannot parse command")
|
||||||
|
}
|
||||||
|
if cmdRun, ok := cmd.(*instructions.RunCommand); ok {
|
||||||
|
mounts := instructions.GetMounts(cmdRun)
|
||||||
|
for _, mount := range mounts {
|
||||||
|
if mount.Type != instructions.MountTypeBind || len(mount.From) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
name, err := c.shlex.ProcessWordWithMap(mount.From, metaArgsToMap(c.metaArgs))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
image := Image{
|
||||||
|
Name: name,
|
||||||
|
Code: node.Original,
|
||||||
|
Comments: node.PrevComment,
|
||||||
|
Line: node.StartLine,
|
||||||
|
}
|
||||||
|
if c.isStageName(name) || images.has(image) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
images = append(images, image)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return images, nil
|
||||||
|
}
|
||||||
@@ -84,3 +84,13 @@ func NewTrue() *bool {
|
|||||||
func NewDuration(duration time.Duration) *time.Duration {
|
func NewDuration(duration time.Duration) *time.Duration {
|
||||||
return &duration
|
return &duration
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Contains checks if a slice contains a string
|
||||||
|
func Contains(s []string, e string) bool {
|
||||||
|
for _, a := range s {
|
||||||
|
if a == e {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user