//go:build !remote package libimage import ( "context" "fmt" "os" goruntime "runtime" "testing" "github.com/containers/common/pkg/config" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestPull(t *testing.T) { runtime := testNewRuntime(t) ctx := context.Background() pullOptions := &PullOptions{} pullOptions.Writer = os.Stdout // Make sure that parsing errors of the daemon transport are returned // and that we do not fallthrough attempting to pull the specified // string as an image from a registry. _, err := runtime.Pull(ctx, "docker-daemon:alpine", config.PullPolicyAlways, pullOptions) require.Error(t, err, "return parsing error from daemon transport") for _, test := range []struct { input string expectError bool numImages int names []string }{ // DOCKER ARCHIVE {"docker-archive:testdata/docker-name-only.tar.xz", false, 1, []string{"localhost/pretty-empty:latest"}}, {"docker-archive:testdata/docker-registry-name.tar.xz", false, 1, []string{"example.com/empty:latest"}}, {"docker-archive:testdata/docker-two-names.tar.xz", false, 2, []string{"example.com/empty:latest", "localhost/pretty-empty:latest"}}, {"docker-archive:testdata/docker-two-images.tar.xz", true, 0, nil}, // LOAD must be used here {"docker-archive:testdata/docker-unnamed.tar.xz", false, 1, []string{"ec9293436c2e66da44edb9efb8d41f6b13baf62283ebe846468bc992d76d7951"}}, // OCI ARCHIVE {"oci-archive:testdata/oci-name-only.tar.gz", false, 1, []string{"localhost/pretty-empty:latest"}}, {"oci-archive:testdata/oci-non-docker-name.tar.gz", true, 0, nil}, {"oci-archive:testdata/oci-registry-name.tar.gz", false, 1, []string{"example.com/empty:latest"}}, {"oci-archive:testdata/oci-unnamed.tar.gz", false, 1, []string{"5c8aca8137ac47e84c69ae93ce650ce967917cc001ba7aad5494073fac75b8b6"}}, // REGISTRY {"alpine", false, 1, []string{"docker.io/library/alpine:latest"}}, {"docker://alpine", false, 1, []string{"docker.io/library/alpine:latest"}}, {"docker.io/library/alpine", false, 1, []string{"docker.io/library/alpine:latest"}}, {"docker://docker.io/library/alpine", false, 1, []string{"docker.io/library/alpine:latest"}}, {"quay.io/libpod/alpine@sha256:634a8f35b5f16dcf4aaa0822adc0b1964bb786fca12f6831de8ddc45e5986a00", false, 1, []string{"quay.io/libpod/alpine@sha256:634a8f35b5f16dcf4aaa0822adc0b1964bb786fca12f6831de8ddc45e5986a00"}}, {"quay.io/libpod/alpine:pleaseignorethistag@sha256:634a8f35b5f16dcf4aaa0822adc0b1964bb786fca12f6831de8ddc45e5986a00", false, 1, []string{"quay.io/libpod/alpine@sha256:634a8f35b5f16dcf4aaa0822adc0b1964bb786fca12f6831de8ddc45e5986a00"}}, // DIR {"dir:testdata/scratch-dir-5pec!@L", false, 1, []string{"61e17f84d763cc086d43c67dcf4cdbd69f9224c74e961c53b589b70499eac443"}}, } { pulledImages, err := runtime.Pull(ctx, test.input, config.PullPolicyAlways, pullOptions) if test.expectError { require.Error(t, err, test.input) continue } require.NoError(t, err, test.input) require.Len(t, pulledImages, test.numImages) // Now lookup an image with the expected name and compare IDs. image, resolvedName, err := runtime.LookupImage(test.names[0], nil) require.NoError(t, err, test.input) require.Equal(t, test.names[0], resolvedName, fmt.Sprintf("%v", image.Names())) require.Equal(t, pulledImages[0].ID(), image.ID(), test.input) // Now remove the image. rmReports, rmErrors := runtime.RemoveImages(ctx, test.names, &RemoveImagesOptions{Force: true}) require.Len(t, rmErrors, 0) require.Len(t, rmReports, 1) assert.Equal(t, image.ID(), rmReports[0].ID) assert.True(t, rmReports[0].Removed) } } func TestPullPlatforms(t *testing.T) { runtime := testNewRuntime(t) ctx := context.Background() pullOptions := &PullOptions{} pullOptions.Writer = os.Stdout localArch := goruntime.GOARCH localOS := goruntime.GOOS withTag := "quay.io/libpod/busybox:musl" pulledImages, err := runtime.Pull(ctx, withTag, config.PullPolicyAlways, pullOptions) require.NoError(t, err, "pull busybox") require.Len(t, pulledImages, 1) // Repulling with a bogus architecture should yield an error and not // choose the local image. pullOptions.Architecture = "bogus" _, err = runtime.Pull(ctx, withTag, config.PullPolicyNewer, pullOptions) require.Error(t, err, "pulling with a bogus architecture must fail even if there is a local image of another architecture") require.Contains(t, err.Error(), `no image found in manifest list for architecture "bogus"`) image, _, err := runtime.LookupImage(withTag, nil) require.NoError(t, err, "lookup busybox") require.NotNil(t, image, "lookup busybox") _, _, err = runtime.LookupImage("busybox", nil) require.Error(t, err, "untagged image resolves to non-existent :latest") image, _, err = runtime.LookupImage(withTag, &LookupImageOptions{Architecture: localArch}) require.NoError(t, err, "lookup busybox - by local arch") require.NotNil(t, image, "lookup busybox - by local arch") image, _, err = runtime.LookupImage(withTag, &LookupImageOptions{OS: localOS}) require.NoError(t, err, "lookup busybox - by local arch") require.NotNil(t, image, "lookup busybox - by local arch") _, _, err = runtime.LookupImage(withTag, &LookupImageOptions{Architecture: "bogus"}) require.Error(t, err, "lookup busybox - bogus arch") _, _, err = runtime.LookupImage(withTag, &LookupImageOptions{OS: "bogus"}) require.Error(t, err, "lookup busybox - bogus OS") pullOptions.Architecture = "arm" pulledImages, err = runtime.Pull(ctx, withTag, config.PullPolicyAlways, pullOptions) require.NoError(t, err, "pull busybox - arm") require.Len(t, pulledImages, 1) pullOptions.Architecture = "" image, _, err = runtime.LookupImage(withTag, &LookupImageOptions{Architecture: "arm"}) require.NoError(t, err, "lookup busybox - by arm") require.NotNil(t, image, "lookup busybox - by arm") pullOptions.Architecture = "aarch64" pulledImages, err = runtime.Pull(ctx, withTag, config.PullPolicyAlways, pullOptions) require.NoError(t, err, "pull busybox - aarch64") require.Len(t, pulledImages, 1) } func TestPullPlatformsWithEmptyRegistriesConf(t *testing.T) { runtime := testNewRuntime(t, testNewRuntimeOptions{registriesConfPath: "/dev/null"}) ctx := context.Background() pullOptions := &PullOptions{} pullOptions.Writer = os.Stdout localArch := goruntime.GOARCH localOS := goruntime.GOOS imageName := "quay.io/libpod/busybox" newTag := "crazy:train" pulledImages, err := runtime.Pull(ctx, imageName, config.PullPolicyAlways, pullOptions) require.NoError(t, err, "pull "+imageName) require.Len(t, pulledImages, 1) err = pulledImages[0].Tag(newTag) require.NoError(t, err, "tag") // See containers/podman/issues/12707: a custom platform will enforce // pulling via newer. Older versions enforced always which can lead to // errors. pullOptions.OS = localOS pullOptions.Architecture = localArch pulledImages, err = runtime.Pull(ctx, newTag, config.PullPolicyMissing, pullOptions) require.NoError(t, err, "pull "+newTag) require.Len(t, pulledImages, 1) } func TestPullPolicy(t *testing.T) { runtime := testNewRuntime(t) ctx := context.Background() pullOptions := &PullOptions{} pulledImages, err := runtime.Pull(ctx, "alpine", config.PullPolicyNever, pullOptions) require.Error(t, err, "Never pull different arch alpine") require.Nil(t, pulledImages, "lookup alpine") pulledImages, err = runtime.Pull(ctx, "alpine", config.PullPolicyNewer, pullOptions) require.NoError(t, err, "Newer pull different arch alpine") require.NotNil(t, pulledImages, "lookup alpine") pulledImages, err = runtime.Pull(ctx, "alpine", config.PullPolicyNever, pullOptions) require.NoError(t, err, "Never pull different arch alpine") require.NotNil(t, pulledImages, "lookup alpine") } func TestShortNameAndIDconflict(t *testing.T) { // Regression test for https://github.com/containers/podman/issues/12761 runtime := testNewRuntime(t) ctx := context.Background() pullOptions := &PullOptions{} pullOptions.Writer = os.Stdout busybox, err := runtime.Pull(ctx, "busybox", config.PullPolicyAlways, pullOptions) require.NoError(t, err) require.Len(t, busybox, 1) alpine, err := runtime.Pull(ctx, "alpine", config.PullPolicyAlways, pullOptions) require.NoError(t, err) require.Len(t, alpine, 1) // Tag the alpine image with the first character of busybox's ID to // cause a conflict when looking up the image. The expected outcome is // that short names always have precedence of IDs. c := busybox[0].ID()[0:1] err = alpine[0].Tag(c) require.NoError(t, err, "tag") // Short name is selected over ID. img, _, err := runtime.LookupImage(c, nil) require.NoError(t, err) require.Equal(t, alpine[0].ID(), img.ID()) // Not matching short name, so ID is selected. img, _, err = runtime.LookupImage(busybox[0].ID()[0:2], nil) require.NoError(t, err) require.Equal(t, busybox[0].ID(), img.ID()) } func TestPullOCINoReference(t *testing.T) { // Exercise pulling from the OCI transport and make sure that a // specified reference is preserved in the image name. busybox := "quay.io/libpod/busybox:latest" runtime := testNewRuntime(t) ctx := context.Background() pullOptions := &PullOptions{} pullOptions.Writer = os.Stdout images, err := runtime.Pull(ctx, busybox, config.PullPolicyAlways, pullOptions) require.NoError(t, err) require.Len(t, images, 1) // Push one image without the optional reference ociPathNoRef := "oci:" + t.TempDir() + "noRef" _, err = runtime.Push(ctx, busybox, ociPathNoRef, nil) require.NoError(t, err) // Push another image _with_ the optional reference which allows for // preserving the name. ociPathWithRef := "oci:" + t.TempDir() + "withRef:" + busybox _, err = runtime.Push(ctx, busybox, ociPathWithRef, nil) require.NoError(t, err) _, errors := runtime.RemoveImages(ctx, []string{busybox}, nil) require.Nil(t, errors) images, err = runtime.Pull(ctx, ociPathNoRef, config.PullPolicyAlways, pullOptions) require.NoError(t, err) require.Len(t, images, 1) exists, err := runtime.Exists(busybox) // busybox does not exist require.NoError(t, err) require.False(t, exists) names := images[0].Names() // The image has no names (i.e., ) require.Nil(t, names) images, err = runtime.Pull(ctx, ociPathWithRef, config.PullPolicyAlways, pullOptions) require.NoError(t, err) require.Len(t, images, 1) exists, err = runtime.Exists(busybox) // busybox does exist now require.NoError(t, err) require.True(t, exists) }