polardbxoperator/pkg/util/ini/ini.go

232 lines
5.2 KiB
Go

/*
Copyright 2021 Alibaba Group Holding Limited.
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 ini
import (
"bytes"
"io"
"reflect"
"strings"
"gopkg.in/ini.v1"
)
// IsIniKeyBooleanValue returns true when key is a bool value, aka. without value.
func IsIniKeyBooleanValue(k *ini.Key) bool {
if k == nil {
return false
}
return reflect.Indirect(reflect.ValueOf(k)).FieldByName("isBooleanType").Bool()
}
// CanonicalizeMyCnfFile replaces every dash-linked variable with underscore_linked.
// It will silently overwrite the existing duplicate if allowDuplicate is true.
func CanonicalizeMyCnfFile(f *ini.File, allowDuplicate bool) error {
for _, sec := range f.Sections() {
for _, key := range sec.Keys() {
canonicalName := strings.ReplaceAll(key.Name(), "-", "_")
// Keep the last "-", which has special meaning.
if key.Name()[len(canonicalName)-1] == '-' {
canonicalName = canonicalName[:len(canonicalName)-1] + "-"
}
// Delete the old key and insert a new key if name is changed.
if canonicalName != key.Name() {
sec.DeleteKey(key.Name())
if IsIniKeyBooleanValue(key) {
_, err := sec.NewBooleanKey(canonicalName)
if err != nil {
return err
}
} else {
_, err := sec.NewKey(canonicalName, key.Value())
if err != nil {
return err
}
}
}
}
}
return nil
}
func MoveDefaultSectionInto(f *ini.File, section string) error {
if len(section) == 0 || section == ini.DefaultSection {
return nil
}
src := f.Section(ini.DefaultSection)
dst := f.Section(section)
for _, key := range src.Keys() {
if IsIniKeyBooleanValue(key) {
dstKey, err := dst.NewBooleanKey(key.Name())
if err != nil {
return err
}
dstKey.Comment = key.Comment
} else {
dstKey, err := dst.NewKey(key.Name(), key.Value())
if err != nil {
return err
}
dstKey.Comment = key.Comment
}
}
f.DeleteSection(ini.DefaultSection)
return nil
}
func parseMyCnfFile(r io.Reader) (*ini.File, error) {
f, err := ini.LoadSources(ini.LoadOptions{
AllowBooleanKeys: true,
AllowPythonMultilineValues: true,
SpaceBeforeInlineComment: true,
PreserveSurroundedQuote: true,
IgnoreInlineComment: true,
}, r)
if err != nil {
return nil, err
}
if err := CanonicalizeMyCnfFile(f, false); err != nil {
return nil, err
}
return f, nil
}
func ParseMyCnfTemplateFile(r io.Reader) (*ini.File, error) {
f, err := parseMyCnfFile(r)
if err != nil {
return nil, err
}
f.DeleteSection(ini.DefaultSection)
return f, nil
}
func ParseMyCnfOverlayFile(r io.Reader) (*ini.File, error) {
f, err := parseMyCnfFile(r)
if err != nil {
return nil, err
}
// All we care about is the mysqld section.
err = MoveDefaultSectionInto(f, "mysqld")
if err != nil {
return nil, err
}
return f, nil
}
// Patch patches the origin ini file with the given patch. Key with different values
// will be overwritten and new keys will be inserted. Patch extends the update-insert
// only behaviour with another delete semantic: patch a bool key with "-" suffix will
// try to delete the key without "-" in the original file. For example,
//
// origin:
// [mysqld]
// core-file
//
// patch:
// [mysqld]
// core-file-
//
// After the Patch, the 'core-file' in origin will be removed.
func Patch(origin, patch *ini.File) (*ini.File, error) {
if patch == nil {
return origin, nil
}
for _, section := range patch.Sections() {
sectionName := section.Name()
dst := origin.Section(sectionName)
for _, key := range section.Keys() {
if IsIniKeyBooleanValue(key) {
// Handle the special case that key ends with '-',
// it means delete the original key in template
if strings.HasSuffix(key.Name(), "-") {
keyName := key.Name()[:len(key.Name())-1]
if len(keyName) != 0 && dst.HasKey(keyName) {
dst.DeleteKey(keyName)
}
continue
}
if dst.HasKey(key.Name()) {
continue
}
dstKey, err := dst.NewBooleanKey(key.Name())
if err != nil {
return nil, err
}
dstKey.Comment = key.Comment
} else {
dstKey := dst.Key(key.Name())
dstKey.SetValue(key.Value())
dstKey.Comment = key.Comment
}
}
}
return origin, nil
}
func DiffMyCnfFile(old, new *ini.File, section string) map[string]string {
newSec := new.Section(section)
oldSec := old.Section(section)
diff := make(map[string]string)
for _, k := range newSec.Keys() {
// Also ignore boolean key
if IsIniKeyBooleanValue(k) {
continue
}
if !oldSec.HasKey(k.Name()) {
diff[k.Name()] = k.Value()
} else {
oldK := oldSec.Key(k.Name())
if k.Value() != oldK.Value() {
diff[k.Name()] = k.Value()
}
}
}
return diff
}
func ToString(f *ini.File) string {
if f == nil {
return ""
}
w := &bytes.Buffer{}
if _, err := f.WriteTo(w); err != nil {
return ""
}
return w.String()
}