feat: upgrade existing filesystem #264
Conversation
Signed-off-by: Paul Mars <paul.mars@canonical.com>
|
letFunny
left a comment
There was a problem hiding this comment.
First review. I am missing a couple of files but I wanted to publish it as quickly as possible so we have more time to iterate. This is looking great Paul, many things are looking dead simple which is always an extremely good sign. I have written some comments about how to make the PR shorter and how to make it, hopefully, even simpler. Let me know what you think.
I see some of the comments are similar to past discussions we had in upils#1. We spent some effort there, so please make sure all the comments were carried over to this one.
letFunny
left a comment
There was a problem hiding this comment.
Thanks Paul, I like the new comments. And thank you for postponing the previous change, this PR is long enough as it is.
niemeyer
left a comment
There was a problem hiding this comment.
Here is a first pass.
For future PRs, let's please try to break it down into smaller parts so it's lighter on you and on the reviewer to get it in quickly.
| if err != nil { | ||
| return err | ||
| } | ||
| if mfest != nil { |
There was a problem hiding this comment.
This must never ever happen if err != nil. This is an important invariant to keep in mind in every Go project we work on. If we skip this rule, we're straight into billion Euro mistake territory. If you asked to select a valid manifest, and it did not return an error, it must have selected a valid manifest!
Let's please fix the function implementation and drop this verification from every call site.
cc @letFunny
There was a problem hiding this comment.
I completely agree. I brought the same point in the past out of muscle memory but I couldn't find any document to justify it properly. Maybe we should include it in the go style document that Ed wrote here.
There was a problem hiding this comment.
I reworked it and added a custom error type. In cmd_cut.go the error message is not used but I wonder if it should be logged or not. If we are not planning on doing anything with these messages the implementation could be simplified by removing the message field or even by replacing the custom type by a named error.
There was a problem hiding this comment.
As a rule of thumb, if we don't have a use case for the error type we don't introduce the error type, but I'm not sure I understand what you're asking, as it sounds like you're both saying "I added a custom error type" and "I don't need to add an error type".
| for path, info := range slice.Contents { | ||
| if info.Generate == setup.GenerateManifest { | ||
| dir := strings.TrimSuffix(path, "**") | ||
| path = filepath.Join(dir, DefaultFilename) |
There was a problem hiding this comment.
This is too loose. Imagine what happens if path is "/hmm/oops**", for instance, or just "/oops".
We need to be strict about how a manifest path looks like. Hopefully this is already the case elsewhere so there's already a pattern to follow. If not, please let me know.
There was a problem hiding this comment.
Following our discussion offline, see 85ea2b6. The validation function is heavily inspired from validateGeneratePath in internal/setup/yaml.go.
There was a problem hiding this comment.
Can you please outline what was the outcome here? Many comments here saying "See XYZ.", but it's not practical to go to several different links trying to find out how you handled the issue that was outlined here.
There was a problem hiding this comment.
I added a validateManifestPath function verifying that the received path matches our definition of a valid manifest path before doing any manipulation on it. This function is now used in FindPaths and FindPathsInRelease. I also added a couple of tests to make sure the issue you pointed out was handled properly.
| defer func() { | ||
| os.RemoveAll(tmpWorkDir) | ||
| }() | ||
| } |
There was a problem hiding this comment.
Can we please try harder to make this logic look nice?
As a hint, it often pays off to resist the temptation of manipulating important core functions, or long functions, and sprinkling it with previously missing needs of new functionality. Instead, it's worth considering what it is that you're adding new, and then consider which specific abstractions are needed for it.
There was a problem hiding this comment.
I did this change trying to follow the principle you mention. I isolated the "core" feature of Run in the dedicated cut function specifically to not change its behavior, except to return the report, then added the additional step of applying the recut. It also felt consistent with how it is done in Extract in extract.go. Looking at the diff again, it is a bit misleading and gives the impression of a bigger change than what it really is.
Am I missing your point? Is your comment about another aspect of the function? Would you mind giving more details on why or what seems off here?
There was a problem hiding this comment.
Was anything done here or is it still the same logic? My request was for you both to discuss to see if there was an opportunity to make this look nicer before I jump in and help out with specific details if possible. The comment above seems to be justifying what exists instead of looking for improvements. Was there an actual conversation about how to make this nice?
There was a problem hiding this comment.
The current implementation is the result of discussions and several iterations to separate concerns and make it consistent with the rest of the code. We discussed it with Alberto and I did not change anything since your first comment because I cannot see is not nice from your point of view.
| defer f.Close() | ||
|
|
||
| h := sha256.New() | ||
| if _, err := io.Copy(h, f); err != nil { |
There was a problem hiding this comment.
There are more convenient APIs inside that package.
There was a problem hiding this comment.
For the sole purpose of getting the hash of the manifest this could be simplified using os.ReadFile and sha256.Sum256. However, in the next step I plan on using this helper to get hashes of the existing content in the rootfs when verifying the rootfs against the manifest. In that situation, this function will handle arbitrarily big files that may not fit in memory. So I thought it safer to use io.Copy.
Were you suggestion using readerProxy from fsutil/create.go? If so, I fail to see how more convenient that would be to use it.
| mfestSchema := db.Schema() | ||
| if mfestSchema != Schema { | ||
| return nil, fmt.Errorf("unknown schema version %q", mfestSchema) | ||
| return nil, fmt.Errorf("%w %q", ErrUnknownSchema, mfestSchema) |
There was a problem hiding this comment.
As Alberto says, we never promise to keep error messages intact. With that said, we can do better here because I still want to know what the unsupported schema was.
Let's please have an actual error type called UnknownSchemaError that contains the wrong schema version as a public Version field, and reports the message "unknown manifest schema version %q".
This reverts commit 5cda705.
| // process assumes the targetDir was verified with prevManifest, and so | ||
| // prevManifest is an accurate representation of files and directories | ||
| // previously installed by Chisel in the targetDir. | ||
| func applyRecut(targetDir string, tempDir string, report *manifestutil.Report, prevManifest *manifest.Manifest) error { |
There was a problem hiding this comment.
[Note to reviewer]: I have reworked the whole approach in a way that is much more in line with the agreement, and should be much safer for user content. This is a 3-steps process, relying on the fact that the rootfs will be verified with the previous manifest.
This approach is being very careful of user content by:
- only removing what was known to Chisel (using the previous manifest)
- checking all new paths do not collide with user content.
The rootfs verification will be added in a follow up PR, as an independent operation executed before cutting the new rootfs.
Finally this approach might also set us in a better position to recover from crashes as a future improvement. As we rely on the previous manifest and the new one, both of these could be stored in the .chisel dir before doing any destructive operation. They could then be used to build a clear representation of what happened before the crash.
niemeyer
left a comment
There was a problem hiding this comment.
Paul, this PR is relatively large, is sitting here for a while, and the replies to comments made are quite unclear in terms of where we are reaching agreements, or disagreements, or when things are getting changed, or need discussinon.
By now I'm seriously tempted to ask for the PR to be broken down a little bit further into easier to agree pieces that we can get merged more easily, so we can make more steady progress.
|
This PR is being split in smaller ones to ease review. First step #288. |
This commit enables Chisel upgrading an existing rootfs.
It detects the target directory contains the result of a previous execution
to then operate an upgrade of the content. This initial simple implementation
has the following limitationg:
As a consequences:
different and thus the new OCI layer contains duplicated files.