@@ -20,56 +20,65 @@ import (
2020// ExportBlueprint exports Authentik configuration as Blueprint YAML
2121// VENDOR APPROACH: Uses `ak export_blueprint` command in worker container
2222// BENEFITS: Automatic UUID handling, dependency resolution, official support
23+ // FIXED: ak export_blueprint outputs to stdout (no --output flag exists)
24+ // EVIDENCE: https://docs.goauthentik.io/customize/blueprints/export/
2325func (c * Client ) ExportBlueprint (ctx context.Context , outputPath string ) error {
24- // Run ak export_blueprint command in worker container
25- // NOTE: Exports flows, stages, policies, providers as YAML
26+ // CORRECTED: ak export_blueprint outputs to stdout, redirect to file
27+ // BEFORE (WRONG): "ak", "export_blueprint", "--output", "/tmp/blueprint.yaml"
28+ // AFTER (CORRECT): "ak", "export_blueprint" > file
2629 cmd := exec .CommandContext (ctx ,
2730 "docker" , "exec" ,
2831 "hecate-server-1" , // Authentik server container
2932 "ak" , "export_blueprint" ,
30- "--output" , "/tmp/blueprint.yaml" ,
3133 )
3234
33- output , err := cmd .CombinedOutput ()
35+ // Capture stdout (blueprint YAML)
36+ output , err := cmd .Output ()
3437 if err != nil {
35- return fmt .Errorf ("blueprint export failed: %w (output: %s)" , err , string (output ))
38+ // Include stderr for diagnostics
39+ if exitErr , ok := err .(* exec.ExitError ); ok {
40+ return fmt .Errorf ("blueprint export failed: %w\n Stderr: %s" , err , string (exitErr .Stderr ))
41+ }
42+ return fmt .Errorf ("blueprint export failed: %w" , err )
3643 }
3744
38- // Copy blueprint from container to host
39- copyCmd := exec .CommandContext (ctx ,
40- "docker" , "cp" ,
41- "hecate-server-1:/tmp/blueprint.yaml" ,
42- outputPath ,
43- )
45+ // Write blueprint output directly to host file
46+ if err := os .WriteFile (outputPath , output , 0600 ); err != nil {
47+ return fmt .Errorf ("failed to write blueprint to %s: %w" , outputPath , err )
48+ }
4449
45- if err := copyCmd .Run (); err != nil {
46- return fmt .Errorf ("failed to copy blueprint from container: %w" , err )
50+ // Verify file has content
51+ if len (output ) == 0 {
52+ return fmt .Errorf ("blueprint export produced empty output" )
4753 }
4854
4955 return nil
5056}
5157
5258// ExportBlueprintToDirectory exports Blueprint and saves to specified directory
5359// CONVENIENCE: Wrapper around ExportBlueprint with timestamped filename
60+ // FIXED: ak export_blueprint outputs to stdout (no --output flag exists)
61+ // EVIDENCE: https://docs.goauthentik.io/customize/blueprints/export/
5462func ExportBlueprintToDirectory (rc * eos_io.RuntimeContext , outputDir string ) (string , error ) {
5563 logger := otelzap .Ctx (rc .Ctx )
5664
5765 // Create Blueprint filename
5866 blueprintPath := filepath .Join (outputDir , "23_authentik_blueprint.yaml" )
5967
60- // Use unified client to export
61- // NOTE: For now, use exec directly until Client consolidation complete
68+ // CORRECTED: ak export_blueprint outputs to stdout, redirect to file
69+ // BEFORE (WRONG): "ak", "export_blueprint", "--output", "/tmp/blueprint.yaml"
70+ // AFTER (CORRECT): "ak", "export_blueprint" > file
6271 cmd := exec .CommandContext (rc .Ctx ,
6372 "docker" , "exec" ,
6473 "hecate-server-1" ,
6574 "ak" , "export_blueprint" ,
66- "--output" , "/tmp/blueprint.yaml" ,
6775 )
6876
6977 logger .Info ("Exporting Authentik Blueprint via ak command" ,
7078 zap .String ("container" , "hecate-server-1" ))
7179
72- output , err := cmd .CombinedOutput ()
80+ // Capture stdout (blueprint YAML)
81+ output , err := cmd .Output ()
7382 if err != nil {
7483 // Check if container exists
7584 checkCmd := exec .CommandContext (rc .Ctx , "docker" , "ps" , "-a" , "--filter" , "name=hecate-server-1" , "--format" , "{{.Names}}" )
@@ -78,18 +87,16 @@ func ExportBlueprintToDirectory(rc *eos_io.RuntimeContext, outputDir string) (st
7887 return "" , fmt .Errorf ("Authentik server container not found (hecate-server-1) - is docker-compose running?" )
7988 }
8089
81- return "" , fmt .Errorf ("blueprint export failed: %w (output: %s)" , err , string (output ))
90+ // Include stderr for diagnostics
91+ if exitErr , ok := err .(* exec.ExitError ); ok {
92+ return "" , fmt .Errorf ("blueprint export failed: %w\n Stderr: %s" , err , string (exitErr .Stderr ))
93+ }
94+ return "" , fmt .Errorf ("blueprint export failed: %w" , err )
8295 }
8396
84- // Copy blueprint from container to host
85- copyCmd := exec .CommandContext (rc .Ctx ,
86- "docker" , "cp" ,
87- "hecate-server-1:/tmp/blueprint.yaml" ,
88- blueprintPath ,
89- )
90-
91- if err := copyCmd .Run (); err != nil {
92- return "" , fmt .Errorf ("failed to copy blueprint from container: %w" , err )
97+ // Write blueprint output directly to host file
98+ if err := os .WriteFile (blueprintPath , output , 0600 ); err != nil {
99+ return "" , fmt .Errorf ("failed to write blueprint to %s: %w" , blueprintPath , err )
93100 }
94101
95102 // Verify file was created and has content
@@ -98,6 +105,10 @@ func ExportBlueprintToDirectory(rc *eos_io.RuntimeContext, outputDir string) (st
98105 return "" , fmt .Errorf ("blueprint file not created: %w" , err )
99106 }
100107
108+ if info .Size () == 0 {
109+ return "" , fmt .Errorf ("blueprint file is empty - export may have failed silently" )
110+ }
111+
101112 logger .Info ("✓ Exported Authentik Blueprint" ,
102113 zap .String ("file" , "23_authentik_blueprint.yaml" ),
103114 zap .Int64 ("size_bytes" , info .Size ()),
0 commit comments