Move facade to the root package
This commit is contained in:
179
secret.go
179
secret.go
@@ -1,153 +1,52 @@
|
||||
package configkit
|
||||
|
||||
import "gitea.maximumdirect.net/eric/ConfigKit/internal/document"
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"strconv"
|
||||
|
||||
type secretDependency struct {
|
||||
target Path
|
||||
dependency Path
|
||||
"gitea.maximumdirect.net/eric/ConfigKit/internal/engine"
|
||||
)
|
||||
|
||||
// Secret is an opaque reference to an environment variable. Its name is public
|
||||
// configuration; its resolved value is available only through Value.
|
||||
type Secret struct {
|
||||
name, value string
|
||||
specified, resolved bool
|
||||
}
|
||||
|
||||
func classifySecretPaths(schema *schemaDescriptor, predicate func(Path) bool, metadata Metadata, final *document.Node, applications []historyApplication, projections ...*document.Node) map[string]bool {
|
||||
paths := make(map[string]Path)
|
||||
finalPaths := make(map[string]*document.Node)
|
||||
indexDocument(final, Path{}, finalPaths)
|
||||
for pointer := range finalPaths {
|
||||
addSecretPath(paths, pointer)
|
||||
}
|
||||
for _, application := range applications {
|
||||
applicationPaths := make(map[string]*document.Node)
|
||||
indexDocument(application.value, application.path, applicationPaths)
|
||||
for pointer := range applicationPaths {
|
||||
addSecretPath(paths, pointer)
|
||||
}
|
||||
}
|
||||
var dependencies []secretDependency
|
||||
for pointer, history := range metadata.history {
|
||||
addSecretPath(paths, pointer)
|
||||
for _, change := range history {
|
||||
paths[change.Path.String()] = change.Path.clone()
|
||||
for _, dependency := range change.Dependencies {
|
||||
paths[dependency.String()] = dependency.clone()
|
||||
dependencies = append(dependencies, secretDependency{
|
||||
target: change.Path.clone(), dependency: dependency.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
// SecretEnv constructs an unresolved environment-backed secret reference.
|
||||
func SecretEnv(name string) Secret { return Secret{name: name, specified: true} }
|
||||
|
||||
secret := make(map[string]bool)
|
||||
for _, tree := range append(projections, final) {
|
||||
projected := make(map[string]*document.Node)
|
||||
indexDocument(tree, Path{}, projected)
|
||||
for pointer, node := range projected {
|
||||
addSecretPath(paths, pointer)
|
||||
if node.Sensitive {
|
||||
secret[pointer] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
for pointer, path := range paths {
|
||||
if root, ok := schemaSecretRoot(schema, path); ok {
|
||||
secret[root.String()] = true
|
||||
}
|
||||
if predicate != nil && predicate(path.clone()) {
|
||||
secret[pointer] = true
|
||||
}
|
||||
}
|
||||
for {
|
||||
changed := false
|
||||
for _, dependency := range dependencies {
|
||||
if secretIntersectsPath(paths, secret, dependency.dependency) && !secret[dependency.target.String()] {
|
||||
secret[dependency.target.String()] = true
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
if !changed {
|
||||
return secret
|
||||
}
|
||||
// EnvName returns the public environment-variable name.
|
||||
func (s Secret) EnvName() string { return s.name }
|
||||
|
||||
// Value returns the resolved credential, or an empty string when unresolved.
|
||||
func (s Secret) Value() string {
|
||||
if !s.resolved {
|
||||
return ""
|
||||
}
|
||||
return s.value
|
||||
}
|
||||
|
||||
func addSecretPath(paths map[string]Path, pointer string) {
|
||||
if path, err := ParsePath(pointer); err == nil {
|
||||
paths[pointer] = path
|
||||
}
|
||||
// IsResolved reports whether this reference resolved to a nonempty value during its configuration load.
|
||||
func (s Secret) IsResolved() bool { return s.resolved }
|
||||
|
||||
// Format renders only the public reference name for every formatting verb.
|
||||
func (s Secret) Format(state fmt.State, _ rune) {
|
||||
_, _ = io.WriteString(state, "configkit.SecretEnv("+strconv.Quote(s.name)+")")
|
||||
}
|
||||
|
||||
func secretIntersectsPath(paths map[string]Path, secret map[string]bool, candidate Path) bool {
|
||||
for pointer := range secret {
|
||||
classified, ok := paths[pointer]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if pathContains(classified, candidate) || pathContains(candidate, classified) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
// MarshalJSON emits only the public environment-variable name.
|
||||
func (s Secret) MarshalJSON() ([]byte, error) { return json.Marshal(s.name) }
|
||||
|
||||
func schemaSecretRoot(schema *schemaDescriptor, path Path) (Path, bool) {
|
||||
if schema == nil || schema.root == nil || !path.valid() {
|
||||
return Path{}, false
|
||||
}
|
||||
current := schema.root
|
||||
walked := Path{}
|
||||
for _, segment := range path.segments {
|
||||
walked.segments = append(walked.segments, segment)
|
||||
for current.kind == descriptorPointer {
|
||||
current = current.element
|
||||
}
|
||||
switch current.kind {
|
||||
case descriptorStruct:
|
||||
if segment.kind == pathSegmentIndex {
|
||||
return Path{}, false
|
||||
}
|
||||
position, ok := current.fieldByName[segment.key]
|
||||
var field *fieldDescriptor
|
||||
if ok {
|
||||
field = ¤t.fields[position]
|
||||
} else {
|
||||
field = current.inlineMap
|
||||
}
|
||||
if field == nil {
|
||||
return Path{}, false
|
||||
}
|
||||
if field.secret {
|
||||
return walked.clone(), true
|
||||
}
|
||||
if field.inlineMap {
|
||||
current = field.descriptor.element
|
||||
} else {
|
||||
current = field.descriptor
|
||||
}
|
||||
case descriptorMap:
|
||||
if segment.kind == pathSegmentIndex {
|
||||
return Path{}, false
|
||||
}
|
||||
current = current.element
|
||||
case descriptorSlice, descriptorArray:
|
||||
if _, ok := schemaIndex(segment); !ok {
|
||||
return Path{}, false
|
||||
}
|
||||
current = current.element
|
||||
case descriptorDynamic:
|
||||
return Path{}, false
|
||||
default:
|
||||
return Path{}, false
|
||||
}
|
||||
}
|
||||
return Path{}, false
|
||||
}
|
||||
|
||||
// Dynamic type replacement can change classification even when projected text
|
||||
// is equal. Validators must preserve this inspection metadata as well as values.
|
||||
func projectedSensitivity(root *document.Node) map[string]Path {
|
||||
result := make(map[string]Path)
|
||||
walkDocument(root, Path{}, func(path Path, node *document.Node) {
|
||||
if node.Sensitive {
|
||||
result[path.String()] = path.clone()
|
||||
}
|
||||
})
|
||||
return result
|
||||
func secretBinding() engine.SecretBinding {
|
||||
return engine.SecretBinding{Type: reflect.TypeFor[Secret](), Read: func(value reflect.Value) engine.SecretState {
|
||||
secret := value.Interface().(Secret)
|
||||
return engine.SecretState{Name: secret.name, Value: secret.value, Specified: secret.specified, Resolved: secret.resolved}
|
||||
}, Make: func(state engine.SecretState) reflect.Value {
|
||||
return reflect.ValueOf(Secret{name: state.Name, value: state.Value, specified: state.Specified, resolved: state.Resolved})
|
||||
}}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user