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 }