... | ... |
@@ -387,7 +387,7 @@ func (srv *Server) CmdImport(stdin io.ReadCloser, stdout io.Writer, args ...stri |
387 | 387 |
} |
388 | 388 |
archive = ProgressReader(resp.Body, int(resp.ContentLength), stdout) |
389 | 389 |
} |
390 |
- img, err := srv.runtime.graph.Create(archive, "", "Imported from "+src) |
|
390 |
+ img, err := srv.runtime.graph.Create(archive, nil, "Imported from "+src) |
|
391 | 391 |
if err != nil { |
392 | 392 |
return err |
393 | 393 |
} |
... | ... |
@@ -737,33 +737,22 @@ func (srv *Server) CmdPs(stdin io.ReadCloser, stdout io.Writer, args ...string) |
737 | 737 |
|
738 | 738 |
func (srv *Server) CmdCommit(stdin io.ReadCloser, stdout io.Writer, args ...string) error { |
739 | 739 |
cmd := rcli.Subcmd(stdout, |
740 |
- "commit", "[OPTIONS] CONTAINER [DEST]", |
|
740 |
+ "commit", "[OPTIONS] CONTAINER [REPOSITORY [TAG]]", |
|
741 | 741 |
"Create a new image from a container's changes") |
742 | 742 |
if err := cmd.Parse(args); err != nil { |
743 | 743 |
return nil |
744 | 744 |
} |
745 |
- containerName, imgName := cmd.Arg(0), cmd.Arg(1) |
|
746 |
- if containerName == "" || imgName == "" { |
|
745 |
+ containerName, repository, tag := cmd.Arg(0), cmd.Arg(1), cmd.Arg(2) |
|
746 |
+ if containerName == "" { |
|
747 | 747 |
cmd.Usage() |
748 | 748 |
return nil |
749 | 749 |
} |
750 |
- if container := srv.runtime.Get(containerName); container != nil { |
|
751 |
- // FIXME: freeze the container before copying it to avoid data corruption? |
|
752 |
- // FIXME: this shouldn't be in commands. |
|
753 |
- rwTar, err := container.ExportRw() |
|
754 |
- if err != nil { |
|
755 |
- return err |
|
756 |
- } |
|
757 |
- // Create a new image from the container's base layers + a new layer from container changes |
|
758 |
- img, err := srv.runtime.graph.Create(rwTar, container.Image, "") |
|
759 |
- if err != nil { |
|
760 |
- return err |
|
761 |
- } |
|
762 |
- |
|
763 |
- fmt.Fprintln(stdout, img.Id) |
|
764 |
- return nil |
|
750 |
+ img, err := srv.runtime.Commit(containerName, repository, tag) |
|
751 |
+ if err != nil { |
|
752 |
+ return err |
|
765 | 753 |
} |
766 |
- return errors.New("No such container: " + containerName) |
|
754 |
+ fmt.Fprintln(stdout, img.Id) |
|
755 |
+ return nil |
|
767 | 756 |
} |
768 | 757 |
|
769 | 758 |
func (srv *Server) CmdExport(stdin io.ReadCloser, stdout io.Writer, args ...string) error { |
... | ... |
@@ -46,7 +46,7 @@ func TestCommitRun(t *testing.T) { |
46 | 46 |
if err != nil { |
47 | 47 |
t.Error(err) |
48 | 48 |
} |
49 |
- img, err := runtime.graph.Create(rwTar, container1.Image, "unit test commited image") |
|
49 |
+ img, err := runtime.graph.Create(rwTar, container1, "unit test commited image") |
|
50 | 50 |
if err != nil { |
51 | 51 |
t.Error(err) |
52 | 52 |
} |
... | ... |
@@ -47,13 +47,17 @@ func (graph *Graph) Get(id string) (*Image, error) { |
47 | 47 |
return img, nil |
48 | 48 |
} |
49 | 49 |
|
50 |
-func (graph *Graph) Create(layerData Archive, parent, comment string) (*Image, error) { |
|
50 |
+func (graph *Graph) Create(layerData Archive, container *Container, comment string) (*Image, error) { |
|
51 | 51 |
img := &Image{ |
52 | 52 |
Id: GenerateId(), |
53 |
- Parent: parent, |
|
54 | 53 |
Comment: comment, |
55 | 54 |
Created: time.Now(), |
56 | 55 |
} |
56 |
+ if container != nil { |
|
57 |
+ img.Parent = container.Image |
|
58 |
+ img.ParentContainer = container.Id |
|
59 |
+ img.ParentCommand = append([]string{container.Path}, container.Args...) |
|
60 |
+ } |
|
57 | 61 |
if err := graph.Register(layerData, img); err != nil { |
58 | 62 |
return nil, err |
59 | 63 |
} |
... | ... |
@@ -35,7 +35,7 @@ func TestGraphCreate(t *testing.T) { |
35 | 35 |
if err != nil { |
36 | 36 |
t.Fatal(err) |
37 | 37 |
} |
38 |
- image, err := graph.Create(archive, "", "Testing") |
|
38 |
+ image, err := graph.Create(archive, nil, "Testing") |
|
39 | 39 |
if err != nil { |
40 | 40 |
t.Fatal(err) |
41 | 41 |
} |
... | ... |
@@ -92,7 +92,7 @@ func TestMount(t *testing.T) { |
92 | 92 |
if err != nil { |
93 | 93 |
t.Fatal(err) |
94 | 94 |
} |
95 |
- image, err := graph.Create(archive, "", "Testing") |
|
95 |
+ image, err := graph.Create(archive, nil, "Testing") |
|
96 | 96 |
if err != nil { |
97 | 97 |
t.Fatal(err) |
98 | 98 |
} |
... | ... |
@@ -128,7 +128,7 @@ func TestDelete(t *testing.T) { |
128 | 128 |
t.Fatal(err) |
129 | 129 |
} |
130 | 130 |
assertNImages(graph, t, 0) |
131 |
- img, err := graph.Create(archive, "", "Bla bla") |
|
131 |
+ img, err := graph.Create(archive, nil, "Bla bla") |
|
132 | 132 |
if err != nil { |
133 | 133 |
t.Fatal(err) |
134 | 134 |
} |
... | ... |
@@ -139,11 +139,11 @@ func TestDelete(t *testing.T) { |
139 | 139 |
assertNImages(graph, t, 0) |
140 | 140 |
|
141 | 141 |
// Test 2 create (same name) / 1 delete |
142 |
- img1, err := graph.Create(archive, "foo", "Testing") |
|
142 |
+ img1, err := graph.Create(archive, nil, "Testing") |
|
143 | 143 |
if err != nil { |
144 | 144 |
t.Fatal(err) |
145 | 145 |
} |
146 |
- if _, err = graph.Create(archive, "foo", "Testing"); err != nil { |
|
146 |
+ if _, err = graph.Create(archive, nil, "Testing"); err != nil { |
|
147 | 147 |
t.Fatal(err) |
148 | 148 |
} |
149 | 149 |
assertNImages(graph, t, 2) |
... | ... |
@@ -15,11 +15,13 @@ import ( |
15 | 15 |
) |
16 | 16 |
|
17 | 17 |
type Image struct { |
18 |
- Id string `json:"id"` |
|
19 |
- Parent string `json:"parent,omitempty"` |
|
20 |
- Comment string `json:"comment,omitempty"` |
|
21 |
- Created time.Time `json:"created"` |
|
22 |
- graph *Graph |
|
18 |
+ Id string `json:"id"` |
|
19 |
+ Parent string `json:"parent,omitempty"` |
|
20 |
+ Comment string `json:"comment,omitempty"` |
|
21 |
+ Created time.Time `json:"created"` |
|
22 |
+ ParentContainer string `json:-` |
|
23 |
+ ParentCommand []string `json:-` |
|
24 |
+ graph *Graph |
|
23 | 25 |
} |
24 | 26 |
|
25 | 27 |
func LoadImage(root string) (*Image, error) { |
... | ... |
@@ -200,6 +200,33 @@ func (runtime *Runtime) Destroy(container *Container) error { |
200 | 200 |
return nil |
201 | 201 |
} |
202 | 202 |
|
203 |
+// Commit creates a new filesystem image from the current state of a container. |
|
204 |
+// The image can optionally be tagged into a repository |
|
205 |
+func (runtime *Runtime) Commit(id, repository, tag string) (*Image, error) { |
|
206 |
+ container := runtime.Get(id) |
|
207 |
+ if container == nil { |
|
208 |
+ return nil, fmt.Errorf("No such container: %s", id) |
|
209 |
+ } |
|
210 |
+ // FIXME: freeze the container before copying it to avoid data corruption? |
|
211 |
+ // FIXME: this shouldn't be in commands. |
|
212 |
+ rwTar, err := container.ExportRw() |
|
213 |
+ if err != nil { |
|
214 |
+ return nil, err |
|
215 |
+ } |
|
216 |
+ // Create a new image from the container's base layers + a new layer from container changes |
|
217 |
+ img, err := runtime.graph.Create(rwTar, container, "") |
|
218 |
+ if err != nil { |
|
219 |
+ return nil, err |
|
220 |
+ } |
|
221 |
+ // Register the image if needed |
|
222 |
+ if repository != "" { |
|
223 |
+ if err := runtime.repositories.Set(repository, tag, img.Id); err != nil { |
|
224 |
+ return img, err |
|
225 |
+ } |
|
226 |
+ } |
|
227 |
+ return img, nil |
|
228 |
+} |
|
229 |
+ |
|
203 | 230 |
func (runtime *Runtime) restore() error { |
204 | 231 |
dir, err := ioutil.ReadDir(runtime.repository) |
205 | 232 |
if err != nil { |
... | ... |
@@ -2,9 +2,11 @@ package docker |
2 | 2 |
|
3 | 3 |
import ( |
4 | 4 |
"encoding/json" |
5 |
+ "fmt" |
|
5 | 6 |
"io/ioutil" |
6 | 7 |
"os" |
7 | 8 |
"path/filepath" |
9 |
+ "strings" |
|
8 | 10 |
) |
9 | 11 |
|
10 | 12 |
type TagStore struct { |
... | ... |
@@ -60,6 +62,12 @@ func (store *TagStore) Reload() error { |
60 | 60 |
} |
61 | 61 |
|
62 | 62 |
func (store *TagStore) Set(repoName, tag, revision string) error { |
63 |
+ if strings.Contains(repoName, ":") { |
|
64 |
+ return fmt.Errorf("Illegal repository name: %s", repoName) |
|
65 |
+ } |
|
66 |
+ if strings.Contains(repoName, ":") { |
|
67 |
+ return fmt.Errorf("Illegal tag name: %s", tag) |
|
68 |
+ } |
|
63 | 69 |
if err := store.Reload(); err != nil { |
64 | 70 |
return err |
65 | 71 |
} |