Skip to content

Commit 39fd752

Browse files
committed
feat(multiarch): support platform setting per image in werf.yaml configuration
Signed-off-by: Timofey Kirillov <timofey.kirillov@flant.com>
1 parent e9c5727 commit 39fd752

File tree

9 files changed

+150
-110
lines changed

9 files changed

+150
-110
lines changed

pkg/build/build_phase.go

Lines changed: 54 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -187,18 +187,41 @@ func (phase *BuildPhase) BeforeImages(ctx context.Context) error {
187187
}
188188

189189
func (phase *BuildPhase) AfterImages(ctx context.Context) error {
190-
targetPlatforms, err := phase.Conveyor.GetTargetPlatforms()
190+
forcedTargetPlatforms := phase.Conveyor.GetForcedTargetPlatforms()
191+
commonTargetPlatforms, err := phase.Conveyor.GetTargetPlatforms()
191192
if err != nil {
192-
return fmt.Errorf("unable to get target platforms: %w", err)
193+
return fmt.Errorf("invalid common target platforms: %w", err)
193194
}
194-
if len(targetPlatforms) == 0 {
195-
targetPlatforms = []string{phase.Conveyor.ContainerBackend.GetDefaultPlatform()}
195+
if len(commonTargetPlatforms) == 0 {
196+
commonTargetPlatforms = []string{phase.Conveyor.ContainerBackend.GetDefaultPlatform()}
196197
}
197198

198199
for _, desc := range phase.Conveyor.imagesTree.GetImagesByName(false) {
199200
name, images := desc.Unpair()
200201
platforms := util.MapFuncToSlice(images, func(img *image.Image) string { return img.TargetPlatform })
201202

203+
// TODO: this target platforms assertion could be removed in future versions and now exists only as a additional self-testing code
204+
var targetPlatforms []string
205+
if len(forcedTargetPlatforms) > 0 {
206+
targetPlatforms = forcedTargetPlatforms
207+
} else {
208+
targetName := name
209+
nameParts := strings.SplitN(name, "/", 3)
210+
if len(nameParts) == 3 && nameParts[1] == "stage" {
211+
targetName = nameParts[0]
212+
}
213+
214+
imageTargetPlatforms, err := phase.Conveyor.GetImageTargetPlatforms(targetName)
215+
if err != nil {
216+
return fmt.Errorf("invalid image %q target platforms: %w", name, err)
217+
}
218+
if len(imageTargetPlatforms) > 0 {
219+
targetPlatforms = imageTargetPlatforms
220+
} else {
221+
targetPlatforms = commonTargetPlatforms
222+
}
223+
}
224+
202225
AssertAllTargetPlatformsPresent:
203226
for _, targetPlatform := range targetPlatforms {
204227
for _, platform := range platforms {
@@ -426,49 +449,38 @@ func (phase *BuildPhase) publishMultiplatformImageMetadata(ctx context.Context,
426449
}
427450

428451
func (phase *BuildPhase) createReport(ctx context.Context) error {
429-
targetPlatforms, err := phase.Conveyor.GetTargetPlatforms()
430-
if err != nil {
431-
return fmt.Errorf("invalid target platforms: %w", err)
432-
}
433-
if len(targetPlatforms) == 0 {
434-
targetPlatforms = []string{phase.Conveyor.ContainerBackend.GetDefaultPlatform()}
435-
}
436-
437-
for _, img := range phase.Conveyor.imagesTree.GetImages() {
438-
if !img.IsFinal() {
439-
continue
440-
}
452+
for _, desc := range phase.Conveyor.imagesTree.GetImagesByName(true) {
453+
name, images := desc.Unpair()
454+
targetPlatforms := util.MapFuncToSlice(images, func(img *image.Image) string { return img.TargetPlatform })
441455

442-
stageImage := img.GetLastNonEmptyStage().GetStageImage().Image
443-
desc := stageImage.GetFinalStageDescription()
444-
if desc == nil {
445-
desc = stageImage.GetStageDescription()
446-
}
456+
for _, img := range images {
457+
stageImage := img.GetLastNonEmptyStage().GetStageImage().Image
458+
desc := stageImage.GetFinalStageDescription()
459+
if desc == nil {
460+
desc = stageImage.GetStageDescription()
461+
}
447462

448-
record := ReportImageRecord{
449-
WerfImageName: img.GetName(),
450-
DockerRepo: desc.Info.Repository,
451-
DockerTag: desc.Info.Tag,
452-
DockerImageID: desc.Info.ID,
453-
DockerImageDigest: desc.Info.RepoDigest,
454-
DockerImageName: desc.Info.Name,
455-
Rebuilt: img.GetRebuilt(),
456-
}
463+
record := ReportImageRecord{
464+
WerfImageName: img.GetName(),
465+
DockerRepo: desc.Info.Repository,
466+
DockerTag: desc.Info.Tag,
467+
DockerImageID: desc.Info.ID,
468+
DockerImageDigest: desc.Info.RepoDigest,
469+
DockerImageName: desc.Info.Name,
470+
Rebuilt: img.GetRebuilt(),
471+
}
457472

458-
if os.Getenv("WERF_ENABLE_REPORT_BY_PLATFORM") == "1" {
459-
phase.ImagesReport.SetImageByPlatformRecord(img.TargetPlatform, img.GetName(), record)
460-
}
461-
if len(targetPlatforms) == 1 {
462-
phase.ImagesReport.SetImageRecord(img.Name, record)
473+
if os.Getenv("WERF_ENABLE_REPORT_BY_PLATFORM") == "1" {
474+
phase.ImagesReport.SetImageByPlatformRecord(img.TargetPlatform, img.GetName(), record)
475+
}
476+
if len(targetPlatforms) == 1 {
477+
phase.ImagesReport.SetImageRecord(img.Name, record)
478+
}
463479
}
464-
}
465480

466-
if _, isLocal := phase.Conveyor.StorageManager.GetStagesStorage().(*storage.LocalStagesStorage); !isLocal {
467-
if len(targetPlatforms) > 1 {
468-
for _, img := range phase.Conveyor.imagesTree.GetMultiplatformImages() {
469-
if !img.IsFinal() {
470-
continue
471-
}
481+
if _, isLocal := phase.Conveyor.StorageManager.GetStagesStorage().(*storage.LocalStagesStorage); !isLocal {
482+
if len(targetPlatforms) > 1 {
483+
img := phase.Conveyor.imagesTree.GetMultiplatformImage(name)
472484

473485
isRebuilt := false
474486
for _, pImg := range img.Images {

pkg/build/conveyor.go

Lines changed: 42 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -121,23 +121,44 @@ func NewConveyor(werfConfig *config.WerfConfig, giterminismManager giterminism_m
121121
return c
122122
}
123123

124-
func (c *Conveyor) GetTargetPlatforms() ([]string, error) {
125-
if len(c.ConveyorOptions.TargetPlatforms) > 0 {
126-
return c.ConveyorOptions.TargetPlatforms, nil
127-
}
128-
129-
for _, p := range c.werfConfig.Meta.Build.Platform {
124+
func validatePlatforms(platforms []string) error {
125+
for _, p := range platforms {
130126
parts := strings.Split(p, ",")
131127
if len(parts) > 1 {
132-
return nil, fmt.Errorf("invalid platform specified %q: specify multiple platforms using yaml array", p)
128+
return fmt.Errorf("invalid platform specified %q: specify multiple platforms using yaml array", p)
133129
}
134130
}
131+
return nil
132+
}
135133

136-
platforms, err := platformutil.NormalizeUserParams(c.werfConfig.Meta.Build.Platform)
134+
func prepareConfigurationPlatforms(platforms []string) ([]string, error) {
135+
if err := validatePlatforms(platforms); err != nil {
136+
return nil, fmt.Errorf("unable to validate platforms: %w", err)
137+
}
138+
res, err := platformutil.NormalizeUserParams(platforms)
137139
if err != nil {
138-
return nil, fmt.Errorf("unable to normalize platforms specified in the werf.yaml %v: %w", c.werfConfig.Meta.Build.Platform, err)
140+
return nil, fmt.Errorf("unable to normalize platforms specified in the werf.yaml %v: %w", platforms, err)
141+
}
142+
return res, nil
143+
}
144+
145+
func (c *Conveyor) GetImageTargetPlatforms(targetImageName string) ([]string, error) {
146+
if img := c.werfConfig.GetStapelImage(targetImageName); img != nil {
147+
return prepareConfigurationPlatforms(img.Platform)
148+
} else if img := c.werfConfig.GetArtifact(targetImageName); img != nil {
149+
return prepareConfigurationPlatforms(img.Platform)
150+
} else if img := c.werfConfig.GetDockerfileImage(targetImageName); img != nil {
151+
return prepareConfigurationPlatforms(img.Platform)
139152
}
140-
return platforms, nil
153+
return nil, nil
154+
}
155+
156+
func (c *Conveyor) GetForcedTargetPlatforms() []string {
157+
return c.ConveyorOptions.TargetPlatforms
158+
}
159+
160+
func (c *Conveyor) GetTargetPlatforms() ([]string, error) {
161+
return prepareConfigurationPlatforms(c.werfConfig.Meta.Build.Platform)
141162
}
142163

143164
func (c *Conveyor) GetServiceRWMutex(service string) *sync.RWMutex {
@@ -383,41 +404,26 @@ func (c *Conveyor) FetchLastImageStage(ctx context.Context, targetPlatform, imag
383404
}
384405

385406
func (c *Conveyor) GetImageInfoGetters(opts imagePkg.InfoGetterOptions) ([]*imagePkg.InfoGetter, error) {
386-
targetPlatforms, err := c.GetTargetPlatforms()
387-
if err != nil {
388-
return nil, fmt.Errorf("unable to get target platforms: %w", err)
389-
}
390-
if len(targetPlatforms) == 0 {
391-
targetPlatforms = []string{c.ContainerBackend.GetDefaultPlatform()}
392-
}
407+
var imagesGetters []*imagePkg.InfoGetter
408+
for _, desc := range c.imagesTree.GetImagesByName(true) {
409+
name, images := desc.Unpair()
410+
platforms := util.MapFuncToSlice(images, func(img *image.Image) string { return img.TargetPlatform })
393411

394-
var images []*imagePkg.InfoGetter
395-
396-
if len(targetPlatforms) == 1 {
397-
for _, img := range c.imagesTree.GetImages() {
398-
if img.IsArtifact {
399-
continue
400-
}
412+
if len(platforms) == 1 {
413+
img := images[0]
401414
getter := c.StorageManager.GetImageInfoGetter(img.Name, img.GetLastNonEmptyStage().GetStageImage().Image.GetStageDescription(), opts)
402-
images = append(images, getter)
403-
}
404-
} else {
405-
for _, img := range c.imagesTree.GetMultiplatformImages() {
406-
if !img.IsFinal() {
407-
continue
408-
}
409-
415+
imagesGetters = append(imagesGetters, getter)
416+
} else {
417+
img := c.imagesTree.GetMultiplatformImage(name)
410418
desc := img.GetFinalStageDescription()
411419
if desc == nil {
412420
desc = img.GetStageDescription()
413421
}
414-
415422
getter := c.StorageManager.GetImageInfoGetter(img.Name, desc, opts)
416-
images = append(images, getter)
423+
imagesGetters = append(imagesGetters, getter)
417424
}
418425
}
419-
420-
return images, nil
426+
return imagesGetters, nil
421427
}
422428

423429
func (c *Conveyor) GetExportedImages() (res []*image.Image) {

pkg/build/export_phase.go

Lines changed: 16 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
build_image "github.com/werf/werf/pkg/build/image"
1414
"github.com/werf/werf/pkg/image"
1515
"github.com/werf/werf/pkg/storage"
16+
"github.com/werf/werf/pkg/util"
1617
)
1718

1819
type ExportPhase struct {
@@ -42,38 +43,27 @@ func (phase *ExportPhase) AfterImages(ctx context.Context) error {
4243
return nil
4344
}
4445

45-
targetPlatforms, err := phase.Conveyor.GetTargetPlatforms()
46-
if err != nil {
47-
return fmt.Errorf("unable to get target platforms: %w", err)
48-
}
49-
if len(targetPlatforms) == 0 {
50-
targetPlatforms = []string{phase.Conveyor.ContainerBackend.GetDefaultPlatform()}
51-
}
46+
for _, desc := range phase.Conveyor.imagesTree.GetImagesByName(true) {
47+
name, images := desc.Unpair()
48+
if !slices.Contains(phase.ExportImageNameList, name) {
49+
continue
50+
}
5251

53-
if len(targetPlatforms) == 1 {
54-
// single platform mode
55-
for _, desc := range phase.Conveyor.imagesTree.GetImagesByName(true) {
56-
_, images := desc.Unpair()
52+
targetPlatforms := util.MapFuncToSlice(images, func(img *build_image.Image) string { return img.TargetPlatform })
53+
if len(targetPlatforms) == 1 {
5754
img := images[0]
58-
if !slices.Contains(phase.ExportImageNameList, img.Name) {
59-
continue
60-
}
6155
if err := phase.exportImage(ctx, img); err != nil {
6256
return fmt.Errorf("unable to export image %q: %w", img.Name, err)
6357
}
64-
}
65-
} else {
66-
// FIXME(multiarch): Support multiplatform manifest by pushing local images to repo first, then create manifest list.
67-
// FIXME(multiarch): Also support multiplatform manifest in werf build command in local mode with enabled final-repo.
68-
if _, isLocal := phase.Conveyor.StorageManager.GetStagesStorage().(*storage.LocalStagesStorage); isLocal {
69-
return fmt.Errorf("export command is not supported in multiplatform mode")
70-
}
71-
72-
// multiplatform mode
73-
for _, img := range phase.Conveyor.imagesTree.GetMultiplatformImages() {
74-
if !slices.Contains(phase.ExportImageNameList, img.Name) {
75-
continue
58+
} else {
59+
// FIXME(multiarch): Support multiplatform manifest by pushing local images to repo first, then create manifest list.
60+
// FIXME(multiarch): Also support multiplatform manifest in werf build command in local mode with enabled final-repo.
61+
if _, isLocal := phase.Conveyor.StorageManager.GetStagesStorage().(*storage.LocalStagesStorage); isLocal {
62+
return fmt.Errorf("export command is not supported in multiplatform mode")
7663
}
64+
65+
// multiplatform mode
66+
img := phase.Conveyor.imagesTree.GetMultiplatformImage(name)
7767
if err := phase.exportMultiplatformImage(ctx, img); err != nil {
7868
return fmt.Errorf("unable to export multiplatform image %q: %w", img.Name, err)
7969
}

pkg/build/image/conveyor.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@ type Conveyor interface {
1212

1313
GetImage(targetPlatform, name string) *Image
1414
GetOrCreateStageImage(name string, prevStageImage *stage.StageImage, stg stage.Interface, img *Image) *stage.StageImage
15+
16+
GetForcedTargetPlatforms() []string
1517
GetTargetPlatforms() ([]string, error)
18+
GetImageTargetPlatforms(imageName string) ([]string, error)
1619

1720
IsBaseImagesRepoIdsCacheExist(key string) bool
1821
GetBaseImagesRepoIdsCache(key string) string

pkg/build/image/image_tree.go

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,21 +51,37 @@ func (tree *ImagesTree) Calculate(ctx context.Context) error {
5151
return fmt.Errorf("unable to group werf config images by independent sets: %w", err)
5252
}
5353

54-
targetPlatforms, err := tree.Conveyor.GetTargetPlatforms()
54+
forcedTargetPlatforms := tree.Conveyor.GetForcedTargetPlatforms()
55+
commonTargetPlatforms, err := tree.Conveyor.GetTargetPlatforms()
5556
if err != nil {
56-
return fmt.Errorf("invalid target platforms: %w", err)
57+
return fmt.Errorf("invalid common target platforms: %w", err)
5758
}
58-
if len(targetPlatforms) == 0 {
59-
targetPlatforms = []string{tree.ContainerBackend.GetDefaultPlatform()}
59+
if len(commonTargetPlatforms) == 0 {
60+
commonTargetPlatforms = []string{tree.ContainerBackend.GetDefaultPlatform()}
6061
}
6162

6263
commonImageOpts := tree.CommonImageOptions
63-
commonImageOpts.ForceTargetPlatformLogging = (len(targetPlatforms) > 1)
64-
6564
builder := NewImagesSetsBuilder()
6665

6766
for _, iteration := range imageConfigSets {
6867
for _, imageConfigI := range iteration {
68+
var targetPlatforms []string
69+
if len(forcedTargetPlatforms) > 0 {
70+
targetPlatforms = forcedTargetPlatforms
71+
} else {
72+
imageTargetPlatforms, err := tree.Conveyor.GetImageTargetPlatforms(imageConfigI.GetName())
73+
if err != nil {
74+
return fmt.Errorf("invalid image %q target platforms: %w", imageConfigI.GetName(), err)
75+
}
76+
if len(imageTargetPlatforms) > 0 {
77+
targetPlatforms = imageTargetPlatforms
78+
} else {
79+
targetPlatforms = commonTargetPlatforms
80+
}
81+
}
82+
83+
commonImageOpts.ForceTargetPlatformLogging = (len(targetPlatforms) > 1)
84+
6985
for _, targetPlatform := range targetPlatforms {
7086
var imageLogName string
7187
var style color.Style
@@ -181,6 +197,13 @@ func (tree *ImagesTree) GetImagePlatformsByName(finalOnly bool) map[string][]str
181197
return res
182198
}
183199

200+
func (tree *ImagesTree) GetImagesNames() (res []string) {
201+
for _, img := range tree.allImages {
202+
res = util.UniqAppendString(res, img.Name)
203+
}
204+
return
205+
}
206+
184207
func (tree *ImagesTree) GetImages() []*Image {
185208
return tree.allImages
186209
}

pkg/config/image_from_dockerfile.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ type ImageFromDockerfile struct {
1919
SSH string
2020
Dependencies []*Dependency
2121
Staged bool
22+
Platform []string
2223

2324
raw *rawImageFromDockerfile
2425
}

pkg/config/raw_image_from_dockerfile.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ type rawImageFromDockerfile struct {
2020
SSH string `yaml:"ssh,omitempty"`
2121
RawDependencies []*rawDependency `yaml:"dependencies,omitempty"`
2222
Staged bool `yaml:"staged,omitempty"`
23+
Platform []string `yaml:"platform,omitempty"`
2324

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

@@ -128,6 +129,7 @@ func (c *rawImageFromDockerfile) toImageFromDockerfileDirective(giterminismManag
128129
}
129130

130131
image.Staged = c.Staged || util.GetBoolEnvironmentDefaultFalse("WERF_FORCE_STAGED_DOCKERFILE")
132+
image.Platform = append([]string{}, c.Platform...)
131133
image.raw = c
132134

133135
if err := image.validate(giterminismManager); err != nil {

0 commit comments

Comments
 (0)