func Parse(data []byte) (types.Config, report.Report) { var cfg types.Config var r report.Report if err := yaml.Unmarshal(data, &cfg); err != nil { return types.Config{}, report.ReportFromError(err, report.EntryError) } nodes := yaml.UnmarshalToNode(data) if nodes == nil { r.Add(report.Entry{ Kind: report.EntryWarning, Message: "Configuration is empty", }) r.Merge(validate.ValidateWithoutSource(reflect.ValueOf(cfg))) } else { root, err := fromYamlDocumentNode(*nodes) if err != nil { return types.Config{}, report.ReportFromError(err, report.EntryError) } r.Merge(validate.Validate(reflect.ValueOf(cfg), root, nil)) } if r.IsFatal() { return types.Config{}, r } return cfg, r }
func checkDuplicateFilesystems(cfg Config, r *report.Report) { filesystems := map[string]struct{}{"root": {}} for _, filesystem := range cfg.Storage.Filesystems { if _, ok := filesystems[filesystem.Name]; ok { r.Add(report.Entry{ Kind: report.EntryWarning, Message: fmt.Sprintf("Filesystem %q shadows exising filesystem definition", filesystem.Name), }) } filesystems[filesystem.Name] = struct{}{} } }
func (n Disk) Validate() report.Report { r := report.Report{} if len(n.Device) == 0 { r.Add(report.Entry{ Message: "disk device is required", Kind: report.EntryError, }) } if n.partitionNumbersCollide() { r.Add(report.Entry{ Message: fmt.Sprintf("disk %q: partition numbers collide", n.Device), Kind: report.EntryError, }) } if n.partitionsOverlap() { r.Add(report.Entry{ Message: fmt.Sprintf("disk %q: partitions overlap", n.Device), Kind: report.EntryError, }) } if n.partitionsMisaligned() { r.Add(report.Entry{ Message: fmt.Sprintf("disk %q: partitions misaligned", n.Device), Kind: report.EntryError, }) } // Disks which have no errors at this point will likely succeed in sgdisk return r }
func checkFilesFilesystems(cfg Config, r *report.Report) { filesystems := map[string]struct{}{"root": {}} for _, filesystem := range cfg.Storage.Filesystems { filesystems[filesystem.Name] = struct{}{} } for _, file := range cfg.Storage.Files { if file.Filesystem == "" { // Filesystem was not specified. This is an error, but its handled in types.File's Validate, not here continue } _, ok := filesystems[file.Filesystem] if !ok { r.Add(report.Entry{ Kind: report.EntryWarning, Message: fmt.Sprintf("File %q references nonexistent filesystem %q. (This is ok if it is defined in a referenced config)", file.Path, file.Filesystem), }) } } }
func ParseFromLatest(rawConfig []byte) (types.Config, report.Report, error) { if isEmpty(rawConfig) { return types.Config{}, report.Report{}, ErrEmpty } else if isCloudConfig(rawConfig) { return types.Config{}, report.Report{}, ErrCloudConfig } else if isScript(rawConfig) { return types.Config{}, report.Report{}, ErrScript } var err error var config types.Config // These errors are fatal and the config should not be further validated if err = json.Unmarshal(rawConfig, &config); err == nil { versionReport := config.Ignition.Version.Validate() if versionReport.IsFatal() { return types.Config{}, versionReport, ErrInvalid } } // Handle json syntax and type errors first, since they are fatal but have offset info if serr, ok := err.(*json.SyntaxError); ok { line, col, highlight := errorutil.HighlightBytePosition(bytes.NewReader(rawConfig), serr.Offset) return types.Config{}, report.Report{ Entries: []report.Entry{{ Kind: report.EntryError, Message: serr.Error(), Line: line, Column: col, Highlight: highlight, }}, }, ErrInvalid } if terr, ok := err.(*json.UnmarshalTypeError); ok { line, col, highlight := errorutil.HighlightBytePosition(bytes.NewReader(rawConfig), terr.Offset) return types.Config{}, report.Report{ Entries: []report.Entry{{ Kind: report.EntryError, Message: terr.Error(), Line: line, Column: col, Highlight: highlight, }}, }, ErrInvalid } // Handle other fatal errors (i.e. invalid version) if err != nil { return types.Config{}, report.ReportFromError(err, report.EntryError), err } // Unmarshal again to a json.Node to get offset information for building a report var ast json.Node var r report.Report configValue := reflect.ValueOf(config) if err := json.Unmarshal(rawConfig, &ast); err != nil { r.Add(report.Entry{ Kind: report.EntryWarning, Message: "Ignition could not unmarshal your config for reporting line numbers. This should never happen. Please file a bug.", }) r.Merge(validate.ValidateWithoutSource(configValue)) } else { r.Merge(validate.Validate(configValue, astjson.FromJsonRoot(ast), bytes.NewReader(rawConfig))) } if r.IsFatal() { return types.Config{}, r, ErrInvalid } return config, r, nil }
func validateStruct(vObj reflect.Value, ast AstNode, source io.ReadSeeker) report.Report { r := report.Report{} // isFromObject will be true if this struct was unmarshalled from a JSON object. keys, isFromObject := map[string]AstNode{}, false if ast != nil { keys, isFromObject = ast.KeyValueMap() } // Maintain a set of key's that have been used. usedKeys := map[string]struct{}{} // Maintain a list of all the tags in the struct for fuzzy matching later. tags := []string{} for _, f := range getFields(vObj) { // Default to nil AstNode if the field's corrosponding node cannot be found. var sub_node AstNode // Default to passing a nil source if the field's corrosponding node cannot be found. // This ensures the line numbers reported from all sub-structs are 0 and will be changed by AddPosition var src io.ReadSeeker // Try to determine the json.Node that corrosponds with the struct field if isFromObject { tag := strings.SplitN(f.Type.Tag.Get(ast.Tag()), ",", 2)[0] // Save the tag so we have a list of all the tags in the struct tags = append(tags, tag) // mark that this key was used usedKeys[tag] = struct{}{} if sub, ok := keys[tag]; ok { // Found it sub_node = sub src = source } } sub_report := Validate(f.Value, sub_node, src) // Default to deepest node if the node's type isn't an object, // such as when a json string actually unmarshal to structs (like with version) line, col := 0, 0 if ast != nil { line, col, _ = ast.ValueLineCol(src) } sub_report.AddPosition(line, col, "") r.Merge(sub_report) } if !isFromObject { // If this struct was not unmarshalled from a JSON object, there cannot be unused keys. return r } for k, v := range keys { if _, hasKey := usedKeys[k]; hasKey { continue } line, col, highlight := v.KeyLineCol(source) typo := similar(k, tags) r.Add(report.Entry{ Kind: report.EntryWarning, Message: fmt.Sprintf("Config has unrecognized key: %s", k), Line: line, Column: col, Highlight: highlight, }) if typo != "" { r.Add(report.Entry{ Kind: report.EntryInfo, Message: fmt.Sprintf("Did you mean %s instead of %s", typo, k), Line: line, Column: col, Highlight: highlight, }) } } return r }