Skip to content

Commit 32fae1e

Browse files
committed
drop parse support and move logger into terraform package
Signed-off-by: Spike Curtis <spike@coder.com>
1 parent ceaa402 commit 32fae1e

File tree

7 files changed

+126
-301
lines changed

7 files changed

+126
-301
lines changed

provisioner/terraform/executor.go

+57-16
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package terraform
22

33
import (
4+
"bufio"
45
"bytes"
56
"context"
67
"encoding/json"
@@ -17,7 +18,6 @@ import (
1718
"github.com/hashicorp/go-version"
1819
tfjson "github.com/hashicorp/terraform-json"
1920

20-
"github.com/coder/coder/provisionersdk"
2121
"github.com/coder/coder/provisionersdk/proto"
2222
)
2323

@@ -113,14 +113,14 @@ func (e executor) version(ctx context.Context) (*version.Version, error) {
113113
return version.NewVersion(vj.Version)
114114
}
115115

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)
118118
defer func() { <-doneLogging }()
119119
return e.execWriteOutput(ctx, []string{"init"}, e.basicEnv(), writer)
120120
}
121121

122122
// 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) {
124124
planfilePath := filepath.Join(e.workdir, "terraform.tfplan")
125125
args := []string{
126126
"plan",
@@ -137,7 +137,7 @@ func (e executor) plan(ctx context.Context, env, vars []string, logger provision
137137
args = append(args, "-var", variable)
138138
}
139139

140-
writer, doneLogging := provisionLogWriter(logger)
140+
writer, doneLogging := provisionLogWriter(logr)
141141
defer func() { <-doneLogging }()
142142

143143
err := e.execWriteOutput(ctx, args, env, writer)
@@ -190,7 +190,7 @@ func (e executor) graph(ctx context.Context) (string, error) {
190190
}
191191

192192
// 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,
194194
) (*proto.Provision_Response, error) {
195195
args := []string{
196196
"apply",
@@ -207,7 +207,7 @@ func (e executor) apply(ctx context.Context, env, vars []string, logger provisio
207207
args = append(args, "-var", variable)
208208
}
209209

210-
writer, doneLogging := provisionLogWriter(logger)
210+
writer, doneLogging := provisionLogWriter(logr)
211211
defer func() { <-doneLogging }()
212212

213213
err := e.execWriteOutput(ctx, args, env, writer)
@@ -262,14 +262,55 @@ func (e executor) state(ctx context.Context) (*tfjson.State, error) {
262262
return state, nil
263263
}
264264

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) {
266307
r, w := io.Pipe()
267308
done := make(chan any)
268-
go provisionReadAndLog(logger, r, done)
309+
go provisionReadAndLog(logr, r, done)
269310
return w, done
270311
}
271312

272-
func provisionReadAndLog(logger provisionersdk.Logger, reader io.Reader, done chan<- any) {
313+
func provisionReadAndLog(logr logger, reader io.Reader, done chan<- any) {
273314
defer close(done)
274315
decoder := json.NewDecoder(reader)
275316
for {
@@ -278,9 +319,9 @@ func provisionReadAndLog(logger provisionersdk.Logger, reader io.Reader, done ch
278319
if err != nil {
279320
return
280321
}
281-
logLevel := convertTerraformLogLevel(log.Level, logger)
322+
logLevel := convertTerraformLogLevel(log.Level, logr)
282323

283-
err = logger.Log(&proto.Log{Level: logLevel, Output: log.Message})
324+
err = logr.Log(&proto.Log{Level: logLevel, Output: log.Message})
284325
if err != nil {
285326
// Not much we can do. We can't log because logging is itself breaking!
286327
return
@@ -291,19 +332,19 @@ func provisionReadAndLog(logger provisionersdk.Logger, reader io.Reader, done ch
291332
}
292333

293334
// 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)
295336
if err != nil {
296337
continue
297338
}
298-
err = logger.Log(&proto.Log{Level: logLevel, Output: log.Diagnostic.Detail})
339+
err = logr.Log(&proto.Log{Level: logLevel, Output: log.Diagnostic.Detail})
299340
if err != nil {
300341
// Not much we can do. We can't log because logging is itself breaking!
301342
return
302343
}
303344
}
304345
}
305346

306-
func convertTerraformLogLevel(logLevel string, logger provisionersdk.Logger) proto.LogLevel {
347+
func convertTerraformLogLevel(logLevel string, logr logger) proto.LogLevel {
307348
switch strings.ToLower(logLevel) {
308349
case "trace":
309350
return proto.LogLevel_TRACE
@@ -316,7 +357,7 @@ func convertTerraformLogLevel(logLevel string, logger provisionersdk.Logger) pro
316357
case "error":
317358
return proto.LogLevel_ERROR
318359
default:
319-
_ = logger.Log(&proto.Log{
360+
_ = logr.Log(&proto.Log{
320361
Level: proto.LogLevel_WARN,
321362
Output: fmt.Sprintf("unable to convert log level %s", logLevel),
322363
})
+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package terraform
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/require"
7+
"golang.org/x/xerrors"
8+
9+
"github.com/coder/coder/provisionersdk/proto"
10+
)
11+
12+
type mockLogger struct {
13+
logs []*proto.Log
14+
retVal error
15+
}
16+
17+
func (m *mockLogger) Log(l *proto.Log) error {
18+
m.logs = append(m.logs, l)
19+
return m.retVal
20+
}
21+
22+
func TestLogWriter_Mainline(t *testing.T) {
23+
t.Parallel()
24+
25+
logr := &mockLogger{retVal: nil}
26+
writer, doneLogging := logWriter(logr, proto.LogLevel_INFO)
27+
28+
_, err := writer.Write([]byte(`Sitting in an English garden
29+
Waiting for the sun
30+
If the sun don't come you get a tan
31+
From standing in the English rain`))
32+
require.NoError(t, err)
33+
err = writer.Close()
34+
require.NoError(t, err)
35+
<-doneLogging
36+
37+
expected := []*proto.Log{
38+
{Level: proto.LogLevel_INFO, Output: "Sitting in an English garden"},
39+
{Level: proto.LogLevel_INFO, Output: "Waiting for the sun"},
40+
{Level: proto.LogLevel_INFO, Output: "If the sun don't come you get a tan"},
41+
{Level: proto.LogLevel_INFO, Output: "From standing in the English rain"},
42+
}
43+
require.Equal(t, logr.logs, expected)
44+
}
45+
46+
func TestLogWriter_SendError(t *testing.T) {
47+
t.Parallel()
48+
49+
logr := &mockLogger{retVal: xerrors.New("Goo goo g'joob")}
50+
writer, doneLogging := logWriter(logr, proto.LogLevel_INFO)
51+
52+
_, err := writer.Write([]byte(`Sitting in an English garden
53+
Waiting for the sun
54+
If the sun don't come you get a tan
55+
From standing in the English rain`))
56+
require.NoError(t, err)
57+
err = writer.Close()
58+
require.NoError(t, err)
59+
<-doneLogging
60+
expected := []*proto.Log{{Level: proto.LogLevel_INFO, Output: "Sitting in an English garden"}}
61+
require.Equal(t, logr.logs, expected)
62+
}

provisioner/terraform/provision.go

+7-7
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import (
1515

1616
// Provision executes `terraform apply` or `terraform plan` for dry runs.
1717
func (t *server) Provision(stream proto.DRPCProvisioner_ProvisionStream) error {
18-
logger := provisionersdk.NewProvisionLogger(stream)
18+
logr := streamLogger{stream: stream}
1919
shutdown, shutdownFunc := context.WithCancel(stream.Context())
2020
defer shutdownFunc()
2121

@@ -53,7 +53,7 @@ func (t *server) Provision(stream proto.DRPCProvisioner_ProvisionStream) error {
5353
if err := e.checkMinVersion(stream.Context()); err != nil {
5454
return err
5555
}
56-
if err := logTerraformEnvVars(logger); err != nil {
56+
if err := logTerraformEnvVars(logr); err != nil {
5757
return err
5858
}
5959

@@ -88,7 +88,7 @@ func (t *server) Provision(stream proto.DRPCProvisioner_ProvisionStream) error {
8888
}
8989

9090
t.logger.Debug(shutdown, "running initialization")
91-
err = e.init(stream.Context(), logger)
91+
err = e.init(stream.Context(), logr)
9292
if err != nil {
9393
return xerrors.Errorf("initialize terraform: %w", err)
9494
}
@@ -104,10 +104,10 @@ func (t *server) Provision(stream proto.DRPCProvisioner_ProvisionStream) error {
104104
}
105105
var resp *proto.Provision_Response
106106
if start.DryRun {
107-
resp, err = e.plan(shutdown, env, vars, logger,
107+
resp, err = e.plan(shutdown, env, vars, logr,
108108
start.Metadata.WorkspaceTransition == proto.WorkspaceTransition_DESTROY)
109109
} else {
110-
resp, err = e.apply(shutdown, env, vars, logger,
110+
resp, err = e.apply(shutdown, env, vars, logr,
111111
start.Metadata.WorkspaceTransition == proto.WorkspaceTransition_DESTROY)
112112
}
113113
if err != nil {
@@ -198,7 +198,7 @@ var (
198198
}
199199
)
200200

201-
func logTerraformEnvVars(logger provisionersdk.Logger) error {
201+
func logTerraformEnvVars(logr logger) error {
202202
env := os.Environ()
203203
for _, e := range env {
204204
if strings.HasPrefix(e, "TF_") {
@@ -209,7 +209,7 @@ func logTerraformEnvVars(logger provisionersdk.Logger) error {
209209
if !tfEnvSafeToPrint[parts[0]] {
210210
parts[1] = "<value redacted>"
211211
}
212-
err := logger.Log(&proto.Log{
212+
err := logr.Log(&proto.Log{
213213
Level: proto.LogLevel_WARN,
214214
Output: fmt.Sprintf("terraform environment variable: %s=%s", parts[0], parts[1]),
215215
})

provisionersdk/logger.go

-71
This file was deleted.

0 commit comments

Comments
 (0)