diff --git a/asconfig/as_getconfig.go b/asconfig/as_getconfig.go index ce68818..40ec179 100644 --- a/asconfig/as_getconfig.go +++ b/asconfig/as_getconfig.go @@ -4,6 +4,7 @@ import ( "fmt" "strings" + sets "github.com/deckarep/golang-set/v2" "github.com/go-logr/logr" aero "github.com/aerospike/aerospike-client-go/v7" @@ -13,44 +14,54 @@ import ( ) // GetASConfig returns the value of the given path from the aerospike config from given host. -func GetASConfig(path *string, conn *deployment.ASConn, aerospikePolicy *aero.ClientPolicy) ( - confToReturn interface{}, err error) { +func GetASConfig(paths []string, conn *deployment.ASConn, aerospikePolicy *aero.ClientPolicy) ( + confToReturn map[string]interface{}, err error) { h := aero.Host{ Name: conn.AerospikeHostName, Port: conn.AerospikePort, TLSName: conn.AerospikeTLSName, } asinfo := info.NewAsInfo(conn.Log, &h, aerospikePolicy) + ctxs := sets.NewSet[string]() + + for _, path := range paths { + ctx := ContextKey(path) + ctxs.Add(ctx) - var ctxs []string - if path != nil { // Get the corresponding sets info also if context is namespace. - ctxs = []string{ContextKey(*path)} - if ctxs[0] == info.ConfigNamespaceContext { - ctxs = append(ctxs, info.ConfigSetContext) + if ctx == info.ConfigNamespaceContext { + ctxs.Add(info.ConfigSetContext) } } - conf, err := asinfo.GetAsConfig(ctxs...) + conf, err := asinfo.GetAsConfig(ctxs.ToSlice()...) if err != nil { conn.Log.Error(err, "failed to get asconfig") return nil, err } - if path == nil { + if len(paths) == 0 { return conf, nil } - confToReturn = conf[ctxs[0]] - if confToReturn == nil { - conn.Log.Info("Config is nil", "context", ctxs[0]) - return nil, nil - } + confToReturn = make(map[string]interface{}) - confToReturn, err = traverseConfig(conn.Log, confToReturn, *path, ctxs[0]) - if err != nil { - conn.Log.Error(err, "failed to traverse config") - return nil, err + for _, path := range paths { + ctx := ContextKey(path) + + ctxConf := conf[ctx] + if ctxConf == nil { + conn.Log.Info("Config is nil", "context", ctxConf) + return nil, nil + } + + ctxConf, err = traverseConfig(conn.Log, ctxConf, path, ctx) + if err != nil { + conn.Log.Error(err, "failed to traverse config") + return nil, err + } + + confToReturn[path] = ctxConf } return confToReturn, nil diff --git a/asconfig/as_setconfig.go b/asconfig/as_setconfig.go index bcec498..1c0fb38 100644 --- a/asconfig/as_setconfig.go +++ b/asconfig/as_setconfig.go @@ -5,10 +5,11 @@ import ( "fmt" "strings" + "github.com/go-logr/logr" + aero "github.com/aerospike/aerospike-client-go/v7" "github.com/aerospike/aerospike-management-lib/deployment" "github.com/aerospike/aerospike-management-lib/info" - "github.com/go-logr/logr" ) const ( @@ -22,8 +23,8 @@ const ( // convertValueToString converts the value of a config to a string. // only string type can be used to populate set-config commands with values. -func convertValueToString(v1 map[Operation]interface{}) (map[Operation][]string, error) { - valueMap := make(map[Operation][]string) +func convertValueToString(v1 map[OpType]interface{}) (map[OpType][]string, error) { + valueMap := make(map[OpType][]string) for k, v := range v1 { values := make([]string, 0) @@ -54,7 +55,7 @@ func convertValueToString(v1 map[Operation]interface{}) (map[Operation][]string, } // createSetConfigServiceCmdList creates set-config commands for service context. -func createSetConfigServiceCmdList(tokens []string, operationValueMap map[Operation][]string) []string { +func createSetConfigServiceCmdList(tokens []string, operationValueMap map[OpType][]string) []string { val := operationValueMap[Update] cmdList := make([]string, 0, len(val)) cmd := cmdSetConfigService @@ -74,7 +75,7 @@ func createSetConfigServiceCmdList(tokens []string, operationValueMap map[Operat } // createSetConfigNetworkCmdList creates set-config commands for network context. -func createSetConfigNetworkCmdList(tokens []string, operationValueMap map[Operation][]string) []string { +func createSetConfigNetworkCmdList(tokens []string, operationValueMap map[OpType][]string) []string { val := operationValueMap[Update] cmdList := make([]string, 0, len(val)) cmd := cmdSetConfigNetwork @@ -94,7 +95,7 @@ func createSetConfigNetworkCmdList(tokens []string, operationValueMap map[Operat } // createSetConfigSecurityCmdList creates set-config commands for security context. -func createSetConfigSecurityCmdList(tokens []string, operationValueMap map[Operation][]string) []string { +func createSetConfigSecurityCmdList(tokens []string, operationValueMap map[OpType][]string) []string { cmdList := make([]string, 0, len(operationValueMap)) cmd := cmdSetConfigSecurity @@ -178,7 +179,7 @@ func createSetConfigSecurityCmdList(tokens []string, operationValueMap map[Opera } // createSetConfigNamespaceCmdList creates set-config commands for namespace context. -func createSetConfigNamespaceCmdList(tokens []string, operationValueMap map[Operation][]string) []string { +func createSetConfigNamespaceCmdList(tokens []string, operationValueMap map[OpType][]string) []string { val := operationValueMap[Update] cmdList := make([]string, 0, len(val)) cmd := cmdSetConfigNamespace @@ -221,7 +222,7 @@ func createSetConfigNamespaceCmdList(tokens []string, operationValueMap map[Oper } // createLogSetCmdList creates log-set commands for logging context. -func createLogSetCmdList(tokens []string, operationValueMap map[Operation][]string, +func createLogSetCmdList(tokens []string, operationValueMap map[OpType][]string, conn deployment.ASConnInterface, aerospikePolicy *aero.ClientPolicy) ([]string, error) { val := operationValueMap[Update] cmdList := make([]string, 0, len(val)) @@ -254,7 +255,7 @@ func createLogSetCmdList(tokens []string, operationValueMap map[Operation][]stri } // createSetConfigXDRCmdList creates set-config commands for XDR context. -func createSetConfigXDRCmdList(tokens []string, operationValueMap map[Operation][]string) []string { +func createSetConfigXDRCmdList(tokens []string, operationValueMap map[OpType][]string) []string { cmdList := make([]string, 0, len(operationValueMap)) cmd := cmdSetConfigXDR prevToken := "" @@ -464,3 +465,104 @@ func rearrangeConfigMap(log logr.Logger, configMap DynamicConfigMap) []string { return finalList } + +func ValidConfigOperations() []OpType { + return []OpType{Add, Update, Remove} +} + +func (o OpType) Validate() error { + switch o { + case Add, Update, Remove: + return nil + } + + return fmt.Errorf("invalid operation type: %s, Valid operations: %v", o, ValidConfigOperations()) +} + +type ConfigOperation struct { + Operation OpType `json:"op"` + Context string `json:"context"` + Config string `json:"config"` + Value string `json:"value,omitempty"` +} + +func (p ConfigOperation) Validate() error { + return p.Operation.Validate() +} + +func CreateConfigSetCmdsUsingPatch( + configMap map[string]interface{}, conn *deployment.ASConn, aerospikePolicy *aero.ClientPolicy, version string, +) ([]string, error) { + conf, err := NewMapAsConfig(conn.Log, configMap) + if err != nil { + return nil, err + } + + flatConf := conf.GetFlatMap() + asConfChange := make(DynamicConfigMap) + + for k, v := range *flatConf { + if strings.HasSuffix(k, sep+KeyName) || strings.HasSuffix(k, sep+keyIndex) { + // skip namespace, dc, etc names + continue + } + + if ok, _ := isListField(k); ok { + // Ignore these fields as these operations are not update operations + //TODO: Should we through an error if these fields are present in the configMap patch? + continue + } + + valueMap := make(map[OpType]interface{}) + valueMap[Update] = v + asConfChange[k] = valueMap + } + + isDynamic, err := IsAllDynamicConfig(conn.Log, asConfChange, version) + if err != nil { + return nil, err + } + + if !isDynamic { + return nil, fmt.Errorf("static field has been changed, cannot change config dynamically") + } + + return CreateSetConfigCmdList(logr.Logger{}, asConfChange, conn, aerospikePolicy) +} + +func CreateConfigSetCmdsUsingOperation( + confOp ConfigOperation, conn *deployment.ASConn, aerospikePolicy *aero.ClientPolicy, version string, +) ([]string, error) { + if err := confOp.Validate(); err != nil { + return nil, err + } + // Context: security.log, Config:report-data-op, Value:test set + // Map: security.log.report-data-op:map[remove:"test set"] + path := confOp.Context + sep + confOp.Config + value := confOp.Value + + if confOp.Config == KeyName { + if confOp.Operation == Update { + return nil, fmt.Errorf("cannot update name field") + } + // Context: namespaces, Config: name, value: ns1 + // Map: namespaces.{testMem}.name:map[remove:testMem] + path = confOp.Context + sep + string(SectionNameStartChar) + value + string(SectionNameEndChar) + sep + KeyName + } + + asConfChange := make(DynamicConfigMap) + valueMap := make(map[OpType]interface{}) + valueMap[confOp.Operation] = value + asConfChange[path] = valueMap + + isDynamic, err := IsAllDynamicConfig(conn.Log, asConfChange, version) + if err != nil { + return nil, err + } + + if !isDynamic { + return nil, fmt.Errorf("static field has been changed, cannot change config dynamically") + } + + return CreateSetConfigCmdList(logr.Logger{}, asConfChange, conn, aerospikePolicy) +} diff --git a/asconfig/confdiff.go b/asconfig/confdiff.go index 1e58d51..c27c60f 100644 --- a/asconfig/confdiff.go +++ b/asconfig/confdiff.go @@ -157,7 +157,7 @@ func handleMissingSection(log logr.Logger, key string, desired, current Conf, d // Whole section which has "name" as key is not present in current // If token is under "{}", then it is a named section if _, okay := current[nameKeyPath]; ReCurlyBraces.MatchString(token) && !okay { - operationValueMap := make(map[Operation]interface{}) + operationValueMap := make(map[OpType]interface{}) if desiredToActual { if _, updated := d[key]; !updated { @@ -193,7 +193,7 @@ func handlePartialMissingSection(desiredKey, ver string, current Conf, d Dynamic continue } - operationValueMap := make(map[Operation]interface{}) + operationValueMap := make(map[OpType]interface{}) // If removed subsection is of type slice, then there is no default values to be set. // eg. current = security.log.report-data-op: []string{test} // desired = security: {} @@ -218,7 +218,7 @@ func handlePartialMissingSection(desiredKey, ver string, current Conf, d Dynamic } func handleSliceFields(key string, desired Conf, d DynamicConfigMap, desiredToActual bool) { - operationValueMap := make(map[Operation]interface{}) + operationValueMap := make(map[OpType]interface{}) if reflect.ValueOf(desired[key]).Kind() == reflect.Slice { if desiredToActual { @@ -234,7 +234,7 @@ func handleSliceFields(key string, desired Conf, d DynamicConfigMap, desiredToAc } func handleValueDiff(key string, desiredValue, currentValue interface{}, d DynamicConfigMap) { - operationValueMap := make(map[Operation]interface{}) + operationValueMap := make(map[OpType]interface{}) if reflect.ValueOf(desiredValue).Kind() == reflect.Slice { currentSet := sets.NewSet[string]() @@ -366,7 +366,7 @@ func ConfDiff( return nil, err } - valueMap := make(map[Operation]interface{}) + valueMap := make(map[OpType]interface{}) valueMap[Update] = getDefaultValue(defaultMap, removedConfigKey) diffs[removedConfigKey] = valueMap } diff --git a/asconfig/constants.go b/asconfig/constants.go index 80462b4..3dd2cb3 100644 --- a/asconfig/constants.go +++ b/asconfig/constants.go @@ -1,6 +1,6 @@ package asconfig -type Operation string +type OpType string // All the aerospike config related keys const ( @@ -33,8 +33,8 @@ const ( equal = "=" colon = ":" - // Enum values for Operation - Add Operation = "add" - Remove Operation = "remove" - Update Operation = "update" + // Enum values for OpType + Add OpType = "add" + Remove OpType = "remove" + Update OpType = "update" ) diff --git a/asconfig/schema.go b/asconfig/schema.go index 99232d8..cf72718 100644 --- a/asconfig/schema.go +++ b/asconfig/schema.go @@ -195,7 +195,7 @@ func IsAllDynamicConfig(log logr.Logger, configMap DynamicConfigMap, version str // IsDynamicConfig returns true if the given field is dynamically configured. func IsDynamicConfig(log logr.Logger, dynamic sets.Set[string], conf string, - valueMap map[Operation]interface{}) bool { + valueMap map[OpType]interface{}) bool { tokens := SplitKey(log, conf, sep) baseKey := tokens[len(tokens)-1] context := tokens[0] diff --git a/asconfig/utils.go b/asconfig/utils.go index 3fc2e18..f1f85bf 100644 --- a/asconfig/utils.go +++ b/asconfig/utils.go @@ -1136,4 +1136,4 @@ var ReCurlyBraces = regexp.MustCompile(`^\{.*\}$`) // DynamicConfigMap is a map of config flatten keys and their operations and values // for eg: "xdr.dcs.{DC3}.node-address-ports": {Remove: []string{"1.1.2.1 3000"}} -type DynamicConfigMap map[string]map[Operation]interface{} +type DynamicConfigMap map[string]map[OpType]interface{}