-
Notifications
You must be signed in to change notification settings - Fork 0
feat: search, load and validates manifests #1
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
upils
commented
Nov 26, 2025
- Have you signed the CLA?
Signed-off-by: Paul Mars <paul.mars@canonical.com>
Signed-off-by: Paul Mars <paul.mars@canonical.com>
Signed-off-by: Paul Mars <paul.mars@canonical.com>
Signed-off-by: Paul Mars <paul.mars@canonical.com>
Signed-off-by: Paul Mars <paul.mars@canonical.com>
Signed-off-by: Paul Mars <paul.mars@canonical.com>
Signed-off-by: Paul Mars <paul.mars@canonical.com>
Signed-off-by: Paul Mars <paul.mars@canonical.com>
Signed-off-by: Paul Mars <paul.mars@canonical.com>
Signed-off-by: Paul Mars <paul.mars@canonical.com>
Signed-off-by: Paul Mars <paul.mars@canonical.com>
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.
Pull request overview
This PR implements functionality to search, load, and validate manifests from a rootfs directory. The main purpose is to enable chisel to detect and reuse existing rootfs installations by reading and validating their manifests.
Key changes:
- Added manifest verification logic to validate rootfs contents against manifest entries
- Implemented manifest loading and consistency checking across multiple manifest files
- Integrated rootfs detection into the cut command to merge existing slice keys with newly requested ones
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| internal/manifestutil/verify.go | New file implementing rootfs validation against manifest entries, including path grouping, hardlink verification, and file integrity checks |
| internal/manifestutil/manifestutil.go | Extended with functions to find manifests in releases, load and validate manifest files, check consistency between manifests, and extract reports from rootfs |
| cmd/chisel/cmd_cut.go | Modified to detect pre-existing rootfs and merge slice keys from existing manifests with requested slices |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Signed-off-by: Paul Mars <paul.mars@canonical.com>
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.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Signed-off-by: Paul Mars <paul.mars@canonical.com>
Signed-off-by: Paul Mars <paul.mars@canonical.com>
Signed-off-by: Paul Mars <paul.mars@canonical.com>
Signed-off-by: Paul Mars <paul.mars@canonical.com>
Signed-off-by: Paul Mars <paul.mars@canonical.com>
TheSignPainter98
left a comment
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.
Here are a few things to watch out for :)
Signed-off-by: Paul Mars <paul.mars@canonical.com>
Signed-off-by: Paul Mars <paul.mars@canonical.com>
Signed-off-by: Paul Mars <paul.mars@canonical.com>
Signed-off-by: Paul Mars <paul.mars@canonical.com>
Signed-off-by: Paul Mars <paul.mars@canonical.com>
TheSignPainter98
left a comment
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 looking much better now, thanks for the changes! Just a few minor things then I think this looks good
Signed-off-by: Paul Mars <paul.mars@canonical.com>
| if mfest != nil { | ||
| err = manifestutil.CheckDir(mfest, cmd.RootDir) | ||
| if err != nil { | ||
| return err |
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.
[Note to reviewer]: At this point a valid manifest was extracted from the root dir but the dir is invalid (or we failed to validate it) so we return the error.
| } | ||
| return nil, fmt.Errorf("cannot read manifest %q from the root directory: %v", manifestPath, err) | ||
| } | ||
| err = Validate(mfest) |
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.
[Note to reviewer]: The manifest validation was moved out of the load function to be called here instead.
cmd/chisel/cmd_cut.go
Outdated
|
|
||
| manifestPaths := manifestutil.FindPathsInRelease(release) | ||
| if len(manifestPaths) >= 0 { | ||
| mfest, mfestPath, err := manifestutil.FromDir(manifestPaths, cmd.RootDir) |
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.
[Note to reviewer]: I am not very happy about having to also return the manifest path so I am open to suggestions to avoid that.
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 am not very happy about having to also return the manifest path so I am open to suggestions to avoid that.
I have the same suggestion as in all the comments above :)
public/manifest/manifest.go
Outdated
| return nil | ||
| } | ||
|
|
||
| func (manifest *Manifest) Schema() string { |
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.
[Note to reviewer]: This is needed to compare manifests by schema versions but exposing it while the format of this value is not precisely defined might cause issues in the future. Should we hold on exposing and for now internally rely on the assumption that all valid manifests are necessarily using the 1.0 schema?
internal/slicer/slicer.go
Outdated
| } | ||
| defer r.Close() | ||
|
|
||
| mfest, err = manifest.Read(r) |
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.
[Note to reviewer]: This call can return 2 kinds of errors:
- Errors actually about reading from the reader, such as the OS unable to read a piece of the file from disk.
- Unmarshalling and invalid schema errors. So "business logic" errors.
Ideally I would like to differentiate these errors: return in the first case and continue in the second. But I cannot see a proper solution that would not require changing the API of manifest.Read to do that.
So my approach here is to continue, because in that case this file is not a good fit to get the manifest and the main goal is to get one valid manifest, not identify bad candidates.
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.
invalid schema errors
As I said on MM, I think we named a named error for this one in particular if we want to ignore them. Old versions of Chisel will not parse correctly new versions of the manifest, so it is useful to know if the error is related to schema version and skip. If it is not then fail.
letFunny
left a comment
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 this Paul, I wrote a lot of comments mainly minor, which is always a good sign :). This is looking much better already. I think we are at a stage where you need to write the tests so that we know how the feature looks like, how it is called and what is implemented.
| return nil | ||
| } | ||
|
|
||
| // CompareSchemas compare two manifest schema strings |
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.
How does it compare them? What is the return value? This looks a bit artificial at the moment, it is saying:
- If you give me two manifests with the same version and it is
manifest.Schemathen 0. - Else -1.
Which is a little bit weird for a comparator, right? Normally, either -1, 0 and 1 are returned based on a total order or true, false is returned if the order is partial.
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 is a little weird on purpose. My goal was to have a signature consistent with other comparison functions in the project and avoid changing it when actually supporting multiple schema versions. The end goal is to provide a total order, but since the schema format is not yet formally defined, I did not want to implement a complex comparison making assumptions (that could be wrong in the future). When we actually support multiple schema, the format will be properly defined and we should naturally change this implementation because it will look obviously wrong.
For now it indeed feels a bit artificial but that may be a good thing(?).
| allSlices = append(allSlices, slice) | ||
| } | ||
| } | ||
| manifestMap := FindPaths(allSlices) |
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 a bit unfortunate that we have to iterate over all slices to collect them just to iterate over them again. Because the real business logic takes a path + info and returns the manifest path by validating then appending a filename, what about extracting only that bit into an auxiliary function instead? Then have both functions call the auxiliary one. Thoughts?
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.
After implementing a naive version of FindPathsInRelease I thought the same. I did the extraction and ended up with something longer and harder to understand. So I opted for simplicity at this time but now I think I may have underestimated the problem when in the future the number of slices is more than 50x what we have today. I will rework that.
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.
Reworked in 4ecb555
internal/slicer/check.go
Outdated
| // the manifest. | ||
| // This function works under the assumption the manifest is valid. | ||
| // Files not managed by chisel are ignored. | ||
| func checkDir(mfest *manifest.Manifest, rootDir string) 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.
I think checkRootDir might be a more appropriate name as we expect the directory to be the root dir of the installation that the manifest describes.
I am still not happy with having it here, but let's postpone that discussion. It is not blocking and it is not major (for now if we don't introduce a chisel validate).
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 checkRootDir might be a more appropriate name as we expect the directory to be the root dir of the installation that the manifest describ
Good point, and this is in line with the rootDir argument. Fixed in 15550ad.
I am still not happy with having it here, but let's postpone that discussion. It is not blocking and it is not major (for now if we don't introduce a chisel validate).
I agree.
internal/slicer/check.go
Outdated
| inode := stat.Ino | ||
|
|
||
| if path.Inode == 0 { | ||
| // This path must not be linked to any other |
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.
Make sure all comments are punctuated.
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.
See 15550ad
| targetDir = filepath.Clean(targetDir) | ||
| if !filepath.IsAbs(targetDir) { | ||
| dir, err := os.Getwd() | ||
| if err != nil { | ||
| return nil, fmt.Errorf("cannot obtain current directory: %w", err) | ||
| } | ||
| targetDir = filepath.Join(dir, targetDir) | ||
| } |
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 are doing this every time for an internal function. If the function is internal then you can expect an absolute path and you can document it or check with IsAbs and return an error, either one of them work. If the function is public then you have to choice to make it absolute or fail. In that case I prefer the former to follow convention.
| defer r.Close() | ||
| mfest, err := manifest.Read(r) | ||
| if err != nil { | ||
| return 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.
See my other other comment. Here it is even more important. And manifest.Validate has to be an error.
| return nil | ||
| } | ||
|
|
||
| if selected == nil || manifestutil.CompareSchemas(mfest.Schema(), selected.Schema()) > 0 { |
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.
Compare is never > 0. In my opinion, in terms of logic the code should check all manifests of the same version have to be equal. You don't have to use the content has because you have both manifests in memory and you need to return selected anyways, so you can check whether they match or not (in a separate function please).
| if !ok { | ||
| schemaManifest[mfest.Schema()] = manifestHash{mfestPath, mfestHash} | ||
| } else if refMfest.hash != mfestHash { | ||
| return fmt.Errorf("inconsistent manifests: %q and %q", refMfest.path, mfestPath) |
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 will likely change, but this error message doesn't state that the contents of the manifests should be equal and they are not.
|
Closing in favor of canonical#264 |