From 0633cb3fc9aa88d9efdb60e620497b5f5a1a6514 Mon Sep 17 00:00:00 2001 From: healthjyk Date: Tue, 19 Mar 2024 14:29:50 +0800 Subject: [PATCH] feat: update the workspace storage definition and implementation --- pkg/backend/backend.go | 19 ++++- pkg/workspace/storage.go | 11 ++- pkg/workspace/storages/local.go | 111 ++++++++++---------------- pkg/workspace/storages/local_test.go | 49 +----------- pkg/workspace/storages/mysql.go | 39 ++++++--- pkg/workspace/storages/oss.go | 115 +++++++++++---------------- pkg/workspace/storages/oss_test.go | 62 +++------------ pkg/workspace/storages/s3.go | 114 ++++++++++---------------- pkg/workspace/storages/s3_test.go | 62 +++------------ pkg/workspace/storages/util.go | 5 +- 10 files changed, 204 insertions(+), 383 deletions(-) diff --git a/pkg/backend/backend.go b/pkg/backend/backend.go index c045bc62..7f0eaccb 100644 --- a/pkg/backend/backend.go +++ b/pkg/backend/backend.go @@ -16,15 +16,17 @@ type Backend interface { // todo: add functions to parse storage for spec, the format is like the following: // SpecStorage(projectName, stackName string) spec.Storage + // WorkspaceStorage returns the workspace storage and init default workspace. WorkspaceStorage() (workspace.Storage, error) + // StateStorage returns the state storage. StateStorage(project, stack, workspace string) state.Storage } -// NewBackend creates the Backend with the configuration set in the Kusion configuration file. If the -// backend configuration of the specified name does not exist or is invalid, NewBackend will get failed. -// If the input name is empty, use the current backend. If no current backend is specified or backends -// config is empty, use the default local storage. +// NewBackend creates the Backend with the configuration set in the Kusion configuration file, where the input +// is the configured backend name. If the backend configuration is invalid, NewBackend will get failed. If the +// input name is empty, use the current backend. If no current backend is specified or backends config is empty, +// and the input name is empty, use the default local storage. func NewBackend(name string) (Backend, error) { var emptyCfg bool cfg, err := config.GetConfig() @@ -93,3 +95,12 @@ func NewBackend(name string) (Backend, error) { } return storage, nil } + +// NewWorkspaceStorage calls NewBackend and WorkspaceStorage to new a workspace storage from specified backend. +func NewWorkspaceStorage(backendName string) (workspace.Storage, error) { + bk, err := NewBackend(backendName) + if err != nil { + return nil, err + } + return bk.WorkspaceStorage() +} diff --git a/pkg/workspace/storage.go b/pkg/workspace/storage.go index b4bc5f8b..95b4f7ff 100644 --- a/pkg/workspace/storage.go +++ b/pkg/workspace/storage.go @@ -6,21 +6,20 @@ import ( // Storage is used to provide the storage service for multiple workspaces. type Storage interface { - // Get returns the specified workspace configurations. + // Get returns the workspace configurations. If name is not specified, get the current workspace + // configurations. Get(name string) (*v1.Workspace, error) // Create the workspace. Create(ws *v1.Workspace) error - // Update the workspace. + // Update the workspace. If name is not specified, updates the current workspace, and set the current + // workspace name in the input's name field. Update(ws *v1.Workspace) error - // Delete deletes the specified workspace. + // Delete deletes the workspace. If name is not specified, deletes the current workspace. Delete(name string) error - // Exist returns the specified workspace exists or not. - Exist(name string) (bool, error) - // GetNames returns the names of all the existing workspaces. GetNames() ([]string, error) diff --git a/pkg/workspace/storages/local.go b/pkg/workspace/storages/local.go index f69f6c9d..14497018 100644 --- a/pkg/workspace/storages/local.go +++ b/pkg/workspace/storages/local.go @@ -14,6 +14,8 @@ import ( type LocalStorage struct { // The directory path to store the workspace files. path string + + meta *workspacesMetaData } // NewLocalStorage news local workspace storage and init default workspace. @@ -24,16 +26,19 @@ func NewLocalStorage(path string) (*LocalStorage, error) { if err := os.MkdirAll(s.path, os.ModePerm); err != nil { return nil, fmt.Errorf("create workspace directory failed, %w", err) } + // read workspaces metadata + if err := s.readMeta(); err != nil { + return nil, err + } return s, s.initDefaultWorkspaceIf() } func (s *LocalStorage) Get(name string) (*v1.Workspace, error) { - exist, err := s.Exist(name) - if err != nil { - return nil, err + if name == "" { + name = s.meta.Current } - if !exist { + if !checkWorkspaceExistence(s.meta, name) { return nil, ErrWorkspaceNotExist } @@ -51,28 +56,23 @@ func (s *LocalStorage) Get(name string) (*v1.Workspace, error) { } func (s *LocalStorage) Create(ws *v1.Workspace) error { - meta, err := s.readMeta() - if err != nil { - return err - } - if checkWorkspaceExistence(meta, ws.Name) { + if checkWorkspaceExistence(s.meta, ws.Name) { return ErrWorkspaceAlreadyExist } - if err = s.writeWorkspace(ws); err != nil { + if err := s.writeWorkspace(ws); err != nil { return err } - addAvailableWorkspaces(meta, ws.Name) - return s.writeMeta(meta) + addAvailableWorkspaces(s.meta, ws.Name) + return s.writeMeta() } func (s *LocalStorage) Update(ws *v1.Workspace) error { - exist, err := s.Exist(ws.Name) - if err != nil { - return err + if ws.Name == "" { + ws.Name = s.meta.Current } - if !exist { + if !checkWorkspaceExistence(s.meta, ws.Name) { return ErrWorkspaceNotExist } @@ -80,100 +80,77 @@ func (s *LocalStorage) Update(ws *v1.Workspace) error { } func (s *LocalStorage) Delete(name string) error { - meta, err := s.readMeta() - if err != nil { - return err + if name == "" { + name = s.meta.Current } - if !checkWorkspaceExistence(meta, name) { + if !checkWorkspaceExistence(s.meta, name) { return nil } - if err = os.Remove(filepath.Join(s.path, name+yamlSuffix)); err != nil { + if err := os.Remove(filepath.Join(s.path, name+yamlSuffix)); err != nil { return fmt.Errorf("remove workspace file failed: %w", err) } - removeAvailableWorkspaces(meta, name) - return s.writeMeta(meta) -} - -func (s *LocalStorage) Exist(name string) (bool, error) { - meta, err := s.readMeta() - if err != nil { - return false, err - } - return checkWorkspaceExistence(meta, name), nil + removeAvailableWorkspaces(s.meta, name) + return s.writeMeta() } func (s *LocalStorage) GetNames() ([]string, error) { - meta, err := s.readMeta() - if err != nil { - return nil, err - } - return meta.AvailableWorkspaces, nil + return s.meta.AvailableWorkspaces, nil } func (s *LocalStorage) GetCurrent() (string, error) { - meta, err := s.readMeta() - if err != nil { - return "", err - } - return meta.Current, nil + return s.meta.Current, nil } func (s *LocalStorage) SetCurrent(name string) error { - meta, err := s.readMeta() - if err != nil { - return err - } - if !checkWorkspaceExistence(meta, name) { + if !checkWorkspaceExistence(s.meta, name) { return ErrWorkspaceNotExist } - meta.Current = name - return s.writeMeta(meta) + s.meta.Current = name + return s.writeMeta() } func (s *LocalStorage) initDefaultWorkspaceIf() error { - meta, err := s.readMeta() - if err != nil { - return err - } - if !checkWorkspaceExistence(meta, defaultWorkspace) { + if !checkWorkspaceExistence(s.meta, DefaultWorkspace) { // if there is no default workspace, create one with empty workspace. - if err = s.writeWorkspace(&v1.Workspace{Name: defaultWorkspace}); err != nil { + if err := s.writeWorkspace(&v1.Workspace{Name: DefaultWorkspace}); err != nil { return err } - addAvailableWorkspaces(meta, defaultWorkspace) + addAvailableWorkspaces(s.meta, DefaultWorkspace) } - if meta.Current == "" { - meta.Current = defaultWorkspace + if s.meta.Current == "" { + s.meta.Current = DefaultWorkspace } - return s.writeMeta(meta) + return s.writeMeta() } -func (s *LocalStorage) readMeta() (*workspacesMetaData, error) { +func (s *LocalStorage) readMeta() error { content, err := os.ReadFile(filepath.Join(s.path, metadataFile)) if os.IsNotExist(err) { - return &workspacesMetaData{}, nil + s.meta = &workspacesMetaData{} + return nil } else if err != nil { - return nil, fmt.Errorf("read workspace meta data file failed: %w", err) + return fmt.Errorf("read workspace metadata file failed: %w", err) } meta := &workspacesMetaData{} if err = yaml.Unmarshal(content, meta); err != nil { - return nil, fmt.Errorf("yaml unmarshal workspaces meta data failed: %w", err) + return fmt.Errorf("yaml unmarshal workspaces metadata failed: %w", err) } - return meta, nil + s.meta = meta + return nil } -func (s *LocalStorage) writeMeta(meta *workspacesMetaData) error { - content, err := yaml.Marshal(meta) +func (s *LocalStorage) writeMeta() error { + content, err := yaml.Marshal(s.meta) if err != nil { - return fmt.Errorf("yaml marshal workspaces meta data failed: %w", err) + return fmt.Errorf("yaml marshal workspaces metadata failed: %w", err) } if err = os.WriteFile(filepath.Join(s.path, metadataFile), content, os.ModePerm); err != nil { - return fmt.Errorf("write workspaces meta data file failed: %w", err) + return fmt.Errorf("write workspaces metadata file failed: %w", err) } return nil } diff --git a/pkg/workspace/storages/local_test.go b/pkg/workspace/storages/local_test.go index 00dcc5a6..452fccb0 100644 --- a/pkg/workspace/storages/local_test.go +++ b/pkg/workspace/storages/local_test.go @@ -123,10 +123,7 @@ func TestLocalStorageOperation(t *testing.T) { s, err := NewLocalStorage(tc.path) assert.Equal(t, tc.success, err == nil) if tc.success { - var meta *workspacesMetaData - meta, err = s.readMeta() - assert.NoError(t, err) - assert.Equal(t, tc.expectedMeta, meta) + assert.Equal(t, tc.expectedMeta, s.meta) } if tc.deletePath { _ = os.RemoveAll(tc.path) @@ -203,10 +200,7 @@ func TestLocalStorage_Create(t *testing.T) { err = s.Create(tc.workspace) assert.Equal(t, tc.success, err == nil) if tc.success { - var meta *workspacesMetaData - meta, err = s.readMeta() - assert.NoError(t, err) - assert.Equal(t, tc.expectedMeta, meta) + assert.Equal(t, tc.expectedMeta, s.meta) _ = s.Delete(tc.workspace.Name) } }) @@ -277,50 +271,13 @@ func TestLocalStorage_Delete(t *testing.T) { err = s.Delete(tc.wsName) assert.Equal(t, tc.success, err == nil) if tc.success { - var meta *workspacesMetaData - meta, err = s.readMeta() - assert.NoError(t, err) - assert.Equal(t, tc.expectedMeta, meta) + assert.Equal(t, tc.expectedMeta, s.meta) _ = s.Create(mockWorkspace(tc.wsName)) } }) } } -func TestLocalStorage_Exist(t *testing.T) { - testcases := []struct { - name string - success bool - wsName string - expectedExist bool - }{ - { - name: "exist workspace", - success: true, - wsName: "dev", - expectedExist: true, - }, - { - name: "not exist workspace", - success: true, - wsName: "pre", - expectedExist: false, - }, - } - - for _, tc := range testcases { - t.Run(tc.name, func(t *testing.T) { - s, err := NewLocalStorage(testDataFolder("workspaces")) - assert.NoError(t, err) - exist, err := s.Exist(tc.wsName) - assert.Equal(t, tc.success, err == nil) - if tc.success { - assert.Equal(t, tc.expectedExist, exist) - } - }) - } -} - func TestLocalStorage_GetNames(t *testing.T) { testcases := []struct { name string diff --git a/pkg/workspace/storages/mysql.go b/pkg/workspace/storages/mysql.go index c95e9bb9..4dc5f488 100644 --- a/pkg/workspace/storages/mysql.go +++ b/pkg/workspace/storages/mysql.go @@ -23,18 +23,14 @@ func NewMysqlStorage(db *gorm.DB) (*MysqlStorage, error) { } func (s *MysqlStorage) Get(name string) (*v1.Workspace, error) { - exist, err := checkWorkspaceExistenceInMysql(s.db, name) + w, err := getWorkspaceFromMysql(s.db, name) if err != nil { - return nil, err + return nil, fmt.Errorf("get workspace from mysql database failed: %w", err) } - if !exist { + if w == nil { return nil, ErrWorkspaceNotExist } - w, err := getWorkspaceFromMysql(s.db, name) - if err != nil { - return nil, fmt.Errorf("get workspace from mysql database failed: %w", err) - } ws, err := convertFromMysqlDO(w) if err != nil { return nil, err @@ -60,6 +56,13 @@ func (s *MysqlStorage) Create(ws *v1.Workspace) error { } func (s *MysqlStorage) Update(ws *v1.Workspace) error { + if ws.Name == "" { + name, err := getCurrentWorkspaceNameFromMysql(s.db) + if err != nil { + return err + } + ws.Name = name + } exist, err := checkWorkspaceExistenceInMysql(s.db, ws.Name) if err != nil { return err @@ -76,13 +79,16 @@ func (s *MysqlStorage) Update(ws *v1.Workspace) error { } func (s *MysqlStorage) Delete(name string) error { + if name == "" { + var err error + name, err = getCurrentWorkspaceNameFromMysql(s.db) + if err != nil { + return err + } + } return deleteWorkspaceInMysql(s.db, name) } -func (s *MysqlStorage) Exist(name string) (bool, error) { - return checkWorkspaceExistenceInMysql(s.db, name) -} - func (s *MysqlStorage) GetNames() ([]string, error) { return getWorkspaceNamesFromMysql(s.db) } @@ -104,7 +110,7 @@ func (s *MysqlStorage) SetCurrent(name string) error { } func (s *MysqlStorage) initDefaultWorkspaceIf() error { - exist, err := checkWorkspaceExistenceInMysql(s.db, defaultWorkspace) + exist, err := checkWorkspaceExistenceInMysql(s.db, DefaultWorkspace) if err != nil { return err } @@ -112,7 +118,7 @@ func (s *MysqlStorage) initDefaultWorkspaceIf() error { return nil } - w := &WorkspaceMysqlDO{Name: defaultWorkspace} + w := &WorkspaceMysqlDO{Name: DefaultWorkspace} currentName, err := getCurrentWorkspaceNameFromMysql(s.db) if err != nil { return err @@ -138,8 +144,15 @@ func (s WorkspaceMysqlDO) TableName() string { func getWorkspaceFromMysql(db *gorm.DB, name string) (*WorkspaceMysqlDO, error) { q := &WorkspaceMysqlDO{Name: name} + if name == "" { + isCurrent := true + q.IsCurrent = &isCurrent + } w := &WorkspaceMysqlDO{} result := db.Where(q).First(w) + if errors.Is(result.Error, gorm.ErrRecordNotFound) { + return nil, nil + } return w, result.Error } diff --git a/pkg/workspace/storages/oss.go b/pkg/workspace/storages/oss.go index 4b6e35e1..c521b75c 100644 --- a/pkg/workspace/storages/oss.go +++ b/pkg/workspace/storages/oss.go @@ -17,6 +17,8 @@ type OssStorage struct { // The prefix to store the workspaces files. prefix string + + meta *workspacesMetaData } // NewOssStorage news oss workspace storage and init default workspace. @@ -25,15 +27,17 @@ func NewOssStorage(bucket *oss.Bucket, prefix string) (*OssStorage, error) { bucket: bucket, prefix: prefix, } + if err := s.readMeta(); err != nil { + return nil, err + } return s, s.initDefaultWorkspaceIf() } func (s *OssStorage) Get(name string) (*v1.Workspace, error) { - exist, err := s.Exist(name) - if err != nil { - return nil, err + if name == "" { + name = s.meta.Current } - if !exist { + if !checkWorkspaceExistence(s.meta, name) { return nil, ErrWorkspaceNotExist } @@ -58,28 +62,23 @@ func (s *OssStorage) Get(name string) (*v1.Workspace, error) { } func (s *OssStorage) Create(ws *v1.Workspace) error { - meta, err := s.readMeta() - if err != nil { - return err - } - if checkWorkspaceExistence(meta, ws.Name) { + if checkWorkspaceExistence(s.meta, ws.Name) { return ErrWorkspaceAlreadyExist } - if err = s.writeWorkspace(ws); err != nil { + if err := s.writeWorkspace(ws); err != nil { return err } - addAvailableWorkspaces(meta, ws.Name) - return s.writeMeta(meta) + addAvailableWorkspaces(s.meta, ws.Name) + return s.writeMeta() } func (s *OssStorage) Update(ws *v1.Workspace) error { - exist, err := s.Exist(ws.Name) - if err != nil { - return err + if ws.Name == "" { + ws.Name = s.meta.Current } - if !exist { + if !checkWorkspaceExistence(s.meta, ws.Name) { return ErrWorkspaceNotExist } @@ -87,86 +86,62 @@ func (s *OssStorage) Update(ws *v1.Workspace) error { } func (s *OssStorage) Delete(name string) error { - meta, err := s.readMeta() - if err != nil { - return err + if name == "" { + name = s.meta.Current } - if !checkWorkspaceExistence(meta, name) { + if !checkWorkspaceExistence(s.meta, name) { return nil } - if err = s.bucket.DeleteObject(s.prefix + "/" + name + yamlSuffix); err != nil { + if err := s.bucket.DeleteObject(s.prefix + "/" + name + yamlSuffix); err != nil { return fmt.Errorf("remove workspace in oss failed: %w", err) } - removeAvailableWorkspaces(meta, name) - return s.writeMeta(meta) -} - -func (s *OssStorage) Exist(name string) (bool, error) { - meta, err := s.readMeta() - if err != nil { - return false, err - } - return checkWorkspaceExistence(meta, name), nil + removeAvailableWorkspaces(s.meta, name) + return s.writeMeta() } func (s *OssStorage) GetNames() ([]string, error) { - meta, err := s.readMeta() - if err != nil { - return nil, err - } - return meta.AvailableWorkspaces, nil + return s.meta.AvailableWorkspaces, nil } func (s *OssStorage) GetCurrent() (string, error) { - meta, err := s.readMeta() - if err != nil { - return "", err - } - return meta.Current, nil + return s.meta.Current, nil } func (s *OssStorage) SetCurrent(name string) error { - meta, err := s.readMeta() - if err != nil { - return err - } - if !checkWorkspaceExistence(meta, name) { + if !checkWorkspaceExistence(s.meta, name) { return ErrWorkspaceNotExist } - meta.Current = name - return s.writeMeta(meta) + s.meta.Current = name + return s.writeMeta() } func (s *OssStorage) initDefaultWorkspaceIf() error { - meta, err := s.readMeta() - if err != nil { - return err - } - if !checkWorkspaceExistence(meta, defaultWorkspace) { + if !checkWorkspaceExistence(s.meta, DefaultWorkspace) { // if there is no default workspace, create one with empty workspace. - if err = s.writeWorkspace(&v1.Workspace{Name: defaultWorkspace}); err != nil { + if err := s.writeWorkspace(&v1.Workspace{Name: DefaultWorkspace}); err != nil { return err } - addAvailableWorkspaces(meta, defaultWorkspace) + addAvailableWorkspaces(s.meta, DefaultWorkspace) } - if meta.Current == "" { - meta.Current = defaultWorkspace + if s.meta.Current == "" { + s.meta.Current = DefaultWorkspace } - return s.writeMeta(meta) + return s.writeMeta() } -func (s *OssStorage) readMeta() (*workspacesMetaData, error) { +func (s *OssStorage) readMeta() error { body, err := s.bucket.GetObject(s.prefix + "/" + metadataFile) if err != nil { ossErr, ok := err.(oss.ServiceError) // error code ref: github.com/aliyun/aliyun-oss-go-sdk@v2.1.8+incompatible/oss/bucket.go:553 if ok && ossErr.StatusCode == 404 { - return &workspacesMetaData{}, nil + s.meta = &workspacesMetaData{} + return nil } - return nil, fmt.Errorf("get workspaces meta data from oss failed: %w", err) + return fmt.Errorf("get workspaces metadata from oss failed: %w", err) } defer func() { _ = body.Close() @@ -174,27 +149,29 @@ func (s *OssStorage) readMeta() (*workspacesMetaData, error) { content, err := io.ReadAll(body) if err != nil { - return nil, fmt.Errorf("read workspaces meta data failed: %w", err) + return fmt.Errorf("read workspaces metadata failed: %w", err) } if len(content) == 0 { - return &workspacesMetaData{}, nil + s.meta = &workspacesMetaData{} + return nil } meta := &workspacesMetaData{} if err = yaml.Unmarshal(content, meta); err != nil { - return nil, fmt.Errorf("yaml unmarshal workspaces meta data failed: %w", err) + return fmt.Errorf("yaml unmarshal workspaces metadata failed: %w", err) } - return meta, nil + s.meta = meta + return nil } -func (s *OssStorage) writeMeta(meta *workspacesMetaData) error { - content, err := yaml.Marshal(meta) +func (s *OssStorage) writeMeta() error { + content, err := yaml.Marshal(s.meta) if err != nil { - return fmt.Errorf("yaml marshal workspaces meta data failed: %w", err) + return fmt.Errorf("yaml marshal workspaces metadata failed: %w", err) } if err = s.bucket.PutObject(s.prefix+"/"+metadataFile, bytes.NewReader(content)); err != nil { - return fmt.Errorf("put workspaces meta data to oss failed: %w", err) + return fmt.Errorf("put workspaces metadata to oss failed: %w", err) } return nil } diff --git a/pkg/workspace/storages/oss_test.go b/pkg/workspace/storages/oss_test.go index 9f1e49c2..66d4014b 100644 --- a/pkg/workspace/storages/oss_test.go +++ b/pkg/workspace/storages/oss_test.go @@ -12,12 +12,8 @@ import ( v1 "kusionstack.io/kusion/pkg/apis/core/v1" ) -func mockOssStorage() *OssStorage { - return &OssStorage{bucket: &oss.Bucket{}} -} - -func mockOssStorageReadMeta(meta *workspacesMetaData) { - mockey.Mock((*OssStorage).readMeta).Return(meta, nil).Build() +func mockOssStorage(meta *workspacesMetaData) *OssStorage { + return &OssStorage{bucket: &oss.Bucket{}, meta: meta} } func mockOssStorageWriteMeta() { @@ -50,8 +46,7 @@ func TestOssStorage_Get(t *testing.T) { mockey.PatchConvey("mock oss operation", t, func() { mockey.Mock(oss.Bucket.GetObject).Return(io.NopCloser(bytes.NewReader([]byte(""))), nil).Build() mockey.Mock(io.ReadAll).Return(tc.content, nil).Build() - mockOssStorageReadMeta(mockWorkspacesMetaData()) - workspace, err := mockOssStorage().Get(tc.wsName) + workspace, err := mockOssStorage(mockWorkspacesMetaData()).Get(tc.wsName) assert.Equal(t, tc.success, err == nil) assert.Equal(t, tc.expectedWorkspace, workspace) }) @@ -80,10 +75,9 @@ func TestOssStorage_Create(t *testing.T) { for _, tc := range testcases { t.Run(tc.name, func(t *testing.T) { mockey.PatchConvey("mock oss operation", t, func() { - mockOssStorageReadMeta(mockWorkspacesMetaData()) mockOssStorageWriteMeta() mockOssStorageWriteWorkspace() - err := mockOssStorage().Create(tc.workspace) + err := mockOssStorage(mockWorkspacesMetaData()).Create(tc.workspace) assert.Equal(t, tc.success, err == nil) }) }) @@ -111,9 +105,8 @@ func TestOssStorage_Update(t *testing.T) { for _, tc := range testcases { t.Run(tc.name, func(t *testing.T) { mockey.PatchConvey("mock oss operation", t, func() { - mockOssStorageReadMeta(mockWorkspacesMetaData()) mockOssStorageWriteWorkspace() - err := mockOssStorage().Update(tc.workspace) + err := mockOssStorage(mockWorkspacesMetaData()).Update(tc.workspace) assert.Equal(t, tc.success, err == nil) }) }) @@ -137,43 +130,9 @@ func TestOssStorage_Delete(t *testing.T) { t.Run(tc.name, func(t *testing.T) { mockey.PatchConvey("mock oss operation", t, func() { mockey.Mock(oss.Bucket.DeleteObject).Return(nil).Build() - mockOssStorageReadMeta(mockWorkspacesMetaData()) mockOssStorageWriteMeta() - err := mockOssStorage().Delete(tc.wsName) - assert.Equal(t, tc.success, err == nil) - }) - }) - } -} - -func TestOssStorage_Exist(t *testing.T) { - testcases := []struct { - name string - success bool - wsName string - expectedExist bool - }{ - { - name: "exist workspace", - success: true, - wsName: "dev", - expectedExist: true, - }, - { - name: "not exist workspace", - success: true, - wsName: "pre", - expectedExist: false, - }, - } - - for _, tc := range testcases { - t.Run(tc.name, func(t *testing.T) { - mockey.PatchConvey("mock oss operation", t, func() { - mockOssStorageReadMeta(mockWorkspacesMetaData()) - exist, err := mockOssStorage().Exist(tc.wsName) + err := mockOssStorage(mockWorkspacesMetaData()).Delete(tc.wsName) assert.Equal(t, tc.success, err == nil) - assert.Equal(t, tc.expectedExist, exist) }) }) } @@ -195,8 +154,7 @@ func TestOssStorage_GetNames(t *testing.T) { for _, tc := range testcases { t.Run(tc.name, func(t *testing.T) { mockey.PatchConvey("mock oss operation", t, func() { - mockOssStorageReadMeta(mockWorkspacesMetaData()) - wsNames, err := mockOssStorage().GetNames() + wsNames, err := mockOssStorage(mockWorkspacesMetaData()).GetNames() assert.Equal(t, tc.success, err == nil) if tc.success { assert.Equal(t, tc.expectedNames, wsNames) @@ -222,8 +180,7 @@ func TestOssStorage_GetCurrent(t *testing.T) { for _, tc := range testcases { t.Run(tc.name, func(t *testing.T) { mockey.PatchConvey("mock oss operation", t, func() { - mockOssStorageReadMeta(mockWorkspacesMetaData()) - current, err := mockOssStorage().GetCurrent() + current, err := mockOssStorage(mockWorkspacesMetaData()).GetCurrent() assert.Equal(t, tc.success, err == nil) if tc.success { assert.Equal(t, tc.expectedCurrent, current) @@ -254,9 +211,8 @@ func TestOssStorage_SetCurrent(t *testing.T) { for _, tc := range testcases { t.Run(tc.name, func(t *testing.T) { mockey.PatchConvey("mock oss operation", t, func() { - mockOssStorageReadMeta(mockWorkspacesMetaData()) mockOssStorageWriteMeta() - err := mockOssStorage().SetCurrent(tc.current) + err := mockOssStorage(mockWorkspacesMetaData()).SetCurrent(tc.current) assert.Equal(t, tc.success, err == nil) }) }) diff --git a/pkg/workspace/storages/s3.go b/pkg/workspace/storages/s3.go index 6ed518e9..6d141fae 100644 --- a/pkg/workspace/storages/s3.go +++ b/pkg/workspace/storages/s3.go @@ -20,6 +20,8 @@ type S3Storage struct { // The prefix to store the workspaces files. prefix string + + meta *workspacesMetaData } // NewS3Storage news s3 workspace storage and init default workspace. @@ -29,15 +31,17 @@ func NewS3Storage(s3 *s3.S3, bucket, prefix string) (*S3Storage, error) { bucket: bucket, prefix: prefix, } + if err := s.readMeta(); err != nil { + return nil, err + } return s, s.initDefaultWorkspaceIf() } func (s *S3Storage) Get(name string) (*v1.Workspace, error) { - exist, err := s.Exist(name) - if err != nil { - return nil, err + if name == "" { + name = s.meta.Current } - if !exist { + if !checkWorkspaceExistence(s.meta, name) { return nil, ErrWorkspaceNotExist } @@ -67,28 +71,23 @@ func (s *S3Storage) Get(name string) (*v1.Workspace, error) { } func (s *S3Storage) Create(ws *v1.Workspace) error { - meta, err := s.readMeta() - if err != nil { - return err - } - if checkWorkspaceExistence(meta, ws.Name) { + if checkWorkspaceExistence(s.meta, ws.Name) { return ErrWorkspaceAlreadyExist } - if err = s.writeWorkspace(ws); err != nil { + if err := s.writeWorkspace(ws); err != nil { return err } - addAvailableWorkspaces(meta, ws.Name) - return s.writeMeta(meta) + addAvailableWorkspaces(s.meta, ws.Name) + return s.writeMeta() } func (s *S3Storage) Update(ws *v1.Workspace) error { - exist, err := s.Exist(ws.Name) - if err != nil { - return err + if ws.Name == "" { + ws.Name = s.meta.Current } - if !exist { + if !checkWorkspaceExistence(s.meta, ws.Name) { return ErrWorkspaceNotExist } @@ -96,11 +95,7 @@ func (s *S3Storage) Update(ws *v1.Workspace) error { } func (s *S3Storage) Delete(name string) error { - meta, err := s.readMeta() - if err != nil { - return err - } - if !checkWorkspaceExistence(meta, name) { + if !checkWorkspaceExistence(s.meta, name) { return nil } @@ -108,70 +103,46 @@ func (s *S3Storage) Delete(name string) error { Bucket: aws.String(s.bucket), Key: aws.String(s.prefix + "/" + name + yamlSuffix), } - if _, err = s.s3.DeleteObject(input); err != nil { + if _, err := s.s3.DeleteObject(input); err != nil { return fmt.Errorf("remove workspace in s3 failed: %w", err) } - removeAvailableWorkspaces(meta, name) - return s.writeMeta(meta) -} - -func (s *S3Storage) Exist(name string) (bool, error) { - meta, err := s.readMeta() - if err != nil { - return false, err - } - return checkWorkspaceExistence(meta, name), nil + removeAvailableWorkspaces(s.meta, name) + return s.writeMeta() } func (s *S3Storage) GetNames() ([]string, error) { - meta, err := s.readMeta() - if err != nil { - return nil, err - } - return meta.AvailableWorkspaces, nil + return s.meta.AvailableWorkspaces, nil } func (s *S3Storage) GetCurrent() (string, error) { - meta, err := s.readMeta() - if err != nil { - return "", err - } - return meta.Current, nil + return s.meta.Current, nil } func (s *S3Storage) SetCurrent(name string) error { - meta, err := s.readMeta() - if err != nil { - return err - } - if !checkWorkspaceExistence(meta, name) { + if !checkWorkspaceExistence(s.meta, name) { return ErrWorkspaceNotExist } - meta.Current = name - return s.writeMeta(meta) + s.meta.Current = name + return s.writeMeta() } func (s *S3Storage) initDefaultWorkspaceIf() error { - meta, err := s.readMeta() - if err != nil { - return err - } - if !checkWorkspaceExistence(meta, defaultWorkspace) { + if !checkWorkspaceExistence(s.meta, DefaultWorkspace) { // if there is no default workspace, create one with empty workspace. - if err = s.writeWorkspace(&v1.Workspace{Name: defaultWorkspace}); err != nil { + if err := s.writeWorkspace(&v1.Workspace{Name: DefaultWorkspace}); err != nil { return err } - addAvailableWorkspaces(meta, defaultWorkspace) + addAvailableWorkspaces(s.meta, DefaultWorkspace) } - if meta.Current == "" { - meta.Current = defaultWorkspace + if s.meta.Current == "" { + s.meta.Current = DefaultWorkspace } - return s.writeMeta(meta) + return s.writeMeta() } -func (s *S3Storage) readMeta() (*workspacesMetaData, error) { +func (s *S3Storage) readMeta() error { key := s.prefix + "/" + metadataFile input := &s3.GetObjectInput{ Bucket: aws.String(s.bucket), @@ -181,9 +152,10 @@ func (s *S3Storage) readMeta() (*workspacesMetaData, error) { if err != nil { awsErr, ok := err.(awserr.Error) if ok && awsErr.Code() == s3.ErrCodeNoSuchKey { - return &workspacesMetaData{}, nil + s.meta = &workspacesMetaData{} + return nil } - return nil, fmt.Errorf("get workspaces meta data from s3 failed: %w", err) + return fmt.Errorf("get workspaces meta data from s3 failed: %w", err) } defer func() { _ = output.Body.Close() @@ -191,23 +163,25 @@ func (s *S3Storage) readMeta() (*workspacesMetaData, error) { content, err := io.ReadAll(output.Body) if err != nil { - return nil, fmt.Errorf("read workspaces meta data failed: %w", err) + return fmt.Errorf("read workspaces meta data failed: %w", err) } if len(content) == 0 { - return &workspacesMetaData{}, nil + s.meta = &workspacesMetaData{} + return nil } meta := &workspacesMetaData{} if err = yaml.Unmarshal(content, meta); err != nil { - return nil, fmt.Errorf("yaml unmarshal workspaces meta data failed: %w", err) + return fmt.Errorf("yaml unmarshal workspaces metadata failed: %w", err) } - return meta, nil + s.meta = meta + return nil } -func (s *S3Storage) writeMeta(meta *workspacesMetaData) error { - content, err := yaml.Marshal(meta) +func (s *S3Storage) writeMeta() error { + content, err := yaml.Marshal(s.meta) if err != nil { - return fmt.Errorf("yaml marshal workspaces meta data failed: %w", err) + return fmt.Errorf("yaml marshal workspaces metadata failed: %w", err) } input := &s3.PutObjectInput{ @@ -216,7 +190,7 @@ func (s *S3Storage) writeMeta(meta *workspacesMetaData) error { Body: bytes.NewReader(content), } if _, err = s.s3.PutObject(input); err != nil { - return fmt.Errorf("put workspaces meta data to s3 failed: %w", err) + return fmt.Errorf("put workspaces metadata to s3 failed: %w", err) } return nil } diff --git a/pkg/workspace/storages/s3_test.go b/pkg/workspace/storages/s3_test.go index e2fed52d..a85af09a 100644 --- a/pkg/workspace/storages/s3_test.go +++ b/pkg/workspace/storages/s3_test.go @@ -12,12 +12,8 @@ import ( v1 "kusionstack.io/kusion/pkg/apis/core/v1" ) -func mockS3Storage() *S3Storage { - return &S3Storage{s3: &s3.S3{}} -} - -func mockS3StorageReadMeta(meta *workspacesMetaData) { - mockey.Mock((*S3Storage).readMeta).Return(meta, nil).Build() +func mockS3Storage(meta *workspacesMetaData) *S3Storage { + return &S3Storage{s3: &s3.S3{}, meta: meta} } func mockS3StorageWriteMeta() { @@ -52,8 +48,7 @@ func TestS3Storage_Get(t *testing.T) { Body: io.NopCloser(bytes.NewReader([]byte(""))), }, nil).Build() mockey.Mock(io.ReadAll).Return(tc.content, nil).Build() - mockS3StorageReadMeta(mockWorkspacesMetaData()) - workspace, err := mockS3Storage().Get(tc.wsName) + workspace, err := mockS3Storage(mockWorkspacesMetaData()).Get(tc.wsName) assert.Equal(t, tc.success, err == nil) assert.Equal(t, tc.expectedWorkspace, workspace) }) @@ -82,10 +77,9 @@ func TestS3Storage_Create(t *testing.T) { for _, tc := range testcases { t.Run(tc.name, func(t *testing.T) { mockey.PatchConvey("mock s3 operation", t, func() { - mockS3StorageReadMeta(mockWorkspacesMetaData()) mockS3StorageWriteMeta() mockS3StorageWriteWorkspace() - err := mockS3Storage().Create(tc.workspace) + err := mockS3Storage(mockWorkspacesMetaData()).Create(tc.workspace) assert.Equal(t, tc.success, err == nil) }) }) @@ -113,9 +107,8 @@ func TestS3Storage_Update(t *testing.T) { for _, tc := range testcases { t.Run(tc.name, func(t *testing.T) { mockey.PatchConvey("mock s3 operation", t, func() { - mockS3StorageReadMeta(mockWorkspacesMetaData()) mockS3StorageWriteWorkspace() - err := mockS3Storage().Update(tc.workspace) + err := mockS3Storage(mockWorkspacesMetaData()).Update(tc.workspace) assert.Equal(t, tc.success, err == nil) }) }) @@ -139,43 +132,9 @@ func TestS3Storage_Delete(t *testing.T) { t.Run(tc.name, func(t *testing.T) { mockey.PatchConvey("mock s3 operation", t, func() { mockey.Mock((*s3.S3).DeleteObject).Return(nil, nil).Build() - mockS3StorageReadMeta(mockWorkspacesMetaData()) mockS3StorageWriteMeta() - err := mockS3Storage().Delete(tc.wsName) - assert.Equal(t, tc.success, err == nil) - }) - }) - } -} - -func TestS3Storage_Exist(t *testing.T) { - testcases := []struct { - name string - success bool - wsName string - expectedExist bool - }{ - { - name: "exist workspace", - success: true, - wsName: "dev", - expectedExist: true, - }, - { - name: "not exist workspace", - success: true, - wsName: "pre", - expectedExist: false, - }, - } - - for _, tc := range testcases { - t.Run(tc.name, func(t *testing.T) { - mockey.PatchConvey("mock s3 operation", t, func() { - mockS3StorageReadMeta(mockWorkspacesMetaData()) - exist, err := mockS3Storage().Exist(tc.wsName) + err := mockS3Storage(mockWorkspacesMetaData()).Delete(tc.wsName) assert.Equal(t, tc.success, err == nil) - assert.Equal(t, tc.expectedExist, exist) }) }) } @@ -197,8 +156,7 @@ func TestS3Storage_GetNames(t *testing.T) { for _, tc := range testcases { t.Run(tc.name, func(t *testing.T) { mockey.PatchConvey("mock s3 operation", t, func() { - mockS3StorageReadMeta(mockWorkspacesMetaData()) - wsNames, err := mockS3Storage().GetNames() + wsNames, err := mockS3Storage(mockWorkspacesMetaData()).GetNames() assert.Equal(t, tc.success, err == nil) if tc.success { assert.Equal(t, tc.expectedNames, wsNames) @@ -224,8 +182,7 @@ func TestS3Storage_GetCurrent(t *testing.T) { for _, tc := range testcases { t.Run(tc.name, func(t *testing.T) { mockey.PatchConvey("mock s3 operation", t, func() { - mockS3StorageReadMeta(mockWorkspacesMetaData()) - current, err := mockS3Storage().GetCurrent() + current, err := mockS3Storage(mockWorkspacesMetaData()).GetCurrent() assert.Equal(t, tc.success, err == nil) if tc.success { assert.Equal(t, tc.expectedCurrent, current) @@ -256,9 +213,8 @@ func TestS3Storage_SetCurrent(t *testing.T) { for _, tc := range testcases { t.Run(tc.name, func(t *testing.T) { mockey.PatchConvey("mock s3 operation", t, func() { - mockS3StorageReadMeta(mockWorkspacesMetaData()) mockS3StorageWriteMeta() - err := mockS3Storage().SetCurrent(tc.current) + err := mockS3Storage(mockWorkspacesMetaData()).SetCurrent(tc.current) assert.Equal(t, tc.success, err == nil) }) }) diff --git a/pkg/workspace/storages/util.go b/pkg/workspace/storages/util.go index 3a526c1e..d7e6187c 100644 --- a/pkg/workspace/storages/util.go +++ b/pkg/workspace/storages/util.go @@ -8,9 +8,10 @@ import ( ) const ( + DefaultWorkspace = "default" + workspacesPrefix = "workspaces" workspaceTable = "workspace" - defaultWorkspace = "default" metadataFile = ".metadata.yml" yamlSuffix = ".yaml" ) @@ -70,6 +71,6 @@ func removeAvailableWorkspaces(meta *workspacesMetaData, name string) { // if the current workspace is the removing one, set current to default. if meta.Current == name { - meta.Current = defaultWorkspace + meta.Current = DefaultWorkspace } }