-
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
Changes from 15 commits
60320ba
c67c661
22ae256
6cb437f
00825de
442bcc6
e19f6f1
b6ecc27
1406b18
8aba035
70b2c5e
c8b7a39
1181871
31f1d3d
df49dfc
f4169b9
aade454
e0bd4a4
2e572fb
3c5de1c
7eff973
b7c7c66
c7196e9
7539db7
cd22c1f
27d8645
373f3c1
1414990
2ce5b21
17162f4
c21ef34
77f5771
40ef8e8
0a25844
71c4496
4696dcc
089109d
09faa6a
bd7a707
3ea70ce
3efb7d2
a421818
e88de2d
acac421
0262cb2
7445fcd
5fca0c5
fb6500b
d7fa155
43e8a71
46d2119
164c68b
15550ad
4ecb555
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,9 +1,12 @@ | ||
| package manifestutil | ||
|
|
||
| import ( | ||
| "crypto/sha256" | ||
| "fmt" | ||
| "io" | ||
| "io/fs" | ||
| "os" | ||
| "path" | ||
| "path/filepath" | ||
| "slices" | ||
| "sort" | ||
|
|
@@ -14,6 +17,7 @@ import ( | |
| "github.com/canonical/chisel/internal/setup" | ||
| "github.com/canonical/chisel/public/jsonwall" | ||
| "github.com/canonical/chisel/public/manifest" | ||
| "github.com/klauspost/compress/zstd" | ||
| ) | ||
|
|
||
| const DefaultFilename = "manifest.wall" | ||
|
|
@@ -34,6 +38,26 @@ func FindPaths(slices []*setup.Slice) map[string][]*setup.Slice { | |
| return manifestSlices | ||
| } | ||
|
|
||
| // FindPathsInRelease finds all the paths marked with "generate:manifest" | ||
| // for the given release. | ||
| func FindPathsInRelease(r *setup.Release) []string { | ||
| allSlices := []*setup.Slice{} | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not use the
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you elaborate? Do you mean use |
||
| for _, pkg := range r.Packages { | ||
| for _, slice := range pkg.Slices { | ||
| allSlices = append(allSlices, slice) | ||
| } | ||
| } | ||
|
|
||
| manifestMap := FindPaths(allSlices) | ||
|
upils marked this conversation as resolved.
Outdated
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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?
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. After implementing a naive version of
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Reworked in 4ecb555 |
||
|
|
||
| paths := make([]string, 0, len(manifestMap)) | ||
|
upils marked this conversation as resolved.
Outdated
|
||
| for path := range manifestMap { | ||
| paths = append(paths, path) | ||
| } | ||
|
upils marked this conversation as resolved.
Outdated
|
||
|
|
||
|
upils marked this conversation as resolved.
Outdated
|
||
| return paths | ||
| } | ||
|
|
||
| type WriteOptions struct { | ||
| PackageInfo []*archive.PackageInfo | ||
| Selection []*setup.Slice | ||
|
|
@@ -340,3 +364,126 @@ func Validate(mfest *manifest.Manifest) (err error) { | |
| } | ||
| return nil | ||
| } | ||
|
|
||
| // FromDir extracts, validates and returns the manifest from a targetDir | ||
|
upils marked this conversation as resolved.
Outdated
|
||
| func FromDir(release *setup.Release, targetDir string) (*manifest.Manifest, error) { | ||
| manifestPaths := FindPathsInRelease(release) | ||
| if len(manifestPaths) == 0 { | ||
| // No manifest in the release means it cannot produce a rootfs that can | ||
| // be recut. Treat this case as cutting a new rootfs. | ||
| return nil, fmt.Errorf("no manifest generated for this release") | ||
|
upils marked this conversation as resolved.
Outdated
|
||
| } | ||
|
|
||
| // Select the first manifest of the list as the reference one for now. | ||
| // Another heuristic could be used (ex. select the one from base-files_chisel). | ||
| referenceRelPath := manifestPaths[0] | ||
| referencePath := path.Join(targetDir, referenceRelPath) | ||
|
upils marked this conversation as resolved.
Outdated
|
||
| reference, err := load(referencePath) | ||
|
upils marked this conversation as resolved.
Outdated
|
||
| if err != nil { | ||
| return nil, fmt.Errorf("cannot read manifest %q from the root directory: %v", referenceRelPath, err) | ||
|
upils marked this conversation as resolved.
Outdated
|
||
| } | ||
|
|
||
| err = checkConsistency(referenceRelPath, targetDir, manifestPaths[1:]) | ||
|
upils marked this conversation as resolved.
Outdated
|
||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| return reference, nil | ||
| } | ||
|
|
||
| // load reads, validates and returns a manifest. | ||
| func load(manifestPath string) (*manifest.Manifest, error) { | ||
|
upils marked this conversation as resolved.
Outdated
|
||
| f, err := os.Open(manifestPath) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| defer f.Close() | ||
|
|
||
| r, err := zstd.NewReader(f) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| defer r.Close() | ||
|
|
||
| mfest, err := manifest.Read(r) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| err = Validate(mfest) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| return mfest, nil | ||
| } | ||
|
|
||
| // checkConsistency checks consistency between a list of manifests and a | ||
| // reference one. | ||
| func checkConsistency(referenceRelPath string, targetDir string, manifests []string) error { | ||
|
upils marked this conversation as resolved.
Outdated
|
||
| reference := path.Join(targetDir, referenceRelPath) | ||
| hashReference, err := hash(reference) | ||
| if err != nil { | ||
| return fmt.Errorf("internal error: cannot compute hash for %q: %w", referenceRelPath, err) | ||
| } | ||
|
|
||
| infoRef, err := os.Stat(reference) | ||
|
upils marked this conversation as resolved.
Outdated
|
||
| if err != nil { | ||
| return fmt.Errorf("internal error: cannot get file info for %q: %w", referenceRelPath, err) | ||
| } | ||
|
|
||
| modeRef := infoRef.Mode() | ||
|
|
||
| for _, manifestRelPath := range manifests { | ||
| manifestPath := path.Join(targetDir, manifestRelPath) | ||
| infoManifest, err := os.Stat(manifestPath) | ||
| if err != nil { | ||
| return fmt.Errorf("internal error: cannot get file info for %q: %w", manifestRelPath, err) | ||
| } | ||
|
|
||
| modeManifest := infoManifest.Mode() | ||
|
upils marked this conversation as resolved.
Outdated
|
||
| if modeManifest != modeRef { | ||
| return fmt.Errorf("invalid manifest: permissions on %s (%s) are different from the reference manifest %s (%s)", manifestRelPath, modeManifest, referenceRelPath, modeRef) | ||
|
upils marked this conversation as resolved.
Outdated
|
||
| } | ||
|
|
||
| hashM, err := hash(manifestPath) | ||
|
upils marked this conversation as resolved.
Outdated
|
||
| if err != nil { | ||
| return fmt.Errorf("internal error: cannot compute hash for %q: %w", manifestRelPath, err) | ||
| } | ||
| if !slices.Equal(hashM, hashReference) { | ||
| return fmt.Errorf("invalid manifest: %s is inconsistent with %s", manifestRelPath, referenceRelPath) | ||
| } | ||
| } | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| func hash(path string) ([]byte, error) { | ||
|
upils marked this conversation as resolved.
Outdated
|
||
| f, err := os.Open(path) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| defer f.Close() | ||
|
|
||
| h := sha256.New() | ||
| if _, err := io.Copy(h, f); err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| return h.Sum(nil), nil | ||
| } | ||
|
|
||
| // SliceKeys returns setup.SliceKeys from a manifest. | ||
| func SliceKeys(mfest *manifest.Manifest) []setup.SliceKey { | ||
|
upils marked this conversation as resolved.
Outdated
|
||
| sliceKeys := []setup.SliceKey{} | ||
| mfest.IterateSlices("", func(slice *manifest.Slice) error { | ||
| sk, err := apacheutil.ParseSliceKey(slice.Name) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| sliceKeys = append(sliceKeys, sk) | ||
| return nil | ||
| }) | ||
|
|
||
| return sliceKeys | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.