Composer can load configuration values defined as map from ENV. Previously, when loading the configuration from ENV, the whole map would get overridden, not just values defined in the ENV. This is however not intended and not consistent with how loading configuration from file works. Adjust the configuration loading from ENV and adjust the unit test accordingly. Signed-off-by: Tomáš Hozza <thozza@redhat.com>
246 lines
6.9 KiB
Go
246 lines
6.9 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"reflect"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/BurntSushi/toml"
|
|
)
|
|
|
|
type ComposerConfigFile struct {
|
|
Koji KojiAPIConfig `toml:"koji"`
|
|
Worker WorkerAPIConfig `toml:"worker"`
|
|
WeldrAPI WeldrAPIConfig `toml:"weldr_api"`
|
|
DistroAliases map[string]string `toml:"distro_aliases" env:"DISTRO_ALIASES"`
|
|
LogLevel string `toml:"log_level"`
|
|
LogFormat string `toml:"log_format"`
|
|
DNFJson string `toml:"dnf-json"`
|
|
SplunkHost string `env:"SPLUNK_HEC_HOST"`
|
|
SplunkPort string `env:"SPLUNK_HEC_PORT"`
|
|
SplunkToken string `env:"SPLUNK_HEC_TOKEN"`
|
|
GlitchTipDSN string `env:"GLITCHTIP_DSN"`
|
|
}
|
|
|
|
type KojiAPIConfig struct {
|
|
AllowedDomains []string `toml:"allowed_domains"`
|
|
CA string `toml:"ca"`
|
|
EnableTLS bool `toml:"enable_tls"`
|
|
EnableMTLS bool `toml:"enable_mtls"`
|
|
EnableJWT bool `toml:"enable_jwt"`
|
|
JWTKeysURLs []string `toml:"jwt_keys_urls"`
|
|
JWTKeysCA string `toml:"jwt_ca_file"`
|
|
JWTACLFile string `toml:"jwt_acl_file"`
|
|
JWTTenantProviderFields []string `toml:"jwt_tenant_provider_fields"`
|
|
}
|
|
|
|
type WorkerAPIConfig struct {
|
|
AllowedDomains []string `toml:"allowed_domains"`
|
|
CA string `toml:"ca"`
|
|
RequestJobTimeout string `toml:"request_job_timeout"`
|
|
BasePath string `toml:"base_path"`
|
|
EnableArtifacts bool `toml:"enable_artifacts"`
|
|
PGHost string `toml:"pg_host" env:"PGHOST"`
|
|
PGPort string `toml:"pg_port" env:"PGPORT"`
|
|
PGDatabase string `toml:"pg_database" env:"PGDATABASE"`
|
|
PGUser string `toml:"pg_user" env:"PGUSER"`
|
|
PGPassword string `toml:"pg_password" env:"PGPASSWORD"`
|
|
PGSSLMode string `toml:"pg_ssl_mode" env:"PGSSLMODE"`
|
|
PGMaxConns int `toml:"pg_max_conns" env:"PGMAXCONNS"`
|
|
EnableTLS bool `toml:"enable_tls"`
|
|
EnableMTLS bool `toml:"enable_mtls"`
|
|
EnableJWT bool `toml:"enable_jwt"`
|
|
JWTKeysURLs []string `toml:"jwt_keys_urls"`
|
|
JWTKeysCA string `toml:"jwt_ca_file"`
|
|
JWTACLFile string `toml:"jwt_acl_file"`
|
|
JWTTenantProviderFields []string `toml:"jwt_tenant_provider_fields"`
|
|
WorkerHeartbeatTimeout string `toml:"worker_heartbeat_timeout"`
|
|
}
|
|
|
|
type WeldrAPIConfig struct {
|
|
DistroConfigs map[string]WeldrDistroConfig `toml:"distros"`
|
|
}
|
|
|
|
type WeldrDistroConfig struct {
|
|
ImageTypeDenyList []string `toml:"image_type_denylist"`
|
|
}
|
|
|
|
// weldrDistrosImageTypeDenyList returns a map of distro-specific Image Type
|
|
// deny lists for Weldr API.
|
|
func (c *ComposerConfigFile) weldrDistrosImageTypeDenyList() map[string][]string {
|
|
distrosImageTypeDenyList := map[string][]string{}
|
|
|
|
for distro, distroConfig := range c.WeldrAPI.DistroConfigs {
|
|
if distroConfig.ImageTypeDenyList != nil {
|
|
distrosImageTypeDenyList[distro] = append([]string{}, distroConfig.ImageTypeDenyList...)
|
|
}
|
|
}
|
|
|
|
return distrosImageTypeDenyList
|
|
}
|
|
|
|
// GetDefaultConfig returns the default configuration of osbuild-composer
|
|
// Defaults:
|
|
// - 'azure-rhui', 'azure-sap-rhui', 'ec2', 'ec2-ha', 'ec2-sap' image types on 'rhel-*'
|
|
// are not exposed via Weldr API
|
|
func GetDefaultConfig() *ComposerConfigFile {
|
|
return &ComposerConfigFile{
|
|
Koji: KojiAPIConfig{
|
|
EnableTLS: true,
|
|
EnableMTLS: true,
|
|
EnableJWT: false,
|
|
},
|
|
Worker: WorkerAPIConfig{
|
|
RequestJobTimeout: "0",
|
|
BasePath: "/api/worker/v1",
|
|
EnableArtifacts: true,
|
|
EnableTLS: true,
|
|
EnableMTLS: true,
|
|
EnableJWT: false,
|
|
WorkerHeartbeatTimeout: "1h",
|
|
},
|
|
WeldrAPI: WeldrAPIConfig{
|
|
map[string]WeldrDistroConfig{
|
|
"rhel-*": {
|
|
ImageTypeDenyList: []string{
|
|
"azure-eap7-rhui",
|
|
"azure-rhui",
|
|
"azure-sap-rhui",
|
|
"ec2",
|
|
"ec2-ha",
|
|
"ec2-sap",
|
|
"gce-rhui",
|
|
},
|
|
},
|
|
"fedora-*": {
|
|
ImageTypeDenyList: []string{
|
|
"iot-bootable-container",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
DistroAliases: map[string]string{
|
|
"rhel-7": "rhel-7.9",
|
|
"rhel-8": "rhel-8.10",
|
|
"rhel-9": "rhel-9.5",
|
|
"rhel-10": "rhel-10.0",
|
|
},
|
|
LogLevel: "info",
|
|
LogFormat: "journal",
|
|
DNFJson: "/usr/libexec/osbuild-depsolve-dnf",
|
|
}
|
|
}
|
|
|
|
func LoadConfig(name string) (*ComposerConfigFile, error) {
|
|
c := GetDefaultConfig()
|
|
_, err := toml.DecodeFile(name, c)
|
|
if err != nil && !os.IsNotExist(err) {
|
|
return nil, err
|
|
}
|
|
err = loadConfigFromEnv(c)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return c, nil
|
|
}
|
|
|
|
// envStrToMap converts map string to map[string]string
|
|
func envStrToMap(s string) (map[string]string, error) {
|
|
result := map[string]string{}
|
|
if s == "" {
|
|
return result, nil
|
|
}
|
|
|
|
parts := strings.Split(s, ",")
|
|
for _, part := range parts {
|
|
keyValue := strings.Split(part, "=")
|
|
if len(keyValue) != 2 {
|
|
return nil, fmt.Errorf("Invalid key-value pair in map string: %s", part)
|
|
}
|
|
result[keyValue[0]] = keyValue[1]
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func loadConfigFromEnv(intf interface{}) error {
|
|
t := reflect.TypeOf(intf).Elem()
|
|
v := reflect.ValueOf(intf).Elem()
|
|
|
|
for i := 0; i < v.NumField(); i++ {
|
|
fieldT := t.Field(i)
|
|
fieldV := v.Field(i)
|
|
kind := fieldV.Kind()
|
|
|
|
switch kind {
|
|
case reflect.String:
|
|
key, ok := fieldT.Tag.Lookup("env")
|
|
if !ok {
|
|
continue
|
|
}
|
|
confV, ok := os.LookupEnv(key)
|
|
if !ok {
|
|
continue
|
|
}
|
|
fieldV.SetString(confV)
|
|
case reflect.Int:
|
|
key, ok := fieldT.Tag.Lookup("env")
|
|
if !ok {
|
|
continue
|
|
}
|
|
confV, ok := os.LookupEnv(key)
|
|
if !ok {
|
|
continue
|
|
}
|
|
value, err := strconv.ParseInt(confV, 10, 64)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fieldV.SetInt(value)
|
|
case reflect.Bool:
|
|
// no-op
|
|
continue
|
|
case reflect.Slice:
|
|
// no-op
|
|
continue
|
|
case reflect.Map:
|
|
key, ok := fieldT.Tag.Lookup("env")
|
|
if !ok {
|
|
continue
|
|
}
|
|
// handle only map[string]string
|
|
if fieldV.Type().Key().Kind() != reflect.String || fieldV.Type().Elem().Kind() != reflect.String {
|
|
return fmt.Errorf("Unsupported map type for loading from ENV: %s", kind)
|
|
}
|
|
confV, ok := os.LookupEnv(key)
|
|
if !ok {
|
|
continue
|
|
}
|
|
value, err := envStrToMap(confV)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// Don't override the whole map, just update the keys that are present in the env.
|
|
// This is consistent with how loading config from the file works.
|
|
for k, v := range value {
|
|
fieldV.SetMapIndex(reflect.ValueOf(k), reflect.ValueOf(v))
|
|
}
|
|
case reflect.Struct:
|
|
err := loadConfigFromEnv(fieldV.Addr().Interface())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
default:
|
|
return fmt.Errorf("Unsupported type: %s", kind)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func DumpConfig(c ComposerConfigFile, w io.Writer) error {
|
|
// sensor sensitive fields
|
|
c.Worker.PGPassword = ""
|
|
return toml.NewEncoder(w).Encode(c)
|
|
}
|