func postImagePush(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } metaHeaders := map[string][]string{} for k, v := range r.Header { if strings.HasPrefix(k, "X-Meta-") { metaHeaders[k] = v } } if err := parseForm(r); err != nil { return err } authConfig := &cliconfig.AuthConfig{} output := ioutils.NewWriteFlusher(w) authEncoded := r.Header.Get("X-Registry-Auth") if authEncoded != "" { // the new format is to handle the authConfig as a header authJson := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded)) if err := json.NewDecoder(authJson).Decode(authConfig); err != nil { // to increase compatibility to existing api it is defaulting to be empty authConfig = &cliconfig.AuthConfig{} } } else { // the old format is supported for compatibility if there was no authConfig header if err := json.NewDecoder(r.Body).Decode(authConfig); err != nil { err = fmt.Errorf("Bad parameters and missing X-Registry-Auth: %v", err) sf := streamformatter.NewJSONStreamFormatter() output.Write(sf.FormatError(err)) return nil } } imagePushConfig := &types.ImagePushConfig{ MetaHeaders: metaHeaders, AuthConfig: authConfig, Tag: r.Form.Get("tag"), } w.Header().Set("Content-Type", "application/json") job := eng.Job("push", r.Form.Get("remote")) job.Stdout.Add(output) if err := job.SetenvJson("ImagePushConfig", imagePushConfig); err != nil { sf := streamformatter.NewJSONStreamFormatter() output.Write(sf.FormatError(err)) return nil } if err := job.Run(); err != nil { sf := streamformatter.NewJSONStreamFormatter() output.Write(sf.FormatError(err)) } return nil }
func (s *Server) getImagesGet(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } if err := parseForm(r); err != nil { return err } w.Header().Set("Content-Type", "application/x-tar") output := ioutils.NewWriteFlusher(w) imageExportConfig := &graph.ImageExportConfig{Outstream: output} if name, ok := vars["name"]; ok { imageExportConfig.Names = []string{name} } else { imageExportConfig.Names = r.Form["names"] } if err := s.daemon.Repositories().ImageExport(imageExportConfig); err != nil { if !output.Flushed() { return err } sf := streamformatter.NewJSONStreamFormatter() output.Write(sf.FormatError(err)) } return nil }
func TestLearnRegistryURL(t *testing.T) { options := Options{ Outstream: os.Stdout, } ic := NewImageC(options, streamformatter.NewJSONStreamFormatter()) s := httptest.NewServer( http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Docker-Distribution-API-Version", "registry/2.0") http.Error(w, "You shall not pass", http.StatusUnauthorized) })) defer s.Close() ic.Options.Registry = s.URL[7:] ic.Options.Image = Image ic.Options.Tag = Tag ic.Options.Timeout = DefaultHTTPTimeout // should fail _, err := LearnRegistryURL(ic.Options) if err == nil { t.Errorf(err.Error()) } // should pass ic.Options.InsecureAllowHTTP = true _, err = LearnRegistryURL(ic.Options) if err != nil { t.Errorf(err.Error()) } }
func TestParseReference(t *testing.T) { options := Options{ Outstream: os.Stdout, } ic := NewImageC(options, streamformatter.NewJSONStreamFormatter()) ic.Options.Reference = "busybox" if err := ic.ParseReference(); err != nil { t.Errorf(err.Error()) } ic.Options.Reference = "library/busybox" if err := ic.ParseReference(); err != nil { t.Errorf(err.Error()) } ic.Options.Reference = "library/busybox:latest" if err := ic.ParseReference(); err != nil { t.Errorf(err.Error()) } // should fail ic.Options.Reference = "library/busybox@invalid" if err := ic.ParseReference(); err == nil { t.Errorf(err.Error()) } }
func (s *imageRouter) getImagesGet(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := httputils.ParseForm(r); err != nil { return err } w.Header().Set("Content-Type", "application/x-tar") output := ioutils.NewWriteFlusher(w) defer output.Close() var names []string if name, ok := vars["name"]; ok { names = []string{name} } else { names = r.Form["names"] } if err := s.backend.ExportImage(names, output); err != nil { if !output.Flushed() { return err } sf := streamformatter.NewJSONStreamFormatter() output.Write(sf.FormatError(err)) } return nil }
func (s *router) getImagesGet(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } if err := httputils.ParseForm(r); err != nil { return err } w.Header().Set("Content-Type", "application/x-tar") output := ioutils.NewWriteFlusher(w) var names []string if name, ok := vars["name"]; ok { names = []string{name} } else { names = r.Form["names"] } if err := s.daemon.Repositories().ImageExport(names, output); err != nil { if !output.Flushed() { return err } sf := streamformatter.NewJSONStreamFormatter() output.Write(sf.FormatError(err)) } return nil }
func TestLearnAuthURL(t *testing.T) { options := Options{ Outstream: os.Stdout, } ic := NewImageC(options, streamformatter.NewJSONStreamFormatter()) s := httptest.NewServer( http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("www-authenticate", "Bearer realm=\"https://auth.docker.io/token\",service=\"registry.docker.io\",scope=\"repository:library/photon:pull\"") http.Error(w, "You shall not pass", http.StatusUnauthorized) })) defer s.Close() ic.Options.Registry = s.URL ic.Options.Image = Image ic.Options.Tag = Tag ic.Options.Timeout = DefaultHTTPTimeout url, err := LearnAuthURL(ic.Options) if err != nil { t.Errorf(err.Error()) } if url.String() != "https://auth.docker.io/token?scope=repository%3Alibrary%2Fphoton%3Apull&service=registry.docker.io" { t.Errorf("Returned url %s is different than expected", url) } }
// FIXME: Allow to interrupt current push when new push of same image is done. func (s *TagStore) Push(localName string, imagePushConfig *ImagePushConfig) error { var ( sf = streamformatter.NewJSONStreamFormatter() ) // Resolve the Repository name from fqn to RepositoryInfo repoInfo, err := s.registryService.ResolveRepository(localName) if err != nil { return err } if _, err := s.poolAdd("push", repoInfo.LocalName); err != nil { return err } defer s.poolRemove("push", repoInfo.LocalName) endpoint, err := repoInfo.GetEndpoint() if err != nil { return err } r, err := registry.NewSession(imagePushConfig.AuthConfig, registry.HTTPRequestFactory(imagePushConfig.MetaHeaders), endpoint, false) if err != nil { return err } reposLen := 1 if imagePushConfig.Tag == "" { reposLen = len(s.Repositories[repoInfo.LocalName]) } imagePushConfig.OutStream.Write(sf.FormatStatus("", "The push refers to a repository [%s] (len: %d)", repoInfo.CanonicalName, reposLen)) // If it fails, try to get the repository localRepo, exists := s.Repositories[repoInfo.LocalName] if !exists { return fmt.Errorf("Repository does not exist: %s", repoInfo.LocalName) } if repoInfo.Index.Official || endpoint.Version == registry.APIVersion2 { err := s.pushV2Repository(r, localRepo, imagePushConfig.OutStream, repoInfo, imagePushConfig.Tag, sf) if err == nil { s.eventsService.Log("push", repoInfo.LocalName, "") return nil } if err != ErrV2RegistryUnavailable { return fmt.Errorf("Error pushing to registry: %s", err) } } if err := s.pushRepository(r, imagePushConfig.OutStream, repoInfo, localRepo, imagePushConfig.Tag, sf); err != nil { return err } s.eventsService.Log("push", repoInfo.LocalName, "") return nil }
// Push initiates a push operation on the repository named localName. func (s *TagStore) Push(localName string, imagePushConfig *ImagePushConfig) error { // FIXME: Allow to interrupt current push when new push of same image is done. var sf = streamformatter.NewJSONStreamFormatter() // Resolve the Repository name from fqn to RepositoryInfo repoInfo, err := s.registryService.ResolveRepository(localName) if err != nil { return err } endpoints, err := s.registryService.LookupPushEndpoints(repoInfo.CanonicalName) if err != nil { return err } reposLen := 1 if imagePushConfig.Tag == "" { reposLen = len(s.Repositories[repoInfo.LocalName]) } imagePushConfig.OutStream.Write(sf.FormatStatus("", "The push refers to a repository [%s] (len: %d)", repoInfo.CanonicalName, reposLen)) // If it fails, try to get the repository localRepo, exists := s.Repositories[repoInfo.LocalName] if !exists { return fmt.Errorf("Repository does not exist: %s", repoInfo.LocalName) } var lastErr error for _, endpoint := range endpoints { logrus.Debugf("Trying to push %s to %s %s", repoInfo.CanonicalName, endpoint.URL, endpoint.Version) pusher, err := s.NewPusher(endpoint, localRepo, repoInfo, imagePushConfig, sf) if err != nil { lastErr = err continue } if fallback, err := pusher.Push(); err != nil { if fallback { lastErr = err continue } logrus.Debugf("Not continuing with error: %v", err) return err } s.eventsService.Log("push", repoInfo.LocalName, "") return nil } if lastErr == nil { lastErr = fmt.Errorf("no endpoints found for %s", repoInfo.CanonicalName) } return lastErr }
// Import imports an image, getting the archived layer data either from // inConfig (if src is "-"), or from a URI specified in src. Progress output is // written to outStream. Repository and tag names can optionally be given in // the repo and tag arguments, respectively. func (s *TagStore) Import(src string, repo string, tag string, inConfig io.ReadCloser, outStream io.Writer, containerConfig *runconfig.Config) error { var ( sf = streamformatter.NewJSONStreamFormatter() archive io.ReadCloser resp *http.Response ) if src == "-" { archive = inConfig } else { inConfig.Close() u, err := url.Parse(src) if err != nil { return err } if u.Scheme == "" { u.Scheme = "http" u.Host = src u.Path = "" } outStream.Write(sf.FormatStatus("", "Downloading from %s", u)) resp, err = httputils.Download(u.String()) if err != nil { return err } progressReader := progressreader.New(progressreader.Config{ In: resp.Body, Out: outStream, Formatter: sf, Size: resp.ContentLength, NewLines: true, ID: "", Action: "Importing", }) archive = progressReader } defer archive.Close() img, err := s.graph.Create(archive, "", "", "Imported from "+src, "", nil, containerConfig) if err != nil { return err } // Optionally register the image at REPO/TAG if repo != "" { if err := s.Tag(repo, tag, img.ID, true); err != nil { return err } } outStream.Write(sf.FormatStatus("", img.ID)) logID := img.ID if tag != "" { logID = utils.ImageReference(logID, tag) } s.eventsService.Log("import", logID, "") return nil }
func (s *router) postImagesPush(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { metaHeaders := map[string][]string{} for k, v := range r.Header { if strings.HasPrefix(k, "X-Meta-") { metaHeaders[k] = v } } if err := httputils.ParseForm(r); err != nil { return err } authConfig := &types.AuthConfig{} authEncoded := r.Header.Get("X-Registry-Auth") if authEncoded != "" { // the new format is to handle the authConfig as a header authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded)) if err := json.NewDecoder(authJSON).Decode(authConfig); err != nil { // to increase compatibility to existing api it is defaulting to be empty authConfig = &types.AuthConfig{} } } else { // the old format is supported for compatibility if there was no authConfig header if err := json.NewDecoder(r.Body).Decode(authConfig); err != nil { return fmt.Errorf("Bad parameters and missing X-Registry-Auth: %v", err) } } ref, err := reference.ParseNamed(vars["name"]) if err != nil { return err } tag := r.Form.Get("tag") if tag != "" { // Push by digest is not supported, so only tags are supported. ref, err = reference.WithTag(ref, tag) if err != nil { return err } } output := ioutils.NewWriteFlusher(w) defer output.Close() w.Header().Set("Content-Type", "application/json") if err := s.daemon.PushImage(ref, metaHeaders, authConfig, output); err != nil { if !output.Flushed() { return err } sf := streamformatter.NewJSONStreamFormatter() output.Write(sf.FormatError(err)) } return nil }
// Creates an image from Pull or from Import func (s *imageRouter) postImagesCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := httputils.ParseForm(r); err != nil { return err } var ( image = r.Form.Get("fromImage") repo = r.Form.Get("repo") tag = r.Form.Get("tag") message = r.Form.Get("message") err error output = ioutils.NewWriteFlusher(w) ) defer output.Close() w.Header().Set("Content-Type", "application/json") if image != "" { //pull metaHeaders := map[string][]string{} for k, v := range r.Header { if strings.HasPrefix(k, "X-Meta-") { metaHeaders[k] = v } } authEncoded := r.Header.Get("X-Registry-Auth") authConfig := &types.AuthConfig{} if authEncoded != "" { authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded)) if err := json.NewDecoder(authJSON).Decode(authConfig); err != nil { // for a pull it is not an error if no auth was given // to increase compatibility with the existing api it is defaulting to be empty authConfig = &types.AuthConfig{} } } err = s.backend.PullImage(ctx, image, tag, metaHeaders, authConfig, output) } else { //import src := r.Form.Get("fromSrc") // 'err' MUST NOT be defined within this block, we need any error // generated from the download to be available to the output // stream processing below err = s.backend.ImportImage(src, repo, tag, message, r.Body, output, r.Form["changes"]) } if err != nil { if !output.Flushed() { return err } sf := streamformatter.NewJSONStreamFormatter() output.Write(sf.FormatError(err)) } return nil }
func (s *Server) postImagesPush(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } metaHeaders := map[string][]string{} for k, v := range r.Header { if strings.HasPrefix(k, "X-Meta-") { metaHeaders[k] = v } } if err := parseForm(r); err != nil { return err } authConfig := &cliconfig.AuthConfig{} authEncoded := r.Header.Get("X-Registry-Auth") if authEncoded != "" { // the new format is to handle the authConfig as a header authJson := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded)) if err := json.NewDecoder(authJson).Decode(authConfig); err != nil { // to increase compatibility to existing api it is defaulting to be empty authConfig = &cliconfig.AuthConfig{} } } else { // the old format is supported for compatibility if there was no authConfig header if err := json.NewDecoder(r.Body).Decode(authConfig); err != nil { return fmt.Errorf("Bad parameters and missing X-Registry-Auth: %v", err) } } name := vars["name"] output := ioutils.NewWriteFlusher(w) imagePushConfig := &graph.ImagePushConfig{ MetaHeaders: metaHeaders, AuthConfig: authConfig, Tag: r.Form.Get("tag"), OutStream: output, } w.Header().Set("Content-Type", "application/json") if err := s.daemon.Repositories().Push(name, imagePushConfig); err != nil { if !output.Flushed() { return err } sf := streamformatter.NewJSONStreamFormatter() output.Write(sf.FormatError(err)) } return nil }
// Push initiates a push operation on the repository named localName. // ref is the specific variant of the image to be pushed. // If no tag is provided, all tags will be pushed. func Push(ref reference.Named, imagePushConfig *ImagePushConfig) error { // FIXME: Allow to interrupt current push when new push of same image is done. var sf = streamformatter.NewJSONStreamFormatter() // Resolve the Repository name from fqn to RepositoryInfo repoInfo, err := imagePushConfig.RegistryService.ResolveRepository(ref) if err != nil { return err } endpoints, err := imagePushConfig.RegistryService.LookupPushEndpoints(repoInfo.CanonicalName) if err != nil { return err } imagePushConfig.OutStream.Write(sf.FormatStatus("", "The push refers to a repository [%s]", repoInfo.CanonicalName)) associations := imagePushConfig.TagStore.ReferencesByName(repoInfo.LocalName) if len(associations) == 0 { return fmt.Errorf("Repository does not exist: %s", repoInfo.LocalName) } var lastErr error for _, endpoint := range endpoints { logrus.Debugf("Trying to push %s to %s %s", repoInfo.CanonicalName, endpoint.URL, endpoint.Version) pusher, err := NewPusher(ref, endpoint, repoInfo, imagePushConfig, sf) if err != nil { lastErr = err continue } if fallback, err := pusher.Push(); err != nil { if fallback { lastErr = err continue } logrus.Debugf("Not continuing with error: %v", err) return err } imagePushConfig.EventsService.Log("push", repoInfo.LocalName.Name(), "") return nil } if lastErr == nil { lastErr = fmt.Errorf("no endpoints found for %s", repoInfo.CanonicalName) } return lastErr }
func writeDistributionProgress(cancelFunc func(), outStream io.Writer, progressChan <-chan progress.Progress) { progressOutput := streamformatter.NewJSONStreamFormatter().NewProgressOutput(outStream, false) operationCancelled := false for prog := range progressChan { if err := progressOutput.WriteProgress(prog); err != nil && !operationCancelled { logrus.Errorf("error writing progress to client: %v", err) cancelFunc() operationCancelled = true // Don't return, because we need to continue draining // progressChan until it's closed to avoid a deadlock. } } }
func (s *imageRouter) postImagesLoad(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := httputils.ParseForm(r); err != nil { return err } quiet := httputils.BoolValueOrDefault(r, "quiet", true) w.Header().Set("Content-Type", "application/json") output := ioutils.NewWriteFlusher(w) defer output.Close() if err := s.backend.LoadImage(r.Body, output, quiet); err != nil { output.Write(streamformatter.NewJSONStreamFormatter().FormatError(err)) } return nil }
func (s *router) postImagesLoad(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { w.Header().Set("Content-Type", "application/json") output := ioutils.NewWriteFlusher(w) defer output.Close() err := s.daemon.LoadImage(r.Body, output) if err != nil { if !output.Flushed() { return err } sf := streamformatter.NewJSONStreamFormatter() output.Write(sf.FormatError(err)) } return nil }
func (i *Image) PullImage(ctx context.Context, ref reference.Named, metaHeaders map[string][]string, authConfig *types.AuthConfig, outStream io.Writer) error { defer trace.End(trace.Begin(ref.String())) log.Debugf("PullImage: ref = %+v, metaheaders = %+v\n", ref, metaHeaders) options := imagec.Options{ Destination: os.TempDir(), Reference: ref.String(), Timeout: imagec.DefaultHTTPTimeout, Outstream: outStream, } if authConfig != nil { if len(authConfig.Username) > 0 { options.Username = authConfig.Username } if len(authConfig.Password) > 0 { options.Password = authConfig.Password } } portLayerServer := PortLayerServer() if portLayerServer != "" { options.Host = portLayerServer } insecureRegistries := InsecureRegistries() for _, registry := range insecureRegistries { if registry == ref.Hostname() { options.InsecureAllowHTTP = true break } } log.Infof("PullImage: reference: %s, %s, portlayer: %#v", options.Reference, options.Host, portLayerServer) ic := imagec.NewImageC(options, streamformatter.NewJSONStreamFormatter()) err := ic.PullImage() if err != nil { return err } return nil }
func (pr *pluginRouter) pushPlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := httputils.ParseForm(r); err != nil { return errors.Wrap(err, "failed to parse form") } metaHeaders, authConfig := parseHeaders(r.Header) w.Header().Set("Content-Type", "application/json") output := ioutils.NewWriteFlusher(w) if err := pr.backend.Push(ctx, vars["name"], metaHeaders, authConfig, output); err != nil { if !output.Flushed() { return err } output.Write(streamformatter.NewJSONStreamFormatter().FormatError(err)) } return nil }
func writeDistributionProgress(cancelFunc func(), outStream io.Writer, progressChan <-chan progress.Progress) { progressOutput := streamformatter.NewJSONStreamFormatter().NewProgressOutput(outStream, false) operationCancelled := false for prog := range progressChan { if err := progressOutput.WriteProgress(prog); err != nil && !operationCancelled { // don't log broken pipe errors as this is the normal case when a client aborts if isBrokenPipe(err) { logrus.Info("Pull session cancelled") } else { logrus.Errorf("error writing progress to client: %v", err) } cancelFunc() operationCancelled = true // Don't return, because we need to continue draining // progressChan until it's closed to avoid a deadlock. } } }
func postImageBuild(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } w.Header().Set("Content-Type", "application/json") glog.V(1).Infof("Image name is %s", r.Form.Get("name")) job := eng.Job("build", r.Form.Get("name"), fmt.Sprintf("%d", r.ContentLength)) stdoutBuf := bytes.NewBuffer(nil) job.Stdout.Add(stdoutBuf) job.Stdin.Add(r.Body) output := ioutils.NewWriteFlusher(w) if err := job.Run(); err != nil { sf := streamformatter.NewJSONStreamFormatter() output.Write(sf.FormatError(err)) } return nil }
func TestFetchImageManifest(t *testing.T) { options := Options{ Outstream: os.Stdout, } ic := NewImageC(options, streamformatter.NewJSONStreamFormatter()) s := httptest.NewServer( http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.Write([]byte(DefaultManifest)) })) defer s.Close() ic.Options.Registry = s.URL ic.Options.Image = Image ic.Options.Tag = Tag ic.Options.Timeout = DefaultHTTPTimeout ic.Options.Token = &urlfetcher.Token{Token: OAuthToken} // create a temporary directory dir, err := ioutil.TempDir("", "imagec") if err != nil { t.Errorf(err.Error()) } defer os.RemoveAll(dir) ic.Options.Destination = dir ctx := context.TODO() manifest, err := FetchImageManifest(ctx, ic.Options, ic.progressOutput) if err != nil { t.Errorf(err.Error()) } if manifest.FSLayers[0].BlobSum != DigestSHA256EmptyData { t.Errorf("Returned manifest %#v is different than expected", manifest) } }
func TestListImages(t *testing.T) { options := Options{ Outstream: os.Stdout, } ic := NewImageC(options, streamformatter.NewJSONStreamFormatter()) s := httptest.NewServer( http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) resp := ` [ { "ID": "7bd023c8937ded982c1b98da453b1a5afec86f390ffad8fa0f4fba244a6155f1", "Metadata": { "v1Compatibility": "{\"id\":\"7bd023c8937ded982c1b98da453b1a5afec86f390ffad8fa0f4fba244a6155f1\",\"parent\":\"b873f334fa5259acb24cf0e2cd2639d3a9fb3eb9bafbca06ed4f702c289b31c0\",\"created\":\"2016-05-27T14:15:02.359284074Z\",\"container\":\"b8bd6a8e8874a87f626871ce370f4775bdf598865637082da2949ee0f4786432\",\"container_config\":{\"Hostname\":\"914cf42a3e15\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[],\"Cmd\":[\"/bin/sh\",\"-c\",\"#(nop) CMD [\\\"/bin/bash\\\"]\"],\"Image\":\"b873f334fa5259acb24cf0e2cd2639d3a9fb3eb9bafbca06ed4f702c289b31c0\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"OnBuild\":null,\"Labels\":{}},\"docker_version\":\"1.9.1\",\"config\":{\"Hostname\":\"914cf42a3e15\",\"Domainname\":\"\",\"User\":\"\",\"AttachStdin\":false,\"AttachStdout\":false,\"AttachStderr\":false,\"Tty\":false,\"OpenStdin\":false,\"StdinOnce\":false,\"Env\":[],\"Cmd\":[\"/bin/bash\"],\"Image\":\"b873f334fa5259acb24cf0e2cd2639d3a9fb3eb9bafbca06ed4f702c289b31c0\",\"Volumes\":null,\"WorkingDir\":\"\",\"Entrypoint\":null,\"OnBuild\":null,\"Labels\":{}},\"architecture\":\"amd64\",\"os\":\"linux\"}" }, "SelfLink": "http://Photon/storage/7bd023c8937ded982c1b98da453b1a5afec86f390ffad8fa0f4fba244a6155f1", "Store": "http://Photon/storage/PetStore" } ] ` w.Write([]byte(resp)) })) defer s.Close() ic.Options.Host = s.URL[7:] m, err := ListImages(ic.Host, ic.Storename, nil) if err != nil { t.Errorf(err.Error()) } if m["7bd023c8937ded982c1b98da453b1a5afec86f390ffad8fa0f4fba244a6155f1"].ID != "7bd023c8937ded982c1b98da453b1a5afec86f390ffad8fa0f4fba244a6155f1" { t.Errorf("Returned list %#v is different than expected", m) } }
func postImageCreate(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } authEncoded := r.Header.Get("X-Registry-Auth") authConfig := &cliconfig.AuthConfig{} if authEncoded != "" { authJson := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded)) if err := json.NewDecoder(authJson).Decode(authConfig); err != nil { // for a pull it is not an error if no auth was given // to increase compatibility with the existing api it is defaulting to be empty authConfig = &cliconfig.AuthConfig{} } } w.Header().Set("Content-Type", "application/json") glog.V(1).Infof("Image name is %s", r.Form.Get("imageName")) job := eng.Job("pull", r.Form.Get("imageName")) output := ioutils.NewWriteFlusher(w) metaHeaders := map[string][]string{} for k, v := range r.Header { if strings.HasPrefix(k, "X-Meta-") { metaHeaders[k] = v } } job.Stdout.Add(output) imagePullConfig := &types.ImagePullConfig{ MetaHeaders: metaHeaders, AuthConfig: authConfig, } job.SetenvJson("ImagePullConfig", imagePullConfig) if err := job.Run(); err != nil { sf := streamformatter.NewJSONStreamFormatter() output.Write(sf.FormatError(err)) } return nil }
func TestPingPortLayer(t *testing.T) { options := Options{ Outstream: os.Stdout, } ic := NewImageC(options, streamformatter.NewJSONStreamFormatter()) s := httptest.NewServer( http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.Write([]byte("\"OK\"")) })) defer s.Close() ic.Options.Host = s.URL[7:] b, err := PingPortLayer(ic.Host) if err != nil || b != true { t.Errorf(err.Error()) } }
func TestFetchToken(t *testing.T) { options := Options{ Outstream: os.Stdout, Timeout: DefaultHTTPTimeout, } ic := NewImageC(options, streamformatter.NewJSONStreamFormatter()) s := httptest.NewServer( http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") body, err := json.Marshal(&urlfetcher.Token{Token: OAuthToken}) if err != nil { t.Errorf(err.Error()) } w.Write(body) })) defer s.Close() url, err := url.Parse(s.URL) if err != nil { t.Errorf(err.Error()) } url.Path = path.Join(url.Path, "token?scope=repository%3Alibrary%2Fphoton%3Apull&service=registry.docker.io") ctx := context.TODO() token, err := FetchToken(ctx, ic.Options, url, ic.progressOutput) if err != nil { t.Errorf(err.Error()) } if token.Token != OAuthToken { t.Errorf("Returned token %s is different than expected", token.Token) } }
// ImportImage imports an image, getting the archived layer data either from // inConfig (if src is "-"), or from a URI specified in src. Progress output is // written to outStream. Repository and tag names can optionally be given in // the repo and tag arguments, respectively. func (daemon *Daemon) ImportImage(src string, repository, tag string, msg string, inConfig io.ReadCloser, outStream io.Writer, changes []string) error { var ( sf = streamformatter.NewJSONStreamFormatter() rc io.ReadCloser resp *http.Response newRef reference.Named ) if repository != "" { var err error newRef, err = reference.ParseNamed(repository) if err != nil { return err } if _, isCanonical := newRef.(reference.Canonical); isCanonical { return errors.New("cannot import digest reference") } if tag != "" { newRef, err = reference.WithTag(newRef, tag) if err != nil { return err } } } config, err := dockerfile.BuildFromConfig(&container.Config{}, changes) if err != nil { return err } if src == "-" { rc = inConfig } else { inConfig.Close() u, err := url.Parse(src) if err != nil { return err } if u.Scheme == "" { u.Scheme = "http" u.Host = src u.Path = "" } outStream.Write(sf.FormatStatus("", "Downloading from %s", u)) resp, err = httputils.Download(u.String()) if err != nil { return err } progressOutput := sf.NewProgressOutput(outStream, true) rc = progress.NewProgressReader(resp.Body, progressOutput, resp.ContentLength, "", "Importing") } defer rc.Close() if len(msg) == 0 { msg = "Imported from " + src } inflatedLayerData, err := archive.DecompressStream(rc) if err != nil { return err } // TODO: support windows baselayer? l, err := daemon.layerStore.Register(inflatedLayerData, "") if err != nil { return err } defer layer.ReleaseAndLog(daemon.layerStore, l) created := time.Now().UTC() imgConfig, err := json.Marshal(&image.Image{ V1Image: image.V1Image{ DockerVersion: dockerversion.Version, Config: config, Architecture: runtime.GOARCH, OS: runtime.GOOS, Created: created, Comment: msg, }, RootFS: &image.RootFS{ Type: "layers", DiffIDs: []layer.DiffID{l.DiffID()}, }, History: []image.History{{ Created: created, Comment: msg, }}, }) if err != nil { return err } id, err := daemon.imageStore.Create(imgConfig) if err != nil { return err } // FIXME: connect with commit code and call refstore directly if newRef != nil { if err := daemon.TagImageWithReference(id, newRef); err != nil { return err } } daemon.LogImageEvent(id.String(), id.String(), "import") outStream.Write(sf.FormatStatus("", id.String())) return nil }
// Creates an image from Pull or from Import func (s *Server) postImagesCreate(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } var ( image = r.Form.Get("fromImage") repo = r.Form.Get("repo") tag = r.Form.Get("tag") ) authEncoded := r.Header.Get("X-Registry-Auth") authConfig := &cliconfig.AuthConfig{} if authEncoded != "" { authJson := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded)) if err := json.NewDecoder(authJson).Decode(authConfig); err != nil { // for a pull it is not an error if no auth was given // to increase compatibility with the existing api it is defaulting to be empty authConfig = &cliconfig.AuthConfig{} } } var ( err error output = ioutils.NewWriteFlusher(w) ) w.Header().Set("Content-Type", "application/json") if image != "" { //pull if tag == "" { image, tag = parsers.ParseRepositoryTag(image) } metaHeaders := map[string][]string{} for k, v := range r.Header { if strings.HasPrefix(k, "X-Meta-") { metaHeaders[k] = v } } imagePullConfig := &graph.ImagePullConfig{ MetaHeaders: metaHeaders, AuthConfig: authConfig, OutStream: output, } err = s.daemon.Repositories().Pull(image, tag, imagePullConfig) } else { //import if tag == "" { repo, tag = parsers.ParseRepositoryTag(repo) } src := r.Form.Get("fromSrc") imageImportConfig := &graph.ImageImportConfig{ Changes: r.Form["changes"], InConfig: r.Body, OutStream: output, } // 'err' MUST NOT be defined within this block, we need any error // generated from the download to be available to the output // stream processing below var newConfig *runconfig.Config newConfig, err = builder.BuildFromConfig(s.daemon, &runconfig.Config{}, imageImportConfig.Changes) if err != nil { return err } imageImportConfig.ContainerConfig = newConfig err = s.daemon.Repositories().Import(src, repo, tag, imageImportConfig) } if err != nil { if !output.Flushed() { return err } sf := streamformatter.NewJSONStreamFormatter() output.Write(sf.FormatError(err)) } return nil }
func (br *buildRouter) postBuild(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { var ( authConfigs = map[string]types.AuthConfig{} authConfigsEncoded = r.Header.Get("X-Registry-Config") buildConfig = &dockerfile.Config{} notVerboseBuffer = bytes.NewBuffer(nil) ) if authConfigsEncoded != "" { authConfigsJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authConfigsEncoded)) if err := json.NewDecoder(authConfigsJSON).Decode(&authConfigs); err != nil { // for a pull it is not an error if no auth was given // to increase compatibility with the existing api it is defaulting // to be empty. } } w.Header().Set("Content-Type", "application/json") version := httputils.VersionFromContext(ctx) output := ioutils.NewWriteFlusher(w) defer output.Close() sf := streamformatter.NewJSONStreamFormatter() errf := func(err error) error { if !buildConfig.Verbose && notVerboseBuffer.Len() > 0 { output.Write(notVerboseBuffer.Bytes()) } // Do not write the error in the http output if it's still empty. // This prevents from writing a 200(OK) when there is an internal error. if !output.Flushed() { return err } _, err = w.Write(sf.FormatError(errors.New(utils.GetErrorMessage(err)))) if err != nil { logrus.Warnf("could not write error response: %v", err) } return nil } if httputils.BoolValue(r, "forcerm") && version.GreaterThanOrEqualTo("1.12") { buildConfig.Remove = true } else if r.FormValue("rm") == "" && version.GreaterThanOrEqualTo("1.12") { buildConfig.Remove = true } else { buildConfig.Remove = httputils.BoolValue(r, "rm") } if httputils.BoolValue(r, "pull") && version.GreaterThanOrEqualTo("1.16") { buildConfig.Pull = true } repoAndTags, err := sanitizeRepoAndTags(r.Form["t"]) if err != nil { return errf(err) } buildConfig.DockerfileName = r.FormValue("dockerfile") buildConfig.Verbose = !httputils.BoolValue(r, "q") buildConfig.UseCache = !httputils.BoolValue(r, "nocache") buildConfig.ForceRemove = httputils.BoolValue(r, "forcerm") buildConfig.MemorySwap = httputils.Int64ValueOrZero(r, "memswap") buildConfig.Memory = httputils.Int64ValueOrZero(r, "memory") buildConfig.CPUShares = httputils.Int64ValueOrZero(r, "cpushares") buildConfig.CPUPeriod = httputils.Int64ValueOrZero(r, "cpuperiod") buildConfig.CPUQuota = httputils.Int64ValueOrZero(r, "cpuquota") buildConfig.CPUSetCpus = r.FormValue("cpusetcpus") buildConfig.CPUSetMems = r.FormValue("cpusetmems") buildConfig.CgroupParent = r.FormValue("cgroupparent") if r.Form.Get("shmsize") != "" { shmSize, err := strconv.ParseInt(r.Form.Get("shmsize"), 10, 64) if err != nil { return errf(err) } buildConfig.ShmSize = &shmSize } if i := container.IsolationLevel(r.FormValue("isolation")); i != "" { if !container.IsolationLevel.IsValid(i) { return errf(fmt.Errorf("Unsupported isolation: %q", i)) } buildConfig.Isolation = i } var buildUlimits = []*ulimit.Ulimit{} ulimitsJSON := r.FormValue("ulimits") if ulimitsJSON != "" { if err := json.NewDecoder(strings.NewReader(ulimitsJSON)).Decode(&buildUlimits); err != nil { return errf(err) } buildConfig.Ulimits = buildUlimits } var buildArgs = map[string]string{} buildArgsJSON := r.FormValue("buildargs") if buildArgsJSON != "" { if err := json.NewDecoder(strings.NewReader(buildArgsJSON)).Decode(&buildArgs); err != nil { return errf(err) } buildConfig.BuildArgs = buildArgs } remoteURL := r.FormValue("remote") // Currently, only used if context is from a remote url. // Look at code in DetectContextFromRemoteURL for more information. createProgressReader := func(in io.ReadCloser) io.ReadCloser { progressOutput := sf.NewProgressOutput(output, true) if !buildConfig.Verbose { progressOutput = sf.NewProgressOutput(notVerboseBuffer, true) } return progress.NewProgressReader(in, progressOutput, r.ContentLength, "Downloading context", remoteURL) } var ( context builder.ModifiableContext dockerfileName string ) context, dockerfileName, err = daemonbuilder.DetectContextFromRemoteURL(r.Body, remoteURL, createProgressReader) if err != nil { return errf(err) } defer func() { if err := context.Close(); err != nil { logrus.Debugf("[BUILDER] failed to remove temporary context: %v", err) } }() uidMaps, gidMaps := br.backend.GetUIDGIDMaps() defaultArchiver := &archive.Archiver{ Untar: chrootarchive.Untar, UIDMaps: uidMaps, GIDMaps: gidMaps, } docker := &daemonbuilder.Docker{ Daemon: br.backend, OutOld: output, AuthConfigs: authConfigs, Archiver: defaultArchiver, } if !buildConfig.Verbose { docker.OutOld = notVerboseBuffer } b, err := dockerfile.NewBuilder(buildConfig, docker, builder.DockerIgnoreContext{ModifiableContext: context}, nil) if err != nil { return errf(err) } b.Stdout = &streamformatter.StdoutFormatter{Writer: output, StreamFormatter: sf} b.Stderr = &streamformatter.StderrFormatter{Writer: output, StreamFormatter: sf} if !buildConfig.Verbose { b.Stdout = &streamformatter.StdoutFormatter{Writer: notVerboseBuffer, StreamFormatter: sf} b.Stderr = &streamformatter.StderrFormatter{Writer: notVerboseBuffer, StreamFormatter: sf} } if closeNotifier, ok := w.(http.CloseNotifier); ok { finished := make(chan struct{}) defer close(finished) go func() { select { case <-finished: case <-closeNotifier.CloseNotify(): logrus.Infof("Client disconnected, cancelling job: build") b.Cancel() } }() } if len(dockerfileName) > 0 { b.DockerfileName = dockerfileName } imgID, err := b.Build() if err != nil { return errf(err) } for _, rt := range repoAndTags { if err := br.backend.TagImage(rt, imgID); err != nil { return errf(err) } } // Everything worked so if -q was provided the output from the daemon // should be just the image ID and we'll print that to stdout. if !buildConfig.Verbose { stdout := &streamformatter.StdoutFormatter{Writer: output, StreamFormatter: sf} fmt.Fprintf(stdout, "%s\n", string(imgID)) } return nil }
func (s *Server) postBuild(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { var ( authConfig = &cliconfig.AuthConfig{} configFileEncoded = r.Header.Get("X-Registry-Config") configFile = &cliconfig.ConfigFile{} buildConfig = builder.NewBuildConfig() ) if configFileEncoded != "" { configFileJson := base64.NewDecoder(base64.URLEncoding, strings.NewReader(configFileEncoded)) if err := json.NewDecoder(configFileJson).Decode(configFile); err != nil { // for a pull it is not an error if no auth was given // to increase compatibility with the existing api it is defaulting to be empty configFile = &cliconfig.ConfigFile{} } } w.Header().Set("Content-Type", "application/json") if boolValue(r, "forcerm") && version.GreaterThanOrEqualTo("1.12") { buildConfig.Remove = true } else if r.FormValue("rm") == "" && version.GreaterThanOrEqualTo("1.12") { buildConfig.Remove = true } else { buildConfig.Remove = boolValue(r, "rm") } if boolValue(r, "pull") && version.GreaterThanOrEqualTo("1.16") { buildConfig.Pull = true } output := ioutils.NewWriteFlusher(w) buildConfig.Stdout = output buildConfig.Context = r.Body buildConfig.RemoteURL = r.FormValue("remote") buildConfig.DockerfileName = r.FormValue("dockerfile") buildConfig.RepoName = r.FormValue("t") buildConfig.SuppressOutput = boolValue(r, "q") buildConfig.NoCache = boolValue(r, "nocache") buildConfig.ForceRemove = boolValue(r, "forcerm") buildConfig.AuthConfig = authConfig buildConfig.ConfigFile = configFile buildConfig.MemorySwap = int64ValueOrZero(r, "memswap") buildConfig.Memory = int64ValueOrZero(r, "memory") buildConfig.CpuShares = int64ValueOrZero(r, "cpushares") buildConfig.CpuPeriod = int64ValueOrZero(r, "cpuperiod") buildConfig.CpuQuota = int64ValueOrZero(r, "cpuquota") buildConfig.CpuSetCpus = r.FormValue("cpusetcpus") buildConfig.CpuSetMems = r.FormValue("cpusetmems") buildConfig.CgroupParent = r.FormValue("cgroupparent") // Job cancellation. Note: not all job types support this. if closeNotifier, ok := w.(http.CloseNotifier); ok { finished := make(chan struct{}) defer close(finished) go func() { select { case <-finished: case <-closeNotifier.CloseNotify(): logrus.Infof("Client disconnected, cancelling job: build") buildConfig.Cancel() } }() } if err := builder.Build(s.daemon, buildConfig); err != nil { // Do not write the error in the http output if it's still empty. // This prevents from writing a 200(OK) when there is an interal error. if !output.Flushed() { return err } sf := streamformatter.NewJSONStreamFormatter() w.Write(sf.FormatError(err)) } return nil }