Skip to content

Commit

Permalink
feat: add backend definition and new function (#895)
Browse files Browse the repository at this point in the history
  • Loading branch information
healthjyk committed Mar 8, 2024
1 parent cf24848 commit c0c6265
Show file tree
Hide file tree
Showing 4 changed files with 258 additions and 0 deletions.
90 changes: 90 additions & 0 deletions pkg/backend/backend.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package backend

import (
"errors"
"fmt"

v1 "kusionstack.io/kusion/pkg/apis/core/v1"
"kusionstack.io/kusion/pkg/backend/storages"
"kusionstack.io/kusion/pkg/config"
)

// 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
// StateStorage(projectName, stackName string) state.Storage
// SpecStorage(projectName, stackName string) spec.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.
func NewBackend(name string) (Backend, error) {
var emptyCfg bool
cfg, err := config.GetConfig()
if errors.Is(err, config.ErrEmptyConfig) {
emptyCfg = true
} else if err != nil {
return nil, err
}
if cfg.Backends == nil {
emptyCfg = true
}

var bkCfg *v1.BackendConfig
if name == "" {
if emptyCfg || cfg.Backends.Current == "" {
// if empty backends config or empty current backend, use the default storage
return storages.NewDefaultStorage(), nil
}
name = cfg.Backends.Current
bkCfg = cfg.Backends.Backends[name]
} else {
bkCfg = cfg.Backends.Backends[name]
if bkCfg == nil {
return nil, fmt.Errorf("config of backend %s does not exist", name)
}
}

var storage Backend
switch bkCfg.Type {
case v1.BackendTypeLocal:
bkConfig := bkCfg.ToLocalBackend()
return storages.NewLocalStorage(bkConfig), nil
case v1.BackendTypeMysql:
bkConfig := bkCfg.ToMysqlBackend()
storages.CompleteMysqlConfig(bkConfig)
if err = storages.ValidateMysqlConfig(bkConfig); err != nil {
return nil, fmt.Errorf("invalid config of backend %s: %w", name, err)
}
storage, err = storages.NewMysqlStorage(bkConfig)
if err != nil {
return nil, fmt.Errorf("new mysql storage of backend %s failed, %w", name, err)
}
case v1.BackendTypeOss:
bkConfig := bkCfg.ToOssBackend()
storages.CompleteOssConfig(bkConfig)
if err = storages.ValidateOssConfig(bkConfig); err != nil {
return nil, fmt.Errorf("invalid config of backend %s: %w", name, err)
}
storage, err = storages.NewOssStorage(bkConfig)
if err != nil {
return nil, fmt.Errorf("new oss storage of backend %s failed, %w", name, err)
}
case v1.BackendTypeS3:
bkConfig := bkCfg.ToS3Backend()
storages.CompleteS3Config(bkConfig)
if err = storages.ValidateS3Config(bkConfig); err != nil {
return nil, fmt.Errorf("invalid config of backend %s: %w", name, err)
}
storage, err = storages.NewS3Storage(bkConfig)
if err != nil {
return nil, fmt.Errorf("new s3 storage of backend %s failed, %w", name, err)
}
default:
return nil, fmt.Errorf("invalid type %s of backend %s", bkCfg.Type, name)
}
return storage, nil
}
158 changes: 158 additions & 0 deletions pkg/backend/backend_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
package backend

import (
"os"
"reflect"
"testing"

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

v1 "kusionstack.io/kusion/pkg/apis/core/v1"
"kusionstack.io/kusion/pkg/backend/storages"
"kusionstack.io/kusion/pkg/config"
)

func mockConfig() *v1.Config {
return &v1.Config{
Backends: &v1.BackendConfigs{
Current: "pre",
Backends: map[string]*v1.BackendConfig{
"dev": {
Type: v1.BackendTypeLocal,
Configs: map[string]any{
v1.BackendLocalPath: "/etc",
},
},
"pre": {
Type: v1.BackendTypeMysql,
Configs: map[string]any{
v1.BackendMysqlDBName: "kusion",
v1.BackendMysqlUser: "kk",
v1.BackendMysqlHost: "127.0.0.1",
v1.BackendMysqlPort: 3306,
},
},
"staging": {
Type: v1.BackendTypeOss,
Configs: map[string]any{
v1.BackendGenericOssEndpoint: "http://oss-cn-hangzhou.aliyuncs.com",
v1.BackendGenericOssBucket: "kusion",
},
},
"prod": {
Type: v1.BackendTypeS3,
Configs: map[string]any{
v1.BackendGenericOssBucket: "kusion",
},
},
},
},
}
}

func mockNewStorage() {
mockey.Mock(storages.NewLocalStorage).Return(&storages.LocalStorage{}).Build()
mockey.Mock(storages.NewMysqlStorage).Return(&storages.MysqlStorage{}, nil).Build()
mockey.Mock(storages.NewOssStorage).Return(&storages.OssStorage{}, nil).Build()
mockey.Mock(storages.NewS3Storage).Return(&storages.S3Storage{}, nil).Build()
}

func TestNewBackend(t *testing.T) {
testcases := []struct {
name string
success bool
cfg *v1.Config
envs map[string]string
bkName string
storage Backend
}{
{
name: "new default backend",
success: true,
cfg: func() *v1.Config {
cfg := mockConfig()
cfg.Backends.Current = ""
return cfg
}(),
envs: nil,
bkName: "",
storage: &storages.LocalStorage{},
},
{
name: "new current backend",
success: true,
cfg: mockConfig(),
envs: nil,
bkName: "",
storage: &storages.MysqlStorage{},
},
{
name: "new local backend",
success: true,
cfg: mockConfig(),
envs: nil,
bkName: "dev",
storage: &storages.LocalStorage{},
},
{
name: "new local backend",
success: true,
cfg: mockConfig(),
envs: nil,
bkName: "dev",
storage: &storages.LocalStorage{},
},
{
name: "new mysql backend",
success: true,
cfg: mockConfig(),
envs: map[string]string{
v1.EnvBackendMysqlPassword: "fake-password",
},
bkName: "pre",
storage: &storages.MysqlStorage{},
},
{
name: "new oss backend",
success: true,
cfg: mockConfig(),
envs: map[string]string{
v1.EnvOssAccessKeyID: "fake-ak",
v1.EnvOssAccessKeySecret: "fake-sk",
},
bkName: "staging",
storage: &storages.OssStorage{},
},
{
name: "new s3 backend",
success: true,
cfg: mockConfig(),
envs: map[string]string{
v1.EnvAwsRegion: "us-east-1",
v1.EnvAwsAccessKeyID: "fake-ak",
v1.EnvAwsSecretAccessKey: "fake-sk",
},
bkName: "prod",
storage: &storages.S3Storage{},
},
}

for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
mockey.PatchConvey("mock config", t, func() {
mockey.Mock(config.GetConfig).Return(tc.cfg, nil).Build()
mockNewStorage()
for k, v := range tc.envs {
_ = os.Setenv(k, v)
}
storage, err := NewBackend(tc.bkName)
assert.Equal(t, tc.success, err == nil)
assert.Equal(t, reflect.TypeOf(tc.storage), reflect.TypeOf(storage))
for k := range tc.envs {
_ = os.Unsetenv(k)
}
})
})
}
}
6 changes: 6 additions & 0 deletions pkg/backend/storages/completion_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ func TestCompleteOssConfig(t *testing.T) {
}
CompleteOssConfig(tc.config)
assert.Equal(t, tc.completeConfig, tc.config)
for k := range tc.envs {
_ = os.Unsetenv(k)
}
})
}
}
Expand Down Expand Up @@ -127,6 +130,9 @@ func TestCompleteS3Config(t *testing.T) {
}
CompleteS3Config(tc.config)
assert.Equal(t, tc.completeConfig, tc.config)
for k := range tc.envs {
_ = os.Unsetenv(k)
}
})
}
}
4 changes: 4 additions & 0 deletions pkg/backend/storages/local.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,7 @@ type LocalStorage struct {
func NewLocalStorage(config *v1.BackendLocalConfig) *LocalStorage {
return &LocalStorage{path: config.Path}
}

func NewDefaultStorage() *LocalStorage {
return NewLocalStorage(&v1.BackendLocalConfig{})
}

0 comments on commit c0c6265

Please sign in to comment.