Skip to content

Commit

Permalink
feat(build, dockerfile, multiplatform): support automatic platform ARGs
Browse files Browse the repository at this point in the history
Added support for automatic platform ARGs that can be used when configuring images in the Dockerfile.

Supported ARGs:
- TARGETPLATFORM: platform of the build result. E.g., linux/amd64, linux/arm/v7, windows/amd64.
- TARGETOS: OS component of TARGETPLATFORM.
- TARGETARCH: architecture component of TARGETPLATFORM.
- TARGETVARIANT: variant component of TARGETPLATFORM.
- BUILDPLATFORM: platform of the node performing the build.
- BUILDOS: OS component of BUILDPLATFORM.
- BUILDARCH: architecture component of BUILDPLATFORM.
- BUILDVARIANT: variant component of BUILDPLATFORM.

Reference: https://docs.docker.com/reference/dockerfile/#automatic-platform-args-in-the-global-scope

This allows the use of platform arguments in the global scope of the Dockerfile for enhanced multi-platform image building.

Signed-off-by: Alexey Igrychev <alexey.igrychev@flant.com>
  • Loading branch information
alexey-igrychev committed Jun 7, 2024
1 parent cfcee71 commit fbca962
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 40 deletions.
1 change: 1 addition & 0 deletions pkg/build/image/dockerfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ func MapDockerfileConfigToImagesSets(ctx context.Context, dockerfileImageConfig

d, err := frontend.ParseDockerfileWithBuildkit(dockerfileID, dockerfileData, dockerfileImageConfig.Name, dockerfile.DockerfileOptions{
Target: dockerfileImageConfig.Target,
TargetPlatform: targetPlatform,
BuildArgs: util.MapStringInterfaceToMapStringString(dockerfileImageConfig.Args),
AddHost: dockerfileImageConfig.AddHost,
Network: dockerfileImageConfig.Network,
Expand Down
33 changes: 27 additions & 6 deletions pkg/build/stage/full_dockerfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"github.com/werf/werf/v2/pkg/config"
"github.com/werf/werf/v2/pkg/container_backend"
"github.com/werf/werf/v2/pkg/container_backend/stage_builder"
"github.com/werf/werf/v2/pkg/container_backend/thirdparty/platformutil"
"github.com/werf/werf/v2/pkg/context_manager"
"github.com/werf/werf/v2/pkg/docker_registry"
"github.com/werf/werf/v2/pkg/dockerfile"
Expand Down Expand Up @@ -297,9 +298,19 @@ type dockerfileInstructionInterface interface {
func (s *FullDockerfileStage) FetchDependencies(ctx context.Context, c Conveyor, containerBackend container_backend.ContainerBackend, dockerRegistry docker_registry.GenericApiInterface) error {
resolvedDependenciesArgsHash := ResolveDependenciesArgs(s.targetPlatform, s.dependencies, c)

resolvedDockerMetaArgsHash, err := s.resolveDockerMetaArgs(resolvedDependenciesArgsHash)
if err != nil {
return fmt.Errorf("unable to resolve docker meta args: %w", err)
var resolvedDockerMetaArgsHash map[string]string
{
metaArgs, err := s.resolveDockerMetaArgs(resolvedDependenciesArgsHash)
if err != nil {
return fmt.Errorf("unable to resolve docker meta args: %w", err)
}

platformMetaArgs, err := platformutil.GetPlatformMetaArgsMap(s.targetPlatform)
if err != nil {
return fmt.Errorf("unable to get platform args: %w", err)
}

resolvedDockerMetaArgsHash = util.MergeMaps(platformMetaArgs, metaArgs)
}

outerLoop:
Expand Down Expand Up @@ -389,9 +400,19 @@ 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 := ResolveDependenciesArgs(s.targetPlatform, s.dependencies, c)

resolvedDockerMetaArgsHash, err := s.resolveDockerMetaArgs(resolvedDependenciesArgsHash)
if err != nil {
return "", fmt.Errorf("unable to resolve docker meta args: %w", err)
var resolvedDockerMetaArgsHash map[string]string
{
metaArgs, err := s.resolveDockerMetaArgs(resolvedDependenciesArgsHash)
if err != nil {
return "", fmt.Errorf("unable to resolve docker meta args: %w", err)
}

platformMetaArgs, err := platformutil.GetPlatformMetaArgsMap(s.targetPlatform)
if err != nil {
return "", fmt.Errorf("unable to get platform args: %w", err)
}

resolvedDockerMetaArgsHash = util.MergeMaps(platformMetaArgs, metaArgs)
}

var stagesDependencies [][]string
Expand Down
31 changes: 31 additions & 0 deletions pkg/container_backend/thirdparty/platformutil/platformutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ package platformutil

import (
"fmt"

"github.com/containerd/containerd/platforms"
specs "github.com/opencontainers/image-spec/specs-go/v1"
)

func NormalizeUserParams(platformParams []string) ([]string, error) {
Expand All @@ -12,3 +15,31 @@ func NormalizeUserParams(platformParams []string) ([]string, error) {

return Format(Dedupe(specs)), nil
}

func GetPlatformMetaArgsMap(targetPlatform string) (map[string]string, error) {
var bp, tp specs.Platform
{
bp = platforms.DefaultSpec()
if targetPlatform != "" {
p, err := platforms.Parse(targetPlatform)
if err != nil {
return nil, err
}

tp = p
} else {
tp = bp
}
}

return map[string]string{
"BUILDPLATFORM": platforms.Format(bp),
"BUILDOS": bp.OS,
"BUILDARCH": bp.Architecture,
"BUILDVARIANT": bp.Variant,
"TARGETPLATFORM": platforms.Format(tp),
"TARGETOS": tp.OS,
"TARGETARCH": tp.Architecture,
"TARGETVARIANT": tp.Variant,
}, nil
}
1 change: 1 addition & 0 deletions pkg/dockerfile/dockerfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ type DockerfileOptions struct {
AddHost []string
Network string
SSH string
TargetPlatform string
}

func NewDockerfile(dockerfileID string, stages []*DockerfileStage, opts DockerfileOptions) *Dockerfile {
Expand Down
52 changes: 18 additions & 34 deletions pkg/dockerfile/frontend/buildkit_dockerfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ import (
"github.com/moby/buildkit/frontend/dockerfile/instructions"
"github.com/moby/buildkit/frontend/dockerfile/parser"

"github.com/werf/werf/v2/pkg/container_backend/thirdparty/platformutil"
"github.com/werf/werf/v2/pkg/dockerfile"
"github.com/werf/werf/v2/pkg/util"
)

func ParseDockerfileWithBuildkit(dockerfileID string, dockerfileBytes []byte, werfImageName string, opts dockerfile.DockerfileOptions) (*dockerfile.Dockerfile, error) {
Expand All @@ -25,7 +27,7 @@ func ParseDockerfileWithBuildkit(dockerfileID string, dockerfileBytes []byte, we

expanderFactory := NewShlexExpanderFactory(p.EscapeToken)

metaArgs, err := resolveMetaArgs(dockerMetaArgsCommands, opts.BuildArgs, opts.DependenciesArgsKeys, expanderFactory)
metaArgs, err := resolveMetaArgs(dockerMetaArgsCommands, opts.BuildArgs, opts.DependenciesArgsKeys, opts.TargetPlatform, expanderFactory)
if err != nil {
return nil, fmt.Errorf("unable to process meta args: %w", err)
}
Expand Down Expand Up @@ -245,17 +247,10 @@ func removeDependenciesArgs(args []instructions.KeyValuePairOptional, dependenci
return
}

func resolveMetaArgs(metaArgsCommands []instructions.ArgCommand, buildArgs map[string]string, dependenciesArgsKeys []string, expanderFactory *ShlexExpanderFactory) (map[string]string, error) {
func resolveMetaArgs(metaArgsCommands []instructions.ArgCommand, buildArgs map[string]string, dependenciesArgsKeys []string, targetPlatform string, expanderFactory *ShlexExpanderFactory) (map[string]string, error) {
var optMetaArgs []instructions.KeyValuePairOptional

// TODO(staged-dockerfile): need to support builtin BUILD* and TARGET* args

// platformOpt := buildPlatformOpt(&opt)
// optMetaArgs := getPlatformArgs(platformOpt)
// for i, arg := range optMetaArgs {
// optMetaArgs[i] = setKVValue(arg, opt.BuildArgs)
// }

// Resolve meta arg commands and build args.
for _, cmd := range metaArgsCommands {
for _, metaArg := range cmd.Args {
if isDependencyArg(metaArg.Key, dependenciesArgsKeys) {
Expand All @@ -269,7 +264,19 @@ func resolveMetaArgs(metaArgsCommands []instructions.ArgCommand, buildArgs map[s
}
}

return metaArgsToMap(optMetaArgs), nil
// Merge with automatic meta args.
var metaArgsMap map[string]string
{
optMetaArgsMap := metaArgsToMap(optMetaArgs)
platformMetaArgsMap, err := platformutil.GetPlatformMetaArgsMap(targetPlatform)
if err != nil {
return nil, fmt.Errorf("unable to get platform args: %w", err)
}

metaArgsMap = util.MergeMaps(platformMetaArgsMap, optMetaArgsMap)
}

return metaArgsMap, nil
}

func metaArgsToMap(metaArgs []instructions.KeyValuePairOptional) map[string]string {
Expand All @@ -287,29 +294,6 @@ func setKVValue(kvpo instructions.KeyValuePairOptional, values map[string]string
return kvpo
}

// TODO(staged-dockerfile)
//
// func getPlatformArgs(po *platformOpt) []instructions.KeyValuePairOptional {
// bp := po.buildPlatforms[0]
// tp := po.targetPlatform
// m := map[string]string{
// "BUILDPLATFORM": platforms.Format(bp),
// "BUILDOS": bp.OS,
// "BUILDARCH": bp.Architecture,
// "BUILDVARIANT": bp.Variant,
// "TARGETPLATFORM": platforms.Format(tp),
// "TARGETOS": tp.OS,
// "TARGETARCH": tp.Architecture,
// "TARGETVARIANT": tp.Variant,
// }
// opts := make([]instructions.KeyValuePairOptional, 0, len(m))
// for k, v := range m {
// s := v
// opts = append(opts, instructions.KeyValuePairOptional{Key: k, Value: &s})
// }
// return opts
// }

func GetDockerStagesNameToIndexMap(stages []instructions.Stage) map[string]int {
nameToIndex := make(map[string]int)
for i, s := range stages {
Expand Down

0 comments on commit fbca962

Please sign in to comment.