Checkpoint - context working

This commit is contained in:
Brian Kane
2026-02-10 17:43:06 +00:00
parent bfa143297b
commit 32d166c219
10 changed files with 710 additions and 31 deletions

View File

@@ -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]

View File

@@ -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())
})

View File

@@ -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
}

View File

@@ -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))
})
})

View File

@@ -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