Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit8d86feb

Browse files
committed
feat: log resource drift as warning in build logs
Signed-off-by: Danny Kopping <dannykopping@gmail.com>
1 parentdd15026 commit8d86feb

File tree

2 files changed

+190
-13
lines changed

2 files changed

+190
-13
lines changed

‎provisioner/terraform/executor.go

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -322,20 +322,12 @@ func (e *executor) plan(ctx, killCtx context.Context, env, vars []string, logr l
322322

323323
// When a prebuild claim attempt is made, log a warning if a resource is due to be replaced, since this will obviate
324324
// the point of prebuilding if the expensive resource is replaced once claimed!
325-
var (
326-
isPrebuildClaimAttempt=!destroy&&metadata.GetPrebuiltWorkspaceBuildStage().IsPrebuiltWorkspaceClaim()
327-
resReps []*proto.ResourceReplacement
328-
)
325+
// We now also log drift for all builds to help with debugging resource replacements.
326+
varresReps []*proto.ResourceReplacement
329327
ifrepsFromPlan:=findResourceReplacements(plan);len(repsFromPlan)>0 {
330-
ifisPrebuildClaimAttempt {
331-
// TODO(dannyk): we should log drift always (not just during prebuild claim attempts); we're validating that this output
332-
// will not be overwhelming for end-users, but it'll certainly be super valuable for template admins
333-
// to diagnose this resource replacement issue, at least.
334-
// Once prebuilds moves out of beta, consider deleting this condition.
335-
336-
// Lock held before calling (see top of method).
337-
e.logDrift(ctx,killCtx,planfilePath,logr)
338-
}
328+
// Log drift details for all builds to help with debugging resource replacements.
329+
// Lock held before calling (see top of method).
330+
e.logDrift(ctx,killCtx,planfilePath,logr)
339331

340332
resReps=make([]*proto.ResourceReplacement,0,len(repsFromPlan))
341333
forn,p:=rangerepsFromPlan {

‎provisioner/terraform/executor_internal_test.go

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,21 @@
11
package terraform
22

33
import (
4+
"context"
45
"encoding/json"
6+
"os"
7+
"path/filepath"
8+
"strings"
9+
"sync"
510
"testing"
611

712
tfjson"github.com/hashicorp/terraform-json"
813
"github.com/stretchr/testify/require"
14+
"go.opentelemetry.io/otel/trace/noop"
915

16+
"github.com/coder/coder/v2/coderd/database"
1017
"github.com/coder/coder/v2/provisionersdk/proto"
18+
"github.com/coder/coder/v2/testutil"
1119
)
1220

1321
typemockLoggerstruct {
@@ -173,3 +181,180 @@ func TestOnlyDataResources(t *testing.T) {
173181
})
174182
}
175183
}
184+
185+
funcTestLogDrift_WithRealTerraformPlan(t*testing.T) {
186+
t.Parallel()
187+
188+
logger:=testutil.Logger(t)
189+
tmpDir:=t.TempDir()
190+
191+
binPath,err:=Install(t.Context(),logger,true,tmpDir,TerraformVersion)
192+
require.NoError(t,err)
193+
194+
tfConfig:=`
195+
terraform {
196+
required_providers {
197+
local = {
198+
source = "hashicorp/local"
199+
version = "~> 2.0"
200+
}
201+
}
202+
}
203+
204+
resource "local_file" "test_file" {
205+
content = "initial content"
206+
filename = "test.txt"
207+
}
208+
`
209+
210+
tfFile:=filepath.Join(tmpDir,"main.tf")
211+
require.NoError(t,os.WriteFile(tfFile, []byte(tfConfig),0o644))
212+
213+
// Create a minimal server for the executor.
214+
mockSrv:=&server{
215+
logger:logger,
216+
execMut:&sync.Mutex{},
217+
tracer:noop.NewTracerProvider().Tracer("test"),
218+
}
219+
220+
e:=&executor{
221+
logger:logger,
222+
binaryPath:binPath,
223+
workdir:tmpDir,
224+
mut:mockSrv.execMut,
225+
server:mockSrv,
226+
timings:newTimingAggregator(database.ProvisionerJobTimingStagePlan),
227+
}
228+
229+
// These contexts must be explicitly separate from the test context.
230+
// We have a log message which prints when these contexts are canceled (or when the test completes if using t.Context()),
231+
// and this log output would be confusing to the casual reader, while innocuous.
232+
// See interruptCommandOnCancel in executor.go.
233+
ctx:=context.Background()
234+
killCtx:=context.Background()
235+
236+
varmockSinkmockLogger
237+
err=e.init(ctx,killCtx,&mockSink)
238+
require.NoError(t,err)
239+
240+
// Create initial plan to establish state.
241+
_,err=e.plan(ctx,killCtx,e.basicEnv(), []string{},&mockSink,&proto.Metadata{
242+
WorkspaceTransition:proto.WorkspaceTransition_START,
243+
})
244+
require.NoError(t,err)
245+
246+
// Apply the plan to create initial state.
247+
_,err=e.apply(ctx,killCtx,e.basicEnv(),&mockSink)
248+
require.NoError(t,err)
249+
250+
// Now modify the terraform configuration to cause drift.
251+
driftConfig:=`
252+
terraform {
253+
required_providers {
254+
local = {
255+
source = "hashicorp/local"
256+
version = "~> 2.0"
257+
}
258+
}
259+
}
260+
261+
resource "local_file" "test_file" {
262+
content = "changed content that forces replacement"
263+
filename = "test.txt"
264+
}
265+
`
266+
267+
// Write the modified configuration.
268+
require.NoError(t,os.WriteFile(tfFile, []byte(driftConfig),0o644))
269+
270+
// Create a new plan that will show the drift/replacement.
271+
driftLogger:=&mockLogger{}
272+
planResult,err:=e.plan(ctx,killCtx,e.basicEnv(), []string{},driftLogger,&proto.Metadata{
273+
WorkspaceTransition:proto.WorkspaceTransition_START,
274+
})
275+
require.NoError(t,err)
276+
277+
// Verify we detected resource replacements (this triggers logDrift).
278+
require.NotEmpty(t,planResult.ResourceReplacements,"Should detect resource replacements that trigger drift logging")
279+
280+
// Verify that drift logs were captured.
281+
require.NotEmpty(t,driftLogger.logs,"logDrift should produce log output")
282+
283+
// Check that we have logs showing the resource replacement(s).
284+
var (
285+
foundReplacementLog,foundInfoLogs,foundWarnLogsbool
286+
)
287+
288+
for_,log:=rangedriftLogger.logs {
289+
t.Logf("[%s] %s",log.Level.String(),log.Output)
290+
291+
ifstrings.Contains(log.Output,"# forces replacement") {
292+
foundReplacementLog=true
293+
require.Equal(t,proto.LogLevel_WARN,log.Level,"Lines containing '# forces replacement' should be logged at WARN level")
294+
foundWarnLogs=true
295+
}
296+
297+
iflog.Level==proto.LogLevel_INFO {
298+
foundInfoLogs=true
299+
}
300+
}
301+
302+
// Verify we found the expected log types.
303+
require.True(t,foundReplacementLog,"Should find log lines containing '# forces replacement'")
304+
require.True(t,foundInfoLogs,"Should find INFO level logs showing the drift details")
305+
require.True(t,foundWarnLogs,"Should find WARN level logs for resource replacements")
306+
307+
// Verify that the drift shows the resource change.
308+
logOutput:=strings.Join(func() []string {
309+
varoutputs []string
310+
for_,log:=rangedriftLogger.logs {
311+
outputs=append(outputs,log.Output)
312+
}
313+
returnoutputs
314+
}(),"\n")
315+
316+
require.Contains(t,logOutput,"local_file.test_file","Drift logs should mention the specific resource")
317+
}
318+
319+
funcTestResourceReplaceLogWriter(t*testing.T) {
320+
t.Parallel()
321+
322+
varlogrmockLogger
323+
logger:=testutil.Logger(t)
324+
writer,doneLogging:=resourceReplaceLogWriter(&logr,logger)
325+
326+
// Test input with both normal lines and replacement lines.
327+
testInput:=` # local_file.test_file will be replaced
328+
-/+ resource "local_file" "test_file" {
329+
~ content = "initial content" -> "changed content" # forces replacement
330+
~ filename = "test.txt"
331+
id = "1234567890"
332+
}
333+
334+
Plan: 1 to add, 0 to change, 1 to destroy.`
335+
336+
_,err:=writer.Write([]byte(testInput))
337+
require.NoError(t,err)
338+
err=writer.Close()
339+
require.NoError(t,err)
340+
<-doneLogging
341+
342+
// Verify the logs
343+
require.NotEmpty(t,logr.logs,"Should produce log output")
344+
345+
varfoundReplacementWarn,foundInfoLogsbool
346+
347+
for_,log:=rangelogr.logs {
348+
t.Logf("[%s] %s",log.Level.String(),log.Output)
349+
350+
ifstrings.Contains(log.Output,"# forces replacement") {
351+
require.Equal(t,proto.LogLevel_WARN,log.Level,"Lines containing '# forces replacement' should be WARN level")
352+
foundReplacementWarn=true
353+
}elseiflog.Level==proto.LogLevel_INFO {
354+
foundInfoLogs=true
355+
}
356+
}
357+
358+
require.True(t,foundReplacementWarn,"Should find WARN level log for '# forces replacement' line")
359+
require.True(t,foundInfoLogs,"Should find INFO level logs for other lines")
360+
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp