diff options
-rw-r--r-- | cmd/podman/tree.go | 6 | ||||
-rw-r--r-- | libpod/image/image.go | 94 | ||||
-rw-r--r-- | test/e2e/images_test.go | 70 |
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("[]")) + }) }) |