Skip to content

Commit baacae8

Browse files
author
Solomon Hykes
committed
'docker push' buffers filesystem archives on disk instead of memory.
1 parent 52cedb8 commit baacae8

4 files changed

Lines changed: 77 additions & 13 deletions

File tree

archive.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"errors"
55
"io"
66
"io/ioutil"
7+
"os"
78
"os/exec"
89
)
910

@@ -86,3 +87,38 @@ func CmdStream(cmd *exec.Cmd) (io.Reader, error) {
8687
}
8788
return pipeR, nil
8889
}
90+
91+
// NewTempArchive reads the content of src into a temporary file, and returns the contents
92+
// of that file as an archive. The archive can only be read once - as soon as reading completes,
93+
// the file will be deleted.
94+
func NewTempArchive(src Archive, dir string) (*TempArchive, error) {
95+
f, err := ioutil.TempFile(dir, "")
96+
if err != nil {
97+
return nil, err
98+
}
99+
if _, err := io.Copy(f, src); err != nil {
100+
return nil, err
101+
}
102+
if _, err := f.Seek(0, 0); err != nil {
103+
return nil, err
104+
}
105+
st, err := f.Stat()
106+
if err != nil {
107+
return nil, err
108+
}
109+
size := st.Size()
110+
return &TempArchive{f, size}, nil
111+
}
112+
113+
type TempArchive struct {
114+
*os.File
115+
Size int64 // Pre-computed from Stat().Size() as a convenience
116+
}
117+
118+
func (archive *TempArchive) Read(data []byte) (int, error) {
119+
n, err := archive.File.Read(data)
120+
if err != nil {
121+
os.Remove(archive.File.Name())
122+
}
123+
return n, err
124+
}

graph.go

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,12 +129,30 @@ func (graph *Graph) Register(layerData Archive, img *Image) error {
129129
return nil
130130
}
131131

132+
// TempLayerArchive creates a temporary archive of the given image's filesystem layer.
133+
// The archive is stored on disk and will be automatically deleted as soon as has been read.
134+
func (graph *Graph) TempLayerArchive(id string, compression Compression) (*TempArchive, error) {
135+
image, err := graph.Get(id)
136+
if err != nil {
137+
return nil, err
138+
}
139+
tmp, err := graph.tmp()
140+
if err != nil {
141+
return nil, err
142+
}
143+
archive, err := image.TarLayer(compression)
144+
if err != nil {
145+
return nil, err
146+
}
147+
return NewTempArchive(archive, tmp.Root)
148+
}
149+
132150
// Mktemp creates a temporary sub-directory inside the graph's filesystem.
133151
func (graph *Graph) Mktemp(id string) (string, error) {
134152
if id == "" {
135153
id = GenerateId()
136154
}
137-
tmp, err := NewGraph(path.Join(graph.Root, ":tmp:"))
155+
tmp, err := graph.tmp()
138156
if err != nil {
139157
return "", fmt.Errorf("Couldn't create temp: %s", err)
140158
}
@@ -144,6 +162,10 @@ func (graph *Graph) Mktemp(id string) (string, error) {
144162
return tmp.imageRoot(id), nil
145163
}
146164

165+
func (graph *Graph) tmp() (*Graph, error) {
166+
return NewGraph(path.Join(graph.Root, ":tmp:"))
167+
}
168+
147169
// Check if given error is "not empty".
148170
// Note: this is the way golang does it internally with os.IsNotExists.
149171
func isNotEmpty(err error) bool {

image.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,15 @@ func MountAUFS(ro []string, rw string, target string) error {
110110
return nil
111111
}
112112

113+
// TarLayer returns a tar archive of the image's filesystem layer.
114+
func (image *Image) TarLayer(compression Compression) (Archive, error) {
115+
layerPath, err := image.layer()
116+
if err != nil {
117+
return nil, err
118+
}
119+
return Tar(layerPath, compression)
120+
}
121+
113122
func (image *Image) Mount(root, rw string) error {
114123
if mounted, err := Mounted(root); err != nil {
115124
return err

registry.go

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"io"
88
"io/ioutil"
99
"net/http"
10+
"os"
1011
"path"
1112
"strings"
1213
)
@@ -269,24 +270,20 @@ func (graph *Graph) PushImage(stdout io.Writer, imgOrig *Image, authConfig *auth
269270
return fmt.Errorf("Failed to retrieve layer upload location: %s", err)
270271
}
271272

272-
// FIXME: Don't do this :D. Check the S3 requierement and implement chunks of 5MB
273-
// FIXME2: I won't stress it enough, DON'T DO THIS! very high priority
274-
layerData2, err := Tar(path.Join(graph.Root, img.Id, "layer"), Xz)
275-
tmp, err := ioutil.ReadAll(layerData2)
273+
// FIXME: stream the archive directly to the registry instead of buffering it on disk. This requires either:
274+
// a) Implementing S3's proprietary streaming logic, or
275+
// b) Stream directly to the registry instead of S3.
276+
// I prefer option b. because it doesn't lock us into a proprietary cloud service.
277+
tmpLayer, err := graph.TempLayerArchive(img.Id, Xz)
276278
if err != nil {
277279
return err
278280
}
279-
layerLength := len(tmp)
280-
281-
layerData, err := Tar(path.Join(graph.Root, img.Id, "layer"), Xz)
282-
if err != nil {
283-
return fmt.Errorf("Failed to generate layer archive: %s", err)
284-
}
285-
req3, err := http.NewRequest("PUT", url.String(), ProgressReader(layerData.(io.ReadCloser), layerLength, stdout))
281+
defer os.Remove(tmpLayer.Name())
282+
req3, err := http.NewRequest("PUT", url.String(), ProgressReader(tmpLayer, int(tmpLayer.Size), stdout))
286283
if err != nil {
287284
return err
288285
}
289-
req3.ContentLength = int64(layerLength)
286+
req3.ContentLength = int64(tmpLayer.Size)
290287

291288
req3.TransferEncoding = []string{"none"}
292289
res3, err := client.Do(req3)

0 commit comments

Comments
 (0)