Skip to content
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

snap: add validation of gadget.yaml #6634

Merged
merged 13 commits into from Apr 1, 2019

Conversation

bboozzoo
Copy link
Collaborator

Add validation of gadget definition based on what ubuntu-image does. Try to match error message where possible.

The validation only considers the structure of gadget.yaml, internal references and sanity of defined entries.

Things like validation of structure overlap, offset sizes are best done in a separate step, when the structure is 'resolved' for writing. Meaning, that the information from gadget.yaml is conbined with the contents of the gadget snap (eg. when size is left unspecified) and platform requirements (eg. default sector size). The ubuntu-image tries to do that upfront, but makes some assumptions along the way.

The PR contains #6609

Add validation of gadget definition based on what ubuntu-image does. Try to
match error message where possible.

Signed-off-by: Maciej Borzecki <maciej.zenon.borzecki@canonical.com>
Signed-off-by: Maciej Borzecki <maciej.zenon.borzecki@canonical.com>
@pedronis pedronis self-requested a review March 22, 2019 14:32
@zyga
Copy link
Collaborator

zyga commented Mar 25, 2019

Related to this work: what is the future interaction with ubuntu-image? I would expect that there is only one implementation of certain things. Will ubuntu-image depend on "snap" to pack/validate gadgets? Will we have to live with two implementations?

@bboozzoo
Copy link
Collaborator Author

As discussed, we'd like to eventually grow the functionality to be able to assemble the image ourselves. At which time ubuntu-image could be updated to use snap.

@zyga
Copy link
Collaborator

zyga commented Mar 25, 2019

@bboozzoo that is a good plan. Thanks

Copy link
Contributor

@mvo5 mvo5 left a comment

Choose a reason for hiding this comment

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

Looks nice, some comments and nitpicks for consideration but no blockers, feel free to ignore most of them (or act, depending on how much you like them).

snap/gadget.go Outdated
@@ -223,6 +244,7 @@ func ReadGadgetInfo(info *Info, classic bool) (*GadgetInfo, error) {
default:
return nil, fmt.Errorf(errorFormat, "bootloader must be one of grub, u-boot or android-boot")
}

Copy link
Contributor

Choose a reason for hiding this comment

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

Extra whitespace? Or deliberate?

snap/gadget.go Outdated
@@ -234,6 +256,225 @@ func ReadGadgetInfo(info *Info, classic bool) (*GadgetInfo, error) {
return &gi, nil
}

func indexAndName(idx int, name string) string {
Copy link
Contributor

Choose a reason for hiding this comment

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

(nitpick²) maybe fmtIndexAndName()?

snap/gadget.go Outdated
if err := validateVolumeStructure(&s, vol); err != nil {
maybeName := ""
if s.Name != "" {
maybeName = fmt.Sprintf("(%q)", s.Name)
Copy link
Contributor

Choose a reason for hiding this comment

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

Don't we need a leading space here: " (%q)" ? The error will otherwise concat idx and name to something like 1("foo"). Or am I misreading (or is it intentional)?

snap/gadget.go Outdated

func validateVolume(name string, vol *GadgetVolume) error {
if !validVolumeName.MatchString(name) {
return errors.New("invalid volume name")
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need to include what volume name string is invalid here (i.e. fmt.Error("invalid volume name %q"), only ? Or will this error be wrapped somewhere that includes this info? - I see that this is wrapped.

Copy link
Collaborator

@pedronis pedronis Mar 26, 2019

Choose a reason for hiding this comment

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

do we need to mention "volume" in this error?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Updated. The error just below needs the same treatment.

snap/gadget.go Outdated
indexAndName(idx, s.Name), s.OffsetWrite.RelativeTo)
}

if s.Filesystem != "" && s.Filesystem != "none" {
Copy link
Contributor

Choose a reason for hiding this comment

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

We have a "IsBare" helper, could that be used here?

snap/gadget.go Outdated
func ParseGadgetRelativeOffset(grs string) (*GadgetRelativeOffset, error) {
toWhat := ""
sizeSpec := grs
idx := strings.IndexRune(grs, '+')
Copy link
Contributor

Choose a reason for hiding this comment

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

Would strings.Split() be slightly shorter here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Did the change locally, looks very much the same.

snap/gadget.go Outdated
if err != nil {
return nil, fmt.Errorf("cannot parse offset %q: %v", sizeSpec, err)
}
if size > 4*2<<30 {
Copy link
Contributor

Choose a reason for hiding this comment

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

(nitpick) maybe var limit4G = 4*2<<30 \n if size > limit4G ... ?

target: /
`)

func mustParseGadgetSize(c *C, s string) snap.GadgetSize {
Copy link
Contributor

Choose a reason for hiding this comment

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

(nitpick²) all(?) of the other "mustFoo()" do not take a "check.C" and just panic. Probably not worth changing though.


// ParseGadgetSize parses a string expressing size in gadget declaration. The
// accepted format is one of: <bytes> | <bytes/2^20>M | <bytes/2^30>G.
func ParseGadgetSize(gs string) (GadgetSize, error) {
Copy link
Contributor

Choose a reason for hiding this comment

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

We have similar code in strutil.ParseByteSize I was wondering if we could unify those, but the trouble is of course is that that strutil.ParseByteSize uses MB MegaByte (10^6) vs this code here which uses MiB (^20) but uses "M" as the ending which is not the correct extentision, it should be "MiB". So probably easiest to just keep it as is.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah, I wanted to use the helper we have, but the formats accepted in gadget.yaml are only these 3:

  • <number> (bytes)
  • <number>M
  • <number>G

err string
}{
{"1234", 1234, ""},
{"1234M", 1234 * 2 << 20, ""},
Copy link
Contributor

Choose a reason for hiding this comment

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

Given how many 2 << 20 and 2 << 30 I'm seeing I wonder if we should have a const in snapd for this somewhere? Like "2units.Mebibyte" and "2units.Gigibyte" etc ?

Copy link
Collaborator

@zyga zyga left a comment

Choose a reason for hiding this comment

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

Old partial review, starting afresh.

snap/gadget.go Outdated
)

var (
validVolumeName = regexp.MustCompile("^[a-z-]+$")
Copy link
Collaborator

Choose a reason for hiding this comment

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

This allows a volume to start with -, is that desirable? Do volume names have to be lower case? I would suspect at least some volumes wanting to have all-caps name for some legacy reason.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Looks like ubuntu-image is bad too, this is regexp they use: ^[-a-zA-Z0-9]+$, and this is what the docs page states: [a-z-]+

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Updated to match what ubuntu-image does.

var (
validVolumeName = regexp.MustCompile("^[a-z-]+$")
validTypeID = regexp.MustCompile("^[0-9A-F]{2}$")
validGUUID = regexp.MustCompile("^[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}$")
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nice :)

snap/gadget.go Show resolved Hide resolved
for _, v := range gi.Volumes {
for name, v := range gi.Volumes {
if err := validateVolume(name, &v); err != nil {
return nil, fmt.Errorf("invalid volume %q: %v", name, err)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Would it be sensible that the error you create here was actually returned by validateVolume and that this place simply returned it verbatim?

Copy link
Contributor

Choose a reason for hiding this comment

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

Ha! I was thinking exactly the same and then wondered if we have a pattern and then wondered if go has a pattern and then got lost in the weeds. But I still wonder if there is a pattern for this in the go community in cases like this where it seems like both choices make sense. Maybe @pedronis has an opinion/thoughts?

Copy link
Collaborator

Choose a reason for hiding this comment

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

There's this https://github.com/pkg/errors that has very nicely defined Wrap function: https://github.com/pkg/errors/blob/master/errors.go#L181 for the case of taking one error and wrapping it with some more messages.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

You can have multiple volumes in gadget.yaml. Returning error as is would imply the validate* has to include all necessary information to be able to locate the invalid entity.

FWIW we have a similar problem with code in snap/validate.go.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think this if fine, we just need to be careful not to use "volume" in the errors below too unnecessarily

Signed-off-by: Maciej Borzecki <maciej.zenon.borzecki@canonical.com>
Signed-off-by: Maciej Borzecki <maciej.zenon.borzecki@canonical.com>
Copy link
Collaborator

@pedronis pedronis left a comment

Choose a reason for hiding this comment

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

thank you, did a first pass

for _, v := range gi.Volumes {
for name, v := range gi.Volumes {
if err := validateVolume(name, &v); err != nil {
return nil, fmt.Errorf("invalid volume %q: %v", name, err)
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think this if fine, we just need to be careful not to use "volume" in the errors below too unnecessarily

snap/gadget.go Outdated

func validateVolume(name string, vol *GadgetVolume) error {
if !validVolumeName.MatchString(name) {
return errors.New("invalid volume name")
Copy link
Collaborator

@pedronis pedronis Mar 26, 2019

Choose a reason for hiding this comment

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

do we need to mention "volume" in this error?

if name != "" {
return fmt.Sprintf("#%v (%q)", idx, name)
}
return fmt.Sprintf("#%v", idx)
Copy link
Collaborator

Choose a reason for hiding this comment

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

this path seems not to be exercised in the tests


// GadgetSize describes the size of a structure item or an offset within the
// structure.
type GadgetSize uint64
Copy link
Collaborator

Choose a reason for hiding this comment

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

it's fine (in that world)

snap/gadget.go Outdated
names := make(map[string]bool, len(vs.Update.Preserve))
for _, n := range vs.Update.Preserve {
if names[n] {
return fmt.Errorf("duplicate preserve entry %q", n)
Copy link
Collaborator

Choose a reason for hiding this comment

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

this reads strangely, maybe quotes help:

duplicate "preserve" entry %q

snap/gadget.go Outdated
bare := vs.IsBare()
for i, c := range vs.Content {
var err error
if bare {
Copy link
Collaborator

Choose a reason for hiding this comment

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

but we could simply set a checker outside and not have the if here:

var contentChecker func(*VolumeContent) error

structureNames := make(map[string]bool, len(vol.Structure))
for idx, s := range vol.Structure {
if err := validateVolumeStructure(&s, vol); err != nil {
return fmt.Errorf("invalid structure %s: %v", fmtIndexAndName(idx, s.Name), err)
Copy link
Collaborator

Choose a reason for hiding this comment

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

seems tests never get to this error

}

if s == "" {
return errors.New(`type is not specified`)
Copy link
Collaborator

Choose a reason for hiding this comment

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

this error is not exercised

snap/gadget.go Outdated

func validateVolumeStructure(vs *VolumeStructure, vol *GadgetVolume) error {
if err := validateStructureType(vs.Type, vol); err != nil {
return fmt.Errorf("invalid type %q: %v", vs.Type, err)
Copy link
Collaborator

Choose a reason for hiding this comment

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

tests don't get to this error

snap/gadget.go Outdated
return fmt.Errorf("invalid type %q: %v", vs.Type, err)
}
if err := validateRole(vs, vol); err != nil {
return fmt.Errorf("invalid role %q: %v", vs.Role, err)
Copy link
Collaborator

Choose a reason for hiding this comment

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

tests don't get to this error

Signed-off-by: Maciej Borzecki <maciej.zenon.borzecki@canonical.com>
Signed-off-by: Maciej Borzecki <maciej.zenon.borzecki@canonical.com>
Signed-off-by: Maciej Borzecki <maciej.zenon.borzecki@canonical.com>
@bboozzoo bboozzoo requested review from pedronis and zyga March 28, 2019 11:42
Copy link
Collaborator

@pedronis pedronis left a comment

Choose a reason for hiding this comment

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

thank you

snap/gadget.go Outdated
Filesystem string `yaml:"filesystem"`
Content []VolumeContent `yaml:"content"`
Update VolumeUpdate `yaml:"update"`
Name string `yaml:"name"`
Copy link
Collaborator

Choose a reason for hiding this comment

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

What is the difference between the label and the name?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Label is just a filesystem label. Name written as partition name if GPT schema is in use, otherwise it's informative.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Ah, can you document that in the structure please?

Copy link
Collaborator

@zyga zyga left a comment

Choose a reason for hiding this comment

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

LGTM because it's green and it is Friday but please look at my comments for several simple follow-ups.

snap/gadget.go Outdated Show resolved Hide resolved
return fmt.Sprintf("#%v", idx)
}

func validateVolume(name string, vol *GadgetVolume) error {
Copy link
Collaborator

Choose a reason for hiding this comment

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

+1

return nil
}

func validateVolumeStructure(vs *VolumeStructure, vol *GadgetVolume) error {
Copy link
Collaborator

Choose a reason for hiding this comment

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

+1

// - "bare"
// - [0-9A-Z]{2} - MBR type
// - GPT UUID
// - hybrid ID
Copy link
Collaborator

Choose a reason for hiding this comment

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

Perhaps add a wikipedia reference to explain what hybrid IDs are for.

return nil
}

func validateStructureType(s string, vol *GadgetVolume) error {
Copy link
Collaborator

Choose a reason for hiding this comment

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

+1

return nil
}

func validateStructureUpdate(up *VolumeUpdate, vs *VolumeStructure) error {
Copy link
Collaborator

Choose a reason for hiding this comment

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

+1

snap/gadget.go Outdated Show resolved Hide resolved

// ParseGadgetSize parses a string expressing size in gadget declaration. The
// accepted format is one of: <bytes> | <bytes/2^20>M | <bytes/2^30>G.
func ParseGadgetSize(gs string) (GadgetSize, error) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

+1

if err != nil {
return nil, fmt.Errorf("cannot parse offset %q: %v", sizeSpec, err)
}
if size > 4*SizeGiB {
Copy link
Collaborator

Choose a reason for hiding this comment

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

The comment below is redundant. Can you please add a reference to the origin of the limit?

snap/gadget.go Outdated Show resolved Hide resolved
@bboozzoo
Copy link
Collaborator Author

Pushed a patch adding comments to volume, structure and content, based on what's there in https://docs.snapcraft.io/the-gadget-snap/696.

@bboozzoo bboozzoo merged commit db6412d into snapcore:master Apr 1, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
4 participants