1
1
package terraform
2
2
3
3
import (
4
+ "bufio"
4
5
"bytes"
5
6
"context"
6
7
"encoding/json"
@@ -17,7 +18,6 @@ import (
17
18
"github.com/hashicorp/go-version"
18
19
tfjson"github.com/hashicorp/terraform-json"
19
20
20
- "github.com/coder/coder/provisionersdk"
21
21
"github.com/coder/coder/provisionersdk/proto"
22
22
)
23
23
@@ -113,14 +113,14 @@ func (e executor) version(ctx context.Context) (*version.Version, error) {
113
113
return version .NewVersion (vj .Version )
114
114
}
115
115
116
- func (e executor )init (ctx context.Context ,logger provisionersdk. Logger )error {
117
- writer ,doneLogging := provisionersdk . LogWriter ( logger ,proto .LogLevel_DEBUG )
116
+ func (e executor )init (ctx context.Context ,logr logger )error {
117
+ writer ,doneLogging := logWriter ( logr ,proto .LogLevel_DEBUG )
118
118
defer func () {<- doneLogging }()
119
119
return e .execWriteOutput (ctx , []string {"init" },e .basicEnv (),writer )
120
120
}
121
121
122
122
// revive:disable-next-line:flag-parameter
123
- func (e executor )plan (ctx context.Context ,env ,vars []string ,logger provisionersdk. Logger ,destroy bool ) (* proto.Provision_Response ,error ) {
123
+ func (e executor )plan (ctx context.Context ,env ,vars []string ,logr logger ,destroy bool ) (* proto.Provision_Response ,error ) {
124
124
planfilePath := filepath .Join (e .workdir ,"terraform.tfplan" )
125
125
args := []string {
126
126
"plan" ,
@@ -137,7 +137,7 @@ func (e executor) plan(ctx context.Context, env, vars []string, logger provision
137
137
args = append (args ,"-var" ,variable )
138
138
}
139
139
140
- writer ,doneLogging := provisionLogWriter (logger )
140
+ writer ,doneLogging := provisionLogWriter (logr )
141
141
defer func () {<- doneLogging }()
142
142
143
143
err := e .execWriteOutput (ctx ,args ,env ,writer )
@@ -190,7 +190,7 @@ func (e executor) graph(ctx context.Context) (string, error) {
190
190
}
191
191
192
192
// revive:disable-next-line:flag-parameter
193
- func (e executor )apply (ctx context.Context ,env ,vars []string ,logger provisionersdk. Logger ,destroy bool ,
193
+ func (e executor )apply (ctx context.Context ,env ,vars []string ,logr logger ,destroy bool ,
194
194
) (* proto.Provision_Response ,error ) {
195
195
args := []string {
196
196
"apply" ,
@@ -207,7 +207,7 @@ func (e executor) apply(ctx context.Context, env, vars []string, logger provisio
207
207
args = append (args ,"-var" ,variable )
208
208
}
209
209
210
- writer ,doneLogging := provisionLogWriter (logger )
210
+ writer ,doneLogging := provisionLogWriter (logr )
211
211
defer func () {<- doneLogging }()
212
212
213
213
err := e .execWriteOutput (ctx ,args ,env ,writer )
@@ -262,14 +262,55 @@ func (e executor) state(ctx context.Context) (*tfjson.State, error) {
262
262
return state ,nil
263
263
}
264
264
265
- func provisionLogWriter (logger provisionersdk.Logger ) (io.WriteCloser ,<- chan any ) {
265
+ type logger interface {
266
+ Log (* proto.Log )error
267
+ }
268
+
269
+ type streamLogger struct {
270
+ stream proto.DRPCProvisioner_ProvisionStream
271
+ }
272
+
273
+ func (s streamLogger )Log (l * proto.Log )error {
274
+ return s .stream .Send (& proto.Provision_Response {
275
+ Type :& proto.Provision_Response_Log {
276
+ Log :l ,
277
+ },
278
+ })
279
+ }
280
+
281
+ // logWriter creates a WriteCloser that will log each line of text at the given level. The WriteCloser must be closed
282
+ // by the caller to end logging, after which the returned channel will be closed to indicate that logging of the written
283
+ // data has finished. Failure to close the WriteCloser will leak a goroutine.
284
+ func logWriter (logr logger ,level proto.LogLevel ) (io.WriteCloser ,<- chan any ) {
285
+ r ,w := io .Pipe ()
286
+ done := make (chan any )
287
+ go readAndLog (logr ,r ,done ,level )
288
+ return w ,done
289
+ }
290
+
291
+ func readAndLog (logr logger ,r io.Reader ,done chan <- any ,level proto.LogLevel ) {
292
+ defer close (done )
293
+ scanner := bufio .NewScanner (r )
294
+ for scanner .Scan () {
295
+ err := logr .Log (& proto.Log {Level :level ,Output :scanner .Text ()})
296
+ if err != nil {
297
+ // Not much we can do. We can't log because logging is itself breaking!
298
+ return
299
+ }
300
+ }
301
+ }
302
+
303
+ // provisionLogWriter creates a WriteCloser that will log each JSON formatted terraform log. The WriteCloser must be
304
+ // closed by the caller to end logging, after which the returned channel will be closed to indicate that logging of the
305
+ // written data has finished. Failure to close the WriteCloser will leak a goroutine.
306
+ func provisionLogWriter (logr logger ) (io.WriteCloser ,<- chan any ) {
266
307
r ,w := io .Pipe ()
267
308
done := make (chan any )
268
- go provisionReadAndLog (logger ,r ,done )
309
+ go provisionReadAndLog (logr ,r ,done )
269
310
return w ,done
270
311
}
271
312
272
- func provisionReadAndLog (logger provisionersdk. Logger ,reader io.Reader ,done chan <- any ) {
313
+ func provisionReadAndLog (logr logger ,reader io.Reader ,done chan <- any ) {
273
314
defer close (done )
274
315
decoder := json .NewDecoder (reader )
275
316
for {
@@ -278,9 +319,9 @@ func provisionReadAndLog(logger provisionersdk.Logger, reader io.Reader, done ch
278
319
if err != nil {
279
320
return
280
321
}
281
- logLevel := convertTerraformLogLevel (log .Level ,logger )
322
+ logLevel := convertTerraformLogLevel (log .Level ,logr )
282
323
283
- err = logger .Log (& proto.Log {Level :logLevel ,Output :log .Message })
324
+ err = logr .Log (& proto.Log {Level :logLevel ,Output :log .Message })
284
325
if err != nil {
285
326
// Not much we can do. We can't log because logging is itself breaking!
286
327
return
@@ -291,19 +332,19 @@ func provisionReadAndLog(logger provisionersdk.Logger, reader io.Reader, done ch
291
332
}
292
333
293
334
// If the diagnostic is provided, let's provide a bit more info!
294
- logLevel = convertTerraformLogLevel (log .Diagnostic .Severity ,logger )
335
+ logLevel = convertTerraformLogLevel (log .Diagnostic .Severity ,logr )
295
336
if err != nil {
296
337
continue
297
338
}
298
- err = logger .Log (& proto.Log {Level :logLevel ,Output :log .Diagnostic .Detail })
339
+ err = logr .Log (& proto.Log {Level :logLevel ,Output :log .Diagnostic .Detail })
299
340
if err != nil {
300
341
// Not much we can do. We can't log because logging is itself breaking!
301
342
return
302
343
}
303
344
}
304
345
}
305
346
306
- func convertTerraformLogLevel (logLevel string ,logger provisionersdk. Logger ) proto.LogLevel {
347
+ func convertTerraformLogLevel (logLevel string ,logr logger ) proto.LogLevel {
307
348
switch strings .ToLower (logLevel ) {
308
349
case "trace" :
309
350
return proto .LogLevel_TRACE
@@ -316,7 +357,7 @@ func convertTerraformLogLevel(logLevel string, logger provisionersdk.Logger) pro
316
357
case "error" :
317
358
return proto .LogLevel_ERROR
318
359
default :
319
- _ = logger .Log (& proto.Log {
360
+ _ = logr .Log (& proto.Log {
320
361
Level :proto .LogLevel_WARN ,
321
362
Output :fmt .Sprintf ("unable to convert log level %s" ,logLevel ),
322
363
})