@@ -3,11 +3,18 @@ package main
33import (
44 "encoding/json"
55 "fmt"
6- "os"
76 "os/exec"
87 "strings"
98)
109
10+ // DebugInfo contains command execution details for debugging
11+ type DebugInfo struct {
12+ Command string // The command that was run
13+ StdOut string // Standard output
14+ StdErr string // Standard error
15+ WasSuccess bool // Whether the command succeeded
16+ }
17+
1118// Deployment spec for a single job.
1219type JobSpec struct {
1320 Output string `json:"output"`
@@ -27,44 +34,51 @@ type NixEvalJobsResult struct {
2734 System string `json:"system"`
2835}
2936
30- func fatal (format string , args ... any ) {
31- fmt .Fprintf (os .Stderr , "[nynx] Error: " + format + "\n " , args ... )
32- os .Exit (1 )
33- }
34-
35- func info (format string , args ... any ) {
36- fmt .Printf ("[nynx] " + format + "\n " , args ... )
37- }
38-
39- func getConfigAttr (cfg string , job string , attr string ) string {
37+ // getConfigAttr evaluates a configuration attribute from the nix flake.
38+ // Returns empty string if evaluation fails.
39+ func getConfigAttr (cfg string , job string , attr string ) (string , * DebugInfo , error ) {
4040 attrPath := fmt .Sprintf ("%s#nynxDeployments.%s.%s" , cfg , job , attr )
41- value , err := runJSON [string ]("nix" , "eval" , "--json" , attrPath )
41+ value , debug , err := runJSON [string ]("nix" , "eval" , "--json" , attrPath )
4242 if err != nil {
43- return ""
43+ return "" , debug , fmt . Errorf ( "failed to evaluate attribute %s for job %s: %w" , attr , job , err )
4444 }
45- return value
45+ return value , debug , nil
4646}
4747
48- func evalDeployments (cfg string ) (map [string ]JobSpec , error ) {
48+ func evalDeployments (cfg string ) (map [string ]JobSpec , []* DebugInfo , error ) {
49+ var debugInfos []* DebugInfo
4950 flakeReference := fmt .Sprintf ("%s#nynxDeployments" , cfg )
5051
5152 // Get raw output since nix-eval-jobs uses JSON Lines format
53+ cmdStr := fmt .Sprintf ("nix-eval-jobs --force-recurse --flake %s" , flakeReference )
5254 c := exec .Command ("nix-eval-jobs" , "--force-recurse" , "--flake" , flakeReference )
5355 out , err := c .Output ()
56+
57+ debug := & DebugInfo {
58+ Command : cmdStr ,
59+ WasSuccess : err == nil ,
60+ }
61+
5462 if err != nil {
5563 if ee , ok := err .(* exec.ExitError ); ok {
56- return nil , fmt .Errorf ("failed to run nix-eval-jobs on %s: %s" , cfg , string (ee .Stderr ))
64+ debug .StdErr = string (ee .Stderr )
65+ debugInfos = append (debugInfos , debug )
66+ return nil , debugInfos , fmt .Errorf ("failed to run nix-eval-jobs on %s: %s" , cfg , string (ee .Stderr ))
5767 }
58- return nil , fmt .Errorf ("failed to run nix-eval-jobs on %s: %v" , cfg , err )
68+ debugInfos = append (debugInfos , debug )
69+ return nil , debugInfos , fmt .Errorf ("failed to run nix-eval-jobs on %s: %v" , cfg , err )
5970 }
6071
72+ debug .StdOut = string (out )
73+ debugInfos = append (debugInfos , debug )
74+
6175 // Parse JSON Lines format
6276 lines := strings .Split (strings .TrimSpace (string (out )), "\n " )
6377 results := make ([]NixEvalJobsResult , 0 , len (lines ))
6478 for _ , line := range lines {
6579 var result NixEvalJobsResult
6680 if err := json .Unmarshal ([]byte (line ), & result ); err != nil {
67- return nil , fmt .Errorf ("failed to parse JSON line from nix-eval-jobs : %w\n Line: %s " , err , line )
81+ return nil , debugInfos , fmt .Errorf ("invalid JSON output : %w" , err )
6882 }
6983 results = append (results , result )
7084 }
@@ -74,13 +88,13 @@ func evalDeployments(cfg string) (map[string]JobSpec, error) {
7488 // First pass: create basic job specs
7589 for _ , result := range results {
7690 if len (result .AttrPath ) < 2 {
77- return nil , fmt .Errorf ("invalid attrPath format : %v" , result .AttrPath )
91+ return nil , debugInfos , fmt .Errorf ("malformed attrPath: %v" , result .AttrPath )
7892 }
7993
8094 jobName := result .AttrPath [0 ]
8195 outputPath , ok := result .Outputs ["out" ]
8296 if ! ok {
83- return nil , fmt .Errorf ("missing 'out' output for job: %s " , jobName )
97+ return nil , debugInfos , fmt .Errorf ("job %s: missing 'out' output" , jobName )
8498 }
8599
86100 jobs [jobName ] = JobSpec {
@@ -93,19 +107,27 @@ func evalDeployments(cfg string) (map[string]JobSpec, error) {
93107
94108 // Second pass: enrich specs with additional attributes
95109 for jobName , spec := range jobs {
96- if hostname := getConfigAttr (cfg , jobName , "hostname" ); hostname != "" {
110+ if hostname , debug , err := getConfigAttr (cfg , jobName , "hostname" ); err == nil && hostname != "" {
97111 spec .Hostname = hostname
112+ debugInfos = append (debugInfos , debug )
98113 }
99114
100- spec .User = getConfigAttr (cfg , jobName , "user" )
101- spec .Type = getConfigAttr (cfg , jobName , "type" )
115+ if user , debug , err := getConfigAttr (cfg , jobName , "user" ); err == nil {
116+ spec .User = user
117+ debugInfos = append (debugInfos , debug )
118+ }
119+
120+ if sysType , debug , err := getConfigAttr (cfg , jobName , "type" ); err == nil {
121+ spec .Type = sysType
122+ debugInfos = append (debugInfos , debug )
123+ }
102124
103125 if spec .Output == "" {
104- return nil , fmt .Errorf ("missing 'output' for job: %s" , jobName )
126+ return nil , debugInfos , fmt .Errorf ("job %s: missing output path " , jobName )
105127 }
106128
107129 if spec .User == "" {
108- return nil , fmt .Errorf ("missing 'user' for job: %s" , jobName )
130+ return nil , debugInfos , fmt .Errorf ("job %s: missing user attribute " , jobName )
109131 }
110132
111133 // Infer Type if missing
@@ -125,46 +147,62 @@ func evalDeployments(cfg string) (map[string]JobSpec, error) {
125147 case strings .Contains (systemFound , "linux" ):
126148 spec .Type = "nixos"
127149 default :
128- return nil , fmt .Errorf ("could not infer system type for job '%s' from system '%s' " , jobName , systemFound )
150+ return nil , debugInfos , fmt .Errorf ("job %s: unknown system type %s " , jobName , systemFound )
129151 }
130152 }
131153
132154 if spec .Type != "nixos" && spec .Type != "darwin" {
133- return nil , fmt .Errorf ("unsupported system type '%s' for job: %s " , spec .Type , jobName )
155+ return nil , debugInfos , fmt .Errorf ("job %s: unsupported system type %s " , jobName , spec .Type )
134156 }
135157
136158 jobs [jobName ] = spec
137159 }
138160
139- return jobs , nil
161+ return jobs , debugInfos , nil
140162}
141163
142- func run (cmd string , args ... string ) ([]byte , error ) {
143- c := exec .Command (cmd , args ... )
144- out , err := c .CombinedOutput ()
164+ func run (cmd string , args ... string ) ([]byte , * DebugInfo , error ) {
165+ cmdStr := fmt .Sprintf ("%s %s" , cmd , strings .Join (args , " " ))
166+ debug := & DebugInfo {
167+ Command : cmdStr ,
168+ }
169+
170+ out , err := exec .Command (cmd , args ... ).CombinedOutput ()
171+ debug .StdOut = string (out )
172+ debug .WasSuccess = err == nil
173+
145174 if err != nil {
146- return out , fmt .Errorf ("` %s %v` failed: %s" , cmd , args , string (out ))
175+ return out , debug , fmt .Errorf ("%s failed: %s" , cmdStr , strings . TrimSpace ( string (out ) ))
147176 }
148- return out , nil
177+ return out , debug , nil
149178}
150179
151- func runJSON [T any ](cmd string , args ... string ) (T , error ) {
180+ func runJSON [T any ](cmd string , args ... string ) (T , * DebugInfo , error ) {
152181 var result T
182+ cmdStr := fmt .Sprintf ("%s %s" , cmd , strings .Join (args , " " ))
183+ debug := & DebugInfo {
184+ Command : cmdStr ,
185+ }
186+
153187 c := exec .Command (cmd , args ... )
154188 out , err := c .Output () // only capture stdout
189+ debug .StdOut = string (out )
190+ debug .WasSuccess = err == nil
191+
155192 if err != nil {
156193 if ee , ok := err .(* exec.ExitError ); ok {
157- return result , fmt .Errorf ("`%s %v` failed: %s" , cmd , args , string (ee .Stderr ))
194+ debug .StdErr = string (ee .Stderr )
195+ return result , debug , fmt .Errorf ("%s failed: %s" , cmdStr , strings .TrimSpace (string (ee .Stderr )))
158196 }
159- return result , fmt .Errorf ("` %s %v` failed: %v" , cmd , args , err )
197+ return result , debug , fmt .Errorf ("%s failed: %v" , cmdStr , err )
160198 }
161199
162200 if err := json .Unmarshal (out , & result ); err != nil {
163- return result , fmt .Errorf ("failed to unmarshal JSON output from `%s %v` : %w\n Raw output: %s" ,
164- cmd , args , err , string (out ))
201+ return result , debug , fmt .Errorf ("failed to unmarshal JSON output from %s : %w\n Raw output: %s" ,
202+ cmdStr , err , strings . TrimSpace ( string (out ) ))
165203 }
166204
167- return result , nil
205+ return result , debug , nil
168206}
169207
170208func validateOperations (jobs map [string ]JobSpec , op string ) ([]string , error ) {
@@ -194,13 +232,3 @@ func validateOperations(jobs map[string]JobSpec, op string) ([]string, error) {
194232 }
195233 return warnings , nil
196234}
197-
198- func verboseInfo (verbose bool , format string , args ... any ) {
199- if verbose {
200- info (format , args ... )
201- }
202- }
203-
204- func warn (format string , args ... any ) {
205- fmt .Fprintf (os .Stderr , "[nynx] Warning: " + format + "\n " , args ... )
206- }
0 commit comments