Skip to content

Commit

Permalink
Fix DB v6 curator directory creation (#2293)
Browse files Browse the repository at this point in the history
* fix DB dir creation

Signed-off-by: Alex Goodman <[email protected]>

* add dne test for v6 client

Signed-off-by: Alex Goodman <[email protected]>

---------

Signed-off-by: Alex Goodman <[email protected]>
  • Loading branch information
wagoodman authored Dec 2, 2024
1 parent 6a5cfcc commit 796d144
Show file tree
Hide file tree
Showing 4 changed files with 144 additions and 4 deletions.
6 changes: 5 additions & 1 deletion grype/db/v6/distribution/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,10 +128,14 @@ func (c client) isUpdateAvailable(current *v6.Description, candidate *LatestDocu
func (c client) Download(archive Archive, dest string, downloadProgress *progress.Manual) (string, error) {
defer downloadProgress.SetCompleted()

if err := os.MkdirAll(dest, 0700); err != nil {
return "", fmt.Errorf("unable to create db download root dir: %w", err)
}

// note: as much as I'd like to use the afero FS abstraction here, the go-getter library does not support it
tempDir, err := os.MkdirTemp(dest, "grype-db-download")
if err != nil {
return "", fmt.Errorf("unable to create db temp dir: %w", err)
return "", fmt.Errorf("unable to create db client temp dir: %w", err)
}

// download the db to the temp dir
Expand Down
13 changes: 13 additions & 0 deletions grype/db/v6/distribution/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"errors"
"net/http"
"net/http/httptest"
"path/filepath"
"testing"
"time"

Expand Down Expand Up @@ -172,6 +173,18 @@ func TestClient_Download(t *testing.T) {

mg.AssertExpectations(t)
})

t.Run("nested into dir that does not exist", func(t *testing.T) {
c, mg := setup()
mg.On("GetToDir", mock.Anything, "http://localhost:8080/path/to/archive.tar.gz?checksum=checksum123", mock.Anything).Return(nil)

nestedPath := filepath.Join(destDir, "nested")
tempDir, err := c.Download(*archive, nestedPath, &progress.Manual{})
require.NoError(t, err)
require.True(t, len(tempDir) > 0)

mg.AssertExpectations(t)
})
}

func TestClient_IsUpdateAvailable(t *testing.T) {
Expand Down
20 changes: 17 additions & 3 deletions grype/db/v6/installation/curator.go
Original file line number Diff line number Diff line change
Expand Up @@ -287,10 +287,14 @@ func (c curator) Import(dbArchivePath string) error {
mon.Set("unarchiving")
defer mon.SetCompleted()

if err := os.MkdirAll(c.config.DBRootDir, 0700); err != nil {
return fmt.Errorf("unable to create db root dir: %w", err)
}

// note: the temp directory is persisted upon download/validation/activation failure to allow for investigation
tempDir, err := os.MkdirTemp(c.config.DBRootDir, fmt.Sprintf("tmp-v%v-import", db.ModelVersion))
if err != nil {
return fmt.Errorf("unable to create db temp dir: %w", err)
return fmt.Errorf("unable to create db import temp dir: %w", err)
}

err = archiver.Unarchive(dbArchivePath, tempDir)
Expand Down Expand Up @@ -336,8 +340,13 @@ func (c curator) activate(dbDirPath string, mon monitor) error {

mon.Set("activating")

return c.replaceDB(dbDirPath)
}

// replaceDB swaps over to using the given path.
func (c curator) replaceDB(dbDirPath string) error {
dbDir := c.config.DBDirectoryPath()
_, err = c.fs.Stat(dbDir)
_, err := c.fs.Stat(dbDir)
if !os.IsNotExist(err) {
// remove any previous databases
err = c.Delete()
Expand All @@ -346,8 +355,13 @@ func (c curator) activate(dbDirPath string, mon monitor) error {
}
}

// ensure parent db directory exists
if err := c.fs.MkdirAll(filepath.Dir(dbDir), 0700); err != nil {
return fmt.Errorf("unable to create db parent directory: %w", err)
}

// activate the new db cache by moving the temp dir to final location
return os.Rename(dbDirPath, dbDir)
return c.fs.Rename(dbDirPath, dbDir)
}

func (c curator) validateIntegrity(metadata *db.Description, dbFilePath string, validateChecksum bool) (*db.Description, string, error) {
Expand Down
109 changes: 109 additions & 0 deletions grype/db/v6/installation/curator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"time"

"github.com/spf13/afero"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"github.com/wagoodman/go-progress"
Expand Down Expand Up @@ -547,6 +548,114 @@ func TestCurator_ValidateIntegrity(t *testing.T) {
})
}

func TestReplaceDB(t *testing.T) {
cases := []struct {
name string
config Config
expected map[string]string // expected file name to content mapping in the DB dir
init func(t *testing.T, dir string, dbDir string) afero.Fs
wantErr require.ErrorAssertionFunc
verify func(t *testing.T, fs afero.Fs, config Config, expected map[string]string)
}{
{
name: "replace non-existent DB",
config: Config{
DBRootDir: "/test",
},
expected: map[string]string{
"file.txt": "new content",
},
init: func(t *testing.T, dir string, dbDir string) afero.Fs {
fs := afero.NewBasePathFs(afero.NewOsFs(), t.TempDir())
require.NoError(t, fs.MkdirAll(dir, 0700))
require.NoError(t, afero.WriteFile(fs, filepath.Join(dir, "file.txt"), []byte("new content"), 0644))
return fs
},
},
{
name: "replace existing DB",
config: Config{
DBRootDir: "/test",
},
expected: map[string]string{
"new_file.txt": "new content",
},
init: func(t *testing.T, dir string, dbDir string) afero.Fs {
fs := afero.NewBasePathFs(afero.NewOsFs(), t.TempDir())
require.NoError(t, fs.MkdirAll(dbDir, 0700))
require.NoError(t, afero.WriteFile(fs, filepath.Join(dbDir, "old_file.txt"), []byte("old content"), 0644))
require.NoError(t, fs.MkdirAll(dir, 0700))
require.NoError(t, afero.WriteFile(fs, filepath.Join(dir, "new_file.txt"), []byte("new content"), 0644))
return fs
},
},
{
name: "non-existent parent dir creation",
config: Config{
DBRootDir: "/dir/does/not/exist/db3",
},
expected: map[string]string{
"file.txt": "new content",
},
init: func(t *testing.T, dir string, dbDir string) afero.Fs {
fs := afero.NewBasePathFs(afero.NewOsFs(), t.TempDir())
require.NoError(t, fs.MkdirAll(dir, 0700))
require.NoError(t, afero.WriteFile(fs, filepath.Join(dir, "file.txt"), []byte("new content"), 0644))
return fs
},
},
{
name: "error during rename",
config: Config{
DBRootDir: "/test",
},
expected: nil, // no files expected since operation fails
init: func(t *testing.T, dir string, dbDir string) afero.Fs {
fs := afero.NewBasePathFs(afero.NewOsFs(), t.TempDir())
require.NoError(t, fs.MkdirAll(dir, 0700))
require.NoError(t, afero.WriteFile(fs, filepath.Join(dir, "file.txt"), []byte("content"), 0644))
return afero.NewReadOnlyFs(fs)
},
wantErr: require.Error,
verify: func(t *testing.T, fs afero.Fs, config Config, expected map[string]string) {
_, err := fs.Stat(config.DBDirectoryPath())
require.Error(t, err)
},
},
}

for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
if tc.wantErr == nil {
tc.wantErr = require.NoError
}
dbDir := tc.config.DBDirectoryPath()
candidateDir := "/temp/db"
fs := tc.init(t, candidateDir, dbDir)

c := curator{
fs: fs,
config: tc.config,
}

err := c.replaceDB(candidateDir)
tc.wantErr(t, err)
if tc.verify != nil {
tc.verify(t, fs, tc.config, tc.expected)
}
if err != nil {
return
}
for fileName, expectedContent := range tc.expected {
filePath := filepath.Join(tc.config.DBDirectoryPath(), fileName)
actualContent, err := afero.ReadFile(fs, filePath)
assert.NoError(t, err)
assert.Equal(t, expectedContent, string(actualContent))
}
})
}
}

func setupTestDB(t *testing.T, dbDir string) db.ReadWriter {
s, err := db.NewWriter(db.Config{
DBDirPath: dbDir,
Expand Down

0 comments on commit 796d144

Please sign in to comment.