1+ // Package engine provides the implementation of Terragrunt IaC engine interface
12package engine
23
34import (
45 "bufio"
56 "context"
7+ "errors"
68 "fmt"
79 "io"
810 "os"
@@ -35,36 +37,41 @@ func (c *TerraformEngine) Init(req *tgengine.InitRequest, stream tgengine.Engine
3537 if err != nil {
3638 return err
3739 }
40+
3841 return nil
3942}
4043
4144func (c * TerraformEngine ) Run (req * tgengine.RunRequest , stream tgengine.Engine_RunServer ) error {
42- log .Infof ("Run Terraform engine %v" , req .WorkingDir )
43- cmd := exec .Command (iacCommand , req .Args ... )
44- cmd .Dir = req .WorkingDir
45- env := make ([]string , 0 , len (req .EnvVars ))
46- for key , value := range req .EnvVars {
45+ log .Infof ("Run Terraform engine %v" , req .GetWorkingDir ())
46+ cmd := exec .Command (iacCommand , req .GetArgs ()... )
47+ cmd .Dir = req .GetWorkingDir ()
48+
49+ env := make ([]string , 0 , len (req .GetEnvVars ()))
50+ for key , value := range req .GetEnvVars () {
4751 env = append (env , fmt .Sprintf ("%s=%s" , key , value ))
4852 }
53+
4954 cmd .Env = append (cmd .Env , env ... )
5055
5156 stdoutPipe , err := cmd .StdoutPipe ()
5257 if err != nil {
5358 sendError (stream , err )
5459 return err
5560 }
61+
5662 stderrPipe , err := cmd .StderrPipe ()
5763 if err != nil {
5864 sendError (stream , err )
5965 return err
6066 }
6167
62- if req .AllocatePseudoTty {
68+ if req .GetAllocatePseudoTty () {
6369 ptmx , err := pty .Start (cmd )
6470 if err != nil {
6571 log .Errorf ("Error allocating pseudo-TTY: %v" , err )
6672 return err
6773 }
74+
6875 defer func () { _ = ptmx .Close () }()
6976
7077 go func () {
@@ -87,61 +94,81 @@ func (c *TerraformEngine) Run(req *tgengine.RunRequest, stream tgengine.Engine_R
8794
8895 var wg sync.WaitGroup
8996
90- // 2 streams to send stdout and stderr
9197 wg .Add (wgSize )
9298
93- // Stream stdout
9499 go func () {
100+ // Ensure this goroutine signals completion when it returns
95101 defer wg .Done ()
102+
103+ // Create a reader that translates data from stdoutPipe into UTF-8 runes
96104 reader := transform .NewReader (stdoutPipe , unicode .UTF8 .NewDecoder ())
105+ // Wrap the reader in a buffered reader for efficient reading
97106 bufReader := bufio .NewReader (reader )
107+
98108 for {
109+ // Read a single rune from the buffered reader
99110 char , _ , err := bufReader .ReadRune ()
100111 if err != nil {
101- if err != io .EOF {
112+ // If there's an error and it's not EOF, log it
113+ if ! errors .Is (err , io .EOF ) {
102114 log .Errorf ("Error reading stdout: %v" , err )
103115 }
116+ // Exit the loop on EOF or any other error
104117 break
105118 }
119+
120+ // Stream the read character back to the client
106121 if err = stream .Send (& tgengine.RunResponse {Stdout : string (char )}); err != nil {
122+ // If streaming fails, log the error and exit
107123 log .Errorf ("Error sending stdout: %v" , err )
108124 return
109125 }
110126 }
111127 }()
112128
113- // Stream stderr
129+ // Starts a goroutine that captures stderr output character by character,
130+ // applying UTF-8 decoding, and streams each character to the client.
131+ // Handles errors appropriately and signals completion via WaitGroup.
132+ // Terminates on EOF or transmission errors.
114133 go func () {
115134 defer wg .Done ()
135+
116136 reader := transform .NewReader (stderrPipe , unicode .UTF8 .NewDecoder ())
117137 bufReader := bufio .NewReader (reader )
138+
118139 for {
119140 char , _ , err := bufReader .ReadRune ()
120141 if err != nil {
121- if err != io .EOF {
142+ if ! errors . Is ( err , io .EOF ) {
122143 log .Errorf ("Error reading stderr: %v" , err )
123144 }
145+
124146 break
125147 }
148+
126149 if err = stream .Send (& tgengine.RunResponse {Stderr : string (char )}); err != nil {
127150 log .Errorf ("Error sending stderr: %v" , err )
128151 return
129152 }
130153 }
131154 }()
132155 wg .Wait ()
133- err = cmd . Wait ()
156+
134157 resultCode := 0
135- if err != nil {
136- if exitError , ok := err .(* exec.ExitError ); ok {
158+
159+ if err := cmd .Wait (); err != nil {
160+ var exitError * exec.ExitError
161+ if ok := errors .As (err , & exitError ); ok {
137162 resultCode = exitError .ExitCode ()
138163 } else {
139164 resultCode = 1
140165 }
141166 }
167+
142168 if err := stream .Send (& tgengine.RunResponse {ResultCode : int32 (resultCode )}); err != nil {
143169 return err
144170 }
171+
145172 return nil
146173}
147174
@@ -158,6 +185,7 @@ func (c *TerraformEngine) Shutdown(req *tgengine.ShutdownRequest, stream tgengin
158185 if err != nil {
159186 return err
160187 }
188+
161189 return nil
162190}
163191
0 commit comments