mirror of
https://github.com/crazy-max/diun.git
synced 2025-12-24 06:28:13 +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)
|
||||
* providers
|
||||
* [docker](../providers/docker.md)
|
||||
* [file](../providers/file.md)
|
||||
* [kubernetes](../providers/kubernetes.md)
|
||||
* [swarm](../providers/swarm.md)
|
||||
* [dockerfile](../providers/dockerfile.md)
|
||||
* [file](../providers/file.md)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# Providers configuration
|
||||
|
||||
* [`docker`](../providers/docker.md)
|
||||
* [`file`](../providers/file.md)
|
||||
* [`kubernetes`](../providers/kubernetes.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`
|
||||
[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
|
||||
* **Required**
|
||||
|
||||
@@ -31,8 +31,8 @@ This support includes Linux, macOS, and Windows, on architectures like amd64, i3
|
||||
* Internal cron implementation through go routines
|
||||
* Worker pool to parallelize analyses
|
||||
* Allow overriding image os and architecture
|
||||
* [Docker](providers/docker.md), [Swarm](providers/swarm.md), [Kubernetes](providers/kubernetes.md)
|
||||
and [File](providers/file.md) providers available
|
||||
* [Docker](providers/docker.md), [Swarm](providers/swarm.md), [Kubernetes](providers/kubernetes.md),
|
||||
[Dockerfile](providers/dockerfile.md) and [File](providers/file.md) providers available
|
||||
* Get notified through Gotify, Mail, Slack, Telegram and [more](config/index.md#reference)
|
||||
* [Healthchecks support](config/watch.md#healthchecks) to monitor Diun watcher
|
||||
* Enhanced logging
|
||||
|
||||
@@ -26,7 +26,7 @@ Allow to send notifications to your Discord channel.
|
||||
|
||||
!!! abstract "Environment variables"
|
||||
* `DIUN_NOTIF_DISCORD_WEBHOOKURL`
|
||||
* `DIUN_NOTIF_DISCORD_MENTIONS`
|
||||
* `DIUN_NOTIF_DISCORD_MENTIONS` (comma separated)
|
||||
* `DIUN_NOTIF_DISCORD_TIMEOUT`
|
||||
|
||||
## Sample
|
||||
|
||||
@@ -35,7 +35,7 @@ DIUN_ENTRY_PLATFORM=linux/amd64
|
||||
|
||||
!!! abstract "Environment variables"
|
||||
* `DIUN_NOTIF_SCRIPT_CMD`
|
||||
* `DIUN_NOTIF_SCRIPT_ARGS`
|
||||
* `DIUN_NOTIF_SCRIPT_ARGS` (comma separated)
|
||||
* `DIUN_NOTIF_SCRIPT_DIR`
|
||||
|
||||
[^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.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`) |
|
||||
|
||||
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
|
||||
# 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
|
||||
watch_repo: true
|
||||
max_tags: 10
|
||||
include_tags:
|
||||
- ^(0|[1-9]\d*)\..*
|
||||
- ^\d+\.\d+\..*
|
||||
|
||||
# Watch alpine image (library) and assume docker.io registry and latest tag
|
||||
# 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 |
|
||||
| `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` |
|
||||
| `platform.os` | dynamic based on your OS specs | Operating system to use as custom platform |
|
||||
| `platform.arch` | dynamic based on your OS specs | 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.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 |
|
||||
|
||||
@@ -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.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`) |
|
||||
|
||||
@@ -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.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`) |
|
||||
|
||||
@@ -41,7 +41,7 @@ services:
|
||||
- name: traefik
|
||||
watch_repo: true
|
||||
include_tags:
|
||||
- ^(0|[1-9]\d*)\..*
|
||||
- ^\d+\.\d+\..*
|
||||
```
|
||||
|
||||
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 (
|
||||
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/crazy-max/gohealthchecks v0.3.0
|
||||
github.com/crazy-max/gonfig v0.4.0
|
||||
@@ -19,6 +21,7 @@ require (
|
||||
github.com/matcornic/hermes/v2 v2.1.0
|
||||
github.com/matrix-org/gomatrix v0.0.0-20200501121722-e5578b12c752
|
||||
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/nlopes/slack v0.6.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/notif"
|
||||
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"
|
||||
kubernetesPrd "github.com/crazy-max/diun/v4/internal/provider/kubernetes"
|
||||
swarmPrd "github.com/crazy-max/diun/v4/internal/provider/swarm"
|
||||
@@ -156,6 +157,11 @@ func (di *Diun) Run() {
|
||||
di.createJob(job)
|
||||
}
|
||||
|
||||
// Dokcerfile provider
|
||||
for _, job := range dockerfilePrd.New(di.cfg.Providers.Dockerfile).ListJob() {
|
||||
di.createJob(job)
|
||||
}
|
||||
|
||||
di.wg.Wait()
|
||||
log.Info().
|
||||
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"`
|
||||
Kubernetes *PrdKubernetes `yaml:"kubernetes,omitempty" json:"kubernetes,omitempty" label:"allowEmpty" file:"allowEmpty"`
|
||||
File *PrdFile `yaml:"file,omitempty" json:"file,omitempty"`
|
||||
Dockerfile *PrdDockerfile `yaml:"dockerfile,omitempty" json:"dockerfile,omitempty"`
|
||||
}
|
||||
|
||||
// GetDefaults gets the default values
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/containerd/containerd/platforms"
|
||||
"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, ";")
|
||||
case "diun.hub_tpl":
|
||||
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
|
||||
- Providers:
|
||||
- Docker: providers/docker.md
|
||||
- File: providers/file.md
|
||||
- Kubernetes: providers/kubernetes.md
|
||||
- Swarm: providers/swarm.md
|
||||
- Dockerfile: providers/dockerfile.md
|
||||
- File: providers/file.md
|
||||
- User guides:
|
||||
- Docker + File providers: user-guides/docker-file-providers.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 {
|
||||
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