11package utils
22
33import (
4+ "context"
45 "crypto/md5"
56 "encoding/hex"
67 "encoding/json"
@@ -9,6 +10,7 @@ import (
910 "log"
1011 "net/http"
1112 "os"
13+ "os/exec"
1214 "strings"
1315
1416 "github.com/spf13/viper"
@@ -122,3 +124,77 @@ func (s *State) GetDimData(dimensionKey string, dimensionValue string, skipOnNot
122124
123125 return dimensionJsonMap , nil
124126}
127+
128+ func (s * State ) ReportHistory (cmdToExec string , cmdArgs []string , cmdMainArg string , exitCode int ) {
129+ if s .IacconsoleApiUrl == "" {
130+ // Only report to API if URL is configured
131+ return
132+ }
133+
134+ var outputs map [string ]interface {}
135+
136+ if exitCode == 0 && (cmdMainArg == "apply" || cmdMainArg == "destroy" ) {
137+ // Run tofu output -json to gather outputs
138+ outputCmd := exec .Command (cmdToExec , "output" , "-json" )
139+ outputCmd .Dir = s .CmdWorkTempDir
140+
141+ outputBytes , err := outputCmd .Output ()
142+ if err == nil && len (outputBytes ) > 0 {
143+ // Terraform outputs structure: {"name": {"sensitive": false, "type": "string", "value": "val"}}
144+ var tfOutputs map [string ]interface {}
145+ if err := json .Unmarshal (outputBytes , & tfOutputs ); err == nil {
146+ // We can either pass the entire tfOutputs or extract just the values
147+ // The API's HistoryRequestPost map[string]interface{} can handle the whole structure,
148+ // so let's pass it as is, or we can unpack values. Passing as is gives type and sensitive flags.
149+ outputs = tfOutputs
150+ } else {
151+ log .Printf ("Failed to parse tf outputs: %v\n " , err )
152+ }
153+ } else if err != nil {
154+ log .Printf ("Failed to get tf outputs: %v\n " , err )
155+ }
156+ }
157+
158+ workspace := s .Workspace
159+ if workspace == "" {
160+ workspace = "master"
161+ }
162+
163+ payload := map [string ]interface {}{
164+ "cmdtoexec" : cmdToExec ,
165+ "cmdargs" : cmdArgs ,
166+ "cmdmainarg" : cmdMainArg ,
167+ "exitcode" : exitCode ,
168+ "dimensions" : s .ParsedDimensions ,
169+ "outputs" : outputs ,
170+ }
171+
172+ payloadBytes , err := json .Marshal (payload )
173+ if err != nil {
174+ log .Printf ("Failed to marshal history payload: %v" , err )
175+ return
176+ }
177+
178+ url := fmt .Sprintf ("%s/v1/history/%s/%s/%s" , s .IacconsoleApiUrl , s .OrgName , workspace , s .UnitName )
179+ req , err := http .NewRequestWithContext (context .TODO (), "POST" , url , strings .NewReader (string (payloadBytes )))
180+ if err != nil {
181+ log .Printf ("Failed to create request for history: %v" , err )
182+ return
183+ }
184+ req .Header .Set ("Content-Type" , "application/json" )
185+
186+ client := & http.Client {}
187+ resp , err := client .Do (req )
188+ if err != nil {
189+ log .Printf ("Failed to report history to %s: %v" , url , err )
190+ return
191+ }
192+ defer resp .Body .Close ()
193+
194+ if resp .StatusCode != http .StatusOK {
195+ body , _ := io .ReadAll (resp .Body )
196+ log .Printf ("Failed to report history, status code %d: %s" , resp .StatusCode , string (body ))
197+ } else {
198+ log .Println ("Successfully reported execution history to IaC Console API" )
199+ }
200+ }
0 commit comments