aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmd/podman/tree.go6
-rw-r--r--libpod/image/image.go94
-rw-r--r--test/e2e/images_test.go70
3 files changed, 123 insertions, 47 deletions
diff --git a/cmd/podman/tree.go b/cmd/podman/tree.go
index 6490c609d..0f62858e8 100644
--- a/cmd/podman/tree.go
+++ b/cmd/podman/tree.go
@@ -72,7 +72,11 @@ func printTree(imageInfo *image.InfoImage, layerInfoMap map[string]*image.LayerI
fmt.Printf("Image ID: %s\n", imageInfo.ID[:12])
fmt.Printf("Tags:\t %s\n", imageInfo.Tags)
fmt.Printf("Size:\t %v\n", units.HumanSizeWithPrecision(float64(*size), 4))
- fmt.Printf(fmt.Sprintf("Image Layers\n"))
+ if img.TopLayer() != "" {
+ fmt.Printf("Image Layers\n")
+ } else {
+ fmt.Printf("No Image Layers\n")
+ }
if !whatRequires {
// fill imageInfo with layers associated with image.
diff --git a/libpod/image/image.go b/libpod/image/image.go
index 89a68a1bd..76e46f74f 100644
--- a/libpod/image/image.go
+++ b/libpod/image/image.go
@@ -660,11 +660,7 @@ func (i *Image) Size(ctx context.Context) (*uint64, error) {
// DriverData gets the driver data from the store on a layer
func (i *Image) DriverData() (*driver.Data, error) {
- topLayer, err := i.Layer()
- if err != nil {
- return nil, err
- }
- return driver.GetDriverData(i.imageruntime.store, topLayer.ID)
+ return driver.GetDriverData(i.imageruntime.store, i.TopLayer())
}
// Layer returns the image's top layer
@@ -693,13 +689,17 @@ func (i *Image) History(ctx context.Context) ([]*History, error) {
return nil, err
}
- // Use our layers list to find images that use one of them as its
+ // Use our layers list to find images that use any of them (or no
+ // layer, since every base layer is derived from an empty layer) as its
// topmost layer.
interestingLayers := make(map[string]bool)
- layer, err := i.imageruntime.store.Layer(i.TopLayer())
- if err != nil {
- return nil, err
+ var layer *storage.Layer
+ if i.TopLayer() != "" {
+ if layer, err = i.imageruntime.store.Layer(i.TopLayer()); err != nil {
+ return nil, err
+ }
}
+ interestingLayers[""] = true
for layer != nil {
interestingLayers[layer.ID] = true
if layer.Parent == "" {
@@ -795,27 +795,6 @@ func (i *Image) History(ctx context.Context) ([]*History, error) {
return allHistory, nil
}
-// historyLayerIDs goes through the images in store and checks if the top layer of an image
-// is the same as the parent of topLayerID
-func (i *Image) historyLayerIDs(topLayerID string, images []*Image, IDs *[]string) error {
- for _, image := range images {
- // Get the layer info of topLayerID
- layer, err := i.imageruntime.store.Layer(topLayerID)
- if err != nil {
- return errors.Wrapf(err, "error getting layer info %q", topLayerID)
- }
- // Check if the parent of layer is equal to the image's top layer
- // If so add the image ID to the list of IDs and find the parent of
- // the top layer of the image ID added to the list
- // Since we are checking for parent, each top layer can only have one parent
- if layer.Parent == image.TopLayer() {
- *IDs = append(*IDs, image.ID())
- return i.historyLayerIDs(image.TopLayer(), images, IDs)
- }
- }
- return nil
-}
-
// Dangling returns a bool if the image is "dangling"
func (i *Image) Dangling() bool {
return len(i.Names()) == 0
@@ -1143,13 +1122,15 @@ func areParentAndChild(parent, child *imgspecv1.Image) bool {
// GetParent returns the image ID of the parent. Return nil if a parent is not found.
func (i *Image) GetParent(ctx context.Context) (*Image, error) {
+ var childLayer *storage.Layer
images, err := i.imageruntime.GetImages()
if err != nil {
return nil, err
}
- childLayer, err := i.imageruntime.store.Layer(i.TopLayer())
- if err != nil {
- return nil, err
+ if i.TopLayer() != "" {
+ if childLayer, err = i.imageruntime.store.Layer(i.TopLayer()); err != nil {
+ return nil, err
+ }
}
// fetch the configuration for the child image
child, err := i.ociv1Image(ctx)
@@ -1161,11 +1142,23 @@ func (i *Image) GetParent(ctx context.Context) (*Image, error) {
continue
}
candidateLayer := img.TopLayer()
- // as a child, our top layer is either the candidate parent's
- // layer, or one that's derived from it, so skip over any
- // candidate image where we know that isn't the case
- if candidateLayer != childLayer.Parent && candidateLayer != childLayer.ID {
- continue
+ // as a child, our top layer, if we have one, is either the
+ // candidate parent's layer, or one that's derived from it, so
+ // skip over any candidate image where we know that isn't the
+ // case
+ if childLayer != nil {
+ // The child has at least one layer, so a parent would
+ // have a top layer that's either the same as the child's
+ // top layer or the top layer's recorded parent layer,
+ // which could be an empty value.
+ if candidateLayer != childLayer.Parent && candidateLayer != childLayer.ID {
+ continue
+ }
+ } else {
+ // The child has no layers, but the candidate does.
+ if candidateLayer != "" {
+ continue
+ }
}
// fetch the configuration for the candidate image
candidate, err := img.ociv1Image(ctx)
@@ -1204,14 +1197,22 @@ func (i *Image) getChildren(ctx context.Context, max int) ([]string, error) {
if img.ID() == i.ID() {
continue
}
- candidateLayer, err := img.Layer()
- if err != nil {
- return nil, err
- }
- // if this image's top layer is not our top layer, and is not
- // based on our top layer, we can skip it
- if candidateLayer.Parent != parentLayer && candidateLayer.ID != parentLayer {
- continue
+ if img.TopLayer() == "" {
+ if parentLayer != "" {
+ // this image has no layers, but we do, so
+ // it can't be derived from this one
+ continue
+ }
+ } else {
+ candidateLayer, err := img.Layer()
+ if err != nil {
+ return nil, err
+ }
+ // if this image's top layer is not our top layer, and is not
+ // based on our top layer, we can skip it
+ if candidateLayer.Parent != parentLayer && candidateLayer.ID != parentLayer {
+ continue
+ }
}
// fetch the configuration for the candidate image
candidate, err := img.ociv1Image(ctx)
@@ -1443,6 +1444,7 @@ func GetLayersMapWithImageInfo(imageruntime *Runtime) (map[string]*LayerInfo, er
if err != nil {
return nil, err
}
+ layerInfoMap[""] = &LayerInfo{}
for _, img := range imgs {
e, ok := layerInfoMap[img.TopLayer]
if !ok {
diff --git a/test/e2e/images_test.go b/test/e2e/images_test.go
index 07d61e885..b6dae33ee 100644
--- a/test/e2e/images_test.go
+++ b/test/e2e/images_test.go
@@ -4,6 +4,7 @@ import (
"fmt"
"os"
"sort"
+ "strings"
. "github.com/containers/libpod/test/utils"
"github.com/docker/go-units"
@@ -318,4 +319,73 @@ LABEL "com.example.vendor"="Example Vendor"
Expect(session.ExitCode()).To(Equal(0))
Expect(len(session.OutputToStringArray())).To(Equal(2))
})
+
+ It("podman with images with no layers", func() {
+ if podmanTest.RemoteTest {
+ Skip("Does not work on remote client")
+ }
+
+ dockerfile := strings.Join([]string{
+ `FROM scratch`,
+ `LABEL org.opencontainers.image.authors="<somefolks@example.org>"`,
+ `LABEL org.opencontainers.image.created=2019-06-11T19:03:37Z`,
+ `LABEL org.opencontainers.image.description="This is a test image"`,
+ `LABEL org.opencontainers.image.title=test`,
+ `LABEL org.opencontainers.image.vendor="Example.org"`,
+ `LABEL org.opencontainers.image.version=1`,
+ }, "\n")
+ podmanTest.BuildImage(dockerfile, "foo", "true")
+
+ session := podmanTest.Podman([]string{"images", "foo"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ output := session.OutputToString()
+ Expect(output).To(Not(MatchRegexp("<missing>")))
+ Expect(output).To(Not(MatchRegexp("error")))
+
+ session = podmanTest.Podman([]string{"image", "tree", "foo"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ output = session.OutputToString()
+ Expect(output).To(MatchRegexp("No Image Layers"))
+
+ session = podmanTest.Podman([]string{"history", "foo"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ output = session.OutputToString()
+ Expect(output).To(Not(MatchRegexp("<missing>")))
+ Expect(output).To(Not(MatchRegexp("error")))
+
+ session = podmanTest.Podman([]string{"history", "--quiet", "foo"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ Expect(len(session.OutputToStringArray())).To(Equal(6))
+
+ session = podmanTest.Podman([]string{"image", "list", "foo"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ output = session.OutputToString()
+ Expect(output).To(Not(MatchRegexp("<missing>")))
+ Expect(output).To(Not(MatchRegexp("error")))
+
+ session = podmanTest.Podman([]string{"image", "list"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ output = session.OutputToString()
+ Expect(output).To(Not(MatchRegexp("<missing>")))
+ Expect(output).To(Not(MatchRegexp("error")))
+
+ session = podmanTest.Podman([]string{"inspect", "foo"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ output = session.OutputToString()
+ Expect(output).To(Not(MatchRegexp("<missing>")))
+ Expect(output).To(Not(MatchRegexp("error")))
+
+ session = podmanTest.Podman([]string{"inspect", "--format", "{{.RootFS.Layers}}", "foo"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+ output = session.OutputToString()
+ Expect(output).To(Equal("[]"))
+ })
})