From 1b178f6c70d26fb980c50256246880177099cf34 Mon Sep 17 00:00:00 2001 From: KK <68334452+healthjyk@users.noreply.github.com> Date: Fri, 22 Dec 2023 15:04:45 +0800 Subject: [PATCH] feat: add func of getting specified type from workspace genericConfig (#708) --- .../generators/workload/service_generator.go | 4 +- pkg/modules/util.go | 10 +- pkg/workspace/util.go | 130 +++++--- pkg/workspace/util_test.go | 302 ++++++++++++++---- 4 files changed, 328 insertions(+), 118 deletions(-) diff --git a/pkg/modules/generators/workload/service_generator.go b/pkg/modules/generators/workload/service_generator.go index 1f8b1a6a..77c58412 100644 --- a/pkg/modules/generators/workload/service_generator.go +++ b/pkg/modules/generators/workload/service_generator.go @@ -6,14 +6,14 @@ import ( appsv1 "k8s.io/api/apps/v1" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "kusionstack.io/kube-api/apps/v1alpha1" apiv1 "kusionstack.io/kusion/pkg/apis/core/v1" "kusionstack.io/kusion/pkg/apis/intent" "kusionstack.io/kusion/pkg/modules" - "kusionstack.io/kusion/pkg/modules/inputs/workload" - "kusionstack.io/kusion/pkg/modules/generators/workload/network" + "kusionstack.io/kusion/pkg/modules/inputs/workload" ) // workloadServiceGenerator is a struct for generating service workload resources. diff --git a/pkg/modules/util.go b/pkg/modules/util.go index cab3bc78..edf6e967 100644 --- a/pkg/modules/util.go +++ b/pkg/modules/util.go @@ -219,12 +219,8 @@ func PatchResource[T any](resources map[string][]*intent.Resource, gvk string, p // AddKubeConfigIf adds kubeConfig from workspace to extensions of Kubernetes type resource in intent. // If there is already has kubeConfig in extensions, use the kubeConfig in extensions. func AddKubeConfigIf(i *intent.Intent, ws *apiv1.Workspace) { - config, err := workspace.GetKubernetesConfig(ws.Runtimes) - if errors.Is(err, workspace.ErrEmptyRuntimeConfigs) || errors.Is(err, workspace.ErrEmptyKubernetesConfig) { - return - } - kubeConfig := config.KubeConfig - if kubeConfig == "" { + config := workspace.GetKubernetesConfig(ws.Runtimes) + if config == nil || config.KubeConfig == "" { return } for n, resource := range i.Resources { @@ -233,7 +229,7 @@ func AddKubeConfigIf(i *intent.Intent, ws *apiv1.Workspace) { i.Resources[n].Extensions = make(map[string]any) } if extensionsKubeConfig, ok := resource.Extensions[intent.ResourceExtensionKubeConfig]; !ok || extensionsKubeConfig == "" { - i.Resources[n].Extensions[intent.ResourceExtensionKubeConfig] = kubeConfig + i.Resources[n].Extensions[intent.ResourceExtensionKubeConfig] = config.KubeConfig } } } diff --git a/pkg/workspace/util.go b/pkg/workspace/util.go index ad356f6d..2bab2c91 100644 --- a/pkg/workspace/util.go +++ b/pkg/workspace/util.go @@ -5,22 +5,13 @@ import ( "fmt" "os" - "kusionstack.io/kusion/pkg/apis/core/v1" + v1 "kusionstack.io/kusion/pkg/apis/core/v1" ) -var ( - ErrEmptyProjectName = errors.New("empty project name") - ErrEmptyModuleConfigs = errors.New("empty module configs") - ErrEmptyProjectModuleConfigs = errors.New("empty module configs of the project") - ErrEmptyProjectModuleConfig = errors.New("empty module config of the project") +var ErrEmptyProjectName = errors.New("empty project name") - ErrEmptyRuntimeConfigs = errors.New("empty runtime configs") - ErrEmptyKubernetesConfig = errors.New("empty kubernetes config") - ErrEmptyTerraformConfig = errors.New("empty terraform config") -) - -// CompleteWorkspace sets the workspace name and default value of unset item, should be called after ValidateWorkspace. -// The config items set as environment variables are not got by CompleteWorkspace. +// CompleteWorkspace sets the workspace name and default value of unset item, should be called after Validatev1. +// The config items set as environment variables are not got by Completev1. func CompleteWorkspace(ws *v1.Workspace, name string) { if ws.Name != "" { ws.Name = name @@ -32,10 +23,10 @@ func CompleteWorkspace(ws *v1.Workspace, name string) { // 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, ErrEmptyProjectModuleConfigs will get returned. +// If got empty module configs, return nil config and nil error. func GetProjectModuleConfigs(configs v1.ModuleConfigs, projectName string) (map[string]v1.GenericConfig, error) { if len(configs) == 0 { - return nil, ErrEmptyModuleConfigs + return nil, nil } if projectName == "" { return nil, ErrEmptyProjectName @@ -44,7 +35,7 @@ func GetProjectModuleConfigs(configs v1.ModuleConfigs, projectName string) (map[ projectCfgs := make(map[string]v1.GenericConfig) for name, cfg := range configs { projectCfg, err := getProjectModuleConfig(cfg, projectName) - if errors.Is(err, ErrEmptyProjectModuleConfig) { + if projectCfg == nil { continue } if err != nil { @@ -55,18 +46,15 @@ func GetProjectModuleConfigs(configs v1.ModuleConfigs, projectName string) (map[ } } - if len(projectCfgs) == 0 { - return nil, ErrEmptyProjectModuleConfigs - } return projectCfgs, nil } // GetProjectModuleConfig returns the module config of a specified project, should be called after // ValidateModuleConfig. -// If got empty module config, ErrEmptyProjectModuleConfig will get returned. +// If got empty module config, return nil config and nil error. func GetProjectModuleConfig(config *v1.ModuleConfig, projectName string) (v1.GenericConfig, error) { if config == nil { - return nil, ErrEmptyModuleConfig + return nil, nil } if projectName == "" { return nil, ErrEmptyProjectName @@ -106,55 +94,41 @@ func getProjectModuleConfig(config *v1.ModuleConfig, projectName string) (v1.Gen } } - if len(projectCfg) == 0 { - return nil, ErrEmptyProjectModuleConfig - } return projectCfg, nil } // GetKubernetesConfig returns kubernetes config from runtime config, should be called after // ValidateRuntimeConfigs. -// If got empty kubernetes config, ErrEmptyKubernetesConfig will get returned. -func GetKubernetesConfig(configs *v1.RuntimeConfigs) (*v1.KubernetesConfig, error) { +// If got empty kubernetes config, return nil. +func GetKubernetesConfig(configs *v1.RuntimeConfigs) *v1.KubernetesConfig { if configs == nil { - return nil, ErrEmptyRuntimeConfigs + return nil } - if configs.Kubernetes == nil { - return nil, ErrEmptyKubernetesConfig - } - return configs.Kubernetes, nil + return configs.Kubernetes } // GetTerraformConfig returns terraform config from runtime config, should be called after // ValidateRuntimeConfigs. -// If got empty terraform config, ErrEmptyTerraformConfig will get returned. -func GetTerraformConfig(configs *v1.RuntimeConfigs) (v1.TerraformConfig, error) { +// If got empty terraform config, return nil. +func GetTerraformConfig(configs *v1.RuntimeConfigs) v1.TerraformConfig { if configs == nil { - return nil, ErrEmptyRuntimeConfigs - } - if len(configs.Terraform) == 0 { - return nil, ErrEmptyTerraformConfig + return nil } - return configs.Terraform, nil + return configs.Terraform } // GetProviderConfig returns the specified terraform provider config from runtime config, should be called // after ValidateRuntimeConfigs. -// If got empty terraform config, ErrEmptyTerraformProviderConfig will get returned. +// If got empty terraform config, return nil config and nil error. func GetProviderConfig(configs *v1.RuntimeConfigs, providerName string) (*v1.ProviderConfig, error) { if providerName == "" { return nil, ErrEmptyTerraformProviderName } - config, err := GetTerraformConfig(configs) - if err != nil { - return nil, err - } - - cfg, ok := config[providerName] - if !ok { - return nil, ErrEmptyTerraformProviderConfig + config := GetTerraformConfig(configs) + if config == nil { + return nil, nil } - return cfg, nil + return config[providerName], nil } // GetBackendName returns the backend name that is configured in BackendConfigs, should be called after @@ -237,3 +211,63 @@ func CompleteWholeS3Config(config *v1.S3Config) { config.Region = region } } + +// GetIntFromGenericConfig returns the value of the key in config which should be of type int. +// If exist but not int, return error. If not exist, return 0, nil. +func GetIntFromGenericConfig(config v1.GenericConfig, key string) (int, error) { + value, ok := config[key] + if !ok { + return 0, nil + } + i, ok := value.(int) + if !ok { + return 0, fmt.Errorf("the value of %s is not map", key) + } + return i, nil +} + +// GetStringFromGenericConfig returns the value of the key in config which should be of type string. +// If exist but not string, return error; If not exist, return "", nil. +func GetStringFromGenericConfig(config v1.GenericConfig, key string) (string, error) { + value, ok := config[key] + if !ok { + return "", nil + } + s, ok := value.(string) + if !ok { + return "", fmt.Errorf("the value of %s is not string", key) + } + return s, nil +} + +// GetMapFromGenericConfig returns the value of the key in config which should be of type map[string]any. +// If exist but not map[string]any, return error; If not exist, return nil, nil. +func GetMapFromGenericConfig(config v1.GenericConfig, key string) (map[string]any, error) { + value, ok := config[key] + if !ok { + return nil, nil + } + m, ok := value.(map[string]any) + if !ok { + return nil, fmt.Errorf("the value of %s is not map", key) + } + return m, nil +} + +// GetStringMapFromGenericConfig returns the value of the key in config which should be of type map[string]string. +// If exist but not map[string]string, return error; If not exist, return nil, nil. +func GetStringMapFromGenericConfig(config v1.GenericConfig, key string) (map[string]string, error) { + m, err := GetMapFromGenericConfig(config, key) + if err != nil { + return nil, err + } + stringMap := make(map[string]string) + for k, v := range m { + stringValue, ok := v.(string) + if !ok { + return nil, fmt.Errorf("the value of %s.%s is not string", key, k) + } + stringMap[k] = stringValue + } + return stringMap, nil +} diff --git a/pkg/workspace/util_test.go b/pkg/workspace/util_test.go index e21d9fca..8e48b876 100644 --- a/pkg/workspace/util_test.go +++ b/pkg/workspace/util_test.go @@ -5,22 +5,38 @@ import ( "github.com/stretchr/testify/assert" - "kusionstack.io/kusion/pkg/apis/core/v1" + v1 "kusionstack.io/kusion/pkg/apis/core/v1" ) +func mockGenericConfig() v1.GenericConfig { + return v1.GenericConfig{ + "int_type_field": 2, + "string_type_field": "kusion", + "map_type_field": map[string]any{ + "k1": "v1", + "k2": 2, + }, + "string_map_type_field": map[string]any{ + "k1": "v1", + "k2": "v2", + }, + } +} + func Test_GetProjectModuleConfigs(t *testing.T) { testcases := []struct { - name string - success bool - projectName string - projectConfigs map[string]v1.GenericConfig - moduleConfigs v1.ModuleConfigs + name string + projectName string + moduleConfigs v1.ModuleConfigs + success bool + expectedProjectConfigs map[string]v1.GenericConfig }{ { - name: "successfully get project module configs", - success: true, - projectName: "foo", - projectConfigs: map[string]v1.GenericConfig{ + name: "successfully get project module configs", + projectName: "foo", + moduleConfigs: mockValidModuleConfigs(), + success: true, + expectedProjectConfigs: map[string]v1.GenericConfig{ "database": { "type": "aws", "version": "5.7", @@ -30,7 +46,6 @@ func Test_GetProjectModuleConfigs(t *testing.T) { "type": "aws", }, }, - moduleConfigs: mockValidModuleConfigs(), }, } @@ -38,47 +53,47 @@ func Test_GetProjectModuleConfigs(t *testing.T) { t.Run(tc.name, func(t *testing.T) { cfg, err := GetProjectModuleConfigs(tc.moduleConfigs, tc.projectName) assert.Equal(t, tc.success, err == nil) - assert.Equal(t, tc.projectConfigs, cfg) + assert.Equal(t, tc.expectedProjectConfigs, cfg) }) } } func Test_GetProjectModuleConfig(t *testing.T) { testcases := []struct { - name string - success bool - projectName string - projectConfig v1.GenericConfig - moduleConfig *v1.ModuleConfig + name string + success bool + projectName string + moduleConfig *v1.ModuleConfig + expectedProjectConfig v1.GenericConfig }{ { - name: "successfully get default project module config", - success: true, - projectName: "baz", - projectConfig: v1.GenericConfig{ + name: "successfully get default project module config", + projectName: "baz", + moduleConfig: mockValidModuleConfigs()["database"], + success: true, + expectedProjectConfig: v1.GenericConfig{ "type": "aws", "version": "5.7", "instanceType": "db.t3.micro", }, - moduleConfig: mockValidModuleConfigs()["database"], }, { - name: "successfully get override project module config", - success: true, - projectName: "foo", - projectConfig: v1.GenericConfig{ + name: "successfully get override project module config", + projectName: "foo", + moduleConfig: mockValidModuleConfigs()["database"], + success: true, + expectedProjectConfig: v1.GenericConfig{ "type": "aws", "version": "5.7", "instanceType": "db.t3.small", }, - moduleConfig: mockValidModuleConfigs()["database"], }, { - name: "failed to get config empty project name", - success: false, - projectName: "", - projectConfig: nil, - moduleConfig: mockValidModuleConfigs()["database"], + name: "failed to get config empty project name", + projectName: "", + moduleConfig: mockValidModuleConfigs()["database"], + success: false, + expectedProjectConfig: nil, }, } @@ -86,70 +101,235 @@ func Test_GetProjectModuleConfig(t *testing.T) { t.Run(tc.name, func(t *testing.T) { cfg, err := GetProjectModuleConfig(tc.moduleConfig, tc.projectName) assert.Equal(t, tc.success, err == nil) - assert.Equal(t, tc.projectConfig, cfg) + assert.Equal(t, tc.expectedProjectConfig, cfg) }) } } func Test_GetKubernetesConfig(t *testing.T) { testcases := []struct { - name string - success bool - kubernetesConfig *v1.KubernetesConfig - runtimeConfigs *v1.RuntimeConfigs + name string + runtimeConfigs *v1.RuntimeConfigs + expectedKubernetesConfig *v1.KubernetesConfig }{ { - name: "successfully get kubernetes config", - success: true, - kubernetesConfig: mockValidKubernetesConfig(), - runtimeConfigs: mockValidRuntimeConfigs(), + name: "successfully get kubernetes config", + runtimeConfigs: mockValidRuntimeConfigs(), + expectedKubernetesConfig: mockValidKubernetesConfig(), + }, + { + name: "get nil kubernetes config", + runtimeConfigs: nil, + expectedKubernetesConfig: nil, }, } 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) + cfg := GetKubernetesConfig(tc.runtimeConfigs) + assert.Equal(t, tc.expectedKubernetesConfig, cfg) + }) + } +} + +func Test_GetTerraformConfig(t *testing.T) { + testcases := []struct { + name string + runtimeConfigs *v1.RuntimeConfigs + expectedTerraformConfig v1.TerraformConfig + }{ + { + name: "successfully get terraform config", + runtimeConfigs: mockValidRuntimeConfigs(), + expectedTerraformConfig: mockValidTerraformConfig(), + }, + { + name: "get nil terraform config", + runtimeConfigs: nil, + expectedTerraformConfig: nil, + }, + } + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + cfg := GetTerraformConfig(tc.runtimeConfigs) + assert.Equal(t, tc.expectedTerraformConfig, cfg) }) } } func Test_GetTerraformProviderConfig(t *testing.T) { testcases := []struct { - name string - success bool - providerName string - providerConfig *v1.ProviderConfig - runtimeConfigs *v1.RuntimeConfigs + name string + providerName string + runtimeConfigs *v1.RuntimeConfigs + success bool + expectedProviderConfig *v1.ProviderConfig }{ { - name: "successfully get terraform provider config", - success: true, - providerName: "aws", - providerConfig: &v1.ProviderConfig{ + name: "successfully get terraform provider config", + providerName: "aws", + runtimeConfigs: mockValidRuntimeConfigs(), + success: true, + expectedProviderConfig: &v1.ProviderConfig{ Source: "hashicorp/aws", Version: "1.0.4", GenericConfig: v1.GenericConfig{ "region": "us-east-1", }, }, - runtimeConfigs: mockValidRuntimeConfigs(), }, + } + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + cfg, err := GetProviderConfig(tc.runtimeConfigs, tc.providerName) + assert.Equal(t, tc.success, err == nil) + assert.Equal(t, tc.expectedProviderConfig, cfg) + }) + } +} + +func Test_GetIntFieldFromGenericConfig(t *testing.T) { + testcases := []struct { + name string + key string + success bool + expectedValue int + }{ { - name: "failed to get config not exist provider", - success: false, - providerName: "alicloud", - providerConfig: nil, - runtimeConfigs: mockValidRuntimeConfigs(), + name: "successfully get int type field", + key: "int_type_field", + success: true, + expectedValue: 2, + }, + { + name: "get not exist field", + key: "not_exist", + success: true, + expectedValue: 0, + }, + { + name: "get field failed not int type", + key: "string_type_field", + success: false, + expectedValue: 0, }, } for _, tc := range testcases { t.Run(tc.name, func(t *testing.T) { - cfg, err := GetProviderConfig(tc.runtimeConfigs, tc.providerName) + value, err := GetIntFromGenericConfig(mockGenericConfig(), tc.key) + assert.Equal(t, tc.success, err == nil) + assert.Equal(t, tc.expectedValue, value) + }) + } +} + +func Test_GetStringFieldFromGenericConfig(t *testing.T) { + testcases := []struct { + name string + key string + success bool + expectedValue string + }{ + { + name: "successfully get string type field", + key: "string_type_field", + success: true, + expectedValue: "kusion", + }, + { + name: "get not exist field", + key: "not_exist", + success: true, + expectedValue: "", + }, + { + name: "get field failed not string type", + key: "int_type_field", + success: false, + expectedValue: "", + }, + } + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + value, err := GetStringFromGenericConfig(mockGenericConfig(), tc.key) + assert.Equal(t, tc.success, err == nil) + assert.Equal(t, tc.expectedValue, value) + }) + } +} + +func Test_GetMapFieldFromGenericConfig(t *testing.T) { + testcases := []struct { + name string + key string + success bool + expectedValue map[string]any + }{ + { + name: "successfully get map type field", + key: "map_type_field", + success: true, + expectedValue: map[string]any{ + "k1": "v1", + "k2": 2, + }, + }, + { + name: "get not exist field", + key: "not_exist", + success: true, + expectedValue: nil, + }, + { + name: "get field failed not map type", + key: "int_type_field", + success: false, + expectedValue: nil, + }, + } + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + value, err := GetMapFromGenericConfig(mockGenericConfig(), tc.key) + assert.Equal(t, tc.success, err == nil) + assert.Equal(t, tc.expectedValue, value) + }) + } +} + +func Test_GetStringMapFieldFromGenericConfig(t *testing.T) { + testcases := []struct { + name string + key string + success bool + expectedValue map[string]string + }{ + { + name: "successfully get string map type field", + key: "string_map_type_field", + success: true, + expectedValue: map[string]string{ + "k1": "v1", + "k2": "v2", + }, + }, + { + name: "get field failed map key not string", + key: "map_type_field", + success: false, + expectedValue: nil, + }, + } + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + value, err := GetStringMapFromGenericConfig(mockGenericConfig(), tc.key) assert.Equal(t, tc.success, err == nil) - assert.Equal(t, tc.providerConfig, cfg) + assert.Equal(t, tc.expectedValue, value) }) } }