Skip to content

Commit

Permalink
feat(multiarch): support :local mode multiarch building for docker se…
Browse files Browse the repository at this point in the history
…rver backend

Signed-off-by: Timofey Kirillov <timofey.kirillov@flant.com>
  • Loading branch information
distorhead committed Apr 12, 2023
1 parent e5819f6 commit e519902
Show file tree
Hide file tree
Showing 13 changed files with 183 additions and 123 deletions.
1 change: 0 additions & 1 deletion cmd/werf/common/container_backend.go
Expand Up @@ -135,7 +135,6 @@ func InitProcessContainerBackend(ctx context.Context, cmdData *CmdData) (contain
func InitProcessDocker(ctx context.Context, cmdData *CmdData) (context.Context, error) {
opts := docker.InitOptions{
DockerConfigDir: *cmdData.DockerConfig,
ClaimPlatforms: cmdData.GetPlatform(),
Verbose: *cmdData.LogVerbose,
Debug: *cmdData.LogDebug,
}
Expand Down
196 changes: 103 additions & 93 deletions pkg/build/build_phase.go
Expand Up @@ -215,28 +215,84 @@ func (phase *BuildPhase) AfterImages(ctx context.Context) error {
}

if len(targetPlatforms) == 1 {
if err := phase.publishImageMetadata(ctx, name, images[0]); err != nil {
return fmt.Errorf("unable to publish image %q metadata: %w", name, err)
img := images[0]

if img.IsFinal() {
if err := phase.publishFinalImage(ctx, name, img); err != nil {
return err
}
}

// TODO: Separate LocalStagesStorage and RepoStagesStorage interfaces, local should not include metadata publishing methods at all
if _, isLocal := phase.Conveyor.StorageManager.GetStagesStorage().(*storage.LocalStagesStorage); !isLocal {
if err := phase.publishImageMetadata(ctx, name, img); err != nil {
return fmt.Errorf("unable to publish image %q metadata: %w", name, err)
}
}
} else {
if err := logboek.Context(ctx).LogProcess(logging.ImageLogProcessName(name, false, "")).
Options(func(options types.LogProcessOptionsInterface) {
options.Style(logging.ImageMetadataStyle())
}).
DoError(func() error {
if err := phase.publishMultiplatformImageMetadata(ctx, name, images); err != nil {
return fmt.Errorf("unable to publish image %q multiplatform metadata: %w", name, err)
}
return nil
}); err != nil {
return err
opts := image.MultiplatformImageOptions{
IsArtifact: images[0].IsArtifact,
IsDockerfileImage: images[0].IsDockerfileImage,
IsDockerfileTargetStage: images[0].IsDockerfileTargetStage,
}
img := image.NewMultiplatformImage(name, images, phase.Conveyor.StorageManager, opts)
phase.Conveyor.imagesTree.SetMultiplatformImage(img)

if img.IsFinal() {
if err := phase.publishMultiplatformFinalImage(ctx, name, images); err != nil {
return err
}
}

// TODO: Separate LocalStagesStorage and RepoStagesStorage interfaces, local should not include metadata publishing methods at all
if _, isLocal := phase.Conveyor.StorageManager.GetStagesStorage().(*storage.LocalStagesStorage); !isLocal {
if err := logboek.Context(ctx).LogProcess(logging.ImageLogProcessName(name, false, "")).
Options(func(options types.LogProcessOptionsInterface) {
options.Style(logging.ImageMetadataStyle())
}).
DoError(func() error {
if err := phase.publishMultiplatformImageMetadata(ctx, name, img); err != nil {
return fmt.Errorf("unable to publish image %q multiplatform metadata: %w", name, err)
}
return nil
}); err != nil {
return err
}
}
}
}

return phase.createReport(ctx)
}

func (phase *BuildPhase) publishFinalImage(ctx context.Context, name string, img *image.Image) error {
if phase.Conveyor.StorageManager.GetFinalStagesStorage() != nil {
if err := phase.Conveyor.StorageManager.CopyStageIntoFinalStorage(ctx, img.GetLastNonEmptyStage(), phase.Conveyor.ContainerBackend, manager.CopyStageIntoFinalStorageOptions{ShouldBeBuiltMode: phase.ShouldBeBuiltMode}); err != nil {
return err
}
}
return nil
}

func (phase *BuildPhase) publishMultiplatformFinalImage(ctx context.Context, name string, images []*image.Image) error {
// FIXME(multiarch): copy manifest list into final stages storage with all dependant images
if phase.Conveyor.StorageManager.GetFinalStagesStorage() != nil {
// if err := phase.Conveyor.StorageManager.CopyStageIntoFinalStorage(
// ctx, img.GetLastNonEmptyStage(), phase.Conveyor.ContainerBackend,
// manager.CopyStageIntoFinalStorageOptions{ShouldBeBuiltMode: phase.ShouldBeBuiltMode},
// ); 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
}

func (phase *BuildPhase) publishImageMetadata(ctx context.Context, name string, img *image.Image) error {
if err := phase.addManagedImage(ctx, name); err != nil {
return err
Expand All @@ -248,27 +304,17 @@ func (phase *BuildPhase) publishImageMetadata(ctx context.Context, name string,
DoError(func() error {
return phase.publishImageGitMetadata(
ctx, img.GetName(),
img.GetLastNonEmptyStage().GetStageImage().Image.GetStageDescription().Info.Repository,
*img.GetLastNonEmptyStage().GetStageImage().Image.GetStageDescription().StageID,
)
}); err != nil {
return err
}
}

if img.IsArtifact {
return nil
}
if img.IsDockerfileImage && !img.IsDockerfileTargetStage {
if !img.IsFinal() {
return nil
}

if phase.Conveyor.StorageManager.GetFinalStagesStorage() != nil {
if err := phase.Conveyor.StorageManager.CopyStageIntoFinalStorage(ctx, img.GetLastNonEmptyStage(), phase.Conveyor.ContainerBackend, manager.CopyStageIntoFinalStorageOptions{ShouldBeBuiltMode: phase.ShouldBeBuiltMode}); err != nil {
return err
}
}

var customTagStorage storage.StagesStorage
var customTagStage *imagePkg.StageDescription
if phase.Conveyor.StorageManager.GetFinalStagesStorage() != nil {
Expand All @@ -292,26 +338,18 @@ func (phase *BuildPhase) publishImageMetadata(ctx context.Context, name string,
return nil
}

func (phase *BuildPhase) publishMultiplatformImageMetadata(ctx context.Context, name string, images []*image.Image) error {
func (phase *BuildPhase) publishMultiplatformImageMetadata(ctx context.Context, name string, img *image.MultiplatformImage) error {
if err := phase.addManagedImage(ctx, name); err != nil {
return err
}

opts := image.MultiplatformImageOptions{
IsArtifact: images[0].IsArtifact,
IsDockerfileImage: images[0].IsDockerfileImage,
IsDockerfileTargetStage: images[0].IsDockerfileTargetStage,
}

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

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

if len(phase.CustomTagFuncList) == 0 {
platforms := util.MapFuncToSlice(images, func(img *image.Image) string { return img.TargetPlatform })
fullImageName := stagesStorage.ConstructStageImageName(phase.Conveyor.ProjectName(), img.GetStageID().Digest, img.GetStageID().UniqueID)
platforms := img.GetPlatforms()

container_backend.LogImageName(ctx, fmt.Sprintf("%s:%s", stagesStorage.Address(), img.GetStageID()))
container_backend.LogImageName(ctx, fullImageName)
container_backend.LogMultiplatformImageInfo(ctx, platforms)

if err := stagesStorage.PostMultiplatformImage(ctx, phase.Conveyor.ProjectName(), img.GetStageID().String(), img.GetImagesInfoList()); err != nil {
Expand Down Expand Up @@ -346,35 +384,11 @@ func (phase *BuildPhase) publishMultiplatformImageMetadata(ctx context.Context,
if err := logboek.Context(ctx).Info().
LogProcess(fmt.Sprintf("Publish multiarch image %s git metadata", name)).
DoError(func() error {
return phase.publishImageGitMetadata(ctx, name, stagesStorage.Address(), img.GetStageID())
return phase.publishImageGitMetadata(ctx, name, img.GetStageID())
}); err != nil {
return err
}
}

if img.IsArtifact {
return nil
}
if img.IsDockerfileImage && !img.IsDockerfileTargetStage {
return nil
}

if phase.Conveyor.StorageManager.GetFinalStagesStorage() != nil {
// FIXME(multiarch): copy manifest list into final stages storage with all dependant images
// if err := phase.Conveyor.StorageManager.CopyStageIntoFinalStorage(
// ctx, img.GetLastNonEmptyStage(), phase.Conveyor.ContainerBackend,
// manager.CopyStageIntoFinalStorageOptions{ShouldBeBuiltMode: phase.ShouldBeBuiltMode},
// ); 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 @@ -388,10 +402,7 @@ func (phase *BuildPhase) createReport(ctx context.Context) error {
}

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

Expand Down Expand Up @@ -419,36 +430,34 @@ func (phase *BuildPhase) createReport(ctx context.Context) error {
}
}

if len(targetPlatforms) > 1 {
for _, img := range phase.Conveyor.imagesTree.GetMultiplatformImages() {
if img.IsArtifact {
continue
}
if img.IsDockerfileImage && !img.IsDockerfileTargetStage {
continue
}
if _, isLocal := phase.Conveyor.StorageManager.GetStagesStorage().(*storage.LocalStagesStorage); !isLocal {
if len(targetPlatforms) > 1 {
for _, img := range phase.Conveyor.imagesTree.GetMultiplatformImages() {
if !img.IsFinal() {
continue
}

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

desc := img.GetFinalStageDescription()
if desc == nil {
desc = img.GetStageDescription()
}
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,
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)
}

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

Expand Down Expand Up @@ -537,7 +546,7 @@ func (phase *BuildPhase) addManagedImage(ctx context.Context, name string) error
return nil
}

func (phase *BuildPhase) publishImageGitMetadata(ctx context.Context, imageName, repository string, stageID imagePkg.StageID) error {
func (phase *BuildPhase) publishImageGitMetadata(ctx context.Context, imageName string, stageID imagePkg.StageID) error {
var commits []string

headCommit := phase.Conveyor.giterminismManager.HeadCommit()
Expand All @@ -554,7 +563,8 @@ func (phase *BuildPhase) publishImageGitMetadata(ctx context.Context, imageName,

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

logboek.Context(ctx).Info().LogF("name: %s:%s\n", repository, stageID)
fullImageName := stagesStorage.ConstructStageImageName(phase.Conveyor.ProjectName(), stageID.Digest, stageID.UniqueID)
logboek.Context(ctx).Info().LogF("name: %s\n", fullImageName)
logboek.Context(ctx).Info().LogF("commits:\n")

for _, commit := range commits {
Expand Down
13 changes: 8 additions & 5 deletions pkg/build/conveyor.go
Expand Up @@ -402,10 +402,7 @@ func (c *Conveyor) GetImageInfoGetters(opts imagePkg.InfoGetterOptions) ([]*imag
}
} else {
for _, img := range c.imagesTree.GetMultiplatformImages() {
if img.IsArtifact {
continue
}
if img.IsDockerfileImage && !img.IsDockerfileTargetStage {
if !img.IsFinal() {
continue
}

Expand Down Expand Up @@ -445,7 +442,13 @@ func (c *Conveyor) GetImagesEnvArray() []string {
return envArray
}

func (c *Conveyor) checkContainerBackendSupported(_ context.Context) error {
func (c *Conveyor) checkContainerBackendSupported(ctx context.Context) error {
targetPlatforms, err := c.GetTargetPlatforms()
if err != nil {
return fmt.Errorf("error getting target platforms: %w", err)
}
c.ContainerBackend.ClaimTargetPlatforms(ctx, targetPlatforms)

if _, isBuildah := c.ContainerBackend.(*container_backend.BuildahBackend); !isBuildah {
return nil
}
Expand Down
10 changes: 10 additions & 0 deletions pkg/build/image/image.go
Expand Up @@ -145,6 +145,16 @@ func ImageLogTagStyle(isArtifact bool) color.Style {
return logging.ImageDefaultStyle(isArtifact)
}

func (i *Image) IsFinal() bool {
if i.IsArtifact {
return false
}
if i.IsDockerfileImage && !i.IsDockerfileTargetStage {
return false
}
return true
}

func (i *Image) SetStages(stages []stage.Interface) {
i.stages = stages
}
Expand Down
10 changes: 10 additions & 0 deletions pkg/build/image/multiplatform_image.go
Expand Up @@ -74,3 +74,13 @@ func (img *MultiplatformImage) GetStageDescription() *image.StageDescription {
func (img *MultiplatformImage) SetStageDescription(desc *common_image.StageDescription) {
img.stageDescription = desc
}

func (img *MultiplatformImage) IsFinal() bool {
if img.IsArtifact {
return false
}
if img.IsDockerfileImage && !img.IsDockerfileTargetStage {
return false
}
return true
}
2 changes: 2 additions & 0 deletions pkg/container_backend/buildah_backend.go
Expand Up @@ -1005,3 +1005,5 @@ func (runtime *BuildahBackend) Rm(ctx context.Context, name string, opts RmOpts)
func (runtime *BuildahBackend) PostManifest(ctx context.Context, ref string, opts PostManifestOpts) error {
return fmt.Errorf("not implemented")
}

func (runtime *BuildahBackend) ClaimTargetPlatforms(ctx context.Context, targetPlatforms []string) {}
6 changes: 5 additions & 1 deletion pkg/container_backend/docker_server_backend.go
Expand Up @@ -23,6 +23,10 @@ func NewDockerServerBackend() *DockerServerBackend {
return &DockerServerBackend{}
}

func (runtime *DockerServerBackend) ClaimTargetPlatforms(ctx context.Context, targetPlatforms []string) {
docker.ClaimTargetPlatforms(targetPlatforms)
}

func (runtime *DockerServerBackend) GetDefaultPlatform() string {
return docker.GetDefaultPlatform()
}
Expand Down Expand Up @@ -336,5 +340,5 @@ func (runtime *DockerServerBackend) Containers(ctx context.Context, opts Contain
}

func (runtime *DockerServerBackend) PostManifest(ctx context.Context, ref string, opts PostManifestOpts) error {
return docker.CreateImage(ctx, ref, opts.Labels)
return docker.CreateImage(ctx, ref, docker.CreateImageOptions{Labels: opts.Labels})
}
2 changes: 2 additions & 0 deletions pkg/container_backend/interface.go
Expand Up @@ -91,6 +91,8 @@ type ContainerBackend interface {
Images(ctx context.Context, opts ImagesOptions) (image.ImagesList, error)
Containers(ctx context.Context, opts ContainersOptions) (image.ContainerList, error)

ClaimTargetPlatforms(ctx context.Context, targetPlatforms []string)

String() string

// TODO: Util method for cleanup, which possibly should be avoided in the future
Expand Down
5 changes: 5 additions & 0 deletions pkg/container_backend/perf_check_container_backend.go
Expand Up @@ -186,3 +186,8 @@ func (runtime *PerfCheckContainerBackend) TagImageByName(ctx context.Context, im
})
return
}

func (runtime *PerfCheckContainerBackend) ClaimTargetPlatforms(ctx context.Context, targetPlatforms []string) {
logboek.Context(ctx).Default().LogProcess("ContainerBackend.ClaimTargetPlatforms %v", targetPlatforms).
Do(func() { runtime.ContainerBackend.ClaimTargetPlatforms(ctx, targetPlatforms) })
}

0 comments on commit e519902

Please sign in to comment.