@@ -14,6 +14,7 @@ import (
1414 "perfspect/internal/report"
1515 "perfspect/internal/script"
1616 "perfspect/internal/target"
17+ "perfspect/internal/util"
1718 "slices"
1819 "strings"
1920
@@ -52,6 +53,22 @@ func runCmd(cmd *cobra.Command, args []string) error {
5253 // appContext is the application context that holds common data and resources.
5354 appContext := cmd .Parent ().Context ().Value (common.AppContext {}).(common.AppContext )
5455 localTempDir := appContext .LocalTempDir
56+ outputDir := appContext .OutputDir
57+
58+ flagRecord := cmd .Flags ().Lookup (flagRecordName ).Value .String () == "true"
59+ flagNoSummary := cmd .Flags ().Lookup (flagNoSummaryName ).Value .String () == "true"
60+
61+ // create output directory if we are recording the configuration
62+ if flagRecord {
63+ err := util .CreateDirectoryIfNotExists (outputDir , 0755 ) // #nosec G301
64+ if err != nil {
65+ err = fmt .Errorf ("failed to create output directory: %w" , err )
66+ fmt .Fprintf (os .Stderr , "Error: %v\n " , err )
67+ slog .Error (err .Error ())
68+ cmd .SilenceUsage = true
69+ return err
70+ }
71+ }
5572 // get the targets
5673 myTargets , targetErrs , err := common .GetTargets (cmd , true , true , localTempDir )
5774 if err != nil {
@@ -91,14 +108,40 @@ func runCmd(cmd *cobra.Command, args []string) error {
91108 cmd .SilenceUsage = true
92109 return err
93110 }
94- // print config prior to changes, optionally
95- if ! cmd .Flags ().Lookup (flagNoSummaryName ).Changed {
96- if err := printConfig (myTargets , localTempDir ); err != nil {
111+ // collect and print and/or record the configuration before making changes
112+ if ! flagNoSummary || flagRecord {
113+ config , err := getConfig (myTargets , localTempDir )
114+ if err != nil {
115+ fmt .Fprintf (os .Stderr , "Error: %v\n " , err )
116+ slog .Error (err .Error ())
117+ cmd .SilenceUsage = true
118+ return err
119+ }
120+ reports , err := processConfig (config )
121+ if err != nil {
122+ fmt .Fprintf (os .Stderr , "Error: %v\n " , err )
123+ slog .Error (err .Error ())
124+ cmd .SilenceUsage = true
125+ return err
126+ }
127+ filesWritten , err := printConfig (reports , ! flagNoSummary , flagRecord , outputDir )
128+ if err != nil {
97129 fmt .Fprintf (os .Stderr , "Error: %v\n " , err )
98130 slog .Error (err .Error ())
99131 cmd .SilenceUsage = true
100132 return err
101133 }
134+ if len (filesWritten ) > 0 {
135+ message := "Configuration"
136+ if len (filesWritten ) > 1 {
137+ message = "Configurations"
138+ }
139+ fmt .Printf ("%s recorded:\n " , message )
140+ for _ , fileWritten := range filesWritten {
141+ fmt .Printf (" %s\n " , fileWritten )
142+ }
143+ fmt .Println ()
144+ }
102145 }
103146 // if no changes were requested, print a message and return
104147 var changeRequested bool
@@ -138,9 +181,24 @@ func runCmd(cmd *cobra.Command, args []string) error {
138181 }
139182 multiSpinner .Finish ()
140183 fmt .Println () // blank line
141- // print config after making changes
142- if ! cmd .Flags ().Lookup (flagNoSummaryName ).Changed {
143- if err := printConfig (myTargets , localTempDir ); err != nil {
184+ // collect and print the configuration before making changes
185+ if ! flagNoSummary {
186+ config , err := getConfig (myTargets , localTempDir )
187+ if err != nil {
188+ fmt .Fprintf (os .Stderr , "Error: %v\n " , err )
189+ slog .Error (err .Error ())
190+ cmd .SilenceUsage = true
191+ return err
192+ }
193+ reports , err := processConfig (config )
194+ if err != nil {
195+ fmt .Fprintf (os .Stderr , "Error: %v\n " , err )
196+ slog .Error (err .Error ())
197+ cmd .SilenceUsage = true
198+ return err
199+ }
200+ _ , err = printConfig (reports , ! flagNoSummary , false , outputDir ) // print, don't record
201+ if err != nil {
144202 fmt .Fprintf (os .Stderr , "Error: %v\n " , err )
145203 slog .Error (err .Error ())
146204 cmd .SilenceUsage = true
@@ -227,7 +285,8 @@ func setOnTarget(cmd *cobra.Command, myTarget target.Target, flagGroups []flagGr
227285 channelError <- nil
228286}
229287
230- func printConfig (myTargets []target.Target , localTempDir string ) (err error ) {
288+ // getConfig collects the configuration data from the target(s)
289+ func getConfig (myTargets []target.Target , localTempDir string ) ([]common.TargetScriptOutputs , error ) {
231290 scriptNames := report .GetScriptNamesForTable (report .ConfigurationTableName )
232291 var scriptsToRun []script.ScriptDefinition
233292 for _ , scriptName := range scriptNames {
@@ -239,10 +298,10 @@ func printConfig(myTargets []target.Target, localTempDir string) (err error) {
239298 channelTargetScriptOutputs := make (chan common.TargetScriptOutputs )
240299 channelError := make (chan error )
241300 for _ , myTarget := range myTargets {
242- err = multiSpinner .AddSpinner (myTarget .GetName ())
301+ err : = multiSpinner .AddSpinner (myTarget .GetName ())
243302 if err != nil {
244303 err = fmt .Errorf ("failed to add spinner: %v" , err )
245- return
304+ return nil , err
246305 }
247306 // run the selected scripts on the target
248307 go collectOnTarget (myTarget , scriptsToRun , localTempDir , channelTargetScriptOutputs , channelError , multiSpinner .Status )
@@ -269,28 +328,55 @@ func printConfig(myTargets []target.Target, localTempDir string) (err error) {
269328 }
270329 }
271330 multiSpinner .Finish ()
272- // process and print the table for each target
273- for _ , targetScriptOutputs := range orderedTargetScriptOutputs {
331+ return orderedTargetScriptOutputs , nil
332+ }
333+
334+ // processConfig processes the collected configuration data and creates text reports
335+ func processConfig (targetScriptOutputs []common.TargetScriptOutputs ) (map [string ][]byte , error ) {
336+ reports := make (map [string ][]byte )
337+ var err error
338+ for _ , targetScriptOutput := range targetScriptOutputs {
274339 // process the tables, i.e., get field values from raw script output
275340 tableNames := []string {report .ConfigurationTableName }
276341 var tableValues []report.TableValues
277- if tableValues , err = report .ProcessTables (tableNames , targetScriptOutputs .ScriptOutputs ); err != nil {
342+ if tableValues , err = report .ProcessTables (tableNames , targetScriptOutput .ScriptOutputs ); err != nil {
278343 err = fmt .Errorf ("failed to process collected data: %v" , err )
279- return
344+ return nil , err
280345 }
281346 // create the report for this single table
282347 var reportBytes []byte
283- if reportBytes , err = report .Create ("txt" , tableValues , targetScriptOutputs .TargetName ); err != nil {
348+ if reportBytes , err = report .Create ("txt" , tableValues , targetScriptOutput .TargetName ); err != nil {
284349 err = fmt .Errorf ("failed to create report: %v" , err )
285- return
350+ return nil , err
286351 }
287- // print the report
288- if len (orderedTargetScriptOutputs ) > 1 {
289- fmt .Printf ("%s\n " , targetScriptOutputs .TargetName )
352+ // append the report to the list
353+ reports [targetScriptOutput .TargetName ] = reportBytes
354+ }
355+ return reports , nil
356+ }
357+
358+ // printConfig prints and/or saves the configuration reports
359+ func printConfig (reports map [string ][]byte , toStdout bool , toFile bool , outputDir string ) ([]string , error ) {
360+ filesWriten := []string {}
361+ for targetName , reportBytes := range reports {
362+ if toStdout {
363+ // print the report to stdout
364+ if len (reports ) > 1 {
365+ fmt .Printf ("%s\n " , targetName )
366+ }
367+ fmt .Print (string (reportBytes ))
368+ }
369+ if toFile {
370+ outputFilePath := fmt .Sprintf ("%s/config_%s.txt" , outputDir , targetName )
371+ err := os .WriteFile (outputFilePath , reportBytes , 0644 ) // #nosec G306
372+ if err != nil {
373+ err = fmt .Errorf ("failed to write configuration report to file: %v" , err )
374+ return filesWriten , err
375+ }
376+ filesWriten = append (filesWriten , outputFilePath )
290377 }
291- fmt .Print (string (reportBytes ))
292378 }
293- return
379+ return filesWriten , nil
294380}
295381
296382// collectOnTarget runs the scripts on the target and sends the results to the appropriate channels
0 commit comments