Stop nested class parameters being parsed as classes
encapi's cobbler-wire document keys class parameters under the role name, and the line-based parser treats every indented line as a class, so a node with class params is classified with the parameter keys as extra classes. - track the indentation of the classes block and skip deeper lines - cover the nested form real cobbler-imported nodes return
This commit is contained in:
@@ -81,6 +81,10 @@ func parseClasses(lines []string, i int, inline string) ([]string, int, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var classes []string
|
var classes []string
|
||||||
|
// Entries of the block sit at one indentation level. A class's value may
|
||||||
|
// itself be a nested block (class parameters, which encapi emits keyed by
|
||||||
|
// role name), whose deeper lines are values, not class names.
|
||||||
|
entryIndent := -1
|
||||||
j := i + 1
|
j := i + 1
|
||||||
for ; j < len(lines); j++ {
|
for ; j < len(lines); j++ {
|
||||||
line := lines[j]
|
line := lines[j]
|
||||||
@@ -88,18 +92,23 @@ func parseClasses(lines []string, i int, inline string) ([]string, int, error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
trimmed := strings.TrimSpace(line)
|
trimmed := strings.TrimSpace(line)
|
||||||
|
indent := len(line) - len(strings.TrimLeft(line, " \t"))
|
||||||
|
// An unindented, non-list line is the next top-level key, so stop.
|
||||||
|
if indent == 0 && !strings.HasPrefix(trimmed, "-") {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if entryIndent < 0 {
|
||||||
|
entryIndent = indent
|
||||||
|
}
|
||||||
|
if indent > entryIndent {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if strings.HasPrefix(trimmed, "- ") || trimmed == "-" {
|
if strings.HasPrefix(trimmed, "- ") || trimmed == "-" {
|
||||||
// Block list form, at either the parent indent ("- roles::base",
|
// Block list form, at either the parent indent ("- roles::base",
|
||||||
// as yaml.dump emits) or nested (" - roles::base").
|
// as yaml.dump emits) or nested (" - roles::base").
|
||||||
classes = append(classes, unquote(strings.TrimSpace(trimmed[1:])))
|
classes = append(classes, unquote(strings.TrimSpace(trimmed[1:])))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// An indented, non-list line is a nested map entry belonging to
|
|
||||||
// classes (" roles::base: {}"). An unindented, non-list line is the
|
|
||||||
// next top-level key, so stop.
|
|
||||||
if line[0] != ' ' && line[0] != '\t' {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
name, _, ok := splitKV(trimmed)
|
name, _, ok := splitKV(trimmed)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, 0, fmt.Errorf("unexpected classes entry: %q", line)
|
return nil, 0, fmt.Errorf("unexpected classes entry: %q", line)
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
// Nodes imported from cobbler carry class parameters, which encapi emits as a
|
||||||
|
// nested block under the role name. ausyd1nxvm2020/2021/2022 carry the role
|
||||||
|
// name as its own class parameter, so a line-based parser that treats every
|
||||||
|
// indented line as a class name emits the role twice.
|
||||||
|
func TestParseClassesNestedBlockIsOneClass(t *testing.T) {
|
||||||
|
body := []byte("classes:\n" +
|
||||||
|
" roles::infra::proxy::jumphost:\n" +
|
||||||
|
" roles::infra::proxy::jumphost: '~'\n" +
|
||||||
|
"environment: develop\n" +
|
||||||
|
"parameters: {}\n")
|
||||||
|
|
||||||
|
doc, err := parseCobbler(body)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("parseCobbler returned error: %v", err)
|
||||||
|
}
|
||||||
|
want := []string{"roles::infra::proxy::jumphost"}
|
||||||
|
if len(doc.classes) != len(want) || doc.classes[0] != want[0] {
|
||||||
|
t.Errorf("classes = %q, want %q", doc.classes, want)
|
||||||
|
}
|
||||||
|
if doc.environment != "develop" {
|
||||||
|
t.Errorf("environment = %q, want develop", doc.environment)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The same shape with ordinary class parameters: the parameter keys are values
|
||||||
|
// of the class, never classes themselves.
|
||||||
|
func TestParseClassesNestedParamsAreNotClasses(t *testing.T) {
|
||||||
|
body := []byte("classes:\n" +
|
||||||
|
" roles::base:\n" +
|
||||||
|
" listen_port: 8140\n" +
|
||||||
|
" tls: true\n" +
|
||||||
|
" roles::infra::dns::master: {}\n" +
|
||||||
|
"environment: production\n" +
|
||||||
|
"parameters: {}\n")
|
||||||
|
|
||||||
|
doc, err := parseCobbler(body)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("parseCobbler returned error: %v", err)
|
||||||
|
}
|
||||||
|
want := []string{"roles::base", "roles::infra::dns::master"}
|
||||||
|
if len(doc.classes) != len(want) {
|
||||||
|
t.Fatalf("classes = %q, want %q", doc.classes, want)
|
||||||
|
}
|
||||||
|
for i := range want {
|
||||||
|
if doc.classes[i] != want[i] {
|
||||||
|
t.Errorf("classes[%d] = %q, want %q", i, doc.classes[i], want[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user