func validateEndpoint(endpoint *Endpoint) error { logrus.Debugf("pinging registry endpoint %s", endpoint) // Try HTTPS ping to registry endpoint.URL.Scheme = "https" if _, err := endpoint.Ping(); err != nil { if endpoint.IsSecure { // If registry is secure and HTTPS failed, show user the error and tell them about `--insecure-registry` // in case that's what they need. DO NOT accept unknown CA certificates, and DO NOT fallback to HTTP. return fmt.Errorf("invalid registry endpoint %s: %v. If this private registry supports only HTTP or HTTPS with an unknown CA certificate, please add `--insecure-registry %s` to the daemon's arguments. In the case of HTTPS, if you have access to the registry's CA certificate, no need for the flag; simply place the CA certificate at /etc/docker/certs.d/%s/ca.crt", endpoint, err, endpoint.URL.Host, endpoint.URL.Host) } // If registry is insecure and HTTPS failed, fallback to HTTP. logrus.Debugf("Error from registry %q marked as insecure: %v. Insecurely falling back to HTTP", endpoint, err) endpoint.URL.Scheme = "http" var err2 error if _, err2 = endpoint.Ping(); err2 == nil { return nil } return fmt.Errorf("invalid registry endpoint %q. HTTPS attempt: %v. HTTP attempt: %v", endpoint, err, err2) } return nil }
// Lookup returns the content and the actual filename of the file that is "built" using the // specified file and relativeTo string. file and relativeTo are supposed to be file path. // If file starts with a slash ('/'), it tries to load it, otherwise it will build a // filename using the folder part of relativeTo joined with file. func (f *FileConfigLookup) Lookup(file, relativeTo string) ([]byte, string, error) { if strings.HasPrefix(file, "/") { logrus.Debugf("Reading file %s", file) bytes, err := ioutil.ReadFile(file) return bytes, file, err } fileName := path.Join(path.Dir(relativeTo), file) logrus.Debugf("Reading file %s relative to %s", fileName, relativeTo) bytes, err := ioutil.ReadFile(fileName) return bytes, fileName, err }
// ExportChanges produces an Archive from the provided changes, relative to dir. func ExportChanges(dir string, changes []Change) (Archive, error) { reader, writer := io.Pipe() go func() { ta := &tarAppender{ TarWriter: tar.NewWriter(writer), Buffer: pools.BufioWriter32KPool.Get(nil), SeenFiles: make(map[uint64]string), } // this buffer is needed for the duration of this piped stream defer pools.BufioWriter32KPool.Put(ta.Buffer) sort.Sort(changesByPath(changes)) // In general we log errors here but ignore them because // during e.g. a diff operation the container can continue // mutating the filesystem and we can see transient errors // from this for _, change := range changes { if change.Kind == ChangeDelete { whiteOutDir := filepath.Dir(change.Path) whiteOutBase := filepath.Base(change.Path) whiteOut := filepath.Join(whiteOutDir, ".wh."+whiteOutBase) timestamp := time.Now() hdr := &tar.Header{ Name: whiteOut[1:], Size: 0, ModTime: timestamp, AccessTime: timestamp, ChangeTime: timestamp, } if err := ta.TarWriter.WriteHeader(hdr); err != nil { logrus.Debugf("Can't write whiteout header: %s", err) } } else { path := filepath.Join(dir, change.Path) if err := ta.addTarFile(path, change.Path[1:]); err != nil { logrus.Debugf("Can't add file %s to tar: %s", path, err) } } } // Make sure to check the error on Close. if err := ta.TarWriter.Close(); err != nil { logrus.Debugf("Can't close layer: %s", err) } if err := writer.Close(); err != nil { logrus.Debugf("failed close Changes writer: %s", err) } }() return reader, nil }
func (p *Project) startService(wrappers map[string]*serviceWrapper, history []string, selected, launched map[string]bool, wrapper *serviceWrapper, action wrapperAction, cycleAction serviceAction) error { if launched[wrapper.name] { return nil } launched[wrapper.name] = true history = append(history, wrapper.name) for _, dep := range wrapper.service.DependentServices() { target := wrappers[dep.Target] if target == nil { log.Errorf("Failed to find %s", dep.Target) continue } if utils.Contains(history, dep.Target) { cycle := strings.Join(append(history, dep.Target), "->") if dep.Optional { log.Debugf("Ignoring cycle for %s", cycle) wrapper.IgnoreDep(dep.Target) if cycleAction != nil { var err error log.Debugf("Running cycle action for %s", cycle) err = cycleAction(target.service) if err != nil { return err } } } else { return fmt.Errorf("Cycle detected in path %s", cycle) } continue } err := p.startService(wrappers, history, selected, launched, target, action, cycleAction) if err != nil { return err } } if isSelected(wrapper, selected) { log.Debugf("Launching action for %s", wrapper.name) go action(wrapper, wrappers) } else { wrapper.Ignore() } return nil }
func (c *Container) createContainer(imageName string, configOverride *project.ServiceConfig) (*dockerclient.APIContainers, error) { serviceConfig := c.service.serviceConfig if configOverride != nil { serviceConfig.Command = configOverride.Command serviceConfig.Tty = configOverride.Tty } createOpts, err := ConvertToAPI(serviceConfig, c.name) if err != nil { return nil, err } createOpts.Config.Image = imageName if createOpts.Config.Labels == nil { createOpts.Config.Labels = map[string]string{} } createOpts.Config.Labels[NAME.Str()] = c.name createOpts.Config.Labels[SERVICE.Str()] = c.service.name createOpts.Config.Labels[PROJECT.Str()] = c.service.context.Project.Name createOpts.Config.Labels[HASH.Str()] = project.GetServiceHash(c.service) err = c.populateAdditionalHostConfig(createOpts.HostConfig) if err != nil { return nil, err } logrus.Debugf("Creating container %s %#v", c.name, createOpts) _, err = c.client.CreateContainer(*createOpts) if err != nil && err == dockerclient.ErrNoSuchImage { logrus.Debugf("Not Found, pulling image %s", createOpts.Config.Image) if err = c.pull(createOpts.Config.Image); err != nil { return nil, err } if _, err = c.client.CreateContainer(*createOpts); err != nil { return nil, err } } if err != nil { logrus.Debugf("Failed to create container %s: %v", c.name, err) return nil, err } return c.findExisting() }
// GetRemoteImageLayer retrieves an image layer from the registry func (r *Session) GetRemoteImageLayer(imgID, registry string, imgSize int64) (io.ReadCloser, error) { var ( retries = 5 statusCode = 0 res *http.Response err error imageURL = fmt.Sprintf("%simages/%s/layer", registry, imgID) ) req, err := http.NewRequest("GET", imageURL, nil) if err != nil { return nil, fmt.Errorf("Error while getting from the server: %v", err) } // TODO(tiborvass): why are we doing retries at this level? // These retries should be generic to both v1 and v2 for i := 1; i <= retries; i++ { statusCode = 0 res, err = r.client.Do(req) if err == nil { break } logrus.Debugf("Error contacting registry %s: %v", registry, err) if res != nil { if res.Body != nil { res.Body.Close() } statusCode = res.StatusCode } if i == retries { return nil, fmt.Errorf("Server error: Status %d while fetching image layer (%s)", statusCode, imgID) } time.Sleep(time.Duration(i) * 5 * time.Second) } if res.StatusCode != 200 { res.Body.Close() return nil, fmt.Errorf("Server error: Status %d while fetching image layer (%s)", res.StatusCode, imgID) } if res.Header.Get("Accept-Ranges") == "bytes" && imgSize > 0 { logrus.Debugf("server supports resume") return httputils.ResumableRequestReaderWithInitialResponse(r.client, req, 5, imgSize, res), nil } logrus.Debugf("server doesn't support resume") return res.Body, nil }
func handlerAccessLog(handler http.Handler) http.Handler { logHandler := func(w http.ResponseWriter, r *http.Request) { logrus.Debugf("%s \"%s %s\"", r.RemoteAddr, r.Method, r.URL) handler.ServeHTTP(w, r) } return http.HandlerFunc(logHandler) }
// PushImageJSONRegistry pushes JSON metadata for a local image to the registry func (r *Session) PushImageJSONRegistry(imgData *ImgData, jsonRaw []byte, registry string) error { u := registry + "images/" + imgData.ID + "/json" logrus.Debugf("[registry] Calling PUT %s", u) req, err := http.NewRequest("PUT", u, bytes.NewReader(jsonRaw)) if err != nil { return err } req.Header.Add("Content-type", "application/json") res, err := r.client.Do(req) if err != nil { return fmt.Errorf("Failed to upload metadata: %s", err) } defer res.Body.Close() if res.StatusCode == 401 && strings.HasPrefix(registry, "http://") { return httputils.NewHTTPRequestError("HTTP code 401, Docker will not send auth headers over HTTP.", res) } if res.StatusCode != 200 { errBody, err := ioutil.ReadAll(res.Body) if err != nil { return httputils.NewHTTPRequestError(fmt.Sprintf("HTTP code %d while uploading metadata and error when trying to parse response body: %s", res.StatusCode, err), res) } var jsonBody map[string]string if err := json.Unmarshal(errBody, &jsonBody); err != nil { errBody = []byte(err.Error()) } else if jsonBody["error"] == "Image already exists" { return ErrAlreadyExists } return httputils.NewHTTPRequestError(fmt.Sprintf("HTTP code %d while uploading metadata: %q", res.StatusCode, errBody), res) } return nil }
// PushImageChecksumRegistry uploads checksums for an image func (r *Session) PushImageChecksumRegistry(imgData *ImgData, registry string) error { u := registry + "images/" + imgData.ID + "/checksum" logrus.Debugf("[registry] Calling PUT %s", u) req, err := http.NewRequest("PUT", u, nil) if err != nil { return err } req.Header.Set("X-Docker-Checksum", imgData.Checksum) req.Header.Set("X-Docker-Checksum-Payload", imgData.ChecksumPayload) res, err := r.client.Do(req) if err != nil { return fmt.Errorf("Failed to upload metadata: %v", err) } defer res.Body.Close() if len(res.Cookies()) > 0 { r.client.Jar.SetCookies(req.URL, res.Cookies()) } if res.StatusCode != 200 { errBody, err := ioutil.ReadAll(res.Body) if err != nil { return fmt.Errorf("HTTP code %d while uploading metadata and error when trying to parse response body: %s", res.StatusCode, err) } var jsonBody map[string]string if err := json.Unmarshal(errBody, &jsonBody); err != nil { errBody = []byte(err.Error()) } else if jsonBody["error"] == "Image already exists" { return ErrAlreadyExists } return fmt.Errorf("HTTP code %d while uploading metadata: %q", res.StatusCode, errBody) } return nil }
// GetRemoteTags retrieves all tags from the given repository. It queries each // of the registries supplied in the registries argument, and returns data from // the first one that answers the query successfully. It returns a map with // tag names as the keys and image IDs as the values. func (r *Session) GetRemoteTags(registries []string, repository string) (map[string]string, error) { if strings.Count(repository, "/") == 0 { // This will be removed once the registry supports auto-resolution on // the "library" namespace repository = "library/" + repository } for _, host := range registries { endpoint := fmt.Sprintf("%srepositories/%s/tags", host, repository) res, err := r.client.Get(endpoint) if err != nil { return nil, err } logrus.Debugf("Got status code %d from %s", res.StatusCode, endpoint) defer res.Body.Close() if res.StatusCode == 404 { return nil, ErrRepoNotFound } if res.StatusCode != 200 { continue } result := make(map[string]string) if err := json.NewDecoder(res.Body).Decode(&result); err != nil { return nil, err } return result, nil } return nil, fmt.Errorf("Could not reach any registry endpoint") }
func (s *Service) up(imageName string, create bool) error { containers, err := s.collectContainers() if err != nil { return err } logrus.Debugf("Found %d existing containers for service %s", len(containers), s.name) if len(containers) == 0 && create { c, err := s.createOne() if err != nil { return err } containers = []*Container{c} } return s.eachContainer(func(c *Container) error { if outOfSync, err := c.OutOfSync(); err != nil { return err } else if outOfSync { logrus.Warnf("%s needs rebuilding", s.Name()) } return c.Up(imageName) }) }
// NewSession creates a new session // TODO(tiborvass): remove authConfig param once registry client v2 is vendored func NewSession(client *http.Client, authConfig *cliconfig.AuthConfig, endpoint *Endpoint) (r *Session, err error) { r = &Session{ authConfig: authConfig, client: client, indexEndpoint: endpoint, id: stringid.GenerateRandomID(), } var alwaysSetBasicAuth bool // If we're working with a standalone private registry over HTTPS, send Basic Auth headers // alongside all our requests. if endpoint.VersionString(1) != IndexServer && endpoint.URL.Scheme == "https" { info, err := endpoint.Ping() if err != nil { return nil, err } if info.Standalone && authConfig != nil { logrus.Debugf("Endpoint %s is eligible for private registry. Enabling decorator.", endpoint.String()) alwaysSetBasicAuth = true } } // Annotate the transport unconditionally so that v2 can // properly fallback on v1 when an image is not found. client.Transport = AuthTransport(client.Transport, authConfig, alwaysSetBasicAuth) jar, err := cookiejar.New(nil) if err != nil { return nil, errors.New("cookiejar.New is not supposed to return an error") } client.Jar = jar return r, nil }
func (c *Context) readComposeFile() error { if c.ComposeBytes != nil { return nil } logrus.Debugf("Opening compose file: %s", c.ComposeFile) if c.ComposeFile == "-" { composeBytes, err := ioutil.ReadAll(os.Stdin) if err != nil { logrus.Errorf("Failed to read compose file from stdin: %v", err) return err } c.ComposeBytes = composeBytes } else if c.ComposeFile != "" { if composeBytes, err := ioutil.ReadFile(c.ComposeFile); os.IsNotExist(err) { if c.IgnoreMissingConfig { return nil } logrus.Errorf("Failed to find %s", c.ComposeFile) return err } else if err != nil { logrus.Errorf("Failed to open %s", c.ComposeFile) return err } else { c.ComposeBytes = composeBytes } } return nil }
func shouldV2Fallback(err errcode.Error) bool { logrus.Debugf("v2 error: %T %v", err, err) switch err.Code { case v2.ErrorCodeUnauthorized, v2.ErrorCodeManifestUnknown: return true } return false }
// ReadCertsDirectory reads the directory for TLS certificates // including roots and certificate pairs and updates the // provided TLS configuration. func ReadCertsDirectory(tlsConfig *tls.Config, directory string) error { fs, err := ioutil.ReadDir(directory) if err != nil && !os.IsNotExist(err) { return err } for _, f := range fs { if strings.HasSuffix(f.Name(), ".crt") { if tlsConfig.RootCAs == nil { // TODO(dmcgowan): Copy system pool tlsConfig.RootCAs = x509.NewCertPool() } logrus.Debugf("crt: %s", filepath.Join(directory, f.Name())) data, err := ioutil.ReadFile(filepath.Join(directory, f.Name())) if err != nil { return err } tlsConfig.RootCAs.AppendCertsFromPEM(data) } if strings.HasSuffix(f.Name(), ".cert") { certName := f.Name() keyName := certName[:len(certName)-5] + ".key" logrus.Debugf("cert: %s", filepath.Join(directory, f.Name())) if !hasFile(fs, keyName) { return fmt.Errorf("Missing key %s for certificate %s", keyName, certName) } cert, err := tls.LoadX509KeyPair(filepath.Join(directory, certName), filepath.Join(directory, keyName)) if err != nil { return err } tlsConfig.Certificates = append(tlsConfig.Certificates, cert) } if strings.HasSuffix(f.Name(), ".key") { keyName := f.Name() certName := keyName[:len(keyName)-4] + ".cert" logrus.Debugf("key: %s", filepath.Join(directory, f.Name())) if !hasFile(fs, certName) { return fmt.Errorf("Missing certificate %s for key %s", certName, keyName) } } } return nil }
// configureLogging prepares the context with a logger using the // configuration. func configureLogging(ctx context.Context, config *configuration.Configuration) (context.Context, error) { if config.Log.Level == "" && config.Log.Formatter == "" { // If no config for logging is set, fallback to deprecated "Loglevel". log.SetLevel(logLevel(config.Loglevel)) ctx = context.WithLogger(ctx, context.GetLogger(ctx, "version")) return ctx, nil } log.SetLevel(logLevel(config.Log.Level)) formatter := config.Log.Formatter if formatter == "" { formatter = "text" // default formatter } switch formatter { case "json": log.SetFormatter(&log.JSONFormatter{ TimestampFormat: time.RFC3339Nano, }) case "text": log.SetFormatter(&log.TextFormatter{ TimestampFormat: time.RFC3339Nano, }) case "logstash": log.SetFormatter(&logstash.LogstashFormatter{ TimestampFormat: time.RFC3339Nano, }) default: // just let the library use default on empty string. if config.Log.Formatter != "" { return ctx, fmt.Errorf("unsupported logging formatter: %q", config.Log.Formatter) } } if config.Log.Formatter != "" { log.Debugf("using %q logging formatter", config.Log.Formatter) } // log the application version with messages ctx = context.WithLogger(ctx, context.GetLogger(ctx, "version")) if len(config.Log.Fields) > 0 { // build up the static fields, if present. var fields []interface{} for k := range config.Log.Fields { fields = append(fields, k) } ctx = context.WithValues(ctx, config.Log.Fields) ctx = context.WithLogger(ctx, context.GetLogger(ctx, fields...)) } return ctx, nil }
// loginV2 tries to login to the v2 registry server. The given registry endpoint has been // pinged or setup with a list of authorization challenges. Each of these challenges are // tried until one of them succeeds. Currently supported challenge schemes are: // HTTP Basic Authorization // Token Authorization with a separate token issuing server // NOTE: the v2 logic does not attempt to create a user account if one doesn't exist. For // now, users should create their account through other means like directly from a web page // served by the v2 registry service provider. Whether this will be supported in the future // is to be determined. func loginV2(authConfig *cliconfig.AuthConfig, registryEndpoint *Endpoint, scope string) (string, error) { logrus.Debugf("attempting v2 login to registry endpoint %s", registryEndpoint) var ( err error allErrors []error ) for _, challenge := range registryEndpoint.AuthChallenges { params := make(map[string]string, len(challenge.Parameters)+1) for k, v := range challenge.Parameters { params[k] = v } params["scope"] = scope logrus.Debugf("trying %q auth challenge with params %v", challenge.Scheme, params) switch strings.ToLower(challenge.Scheme) { case "basic": err = tryV2BasicAuthLogin(authConfig, params, registryEndpoint) case "bearer": err = tryV2TokenAuthLogin(authConfig, params, registryEndpoint) default: // Unsupported challenge types are explicitly skipped. err = fmt.Errorf("unsupported auth scheme: %q", challenge.Scheme) } if err == nil { return "Login Succeeded", nil } logrus.Debugf("error trying auth challenge %q: %s", challenge.Scheme, err) allErrors = append(allErrors, err) } return "", fmt.Errorf("no successful auth challenge for %s - errors: %s", registryEndpoint, allErrors) }
func newTLSConfig(hostname string, isSecure bool) (*tls.Config, error) { // PreferredServerCipherSuites should have no effect tlsConfig := tlsconfig.ServerDefault tlsConfig.InsecureSkipVerify = !isSecure if isSecure { hostDir := filepath.Join(CertsDir, hostname) logrus.Debugf("hostDir: %s", hostDir) if err := ReadCertsDirectory(&tlsConfig, hostDir); err != nil { return nil, err } } return &tlsConfig, nil }
func (e *Endpoint) pingV2() (PingResult, error) { logrus.Debugf("attempting v2 ping for registry endpoint %s", e) req, err := http.NewRequest("GET", e.Path(""), nil) if err != nil { return PingResult{}, err } resp, err := e.client.Do(req) if err != nil { return PingResult{}, err } defer resp.Body.Close() // The endpoint may have multiple supported versions. // Ensure it supports the v2 Registry API. var supportsV2 bool HeaderLoop: for _, supportedVersions := range resp.Header[http.CanonicalHeaderKey("Docker-Distribution-API-Version")] { for _, versionName := range strings.Fields(supportedVersions) { if versionName == "registry/2.0" { supportsV2 = true break HeaderLoop } } } if !supportsV2 { return PingResult{}, fmt.Errorf("%s does not appear to be a v2 registry endpoint", e) } if resp.StatusCode == http.StatusOK { // It would seem that no authentication/authorization is required. // So we don't need to parse/add any authorization schemes. return PingResult{Standalone: true}, nil } if resp.StatusCode == http.StatusUnauthorized { // Parse the WWW-Authenticate Header and store the challenges // on this endpoint object. e.AuthChallenges = parseAuthHeader(resp.Header) return PingResult{}, nil } return PingResult{}, fmt.Errorf("v2 registry endpoint returned status %d: %q", resp.StatusCode, http.StatusText(resp.StatusCode)) }
// Start the specified container with the specified host config func (c *Container) Start(container *dockerclient.APIContainers, hostConfig *dockerclient.HostConfig) error { logrus.Debugf("Starting container: %s: %#v", container.ID, hostConfig) err := c.populateAdditionalHostConfig(hostConfig) if err != nil { return err } if err := c.client.StartContainer(container.ID, hostConfig); err != nil { return err } c.service.context.Project.Notify(project.EventContainerStarted, c.service.Name(), map[string]string{ "name": c.Name(), }) return nil }
func (e *Endpoint) pingV1() (PingResult, error) { logrus.Debugf("attempting v1 ping for registry endpoint %s", e) if e.String() == IndexServer { // Skip the check, we know this one is valid // (and we never want to fallback to http in case of error) return PingResult{Standalone: false}, nil } req, err := http.NewRequest("GET", e.Path("_ping"), nil) if err != nil { return PingResult{Standalone: false}, err } resp, err := e.client.Do(req) if err != nil { return PingResult{Standalone: false}, err } defer resp.Body.Close() jsonString, err := ioutil.ReadAll(resp.Body) if err != nil { return PingResult{Standalone: false}, fmt.Errorf("error while reading the http response: %s", err) } // If the header is absent, we assume true for compatibility with earlier // versions of the registry. default to true info := PingResult{ Standalone: true, } if err := json.Unmarshal(jsonString, &info); err != nil { logrus.Debugf("Error unmarshalling the _ping PingResult: %s", err) // don't stop here. Just assume sane defaults } if hdr := resp.Header.Get("X-Docker-Registry-Version"); hdr != "" { logrus.Debugf("Registry version header: '%s'", hdr) info.Version = hdr } logrus.Debugf("PingResult.Version: %q", info.Version) standalone := resp.Header.Get("X-Docker-Registry-Standalone") logrus.Debugf("Registry standalone header: '%s'", standalone) // Accepted values are "true" (case-insensitive) and "1". if strings.EqualFold(standalone, "true") || standalone == "1" { info.Standalone = true } else if len(standalone) > 0 { // there is a header set, and it is not "true" or "1", so assume fails info.Standalone = false } logrus.Debugf("PingResult.Standalone: %t", info.Standalone) return info, nil }
// certPool returns an X.509 certificate pool from `caFile`, the certificate file. func certPool(caFile string) (*x509.CertPool, error) { // If we should verify the server, we need to load a trusted ca certPool := x509.NewCertPool() pem, err := ioutil.ReadFile(caFile) if err != nil { return nil, fmt.Errorf("Could not read CA certificate %s: %v", caFile, err) } if !certPool.AppendCertsFromPEM(pem) { return nil, fmt.Errorf("failed to append certificates from PEM file: %s", caFile) } s := certPool.Subjects() subjects := make([]string, len(s)) for i, subject := range s { subjects[i] = string(subject) } logrus.Debugf("Trusting certs with subjects: %v", subjects) return certPool, nil }
func (s *Service) constructContainers(create bool, count int) ([]*Container, error) { result, err := s.collectContainers() if err != nil { return nil, err } client := s.context.ClientFactory.Create(s) var namer Namer if s.serviceConfig.ContainerName != "" { if count > 1 { logrus.Warnf(`The "%s" service is using the custom container name "%s". Docker requires each container to have a unique name. Remove the custom name to scale the service.`, s.name, s.serviceConfig.ContainerName) } namer = NewSingleNamer(s.serviceConfig.ContainerName) } else { namer = NewNamer(client, s.context.Project.Name, s.name) } defer namer.Close() for i := len(result); i < count; i++ { containerName := namer.Next() c := NewContainer(client, containerName, s) if create { imageName, err := s.build() if err != nil { return nil, err } dockerContainer, err := c.Create(imageName) if err != nil { return nil, err } logrus.Debugf("Created container %s: %v", dockerContainer.ID, dockerContainer.Names) } result = append(result, c) } return result, nil }
// SetWinsize tries to set the specified window size for the specified file descriptor. func SetWinsize(fd uintptr, ws *Winsize) error { // Ensure the requested dimensions are no larger than the maximum window size info, err := winterm.GetConsoleScreenBufferInfo(fd) if err != nil { return err } if ws.Width == 0 || ws.Height == 0 || ws.Width > uint16(info.MaximumWindowSize.X) || ws.Height > uint16(info.MaximumWindowSize.Y) { return fmt.Errorf("Illegal window size: (%v,%v) -- Maximum allow: (%v,%v)", ws.Width, ws.Height, info.MaximumWindowSize.X, info.MaximumWindowSize.Y) } // Narrow the sizes to that used by Windows width := winterm.SHORT(ws.Width) height := winterm.SHORT(ws.Height) // Set the dimensions while ensuring they remain within the bounds of the backing console buffer // -- Shrinking will always succeed. Growing may push the edges past the buffer boundary. When that occurs, // shift the upper left just enough to keep the new window within the buffer. rect := info.Window if width < rect.Right-rect.Left+1 { rect.Right = rect.Left + width - 1 } else if width > rect.Right-rect.Left+1 { rect.Right = rect.Left + width - 1 if rect.Right >= info.Size.X { rect.Left = info.Size.X - width rect.Right = info.Size.X - 1 } } if height < rect.Bottom-rect.Top+1 { rect.Bottom = rect.Top + height - 1 } else if height > rect.Bottom-rect.Top+1 { rect.Bottom = rect.Top + height - 1 if rect.Bottom >= info.Size.Y { rect.Top = info.Size.Y - height rect.Bottom = info.Size.Y - 1 } } logrus.Debugf("[windows] SetWinsize: Requested((%v,%v)) Actual(%v)", ws.Width, ws.Height, rect) return winterm.SetConsoleWindowInfo(fd, true, rect) }
// PushImageLayerRegistry sends the checksum of an image layer to the registry func (r *Session) PushImageLayerRegistry(imgID string, layer io.Reader, registry string, jsonRaw []byte) (checksum string, checksumPayload string, err error) { u := registry + "images/" + imgID + "/layer" logrus.Debugf("[registry] Calling PUT %s", u) tarsumLayer, err := tarsum.NewTarSum(layer, false, tarsum.Version0) if err != nil { return "", "", err } h := sha256.New() h.Write(jsonRaw) h.Write([]byte{'\n'}) checksumLayer := io.TeeReader(tarsumLayer, h) req, err := http.NewRequest("PUT", u, checksumLayer) if err != nil { return "", "", err } req.Header.Add("Content-Type", "application/octet-stream") req.ContentLength = -1 req.TransferEncoding = []string{"chunked"} res, err := r.client.Do(req) if err != nil { return "", "", fmt.Errorf("Failed to upload layer: %v", err) } if rc, ok := layer.(io.Closer); ok { if err := rc.Close(); err != nil { return "", "", err } } defer res.Body.Close() if res.StatusCode != 200 { errBody, err := ioutil.ReadAll(res.Body) if err != nil { return "", "", httputils.NewHTTPRequestError(fmt.Sprintf("HTTP code %d while uploading metadata and error when trying to parse response body: %s", res.StatusCode, err), res) } return "", "", httputils.NewHTTPRequestError(fmt.Sprintf("Received HTTP code %d while uploading layer: %q", res.StatusCode, errBody), res) } checksumPayload = "sha256:" + hex.EncodeToString(h.Sum(nil)) return tarsumLayer.Sum(jsonRaw), checksumPayload, nil }
// GetRemoteHistory retrieves the history of a given image from the registry. // It returns a list of the parent's JSON files (including the requested image). func (r *Session) GetRemoteHistory(imgID, registry string) ([]string, error) { res, err := r.client.Get(registry + "images/" + imgID + "/ancestry") if err != nil { return nil, err } defer res.Body.Close() if res.StatusCode != 200 { if res.StatusCode == 401 { return nil, errLoginRequired } return nil, httputils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to fetch remote history for %s", res.StatusCode, imgID), res) } var history []string if err := json.NewDecoder(res.Body).Decode(&history); err != nil { return nil, fmt.Errorf("Error while reading the http response: %v", err) } logrus.Debugf("Ancestry: %v", history) return history, nil }
// SearchRepositories performs a search against the remote repository func (r *Session) SearchRepositories(term string) (*SearchResults, error) { logrus.Debugf("Index server: %s", r.indexEndpoint) u := r.indexEndpoint.VersionString(1) + "search?q=" + url.QueryEscape(term) req, err := http.NewRequest("GET", u, nil) if err != nil { return nil, fmt.Errorf("Error while getting from the server: %v", err) } // Have the AuthTransport send authentication, when logged in. req.Header.Set("X-Docker-Token", "true") res, err := r.client.Do(req) if err != nil { return nil, err } defer res.Body.Close() if res.StatusCode != 200 { return nil, httputils.NewHTTPRequestError(fmt.Sprintf("Unexpected status code %d", res.StatusCode), res) } result := new(SearchResults) return result, json.NewDecoder(res.Body).Decode(result) }
// TarResource archives the resource at the given sourcePath into a Tar // archive. A non-nil error is returned if sourcePath does not exist or is // asserted to be a directory but exists as another type of file. // // This function acts as a convenient wrapper around TarWithOptions, which // requires a directory as the source path. TarResource accepts either a // directory or a file path and correctly sets the Tar options. func TarResource(sourcePath string) (content Archive, err error) { if _, err = os.Lstat(sourcePath); err != nil { // Catches the case where the source does not exist or is not a // directory if asserted to be a directory, as this also causes an // error. return } if len(sourcePath) > 1 && HasTrailingPathSeparator(sourcePath) { // In the case where the source path is a symbolic link AND it ends // with a path separator, we will want to evaluate the symbolic link. trimmedPath := sourcePath[:len(sourcePath)-1] stat, err := os.Lstat(trimmedPath) if err != nil { return nil, err } if stat.Mode()&os.ModeSymlink != 0 { if sourcePath, err = filepath.EvalSymlinks(trimmedPath); err != nil { return nil, err } } } // Separate the source path between it's directory and // the entry in that directory which we are archiving. sourceDir, sourceBase := SplitPathDirEntry(sourcePath) filter := []string{sourceBase} logrus.Debugf("copying %q from %q", sourceBase, sourceDir) return TarWithOptions(sourceDir, &TarOptions{ Compression: Uncompressed, IncludeFiles: filter, IncludeSourceDir: true, }) }
// OptimizedMatches is basically the same as fileutils.Matches() but optimized for archive.go. // It will assume that the inputs have been preprocessed and therefore the function // doen't need to do as much error checking and clean-up. This was done to avoid // repeating these steps on each file being checked during the archive process. // The more generic fileutils.Matches() can't make these assumptions. func OptimizedMatches(file string, patterns []string, patDirs [][]string) (bool, error) { matched := false parentPath := filepath.Dir(file) parentPathDirs := strings.Split(parentPath, "/") for i, pattern := range patterns { negative := false if exclusion(pattern) { negative = true pattern = pattern[1:] } match, err := filepath.Match(pattern, file) if err != nil { return false, err } if !match && parentPath != "." { // Check to see if the pattern matches one of our parent dirs. if len(patDirs[i]) <= len(parentPathDirs) { match, _ = filepath.Match(strings.Join(patDirs[i], "/"), strings.Join(parentPathDirs[:len(patDirs[i])], "/")) } } if match { matched = !negative } } if matched { logrus.Debugf("Skipping excluded path: %s", file) } return matched, nil }
// run is the main broadcast loop, started when the broadcaster is created. // Under normal conditions, it waits for events on the event channel. After // Close is called, this goroutine will exit. func (b *Broadcaster) run() { for { select { case block := <-b.events: for _, sink := range b.sinks { if err := sink.Write(block...); err != nil { logrus.Errorf("broadcaster: error writing events to %v, these events will be lost: %v", sink, err) } } case closing := <-b.closed: // close all the underlying sinks for _, sink := range b.sinks { if err := sink.Close(); err != nil { logrus.Errorf("broadcaster: error closing sink %v: %v", sink, err) } } closing <- struct{}{} logrus.Debugf("broadcaster: closed") return } } }