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 workspace, modules, backends, runtimes management #642

Merged
merged 4 commits into from
Dec 4, 2023
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
15 changes: 0 additions & 15 deletions pkg/apis/workspace/backends.go

This file was deleted.

15 changes: 0 additions & 15 deletions pkg/apis/workspace/modules.go

This file was deleted.

20 changes: 0 additions & 20 deletions pkg/apis/workspace/runtimes.go

This file was deleted.

89 changes: 89 additions & 0 deletions pkg/apis/workspace/types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package workspace

const (
DefaultBlock = "default"
ProjectSelectorField = "projectSelector"
)

// Workspace is a logical concept representing a target that stacks will be deployed to.
// Workspace is managed by platform engineers, which contains a set of configurations
// that application developers do not want or should not concern, and is reused by multiple
// stacks belonging to different projects.
type Workspace struct {
// Name identifies a Workspace uniquely.
Name string `yaml:"-" json:"-"`

// Modules are the configs of a set of modules.
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"`

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

// ModuleConfigs is a set of multiple ModuleConfig, whose key is the module name.
type ModuleConfigs map[string]ModuleConfig

// ModuleConfig is the config of a module, which contains a default and several patcher blocks.
//
// The default block's key is "default", and value is the module inputs. The patcher blocks' keys
// are the patcher names, which are just block identifiers without specific meaning, but must
// not be "default". Besides module inputs, patcher block's value also contains a field named
// "projectSelector", whose value is a slice containing the project names that use the patcher
// 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"`

// Terraform contains the config of multiple terraform providers.
Terraform TerraformConfig `yaml:"terraform,omitempty" json:"terraform,omitempty"`
}

// KubernetesConfig contains config to access a kubernetes cluster.
type KubernetesConfig struct {
// KubeConfig is the path of the kubeconfig file.
KubeConfig string `yaml:"kubeConfig" json:"kubeConfig"`
}

// TerraformConfig contains the config of multiple terraform provider config, whose key is
// the provider name.
type TerraformConfig map[string]GenericConfig

// BackendConfigs contains config of the backend, which is used to store state, etc. Only one kind
// backend can be configured.
// 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"`
}

// LocalFileConfig contains the config of using local file system as backend.
type LocalFileConfig struct {
// Path is place to store state, etc.
Path string `yaml:"path" json:"path"`
}

// GenericConfig is a generic model to describe config which shields the difference among multiple concrete
// models. GenericConfig is designed for extensibility, used for module, terraform runtime config, etc.
type GenericConfig map[string]any
23 changes: 0 additions & 23 deletions pkg/apis/workspace/workspace.go

This file was deleted.

169 changes: 169 additions & 0 deletions pkg/workspace/util.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
package workspace

import (
"errors"
"fmt"

"gopkg.in/yaml.v3"

"kusionstack.io/kusion/pkg/apis/workspace"
)

var (
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,
// 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)
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
// 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
}

return getProjectModuleConfig(config, projectName)
}

// 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]
if len(projectCfg) == 0 {
projectCfg = make(workspace.GenericConfig)
}

for name, cfg := range config {
if name == workspace.DefaultBlock {
continue
}
projects, err := parseProjectsFromProjectSelector(cfg[workspace.ProjectSelectorField])
if err != nil {
return nil, fmt.Errorf("%w, patcher block: %s", err, name)
}
// check the project is assigned in the block or not.
var contain bool
for _, project := range projects {
if projectName == project {
contain = true
break
}
}
if contain {
for k, v := range cfg {
if k == workspace.ProjectSelectorField {
continue
}
projectCfg[k] = v
}
break
}
}

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

// parseProjectsFromProjectSelector parses the projects in projectSelector field to string slice.
func parseProjectsFromProjectSelector(unstructuredProjects any) ([]string, error) {
var projects []string
bytes, err := yaml.Marshal(unstructuredProjects)
if err != nil {
return nil, fmt.Errorf("%w, marshal failed: %v", ErrInvalidModuleConfigProjectSelector, err)
}
if err = yaml.Unmarshal(bytes, &projects); err != nil {
return nil, fmt.Errorf("%w, unmarshal failed: %v", ErrInvalidModuleConfigProjectSelector, err)
}
if len(projects) == 0 {
return nil, fmt.Errorf("%w, empty projects", ErrInvalidModuleConfigProjectSelector)
}
return projects, nil
}

// 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
}
config, err := GetTerraformConfig(configs)
if err != nil {
return nil, err
}

cfg, ok := config[providerName]
if !ok {
return nil, ErrNotExistTerraformProviderConfig
}
return cfg, nil
}
Loading
Loading