commit: commit on every instruction, but not always with layers
When building an image with multiple layers, go back to committing images for instructions for which we previously wouldn't bother committing an image, but create them without adding a new layer. This violates some assumptions that we currently make elsewhere, as it's possible for an image that's derived from a base image to add no layers relative to the base image, when previously it was always the case that we'd add at least one whenever we committed it. Signed-off-by: Nalin Dahyabhai <nalin@redhat.com> Closes: #1539 Approved by: rhatdan
This commit is contained in:
parent
6306386e07
commit
bc53b5d980
|
@ -13,6 +13,7 @@ import (
|
|||
"github.com/containers/image/types"
|
||||
"github.com/containers/storage"
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
|
@ -206,65 +207,154 @@ func defaultFormat() string {
|
|||
// imageIsParent goes through the layers in the store and checks if i.TopLayer is
|
||||
// the parent of any other layer in store. Double check that image with that
|
||||
// layer exists as well.
|
||||
func imageIsParent(store storage.Store, topLayer string) (bool, error) {
|
||||
children, err := getChildren(store, topLayer)
|
||||
func imageIsParent(ctx context.Context, sc *types.SystemContext, store storage.Store, image *storage.Image) (bool, error) {
|
||||
children, err := getChildren(ctx, sc, store, image, 1)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return len(children) > 0, nil
|
||||
}
|
||||
|
||||
// getParent returns the image ID of the parent. Return nil if a parent is not found.
|
||||
func getParent(store storage.Store, topLayer string) (*storage.Image, error) {
|
||||
func getImageConfig(ctx context.Context, sc *types.SystemContext, store storage.Store, imageID string) (*imgspecv1.Image, error) {
|
||||
ref, err := is.Transport.ParseStoreReference(store, imageID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "unable to parse reference to image %q", imageID)
|
||||
}
|
||||
image, err := ref.NewImage(ctx, sc)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "unable to open image %q", imageID)
|
||||
}
|
||||
config, err := image.OCIConfig(ctx)
|
||||
defer image.Close()
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "unable to read configuration from image %q", imageID)
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func historiesDiffer(a, b []imgspecv1.History) bool {
|
||||
if len(a) != len(b) {
|
||||
return true
|
||||
}
|
||||
i := 0
|
||||
for i < len(a) {
|
||||
if a[i].Created == nil && b[i].Created != nil {
|
||||
break
|
||||
}
|
||||
if a[i].Created != nil && b[i].Created == nil {
|
||||
break
|
||||
}
|
||||
if a[i].Created != nil && b[i].Created != nil && !a[i].Created.Equal(*(b[i].Created)) {
|
||||
break
|
||||
}
|
||||
if a[i].CreatedBy != b[i].CreatedBy {
|
||||
break
|
||||
}
|
||||
if a[i].Author != b[i].Author {
|
||||
break
|
||||
}
|
||||
if a[i].Comment != b[i].Comment {
|
||||
break
|
||||
}
|
||||
if a[i].EmptyLayer != b[i].EmptyLayer {
|
||||
break
|
||||
}
|
||||
i++
|
||||
}
|
||||
return i != len(a)
|
||||
}
|
||||
|
||||
// getParent returns the image's parent image. Return nil if a parent is not found.
|
||||
func getParent(ctx context.Context, sc *types.SystemContext, store storage.Store, child *storage.Image) (*storage.Image, error) {
|
||||
images, err := store.Images()
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "unable to retrieve images from store")
|
||||
return nil, errors.Wrapf(err, "unable to retrieve image list from store")
|
||||
}
|
||||
layer, err := store.Layer(topLayer)
|
||||
childLayer, err := store.Layer(child.TopLayer)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "unable to retrieve layers from store")
|
||||
return nil, errors.Wrapf(err, "unable to retrieve layer list from store")
|
||||
}
|
||||
for _, img := range images {
|
||||
if img.TopLayer == layer.Parent {
|
||||
return &img, nil
|
||||
childConfig, err := getImageConfig(ctx, sc, store, child.ID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "unable to read configuration from image %q", child.ID)
|
||||
}
|
||||
for _, parent := range images {
|
||||
if parent.ID == child.ID {
|
||||
continue
|
||||
}
|
||||
if parent.TopLayer != childLayer.Parent && parent.TopLayer != childLayer.ID {
|
||||
continue
|
||||
}
|
||||
parentConfig, err := getImageConfig(ctx, sc, store, parent.ID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "unable to read configuration from image %q", parent.ID)
|
||||
}
|
||||
if len(parentConfig.History)+1 != len(childConfig.History) {
|
||||
continue
|
||||
}
|
||||
if len(parentConfig.RootFS.DiffIDs) > 0 {
|
||||
if len(childConfig.RootFS.DiffIDs) < len(parentConfig.RootFS.DiffIDs) {
|
||||
continue
|
||||
}
|
||||
childUsesAllParentLayers := true
|
||||
for i := range parentConfig.RootFS.DiffIDs {
|
||||
if childConfig.RootFS.DiffIDs[i] != parentConfig.RootFS.DiffIDs[i] {
|
||||
childUsesAllParentLayers = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if !childUsesAllParentLayers {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if historiesDiffer(parentConfig.History, childConfig.History[:len(parentConfig.History)]) {
|
||||
continue
|
||||
}
|
||||
return &parent, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// getChildren returns a list of the imageIDs that depend on the image
|
||||
func getChildren(store storage.Store, topLayer string) ([]string, error) {
|
||||
func getChildren(ctx context.Context, sc *types.SystemContext, store storage.Store, parent *storage.Image, max int) ([]string, error) {
|
||||
var children []string
|
||||
images, err := store.Images()
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "unable to retrieve images from store")
|
||||
}
|
||||
layers, err := store.Layers()
|
||||
parentConfig, err := getImageConfig(ctx, sc, store, parent.ID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "unable to retrieve layers from store")
|
||||
return nil, errors.Wrapf(err, "unable to read configuration from image %q", parent.ID)
|
||||
}
|
||||
|
||||
for _, layer := range layers {
|
||||
if layer.Parent == topLayer {
|
||||
if imageID := getImageOfTopLayer(images, layer.ID); len(imageID) > 0 {
|
||||
children = append(children, imageID...)
|
||||
}
|
||||
for _, child := range images {
|
||||
if child.ID == parent.ID {
|
||||
continue
|
||||
}
|
||||
childLayer, err := store.Layer(child.TopLayer)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "unable to retrieve information about layer %q from store", child.TopLayer)
|
||||
}
|
||||
if childLayer.Parent != parent.TopLayer && childLayer.ID != parent.TopLayer {
|
||||
continue
|
||||
}
|
||||
childConfig, err := getImageConfig(ctx, sc, store, child.ID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "unable to read configuration from image %q", child.ID)
|
||||
}
|
||||
if len(parentConfig.History)+1 != len(childConfig.History) {
|
||||
continue
|
||||
}
|
||||
if historiesDiffer(parentConfig.History, childConfig.History[:len(parentConfig.History)]) {
|
||||
continue
|
||||
}
|
||||
children = append(children, child.ID)
|
||||
if max > 0 && len(children) >= max {
|
||||
break
|
||||
}
|
||||
}
|
||||
return children, nil
|
||||
}
|
||||
|
||||
// getImageOfTopLayer returns the image ID where layer is the top layer of the image
|
||||
func getImageOfTopLayer(images []storage.Image, layer string) []string {
|
||||
var matches []string
|
||||
for _, img := range images {
|
||||
if img.TopLayer == layer {
|
||||
matches = append(matches, img.ID)
|
||||
}
|
||||
}
|
||||
return matches
|
||||
}
|
||||
|
||||
func getFormat(format string) (string, error) {
|
||||
switch format {
|
||||
case buildah.OCI:
|
||||
|
|
|
@ -10,7 +10,9 @@ import (
|
|||
|
||||
buildahcli "github.com/containers/buildah/pkg/cli"
|
||||
"github.com/containers/buildah/pkg/formats"
|
||||
"github.com/containers/buildah/pkg/parse"
|
||||
is "github.com/containers/image/storage"
|
||||
"github.com/containers/image/types"
|
||||
"github.com/containers/storage"
|
||||
units "github.com/docker/go-units"
|
||||
"github.com/pkg/errors"
|
||||
|
@ -122,6 +124,11 @@ func imagesCmd(c *cobra.Command, args []string, iopts *imageResults) error {
|
|||
return err
|
||||
}
|
||||
|
||||
systemContext, err := parse.SystemContextFromOptions(c)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error building system context")
|
||||
}
|
||||
|
||||
images, err := store.Images()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error reading images")
|
||||
|
@ -150,7 +157,7 @@ func imagesCmd(c *cobra.Command, args []string, iopts *imageResults) error {
|
|||
}
|
||||
}
|
||||
|
||||
return outputImages(ctx, store, images, params, name, opts)
|
||||
return outputImages(ctx, systemContext, store, images, params, name, opts)
|
||||
}
|
||||
|
||||
func parseFilter(ctx context.Context, store storage.Store, images []storage.Image, filter string) (*filterParams, error) {
|
||||
|
@ -237,7 +244,7 @@ func outputHeader(opts imageOptions) string {
|
|||
|
||||
type imagesSorted []imageOutputParams
|
||||
|
||||
func outputImages(ctx context.Context, store storage.Store, images []storage.Image, filters *filterParams, argName string, opts imageOptions) error {
|
||||
func outputImages(ctx context.Context, systemContext *types.SystemContext, store storage.Store, images []storage.Image, filters *filterParams, argName string, opts imageOptions) error {
|
||||
found := false
|
||||
var imagesParams imagesSorted
|
||||
jsonImages := []jsonImage{}
|
||||
|
@ -252,11 +259,12 @@ func outputImages(ctx context.Context, store storage.Store, images []storage.Ima
|
|||
}
|
||||
createdTime = createdTime.Local()
|
||||
|
||||
// If all is false and the image doesn't have a name, check to see if the top layer of the image is a parent
|
||||
// to another image's top layer. If it is, then it is an intermediate image so don't print out if the --all flag
|
||||
// is not set.
|
||||
// If "all" is false and this image doesn't have a name, check
|
||||
// to see if the image is the parent of any other image. If it
|
||||
// is, then it is an intermediate image, so don't list it if
|
||||
// the --all flag is not set.
|
||||
if !opts.all && len(image.Names) == 0 {
|
||||
isParent, err := imageIsParent(store, image.TopLayer)
|
||||
isParent, err := imageIsParent(ctx, systemContext, store, &image)
|
||||
if err != nil {
|
||||
logrus.Errorf("error checking if image is a parent %q: %v", image.ID, err)
|
||||
}
|
||||
|
|
|
@ -106,7 +106,7 @@ func TestOutputImagesQuietNotTruncated(t *testing.T) {
|
|||
|
||||
// Tests quiet and non-truncated output
|
||||
output, err := captureOutputWithError(func() error {
|
||||
return outputImages(getContext(), store, images[:1], nil, "", opts)
|
||||
return outputImages(getContext(), &testSystemContext, store, images[:1], nil, "", opts)
|
||||
})
|
||||
expectedOutput := fmt.Sprintf("sha256:%s\n", images[0].ID)
|
||||
if err != nil {
|
||||
|
@ -144,7 +144,7 @@ func TestOutputImagesFormatString(t *testing.T) {
|
|||
|
||||
// Tests output with format template
|
||||
output, err := captureOutputWithError(func() error {
|
||||
return outputImages(getContext(), store, images[:1], nil, "", opts)
|
||||
return outputImages(getContext(), &testSystemContext, store, images[:1], nil, "", opts)
|
||||
})
|
||||
expectedOutput := images[0].ID
|
||||
if err != nil {
|
||||
|
@ -183,7 +183,7 @@ func TestOutputImagesArgNoMatch(t *testing.T) {
|
|||
// because all images in the repository must have a tag, and here the tag is an
|
||||
// empty string
|
||||
_, err = captureOutputWithError(func() error {
|
||||
return outputImages(getContext(), store, images[:1], nil, "foo:", opts)
|
||||
return outputImages(getContext(), &testSystemContext, store, images[:1], nil, "foo:", opts)
|
||||
})
|
||||
if err == nil || err.Error() != "No such image foo:" {
|
||||
t.Fatalf("expected error arg no match")
|
||||
|
|
|
@ -176,7 +176,7 @@ func deleteImages(ctx context.Context, systemContext *types.SystemContext, store
|
|||
}
|
||||
}
|
||||
|
||||
isParent, err := imageIsParent(store, image.TopLayer)
|
||||
isParent, err := imageIsParent(ctx, systemContext, store, image)
|
||||
if err != nil {
|
||||
if lastError != nil {
|
||||
fmt.Fprintln(os.Stderr, lastError)
|
||||
|
@ -197,7 +197,7 @@ func deleteImages(ctx context.Context, systemContext *types.SystemContext, store
|
|||
lastError = errors.Errorf("unable to delete %q (cannot be forced) - image has dependent child images", image.ID)
|
||||
continue
|
||||
}
|
||||
id, err := removeImage(store, image)
|
||||
id, err := removeImage(ctx, systemContext, store, image)
|
||||
if err != nil {
|
||||
if lastError != nil {
|
||||
fmt.Fprintln(os.Stderr, lastError)
|
||||
|
@ -256,8 +256,8 @@ func untagImage(imgArg string, store storage.Store, image *storage.Image) (strin
|
|||
return removedName, nil
|
||||
}
|
||||
|
||||
func removeImage(store storage.Store, image *storage.Image) (string, error) {
|
||||
parent, err := getParent(store, image.TopLayer)
|
||||
func removeImage(ctx context.Context, systemContext *types.SystemContext, store storage.Store, image *storage.Image) (string, error) {
|
||||
parent, err := getParent(ctx, systemContext, store, image)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
@ -265,17 +265,17 @@ func removeImage(store storage.Store, image *storage.Image) (string, error) {
|
|||
return "", errors.Wrapf(err, "could not remove image %q", image.ID)
|
||||
}
|
||||
for parent != nil {
|
||||
nextParent, err := getParent(store, parent.TopLayer)
|
||||
nextParent, err := getParent(ctx, systemContext, store, parent)
|
||||
if err != nil {
|
||||
return image.ID, errors.Wrapf(err, "unable to get parent from image %q", image.ID)
|
||||
}
|
||||
children, err := getChildren(store, parent.TopLayer)
|
||||
isParent, err := imageIsParent(ctx, systemContext, store, parent)
|
||||
if err != nil {
|
||||
return image.ID, errors.Wrapf(err, "unable to get children from image %q", image.ID)
|
||||
return image.ID, errors.Wrapf(err, "unable to get check if image %q is a parent", image.ID)
|
||||
}
|
||||
// Do not remove if image is a base image and is not untagged, or if
|
||||
// the image has more children.
|
||||
if len(parent.Names) > 0 || len(children) > 0 {
|
||||
if len(parent.Names) > 0 || isParent {
|
||||
return image.ID, nil
|
||||
}
|
||||
id := parent.ID
|
||||
|
|
|
@ -64,6 +64,9 @@ type CommitOptions struct {
|
|||
// manifest of the new image will reference the blobs rather than
|
||||
// on-disk layers.
|
||||
BlobDirectory string
|
||||
// EmptyLayer tells the builder to omit the diff for the working
|
||||
// container.
|
||||
EmptyLayer bool
|
||||
// OmitTimestamp forces epoch 0 as created timestamp to allow for
|
||||
// deterministic, content-addressable builds.
|
||||
OmitTimestamp bool
|
||||
|
|
11
image.go
11
image.go
|
@ -56,6 +56,7 @@ type containerImageRef struct {
|
|||
preferredManifestType string
|
||||
exporting bool
|
||||
squash bool
|
||||
emptyLayer bool
|
||||
tarPath func(path string) (io.ReadCloser, error)
|
||||
parent string
|
||||
blobDirectory string
|
||||
|
@ -290,6 +291,11 @@ func (i *containerImageRef) NewImageSource(ctx context.Context, sc *types.System
|
|||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "unable to locate layer %q", layerID)
|
||||
}
|
||||
// If we're up to the final layer, but we don't want to include
|
||||
// a diff for it, we're done.
|
||||
if i.emptyLayer && layerID == i.layerID {
|
||||
continue
|
||||
}
|
||||
// If we're not re-exporting the data, and we're reusing layers individually, reuse
|
||||
// the blobsum and diff IDs.
|
||||
if !i.exporting && !i.squash && layerID != i.layerID {
|
||||
|
@ -433,7 +439,7 @@ func (i *containerImageRef) NewImageSource(ctx context.Context, sc *types.System
|
|||
CreatedBy: i.createdBy,
|
||||
Author: oimage.Author,
|
||||
Comment: i.historyComment,
|
||||
EmptyLayer: false,
|
||||
EmptyLayer: i.emptyLayer,
|
||||
}
|
||||
oimage.History = append(oimage.History, onews)
|
||||
dnews := docker.V2S2History{
|
||||
|
@ -441,7 +447,7 @@ func (i *containerImageRef) NewImageSource(ctx context.Context, sc *types.System
|
|||
CreatedBy: i.createdBy,
|
||||
Author: dimage.Author,
|
||||
Comment: i.historyComment,
|
||||
EmptyLayer: false,
|
||||
EmptyLayer: i.emptyLayer,
|
||||
}
|
||||
dimage.History = append(dimage.History, dnews)
|
||||
appendHistory(i.postEmptyLayers)
|
||||
|
@ -700,6 +706,7 @@ func (b *Builder) makeImageRef(options CommitOptions, exporting bool) (types.Ima
|
|||
preferredManifestType: manifestType,
|
||||
exporting: exporting,
|
||||
squash: options.Squash,
|
||||
emptyLayer: options.EmptyLayer,
|
||||
tarPath: b.tarPath(),
|
||||
parent: parent,
|
||||
blobDirectory: options.BlobDirectory,
|
||||
|
|
|
@ -870,9 +870,9 @@ func (b *Executor) resolveNameToImageRef(output string) (types.ImageReference, e
|
|||
return imageRef, nil
|
||||
}
|
||||
|
||||
// stepRequiresCommit indicates whether or not the step should be followed by
|
||||
// committing the in-progress container to create an intermediate image.
|
||||
func (*StageExecutor) stepRequiresCommit(step *imagebuilder.Step) bool {
|
||||
// stepRequiresLayer indicates whether or not the step should be followed by
|
||||
// committing a layer container when creating an intermediate image.
|
||||
func (*StageExecutor) stepRequiresLayer(step *imagebuilder.Step) bool {
|
||||
switch strings.ToUpper(step.Command) {
|
||||
case "ADD", "COPY", "RUN":
|
||||
return true
|
||||
|
@ -951,7 +951,7 @@ func (s *StageExecutor) Execute(ctx context.Context, stage imagebuilder.Stage, b
|
|||
// squash the contents of the base image. Whichever is
|
||||
// the case, we need to commit() to create a new image.
|
||||
logCommit(s.output, -1)
|
||||
if imgID, ref, err = s.commit(ctx, ib, s.executor.getCreatedBy(nil), s.output); err != nil {
|
||||
if imgID, ref, err = s.commit(ctx, ib, s.executor.getCreatedBy(nil), false, s.output); err != nil {
|
||||
return "", nil, errors.Wrapf(err, "error committing base container")
|
||||
}
|
||||
} else {
|
||||
|
@ -1032,7 +1032,7 @@ func (s *StageExecutor) Execute(ctx context.Context, stage imagebuilder.Stage, b
|
|||
// if it's used as the basis for a later stage.
|
||||
if lastStage || imageIsUsedLater {
|
||||
logCommit(s.output, i)
|
||||
imgID, ref, err = s.commit(ctx, ib, s.executor.getCreatedBy(node), s.output)
|
||||
imgID, ref, err = s.commit(ctx, ib, s.executor.getCreatedBy(node), false, s.output)
|
||||
if err != nil {
|
||||
return "", nil, errors.Wrapf(err, "error committing container for step %+v", *step)
|
||||
}
|
||||
|
@ -1061,11 +1061,7 @@ func (s *StageExecutor) Execute(ctx context.Context, stage imagebuilder.Stage, b
|
|||
// If we're using the cache, and we've managed to stick with
|
||||
// cached images so far, look for one that matches what we
|
||||
// expect to produce for this instruction.
|
||||
// Only check at steps where we commit, so that we don't
|
||||
// abandon the cache at this step just because we can't find an
|
||||
// image with a history entry in it that we wouldn't have
|
||||
// committed.
|
||||
if checkForLayers && (s.stepRequiresCommit(step) || lastInstruction) && !(s.executor.squash && lastInstruction && lastStage) {
|
||||
if checkForLayers && !(s.executor.squash && lastInstruction && lastStage) {
|
||||
cacheID, err = s.layerExists(ctx, node, children[:i])
|
||||
if err != nil {
|
||||
return "", nil, errors.Wrap(err, "error checking if cached image exists from a previous build")
|
||||
|
@ -1085,8 +1081,8 @@ func (s *StageExecutor) Execute(ctx context.Context, stage imagebuilder.Stage, b
|
|||
// the last step in this stage, add the name to the
|
||||
// image.
|
||||
imgID = cacheID
|
||||
if commitName != "" && (s.stepRequiresCommit(step) || lastInstruction) {
|
||||
logCommit(s.output, i)
|
||||
if commitName != "" {
|
||||
logCommit(commitName, i)
|
||||
if imgID, ref, err = s.copyExistingImage(ctx, cacheID, commitName); err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
@ -1098,6 +1094,19 @@ func (s *StageExecutor) Execute(ctx context.Context, stage imagebuilder.Stage, b
|
|||
// filesystem to match the image contents for the sake
|
||||
// of a later stage that wants to copy content from it.
|
||||
rebase = moreInstructions || rootfsIsUsedLater
|
||||
// If the instruction would affect our configuration,
|
||||
// process the configuration change so that, if we fall
|
||||
// off the cache path, the filesystem changes from the
|
||||
// last cache image will be all that we need, since we
|
||||
// still don't want to restart using the image's
|
||||
// configuration blob.
|
||||
if !s.stepRequiresLayer(step) {
|
||||
err := ib.Run(step, s, noRunsRemaining)
|
||||
if err != nil {
|
||||
logrus.Debugf("%v", errors.Wrapf(err, "error building at step %+v", *step))
|
||||
return "", nil, errors.Wrapf(err, "error building at STEP \"%s\"", step.Message)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If we didn't find a cached image that we could just reuse,
|
||||
// process the instruction directly.
|
||||
|
@ -1106,32 +1115,20 @@ func (s *StageExecutor) Execute(ctx context.Context, stage imagebuilder.Stage, b
|
|||
logrus.Debugf("%v", errors.Wrapf(err, "error building at step %+v", *step))
|
||||
return "", nil, errors.Wrapf(err, "error building at STEP \"%s\"", step.Message)
|
||||
}
|
||||
if s.stepRequiresCommit(step) || lastInstruction {
|
||||
// Either this is the last instruction, or
|
||||
// there are more instructions and we need to
|
||||
// create a layer from this one before
|
||||
// continuing.
|
||||
logCommit(s.output, i)
|
||||
imgID, ref, err = s.commit(ctx, ib, s.executor.getCreatedBy(node), commitName)
|
||||
if err != nil {
|
||||
return "", nil, errors.Wrapf(err, "error committing container for step %+v", *step)
|
||||
}
|
||||
logImageID(imgID)
|
||||
// We only need to build a new container rootfs
|
||||
// using this image if we plan on making
|
||||
// further changes to it. Subsequent stages
|
||||
// that just want to use the rootfs as a source
|
||||
// for COPY or ADD will be content with what we
|
||||
// already have.
|
||||
rebase = moreInstructions
|
||||
} else {
|
||||
// There are still more instructions to process
|
||||
// for this stage, and we don't need to commit
|
||||
// here. Make a note of the instruction in the
|
||||
// history for the next commit.
|
||||
now := time.Now()
|
||||
s.builder.AddPrependedEmptyLayer(&now, s.executor.getCreatedBy(node), "", "")
|
||||
// Create a new image, maybe with a new layer.
|
||||
logCommit(s.output, i)
|
||||
imgID, ref, err = s.commit(ctx, ib, s.executor.getCreatedBy(node), !s.stepRequiresLayer(step), commitName)
|
||||
if err != nil {
|
||||
return "", nil, errors.Wrapf(err, "error committing container for step %+v", *step)
|
||||
}
|
||||
logImageID(imgID)
|
||||
// We only need to build a new container rootfs
|
||||
// using this image if we plan on making
|
||||
// further changes to it. Subsequent stages
|
||||
// that just want to use the rootfs as a source
|
||||
// for COPY or ADD will be content with what we
|
||||
// already have.
|
||||
rebase = moreInstructions
|
||||
}
|
||||
|
||||
if rebase {
|
||||
|
@ -1217,7 +1214,7 @@ func (s *StageExecutor) layerExists(ctx context.Context, currNode *parser.Node,
|
|||
// it means that this image is potentially a cached intermediate image from a previous
|
||||
// build. Next we double check that the history of this image is equivalent to the previous
|
||||
// lines in the Dockerfile up till the point we are at in the build.
|
||||
if layer.Parent == s.executor.topLayers[len(s.executor.topLayers)-1] {
|
||||
if layer.Parent == s.executor.topLayers[len(s.executor.topLayers)-1] || layer.ID == s.executor.topLayers[len(s.executor.topLayers)-1] {
|
||||
history, err := s.executor.getImageHistory(ctx, image.ID)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "error getting history of %q", image.ID)
|
||||
|
@ -1398,7 +1395,7 @@ func urlContentModified(url string, historyTime *time.Time) (bool, error) {
|
|||
|
||||
// commit writes the container's contents to an image, using a passed-in tag as
|
||||
// the name if there is one, generating a unique ID-based one otherwise.
|
||||
func (s *StageExecutor) commit(ctx context.Context, ib *imagebuilder.Builder, createdBy, output string) (string, reference.Canonical, error) {
|
||||
func (s *StageExecutor) commit(ctx context.Context, ib *imagebuilder.Builder, createdBy string, emptyLayer bool, output string) (string, reference.Canonical, error) {
|
||||
var imageRef types.ImageReference
|
||||
if output != "" {
|
||||
imageRef2, err := s.executor.resolveNameToImageRef(output)
|
||||
|
@ -1488,6 +1485,7 @@ func (s *StageExecutor) commit(ctx context.Context, ib *imagebuilder.Builder, cr
|
|||
PreferredManifestType: s.executor.outputFormat,
|
||||
SystemContext: s.executor.systemContext,
|
||||
Squash: s.executor.squash,
|
||||
EmptyLayer: emptyLayer,
|
||||
BlobDirectory: s.executor.blobDirectory,
|
||||
}
|
||||
imgID, _, manifestDigest, err := s.builder.Commit(ctx, imageRef, options)
|
||||
|
|
|
@ -50,36 +50,40 @@ load helpers
|
|||
|
||||
buildah bud --signature-policy ${TESTSDIR}/policy.json --layers -t test1 ${TESTDIR}/use-layers
|
||||
run_buildah --debug=false images -a
|
||||
expect_line_count 6
|
||||
expect_line_count 8
|
||||
buildah bud --signature-policy ${TESTSDIR}/policy.json --layers -t test2 ${TESTDIR}/use-layers
|
||||
run_buildah --debug=false images -a
|
||||
expect_line_count 8
|
||||
run_buildah --debug=false inspect --format "{{index .Docker.ContainerConfig.Env 1}}" test2
|
||||
expect_line_count 10
|
||||
run_buildah --debug=false inspect --format "{{index .Docker.ContainerConfig.Env 1}}" test1
|
||||
expect_output "foo=bar"
|
||||
run_buildah --debug=false inspect --format "{{index .Docker.ContainerConfig.Env 1}}" test2
|
||||
expect_output "foo=bar"
|
||||
run_buildah --debug=false inspect --format "{{.Docker.ContainerConfig.ExposedPorts}}" test1
|
||||
expect_output "map[8080/tcp:{}]"
|
||||
run_buildah --debug=false inspect --format "{{.Docker.ContainerConfig.ExposedPorts}}" test2
|
||||
expect_output "map[8080/tcp:{}]"
|
||||
|
||||
buildah bud --signature-policy ${TESTSDIR}/policy.json --layers -t test3 -f Dockerfile.2 ${TESTDIR}/use-layers
|
||||
run_buildah --debug=false images -a
|
||||
expect_line_count 10
|
||||
expect_line_count 12
|
||||
|
||||
mkdir -p ${TESTDIR}/use-layers/mount/subdir
|
||||
buildah bud --signature-policy ${TESTSDIR}/policy.json --layers -t test4 -f Dockerfile.3 ${TESTDIR}/use-layers
|
||||
run_buildah --debug=false images -a
|
||||
expect_line_count 12
|
||||
expect_line_count 14
|
||||
|
||||
buildah bud --signature-policy ${TESTSDIR}/policy.json --layers -t test5 -f Dockerfile.3 ${TESTDIR}/use-layers
|
||||
run_buildah --debug=false images -a
|
||||
expect_line_count 13
|
||||
expect_line_count 15
|
||||
|
||||
touch ${TESTDIR}/use-layers/mount/subdir/file.txt
|
||||
buildah bud --signature-policy ${TESTSDIR}/policy.json --layers -t test6 -f Dockerfile.3 ${TESTDIR}/use-layers
|
||||
run_buildah --debug=false images -a
|
||||
expect_line_count 15
|
||||
expect_line_count 17
|
||||
|
||||
buildah bud --signature-policy ${TESTSDIR}/policy.json --no-cache -t test7 -f Dockerfile.2 ${TESTDIR}/use-layers
|
||||
run_buildah --debug=false images -a
|
||||
expect_line_count 16
|
||||
expect_line_count 18
|
||||
|
||||
buildah rmi -a -f
|
||||
}
|
||||
|
@ -186,21 +190,25 @@ load helpers
|
|||
}
|
||||
|
||||
@test "bud with --layers and --build-args" {
|
||||
# base plus 3, plus the header line
|
||||
buildah bud --signature-policy ${TESTSDIR}/policy.json --build-arg=user=0 --layers -t test -f Dockerfile.build-args ${TESTSDIR}/bud/use-layers
|
||||
run_buildah --debug=false images -a
|
||||
expect_line_count 4
|
||||
expect_line_count 5
|
||||
|
||||
# two more, starting at the "echo $user" instruction
|
||||
buildah bud --signature-policy ${TESTSDIR}/policy.json --build-arg=user=1 --layers -t test1 -f Dockerfile.build-args ${TESTSDIR}/bud/use-layers
|
||||
run_buildah --debug=false images -a
|
||||
expect_line_count 6
|
||||
|
||||
buildah bud --signature-policy ${TESTSDIR}/policy.json --build-arg=user=1 --layers -t test2 -f Dockerfile.build-args ${TESTSDIR}/bud/use-layers
|
||||
run_buildah --debug=false images -a
|
||||
expect_line_count 7
|
||||
|
||||
# one more, because we added a new name to the same image
|
||||
buildah bud --signature-policy ${TESTSDIR}/policy.json --build-arg=user=1 --layers -t test2 -f Dockerfile.build-args ${TESTSDIR}/bud/use-layers
|
||||
run_buildah --debug=false images -a
|
||||
expect_line_count 8
|
||||
|
||||
# two more, starting at the "echo $user" instruction
|
||||
buildah bud --signature-policy ${TESTSDIR}/policy.json --layers -t test3 -f Dockerfile.build-args ${TESTSDIR}/bud/use-layers
|
||||
run_buildah --debug=false images -a
|
||||
expect_line_count 9
|
||||
expect_line_count 10
|
||||
|
||||
buildah rmi -a -f
|
||||
}
|
||||
|
@ -212,7 +220,7 @@ load helpers
|
|||
|
||||
buildah bud --signature-policy ${TESTSDIR}/policy.json --rm=false --layers -t test2 ${TESTSDIR}/bud/use-layers
|
||||
run_buildah --debug=false containers
|
||||
expect_line_count 5
|
||||
expect_line_count 7
|
||||
|
||||
buildah rm -a
|
||||
buildah rmi -a -f
|
||||
|
@ -1086,7 +1094,7 @@ load helpers
|
|||
|
||||
@test "bud with copy-from in Dockerfile no prior FROM" {
|
||||
target=php-image
|
||||
run_buildah --debug=false bud --signature-policy ${TESTSDIR}/policy.json -t ${target} -f ${TESTSDIR}/bud/copy-from ${TESTSDIR}/bud/copy-from
|
||||
run_buildah bud --signature-policy ${TESTSDIR}/policy.json -t ${target} -f ${TESTSDIR}/bud/copy-from ${TESTSDIR}/bud/copy-from
|
||||
|
||||
ctr=$(buildah --debug=false from --signature-policy ${TESTSDIR}/policy.json ${target})
|
||||
mnt=$(buildah --debug=false mount ${ctr})
|
||||
|
|
|
@ -30,7 +30,7 @@ load helpers
|
|||
expect_line_count 3
|
||||
|
||||
run_buildah --debug=false images -a
|
||||
expect_line_count 6
|
||||
expect_line_count 8
|
||||
|
||||
# create a no name image which should show up when doing buildah images without the --all flag
|
||||
buildah bud --signature-policy ${TESTSDIR}/policy.json ${TESTSDIR}/bud/use-layers
|
||||
|
|
|
@ -135,13 +135,13 @@ load helpers
|
|||
buildah rmi -a -f
|
||||
buildah bud --signature-policy ${TESTSDIR}/policy.json --layers -t test1 ${TESTSDIR}/bud/use-layers
|
||||
run_buildah --debug=false images -a -q
|
||||
expect_line_count 5
|
||||
expect_line_count 7
|
||||
buildah bud --signature-policy ${TESTSDIR}/policy.json --layers -t test2 -f Dockerfile.2 ${TESTSDIR}/bud/use-layers
|
||||
run_buildah --debug=false images -a -q
|
||||
expect_line_count 7
|
||||
expect_line_count 9
|
||||
run_buildah --debug=false rmi test2
|
||||
run_buildah --debug=false images -a -q
|
||||
expect_line_count 5
|
||||
expect_line_count 7
|
||||
run_buildah --debug=false rmi test1
|
||||
run_buildah --debug=false images -a -q
|
||||
expect_line_count 1
|
||||
|
|
Loading…
Reference in New Issue