Skip to content

Commit

Permalink
build should check and only pull if requested
Browse files Browse the repository at this point in the history
Signed-off-by: Avi Deitcher <avi@deitcher.net>
  • Loading branch information
deitch committed Nov 1, 2022
1 parent 9ea2d6d commit a3995bb
Show file tree
Hide file tree
Showing 5 changed files with 129 additions and 22 deletions.
57 changes: 57 additions & 0 deletions src/cmd/linuxkit/cache/write.go
Original file line number Diff line number Diff line change
Expand Up @@ -353,3 +353,60 @@ func (p *Provider) DescriptorWrite(ref *reference.Spec, desc v1.Descriptor) (lkt
&desc,
), nil
}

func (p *Provider) ImageInCache(ref *reference.Spec, trustedRef, architecture string) (bool, error) {
if _, err := p.findImage(ref.String(), architecture); err != nil {
return false, err
}
return true, nil
}

// ImageInRegistry takes an image name and checks that the image manifest or index to which it refers
// exists in the registry.
func (p *Provider) ImageInRegistry(ref *reference.Spec, trustedRef, architecture string) (bool, error) {
image := ref.String()
remoteOptions := []remote.Option{remote.WithAuthFromKeychain(authn.DefaultKeychain)}
log.Debugf("Checking image %s in registry", image)

remoteRef, err := name.ParseReference(image)
if err != nil {
return false, fmt.Errorf("invalid image name %s: %v", image, err)
}

desc, err := remote.Get(remoteRef, remoteOptions...)
if err != nil {
return false, fmt.Errorf("error getting manifest for image %s: %v", image, err)
}
// first attempt as an index
ii, err := desc.ImageIndex()
if err == nil {
log.Debugf("ImageExists retrieved %s as index", remoteRef)
im, err := ii.IndexManifest()
if err != nil {
return false, fmt.Errorf("unable to get IndexManifest: %v", err)
}
for _, m := range im.Manifests {
if m.MediaType.IsImage() && (m.Platform == nil || m.Platform.Architecture == architecture) {
return true, nil
}
}
// we went through all of the manifests and did not find one that matches the target architecture
} else {
var im v1.Image
// try an image
im, err = desc.Image()
if err != nil {
return false, fmt.Errorf("provided image is neither an image nor an index: %s", image)
}
log.Debugf("ImageExists retrieved %s as image", remoteRef)
conf, err := im.ConfigFile()
if err != nil {
return false, fmt.Errorf("unable to get ConfigFile: %v", err)
}
if conf.Architecture == architecture {
return true, nil
}
// the image had the wrong architecture
}
return false, nil
}
1 change: 0 additions & 1 deletion src/cmd/linuxkit/pkg_build.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@ func pkgBuildPush(args []string, withPush bool) {
// pkg push --force - always builds even if is in cache
// pkg push --nobuild - skips build; if not in cache, fails
// pkg push --nobuild --force - nonsensical
// pkg pull - pull from registry if available, otherwise fail

var (
release *string
Expand Down
69 changes: 48 additions & 21 deletions src/cmd/linuxkit/pkglib/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ func WithRelease(r string) BuildOpt {
func WithBuildTargetDockerCache() BuildOpt {
return func(bo *buildOpts) error {
bo.targetDocker = true
bo.pull = true // if we are to load it into docker, it must be in local cache
return nil
}
}
Expand Down Expand Up @@ -235,34 +236,60 @@ func (p Pkg) Build(bos ...BuildOpt) error {

var platformsToBuild []imagespec.Platform
switch {
case bo.force && bo.skipBuild:
return errors.New("cannot force build and skip build")
case bo.force:
case bo.pull:
case !bo.skipBuild:
}

if bo.force {
// force local build
platformsToBuild = bo.platforms
} else if !bo.skipBuild {
fmt.Fprintf(writer, "checking for %s in local cache, fallback to remote registry...\n", ref)
case bo.skipBuild:
// do not build anything if we explicitly did skipBuild
platformsToBuild = nil
default:
// check local cache, fallback to check registry / pull image from registry, fallback to build
fmt.Fprintf(writer, "checking for %s in local cache...\n", ref)
for _, platform := range bo.platforms {
if _, err := c.ImagePull(&ref, "", platform.Architecture, false); err == nil {
fmt.Fprintf(writer, "%s found or pulled\n", ref)
if bo.targetDocker {
archRef, err := reference.Parse(fmt.Sprintf("%s-%s", p.FullTag(), platform.Architecture))
if err != nil {
return err
}
fmt.Fprintf(writer, "checking for %s in local cache, fallback to remote registry...\n", archRef)
if _, err := c.ImagePull(&archRef, "", platform.Architecture, false); err == nil {
fmt.Fprintf(writer, "%s found or pulled\n", archRef)
} else {
fmt.Fprintf(writer, "%s not found, will build: %s\n", archRef, err)
platformsToBuild = append(platformsToBuild, platform)
if exists, err := c.ImageInCache(&ref, "", platform.Architecture); err == nil && exists {
fmt.Fprintf(writer, "found %s in local cache, skipping build\n", ref)
continue
}
if bo.pull {
// need to pull the image from the registry, else build
fmt.Fprintf(writer, "%s %s not found in local cache, trying to pull\n", ref, platform.Architecture)
if _, err := c.ImagePull(&ref, "", platform.Architecture, false); err == nil {
fmt.Fprintf(writer, "%s pulled\n", ref)
if bo.targetDocker {
archRef, err := reference.Parse(fmt.Sprintf("%s-%s", p.FullTag(), platform.Architecture))
if err != nil {
return err
}
fmt.Fprintf(writer, "checking for %s in local cache, fallback to remote registry...\n", archRef)
if _, err := c.ImagePull(&archRef, "", platform.Architecture, false); err == nil {
fmt.Fprintf(writer, "%s found or pulled\n", archRef)
} else {
fmt.Fprintf(writer, "%s not found, will build: %s\n", archRef, err)
platformsToBuild = append(platformsToBuild, platform)
}
}
// successfully pulled, no need to build, continue with next platform
continue
}
} else {
fmt.Fprintf(writer, "%s not found, will build: %s\n", ref, err)
platformsToBuild = append(platformsToBuild, platform)
} else {
// do not pull, just check if it exists in a registry
fmt.Fprintf(writer, "%s %s not found in local cache, checking registry\n", ref, platform.Architecture)
exists, err := c.ImageInRegistry(&ref, "", platform.Architecture)
if err != nil {
return fmt.Errorf("error checking remote registry for %s: %v", ref, err)
}

if exists {
fmt.Fprintf(writer, "%s %s found on registry\n", ref, platform.Architecture)
continue
}
fmt.Fprintf(writer, "%s %s not found, will build\n", ref, platform.Architecture)
platformsToBuild = append(platformsToBuild, platform)

}
}
}
Expand Down
18 changes: 18 additions & 0 deletions src/cmd/linuxkit/pkglib/build_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,24 @@ func (c *cacheMocker) ImagePull(ref *reference.Spec, trustedRef, architecture st
return c.imageWriteStream(ref, architecture, bytes.NewReader(b))
}

func (c *cacheMocker) ImageInCache(ref *reference.Spec, trustedRef, architecture string) (bool, error) {
image := ref.String()
desc, ok := c.images[image]
if !ok {
return false, nil
}
for _, d := range desc {
if d.Platform != nil && d.Platform.Architecture == architecture {
return true, nil
}
}
return false, nil
}

func (c *cacheMocker) ImageInRegistry(ref *reference.Spec, trustedRef, architecture string) (bool, error) {
return false, nil
}

func (c *cacheMocker) ImageLoad(ref *reference.Spec, architecture string, r io.Reader) (lktspec.ImageSource, error) {
if !c.enableImageLoad {
return nil, errors.New("ImageLoad disabled")
Expand Down
6 changes: 6 additions & 0 deletions src/cmd/linuxkit/spec/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ type CacheProvider interface {
// efficient and only write missing blobs, based on their content hash. If the ref already
// exists in the cache, it should not pull anything, unless alwaysPull is set to true.
ImagePull(ref *reference.Spec, trustedRef, architecture string, alwaysPull bool) (ImageSource, error)
// ImageInCache takes an image name and checks if it exists in the cache, including checking that the given
// architecture is complete. Like ImagePull, it should be efficient and only write missing blobs, based on
// their content hash.
ImageInCache(ref *reference.Spec, trustedRef, architecture string) (bool, error)
// ImageInRegistry takes an image name and checks if it exists in the registry.
ImageInRegistry(ref *reference.Spec, trustedRef, architecture string) (bool, error)
// IndexWrite takes an image name and creates an index for the descriptors to which it points.
// Cache implementation determines whether it should pull missing blobs from a remote registry.
// If the provided reference already exists and it is an index, updates the manifests in the
Expand Down

0 comments on commit a3995bb

Please sign in to comment.