(untested!) added temp backup api scaffoldings before introducing autobackups and rotations
This commit is contained in:
+21
-4
@@ -5,10 +5,19 @@ import (
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Create creates a new zip archive from src dir content and saves it in dest path.
|
||||
func Create(src, dest string) error {
|
||||
//
|
||||
// You can specify skipPaths to skip/ignore certain directories and files (relative to src)
|
||||
// preventing adding them in the final archive.
|
||||
func Create(src string, dest string, skipPaths ...string) error {
|
||||
if err := os.MkdirAll(filepath.Dir(dest), os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
zf, err := os.Create(dest)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -18,8 +27,8 @@ func Create(src, dest string) error {
|
||||
zw := zip.NewWriter(zf)
|
||||
defer zw.Close()
|
||||
|
||||
if err := zipAddFS(zw, os.DirFS(src)); err != nil {
|
||||
// try to cleanup the created zip file
|
||||
if err := zipAddFS(zw, os.DirFS(src), skipPaths...); err != nil {
|
||||
// try to cleanup at least the created zip file
|
||||
os.Remove(dest)
|
||||
|
||||
return err
|
||||
@@ -29,7 +38,7 @@ func Create(src, dest string) error {
|
||||
}
|
||||
|
||||
// note remove after similar method is added in the std lib (https://github.com/golang/go/issues/54898)
|
||||
func zipAddFS(w *zip.Writer, fsys fs.FS) error {
|
||||
func zipAddFS(w *zip.Writer, fsys fs.FS, skipPaths ...string) error {
|
||||
return fs.WalkDir(fsys, ".", func(name string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -39,6 +48,14 @@ func zipAddFS(w *zip.Writer, fsys fs.FS) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// skip
|
||||
for _, ignore := range skipPaths {
|
||||
if ignore == name ||
|
||||
strings.HasPrefix(name+string(os.PathSeparator), filepath.Clean(ignore)+string(os.PathSeparator)) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
info, err := d.Info()
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -34,8 +34,8 @@ func TestCreateSuccess(t *testing.T) {
|
||||
zipPath := filepath.Join(os.TempDir(), zipName)
|
||||
defer os.RemoveAll(zipPath)
|
||||
|
||||
// zip testDir content
|
||||
if err := archive.Create(testDir, zipPath); err != nil {
|
||||
// zip testDir content (excluding test and a/b/c dir)
|
||||
if err := archive.Create(testDir, zipPath, "a/b/c", "test"); err != nil {
|
||||
t.Fatalf("Failed to create archive: %v", err)
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ func TestCreateSuccess(t *testing.T) {
|
||||
t.Fatalf("Expected zip with name %q, got %q", zipName, name)
|
||||
}
|
||||
|
||||
expectedSize := int64(300)
|
||||
expectedSize := int64(405)
|
||||
if size := info.Size(); size != expectedSize {
|
||||
t.Fatalf("Expected zip with size %d, got %d", expectedSize, size)
|
||||
}
|
||||
@@ -68,17 +68,53 @@ func createTestDir(t *testing.T) string {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
sub1, err := os.OpenFile(filepath.Join(dir, "a/sub1.txt"), os.O_WRONLY|os.O_CREATE, 0644)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
{
|
||||
f, err := os.OpenFile(filepath.Join(dir, "test"), os.O_WRONLY|os.O_CREATE, 0644)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f.Close()
|
||||
}
|
||||
sub1.Close()
|
||||
|
||||
sub2, err := os.OpenFile(filepath.Join(dir, "a/b/c/sub2.txt"), os.O_WRONLY|os.O_CREATE, 0644)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
{
|
||||
f, err := os.OpenFile(filepath.Join(dir, "test2"), os.O_WRONLY|os.O_CREATE, 0644)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f.Close()
|
||||
}
|
||||
|
||||
{
|
||||
f, err := os.OpenFile(filepath.Join(dir, "a/test"), os.O_WRONLY|os.O_CREATE, 0644)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f.Close()
|
||||
}
|
||||
|
||||
{
|
||||
f, err := os.OpenFile(filepath.Join(dir, "a/b/sub1"), os.O_WRONLY|os.O_CREATE, 0644)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f.Close()
|
||||
}
|
||||
|
||||
{
|
||||
f, err := os.OpenFile(filepath.Join(dir, "a/b/c/sub2"), os.O_WRONLY|os.O_CREATE, 0644)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f.Close()
|
||||
}
|
||||
|
||||
{
|
||||
f, err := os.OpenFile(filepath.Join(dir, "a/b/c/sub3"), os.O_WRONLY|os.O_CREATE, 0644)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f.Close()
|
||||
}
|
||||
sub2.Close()
|
||||
|
||||
return dir
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package archive_test
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
@@ -13,15 +14,15 @@ func TestExtractFailure(t *testing.T) {
|
||||
defer os.RemoveAll(testDir)
|
||||
|
||||
missingZipPath := filepath.Join(os.TempDir(), "pb_missing_test.zip")
|
||||
extractPath := filepath.Join(os.TempDir(), "pb_zip_extract")
|
||||
defer os.RemoveAll(extractPath)
|
||||
extractedPath := filepath.Join(os.TempDir(), "pb_zip_extract")
|
||||
defer os.RemoveAll(extractedPath)
|
||||
|
||||
if err := archive.Extract(missingZipPath, extractPath); err == nil {
|
||||
if err := archive.Extract(missingZipPath, extractedPath); err == nil {
|
||||
t.Fatal("Expected Extract to fail due to missing zipPath")
|
||||
}
|
||||
|
||||
if _, err := os.Stat(extractPath); err == nil {
|
||||
t.Fatalf("Expected %q to not be created", extractPath)
|
||||
if _, err := os.Stat(extractedPath); err == nil {
|
||||
t.Fatalf("Expected %q to not be created", extractedPath)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,26 +33,55 @@ func TestExtractSuccess(t *testing.T) {
|
||||
zipPath := filepath.Join(os.TempDir(), "pb_test.zip")
|
||||
defer os.RemoveAll(zipPath)
|
||||
|
||||
extractPath := filepath.Join(os.TempDir(), "pb_zip_extract")
|
||||
defer os.RemoveAll(extractPath)
|
||||
extractedPath := filepath.Join(os.TempDir(), "pb_zip_extract")
|
||||
defer os.RemoveAll(extractedPath)
|
||||
|
||||
// zip testDir content
|
||||
if err := archive.Create(testDir, zipPath); err != nil {
|
||||
// zip testDir content (with exclude)
|
||||
if err := archive.Create(testDir, zipPath, "a/b/c", "test", "sub2"); err != nil {
|
||||
t.Fatalf("Failed to create archive: %v", err)
|
||||
}
|
||||
|
||||
if err := archive.Extract(zipPath, extractPath); err != nil {
|
||||
t.Fatalf("Failed to extract %q in %q", zipPath, extractPath)
|
||||
if err := archive.Extract(zipPath, extractedPath); err != nil {
|
||||
t.Fatalf("Failed to extract %q in %q", zipPath, extractedPath)
|
||||
}
|
||||
|
||||
pathsToCheck := []string{
|
||||
filepath.Join(extractPath, "a/sub1.txt"),
|
||||
filepath.Join(extractPath, "a/b/c/sub2.txt"),
|
||||
}
|
||||
availableFiles := []string{}
|
||||
|
||||
for _, p := range pathsToCheck {
|
||||
if _, err := os.Stat(p); err != nil {
|
||||
t.Fatalf("Failed to retrieve extracted file %q: %v", p, err)
|
||||
walkErr := filepath.WalkDir(extractedPath, func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if d.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
availableFiles = append(availableFiles, path)
|
||||
|
||||
return nil
|
||||
})
|
||||
if walkErr != nil {
|
||||
t.Fatalf("Failed to read the extracted dir: %v", walkErr)
|
||||
}
|
||||
|
||||
expectedFiles := []string{
|
||||
filepath.Join(extractedPath, "test2"),
|
||||
filepath.Join(extractedPath, "a/test"),
|
||||
filepath.Join(extractedPath, "a/b/sub1"),
|
||||
}
|
||||
|
||||
if len(availableFiles) != len(expectedFiles) {
|
||||
t.Fatalf("Expected \n%v, \ngot \n%v", expectedFiles, availableFiles)
|
||||
}
|
||||
|
||||
ExpectedLoop:
|
||||
for _, expected := range expectedFiles {
|
||||
for _, available := range availableFiles {
|
||||
if available == expected {
|
||||
continue ExpectedLoop
|
||||
}
|
||||
}
|
||||
|
||||
t.Fatalf("Missing file %q in \n%v", expected, availableFiles)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,6 +82,12 @@ func NewLocal(dirPath string) (*System, error) {
|
||||
return &System{ctx: ctx, bucket: bucket}, nil
|
||||
}
|
||||
|
||||
// @todo add test
|
||||
// SetContext assigns the specified context to the current filesystem.
|
||||
func (s *System) SetContext(ctx context.Context) {
|
||||
s.ctx = ctx
|
||||
}
|
||||
|
||||
// Close releases any resources used for the related filesystem.
|
||||
func (s *System) Close() error {
|
||||
return s.bucket.Close()
|
||||
@@ -109,6 +115,28 @@ func (s *System) GetFile(fileKey string) (*blob.Reader, error) {
|
||||
return br, nil
|
||||
}
|
||||
|
||||
// List returns a flat list with info for all files under the specified prefix.
|
||||
func (s *System) List(prefix string) ([]*blob.ListObject, error) {
|
||||
files := []*blob.ListObject{}
|
||||
|
||||
iter := s.bucket.List(&blob.ListOptions{
|
||||
Prefix: prefix,
|
||||
})
|
||||
|
||||
for {
|
||||
obj, err := iter.Next(s.ctx)
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
return nil, err
|
||||
}
|
||||
break
|
||||
}
|
||||
files = append(files, obj)
|
||||
}
|
||||
|
||||
return files, nil
|
||||
}
|
||||
|
||||
// Upload writes content into the fileKey location.
|
||||
func (s *System) Upload(content []byte, fileKey string) error {
|
||||
opts := &blob.WriterOptions{
|
||||
|
||||
@@ -401,6 +401,67 @@ func TestFileSystemGetFile(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileSystemList(t *testing.T) {
|
||||
dir := createTestDir(t)
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
fs, err := filesystem.NewLocal(dir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer fs.Close()
|
||||
|
||||
scenarios := []struct {
|
||||
prefix string
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
"",
|
||||
[]string{
|
||||
"image.png",
|
||||
"image.svg",
|
||||
"image_! noext",
|
||||
"style.css",
|
||||
"test/sub1.txt",
|
||||
"test/sub2.txt",
|
||||
},
|
||||
},
|
||||
{
|
||||
"test",
|
||||
[]string{
|
||||
"test/sub1.txt",
|
||||
"test/sub2.txt",
|
||||
},
|
||||
},
|
||||
{
|
||||
"missing",
|
||||
[]string{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
objs, err := fs.List(s.prefix)
|
||||
if err != nil {
|
||||
t.Fatalf("[%s] %v", s.prefix, err)
|
||||
}
|
||||
|
||||
if len(s.expected) != len(objs) {
|
||||
t.Fatalf("[%s] Expected %d files, got \n%v", s.prefix, len(s.expected), objs)
|
||||
}
|
||||
|
||||
ObjsLoop:
|
||||
for _, obj := range objs {
|
||||
for _, name := range s.expected {
|
||||
if name == obj.Key {
|
||||
continue ObjsLoop
|
||||
}
|
||||
}
|
||||
|
||||
t.Fatalf("[%s] Unexpected file %q", s.prefix, obj.Key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileSystemServeSingleRange(t *testing.T) {
|
||||
dir := createTestDir(t)
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
Reference in New Issue
Block a user