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 }