This is part of the ongoing effort to remove the deprecated server/
package, and generally cleanup and simplify the codebase.
Signed-off-by: Solomon Hykes <solomon@docker.com>
| 1 | 1 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,41 @@ |
| 0 |
+package graph |
|
| 1 |
+ |
|
| 2 |
+import "testing" |
|
| 3 |
+ |
|
| 4 |
+func TestPools(t *testing.T) {
|
|
| 5 |
+ s := &TagStore{
|
|
| 6 |
+ pullingPool: make(map[string]chan struct{}),
|
|
| 7 |
+ pushingPool: make(map[string]chan struct{}),
|
|
| 8 |
+ } |
|
| 9 |
+ |
|
| 10 |
+ if _, err := s.poolAdd("pull", "test1"); err != nil {
|
|
| 11 |
+ t.Fatal(err) |
|
| 12 |
+ } |
|
| 13 |
+ if _, err := s.poolAdd("pull", "test2"); err != nil {
|
|
| 14 |
+ t.Fatal(err) |
|
| 15 |
+ } |
|
| 16 |
+ if _, err := s.poolAdd("push", "test1"); err == nil || err.Error() != "pull test1 is already in progress" {
|
|
| 17 |
+ t.Fatalf("Expected `pull test1 is already in progress`")
|
|
| 18 |
+ } |
|
| 19 |
+ if _, err := s.poolAdd("pull", "test1"); err == nil || err.Error() != "pull test1 is already in progress" {
|
|
| 20 |
+ t.Fatalf("Expected `pull test1 is already in progress`")
|
|
| 21 |
+ } |
|
| 22 |
+ if _, err := s.poolAdd("wait", "test3"); err == nil || err.Error() != "Unknown pool type" {
|
|
| 23 |
+ t.Fatalf("Expected `Unknown pool type`")
|
|
| 24 |
+ } |
|
| 25 |
+ if err := s.poolRemove("pull", "test2"); err != nil {
|
|
| 26 |
+ t.Fatal(err) |
|
| 27 |
+ } |
|
| 28 |
+ if err := s.poolRemove("pull", "test2"); err != nil {
|
|
| 29 |
+ t.Fatal(err) |
|
| 30 |
+ } |
|
| 31 |
+ if err := s.poolRemove("pull", "test1"); err != nil {
|
|
| 32 |
+ t.Fatal(err) |
|
| 33 |
+ } |
|
| 34 |
+ if err := s.poolRemove("push", "test1"); err != nil {
|
|
| 35 |
+ t.Fatal(err) |
|
| 36 |
+ } |
|
| 37 |
+ if err := s.poolRemove("wait", "test3"); err == nil || err.Error() != "Unknown pool type" {
|
|
| 38 |
+ t.Fatalf("Expected `Unknown pool type`")
|
|
| 39 |
+ } |
|
| 40 |
+} |
| 0 | 41 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,300 @@ |
| 0 |
+package graph |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ "io" |
|
| 5 |
+ "net" |
|
| 6 |
+ "net/url" |
|
| 7 |
+ "strings" |
|
| 8 |
+ "time" |
|
| 9 |
+ |
|
| 10 |
+ "github.com/docker/docker/engine" |
|
| 11 |
+ "github.com/docker/docker/image" |
|
| 12 |
+ "github.com/docker/docker/registry" |
|
| 13 |
+ "github.com/docker/docker/utils" |
|
| 14 |
+) |
|
| 15 |
+ |
|
| 16 |
+func (s *TagStore) CmdPull(job *engine.Job) engine.Status {
|
|
| 17 |
+ if n := len(job.Args); n != 1 && n != 2 {
|
|
| 18 |
+ return job.Errorf("Usage: %s IMAGE [TAG]", job.Name)
|
|
| 19 |
+ } |
|
| 20 |
+ var ( |
|
| 21 |
+ localName = job.Args[0] |
|
| 22 |
+ tag string |
|
| 23 |
+ sf = utils.NewStreamFormatter(job.GetenvBool("json"))
|
|
| 24 |
+ authConfig = ®istry.AuthConfig{}
|
|
| 25 |
+ metaHeaders map[string][]string |
|
| 26 |
+ ) |
|
| 27 |
+ if len(job.Args) > 1 {
|
|
| 28 |
+ tag = job.Args[1] |
|
| 29 |
+ } |
|
| 30 |
+ |
|
| 31 |
+ job.GetenvJson("authConfig", authConfig)
|
|
| 32 |
+ job.GetenvJson("metaHeaders", &metaHeaders)
|
|
| 33 |
+ |
|
| 34 |
+ c, err := s.poolAdd("pull", localName+":"+tag)
|
|
| 35 |
+ if err != nil {
|
|
| 36 |
+ if c != nil {
|
|
| 37 |
+ // Another pull of the same repository is already taking place; just wait for it to finish |
|
| 38 |
+ job.Stdout.Write(sf.FormatStatus("", "Repository %s already being pulled by another client. Waiting.", localName))
|
|
| 39 |
+ <-c |
|
| 40 |
+ return engine.StatusOK |
|
| 41 |
+ } |
|
| 42 |
+ return job.Error(err) |
|
| 43 |
+ } |
|
| 44 |
+ defer s.poolRemove("pull", localName+":"+tag)
|
|
| 45 |
+ |
|
| 46 |
+ // Resolve the Repository name from fqn to endpoint + name |
|
| 47 |
+ hostname, remoteName, err := registry.ResolveRepositoryName(localName) |
|
| 48 |
+ if err != nil {
|
|
| 49 |
+ return job.Error(err) |
|
| 50 |
+ } |
|
| 51 |
+ |
|
| 52 |
+ endpoint, err := registry.ExpandAndVerifyRegistryUrl(hostname) |
|
| 53 |
+ if err != nil {
|
|
| 54 |
+ return job.Error(err) |
|
| 55 |
+ } |
|
| 56 |
+ |
|
| 57 |
+ r, err := registry.NewRegistry(authConfig, registry.HTTPRequestFactory(metaHeaders), endpoint, true) |
|
| 58 |
+ if err != nil {
|
|
| 59 |
+ return job.Error(err) |
|
| 60 |
+ } |
|
| 61 |
+ |
|
| 62 |
+ if endpoint == registry.IndexServerAddress() {
|
|
| 63 |
+ // If pull "index.docker.io/foo/bar", it's stored locally under "foo/bar" |
|
| 64 |
+ localName = remoteName |
|
| 65 |
+ } |
|
| 66 |
+ |
|
| 67 |
+ if err = s.pullRepository(r, job.Stdout, localName, remoteName, tag, sf, job.GetenvBool("parallel")); err != nil {
|
|
| 68 |
+ return job.Error(err) |
|
| 69 |
+ } |
|
| 70 |
+ |
|
| 71 |
+ return engine.StatusOK |
|
| 72 |
+} |
|
| 73 |
+ |
|
| 74 |
+func (s *TagStore) pullRepository(r *registry.Registry, out io.Writer, localName, remoteName, askedTag string, sf *utils.StreamFormatter, parallel bool) error {
|
|
| 75 |
+ out.Write(sf.FormatStatus("", "Pulling repository %s", localName))
|
|
| 76 |
+ |
|
| 77 |
+ repoData, err := r.GetRepositoryData(remoteName) |
|
| 78 |
+ if err != nil {
|
|
| 79 |
+ if strings.Contains(err.Error(), "HTTP code: 404") {
|
|
| 80 |
+ return fmt.Errorf("Error: image %s not found", remoteName)
|
|
| 81 |
+ } else {
|
|
| 82 |
+ // Unexpected HTTP error |
|
| 83 |
+ return err |
|
| 84 |
+ } |
|
| 85 |
+ } |
|
| 86 |
+ |
|
| 87 |
+ utils.Debugf("Retrieving the tag list")
|
|
| 88 |
+ tagsList, err := r.GetRemoteTags(repoData.Endpoints, remoteName, repoData.Tokens) |
|
| 89 |
+ if err != nil {
|
|
| 90 |
+ utils.Errorf("%v", err)
|
|
| 91 |
+ return err |
|
| 92 |
+ } |
|
| 93 |
+ |
|
| 94 |
+ for tag, id := range tagsList {
|
|
| 95 |
+ repoData.ImgList[id] = ®istry.ImgData{
|
|
| 96 |
+ ID: id, |
|
| 97 |
+ Tag: tag, |
|
| 98 |
+ Checksum: "", |
|
| 99 |
+ } |
|
| 100 |
+ } |
|
| 101 |
+ |
|
| 102 |
+ utils.Debugf("Registering tags")
|
|
| 103 |
+ // If no tag has been specified, pull them all |
|
| 104 |
+ if askedTag == "" {
|
|
| 105 |
+ for tag, id := range tagsList {
|
|
| 106 |
+ repoData.ImgList[id].Tag = tag |
|
| 107 |
+ } |
|
| 108 |
+ } else {
|
|
| 109 |
+ // Otherwise, check that the tag exists and use only that one |
|
| 110 |
+ id, exists := tagsList[askedTag] |
|
| 111 |
+ if !exists {
|
|
| 112 |
+ return fmt.Errorf("Tag %s not found in repository %s", askedTag, localName)
|
|
| 113 |
+ } |
|
| 114 |
+ repoData.ImgList[id].Tag = askedTag |
|
| 115 |
+ } |
|
| 116 |
+ |
|
| 117 |
+ errors := make(chan error) |
|
| 118 |
+ for _, image := range repoData.ImgList {
|
|
| 119 |
+ downloadImage := func(img *registry.ImgData) {
|
|
| 120 |
+ if askedTag != "" && img.Tag != askedTag {
|
|
| 121 |
+ utils.Debugf("(%s) does not match %s (id: %s), skipping", img.Tag, askedTag, img.ID)
|
|
| 122 |
+ if parallel {
|
|
| 123 |
+ errors <- nil |
|
| 124 |
+ } |
|
| 125 |
+ return |
|
| 126 |
+ } |
|
| 127 |
+ |
|
| 128 |
+ if img.Tag == "" {
|
|
| 129 |
+ utils.Debugf("Image (id: %s) present in this repository but untagged, skipping", img.ID)
|
|
| 130 |
+ if parallel {
|
|
| 131 |
+ errors <- nil |
|
| 132 |
+ } |
|
| 133 |
+ return |
|
| 134 |
+ } |
|
| 135 |
+ |
|
| 136 |
+ // ensure no two downloads of the same image happen at the same time |
|
| 137 |
+ if c, err := s.poolAdd("pull", "img:"+img.ID); err != nil {
|
|
| 138 |
+ if c != nil {
|
|
| 139 |
+ out.Write(sf.FormatProgress(utils.TruncateID(img.ID), "Layer already being pulled by another client. Waiting.", nil)) |
|
| 140 |
+ <-c |
|
| 141 |
+ out.Write(sf.FormatProgress(utils.TruncateID(img.ID), "Download complete", nil)) |
|
| 142 |
+ } else {
|
|
| 143 |
+ utils.Debugf("Image (id: %s) pull is already running, skipping: %v", img.ID, err)
|
|
| 144 |
+ } |
|
| 145 |
+ if parallel {
|
|
| 146 |
+ errors <- nil |
|
| 147 |
+ } |
|
| 148 |
+ return |
|
| 149 |
+ } |
|
| 150 |
+ defer s.poolRemove("pull", "img:"+img.ID)
|
|
| 151 |
+ |
|
| 152 |
+ out.Write(sf.FormatProgress(utils.TruncateID(img.ID), fmt.Sprintf("Pulling image (%s) from %s", img.Tag, localName), nil))
|
|
| 153 |
+ success := false |
|
| 154 |
+ var lastErr error |
|
| 155 |
+ for _, ep := range repoData.Endpoints {
|
|
| 156 |
+ out.Write(sf.FormatProgress(utils.TruncateID(img.ID), fmt.Sprintf("Pulling image (%s) from %s, endpoint: %s", img.Tag, localName, ep), nil))
|
|
| 157 |
+ if err := s.pullImage(r, out, img.ID, ep, repoData.Tokens, sf); err != nil {
|
|
| 158 |
+ // It's not ideal that only the last error is returned, it would be better to concatenate the errors. |
|
| 159 |
+ // As the error is also given to the output stream the user will see the error. |
|
| 160 |
+ lastErr = err |
|
| 161 |
+ out.Write(sf.FormatProgress(utils.TruncateID(img.ID), fmt.Sprintf("Error pulling image (%s) from %s, endpoint: %s, %s", img.Tag, localName, ep, err), nil))
|
|
| 162 |
+ continue |
|
| 163 |
+ } |
|
| 164 |
+ success = true |
|
| 165 |
+ break |
|
| 166 |
+ } |
|
| 167 |
+ if !success {
|
|
| 168 |
+ err := fmt.Errorf("Error pulling image (%s) from %s, %v", img.Tag, localName, lastErr)
|
|
| 169 |
+ out.Write(sf.FormatProgress(utils.TruncateID(img.ID), err.Error(), nil)) |
|
| 170 |
+ if parallel {
|
|
| 171 |
+ errors <- err |
|
| 172 |
+ return |
|
| 173 |
+ } |
|
| 174 |
+ } |
|
| 175 |
+ out.Write(sf.FormatProgress(utils.TruncateID(img.ID), "Download complete", nil)) |
|
| 176 |
+ |
|
| 177 |
+ if parallel {
|
|
| 178 |
+ errors <- nil |
|
| 179 |
+ } |
|
| 180 |
+ } |
|
| 181 |
+ |
|
| 182 |
+ if parallel {
|
|
| 183 |
+ go downloadImage(image) |
|
| 184 |
+ } else {
|
|
| 185 |
+ downloadImage(image) |
|
| 186 |
+ } |
|
| 187 |
+ } |
|
| 188 |
+ if parallel {
|
|
| 189 |
+ var lastError error |
|
| 190 |
+ for i := 0; i < len(repoData.ImgList); i++ {
|
|
| 191 |
+ if err := <-errors; err != nil {
|
|
| 192 |
+ lastError = err |
|
| 193 |
+ } |
|
| 194 |
+ } |
|
| 195 |
+ if lastError != nil {
|
|
| 196 |
+ return lastError |
|
| 197 |
+ } |
|
| 198 |
+ |
|
| 199 |
+ } |
|
| 200 |
+ for tag, id := range tagsList {
|
|
| 201 |
+ if askedTag != "" && tag != askedTag {
|
|
| 202 |
+ continue |
|
| 203 |
+ } |
|
| 204 |
+ if err := s.Set(localName, tag, id, true); err != nil {
|
|
| 205 |
+ return err |
|
| 206 |
+ } |
|
| 207 |
+ } |
|
| 208 |
+ |
|
| 209 |
+ return nil |
|
| 210 |
+} |
|
| 211 |
+ |
|
| 212 |
+func (s *TagStore) pullImage(r *registry.Registry, out io.Writer, imgID, endpoint string, token []string, sf *utils.StreamFormatter) error {
|
|
| 213 |
+ history, err := r.GetRemoteHistory(imgID, endpoint, token) |
|
| 214 |
+ if err != nil {
|
|
| 215 |
+ return err |
|
| 216 |
+ } |
|
| 217 |
+ out.Write(sf.FormatProgress(utils.TruncateID(imgID), "Pulling dependent layers", nil)) |
|
| 218 |
+ // FIXME: Try to stream the images? |
|
| 219 |
+ // FIXME: Launch the getRemoteImage() in goroutines |
|
| 220 |
+ |
|
| 221 |
+ for i := len(history) - 1; i >= 0; i-- {
|
|
| 222 |
+ id := history[i] |
|
| 223 |
+ |
|
| 224 |
+ // ensure no two downloads of the same layer happen at the same time |
|
| 225 |
+ if c, err := s.poolAdd("pull", "layer:"+id); err != nil {
|
|
| 226 |
+ utils.Debugf("Image (id: %s) pull is already running, skipping: %v", id, err)
|
|
| 227 |
+ <-c |
|
| 228 |
+ } |
|
| 229 |
+ defer s.poolRemove("pull", "layer:"+id)
|
|
| 230 |
+ |
|
| 231 |
+ if !s.graph.Exists(id) {
|
|
| 232 |
+ out.Write(sf.FormatProgress(utils.TruncateID(id), "Pulling metadata", nil)) |
|
| 233 |
+ var ( |
|
| 234 |
+ imgJSON []byte |
|
| 235 |
+ imgSize int |
|
| 236 |
+ err error |
|
| 237 |
+ img *image.Image |
|
| 238 |
+ ) |
|
| 239 |
+ retries := 5 |
|
| 240 |
+ for j := 1; j <= retries; j++ {
|
|
| 241 |
+ imgJSON, imgSize, err = r.GetRemoteImageJSON(id, endpoint, token) |
|
| 242 |
+ if err != nil && j == retries {
|
|
| 243 |
+ out.Write(sf.FormatProgress(utils.TruncateID(id), "Error pulling dependent layers", nil)) |
|
| 244 |
+ return err |
|
| 245 |
+ } else if err != nil {
|
|
| 246 |
+ time.Sleep(time.Duration(j) * 500 * time.Millisecond) |
|
| 247 |
+ continue |
|
| 248 |
+ } |
|
| 249 |
+ img, err = image.NewImgJSON(imgJSON) |
|
| 250 |
+ if err != nil && j == retries {
|
|
| 251 |
+ out.Write(sf.FormatProgress(utils.TruncateID(id), "Error pulling dependent layers", nil)) |
|
| 252 |
+ return fmt.Errorf("Failed to parse json: %s", err)
|
|
| 253 |
+ } else if err != nil {
|
|
| 254 |
+ time.Sleep(time.Duration(j) * 500 * time.Millisecond) |
|
| 255 |
+ continue |
|
| 256 |
+ } else {
|
|
| 257 |
+ break |
|
| 258 |
+ } |
|
| 259 |
+ } |
|
| 260 |
+ |
|
| 261 |
+ for j := 1; j <= retries; j++ {
|
|
| 262 |
+ // Get the layer |
|
| 263 |
+ status := "Pulling fs layer" |
|
| 264 |
+ if j > 1 {
|
|
| 265 |
+ status = fmt.Sprintf("Pulling fs layer [retries: %d]", j)
|
|
| 266 |
+ } |
|
| 267 |
+ out.Write(sf.FormatProgress(utils.TruncateID(id), status, nil)) |
|
| 268 |
+ layer, err := r.GetRemoteImageLayer(img.ID, endpoint, token, int64(imgSize)) |
|
| 269 |
+ if uerr, ok := err.(*url.Error); ok {
|
|
| 270 |
+ err = uerr.Err |
|
| 271 |
+ } |
|
| 272 |
+ if terr, ok := err.(net.Error); ok && terr.Timeout() && j < retries {
|
|
| 273 |
+ time.Sleep(time.Duration(j) * 500 * time.Millisecond) |
|
| 274 |
+ continue |
|
| 275 |
+ } else if err != nil {
|
|
| 276 |
+ out.Write(sf.FormatProgress(utils.TruncateID(id), "Error pulling dependent layers", nil)) |
|
| 277 |
+ return err |
|
| 278 |
+ } |
|
| 279 |
+ defer layer.Close() |
|
| 280 |
+ |
|
| 281 |
+ err = s.graph.Register(imgJSON, |
|
| 282 |
+ utils.ProgressReader(layer, imgSize, out, sf, false, utils.TruncateID(id), "Downloading"), |
|
| 283 |
+ img) |
|
| 284 |
+ if terr, ok := err.(net.Error); ok && terr.Timeout() && j < retries {
|
|
| 285 |
+ time.Sleep(time.Duration(j) * 500 * time.Millisecond) |
|
| 286 |
+ continue |
|
| 287 |
+ } else if err != nil {
|
|
| 288 |
+ out.Write(sf.FormatProgress(utils.TruncateID(id), "Error downloading dependent layers", nil)) |
|
| 289 |
+ return err |
|
| 290 |
+ } else {
|
|
| 291 |
+ break |
|
| 292 |
+ } |
|
| 293 |
+ } |
|
| 294 |
+ } |
|
| 295 |
+ out.Write(sf.FormatProgress(utils.TruncateID(id), "Download complete", nil)) |
|
| 296 |
+ |
|
| 297 |
+ } |
|
| 298 |
+ return nil |
|
| 299 |
+} |
| 0 | 300 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,249 @@ |
| 0 |
+package graph |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ "io" |
|
| 5 |
+ "io/ioutil" |
|
| 6 |
+ "os" |
|
| 7 |
+ "path" |
|
| 8 |
+ |
|
| 9 |
+ "github.com/docker/docker/archive" |
|
| 10 |
+ "github.com/docker/docker/engine" |
|
| 11 |
+ "github.com/docker/docker/registry" |
|
| 12 |
+ "github.com/docker/docker/utils" |
|
| 13 |
+) |
|
| 14 |
+ |
|
| 15 |
+// Retrieve the all the images to be uploaded in the correct order |
|
| 16 |
+func (s *TagStore) getImageList(localRepo map[string]string, requestedTag string) ([]string, map[string][]string, error) {
|
|
| 17 |
+ var ( |
|
| 18 |
+ imageList []string |
|
| 19 |
+ imagesSeen map[string]bool = make(map[string]bool) |
|
| 20 |
+ tagsByImage map[string][]string = make(map[string][]string) |
|
| 21 |
+ ) |
|
| 22 |
+ |
|
| 23 |
+ for tag, id := range localRepo {
|
|
| 24 |
+ if requestedTag != "" && requestedTag != tag {
|
|
| 25 |
+ continue |
|
| 26 |
+ } |
|
| 27 |
+ var imageListForThisTag []string |
|
| 28 |
+ |
|
| 29 |
+ tagsByImage[id] = append(tagsByImage[id], tag) |
|
| 30 |
+ |
|
| 31 |
+ for img, err := s.graph.Get(id); img != nil; img, err = img.GetParent() {
|
|
| 32 |
+ if err != nil {
|
|
| 33 |
+ return nil, nil, err |
|
| 34 |
+ } |
|
| 35 |
+ |
|
| 36 |
+ if imagesSeen[img.ID] {
|
|
| 37 |
+ // This image is already on the list, we can ignore it and all its parents |
|
| 38 |
+ break |
|
| 39 |
+ } |
|
| 40 |
+ |
|
| 41 |
+ imagesSeen[img.ID] = true |
|
| 42 |
+ imageListForThisTag = append(imageListForThisTag, img.ID) |
|
| 43 |
+ } |
|
| 44 |
+ |
|
| 45 |
+ // reverse the image list for this tag (so the "most"-parent image is first) |
|
| 46 |
+ for i, j := 0, len(imageListForThisTag)-1; i < j; i, j = i+1, j-1 {
|
|
| 47 |
+ imageListForThisTag[i], imageListForThisTag[j] = imageListForThisTag[j], imageListForThisTag[i] |
|
| 48 |
+ } |
|
| 49 |
+ |
|
| 50 |
+ // append to main image list |
|
| 51 |
+ imageList = append(imageList, imageListForThisTag...) |
|
| 52 |
+ } |
|
| 53 |
+ if len(imageList) == 0 {
|
|
| 54 |
+ return nil, nil, fmt.Errorf("No images found for the requested repository / tag")
|
|
| 55 |
+ } |
|
| 56 |
+ utils.Debugf("Image list: %v", imageList)
|
|
| 57 |
+ utils.Debugf("Tags by image: %v", tagsByImage)
|
|
| 58 |
+ |
|
| 59 |
+ return imageList, tagsByImage, nil |
|
| 60 |
+} |
|
| 61 |
+ |
|
| 62 |
+func (s *TagStore) pushRepository(r *registry.Registry, out io.Writer, localName, remoteName string, localRepo map[string]string, tag string, sf *utils.StreamFormatter) error {
|
|
| 63 |
+ out = utils.NewWriteFlusher(out) |
|
| 64 |
+ utils.Debugf("Local repo: %s", localRepo)
|
|
| 65 |
+ imgList, tagsByImage, err := s.getImageList(localRepo, tag) |
|
| 66 |
+ if err != nil {
|
|
| 67 |
+ return err |
|
| 68 |
+ } |
|
| 69 |
+ |
|
| 70 |
+ out.Write(sf.FormatStatus("", "Sending image list"))
|
|
| 71 |
+ |
|
| 72 |
+ var ( |
|
| 73 |
+ repoData *registry.RepositoryData |
|
| 74 |
+ imageIndex []*registry.ImgData |
|
| 75 |
+ ) |
|
| 76 |
+ |
|
| 77 |
+ for _, imgId := range imgList {
|
|
| 78 |
+ if tags, exists := tagsByImage[imgId]; exists {
|
|
| 79 |
+ // If an image has tags you must add an entry in the image index |
|
| 80 |
+ // for each tag |
|
| 81 |
+ for _, tag := range tags {
|
|
| 82 |
+ imageIndex = append(imageIndex, ®istry.ImgData{
|
|
| 83 |
+ ID: imgId, |
|
| 84 |
+ Tag: tag, |
|
| 85 |
+ }) |
|
| 86 |
+ } |
|
| 87 |
+ } else {
|
|
| 88 |
+ // If the image does not have a tag it still needs to be sent to the |
|
| 89 |
+ // registry with an empty tag so that it is accociated with the repository |
|
| 90 |
+ imageIndex = append(imageIndex, ®istry.ImgData{
|
|
| 91 |
+ ID: imgId, |
|
| 92 |
+ Tag: "", |
|
| 93 |
+ }) |
|
| 94 |
+ |
|
| 95 |
+ } |
|
| 96 |
+ } |
|
| 97 |
+ |
|
| 98 |
+ utils.Debugf("Preparing to push %s with the following images and tags\n", localRepo)
|
|
| 99 |
+ for _, data := range imageIndex {
|
|
| 100 |
+ utils.Debugf("Pushing ID: %s with Tag: %s\n", data.ID, data.Tag)
|
|
| 101 |
+ } |
|
| 102 |
+ |
|
| 103 |
+ // Register all the images in a repository with the registry |
|
| 104 |
+ // If an image is not in this list it will not be associated with the repository |
|
| 105 |
+ repoData, err = r.PushImageJSONIndex(remoteName, imageIndex, false, nil) |
|
| 106 |
+ if err != nil {
|
|
| 107 |
+ return err |
|
| 108 |
+ } |
|
| 109 |
+ |
|
| 110 |
+ nTag := 1 |
|
| 111 |
+ if tag == "" {
|
|
| 112 |
+ nTag = len(localRepo) |
|
| 113 |
+ } |
|
| 114 |
+ for _, ep := range repoData.Endpoints {
|
|
| 115 |
+ out.Write(sf.FormatStatus("", "Pushing repository %s (%d tags)", localName, nTag))
|
|
| 116 |
+ |
|
| 117 |
+ for _, imgId := range imgList {
|
|
| 118 |
+ if r.LookupRemoteImage(imgId, ep, repoData.Tokens) {
|
|
| 119 |
+ out.Write(sf.FormatStatus("", "Image %s already pushed, skipping", utils.TruncateID(imgId)))
|
|
| 120 |
+ } else {
|
|
| 121 |
+ if _, err := s.pushImage(r, out, remoteName, imgId, ep, repoData.Tokens, sf); err != nil {
|
|
| 122 |
+ // FIXME: Continue on error? |
|
| 123 |
+ return err |
|
| 124 |
+ } |
|
| 125 |
+ } |
|
| 126 |
+ |
|
| 127 |
+ for _, tag := range tagsByImage[imgId] {
|
|
| 128 |
+ out.Write(sf.FormatStatus("", "Pushing tag for rev [%s] on {%s}", utils.TruncateID(imgId), ep+"repositories/"+remoteName+"/tags/"+tag))
|
|
| 129 |
+ |
|
| 130 |
+ if err := r.PushRegistryTag(remoteName, imgId, tag, ep, repoData.Tokens); err != nil {
|
|
| 131 |
+ return err |
|
| 132 |
+ } |
|
| 133 |
+ } |
|
| 134 |
+ } |
|
| 135 |
+ } |
|
| 136 |
+ |
|
| 137 |
+ if _, err := r.PushImageJSONIndex(remoteName, imageIndex, true, repoData.Endpoints); err != nil {
|
|
| 138 |
+ return err |
|
| 139 |
+ } |
|
| 140 |
+ |
|
| 141 |
+ return nil |
|
| 142 |
+} |
|
| 143 |
+ |
|
| 144 |
+func (s *TagStore) pushImage(r *registry.Registry, out io.Writer, remote, imgID, ep string, token []string, sf *utils.StreamFormatter) (checksum string, err error) {
|
|
| 145 |
+ out = utils.NewWriteFlusher(out) |
|
| 146 |
+ jsonRaw, err := ioutil.ReadFile(path.Join(s.graph.Root, imgID, "json")) |
|
| 147 |
+ if err != nil {
|
|
| 148 |
+ return "", fmt.Errorf("Cannot retrieve the path for {%s}: %s", imgID, err)
|
|
| 149 |
+ } |
|
| 150 |
+ out.Write(sf.FormatProgress(utils.TruncateID(imgID), "Pushing", nil)) |
|
| 151 |
+ |
|
| 152 |
+ imgData := ®istry.ImgData{
|
|
| 153 |
+ ID: imgID, |
|
| 154 |
+ } |
|
| 155 |
+ |
|
| 156 |
+ // Send the json |
|
| 157 |
+ if err := r.PushImageJSONRegistry(imgData, jsonRaw, ep, token); err != nil {
|
|
| 158 |
+ if err == registry.ErrAlreadyExists {
|
|
| 159 |
+ out.Write(sf.FormatProgress(utils.TruncateID(imgData.ID), "Image already pushed, skipping", nil)) |
|
| 160 |
+ return "", nil |
|
| 161 |
+ } |
|
| 162 |
+ return "", err |
|
| 163 |
+ } |
|
| 164 |
+ |
|
| 165 |
+ layerData, err := s.graph.TempLayerArchive(imgID, archive.Uncompressed, sf, out) |
|
| 166 |
+ if err != nil {
|
|
| 167 |
+ return "", fmt.Errorf("Failed to generate layer archive: %s", err)
|
|
| 168 |
+ } |
|
| 169 |
+ defer os.RemoveAll(layerData.Name()) |
|
| 170 |
+ |
|
| 171 |
+ // Send the layer |
|
| 172 |
+ utils.Debugf("rendered layer for %s of [%d] size", imgData.ID, layerData.Size)
|
|
| 173 |
+ |
|
| 174 |
+ checksum, checksumPayload, err := r.PushImageLayerRegistry(imgData.ID, utils.ProgressReader(layerData, int(layerData.Size), out, sf, false, utils.TruncateID(imgData.ID), "Pushing"), ep, token, jsonRaw) |
|
| 175 |
+ if err != nil {
|
|
| 176 |
+ return "", err |
|
| 177 |
+ } |
|
| 178 |
+ imgData.Checksum = checksum |
|
| 179 |
+ imgData.ChecksumPayload = checksumPayload |
|
| 180 |
+ // Send the checksum |
|
| 181 |
+ if err := r.PushImageChecksumRegistry(imgData, ep, token); err != nil {
|
|
| 182 |
+ return "", err |
|
| 183 |
+ } |
|
| 184 |
+ |
|
| 185 |
+ out.Write(sf.FormatProgress(utils.TruncateID(imgData.ID), "Image successfully pushed", nil)) |
|
| 186 |
+ return imgData.Checksum, nil |
|
| 187 |
+} |
|
| 188 |
+ |
|
| 189 |
+// FIXME: Allow to interrupt current push when new push of same image is done. |
|
| 190 |
+func (s *TagStore) CmdPush(job *engine.Job) engine.Status {
|
|
| 191 |
+ if n := len(job.Args); n != 1 {
|
|
| 192 |
+ return job.Errorf("Usage: %s IMAGE", job.Name)
|
|
| 193 |
+ } |
|
| 194 |
+ var ( |
|
| 195 |
+ localName = job.Args[0] |
|
| 196 |
+ sf = utils.NewStreamFormatter(job.GetenvBool("json"))
|
|
| 197 |
+ authConfig = ®istry.AuthConfig{}
|
|
| 198 |
+ metaHeaders map[string][]string |
|
| 199 |
+ ) |
|
| 200 |
+ |
|
| 201 |
+ tag := job.Getenv("tag")
|
|
| 202 |
+ job.GetenvJson("authConfig", authConfig)
|
|
| 203 |
+ job.GetenvJson("metaHeaders", &metaHeaders)
|
|
| 204 |
+ if _, err := s.poolAdd("push", localName); err != nil {
|
|
| 205 |
+ return job.Error(err) |
|
| 206 |
+ } |
|
| 207 |
+ defer s.poolRemove("push", localName)
|
|
| 208 |
+ |
|
| 209 |
+ // Resolve the Repository name from fqn to endpoint + name |
|
| 210 |
+ hostname, remoteName, err := registry.ResolveRepositoryName(localName) |
|
| 211 |
+ if err != nil {
|
|
| 212 |
+ return job.Error(err) |
|
| 213 |
+ } |
|
| 214 |
+ |
|
| 215 |
+ endpoint, err := registry.ExpandAndVerifyRegistryUrl(hostname) |
|
| 216 |
+ if err != nil {
|
|
| 217 |
+ return job.Error(err) |
|
| 218 |
+ } |
|
| 219 |
+ |
|
| 220 |
+ img, err := s.graph.Get(localName) |
|
| 221 |
+ r, err2 := registry.NewRegistry(authConfig, registry.HTTPRequestFactory(metaHeaders), endpoint, false) |
|
| 222 |
+ if err2 != nil {
|
|
| 223 |
+ return job.Error(err2) |
|
| 224 |
+ } |
|
| 225 |
+ |
|
| 226 |
+ if err != nil {
|
|
| 227 |
+ reposLen := 1 |
|
| 228 |
+ if tag == "" {
|
|
| 229 |
+ reposLen = len(s.Repositories[localName]) |
|
| 230 |
+ } |
|
| 231 |
+ job.Stdout.Write(sf.FormatStatus("", "The push refers to a repository [%s] (len: %d)", localName, reposLen))
|
|
| 232 |
+ // If it fails, try to get the repository |
|
| 233 |
+ if localRepo, exists := s.Repositories[localName]; exists {
|
|
| 234 |
+ if err := s.pushRepository(r, job.Stdout, localName, remoteName, localRepo, tag, sf); err != nil {
|
|
| 235 |
+ return job.Error(err) |
|
| 236 |
+ } |
|
| 237 |
+ return engine.StatusOK |
|
| 238 |
+ } |
|
| 239 |
+ return job.Error(err) |
|
| 240 |
+ } |
|
| 241 |
+ |
|
| 242 |
+ var token []string |
|
| 243 |
+ job.Stdout.Write(sf.FormatStatus("", "The push refers to an image: [%s]", localName))
|
|
| 244 |
+ if _, err := s.pushImage(r, job.Stdout, remoteName, img.ID, endpoint, token, sf); err != nil {
|
|
| 245 |
+ return job.Error(err) |
|
| 246 |
+ } |
|
| 247 |
+ return engine.StatusOK |
|
| 248 |
+} |
| ... | ... |
@@ -23,6 +23,8 @@ func (s *TagStore) Install(eng *engine.Engine) error {
|
| 23 | 23 |
"viz": s.CmdViz, |
| 24 | 24 |
"load": s.CmdLoad, |
| 25 | 25 |
"import": s.CmdImport, |
| 26 |
+ "pull": s.CmdPull, |
|
| 27 |
+ "push": s.CmdPush, |
|
| 26 | 28 |
} {
|
| 27 | 29 |
if err := eng.Register(name, handler); err != nil {
|
| 28 | 30 |
return fmt.Errorf("Could not register %q: %v", name, err)
|
| ... | ... |
@@ -22,6 +22,10 @@ type TagStore struct {
|
| 22 | 22 |
graph *Graph |
| 23 | 23 |
Repositories map[string]Repository |
| 24 | 24 |
sync.Mutex |
| 25 |
+ // FIXME: move push/pull-related fields |
|
| 26 |
+ // to a helper type |
|
| 27 |
+ pullingPool map[string]chan struct{}
|
|
| 28 |
+ pushingPool map[string]chan struct{}
|
|
| 25 | 29 |
} |
| 26 | 30 |
|
| 27 | 31 |
type Repository map[string]string |
| ... | ... |
@@ -35,6 +39,8 @@ func NewTagStore(path string, graph *Graph) (*TagStore, error) {
|
| 35 | 35 |
path: abspath, |
| 36 | 36 |
graph: graph, |
| 37 | 37 |
Repositories: make(map[string]Repository), |
| 38 |
+ pullingPool: make(map[string]chan struct{}),
|
|
| 39 |
+ pushingPool: make(map[string]chan struct{}),
|
|
| 38 | 40 |
} |
| 39 | 41 |
// Load the json file if it exists, otherwise create it. |
| 40 | 42 |
if err := store.reload(); os.IsNotExist(err) {
|
| ... | ... |
@@ -263,3 +269,46 @@ func validateTagName(name string) error {
|
| 263 | 263 |
} |
| 264 | 264 |
return nil |
| 265 | 265 |
} |
| 266 |
+ |
|
| 267 |
+func (s *TagStore) poolAdd(kind, key string) (chan struct{}, error) {
|
|
| 268 |
+ s.Lock() |
|
| 269 |
+ defer s.Unlock() |
|
| 270 |
+ |
|
| 271 |
+ if c, exists := s.pullingPool[key]; exists {
|
|
| 272 |
+ return c, fmt.Errorf("pull %s is already in progress", key)
|
|
| 273 |
+ } |
|
| 274 |
+ if c, exists := s.pushingPool[key]; exists {
|
|
| 275 |
+ return c, fmt.Errorf("push %s is already in progress", key)
|
|
| 276 |
+ } |
|
| 277 |
+ |
|
| 278 |
+ c := make(chan struct{})
|
|
| 279 |
+ switch kind {
|
|
| 280 |
+ case "pull": |
|
| 281 |
+ s.pullingPool[key] = c |
|
| 282 |
+ case "push": |
|
| 283 |
+ s.pushingPool[key] = c |
|
| 284 |
+ default: |
|
| 285 |
+ return nil, fmt.Errorf("Unknown pool type")
|
|
| 286 |
+ } |
|
| 287 |
+ return c, nil |
|
| 288 |
+} |
|
| 289 |
+ |
|
| 290 |
+func (s *TagStore) poolRemove(kind, key string) error {
|
|
| 291 |
+ s.Lock() |
|
| 292 |
+ defer s.Unlock() |
|
| 293 |
+ switch kind {
|
|
| 294 |
+ case "pull": |
|
| 295 |
+ if c, exists := s.pullingPool[key]; exists {
|
|
| 296 |
+ close(c) |
|
| 297 |
+ delete(s.pullingPool, key) |
|
| 298 |
+ } |
|
| 299 |
+ case "push": |
|
| 300 |
+ if c, exists := s.pushingPool[key]; exists {
|
|
| 301 |
+ close(c) |
|
| 302 |
+ delete(s.pushingPool, key) |
|
| 303 |
+ } |
|
| 304 |
+ default: |
|
| 305 |
+ return fmt.Errorf("Unknown pool type")
|
|
| 306 |
+ } |
|
| 307 |
+ return nil |
|
| 308 |
+} |
| ... | ... |
@@ -5,21 +5,15 @@ |
| 5 | 5 |
package server |
| 6 | 6 |
|
| 7 | 7 |
import ( |
| 8 |
- "fmt" |
|
| 9 | 8 |
"io" |
| 10 | 9 |
"io/ioutil" |
| 11 |
- "net" |
|
| 12 |
- "net/url" |
|
| 13 | 10 |
"os" |
| 14 | 11 |
"os/exec" |
| 15 |
- "path" |
|
| 16 | 12 |
"strings" |
| 17 |
- "time" |
|
| 18 | 13 |
|
| 19 | 14 |
"github.com/docker/docker/archive" |
| 20 | 15 |
"github.com/docker/docker/builder" |
| 21 | 16 |
"github.com/docker/docker/engine" |
| 22 |
- "github.com/docker/docker/image" |
|
| 23 | 17 |
"github.com/docker/docker/pkg/parsers" |
| 24 | 18 |
"github.com/docker/docker/registry" |
| 25 | 19 |
"github.com/docker/docker/utils" |
| ... | ... |
@@ -104,566 +98,3 @@ func (srv *Server) Build(job *engine.Job) engine.Status {
|
| 104 | 104 |
} |
| 105 | 105 |
return engine.StatusOK |
| 106 | 106 |
} |
| 107 |
- |
|
| 108 |
-func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgID, endpoint string, token []string, sf *utils.StreamFormatter) error {
|
|
| 109 |
- history, err := r.GetRemoteHistory(imgID, endpoint, token) |
|
| 110 |
- if err != nil {
|
|
| 111 |
- return err |
|
| 112 |
- } |
|
| 113 |
- out.Write(sf.FormatProgress(utils.TruncateID(imgID), "Pulling dependent layers", nil)) |
|
| 114 |
- // FIXME: Try to stream the images? |
|
| 115 |
- // FIXME: Launch the getRemoteImage() in goroutines |
|
| 116 |
- |
|
| 117 |
- for i := len(history) - 1; i >= 0; i-- {
|
|
| 118 |
- id := history[i] |
|
| 119 |
- |
|
| 120 |
- // ensure no two downloads of the same layer happen at the same time |
|
| 121 |
- if c, err := srv.poolAdd("pull", "layer:"+id); err != nil {
|
|
| 122 |
- utils.Debugf("Image (id: %s) pull is already running, skipping: %v", id, err)
|
|
| 123 |
- <-c |
|
| 124 |
- } |
|
| 125 |
- defer srv.poolRemove("pull", "layer:"+id)
|
|
| 126 |
- |
|
| 127 |
- if !srv.daemon.Graph().Exists(id) {
|
|
| 128 |
- out.Write(sf.FormatProgress(utils.TruncateID(id), "Pulling metadata", nil)) |
|
| 129 |
- var ( |
|
| 130 |
- imgJSON []byte |
|
| 131 |
- imgSize int |
|
| 132 |
- err error |
|
| 133 |
- img *image.Image |
|
| 134 |
- ) |
|
| 135 |
- retries := 5 |
|
| 136 |
- for j := 1; j <= retries; j++ {
|
|
| 137 |
- imgJSON, imgSize, err = r.GetRemoteImageJSON(id, endpoint, token) |
|
| 138 |
- if err != nil && j == retries {
|
|
| 139 |
- out.Write(sf.FormatProgress(utils.TruncateID(id), "Error pulling dependent layers", nil)) |
|
| 140 |
- return err |
|
| 141 |
- } else if err != nil {
|
|
| 142 |
- time.Sleep(time.Duration(j) * 500 * time.Millisecond) |
|
| 143 |
- continue |
|
| 144 |
- } |
|
| 145 |
- img, err = image.NewImgJSON(imgJSON) |
|
| 146 |
- if err != nil && j == retries {
|
|
| 147 |
- out.Write(sf.FormatProgress(utils.TruncateID(id), "Error pulling dependent layers", nil)) |
|
| 148 |
- return fmt.Errorf("Failed to parse json: %s", err)
|
|
| 149 |
- } else if err != nil {
|
|
| 150 |
- time.Sleep(time.Duration(j) * 500 * time.Millisecond) |
|
| 151 |
- continue |
|
| 152 |
- } else {
|
|
| 153 |
- break |
|
| 154 |
- } |
|
| 155 |
- } |
|
| 156 |
- |
|
| 157 |
- for j := 1; j <= retries; j++ {
|
|
| 158 |
- // Get the layer |
|
| 159 |
- status := "Pulling fs layer" |
|
| 160 |
- if j > 1 {
|
|
| 161 |
- status = fmt.Sprintf("Pulling fs layer [retries: %d]", j)
|
|
| 162 |
- } |
|
| 163 |
- out.Write(sf.FormatProgress(utils.TruncateID(id), status, nil)) |
|
| 164 |
- layer, err := r.GetRemoteImageLayer(img.ID, endpoint, token, int64(imgSize)) |
|
| 165 |
- if uerr, ok := err.(*url.Error); ok {
|
|
| 166 |
- err = uerr.Err |
|
| 167 |
- } |
|
| 168 |
- if terr, ok := err.(net.Error); ok && terr.Timeout() && j < retries {
|
|
| 169 |
- time.Sleep(time.Duration(j) * 500 * time.Millisecond) |
|
| 170 |
- continue |
|
| 171 |
- } else if err != nil {
|
|
| 172 |
- out.Write(sf.FormatProgress(utils.TruncateID(id), "Error pulling dependent layers", nil)) |
|
| 173 |
- return err |
|
| 174 |
- } |
|
| 175 |
- defer layer.Close() |
|
| 176 |
- |
|
| 177 |
- err = srv.daemon.Graph().Register(imgJSON, |
|
| 178 |
- utils.ProgressReader(layer, imgSize, out, sf, false, utils.TruncateID(id), "Downloading"), |
|
| 179 |
- img) |
|
| 180 |
- if terr, ok := err.(net.Error); ok && terr.Timeout() && j < retries {
|
|
| 181 |
- time.Sleep(time.Duration(j) * 500 * time.Millisecond) |
|
| 182 |
- continue |
|
| 183 |
- } else if err != nil {
|
|
| 184 |
- out.Write(sf.FormatProgress(utils.TruncateID(id), "Error downloading dependent layers", nil)) |
|
| 185 |
- return err |
|
| 186 |
- } else {
|
|
| 187 |
- break |
|
| 188 |
- } |
|
| 189 |
- } |
|
| 190 |
- } |
|
| 191 |
- out.Write(sf.FormatProgress(utils.TruncateID(id), "Download complete", nil)) |
|
| 192 |
- |
|
| 193 |
- } |
|
| 194 |
- return nil |
|
| 195 |
-} |
|
| 196 |
- |
|
| 197 |
-func (srv *Server) pullRepository(r *registry.Registry, out io.Writer, localName, remoteName, askedTag string, sf *utils.StreamFormatter, parallel bool) error {
|
|
| 198 |
- out.Write(sf.FormatStatus("", "Pulling repository %s", localName))
|
|
| 199 |
- |
|
| 200 |
- repoData, err := r.GetRepositoryData(remoteName) |
|
| 201 |
- if err != nil {
|
|
| 202 |
- if strings.Contains(err.Error(), "HTTP code: 404") {
|
|
| 203 |
- return fmt.Errorf("Error: image %s not found", remoteName)
|
|
| 204 |
- } else {
|
|
| 205 |
- // Unexpected HTTP error |
|
| 206 |
- return err |
|
| 207 |
- } |
|
| 208 |
- } |
|
| 209 |
- |
|
| 210 |
- utils.Debugf("Retrieving the tag list")
|
|
| 211 |
- tagsList, err := r.GetRemoteTags(repoData.Endpoints, remoteName, repoData.Tokens) |
|
| 212 |
- if err != nil {
|
|
| 213 |
- utils.Errorf("%v", err)
|
|
| 214 |
- return err |
|
| 215 |
- } |
|
| 216 |
- |
|
| 217 |
- for tag, id := range tagsList {
|
|
| 218 |
- repoData.ImgList[id] = ®istry.ImgData{
|
|
| 219 |
- ID: id, |
|
| 220 |
- Tag: tag, |
|
| 221 |
- Checksum: "", |
|
| 222 |
- } |
|
| 223 |
- } |
|
| 224 |
- |
|
| 225 |
- utils.Debugf("Registering tags")
|
|
| 226 |
- // If no tag has been specified, pull them all |
|
| 227 |
- if askedTag == "" {
|
|
| 228 |
- for tag, id := range tagsList {
|
|
| 229 |
- repoData.ImgList[id].Tag = tag |
|
| 230 |
- } |
|
| 231 |
- } else {
|
|
| 232 |
- // Otherwise, check that the tag exists and use only that one |
|
| 233 |
- id, exists := tagsList[askedTag] |
|
| 234 |
- if !exists {
|
|
| 235 |
- return fmt.Errorf("Tag %s not found in repository %s", askedTag, localName)
|
|
| 236 |
- } |
|
| 237 |
- repoData.ImgList[id].Tag = askedTag |
|
| 238 |
- } |
|
| 239 |
- |
|
| 240 |
- errors := make(chan error) |
|
| 241 |
- for _, image := range repoData.ImgList {
|
|
| 242 |
- downloadImage := func(img *registry.ImgData) {
|
|
| 243 |
- if askedTag != "" && img.Tag != askedTag {
|
|
| 244 |
- utils.Debugf("(%s) does not match %s (id: %s), skipping", img.Tag, askedTag, img.ID)
|
|
| 245 |
- if parallel {
|
|
| 246 |
- errors <- nil |
|
| 247 |
- } |
|
| 248 |
- return |
|
| 249 |
- } |
|
| 250 |
- |
|
| 251 |
- if img.Tag == "" {
|
|
| 252 |
- utils.Debugf("Image (id: %s) present in this repository but untagged, skipping", img.ID)
|
|
| 253 |
- if parallel {
|
|
| 254 |
- errors <- nil |
|
| 255 |
- } |
|
| 256 |
- return |
|
| 257 |
- } |
|
| 258 |
- |
|
| 259 |
- // ensure no two downloads of the same image happen at the same time |
|
| 260 |
- if c, err := srv.poolAdd("pull", "img:"+img.ID); err != nil {
|
|
| 261 |
- if c != nil {
|
|
| 262 |
- out.Write(sf.FormatProgress(utils.TruncateID(img.ID), "Layer already being pulled by another client. Waiting.", nil)) |
|
| 263 |
- <-c |
|
| 264 |
- out.Write(sf.FormatProgress(utils.TruncateID(img.ID), "Download complete", nil)) |
|
| 265 |
- } else {
|
|
| 266 |
- utils.Debugf("Image (id: %s) pull is already running, skipping: %v", img.ID, err)
|
|
| 267 |
- } |
|
| 268 |
- if parallel {
|
|
| 269 |
- errors <- nil |
|
| 270 |
- } |
|
| 271 |
- return |
|
| 272 |
- } |
|
| 273 |
- defer srv.poolRemove("pull", "img:"+img.ID)
|
|
| 274 |
- |
|
| 275 |
- out.Write(sf.FormatProgress(utils.TruncateID(img.ID), fmt.Sprintf("Pulling image (%s) from %s", img.Tag, localName), nil))
|
|
| 276 |
- success := false |
|
| 277 |
- var lastErr error |
|
| 278 |
- for _, ep := range repoData.Endpoints {
|
|
| 279 |
- out.Write(sf.FormatProgress(utils.TruncateID(img.ID), fmt.Sprintf("Pulling image (%s) from %s, endpoint: %s", img.Tag, localName, ep), nil))
|
|
| 280 |
- if err := srv.pullImage(r, out, img.ID, ep, repoData.Tokens, sf); err != nil {
|
|
| 281 |
- // It's not ideal that only the last error is returned, it would be better to concatenate the errors. |
|
| 282 |
- // As the error is also given to the output stream the user will see the error. |
|
| 283 |
- lastErr = err |
|
| 284 |
- out.Write(sf.FormatProgress(utils.TruncateID(img.ID), fmt.Sprintf("Error pulling image (%s) from %s, endpoint: %s, %s", img.Tag, localName, ep, err), nil))
|
|
| 285 |
- continue |
|
| 286 |
- } |
|
| 287 |
- success = true |
|
| 288 |
- break |
|
| 289 |
- } |
|
| 290 |
- if !success {
|
|
| 291 |
- err := fmt.Errorf("Error pulling image (%s) from %s, %v", img.Tag, localName, lastErr)
|
|
| 292 |
- out.Write(sf.FormatProgress(utils.TruncateID(img.ID), err.Error(), nil)) |
|
| 293 |
- if parallel {
|
|
| 294 |
- errors <- err |
|
| 295 |
- return |
|
| 296 |
- } |
|
| 297 |
- } |
|
| 298 |
- out.Write(sf.FormatProgress(utils.TruncateID(img.ID), "Download complete", nil)) |
|
| 299 |
- |
|
| 300 |
- if parallel {
|
|
| 301 |
- errors <- nil |
|
| 302 |
- } |
|
| 303 |
- } |
|
| 304 |
- |
|
| 305 |
- if parallel {
|
|
| 306 |
- go downloadImage(image) |
|
| 307 |
- } else {
|
|
| 308 |
- downloadImage(image) |
|
| 309 |
- } |
|
| 310 |
- } |
|
| 311 |
- if parallel {
|
|
| 312 |
- var lastError error |
|
| 313 |
- for i := 0; i < len(repoData.ImgList); i++ {
|
|
| 314 |
- if err := <-errors; err != nil {
|
|
| 315 |
- lastError = err |
|
| 316 |
- } |
|
| 317 |
- } |
|
| 318 |
- if lastError != nil {
|
|
| 319 |
- return lastError |
|
| 320 |
- } |
|
| 321 |
- |
|
| 322 |
- } |
|
| 323 |
- for tag, id := range tagsList {
|
|
| 324 |
- if askedTag != "" && tag != askedTag {
|
|
| 325 |
- continue |
|
| 326 |
- } |
|
| 327 |
- if err := srv.daemon.Repositories().Set(localName, tag, id, true); err != nil {
|
|
| 328 |
- return err |
|
| 329 |
- } |
|
| 330 |
- } |
|
| 331 |
- |
|
| 332 |
- return nil |
|
| 333 |
-} |
|
| 334 |
- |
|
| 335 |
-func (srv *Server) ImagePull(job *engine.Job) engine.Status {
|
|
| 336 |
- if n := len(job.Args); n != 1 && n != 2 {
|
|
| 337 |
- return job.Errorf("Usage: %s IMAGE [TAG]", job.Name)
|
|
| 338 |
- } |
|
| 339 |
- var ( |
|
| 340 |
- localName = job.Args[0] |
|
| 341 |
- tag string |
|
| 342 |
- sf = utils.NewStreamFormatter(job.GetenvBool("json"))
|
|
| 343 |
- authConfig = ®istry.AuthConfig{}
|
|
| 344 |
- metaHeaders map[string][]string |
|
| 345 |
- ) |
|
| 346 |
- if len(job.Args) > 1 {
|
|
| 347 |
- tag = job.Args[1] |
|
| 348 |
- } |
|
| 349 |
- |
|
| 350 |
- job.GetenvJson("authConfig", authConfig)
|
|
| 351 |
- job.GetenvJson("metaHeaders", &metaHeaders)
|
|
| 352 |
- |
|
| 353 |
- c, err := srv.poolAdd("pull", localName+":"+tag)
|
|
| 354 |
- if err != nil {
|
|
| 355 |
- if c != nil {
|
|
| 356 |
- // Another pull of the same repository is already taking place; just wait for it to finish |
|
| 357 |
- job.Stdout.Write(sf.FormatStatus("", "Repository %s already being pulled by another client. Waiting.", localName))
|
|
| 358 |
- <-c |
|
| 359 |
- return engine.StatusOK |
|
| 360 |
- } |
|
| 361 |
- return job.Error(err) |
|
| 362 |
- } |
|
| 363 |
- defer srv.poolRemove("pull", localName+":"+tag)
|
|
| 364 |
- |
|
| 365 |
- // Resolve the Repository name from fqn to endpoint + name |
|
| 366 |
- hostname, remoteName, err := registry.ResolveRepositoryName(localName) |
|
| 367 |
- if err != nil {
|
|
| 368 |
- return job.Error(err) |
|
| 369 |
- } |
|
| 370 |
- |
|
| 371 |
- endpoint, err := registry.ExpandAndVerifyRegistryUrl(hostname) |
|
| 372 |
- if err != nil {
|
|
| 373 |
- return job.Error(err) |
|
| 374 |
- } |
|
| 375 |
- |
|
| 376 |
- r, err := registry.NewRegistry(authConfig, registry.HTTPRequestFactory(metaHeaders), endpoint, true) |
|
| 377 |
- if err != nil {
|
|
| 378 |
- return job.Error(err) |
|
| 379 |
- } |
|
| 380 |
- |
|
| 381 |
- if endpoint == registry.IndexServerAddress() {
|
|
| 382 |
- // If pull "index.docker.io/foo/bar", it's stored locally under "foo/bar" |
|
| 383 |
- localName = remoteName |
|
| 384 |
- } |
|
| 385 |
- |
|
| 386 |
- if err = srv.pullRepository(r, job.Stdout, localName, remoteName, tag, sf, job.GetenvBool("parallel")); err != nil {
|
|
| 387 |
- return job.Error(err) |
|
| 388 |
- } |
|
| 389 |
- |
|
| 390 |
- return engine.StatusOK |
|
| 391 |
-} |
|
| 392 |
- |
|
| 393 |
-// Retrieve the all the images to be uploaded in the correct order |
|
| 394 |
-func (srv *Server) getImageList(localRepo map[string]string, requestedTag string) ([]string, map[string][]string, error) {
|
|
| 395 |
- var ( |
|
| 396 |
- imageList []string |
|
| 397 |
- imagesSeen map[string]bool = make(map[string]bool) |
|
| 398 |
- tagsByImage map[string][]string = make(map[string][]string) |
|
| 399 |
- ) |
|
| 400 |
- |
|
| 401 |
- for tag, id := range localRepo {
|
|
| 402 |
- if requestedTag != "" && requestedTag != tag {
|
|
| 403 |
- continue |
|
| 404 |
- } |
|
| 405 |
- var imageListForThisTag []string |
|
| 406 |
- |
|
| 407 |
- tagsByImage[id] = append(tagsByImage[id], tag) |
|
| 408 |
- |
|
| 409 |
- for img, err := srv.daemon.Graph().Get(id); img != nil; img, err = img.GetParent() {
|
|
| 410 |
- if err != nil {
|
|
| 411 |
- return nil, nil, err |
|
| 412 |
- } |
|
| 413 |
- |
|
| 414 |
- if imagesSeen[img.ID] {
|
|
| 415 |
- // This image is already on the list, we can ignore it and all its parents |
|
| 416 |
- break |
|
| 417 |
- } |
|
| 418 |
- |
|
| 419 |
- imagesSeen[img.ID] = true |
|
| 420 |
- imageListForThisTag = append(imageListForThisTag, img.ID) |
|
| 421 |
- } |
|
| 422 |
- |
|
| 423 |
- // reverse the image list for this tag (so the "most"-parent image is first) |
|
| 424 |
- for i, j := 0, len(imageListForThisTag)-1; i < j; i, j = i+1, j-1 {
|
|
| 425 |
- imageListForThisTag[i], imageListForThisTag[j] = imageListForThisTag[j], imageListForThisTag[i] |
|
| 426 |
- } |
|
| 427 |
- |
|
| 428 |
- // append to main image list |
|
| 429 |
- imageList = append(imageList, imageListForThisTag...) |
|
| 430 |
- } |
|
| 431 |
- if len(imageList) == 0 {
|
|
| 432 |
- return nil, nil, fmt.Errorf("No images found for the requested repository / tag")
|
|
| 433 |
- } |
|
| 434 |
- utils.Debugf("Image list: %v", imageList)
|
|
| 435 |
- utils.Debugf("Tags by image: %v", tagsByImage)
|
|
| 436 |
- |
|
| 437 |
- return imageList, tagsByImage, nil |
|
| 438 |
-} |
|
| 439 |
- |
|
| 440 |
-func (srv *Server) pushRepository(r *registry.Registry, out io.Writer, localName, remoteName string, localRepo map[string]string, tag string, sf *utils.StreamFormatter) error {
|
|
| 441 |
- out = utils.NewWriteFlusher(out) |
|
| 442 |
- utils.Debugf("Local repo: %s", localRepo)
|
|
| 443 |
- imgList, tagsByImage, err := srv.getImageList(localRepo, tag) |
|
| 444 |
- if err != nil {
|
|
| 445 |
- return err |
|
| 446 |
- } |
|
| 447 |
- |
|
| 448 |
- out.Write(sf.FormatStatus("", "Sending image list"))
|
|
| 449 |
- |
|
| 450 |
- var ( |
|
| 451 |
- repoData *registry.RepositoryData |
|
| 452 |
- imageIndex []*registry.ImgData |
|
| 453 |
- ) |
|
| 454 |
- |
|
| 455 |
- for _, imgId := range imgList {
|
|
| 456 |
- if tags, exists := tagsByImage[imgId]; exists {
|
|
| 457 |
- // If an image has tags you must add an entry in the image index |
|
| 458 |
- // for each tag |
|
| 459 |
- for _, tag := range tags {
|
|
| 460 |
- imageIndex = append(imageIndex, ®istry.ImgData{
|
|
| 461 |
- ID: imgId, |
|
| 462 |
- Tag: tag, |
|
| 463 |
- }) |
|
| 464 |
- } |
|
| 465 |
- } else {
|
|
| 466 |
- // If the image does not have a tag it still needs to be sent to the |
|
| 467 |
- // registry with an empty tag so that it is accociated with the repository |
|
| 468 |
- imageIndex = append(imageIndex, ®istry.ImgData{
|
|
| 469 |
- ID: imgId, |
|
| 470 |
- Tag: "", |
|
| 471 |
- }) |
|
| 472 |
- |
|
| 473 |
- } |
|
| 474 |
- } |
|
| 475 |
- |
|
| 476 |
- utils.Debugf("Preparing to push %s with the following images and tags\n", localRepo)
|
|
| 477 |
- for _, data := range imageIndex {
|
|
| 478 |
- utils.Debugf("Pushing ID: %s with Tag: %s\n", data.ID, data.Tag)
|
|
| 479 |
- } |
|
| 480 |
- |
|
| 481 |
- // Register all the images in a repository with the registry |
|
| 482 |
- // If an image is not in this list it will not be associated with the repository |
|
| 483 |
- repoData, err = r.PushImageJSONIndex(remoteName, imageIndex, false, nil) |
|
| 484 |
- if err != nil {
|
|
| 485 |
- return err |
|
| 486 |
- } |
|
| 487 |
- |
|
| 488 |
- nTag := 1 |
|
| 489 |
- if tag == "" {
|
|
| 490 |
- nTag = len(localRepo) |
|
| 491 |
- } |
|
| 492 |
- for _, ep := range repoData.Endpoints {
|
|
| 493 |
- out.Write(sf.FormatStatus("", "Pushing repository %s (%d tags)", localName, nTag))
|
|
| 494 |
- |
|
| 495 |
- for _, imgId := range imgList {
|
|
| 496 |
- if r.LookupRemoteImage(imgId, ep, repoData.Tokens) {
|
|
| 497 |
- out.Write(sf.FormatStatus("", "Image %s already pushed, skipping", utils.TruncateID(imgId)))
|
|
| 498 |
- } else {
|
|
| 499 |
- if _, err := srv.pushImage(r, out, remoteName, imgId, ep, repoData.Tokens, sf); err != nil {
|
|
| 500 |
- // FIXME: Continue on error? |
|
| 501 |
- return err |
|
| 502 |
- } |
|
| 503 |
- } |
|
| 504 |
- |
|
| 505 |
- for _, tag := range tagsByImage[imgId] {
|
|
| 506 |
- out.Write(sf.FormatStatus("", "Pushing tag for rev [%s] on {%s}", utils.TruncateID(imgId), ep+"repositories/"+remoteName+"/tags/"+tag))
|
|
| 507 |
- |
|
| 508 |
- if err := r.PushRegistryTag(remoteName, imgId, tag, ep, repoData.Tokens); err != nil {
|
|
| 509 |
- return err |
|
| 510 |
- } |
|
| 511 |
- } |
|
| 512 |
- } |
|
| 513 |
- } |
|
| 514 |
- |
|
| 515 |
- if _, err := r.PushImageJSONIndex(remoteName, imageIndex, true, repoData.Endpoints); err != nil {
|
|
| 516 |
- return err |
|
| 517 |
- } |
|
| 518 |
- |
|
| 519 |
- return nil |
|
| 520 |
-} |
|
| 521 |
- |
|
| 522 |
-func (srv *Server) pushImage(r *registry.Registry, out io.Writer, remote, imgID, ep string, token []string, sf *utils.StreamFormatter) (checksum string, err error) {
|
|
| 523 |
- out = utils.NewWriteFlusher(out) |
|
| 524 |
- jsonRaw, err := ioutil.ReadFile(path.Join(srv.daemon.Graph().Root, imgID, "json")) |
|
| 525 |
- if err != nil {
|
|
| 526 |
- return "", fmt.Errorf("Cannot retrieve the path for {%s}: %s", imgID, err)
|
|
| 527 |
- } |
|
| 528 |
- out.Write(sf.FormatProgress(utils.TruncateID(imgID), "Pushing", nil)) |
|
| 529 |
- |
|
| 530 |
- imgData := ®istry.ImgData{
|
|
| 531 |
- ID: imgID, |
|
| 532 |
- } |
|
| 533 |
- |
|
| 534 |
- // Send the json |
|
| 535 |
- if err := r.PushImageJSONRegistry(imgData, jsonRaw, ep, token); err != nil {
|
|
| 536 |
- if err == registry.ErrAlreadyExists {
|
|
| 537 |
- out.Write(sf.FormatProgress(utils.TruncateID(imgData.ID), "Image already pushed, skipping", nil)) |
|
| 538 |
- return "", nil |
|
| 539 |
- } |
|
| 540 |
- return "", err |
|
| 541 |
- } |
|
| 542 |
- |
|
| 543 |
- layerData, err := srv.daemon.Graph().TempLayerArchive(imgID, archive.Uncompressed, sf, out) |
|
| 544 |
- if err != nil {
|
|
| 545 |
- return "", fmt.Errorf("Failed to generate layer archive: %s", err)
|
|
| 546 |
- } |
|
| 547 |
- defer os.RemoveAll(layerData.Name()) |
|
| 548 |
- |
|
| 549 |
- // Send the layer |
|
| 550 |
- utils.Debugf("rendered layer for %s of [%d] size", imgData.ID, layerData.Size)
|
|
| 551 |
- |
|
| 552 |
- checksum, checksumPayload, err := r.PushImageLayerRegistry(imgData.ID, utils.ProgressReader(layerData, int(layerData.Size), out, sf, false, utils.TruncateID(imgData.ID), "Pushing"), ep, token, jsonRaw) |
|
| 553 |
- if err != nil {
|
|
| 554 |
- return "", err |
|
| 555 |
- } |
|
| 556 |
- imgData.Checksum = checksum |
|
| 557 |
- imgData.ChecksumPayload = checksumPayload |
|
| 558 |
- // Send the checksum |
|
| 559 |
- if err := r.PushImageChecksumRegistry(imgData, ep, token); err != nil {
|
|
| 560 |
- return "", err |
|
| 561 |
- } |
|
| 562 |
- |
|
| 563 |
- out.Write(sf.FormatProgress(utils.TruncateID(imgData.ID), "Image successfully pushed", nil)) |
|
| 564 |
- return imgData.Checksum, nil |
|
| 565 |
-} |
|
| 566 |
- |
|
| 567 |
-// FIXME: Allow to interrupt current push when new push of same image is done. |
|
| 568 |
-func (srv *Server) ImagePush(job *engine.Job) engine.Status {
|
|
| 569 |
- if n := len(job.Args); n != 1 {
|
|
| 570 |
- return job.Errorf("Usage: %s IMAGE", job.Name)
|
|
| 571 |
- } |
|
| 572 |
- var ( |
|
| 573 |
- localName = job.Args[0] |
|
| 574 |
- sf = utils.NewStreamFormatter(job.GetenvBool("json"))
|
|
| 575 |
- authConfig = ®istry.AuthConfig{}
|
|
| 576 |
- metaHeaders map[string][]string |
|
| 577 |
- ) |
|
| 578 |
- |
|
| 579 |
- tag := job.Getenv("tag")
|
|
| 580 |
- job.GetenvJson("authConfig", authConfig)
|
|
| 581 |
- job.GetenvJson("metaHeaders", &metaHeaders)
|
|
| 582 |
- if _, err := srv.poolAdd("push", localName); err != nil {
|
|
| 583 |
- return job.Error(err) |
|
| 584 |
- } |
|
| 585 |
- defer srv.poolRemove("push", localName)
|
|
| 586 |
- |
|
| 587 |
- // Resolve the Repository name from fqn to endpoint + name |
|
| 588 |
- hostname, remoteName, err := registry.ResolveRepositoryName(localName) |
|
| 589 |
- if err != nil {
|
|
| 590 |
- return job.Error(err) |
|
| 591 |
- } |
|
| 592 |
- |
|
| 593 |
- endpoint, err := registry.ExpandAndVerifyRegistryUrl(hostname) |
|
| 594 |
- if err != nil {
|
|
| 595 |
- return job.Error(err) |
|
| 596 |
- } |
|
| 597 |
- |
|
| 598 |
- img, err := srv.daemon.Graph().Get(localName) |
|
| 599 |
- r, err2 := registry.NewRegistry(authConfig, registry.HTTPRequestFactory(metaHeaders), endpoint, false) |
|
| 600 |
- if err2 != nil {
|
|
| 601 |
- return job.Error(err2) |
|
| 602 |
- } |
|
| 603 |
- |
|
| 604 |
- if err != nil {
|
|
| 605 |
- reposLen := 1 |
|
| 606 |
- if tag == "" {
|
|
| 607 |
- reposLen = len(srv.daemon.Repositories().Repositories[localName]) |
|
| 608 |
- } |
|
| 609 |
- job.Stdout.Write(sf.FormatStatus("", "The push refers to a repository [%s] (len: %d)", localName, reposLen))
|
|
| 610 |
- // If it fails, try to get the repository |
|
| 611 |
- if localRepo, exists := srv.daemon.Repositories().Repositories[localName]; exists {
|
|
| 612 |
- if err := srv.pushRepository(r, job.Stdout, localName, remoteName, localRepo, tag, sf); err != nil {
|
|
| 613 |
- return job.Error(err) |
|
| 614 |
- } |
|
| 615 |
- return engine.StatusOK |
|
| 616 |
- } |
|
| 617 |
- return job.Error(err) |
|
| 618 |
- } |
|
| 619 |
- |
|
| 620 |
- var token []string |
|
| 621 |
- job.Stdout.Write(sf.FormatStatus("", "The push refers to an image: [%s]", localName))
|
|
| 622 |
- if _, err := srv.pushImage(r, job.Stdout, remoteName, img.ID, endpoint, token, sf); err != nil {
|
|
| 623 |
- return job.Error(err) |
|
| 624 |
- } |
|
| 625 |
- return engine.StatusOK |
|
| 626 |
-} |
|
| 627 |
- |
|
| 628 |
-func (srv *Server) poolAdd(kind, key string) (chan struct{}, error) {
|
|
| 629 |
- srv.Lock() |
|
| 630 |
- defer srv.Unlock() |
|
| 631 |
- |
|
| 632 |
- if c, exists := srv.pullingPool[key]; exists {
|
|
| 633 |
- return c, fmt.Errorf("pull %s is already in progress", key)
|
|
| 634 |
- } |
|
| 635 |
- if c, exists := srv.pushingPool[key]; exists {
|
|
| 636 |
- return c, fmt.Errorf("push %s is already in progress", key)
|
|
| 637 |
- } |
|
| 638 |
- |
|
| 639 |
- c := make(chan struct{})
|
|
| 640 |
- switch kind {
|
|
| 641 |
- case "pull": |
|
| 642 |
- srv.pullingPool[key] = c |
|
| 643 |
- case "push": |
|
| 644 |
- srv.pushingPool[key] = c |
|
| 645 |
- default: |
|
| 646 |
- return nil, fmt.Errorf("Unknown pool type")
|
|
| 647 |
- } |
|
| 648 |
- return c, nil |
|
| 649 |
-} |
|
| 650 |
- |
|
| 651 |
-func (srv *Server) poolRemove(kind, key string) error {
|
|
| 652 |
- srv.Lock() |
|
| 653 |
- defer srv.Unlock() |
|
| 654 |
- switch kind {
|
|
| 655 |
- case "pull": |
|
| 656 |
- if c, exists := srv.pullingPool[key]; exists {
|
|
| 657 |
- close(c) |
|
| 658 |
- delete(srv.pullingPool, key) |
|
| 659 |
- } |
|
| 660 |
- case "push": |
|
| 661 |
- if c, exists := srv.pushingPool[key]; exists {
|
|
| 662 |
- close(c) |
|
| 663 |
- delete(srv.pushingPool, key) |
|
| 664 |
- } |
|
| 665 |
- default: |
|
| 666 |
- return fmt.Errorf("Unknown pool type")
|
|
| 667 |
- } |
|
| 668 |
- return nil |
|
| 669 |
-} |
| ... | ... |
@@ -33,8 +33,6 @@ func InitServer(job *engine.Job) engine.Status {
|
| 33 | 33 |
|
| 34 | 34 |
for name, handler := range map[string]engine.Handler{
|
| 35 | 35 |
"build": srv.Build, |
| 36 |
- "pull": srv.ImagePull, |
|
| 37 |
- "push": srv.ImagePush, |
|
| 38 | 36 |
} {
|
| 39 | 37 |
if err := job.Eng.Register(name, srv.handlerWrap(handler)); err != nil {
|
| 40 | 38 |
return job.Error(err) |
| ... | ... |
@@ -59,10 +57,8 @@ func NewServer(eng *engine.Engine, config *daemonconfig.Config) (*Server, error) |
| 59 | 59 |
return nil, err |
| 60 | 60 |
} |
| 61 | 61 |
srv := &Server{
|
| 62 |
- Eng: eng, |
|
| 63 |
- daemon: daemon, |
|
| 64 |
- pullingPool: make(map[string]chan struct{}),
|
|
| 65 |
- pushingPool: make(map[string]chan struct{}),
|
|
| 62 |
+ Eng: eng, |
|
| 63 |
+ daemon: daemon, |
|
| 66 | 64 |
} |
| 67 | 65 |
return srv, nil |
| 68 | 66 |
} |
| ... | ... |
@@ -30,9 +30,7 @@ import ( |
| 30 | 30 |
|
| 31 | 31 |
type Server struct {
|
| 32 | 32 |
sync.RWMutex |
| 33 |
- daemon *daemon.Daemon |
|
| 34 |
- pullingPool map[string]chan struct{}
|
|
| 35 |
- pushingPool map[string]chan struct{}
|
|
| 36 |
- Eng *engine.Engine |
|
| 37 |
- tasks sync.WaitGroup |
|
| 33 |
+ daemon *daemon.Daemon |
|
| 34 |
+ Eng *engine.Engine |
|
| 35 |
+ tasks sync.WaitGroup |
|
| 38 | 36 |
} |
| 39 | 37 |
deleted file mode 100644 |
| ... | ... |
@@ -1,41 +0,0 @@ |
| 1 |
-package server |
|
| 2 |
- |
|
| 3 |
-import "testing" |
|
| 4 |
- |
|
| 5 |
-func TestPools(t *testing.T) {
|
|
| 6 |
- srv := &Server{
|
|
| 7 |
- pullingPool: make(map[string]chan struct{}),
|
|
| 8 |
- pushingPool: make(map[string]chan struct{}),
|
|
| 9 |
- } |
|
| 10 |
- |
|
| 11 |
- if _, err := srv.poolAdd("pull", "test1"); err != nil {
|
|
| 12 |
- t.Fatal(err) |
|
| 13 |
- } |
|
| 14 |
- if _, err := srv.poolAdd("pull", "test2"); err != nil {
|
|
| 15 |
- t.Fatal(err) |
|
| 16 |
- } |
|
| 17 |
- if _, err := srv.poolAdd("push", "test1"); err == nil || err.Error() != "pull test1 is already in progress" {
|
|
| 18 |
- t.Fatalf("Expected `pull test1 is already in progress`")
|
|
| 19 |
- } |
|
| 20 |
- if _, err := srv.poolAdd("pull", "test1"); err == nil || err.Error() != "pull test1 is already in progress" {
|
|
| 21 |
- t.Fatalf("Expected `pull test1 is already in progress`")
|
|
| 22 |
- } |
|
| 23 |
- if _, err := srv.poolAdd("wait", "test3"); err == nil || err.Error() != "Unknown pool type" {
|
|
| 24 |
- t.Fatalf("Expected `Unknown pool type`")
|
|
| 25 |
- } |
|
| 26 |
- if err := srv.poolRemove("pull", "test2"); err != nil {
|
|
| 27 |
- t.Fatal(err) |
|
| 28 |
- } |
|
| 29 |
- if err := srv.poolRemove("pull", "test2"); err != nil {
|
|
| 30 |
- t.Fatal(err) |
|
| 31 |
- } |
|
| 32 |
- if err := srv.poolRemove("pull", "test1"); err != nil {
|
|
| 33 |
- t.Fatal(err) |
|
| 34 |
- } |
|
| 35 |
- if err := srv.poolRemove("push", "test1"); err != nil {
|
|
| 36 |
- t.Fatal(err) |
|
| 37 |
- } |
|
| 38 |
- if err := srv.poolRemove("wait", "test3"); err == nil || err.Error() != "Unknown pool type" {
|
|
| 39 |
- t.Fatalf("Expected `Unknown pool type`")
|
|
| 40 |
- } |
|
| 41 |
-} |