Skip to content

Commit

Permalink
extract layers and extend the export phase
Browse files Browse the repository at this point in the history
Signed-off-by: Darshan Kumar <[email protected]>
  • Loading branch information
itsdarshankumar committed Jun 11, 2023
1 parent abdf62c commit 5c460c5
Show file tree
Hide file tree
Showing 3 changed files with 137 additions and 11 deletions.
2 changes: 2 additions & 0 deletions internal/build/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,6 @@ type DockerClient interface {
ContainerRemove(ctx context.Context, container string, options types.ContainerRemoveOptions) error
CopyToContainer(ctx context.Context, container, path string, content io.Reader, options types.CopyToContainerOptions) error
ImageBuild(ctx context.Context, buildContext io.Reader, options types.ImageBuildOptions) (types.ImageBuildResponse, error)
ImageInspectWithRaw(ctx context.Context, image string) (types.ImageInspect, []byte, error)
ImageSave(ctx context.Context, imageIDs []string) (io.ReadCloser, error)
}
89 changes: 88 additions & 1 deletion internal/build/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,20 @@ package build

import (
"fmt"
"io"
"os"
"path/filepath"
"strings"

"github.com/BurntSushi/toml"
"github.com/buildpacks/imgutil/layout/sparse"
"github.com/buildpacks/lifecycle/buildpack"
"github.com/buildpacks/lifecycle/cmd"

"github.com/buildpacks/pack/pkg/logging"
"github.com/google/go-containerregistry/pkg/name"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/daemon"
"golang.org/x/sync/errgroup"
)

const (
Expand Down Expand Up @@ -88,3 +93,85 @@ func readExtensionsGroup(path string) ([]buildpack.GroupElement, error) {
func escapeID(id string) string {
return strings.ReplaceAll(id, "/", "_")
}

func SaveLayers(group *errgroup.Group, image v1.Image, origTopLayerHash string, dest string) error {
layoutPath, err := sparse.NewImage(dest, image)
if err != nil {
fmt.Println("sparse.NewImage err", err)
return err
}
if err = layoutPath.Save(); err != nil {
return err
}
if err != nil {
fmt.Println("sparse.NewImage err", err)
return err
}
layers, err := image.Layers()
if err != nil {
return fmt.Errorf("getting image layers: %w", err)
}
var (
currentHash v1.Hash
needsCopying bool
)
if origTopLayerHash == "" {
needsCopying = true
}
for _, currentLayer := range layers {
currentHash, err = currentLayer.Digest()
if err != nil {
return fmt.Errorf("getting layer hash: %w", err)
}
switch {
case needsCopying:
currentLayer := currentLayer
group.Go(func() error {
return copyLayer(currentLayer, dest)
})
case currentHash.String() == origTopLayerHash:
needsCopying = true
continue
default:
continue
}
}
return nil
}

func copyLayer(layer v1.Layer, toSparseImage string) error {
digest, err := layer.Digest()
if err != nil {
return err
}
f, err := os.Create(filepath.Join(toSparseImage, "blobs", digest.Algorithm, digest.Hex))
if err != nil {
return err
}
defer f.Close()
rc, err := layer.Compressed()
if err != nil {
return err
}
defer rc.Close()
_, err = io.Copy(f, rc)
return err
}

func topLayerHash(image *string) (string, error) {
baseRef, err := name.ParseReference(*image)
if err != nil {
return "", fmt.Errorf("failed to parse reference: %v", err)
}
baseImage, err := daemon.Image(baseRef)
if err != nil {
return "", fmt.Errorf("failed to get v1.Image: %v", err)
}
baseManifest, err := baseImage.Manifest()
if err != nil {
return "", fmt.Errorf("getting image manifest: %w", err)
}
baseLayers := baseManifest.Layers
topLayerHash := baseLayers[len(baseLayers)-1].Digest.String()
return topLayerHash, nil
}
57 changes: 47 additions & 10 deletions internal/build/lifecycle_execution.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import (
"context"
"fmt"
"io"

"math/rand"
"os"
"path/filepath"
"strconv"
"time"

"github.com/BurntSushi/toml"

Expand All @@ -19,6 +19,7 @@ import (
"github.com/docker/docker/api/types"
"github.com/google/go-containerregistry/pkg/authn"
"github.com/google/go-containerregistry/pkg/name"
"github.com/google/go-containerregistry/pkg/v1/daemon"
"github.com/pkg/errors"
"golang.org/x/sync/errgroup"

Expand Down Expand Up @@ -250,23 +251,26 @@ func (l *LifecycleExecution) Run(ctx context.Context, phaseFactoryCreator PhaseF
return err
}
}

var start time.Time
if l.platformAPI.AtLeast("0.12") && l.hasExtensionsForRun() {
if l.opts.Publish {
group.Go(func() error {
l.logger.Info(style.Step("EXTENDING (RUN)"))
return l.ExtendRun(ctx, buildCache, phaseFactory)
})
} else {
start = time.Now()
group.Go(func() error {
l.logger.Info(style.Step("EXTENDING (RUN) BY DAEMON"))
return l.ExtendRunByDaemon(ctx, &currentRunImage)
return l.ExtendRunByDaemon(ctx, group, &currentRunImage)
})
}
}
if err := group.Wait(); err != nil {
return err
}
duration := time.Since(start)
fmt.Println("Execution time:", duration)

l.logger.Info(style.Step("EXPORTING"))
return l.Export(ctx, buildCache, launchCache, phaseFactory)
Expand Down Expand Up @@ -722,24 +726,29 @@ func (l *LifecycleExecution) ExtendRun(ctx context.Context, buildCache Cache, ph
return extend.Run(ctx)
}

func (l *LifecycleExecution) ExtendRunByDaemon(ctx context.Context, currentRunImage *string) error {
func (l *LifecycleExecution) ExtendRunByDaemon(ctx context.Context, group *errgroup.Group, currentRunImage *string) error {
defaultFilterFunc := func(file string) bool { return true }
var extensions Extensions
l.logger.Debugf("extending run image %s", *currentRunImage)
fmt.Println("tmpDir: ", l.tmpDir)
extensions.SetExtensions(l.tmpDir, l.logger)
dockerfiles, err := extensions.DockerFiles(DockerfileKindRun, l.tmpDir, l.logger)
if err != nil {
return fmt.Errorf("getting %s.Dockerfiles: %w", DockerfileKindRun, err)
}
fmt.Println("Dockerfiles: ", dockerfiles)
fmt.Println("extend: ", dockerfiles[1].Extend)
nestedCtx, cancel := context.WithCancel(ctx)
defer cancel()
nestedGroup, _ := errgroup.WithContext(nestedCtx)
var origTopLayerHash string = ""
nestedGroup.Go(func() error {
origTopLayerHash, err = topLayerHash(currentRunImage)
if err != nil {
return fmt.Errorf("getting top layer hash of run image: %w", err)
}
return nil
})
for _, dockerfile := range dockerfiles {
if dockerfile.Extend {
fmt.Println("dockerfile: ", dockerfile)
fmt.Println("dockerfile.Path dir: ", filepath.Dir(dockerfile.Path))
buildContext := archive.ReadDirAsTar(filepath.Dir(dockerfile.Path), "/", 0, 0, -1, true, false, defaultFilterFunc)
fmt.Println("buildContext: ", buildContext)
buildArguments := map[string]*string{}
if dockerfile.WithBase == "" {
buildArguments["base_image"] = currentRunImage
Expand All @@ -763,6 +772,26 @@ func (l *LifecycleExecution) ExtendRunByDaemon(ctx context.Context, currentRunIm
l.logger.Debugf("build response for the extend: %v", response)
}
}
ref, err := name.ParseReference("run-image:latest")
if err != nil {
return fmt.Errorf("failed to parse reference: %v", err)
}
image, err := daemon.Image(ref)
if err != nil {
return fmt.Errorf("failed to get v1.Image: %v", err)
}
imageHash, err := image.Digest()
if err != nil {
return fmt.Errorf("getting image hash: %w", err)
}
dest := filepath.Join(l.tmpDir, "extended-new", "run", imageHash.String())
waiterr := nestedGroup.Wait()
if waiterr != nil {
return err
}
if err = SaveLayers(group, image, origTopLayerHash, dest); err != nil {
return fmt.Errorf("copying selective image to output directory: %w", err)
}
return nil
}

Expand Down Expand Up @@ -792,6 +821,12 @@ func (l *LifecycleExecution) Export(ctx context.Context, buildCache, launchCache
}
}

extDirEnv := NullOp()
if !l.opts.Publish {
l.logger.Debug("export for extend by daemon")
extDirEnv = WithEnv("CNB_EXTENDED_DIR=" + filepath.Join("/", "extended-new"))
}

if l.platformAPI.LessThan("0.7") {
flags = append(flags, "-run-image", l.opts.RunImage)
}
Expand Down Expand Up @@ -828,6 +863,7 @@ func (l *LifecycleExecution) Export(ctx context.Context, buildCache, launchCache
),
WithArgs(append([]string{l.opts.Image.String()}, l.opts.AdditionalTags...)...),
WithRoot(),
WithBinds(filepath.Join(l.tmpDir, "extended-new") + ":/extended-new"),
WithNetwork(l.opts.Network),
cacheBindOp,
WithContainerOperations(WriteStackToml(l.mountPaths.stackPath(), l.opts.Builder.Stack(), l.os)),
Expand All @@ -844,6 +880,7 @@ func (l *LifecycleExecution) Export(ctx context.Context, buildCache, launchCache
CopyOut(l.opts.Termui.ReadLayers, l.mountPaths.layersDir(), l.mountPaths.appDir()))),
epochEnv,
expEnv,
extDirEnv,
}

var export RunnerCleaner
Expand Down

0 comments on commit 5c460c5

Please sign in to comment.