diff --git a/pkg/build/build_phase.go b/pkg/build/build_phase.go index 6912e263c3..03a4e45af9 100644 --- a/pkg/build/build_phase.go +++ b/pkg/build/build_phase.go @@ -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 { @@ -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()) @@ -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 @@ -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() @@ -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) } } diff --git a/pkg/build/conveyor.go b/pkg/build/conveyor.go index dc436a6da0..103baddd94 100644 --- a/pkg/build/conveyor.go +++ b/pkg/build/conveyor.go @@ -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 } diff --git a/pkg/build/image/image_tree.go b/pkg/build/image/image_tree.go index ef60006d7f..603f703a75 100644 --- a/pkg/build/image/image_tree.go +++ b/pkg/build/image/image_tree.go @@ -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 diff --git a/pkg/build/image/multiplatform_image.go b/pkg/build/image/multiplatform_image.go index a86820bb98..dc0c946276 100644 --- a/pkg/build/image/multiplatform_image.go +++ b/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" @@ -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 { @@ -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 } @@ -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 +} diff --git a/pkg/image/stage.go b/pkg/image/stage.go index 973b562883..a472c58ff3 100644 --- a/pkg/image/stage.go +++ b/pkg/image/stage.go @@ -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) } diff --git a/pkg/storage/manager/storage_manager.go b/pkg/storage/manager/storage_manager.go index f32473fa3c..7de821a6f5 100644 --- a/pkg/storage/manager/storage_manager.go +++ b/pkg/storage/manager/storage_manager.go @@ -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 @@ -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 { diff --git a/pkg/storage/repo_stages_storage.go b/pkg/storage/repo_stages_storage.go index 526271fede..6bd464d476 100644 --- a/pkg/storage/repo_stages_storage.go +++ b/pkg/storage/repo_stages_storage.go @@ -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" @@ -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) { diff --git a/pkg/util/map.go b/pkg/util/map.go index c7b98fa4f6..ad01da9cf3 100644 --- a/pkg/util/map.go +++ b/pkg/util/map.go @@ -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) diff --git a/test/e2e/build/multiarch_test.go b/test/e2e/build/multiarch_test.go index 6024ad7089..7fcac1910a 100644 --- a/test/e2e/build/multiarch_test.go +++ b/test/e2e/build/multiarch_test.go @@ -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" ) @@ -27,7 +29,6 @@ type multiarchTestOptions struct { type expectedImageInfo struct { ImageName string - MetaDigest string DigestByPlatform map[string]string } @@ -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()) @@ -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()) + } } }, @@ -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", @@ -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", }, @@ -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",