From dd3d653361c8b6f9d539bc276888d7415683d0a1 Mon Sep 17 00:00:00 2001 From: Timofey Kirillov Date: Wed, 17 May 2023 13:39:06 +0300 Subject: [PATCH] fix(staged-dockerfile): separate FROM stage with caching in the container-registry and possibility to reset by global cache version * This also fixes panic on single FROM instruction stage. * Added possibility to reset FROM instruction stage cache. Signed-off-by: Timofey Kirillov --- pkg/build/build_phase.go | 43 +++++++---- pkg/build/conveyor.go | 2 +- pkg/build/image/dockerfile.go | 71 +++++++++++------- pkg/build/image/image.go | 51 +++++++++---- pkg/build/stage/base.go | 2 +- pkg/build/stage/instruction/from.go | 70 ++++++++++++++++++ pkg/build/stage/interface.go | 2 +- pkg/buildah/common.go | 1 + pkg/buildah/native_linux.go | 8 +- pkg/container_backend/buildah_backend.go | 16 +++- .../docker_server_backend.go | 3 +- .../stage_builder/dockerfile_builder.go | 8 +- pkg/docker/image_info.go | 39 ++++++++++ pkg/docker_registry/api.go | 4 +- pkg/docker_registry/image_info.go | 24 ++++++ pkg/image/info.go | 73 ++++++------------- pkg/image/manifest_cache.go | 2 +- pkg/image/summary.go | 3 +- pkg/storage/manager/storage_manager.go | 2 +- pkg/werf/main.go | 9 +++ pkg/werf/staged_dockerfile.go | 14 ++++ 21 files changed, 327 insertions(+), 120 deletions(-) create mode 100644 pkg/build/stage/instruction/from.go create mode 100644 pkg/docker/image_info.go create mode 100644 pkg/docker_registry/image_info.go create mode 100644 pkg/werf/staged_dockerfile.go diff --git a/pkg/build/build_phase.go b/pkg/build/build_phase.go index bfc84ce1dc..b6c1a61ba5 100644 --- a/pkg/build/build_phase.go +++ b/pkg/build/build_phase.go @@ -465,7 +465,7 @@ func (phase *BuildPhase) createReport(ctx context.Context) error { DockerRepo: desc.Info.Repository, DockerTag: desc.Info.Tag, DockerImageID: desc.Info.ID, - DockerImageDigest: desc.Info.RepoDigest, + DockerImageDigest: desc.Info.GetDigest(), DockerImageName: desc.Info.Name, Rebuilt: img.GetRebuilt(), } @@ -497,7 +497,7 @@ func (phase *BuildPhase) createReport(ctx context.Context) error { DockerRepo: desc.Info.Repository, DockerTag: desc.Info.Tag, DockerImageID: desc.Info.ID, - DockerImageDigest: desc.Info.RepoDigest, + DockerImageDigest: desc.Info.GetDigest(), DockerImageName: desc.Info.Name, Rebuilt: isRebuilt, } @@ -874,13 +874,18 @@ func (phase *BuildPhase) calculateStage(ctx context.Context, img *image.Image, s return false, nil, err } - var opts calculateDigestOption - if img.IsDockerfileImage && img.DockerfileImageConfig.Staged { - if !stg.HasPrevStage() { - opts.BaseImage = img.GetBaseImageReference() + var opts calculateDigestOptions + // TODO: common cache version / per image cache version / fromCacheVersion goes into this + opts.CacheVersionParts = nil + opts.TargetPlatform = img.TargetPlatform + + if werf.GetStagedDockerfileVersion() == werf.StagedDockerfileV1 { + if img.IsDockerfileImage && img.DockerfileImageConfig.Staged { + if !stg.HasPrevStage() { + opts.BaseImage = img.GetBaseImageReference() + } } } - opts.TargetPlatform = img.TargetPlatform stageDigest, err := calculateDigest(ctx, stage.GetLegacyCompatibleStageName(stg.Name()), stageDependencies, phase.StagesIterator.PrevNonEmptyStage, phase.Conveyor, opts) if err != nil { @@ -911,7 +916,7 @@ func (phase *BuildPhase) calculateStage(ctx context.Context, img *image.Image, s } } - stageContentSig, err := calculateDigest(ctx, fmt.Sprintf("%s-content", stg.Name()), "", stg, phase.Conveyor, calculateDigestOption{TargetPlatform: img.TargetPlatform}) + stageContentSig, err := calculateDigest(ctx, fmt.Sprintf("%s-content", stg.Name()), "", stg, phase.Conveyor, calculateDigestOptions{TargetPlatform: img.TargetPlatform}) if err != nil { return false, phase.Conveyor.GetStageDigestMutex(stg.GetDigest()).Unlock, fmt.Errorf("unable to calculate stage %s content digest: %w", stg.Name(), err) } @@ -1032,8 +1037,8 @@ func (phase *BuildPhase) buildStage(ctx context.Context, img *image.Image, stg s options.Style(style.Highlight()) }). DoError(func() (err error) { - if err := stg.PreRunHook(ctx, phase.Conveyor); err != nil { - return fmt.Errorf("%s preRunHook failed: %w", stg.LogDetailedName(), err) + if err := stg.PreRun(ctx, phase.Conveyor); err != nil { + return fmt.Errorf("%s preRun failed: %w", stg.LogDetailedName(), err) } return phase.atomicBuildStageImage(ctx, img, stg) @@ -1168,12 +1173,13 @@ func introspectStage(ctx context.Context, s stage.Interface) error { }) } -type calculateDigestOption struct { - BaseImage string - TargetPlatform string +type calculateDigestOptions struct { + TargetPlatform string + CacheVersionParts []string + BaseImage string // TODO(staged-dockerfile): legacy compatibility field } -func calculateDigest(ctx context.Context, stageName, stageDependencies string, prevNonEmptyStage stage.Interface, conveyor *Conveyor, opts calculateDigestOption) (string, error) { +func calculateDigest(ctx context.Context, stageName, stageDependencies string, prevNonEmptyStage stage.Interface, conveyor *Conveyor, opts calculateDigestOptions) (string, error) { var checksumArgs []string var checksumArgsNames []string @@ -1203,6 +1209,15 @@ func calculateDigest(ctx context.Context, stageName, stageDependencies string, p ) } + if len(opts.CacheVersionParts) > 0 { + for i, cacheVersion := range opts.CacheVersionParts { + name := fmt.Sprintf("CacheVersion%d", i) + checksumArgsNames = append(checksumArgsNames, name) + checksumArgs = append(checksumArgs, name, cacheVersion) + } + } + + // TODO(staged-dockerfile): this is legacy digest part used for StagedDockerfileV1 if opts.BaseImage != "" { checksumArgs = append(checksumArgs, opts.BaseImage) checksumArgsNames = append(checksumArgsNames, "BaseImage") diff --git a/pkg/build/conveyor.go b/pkg/build/conveyor.go index 170b525a38..2759de321b 100644 --- a/pkg/build/conveyor.go +++ b/pkg/build/conveyor.go @@ -852,7 +852,7 @@ func (c *Conveyor) GetImageIDForLastImageStage(targetPlatform, imageName string) } func (c *Conveyor) GetImageDigestForLastImageStage(targetPlatform, imageName string) string { - return c.GetImage(targetPlatform, imageName).GetLastNonEmptyStage().GetStageImage().Image.GetStageDescription().Info.RepoDigest + return c.GetImage(targetPlatform, imageName).GetLastNonEmptyStage().GetStageImage().Image.GetStageDescription().Info.GetDigest() } func (c *Conveyor) GetImageIDForImageStage(targetPlatform, imageName, stageName string) string { diff --git a/pkg/build/image/dockerfile.go b/pkg/build/image/dockerfile.go index 39ca55833e..b9bd50b586 100644 --- a/pkg/build/image/dockerfile.go +++ b/pkg/build/image/dockerfile.go @@ -20,6 +20,7 @@ import ( "github.com/werf/werf/pkg/giterminism_manager" "github.com/werf/werf/pkg/path_matcher" "github.com/werf/werf/pkg/util" + "github.com/werf/werf/pkg/werf" ) func MapDockerfileConfigToImagesSets(ctx context.Context, dockerfileImageConfig *config.ImageFromDockerfile, targetPlatform string, opts CommonImageOptions) (ImagesSets, error) { @@ -152,54 +153,70 @@ func mapDockerfileToImagesSets(ctx context.Context, cfg *dockerfile.Dockerfile, } } - for ind, instr := range stg.Instructions { - stageLogName := fmt.Sprintf("%s%d", strings.ToUpper(instr.GetInstructionData().Name()), ind+1) + commonBaseStageOptions := &stage.BaseStageOptions{ + TargetPlatform: img.TargetPlatform, + ImageName: img.Name, + ImageTmpDir: img.TmpDir, + ContainerWerfDir: img.ContainerWerfDir, + ProjectName: opts.ProjectName, + } + + var instrNum int + + if werf.GetStagedDockerfileVersion() == werf.StagedDockerfileV2 { + baseStageOptions := *commonBaseStageOptions + baseStageOptions.LogName = "FROM1" + img.stages = append(img.stages, stage_instruction.NewFrom( + img.GetBaseImageReference(), img.GetBaseImageRepoDigest(), + &baseStageOptions, + )) + instrNum = 1 + } else { + instrNum = 0 + } + + for _, instr := range stg.Instructions { + stageLogName := fmt.Sprintf("%s%d", strings.ToUpper(instr.GetInstructionData().Name()), instrNum+1) isFirstStage := (len(img.stages) == 0) - baseStageOptions := &stage.BaseStageOptions{ - TargetPlatform: img.TargetPlatform, - ImageName: img.Name, - LogName: stageLogName, - ImageTmpDir: img.TmpDir, - ContainerWerfDir: img.ContainerWerfDir, - ProjectName: opts.ProjectName, - } + baseStageOptions := *commonBaseStageOptions + baseStageOptions.LogName = stageLogName var stg stage.Interface switch typedInstr := any(instr).(type) { case *dockerfile.DockerfileStageInstruction[*instructions.ArgCommand]: continue case *dockerfile.DockerfileStageInstruction[*instructions.AddCommand]: - stg = stage_instruction.NewAdd(typedInstr, dockerfileImageConfig.Dependencies, !isFirstStage, baseStageOptions) + stg = stage_instruction.NewAdd(typedInstr, dockerfileImageConfig.Dependencies, !isFirstStage, &baseStageOptions) case *dockerfile.DockerfileStageInstruction[*instructions.CmdCommand]: - stg = stage_instruction.NewCmd(typedInstr, dockerfileImageConfig.Dependencies, !isFirstStage, baseStageOptions) + stg = stage_instruction.NewCmd(typedInstr, dockerfileImageConfig.Dependencies, !isFirstStage, &baseStageOptions) case *dockerfile.DockerfileStageInstruction[*instructions.CopyCommand]: - stg = stage_instruction.NewCopy(typedInstr, dockerfileImageConfig.Dependencies, !isFirstStage, baseStageOptions) + stg = stage_instruction.NewCopy(typedInstr, dockerfileImageConfig.Dependencies, !isFirstStage, &baseStageOptions) case *dockerfile.DockerfileStageInstruction[*instructions.EntrypointCommand]: - stg = stage_instruction.NewEntrypoint(typedInstr, dockerfileImageConfig.Dependencies, !isFirstStage, baseStageOptions) + stg = stage_instruction.NewEntrypoint(typedInstr, dockerfileImageConfig.Dependencies, !isFirstStage, &baseStageOptions) case *dockerfile.DockerfileStageInstruction[*instructions.EnvCommand]: - stg = stage_instruction.NewEnv(typedInstr, dockerfileImageConfig.Dependencies, !isFirstStage, baseStageOptions) + stg = stage_instruction.NewEnv(typedInstr, dockerfileImageConfig.Dependencies, !isFirstStage, &baseStageOptions) case *dockerfile.DockerfileStageInstruction[*instructions.ExposeCommand]: - stg = stage_instruction.NewExpose(typedInstr, dockerfileImageConfig.Dependencies, !isFirstStage, baseStageOptions) + stg = stage_instruction.NewExpose(typedInstr, dockerfileImageConfig.Dependencies, !isFirstStage, &baseStageOptions) case *dockerfile.DockerfileStageInstruction[*instructions.HealthCheckCommand]: - stg = stage_instruction.NewHealthcheck(typedInstr, dockerfileImageConfig.Dependencies, !isFirstStage, baseStageOptions) + stg = stage_instruction.NewHealthcheck(typedInstr, dockerfileImageConfig.Dependencies, !isFirstStage, &baseStageOptions) case *dockerfile.DockerfileStageInstruction[*instructions.LabelCommand]: - stg = stage_instruction.NewLabel(typedInstr, dockerfileImageConfig.Dependencies, !isFirstStage, baseStageOptions) + stg = stage_instruction.NewLabel(typedInstr, dockerfileImageConfig.Dependencies, !isFirstStage, &baseStageOptions) case *dockerfile.DockerfileStageInstruction[*instructions.MaintainerCommand]: - stg = stage_instruction.NewMaintainer(typedInstr, dockerfileImageConfig.Dependencies, !isFirstStage, baseStageOptions) + stg = stage_instruction.NewMaintainer(typedInstr, dockerfileImageConfig.Dependencies, !isFirstStage, &baseStageOptions) case *dockerfile.DockerfileStageInstruction[*instructions.OnbuildCommand]: - stg = stage_instruction.NewOnBuild(typedInstr, dockerfileImageConfig.Dependencies, !isFirstStage, baseStageOptions) + stg = stage_instruction.NewOnBuild(typedInstr, dockerfileImageConfig.Dependencies, !isFirstStage, &baseStageOptions) case *dockerfile.DockerfileStageInstruction[*instructions.RunCommand]: - stg = stage_instruction.NewRun(typedInstr, dockerfileImageConfig.Dependencies, !isFirstStage, baseStageOptions) + stg = stage_instruction.NewRun(typedInstr, dockerfileImageConfig.Dependencies, !isFirstStage, &baseStageOptions) case *dockerfile.DockerfileStageInstruction[*instructions.ShellCommand]: - stg = stage_instruction.NewShell(typedInstr, dockerfileImageConfig.Dependencies, !isFirstStage, baseStageOptions) + stg = stage_instruction.NewShell(typedInstr, dockerfileImageConfig.Dependencies, !isFirstStage, &baseStageOptions) case *dockerfile.DockerfileStageInstruction[*instructions.StopSignalCommand]: - stg = stage_instruction.NewStopSignal(typedInstr, dockerfileImageConfig.Dependencies, !isFirstStage, baseStageOptions) + stg = stage_instruction.NewStopSignal(typedInstr, dockerfileImageConfig.Dependencies, !isFirstStage, &baseStageOptions) case *dockerfile.DockerfileStageInstruction[*instructions.UserCommand]: - stg = stage_instruction.NewUser(typedInstr, dockerfileImageConfig.Dependencies, !isFirstStage, baseStageOptions) + stg = stage_instruction.NewUser(typedInstr, dockerfileImageConfig.Dependencies, !isFirstStage, &baseStageOptions) case *dockerfile.DockerfileStageInstruction[*instructions.VolumeCommand]: - stg = stage_instruction.NewVolume(typedInstr, dockerfileImageConfig.Dependencies, !isFirstStage, baseStageOptions) + stg = stage_instruction.NewVolume(typedInstr, dockerfileImageConfig.Dependencies, !isFirstStage, &baseStageOptions) case *dockerfile.DockerfileStageInstruction[*instructions.WorkdirCommand]: - stg = stage_instruction.NewWorkdir(typedInstr, dockerfileImageConfig.Dependencies, !isFirstStage, baseStageOptions) + stg = stage_instruction.NewWorkdir(typedInstr, dockerfileImageConfig.Dependencies, !isFirstStage, &baseStageOptions) default: panic(fmt.Sprintf("unsupported instruction type %#v", instr)) } @@ -209,6 +226,8 @@ func mapDockerfileToImagesSets(ctx context.Context, cfg *dockerfile.Dockerfile, for _, dep := range instr.GetDependenciesByStageRef() { appendQueue(dep.GetWerfImageName(), dep, item.Level+1) } + + instrNum++ } appendImageToCurrentSet(img) diff --git a/pkg/build/image/image.go b/pkg/build/image/image.go index 0f0260d064..a78531230c 100644 --- a/pkg/build/image/image.go +++ b/pkg/build/image/image.go @@ -80,7 +80,7 @@ func NewImage(ctx context.Context, targetPlatform, name string, baseImageType Ba } if opts.FetchLatestBaseImage { - if _, err := i.getFromBaseImageIdFromRegistry(ctx, i.baseImageReference); err != nil { + if err := i.setupBaseImageRepoDigest(ctx, i.baseImageReference); err != nil { return nil, fmt.Errorf("error fetching base image id from registry: %w", err) } } @@ -108,7 +108,10 @@ type Image struct { baseImageName string dockerfileExpanderFactory dockerfile.ExpanderFactory - baseImageRepoId string + // NOTICE: baseImageRepoId is a legacy field, better use Digest instead everywhere + baseImageRepoId string + baseImageRepoDigest string + baseStageImage *stage.StageImage stageAsBaseImage stage.Interface } @@ -233,6 +236,8 @@ func isUnsupportedMediaTypeError(err error) bool { } func (i *Image) SetupBaseImage(ctx context.Context, storageManager manager.StorageManagerInterface, storageOpts manager.StorageOptions) error { + logboek.Context(ctx).Debug().LogF(" -- SetupBaseImage for %q\n", i.Name) + switch i.baseImageType { case StageAsBaseImage: i.stageAsBaseImage = i.Conveyor.GetImage(i.TargetPlatform, i.baseImageName).GetLastNonEmptyStage() @@ -318,7 +323,13 @@ func (i *Image) GetBaseImageReference() string { return i.baseImageReference } +func (i *Image) GetBaseImageRepoDigest() string { + return i.baseImageRepoDigest +} + func (i *Image) FetchBaseImage(ctx context.Context) error { + logboek.Context(ctx).Debug().LogF(" -- FetchBaseImage for %q\n", i.Name) + switch i.baseImageType { case ImageFromRegistryAsBaseImage: if i.baseStageImage.Image.Name() == "scratch" { @@ -330,20 +341,24 @@ func (i *Image) FetchBaseImage(ctx context.Context) error { if info, err := i.ContainerBackend.GetImageInfo(ctx, i.baseStageImage.Image.Name(), container_backend.GetImageInfoOpts{}); err != nil { return fmt.Errorf("unable to inspect local image %s: %w", i.baseStageImage.Image.Name(), err) } else if info != nil { + logboek.Context(ctx).Debug().LogF("GetImageInfo of %q -> %#v\n", i.baseStageImage.Image.Name(), info) + // TODO: do not use container_backend.LegacyStageImage for base image i.baseStageImage.Image.SetStageDescription(&image.StageDescription{ StageID: nil, // this is not a stage actually, TODO Info: info, }) - baseImageRepoId, err := i.getFromBaseImageIdFromRegistry(ctx, i.baseStageImage.Image.Name()) - if baseImageRepoId == info.ID || (err != nil && !isUnsupportedMediaTypeError(err)) { + err = i.setupBaseImageRepoDigest(ctx, i.baseStageImage.Image.Name()) + if (i.baseImageRepoDigest != "" && i.baseImageRepoDigest == info.RepoDigest) || (err != nil && !isUnsupportedMediaTypeError(err)) { if err != nil { logboek.Context(ctx).Warn().LogF("WARNING: cannot get base image id (%s): %s\n", i.baseStageImage.Image.Name(), err) logboek.Context(ctx).Warn().LogF("WARNING: using existing image %s without pull\n", i.baseStageImage.Image.Name()) logboek.Context(ctx).Warn().LogOptionalLn() + } else { + logboek.Context(ctx).Info().LogF("No pull needed for base image %s of image %q: image by digest %s is up to date\n", i.baseImageReference, i.Name, i.baseImageRepoDigest) } - + // No image pull return nil } } @@ -384,18 +399,27 @@ func (i *Image) FetchBaseImage(ctx context.Context) error { } } -func (i *Image) getFromBaseImageIdFromRegistry(ctx context.Context, reference string) (string, error) { +func packRepoIDAndDigest(repoID, digest string) string { + return fmt.Sprintf("%s/%s", repoID, digest) +} + +func unpackRepoIDAndDigest(packed string) (string, string) { + parts := strings.SplitN(packed, "/", 2) + return parts[0], parts[1] +} + +func (i *Image) setupBaseImageRepoDigest(ctx context.Context, reference string) error { i.Conveyor.GetServiceRWMutex("baseImagesRepoIdsCache" + reference).Lock() defer i.Conveyor.GetServiceRWMutex("baseImagesRepoIdsCache" + reference).Unlock() switch { case i.baseImageRepoId != "": - return i.baseImageRepoId, nil + return nil case i.Conveyor.IsBaseImagesRepoIdsCacheExist(reference): - i.baseImageRepoId = i.Conveyor.GetBaseImagesRepoIdsCache(reference) - return i.baseImageRepoId, nil + i.baseImageRepoId, i.baseImageRepoDigest = unpackRepoIDAndDigest(i.Conveyor.GetBaseImagesRepoIdsCache(reference)) + return nil case i.Conveyor.IsBaseImagesRepoErrCacheExist(reference): - return "", i.Conveyor.GetBaseImagesRepoErrCache(reference) + return i.Conveyor.GetBaseImagesRepoErrCache(reference) } var fetchedBaseRepoImage *image.Info @@ -410,13 +434,14 @@ func (i *Image) getFromBaseImageIdFromRegistry(ctx context.Context, reference st return nil }); err != nil { - return "", err + return err } i.baseImageRepoId = fetchedBaseRepoImage.ID - i.Conveyor.SetBaseImagesRepoIdsCache(reference, i.baseImageRepoId) + i.baseImageRepoDigest = fetchedBaseRepoImage.RepoDigest + i.Conveyor.SetBaseImagesRepoIdsCache(reference, packRepoIDAndDigest(i.baseImageRepoId, i.baseImageRepoDigest)) - return i.baseImageRepoId, nil + return nil } func EnvToMap(env []string) map[string]string { diff --git a/pkg/build/stage/base.go b/pkg/build/stage/base.go index d4fcb4ccf4..6384223229 100644 --- a/pkg/build/stage/base.go +++ b/pkg/build/stage/base.go @@ -299,7 +299,7 @@ func (s *BaseStage) PrepareImage(ctx context.Context, c Conveyor, cb container_b return nil } -func (s *BaseStage) PreRunHook(_ context.Context, _ Conveyor) error { +func (s *BaseStage) PreRun(_ context.Context, _ Conveyor) error { return nil } diff --git a/pkg/build/stage/instruction/from.go b/pkg/build/stage/instruction/from.go new file mode 100644 index 0000000000..3787e895f8 --- /dev/null +++ b/pkg/build/stage/instruction/from.go @@ -0,0 +1,70 @@ +package instruction + +import ( + "context" + + "github.com/werf/logboek" + "github.com/werf/werf/pkg/build/stage" + "github.com/werf/werf/pkg/container_backend" + "github.com/werf/werf/pkg/docker_registry" + "github.com/werf/werf/pkg/util" +) + +type From struct { + stage.BaseStage + + BaseImageReference string + BaseImageRepoDigest string +} + +func NewFrom(baseImageReference, baseImageRepoDigest string, opts *stage.BaseStageOptions) *From { + return &From{ + BaseImageReference: baseImageReference, + BaseImageRepoDigest: baseImageRepoDigest, + BaseStage: *stage.NewBaseStage( + stage.StageName("FROM"), + opts, + ), + } +} + +func (stg *From) HasPrevStage() bool { + return false +} + +func (stg *From) IsStapelStage() bool { + return false +} + +func (stg *From) UsesBuildContext() bool { + return false +} + +func (stg *From) ExpandDependencies(ctx context.Context, c stage.Conveyor, baseEnv map[string]string) error { + return nil +} + +func (stg *From) PrepareImage(ctx context.Context, c stage.Conveyor, cb container_backend.ContainerBackend, prevBuiltImage, stageImage *stage.StageImage, buildContextArchive container_backend.BuildContextArchiver) error { + return nil +} + +func (s *From) FetchDependencies(_ context.Context, _ stage.Conveyor, _ container_backend.ContainerBackend, _ docker_registry.ApiInterface) error { + return nil +} + +func (s *From) PreRun(ctx context.Context, _ stage.Conveyor) error { + logboek.Context(ctx).LogFDetails(" ref: %s\n", s.BaseImageReference) + if s.BaseImageRepoDigest != "" { + logboek.Context(ctx).LogFDetails(" digest: %s\n", s.BaseImageRepoDigest) + } + return nil +} + +func (s *From) GetDependencies(ctx context.Context, c stage.Conveyor, cb container_backend.ContainerBackend, prevImage, prevBuiltImage *stage.StageImage, buildContextArchive container_backend.BuildContextArchiver) (string, error) { + var args []string + args = append(args, "BaseImageReference", s.BaseImageReference) + if s.BaseImageRepoDigest != "" { + args = append(args, "BaseImageRepoDigest", s.BaseImageRepoDigest) + } + return util.Sha256Hash(args...), nil +} diff --git a/pkg/build/stage/interface.go b/pkg/build/stage/interface.go index a9cbd424c6..279f20f6b1 100644 --- a/pkg/build/stage/interface.go +++ b/pkg/build/stage/interface.go @@ -21,7 +21,7 @@ type Interface interface { PrepareImage(ctx context.Context, c Conveyor, cb container_backend.ContainerBackend, prevBuiltImage, stageImage *StageImage, buildContextArchive container_backend.BuildContextArchiver) error - PreRunHook(context.Context, Conveyor) error + PreRun(context.Context, Conveyor) error SetDigest(digest string) GetDigest() string diff --git a/pkg/buildah/common.go b/pkg/buildah/common.go index 0e56ebd054..656e3d053b 100644 --- a/pkg/buildah/common.go +++ b/pkg/buildah/common.go @@ -126,6 +126,7 @@ type AddOpts struct { type ImagesOptions struct { CommitOpts + Names []string Filters []util.Pair[string, string] } diff --git a/pkg/buildah/native_linux.go b/pkg/buildah/native_linux.go index 1775abe70d..c50bdcb9be 100644 --- a/pkg/buildah/native_linux.go +++ b/pkg/buildah/native_linux.go @@ -815,7 +815,7 @@ func (b *NativeBuildah) Images(ctx context.Context, opts ImagesOptions) (image.I for _, filter := range opts.Filters { listOpts.Filters = append(listOpts.Filters, fmt.Sprintf("%s=%s", filter.First, filter.Second)) } - images, err := runtime.ListImages(ctx, nil, listOpts) + images, err := runtime.ListImages(ctx, opts.Names, listOpts) if err != nil { return nil, err } @@ -826,7 +826,11 @@ func (b *NativeBuildah) Images(ctx context.Context, opts ImagesOptions) (image.I if err != nil { return nil, fmt.Errorf("unable to get image %s repo tags: %w", img.ID(), err) } - res = append(res, image.Summary{RepoTags: repoTags}) + repoDigests, err := img.RepoDigests() + if err != nil { + return nil, fmt.Errorf("unable to get image %s repo digests: %w", img.ID(), err) + } + res = append(res, image.Summary{RepoTags: repoTags, RepoDigests: repoDigests}) } return res, nil diff --git a/pkg/container_backend/buildah_backend.go b/pkg/container_backend/buildah_backend.go index 9ca9cf2268..c21ba8758e 100644 --- a/pkg/container_backend/buildah_backend.go +++ b/pkg/container_backend/buildah_backend.go @@ -576,8 +576,6 @@ func (backend *BuildahBackend) GetImageInfo(ctx context.Context, ref string, opt return nil, nil } - repository, tag := image.ParseRepositoryAndTag(ref) - var parentID string if id, ok := inspect.Docker.Config.Labels["werf.io/base-image-id"]; ok { parentID = id @@ -585,13 +583,25 @@ func (backend *BuildahBackend) GetImageInfo(ctx context.Context, ref string, opt parentID = string(inspect.Docker.Parent) } + var repository, tag, repoDigest string + if !strings.HasPrefix(ref, "sha256:") { + repository, tag = image.ParseRepositoryAndTag(ref) + list, err := backend.buildah.Images(ctx, buildah.ImagesOptions{Names: []string{ref}}) + if err != nil { + return nil, fmt.Errorf("error getting buildah info for image %q: %w", ref, err) + } + if len(list) > 0 { + repoDigest = image.ExtractRepoDigest(list[0].RepoDigests, repository) + } + } + return &image.Info{ Name: ref, Repository: repository, + RepoDigest: repoDigest, Tag: tag, Labels: inspect.Docker.Config.Labels, CreatedAtUnixNano: inspect.Docker.Created.UnixNano(), - RepoDigest: inspect.FromImageDigest, OnBuild: inspect.Docker.Config.OnBuild, Env: inspect.Docker.Config.Env, ID: inspect.FromImageID, diff --git a/pkg/container_backend/docker_server_backend.go b/pkg/container_backend/docker_server_backend.go index 83f1bcc3b7..397fa13ad8 100644 --- a/pkg/container_backend/docker_server_backend.go +++ b/pkg/container_backend/docker_server_backend.go @@ -124,8 +124,7 @@ func (backend *DockerServerBackend) GetImageInfo(ctx context.Context, ref string } else if err != nil { return nil, fmt.Errorf("unable to inspect docker image: %w", err) } - - return image.NewInfoFromInspect(ref, inspect), nil + return docker.NewInfoFromInspect(ref, inspect), nil } // GetImageInspect only available for DockerServerBackend diff --git a/pkg/container_backend/stage_builder/dockerfile_builder.go b/pkg/container_backend/stage_builder/dockerfile_builder.go index 5d0048e6a1..cfe074eeb2 100644 --- a/pkg/container_backend/stage_builder/dockerfile_builder.go +++ b/pkg/container_backend/stage_builder/dockerfile_builder.go @@ -65,9 +65,11 @@ func (b *DockerfileBuilder) Cleanup(ctx context.Context) error { return nil } - logboek.Context(ctx).Info().LogF("Cleanup built dockerfile image %q\n", b.Image.BuiltID()) - if err := b.ContainerBackend.Rmi(ctx, b.Image.BuiltID(), container_backend.RmiOpts{}); err != nil { - return fmt.Errorf("unable to remove built dockerfile image %q: %w", b.Image.BuiltID(), err) + if b.Image.BuiltID() != "" { + logboek.Context(ctx).Info().LogF("Cleanup built dockerfile image %q\n", b.Image.BuiltID()) + if err := b.ContainerBackend.Rmi(ctx, b.Image.BuiltID(), container_backend.RmiOpts{}); err != nil { + return fmt.Errorf("unable to remove built dockerfile image %q: %w", b.Image.BuiltID(), err) + } } return nil } diff --git a/pkg/docker/image_info.go b/pkg/docker/image_info.go new file mode 100644 index 0000000000..36223de1cc --- /dev/null +++ b/pkg/docker/image_info.go @@ -0,0 +1,39 @@ +package docker + +import ( + "strings" + + "github.com/docker/docker/api/types" + + "github.com/werf/werf/pkg/image" +) + +func NewInfoFromInspect(ref string, inspect *types.ImageInspect) *image.Info { + var repository, tag, repoDigest string + if !strings.HasPrefix(ref, "sha256:") { + repository, tag = image.ParseRepositoryAndTag(ref) + repoDigest = image.ExtractRepoDigest(inspect.RepoDigests, repository) + } + + var parentID string + if id, ok := inspect.Config.Labels["werf.io/base-image-id"]; ok { + parentID = id + } else { + // TODO(1.3): Legacy compatibility mode + parentID = inspect.Config.Image + } + + return &image.Info{ + Name: ref, + Repository: repository, + Tag: tag, + Labels: inspect.Config.Labels, + OnBuild: inspect.Config.OnBuild, + Env: inspect.Config.Env, + CreatedAtUnixNano: image.MustParseTimestampString(inspect.Created).UnixNano(), + RepoDigest: repoDigest, + ID: inspect.ID, + ParentID: parentID, + Size: inspect.Size, + } +} diff --git a/pkg/docker_registry/api.go b/pkg/docker_registry/api.go index ebcde05a07..50cbdbabca 100644 --- a/pkg/docker_registry/api.go +++ b/pkg/docker_registry/api.go @@ -153,7 +153,7 @@ func (api *api) getRepoImageByDesc(ctx context.Context, originalTag string, desc if err != nil { return nil, fmt.Errorf("error getting image index digest: %w", err) } - repoImage.RepoDigest = digest.String() + repoImage.RepoDigest = fmt.Sprintf("%s@%s", image.NormalizeRepository(repoImage.Repository), digest.String()) im, err := ii.IndexManifest() if err != nil { @@ -183,7 +183,7 @@ func (api *api) getRepoImageByDesc(ctx context.Context, originalTag string, desc if err != nil { return nil, err } - repoImage.RepoDigest = digest.String() + repoImage.RepoDigest = fmt.Sprintf("%s@%s", image.NormalizeRepository(repoImage.Repository), digest.String()) manifest, err := imageInfo.Manifest() if err != nil { diff --git a/pkg/docker_registry/image_info.go b/pkg/docker_registry/image_info.go new file mode 100644 index 0000000000..6ef63a2933 --- /dev/null +++ b/pkg/docker_registry/image_info.go @@ -0,0 +1,24 @@ +package docker_registry + +import ( + v1 "github.com/google/go-containerregistry/pkg/v1" + + "github.com/werf/werf/pkg/image" +) + +func NewImageInfoFromRegistryConfig(ref string, cfg *v1.ConfigFile) *image.Info { + repository, tag := image.ParseRepositoryAndTag(ref) + return &image.Info{ + Name: ref, + Repository: repository, + Tag: tag, + Labels: cfg.Config.Labels, + OnBuild: cfg.Config.OnBuild, + Env: cfg.Config.Env, + CreatedAtUnixNano: cfg.Created.UnixNano(), + RepoDigest: "", // TODO + ID: "", // TODO + ParentID: "", // TODO + Size: 0, // TODO + } +} diff --git a/pkg/image/info.go b/pkg/image/info.go index 06e817d0c1..fc434f3670 100644 --- a/pkg/image/info.go +++ b/pkg/image/info.go @@ -5,12 +5,14 @@ import ( "strings" "time" - "github.com/docker/docker/api/types" - v1 "github.com/google/go-containerregistry/pkg/v1" - "github.com/werf/werf/pkg/util" ) +const ( + DockerHubRepositoryPrefix = "docker.io/library/" + IndexDockerHubRepositoryPrefix = "index.docker.io/library/" +) + type Info struct { Name string `json:"name"` Repository string `json:"repository"` @@ -29,6 +31,10 @@ type Info struct { Index []*Info } +func (info *Info) GetDigest() string { + return strings.TrimPrefix(info.RepoDigest, fmt.Sprintf("%s@", info.Repository)) +} + func (info *Info) SetCreatedAtUnix(seconds int64) { info.CreatedAtUnixNano = seconds * 1000_000_000 } @@ -73,38 +79,6 @@ func (info *Info) LogName() string { } } -func NewInfoFromInspect(ref string, inspect *types.ImageInspect) *Info { - repository, tag := ParseRepositoryAndTag(ref) - - var repoDigest string - if len(inspect.RepoDigests) > 0 { - // NOTE: suppose we have a single repo for each stage - repoDigest = inspect.RepoDigests[0] - } - - var parentID string - if id, ok := inspect.Config.Labels["werf.io/base-image-id"]; ok { - parentID = id - } else { - // TODO(1.3): Legacy compatibility mode - parentID = inspect.Config.Image - } - - return &Info{ - Name: ref, - Repository: repository, - Tag: tag, - Labels: inspect.Config.Labels, - OnBuild: inspect.Config.OnBuild, - Env: inspect.Config.Env, - CreatedAtUnixNano: MustParseTimestampString(inspect.Created).UnixNano(), - RepoDigest: repoDigest, - ID: inspect.ID, - ParentID: parentID, - Size: inspect.Size, - } -} - func MustParseTimestampString(timestampString string) time.Time { t, err := time.Parse(time.RFC3339, timestampString) if err != nil { @@ -123,19 +97,20 @@ func ParseRepositoryAndTag(ref string) (string, string) { return repository, tag } -func NewImageInfoFromRegistryConfig(ref string, cfg *v1.ConfigFile) *Info { - repository, tag := ParseRepositoryAndTag(ref) - return &Info{ - Name: ref, - Repository: repository, - Tag: tag, - Labels: cfg.Config.Labels, - OnBuild: cfg.Config.OnBuild, - Env: cfg.Config.Env, - CreatedAtUnixNano: cfg.Created.UnixNano(), - RepoDigest: "", // TODO - ID: "", // TODO - ParentID: "", // TODO - Size: 0, // TODO +func NormalizeRepository(repository string) (res string) { + res = repository + res = strings.TrimPrefix(res, IndexDockerHubRepositoryPrefix) + res = strings.TrimPrefix(res, DockerHubRepositoryPrefix) + return +} + +func ExtractRepoDigest(inspectRepoDigests []string, repository string) string { + for _, inspectRepoDigest := range inspectRepoDigests { + repoAndDigest := strings.SplitN(inspectRepoDigest, "@sha256:", 2) + repo := NormalizeRepository(repoAndDigest[0]) + if len(repoAndDigest) == 2 && NormalizeRepository(repository) == repo { + return fmt.Sprintf("%s@sha256:%s", repo, repoAndDigest[1]) + } } + return "" } diff --git a/pkg/image/manifest_cache.go b/pkg/image/manifest_cache.go index 7490973a4d..fb291f2e3e 100644 --- a/pkg/image/manifest_cache.go +++ b/pkg/image/manifest_cache.go @@ -17,7 +17,7 @@ import ( ) const ( - ManifestCacheVersion = "4" + ManifestCacheVersion = "5" ) type ManifestCache struct { diff --git a/pkg/image/summary.go b/pkg/image/summary.go index 7d95568c0f..b8a9cd60ba 100644 --- a/pkg/image/summary.go +++ b/pkg/image/summary.go @@ -6,7 +6,8 @@ import ( ) type Summary struct { - RepoTags []string + RepoTags []string + RepoDigests []string } type ImagesList []Summary diff --git a/pkg/storage/manager/storage_manager.go b/pkg/storage/manager/storage_manager.go index 11c5bce36f..5bcdbf3259 100644 --- a/pkg/storage/manager/storage_manager.go +++ b/pkg/storage/manager/storage_manager.go @@ -384,7 +384,7 @@ func (m *StorageManager) getImageInfoFromRegistry(ctx context.Context, ref strin if err != nil { return nil, err } - return image.NewImageInfoFromRegistryConfig(ref, cfg), nil + return docker_registry.NewImageInfoFromRegistryConfig(ref, cfg), nil } func (m *StorageManager) LockStageImage(ctx context.Context, imageName string) error { diff --git a/pkg/werf/main.go b/pkg/werf/main.go index 6f7e1e9f45..be6d017f8d 100644 --- a/pkg/werf/main.go +++ b/pkg/werf/main.go @@ -166,5 +166,14 @@ func Init(tmpDirOption, homeDirOption string) error { return fmt.Errorf("error setting werf last run at timestamp: %w", err) } + switch v := os.Getenv("WERF_STAGED_DOCKERFILE_VERSION"); v { + case "", "v1": + stagedDockerfileVersion = StagedDockerfileV1 + case "v2": + stagedDockerfileVersion = StagedDockerfileV2 + default: + return fmt.Errorf("unsupported WERF_STAGED_DOCKERFILE_VERSION=%q, expected v1 or v2 (recommended)", v) + } + return nil } diff --git a/pkg/werf/staged_dockerfile.go b/pkg/werf/staged_dockerfile.go new file mode 100644 index 0000000000..eb0f90107a --- /dev/null +++ b/pkg/werf/staged_dockerfile.go @@ -0,0 +1,14 @@ +package werf + +type StagedDockerfileVersion string + +const ( + StagedDockerfileV1 StagedDockerfileVersion = "StagedDockerfileV1" + StagedDockerfileV2 StagedDockerfileVersion = "StagedDockerfileV2" +) + +var stagedDockerfileVersion StagedDockerfileVersion + +func GetStagedDockerfileVersion() StagedDockerfileVersion { + return stagedDockerfileVersion +}