diff --git a/pkg/modules/generators/accessories/database/alicloud_rds_test.go b/pkg/modules/generators/accessories/database/alicloud_rds_test.go index 49d4427a6..056f30dbd 100644 --- a/pkg/modules/generators/accessories/database/alicloud_rds_test.go +++ b/pkg/modules/generators/accessories/database/alicloud_rds_test.go @@ -37,11 +37,12 @@ func TestGenerateAlicloudResources(t *testing.T) { SubnetID: "test_subnet_id", } generator := &databaseGenerator{ - project: project, - stack: stack, - appName: appName, - workload: workload, - database: database, + project: project, + stack: stack, + appName: appName, + workload: workload, + database: database, + namespace: project.Name, } alicloudProviderRegion = "cn-beijing" @@ -99,11 +100,12 @@ func TestGenerateAlicloudDBInstance(t *testing.T) { SubnetID: "test_subnet_id", } generator := &databaseGenerator{ - project: project, - stack: stack, - appName: appName, - workload: workload, - database: database, + project: project, + stack: stack, + appName: appName, + workload: workload, + database: database, + namespace: project.Name, } alicloudDBInstanceID, r := generator.generateAlicloudDBInstance(alicloudProviderRegion, alicloudProvider, database) @@ -171,11 +173,12 @@ func TestGenerateAlicloudDBConnection(t *testing.T) { SubnetID: "test_subnet_id", } generator := &databaseGenerator{ - project: project, - stack: stack, - appName: appName, - workload: workload, - database: database, + project: project, + stack: stack, + appName: appName, + workload: workload, + database: database, + namespace: project.Name, } dbInstanceID := "aliyun:alicloud:alicloud_db_instance:testapp" @@ -228,11 +231,12 @@ func TestGenerateAlicloudRDSAccount(t *testing.T) { SubnetID: "test_subnet_id", } generator := &databaseGenerator{ - project: project, - stack: stack, - appName: appName, - workload: workload, - database: database, + project: project, + stack: stack, + appName: appName, + workload: workload, + database: database, + namespace: project.Name, } accountName := database.Username diff --git a/pkg/modules/generators/accessories/database/aws_rds_test.go b/pkg/modules/generators/accessories/database/aws_rds_test.go index 41fad6a22..71699e156 100644 --- a/pkg/modules/generators/accessories/database/aws_rds_test.go +++ b/pkg/modules/generators/accessories/database/aws_rds_test.go @@ -31,11 +31,12 @@ func TestGenerateAWSResources(t *testing.T) { Username: "root", } generator := &databaseGenerator{ - project: project, - stack: stack, - appName: appName, - workload: workload, - database: database, + project: project, + stack: stack, + appName: appName, + workload: workload, + database: database, + namespace: project.Name, } awsProviderRegion = "us-east-1" @@ -87,11 +88,12 @@ func TestGenerateAWSSecurityGroup(t *testing.T) { Username: "root", } generator := &databaseGenerator{ - project: project, - stack: stack, - appName: appName, - workload: workload, - database: database, + project: project, + stack: stack, + appName: appName, + workload: workload, + database: database, + namespace: project.Name, } var cidrBlocks []string @@ -154,11 +156,12 @@ func TestGenerateAWSDBInstance(t *testing.T) { Username: "root", } generator := &databaseGenerator{ - project: project, - stack: stack, - appName: appName, - workload: workload, - database: database, + project: project, + stack: stack, + appName: appName, + workload: workload, + database: database, + namespace: project.Name, } awsSecurityGroupID := "hashicorp:aws:aws_security_group:testapp-db" diff --git a/pkg/modules/generators/accessories/database/database_generator.go b/pkg/modules/generators/accessories/database/database_generator.go index e161ea7b4..35fd8319c 100644 --- a/pkg/modules/generators/accessories/database/database_generator.go +++ b/pkg/modules/generators/accessories/database/database_generator.go @@ -26,42 +26,28 @@ const ( ) type databaseGenerator struct { - project *apiv1.Project - stack *apiv1.Stack - appName string - workload *workload.Workload - database *database.Database + project *apiv1.Project + stack *apiv1.Stack + appName string + workload *workload.Workload + database *database.Database + namespace string } -func NewDatabaseGenerator( - project *apiv1.Project, - stack *apiv1.Stack, - appName string, - workload *workload.Workload, - database *database.Database, -) (modules.Generator, error) { - if len(project.Name) == 0 { - return nil, fmt.Errorf("project name must not be empty") - } - +func NewDatabaseGenerator(ctx modules.GeneratorContext) (modules.Generator, error) { return &databaseGenerator{ - project: project, - stack: stack, - appName: appName, - workload: workload, - database: database, + project: ctx.Project, + stack: ctx.Stack, + appName: ctx.Application.Name, + workload: ctx.Application.Workload, + database: ctx.Application.Database, + namespace: ctx.Namespace, }, nil } -func NewDatabaseGeneratorFunc( - project *apiv1.Project, - stack *apiv1.Stack, - appName string, - workload *workload.Workload, - database *database.Database, -) modules.NewGeneratorFunc { +func NewDatabaseGeneratorFunc(ctx modules.GeneratorContext) modules.NewGeneratorFunc { return func() (modules.Generator, error) { - return NewDatabaseGenerator(project, stack, appName, workload, database) + return NewDatabaseGenerator(ctx) } } @@ -160,7 +146,7 @@ func (g *databaseGenerator) generateDBSecret(hostAddress, username, password str }, ObjectMeta: metav1.ObjectMeta{ Name: g.appName + dbResSuffix, - Namespace: g.project.Name, + Namespace: g.namespace, }, StringData: data, } diff --git a/pkg/modules/generators/accessories/database/database_generator_test.go b/pkg/modules/generators/accessories/database/database_generator_test.go index bb8cdb85c..24f202f6c 100644 --- a/pkg/modules/generators/accessories/database/database_generator_test.go +++ b/pkg/modules/generators/accessories/database/database_generator_test.go @@ -9,38 +9,49 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" apiv1 "kusionstack.io/kusion/pkg/apis/core/v1" + "kusionstack.io/kusion/pkg/modules" "kusionstack.io/kusion/pkg/modules/inputs" "kusionstack.io/kusion/pkg/modules/inputs/accessories/database" "kusionstack.io/kusion/pkg/modules/inputs/workload" "kusionstack.io/kusion/pkg/modules/inputs/workload/container" ) -func TestNewDatabaseGenerator(t *testing.T) { - project := &apiv1.Project{ - Name: "testproject", +func newGeneratorContext( + project *apiv1.Project, + stack *apiv1.Stack, + appName string, + workload *workload.Workload, + database *database.Database, +) modules.GeneratorContext { + application := &inputs.AppConfiguration{ + Name: appName, + Workload: workload, + Database: database, } - stack := &apiv1.Stack{ - Name: "teststack", + return modules.GeneratorContext{ + Project: project, + Stack: stack, + Application: application, + Namespace: project.Name, } +} + +func TestNewDatabaseGenerator(t *testing.T) { + project := &apiv1.Project{Name: "testproject"} + stack := &apiv1.Stack{Name: "teststack"} appName := "testapp" - workload := &workload.Workload{} - database := &database.Database{} - generator, err := NewDatabaseGenerator(project, stack, appName, workload, database) + context := newGeneratorContext(project, stack, appName, &workload.Workload{}, &database.Database{}) + generator, err := NewDatabaseGenerator(context) assert.NoError(t, err) assert.NotNil(t, generator) } func TestGenerate(t *testing.T) { - project := &apiv1.Project{ - Name: "testproject", - } - stack := &apiv1.Stack{ - Name: "teststack", - } + project := &apiv1.Project{Name: "testproject"} + stack := &apiv1.Stack{Name: "teststack"} appName := "testapp" - workload := &workload.Workload{} - database := &database.Database{ + db := &database.Database{ Type: "aws", Engine: "mysql", Version: "5.7", @@ -48,7 +59,8 @@ func TestGenerate(t *testing.T) { Size: 10, Username: "root", } - generator, _ := NewDatabaseGenerator(project, stack, appName, workload, database) + context := newGeneratorContext(project, stack, appName, &workload.Workload{}, db) + generator, _ := NewDatabaseGenerator(context) awsProviderRegion = "us-east-1" spec := &apiv1.Intent{} @@ -105,15 +117,15 @@ func TestGenerate(t *testing.T) { ID: "hashicorp:aws:aws_db_instance:testapp", Type: "Terraform", Attributes: map[string]interface{}{ - "allocated_storage": database.Size, - "engine": database.Engine, - "engine_version": database.Version, + "allocated_storage": db.Size, + "engine": db.Engine, + "engine_version": db.Version, "identifier": appName, - "instance_class": database.InstanceType, + "instance_class": db.InstanceType, "password": "$kusion_path.hashicorp:random:random_password:testapp-db.result", "publicly_accessible": false, "skip_final_snapshot": true, - "username": database.Username, + "username": db.Username, "vpc_security_group_ids": []string{ "$kusion_path.hashicorp:aws:aws_security_group:testapp-db.id", }, @@ -140,7 +152,7 @@ func TestGenerate(t *testing.T) { "stringData": map[string]interface{}{ "hostAddress": "$kusion_path.hashicorp:aws:aws_db_instance:testapp.address", "password": "$kusion_path.hashicorp:random:random_password:testapp-db.result", - "username": database.Username, + "username": db.Username, }, }, Extensions: map[string]interface{}{ @@ -183,11 +195,12 @@ func TestInjectSecret(t *testing.T) { Username: "root", } generator := &databaseGenerator{ - project: project, - stack: stack, - appName: appName, - workload: workload, - database: database, + project: project, + stack: stack, + appName: appName, + workload: workload, + database: database, + namespace: project.Name, } data := make(map[string]string) @@ -242,11 +255,12 @@ func TestGenerateTFRandomPassword(t *testing.T) { workload := &workload.Workload{} database := &database.Database{} generator := &databaseGenerator{ - project: project, - stack: stack, - appName: appName, - workload: workload, - database: database, + project: project, + stack: stack, + appName: appName, + workload: workload, + database: database, + namespace: project.Name, } randomProvider := &inputs.Provider{} randomProvider.SetString(randomProviderURL) @@ -284,11 +298,12 @@ func TestGenerateDBSeret(t *testing.T) { workload := &workload.Workload{} database := &database.Database{} generator := &databaseGenerator{ - project: project, - stack: stack, - appName: appName, - workload: workload, - database: database, + project: project, + stack: stack, + appName: appName, + workload: workload, + database: database, + namespace: project.Name, } spec := &apiv1.Intent{} diff --git a/pkg/modules/generators/accessories/database/local_database.go b/pkg/modules/generators/accessories/database/local_database.go index 99a4e4181..fda659b00 100644 --- a/pkg/modules/generators/accessories/database/local_database.go +++ b/pkg/modules/generators/accessories/database/local_database.go @@ -65,7 +65,7 @@ func (g *databaseGenerator) generateLocalSecret(spec *apiv1.Intent) (string, err }, ObjectMeta: metav1.ObjectMeta{ Name: g.appName + dbResSuffix + localSecretSuffix, - Namespace: g.project.Name, + Namespace: g.namespace, }, StringData: data, } @@ -89,7 +89,7 @@ func (g *databaseGenerator) generateLocalPVC(db *database.Database, spec *apiv1. }, ObjectMeta: metav1.ObjectMeta{ Name: g.appName + dbResSuffix + localPVCSuffix, - Namespace: g.project.Name, + Namespace: g.namespace, Labels: localMatchLabels, }, Spec: v1.PersistentVolumeClaimSpec{ @@ -127,7 +127,7 @@ func (g *databaseGenerator) generateLocalDeployment(db *database.Database, spec }, ObjectMeta: metav1.ObjectMeta{ Name: g.appName + dbResSuffix + localDeploymentSuffix, - Namespace: g.project.Name, + Namespace: g.namespace, }, Spec: appsv1.DeploymentSpec{ Selector: &metav1.LabelSelector{ @@ -166,7 +166,7 @@ func (g *databaseGenerator) generateLocalService(db *database.Database, spec *ap }, ObjectMeta: metav1.ObjectMeta{ Name: svcName, - Namespace: g.project.Name, + Namespace: g.namespace, Labels: localMatchLabels, }, Spec: v1.ServiceSpec{ diff --git a/pkg/modules/generators/accessories/database/local_database_test.go b/pkg/modules/generators/accessories/database/local_database_test.go index bb746b1e7..853dc5c0e 100644 --- a/pkg/modules/generators/accessories/database/local_database_test.go +++ b/pkg/modules/generators/accessories/database/local_database_test.go @@ -29,11 +29,12 @@ func TestGenerateLocalResources(t *testing.T) { Username: "root", } generator := &databaseGenerator{ - project: project, - stack: stack, - appName: appName, - workload: workload, - database: database, + project: project, + stack: stack, + appName: appName, + workload: workload, + database: database, + namespace: project.Name, } spec := &apiv1.Intent{} @@ -79,11 +80,12 @@ func TestGenerateLocalSecret(t *testing.T) { Username: "root", } generator := &databaseGenerator{ - project: project, - stack: stack, - appName: appName, - workload: workload, - database: database, + project: project, + stack: stack, + appName: appName, + workload: workload, + database: database, + namespace: project.Name, } spec := &apiv1.Intent{} @@ -111,11 +113,12 @@ func TestGenerateLocalPVC(t *testing.T) { Username: "root", } generator := &databaseGenerator{ - project: project, - stack: stack, - appName: appName, - workload: workload, - database: database, + project: project, + stack: stack, + appName: appName, + workload: workload, + database: database, + namespace: project.Name, } spec := &apiv1.Intent{} @@ -141,11 +144,12 @@ func TestGenerateLocalDeployment(t *testing.T) { Username: "root", } generator := &databaseGenerator{ - project: project, - stack: stack, - appName: appName, - workload: workload, - database: database, + project: project, + stack: stack, + appName: appName, + workload: workload, + database: database, + namespace: project.Name, } spec := &apiv1.Intent{} @@ -171,11 +175,12 @@ func TestGenerateLocalService(t *testing.T) { Username: "root", } generator := &databaseGenerator{ - project: project, - stack: stack, - appName: appName, - workload: workload, - database: database, + project: project, + stack: stack, + appName: appName, + workload: workload, + database: database, + namespace: project.Name, } spec := &apiv1.Intent{} diff --git a/pkg/modules/generators/app_configurations_generator.go b/pkg/modules/generators/app_configurations_generator.go index 382fc43ee..fc660b937 100644 --- a/pkg/modules/generators/app_configurations_generator.go +++ b/pkg/modules/generators/app_configurations_generator.go @@ -82,13 +82,24 @@ func (g *appConfigurationGenerator) Generate(i *apiv1.Intent) error { return err } + // construct proper generator context + namespaceName := g.getNamespaceName(modulesConfig) + g.app.Name = g.appName + context := modules.GeneratorContext{ + Project: g.project, + Stack: g.stack, + Application: g.app, + Namespace: namespaceName, + ModuleInputs: modulesConfig, + } + // Generate resources gfs := []modules.NewGeneratorFunc{ - NewNamespaceGeneratorFunc(g.project.Name, g.ws), - accessories.NewDatabaseGeneratorFunc(g.project, g.stack, g.appName, g.app.Workload, g.app.Database), - workload.NewWorkloadGeneratorFunc(g.project, g.stack, g.appName, g.app.Workload, modulesConfig), - trait.NewOpsRuleGeneratorFunc(g.project, g.stack, g.appName, g.app, modulesConfig), - monitoring.NewMonitoringGeneratorFunc(g.project, g.app.Monitoring, g.appName), + NewNamespaceGeneratorFunc(context), + accessories.NewDatabaseGeneratorFunc(context), + workload.NewWorkloadGeneratorFunc(context), + trait.NewOpsRuleGeneratorFunc(context), + monitoring.NewMonitoringGeneratorFunc(context), // The OrderedResourcesGenerator should be executed after all resources are generated. NewOrderedResourcesGeneratorFunc(), } @@ -110,3 +121,25 @@ func (g *appConfigurationGenerator) Generate(i *apiv1.Intent) error { return nil } + +// getNamespaceName obtains the final namespace name using the following precedence +// (from lower to higher): +// - Project name +// - Namespace module config (specified in corresponding workspace file) +func (g *appConfigurationGenerator) getNamespaceName(moduleConfigs map[string]apiv1.GenericConfig) string { + if moduleConfigs == nil { + return g.project.Name + } + + namespaceName := g.project.Name + namespaceModuleConfigs, exist := moduleConfigs["namespace"] + if exist { + if name, ok := namespaceModuleConfigs["name"]; ok { + customNamespaceName, isString := name.(string) + if isString && len(customNamespaceName) > 0 { + namespaceName = customNamespaceName + } + } + } + return namespaceName +} diff --git a/pkg/modules/generators/app_configurations_generator_test.go b/pkg/modules/generators/app_configurations_generator_test.go index cf606ffc1..b44bbc255 100644 --- a/pkg/modules/generators/app_configurations_generator_test.go +++ b/pkg/modules/generators/app_configurations_generator_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "kusionstack.io/kusion/pkg/apis/core/v1" appmodel "kusionstack.io/kusion/pkg/modules/inputs" @@ -14,7 +15,7 @@ import ( func TestAppConfigurationGenerator_Generate(t *testing.T) { project, stack := buildMockProjectAndStack() appName, app := buildMockApp() - ws := buildMockWorkspace() + ws := buildMockWorkspace("") g := &appConfigurationGenerator{ project: project, @@ -31,12 +32,60 @@ func TestAppConfigurationGenerator_Generate(t *testing.T) { err := g.Generate(spec) assert.NoError(t, err) assert.NotEmpty(t, spec.Resources) + + // namespace name assertion + for _, res := range spec.Resources { + if res.Type != v1.Kubernetes { + continue + } + actual := mapToUnstructured(res.Attributes) + if actual.GetKind() == "Namespace" { + assert.Equal(t, "testproject", actual.GetName(), "namespace name should be fakeNs") + } else { + assert.Equal(t, "testproject", actual.GetNamespace(), "namespace name should be fakeNs") + } + } +} + +func TestAppConfigurationGenerator_Generate_CustomNamespace(t *testing.T) { + project, stack := buildMockProjectAndStack() + appName, app := buildMockApp() + ws := buildMockWorkspace("fakeNs") + + g := &appConfigurationGenerator{ + project: project, + stack: stack, + appName: appName, + app: app, + ws: ws, + } + + spec := &v1.Intent{ + Resources: []v1.Resource{}, + } + + err := g.Generate(spec) + assert.NoError(t, err) + assert.NotEmpty(t, spec.Resources) + + // namespace name assertion + for _, res := range spec.Resources { + if res.Type != v1.Kubernetes { + continue + } + actual := mapToUnstructured(res.Attributes) + if actual.GetKind() == "Namespace" { + assert.Equal(t, "fakeNs", actual.GetName(), "namespace name should be fakeNs") + } else { + assert.Equal(t, "fakeNs", actual.GetNamespace(), "namespace name should be fakeNs") + } + } } func TestNewAppConfigurationGeneratorFunc(t *testing.T) { project, stack := buildMockProjectAndStack() appName, app := buildMockApp() - ws := buildMockWorkspace() + ws := buildMockWorkspace("") t.Run("Valid app configuration generator func", func(t *testing.T) { g, err := NewAppConfigurationGeneratorFunc(project, stack, appName, app, ws)() @@ -92,7 +141,7 @@ func buildMockApp() (string, *appmodel.AppConfiguration) { } } -func buildMockWorkspace() *v1.Workspace { +func buildMockWorkspace(namespace string) *v1.Workspace { return &v1.Workspace{ Name: "test", Modules: v1.ModuleConfigs{ @@ -116,6 +165,11 @@ func buildMockWorkspace() *v1.Workspace { "type": "aws", }, }, + "namespace": { + Default: v1.GenericConfig{ + "name": namespace, + }, + }, }, Runtimes: &v1.RuntimeConfigs{ Kubernetes: &v1.KubernetesConfig{ @@ -139,3 +193,9 @@ func buildMockProjectAndStack() (*v1.Project, *v1.Stack) { return project, stack } + +func mapToUnstructured(data map[string]interface{}) *unstructured.Unstructured { + unstructuredObj := &unstructured.Unstructured{} + unstructuredObj.SetUnstructuredContent(data) + return unstructuredObj +} diff --git a/pkg/modules/generators/monitoring/monitoring_generator.go b/pkg/modules/generators/monitoring/monitoring_generator.go index 757487531..9fcf1a23f 100644 --- a/pkg/modules/generators/monitoring/monitoring_generator.go +++ b/pkg/modules/generators/monitoring/monitoring_generator.go @@ -12,37 +12,31 @@ import ( ) type monitoringGenerator struct { - project *apiv1.Project - monitor *monitoring.Monitor - appName string + project *apiv1.Project + monitor *monitoring.Monitor + appName string + namespace string } -func NewMonitoringGenerator( - project *apiv1.Project, - monitor *monitoring.Monitor, - appName string, -) (modules.Generator, error) { - if len(project.Name) == 0 { +func NewMonitoringGenerator(ctx modules.GeneratorContext) (modules.Generator, error) { + if len(ctx.Project.Name) == 0 { return nil, fmt.Errorf("project name must not be empty") } - if len(appName) == 0 { + if len(ctx.Application.Name) == 0 { return nil, fmt.Errorf("app name must not be empty") } return &monitoringGenerator{ - project: project, - monitor: monitor, - appName: appName, + project: ctx.Project, + monitor: ctx.Application.Monitoring, + appName: ctx.Application.Name, + namespace: ctx.Namespace, }, nil } -func NewMonitoringGeneratorFunc( - project *apiv1.Project, - monitor *monitoring.Monitor, - appName string, -) modules.NewGeneratorFunc { +func NewMonitoringGeneratorFunc(ctx modules.GeneratorContext) modules.NewGeneratorFunc { return func() (modules.Generator, error) { - return NewMonitoringGenerator(project, monitor, appName) + return NewMonitoringGenerator(ctx) } } @@ -81,7 +75,7 @@ func (g *monitoringGenerator) Generate(spec *apiv1.Intent) error { Kind: "ServiceMonitor", APIVersion: prometheusv1.SchemeGroupVersion.String(), }, - ObjectMeta: metav1.ObjectMeta{Name: fmt.Sprintf("%s-service-monitor", g.appName), Namespace: g.project.Name}, + ObjectMeta: metav1.ObjectMeta{Name: fmt.Sprintf("%s-service-monitor", g.appName), Namespace: g.namespace}, Spec: prometheusv1.ServiceMonitorSpec{ Selector: metav1.LabelSelector{ MatchLabels: monitoringLabels, @@ -113,7 +107,7 @@ func (g *monitoringGenerator) Generate(spec *apiv1.Intent) error { Kind: "PodMonitor", APIVersion: prometheusv1.SchemeGroupVersion.String(), }, - ObjectMeta: metav1.ObjectMeta{Name: fmt.Sprintf("%s-pod-monitor", g.appName), Namespace: g.project.Name}, + ObjectMeta: metav1.ObjectMeta{Name: fmt.Sprintf("%s-pod-monitor", g.appName), Namespace: g.namespace}, Spec: prometheusv1.PodMonitorSpec{ Selector: metav1.LabelSelector{ MatchLabels: monitoringLabels, diff --git a/pkg/modules/generators/monitoring/monitoring_generator_test.go b/pkg/modules/generators/monitoring/monitoring_generator_test.go index 1109eebcb..f7cd82dea 100644 --- a/pkg/modules/generators/monitoring/monitoring_generator_test.go +++ b/pkg/modules/generators/monitoring/monitoring_generator_test.go @@ -129,9 +129,10 @@ func TestMonitoringGenerator_Generate(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { g := &monitoringGenerator{ - project: tt.fields.project, - monitor: tt.fields.monitor, - appName: tt.fields.appName, + project: tt.fields.project, + monitor: tt.fields.monitor, + appName: tt.fields.appName, + namespace: tt.fields.project.Name, } if err := g.Generate(tt.args.spec); (err != nil) != tt.wantErr { t.Errorf("Generate() error = %v, wantErr %v", err, tt.wantErr) diff --git a/pkg/modules/generators/namespace_generator.go b/pkg/modules/generators/namespace_generator.go index 45fa2d885..96d6e4e61 100644 --- a/pkg/modules/generators/namespace_generator.go +++ b/pkg/modules/generators/namespace_generator.go @@ -1,39 +1,26 @@ package generators import ( - "fmt" - corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" apiv1 "kusionstack.io/kusion/pkg/apis/core/v1" "kusionstack.io/kusion/pkg/modules" - "kusionstack.io/kusion/pkg/workspace" ) type namespaceGenerator struct { - projectName string - moduleInputs map[string]apiv1.GenericConfig + context modules.GeneratorContext } -func NewNamespaceGenerator(projectName string, ws *apiv1.Workspace) (modules.Generator, error) { - if len(projectName) == 0 { - return nil, fmt.Errorf("project name must not be empty") - } - moduleInputs, err := workspace.GetProjectModuleConfigs(ws.Modules, projectName) - if err != nil { - return nil, fmt.Errorf("parse project matched module configs failed: %v", err) - } - +func NewNamespaceGenerator(ctx modules.GeneratorContext) (modules.Generator, error) { return &namespaceGenerator{ - projectName: projectName, - moduleInputs: moduleInputs, + context: ctx, }, nil } -func NewNamespaceGeneratorFunc(projectName string, workspace *apiv1.Workspace) modules.NewGeneratorFunc { +func NewNamespaceGeneratorFunc(ctx modules.GeneratorContext) modules.NewGeneratorFunc { return func() (modules.Generator, error) { - return NewNamespaceGenerator(projectName, workspace) + return NewNamespaceGenerator(ctx) } } @@ -47,7 +34,7 @@ func (g *namespaceGenerator) Generate(i *apiv1.Intent) error { Kind: "Namespace", APIVersion: corev1.SchemeGroupVersion.String(), }, - ObjectMeta: metav1.ObjectMeta{Name: g.getName(g.projectName)}, + ObjectMeta: metav1.ObjectMeta{Name: g.context.Namespace}, } // Avoid generating duplicate namespaces with the same ID. @@ -60,25 +47,3 @@ func (g *namespaceGenerator) Generate(i *apiv1.Intent) error { return modules.AppendToIntent(apiv1.Kubernetes, id, i, ns) } - -// getName obtains the name for this Namespace using the following precedence -// (from lower to higher): -// - Project name -// - Namespace module config (specified in corresponding workspace file) -func (g *namespaceGenerator) getName(projectName string) string { - if g.moduleInputs == nil { - return projectName - } - - namespaceName := projectName - namespaceModuleConfigs, exist := g.moduleInputs["namespace"] - if exist { - if name, ok := namespaceModuleConfigs["name"]; ok { - customNamespaceName, isString := name.(string) - if isString && len(customNamespaceName) > 0 { - namespaceName = customNamespaceName - } - } - } - return namespaceName -} diff --git a/pkg/modules/generators/namespace_generator_test.go b/pkg/modules/generators/namespace_generator_test.go index dda2400c8..abe398409 100644 --- a/pkg/modules/generators/namespace_generator_test.go +++ b/pkg/modules/generators/namespace_generator_test.go @@ -6,12 +6,12 @@ import ( "github.com/stretchr/testify/require" apiv1 "kusionstack.io/kusion/pkg/apis/core/v1" + "kusionstack.io/kusion/pkg/modules" ) func Test_namespaceGenerator_Generate(t *testing.T) { type fields struct { - projectName string - moduleInputs map[string]apiv1.GenericConfig + namespace string } type args struct { intent *apiv1.Intent @@ -26,7 +26,7 @@ func Test_namespaceGenerator_Generate(t *testing.T) { { name: "namespace", fields: fields{ - projectName: "fake-project", + namespace: "fakeNs", }, args: args{ intent: &apiv1.Intent{}, @@ -34,88 +34,14 @@ func Test_namespaceGenerator_Generate(t *testing.T) { want: &apiv1.Intent{ Resources: []apiv1.Resource{ { - ID: "v1:Namespace:fake-project", + ID: "v1:Namespace:fakeNs", Type: "Kubernetes", Attributes: map[string]interface{}{ "apiVersion": "v1", "kind": "Namespace", "metadata": map[string]interface{}{ "creationTimestamp": nil, - "name": "fake-project", - }, - "spec": make(map[string]interface{}), - "status": make(map[string]interface{}), - }, - DependsOn: nil, - Extensions: map[string]interface{}{ - "GVK": "/v1, Kind=Namespace", - }, - }, - }, - }, - wantErr: false, - }, - { - name: "customize_namespace", - fields: fields{ - projectName: "beep", - moduleInputs: map[string]apiv1.GenericConfig{ - "namespace": { - "name": "foo", - }, - }, - }, - args: args{ - intent: &apiv1.Intent{}, - }, - want: &apiv1.Intent{ - Resources: []apiv1.Resource{ - { - ID: "v1:Namespace:foo", - Type: "Kubernetes", - Attributes: map[string]interface{}{ - "apiVersion": "v1", - "kind": "Namespace", - "metadata": map[string]interface{}{ - "creationTimestamp": nil, - "name": "foo", - }, - "spec": make(map[string]interface{}), - "status": make(map[string]interface{}), - }, - DependsOn: nil, - Extensions: map[string]interface{}{ - "GVK": "/v1, Kind=Namespace", - }, - }, - }, - }, - wantErr: false, - }, - { - name: "mismatch_module_input", - fields: fields{ - projectName: "beep", - moduleInputs: map[string]apiv1.GenericConfig{ - "namespace": { - "type": "foo", - }, - }, - }, - args: args{ - intent: &apiv1.Intent{}, - }, - want: &apiv1.Intent{ - Resources: []apiv1.Resource{ - { - ID: "v1:Namespace:beep", - Type: "Kubernetes", - Attributes: map[string]interface{}{ - "apiVersion": "v1", - "kind": "Namespace", - "metadata": map[string]interface{}{ - "creationTimestamp": nil, - "name": "beep", + "name": "fakeNs", }, "spec": make(map[string]interface{}), "status": make(map[string]interface{}), @@ -132,9 +58,11 @@ func Test_namespaceGenerator_Generate(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + ctx := modules.GeneratorContext{ + Namespace: tt.fields.namespace, + } g := &namespaceGenerator{ - projectName: tt.fields.projectName, - moduleInputs: tt.fields.moduleInputs, + context: ctx, } if err := g.Generate(tt.args.intent); (err != nil) != tt.wantErr { t.Errorf("Generate() error = %v, wantErr %v", err, tt.wantErr) diff --git a/pkg/modules/generators/trait/ops_rule_generator.go b/pkg/modules/generators/trait/ops_rule_generator.go index 7dd049226..cf5e83ded 100644 --- a/pkg/modules/generators/trait/ops_rule_generator.go +++ b/pkg/modules/generators/trait/ops_rule_generator.go @@ -17,33 +17,22 @@ type opsRuleGenerator struct { appName string app *appmodule.AppConfiguration modulesConfig map[string]apiv1.GenericConfig + namespace string } -func NewOpsRuleGenerator( - project *apiv1.Project, - stack *apiv1.Stack, - appName string, - app *appmodule.AppConfiguration, - modulesConfig map[string]apiv1.GenericConfig, -) (modules.Generator, error) { +func NewOpsRuleGenerator(ctx modules.GeneratorContext) (modules.Generator, error) { return &opsRuleGenerator{ - project: project, - stack: stack, - appName: appName, - app: app, - modulesConfig: modulesConfig, + project: ctx.Project, + stack: ctx.Stack, + appName: ctx.Application.Name, + app: ctx.Application, + modulesConfig: ctx.ModuleInputs, }, nil } -func NewOpsRuleGeneratorFunc( - project *apiv1.Project, - stack *apiv1.Stack, - appName string, - app *appmodule.AppConfiguration, - modulesConfig map[string]apiv1.GenericConfig, -) modules.NewGeneratorFunc { +func NewOpsRuleGeneratorFunc(ctx modules.GeneratorContext) modules.NewGeneratorFunc { return func() (modules.Generator, error) { - return NewOpsRuleGenerator(project, stack, appName, app, modulesConfig) + return NewOpsRuleGenerator(ctx) } } @@ -70,7 +59,7 @@ func (g *opsRuleGenerator) Generate(spec *apiv1.Intent) error { }, ObjectMeta: metav1.ObjectMeta{ Name: modules.UniqueAppName(g.project.Name, g.stack.Name, g.appName), - Namespace: g.project.Name, + Namespace: g.namespace, }, Spec: v1alpha1.PodTransitionRuleSpec{ Selector: &metav1.LabelSelector{ diff --git a/pkg/modules/generators/trait/ops_rule_generator_test.go b/pkg/modules/generators/trait/ops_rule_generator_test.go index 06fb4dd6a..62b704db5 100644 --- a/pkg/modules/generators/trait/ops_rule_generator_test.go +++ b/pkg/modules/generators/trait/ops_rule_generator_test.go @@ -7,6 +7,7 @@ import ( "github.com/stretchr/testify/require" apiv1 "kusionstack.io/kusion/pkg/apis/core/v1" + "kusionstack.io/kusion/pkg/modules" appmodule "kusionstack.io/kusion/pkg/modules/inputs" "kusionstack.io/kusion/pkg/modules/inputs/trait" "kusionstack.io/kusion/pkg/modules/inputs/workload" @@ -189,6 +190,7 @@ func Test_opsRuleGenerator_Generate(t *testing.T) { appName: tt.fields.appName, app: tt.fields.app, modulesConfig: tt.fields.workspaceConfig, + namespace: tt.fields.project.Name, } err := g.Generate(tt.args.spec) if tt.wantErr { @@ -210,6 +212,9 @@ func TestNewOpsRuleGeneratorFunc(t *testing.T) { s := &apiv1.Stack{ Name: "dev", } + app := &appmodule.AppConfiguration{ + Name: "beep", + } type args struct { project *apiv1.Project @@ -230,7 +235,7 @@ func TestNewOpsRuleGeneratorFunc(t *testing.T) { project: p, stack: s, appName: "", - app: nil, + app: app, ws: map[string]apiv1.GenericConfig{ "opsRule": { "maxUnavailable": "30%", @@ -241,7 +246,8 @@ func TestNewOpsRuleGeneratorFunc(t *testing.T) { want: &opsRuleGenerator{ project: p, stack: s, - appName: "", + appName: "beep", + app: app, modulesConfig: map[string]apiv1.GenericConfig{ "opsRule": { "maxUnavailable": "30%", @@ -253,7 +259,14 @@ func TestNewOpsRuleGeneratorFunc(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - f := NewOpsRuleGeneratorFunc(tt.args.project, tt.args.stack, tt.args.appName, tt.args.app, tt.args.ws) + context := modules.GeneratorContext{ + Project: tt.args.project, + Stack: tt.args.stack, + Application: tt.args.app, + Namespace: tt.args.project.Name, + ModuleInputs: tt.args.ws, + } + f := NewOpsRuleGeneratorFunc(context) g, err := f() if tt.wantErr { require.Error(t, err) diff --git a/pkg/modules/generators/workload/job_generator.go b/pkg/modules/generators/workload/job_generator.go index 070bf83f7..04578a03c 100644 --- a/pkg/modules/generators/workload/job_generator.go +++ b/pkg/modules/generators/workload/job_generator.go @@ -18,33 +18,23 @@ type jobGenerator struct { appName string job *workload.Job jobConfig apiv1.GenericConfig + namespace string } -func NewJobGenerator( - project *apiv1.Project, - stack *apiv1.Stack, - appName string, - job *workload.Job, - jobConfig apiv1.GenericConfig, -) (modules.Generator, error) { +func NewJobGenerator(ctx modules.GeneratorContext) (modules.Generator, error) { return &jobGenerator{ - project: project, - stack: stack, - appName: appName, - job: job, - jobConfig: jobConfig, + project: ctx.Project, + stack: ctx.Stack, + appName: ctx.Application.Name, + job: ctx.Application.Workload.Job, + jobConfig: ctx.ModuleInputs[workload.ModuleJob], + namespace: ctx.Namespace, }, nil } -func NewJobGeneratorFunc( - project *apiv1.Project, - stack *apiv1.Stack, - appName string, - job *workload.Job, - jobConfig apiv1.GenericConfig, -) modules.NewGeneratorFunc { +func NewJobGeneratorFunc(ctx modules.GeneratorContext) modules.NewGeneratorFunc { return func() (modules.Generator, error) { - return NewJobGenerator(project, stack, appName, job, jobConfig) + return NewJobGenerator(ctx) } } @@ -65,7 +55,7 @@ func (g *jobGenerator) Generate(spec *apiv1.Intent) error { uniqueAppName := modules.UniqueAppName(g.project.Name, g.stack.Name, g.appName) meta := metav1.ObjectMeta{ - Namespace: g.project.Name, + Namespace: g.namespace, Name: uniqueAppName, Labels: modules.MergeMaps( modules.UniqueAppLabels(g.project.Name, g.appName), @@ -83,7 +73,7 @@ func (g *jobGenerator) Generate(spec *apiv1.Intent) error { for _, cm := range configMaps { cmObj := cm - cmObj.Namespace = g.project.Name + cmObj.Namespace = g.namespace if err = modules.AppendToIntent( apiv1.Kubernetes, modules.KubernetesResourceID(cmObj.TypeMeta, cmObj.ObjectMeta), diff --git a/pkg/modules/generators/workload/job_generator_test.go b/pkg/modules/generators/workload/job_generator_test.go index 77e6112d8..270cf3510 100644 --- a/pkg/modules/generators/workload/job_generator_test.go +++ b/pkg/modules/generators/workload/job_generator_test.go @@ -8,9 +8,35 @@ import ( apiv1 "kusionstack.io/kusion/pkg/apis/core/v1" "kusionstack.io/kusion/pkg/modules" + "kusionstack.io/kusion/pkg/modules/inputs" "kusionstack.io/kusion/pkg/modules/inputs/workload" ) +func newGeneratorContextWithJob( + project *apiv1.Project, + stack *apiv1.Stack, + appName string, + job *workload.Job, + jobConfig apiv1.GenericConfig, +) modules.GeneratorContext { + application := &inputs.AppConfiguration{ + Name: appName, + Workload: &workload.Workload{ + Job: job, + }, + } + moduleInputs := map[string]apiv1.GenericConfig{ + workload.ModuleJob: jobConfig, + } + return modules.GeneratorContext{ + Project: project, + Stack: stack, + Application: application, + Namespace: project.Name, + ModuleInputs: moduleInputs, + } +} + func TestNewJobGenerator(t *testing.T) { expectedProject := &apiv1.Project{ Name: "test", @@ -26,7 +52,8 @@ func TestNewJobGenerator(t *testing.T) { "workload-type": "Job", }, } - actual, err := NewJobGenerator(expectedProject, expectedStack, expectedAppName, expectedJob, expectedJobConfig) + ctx := newGeneratorContextWithJob(expectedProject, expectedStack, expectedAppName, expectedJob, expectedJobConfig) + actual, err := NewJobGenerator(ctx) assert.NoError(t, err, "Error should be nil") assert.NotNil(t, actual, "Generator should not be nil") @@ -52,7 +79,8 @@ func TestNewJobGeneratorFunc(t *testing.T) { "workload-type": "Job", }, } - generatorFunc := NewJobGeneratorFunc(expectedProject, expectedStack, expectedAppName, expectedJob, expectedJobConfig) + ctx := newGeneratorContextWithJob(expectedProject, expectedStack, expectedAppName, expectedJob, expectedJobConfig) + generatorFunc := NewJobGeneratorFunc(ctx) actualGenerator, err := generatorFunc() assert.NoError(t, err, "Error should be nil") @@ -94,7 +122,8 @@ func TestJobGenerator_Generate(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - generator, _ := NewJobGenerator(tc.expectedProject, tc.expectedStack, tc.expectedAppName, tc.expectedJob, tc.expectedJobConfig) + ctx := newGeneratorContextWithJob(tc.expectedProject, tc.expectedStack, tc.expectedAppName, tc.expectedJob, tc.expectedJobConfig) + generator, _ := NewJobGenerator(ctx) spec := &apiv1.Intent{} err := generator.Generate(spec) diff --git a/pkg/modules/generators/workload/network/ports_generator.go b/pkg/modules/generators/workload/network/ports_generator.go index c7e43c20f..b4a071c60 100644 --- a/pkg/modules/generators/workload/network/ports_generator.go +++ b/pkg/modules/generators/workload/network/ports_generator.go @@ -51,22 +51,23 @@ type portsGenerator struct { labels map[string]string annotations map[string]string ports []network.Port + namespace string } // NewPortsGenerator returns a new portsGenerator instance, and do the validation and completion job. func NewPortsGenerator( - appName, projectName, stackName string, + ctx modules.GeneratorContext, selectors, labels, annotations map[string]string, - ports []network.Port, ) (modules.Generator, error) { generator := &portsGenerator{ - appName: appName, - projectName: projectName, - stackName: stackName, + appName: ctx.Application.Name, + projectName: ctx.Project.Name, + stackName: ctx.Stack.Name, selector: selectors, labels: labels, annotations: annotations, - ports: ports, + ports: ctx.Application.Workload.Service.Ports, + namespace: ctx.Namespace, } if err := generator.validate(); err != nil { @@ -79,12 +80,11 @@ func NewPortsGenerator( // NewPortsGeneratorFunc returns a new NewGeneratorFunc that returns a portsGenerator instance. func NewPortsGeneratorFunc( - appName, projectName, stackName string, + ctx modules.GeneratorContext, selectors, labels, annotations map[string]string, - ports []network.Port, ) modules.NewGeneratorFunc { return func() (modules.Generator, error) { - return NewPortsGenerator(appName, projectName, stackName, selectors, labels, annotations, ports) + return NewPortsGenerator(ctx, selectors, labels, annotations) } } @@ -154,7 +154,7 @@ func (g *portsGenerator) generateK8sSvc(public bool, ports []network.Port) *v1.S }, ObjectMeta: metav1.ObjectMeta{ Name: name, - Namespace: g.projectName, + Namespace: g.namespace, Labels: g.labels, Annotations: g.annotations, }, diff --git a/pkg/modules/generators/workload/network/ports_generator_test.go b/pkg/modules/generators/workload/network/ports_generator_test.go index 7ab627557..545e7f56b 100644 --- a/pkg/modules/generators/workload/network/ports_generator_test.go +++ b/pkg/modules/generators/workload/network/ports_generator_test.go @@ -123,6 +123,7 @@ func TestPortsGenerator_Generate(t *testing.T) { Public: false, }, }, + namespace: "testProject", }, }, args: struct { diff --git a/pkg/modules/generators/workload/secret/secret_generator.go b/pkg/modules/generators/workload/secret/secret_generator.go index 107665541..b29d137e2 100644 --- a/pkg/modules/generators/workload/secret/secret_generator.go +++ b/pkg/modules/generators/workload/secret/secret_generator.go @@ -13,30 +13,33 @@ import ( ) type secretGenerator struct { - project *apiv1.Project - secrets map[string]workload.Secret + project *apiv1.Project + secrets map[string]workload.Secret + namespace string } -func NewSecretGenerator( - project *apiv1.Project, - secrets map[string]workload.Secret, -) (modules.Generator, error) { - if len(project.Name) == 0 { +func NewSecretGenerator(ctx modules.GeneratorContext) (modules.Generator, error) { + if len(ctx.Project.Name) == 0 { return nil, fmt.Errorf("project name must not be empty") } + var secrets map[string]workload.Secret + if ctx.Application.Workload.Service != nil { + secrets = ctx.Application.Workload.Service.Secrets + } else { + secrets = ctx.Application.Workload.Job.Secrets + } + return &secretGenerator{ - project: project, - secrets: secrets, + project: ctx.Project, + secrets: secrets, + namespace: ctx.Namespace, }, nil } -func NewSecretGeneratorFunc( - project *apiv1.Project, - secrets map[string]workload.Secret, -) modules.NewGeneratorFunc { +func NewSecretGeneratorFunc(ctx modules.GeneratorContext) modules.NewGeneratorFunc { return func() (modules.Generator, error) { - return NewSecretGenerator(project, secrets) + return NewSecretGenerator(ctx) } } @@ -46,7 +49,7 @@ func (g *secretGenerator) Generate(spec *apiv1.Intent) error { } for secretName, secretRef := range g.secrets { - secret, err := generateSecret(g.project, secretName, secretRef) + secret, err := generateSecret(g.namespace, secretName, secretRef) if err != nil { return err } @@ -69,16 +72,16 @@ func (g *secretGenerator) Generate(spec *apiv1.Intent) error { // generateSecret generates target secret based on secret type. Most of these secret types are just semantic wrapper // of native Kubernetes secret types:https://kubernetes.io/docs/concepts/configuration/secret/#secret-types, and more // detailed usage info can be found in public documentation. -func generateSecret(project *apiv1.Project, secretName string, secretRef workload.Secret) (*v1.Secret, error) { +func generateSecret(namespace, secretName string, secretRef workload.Secret) (*v1.Secret, error) { switch secretRef.Type { case "basic": - return generateBasic(project, secretName, secretRef) + return generateBasic(namespace, secretName, secretRef) case "token": - return generateToken(project, secretName, secretRef) + return generateToken(namespace, secretName, secretRef) case "opaque": - return generateOpaque(project, secretName, secretRef) + return generateOpaque(namespace, secretName, secretRef) case "certificate": - return generateCertificate(project, secretName, secretRef) + return generateCertificate(namespace, secretName, secretRef) default: return nil, fmt.Errorf("unrecognized secret type %s", secretRef.Type) } @@ -86,7 +89,7 @@ func generateSecret(project *apiv1.Project, secretName string, secretRef workloa // generateBasic generates secret used for basic authentication. The basic secret type // is used for username / password pairs. -func generateBasic(project *apiv1.Project, secretName string, secretRef workload.Secret) (*v1.Secret, error) { +func generateBasic(namespace, secretName string, secretRef workload.Secret) (*v1.Secret, error) { secret := &v1.Secret{ TypeMeta: metav1.TypeMeta{ APIVersion: v1.SchemeGroupVersion.String(), @@ -94,7 +97,7 @@ func generateBasic(project *apiv1.Project, secretName string, secretRef workload }, ObjectMeta: metav1.ObjectMeta{ Name: secretName, - Namespace: project.Name, + Namespace: namespace, }, Data: grabData(secretRef.Data, v1.BasicAuthUsernameKey, v1.BasicAuthPasswordKey), Immutable: &secretRef.Immutable, @@ -113,7 +116,7 @@ func generateBasic(project *apiv1.Project, secretName string, secretRef workload // generateToken generates secret used for password. Token secrets are useful for generating // a password or secure string used for passwords when the user is already known or not required. -func generateToken(project *apiv1.Project, secretName string, secretRef workload.Secret) (*v1.Secret, error) { +func generateToken(namespace, secretName string, secretRef workload.Secret) (*v1.Secret, error) { secret := &v1.Secret{ TypeMeta: metav1.TypeMeta{ APIVersion: v1.SchemeGroupVersion.String(), @@ -121,7 +124,7 @@ func generateToken(project *apiv1.Project, secretName string, secretRef workload }, ObjectMeta: metav1.ObjectMeta{ Name: secretName, - Namespace: project.Name, + Namespace: namespace, }, Data: grabData(secretRef.Data, "token"), Immutable: &secretRef.Immutable, @@ -137,7 +140,7 @@ func generateToken(project *apiv1.Project, secretName string, secretRef workload } // generateOpaque generates secret used for arbitrary user-defined data. -func generateOpaque(project *apiv1.Project, secretName string, secretRef workload.Secret) (*v1.Secret, error) { +func generateOpaque(namespace, secretName string, secretRef workload.Secret) (*v1.Secret, error) { secret := &v1.Secret{ TypeMeta: metav1.TypeMeta{ APIVersion: v1.SchemeGroupVersion.String(), @@ -145,7 +148,7 @@ func generateOpaque(project *apiv1.Project, secretName string, secretRef workloa }, ObjectMeta: metav1.ObjectMeta{ Name: secretName, - Namespace: project.Name, + Namespace: namespace, }, Data: grabData(secretRef.Data, maps.Keys(secretRef.Data)...), Immutable: &secretRef.Immutable, @@ -158,7 +161,7 @@ func generateOpaque(project *apiv1.Project, secretName string, secretRef workloa // generateCertificate generates secret used for storing a certificate and its associated key. // One common use for TLS Secrets is to configure encryption in transit for an Ingress, but // you can also use it with other resources or directly in your workload. -func generateCertificate(project *apiv1.Project, secretName string, secretRef workload.Secret) (*v1.Secret, error) { +func generateCertificate(namespace, secretName string, secretRef workload.Secret) (*v1.Secret, error) { secret := &v1.Secret{ TypeMeta: metav1.TypeMeta{ APIVersion: v1.SchemeGroupVersion.String(), @@ -166,7 +169,7 @@ func generateCertificate(project *apiv1.Project, secretName string, secretRef wo }, ObjectMeta: metav1.ObjectMeta{ Name: secretName, - Namespace: project.Name, + Namespace: namespace, }, Data: grabData(secretRef.Data, v1.TLSCertKey, v1.TLSPrivateKeyKey), Immutable: &secretRef.Immutable, diff --git a/pkg/modules/generators/workload/secret/secret_generator_test.go b/pkg/modules/generators/workload/secret/secret_generator_test.go index 20af65e7a..de8b0a5e2 100644 --- a/pkg/modules/generators/workload/secret/secret_generator_test.go +++ b/pkg/modules/generators/workload/secret/secret_generator_test.go @@ -6,6 +6,8 @@ import ( "github.com/stretchr/testify/require" apiv1 "kusionstack.io/kusion/pkg/apis/core/v1" + "kusionstack.io/kusion/pkg/modules" + "kusionstack.io/kusion/pkg/modules/inputs" "kusionstack.io/kusion/pkg/modules/inputs/workload" ) @@ -82,7 +84,20 @@ func TestGenerateSecret(t *testing.T) { }, } intent := &apiv1.Intent{} - generator, _ := NewSecretGenerator(project, secrets) + context := modules.GeneratorContext{ + Project: project, + Application: &inputs.AppConfiguration{ + Workload: &workload.Workload{ + Service: &workload.Service{ + Base: workload.Base{ + Secrets: secrets, + }, + }, + }, + }, + Namespace: project.Name, + } + generator, _ := NewSecretGenerator(context) err := generator.Generate(intent) if test.expectErr == "" { require.NoError(t, err) diff --git a/pkg/modules/generators/workload/service_generator.go b/pkg/modules/generators/workload/service_generator.go index 179b375f6..0a863108d 100644 --- a/pkg/modules/generators/workload/service_generator.go +++ b/pkg/modules/generators/workload/service_generator.go @@ -24,47 +24,41 @@ type workloadServiceGenerator struct { appName string service *workload.Service serviceConfig apiv1.GenericConfig + namespace string + + // for internal generator + context modules.GeneratorContext } // NewWorkloadServiceGenerator returns a new workloadServiceGenerator instance. -func NewWorkloadServiceGenerator( - project *apiv1.Project, - stack *apiv1.Stack, - appName string, - service *workload.Service, - serviceConfig apiv1.GenericConfig, -) (modules.Generator, error) { - if len(project.Name) == 0 { +func NewWorkloadServiceGenerator(ctx modules.GeneratorContext) (modules.Generator, error) { + if len(ctx.Project.Name) == 0 { return nil, fmt.Errorf("project name must not be empty") } - if len(appName) == 0 { + if len(ctx.Application.Name) == 0 { return nil, fmt.Errorf("app name must not be empty") } - if service == nil { + if ctx.Application.Workload.Service == nil { return nil, fmt.Errorf("service workload must not be nil") } return &workloadServiceGenerator{ - project: project, - stack: stack, - appName: appName, - service: service, - serviceConfig: serviceConfig, + project: ctx.Project, + stack: ctx.Stack, + appName: ctx.Application.Name, + service: ctx.Application.Workload.Service, + serviceConfig: ctx.ModuleInputs[workload.ModuleService], + namespace: ctx.Namespace, + context: ctx, }, nil } // NewWorkloadServiceGeneratorFunc returns a new NewGeneratorFunc that returns a workloadServiceGenerator instance. -func NewWorkloadServiceGeneratorFunc( - project *apiv1.Project, - stack *apiv1.Stack, - appName string, - service *workload.Service, - serviceConfig apiv1.GenericConfig, -) modules.NewGeneratorFunc { +func NewWorkloadServiceGeneratorFunc(ctx modules.GeneratorContext) modules.NewGeneratorFunc { return func() (modules.Generator, error) { - return NewWorkloadServiceGenerator(project, stack, appName, service, serviceConfig) + return NewWorkloadServiceGenerator(ctx) } } @@ -96,7 +90,7 @@ func (g *workloadServiceGenerator) Generate(spec *apiv1.Intent) error { // Create ConfigMap objects based on the app's configuration. for _, cm := range configMaps { cmObj := cm - cmObj.Namespace = g.project.Name + cmObj.Namespace = g.namespace if err = modules.AppendToIntent( apiv1.Kubernetes, modules.KubernetesResourceID(cmObj.TypeMeta, cmObj.ObjectMeta), @@ -117,7 +111,7 @@ func (g *workloadServiceGenerator) Generate(spec *apiv1.Intent) error { Labels: labels, Annotations: annotations, Name: uniqueAppName, - Namespace: g.project.Name, + Namespace: g.namespace, } podTemplateSpec := v1.PodTemplateSpec{ ObjectMeta: metav1.ObjectMeta{ @@ -172,7 +166,7 @@ func (g *workloadServiceGenerator) Generate(spec *apiv1.Intent) error { // generate K8s Service from ports config. if len(g.service.Ports) != 0 { - portsGeneratorFunc := network.NewPortsGeneratorFunc(g.appName, g.project.Name, g.stack.Name, selector, labels, annotations, g.service.Ports) + portsGeneratorFunc := network.NewPortsGeneratorFunc(g.context, selector, labels, annotations) if err = modules.CallGenerators(spec, portsGeneratorFunc); err != nil { return err } diff --git a/pkg/modules/generators/workload/service_generator_test.go b/pkg/modules/generators/workload/service_generator_test.go index 3fedd789a..39a841b08 100644 --- a/pkg/modules/generators/workload/service_generator_test.go +++ b/pkg/modules/generators/workload/service_generator_test.go @@ -9,6 +9,8 @@ import ( "gopkg.in/yaml.v3" apiv1 "kusionstack.io/kusion/pkg/apis/core/v1" + "kusionstack.io/kusion/pkg/modules" + "kusionstack.io/kusion/pkg/modules/inputs" "kusionstack.io/kusion/pkg/modules/inputs/workload" "kusionstack.io/kusion/pkg/modules/inputs/workload/container" "kusionstack.io/kusion/pkg/modules/inputs/workload/network" @@ -280,12 +282,25 @@ status: {} } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + ctx := modules.GeneratorContext{ + Project: tt.fields.project, + Stack: tt.fields.stack, + Application: &inputs.AppConfiguration{ + Name: tt.fields.appName, + Workload: &workload.Workload{ + Service: tt.fields.service, + }, + }, + Namespace: tt.fields.project.Name, + } g := &workloadServiceGenerator{ project: tt.fields.project, stack: tt.fields.stack, appName: tt.fields.appName, service: tt.fields.service, serviceConfig: tt.fields.serviceConfig, + namespace: tt.fields.project.Name, + context: ctx, } if err := g.Generate(tt.args.spec); (err != nil) != tt.wantErr { t.Errorf("Generate() error = %v, wantErr %v", err, tt.wantErr) diff --git a/pkg/modules/generators/workload/workload_generator.go b/pkg/modules/generators/workload/workload_generator.go index 7e4aec7d5..e8c181113 100644 --- a/pkg/modules/generators/workload/workload_generator.go +++ b/pkg/modules/generators/workload/workload_generator.go @@ -30,37 +30,31 @@ type workloadGenerator struct { appName string workload *workload.Workload moduleConfigs map[string]apiv1.GenericConfig + namespace string + + // for internal generator + context modules.GeneratorContext } -func NewWorkloadGenerator( - project *apiv1.Project, - stack *apiv1.Stack, - appName string, - workload *workload.Workload, - moduleConfigs map[string]apiv1.GenericConfig, -) (modules.Generator, error) { - if len(project.Name) == 0 { +func NewWorkloadGenerator(ctx modules.GeneratorContext) (modules.Generator, error) { + if len(ctx.Project.Name) == 0 { return nil, fmt.Errorf("project name must not be empty") } return &workloadGenerator{ - project: project, - stack: stack, - appName: appName, - workload: workload, - moduleConfigs: moduleConfigs, + project: ctx.Project, + stack: ctx.Stack, + appName: ctx.Application.Name, + workload: ctx.Application.Workload, + moduleConfigs: ctx.ModuleInputs, + namespace: ctx.Namespace, + context: ctx, }, nil } -func NewWorkloadGeneratorFunc( - project *apiv1.Project, - stack *apiv1.Stack, - appName string, - workload *workload.Workload, - moduleConfigs map[string]apiv1.GenericConfig, -) modules.NewGeneratorFunc { +func NewWorkloadGeneratorFunc(ctx modules.GeneratorContext) modules.NewGeneratorFunc { return func() (modules.Generator, error) { - return NewWorkloadGenerator(project, stack, appName, workload, moduleConfigs) + return NewWorkloadGenerator(ctx) } } @@ -75,12 +69,12 @@ func (g *workloadGenerator) Generate(spec *apiv1.Intent) error { switch g.workload.Header.Type { case workload.TypeService: gfs = append(gfs, - NewWorkloadServiceGeneratorFunc(g.project, g.stack, g.appName, g.workload.Service, g.moduleConfigs[workload.ModuleService]), - secret.NewSecretGeneratorFunc(g.project, g.workload.Service.Secrets)) + NewWorkloadServiceGeneratorFunc(g.context), + secret.NewSecretGeneratorFunc(g.context)) case workload.TypeJob: gfs = append(gfs, - NewJobGeneratorFunc(g.project, g.stack, g.appName, g.workload.Job, g.moduleConfigs[workload.ModuleJob]), - secret.NewSecretGeneratorFunc(g.project, g.workload.Job.Secrets)) + NewJobGeneratorFunc(g.context), + secret.NewSecretGeneratorFunc(g.context)) } if err := modules.CallGenerators(spec, gfs...); err != nil { diff --git a/pkg/modules/generators/workload/workload_generator_test.go b/pkg/modules/generators/workload/workload_generator_test.go index 3d61e852d..2405c2b16 100644 --- a/pkg/modules/generators/workload/workload_generator_test.go +++ b/pkg/modules/generators/workload/workload_generator_test.go @@ -9,11 +9,32 @@ import ( apiv1 "kusionstack.io/kusion/pkg/apis/core/v1" "kusionstack.io/kusion/pkg/modules" + "kusionstack.io/kusion/pkg/modules/inputs" "kusionstack.io/kusion/pkg/modules/inputs/workload" "kusionstack.io/kusion/pkg/modules/inputs/workload/container" "kusionstack.io/kusion/pkg/modules/inputs/workload/network" ) +func newGeneratorContext( + project *apiv1.Project, + stack *apiv1.Stack, + appName string, + workload *workload.Workload, + moduleInputs map[string]apiv1.GenericConfig, +) modules.GeneratorContext { + application := &inputs.AppConfiguration{ + Name: appName, + Workload: workload, + } + return modules.GeneratorContext{ + Project: project, + Stack: stack, + Application: application, + Namespace: project.Name, + ModuleInputs: moduleInputs, + } +} + func TestNewWorkloadGenerator(t *testing.T) { t.Run("NewWorkloadGenerator should return a valid generator", func(t *testing.T) { expectedProject := &apiv1.Project{ @@ -31,7 +52,8 @@ func TestNewWorkloadGenerator(t *testing.T) { }, } - actualGenerator, err := NewWorkloadGenerator(expectedProject, expectedStack, expectedAppName, expectedWorkload, expectedModuleConfigs) + ctx := newGeneratorContext(expectedProject, expectedStack, expectedAppName, expectedWorkload, expectedModuleConfigs) + actualGenerator, err := NewWorkloadGenerator(ctx) assert.NoError(t, err, "Error should be nil") assert.NotNil(t, actualGenerator, "Generator should not be nil") @@ -60,7 +82,8 @@ func TestNewWorkloadGeneratorFunc(t *testing.T) { }, } - generatorFunc := NewWorkloadGeneratorFunc(expectedProject, expectedStack, expectedAppName, expectedWorkload, expectedModuleConfigs) + ctx := newGeneratorContext(expectedProject, expectedStack, expectedAppName, expectedWorkload, expectedModuleConfigs) + generatorFunc := NewWorkloadGeneratorFunc(ctx) actualGenerator, err := generatorFunc() assert.NoError(t, err, "Error should be nil") @@ -133,7 +156,8 @@ func TestWorkloadGenerator_Generate(t *testing.T) { }, } - actualGenerator, _ := NewWorkloadGenerator(expectedProject, expectedStack, expectedAppName, tc.expectedWorkload, expectedModuleConfigs) + ctx := newGeneratorContext(expectedProject, expectedStack, expectedAppName, tc.expectedWorkload, expectedModuleConfigs) + actualGenerator, _ := NewWorkloadGenerator(ctx) spec := &apiv1.Intent{} err := actualGenerator.Generate(spec) assert.NoError(t, err, "Error should be nil") diff --git a/pkg/modules/inputs/appconfiguration.go b/pkg/modules/inputs/appconfiguration.go index 69793f0f7..c05536c02 100644 --- a/pkg/modules/inputs/appconfiguration.go +++ b/pkg/modules/inputs/appconfiguration.go @@ -29,6 +29,8 @@ import ( // } // } type AppConfiguration struct { + // Name of the target Application. + Name string `json:"name,omitempty" yaml:"name,omitempty"` // Workload defines how to run your application code. Workload *workload.Workload `json:"workload" yaml:"workload"` // OpsRule specifies collection of rules that will be checked for Day-2 operation. diff --git a/pkg/modules/interfaces.go b/pkg/modules/interfaces.go new file mode 100644 index 000000000..2da3739cf --- /dev/null +++ b/pkg/modules/interfaces.go @@ -0,0 +1,47 @@ +package modules + +import ( + appsv1 "k8s.io/api/apps/v1" + + "kusionstack.io/kusion/pkg/apis/core/v1" + "kusionstack.io/kusion/pkg/modules/inputs" +) + +// GVKDeployment is the GroupVersionKind of Deployment +var GVKDeployment = appsv1.SchemeGroupVersion.WithKind("Deployment").String() + +// Generator is an interface for things that can generate Intent from input +// configurations. +type Generator interface { + // Generate performs the intent generate operation. + Generate(intent *v1.Intent) error +} + +// Patcher is the interface that wraps the Patch method. +type Patcher interface { + Patch(resources map[string][]*v1.Resource) error +} + +// NewGeneratorFunc is a function that returns a Generator. +type NewGeneratorFunc func() (Generator, error) + +// NewPatcherFunc is a function that returns a Patcher. +type NewPatcherFunc func() (Patcher, error) + +// GeneratorContext defines the context object used for generator. +type GeneratorContext struct { + // Project provides basic project information for a given generator. + Project *v1.Project + + // Stack provides basic stack information for a given generator. + Stack *v1.Stack + + // Application provides basic application information for a given generator. + Application *inputs.AppConfiguration + + // Namespace specifies the target Kubernetes namespace. + Namespace string + + // ModuleInputs is the collection of module inputs for the target project. + ModuleInputs map[string]v1.GenericConfig +} diff --git a/pkg/modules/types.go b/pkg/modules/types.go deleted file mode 100644 index c955fc38f..000000000 --- a/pkg/modules/types.go +++ /dev/null @@ -1,26 +0,0 @@ -package modules - -import ( - appsv1 "k8s.io/api/apps/v1" - - "kusionstack.io/kusion/pkg/apis/core/v1" -) - -// GVKDeployment is the GroupVersionKind of Deployment -var GVKDeployment = appsv1.SchemeGroupVersion.WithKind("Deployment").String() - -// Generator is the interface that wraps the Generate method. -type Generator interface { - Generate(intent *v1.Intent) error -} - -// Patcher is the interface that wraps the Patch method. -type Patcher interface { - Patch(resources map[string][]*v1.Resource) error -} - -// NewGeneratorFunc is a function that returns a Generator. -type NewGeneratorFunc func() (Generator, error) - -// NewPatcherFunc is a function that returns a Patcher. -type NewPatcherFunc func() (Patcher, error)