Skip to content

feat: clean stale provisioner files #9545

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 27 commits into from
Sep 11, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Some golden files
  • Loading branch information
mtojek committed Sep 7, 2023
commit 44e0517c48996c3a79c51e5979a96fafc757c20b
6 changes: 5 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -570,7 +570,7 @@ coderd/apidoc/swagger.json: $(shell find ./scripts/apidocgen $(FIND_EXCLUSIONS)
./scripts/apidocgen/generate.sh
pnpm run format:write:only ./docs/api ./docs/manifest.json ./coderd/apidoc/swagger.json

update-golden-files: cli/testdata/.gen-golden helm/coder/tests/testdata/.gen-golden helm/provisioner/tests/testdata/.gen-golden scripts/ci-report/testdata/.gen-golden enterprise/cli/testdata/.gen-golden coderd/.gen-golden
update-golden-files: cli/testdata/.gen-golden helm/coder/tests/testdata/.gen-golden helm/provisioner/tests/testdata/.gen-golden scripts/ci-report/testdata/.gen-golden enterprise/cli/testdata/.gen-golden coderd/.gen-golden provisioner/terraform/testdata/.gen-golden
.PHONY: update-golden-files

cli/testdata/.gen-golden: $(wildcard cli/testdata/*.golden) $(wildcard cli/*.tpl) $(GO_SRC_FILES) $(wildcard cli/*_test.go)
Expand All @@ -593,6 +593,10 @@ coderd/.gen-golden: $(wildcard coderd/testdata/*/*.golden) $(GO_SRC_FILES) $(wil
go test ./coderd -run="Test.*Golden$$" -update
touch "$@"

provisioner/terraform/testdata/.gen-golden: $(wildcard provisioner/terraform/testdata/*/*.golden) $(GO_SRC_FILES) $(wildcard provisioner/terraform/*_test.go)
go test ./provisioner/terraform -run="Test.*Golden$$" -update
touch "$@"

scripts/ci-report/testdata/.gen-golden: $(wildcard scripts/ci-report/testdata/*) $(wildcard scripts/ci-report/*.go)
go test ./scripts/ci-report -run=TestOutputMatchesGoldenFile -update
touch "$@"
Expand Down
10 changes: 7 additions & 3 deletions provisioner/terraform/cleanup.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ func CleanStaleTerraformPlugins(ctx context.Context, cachePath string, fs afero.

// Compact the plugin structure by removing empty directories.
wd := stalePluginPath
level := 4 // <repositoryURL>/<company>/<plugin>/<version>/<distribution>
level := 5 // <repositoryURL>/<company>/<plugin>/<version>/<distribution>
for {
level--
if level == 0 {
Expand Down Expand Up @@ -136,8 +136,12 @@ func latestAccessTime(fs afero.Fs, pluginPath string) (time.Time, error) {
return err
}

timeSpec := times.Get(info)
accessTime := timeSpec.AccessTime()
var 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
}
Expand Down
175 changes: 168 additions & 7 deletions provisioner/terraform/cleanup_test.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
package terraform_test

import (
"bytes"
"context"
"flag"
"os"
"path/filepath"
"strings"
"testing"
"time"

"github.com/google/go-cmp/cmp"
"github.com/spf13/afero"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"cdr.dev/slog"
"cdr.dev/slog/sloggers/slogtest"
"github.com/coder/coder/v2/provisioner/terraform"
"github.com/coder/coder/v2/testutil"
Expand All @@ -16,14 +25,166 @@ const (
cachePath = "/tmp/coder/provisioner-0/tf"
)

func TestOnePluginIsStale(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort)
defer cancel()
// updateGoldenFiles is a flag that can be set to update golden files.
var updateGoldenFiles = flag.Bool("update", false, "Update golden files")

fs := afero.NewMemMapFs()
now := time.Now()
logger := slogtest.Make(t, nil).Named("cleanup-test")
func TestPluginCache_Golden(t *testing.T) {
t.Parallel()

terraform.CleanStaleTerraformPlugins(ctx, cachePath, fs, now, logger)
t.Run("all plugins are stale", func(t *testing.T) {
t.Parallel()

ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort)
defer cancel()

// prepare
fs := afero.NewMemMapFs()
now := time.Date(2023, time.June, 3, 4, 5, 6, 0, time.UTC)

logger := slogtest.Make(t, nil).
Leveled(slog.LevelDebug).
Named("cleanup-test")

// given
// This plugin is older than 30 days.
addPluginFile(t, fs, "registry.terraform.io/coder/coder/0.11.1/darwin_arm64", "terraform-provider-coder_v0.11.1", now.Add(-63*24*time.Hour))
addPluginFile(t, fs, "registry.terraform.io/coder/coder/0.11.1/darwin_arm64", "LICENSE", now.Add(-33*24*time.Hour))
addPluginFile(t, fs, "registry.terraform.io/coder/coder/0.11.1/darwin_arm64", "README.md", now.Add(-31*24*time.Hour))
addPluginFolder(t, fs, "registry.terraform.io/coder/coder/0.11.1/darwin_arm64", "new_folder", now.Add(-31*24*time.Hour))
addPluginFile(t, fs, "registry.terraform.io/coder/coder/0.11.1/darwin_arm64", "new_folder/foobar.tf", now.Add(-43*24*time.Hour))

// This plugin is older than 30 days.
addPluginFile(t, fs, "registry.terraform.io/kreuzwerker/docker/2.25.0/darwin_arm64", "terraform-provider-docker_v2.25.0", now.Add(-31*24*time.Hour))
addPluginFile(t, fs, "registry.terraform.io/kreuzwerker/docker/2.25.0/darwin_arm64", "LICENSE", now.Add(-32*24*time.Hour))
addPluginFile(t, fs, "registry.terraform.io/kreuzwerker/docker/2.25.0/darwin_arm64", "README.md", now.Add(-33*24*time.Hour))

// when
terraform.CleanStaleTerraformPlugins(ctx, cachePath, fs, now, logger)

// then
diffFileSystem(t, fs)
})

t.Run("one plugin is stale", func(t *testing.T) {
t.Parallel()

ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort)
defer cancel()

// prepare
fs := afero.NewMemMapFs()
now := time.Date(2023, time.June, 3, 4, 5, 6, 0, time.UTC)

logger := slogtest.Make(t, nil).
Leveled(slog.LevelDebug).
Named("cleanup-test")

// given
addPluginFile(t, fs, "registry.terraform.io/coder/coder/0.11.1/darwin_arm64", "terraform-provider-coder_v0.11.1", now.Add(-2*time.Hour))
addPluginFile(t, fs, "registry.terraform.io/coder/coder/0.11.1/darwin_arm64", "LICENSE", now.Add(-3*time.Hour))
addPluginFile(t, fs, "registry.terraform.io/coder/coder/0.11.1/darwin_arm64", "README.md", now.Add(-4*time.Hour))
addPluginFolder(t, fs, "registry.terraform.io/coder/coder/0.11.1/darwin_arm64", "new_folder", now.Add(-5*time.Hour))
addPluginFile(t, fs, "registry.terraform.io/coder/coder/0.11.1/darwin_arm64", "new_folder/foobar.tf", now.Add(-4*time.Hour))

// This plugin is older than 30 days.
addPluginFile(t, fs, "registry.terraform.io/kreuzwerker/docker/2.25.0/darwin_arm64", "terraform-provider-docker_v2.25.0", now.Add(-31*24*time.Hour))
addPluginFile(t, fs, "registry.terraform.io/kreuzwerker/docker/2.25.0/darwin_arm64", "LICENSE", now.Add(-32*24*time.Hour))
addPluginFile(t, fs, "registry.terraform.io/kreuzwerker/docker/2.25.0/darwin_arm64", "README.md", now.Add(-33*24*time.Hour))

// when
terraform.CleanStaleTerraformPlugins(ctx, cachePath, fs, now, logger)

// then
diffFileSystem(t, fs)
})

t.Run("one plugin file is touched", func(t *testing.T) {
t.Parallel()

ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort)
defer cancel()

// prepare
fs := afero.NewMemMapFs()
now := time.Date(2023, time.June, 3, 4, 5, 6, 0, time.UTC)

logger := slogtest.Make(t, nil).
Leveled(slog.LevelDebug).
Named("cleanup-test")

// given
addPluginFile(t, fs, "registry.terraform.io/coder/coder/0.11.1/darwin_arm64", "terraform-provider-coder_v0.11.1", now.Add(-63*24*time.Hour))
addPluginFile(t, fs, "registry.terraform.io/coder/coder/0.11.1/darwin_arm64", "LICENSE", now.Add(-33*24*time.Hour))
addPluginFile(t, fs, "registry.terraform.io/coder/coder/0.11.1/darwin_arm64", "README.md", now.Add(-31*24*time.Hour))
addPluginFolder(t, fs, "registry.terraform.io/coder/coder/0.11.1/darwin_arm64", "new_folder", now.Add(-4*time.Hour))
addPluginFile(t, fs, "registry.terraform.io/coder/coder/0.11.1/darwin_arm64", "new_folder/foobar.tf", now.Add(-43*24*time.Hour))

addPluginFile(t, fs, "registry.terraform.io/kreuzwerker/docker/2.25.0/darwin_arm64", "terraform-provider-docker_v2.25.0", now.Add(-31*24*time.Hour))
addPluginFile(t, fs, "registry.terraform.io/kreuzwerker/docker/2.25.0/darwin_arm64", "LICENSE", now.Add(-2*time.Hour))
addPluginFile(t, fs, "registry.terraform.io/kreuzwerker/docker/2.25.0/darwin_arm64", "README.md", now.Add(-33*24*time.Hour))

// when
terraform.CleanStaleTerraformPlugins(ctx, cachePath, fs, now, logger)

// then
diffFileSystem(t, fs)
})
}

func addPluginFile(t *testing.T, fs afero.Fs, pluginPath string, resourcePath string, accessTime time.Time) {
err := fs.MkdirAll(filepath.Join(cachePath, pluginPath), 0755)
require.NoError(t, err, "can't create test folder for plugin file")

err = fs.Chtimes(filepath.Join(cachePath, pluginPath), accessTime, accessTime)
require.NoError(t, err, "can't set times")

err = afero.WriteFile(fs, filepath.Join(cachePath, pluginPath, resourcePath), []byte("foo"), 0644)
require.NoError(t, err, "can't create test file")

err = fs.Chtimes(filepath.Join(cachePath, pluginPath, resourcePath), accessTime, accessTime)
require.NoError(t, err, "can't set times")
}

func addPluginFolder(t *testing.T, fs afero.Fs, pluginPath string, folderPath string, accessTime time.Time) {
err := fs.MkdirAll(filepath.Join(cachePath, pluginPath, folderPath), 0755)
require.NoError(t, err, "can't create plugin folder")

err = fs.Chtimes(filepath.Join(cachePath, pluginPath, folderPath), accessTime, accessTime)
require.NoError(t, err, "can't set times")
}

func diffFileSystem(t *testing.T, fs afero.Fs) {
actual := dumpFileSystem(t, fs)

partialName := strings.Join(strings.Split(t.Name(), "/")[1:], "_")
goldenFile := filepath.Join("testdata", "cleanup-stale-plugins", partialName+".txt.golden")
if *updateGoldenFiles {
err := os.MkdirAll(filepath.Dir(goldenFile), 0o755)
require.NoError(t, err, "want no error creating golden file directory")

err = os.WriteFile(goldenFile, actual, 0600)
require.NoError(t, err, "want no error creating golden file")
return
}

want, err := os.ReadFile(goldenFile)
require.NoError(t, err, "open golden file, run \"make update-golden-files\" and commit the changes")
assert.Empty(t, cmp.Diff(want, actual), "golden file mismatch (-want +got): %s, run \"make update-golden-files\", verify and commit the changes", goldenFile)
}

func dumpFileSystem(t *testing.T, fs afero.Fs) []byte {
var buffer bytes.Buffer
err := afero.Walk(fs, "/", func(path string, info os.FileInfo, err error) error {
_, _ = buffer.WriteString(path)
_ = buffer.WriteByte(' ')
if info.IsDir() {
_ = buffer.WriteByte('d')
} else {
_ = buffer.WriteByte('f')
}
_ = buffer.WriteByte('\n')
return nil
})
require.NoError(t, err, "can't dump the file system")
return buffer.Bytes()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/ d
/tmp d
/tmp/coder d
/tmp/coder/provisioner-0 d
/tmp/coder/provisioner-0/tf d
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/ d
/tmp d
/tmp/coder d
/tmp/coder/provisioner-0 d
/tmp/coder/provisioner-0/tf d
/tmp/coder/provisioner-0/tf/registry.terraform.io d
/tmp/coder/provisioner-0/tf/registry.terraform.io/coder d
/tmp/coder/provisioner-0/tf/registry.terraform.io/coder/coder d
/tmp/coder/provisioner-0/tf/registry.terraform.io/coder/coder/0.11.1 d
/tmp/coder/provisioner-0/tf/registry.terraform.io/coder/coder/0.11.1/darwin_arm64 d
/tmp/coder/provisioner-0/tf/registry.terraform.io/coder/coder/0.11.1/darwin_arm64/LICENSE f
/tmp/coder/provisioner-0/tf/registry.terraform.io/coder/coder/0.11.1/darwin_arm64/README.md f
/tmp/coder/provisioner-0/tf/registry.terraform.io/coder/coder/0.11.1/darwin_arm64/new_folder d
/tmp/coder/provisioner-0/tf/registry.terraform.io/coder/coder/0.11.1/darwin_arm64/new_folder/foobar.tf f
/tmp/coder/provisioner-0/tf/registry.terraform.io/coder/coder/0.11.1/darwin_arm64/terraform-provider-coder_v0.11.1 f
/tmp/coder/provisioner-0/tf/registry.terraform.io/kreuzwerker d
/tmp/coder/provisioner-0/tf/registry.terraform.io/kreuzwerker/docker d
/tmp/coder/provisioner-0/tf/registry.terraform.io/kreuzwerker/docker/2.25.0 d
/tmp/coder/provisioner-0/tf/registry.terraform.io/kreuzwerker/docker/2.25.0/darwin_arm64 d
/tmp/coder/provisioner-0/tf/registry.terraform.io/kreuzwerker/docker/2.25.0/darwin_arm64/LICENSE f
/tmp/coder/provisioner-0/tf/registry.terraform.io/kreuzwerker/docker/2.25.0/darwin_arm64/README.md f
/tmp/coder/provisioner-0/tf/registry.terraform.io/kreuzwerker/docker/2.25.0/darwin_arm64/terraform-provider-docker_v2.25.0 f
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/ d
/tmp d
/tmp/coder d
/tmp/coder/provisioner-0 d
/tmp/coder/provisioner-0/tf d
/tmp/coder/provisioner-0/tf/registry.terraform.io d
/tmp/coder/provisioner-0/tf/registry.terraform.io/coder d
/tmp/coder/provisioner-0/tf/registry.terraform.io/coder/coder d
/tmp/coder/provisioner-0/tf/registry.terraform.io/coder/coder/0.11.1 d
/tmp/coder/provisioner-0/tf/registry.terraform.io/coder/coder/0.11.1/darwin_arm64 d
/tmp/coder/provisioner-0/tf/registry.terraform.io/coder/coder/0.11.1/darwin_arm64/LICENSE f
/tmp/coder/provisioner-0/tf/registry.terraform.io/coder/coder/0.11.1/darwin_arm64/README.md f
/tmp/coder/provisioner-0/tf/registry.terraform.io/coder/coder/0.11.1/darwin_arm64/new_folder d
/tmp/coder/provisioner-0/tf/registry.terraform.io/coder/coder/0.11.1/darwin_arm64/new_folder/foobar.tf f
/tmp/coder/provisioner-0/tf/registry.terraform.io/coder/coder/0.11.1/darwin_arm64/terraform-provider-coder_v0.11.1 f
6 changes: 6 additions & 0 deletions provisioner/terraform/testdata/generate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ for d in */; do
continue
fi

# This directory is used for a different purpose (quick workaround).
if [[ $name == "cleanup-stale-plugins" ]]; then
popd
continue
fi

terraform init -upgrade
terraform plan -out terraform.tfplan
terraform show -json ./terraform.tfplan | jq >"$name".tfplan.json
Expand Down