2014-08-08 02:58:58 -04:00
|
|
|
package graph
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
|
|
|
"path"
|
2014-12-18 04:38:31 -05:00
|
|
|
"sync"
|
2014-08-08 02:58:58 -04:00
|
|
|
|
2014-10-24 18:11:48 -04:00
|
|
|
log "github.com/Sirupsen/logrus"
|
2014-08-08 02:58:58 -04:00
|
|
|
"github.com/docker/docker/engine"
|
2014-09-30 02:23:36 -04:00
|
|
|
"github.com/docker/docker/pkg/archive"
|
2014-08-08 02:58:58 -04:00
|
|
|
"github.com/docker/docker/registry"
|
|
|
|
"github.com/docker/docker/utils"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Retrieve the all the images to be uploaded in the correct order
|
|
|
|
func (s *TagStore) getImageList(localRepo map[string]string, requestedTag string) ([]string, map[string][]string, error) {
|
|
|
|
var (
|
|
|
|
imageList []string
|
2014-08-29 07:21:28 -04:00
|
|
|
imagesSeen = make(map[string]bool)
|
|
|
|
tagsByImage = make(map[string][]string)
|
2014-08-08 02:58:58 -04:00
|
|
|
)
|
|
|
|
|
|
|
|
for tag, id := range localRepo {
|
|
|
|
if requestedTag != "" && requestedTag != tag {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
var imageListForThisTag []string
|
|
|
|
|
|
|
|
tagsByImage[id] = append(tagsByImage[id], tag)
|
|
|
|
|
|
|
|
for img, err := s.graph.Get(id); img != nil; img, err = img.GetParent() {
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if imagesSeen[img.ID] {
|
|
|
|
// This image is already on the list, we can ignore it and all its parents
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
imagesSeen[img.ID] = true
|
|
|
|
imageListForThisTag = append(imageListForThisTag, img.ID)
|
|
|
|
}
|
|
|
|
|
|
|
|
// reverse the image list for this tag (so the "most"-parent image is first)
|
|
|
|
for i, j := 0, len(imageListForThisTag)-1; i < j; i, j = i+1, j-1 {
|
|
|
|
imageListForThisTag[i], imageListForThisTag[j] = imageListForThisTag[j], imageListForThisTag[i]
|
|
|
|
}
|
|
|
|
|
|
|
|
// append to main image list
|
|
|
|
imageList = append(imageList, imageListForThisTag...)
|
|
|
|
}
|
|
|
|
if len(imageList) == 0 {
|
|
|
|
return nil, nil, fmt.Errorf("No images found for the requested repository / tag")
|
|
|
|
}
|
2014-07-24 16:37:44 -04:00
|
|
|
log.Debugf("Image list: %v", imageList)
|
|
|
|
log.Debugf("Tags by image: %v", tagsByImage)
|
2014-08-08 02:58:58 -04:00
|
|
|
|
|
|
|
return imageList, tagsByImage, nil
|
|
|
|
}
|
|
|
|
|
2014-10-06 21:54:52 -04:00
|
|
|
func (s *TagStore) pushRepository(r *registry.Session, out io.Writer, repoInfo *registry.RepositoryInfo, localRepo map[string]string, tag string, sf *utils.StreamFormatter) error {
|
2014-08-08 02:58:58 -04:00
|
|
|
out = utils.NewWriteFlusher(out)
|
2014-07-24 16:37:44 -04:00
|
|
|
log.Debugf("Local repo: %s", localRepo)
|
2014-08-08 02:58:58 -04:00
|
|
|
imgList, tagsByImage, err := s.getImageList(localRepo, tag)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
out.Write(sf.FormatStatus("", "Sending image list"))
|
|
|
|
|
|
|
|
var (
|
|
|
|
repoData *registry.RepositoryData
|
|
|
|
imageIndex []*registry.ImgData
|
|
|
|
)
|
|
|
|
|
|
|
|
for _, imgId := range imgList {
|
|
|
|
if tags, exists := tagsByImage[imgId]; exists {
|
|
|
|
// If an image has tags you must add an entry in the image index
|
|
|
|
// for each tag
|
|
|
|
for _, tag := range tags {
|
|
|
|
imageIndex = append(imageIndex, ®istry.ImgData{
|
|
|
|
ID: imgId,
|
|
|
|
Tag: tag,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// If the image does not have a tag it still needs to be sent to the
|
|
|
|
// registry with an empty tag so that it is accociated with the repository
|
|
|
|
imageIndex = append(imageIndex, ®istry.ImgData{
|
|
|
|
ID: imgId,
|
|
|
|
Tag: "",
|
|
|
|
})
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-08-13 21:36:26 -04:00
|
|
|
log.Debugf("Preparing to push %s with the following images and tags", localRepo)
|
2014-08-08 02:58:58 -04:00
|
|
|
for _, data := range imageIndex {
|
2014-08-13 21:36:26 -04:00
|
|
|
log.Debugf("Pushing ID: %s with Tag: %s", data.ID, data.Tag)
|
2014-08-08 02:58:58 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// Register all the images in a repository with the registry
|
|
|
|
// If an image is not in this list it will not be associated with the repository
|
2014-10-06 21:54:52 -04:00
|
|
|
repoData, err = r.PushImageJSONIndex(repoInfo.RemoteName, imageIndex, false, nil)
|
2014-08-08 02:58:58 -04:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
nTag := 1
|
|
|
|
if tag == "" {
|
|
|
|
nTag = len(localRepo)
|
|
|
|
}
|
2014-12-18 04:38:31 -05:00
|
|
|
var wg sync.WaitGroup
|
2014-12-17 17:17:14 -05:00
|
|
|
needsPush := make([]bool, len(imgList))
|
2014-08-08 02:58:58 -04:00
|
|
|
for _, ep := range repoData.Endpoints {
|
2014-10-06 21:54:52 -04:00
|
|
|
out.Write(sf.FormatStatus("", "Pushing repository %s (%d tags)", repoInfo.CanonicalName, nTag))
|
2014-12-17 17:17:14 -05:00
|
|
|
|
|
|
|
for i, imgId := range imgList {
|
2014-12-18 04:38:31 -05:00
|
|
|
wg.Add(1)
|
2014-12-17 17:17:14 -05:00
|
|
|
go func(i int, imgId string) {
|
2014-12-18 04:38:31 -05:00
|
|
|
defer wg.Done()
|
2014-12-17 17:17:14 -05:00
|
|
|
if err := r.LookupRemoteImage(imgId, ep, repoData.Tokens); err == nil {
|
|
|
|
out.Write(sf.FormatStatus("", "Image %s already pushed, skipping", utils.TruncateID(imgId)))
|
|
|
|
needsPush[i] = false
|
|
|
|
} else {
|
|
|
|
log.Errorf("Error in LookupRemoteImage: %s", err)
|
|
|
|
out.Write(sf.FormatStatus("", "Image %s not pushed, adding to queue", utils.TruncateID(imgId)))
|
|
|
|
needsPush[i] = true
|
|
|
|
}
|
|
|
|
}(i, imgId)
|
|
|
|
}
|
2014-12-18 04:38:31 -05:00
|
|
|
|
|
|
|
wg.Wait()
|
|
|
|
|
2014-12-17 17:17:14 -05:00
|
|
|
for i, imgId := range imgList {
|
|
|
|
if needsPush[i] {
|
|
|
|
if _, err := s.pushImage(r, out, remoteName, imgId, ep, repoData.Tokens, sf); err != nil {
|
2014-08-08 02:58:58 -04:00
|
|
|
// FIXME: Continue on error?
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2014-12-17 17:17:14 -05:00
|
|
|
|
2014-08-08 02:58:58 -04:00
|
|
|
for _, tag := range tagsByImage[imgId] {
|
2014-10-06 21:54:52 -04:00
|
|
|
out.Write(sf.FormatStatus("", "Pushing tag for rev [%s] on {%s}", utils.TruncateID(imgId), ep+"repositories/"+repoInfo.RemoteName+"/tags/"+tag))
|
2014-08-08 02:58:58 -04:00
|
|
|
|
2014-10-06 21:54:52 -04:00
|
|
|
if err := r.PushRegistryTag(repoInfo.RemoteName, imgId, tag, ep, repoData.Tokens); err != nil {
|
2014-08-08 02:58:58 -04:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-10-06 21:54:52 -04:00
|
|
|
if _, err := r.PushImageJSONIndex(repoInfo.RemoteName, imageIndex, true, repoData.Endpoints); err != nil {
|
2014-08-08 02:58:58 -04:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2014-10-06 21:54:52 -04:00
|
|
|
func (s *TagStore) pushImage(r *registry.Session, out io.Writer, imgID, ep string, token []string, sf *utils.StreamFormatter) (checksum string, err error) {
|
2014-08-08 02:58:58 -04:00
|
|
|
out = utils.NewWriteFlusher(out)
|
|
|
|
jsonRaw, err := ioutil.ReadFile(path.Join(s.graph.Root, imgID, "json"))
|
|
|
|
if err != nil {
|
|
|
|
return "", fmt.Errorf("Cannot retrieve the path for {%s}: %s", imgID, err)
|
|
|
|
}
|
|
|
|
out.Write(sf.FormatProgress(utils.TruncateID(imgID), "Pushing", nil))
|
|
|
|
|
|
|
|
imgData := ®istry.ImgData{
|
|
|
|
ID: imgID,
|
|
|
|
}
|
|
|
|
|
|
|
|
// Send the json
|
|
|
|
if err := r.PushImageJSONRegistry(imgData, jsonRaw, ep, token); err != nil {
|
|
|
|
if err == registry.ErrAlreadyExists {
|
|
|
|
out.Write(sf.FormatProgress(utils.TruncateID(imgData.ID), "Image already pushed, skipping", nil))
|
|
|
|
return "", nil
|
|
|
|
}
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
layerData, err := s.graph.TempLayerArchive(imgID, archive.Uncompressed, sf, out)
|
|
|
|
if err != nil {
|
|
|
|
return "", fmt.Errorf("Failed to generate layer archive: %s", err)
|
|
|
|
}
|
|
|
|
defer os.RemoveAll(layerData.Name())
|
|
|
|
|
|
|
|
// Send the layer
|
2014-07-24 16:37:44 -04:00
|
|
|
log.Debugf("rendered layer for %s of [%d] size", imgData.ID, layerData.Size)
|
2014-08-08 02:58:58 -04:00
|
|
|
|
|
|
|
checksum, checksumPayload, err := r.PushImageLayerRegistry(imgData.ID, utils.ProgressReader(layerData, int(layerData.Size), out, sf, false, utils.TruncateID(imgData.ID), "Pushing"), ep, token, jsonRaw)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
imgData.Checksum = checksum
|
|
|
|
imgData.ChecksumPayload = checksumPayload
|
|
|
|
// Send the checksum
|
|
|
|
if err := r.PushImageChecksumRegistry(imgData, ep, token); err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
out.Write(sf.FormatProgress(utils.TruncateID(imgData.ID), "Image successfully pushed", nil))
|
|
|
|
return imgData.Checksum, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// FIXME: Allow to interrupt current push when new push of same image is done.
|
|
|
|
func (s *TagStore) CmdPush(job *engine.Job) engine.Status {
|
|
|
|
if n := len(job.Args); n != 1 {
|
|
|
|
return job.Errorf("Usage: %s IMAGE", job.Name)
|
|
|
|
}
|
|
|
|
var (
|
|
|
|
localName = job.Args[0]
|
|
|
|
sf = utils.NewStreamFormatter(job.GetenvBool("json"))
|
|
|
|
authConfig = ®istry.AuthConfig{}
|
|
|
|
metaHeaders map[string][]string
|
|
|
|
)
|
|
|
|
|
2014-10-06 21:54:52 -04:00
|
|
|
// Resolve the Repository name from fqn to RepositoryInfo
|
|
|
|
repoInfo, err := registry.ResolveRepositoryInfo(job, localName)
|
|
|
|
if err != nil {
|
|
|
|
return job.Error(err)
|
|
|
|
}
|
|
|
|
|
2014-08-08 02:58:58 -04:00
|
|
|
tag := job.Getenv("tag")
|
|
|
|
job.GetenvJson("authConfig", authConfig)
|
|
|
|
job.GetenvJson("metaHeaders", &metaHeaders)
|
|
|
|
|
2014-10-06 21:54:52 -04:00
|
|
|
if _, err := s.poolAdd("push", repoInfo.LocalName); err != nil {
|
2014-08-08 02:58:58 -04:00
|
|
|
return job.Error(err)
|
|
|
|
}
|
2014-10-06 21:54:52 -04:00
|
|
|
defer s.poolRemove("push", repoInfo.LocalName)
|
2014-08-08 02:58:58 -04:00
|
|
|
|
2014-10-06 21:54:52 -04:00
|
|
|
endpoint, err := repoInfo.GetEndpoint()
|
2014-08-08 02:58:58 -04:00
|
|
|
if err != nil {
|
|
|
|
return job.Error(err)
|
|
|
|
}
|
|
|
|
|
2014-10-06 21:54:52 -04:00
|
|
|
img, err := s.graph.Get(repoInfo.LocalName)
|
2014-08-07 10:43:06 -04:00
|
|
|
r, err2 := registry.NewSession(authConfig, registry.HTTPRequestFactory(metaHeaders), endpoint, false)
|
2014-08-08 02:58:58 -04:00
|
|
|
if err2 != nil {
|
|
|
|
return job.Error(err2)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
reposLen := 1
|
|
|
|
if tag == "" {
|
2014-10-06 21:54:52 -04:00
|
|
|
reposLen = len(s.Repositories[repoInfo.LocalName])
|
2014-08-08 02:58:58 -04:00
|
|
|
}
|
2014-10-06 21:54:52 -04:00
|
|
|
job.Stdout.Write(sf.FormatStatus("", "The push refers to a repository [%s] (len: %d)", repoInfo.CanonicalName, reposLen))
|
2014-08-08 02:58:58 -04:00
|
|
|
// If it fails, try to get the repository
|
2014-10-06 21:54:52 -04:00
|
|
|
if localRepo, exists := s.Repositories[repoInfo.LocalName]; exists {
|
|
|
|
if err := s.pushRepository(r, job.Stdout, repoInfo, localRepo, tag, sf); err != nil {
|
2014-08-08 02:58:58 -04:00
|
|
|
return job.Error(err)
|
|
|
|
}
|
|
|
|
return engine.StatusOK
|
|
|
|
}
|
|
|
|
return job.Error(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
var token []string
|
2014-10-06 21:54:52 -04:00
|
|
|
job.Stdout.Write(sf.FormatStatus("", "The push refers to an image: [%s]", repoInfo.CanonicalName))
|
|
|
|
if _, err := s.pushImage(r, job.Stdout, img.ID, endpoint.String(), token, sf); err != nil {
|
2014-08-08 02:58:58 -04:00
|
|
|
return job.Error(err)
|
|
|
|
}
|
|
|
|
return engine.StatusOK
|
|
|
|
}
|