Cleanup: move image management and logging out of deprecated Server
| ... | ... |
@@ -8,6 +8,7 @@ import ( |
| 8 | 8 |
"github.com/docker/docker/daemon/networkdriver/bridge" |
| 9 | 9 |
"github.com/docker/docker/dockerversion" |
| 10 | 10 |
"github.com/docker/docker/engine" |
| 11 |
+ "github.com/docker/docker/events" |
|
| 11 | 12 |
"github.com/docker/docker/pkg/parsers/kernel" |
| 12 | 13 |
"github.com/docker/docker/registry" |
| 13 | 14 |
"github.com/docker/docker/server" |
| ... | ... |
@@ -20,6 +21,9 @@ func Register(eng *engine.Engine) error {
|
| 20 | 20 |
if err := remote(eng); err != nil {
|
| 21 | 21 |
return err |
| 22 | 22 |
} |
| 23 |
+ if err := events.New().Install(eng); err != nil {
|
|
| 24 |
+ return err |
|
| 25 |
+ } |
|
| 23 | 26 |
if err := eng.Register("version", dockerVersion); err != nil {
|
| 24 | 27 |
return err |
| 25 | 28 |
} |
| ... | ... |
@@ -168,6 +168,13 @@ func (container *Container) WriteHostConfig() error {
|
| 168 | 168 |
return ioutil.WriteFile(pth, data, 0666) |
| 169 | 169 |
} |
| 170 | 170 |
|
| 171 |
+func (container *Container) LogEvent(action string) {
|
|
| 172 |
+ d := container.daemon |
|
| 173 |
+ if err := d.eng.Job("log", action, container.ID, d.Repositories().ImageName(container.Image)).Run(); err != nil {
|
|
| 174 |
+ utils.Errorf("Error running container: %s", err)
|
|
| 175 |
+ } |
|
| 176 |
+} |
|
| 177 |
+ |
|
| 171 | 178 |
func (container *Container) getResourcePath(path string) (string, error) {
|
| 172 | 179 |
cleanPath := filepath.Join("/", path)
|
| 173 | 180 |
return symlink.FollowSymlinkInScope(filepath.Join(container.basefs, cleanPath), container.basefs) |
| ... | ... |
@@ -508,7 +515,7 @@ func (container *Container) monitor(callback execdriver.StartCallback) error {
|
| 508 | 508 |
container.stdin, container.stdinPipe = io.Pipe() |
| 509 | 509 |
} |
| 510 | 510 |
if container.daemon != nil && container.daemon.srv != nil {
|
| 511 |
- container.daemon.srv.LogEvent("die", container.ID, container.daemon.repositories.ImageName(container.Image))
|
|
| 511 |
+ container.LogEvent("die")
|
|
| 512 | 512 |
} |
| 513 | 513 |
if container.daemon != nil && container.daemon.srv != nil && container.daemon.srv.IsRunning() {
|
| 514 | 514 |
// FIXME: here is race condition between two RUN instructions in Dockerfile |
| ... | ... |
@@ -40,7 +40,7 @@ func (daemon *Daemon) ContainerCreate(job *engine.Job) engine.Status {
|
| 40 | 40 |
if !container.Config.NetworkDisabled && daemon.SystemConfig().IPv4ForwardingDisabled {
|
| 41 | 41 |
job.Errorf("IPv4 forwarding is disabled.\n")
|
| 42 | 42 |
} |
| 43 |
- job.Eng.Job("log", "create", container.ID, daemon.Repositories().ImageName(container.Image)).Run()
|
|
| 43 |
+ container.LogEvent("create")
|
|
| 44 | 44 |
// FIXME: this is necessary because daemon.Create might return a nil container |
| 45 | 45 |
// with a non-nil error. This should not happen! Once it's fixed we |
| 46 | 46 |
// can remove this workaround. |
| ... | ... |
@@ -107,6 +107,7 @@ type Daemon struct {
|
| 107 | 107 |
func (daemon *Daemon) Install(eng *engine.Engine) error {
|
| 108 | 108 |
// FIXME: rename "delete" to "rm" for consistency with the CLI command |
| 109 | 109 |
// FIXME: rename ContainerDestroy to ContainerRm for consistency with the CLI command |
| 110 |
+ // FIXME: remove ImageDelete's dependency on Daemon, then move to graph/ |
|
| 110 | 111 |
for name, method := range map[string]engine.Handler{
|
| 111 | 112 |
"attach": daemon.ContainerAttach, |
| 112 | 113 |
"commit": daemon.ContainerCommit, |
| ... | ... |
@@ -127,6 +128,7 @@ func (daemon *Daemon) Install(eng *engine.Engine) error {
|
| 127 | 127 |
"top": daemon.ContainerTop, |
| 128 | 128 |
"unpause": daemon.ContainerUnpause, |
| 129 | 129 |
"wait": daemon.ContainerWait, |
| 130 |
+ "image_delete": daemon.ImageDelete, // FIXME: see above |
|
| 130 | 131 |
} {
|
| 131 | 132 |
if err := eng.Register(name, method); err != nil {
|
| 132 | 133 |
return err |
| ... | ... |
@@ -70,7 +70,7 @@ func (daemon *Daemon) ContainerDestroy(job *engine.Job) engine.Status {
|
| 70 | 70 |
if err := daemon.Destroy(container); err != nil {
|
| 71 | 71 |
return job.Errorf("Cannot destroy container %s: %s", name, err)
|
| 72 | 72 |
} |
| 73 |
- job.Eng.Job("log", "destroy", container.ID, daemon.Repositories().ImageName(container.Image)).Run()
|
|
| 73 |
+ container.LogEvent("destroy")
|
|
| 74 | 74 |
|
| 75 | 75 |
if removeVolume {
|
| 76 | 76 |
var ( |
| ... | ... |
@@ -23,7 +23,7 @@ func (daemon *Daemon) ContainerExport(job *engine.Job) engine.Status {
|
| 23 | 23 |
return job.Errorf("%s: %s", name, err)
|
| 24 | 24 |
} |
| 25 | 25 |
// FIXME: factor job-specific LogEvent to engine.Job.Run() |
| 26 |
- job.Eng.Job("log", "export", container.ID, daemon.Repositories().ImageName(container.Image)).Run()
|
|
| 26 |
+ container.LogEvent("export")
|
|
| 27 | 27 |
return engine.StatusOK |
| 28 | 28 |
} |
| 29 | 29 |
return job.Errorf("No such container: %s", name)
|
| 30 | 30 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,156 @@ |
| 0 |
+package daemon |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ "strings" |
|
| 5 |
+ |
|
| 6 |
+ "github.com/docker/docker/engine" |
|
| 7 |
+ "github.com/docker/docker/graph" |
|
| 8 |
+ "github.com/docker/docker/image" |
|
| 9 |
+ "github.com/docker/docker/pkg/parsers" |
|
| 10 |
+ "github.com/docker/docker/utils" |
|
| 11 |
+) |
|
| 12 |
+ |
|
| 13 |
+func (daemon *Daemon) ImageDelete(job *engine.Job) engine.Status {
|
|
| 14 |
+ if n := len(job.Args); n != 1 {
|
|
| 15 |
+ return job.Errorf("Usage: %s IMAGE", job.Name)
|
|
| 16 |
+ } |
|
| 17 |
+ imgs := engine.NewTable("", 0)
|
|
| 18 |
+ if err := daemon.DeleteImage(job.Eng, job.Args[0], imgs, true, job.GetenvBool("force"), job.GetenvBool("noprune")); err != nil {
|
|
| 19 |
+ return job.Error(err) |
|
| 20 |
+ } |
|
| 21 |
+ if len(imgs.Data) == 0 {
|
|
| 22 |
+ return job.Errorf("Conflict, %s wasn't deleted", job.Args[0])
|
|
| 23 |
+ } |
|
| 24 |
+ if _, err := imgs.WriteListTo(job.Stdout); err != nil {
|
|
| 25 |
+ return job.Error(err) |
|
| 26 |
+ } |
|
| 27 |
+ return engine.StatusOK |
|
| 28 |
+} |
|
| 29 |
+ |
|
| 30 |
+// FIXME: make this private and use the job instead |
|
| 31 |
+func (daemon *Daemon) DeleteImage(eng *engine.Engine, name string, imgs *engine.Table, first, force, noprune bool) error {
|
|
| 32 |
+ var ( |
|
| 33 |
+ repoName, tag string |
|
| 34 |
+ tags = []string{}
|
|
| 35 |
+ tagDeleted bool |
|
| 36 |
+ ) |
|
| 37 |
+ |
|
| 38 |
+ // FIXME: please respect DRY and centralize repo+tag parsing in a single central place! -- shykes |
|
| 39 |
+ repoName, tag = parsers.ParseRepositoryTag(name) |
|
| 40 |
+ if tag == "" {
|
|
| 41 |
+ tag = graph.DEFAULTTAG |
|
| 42 |
+ } |
|
| 43 |
+ |
|
| 44 |
+ img, err := daemon.Repositories().LookupImage(name) |
|
| 45 |
+ if err != nil {
|
|
| 46 |
+ if r, _ := daemon.Repositories().Get(repoName); r != nil {
|
|
| 47 |
+ return fmt.Errorf("No such image: %s:%s", repoName, tag)
|
|
| 48 |
+ } |
|
| 49 |
+ return fmt.Errorf("No such image: %s", name)
|
|
| 50 |
+ } |
|
| 51 |
+ |
|
| 52 |
+ if strings.Contains(img.ID, name) {
|
|
| 53 |
+ repoName = "" |
|
| 54 |
+ tag = "" |
|
| 55 |
+ } |
|
| 56 |
+ |
|
| 57 |
+ byParents, err := daemon.Graph().ByParent() |
|
| 58 |
+ if err != nil {
|
|
| 59 |
+ return err |
|
| 60 |
+ } |
|
| 61 |
+ |
|
| 62 |
+ //If delete by id, see if the id belong only to one repository |
|
| 63 |
+ if repoName == "" {
|
|
| 64 |
+ for _, repoAndTag := range daemon.Repositories().ByID()[img.ID] {
|
|
| 65 |
+ parsedRepo, parsedTag := parsers.ParseRepositoryTag(repoAndTag) |
|
| 66 |
+ if repoName == "" || repoName == parsedRepo {
|
|
| 67 |
+ repoName = parsedRepo |
|
| 68 |
+ if parsedTag != "" {
|
|
| 69 |
+ tags = append(tags, parsedTag) |
|
| 70 |
+ } |
|
| 71 |
+ } else if repoName != parsedRepo && !force {
|
|
| 72 |
+ // the id belongs to multiple repos, like base:latest and user:test, |
|
| 73 |
+ // in that case return conflict |
|
| 74 |
+ return fmt.Errorf("Conflict, cannot delete image %s because it is tagged in multiple repositories, use -f to force", name)
|
|
| 75 |
+ } |
|
| 76 |
+ } |
|
| 77 |
+ } else {
|
|
| 78 |
+ tags = append(tags, tag) |
|
| 79 |
+ } |
|
| 80 |
+ |
|
| 81 |
+ if !first && len(tags) > 0 {
|
|
| 82 |
+ return nil |
|
| 83 |
+ } |
|
| 84 |
+ |
|
| 85 |
+ //Untag the current image |
|
| 86 |
+ for _, tag := range tags {
|
|
| 87 |
+ tagDeleted, err = daemon.Repositories().Delete(repoName, tag) |
|
| 88 |
+ if err != nil {
|
|
| 89 |
+ return err |
|
| 90 |
+ } |
|
| 91 |
+ if tagDeleted {
|
|
| 92 |
+ out := &engine.Env{}
|
|
| 93 |
+ out.Set("Untagged", repoName+":"+tag)
|
|
| 94 |
+ imgs.Add(out) |
|
| 95 |
+ eng.Job("log", "untag", img.ID, "").Run()
|
|
| 96 |
+ } |
|
| 97 |
+ } |
|
| 98 |
+ tags = daemon.Repositories().ByID()[img.ID] |
|
| 99 |
+ if (len(tags) <= 1 && repoName == "") || len(tags) == 0 {
|
|
| 100 |
+ if len(byParents[img.ID]) == 0 {
|
|
| 101 |
+ if err := daemon.canDeleteImage(img.ID, force, tagDeleted); err != nil {
|
|
| 102 |
+ return err |
|
| 103 |
+ } |
|
| 104 |
+ if err := daemon.Repositories().DeleteAll(img.ID); err != nil {
|
|
| 105 |
+ return err |
|
| 106 |
+ } |
|
| 107 |
+ if err := daemon.Graph().Delete(img.ID); err != nil {
|
|
| 108 |
+ return err |
|
| 109 |
+ } |
|
| 110 |
+ out := &engine.Env{}
|
|
| 111 |
+ out.Set("Deleted", img.ID)
|
|
| 112 |
+ imgs.Add(out) |
|
| 113 |
+ eng.Job("log", "delete", img.ID, "").Run()
|
|
| 114 |
+ if img.Parent != "" && !noprune {
|
|
| 115 |
+ err := daemon.DeleteImage(eng, img.Parent, imgs, false, force, noprune) |
|
| 116 |
+ if first {
|
|
| 117 |
+ return err |
|
| 118 |
+ } |
|
| 119 |
+ |
|
| 120 |
+ } |
|
| 121 |
+ |
|
| 122 |
+ } |
|
| 123 |
+ } |
|
| 124 |
+ return nil |
|
| 125 |
+} |
|
| 126 |
+ |
|
| 127 |
+func (daemon *Daemon) canDeleteImage(imgID string, force, untagged bool) error {
|
|
| 128 |
+ var message string |
|
| 129 |
+ if untagged {
|
|
| 130 |
+ message = " (docker untagged the image)" |
|
| 131 |
+ } |
|
| 132 |
+ for _, container := range daemon.List() {
|
|
| 133 |
+ parent, err := daemon.Repositories().LookupImage(container.Image) |
|
| 134 |
+ if err != nil {
|
|
| 135 |
+ return err |
|
| 136 |
+ } |
|
| 137 |
+ |
|
| 138 |
+ if err := parent.WalkHistory(func(p *image.Image) error {
|
|
| 139 |
+ if imgID == p.ID {
|
|
| 140 |
+ if container.State.IsRunning() {
|
|
| 141 |
+ if force {
|
|
| 142 |
+ return fmt.Errorf("Conflict, cannot force delete %s because the running container %s is using it%s, stop it and retry", utils.TruncateID(imgID), utils.TruncateID(container.ID), message)
|
|
| 143 |
+ } |
|
| 144 |
+ return fmt.Errorf("Conflict, cannot delete %s because the running container %s is using it%s, stop it and use -f to force", utils.TruncateID(imgID), utils.TruncateID(container.ID), message)
|
|
| 145 |
+ } else if !force {
|
|
| 146 |
+ return fmt.Errorf("Conflict, cannot delete %s because the container %s is using it%s, use -f to force", utils.TruncateID(imgID), utils.TruncateID(container.ID), message)
|
|
| 147 |
+ } |
|
| 148 |
+ } |
|
| 149 |
+ return nil |
|
| 150 |
+ }); err != nil {
|
|
| 151 |
+ return err |
|
| 152 |
+ } |
|
| 153 |
+ } |
|
| 154 |
+ return nil |
|
| 155 |
+} |
| ... | ... |
@@ -44,7 +44,7 @@ func (daemon *Daemon) ContainerKill(job *engine.Job) engine.Status {
|
| 44 | 44 |
if err := container.Kill(); err != nil {
|
| 45 | 45 |
return job.Errorf("Cannot kill container %s: %s", name, err)
|
| 46 | 46 |
} |
| 47 |
- job.Eng.Job("log", "kill", container.ID, daemon.Repositories().ImageName(container.Image)).Run()
|
|
| 47 |
+ container.LogEvent("kill")
|
|
| 48 | 48 |
} else {
|
| 49 | 49 |
// Otherwise, just send the requested signal |
| 50 | 50 |
if err := container.KillSig(int(sig)); err != nil {
|
| ... | ... |
@@ -16,7 +16,7 @@ func (daemon *Daemon) ContainerPause(job *engine.Job) engine.Status {
|
| 16 | 16 |
if err := container.Pause(); err != nil {
|
| 17 | 17 |
return job.Errorf("Cannot pause container %s: %s", name, err)
|
| 18 | 18 |
} |
| 19 |
- job.Eng.Job("log", "pause", container.ID, daemon.Repositories().ImageName(container.Image)).Run()
|
|
| 19 |
+ container.LogEvent("pause")
|
|
| 20 | 20 |
return engine.StatusOK |
| 21 | 21 |
} |
| 22 | 22 |
|
| ... | ... |
@@ -32,6 +32,6 @@ func (daemon *Daemon) ContainerUnpause(job *engine.Job) engine.Status {
|
| 32 | 32 |
if err := container.Unpause(); err != nil {
|
| 33 | 33 |
return job.Errorf("Cannot unpause container %s: %s", name, err)
|
| 34 | 34 |
} |
| 35 |
- job.Eng.Job("log", "unpause", container.ID, daemon.Repositories().ImageName(container.Image)).Run()
|
|
| 35 |
+ container.LogEvent("unpause")
|
|
| 36 | 36 |
return engine.StatusOK |
| 37 | 37 |
} |
| ... | ... |
@@ -19,7 +19,7 @@ func (daemon *Daemon) ContainerRestart(job *engine.Job) engine.Status {
|
| 19 | 19 |
if err := container.Restart(int(t)); err != nil {
|
| 20 | 20 |
return job.Errorf("Cannot restart container %s: %s\n", name, err)
|
| 21 | 21 |
} |
| 22 |
- job.Eng.Job("log", "restart", container.ID, daemon.Repositories().ImageName(container.Image)).Run()
|
|
| 22 |
+ container.LogEvent("restart")
|
|
| 23 | 23 |
} else {
|
| 24 | 24 |
return job.Errorf("No such container: %s\n", name)
|
| 25 | 25 |
} |
| ... | ... |
@@ -36,8 +36,7 @@ func (daemon *Daemon) ContainerStart(job *engine.Job) engine.Status {
|
| 36 | 36 |
if err := container.Start(); err != nil {
|
| 37 | 37 |
return job.Errorf("Cannot start container %s: %s", name, err)
|
| 38 | 38 |
} |
| 39 |
- job.Eng.Job("log", "start", container.ID, daemon.Repositories().ImageName(container.Image)).Run()
|
|
| 40 |
- |
|
| 39 |
+ container.LogEvent("start")
|
|
| 41 | 40 |
return engine.StatusOK |
| 42 | 41 |
} |
| 43 | 42 |
|
| ... | ... |
@@ -22,7 +22,7 @@ func (daemon *Daemon) ContainerStop(job *engine.Job) engine.Status {
|
| 22 | 22 |
if err := container.Stop(int(t)); err != nil {
|
| 23 | 23 |
return job.Errorf("Cannot stop container %s: %s\n", name, err)
|
| 24 | 24 |
} |
| 25 |
- job.Eng.Job("log", "stop", container.ID, daemon.Repositories().ImageName(container.Image)).Run()
|
|
| 25 |
+ container.LogEvent("stop")
|
|
| 26 | 26 |
} else {
|
| 27 | 27 |
return job.Errorf("No such container: %s\n", name)
|
| 28 | 28 |
} |
| 29 | 29 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,176 @@ |
| 0 |
+package events |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "encoding/json" |
|
| 4 |
+ "sync" |
|
| 5 |
+ "time" |
|
| 6 |
+ |
|
| 7 |
+ "github.com/docker/docker/engine" |
|
| 8 |
+ "github.com/docker/docker/utils" |
|
| 9 |
+) |
|
| 10 |
+ |
|
| 11 |
+const eventsLimit = 64 |
|
| 12 |
+ |
|
| 13 |
+type listener chan<- *utils.JSONMessage |
|
| 14 |
+ |
|
| 15 |
+type Events struct {
|
|
| 16 |
+ mu sync.RWMutex |
|
| 17 |
+ events []*utils.JSONMessage |
|
| 18 |
+ subscribers []listener |
|
| 19 |
+} |
|
| 20 |
+ |
|
| 21 |
+func New() *Events {
|
|
| 22 |
+ return &Events{
|
|
| 23 |
+ events: make([]*utils.JSONMessage, 0, eventsLimit), |
|
| 24 |
+ } |
|
| 25 |
+} |
|
| 26 |
+ |
|
| 27 |
+// Install installs events public api in docker engine |
|
| 28 |
+func (e *Events) Install(eng *engine.Engine) error {
|
|
| 29 |
+ // Here you should describe public interface |
|
| 30 |
+ jobs := map[string]engine.Handler{
|
|
| 31 |
+ "events": e.Get, |
|
| 32 |
+ "log": e.Log, |
|
| 33 |
+ "subscribers_count": e.SubscribersCount, |
|
| 34 |
+ } |
|
| 35 |
+ for name, job := range jobs {
|
|
| 36 |
+ if err := eng.Register(name, job); err != nil {
|
|
| 37 |
+ return err |
|
| 38 |
+ } |
|
| 39 |
+ } |
|
| 40 |
+ return nil |
|
| 41 |
+} |
|
| 42 |
+ |
|
| 43 |
+func (e *Events) Get(job *engine.Job) engine.Status {
|
|
| 44 |
+ var ( |
|
| 45 |
+ since = job.GetenvInt64("since")
|
|
| 46 |
+ until = job.GetenvInt64("until")
|
|
| 47 |
+ timeout = time.NewTimer(time.Unix(until, 0).Sub(time.Now())) |
|
| 48 |
+ ) |
|
| 49 |
+ |
|
| 50 |
+ // If no until, disable timeout |
|
| 51 |
+ if until == 0 {
|
|
| 52 |
+ timeout.Stop() |
|
| 53 |
+ } |
|
| 54 |
+ |
|
| 55 |
+ listener := make(chan *utils.JSONMessage) |
|
| 56 |
+ e.subscribe(listener) |
|
| 57 |
+ defer e.unsubscribe(listener) |
|
| 58 |
+ |
|
| 59 |
+ job.Stdout.Write(nil) |
|
| 60 |
+ |
|
| 61 |
+ // Resend every event in the [since, until] time interval. |
|
| 62 |
+ if since != 0 {
|
|
| 63 |
+ if err := e.writeCurrent(job, since, until); err != nil {
|
|
| 64 |
+ return job.Error(err) |
|
| 65 |
+ } |
|
| 66 |
+ } |
|
| 67 |
+ |
|
| 68 |
+ for {
|
|
| 69 |
+ select {
|
|
| 70 |
+ case event, ok := <-listener: |
|
| 71 |
+ if !ok {
|
|
| 72 |
+ return engine.StatusOK |
|
| 73 |
+ } |
|
| 74 |
+ if err := writeEvent(job, event); err != nil {
|
|
| 75 |
+ return job.Error(err) |
|
| 76 |
+ } |
|
| 77 |
+ case <-timeout.C: |
|
| 78 |
+ return engine.StatusOK |
|
| 79 |
+ } |
|
| 80 |
+ } |
|
| 81 |
+} |
|
| 82 |
+ |
|
| 83 |
+func (e *Events) Log(job *engine.Job) engine.Status {
|
|
| 84 |
+ if len(job.Args) != 3 {
|
|
| 85 |
+ return job.Errorf("usage: %s ACTION ID FROM", job.Name)
|
|
| 86 |
+ } |
|
| 87 |
+ // not waiting for receivers |
|
| 88 |
+ go e.log(job.Args[0], job.Args[1], job.Args[2]) |
|
| 89 |
+ return engine.StatusOK |
|
| 90 |
+} |
|
| 91 |
+ |
|
| 92 |
+func (e *Events) SubscribersCount(job *engine.Job) engine.Status {
|
|
| 93 |
+ ret := &engine.Env{}
|
|
| 94 |
+ ret.SetInt("count", e.subscribersCount())
|
|
| 95 |
+ ret.WriteTo(job.Stdout) |
|
| 96 |
+ return engine.StatusOK |
|
| 97 |
+} |
|
| 98 |
+ |
|
| 99 |
+func writeEvent(job *engine.Job, event *utils.JSONMessage) error {
|
|
| 100 |
+ // When sending an event JSON serialization errors are ignored, but all |
|
| 101 |
+ // other errors lead to the eviction of the listener. |
|
| 102 |
+ if b, err := json.Marshal(event); err == nil {
|
|
| 103 |
+ if _, err = job.Stdout.Write(b); err != nil {
|
|
| 104 |
+ return err |
|
| 105 |
+ } |
|
| 106 |
+ } |
|
| 107 |
+ return nil |
|
| 108 |
+} |
|
| 109 |
+ |
|
| 110 |
+func (e *Events) writeCurrent(job *engine.Job, since, until int64) error {
|
|
| 111 |
+ e.mu.RLock() |
|
| 112 |
+ for _, event := range e.events {
|
|
| 113 |
+ if event.Time >= since && (event.Time <= until || until == 0) {
|
|
| 114 |
+ if err := writeEvent(job, event); err != nil {
|
|
| 115 |
+ e.mu.RUnlock() |
|
| 116 |
+ return err |
|
| 117 |
+ } |
|
| 118 |
+ } |
|
| 119 |
+ } |
|
| 120 |
+ e.mu.RUnlock() |
|
| 121 |
+ return nil |
|
| 122 |
+} |
|
| 123 |
+ |
|
| 124 |
+func (e *Events) subscribersCount() int {
|
|
| 125 |
+ e.mu.RLock() |
|
| 126 |
+ c := len(e.subscribers) |
|
| 127 |
+ e.mu.RUnlock() |
|
| 128 |
+ return c |
|
| 129 |
+} |
|
| 130 |
+ |
|
| 131 |
+func (e *Events) log(action, id, from string) {
|
|
| 132 |
+ e.mu.Lock() |
|
| 133 |
+ now := time.Now().UTC().Unix() |
|
| 134 |
+ jm := &utils.JSONMessage{Status: action, ID: id, From: from, Time: now}
|
|
| 135 |
+ if len(e.events) == cap(e.events) {
|
|
| 136 |
+ // discard oldest event |
|
| 137 |
+ copy(e.events, e.events[1:]) |
|
| 138 |
+ e.events[len(e.events)-1] = jm |
|
| 139 |
+ } else {
|
|
| 140 |
+ e.events = append(e.events, jm) |
|
| 141 |
+ } |
|
| 142 |
+ for _, s := range e.subscribers {
|
|
| 143 |
+ // We give each subscriber a 100ms time window to receive the event, |
|
| 144 |
+ // after which we move to the next. |
|
| 145 |
+ select {
|
|
| 146 |
+ case s <- jm: |
|
| 147 |
+ case <-time.After(100 * time.Millisecond): |
|
| 148 |
+ } |
|
| 149 |
+ } |
|
| 150 |
+ e.mu.Unlock() |
|
| 151 |
+} |
|
| 152 |
+ |
|
| 153 |
+func (e *Events) subscribe(l listener) {
|
|
| 154 |
+ e.mu.Lock() |
|
| 155 |
+ e.subscribers = append(e.subscribers, l) |
|
| 156 |
+ e.mu.Unlock() |
|
| 157 |
+} |
|
| 158 |
+ |
|
| 159 |
+// unsubscribe closes and removes the specified listener from the list of |
|
| 160 |
+// previously registed ones. |
|
| 161 |
+// It returns a boolean value indicating if the listener was successfully |
|
| 162 |
+// found, closed and unregistered. |
|
| 163 |
+func (e *Events) unsubscribe(l listener) bool {
|
|
| 164 |
+ e.mu.Lock() |
|
| 165 |
+ for i, subscriber := range e.subscribers {
|
|
| 166 |
+ if subscriber == l {
|
|
| 167 |
+ close(l) |
|
| 168 |
+ e.subscribers = append(e.subscribers[:i], e.subscribers[i+1:]...) |
|
| 169 |
+ e.mu.Unlock() |
|
| 170 |
+ return true |
|
| 171 |
+ } |
|
| 172 |
+ } |
|
| 173 |
+ e.mu.Unlock() |
|
| 174 |
+ return false |
|
| 175 |
+} |
| 0 | 176 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,154 @@ |
| 0 |
+package events |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "bytes" |
|
| 4 |
+ "encoding/json" |
|
| 5 |
+ "fmt" |
|
| 6 |
+ "io" |
|
| 7 |
+ "testing" |
|
| 8 |
+ "time" |
|
| 9 |
+ |
|
| 10 |
+ "github.com/docker/docker/engine" |
|
| 11 |
+ "github.com/docker/docker/utils" |
|
| 12 |
+) |
|
| 13 |
+ |
|
| 14 |
+func TestEventsPublish(t *testing.T) {
|
|
| 15 |
+ e := New() |
|
| 16 |
+ l1 := make(chan *utils.JSONMessage) |
|
| 17 |
+ l2 := make(chan *utils.JSONMessage) |
|
| 18 |
+ e.subscribe(l1) |
|
| 19 |
+ e.subscribe(l2) |
|
| 20 |
+ count := e.subscribersCount() |
|
| 21 |
+ if count != 2 {
|
|
| 22 |
+ t.Fatalf("Must be 2 subscribers, got %d", count)
|
|
| 23 |
+ } |
|
| 24 |
+ go e.log("test", "cont", "image")
|
|
| 25 |
+ select {
|
|
| 26 |
+ case msg := <-l1: |
|
| 27 |
+ if len(e.events) != 1 {
|
|
| 28 |
+ t.Fatalf("Must be only one event, got %d", len(e.events))
|
|
| 29 |
+ } |
|
| 30 |
+ if msg.Status != "test" {
|
|
| 31 |
+ t.Fatalf("Status should be test, got %s", msg.Status)
|
|
| 32 |
+ } |
|
| 33 |
+ if msg.ID != "cont" {
|
|
| 34 |
+ t.Fatalf("ID should be cont, got %s", msg.ID)
|
|
| 35 |
+ } |
|
| 36 |
+ if msg.From != "image" {
|
|
| 37 |
+ t.Fatalf("From should be image, got %s", msg.From)
|
|
| 38 |
+ } |
|
| 39 |
+ case <-time.After(1 * time.Second): |
|
| 40 |
+ t.Fatal("Timeout waiting for broadcasted message")
|
|
| 41 |
+ } |
|
| 42 |
+ select {
|
|
| 43 |
+ case msg := <-l2: |
|
| 44 |
+ if len(e.events) != 1 {
|
|
| 45 |
+ t.Fatalf("Must be only one event, got %d", len(e.events))
|
|
| 46 |
+ } |
|
| 47 |
+ if msg.Status != "test" {
|
|
| 48 |
+ t.Fatalf("Status should be test, got %s", msg.Status)
|
|
| 49 |
+ } |
|
| 50 |
+ if msg.ID != "cont" {
|
|
| 51 |
+ t.Fatalf("ID should be cont, got %s", msg.ID)
|
|
| 52 |
+ } |
|
| 53 |
+ if msg.From != "image" {
|
|
| 54 |
+ t.Fatalf("From should be image, got %s", msg.From)
|
|
| 55 |
+ } |
|
| 56 |
+ case <-time.After(1 * time.Second): |
|
| 57 |
+ t.Fatal("Timeout waiting for broadcasted message")
|
|
| 58 |
+ } |
|
| 59 |
+} |
|
| 60 |
+ |
|
| 61 |
+func TestEventsPublishTimeout(t *testing.T) {
|
|
| 62 |
+ e := New() |
|
| 63 |
+ l := make(chan *utils.JSONMessage) |
|
| 64 |
+ e.subscribe(l) |
|
| 65 |
+ |
|
| 66 |
+ c := make(chan struct{})
|
|
| 67 |
+ go func() {
|
|
| 68 |
+ e.log("test", "cont", "image")
|
|
| 69 |
+ close(c) |
|
| 70 |
+ }() |
|
| 71 |
+ |
|
| 72 |
+ select {
|
|
| 73 |
+ case <-c: |
|
| 74 |
+ case <-time.After(time.Second): |
|
| 75 |
+ t.Fatal("Timeout publishing message")
|
|
| 76 |
+ } |
|
| 77 |
+} |
|
| 78 |
+ |
|
| 79 |
+func TestLogEvents(t *testing.T) {
|
|
| 80 |
+ e := New() |
|
| 81 |
+ eng := engine.New() |
|
| 82 |
+ if err := e.Install(eng); err != nil {
|
|
| 83 |
+ t.Fatal(err) |
|
| 84 |
+ } |
|
| 85 |
+ |
|
| 86 |
+ for i := 0; i < eventsLimit+16; i++ {
|
|
| 87 |
+ action := fmt.Sprintf("action_%d", i)
|
|
| 88 |
+ id := fmt.Sprintf("cont_%d", i)
|
|
| 89 |
+ from := fmt.Sprintf("image_%d", i)
|
|
| 90 |
+ job := eng.Job("log", action, id, from)
|
|
| 91 |
+ if err := job.Run(); err != nil {
|
|
| 92 |
+ t.Fatal(err) |
|
| 93 |
+ } |
|
| 94 |
+ } |
|
| 95 |
+ time.Sleep(50 * time.Millisecond) |
|
| 96 |
+ if len(e.events) != eventsLimit {
|
|
| 97 |
+ t.Fatalf("Must be %d events, got %d", eventsLimit, len(e.events))
|
|
| 98 |
+ } |
|
| 99 |
+ |
|
| 100 |
+ job := eng.Job("events")
|
|
| 101 |
+ job.SetenvInt64("since", 1)
|
|
| 102 |
+ job.SetenvInt64("until", time.Now().Unix())
|
|
| 103 |
+ buf := bytes.NewBuffer(nil) |
|
| 104 |
+ job.Stdout.Add(buf) |
|
| 105 |
+ if err := job.Run(); err != nil {
|
|
| 106 |
+ t.Fatal(err) |
|
| 107 |
+ } |
|
| 108 |
+ buf = bytes.NewBuffer(buf.Bytes()) |
|
| 109 |
+ dec := json.NewDecoder(buf) |
|
| 110 |
+ var msgs []utils.JSONMessage |
|
| 111 |
+ for {
|
|
| 112 |
+ var jm utils.JSONMessage |
|
| 113 |
+ if err := dec.Decode(&jm); err != nil {
|
|
| 114 |
+ if err == io.EOF {
|
|
| 115 |
+ break |
|
| 116 |
+ } |
|
| 117 |
+ t.Fatal(err) |
|
| 118 |
+ } |
|
| 119 |
+ msgs = append(msgs, jm) |
|
| 120 |
+ } |
|
| 121 |
+ if len(msgs) != eventsLimit {
|
|
| 122 |
+ t.Fatalf("Must be %d events, got %d", eventsLimit, len(msgs))
|
|
| 123 |
+ } |
|
| 124 |
+ first := msgs[0] |
|
| 125 |
+ if first.Status != "action_16" {
|
|
| 126 |
+ t.Fatalf("First action is %s, must be action_15", first.Status)
|
|
| 127 |
+ } |
|
| 128 |
+ last := msgs[len(msgs)-1] |
|
| 129 |
+ if last.Status != "action_79" {
|
|
| 130 |
+ t.Fatalf("First action is %s, must be action_79", first.Status)
|
|
| 131 |
+ } |
|
| 132 |
+} |
|
| 133 |
+ |
|
| 134 |
+func TestEventsCountJob(t *testing.T) {
|
|
| 135 |
+ e := New() |
|
| 136 |
+ eng := engine.New() |
|
| 137 |
+ if err := e.Install(eng); err != nil {
|
|
| 138 |
+ t.Fatal(err) |
|
| 139 |
+ } |
|
| 140 |
+ l1 := make(chan *utils.JSONMessage) |
|
| 141 |
+ l2 := make(chan *utils.JSONMessage) |
|
| 142 |
+ e.subscribe(l1) |
|
| 143 |
+ e.subscribe(l2) |
|
| 144 |
+ job := eng.Job("subscribers_count")
|
|
| 145 |
+ env, _ := job.Stdout.AddEnv() |
|
| 146 |
+ if err := job.Run(); err != nil {
|
|
| 147 |
+ t.Fatal(err) |
|
| 148 |
+ } |
|
| 149 |
+ count := env.GetInt("count")
|
|
| 150 |
+ if count != 2 {
|
|
| 151 |
+ t.Fatalf("There must be 2 subscribers, got %d", count)
|
|
| 152 |
+ } |
|
| 153 |
+} |
| 0 | 154 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,147 @@ |
| 0 |
+package graph |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "encoding/json" |
|
| 4 |
+ "io" |
|
| 5 |
+ "io/ioutil" |
|
| 6 |
+ "os" |
|
| 7 |
+ "path" |
|
| 8 |
+ |
|
| 9 |
+ "github.com/docker/docker/archive" |
|
| 10 |
+ "github.com/docker/docker/engine" |
|
| 11 |
+ "github.com/docker/docker/pkg/parsers" |
|
| 12 |
+ "github.com/docker/docker/utils" |
|
| 13 |
+) |
|
| 14 |
+ |
|
| 15 |
+// CmdImageExport exports all images with the given tag. All versions |
|
| 16 |
+// containing the same tag are exported. The resulting output is an |
|
| 17 |
+// uncompressed tar ball. |
|
| 18 |
+// name is the set of tags to export. |
|
| 19 |
+// out is the writer where the images are written to. |
|
| 20 |
+func (s *TagStore) CmdImageExport(job *engine.Job) engine.Status {
|
|
| 21 |
+ if len(job.Args) != 1 {
|
|
| 22 |
+ return job.Errorf("Usage: %s IMAGE\n", job.Name)
|
|
| 23 |
+ } |
|
| 24 |
+ name := job.Args[0] |
|
| 25 |
+ // get image json |
|
| 26 |
+ tempdir, err := ioutil.TempDir("", "docker-export-")
|
|
| 27 |
+ if err != nil {
|
|
| 28 |
+ return job.Error(err) |
|
| 29 |
+ } |
|
| 30 |
+ defer os.RemoveAll(tempdir) |
|
| 31 |
+ |
|
| 32 |
+ utils.Debugf("Serializing %s", name)
|
|
| 33 |
+ |
|
| 34 |
+ rootRepoMap := map[string]Repository{}
|
|
| 35 |
+ rootRepo, err := s.Get(name) |
|
| 36 |
+ if err != nil {
|
|
| 37 |
+ return job.Error(err) |
|
| 38 |
+ } |
|
| 39 |
+ if rootRepo != nil {
|
|
| 40 |
+ // this is a base repo name, like 'busybox' |
|
| 41 |
+ |
|
| 42 |
+ for _, id := range rootRepo {
|
|
| 43 |
+ if err := s.exportImage(job.Eng, id, tempdir); err != nil {
|
|
| 44 |
+ return job.Error(err) |
|
| 45 |
+ } |
|
| 46 |
+ } |
|
| 47 |
+ rootRepoMap[name] = rootRepo |
|
| 48 |
+ } else {
|
|
| 49 |
+ img, err := s.LookupImage(name) |
|
| 50 |
+ if err != nil {
|
|
| 51 |
+ return job.Error(err) |
|
| 52 |
+ } |
|
| 53 |
+ if img != nil {
|
|
| 54 |
+ // This is a named image like 'busybox:latest' |
|
| 55 |
+ repoName, repoTag := parsers.ParseRepositoryTag(name) |
|
| 56 |
+ if err := s.exportImage(job.Eng, img.ID, tempdir); err != nil {
|
|
| 57 |
+ return job.Error(err) |
|
| 58 |
+ } |
|
| 59 |
+ // check this length, because a lookup of a truncated has will not have a tag |
|
| 60 |
+ // and will not need to be added to this map |
|
| 61 |
+ if len(repoTag) > 0 {
|
|
| 62 |
+ rootRepoMap[repoName] = Repository{repoTag: img.ID}
|
|
| 63 |
+ } |
|
| 64 |
+ } else {
|
|
| 65 |
+ // this must be an ID that didn't get looked up just right? |
|
| 66 |
+ if err := s.exportImage(job.Eng, name, tempdir); err != nil {
|
|
| 67 |
+ return job.Error(err) |
|
| 68 |
+ } |
|
| 69 |
+ } |
|
| 70 |
+ } |
|
| 71 |
+ // write repositories, if there is something to write |
|
| 72 |
+ if len(rootRepoMap) > 0 {
|
|
| 73 |
+ rootRepoJson, _ := json.Marshal(rootRepoMap) |
|
| 74 |
+ |
|
| 75 |
+ if err := ioutil.WriteFile(path.Join(tempdir, "repositories"), rootRepoJson, os.FileMode(0644)); err != nil {
|
|
| 76 |
+ return job.Error(err) |
|
| 77 |
+ } |
|
| 78 |
+ } else {
|
|
| 79 |
+ utils.Debugf("There were no repositories to write")
|
|
| 80 |
+ } |
|
| 81 |
+ |
|
| 82 |
+ fs, err := archive.Tar(tempdir, archive.Uncompressed) |
|
| 83 |
+ if err != nil {
|
|
| 84 |
+ return job.Error(err) |
|
| 85 |
+ } |
|
| 86 |
+ defer fs.Close() |
|
| 87 |
+ |
|
| 88 |
+ if _, err := io.Copy(job.Stdout, fs); err != nil {
|
|
| 89 |
+ return job.Error(err) |
|
| 90 |
+ } |
|
| 91 |
+ utils.Debugf("End Serializing %s", name)
|
|
| 92 |
+ return engine.StatusOK |
|
| 93 |
+} |
|
| 94 |
+ |
|
| 95 |
+// FIXME: this should be a top-level function, not a class method |
|
| 96 |
+func (s *TagStore) exportImage(eng *engine.Engine, name, tempdir string) error {
|
|
| 97 |
+ for n := name; n != ""; {
|
|
| 98 |
+ // temporary directory |
|
| 99 |
+ tmpImageDir := path.Join(tempdir, n) |
|
| 100 |
+ if err := os.Mkdir(tmpImageDir, os.FileMode(0755)); err != nil {
|
|
| 101 |
+ if os.IsExist(err) {
|
|
| 102 |
+ return nil |
|
| 103 |
+ } |
|
| 104 |
+ return err |
|
| 105 |
+ } |
|
| 106 |
+ |
|
| 107 |
+ var version = "1.0" |
|
| 108 |
+ var versionBuf = []byte(version) |
|
| 109 |
+ |
|
| 110 |
+ if err := ioutil.WriteFile(path.Join(tmpImageDir, "VERSION"), versionBuf, os.FileMode(0644)); err != nil {
|
|
| 111 |
+ return err |
|
| 112 |
+ } |
|
| 113 |
+ |
|
| 114 |
+ // serialize json |
|
| 115 |
+ json, err := os.Create(path.Join(tmpImageDir, "json")) |
|
| 116 |
+ if err != nil {
|
|
| 117 |
+ return err |
|
| 118 |
+ } |
|
| 119 |
+ job := eng.Job("image_inspect", n)
|
|
| 120 |
+ job.SetenvBool("raw", true)
|
|
| 121 |
+ job.Stdout.Add(json) |
|
| 122 |
+ if err := job.Run(); err != nil {
|
|
| 123 |
+ return err |
|
| 124 |
+ } |
|
| 125 |
+ |
|
| 126 |
+ // serialize filesystem |
|
| 127 |
+ fsTar, err := os.Create(path.Join(tmpImageDir, "layer.tar")) |
|
| 128 |
+ if err != nil {
|
|
| 129 |
+ return err |
|
| 130 |
+ } |
|
| 131 |
+ job = eng.Job("image_tarlayer", n)
|
|
| 132 |
+ job.Stdout.Add(fsTar) |
|
| 133 |
+ if err := job.Run(); err != nil {
|
|
| 134 |
+ return err |
|
| 135 |
+ } |
|
| 136 |
+ |
|
| 137 |
+ // find parent |
|
| 138 |
+ job = eng.Job("image_get", n)
|
|
| 139 |
+ info, _ := job.Stdout.AddEnv() |
|
| 140 |
+ if err := job.Run(); err != nil {
|
|
| 141 |
+ return err |
|
| 142 |
+ } |
|
| 143 |
+ n = info.Get("Parent")
|
|
| 144 |
+ } |
|
| 145 |
+ return nil |
|
| 146 |
+} |
| 0 | 147 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,46 @@ |
| 0 |
+package graph |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "strings" |
|
| 4 |
+ |
|
| 5 |
+ "github.com/docker/docker/engine" |
|
| 6 |
+ "github.com/docker/docker/image" |
|
| 7 |
+) |
|
| 8 |
+ |
|
| 9 |
+func (s *TagStore) CmdHistory(job *engine.Job) engine.Status {
|
|
| 10 |
+ if n := len(job.Args); n != 1 {
|
|
| 11 |
+ return job.Errorf("Usage: %s IMAGE", job.Name)
|
|
| 12 |
+ } |
|
| 13 |
+ name := job.Args[0] |
|
| 14 |
+ foundImage, err := s.LookupImage(name) |
|
| 15 |
+ if err != nil {
|
|
| 16 |
+ return job.Error(err) |
|
| 17 |
+ } |
|
| 18 |
+ |
|
| 19 |
+ lookupMap := make(map[string][]string) |
|
| 20 |
+ for name, repository := range s.Repositories {
|
|
| 21 |
+ for tag, id := range repository {
|
|
| 22 |
+ // If the ID already has a reverse lookup, do not update it unless for "latest" |
|
| 23 |
+ if _, exists := lookupMap[id]; !exists {
|
|
| 24 |
+ lookupMap[id] = []string{}
|
|
| 25 |
+ } |
|
| 26 |
+ lookupMap[id] = append(lookupMap[id], name+":"+tag) |
|
| 27 |
+ } |
|
| 28 |
+ } |
|
| 29 |
+ |
|
| 30 |
+ outs := engine.NewTable("Created", 0)
|
|
| 31 |
+ err = foundImage.WalkHistory(func(img *image.Image) error {
|
|
| 32 |
+ out := &engine.Env{}
|
|
| 33 |
+ out.Set("Id", img.ID)
|
|
| 34 |
+ out.SetInt64("Created", img.Created.Unix())
|
|
| 35 |
+ out.Set("CreatedBy", strings.Join(img.ContainerConfig.Cmd, " "))
|
|
| 36 |
+ out.SetList("Tags", lookupMap[img.ID])
|
|
| 37 |
+ out.SetInt64("Size", img.Size)
|
|
| 38 |
+ outs.Add(out) |
|
| 39 |
+ return nil |
|
| 40 |
+ }) |
|
| 41 |
+ if _, err := outs.WriteListTo(job.Stdout); err != nil {
|
|
| 42 |
+ return job.Error(err) |
|
| 43 |
+ } |
|
| 44 |
+ return engine.StatusOK |
|
| 45 |
+} |
| 0 | 46 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,61 @@ |
| 0 |
+package graph |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "net/http" |
|
| 4 |
+ "net/url" |
|
| 5 |
+ |
|
| 6 |
+ "github.com/docker/docker/archive" |
|
| 7 |
+ "github.com/docker/docker/engine" |
|
| 8 |
+ "github.com/docker/docker/utils" |
|
| 9 |
+) |
|
| 10 |
+ |
|
| 11 |
+func (s *TagStore) CmdImport(job *engine.Job) engine.Status {
|
|
| 12 |
+ if n := len(job.Args); n != 2 && n != 3 {
|
|
| 13 |
+ return job.Errorf("Usage: %s SRC REPO [TAG]", job.Name)
|
|
| 14 |
+ } |
|
| 15 |
+ var ( |
|
| 16 |
+ src = job.Args[0] |
|
| 17 |
+ repo = job.Args[1] |
|
| 18 |
+ tag string |
|
| 19 |
+ sf = utils.NewStreamFormatter(job.GetenvBool("json"))
|
|
| 20 |
+ archive archive.ArchiveReader |
|
| 21 |
+ resp *http.Response |
|
| 22 |
+ ) |
|
| 23 |
+ if len(job.Args) > 2 {
|
|
| 24 |
+ tag = job.Args[2] |
|
| 25 |
+ } |
|
| 26 |
+ |
|
| 27 |
+ if src == "-" {
|
|
| 28 |
+ archive = job.Stdin |
|
| 29 |
+ } else {
|
|
| 30 |
+ u, err := url.Parse(src) |
|
| 31 |
+ if err != nil {
|
|
| 32 |
+ return job.Error(err) |
|
| 33 |
+ } |
|
| 34 |
+ if u.Scheme == "" {
|
|
| 35 |
+ u.Scheme = "http" |
|
| 36 |
+ u.Host = src |
|
| 37 |
+ u.Path = "" |
|
| 38 |
+ } |
|
| 39 |
+ job.Stdout.Write(sf.FormatStatus("", "Downloading from %s", u))
|
|
| 40 |
+ resp, err = utils.Download(u.String()) |
|
| 41 |
+ if err != nil {
|
|
| 42 |
+ return job.Error(err) |
|
| 43 |
+ } |
|
| 44 |
+ progressReader := utils.ProgressReader(resp.Body, int(resp.ContentLength), job.Stdout, sf, true, "", "Importing") |
|
| 45 |
+ defer progressReader.Close() |
|
| 46 |
+ archive = progressReader |
|
| 47 |
+ } |
|
| 48 |
+ img, err := s.graph.Create(archive, "", "", "Imported from "+src, "", nil, nil) |
|
| 49 |
+ if err != nil {
|
|
| 50 |
+ return job.Error(err) |
|
| 51 |
+ } |
|
| 52 |
+ // Optionally register the image at REPO/TAG |
|
| 53 |
+ if repo != "" {
|
|
| 54 |
+ if err := s.Set(repo, tag, img.ID, true); err != nil {
|
|
| 55 |
+ return job.Error(err) |
|
| 56 |
+ } |
|
| 57 |
+ } |
|
| 58 |
+ job.Stdout.Write(sf.FormatStatus("", img.ID))
|
|
| 59 |
+ return engine.StatusOK |
|
| 60 |
+} |
| 0 | 61 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,103 @@ |
| 0 |
+package graph |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ "log" |
|
| 5 |
+ "path" |
|
| 6 |
+ "strings" |
|
| 7 |
+ |
|
| 8 |
+ "github.com/docker/docker/engine" |
|
| 9 |
+ "github.com/docker/docker/image" |
|
| 10 |
+ "github.com/docker/docker/pkg/parsers/filters" |
|
| 11 |
+) |
|
| 12 |
+ |
|
| 13 |
+func (s *TagStore) CmdImages(job *engine.Job) engine.Status {
|
|
| 14 |
+ var ( |
|
| 15 |
+ allImages map[string]*image.Image |
|
| 16 |
+ err error |
|
| 17 |
+ filt_tagged = true |
|
| 18 |
+ ) |
|
| 19 |
+ |
|
| 20 |
+ imageFilters, err := filters.FromParam(job.Getenv("filters"))
|
|
| 21 |
+ if err != nil {
|
|
| 22 |
+ return job.Error(err) |
|
| 23 |
+ } |
|
| 24 |
+ if i, ok := imageFilters["dangling"]; ok {
|
|
| 25 |
+ for _, value := range i {
|
|
| 26 |
+ if strings.ToLower(value) == "true" {
|
|
| 27 |
+ filt_tagged = false |
|
| 28 |
+ } |
|
| 29 |
+ } |
|
| 30 |
+ } |
|
| 31 |
+ |
|
| 32 |
+ if job.GetenvBool("all") && filt_tagged {
|
|
| 33 |
+ allImages, err = s.graph.Map() |
|
| 34 |
+ } else {
|
|
| 35 |
+ allImages, err = s.graph.Heads() |
|
| 36 |
+ } |
|
| 37 |
+ if err != nil {
|
|
| 38 |
+ return job.Error(err) |
|
| 39 |
+ } |
|
| 40 |
+ lookup := make(map[string]*engine.Env) |
|
| 41 |
+ s.Lock() |
|
| 42 |
+ for name, repository := range s.Repositories {
|
|
| 43 |
+ if job.Getenv("filter") != "" {
|
|
| 44 |
+ if match, _ := path.Match(job.Getenv("filter"), name); !match {
|
|
| 45 |
+ continue |
|
| 46 |
+ } |
|
| 47 |
+ } |
|
| 48 |
+ for tag, id := range repository {
|
|
| 49 |
+ image, err := s.graph.Get(id) |
|
| 50 |
+ if err != nil {
|
|
| 51 |
+ log.Printf("Warning: couldn't load %s from %s/%s: %s", id, name, tag, err)
|
|
| 52 |
+ continue |
|
| 53 |
+ } |
|
| 54 |
+ |
|
| 55 |
+ if out, exists := lookup[id]; exists {
|
|
| 56 |
+ if filt_tagged {
|
|
| 57 |
+ out.SetList("RepoTags", append(out.GetList("RepoTags"), fmt.Sprintf("%s:%s", name, tag)))
|
|
| 58 |
+ } |
|
| 59 |
+ } else {
|
|
| 60 |
+ // get the boolean list for if only the untagged images are requested |
|
| 61 |
+ delete(allImages, id) |
|
| 62 |
+ if filt_tagged {
|
|
| 63 |
+ out := &engine.Env{}
|
|
| 64 |
+ out.Set("ParentId", image.Parent)
|
|
| 65 |
+ out.SetList("RepoTags", []string{fmt.Sprintf("%s:%s", name, tag)})
|
|
| 66 |
+ out.Set("Id", image.ID)
|
|
| 67 |
+ out.SetInt64("Created", image.Created.Unix())
|
|
| 68 |
+ out.SetInt64("Size", image.Size)
|
|
| 69 |
+ out.SetInt64("VirtualSize", image.GetParentsSize(0)+image.Size)
|
|
| 70 |
+ lookup[id] = out |
|
| 71 |
+ } |
|
| 72 |
+ } |
|
| 73 |
+ |
|
| 74 |
+ } |
|
| 75 |
+ } |
|
| 76 |
+ s.Unlock() |
|
| 77 |
+ |
|
| 78 |
+ outs := engine.NewTable("Created", len(lookup))
|
|
| 79 |
+ for _, value := range lookup {
|
|
| 80 |
+ outs.Add(value) |
|
| 81 |
+ } |
|
| 82 |
+ |
|
| 83 |
+ // Display images which aren't part of a repository/tag |
|
| 84 |
+ if job.Getenv("filter") == "" {
|
|
| 85 |
+ for _, image := range allImages {
|
|
| 86 |
+ out := &engine.Env{}
|
|
| 87 |
+ out.Set("ParentId", image.Parent)
|
|
| 88 |
+ out.SetList("RepoTags", []string{"<none>:<none>"})
|
|
| 89 |
+ out.Set("Id", image.ID)
|
|
| 90 |
+ out.SetInt64("Created", image.Created.Unix())
|
|
| 91 |
+ out.SetInt64("Size", image.Size)
|
|
| 92 |
+ out.SetInt64("VirtualSize", image.GetParentsSize(0)+image.Size)
|
|
| 93 |
+ outs.Add(out) |
|
| 94 |
+ } |
|
| 95 |
+ } |
|
| 96 |
+ |
|
| 97 |
+ outs.ReverseSort() |
|
| 98 |
+ if _, err := outs.WriteListTo(job.Stdout); err != nil {
|
|
| 99 |
+ return job.Error(err) |
|
| 100 |
+ } |
|
| 101 |
+ return engine.StatusOK |
|
| 102 |
+} |
| 0 | 103 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,118 @@ |
| 0 |
+package graph |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "encoding/json" |
|
| 4 |
+ "io" |
|
| 5 |
+ "io/ioutil" |
|
| 6 |
+ "os" |
|
| 7 |
+ "path" |
|
| 8 |
+ |
|
| 9 |
+ "github.com/docker/docker/archive" |
|
| 10 |
+ "github.com/docker/docker/engine" |
|
| 11 |
+ "github.com/docker/docker/image" |
|
| 12 |
+ "github.com/docker/docker/utils" |
|
| 13 |
+) |
|
| 14 |
+ |
|
| 15 |
+// Loads a set of images into the repository. This is the complementary of ImageExport. |
|
| 16 |
+// The input stream is an uncompressed tar ball containing images and metadata. |
|
| 17 |
+func (s *TagStore) CmdLoad(job *engine.Job) engine.Status {
|
|
| 18 |
+ tmpImageDir, err := ioutil.TempDir("", "docker-import-")
|
|
| 19 |
+ if err != nil {
|
|
| 20 |
+ return job.Error(err) |
|
| 21 |
+ } |
|
| 22 |
+ defer os.RemoveAll(tmpImageDir) |
|
| 23 |
+ |
|
| 24 |
+ var ( |
|
| 25 |
+ repoTarFile = path.Join(tmpImageDir, "repo.tar") |
|
| 26 |
+ repoDir = path.Join(tmpImageDir, "repo") |
|
| 27 |
+ ) |
|
| 28 |
+ |
|
| 29 |
+ tarFile, err := os.Create(repoTarFile) |
|
| 30 |
+ if err != nil {
|
|
| 31 |
+ return job.Error(err) |
|
| 32 |
+ } |
|
| 33 |
+ if _, err := io.Copy(tarFile, job.Stdin); err != nil {
|
|
| 34 |
+ return job.Error(err) |
|
| 35 |
+ } |
|
| 36 |
+ tarFile.Close() |
|
| 37 |
+ |
|
| 38 |
+ repoFile, err := os.Open(repoTarFile) |
|
| 39 |
+ if err != nil {
|
|
| 40 |
+ return job.Error(err) |
|
| 41 |
+ } |
|
| 42 |
+ if err := os.Mkdir(repoDir, os.ModeDir); err != nil {
|
|
| 43 |
+ return job.Error(err) |
|
| 44 |
+ } |
|
| 45 |
+ if err := archive.Untar(repoFile, repoDir, nil); err != nil {
|
|
| 46 |
+ return job.Error(err) |
|
| 47 |
+ } |
|
| 48 |
+ |
|
| 49 |
+ dirs, err := ioutil.ReadDir(repoDir) |
|
| 50 |
+ if err != nil {
|
|
| 51 |
+ return job.Error(err) |
|
| 52 |
+ } |
|
| 53 |
+ |
|
| 54 |
+ for _, d := range dirs {
|
|
| 55 |
+ if d.IsDir() {
|
|
| 56 |
+ if err := s.recursiveLoad(job.Eng, d.Name(), tmpImageDir); err != nil {
|
|
| 57 |
+ return job.Error(err) |
|
| 58 |
+ } |
|
| 59 |
+ } |
|
| 60 |
+ } |
|
| 61 |
+ |
|
| 62 |
+ repositoriesJson, err := ioutil.ReadFile(path.Join(tmpImageDir, "repo", "repositories")) |
|
| 63 |
+ if err == nil {
|
|
| 64 |
+ repositories := map[string]Repository{}
|
|
| 65 |
+ if err := json.Unmarshal(repositoriesJson, &repositories); err != nil {
|
|
| 66 |
+ return job.Error(err) |
|
| 67 |
+ } |
|
| 68 |
+ |
|
| 69 |
+ for imageName, tagMap := range repositories {
|
|
| 70 |
+ for tag, address := range tagMap {
|
|
| 71 |
+ if err := s.Set(imageName, tag, address, true); err != nil {
|
|
| 72 |
+ return job.Error(err) |
|
| 73 |
+ } |
|
| 74 |
+ } |
|
| 75 |
+ } |
|
| 76 |
+ } else if !os.IsNotExist(err) {
|
|
| 77 |
+ return job.Error(err) |
|
| 78 |
+ } |
|
| 79 |
+ |
|
| 80 |
+ return engine.StatusOK |
|
| 81 |
+} |
|
| 82 |
+ |
|
| 83 |
+func (s *TagStore) recursiveLoad(eng *engine.Engine, address, tmpImageDir string) error {
|
|
| 84 |
+ if err := eng.Job("image_get", address).Run(); err != nil {
|
|
| 85 |
+ utils.Debugf("Loading %s", address)
|
|
| 86 |
+ |
|
| 87 |
+ imageJson, err := ioutil.ReadFile(path.Join(tmpImageDir, "repo", address, "json")) |
|
| 88 |
+ if err != nil {
|
|
| 89 |
+ utils.Debugf("Error reading json", err)
|
|
| 90 |
+ return err |
|
| 91 |
+ } |
|
| 92 |
+ |
|
| 93 |
+ layer, err := os.Open(path.Join(tmpImageDir, "repo", address, "layer.tar")) |
|
| 94 |
+ if err != nil {
|
|
| 95 |
+ utils.Debugf("Error reading embedded tar", err)
|
|
| 96 |
+ return err |
|
| 97 |
+ } |
|
| 98 |
+ img, err := image.NewImgJSON(imageJson) |
|
| 99 |
+ if err != nil {
|
|
| 100 |
+ utils.Debugf("Error unmarshalling json", err)
|
|
| 101 |
+ return err |
|
| 102 |
+ } |
|
| 103 |
+ if img.Parent != "" {
|
|
| 104 |
+ if !s.graph.Exists(img.Parent) {
|
|
| 105 |
+ if err := s.recursiveLoad(eng, img.Parent, tmpImageDir); err != nil {
|
|
| 106 |
+ return err |
|
| 107 |
+ } |
|
| 108 |
+ } |
|
| 109 |
+ } |
|
| 110 |
+ if err := s.graph.Register(imageJson, layer, img); err != nil {
|
|
| 111 |
+ return err |
|
| 112 |
+ } |
|
| 113 |
+ } |
|
| 114 |
+ utils.Debugf("Completed processing %s", address)
|
|
| 115 |
+ |
|
| 116 |
+ return nil |
|
| 117 |
+} |
| ... | ... |
@@ -1,20 +1,33 @@ |
| 1 | 1 |
package graph |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 |
+ "fmt" |
|
| 4 | 5 |
"io" |
| 5 | 6 |
|
| 6 | 7 |
"github.com/docker/docker/engine" |
| 7 | 8 |
"github.com/docker/docker/image" |
| 8 |
- "github.com/docker/docker/pkg/parsers" |
|
| 9 | 9 |
"github.com/docker/docker/utils" |
| 10 | 10 |
) |
| 11 | 11 |
|
| 12 | 12 |
func (s *TagStore) Install(eng *engine.Engine) error {
|
| 13 |
- eng.Register("image_set", s.CmdSet)
|
|
| 14 |
- eng.Register("image_tag", s.CmdTag)
|
|
| 15 |
- eng.Register("image_get", s.CmdGet)
|
|
| 16 |
- eng.Register("image_inspect", s.CmdLookup)
|
|
| 17 |
- eng.Register("image_tarlayer", s.CmdTarLayer)
|
|
| 13 |
+ for name, handler := range map[string]engine.Handler{
|
|
| 14 |
+ "image_set": s.CmdSet, |
|
| 15 |
+ "image_tag": s.CmdTag, |
|
| 16 |
+ "tag": s.CmdTagLegacy, // FIXME merge with "image_tag" |
|
| 17 |
+ "image_get": s.CmdGet, |
|
| 18 |
+ "image_inspect": s.CmdLookup, |
|
| 19 |
+ "image_tarlayer": s.CmdTarLayer, |
|
| 20 |
+ "image_export": s.CmdImageExport, |
|
| 21 |
+ "history": s.CmdHistory, |
|
| 22 |
+ "images": s.CmdImages, |
|
| 23 |
+ "viz": s.CmdViz, |
|
| 24 |
+ "load": s.CmdLoad, |
|
| 25 |
+ "import": s.CmdImport, |
|
| 26 |
+ } {
|
|
| 27 |
+ if err := eng.Register(name, handler); err != nil {
|
|
| 28 |
+ return fmt.Errorf("Could not register %q: %v", name, err)
|
|
| 29 |
+ } |
|
| 30 |
+ } |
|
| 18 | 31 |
return nil |
| 19 | 32 |
} |
| 20 | 33 |
|
| ... | ... |
@@ -65,29 +78,6 @@ func (s *TagStore) CmdSet(job *engine.Job) engine.Status {
|
| 65 | 65 |
return engine.StatusOK |
| 66 | 66 |
} |
| 67 | 67 |
|
| 68 |
-// CmdTag assigns a new name and tag to an existing image. If the tag already exists, |
|
| 69 |
-// it is changed and the image previously referenced by the tag loses that reference. |
|
| 70 |
-// This may cause the old image to be garbage-collected if its reference count reaches zero. |
|
| 71 |
-// |
|
| 72 |
-// Syntax: image_tag NEWNAME OLDNAME |
|
| 73 |
-// Example: image_tag shykes/myapp:latest shykes/myapp:1.42.0 |
|
| 74 |
-func (s *TagStore) CmdTag(job *engine.Job) engine.Status {
|
|
| 75 |
- if len(job.Args) != 2 {
|
|
| 76 |
- return job.Errorf("usage: %s NEWNAME OLDNAME", job.Name)
|
|
| 77 |
- } |
|
| 78 |
- var ( |
|
| 79 |
- newName = job.Args[0] |
|
| 80 |
- oldName = job.Args[1] |
|
| 81 |
- ) |
|
| 82 |
- newRepo, newTag := parsers.ParseRepositoryTag(newName) |
|
| 83 |
- // FIXME: Set should either parse both old and new name, or neither. |
|
| 84 |
- // the current prototype is inconsistent. |
|
| 85 |
- if err := s.Set(newRepo, newTag, oldName, true); err != nil {
|
|
| 86 |
- return job.Error(err) |
|
| 87 |
- } |
|
| 88 |
- return engine.StatusOK |
|
| 89 |
-} |
|
| 90 |
- |
|
| 91 | 68 |
// CmdGet returns information about an image. |
| 92 | 69 |
// If the image doesn't exist, an empty object is returned, to allow |
| 93 | 70 |
// checking for an image's existence. |
| 94 | 71 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,44 @@ |
| 0 |
+package graph |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "github.com/docker/docker/engine" |
|
| 4 |
+ "github.com/docker/docker/pkg/parsers" |
|
| 5 |
+) |
|
| 6 |
+ |
|
| 7 |
+// CmdTag assigns a new name and tag to an existing image. If the tag already exists, |
|
| 8 |
+// it is changed and the image previously referenced by the tag loses that reference. |
|
| 9 |
+// This may cause the old image to be garbage-collected if its reference count reaches zero. |
|
| 10 |
+// |
|
| 11 |
+// Syntax: image_tag NEWNAME OLDNAME |
|
| 12 |
+// Example: image_tag shykes/myapp:latest shykes/myapp:1.42.0 |
|
| 13 |
+func (s *TagStore) CmdTag(job *engine.Job) engine.Status {
|
|
| 14 |
+ if len(job.Args) != 2 {
|
|
| 15 |
+ return job.Errorf("usage: %s NEWNAME OLDNAME", job.Name)
|
|
| 16 |
+ } |
|
| 17 |
+ var ( |
|
| 18 |
+ newName = job.Args[0] |
|
| 19 |
+ oldName = job.Args[1] |
|
| 20 |
+ ) |
|
| 21 |
+ newRepo, newTag := parsers.ParseRepositoryTag(newName) |
|
| 22 |
+ // FIXME: Set should either parse both old and new name, or neither. |
|
| 23 |
+ // the current prototype is inconsistent. |
|
| 24 |
+ if err := s.Set(newRepo, newTag, oldName, true); err != nil {
|
|
| 25 |
+ return job.Error(err) |
|
| 26 |
+ } |
|
| 27 |
+ return engine.StatusOK |
|
| 28 |
+} |
|
| 29 |
+ |
|
| 30 |
+// FIXME: merge into CmdTag above, and merge "image_tag" and "tag" into a single job. |
|
| 31 |
+func (s *TagStore) CmdTagLegacy(job *engine.Job) engine.Status {
|
|
| 32 |
+ if len(job.Args) != 2 && len(job.Args) != 3 {
|
|
| 33 |
+ return job.Errorf("Usage: %s IMAGE REPOSITORY [TAG]\n", job.Name)
|
|
| 34 |
+ } |
|
| 35 |
+ var tag string |
|
| 36 |
+ if len(job.Args) == 3 {
|
|
| 37 |
+ tag = job.Args[2] |
|
| 38 |
+ } |
|
| 39 |
+ if err := s.Set(job.Args[1], tag, job.Args[0], job.GetenvBool("force")); err != nil {
|
|
| 40 |
+ return job.Error(err) |
|
| 41 |
+ } |
|
| 42 |
+ return engine.StatusOK |
|
| 43 |
+} |
| 0 | 44 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,38 @@ |
| 0 |
+package graph |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "strings" |
|
| 4 |
+ |
|
| 5 |
+ "github.com/docker/docker/engine" |
|
| 6 |
+ "github.com/docker/docker/image" |
|
| 7 |
+) |
|
| 8 |
+ |
|
| 9 |
+func (s *TagStore) CmdViz(job *engine.Job) engine.Status {
|
|
| 10 |
+ images, _ := s.graph.Map() |
|
| 11 |
+ if images == nil {
|
|
| 12 |
+ return engine.StatusOK |
|
| 13 |
+ } |
|
| 14 |
+ job.Stdout.Write([]byte("digraph docker {\n"))
|
|
| 15 |
+ |
|
| 16 |
+ var ( |
|
| 17 |
+ parentImage *image.Image |
|
| 18 |
+ err error |
|
| 19 |
+ ) |
|
| 20 |
+ for _, image := range images {
|
|
| 21 |
+ parentImage, err = image.GetParent() |
|
| 22 |
+ if err != nil {
|
|
| 23 |
+ return job.Errorf("Error while getting parent image: %v", err)
|
|
| 24 |
+ } |
|
| 25 |
+ if parentImage != nil {
|
|
| 26 |
+ job.Stdout.Write([]byte(" \"" + parentImage.ID + "\" -> \"" + image.ID + "\"\n"))
|
|
| 27 |
+ } else {
|
|
| 28 |
+ job.Stdout.Write([]byte(" base -> \"" + image.ID + "\" [style=invis]\n"))
|
|
| 29 |
+ } |
|
| 30 |
+ } |
|
| 31 |
+ |
|
| 32 |
+ for id, repos := range s.GetRepoRefs() {
|
|
| 33 |
+ job.Stdout.Write([]byte(" \"" + id + "\" [label=\"" + id + "\\n" + strings.Join(repos, "\\n") + "\",shape=box,fillcolor=\"paleturquoise\",style=\"filled,rounded\"];\n"))
|
|
| 34 |
+ } |
|
| 35 |
+ job.Stdout.Write([]byte(" base [style=invisible]\n}\n"))
|
|
| 36 |
+ return engine.StatusOK |
|
| 37 |
+} |
| ... | ... |
@@ -3,6 +3,7 @@ package main |
| 3 | 3 |
import ( |
| 4 | 4 |
"os" |
| 5 | 5 |
"os/exec" |
| 6 |
+ "strings" |
|
| 6 | 7 |
"testing" |
| 7 | 8 |
) |
| 8 | 9 |
|
| ... | ... |
@@ -92,6 +93,69 @@ func TestRemoveContainerWithStopAndKill(t *testing.T) {
|
| 92 | 92 |
logDone("rm - with --stop=true and --kill=true")
|
| 93 | 93 |
} |
| 94 | 94 |
|
| 95 |
+func TestContainerOrphaning(t *testing.T) {
|
|
| 96 |
+ dockerfile1 := `FROM busybox:latest |
|
| 97 |
+ ENTRYPOINT ["/bin/true"]` |
|
| 98 |
+ img := "test-container-orphaning" |
|
| 99 |
+ dockerfile2 := `FROM busybox:latest |
|
| 100 |
+ ENTRYPOINT ["/bin/true"] |
|
| 101 |
+ MAINTAINER Integration Tests` |
|
| 102 |
+ |
|
| 103 |
+ // build first dockerfile |
|
| 104 |
+ img1, err := buildImage(img, dockerfile1, true) |
|
| 105 |
+ if err != nil {
|
|
| 106 |
+ t.Fatalf("Could not build image %s: %v", img, err)
|
|
| 107 |
+ } |
|
| 108 |
+ // run container on first image |
|
| 109 |
+ if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", img)); err != nil {
|
|
| 110 |
+ t.Fatalf("Could not run image %s: %v: %s", img, err, out)
|
|
| 111 |
+ } |
|
| 112 |
+ // rebuild dockerfile with a small addition at the end |
|
| 113 |
+ if _, err := buildImage(img, dockerfile2, true); err != nil {
|
|
| 114 |
+ t.Fatalf("Could not rebuild image %s: %v", img, err)
|
|
| 115 |
+ } |
|
| 116 |
+ // try to remove the image, should error out. |
|
| 117 |
+ if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "rmi", img)); err == nil {
|
|
| 118 |
+ t.Fatalf("Expected to error out removing the image, but succeeded: %s", out)
|
|
| 119 |
+ } |
|
| 120 |
+ // check if we deleted the first image |
|
| 121 |
+ out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "images", "-q", "--no-trunc")) |
|
| 122 |
+ if err != nil {
|
|
| 123 |
+ t.Fatalf("%v: %s", err, out)
|
|
| 124 |
+ } |
|
| 125 |
+ if !strings.Contains(out, img1) {
|
|
| 126 |
+ t.Fatal("Orphaned container (could not find '%s' in docker images): %s", img1, out)
|
|
| 127 |
+ } |
|
| 128 |
+ |
|
| 129 |
+ deleteAllContainers() |
|
| 130 |
+ |
|
| 131 |
+ logDone("rm - container orphaning")
|
|
| 132 |
+} |
|
| 133 |
+ |
|
| 134 |
+func TestDeleteTagWithExistingContainers(t *testing.T) {
|
|
| 135 |
+ container := "test-delete-tag" |
|
| 136 |
+ newtag := "busybox:newtag" |
|
| 137 |
+ bb := "busybox:latest" |
|
| 138 |
+ if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "tag", bb, newtag)); err != nil {
|
|
| 139 |
+ t.Fatalf("Could not tag busybox: %v: %s", err, out)
|
|
| 140 |
+ } |
|
| 141 |
+ if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "--name", container, bb, "/bin/true")); err != nil {
|
|
| 142 |
+ t.Fatalf("Could not run busybox: %v: %s", err, out)
|
|
| 143 |
+ } |
|
| 144 |
+ out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "rmi", newtag)) |
|
| 145 |
+ if err != nil {
|
|
| 146 |
+ t.Fatalf("Could not remove tag %s: %v: %s", newtag, err, out)
|
|
| 147 |
+ } |
|
| 148 |
+ if d := strings.Count(out, "Untagged: "); d != 1 {
|
|
| 149 |
+ t.Fatalf("Expected 1 untagged entry got %d: %q", d, out)
|
|
| 150 |
+ } |
|
| 151 |
+ |
|
| 152 |
+ deleteAllContainers() |
|
| 153 |
+ |
|
| 154 |
+ logDone("rm - delete tag with existing containers")
|
|
| 155 |
+ |
|
| 156 |
+} |
|
| 157 |
+ |
|
| 95 | 158 |
func createRunningContainer(t *testing.T, name string) {
|
| 96 | 159 |
cmd := exec.Command(dockerBinary, "run", "-dt", "--name", name, "busybox", "top") |
| 97 | 160 |
if _, err := runCommand(cmd); err != nil {
|
| ... | ... |
@@ -13,7 +13,6 @@ import ( |
| 13 | 13 |
|
| 14 | 14 |
"github.com/docker/docker/api/client" |
| 15 | 15 |
"github.com/docker/docker/daemon" |
| 16 |
- "github.com/docker/docker/engine" |
|
| 17 | 16 |
"github.com/docker/docker/pkg/term" |
| 18 | 17 |
"github.com/docker/docker/utils" |
| 19 | 18 |
) |
| ... | ... |
@@ -682,70 +681,3 @@ func TestRunCidFileCleanupIfEmpty(t *testing.T) {
|
| 682 | 682 |
<-c |
| 683 | 683 |
}) |
| 684 | 684 |
} |
| 685 |
- |
|
| 686 |
-func TestContainerOrphaning(t *testing.T) {
|
|
| 687 |
- |
|
| 688 |
- // setup a temporary directory |
|
| 689 |
- tmpDir, err := ioutil.TempDir("", "project")
|
|
| 690 |
- if err != nil {
|
|
| 691 |
- t.Fatal(err) |
|
| 692 |
- } |
|
| 693 |
- defer os.RemoveAll(tmpDir) |
|
| 694 |
- |
|
| 695 |
- // setup a CLI and server |
|
| 696 |
- cli := client.NewDockerCli(nil, ioutil.Discard, ioutil.Discard, testDaemonProto, testDaemonAddr, nil) |
|
| 697 |
- defer cleanup(globalEngine, t) |
|
| 698 |
- srv := mkServerFromEngine(globalEngine, t) |
|
| 699 |
- |
|
| 700 |
- // closure to build something |
|
| 701 |
- buildSomething := func(template string, image string) string {
|
|
| 702 |
- dockerfile := path.Join(tmpDir, "Dockerfile") |
|
| 703 |
- replacer := strings.NewReplacer("{IMAGE}", unitTestImageID)
|
|
| 704 |
- contents := replacer.Replace(template) |
|
| 705 |
- ioutil.WriteFile(dockerfile, []byte(contents), 0x777) |
|
| 706 |
- if err := cli.CmdBuild("-t", image, tmpDir); err != nil {
|
|
| 707 |
- t.Fatal(err) |
|
| 708 |
- } |
|
| 709 |
- job := globalEngine.Job("image_get", image)
|
|
| 710 |
- info, _ := job.Stdout.AddEnv() |
|
| 711 |
- if err := job.Run(); err != nil {
|
|
| 712 |
- t.Fatal(err) |
|
| 713 |
- } |
|
| 714 |
- return info.Get("Id")
|
|
| 715 |
- } |
|
| 716 |
- |
|
| 717 |
- // build an image |
|
| 718 |
- imageName := "orphan-test" |
|
| 719 |
- template1 := ` |
|
| 720 |
- from {IMAGE}
|
|
| 721 |
- cmd ["/bin/echo", "holla"] |
|
| 722 |
- ` |
|
| 723 |
- img1 := buildSomething(template1, imageName) |
|
| 724 |
- |
|
| 725 |
- // create a container using the fist image |
|
| 726 |
- if err := cli.CmdRun(imageName); err != nil {
|
|
| 727 |
- t.Fatal(err) |
|
| 728 |
- } |
|
| 729 |
- |
|
| 730 |
- // build a new image that splits lineage |
|
| 731 |
- template2 := ` |
|
| 732 |
- from {IMAGE}
|
|
| 733 |
- cmd ["/bin/echo", "holla"] |
|
| 734 |
- expose 22 |
|
| 735 |
- ` |
|
| 736 |
- buildSomething(template2, imageName) |
|
| 737 |
- |
|
| 738 |
- // remove the second image by name |
|
| 739 |
- resp := engine.NewTable("", 0)
|
|
| 740 |
- if err := srv.DeleteImage(imageName, resp, true, false, false); err == nil {
|
|
| 741 |
- t.Fatal("Expected error, got none")
|
|
| 742 |
- } |
|
| 743 |
- |
|
| 744 |
- // see if we deleted the first image (and orphaned the container) |
|
| 745 |
- for _, i := range resp.Data {
|
|
| 746 |
- if img1 == i.Get("Deleted") {
|
|
| 747 |
- t.Fatal("Orphaned image with container")
|
|
| 748 |
- } |
|
| 749 |
- } |
|
| 750 |
- |
|
| 751 |
-} |
| ... | ... |
@@ -297,56 +297,3 @@ func TestImagesFilter(t *testing.T) {
|
| 297 | 297 |
t.Fatal("incorrect number of matches returned")
|
| 298 | 298 |
} |
| 299 | 299 |
} |
| 300 |
- |
|
| 301 |
-// Regression test for being able to untag an image with an existing |
|
| 302 |
-// container |
|
| 303 |
-func TestDeleteTagWithExistingContainers(t *testing.T) {
|
|
| 304 |
- eng := NewTestEngine(t) |
|
| 305 |
- defer nuke(mkDaemonFromEngine(eng, t)) |
|
| 306 |
- |
|
| 307 |
- srv := mkServerFromEngine(eng, t) |
|
| 308 |
- |
|
| 309 |
- // Tag the image |
|
| 310 |
- if err := eng.Job("tag", unitTestImageID, "utest", "tag1").Run(); err != nil {
|
|
| 311 |
- t.Fatal(err) |
|
| 312 |
- } |
|
| 313 |
- |
|
| 314 |
- // Create a container from the image |
|
| 315 |
- config, _, _, err := runconfig.Parse([]string{unitTestImageID, "echo test"}, nil)
|
|
| 316 |
- if err != nil {
|
|
| 317 |
- t.Fatal(err) |
|
| 318 |
- } |
|
| 319 |
- |
|
| 320 |
- id := createNamedTestContainer(eng, config, t, "testingtags") |
|
| 321 |
- if id == "" {
|
|
| 322 |
- t.Fatal("No id returned")
|
|
| 323 |
- } |
|
| 324 |
- |
|
| 325 |
- job := srv.Eng.Job("containers")
|
|
| 326 |
- job.SetenvBool("all", true)
|
|
| 327 |
- outs, err := job.Stdout.AddListTable() |
|
| 328 |
- if err != nil {
|
|
| 329 |
- t.Fatal(err) |
|
| 330 |
- } |
|
| 331 |
- if err := job.Run(); err != nil {
|
|
| 332 |
- t.Fatal(err) |
|
| 333 |
- } |
|
| 334 |
- |
|
| 335 |
- if len(outs.Data) != 1 {
|
|
| 336 |
- t.Fatalf("Expected 1 container got %d", len(outs.Data))
|
|
| 337 |
- } |
|
| 338 |
- |
|
| 339 |
- // Try to remove the tag |
|
| 340 |
- imgs := engine.NewTable("", 0)
|
|
| 341 |
- if err := srv.DeleteImage("utest:tag1", imgs, true, false, false); err != nil {
|
|
| 342 |
- t.Fatal(err) |
|
| 343 |
- } |
|
| 344 |
- |
|
| 345 |
- if len(imgs.Data) != 1 {
|
|
| 346 |
- t.Fatalf("Should only have deleted one untag %d", len(imgs.Data))
|
|
| 347 |
- } |
|
| 348 |
- |
|
| 349 |
- if untag := imgs.Data[0].Get("Untagged"); untag != "utest:tag1" {
|
|
| 350 |
- t.Fatalf("Expected %s got %s", unitTestImageID, untag)
|
|
| 351 |
- } |
|
| 352 |
-} |
| 353 | 300 |
deleted file mode 100644 |
| ... | ... |
@@ -1,108 +0,0 @@ |
| 1 |
-// DEPRECATION NOTICE. PLEASE DO NOT ADD ANYTHING TO THIS FILE. |
|
| 2 |
-// |
|
| 3 |
-// For additional commments see server/server.go |
|
| 4 |
-// |
|
| 5 |
-package server |
|
| 6 |
- |
|
| 7 |
-import ( |
|
| 8 |
- "encoding/json" |
|
| 9 |
- "time" |
|
| 10 |
- |
|
| 11 |
- "github.com/docker/docker/engine" |
|
| 12 |
- "github.com/docker/docker/utils" |
|
| 13 |
-) |
|
| 14 |
- |
|
| 15 |
-func (srv *Server) Events(job *engine.Job) engine.Status {
|
|
| 16 |
- if len(job.Args) != 0 {
|
|
| 17 |
- return job.Errorf("Usage: %s", job.Name)
|
|
| 18 |
- } |
|
| 19 |
- |
|
| 20 |
- var ( |
|
| 21 |
- since = job.GetenvInt64("since")
|
|
| 22 |
- until = job.GetenvInt64("until")
|
|
| 23 |
- timeout = time.NewTimer(time.Unix(until, 0).Sub(time.Now())) |
|
| 24 |
- ) |
|
| 25 |
- |
|
| 26 |
- // If no until, disable timeout |
|
| 27 |
- if until == 0 {
|
|
| 28 |
- timeout.Stop() |
|
| 29 |
- } |
|
| 30 |
- |
|
| 31 |
- listener := make(chan utils.JSONMessage) |
|
| 32 |
- srv.eventPublisher.Subscribe(listener) |
|
| 33 |
- defer srv.eventPublisher.Unsubscribe(listener) |
|
| 34 |
- |
|
| 35 |
- // When sending an event JSON serialization errors are ignored, but all |
|
| 36 |
- // other errors lead to the eviction of the listener. |
|
| 37 |
- sendEvent := func(event *utils.JSONMessage) error {
|
|
| 38 |
- if b, err := json.Marshal(event); err == nil {
|
|
| 39 |
- if _, err = job.Stdout.Write(b); err != nil {
|
|
| 40 |
- return err |
|
| 41 |
- } |
|
| 42 |
- } |
|
| 43 |
- return nil |
|
| 44 |
- } |
|
| 45 |
- |
|
| 46 |
- job.Stdout.Write(nil) |
|
| 47 |
- |
|
| 48 |
- // Resend every event in the [since, until] time interval. |
|
| 49 |
- if since != 0 {
|
|
| 50 |
- for _, event := range srv.GetEvents() {
|
|
| 51 |
- if event.Time >= since && (event.Time <= until || until == 0) {
|
|
| 52 |
- if err := sendEvent(&event); err != nil {
|
|
| 53 |
- return job.Error(err) |
|
| 54 |
- } |
|
| 55 |
- } |
|
| 56 |
- } |
|
| 57 |
- } |
|
| 58 |
- |
|
| 59 |
- for {
|
|
| 60 |
- select {
|
|
| 61 |
- case event, ok := <-listener: |
|
| 62 |
- if !ok {
|
|
| 63 |
- return engine.StatusOK |
|
| 64 |
- } |
|
| 65 |
- if err := sendEvent(&event); err != nil {
|
|
| 66 |
- return job.Error(err) |
|
| 67 |
- } |
|
| 68 |
- case <-timeout.C: |
|
| 69 |
- return engine.StatusOK |
|
| 70 |
- } |
|
| 71 |
- } |
|
| 72 |
-} |
|
| 73 |
- |
|
| 74 |
-// FIXME: this is a shim to allow breaking up other parts of Server without |
|
| 75 |
-// dragging the sphagetti dependency along. |
|
| 76 |
-func (srv *Server) Log(job *engine.Job) engine.Status {
|
|
| 77 |
- if len(job.Args) != 3 {
|
|
| 78 |
- return job.Errorf("usage: %s ACTION ID FROM", job.Name)
|
|
| 79 |
- } |
|
| 80 |
- srv.LogEvent(job.Args[0], job.Args[1], job.Args[2]) |
|
| 81 |
- return engine.StatusOK |
|
| 82 |
-} |
|
| 83 |
- |
|
| 84 |
-func (srv *Server) LogEvent(action, id, from string) *utils.JSONMessage {
|
|
| 85 |
- now := time.Now().UTC().Unix() |
|
| 86 |
- jm := utils.JSONMessage{Status: action, ID: id, From: from, Time: now}
|
|
| 87 |
- srv.AddEvent(jm) |
|
| 88 |
- srv.eventPublisher.Publish(jm) |
|
| 89 |
- return &jm |
|
| 90 |
-} |
|
| 91 |
- |
|
| 92 |
-func (srv *Server) AddEvent(jm utils.JSONMessage) {
|
|
| 93 |
- srv.Lock() |
|
| 94 |
- if len(srv.events) == cap(srv.events) {
|
|
| 95 |
- // discard oldest event |
|
| 96 |
- copy(srv.events, srv.events[1:]) |
|
| 97 |
- srv.events[len(srv.events)-1] = jm |
|
| 98 |
- } else {
|
|
| 99 |
- srv.events = append(srv.events, jm) |
|
| 100 |
- } |
|
| 101 |
- srv.Unlock() |
|
| 102 |
-} |
|
| 103 |
- |
|
| 104 |
-func (srv *Server) GetEvents() []utils.JSONMessage {
|
|
| 105 |
- srv.RLock() |
|
| 106 |
- defer srv.RUnlock() |
|
| 107 |
- return srv.events |
|
| 108 |
-} |
| ... | ... |
@@ -5,13 +5,10 @@ |
| 5 | 5 |
package server |
| 6 | 6 |
|
| 7 | 7 |
import ( |
| 8 |
- "encoding/json" |
|
| 9 | 8 |
"fmt" |
| 10 | 9 |
"io" |
| 11 | 10 |
"io/ioutil" |
| 12 |
- "log" |
|
| 13 | 11 |
"net" |
| 14 |
- "net/http" |
|
| 15 | 12 |
"net/url" |
| 16 | 13 |
"os" |
| 17 | 14 |
"os/exec" |
| ... | ... |
@@ -22,146 +19,12 @@ import ( |
| 22 | 22 |
"github.com/docker/docker/archive" |
| 23 | 23 |
"github.com/docker/docker/builder" |
| 24 | 24 |
"github.com/docker/docker/engine" |
| 25 |
- "github.com/docker/docker/graph" |
|
| 26 | 25 |
"github.com/docker/docker/image" |
| 27 | 26 |
"github.com/docker/docker/pkg/parsers" |
| 28 |
- "github.com/docker/docker/pkg/parsers/filters" |
|
| 29 | 27 |
"github.com/docker/docker/registry" |
| 30 | 28 |
"github.com/docker/docker/utils" |
| 31 | 29 |
) |
| 32 | 30 |
|
| 33 |
-// ImageExport exports all images with the given tag. All versions |
|
| 34 |
-// containing the same tag are exported. The resulting output is an |
|
| 35 |
-// uncompressed tar ball. |
|
| 36 |
-// name is the set of tags to export. |
|
| 37 |
-// out is the writer where the images are written to. |
|
| 38 |
-func (srv *Server) ImageExport(job *engine.Job) engine.Status {
|
|
| 39 |
- if len(job.Args) != 1 {
|
|
| 40 |
- return job.Errorf("Usage: %s IMAGE\n", job.Name)
|
|
| 41 |
- } |
|
| 42 |
- name := job.Args[0] |
|
| 43 |
- // get image json |
|
| 44 |
- tempdir, err := ioutil.TempDir("", "docker-export-")
|
|
| 45 |
- if err != nil {
|
|
| 46 |
- return job.Error(err) |
|
| 47 |
- } |
|
| 48 |
- defer os.RemoveAll(tempdir) |
|
| 49 |
- |
|
| 50 |
- utils.Debugf("Serializing %s", name)
|
|
| 51 |
- |
|
| 52 |
- rootRepoMap := map[string]graph.Repository{}
|
|
| 53 |
- rootRepo, err := srv.daemon.Repositories().Get(name) |
|
| 54 |
- if err != nil {
|
|
| 55 |
- return job.Error(err) |
|
| 56 |
- } |
|
| 57 |
- if rootRepo != nil {
|
|
| 58 |
- // this is a base repo name, like 'busybox' |
|
| 59 |
- |
|
| 60 |
- for _, id := range rootRepo {
|
|
| 61 |
- if err := srv.exportImage(job.Eng, id, tempdir); err != nil {
|
|
| 62 |
- return job.Error(err) |
|
| 63 |
- } |
|
| 64 |
- } |
|
| 65 |
- rootRepoMap[name] = rootRepo |
|
| 66 |
- } else {
|
|
| 67 |
- img, err := srv.daemon.Repositories().LookupImage(name) |
|
| 68 |
- if err != nil {
|
|
| 69 |
- return job.Error(err) |
|
| 70 |
- } |
|
| 71 |
- if img != nil {
|
|
| 72 |
- // This is a named image like 'busybox:latest' |
|
| 73 |
- repoName, repoTag := parsers.ParseRepositoryTag(name) |
|
| 74 |
- if err := srv.exportImage(job.Eng, img.ID, tempdir); err != nil {
|
|
| 75 |
- return job.Error(err) |
|
| 76 |
- } |
|
| 77 |
- // check this length, because a lookup of a truncated has will not have a tag |
|
| 78 |
- // and will not need to be added to this map |
|
| 79 |
- if len(repoTag) > 0 {
|
|
| 80 |
- rootRepoMap[repoName] = graph.Repository{repoTag: img.ID}
|
|
| 81 |
- } |
|
| 82 |
- } else {
|
|
| 83 |
- // this must be an ID that didn't get looked up just right? |
|
| 84 |
- if err := srv.exportImage(job.Eng, name, tempdir); err != nil {
|
|
| 85 |
- return job.Error(err) |
|
| 86 |
- } |
|
| 87 |
- } |
|
| 88 |
- } |
|
| 89 |
- // write repositories, if there is something to write |
|
| 90 |
- if len(rootRepoMap) > 0 {
|
|
| 91 |
- rootRepoJson, _ := json.Marshal(rootRepoMap) |
|
| 92 |
- |
|
| 93 |
- if err := ioutil.WriteFile(path.Join(tempdir, "repositories"), rootRepoJson, os.FileMode(0644)); err != nil {
|
|
| 94 |
- return job.Error(err) |
|
| 95 |
- } |
|
| 96 |
- } else {
|
|
| 97 |
- utils.Debugf("There were no repositories to write")
|
|
| 98 |
- } |
|
| 99 |
- |
|
| 100 |
- fs, err := archive.Tar(tempdir, archive.Uncompressed) |
|
| 101 |
- if err != nil {
|
|
| 102 |
- return job.Error(err) |
|
| 103 |
- } |
|
| 104 |
- defer fs.Close() |
|
| 105 |
- |
|
| 106 |
- if _, err := io.Copy(job.Stdout, fs); err != nil {
|
|
| 107 |
- return job.Error(err) |
|
| 108 |
- } |
|
| 109 |
- utils.Debugf("End Serializing %s", name)
|
|
| 110 |
- return engine.StatusOK |
|
| 111 |
-} |
|
| 112 |
- |
|
| 113 |
-func (srv *Server) exportImage(eng *engine.Engine, name, tempdir string) error {
|
|
| 114 |
- for n := name; n != ""; {
|
|
| 115 |
- // temporary directory |
|
| 116 |
- tmpImageDir := path.Join(tempdir, n) |
|
| 117 |
- if err := os.Mkdir(tmpImageDir, os.FileMode(0755)); err != nil {
|
|
| 118 |
- if os.IsExist(err) {
|
|
| 119 |
- return nil |
|
| 120 |
- } |
|
| 121 |
- return err |
|
| 122 |
- } |
|
| 123 |
- |
|
| 124 |
- var version = "1.0" |
|
| 125 |
- var versionBuf = []byte(version) |
|
| 126 |
- |
|
| 127 |
- if err := ioutil.WriteFile(path.Join(tmpImageDir, "VERSION"), versionBuf, os.FileMode(0644)); err != nil {
|
|
| 128 |
- return err |
|
| 129 |
- } |
|
| 130 |
- |
|
| 131 |
- // serialize json |
|
| 132 |
- json, err := os.Create(path.Join(tmpImageDir, "json")) |
|
| 133 |
- if err != nil {
|
|
| 134 |
- return err |
|
| 135 |
- } |
|
| 136 |
- job := eng.Job("image_inspect", n)
|
|
| 137 |
- job.SetenvBool("raw", true)
|
|
| 138 |
- job.Stdout.Add(json) |
|
| 139 |
- if err := job.Run(); err != nil {
|
|
| 140 |
- return err |
|
| 141 |
- } |
|
| 142 |
- |
|
| 143 |
- // serialize filesystem |
|
| 144 |
- fsTar, err := os.Create(path.Join(tmpImageDir, "layer.tar")) |
|
| 145 |
- if err != nil {
|
|
| 146 |
- return err |
|
| 147 |
- } |
|
| 148 |
- job = eng.Job("image_tarlayer", n)
|
|
| 149 |
- job.Stdout.Add(fsTar) |
|
| 150 |
- if err := job.Run(); err != nil {
|
|
| 151 |
- return err |
|
| 152 |
- } |
|
| 153 |
- |
|
| 154 |
- // find parent |
|
| 155 |
- job = eng.Job("image_get", n)
|
|
| 156 |
- info, _ := job.Stdout.AddEnv() |
|
| 157 |
- if err := job.Run(); err != nil {
|
|
| 158 |
- return err |
|
| 159 |
- } |
|
| 160 |
- n = info.Get("Parent")
|
|
| 161 |
- } |
|
| 162 |
- return nil |
|
| 163 |
-} |
|
| 164 |
- |
|
| 165 | 31 |
func (srv *Server) Build(job *engine.Job) engine.Status {
|
| 166 | 32 |
if len(job.Args) != 0 {
|
| 167 | 33 |
return job.Errorf("Usage: %s\n", job.Name)
|
| ... | ... |
@@ -242,282 +105,6 @@ func (srv *Server) Build(job *engine.Job) engine.Status {
|
| 242 | 242 |
return engine.StatusOK |
| 243 | 243 |
} |
| 244 | 244 |
|
| 245 |
-// Loads a set of images into the repository. This is the complementary of ImageExport. |
|
| 246 |
-// The input stream is an uncompressed tar ball containing images and metadata. |
|
| 247 |
-func (srv *Server) ImageLoad(job *engine.Job) engine.Status {
|
|
| 248 |
- tmpImageDir, err := ioutil.TempDir("", "docker-import-")
|
|
| 249 |
- if err != nil {
|
|
| 250 |
- return job.Error(err) |
|
| 251 |
- } |
|
| 252 |
- defer os.RemoveAll(tmpImageDir) |
|
| 253 |
- |
|
| 254 |
- var ( |
|
| 255 |
- repoTarFile = path.Join(tmpImageDir, "repo.tar") |
|
| 256 |
- repoDir = path.Join(tmpImageDir, "repo") |
|
| 257 |
- ) |
|
| 258 |
- |
|
| 259 |
- tarFile, err := os.Create(repoTarFile) |
|
| 260 |
- if err != nil {
|
|
| 261 |
- return job.Error(err) |
|
| 262 |
- } |
|
| 263 |
- if _, err := io.Copy(tarFile, job.Stdin); err != nil {
|
|
| 264 |
- return job.Error(err) |
|
| 265 |
- } |
|
| 266 |
- tarFile.Close() |
|
| 267 |
- |
|
| 268 |
- repoFile, err := os.Open(repoTarFile) |
|
| 269 |
- if err != nil {
|
|
| 270 |
- return job.Error(err) |
|
| 271 |
- } |
|
| 272 |
- if err := os.Mkdir(repoDir, os.ModeDir); err != nil {
|
|
| 273 |
- return job.Error(err) |
|
| 274 |
- } |
|
| 275 |
- if err := archive.Untar(repoFile, repoDir, nil); err != nil {
|
|
| 276 |
- return job.Error(err) |
|
| 277 |
- } |
|
| 278 |
- |
|
| 279 |
- dirs, err := ioutil.ReadDir(repoDir) |
|
| 280 |
- if err != nil {
|
|
| 281 |
- return job.Error(err) |
|
| 282 |
- } |
|
| 283 |
- |
|
| 284 |
- for _, d := range dirs {
|
|
| 285 |
- if d.IsDir() {
|
|
| 286 |
- if err := srv.recursiveLoad(job.Eng, d.Name(), tmpImageDir); err != nil {
|
|
| 287 |
- return job.Error(err) |
|
| 288 |
- } |
|
| 289 |
- } |
|
| 290 |
- } |
|
| 291 |
- |
|
| 292 |
- repositoriesJson, err := ioutil.ReadFile(path.Join(tmpImageDir, "repo", "repositories")) |
|
| 293 |
- if err == nil {
|
|
| 294 |
- repositories := map[string]graph.Repository{}
|
|
| 295 |
- if err := json.Unmarshal(repositoriesJson, &repositories); err != nil {
|
|
| 296 |
- return job.Error(err) |
|
| 297 |
- } |
|
| 298 |
- |
|
| 299 |
- for imageName, tagMap := range repositories {
|
|
| 300 |
- for tag, address := range tagMap {
|
|
| 301 |
- if err := srv.daemon.Repositories().Set(imageName, tag, address, true); err != nil {
|
|
| 302 |
- return job.Error(err) |
|
| 303 |
- } |
|
| 304 |
- } |
|
| 305 |
- } |
|
| 306 |
- } else if !os.IsNotExist(err) {
|
|
| 307 |
- return job.Error(err) |
|
| 308 |
- } |
|
| 309 |
- |
|
| 310 |
- return engine.StatusOK |
|
| 311 |
-} |
|
| 312 |
- |
|
| 313 |
-func (srv *Server) recursiveLoad(eng *engine.Engine, address, tmpImageDir string) error {
|
|
| 314 |
- if err := eng.Job("image_get", address).Run(); err != nil {
|
|
| 315 |
- utils.Debugf("Loading %s", address)
|
|
| 316 |
- |
|
| 317 |
- imageJson, err := ioutil.ReadFile(path.Join(tmpImageDir, "repo", address, "json")) |
|
| 318 |
- if err != nil {
|
|
| 319 |
- utils.Debugf("Error reading json", err)
|
|
| 320 |
- return err |
|
| 321 |
- } |
|
| 322 |
- |
|
| 323 |
- layer, err := os.Open(path.Join(tmpImageDir, "repo", address, "layer.tar")) |
|
| 324 |
- if err != nil {
|
|
| 325 |
- utils.Debugf("Error reading embedded tar", err)
|
|
| 326 |
- return err |
|
| 327 |
- } |
|
| 328 |
- img, err := image.NewImgJSON(imageJson) |
|
| 329 |
- if err != nil {
|
|
| 330 |
- utils.Debugf("Error unmarshalling json", err)
|
|
| 331 |
- return err |
|
| 332 |
- } |
|
| 333 |
- if img.Parent != "" {
|
|
| 334 |
- if !srv.daemon.Graph().Exists(img.Parent) {
|
|
| 335 |
- if err := srv.recursiveLoad(eng, img.Parent, tmpImageDir); err != nil {
|
|
| 336 |
- return err |
|
| 337 |
- } |
|
| 338 |
- } |
|
| 339 |
- } |
|
| 340 |
- if err := srv.daemon.Graph().Register(imageJson, layer, img); err != nil {
|
|
| 341 |
- return err |
|
| 342 |
- } |
|
| 343 |
- } |
|
| 344 |
- utils.Debugf("Completed processing %s", address)
|
|
| 345 |
- |
|
| 346 |
- return nil |
|
| 347 |
-} |
|
| 348 |
- |
|
| 349 |
-func (srv *Server) ImagesViz(job *engine.Job) engine.Status {
|
|
| 350 |
- images, _ := srv.daemon.Graph().Map() |
|
| 351 |
- if images == nil {
|
|
| 352 |
- return engine.StatusOK |
|
| 353 |
- } |
|
| 354 |
- job.Stdout.Write([]byte("digraph docker {\n"))
|
|
| 355 |
- |
|
| 356 |
- var ( |
|
| 357 |
- parentImage *image.Image |
|
| 358 |
- err error |
|
| 359 |
- ) |
|
| 360 |
- for _, image := range images {
|
|
| 361 |
- parentImage, err = image.GetParent() |
|
| 362 |
- if err != nil {
|
|
| 363 |
- return job.Errorf("Error while getting parent image: %v", err)
|
|
| 364 |
- } |
|
| 365 |
- if parentImage != nil {
|
|
| 366 |
- job.Stdout.Write([]byte(" \"" + parentImage.ID + "\" -> \"" + image.ID + "\"\n"))
|
|
| 367 |
- } else {
|
|
| 368 |
- job.Stdout.Write([]byte(" base -> \"" + image.ID + "\" [style=invis]\n"))
|
|
| 369 |
- } |
|
| 370 |
- } |
|
| 371 |
- |
|
| 372 |
- for id, repos := range srv.daemon.Repositories().GetRepoRefs() {
|
|
| 373 |
- job.Stdout.Write([]byte(" \"" + id + "\" [label=\"" + id + "\\n" + strings.Join(repos, "\\n") + "\",shape=box,fillcolor=\"paleturquoise\",style=\"filled,rounded\"];\n"))
|
|
| 374 |
- } |
|
| 375 |
- job.Stdout.Write([]byte(" base [style=invisible]\n}\n"))
|
|
| 376 |
- return engine.StatusOK |
|
| 377 |
-} |
|
| 378 |
- |
|
| 379 |
-func (srv *Server) Images(job *engine.Job) engine.Status {
|
|
| 380 |
- var ( |
|
| 381 |
- allImages map[string]*image.Image |
|
| 382 |
- err error |
|
| 383 |
- filt_tagged = true |
|
| 384 |
- ) |
|
| 385 |
- |
|
| 386 |
- imageFilters, err := filters.FromParam(job.Getenv("filters"))
|
|
| 387 |
- if err != nil {
|
|
| 388 |
- return job.Error(err) |
|
| 389 |
- } |
|
| 390 |
- if i, ok := imageFilters["dangling"]; ok {
|
|
| 391 |
- for _, value := range i {
|
|
| 392 |
- if strings.ToLower(value) == "true" {
|
|
| 393 |
- filt_tagged = false |
|
| 394 |
- } |
|
| 395 |
- } |
|
| 396 |
- } |
|
| 397 |
- |
|
| 398 |
- if job.GetenvBool("all") && filt_tagged {
|
|
| 399 |
- allImages, err = srv.daemon.Graph().Map() |
|
| 400 |
- } else {
|
|
| 401 |
- allImages, err = srv.daemon.Graph().Heads() |
|
| 402 |
- } |
|
| 403 |
- if err != nil {
|
|
| 404 |
- return job.Error(err) |
|
| 405 |
- } |
|
| 406 |
- lookup := make(map[string]*engine.Env) |
|
| 407 |
- srv.daemon.Repositories().Lock() |
|
| 408 |
- for name, repository := range srv.daemon.Repositories().Repositories {
|
|
| 409 |
- if job.Getenv("filter") != "" {
|
|
| 410 |
- if match, _ := path.Match(job.Getenv("filter"), name); !match {
|
|
| 411 |
- continue |
|
| 412 |
- } |
|
| 413 |
- } |
|
| 414 |
- for tag, id := range repository {
|
|
| 415 |
- image, err := srv.daemon.Graph().Get(id) |
|
| 416 |
- if err != nil {
|
|
| 417 |
- log.Printf("Warning: couldn't load %s from %s/%s: %s", id, name, tag, err)
|
|
| 418 |
- continue |
|
| 419 |
- } |
|
| 420 |
- |
|
| 421 |
- if out, exists := lookup[id]; exists {
|
|
| 422 |
- if filt_tagged {
|
|
| 423 |
- out.SetList("RepoTags", append(out.GetList("RepoTags"), fmt.Sprintf("%s:%s", name, tag)))
|
|
| 424 |
- } |
|
| 425 |
- } else {
|
|
| 426 |
- // get the boolean list for if only the untagged images are requested |
|
| 427 |
- delete(allImages, id) |
|
| 428 |
- if filt_tagged {
|
|
| 429 |
- out := &engine.Env{}
|
|
| 430 |
- out.Set("ParentId", image.Parent)
|
|
| 431 |
- out.SetList("RepoTags", []string{fmt.Sprintf("%s:%s", name, tag)})
|
|
| 432 |
- out.Set("Id", image.ID)
|
|
| 433 |
- out.SetInt64("Created", image.Created.Unix())
|
|
| 434 |
- out.SetInt64("Size", image.Size)
|
|
| 435 |
- out.SetInt64("VirtualSize", image.GetParentsSize(0)+image.Size)
|
|
| 436 |
- lookup[id] = out |
|
| 437 |
- } |
|
| 438 |
- } |
|
| 439 |
- |
|
| 440 |
- } |
|
| 441 |
- } |
|
| 442 |
- srv.daemon.Repositories().Unlock() |
|
| 443 |
- |
|
| 444 |
- outs := engine.NewTable("Created", len(lookup))
|
|
| 445 |
- for _, value := range lookup {
|
|
| 446 |
- outs.Add(value) |
|
| 447 |
- } |
|
| 448 |
- |
|
| 449 |
- // Display images which aren't part of a repository/tag |
|
| 450 |
- if job.Getenv("filter") == "" {
|
|
| 451 |
- for _, image := range allImages {
|
|
| 452 |
- out := &engine.Env{}
|
|
| 453 |
- out.Set("ParentId", image.Parent)
|
|
| 454 |
- out.SetList("RepoTags", []string{"<none>:<none>"})
|
|
| 455 |
- out.Set("Id", image.ID)
|
|
| 456 |
- out.SetInt64("Created", image.Created.Unix())
|
|
| 457 |
- out.SetInt64("Size", image.Size)
|
|
| 458 |
- out.SetInt64("VirtualSize", image.GetParentsSize(0)+image.Size)
|
|
| 459 |
- outs.Add(out) |
|
| 460 |
- } |
|
| 461 |
- } |
|
| 462 |
- |
|
| 463 |
- outs.ReverseSort() |
|
| 464 |
- if _, err := outs.WriteListTo(job.Stdout); err != nil {
|
|
| 465 |
- return job.Error(err) |
|
| 466 |
- } |
|
| 467 |
- return engine.StatusOK |
|
| 468 |
-} |
|
| 469 |
- |
|
| 470 |
-func (srv *Server) ImageHistory(job *engine.Job) engine.Status {
|
|
| 471 |
- if n := len(job.Args); n != 1 {
|
|
| 472 |
- return job.Errorf("Usage: %s IMAGE", job.Name)
|
|
| 473 |
- } |
|
| 474 |
- name := job.Args[0] |
|
| 475 |
- foundImage, err := srv.daemon.Repositories().LookupImage(name) |
|
| 476 |
- if err != nil {
|
|
| 477 |
- return job.Error(err) |
|
| 478 |
- } |
|
| 479 |
- |
|
| 480 |
- lookupMap := make(map[string][]string) |
|
| 481 |
- for name, repository := range srv.daemon.Repositories().Repositories {
|
|
| 482 |
- for tag, id := range repository {
|
|
| 483 |
- // If the ID already has a reverse lookup, do not update it unless for "latest" |
|
| 484 |
- if _, exists := lookupMap[id]; !exists {
|
|
| 485 |
- lookupMap[id] = []string{}
|
|
| 486 |
- } |
|
| 487 |
- lookupMap[id] = append(lookupMap[id], name+":"+tag) |
|
| 488 |
- } |
|
| 489 |
- } |
|
| 490 |
- |
|
| 491 |
- outs := engine.NewTable("Created", 0)
|
|
| 492 |
- err = foundImage.WalkHistory(func(img *image.Image) error {
|
|
| 493 |
- out := &engine.Env{}
|
|
| 494 |
- out.Set("Id", img.ID)
|
|
| 495 |
- out.SetInt64("Created", img.Created.Unix())
|
|
| 496 |
- out.Set("CreatedBy", strings.Join(img.ContainerConfig.Cmd, " "))
|
|
| 497 |
- out.SetList("Tags", lookupMap[img.ID])
|
|
| 498 |
- out.SetInt64("Size", img.Size)
|
|
| 499 |
- outs.Add(out) |
|
| 500 |
- return nil |
|
| 501 |
- }) |
|
| 502 |
- if _, err := outs.WriteListTo(job.Stdout); err != nil {
|
|
| 503 |
- return job.Error(err) |
|
| 504 |
- } |
|
| 505 |
- return engine.StatusOK |
|
| 506 |
-} |
|
| 507 |
-func (srv *Server) ImageTag(job *engine.Job) engine.Status {
|
|
| 508 |
- if len(job.Args) != 2 && len(job.Args) != 3 {
|
|
| 509 |
- return job.Errorf("Usage: %s IMAGE REPOSITORY [TAG]\n", job.Name)
|
|
| 510 |
- } |
|
| 511 |
- var tag string |
|
| 512 |
- if len(job.Args) == 3 {
|
|
| 513 |
- tag = job.Args[2] |
|
| 514 |
- } |
|
| 515 |
- if err := srv.daemon.Repositories().Set(job.Args[1], tag, job.Args[0], job.GetenvBool("force")); err != nil {
|
|
| 516 |
- return job.Error(err) |
|
| 517 |
- } |
|
| 518 |
- return engine.StatusOK |
|
| 519 |
-} |
|
| 520 |
- |
|
| 521 | 245 |
func (srv *Server) pullImage(r *registry.Registry, out io.Writer, imgID, endpoint string, token []string, sf *utils.StreamFormatter) error {
|
| 522 | 246 |
history, err := r.GetRemoteHistory(imgID, endpoint, token) |
| 523 | 247 |
if err != nil {
|
| ... | ... |
@@ -1038,198 +625,6 @@ func (srv *Server) ImagePush(job *engine.Job) engine.Status {
|
| 1038 | 1038 |
return engine.StatusOK |
| 1039 | 1039 |
} |
| 1040 | 1040 |
|
| 1041 |
-func (srv *Server) ImageImport(job *engine.Job) engine.Status {
|
|
| 1042 |
- if n := len(job.Args); n != 2 && n != 3 {
|
|
| 1043 |
- return job.Errorf("Usage: %s SRC REPO [TAG]", job.Name)
|
|
| 1044 |
- } |
|
| 1045 |
- var ( |
|
| 1046 |
- src = job.Args[0] |
|
| 1047 |
- repo = job.Args[1] |
|
| 1048 |
- tag string |
|
| 1049 |
- sf = utils.NewStreamFormatter(job.GetenvBool("json"))
|
|
| 1050 |
- archive archive.ArchiveReader |
|
| 1051 |
- resp *http.Response |
|
| 1052 |
- ) |
|
| 1053 |
- if len(job.Args) > 2 {
|
|
| 1054 |
- tag = job.Args[2] |
|
| 1055 |
- } |
|
| 1056 |
- |
|
| 1057 |
- if src == "-" {
|
|
| 1058 |
- archive = job.Stdin |
|
| 1059 |
- } else {
|
|
| 1060 |
- u, err := url.Parse(src) |
|
| 1061 |
- if err != nil {
|
|
| 1062 |
- return job.Error(err) |
|
| 1063 |
- } |
|
| 1064 |
- if u.Scheme == "" {
|
|
| 1065 |
- u.Scheme = "http" |
|
| 1066 |
- u.Host = src |
|
| 1067 |
- u.Path = "" |
|
| 1068 |
- } |
|
| 1069 |
- job.Stdout.Write(sf.FormatStatus("", "Downloading from %s", u))
|
|
| 1070 |
- resp, err = utils.Download(u.String()) |
|
| 1071 |
- if err != nil {
|
|
| 1072 |
- return job.Error(err) |
|
| 1073 |
- } |
|
| 1074 |
- progressReader := utils.ProgressReader(resp.Body, int(resp.ContentLength), job.Stdout, sf, true, "", "Importing") |
|
| 1075 |
- defer progressReader.Close() |
|
| 1076 |
- archive = progressReader |
|
| 1077 |
- } |
|
| 1078 |
- img, err := srv.daemon.Graph().Create(archive, "", "", "Imported from "+src, "", nil, nil) |
|
| 1079 |
- if err != nil {
|
|
| 1080 |
- return job.Error(err) |
|
| 1081 |
- } |
|
| 1082 |
- // Optionally register the image at REPO/TAG |
|
| 1083 |
- if repo != "" {
|
|
| 1084 |
- if err := srv.daemon.Repositories().Set(repo, tag, img.ID, true); err != nil {
|
|
| 1085 |
- return job.Error(err) |
|
| 1086 |
- } |
|
| 1087 |
- } |
|
| 1088 |
- job.Stdout.Write(sf.FormatStatus("", img.ID))
|
|
| 1089 |
- return engine.StatusOK |
|
| 1090 |
-} |
|
| 1091 |
-func (srv *Server) DeleteImage(name string, imgs *engine.Table, first, force, noprune bool) error {
|
|
| 1092 |
- var ( |
|
| 1093 |
- repoName, tag string |
|
| 1094 |
- tags = []string{}
|
|
| 1095 |
- tagDeleted bool |
|
| 1096 |
- ) |
|
| 1097 |
- |
|
| 1098 |
- repoName, tag = parsers.ParseRepositoryTag(name) |
|
| 1099 |
- if tag == "" {
|
|
| 1100 |
- tag = graph.DEFAULTTAG |
|
| 1101 |
- } |
|
| 1102 |
- |
|
| 1103 |
- img, err := srv.daemon.Repositories().LookupImage(name) |
|
| 1104 |
- if err != nil {
|
|
| 1105 |
- if r, _ := srv.daemon.Repositories().Get(repoName); r != nil {
|
|
| 1106 |
- return fmt.Errorf("No such image: %s:%s", repoName, tag)
|
|
| 1107 |
- } |
|
| 1108 |
- return fmt.Errorf("No such image: %s", name)
|
|
| 1109 |
- } |
|
| 1110 |
- |
|
| 1111 |
- if strings.Contains(img.ID, name) {
|
|
| 1112 |
- repoName = "" |
|
| 1113 |
- tag = "" |
|
| 1114 |
- } |
|
| 1115 |
- |
|
| 1116 |
- byParents, err := srv.daemon.Graph().ByParent() |
|
| 1117 |
- if err != nil {
|
|
| 1118 |
- return err |
|
| 1119 |
- } |
|
| 1120 |
- |
|
| 1121 |
- //If delete by id, see if the id belong only to one repository |
|
| 1122 |
- if repoName == "" {
|
|
| 1123 |
- for _, repoAndTag := range srv.daemon.Repositories().ByID()[img.ID] {
|
|
| 1124 |
- parsedRepo, parsedTag := parsers.ParseRepositoryTag(repoAndTag) |
|
| 1125 |
- if repoName == "" || repoName == parsedRepo {
|
|
| 1126 |
- repoName = parsedRepo |
|
| 1127 |
- if parsedTag != "" {
|
|
| 1128 |
- tags = append(tags, parsedTag) |
|
| 1129 |
- } |
|
| 1130 |
- } else if repoName != parsedRepo && !force {
|
|
| 1131 |
- // the id belongs to multiple repos, like base:latest and user:test, |
|
| 1132 |
- // in that case return conflict |
|
| 1133 |
- return fmt.Errorf("Conflict, cannot delete image %s because it is tagged in multiple repositories, use -f to force", name)
|
|
| 1134 |
- } |
|
| 1135 |
- } |
|
| 1136 |
- } else {
|
|
| 1137 |
- tags = append(tags, tag) |
|
| 1138 |
- } |
|
| 1139 |
- |
|
| 1140 |
- if !first && len(tags) > 0 {
|
|
| 1141 |
- return nil |
|
| 1142 |
- } |
|
| 1143 |
- |
|
| 1144 |
- //Untag the current image |
|
| 1145 |
- for _, tag := range tags {
|
|
| 1146 |
- tagDeleted, err = srv.daemon.Repositories().Delete(repoName, tag) |
|
| 1147 |
- if err != nil {
|
|
| 1148 |
- return err |
|
| 1149 |
- } |
|
| 1150 |
- if tagDeleted {
|
|
| 1151 |
- out := &engine.Env{}
|
|
| 1152 |
- out.Set("Untagged", repoName+":"+tag)
|
|
| 1153 |
- imgs.Add(out) |
|
| 1154 |
- srv.LogEvent("untag", img.ID, "")
|
|
| 1155 |
- } |
|
| 1156 |
- } |
|
| 1157 |
- tags = srv.daemon.Repositories().ByID()[img.ID] |
|
| 1158 |
- if (len(tags) <= 1 && repoName == "") || len(tags) == 0 {
|
|
| 1159 |
- if len(byParents[img.ID]) == 0 {
|
|
| 1160 |
- if err := srv.canDeleteImage(img.ID, force, tagDeleted); err != nil {
|
|
| 1161 |
- return err |
|
| 1162 |
- } |
|
| 1163 |
- if err := srv.daemon.Repositories().DeleteAll(img.ID); err != nil {
|
|
| 1164 |
- return err |
|
| 1165 |
- } |
|
| 1166 |
- if err := srv.daemon.Graph().Delete(img.ID); err != nil {
|
|
| 1167 |
- return err |
|
| 1168 |
- } |
|
| 1169 |
- out := &engine.Env{}
|
|
| 1170 |
- out.Set("Deleted", img.ID)
|
|
| 1171 |
- imgs.Add(out) |
|
| 1172 |
- srv.LogEvent("delete", img.ID, "")
|
|
| 1173 |
- if img.Parent != "" && !noprune {
|
|
| 1174 |
- err := srv.DeleteImage(img.Parent, imgs, false, force, noprune) |
|
| 1175 |
- if first {
|
|
| 1176 |
- return err |
|
| 1177 |
- } |
|
| 1178 |
- |
|
| 1179 |
- } |
|
| 1180 |
- |
|
| 1181 |
- } |
|
| 1182 |
- } |
|
| 1183 |
- return nil |
|
| 1184 |
-} |
|
| 1185 |
- |
|
| 1186 |
-func (srv *Server) ImageDelete(job *engine.Job) engine.Status {
|
|
| 1187 |
- if n := len(job.Args); n != 1 {
|
|
| 1188 |
- return job.Errorf("Usage: %s IMAGE", job.Name)
|
|
| 1189 |
- } |
|
| 1190 |
- imgs := engine.NewTable("", 0)
|
|
| 1191 |
- if err := srv.DeleteImage(job.Args[0], imgs, true, job.GetenvBool("force"), job.GetenvBool("noprune")); err != nil {
|
|
| 1192 |
- return job.Error(err) |
|
| 1193 |
- } |
|
| 1194 |
- if len(imgs.Data) == 0 {
|
|
| 1195 |
- return job.Errorf("Conflict, %s wasn't deleted", job.Args[0])
|
|
| 1196 |
- } |
|
| 1197 |
- if _, err := imgs.WriteListTo(job.Stdout); err != nil {
|
|
| 1198 |
- return job.Error(err) |
|
| 1199 |
- } |
|
| 1200 |
- return engine.StatusOK |
|
| 1201 |
-} |
|
| 1202 |
- |
|
| 1203 |
-func (srv *Server) canDeleteImage(imgID string, force, untagged bool) error {
|
|
| 1204 |
- var message string |
|
| 1205 |
- if untagged {
|
|
| 1206 |
- message = " (docker untagged the image)" |
|
| 1207 |
- } |
|
| 1208 |
- for _, container := range srv.daemon.List() {
|
|
| 1209 |
- parent, err := srv.daemon.Repositories().LookupImage(container.Image) |
|
| 1210 |
- if err != nil {
|
|
| 1211 |
- return err |
|
| 1212 |
- } |
|
| 1213 |
- |
|
| 1214 |
- if err := parent.WalkHistory(func(p *image.Image) error {
|
|
| 1215 |
- if imgID == p.ID {
|
|
| 1216 |
- if container.State.IsRunning() {
|
|
| 1217 |
- if force {
|
|
| 1218 |
- return fmt.Errorf("Conflict, cannot force delete %s because the running container %s is using it%s, stop it and retry", utils.TruncateID(imgID), utils.TruncateID(container.ID), message)
|
|
| 1219 |
- } |
|
| 1220 |
- return fmt.Errorf("Conflict, cannot delete %s because the running container %s is using it%s, stop it and use -f to force", utils.TruncateID(imgID), utils.TruncateID(container.ID), message)
|
|
| 1221 |
- } else if !force {
|
|
| 1222 |
- return fmt.Errorf("Conflict, cannot delete %s because the container %s is using it%s, use -f to force", utils.TruncateID(imgID), utils.TruncateID(container.ID), message)
|
|
| 1223 |
- } |
|
| 1224 |
- } |
|
| 1225 |
- return nil |
|
| 1226 |
- }); err != nil {
|
|
| 1227 |
- return err |
|
| 1228 |
- } |
|
| 1229 |
- } |
|
| 1230 |
- return nil |
|
| 1231 |
-} |
|
| 1232 |
- |
|
| 1233 | 1041 |
func (srv *Server) poolAdd(kind, key string) (chan struct{}, error) {
|
| 1234 | 1042 |
srv.Lock() |
| 1235 | 1043 |
defer srv.Unlock() |
| ... | ... |
@@ -86,20 +86,10 @@ func InitServer(job *engine.Job) engine.Status {
|
| 86 | 86 |
job.Eng.Hack_SetGlobalVar("httpapi.daemon", srv.daemon)
|
| 87 | 87 |
|
| 88 | 88 |
for name, handler := range map[string]engine.Handler{
|
| 89 |
- "tag": srv.ImageTag, // FIXME merge with "image_tag" |
|
| 90 |
- "info": srv.DockerInfo, |
|
| 91 |
- "image_export": srv.ImageExport, |
|
| 92 |
- "images": srv.Images, |
|
| 93 |
- "history": srv.ImageHistory, |
|
| 94 |
- "viz": srv.ImagesViz, |
|
| 95 |
- "log": srv.Log, |
|
| 96 |
- "load": srv.ImageLoad, |
|
| 97 |
- "build": srv.Build, |
|
| 98 |
- "pull": srv.ImagePull, |
|
| 99 |
- "import": srv.ImageImport, |
|
| 100 |
- "image_delete": srv.ImageDelete, |
|
| 101 |
- "events": srv.Events, |
|
| 102 |
- "push": srv.ImagePush, |
|
| 89 |
+ "info": srv.DockerInfo, |
|
| 90 |
+ "build": srv.Build, |
|
| 91 |
+ "pull": srv.ImagePull, |
|
| 92 |
+ "push": srv.ImagePush, |
|
| 103 | 93 |
} {
|
| 104 | 94 |
if err := job.Eng.Register(name, srv.handlerWrap(handler)); err != nil {
|
| 105 | 95 |
return job.Error(err) |
| ... | ... |
@@ -125,12 +115,10 @@ func NewServer(eng *engine.Engine, config *daemonconfig.Config) (*Server, error) |
| 125 | 125 |
return nil, err |
| 126 | 126 |
} |
| 127 | 127 |
srv := &Server{
|
| 128 |
- Eng: eng, |
|
| 129 |
- daemon: daemon, |
|
| 130 |
- pullingPool: make(map[string]chan struct{}),
|
|
| 131 |
- pushingPool: make(map[string]chan struct{}),
|
|
| 132 |
- events: make([]utils.JSONMessage, 0, 64), //only keeps the 64 last events |
|
| 133 |
- eventPublisher: utils.NewJSONMessagePublisher(), |
|
| 128 |
+ Eng: eng, |
|
| 129 |
+ daemon: daemon, |
|
| 130 |
+ pullingPool: make(map[string]chan struct{}),
|
|
| 131 |
+ pushingPool: make(map[string]chan struct{}),
|
|
| 134 | 132 |
} |
| 135 | 133 |
daemon.SetServer(srv) |
| 136 | 134 |
return srv, nil |
| ... | ... |
@@ -67,6 +67,11 @@ func (srv *Server) DockerInfo(job *engine.Job) engine.Status {
|
| 67 | 67 |
initPath = srv.daemon.SystemInitPath() |
| 68 | 68 |
} |
| 69 | 69 |
|
| 70 |
+ cjob := job.Eng.Job("subscribers_count")
|
|
| 71 |
+ env, _ := cjob.Stdout.AddEnv() |
|
| 72 |
+ if err := cjob.Run(); err != nil {
|
|
| 73 |
+ return job.Error(err) |
|
| 74 |
+ } |
|
| 70 | 75 |
v := &engine.Env{}
|
| 71 | 76 |
v.SetInt("Containers", len(srv.daemon.List()))
|
| 72 | 77 |
v.SetInt("Images", imgcount)
|
| ... | ... |
@@ -79,7 +84,7 @@ func (srv *Server) DockerInfo(job *engine.Job) engine.Status {
|
| 79 | 79 |
v.SetInt("NFd", utils.GetTotalUsedFds())
|
| 80 | 80 |
v.SetInt("NGoroutines", runtime.NumGoroutine())
|
| 81 | 81 |
v.Set("ExecutionDriver", srv.daemon.ExecutionDriver().Name())
|
| 82 |
- v.SetInt("NEventsListener", srv.eventPublisher.SubscribersCount())
|
|
| 82 |
+ v.SetInt("NEventsListener", env.GetInt("count"))
|
|
| 83 | 83 |
v.Set("KernelVersion", kernelVersion)
|
| 84 | 84 |
v.Set("OperatingSystem", operatingSystem)
|
| 85 | 85 |
v.Set("IndexServerAddress", registry.IndexServerAddress())
|
| ... | ... |
@@ -128,12 +133,10 @@ func (srv *Server) Close() error {
|
| 128 | 128 |
|
| 129 | 129 |
type Server struct {
|
| 130 | 130 |
sync.RWMutex |
| 131 |
- daemon *daemon.Daemon |
|
| 132 |
- pullingPool map[string]chan struct{}
|
|
| 133 |
- pushingPool map[string]chan struct{}
|
|
| 134 |
- events []utils.JSONMessage |
|
| 135 |
- eventPublisher *utils.JSONMessagePublisher |
|
| 136 |
- Eng *engine.Engine |
|
| 137 |
- running bool |
|
| 138 |
- tasks sync.WaitGroup |
|
| 131 |
+ daemon *daemon.Daemon |
|
| 132 |
+ pullingPool map[string]chan struct{}
|
|
| 133 |
+ pushingPool map[string]chan struct{}
|
|
| 134 |
+ Eng *engine.Engine |
|
| 135 |
+ running bool |
|
| 136 |
+ tasks sync.WaitGroup |
|
| 139 | 137 |
} |
| ... | ... |
@@ -1,11 +1,6 @@ |
| 1 | 1 |
package server |
| 2 | 2 |
|
| 3 |
-import ( |
|
| 4 |
- "testing" |
|
| 5 |
- "time" |
|
| 6 |
- |
|
| 7 |
- "github.com/docker/docker/utils" |
|
| 8 |
-) |
|
| 3 |
+import "testing" |
|
| 9 | 4 |
|
| 10 | 5 |
func TestPools(t *testing.T) {
|
| 11 | 6 |
srv := &Server{
|
| ... | ... |
@@ -44,55 +39,3 @@ func TestPools(t *testing.T) {
|
| 44 | 44 |
t.Fatalf("Expected `Unknown pool type`")
|
| 45 | 45 |
} |
| 46 | 46 |
} |
| 47 |
- |
|
| 48 |
-func TestLogEvent(t *testing.T) {
|
|
| 49 |
- srv := &Server{
|
|
| 50 |
- events: make([]utils.JSONMessage, 0, 64), |
|
| 51 |
- eventPublisher: utils.NewJSONMessagePublisher(), |
|
| 52 |
- } |
|
| 53 |
- |
|
| 54 |
- srv.LogEvent("fakeaction", "fakeid", "fakeimage")
|
|
| 55 |
- |
|
| 56 |
- listener := make(chan utils.JSONMessage) |
|
| 57 |
- srv.eventPublisher.Subscribe(listener) |
|
| 58 |
- |
|
| 59 |
- srv.LogEvent("fakeaction2", "fakeid", "fakeimage")
|
|
| 60 |
- |
|
| 61 |
- numEvents := len(srv.GetEvents()) |
|
| 62 |
- if numEvents != 2 {
|
|
| 63 |
- t.Fatalf("Expected 2 events, found %d", numEvents)
|
|
| 64 |
- } |
|
| 65 |
- go func() {
|
|
| 66 |
- time.Sleep(200 * time.Millisecond) |
|
| 67 |
- srv.LogEvent("fakeaction3", "fakeid", "fakeimage")
|
|
| 68 |
- time.Sleep(200 * time.Millisecond) |
|
| 69 |
- srv.LogEvent("fakeaction4", "fakeid", "fakeimage")
|
|
| 70 |
- }() |
|
| 71 |
- |
|
| 72 |
- setTimeout(t, "Listening for events timed out", 2*time.Second, func() {
|
|
| 73 |
- for i := 2; i < 4; i++ {
|
|
| 74 |
- event := <-listener |
|
| 75 |
- if event != srv.GetEvents()[i] {
|
|
| 76 |
- t.Fatalf("Event received it different than expected")
|
|
| 77 |
- } |
|
| 78 |
- } |
|
| 79 |
- }) |
|
| 80 |
-} |
|
| 81 |
- |
|
| 82 |
-// FIXME: this is duplicated from integration/commands_test.go |
|
| 83 |
-func setTimeout(t *testing.T, msg string, d time.Duration, f func()) {
|
|
| 84 |
- c := make(chan bool) |
|
| 85 |
- |
|
| 86 |
- // Make sure we are not too long |
|
| 87 |
- go func() {
|
|
| 88 |
- time.Sleep(d) |
|
| 89 |
- c <- true |
|
| 90 |
- }() |
|
| 91 |
- go func() {
|
|
| 92 |
- f() |
|
| 93 |
- c <- false |
|
| 94 |
- }() |
|
| 95 |
- if <-c && msg != "" {
|
|
| 96 |
- t.Fatal(msg) |
|
| 97 |
- } |
|
| 98 |
-} |
| 99 | 47 |
deleted file mode 100644 |
| ... | ... |
@@ -1,61 +0,0 @@ |
| 1 |
-package utils |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "sync" |
|
| 5 |
- "time" |
|
| 6 |
-) |
|
| 7 |
- |
|
| 8 |
-func NewJSONMessagePublisher() *JSONMessagePublisher {
|
|
| 9 |
- return &JSONMessagePublisher{}
|
|
| 10 |
-} |
|
| 11 |
- |
|
| 12 |
-type JSONMessageListener chan<- JSONMessage |
|
| 13 |
- |
|
| 14 |
-type JSONMessagePublisher struct {
|
|
| 15 |
- m sync.RWMutex |
|
| 16 |
- subscribers []JSONMessageListener |
|
| 17 |
-} |
|
| 18 |
- |
|
| 19 |
-func (p *JSONMessagePublisher) Subscribe(l JSONMessageListener) {
|
|
| 20 |
- p.m.Lock() |
|
| 21 |
- p.subscribers = append(p.subscribers, l) |
|
| 22 |
- p.m.Unlock() |
|
| 23 |
-} |
|
| 24 |
- |
|
| 25 |
-func (p *JSONMessagePublisher) SubscribersCount() int {
|
|
| 26 |
- p.m.RLock() |
|
| 27 |
- count := len(p.subscribers) |
|
| 28 |
- p.m.RUnlock() |
|
| 29 |
- return count |
|
| 30 |
-} |
|
| 31 |
- |
|
| 32 |
-// Unsubscribe closes and removes the specified listener from the list of |
|
| 33 |
-// previously registed ones. |
|
| 34 |
-// It returns a boolean value indicating if the listener was successfully |
|
| 35 |
-// found, closed and unregistered. |
|
| 36 |
-func (p *JSONMessagePublisher) Unsubscribe(l JSONMessageListener) bool {
|
|
| 37 |
- p.m.Lock() |
|
| 38 |
- defer p.m.Unlock() |
|
| 39 |
- |
|
| 40 |
- for i, subscriber := range p.subscribers {
|
|
| 41 |
- if subscriber == l {
|
|
| 42 |
- close(l) |
|
| 43 |
- p.subscribers = append(p.subscribers[:i], p.subscribers[i+1:]...) |
|
| 44 |
- return true |
|
| 45 |
- } |
|
| 46 |
- } |
|
| 47 |
- return false |
|
| 48 |
-} |
|
| 49 |
- |
|
| 50 |
-func (p *JSONMessagePublisher) Publish(m JSONMessage) {
|
|
| 51 |
- p.m.RLock() |
|
| 52 |
- for _, subscriber := range p.subscribers {
|
|
| 53 |
- // We give each subscriber a 100ms time window to receive the event, |
|
| 54 |
- // after which we move to the next. |
|
| 55 |
- select {
|
|
| 56 |
- case subscriber <- m: |
|
| 57 |
- case <-time.After(100 * time.Millisecond): |
|
| 58 |
- } |
|
| 59 |
- } |
|
| 60 |
- p.m.RUnlock() |
|
| 61 |
-} |
| 62 | 1 |
deleted file mode 100644 |
| ... | ... |
@@ -1,73 +0,0 @@ |
| 1 |
-package utils |
|
| 2 |
- |
|
| 3 |
-import ( |
|
| 4 |
- "testing" |
|
| 5 |
- "time" |
|
| 6 |
-) |
|
| 7 |
- |
|
| 8 |
-func assertSubscribersCount(t *testing.T, q *JSONMessagePublisher, expected int) {
|
|
| 9 |
- if q.SubscribersCount() != expected {
|
|
| 10 |
- t.Fatalf("Expected %d registered subscribers, got %d", expected, q.SubscribersCount())
|
|
| 11 |
- } |
|
| 12 |
-} |
|
| 13 |
- |
|
| 14 |
-func TestJSONMessagePublisherSubscription(t *testing.T) {
|
|
| 15 |
- q := NewJSONMessagePublisher() |
|
| 16 |
- l1 := make(chan JSONMessage) |
|
| 17 |
- l2 := make(chan JSONMessage) |
|
| 18 |
- |
|
| 19 |
- assertSubscribersCount(t, q, 0) |
|
| 20 |
- q.Subscribe(l1) |
|
| 21 |
- assertSubscribersCount(t, q, 1) |
|
| 22 |
- q.Subscribe(l2) |
|
| 23 |
- assertSubscribersCount(t, q, 2) |
|
| 24 |
- |
|
| 25 |
- q.Unsubscribe(l1) |
|
| 26 |
- q.Unsubscribe(l2) |
|
| 27 |
- assertSubscribersCount(t, q, 0) |
|
| 28 |
-} |
|
| 29 |
- |
|
| 30 |
-func TestJSONMessagePublisherPublish(t *testing.T) {
|
|
| 31 |
- q := NewJSONMessagePublisher() |
|
| 32 |
- l1 := make(chan JSONMessage) |
|
| 33 |
- l2 := make(chan JSONMessage) |
|
| 34 |
- |
|
| 35 |
- go func() {
|
|
| 36 |
- for {
|
|
| 37 |
- select {
|
|
| 38 |
- case <-l1: |
|
| 39 |
- close(l1) |
|
| 40 |
- l1 = nil |
|
| 41 |
- case <-l2: |
|
| 42 |
- close(l2) |
|
| 43 |
- l2 = nil |
|
| 44 |
- case <-time.After(1 * time.Second): |
|
| 45 |
- q.Unsubscribe(l1) |
|
| 46 |
- q.Unsubscribe(l2) |
|
| 47 |
- t.Fatal("Timeout waiting for broadcasted message")
|
|
| 48 |
- } |
|
| 49 |
- } |
|
| 50 |
- }() |
|
| 51 |
- |
|
| 52 |
- q.Subscribe(l1) |
|
| 53 |
- q.Subscribe(l2) |
|
| 54 |
- q.Publish(JSONMessage{})
|
|
| 55 |
-} |
|
| 56 |
- |
|
| 57 |
-func TestJSONMessagePublishTimeout(t *testing.T) {
|
|
| 58 |
- q := NewJSONMessagePublisher() |
|
| 59 |
- l := make(chan JSONMessage) |
|
| 60 |
- q.Subscribe(l) |
|
| 61 |
- |
|
| 62 |
- c := make(chan struct{})
|
|
| 63 |
- go func() {
|
|
| 64 |
- q.Publish(JSONMessage{})
|
|
| 65 |
- close(c) |
|
| 66 |
- }() |
|
| 67 |
- |
|
| 68 |
- select {
|
|
| 69 |
- case <-c: |
|
| 70 |
- case <-time.After(time.Second): |
|
| 71 |
- t.Fatal("Timeout publishing message")
|
|
| 72 |
- } |
|
| 73 |
-} |