github.com/ruphin/docker@v1.10.1/integration-cli/docker_cli_pull_local_test.go (about) 1 package main 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io/ioutil" 7 "os" 8 "os/exec" 9 "path/filepath" 10 "runtime" 11 "strings" 12 13 "github.com/docker/distribution" 14 "github.com/docker/distribution/digest" 15 "github.com/docker/distribution/manifest" 16 "github.com/docker/distribution/manifest/manifestlist" 17 "github.com/docker/distribution/manifest/schema2" 18 "github.com/docker/docker/pkg/integration/checker" 19 "github.com/go-check/check" 20 ) 21 22 // testPullImageWithAliases pulls a specific image tag and verifies that any aliases (i.e., other 23 // tags for the same image) are not also pulled down. 24 // 25 // Ref: docker/docker#8141 26 func testPullImageWithAliases(c *check.C) { 27 repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL) 28 29 repos := []string{} 30 for _, tag := range []string{"recent", "fresh"} { 31 repos = append(repos, fmt.Sprintf("%v:%v", repoName, tag)) 32 } 33 34 // Tag and push the same image multiple times. 35 for _, repo := range repos { 36 dockerCmd(c, "tag", "busybox", repo) 37 dockerCmd(c, "push", repo) 38 } 39 40 // Clear local images store. 41 args := append([]string{"rmi"}, repos...) 42 dockerCmd(c, args...) 43 44 // Pull a single tag and verify it doesn't bring down all aliases. 45 dockerCmd(c, "pull", repos[0]) 46 dockerCmd(c, "inspect", repos[0]) 47 for _, repo := range repos[1:] { 48 _, _, err := dockerCmdWithError("inspect", repo) 49 c.Assert(err, checker.NotNil, check.Commentf("Image %v shouldn't have been pulled down", repo)) 50 } 51 } 52 53 func (s *DockerRegistrySuite) TestPullImageWithAliases(c *check.C) { 54 testPullImageWithAliases(c) 55 } 56 57 func (s *DockerSchema1RegistrySuite) TestPullImageWithAliases(c *check.C) { 58 testPullImageWithAliases(c) 59 } 60 61 // testConcurrentPullWholeRepo pulls the same repo concurrently. 62 func testConcurrentPullWholeRepo(c *check.C) { 63 repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL) 64 65 repos := []string{} 66 for _, tag := range []string{"recent", "fresh", "todays"} { 67 repo := fmt.Sprintf("%v:%v", repoName, tag) 68 _, err := buildImage(repo, fmt.Sprintf(` 69 FROM busybox 70 ENTRYPOINT ["/bin/echo"] 71 ENV FOO foo 72 ENV BAR bar 73 CMD echo %s 74 `, repo), true) 75 c.Assert(err, checker.IsNil) 76 dockerCmd(c, "push", repo) 77 repos = append(repos, repo) 78 } 79 80 // Clear local images store. 81 args := append([]string{"rmi"}, repos...) 82 dockerCmd(c, args...) 83 84 // Run multiple re-pulls concurrently 85 results := make(chan error) 86 numPulls := 3 87 88 for i := 0; i != numPulls; i++ { 89 go func() { 90 _, _, err := runCommandWithOutput(exec.Command(dockerBinary, "pull", "-a", repoName)) 91 results <- err 92 }() 93 } 94 95 // These checks are separate from the loop above because the check 96 // package is not goroutine-safe. 97 for i := 0; i != numPulls; i++ { 98 err := <-results 99 c.Assert(err, checker.IsNil, check.Commentf("concurrent pull failed with error: %v", err)) 100 } 101 102 // Ensure all tags were pulled successfully 103 for _, repo := range repos { 104 dockerCmd(c, "inspect", repo) 105 out, _ := dockerCmd(c, "run", "--rm", repo) 106 c.Assert(strings.TrimSpace(out), checker.Equals, "/bin/sh -c echo "+repo) 107 } 108 } 109 110 func (s *DockerRegistrySuite) testConcurrentPullWholeRepo(c *check.C) { 111 testConcurrentPullWholeRepo(c) 112 } 113 114 func (s *DockerSchema1RegistrySuite) testConcurrentPullWholeRepo(c *check.C) { 115 testConcurrentPullWholeRepo(c) 116 } 117 118 // testConcurrentFailingPull tries a concurrent pull that doesn't succeed. 119 func testConcurrentFailingPull(c *check.C) { 120 repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL) 121 122 // Run multiple pulls concurrently 123 results := make(chan error) 124 numPulls := 3 125 126 for i := 0; i != numPulls; i++ { 127 go func() { 128 _, _, err := runCommandWithOutput(exec.Command(dockerBinary, "pull", repoName+":asdfasdf")) 129 results <- err 130 }() 131 } 132 133 // These checks are separate from the loop above because the check 134 // package is not goroutine-safe. 135 for i := 0; i != numPulls; i++ { 136 err := <-results 137 c.Assert(err, checker.NotNil, check.Commentf("expected pull to fail")) 138 } 139 } 140 141 func (s *DockerRegistrySuite) testConcurrentFailingPull(c *check.C) { 142 testConcurrentFailingPull(c) 143 } 144 145 func (s *DockerSchema1RegistrySuite) testConcurrentFailingPull(c *check.C) { 146 testConcurrentFailingPull(c) 147 } 148 149 // testConcurrentPullMultipleTags pulls multiple tags from the same repo 150 // concurrently. 151 func testConcurrentPullMultipleTags(c *check.C) { 152 repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL) 153 154 repos := []string{} 155 for _, tag := range []string{"recent", "fresh", "todays"} { 156 repo := fmt.Sprintf("%v:%v", repoName, tag) 157 _, err := buildImage(repo, fmt.Sprintf(` 158 FROM busybox 159 ENTRYPOINT ["/bin/echo"] 160 ENV FOO foo 161 ENV BAR bar 162 CMD echo %s 163 `, repo), true) 164 c.Assert(err, checker.IsNil) 165 dockerCmd(c, "push", repo) 166 repos = append(repos, repo) 167 } 168 169 // Clear local images store. 170 args := append([]string{"rmi"}, repos...) 171 dockerCmd(c, args...) 172 173 // Re-pull individual tags, in parallel 174 results := make(chan error) 175 176 for _, repo := range repos { 177 go func(repo string) { 178 _, _, err := runCommandWithOutput(exec.Command(dockerBinary, "pull", repo)) 179 results <- err 180 }(repo) 181 } 182 183 // These checks are separate from the loop above because the check 184 // package is not goroutine-safe. 185 for range repos { 186 err := <-results 187 c.Assert(err, checker.IsNil, check.Commentf("concurrent pull failed with error: %v", err)) 188 } 189 190 // Ensure all tags were pulled successfully 191 for _, repo := range repos { 192 dockerCmd(c, "inspect", repo) 193 out, _ := dockerCmd(c, "run", "--rm", repo) 194 c.Assert(strings.TrimSpace(out), checker.Equals, "/bin/sh -c echo "+repo) 195 } 196 } 197 198 func (s *DockerRegistrySuite) TestConcurrentPullMultipleTags(c *check.C) { 199 testConcurrentPullMultipleTags(c) 200 } 201 202 func (s *DockerSchema1RegistrySuite) TestConcurrentPullMultipleTags(c *check.C) { 203 testConcurrentPullMultipleTags(c) 204 } 205 206 // testPullIDStability verifies that pushing an image and pulling it back 207 // preserves the image ID. 208 func testPullIDStability(c *check.C) { 209 derivedImage := privateRegistryURL + "/dockercli/id-stability" 210 baseImage := "busybox" 211 212 _, err := buildImage(derivedImage, fmt.Sprintf(` 213 FROM %s 214 ENV derived true 215 ENV asdf true 216 RUN dd if=/dev/zero of=/file bs=1024 count=1024 217 CMD echo %s 218 `, baseImage, derivedImage), true) 219 if err != nil { 220 c.Fatal(err) 221 } 222 223 originalID, err := getIDByName(derivedImage) 224 if err != nil { 225 c.Fatalf("error inspecting: %v", err) 226 } 227 dockerCmd(c, "push", derivedImage) 228 229 // Pull 230 out, _ := dockerCmd(c, "pull", derivedImage) 231 if strings.Contains(out, "Pull complete") { 232 c.Fatalf("repull redownloaded a layer: %s", out) 233 } 234 235 derivedIDAfterPull, err := getIDByName(derivedImage) 236 if err != nil { 237 c.Fatalf("error inspecting: %v", err) 238 } 239 240 if derivedIDAfterPull != originalID { 241 c.Fatal("image's ID unexpectedly changed after a repush/repull") 242 } 243 244 // Make sure the image runs correctly 245 out, _ = dockerCmd(c, "run", "--rm", derivedImage) 246 if strings.TrimSpace(out) != derivedImage { 247 c.Fatalf("expected %s; got %s", derivedImage, out) 248 } 249 250 // Confirm that repushing and repulling does not change the computed ID 251 dockerCmd(c, "push", derivedImage) 252 dockerCmd(c, "rmi", derivedImage) 253 dockerCmd(c, "pull", derivedImage) 254 255 derivedIDAfterPull, err = getIDByName(derivedImage) 256 if err != nil { 257 c.Fatalf("error inspecting: %v", err) 258 } 259 260 if derivedIDAfterPull != originalID { 261 c.Fatal("image's ID unexpectedly changed after a repush/repull") 262 } 263 if err != nil { 264 c.Fatalf("error inspecting: %v", err) 265 } 266 267 // Make sure the image still runs 268 out, _ = dockerCmd(c, "run", "--rm", derivedImage) 269 if strings.TrimSpace(out) != derivedImage { 270 c.Fatalf("expected %s; got %s", derivedImage, out) 271 } 272 } 273 274 func (s *DockerRegistrySuite) TestPullIDStability(c *check.C) { 275 testPullIDStability(c) 276 } 277 278 func (s *DockerSchema1RegistrySuite) TestPullIDStability(c *check.C) { 279 testPullIDStability(c) 280 } 281 282 func (s *DockerRegistrySuite) TestPullManifestList(c *check.C) { 283 pushDigest, err := setupImage(c) 284 c.Assert(err, checker.IsNil, check.Commentf("error setting up image")) 285 286 // Inject a manifest list into the registry 287 manifestList := &manifestlist.ManifestList{ 288 Versioned: manifest.Versioned{ 289 SchemaVersion: 2, 290 MediaType: manifestlist.MediaTypeManifestList, 291 }, 292 Manifests: []manifestlist.ManifestDescriptor{ 293 { 294 Descriptor: distribution.Descriptor{ 295 Digest: "sha256:1a9ec845ee94c202b2d5da74a24f0ed2058318bfa9879fa541efaecba272e86b", 296 Size: 3253, 297 MediaType: schema2.MediaTypeManifest, 298 }, 299 Platform: manifestlist.PlatformSpec{ 300 Architecture: "bogus_arch", 301 OS: "bogus_os", 302 }, 303 }, 304 { 305 Descriptor: distribution.Descriptor{ 306 Digest: pushDigest, 307 Size: 3253, 308 MediaType: schema2.MediaTypeManifest, 309 }, 310 Platform: manifestlist.PlatformSpec{ 311 Architecture: runtime.GOARCH, 312 OS: runtime.GOOS, 313 }, 314 }, 315 }, 316 } 317 318 manifestListJSON, err := json.MarshalIndent(manifestList, "", " ") 319 c.Assert(err, checker.IsNil, check.Commentf("error marshalling manifest list")) 320 321 manifestListDigest := digest.FromBytes(manifestListJSON) 322 hexDigest := manifestListDigest.Hex() 323 324 registryV2Path := filepath.Join(s.reg.dir, "docker", "registry", "v2") 325 326 // Write manifest list to blob store 327 blobDir := filepath.Join(registryV2Path, "blobs", "sha256", hexDigest[:2], hexDigest) 328 err = os.MkdirAll(blobDir, 0755) 329 c.Assert(err, checker.IsNil, check.Commentf("error creating blob dir")) 330 blobPath := filepath.Join(blobDir, "data") 331 err = ioutil.WriteFile(blobPath, []byte(manifestListJSON), 0644) 332 c.Assert(err, checker.IsNil, check.Commentf("error writing manifest list")) 333 334 // Add to revision store 335 revisionDir := filepath.Join(registryV2Path, "repositories", remoteRepoName, "_manifests", "revisions", "sha256", hexDigest) 336 err = os.Mkdir(revisionDir, 0755) 337 c.Assert(err, checker.IsNil, check.Commentf("error creating revision dir")) 338 revisionPath := filepath.Join(revisionDir, "link") 339 err = ioutil.WriteFile(revisionPath, []byte(manifestListDigest.String()), 0644) 340 c.Assert(err, checker.IsNil, check.Commentf("error writing revision link")) 341 342 // Update tag 343 tagPath := filepath.Join(registryV2Path, "repositories", remoteRepoName, "_manifests", "tags", "latest", "current", "link") 344 err = ioutil.WriteFile(tagPath, []byte(manifestListDigest.String()), 0644) 345 c.Assert(err, checker.IsNil, check.Commentf("error writing tag link")) 346 347 // Verify that the image can be pulled through the manifest list. 348 out, _ := dockerCmd(c, "pull", repoName) 349 350 // The pull output includes "Digest: <digest>", so find that 351 matches := digestRegex.FindStringSubmatch(out) 352 c.Assert(matches, checker.HasLen, 2, check.Commentf("unable to parse digest from pull output: %s", out)) 353 pullDigest := matches[1] 354 355 // Make sure the pushed and pull digests match 356 c.Assert(manifestListDigest.String(), checker.Equals, pullDigest) 357 358 // Was the image actually created? 359 dockerCmd(c, "inspect", repoName) 360 361 dockerCmd(c, "rmi", repoName) 362 }