Skip to content

Commit 40e1784

Browse files
authored
feat(provisioner): warn when .terraform.lock.hcl is modified during init (coder#20451)
Fixes: coder#20451
1 parent 5a31c59 commit 40e1784

File tree

2 files changed

+74
-1
lines changed

2 files changed

+74
-1
lines changed

provisioner/terraform/executor.go

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"context"
77
"encoding/json"
88
"fmt"
9+
"hash/crc32"
910
"io"
1011
"os"
1112
"os/exec"
@@ -220,6 +221,10 @@ func (e *executor) init(ctx, killCtx context.Context, logr logSink) error {
220221
e.mut.Lock()
221222
defer e.mut.Unlock()
222223

224+
// Record lock file checksum before init
225+
lockFilePath := filepath.Join(e.workdir, ".terraform.lock.hcl")
226+
preInitChecksum := checksumFileCRC32(ctx, e.logger, lockFilePath)
227+
223228
outWriter, doneOut := logWriter(logr, proto.LogLevel_DEBUG)
224229
errWriter, doneErr := logWriter(logr, proto.LogLevel_ERROR)
225230
defer func() {
@@ -246,7 +251,30 @@ func (e *executor) init(ctx, killCtx context.Context, logr logSink) error {
246251
return &textFileBusyError{exitErr: exitErr, stderr: errBuf.b.String()}
247252
}
248253
}
249-
return err
254+
if err != nil {
255+
return err
256+
}
257+
258+
// Check if lock file was modified
259+
postInitChecksum := checksumFileCRC32(ctx, e.logger, lockFilePath)
260+
if preInitChecksum != 0 && postInitChecksum != 0 && preInitChecksum != postInitChecksum {
261+
e.logger.Warn(ctx, fmt.Sprintf(".terraform.lock.hcl was modified during init. This means provider hashes "+
262+
"are missing for the current platform (%s_%s). Update the lock file with:\n\n"+
263+
" terraform providers lock -platform=linux_amd64 -platform=linux_arm64 "+
264+
"-platform=darwin_amd64 -platform=darwin_arm64 -platform=windows_amd64\n",
265+
runtime.GOOS, runtime.GOARCH),
266+
)
267+
}
268+
return nil
269+
}
270+
271+
func checksumFileCRC32(ctx context.Context, logger slog.Logger, path string) uint32 {
272+
content, err := os.ReadFile(path)
273+
if err != nil {
274+
logger.Debug(ctx, "file %s does not exist or can't be read, skip checksum calculation")
275+
return 0
276+
}
277+
return crc32.ChecksumIEEE(content)
250278
}
251279

252280
func getPlanFilePath(workdir string) string {

provisioner/terraform/executor_internal_test.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@ package terraform
22

33
import (
44
"encoding/json"
5+
"os"
56
"testing"
67

78
tfjson "github.com/hashicorp/terraform-json"
89
"github.com/stretchr/testify/require"
910

1011
"github.com/coder/coder/v2/provisionersdk/proto"
12+
"github.com/coder/coder/v2/testutil"
1113
)
1214

1315
type mockLogger struct {
@@ -171,3 +173,46 @@ func TestOnlyDataResources(t *testing.T) {
171173
})
172174
}
173175
}
176+
177+
func TestChecksumFileCRC32(t *testing.T) {
178+
t.Parallel()
179+
180+
t.Run("file exists", func(t *testing.T) {
181+
t.Parallel()
182+
183+
ctx := testutil.Context(t, testutil.WaitShort)
184+
logger := testutil.Logger(t)
185+
186+
tmpfile, err := os.CreateTemp("", "lockfile-*.hcl")
187+
require.NoError(t, err)
188+
defer os.Remove(tmpfile.Name())
189+
190+
content := []byte("provider \"aws\" { version = \"5.0.0\" }")
191+
_, err = tmpfile.Write(content)
192+
require.NoError(t, err)
193+
tmpfile.Close()
194+
195+
// Calculate checksum - expected value for this specific content
196+
expectedChecksum := uint32(0x08f39f51)
197+
checksum := checksumFileCRC32(ctx, logger, tmpfile.Name())
198+
require.Equal(t, expectedChecksum, checksum)
199+
200+
// Modify file
201+
err = os.WriteFile(tmpfile.Name(), []byte("modified content"), 0o600)
202+
require.NoError(t, err)
203+
204+
// Checksum should be different
205+
modifiedChecksum := checksumFileCRC32(ctx, logger, tmpfile.Name())
206+
require.NotEqual(t, expectedChecksum, modifiedChecksum)
207+
})
208+
209+
t.Run("file does not exist", func(t *testing.T) {
210+
t.Parallel()
211+
212+
ctx := testutil.Context(t, testutil.WaitShort)
213+
logger := testutil.Logger(t)
214+
215+
checksum := checksumFileCRC32(ctx, logger, "/nonexistent/file.hcl")
216+
require.Zero(t, checksum)
217+
})
218+
}

0 commit comments

Comments
 (0)