Skip to content

Commit

Permalink
feat(multiarch): use multiplatform images for converge/render and in …
Browse files Browse the repository at this point in the history
…the build report

Also refactored manifest list image tag: use only digest without uniqueID timestamp.

Signed-off-by: Timofey Kirillov <timofey.kirillov@flant.com>
  • Loading branch information
distorhead committed Apr 7, 2023
1 parent 1913c95 commit 370d14c
Show file tree
Hide file tree
Showing 9 changed files with 153 additions and 43 deletions.
63 changes: 57 additions & 6 deletions pkg/build/build_phase.go
Expand Up @@ -304,6 +304,8 @@ func (phase *BuildPhase) publishMultiplatformImageMetadata(ctx context.Context,
}

img := image.NewMultiplatformImage(name, images, phase.Conveyor.StorageManager, opts)
phase.Conveyor.imagesTree.SetMultiplatformImage(img)

stagesStorage := phase.Conveyor.StorageManager.GetStagesStorage()

if len(phase.CustomTagFuncList) == 0 {
Expand All @@ -315,6 +317,16 @@ func (phase *BuildPhase) publishMultiplatformImageMetadata(ctx context.Context,
if err := stagesStorage.PostMultiplatformImage(ctx, phase.Conveyor.ProjectName(), img.GetStageID().String(), img.GetImagesInfoList()); err != nil {
return fmt.Errorf("unable to post multiplatform image %s %s: %w", name, img.GetStageID(), err)
}

desc, err := stagesStorage.GetStageDescription(ctx, phase.Conveyor.ProjectName(), img.GetStageID().Digest, img.GetStageID().UniqueID)
if err != nil {
return fmt.Errorf("unable to get image %s %s descriptor: %w", name, img.GetStageID(), err)
}
if desc == nil {
return fmt.Errorf("unable to get image %s %s descriptor: no manifest found", name, img.GetStageID())
}
img.SetStageDescription(desc)

} else {
for _, tagFunc := range phase.CustomTagFuncList {
tag := tagFunc(name, img.GetStageID().String())
Expand Down Expand Up @@ -355,6 +367,12 @@ func (phase *BuildPhase) publishMultiplatformImageMetadata(ctx context.Context,
// ); err != nil {
// return err
// }

// desc, err := phase.Conveyor.StorageManager.GetFinalStagesStorage().GetStageDescription(ctx, phase.Conveyor.ProjectName(), img.GetStageID().Digest, img.GetStageID().UniqueID)
// if err != nil {
// return fmt.Errorf("unable to get image %s %s descriptor: %w", name, img.GetStageID(), err)
// }
// img.SetFinalStageDescription(desc)
}

return nil
Expand All @@ -363,19 +381,19 @@ func (phase *BuildPhase) publishMultiplatformImageMetadata(ctx context.Context,
func (phase *BuildPhase) createReport(ctx context.Context) error {
targetPlatforms, err := phase.Conveyor.GetTargetPlatforms()
if err != nil {
return fmt.Errorf("unable to get target platforms: %w", err)
return fmt.Errorf("invalid target platforms: %w", err)
}
if len(targetPlatforms) == 0 {
targetPlatforms = []string{phase.Conveyor.ContainerBackend.GetDefaultPlatform()}
}

// FIXME(multiarch): instead of using first specified platform use multiarch-manifest
targetPlatform := targetPlatforms[0]

for _, img := range phase.Conveyor.imagesTree.GetImages() {
if img.IsArtifact {
continue
}
if img.IsDockerfileImage && !img.IsDockerfileTargetStage {
continue
}

stageImage := img.GetLastNonEmptyStage().GetStageImage().Image
desc := stageImage.GetFinalStageDescription()
Expand All @@ -396,8 +414,41 @@ func (phase *BuildPhase) createReport(ctx context.Context) error {
if os.Getenv("WERF_ENABLE_REPORT_BY_PLATFORM") == "1" {
phase.ImagesReport.SetImageByPlatformRecord(img.TargetPlatform, img.GetName(), record)
}
if img.TargetPlatform == targetPlatform {
phase.ImagesReport.SetImageRecord(img.GetName(), record)
if len(targetPlatforms) == 1 {
phase.ImagesReport.SetImageRecord(img.Name, record)
}
}

if len(targetPlatforms) > 1 {
for _, img := range phase.Conveyor.imagesTree.GetMultiplatformImages() {
if img.IsArtifact {
continue
}
if img.IsDockerfileImage && !img.IsDockerfileTargetStage {
continue
}

isRebuilt := false
for _, pImg := range img.Images {
isRebuilt = (isRebuilt || pImg.GetRebuilt())
}

desc := img.GetFinalStageDescription()
if desc == nil {
desc = img.GetStageDescription()
}

record := ReportImageRecord{
WerfImageName: img.Name,
DockerRepo: desc.Info.Repository,
DockerTag: desc.Info.Tag,
DockerImageID: desc.Info.ID,
DockerImageDigest: desc.Info.RepoDigest,
DockerImageName: desc.Info.Name,
Rebuilt: isRebuilt,
}

phase.ImagesReport.SetImageRecord(img.Name, record)
}
}

Expand Down
36 changes: 25 additions & 11 deletions pkg/build/conveyor.go
Expand Up @@ -390,21 +390,35 @@ func (c *Conveyor) GetImageInfoGetters(opts imagePkg.InfoGetterOptions) ([]*imag
targetPlatforms = []string{c.ContainerBackend.GetDefaultPlatform()}
}

// FIXME(multiarch): instead of using first specified platform use multiarch-manifest
targetPlatform := targetPlatforms[0]

var images []*imagePkg.InfoGetter
for _, img := range c.imagesTree.GetImages() {
if img.IsArtifact {
continue
}
if img.TargetPlatform != targetPlatform {
continue

if len(targetPlatforms) == 1 {
for _, img := range c.imagesTree.GetImages() {
if img.IsArtifact {
continue
}
getter := c.StorageManager.GetImageInfoGetter(img.Name, img.GetLastNonEmptyStage().GetStageImage().Image.GetStageDescription(), opts)
images = append(images, getter)
}
} else {
for _, img := range c.imagesTree.GetMultiplatformImages() {
if img.IsArtifact {
continue
}
if img.IsDockerfileImage && !img.IsDockerfileTargetStage {
continue
}

getter := c.StorageManager.GetImageInfoGetter(img.Name, img.GetLastNonEmptyStage(), opts)
images = append(images, getter)
desc := img.GetFinalStageDescription()
if desc == nil {
desc = img.GetStageDescription()
}

getter := c.StorageManager.GetImageInfoGetter(img.Name, desc, opts)
images = append(images, getter)
}
}

return images, nil
}

Expand Down
4 changes: 4 additions & 0 deletions pkg/build/image/image_tree.go
Expand Up @@ -207,6 +207,10 @@ func (tree *ImagesTree) SetMultiplatformImage(newImg *MultiplatformImage) {
tree.multiplatformImages = append(tree.multiplatformImages, newImg)
}

func (tree *ImagesTree) GetMultiplatformImages() []*MultiplatformImage {
return tree.multiplatformImages
}

func getFromFields(imageBaseConfig *config.StapelImageBase) (string, string, bool) {
var from string
var fromImageName string
Expand Down
27 changes: 22 additions & 5 deletions pkg/build/image/multiplatform_image.go
@@ -1,6 +1,7 @@
package image

import (
"github.com/werf/werf/pkg/image"
common_image "github.com/werf/werf/pkg/image"
"github.com/werf/werf/pkg/storage/manager"
"github.com/werf/werf/pkg/util"
Expand All @@ -12,8 +13,10 @@ type MultiplatformImage struct {

MultiplatformImageOptions

calculatedDigest string
stageID common_image.StageID
calculatedDigest string
stageID common_image.StageID
stageDescription *common_image.StageDescription
finalStageDescription *common_image.StageDescription
}

type MultiplatformImageOptions struct {
Expand All @@ -32,9 +35,7 @@ func NewMultiplatformImage(name string, images []*Image, storageManager manager.
return stageDesc.StageID.String()
})
img.calculatedDigest = util.Sha3_224Hash(metaStageDeps...)

_, uniqueID := storageManager.GenerateStageUniqueID(img.GetDigest(), nil)
img.stageID = common_image.StageID{Digest: img.GetDigest(), UniqueID: uniqueID}
img.stageID = common_image.StageID{Digest: img.GetDigest()}

return img
}
Expand All @@ -57,3 +58,19 @@ func (img *MultiplatformImage) GetImagesInfoList() []*common_image.Info {
return stageDesc.Info
})
}

func (img *MultiplatformImage) GetFinalStageDescription() *image.StageDescription {
return img.finalStageDescription
}

func (img *MultiplatformImage) SetFinalStageDescription(desc *common_image.StageDescription) {
img.finalStageDescription = desc
}

func (img *MultiplatformImage) GetStageDescription() *image.StageDescription {
return img.stageDescription
}

func (img *MultiplatformImage) SetStageDescription(desc *common_image.StageDescription) {
img.stageDescription = desc
}
3 changes: 3 additions & 0 deletions pkg/image/stage.go
Expand Up @@ -12,6 +12,9 @@ type StageID struct {
}

func (id StageID) String() string {
if id.UniqueID == 0 {
return id.Digest
}
return fmt.Sprintf("%s-%d", id.Digest, id.UniqueID)
}

Expand Down
11 changes: 4 additions & 7 deletions pkg/storage/manager/storage_manager.go
Expand Up @@ -59,7 +59,7 @@ type StorageManagerInterface interface {
GetStagesStorage() storage.PrimaryStagesStorage
GetFinalStagesStorage() storage.StagesStorage
GetSecondaryStagesStorageList() []storage.StagesStorage
GetImageInfoGetter(imageName string, stg stage.Interface, opts image.InfoGetterOptions) *image.InfoGetter
GetImageInfoGetter(imageName string, desc *image.StageDescription, opts image.InfoGetterOptions) *image.InfoGetter

EnableParallel(parallelTasksLimit int)
MaxNumberOfWorkers() int
Expand Down Expand Up @@ -191,16 +191,13 @@ func (m *StorageManager) GetServiceValuesRepo() string {
return m.StagesStorage.String()
}

func (m *StorageManager) GetImageInfoGetter(imageName string, stg stage.Interface, opts image.InfoGetterOptions) *image.InfoGetter {
stageID := stg.GetStageImage().Image.GetStageDescription().StageID
info := stg.GetStageImage().Image.GetStageDescription().Info

func (m *StorageManager) GetImageInfoGetter(imageName string, desc *image.StageDescription, opts image.InfoGetterOptions) *image.InfoGetter {
if m.FinalStagesStorage != nil {
finalImageName := m.FinalStagesStorage.ConstructStageImageName(m.ProjectName, stageID.Digest, stageID.UniqueID)
finalImageName := m.FinalStagesStorage.ConstructStageImageName(m.ProjectName, desc.StageID.Digest, desc.StageID.UniqueID)
return image.NewInfoGetter(imageName, finalImageName, opts)
}

return image.NewInfoGetter(imageName, info.Name, opts)
return image.NewInfoGetter(imageName, desc.Info.Name, opts)
}

func (m *StorageManager) InitCache(ctx context.Context) error {
Expand Down
8 changes: 6 additions & 2 deletions pkg/storage/repo_stages_storage.go
Expand Up @@ -17,7 +17,8 @@ import (
)

const (
RepoStage_ImageFormat = "%s:%s-%d"
RepoStage_ImageFormatWithUniqueID = "%s:%s-%d"
RepoStage_ImageFormat = "%s:%s"

RepoManagedImageRecord_ImageTagPrefix = "managed-image-"
RepoManagedImageRecord_ImageNameFormat = "%s:managed-image-%s"
Expand Down Expand Up @@ -73,7 +74,10 @@ func NewRepoStagesStorage(repoAddress string, containerBackend container_backend
}

func (storage *RepoStagesStorage) ConstructStageImageName(_, digest string, uniqueID int64) string {
return fmt.Sprintf(RepoStage_ImageFormat, storage.RepoAddress, digest, uniqueID)
if uniqueID == 0 {
return fmt.Sprintf(RepoStage_ImageFormat, storage.RepoAddress, digest)
}
return fmt.Sprintf(RepoStage_ImageFormatWithUniqueID, storage.RepoAddress, digest, uniqueID)
}

func (storage *RepoStagesStorage) GetStagesIDs(ctx context.Context, _ string, opts ...Option) ([]image.StageID, error) {
Expand Down
7 changes: 7 additions & 0 deletions pkg/util/map.go
Expand Up @@ -19,6 +19,13 @@ func MergeMaps[K comparable, V any](src, dest map[K]V) map[K]V {
return result
}

func MapValues[M ~map[K]V, K comparable, V any](m M) (res []V) {
for _, v := range m {
res = append(res, v)
}
return
}

func MapKeys[M ~map[K]V, K comparable, V any](m M) (res []K) {
for k := range m {
res = append(res, k)
Expand Down
37 changes: 25 additions & 12 deletions test/e2e/build/multiarch_test.go
Expand Up @@ -2,12 +2,14 @@ package e2e_build_test

import (
"fmt"
"sort"
"strings"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"

"github.com/werf/werf/pkg/container_backend/thirdparty/platformutil"
"github.com/werf/werf/pkg/util"
"github.com/werf/werf/test/pkg/contback"
"github.com/werf/werf/test/pkg/werf"
)
Expand All @@ -27,7 +29,6 @@ type multiarchTestOptions struct {

type expectedImageInfo struct {
ImageName string
MetaDigest string
DigestByPlatform map[string]string
}

Expand Down Expand Up @@ -89,6 +90,7 @@ var _ = Describe("Multiarch build", Label("e2e", "build", "multiarch", "simple")
for _, expect := range expects {
byPlatform := buildReport.ImagesByPlatform[expect.ImageName]
Expect(len(byPlatform)).To(Equal(len(testOpts.Platforms)))

for _, platform := range testOpts.Platforms {
Expect(strings.HasPrefix(byPlatform[platform].DockerTag, expect.DigestByPlatform[platform]+"-")).To(BeTrue())

Expand All @@ -104,7 +106,23 @@ var _ = Describe("Multiarch build", Label("e2e", "build", "multiarch", "simple")
Expect(inspect.Architecture).To(Equal(platformSpec.Architecture))
Expect(inspect.Variant).To(Equal(platformSpec.Variant))
}
Expect(strings.HasPrefix(buildReport.Images[expect.ImageName].DockerTag, expect.MetaDigest+"-")).To(BeTrue())

// Meta digest only used for multiplatform builds
if len(expect.DigestByPlatform) > 1 {
platforms := util.MapKeys(expect.DigestByPlatform)
sort.Strings(platforms)

metaDeps := util.MapFuncToSlice(platforms, func(platform string) string {
return buildReport.ImagesByPlatform[expect.ImageName][platform].DockerTag
})
expectedMetaDigest := util.Sha3_224Hash(metaDeps...)

fmt.Printf("metaDeps %v -> %q\n", metaDeps, expectedMetaDigest)
Expect(buildReport.Images[expect.ImageName].DockerTag).To(Equal(expectedMetaDigest))
} else {
expectedDigest := util.MapValues(expect.DigestByPlatform)[0]
Expect(strings.HasPrefix(buildReport.Images[expect.ImageName].DockerTag, expectedDigest+"-")).To(BeTrue())
}
}
},

Expand All @@ -119,24 +137,21 @@ var _ = Describe("Multiarch build", Label("e2e", "build", "multiarch", "simple")
EnableDockerfileImage: true,

ExpectedStapelImageInfo: expectedImageInfo{
ImageName: "orange",
MetaDigest: "0bfe25871a0014046617e5c47e399183886b23ab394ee8422cc8b10c",
ImageName: "orange",
DigestByPlatform: map[string]string{
"linux/arm64": "0bfe25871a0014046617e5c47e399183886b23ab394ee8422cc8b10c",
"linux/amd64": "f401fc847eba504268377fe9a6e192bd90cd9cd4e9333c3564846264",
},
},
ExpectedStagedDockerfileImageInfo: expectedImageInfo{
ImageName: "apple",
MetaDigest: "e09a53cbff65cd32668dd9749845923d46be8a70c1e1f12b1ae3318b",
ImageName: "apple",
DigestByPlatform: map[string]string{
"linux/arm64": "e09a53cbff65cd32668dd9749845923d46be8a70c1e1f12b1ae3318b",
"linux/amd64": "8f89f4cdc76a994e38f4146ef4e3edd83e070266cc15c135696bddd2",
},
},
ExpectedDockerfileImageInfo: expectedImageInfo{
ImageName: "potato",
MetaDigest: "cd8956d94612821a843cfbbb3b44d9ed837f8001f2a2361a4815acce",
ImageName: "potato",
DigestByPlatform: map[string]string{
"linux/arm64": "cd8956d94612821a843cfbbb3b44d9ed837f8001f2a2361a4815acce",
"linux/amd64": "30bd7a840fcb35eb283d9940f58d1dd02a576a6967e416d9c13deaf2",
Expand All @@ -153,8 +168,7 @@ var _ = Describe("Multiarch build", Label("e2e", "build", "multiarch", "simple")
EnableStapelImage: true,

ExpectedStapelImageInfo: expectedImageInfo{
ImageName: "orange",
MetaDigest: "f401fc847eba504268377fe9a6e192bd90cd9cd4e9333c3564846264",
ImageName: "orange",
DigestByPlatform: map[string]string{
"linux/amd64": "f401fc847eba504268377fe9a6e192bd90cd9cd4e9333c3564846264",
},
Expand All @@ -170,8 +184,7 @@ var _ = Describe("Multiarch build", Label("e2e", "build", "multiarch", "simple")
EnableDockerfileImage: true,

ExpectedDockerfileImageInfo: expectedImageInfo{
ImageName: "potato",
MetaDigest: "cd8956d94612821a843cfbbb3b44d9ed837f8001f2a2361a4815acce",
ImageName: "potato",
DigestByPlatform: map[string]string{
"linux/arm64": "cd8956d94612821a843cfbbb3b44d9ed837f8001f2a2361a4815acce",
"linux/amd64": "30bd7a840fcb35eb283d9940f58d1dd02a576a6967e416d9c13deaf2",
Expand Down

0 comments on commit 370d14c

Please sign in to comment.