2024-04-24 14:18:58 +00:00
|
|
|
package filesystem
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"bytes"
|
|
|
|
|
"context"
|
|
|
|
|
"crypto/rand"
|
|
|
|
|
"crypto/sha256"
|
|
|
|
|
"encoding/hex"
|
2024-05-07 20:50:44 +00:00
|
|
|
"fmt"
|
2024-04-24 14:18:58 +00:00
|
|
|
"io"
|
2024-05-07 20:50:44 +00:00
|
|
|
"os"
|
|
|
|
|
"path/filepath"
|
2024-04-24 14:18:58 +00:00
|
|
|
"testing"
|
2024-08-13 12:27:10 +00:00
|
|
|
"time"
|
2024-04-24 14:18:58 +00:00
|
|
|
|
|
|
|
|
"github.com/fleetdm/fleet/v4/server/fleet"
|
2024-05-07 20:50:44 +00:00
|
|
|
"github.com/google/uuid"
|
2024-04-24 14:18:58 +00:00
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func TestSoftwareInstaller(t *testing.T) {
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
|
|
|
|
|
dir := t.TempDir()
|
|
|
|
|
store, err := NewSoftwareInstallerStore(dir)
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
// get a non-existing installer
|
|
|
|
|
blob, length, err := store.Get(ctx, "no-such-installer")
|
|
|
|
|
require.Error(t, err)
|
|
|
|
|
require.True(t, fleet.IsNotFound(err))
|
|
|
|
|
require.Nil(t, blob)
|
|
|
|
|
require.Zero(t, length)
|
|
|
|
|
|
|
|
|
|
exists, err := store.Exists(ctx, "no-such-installer")
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
require.False(t, exists)
|
|
|
|
|
|
|
|
|
|
createInstallerAndHash := func() ([]byte, string) {
|
|
|
|
|
b := make([]byte, 1024)
|
|
|
|
|
_, err = rand.Read(b)
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
h := sha256.New()
|
|
|
|
|
_, err = h.Write(b)
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
installerID := hex.EncodeToString(h.Sum(nil))
|
|
|
|
|
|
|
|
|
|
return b, installerID
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getAndCheck := func(installerID string, expected []byte) {
|
|
|
|
|
rc, sz, err := store.Get(ctx, installerID)
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
require.EqualValues(t, len(expected), sz)
|
|
|
|
|
defer rc.Close()
|
|
|
|
|
|
|
|
|
|
got, err := io.ReadAll(rc)
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
require.Equal(t, expected, got)
|
|
|
|
|
|
|
|
|
|
exists, err := store.Exists(ctx, installerID)
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
require.True(t, exists)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// store an installer
|
|
|
|
|
b0, id0 := createInstallerAndHash()
|
|
|
|
|
err = store.Put(ctx, id0, bytes.NewReader(b0))
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
// read it back, it should match
|
|
|
|
|
getAndCheck(id0, b0)
|
|
|
|
|
|
|
|
|
|
// store another one
|
|
|
|
|
b1, id1 := createInstallerAndHash()
|
|
|
|
|
err = store.Put(ctx, id1, bytes.NewReader(b1))
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
// read it back, it should match
|
|
|
|
|
getAndCheck(id1, b1)
|
|
|
|
|
|
|
|
|
|
// replace the first one
|
|
|
|
|
err = store.Put(ctx, id0, bytes.NewReader(b0))
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
// read it back, it should still match
|
|
|
|
|
getAndCheck(id0, b0)
|
|
|
|
|
}
|
2024-05-07 20:50:44 +00:00
|
|
|
|
|
|
|
|
func TestSoftwareInstallerCleanup(t *testing.T) {
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
|
|
|
|
|
dir := t.TempDir()
|
|
|
|
|
store, err := NewSoftwareInstallerStore(dir)
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
assertExisting := func(want []string) {
|
|
|
|
|
dirEnts, err := os.ReadDir(filepath.Join(dir, softwareInstallersPrefix))
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
got := make([]string, 0, len(dirEnts))
|
|
|
|
|
for _, de := range dirEnts {
|
|
|
|
|
if de.Type().IsRegular() {
|
|
|
|
|
got = append(got, de.Name())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
require.ElementsMatch(t, want, got)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// cleanup an empty store
|
2024-08-13 12:27:10 +00:00
|
|
|
n, err := store.Cleanup(ctx, nil, time.Now())
|
2024-05-07 20:50:44 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
require.Equal(t, 0, n)
|
|
|
|
|
|
|
|
|
|
// put an installer
|
|
|
|
|
ins0 := uuid.NewString()
|
|
|
|
|
err = store.Put(ctx, ins0, bytes.NewReader([]byte("installer0")))
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
// cleanup but mark it as used
|
2024-08-13 12:27:10 +00:00
|
|
|
n, err = store.Cleanup(ctx, []string{ins0}, time.Now())
|
2024-05-07 20:50:44 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
require.Equal(t, 0, n)
|
|
|
|
|
|
|
|
|
|
assertExisting([]string{ins0})
|
|
|
|
|
|
|
|
|
|
// cleanup but mark it as unused
|
2024-08-13 12:27:10 +00:00
|
|
|
n, err = store.Cleanup(ctx, []string{}, time.Now())
|
2024-05-07 20:50:44 +00:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
require.Equal(t, 1, n)
|
|
|
|
|
|
|
|
|
|
assertExisting(nil)
|
|
|
|
|
|
|
|
|
|
// put a few installers
|
|
|
|
|
installers := []string{uuid.NewString(), uuid.NewString(), uuid.NewString(), uuid.NewString()}
|
|
|
|
|
for i, ins := range installers {
|
|
|
|
|
err = store.Put(ctx, ins, bytes.NewReader([]byte("installer"+fmt.Sprint(i))))
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-13 12:27:10 +00:00
|
|
|
// cleanup with a time in the past, nothing gets removed
|
|
|
|
|
n, err = store.Cleanup(ctx, []string{}, time.Now().Add(-time.Minute))
|
2024-05-07 20:50:44 +00:00
|
|
|
require.NoError(t, err)
|
2024-08-13 12:27:10 +00:00
|
|
|
require.Equal(t, 0, n)
|
|
|
|
|
assertExisting([]string{installers[0], installers[1], installers[2], installers[3]})
|
2024-05-07 20:50:44 +00:00
|
|
|
|
2024-08-13 12:27:10 +00:00
|
|
|
// cleanup in the future, all unused get removed
|
|
|
|
|
n, err = store.Cleanup(ctx, []string{installers[0], installers[2]}, time.Now().Add(time.Minute))
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
require.Equal(t, 2, n)
|
2024-05-07 20:50:44 +00:00
|
|
|
assertExisting([]string{installers[0], installers[2]})
|
|
|
|
|
}
|