mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-20 17:16:18 +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
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/google/cel-go/cel"
|
"github.com/google/cel-go/cel"
|
||||||
|
"k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Resolver declares methods to find policy templates and related configuration objects.
|
// Resolver declares methods to find policy templates and related configuration objects.
|
||||||
type Resolver interface {
|
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
|
// FindType returns a DeclType instance corresponding to the given fully-qualified name, if
|
||||||
// present.
|
// present.
|
||||||
FindType(name string) (*DeclType, bool)
|
FindType(name string) (*DeclType, bool)
|
||||||
@ -50,25 +32,15 @@ type Resolver interface {
|
|||||||
// from a base cel.Env expression environment.
|
// from a base cel.Env expression environment.
|
||||||
func NewRegistry(stdExprEnv *cel.Env) *Registry {
|
func NewRegistry(stdExprEnv *cel.Env) *Registry {
|
||||||
return &Registry{
|
return &Registry{
|
||||||
envs: map[string]*Env{},
|
|
||||||
exprEnvs: map[string]*cel.Env{"": stdExprEnv},
|
exprEnvs: map[string]*cel.Env{"": stdExprEnv},
|
||||||
schemas: map[string]*OpenAPISchema{
|
schemas: map[string]*schema.Structural{},
|
||||||
"#anySchema": AnySchema,
|
|
||||||
"#envSchema": envSchema,
|
|
||||||
"#instanceSchema": instanceSchema,
|
|
||||||
"#openAPISchema": schemaDef,
|
|
||||||
"#templateSchema": templateSchema,
|
|
||||||
},
|
|
||||||
templates: map[string]*Template{},
|
|
||||||
types: map[string]*DeclType{
|
types: map[string]*DeclType{
|
||||||
AnyType.TypeName(): AnyType,
|
|
||||||
BoolType.TypeName(): BoolType,
|
BoolType.TypeName(): BoolType,
|
||||||
BytesType.TypeName(): BytesType,
|
BytesType.TypeName(): BytesType,
|
||||||
DoubleType.TypeName(): DoubleType,
|
DoubleType.TypeName(): DoubleType,
|
||||||
DurationType.TypeName(): DurationType,
|
DurationType.TypeName(): DurationType,
|
||||||
IntType.TypeName(): IntType,
|
IntType.TypeName(): IntType,
|
||||||
NullType.TypeName(): NullType,
|
NullType.TypeName(): NullType,
|
||||||
PlainTextType.TypeName(): PlainTextType,
|
|
||||||
StringType.TypeName(): StringType,
|
StringType.TypeName(): StringType,
|
||||||
TimestampType.TypeName(): TimestampType,
|
TimestampType.TypeName(): TimestampType,
|
||||||
UintType.TypeName(): UintType,
|
UintType.TypeName(): UintType,
|
||||||
@ -83,45 +55,11 @@ func NewRegistry(stdExprEnv *cel.Env) *Registry {
|
|||||||
// Registry instances are concurrency-safe.
|
// Registry instances are concurrency-safe.
|
||||||
type Registry struct {
|
type Registry struct {
|
||||||
rwMux sync.RWMutex
|
rwMux sync.RWMutex
|
||||||
envs map[string]*Env
|
|
||||||
exprEnvs map[string]*cel.Env
|
exprEnvs map[string]*cel.Env
|
||||||
schemas map[string]*OpenAPISchema
|
schemas map[string]*schema.Structural
|
||||||
templates map[string]*Template
|
|
||||||
types map[string]*DeclType
|
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// FindType implements the Resolver interface method.
|
// FindType implements the Resolver interface method.
|
||||||
func (r *Registry) FindType(name string) (*DeclType, bool) {
|
func (r *Registry) FindType(name string) (*DeclType, bool) {
|
||||||
r.rwMux.RLock()
|
r.rwMux.RLock()
|
||||||
@ -133,53 +71,6 @@ func (r *Registry) FindType(name string) (*DeclType, bool) {
|
|||||||
return typ, found
|
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.
|
// SetType registers a DeclType descriptor by its fully qualified name.
|
||||||
func (r *Registry) SetType(name string, declType *DeclType) error {
|
func (r *Registry) SetType(name string, declType *DeclType) error {
|
||||||
r.rwMux.Lock()
|
r.rwMux.Lock()
|
||||||
|
@ -15,437 +15,151 @@
|
|||||||
package model
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
"k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
|
||||||
|
|
||||||
"gopkg.in/yaml.v3"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// 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
|
// SchemaDeclTypes constructs a top-down set of DeclType instances whose name is derived from the root
|
||||||
// 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
|
|
||||||
// type name provided on the call, if not set to a custom type.
|
// type name provided on the call, if not set to a custom type.
|
||||||
func (s *OpenAPISchema) DeclTypes(maybeRootType string) (*DeclType, map[string]*DeclType) {
|
func SchemaDeclTypes(s *schema.Structural, maybeRootType string) (*DeclType, map[string]*DeclType) {
|
||||||
root := s.DeclType().MaybeAssignTypeName(maybeRootType)
|
root := SchemaDeclType(s).MaybeAssignTypeName(maybeRootType)
|
||||||
types := FieldTypeMap(maybeRootType, root)
|
types := FieldTypeMap(maybeRootType, root)
|
||||||
return root, types
|
return root, types
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeclType returns the CEL Policy Templates type name associated with the schema element.
|
// SchemaDeclType returns the cel type name associated with the schema element.
|
||||||
func (s *OpenAPISchema) DeclType() *DeclType {
|
func SchemaDeclType(s *schema.Structural) *DeclType {
|
||||||
if s.TypeParam != "" {
|
if s == nil {
|
||||||
return NewTypeParam(s.TypeParam)
|
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]
|
declType, found := openAPISchemaTypes[s.Type]
|
||||||
if !found {
|
if !found {
|
||||||
return NewObjectTypeRef("*error*")
|
return nil
|
||||||
}
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
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 (
|
var (
|
||||||
// SchemaDef defines an Open API Schema definition in terms of an Open API Schema.
|
openAPISchemaTypes = map[string]*DeclType{
|
||||||
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{
|
|
||||||
"boolean": BoolType,
|
"boolean": BoolType,
|
||||||
"number": DoubleType,
|
"number": DoubleType,
|
||||||
"integer": IntType,
|
"integer": IntType,
|
||||||
"null": NullType,
|
"null": NullType,
|
||||||
"string": StringType,
|
"string": StringType,
|
||||||
"google-duration": DurationType,
|
"date": DateType,
|
||||||
"google-datetime": TimestampType,
|
|
||||||
"date": TimestampType,
|
|
||||||
"date-time": TimestampType,
|
|
||||||
"array": ListType,
|
"array": ListType,
|
||||||
"object": MapType,
|
"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 (
|
// WithTypeAndObjectMeta ensures the kind, apiVersion and
|
||||||
schemaDefYaml = `
|
// metadata.name and metadata.generateName properties are specified, making a shallow copy of the provided schema if needed.
|
||||||
type: object
|
func WithTypeAndObjectMeta(s *schema.Structural) *schema.Structural {
|
||||||
properties:
|
if s.Properties != nil &&
|
||||||
$ref:
|
s.Properties["kind"].Type == "string" &&
|
||||||
type: string
|
s.Properties["apiVersion"].Type == "string" &&
|
||||||
type:
|
s.Properties["metadata"].Type == "object" &&
|
||||||
type: string
|
s.Properties["metadata"].Properties != nil &&
|
||||||
type_param: # prohibited unless used within an environment.
|
s.Properties["metadata"].Properties["name"].Type == "string" &&
|
||||||
type: string
|
s.Properties["metadata"].Properties["generateName"].Type == "string" {
|
||||||
format:
|
return s
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
envSchema = NewOpenAPISchema()
|
result := &schema.Structural{
|
||||||
in = strings.ReplaceAll(envSchemaYaml, "\t", " ")
|
Generic: s.Generic,
|
||||||
err = yaml.Unmarshal([]byte(in), envSchema)
|
Extensions: s.Extensions,
|
||||||
if err != nil {
|
ValueValidation: s.ValueValidation,
|
||||||
panic(err)
|
|
||||||
}
|
}
|
||||||
schemaDef = NewOpenAPISchema()
|
props := make(map[string]schema.Structural, len(s.Properties))
|
||||||
in = strings.ReplaceAll(schemaDefYaml, "\t", " ")
|
for k, prop := range s.Properties {
|
||||||
err = yaml.Unmarshal([]byte(in), schemaDef)
|
props[k] = prop
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
}
|
||||||
templateSchema = NewOpenAPISchema()
|
stringType := schema.Structural{Generic: schema.Generic{Type: "string"}}
|
||||||
in = strings.ReplaceAll(templateSchemaYaml, "\t", " ")
|
props["kind"] = stringType
|
||||||
err = yaml.Unmarshal([]byte(in), templateSchema)
|
props["apiVersion"] = stringType
|
||||||
if err != nil {
|
props["metadata"] = schema.Structural{
|
||||||
panic(err)
|
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"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/google/cel-go/checker/decls"
|
|
||||||
"github.com/google/cel-go/common/types"
|
"github.com/google/cel-go/common/types"
|
||||||
|
|
||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
|
"k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSchemaDeclType(t *testing.T) {
|
func TestSchemaDeclType(t *testing.T) {
|
||||||
ts := testSchema()
|
ts := testSchema()
|
||||||
cust := ts.DeclType()
|
cust := SchemaDeclType(ts)
|
||||||
if cust.TypeName() != "CustomObject" {
|
if cust.TypeName() != "object" {
|
||||||
t.Errorf("incorrect type name, got %v, wanted CustomObject", cust.TypeName())
|
t.Errorf("incorrect type name, got %v, wanted object", cust.TypeName())
|
||||||
}
|
}
|
||||||
if len(cust.Fields) != 4 {
|
if len(cust.Fields) != 4 {
|
||||||
t.Errorf("incorrect number of fields, got %d, wanted 4", len(cust.Fields))
|
t.Errorf("incorrect number of fields, got %d, wanted 4", len(cust.Fields))
|
||||||
}
|
}
|
||||||
for _, f := range cust.Fields {
|
for _, f := range cust.Fields {
|
||||||
prop, found := ts.FindProperty(f.Name)
|
prop, found := ts.Properties[f.Name]
|
||||||
if !found {
|
if !found {
|
||||||
t.Errorf("type field not found in schema, field: %s", f.Name)
|
t.Errorf("type field not found in schema, field: %s", f.Name)
|
||||||
}
|
}
|
||||||
fdv := f.DefaultValue()
|
fdv := f.DefaultValue()
|
||||||
if prop.DefaultValue != nil {
|
if prop.Default.Object != nil {
|
||||||
pdv := types.DefaultTypeAdapter.NativeToValue(prop.DefaultValue)
|
pdv := types.DefaultTypeAdapter.NativeToValue(prop.Default.Object)
|
||||||
if !reflect.DeepEqual(fdv, pdv) {
|
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)
|
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()
|
fevs := f.EnumValues()
|
||||||
for _, fev := range fevs {
|
for _, fev := range fevs {
|
||||||
found := false
|
found := false
|
||||||
for _, pev := range prop.Enum {
|
for _, pev := range prop.ValueValidation.Enum {
|
||||||
pev = types.DefaultTypeAdapter.NativeToValue(pev)
|
celpev := types.DefaultTypeAdapter.NativeToValue(pev.Object)
|
||||||
if reflect.DeepEqual(fev, pev) {
|
if reflect.DeepEqual(fev, celpev) {
|
||||||
found = true
|
found = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -67,7 +67,8 @@ func TestSchemaDeclType(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, name := range ts.Required {
|
if ts.ValueValidation != nil {
|
||||||
|
for _, name := range ts.ValueValidation.Required {
|
||||||
df, found := cust.FindField(name)
|
df, found := cust.FindField(name)
|
||||||
if !found {
|
if !found {
|
||||||
t.Errorf("custom type missing required field. field=%s", name)
|
t.Errorf("custom type missing required field. field=%s", name)
|
||||||
@ -76,18 +77,18 @@ func TestSchemaDeclType(t *testing.T) {
|
|||||||
t.Errorf("field marked as required in schema, but optional in type. field=%s", df.Name)
|
t.Errorf("field marked as required in schema, but optional in type. field=%s", df.Name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSchemaDeclTypes(t *testing.T) {
|
func TestSchemaDeclTypes(t *testing.T) {
|
||||||
ts := testSchema()
|
ts := testSchema()
|
||||||
cust, typeMap := ts.DeclTypes("mock_template")
|
cust, typeMap := SchemaDeclTypes(ts, "CustomObject")
|
||||||
nested, _ := cust.FindField("nested")
|
nested, _ := cust.FindField("nested")
|
||||||
metadata, _ := cust.FindField("metadata")
|
metadata, _ := cust.FindField("metadata")
|
||||||
metadataElem := metadata.Type.ElemType
|
|
||||||
expectedObjTypeMap := map[string]*DeclType{
|
expectedObjTypeMap := map[string]*DeclType{
|
||||||
"CustomObject": cust,
|
"CustomObject": cust,
|
||||||
"CustomObject.nested": nested.Type,
|
"CustomObject.nested": nested.Type,
|
||||||
"CustomObject.metadata.@elem": metadataElem,
|
"CustomObject.metadata": metadata.Type,
|
||||||
}
|
}
|
||||||
objTypeMap := map[string]*DeclType{}
|
objTypeMap := map[string]*DeclType{}
|
||||||
for name, t := range typeMap {
|
for name, t := range typeMap {
|
||||||
@ -96,7 +97,7 @@ func TestSchemaDeclTypes(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(objTypeMap) != len(expectedObjTypeMap) {
|
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 {
|
for exp, expType := range expectedObjTypeMap {
|
||||||
actType, found := objTypeMap[exp]
|
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())
|
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:
|
// Manual construction of a schema with the following definition:
|
||||||
//
|
//
|
||||||
// schema:
|
// schema:
|
||||||
@ -160,44 +153,86 @@ func testSchema() *OpenAPISchema {
|
|||||||
// format: int64
|
// format: int64
|
||||||
// default: 1
|
// default: 1
|
||||||
// enum: [1,2,3]
|
// enum: [1,2,3]
|
||||||
nameField := NewOpenAPISchema()
|
ts := &schema.Structural{
|
||||||
nameField.Type = "string"
|
Generic: schema.Generic{
|
||||||
valueField := NewOpenAPISchema()
|
Type: "object",
|
||||||
valueField.Type = "integer"
|
},
|
||||||
valueField.Format = "int64"
|
Properties: map[string]schema.Structural{
|
||||||
valueField.DefaultValue = int64(1)
|
"name": {
|
||||||
valueField.Enum = []interface{}{int64(1), int64(2), int64(3)}
|
Generic: schema.Generic{
|
||||||
nestedObjField := NewOpenAPISchema()
|
Type: "string",
|
||||||
nestedObjField.Type = "object"
|
},
|
||||||
nestedObjField.Properties["subname"] = NewOpenAPISchema()
|
},
|
||||||
nestedObjField.Properties["subname"].Type = "string"
|
"value": {
|
||||||
nestedObjField.Properties["flags"] = NewOpenAPISchema()
|
Generic: schema.Generic{
|
||||||
nestedObjField.Properties["flags"].Type = "object"
|
Type: "integer",
|
||||||
nestedObjField.Properties["flags"].AdditionalProperties = NewOpenAPISchema()
|
Default: schema.JSON{Object: int64(1)},
|
||||||
nestedObjField.Properties["flags"].AdditionalProperties.Type = "boolean"
|
},
|
||||||
nestedObjField.Properties["dates"] = NewOpenAPISchema()
|
ValueValidation: &schema.ValueValidation{
|
||||||
nestedObjField.Properties["dates"].Type = "array"
|
Format: "int64",
|
||||||
nestedObjField.Properties["dates"].Items = NewOpenAPISchema()
|
Enum: []schema.JSON{{Object: int64(1)}, {Object: int64(2)}, {Object: int64(3)}},
|
||||||
nestedObjField.Properties["dates"].Items.Type = "string"
|
},
|
||||||
nestedObjField.Properties["dates"].Items.Format = "date-time"
|
},
|
||||||
metadataKeyValue := NewOpenAPISchema()
|
"nested": {
|
||||||
metadataKeyValue.Type = "object"
|
Generic: schema.Generic{
|
||||||
metadataKeyValue.Properties["key"] = NewOpenAPISchema()
|
Type: "object",
|
||||||
metadataKeyValue.Properties["key"].Type = "string"
|
},
|
||||||
metadataKeyValue.Properties["values"] = NewOpenAPISchema()
|
Properties: map[string]schema.Structural{
|
||||||
metadataKeyValue.Properties["values"].Type = "array"
|
"subname": {
|
||||||
metadataKeyValue.Properties["values"].Items = NewOpenAPISchema()
|
Generic: schema.Generic{
|
||||||
metadataKeyValue.Properties["values"].Items.Type = "string"
|
Type: "string",
|
||||||
metadataObjField := NewOpenAPISchema()
|
},
|
||||||
metadataObjField.Type = "object"
|
},
|
||||||
metadataObjField.AdditionalProperties = metadataKeyValue
|
"flags": {
|
||||||
ts := NewOpenAPISchema()
|
Generic: schema.Generic{
|
||||||
ts.Type = "object"
|
Type: "object",
|
||||||
ts.Metadata["custom_type"] = "CustomObject"
|
AdditionalProperties: &schema.StructuralOrBool{
|
||||||
ts.Required = []string{"name", "value"}
|
Structural: &schema.Structural{
|
||||||
ts.Properties["name"] = nameField
|
Generic: schema.Generic{
|
||||||
ts.Properties["value"] = valueField
|
Type: "boolean",
|
||||||
ts.Properties["nested"] = nestedObjField
|
},
|
||||||
ts.Properties["metadata"] = metadataObjField
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"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
|
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"
|
"google.golang.org/protobuf/proto"
|
||||||
|
|
||||||
exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
|
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.
|
// 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 {
|
type DeclType struct {
|
||||||
fmt.Stringer
|
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.
|
// NewRuleTypes returns an Open API Schema-based type-system which is CEL compatible.
|
||||||
func NewRuleTypes(kind string,
|
func NewRuleTypes(kind string,
|
||||||
schema *OpenAPISchema,
|
schema *schema.Structural,
|
||||||
res Resolver) (*RuleTypes, error) {
|
res Resolver) (*RuleTypes, error) {
|
||||||
// Note, if the schema indicates that it's actually based on another proto
|
// 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
|
// then prefer the proto definition. For expressions in the proto, a new field
|
||||||
@ -325,13 +326,13 @@ func NewRuleTypes(kind string,
|
|||||||
// type-system.
|
// type-system.
|
||||||
type RuleTypes struct {
|
type RuleTypes struct {
|
||||||
ref.TypeProvider
|
ref.TypeProvider
|
||||||
Schema *OpenAPISchema
|
Schema *schema.Structural
|
||||||
ruleSchemaDeclTypes *schemaTypeProvider
|
ruleSchemaDeclTypes *schemaTypeProvider
|
||||||
typeAdapter ref.TypeAdapter
|
typeAdapter ref.TypeAdapter
|
||||||
resolver Resolver
|
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.
|
// as well as a custom ref.TypeProvider.
|
||||||
//
|
//
|
||||||
// Note, the standard declaration set includes 'rule' which is defined as the top-level rule-schema
|
// 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)
|
tpType, found := tp.FindType(name)
|
||||||
if found && !proto.Equal(tpType, declType.ExprType()) {
|
if found && !proto.Equal(tpType, declType.ExprType()) {
|
||||||
return nil, fmt.Errorf(
|
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{
|
return []cel.EnvOption{
|
||||||
@ -370,7 +371,7 @@ func (rt *RuleTypes) EnvOptions(tp ref.TypeProvider) ([]cel.EnvOption, error) {
|
|||||||
}, nil
|
}, 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.
|
// from the embedded ref.TypeProvider.
|
||||||
//
|
//
|
||||||
// FindType overrides the default type-finding behavior of the embedded 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
|
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
|
// 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 {
|
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.
|
// 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) {
|
func newSchemaTypeProvider(kind string, schema *schema.Structural) (*schemaTypeProvider, error) {
|
||||||
root := schema.DeclType().MaybeAssignTypeName(kind)
|
root := SchemaDeclType(schema).MaybeAssignTypeName(kind)
|
||||||
types := FieldTypeMap(kind, root)
|
types := FieldTypeMap(kind, root)
|
||||||
return &schemaTypeProvider{
|
return &schemaTypeProvider{
|
||||||
root: root,
|
root: root,
|
||||||
@ -515,7 +501,7 @@ type schemaTypeProvider struct {
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
// AnyType is equivalent to the CEL 'protobuf.Any' type in that the value may have any of the
|
// 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)
|
AnyType = newSimpleType("any", decls.Any, nil)
|
||||||
|
|
||||||
// BoolType is equivalent to the CEL 'bool' type.
|
// BoolType is equivalent to the CEL 'bool' type.
|
||||||
@ -530,6 +516,9 @@ var (
|
|||||||
// DurationType is equivalent to the CEL 'duration' type.
|
// DurationType is equivalent to the CEL 'duration' type.
|
||||||
DurationType = newSimpleType("duration", decls.Duration, types.Duration{Duration: time.Duration(0)})
|
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
|
// DynType is the equivalent of the CEL 'dyn' concept which indicates that the type will be
|
||||||
// determined at runtime rather than compile time.
|
// determined at runtime rather than compile time.
|
||||||
DynType = newSimpleType("dyn", decls.Dyn, nil)
|
DynType = newSimpleType("dyn", decls.Dyn, nil)
|
||||||
@ -544,10 +533,6 @@ var (
|
|||||||
// StringType values may either be string literals or expression strings.
|
// StringType values may either be string literals or expression strings.
|
||||||
StringType = newSimpleType("string", decls.String, types.String(""))
|
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 corresponds to the well-known protobuf.Timestamp type supported within CEL.
|
||||||
TimestampType = newSimpleType("timestamp", decls.Timestamp, types.Timestamp{Time: time.Time{}})
|
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) {
|
func TestTypes_RuleTypesFieldMapping(t *testing.T) {
|
||||||
stdEnv, _ := cel.NewEnv()
|
stdEnv, _ := cel.NewEnv()
|
||||||
reg := NewRegistry(stdEnv)
|
reg := NewRegistry(stdEnv)
|
||||||
rt, err := NewRuleTypes("mock_template", testSchema(), reg)
|
rt, err := NewRuleTypes("CustomObject", testSchema(), reg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
rt.TypeProvider = stdEnv.TypeProvider()
|
||||||
nestedFieldType, found := rt.FindFieldType("CustomObject", "nested")
|
nestedFieldType, found := rt.FindFieldType("CustomObject", "nested")
|
||||||
if !found {
|
if !found {
|
||||||
t.Fatal("got field not found for 'CustomObject.nested', wanted 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 := NewMapValue()
|
||||||
mapVal.AddField(name)
|
mapVal.AddField(name)
|
||||||
mapVal.AddField(nested)
|
mapVal.AddField(nested)
|
||||||
rule := rt.ConvertToRule(testValue(t, 11, mapVal))
|
//rule := rt.ConvertToRule(testValue(t, 11, mapVal))
|
||||||
if rule == nil {
|
//if rule == nil {
|
||||||
t.Error("map could not be converted to rule")
|
// t.Error("map could not be converted to rule")
|
||||||
}
|
//}
|
||||||
if rule.GetID() != 11 {
|
//if rule.GetID() != 11 {
|
||||||
t.Errorf("got %d as the rule id, wanted 11", rule.GetID())
|
// t.Errorf("got %d as the rule id, wanted 11", rule.GetID())
|
||||||
}
|
//}
|
||||||
ruleVal := rt.NativeToValue(rule)
|
//ruleVal := rt.NativeToValue(rule)
|
||||||
if ruleVal == nil {
|
//if ruleVal == nil {
|
||||||
t.Error("got CEL rule value of nil, wanted non-nil")
|
// t.Error("got CEL rule value of nil, wanted non-nil")
|
||||||
}
|
//}
|
||||||
|
|
||||||
opts, err := rt.EnvOptions(stdEnv.TypeProvider())
|
opts, err := rt.EnvOptions(stdEnv.TypeProvider())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -45,13 +45,6 @@ const (
|
|||||||
LiteralStyle
|
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.
|
// NewEmptyDynValue returns the zero-valued DynValue.
|
||||||
func NewEmptyDynValue() *DynValue {
|
func NewEmptyDynValue() *DynValue {
|
||||||
// note: 0 is not a valid parse node identifier.
|
// 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
|
return types.String(v), StringType, nil
|
||||||
case uint64:
|
case uint64:
|
||||||
return types.Uint(v), UintType, nil
|
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:
|
case time.Duration:
|
||||||
return types.Duration{Duration: v}, DurationType, nil
|
return types.Duration{Duration: v}, DurationType, nil
|
||||||
case time.Time:
|
case time.Time:
|
||||||
|
@ -35,8 +35,6 @@ func TestConvertToType(t *testing.T) {
|
|||||||
{float64(1.2), types.DoubleType},
|
{float64(1.2), types.DoubleType},
|
||||||
{int64(-42), types.IntType},
|
{int64(-42), types.IntType},
|
||||||
{uint64(63), types.UintType},
|
{uint64(63), types.UintType},
|
||||||
{PlainTextValue("plain text"), types.StringType},
|
|
||||||
{&MultilineStringValue{Value: "multiline", Raw: "multi\nline"}, types.StringType},
|
|
||||||
{time.Duration(300), types.DurationType},
|
{time.Duration(300), types.DurationType},
|
||||||
{time.Now().UTC(), types.TimestampType},
|
{time.Now().UTC(), types.TimestampType},
|
||||||
{types.NullValue, types.NullType},
|
{types.NullValue, types.NullType},
|
||||||
@ -63,8 +61,7 @@ func TestConvertToType(t *testing.T) {
|
|||||||
|
|
||||||
func TestEqual(t *testing.T) {
|
func TestEqual(t *testing.T) {
|
||||||
vals := []interface{}{
|
vals := []interface{}{
|
||||||
true, []byte("bytes"), float64(1.2), int64(-42), uint64(63), PlainTextValue("plain text"),
|
true, []byte("bytes"), float64(1.2), int64(-42), uint64(63), time.Duration(300),
|
||||||
&MultilineStringValue{Value: "multiline", Raw: "multi\nline"}, time.Duration(300),
|
|
||||||
time.Now().UTC(), types.NullValue, NewListValue(), NewMapValue(),
|
time.Now().UTC(), types.NullValue, NewListValue(), NewMapValue(),
|
||||||
NewObjectValue(NewObjectType("TestObject", map[string]*DeclField{})),
|
NewObjectValue(NewObjectType("TestObject", map[string]*DeclField{})),
|
||||||
}
|
}
|
||||||
@ -327,7 +324,7 @@ func TestObjectValueEqual(t *testing.T) {
|
|||||||
objType := NewObjectType("Notice", map[string]*DeclField{
|
objType := NewObjectType("Notice", map[string]*DeclField{
|
||||||
"name": &DeclField{Name: "name", Type: StringType},
|
"name": &DeclField{Name: "name", Type: StringType},
|
||||||
"priority": &DeclField{Name: "priority", Type: IntType},
|
"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 := NewField(1, "name")
|
||||||
name.Ref = testValue(t, 2, "alert")
|
name.Ref = testValue(t, 2, "alert")
|
||||||
|
Loading…
Reference in New Issue
Block a user