-
Notifications
You must be signed in to change notification settings - Fork 582
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
many: make snap pack --check-skeleton error logs consistent #14547
Changes from 2 commits
7c543cb
a195b7e
e0c053f
919676a
30aac23
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 |
---|---|---|
|
@@ -318,7 +318,7 @@ func validateContainer(c Container, needsrx, needsx, needsr, needsf, noskipd map | |
|
||
// bad modes are logged instead of being returned because the end user | ||
// can do nothing with the info (and the developer can read the logs) | ||
hasBadModes := false | ||
var firstBadModeErr error | ||
err := c.Walk(".", func(path string, info os.FileInfo, err error) error { | ||
if err != nil { | ||
return err | ||
|
@@ -342,7 +342,9 @@ func validateContainer(c Container, needsrx, needsx, needsr, needsf, noskipd map | |
symlinkInfo, err := evalAndValidateSymlink(c, path) | ||
if err != nil { | ||
logf("%s", err) | ||
hasBadModes = true | ||
if firstBadModeErr == nil { | ||
firstBadModeErr = err | ||
} | ||
} else { | ||
// use target mode for checks below | ||
mode = symlinkInfo.targetMode | ||
|
@@ -351,33 +353,48 @@ func validateContainer(c Container, needsrx, needsx, needsr, needsf, noskipd map | |
|
||
if mode.IsDir() { | ||
if mode.Perm()&0555 != 0555 { | ||
logf("in %s %q: %q should be world-readable and executable, and isn't: %s", contType, name, path, mode) | ||
hasBadModes = true | ||
err := fmt.Errorf("%q should be world-readable and executable, and isn't: %s", path, mode) | ||
logf("in %s %q: %v", contType, name, err) | ||
if firstBadModeErr == nil { | ||
firstBadModeErr = err | ||
} | ||
} | ||
} else { | ||
if needsrx[path] { | ||
if mode.Perm()&0555 != 0555 { | ||
logf("in snap %q: %q should be world-readable and executable, and isn't: %s", name, path, mode) | ||
hasBadModes = true | ||
err := fmt.Errorf("%q should be world-readable and executable, and isn't: %s", path, mode) | ||
logf("in snap %q: %v", name, err) | ||
if firstBadModeErr == nil { | ||
firstBadModeErr = err | ||
} | ||
} | ||
} | ||
// XXX: do we need to match other directories? | ||
if needsf[path] || strings.HasPrefix(path, "meta/") { | ||
if mode&(os.ModeNamedPipe|os.ModeSocket|os.ModeDevice) != 0 { | ||
logf("in %s %q: %q should be a regular file (or a symlink) and isn't", contType, name, path) | ||
hasBadModes = true | ||
err := fmt.Errorf("%q should be a regular file (or a symlink) and isn't", path) | ||
logf("in %s %q: ", contType, name, err) | ||
if firstBadModeErr == nil { | ||
firstBadModeErr = err | ||
} | ||
} | ||
} | ||
if needsx[path] || strings.HasPrefix(path, "meta/hooks/") { | ||
if mode.Perm()&0111 == 0 { | ||
logf("in %s %q: %q should be executable, and isn't: %s", contType, name, path, mode) | ||
hasBadModes = true | ||
err := fmt.Errorf("%q should be executable, and isn't: %s", path, mode) | ||
logf("in %s %q: %v", contType, name, err) | ||
if firstBadModeErr == nil { | ||
firstBadModeErr = err | ||
} | ||
} | ||
} else { | ||
// in needsr, or under meta but not a hook | ||
if mode.Perm()&0444 != 0444 { | ||
logf("in %s %q: %q should be world-readable, and isn't: %s", contType, name, path, mode) | ||
hasBadModes = true | ||
err := fmt.Errorf("%q should be world-readable, and isn't: %s", path, mode) | ||
logf("in %s %q: %v", contType, name, err) | ||
if firstBadModeErr == nil { | ||
firstBadModeErr = err | ||
} | ||
} | ||
} | ||
} | ||
|
@@ -387,18 +404,25 @@ func validateContainer(c Container, needsrx, needsx, needsr, needsf, noskipd map | |
return err | ||
} | ||
if len(seen) != len(needsx)+len(needsrx)+len(needsr) { | ||
var firstPath string | ||
for _, needs := range []map[string]bool{needsx, needsrx, needsr} { | ||
for path := range needs { | ||
if !seen[path] { | ||
logf("in %s %q: path %q does not exist", contType, name, path) | ||
if firstPath == "" { | ||
firstPath = path | ||
} | ||
} | ||
} | ||
} | ||
return ErrMissingPaths | ||
// TODO: aggregate path errors not just the first one | ||
return fmt.Errorf("%w: path %q does not exist", ErrMissingPaths, firstPath) | ||
} | ||
|
||
if hasBadModes { | ||
return ErrBadModes | ||
if firstBadModeErr != nil { | ||
// TODO: fmt.Errorf("%w: %w", ErrBadModes, firstBadModeErr) when using go 1.20+ | ||
// TODO: aggregate bad mode errors not just the first one | ||
return fmt.Errorf("%w: %v", ErrBadModes, firstBadModeErr) | ||
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 we use %w twice? I think we can but I'm not sure if this goes all the way back to go 1.18. 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. Unfortunately we can't, I believe we need go 1.20 for that https://tip.golang.org/doc/go1.20#errors |
||
} | ||
return nil | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -84,10 +84,10 @@ version: 1 | |
c.Assert(err, IsNil) | ||
|
||
err = snap.ValidateSnapContainer(container, info, discard) | ||
c.Check(err, Equals, snap.ErrMissingPaths) | ||
c.Check(err, ErrorMatches, `snap is unusable due to missing files: path "meta" does not exist`) | ||
|
||
err = snap.ValidateComponentContainer(container, "empty-snap+comp.comp", discard) | ||
c.Check(err, Equals, snap.ErrMissingPaths) | ||
c.Check(err, ErrorMatches, `snap is unusable due to missing files: path "meta" does not exist`) | ||
} | ||
|
||
func (s *validateSuite) TestValidateContainerEmptyButBadPermFails(c *C) { | ||
|
@@ -107,7 +107,7 @@ version: 1 | |
c.Assert(err, IsNil) | ||
|
||
err = snap.ValidateSnapContainer(s.container(), info, discard) | ||
c.Check(err, Equals, snap.ErrBadModes) | ||
c.Check(err, ErrorMatches, `snap is unusable due to bad permissions: "." should be world-readable and executable, and isn't: drwx------`) | ||
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. Perhaps add something that checks with the equivalent of errors.Is, for completeness. 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. good point, thanks! I added those to all relevant tests |
||
} | ||
|
||
func (s *validateSuite) TestValidateComponentContainerEmptyButBadPermFails(c *C) { | ||
|
@@ -121,29 +121,29 @@ func (s *validateSuite) TestValidateComponentContainerEmptyButBadPermFails(c *C) | |
// snapdir has /meta/component.yaml, but / is 0700 | ||
|
||
err = snap.ValidateComponentContainer(s.container(), "empty-snap+comp.comp", discard) | ||
c.Check(err, Equals, snap.ErrBadModes) | ||
c.Check(err, ErrorMatches, `snap is unusable due to bad permissions: "." should be world-readable and executable, and isn't: drwx------`) | ||
} | ||
|
||
func (s *validateSuite) TestValidateContainerMissingSnapYamlFails(c *C) { | ||
const yaml = `name: empty-snap | ||
version: 1 | ||
` | ||
container := s.container() | ||
c.Assert(os.Chmod(s.snapDirPath, 0755), IsNil) | ||
c.Assert(os.Mkdir(filepath.Join(s.snapDirPath, "meta"), 0755), IsNil) | ||
container := s.container() | ||
|
||
// snapdir's / and /meta are 0755 (i.e. OK), but no /meta/snap.yaml | ||
|
||
info, err := snap.InfoFromSnapYaml([]byte(yaml)) | ||
c.Assert(err, IsNil) | ||
|
||
err = snap.ValidateSnapContainer(container, info, discard) | ||
c.Check(err, Equals, snap.ErrMissingPaths) | ||
c.Check(err, ErrorMatches, `snap is unusable due to missing files: path "meta/snap.yaml" does not exist`) | ||
|
||
// component's / and /meta are 0755 (i.e. OK), but no /meta/component.yaml | ||
|
||
err = snap.ValidateComponentContainer(container, "empty-snap+comp.comp", discard) | ||
c.Check(err, Equals, snap.ErrMissingPaths) | ||
c.Check(err, ErrorMatches, `snap is unusable due to missing files: path "meta/component.yaml" does not exist`) | ||
} | ||
|
||
func (s *validateSuite) TestValidateContainerSnapYamlBadPermsFails(c *C) { | ||
|
@@ -161,7 +161,7 @@ version: 1 | |
c.Assert(err, IsNil) | ||
|
||
err = snap.ValidateSnapContainer(s.container(), info, discard) | ||
c.Check(err, Equals, snap.ErrBadModes) | ||
c.Check(err, ErrorMatches, `snap is unusable due to bad permissions: "meta/snap.yaml" should be world-readable, and isn't: ----------`) | ||
} | ||
|
||
func (s *validateSuite) TestValidateComponentContainerSnapYamlBadPermsFails(c *C) { | ||
|
@@ -173,7 +173,7 @@ func (s *validateSuite) TestValidateComponentContainerSnapYamlBadPermsFails(c *C | |
// /meta/component.yaml exists, but isn't readable | ||
|
||
err := snap.ValidateComponentContainer(s.container(), "empty-snap+comp.comp", discard) | ||
c.Check(err, Equals, snap.ErrBadModes) | ||
c.Check(err, ErrorMatches, `snap is unusable due to bad permissions: "meta/component.yaml" should be world-readable, and isn't: ----------`) | ||
} | ||
|
||
func (s *validateSuite) TestValidateContainerSnapYamlNonRegularFails(c *C) { | ||
|
@@ -191,7 +191,7 @@ version: 1 | |
c.Assert(err, IsNil) | ||
|
||
err = snap.ValidateSnapContainer(s.container(), info, discard) | ||
c.Check(err, Equals, snap.ErrBadModes) | ||
c.Check(err, ErrorMatches, `snap is unusable due to bad permissions: "meta/snap.yaml" should be a regular file \(or a symlink\) and isn't`) | ||
} | ||
|
||
// bootstrapEmptyContainer creates a minimal container directory under | ||
|
@@ -233,7 +233,7 @@ apps: | |
c.Assert(err, IsNil) | ||
|
||
err = snap.ValidateSnapContainer(s.container(), info, discard) | ||
c.Check(err, Equals, snap.ErrMissingPaths) | ||
c.Check(err, ErrorMatches, `snap is unusable due to missing files: path "foo" does not exist`) | ||
} | ||
|
||
func (s *validateSuite) TestValidateContainerBadAppPermsFails(c *C) { | ||
|
@@ -252,7 +252,7 @@ apps: | |
c.Assert(err, IsNil) | ||
|
||
err = snap.ValidateSnapContainer(s.container(), info, discard) | ||
c.Check(err, Equals, snap.ErrBadModes) | ||
c.Check(err, ErrorMatches, `snap is unusable due to bad permissions: "foo" should be world-readable and executable, and isn't: -r--r--r--`) | ||
} | ||
|
||
func (s *validateSuite) TestValidateContainerBadAppDirPermsFails(c *C) { | ||
|
@@ -272,7 +272,7 @@ apps: | |
c.Assert(err, IsNil) | ||
|
||
err = snap.ValidateSnapContainer(s.container(), info, discard) | ||
c.Check(err, Equals, snap.ErrBadModes) | ||
c.Check(err, ErrorMatches, `snap is unusable due to bad permissions: "apps" should be world-readable and executable, and isn't: drwx------`) | ||
} | ||
|
||
func (s *validateSuite) TestValidateContainerBadSvcPermsFails(c *C) { | ||
|
@@ -293,7 +293,7 @@ apps: | |
c.Assert(err, IsNil) | ||
|
||
err = snap.ValidateSnapContainer(s.container(), info, discard) | ||
c.Check(err, Equals, snap.ErrBadModes) | ||
c.Check(err, ErrorMatches, `snap is unusable due to bad permissions: "svcs/bar" should be executable, and isn't: ----------`) | ||
} | ||
|
||
func (s *validateSuite) TestValidateContainerCompleterFails(c *C) { | ||
|
@@ -316,7 +316,7 @@ apps: | |
c.Assert(err, IsNil) | ||
|
||
err = snap.ValidateSnapContainer(s.container(), info, discard) | ||
c.Check(err, Equals, snap.ErrMissingPaths) | ||
c.Check(err, ErrorMatches, `snap is unusable due to missing files: path "comp/foo.sh" does not exist`) | ||
} | ||
|
||
func (s *validateSuite) TestValidateContainerBadAppPathOK(c *C) { | ||
|
@@ -404,7 +404,7 @@ version: 1 | |
c.Assert(err, IsNil) | ||
|
||
err = snap.ValidateSnapContainer(container, info, discard) | ||
c.Check(err, ErrorMatches, "snap is unusable due to bad permissions") | ||
c.Check(err, ErrorMatches, `snap is unusable due to bad permissions: "meta/symlink" should be world-readable, and isn't: -rwx--x--x`) | ||
} | ||
|
||
func (s *validateSuite) TestValidateContainerSymlinksMetaBadTargetMode0000(c *C) { | ||
|
@@ -429,7 +429,7 @@ version: 1 | |
c.Assert(err, IsNil) | ||
|
||
err = snap.ValidateSnapContainer(container, info, discard) | ||
c.Check(err, ErrorMatches, "snap is unusable due to bad permissions") | ||
c.Check(err, ErrorMatches, `snap is unusable due to bad permissions: "meta/symlink" should be world-readable, and isn't: ----------`) | ||
} | ||
|
||
func (s *validateSuite) TestValidateContainerMetaExternalAbsSymlinksFails(c *C) { | ||
|
@@ -450,7 +450,7 @@ version: 1 | |
} | ||
|
||
err = snap.ValidateSnapContainer(s.container(), info, mockLogf) | ||
c.Check(err, Equals, snap.ErrBadModes) | ||
c.Check(err, ErrorMatches, `snap is unusable due to bad permissions: external symlink found: meta/gui/icons/snap.empty-snap.png -> /etc/shadow`) | ||
} | ||
|
||
func (s *validateSuite) TestValidateContainerMetaExternalRelativeSymlinksFails(c *C) { | ||
|
@@ -472,7 +472,7 @@ version: 1 | |
} | ||
|
||
err = snap.ValidateSnapContainer(s.container(), info, mockLogf) | ||
c.Check(err, Equals, snap.ErrBadModes) | ||
c.Check(err, ErrorMatches, `snap is unusable due to bad permissions: external symlink found: meta/gui/icons/snap.empty-snap.png -> 1/../../2/../../3/4/../../../../..`) | ||
} | ||
|
||
func (s *validateSuite) TestValidateContainerMetaExternalRelativeSymlinksOk(c *C) { | ||
|
@@ -518,7 +518,7 @@ version: 1 | |
c.Check(metaDirSymlinkErrFound, Equals, true) | ||
// the check for missing files precedes check for permission errors, so we | ||
// check for it instead. | ||
c.Check(err, Equals, snap.ErrMissingPaths) | ||
c.Check(err, ErrorMatches, `snap is unusable due to missing files: path "meta/snap.yaml" does not exist`) | ||
} | ||
|
||
func (s *validateSuite) TestValidateContainerAppsOK(c *C) { | ||
|
@@ -599,7 +599,7 @@ version: 1 | |
} | ||
|
||
err = snap.ValidateSnapContainer(s.container(), info, mockLogf) | ||
c.Check(err, Equals, snap.ErrBadModes) | ||
c.Check(err, ErrorMatches, "snap is unusable due to bad permissions: too many levels of symbolic links") | ||
c.Check(loopFound, Equals, true) | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -122,7 +122,7 @@ func loadAndValidate(sourceDir string, yaml []byte) (*snap.Info, error) { | |
return nil, fmt.Errorf("cannot validate snap %q: %v", info.InstanceName(), err) | ||
} | ||
|
||
if err := snap.ValidateSnapContainer(container, info, logger.Noticef); err != nil { | ||
if err := snap.ValidateSnapContainer(container, info, logger.Debugf); err != nil { | ||
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 seems little beyond the short term requirement. Concern is anyone relying on the log entries..., preference is to do this as part of larger rework for aggregation. 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. Switched back to Noticef to avoid breaking scripts that depend on the logs until we have a long term soln. |
||
return nil, err | ||
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. Wondering if it makes sense to give context that we are busy with "container validation", similar to what is done for "snap validation" with "cannot validate snap %q:"... but perhaps not worth the repercussions as part of short term minimal fix... 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. I agree, it's better to avoid breaking scripts that depend on some regex until we have a good plan. |
||
} | ||
if _, err := snap.ReadSnapshotYamlFromSnapFile(container); err != nil { | ||
|
@@ -256,7 +256,7 @@ func packComponent(sourceDir string, yaml []byte, opts *Options) (string, error) | |
return "", err | ||
} | ||
compPath := componentPath(ci, opts.TargetDir, opts.SnapName) | ||
if err := snap.ValidateComponentContainer(cont, compPath, logger.Noticef); err != nil { | ||
if err := snap.ValidateComponentContainer(cont, compPath, logger.Debugf); 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.
This works.
It made me think about error aggregation and identification of first instance as a generally useful concept.
Could create error type for aggregation, containing list of
Errors []error
that defaults to error[0] for message, but can be iterated to concert errors to e.g. json output or detail log entry etc as required.This could the become e.g. accErrorBadModes.add(...)
Anyways, more applicable to longer term solution...