github.com/containers/libpod@v1.9.4-0.20220419124438-4284fd425507/libpod/image/image_test.go (about)

     1  package image
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"os"
     8  	"testing"
     9  
    10  	"github.com/containers/libpod/libpod/events"
    11  	"github.com/containers/libpod/pkg/util"
    12  	"github.com/containers/storage"
    13  	"github.com/containers/storage/pkg/reexec"
    14  	"github.com/opencontainers/go-digest"
    15  	"github.com/stretchr/testify/assert"
    16  )
    17  
    18  var (
    19  	bbNames      = []string{"docker.io/library/busybox:latest", "docker.io/library/busybox", "docker.io/busybox:latest", "docker.io/busybox", "busybox:latest", "busybox"}
    20  	bbGlibcNames = []string{"docker.io/library/busybox:glibc", "docker.io/busybox:glibc", "busybox:glibc"}
    21  )
    22  
    23  type localImageTest struct {
    24  	fqname, taggedName string
    25  	img                *Image
    26  	names              []string
    27  }
    28  
    29  // make a temporary directory for the runtime
    30  func mkWorkDir() (string, error) {
    31  	return ioutil.TempDir("", "podman-test")
    32  }
    33  
    34  // shutdown the runtime and clean behind it
    35  func cleanup(workdir string, ir *Runtime) {
    36  	if err := ir.Shutdown(false); err != nil {
    37  		fmt.Println(err)
    38  		os.Exit(1)
    39  	}
    40  	err := os.RemoveAll(workdir)
    41  	if err != nil {
    42  		fmt.Println(err)
    43  		os.Exit(1)
    44  	}
    45  }
    46  
    47  func makeLocalMatrix(b, bg *Image) ([]localImageTest, error) {
    48  	var l []localImageTest
    49  	// busybox
    50  	busybox := localImageTest{
    51  		fqname:     "docker.io/library/busybox:latest",
    52  		taggedName: "bb:latest",
    53  	}
    54  	busybox.img = b
    55  	busybox.names = b.Names()
    56  	busybox.names = append(busybox.names, []string{"bb:latest", "bb", b.ID(), b.ID()[0:7], fmt.Sprintf("busybox@%s", b.Digest())}...)
    57  
    58  	// busybox-glibc
    59  	busyboxGlibc := localImageTest{
    60  		fqname:     "docker.io/library/busybox:glibc",
    61  		taggedName: "bb:glibc",
    62  	}
    63  
    64  	busyboxGlibc.img = bg
    65  	busyboxGlibc.names = bbGlibcNames
    66  
    67  	l = append(l, busybox, busyboxGlibc)
    68  	return l, nil
    69  
    70  }
    71  
    72  func TestMain(m *testing.M) {
    73  	if reexec.Init() {
    74  		return
    75  	}
    76  	os.Exit(m.Run())
    77  }
    78  
    79  // TestImage_NewFromLocal tests finding the image locally by various names,
    80  // tags, and aliases
    81  func TestImage_NewFromLocal(t *testing.T) {
    82  	if os.Geteuid() != 0 { // containers/storage requires root access
    83  		t.Skipf("Test not running as root")
    84  	}
    85  
    86  	workdir, err := mkWorkDir()
    87  	assert.NoError(t, err)
    88  	so := storage.StoreOptions{
    89  		RunRoot:   workdir,
    90  		GraphRoot: workdir,
    91  	}
    92  	writer := os.Stdout
    93  
    94  	// Need images to be present for this test
    95  	ir, err := NewImageRuntimeFromOptions(so)
    96  	assert.NoError(t, err)
    97  	ir.Eventer = events.NewNullEventer()
    98  	bb, err := ir.New(context.Background(), "docker.io/library/busybox:latest", "", "", writer, nil, SigningOptions{}, nil, util.PullImageMissing)
    99  	assert.NoError(t, err)
   100  	bbglibc, err := ir.New(context.Background(), "docker.io/library/busybox:glibc", "", "", writer, nil, SigningOptions{}, nil, util.PullImageMissing)
   101  	assert.NoError(t, err)
   102  
   103  	tm, err := makeLocalMatrix(bb, bbglibc)
   104  	assert.NoError(t, err)
   105  
   106  	for _, image := range tm {
   107  		// tag our images
   108  		err = image.img.TagImage(image.taggedName)
   109  		assert.NoError(t, err)
   110  		for _, name := range image.names {
   111  			newImage, err := ir.NewFromLocal(name)
   112  			assert.NoError(t, err)
   113  			assert.Equal(t, newImage.ID(), image.img.ID())
   114  		}
   115  	}
   116  
   117  	// Shutdown the runtime and remove the temporary storage
   118  	cleanup(workdir, ir)
   119  }
   120  
   121  // TestImage_New tests pulling the image by various names, tags, and from
   122  // different registries
   123  func TestImage_New(t *testing.T) {
   124  	if os.Geteuid() != 0 { // containers/storage requires root access
   125  		t.Skipf("Test not running as root")
   126  	}
   127  
   128  	var names []string
   129  	workdir, err := mkWorkDir()
   130  	assert.NoError(t, err)
   131  
   132  	so := storage.StoreOptions{
   133  		RunRoot:   workdir,
   134  		GraphRoot: workdir,
   135  	}
   136  	ir, err := NewImageRuntimeFromOptions(so)
   137  	assert.NoError(t, err)
   138  	ir.Eventer = events.NewNullEventer()
   139  	// Build the list of pull names
   140  	names = append(names, bbNames...)
   141  	writer := os.Stdout
   142  
   143  	// Iterate over the names and delete the image
   144  	// after the pull
   145  	for _, img := range names {
   146  		newImage, err := ir.New(context.Background(), img, "", "", writer, nil, SigningOptions{}, nil, util.PullImageMissing)
   147  		assert.NoError(t, err)
   148  		assert.NotEqual(t, newImage.ID(), "")
   149  		err = newImage.Remove(context.Background(), false)
   150  		assert.NoError(t, err)
   151  	}
   152  
   153  	// Shutdown the runtime and remove the temporary storage
   154  	cleanup(workdir, ir)
   155  }
   156  
   157  // TestImage_MatchRepoTag tests the various inputs we need to match
   158  // against an image's reponames
   159  func TestImage_MatchRepoTag(t *testing.T) {
   160  	if os.Geteuid() != 0 { // containers/storage requires root access
   161  		t.Skipf("Test not running as root")
   162  	}
   163  
   164  	//Set up
   165  	workdir, err := mkWorkDir()
   166  	assert.NoError(t, err)
   167  
   168  	so := storage.StoreOptions{
   169  		RunRoot:   workdir,
   170  		GraphRoot: workdir,
   171  	}
   172  	ir, err := NewImageRuntimeFromOptions(so)
   173  	assert.NoError(t, err)
   174  	ir.Eventer = events.NewNullEventer()
   175  	newImage, err := ir.New(context.Background(), "busybox", "", "", os.Stdout, nil, SigningOptions{}, nil, util.PullImageMissing)
   176  	assert.NoError(t, err)
   177  	err = newImage.TagImage("foo:latest")
   178  	assert.NoError(t, err)
   179  	err = newImage.TagImage("foo:bar")
   180  	assert.NoError(t, err)
   181  
   182  	// Tests start here.
   183  	for _, name := range bbNames {
   184  		repoTag, err := newImage.MatchRepoTag(name)
   185  		assert.NoError(t, err)
   186  		assert.Equal(t, "docker.io/library/busybox:latest", repoTag)
   187  	}
   188  
   189  	// Test against tagged images of busybox
   190  
   191  	// foo should resolve to foo:latest
   192  	repoTag, err := newImage.MatchRepoTag("foo")
   193  	assert.NoError(t, err)
   194  	assert.Equal(t, "localhost/foo:latest", repoTag)
   195  
   196  	// foo:bar should resolve to foo:bar
   197  	repoTag, err = newImage.MatchRepoTag("foo:bar")
   198  	assert.NoError(t, err)
   199  	assert.Equal(t, "localhost/foo:bar", repoTag)
   200  	// Shutdown the runtime and remove the temporary storage
   201  	cleanup(workdir, ir)
   202  }
   203  
   204  // TestImage_RepoDigests tests RepoDigest generation.
   205  func TestImage_RepoDigests(t *testing.T) {
   206  	dgst, err := digest.Parse("sha256:7173b809ca12ec5dee4506cd86be934c4596dd234ee82c0662eac04a8c2c71dc")
   207  	if err != nil {
   208  		t.Fatal(err)
   209  	}
   210  
   211  	for _, tt := range []struct {
   212  		name     string
   213  		names    []string
   214  		expected []string
   215  	}{
   216  		{
   217  			name:     "empty",
   218  			names:    []string{},
   219  			expected: nil,
   220  		},
   221  		{
   222  			name:     "tagged",
   223  			names:    []string{"docker.io/library/busybox:latest"},
   224  			expected: []string{"docker.io/library/busybox@sha256:7173b809ca12ec5dee4506cd86be934c4596dd234ee82c0662eac04a8c2c71dc"},
   225  		},
   226  		{
   227  			name:     "digest",
   228  			names:    []string{"docker.io/library/busybox@sha256:7173b809ca12ec5dee4506cd86be934c4596dd234ee82c0662eac04a8c2c71dc"},
   229  			expected: []string{"docker.io/library/busybox@sha256:7173b809ca12ec5dee4506cd86be934c4596dd234ee82c0662eac04a8c2c71dc"},
   230  		},
   231  	} {
   232  		test := tt
   233  		t.Run(test.name, func(t *testing.T) {
   234  			image := &Image{
   235  				image: &storage.Image{
   236  					Names:  test.names,
   237  					Digest: dgst,
   238  				},
   239  			}
   240  			actual, err := image.RepoDigests()
   241  			if err != nil {
   242  				t.Fatal(err)
   243  			}
   244  
   245  			assert.Equal(t, test.expected, actual)
   246  
   247  			image = &Image{
   248  				image: &storage.Image{
   249  					Names:   test.names,
   250  					Digests: []digest.Digest{dgst},
   251  				},
   252  			}
   253  			actual, err = image.RepoDigests()
   254  			if err != nil {
   255  				t.Fatal(err)
   256  			}
   257  
   258  			assert.Equal(t, test.expected, actual)
   259  		})
   260  	}
   261  }
   262  
   263  // Test_splitString tests the splitString function in image that
   264  // takes input and splits on / and returns the last array item
   265  func Test_splitString(t *testing.T) {
   266  	assert.Equal(t, splitString("foo/bar"), "bar")
   267  	assert.Equal(t, splitString("a/foo/bar"), "bar")
   268  	assert.Equal(t, splitString("bar"), "bar")
   269  }
   270  
   271  // Test_stripSha256 tests test the stripSha256 function which removes
   272  // the prefix "sha256:" from a string if it is present
   273  func Test_stripSha256(t *testing.T) {
   274  	assert.Equal(t, stripSha256(""), "")
   275  	assert.Equal(t, stripSha256("test1"), "test1")
   276  	assert.Equal(t, stripSha256("sha256:9110ae7f579f35ee0c3938696f23fe0f5fbe641738ea52eb83c2df7e9995fa17"), "9110ae7f579f35ee0c3938696f23fe0f5fbe641738ea52eb83c2df7e9995fa17")
   277  	assert.Equal(t, stripSha256("sha256:9110ae7f"), "9110ae7f")
   278  	assert.Equal(t, stripSha256("sha256:"), "sha256:")
   279  	assert.Equal(t, stripSha256("sha256:a"), "a")
   280  }
   281  
   282  func TestNormalizedTag(t *testing.T) {
   283  	const digestSuffix = "@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
   284  
   285  	for _, c := range []struct{ input, expected string }{
   286  		{"#", ""}, // Clearly invalid
   287  		{"example.com/busybox", "example.com/busybox:latest"},                                            // Qualified name-only
   288  		{"example.com/busybox:notlatest", "example.com/busybox:notlatest"},                               // Qualified name:tag
   289  		{"example.com/busybox" + digestSuffix, "example.com/busybox" + digestSuffix},                     // Qualified name@digest; FIXME? Should we allow tagging with a digest at all?
   290  		{"example.com/busybox:notlatest" + digestSuffix, "example.com/busybox:notlatest" + digestSuffix}, // Qualified name:tag@digest
   291  		{"busybox:latest", "localhost/busybox:latest"},                                                   // Unqualified name-only
   292  		{"ns/busybox:latest", "localhost/ns/busybox:latest"},                                             // Unqualified with a dot-less namespace
   293  		{"docker.io/busybox:latest", "docker.io/library/busybox:latest"},                                 // docker.io without /library/
   294  	} {
   295  		res, err := NormalizedTag(c.input)
   296  		if c.expected == "" {
   297  			assert.Error(t, err, c.input)
   298  		} else {
   299  			assert.NoError(t, err, c.input)
   300  			assert.Equal(t, c.expected, res.String())
   301  		}
   302  	}
   303  }