2013-03-21 13:10:14 -04:00
|
|
|
package docker
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
2013-03-22 04:25:27 -04:00
|
|
|
"github.com/dotcloud/docker/auth"
|
2013-03-21 13:10:14 -04:00
|
|
|
"io"
|
|
|
|
"io/ioutil"
|
|
|
|
"net/http"
|
|
|
|
"path"
|
|
|
|
"strings"
|
|
|
|
)
|
|
|
|
|
|
|
|
//FIXME: Set the endpoint in a conf file or via commandline
|
|
|
|
//const REGISTRY_ENDPOINT = "http://registry-creack.dotcloud.com/v1"
|
2013-03-22 04:25:27 -04:00
|
|
|
const REGISTRY_ENDPOINT = auth.REGISTRY_SERVER + "/v1"
|
2013-03-21 13:10:14 -04:00
|
|
|
|
|
|
|
// Build an Image object from raw json data
|
|
|
|
func NewImgJson(src []byte) (*Image, error) {
|
|
|
|
ret := &Image{}
|
|
|
|
|
|
|
|
fmt.Printf("Json string: {%s}\n", src)
|
|
|
|
// FIXME: Is there a cleaner way to "puryfy" the input json?
|
|
|
|
src = []byte(strings.Replace(string(src), "null", "\"\"", -1))
|
|
|
|
|
|
|
|
if err := json.Unmarshal(src, ret); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return ret, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Build an Image object list from a raw json data
|
|
|
|
// FIXME: Do this in "stream" mode
|
|
|
|
func NewMultipleImgJson(src []byte) ([]*Image, error) {
|
|
|
|
ret := []*Image{}
|
|
|
|
|
|
|
|
dec := json.NewDecoder(strings.NewReader(strings.Replace(string(src), "null", "\"\"", -1)))
|
|
|
|
for {
|
|
|
|
m := &Image{}
|
|
|
|
if err := dec.Decode(m); err == io.EOF {
|
|
|
|
break
|
|
|
|
} else if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
ret = append(ret, m)
|
|
|
|
}
|
|
|
|
return ret, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Retrieve the history of a given image from the Registry.
|
|
|
|
// Return a list of the parent's json (requested image included)
|
2013-03-22 05:57:28 -04:00
|
|
|
func (graph *Graph) getRemoteHistory(imgId string, authConfig *auth.AuthConfig) ([]*Image, error) {
|
|
|
|
client := &http.Client{}
|
|
|
|
|
|
|
|
req, err := http.NewRequest("GET", REGISTRY_ENDPOINT+"/images/"+imgId+"/history", nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
req.SetBasicAuth(authConfig.Username, authConfig.Password)
|
|
|
|
res, err := client.Do(req)
|
2013-03-22 07:44:07 -04:00
|
|
|
if err != nil || res.StatusCode != 200 {
|
|
|
|
if res != nil {
|
|
|
|
return nil, fmt.Errorf("Internal server error: %d trying to fetch remote history for %s", res.StatusCode, imgId)
|
|
|
|
}
|
2013-03-22 05:57:28 -04:00
|
|
|
return nil, err
|
2013-03-21 13:10:14 -04:00
|
|
|
}
|
|
|
|
defer res.Body.Close()
|
|
|
|
|
|
|
|
jsonString, err := ioutil.ReadAll(res.Body)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("Error while reading the http response: %s\n", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
history, err := NewMultipleImgJson(jsonString)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("Error while parsing the json: %s\n", err)
|
|
|
|
}
|
|
|
|
return history, nil
|
|
|
|
}
|
|
|
|
|
2013-03-22 04:25:27 -04:00
|
|
|
// Check if an image exists in the Registry
|
2013-03-22 05:57:28 -04:00
|
|
|
func (graph *Graph) LookupRemoteImage(imgId string, authConfig *auth.AuthConfig) bool {
|
2013-03-22 13:22:22 -04:00
|
|
|
rt := &http.Transport{Proxy: http.ProxyFromEnvironment}
|
2013-03-22 05:57:28 -04:00
|
|
|
|
|
|
|
req, err := http.NewRequest("GET", REGISTRY_ENDPOINT+"/images/"+imgId+"/json", nil)
|
|
|
|
if err != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
req.SetBasicAuth(authConfig.Username, authConfig.Password)
|
2013-03-22 13:22:22 -04:00
|
|
|
res, err := rt.RoundTrip(req)
|
2013-03-22 07:44:07 -04:00
|
|
|
if err != nil || res.StatusCode != 307 {
|
2013-03-22 04:25:27 -04:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
return res.StatusCode == 307
|
|
|
|
}
|
|
|
|
|
2013-03-21 13:10:14 -04:00
|
|
|
// Retrieve an image from the Registry.
|
|
|
|
// Returns the Image object as well as the layer as an Archive (io.Reader)
|
2013-03-22 05:57:28 -04:00
|
|
|
func (graph *Graph) getRemoteImage(imgId string, authConfig *auth.AuthConfig) (*Image, Archive, error) {
|
|
|
|
client := &http.Client{}
|
|
|
|
|
2013-03-21 13:10:14 -04:00
|
|
|
// Get the Json
|
2013-03-22 05:57:28 -04:00
|
|
|
req, err := http.NewRequest("GET", REGISTRY_ENDPOINT+"/images/"+imgId+"/json", nil)
|
2013-03-21 13:10:14 -04:00
|
|
|
if err != nil {
|
|
|
|
return nil, nil, fmt.Errorf("Error while getting from the server: %s\n", err)
|
|
|
|
}
|
2013-03-22 05:57:28 -04:00
|
|
|
req.SetBasicAuth(authConfig.Username, authConfig.Password)
|
|
|
|
res, err := client.Do(req)
|
2013-03-22 10:56:44 -04:00
|
|
|
if err != nil || res.StatusCode != 200 {
|
2013-03-22 07:44:07 -04:00
|
|
|
if res != nil {
|
|
|
|
return nil, nil, fmt.Errorf("Internal server error: %d trying to get image %s", res.StatusCode, imgId)
|
|
|
|
}
|
2013-03-22 05:57:28 -04:00
|
|
|
return nil, nil, err
|
|
|
|
}
|
2013-03-21 13:10:14 -04:00
|
|
|
defer res.Body.Close()
|
|
|
|
|
|
|
|
jsonString, err := ioutil.ReadAll(res.Body)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, fmt.Errorf("Error while reading the http response: %s\n", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
img, err := NewImgJson(jsonString)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, fmt.Errorf("Error while parsing the json: %s\n", err)
|
|
|
|
}
|
|
|
|
img.Id = imgId
|
|
|
|
|
|
|
|
// Get the layer
|
2013-03-22 10:56:44 -04:00
|
|
|
req, err = http.NewRequest("GET", REGISTRY_ENDPOINT+"/images/"+imgId+"/layer", nil)
|
2013-03-21 13:10:14 -04:00
|
|
|
if err != nil {
|
|
|
|
return nil, nil, fmt.Errorf("Error while getting from the server: %s\n", err)
|
|
|
|
}
|
2013-03-22 10:56:44 -04:00
|
|
|
req.SetBasicAuth(authConfig.Username, authConfig.Password)
|
|
|
|
res, err = client.Do(req)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
2013-03-21 13:10:14 -04:00
|
|
|
return img, res.Body, nil
|
|
|
|
}
|
|
|
|
|
2013-03-22 05:57:28 -04:00
|
|
|
func (graph *Graph) PullImage(imgId string, authConfig *auth.AuthConfig) error {
|
|
|
|
history, err := graph.getRemoteHistory(imgId, authConfig)
|
2013-03-21 13:10:14 -04:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
// FIXME: Try to stream the images?
|
|
|
|
// FIXME: Lunch the getRemoteImage() in goroutines
|
|
|
|
for _, j := range history {
|
|
|
|
if !graph.Exists(j.Id) {
|
2013-03-22 05:57:28 -04:00
|
|
|
img, layer, err := graph.getRemoteImage(j.Id, authConfig)
|
2013-03-21 13:10:14 -04:00
|
|
|
if err != nil {
|
|
|
|
// FIXME: Keep goging in case of error?
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err = graph.Register(layer, img); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2013-03-22 04:25:27 -04:00
|
|
|
// FIXME: Handle the askedTag parameter
|
2013-03-22 10:56:44 -04:00
|
|
|
func (graph *Graph) PullRepository(remote, askedTag string, repositories *TagStore, authConfig *auth.AuthConfig) error {
|
2013-03-22 04:25:27 -04:00
|
|
|
client := &http.Client{}
|
|
|
|
|
2013-03-22 10:56:44 -04:00
|
|
|
fmt.Printf("Pulling repo: %s\n", REGISTRY_ENDPOINT+"/users/"+remote)
|
|
|
|
|
|
|
|
req, err := http.NewRequest("GET", REGISTRY_ENDPOINT+"/users/"+remote, nil)
|
2013-03-22 04:25:27 -04:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2013-03-22 05:19:39 -04:00
|
|
|
req.SetBasicAuth(authConfig.Username, authConfig.Password)
|
2013-03-22 04:25:27 -04:00
|
|
|
res, err := client.Do(req)
|
2013-03-22 07:44:07 -04:00
|
|
|
if err != nil || res.StatusCode != 200 {
|
|
|
|
if res != nil {
|
2013-03-22 10:56:44 -04:00
|
|
|
return fmt.Errorf("Internal server error: %d trying to pull %s", res.StatusCode, remote)
|
2013-03-22 07:44:07 -04:00
|
|
|
}
|
2013-03-22 04:25:27 -04:00
|
|
|
return err
|
|
|
|
}
|
2013-03-22 05:57:28 -04:00
|
|
|
defer res.Body.Close()
|
2013-03-22 04:25:27 -04:00
|
|
|
rawJson, err := ioutil.ReadAll(res.Body)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
t := map[string]string{}
|
|
|
|
if err = json.Unmarshal(rawJson, &t); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
for tag, rev := range t {
|
2013-03-22 05:57:28 -04:00
|
|
|
if err = graph.PullImage(rev, authConfig); err != nil {
|
2013-03-22 04:25:27 -04:00
|
|
|
return err
|
|
|
|
}
|
2013-03-22 10:56:44 -04:00
|
|
|
if err = repositories.Set(remote, tag, rev, true); err != nil {
|
2013-03-22 04:25:27 -04:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if err = repositories.Save(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2013-03-21 13:10:14 -04:00
|
|
|
// Push a local image to the registry with its history if needed
|
2013-03-22 05:57:28 -04:00
|
|
|
func (graph *Graph) PushImage(imgOrig *Image, authConfig *auth.AuthConfig) error {
|
2013-03-21 13:10:14 -04:00
|
|
|
client := &http.Client{}
|
|
|
|
|
|
|
|
// FIXME: Factorize the code
|
|
|
|
// FIXME: Do the puts in goroutines
|
|
|
|
if err := imgOrig.WalkHistory(func(img *Image) error {
|
|
|
|
|
|
|
|
jsonRaw, err := ioutil.ReadFile(path.Join(graph.Root, img.Id, "json"))
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("Error while retreiving the path for {%s}: %s", img.Id, err)
|
|
|
|
}
|
2013-03-22 14:44:12 -04:00
|
|
|
|
|
|
|
Debugf("Pushing image [%s] on {%s}\n", img.Id, REGISTRY_ENDPOINT+"/images/"+img.Id+"/json")
|
|
|
|
|
2013-03-22 04:25:27 -04:00
|
|
|
// FIXME: try json with UTF8
|
2013-03-21 13:10:14 -04:00
|
|
|
jsonData := strings.NewReader(string(jsonRaw))
|
|
|
|
req, err := http.NewRequest("PUT", REGISTRY_ENDPOINT+"/images/"+img.Id+"/json", jsonData)
|
2013-03-22 04:25:27 -04:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2013-03-22 06:22:36 -04:00
|
|
|
req.Header.Add("Content-type", "application/json")
|
2013-03-22 05:57:28 -04:00
|
|
|
req.SetBasicAuth(authConfig.Username, authConfig.Password)
|
2013-03-21 13:10:14 -04:00
|
|
|
res, err := client.Do(req)
|
|
|
|
if err != nil || res.StatusCode != 200 {
|
|
|
|
if res == nil {
|
|
|
|
return fmt.Errorf(
|
|
|
|
"Error: Internal server error trying to push image {%s} (json): %s",
|
|
|
|
img.Id, err)
|
|
|
|
}
|
2013-03-22 10:10:52 -04:00
|
|
|
fmt.Printf("Pushing return status: %d\n", res.StatusCode)
|
2013-03-21 13:10:14 -04:00
|
|
|
switch res.StatusCode {
|
|
|
|
case 204:
|
|
|
|
// Case where the image is already on the Registry
|
|
|
|
// FIXME: Do not be silent?
|
|
|
|
return nil
|
|
|
|
case 400:
|
|
|
|
return fmt.Errorf("Error: Invalid Json")
|
|
|
|
default:
|
|
|
|
return fmt.Errorf(
|
2013-03-22 07:44:07 -04:00
|
|
|
"Error: Internal server error: %d trying to push image {%s} (json): %s\n",
|
|
|
|
res.StatusCode, img.Id, err)
|
2013-03-21 13:10:14 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
req2, err := http.NewRequest("PUT", REGISTRY_ENDPOINT+"/images/"+img.Id+"/layer", nil)
|
2013-03-22 05:57:28 -04:00
|
|
|
req2.SetBasicAuth(authConfig.Username, authConfig.Password)
|
2013-03-21 13:10:14 -04:00
|
|
|
res2, err := client.Do(req2)
|
|
|
|
if err != nil || res2.StatusCode != 307 {
|
|
|
|
return fmt.Errorf(
|
2013-03-22 07:44:07 -04:00
|
|
|
"Internal server error trying to push image {%s} (layer 1): %s\n",
|
2013-03-21 13:10:14 -04:00
|
|
|
img.Id, err)
|
|
|
|
}
|
|
|
|
url, err := res2.Location()
|
|
|
|
if err != nil || url == nil {
|
|
|
|
return fmt.Errorf(
|
|
|
|
"Fail to retrieve layer storage URL for image {%s}: %s\n",
|
|
|
|
img.Id, err)
|
|
|
|
}
|
2013-03-22 14:44:12 -04:00
|
|
|
|
2013-03-21 13:10:14 -04:00
|
|
|
// FIXME: Don't do this :D. Check the S3 requierement and implement chunks of 5MB
|
|
|
|
// FIXME2: I won't stress it enough, DON'T DO THIS! very high priority
|
|
|
|
layerData2, err := Tar(path.Join(graph.Root, img.Id, "layer"), Gzip)
|
|
|
|
layerData, err := Tar(path.Join(graph.Root, img.Id, "layer"), Gzip)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf(
|
|
|
|
"Error while retrieving layer for {%s}: %s\n",
|
|
|
|
img.Id, err)
|
|
|
|
}
|
|
|
|
req3, err := http.NewRequest("PUT", url.String(), layerData)
|
2013-03-22 05:57:28 -04:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
tmp, err := ioutil.ReadAll(layerData2)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2013-03-21 13:10:14 -04:00
|
|
|
req3.ContentLength = int64(len(tmp))
|
|
|
|
|
|
|
|
req3.TransferEncoding = []string{"none"}
|
|
|
|
res3, err := client.Do(req3)
|
|
|
|
if err != nil || res3.StatusCode != 200 {
|
|
|
|
if res3 == nil {
|
|
|
|
return fmt.Errorf(
|
|
|
|
"Error trying to push image {%s} (layer 2): %s\n",
|
|
|
|
img.Id, err)
|
|
|
|
}
|
2013-03-22 07:44:07 -04:00
|
|
|
return fmt.Errorf(
|
|
|
|
"Error trying to push image {%s} (layer 2): %s (%d)\n",
|
|
|
|
img.Id, err, res3.StatusCode)
|
2013-03-21 13:10:14 -04:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
2013-03-22 04:25:27 -04:00
|
|
|
|
2013-03-22 09:38:54 -04:00
|
|
|
// push a tag on the registry.
|
|
|
|
// Remote has the format '<user>/<repo>
|
|
|
|
func (graph *Graph) pushTag(remote, revision, tag string, authConfig *auth.AuthConfig) error {
|
2013-03-22 04:25:27 -04:00
|
|
|
|
2013-03-22 09:38:54 -04:00
|
|
|
// Keep this for backward compatibility
|
2013-03-22 04:25:27 -04:00
|
|
|
if tag == "" {
|
|
|
|
tag = "lastest"
|
|
|
|
}
|
|
|
|
|
2013-03-22 09:38:54 -04:00
|
|
|
// "jsonify" the string
|
2013-03-22 04:25:27 -04:00
|
|
|
revision = "\"" + revision + "\""
|
|
|
|
|
2013-03-22 14:44:12 -04:00
|
|
|
Debugf("Pushing tags for rev [%s] on {%s}\n", revision, REGISTRY_ENDPOINT+"/users/"+remote+"/"+tag)
|
|
|
|
|
2013-03-22 04:25:27 -04:00
|
|
|
client := &http.Client{}
|
2013-03-22 09:38:54 -04:00
|
|
|
req, err := http.NewRequest("PUT", REGISTRY_ENDPOINT+"/users/"+remote+"/"+tag, strings.NewReader(revision))
|
2013-03-22 04:25:27 -04:00
|
|
|
req.Header.Add("Content-type", "application/json")
|
2013-03-22 05:57:28 -04:00
|
|
|
req.SetBasicAuth(authConfig.Username, authConfig.Password)
|
2013-03-22 04:25:27 -04:00
|
|
|
res, err := client.Do(req)
|
2013-03-22 07:44:07 -04:00
|
|
|
if err != nil || (res.StatusCode != 200 && res.StatusCode != 201) {
|
|
|
|
if res != nil {
|
2013-03-22 09:38:54 -04:00
|
|
|
return fmt.Errorf("Internal server error: %d trying to push tag %s on %s", res.StatusCode, tag, remote)
|
2013-03-22 07:44:07 -04:00
|
|
|
}
|
2013-03-22 04:25:27 -04:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
fmt.Printf("Result of push tag: %d\n", res.StatusCode)
|
|
|
|
switch res.StatusCode {
|
|
|
|
default:
|
|
|
|
return fmt.Errorf("Error %d\n", res.StatusCode)
|
|
|
|
case 200:
|
|
|
|
case 201:
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2013-03-22 13:42:13 -04:00
|
|
|
func (graph *Graph) LookupRemoteRepository(remote string, authConfig *auth.AuthConfig) bool {
|
|
|
|
rt := &http.Transport{Proxy: http.ProxyFromEnvironment}
|
|
|
|
|
|
|
|
req, err := http.NewRequest("GET", REGISTRY_ENDPOINT+"/users/"+remote, nil)
|
|
|
|
if err != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
req.SetBasicAuth(authConfig.Username, authConfig.Password)
|
|
|
|
res, err := rt.RoundTrip(req)
|
|
|
|
if err != nil || res.StatusCode != 200 {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
func (graph *Graph) pushPrimitive(remote, tag, imgId string, authConfig *auth.AuthConfig) error {
|
|
|
|
// CHeck if the local impage exists
|
|
|
|
img, err := graph.Get(imgId)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
// Push the image
|
|
|
|
if err = graph.PushImage(img, authConfig); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
// And then the tag
|
|
|
|
if err = graph.pushTag(remote, imgId, tag, authConfig); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2013-03-22 09:38:54 -04:00
|
|
|
// Push a repository to the registry.
|
|
|
|
// Remote has the format '<user>/<repo>
|
|
|
|
func (graph *Graph) PushRepository(remote string, localRepo Repository, authConfig *auth.AuthConfig) error {
|
2013-03-22 13:42:13 -04:00
|
|
|
// Check if the remote repository exists
|
2013-03-22 16:10:17 -04:00
|
|
|
// FIXME: @lopter How to handle this?
|
|
|
|
// if !graph.LookupRemoteRepository(remote, authConfig) {
|
|
|
|
// return fmt.Errorf("The remote repository %s does not exist\n", remote)
|
|
|
|
// }
|
2013-03-22 13:42:13 -04:00
|
|
|
|
|
|
|
// For each image within the repo, push them
|
2013-03-22 09:38:54 -04:00
|
|
|
for tag, imgId := range localRepo {
|
2013-03-22 13:42:13 -04:00
|
|
|
if err := graph.pushPrimitive(remote, tag, imgId, authConfig); err != nil {
|
|
|
|
// FIXME: Continue on error?
|
2013-03-22 04:25:27 -04:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|