Files
kubesphere/vendor/github.com/open-policy-agent/opa/internal/config/config.go
KubeSphere CI Bot 447a51f08b feat: kubesphere 4.0 (#6115)
* feat: kubesphere 4.0

Signed-off-by: ci-bot <ci-bot@kubesphere.io>

* feat: kubesphere 4.0

Signed-off-by: ci-bot <ci-bot@kubesphere.io>

---------

Signed-off-by: ci-bot <ci-bot@kubesphere.io>
Co-authored-by: ks-ci-bot <ks-ci-bot@example.com>
Co-authored-by: joyceliu <joyceliu@yunify.com>
2024-09-06 11:05:52 +08:00

169 lines
5.3 KiB
Go

// Copyright 2020 The OPA Authors. All rights reserved.
// Use of this source code is governed by an Apache2
// license that can be found in the LICENSE file.
// Package config implements helper functions to parse OPA's configuration.
package config
import (
"encoding/json"
"fmt"
"os"
"regexp"
"strings"
"sigs.k8s.io/yaml"
"github.com/open-policy-agent/opa/internal/strvals"
"github.com/open-policy-agent/opa/keys"
"github.com/open-policy-agent/opa/logging"
"github.com/open-policy-agent/opa/plugins/rest"
"github.com/open-policy-agent/opa/tracing"
"github.com/open-policy-agent/opa/util"
)
// ServiceOptions stores the options passed to ParseServicesConfig
type ServiceOptions struct {
Raw json.RawMessage
AuthPlugin rest.AuthPluginLookupFunc
Keys map[string]*keys.Config
Logger logging.Logger
DistributedTacingOpts tracing.Options
}
// ParseServicesConfig returns a set of named service clients. The service
// clients can be specified either as an array or as a map. Some systems (e.g.,
// Helm) do not have proper support for configuration values nested under
// arrays, so just support both here.
func ParseServicesConfig(opts ServiceOptions) (map[string]rest.Client, error) {
services := map[string]rest.Client{}
var arr []json.RawMessage
var obj map[string]json.RawMessage
if err := util.Unmarshal(opts.Raw, &arr); err == nil {
for _, s := range arr {
client, err := rest.New(s, opts.Keys, rest.AuthPluginLookup(opts.AuthPlugin), rest.Logger(opts.Logger), rest.DistributedTracingOpts(opts.DistributedTacingOpts))
if err != nil {
return nil, err
}
services[client.Service()] = client
}
} else if util.Unmarshal(opts.Raw, &obj) == nil {
for k := range obj {
client, err := rest.New(obj[k], opts.Keys, rest.Name(k), rest.AuthPluginLookup(opts.AuthPlugin), rest.Logger(opts.Logger), rest.DistributedTracingOpts(opts.DistributedTacingOpts))
if err != nil {
return nil, err
}
services[client.Service()] = client
}
} else {
// Return error from array decode as that is the default format.
return nil, err
}
return services, nil
}
// Load implements configuration file loading. The supplied config file will be
// read from disk (if specified) and overrides will be applied. If no config file is
// specified, the overrides can still be applied to an empty config.
func Load(configFile string, overrides []string, overrideFiles []string) ([]byte, error) {
baseConf := map[string]interface{}{}
// User specified config file
if configFile != "" {
var bytes []byte
var err error
bytes, err = os.ReadFile(configFile)
if err != nil {
return nil, err
}
processedConf := subEnvVars(string(bytes))
if err := yaml.Unmarshal([]byte(processedConf), &baseConf); err != nil {
return nil, fmt.Errorf("failed to parse %s: %s", configFile, err)
}
}
overrideConf := map[string]interface{}{}
// User specified a config override via --set
for _, override := range overrides {
processedOverride := subEnvVars(override)
if err := strvals.ParseInto(processedOverride, overrideConf); err != nil {
return nil, fmt.Errorf("failed parsing --set data: %s", err)
}
}
// User specified a config override value via --set-file
for _, override := range overrideFiles {
reader := func(rs []rune) (interface{}, error) {
bytes, err := os.ReadFile(string(rs))
value := strings.TrimSpace(string(bytes))
return value, err
}
if err := strvals.ParseIntoFile(override, overrideConf, reader); err != nil {
return nil, fmt.Errorf("failed parsing --set-file data: %s", err)
}
}
// Merge together base config file and overrides, prefer the overrides
conf := mergeValues(baseConf, overrideConf)
// Take the patched config and marshal back to YAML
return yaml.Marshal(conf)
}
// regex looking for ${...} notation strings
var envRegex = regexp.MustCompile(`(?U:\${.*})`)
// subEnvVars will look for any environment variables in the passed in string
// with the syntax of ${VAR_NAME} and replace that string with ENV[VAR_NAME]
func subEnvVars(s string) string {
updatedConfig := envRegex.ReplaceAllStringFunc(s, func(s string) string {
// Trim off the '${' and '}'
if len(s) <= 3 {
// This should never happen..
return ""
}
varName := s[2 : len(s)-1]
// Lookup the variable in the environment. We play by
// bash rules.. if its undefined we'll treat it as an
// empty string instead of raising an error.
return os.Getenv(varName)
})
return updatedConfig
}
// mergeValues will merge source and destination map, preferring values from the source map
func mergeValues(dest map[string]interface{}, src map[string]interface{}) map[string]interface{} {
for k, v := range src {
// If the key doesn't exist already, then just set the key to that value
if _, exists := dest[k]; !exists {
dest[k] = v
continue
}
nextMap, ok := v.(map[string]interface{})
// If it isn't another map, overwrite the value
if !ok {
dest[k] = v
continue
}
// Edge case: If the key exists in the destination, but isn't a map
destMap, isMap := dest[k].(map[string]interface{})
// If the source map has a map for this key, prefer it
if !isMap {
dest[k] = v
continue
}
// If we got to this point, it is a map in both, so merge them
dest[k] = mergeValues(destMap, nextMap)
}
return dest
}