// checkImageDeleteConflict determines whether there are any conflicts // preventing deletion of the given image from this daemon. A hard conflict is // any image which has the given image as a parent or any running container // using the image. A soft conflict is any tags/digest referencing the given // image or any stopped container using the image. If ignoreSoftConflicts is // true, this function will not check for soft conflict conditions. func (daemon *Daemon) checkImageDeleteConflict(imgID image.ID, mask conflictType) *imageDeleteConflict { // Check if the image has any descendent images. if mask&conflictDependentChild != 0 && len(daemon.imageStore.Children(imgID)) > 0 { return &imageDeleteConflict{ hard: true, imgID: imgID, message: "image has dependent child images", } } if mask&conflictRunningContainer != 0 { // Check if any running container is using the image. for _, container := range daemon.List() { if !container.IsRunning() { // Skip this until we check for soft conflicts later. continue } if container.ImageID == imgID { return &imageDeleteConflict{ imgID: imgID, hard: true, used: true, message: fmt.Sprintf("image is being used by running container %s", stringid.TruncateID(container.ID)), } } } } // Check if any repository tags/digest reference this image. if mask&conflictActiveReference != 0 && len(daemon.referenceStore.References(imgID)) > 0 { return &imageDeleteConflict{ imgID: imgID, message: "image is referenced in one or more repositories", } } if mask&conflictStoppedContainer != 0 { // Check if any stopped containers reference this image. for _, container := range daemon.List() { if container.IsRunning() { // Skip this as it was checked above in hard conflict conditions. continue } if container.ImageID == imgID { return &imageDeleteConflict{ imgID: imgID, used: true, message: fmt.Sprintf("image is being used by stopped container %s", stringid.TruncateID(container.ID)), } } } } return nil }
func (daemon *Daemon) checkImageDeleteSoftConflict(imgID image.ID) *imageDeleteConflict { // Check if any repository tags/digest reference this image. if len(daemon.referenceStore.References(imgID)) > 0 { return &imageDeleteConflict{ imgID: imgID, message: "image is referenced in one or more repositories", } } // Check if any stopped containers reference this image. for _, container := range daemon.List() { if container.IsRunning() { // Skip this as it was checked above in hard conflict conditions. continue } if container.ImageID == imgID { return &imageDeleteConflict{ imgID: imgID, used: true, message: fmt.Sprintf("image is being used by stopped container %s", stringid.TruncateID(container.ID)), } } } return nil }
func (daemon *Daemon) checkImageDeleteHardConflict(imgID image.ID) *imageDeleteConflict { // Check if the image has any descendent images. if len(daemon.imageStore.Children(imgID)) > 0 { return &imageDeleteConflict{ hard: true, imgID: imgID, message: "image has dependent child images", } } // Check if any running container is using the image. for _, container := range daemon.List() { if !container.IsRunning() { // Skip this until we check for soft conflicts later. continue } if container.ImageID == imgID { return &imageDeleteConflict{ imgID: imgID, hard: true, used: true, message: fmt.Sprintf("image is being used by running container %s", stringid.TruncateID(container.ID)), } } } return nil }
// getExecConfig looks up the exec instance by name. If the container associated // with the exec instance is stopped or paused, it will return an error. func (d *Daemon) getExecConfig(name string) (*exec.Config, error) { ec := d.execCommands.Get(name) // If the exec is found but its container is not in the daemon's list of // containers then it must have been deleted, in which case instead of // saying the container isn't running, we should return a 404 so that // the user sees the same error now that they will after the // 5 minute clean-up loop is run which erases old/dead execs. if ec != nil { if container := d.containers.Get(ec.ContainerID); container != nil { if !container.IsRunning() { return nil, fmt.Errorf("Container %s is not running: %s", container.ID, container.State.String()) } if container.IsPaused() { return nil, errExecPaused(container.ID) } if container.IsRestarting() { return nil, errContainerIsRestarting(container.ID) } return ec, nil } } return nil, errExecNotFound(name) }
// ContainerStart starts a container. func (daemon *Daemon) ContainerStart(name string, hostConfig *containertypes.HostConfig) error { container, err := daemon.GetContainer(name) if err != nil { return err } if container.IsPaused() { return fmt.Errorf("Cannot start a paused container, try unpause instead.") } if container.IsRunning() { err := fmt.Errorf("Container already started") return errors.NewErrorWithStatusCode(err, http.StatusNotModified) } // Windows does not have the backwards compatibility issue here. if runtime.GOOS != "windows" { // This is kept for backward compatibility - hostconfig should be passed when // creating a container, not during start. if hostConfig != nil { logrus.Warn("DEPRECATED: Setting host configuration options when the container starts is deprecated and will be removed in Docker 1.12") oldNetworkMode := container.HostConfig.NetworkMode if err := daemon.setSecurityOptions(container, hostConfig); err != nil { return err } if err := daemon.setHostConfig(container, hostConfig); err != nil { return err } newNetworkMode := container.HostConfig.NetworkMode if string(oldNetworkMode) != string(newNetworkMode) { // if user has change the network mode on starting, clean up the // old networks. It is a deprecated feature and will be removed in Docker 1.12 container.NetworkSettings.Networks = nil if err := container.ToDisk(); err != nil { return err } } container.InitDNSHostConfig() } } else { if hostConfig != nil { return fmt.Errorf("Supplying a hostconfig on start is not supported. It should be supplied on create") } } // check if hostConfig is in line with the current system settings. // It may happen cgroups are umounted or the like. if _, err = daemon.verifyContainerSettings(container.HostConfig, nil, false); err != nil { return err } // Adapt for old containers in case we have updates in this function and // old containers never have chance to call the new function in create stage. if err := daemon.adaptContainerSettings(container.HostConfig, false); err != nil { return err } return daemon.containerStart(container) }
// ContainerStart starts a container. func (daemon *Daemon) ContainerStart(name string, hostConfig *runconfig.HostConfig) error { container, err := daemon.Get(name) if err != nil { return err } if container.IsPaused() { return derr.ErrorCodeStartPaused } if container.IsRunning() { return derr.ErrorCodeAlreadyStarted } // Windows does not have the backwards compatibility issue here. if runtime.GOOS != "windows" { // This is kept for backward compatibility - hostconfig should be passed when // creating a container, not during start. if hostConfig != nil { logrus.Warn("DEPRECATED: Setting host configuration options when the container starts is deprecated and will be removed in Docker 1.12") container.Lock() if err := parseSecurityOpt(container, hostConfig); err != nil { container.Unlock() return err } container.Unlock() if err := daemon.adaptContainerSettings(hostConfig, false); err != nil { return err } if err := daemon.setHostConfig(container, hostConfig); err != nil { return err } container.InitDNSHostConfig() } } else { if hostConfig != nil { return derr.ErrorCodeHostConfigStart } } // check if hostConfig is in line with the current system settings. // It may happen cgroups are umounted or the like. if _, err = daemon.verifyContainerSettings(container.HostConfig, nil); err != nil { return err } if err := daemon.containerStart(container); err != nil { return err } return nil }
// ContainerStop looks for the given container and terminates it, // waiting the given number of seconds before forcefully killing the // container. If a negative number of seconds is given, ContainerStop // will wait for a graceful termination. An error is returned if the // container is not found, is already stopped, or if there is a // problem stopping the container. func (daemon *Daemon) ContainerStop(name string, seconds int) error { container, err := daemon.GetContainer(name) if err != nil { return err } if !container.IsRunning() { return derr.ErrorCodeStopped } if err := daemon.containerStop(container, seconds); err != nil { return derr.ErrorCodeCantStop.WithArgs(name, err) } return nil }
func (d *Daemon) getActiveContainer(name string) (*container.Container, error) { container, err := d.GetContainer(name) if err != nil { return nil, err } if !container.IsRunning() { return nil, derr.ErrorCodeNotRunning.WithArgs(name) } if container.IsPaused() { return nil, derr.ErrorCodeExecPaused.WithArgs(name) } return container, nil }
// ContainerStop looks for the given container and terminates it, // waiting the given number of seconds before forcefully killing the // container. If a negative number of seconds is given, ContainerStop // will wait for a graceful termination. An error is returned if the // container is not found, is already stopped, or if there is a // problem stopping the container. func (daemon *Daemon) ContainerStop(name string, seconds int) error { container, err := daemon.GetContainer(name) if err != nil { return err } if !container.IsRunning() { err := fmt.Errorf("Container %s is already stopped", name) return errors.NewErrorWithStatusCode(err, http.StatusNotModified) } if err := daemon.containerStop(container, seconds); err != nil { return fmt.Errorf("Cannot stop container %s: %v", name, err) } return nil }
func (d *Daemon) getActiveContainer(name string) (*container.Container, error) { container, err := d.GetContainer(name) if err != nil { return nil, err } if !container.IsRunning() { return nil, errNotRunning{container.ID} } if container.IsPaused() { return nil, errExecPaused(name) } if container.IsRestarting() { return nil, errContainerIsRestarting(container.ID) } return container, nil }
// Commit creates a new filesystem image from the current state of a container. // The image can optionally be tagged into a repository. func (daemon *Daemon) Commit(name string, c *backend.ContainerCommitConfig) (string, error) { container, err := daemon.GetContainer(name) if err != nil { return "", err } // It is not possible to commit a running container on Windows if runtime.GOOS == "windows" && container.IsRunning() { return "", fmt.Errorf("Windows does not support commit of a running container") } if c.Pause && !container.IsPaused() { daemon.containerPause(container) defer daemon.containerUnpause(container) } newConfig, err := dockerfile.BuildFromConfig(c.Config, c.Changes) if err != nil { return "", err } if c.MergeConfigs { if err := merge(newConfig, container.Config); err != nil { return "", err } } rwTar, err := daemon.exportContainerRw(container) if err != nil { return "", err } defer func() { if rwTar != nil { rwTar.Close() } }() var history []image.History rootFS := image.NewRootFS() osVersion := "" var osFeatures []string if container.ImageID != "" { img, err := daemon.imageStore.Get(container.ImageID) if err != nil { return "", err } history = img.History rootFS = img.RootFS osVersion = img.OSVersion osFeatures = img.OSFeatures } l, err := daemon.layerStore.Register(rwTar, rootFS.ChainID()) if err != nil { return "", err } defer layer.ReleaseAndLog(daemon.layerStore, l) h := image.History{ Author: c.Author, Created: time.Now().UTC(), CreatedBy: strings.Join(container.Config.Cmd, " "), Comment: c.Comment, EmptyLayer: true, } if diffID := l.DiffID(); layer.DigestSHA256EmptyTar != diffID { h.EmptyLayer = false rootFS.Append(diffID) } history = append(history, h) config, err := json.Marshal(&image.Image{ V1Image: image.V1Image{ DockerVersion: dockerversion.Version, Config: newConfig, Architecture: runtime.GOARCH, OS: runtime.GOOS, Container: container.ID, ContainerConfig: *container.Config, Author: c.Author, Created: h.Created, }, RootFS: rootFS, History: history, OSFeatures: osFeatures, OSVersion: osVersion, }) if err != nil { return "", err } id, err := daemon.imageStore.Create(config) if err != nil { return "", err } if container.ImageID != "" { if err := daemon.imageStore.SetParent(id, container.ImageID); err != nil { return "", err } } if c.Repo != "" { newTag, err := reference.WithName(c.Repo) // todo: should move this to API layer if err != nil { return "", err } if c.Tag != "" { if newTag, err = reference.WithTag(newTag, c.Tag); err != nil { return "", err } } if err := daemon.TagImageWithReference(id, newTag); err != nil { return "", err } } attributes := map[string]string{ "comment": c.Comment, } daemon.LogContainerEventWithAttributes(container, "commit", attributes) return id.String(), nil }
// ContainerLogs hooks up a container's stdout and stderr streams // configured with the given struct. func (daemon *Daemon) ContainerLogs(ctx context.Context, containerName string, config *backend.ContainerLogsConfig, started chan struct{}) error { container, err := daemon.GetContainer(containerName) if err != nil { return err } if !(config.ShowStdout || config.ShowStderr) { return fmt.Errorf("You must choose at least one stream") } cLog, err := daemon.getLogger(container) if err != nil { return err } logReader, ok := cLog.(logger.LogReader) if !ok { return logger.ErrReadLogsNotSupported } follow := config.Follow && container.IsRunning() tailLines, err := strconv.Atoi(config.Tail) if err != nil { tailLines = -1 } logrus.Debug("logs: begin stream") var since time.Time if config.Since != "" { s, n, err := timetypes.ParseTimestamps(config.Since, 0) if err != nil { return err } since = time.Unix(s, n) } readConfig := logger.ReadConfig{ Since: since, Tail: tailLines, Follow: follow, } logs := logReader.ReadLogs(readConfig) wf := ioutils.NewWriteFlusher(config.OutStream) defer wf.Close() close(started) wf.Flush() var outStream io.Writer outStream = wf errStream := outStream if !container.Config.Tty { errStream = stdcopy.NewStdWriter(outStream, stdcopy.Stderr) outStream = stdcopy.NewStdWriter(outStream, stdcopy.Stdout) } for { select { case err := <-logs.Err: logrus.Errorf("Error streaming logs: %v", err) return nil case <-ctx.Done(): logs.Close() return nil case msg, ok := <-logs.Msg: if !ok { logrus.Debug("logs: end stream") logs.Close() if cLog != container.LogDriver { // Since the logger isn't cached in the container, which occurs if it is running, it // must get explicitly closed here to avoid leaking it and any file handles it has. if err := cLog.Close(); err != nil { logrus.Errorf("Error closing logger: %v", err) } } return nil } logLine := msg.Line if config.Details { logLine = append([]byte(msg.Attrs.String()+" "), logLine...) } if config.Timestamps { logLine = append([]byte(msg.Timestamp.Format(logger.TimeFormat)+" "), logLine...) } if msg.Source == "stdout" && config.ShowStdout { outStream.Write(logLine) } if msg.Source == "stderr" && config.ShowStderr { errStream.Write(logLine) } } } }
// ContainerLogs hooks up a container's stdout and stderr streams // configured with the given struct. func (daemon *Daemon) ContainerLogs(containerName string, config *backend.ContainerLogsConfig, started chan struct{}) error { container, err := daemon.GetContainer(containerName) if err != nil { return err } if !(config.ShowStdout || config.ShowStderr) { return fmt.Errorf("You must choose at least one stream") } cLog, err := daemon.getLogger(container) if err != nil { return err } logReader, ok := cLog.(logger.LogReader) if !ok { return logger.ErrReadLogsNotSupported } follow := config.Follow && container.IsRunning() tailLines, err := strconv.Atoi(config.Tail) if err != nil { tailLines = -1 } logrus.Debug("logs: begin stream") var since time.Time if config.Since != "" { s, n, err := timetypes.ParseTimestamps(config.Since, 0) if err != nil { return err } since = time.Unix(s, n) } readConfig := logger.ReadConfig{ Since: since, Tail: tailLines, Follow: follow, } logs := logReader.ReadLogs(readConfig) wf := ioutils.NewWriteFlusher(config.OutStream) defer wf.Close() close(started) wf.Flush() var outStream io.Writer = wf errStream := outStream if !container.Config.Tty { errStream = stdcopy.NewStdWriter(outStream, stdcopy.Stderr) outStream = stdcopy.NewStdWriter(outStream, stdcopy.Stdout) } for { select { case err := <-logs.Err: logrus.Errorf("Error streaming logs: %v", err) return nil case <-config.Stop: logs.Close() return nil case msg, ok := <-logs.Msg: if !ok { logrus.Debugf("logs: end stream") return nil } logLine := msg.Line if config.Timestamps { logLine = append([]byte(msg.Timestamp.Format(logger.TimeFormat)+" "), logLine...) } if msg.Source == "stdout" && config.ShowStdout { outStream.Write(logLine) } if msg.Source == "stderr" && config.ShowStderr { errStream.Write(logLine) } } } }
// ContainerStats writes information about the container to the stream // given in the config object. func (daemon *Daemon) ContainerStats(ctx context.Context, prefixOrName string, config *backend.ContainerStatsConfig) error { if runtime.GOOS == "solaris" { return fmt.Errorf("%+v does not support stats", runtime.GOOS) } // Remote API version (used for backwards compatibility) apiVersion := config.Version container, err := daemon.GetContainer(prefixOrName) if err != nil { return err } // If the container is not running and requires no stream, return an empty stats. if !container.IsRunning() && !config.Stream { return json.NewEncoder(config.OutStream).Encode(&types.Stats{}) } outStream := config.OutStream if config.Stream { wf := ioutils.NewWriteFlusher(outStream) defer wf.Close() wf.Flush() outStream = wf } var preCPUStats types.CPUStats var preRead time.Time getStatJSON := func(v interface{}) *types.StatsJSON { ss := v.(types.StatsJSON) ss.PreCPUStats = preCPUStats ss.PreRead = preRead preCPUStats = ss.CPUStats preRead = ss.Read return &ss } enc := json.NewEncoder(outStream) updates := daemon.subscribeToContainerStats(container) defer daemon.unsubscribeToContainerStats(container, updates) noStreamFirstFrame := true for { select { case v, ok := <-updates: if !ok { return nil } var statsJSON interface{} statsJSONPost120 := getStatJSON(v) if versions.LessThan(apiVersion, "1.21") { if runtime.GOOS == "windows" { return errors.New("API versions pre v1.21 do not support stats on Windows") } var ( rxBytes uint64 rxPackets uint64 rxErrors uint64 rxDropped uint64 txBytes uint64 txPackets uint64 txErrors uint64 txDropped uint64 ) for _, v := range statsJSONPost120.Networks { rxBytes += v.RxBytes rxPackets += v.RxPackets rxErrors += v.RxErrors rxDropped += v.RxDropped txBytes += v.TxBytes txPackets += v.TxPackets txErrors += v.TxErrors txDropped += v.TxDropped } statsJSON = &v1p20.StatsJSON{ Stats: statsJSONPost120.Stats, Network: types.NetworkStats{ RxBytes: rxBytes, RxPackets: rxPackets, RxErrors: rxErrors, RxDropped: rxDropped, TxBytes: txBytes, TxPackets: txPackets, TxErrors: txErrors, TxDropped: txDropped, }, } } else { statsJSON = statsJSONPost120 } if !config.Stream && noStreamFirstFrame { // prime the cpu stats so they aren't 0 in the final output noStreamFirstFrame = false continue } if err := enc.Encode(statsJSON); err != nil { return err } if !config.Stream { return nil } case <-ctx.Done(): return nil } } }
// ContainerLogs hooks up a container's stdout and stderr streams // configured with the given struct. func (daemon *Daemon) ContainerLogs(containerName string, config *ContainerLogsConfig) error { container, err := daemon.GetContainer(containerName) if err != nil { return derr.ErrorCodeNoSuchContainer.WithArgs(containerName) } if !(config.UseStdout || config.UseStderr) { return derr.ErrorCodeNeedStream } outStream := config.OutStream errStream := outStream if !container.Config.Tty { errStream = stdcopy.NewStdWriter(outStream, stdcopy.Stderr) outStream = stdcopy.NewStdWriter(outStream, stdcopy.Stdout) } config.OutStream = outStream cLog, err := daemon.getLogger(container) if err != nil { return err } logReader, ok := cLog.(logger.LogReader) if !ok { return logger.ErrReadLogsNotSupported } follow := config.Follow && container.IsRunning() tailLines, err := strconv.Atoi(config.Tail) if err != nil { tailLines = -1 } logrus.Debug("logs: begin stream") readConfig := logger.ReadConfig{ Since: config.Since, Tail: tailLines, Follow: follow, } logs := logReader.ReadLogs(readConfig) for { select { case err := <-logs.Err: logrus.Errorf("Error streaming logs: %v", err) return nil case <-config.Stop: logs.Close() return nil case msg, ok := <-logs.Msg: if !ok { logrus.Debugf("logs: end stream") return nil } logLine := msg.Line if config.Timestamps { logLine = append([]byte(msg.Timestamp.Format(logger.TimeFormat)+" "), logLine...) } if msg.Source == "stdout" && config.UseStdout { outStream.Write(logLine) } if msg.Source == "stderr" && config.UseStderr { errStream.Write(logLine) } } } }