mirror of https://github.com/coder/coder.git
fix(provisioner/terraform/cleanup): use mtime instead of atime (#10892)
- Updates plugin staleness check to check mtime instead of atime, as atime has been shown to be unreliable - Updates existing unit test to use a real filesystem as Afero's in-memory FS doesn't support atimes at all
This commit is contained in:
parent
707d0e97d9
commit
0babc3c555
|
@ -7,7 +7,6 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/djherbis/times"
|
||||
"github.com/spf13/afero"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
|
@ -76,16 +75,16 @@ func CleanStaleTerraformPlugins(ctx context.Context, cachePath string, fs afero.
|
|||
// Identify stale plugins
|
||||
var stalePlugins []string
|
||||
for _, pluginPath := range pluginPaths {
|
||||
accessTime, err := latestAccessTime(fs, pluginPath)
|
||||
modTime, err := latestModTime(fs, pluginPath)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("unable to evaluate latest access time for directory %q: %w", pluginPath, err)
|
||||
return xerrors.Errorf("unable to evaluate latest mtime for directory %q: %w", pluginPath, err)
|
||||
}
|
||||
|
||||
if accessTime.Add(staleTerraformPluginRetention).Before(now) {
|
||||
logger.Info(ctx, "plugin directory is stale and will be removed", slog.F("plugin_path", pluginPath))
|
||||
if modTime.Add(staleTerraformPluginRetention).Before(now) {
|
||||
logger.Info(ctx, "plugin directory is stale and will be removed", slog.F("plugin_path", pluginPath), slog.F("mtime", modTime))
|
||||
stalePlugins = append(stalePlugins, pluginPath)
|
||||
} else {
|
||||
logger.Debug(ctx, "plugin directory is not stale", slog.F("plugin_path", pluginPath))
|
||||
logger.Debug(ctx, "plugin directory is not stale", slog.F("plugin_path", pluginPath), slog.F("mtime", modTime))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -127,22 +126,19 @@ func CleanStaleTerraformPlugins(ctx context.Context, cachePath string, fs afero.
|
|||
return nil
|
||||
}
|
||||
|
||||
// latestAccessTime walks recursively through the directory content, and locates
|
||||
// the last accessed file.
|
||||
func latestAccessTime(fs afero.Fs, pluginPath string) (time.Time, error) {
|
||||
// latestModTime walks recursively through the directory content, and locates
|
||||
// the last created/modified file.
|
||||
func latestModTime(fs afero.Fs, pluginPath string) (time.Time, error) {
|
||||
var latest time.Time
|
||||
err := afero.Walk(fs, pluginPath, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
accessTime := info.ModTime() // fallback to modTime if accessTime is not available (afero)
|
||||
if info.Sys() != nil {
|
||||
timeSpec := times.Get(info)
|
||||
accessTime = timeSpec.AccessTime()
|
||||
}
|
||||
if latest.Before(accessTime) {
|
||||
latest = accessTime
|
||||
// atime is not reliable, so always use mtime.
|
||||
modTime := info.ModTime()
|
||||
if modTime.After(latest) {
|
||||
latest = modTime
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
|
|
@ -29,6 +29,7 @@ const cachePath = "/tmp/coder/provisioner-0/tf"
|
|||
var updateGoldenFiles = flag.Bool("update", false, "Update golden files")
|
||||
|
||||
var (
|
||||
now = time.Date(2023, 6, 3, 4, 5, 6, 0, time.UTC)
|
||||
coderPluginPath = filepath.Join("registry.terraform.io", "coder", "coder", "0.11.1", "darwin_arm64")
|
||||
dockerPluginPath = filepath.Join("registry.terraform.io", "kreuzwerker", "docker", "2.25.0", "darwin_arm64")
|
||||
)
|
||||
|
@ -36,13 +37,14 @@ var (
|
|||
func TestPluginCache_Golden(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
prepare := func() (afero.Fs, time.Time, slog.Logger) {
|
||||
fs := afero.NewMemMapFs()
|
||||
now := time.Date(2023, time.June, 3, 4, 5, 6, 0, time.UTC)
|
||||
prepare := func() (afero.Fs, slog.Logger) {
|
||||
// afero.MemMapFs does not modify atimes, so use a real FS instead.
|
||||
tmpDir := t.TempDir()
|
||||
fs := afero.NewBasePathFs(afero.NewOsFs(), tmpDir)
|
||||
logger := slogtest.Make(t, nil).
|
||||
Leveled(slog.LevelDebug).
|
||||
Named("cleanup-test")
|
||||
return fs, now, logger
|
||||
return fs, logger
|
||||
}
|
||||
|
||||
t.Run("all plugins are stale", func(t *testing.T) {
|
||||
|
@ -51,7 +53,7 @@ func TestPluginCache_Golden(t *testing.T) {
|
|||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort)
|
||||
defer cancel()
|
||||
|
||||
fs, now, logger := prepare()
|
||||
fs, logger := prepare()
|
||||
|
||||
// given
|
||||
// This plugin is older than 30 days.
|
||||
|
@ -79,7 +81,7 @@ func TestPluginCache_Golden(t *testing.T) {
|
|||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort)
|
||||
defer cancel()
|
||||
|
||||
fs, now, logger := prepare()
|
||||
fs, logger := prepare()
|
||||
|
||||
// given
|
||||
addPluginFile(t, fs, coderPluginPath, "terraform-provider-coder_v0.11.1", now.Add(-2*time.Hour))
|
||||
|
@ -106,17 +108,17 @@ func TestPluginCache_Golden(t *testing.T) {
|
|||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort)
|
||||
defer cancel()
|
||||
|
||||
fs, now, logger := prepare()
|
||||
fs, logger := prepare()
|
||||
|
||||
// given
|
||||
addPluginFile(t, fs, coderPluginPath, "terraform-provider-coder_v0.11.1", now.Add(-63*24*time.Hour))
|
||||
addPluginFile(t, fs, coderPluginPath, "LICENSE", now.Add(-33*24*time.Hour))
|
||||
addPluginFile(t, fs, coderPluginPath, "README.md", now.Add(-31*24*time.Hour))
|
||||
addPluginFolder(t, fs, coderPluginPath, "new_folder", now.Add(-4*time.Hour)) // touched
|
||||
addPluginFile(t, fs, coderPluginPath, filepath.Join("new_folder", "foobar.tf"), now.Add(-43*24*time.Hour))
|
||||
addPluginFolder(t, fs, coderPluginPath, "new_folder", now.Add(-43*24*time.Hour))
|
||||
addPluginFile(t, fs, coderPluginPath, filepath.Join("new_folder", "foobar.tf"), now.Add(-4*time.Hour)) // touched
|
||||
|
||||
addPluginFile(t, fs, dockerPluginPath, "terraform-provider-docker_v2.25.0", now.Add(-31*24*time.Hour))
|
||||
addPluginFile(t, fs, dockerPluginPath, "LICENSE", now.Add(-2*time.Hour))
|
||||
addPluginFile(t, fs, dockerPluginPath, "LICENSE", now.Add(-2*time.Hour)) // also touched
|
||||
addPluginFile(t, fs, dockerPluginPath, "README.md", now.Add(-33*24*time.Hour))
|
||||
|
||||
// when
|
||||
|
@ -127,25 +129,34 @@ func TestPluginCache_Golden(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func addPluginFile(t *testing.T, fs afero.Fs, pluginPath string, resourcePath string, accessTime time.Time) {
|
||||
func addPluginFile(t *testing.T, fs afero.Fs, pluginPath string, resourcePath string, mtime time.Time) {
|
||||
err := fs.MkdirAll(filepath.Join(cachePath, pluginPath), 0o755)
|
||||
require.NoError(t, err, "can't create test folder for plugin file")
|
||||
|
||||
err = fs.Chtimes(filepath.Join(cachePath, pluginPath), accessTime, accessTime)
|
||||
err = fs.Chtimes(filepath.Join(cachePath, pluginPath), now, mtime)
|
||||
require.NoError(t, err, "can't set times")
|
||||
|
||||
err = afero.WriteFile(fs, filepath.Join(cachePath, pluginPath, resourcePath), []byte("foo"), 0o644)
|
||||
require.NoError(t, err, "can't create test file")
|
||||
|
||||
err = fs.Chtimes(filepath.Join(cachePath, pluginPath, resourcePath), accessTime, accessTime)
|
||||
err = fs.Chtimes(filepath.Join(cachePath, pluginPath, resourcePath), now, mtime)
|
||||
require.NoError(t, err, "can't set times")
|
||||
|
||||
// as creating a file will update mtime of parent, we also want to
|
||||
// set the mtime of parent to match that of the new child.
|
||||
parent, _ := filepath.Split(filepath.Join(cachePath, pluginPath, resourcePath))
|
||||
parentInfo, err := fs.Stat(parent)
|
||||
require.NoError(t, err, "can't stat parent")
|
||||
if parentInfo.ModTime().After(mtime) {
|
||||
require.NoError(t, fs.Chtimes(parent, now, mtime), "can't set mtime of parent to match child")
|
||||
}
|
||||
}
|
||||
|
||||
func addPluginFolder(t *testing.T, fs afero.Fs, pluginPath string, folderPath string, accessTime time.Time) {
|
||||
func addPluginFolder(t *testing.T, fs afero.Fs, pluginPath string, folderPath string, mtime time.Time) {
|
||||
err := fs.MkdirAll(filepath.Join(cachePath, pluginPath, folderPath), 0o755)
|
||||
require.NoError(t, err, "can't create plugin folder")
|
||||
|
||||
err = fs.Chtimes(filepath.Join(cachePath, pluginPath, folderPath), accessTime, accessTime)
|
||||
err = fs.Chtimes(filepath.Join(cachePath, pluginPath, folderPath), now, mtime)
|
||||
require.NoError(t, err, "can't set times")
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue