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

Commit9ca5b44

Browse files
authored
chore: implement persistent terraform directories (experimental) (#20563)
Prior to this, every workspace build ran `terraform init` in a freshdirectory. This would mean the `modules` are downloaded fresh. If themodule is not pinned, subsequent workspace builds would have differentmodules.
1 parent14f0844 commit9ca5b44

File tree

11 files changed

+428
-81
lines changed

11 files changed

+428
-81
lines changed

‎coderd/provisionerdserver/provisionerdserver.go‎

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -699,16 +699,19 @@ func (s *server) acquireProtoJob(ctx context.Context, job database.ProvisionerJo
699699
}
700700
}
701701

702+
activeVersion:=template.ActiveVersionID==templateVersion.ID
702703
protoJob.Type=&proto.AcquiredJob_WorkspaceBuild_{
703704
WorkspaceBuild:&proto.AcquiredJob_WorkspaceBuild{
704-
WorkspaceBuildId:workspaceBuild.ID.String(),
705-
WorkspaceName:workspace.Name,
706-
State:workspaceBuild.ProvisionerState,
707-
RichParameterValues:convertRichParameterValues(workspaceBuildParameters),
708-
PreviousParameterValues:convertRichParameterValues(lastWorkspaceBuildParameters),
709-
VariableValues:asVariableValues(templateVariables),
710-
ExternalAuthProviders:externalAuthProviders,
711-
ExpReuseTerraformWorkspace:ptr.Ref(false),// TODO: Toggle based on experiment
705+
WorkspaceBuildId:workspaceBuild.ID.String(),
706+
WorkspaceName:workspace.Name,
707+
State:workspaceBuild.ProvisionerState,
708+
RichParameterValues:convertRichParameterValues(workspaceBuildParameters),
709+
PreviousParameterValues:convertRichParameterValues(lastWorkspaceBuildParameters),
710+
VariableValues:asVariableValues(templateVariables),
711+
ExternalAuthProviders:externalAuthProviders,
712+
// If active and experiment is enabled, allow workspace reuse existing TF
713+
// workspaces (directories) for a faster startup.
714+
ExpReuseTerraformWorkspace:ptr.Ref(activeVersion&&s.Experiments.Enabled(codersdk.ExperimentTerraformWorkspace)),
712715
Metadata:&sdkproto.Metadata{
713716
CoderUrl:s.AccessURL.String(),
714717
WorkspaceTransition:transition,
@@ -722,6 +725,7 @@ func (s *server) acquireProtoJob(ctx context.Context, job database.ProvisionerJo
722725
WorkspaceOwnerId:owner.ID.String(),
723726
TemplateId:template.ID.String(),
724727
TemplateName:template.Name,
728+
TemplateVersionId:templateVersion.ID.String(),
725729
TemplateVersion:templateVersion.Name,
726730
WorkspaceOwnerSessionToken:sessionToken,
727731
WorkspaceOwnerSshPublicKey:ownerSSHPublicKey,

‎coderd/provisionerdserver/provisionerdserver_test.go‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,7 @@ func TestAcquireJob(t *testing.T) {
452452
TemplateId:template.ID.String(),
453453
TemplateName:template.Name,
454454
TemplateVersion:version.Name,
455+
TemplateVersionId:version.ID.String(),
455456
WorkspaceOwnerSessionToken:sessionToken,
456457
WorkspaceOwnerSshPublicKey:sshKey.PublicKey,
457458
WorkspaceOwnerSshPrivateKey:sshKey.PrivateKey,

‎provisioner/terraform/executor.go‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ type executor struct {
4141
// cachePath and files must not be used by multiple processes at once.
4242
cachePathstring
4343
cliConfigPathstring
44-
files tfpath.Layout
44+
files tfpath.Layouter
4545
// used to capture execution times at various stages
4646
timings*timingAggregator
4747
}
@@ -536,7 +536,11 @@ func (e *executor) graph(ctx, killCtx context.Context) (string, error) {
536536
iferr!=nil {
537537
return"",err
538538
}
539-
args:= []string{"graph"}
539+
args:= []string{
540+
"graph",
541+
// TODO: When the plan is present, we should probably use it?
542+
// "-plan=" + e.files.PlanFilePath(),
543+
}
540544
ifver.GreaterThanOrEqual(version170) {
541545
args=append(args,"-type=plan")
542546
}

‎provisioner/terraform/modules.go‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ func parseModulesFile(filePath string) ([]*proto.Module, error) {
5858
// getModules returns the modules from the modules file if it exists.
5959
// It returns nil if the file does not exist.
6060
// Modules become available after terraform init.
61-
funcgetModules(files tfpath.Layout) ([]*proto.Module,error) {
61+
funcgetModules(files tfpath.Layouter) ([]*proto.Module,error) {
6262
filePath:=files.ModulesFilePath()
6363
if_,err:=os.Stat(filePath);os.IsNotExist(err) {
6464
returnnil,nil

‎provisioner/terraform/serve.go‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ func (s *server) startTrace(ctx context.Context, name string, opts ...trace.Span
161161
))...)
162162
}
163163

164-
func (s*server)executor(files tfpath.Layout,stage database.ProvisionerJobTimingStage)*executor {
164+
func (s*server)executor(files tfpath.Layouter,stage database.ProvisionerJobTimingStage)*executor {
165165
return&executor{
166166
server:s,
167167
mut:s.execMut,

‎provisionerd/provisionerd_test.go‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"github.com/coder/coder/v2/provisionerd/proto"
2727
"github.com/coder/coder/v2/provisionersdk"
2828
sdkproto"github.com/coder/coder/v2/provisionersdk/proto"
29+
"github.com/coder/coder/v2/provisionersdk/tfpath"
2930
"github.com/coder/coder/v2/testutil"
3031
)
3132

@@ -318,8 +319,8 @@ func TestProvisionerd(t *testing.T) {
318319
JobId:"test",
319320
Provisioner:"someprovisioner",
320321
TemplateSourceArchive:testutil.CreateTar(t,map[string]string{
321-
"test.txt":"content",
322-
provisionersdk.ReadmeFile:"# A cool template 😎\n",
322+
"test.txt":"content",
323+
tfpath.ReadmeFile:"# A cool template 😎\n",
323324
}),
324325
Type:&proto.AcquiredJob_TemplateImport_{
325326
TemplateImport:&proto.AcquiredJob_TemplateImport{

‎provisionersdk/cleanup.go‎

Lines changed: 0 additions & 48 deletions
This file was deleted.

‎provisionersdk/cleanup_test.go‎

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import (
1111
"github.com/stretchr/testify/require"
1212

1313
"cdr.dev/slog"
14-
"github.com/coder/coder/v2/provisionersdk"
1514
"github.com/coder/coder/v2/provisionersdk/tfpath"
1615
"github.com/coder/coder/v2/testutil"
1716
)
@@ -47,9 +46,12 @@ func TestStaleSessions(t *testing.T) {
4746
addSessionFolder(t,fs,second,now.Add(-8*24*time.Hour))
4847
third:=tfpath.Session(workDirectory,uuid.NewString())
4948
addSessionFolder(t,fs,third,now.Add(-9*24*time.Hour))
49+
// tfDir is a fake session that will clean up the others
50+
tfDir:=tfpath.Session(workDirectory,uuid.NewString())
5051

5152
// when
52-
provisionersdk.CleanStaleSessions(ctx,workDirectory,fs,now,logger)
53+
err:=tfDir.CleanStaleSessions(ctx,logger,fs,now)
54+
require.NoError(t,err)
5355

5456
// then
5557
entries,err:=afero.ReadDir(fs,workDirectory)
@@ -70,9 +72,11 @@ func TestStaleSessions(t *testing.T) {
7072
addSessionFolder(t,fs,first,now.Add(-7*24*time.Hour))
7173
second:=tfpath.Session(workDirectory,uuid.NewString())
7274
addSessionFolder(t,fs,second,now.Add(-6*24*time.Hour))
75+
tfDir:=tfpath.Session(workDirectory,uuid.NewString())
7376

7477
// when
75-
provisionersdk.CleanStaleSessions(ctx,workDirectory,fs,now,logger)
78+
err:=tfDir.CleanStaleSessions(ctx,logger,fs,now)
79+
require.NoError(t,err)
7680

7781
// then
7882
entries,err:=afero.ReadDir(fs,workDirectory)
@@ -94,9 +98,11 @@ func TestStaleSessions(t *testing.T) {
9498
addSessionFolder(t,fs,first,now.Add(-6*24*time.Hour))
9599
second:=tfpath.Session(workDirectory,uuid.NewString())
96100
addSessionFolder(t,fs,second,now.Add(-5*24*time.Hour))
101+
tfDir:=tfpath.Session(workDirectory,uuid.NewString())
97102

98103
// when
99-
provisionersdk.CleanStaleSessions(ctx,workDirectory,fs,now,logger)
104+
err:=tfDir.CleanStaleSessions(ctx,logger,fs,now)
105+
require.NoError(t,err)
100106

101107
// then
102108
entries,err:=afero.ReadDir(fs,workDirectory)

‎provisionersdk/session.go‎

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,22 +13,16 @@ import (
1313
"golang.org/x/xerrors"
1414

1515
"cdr.dev/slog"
16+
"github.com/coder/coder/v2/codersdk"
1617
"github.com/coder/coder/v2/codersdk/drpcsdk"
1718
"github.com/coder/coder/v2/provisionersdk/tfpath"
19+
"github.com/coder/coder/v2/provisionersdk/tfpath/x"
1820

1921
protobuf"google.golang.org/protobuf/proto"
2022

2123
"github.com/coder/coder/v2/provisionersdk/proto"
2224
)
2325

24-
const (
25-
// ReadmeFile is the location we look for to extract documentation from template versions.
26-
ReadmeFile="README.md"
27-
28-
sessionDirPrefix="Session"
29-
staleSessionRetention=7*24*time.Hour
30-
)
31-
3226
// protoServer is a wrapper that translates the dRPC protocol into a Session with method calls into the Server.
3327
typeprotoServerstruct {
3428
serverServer
@@ -43,11 +37,6 @@ func (p *protoServer) Session(stream proto.DRPCProvisioner_SessionStream) error
4337
server:p.server,
4438
}
4539

46-
err:=CleanStaleSessions(s.Context(),p.opts.WorkDirectory,afero.NewOsFs(),time.Now(),s.Logger)
47-
iferr!=nil {
48-
returnxerrors.Errorf("unable to clean stale sessions %q: %w",s.Files,err)
49-
}
50-
5140
s.Files=tfpath.Session(p.opts.WorkDirectory,sessID)
5241

5342
deferfunc() {
@@ -67,6 +56,16 @@ func (p *protoServer) Session(stream proto.DRPCProvisioner_SessionStream) error
6756
s.logLevel=proto.LogLevel_value[strings.ToUpper(s.Config.ProvisionerLogLevel)]
6857
}
6958

59+
ifp.opts.Experiments.Enabled(codersdk.ExperimentTerraformWorkspace) {
60+
s.Files=x.SessionDir(p.opts.WorkDirectory,sessID,config)
61+
}
62+
63+
// Cleanup any previously left stale sessions.
64+
err=s.Files.CleanStaleSessions(s.Context(),s.Logger,afero.NewOsFs(),time.Now())
65+
iferr!=nil {
66+
returnxerrors.Errorf("unable to clean stale sessions %q: %w",s.Files,err)
67+
}
68+
7069
err=s.Files.ExtractArchive(s.Context(),s.Logger,afero.NewOsFs(),s.Config)
7170
iferr!=nil {
7271
returnxerrors.Errorf("extract archive: %w",err)
@@ -199,7 +198,7 @@ func (s *Session) handleRequests() error {
199198

200199
typeSessionstruct {
201200
Logger slog.Logger
202-
Files tfpath.Layout
201+
Files tfpath.Layouter
203202
Config*proto.Config
204203

205204
serverServer

‎provisionersdk/tfpath/tfpath.go‎

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,28 @@ import (
1919
"github.com/coder/coder/v2/provisionersdk/proto"
2020
)
2121

22+
typeLayouterinterface {
23+
WorkDirectory()string
24+
StateFilePath()string
25+
PlanFilePath()string
26+
TerraformLockFile()string
27+
ReadmeFilePath()string
28+
TerraformMetadataDir()string
29+
ModulesDirectory()string
30+
ModulesFilePath()string
31+
ExtractArchive(ctx context.Context,logger slog.Logger,fs afero.Fs,cfg*proto.Config)error
32+
Cleanup(ctx context.Context,logger slog.Logger,fs afero.Fs)
33+
CleanStaleSessions(ctx context.Context,logger slog.Logger,fs afero.Fs,now time.Time)error
34+
}
35+
36+
var_Layouter= (*Layout)(nil)
37+
2238
const (
2339
// ReadmeFile is the location we look for to extract documentation from template versions.
2440
ReadmeFile="README.md"
2541

26-
sessionDirPrefix="Session"
42+
sessionDirPrefix="Session"
43+
staleSessionRetention=7*24*time.Hour
2744
)
2845

2946
// Session creates a directory structure layout for terraform execution. The
@@ -34,6 +51,10 @@ func Session(parentDirPath, sessionID string) Layout {
3451
returnLayout(filepath.Join(parentDirPath,sessionDirPrefix+sessionID))
3552
}
3653

54+
funcFromWorkingDirectory(workDirstring)Layout {
55+
returnLayout(workDir)
56+
}
57+
3758
// Layout is the terraform execution working directory structure.
3859
// It also contains some methods for common file operations within that layout.
3960
// Such as "Cleanup" and "ExtractArchive".
@@ -82,6 +103,8 @@ func (l Layout) ExtractArchive(ctx context.Context, logger slog.Logger, fs afero
82103
returnxerrors.Errorf("create work directory %q: %w",l.WorkDirectory(),err)
83104
}
84105

106+
// TODO: Pass in cfg.TemplateSourceArchive, not the full config.
107+
// niling out the config field is a bit hacky.
85108
reader:=tar.NewReader(bytes.NewBuffer(cfg.TemplateSourceArchive))
86109
// for safety, nil out the reference on Config, since the reader now owns it.
87110
cfg.TemplateSourceArchive=nil
@@ -190,3 +213,40 @@ func (l Layout) Cleanup(ctx context.Context, logger slog.Logger, fs afero.Fs) {
190213
logger.Error(ctx,"failed to clean up work directory after multiple attempts",
191214
slog.F("path",path),slog.Error(err))
192215
}
216+
217+
// CleanStaleSessions browses the work directory searching for stale session
218+
// directories. Coder provisioner is supposed to remove them once after finishing the provisioning,
219+
// but there is a risk of keeping them in case of a failure.
220+
func (lLayout)CleanStaleSessions(ctx context.Context,logger slog.Logger,fs afero.Fs,now time.Time)error {
221+
parent:=filepath.Dir(l.WorkDirectory())
222+
entries,err:=afero.ReadDir(fs,filepath.Dir(l.WorkDirectory()))
223+
iferr!=nil {
224+
returnxerrors.Errorf("can't read %q directory",parent)
225+
}
226+
227+
for_,fi:=rangeentries {
228+
dirName:=fi.Name()
229+
230+
iffi.IsDir()&&isValidSessionDir(dirName) {
231+
sessionDirPath:=filepath.Join(parent,dirName)
232+
233+
modTime:=fi.ModTime()// fallback to modTime if modTime is not available (afero)
234+
235+
ifmodTime.Add(staleSessionRetention).After(now) {
236+
continue
237+
}
238+
239+
logger.Info(ctx,"remove stale session directory",slog.F("session_path",sessionDirPath))
240+
err=fs.RemoveAll(sessionDirPath)
241+
iferr!=nil {
242+
returnxerrors.Errorf("can't remove %q directory: %w",sessionDirPath,err)
243+
}
244+
}
245+
}
246+
returnnil
247+
}
248+
249+
funcisValidSessionDir(dirNamestring)bool {
250+
match,err:=filepath.Match(sessionDirPrefix+"*",dirName)
251+
returnerr==nil&&match
252+
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp