Skip to content
Permalink
Browse files
feat(staged-dockerfile): support werf images dependencies build-args
Implemented 2-stage build-args expansion:
1. Expand all build-args except dependencies-args on early dockerfile parsing stage.
2. Expand dependencies-args when dependencies images names are available during image conveyor processing.

refs #2215

Signed-off-by: Timofey Kirillov <timofey.kirillov@flant.com>
  • Loading branch information
distorhead committed Nov 7, 2022
1 parent 334644d commit 8faf229ff4cf8523ae995a72bd76762cd4024833
Show file tree
Hide file tree
Showing 19 changed files with 282 additions and 103 deletions.
@@ -225,7 +225,9 @@ func (phase *BuildPhase) ImageProcessingShouldBeStopped(_ context.Context, _ *im
func (phase *BuildPhase) BeforeImageStages(ctx context.Context, img *image.Image) (deferFn func(), err error) {
phase.StagesIterator = NewStagesIterator(phase.Conveyor)

img.SetupBaseImage()
if err := img.SetupBaseImage(); err != nil {
return nil, err
}

if img.UsesBuildContext() {
phase.buildContextArchive = image.NewBuildContextArchive(phase.Conveyor.giterminismManager, img.TmpDir)
@@ -30,11 +30,12 @@ func MapDockerfileConfigToImagesSets(ctx context.Context, dockerfileImageConfig
}

d, err := frontend.ParseDockerfileWithBuildkit(dockerfileData, dockerfile.DockerfileOptions{
Target: dockerfileImageConfig.Target,
BuildArgs: util.MapStringInterfaceToMapStringString(dockerfileImageConfig.Args),
AddHost: dockerfileImageConfig.AddHost,
Network: dockerfileImageConfig.Network,
SSH: dockerfileImageConfig.SSH,
Target: dockerfileImageConfig.Target,
BuildArgs: util.MapStringInterfaceToMapStringString(dockerfileImageConfig.Args),
AddHost: dockerfileImageConfig.AddHost,
Network: dockerfileImageConfig.Network,
SSH: dockerfileImageConfig.SSH,
DependenciesArgsKeys: stage.GetDependenciesArgsKeys(dockerfileImageConfig.Dependencies),
})
if err != nil {
return nil, fmt.Errorf("unable to parse dockerfile %s: %w", relDockerfilePath, err)
@@ -101,10 +102,11 @@ func mapDockerfileToImagesSets(ctx context.Context, cfg *dockerfile.Dockerfile,
var err error
if baseStg := cfg.FindStage(stg.BaseName); baseStg != nil {
img, err = NewImage(ctx, item.WerfImageName, StageAsBaseImage, ImageOptions{
IsDockerfileImage: true,
DockerfileImageConfig: dockerfileImageConfig,
CommonImageOptions: opts,
BaseImageName: baseStg.WerfImageName(),
IsDockerfileImage: true,
DockerfileImageConfig: dockerfileImageConfig,
CommonImageOptions: opts,
BaseImageName: baseStg.WerfImageName(),
DockerfileExpanderFactory: stg.ExpanderFactory,
})
if err != nil {
return nil, fmt.Errorf("unable to map stage %s to werf image %q: %w", stg.LogName(), dockerfileImageConfig.Name, err)
@@ -113,13 +115,14 @@ func mapDockerfileToImagesSets(ctx context.Context, cfg *dockerfile.Dockerfile,
appendQueue(baseStg.WerfImageName(), baseStg, item.Level+1)
} else {
img, err = NewImage(ctx, item.WerfImageName, ImageFromRegistryAsBaseImage, ImageOptions{
IsDockerfileImage: true,
DockerfileImageConfig: dockerfileImageConfig,
CommonImageOptions: opts,
BaseImageReference: targetStage.BaseName,
IsDockerfileImage: true,
DockerfileImageConfig: dockerfileImageConfig,
CommonImageOptions: opts,
BaseImageReference: stg.BaseName,
DockerfileExpanderFactory: stg.ExpanderFactory,
})
if err != nil {
return nil, fmt.Errorf("unable to map stage %s to werf image %q: %w", targetStage.LogName(), dockerfileImageConfig.Name, err)
return nil, fmt.Errorf("unable to map stage %s to werf image %q: %w", stg.LogName(), dockerfileImageConfig.Name, err)
}
}

@@ -136,7 +139,6 @@ func mapDockerfileToImagesSets(ctx context.Context, cfg *dockerfile.Dockerfile,
var stg stage.Interface
switch typedInstr := any(instr).(type) {
case *dockerfile.DockerfileStageInstruction[*instructions.ArgCommand]:
// TODO(staged-dockerfile): support build-args at this level or dockerfile pkg level (?)
continue
case *dockerfile.DockerfileStageInstruction[*instructions.AddCommand]:
stg = stage_instruction.NewAdd(stageName, typedInstr, dockerfileImageConfig.Dependencies, !isFirstStage, baseStageOptions)
@@ -13,6 +13,7 @@ import (
"github.com/werf/werf/pkg/config"
"github.com/werf/werf/pkg/container_backend"
"github.com/werf/werf/pkg/docker_registry"
"github.com/werf/werf/pkg/dockerfile"
"github.com/werf/werf/pkg/giterminism_manager"
"github.com/werf/werf/pkg/image"
"github.com/werf/werf/pkg/logging"
@@ -43,9 +44,10 @@ type ImageOptions struct {
IsArtifact, IsDockerfileImage bool
DockerfileImageConfig *config.ImageFromDockerfile

BaseImageReference string
BaseImageName string
FetchLatestBaseImage bool
BaseImageReference string
BaseImageName string
FetchLatestBaseImage bool
DockerfileExpanderFactory dockerfile.ExpanderFactory
}

func NewImage(ctx context.Context, name string, baseImageType BaseImageType, opts ImageOptions) (*Image, error) {
@@ -62,9 +64,10 @@ func NewImage(ctx context.Context, name string, baseImageType BaseImageType, opt
IsDockerfileImage: opts.IsDockerfileImage,
DockerfileImageConfig: opts.DockerfileImageConfig,

baseImageType: baseImageType,
baseImageReference: opts.BaseImageReference,
baseImageName: opts.BaseImageName,
baseImageType: baseImageType,
baseImageReference: opts.BaseImageReference,
baseImageName: opts.BaseImageName,
dockerfileExpanderFactory: opts.DockerfileExpanderFactory,
}

if opts.FetchLatestBaseImage {
@@ -89,9 +92,10 @@ type Image struct {
contentDigest string
rebuilt bool

baseImageType BaseImageType
baseImageReference string
baseImageName string
baseImageType BaseImageType
baseImageReference string
baseImageName string
dockerfileExpanderFactory dockerfile.ExpanderFactory

baseImageRepoId string
baseStageImage *stage.StageImage
@@ -197,18 +201,28 @@ func (i *Image) GetRebuilt() bool {
return i.rebuilt
}

func (i *Image) SetupBaseImage() {
func (i *Image) SetupBaseImage() error {
switch i.baseImageType {
case StageAsBaseImage:
i.stageAsBaseImage = i.Conveyor.GetImage(i.baseImageName).GetLastNonEmptyStage()
i.baseImageReference = i.stageAsBaseImage.GetStageImage().Image.Name()
i.baseStageImage = i.stageAsBaseImage.GetStageImage()
case ImageFromRegistryAsBaseImage:
if i.IsDockerfileImage && i.dockerfileExpanderFactory != nil {
dependenciesArgs := stage.ResolveDependenciesArgs(i.DockerfileImageConfig.Dependencies, i.Conveyor)
ref, err := i.dockerfileExpanderFactory.GetExpander(dockerfile.ExpandOptions{SkipUnsetEnv: false}).ProcessWordWithMap(i.baseImageReference, dependenciesArgs)
if err != nil {
return fmt.Errorf("unable to expand dockerfile base image reference %q: %w", i.baseImageReference, err)
}
i.baseImageReference = ref
}
i.baseStageImage = i.Conveyor.GetOrCreateStageImage(i.baseImageReference, nil, nil, i)
case NoBaseImage:
default:
panic(fmt.Sprintf("unknown base image type %q", i.baseImageType))
}

return nil
}

// TODO(staged-dockerfile): this is only for compatibility with stapel-builder logic, and this should be unified with new staged-dockerfile logic
@@ -122,6 +122,10 @@ func (s *BaseStage) LogDetailedName() string {
return fmt.Sprintf("%s/%s", imageName, s.Name())
}

func (s *BaseStage) ImageName() string {
return s.imageName
}

func (s *BaseStage) Name() StageName {
if s.name != "" {
return s.name
@@ -0,0 +1,40 @@
package stage

import (
"github.com/werf/werf/pkg/config"
"github.com/werf/werf/pkg/image"
)

func GetDependenciesArgsKeys(dependencies []*config.Dependency) (res []string) {
for _, dep := range dependencies {
for _, imp := range dep.Imports {
res = append(res, imp.TargetBuildArg)
}
}
return
}

func ResolveDependenciesArgs(dependencies []*config.Dependency, c Conveyor) map[string]string {
resolved := make(map[string]string)

for _, dep := range dependencies {
depImageName := c.GetImageNameForLastImageStage(dep.ImageName)
depImageID := c.GetImageIDForLastImageStage(dep.ImageName)
depImageRepo, depImageTag := image.ParseRepositoryAndTag(depImageName)

for _, imp := range dep.Imports {
switch imp.Type {
case config.ImageRepoImport:
resolved[imp.TargetBuildArg] = depImageRepo
case config.ImageTagImport:
resolved[imp.TargetBuildArg] = depImageTag
case config.ImageNameImport:
resolved[imp.TargetBuildArg] = depImageName
case config.ImageIDImport:
resolved[imp.TargetBuildArg] = depImageID
}
}
}

return resolved
}
@@ -174,31 +174,6 @@ func (ds *DockerStages) resolveDockerMetaArg(key, value string, resolvedDockerMe
return resolvedKey, resolvedValue, err
}

func resolveDependenciesArgsHash(dependencies []*config.Dependency, c Conveyor) map[string]string {
resolved := make(map[string]string)

for _, dep := range dependencies {
depImageName := c.GetImageNameForLastImageStage(dep.ImageName)
depImageID := c.GetImageIDForLastImageStage(dep.ImageName)
depImageRepo, depImageTag := image.ParseRepositoryAndTag(depImageName)

for _, img := range dep.Imports {
switch img.Type {
case config.ImageRepoImport:
resolved[img.TargetBuildArg] = depImageRepo
case config.ImageTagImport:
resolved[img.TargetBuildArg] = depImageTag
case config.ImageNameImport:
resolved[img.TargetBuildArg] = depImageName
case config.ImageIDImport:
resolved[img.TargetBuildArg] = depImageID
}
}
}

return resolved
}

// resolveDockerStageArg function sets dependency arg value, or --build-arg value, or resolved dockerfile stage ARG value, or resolved meta ARG value (if stage ARG value is empty)
func (ds *DockerStages) resolveDockerStageArg(dockerStageID int, key, value string, resolvedDockerMetaArgsHash, resolvedDependenciesArgsHash map[string]string) (string, string, error) {
resolvedKey, err := ds.ShlexProcessWordWithStageArgsAndEnvs(dockerStageID, key)
@@ -320,7 +295,7 @@ type dockerfileInstructionInterface interface {
}

func (s *FullDockerfileStage) FetchDependencies(ctx context.Context, c Conveyor, containerBackend container_backend.ContainerBackend, dockerRegistry docker_registry.ApiInterface) error {
resolvedDependenciesArgsHash := resolveDependenciesArgsHash(s.dependencies, c)
resolvedDependenciesArgsHash := ResolveDependenciesArgs(s.dependencies, c)

resolvedDockerMetaArgsHash, err := s.resolveDockerMetaArgs(resolvedDependenciesArgsHash)
if err != nil {
@@ -412,7 +387,7 @@ func isUnsupportedMediaTypeError(err error) bool {
var errImageNotExistLocally = errors.New("IMAGE_NOT_EXIST_LOCALLY")

func (s *FullDockerfileStage) GetDependencies(ctx context.Context, c Conveyor, cb container_backend.ContainerBackend, prevImage, prevBuiltImage *StageImage, buildContextArchive container_backend.BuildContextArchiver) (string, error) {
resolvedDependenciesArgsHash := resolveDependenciesArgsHash(s.dependencies, c)
resolvedDependenciesArgsHash := ResolveDependenciesArgs(s.dependencies, c)

resolvedDockerMetaArgsHash, err := s.resolveDockerMetaArgs(resolvedDependenciesArgsHash)
if err != nil {
@@ -732,7 +707,7 @@ func (s *FullDockerfileStage) SetupDockerImageBuilder(b stage_builder.Dockerfile
}
}

resolvedDependenciesArgsHash := resolveDependenciesArgsHash(s.dependencies, c)
resolvedDependenciesArgsHash := ResolveDependenciesArgs(s.dependencies, c)
if len(resolvedDependenciesArgsHash) > 0 {
for key, value := range resolvedDependenciesArgsHash {
b.AppendBuildArgs(fmt.Sprintf("%s=%v", key, value))
@@ -54,7 +54,9 @@ func (stg *Base[T, BT]) PrepareImage(ctx context.Context, c stage.Conveyor, cb c
}

func (stg *Base[T, BT]) ExpandInstruction(ctx context.Context, c stage.Conveyor, cb container_backend.ContainerBackend, prevBuiltImage, stageImage *stage.StageImage, buildContextArchive container_backend.BuildContextArchiver) error {
return nil
dependenciesArgs := stage.ResolveDependenciesArgs(stg.dependencies, c)
// NOTE: do not skip unset envs during second stage expansion
return stg.instruction.Expand(dependenciesArgs, dockerfile.ExpandOptions{SkipUnsetEnv: false})
}

type InstructionExpander interface {
@@ -23,6 +23,10 @@ func NewCopy(name stage.StageName, i *dockerfile.DockerfileStageInstruction[*ins
}

func (stg *Copy) ExpandInstruction(ctx context.Context, c stage.Conveyor, cb container_backend.ContainerBackend, prevBuiltImage, stageImage *stage.StageImage, buildContextArchive container_backend.BuildContextArchiver) error {
if err := stg.Base.ExpandInstruction(ctx, c, cb, prevBuiltImage, stageImage, buildContextArchive); err != nil {
return err
}

if stg.instruction.Data.From != "" {
if ds := stg.instruction.GetDependencyByStageRef(stg.instruction.Data.From); ds != nil {
depStageImageName := c.GetImageNameForLastImageStage(ds.WerfImageName())
@@ -3,9 +3,11 @@ package instruction
import (
"context"
"fmt"
"strings"

"github.com/moby/buildkit/frontend/dockerfile/instructions"

"github.com/werf/logboek"
"github.com/werf/werf/pkg/buildah"
"github.com/werf/werf/pkg/container_backend"
)
@@ -55,6 +57,8 @@ func (i *Run) Apply(ctx context.Context, containerName string, drv buildah.Build
addCapabilities = []string{"all"}
}

logboek.Context(ctx).Default().LogF("$ %s\n", strings.Join(i.CmdLine, " "))

if err := drv.RunCommand(ctx, containerName, i.CmdLine, buildah.RunCommandOpts{
CommonOpts: drvOpts,
ContextDir: contextDir,
@@ -5,11 +5,12 @@ import (
)

type DockerfileOptions struct {
Target string
BuildArgs map[string]string
AddHost []string
Network string
SSH string
Target string
BuildArgs map[string]string
DependenciesArgsKeys []string
AddHost []string
Network string
SSH string
}

func NewDockerfile(stages []*DockerfileStage, opts DockerfileOptions) *Dockerfile {
@@ -8,14 +8,21 @@ import (
"github.com/moby/buildkit/frontend/dockerfile/instructions"
)

func NewDockerfileStage(index int, baseName, stageName string, instructions []DockerfileStageInstructionInterface, platform string) *DockerfileStage {
return &DockerfileStage{BaseName: baseName, StageName: stageName, Instructions: instructions, Platform: platform}
func NewDockerfileStage(index int, baseName, stageName string, instructions []DockerfileStageInstructionInterface, platform string, expanderFactory ExpanderFactory) *DockerfileStage {
return &DockerfileStage{
ExpanderFactory: expanderFactory,
BaseName: baseName,
StageName: stageName,
Instructions: instructions,
Platform: platform,
}
}

type DockerfileStage struct {
Dockerfile *Dockerfile
Dependencies []*DockerfileStage
BaseStage *DockerfileStage
Dockerfile *Dockerfile
Dependencies []*DockerfileStage
BaseStage *DockerfileStage
ExpanderFactory ExpanderFactory

BaseName string
Index int

0 comments on commit 8faf229

Please sign in to comment.