From b65d2b41223507dfdfc64409cccb77aaf74a8c68 Mon Sep 17 00:00:00 2001 From: Sokwhan Huh Date: Mon, 4 May 2026 16:00:28 -0700 Subject: [PATCH] Internal Changes PiperOrigin-RevId: 910292784 --- .../dev/cel/conformance/policy/BUILD.bazel | 2 + .../policy/PolicyConformanceTest.java | 9 ++ .../main/java/dev/cel/policy/CelPolicy.java | 2 +- .../java/dev/cel/policy/testing/BUILD.bazel | 27 ++++ .../dev/cel/policy/testing/K8sTagHandler.java | 117 ++++++++++++++++++ .../src/test/java/dev/cel/policy/BUILD.bazel | 2 +- .../cel/policy/CelPolicyCompilerImplTest.java | 2 +- .../cel/policy/CelPolicyYamlParserTest.java | 2 +- .../java/dev/cel/policy/PolicyTestHelper.java | 108 ---------------- policy/testing/BUILD.bazel | 12 ++ 10 files changed, 171 insertions(+), 112 deletions(-) create mode 100644 policy/src/main/java/dev/cel/policy/testing/BUILD.bazel create mode 100644 policy/src/main/java/dev/cel/policy/testing/K8sTagHandler.java create mode 100644 policy/testing/BUILD.bazel diff --git a/conformance/src/test/java/dev/cel/conformance/policy/BUILD.bazel b/conformance/src/test/java/dev/cel/conformance/policy/BUILD.bazel index ca34530fb..27853f29b 100644 --- a/conformance/src/test/java/dev/cel/conformance/policy/BUILD.bazel +++ b/conformance/src/test/java/dev/cel/conformance/policy/BUILD.bazel @@ -12,6 +12,8 @@ java_library( deps = [ "//:auto_value", "//bundle:cel", + "//policy:parser_factory", + "//policy/testing:k8s_test_tag_handler", "//runtime:function_binding", "//testing/testrunner:cel_expression_source", "//testing/testrunner:cel_test_context", diff --git a/conformance/src/test/java/dev/cel/conformance/policy/PolicyConformanceTest.java b/conformance/src/test/java/dev/cel/conformance/policy/PolicyConformanceTest.java index cd24339c0..4f1c643c2 100644 --- a/conformance/src/test/java/dev/cel/conformance/policy/PolicyConformanceTest.java +++ b/conformance/src/test/java/dev/cel/conformance/policy/PolicyConformanceTest.java @@ -18,6 +18,8 @@ import dev.cel.bundle.Cel; import dev.cel.bundle.CelFactory; import dev.cel.expr.conformance.proto3.TestAllTypes; +import dev.cel.policy.CelPolicyParserFactory; +import dev.cel.policy.testing.K8sTagHandler; import dev.cel.runtime.CelFunctionBinding; import dev.cel.testing.testrunner.CelExpressionSource; import dev.cel.testing.testrunner.CelTestContext; @@ -77,6 +79,13 @@ public void evaluate() throws Throwable { TestAllTypes.getDescriptor().getFile(), Struct.getDescriptor().getFile()); + // Scopes the custom Kubernetes tag visitor exclusively to k8s tests to prevent non-standard + // grammar leakage. + if (name.startsWith("k8s/")) { + contextBuilder.setCelPolicyParser( + CelPolicyParserFactory.newYamlParserBuilder().addTagVisitor(new K8sTagHandler()).build()); + } + Path yamlConfigPath = Paths.get(dirPath, "config.yaml"); Path textprotoConfigPath = Paths.get(dirPath, "config.textproto"); diff --git a/policy/src/main/java/dev/cel/policy/CelPolicy.java b/policy/src/main/java/dev/cel/policy/CelPolicy.java index 9e442a2e7..19f6631d0 100644 --- a/policy/src/main/java/dev/cel/policy/CelPolicy.java +++ b/policy/src/main/java/dev/cel/policy/CelPolicy.java @@ -252,7 +252,7 @@ public abstract static class Builder implements RequiredFieldsChecker { abstract Optional id(); - abstract Optional result(); + public abstract Optional result(); abstract Optional explanation(); diff --git a/policy/src/main/java/dev/cel/policy/testing/BUILD.bazel b/policy/src/main/java/dev/cel/policy/testing/BUILD.bazel new file mode 100644 index 000000000..3a8a4950b --- /dev/null +++ b/policy/src/main/java/dev/cel/policy/testing/BUILD.bazel @@ -0,0 +1,27 @@ +load("@rules_java//java:defs.bzl", "java_library") + +package( + default_applicable_licenses = [ + "//:license", + ], + default_testonly = True, + default_visibility = [ + "//policy/testing:__pkg__", + ], +) + +java_library( + name = "k8s_tag_handler", + srcs = ["K8sTagHandler.java"], + tags = [ + ], + deps = [ + "//common/formats:value_string", + "//common/formats:yaml_helper", + "//policy", + "//policy:parser", + "//policy:policy_parser_context", + "@maven//:com_google_guava_guava", + "@maven//:org_yaml_snakeyaml", + ], +) diff --git a/policy/src/main/java/dev/cel/policy/testing/K8sTagHandler.java b/policy/src/main/java/dev/cel/policy/testing/K8sTagHandler.java new file mode 100644 index 000000000..04635e054 --- /dev/null +++ b/policy/src/main/java/dev/cel/policy/testing/K8sTagHandler.java @@ -0,0 +1,117 @@ +// Copyright 2026 Google LLC +// +// 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 +// +// https://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 dev.cel.policy.testing; + +import com.google.common.annotations.VisibleForTesting; +import dev.cel.common.formats.ValueString; +import dev.cel.common.formats.YamlHelper; +import dev.cel.common.formats.YamlHelper.YamlNodeType; +import dev.cel.policy.CelPolicy; +import dev.cel.policy.CelPolicy.Match; +import dev.cel.policy.CelPolicyParser.TagVisitor; +import dev.cel.policy.PolicyParserContext; +import org.yaml.snakeyaml.nodes.Node; +import org.yaml.snakeyaml.nodes.SequenceNode; + +/** + * K8sTagHandler is a {@link TagVisitor} implementation to support parsing Kubernetes + * ValidatingAdmissionPolicy structures in testing and conformance environments. + */ +@VisibleForTesting +public final class K8sTagHandler implements TagVisitor { + + @Override + public void visitPolicyTag( + PolicyParserContext ctx, + long id, + String tagName, + Node node, + CelPolicy.Builder policyBuilder) { + switch (tagName) { + case "kind": + policyBuilder.putMetadata("kind", ctx.newYamlString(node).value()); + break; + case "metadata": + YamlHelper.assertYamlType(ctx, id, node, YamlNodeType.MAP); + break; + case "spec": + CelPolicy.Rule spec = ctx.parseRule(ctx, policyBuilder, node); + policyBuilder.setRule(spec); + break; + default: + TagVisitor.super.visitPolicyTag(ctx, id, tagName, node, policyBuilder); + break; + } + } + + @Override + public void visitRuleTag( + PolicyParserContext ctx, + long id, + String tagName, + Node node, + CelPolicy.Builder policyBuilder, + CelPolicy.Rule.Builder ruleBuilder) { + switch (tagName) { + case "failurePolicy": + policyBuilder.putMetadata(tagName, ctx.newYamlString(node).value()); + break; + case "matchConstraints": + YamlHelper.assertYamlType(ctx, id, node, YamlNodeType.MAP); + break; + case "validations": + if (!YamlHelper.assertYamlType(ctx, id, node, YamlNodeType.LIST)) { + return; + } + SequenceNode seqNode = (SequenceNode) node; + for (Node valNode : seqNode.getValue()) { + ruleBuilder.addMatches(ctx.parseMatch(ctx, policyBuilder, valNode)); + } + break; + default: + TagVisitor.super.visitRuleTag(ctx, id, tagName, node, policyBuilder, ruleBuilder); + break; + } + } + + @Override + public void visitMatchTag( + PolicyParserContext ctx, + long id, + String tagName, + Node node, + CelPolicy.Builder policyBuilder, + CelPolicy.Match.Builder matchBuilder) { + if (!matchBuilder.result().isPresent()) { + matchBuilder.setResult( + Match.Result.ofOutput(ValueString.of(ctx.nextId(), "'invalid admission request'"))); + } + switch (tagName) { + case "expression": + // The K8s expression to validate must return false in order to generate a violation + // message. + ValueString condition = ctx.newSourceString(node); + String invertedCondition = "!(" + condition.value() + ")"; + matchBuilder.setCondition(ValueString.of(condition.id(), invertedCondition)); + break; + case "messageExpression": + matchBuilder.setResult(Match.Result.ofOutput(ctx.newSourceString(node))); + break; + default: + TagVisitor.super.visitMatchTag(ctx, id, tagName, node, policyBuilder, matchBuilder); + break; + } + } +} diff --git a/policy/src/test/java/dev/cel/policy/BUILD.bazel b/policy/src/test/java/dev/cel/policy/BUILD.bazel index 8a28caee1..3089a3849 100644 --- a/policy/src/test/java/dev/cel/policy/BUILD.bazel +++ b/policy/src/test/java/dev/cel/policy/BUILD.bazel @@ -30,9 +30,9 @@ java_library( "//policy:compiler_factory", "//policy:parser", "//policy:parser_factory", - "//policy:policy_parser_context", "//policy:source", "//policy:validation_exception", + "//policy/testing:k8s_test_tag_handler", "//runtime", "//runtime:function_binding", "//testing:cel_runtime_flavor", diff --git a/policy/src/test/java/dev/cel/policy/CelPolicyCompilerImplTest.java b/policy/src/test/java/dev/cel/policy/CelPolicyCompilerImplTest.java index 35e249407..416e3b95f 100644 --- a/policy/src/test/java/dev/cel/policy/CelPolicyCompilerImplTest.java +++ b/policy/src/test/java/dev/cel/policy/CelPolicyCompilerImplTest.java @@ -37,12 +37,12 @@ import dev.cel.extensions.CelOptionalLibrary; import dev.cel.parser.CelStandardMacro; import dev.cel.parser.CelUnparserFactory; -import dev.cel.policy.PolicyTestHelper.K8sTagHandler; import dev.cel.policy.PolicyTestHelper.PolicyTestSuite; import dev.cel.policy.PolicyTestHelper.PolicyTestSuite.PolicyTestSection; import dev.cel.policy.PolicyTestHelper.PolicyTestSuite.PolicyTestSection.PolicyTestCase; import dev.cel.policy.PolicyTestHelper.PolicyTestSuite.PolicyTestSection.PolicyTestCase.PolicyTestInput; import dev.cel.policy.PolicyTestHelper.TestYamlPolicy; +import dev.cel.policy.testing.K8sTagHandler; import dev.cel.runtime.CelFunctionBinding; import dev.cel.runtime.CelLateFunctionBindings; import dev.cel.testing.CelRuntimeFlavor; diff --git a/policy/src/test/java/dev/cel/policy/CelPolicyYamlParserTest.java b/policy/src/test/java/dev/cel/policy/CelPolicyYamlParserTest.java index 22aec6746..2a2c47a98 100644 --- a/policy/src/test/java/dev/cel/policy/CelPolicyYamlParserTest.java +++ b/policy/src/test/java/dev/cel/policy/CelPolicyYamlParserTest.java @@ -22,8 +22,8 @@ import com.google.testing.junit.testparameterinjector.TestParameterInjector; import dev.cel.common.formats.ValueString; import dev.cel.policy.CelPolicy.Import; -import dev.cel.policy.PolicyTestHelper.K8sTagHandler; import dev.cel.policy.PolicyTestHelper.TestYamlPolicy; +import dev.cel.policy.testing.K8sTagHandler; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/policy/src/test/java/dev/cel/policy/PolicyTestHelper.java b/policy/src/test/java/dev/cel/policy/PolicyTestHelper.java index 59647f4d9..6e918286b 100644 --- a/policy/src/test/java/dev/cel/policy/PolicyTestHelper.java +++ b/policy/src/test/java/dev/cel/policy/PolicyTestHelper.java @@ -19,11 +19,6 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Ascii; import com.google.common.io.Resources; -import dev.cel.common.formats.ValueString; -import dev.cel.policy.CelPolicy.Match; -import dev.cel.policy.CelPolicy.Match.Result; -import dev.cel.policy.CelPolicy.Rule; -import dev.cel.policy.CelPolicyParser.TagVisitor; import java.io.IOException; import java.net.URL; import java.util.List; @@ -31,8 +26,6 @@ import org.yaml.snakeyaml.LoaderOptions; import org.yaml.snakeyaml.Yaml; import org.yaml.snakeyaml.constructor.Constructor; -import org.yaml.snakeyaml.nodes.Node; -import org.yaml.snakeyaml.nodes.SequenceNode; /** Package-private class to assist with policy testing. */ final class PolicyTestHelper { @@ -273,106 +266,5 @@ private static String readFile(String path) throws IOException { return Resources.toString(getResource(path), UTF_8); } - static class K8sTagHandler implements TagVisitor { - - @Override - public void visitPolicyTag( - PolicyParserContext ctx, - long id, - String tagName, - Node node, - CelPolicy.Builder policyBuilder) { - switch (tagName) { - case "kind": - policyBuilder.putMetadata("kind", ctx.newYamlString(node)); - break; - case "metadata": - long metadataId = ctx.collectMetadata(node); - if (!node.getTag().getValue().equals("tag:yaml.org,2002:map")) { - ctx.reportError( - metadataId, - String.format( - "invalid 'metadata' type, expected map got: %s", node.getTag().getValue())); - } - break; - case "spec": - Rule rule = ctx.parseRule(ctx, policyBuilder, node); - policyBuilder.setRule(rule); - break; - default: - TagVisitor.super.visitPolicyTag(ctx, id, tagName, node, policyBuilder); - break; - } - } - - @Override - public void visitRuleTag( - PolicyParserContext ctx, - long id, - String tagName, - Node node, - CelPolicy.Builder policyBuilder, - Rule.Builder ruleBuilder) { - switch (tagName) { - case "failurePolicy": - policyBuilder.putMetadata(tagName, ctx.newYamlString(node)); - break; - case "matchConstraints": - long matchConstraintsId = ctx.collectMetadata(node); - if (!node.getTag().getValue().equals("tag:yaml.org,2002:map")) { - ctx.reportError( - matchConstraintsId, - String.format( - "invalid 'matchConstraints' type, expected map got: %s", - node.getTag().getValue())); - } - break; - case "validations": - long validationId = ctx.collectMetadata(node); - if (!node.getTag().getValue().equals("tag:yaml.org,2002:seq")) { - ctx.reportError( - validationId, - String.format( - "invalid 'validations' type, expected list got: %s", node.getTag().getValue())); - } - - SequenceNode validationNodes = (SequenceNode) node; - for (Node element : validationNodes.getValue()) { - ruleBuilder.addMatches(ctx.parseMatch(ctx, policyBuilder, element)); - } - break; - default: - TagVisitor.super.visitRuleTag(ctx, id, tagName, node, policyBuilder, ruleBuilder); - break; - } - } - - @Override - public void visitMatchTag( - PolicyParserContext ctx, - long id, - String tagName, - Node node, - CelPolicy.Builder policyBuilder, - Match.Builder matchBuilder) { - switch (tagName) { - case "expression": - // The K8s expression to validate must return false in order to generate a violation - // message. - ValueString conditionValue = ctx.newYamlString(node); - conditionValue = - conditionValue.toBuilder().setValue("!(" + conditionValue.value() + ")").build(); - matchBuilder.setCondition(conditionValue); - break; - case "messageExpression": - matchBuilder.setResult(Result.ofOutput(ctx.newYamlString(node))); - break; - default: - TagVisitor.super.visitMatchTag(ctx, id, tagName, node, policyBuilder, matchBuilder); - break; - } - } - } - private PolicyTestHelper() {} } diff --git a/policy/testing/BUILD.bazel b/policy/testing/BUILD.bazel new file mode 100644 index 000000000..898368c3c --- /dev/null +++ b/policy/testing/BUILD.bazel @@ -0,0 +1,12 @@ +load("@rules_java//java:defs.bzl", "java_library") + +package( + default_applicable_licenses = ["//:license"], + default_testonly = True, + default_visibility = ["//:internal"], +) + +java_library( + name = "k8s_test_tag_handler", + exports = ["//policy/src/main/java/dev/cel/policy/testing:k8s_tag_handler"], +)