Share secret traversal between resolution and validation
This commit is contained in:
@@ -49,81 +49,45 @@ func resolveSecretReferences(root reflect.Value, schema *schemaDescriptor, input
|
||||
lookup = func(string) (string, bool) { return "", false }
|
||||
}
|
||||
locations := &secretReferenceLocationResolver{input: input}
|
||||
resolveSecretValue(root, schema.root, schema.secrets, Path{}, false, locations, lookup, &result)
|
||||
walkSecretSlots(root, schema.root, secretSlotsWritable, func(value reflect.Value, path Path, optional bool) {
|
||||
resolveSecretReference(value, schema.secrets, path, optional, locations, lookup, &result)
|
||||
})
|
||||
result.diagnostics = canonicalDiagnostics(result.diagnostics)
|
||||
return result
|
||||
}
|
||||
|
||||
func resolveSecretValue(value reflect.Value, descriptor *typeDescriptor, secrets SecretBinding, path Path, optional bool, locations *secretReferenceLocationResolver, lookup secretLookup, result *secretResolutionResult) {
|
||||
if descriptor.kind == descriptorPointer {
|
||||
if value.IsNil() {
|
||||
return
|
||||
func resolveSecretReference(value reflect.Value, secrets SecretBinding, path Path, optional bool, locations *secretReferenceLocationResolver, lookup secretLookup, result *secretResolutionResult) {
|
||||
reference := secrets.Read(value)
|
||||
name := reference.Name
|
||||
if name == "" {
|
||||
code := CodeSecretRequired
|
||||
message := "required secret reference is absent"
|
||||
if optional || reference.Specified {
|
||||
code = CodeSecretInvalidName
|
||||
message = "secret environment name is empty"
|
||||
}
|
||||
resolveSecretValue(value.Elem(), descriptor.element, secrets, path, descriptor.element.kind == descriptorSecret, locations, lookup, result)
|
||||
result.diagnostics = append(result.diagnostics, locations.diagnostic(code, path, message))
|
||||
return
|
||||
}
|
||||
|
||||
switch descriptor.kind {
|
||||
case descriptorSecret:
|
||||
reference := secrets.Read(value)
|
||||
name := reference.Name
|
||||
if name == "" {
|
||||
code := CodeSecretRequired
|
||||
message := "required secret reference is absent"
|
||||
if optional || reference.Specified {
|
||||
code = CodeSecretInvalidName
|
||||
message = "secret environment name is empty"
|
||||
}
|
||||
result.diagnostics = append(result.diagnostics, locations.diagnostic(code, path, message))
|
||||
return
|
||||
}
|
||||
if !validEnvironmentName(name) {
|
||||
result.diagnostics = append(result.diagnostics, locations.diagnostic(CodeSecretInvalidName, path, "secret environment name is invalid"))
|
||||
return
|
||||
}
|
||||
found, exists := result.lookups[name]
|
||||
if !exists {
|
||||
found.value, found.present = lookup(name)
|
||||
result.lookups[name] = found
|
||||
}
|
||||
if !found.present {
|
||||
result.diagnostics = append(result.diagnostics, locations.diagnostic(CodeSecretUnset, path, "secret environment variable is not set"))
|
||||
return
|
||||
}
|
||||
if found.value == "" {
|
||||
result.diagnostics = append(result.diagnostics, locations.diagnostic(CodeSecretEmpty, path, "secret environment variable is empty"))
|
||||
return
|
||||
}
|
||||
value.Set(secrets.Make(SecretState{Name: name, Value: found.value, Specified: true, Resolved: true}))
|
||||
result.references[path.String()] = name
|
||||
case descriptorStruct:
|
||||
for _, member := range descriptor.members {
|
||||
memberPath := path
|
||||
if !member.inline {
|
||||
memberPath = path.Key(member.name)
|
||||
}
|
||||
resolveSecretValue(value.Field(member.index), member.descriptor, secrets, memberPath, false, locations, lookup, result)
|
||||
}
|
||||
case descriptorMap:
|
||||
if value.IsNil() {
|
||||
return
|
||||
}
|
||||
keys := value.MapKeys()
|
||||
slices.SortFunc(keys, func(left, right reflect.Value) int { return cmp.Compare(left.String(), right.String()) })
|
||||
for _, key := range keys {
|
||||
entry := reflect.New(value.Type().Elem()).Elem()
|
||||
entry.Set(value.MapIndex(key))
|
||||
resolveSecretValue(entry, descriptor.element, secrets, path.Key(key.String()), false, locations, lookup, result)
|
||||
value.SetMapIndex(key, entry)
|
||||
}
|
||||
case descriptorSlice, descriptorArray:
|
||||
if descriptor.kind == descriptorSlice && value.IsNil() {
|
||||
return
|
||||
}
|
||||
for index := 0; index < value.Len(); index++ {
|
||||
resolveSecretValue(value.Index(index), descriptor.element, secrets, path.Index(index), false, locations, lookup, result)
|
||||
}
|
||||
if !validEnvironmentName(name) {
|
||||
result.diagnostics = append(result.diagnostics, locations.diagnostic(CodeSecretInvalidName, path, "secret environment name is invalid"))
|
||||
return
|
||||
}
|
||||
found, exists := result.lookups[name]
|
||||
if !exists {
|
||||
found.value, found.present = lookup(name)
|
||||
result.lookups[name] = found
|
||||
}
|
||||
if !found.present {
|
||||
result.diagnostics = append(result.diagnostics, locations.diagnostic(CodeSecretUnset, path, "secret environment variable is not set"))
|
||||
return
|
||||
}
|
||||
if found.value == "" {
|
||||
result.diagnostics = append(result.diagnostics, locations.diagnostic(CodeSecretEmpty, path, "secret environment variable is empty"))
|
||||
return
|
||||
}
|
||||
value.Set(secrets.Make(SecretState{Name: name, Value: found.value, Specified: true, Resolved: true}))
|
||||
result.references[path.String()] = name
|
||||
}
|
||||
|
||||
func changedSecretStates(root reflect.Value, schema *schemaDescriptor, lookups map[string]secretLookupResult) []Path {
|
||||
@@ -131,50 +95,15 @@ func changedSecretStates(root reflect.Value, schema *schemaDescriptor, lookups m
|
||||
if schema == nil || schema.root == nil {
|
||||
return nil
|
||||
}
|
||||
collectChangedSecretStates(root, schema.root, schema.secrets, Path{}, lookups, &changed)
|
||||
slices.SortFunc(changed, func(left, right Path) int { return cmp.Compare(left.String(), right.String()) })
|
||||
return changed
|
||||
}
|
||||
|
||||
func collectChangedSecretStates(value reflect.Value, descriptor *typeDescriptor, secrets SecretBinding, path Path, lookups map[string]secretLookupResult, changed *[]Path) {
|
||||
if descriptor.kind == descriptorPointer {
|
||||
if !value.IsNil() {
|
||||
collectChangedSecretStates(value.Elem(), descriptor.element, secrets, path, lookups, changed)
|
||||
}
|
||||
return
|
||||
}
|
||||
switch descriptor.kind {
|
||||
case descriptorSecret:
|
||||
reference := secrets.Read(value)
|
||||
walkSecretSlots(root, schema.root, secretSlotsReadOnly, func(value reflect.Value, path Path, _ bool) {
|
||||
reference := schema.secrets.Read(value)
|
||||
expected, ok := lookups[reference.Name]
|
||||
if ok && (!expected.present || expected.value == "" || !reference.Resolved || reference.Value != expected.value) {
|
||||
*changed = append(*changed, path.clone())
|
||||
changed = append(changed, path.clone())
|
||||
}
|
||||
case descriptorStruct:
|
||||
for _, member := range descriptor.members {
|
||||
memberPath := path
|
||||
if !member.inline {
|
||||
memberPath = path.Key(member.name)
|
||||
}
|
||||
collectChangedSecretStates(value.Field(member.index), member.descriptor, secrets, memberPath, lookups, changed)
|
||||
}
|
||||
case descriptorMap:
|
||||
if value.IsNil() {
|
||||
return
|
||||
}
|
||||
keys := value.MapKeys()
|
||||
slices.SortFunc(keys, func(left, right reflect.Value) int { return cmp.Compare(left.String(), right.String()) })
|
||||
for _, key := range keys {
|
||||
collectChangedSecretStates(value.MapIndex(key), descriptor.element, secrets, path.Key(key.String()), lookups, changed)
|
||||
}
|
||||
case descriptorSlice, descriptorArray:
|
||||
if descriptor.kind == descriptorSlice && value.IsNil() {
|
||||
return
|
||||
}
|
||||
for index := 0; index < value.Len(); index++ {
|
||||
collectChangedSecretStates(value.Index(index), descriptor.element, secrets, path.Index(index), lookups, changed)
|
||||
}
|
||||
}
|
||||
})
|
||||
slices.SortFunc(changed, func(left, right Path) int { return cmp.Compare(left.String(), right.String()) })
|
||||
return changed
|
||||
}
|
||||
|
||||
func cloneSecretReferences(values map[string]string) map[string]string {
|
||||
|
||||
Reference in New Issue
Block a user