Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
feat(config): dependencies directive parser
  • Loading branch information
ilya-lesikov committed Jan 28, 2022
1 parent 9d80598 commit 3eb94e4
Show file tree
Hide file tree
Showing 10 changed files with 322 additions and 27 deletions.
52 changes: 51 additions & 1 deletion pkg/config/dependency.go
@@ -1,8 +1,58 @@
package config

import (
"fmt"
"strings"

"github.com/werf/werf/pkg/util"
)

type Dependency struct {
ImageName string
Before string
After string
Imports []*DependencyImport

// TODO: raw *rawDependencies
raw *rawDependency
}

func (d *Dependency) validate() error {
switch {
case d.ImageName == "":
return newDetailedConfigError("image name is not specified for dependency", d.raw, d.raw.doc())
case d.Before == "" && d.After == "":
return newDetailedConfigError("dependency stage is not specified with `before: install|setup` or `after: install|setup`", d.raw, d.raw.doc())
case d.Before != "" && d.After != "":
return newDetailedConfigError("only one dependency stage can be specified with `before: install|setup` or `after: install|setup`, but not both", d.raw, d.raw.doc())
case d.Before != "" && d.Before != "install" && d.Before != "setup":
return newDetailedConfigError(fmt.Sprintf("invalid dependency stage `before: %s`: expected install or setup!", d.Before), d.raw, d.raw.doc())
case d.After != "" && d.After != "install" && d.After != "setup":
return newDetailedConfigError(fmt.Sprintf("invalid dependency stage `after: %s`: expected install or setup!", d.After), d.raw, d.raw.doc())
}

var targetEnvs, targetBuildArgs []string
for _, depImport := range d.Imports {
if depImport.TargetEnv != "" {
targetEnvs = append(targetEnvs, depImport.TargetEnv)
}
if depImport.TargetBuildArg != "" {
targetBuildArgs = append(targetBuildArgs, depImport.TargetBuildArg)
}
}

if len(targetEnvs) > 0 {
duplicatedTargetEnvs := util.FindDuplicatedStrings(targetEnvs)
if len(duplicatedTargetEnvs) > 0 {
return newDetailedConfigError(fmt.Sprintf("each targetEnv for dependency import should be unique, but found duplicates for: %s", strings.Join(duplicatedTargetEnvs, ", ")), d.raw, d.raw.doc())
}
}

if len(targetBuildArgs) > 0 {
duplicatedTargetBuildArgs := util.FindDuplicatedStrings(targetBuildArgs)
if len(duplicatedTargetBuildArgs) > 0 {
return newDetailedConfigError(fmt.Sprintf("each targetBuildArg for dependency import should be unique, but found duplicates for: %s", strings.Join(duplicatedTargetBuildArgs, ", ")), d.raw, d.raw.doc())
}
}

return nil
}
32 changes: 22 additions & 10 deletions pkg/config/dependency_import.go
Expand Up @@ -10,21 +10,33 @@ type DependencyImport struct {
TargetBuildArg string
TargetEnv string

// TODO: raw *rawDependencyImport
raw *rawDependencyImport
}

func (i *DependencyImport) validate(img ImageInterface) error {
switch {
case img.IsStapel() && i.TargetBuildArg != "":
return newDetailedConfigError("`targetBuildArg cannot be used in the stapel image", nil, nil) // TODO: raw
case !img.IsStapel() && i.TargetEnv != "":
return newDetailedConfigError("`targetEnv cannot be used in the dockerfile image", nil, nil) // TODO: raw
}

func (i *DependencyImport) validate() error {
switch i.Type {
case ImageNameImport, ImageTagImport, ImageRepoImport:
default:
return newDetailedConfigError(fmt.Sprintf("invalid `type: %s` for dependency import, expected one of: %s", i.Type, strings.Join([]string{string(ImageNameImport), string(ImageTagImport), string(ImageRepoImport)}, ", ")), nil, nil) // TODO: raw
return newDetailedConfigError(fmt.Sprintf("invalid `type: %s` for dependency import, expected one of: %s", i.Type, strings.Join([]string{string(ImageNameImport), string(ImageTagImport), string(ImageRepoImport)}, ", ")), i.raw, i.raw.rawDependency.doc())
}

switch imgType := i.raw.rawDependency.imageType(); imgType {
case dependencyImageTypeStapel:
switch {
case i.TargetEnv == "":
return newDetailedConfigError("targetEnv directive cannot be empty for a Stapel image", i.raw, i.raw.rawDependency.doc())
case i.TargetBuildArg != "":
return newDetailedConfigError("targetBuildArg directive cannot be used for a Stapel image", i.raw, i.raw.rawDependency.doc())
}
case dependencyImageTypeDockerfile:
switch {
case i.TargetBuildArg == "":
return newDetailedConfigError("targetBuildArg directive cannot be empty for a Dockerfile image", i.raw, i.raw.rawDependency.doc())
case i.TargetEnv != "":
return newDetailedConfigError("targetEnv directive cannot be used for a Dockerfile image", i.raw, i.raw.rawDependency.doc())
}
default:
panic(fmt.Sprintf("unexpected dependencyImageType: %s", imgType))
}

return nil
Expand Down
11 changes: 11 additions & 0 deletions pkg/config/image_from_dockerfile.go
@@ -1,6 +1,7 @@
package config

import (
"fmt"
"path/filepath"

"github.com/werf/werf/pkg/giterminism_manager"
Expand Down Expand Up @@ -37,6 +38,16 @@ func (c *ImageFromDockerfile) validate(giterminismManager giterminism_manager.In
}
}

if len(c.Args) > 0 {
for _, dep := range c.Dependencies {
for _, depImport := range dep.Imports {
if _, ok := c.Args[depImport.TargetBuildArg]; ok {
return newDetailedConfigError(fmt.Sprintf("dockerfile `args:` build arg %q already defined in dependency import `targetBuildArg:` directive. This is not allowed, avoid duplicated build args!", depImport.TargetBuildArg), nil, c.raw.doc)
}
}
}
}

return nil
}

Expand Down
4 changes: 4 additions & 0 deletions pkg/config/parser.go
Expand Up @@ -531,6 +531,10 @@ func prepareWerfConfig(giterminismManager giterminism_manager.Interface, rawImag
return nil, err
}

if err := werfConfig.validateDependencies(); err != nil {
return nil, err
}

return werfConfig, nil
}

Expand Down
92 changes: 92 additions & 0 deletions pkg/config/raw_dependency.go
@@ -0,0 +1,92 @@
package config

type dependencyImageType string

var (
dependencyImageTypeUnknown dependencyImageType = ""
dependencyImageTypeStapel dependencyImageType = "stapel"
dependencyImageTypeDockerfile dependencyImageType = "dockerfile"
)

type rawDependency struct {
Image string `yaml:"image,omitempty"`
Before string `yaml:"before,omitempty"`
After string `yaml:"after,omitempty"`
Imports []*rawDependencyImport `yaml:"imports,omitempty"`

rawStapelImage *rawStapelImage `yaml:"-"` // possible parent
rawImageFromDockerfile *rawImageFromDockerfile `yaml:"-"` // possible parent

UnsupportedAttributes map[string]interface{} `yaml:",inline"`
}

func (d *rawDependency) doc() *doc {
var document *doc
switch d.imageType() {
case dependencyImageTypeStapel:
document = d.rawStapelImage.doc
case dependencyImageTypeDockerfile:
document = d.rawImageFromDockerfile.doc
}

return document
}

func (d *rawDependency) UnmarshalYAML(unmarshal func(interface{}) error) error {
switch parent := parentStack.Peek().(type) {
case *rawStapelImage:
d.rawStapelImage = parent
case *rawImageFromDockerfile:
d.rawImageFromDockerfile = parent
}

parentStack.Push(d)
type plain rawDependency
err := unmarshal((*plain)(d))
parentStack.Pop()
if err != nil {
return err
}

if err := checkOverflow(d.UnsupportedAttributes, d, d.doc()); err != nil {
return err
}

return nil
}

func (d *rawDependency) toDirective() (*Dependency, error) {
dependency := &Dependency{
ImageName: d.Image,
Before: d.Before,
After: d.After,
raw: d,
}

for _, rawDepImport := range d.Imports {
depImport, err := rawDepImport.toDirective()
if err != nil {
return nil, err
}

dependency.Imports = append(dependency.Imports, depImport)
}

if err := dependency.validate(); err != nil {
return nil, err
}

return dependency, nil
}

func (d *rawDependency) imageType() dependencyImageType {
if d.rawStapelImage != nil {
return dependencyImageTypeStapel
}

if d.rawImageFromDockerfile != nil {
return dependencyImageTypeDockerfile
}

return dependencyImageTypeUnknown
}
43 changes: 43 additions & 0 deletions pkg/config/raw_dependency_import.go
@@ -0,0 +1,43 @@
package config

type rawDependencyImport struct {
Type string `yaml:"type,omitempty"`
TargetBuildArg string `yaml:"targetBuildArg,omitempty"`
TargetEnv string `yaml:"targetEnv,omitempty"`

rawDependency *rawDependency `yaml:"-"` // parent

UnsupportedAttributes map[string]interface{} `yaml:",inline"`
}

func (i *rawDependencyImport) UnmarshalYAML(unmarshal func(interface{}) error) error {
if parent, ok := parentStack.Peek().(*rawDependency); ok {
i.rawDependency = parent
}

type plain rawDependencyImport
if err := unmarshal((*plain)(i)); err != nil {
return err
}

if err := checkOverflow(i.UnsupportedAttributes, i, i.rawDependency.doc()); err != nil {
return err
}

return nil
}

func (i *rawDependencyImport) toDirective() (*DependencyImport, error) {
depImport := &DependencyImport{
Type: DependencyImportType(i.Type),
TargetBuildArg: i.TargetBuildArg,
TargetEnv: i.TargetEnv,
raw: i,
}

if err := depImport.validate(); err != nil {
return nil, err
}

return depImport, nil
}
10 changes: 10 additions & 0 deletions pkg/config/raw_image_from_dockerfile.go
Expand Up @@ -17,6 +17,7 @@ type rawImageFromDockerfile struct {
AddHost interface{} `yaml:"addHost,omitempty"`
Network string `yaml:"network,omitempty"`
SSH string `yaml:"ssh,omitempty"`
RawDependencies []*rawDependency `yaml:"dependencies,omitempty"`

doc *doc `yaml:"-"` // parent

Expand Down Expand Up @@ -115,6 +116,15 @@ func (c *rawImageFromDockerfile) toImageFromDockerfileDirective(giterminismManag
image.Network = c.Network
image.SSH = c.SSH

for _, rawDep := range c.RawDependencies {
dependencyDirective, err := rawDep.toDirective()
if err != nil {
return nil, err
}

image.Dependencies = append(image.Dependencies, dependencyDirective)
}

image.raw = c

if err := image.validate(giterminismManager); err != nil {
Expand Down
46 changes: 30 additions & 16 deletions pkg/config/raw_stapel_image.go
Expand Up @@ -7,19 +7,20 @@ import (
)

type rawStapelImage struct {
Images []string `yaml:"-"`
Artifact string `yaml:"artifact,omitempty"`
From string `yaml:"from,omitempty"`
FromLatest bool `yaml:"fromLatest,omitempty"`
FromCacheVersion string `yaml:"fromCacheVersion,omitempty"`
FromImage string `yaml:"fromImage,omitempty"`
FromArtifact string `yaml:"fromArtifact,omitempty"`
RawGit []*rawGit `yaml:"git,omitempty"`
RawShell *rawShell `yaml:"shell,omitempty"`
RawAnsible *rawAnsible `yaml:"ansible,omitempty"`
RawMount []*rawMount `yaml:"mount,omitempty"`
RawDocker *rawDocker `yaml:"docker,omitempty"`
RawImport []*rawImport `yaml:"import,omitempty"`
Images []string `yaml:"-"`
Artifact string `yaml:"artifact,omitempty"`
From string `yaml:"from,omitempty"`
FromLatest bool `yaml:"fromLatest,omitempty"`
FromCacheVersion string `yaml:"fromCacheVersion,omitempty"`
FromImage string `yaml:"fromImage,omitempty"`
FromArtifact string `yaml:"fromArtifact,omitempty"`
RawGit []*rawGit `yaml:"git,omitempty"`
RawShell *rawShell `yaml:"shell,omitempty"`
RawAnsible *rawAnsible `yaml:"ansible,omitempty"`
RawMount []*rawMount `yaml:"mount,omitempty"`
RawDocker *rawDocker `yaml:"docker,omitempty"`
RawImport []*rawImport `yaml:"import,omitempty"`
RawDependencies []*rawDependency `yaml:"dependencies,omitempty"`

doc *doc `yaml:"-"` // parent

Expand Down Expand Up @@ -113,7 +114,7 @@ func (c *rawStapelImage) toStapelImageArtifactDirectives(giterminismManager gite
imageArtifact := &StapelImageArtifact{}

var err error
if imageArtifact.StapelImageBase, err = c.toStapelImageBaseDirective(giterminismManager, c.Artifact); err != nil {
if imageArtifact.StapelImageBase, err = c.toStapelImageBaseDirective(giterminismManager, c.Artifact, true); err != nil {
return nil, err
}

Expand All @@ -127,7 +128,7 @@ func (c *rawStapelImage) toStapelImageArtifactDirectives(giterminismManager gite
func (c *rawStapelImage) toStapelImageDirective(giterminismManager giterminism_manager.Interface, name string) (*StapelImage, error) {
image := &StapelImage{}

if imageBase, err := c.toStapelImageBaseDirective(giterminismManager, name); err != nil {
if imageBase, err := c.toStapelImageBaseDirective(giterminismManager, name, false); err != nil {
return nil, err
} else {
image.StapelImageBase = imageBase
Expand Down Expand Up @@ -202,7 +203,7 @@ func (c *rawStapelImage) validateStapelImageArtifactDirective(imageArtifact *Sta
return nil
}

func (c *rawStapelImage) toStapelImageBaseDirective(giterminismManager giterminism_manager.Interface, name string) (imageBase *StapelImageBase, err error) {
func (c *rawStapelImage) toStapelImageBaseDirective(giterminismManager giterminism_manager.Interface, name string, isArtifact bool) (imageBase *StapelImageBase, err error) {
if imageBase, err = c.toBaseStapelImageBaseDirective(giterminismManager, name); err != nil {
return nil, err
}
Expand Down Expand Up @@ -253,6 +254,19 @@ func (c *rawStapelImage) toStapelImageBaseDirective(giterminismManager gitermini
}
}

if isArtifact && len(c.RawDependencies) > 0 {
return nil, newDetailedConfigError(fmt.Sprintf("dependencies directive is specified for %q artifact, but dependencies are not supported for artifacts!", name), nil, c.doc)
}

for _, rawDep := range c.RawDependencies {
dependencyDirective, err := rawDep.toDirective()
if err != nil {
return nil, err
}

imageBase.Dependencies = append(imageBase.Dependencies, dependencyDirective)
}

if err := c.validateStapelImageBaseDirective(giterminismManager, imageBase); err != nil {
return nil, err
}
Expand Down

0 comments on commit 3eb94e4

Please sign in to comment.