|
| 1 | +//go:build linux || darwin |
| 2 | + |
| 3 | +package terraform_test |
| 4 | + |
| 5 | +import ( |
| 6 | +"bytes" |
| 7 | +"context" |
| 8 | +"flag" |
| 9 | +"os" |
| 10 | +"path/filepath" |
| 11 | +"strings" |
| 12 | +"testing" |
| 13 | +"time" |
| 14 | + |
| 15 | +"github.com/google/go-cmp/cmp" |
| 16 | +"github.com/spf13/afero" |
| 17 | +"github.com/stretchr/testify/assert" |
| 18 | +"github.com/stretchr/testify/require" |
| 19 | + |
| 20 | +"cdr.dev/slog" |
| 21 | +"cdr.dev/slog/sloggers/slogtest" |
| 22 | +"github.com/coder/coder/v2/provisioner/terraform" |
| 23 | +"github.com/coder/coder/v2/testutil" |
| 24 | +) |
| 25 | + |
| 26 | +constcachePath="/tmp/coder/provisioner-0/tf" |
| 27 | + |
| 28 | +// updateGoldenFiles is a flag that can be set to update golden files. |
| 29 | +varupdateGoldenFiles=flag.Bool("update",false,"Update golden files") |
| 30 | + |
| 31 | +var ( |
| 32 | +coderPluginPath=filepath.Join("registry.terraform.io","coder","coder","0.11.1","darwin_arm64") |
| 33 | +dockerPluginPath=filepath.Join("registry.terraform.io","kreuzwerker","docker","2.25.0","darwin_arm64") |
| 34 | +) |
| 35 | + |
| 36 | +funcTestPluginCache_Golden(t*testing.T) { |
| 37 | +t.Parallel() |
| 38 | + |
| 39 | +prepare:=func() (afero.Fs, time.Time, slog.Logger) { |
| 40 | +fs:=afero.NewMemMapFs() |
| 41 | +now:=time.Date(2023,time.June,3,4,5,6,0,time.UTC) |
| 42 | +logger:=slogtest.Make(t,nil). |
| 43 | +Leveled(slog.LevelDebug). |
| 44 | +Named("cleanup-test") |
| 45 | +returnfs,now,logger |
| 46 | +} |
| 47 | + |
| 48 | +t.Run("all plugins are stale",func(t*testing.T) { |
| 49 | +t.Parallel() |
| 50 | + |
| 51 | +ctx,cancel:=context.WithTimeout(context.Background(),testutil.WaitShort) |
| 52 | +defercancel() |
| 53 | + |
| 54 | +fs,now,logger:=prepare() |
| 55 | + |
| 56 | +// given |
| 57 | +// This plugin is older than 30 days. |
| 58 | +addPluginFile(t,fs,coderPluginPath,"terraform-provider-coder_v0.11.1",now.Add(-63*24*time.Hour)) |
| 59 | +addPluginFile(t,fs,coderPluginPath,"LICENSE",now.Add(-33*24*time.Hour)) |
| 60 | +addPluginFile(t,fs,coderPluginPath,"README.md",now.Add(-31*24*time.Hour)) |
| 61 | +addPluginFolder(t,fs,coderPluginPath,"new_folder",now.Add(-31*24*time.Hour)) |
| 62 | +addPluginFile(t,fs,coderPluginPath,filepath.Join("new_folder","foobar.tf"),now.Add(-43*24*time.Hour)) |
| 63 | + |
| 64 | +// This plugin is older than 30 days. |
| 65 | +addPluginFile(t,fs,dockerPluginPath,"terraform-provider-docker_v2.25.0",now.Add(-31*24*time.Hour)) |
| 66 | +addPluginFile(t,fs,dockerPluginPath,"LICENSE",now.Add(-32*24*time.Hour)) |
| 67 | +addPluginFile(t,fs,dockerPluginPath,"README.md",now.Add(-33*24*time.Hour)) |
| 68 | + |
| 69 | +// when |
| 70 | +terraform.CleanStaleTerraformPlugins(ctx,cachePath,fs,now,logger) |
| 71 | + |
| 72 | +// then |
| 73 | +diffFileSystem(t,fs) |
| 74 | +}) |
| 75 | + |
| 76 | +t.Run("one plugin is stale",func(t*testing.T) { |
| 77 | +t.Parallel() |
| 78 | + |
| 79 | +ctx,cancel:=context.WithTimeout(context.Background(),testutil.WaitShort) |
| 80 | +defercancel() |
| 81 | + |
| 82 | +fs,now,logger:=prepare() |
| 83 | + |
| 84 | +// given |
| 85 | +addPluginFile(t,fs,coderPluginPath,"terraform-provider-coder_v0.11.1",now.Add(-2*time.Hour)) |
| 86 | +addPluginFile(t,fs,coderPluginPath,"LICENSE",now.Add(-3*time.Hour)) |
| 87 | +addPluginFile(t,fs,coderPluginPath,"README.md",now.Add(-4*time.Hour)) |
| 88 | +addPluginFolder(t,fs,coderPluginPath,"new_folder",now.Add(-5*time.Hour)) |
| 89 | +addPluginFile(t,fs,coderPluginPath,filepath.Join("new_folder","foobar.tf"),now.Add(-4*time.Hour)) |
| 90 | + |
| 91 | +// This plugin is older than 30 days. |
| 92 | +addPluginFile(t,fs,dockerPluginPath,"terraform-provider-docker_v2.25.0",now.Add(-31*24*time.Hour)) |
| 93 | +addPluginFile(t,fs,dockerPluginPath,"LICENSE",now.Add(-32*24*time.Hour)) |
| 94 | +addPluginFile(t,fs,dockerPluginPath,"README.md",now.Add(-33*24*time.Hour)) |
| 95 | + |
| 96 | +// when |
| 97 | +terraform.CleanStaleTerraformPlugins(ctx,cachePath,fs,now,logger) |
| 98 | + |
| 99 | +// then |
| 100 | +diffFileSystem(t,fs) |
| 101 | +}) |
| 102 | + |
| 103 | +t.Run("one plugin file is touched",func(t*testing.T) { |
| 104 | +t.Parallel() |
| 105 | + |
| 106 | +ctx,cancel:=context.WithTimeout(context.Background(),testutil.WaitShort) |
| 107 | +defercancel() |
| 108 | + |
| 109 | +fs,now,logger:=prepare() |
| 110 | + |
| 111 | +// given |
| 112 | +addPluginFile(t,fs,coderPluginPath,"terraform-provider-coder_v0.11.1",now.Add(-63*24*time.Hour)) |
| 113 | +addPluginFile(t,fs,coderPluginPath,"LICENSE",now.Add(-33*24*time.Hour)) |
| 114 | +addPluginFile(t,fs,coderPluginPath,"README.md",now.Add(-31*24*time.Hour)) |
| 115 | +addPluginFolder(t,fs,coderPluginPath,"new_folder",now.Add(-4*time.Hour))// touched |
| 116 | +addPluginFile(t,fs,coderPluginPath,filepath.Join("new_folder","foobar.tf"),now.Add(-43*24*time.Hour)) |
| 117 | + |
| 118 | +addPluginFile(t,fs,dockerPluginPath,"terraform-provider-docker_v2.25.0",now.Add(-31*24*time.Hour)) |
| 119 | +addPluginFile(t,fs,dockerPluginPath,"LICENSE",now.Add(-2*time.Hour)) |
| 120 | +addPluginFile(t,fs,dockerPluginPath,"README.md",now.Add(-33*24*time.Hour)) |
| 121 | + |
| 122 | +// when |
| 123 | +terraform.CleanStaleTerraformPlugins(ctx,cachePath,fs,now,logger) |
| 124 | + |
| 125 | +// then |
| 126 | +diffFileSystem(t,fs) |
| 127 | +}) |
| 128 | +} |
| 129 | + |
| 130 | +funcaddPluginFile(t*testing.T,fs afero.Fs,pluginPathstring,resourcePathstring,accessTime time.Time) { |
| 131 | +err:=fs.MkdirAll(filepath.Join(cachePath,pluginPath),0o755) |
| 132 | +require.NoError(t,err,"can't create test folder for plugin file") |
| 133 | + |
| 134 | +err=fs.Chtimes(filepath.Join(cachePath,pluginPath),accessTime,accessTime) |
| 135 | +require.NoError(t,err,"can't set times") |
| 136 | + |
| 137 | +err=afero.WriteFile(fs,filepath.Join(cachePath,pluginPath,resourcePath), []byte("foo"),0o644) |
| 138 | +require.NoError(t,err,"can't create test file") |
| 139 | + |
| 140 | +err=fs.Chtimes(filepath.Join(cachePath,pluginPath,resourcePath),accessTime,accessTime) |
| 141 | +require.NoError(t,err,"can't set times") |
| 142 | +} |
| 143 | + |
| 144 | +funcaddPluginFolder(t*testing.T,fs afero.Fs,pluginPathstring,folderPathstring,accessTime time.Time) { |
| 145 | +err:=fs.MkdirAll(filepath.Join(cachePath,pluginPath,folderPath),0o755) |
| 146 | +require.NoError(t,err,"can't create plugin folder") |
| 147 | + |
| 148 | +err=fs.Chtimes(filepath.Join(cachePath,pluginPath,folderPath),accessTime,accessTime) |
| 149 | +require.NoError(t,err,"can't set times") |
| 150 | +} |
| 151 | + |
| 152 | +funcdiffFileSystem(t*testing.T,fs afero.Fs) { |
| 153 | +actual:=dumpFileSystem(t,fs) |
| 154 | + |
| 155 | +partialName:=strings.Join(strings.Split(t.Name(),"/")[1:],"_") |
| 156 | +goldenFile:=filepath.Join("testdata","cleanup-stale-plugins",partialName+".txt.golden") |
| 157 | +if*updateGoldenFiles { |
| 158 | +err:=os.MkdirAll(filepath.Dir(goldenFile),0o755) |
| 159 | +require.NoError(t,err,"want no error creating golden file directory") |
| 160 | + |
| 161 | +err=os.WriteFile(goldenFile,actual,0o600) |
| 162 | +require.NoError(t,err,"want no error creating golden file") |
| 163 | +return |
| 164 | +} |
| 165 | + |
| 166 | +want,err:=os.ReadFile(goldenFile) |
| 167 | +require.NoError(t,err,"open golden file, run\"make update-golden-files\" and commit the changes") |
| 168 | +assert.Empty(t,cmp.Diff(want,actual),"golden file mismatch (-want +got): %s, run\"make update-golden-files\", verify and commit the changes",goldenFile) |
| 169 | +} |
| 170 | + |
| 171 | +funcdumpFileSystem(t*testing.T,fs afero.Fs) []byte { |
| 172 | +varbuffer bytes.Buffer |
| 173 | +err:=afero.Walk(fs,"/",func(pathstring,info os.FileInfo,errerror)error { |
| 174 | +_,_=buffer.WriteString(path) |
| 175 | +_=buffer.WriteByte(' ') |
| 176 | +ifinfo.IsDir() { |
| 177 | +_=buffer.WriteByte('d') |
| 178 | +}else { |
| 179 | +_=buffer.WriteByte('f') |
| 180 | +} |
| 181 | +_=buffer.WriteByte('\n') |
| 182 | +returnnil |
| 183 | +}) |
| 184 | +require.NoError(t,err,"can't dump the file system") |
| 185 | +returnbuffer.Bytes() |
| 186 | +} |