* 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>
169 lines
5.3 KiB
Go
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
|
|
}
|