Skip to content

Commit 670fdac

Browse files
authored
Add storage migration support (#1174)
1 parent 55d9291 commit 670fdac

File tree

10 files changed

+291
-188
lines changed

10 files changed

+291
-188
lines changed

config.example.jsonc

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,6 @@
5454
"secretAccessKey": ""
5555
},
5656

57-
// Cache package raw files in the storage, default is false.
58-
// The server cleans up npm store periodically, to avoid unnecessary installation when accessing
59-
// package raw files, you can enable this option to cache package raw files in the storage.
60-
// Note: this option will increase the storage usage, we recommend to enable it if you are using
61-
// a S3-compatible storage.
62-
"cacheRawFile": false,
63-
6457
// The custom landing page options, default is empty.
6558
// The server will proxy the `/` request to the `origin` server if it's provided.
6659
// If your custom landing page has own assets, you also need to provide those asset paths in the `assets` field.

internal/storage/migration.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package storage
2+
3+
import (
4+
"errors"
5+
"io"
6+
)
7+
8+
// NewMigrationStorage creates a new storage instance that migrates files from one storage to another.
9+
// It first tries to get files from the front storage, and if not found, it retrieves them from the back storage.
10+
func NewMigrationStorage(frontStorage Storage, backStorage Storage) (storage Storage) {
11+
return &migrationStorage{
12+
front: frontStorage,
13+
back: backStorage,
14+
}
15+
}
16+
17+
type migrationStorage struct {
18+
front Storage
19+
back Storage
20+
}
21+
22+
func (m *migrationStorage) Stat(key string) (stat Stat, err error) {
23+
stat, err = m.front.Stat(key)
24+
if err == ErrNotFound {
25+
stat, err = m.back.Stat(key)
26+
}
27+
return
28+
}
29+
30+
func (m *migrationStorage) Get(key string) (content io.ReadCloser, stat Stat, err error) {
31+
content, stat, err = m.front.Get(key)
32+
if err == ErrNotFound {
33+
content, stat, err = m.back.Get(key)
34+
if err == nil {
35+
defer content.Close()
36+
err = m.front.Put(key, content)
37+
if err != nil {
38+
return
39+
}
40+
content, stat, err = m.front.Get(key)
41+
}
42+
}
43+
return
44+
}
45+
46+
func (m *migrationStorage) List(prefix string) (keys []string, err error) {
47+
return nil, errors.New("List operation is not supported in migration storage")
48+
}
49+
50+
func (m *migrationStorage) Put(key string, content io.Reader) (err error) {
51+
return m.front.Put(key, content)
52+
}
53+
54+
func (m *migrationStorage) Delete(key string) (err error) {
55+
m.back.Delete(key)
56+
return m.front.Delete(key)
57+
}
58+
59+
func (m *migrationStorage) DeleteAll(prefix string) (deletedKeys []string, err error) {
60+
m.back.DeleteAll(prefix)
61+
return m.front.DeleteAll(prefix)
62+
}

internal/storage/migration_test.go

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package storage
2+
3+
import (
4+
"bytes"
5+
"io"
6+
"os"
7+
"path"
8+
"testing"
9+
10+
"github.com/ije/gox/crypto/rand"
11+
)
12+
13+
func TestMigrationStorage(t *testing.T) {
14+
root1 := path.Join(os.TempDir(), "storage_test_"+rand.Hex.String(8))
15+
back, err := NewFSStorage(&StorageOptions{Type: "fs", Endpoint: root1})
16+
if err != nil {
17+
t.Fatal(err)
18+
}
19+
defer os.RemoveAll(root1)
20+
21+
root2 := path.Join(os.TempDir(), "storage_test_"+rand.Hex.String(8))
22+
front, err := NewFSStorage(&StorageOptions{Type: "fs", Endpoint: root2})
23+
if err != nil {
24+
t.Fatal(err)
25+
}
26+
defer os.RemoveAll(root2)
27+
28+
migrationStorage := NewMigrationStorage(front, back)
29+
30+
err = back.Put("test.txt", bytes.NewBufferString("Hello World!"))
31+
if err != nil {
32+
t.Fatal(err)
33+
}
34+
35+
_, err = front.Stat("test.txt")
36+
if err != ErrNotFound {
37+
t.Fatal("Expected error, but got nil")
38+
}
39+
40+
_, _, err = front.Get("test.txt")
41+
if err != ErrNotFound {
42+
t.Fatal("Expected error, but got nil")
43+
}
44+
45+
fi, err := migrationStorage.Stat("test.txt")
46+
if err != nil {
47+
t.Fatal(err)
48+
}
49+
50+
if fi.Size() != 12 {
51+
t.Fatalf("invalid file size(%d), shoud be 12", fi.Size())
52+
}
53+
54+
f, fi, err := migrationStorage.Get("test.txt")
55+
if err != nil {
56+
t.Fatal(err)
57+
}
58+
defer f.Close()
59+
60+
if fi.Size() != 12 {
61+
t.Fatalf("invalid file size(%d), shoud be 12", fi.Size())
62+
}
63+
64+
data, err := io.ReadAll(f)
65+
if err != nil {
66+
t.Fatal(err)
67+
}
68+
69+
if string(data) != "Hello World!" {
70+
t.Fatalf("invalid file content('%s'), shoud be 'Hello World!'", string(data))
71+
}
72+
73+
fi, err = front.Stat("test.txt")
74+
if err != nil {
75+
t.Fatal(err)
76+
}
77+
78+
if fi.Size() != 12 {
79+
t.Fatalf("invalid file size(%d), shoud be 12", fi.Size())
80+
}
81+
82+
f, fi, err = front.Get("test.txt")
83+
if err != nil {
84+
t.Fatal(err)
85+
}
86+
defer f.Close()
87+
88+
if fi.Size() != 12 {
89+
t.Fatalf("invalid file size(%d), shoud be 12", fi.Size())
90+
}
91+
92+
data, err = io.ReadAll(f)
93+
if err != nil {
94+
t.Fatal(err)
95+
}
96+
97+
if string(data) != "Hello World!" {
98+
t.Fatalf("invalid file content('%s'), shoud be 'Hello World!'", string(data))
99+
}
100+
}

internal/storage/storage.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,10 @@ type StorageOptions struct {
2020

2121
type Storage interface {
2222
Stat(key string) (stat Stat, err error)
23-
List(prefix string) (keys []string, err error)
2423
Get(key string) (content io.ReadCloser, stat Stat, err error)
24+
List(prefix string) (keys []string, err error)
2525
Put(key string, r io.Reader) error
26-
Delete(keys ...string) error
26+
Delete(key string) error
2727
DeleteAll(prefix string) (deletedKeys []string, err error)
2828
}
2929

internal/storage/storage_fs.go

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,6 @@ func (fs *fsStorage) Stat(key string) (stat Stat, err error) {
4242
return fi, nil
4343
}
4444

45-
func (fs *fsStorage) List(prefix string) (keys []string, err error) {
46-
dir := strings.TrimSuffix(utils.NormalizePathname(prefix)[1:], "/")
47-
return findFiles(filepath.Join(fs.root, dir), dir)
48-
}
49-
5045
func (fs *fsStorage) Get(key string) (content io.ReadCloser, stat Stat, err error) {
5146
filename := filepath.Join(fs.root, key)
5247
file, err := os.Open(filename)
@@ -63,6 +58,11 @@ func (fs *fsStorage) Get(key string) (content io.ReadCloser, stat Stat, err erro
6358
return
6459
}
6560

61+
func (fs *fsStorage) List(prefix string) (keys []string, err error) {
62+
dir := strings.TrimSuffix(utils.NormalizePathname(prefix)[1:], "/")
63+
return findFiles(filepath.Join(fs.root, dir), dir)
64+
}
65+
6666
func (fs *fsStorage) Put(key string, content io.Reader) (err error) {
6767
filename := filepath.Join(fs.root, key)
6868
err = ensureDir(filepath.Dir(filename))
@@ -74,17 +74,17 @@ func (fs *fsStorage) Put(key string, content io.Reader) (err error) {
7474
if err != nil {
7575
return
7676
}
77-
defer file.Close()
7877

7978
_, err = io.Copy(file, content)
79+
file.Close()
80+
if err != nil {
81+
os.Remove(filename) // clean up if error occurs
82+
}
8083
return
8184
}
8285

83-
func (fs *fsStorage) Delete(keys ...string) (err error) {
84-
for _, key := range keys {
85-
os.Remove(filepath.Join(fs.root, key))
86-
}
87-
return
86+
func (fs *fsStorage) Delete(key string) (err error) {
87+
return os.Remove(filepath.Join(fs.root, key))
8888
}
8989

9090
func (fs *fsStorage) DeleteAll(prefix string) (deletedKeys []string, err error) {

internal/storage/storage_fs_test.go

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -18,42 +18,42 @@ func TestFSStorage(t *testing.T) {
1818
}
1919
defer os.RemoveAll(root)
2020

21-
err = fs.Put("foo.txt", bytes.NewBufferString("Hello, World!"))
21+
err = fs.Put("test.txt", bytes.NewBufferString("Hello World!"))
2222
if err != nil {
2323
t.Fatal(err)
2424
}
2525

26-
err = fs.Put("foo/bar.txt", bytes.NewBufferString("Hello, World!"))
26+
err = fs.Put("hello/world.txt", bytes.NewBufferString("Hello World!"))
2727
if err != nil {
2828
t.Fatal(err)
2929
}
3030

31-
fi, err := fs.Stat("foo.txt")
31+
fi, err := fs.Stat("test.txt")
3232
if err != nil {
3333
t.Fatal(err)
3434
}
3535

36-
if fi.Size() != 13 {
37-
t.Fatalf("invalid file size(%d), shoud be 13", fi.Size())
36+
if fi.Size() != 12 {
37+
t.Fatalf("invalid file size(%d), shoud be 12", fi.Size())
3838
}
3939

40-
f, fi, err := fs.Get("foo.txt")
40+
f, fi, err := fs.Get("test.txt")
4141
if err != nil {
4242
t.Fatal(err)
4343
}
4444
defer f.Close()
4545

46-
if fi.Size() != 13 {
47-
t.Fatalf("invalid file size(%d), shoud be 13", fi.Size())
46+
if fi.Size() != 12 {
47+
t.Fatalf("invalid file size(%d), shoud be 12", fi.Size())
4848
}
4949

5050
data, err := io.ReadAll(f)
5151
if err != nil {
5252
t.Fatal(err)
5353
}
5454

55-
if string(data) != "Hello, World!" {
56-
t.Fatalf("invalid file content('%s'), shoud be 'Hello, World!'", string(data))
55+
if string(data) != "Hello World!" {
56+
t.Fatalf("invalid file content('%s'), shoud be 'Hello World!'", string(data))
5757
}
5858

5959
keys, err := fs.List("")
@@ -65,7 +65,7 @@ func TestFSStorage(t *testing.T) {
6565
t.Fatalf("invalid keys count(%d), shoud be 2", len(keys))
6666
}
6767

68-
keys, err = fs.List("foo/")
68+
keys, err = fs.List("hello/")
6969
if err != nil {
7070
t.Fatal(err)
7171
}
@@ -74,26 +74,26 @@ func TestFSStorage(t *testing.T) {
7474
t.Fatalf("invalid keys count(%d), shoud be 1", len(keys))
7575
}
7676

77-
if keys[0] != "foo/bar.txt" {
78-
t.Fatalf("invalid key('%s'), shoud be 'foo/bar.txt'", keys[0])
77+
if keys[0] != "hello/world.txt" {
78+
t.Fatalf("invalid key('%s'), shoud be 'hello/world.txt'", keys[0])
7979
}
8080

81-
err = fs.Delete("foo.txt")
81+
err = fs.Delete("test.txt")
8282
if err != nil {
8383
t.Fatal(err)
8484
}
8585

86-
_, err = fs.Stat("foo.txt")
86+
_, err = fs.Stat("test.txt")
8787
if err != ErrNotFound {
8888
t.Fatalf("File should be not existent")
8989
}
9090

91-
_, _, err = fs.Get("foo.txt")
91+
_, _, err = fs.Get("test.txt")
9292
if err != ErrNotFound {
9393
t.Fatalf("File should be not existent")
9494
}
9595

96-
deletedKeys, err := fs.DeleteAll("foo/")
96+
deletedKeys, err := fs.DeleteAll("hello/")
9797
if err != nil {
9898
t.Fatal(err)
9999
}
@@ -102,8 +102,8 @@ func TestFSStorage(t *testing.T) {
102102
t.Fatalf("invalid deleted keys count(%d), shoud be 1", len(deletedKeys))
103103
}
104104

105-
if deletedKeys[0] != "foo/bar.txt" {
106-
t.Fatalf("invalid deleted key('%s'), shoud be 'foo/bar.txt'", deletedKeys[0])
105+
if deletedKeys[0] != "hello/world.txt" {
106+
t.Fatalf("invalid deleted key('%s'), shoud be 'hello/world.txt'", deletedKeys[0])
107107
}
108108

109109
keys, err = fs.List("")

0 commit comments

Comments
 (0)