493 lines
23 KiB
Go
493 lines
23 KiB
Go
package configkit_test
|
|
|
|
import (
|
|
"bytes"
|
|
"io/fs"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
"testing/fstest"
|
|
|
|
configkit "gitea.maximumdirect.net/eric/ConfigKit"
|
|
)
|
|
|
|
type inheritedEntry struct {
|
|
Enabled bool `yaml:"enabled"`
|
|
Count int `yaml:"count"`
|
|
Resource string `yaml:"resource"`
|
|
Settings map[string]map[string]string `yaml:"settings"`
|
|
}
|
|
|
|
type inheritedConfiguration struct {
|
|
Version string `yaml:"version"`
|
|
Entries map[string]inheritedEntry `yaml:"entries"`
|
|
}
|
|
|
|
func TestFacadeInheritancePreservesPresenceOriginsAndHistory(t *testing.T) {
|
|
input := []byte(`entries:
|
|
parent:
|
|
enabled: false
|
|
count: 0
|
|
settings:
|
|
primary: {value: original}
|
|
secondary: {extends: primary}
|
|
child:
|
|
extends: parent
|
|
settings:
|
|
primary: {value: custom}
|
|
`)
|
|
defaultRan := false
|
|
result, err := configkit.Load(configkit.Bytes("config.yml", input), configkit.Options[inheritedConfiguration]{Definition: configkit.Definition[inheritedConfiguration]{
|
|
Resolve: func(config *inheritedConfiguration, resolution *configkit.Resolution) error {
|
|
if !resolution.IsExplicit(configkit.Keys("entries", "child", "enabled")) {
|
|
t.Error("inherited false is not explicit")
|
|
}
|
|
info, found := resolution.InputInfo(configkit.Keys("entries", "child", "enabled"))
|
|
if !found || len(info.Origin.Inheritance) != 1 {
|
|
t.Errorf("resolver inherited origin = %#v, %v", info, found)
|
|
}
|
|
child := config.Entries["child"]
|
|
configkit.Default(resolution, configkit.Keys("entries", "child", "enabled"), &child.Enabled, true, "use fallback")
|
|
defaultRan = child.Enabled
|
|
config.Entries["child"] = child
|
|
return nil
|
|
},
|
|
}})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
child := result.Config.Entries["child"]
|
|
if defaultRan || child.Enabled || child.Count != 0 || child.Settings["secondary"]["value"] != "custom" {
|
|
t.Fatalf("inherited values = %#v, default ran %v", child, defaultRan)
|
|
}
|
|
path := configkit.Keys("entries", "child", "settings", "secondary", "value")
|
|
info, found := result.Metadata().Lookup(path)
|
|
if !found || !info.Explicit || len(info.Origin.Inheritance) != 1 ||
|
|
info.Origin.Inheritance[0].From.String() != "/entries/child/settings/primary/value" ||
|
|
info.Origin.Inheritance[0].To.String() != path.String() {
|
|
t.Fatalf("nested destination origin = %#v, %v", info, found)
|
|
}
|
|
info.Origin.Inheritance[0].Locations[0].SourceID = "mutated"
|
|
info, _ = result.Metadata().Lookup(path)
|
|
if info.Origin.Inheritance[0].Locations[0].SourceID == "mutated" {
|
|
t.Fatal("public inheritance record aliases inspection state")
|
|
}
|
|
if _, found := result.Metadata().Lookup(configkit.Keys("entries", "child", "extends")); found {
|
|
t.Fatal("consumed directive remained effective")
|
|
}
|
|
control, found := result.Explain(configkit.Keys("entries", "child", "extends"))
|
|
if !found || control.Effective.Present || len(control.History) < 2 ||
|
|
control.History[len(control.History)-1].Origin.Operation != configkit.ChangeInheritance {
|
|
t.Fatalf("consumed directive = %#v, %v", control, found)
|
|
}
|
|
explanation, found := result.Explain(path)
|
|
if !found || explanation.Effective.YAML != "custom\n" {
|
|
t.Fatalf("nested explanation = %#v, %v", explanation, found)
|
|
}
|
|
for _, change := range explanation.History {
|
|
if change.Origin.Operation == configkit.ChangeDecoding {
|
|
t.Fatalf("inherited value attributed to decoding: %#v", explanation.History)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFacadeIdentifiedInheritanceResolvesChainsAndNestedCollections(t *testing.T) {
|
|
const secretName = "CONFIGKIT_IDENTIFIED_INHERITANCE_SECRET"
|
|
const canary = "IDENTIFIED-INHERITANCE-SECRET-CANARY"
|
|
t.Setenv(secretName, canary)
|
|
type step struct {
|
|
ID string `yaml:"id"`
|
|
Value string `yaml:"value"`
|
|
}
|
|
type job struct {
|
|
ID string `yaml:"id"`
|
|
Name string `yaml:"name"`
|
|
Credential configkit.Secret `yaml:"credential"`
|
|
Resource string `yaml:"resource"`
|
|
Steps []step `yaml:"steps"`
|
|
Settings map[string]map[string]string `yaml:"settings"`
|
|
}
|
|
type configuration struct {
|
|
Jobs []job `yaml:"jobs"`
|
|
}
|
|
input := []byte(`jobs:
|
|
- id: child
|
|
extends: {id: parent}
|
|
name: child-name
|
|
- id: parent
|
|
extends: {id: base}
|
|
- id: base
|
|
name: base-name
|
|
credential: CONFIGKIT_IDENTIFIED_INHERITANCE_SECRET
|
|
resource: icon.txt
|
|
steps:
|
|
- id: base-step
|
|
value: base-step-value
|
|
- id: second-step
|
|
value: second-step-value
|
|
settings:
|
|
primary: {value: base-setting}
|
|
secondary: {extends: primary}
|
|
`)
|
|
filesystem := configkit.NewFilesystem("identified-bundle", fstest.MapFS{
|
|
"config/identified.yml": &fstest.MapFile{Data: input},
|
|
"config/icon.txt": &fstest.MapFile{Data: []byte("identified-parent-resource")},
|
|
})
|
|
registrations := []configkit.SequenceComposition{
|
|
{Path: configkit.Keys("jobs"), IdentityField: "id"},
|
|
}
|
|
result, err := configkit.Load(filesystem.File("config/identified.yml"), configkit.Options[configuration]{
|
|
Definition: configkit.Definition[configuration]{
|
|
SequenceCompositions: registrations,
|
|
Secret: func(path configkit.Path) bool {
|
|
return path.String() == "/jobs/0/credential" || path.String() == "/jobs/1/credential" || path.String() == "/jobs/2/credential"
|
|
},
|
|
},
|
|
})
|
|
if err != nil {
|
|
if loadError, ok := err.(*configkit.LoadError); ok {
|
|
t.Fatalf("load identified inheritance: %v (%#v)", err, loadError.Diagnostics())
|
|
}
|
|
t.Fatal(err)
|
|
}
|
|
if len(result.Config.Jobs) != 3 || result.Config.Jobs[0].ID != "child" || result.Config.Jobs[1].ID != "parent" || result.Config.Jobs[2].ID != "base" {
|
|
t.Fatalf("inherited job positions and identities = %#v", result.Config.Jobs)
|
|
}
|
|
child := result.Config.Jobs[0]
|
|
if child.Name != "child-name" || child.Credential.Value() != canary || child.Resource != "icon.txt" || len(child.Steps) != 2 {
|
|
t.Fatalf("inherited child = %#v", child)
|
|
}
|
|
if got := []string{child.Steps[0].ID, child.Steps[1].ID}; strings.Join(got, ",") != "base-step,second-step" {
|
|
t.Fatalf("inherited nested steps = %v", got)
|
|
}
|
|
if child.Steps[0].Value != "base-step-value" || child.Steps[1].Value != "second-step-value" || child.Settings["secondary"]["value"] != "base-setting" {
|
|
t.Fatalf("inherited nested collection and mapping = %#v", child)
|
|
}
|
|
credentialPath := configkit.Keys("jobs").Index(0).Key("credential")
|
|
credentialInfo, found := result.Metadata().Lookup(credentialPath)
|
|
if !found || len(credentialInfo.Origin.Inheritance) != 2 {
|
|
t.Fatalf("inherited secret provenance = %#v, %v", credentialInfo, found)
|
|
}
|
|
resourceInfo, found := result.Metadata().Lookup(configkit.Keys("jobs").Index(0).Key("resource"))
|
|
if !found || len(resourceInfo.Origin.Inheritance) != 2 || len(resourceInfo.Origin.Locations) != 1 {
|
|
t.Fatalf("inherited resource provenance = %#v, %v", resourceInfo, found)
|
|
}
|
|
context, found := result.SourceContext(resourceInfo.Origin.Locations[0].SourceID)
|
|
if !found || context.BaseDir != "config" || context.FS == nil {
|
|
t.Fatalf("inherited resource context = %#v, %v", context, found)
|
|
}
|
|
resource, readErr := fs.ReadFile(context.FS, context.BaseDir+"/"+child.Resource)
|
|
if readErr != nil || string(resource) != "identified-parent-resource" {
|
|
t.Fatalf("inherited resource = %q, %v", resource, readErr)
|
|
}
|
|
resourceHistory, found := result.Explain(configkit.Keys("jobs").Index(0).Key("resource"))
|
|
if !found || len(resourceHistory.History) == 0 {
|
|
t.Fatalf("inherited resource history = %#v, %v", resourceHistory, found)
|
|
}
|
|
credentialHistory, found := result.Explain(credentialPath)
|
|
if !found || !credentialHistory.Effective.Redacted {
|
|
t.Fatalf("inherited secret explanation = %#v, %v", credentialHistory, found)
|
|
}
|
|
identityInfo, found := result.Metadata().Lookup(configkit.Keys("jobs").Index(0).Key("id"))
|
|
if !found || !identityInfo.Explicit || len(identityInfo.Origin.Inheritance) != 0 ||
|
|
len(identityInfo.Origin.Locations) != 1 {
|
|
t.Fatalf("child identity origin and presence = %#v, %v", identityInfo, found)
|
|
}
|
|
if _, found := result.Metadata().Lookup(configkit.Keys("jobs").Index(0).Key("extends")); found {
|
|
t.Fatal("identified record relationship remained in effective metadata")
|
|
}
|
|
var rendered bytes.Buffer
|
|
if err := result.WriteEffective(&rendered); err != nil || strings.Contains(rendered.String(), canary) || strings.Contains(rendered.String(), "extends:") {
|
|
t.Fatalf("effective inherited configuration = %q, %v", rendered.String(), err)
|
|
}
|
|
}
|
|
|
|
func TestFacadeIdentifiedInheritanceFailureDoesNotRevealSecretCanary(t *testing.T) {
|
|
const secretName = "CONFIGKIT_IDENTIFIED_INHERITANCE_FAILURE_SECRET"
|
|
const canary = "IDENTIFIED-INHERITANCE-FAILURE-CANARY"
|
|
t.Setenv(secretName, canary)
|
|
type job struct {
|
|
ID string `yaml:"id"`
|
|
Credential configkit.Secret `yaml:"credential"`
|
|
}
|
|
type configuration struct {
|
|
Jobs []job `yaml:"jobs"`
|
|
}
|
|
input := []byte(`jobs:
|
|
- id: child
|
|
extends: {id: missing}
|
|
credential: CONFIGKIT_IDENTIFIED_INHERITANCE_FAILURE_SECRET
|
|
`)
|
|
_, err := configkit.Load(configkit.Bytes("identified-failure.yml", input), configkit.Options[configuration]{
|
|
Definition: configkit.Definition[configuration]{
|
|
SequenceCompositions: []configkit.SequenceComposition{{Path: configkit.Keys("jobs"), IdentityField: "id"}},
|
|
Secret: func(path configkit.Path) bool { return path.String() == "/jobs/0/credential" },
|
|
},
|
|
})
|
|
if err == nil || strings.Contains(err.Error(), canary) {
|
|
t.Fatalf("identified inheritance failure = %v; secret canary must remain undisclosed", err)
|
|
}
|
|
}
|
|
|
|
func TestFacadeLayeredIdentifiedInheritanceUsesSurvivingRecords(t *testing.T) {
|
|
policy := func(mode string) string {
|
|
return "configkit:\n sequences:\n - destination: [jobs]\n key: id\n mode: " + mode + "\n"
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
lower string
|
|
higher string
|
|
wantCode configkit.Code
|
|
wantValue string
|
|
wantSuccess bool
|
|
}{
|
|
{
|
|
name: "upserted parent supplies its final payload",
|
|
lower: policy("upsert") + "jobs: [{id: parent, value: lower}]\n",
|
|
higher: policy("upsert") + "jobs: [{id: parent, value: higher}, {id: child, extends: {id: parent}}]\n",
|
|
wantValue: "higher", wantSuccess: true,
|
|
},
|
|
{
|
|
name: "replacement removes a lower parent before lookup",
|
|
lower: policy("upsert") + "jobs: [{id: parent, value: lower}]\n",
|
|
higher: policy("replace") + "jobs: [{id: child, extends: {id: parent}}]\n",
|
|
wantCode: configkit.CodeInheritanceMissing,
|
|
},
|
|
{
|
|
name: "upserted child without a directive replaces the invalid lower child",
|
|
lower: policy("upsert") + "jobs: [{id: child, extends: {id: missing}, value: lower}]\n",
|
|
higher: policy("upsert") + "jobs: [{id: child, value: higher}]\n",
|
|
wantValue: "higher", wantSuccess: true,
|
|
},
|
|
{
|
|
name: "self-reference cannot resolve through a lower predecessor",
|
|
lower: policy("upsert") + "jobs: [{id: parent, value: lower}]\n",
|
|
higher: policy("upsert") + "jobs: [{id: parent, extends: {id: parent}, value: higher}]\n",
|
|
wantCode: configkit.CodeInheritanceCycle,
|
|
},
|
|
}
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
result, err := configkit.LoadLayered(configkit.Input{Source: configkit.Bytes("main.yml", []byte(test.higher))}, configkit.LayeredOptions[map[string]any]{
|
|
Underlays: []configkit.Underlay{{Inputs: []configkit.Input{{Source: configkit.Bytes("lower.yml", []byte(test.lower))}}}},
|
|
})
|
|
if test.wantSuccess {
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
jobs, ok := result.Config["jobs"].([]any)
|
|
if !ok || len(jobs) == 0 {
|
|
t.Fatalf("assembled jobs = %#v", result.Config["jobs"])
|
|
}
|
|
child, ok := jobs[len(jobs)-1].(map[string]any)
|
|
if !ok || child["value"] != test.wantValue {
|
|
t.Fatalf("surviving record = %#v, want inherited value %q", jobs[len(jobs)-1], test.wantValue)
|
|
}
|
|
return
|
|
}
|
|
loadError, ok := err.(*configkit.LoadError)
|
|
if !ok {
|
|
t.Fatalf("load error = %v, want code %s", err, test.wantCode)
|
|
}
|
|
for _, diagnostic := range loadError.Diagnostics() {
|
|
if diagnostic.Code == test.wantCode {
|
|
return
|
|
}
|
|
}
|
|
t.Fatalf("load diagnostics = %#v, want code %s", loadError.Diagnostics(), test.wantCode)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestFacadeInheritanceLayeredPrecedenceAndSourceContext(t *testing.T) {
|
|
filesystem := configkit.NewFilesystem("bundle", fstest.MapFS{
|
|
"config/main.yml": &fstest.MapFile{Data: []byte("entries: {child: {extends: parent}}\n")},
|
|
"shared/parent.yml": &fstest.MapFile{Data: []byte("entries: {parent: {resource: icon.txt, count: 1}}\n")},
|
|
"shared/flags.yml": &fstest.MapFile{Data: []byte("entries: {parent: {enabled: false}}\n")},
|
|
"shared/icon.txt": &fstest.MapFile{Data: []byte("parent-resource")},
|
|
"config/overlays/test.yml": &fstest.MapFile{Data: []byte("entries: {parent: {count: 2}}\n")},
|
|
})
|
|
result, err := configkit.LoadLayered(configkit.Input{Source: filesystem.File("config/main.yml")}, configkit.LayeredOptions[inheritedConfiguration]{
|
|
Underlays: []configkit.Underlay{
|
|
{Inputs: []configkit.Input{{Source: filesystem.File("shared/parent.yml")}}},
|
|
{Inputs: []configkit.Input{{Source: filesystem.File("shared/flags.yml")}}},
|
|
},
|
|
Overlay: "test", OverlayFilesystem: &configkit.FilesystemContext{Root: "config", BaseDir: "config", FS: &filesystem},
|
|
Overrides: []configkit.Override{{Path: configkit.Keys("entries", "child", "count"), Value: 3, Flag: "--count"}},
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
child := result.Config.Entries["child"]
|
|
if child.Count != 3 || child.Resource != "icon.txt" {
|
|
t.Fatalf("layered inheritance = %#v", child)
|
|
}
|
|
info, found := result.Metadata().Lookup(configkit.Keys("entries", "child", "resource"))
|
|
if !found || len(info.Origin.Inheritance) != 1 || len(info.Origin.Locations) != 1 {
|
|
t.Fatalf("resource origin = %#v, %v", info, found)
|
|
}
|
|
context, found := result.SourceContext(info.Origin.Locations[0].SourceID)
|
|
if !found || context.BaseDir != "shared" || context.FS == nil {
|
|
t.Fatalf("source context = %#v, %v", context, found)
|
|
}
|
|
data, err := fs.ReadFile(context.FS, context.BaseDir+"/"+child.Resource)
|
|
if err != nil || string(data) != "parent-resource" {
|
|
t.Fatalf("source-relative resource = %q, %v", data, err)
|
|
}
|
|
count, found := result.Metadata().Lookup(configkit.Keys("entries", "child", "count"))
|
|
if !found || count.Origin.Layer != configkit.LayerCLI || len(count.Origin.Inheritance) != 0 {
|
|
t.Fatalf("CLI replacement origin = %#v, %v", count, found)
|
|
}
|
|
var rendered bytes.Buffer
|
|
if err := result.WriteEffective(&rendered); err != nil || strings.Contains(rendered.String(), "extends:") {
|
|
t.Fatalf("effective rendering = %q, %v", rendered.String(), err)
|
|
}
|
|
}
|
|
|
|
func TestFacadeInheritanceMappingMatchesBytes(t *testing.T) {
|
|
values := map[string]any{"entries": map[string]any{
|
|
"parent": map[string]any{"enabled": false, "count": 0},
|
|
"child": map[string]any{"extends": "parent"},
|
|
}}
|
|
mapped, err := configkit.Load[inheritedConfiguration](configkit.Mapping("mapping", values))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
fromYAML, err := configkit.Load[inheritedConfiguration](configkit.Bytes("config.yml", []byte("entries: {parent: {enabled: false, count: 0}, child: {extends: parent}}\n")))
|
|
if err != nil || !reflect.DeepEqual(mapped.Config, fromYAML.Config) {
|
|
t.Fatalf("mapping and bytes differ: %#v, %#v, %v", mapped, fromYAML, err)
|
|
}
|
|
}
|
|
|
|
func TestFacadeInheritanceProtectsVersionOwnership(t *testing.T) {
|
|
for _, test := range []struct {
|
|
name, input string
|
|
path configkit.Path
|
|
wantFailure bool
|
|
}{
|
|
{"inherited version", "entries: {parent: {count: 1}, child: {extends: parent}}\n", configkit.Keys("entries", "child", "count"), true},
|
|
{"overridden version", "entries: {parent: {count: 1}, child: {extends: parent, count: 1}}\n", configkit.Keys("entries", "child", "count"), true},
|
|
{"direct unchanged version", "entries: {parent: {enabled: false}, child: {extends: parent, count: 1}}\n", configkit.Keys("entries", "child", "count"), false},
|
|
{"consumed checked directive", "entries: {parent: {count: 1}, child: {extends: parent}}\n", configkit.Keys("entries", "child", "extends"), true},
|
|
{"independent top-level version", "version: v1\nentries: {parent: {count: 1}, child: {extends: parent}}\n", configkit.Keys("version"), false},
|
|
} {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
checks := struct{ version, defaults, resolution, validation int }{}
|
|
result, err := configkit.Load(configkit.Bytes("version.yml", []byte(test.input)), configkit.Options[inheritedConfiguration]{Definition: configkit.Definition[inheritedConfiguration]{
|
|
Version: &configkit.VersionRule{Path: test.path, Check: func(configkit.Version, *configkit.Validation) error {
|
|
checks.version++
|
|
return nil
|
|
}},
|
|
Defaults: func() inheritedConfiguration { checks.defaults++; return inheritedConfiguration{} },
|
|
Resolve: func(*inheritedConfiguration, *configkit.Resolution) error { checks.resolution++; return nil },
|
|
Validate: func(*inheritedConfiguration, *configkit.Validation) { checks.validation++ },
|
|
}})
|
|
if test.wantFailure {
|
|
if result != nil || err == nil {
|
|
t.Fatalf("protected version = %#v, %v", result, err)
|
|
}
|
|
loadError, ok := err.(*configkit.LoadError)
|
|
if !ok || len(loadError.Diagnostics()) == 0 || loadError.Diagnostics()[0].Code != configkit.CodeInheritanceVersion ||
|
|
checks != (struct{ version, defaults, resolution, validation int }{version: 1}) {
|
|
t.Fatalf("version failure = %v, callbacks %#v", err, checks)
|
|
}
|
|
} else if err != nil || result == nil || checks != (struct{ version, defaults, resolution, validation int }{1, 1, 1, 1}) {
|
|
t.Fatalf("unchanged version = %#v, %v, callbacks %#v", result, err, checks)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestFacadeInheritanceFailureStopsLaterCallbacks(t *testing.T) {
|
|
checks := struct{ defaults, resolution, validation int }{}
|
|
result, err := configkit.Load(configkit.Bytes("invalid.yml", []byte("entries: {child: {extends: PRIVATE-REFERENCE-CANARY}}\n")), configkit.Options[inheritedConfiguration]{Definition: configkit.Definition[inheritedConfiguration]{
|
|
Defaults: func() inheritedConfiguration { checks.defaults++; return inheritedConfiguration{} },
|
|
Resolve: func(*inheritedConfiguration, *configkit.Resolution) error { checks.resolution++; return nil },
|
|
Validate: func(*inheritedConfiguration, *configkit.Validation) { checks.validation++ },
|
|
}})
|
|
if result != nil || err == nil || checks != (struct{ defaults, resolution, validation int }{}) || strings.Contains(err.Error(), "PRIVATE-REFERENCE-CANARY") {
|
|
t.Fatalf("invalid inheritance = %#v, %v, callbacks %#v", result, err, checks)
|
|
}
|
|
loadError, ok := err.(*configkit.LoadError)
|
|
if !ok || len(loadError.Diagnostics()) == 0 || loadError.Diagnostics()[0].Code != configkit.CodeInheritanceMissing {
|
|
t.Fatalf("missing reference diagnostics = %v", err)
|
|
}
|
|
}
|
|
|
|
func TestFacadeInheritanceResolvesAfterImportsAndCLIReferenceChange(t *testing.T) {
|
|
filesystem := configkit.NewFilesystem("bundle", fstest.MapFS{
|
|
"main.yml": &fstest.MapFile{Data: []byte("configkit: {imports: [{path: parent.yml}]}\nentries: {child: {extends: first}}\n")},
|
|
"parent.yml": &fstest.MapFile{Data: []byte("entries: {first: {count: 1}, second: {count: 2}}\n")},
|
|
})
|
|
result, err := configkit.Load(filesystem.File("main.yml"), configkit.Options[inheritedConfiguration]{
|
|
Imports: true, Overrides: []configkit.Override{{Path: configkit.Keys("entries", "child", "extends"), Value: "second", Flag: "--extends"}},
|
|
})
|
|
if err != nil || result.Config.Entries["child"].Count != 2 {
|
|
t.Fatalf("import and CLI reference = %#v, %v", result, err)
|
|
}
|
|
history := result.Metadata().History(configkit.Keys("entries", "child", "extends"))
|
|
if len(history) < 3 || history[len(history)-1].Origin.Operation != configkit.ChangeInheritance {
|
|
t.Fatalf("superseded reference history = %#v", history)
|
|
}
|
|
conflicting := configkit.NewFilesystem("bundle", fstest.MapFS{
|
|
"main.yml": &fstest.MapFile{Data: []byte("configkit: {imports: [{path: parent.yml}]}\nentries: {child: {extends: first}, first: {count: 3}}\n")},
|
|
"parent.yml": &fstest.MapFile{Data: []byte("entries: {first: {count: 1}}\n")},
|
|
})
|
|
_, err = configkit.Load[inheritedConfiguration](conflicting.File("main.yml"), configkit.Options[inheritedConfiguration]{Imports: true})
|
|
loadError, ok := err.(*configkit.LoadError)
|
|
if !ok || len(loadError.Diagnostics()) == 0 || loadError.Diagnostics()[0].Code != configkit.CodeComposeConflict {
|
|
t.Fatalf("strict conflict before expansion = %v", err)
|
|
}
|
|
}
|
|
|
|
func TestFacadeInheritanceSelectedOverlayCancelsRelationship(t *testing.T) {
|
|
filesystem := configkit.NewFilesystem("bundle", fstest.MapFS{
|
|
"main.yml": &fstest.MapFile{Data: []byte("entries: {parent: {count: 7}, child: {extends: parent, enabled: false}}\n")},
|
|
"overlays/test.yml": &fstest.MapFile{Data: []byte("entries: {child: {extends: null}}\n")},
|
|
})
|
|
result, err := configkit.Load(filesystem.File("main.yml"), configkit.Options[inheritedConfiguration]{
|
|
Overlay: "test", Filesystem: &configkit.FilesystemContext{Root: ".", FS: &filesystem},
|
|
})
|
|
if err != nil || result.Config.Entries["child"].Count != 0 || result.Config.Entries["child"].Enabled {
|
|
if loadError, ok := err.(*configkit.LoadError); ok {
|
|
t.Fatalf("cancelled relationship = %#v, %v, diagnostics %#v", result, err, loadError.Diagnostics())
|
|
}
|
|
t.Fatalf("cancelled relationship = %#v, %v", result, err)
|
|
}
|
|
history := result.Metadata().History(configkit.Keys("entries", "child", "extends"))
|
|
if len(history) < 3 || history[len(history)-1].Origin.Operation != configkit.ChangeInheritance {
|
|
t.Fatalf("cancellation history = %#v", history)
|
|
}
|
|
}
|
|
|
|
func TestFacadeInheritedSecretsStayRedactedAfterLookup(t *testing.T) {
|
|
const canary = "INHERITED-RELEASE-TEST-CREDENTIAL"
|
|
t.Setenv("CONFIGKIT_INHERITED_TEST_SECRET", canary)
|
|
type entry struct {
|
|
Credential configkit.Secret `yaml:"credential"`
|
|
Plain string `yaml:"plain"`
|
|
}
|
|
type configuration struct {
|
|
Entries map[string]entry `yaml:"entries"`
|
|
}
|
|
result, err := configkit.Load[configuration](configkit.Bytes("secret.yml", []byte("entries: {parent: {credential: CONFIGKIT_INHERITED_TEST_SECRET, plain: visible}, child: {extends: parent}}\n")), configkit.Options[configuration]{Definition: configkit.Definition[configuration]{
|
|
Secret: func(path configkit.Path) bool { return path.String() == "/entries/parent/credential" },
|
|
}})
|
|
if err != nil || result.Config.Entries["child"].Credential.Value() != canary {
|
|
t.Fatalf("inherited credential = %#v, %v", result, err)
|
|
}
|
|
path := configkit.Keys("entries", "child", "credential")
|
|
explanation, found := result.Explain(path)
|
|
if !found || !explanation.Effective.Redacted || strings.Contains(explanation.Effective.YAML, canary) {
|
|
t.Fatalf("credential explanation = %#v, %v", explanation, found)
|
|
}
|
|
var rendered bytes.Buffer
|
|
if err := result.WriteEffective(&rendered); err != nil || strings.Contains(rendered.String(), canary) || !strings.Contains(rendered.String(), "visible") {
|
|
t.Fatalf("effective secret rendering = %q, %v", rendered.String(), err)
|
|
}
|
|
rendered.Reset()
|
|
if err := result.WriteExplanation(&rendered, path); err != nil || strings.Contains(rendered.String(), canary) {
|
|
t.Fatalf("historical secret rendering = %q, %v", rendered.String(), err)
|
|
}
|
|
}
|