Skip to content

Add method to flatten and extract image fs#99

Merged
dlorenc merged 6 commits into
google:masterfrom
nkubala:flatten
Apr 24, 2018
Merged

Add method to flatten and extract image fs#99
dlorenc merged 6 commits into
google:masterfrom
nkubala:flatten

Conversation

@nkubala
Copy link
Copy Markdown
Contributor

@nkubala nkubala commented Apr 23, 2018

This will let us pass in any image tar and extract its flattened filesystem. We'll use this in https://github.com/GoogleContainerTools/container-diff to do image filesystem comparisons among other things. This can also be used later to implement flattening of arbitrary images.

Comment thread v1/tarball/image.go Outdated
return fmt.Errorf("Error creating tar file: %v", err)
}
defer tarFile.Close()
fileWriter := bufio.NewWriter(tarFile)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we need the bufio writer?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we don't :)

Comment thread v1/tarball/image.go Outdated
if err != nil {
return fmt.Errorf("Error retriving image layers: %v", err)
}
for i := len(layers) - 1; i >= 0; i-- {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a comment on why we go in reverse?

An overall link to the python implementation might help people understand.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Comment thread v1/tarball/image.go Outdated
return nil
}

func inWhiteoutDir(fileMap map[string]bool, file string) bool {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

some unit tests for this helper would be useful.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added a few

Comment thread v1/tarball/image.go Outdated
// any entries with a matching (or child) name
fileMap[name] = tombstone || !(header.Typeflag == tar.TypeDir)
if !tombstone {
buf := make([]byte, header.Size)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can use io.Copy from one tar into the other here, after writing the header.

You only have to do it if size != 0.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah yeah, for some reason I thought I couldn't copy straight from the tar reader. fixed

Comment thread v1/tarball/image.go Outdated

layers, err := img.Layers()
if err != nil {
return fmt.Errorf("Error retriving image layers: %v", err)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: retrieving

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks

Comment thread v1/tarball/image_test.go Outdated
if err != nil {
t.Errorf("Error when opening tar file for reading: %v", err)
}
r := bufio.NewReader(f)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't need the bufio.Reader here either.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Comment thread v1/tarball/image.go Outdated
tarWriter := tar.NewWriter(tarFile)
defer tarWriter.Close()

fileMap := make(map[string]bool, 0)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I find the map literal form a bit more straightforward: fileMap := map[string]bool{}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fair enough

Comment thread v1/tarball/image.go Outdated
"github.com/google/go-containerregistry/v1/v1util"
)

const whiteoutPrefix string = ".wh."
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can drop the "string" here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Comment thread v1/tarball/image.go Outdated
if !tombstone {
tarWriter.WriteHeader(header)
if header.Size > 0 {
io.Copy(tarWriter, tarReader)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: check the error

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Comment thread v1/tarball/image_test.go Outdated
)

func TestFlatten(t *testing.T) {
img, err := ImageFromPath("testdata/whiteout.tar", nil)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be possible to construct this with bazel and reference it as a dependency of the test, instead of checking it in? It would make the contents/functionality of this test more transparent.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep, updated to use container_layer to manually create a whiteout layer and test that that file isn't in the final tarball

Comment thread v1/tarball/image_test.go Outdated
name := header.Name
// this image was built by creating a directory called "foo",
// touching "/foo/bar", and then removing the whole directory.
for _, part := range filepath.SplitList(name) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should also add a file to the tar that exercises a file being overwritten in a subsequent layer.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah yeah good idea.

@jonjohnsonjr
Copy link
Copy Markdown
Collaborator

jonjohnsonjr commented Apr 23, 2018

Is it possible to make this just a generic v1.Image -> (v1.Image, error) function so we can use it with any source or destination?

For example, to flatten a remote image:

src, err := remote.Image(tag, auth, http.DefaultTransport)
if err != nil {
  return err
}

flattened, err := mutate.Flatten(src)
if err != nil {
  return err
}

if err := remote.Write(tag, flattened, http.DefaultTransport, remote.WriteOptions{}); err != nil {
  return err
}

Or to flatten a local tarball:

src, err := tarball.ImageFromPath(path, nil)
if err != nil {
  log.Fatalln(err)
}

flattened, err := mutate.Flatten(src)
if err != nil {
  return err
}

if err := tarball.Write(path, "", flattened, &tarball.WriteOptions{}); err != nil {
  return err
}

This currently just writes the output to a tarball on disk, which seems like it would be hard to use as a library.

Or even if it just took a tar.Writer instead of tarFilePath, I think it would be easier to use.

@nkubala
Copy link
Copy Markdown
Contributor Author

nkubala commented Apr 24, 2018

@jonjohnsonjr I think I was a little off with the naming/description of this method. this is an adaptation of the extract() function in the old containerregistry that essentially pulls out the final fs of an image as a tarball. so the idea was never actually to end up with an image here, just the fs tarball.

that being said, there's not really any reason why this can't be a little more generic so it can be used modularly. @dlorenc had suggested returning either a []byte or an io.Reader that can then be used by other functions to create the desired resulting object.

extracting the final filesystem from a remote image:

src, err := remote.Image(tag, auth, http.DefaultTransport)
if err != nil {
  return err
}

fsReader, err := mutate.Flatten(src) // reader containing the extracted fs contents
if err != nil {
  return err
}

tarWriter := tar.NewWriter(some_file_path)
if _, err := io.Copy(tarWriter, tarReader); err != nil {
  return err
}

we should be able to use this to flatten any arbitrary image (and actually return a v1.Image, not a tarball) as well. WDYT?

@jonjohnsonjr
Copy link
Copy Markdown
Collaborator

SGTM!

@nkubala nkubala changed the title Add method to flatten image fs directly into tarball Add method to flatten and extract image fs Apr 24, 2018
Copy link
Copy Markdown
Collaborator

@jonjohnsonjr jonjohnsonjr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just one nit

Comment thread v1/mutate/mutate.go Outdated
return tar.NewReader(&b), nil
}

func InWhiteoutDir(fileMap map[string]bool, file string) bool {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this need to be public?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

whoops, nope thanks

@dlorenc dlorenc merged commit 4f6b76b into google:master Apr 24, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants