internal/loader/typed_setting.go (132 lines of code) (raw):
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
package loader
import (
acpv1 "azappconfig/provider/api/v1"
"encoding/json"
"fmt"
"regexp"
"strings"
"gopkg.in/yaml.v3"
"k8s.io/klog/v2"
)
func createTypedSettings(rawSettings *RawSettings, dataOptions *acpv1.ConfigMapDataOptions) (map[string]string, error) {
result := make(map[string]string)
if len(rawSettings.KeyValueSettings) == 0 && rawSettings.FeatureFlagSettings == nil {
return result, nil
}
if dataOptions == nil || dataOptions.Type == acpv1.Default || dataOptions.Type == acpv1.Properties {
tmpSettings := make(map[string]string)
for k, v := range rawSettings.KeyValueSettings {
if v != nil {
tmpSettings[k] = *v
} else {
klog.Warningf("value of the setting '%s' is null, just ignore it", k)
}
}
if dataOptions == nil || dataOptions.Type == acpv1.Default {
return tmpSettings, nil
}
result[dataOptions.Key] = marshalProperties(tmpSettings)
return result, nil
}
// dataOptions.Type = json or yaml
root := &Tree{}
parsedSettings := make(map[string]interface{})
for k, v := range rawSettings.KeyValueSettings {
var value interface{} = nil
if v != nil {
value = *v
if rawSettings.IsJsonContentTypeMap[k] {
var out interface{}
err := json.Unmarshal([]byte(*v), &out)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal json value for key '%s': %s", k, err.Error())
}
value = out
}
}
if dataOptions.Separator != nil {
root.insert(strings.Split(k, string(*dataOptions.Separator)), value)
} else {
parsedSettings[k] = value
}
}
if dataOptions.Separator != nil {
parsedSettings = root.build()
}
if rawSettings.FeatureFlagSettings != nil {
// FeatureManagementSection = {"feature_management": { "feature_flags" : [{...}, {...}]}
parsedSettings[FeatureManagementSectionName] = rawSettings.FeatureFlagSettings
}
if typedStr, err := marshalJsonYaml(parsedSettings, dataOptions); err != nil {
return nil, err
} else {
result[dataOptions.Key] = typedStr
}
return result, nil
}
func marshalProperties(settings map[string]string) string {
stringBuilder := strings.Builder{}
separator := "\n"
if settings != nil {
i := 0
for k, v := range settings {
stringBuilder.WriteString(fmt.Sprintf("%s=%s", k, v))
if i < len(settings)-1 {
stringBuilder.WriteString(separator)
}
i++
}
}
return stringBuilder.String()
}
func isJsonContentType(contentType *string) bool {
if contentType == nil {
return false
}
contentTypeStr := strings.ToLower(strings.Trim(*contentType, " "))
matched, _ := regexp.MatchString("^application\\/(?:[^\\/]+\\+)?json(;.*)?$", contentTypeStr)
return matched
}
// Used for scenarios related to feature flag refresh.
// Currently, only supports feature flag settings in JSON/YAML formats.
func unmarshalConfigMap(existingConfigMapSetting *map[string]string, dataOptions *acpv1.ConfigMapDataOptions) (map[string]interface{}, map[string]interface{}, error) {
var existingConfigMapData string
var ok bool
if existingConfigMapData, ok = (*existingConfigMapSetting)[dataOptions.Key]; !ok {
return nil, nil, fmt.Errorf("failed to get data for key '%s' from configMap", dataOptions.Key)
}
keyValueSettings := make(map[string]interface{})
var featureFlagSection map[string]interface{}
switch dataOptions.Type {
case acpv1.Yaml:
err := yaml.Unmarshal([]byte(existingConfigMapData), &keyValueSettings)
if err != nil {
return nil, nil, err
}
case acpv1.Json:
err := json.Unmarshal([]byte(existingConfigMapData), &keyValueSettings)
if err != nil {
return nil, nil, err
}
default:
return nil, nil, fmt.Errorf("unsupported data type '%s', only supports json/yaml for now", dataOptions.Type)
}
if featureFlagSection, ok = keyValueSettings[FeatureManagementSectionName].(map[string]interface{}); !ok {
return nil, nil, fmt.Errorf("failed to get feature flags from configMap data '%s'", existingConfigMapData)
}
delete(keyValueSettings, FeatureManagementSectionName)
return featureFlagSection, keyValueSettings, nil
}
func marshalJsonYaml(settings map[string]interface{}, dataOptions *acpv1.ConfigMapDataOptions) (string, error) {
switch dataOptions.Type {
case acpv1.Yaml:
yamlStr, err := yaml.Marshal(settings)
if err != nil {
return "", fmt.Errorf("failed to marshal key-values to yaml: %s", err.Error())
}
return string(yamlStr), nil
case acpv1.Json:
jsonStr, err := json.Marshal(settings)
if err != nil {
return "", fmt.Errorf("failed to marshal key-values to json: %s", err.Error())
}
return string(jsonStr), nil
}
return "", nil
}