Skip to content

Commit

Permalink
feat: add more manager for the workspace management
Browse files Browse the repository at this point in the history
  • Loading branch information
healthjyk committed Dec 4, 2023
1 parent 1d7b31d commit c01c05f
Show file tree
Hide file tree
Showing 5 changed files with 172 additions and 76 deletions.
22 changes: 18 additions & 4 deletions pkg/apis/workspace/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ type Workspace struct {
Modules ModuleConfigs `yaml:"modules,omitempty" json:"modules,omitempty"`

// Runtimes are the configs of a set of runtimes.
Runtimes RuntimeConfigs `yaml:"runtimes,omitempty" json:"runtimes,omitempty"`
Runtimes *RuntimeConfigs `yaml:"runtimes,omitempty" json:"runtimes,omitempty"`

// Backends are the configs of a set of backends.
Backends BackendConfigs `yaml:"backends,omitempty" json:"backends,omitempty"`
Backends *BackendConfigs `yaml:"backends,omitempty" json:"backends,omitempty"`
}

// ModuleConfigs is a set of multiple ModuleConfig, whose key is the module name.
Expand All @@ -35,12 +35,26 @@ type ModuleConfigs map[string]ModuleConfig
// configs. A project can only be assigned in a patcher's "projectSelector" field, the assignment
// in multiple patchers is not allowed. For a project, if not specified in the patcher block's
// "projectSelector" field, the default config will be used.
//
// Take the ModuleConfig of "database" for an example, which is shown as below:
//
// config := ModuleConfig {
// "default": {
// "type": "aws",
// "version": "5.7",
// "instanceType": "db.t3.micro",
// },
// "smallClass": {
// "instanceType": "db.t3.small",
// "projectSelector": []string{"foo", "bar"},
// },
// }
type ModuleConfig map[string]GenericConfig

// RuntimeConfigs contains a set of runtime config.
type RuntimeConfigs struct {
// Kubernetes contains the config to access a kubernetes cluster.
Kubernetes KubernetesConfig `yaml:"kubernetes,omitempty" json:"kubernetes,omitempty"`
Kubernetes *KubernetesConfig `yaml:"kubernetes,omitempty" json:"kubernetes,omitempty"`

// Terraform contains the config of multiple terraform providers.
Terraform TerraformConfig `yaml:"terraform,omitempty" json:"terraform,omitempty"`
Expand All @@ -61,7 +75,7 @@ type TerraformConfig map[string]GenericConfig
// todo: add more backends declared in pkg/engine/backend
type BackendConfigs struct {
// Local is backend to use local file system.
Local LocalFileConfig `yaml:"local,omitempty" json:"local,omitempty"`
Local *LocalFileConfig `yaml:"local,omitempty" json:"local,omitempty"`
}

// LocalFileConfig contains the config of using local file system as backend.
Expand Down
85 changes: 72 additions & 13 deletions pkg/workspace/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,32 +10,55 @@ import (
)

var (
ErrEmptyProjectName = errors.New("empty query project name")
ErrEmptyProjectName = errors.New("empty project name")
ErrEmptyModuleConfigs = errors.New("empty module configs")
ErrNotExistProjectModuleConfigs = errors.New("not exist module configs of the project")
ErrNotExistProjectModuleConfig = errors.New("not exist module config of the project")

ErrEmptyRuntimeConfigs = errors.New("empty runtime configs")
ErrNotExistKubernetesConfig = errors.New("not exist kubernetes config")
ErrNotExistTerraformConfig = errors.New("not exist terraform config")
ErrNotExistTerraformProviderConfig = errors.New("not exist terraform provider config")
)

// GetProjectModuleConfigs returns the module configs of a specified project, whose key is the module name.
func GetProjectModuleConfigs(configs *workspace.ModuleConfigs, projectName string) (map[string]workspace.GenericConfig, error) {
// GetProjectModuleConfigs returns the module configs of a specified project, whose key is the module name,
// should be called after ValidateModuleConfigs.
// If got empty module configs, ErrNotExistProjectModuleConfigs will get returned.
func GetProjectModuleConfigs(configs workspace.ModuleConfigs, projectName string) (map[string]workspace.GenericConfig, error) {
if len(configs) == 0 {
return nil, ErrEmptyModuleConfigs
}
if projectName == "" {
return nil, ErrEmptyProjectName
}

projectCfgs := make(map[string]workspace.GenericConfig)
for name, cfg := range *configs {
projectCfg, err := getProjectModuleConfig(&cfg, projectName)
for name, cfg := range configs {
projectCfg, err := getProjectModuleConfig(cfg, projectName)
if errors.Is(err, ErrNotExistProjectModuleConfig) {
continue
}
if err != nil {
return nil, fmt.Errorf("%w, module name: %s", err, name)
}
if len(projectCfg) != 0 {
projectCfgs[name] = projectCfg
}
}

if len(projectCfgs) == 0 {
return nil, ErrNotExistProjectModuleConfigs
}
return projectCfgs, nil
}

// GetProjectModuleConfig returns the module config of a specified project, should be called after Validate.
func GetProjectModuleConfig(config *workspace.ModuleConfig, projectName string) (workspace.GenericConfig, error) {
// GetProjectModuleConfig returns the module config of a specified project, should be called after
// ValidateModuleConfig.
// If got empty module config, ErrNotExistProjectModuleConfig will get returned.
func GetProjectModuleConfig(config workspace.ModuleConfig, projectName string) (workspace.GenericConfig, error) {
if len(config) == 0 {
return nil, ErrEmptyModuleConfig
}
if projectName == "" {
return nil, ErrEmptyProjectName
}
Expand All @@ -45,13 +68,13 @@ func GetProjectModuleConfig(config *workspace.ModuleConfig, projectName string)

// getProjectModuleConfig gets the module config of a specified project without checking the correctness
// of project name.
func getProjectModuleConfig(config *workspace.ModuleConfig, projectName string) (workspace.GenericConfig, error) {
projectCfg := (*config)[workspace.DefaultBlock]
func getProjectModuleConfig(config workspace.ModuleConfig, projectName string) (workspace.GenericConfig, error) {
projectCfg := config[workspace.DefaultBlock]
if len(projectCfg) == 0 {
projectCfg = make(workspace.GenericConfig)
}

for name, cfg := range *config {
for name, cfg := range config {
if name == workspace.DefaultBlock {
continue
}
Expand All @@ -78,6 +101,9 @@ func getProjectModuleConfig(config *workspace.ModuleConfig, projectName string)
}
}

if len(projectCfg) == 0 {
return nil, ErrNotExistProjectModuleConfig
}
return projectCfg, nil
}

Expand All @@ -97,12 +123,45 @@ func parseProjectsFromProjectSelector(unstructuredProjects any) ([]string, error
return projects, nil
}

// GetTerraformProviderConfig is used to get a specified provider config.
func GetTerraformProviderConfig(config *workspace.TerraformConfig, providerName string) (workspace.GenericConfig, error) {
// GetKubernetesConfig returns kubernetes config from runtime config, should be called after
// ValidateRuntimeConfigs.
// If got empty kubernetes config, ErrNotExistKubernetesConfig will get returned.
func GetKubernetesConfig(configs *workspace.RuntimeConfigs) (*workspace.KubernetesConfig, error) {
if configs == nil {
return nil, ErrEmptyRuntimeConfigs
}
if configs.Kubernetes == nil {
return nil, ErrNotExistKubernetesConfig
}
return configs.Kubernetes, nil
}

// GetTerraformConfig returns terraform config from runtime config, should be called after
// ValidateRuntimeConfigs.
// If got empty terraform config, ErrNotExistTerraformConfig will get returned.
func GetTerraformConfig(configs *workspace.RuntimeConfigs) (workspace.TerraformConfig, error) {
if configs == nil {
return nil, ErrEmptyRuntimeConfigs
}
if len(configs.Terraform) == 0 {
return nil, ErrNotExistTerraformConfig
}
return configs.Terraform, nil
}

// GetTerraformProviderConfig returns the specified terraform provider config from runtime config, should
// be called after ValidateRuntimeConfigs.
// If got empty terraform config, ErrNotExistTerraformProviderConfig will get returned.
func GetTerraformProviderConfig(configs *workspace.RuntimeConfigs, providerName string) (workspace.GenericConfig, error) {
if providerName == "" {
return nil, ErrEmptyTerraformProviderName
}
cfg, ok := (*config)[providerName]
config, err := GetTerraformConfig(configs)
if err != nil {
return nil, err
}

cfg, ok := config[providerName]
if !ok {
return nil, ErrNotExistTerraformProviderConfig
}
Expand Down
52 changes: 38 additions & 14 deletions pkg/workspace/util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ func Test_GetProjectModuleConfigs(t *testing.T) {

for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
cfg, err := GetProjectModuleConfigs(&tc.moduleConfigs, tc.projectName)
cfg, err := GetProjectModuleConfigs(tc.moduleConfigs, tc.projectName)
assert.Equal(t, tc.success, err == nil)
assert.Equal(t, tc.projectConfigs, cfg)
})
Expand Down Expand Up @@ -84,20 +84,44 @@ func Test_GetProjectModuleConfig(t *testing.T) {

for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
cfg, err := GetProjectModuleConfig(&tc.moduleConfig, tc.projectName)
cfg, err := GetProjectModuleConfig(tc.moduleConfig, tc.projectName)
assert.Equal(t, tc.success, err == nil)
assert.Equal(t, tc.projectConfig, cfg)
})
}
}

func Test_GetKubernetesConfig(t *testing.T) {
testcases := []struct {
name string
success bool
kubernetesConfig *workspace.KubernetesConfig
runtimeConfigs *workspace.RuntimeConfigs
}{
{
name: "successfully get kubernetes config",
success: true,
kubernetesConfig: mockValidKubernetesConfig(),
runtimeConfigs: mockValidRuntimeConfigs(),
},
}

for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
cfg, err := GetKubernetesConfig(tc.runtimeConfigs)
assert.Equal(t, tc.success, err == nil)
assert.Equal(t, tc.kubernetesConfig, cfg)
})
}
}

func Test_GetTerraformProviderConfig(t *testing.T) {
testcases := []struct {
name string
success bool
providerName string
providerConfig workspace.GenericConfig
terraformConfig workspace.TerraformConfig
name string
success bool
providerName string
providerConfig workspace.GenericConfig
runtimeConfigs *workspace.RuntimeConfigs
}{
{
name: "successfully get terraform provider config",
Expand All @@ -108,20 +132,20 @@ func Test_GetTerraformProviderConfig(t *testing.T) {
"source": "hashicorp/aws",
"region": "us-east-1",
},
terraformConfig: mockValidTerraformConfig(),
runtimeConfigs: mockValidRuntimeConfigs(),
},
{
name: "failed to get config not exist provider",
success: false,
providerName: "alicloud",
providerConfig: nil,
terraformConfig: mockValidTerraformConfig(),
name: "failed to get config not exist provider",
success: false,
providerName: "alicloud",
providerConfig: nil,
runtimeConfigs: mockValidRuntimeConfigs(),
},
}

for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
cfg, err := GetTerraformProviderConfig(&tc.terraformConfig, tc.providerName)
cfg, err := GetTerraformProviderConfig(tc.runtimeConfigs, tc.providerName)
assert.Equal(t, tc.success, err == nil)
assert.Equal(t, tc.providerConfig, cfg)
})
Expand Down
37 changes: 18 additions & 19 deletions pkg/workspace/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package workspace
import (
"errors"
"fmt"
"reflect"

"kusionstack.io/kusion/pkg/apis/workspace"
)
Expand Down Expand Up @@ -33,34 +32,34 @@ func ValidateWorkspace(ws *workspace.Workspace) error {
if ws.Name == "" {
return ErrEmptyWorkspaceName
}
if !reflect.DeepEqual(ws.Modules, workspace.ModuleConfigs{}) {
if err := ValidateModuleConfigs(&ws.Modules); err != nil {
if len(ws.Modules) != 0 {
if err := ValidateModuleConfigs(ws.Modules); err != nil {
return err
}
}
if !reflect.DeepEqual(ws.Runtimes, workspace.RuntimeConfigs{}) {
if err := ValidateRuntimeConfigs(&ws.Runtimes); err != nil {
if ws.Runtimes != nil {
if err := ValidateRuntimeConfigs(ws.Runtimes); err != nil {
return err
}
}
if !reflect.DeepEqual(ws.Backends, workspace.BackendConfigs{}) {
if err := ValidateBackendConfigs(&ws.Backends); err != nil {
if ws.Backends != nil {
if err := ValidateBackendConfigs(ws.Backends); err != nil {
return err
}
}
return nil
}

// ValidateModuleConfigs validates the workspace.ModuleConfigs is valid or not.
func ValidateModuleConfigs(configs *workspace.ModuleConfigs) error {
for name, cfg := range *configs {
func ValidateModuleConfigs(configs workspace.ModuleConfigs) error {
for name, cfg := range configs {
if name == "" {
return ErrEmptyModuleName
}
if len(cfg) == 0 {
return fmt.Errorf("%w, module name: %s", ErrEmptyModuleConfig, name)
}
if err := ValidateModuleConfig(&cfg); err != nil {
if err := ValidateModuleConfig(cfg); err != nil {
return fmt.Errorf("%w, module name: %s", err, name)
}
}
Expand All @@ -69,11 +68,11 @@ func ValidateModuleConfigs(configs *workspace.ModuleConfigs) error {
}

// ValidateModuleConfig is used to validate the workspace.ModuleConfig is valid or not.
func ValidateModuleConfig(config *workspace.ModuleConfig) error {
func ValidateModuleConfig(config workspace.ModuleConfig) error {
// allProjects is used to inspect if there are repeated projects in projectSelector
// field or not.
allProjects := make(map[string]string)
for name, cfg := range *config {
for name, cfg := range config {
switch name {
case "":
return ErrEmptyModuleConfigBlockName
Expand Down Expand Up @@ -123,13 +122,13 @@ func ValidateModuleConfig(config *workspace.ModuleConfig) error {

// ValidateRuntimeConfigs is used to validate the workspace.RuntimeConfigs is valid or not.
func ValidateRuntimeConfigs(configs *workspace.RuntimeConfigs) error {
if !reflect.DeepEqual(configs.Kubernetes, workspace.KubernetesConfig{}) {
if err := ValidateKubernetesConfig(&configs.Kubernetes); err != nil {
if configs.Kubernetes != nil {
if err := ValidateKubernetesConfig(configs.Kubernetes); err != nil {
return err
}
}
if len(configs.Terraform) != 0 {
if err := ValidateTerraformConfig(&configs.Terraform); err != nil {
if err := ValidateTerraformConfig(configs.Terraform); err != nil {
return err
}
}
Expand All @@ -145,8 +144,8 @@ func ValidateKubernetesConfig(config *workspace.KubernetesConfig) error {
}

// ValidateTerraformConfig is used to validate the workspace.TerraformConfig is valid or not.
func ValidateTerraformConfig(config *workspace.TerraformConfig) error {
for name, cfg := range *config {
func ValidateTerraformConfig(config workspace.TerraformConfig) error {
for name, cfg := range config {
if name == "" {
return ErrEmptyTerraformProviderName
}
Expand All @@ -159,8 +158,8 @@ func ValidateTerraformConfig(config *workspace.TerraformConfig) error {

// ValidateBackendConfigs is used to validate workspace.BackendConfigs is valid or not.
func ValidateBackendConfigs(configs *workspace.BackendConfigs) error {
if !reflect.DeepEqual(configs.Local, workspace.LocalFileConfig{}) {
if err := ValidateLocalFileConfig(&configs.Local); err != nil {
if configs.Local != nil {
if err := ValidateLocalFileConfig(configs.Local); err != nil {
return err
}
}
Expand Down
Loading

0 comments on commit c01c05f

Please sign in to comment.