Skip to content
Closed
Show file tree
Hide file tree
Changes from 52 commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
60320ba
feat: search, load and validates manifests
upils Nov 26, 2025
c67c661
fix: revert unintentional change
upils Nov 26, 2025
22ae256
refactor: move things around
upils Nov 26, 2025
6cb437f
refactor: move logic to manifestutil
upils Nov 26, 2025
00825de
feat: validate rootfs with manifest
upils Nov 27, 2025
442bcc6
fix: debugging
upils Nov 27, 2025
e19f6f1
fix: improve hardlinks check
upils Nov 27, 2025
b6ecc27
feat: trying alternative approach
upils Nov 28, 2025
1406b18
feat: refine first approach
upils Dec 1, 2025
8aba035
refactor: dedicated verify.go
upils Dec 1, 2025
70b2c5e
refactor: rework verify* funcs
upils Dec 2, 2025
c8b7a39
fix: typos and inconsistencies
upils Dec 2, 2025
1181871
refactor: improve readability
upils Dec 2, 2025
31f1d3d
refactor: improve consistency
upils Dec 3, 2025
df49dfc
style: respect internal best practices
upils Dec 3, 2025
f4169b9
style: minor consistency fixes
upils Dec 4, 2025
aade454
fix: ignore size check on manifest
upils Dec 5, 2025
e0bd4a4
style: apply suggestions
upils Dec 8, 2025
2e572fb
style: apply suggestions
upils Dec 8, 2025
3c5de1c
style: apply suggestions
upils Dec 8, 2025
7eff973
style: apply suggestions
upils Dec 8, 2025
b7c7c66
style: apply suggestions
upils Dec 8, 2025
c7196e9
fix: apply suggestions
upils Dec 16, 2025
7539db7
refactor: simplify
upils Dec 19, 2025
cd22c1f
feat: intermediary representation
upils Jan 5, 2026
27d8645
refactor: simplify existing dir checking
upils Jan 5, 2026
373f3c1
refactor: regroup hardlink-related code
upils Jan 5, 2026
1414990
refactor: refine manifest checking API
upils Jan 5, 2026
2ce5b21
refactor: add obtainManifest
upils Jan 6, 2026
17162f4
refactor: simplify by removing SliceKeys function
upils Jan 6, 2026
c21ef34
style: remove redondant comment
upils Jan 13, 2026
77f5771
refactor: rework pathInfos comparison
upils Jan 14, 2026
40ef8e8
refactor: simplify FindPathsInRelease
upils Jan 14, 2026
0a25844
refactor: refine API
upils Jan 14, 2026
71c4496
refactor: simplify based on PR suggestions
upils Jan 15, 2026
4696dcc
style: tweak error messages
upils Jan 15, 2026
089109d
fix: fail if dir inconsistent with manifest
upils Jan 16, 2026
09faa6a
fix: use loaded manifest hash as reference
upils Jan 16, 2026
bd7a707
fix: raise error when path should not be hardlinked
upils Jan 19, 2026
3ea70ce
fix: collect and record reference manifest size
upils Jan 19, 2026
3efb7d2
feat: log root directory processing
upils Jan 19, 2026
a421818
refactor: more experiment around API design
upils Jan 21, 2026
e88de2d
refactor: experiment to improve API
upils Jan 21, 2026
acac421
refactor: more refining
upils Jan 22, 2026
0262cb2
refactor: CheckDir in slicer
upils Jan 22, 2026
7445fcd
refactor: add check in slicer
upils Jan 22, 2026
5fca0c5
refactor: simplify and reduce slicer API
upils Jan 22, 2026
fb6500b
ci: rerun
upils Jan 22, 2026
d7fa155
wip
upils Jan 23, 2026
43e8a71
refactor: cleaning
upils Jan 23, 2026
46d2119
fix: check manifest consistency at selection
upils Jan 23, 2026
164c68b
refactor: return a manifest from inspection
upils Jan 26, 2026
15550ad
refactor: apply PR suggestions
upils Jan 26, 2026
4ecb555
refactor: avoid iterating twice on slices
upils Jan 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions cmd/chisel/cmd_cut.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/canonical/chisel/internal/cache"
"github.com/canonical/chisel/internal/setup"
"github.com/canonical/chisel/internal/slicer"
"github.com/canonical/chisel/public/manifest"
)

var shortCutHelp = "Cut a tree with selected slices"
Expand Down Expand Up @@ -73,6 +74,21 @@ func (cmd *cmdCut) Execute(args []string) error {
}
}

mfest, err := slicer.Inspect(cmd.RootDir, release)
if err != nil {
return err
}
Comment thread
upils marked this conversation as resolved.
if mfest != nil {
mfest.IterateSlices("", func(slice *manifest.Slice) error {
sk, err := setup.ParseSliceKey(slice.Name)
if err != nil {
return err

Copy link
Copy Markdown
Owner Author

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.

}
sliceKeys = append(sliceKeys, sk)
return nil
})
}

selection, err := setup.Select(release, sliceKeys, cmd.Arch)
if err != nil {
return err
Expand Down
23 changes: 23 additions & 0 deletions internal/manifestutil/manifestutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"io"
"io/fs"
"maps"
"path/filepath"
"slices"
"sort"
Expand Down Expand Up @@ -34,6 +35,19 @@ 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{}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Why not use the Selection which should include all the slices we want to install?

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Can you elaborate? Do you mean use Selection as the argument to FindPathsInRelease? Why would we want to do that? The intention was not to look into the subset of slices selected so I do not see a relation with the selection.

for _, pkg := range r.Packages {
for _, slice := range pkg.Slices {
allSlices = append(allSlices, slice)
}
}
manifestMap := FindPaths(allSlices)
Comment thread
upils marked this conversation as resolved.
Outdated

Copy link
Copy Markdown

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?

@upils upils Jan 26, 2026

Copy link
Copy Markdown
Owner Author

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.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Reworked in 4ecb555

return slices.Sorted(maps.Keys(manifestMap))
}

type WriteOptions struct {
PackageInfo []*archive.PackageInfo
Selection []*setup.Slice
Expand Down Expand Up @@ -340,3 +354,12 @@ func Validate(mfest *manifest.Manifest) (err error) {
}
return nil
}

// CompareSchemas compare two manifest schema strings

Copy link
Copy Markdown

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.Schema then 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.

Copy link
Copy Markdown
Owner Author

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(?).

func CompareSchemas(va, vb string) int {
if va == manifest.Schema && va == vb {
return 0
}
return -1
}

186 changes: 186 additions & 0 deletions internal/slicer/check.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
package slicer

import (
"crypto/sha256"
"encoding/hex"
"fmt"
"io"
"io/fs"
"os"
"path/filepath"
"syscall"

"github.com/klauspost/compress/zstd"

"github.com/canonical/chisel/internal/manifestutil"
"github.com/canonical/chisel/public/manifest"
)

type pathInfo struct {
mode string
size int64
link string
hash string
}

func unixPerm(mode fs.FileMode) (perm uint32) {
perm = uint32(mode.Perm())
if mode&fs.ModeSticky != 0 {
perm |= 0o1000
}
return perm
}

// checkDir checks the content of the target directory matches with
// 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 {

Copy link
Copy Markdown

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).

Copy link
Copy Markdown
Owner Author

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.

singlePathsByFSInode := make(map[uint64]string)
fsInodeByManifestInode := make(map[uint64]uint64)
manifestInfos := make(map[string]*pathInfo)
err := mfest.IteratePaths("", func(path *manifest.Path) error {
pathHash := path.FinalSHA256
if pathHash == "" {
pathHash = path.SHA256
}
recordedPathInfo := &pathInfo{
mode: path.Mode,
size: int64(path.Size),
link: path.Link,
hash: pathHash,
}

fsInfo := &pathInfo{}
fullPath := filepath.Join(rootDir, path.Path)
info, err := os.Lstat(fullPath)
if err != nil {
return err
}
mode := info.Mode()
Comment thread
upils marked this conversation as resolved.
fsInfo.mode = fmt.Sprintf("0%o", unixPerm(mode))
ftype := mode & fs.ModeType
switch ftype {
case fs.ModeDir:
// Nothing to do
case fs.ModeSymlink:
fsInfo.link, err = os.Readlink(fullPath)
if err != nil {
return fmt.Errorf("cannot read symlink %q: %w", fullPath, err)
}
case 0: // Regular
h, err := contentHash(fullPath)
if err != nil {
return fmt.Errorf("cannot compute hash for %q: %w", fullPath, err)
}
fsInfo.hash = hex.EncodeToString(h)
fsInfo.size = info.Size()
default:
return fmt.Errorf("inconsistent content: %q has unrecognized type %s", fullPath, mode.String())

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I would remove the prefix inconsistent content, the error is that we cannot even read it because it is a pipe/socket/etc.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

See 15550ad

}

// Collect manifests for tailored checking later.
// Adjust observed hash and size to still compare in a generic way.
if filepath.Base(path.Path) == manifestutil.DefaultFilename && recordedPathInfo.size == 0 && recordedPathInfo.hash == "" {
mfestInfo := *fsInfo
manifestInfos[path.Path] = &mfestInfo
fsInfo.size = 0
fsInfo.hash = ""
}

if recordedPathInfo.mode != fsInfo.mode {
return fmt.Errorf("inconsistent mode at %q: recorded %v, observed %v", path.Path, recordedPathInfo.mode, fsInfo.mode)
}
if recordedPathInfo.size != fsInfo.size {
return fmt.Errorf("inconsistent size at %q: recorded %v, observed %v", path.Path, recordedPathInfo.size, fsInfo.size)
}
if recordedPathInfo.link != fsInfo.link {
return fmt.Errorf("inconsistent link at %q: recorded %v, observed %v", path.Path, recordedPathInfo.link, fsInfo.link)
}
if recordedPathInfo.hash != fsInfo.hash {
return fmt.Errorf("inconsistent hash at %q: recorded %v, observed %v", path.Path, recordedPathInfo.hash, fsInfo.hash)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Normally we use got ..., expected .... But please look around the code to see other error messages and make it consistent.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

I used these terms at first but they felt opinionated. Since the manifest is found in the rootfs, it could be "wrong" too. But it depends on the abstraction we want to present to the user. I think it would be useful for them to be aware that the inspection relies on the manifest so they can more easily solve the problem if the inspection fails.

If we decide that the usage of the manifest is in an implementation detail then I agree that got ..., expected ... would be more consistent.

WDYT?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

If we decide that the usage of the manifest is in an implementation detail then I agree that got ..., expected ... would be more consistent.

It is NOT an implementation detail. The fact we use the manifest has to be transparent IMO, how else would we do it. What can the user expect if not using the manifest

}
// Check hardlink
if ftype != fs.ModeDir {
stat, ok := info.Sys().(*syscall.Stat_t)
if !ok {
return fmt.Errorf("cannot get syscall stat info for %q", info.Name())
}
inode := stat.Ino

if path.Inode == 0 {
// This path must not be linked to any other

Copy link
Copy Markdown

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.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

See 15550ad

singlePath, ok := singlePathsByFSInode[inode]
if ok {
return fmt.Errorf("inconsistent content at %q: file hardlinked to %q", path.Path, singlePath)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This error message is a bit cryptic, as you say above normally we prefer the structure of recorded ..., observed ... or to state explicitly that no hardlinks are expected.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

I agree. I still would like to display the other path already hardlinked to the inode to help the user solve the problem. See 15550ad

}
singlePathsByFSInode[inode] = path.Path
} else {
recordedInode, ok := fsInodeByManifestInode[path.Inode]
if !ok {
fsInodeByManifestInode[path.Inode] = inode
} else if recordedInode != inode {
return fmt.Errorf("inconsistent content at %q: file hardlinked to a different inode", path.Path)
}
}
}
return nil
})
if err != nil {
return err
}

// Check manifests
// They must all be valid manifests.
// They must be consistent per schema version.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I see that you like this comment style :) but to be consistent with the rest of the code it is better to group sentences into paragraphs unless they are unrelated or need a paragraph break (which IMO is not the case here).

This suggestion applies to all comments which follow the same style.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

See 15550ad for improved comments.

schemaManifestInfos := make(map[string]*pathInfo)
for path, info := range manifestInfos {
fullPath := filepath.Join(rootDir, path)
f, err := os.Open(fullPath)
if err != nil {
return err
}
defer f.Close()
r, err := zstd.NewReader(f)
if err != nil {
return err
}
defer r.Close()
mfest, err = manifest.Read(r)
if err != nil {
return err

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

See my other comment, I think we need a named error here to know if we should skip. If we find a valid format then manifest.Validate should succeed or we should return an error, so I think you captured that correctly.

}
err = manifestutil.Validate(mfest)
if err != nil {
return err
}
schema := mfest.Schema()
refInfo, ok := schemaManifestInfos[schema]
if !ok {
schemaManifestInfos[schema] = info
continue
}

if refInfo.size != info.size {
return fmt.Errorf("inconsistent manifest size for version %s at %q: recorded %v, observed %v", schema, path, refInfo.size, info.size)
}
if refInfo.hash != info.hash {
return fmt.Errorf("inconsistent manifest hash for version %s at %q: recorded %v, observed %v", schema, path, refInfo.hash, info.hash)
}
Comment thread
upils marked this conversation as resolved.
}
return nil
}

func contentHash(path string) ([]byte, error) {
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
}
93 changes: 93 additions & 0 deletions internal/slicer/slicer.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ package slicer
import (
"archive/tar"
"bytes"
"encoding/hex"
"fmt"
"io"
"io/fs"
"os"
"path"
"path/filepath"
"slices"
"sort"
Expand All @@ -21,6 +23,7 @@ import (
"github.com/canonical/chisel/internal/manifestutil"
"github.com/canonical/chisel/internal/scripts"
"github.com/canonical/chisel/internal/setup"
"github.com/canonical/chisel/public/manifest"
)

const manifestMode fs.FileMode = 0644
Expand Down Expand Up @@ -537,3 +540,93 @@ func selectPkgArchives(archives map[string]archive.Archive, selection *setup.Sel
}
return pkgArchive, nil
}

// Inspect examines and validates the targetDir.
// Return the list of SliceKeys used to build the targetDir.
func Inspect(targetDir string, release *setup.Release) (*manifest.Manifest, error) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I think this name and comment is too generic, let's think of something better. What about VerifyPreviousCut or VerifyCut.

One problem I see with this grouping is that when I get back (nil, nil) it is not easy for me as a user to know if it means there was a manifest but it didn't match the content, or whether there was no manifest at all. The former is obviously not what we want, if we want a manifest then we want to get it or fail. It might also be the name that is throwing me off.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

I think this name and comment is too generic, let's think of something better. What about VerifyPreviousCut or VerifyCut.

The "cut" concept is an external abstraction, unknown to the slicer. I was looking for a name at the same level of abstraction than Run. At the beginning I also used the verify term but while discussing it with Ed I understood that it had a subtly different meaning than I thought. "Verify" is "prove the truth of", and at the abstraction level of Inspect I think it is not exactly (and not only) what Inspect does.

One problem I see with this grouping is that when I get back (nil, nil) it is not easy for me as a user to know if it means there was a manifest but it didn't match the content, or whether there was no manifest at all. The former is obviously not what we want, if we want a manifest then we want to get it or fail. It might also be the name that is throwing me off.

I agree that (nil, nil) is ambiguous and I am not super satisfied with it. However for now I could not see another way. Do you have a concrete proposition to organize the code in a way that would avoid that?

var mfest *manifest.Manifest
manifestPaths := manifestutil.FindPathsInRelease(release)
if len(manifestPaths) > 0 {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Prefer if len(manifestPaths) == 0 // early return rather than extra indentation.

logf("Inspecting root directory...")
var err error
mfest, err = selectValidManifest(targetDir, manifestPaths)
if err != nil {
return nil, err
}
if mfest != nil {
err = checkDir(mfest, targetDir)
if err != nil {
return nil, err
}
}
}
return mfest, nil
}

// selectValidManifest the first valid manifest found in a directory with the
// latest schema version.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

first is ambiguous. If the manifest is consistent with all others then that detail doesn't matter. You can state that all manifests have to be consistent so the choice is determinisitic / doesn't matter / whatever expression you prefer.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Improved in 15550ad. Let me know what you think.

// A manifest is considered valid if it is consistent with others present and
// of the same schema.
func selectValidManifest(targetDir string, manifestPaths []string) (*manifest.Manifest, error) {
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)
}
Comment on lines +570 to +577

Copy link
Copy Markdown

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.


type manifestHash struct {
path string
hash string
}
var selected *manifest.Manifest
schemaManifest := make(map[string]manifestHash)
for _, mfestPath := range manifestPaths {
err := func() error {
mfestFullPath := path.Join(targetDir, mfestPath)
f, err := os.Open(mfestFullPath)
if err != nil {
if os.IsNotExist(err) {
return nil
}
return err
}
defer f.Close()
r, err := zstd.NewReader(f)
if err != nil {
return err
}
defer r.Close()
mfest, err := manifest.Read(r)
if err != nil {
return nil

Copy link
Copy Markdown

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.

}
err = manifestutil.Validate(mfest)
if err != nil {
return nil
}

if selected == nil || manifestutil.CompareSchemas(mfest.Schema(), selected.Schema()) > 0 {

Copy link
Copy Markdown

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).

h, err := contentHash(mfestFullPath)
if err != nil {
return fmt.Errorf("cannot compute hash for %q: %w", mfestFullPath, err)
}
mfestHash := hex.EncodeToString(h)
refMfest, ok := schemaManifest[mfest.Schema()]
if !ok {
schemaManifest[mfest.Schema()] = manifestHash{mfestPath, mfestHash}
} else if refMfest.hash != mfestHash {
return fmt.Errorf("inconsistent manifests: %q and %q", refMfest.path, mfestPath)

Copy link
Copy Markdown

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.

}
selected = mfest
}
return nil
}()
if err != nil {
return nil, err
}
}
return selected, nil
}
4 changes: 4 additions & 0 deletions public/manifest/manifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,7 @@ func iteratePrefix[T prefixable](manifest *Manifest, prefix *T, onMatch func(*T)
}
return nil
}

func (manifest *Manifest) Schema() string {

Copy link
Copy Markdown
Owner Author

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?

return manifest.db.Schema()
}
Comment thread
upils marked this conversation as resolved.
Outdated