pkg/ndbconfig/configparser/config_parser.go (147 lines of code) (raw):
// Copyright (c) 2020, 2022, Oracle and/or its affiliates.
//
// Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/
package configparser
import (
"bufio"
"fmt"
"reflect"
"strings"
"github.com/mysql/ndb-operator/config/debug"
)
const (
headerSection = "header"
)
// Section is a map of all config variable names and
// their value extracted from a particular section.
type Section map[string]string
// GetValue extracts the value of the configParam from the section
func (s Section) GetValue(configParam string) (value string, exists bool) {
value, exists = s[strings.ToLower(configParam)]
return value, exists
}
// SetValue sets the value of the configParam in the Section
func (s Section) SetValue(configParam string, value string) {
s[strings.ToLower(configParam)] = value
}
// ConfigIni holds the parsed management configuration. It is a map
// of section names, and an array of all Sections with that name.
type ConfigIni map[string][]Section
// GetAllSections returns all the Sections with the sectionName.
func (ci ConfigIni) GetAllSections(sectionName string) []Section {
return ci[strings.ToLower(sectionName)]
}
// GetSection returns the Section with the sectionName.
// If multiple sections exist with the given sectionName, the method will panic.
func (ci ConfigIni) GetSection(sectionName string) Section {
grp := ci.GetAllSections(sectionName)
switch len(grp) {
case 0:
// no Sections exist
return nil
case 1:
return grp[0]
default:
// Wrong usage : multiple Sections exist with the same
// name, and the method doesn't know which one to return.
panic("GetSection : multiple Sections exist with the sectionName")
}
}
// GetValueFromSection extracts the config value of the key from the requested
// section. If multiple sections exist with the sectionName, the method will panic.
func (ci ConfigIni) GetValueFromSection(sectionName string, key string) (value string) {
section := ci.GetSection(sectionName)
if section != nil {
value, _ = section.GetValue(key)
}
return value
}
// GetNumberOfSections returns the number of sections with the given sectionName.
func (ci ConfigIni) GetNumberOfSections(sectionName string) int {
return len(ci.GetAllSections(sectionName))
}
func (ci ConfigIni) addSection(sectionName string) Section {
sectionNameInLower := strings.ToLower(sectionName)
grp := ci[sectionNameInLower]
if sectionName == headerSection && len(grp) == 1 {
// header group should have only one section, and it already exists
return grp[0]
}
if grp == nil {
// Section group doesn't exist - create one
grp = []Section{}
ci[sectionNameInLower] = grp
}
// Add the new section and return
newSection := make(Section)
ci[sectionNameInLower] = append(grp, newSection)
return newSection
}
// IsEqual returns if the given ConfigIni is equal to ci
func (ci ConfigIni) IsEqual(ci2 ConfigIni) bool {
if len(ci) != len(ci2) {
// One of the config has extra section(s) (or) one of them is nil
return false
}
// Loop all sections of c1 and c2, and check if there
// is any difference between them.
for sectionName, sections1 := range ci {
sections2, sectionExists := ci2[sectionName]
if !sectionExists || len(sections1) != len(sections2) {
// Either section with sectionName doesn't exist in c2 (or)
// The number of sections under sectionName is not equal
return false
}
// Compare the sections
matched := make([]bool, len(sections1))
for _, section1 := range sections1 {
// Loop the sections in sections2 and look for a match for section1
var matchFoundForSection1 bool
for i, section2 := range sections2 {
if !matched[i] && reflect.DeepEqual(section1, section2) {
// Found a match. Mark it as matched to avoid other
// sections getting matched against the same section again.
matchFoundForSection1 = true
matched[i] = true
break
}
}
if !matchFoundForSection1 {
// No match found for Section1
return false
}
}
}
return true
}
// ParseString parses the config string into a ConfigIni object
func ParseString(configStr string) (ConfigIni, error) {
c := make(ConfigIni)
var lineNo int
var currentSection Section
// Parse the config string using a bufio.Scanner
scanner := bufio.NewScanner(strings.NewReader(configStr))
for scanner.Scan() {
// Process one line at a time
line := strings.TrimSpace(scanner.Text())
lineNo++
if line == "" {
// Empty line
continue
}
// Check if this is a comment.
// Any key value pair declared inside the first comment block
// on top of the config, before any section is declared, will
// be collected under the "header" section.
var isComment bool
if line[0] == ';' || line[0] == '#' {
isComment = true
line = strings.TrimSpace(strings.TrimLeft(line, ";#"))
if line == "" {
// No more text in comment
continue
}
if len(c) == 0 {
// No sections have been declared yet and this is the first comment block.
// Collect any key=value pairs under the section "header"
currentSection = c.addSection(headerSection)
} else if len(c) == 1 && c.GetSection(headerSection) != nil {
// First comment block and headerSection is being read
currentSection = c.GetSection(headerSection)
} else {
// We can ignore this comment
continue
}
} else if line[0] == '[' {
// A section starts
if line[len(line)-1] != ']' {
return nil, fmt.Errorf("Incomplete section name at line %d : %s", lineNo, line)
}
// create/load the section
sectionName := line[1 : len(line)-1]
currentSection = c.addSection(sectionName)
continue
}
if currentSection == nil {
// No section is currently being read
return nil, fmt.Errorf("Non-empty line without section at line %d : %s", lineNo, line)
}
// Split the line to look for a key value pair
tokens := strings.SplitN(line, "=", 2)
if len(tokens) != 2 || tokens[1] == "" {
if isComment {
// Ignore errors in a comment
continue
}
return nil, fmt.Errorf("Format error at line %d : %s", lineNo, line)
}
// store the config key value pair
currentSection.SetValue(tokens[0], tokens[1])
}
if err := scanner.Err(); err != nil {
// Error occurred during config scan
return nil, err
}
return c, nil
}
// ConfigEqual returns if the given two config strings are equal
func ConfigEqual(config1 string, config2 string) bool {
// Parse both the configs
c1, err := ParseString(config1)
if err != nil {
debug.Panic(fmt.Sprintf("ConfigEqual failed : %s", err))
return false
}
c2, err := ParseString(config2)
if err != nil {
debug.Panic(fmt.Sprintf("ConfigEqual failed : %s", err))
return false
}
return c1.IsEqual(c2)
}