Skip to content

Commit 95f149a

Browse files
committed
move around some functions
1 parent dc743ac commit 95f149a

File tree

6 files changed

+91
-98
lines changed

6 files changed

+91
-98
lines changed

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: 9 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,11 @@ 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 := tfpath.FromWorkingDirectory(workDirectory)
5050

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

5455
// then
5556
entries, err := afero.ReadDir(fs, workDirectory)
@@ -70,9 +71,11 @@ func TestStaleSessions(t *testing.T) {
7071
addSessionFolder(t, fs, first, now.Add(-7*24*time.Hour))
7172
second := tfpath.Session(workDirectory, uuid.NewString())
7273
addSessionFolder(t, fs, second, now.Add(-6*24*time.Hour))
74+
tfDir := tfpath.FromWorkingDirectory(workDirectory)
7375

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

7780
// then
7881
entries, err := afero.ReadDir(fs, workDirectory)
@@ -94,9 +97,11 @@ func TestStaleSessions(t *testing.T) {
9497
addSessionFolder(t, fs, first, now.Add(-6*24*time.Hour))
9598
second := tfpath.Session(workDirectory, uuid.NewString())
9699
addSessionFolder(t, fs, second, now.Add(-5*24*time.Hour))
100+
tfDir := tfpath.FromWorkingDirectory(workDirectory)
97101

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

101106
// then
102107
entries, err := afero.ReadDir(fs, workDirectory)

provisionersdk/session.go

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,6 @@ import (
2323
"github.com/coder/coder/v2/provisionersdk/proto"
2424
)
2525

26-
const (
27-
// ReadmeFile is the location we look for to extract documentation from template versions.
28-
ReadmeFile = "README.md"
29-
30-
sessionDirPrefix = "Session"
31-
staleSessionRetention = 7 * 24 * time.Hour
32-
)
33-
3426
// protoServer is a wrapper that translates the dRPC protocol into a Session with method calls into the Server.
3527
type protoServer struct {
3628
server Server
@@ -45,11 +37,6 @@ func (p *protoServer) Session(stream proto.DRPCProvisioner_SessionStream) error
4537
server: p.server,
4638
}
4739

48-
err := CleanStaleSessions(s.Context(), p.opts.WorkDirectory, afero.NewOsFs(), time.Now(), s.Logger)
49-
if err != nil {
50-
return xerrors.Errorf("unable to clean stale sessions %q: %w", s.Files, err)
51-
}
52-
5340
s.Files = tfpath.Session(p.opts.WorkDirectory, sessID)
5441

5542
defer func() {
@@ -70,13 +57,13 @@ func (p *protoServer) Session(stream proto.DRPCProvisioner_SessionStream) error
7057
}
7158

7259
if p.opts.Experiments.Enabled(codersdk.ExperimentTerraformWorkspace) {
73-
// TODO: Also indicate if opted into the feature via config
7460
s.Files = x.SessionDir(p.opts.WorkDirectory, sessID, config)
61+
}
7562

76-
err = s.Files.CleanInactiveTemplateVersions(s.Context(), s.Logger, afero.NewOsFs())
77-
if err != nil {
78-
return xerrors.Errorf("unable to clean inactive versions %q: %w", s.Files.WorkDirectory(), err)
79-
}
63+
// Cleanup any previously left stale sessions.
64+
err = s.Files.CleanStaleSessions(s.Context(), s.Logger, afero.NewOsFs(), time.Now())
65+
if err != nil {
66+
return xerrors.Errorf("unable to clean stale sessions %q: %w", s.Files, err)
8067
}
8168

8269
// Extract the template source archive into the work directory.

provisionersdk/tfpath/tfpath.go

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,17 @@ type LayoutInterface interface {
3030
ModulesFilePath() string
3131
ExtractArchive(ctx context.Context, logger slog.Logger, fs afero.Fs, cfg *proto.Config) error
3232
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
3334
}
3435

36+
var _ LayoutInterface = (*Layout)(nil)
37+
3538
const (
3639
// ReadmeFile is the location we look for to extract documentation from template versions.
3740
ReadmeFile = "README.md"
3841

39-
sessionDirPrefix = "Session"
42+
sessionDirPrefix = "Session"
43+
staleSessionRetention = 7 * 24 * time.Hour
4044
)
4145

4246
// Session creates a directory structure layout for terraform execution. The
@@ -47,6 +51,10 @@ func Session(parentDirPath, sessionID string) Layout {
4751
return Layout(filepath.Join(parentDirPath, sessionDirPrefix+sessionID))
4852
}
4953

54+
func FromWorkingDirectory(workDir string) Layout {
55+
return Layout(workDir)
56+
}
57+
5058
// Layout is the terraform execution working directory structure.
5159
// It also contains some methods for common file operations within that layout.
5260
// Such as "Cleanup" and "ExtractArchive".
@@ -202,3 +210,39 @@ func (l Layout) Cleanup(ctx context.Context, logger slog.Logger, fs afero.Fs) {
202210
logger.Error(ctx, "failed to clean up work directory after multiple attempts",
203211
slog.F("path", path), slog.Error(err))
204212
}
213+
214+
// CleanStaleSessions browses the work directory searching for stale session
215+
// directories. Coder provisioner is supposed to remove them once after finishing the provisioning,
216+
// but there is a risk of keeping them in case of a failure.
217+
func (l Layout) CleanStaleSessions(ctx context.Context, logger slog.Logger, fs afero.Fs, now time.Time) error {
218+
entries, err := afero.ReadDir(fs, l.WorkDirectory())
219+
if err != nil {
220+
return xerrors.Errorf("can't read %q directory", l.WorkDirectory())
221+
}
222+
223+
for _, fi := range entries {
224+
dirName := fi.Name()
225+
226+
if fi.IsDir() && isValidSessionDir(dirName) {
227+
sessionDirPath := filepath.Join(l.WorkDirectory(), dirName)
228+
229+
modTime := fi.ModTime() // fallback to modTime if modTime is not available (afero)
230+
231+
if modTime.Add(staleSessionRetention).After(now) {
232+
continue
233+
}
234+
235+
logger.Info(ctx, "remove stale session directory", slog.F("session_path", sessionDirPath))
236+
err = fs.RemoveAll(sessionDirPath)
237+
if err != nil {
238+
return xerrors.Errorf("can't remove %q directory: %w", sessionDirPath, err)
239+
}
240+
}
241+
}
242+
return nil
243+
}
244+
245+
func isValidSessionDir(dirName string) bool {
246+
match, err := filepath.Match(sessionDirPrefix+"*", dirName)
247+
return err == nil && match
248+
}

provisionersdk/x/directories.go

Lines changed: 29 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,18 @@ import (
2121
"github.com/coder/coder/v2/provisionersdk/tfpath"
2222
)
2323

24-
var _ tfpath.LayoutInterface = (*TerraformDirectory)(nil)
24+
var _ tfpath.LayoutInterface = (*Layout)(nil)
2525

26-
func SessionDir(parentDir, sessID string, config *proto.Config) TerraformDirectory {
27-
if config.TemplateId == "" || config.TemplateId == uuid.Nil.String() ||
28-
config.TemplateVersionId == "" || config.TemplateVersionId == uuid.Nil.String() {
26+
func SessionDir(parentDir, sessID string, config *proto.Config) Layout {
27+
missingID := config.TemplateId == "" || config.TemplateId == uuid.Nil.String() ||
28+
config.TemplateVersionId == "" || config.TemplateVersionId == uuid.Nil.String()
29+
30+
// Both templateID and templateVersionID must be set to reuse workspace.
31+
if !config.TerraformWorkspaceReuse || missingID {
2932
return EphemeralSessionDir(parentDir, sessID)
3033
}
3134

32-
return TerraformDirectory{
35+
return Layout{
3336
workDirectory: filepath.Join(parentDir, config.TemplateId, config.TemplateVersionId),
3437
sessionID: sessID,
3538
ephemeral: false,
@@ -39,15 +42,15 @@ func SessionDir(parentDir, sessID string, config *proto.Config) TerraformDirecto
3942
// EphemeralSessionDir returns the directory name with mandatory prefix. These
4043
// directories are created for each provisioning session and are meant to be
4144
// ephemeral.
42-
func EphemeralSessionDir(parentDir, sessID string) TerraformDirectory {
43-
return TerraformDirectory{
45+
func EphemeralSessionDir(parentDir, sessID string) Layout {
46+
return Layout{
4447
workDirectory: filepath.Join(parentDir, sessionDirPrefix+sessID),
4548
sessionID: sessID,
4649
ephemeral: true,
4750
}
4851
}
4952

50-
type TerraformDirectory struct {
53+
type Layout struct {
5154
workDirectory string
5255
sessionID string
5356
ephemeral bool
@@ -60,7 +63,7 @@ const (
6063
sessionDirPrefix = "Session"
6164
)
6265

63-
func (td TerraformDirectory) Cleanup(ctx context.Context, logger slog.Logger, fs afero.Fs) {
66+
func (td Layout) Cleanup(ctx context.Context, logger slog.Logger, fs afero.Fs) {
6467
var err error
6568
path := td.WorkDirectory()
6669
if !td.ephemeral {
@@ -90,7 +93,7 @@ func (td TerraformDirectory) Cleanup(ctx context.Context, logger slog.Logger, fs
9093
return
9194
}
9295

93-
func (td TerraformDirectory) WorkDirectory() string {
96+
func (td Layout) WorkDirectory() string {
9497
return td.workDirectory
9598
}
9699

@@ -99,39 +102,39 @@ func (td TerraformDirectory) WorkDirectory() string {
99102
//
100103
// These files should be cleaned up on exit. In the case of a failure, they will
101104
// not collide with other builds since each build uses a unique session ID.
102-
func (td TerraformDirectory) StateSessionDirectory() string {
105+
func (td Layout) StateSessionDirectory() string {
103106
return filepath.Join(td.workDirectory, "terraform.tfstate.d", td.sessionID)
104107
}
105108

106-
func (td TerraformDirectory) StateFilePath() string {
109+
func (td Layout) StateFilePath() string {
107110
return filepath.Join(td.StateSessionDirectory(), "terraform.tfstate")
108111
}
109112

110-
func (td TerraformDirectory) PlanFilePath() string {
113+
func (td Layout) PlanFilePath() string {
111114
return filepath.Join(td.StateSessionDirectory(), "terraform.tfplan")
112115
}
113116

114-
func (td TerraformDirectory) TerraformLockFile() string {
117+
func (td Layout) TerraformLockFile() string {
115118
return filepath.Join(td.WorkDirectory(), ".terraform.lock.hcl")
116119
}
117120

118-
func (td TerraformDirectory) ReadmeFilePath() string {
121+
func (td Layout) ReadmeFilePath() string {
119122
return filepath.Join(td.WorkDirectory(), ReadmeFile)
120123
}
121124

122-
func (td TerraformDirectory) TerraformMetadataDir() string {
125+
func (td Layout) TerraformMetadataDir() string {
123126
return filepath.Join(td.WorkDirectory(), ".terraform")
124127
}
125128

126-
func (td TerraformDirectory) ModulesDirectory() string {
129+
func (td Layout) ModulesDirectory() string {
127130
return filepath.Join(td.TerraformMetadataDir(), "modules")
128131
}
129132

130-
func (td TerraformDirectory) ModulesFilePath() string {
133+
func (td Layout) ModulesFilePath() string {
131134
return filepath.Join(td.ModulesDirectory(), "modules.json")
132135
}
133136

134-
func (td TerraformDirectory) ExtractArchive(ctx context.Context, logger slog.Logger, fs afero.Fs, cfg *proto.Config) error {
137+
func (td Layout) ExtractArchive(ctx context.Context, logger slog.Logger, fs afero.Fs, cfg *proto.Config) error {
135138
logger.Info(ctx, "unpacking template source archive",
136139
slog.F("size_bytes", len(cfg.TemplateSourceArchive)),
137140
)
@@ -224,13 +227,14 @@ func (td TerraformDirectory) ExtractArchive(ctx context.Context, logger slog.Log
224227
return nil
225228
}
226229

227-
// CleanInactiveTemplateVersions assumes this TerraformDirectory is the latest
228-
// active template version. Assuming that, any other template version directories
229-
// found alongside it are considered inactive and can be removed. Inactive
230-
// template versions should use ephemeral TerraformDirectories.
231-
func (td TerraformDirectory) CleanInactiveTemplateVersions(ctx context.Context, logger slog.Logger, fs afero.Fs) error {
230+
// CleanStaleSessions assumes this Layout is the latest active template version.
231+
// Assuming that, any other template version directories found alongside it are
232+
// considered inactive and can be removed. Inactive template versions should use
233+
// ephemeral TerraformDirectories.
234+
func (td Layout) CleanStaleSessions(ctx context.Context, logger slog.Logger, fs afero.Fs, now time.Time) error {
232235
if td.ephemeral {
233-
return nil
236+
// Use the existing cleanup for ephemeral sessions.
237+
return tfpath.FromWorkingDirectory(td.workDirectory).CleanStaleSessions(ctx, logger, fs, now)
234238
}
235239

236240
wd := td.WorkDirectory()

0 commit comments

Comments
 (0)