diff --git a/commands.go b/commands.go index b7f92b3a78..c5e321e229 100644 --- a/commands.go +++ b/commands.go @@ -8,10 +8,12 @@ import ( "github.com/dotcloud/docker/auth" "github.com/dotcloud/docker/rcli" "io" + "io/ioutil" "log" "math/rand" "net/http" "net/url" + "path" "runtime" "strconv" "strings" @@ -21,6 +23,7 @@ import ( ) const VERSION = "0.0.3" +const REGISTRY_ENDPOINT = "http://registry-creack.dotcloud.com/v1" func (srv *Server) Name() string { return "docker" @@ -421,6 +424,209 @@ func (srv *Server) CmdImport(stdin io.ReadCloser, stdout io.Writer, args ...stri return nil } +func (srv *Server) CmdPush(stdin io.ReadCloser, stdout io.Writer, args ...string) error { + cmd := rcli.Subcmd(stdout, "push", "[OPTIONS] IMAGE", "Push an image to the registry") + if err := cmd.Parse(args); err != nil { + return nil + } + if cmd.NArg() == 0 { + cmd.Usage() + return nil + } + + client := &http.Client{} + if img, err := srv.runtime.graph.Get(cmd.Arg(0)); err != nil { + return nil + } else { + img.WalkHistory(func(img *Image) { + fmt.Fprintf(stdout, "Pushing %s\n", img.Id) + + jsonRaw, err := ioutil.ReadFile(path.Join(srv.runtime.graph.Root, img.Id, "json")) + if err != nil { + fmt.Fprintf(stdout, "Error while retreiving the path for {%s}: %s\n", img.Id, err) + return + } + jsonData := strings.NewReader(string(jsonRaw)) + req, err := http.NewRequest("PUT", REGISTRY_ENDPOINT+"/images/"+img.Id+"/json", jsonData) + res, err := client.Do(req) + if err != nil || res.StatusCode != 200 { + if res == nil { + fmt.Fprintf(stdout, + "Error: Internal server error trying to push image {%s} (json): %s\n", + img.Id, err) + return + } + switch res.StatusCode { + case 204: + fmt.Fprintf(stdout, "Image already on the repository\n") + return + case 400: + fmt.Fprintf(stdout, "Error: Invalid Json\n") + return + default: + fmt.Fprintf(stdout, + "Error: Internal server error trying to push image {%s} (json): %s (%d)\n", + img.Id, err, res.StatusCode) + return + } + } + + req2, err := http.NewRequest("PUT", REGISTRY_ENDPOINT+"/images/"+img.Id+"/layer", nil) + res2, err := client.Do(req2) + if err != nil || res2.StatusCode != 307 { + fmt.Fprintf(stdout, + "Error trying to push image {%s} (layer 1): %s\n", + img.Id, err) + return + } + url, err := res2.Location() + if err != nil || url == nil { + fmt.Fprintf(stdout, + "Fail to retrieve layer storage URL for image {%s}: %s\n", + img.Id, err) + return + } + // FIXME: Don't do this :D. Check the S3 requierement and implement chunks of 5MB + layerData2, err := Tar(path.Join(srv.runtime.graph.Root, img.Id, "layer"), Gzip) + layerData, err := Tar(path.Join(srv.runtime.graph.Root, img.Id, "layer"), Gzip) + if err != nil { + fmt.Fprintf(stdout, + "Error while retrieving layer for {%s}: %s\n", + img.Id, err) + return + } + req3, err := http.NewRequest("PUT", url.String(), layerData) + tmp, _ := ioutil.ReadAll(layerData2) + req3.ContentLength = int64(len(tmp)) + + req3.TransferEncoding = []string{"none"} + res3, err := client.Do(req3) + if err != nil || res3.StatusCode != 200 { + if res3 == nil { + fmt.Fprintf(stdout, + "Error trying to push image {%s} (layer 2): %s\n", + img.Id, err) + } else { + fmt.Fprintf(stdout, + "Error trying to push image {%s} (layer 2): %s (%d)\n", + img.Id, err, res3.StatusCode) + } + return + } + }) + } + return nil +} + +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 +} + +func newMultipleImgJson(src []byte) (map[*Image]Archive, error) { + ret := map[*Image]Archive{} + + fmt.Printf("Json string2: {%s}\n", src) + 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[m] = nil + } + return ret, nil +} + +func getHistory(base_uri, id string) (map[*Image]Archive, error) { + res, err := http.Get(base_uri + id + "/history") + if err != nil { + return nil, fmt.Errorf("Error while getting from the server: %s\n", err) + } + 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 +} + +func getRemoteImage(base_uri, id string) (*Image, Archive, error) { + // Get the Json + res, err := http.Get(base_uri + id + "/json") + if err != nil { + return nil, nil, fmt.Errorf("Error while getting from the server: %s\n", err) + } + 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 = id + + // Get the layer + res, err = http.Get(base_uri + id + "/layer") + if err != nil { + return nil, nil, fmt.Errorf("Error while getting from the server: %s\n", err) + } + return img, res.Body, nil +} + +func (srv *Server) CmdPulli(stdin io.ReadCloser, stdout io.Writer, args ...string) error { + cmd := rcli.Subcmd(stdout, "pulli", "[OPTIONS] IMAGE", "Pull an image from the registry") + if err := cmd.Parse(args); err != nil { + return nil + } + if cmd.NArg() == 0 { + cmd.Usage() + return nil + } + + // First, retrieve the history + base_uri := REGISTRY_ENDPOINT + "/images/" + + // Now we have the history, remove the images we already have + history, err := getHistory(base_uri, cmd.Arg(0)) + if err != nil { + return err + } + for j := range history { + if !srv.runtime.graph.Exists(j.Id) { + img, layer, err := getRemoteImage(base_uri, j.Id) + if err != nil { + // FIXME: Keep goging in case of error? + return err + } + if err = srv.runtime.graph.Register(layer, img); err != nil { + return err + } + } + } + return nil +} + func (srv *Server) CmdImages(stdin io.ReadCloser, stdout io.Writer, args ...string) error { cmd := rcli.Subcmd(stdout, "images", "[OPTIONS] [NAME]", "List images") //limit := cmd.Int("l", 0, "Only show the N most recent versions of each image") @@ -475,7 +681,7 @@ func (srv *Server) CmdImages(stdin io.ReadCloser, stdout io.Writer, args ...stri } } } - // Display images which aren't part of a + // Display images which aren't part of a if nameFilter == "" { for id, image := range allImages { if !*quiet { diff --git a/image.go b/image.go index 84935e3996..546237c983 100644 --- a/image.go +++ b/image.go @@ -15,12 +15,12 @@ import ( ) type Image struct { - Id string - Parent string - Comment string - Created time.Time - ParentContainer string - ParentCommand []string + Id string `json:"id"` + Parent string `json:"parent,omitempty"` + Comment string `json:"comment,omitempty"` + Created time.Time `json:"created"` + ParentContainer string `json:-` + ParentCommand []string `json:-` graph *Graph }