/
githubmirror
/
ignition
Обзор
Документация
Войти
/
githubmirror
/
ignition
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
config/doc/schema.go
346 строк
9 KB
Benjamin Gilbert
config/doc: drop "unless" keyword from YAML
09 май 2023, 22:18
09 май 2023, 22:18
20b0584
Код
Авторство
О чём код?
// Copyright 2023 Red Hat, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package doc import ( "bytes" _ "embed" "fmt" "io" "reflect" "regexp" "strings" "github.com/coreos/ignition/v2/config/util" "github.com/coreos/go-semver/semver" "github.com/mitchellh/copystructure" "gopkg.in/yaml.v3" ) //go:embed ignition.yaml var ignitionDocs []byte const IGNITION_VARIANT = "ignition" const ROOT_COMPONENT = "root" type Components map[string]DocNode type DocNode struct { Name string `yaml:"name"` Description string `yaml:"desc"` Required *bool `yaml:"required"` RequiredIf Constraints `yaml:"required-if"` Transforms []Transform `yaml:"transforms"` Children []DocNode `yaml:"children"` Component string `yaml:"use"` After string `yaml:"after"` // populated after component resolution Parent *DocNode } type Transform struct { Regex string `yaml:"regex"` Replacement string `yaml:"replacement"` Constraints Constraints `yaml:"if"` Descendants bool `yaml:"descendants"` } type Constraints []Constraint type Constraint struct { Variant string `yaml:"variant"` MinVer *string `yaml:"min"` MaxVer *string `yaml:"max"` } type VariantVersions map[string]semver.Version // takes a slice of path elements, returns whether to ignore the subtree type IgnoreFunc func([]string) bool func IgnitionComponents() (Components, error) { return ParseComponents(bytes.NewBuffer(ignitionDocs)) } func ParseComponents(r io.Reader) (Components, error) { decoder := yaml.NewDecoder(r) decoder.KnownFields(true) var comps Components if err := decoder.Decode(&comps); err != nil { return comps, fmt.Errorf("parsing components: %w", err) } return comps, nil } func (comps Components) Generate(vers VariantVersions, config any, ignore IgnoreFunc, w io.Writer) error { root, err := comps.resolve() if err != nil { return err } gen := generator{ vers: vers, ignore: ignore, w: w, } return gen.descendNode(root, reflect.TypeOf(config), nil) } func (comps Components) resolve() (DocNode, error) { root, ok := comps[ROOT_COMPONENT] if !ok { return DocNode{}, fmt.Errorf("missing component %q", ROOT_COMPONENT) } root = copystructure.Must(copystructure.Copy(root)).(DocNode) if err := comps.resolveComponents(&root); err != nil { return DocNode{}, err } root.setParentLinks() return root, nil } func (comps Components) resolveComponents(node *DocNode) error { // recursively insert the subtree of any component reference if node.Component != "" { comp, ok := comps[node.Component] if !ok { return fmt.Errorf("field %q: no such component %q", node.Name, node.Component) } if comp.Component != "" { return fmt.Errorf("component %q cannot itself refer to a component", node.Component) } comp = copystructure.Must(copystructure.Copy(comp)).(DocNode) if err := comp.merge(*node); err != nil { return err } comp.Component = "" *node = comp } // now that all merging has happened, any remaining After field is // incorrect if node.After != "" { return fmt.Errorf("field %q: stray `after` parameter %q", node.Name, node.After) } // descend children for i := range node.Children { if err := comps.resolveComponents(&node.Children[i]); err != nil { return err } } return nil } func (comps Components) Merge(override Components) error { for name, comp := range comps { overrideComp, ok := override[name] if !ok { // no override continue } // present in both if err := comp.merge(overrideComp); err != nil { return fmt.Errorf("merging component %q: %w", name, err) } comps[name] = comp } for name, comp := range override { if _, ok := comps[name]; ok { // present in both continue } // only present in override; add to current comps[name] = comp } return nil } func (node *DocNode) setParentLinks() { for i := range node.Children { child := &node.Children[i] child.Parent = node child.setParentLinks() } } func (node *DocNode) renderDescription(vers VariantVersions) (string, error) { desc := node.Description for _, xfrm := range node.transforms() { matches, err := xfrm.Constraints.matches(vers) if err != nil { return "", fmt.Errorf("field %q: %w", node.Name, err) } if util.IsFalse(matches) { continue } re, err := regexp.Compile(xfrm.Regex) if err != nil { return "", fmt.Errorf("field %q: compiling %q: %w", node.Name, xfrm.Regex, err) } new := re.ReplaceAllString(desc, xfrm.Replacement) if !xfrm.Descendants && new == desc { return "", fmt.Errorf("field %q: applying %q: transform didn't change anything", node.Name, xfrm.Regex) } desc = new } // substitute version variables last, so transforms can add them for variant, ver := range vers { desc = strings.ReplaceAll(desc, fmt.Sprintf("%%%s_version%%", variant), ver.String()) } return desc, nil } func (node *DocNode) required(vers VariantVersions) (*bool, error) { conditional, err := node.RequiredIf.matches(vers) if err != nil { return nil, err } if conditional != nil { return conditional, nil } return node.Required, nil } func (node *DocNode) transforms() []Transform { var ret []Transform var descend func(node *DocNode, inheritedOnly bool) descend = func(node *DocNode, inheritedOnly bool) { for _, xfrm := range node.Transforms { if inheritedOnly && !xfrm.Descendants { continue } ret = append(ret, xfrm) } if node.Parent != nil { descend(node.Parent, true) } } descend(node, false) return ret } func (node *DocNode) merge(override DocNode) error { // merge fields if override.Name != "" { node.Name = override.Name } if override.Description != "" { node.Description = override.Description } if override.Required != nil { node.Required = override.Required node.RequiredIf = nil } if len(override.RequiredIf) > 0 { node.Required = nil node.RequiredIf = append(node.RequiredIf, override.RequiredIf...) } node.Transforms = append(node.Transforms, override.Transforms...) if override.Component != "" { node.Component = override.Component } if override.After != "" { node.After = override.After } // insertions and overrides for children unseenOverrides := make(map[string]DocNode) overrideByName := make(map[string]DocNode) insertionsByPredecessor := make(map[string][]DocNode) for _, child := range override.Children { unseenOverrides[child.Name] = child overrideByName[child.Name] = child if child.After != "" { insertionsByPredecessor[child.After] = append(insertionsByPredecessor[child.After], child) } } var children []DocNode insert := func(predecessor string) { for _, child := range insertionsByPredecessor[predecessor] { child.After = "" children = append(children, child) delete(unseenOverrides, child.Name) } } insert("^") for _, child := range node.Children { if override, ok := overrideByName[child.Name]; ok { if override.After != "" { return fmt.Errorf("field %q: override %q sets `after` and also matches existing field", node.Name, child.Name) } if err := child.merge(override); err != nil { return err } delete(unseenOverrides, child.Name) } children = append(children, child) insert(child.Name) } insert("$") node.Children = children // find unused overrides for _, child := range unseenOverrides { if child.After != "" { return fmt.Errorf("field %q: child %q: `after` field %q not found", node.Name, child.Name, child.After) } else { return fmt.Errorf("field %q: override %q not found; did you mean to set `after`?", node.Name, child.Name) } } return nil } func (cons Constraints) matches(vers VariantVersions) (*bool, error) { if len(cons) == 0 { // no constraints return nil, nil } for _, con := range cons { if con.Variant == "" { return nil, fmt.Errorf("missing `variant` in constraint") } ver, ok := vers[con.Variant] if !ok { // constraint isn't relevant to us continue } if con.MinVer != nil { min, err := semver.NewVersion(*con.MinVer) if err != nil { return nil, fmt.Errorf("parsing min %q: %w", *con.MinVer, err) } if ver.LessThan(*min) { // constraint failed; try others continue } } if con.MaxVer != nil { max, err := semver.NewVersion(*con.MaxVer) if err != nil { return nil, fmt.Errorf("parsing max %q: %w", *con.MaxVer, err) } if max.LessThan(ver) { // constraint failed; try others continue } } // one constraint matched; accept return util.BoolToPtr(true), nil } // no constraints matched; reject return util.BoolToPtr(false), nil }