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

Commit6fc7c5a

Browse files
committed
chore: pass through file locations more cleanly
1 parent27759a1 commit6fc7c5a

File tree

2 files changed

+263
-0
lines changed

2 files changed

+263
-0
lines changed

‎provisionersdk/session.go‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ 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"
1819

@@ -67,6 +68,17 @@ func (p *protoServer) Session(stream proto.DRPCProvisioner_SessionStream) error
6768
s.logLevel=proto.LogLevel_value[strings.ToUpper(s.Config.ProvisionerLogLevel)]
6869
}
6970

71+
ifp.opts.Experiments.Enabled(codersdk.ExperimentTerraformWorkspace) {
72+
// TODO: Also indicate if opted into the feature via config
73+
s.Files=x.SessionDir(p.opts.WorkDirectory,sessID,config)
74+
75+
err=s.Files.CleanInactiveTemplateVersions(s.Context(),s.Logger,afero.NewOsFs())
76+
iferr!=nil {
77+
returnxerrors.Errorf("unable to clean inactive versions %q: %w",s.Files.WorkDirectory(),err)
78+
}
79+
}
80+
81+
7082
// Extract the template source archive into the work directory.
7183
err=s.Files.ExtractArchive(s.Context(),s.Logger,afero.NewOsFs(),s.Config)
7284
iferr!=nil {

‎provisionersdk/x/directories.go‎

Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
package x
2+
3+
import (
4+
"archive/tar"
5+
"bytes"
6+
"context"
7+
"fmt"
8+
"hash/crc32"
9+
"io"
10+
"os"
11+
"path/filepath"
12+
"strings"
13+
"time"
14+
15+
"github.com/google/uuid"
16+
"github.com/spf13/afero"
17+
"golang.org/x/xerrors"
18+
19+
"cdr.dev/slog"
20+
"github.com/coder/coder/v2/provisionersdk/proto"
21+
)
22+
23+
funcSessionDir(parentDir,sessIDstring,config*proto.Config)TerraformDirectory {
24+
ifconfig.TemplateId==""||config.TemplateId==uuid.Nil.String()||
25+
config.TemplateVersionId==""||config.TemplateVersionId==uuid.Nil.String() {
26+
returnEphemeralSessionDir(parentDir,sessID)
27+
}
28+
29+
returnTerraformDirectory{
30+
workDirectory:filepath.Join(parentDir,config.TemplateId,config.TemplateVersionId),
31+
sessionID:sessID,
32+
ephemeral:false,
33+
}
34+
}
35+
36+
// EphemeralSessionDir returns the directory name with mandatory prefix. These
37+
// directories are created for each provisioning session and are meant to be
38+
// ephemeral.
39+
funcEphemeralSessionDir(parentDir,sessIDstring)TerraformDirectory {
40+
returnTerraformDirectory{
41+
workDirectory:filepath.Join(parentDir,sessionDirPrefix+sessID),
42+
sessionID:sessID,
43+
ephemeral:true,
44+
}
45+
}
46+
47+
typeTerraformDirectorystruct {
48+
workDirectorystring
49+
sessionIDstring
50+
ephemeralbool
51+
}
52+
53+
const (
54+
// ReadmeFile is the location we look for to extract documentation from template versions.
55+
ReadmeFile="README.md"
56+
57+
sessionDirPrefix="Session"
58+
)
59+
60+
func (tdTerraformDirectory)Cleanup(ctx context.Context,logger slog.Logger) {
61+
varerrerror
62+
path:=td.workDirectory
63+
if!td.ephemeral {
64+
// Non-ephemeral directories only clean up the session subdirectory.
65+
// Leaving in place the wider work directory for reuse.
66+
path=td.StateSessionDirectory()
67+
}
68+
forattempt:=0;attempt<5;attempt++ {
69+
err:=os.RemoveAll(path)
70+
iferr!=nil {
71+
// On Windows, open files cannot be removed.
72+
// When the provisioner daemon is shutting down,
73+
// it may take a few milliseconds for processes to exit.
74+
// See: https://github.com/golang/go/issues/50510
75+
logger.Debug(ctx,"failed to clean work directory; trying again",slog.Error(err))
76+
// TODO: Should we abort earlier if the context is done?
77+
time.Sleep(250*time.Millisecond)
78+
continue
79+
}
80+
logger.Debug(ctx,"cleaned up work directory")
81+
return
82+
}
83+
84+
logger.Error(ctx,"failed to clean up work directory after multiple attempts",
85+
slog.F("path",path),slog.Error(err))
86+
87+
return
88+
}
89+
90+
func (tdTerraformDirectory)WorkDirectory()string {
91+
returntd.workDirectory
92+
}
93+
94+
// StateSessionDirectory follows the same directory structure as Terraform
95+
// workspaces. All build specific state is stored within this directory.
96+
//
97+
// These files should be cleaned up on exit. In the case of a failure, they will
98+
// not collide with other builds since each build uses a unique session ID.
99+
func (tdTerraformDirectory)StateSessionDirectory()string {
100+
returnfilepath.Join(td.workDirectory,"terraform.tfstate.d",td.sessionID)
101+
}
102+
103+
func (tdTerraformDirectory)StateFilePath()string {
104+
returnfilepath.Join(td.StateSessionDirectory(),"terraform.tfstate")
105+
}
106+
107+
func (tdTerraformDirectory)PlanFilePath()string {
108+
returnfilepath.Join(td.StateSessionDirectory(),"terraform.tfplan")
109+
}
110+
111+
func (tdTerraformDirectory)TerraformLockFile()string {
112+
returnfilepath.Join(td.WorkDirectory(),".terraform.lock.hcl")
113+
}
114+
115+
func (tdTerraformDirectory)ReadmeFilePath()string {
116+
returnfilepath.Join(td.WorkDirectory(),ReadmeFile)
117+
}
118+
119+
func (tdTerraformDirectory)ModulesDirectory()string {
120+
returnfilepath.Join(td.WorkDirectory(),".terraform","modules")
121+
}
122+
123+
func (tdTerraformDirectory)ModulesFilePath()string {
124+
returnfilepath.Join(td.ModulesDirectory(),"modules.json")
125+
}
126+
127+
func (tdTerraformDirectory)ExtractArchive(ctx context.Context,logger slog.Logger,cfg*proto.Config)error {
128+
logger.Info(ctx,"unpacking template source archive",
129+
slog.F("size_bytes",len(cfg.TemplateSourceArchive)),
130+
)
131+
132+
err:=os.MkdirAll(td.WorkDirectory(),0o700)
133+
iferr!=nil {
134+
returnxerrors.Errorf("create work directory %q: %w",td.WorkDirectory(),err)
135+
}
136+
137+
reader:=tar.NewReader(bytes.NewBuffer(cfg.TemplateSourceArchive))
138+
// for safety, nil out the reference on Config, since the reader now owns it.
139+
cfg.TemplateSourceArchive=nil
140+
for {
141+
header,err:=reader.Next()
142+
iferr!=nil {
143+
ifxerrors.Is(err,io.EOF) {
144+
break
145+
}
146+
returnxerrors.Errorf("read template source archive: %w",err)
147+
}
148+
logger.Debug(context.Background(),"read archive entry",
149+
slog.F("name",header.Name),
150+
slog.F("mod_time",header.ModTime),
151+
slog.F("size",header.Size))
152+
153+
// Security: don't untar absolute or relative paths, as this can allow a malicious tar to overwrite
154+
// files outside the workdir.
155+
if!filepath.IsLocal(header.Name) {
156+
returnxerrors.Errorf("refusing to extract to non-local path")
157+
}
158+
// nolint: gosec
159+
headerPath:=filepath.Join(td.WorkDirectory(),header.Name)
160+
if!strings.HasPrefix(headerPath,filepath.Clean(td.WorkDirectory())) {
161+
returnxerrors.New("tar attempts to target relative upper directory")
162+
}
163+
mode:=header.FileInfo().Mode()
164+
ifmode==0 {
165+
mode=0o600
166+
}
167+
168+
// Always check for context cancellation before reading the next header.
169+
// This is mainly important for unit tests, since a canceled context means
170+
// the underlying directory is going to be deleted. There still exists
171+
// the small race condition that the context is canceled after this, and
172+
// before the disk write.
173+
ifctx.Err()!=nil {
174+
returnxerrors.Errorf("context canceled: %w",ctx.Err())
175+
}
176+
switchheader.Typeflag {
177+
casetar.TypeDir:
178+
err=os.MkdirAll(headerPath,mode)
179+
iferr!=nil {
180+
returnxerrors.Errorf("mkdir %q: %w",headerPath,err)
181+
}
182+
logger.Debug(context.Background(),"extracted directory",
183+
slog.F("path",headerPath),
184+
slog.F("mode",fmt.Sprintf("%O",mode)))
185+
casetar.TypeReg:
186+
// TODO: If we are overwriting an existing file, that means we are reusing
187+
// the terraform directory. In that case, we should check the file content
188+
// matches what already exists on disk.
189+
file,err:=os.OpenFile(headerPath,os.O_CREATE|os.O_RDWR|os.O_TRUNC,mode)
190+
iferr!=nil {
191+
returnxerrors.Errorf("create file %q (mode %s): %w",headerPath,mode,err)
192+
}
193+
194+
hash:=crc32.NewIEEE()
195+
hashReader:=io.TeeReader(reader,hash)
196+
// Max file size of 10MiB.
197+
size,err:=io.CopyN(file,hashReader,10<<20)
198+
ifxerrors.Is(err,io.EOF) {
199+
err=nil
200+
}
201+
iferr!=nil {
202+
_=file.Close()
203+
returnxerrors.Errorf("copy file %q: %w",headerPath,err)
204+
}
205+
err=file.Close()
206+
iferr!=nil {
207+
returnxerrors.Errorf("close file %q: %s",headerPath,err)
208+
}
209+
logger.Debug(context.Background(),"extracted file",
210+
slog.F("size_bytes",size),
211+
slog.F("path",headerPath),
212+
slog.F("mode",mode),
213+
slog.F("checksum",fmt.Sprintf("%x",hash.Sum(nil))))
214+
}
215+
}
216+
217+
returnnil
218+
}
219+
220+
// CleanInactiveTemplateVersions assumes this TerraformDirectory is the latest
221+
// active template version. Assuming that, any other template version directories
222+
// found alongside it are considered inactive and can be removed. Inactive
223+
// template versions should use ephemeral TerraformDirectories.
224+
func (tdTerraformDirectory)CleanInactiveTemplateVersions(ctx context.Context,logger slog.Logger,fs afero.Fs)error {
225+
iftd.ephemeral {
226+
returnnil
227+
}
228+
229+
wd:=td.WorkDirectory()
230+
templateDir:=filepath.Dir(wd)
231+
versionDir:=filepath.Base(wd)
232+
233+
entries,err:=afero.ReadDir(fs,templateDir)
234+
iferr!=nil {
235+
returnxerrors.Errorf("can't read %q directory: %w",templateDir,err)
236+
}
237+
238+
for_,fi:=rangeentries {
239+
iffi.IsDir()&&fi.Name()==versionDir {
240+
continue
241+
}
242+
243+
oldVerDir:=filepath.Join(wd,fi.Name())
244+
logger.Info(ctx,"remove inactive template version directory",slog.F("version_path",oldVerDir))
245+
err=fs.RemoveAll(oldVerDir)
246+
iferr!=nil {
247+
returnxerrors.Errorf("can't remove inactive template version %q: %w",fi.Name(),err)
248+
}
249+
}
250+
returnnil
251+
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp