mirror of
https://github.com/lukaszraczylo/harness-sync.git
synced 2026-10-12 00:45:22 +00:00
67 lines
2.0 KiB
Go
67 lines
2.0 KiB
Go
// Package secrets provides ${VAR} substitution for canonical configs.
|
|
package secrets
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
)
|
|
|
|
// Lookup resolves a placeholder name to a value. Returns false if missing.
|
|
type Lookup func(name string) (string, bool)
|
|
|
|
// OSEnv resolves via os.LookupEnv. Returns (value, true) when the variable
|
|
// is set, otherwise (placeholder, true) so the literal ${NAME} reaches the
|
|
// downstream config unchanged.
|
|
//
|
|
// Rationale: many harness MCP configs reference variables that the harness
|
|
// itself injects at MCP launch time (e.g. ${CLAUDE_PROJECT} for claude-code,
|
|
// ${GITHUB_TOKEN} when the user keeps it out of their login shell). Aborting
|
|
// the apply for an unset variable would block valid configurations, so unset
|
|
// names are passed through. Real secrets the user wants resolved at apply
|
|
// time must be exported in the process environment.
|
|
func OSEnv(name string) (string, bool) {
|
|
if v, ok := os.LookupEnv(name); ok {
|
|
return v, true
|
|
}
|
|
return "${" + name + "}", true
|
|
}
|
|
|
|
// StrictOSEnv resolves via os.LookupEnv and reports false for unset
|
|
// variables, causing Substitute to return an error. Use when an empty
|
|
// resolution is unacceptable (e.g. dedicated secrets pipeline).
|
|
func StrictOSEnv(name string) (string, bool) {
|
|
return os.LookupEnv(name)
|
|
}
|
|
|
|
// Substitute replaces ${NAME} placeholders in s using lookup. Escape with $$.
|
|
// Missing keys produce an error so secrets are never silently empty.
|
|
func Substitute(s string, lookup Lookup) (string, error) {
|
|
var b strings.Builder
|
|
i := 0
|
|
for i < len(s) {
|
|
if i+1 < len(s) && s[i] == '$' && s[i+1] == '$' {
|
|
b.WriteByte('$')
|
|
i += 2
|
|
continue
|
|
}
|
|
if i+1 < len(s) && s[i] == '$' && s[i+1] == '{' {
|
|
end := strings.IndexByte(s[i+2:], '}')
|
|
if end == -1 {
|
|
return "", fmt.Errorf("unterminated placeholder at offset %d", i)
|
|
}
|
|
name := s[i+2 : i+2+end]
|
|
val, ok := lookup(name)
|
|
if !ok {
|
|
return "", fmt.Errorf("missing env var %q", name)
|
|
}
|
|
b.WriteString(val)
|
|
i += 2 + end + 1
|
|
continue
|
|
}
|
|
b.WriteByte(s[i])
|
|
i++
|
|
}
|
|
return b.String(), nil
|
|
}
|