Skip to content

Commit d789bd8

Browse files
l46kokcopybara-github
authored andcommitted
Internal Changes
PiperOrigin-RevId: 910292784
1 parent d08c424 commit d789bd8

17 files changed

Lines changed: 266 additions & 133 deletions

File tree

bundle/src/main/java/dev/cel/bundle/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ java_library(
104104
":required_fields_checker",
105105
"//:auto_value",
106106
"//bundle:cel",
107+
"//checker:proto_type_mask",
107108
"//checker:standard_decl",
108109
"//common:compiler_common",
109110
"//common:container",

bundle/src/main/java/dev/cel/bundle/CelEnvironment.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import dev.cel.checker.CelStandardDeclarations;
3131
import dev.cel.checker.CelStandardDeclarations.StandardFunction;
3232
import dev.cel.checker.CelStandardDeclarations.StandardOverload;
33+
import dev.cel.checker.ProtoTypeMask;
3334
import dev.cel.common.CelContainer;
3435
import dev.cel.common.CelFunctionDecl;
3536
import dev.cel.common.CelOptions;
@@ -134,6 +135,9 @@ public abstract class CelEnvironment {
134135
/** Limits to set in the environment. */
135136
public abstract ImmutableSet<Limit> limits();
136137

138+
/** Context variable to enable in the environment. */
139+
public abstract Optional<ContextVariable> contextVariable();
140+
137141
/** Builder for {@link CelEnvironment}. */
138142
@AutoValue.Builder
139143
public abstract static class Builder {
@@ -199,6 +203,8 @@ public Builder setLimits(Limit... limits) {
199203

200204
public abstract Builder setLimits(ImmutableSet<Limit> limits);
201205

206+
public abstract Builder setContextVariable(ContextVariable contextVariable);
207+
202208
abstract CelEnvironment autoBuild();
203209

204210
@CheckReturnValue
@@ -258,6 +264,12 @@ public CelCompiler extend(CelCompiler celCompiler, CelOptions celOptions)
258264

259265
applyStandardLibrarySubset(compilerBuilder);
260266

267+
contextVariable()
268+
.ifPresent(
269+
cv ->
270+
compilerBuilder.addProtoTypeMasks(
271+
ProtoTypeMask.ofAllFields(cv.typeName()).withFieldsAsVariableDeclarations()));
272+
261273
return compilerBuilder.build();
262274
} catch (RuntimeException e) {
263275
throw new CelEnvironmentException(e.getMessage(), e);
@@ -406,6 +418,17 @@ private static CanonicalCelExtension getExtensionOrThrow(String extensionName) {
406418
return extension;
407419
}
408420

421+
/** Represents a context variable declaration. */
422+
@AutoValue
423+
public abstract static class ContextVariable {
424+
/** Fully qualified type name of the context variable. */
425+
public abstract String typeName();
426+
427+
public static ContextVariable create(String typeName) {
428+
return new AutoValue_CelEnvironment_ContextVariable(typeName);
429+
}
430+
}
431+
409432
/** Represents a policy variable declaration. */
410433
@AutoValue
411434
public abstract static class VariableDecl {

bundle/src/main/java/dev/cel/bundle/CelEnvironmentYamlParser.java

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import com.google.common.collect.ImmutableSet;
2929
import com.google.errorprone.annotations.CanIgnoreReturnValue;
3030
import dev.cel.bundle.CelEnvironment.Alias;
31+
import dev.cel.bundle.CelEnvironment.ContextVariable;
3132
import dev.cel.bundle.CelEnvironment.ExtensionConfig;
3233
import dev.cel.bundle.CelEnvironment.FunctionDecl;
3334
import dev.cel.bundle.CelEnvironment.LibrarySubset;
@@ -320,6 +321,36 @@ private ImmutableSet<String> parseAbbreviations(ParserContext<Node> ctx, Node no
320321
return builder.build();
321322
}
322323

324+
private ContextVariable parseContextVariable(ParserContext<Node> ctx, Node node) {
325+
long id = ctx.collectMetadata(node);
326+
if (!assertYamlType(ctx, id, node, YamlNodeType.MAP)) {
327+
return ContextVariable.create("");
328+
}
329+
330+
MappingNode mapNode = (MappingNode) node;
331+
String typeName = "";
332+
for (NodeTuple nodeTuple : mapNode.getValue()) {
333+
Node keyNode = nodeTuple.getKeyNode();
334+
long keyId = ctx.collectMetadata(keyNode);
335+
Node valueNode = nodeTuple.getValueNode();
336+
String keyName = ((ScalarNode) keyNode).getValue();
337+
switch (keyName) {
338+
case "type_name":
339+
typeName = newString(ctx, valueNode);
340+
break;
341+
default:
342+
ctx.reportError(keyId, String.format("Unsupported context_variable tag: %s", keyName));
343+
break;
344+
}
345+
}
346+
347+
if (typeName.isEmpty()) {
348+
ctx.reportError(id, "Missing required attribute(s): type_name");
349+
}
350+
351+
return ContextVariable.create(typeName);
352+
}
353+
323354
private ImmutableSet<VariableDecl> parseVariables(ParserContext<Node> ctx, Node node) {
324355
long valueId = ctx.collectMetadata(node);
325356
ImmutableSet.Builder<VariableDecl> variableSetBuilder = ImmutableSet.builder();
@@ -900,6 +931,9 @@ private CelEnvironment.Builder parseConfig(ParserContext<Node> ctx, Node node) {
900931
case "limits":
901932
builder.setLimits(parseLimits(ctx, valueNode));
902933
break;
934+
case "context_variable":
935+
builder.setContextVariable(parseContextVariable(ctx, valueNode));
936+
break;
903937
default:
904938
ctx.reportError(id, "Unknown config tag: " + fieldName);
905939
// continue handling the rest of the nodes

conformance/src/test/java/dev/cel/conformance/policy/BUILD.bazel

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ java_library(
1212
deps = [
1313
"//:auto_value",
1414
"//bundle:cel",
15+
"//policy:parser_factory",
16+
"//policy/testing:k8s_test_tag_handler",
1517
"//runtime:function_binding",
1618
"//testing/testrunner:cel_expression_source",
1719
"//testing/testrunner:cel_test_context",

conformance/src/test/java/dev/cel/conformance/policy/PolicyConformanceTest.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
import dev.cel.bundle.Cel;
1919
import dev.cel.bundle.CelFactory;
2020
import dev.cel.expr.conformance.proto3.TestAllTypes;
21+
import dev.cel.policy.CelPolicyParserFactory;
22+
import dev.cel.policy.testing.K8sTagHandler;
2123
import dev.cel.runtime.CelFunctionBinding;
2224
import dev.cel.testing.testrunner.CelExpressionSource;
2325
import dev.cel.testing.testrunner.CelTestContext;
@@ -77,6 +79,13 @@ public void evaluate() throws Throwable {
7779
TestAllTypes.getDescriptor().getFile(),
7880
Struct.getDescriptor().getFile());
7981

82+
// Scopes the custom Kubernetes tag visitor exclusively to k8s tests to prevent non-standard
83+
// grammar leakage.
84+
if (name.startsWith("k8s/")) {
85+
contextBuilder.setCelPolicyParser(
86+
CelPolicyParserFactory.newYamlParserBuilder().addTagVisitor(new K8sTagHandler()).build());
87+
}
88+
8089
Path yamlConfigPath = Paths.get(dirPath, "config.yaml");
8190
Path textprotoConfigPath = Paths.get(dirPath, "config.textproto");
8291

policy/src/main/java/dev/cel/policy/CelPolicy.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,7 @@ public abstract static class Builder implements RequiredFieldsChecker {
252252

253253
abstract Optional<Long> id();
254254

255-
abstract Optional<Result> result();
255+
public abstract Optional<Result> result();
256256

257257
abstract Optional<ValueString> explanation();
258258

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
load("@rules_java//java:defs.bzl", "java_library")
2+
3+
package(
4+
default_applicable_licenses = [
5+
"//:license",
6+
],
7+
default_testonly = True,
8+
default_visibility = [
9+
"//policy/testing:__pkg__",
10+
],
11+
)
12+
13+
java_library(
14+
name = "k8s_tag_handler",
15+
srcs = ["K8sTagHandler.java"],
16+
tags = [
17+
],
18+
deps = [
19+
"//common/formats:value_string",
20+
"//common/formats:yaml_helper",
21+
"//policy",
22+
"//policy:parser",
23+
"//policy:policy_parser_context",
24+
"@maven//:com_google_guava_guava",
25+
"@maven//:org_yaml_snakeyaml",
26+
],
27+
)
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
// Copyright 2026 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package dev.cel.policy.testing;
16+
17+
import com.google.common.annotations.VisibleForTesting;
18+
import dev.cel.common.formats.ValueString;
19+
import dev.cel.common.formats.YamlHelper;
20+
import dev.cel.common.formats.YamlHelper.YamlNodeType;
21+
import dev.cel.policy.CelPolicy;
22+
import dev.cel.policy.CelPolicy.Match;
23+
import dev.cel.policy.CelPolicyParser.TagVisitor;
24+
import dev.cel.policy.PolicyParserContext;
25+
import org.yaml.snakeyaml.nodes.Node;
26+
import org.yaml.snakeyaml.nodes.SequenceNode;
27+
28+
/**
29+
* K8sTagHandler is a {@link TagVisitor} implementation to support parsing Kubernetes
30+
* ValidatingAdmissionPolicy structures in testing and conformance environments.
31+
*/
32+
@VisibleForTesting
33+
public final class K8sTagHandler implements TagVisitor<Node> {
34+
35+
@Override
36+
public void visitPolicyTag(
37+
PolicyParserContext<Node> ctx,
38+
long id,
39+
String tagName,
40+
Node node,
41+
CelPolicy.Builder policyBuilder) {
42+
switch (tagName) {
43+
case "kind":
44+
policyBuilder.putMetadata("kind", ctx.newYamlString(node).value());
45+
break;
46+
case "metadata":
47+
YamlHelper.assertYamlType(ctx, id, node, YamlNodeType.MAP);
48+
break;
49+
case "spec":
50+
CelPolicy.Rule spec = ctx.parseRule(ctx, policyBuilder, node);
51+
policyBuilder.setRule(spec);
52+
break;
53+
default:
54+
TagVisitor.super.visitPolicyTag(ctx, id, tagName, node, policyBuilder);
55+
break;
56+
}
57+
}
58+
59+
@Override
60+
public void visitRuleTag(
61+
PolicyParserContext<Node> ctx,
62+
long id,
63+
String tagName,
64+
Node node,
65+
CelPolicy.Builder policyBuilder,
66+
CelPolicy.Rule.Builder ruleBuilder) {
67+
switch (tagName) {
68+
case "failurePolicy":
69+
policyBuilder.putMetadata(tagName, ctx.newYamlString(node).value());
70+
break;
71+
case "matchConstraints":
72+
YamlHelper.assertYamlType(ctx, id, node, YamlNodeType.MAP);
73+
break;
74+
case "validations":
75+
if (!YamlHelper.assertYamlType(ctx, id, node, YamlNodeType.LIST)) {
76+
return;
77+
}
78+
SequenceNode seqNode = (SequenceNode) node;
79+
for (Node valNode : seqNode.getValue()) {
80+
ruleBuilder.addMatches(ctx.parseMatch(ctx, policyBuilder, valNode));
81+
}
82+
break;
83+
default:
84+
TagVisitor.super.visitRuleTag(ctx, id, tagName, node, policyBuilder, ruleBuilder);
85+
break;
86+
}
87+
}
88+
89+
@Override
90+
public void visitMatchTag(
91+
PolicyParserContext<Node> ctx,
92+
long id,
93+
String tagName,
94+
Node node,
95+
CelPolicy.Builder policyBuilder,
96+
CelPolicy.Match.Builder matchBuilder) {
97+
if (!matchBuilder.result().isPresent()) {
98+
matchBuilder.setResult(
99+
Match.Result.ofOutput(ValueString.of(ctx.nextId(), "'invalid admission request'")));
100+
}
101+
switch (tagName) {
102+
case "expression":
103+
// The K8s expression to validate must return false in order to generate a violation
104+
// message.
105+
ValueString condition = ctx.newSourceString(node);
106+
String invertedCondition = "!(" + condition.value() + ")";
107+
matchBuilder.setCondition(ValueString.of(condition.id(), invertedCondition));
108+
break;
109+
case "messageExpression":
110+
matchBuilder.setResult(Match.Result.ofOutput(ctx.newSourceString(node)));
111+
break;
112+
default:
113+
TagVisitor.super.visitMatchTag(ctx, id, tagName, node, policyBuilder, matchBuilder);
114+
break;
115+
}
116+
}
117+
}

policy/src/test/java/dev/cel/policy/BUILD.bazel

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ java_library(
3030
"//policy:compiler_factory",
3131
"//policy:parser",
3232
"//policy:parser_factory",
33-
"//policy:policy_parser_context",
3433
"//policy:source",
3534
"//policy:validation_exception",
35+
"//policy/testing:k8s_test_tag_handler",
3636
"//runtime",
3737
"//runtime:function_binding",
3838
"//testing:cel_runtime_flavor",

policy/src/test/java/dev/cel/policy/CelPolicyCompilerImplTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,12 @@
3737
import dev.cel.extensions.CelOptionalLibrary;
3838
import dev.cel.parser.CelStandardMacro;
3939
import dev.cel.parser.CelUnparserFactory;
40-
import dev.cel.policy.PolicyTestHelper.K8sTagHandler;
4140
import dev.cel.policy.PolicyTestHelper.PolicyTestSuite;
4241
import dev.cel.policy.PolicyTestHelper.PolicyTestSuite.PolicyTestSection;
4342
import dev.cel.policy.PolicyTestHelper.PolicyTestSuite.PolicyTestSection.PolicyTestCase;
4443
import dev.cel.policy.PolicyTestHelper.PolicyTestSuite.PolicyTestSection.PolicyTestCase.PolicyTestInput;
4544
import dev.cel.policy.PolicyTestHelper.TestYamlPolicy;
45+
import dev.cel.policy.testing.K8sTagHandler;
4646
import dev.cel.runtime.CelFunctionBinding;
4747
import dev.cel.runtime.CelLateFunctionBindings;
4848
import dev.cel.testing.CelRuntimeFlavor;

0 commit comments

Comments
 (0)