Skip to content

Conversation

@umohnani8
Copy link
Member

Need this fix for kpod load so that stdout can write to a file when redirected. Checks if the file exists and is empty first as the file is created then written to.

Signed-off-by: umohnani8 umohnani@redhat.com

@rhatdan
Copy link
Member

rhatdan commented Jul 20, 2017

This patch fixes the issue where a user executes
kpod save > fedora.tar
In this situation the bash command creates an empty file, which the image layer should not reject.

} else {
fhStat, err := fh.Stat()
if err != nil {
return nil, err
Copy link
Member

Choose a reason for hiding this comment

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

Probably should wrap this err with some more useful message.
errors.Wrapf(err, "docker-archive file %v can not be stat'd", ref.path)

// tries to open a file first to accomodate for load when using stdout
// as the file is created first then written to
// if the file doesn't exist then it creates a new file for exclusive use
// if the file exists, it checks to ensure the size is 0
Copy link
Collaborator

Choose a reason for hiding this comment

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

“to accomodate for load” is difficult to understand for me as a non-native speaker.

(In general, don’t write comments describing what every line does; that should be obvious from the code, and such comments are over time more likely to become obsolete and misleading than to be helpful; instead, write comments saying what is the code trying to do, most importantly the situation/problem (“Note that this needs to account for $this_weird_case”), and, if non-obvious, the solution.)

Copy link
Member

Choose a reason for hiding this comment

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

Yes lets remove the comment about load, and just state that the file needs to be created new and open for write, if the file prexisted it needs to be null for now. @mtrmac @umohnani8 is going to work on the append to the tar ball soon, but we need to get this in now.

Copy link
Collaborator

Choose a reason for hiding this comment

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

append to the tar ball soon

You mean to update an archive by adding new files to the end of an existing tarball? That works with the “tar as tape processed at once” model (and seems to be documented at https://www.gnu.org/software/tar/manual/html_node/multiple.html ), but it's not obviously interoperable (in particular, the reader in this package stops at the first match). Of course that particular reader can be updated, but in general?

Ultimately, what is the use case here? Why would anyone want to keep updating a set of images in a giant single file with O(N) seeking and no way to delete a layer in the middle, as opposed to using either a registry or the OCI expanded dirs+files format?

Copy link
Member

Choose a reason for hiding this comment

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

We are trying to match the docker save command, their use case is to put multiple versions of the same image/tags into a tar ball at the same time.

fedora:rawhide
fedora:latest
fedora:f26
fedora:f25

If we decide this is a bad idea, we can drop support for it. Currently kpod load is able to read the images created with multiple tags, not sure if it works correctly or not.

docker save --help

Usage:	docker save [OPTIONS] IMAGE [IMAGE...]

Save one or more images to a tar archive (streamed to STDOUT by default)

Options:
      --help            Print usage
  -o, --output string   Write to a file, instead of STDOUT

Copy link
Member

Choose a reason for hiding this comment

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

It is curious that the documentation for docker load seems to indicate one image.

 docker load --help

Usage:	docker load [OPTIONS]

Load an image from a tar archive or STDIN

Options:
      --help           Print usage
  -i, --input string   Read from tar archive file, instead of STDIN
  -q, --quiet          Suppress the load output

Copy link
Member Author

Choose a reason for hiding this comment

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

Right, I meant it works for multiple tags of the same image. So if we save
alpine:2.6
alpine:2.7
into the same docker archive and load that using kpod, it will load both alpine:2.6 and alpine:2.7

The error you are getting above is because the loadTarManifest was throwing an error if it received more than one object. I changed that in containers/image to not throw an error if there is more than one so that kpod load can load all the tags in one archive.
This was the pull request for that change that got accepted and merged #309

Copy link
Collaborator

Choose a reason for hiding this comment

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

(To be precise, what matters is that the image (config+layers) is the same. The names may differ, both in the tag and in the repo (busybox:latest and example.com/personalproject/myspecialcopy:today), as long as they point to the same contents.)

#309 only allows listing the various tags; but actually copying the images out still fails with the quoted error (see also the warning above; the code does not allow choosing which image to read). (Just clarifying the current implementation state. The “load” path is fixable easily enough—we insist on seekable input, so it’s quite possible to choose one of the images; it’s the “save” part that is problematic.)


I guess short-term the easiest way to get the “save multiple” case working would be to create a temporary directory, copy all of the images in there one at a time using the oci: transport, and then tar it up. That bypasses the need to edit tar archives. Downsides would be:

  • Extra code happening outside of containers/image, arguably working around its inflexible model
  • OCI does not currently support multiple images either ( oci transport will remove existing images #306 ), though that should be trivially fixable

Copy link
Collaborator

Choose a reason for hiding this comment

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

I guess short-term the easiest way to get the “save multiple” case working

… for the OCI format, not for the docker-compatible tar one; but, if anything, we actually need the docker-compatible more, to preserve compatibility with existing workflows.

Copy link
Member Author

Choose a reason for hiding this comment

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

Okay, so after I changed loadTarManifest (#309) I was able to get the list of tags and it pulled all the tags in. It is possible that it pulled the same image multiple times and named it differently as the tags are different - basically loading multiple images does not fail for me.
Right, getting the docker one working is more important to match their functionality.

Copy link
Member

Choose a reason for hiding this comment

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

I say if an image has multiple tags we should saver the tags, but not do anything with multiple images.

return nil, err
}

if fhStat.Size() > 0 {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Awkwardly, AFAICT it is not actually documented anywhere that struct stat.st_size has any particular value for pipes and FIFOs. I guess this could explicitly check for (fhStat.Mode()&ModeType) == ModeNamedPipe || (fhStat.Mode().IsRegular() && fhStat.Size() == 0) or something like that.

Copy link
Member

Choose a reason for hiding this comment

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

I agree.

// it and then do a rename(2).
if os.IsExist(err) {
err = errors.New("docker-archive doesn't support modifying existing images")
fh, err = os.OpenFile(ref.path, os.O_WRONLY|os.O_EXCL|os.O_CREATE, 0644)
Copy link
Collaborator

Choose a reason for hiding this comment

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

I don’t think using O_EXCL with the same path again is all that valuable; either err was an unrelated failure and this should fail again in the same way, or we already know that a file did not exist, so if it exists now and O_EXCL causes us to fail, we are racing against something else modifying the filesystem and failing is not necessarily the right thing to do.

Couldn’t this be simplified to a single os.OpenFile(…O_WRONLY | O_CREATE) call followed by the “size of the file (whether pre-existing or newly created) is 0” check? (That’s also racy against concurrent writes to the same filename, though differently, but at least it is trivially simple.)

Copy link
Member

Choose a reason for hiding this comment

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

SGTM

Copy link
Member Author

Choose a reason for hiding this comment

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

Yup, sounds like a better idea. Will do that.

@umohnani8 umohnani8 force-pushed the master branch 2 times, most recently from f662f7e to 230921c Compare July 21, 2017 15:55
err = errors.New("docker-archive doesn't support modifying existing images")
}
return nil, err
return nil, errors.Wrapf(err, "error retrieving stat of file")
Copy link
Member

Choose a reason for hiding this comment

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

What file?

 +		return nil, errors.Wrapf(err, "error statting file %q", ref.path) 

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed.

@rhatdan
Copy link
Member

rhatdan commented Jul 24, 2017

@mtrmac PTAL, I think we are in agreement, and this is needed to complete kpod save.

fh, err := os.OpenFile(ref.path, os.O_WRONLY|os.O_EXCL|os.O_CREATE, 0644)

// the file needs to be created for write only
// if the file prexisted it needs to be empty
Copy link
Collaborator

Choose a reason for hiding this comment

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

This comment still says “what” but not “why”. Perhaps something like

ref.path may be either a regular file or a pipe. In the pipe case we must be able to open a pre-existing file.

OTOH for regular files we don’t want to overwrite a pre-existing file, if there is any,
so we check for Size() == 0 below. (This is racy, but using O_EXCL would also be racy,
only in a different way. Either way, it’s up to the user not to have two writers to the same path.)

Copy link
Member Author

Choose a reason for hiding this comment

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

Changed the comment to this.

return nil, errors.Wrapf(err, "error statting file %q", ref.path)
}

if fhStat.Mode()&os.ModeNamedPipe != 0 || (fhStat.Mode().IsRegular() && fhStat.Size() == 0) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please use (fhStat.Mode() & os.ModeType) == os.ModeNamedPipe. The code above is correct (unlike UNIX, os.FileMode uses individual bits for the various non-regular-file file types), but unexpectedly tricky because the negation of the condition is not … == 0 (that would also include regular files).

(Also, as a matter of personal? style, if (the error case) { return error } /* normal processing */ would be more idiomatic.)

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed and negated the if to return the error instead.

@umohnani8 umohnani8 changed the title Add fixes to newImageDestination to allow kpod load to write to a file when stdout is redirected Add fixes to newImageDestination to allow kpod save to write to a file when stdout is redirected Jul 25, 2017
@umohnani8
Copy link
Member Author

@rhatdan PTAL

fh, err := os.OpenFile(ref.path, os.O_WRONLY|os.O_EXCL|os.O_CREATE, 0644)

// ref.path can be either a pipe or a regular file
// in the case of a pipe, we must be able to open a pre-existing file
Copy link
Member

@rhatdan rhatdan Jul 25, 2017

Choose a reason for hiding this comment

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

// in the case of a pipe, we must be able to open a pre-existing file
This line is wrong.
//in the case of a pipe, we require that we can open it for write

Copy link
Member Author

Choose a reason for hiding this comment

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

changed.

@rhatdan
Copy link
Member

rhatdan commented Jul 25, 2017

LGTM
@mtrmac PTAL

return nil, errors.Wrapf(err, "error statting file %q", ref.path)
}

if fhStat.Mode()&os.ModeType != os.ModeNamedPipe && (fhStat.Mode().IsRegular() && fhStat.Size() != 0) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is not actually a mathematical negation of the original, and fhStat.Mode().IsRegular() always implies fhStat.Mode()&os.ModeType != os.ModeNamedPipe.

We could either just ignore other file types, and say

if fhStat.Mode().IsRegular() && fhStat.Size() != 0 {
   /* fail */
}

, or something like

if fhStat.Mode().IsRegular() && fhStat.Size() != 0 {
  /* fail as above */
} else if fhStat.Mode()&os.ModeType != os.ModeNamedPipe {
  return nil, fmt.Errorf("Unsupported file type with mode %x", fhstat.Mode()")
}

Looking at the history of this PR, all we really wanted to check for is the non-zero size, so the shorter variant should be enough.

I guess I can live with the code as is, so 👍 in the interest of speeding things up. But I’d prefer this to be cleaned up—and we are waiting for @runcom anyway.

Copy link
Member Author

Choose a reason for hiding this comment

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

Okay, changed it to the if - else if method.

Copy link
Collaborator

Choose a reason for hiding this comment

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

This fails tests.

Copy link
Member Author

Choose a reason for hiding this comment

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

The else if was causing it to fail. I just took it out so it is like the shorter version. Hope that is fine.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Sure. Thanks!

@mtrmac
Copy link
Collaborator

mtrmac commented Jul 26, 2017

👍

Approved with PullApprove

…directed

Signed-off-by: umohnani8 <umohnani@redhat.com>
@rhatdan
Copy link
Member

rhatdan commented Jul 26, 2017

@runcom PTAL

@runcom
Copy link
Member

runcom commented Jul 27, 2017

LGTM

Approved with PullApprove

@runcom runcom merged commit 1066078 into containers:master Jul 27, 2017
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