From b6cd50603fda83ddf3baf59aa06c52ad5b0f0fa2 Mon Sep 17 00:00:00 2001 From: Geoff Greer Date: Fri, 6 Mar 2026 15:51:06 -0800 Subject: [PATCH] Config: Add support for environment variable expansion. --- pkg/bsql/config.go | 44 ++++++++++++++++++++++++++++++++++++-- pkg/connector/connector.go | 2 +- 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/pkg/bsql/config.go b/pkg/bsql/config.go index caa7ff5a..adc3f67f 100644 --- a/pkg/bsql/config.go +++ b/pkg/bsql/config.go @@ -5,6 +5,9 @@ import ( "errors" "fmt" "os" + "regexp" + "slices" + "strings" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" @@ -500,15 +503,52 @@ func Parse(data []byte) (*Config, error) { return config, nil } +type LookupFunc func(key string) (string, bool) + +func expandEnvironmentVariables(_ context.Context, data []byte, lookup LookupFunc) ([]byte, error) { + var missingVars []string + envVarRegex := regexp.MustCompile(`\${([^}]+)}`) + + dataStr := envVarRegex.ReplaceAllStringFunc(string(data), func(match string) string { + envVar := match[2 : len(match)-1] + if envVal, exists := lookup(envVar); exists { + if strings.ContainsAny(envVal, "\n\r") { + // Quote multi-line values so they remain a single YAML scalar. + escaped := strings.ReplaceAll(envVal, `\`, `\\`) + escaped = strings.ReplaceAll(escaped, `"`, `\"`) + escaped = strings.ReplaceAll(escaped, "\n", `\n`) + escaped = strings.ReplaceAll(escaped, "\r", `\r`) + return `"` + escaped + `"` + } + return envVal + } + if !slices.Contains(missingVars, envVar) { + missingVars = append(missingVars, envVar) + } + return match + }) + + if len(missingVars) > 0 { + return nil, fmt.Errorf("missing environment variables: %s", strings.Join(missingVars, ", ")) + } + + return []byte(dataStr), nil +} + // LoadConfigFromFile reads a YAML configuration file from the given path and parses its content into a Config struct. -func LoadConfigFromFile(path string) (*Config, error) { +func LoadConfigFromFile(ctx context.Context, path string) (*Config, error) { data, err := os.ReadFile(path) if err != nil { return nil, err } + // First pass: expand environment variables in string nodes + replacedData, err := expandEnvironmentVariables(ctx, data, os.LookupEnv) + if err != nil { + return nil, fmt.Errorf("failed to expand environment variables: %w", err) + } config := &Config{} - err = yaml.Unmarshal(data, config) + err = yaml.Unmarshal(replacedData, config) if err != nil { return nil, err } diff --git a/pkg/connector/connector.go b/pkg/connector/connector.go index 4a9b6131..0d963d1f 100644 --- a/pkg/connector/connector.go +++ b/pkg/connector/connector.go @@ -101,7 +101,7 @@ func (c *Connector) Validate(ctx context.Context) (annotations.Annotations, erro // New returns a new instance of the connector. func New(ctx context.Context, configFilePath string) (*Connector, error) { - c, err := bsql.LoadConfigFromFile(configFilePath) + c, err := bsql.LoadConfigFromFile(ctx, configFilePath) if err != nil { return nil, err }