mirror of
https://github.com/kubevela/kubevela.git
synced 2026-05-20 08:13:23 +00:00
Checkpoint - context working
This commit is contained in:
@@ -82,7 +82,7 @@ func (h *AppHandler) GenerateApplicationSteps(ctx monitorContext.Context,
|
||||
oam.LabelAppName: app.Name,
|
||||
oam.LabelAppNamespace: app.Namespace,
|
||||
}
|
||||
pCtx := velaprocess.NewContext(generateContextDataFromApp(app, appRev.Name))
|
||||
pCtx := velaprocess.NewContext(generateContextDataFromApp(ctx.GetContext(), app, appRev.Name))
|
||||
ctxWithRuntimeParams := oamprovidertypes.WithRuntimeParams(ctx.GetContext(), oamprovidertypes.RuntimeParams{
|
||||
ComponentApply: h.applyComponentFunc(appParser, af),
|
||||
ComponentRender: h.renderComponentFunc(appParser, af),
|
||||
@@ -539,12 +539,14 @@ func getComponentResources(ctx context.Context, manifest *types.ComponentManifes
|
||||
}
|
||||
|
||||
// generateContextDataFromApp builds the process context for workflow (non-component) execution.
|
||||
func generateContextDataFromApp(app *v1beta1.Application, appRev string) velaprocess.ContextData {
|
||||
// The goCtx parameter should contain any policy additionalContext stored by ApplyApplicationScopeTransforms.
|
||||
func generateContextDataFromApp(goCtx context.Context, app *v1beta1.Application, appRev string) velaprocess.ContextData {
|
||||
data := velaprocess.ContextData{
|
||||
Namespace: app.Namespace,
|
||||
AppName: app.Name,
|
||||
CompName: app.Name,
|
||||
AppRevisionName: appRev,
|
||||
Ctx: goCtx, // Pass Go context so process.NewContext can extract policy additionalContext
|
||||
}
|
||||
if app.Annotations != nil {
|
||||
data.WorkflowName = app.Annotations[oam.AnnotationWorkflowName]
|
||||
|
||||
@@ -296,7 +296,7 @@ var _ = Describe("Test Application workflow generator", func() {
|
||||
},
|
||||
Spec: oamcore.ApplicationSpec{Components: []common.ApplicationComponent{}},
|
||||
}
|
||||
ctxData := generateContextDataFromApp(app, "apprev-with-meta")
|
||||
ctxData := generateContextDataFromApp(context.Background(), app, "apprev-with-meta")
|
||||
Expect(ctxData.AppLabels).To(Equal(app.Labels))
|
||||
Expect(ctxData.AppAnnotations).To(Equal(app.Annotations))
|
||||
})
|
||||
@@ -307,7 +307,7 @@ var _ = Describe("Test Application workflow generator", func() {
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "app-without-meta", Namespace: namespaceName},
|
||||
Spec: oamcore.ApplicationSpec{Components: []common.ApplicationComponent{}},
|
||||
}
|
||||
ctxData := generateContextDataFromApp(app, "apprev-without-meta")
|
||||
ctxData := generateContextDataFromApp(context.Background(), app, "apprev-without-meta")
|
||||
Expect(ctxData.AppLabels).To(BeNil())
|
||||
Expect(ctxData.AppAnnotations).To(BeNil())
|
||||
})
|
||||
|
||||
@@ -42,8 +42,13 @@ import (
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/pkg/appfile"
|
||||
"github.com/oam-dev/kubevela/pkg/config"
|
||||
velaprocess "github.com/oam-dev/kubevela/pkg/cue/process"
|
||||
"github.com/oam-dev/kubevela/pkg/features"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/apply"
|
||||
oamprovidertypes "github.com/oam-dev/kubevela/pkg/workflow/providers/types"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -166,6 +171,15 @@ func (h *AppHandler) ApplyApplicationScopeTransforms(ctx monitorContext.Context,
|
||||
|
||||
// Apply the rendered global policy results (either from cache or freshly rendered)
|
||||
for _, result := range globalRenderedResults {
|
||||
// Check feature gate for Application-scoped policies
|
||||
if !utilfeature.DefaultMutableFeatureGate.Enabled(features.EnableApplicationScopedPolicies) {
|
||||
ctx.Info("Skipping Application-scoped global policy (feature gate disabled)",
|
||||
"policy", result.PolicyName,
|
||||
"namespace", result.PolicyNamespace,
|
||||
"featureGate", "EnableApplicationScopedPolicies")
|
||||
continue
|
||||
}
|
||||
|
||||
ctx.Info("Applying global policy result", "policy", result.PolicyName, "enabled", result.Enabled, "fromCache", cacheHit, "sequence", sequence)
|
||||
|
||||
// Get priority from the result (stored during render)
|
||||
@@ -213,6 +227,15 @@ func (h *AppHandler) ApplyApplicationScopeTransforms(ctx monitorContext.Context,
|
||||
continue
|
||||
}
|
||||
|
||||
// Check feature gate for Application-scoped policies
|
||||
if !utilfeature.DefaultMutableFeatureGate.Enabled(features.EnableApplicationScopedPolicies) {
|
||||
ctx.Info("Skipping Application-scoped policy (feature gate disabled)",
|
||||
"policy", policy.Type,
|
||||
"name", policy.Name,
|
||||
"featureGate", "EnableApplicationScopedPolicies")
|
||||
continue
|
||||
}
|
||||
|
||||
ctx.Info("Applying explicit Application-scoped policy", "policy", policy.Type, "name", policy.Name)
|
||||
|
||||
// Render and apply (not cached - explicit policies can have unique parameters)
|
||||
@@ -622,50 +645,74 @@ func (h *AppHandler) applyRenderedPolicyResult(ctx monitorContext.Context, app *
|
||||
}
|
||||
|
||||
// renderPolicyCUETemplate renders the policy CUE template with parameter and context.application
|
||||
// Follows the same pattern as workloadDef.Complete() and traitDef.Complete() to properly handle import statements
|
||||
// Now includes CueX support by creating a proper process.Context with runtime parameters.
|
||||
// This enables CueX actions like kube.#Read while preserving all existing functionality:
|
||||
// - context.application (Full Application CR)
|
||||
// - context.prior (Previous policy result for incremental policies)
|
||||
// - parameter (Policy parameters from Application spec)
|
||||
func (h *AppHandler) renderPolicyCUETemplate(ctx monitorContext.Context, app *v1beta1.Application, params map[string]interface{}, policyDef *v1beta1.PolicyDefinition, priorResult map[string]interface{}) (cue.Value, error) {
|
||||
// Build CUE source following the pattern from pkg/cue/definition/template.go
|
||||
// Order matters: template (with imports) must come first, then type annotations, then values
|
||||
var cueSources []string
|
||||
// Create runtime context with KubeClient so kube.#Read and other CueX actions work
|
||||
runtimeCtx := oamprovidertypes.WithRuntimeParams(ctx.GetContext(), oamprovidertypes.RuntimeParams{
|
||||
KubeClient: h.Client,
|
||||
ConfigFactory: config.NewConfigFactoryWithDispatcher(h.Client, func(goCtx context.Context, resources []*unstructured.Unstructured, applyOptions []apply.ApplyOption) error {
|
||||
// Policies don't dispatch resources directly, but provide this for CueX consistency
|
||||
return nil
|
||||
}),
|
||||
})
|
||||
|
||||
// 1. Add the policy template FIRST (preserves any import statements at the top)
|
||||
cueSources = append(cueSources, policyDef.Spec.Schematic.CUE.Template)
|
||||
// Create a process.Context with proper runtime parameters embedded for CueX execution
|
||||
pCtx := velaprocess.NewContext(velaprocess.ContextData{
|
||||
Namespace: app.Namespace,
|
||||
AppName: app.Name,
|
||||
CompName: app.Name, // Policy context doesn't have specific component
|
||||
Ctx: runtimeCtx, // Use runtime context with CueX providers
|
||||
})
|
||||
|
||||
// 2. Add type annotations (following renderTemplate() pattern from template.go:489)
|
||||
cueSources = append(cueSources, "parameter: _")
|
||||
cueSources = append(cueSources, "context: _")
|
||||
|
||||
// 3. Add parameter values
|
||||
// Build parameter file (as JSON, not type annotation)
|
||||
var paramFile string
|
||||
if params != nil {
|
||||
paramJSON, err := json.Marshal(params)
|
||||
if err != nil {
|
||||
return cue.Value{}, errors.Wrap(err, "failed to marshal parameters")
|
||||
}
|
||||
cueSources = append(cueSources, fmt.Sprintf("parameter: %s", string(paramJSON)))
|
||||
paramFile = fmt.Sprintf("parameter: %s", string(paramJSON))
|
||||
} else {
|
||||
cueSources = append(cueSources, "parameter: {}")
|
||||
paramFile = "parameter: {}"
|
||||
}
|
||||
|
||||
// 4. Add context.application (convert Application to JSON)
|
||||
// Build context object - PRESERVES ALL EXISTING FUNCTIONALITY
|
||||
// context.application: Full Application CR (existing feature)
|
||||
// context.prior: Previous policy result for incremental policies (existing feature)
|
||||
contextParts := []string{}
|
||||
|
||||
// Add application - SAME AS BEFORE
|
||||
appJSON, err := json.Marshal(app)
|
||||
if err != nil {
|
||||
return cue.Value{}, errors.Wrap(err, "failed to marshal Application")
|
||||
}
|
||||
cueSources = append(cueSources, fmt.Sprintf("context: application: %s", string(appJSON)))
|
||||
contextParts = append(contextParts, fmt.Sprintf("application: %s", string(appJSON)))
|
||||
|
||||
// 5. Add context.prior if available (previous cached policy result)
|
||||
// Add prior if available - SAME AS BEFORE
|
||||
if priorResult != nil {
|
||||
priorJSON, err := json.Marshal(priorResult)
|
||||
if err != nil {
|
||||
return cue.Value{}, errors.Wrap(err, "failed to marshal prior result")
|
||||
}
|
||||
cueSources = append(cueSources, fmt.Sprintf("context: prior: %s", string(priorJSON)))
|
||||
contextParts = append(contextParts, fmt.Sprintf("prior: %s", string(priorJSON)))
|
||||
}
|
||||
|
||||
// Compile the CUE using the default CueX compiler
|
||||
cueSource := strings.Join(cueSources, "\n")
|
||||
val, err := cuex.DefaultCompiler.Get().CompileString(ctx.GetContext(), cueSource)
|
||||
contextFile := fmt.Sprintf("context: {\n%s\n}", strings.Join(contextParts, "\n"))
|
||||
|
||||
// Build CUE source - NO baseContext needed!
|
||||
// cuex.DefaultCompiler already has all the imports (kube, http, etc.)
|
||||
cueSource := strings.Join([]string{
|
||||
policyDef.Spec.Schematic.CUE.Template,
|
||||
paramFile,
|
||||
contextFile,
|
||||
}, "\n")
|
||||
|
||||
// Compile with CueX execution enabled (cuex.DefaultCompiler automatically resolves actions)
|
||||
val, err := cuex.DefaultCompiler.Get().CompileString(pCtx.GetCtx(), cueSource)
|
||||
if err != nil {
|
||||
return cue.Value{}, errors.Wrap(err, "failed to compile CUE template")
|
||||
}
|
||||
@@ -907,6 +954,10 @@ func deepMerge(target, source map[string]interface{}) map[string]interface{} {
|
||||
return result
|
||||
}
|
||||
|
||||
// policyAdditionalContextKeyString is the string key for storing additionalContext in Go context
|
||||
// We use a string key to avoid type mismatches when accessing from different packages (e.g., pkg/cue/process)
|
||||
const policyAdditionalContextKeyString = "kubevela.oam.dev/policy-additional-context"
|
||||
|
||||
// storeAdditionalContextInCtx stores additional policy context in the Go context
|
||||
// This context will be available in workflow steps as context.custom
|
||||
func storeAdditionalContextInCtx(ctx monitorContext.Context, additionalContext map[string]interface{}) monitorContext.Context {
|
||||
@@ -919,16 +970,16 @@ func storeAdditionalContextInCtx(ctx monitorContext.Context, additionalContext m
|
||||
// Merge new context into existing
|
||||
merged := deepMerge(existing, additionalContext)
|
||||
|
||||
// Store back in context using the PolicyAdditionalContextKey from application_controller.go
|
||||
// Store back in context using a string key (avoids type mismatches across packages)
|
||||
// We need to extract the underlying context.Context, add our value, and wrap it back
|
||||
baseCtx := context.WithValue(ctx.GetContext(), PolicyAdditionalContextKey, merged)
|
||||
baseCtx := context.WithValue(ctx.GetContext(), policyAdditionalContextKeyString, merged)
|
||||
ctx.SetContext(baseCtx)
|
||||
return ctx
|
||||
}
|
||||
|
||||
// getAdditionalContextFromCtx retrieves additional policy context from the Go context
|
||||
func getAdditionalContextFromCtx(ctx monitorContext.Context) map[string]interface{} {
|
||||
if val := ctx.GetContext().Value(PolicyAdditionalContextKey); val != nil {
|
||||
if val := ctx.GetContext().Value(policyAdditionalContextKeyString); val != nil {
|
||||
if contextMap, ok := val.(map[string]interface{}); ok {
|
||||
return contextMap
|
||||
}
|
||||
|
||||
@@ -26,11 +26,13 @@ import (
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
monitorContext "github.com/kubevela/pkg/monitor/context"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
)
|
||||
|
||||
@@ -472,6 +474,258 @@ output: {
|
||||
Expect(app.Labels).Should(HaveLen(1))
|
||||
Expect(app.Labels["original"]).Should(Equal("value"))
|
||||
})
|
||||
|
||||
It("Test Application-scoped policy with CueX kube.#Get action", func() {
|
||||
// Create a test ConfigMap that the policy will read
|
||||
testConfigMap := &corev1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-data-cm",
|
||||
Namespace: namespace,
|
||||
},
|
||||
Data: map[string]string{
|
||||
"key1": "value1",
|
||||
"key2": "value2",
|
||||
},
|
||||
}
|
||||
Expect(k8sClient.Create(ctx, testConfigMap)).Should(Succeed())
|
||||
|
||||
// Create PolicyDefinition with CueX kube.#Get action
|
||||
policyDef := &v1beta1.PolicyDefinition{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "cuex-read-policy",
|
||||
Namespace: namespace,
|
||||
},
|
||||
Spec: v1beta1.PolicyDefinitionSpec{
|
||||
Scope: v1beta1.ApplicationScope,
|
||||
Schematic: &common.Schematic{
|
||||
CUE: &common.CUE{
|
||||
Template: `
|
||||
import "vela/kube"
|
||||
|
||||
parameter: {}
|
||||
enabled: true
|
||||
|
||||
// Use kube.#Get to read a ConfigMap from the cluster
|
||||
output: kube.#Get & {
|
||||
$params: {
|
||||
cluster: ""
|
||||
resource: {
|
||||
apiVersion: "v1"
|
||||
kind: "ConfigMap"
|
||||
metadata: {
|
||||
name: "test-data-cm"
|
||||
namespace: "` + namespace + `"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The ConfigMap data will be available in additionalContext
|
||||
additionalContext: {
|
||||
configMapData: output.$returns.data
|
||||
}
|
||||
|
||||
// Add a label with data from the ConfigMap
|
||||
transforms: {
|
||||
labels: {
|
||||
type: "merge"
|
||||
value: {
|
||||
"from-configmap": output.$returns.data.key1
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
Expect(k8sClient.Create(ctx, policyDef)).Should(Succeed())
|
||||
waitForPolicyDef(ctx, "cuex-read-policy", namespace)
|
||||
|
||||
// Create Application that uses the policy
|
||||
app := &v1beta1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-app-cuex",
|
||||
Namespace: namespace,
|
||||
},
|
||||
Spec: v1beta1.ApplicationSpec{
|
||||
Components: []common.ApplicationComponent{
|
||||
{
|
||||
Name: "my-component",
|
||||
Type: "webservice",
|
||||
},
|
||||
},
|
||||
Policies: []v1beta1.AppPolicy{
|
||||
{
|
||||
Name: "cuex-reader",
|
||||
Type: "cuex-read-policy",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
handler := &AppHandler{
|
||||
Client: k8sClient,
|
||||
}
|
||||
|
||||
monCtx := monitorContext.NewTraceContext(ctx, "test")
|
||||
_, err := handler.ApplyApplicationScopeTransforms(monCtx, app)
|
||||
Expect(err).Should(BeNil())
|
||||
|
||||
// Verify the CueX kube.#Get action executed successfully by checking:
|
||||
// 1. No error occurred (CueX action executed)
|
||||
// 2. The label transform was applied (using data from the ConfigMap)
|
||||
Expect(app.Labels).ShouldNot(BeNil())
|
||||
Expect(app.Labels["from-configmap"]).Should(Equal("value1"))
|
||||
})
|
||||
|
||||
It("Test policy additionalContext is available to components as context.custom", func() {
|
||||
// Create a ConfigMap that the policy will read
|
||||
testCM := &corev1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "policy-data-cm",
|
||||
Namespace: namespace,
|
||||
},
|
||||
Data: map[string]string{
|
||||
"apiEndpoint": "https://api.example.com",
|
||||
"region": "us-west-2",
|
||||
},
|
||||
}
|
||||
Expect(k8sClient.Create(ctx, testCM)).Should(Succeed())
|
||||
|
||||
// Create PolicyDefinition that reads ConfigMap and exposes it via additionalContext
|
||||
policyDef := &v1beta1.PolicyDefinition{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "fetch-config-policy",
|
||||
Namespace: namespace,
|
||||
},
|
||||
Spec: v1beta1.PolicyDefinitionSpec{
|
||||
Scope: v1beta1.ApplicationScope,
|
||||
Schematic: &common.Schematic{
|
||||
CUE: &common.CUE{
|
||||
Template: `
|
||||
import "vela/kube"
|
||||
|
||||
parameter: {}
|
||||
enabled: true
|
||||
|
||||
output: kube.#Get & {
|
||||
$params: {
|
||||
cluster: ""
|
||||
resource: {
|
||||
apiVersion: "v1"
|
||||
kind: "ConfigMap"
|
||||
metadata: {
|
||||
name: "policy-data-cm"
|
||||
namespace: "` + namespace + `"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Expose ConfigMap data via additionalContext so components can access it
|
||||
additionalContext: {
|
||||
config: {
|
||||
endpoint: output.$returns.data.apiEndpoint
|
||||
region: output.$returns.data.region
|
||||
}
|
||||
}
|
||||
`,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
Expect(k8sClient.Create(ctx, policyDef)).Should(Succeed())
|
||||
waitForPolicyDef(ctx, "fetch-config-policy", namespace)
|
||||
|
||||
// Create ComponentDefinition that uses context.custom
|
||||
compDef := &v1beta1.ComponentDefinition{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "custom-context-comp",
|
||||
Namespace: namespace,
|
||||
},
|
||||
Spec: v1beta1.ComponentDefinitionSpec{
|
||||
Workload: common.WorkloadTypeDescriptor{
|
||||
Definition: common.WorkloadGVK{
|
||||
APIVersion: "v1",
|
||||
Kind: "ConfigMap",
|
||||
},
|
||||
},
|
||||
Schematic: &common.Schematic{
|
||||
CUE: &common.CUE{
|
||||
Template: `
|
||||
import "encoding/json"
|
||||
|
||||
output: {
|
||||
apiVersion: "v1"
|
||||
kind: "ConfigMap"
|
||||
metadata: {
|
||||
name: context.name
|
||||
namespace: context.namespace
|
||||
}
|
||||
data: {
|
||||
// Access policy additionalContext via context.custom
|
||||
"from-policy": json.Marshal(context.custom.config)
|
||||
}
|
||||
}
|
||||
`,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
Expect(k8sClient.Create(ctx, compDef)).Should(Succeed())
|
||||
|
||||
// Create Application with the policy and component
|
||||
app := &v1beta1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "app-with-custom-context",
|
||||
Namespace: namespace,
|
||||
},
|
||||
Spec: v1beta1.ApplicationSpec{
|
||||
Policies: []v1beta1.AppPolicy{
|
||||
{
|
||||
Name: "fetch-config",
|
||||
Type: "fetch-config-policy",
|
||||
},
|
||||
},
|
||||
Components: []common.ApplicationComponent{
|
||||
{
|
||||
Name: "my-component",
|
||||
Type: "custom-context-comp",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Apply Application-scoped policy transforms
|
||||
handler := &AppHandler{
|
||||
Client: k8sClient,
|
||||
}
|
||||
|
||||
monCtx := monitorContext.NewTraceContext(ctx, "test-custom-context")
|
||||
monCtx, err := handler.ApplyApplicationScopeTransforms(monCtx, app)
|
||||
Expect(err).Should(BeNil())
|
||||
|
||||
// Verify additionalContext is stored in the Go context
|
||||
// This data will be available to components via context.custom when rendering
|
||||
const policyAdditionalContextKeyString = "kubevela.oam.dev/policy-additional-context"
|
||||
|
||||
additionalCtx := monCtx.GetContext().Value(policyAdditionalContextKeyString)
|
||||
Expect(additionalCtx).ShouldNot(BeNil())
|
||||
|
||||
// Cast and verify the structure
|
||||
ctxMap, ok := additionalCtx.(map[string]interface{})
|
||||
Expect(ok).Should(BeTrue())
|
||||
Expect(ctxMap).Should(HaveKey("config"))
|
||||
|
||||
config, ok := ctxMap["config"].(map[string]interface{})
|
||||
Expect(ok).Should(BeTrue())
|
||||
Expect(config["endpoint"]).Should(Equal("https://api.example.com"))
|
||||
Expect(config["region"]).Should(Equal("us-west-2"))
|
||||
|
||||
// This additionalContext will be extracted by process.NewContext(), wrapped under
|
||||
// "custom" key, and made available to component/trait templates as context.custom.config
|
||||
})
|
||||
})
|
||||
|
||||
var _ = Describe("Test Global Policy Cache", func() {
|
||||
@@ -2939,3 +3193,333 @@ transforms: {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
var _ = Describe("Test Application-scoped policy feature gates", func() {
|
||||
namespace := "policy-featuregate-test"
|
||||
velaSystem := oam.SystemDefinitionNamespace
|
||||
var ctx context.Context
|
||||
|
||||
BeforeEach(func() {
|
||||
ctx = util.SetNamespaceInCtx(context.Background(), namespace)
|
||||
ns := corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}}
|
||||
Expect(k8sClient.Create(ctx, &ns)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
// Clean up policies in test namespace
|
||||
policyList := &v1beta1.PolicyDefinitionList{}
|
||||
_ = k8sClient.List(ctx, policyList, client.InNamespace(namespace))
|
||||
for _, policy := range policyList.Items {
|
||||
_ = k8sClient.Delete(ctx, &policy)
|
||||
}
|
||||
|
||||
// Clean up policies in vela-system
|
||||
velaSystemPolicyList := &v1beta1.PolicyDefinitionList{}
|
||||
_ = k8sClient.List(ctx, velaSystemPolicyList, client.InNamespace(velaSystem))
|
||||
for _, policy := range velaSystemPolicyList.Items {
|
||||
_ = k8sClient.Delete(ctx, &policy)
|
||||
}
|
||||
|
||||
// Restore both gates to enabled
|
||||
Expect(utilfeature.DefaultMutableFeatureGate.Set("EnableGlobalPolicies=true")).ToNot(HaveOccurred())
|
||||
Expect(utilfeature.DefaultMutableFeatureGate.Set("EnableApplicationScopedPolicies=true")).ToNot(HaveOccurred())
|
||||
})
|
||||
|
||||
It("should skip explicit Application-scoped policies when EnableApplicationScopedPolicies=false", func() {
|
||||
// Disable Application-scoped policy execution (but keep global discovery enabled)
|
||||
Expect(utilfeature.DefaultMutableFeatureGate.Set("EnableApplicationScopedPolicies=false")).ToNot(HaveOccurred())
|
||||
|
||||
// Create Application-scoped PolicyDefinition
|
||||
policyDef := &v1beta1.PolicyDefinition{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "add-label-explicit",
|
||||
Namespace: namespace,
|
||||
},
|
||||
Spec: v1beta1.PolicyDefinitionSpec{
|
||||
Scope: v1beta1.ApplicationScope,
|
||||
Schematic: &common.Schematic{
|
||||
CUE: &common.CUE{
|
||||
Template: `
|
||||
parameter: {}
|
||||
enabled: true
|
||||
transforms: {
|
||||
labels: {
|
||||
type: "merge"
|
||||
value: {
|
||||
"test": "gate-disabled"
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
Expect(k8sClient.Create(ctx, policyDef)).Should(Succeed())
|
||||
waitForPolicyDef(ctx, "add-label-explicit", namespace)
|
||||
|
||||
// Create Application with explicit policy
|
||||
app := &v1beta1.Application{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: v1beta1.SchemeGroupVersion.String(),
|
||||
Kind: v1beta1.ApplicationKind,
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-explicit-gate",
|
||||
Namespace: namespace,
|
||||
},
|
||||
Spec: v1beta1.ApplicationSpec{
|
||||
Components: []common.ApplicationComponent{
|
||||
{Name: "my-comp", Type: "webservice"},
|
||||
},
|
||||
Policies: []v1beta1.AppPolicy{
|
||||
{Name: "test-policy", Type: "add-label-explicit"},
|
||||
},
|
||||
},
|
||||
}
|
||||
Expect(k8sClient.Create(ctx, app)).Should(Succeed())
|
||||
|
||||
handler := &AppHandler{Client: k8sClient, app: app}
|
||||
monCtx := monitorContext.NewTraceContext(ctx, "test")
|
||||
_, err := handler.ApplyApplicationScopeTransforms(monCtx, app)
|
||||
Expect(err).Should(BeNil())
|
||||
|
||||
// Verify policy was NOT applied (label not added)
|
||||
Expect(app.Labels).ShouldNot(HaveKey("test"))
|
||||
// Verify no policies in status
|
||||
Expect(app.Status.AppliedApplicationPolicies).Should(BeEmpty())
|
||||
})
|
||||
|
||||
It("should discover but not apply global policies when EnableGlobalPolicies=true but EnableApplicationScopedPolicies=false", func() {
|
||||
// Disable Application-scoped policy execution but keep discovery enabled
|
||||
Expect(utilfeature.DefaultMutableFeatureGate.Set("EnableApplicationScopedPolicies=false")).ToNot(HaveOccurred())
|
||||
|
||||
// Create global Application-scoped PolicyDefinition in vela-system
|
||||
policyDef := &v1beta1.PolicyDefinition{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "global-add-label",
|
||||
Namespace: velaSystem,
|
||||
},
|
||||
Spec: v1beta1.PolicyDefinitionSpec{
|
||||
Scope: v1beta1.ApplicationScope,
|
||||
Global: true,
|
||||
Schematic: &common.Schematic{
|
||||
CUE: &common.CUE{
|
||||
Template: `
|
||||
parameter: {}
|
||||
enabled: true
|
||||
transforms: {
|
||||
labels: {
|
||||
type: "merge"
|
||||
value: {
|
||||
"global": "policy"
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
velaCtx := util.SetNamespaceInCtx(context.Background(), velaSystem)
|
||||
Expect(k8sClient.Create(velaCtx, policyDef)).Should(Succeed())
|
||||
waitForPolicyDef(velaCtx, "global-add-label", velaSystem)
|
||||
|
||||
// Clear in-memory cache to ensure fresh discovery
|
||||
globalPolicyCache.InvalidateAll()
|
||||
|
||||
// Create Application
|
||||
app := &v1beta1.Application{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: v1beta1.SchemeGroupVersion.String(),
|
||||
Kind: v1beta1.ApplicationKind,
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-global-gate",
|
||||
Namespace: namespace,
|
||||
},
|
||||
Spec: v1beta1.ApplicationSpec{
|
||||
Components: []common.ApplicationComponent{
|
||||
{Name: "my-comp", Type: "webservice"},
|
||||
},
|
||||
},
|
||||
}
|
||||
Expect(k8sClient.Create(ctx, app)).Should(Succeed())
|
||||
|
||||
handler := &AppHandler{Client: k8sClient, app: app}
|
||||
monCtx := monitorContext.NewTraceContext(ctx, "test")
|
||||
_, err := handler.ApplyApplicationScopeTransforms(monCtx, app)
|
||||
Expect(err).Should(BeNil())
|
||||
|
||||
// Verify policy was NOT applied (label not added)
|
||||
Expect(app.Labels).ShouldNot(HaveKey("global"))
|
||||
// Verify no policies in status
|
||||
Expect(app.Status.AppliedApplicationPolicies).Should(BeEmpty())
|
||||
})
|
||||
|
||||
It("should not discover global policies when EnableGlobalPolicies=false (even if EnableApplicationScopedPolicies=true)", func() {
|
||||
// Disable global policy discovery but keep execution enabled
|
||||
Expect(utilfeature.DefaultMutableFeatureGate.Set("EnableGlobalPolicies=false")).ToNot(HaveOccurred())
|
||||
|
||||
// Create global Application-scoped PolicyDefinition
|
||||
policyDef := &v1beta1.PolicyDefinition{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "global-no-discovery",
|
||||
Namespace: velaSystem,
|
||||
},
|
||||
Spec: v1beta1.PolicyDefinitionSpec{
|
||||
Scope: v1beta1.ApplicationScope,
|
||||
Global: true,
|
||||
Schematic: &common.Schematic{
|
||||
CUE: &common.CUE{
|
||||
Template: `
|
||||
parameter: {}
|
||||
enabled: true
|
||||
transforms: {
|
||||
labels: {
|
||||
type: "merge"
|
||||
value: {
|
||||
"discovered": "false"
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
velaCtx := util.SetNamespaceInCtx(context.Background(), velaSystem)
|
||||
Expect(k8sClient.Create(velaCtx, policyDef)).Should(Succeed())
|
||||
waitForPolicyDef(velaCtx, "global-no-discovery", velaSystem)
|
||||
|
||||
// Clear in-memory cache
|
||||
globalPolicyCache.InvalidateAll()
|
||||
|
||||
// Create Application
|
||||
app := &v1beta1.Application{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: v1beta1.SchemeGroupVersion.String(),
|
||||
Kind: v1beta1.ApplicationKind,
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-no-discovery",
|
||||
Namespace: namespace,
|
||||
},
|
||||
Spec: v1beta1.ApplicationSpec{
|
||||
Components: []common.ApplicationComponent{
|
||||
{Name: "my-comp", Type: "webservice"},
|
||||
},
|
||||
},
|
||||
}
|
||||
Expect(k8sClient.Create(ctx, app)).Should(Succeed())
|
||||
|
||||
handler := &AppHandler{Client: k8sClient, app: app}
|
||||
monCtx := monitorContext.NewTraceContext(ctx, "test")
|
||||
_, err := handler.ApplyApplicationScopeTransforms(monCtx, app)
|
||||
Expect(err).Should(BeNil())
|
||||
|
||||
// Verify policy was NOT applied (not discovered)
|
||||
Expect(app.Labels).ShouldNot(HaveKey("discovered"))
|
||||
Expect(app.Status.AppliedApplicationPolicies).Should(BeEmpty())
|
||||
})
|
||||
|
||||
It("should apply both global and explicit policies when both gates are enabled", func() {
|
||||
// Both gates already enabled in BeforeSuite
|
||||
|
||||
// Create global PolicyDefinition
|
||||
globalPolicy := &v1beta1.PolicyDefinition{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "global-full-func",
|
||||
Namespace: velaSystem,
|
||||
},
|
||||
Spec: v1beta1.PolicyDefinitionSpec{
|
||||
Scope: v1beta1.ApplicationScope,
|
||||
Global: true,
|
||||
Priority: 100,
|
||||
Schematic: &common.Schematic{
|
||||
CUE: &common.CUE{
|
||||
Template: `
|
||||
parameter: {}
|
||||
enabled: true
|
||||
transforms: {
|
||||
labels: {
|
||||
type: "merge"
|
||||
value: {
|
||||
"global": "applied"
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
velaCtx := util.SetNamespaceInCtx(context.Background(), velaSystem)
|
||||
Expect(k8sClient.Create(velaCtx, globalPolicy)).Should(Succeed())
|
||||
waitForPolicyDef(velaCtx, "global-full-func", velaSystem)
|
||||
|
||||
// Create explicit PolicyDefinition
|
||||
explicitPolicy := &v1beta1.PolicyDefinition{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "explicit-full-func",
|
||||
Namespace: namespace,
|
||||
},
|
||||
Spec: v1beta1.PolicyDefinitionSpec{
|
||||
Scope: v1beta1.ApplicationScope,
|
||||
Schematic: &common.Schematic{
|
||||
CUE: &common.CUE{
|
||||
Template: `
|
||||
parameter: {}
|
||||
enabled: true
|
||||
transforms: {
|
||||
labels: {
|
||||
type: "merge"
|
||||
value: {
|
||||
"explicit": "applied"
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
Expect(k8sClient.Create(ctx, explicitPolicy)).Should(Succeed())
|
||||
waitForPolicyDef(ctx, "explicit-full-func", namespace)
|
||||
|
||||
// Clear in-memory cache
|
||||
globalPolicyCache.InvalidateAll()
|
||||
|
||||
// Create Application with explicit policy
|
||||
app := &v1beta1.Application{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: v1beta1.SchemeGroupVersion.String(),
|
||||
Kind: v1beta1.ApplicationKind,
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-full-func",
|
||||
Namespace: namespace,
|
||||
},
|
||||
Spec: v1beta1.ApplicationSpec{
|
||||
Components: []common.ApplicationComponent{
|
||||
{Name: "my-comp", Type: "webservice"},
|
||||
},
|
||||
Policies: []v1beta1.AppPolicy{
|
||||
{Name: "explicit-policy", Type: "explicit-full-func"},
|
||||
},
|
||||
},
|
||||
}
|
||||
Expect(k8sClient.Create(ctx, app)).Should(Succeed())
|
||||
|
||||
handler := &AppHandler{Client: k8sClient, app: app}
|
||||
monCtx := monitorContext.NewTraceContext(ctx, "test")
|
||||
_, err := handler.ApplyApplicationScopeTransforms(monCtx, app)
|
||||
Expect(err).Should(BeNil())
|
||||
|
||||
// Verify BOTH policies were applied
|
||||
Expect(app.Labels).Should(HaveKeyWithValue("global", "applied"))
|
||||
Expect(app.Labels).Should(HaveKeyWithValue("explicit", "applied"))
|
||||
// Verify status shows both policies
|
||||
Expect(app.Status.AppliedApplicationPolicies).Should(HaveLen(2))
|
||||
})
|
||||
})
|
||||
|
||||
@@ -80,9 +80,10 @@ var _ = BeforeSuite(func() {
|
||||
logf.SetLogger(zap.New(zap.UseDevMode(true), zap.WriteTo(GinkgoWriter)))
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
|
||||
// Enable global policies feature gate for tests
|
||||
// Enable both Application-scoped policy feature gates for tests
|
||||
Expect(utilfeature.DefaultMutableFeatureGate.Set("EnableGlobalPolicies=true")).ToNot(HaveOccurred())
|
||||
logf.Log.Info("Enabled EnableGlobalPolicies feature gate for tests")
|
||||
Expect(utilfeature.DefaultMutableFeatureGate.Set("EnableApplicationScopedPolicies=true")).ToNot(HaveOccurred())
|
||||
logf.Log.Info("Enabled Application-scoped policy feature gates for tests")
|
||||
|
||||
By("bootstrapping test environment")
|
||||
var yamlPath string
|
||||
|
||||
Reference in New Issue
Block a user