Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add backend definition and new function #895

Merged
merged 1 commit into from
Mar 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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{})
}
Loading