From 9d203417b36aad499dc9852659e697b2363b4093 Mon Sep 17 00:00:00 2001 From: markliby <33564655+markliby@users.noreply.github.com> Date: Thu, 18 Aug 2022 19:39:39 +0800 Subject: [PATCH] feat: support customized state backend (#124) --- docs/backend.md | 61 +++++++++ pkg/engine/backend/backend.go | 125 +++++++++++++++++ pkg/engine/backend/backend_test.go | 128 ++++++++++++++++++ pkg/engine/backend/init/init.go | 21 +++ pkg/engine/states/backend.go | 6 - pkg/engine/states/local/backend.go | 35 +++++ pkg/engine/states/local/backend_test.go | 75 ++++++++++ pkg/engine/states/local/filesystem_state.go | 22 --- .../states/local/filesystem_state_test.go | 72 ---------- pkg/engine/states/remote/db_state.go | 4 - pkg/kusionctl/cmd/apply/apply.go | 1 + pkg/kusionctl/cmd/apply/options.go | 25 ++-- pkg/kusionctl/cmd/destroy/destroy.go | 1 + pkg/kusionctl/cmd/destroy/options.go | 37 ++--- pkg/kusionctl/cmd/destroy/options_test.go | 12 +- pkg/projectstack/types.go | 6 +- 16 files changed, 494 insertions(+), 137 deletions(-) create mode 100644 docs/backend.md create mode 100644 pkg/engine/backend/backend.go create mode 100644 pkg/engine/backend/backend_test.go create mode 100644 pkg/engine/backend/init/init.go create mode 100644 pkg/engine/states/local/backend.go create mode 100644 pkg/engine/states/local/backend_test.go diff --git a/docs/backend.md b/docs/backend.md new file mode 100644 index 00000000..af38ae7f --- /dev/null +++ b/docs/backend.md @@ -0,0 +1,61 @@ +# backend +kusion state backends定义state存储位置,默认情况下,kusion使用local类型存储state在本地磁盘上,对于团队协作项目,state可存储在远程服务上,允许多人使用 + +## backend 配置 +### 配置文件 + +kusion 通过 project.yaml 中 backend 配置储存,例如 +``` +backend: + storageType: local + config: + path: kusion_state.json +``` +* storageType - 声明储存类型 +* config - 声明对应存储类型所需参数 +### 命令行配置 +``` +kusion apply --backend-type local --backend-config path=kusion-state.json +``` +### 合并配置 +当配置文件中 config 和命令行中 --backend-config 同时配置时,整个配置合并配置文件和命令行配置,例如 +``` +backend: + storageType: local + config: + path: kusion_state.json +``` +``` +kusion apply --backend-config path-state=kusion-state.json +``` +合并后 backend config 为 +``` +backend: + storageType: local + config: + path: kusion_state.json + path-state: kusion-state.json +``` +## 可用Backend +- local + +### 默认Backend + +当配置文件及命令行都没有声明 Backend 配置时,默认使用 [local](#local) + +### local +local类型存储state在本地文件系统上,在本地操作,不适用于多人协同 + +配置示例: +``` +backend: + storageType: local + config: + path: kusion_state.json +``` +* storageType - local, 表示使用本地文件系统 +* path - (可选) 配置 state 本地存储文件 + + + + diff --git a/pkg/engine/backend/backend.go b/pkg/engine/backend/backend.go new file mode 100644 index 00000000..0a15b144 --- /dev/null +++ b/pkg/engine/backend/backend.go @@ -0,0 +1,125 @@ +package backend + +import ( + "fmt" + "path/filepath" + "strings" + + "github.com/spf13/cobra" + "github.com/zclconf/go-cty/cty" + "github.com/zclconf/go-cty/cty/gocty" + backendInit "kusionstack.io/kusion/pkg/engine/backend/init" + "kusionstack.io/kusion/pkg/engine/states" + "kusionstack.io/kusion/pkg/engine/states/local" + "kusionstack.io/kusion/pkg/util/i18n" +) + +// backend config state storage type +type Storage struct { + Type string `json:"storageType,omitempty" yaml:"storageType,omitempty"` + Config map[string]interface{} `json:"config,omitempty" yaml:"config,omitempty"` +} + +// BackendOps kusion cli backend override config +type BackendOps struct { + // Config is a series of backend configurations, + // such as ["path=kusion_state.json"] + Config []string + + // Type is the type of backend, currently supported: + // local - state is stored to a local file + // TODO: support other storage type + Type string +} + +func (o *BackendOps) AddBackendFlags(cmd *cobra.Command) { + cmd.Flags().StringVar(&o.Type, "backend-type", "", + i18n.T("backend-type specify state storage backend")) + cmd.Flags().StringSliceVarP(&o.Config, "backend-config", "C", []string{}, + i18n.T("backend-config config state storage backend")) +} + +// MergeConfig merge project backend config and cli backend config +func MergeConfig(config, override map[string]interface{}) map[string]interface{} { + content := make(map[string]interface{}) + for k, v := range config { + content[k] = v + } + for k, v := range override { + content[k] = v + } + return content +} + +// NewDefalutBackend return defalut backend. +// default backend is local filesystem +func NewDefaultBackend(dir string, fileName string) *Storage { + return &Storage{ + Type: "local", + Config: map[string]interface{}{ + "path": filepath.Join(dir, fileName), + }, + } +} + +// BackendFromConfig return stateStorage, this func handler +// backend config merge and configure backend. +// return a StateStorage to manage State +func BackendFromConfig(config *Storage, override BackendOps, dir string) (states.StateStorage, error) { + var backendConfig Storage + if config == nil { + config = NewDefaultBackend(dir, local.KusionState) + } + if config.Type != "" { + backendConfig.Type = config.Type + } + + if override.Type != "" { + backendConfig.Type = override.Type + } + configOverride := make(map[string]interface{}) + for _, v := range override.Config { + bk := strings.Split(v, "=") + if len(bk) != 2 { + return nil, fmt.Errorf("kusion cli backend config should be path=kusion_state.json") + } + configOverride[bk[0]] = bk[1] + } + if config.Config != nil || override.Config != nil { + backendConfig.Config = MergeConfig(config.Config, configOverride) + } + + backendFunc := backendInit.GetBackend(backendConfig.Type) + if backendFunc == nil { + return nil, fmt.Errorf("kusion backend storage: %s not support, please check storageType config", backendConfig.Type) + } + + bf := backendFunc() + + backendSchema := bf.ConfigSchema() + err := validBackendConfig(backendConfig.Config, backendSchema) + if err != nil { + return nil, err + } + ctyBackend, err := gocty.ToCtyValue(backendConfig.Config, backendSchema) + if err != nil { + return nil, err + } + + err = bf.Configure(ctyBackend) + if err != nil { + return nil, err + } + + return bf.StateStorage(), nil +} + +// validBackendConfig check backend config. +func validBackendConfig(config map[string]interface{}, schema cty.Type) error { + for k := range config { + if !schema.HasAttribute(k) { + return fmt.Errorf("not support %s in backend config", k) + } + } + return nil +} diff --git a/pkg/engine/backend/backend_test.go b/pkg/engine/backend/backend_test.go new file mode 100644 index 00000000..5a3cc64a --- /dev/null +++ b/pkg/engine/backend/backend_test.go @@ -0,0 +1,128 @@ +package backend + +import ( + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/zclconf/go-cty/cty" + _ "kusionstack.io/kusion/pkg/engine/backend/init" + "kusionstack.io/kusion/pkg/engine/states" + "kusionstack.io/kusion/pkg/engine/states/local" +) + +func TestMergeConfig(t *testing.T) { + type args struct { + config map[string]interface{} + override map[string]interface{} + } + type want struct { + content map[string]interface{} + } + + tests := map[string]struct { + args + want + }{ + "MergeConfig": { + args: args{ + config: map[string]interface{}{ + "path": "kusion_state.json", + }, + override: map[string]interface{}{ + "config": "kusion_config.json", + }, + }, + want: want{ + content: map[string]interface{}{ + "path": "kusion_state.json", + "config": "kusion_config.json", + }, + }, + }, + } + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + mergeConfig := MergeConfig(tt.config, tt.override) + if diff := cmp.Diff(tt.want.content, mergeConfig); diff != "" { + t.Errorf("\nWrapMergeConfigFailed(...): -want message, +got message:\n%s", diff) + } + }) + } +} + +func TestBackendFromConfig(t *testing.T) { + type args struct { + config *Storage + override BackendOps + } + type want struct { + storage states.StateStorage + err error + } + tests := map[string]struct { + args + want + }{ + "BackendFromConfig": { + args: args{ + config: &Storage{ + Type: "local", + Config: map[string]interface{}{ + "path": "kusion_state.json", + }, + }, + override: BackendOps{ + Config: []string{ + "path=kusion_local.json", + }, + }, + }, + want: want{ + storage: &local.FileSystemState{Path: "kusion_local.json"}, + err: nil, + }, + }, + } + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + storage, _ := BackendFromConfig(tt.config, tt.override, "./") + if diff := cmp.Diff(tt.want.storage, storage); diff != "" { + t.Errorf("\nWrapBackendFromConfigFailed(...): -want message, +got message:\n%s", diff) + } + }) + } +} + +func TestValidBackendConfig(t *testing.T) { + type args struct { + config map[string]interface{} + schema cty.Type + } + type want struct { + errMsg string + } + tests := map[string]struct { + args + want + }{ + "InValidBackendConfig": { + args: args{ + config: map[string]interface{}{ + "kusionPath": "kusion_state.json", + }, + schema: cty.Object(map[string]cty.Type{"path": cty.String}), + }, + want: want{ + errMsg: "not support kusionPath in backend config", + }, + }, + } + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + err := validBackendConfig(tt.config, tt.schema) + if diff := cmp.Diff(tt.want.errMsg, err.Error()); diff != "" { + t.Errorf("\nWrapvalidBackendConfigFailed(...): -want message, +got message:\n%s", diff) + } + }) + } +} diff --git a/pkg/engine/backend/init/init.go b/pkg/engine/backend/init/init.go new file mode 100644 index 00000000..342554d6 --- /dev/null +++ b/pkg/engine/backend/init/init.go @@ -0,0 +1,21 @@ +package init + +import ( + "kusionstack.io/kusion/pkg/engine/states" + "kusionstack.io/kusion/pkg/engine/states/local" +) + +// backends store all available backend +var backends map[string]func() states.Backend + +// init backends map with all support backend +func init() { + backends = map[string]func() states.Backend{ + "local": local.NewLocalBackend, + } +} + +// GetBackend return backend, or nil if not exists +func GetBackend(name string) func() states.Backend { + return backends[name] +} diff --git a/pkg/engine/states/backend.go b/pkg/engine/states/backend.go index 8c53faf1..642d57ac 100644 --- a/pkg/engine/states/backend.go +++ b/pkg/engine/states/backend.go @@ -13,9 +13,3 @@ type Backend interface { // StateStorage return a StateStorage to manage State StateStorage() StateStorage } - -var Backends = make(map[string]func() StateStorage) - -func AddToBackends(name string, storage func() StateStorage) { - Backends[name] = storage -} diff --git a/pkg/engine/states/local/backend.go b/pkg/engine/states/local/backend.go new file mode 100644 index 00000000..7a5227a6 --- /dev/null +++ b/pkg/engine/states/local/backend.go @@ -0,0 +1,35 @@ +package local + +import ( + "github.com/zclconf/go-cty/cty" + "kusionstack.io/kusion/pkg/engine/states" +) + +type LocalBackend struct { + FileSystemState +} + +func NewLocalBackend() states.Backend { + return &LocalBackend{} +} + +func (f *LocalBackend) StateStorage() states.StateStorage { + return &FileSystemState{f.Path} +} + +func (f *LocalBackend) ConfigSchema() cty.Type { + config := map[string]cty.Type{ + "path": cty.String, + } + return cty.Object(config) +} + +func (f *LocalBackend) Configure(obj cty.Value) error { + var path cty.Value + if path = obj.GetAttr("path"); !path.IsNull() && path.AsString() != "" { + f.Path = path.AsString() + } else { + f.Path = KusionState + } + return nil +} diff --git a/pkg/engine/states/local/backend_test.go b/pkg/engine/states/local/backend_test.go new file mode 100644 index 00000000..01728774 --- /dev/null +++ b/pkg/engine/states/local/backend_test.go @@ -0,0 +1,75 @@ +package local + +import ( + "reflect" + "testing" + + "github.com/zclconf/go-cty/cty" + "github.com/zclconf/go-cty/cty/gocty" +) + +func TestLocalBackend_ConfigSchema(t *testing.T) { + type fields struct { + Path string + } + tests := []struct { + name string + fields fields + want cty.Type + }{ + { + name: "t1", + fields: fields{ + Path: stateFile, + }, + want: cty.Object(map[string]cty.Type{ + "path": cty.String, + }), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := NewLocalBackend() + if got := s.ConfigSchema(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("LocalBackend.ConfigSchema() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestLocalBackend_Configure(t *testing.T) { + type fields struct { + Path string + } + type args struct { + config map[string]interface{} + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + { + name: "t1", + fields: fields{ + Path: stateFile, + }, + wantErr: false, + args: args{ + config: map[string]interface{}{ + "path": stateFile, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := NewLocalBackend() + obj, _ := gocty.ToCtyValue(tt.args.config, s.ConfigSchema()) + if err := s.Configure(obj); (err != nil) != tt.wantErr { + t.Errorf("LocalBackend.Configure() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/pkg/engine/states/local/filesystem_state.go b/pkg/engine/states/local/filesystem_state.go index 93cff2e1..8f32410d 100644 --- a/pkg/engine/states/local/filesystem_state.go +++ b/pkg/engine/states/local/filesystem_state.go @@ -7,17 +7,12 @@ import ( "os" "time" - "github.com/zclconf/go-cty/cty" "gopkg.in/yaml.v3" "kusionstack.io/kusion/pkg/engine/states" "kusionstack.io/kusion/pkg/log" ) -func init() { - states.AddToBackends("local", NewFileSystemState) -} - var _ states.StateStorage = &FileSystemState{} type FileSystemState struct { @@ -31,23 +26,6 @@ func NewFileSystemState() states.StateStorage { const KusionState = "kusion_state.json" -func (f *FileSystemState) ConfigSchema() cty.Type { - config := map[string]cty.Type{ - "path": cty.String, - } - return cty.Object(config) -} - -func (f *FileSystemState) Configure(obj cty.Value) error { - var path cty.Value - if path = obj.GetAttr("path"); !path.IsNull() && path.AsString() != "" { - f.Path = path.AsString() - } else { - f.Path = KusionState - } - return nil -} - func (f *FileSystemState) GetLatestState(query *states.StateQuery) (*states.State, error) { // create a new state file if no file exists file, err := os.OpenFile(f.Path, os.O_RDWR|os.O_CREATE, fs.ModePerm) diff --git a/pkg/engine/states/local/filesystem_state_test.go b/pkg/engine/states/local/filesystem_state_test.go index 17c07022..e700b11d 100644 --- a/pkg/engine/states/local/filesystem_state_test.go +++ b/pkg/engine/states/local/filesystem_state_test.go @@ -14,8 +14,6 @@ import ( "bou.ke/monkey" "github.com/stretchr/testify/assert" - "github.com/zclconf/go-cty/cty" - "github.com/zclconf/go-cty/cty/gocty" ) var stateFile string @@ -47,76 +45,6 @@ func TestNewFileSystemState(t *testing.T) { } } -func TestFileSystemState_ConfigSchema(t *testing.T) { - type fields struct { - Path string - } - tests := []struct { - name string - fields fields - want cty.Type - }{ - { - name: "t1", - fields: fields{ - Path: stateFile, - }, - want: cty.Object(map[string]cty.Type{ - "path": cty.String, - }), - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - s := &FileSystemState{ - Path: tt.fields.Path, - } - if got := s.ConfigSchema(); !reflect.DeepEqual(got, tt.want) { - t.Errorf("FileSystemState.ConfigSchema() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestFileSystemState_Configure(t *testing.T) { - type fields struct { - Path string - } - type args struct { - config map[string]interface{} - } - tests := []struct { - name string - fields fields - args args - wantErr bool - }{ - { - name: "t1", - fields: fields{ - Path: stateFile, - }, - wantErr: false, - args: args{ - config: map[string]interface{}{ - "path": stateFile, - }, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - s := &FileSystemState{ - Path: tt.fields.Path, - } - obj, _ := gocty.ToCtyValue(tt.args.config, s.ConfigSchema()) - if err := s.Configure(obj); (err != nil) != tt.wantErr { - t.Errorf("FileSystemState.Configure() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} - func TestFileSystemState_GetLatestState(t *testing.T) { type fields struct { Path string diff --git a/pkg/engine/states/remote/db_state.go b/pkg/engine/states/remote/db_state.go index 47e3dfd6..50ab1021 100644 --- a/pkg/engine/states/remote/db_state.go +++ b/pkg/engine/states/remote/db_state.go @@ -23,10 +23,6 @@ import ( jsonutil "kusionstack.io/kusion/pkg/util/json" ) -func init() { - states.AddToBackends("db", NewDBState) -} - var _ states.StateStorage = &DBState{} func NewDBState() states.StateStorage { diff --git a/pkg/kusionctl/cmd/apply/apply.go b/pkg/kusionctl/cmd/apply/apply.go index 563ab5b3..180b94f1 100644 --- a/pkg/kusionctl/cmd/apply/apply.go +++ b/pkg/kusionctl/cmd/apply/apply.go @@ -61,6 +61,7 @@ func NewCmdApply() *cobra.Command { i18n.T("no-style sets to RawOutput mode and disables all of styling")) cmd.Flags().BoolVarP(&o.DryRun, "dry-run", "", false, i18n.T("dry-run to preview the execution effect (always successful) without actually applying the changes")) + o.AddBackendFlags(cmd) return cmd } diff --git a/pkg/kusionctl/cmd/apply/options.go b/pkg/kusionctl/cmd/apply/options.go index 63e5dff2..0518ddf6 100644 --- a/pkg/kusionctl/cmd/apply/options.go +++ b/pkg/kusionctl/cmd/apply/options.go @@ -4,24 +4,20 @@ import ( "fmt" "io" "os" - "path/filepath" "strings" "sync" - runtimeInit "kusionstack.io/kusion/pkg/engine/runtime/init" - "kusionstack.io/kusion/pkg/engine/states/local" - - "kusionstack.io/kusion/pkg/engine/operation" - opsmodels "kusionstack.io/kusion/pkg/engine/operation/models" - - "kusionstack.io/kusion/pkg/engine/operation/types" - "github.com/AlecAivazis/survey/v2" "github.com/pterm/pterm" - "kusionstack.io/kusion/pkg/compile" + "kusionstack.io/kusion/pkg/engine/backend" + _ "kusionstack.io/kusion/pkg/engine/backend/init" "kusionstack.io/kusion/pkg/engine/models" + "kusionstack.io/kusion/pkg/engine/operation" + opsmodels "kusionstack.io/kusion/pkg/engine/operation/models" + "kusionstack.io/kusion/pkg/engine/operation/types" "kusionstack.io/kusion/pkg/engine/runtime" + runtimeInit "kusionstack.io/kusion/pkg/engine/runtime/init" "kusionstack.io/kusion/pkg/engine/states" compilecmd "kusionstack.io/kusion/pkg/kusionctl/cmd/compile" "kusionstack.io/kusion/pkg/log" @@ -38,6 +34,7 @@ type ApplyOptions struct { NoStyle bool DryRun bool OnlyPreview bool + backend.BackendOps } // NewApplyOptions returns a new ApplyOptions instance @@ -82,9 +79,13 @@ func (o *ApplyOptions) Run() error { sp.Success() // Resolve spinner with success message. pterm.Println() - // Compute changes for preview - stateStorage := &local.FileSystemState{Path: filepath.Join(o.WorkDir, local.KusionState)} + // Get stateStroage from backend config to manage state + stateStorage, err := backend.BackendFromConfig(project.Backend, o.BackendOps, o.WorkDir) + if err != nil { + return err + } + // Compute changes for preview runtimes := runtimeInit.InitRuntime() runtime, err := runtimes[planResources.Resources[0].Type]() if err != nil { diff --git a/pkg/kusionctl/cmd/destroy/destroy.go b/pkg/kusionctl/cmd/destroy/destroy.go index 673861bf..7aff0792 100644 --- a/pkg/kusionctl/cmd/destroy/destroy.go +++ b/pkg/kusionctl/cmd/destroy/destroy.go @@ -50,6 +50,7 @@ func NewCmdDestroy() *cobra.Command { i18n.T("Automatically approve and perform the update after previewing it")) cmd.Flags().BoolVarP(&o.Detail, "detail", "d", false, i18n.T("Automatically show plan details after previewing it")) + o.AddBackendFlags(cmd) return cmd } diff --git a/pkg/kusionctl/cmd/destroy/options.go b/pkg/kusionctl/cmd/destroy/options.go index fe569d8b..737da186 100644 --- a/pkg/kusionctl/cmd/destroy/options.go +++ b/pkg/kusionctl/cmd/destroy/options.go @@ -2,24 +2,20 @@ package destroy import ( "fmt" - "path/filepath" "strings" "sync" - runtimeInit "kusionstack.io/kusion/pkg/engine/runtime/init" - "kusionstack.io/kusion/pkg/engine/states/local" - - "kusionstack.io/kusion/pkg/engine/operation" - opsmodels "kusionstack.io/kusion/pkg/engine/operation/models" - - "kusionstack.io/kusion/pkg/engine/operation/types" - "github.com/AlecAivazis/survey/v2" "github.com/pterm/pterm" - "kusionstack.io/kusion/pkg/compile" + "kusionstack.io/kusion/pkg/engine/backend" "kusionstack.io/kusion/pkg/engine/models" + "kusionstack.io/kusion/pkg/engine/operation" + opsmodels "kusionstack.io/kusion/pkg/engine/operation/models" + "kusionstack.io/kusion/pkg/engine/operation/types" "kusionstack.io/kusion/pkg/engine/runtime" + runtimeInit "kusionstack.io/kusion/pkg/engine/runtime/init" + "kusionstack.io/kusion/pkg/engine/states" compilecmd "kusionstack.io/kusion/pkg/kusionctl/cmd/compile" "kusionstack.io/kusion/pkg/log" "kusionstack.io/kusion/pkg/projectstack" @@ -32,6 +28,7 @@ type DestroyOptions struct { Operator string Yes bool Detail bool + backend.BackendOps } func NewDestroyOptions() *DestroyOptions { @@ -82,8 +79,14 @@ func (o *DestroyOptions) Run() error { return nil } + // Get stateStroage from backend config to manage state + stateStorage, err := backend.BackendFromConfig(project.Backend, o.BackendOps, o.WorkDir) + if err != nil { + return err + } + // Compute changes for preview - changes, err := o.preview(planResources, project, stack, runtime) + changes, err := o.preview(planResources, project, stack, runtime, stateStorage) if err != nil { return err } @@ -121,20 +124,22 @@ func (o *DestroyOptions) Run() error { // Destroy fmt.Println("Start destroying resources......") - if err := o.destroy(planResources, changes, runtime); err != nil { + if err := o.destroy(planResources, changes, runtime, stateStorage); err != nil { return err } return nil } -func (o *DestroyOptions) preview(planResources *models.Spec, project *projectstack.Project, stack *projectstack.Stack, runtime runtime.Runtime) (*opsmodels.Changes, error) { +func (o *DestroyOptions) preview(planResources *models.Spec, + project *projectstack.Project, stack *projectstack.Stack, runtime runtime.Runtime, stateStorage states.StateStorage, +) (*opsmodels.Changes, error) { log.Info("Start compute preview changes ...") pc := &operation.PreviewOperation{ Operation: opsmodels.Operation{ OperationType: types.DestroyPreview, Runtime: runtime, - StateStorage: &local.FileSystemState{Path: filepath.Join(o.WorkDir, local.KusionState)}, + StateStorage: stateStorage, ChangeOrder: &opsmodels.ChangeOrder{StepKeys: []string{}, ChangeSteps: map[string]*opsmodels.ChangeStep{}}, }, } @@ -157,13 +162,13 @@ func (o *DestroyOptions) preview(planResources *models.Spec, project *projectsta return opsmodels.NewChanges(project, stack, rsp.Order), nil } -func (o *DestroyOptions) destroy(planResources *models.Spec, changes *opsmodels.Changes, runtime runtime.Runtime) error { +func (o *DestroyOptions) destroy(planResources *models.Spec, changes *opsmodels.Changes, runtime runtime.Runtime, stateStorage states.StateStorage) error { // Build apply operation do := &operation.DestroyOperation{ Operation: opsmodels.Operation{ Runtime: runtime, - StateStorage: &local.FileSystemState{Path: filepath.Join(o.WorkDir, local.KusionState)}, + StateStorage: stateStorage, MsgCh: make(chan opsmodels.Message), }, } diff --git a/pkg/kusionctl/cmd/destroy/options_test.go b/pkg/kusionctl/cmd/destroy/options_test.go index ca41bc23..2f02a4e4 100644 --- a/pkg/kusionctl/cmd/destroy/options_test.go +++ b/pkg/kusionctl/cmd/destroy/options_test.go @@ -7,6 +7,7 @@ import ( "context" "errors" "fmt" + "path/filepath" "reflect" "testing" "time" @@ -23,6 +24,7 @@ import ( opsmodels "kusionstack.io/kusion/pkg/engine/operation/models" "kusionstack.io/kusion/pkg/engine/operation/types" "kusionstack.io/kusion/pkg/engine/runtime" + "kusionstack.io/kusion/pkg/engine/states/local" "kusionstack.io/kusion/pkg/projectstack" "kusionstack.io/kusion/pkg/status" ) @@ -112,7 +114,8 @@ func Test_preview(t *testing.T) { mockOperationPreview() o := NewDestroyOptions() - _, err := o.preview(&models.Spec{Resources: []models.Resource{sa1}}, project, stack, &fakerRuntime{}) + stateStorage := &local.FileSystemState{Path: filepath.Join(o.WorkDir, local.KusionState)} + _, err := o.preview(&models.Spec{Resources: []models.Resource{sa1}}, project, stack, &fakerRuntime{}, stateStorage) assert.Nil(t, err) }) } @@ -225,7 +228,9 @@ func Test_destroy(t *testing.T) { } changes := opsmodels.NewChanges(project, stack, order) - err := o.destroy(planResources, changes, &fakerRuntime{}) + stateStorage := &local.FileSystemState{Path: filepath.Join(o.WorkDir, local.KusionState)} + + err := o.destroy(planResources, changes, &fakerRuntime{}, stateStorage) assert.Nil(t, err) }) t.Run("destroy failed", func(t *testing.T) { @@ -246,8 +251,9 @@ func Test_destroy(t *testing.T) { }, } changes := opsmodels.NewChanges(project, stack, order) + stateStorage := &local.FileSystemState{Path: filepath.Join(o.WorkDir, local.KusionState)} - err := o.destroy(planResources, changes, &fakerRuntime{}) + err := o.destroy(planResources, changes, &fakerRuntime{}, stateStorage) assert.NotNil(t, err) }) } diff --git a/pkg/projectstack/types.go b/pkg/projectstack/types.go index 7f2b3232..4c041de9 100644 --- a/pkg/projectstack/types.go +++ b/pkg/projectstack/types.go @@ -5,6 +5,7 @@ import ( "strings" "github.com/pterm/pterm" + "kusionstack.io/kusion/pkg/engine/backend" "kusionstack.io/kusion/pkg/log" ) @@ -26,8 +27,9 @@ const ( // ProjectConfiguration is the project configuration type ProjectConfiguration struct { - Name string `json:"name" yaml:"name"` // Project name - Tenant string `json:"tenant,omitempty" yaml:"tenant,omitempty"` // Tenant name + Name string `json:"name" yaml:"name"` // Project name + Tenant string `json:"tenant,omitempty" yaml:"tenant,omitempty"` // Tenant name + Backend *backend.Storage `json:"backend,omitempty" yaml:"backend,omitempty"` // state storage config } type Project struct {