Skip to content

Commit

Permalink
feat(multiarch): add support for target platform in container backends
Browse files Browse the repository at this point in the history
Build each image for specified platforms list using actual emulated builder in container backend.

Signed-off-by: Timofey Kirillov <timofey.kirillov@flant.com>
  • Loading branch information
distorhead committed Mar 21, 2023
1 parent d430d7c commit 22ae3cf
Show file tree
Hide file tree
Showing 31 changed files with 521 additions and 359 deletions.
17 changes: 7 additions & 10 deletions cmd/werf/ci_env/ci_env.go
Expand Up @@ -18,7 +18,6 @@ import (
"github.com/werf/logboek"
"github.com/werf/logboek/pkg/level"
"github.com/werf/werf/cmd/werf/common"
"github.com/werf/werf/pkg/container_backend/thirdparty/platformutil"
"github.com/werf/werf/pkg/docker"
"github.com/werf/werf/pkg/docker_registry"
"github.com/werf/werf/pkg/git_repo"
Expand Down Expand Up @@ -118,17 +117,15 @@ func runCIEnv(cmd *cobra.Command, args []string) error {
return err
}

var platform string
if len(commonCmdData.GetPlatform()) > 0 {
platforms, err := platformutil.NormalizeUserParams(commonCmdData.GetPlatform())
if err != nil {
return fmt.Errorf("unable to normalize platforms params %v: %w", commonCmdData.GetPlatform(), err)
}
platform = platforms[0]
}
// FIXME(multiarch): do not initialize platform in backend here
// FIXME(multiarch): why docker initialization here? what if buildah backend enabled?
if err := docker.Init(ctx, dockerConfig, *commonCmdData.LogVerbose, *commonCmdData.LogDebug, platform); err != nil {
opts := docker.InitOptions{
DockerConfigDir: dockerConfig,
ClaimPlatforms: commonCmdData.GetPlatform(),
Verbose: *commonCmdData.LogVerbose,
Debug: *commonCmdData.LogDebug,
}
if err := docker.Init(ctx, opts); err != nil {
return fmt.Errorf("docker init failed in dir %q: %w", dockerConfig, err)
}

Expand Down
24 changes: 7 additions & 17 deletions cmd/werf/common/container_backend.go
Expand Up @@ -10,7 +10,6 @@ import (
"github.com/werf/werf/pkg/buildah"
"github.com/werf/werf/pkg/buildah/thirdparty"
"github.com/werf/werf/pkg/container_backend"
"github.com/werf/werf/pkg/container_backend/thirdparty/platformutil"
"github.com/werf/werf/pkg/docker"
"github.com/werf/werf/pkg/util"
"github.com/werf/werf/pkg/werf"
Expand Down Expand Up @@ -100,24 +99,14 @@ func InitProcessContainerBackend(ctx context.Context, cmdData *CmdData) (contain

insecure := *cmdData.InsecureRegistry || *cmdData.SkipTlsVerifyRegistry

// FIXME(multiarch): rework container backend platform initialization, specify platform only when running build operation
var platform string
if len(cmdData.GetPlatform()) > 0 {
platforms, err := platformutil.NormalizeUserParams(cmdData.GetPlatform())
if err != nil {
return nil, ctx, fmt.Errorf("unable to normalize platforms params %v: %w", cmdData.GetPlatform(), err)
}
platform = platforms[0]
}

b, err := buildah.NewBuildah(*buildahMode, buildah.BuildahOpts{
CommonBuildahOpts: buildah.CommonBuildahOpts{
TmpDir: filepath.Join(werf.GetServiceDir(), "tmp", "buildah"),
Insecure: insecure,
Isolation: buildahIsolation,
StorageDriver: storageDriver,
},
NativeModeOpts: buildah.NativeModeOpts{Platform: platform},
NativeModeOpts: buildah.NativeModeOpts{},
})
if err != nil {
return nil, ctx, fmt.Errorf("unable to get buildah client: %w", err)
Expand All @@ -136,13 +125,14 @@ func InitProcessContainerBackend(ctx context.Context, cmdData *CmdData) (contain
}

func InitProcessDocker(ctx context.Context, cmdData *CmdData) (context.Context, error) {
// FIXME(multiarch): rework container backend platform initialization, specify platform only when running build operation
var platform string
if len(cmdData.GetPlatform()) > 0 {
platform = cmdData.GetPlatform()[0]
opts := docker.InitOptions{
DockerConfigDir: *cmdData.DockerConfig,
ClaimPlatforms: cmdData.GetPlatform(),
Verbose: *cmdData.LogVerbose,
Debug: *cmdData.LogDebug,
}

if err := docker.Init(ctx, *cmdData.DockerConfig, *cmdData.LogVerbose, *cmdData.LogDebug, platform); err != nil {
if err := docker.Init(ctx, opts); err != nil {
return ctx, fmt.Errorf("unable to init docker for buildah container backend: %w", err)
}

Expand Down
12 changes: 10 additions & 2 deletions cmd/werf/kube_run/kube_run.go
Expand Up @@ -416,13 +416,21 @@ func run(ctx context.Context, pod, secret, namespace string, werfConfig *config.
}
}

targetPlatforms, err := c.GetTargetPlatforms()
if err != nil {
return fmt.Errorf("invalid target platforms: %w", err)
}
if len(targetPlatforms) == 0 {
targetPlatforms = []string{containerBackend.GetDefaultPlatform()}
}

// FIXME(multiarch): specify multiarch manifest here
if err := c.FetchLastImageStage(ctx, "", imageName); err != nil {
if err := c.FetchLastImageStage(ctx, targetPlatforms[0], imageName); err != nil {
return err
}

// FIXME(multiarch): specify multiarch manifest here
image = c.GetImageNameForLastImageStage("", imageName)
image = c.GetImageNameForLastImageStage(targetPlatforms[0], imageName)
return nil
}); err != nil {
return err
Expand Down
12 changes: 10 additions & 2 deletions cmd/werf/run/run.go
Expand Up @@ -402,11 +402,19 @@ func run(ctx context.Context, containerBackend container_backend.ContainerBacken
}
}

if err := c.FetchLastImageStage(ctx, "", imageName); err != nil {
targetPlatforms, err := c.GetTargetPlatforms()
if err != nil {
return fmt.Errorf("invalid target platforms: %w", err)
}
if len(targetPlatforms) == 0 {
targetPlatforms = []string{containerBackend.GetDefaultPlatform()}
}

if err := c.FetchLastImageStage(ctx, targetPlatforms[0], imageName); err != nil {
return err
}

dockerImageName = c.GetImageNameForLastImageStage("", imageName)
dockerImageName = c.GetImageNameForLastImageStage(targetPlatforms[0], imageName)
return nil
}); err != nil {
return err
Expand Down
6 changes: 4 additions & 2 deletions pkg/build/build_phase.go
Expand Up @@ -531,7 +531,7 @@ func (phase *BuildPhase) findAndFetchStageFromSecondaryStagesStorage(ctx context
err := logboek.Context(ctx).Default().LogProcess("Copy suitable stage from secondary %s", secondaryStagesStorage.String()).DoError(func() error {
// Copy suitable stage from a secondary stages storage to the primary stages storage
// while primary stages storage lock for this digest is held
if copiedStageDesc, err := storageManager.CopySuitableByDigestStage(ctx, secondaryStageDesc, secondaryStagesStorage, storageManager.GetStagesStorage(), phase.Conveyor.ContainerBackend); err != nil {
if copiedStageDesc, err := storageManager.CopySuitableByDigestStage(ctx, secondaryStageDesc, secondaryStagesStorage, storageManager.GetStagesStorage(), phase.Conveyor.ContainerBackend, img.TargetPlatform); err != nil {
return fmt.Errorf("unable to copy suitable stage %s from %s to %s: %w", secondaryStageDesc.StageID.String(), secondaryStagesStorage.String(), storageManager.GetStagesStorage().String(), err)
} else {
i := phase.Conveyor.GetOrCreateStageImage(copiedStageDesc.Info.Name, phase.StagesIterator.GetPrevImage(img, stg), stg, img)
Expand Down Expand Up @@ -783,7 +783,9 @@ func (phase *BuildPhase) atomicBuildStageImage(ctx context.Context, img *image.I
}

if err := logboek.Context(ctx).Streams().DoErrorWithTag(fmt.Sprintf("%s/%s", img.LogName(), stg.Name()), img.LogTagStyle(), func() error {
return stageImage.Builder.Build(ctx, phase.ImageBuildOptions)
opts := phase.ImageBuildOptions
opts.TargetPlatform = img.TargetPlatform
return stageImage.Builder.Build(ctx, opts)
}); err != nil {
return fmt.Errorf("failed to build image for stage %s with digest %s: %w", stg.Name(), stg.GetDigest(), err)
}
Expand Down
18 changes: 12 additions & 6 deletions pkg/build/conveyor.go
Expand Up @@ -223,10 +223,12 @@ func (c *Conveyor) GetImportServer(ctx context.Context, targetPlatform, imageNam
if stageName != "" {
importServerName += "/" + stageName
}
// FIXME(multiarch): in this place we should get our current platform from the container backend in the case when targetPlatform is empty
if targetPlatform != "" && targetPlatform != "linux/amd64" {
importServerName += "[" + targetPlatform + "]"

if targetPlatform == "" {
panic("assertion: targetPlatform cannot be empty")
}
importServerName += fmt.Sprintf("[%s]", targetPlatform)

if srv, hasKey := c.importServers[importServerName]; hasKey {
return srv, nil
}
Expand All @@ -249,9 +251,9 @@ func (c *Conveyor) GetImportServer(ctx context.Context, targetPlatform, imageNam
DoError(func() error {
var tmpDir string
if stageName == "" {
tmpDir = filepath.Join(c.tmpDir, "import-server", imageName)
tmpDir = filepath.Join(c.tmpDir, "import-server", imageName, targetPlatform)
} else {
tmpDir = filepath.Join(c.tmpDir, "import-server", fmt.Sprintf("%s-%s", imageName, stageName))
tmpDir = filepath.Join(c.tmpDir, "import-server", fmt.Sprintf("%s-%s", imageName, stageName), targetPlatform)
}

if err := os.MkdirAll(tmpDir, os.ModePerm); err != nil {
Expand Down Expand Up @@ -682,7 +684,7 @@ func (c *Conveyor) GetOrCreateStageImage(name string, prevStageImage *stage.Stag
return stageImage
}

i := container_backend.NewLegacyStageImage(extractLegacyStageImage(prevStageImage), name, c.ContainerBackend)
i := container_backend.NewLegacyStageImage(extractLegacyStageImage(prevStageImage), name, c.ContainerBackend, img.TargetPlatform)

var baseImage string
if stg != nil {
Expand All @@ -701,6 +703,10 @@ func (c *Conveyor) GetOrCreateStageImage(name string, prevStageImage *stage.Stag
}

func (c *Conveyor) GetImage(targetPlatform, name string) *image.Image {
if targetPlatform == "" {
panic("assertion: targetPlatform should not be empty")
}

for _, img := range c.imagesTree.GetImages() {
if img.GetName() == name && img.TargetPlatform == targetPlatform {
return img
Expand Down
18 changes: 9 additions & 9 deletions pkg/build/image/dockerfile.go
Expand Up @@ -22,7 +22,7 @@ import (
"github.com/werf/werf/pkg/util"
)

func MapDockerfileConfigToImagesSets(ctx context.Context, dockerfileImageConfig *config.ImageFromDockerfile, opts CommonImageOptions) (ImagesSets, error) {
func MapDockerfileConfigToImagesSets(ctx context.Context, dockerfileImageConfig *config.ImageFromDockerfile, targetPlatform string, opts CommonImageOptions) (ImagesSets, error) {
if dockerfileImageConfig.Staged {
relDockerfilePath := filepath.Join(dockerfileImageConfig.Context, dockerfileImageConfig.Dockerfile)
dockerfileData, err := opts.GiterminismManager.FileReader().ReadDockerfile(ctx, relDockerfilePath)
Expand All @@ -44,10 +44,10 @@ func MapDockerfileConfigToImagesSets(ctx context.Context, dockerfileImageConfig
return nil, fmt.Errorf("unable to parse dockerfile %s: %w", relDockerfilePath, err)
}

return mapDockerfileToImagesSets(ctx, d, dockerfileImageConfig, opts)
return mapDockerfileToImagesSets(ctx, d, dockerfileImageConfig, targetPlatform, opts)
}

img, err := mapLegacyDockerfileToImage(ctx, dockerfileImageConfig, opts)
img, err := mapLegacyDockerfileToImage(ctx, dockerfileImageConfig, targetPlatform, opts)
if err != nil {
return nil, err
}
Expand All @@ -59,7 +59,7 @@ func MapDockerfileConfigToImagesSets(ctx context.Context, dockerfileImageConfig
return ret, nil
}

func mapDockerfileToImagesSets(ctx context.Context, cfg *dockerfile.Dockerfile, dockerfileImageConfig *config.ImageFromDockerfile, opts CommonImageOptions) (ImagesSets, error) {
func mapDockerfileToImagesSets(ctx context.Context, cfg *dockerfile.Dockerfile, dockerfileImageConfig *config.ImageFromDockerfile, targetPlatform string, opts CommonImageOptions) (ImagesSets, error) {
var ret ImagesSets

targetStage, err := cfg.GetTargetStage()
Expand Down Expand Up @@ -106,7 +106,7 @@ func mapDockerfileToImagesSets(ctx context.Context, cfg *dockerfile.Dockerfile,
var img *Image
var err error
if baseStg := cfg.FindStage(stg.BaseName); baseStg != nil {
img, err = NewImage(ctx, item.WerfImageName, StageAsBaseImage, ImageOptions{
img, err = NewImage(ctx, targetPlatform, item.WerfImageName, StageAsBaseImage, ImageOptions{
IsDockerfileImage: true,
IsDockerfileTargetStage: item.IsTargetStage,
DockerfileImageConfig: dockerfileImageConfig,
Expand All @@ -120,7 +120,7 @@ func mapDockerfileToImagesSets(ctx context.Context, cfg *dockerfile.Dockerfile,

appendQueue(baseStg.GetWerfImageName(), baseStg, item.Level+1)
} else {
img, err = NewImage(ctx, item.WerfImageName, ImageFromRegistryAsBaseImage, ImageOptions{
img, err = NewImage(ctx, targetPlatform, item.WerfImageName, ImageFromRegistryAsBaseImage, ImageOptions{
IsDockerfileImage: true,
IsDockerfileTargetStage: item.IsTargetStage,
DockerfileImageConfig: dockerfileImageConfig,
Expand Down Expand Up @@ -198,8 +198,8 @@ func mapDockerfileToImagesSets(ctx context.Context, cfg *dockerfile.Dockerfile,
return ret, nil
}

func mapLegacyDockerfileToImage(ctx context.Context, dockerfileImageConfig *config.ImageFromDockerfile, opts CommonImageOptions) (*Image, error) {
img, err := NewImage(ctx, dockerfileImageConfig.Name, NoBaseImage, ImageOptions{
func mapLegacyDockerfileToImage(ctx context.Context, dockerfileImageConfig *config.ImageFromDockerfile, targetPlatform string, opts CommonImageOptions) (*Image, error) {
img, err := NewImage(ctx, targetPlatform, dockerfileImageConfig.Name, NoBaseImage, ImageOptions{
CommonImageOptions: opts,
IsDockerfileImage: true,
IsDockerfileTargetStage: true,
Expand Down Expand Up @@ -258,7 +258,7 @@ func mapLegacyDockerfileToImage(ctx context.Context, dockerfileImageConfig *conf
)

baseStageOptions := &stage.BaseStageOptions{
TargetPlatform: opts.TargetPlatform,
TargetPlatform: targetPlatform,
ImageName: dockerfileImageConfig.Name,
ProjectName: opts.ProjectName,
}
Expand Down
17 changes: 13 additions & 4 deletions pkg/build/image/image.go
Expand Up @@ -38,7 +38,8 @@ type CommonImageOptions struct {
ProjectName string
ContainerWerfDir string
TmpDir string
TargetPlatform string

ForceTargetPlatformLogging bool
}

type ImageOptions struct {
Expand All @@ -52,21 +53,25 @@ type ImageOptions struct {
DockerfileExpanderFactory dockerfile.ExpanderFactory
}

func NewImage(ctx context.Context, name string, baseImageType BaseImageType, opts ImageOptions) (*Image, error) {
func NewImage(ctx context.Context, targetPlatform, name string, baseImageType BaseImageType, opts ImageOptions) (*Image, error) {
switch baseImageType {
case NoBaseImage, ImageFromRegistryAsBaseImage, StageAsBaseImage:
default:
panic(fmt.Sprintf("unknown opts.BaseImageType %q", baseImageType))
}

if targetPlatform == "" {
panic("assertion: targetPlatform cannot be empty")
}

i := &Image{
Name: name,
CommonImageOptions: opts.CommonImageOptions,
IsArtifact: opts.IsArtifact,
IsDockerfileImage: opts.IsDockerfileImage,
IsDockerfileTargetStage: opts.IsDockerfileTargetStage,
DockerfileImageConfig: opts.DockerfileImageConfig,
TargetPlatform: opts.TargetPlatform,
TargetPlatform: targetPlatform,

baseImageType: baseImageType,
baseImageReference: opts.BaseImageReference,
Expand Down Expand Up @@ -113,7 +118,11 @@ func (i *Image) LogName() string {
}

func (i *Image) LogDetailedName() string {
return logging.ImageLogProcessName(i.Name, i.IsArtifact, i.TargetPlatform)
var targetPlatformForLog string
if i.ForceTargetPlatformLogging || i.TargetPlatform != i.ContainerBackend.GetRuntimePlatform() {
targetPlatformForLog = i.TargetPlatform
}
return logging.ImageLogProcessName(i.Name, i.IsArtifact, targetPlatformForLog)
}

func (i *Image) LogProcessStyle() color.Style {
Expand Down
12 changes: 6 additions & 6 deletions pkg/build/image/image_tree.go
Expand Up @@ -52,9 +52,12 @@ func (tree *ImagesTree) Calculate(ctx context.Context) error {
return fmt.Errorf("invalid target platforms: %w", err)
}
if len(targetPlatforms) == 0 {
targetPlatforms = []string{""}
targetPlatforms = []string{tree.ContainerBackend.GetDefaultPlatform()}
}

commonImageOpts := tree.CommonImageOptions
commonImageOpts.ForceTargetPlatformLogging = (len(targetPlatforms) > 1)

builder := NewImagesSetsBuilder()

for _, iteration := range imageConfigSets {
Expand All @@ -80,18 +83,15 @@ func (tree *ImagesTree) Calculate(ctx context.Context) error {
var err error
var newImagesSets ImagesSets

commonOpts := tree.CommonImageOptions
commonOpts.TargetPlatform = targetPlatform

switch imageConfig := imageConfigI.(type) {
case config.StapelImageInterface:
newImagesSets, err = MapStapelConfigToImagesSets(ctx, tree.werfConfig.Meta, imageConfig, commonOpts)
newImagesSets, err = MapStapelConfigToImagesSets(ctx, tree.werfConfig.Meta, imageConfig, targetPlatform, commonImageOpts)
if err != nil {
return fmt.Errorf("unable to map stapel config to images sets: %w", err)
}

case *config.ImageFromDockerfile:
newImagesSets, err = MapDockerfileConfigToImagesSets(ctx, imageConfig, commonOpts)
newImagesSets, err = MapDockerfileConfigToImagesSets(ctx, imageConfig, targetPlatform, commonImageOpts)
if err != nil {
return fmt.Errorf("unable to map dockerfile to images sets: %w", err)
}
Expand Down
8 changes: 4 additions & 4 deletions pkg/build/image/stapel.go
Expand Up @@ -12,8 +12,8 @@ import (
"github.com/werf/werf/pkg/git_repo"
)

func MapStapelConfigToImagesSets(ctx context.Context, metaConfig *config.Meta, stapelImageConfig config.StapelImageInterface, opts CommonImageOptions) (ImagesSets, error) {
img, err := mapStapelConfigToImage(ctx, metaConfig, stapelImageConfig, opts)
func MapStapelConfigToImagesSets(ctx context.Context, metaConfig *config.Meta, stapelImageConfig config.StapelImageInterface, targetPlatform string, opts CommonImageOptions) (ImagesSets, error) {
img, err := mapStapelConfigToImage(ctx, metaConfig, stapelImageConfig, targetPlatform, opts)
if err != nil {
return nil, err
}
Expand All @@ -25,7 +25,7 @@ func MapStapelConfigToImagesSets(ctx context.Context, metaConfig *config.Meta, s
return ret, nil
}

func mapStapelConfigToImage(ctx context.Context, metaConfig *config.Meta, stapelImageConfig config.StapelImageInterface, opts CommonImageOptions) (*Image, error) {
func mapStapelConfigToImage(ctx context.Context, metaConfig *config.Meta, stapelImageConfig config.StapelImageInterface, targetPlatform string, opts CommonImageOptions) (*Image, error) {
imageBaseConfig := stapelImageConfig.ImageBaseConfig()
imageName := imageBaseConfig.Name
imageArtifact := stapelImageConfig.IsArtifact()
Expand All @@ -46,7 +46,7 @@ func mapStapelConfigToImage(ctx context.Context, metaConfig *config.Meta, stapel
imageOpts.BaseImageName = fromImageName
}

image, err := NewImage(ctx, imageName, baseImageType, imageOpts)
image, err := NewImage(ctx, targetPlatform, imageName, baseImageType, imageOpts)
if err != nil {
return nil, fmt.Errorf("unable to create image %q: %w", imageName, err)
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/build/stage/dependencies.go
Expand Up @@ -324,7 +324,7 @@ func (s *DependenciesStage) generateImportChecksum(ctx context.Context, c Convey
ExcludePaths: importElm.ExcludePaths,
Owner: importElm.Owner,
Group: importElm.Group,
})
}, container_backend.CalculateDependencyImportChecksum{TargetPlatform: s.targetPlatform})
})

if err != nil {
Expand Down

0 comments on commit 22ae3cf

Please sign in to comment.