-
Notifications
You must be signed in to change notification settings - Fork 575
Implement daemon.Write #114
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
v1/tarball/write.go
Outdated
defer tf.Close() | ||
|
||
if img == nil { | ||
return fmt.Errorf("wtf we have a serious problem") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ಠ_ಠ
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
😬
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I want a keyboard shortcut for this.
5f113c9
to
b20d438
Compare
v1/tarball/write.go
Outdated
|
||
go func() { | ||
// Close the writer with any errors encountered during | ||
// extraction. These errors will be returned by the reader end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment mentions extraction.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks
v1/tarball/write.go
Outdated
@@ -49,6 +59,33 @@ func Write(p string, tag name.Tag, img v1.Image, wo *WriteOptions) error { | |||
tf := tar.NewWriter(w) | |||
defer tf.Close() | |||
|
|||
tr := tar.NewReader(Write(tag, img, wo)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You should be able to just io.Copy(f, r)
I think right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought so at first, but the copy doesn't seem to read correctly. Not sure if this is because it's a tar reader or if it's coming from a pipe, but if you have suggestions I'm all ears
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're wrapping these in extra layers of tar read/writers (they are... tarnished? :P), something like this should work:
func WriteToFile(p string, tag name.Tag, img v1.Image, wo *WriteOptions) error {
f, err := os.Create(p)
if err != nil {
return err
}
defer f.Close()
r := Write(tag, img, wo)
defer r.Close()
_, err = io.Copy(f, r)
return err
}
But this feels like going around your elbow to get to your ear. See my comment on tarball.Write
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
-1 for the pun 😆
you're right about the extra read/write wrapping, but yeah this definitely wasn't the best construction. fixed up to make a little bit more sense
v1/daemon/write.go
Outdated
// WriteOptions are used to expose optional information to guide or | ||
// control the image write. | ||
type WriteOptions struct { | ||
// TODO(dlorenc): What kinds of knobs does the daemon expose? | ||
} | ||
|
||
// Write saves the image into the daemon as the given reference. | ||
func Write(ref name.Reference, img v1.Image, wo WriteOptions) error { | ||
return fmt.Errorf("NYI: daemon.Write(%v)", ref) | ||
func Write(ref name.Reference, img v1.Image, wo WriteOptions) (types.ImageLoadResponse, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This library's API shouldn't return some other library's types. Is there another way to express this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah yeah that's a great point. I still want to expose the client's response to the user, so I'll change this to return (string, error)
v1/daemon/write.go
Outdated
if err != nil { | ||
return "", err | ||
} | ||
resp.Body.Close() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: move this up as a defer before you do the ReadAll. Also check the err from ImageLoad separately.
v1/daemon/write.go
Outdated
return "", err | ||
} | ||
|
||
tag, err := name.NewTag(ref.Name(), name.WeakValidation) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this can only be a tag, just have daemon.Write only accept a name.Tag?
FWIW I've seen RepoDigests
somewhere before, so maybe this should be handling digests as well?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not sure why I did this....maybe I was just modeling after remote.Write
? now that you mention it this probably should handle digests, but then shouldn't tarball.Write
as well?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think something about the tarball format expects a tag (or used to?). Not sure.
v1/tarball/write.go
Outdated
@@ -49,6 +59,33 @@ func Write(p string, tag name.Tag, img v1.Image, wo *WriteOptions) error { | |||
tf := tar.NewWriter(w) | |||
defer tf.Close() | |||
|
|||
tr := tar.NewReader(Write(tag, img, wo)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're wrapping these in extra layers of tar read/writers (they are... tarnished? :P), something like this should work:
func WriteToFile(p string, tag name.Tag, img v1.Image, wo *WriteOptions) error {
f, err := os.Create(p)
if err != nil {
return err
}
defer f.Close()
r := Write(tag, img, wo)
defer r.Close()
_, err = io.Copy(f, r)
return err
}
But this feels like going around your elbow to get to your ear. See my comment on tarball.Write
.
v1/tarball/write.go
Outdated
// One manifest.json file at the top level containing information about several images. | ||
// One file for each layer, named after the layer's SHA. | ||
// One file for the config blob, named after its SHA. | ||
// Write writes the image at the given tag to a tarball, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: "at the given tag" feels like it might pull the image first... this wasn't super obvious to me from the method signature. It might be useful to call out that this is a local operation (i.e. just adds it to RepoTags).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah fair enough. I would have clarified but I removed this altogether :)
v1/daemon/write_test.go
Outdated
"github.com/google/go-containerregistry/v1/tarball" | ||
) | ||
|
||
func TestWriteImage(t *testing.T) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't override getImageLoader
, so it has a dependency on docker, which I don't have installed, so I can't run the tests :)
write_test.go:36: Error writing image tar: Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?
(you probably have some test_image_2:latest
s in your daemon 😂 )
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is testing loading an image into the local daemon, so if you don't have docker installed this will definitely fail :)
the test is just doing a tarball.ImageFromPath
on one of the tars checked into testdata and then retagging it as test_image_2:latest
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I won't fight this too hard, but it would be nice if I could bazel test ...
this without installing docker :) can you mock out the docker interaction for this test and add an integration test that exercises the actual daemon?
I'm not a huge fan of implicit dependencies, but if nobody else cares, it's nbd.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no that's a good point, if we were relying on docker to always be present then what would be the point of this library :) fixed to mock out so we don't need docker to test
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks!
v1/tarball/write.go
Outdated
// One file for the config blob, named after its SHA. | ||
// Write writes the image at the given tag to a tarball, | ||
// and returns it through a reader to be consumed | ||
func Write(tag name.Tag, img v1.Image, wo *WriteOptions) io.ReadCloser { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's weird to me that something called Write
returns a ReadCloser
. After spending a few minutes trying to come up with a name that made more sense, I think I realized the issue.
This would be a lot cleaner if tarball.Write
was instead:
func Write(w io.Writer, tag name.Tag, img v1.Image, wo *WriteOptions) error
(Hey look, that's tarball.write
;P)
Then tarball.WriteToFile
could just be sugar to create the file for you:
func WriteToFile(p string, tag name.Tag, img v1.Image, wo *WriteOptions) error {
f, err := os.Create(p)
if err != nil {
return err
}
defer f.Close()
return Write(f, tag, img, wo)
}
And then you can move the io.Pipe
from tarball.Write
into daemon.Write
, which would just pipe tarball.Write
into cli.ImageLoad
.
@imjasonh thoughts? I think this would be a bit more idiomatic/composable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes! I was bumping up on that too and just didn't think about it enough. I think we could probably port that same change to mutate.Extract
too (why is Extract
in mutate
?)
But yeah that's definitely a better API
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 to giving mutate.Extract
the same treatment.
Disclaimer: the rest of this comment is unrelated to this PR.
tl;dr I think we need fs.Write
and fs.Image
(or fs.Layer
)
(why is Extract in mutate?)
I'll bring up my comment again -- if we can change mutate.Extract
to:
func Flatten(v1.Image) (v1.Image, error)
... I think it makes sense to leave it in mutate
. Otherwise, I agree it doesn't really fit the style of mutations in there (maybe it belongs in v1util
or tarball
? or a new fs.Write
?). Unfortunately, the change I want makes things a bit complicated and potentially a lot slower...
I'm wondering if we can abuse this to get what I want without much code... e.g.:
func Flatten(i v1.Image) (v1.Image, error) {
// pseudo-go, will likely need lots of plumbing to avoid excessive buffering
return mutate.AppendLayers(empty.Image(), layer.Uncompressed(mutate.Extract(i)))
}
Then implementing {remote,tarball,daemon} | flatten | {remote,tarball,daemon}
is trivial :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the feedback here, this is definitely a better construction. Passing in a writer rather than returning a reader puts the onus on the caller to do the IO, which I think is a lot cleaner.
re Extract
being in mutate
: I originally had this in tarball
, but wasn't sure it made sense in there since it's not technically limited to tarball
s (though they do get converted before doing the actual extraction).
The actual motivation for this was twofold: I wanted to have a cleaner way of doing filesystem comparisons in container-diff (extracting the flattened fs is an obvious choice), but then I also wanted to implement Flatten
in its original state because it's cool :)
I tried to make it as extensible as possible, and I guess mutate
was the only neutral package that made sense. I'm fine moving it around though.
v1/daemon/write.go
Outdated
// write the image in docker save format first, then load it | ||
reader := tarball.Write(tag, img, &tarball.WriteOptions{}) | ||
resp, err := cli.ImageLoad(context.Background(), reader, false) | ||
b, err := ioutil.ReadAll(resp.Body) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It might be better to just make daemon.Write
return an io.ReadCloser
(i.e. resp.Body
) instead of a string? I'm not sure how this will be used, so it's hard to say.
this should be RFAL |
v1/daemon/write_test.go
Outdated
"github.com/google/go-containerregistry/v1/tarball" | ||
) | ||
|
||
func TestWriteImage(t *testing.T) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I won't fight this too hard, but it would be nice if I could bazel test ...
this without installing docker :) can you mock out the docker interaction for this test and add an integration test that exercises the actual daemon?
I'm not a huge fan of implicit dependencies, but if nobody else cares, it's nbd.
v1/daemon/write.go
Outdated
return "", err | ||
} | ||
|
||
tag, err := name.NewTag(ref.Name(), name.WeakValidation) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think something about the tarball format expects a tag (or used to?). Not sure.
v1/daemon/write.go
Outdated
defer resp.Body.Close() | ||
b, readErr := ioutil.ReadAll(resp.Body) | ||
response := string(b) | ||
if err != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you need to check this before defer resp.Body.Close()
, since resp might be nil.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah yeah good catch, resp can't be nil but resp.Body
can
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM other than some comment nits.
v1/tarball/write.go
Outdated
} | ||
|
||
// Write in the compressed format. | ||
// Return a reader for contents of a tarball, containing: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Update this comment.
v1/daemon/write.go
Outdated
var getImageLoader = func() (ImageLoader, error) { | ||
return client.NewEnvClient() | ||
} | ||
|
||
// WriteOptions are used to expose optional information to guide or | ||
// control the image write. | ||
type WriteOptions struct { | ||
// TODO(dlorenc): What kinds of knobs does the daemon expose? | ||
} | ||
|
||
// Write saves the image into the daemon as the given reference. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/reference/tag/
v1/daemon/write_test.go
Outdated
"github.com/google/go-containerregistry/v1/tarball" | ||
) | ||
|
||
func TestWriteImage(t *testing.T) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks!
This adds support for loading a
v1.Image
into a local docker daemon, by first writing it indocker save
format usingtarball.Write
, and then feeding that to the docker client.Previously
tarball.Write
only supported writing to disk, so to avoid this I changed the signature to return anio.ReadCloser
by default.tarball.WriteToFile
can be used in its place to write the contents of this reader to disk.