github.com/containers/podman/v2@v2.2.2-0.20210501105131-c1e07d070c4c/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/podman/v2/libpod/events"
    11  	"github.com/containers/podman/v2/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 {
    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
    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 := makeLocalMatrix(bb, bbglibc)
   104  	for _, image := range tm {
   105  		// tag our images
   106  		err = image.img.TagImage(image.taggedName)
   107  		assert.NoError(t, err)
   108  		for _, name := range image.names {
   109  			newImage, err := ir.NewFromLocal(name)
   110  			assert.NoError(t, err)
   111  			assert.Equal(t, newImage.ID(), image.img.ID())
   112  		}
   113  	}
   114  
   115  	// Shutdown the runtime and remove the temporary storage
   116  	cleanup(workdir, ir)
   117  }
   118  
   119  // TestImage_New tests pulling the image by various names, tags, and from
   120  // different registries
   121  func TestImage_New(t *testing.T) {
   122  	if os.Geteuid() != 0 { // containers/storage requires root access
   123  		t.Skipf("Test not running as root")
   124  	}
   125  
   126  	var names []string
   127  	workdir, err := mkWorkDir()
   128  	assert.NoError(t, err)
   129  
   130  	so := storage.StoreOptions{
   131  		RunRoot:   workdir,
   132  		GraphRoot: workdir,
   133  	}
   134  	ir, err := NewImageRuntimeFromOptions(so)
   135  	assert.NoError(t, err)
   136  	ir.Eventer = events.NewNullEventer()
   137  	// Build the list of pull names
   138  	names = append(names, bbNames...)
   139  	writer := os.Stdout
   140  
   141  	// Iterate over the names and delete the image
   142  	// after the pull
   143  	for _, img := range names {
   144  		newImage, err := ir.New(context.Background(), img, "", "", writer, nil, SigningOptions{}, nil, util.PullImageMissing)
   145  		assert.NoError(t, err)
   146  		assert.NotEqual(t, newImage.ID(), "")
   147  		err = newImage.Remove(context.Background(), false)
   148  		assert.NoError(t, err)
   149  	}
   150  
   151  	// Shutdown the runtime and remove the temporary storage
   152  	cleanup(workdir, ir)
   153  }
   154  
   155  // TestImage_MatchRepoTag tests the various inputs we need to match
   156  // against an image's reponames
   157  func TestImage_MatchRepoTag(t *testing.T) {
   158  	if os.Geteuid() != 0 { // containers/storage requires root access
   159  		t.Skipf("Test not running as root")
   160  	}
   161  
   162  	//Set up
   163  	workdir, err := mkWorkDir()
   164  	assert.NoError(t, err)
   165  
   166  	so := storage.StoreOptions{
   167  		RunRoot:   workdir,
   168  		GraphRoot: workdir,
   169  	}
   170  	ir, err := NewImageRuntimeFromOptions(so)
   171  	assert.NoError(t, err)
   172  	ir.Eventer = events.NewNullEventer()
   173  	newImage, err := ir.New(context.Background(), "busybox", "", "", os.Stdout, nil, SigningOptions{}, nil, util.PullImageMissing)
   174  	assert.NoError(t, err)
   175  	err = newImage.TagImage("foo:latest")
   176  	assert.NoError(t, err)
   177  	err = newImage.TagImage("foo:bar")
   178  	assert.NoError(t, err)
   179  
   180  	// Tests start here.
   181  	for _, name := range bbNames {
   182  		repoTag, err := newImage.MatchRepoTag(name)
   183  		assert.NoError(t, err)
   184  		assert.Equal(t, "docker.io/library/busybox:latest", repoTag)
   185  	}
   186  
   187  	// Test against tagged images of busybox
   188  
   189  	// foo should resolve to foo:latest
   190  	repoTag, err := newImage.MatchRepoTag("foo")
   191  	assert.NoError(t, err)
   192  	assert.Equal(t, "localhost/foo:latest", repoTag)
   193  
   194  	// foo:bar should resolve to foo:bar
   195  	repoTag, err = newImage.MatchRepoTag("foo:bar")
   196  	assert.NoError(t, err)
   197  	assert.Equal(t, "localhost/foo:bar", repoTag)
   198  	// Shutdown the runtime and remove the temporary storage
   199  	cleanup(workdir, ir)
   200  }
   201  
   202  // TestImage_RepoDigests tests RepoDigest generation.
   203  func TestImage_RepoDigests(t *testing.T) {
   204  	dgst, err := digest.Parse("sha256:7173b809ca12ec5dee4506cd86be934c4596dd234ee82c0662eac04a8c2c71dc")
   205  	if err != nil {
   206  		t.Fatal(err)
   207  	}
   208  
   209  	for _, tt := range []struct {
   210  		name     string
   211  		names    []string
   212  		expected []string
   213  	}{
   214  		{
   215  			name:     "empty",
   216  			names:    []string{},
   217  			expected: nil,
   218  		},
   219  		{
   220  			name:     "tagged",
   221  			names:    []string{"docker.io/library/busybox:latest"},
   222  			expected: []string{"docker.io/library/busybox@sha256:7173b809ca12ec5dee4506cd86be934c4596dd234ee82c0662eac04a8c2c71dc"},
   223  		},
   224  		{
   225  			name:     "digest",
   226  			names:    []string{"docker.io/library/busybox@sha256:7173b809ca12ec5dee4506cd86be934c4596dd234ee82c0662eac04a8c2c71dc"},
   227  			expected: []string{"docker.io/library/busybox@sha256:7173b809ca12ec5dee4506cd86be934c4596dd234ee82c0662eac04a8c2c71dc"},
   228  		},
   229  	} {
   230  		test := tt
   231  		t.Run(test.name, func(t *testing.T) {
   232  			image := &Image{
   233  				image: &storage.Image{
   234  					Names:  test.names,
   235  					Digest: dgst,
   236  				},
   237  			}
   238  			actual, err := image.RepoDigests()
   239  			if err != nil {
   240  				t.Fatal(err)
   241  			}
   242  
   243  			assert.Equal(t, test.expected, actual)
   244  
   245  			image = &Image{
   246  				image: &storage.Image{
   247  					Names:   test.names,
   248  					Digests: []digest.Digest{dgst},
   249  				},
   250  			}
   251  			actual, err = image.RepoDigests()
   252  			if err != nil {
   253  				t.Fatal(err)
   254  			}
   255  
   256  			assert.Equal(t, test.expected, actual)
   257  		})
   258  	}
   259  }
   260  
   261  // Test_splitString tests the splitString function in image that
   262  // takes input and splits on / and returns the last array item
   263  func Test_splitString(t *testing.T) {
   264  	assert.Equal(t, splitString("foo/bar"), "bar")
   265  	assert.Equal(t, splitString("a/foo/bar"), "bar")
   266  	assert.Equal(t, splitString("bar"), "bar")
   267  }
   268  
   269  // Test_stripSha256 tests test the stripSha256 function which removes
   270  // the prefix "sha256:" from a string if it is present
   271  func Test_stripSha256(t *testing.T) {
   272  	assert.Equal(t, stripSha256(""), "")
   273  	assert.Equal(t, stripSha256("test1"), "test1")
   274  	assert.Equal(t, stripSha256("sha256:9110ae7f579f35ee0c3938696f23fe0f5fbe641738ea52eb83c2df7e9995fa17"), "9110ae7f579f35ee0c3938696f23fe0f5fbe641738ea52eb83c2df7e9995fa17")
   275  	assert.Equal(t, stripSha256("sha256:9110ae7f"), "9110ae7f")
   276  	assert.Equal(t, stripSha256("sha256:"), "sha256:")
   277  	assert.Equal(t, stripSha256("sha256:a"), "a")
   278  }
   279  
   280  func TestNormalizedTag(t *testing.T) {
   281  	const digestSuffix = "@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
   282  
   283  	for _, c := range []struct{ input, expected string }{
   284  		{"#", ""}, // Clearly invalid
   285  		{"example.com/busybox", "example.com/busybox:latest"},                                            // Qualified name-only
   286  		{"example.com/busybox:notlatest", "example.com/busybox:notlatest"},                               // Qualified name:tag
   287  		{"example.com/busybox" + digestSuffix, "example.com/busybox" + digestSuffix},                     // Qualified name@digest; FIXME? Should we allow tagging with a digest at all?
   288  		{"example.com/busybox:notlatest" + digestSuffix, "example.com/busybox:notlatest" + digestSuffix}, // Qualified name:tag@digest
   289  		{"busybox:latest", "localhost/busybox:latest"},                                                   // Unqualified name-only
   290  		{"ns/busybox:latest", "localhost/ns/busybox:latest"},                                             // Unqualified with a dot-less namespace
   291  		{"docker.io/busybox:latest", "docker.io/library/busybox:latest"},                                 // docker.io without /library/
   292  	} {
   293  		res, err := NormalizedTag(c.input)
   294  		if c.expected == "" {
   295  			assert.Error(t, err, c.input)
   296  		} else {
   297  			assert.NoError(t, err, c.input)
   298  			assert.Equal(t, c.expected, res.String())
   299  		}
   300  	}
   301  }