Skip to content

Commit

Permalink
fix: customize namespace issue to get correct k8s namespace name (#1132)
Browse files Browse the repository at this point in the history
  • Loading branch information
adohe committed May 23, 2024
1 parent 7765bc1 commit 57dcede
Show file tree
Hide file tree
Showing 7 changed files with 109 additions and 131 deletions.
51 changes: 1 addition & 50 deletions pkg/engine/api/builders/appconfig_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ package builders
import (
"fmt"

"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"kcl-lang.io/kpm/pkg/api"

v1 "kusionstack.io/kusion/pkg/apis/api.kusion.io/v1"
Expand All @@ -41,7 +40,7 @@ func (acg *AppsConfigBuilder) Build(kclPackage *api.KclPackage, project *v1.Proj
return fmt.Errorf("kcl package is nil when generating app configuration for %s", appName)
}
dependencies := kclPackage.GetDependenciesInModFile()
gfs = append(gfs, generators.NewAppConfigurationGeneratorFunc(project.Name, stack.Name, appName, &app, acg.Workspace, dependencies))
gfs = append(gfs, generators.NewAppConfigurationGeneratorFunc(project, stack, appName, &app, acg.Workspace, dependencies))
return nil
})
if err != nil {
Expand All @@ -51,53 +50,5 @@ func (acg *AppsConfigBuilder) Build(kclPackage *api.KclPackage, project *v1.Proj
return nil, err
}

// updates generated spec resources based on project and stack extensions.
patchResourcesWithExtensions(project, stack, i)

return i, nil
}

// patchResourcesWithExtensions updates generated spec resources based on project and stack extensions.
func patchResourcesWithExtensions(project *v1.Project, stack *v1.Stack, spec *v1.Spec) {
extensions := mergeExtensions(project, stack)
if len(extensions) == 0 {
return
}

for _, extension := range extensions {
switch extension.Kind {
case v1.KubernetesNamespace:
patchResourcesKubeNamespace(spec, extension.KubeNamespace.Namespace)
default:
// do nothing
}
}
}

func patchResourcesKubeNamespace(spec *v1.Spec, namespace string) {
for _, resource := range spec.Resources {
if resource.Type == v1.Kubernetes {
u := &unstructured.Unstructured{Object: resource.Attributes}
u.SetNamespace(namespace)
}
}
}

func mergeExtensions(project *v1.Project, stack *v1.Stack) []*v1.Extension {
var extensions []*v1.Extension
extensionKindMap := make(map[string]struct{})
if stack.Extensions != nil && len(stack.Extensions) != 0 {
for _, extension := range stack.Extensions {
extensions = append(extensions, extension)
extensionKindMap[string(extension.Kind)] = struct{}{}
}
}
if project.Extensions != nil && len(project.Extensions) != 0 {
for _, extension := range project.Extensions {
if _, exist := extensionKindMap[string(extension.Kind)]; !exist {
extensions = append(extensions, extension)
}
}
}
return extensions
}
21 changes: 3 additions & 18 deletions pkg/engine/api/generate/run/testdata/base/base.k
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import kam.v1.app_configuration as ac
import kam.v1.workload as wl
import kam.v1.workload.container as c
import kam.v1.workload.container.probe as p
import opsrule as t
import service as svc
import service.container as c

# base.k declares reusable configurations for all stacks.
helloworld: ac.AppConfiguration {
workload: wl.Service {
workload: svc.Service {
containers: {
"nginx": c.Container {
image: "nginx:v1"
Expand All @@ -23,22 +21,9 @@ helloworld: ac.AppConfiguration {
}
# Run the command "/bin/sh -c echo hi", as defined above, in the directory "/tmp"
workingDir: "/tmp"
# Configure a HTTP readiness probe
readinessProbe: p.Probe {
probeHandler: p.Http {
url: "http://localhost:80"
}
initialDelaySeconds: 10
}
}
}
# Set the replicas
replicas: 2
}

accessories: {
"kusionstack/opsrule@v0.0.9": t.OpsRule {
maxUnavailable: "30%"
}
}
}
4 changes: 2 additions & 2 deletions pkg/engine/api/generate/run/testdata/prod/kcl.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ name = "testdata"
version = "0.1.0"

[dependencies]
opsrule = { oci = "oci://ghcr.io/kusionstack/opsrule", tag = "0.1.0" }
kam = { git = "https://github.com/KusionStack/kam.git", tag = "0.1.0" }
kam = { git = "https://github.com/KusionStack/kam.git", tag = "0.2.0-beta" }
service = { oci = "oci://ghcr.io/kusionstack/service", tag = "0.1.0-beta" }

[profile]
entries = ["../base/base.k", "main.k"]
Expand Down
4 changes: 0 additions & 4 deletions pkg/engine/api/generate/run/testdata/prod/main.k
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,4 @@ import kam.v1.app_configuration as ac

# main.k declares customized configurations for prod stack.
helloworld: ac.AppConfiguration {
workload.containers.nginx: {
# prod stack has different image
image = "nginx:v2"
}
}
4 changes: 4 additions & 0 deletions pkg/engine/api/generate/run/testdata/project.yaml
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
# The project basic info
name: testdata
extensions:
- kind: "kubernetesNamespace"
kubernetesNamespace:
namespace: "dev"
95 changes: 62 additions & 33 deletions pkg/modules/generators/app_configurations_generator.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
// Copyright 2024 KusionStack Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package generators

import (
Expand All @@ -24,28 +38,28 @@ import (
)

type appConfigurationGenerator struct {
project string
stack string
project *v1.Project
stack *v1.Stack
appName string
app *v1.AppConfiguration
ws *v1.Workspace
dependencies *pkg.Dependencies
}

func NewAppConfigurationGenerator(
project string,
stack string,
project *v1.Project,
stack *v1.Stack,
appName string,
app *v1.AppConfiguration,
ws *v1.Workspace,
dependencies *pkg.Dependencies,
) (modules.Generator, error) {
if len(project) == 0 {
return nil, fmt.Errorf("project name must not be empty")
if project == nil {
return nil, fmt.Errorf("project must not be nil")
}

if len(stack) == 0 {
return nil, fmt.Errorf("stack name must not be empty")
if stack == nil {
return nil, fmt.Errorf("stack must not be nil")
}

if len(appName) == 0 {
Expand All @@ -61,7 +75,7 @@ func NewAppConfigurationGenerator(
}

if err := workspace.ValidateWorkspace(ws); err != nil {
return nil, fmt.Errorf("invalid config of workspace %s, %w", stack, err)
return nil, fmt.Errorf("invalid config of workspace %s, %w", ws.Name, err)
}

return &appConfigurationGenerator{
Expand All @@ -75,8 +89,8 @@ func NewAppConfigurationGenerator(
}

func NewAppConfigurationGeneratorFunc(
project string,
stack string,
project *v1.Project,
stack *v1.Stack,
appName string,
app *v1.AppConfiguration,
ws *v1.Workspace,
Expand All @@ -94,20 +108,18 @@ func (g *appConfigurationGenerator) Generate(spec *v1.Spec) error {
g.app.Name = g.appName

// retrieve the module configs of the specified project
projectModuleConfigs, err := workspace.GetProjectModuleConfigs(g.ws.Modules, g.project)
projectModuleConfigs, err := workspace.GetProjectModuleConfigs(g.ws.Modules, g.project.Name)
if err != nil {
return err
}

// todo: is namespace a module? how to retrieve it? Currently, it is configured in the workspace file.
namespace := g.getNamespaceName(projectModuleConfigs)

// generate built-in resources
namespace := g.getNamespaceName()
gfs := []modules.NewGeneratorFunc{
NewNamespaceGeneratorFunc(namespace),
workload.NewWorkloadGeneratorFunc(&workload.Generator{
Project: g.project,
Stack: g.stack,
Project: g.project.Name,
Stack: g.stack.Name,
App: g.appName,
Namespace: namespace,
Workload: g.app.Workload,
Expand Down Expand Up @@ -489,8 +501,8 @@ func (g *appConfigurationGenerator) initModuleRequest(config moduleConfig) (*pro
}

protoRequest := &proto.GeneratorRequest{
Project: g.project,
Stack: g.stack,
Project: g.project.Name,
Stack: g.stack.Name,
App: g.appName,
Workload: workloadConfig,
DevConfig: devConfig,
Expand All @@ -503,21 +515,38 @@ func (g *appConfigurationGenerator) initModuleRequest(config moduleConfig) (*pro
// 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]v1.GenericConfig) string {
if moduleConfigs == nil {
return g.project
}

namespaceName := g.project
namespaceModuleConfigs, exist := moduleConfigs["namespace"]
if exist {
if name, ok := namespaceModuleConfigs["name"]; ok {
customNamespaceName, isString := name.(string)
if isString && len(customNamespaceName) > 0 {
namespaceName = customNamespaceName
// - KubernetesNamespace extensions (specified in corresponding workspace file)
func (g *appConfigurationGenerator) getNamespaceName() string {
extensions := mergeExtensions(g.project, g.stack)
if len(extensions) != 0 {
for _, extension := range extensions {
switch extension.Kind {
case v1.KubernetesNamespace:
return extension.KubeNamespace.Namespace
default:
// do nothing
}
}
}

return g.project.Name
}

func mergeExtensions(project *v1.Project, stack *v1.Stack) []*v1.Extension {
var extensions []*v1.Extension
extensionKindMap := make(map[string]struct{})
if stack.Extensions != nil && len(stack.Extensions) != 0 {
for _, extension := range stack.Extensions {
extensions = append(extensions, extension)
extensionKindMap[string(extension.Kind)] = struct{}{}
}
}
if project.Extensions != nil && len(project.Extensions) != 0 {
for _, extension := range project.Extensions {
if _, exist := extensionKindMap[string(extension.Kind)]; !exist {
extensions = append(extensions, extension)
}
}
}
return namespaceName
return extensions
}
Loading

0 comments on commit 57dcede

Please sign in to comment.