Dockerfile provider (#329)

Co-authored-by: CrazyMax <crazy-max@users.noreply.github.com>
This commit is contained in:
CrazyMax
2021-04-29 18:15:29 +02:00
committed by GitHub
parent 75eea45fdb
commit 6ac7139ce2
24 changed files with 1468 additions and 48 deletions

View File

@@ -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)

View File

@@ -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)

View File

@@ -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**

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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`) |

View 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`) |

View File

@@ -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 |

View File

@@ -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`) |

View File

@@ -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`) |

View File

@@ -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
View File

@@ -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

992
go.sum

File diff suppressed because it is too large Load Diff

View File

@@ -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).

View 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
}

View File

@@ -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

View File

@@ -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,
}
} }
} }

View 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
}

View 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
}

View File

@@ -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
View 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
View 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
}

View File

@@ -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
}