minio/pkg/madmin/parse-kv.go

239 lines
5.0 KiB
Go

/*
* MinIO Cloud Storage, (C) 2019 MinIO, 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 madmin
import (
"bufio"
"bytes"
"fmt"
"strings"
"unicode"
)
// KV - is a shorthand of each key value.
type KV struct {
Key string `json:"key"`
Value string `json:"value"`
}
// KVS - is a shorthand for some wrapper functions
// to operate on list of key values.
type KVS []KV
// Empty - return if kv is empty
func (kvs KVS) Empty() bool {
return len(kvs) == 0
}
// Set sets a value, if not sets a default value.
func (kvs *KVS) Set(key, value string) {
for i, kv := range *kvs {
if kv.Key == key {
(*kvs)[i] = KV{
Key: key,
Value: value,
}
return
}
}
*kvs = append(*kvs, KV{
Key: key,
Value: value,
})
}
// Get - returns the value of a key, if not found returns empty.
func (kvs KVS) Get(key string) string {
v, ok := kvs.Lookup(key)
if ok {
return v
}
return ""
}
// Lookup - lookup a key in a list of KVS
func (kvs KVS) Lookup(key string) (string, bool) {
for _, kv := range kvs {
if kv.Key == key {
return kv.Value, true
}
}
return "", false
}
// Target signifies an individual target
type Target struct {
SubSystem string `json:"subSys"`
KVS KVS `json:"kvs"`
}
// Targets sub-system targets
type Targets []Target
// Standard config keys and values.
const (
EnableKey = "enable"
CommentKey = "comment"
// Enable values
EnableOn = "on"
EnableOff = "off"
)
func (kvs KVS) String() string {
var s strings.Builder
for _, kv := range kvs {
// Do not need to print state which is on.
if kv.Key == EnableKey && kv.Value == EnableOn {
continue
}
if kv.Key == CommentKey && kv.Value == "" {
continue
}
s.WriteString(kv.Key)
s.WriteString(KvSeparator)
spc := HasSpace(kv.Value)
if spc {
s.WriteString(KvDoubleQuote)
}
s.WriteString(kv.Value)
if spc {
s.WriteString(KvDoubleQuote)
}
s.WriteString(KvSpaceSeparator)
}
return s.String()
}
// Count - returns total numbers of target
func (t Targets) Count() int {
return len(t)
}
// HasSpace - returns if given string has space.
func HasSpace(s string) bool {
for _, r := range s {
if unicode.IsSpace(r) {
return true
}
}
return false
}
func (t Targets) String() string {
var s strings.Builder
count := t.Count()
// Print all "on" states entries
for _, targetKV := range t {
kv := targetKV.KVS
count--
s.WriteString(targetKV.SubSystem)
s.WriteString(KvSpaceSeparator)
s.WriteString(kv.String())
if len(t) > 1 && count > 0 {
s.WriteString(KvNewline)
}
}
return s.String()
}
// Constant separators
const (
SubSystemSeparator = `:`
KvSeparator = `=`
KvComment = `#`
KvSpaceSeparator = ` `
KvNewline = "\n"
KvDoubleQuote = `"`
KvSingleQuote = `'`
Default = `_`
)
// SanitizeValue - this function is needed, to trim off single or double quotes, creeping into the values.
func SanitizeValue(v string) string {
v = strings.TrimSuffix(strings.TrimPrefix(strings.TrimSpace(v), KvDoubleQuote), KvDoubleQuote)
return strings.TrimSuffix(strings.TrimPrefix(v, KvSingleQuote), KvSingleQuote)
}
// AddTarget - adds new targets, by parsing the input string s.
func (t *Targets) AddTarget(s string) error {
inputs := strings.SplitN(s, KvSpaceSeparator, 2)
if len(inputs) <= 1 {
return fmt.Errorf("invalid number of arguments '%s'", s)
}
subSystemValue := strings.SplitN(inputs[0], SubSystemSeparator, 2)
if len(subSystemValue) == 0 {
return fmt.Errorf("invalid number of arguments %s", s)
}
var kvs = KVS{}
var prevK string
for _, v := range strings.Fields(inputs[1]) {
kv := strings.SplitN(v, KvSeparator, 2)
if len(kv) == 0 {
continue
}
if len(kv) == 1 && prevK != "" {
value := strings.Join([]string{
kvs.Get(prevK),
SanitizeValue(kv[0]),
}, KvSpaceSeparator)
kvs.Set(prevK, value)
continue
}
if len(kv) == 2 {
prevK = kv[0]
kvs.Set(prevK, SanitizeValue(kv[1]))
continue
}
return fmt.Errorf("value for key '%s' cannot be empty", kv[0])
}
for i := range *t {
if (*t)[i].SubSystem == inputs[0] {
(*t)[i] = Target{
SubSystem: inputs[0],
KVS: kvs,
}
return nil
}
}
*t = append(*t, Target{
SubSystem: inputs[0],
KVS: kvs,
})
return nil
}
// ParseSubSysTarget - parse sub-system target
func ParseSubSysTarget(buf []byte) (Targets, error) {
var targets Targets
bio := bufio.NewScanner(bytes.NewReader(buf))
for bio.Scan() {
if err := targets.AddTarget(bio.Text()); err != nil {
return nil, err
}
}
if err := bio.Err(); err != nil {
return nil, err
}
return targets, nil
}