Skip to content

Commit

Permalink
feat: add function workspaceStorage in interface backend, and impleme…
Browse files Browse the repository at this point in the history
…nt it in local/mysql/oss/s3 (#937)
  • Loading branch information
healthjyk committed Mar 19, 2024
1 parent 9ad97e4 commit 1aca5e5
Show file tree
Hide file tree
Showing 10 changed files with 166 additions and 8 deletions.
6 changes: 4 additions & 2 deletions pkg/backend/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,16 @@ import (
"kusionstack.io/kusion/pkg/backend/storages"
"kusionstack.io/kusion/pkg/config"
"kusionstack.io/kusion/pkg/engine/state"
"kusionstack.io/kusion/pkg/workspace"
)

// Backend is used to provide the storage service for Workspace, Spec and State.
type Backend interface {
// todo: add functions to parse storage for workspace, spec and state, the format is like the following:
// WorkspaceStorage() workspace.Storage
// todo: add functions to parse storage for spec, the format is like the following:
// SpecStorage(projectName, stackName string) spec.Storage

WorkspaceStorage() (workspace.Storage, error)

StateStorage(project, stack, workspace string) state.Storage
}

Expand Down
6 changes: 6 additions & 0 deletions pkg/backend/storages/local.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
v1 "kusionstack.io/kusion/pkg/apis/core/v1"
"kusionstack.io/kusion/pkg/engine/state"
statestorages "kusionstack.io/kusion/pkg/engine/state/storages"
"kusionstack.io/kusion/pkg/workspace"
workspacestorages "kusionstack.io/kusion/pkg/workspace/storages"
)

// LocalStorage is an implementation of backend.Backend which uses local filesystem as storage.
Expand All @@ -20,3 +22,7 @@ func NewLocalStorage(config *v1.BackendLocalConfig) *LocalStorage {
func (s *LocalStorage) StateStorage(project, stack, workspace string) state.Storage {
return statestorages.NewLocalStorage(statestorages.GenStateFilePath(s.path, project, stack, workspace))
}

func (s *LocalStorage) WorkspaceStorage() (workspace.Storage, error) {
return workspacestorages.NewLocalStorage(workspacestorages.GenWorkspaceDirPath(s.path))
}
34 changes: 32 additions & 2 deletions pkg/backend/storages/local_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@ import (
"path/filepath"
"testing"

"github.com/bytedance/mockey"
"github.com/stretchr/testify/assert"

v1 "kusionstack.io/kusion/pkg/apis/core/v1"
"kusionstack.io/kusion/pkg/engine/state"
statestorages "kusionstack.io/kusion/pkg/engine/state/storages"
"kusionstack.io/kusion/pkg/workspace"
workspacestorages "kusionstack.io/kusion/pkg/workspace/storages"
)

func TestNewLocalStorage(t *testing.T) {
Expand Down Expand Up @@ -40,7 +43,7 @@ func TestLocalStorage_StateStorage(t *testing.T) {
stateStorage state.Storage
}{
{
name: "state storage from s3 backend",
name: "state storage from local backend",
localStorage: &LocalStorage{
path: "kusion",
},
Expand All @@ -56,7 +59,34 @@ func TestLocalStorage_StateStorage(t *testing.T) {
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
stateStorage := tc.localStorage.StateStorage(tc.project, tc.stack, tc.workspace)
assert.Equal(t, stateStorage, tc.stateStorage)
assert.Equal(t, tc.stateStorage, stateStorage)
})
}
}

func TestLocalStorage_WorkspaceStorage(t *testing.T) {
testcases := []struct {
name string
success bool
localStorage *LocalStorage
workspaceStorage workspace.Storage
}{
{
name: "workspace storage from local backend",
success: true,
localStorage: &LocalStorage{
path: "kusion",
},
},
}

for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
mockey.PatchConvey("mock new local workspace storage", t, func() {
mockey.Mock(workspacestorages.NewLocalStorage).Return(&workspacestorages.LocalStorage{}, nil).Build()
_, err := tc.localStorage.WorkspaceStorage()
assert.Equal(t, tc.success, err == nil)
})
})
}
}
6 changes: 6 additions & 0 deletions pkg/backend/storages/mysql.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import (
v1 "kusionstack.io/kusion/pkg/apis/core/v1"
"kusionstack.io/kusion/pkg/engine/state"
statestorages "kusionstack.io/kusion/pkg/engine/state/storages"
"kusionstack.io/kusion/pkg/workspace"
workspacestorages "kusionstack.io/kusion/pkg/workspace/storages"
)

// MysqlStorage is an implementation of backend.Backend which uses mysql as storage.
Expand Down Expand Up @@ -41,3 +43,7 @@ func NewMysqlStorage(config *v1.BackendMysqlConfig) (*MysqlStorage, error) {
func (s *MysqlStorage) StateStorage(project, stack, workspace string) state.Storage {
return statestorages.NewMysqlStorage(s.db, project, stack, workspace)
}

func (s *MysqlStorage) WorkspaceStorage() (workspace.Storage, error) {
return workspacestorages.NewMysqlStorage(s.db)
}
29 changes: 28 additions & 1 deletion pkg/backend/storages/mysql_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
v1 "kusionstack.io/kusion/pkg/apis/core/v1"
"kusionstack.io/kusion/pkg/engine/state"
statestorages "kusionstack.io/kusion/pkg/engine/state/storages"
workspacestorages "kusionstack.io/kusion/pkg/workspace/storages"
)

func TestNewMysqlStorage(t *testing.T) {
Expand Down Expand Up @@ -68,7 +69,33 @@ func TestMysqlStorage_StateStorage(t *testing.T) {
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
stateStorage := tc.mysqlStorage.StateStorage(tc.project, tc.stack, tc.workspace)
assert.Equal(t, stateStorage, tc.stateStorage)
assert.Equal(t, tc.stateStorage, stateStorage)
})
}
}

func TestMysqlStorage_WorkspaceStorage(t *testing.T) {
testcases := []struct {
name string
success bool
mysqlStorage *MysqlStorage
}{
{
name: "workspace storage from mysql backend",
success: true,
mysqlStorage: &MysqlStorage{
db: &gorm.DB{},
},
},
}

for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
mockey.PatchConvey("mock new mysql workspace storage", t, func() {
mockey.Mock(workspacestorages.NewMysqlStorage).Return(&workspacestorages.MysqlStorage{}, nil).Build()
_, err := tc.mysqlStorage.WorkspaceStorage()
assert.Equal(t, tc.success, err == nil)
})
})
}
}
6 changes: 6 additions & 0 deletions pkg/backend/storages/oss.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import (
v1 "kusionstack.io/kusion/pkg/apis/core/v1"
"kusionstack.io/kusion/pkg/engine/state"
statestorages "kusionstack.io/kusion/pkg/engine/state/storages"
"kusionstack.io/kusion/pkg/workspace"
workspacestorages "kusionstack.io/kusion/pkg/workspace/storages"
)

// OssStorage is an implementation of backend.Backend which uses oss as storage.
Expand All @@ -32,3 +34,7 @@ func NewOssStorage(config *v1.BackendOssConfig) (*OssStorage, error) {
func (s *OssStorage) StateStorage(project, stack, workspace string) state.Storage {
return statestorages.NewOssStorage(s.bucket, statestorages.GenGenericOssStateFileKey(s.prefix, project, stack, workspace))
}

func (s *OssStorage) WorkspaceStorage() (workspace.Storage, error) {
return workspacestorages.NewOssStorage(s.bucket, workspacestorages.GenGenericOssWorkspacePrefixKey(s.prefix))
}
30 changes: 29 additions & 1 deletion pkg/backend/storages/oss_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
v1 "kusionstack.io/kusion/pkg/apis/core/v1"
"kusionstack.io/kusion/pkg/engine/state"
statestorages "kusionstack.io/kusion/pkg/engine/state/storages"
workspacestorages "kusionstack.io/kusion/pkg/workspace/storages"
)

func TestNewOssStorage(t *testing.T) {
Expand Down Expand Up @@ -69,7 +70,34 @@ func TestOssStorage_StateStorage(t *testing.T) {
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
stateStorage := tc.ossStorage.StateStorage(tc.project, tc.stack, tc.workspace)
assert.Equal(t, stateStorage, tc.stateStorage)
assert.Equal(t, tc.stateStorage, stateStorage)
})
}
}

func TestOssStorage_WorkspaceStorage(t *testing.T) {
testcases := []struct {
name string
success bool
ossStorage *OssStorage
}{
{
name: "workspace storage from oss backend",
success: true,
ossStorage: &OssStorage{
bucket: &oss.Bucket{},
prefix: "kusion",
},
},
}

for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
mockey.PatchConvey("mock new oss workspace storage", t, func() {
mockey.Mock(workspacestorages.NewOssStorage).Return(&workspacestorages.OssStorage{}, nil).Build()
_, err := tc.ossStorage.WorkspaceStorage()
assert.Equal(t, tc.success, err == nil)
})
})
}
}
6 changes: 6 additions & 0 deletions pkg/backend/storages/s3.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import (
v1 "kusionstack.io/kusion/pkg/apis/core/v1"
"kusionstack.io/kusion/pkg/engine/state"
statestorages "kusionstack.io/kusion/pkg/engine/state/storages"
"kusionstack.io/kusion/pkg/workspace"
workspacestorages "kusionstack.io/kusion/pkg/workspace/storages"
)

// S3Storage is an implementation of backend.Backend which uses s3 as storage.
Expand Down Expand Up @@ -45,3 +47,7 @@ func NewS3Storage(config *v1.BackendS3Config) (*S3Storage, error) {
func (s *S3Storage) StateStorage(project, stack, workspace string) state.Storage {
return statestorages.NewS3Storage(s.s3, s.bucket, statestorages.GenGenericOssStateFileKey(s.prefix, project, stack, workspace))
}

func (s *S3Storage) WorkspaceStorage() (workspace.Storage, error) {
return workspacestorages.NewS3Storage(s.s3, s.bucket, workspacestorages.GenGenericOssWorkspacePrefixKey(s.prefix))
}
29 changes: 29 additions & 0 deletions pkg/backend/storages/s3_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
v1 "kusionstack.io/kusion/pkg/apis/core/v1"
"kusionstack.io/kusion/pkg/engine/state"
statestorages "kusionstack.io/kusion/pkg/engine/state/storages"
workspacestorages "kusionstack.io/kusion/pkg/workspace/storages"
)

func TestNewS3Storage(t *testing.T) {
Expand Down Expand Up @@ -77,3 +78,31 @@ func TestS3Storage_StateStorage(t *testing.T) {
})
}
}

func TestS3Storage_WorkspaceStorage(t *testing.T) {
testcases := []struct {
name string
success bool
s3Storage *S3Storage
}{
{
name: "workspace storage from s3 backend",
success: true,
s3Storage: &S3Storage{
s3: &s3.S3{},
bucket: "infra",
prefix: "kusion",
},
},
}

for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
mockey.PatchConvey("mock new s3 workspace storage", t, func() {
mockey.Mock(workspacestorages.NewS3Storage).Return(&workspacestorages.S3Storage{}, nil).Build()
_, err := tc.s3Storage.WorkspaceStorage()
assert.Equal(t, tc.success, err == nil)
})
})
}
}
22 changes: 20 additions & 2 deletions pkg/workspace/storages/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,38 @@ package storages

import (
"errors"
"fmt"
"path/filepath"
"strings"
)

const (
metadataFile = ".metadata.yml"
workspacesPrefix = "workspaces"
workspaceTable = "workspace"
yamlSuffix = ".yaml"
defaultWorkspace = "default"
metadataFile = ".metadata.yml"
yamlSuffix = ".yaml"
)

var (
ErrWorkspaceNotExist = errors.New("workspace does not exist")
ErrWorkspaceAlreadyExist = errors.New("workspace has already existed")
)

// GenWorkspaceDirPath generates the workspace directory path, which is used for LocalStorage.
func GenWorkspaceDirPath(dir string) string {
return filepath.Join(dir, workspacesPrefix)
}

// GenGenericOssWorkspacePrefixKey generates generic oss workspace prefix key, which is use for OssStorage and S3Storage.
func GenGenericOssWorkspacePrefixKey(prefix string) string {
prefix = strings.TrimPrefix(prefix, "/")
if prefix != "" {
prefix += "/"
}
return fmt.Sprintf("%s%s", prefix, workspacesPrefix)
}

// workspacesMetaData contains the name of current workspace and all workspaces, whose serialization
// result contains in the metadataFile for LocalStorage, OssStorage and S3Storage.
type workspacesMetaData struct {
Expand Down

0 comments on commit 1aca5e5

Please sign in to comment.