mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-19 08:40:42 +00:00
Manual changes in /third_party/forked/celopenapi to use Kubernetes types and remove unused code.
This commit is contained in:
parent
a37dfa7f0e
commit
89d0623b65
202
staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/LICENSE
vendored
Normal file
202
staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/LICENSE
vendored
Normal file
@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
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.
|
4
staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/README.md
vendored
Normal file
4
staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/README.md
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
This directory contains a port of https://github.com/google/cel-policy-templates-go/tree/master/policy/model modified in a few ways:
|
||||
|
||||
- Uses the Structural schema types
|
||||
- All template related code has been removed
|
@ -1,303 +0,0 @@
|
||||
// Copyright 2020 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 model
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/google/cel-go/cel"
|
||||
"github.com/google/cel-go/common/types"
|
||||
"github.com/google/cel-go/common/types/ref"
|
||||
)
|
||||
|
||||
// NewDecision returns an empty Decision instance.
|
||||
func NewDecision() *Decision {
|
||||
return &Decision{}
|
||||
}
|
||||
|
||||
// Decision contains a decision name, or reference to a decision name, and an output expression.
|
||||
type Decision struct {
|
||||
Name string
|
||||
Reference *cel.Ast
|
||||
Output *cel.Ast
|
||||
}
|
||||
|
||||
// DecisionValue represents a named decision and value.
|
||||
type DecisionValue interface {
|
||||
fmt.Stringer
|
||||
|
||||
// Name returns the decision name.
|
||||
Name() string
|
||||
|
||||
// IsFinal returns whether the decision value will change with additional rule evaluations.
|
||||
//
|
||||
// When a decision is final, additional productions and rules which may also trigger the same
|
||||
// decision may be skipped.
|
||||
IsFinal() bool
|
||||
}
|
||||
|
||||
// SingleDecisionValue extends the DecisionValue which contains a single decision value as well
|
||||
// as some metadata about the evaluation details and the rule that spawned the value.
|
||||
type SingleDecisionValue interface {
|
||||
DecisionValue
|
||||
|
||||
// Value returns the single value for the decision.
|
||||
Value() ref.Val
|
||||
|
||||
// Details returns the evaluation details, if present, that produced the value.
|
||||
Details() *cel.EvalDetails
|
||||
|
||||
// RuleID indicate which policy rule id within an instance that produced the decision.
|
||||
RuleID() int64
|
||||
}
|
||||
|
||||
// MultiDecisionValue extends the DecisionValue which contains a set of decision values as well as
|
||||
// the corresponding metadata about how each value was produced.
|
||||
type MultiDecisionValue interface {
|
||||
DecisionValue
|
||||
|
||||
// Values returns the collection of values produced for the decision.
|
||||
Values() []ref.Val
|
||||
|
||||
// Details returns the evaluation details for each value in the decision.
|
||||
// The value index correponds to the details index. The details may be nil.
|
||||
Details() []*cel.EvalDetails
|
||||
|
||||
// RulesIDs returns the rule id within an instance which produce the decision values.
|
||||
// The value index corresponds to the rule id index.
|
||||
RuleIDs() []int64
|
||||
}
|
||||
|
||||
// DecisionSelector determines whether the given decision is the decision set requested by the
|
||||
// caller.
|
||||
type DecisionSelector func(decision string) bool
|
||||
|
||||
// NewBoolDecisionValue returns a boolean decision with an initial value.
|
||||
func NewBoolDecisionValue(name string, value types.Bool) *BoolDecisionValue {
|
||||
return &BoolDecisionValue{
|
||||
name: name,
|
||||
value: value,
|
||||
}
|
||||
}
|
||||
|
||||
// BoolDecisionValue represents the decision value type associated with a decision.
|
||||
type BoolDecisionValue struct {
|
||||
name string
|
||||
value ref.Val
|
||||
isFinal bool
|
||||
details *cel.EvalDetails
|
||||
ruleID int64
|
||||
}
|
||||
|
||||
// And logically ANDs the current decision value with the incoming CEL value.
|
||||
//
|
||||
// And follows CEL semantics with respect to errors and unknown values where errors may be
|
||||
// absorbed or short-circuited away by subsequent 'false' values. When unkonwns are encountered
|
||||
// the unknown values combine and aggregate within the decision. Unknowns may also be absorbed
|
||||
// per CEL semantics.
|
||||
func (dv *BoolDecisionValue) And(other ref.Val) *BoolDecisionValue {
|
||||
v, vBool := dv.value.(types.Bool)
|
||||
if vBool && v == types.False {
|
||||
return dv
|
||||
}
|
||||
o, oBool := other.(types.Bool)
|
||||
if oBool && o == types.False {
|
||||
dv.value = types.False
|
||||
return dv
|
||||
}
|
||||
if vBool && oBool {
|
||||
return dv
|
||||
}
|
||||
dv.value = logicallyMergeUnkErr(dv.value, other)
|
||||
return dv
|
||||
}
|
||||
|
||||
// Details implements the SingleDecisionValue interface method.
|
||||
func (dv *BoolDecisionValue) Details() *cel.EvalDetails {
|
||||
return dv.details
|
||||
}
|
||||
|
||||
// Finalize marks the decision as immutable with additional input and indicates the rule and
|
||||
// evaluation details which triggered the finalization.
|
||||
func (dv *BoolDecisionValue) Finalize(details *cel.EvalDetails, rule Rule) DecisionValue {
|
||||
dv.details = details
|
||||
if rule != nil {
|
||||
dv.ruleID = rule.GetID()
|
||||
}
|
||||
dv.isFinal = true
|
||||
return dv
|
||||
}
|
||||
|
||||
// IsFinal returns whether the decision is final.
|
||||
func (dv *BoolDecisionValue) IsFinal() bool {
|
||||
return dv.isFinal
|
||||
}
|
||||
|
||||
// Or logically ORs the decision value with the incoming CEL value.
|
||||
//
|
||||
// The ORing logic follows CEL semantics with respect to errors and unknown values.
|
||||
// Errors may be absorbed or short-circuited away by subsequent 'true' values. When unkonwns are
|
||||
// encountered the unknown values combine and aggregate within the decision. Unknowns may also be
|
||||
// absorbed per CEL semantics.
|
||||
func (dv *BoolDecisionValue) Or(other ref.Val) *BoolDecisionValue {
|
||||
v, vBool := dv.value.(types.Bool)
|
||||
if vBool && v == types.True {
|
||||
return dv
|
||||
}
|
||||
o, oBool := other.(types.Bool)
|
||||
if oBool && o == types.True {
|
||||
dv.value = types.True
|
||||
return dv
|
||||
}
|
||||
if vBool && oBool {
|
||||
return dv
|
||||
}
|
||||
dv.value = logicallyMergeUnkErr(dv.value, other)
|
||||
return dv
|
||||
}
|
||||
|
||||
// Name implements the DecisionValue interface method.
|
||||
func (dv *BoolDecisionValue) Name() string {
|
||||
return dv.name
|
||||
}
|
||||
|
||||
// RuleID implements the SingleDecisionValue interface method.
|
||||
func (dv *BoolDecisionValue) RuleID() int64 {
|
||||
return dv.ruleID
|
||||
}
|
||||
|
||||
// String renders the decision value to a string for debug purposes.
|
||||
func (dv *BoolDecisionValue) String() string {
|
||||
var buf strings.Builder
|
||||
buf.WriteString(dv.name)
|
||||
buf.WriteString(": ")
|
||||
buf.WriteString(fmt.Sprintf("rule[%d] -> ", dv.ruleID))
|
||||
buf.WriteString(fmt.Sprintf("%v", dv.value))
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// Value implements the SingleDecisionValue interface method.
|
||||
func (dv *BoolDecisionValue) Value() ref.Val {
|
||||
return dv.value
|
||||
}
|
||||
|
||||
// NewListDecisionValue returns a named decision value which contains a list of CEL values produced
|
||||
// by one or more policy instances and / or production rules.
|
||||
func NewListDecisionValue(name string) *ListDecisionValue {
|
||||
return &ListDecisionValue{
|
||||
name: name,
|
||||
values: []ref.Val{},
|
||||
details: []*cel.EvalDetails{},
|
||||
ruleIDs: []int64{},
|
||||
}
|
||||
}
|
||||
|
||||
// ListDecisionValue represents a named decision which collects into a list of values.
|
||||
type ListDecisionValue struct {
|
||||
name string
|
||||
values []ref.Val
|
||||
isFinal bool
|
||||
details []*cel.EvalDetails
|
||||
ruleIDs []int64
|
||||
}
|
||||
|
||||
// Append accumulates the incoming CEL value into the decision's value list.
|
||||
func (dv *ListDecisionValue) Append(val ref.Val, det *cel.EvalDetails, rule Rule) {
|
||||
dv.values = append(dv.values, val)
|
||||
dv.details = append(dv.details, det)
|
||||
// Rule ids may be null if the policy is a singleton.
|
||||
ruleID := int64(0)
|
||||
if rule != nil {
|
||||
ruleID = rule.GetID()
|
||||
}
|
||||
dv.ruleIDs = append(dv.ruleIDs, ruleID)
|
||||
}
|
||||
|
||||
// Details returns the list of evaluation details observed in computing the values in the decision.
|
||||
// The details indices correlate 1:1 with the value indices.
|
||||
func (dv *ListDecisionValue) Details() []*cel.EvalDetails {
|
||||
return dv.details
|
||||
}
|
||||
|
||||
// Finalize marks the list decision complete.
|
||||
func (dv *ListDecisionValue) Finalize() DecisionValue {
|
||||
dv.isFinal = true
|
||||
return dv
|
||||
}
|
||||
|
||||
// IsFinal implements the DecisionValue interface method.
|
||||
func (dv *ListDecisionValue) IsFinal() bool {
|
||||
return dv.isFinal
|
||||
}
|
||||
|
||||
// Name implements the DecisionValue interface method.
|
||||
func (dv *ListDecisionValue) Name() string {
|
||||
return dv.name
|
||||
}
|
||||
|
||||
// RuleIDs returns the list of rule ids which produced the evaluation results.
|
||||
// The indices of the ruleIDs correlate 1:1 with the value indices.
|
||||
func (dv *ListDecisionValue) RuleIDs() []int64 {
|
||||
return dv.ruleIDs
|
||||
}
|
||||
|
||||
func (dv *ListDecisionValue) String() string {
|
||||
var buf strings.Builder
|
||||
buf.WriteString(dv.name)
|
||||
buf.WriteString(": ")
|
||||
for i, v := range dv.values {
|
||||
if len(dv.ruleIDs) == len(dv.values) {
|
||||
buf.WriteString(fmt.Sprintf("rule[%d] -> ", dv.ruleIDs[i]))
|
||||
}
|
||||
buf.WriteString(fmt.Sprintf("%v", v))
|
||||
buf.WriteString("\n")
|
||||
if i < len(dv.values)-1 {
|
||||
buf.WriteString("\t")
|
||||
}
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// Values implements the MultiDecisionValue interface method.
|
||||
func (dv *ListDecisionValue) Values() []ref.Val {
|
||||
return dv.values
|
||||
}
|
||||
|
||||
func logicallyMergeUnkErr(value, other ref.Val) ref.Val {
|
||||
vUnk := types.IsUnknown(value)
|
||||
oUnk := types.IsUnknown(other)
|
||||
if vUnk && oUnk {
|
||||
merged := types.Unknown{}
|
||||
merged = append(merged, value.(types.Unknown)...)
|
||||
merged = append(merged, other.(types.Unknown)...)
|
||||
return merged
|
||||
}
|
||||
if vUnk {
|
||||
return value
|
||||
}
|
||||
if oUnk {
|
||||
return other
|
||||
}
|
||||
if types.IsError(value) {
|
||||
return value
|
||||
}
|
||||
if types.IsError(other) {
|
||||
return other
|
||||
}
|
||||
return types.NewErr(
|
||||
"got values (%v, %v), wanted boolean values",
|
||||
value, other)
|
||||
}
|
@ -1,121 +0,0 @@
|
||||
// Copyright 2020 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 model
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/google/cel-go/common/types"
|
||||
"github.com/google/cel-go/common/types/ref"
|
||||
)
|
||||
|
||||
func TestBoolDecisionValue_And(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
value types.Bool
|
||||
ands []ref.Val
|
||||
result ref.Val
|
||||
}{
|
||||
{
|
||||
name: "init_false_end_false",
|
||||
value: types.False,
|
||||
ands: []ref.Val{types.NewErr("err"), types.True},
|
||||
result: types.False,
|
||||
},
|
||||
{
|
||||
name: "init_true_end_false",
|
||||
value: types.True,
|
||||
ands: []ref.Val{types.NewErr("err"), types.False},
|
||||
result: types.False,
|
||||
},
|
||||
{
|
||||
name: "init_true_end_err",
|
||||
value: types.True,
|
||||
ands: []ref.Val{types.True, types.NewErr("err")},
|
||||
result: types.NewErr("err"),
|
||||
},
|
||||
{
|
||||
name: "init_true_end_unk",
|
||||
value: types.True,
|
||||
ands: []ref.Val{types.True, types.Unknown{1}, types.NewErr("err"), types.Unknown{2}},
|
||||
result: types.Unknown{1, 2},
|
||||
},
|
||||
}
|
||||
for _, tst := range tests {
|
||||
tc := tst
|
||||
t.Run(tc.name, func(tt *testing.T) {
|
||||
v := NewBoolDecisionValue(tc.name, tc.value)
|
||||
for _, av := range tc.ands {
|
||||
v = v.And(av)
|
||||
}
|
||||
v.Finalize(nil, nil)
|
||||
if !reflect.DeepEqual(v.Value(), tc.result) {
|
||||
tt.Errorf("decision AND failed. got %v, wanted %v", v.Value(), tc.result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBoolDecisionValue_Or(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
value types.Bool
|
||||
ors []ref.Val
|
||||
result ref.Val
|
||||
}{
|
||||
{
|
||||
name: "init_false_end_true",
|
||||
value: types.False,
|
||||
ors: []ref.Val{types.NewErr("err"), types.Unknown{1}, types.True},
|
||||
result: types.True,
|
||||
},
|
||||
{
|
||||
name: "init_true_end_true",
|
||||
value: types.True,
|
||||
ors: []ref.Val{types.NewErr("err"), types.False},
|
||||
result: types.True,
|
||||
},
|
||||
{
|
||||
name: "init_false_end_err",
|
||||
value: types.False,
|
||||
ors: []ref.Val{types.False, types.NewErr("err1"), types.NewErr("err2")},
|
||||
result: types.NewErr("err1"),
|
||||
},
|
||||
{
|
||||
name: "init_false_end_unk",
|
||||
value: types.False,
|
||||
ors: []ref.Val{types.False, types.Unknown{1}, types.NewErr("err"), types.Unknown{2}},
|
||||
result: types.Unknown{1, 2},
|
||||
},
|
||||
}
|
||||
for _, tst := range tests {
|
||||
tc := tst
|
||||
t.Run(tc.name, func(tt *testing.T) {
|
||||
v := NewBoolDecisionValue(tc.name, tc.value)
|
||||
for _, av := range tc.ors {
|
||||
v = v.Or(av)
|
||||
}
|
||||
// Test finalization
|
||||
v.Finalize(nil, nil)
|
||||
// Ensure that calling string on the value doesn't error.
|
||||
_ = v.String()
|
||||
// Compare the output result
|
||||
if !reflect.DeepEqual(v.Value(), tc.result) {
|
||||
tt.Errorf("decision OR failed. got %v, wanted %v", v.Value(), tc.result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -1,209 +0,0 @@
|
||||
// Copyright 2020 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 model
|
||||
|
||||
import (
|
||||
"github.com/google/cel-go/cel"
|
||||
"github.com/google/cel-go/checker/decls"
|
||||
|
||||
exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
|
||||
)
|
||||
|
||||
// NewEnv creates an empty Env instance with a fully qualified name that may be referenced
|
||||
// within templates.
|
||||
func NewEnv(name string) *Env {
|
||||
return &Env{
|
||||
Name: name,
|
||||
Functions: []*Function{},
|
||||
Vars: []*Var{},
|
||||
Types: map[string]*DeclType{},
|
||||
}
|
||||
}
|
||||
|
||||
// Env declares a set of variables, functions, and types available to a given set of CEL
|
||||
// expressions.
|
||||
//
|
||||
// The Env name must be fully qualified as it will be referenced within template evaluators,
|
||||
// validators, and possibly within the metadata of the instance rule schema.
|
||||
//
|
||||
// Note, the Types values currently only holds type definitions associated with a variable
|
||||
// declaration. Any type mentioned in the environment which does not have a definition is
|
||||
// treated as a reference to a type which must be supplied in the base CEL environment provided
|
||||
// by the policy engine.
|
||||
type Env struct {
|
||||
Name string
|
||||
Container string
|
||||
Functions []*Function
|
||||
Vars []*Var
|
||||
Types map[string]*DeclType
|
||||
}
|
||||
|
||||
// ExprEnvOptions returns a set of CEL environment options to be used when extending the base
|
||||
// policy engine CEL environment.
|
||||
func (e *Env) ExprEnvOptions() []cel.EnvOption {
|
||||
opts := []cel.EnvOption{}
|
||||
if e.Container != "" {
|
||||
opts = append(opts, cel.Container(e.Container))
|
||||
}
|
||||
if len(e.Vars) > 0 {
|
||||
vars := make([]*exprpb.Decl, len(e.Vars))
|
||||
for i, v := range e.Vars {
|
||||
vars[i] = v.ExprDecl()
|
||||
}
|
||||
opts = append(opts, cel.Declarations(vars...))
|
||||
}
|
||||
if len(e.Functions) > 0 {
|
||||
funcs := make([]*exprpb.Decl, len(e.Functions))
|
||||
for i, f := range e.Functions {
|
||||
funcs[i] = f.ExprDecl()
|
||||
}
|
||||
opts = append(opts, cel.Declarations(funcs...))
|
||||
}
|
||||
return opts
|
||||
}
|
||||
|
||||
// NewVar creates a new variable with a name and a type.
|
||||
func NewVar(name string, dt *DeclType) *Var {
|
||||
return &Var{
|
||||
Name: name,
|
||||
Type: dt,
|
||||
}
|
||||
}
|
||||
|
||||
// Var represents a named instanced of a type.
|
||||
type Var struct {
|
||||
Name string
|
||||
Type *DeclType
|
||||
}
|
||||
|
||||
// ExprDecl produces a CEL proto declaration for the variable.
|
||||
func (v *Var) ExprDecl() *exprpb.Decl {
|
||||
return decls.NewVar(v.Name, v.Type.ExprType())
|
||||
}
|
||||
|
||||
// NewFunction creates a Function instance with a simple function name and a set of overload
|
||||
// signatures.
|
||||
func NewFunction(name string, overloads ...*Overload) *Function {
|
||||
return &Function{
|
||||
Name: name,
|
||||
Overloads: overloads,
|
||||
}
|
||||
}
|
||||
|
||||
// Function represents a simple name and a set of overload signatures.
|
||||
type Function struct {
|
||||
Name string
|
||||
Overloads []*Overload
|
||||
}
|
||||
|
||||
// ExprDecl produces a CEL proto declaration for the function and its overloads.
|
||||
func (f *Function) ExprDecl() *exprpb.Decl {
|
||||
overloadDecls := make([]*exprpb.Decl_FunctionDecl_Overload, len(f.Overloads))
|
||||
for i, o := range f.Overloads {
|
||||
overloadDecls[i] = o.overloadDecl()
|
||||
}
|
||||
return decls.NewFunction(f.Name, overloadDecls...)
|
||||
}
|
||||
|
||||
// NewOverload returns a receiver-style overload declaration for a given function.
|
||||
//
|
||||
// The overload name must follow the conventions laid out within the CEL overloads.go file.
|
||||
//
|
||||
// // Receiver-style overload name:
|
||||
// <receiver_type>_<func>_<arg_type0>_<arg_typeN>
|
||||
//
|
||||
// Within this function, the first type supplied is the receiver type, and the last type supplied
|
||||
// is used as the return type. At least two types must be specified for a zero-arity receiver
|
||||
// function.
|
||||
func NewOverload(name string, first *DeclType, rest ...*DeclType) *Overload {
|
||||
argTypes := make([]*DeclType, 1+len(rest))
|
||||
argTypes[0] = first
|
||||
for i := 1; i < len(rest)+1; i++ {
|
||||
argTypes[i] = rest[i-1]
|
||||
}
|
||||
returnType := argTypes[len(argTypes)-1]
|
||||
argTypes = argTypes[0 : len(argTypes)-1]
|
||||
return newOverload(name, false, argTypes, returnType)
|
||||
}
|
||||
|
||||
// NewFreeFunctionOverload returns a free function overload for a given function name.
|
||||
//
|
||||
// The overload name must follow the conventions laid out within the CEL overloads.go file:
|
||||
//
|
||||
// // Free function style overload name:
|
||||
// <func>_<arg_type0>_<arg_typeN>
|
||||
//
|
||||
// When the function name is global, <func> will refer to the simple function name. When the
|
||||
// function has a qualified name, replace the '.' characters in the fully-qualified name with
|
||||
// underscores.
|
||||
//
|
||||
// Within this function, the last type supplied is used as the return type. At least one type must
|
||||
// be specified for a zero-arity free function.
|
||||
func NewFreeFunctionOverload(name string, first *DeclType, rest ...*DeclType) *Overload {
|
||||
argTypes := make([]*DeclType, 1+len(rest))
|
||||
argTypes[0] = first
|
||||
for i := 1; i < len(rest)+1; i++ {
|
||||
argTypes[i] = rest[i-1]
|
||||
}
|
||||
returnType := argTypes[len(argTypes)-1]
|
||||
argTypes = argTypes[0 : len(argTypes)-1]
|
||||
return newOverload(name, true, argTypes, returnType)
|
||||
}
|
||||
|
||||
func newOverload(name string,
|
||||
freeFunction bool,
|
||||
argTypes []*DeclType,
|
||||
returnType *DeclType) *Overload {
|
||||
return &Overload{
|
||||
Name: name,
|
||||
FreeFunction: freeFunction,
|
||||
Args: argTypes,
|
||||
ReturnType: returnType,
|
||||
}
|
||||
}
|
||||
|
||||
// Overload represents a single function overload signature.
|
||||
type Overload struct {
|
||||
Name string
|
||||
FreeFunction bool
|
||||
Args []*DeclType
|
||||
ReturnType *DeclType
|
||||
}
|
||||
|
||||
func (o *Overload) overloadDecl() *exprpb.Decl_FunctionDecl_Overload {
|
||||
typeParams := map[string]struct{}{}
|
||||
argExprTypes := make([]*exprpb.Type, len(o.Args))
|
||||
for i, a := range o.Args {
|
||||
if a.TypeParam {
|
||||
typeParams[a.TypeName()] = struct{}{}
|
||||
}
|
||||
argExprTypes[i] = a.ExprType()
|
||||
}
|
||||
returnType := o.ReturnType.ExprType()
|
||||
if len(typeParams) == 0 {
|
||||
if o.FreeFunction {
|
||||
return decls.NewOverload(o.Name, argExprTypes, returnType)
|
||||
}
|
||||
return decls.NewInstanceOverload(o.Name, argExprTypes, returnType)
|
||||
}
|
||||
typeParamNames := make([]string, 0, len(typeParams))
|
||||
for param := range typeParams {
|
||||
typeParamNames = append(typeParamNames, param)
|
||||
}
|
||||
if o.FreeFunction {
|
||||
return decls.NewParameterizedOverload(o.Name, argExprTypes, returnType, typeParamNames)
|
||||
}
|
||||
return decls.NewParameterizedInstanceOverload(o.Name, argExprTypes, returnType, typeParamNames)
|
||||
}
|
@ -1,73 +0,0 @@
|
||||
// Copyright 2020 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 model
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/cel-go/cel"
|
||||
)
|
||||
|
||||
func TestEnv_Vars(t *testing.T) {
|
||||
env := NewEnv("test.v1.Environment")
|
||||
env.Container = "test.v1"
|
||||
env.Vars = []*Var{
|
||||
NewVar("greeting", StringType),
|
||||
NewVar("replies", NewListType(StringType)),
|
||||
}
|
||||
expr := `greeting == 'hello' && replies.size() > 0`
|
||||
stdEnv, _ := cel.NewEnv()
|
||||
ast, iss := stdEnv.Compile(expr)
|
||||
if iss.Err() == nil {
|
||||
t.Errorf("got ast %v, expected error", ast)
|
||||
}
|
||||
custEnv, err := stdEnv.Extend(env.ExprEnvOptions()...)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, iss = custEnv.Compile(expr)
|
||||
if iss.Err() != nil {
|
||||
t.Errorf("got error %v, wanted ast", iss)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnv_Funcs(t *testing.T) {
|
||||
env := NewEnv("test.v1.Environment")
|
||||
env.Container = "test.v1"
|
||||
env.Functions = []*Function{
|
||||
NewFunction("greeting",
|
||||
NewOverload("string_greeting_string", StringType, StringType, BoolType),
|
||||
NewFreeFunctionOverload("greeting_string", StringType, BoolType)),
|
||||
NewFunction("getOrDefault",
|
||||
NewOverload("map_get_or_default_param",
|
||||
NewMapType(NewTypeParam("K"), NewTypeParam("V")),
|
||||
NewTypeParam("K"), NewTypeParam("V"),
|
||||
NewTypeParam("V"))),
|
||||
}
|
||||
expr := `greeting('hello') && 'jim'.greeting('hello') && {'a': 0}.getOrDefault('b', 1) == 1`
|
||||
stdEnv, _ := cel.NewEnv()
|
||||
ast, iss := stdEnv.Compile(expr)
|
||||
if iss.Err() == nil {
|
||||
t.Errorf("got ast %v, expected error", ast)
|
||||
}
|
||||
custEnv, err := stdEnv.Extend(env.ExprEnvOptions()...)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, iss = custEnv.Compile(expr)
|
||||
if iss.Err() != nil {
|
||||
t.Errorf("got error %v, wanted ast", iss)
|
||||
}
|
||||
}
|
80
staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/escaping.go
vendored
Normal file
80
staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/escaping.go
vendored
Normal file
@ -0,0 +1,80 @@
|
||||
/*
|
||||
Copyright 2021 The Kubernetes 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 model
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
)
|
||||
|
||||
// celReservedSymbols is a list of RESERVED symbols defined in the CEL lexer.
|
||||
// No identifiers are allowed to collide with these symbols.
|
||||
// https://github.com/google/cel-spec/blob/master/doc/langdef.md#syntax
|
||||
var celReservedSymbols = sets.NewString(
|
||||
"true", "false", "null", "in",
|
||||
"as", "break", "const", "continue", "else",
|
||||
"for", "function", "if", "import", "let",
|
||||
"loop", "package", "namespace", "return", // !! 'namespace' is used heavily in Kubernetes
|
||||
"var", "void", "while",
|
||||
)
|
||||
|
||||
// celLanguageIdentifiers is a list of identifiers that are part of the CEL language.
|
||||
// This does NOT include builtin macro or function identifiers.
|
||||
// https://github.com/google/cel-spec/blob/master/doc/langdef.md#values
|
||||
var celLanguageIdentifiers = sets.NewString(
|
||||
"int", "uint", "double", "bool", "string", "bytes", "list", "map", "null_type", "type",
|
||||
)
|
||||
|
||||
// IsRootReserved returns true if an identifier is reserved by CEL. Declaring root variables in CEL with
|
||||
// these identifiers is not allowed and would result in an "overlapping identifier for name '<identifier>'"
|
||||
// CEL compilation error.
|
||||
func IsRootReserved(prop string) bool {
|
||||
return celLanguageIdentifiers.Has(prop)
|
||||
}
|
||||
|
||||
// Escape escapes identifiers in the AlwaysReservedIdentifiers set by prefixing ident with "_" and by prefixing
|
||||
// any ident already prefixed with N '_' with N+1 '_'.
|
||||
// For an identifier that does not require escaping, the identifier is returned as-is.
|
||||
func Escape(ident string) string {
|
||||
if strings.HasPrefix(ident, "_") || celReservedSymbols.Has(ident) {
|
||||
return "_" + ident
|
||||
}
|
||||
return ident
|
||||
}
|
||||
|
||||
// EscapeSlice returns identifiers with Escape applied to each.
|
||||
func EscapeSlice(idents []string) []string {
|
||||
result := make([]string, len(idents))
|
||||
for i, prop := range idents {
|
||||
result[i] = Escape(prop)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Unescape unescapes an identifier escaped by Escape.
|
||||
func Unescape(escaped string) string {
|
||||
if strings.HasPrefix(escaped, "_") {
|
||||
trimmed := strings.TrimPrefix(escaped, "_")
|
||||
if strings.HasPrefix(trimmed, "_") || celReservedSymbols.Has(trimmed) {
|
||||
return trimmed
|
||||
}
|
||||
panic(fmt.Sprintf("failed to unescape improperly escaped string: %v", escaped))
|
||||
}
|
||||
return escaped
|
||||
}
|
115
staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/escaping_test.go
vendored
Normal file
115
staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/escaping_test.go
vendored
Normal file
@ -0,0 +1,115 @@
|
||||
/*
|
||||
Copyright 2021 The Kubernetes 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 model
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestEscaping tests that property names are escaped as expected.
|
||||
func TestEscaping(t *testing.T) {
|
||||
cases := []struct{
|
||||
unescaped string
|
||||
escaped string
|
||||
reservedAtRoot bool
|
||||
} {
|
||||
// CEL lexer RESERVED keywords must be escaped
|
||||
{ unescaped: "true", escaped: "_true" },
|
||||
{ unescaped: "false", escaped: "_false" },
|
||||
{ unescaped: "null", escaped: "_null" },
|
||||
{ unescaped: "in", escaped: "_in" },
|
||||
{ unescaped: "as", escaped: "_as" },
|
||||
{ unescaped: "break", escaped: "_break" },
|
||||
{ unescaped: "const", escaped: "_const" },
|
||||
{ unescaped: "continue", escaped: "_continue" },
|
||||
{ unescaped: "else", escaped: "_else" },
|
||||
{ unescaped: "for", escaped: "_for" },
|
||||
{ unescaped: "function", escaped: "_function" },
|
||||
{ unescaped: "if", escaped: "_if" },
|
||||
{ unescaped: "import", escaped: "_import" },
|
||||
{ unescaped: "let", escaped: "_let" },
|
||||
{ unescaped: "loop", escaped: "_loop" },
|
||||
{ unescaped: "package", escaped: "_package" },
|
||||
{ unescaped: "namespace", escaped: "_namespace" },
|
||||
{ unescaped: "return", escaped: "_return" },
|
||||
{ unescaped: "var", escaped: "_var" },
|
||||
{ unescaped: "void", escaped: "_void" },
|
||||
{ unescaped: "while", escaped: "_while" },
|
||||
// CEL language identifiers do not need to be escaped, but collide with builtin language identifier if bound as
|
||||
// root variable names.
|
||||
// i.e. "self.int == 1" is legal, but "int == 1" is not.
|
||||
{ unescaped: "int", escaped: "int", reservedAtRoot: true },
|
||||
{ unescaped: "uint", escaped: "uint", reservedAtRoot: true },
|
||||
{ unescaped: "double", escaped: "double", reservedAtRoot: true },
|
||||
{ unescaped: "bool", escaped: "bool", reservedAtRoot: true },
|
||||
{ unescaped: "string", escaped: "string", reservedAtRoot: true },
|
||||
{ unescaped: "bytes", escaped: "bytes", reservedAtRoot: true },
|
||||
{ unescaped: "list", escaped: "list", reservedAtRoot: true },
|
||||
{ unescaped: "map", escaped: "map", reservedAtRoot: true },
|
||||
{ unescaped: "null_type", escaped: "null_type", reservedAtRoot: true },
|
||||
{ unescaped: "type", escaped: "type", reservedAtRoot: true },
|
||||
// To prevent escaping from colliding with other identifiers, all identifiers prefixed by _s are escaped by
|
||||
// prefixing them with N+1 _s.
|
||||
{ unescaped: "_if", escaped: "__if" },
|
||||
{ unescaped: "__if", escaped: "___if" },
|
||||
{ unescaped: "___if", escaped: "____if" },
|
||||
{ unescaped: "_has", escaped: "__has" },
|
||||
{ unescaped: "_int", escaped: "__int" },
|
||||
{ unescaped: "_anything", escaped: "__anything" },
|
||||
// CEL macro and function names do not need to be escaped because the parser can disambiguate them from the function and
|
||||
// macro identifiers.
|
||||
{ unescaped: "has", escaped: "has" },
|
||||
{ unescaped: "all", escaped: "all" },
|
||||
{ unescaped: "exists", escaped: "exists" },
|
||||
{ unescaped: "exists_one", escaped: "exists_one" },
|
||||
{ unescaped: "filter", escaped: "filter" },
|
||||
{ unescaped: "size", escaped: "size" },
|
||||
{ unescaped: "contains", escaped: "contains" },
|
||||
{ unescaped: "startsWith", escaped: "startsWith" },
|
||||
{ unescaped: "endsWith", escaped: "endsWith" },
|
||||
{ unescaped: "matches", escaped: "matches" },
|
||||
{ unescaped: "duration", escaped: "duration" },
|
||||
{ unescaped: "timestamp", escaped: "timestamp" },
|
||||
{ unescaped: "getDate", escaped: "getDate" },
|
||||
{ unescaped: "getDayOfMonth", escaped: "getDayOfMonth" },
|
||||
{ unescaped: "getDayOfWeek", escaped: "getDayOfWeek" },
|
||||
{ unescaped: "getFullYear", escaped: "getFullYear" },
|
||||
{ unescaped: "getHours", escaped: "getHours" },
|
||||
{ unescaped: "getMilliseconds", escaped: "getMilliseconds" },
|
||||
{ unescaped: "getMinutes", escaped: "getMinutes" },
|
||||
{ unescaped: "getMonth", escaped: "getMonth" },
|
||||
{ unescaped: "getSeconds", escaped: "getSeconds" },
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.unescaped, func(t *testing.T) {
|
||||
e := Escape(tc.unescaped)
|
||||
if tc.escaped != e {
|
||||
t.Errorf("Expected %s to escape to %s, but got %s", tc.unescaped, tc.escaped, e)
|
||||
}
|
||||
u := Unescape(tc.escaped)
|
||||
if tc.unescaped != u {
|
||||
t.Errorf("Expected %s to unescape to %s, but got %s", tc.escaped, tc.unescaped, e)
|
||||
}
|
||||
|
||||
isRootReserved := IsRootReserved(tc.unescaped)
|
||||
if tc.reservedAtRoot != isRootReserved {
|
||||
t.Errorf("Expected isRootReserved=%t for %s, but got %t", tc.reservedAtRoot, tc.unescaped, isRootReserved)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -1,148 +0,0 @@
|
||||
// Copyright 2020 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 model contains abstract representations of policy template and instance config objects.
|
||||
package model
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// NewInstance returns an empty policy instance.
|
||||
func NewInstance(info SourceMetadata) *Instance {
|
||||
return &Instance{
|
||||
Metadata: &InstanceMetadata{},
|
||||
Selectors: []Selector{},
|
||||
Rules: []Rule{},
|
||||
Meta: info,
|
||||
}
|
||||
}
|
||||
|
||||
// Instance represents the compiled, type-checked, and validated policy instance.
|
||||
type Instance struct {
|
||||
APIVersion string
|
||||
Kind string
|
||||
Metadata *InstanceMetadata
|
||||
Description string
|
||||
|
||||
// Selectors determine whether the instance applies to the current evaluation context.
|
||||
// All Selector values must return true for the policy instance to be included in policy
|
||||
// evaluation step.
|
||||
Selectors []Selector
|
||||
|
||||
// Rules represent reference data to be used in evaluation policy decisions.
|
||||
// Depending on the nature of the decisions being emitted, some or all Rules may be evaluated
|
||||
// and the results aggregated according to the decision types being emitted.
|
||||
Rules []Rule
|
||||
|
||||
// Meta represents the source metadata from the input instance.
|
||||
Meta SourceMetadata
|
||||
}
|
||||
|
||||
// MetadataMap returns the metadata name to value map, which can be used in evaluation.
|
||||
// Only "name" field is supported for now.
|
||||
func (i *Instance) MetadataMap() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"name": i.Metadata.Name,
|
||||
}
|
||||
}
|
||||
|
||||
// InstanceMetadata contains standard metadata which may be associated with an instance.
|
||||
type InstanceMetadata struct {
|
||||
UID string
|
||||
Name string
|
||||
Namespace string
|
||||
}
|
||||
|
||||
// Selector interface indicates a pre-formatted instance selection condition.
|
||||
//
|
||||
// The implementations of such conditions are expected to be platform specific.
|
||||
//
|
||||
// Note, if there is a clear need to tailor selection more heavily, then the schema definition
|
||||
// for a selector should be moved into the Template schema.
|
||||
type Selector interface {
|
||||
isSelector()
|
||||
}
|
||||
|
||||
// LabelSelector matches key, value pairs of labels associated with the evaluation context.
|
||||
//
|
||||
// In Kubernetes, the such labels are provided as 'resource.labels'.
|
||||
type LabelSelector struct {
|
||||
// LabelValues provides a map of the string keys and values expected.
|
||||
LabelValues map[string]string
|
||||
}
|
||||
|
||||
func (*LabelSelector) isSelector() {}
|
||||
|
||||
// ExpressionSelector matches a label against an existence condition.
|
||||
type ExpressionSelector struct {
|
||||
// Label name being matched.
|
||||
Label string
|
||||
|
||||
// Operator determines the evaluation behavior. Must be one of Exists, NotExists, In, or NotIn.
|
||||
Operator string
|
||||
|
||||
// Values set, optional, to be used in the NotIn, In set membership tests.
|
||||
Values []interface{}
|
||||
}
|
||||
|
||||
func (*ExpressionSelector) isSelector() {}
|
||||
|
||||
// Rule interface indicates the value types that may be used as Rule instances.
|
||||
//
|
||||
// Note, the code within the main repo deals exclusively with custom, yaml-based rules, but it
|
||||
// is entirely possible to use a protobuf message as the rule container.
|
||||
type Rule interface {
|
||||
isRule()
|
||||
GetID() int64
|
||||
GetFieldID(field string) int64
|
||||
}
|
||||
|
||||
// CustomRule embeds the DynValue and represents rules whose type definition is provided in the
|
||||
// policy template.
|
||||
type CustomRule struct {
|
||||
*DynValue
|
||||
}
|
||||
|
||||
func (*CustomRule) isRule() {}
|
||||
|
||||
// GetID returns the parse-time generated ID of the rule node.
|
||||
func (c *CustomRule) GetID() int64 {
|
||||
return c.ID
|
||||
}
|
||||
|
||||
// GetFieldID returns the parse-time generated ID pointing to the rule field. If field is not
|
||||
// specified or is not found, falls back to the ID of the rule node.
|
||||
func (c *CustomRule) GetFieldID(field string) int64 {
|
||||
if field == "" {
|
||||
return c.GetID()
|
||||
}
|
||||
paths := strings.Split(field, ".")
|
||||
val := c.DynValue
|
||||
for _, path := range paths {
|
||||
var f *Field
|
||||
var ok bool
|
||||
switch v := val.Value().(type) {
|
||||
case *ObjectValue:
|
||||
f, ok = v.GetField(path)
|
||||
case *MapValue:
|
||||
f, ok = v.GetField(path)
|
||||
}
|
||||
if !ok {
|
||||
return c.GetID()
|
||||
}
|
||||
val = f.Ref
|
||||
}
|
||||
return val.ID
|
||||
}
|
@ -15,32 +15,14 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/google/cel-go/cel"
|
||||
"k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
|
||||
)
|
||||
|
||||
// Resolver declares methods to find policy templates and related configuration objects.
|
||||
type Resolver interface {
|
||||
// FindEnv returns an Env object by its fully-qualified name, if present.
|
||||
FindEnv(name string) (*Env, bool)
|
||||
|
||||
// FindExprEnv returns a CEL expression environment by its fully-qualified name, if present.
|
||||
//
|
||||
// Note, the CEL expression environment name corresponds with the model Environment name;
|
||||
// however, the expression environment may inherit configuration via the CEL env.Extend method.
|
||||
FindExprEnv(name string) (*cel.Env, bool)
|
||||
|
||||
// FindSchema returns an Open API Schema instance by name, if present.
|
||||
//
|
||||
// Schema names start with a `#` sign as this method is only used to resolve references to
|
||||
// relative schema elements within `$ref` schema nodes.
|
||||
FindSchema(name string) (*OpenAPISchema, bool)
|
||||
|
||||
// FindTemplate returns a Template by its fully-qualified name, if present.
|
||||
FindTemplate(name string) (*Template, bool)
|
||||
|
||||
// FindType returns a DeclType instance corresponding to the given fully-qualified name, if
|
||||
// present.
|
||||
FindType(name string) (*DeclType, bool)
|
||||
@ -50,25 +32,15 @@ type Resolver interface {
|
||||
// from a base cel.Env expression environment.
|
||||
func NewRegistry(stdExprEnv *cel.Env) *Registry {
|
||||
return &Registry{
|
||||
envs: map[string]*Env{},
|
||||
exprEnvs: map[string]*cel.Env{"": stdExprEnv},
|
||||
schemas: map[string]*OpenAPISchema{
|
||||
"#anySchema": AnySchema,
|
||||
"#envSchema": envSchema,
|
||||
"#instanceSchema": instanceSchema,
|
||||
"#openAPISchema": schemaDef,
|
||||
"#templateSchema": templateSchema,
|
||||
},
|
||||
templates: map[string]*Template{},
|
||||
schemas: map[string]*schema.Structural{},
|
||||
types: map[string]*DeclType{
|
||||
AnyType.TypeName(): AnyType,
|
||||
BoolType.TypeName(): BoolType,
|
||||
BytesType.TypeName(): BytesType,
|
||||
DoubleType.TypeName(): DoubleType,
|
||||
DurationType.TypeName(): DurationType,
|
||||
IntType.TypeName(): IntType,
|
||||
NullType.TypeName(): NullType,
|
||||
PlainTextType.TypeName(): PlainTextType,
|
||||
StringType.TypeName(): StringType,
|
||||
TimestampType.TypeName(): TimestampType,
|
||||
UintType.TypeName(): UintType,
|
||||
@ -82,44 +54,10 @@ func NewRegistry(stdExprEnv *cel.Env) *Registry {
|
||||
//
|
||||
// Registry instances are concurrency-safe.
|
||||
type Registry struct {
|
||||
rwMux sync.RWMutex
|
||||
envs map[string]*Env
|
||||
exprEnvs map[string]*cel.Env
|
||||
schemas map[string]*OpenAPISchema
|
||||
templates map[string]*Template
|
||||
types map[string]*DeclType
|
||||
}
|
||||
|
||||
// FindEnv implements the Resolver interface method.
|
||||
func (r *Registry) FindEnv(name string) (*Env, bool) {
|
||||
r.rwMux.RLock()
|
||||
defer r.rwMux.RUnlock()
|
||||
env, found := r.envs[name]
|
||||
return env, found
|
||||
}
|
||||
|
||||
// FindExprEnv implements the Resolver interface method.
|
||||
func (r *Registry) FindExprEnv(name string) (*cel.Env, bool) {
|
||||
r.rwMux.RLock()
|
||||
defer r.rwMux.RUnlock()
|
||||
exprEnv, found := r.exprEnvs[name]
|
||||
return exprEnv, found
|
||||
}
|
||||
|
||||
// FindSchema implements the Resolver interface method.
|
||||
func (r *Registry) FindSchema(name string) (*OpenAPISchema, bool) {
|
||||
r.rwMux.RLock()
|
||||
defer r.rwMux.RUnlock()
|
||||
schema, found := r.schemas[name]
|
||||
return schema, found
|
||||
}
|
||||
|
||||
// FindTemplate implements the Resolver interface method.
|
||||
func (r *Registry) FindTemplate(name string) (*Template, bool) {
|
||||
r.rwMux.RLock()
|
||||
defer r.rwMux.RUnlock()
|
||||
tmpl, found := r.templates[name]
|
||||
return tmpl, found
|
||||
rwMux sync.RWMutex
|
||||
exprEnvs map[string]*cel.Env
|
||||
schemas map[string]*schema.Structural
|
||||
types map[string]*DeclType
|
||||
}
|
||||
|
||||
// FindType implements the Resolver interface method.
|
||||
@ -133,53 +71,6 @@ func (r *Registry) FindType(name string) (*DeclType, bool) {
|
||||
return typ, found
|
||||
}
|
||||
|
||||
// SetEnv registers an environment description by fully qualified name.
|
||||
func (r *Registry) SetEnv(name string, env *Env) error {
|
||||
r.rwMux.Lock()
|
||||
defer r.rwMux.Unlock()
|
||||
// Cleanup environment related artifacts when the env is reset.
|
||||
priorEnv, found := r.envs[name]
|
||||
if found {
|
||||
for typeName := range priorEnv.Types {
|
||||
delete(r.types, typeName)
|
||||
}
|
||||
}
|
||||
// Configure the new environment.
|
||||
baseExprEnv, found := r.exprEnvs[""]
|
||||
if !found {
|
||||
return fmt.Errorf("missing default expression environment")
|
||||
}
|
||||
exprEnv, err := baseExprEnv.Extend(env.ExprEnvOptions()...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.exprEnvs[name] = exprEnv
|
||||
r.envs[name] = env
|
||||
for typeName, typ := range env.Types {
|
||||
r.types[typeName] = typ
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetSchema registers an OpenAPISchema fragment by its relative name so that it may be referenced
|
||||
// as a reusable schema unit within other OpenAPISchema instances.
|
||||
//
|
||||
// Name format: '#<simpleName>'.
|
||||
func (r *Registry) SetSchema(name string, schema *OpenAPISchema) error {
|
||||
r.rwMux.Lock()
|
||||
defer r.rwMux.Unlock()
|
||||
r.schemas[name] = schema
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetTemplate registers a template by its fully qualified name.
|
||||
func (r *Registry) SetTemplate(name string, tmpl *Template) error {
|
||||
r.rwMux.Lock()
|
||||
defer r.rwMux.Unlock()
|
||||
r.templates[name] = tmpl
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetType registers a DeclType descriptor by its fully qualified name.
|
||||
func (r *Registry) SetType(name string, declType *DeclType) error {
|
||||
r.rwMux.Lock()
|
||||
|
@ -15,437 +15,151 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
"k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
|
||||
)
|
||||
|
||||
// NewOpenAPISchema returns an empty instance of an OpenAPISchema object.
|
||||
func NewOpenAPISchema() *OpenAPISchema {
|
||||
return &OpenAPISchema{
|
||||
Enum: []interface{}{},
|
||||
Metadata: map[string]string{},
|
||||
Properties: map[string]*OpenAPISchema{},
|
||||
Required: []string{},
|
||||
}
|
||||
}
|
||||
|
||||
// OpenAPISchema declares a struct capable of representing a subset of Open API Schemas
|
||||
// supported by Kubernetes which can also be specified within Protocol Buffers.
|
||||
//
|
||||
// There are a handful of notable differences:
|
||||
// - The validating constructs `allOf`, `anyOf`, `oneOf`, `not`, and type-related restrictsion are
|
||||
// not supported as they can be better validated in the template 'validator' block.
|
||||
// - The $ref field supports references to other schema definitions, but such aliases
|
||||
// should be removed before being serialized.
|
||||
// - The `additionalProperties` and `properties` fields are not currently mutually exclusive as is
|
||||
// the case for Kubernetes.
|
||||
//
|
||||
// See: https://kubernetes.io/docs/tasks/access-kubernetes-api/custom-resources/custom-resource-definitions/#validation
|
||||
type OpenAPISchema struct {
|
||||
Title string `yaml:"title,omitempty"`
|
||||
Description string `yaml:"description,omitempty"`
|
||||
Type string `yaml:"type,omitempty"`
|
||||
TypeParam string `yaml:"type_param,omitempty"`
|
||||
TypeRef string `yaml:"$ref,omitempty"`
|
||||
DefaultValue interface{} `yaml:"default,omitempty"`
|
||||
Enum []interface{} `yaml:"enum,omitempty"`
|
||||
Format string `yaml:"format,omitempty"`
|
||||
Items *OpenAPISchema `yaml:"items,omitempty"`
|
||||
Metadata map[string]string `yaml:"metadata,omitempty"`
|
||||
Required []string `yaml:"required,omitempty"`
|
||||
Properties map[string]*OpenAPISchema `yaml:"properties,omitempty"`
|
||||
AdditionalProperties *OpenAPISchema `yaml:"additionalProperties,omitempty"`
|
||||
}
|
||||
|
||||
// DeclTypes constructs a top-down set of DeclType instances whose name is derived from the root
|
||||
// SchemaDeclTypes constructs a top-down set of DeclType instances whose name is derived from the root
|
||||
// type name provided on the call, if not set to a custom type.
|
||||
func (s *OpenAPISchema) DeclTypes(maybeRootType string) (*DeclType, map[string]*DeclType) {
|
||||
root := s.DeclType().MaybeAssignTypeName(maybeRootType)
|
||||
func SchemaDeclTypes(s *schema.Structural, maybeRootType string) (*DeclType, map[string]*DeclType) {
|
||||
root := SchemaDeclType(s).MaybeAssignTypeName(maybeRootType)
|
||||
types := FieldTypeMap(maybeRootType, root)
|
||||
return root, types
|
||||
}
|
||||
|
||||
// DeclType returns the CEL Policy Templates type name associated with the schema element.
|
||||
func (s *OpenAPISchema) DeclType() *DeclType {
|
||||
if s.TypeParam != "" {
|
||||
return NewTypeParam(s.TypeParam)
|
||||
// SchemaDeclType returns the cel type name associated with the schema element.
|
||||
func SchemaDeclType(s *schema.Structural) *DeclType {
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
if s.XIntOrString {
|
||||
// schemas using this extension are not required to have a type, so they must be handled before type lookup
|
||||
return intOrStringType
|
||||
}
|
||||
declType, found := openAPISchemaTypes[s.Type]
|
||||
if !found {
|
||||
return NewObjectTypeRef("*error*")
|
||||
}
|
||||
switch declType.TypeName() {
|
||||
case ListType.TypeName():
|
||||
return NewListType(s.Items.DeclType())
|
||||
case MapType.TypeName():
|
||||
if s.AdditionalProperties != nil {
|
||||
return NewMapType(StringType, s.AdditionalProperties.DeclType())
|
||||
}
|
||||
fields := make(map[string]*DeclField, len(s.Properties))
|
||||
required := make(map[string]struct{}, len(s.Required))
|
||||
for _, name := range s.Required {
|
||||
required[name] = struct{}{}
|
||||
}
|
||||
for name, prop := range s.Properties {
|
||||
_, isReq := required[name]
|
||||
fields[name] = &DeclField{
|
||||
Name: name,
|
||||
Required: isReq,
|
||||
Type: prop.DeclType(),
|
||||
defaultValue: prop.DefaultValue,
|
||||
enumValues: prop.Enum,
|
||||
}
|
||||
}
|
||||
customType, hasCustomType := s.Metadata["custom_type"]
|
||||
if !hasCustomType {
|
||||
return NewObjectType("object", fields)
|
||||
}
|
||||
return NewObjectType(customType, fields)
|
||||
case StringType.TypeName():
|
||||
switch s.Format {
|
||||
case "byte", "binary":
|
||||
return BytesType
|
||||
case "google-duration":
|
||||
return DurationType
|
||||
case "date", "date-time", "google-datetime":
|
||||
return TimestampType
|
||||
case "int64":
|
||||
return IntType
|
||||
case "uint64":
|
||||
return UintType
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// We ignore XPreserveUnknownFields since we don't support validation rules on
|
||||
// data that we don't have schema information for.
|
||||
|
||||
if s.XEmbeddedResource {
|
||||
// 'apiVersion', 'kind', 'metadata.name' and 'metadata.generateName' are always accessible
|
||||
// to validation rules since this part of the schema is well known and validated when CRDs
|
||||
// are created and updated.
|
||||
s = WithTypeAndObjectMeta(s)
|
||||
}
|
||||
|
||||
switch declType.TypeName() {
|
||||
case ListType.TypeName():
|
||||
return NewListType(SchemaDeclType(s.Items))
|
||||
case MapType.TypeName():
|
||||
if s.AdditionalProperties != nil && s.AdditionalProperties.Structural != nil {
|
||||
return NewMapType(StringType, SchemaDeclType(s.AdditionalProperties.Structural))
|
||||
}
|
||||
fields := make(map[string]*DeclField, len(s.Properties))
|
||||
|
||||
required := map[string]bool{}
|
||||
if s.ValueValidation != nil {
|
||||
for _, f := range s.ValueValidation.Required {
|
||||
required[f] = true
|
||||
}
|
||||
}
|
||||
for name, prop := range s.Properties {
|
||||
var enumValues []interface{}
|
||||
if prop.ValueValidation != nil {
|
||||
for _, e := range prop.ValueValidation.Enum {
|
||||
enumValues = append(enumValues, e.Object)
|
||||
}
|
||||
}
|
||||
if fieldType := SchemaDeclType(&prop); fieldType != nil {
|
||||
fields[Escape(name)] = &DeclField{
|
||||
Name: Escape(name),
|
||||
Required: required[name],
|
||||
Type: fieldType,
|
||||
defaultValue: prop.Default.Object,
|
||||
enumValues: enumValues, // Enum values are represented as strings in CEL
|
||||
}
|
||||
}
|
||||
}
|
||||
return NewObjectType("object", fields)
|
||||
case StringType.TypeName():
|
||||
if s.ValueValidation != nil {
|
||||
switch s.ValueValidation.Format {
|
||||
case "byte":
|
||||
return StringType // OpenAPIv3 byte format represents base64 encoded string
|
||||
case "binary":
|
||||
return BytesType
|
||||
case "duration":
|
||||
return DurationType
|
||||
case "date", "date-time":
|
||||
return TimestampType
|
||||
}
|
||||
}
|
||||
}
|
||||
return declType
|
||||
}
|
||||
|
||||
// FindProperty returns the Open API Schema type for the given property name.
|
||||
//
|
||||
// A property may either be explicitly defined in a `properties` map or implicitly defined in an
|
||||
// `additionalProperties` block.
|
||||
func (s *OpenAPISchema) FindProperty(name string) (*OpenAPISchema, bool) {
|
||||
if s.DeclType() == AnyType {
|
||||
return s, true
|
||||
}
|
||||
if s.Properties != nil {
|
||||
prop, found := s.Properties[name]
|
||||
if found {
|
||||
return prop, true
|
||||
}
|
||||
}
|
||||
if s.AdditionalProperties != nil {
|
||||
return s.AdditionalProperties, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
var (
|
||||
// SchemaDef defines an Open API Schema definition in terms of an Open API Schema.
|
||||
schemaDef *OpenAPISchema
|
||||
|
||||
// AnySchema indicates that the value may be of any type.
|
||||
AnySchema *OpenAPISchema
|
||||
|
||||
// EnvSchema defines the schema for CEL environments referenced within Policy Templates.
|
||||
envSchema *OpenAPISchema
|
||||
|
||||
// InstanceSchema defines a basic schema for defining Policy Instances where the instance rule
|
||||
// references a TemplateSchema derived from the Instance's template kind.
|
||||
instanceSchema *OpenAPISchema
|
||||
|
||||
// TemplateSchema defines a schema for defining Policy Templates.
|
||||
templateSchema *OpenAPISchema
|
||||
|
||||
openAPISchemaTypes map[string]*DeclType = map[string]*DeclType{
|
||||
openAPISchemaTypes = map[string]*DeclType{
|
||||
"boolean": BoolType,
|
||||
"number": DoubleType,
|
||||
"integer": IntType,
|
||||
"null": NullType,
|
||||
"string": StringType,
|
||||
"google-duration": DurationType,
|
||||
"google-datetime": TimestampType,
|
||||
"date": TimestampType,
|
||||
"date-time": TimestampType,
|
||||
"date": DateType,
|
||||
"array": ListType,
|
||||
"object": MapType,
|
||||
"": AnyType,
|
||||
}
|
||||
|
||||
// intOrStringType represents the x-kubernetes-int-or-string union type in CEL expressions.
|
||||
// In CEL, the type is represented as an object where either the srtVal
|
||||
// or intVal field is set. In CEL, this allows for typesafe expressions like:
|
||||
//
|
||||
// require that the string representation be a percentage:
|
||||
// `has(intOrStringField.strVal) && intOrStringField.strVal.matches(r'(\d+(\.\d+)?%)')`
|
||||
// validate requirements on both the int and string representation:
|
||||
// `has(intOrStringField.intVal) ? intOrStringField.intVal < 5 : double(intOrStringField.strVal.replace('%', '')) < 0.5
|
||||
//
|
||||
intOrStringType = NewObjectType("intOrString", map[string]*DeclField{
|
||||
"strVal": {Name: "strVal", Type: StringType},
|
||||
"intVal": {Name: "intVal", Type: IntType},
|
||||
})
|
||||
)
|
||||
|
||||
const (
|
||||
schemaDefYaml = `
|
||||
type: object
|
||||
properties:
|
||||
$ref:
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
type_param: # prohibited unless used within an environment.
|
||||
type: string
|
||||
format:
|
||||
type: string
|
||||
description:
|
||||
type: string
|
||||
required:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
enum:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
enumDescriptions:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
default: {}
|
||||
items:
|
||||
$ref: "#openAPISchema"
|
||||
properties:
|
||||
type: object
|
||||
additionalProperties:
|
||||
$ref: "#openAPISchema"
|
||||
additionalProperties:
|
||||
$ref: "#openAPISchema"
|
||||
metadata:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: string
|
||||
`
|
||||
|
||||
templateSchemaYaml = `
|
||||
type: object
|
||||
required:
|
||||
- apiVersion
|
||||
- kind
|
||||
- metadata
|
||||
- evaluator
|
||||
properties:
|
||||
apiVersion:
|
||||
type: string
|
||||
kind:
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
required:
|
||||
- name
|
||||
properties:
|
||||
uid:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
namespace:
|
||||
type: string
|
||||
default: "default"
|
||||
etag:
|
||||
type: string
|
||||
labels:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: string
|
||||
pluralName:
|
||||
type: string
|
||||
description:
|
||||
type: string
|
||||
schema:
|
||||
$ref: "#openAPISchema"
|
||||
validator:
|
||||
type: object
|
||||
required:
|
||||
- productions
|
||||
properties:
|
||||
description:
|
||||
type: string
|
||||
environment:
|
||||
type: string
|
||||
terms:
|
||||
type: object
|
||||
additionalProperties: {}
|
||||
productions:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
required:
|
||||
- message
|
||||
properties:
|
||||
match:
|
||||
type: string
|
||||
default: true
|
||||
field:
|
||||
type: string
|
||||
message:
|
||||
type: string
|
||||
details: {}
|
||||
evaluator:
|
||||
type: object
|
||||
required:
|
||||
- productions
|
||||
properties:
|
||||
description:
|
||||
type: string
|
||||
environment:
|
||||
type: string
|
||||
ranges:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
required:
|
||||
- in
|
||||
properties:
|
||||
in:
|
||||
type: string
|
||||
key:
|
||||
type: string
|
||||
index:
|
||||
type: string
|
||||
value:
|
||||
type: string
|
||||
terms:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: string
|
||||
productions:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
match:
|
||||
type: string
|
||||
default: "true"
|
||||
decision:
|
||||
type: string
|
||||
decisionRef:
|
||||
type: string
|
||||
output: {}
|
||||
decisions:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
required:
|
||||
- output
|
||||
properties:
|
||||
decision:
|
||||
type: string
|
||||
decisionRef:
|
||||
type: string
|
||||
output: {}
|
||||
`
|
||||
|
||||
instanceSchemaYaml = `
|
||||
type: object
|
||||
required:
|
||||
- apiVersion
|
||||
- kind
|
||||
- metadata
|
||||
properties:
|
||||
apiVersion:
|
||||
type: string
|
||||
kind:
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: string
|
||||
description:
|
||||
type: string
|
||||
selector:
|
||||
type: object
|
||||
properties:
|
||||
matchLabels:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: string
|
||||
matchExpressions:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
required:
|
||||
- key
|
||||
- operator
|
||||
properties:
|
||||
key:
|
||||
type: string
|
||||
operator:
|
||||
type: string
|
||||
enum: ["DoesNotExist", "Exists", "In", "NotIn"]
|
||||
values:
|
||||
type: array
|
||||
items: {}
|
||||
default: []
|
||||
rule:
|
||||
$ref: "#templateRuleSchema"
|
||||
rules:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#templateRuleSchema"
|
||||
`
|
||||
|
||||
// TODO: support subsetting of built-in functions and macros
|
||||
// TODO: support naming anonymous types within rule schema and making them accessible to
|
||||
// declarations.
|
||||
// TODO: consider supporting custom macros
|
||||
envSchemaYaml = `
|
||||
type: object
|
||||
required:
|
||||
- name
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
container:
|
||||
type: string
|
||||
variables:
|
||||
type: object
|
||||
additionalProperties:
|
||||
$ref: "#openAPISchema"
|
||||
functions:
|
||||
type: object
|
||||
properties:
|
||||
extensions:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: object # function name
|
||||
additionalProperties:
|
||||
type: object # overload name
|
||||
required:
|
||||
- return
|
||||
properties:
|
||||
free_function:
|
||||
type: boolean
|
||||
args:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#openAPISchema"
|
||||
return:
|
||||
$ref: "#openAPISchema"
|
||||
`
|
||||
)
|
||||
|
||||
func init() {
|
||||
AnySchema = NewOpenAPISchema()
|
||||
|
||||
instanceSchema = NewOpenAPISchema()
|
||||
in := strings.ReplaceAll(instanceSchemaYaml, "\t", " ")
|
||||
err := yaml.Unmarshal([]byte(in), instanceSchema)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
// WithTypeAndObjectMeta ensures the kind, apiVersion and
|
||||
// metadata.name and metadata.generateName properties are specified, making a shallow copy of the provided schema if needed.
|
||||
func WithTypeAndObjectMeta(s *schema.Structural) *schema.Structural {
|
||||
if s.Properties != nil &&
|
||||
s.Properties["kind"].Type == "string" &&
|
||||
s.Properties["apiVersion"].Type == "string" &&
|
||||
s.Properties["metadata"].Type == "object" &&
|
||||
s.Properties["metadata"].Properties != nil &&
|
||||
s.Properties["metadata"].Properties["name"].Type == "string" &&
|
||||
s.Properties["metadata"].Properties["generateName"].Type == "string" {
|
||||
return s
|
||||
}
|
||||
envSchema = NewOpenAPISchema()
|
||||
in = strings.ReplaceAll(envSchemaYaml, "\t", " ")
|
||||
err = yaml.Unmarshal([]byte(in), envSchema)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
result := &schema.Structural{
|
||||
Generic: s.Generic,
|
||||
Extensions: s.Extensions,
|
||||
ValueValidation: s.ValueValidation,
|
||||
}
|
||||
schemaDef = NewOpenAPISchema()
|
||||
in = strings.ReplaceAll(schemaDefYaml, "\t", " ")
|
||||
err = yaml.Unmarshal([]byte(in), schemaDef)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
props := make(map[string]schema.Structural, len(s.Properties))
|
||||
for k, prop := range s.Properties {
|
||||
props[k] = prop
|
||||
}
|
||||
templateSchema = NewOpenAPISchema()
|
||||
in = strings.ReplaceAll(templateSchemaYaml, "\t", " ")
|
||||
err = yaml.Unmarshal([]byte(in), templateSchema)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
stringType := schema.Structural{Generic: schema.Generic{Type: "string"}}
|
||||
props["kind"] = stringType
|
||||
props["apiVersion"] = stringType
|
||||
props["metadata"] = schema.Structural{
|
||||
Generic: schema.Generic{Type: "object"},
|
||||
Properties: map[string]schema.Structural{
|
||||
"name": stringType,
|
||||
"generateName": stringType,
|
||||
},
|
||||
}
|
||||
result.Properties = props
|
||||
|
||||
return result
|
||||
}
|
@ -18,43 +18,43 @@ import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/google/cel-go/checker/decls"
|
||||
"github.com/google/cel-go/common/types"
|
||||
|
||||
"google.golang.org/protobuf/proto"
|
||||
"k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
|
||||
)
|
||||
|
||||
func TestSchemaDeclType(t *testing.T) {
|
||||
ts := testSchema()
|
||||
cust := ts.DeclType()
|
||||
if cust.TypeName() != "CustomObject" {
|
||||
t.Errorf("incorrect type name, got %v, wanted CustomObject", cust.TypeName())
|
||||
cust := SchemaDeclType(ts)
|
||||
if cust.TypeName() != "object" {
|
||||
t.Errorf("incorrect type name, got %v, wanted object", cust.TypeName())
|
||||
}
|
||||
if len(cust.Fields) != 4 {
|
||||
t.Errorf("incorrect number of fields, got %d, wanted 4", len(cust.Fields))
|
||||
}
|
||||
for _, f := range cust.Fields {
|
||||
prop, found := ts.FindProperty(f.Name)
|
||||
prop, found := ts.Properties[f.Name]
|
||||
if !found {
|
||||
t.Errorf("type field not found in schema, field: %s", f.Name)
|
||||
}
|
||||
fdv := f.DefaultValue()
|
||||
if prop.DefaultValue != nil {
|
||||
pdv := types.DefaultTypeAdapter.NativeToValue(prop.DefaultValue)
|
||||
if prop.Default.Object != nil {
|
||||
pdv := types.DefaultTypeAdapter.NativeToValue(prop.Default.Object)
|
||||
if !reflect.DeepEqual(fdv, pdv) {
|
||||
t.Errorf("field and schema do not agree on default value, field: %s", f.Name)
|
||||
t.Errorf("field and schema do not agree on default value for field: %s, field value: %v, schema default: %v", f.Name, fdv, pdv)
|
||||
}
|
||||
}
|
||||
if prop.Enum == nil && len(f.EnumValues()) != 0 {
|
||||
if (prop.ValueValidation == nil || len(prop.ValueValidation.Enum) == 0) && len(f.EnumValues()) != 0 {
|
||||
t.Errorf("field had more enum values than the property. field: %s", f.Name)
|
||||
}
|
||||
if prop.Enum != nil {
|
||||
if prop.ValueValidation != nil {
|
||||
fevs := f.EnumValues()
|
||||
for _, fev := range fevs {
|
||||
found := false
|
||||
for _, pev := range prop.Enum {
|
||||
pev = types.DefaultTypeAdapter.NativeToValue(pev)
|
||||
if reflect.DeepEqual(fev, pev) {
|
||||
for _, pev := range prop.ValueValidation.Enum {
|
||||
celpev := types.DefaultTypeAdapter.NativeToValue(pev.Object)
|
||||
if reflect.DeepEqual(fev, celpev) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
@ -67,27 +67,28 @@ func TestSchemaDeclType(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, name := range ts.Required {
|
||||
df, found := cust.FindField(name)
|
||||
if !found {
|
||||
t.Errorf("custom type missing required field. field=%s", name)
|
||||
}
|
||||
if !df.Required {
|
||||
t.Errorf("field marked as required in schema, but optional in type. field=%s", df.Name)
|
||||
if ts.ValueValidation != nil {
|
||||
for _, name := range ts.ValueValidation.Required {
|
||||
df, found := cust.FindField(name)
|
||||
if !found {
|
||||
t.Errorf("custom type missing required field. field=%s", name)
|
||||
}
|
||||
if !df.Required {
|
||||
t.Errorf("field marked as required in schema, but optional in type. field=%s", df.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSchemaDeclTypes(t *testing.T) {
|
||||
ts := testSchema()
|
||||
cust, typeMap := ts.DeclTypes("mock_template")
|
||||
cust, typeMap := SchemaDeclTypes(ts, "CustomObject")
|
||||
nested, _ := cust.FindField("nested")
|
||||
metadata, _ := cust.FindField("metadata")
|
||||
metadataElem := metadata.Type.ElemType
|
||||
expectedObjTypeMap := map[string]*DeclType{
|
||||
"CustomObject": cust,
|
||||
"CustomObject.nested": nested.Type,
|
||||
"CustomObject.metadata.@elem": metadataElem,
|
||||
"CustomObject": cust,
|
||||
"CustomObject.nested": nested.Type,
|
||||
"CustomObject.metadata": metadata.Type,
|
||||
}
|
||||
objTypeMap := map[string]*DeclType{}
|
||||
for name, t := range typeMap {
|
||||
@ -96,7 +97,7 @@ func TestSchemaDeclTypes(t *testing.T) {
|
||||
}
|
||||
}
|
||||
if len(objTypeMap) != len(expectedObjTypeMap) {
|
||||
t.Errorf("got different type set. got=%v, wanted=%v", typeMap, expectedObjTypeMap)
|
||||
t.Errorf("got different type set. got=%v, wanted=%v", objTypeMap, expectedObjTypeMap)
|
||||
}
|
||||
for exp, expType := range expectedObjTypeMap {
|
||||
actType, found := objTypeMap[exp]
|
||||
@ -108,17 +109,9 @@ func TestSchemaDeclTypes(t *testing.T) {
|
||||
t.Errorf("incompatible CEL types. got=%v, wanted=%v", actType.ExprType(), expType.ExprType())
|
||||
}
|
||||
}
|
||||
|
||||
metaExprType := metadata.Type.ExprType()
|
||||
expectedMetaExprType := decls.NewMapType(
|
||||
decls.String,
|
||||
decls.NewObjectType("CustomObject.metadata.@elem"))
|
||||
if !proto.Equal(expectedMetaExprType, metaExprType) {
|
||||
t.Errorf("got metadata CEL type %v, wanted %v", metaExprType, expectedMetaExprType)
|
||||
}
|
||||
}
|
||||
|
||||
func testSchema() *OpenAPISchema {
|
||||
func testSchema() *schema.Structural {
|
||||
// Manual construction of a schema with the following definition:
|
||||
//
|
||||
// schema:
|
||||
@ -160,44 +153,86 @@ func testSchema() *OpenAPISchema {
|
||||
// format: int64
|
||||
// default: 1
|
||||
// enum: [1,2,3]
|
||||
nameField := NewOpenAPISchema()
|
||||
nameField.Type = "string"
|
||||
valueField := NewOpenAPISchema()
|
||||
valueField.Type = "integer"
|
||||
valueField.Format = "int64"
|
||||
valueField.DefaultValue = int64(1)
|
||||
valueField.Enum = []interface{}{int64(1), int64(2), int64(3)}
|
||||
nestedObjField := NewOpenAPISchema()
|
||||
nestedObjField.Type = "object"
|
||||
nestedObjField.Properties["subname"] = NewOpenAPISchema()
|
||||
nestedObjField.Properties["subname"].Type = "string"
|
||||
nestedObjField.Properties["flags"] = NewOpenAPISchema()
|
||||
nestedObjField.Properties["flags"].Type = "object"
|
||||
nestedObjField.Properties["flags"].AdditionalProperties = NewOpenAPISchema()
|
||||
nestedObjField.Properties["flags"].AdditionalProperties.Type = "boolean"
|
||||
nestedObjField.Properties["dates"] = NewOpenAPISchema()
|
||||
nestedObjField.Properties["dates"].Type = "array"
|
||||
nestedObjField.Properties["dates"].Items = NewOpenAPISchema()
|
||||
nestedObjField.Properties["dates"].Items.Type = "string"
|
||||
nestedObjField.Properties["dates"].Items.Format = "date-time"
|
||||
metadataKeyValue := NewOpenAPISchema()
|
||||
metadataKeyValue.Type = "object"
|
||||
metadataKeyValue.Properties["key"] = NewOpenAPISchema()
|
||||
metadataKeyValue.Properties["key"].Type = "string"
|
||||
metadataKeyValue.Properties["values"] = NewOpenAPISchema()
|
||||
metadataKeyValue.Properties["values"].Type = "array"
|
||||
metadataKeyValue.Properties["values"].Items = NewOpenAPISchema()
|
||||
metadataKeyValue.Properties["values"].Items.Type = "string"
|
||||
metadataObjField := NewOpenAPISchema()
|
||||
metadataObjField.Type = "object"
|
||||
metadataObjField.AdditionalProperties = metadataKeyValue
|
||||
ts := NewOpenAPISchema()
|
||||
ts.Type = "object"
|
||||
ts.Metadata["custom_type"] = "CustomObject"
|
||||
ts.Required = []string{"name", "value"}
|
||||
ts.Properties["name"] = nameField
|
||||
ts.Properties["value"] = valueField
|
||||
ts.Properties["nested"] = nestedObjField
|
||||
ts.Properties["metadata"] = metadataObjField
|
||||
ts := &schema.Structural{
|
||||
Generic: schema.Generic{
|
||||
Type: "object",
|
||||
},
|
||||
Properties: map[string]schema.Structural{
|
||||
"name": {
|
||||
Generic: schema.Generic{
|
||||
Type: "string",
|
||||
},
|
||||
},
|
||||
"value": {
|
||||
Generic: schema.Generic{
|
||||
Type: "integer",
|
||||
Default: schema.JSON{Object: int64(1)},
|
||||
},
|
||||
ValueValidation: &schema.ValueValidation{
|
||||
Format: "int64",
|
||||
Enum: []schema.JSON{{Object: int64(1)}, {Object: int64(2)}, {Object: int64(3)}},
|
||||
},
|
||||
},
|
||||
"nested": {
|
||||
Generic: schema.Generic{
|
||||
Type: "object",
|
||||
},
|
||||
Properties: map[string]schema.Structural{
|
||||
"subname": {
|
||||
Generic: schema.Generic{
|
||||
Type: "string",
|
||||
},
|
||||
},
|
||||
"flags": {
|
||||
Generic: schema.Generic{
|
||||
Type: "object",
|
||||
AdditionalProperties: &schema.StructuralOrBool{
|
||||
Structural: &schema.Structural{
|
||||
Generic: schema.Generic{
|
||||
Type: "boolean",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"dates": {
|
||||
Generic: schema.Generic{
|
||||
Type: "array",
|
||||
},
|
||||
Items: &schema.Structural{
|
||||
Generic: schema.Generic{
|
||||
Type: "string",
|
||||
},
|
||||
ValueValidation: &schema.ValueValidation{
|
||||
Format: "date-time",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"metadata": {
|
||||
Generic: schema.Generic{
|
||||
Type: "object",
|
||||
},
|
||||
Properties: map[string]schema.Structural{
|
||||
"name": {
|
||||
Generic: schema.Generic{
|
||||
Type: "string",
|
||||
},
|
||||
},
|
||||
"value": {
|
||||
Generic: schema.Generic{
|
||||
Type: "array",
|
||||
},
|
||||
Items: &schema.Structural{
|
||||
Generic: schema.Generic{
|
||||
Type: "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
return ts
|
||||
}
|
||||
|
@ -1,190 +0,0 @@
|
||||
// Copyright 2020 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 model
|
||||
|
||||
import (
|
||||
"github.com/google/cel-go/common"
|
||||
)
|
||||
|
||||
// ByteSource converts a byte sequence and location description to a model.Source.
|
||||
func ByteSource(contents []byte, location string) *Source {
|
||||
return StringSource(string(contents), location)
|
||||
}
|
||||
|
||||
// StringSource converts a string and location description to a model.Source.
|
||||
func StringSource(contents, location string) *Source {
|
||||
return &Source{
|
||||
Source: common.NewStringSource(contents, location),
|
||||
}
|
||||
}
|
||||
|
||||
// Source represents the contents of a single source file.
|
||||
type Source struct {
|
||||
common.Source
|
||||
}
|
||||
|
||||
// Relative produces a RelativeSource object for the content provided at the absolute location
|
||||
// within the parent Source as indicated by the line and column.
|
||||
func (src *Source) Relative(content string, line, col int) *RelativeSource {
|
||||
return &RelativeSource{
|
||||
Source: src.Source,
|
||||
localSrc: common.NewStringSource(content, src.Description()),
|
||||
absLoc: common.NewLocation(line, col),
|
||||
}
|
||||
}
|
||||
|
||||
// RelativeSource represents an embedded source element within a larger source.
|
||||
type RelativeSource struct {
|
||||
common.Source
|
||||
localSrc common.Source
|
||||
absLoc common.Location
|
||||
}
|
||||
|
||||
// AbsoluteLocation returns the location within the parent Source where the RelativeSource starts.
|
||||
func (rel *RelativeSource) AbsoluteLocation() common.Location {
|
||||
return rel.absLoc
|
||||
}
|
||||
|
||||
// Content returns the embedded source snippet.
|
||||
func (rel *RelativeSource) Content() string {
|
||||
return rel.localSrc.Content()
|
||||
}
|
||||
|
||||
// OffsetLocation returns the absolute location given the relative offset, if found.
|
||||
func (rel *RelativeSource) OffsetLocation(offset int32) (common.Location, bool) {
|
||||
absOffset, found := rel.Source.LocationOffset(rel.absLoc)
|
||||
if !found {
|
||||
return common.NoLocation, false
|
||||
}
|
||||
return rel.Source.OffsetLocation(absOffset + offset)
|
||||
}
|
||||
|
||||
// NewLocation creates an absolute common.Location based on a local line, column
|
||||
// position from a relative source.
|
||||
func (rel *RelativeSource) NewLocation(line, col int) common.Location {
|
||||
localLoc := common.NewLocation(line, col)
|
||||
relOffset, found := rel.localSrc.LocationOffset(localLoc)
|
||||
if !found {
|
||||
return common.NoLocation
|
||||
}
|
||||
offset, _ := rel.Source.LocationOffset(rel.absLoc)
|
||||
absLoc, _ := rel.Source.OffsetLocation(offset + relOffset)
|
||||
return absLoc
|
||||
}
|
||||
|
||||
// NewSourceInfo creates SourceInfo metadata from a Source object.
|
||||
func NewSourceInfo(src common.Source) *SourceInfo {
|
||||
return &SourceInfo{
|
||||
Comments: make(map[int64][]*Comment),
|
||||
LineOffsets: src.LineOffsets(),
|
||||
Description: src.Description(),
|
||||
Offsets: make(map[int64]int32),
|
||||
}
|
||||
}
|
||||
|
||||
// SourceInfo contains metadata about the Source such as comments, line positions, and source
|
||||
// element offsets.
|
||||
type SourceInfo struct {
|
||||
// Comments mapped by source element id to a comment set.
|
||||
Comments map[int64][]*Comment
|
||||
|
||||
// LineOffsets contains the list of character offsets where newlines occur in the source.
|
||||
LineOffsets []int32
|
||||
|
||||
// Description indicates something about the source, such as its file name.
|
||||
Description string
|
||||
|
||||
// Offsets map from source element id to the character offset where the source element starts.
|
||||
Offsets map[int64]int32
|
||||
}
|
||||
|
||||
// SourceMetadata enables the lookup for expression source metadata by expression id.
|
||||
type SourceMetadata interface {
|
||||
// CommentsByID returns the set of comments associated with the expression id, if present.
|
||||
CommentsByID(int64) ([]*Comment, bool)
|
||||
|
||||
// LocationByID returns the CEL common.Location of the expression id, if present.
|
||||
LocationByID(int64) (common.Location, bool)
|
||||
}
|
||||
|
||||
// CommentsByID returns the set of comments by expression id, if present.
|
||||
func (info *SourceInfo) CommentsByID(id int64) ([]*Comment, bool) {
|
||||
comments, found := info.Comments[id]
|
||||
return comments, found
|
||||
}
|
||||
|
||||
// LocationByID returns the line and column location of source node by its id.
|
||||
func (info *SourceInfo) LocationByID(id int64) (common.Location, bool) {
|
||||
charOff, found := info.Offsets[id]
|
||||
if !found {
|
||||
return common.NoLocation, false
|
||||
}
|
||||
ln, lnOff := info.findLine(charOff)
|
||||
return common.NewLocation(int(ln), int(charOff-lnOff)), true
|
||||
}
|
||||
|
||||
func (info *SourceInfo) findLine(characterOffset int32) (int32, int32) {
|
||||
var line int32 = 1
|
||||
for _, lineOffset := range info.LineOffsets {
|
||||
if lineOffset > characterOffset {
|
||||
break
|
||||
} else {
|
||||
line++
|
||||
}
|
||||
}
|
||||
if line == 1 {
|
||||
return line, 0
|
||||
}
|
||||
return line, info.LineOffsets[line-2]
|
||||
}
|
||||
|
||||
// CommentStyle type used to indicate where a comment occurs.
|
||||
type CommentStyle int
|
||||
|
||||
const (
|
||||
// HeadComment indicates that the comment is defined in the lines preceding the source element.
|
||||
HeadComment CommentStyle = iota + 1
|
||||
|
||||
// LineComment indicates that the comment occurs on the same line after the source element.
|
||||
LineComment
|
||||
|
||||
// FootComment indicates that the comment occurs after the source element with at least one
|
||||
// blank line before the next source element.
|
||||
FootComment
|
||||
)
|
||||
|
||||
// NewHeadComment creates a new HeadComment from the text.
|
||||
func NewHeadComment(txt string) *Comment {
|
||||
return &Comment{Text: txt, Style: HeadComment}
|
||||
}
|
||||
|
||||
// NewLineComment creates a new LineComment from the text.
|
||||
func NewLineComment(txt string) *Comment {
|
||||
return &Comment{Text: txt, Style: LineComment}
|
||||
}
|
||||
|
||||
// NewFootComment creates a new FootComment from the text.
|
||||
func NewFootComment(txt string) *Comment {
|
||||
return &Comment{Text: txt, Style: FootComment}
|
||||
}
|
||||
|
||||
// Comment represents a comment within source.
|
||||
type Comment struct {
|
||||
// Text contains the comment text.
|
||||
Text string
|
||||
|
||||
// Style indicates where the comment appears relative to a source element.
|
||||
Style CommentStyle
|
||||
}
|
@ -1,159 +0,0 @@
|
||||
// Copyright 2020 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 model
|
||||
|
||||
import (
|
||||
"github.com/google/cel-go/cel"
|
||||
|
||||
exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
|
||||
)
|
||||
|
||||
// NewTemplate produces an empty policy Template instance.
|
||||
func NewTemplate(info SourceMetadata) *Template {
|
||||
return &Template{
|
||||
Metadata: NewTemplateMetadata(),
|
||||
Evaluator: NewEvaluator(),
|
||||
Meta: info,
|
||||
}
|
||||
}
|
||||
|
||||
// Template represents the compiled and type-checked policy template.
|
||||
type Template struct {
|
||||
APIVersion string
|
||||
Kind string
|
||||
Metadata *TemplateMetadata
|
||||
Description string
|
||||
RuleTypes *RuleTypes
|
||||
Validator *Evaluator
|
||||
Evaluator *Evaluator
|
||||
Meta SourceMetadata
|
||||
}
|
||||
|
||||
// EvaluatorDecisionCount returns the number of decisions which can be produced by the template
|
||||
// evaluator production rules.
|
||||
func (t *Template) EvaluatorDecisionCount() int {
|
||||
return t.Evaluator.DecisionCount()
|
||||
}
|
||||
|
||||
// MetadataMap returns the metadata name to value map, which can be used in evaluation.
|
||||
// Only "name" field is supported for now.
|
||||
func (t *Template) MetadataMap() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"name": t.Metadata.Name,
|
||||
}
|
||||
}
|
||||
|
||||
// NewTemplateMetadata returns an empty *TemplateMetadata instance.
|
||||
func NewTemplateMetadata() *TemplateMetadata {
|
||||
return &TemplateMetadata{
|
||||
Properties: make(map[string]string),
|
||||
}
|
||||
}
|
||||
|
||||
// TemplateMetadata contains the top-level information about the Template, including its name and
|
||||
// namespace.
|
||||
type TemplateMetadata struct {
|
||||
UID string
|
||||
Name string
|
||||
Namespace string
|
||||
|
||||
// PluralMame is the plural form of the template name to use when managing a collection of
|
||||
// template instances.
|
||||
PluralName string
|
||||
|
||||
// Properties contains an optional set of key-value information which external applications
|
||||
// might find useful.
|
||||
Properties map[string]string
|
||||
}
|
||||
|
||||
// NewEvaluator returns an empty instance of a Template Evaluator.
|
||||
func NewEvaluator() *Evaluator {
|
||||
return &Evaluator{
|
||||
Terms: []*Term{},
|
||||
Productions: []*Production{},
|
||||
}
|
||||
}
|
||||
|
||||
// Evaluator contains a set of production rules used to validate policy templates or
|
||||
// evaluate template instances.
|
||||
//
|
||||
// The evaluator may optionally specify a named and versioned Environment as the basis for the
|
||||
// variables and functions exposed to the CEL expressions within the Evaluator, and an optional
|
||||
// set of terms.
|
||||
//
|
||||
// Terms are like template-local variables. Terms may rely on other terms which precede them.
|
||||
// Term order matters, and no cycles are permitted among terms by design and convention.
|
||||
type Evaluator struct {
|
||||
Environment string
|
||||
Ranges []*Range
|
||||
Terms []*Term
|
||||
Productions []*Production
|
||||
}
|
||||
|
||||
// DecisionCount returns the number of possible decisions which could be emitted by this evaluator.
|
||||
func (e *Evaluator) DecisionCount() int {
|
||||
decMap := map[string]struct{}{}
|
||||
for _, p := range e.Productions {
|
||||
for _, d := range p.Decisions {
|
||||
decMap[d.Name] = struct{}{}
|
||||
}
|
||||
}
|
||||
return len(decMap)
|
||||
}
|
||||
|
||||
// Range expresses a looping condition where the key (or index) and value can be extracted from the
|
||||
// range CEL expression.
|
||||
type Range struct {
|
||||
ID int64
|
||||
Key *exprpb.Decl
|
||||
Value *exprpb.Decl
|
||||
Expr *cel.Ast
|
||||
}
|
||||
|
||||
// NewTerm produces a named Term instance associated with a CEL Ast and a list of the input
|
||||
// terms needed to evaluate the Ast successfully.
|
||||
func NewTerm(id int64, name string, expr *cel.Ast) *Term {
|
||||
return &Term{
|
||||
ID: id,
|
||||
Name: name,
|
||||
Expr: expr,
|
||||
}
|
||||
}
|
||||
|
||||
// Term is a template-local variable whose name may shadow names in the Template environment and
|
||||
// which may depend on preceding terms as input.
|
||||
type Term struct {
|
||||
ID int64
|
||||
Name string
|
||||
Expr *cel.Ast
|
||||
}
|
||||
|
||||
// NewProduction returns an empty instance of a Production rule which minimally contains a single
|
||||
// Decision.
|
||||
func NewProduction(id int64, match *cel.Ast) *Production {
|
||||
return &Production{
|
||||
ID: id,
|
||||
Match: match,
|
||||
Decisions: []*Decision{},
|
||||
}
|
||||
}
|
||||
|
||||
// Production describes an match-decision pair where the match, if set, indicates whether the
|
||||
// Decision is applicable, and the decision indicates its name and output value.
|
||||
type Production struct {
|
||||
ID int64
|
||||
Match *cel.Ast
|
||||
Decisions []*Decision
|
||||
}
|
@ -27,6 +27,7 @@ import (
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
|
||||
"k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
|
||||
)
|
||||
|
||||
// NewListType returns a parameterized list type with a specified element type.
|
||||
@ -92,7 +93,7 @@ func newSimpleType(name string, exprType *exprpb.Type, zeroVal ref.Val) *DeclTyp
|
||||
}
|
||||
}
|
||||
|
||||
// DeclType represents the universal type descriptor for Policy Templates.
|
||||
// DeclType represents the universal type descriptor for OpenAPIv3 types.
|
||||
type DeclType struct {
|
||||
fmt.Stringer
|
||||
|
||||
@ -304,7 +305,7 @@ func (f *DeclField) EnumValues() []ref.Val {
|
||||
|
||||
// NewRuleTypes returns an Open API Schema-based type-system which is CEL compatible.
|
||||
func NewRuleTypes(kind string,
|
||||
schema *OpenAPISchema,
|
||||
schema *schema.Structural,
|
||||
res Resolver) (*RuleTypes, error) {
|
||||
// Note, if the schema indicates that it's actually based on another proto
|
||||
// then prefer the proto definition. For expressions in the proto, a new field
|
||||
@ -325,13 +326,13 @@ func NewRuleTypes(kind string,
|
||||
// type-system.
|
||||
type RuleTypes struct {
|
||||
ref.TypeProvider
|
||||
Schema *OpenAPISchema
|
||||
Schema *schema.Structural
|
||||
ruleSchemaDeclTypes *schemaTypeProvider
|
||||
typeAdapter ref.TypeAdapter
|
||||
resolver Resolver
|
||||
}
|
||||
|
||||
// EnvOptions returns a set of cel.EnvOption values which includes the Template's declaration set
|
||||
// EnvOptions returns a set of cel.EnvOption values which includes the declaration set
|
||||
// as well as a custom ref.TypeProvider.
|
||||
//
|
||||
// Note, the standard declaration set includes 'rule' which is defined as the top-level rule-schema
|
||||
@ -358,7 +359,7 @@ func (rt *RuleTypes) EnvOptions(tp ref.TypeProvider) ([]cel.EnvOption, error) {
|
||||
tpType, found := tp.FindType(name)
|
||||
if found && !proto.Equal(tpType, declType.ExprType()) {
|
||||
return nil, fmt.Errorf(
|
||||
"type %s definition differs between CEL environment and template", name)
|
||||
"type %s definition differs between CEL environment and rule", name)
|
||||
}
|
||||
}
|
||||
return []cel.EnvOption{
|
||||
@ -370,7 +371,7 @@ func (rt *RuleTypes) EnvOptions(tp ref.TypeProvider) ([]cel.EnvOption, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
// FindType attempts to resolve the typeName provided from the template's rule-schema, or if not
|
||||
// FindType attempts to resolve the typeName provided from the rule's rule-schema, or if not
|
||||
// from the embedded ref.TypeProvider.
|
||||
//
|
||||
// FindType overrides the default type-finding behavior of the embedded TypeProvider.
|
||||
@ -425,25 +426,10 @@ func (rt *RuleTypes) FindFieldType(typeName, fieldName string) (*ref.FieldType,
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// ConvertToRule transforms an untyped DynValue into a typed object.
|
||||
//
|
||||
// Conversion is done deeply and will traverse the object graph represented by the dyn value.
|
||||
func (rt *RuleTypes) ConvertToRule(dyn *DynValue) Rule {
|
||||
ruleSchemaType := rt.ruleSchemaDeclTypes.root
|
||||
// TODO: handle conversions to protobuf types.
|
||||
dyn = rt.convertToCustomType(dyn, ruleSchemaType)
|
||||
return &CustomRule{DynValue: dyn}
|
||||
}
|
||||
|
||||
// NativeToValue is an implementation of the ref.TypeAdapater interface which supports conversion
|
||||
// of policy template values to CEL ref.Val instances.
|
||||
// of rule values to CEL ref.Val instances.
|
||||
func (rt *RuleTypes) NativeToValue(val interface{}) ref.Val {
|
||||
switch v := val.(type) {
|
||||
case *CustomRule:
|
||||
return v.ExprValue()
|
||||
default:
|
||||
return rt.typeAdapter.NativeToValue(val)
|
||||
}
|
||||
return rt.typeAdapter.NativeToValue(val)
|
||||
}
|
||||
|
||||
// TypeNames returns the list of type names declared within the RuleTypes object.
|
||||
@ -499,8 +485,8 @@ func (rt *RuleTypes) convertToCustomType(dyn *DynValue, declType *DeclType) *Dyn
|
||||
}
|
||||
}
|
||||
|
||||
func newSchemaTypeProvider(kind string, schema *OpenAPISchema) (*schemaTypeProvider, error) {
|
||||
root := schema.DeclType().MaybeAssignTypeName(kind)
|
||||
func newSchemaTypeProvider(kind string, schema *schema.Structural) (*schemaTypeProvider, error) {
|
||||
root := SchemaDeclType(schema).MaybeAssignTypeName(kind)
|
||||
types := FieldTypeMap(kind, root)
|
||||
return &schemaTypeProvider{
|
||||
root: root,
|
||||
@ -515,7 +501,7 @@ type schemaTypeProvider struct {
|
||||
|
||||
var (
|
||||
// AnyType is equivalent to the CEL 'protobuf.Any' type in that the value may have any of the
|
||||
// types supported by CEL Policy Templates.
|
||||
// types supported.
|
||||
AnyType = newSimpleType("any", decls.Any, nil)
|
||||
|
||||
// BoolType is equivalent to the CEL 'bool' type.
|
||||
@ -530,6 +516,9 @@ var (
|
||||
// DurationType is equivalent to the CEL 'duration' type.
|
||||
DurationType = newSimpleType("duration", decls.Duration, types.Duration{Duration: time.Duration(0)})
|
||||
|
||||
// DateType is equivalent to the CEL 'date' type.
|
||||
DateType = newSimpleType("date", decls.Timestamp, types.Timestamp{Time: time.Time{}})
|
||||
|
||||
// DynType is the equivalent of the CEL 'dyn' concept which indicates that the type will be
|
||||
// determined at runtime rather than compile time.
|
||||
DynType = newSimpleType("dyn", decls.Dyn, nil)
|
||||
@ -544,10 +533,6 @@ var (
|
||||
// StringType values may either be string literals or expression strings.
|
||||
StringType = newSimpleType("string", decls.String, types.String(""))
|
||||
|
||||
// PlainTextType is equivalent to the CEL 'string' type, but which has been specifically
|
||||
// designated as a string literal.
|
||||
PlainTextType = newSimpleType("string_lit", decls.String, types.String(""))
|
||||
|
||||
// TimestampType corresponds to the well-known protobuf.Timestamp type supported within CEL.
|
||||
TimestampType = newSimpleType("timestamp", decls.Timestamp, types.Timestamp{Time: time.Time{}})
|
||||
|
||||
|
@ -67,10 +67,11 @@ func TestTypes_MapType(t *testing.T) {
|
||||
func TestTypes_RuleTypesFieldMapping(t *testing.T) {
|
||||
stdEnv, _ := cel.NewEnv()
|
||||
reg := NewRegistry(stdEnv)
|
||||
rt, err := NewRuleTypes("mock_template", testSchema(), reg)
|
||||
rt, err := NewRuleTypes("CustomObject", testSchema(), reg)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
rt.TypeProvider = stdEnv.TypeProvider()
|
||||
nestedFieldType, found := rt.FindFieldType("CustomObject", "nested")
|
||||
if !found {
|
||||
t.Fatal("got field not found for 'CustomObject.nested', wanted found")
|
||||
@ -119,17 +120,17 @@ func TestTypes_RuleTypesFieldMapping(t *testing.T) {
|
||||
mapVal := NewMapValue()
|
||||
mapVal.AddField(name)
|
||||
mapVal.AddField(nested)
|
||||
rule := rt.ConvertToRule(testValue(t, 11, mapVal))
|
||||
if rule == nil {
|
||||
t.Error("map could not be converted to rule")
|
||||
}
|
||||
if rule.GetID() != 11 {
|
||||
t.Errorf("got %d as the rule id, wanted 11", rule.GetID())
|
||||
}
|
||||
ruleVal := rt.NativeToValue(rule)
|
||||
if ruleVal == nil {
|
||||
t.Error("got CEL rule value of nil, wanted non-nil")
|
||||
}
|
||||
//rule := rt.ConvertToRule(testValue(t, 11, mapVal))
|
||||
//if rule == nil {
|
||||
// t.Error("map could not be converted to rule")
|
||||
//}
|
||||
//if rule.GetID() != 11 {
|
||||
// t.Errorf("got %d as the rule id, wanted 11", rule.GetID())
|
||||
//}
|
||||
//ruleVal := rt.NativeToValue(rule)
|
||||
//if ruleVal == nil {
|
||||
// t.Error("got CEL rule value of nil, wanted non-nil")
|
||||
//}
|
||||
|
||||
opts, err := rt.EnvOptions(stdEnv.TypeProvider())
|
||||
if err != nil {
|
||||
|
@ -45,13 +45,6 @@ const (
|
||||
LiteralStyle
|
||||
)
|
||||
|
||||
// ParsedValue represents a top-level object representing either a template or instance value.
|
||||
type ParsedValue struct {
|
||||
ID int64
|
||||
Value *MapValue
|
||||
Meta SourceMetadata
|
||||
}
|
||||
|
||||
// NewEmptyDynValue returns the zero-valued DynValue.
|
||||
func NewEmptyDynValue() *DynValue {
|
||||
// note: 0 is not a valid parse node identifier.
|
||||
@ -158,10 +151,6 @@ func exprValue(value interface{}) (ref.Val, *DeclType, error) {
|
||||
return types.String(v), StringType, nil
|
||||
case uint64:
|
||||
return types.Uint(v), UintType, nil
|
||||
case PlainTextValue:
|
||||
return types.String(string(v)), PlainTextType, nil
|
||||
case *MultilineStringValue:
|
||||
return types.String(v.Value), StringType, nil
|
||||
case time.Duration:
|
||||
return types.Duration{Duration: v}, DurationType, nil
|
||||
case time.Time:
|
||||
|
@ -35,8 +35,6 @@ func TestConvertToType(t *testing.T) {
|
||||
{float64(1.2), types.DoubleType},
|
||||
{int64(-42), types.IntType},
|
||||
{uint64(63), types.UintType},
|
||||
{PlainTextValue("plain text"), types.StringType},
|
||||
{&MultilineStringValue{Value: "multiline", Raw: "multi\nline"}, types.StringType},
|
||||
{time.Duration(300), types.DurationType},
|
||||
{time.Now().UTC(), types.TimestampType},
|
||||
{types.NullValue, types.NullType},
|
||||
@ -63,8 +61,7 @@ func TestConvertToType(t *testing.T) {
|
||||
|
||||
func TestEqual(t *testing.T) {
|
||||
vals := []interface{}{
|
||||
true, []byte("bytes"), float64(1.2), int64(-42), uint64(63), PlainTextValue("plain text"),
|
||||
&MultilineStringValue{Value: "multiline", Raw: "multi\nline"}, time.Duration(300),
|
||||
true, []byte("bytes"), float64(1.2), int64(-42), uint64(63), time.Duration(300),
|
||||
time.Now().UTC(), types.NullValue, NewListValue(), NewMapValue(),
|
||||
NewObjectValue(NewObjectType("TestObject", map[string]*DeclField{})),
|
||||
}
|
||||
@ -327,7 +324,7 @@ func TestObjectValueEqual(t *testing.T) {
|
||||
objType := NewObjectType("Notice", map[string]*DeclField{
|
||||
"name": &DeclField{Name: "name", Type: StringType},
|
||||
"priority": &DeclField{Name: "priority", Type: IntType},
|
||||
"message": &DeclField{Name: "message", Type: PlainTextType, defaultValue: "<eom>"},
|
||||
"message": &DeclField{Name: "message", Type: StringType, defaultValue: "<eom>"},
|
||||
})
|
||||
name := NewField(1, "name")
|
||||
name.Ref = testValue(t, 2, "alert")
|
||||
|
Loading…
Reference in New Issue
Block a user