Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
fix(staged-dockerfile): eliminate excess manifest get request from ba…
…se image registry

Manifest getter used to fetch base image manifest needed to use base ENV-variables and ONBUILD instructions.
Now werf uses manifest of FROM1 stage for ENV-vars and ONBUILD instructions instead of base image manifest.

Additionally added debug tracer messages to log all requests to docker-registry api. Enable debug by setting WERF_DOCKER_REGISTRY_DEBUG=1.

Signed-off-by: Timofey Kirillov <timofey.kirillov@flant.com>
  • Loading branch information
distorhead committed Jun 13, 2023
1 parent 975668a commit 3103aff
Show file tree
Hide file tree
Showing 13 changed files with 249 additions and 58 deletions.
38 changes: 32 additions & 6 deletions pkg/build/build_phase.go
Expand Up @@ -21,6 +21,7 @@ import (
"github.com/werf/logboek/pkg/types"
"github.com/werf/werf/pkg/build/image"
"github.com/werf/werf/pkg/build/stage"
"github.com/werf/werf/pkg/build/stage/instruction"
"github.com/werf/werf/pkg/container_backend"
backend_instruction "github.com/werf/werf/pkg/container_backend/instruction"
"github.com/werf/werf/pkg/docker_registry"
Expand Down Expand Up @@ -693,15 +694,20 @@ func (phase *BuildPhase) getPrevNonEmptyStageImageSize() int64 {

func (phase *BuildPhase) OnImageStage(ctx context.Context, img *image.Image, stg stage.Interface) error {
return phase.StagesIterator.OnImageStage(ctx, img, stg, func(img *image.Image, stg stage.Interface, isEmpty bool) error {
return phase.onImageStage(ctx, img, stg, isEmpty)
if isEmpty {
return nil
}
if err := phase.onImageStage(ctx, img, stg); err != nil {
return err
}
if err := phase.afterImageStage(ctx, img, stg); err != nil {
return err
}
return nil
})
}

func (phase *BuildPhase) onImageStage(ctx context.Context, img *image.Image, stg stage.Interface, isEmpty bool) error {
if isEmpty {
return nil
}

func (phase *BuildPhase) onImageStage(ctx context.Context, img *image.Image, stg stage.Interface) error {
if err := stg.FetchDependencies(ctx, phase.Conveyor, phase.Conveyor.ContainerBackend, docker_registry.API()); err != nil {
return fmt.Errorf("unable to fetch dependencies for stage %s: %w", stg.LogDetailedName(), err)
}
Expand Down Expand Up @@ -767,6 +773,7 @@ func (phase *BuildPhase) onImageStage(ctx context.Context, img *image.Image, stg
}
}

// debug assertion
if stg.GetStageImage().Image.GetStageDescription() == nil {
panic(fmt.Sprintf("expected stage %s image %q built image info (image name = %s) to be set!", stg.Name(), img.GetName(), stg.GetStageImage().Image.Name()))
}
Expand All @@ -777,6 +784,25 @@ func (phase *BuildPhase) onImageStage(ctx context.Context, img *image.Image, stg
return nil
}

func (phase *BuildPhase) afterImageStage(ctx context.Context, img *image.Image, stg stage.Interface) error {
// TODO(staged-dockerfile): Expand possible ONBUILD instruction into specified intructions,
// TODO(staged-dockerfile): proxying ONBUILD instruction to chain of arbitrary instructions.

if img.IsDockerfileImage && img.DockerfileImageConfig.Staged {
if werf.GetStagedDockerfileVersion() == werf.StagedDockerfileV2 {
if _, isFromStage := stg.(*instruction.From); isFromStage {
fmt.Printf("image %q running Second Stage expansion using base env in FROM stage: %#v\n", img.Name, image.EnvToMap(stg.GetStageImage().Image.GetStageDescription().Info.Env))

if err := img.ExpandDependencies(ctx, image.EnvToMap(stg.GetStageImage().Image.GetStageDescription().Info.Env)); err != nil {
return err
}
}
}
}

return nil
}

func (phase *BuildPhase) findAndFetchStageFromSecondaryStagesStorage(ctx context.Context, img *image.Image, stg stage.Interface) (bool, error) {
foundSuitableStage := false

Expand Down
74 changes: 38 additions & 36 deletions pkg/build/image/image.go
Expand Up @@ -19,6 +19,7 @@ import (
"github.com/werf/werf/pkg/image"
"github.com/werf/werf/pkg/logging"
"github.com/werf/werf/pkg/storage/manager"
"github.com/werf/werf/pkg/werf"
)

type BaseImageType string
Expand Down Expand Up @@ -257,44 +258,42 @@ func (i *Image) SetupBaseImage(ctx context.Context, storageManager manager.Stora
i.baseStageImage = i.Conveyor.GetOrCreateStageImage(i.baseImageReference, nil, nil, i)

if i.IsDockerfileImage && i.DockerfileImageConfig.Staged {
var info *image.Info

if i.baseImageReference != "scratch" {
var err error
info, err = storageManager.GetImageInfo(ctx, i.baseImageReference, storageOpts)
if isUnsupportedMediaTypeError(err) {
if err := logboek.Context(ctx).Default().LogProcess("Pulling base image %s", i.baseStageImage.Image.Name()).
Options(func(options types.LogProcessOptionsInterface) {
options.Style(style.Highlight())
}).
DoError(func() error {
return i.ContainerBackend.PullImageFromRegistry(ctx, i.baseStageImage.Image)
}); err != nil {
return err
}
if werf.GetStagedDockerfileVersion() == werf.StagedDockerfileV1 {
var info *image.Info

if i.baseImageReference != "scratch" {
var err error
info, err = storageManager.GetImageInfo(ctx, i.baseImageReference, storageOpts)
if err != nil {
if isUnsupportedMediaTypeError(err) {
if err := logboek.Context(ctx).Default().LogProcess("Pulling base image %s", i.baseStageImage.Image.Name()).
Options(func(options types.LogProcessOptionsInterface) {
options.Style(style.Highlight())
}).
DoError(func() error {
return i.ContainerBackend.PullImageFromRegistry(ctx, i.baseStageImage.Image)
}); err != nil {
return err
}

info, err = storageManager.GetImageInfo(ctx, i.baseImageReference, storageOpts)
if err != nil {
return fmt.Errorf("unable to get base image %q manifest: %w", i.baseImageReference, err)
}
} else if err != nil {
return fmt.Errorf("unable to get base image %q manifest: %w", i.baseImageReference, err)
}
} else if err != nil {
return fmt.Errorf("unable to get base image %q manifest: %w", i.baseImageReference, err)
}
} else {
info = &image.Info{
Name: i.baseImageReference,
Env: nil,
} else {
info = &image.Info{
Name: i.baseImageReference,
Env: nil,
}
}
}

i.baseStageImage.Image.SetStageDescription(&image.StageDescription{
StageID: nil, // this is not a stage actually, TODO
Info: info,
})

// for _, expression := range info.OnBuild {
// fmt.Printf(">> %q\n", expression)
// }
i.baseStageImage.Image.SetStageDescription(&image.StageDescription{
StageID: nil, // this is not a stage actually, TODO
Info: info,
})
}
}
case NoBaseImage:

Expand All @@ -303,10 +302,12 @@ func (i *Image) SetupBaseImage(ctx context.Context, storageManager manager.Stora
}

if i.IsDockerfileImage && i.DockerfileImageConfig.Staged {
switch i.baseImageType {
case StageAsBaseImage, ImageFromRegistryAsBaseImage:
if err := i.ExpandDependencies(ctx, EnvToMap(i.baseStageImage.Image.GetStageDescription().Info.Env)); err != nil {
return err
if werf.GetStagedDockerfileVersion() == werf.StagedDockerfileV1 {
switch i.baseImageType {
case StageAsBaseImage, ImageFromRegistryAsBaseImage:
if err := i.ExpandDependencies(ctx, EnvToMap(i.baseStageImage.Image.GetStageDescription().Info.Env)); err != nil {
return err
}
}
}
}
Expand Down Expand Up @@ -382,6 +383,7 @@ func (i *Image) FetchBaseImage(ctx context.Context) error {
return fmt.Errorf("unable to inspect local image %s after successful pull: image is not exists", i.baseStageImage.Image.Name())
}

fmt.Printf("SETTING BASE STAGE IMAGE FOR %q to %q\n", i.Name, i.baseStageImage.Image.Name())
i.baseStageImage.Image.SetStageDescription(&image.StageDescription{
StageID: nil, // this is not a stage actually, TODO
Info: info,
Expand Down
2 changes: 1 addition & 1 deletion pkg/build/stage/base.go
Expand Up @@ -155,7 +155,7 @@ func (s *BaseStage) ExpandDependencies(ctx context.Context, c Conveyor, baseEnv
return nil
}

func (s *BaseStage) FetchDependencies(_ context.Context, _ Conveyor, _ container_backend.ContainerBackend, _ docker_registry.ApiInterface) error {
func (s *BaseStage) FetchDependencies(_ context.Context, _ Conveyor, _ container_backend.ContainerBackend, _ docker_registry.GenericApiInterface) error {
return nil
}

Expand Down
2 changes: 1 addition & 1 deletion pkg/build/stage/full_dockerfile.go
Expand Up @@ -294,7 +294,7 @@ type dockerfileInstructionInterface interface {
Name() string
}

func (s *FullDockerfileStage) FetchDependencies(ctx context.Context, c Conveyor, containerBackend container_backend.ContainerBackend, dockerRegistry docker_registry.ApiInterface) error {
func (s *FullDockerfileStage) FetchDependencies(ctx context.Context, c Conveyor, containerBackend container_backend.ContainerBackend, dockerRegistry docker_registry.GenericApiInterface) error {
resolvedDependenciesArgsHash := ResolveDependenciesArgs(s.targetPlatform, s.dependencies, c)

resolvedDockerMetaArgsHash, err := s.resolveDockerMetaArgs(resolvedDependenciesArgsHash)
Expand Down
2 changes: 1 addition & 1 deletion pkg/build/stage/instruction/from.go
Expand Up @@ -48,7 +48,7 @@ func (stg *From) PrepareImage(ctx context.Context, c stage.Conveyor, cb containe
return nil
}

func (s *From) FetchDependencies(_ context.Context, _ stage.Conveyor, _ container_backend.ContainerBackend, _ docker_registry.ApiInterface) error {
func (s *From) FetchDependencies(_ context.Context, _ stage.Conveyor, _ container_backend.ContainerBackend, _ docker_registry.GenericApiInterface) error {
return nil
}

Expand Down
2 changes: 1 addition & 1 deletion pkg/build/stage/interface.go
Expand Up @@ -15,7 +15,7 @@ type Interface interface {
IsEmpty(ctx context.Context, c Conveyor, prevBuiltImage *StageImage) (bool, error)

ExpandDependencies(ctx context.Context, c Conveyor, baseEnv map[string]string) error
FetchDependencies(ctx context.Context, c Conveyor, cb container_backend.ContainerBackend, dockerRegistry docker_registry.ApiInterface) error
FetchDependencies(ctx context.Context, c Conveyor, cb container_backend.ContainerBackend, dockerRegistry docker_registry.GenericApiInterface) error
GetDependencies(ctx context.Context, c Conveyor, cb container_backend.ContainerBackend, prevImage, prevBuiltImage *StageImage, buildContextArchive container_backend.BuildContextArchiver) (string, error)
GetNextStageDependencies(ctx context.Context, c Conveyor) (string, error)

Expand Down
2 changes: 1 addition & 1 deletion pkg/build/stage/stubs.go
Expand Up @@ -210,7 +210,7 @@ func (containerBackend *ContainerBackendStub) Pull(ctx context.Context, ref stri
}

type DockerRegistryApiStub struct {
docker_registry.ApiInterface
docker_registry.GenericApiInterface
}

func NewDockerRegistryApiStub() *DockerRegistryApiStub {
Expand Down
3 changes: 1 addition & 2 deletions pkg/docker_registry/api.go
Expand Up @@ -8,7 +8,6 @@ import (
"io"
"math/rand"
"net/http"
"os"
"regexp"
"strings"
"time"
Expand Down Expand Up @@ -100,7 +99,7 @@ func (api *api) tryGetRepoImage(ctx context.Context, reference, implementation s
if IsImageNotFoundError(err) || IsBrokenImageError(err) {
// TODO: 1. make sure werf never ever creates rejected image records for name-unknown errors.
// TODO: 2. werf-cleanup should remove broken images
if os.Getenv("WERF_DOCKER_REGISTRY_DEBUG") == "1" {
if debugDockerRegistry() {
logboek.Context(ctx).Error().LogF("WARNING: Got an error when inspecting repo image %q: %s\n", reference, err)
}
return nil, nil
Expand Down
144 changes: 144 additions & 0 deletions pkg/docker_registry/debug.go
@@ -0,0 +1,144 @@
package docker_registry

import (
"context"
"io"

v1 "github.com/google/go-containerregistry/pkg/v1"

"github.com/werf/logboek"
"github.com/werf/werf/pkg/image"
)

type DockerRegistryTracer struct {
DockerRegistry Interface
DockerRegistryApi GenericApiInterface
}

func NewDockerRegistryTracer(dockerRegistry Interface, dockerRegistryApi GenericApiInterface) *DockerRegistryTracer {
return &DockerRegistryTracer{
DockerRegistry: dockerRegistry,
DockerRegistryApi: dockerRegistryApi,
}
}

func (r *DockerRegistryTracer) CreateRepo(ctx context.Context, reference string) (err error) {
logboek.Context(ctx).Default().LogProcess("DockerRegistryTracer.CreateRepo %q", reference).Do(func() {
err = r.DockerRegistry.CreateRepo(ctx, reference)
})
return
}

func (r *DockerRegistryTracer) DeleteRepo(ctx context.Context, reference string) (err error) {
logboek.Context(ctx).Default().LogProcess("DockerRegistryTracer.DeleteRepo %q", reference).Do(func() {
err = r.DockerRegistry.DeleteRepo(ctx, reference)
})
return
}

func (r *DockerRegistryTracer) Tags(ctx context.Context, reference string, opts ...Option) (res []string, err error) {
logboek.Context(ctx).Default().LogProcess("DockerRegistryTracer.Tags %q", reference).Do(func() {
res, err = r.DockerRegistry.Tags(ctx, reference, opts...)
})
return
}

func (r *DockerRegistryTracer) IsTagExist(ctx context.Context, reference string, opts ...Option) (res bool, err error) {
logboek.Context(ctx).Default().LogProcess("DockerRegistryTracer.IsTagExist %q", reference).Do(func() {
res, err = r.DockerRegistry.IsTagExist(ctx, reference, opts...)
})
return
}

func (r *DockerRegistryTracer) TagRepoImage(ctx context.Context, repoImage *image.Info, tag string) (err error) {
logboek.Context(ctx).Default().LogProcess("DockerRegistryTracer.TagRepoImage %q", tag).Do(func() {
err = r.DockerRegistry.TagRepoImage(ctx, repoImage, tag)
})
return
}

func (r *DockerRegistryTracer) GetRepoImage(ctx context.Context, reference string) (res *image.Info, err error) {
logboek.Context(ctx).Default().LogProcess("DockerRegistryTracer.GetRepoImage %q", reference).Do(func() {
if r.DockerRegistry != nil {
res, err = r.DockerRegistry.GetRepoImage(ctx, reference)
} else {
res, err = r.DockerRegistryApi.GetRepoImage(ctx, reference)
}
})
return
}

func (r *DockerRegistryTracer) TryGetRepoImage(ctx context.Context, reference string) (res *image.Info, err error) {
logboek.Context(ctx).Default().LogProcess("DockerRegistryTracer.TryGetRepoImage %q", reference).Do(func() {
res, err = r.DockerRegistry.TryGetRepoImage(ctx, reference)
})
return
}

func (r *DockerRegistryTracer) DeleteRepoImage(ctx context.Context, repoImage *image.Info) (err error) {
logboek.Context(ctx).Default().LogProcess("DockerRegistryTracer.DeleteRepoImage %v", repoImage).Do(func() {
err = r.DockerRegistry.DeleteRepoImage(ctx, repoImage)
})
return
}

func (r *DockerRegistryTracer) PushImage(ctx context.Context, reference string, opts *PushImageOptions) (err error) {
logboek.Context(ctx).Default().LogProcess("DockerRegistryTracer.PushImage %q", reference).Do(func() {
err = r.DockerRegistry.PushImage(ctx, reference, opts)
})
return
}

func (r *DockerRegistryTracer) MutateAndPushImage(ctx context.Context, sourceReference, destinationReference string, mutateConfigFunc func(v1.Config) (v1.Config, error)) (err error) {
logboek.Context(ctx).Default().LogProcess("DockerRegistryTracer.MutateAndPushImage %q -> %q", sourceReference, destinationReference).Do(func() {
if r.DockerRegistry != nil {
err = r.DockerRegistry.MutateAndPushImage(ctx, sourceReference, destinationReference, mutateConfigFunc)
} else {
err = r.DockerRegistryApi.MutateAndPushImage(ctx, sourceReference, destinationReference, mutateConfigFunc)
}
})
return
}

func (r *DockerRegistryTracer) CopyImage(ctx context.Context, sourceReference, destinationReference string, opts CopyImageOptions) (err error) {
logboek.Context(ctx).Default().LogProcess("DockerRegistryTracer.CopyImage %q -> %q", sourceReference, destinationReference).Do(func() {
err = r.DockerRegistry.CopyImage(ctx, sourceReference, destinationReference, opts)
})
return
}

func (r *DockerRegistryTracer) PushImageArchive(ctx context.Context, archiveOpener ArchiveOpener, reference string) (err error) {
logboek.Context(ctx).Default().LogProcess("DockerRegistryTracer.PushImageArchive %q", reference).Do(func() {
err = r.DockerRegistry.PushImageArchive(ctx, archiveOpener, reference)
})
return
}

func (r *DockerRegistryTracer) PullImageArchive(ctx context.Context, archiveWriter io.Writer, reference string) (err error) {
logboek.Context(ctx).Default().LogProcess("DockerRegistryTracer.PullImageArchive %q", reference).Do(func() {
err = r.DockerRegistry.PullImageArchive(ctx, archiveWriter, reference)
})
return
}

func (r *DockerRegistryTracer) PushManifestList(ctx context.Context, reference string, opts ManifestListOptions) (err error) {
logboek.Context(ctx).Default().LogProcess("DockerRegistryTracer.PushManifestList %q", reference).Do(func() {
err = r.DockerRegistry.PushManifestList(ctx, reference, opts)
})
return
}

func (r *DockerRegistryTracer) String() (res string) {
return r.DockerRegistry.String()
}

func (r *DockerRegistryTracer) parseReferenceParts(reference string) (res referenceParts, err error) {
return r.DockerRegistry.parseReferenceParts(reference)
}

func (r *DockerRegistryTracer) GetRepoImageConfigFile(ctx context.Context, reference string) (res *v1.ConfigFile, err error) {
logboek.Context(ctx).Default().LogProcess("DockerRegistryTracer.GetRepoImageConfigFile %q", reference).Do(func() {
res, err = r.DockerRegistryApi.GetRepoImageConfigFile(ctx, reference)
})
return
}

0 comments on commit 3103aff

Please sign in to comment.