github.com/containers/podman/v4@v4.9.4/pkg/bindings/test/images_test.go (about) 1 package bindings_test 2 3 import ( 4 "bytes" 5 "fmt" 6 "net/http" 7 "os" 8 "path/filepath" 9 "time" 10 11 podmanRegistry "github.com/containers/podman/v4/hack/podman-registry-go" 12 "github.com/containers/podman/v4/libpod/define" 13 "github.com/containers/podman/v4/pkg/bindings" 14 "github.com/containers/podman/v4/pkg/bindings/containers" 15 "github.com/containers/podman/v4/pkg/bindings/images" 16 "github.com/containers/podman/v4/pkg/domain/entities" 17 . "github.com/onsi/ginkgo/v2" 18 . "github.com/onsi/gomega" 19 . "github.com/onsi/gomega/gexec" 20 . "github.com/onsi/gomega/gstruct" 21 ) 22 23 var _ = Describe("Podman images", func() { 24 var ( 25 // tempdir string 26 // err error 27 // podmanTest *PodmanTestIntegration 28 bt *bindingTest 29 s *Session 30 err error 31 ) 32 33 BeforeEach(func() { 34 // tempdir, err = CreateTempDirInTempDir() 35 // if err != nil { 36 // os.Exit(1) 37 // } 38 // podmanTest = PodmanTestCreate(tempdir) 39 // podmanTest.Setup() 40 // podmanTest.SeedImages() 41 bt = newBindingTest() 42 bt.RestoreImagesFromCache() 43 s = bt.startAPIService() 44 time.Sleep(1 * time.Second) 45 err := bt.NewConnection() 46 Expect(err).ToNot(HaveOccurred()) 47 }) 48 49 AfterEach(func() { 50 // podmanTest.Cleanup() 51 // f := CurrentSpecReport() 52 // processTestResult(f) 53 s.Kill() 54 bt.cleanup() 55 }) 56 57 It("inspect image", func() { 58 // Inspect invalid image be 404 59 _, err = images.GetImage(bt.conn, "foobar5000", nil) 60 Expect(err).To(HaveOccurred()) 61 code, _ := bindings.CheckResponseCode(err) 62 Expect(code).To(BeNumerically("==", http.StatusNotFound)) 63 64 // Inspect by short name 65 data, err := images.GetImage(bt.conn, alpine.shortName, nil) 66 Expect(err).ToNot(HaveOccurred()) 67 68 // Inspect with full ID 69 _, err = images.GetImage(bt.conn, data.ID, nil) 70 Expect(err).ToNot(HaveOccurred()) 71 72 // Inspect with partial ID 73 _, err = images.GetImage(bt.conn, data.ID[0:12], nil) 74 Expect(err).ToNot(HaveOccurred()) 75 76 // Inspect by long name 77 _, err = images.GetImage(bt.conn, alpine.name, nil) 78 Expect(err).ToNot(HaveOccurred()) 79 // TODO it looks like the images API always returns size regardless 80 // of bool or not. What should we do ? 81 // Expect(data.Size).To(BeZero()) 82 83 options := new(images.GetOptions).WithSize(true) 84 // Enabling the size parameter should result in size being populated 85 data, err = images.GetImage(bt.conn, alpine.name, options) 86 Expect(err).ToNot(HaveOccurred()) 87 Expect(data.Size).To(BeNumerically(">", 0)) 88 }) 89 90 // Test to validate the remove image api 91 It("remove image", func() { 92 // NOTE that removing an image that does not exist will still 93 // return a 200 http status. The response, however, includes 94 // the exit code that podman-remote should exit with. 95 // 96 // The libpod/images/remove endpoint supports batch removal of 97 // images for performance reasons and for hiding the logic of 98 // deciding which exit code to use from the client. 99 response, errs := images.Remove(bt.conn, []string{"foobar5000"}, nil) 100 Expect(errs).ToNot(BeEmpty()) 101 Expect(response.ExitCode).To(BeNumerically("==", 1)) // podman-remote would exit with 1 102 103 // Remove an image by name, validate image is removed and error is nil 104 inspectData, err := images.GetImage(bt.conn, busybox.shortName, nil) 105 Expect(err).ToNot(HaveOccurred()) 106 response, errs = images.Remove(bt.conn, []string{busybox.shortName}, nil) 107 Expect(errs).To(BeEmpty()) 108 109 Expect(inspectData.ID).To(Equal(response.Deleted[0])) 110 _, err = images.GetImage(bt.conn, busybox.shortName, nil) 111 code, _ := bindings.CheckResponseCode(err) 112 Expect(code).To(BeNumerically("==", http.StatusNotFound)) 113 114 // Start a container with alpine image 115 var top = "top" 116 _, err = bt.RunTopContainer(&top, nil) 117 Expect(err).ToNot(HaveOccurred()) 118 // we should now have a container called "top" running 119 containerResponse, err := containers.Inspect(bt.conn, "top", nil) 120 Expect(err).ToNot(HaveOccurred()) 121 Expect(containerResponse.Name).To(Equal("top")) 122 123 // try to remove the image "alpine". This should fail since we are not force 124 // deleting hence image cannot be deleted until the container is deleted. 125 _, errs = images.Remove(bt.conn, []string{alpine.shortName}, nil) 126 code, _ = bindings.CheckResponseCode(errs[0]) 127 Expect(code).To(BeNumerically("==", -1)) 128 129 // Removing the image "alpine" where force = true 130 options := new(images.RemoveOptions).WithForce(true) 131 _, errs = images.Remove(bt.conn, []string{alpine.shortName}, options) 132 Expect(errs).To(Or(HaveLen(0), BeNil())) 133 // To be extra sure, check if the previously created container 134 // is gone as well. 135 _, err = containers.Inspect(bt.conn, "top", nil) 136 code, _ = bindings.CheckResponseCode(err) 137 Expect(code).To(BeNumerically("==", http.StatusNotFound)) 138 139 // Now make sure both images are gone. 140 _, err = images.GetImage(bt.conn, busybox.shortName, nil) 141 code, _ = bindings.CheckResponseCode(err) 142 Expect(code).To(BeNumerically("==", http.StatusNotFound)) 143 144 _, err = images.GetImage(bt.conn, alpine.shortName, nil) 145 code, _ = bindings.CheckResponseCode(err) 146 Expect(code).To(BeNumerically("==", http.StatusNotFound)) 147 }) 148 149 // Tests to validate the image tag command. 150 It("tag image", func() { 151 152 // Validates if invalid image name is given a bad response is encountered. 153 err = images.Tag(bt.conn, "dummy", "demo", alpine.shortName, nil) 154 Expect(err).To(HaveOccurred()) 155 code, _ := bindings.CheckResponseCode(err) 156 Expect(code).To(BeNumerically("==", http.StatusNotFound)) 157 158 // Validates if the image is tagged successfully. 159 err = images.Tag(bt.conn, alpine.shortName, "demo", alpine.shortName, nil) 160 Expect(err).ToNot(HaveOccurred()) 161 162 // Validates if name updates when the image is retagged. 163 _, err := images.GetImage(bt.conn, "alpine:demo", nil) 164 Expect(err).ToNot(HaveOccurred()) 165 166 }) 167 168 // Test to validate the List images command. 169 It("List image", func() { 170 // Array to hold the list of images returned 171 imageSummary, err := images.List(bt.conn, nil) 172 // There Should be no errors in the response. 173 Expect(err).ToNot(HaveOccurred()) 174 // Since in the begin context two images are created the 175 // list context should have only 2 images 176 Expect(imageSummary).To(HaveLen(2)) 177 178 // Adding one more image. There Should be no errors in the response. 179 // And the count should be three now. 180 bt.Pull("testimage:20200929") 181 imageSummary, err = images.List(bt.conn, nil) 182 Expect(err).ToNot(HaveOccurred()) 183 Expect(len(imageSummary)).To(BeNumerically(">=", 2)) 184 185 // Validate the image names. 186 var names []string 187 for _, i := range imageSummary { 188 names = append(names, i.RepoTags...) 189 } 190 Expect(names).To(ContainElement(alpine.name)) 191 Expect(names).To(ContainElement(busybox.name)) 192 193 // List images with a filter 194 filters := make(map[string][]string) 195 filters["reference"] = []string{alpine.name} 196 options := new(images.ListOptions).WithFilters(filters).WithAll(false) 197 filteredImages, err := images.List(bt.conn, options) 198 Expect(err).ToNot(HaveOccurred()) 199 Expect(filteredImages).To(HaveLen(1)) 200 201 // List images with a bad filter 202 filters["name"] = []string{alpine.name} 203 options = new(images.ListOptions).WithFilters(filters) 204 _, err = images.List(bt.conn, options) 205 Expect(err).To(HaveOccurred()) 206 code, _ := bindings.CheckResponseCode(err) 207 Expect(code).To(BeNumerically("==", http.StatusInternalServerError)) 208 }) 209 210 It("Image Exists", func() { 211 // exists on bogus image should be false, with no error 212 exists, err := images.Exists(bt.conn, "foobar", nil) 213 Expect(err).ToNot(HaveOccurred()) 214 Expect(exists).To(BeFalse()) 215 216 // exists with shortname should be true 217 exists, err = images.Exists(bt.conn, alpine.shortName, nil) 218 Expect(err).ToNot(HaveOccurred()) 219 Expect(exists).To(BeTrue()) 220 221 // exists with fqname should be true 222 exists, err = images.Exists(bt.conn, alpine.name, nil) 223 Expect(err).ToNot(HaveOccurred()) 224 Expect(exists).To(BeTrue()) 225 }) 226 227 It("Load|Import Image", func() { 228 // load an image 229 _, errs := images.Remove(bt.conn, []string{alpine.name}, nil) 230 Expect(errs).To(BeEmpty()) 231 exists, err := images.Exists(bt.conn, alpine.name, nil) 232 Expect(err).ToNot(HaveOccurred()) 233 Expect(exists).To(BeFalse()) 234 f, err := os.Open(filepath.Join(ImageCacheDir, alpine.tarballName)) 235 Expect(err).ToNot(HaveOccurred()) 236 defer f.Close() 237 names, err := images.Load(bt.conn, f) 238 Expect(err).ToNot(HaveOccurred()) 239 Expect(names.Names[0]).To(Equal(alpine.name)) 240 exists, err = images.Exists(bt.conn, alpine.name, nil) 241 Expect(err).ToNot(HaveOccurred()) 242 Expect(exists).To(BeTrue()) 243 244 // load with a repo name 245 f, err = os.Open(filepath.Join(ImageCacheDir, alpine.tarballName)) 246 Expect(err).ToNot(HaveOccurred()) 247 _, errs = images.Remove(bt.conn, []string{alpine.name}, nil) 248 Expect(errs).To(BeEmpty()) 249 exists, err = images.Exists(bt.conn, alpine.name, nil) 250 Expect(err).ToNot(HaveOccurred()) 251 Expect(exists).To(BeFalse()) 252 names, err = images.Load(bt.conn, f) 253 Expect(err).ToNot(HaveOccurred()) 254 Expect(names.Names[0]).To(Equal(alpine.name)) 255 256 // load with a bad repo name should trigger a 500 257 _, errs = images.Remove(bt.conn, []string{alpine.name}, nil) 258 Expect(errs).To(BeEmpty()) 259 exists, err = images.Exists(bt.conn, alpine.name, nil) 260 Expect(err).ToNot(HaveOccurred()) 261 Expect(exists).To(BeFalse()) 262 }) 263 264 It("Export Image", func() { 265 // Export an image 266 exportPath := filepath.Join(bt.tempDirPath, alpine.tarballName) 267 w, err := os.Create(filepath.Join(bt.tempDirPath, alpine.tarballName)) 268 Expect(err).ToNot(HaveOccurred()) 269 defer w.Close() 270 err = images.Export(bt.conn, []string{alpine.name}, w, nil) 271 Expect(err).ToNot(HaveOccurred()) 272 _, err = os.Stat(exportPath) 273 Expect(err).ToNot(HaveOccurred()) 274 275 // TODO how do we verify that a format change worked? 276 }) 277 278 It("Import Image", func() { 279 // load an image 280 _, errs := images.Remove(bt.conn, []string{alpine.name}, nil) 281 Expect(errs).To(BeEmpty()) 282 exists, err := images.Exists(bt.conn, alpine.name, nil) 283 Expect(err).ToNot(HaveOccurred()) 284 Expect(exists).To(BeFalse()) 285 f, err := os.Open(filepath.Join(ImageCacheDir, alpine.tarballName)) 286 Expect(err).ToNot(HaveOccurred()) 287 defer f.Close() 288 changes := []string{"CMD /bin/foobar"} 289 testMessage := "test_import" 290 options := new(images.ImportOptions).WithMessage(testMessage).WithChanges(changes).WithReference(alpine.name) 291 _, err = images.Import(bt.conn, f, options) 292 Expect(err).ToNot(HaveOccurred()) 293 exists, err = images.Exists(bt.conn, alpine.name, nil) 294 Expect(err).ToNot(HaveOccurred()) 295 Expect(exists).To(BeTrue()) 296 data, err := images.GetImage(bt.conn, alpine.name, nil) 297 Expect(err).ToNot(HaveOccurred()) 298 Expect(data.Comment).To(Equal(testMessage)) 299 300 }) 301 302 It("History Image", func() { 303 // a bogus name should return a 404 304 _, err := images.History(bt.conn, "foobar", nil) 305 Expect(err).To(HaveOccurred()) 306 code, _ := bindings.CheckResponseCode(err) 307 Expect(code).To(BeNumerically("==", http.StatusNotFound)) 308 309 var foundID bool 310 data, err := images.GetImage(bt.conn, alpine.name, nil) 311 Expect(err).ToNot(HaveOccurred()) 312 history, err := images.History(bt.conn, alpine.name, nil) 313 Expect(err).ToNot(HaveOccurred()) 314 for _, i := range history { 315 if i.ID == data.ID { 316 foundID = true 317 break 318 } 319 } 320 Expect(foundID).To(BeTrue()) 321 }) 322 323 It("Search for an image", func() { 324 reports, err := images.Search(bt.conn, "alpine", nil) 325 Expect(err).ToNot(HaveOccurred()) 326 Expect(len(reports)).To(BeNumerically(">", 1)) 327 var foundAlpine bool 328 for _, i := range reports { 329 if i.Name == "docker.io/library/alpine" { 330 foundAlpine = true 331 break 332 } 333 } 334 Expect(foundAlpine).To(BeTrue()) 335 336 // Search for alpine with a limit of 10 337 options := new(images.SearchOptions).WithLimit(10) 338 reports, err = images.Search(bt.conn, "docker.io/alpine", options) 339 Expect(err).ToNot(HaveOccurred()) 340 Expect(len(reports)).To(BeNumerically("<=", 10)) 341 342 filters := make(map[string][]string) 343 filters["stars"] = []string{"100"} 344 // Search for alpine with stars greater than 100 345 options = new(images.SearchOptions).WithFilters(filters) 346 reports, err = images.Search(bt.conn, "docker.io/alpine", options) 347 Expect(err).ToNot(HaveOccurred()) 348 for _, i := range reports { 349 Expect(i.Stars).To(BeNumerically(">=", 100)) 350 } 351 352 // Search with a fqdn 353 reports, err = images.Search(bt.conn, "quay.io/podman/stable", nil) 354 Expect(err).ToNot(HaveOccurred(), "Error in images.Search()") 355 Expect(reports).ToNot(BeEmpty()) 356 }) 357 358 It("Prune images", func() { 359 options := new(images.PruneOptions).WithAll(true) 360 results, err := images.Prune(bt.conn, options) 361 Expect(err).NotTo(HaveOccurred()) 362 Expect(results).ToNot(BeEmpty()) 363 }) 364 365 // TODO: we really need to extent to pull tests once we have a more sophisticated CI. 366 It("Image Pull", func() { 367 rawImage := "docker.io/library/busybox:latest" 368 369 var writer bytes.Buffer 370 pullOpts := new(images.PullOptions).WithProgressWriter(&writer) 371 pulledImages, err := images.Pull(bt.conn, rawImage, pullOpts) 372 Expect(err).NotTo(HaveOccurred()) 373 Expect(pulledImages).To(HaveLen(1)) 374 output := writer.String() 375 Expect(output).To(ContainSubstring("Trying to pull ")) 376 Expect(output).To(ContainSubstring("Getting image source signatures")) 377 378 exists, err := images.Exists(bt.conn, rawImage, nil) 379 Expect(err).NotTo(HaveOccurred()) 380 Expect(exists).To(BeTrue()) 381 382 // Make sure the normalization AND the full-transport reference works. 383 _, err = images.Pull(bt.conn, "docker://"+rawImage, nil) 384 Expect(err).NotTo(HaveOccurred()) 385 386 // The v2 endpoint only supports the docker transport. Let's see if that's really true. 387 _, err = images.Pull(bt.conn, "bogus-transport:bogus.com/image:reference", nil) 388 Expect(err).To(HaveOccurred()) 389 }) 390 391 It("Image Push", func() { 392 registryOptions := &podmanRegistry.Options{ 393 PodmanPath: getPodmanBinary(), 394 } 395 registry, err := podmanRegistry.StartWithOptions(registryOptions) 396 Expect(err).ToNot(HaveOccurred()) 397 398 var writer bytes.Buffer 399 pushOpts := new(images.PushOptions).WithUsername(registry.User).WithPassword(registry.Password).WithSkipTLSVerify(true).WithProgressWriter(&writer).WithQuiet(false) 400 err = images.Push(bt.conn, alpine.name, fmt.Sprintf("localhost:%s/test:latest", registry.Port), pushOpts) 401 Expect(err).ToNot(HaveOccurred()) 402 403 output := writer.String() 404 Expect(output).To(ContainSubstring("Copying blob ")) 405 Expect(output).To(ContainSubstring("Copying config ")) 406 Expect(output).To(ContainSubstring("Writing manifest to image destination")) 407 }) 408 409 It("Build no options", func() { 410 results, err := images.Build(bt.conn, []string{"fixture/Containerfile"}, entities.BuildOptions{}) 411 Expect(err).ToNot(HaveOccurred()) 412 Expect(*results).To(MatchFields(IgnoreMissing, Fields{ 413 "ID": Not(BeEmpty()), 414 "SaveFormat": ContainSubstring(define.OCIArchive), 415 })) 416 }) 417 })