Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
feat(multiarch): add support for target platform in container backends
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.