From d92db198d1a1069d50fe904db5a9141fa9d5850b Mon Sep 17 00:00:00 2001 From: Harshavardhana Date: Thu, 16 Apr 2020 17:43:14 -0700 Subject: [PATCH] Add target parsing code for config (#9375) This code is helper for mcs project --- cmd/config/config.go | 30 +----- cmd/config/config_test.go | 4 +- pkg/madmin/config-help-commands.go | 9 ++ pkg/madmin/parse-kv.go | 142 +++++++++++++++++++++++++++++ 4 files changed, 155 insertions(+), 30 deletions(-) diff --git a/cmd/config/config.go b/cmd/config/config.go index 293673c23..aafb6f9b2 100644 --- a/cmd/config/config.go +++ b/cmd/config/config.go @@ -22,7 +22,6 @@ import ( "fmt" "io" "regexp" - "sort" "strings" "github.com/minio/minio-go/v6/pkg/set" @@ -580,33 +579,6 @@ func (c Config) Clone() Config { return cp } -// Converts an input string of form "k1=v1 k2=v2" into fields -// of ["k1=v1", "k2=v2"], the tokenization of each `k=v` -// happens with the right number of input keys, if keys -// input is empty returned value is empty slice as well. -func kvFields(input string, keys []string) []string { - var valueIndexes []int - for _, key := range keys { - i := strings.Index(input, key+KvSeparator) - if i == -1 { - continue - } - valueIndexes = append(valueIndexes, i) - } - - sort.Ints(valueIndexes) - var fields = make([]string, len(valueIndexes)) - for i := range valueIndexes { - j := i + 1 - if j < len(valueIndexes) { - fields[i] = strings.TrimSpace(input[valueIndexes[i]:valueIndexes[j]]) - } else { - fields[i] = strings.TrimSpace(input[valueIndexes[i]:]) - } - } - return fields -} - // SetKVS - set specific key values per sub-system. func (c Config) SetKVS(s string, defaultKVS map[string]KVS) error { if len(s) == 0 { @@ -635,7 +607,7 @@ func (c Config) SetKVS(s string, defaultKVS map[string]KVS) error { tgt = subSystemValue[1] } - fields := kvFields(inputs[1], defaultKVS[subSys].Keys()) + fields := madmin.KvFields(inputs[1], defaultKVS[subSys].Keys()) if len(fields) == 0 { return Errorf("sub-system '%s' cannot have empty keys", subSys) } diff --git a/cmd/config/config_test.go b/cmd/config/config_test.go index 162a3d9dd..c88619095 100644 --- a/cmd/config/config_test.go +++ b/cmd/config/config_test.go @@ -19,6 +19,8 @@ package config import ( "testing" + + "github.com/minio/minio/pkg/madmin" ) func TestKVFields(t *testing.T) { @@ -90,7 +92,7 @@ func TestKVFields(t *testing.T) { for _, test := range tests { test := test t.Run("", func(t *testing.T) { - gotFields := kvFields(test.input, test.keys) + gotFields := madmin.KvFields(test.input, test.keys) if len(gotFields) != len(test.expectedFields) { t.Errorf("Expected keys %d, found %d", len(test.expectedFields), len(gotFields)) } diff --git a/pkg/madmin/config-help-commands.go b/pkg/madmin/config-help-commands.go index 4d64c35b8..bfcc61ef7 100644 --- a/pkg/madmin/config-help-commands.go +++ b/pkg/madmin/config-help-commands.go @@ -45,6 +45,15 @@ type HelpKV struct { // HelpKVS - implement order of keys help messages. type HelpKVS []HelpKV +// Keys returns help keys +func (h Help) Keys() []string { + var keys []string + for _, kh := range h.KeysHelp { + keys = append(keys, kh.Key) + } + return keys +} + // HelpConfigKV - return help for a given sub-system. func (adm *AdminClient) HelpConfigKV(ctx context.Context, subSys, key string, envOnly bool) (Help, error) { v := url.Values{} diff --git a/pkg/madmin/parse-kv.go b/pkg/madmin/parse-kv.go index 136674b62..982559980 100644 --- a/pkg/madmin/parse-kv.go +++ b/pkg/madmin/parse-kv.go @@ -18,10 +18,71 @@ package madmin import ( + "bufio" + "bytes" + "fmt" + "sort" "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"` +} + // Standard config keys and values. const ( EnableKey = "enable" @@ -60,3 +121,84 @@ func SanitizeValue(v string) string { v = strings.TrimSuffix(strings.TrimPrefix(strings.TrimSpace(v), KvDoubleQuote), KvDoubleQuote) return strings.TrimSuffix(strings.TrimPrefix(v, KvSingleQuote), KvSingleQuote) } + +// KvFields - converts an input string of form "k1=v1 k2=v2" into +// fields of ["k1=v1", "k2=v2"], the tokenization of each `k=v` +// happens with the right number of input keys, if keys +// input is empty returned value is empty slice as well. +func KvFields(input string, keys []string) []string { + var valueIndexes []int + for _, key := range keys { + i := strings.Index(input, key+KvSeparator) + if i == -1 { + continue + } + valueIndexes = append(valueIndexes, i) + } + + sort.Ints(valueIndexes) + var fields = make([]string, len(valueIndexes)) + for i := range valueIndexes { + j := i + 1 + if j < len(valueIndexes) { + fields[i] = strings.TrimSpace(input[valueIndexes[i]:valueIndexes[j]]) + } else { + fields[i] = strings.TrimSpace(input[valueIndexes[i]:]) + } + } + return fields +} + +// ParseTarget - adds new targets, by parsing the input string s. +func ParseTarget(s string, help Help) (*Target, error) { + inputs := strings.SplitN(s, KvSpaceSeparator, 2) + if len(inputs) <= 1 { + return nil, fmt.Errorf("invalid number of arguments '%s'", s) + } + + subSystemValue := strings.SplitN(inputs[0], SubSystemSeparator, 2) + if len(subSystemValue) == 0 { + return nil, fmt.Errorf("invalid number of arguments %s", s) + } + + if help.SubSys != subSystemValue[0] { + return nil, fmt.Errorf("unknown sub-system %s", subSystemValue[0]) + } + + var kvs = KVS{} + var prevK string + for _, v := range KvFields(inputs[1], help.Keys()) { + 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 nil, fmt.Errorf("value for key '%s' cannot be empty", kv[0]) + } + + return &Target{ + SubSystem: inputs[0], + KVS: kvs, + }, nil +} + +// ParseSubSysTarget - parse a sub-system target +func ParseSubSysTarget(buf []byte, help Help) (target *Target, err error) { + bio := bufio.NewScanner(bytes.NewReader(buf)) + if bio.Scan() { + return ParseTarget(bio.Text(), help) + } + return nil, bio.Err() +}