2026-10-11 01:06:21 +00:00
package ckconfig
2026-10-07 14:17:46 +00:00
import (
"bytes"
"fmt"
2026-11-07 04:44:01 +00:00
"strings"
2026-11-07 03:57:10 +00:00
"text/template"
2026-10-07 14:17:46 +00:00
2026-11-04 03:52:46 +00:00
commandkit "go.maximumdirect.net/commandkit"
2026-11-04 14:58:20 +00:00
configkit "go.maximumdirect.net/configkit"
2026-10-07 14:17:46 +00:00
)
type explanationReport struct {
2026-11-07 03:57:10 +00:00
Scope string ` json:"scope" yaml:"scope" `
Path string ` json:"path" yaml:"path" `
Effective snapshotDTO ` json:"effective" yaml:"effective" `
Origin explanationOriginDTO ` json:"origin" yaml:"origin" `
History [ ] explanationChangeDTO ` json:"history" yaml:"history" `
SecretReference * secretReferenceDTO ` json:"secret_reference" yaml:"secret_reference" `
2026-11-07 04:44:01 +00:00
Metadata * explanationMetadata ` json:"metadata,omitempty" `
2026-10-07 14:17:46 +00:00
}
2026-11-07 04:44:01 +00:00
type explanationMetadata struct {
Explicit * bool ` json:"explicit,omitempty" `
Null * bool ` json:"null,omitempty" `
History [ ] metadataHistoryRecord ` json:"history,omitempty" `
}
type metadataHistoryRecord struct {
Path string ` json:"path" `
Previous [ ] explanationOriginDTO ` json:"previous,omitempty" `
}
type pathDiscoveryReport struct {
Scope string ` json:"scope" `
Paths [ ] pathDiscoveryEntry ` json:"paths" `
}
type pathDiscoveryEntry struct {
Path string ` json:"path" `
Present bool ` json:"present" `
Metadata * pathMetadata ` json:"metadata,omitempty" `
}
type pathMetadata struct {
Explicit bool ` json:"explicit" `
}
var pathDiscoveryTemplate = template . Must ( commandkit . ParseTextTemplate ( "paths" , "{{range .Paths}}{{printf \"%q\" .Path}} {{if .Present}}{{if .Metadata.Explicit}}explicit{{else}}non-explicit{{end}}{{else}}historical{{end}}\n{{end}}" , nil ) )
2026-10-07 14:17:46 +00:00
type snapshotDTO struct {
2026-11-07 03:57:10 +00:00
Present bool ` json:"present" yaml:"present" `
Redacted bool ` json:"redacted" yaml:"redacted" `
YAML string ` json:"yaml" yaml:"yaml" `
2026-10-07 14:17:46 +00:00
}
type explanationOriginDTO struct {
2026-11-07 03:57:10 +00:00
Layer string ` json:"layer" yaml:"layer" `
Locations [ ] locationDTO ` json:"locations" yaml:"locations" `
Operation string ` json:"operation" yaml:"operation" `
Inheritance [ ] inheritanceStepDTO ` json:"inheritance" yaml:"inheritance" `
2026-10-07 14:17:46 +00:00
}
type inheritanceStepDTO struct {
2026-11-07 03:57:10 +00:00
From string ` json:"from" yaml:"from" `
To string ` json:"to" yaml:"to" `
Reference string ` json:"reference" yaml:"reference" `
Locations [ ] locationDTO ` json:"locations" yaml:"locations" `
2026-10-07 14:17:46 +00:00
}
type explanationChangeDTO struct {
2026-11-07 03:57:10 +00:00
Before snapshotDTO ` json:"before" yaml:"before" `
After snapshotDTO ` json:"after" yaml:"after" `
Origin explanationOriginDTO ` json:"origin" yaml:"origin" `
Reasons [ ] string ` json:"reasons" yaml:"reasons" `
Dependencies [ ] string ` json:"dependencies" yaml:"dependencies" `
2026-10-07 14:17:46 +00:00
}
type secretReferenceDTO struct {
2026-11-07 03:57:10 +00:00
Name string ` json:"name" yaml:"name" `
2026-10-07 14:17:46 +00:00
}
2026-10-11 01:39:23 +00:00
func runConfigExplanation [ I , S any ] ( invocation commandkit . Invocation , plan * configOperationPlan [ I , S ] , state S ) commandkit . Outcome {
selected := plan . scopes [ 0 ]
name := selected . scope . scope . Name
2026-10-26 03:20:17 +00:00
reporter := invocation . Reporter ( )
2026-10-07 16:47:28 +00:00
if err := invocation . Context . Err ( ) ; err != nil {
2026-10-26 03:20:17 +00:00
return configFailure ( "Configuration explanation was canceled." , commandkit . Explanation { } , err )
2026-10-07 14:17:46 +00:00
}
2026-10-11 01:39:23 +00:00
if selected . scope . scope . Acquire == nil {
2026-10-26 03:20:17 +00:00
cause := fmt . Errorf ( "configuration scope %q has no acquisition callback" , name )
return configFailure ( fmt . Sprintf ( "Unable to explain configuration scope %q." , name ) , commandkit . Explanation { } , cause )
2026-10-07 14:17:46 +00:00
}
2026-10-11 01:39:23 +00:00
acquired , acquireErr := selected . scope . scope . Acquire ( invocation . Context , Request {
Operation : Explain ,
2026-11-07 04:39:34 +00:00
Selection : selected . selection ,
2026-10-11 01:39:23 +00:00
} , selected . input , state )
2026-11-07 04:32:24 +00:00
queueConfigAcquisition ( reporter , acquired )
2026-10-07 14:17:46 +00:00
if acquireErr != nil {
2026-10-26 03:20:17 +00:00
return configFailure ( fmt . Sprintf ( "Unable to explain configuration scope %q." , name ) , acquired . Explanation , acquireErr )
2026-10-07 14:17:46 +00:00
}
2026-10-07 16:47:28 +00:00
if err := invocation . Context . Err ( ) ; err != nil {
2026-10-26 03:20:17 +00:00
return configFailure ( "Configuration explanation was canceled." , acquired . Explanation , err )
2026-10-07 16:47:28 +00:00
}
2026-11-07 04:44:01 +00:00
if plan . list {
if acquired . Value . Metadata == nil {
return configFailure ( "Configuration scope did not provide path metadata." , acquired . Explanation , fmt . Errorf ( "configuration scope %q declared path discovery without metadata" , name ) )
}
report := pathDiscoveryReport { Scope : name , Paths : [ ] pathDiscoveryEntry { } }
for _ , path := range acquired . Value . Metadata . HistoricalPaths ( ) {
if plan . under != nil && ! pathWithin ( path , * plan . under ) {
continue
}
info , present := acquired . Value . Metadata . Lookup ( path )
if ! present && ( ! plan . historical || plan . explicit . Set ) {
continue
}
if present && plan . explicit . Set && info . Explicit != plan . explicit . Value {
continue
}
entry := pathDiscoveryEntry { Path : path . String ( ) , Present : present }
if present {
entry . Metadata = & pathMetadata { Explicit : info . Explicit }
}
report . Paths = append ( report . Paths , entry )
}
_ = reporter . Report ( commandkit . Report { Value : report , TextTemplate : pathDiscoveryTemplate } )
return commandkit . Success ( )
}
2026-10-07 14:17:46 +00:00
if acquired . Value . Explanation == nil {
2026-10-26 03:20:17 +00:00
cause := fmt . Errorf ( "configuration scope %q did not provide an explanation reader" , name )
return configFailure ( "Configuration scope did not provide explanation data." , acquired . Explanation , cause )
2026-10-07 14:17:46 +00:00
}
2026-10-11 01:39:23 +00:00
explanation , found := acquired . Value . Explanation . Explain ( plan . path )
2026-10-07 14:17:46 +00:00
if ! found {
2026-10-26 03:20:17 +00:00
cause := fmt . Errorf ( "configuration path %q is not known in scope %q" , plan . path . String ( ) , name )
return configFailure ( "The requested configuration path is not available." , acquired . Explanation , cause )
2026-10-07 14:17:46 +00:00
}
2026-10-07 16:47:28 +00:00
if err := invocation . Context . Err ( ) ; err != nil {
2026-10-26 03:20:17 +00:00
return configFailure ( "Configuration explanation was canceled." , acquired . Explanation , err )
2026-10-07 16:47:28 +00:00
}
2026-11-07 04:44:01 +00:00
report := makeExplanationReport ( name , explanation )
if acquired . Value . Metadata != nil {
metadata := & explanationMetadata { }
if info , ok := acquired . Value . Metadata . Lookup ( plan . path ) ; ok {
metadata . Explicit , metadata . Null = & info . Explicit , & info . Null
}
for _ , change := range acquired . Value . Metadata . History ( plan . path ) {
entry := metadataHistoryRecord { Path : change . Path . String ( ) }
for _ , previous := range change . Previous {
entry . Previous = append ( entry . Previous , originReport ( previous ) )
}
metadata . History = append ( metadata . History , entry )
}
if metadata . Explicit != nil || len ( metadata . History ) > 0 {
report . Metadata = metadata
}
}
2026-11-07 03:57:10 +00:00
text := template . Must ( commandkit . ParseTextTemplate ( "explanation" , "{{render .}}" , template . FuncMap {
2026-11-07 04:44:01 +00:00
"render" : func ( report explanationReport ) ( string , error ) {
2026-11-07 03:57:10 +00:00
var output bytes . Buffer
err := acquired . Value . Explanation . WriteExplanation ( & output , plan . path )
2026-11-07 04:44:01 +00:00
if err == nil && report . Metadata != nil && report . Metadata . Explicit != nil {
_ , err = fmt . Fprintf ( & output , "Explicit: %t\nNull: %t\n" , * report . Metadata . Explicit , * report . Metadata . Null )
}
2026-11-07 03:57:10 +00:00
return output . String ( ) , err
} ,
} ) )
2026-11-07 04:44:01 +00:00
renderErr := reporter . Report ( commandkit . Report { Value : report , TextTemplate : text } )
2026-11-07 03:57:10 +00:00
if renderErr != nil && invocation . Context . Err ( ) != nil {
return configFailure ( "Configuration explanation was canceled." , acquired . Explanation , invocation . Context . Err ( ) )
2026-10-07 14:17:46 +00:00
}
2026-11-07 03:57:10 +00:00
return commandkit . Success ( )
2026-10-07 14:17:46 +00:00
}
2026-11-07 04:44:01 +00:00
func pathWithin ( path , parent configkit . Path ) bool {
tokens := func ( value string ) [ ] string {
if value == "" {
return nil
}
return strings . Split ( strings . TrimPrefix ( value , "/" ) , "/" )
}
child , prefix := tokens ( path . String ( ) ) , tokens ( parent . String ( ) )
if len ( prefix ) > len ( child ) {
return false
}
for index := range prefix {
if prefix [ index ] != child [ index ] {
return false
}
}
return true
}
2026-10-07 14:17:46 +00:00
func makeExplanationReport ( scope string , explanation configkit . Explanation ) explanationReport {
report := explanationReport {
Scope : scope ,
Path : explanation . Path . String ( ) ,
Effective : snapshotReport ( explanation . Effective ) ,
Origin : originReport ( explanation . Origin ) ,
History : make ( [ ] explanationChangeDTO , 0 , len ( explanation . History ) ) ,
}
for _ , change := range explanation . History {
entry := explanationChangeDTO {
Before : snapshotReport ( change . Before ) ,
After : snapshotReport ( change . After ) ,
Origin : originReport ( change . Origin ) ,
Reasons : append ( [ ] string { } , change . Reasons ... ) ,
Dependencies : make ( [ ] string , 0 , len ( change . Dependencies ) ) ,
}
for _ , dependency := range change . Dependencies {
entry . Dependencies = append ( entry . Dependencies , dependency . String ( ) )
}
report . History = append ( report . History , entry )
}
if explanation . SecretReference != nil {
report . SecretReference = & secretReferenceDTO { Name : explanation . SecretReference . Name }
}
return report
}
func snapshotReport ( snapshot configkit . ValueSnapshot ) snapshotDTO {
return snapshotDTO { Present : snapshot . Present , Redacted : snapshot . Redacted , YAML : snapshot . YAML }
}
func originReport ( origin configkit . Origin ) explanationOriginDTO {
report := explanationOriginDTO {
Layer : string ( origin . Layer ) ,
Locations : locationReports ( origin . Locations ) ,
Operation : string ( origin . Operation ) ,
Inheritance : make ( [ ] inheritanceStepDTO , 0 , len ( origin . Inheritance ) ) ,
}
for _ , step := range origin . Inheritance {
report . Inheritance = append ( report . Inheritance , inheritanceStepDTO {
From : step . From . String ( ) ,
To : step . To . String ( ) ,
Reference : step . Reference . String ( ) ,
Locations : locationReports ( step . Locations ) ,
} )
}
return report
}