From 96126f640e8bd14edda815e536d0303cadb322e1 Mon Sep 17 00:00:00 2001 From: Chris Gavin Date: Tue, 18 Aug 2020 15:12:15 +0100 Subject: [PATCH] Add a package for working with a cache. g --- go.sum | 3 - internal/cachedirectory/cachedirectory.go | 125 ++++++++++++++++++ .../cachedirectory/cachedirectory_test.go | 83 ++++++++++++ test/test.go | 18 +++ 4 files changed, 226 insertions(+), 3 deletions(-) create mode 100644 internal/cachedirectory/cachedirectory.go create mode 100644 internal/cachedirectory/cachedirectory_test.go create mode 100644 test/test.go diff --git a/go.sum b/go.sum index 9686c1d..ebc36ef 100644 --- a/go.sum +++ b/go.sum @@ -89,7 +89,6 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -102,7 +101,6 @@ go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= @@ -120,7 +118,6 @@ golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/internal/cachedirectory/cachedirectory.go b/internal/cachedirectory/cachedirectory.go new file mode 100644 index 0000000..0682532 --- /dev/null +++ b/internal/cachedirectory/cachedirectory.go @@ -0,0 +1,125 @@ +package cachedirectory + +import ( + "fmt" + "io" + "io/ioutil" + "os" + "path" + "path/filepath" + + "github.com/pkg/errors" +) + +const errorCacheWrongVersion = "The cache you are trying to push was created with an old version of the CodeQL Action Sync tool. Please re-pull it with this version of the tool." +const errorNotACacheOrEmpty = "The cache directory you have selected is not empty, but was not created by the CodeQL Action Sync tool. If you are sure you want to use this directory, please delete it and run the sync tool again." +const errorCacheParentDoesNotExist = "Cannot create cache directory because its parent, does not exist." +const errorPushNonCache = "The directory you have provided does not appear to be valid. Please check it exists and that you have run the `pull` command to populate it." + +type CacheDirectory struct { + path string +} + +func NewCacheDirectory(path string) CacheDirectory { + return CacheDirectory{ + path: path, + } +} + +func isEmptyOrNonExistentDirectory(path string) (bool, error) { + f, err := os.Open(path) + if err != nil { + if os.IsNotExist(err) { + return true, nil + } + return false, errors.Wrap(err, fmt.Sprintf("Could not access directory %s.", path)) + } + defer f.Close() + + _, err = f.Readdirnames(1) + if err != nil { + if err == io.EOF { + return true, nil + } + return false, errors.Wrap(err, fmt.Sprintf("Could not read contents of directory %s.", path)) + } + return false, nil +} + +func (cacheDirectory *CacheDirectory) CheckOrCreateVersionFile(pull bool, version string) error { + cacheVersionFilePath := cacheDirectory.versionFilePath() + cacheVersionBytes, err := ioutil.ReadFile(cacheVersionFilePath) + cacheVersionFileExists := !os.IsNotExist(err) + if err != nil && cacheVersionFileExists { + return errors.Wrap(err, "Could not read version file from cache directory.") + } + cacheVersion := string(cacheVersionBytes) + + if cacheVersion == version { + return nil + } + + if pull { + cacheParentPath := filepath.Dir(cacheDirectory.path) + _, err := os.Stat(cacheParentPath) + if err != nil { + if os.IsNotExist(err) { + return errors.New(errorCacheParentDoesNotExist) + } + return errors.Wrap(err, "Could not access parent path of cache directory.") + } + + if cacheVersionFileExists { + err := os.RemoveAll(cacheDirectory.path) + if err != nil { + return errors.Wrap(err, "Error removing outdated cache directory.") + } + } + + isEmptyOrNonExistent, err := isEmptyOrNonExistentDirectory(cacheDirectory.path) + if err != nil { + return err + } + if isEmptyOrNonExistent { + err := os.Mkdir(cacheDirectory.path, 0755) + if err != nil { + return errors.Wrap(err, "Could not create cache directory.") + } + err = ioutil.WriteFile(cacheVersionFilePath, []byte(version), 0644) + if err != nil { + return errors.Wrap(err, "Could not create cache version file.") + } + return nil + } + return errors.New(errorNotACacheOrEmpty) + } + + if cacheVersionFileExists { + return errors.New(errorCacheWrongVersion) + } + return errors.New(errorPushNonCache) +} + +func (cacheDirectory *CacheDirectory) versionFilePath() string { + return path.Join(cacheDirectory.path, ".codeql-actions-sync-version") +} + +func (cacheDirectory *CacheDirectory) GitPath() string { + return path.Join(cacheDirectory.path, "git") +} + +func (cacheDirectory *CacheDirectory) ReleasesPath() string { + return path.Join(cacheDirectory.path, "releases") +} + +func (cacheDirectory *CacheDirectory) ReleasePath(release string) string { + return path.Join(cacheDirectory.ReleasesPath(), release) +} + +func (cacheDirectory *CacheDirectory) AssetsPath(release string) string { + return path.Join(cacheDirectory.ReleasePath(release), "assets") +} + +func (cacheDirectory *CacheDirectory) MetadataPath(release string) string { + return path.Join(cacheDirectory.ReleasePath(release), "metadata.json") +} diff --git a/internal/cachedirectory/cachedirectory_test.go b/internal/cachedirectory/cachedirectory_test.go new file mode 100644 index 0000000..5229180 --- /dev/null +++ b/internal/cachedirectory/cachedirectory_test.go @@ -0,0 +1,83 @@ +package cachedirectory + +import ( + "io/ioutil" + "os" + "path" + "testing" + + "github.com/github/codeql-action-sync/test" + "github.com/stretchr/testify/require" +) + +const aVersion = "1.0.0" +const aDifferentVersion = "1.0.1" + +func TestCreateCacheDirectoryDuringPullThenReuse(t *testing.T) { + temporaryDirectory := test.CreateTemporaryDirectory(t) + cacheDirectory := NewCacheDirectory(path.Join(temporaryDirectory, "cache")) + err := cacheDirectory.CheckOrCreateVersionFile(true, aVersion) + require.NoError(t, err) + flagFile := path.Join(cacheDirectory.path, "flag") + ioutil.WriteFile(flagFile, []byte("test"), 0644) + err = cacheDirectory.CheckOrCreateVersionFile(true, aVersion) + require.NoError(t, err) + err = cacheDirectory.CheckOrCreateVersionFile(false, aVersion) + require.NoError(t, err) + require.FileExists(t, flagFile) +} + +func TestOverwriteCacheDirectoryIfVersionMismatchDuringPull(t *testing.T) { + temporaryDirectory := test.CreateTemporaryDirectory(t) + cacheDirectory := NewCacheDirectory(path.Join(temporaryDirectory, "cache")) + err := cacheDirectory.CheckOrCreateVersionFile(true, aVersion) + require.NoError(t, err) + flagFile := path.Join(cacheDirectory.path, "flag") + ioutil.WriteFile(flagFile, []byte("test"), 0644) + err = cacheDirectory.CheckOrCreateVersionFile(true, aDifferentVersion) + require.NoError(t, err) + require.NoFileExists(t, flagFile) +} + +func TestErrorIfVersionMismatchDuringPush(t *testing.T) { + temporaryDirectory := test.CreateTemporaryDirectory(t) + cacheDirectory := NewCacheDirectory(path.Join(temporaryDirectory, "cache")) + err := cacheDirectory.CheckOrCreateVersionFile(true, aVersion) + require.NoError(t, err) + err = cacheDirectory.CheckOrCreateVersionFile(false, aDifferentVersion) + require.EqualError(t, err, errorCacheWrongVersion) +} + +func TestErrorIfCacheIsNonEmptyAndNotCache(t *testing.T) { + temporaryDirectory := test.CreateTemporaryDirectory(t) + cacheDirectoryPath := path.Join(temporaryDirectory, "cache") + err := os.MkdirAll(cacheDirectoryPath, 0755) + require.NoError(t, err) + flagFile := path.Join(cacheDirectoryPath, "flag") + ioutil.WriteFile(flagFile, []byte("test"), 0644) + cacheDirectory := NewCacheDirectory(cacheDirectoryPath) + err = cacheDirectory.CheckOrCreateVersionFile(true, aVersion) + require.EqualError(t, err, errorNotACacheOrEmpty) + require.FileExists(t, flagFile) +} + +func TestErrorIfCacheParentDoesNotExist(t *testing.T) { + temporaryDirectory := test.CreateTemporaryDirectory(t) + cacheDirectory := NewCacheDirectory(path.Join(temporaryDirectory, "non-existent-parent", "cache")) + err := cacheDirectory.CheckOrCreateVersionFile(true, aVersion) + require.EqualError(t, err, errorCacheParentDoesNotExist) +} + +func TestErrorIfPushNonCache(t *testing.T) { + temporaryDirectory := test.CreateTemporaryDirectory(t) + cacheDirectory := NewCacheDirectory(temporaryDirectory) + err := cacheDirectory.CheckOrCreateVersionFile(false, aVersion) + require.EqualError(t, err, errorPushNonCache) +} + +func TestErrorIfPushNonExistent(t *testing.T) { + temporaryDirectory := test.CreateTemporaryDirectory(t) + cacheDirectory := NewCacheDirectory(path.Join(temporaryDirectory, "cache")) + err := cacheDirectory.CheckOrCreateVersionFile(false, aVersion) + require.EqualError(t, err, errorPushNonCache) +} diff --git a/test/test.go b/test/test.go new file mode 100644 index 0000000..9dfbfe1 --- /dev/null +++ b/test/test.go @@ -0,0 +1,18 @@ +package test + +import ( + "io/ioutil" + "os" + "testing" + + "github.com/stretchr/testify/require" +) + +func CreateTemporaryDirectory(t *testing.T) string { + directory, err := ioutil.TempDir("", "codeql-action-sync-tests") + require.NoError(t, err) + t.Cleanup(func() { + os.RemoveAll(directory) + }) + return directory +}