Merge pull request #115668 from jiahuif-forks/feature/validating-admission-policy/type-system
Type System for ValidatingAdmissionPolicy
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -66,6 +66,19 @@ message AuditAnnotation {
|
||||
optional string valueExpression = 2;
|
||||
}
|
||||
|
||||
// ExpressionWarning is a warning information that targets a specific expression.
|
||||
message ExpressionWarning {
|
||||
// The path to the field that refers the expression.
|
||||
// For example, the reference to the expression of the first item of
|
||||
// validations is "spec.validations[0].expression"
|
||||
optional string fieldRef = 2;
|
||||
|
||||
// The content of type checking information in a human-readable form.
|
||||
// Each line of the warning contains the type that the expression is checked
|
||||
// against, followed by the type check error from the compiler.
|
||||
optional string warning = 3;
|
||||
}
|
||||
|
||||
// MatchResources decides whether to run the admission control policy on an object based
|
||||
// on whether it meets the match criteria.
|
||||
// The exclude rules take precedence over include rules (if a resource matches both, it is excluded)
|
||||
@@ -198,6 +211,15 @@ message ParamRef {
|
||||
optional string namespace = 2;
|
||||
}
|
||||
|
||||
// TypeChecking contains results of type checking the expressions in the
|
||||
// ValidatingAdmissionPolicy
|
||||
message TypeChecking {
|
||||
// The type checking warnings for each expression.
|
||||
// +optional
|
||||
// +listType=atomic
|
||||
repeated ExpressionWarning expressionWarnings = 1;
|
||||
}
|
||||
|
||||
// ValidatingAdmissionPolicy describes the definition of an admission validation policy that accepts or rejects an object without changing it.
|
||||
message ValidatingAdmissionPolicy {
|
||||
// Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata.
|
||||
@@ -206,6 +228,13 @@ message ValidatingAdmissionPolicy {
|
||||
|
||||
// Specification of the desired behavior of the ValidatingAdmissionPolicy.
|
||||
optional ValidatingAdmissionPolicySpec spec = 2;
|
||||
|
||||
// The status of the ValidatingAdmissionPolicy, including warnings that are useful to determine if the policy
|
||||
// behaves in the expected way.
|
||||
// Populated by the system.
|
||||
// Read-only.
|
||||
// +optional
|
||||
optional ValidatingAdmissionPolicyStatus status = 3;
|
||||
}
|
||||
|
||||
// ValidatingAdmissionPolicyBinding binds the ValidatingAdmissionPolicy with paramerized resources.
|
||||
@@ -353,6 +382,24 @@ message ValidatingAdmissionPolicySpec {
|
||||
repeated AuditAnnotation auditAnnotations = 5;
|
||||
}
|
||||
|
||||
// ValidatingAdmissionPolicyStatus represents the status of a ValidatingAdmissionPolicy.
|
||||
message ValidatingAdmissionPolicyStatus {
|
||||
// The generation observed by the controller.
|
||||
// +optional
|
||||
optional int64 observedGeneration = 1;
|
||||
|
||||
// The results of type checking for each expression.
|
||||
// Presence of this field indicates the completion of the type checking.
|
||||
// +optional
|
||||
optional TypeChecking typeChecking = 2;
|
||||
|
||||
// The conditions represent the latest available observations of a policy's current state.
|
||||
// +optional
|
||||
// +listType=map
|
||||
// +listMapKey=type
|
||||
repeated k8s.io.apimachinery.pkg.apis.meta.v1.Condition conditions = 3;
|
||||
}
|
||||
|
||||
// Validation specifies the CEL expression which is used to apply the validation.
|
||||
message Validation {
|
||||
// Expression represents the expression which will be evaluated by CEL.
|
||||
|
@@ -74,6 +74,49 @@ type ValidatingAdmissionPolicy struct {
|
||||
metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
|
||||
// Specification of the desired behavior of the ValidatingAdmissionPolicy.
|
||||
Spec ValidatingAdmissionPolicySpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"`
|
||||
// The status of the ValidatingAdmissionPolicy, including warnings that are useful to determine if the policy
|
||||
// behaves in the expected way.
|
||||
// Populated by the system.
|
||||
// Read-only.
|
||||
// +optional
|
||||
Status ValidatingAdmissionPolicyStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"`
|
||||
}
|
||||
|
||||
// ValidatingAdmissionPolicyStatus represents the status of a ValidatingAdmissionPolicy.
|
||||
type ValidatingAdmissionPolicyStatus struct {
|
||||
// The generation observed by the controller.
|
||||
// +optional
|
||||
ObservedGeneration int64 `json:"observedGeneration,omitempty" protobuf:"varint,1,opt,name=observedGeneration"`
|
||||
// The results of type checking for each expression.
|
||||
// Presence of this field indicates the completion of the type checking.
|
||||
// +optional
|
||||
TypeChecking *TypeChecking `json:"typeChecking,omitempty" protobuf:"bytes,2,opt,name=typeChecking"`
|
||||
// The conditions represent the latest available observations of a policy's current state.
|
||||
// +optional
|
||||
// +listType=map
|
||||
// +listMapKey=type
|
||||
Conditions []metav1.Condition `json:"conditions,omitempty" protobuf:"bytes,3,rep,name=conditions"`
|
||||
}
|
||||
|
||||
// TypeChecking contains results of type checking the expressions in the
|
||||
// ValidatingAdmissionPolicy
|
||||
type TypeChecking struct {
|
||||
// The type checking warnings for each expression.
|
||||
// +optional
|
||||
// +listType=atomic
|
||||
ExpressionWarnings []ExpressionWarning `json:"expressionWarnings,omitempty" protobuf:"bytes,1,rep,name=expressionWarnings"`
|
||||
}
|
||||
|
||||
// ExpressionWarning is a warning information that targets a specific expression.
|
||||
type ExpressionWarning struct {
|
||||
// The path to the field that refers the expression.
|
||||
// For example, the reference to the expression of the first item of
|
||||
// validations is "spec.validations[0].expression"
|
||||
FieldRef string `json:"fieldRef" protobuf:"bytes,2,opt,name=fieldRef"`
|
||||
// The content of type checking information in a human-readable form.
|
||||
// Each line of the warning contains the type that the expression is checked
|
||||
// against, followed by the type check error from the compiler.
|
||||
Warning string `json:"warning" protobuf:"bytes,3,opt,name=warning"`
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
|
@@ -37,6 +37,16 @@ func (AuditAnnotation) SwaggerDoc() map[string]string {
|
||||
return map_AuditAnnotation
|
||||
}
|
||||
|
||||
var map_ExpressionWarning = map[string]string{
|
||||
"": "ExpressionWarning is a warning information that targets a specific expression.",
|
||||
"fieldRef": "The path to the field that refers the expression. For example, the reference to the expression of the first item of validations is \"spec.validations[0].expression\"",
|
||||
"warning": "The content of type checking information in a human-readable form. Each line of the warning contains the type that the expression is checked against, followed by the type check error from the compiler.",
|
||||
}
|
||||
|
||||
func (ExpressionWarning) SwaggerDoc() map[string]string {
|
||||
return map_ExpressionWarning
|
||||
}
|
||||
|
||||
var map_MatchResources = map[string]string{
|
||||
"": "MatchResources decides whether to run the admission control policy on an object based on whether it meets the match criteria. The exclude rules take precedence over include rules (if a resource matches both, it is excluded)",
|
||||
"namespaceSelector": "NamespaceSelector decides whether to run the admission control policy on an object based on whether the namespace for that object matches the selector. If the object itself is a namespace, the matching is performed on object.metadata.labels. If the object is another cluster scoped resource, it never skips the policy.\n\nFor example, to run the webhook on any objects whose namespace is not associated with \"runlevel\" of \"0\" or \"1\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"runlevel\",\n \"operator\": \"NotIn\",\n \"values\": [\n \"0\",\n \"1\"\n ]\n }\n ]\n}\n\nIf instead you want to only run the policy on any objects whose namespace is associated with the \"environment\" of \"prod\" or \"staging\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"environment\",\n \"operator\": \"In\",\n \"values\": [\n \"prod\",\n \"staging\"\n ]\n }\n ]\n}\n\nSee https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ for more examples of label selectors.\n\nDefault to the empty LabelSelector, which matches everything.",
|
||||
@@ -79,10 +89,20 @@ func (ParamRef) SwaggerDoc() map[string]string {
|
||||
return map_ParamRef
|
||||
}
|
||||
|
||||
var map_TypeChecking = map[string]string{
|
||||
"": "TypeChecking contains results of type checking the expressions in the ValidatingAdmissionPolicy",
|
||||
"expressionWarnings": "The type checking warnings for each expression.",
|
||||
}
|
||||
|
||||
func (TypeChecking) SwaggerDoc() map[string]string {
|
||||
return map_TypeChecking
|
||||
}
|
||||
|
||||
var map_ValidatingAdmissionPolicy = map[string]string{
|
||||
"": "ValidatingAdmissionPolicy describes the definition of an admission validation policy that accepts or rejects an object without changing it.",
|
||||
"metadata": "Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata.",
|
||||
"spec": "Specification of the desired behavior of the ValidatingAdmissionPolicy.",
|
||||
"status": "The status of the ValidatingAdmissionPolicy, including warnings that are useful to determine if the policy behaves in the expected way. Populated by the system. Read-only.",
|
||||
}
|
||||
|
||||
func (ValidatingAdmissionPolicy) SwaggerDoc() map[string]string {
|
||||
@@ -144,6 +164,17 @@ func (ValidatingAdmissionPolicySpec) SwaggerDoc() map[string]string {
|
||||
return map_ValidatingAdmissionPolicySpec
|
||||
}
|
||||
|
||||
var map_ValidatingAdmissionPolicyStatus = map[string]string{
|
||||
"": "ValidatingAdmissionPolicyStatus represents the status of a ValidatingAdmissionPolicy.",
|
||||
"observedGeneration": "The generation observed by the controller.",
|
||||
"typeChecking": "The results of type checking for each expression. Presence of this field indicates the completion of the type checking.",
|
||||
"conditions": "The conditions represent the latest available observations of a policy's current state.",
|
||||
}
|
||||
|
||||
func (ValidatingAdmissionPolicyStatus) SwaggerDoc() map[string]string {
|
||||
return map_ValidatingAdmissionPolicyStatus
|
||||
}
|
||||
|
||||
var map_Validation = map[string]string{
|
||||
"": "Validation specifies the CEL expression which is used to apply the validation.",
|
||||
"expression": "Expression represents the expression which will be evaluated by CEL. ref: https://github.com/google/cel-spec CEL expressions have access to the contents of the API request/response, organized into CEL variables as well as some other useful variables:\n\n- 'object' - The object from the incoming request. The value is null for DELETE requests. - 'oldObject' - The existing object. The value is null for CREATE requests. - 'request' - Attributes of the API request([ref](/pkg/apis/admission/types.go#AdmissionRequest)). - 'params' - Parameter resource referred to by the policy binding being evaluated. Only populated if the policy has a ParamKind. - 'authorizer' - A CEL Authorizer. May be used to perform authorization checks for the principal (user or service account) of the request.\n See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz\n- 'authorizer.requestResource' - A CEL ResourceCheck constructed from the 'authorizer' and configured with the\n request resource.\n\nThe `apiVersion`, `kind`, `metadata.name` and `metadata.generateName` are always accessible from the root of the object. No other metadata properties are accessible.\n\nOnly property names of the form `[a-zA-Z_.-/][a-zA-Z0-9_.-/]*` are accessible. Accessible property names are escaped according to the following rules when accessed in the expression: - '__' escapes to '__underscores__' - '.' escapes to '__dot__' - '-' escapes to '__dash__' - '/' escapes to '__slash__' - Property names that exactly match a CEL RESERVED keyword escape to '__{keyword}__'. The keywords are:\n\t \"true\", \"false\", \"null\", \"in\", \"as\", \"break\", \"const\", \"continue\", \"else\", \"for\", \"function\", \"if\",\n\t \"import\", \"let\", \"loop\", \"package\", \"namespace\", \"return\".\nExamples:\n - Expression accessing a property named \"namespace\": {\"Expression\": \"object.__namespace__ > 0\"}\n - Expression accessing a property named \"x-prop\": {\"Expression\": \"object.x__dash__prop > 0\"}\n - Expression accessing a property named \"redact__d\": {\"Expression\": \"object.redact__underscores__d > 0\"}\n\nEquality on arrays with list type of 'set' or 'map' ignores element order, i.e. [1, 2] == [2, 1]. Concatenation on arrays with x-kubernetes-list-type use the semantics of the list type:\n - 'set': `X + Y` performs a union where the array positions of all elements in `X` are preserved and\n non-intersecting elements in `Y` are appended, retaining their partial order.\n - 'map': `X + Y` performs a merge where the array positions of all keys in `X` are preserved but the values\n are overwritten by values in `Y` when the key sets of `X` and `Y` intersect. Elements in `Y` with\n non-intersecting keys are appended, retaining their partial order.\nRequired.",
|
||||
|
@@ -42,6 +42,22 @@ func (in *AuditAnnotation) DeepCopy() *AuditAnnotation {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ExpressionWarning) DeepCopyInto(out *ExpressionWarning) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExpressionWarning.
|
||||
func (in *ExpressionWarning) DeepCopy() *ExpressionWarning {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ExpressionWarning)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *MatchResources) DeepCopyInto(out *MatchResources) {
|
||||
*out = *in
|
||||
@@ -141,12 +157,34 @@ func (in *ParamRef) DeepCopy() *ParamRef {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *TypeChecking) DeepCopyInto(out *TypeChecking) {
|
||||
*out = *in
|
||||
if in.ExpressionWarnings != nil {
|
||||
in, out := &in.ExpressionWarnings, &out.ExpressionWarnings
|
||||
*out = make([]ExpressionWarning, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TypeChecking.
|
||||
func (in *TypeChecking) DeepCopy() *TypeChecking {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(TypeChecking)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ValidatingAdmissionPolicy) DeepCopyInto(out *ValidatingAdmissionPolicy) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
in.Spec.DeepCopyInto(&out.Spec)
|
||||
in.Status.DeepCopyInto(&out.Status)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -335,6 +373,34 @@ func (in *ValidatingAdmissionPolicySpec) DeepCopy() *ValidatingAdmissionPolicySp
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ValidatingAdmissionPolicyStatus) DeepCopyInto(out *ValidatingAdmissionPolicyStatus) {
|
||||
*out = *in
|
||||
if in.TypeChecking != nil {
|
||||
in, out := &in.TypeChecking, &out.TypeChecking
|
||||
*out = new(TypeChecking)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.Conditions != nil {
|
||||
in, out := &in.Conditions, &out.Conditions
|
||||
*out = make([]v1.Condition, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ValidatingAdmissionPolicyStatus.
|
||||
func (in *ValidatingAdmissionPolicyStatus) DeepCopy() *ValidatingAdmissionPolicyStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ValidatingAdmissionPolicyStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Validation) DeepCopyInto(out *Validation) {
|
||||
*out = *in
|
||||
|
@@ -134,5 +134,26 @@
|
||||
"valueExpression": "valueExpressionValue"
|
||||
}
|
||||
]
|
||||
},
|
||||
"status": {
|
||||
"observedGeneration": 1,
|
||||
"typeChecking": {
|
||||
"expressionWarnings": [
|
||||
{
|
||||
"fieldRef": "fieldRefValue",
|
||||
"warning": "warningValue"
|
||||
}
|
||||
]
|
||||
},
|
||||
"conditions": [
|
||||
{
|
||||
"type": "typeValue",
|
||||
"status": "statusValue",
|
||||
"observedGeneration": 3,
|
||||
"lastTransitionTime": "2004-01-01T01:01:01Z",
|
||||
"reason": "reasonValue",
|
||||
"message": "messageValue"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
Binary file not shown.
@@ -87,3 +87,16 @@ spec:
|
||||
message: messageValue
|
||||
messageExpression: messageExpressionValue
|
||||
reason: reasonValue
|
||||
status:
|
||||
conditions:
|
||||
- lastTransitionTime: "2004-01-01T01:01:01Z"
|
||||
message: messageValue
|
||||
observedGeneration: 3
|
||||
reason: reasonValue
|
||||
status: statusValue
|
||||
type: typeValue
|
||||
observedGeneration: 1
|
||||
typeChecking:
|
||||
expressionWarnings:
|
||||
- fieldRef: fieldRefValue
|
||||
warning: warningValue
|
||||
|
@@ -0,0 +1,132 @@
|
||||
{
|
||||
"kind": "ValidatingAdmissionPolicy",
|
||||
"apiVersion": "admissionregistration.k8s.io/v1alpha1",
|
||||
"metadata": {
|
||||
"name": "nameValue",
|
||||
"generateName": "generateNameValue",
|
||||
"namespace": "namespaceValue",
|
||||
"selfLink": "selfLinkValue",
|
||||
"uid": "uidValue",
|
||||
"resourceVersion": "resourceVersionValue",
|
||||
"generation": 7,
|
||||
"creationTimestamp": "2008-01-01T01:01:01Z",
|
||||
"deletionTimestamp": "2009-01-01T01:01:01Z",
|
||||
"deletionGracePeriodSeconds": 10,
|
||||
"labels": {
|
||||
"labelsKey": "labelsValue"
|
||||
},
|
||||
"annotations": {
|
||||
"annotationsKey": "annotationsValue"
|
||||
},
|
||||
"ownerReferences": [
|
||||
{
|
||||
"apiVersion": "apiVersionValue",
|
||||
"kind": "kindValue",
|
||||
"name": "nameValue",
|
||||
"uid": "uidValue",
|
||||
"controller": true,
|
||||
"blockOwnerDeletion": true
|
||||
}
|
||||
],
|
||||
"finalizers": [
|
||||
"finalizersValue"
|
||||
],
|
||||
"managedFields": [
|
||||
{
|
||||
"manager": "managerValue",
|
||||
"operation": "operationValue",
|
||||
"apiVersion": "apiVersionValue",
|
||||
"time": "2004-01-01T01:01:01Z",
|
||||
"fieldsType": "fieldsTypeValue",
|
||||
"fieldsV1": {},
|
||||
"subresource": "subresourceValue"
|
||||
}
|
||||
]
|
||||
},
|
||||
"spec": {
|
||||
"paramKind": {
|
||||
"apiVersion": "apiVersionValue",
|
||||
"kind": "kindValue"
|
||||
},
|
||||
"matchConstraints": {
|
||||
"namespaceSelector": {
|
||||
"matchLabels": {
|
||||
"matchLabelsKey": "matchLabelsValue"
|
||||
},
|
||||
"matchExpressions": [
|
||||
{
|
||||
"key": "keyValue",
|
||||
"operator": "operatorValue",
|
||||
"values": [
|
||||
"valuesValue"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"objectSelector": {
|
||||
"matchLabels": {
|
||||
"matchLabelsKey": "matchLabelsValue"
|
||||
},
|
||||
"matchExpressions": [
|
||||
{
|
||||
"key": "keyValue",
|
||||
"operator": "operatorValue",
|
||||
"values": [
|
||||
"valuesValue"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"resourceRules": [
|
||||
{
|
||||
"resourceNames": [
|
||||
"resourceNamesValue"
|
||||
],
|
||||
"operations": [
|
||||
"operationsValue"
|
||||
],
|
||||
"apiGroups": [
|
||||
"apiGroupsValue"
|
||||
],
|
||||
"apiVersions": [
|
||||
"apiVersionsValue"
|
||||
],
|
||||
"resources": [
|
||||
"resourcesValue"
|
||||
],
|
||||
"scope": "scopeValue"
|
||||
}
|
||||
],
|
||||
"excludeResourceRules": [
|
||||
{
|
||||
"resourceNames": [
|
||||
"resourceNamesValue"
|
||||
],
|
||||
"operations": [
|
||||
"operationsValue"
|
||||
],
|
||||
"apiGroups": [
|
||||
"apiGroupsValue"
|
||||
],
|
||||
"apiVersions": [
|
||||
"apiVersionsValue"
|
||||
],
|
||||
"resources": [
|
||||
"resourcesValue"
|
||||
],
|
||||
"scope": "scopeValue"
|
||||
}
|
||||
],
|
||||
"matchPolicy": "matchPolicyValue"
|
||||
},
|
||||
"validations": [
|
||||
{
|
||||
"expression": "expressionValue",
|
||||
"message": "messageValue",
|
||||
"reason": "reasonValue"
|
||||
}
|
||||
],
|
||||
"failurePolicy": "failurePolicyValue"
|
||||
},
|
||||
"status": {}
|
||||
}
|
Binary file not shown.
@@ -0,0 +1,86 @@
|
||||
apiVersion: admissionregistration.k8s.io/v1alpha1
|
||||
kind: ValidatingAdmissionPolicy
|
||||
metadata:
|
||||
annotations:
|
||||
annotationsKey: annotationsValue
|
||||
creationTimestamp: "2008-01-01T01:01:01Z"
|
||||
deletionGracePeriodSeconds: 10
|
||||
deletionTimestamp: "2009-01-01T01:01:01Z"
|
||||
finalizers:
|
||||
- finalizersValue
|
||||
generateName: generateNameValue
|
||||
generation: 7
|
||||
labels:
|
||||
labelsKey: labelsValue
|
||||
managedFields:
|
||||
- apiVersion: apiVersionValue
|
||||
fieldsType: fieldsTypeValue
|
||||
fieldsV1: {}
|
||||
manager: managerValue
|
||||
operation: operationValue
|
||||
subresource: subresourceValue
|
||||
time: "2004-01-01T01:01:01Z"
|
||||
name: nameValue
|
||||
namespace: namespaceValue
|
||||
ownerReferences:
|
||||
- apiVersion: apiVersionValue
|
||||
blockOwnerDeletion: true
|
||||
controller: true
|
||||
kind: kindValue
|
||||
name: nameValue
|
||||
uid: uidValue
|
||||
resourceVersion: resourceVersionValue
|
||||
selfLink: selfLinkValue
|
||||
uid: uidValue
|
||||
spec:
|
||||
failurePolicy: failurePolicyValue
|
||||
matchConstraints:
|
||||
excludeResourceRules:
|
||||
- apiGroups:
|
||||
- apiGroupsValue
|
||||
apiVersions:
|
||||
- apiVersionsValue
|
||||
operations:
|
||||
- operationsValue
|
||||
resourceNames:
|
||||
- resourceNamesValue
|
||||
resources:
|
||||
- resourcesValue
|
||||
scope: scopeValue
|
||||
matchPolicy: matchPolicyValue
|
||||
namespaceSelector:
|
||||
matchExpressions:
|
||||
- key: keyValue
|
||||
operator: operatorValue
|
||||
values:
|
||||
- valuesValue
|
||||
matchLabels:
|
||||
matchLabelsKey: matchLabelsValue
|
||||
objectSelector:
|
||||
matchExpressions:
|
||||
- key: keyValue
|
||||
operator: operatorValue
|
||||
values:
|
||||
- valuesValue
|
||||
matchLabels:
|
||||
matchLabelsKey: matchLabelsValue
|
||||
resourceRules:
|
||||
- apiGroups:
|
||||
- apiGroupsValue
|
||||
apiVersions:
|
||||
- apiVersionsValue
|
||||
operations:
|
||||
- operationsValue
|
||||
resourceNames:
|
||||
- resourceNamesValue
|
||||
resources:
|
||||
- resourcesValue
|
||||
scope: scopeValue
|
||||
paramKind:
|
||||
apiVersion: apiVersionValue
|
||||
kind: kindValue
|
||||
validations:
|
||||
- expression: expressionValue
|
||||
message: messageValue
|
||||
reason: reasonValue
|
||||
status: {}
|
@@ -20,6 +20,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
"k8s.io/apiserver/pkg/cel/openapi/resolver"
|
||||
quota "k8s.io/apiserver/pkg/quota/v1"
|
||||
"k8s.io/client-go/dynamic"
|
||||
"k8s.io/client-go/informers"
|
||||
@@ -81,3 +82,10 @@ type WantsRESTMapper interface {
|
||||
SetRESTMapper(meta.RESTMapper)
|
||||
admission.InitializationValidator
|
||||
}
|
||||
|
||||
// WantsSchemaResolver defines a function which sets the SchemaResolver for
|
||||
// an admission plugin that needs it.
|
||||
type WantsSchemaResolver interface {
|
||||
SetSchemaResolver(resolver resolver.SchemaResolver)
|
||||
admission.InitializationValidator
|
||||
}
|
||||
|
@@ -81,7 +81,7 @@ func buildRequiredVarsEnv() (*cel.Env, error) {
|
||||
var propDecls []cel.EnvOption
|
||||
reg := apiservercel.NewRegistry(baseEnv)
|
||||
|
||||
requestType := buildRequestType()
|
||||
requestType := BuildRequestType()
|
||||
rt, err := apiservercel.NewRuleTypes(requestType.TypeName(), requestType, reg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -134,11 +134,11 @@ func buildWithOptionalVarsEnvs(requiredVarsEnv *cel.Env) (envs, error) {
|
||||
return envs, nil
|
||||
}
|
||||
|
||||
// buildRequestType generates a DeclType for AdmissionRequest. This may be replaced with a utility that
|
||||
// BuildRequestType generates a DeclType for AdmissionRequest. This may be replaced with a utility that
|
||||
// converts the native type definition to apiservercel.DeclType once such a utility becomes available.
|
||||
// The 'uid' field is omitted since it is not needed for in-process admission review.
|
||||
// The 'object' and 'oldObject' fields are omitted since they are exposed as root level CEL variables.
|
||||
func buildRequestType() *apiservercel.DeclType {
|
||||
func BuildRequestType() *apiservercel.DeclType {
|
||||
field := func(name string, declType *apiservercel.DeclType, required bool) *apiservercel.DeclField {
|
||||
return apiservercel.NewDeclField(name, declType, required, nil, nil)
|
||||
}
|
||||
|
@@ -24,6 +24,7 @@ import (
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
"k8s.io/apiserver/pkg/cel/openapi/resolver"
|
||||
"k8s.io/apiserver/pkg/features"
|
||||
"k8s.io/client-go/dynamic"
|
||||
"k8s.io/component-base/featuregate"
|
||||
@@ -73,6 +74,7 @@ type celAdmissionPlugin struct {
|
||||
dynamicClient dynamic.Interface
|
||||
stopCh <-chan struct{}
|
||||
authorizer authorizer.Authorizer
|
||||
schemaResolver resolver.SchemaResolver
|
||||
}
|
||||
|
||||
var _ initializer.WantsExternalKubeInformerFactory = &celAdmissionPlugin{}
|
||||
@@ -81,7 +83,7 @@ var _ initializer.WantsRESTMapper = &celAdmissionPlugin{}
|
||||
var _ initializer.WantsDynamicClient = &celAdmissionPlugin{}
|
||||
var _ initializer.WantsDrainedNotification = &celAdmissionPlugin{}
|
||||
var _ initializer.WantsAuthorizer = &celAdmissionPlugin{}
|
||||
|
||||
var _ initializer.WantsSchemaResolver = &celAdmissionPlugin{}
|
||||
var _ admission.InitializationValidator = &celAdmissionPlugin{}
|
||||
var _ admission.ValidationInterface = &celAdmissionPlugin{}
|
||||
|
||||
@@ -115,6 +117,10 @@ func (c *celAdmissionPlugin) SetAuthorizer(authorizer authorizer.Authorizer) {
|
||||
c.authorizer = authorizer
|
||||
}
|
||||
|
||||
func (c *celAdmissionPlugin) SetSchemaResolver(resolver resolver.SchemaResolver) {
|
||||
c.schemaResolver = resolver
|
||||
}
|
||||
|
||||
func (c *celAdmissionPlugin) InspectFeatureGates(featureGates featuregate.FeatureGate) {
|
||||
if featureGates.Enabled(features.ValidatingAdmissionPolicy) {
|
||||
c.enabled = true
|
||||
@@ -148,7 +154,7 @@ func (c *celAdmissionPlugin) ValidateInitialization() error {
|
||||
if c.authorizer == nil {
|
||||
return errors.New("missing authorizer")
|
||||
}
|
||||
c.evaluator = NewAdmissionController(c.informerFactory, c.client, c.restMapper, c.dynamicClient, c.authorizer)
|
||||
c.evaluator = NewAdmissionController(c.informerFactory, c.client, c.restMapper, c.schemaResolver /* (optional) */, c.dynamicClient, c.authorizer)
|
||||
if err := c.evaluator.ValidateInitialization(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@@ -43,6 +43,7 @@ import (
|
||||
"k8s.io/apiserver/pkg/admission/plugin/validatingadmissionpolicy/matching"
|
||||
celconfig "k8s.io/apiserver/pkg/apis/cel"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
"k8s.io/apiserver/pkg/cel/openapi/resolver"
|
||||
"k8s.io/apiserver/pkg/warning"
|
||||
"k8s.io/client-go/dynamic"
|
||||
"k8s.io/client-go/informers"
|
||||
@@ -124,15 +125,21 @@ func NewAdmissionController(
|
||||
informerFactory informers.SharedInformerFactory,
|
||||
client kubernetes.Interface,
|
||||
restMapper meta.RESTMapper,
|
||||
schemaResolver resolver.SchemaResolver,
|
||||
dynamicClient dynamic.Interface,
|
||||
authz authorizer.Authorizer,
|
||||
) CELPolicyEvaluator {
|
||||
var typeChecker *TypeChecker
|
||||
if schemaResolver != nil {
|
||||
typeChecker = &TypeChecker{schemaResolver: schemaResolver, restMapper: restMapper}
|
||||
}
|
||||
return &celAdmissionController{
|
||||
definitions: atomic.Value{},
|
||||
policyController: newPolicyController(
|
||||
restMapper,
|
||||
client,
|
||||
dynamicClient,
|
||||
typeChecker,
|
||||
cel.NewFilterCompiler(),
|
||||
NewMatcher(matching.NewMatcher(informerFactory.Core().V1().Namespaces().Lister(), client)),
|
||||
generic.NewInformer[*v1alpha1.ValidatingAdmissionPolicy](
|
||||
|
@@ -27,8 +27,10 @@ import (
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
celmetrics "k8s.io/apiserver/pkg/admission/cel"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/cel"
|
||||
@@ -59,6 +61,11 @@ type policyController struct {
|
||||
|
||||
newValidator
|
||||
|
||||
// The TypeCheck checks the policy's expressions for type errors.
|
||||
// Type of params is defined in policy.Spec.ParamsKind
|
||||
// Types of object are calculated from policy.Spec.MatchingConstraints
|
||||
typeChecker *TypeChecker
|
||||
|
||||
// Lock which protects:
|
||||
// - cachedPolicies
|
||||
// - paramCRDControllers
|
||||
@@ -98,6 +105,7 @@ func newPolicyController(
|
||||
restMapper meta.RESTMapper,
|
||||
client kubernetes.Interface,
|
||||
dynamicClient dynamic.Interface,
|
||||
typeChecker *TypeChecker,
|
||||
filterCompiler cel.FilterCompiler,
|
||||
matcher Matcher,
|
||||
policiesInformer generic.Informer[*v1alpha1.ValidatingAdmissionPolicy],
|
||||
@@ -107,6 +115,7 @@ func newPolicyController(
|
||||
res := &policyController{}
|
||||
*res = policyController{
|
||||
filterCompiler: filterCompiler,
|
||||
typeChecker: typeChecker,
|
||||
definitionInfo: make(map[namespacedName]*definitionInfo),
|
||||
bindingInfos: make(map[namespacedName]*bindingInfo),
|
||||
paramsCRDControllers: make(map[v1alpha1.ParamKind]*paramInfo),
|
||||
@@ -168,7 +177,17 @@ func (c *policyController) HasSynced() bool {
|
||||
func (c *policyController) reconcilePolicyDefinition(namespace, name string, definition *v1alpha1.ValidatingAdmissionPolicy) error {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
err := c.reconcilePolicyDefinitionSpec(namespace, name, definition)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if c.typeChecker != nil {
|
||||
err = c.reconcilePolicyStatus(namespace, name, definition)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *policyController) reconcilePolicyDefinitionSpec(namespace, name string, definition *v1alpha1.ValidatingAdmissionPolicy) error {
|
||||
c.cachedPolicies = nil // invalidate cachedPolicies
|
||||
|
||||
// Namespace for policydefinition is empty.
|
||||
@@ -423,6 +442,30 @@ func (c *policyController) reconcilePolicyBinding(namespace, name string, bindin
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *policyController) reconcilePolicyStatus(namespace, name string, definition *v1alpha1.ValidatingAdmissionPolicy) error {
|
||||
if definition != nil && definition.Status.ObservedGeneration < definition.Generation {
|
||||
st := c.calculatePolicyStatus(definition)
|
||||
newDefinition := definition.DeepCopy()
|
||||
newDefinition.Status = *st
|
||||
_, err := c.client.AdmissionregistrationV1alpha1().ValidatingAdmissionPolicies().UpdateStatus(c.context, newDefinition, metav1.UpdateOptions{})
|
||||
if err != nil {
|
||||
// ignore error when the controller is not able to
|
||||
// mutate the definition, and to avoid infinite requeue.
|
||||
utilruntime.HandleError(err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *policyController) calculatePolicyStatus(definition *v1alpha1.ValidatingAdmissionPolicy) *v1alpha1.ValidatingAdmissionPolicyStatus {
|
||||
expressionWarnings := c.typeChecker.Check(definition)
|
||||
// modifying a deepcopy of the original status, preserving unrelated existing data
|
||||
status := definition.Status.DeepCopy()
|
||||
status.ObservedGeneration = definition.Generation
|
||||
status.TypeChecking = &v1alpha1.TypeChecking{ExpressionWarnings: expressionWarnings}
|
||||
return status
|
||||
}
|
||||
|
||||
func (c *policyController) reconcileParams(namespace, name string, params runtime.Object) error {
|
||||
// Do nothing.
|
||||
// When we add informational type checking we will need to compile in the
|
||||
|
@@ -0,0 +1,435 @@
|
||||
/*
|
||||
Copyright 2023 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 validatingadmissionpolicy
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/google/cel-go/cel"
|
||||
"github.com/google/cel-go/common/types/ref"
|
||||
|
||||
"k8s.io/api/admissionregistration/v1alpha1"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
plugincel "k8s.io/apiserver/pkg/admission/plugin/cel"
|
||||
apiservercel "k8s.io/apiserver/pkg/cel"
|
||||
"k8s.io/apiserver/pkg/cel/common"
|
||||
"k8s.io/apiserver/pkg/cel/library"
|
||||
"k8s.io/apiserver/pkg/cel/openapi"
|
||||
"k8s.io/apiserver/pkg/cel/openapi/resolver"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
const maxTypesToCheck = 10
|
||||
|
||||
type TypeChecker struct {
|
||||
schemaResolver resolver.SchemaResolver
|
||||
restMapper meta.RESTMapper
|
||||
}
|
||||
|
||||
type typeOverwrite struct {
|
||||
object *apiservercel.DeclType
|
||||
params *apiservercel.DeclType
|
||||
}
|
||||
|
||||
// typeCheckingResult holds the issues found during type checking, any returned
|
||||
// error, and the gvk that the type checking is performed against.
|
||||
type typeCheckingResult struct {
|
||||
gvk schema.GroupVersionKind
|
||||
|
||||
issues *cel.Issues
|
||||
err error
|
||||
}
|
||||
|
||||
// Check preforms the type check against the given policy, and format the result
|
||||
// as []ExpressionWarning that is ready to be set in policy.Status
|
||||
// The result is nil if type checking returns no warning.
|
||||
// The policy object is NOT mutated. The caller should update Status accordingly
|
||||
func (c *TypeChecker) Check(policy *v1alpha1.ValidatingAdmissionPolicy) []v1alpha1.ExpressionWarning {
|
||||
exps := make([]string, 0, len(policy.Spec.Validations))
|
||||
// check main validation expressions, located in spec.validations[*]
|
||||
fieldRef := field.NewPath("spec", "validations")
|
||||
for _, v := range policy.Spec.Validations {
|
||||
exps = append(exps, v.Expression)
|
||||
}
|
||||
msgs := c.CheckExpressions(exps, policy.Spec.ParamKind != nil, policy)
|
||||
var results []v1alpha1.ExpressionWarning // intentionally not setting capacity
|
||||
for i, msg := range msgs {
|
||||
if msg != "" {
|
||||
results = append(results, v1alpha1.ExpressionWarning{
|
||||
FieldRef: fieldRef.Index(i).Child("expression").String(),
|
||||
Warning: msg,
|
||||
})
|
||||
}
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
// CheckExpressions checks a set of compiled CEL programs against the GVKs defined in
|
||||
// policy.Spec.MatchConstraints
|
||||
// The result is a human-readable form that describe which expressions
|
||||
// violate what types at what place. The indexes of the return []string
|
||||
// matches these of the input expressions.
|
||||
// TODO: It is much more useful to have machine-readable output and let the
|
||||
// client format it. That requires an update to the KEP, probably in coming
|
||||
// releases.
|
||||
func (c *TypeChecker) CheckExpressions(expressions []string, hasParams bool, policy *v1alpha1.ValidatingAdmissionPolicy) []string {
|
||||
var allWarnings []string
|
||||
allGvks := c.typesToCheck(policy)
|
||||
gvks := make([]schema.GroupVersionKind, 0, len(allGvks))
|
||||
schemas := make([]common.Schema, 0, len(allGvks))
|
||||
for _, gvk := range allGvks {
|
||||
s, err := c.schemaResolver.ResolveSchema(gvk)
|
||||
if err != nil {
|
||||
// type checking errors MUST NOT alter the behavior of the policy
|
||||
// even if an error occurs.
|
||||
if !errors.Is(err, resolver.ErrSchemaNotFound) {
|
||||
// Anything except ErrSchemaNotFound is an internal error
|
||||
klog.ErrorS(err, "internal error: schema resolution failure", "gvk", gvk)
|
||||
}
|
||||
// skip if an unrecoverable error occurs.
|
||||
continue
|
||||
}
|
||||
gvks = append(gvks, gvk)
|
||||
schemas = append(schemas, &openapi.Schema{Schema: s})
|
||||
}
|
||||
|
||||
paramsType := c.paramsType(policy)
|
||||
paramsDeclType, err := c.declType(paramsType)
|
||||
if err != nil {
|
||||
if !errors.Is(err, resolver.ErrSchemaNotFound) {
|
||||
klog.V(2).ErrorS(err, "cannot resolve schema for params", "gvk", paramsType)
|
||||
}
|
||||
paramsDeclType = nil
|
||||
}
|
||||
|
||||
for _, exp := range expressions {
|
||||
var results []typeCheckingResult
|
||||
for i, gvk := range gvks {
|
||||
s := schemas[i]
|
||||
issues, err := c.checkExpression(exp, hasParams, typeOverwrite{
|
||||
object: common.SchemaDeclType(s, true),
|
||||
params: paramsDeclType,
|
||||
})
|
||||
// save even if no issues are found, for the sake of formatting.
|
||||
results = append(results, typeCheckingResult{
|
||||
gvk: gvk,
|
||||
issues: issues,
|
||||
err: err,
|
||||
})
|
||||
}
|
||||
allWarnings = append(allWarnings, c.formatWarning(results))
|
||||
}
|
||||
|
||||
return allWarnings
|
||||
}
|
||||
|
||||
// formatWarning converts the resulting issues and possible error during
|
||||
// type checking into a human-readable string
|
||||
func (c *TypeChecker) formatWarning(results []typeCheckingResult) string {
|
||||
var sb strings.Builder
|
||||
for _, result := range results {
|
||||
if result.issues == nil && result.err == nil {
|
||||
continue
|
||||
}
|
||||
if result.err != nil {
|
||||
sb.WriteString(fmt.Sprintf("%v: type checking error: %v\n", result.gvk, result.err))
|
||||
} else {
|
||||
sb.WriteString(fmt.Sprintf("%v: %s\n", result.gvk, result.issues))
|
||||
}
|
||||
}
|
||||
return strings.TrimSuffix(sb.String(), "\n")
|
||||
}
|
||||
|
||||
func (c *TypeChecker) declType(gvk schema.GroupVersionKind) (*apiservercel.DeclType, error) {
|
||||
if gvk.Empty() {
|
||||
return nil, nil
|
||||
}
|
||||
s, err := c.schemaResolver.ResolveSchema(gvk)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return common.SchemaDeclType(&openapi.Schema{Schema: s}, true), nil
|
||||
}
|
||||
|
||||
func (c *TypeChecker) paramsType(policy *v1alpha1.ValidatingAdmissionPolicy) schema.GroupVersionKind {
|
||||
if policy.Spec.ParamKind == nil {
|
||||
return schema.GroupVersionKind{}
|
||||
}
|
||||
gv, err := schema.ParseGroupVersion(policy.Spec.ParamKind.APIVersion)
|
||||
if err != nil {
|
||||
return schema.GroupVersionKind{}
|
||||
}
|
||||
return gv.WithKind(policy.Spec.ParamKind.Kind)
|
||||
}
|
||||
|
||||
func (c *TypeChecker) checkExpression(expression string, hasParams bool, types typeOverwrite) (*cel.Issues, error) {
|
||||
env, err := buildEnv(hasParams, types)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// We cannot reuse an AST that is parsed by another env, so reparse it here.
|
||||
// Compile = Parse + Check, we especially want the results of Check.
|
||||
//
|
||||
// Paradoxically, we discard the type-checked result and let the admission
|
||||
// controller use the dynamic typed program.
|
||||
// This is a compromise that is defined in the KEP. We can revisit this
|
||||
// decision and expect a change with limited size.
|
||||
_, issues := env.Compile(expression)
|
||||
return issues, nil
|
||||
}
|
||||
|
||||
// typesToCheck extracts a list of GVKs that needs type checking from the policy
|
||||
// the result is sorted in the order of Group, Version, and Kind
|
||||
func (c *TypeChecker) typesToCheck(p *v1alpha1.ValidatingAdmissionPolicy) []schema.GroupVersionKind {
|
||||
gvks := sets.New[schema.GroupVersionKind]()
|
||||
if p.Spec.MatchConstraints == nil || len(p.Spec.MatchConstraints.ResourceRules) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, rule := range p.Spec.MatchConstraints.ResourceRules {
|
||||
groups := extractGroups(&rule.Rule)
|
||||
if len(groups) == 0 {
|
||||
continue
|
||||
}
|
||||
versions := extractVersions(&rule.Rule)
|
||||
if len(versions) == 0 {
|
||||
continue
|
||||
}
|
||||
resources := extractResources(&rule.Rule)
|
||||
if len(resources) == 0 {
|
||||
continue
|
||||
}
|
||||
// sort GVRs so that the loop below provides
|
||||
// consistent results.
|
||||
sort.Strings(groups)
|
||||
sort.Strings(versions)
|
||||
sort.Strings(resources)
|
||||
count := 0
|
||||
for _, group := range groups {
|
||||
for _, version := range versions {
|
||||
for _, resource := range resources {
|
||||
gvr := schema.GroupVersionResource{
|
||||
Group: group,
|
||||
Version: version,
|
||||
Resource: resource,
|
||||
}
|
||||
resolved, err := c.restMapper.KindsFor(gvr)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
for _, r := range resolved {
|
||||
if !r.Empty() {
|
||||
gvks.Insert(r)
|
||||
count++
|
||||
// early return if maximum number of types are already
|
||||
// collected
|
||||
if count == maxTypesToCheck {
|
||||
if gvks.Len() == 0 {
|
||||
return nil
|
||||
}
|
||||
return sortGVKList(gvks.UnsortedList())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if gvks.Len() == 0 {
|
||||
return nil
|
||||
}
|
||||
return sortGVKList(gvks.UnsortedList())
|
||||
}
|
||||
|
||||
func extractGroups(rule *v1alpha1.Rule) []string {
|
||||
groups := make([]string, 0, len(rule.APIGroups))
|
||||
for _, group := range rule.APIGroups {
|
||||
// give up if wildcard
|
||||
if strings.ContainsAny(group, "*") {
|
||||
return nil
|
||||
}
|
||||
groups = append(groups, group)
|
||||
}
|
||||
return groups
|
||||
}
|
||||
|
||||
func extractVersions(rule *v1alpha1.Rule) []string {
|
||||
versions := make([]string, 0, len(rule.APIVersions))
|
||||
for _, version := range rule.APIVersions {
|
||||
if strings.ContainsAny(version, "*") {
|
||||
return nil
|
||||
}
|
||||
versions = append(versions, version)
|
||||
}
|
||||
return versions
|
||||
}
|
||||
|
||||
func extractResources(rule *v1alpha1.Rule) []string {
|
||||
resources := make([]string, 0, len(rule.Resources))
|
||||
for _, resource := range rule.Resources {
|
||||
// skip wildcard and subresources
|
||||
if strings.ContainsAny(resource, "*/") {
|
||||
continue
|
||||
}
|
||||
resources = append(resources, resource)
|
||||
}
|
||||
return resources
|
||||
}
|
||||
|
||||
// sortGVKList sorts the list by Group, Version, and Kind
|
||||
// returns the list itself.
|
||||
func sortGVKList(list []schema.GroupVersionKind) []schema.GroupVersionKind {
|
||||
sort.Slice(list, func(i, j int) bool {
|
||||
if g := strings.Compare(list[i].Group, list[j].Group); g != 0 {
|
||||
return g < 0
|
||||
}
|
||||
if v := strings.Compare(list[i].Version, list[j].Version); v != 0 {
|
||||
return v < 0
|
||||
}
|
||||
return strings.Compare(list[i].Kind, list[j].Kind) < 0
|
||||
})
|
||||
return list
|
||||
}
|
||||
|
||||
func buildEnv(hasParams bool, types typeOverwrite) (*cel.Env, error) {
|
||||
baseEnv, err := getBaseEnv()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
reg := apiservercel.NewRegistry(baseEnv)
|
||||
requestType := plugincel.BuildRequestType()
|
||||
|
||||
var varOpts []cel.EnvOption
|
||||
var rts []*apiservercel.RuleTypes
|
||||
|
||||
// request, hand-crafted type
|
||||
rt, opts, err := createRuleTypesAndOptions(reg, requestType, plugincel.RequestVarName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rts = append(rts, rt)
|
||||
varOpts = append(varOpts, opts...)
|
||||
|
||||
// object and oldObject, same type, type(s) resolved from constraints
|
||||
rt, opts, err = createRuleTypesAndOptions(reg, types.object, plugincel.ObjectVarName, plugincel.OldObjectVarName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rts = append(rts, rt)
|
||||
varOpts = append(varOpts, opts...)
|
||||
|
||||
// params, defined by ParamKind
|
||||
if hasParams {
|
||||
rt, opts, err := createRuleTypesAndOptions(reg, types.params, plugincel.ParamsVarName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rts = append(rts, rt)
|
||||
varOpts = append(varOpts, opts...)
|
||||
}
|
||||
|
||||
opts, err = ruleTypesOpts(rts, baseEnv.TypeProvider())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
opts = append(opts, varOpts...) // add variables after ruleTypes.
|
||||
env, err := baseEnv.Extend(opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return env, nil
|
||||
}
|
||||
|
||||
// createRuleTypeAndOptions creates the cel RuleTypes and a slice of EnvOption
|
||||
// that can be used for creating a CEL env containing variables of declType.
|
||||
// declType can be nil, in which case the variables will be of DynType.
|
||||
func createRuleTypesAndOptions(registry *apiservercel.Registry, declType *apiservercel.DeclType, variables ...string) (*apiservercel.RuleTypes, []cel.EnvOption, error) {
|
||||
opts := make([]cel.EnvOption, 0, len(variables))
|
||||
// untyped, use DynType
|
||||
if declType == nil {
|
||||
for _, v := range variables {
|
||||
opts = append(opts, cel.Variable(v, cel.DynType))
|
||||
}
|
||||
return nil, opts, nil
|
||||
}
|
||||
// create a RuleType for the given type
|
||||
rt, err := apiservercel.NewRuleTypes(declType.TypeName(), declType, registry)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if rt == nil {
|
||||
return nil, nil, nil
|
||||
}
|
||||
for _, v := range variables {
|
||||
opts = append(opts, cel.Variable(v, declType.CelType()))
|
||||
}
|
||||
return rt, opts, nil
|
||||
}
|
||||
|
||||
func ruleTypesOpts(ruleTypes []*apiservercel.RuleTypes, underlyingTypeProvider ref.TypeProvider) ([]cel.EnvOption, error) {
|
||||
var providers []ref.TypeProvider // may be unused, too small to matter
|
||||
var adapters []ref.TypeAdapter
|
||||
for _, rt := range ruleTypes {
|
||||
if rt != nil {
|
||||
withTP, err := rt.WithTypeProvider(underlyingTypeProvider)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
providers = append(providers, withTP)
|
||||
adapters = append(adapters, withTP)
|
||||
}
|
||||
}
|
||||
var tp ref.TypeProvider
|
||||
var ta ref.TypeAdapter
|
||||
switch len(providers) {
|
||||
case 0:
|
||||
return nil, nil
|
||||
case 1:
|
||||
tp = providers[0]
|
||||
ta = adapters[0]
|
||||
default:
|
||||
tp = &apiservercel.CompositedTypeProvider{Providers: providers}
|
||||
ta = &apiservercel.CompositedTypeAdapter{Adapters: adapters}
|
||||
}
|
||||
return []cel.EnvOption{cel.CustomTypeProvider(tp), cel.CustomTypeAdapter(ta)}, nil
|
||||
}
|
||||
|
||||
func getBaseEnv() (*cel.Env, error) {
|
||||
typeCheckingBaseEnvInit.Do(func() {
|
||||
var opts []cel.EnvOption
|
||||
opts = append(opts, cel.HomogeneousAggregateLiterals())
|
||||
// Validate function declarations once during base env initialization,
|
||||
// so they don't need to be evaluated each time a CEL rule is compiled.
|
||||
// This is a relatively expensive operation.
|
||||
opts = append(opts, cel.EagerlyValidateDeclarations(true), cel.DefaultUTCTimeZone(true))
|
||||
opts = append(opts, library.ExtensionLibs...)
|
||||
typeCheckingBaseEnv, typeCheckingBaseEnvError = cel.NewEnv(opts...)
|
||||
})
|
||||
return typeCheckingBaseEnv, typeCheckingBaseEnvError
|
||||
}
|
||||
|
||||
var typeCheckingBaseEnv *cel.Env
|
||||
var typeCheckingBaseEnvError error
|
||||
var typeCheckingBaseEnvInit sync.Once
|
@@ -0,0 +1,409 @@
|
||||
/*
|
||||
Copyright 2023 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 validatingadmissionpolicy
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"k8s.io/api/admissionregistration/v1alpha1"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apiserver/pkg/cel/openapi/resolver"
|
||||
"k8s.io/kube-openapi/pkg/validation/spec"
|
||||
)
|
||||
|
||||
func TestExtractTypeNames(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
policy *v1alpha1.ValidatingAdmissionPolicy
|
||||
expected []schema.GroupVersionKind // must be sorted
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
policy: &v1alpha1.ValidatingAdmissionPolicy{},
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "specific",
|
||||
policy: &v1alpha1.ValidatingAdmissionPolicy{Spec: v1alpha1.ValidatingAdmissionPolicySpec{
|
||||
MatchConstraints: &v1alpha1.MatchResources{ResourceRules: []v1alpha1.NamedRuleWithOperations{
|
||||
{
|
||||
RuleWithOperations: v1alpha1.RuleWithOperations{
|
||||
Rule: v1alpha1.Rule{
|
||||
APIGroups: []string{"apps"},
|
||||
APIVersions: []string{"v1"},
|
||||
Resources: []string{"deployments"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}},
|
||||
}},
|
||||
expected: []schema.GroupVersionKind{{
|
||||
Group: "apps",
|
||||
Version: "v1",
|
||||
Kind: "Deployment",
|
||||
}},
|
||||
},
|
||||
{
|
||||
name: "multiple",
|
||||
policy: &v1alpha1.ValidatingAdmissionPolicy{Spec: v1alpha1.ValidatingAdmissionPolicySpec{
|
||||
MatchConstraints: &v1alpha1.MatchResources{ResourceRules: []v1alpha1.NamedRuleWithOperations{
|
||||
{
|
||||
RuleWithOperations: v1alpha1.RuleWithOperations{
|
||||
Rule: v1alpha1.Rule{
|
||||
APIGroups: []string{"apps"},
|
||||
APIVersions: []string{"v1"},
|
||||
Resources: []string{"deployments"},
|
||||
},
|
||||
},
|
||||
}, {
|
||||
RuleWithOperations: v1alpha1.RuleWithOperations{
|
||||
Rule: v1alpha1.Rule{
|
||||
APIGroups: []string{""},
|
||||
APIVersions: []string{"v1"},
|
||||
Resources: []string{"pods"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}},
|
||||
}},
|
||||
expected: []schema.GroupVersionKind{
|
||||
{
|
||||
Version: "v1",
|
||||
Kind: "Pod",
|
||||
}, {
|
||||
Group: "apps",
|
||||
Version: "v1",
|
||||
Kind: "Deployment",
|
||||
}},
|
||||
},
|
||||
{
|
||||
name: "all resources",
|
||||
policy: &v1alpha1.ValidatingAdmissionPolicy{Spec: v1alpha1.ValidatingAdmissionPolicySpec{
|
||||
MatchConstraints: &v1alpha1.MatchResources{ResourceRules: []v1alpha1.NamedRuleWithOperations{
|
||||
{
|
||||
RuleWithOperations: v1alpha1.RuleWithOperations{
|
||||
Rule: v1alpha1.Rule{
|
||||
APIGroups: []string{"apps"},
|
||||
APIVersions: []string{"v1"},
|
||||
Resources: []string{"*"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}},
|
||||
}},
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "sub resources",
|
||||
policy: &v1alpha1.ValidatingAdmissionPolicy{Spec: v1alpha1.ValidatingAdmissionPolicySpec{
|
||||
MatchConstraints: &v1alpha1.MatchResources{ResourceRules: []v1alpha1.NamedRuleWithOperations{
|
||||
{
|
||||
RuleWithOperations: v1alpha1.RuleWithOperations{
|
||||
Rule: v1alpha1.Rule{
|
||||
APIGroups: []string{"apps"},
|
||||
APIVersions: []string{"v1"},
|
||||
Resources: []string{"pods/*"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}},
|
||||
}},
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "mixtures",
|
||||
policy: &v1alpha1.ValidatingAdmissionPolicy{Spec: v1alpha1.ValidatingAdmissionPolicySpec{
|
||||
MatchConstraints: &v1alpha1.MatchResources{ResourceRules: []v1alpha1.NamedRuleWithOperations{
|
||||
{
|
||||
RuleWithOperations: v1alpha1.RuleWithOperations{
|
||||
Rule: v1alpha1.Rule{
|
||||
APIGroups: []string{"apps"},
|
||||
APIVersions: []string{"v1"},
|
||||
Resources: []string{"deployments"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
RuleWithOperations: v1alpha1.RuleWithOperations{
|
||||
Rule: v1alpha1.Rule{
|
||||
APIGroups: []string{"apps"},
|
||||
APIVersions: []string{"*"},
|
||||
Resources: []string{"deployments"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}},
|
||||
}},
|
||||
expected: []schema.GroupVersionKind{{
|
||||
Group: "apps",
|
||||
Version: "v1",
|
||||
Kind: "Deployment",
|
||||
}},
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
typeChecker := buildTypeChecker(nil)
|
||||
got := typeChecker.typesToCheck(tc.policy)
|
||||
if !reflect.DeepEqual(tc.expected, got) {
|
||||
t.Errorf("expected %v but got %v", tc.expected, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTypeCheck(t *testing.T) {
|
||||
deploymentPolicy := &v1alpha1.ValidatingAdmissionPolicy{Spec: v1alpha1.ValidatingAdmissionPolicySpec{
|
||||
Validations: []v1alpha1.Validation{
|
||||
{
|
||||
Expression: "object.foo == 'bar'",
|
||||
},
|
||||
},
|
||||
MatchConstraints: &v1alpha1.MatchResources{ResourceRules: []v1alpha1.NamedRuleWithOperations{
|
||||
{
|
||||
RuleWithOperations: v1alpha1.RuleWithOperations{
|
||||
Rule: v1alpha1.Rule{
|
||||
APIGroups: []string{"apps"},
|
||||
APIVersions: []string{"v1"},
|
||||
Resources: []string{"deployments"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}},
|
||||
}}
|
||||
multiExpressionPolicy := &v1alpha1.ValidatingAdmissionPolicy{Spec: v1alpha1.ValidatingAdmissionPolicySpec{
|
||||
Validations: []v1alpha1.Validation{
|
||||
{
|
||||
Expression: "object.foo == 'bar'",
|
||||
},
|
||||
{
|
||||
Expression: "object.bar == 'foo'",
|
||||
},
|
||||
},
|
||||
MatchConstraints: &v1alpha1.MatchResources{ResourceRules: []v1alpha1.NamedRuleWithOperations{
|
||||
{
|
||||
RuleWithOperations: v1alpha1.RuleWithOperations{
|
||||
Rule: v1alpha1.Rule{
|
||||
APIGroups: []string{"apps"},
|
||||
APIVersions: []string{"v1"},
|
||||
Resources: []string{"deployments"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}},
|
||||
}}
|
||||
paramsRefPolicy := &v1alpha1.ValidatingAdmissionPolicy{Spec: v1alpha1.ValidatingAdmissionPolicySpec{
|
||||
ParamKind: &v1alpha1.ParamKind{
|
||||
APIVersion: "v1",
|
||||
Kind: "DoesNotMatter",
|
||||
},
|
||||
Validations: []v1alpha1.Validation{
|
||||
{
|
||||
Expression: "object.foo == params.bar",
|
||||
},
|
||||
},
|
||||
MatchConstraints: &v1alpha1.MatchResources{ResourceRules: []v1alpha1.NamedRuleWithOperations{
|
||||
{
|
||||
RuleWithOperations: v1alpha1.RuleWithOperations{
|
||||
Rule: v1alpha1.Rule{
|
||||
APIGroups: []string{"apps"},
|
||||
APIVersions: []string{"v1"},
|
||||
Resources: []string{"deployments"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}},
|
||||
}}
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
schemaToReturn *spec.Schema
|
||||
policy *v1alpha1.ValidatingAdmissionPolicy
|
||||
assertions []assertionFunc
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
policy: &v1alpha1.ValidatingAdmissionPolicy{},
|
||||
assertions: []assertionFunc{toBeEmpty},
|
||||
},
|
||||
{
|
||||
name: "unresolved schema",
|
||||
policy: deploymentPolicy,
|
||||
schemaToReturn: nil,
|
||||
assertions: []assertionFunc{toBeEmpty},
|
||||
},
|
||||
{
|
||||
name: "passed check",
|
||||
policy: deploymentPolicy,
|
||||
schemaToReturn: &spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"object"},
|
||||
Properties: map[string]spec.Schema{
|
||||
"foo": *spec.StringProperty(),
|
||||
},
|
||||
},
|
||||
},
|
||||
assertions: []assertionFunc{toBeEmpty},
|
||||
},
|
||||
{
|
||||
name: "undefined field",
|
||||
policy: deploymentPolicy,
|
||||
schemaToReturn: &spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"object"},
|
||||
Properties: map[string]spec.Schema{
|
||||
"bar": *spec.StringProperty(),
|
||||
},
|
||||
},
|
||||
},
|
||||
assertions: []assertionFunc{
|
||||
toHaveFieldRef("spec.validations[0].expression"),
|
||||
toContain(`undefined field 'foo'`),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "field type mismatch",
|
||||
policy: deploymentPolicy,
|
||||
schemaToReturn: &spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"object"},
|
||||
Properties: map[string]spec.Schema{
|
||||
"foo": *spec.Int64Property(),
|
||||
},
|
||||
},
|
||||
},
|
||||
assertions: []assertionFunc{
|
||||
toHaveFieldRef("spec.validations[0].expression"),
|
||||
toContain(`found no matching overload`),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "params",
|
||||
policy: paramsRefPolicy,
|
||||
schemaToReturn: &spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"object"},
|
||||
Properties: map[string]spec.Schema{
|
||||
"foo": *spec.StringProperty(),
|
||||
},
|
||||
},
|
||||
},
|
||||
assertions: []assertionFunc{
|
||||
toHaveFieldRef("spec.validations[0].expression"),
|
||||
toContain(`undefined field 'bar'`),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multiple expressions",
|
||||
policy: multiExpressionPolicy,
|
||||
schemaToReturn: &spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"object"},
|
||||
Properties: map[string]spec.Schema{
|
||||
"foo": *spec.StringProperty(),
|
||||
},
|
||||
},
|
||||
},
|
||||
assertions: []assertionFunc{
|
||||
toHaveFieldRef("spec.validations[1].expression"), // expressions[0] is okay, [1] is wrong
|
||||
toHaveLengthOf(1),
|
||||
},
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
typeChecker := buildTypeChecker(tc.schemaToReturn)
|
||||
warnings := typeChecker.Check(tc.policy)
|
||||
for _, a := range tc.assertions {
|
||||
a(warnings, t)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func buildTypeChecker(schemaToReturn *spec.Schema) *TypeChecker {
|
||||
restMapper := meta.NewDefaultRESTMapper([]schema.GroupVersion{
|
||||
{
|
||||
Group: "",
|
||||
Version: "v1",
|
||||
},
|
||||
})
|
||||
restMapper.Add(must3(scheme.ObjectKinds(&corev1.Pod{}))[0], meta.RESTScopeRoot)
|
||||
restMapper.Add(must3(scheme.ObjectKinds(&appsv1.Deployment{}))[0], meta.RESTScopeRoot)
|
||||
|
||||
return &TypeChecker{
|
||||
schemaResolver: &fakeSchemaResolver{schemaToReturn: schemaToReturn},
|
||||
restMapper: restMapper,
|
||||
}
|
||||
}
|
||||
|
||||
type fakeSchemaResolver struct {
|
||||
schemaToReturn *spec.Schema
|
||||
}
|
||||
|
||||
func (r *fakeSchemaResolver) ResolveSchema(gvk schema.GroupVersionKind) (*spec.Schema, error) {
|
||||
if r.schemaToReturn == nil {
|
||||
return nil, fmt.Errorf("cannot resolve for %v: %w", gvk, resolver.ErrSchemaNotFound)
|
||||
}
|
||||
return r.schemaToReturn, nil
|
||||
}
|
||||
|
||||
func toBeEmpty(warnings []v1alpha1.ExpressionWarning, t *testing.T) {
|
||||
if len(warnings) != 0 {
|
||||
t.Fatalf("expected empty but got %v", warnings)
|
||||
}
|
||||
}
|
||||
|
||||
func toContain(substring string) func(warnings []v1alpha1.ExpressionWarning, t *testing.T) {
|
||||
return func(warnings []v1alpha1.ExpressionWarning, t *testing.T) {
|
||||
if len(warnings) == 0 {
|
||||
t.Errorf("expected containing %q but got empty", substring)
|
||||
}
|
||||
for i, w := range warnings {
|
||||
if !strings.Contains(w.Warning, substring) {
|
||||
t.Errorf("warning %d does not contain %q, got %v", i, substring, w)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func toHaveLengthOf(expected int) func(warnings []v1alpha1.ExpressionWarning, t *testing.T) {
|
||||
return func(warnings []v1alpha1.ExpressionWarning, t *testing.T) {
|
||||
got := len(warnings)
|
||||
if expected != got {
|
||||
t.Errorf("expect warnings to have length of %d, but got %d", expected, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func toHaveFieldRef(paths ...string) func(warnings []v1alpha1.ExpressionWarning, t *testing.T) {
|
||||
return func(warnings []v1alpha1.ExpressionWarning, t *testing.T) {
|
||||
if len(paths) != len(warnings) {
|
||||
t.Errorf("expect warnings to have length of %d, but got %d", len(paths), len(warnings))
|
||||
}
|
||||
for i := range paths {
|
||||
if paths[i] != warnings[i].FieldRef {
|
||||
t.Errorf("wrong fieldRef at %d, expected %q but got %q", i, paths[i], warnings[i].FieldRef)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type assertionFunc func(warnings []v1alpha1.ExpressionWarning, t *testing.T)
|
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
Copyright 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.
|
||||
*/
|
||||
|
||||
// Code generated by applyconfiguration-gen. DO NOT EDIT.
|
||||
|
||||
package v1alpha1
|
||||
|
||||
// ExpressionWarningApplyConfiguration represents an declarative configuration of the ExpressionWarning type for use
|
||||
// with apply.
|
||||
type ExpressionWarningApplyConfiguration struct {
|
||||
FieldRef *string `json:"fieldRef,omitempty"`
|
||||
Warning *string `json:"warning,omitempty"`
|
||||
}
|
||||
|
||||
// ExpressionWarningApplyConfiguration constructs an declarative configuration of the ExpressionWarning type for use with
|
||||
// apply.
|
||||
func ExpressionWarning() *ExpressionWarningApplyConfiguration {
|
||||
return &ExpressionWarningApplyConfiguration{}
|
||||
}
|
||||
|
||||
// WithFieldRef sets the FieldRef field in the declarative configuration to the given value
|
||||
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||
// If called multiple times, the FieldRef field is set to the value of the last call.
|
||||
func (b *ExpressionWarningApplyConfiguration) WithFieldRef(value string) *ExpressionWarningApplyConfiguration {
|
||||
b.FieldRef = &value
|
||||
return b
|
||||
}
|
||||
|
||||
// WithWarning sets the Warning field in the declarative configuration to the given value
|
||||
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||
// If called multiple times, the Warning field is set to the value of the last call.
|
||||
func (b *ExpressionWarningApplyConfiguration) WithWarning(value string) *ExpressionWarningApplyConfiguration {
|
||||
b.Warning = &value
|
||||
return b
|
||||
}
|
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
Copyright 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.
|
||||
*/
|
||||
|
||||
// Code generated by applyconfiguration-gen. DO NOT EDIT.
|
||||
|
||||
package v1alpha1
|
||||
|
||||
// TypeCheckingApplyConfiguration represents an declarative configuration of the TypeChecking type for use
|
||||
// with apply.
|
||||
type TypeCheckingApplyConfiguration struct {
|
||||
ExpressionWarnings []ExpressionWarningApplyConfiguration `json:"expressionWarnings,omitempty"`
|
||||
}
|
||||
|
||||
// TypeCheckingApplyConfiguration constructs an declarative configuration of the TypeChecking type for use with
|
||||
// apply.
|
||||
func TypeChecking() *TypeCheckingApplyConfiguration {
|
||||
return &TypeCheckingApplyConfiguration{}
|
||||
}
|
||||
|
||||
// WithExpressionWarnings adds the given value to the ExpressionWarnings field in the declarative configuration
|
||||
// and returns the receiver, so that objects can be build by chaining "With" function invocations.
|
||||
// If called multiple times, values provided by each call will be appended to the ExpressionWarnings field.
|
||||
func (b *TypeCheckingApplyConfiguration) WithExpressionWarnings(values ...*ExpressionWarningApplyConfiguration) *TypeCheckingApplyConfiguration {
|
||||
for i := range values {
|
||||
if values[i] == nil {
|
||||
panic("nil value passed to WithExpressionWarnings")
|
||||
}
|
||||
b.ExpressionWarnings = append(b.ExpressionWarnings, *values[i])
|
||||
}
|
||||
return b
|
||||
}
|
@@ -32,7 +32,8 @@ import (
|
||||
type ValidatingAdmissionPolicyApplyConfiguration struct {
|
||||
v1.TypeMetaApplyConfiguration `json:",inline"`
|
||||
*v1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"`
|
||||
Spec *ValidatingAdmissionPolicySpecApplyConfiguration `json:"spec,omitempty"`
|
||||
Spec *ValidatingAdmissionPolicySpecApplyConfiguration `json:"spec,omitempty"`
|
||||
Status *ValidatingAdmissionPolicyStatusApplyConfiguration `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// ValidatingAdmissionPolicy constructs an declarative configuration of the ValidatingAdmissionPolicy type for use with
|
||||
@@ -245,3 +246,11 @@ func (b *ValidatingAdmissionPolicyApplyConfiguration) WithSpec(value *Validating
|
||||
b.Spec = value
|
||||
return b
|
||||
}
|
||||
|
||||
// WithStatus sets the Status field in the declarative configuration to the given value
|
||||
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||
// If called multiple times, the Status field is set to the value of the last call.
|
||||
func (b *ValidatingAdmissionPolicyApplyConfiguration) WithStatus(value *ValidatingAdmissionPolicyStatusApplyConfiguration) *ValidatingAdmissionPolicyApplyConfiguration {
|
||||
b.Status = value
|
||||
return b
|
||||
}
|
||||
|
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
Copyright 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.
|
||||
*/
|
||||
|
||||
// Code generated by applyconfiguration-gen. DO NOT EDIT.
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
v1 "k8s.io/client-go/applyconfigurations/meta/v1"
|
||||
)
|
||||
|
||||
// ValidatingAdmissionPolicyStatusApplyConfiguration represents an declarative configuration of the ValidatingAdmissionPolicyStatus type for use
|
||||
// with apply.
|
||||
type ValidatingAdmissionPolicyStatusApplyConfiguration struct {
|
||||
ObservedGeneration *int64 `json:"observedGeneration,omitempty"`
|
||||
TypeChecking *TypeCheckingApplyConfiguration `json:"typeChecking,omitempty"`
|
||||
Conditions []v1.ConditionApplyConfiguration `json:"conditions,omitempty"`
|
||||
}
|
||||
|
||||
// ValidatingAdmissionPolicyStatusApplyConfiguration constructs an declarative configuration of the ValidatingAdmissionPolicyStatus type for use with
|
||||
// apply.
|
||||
func ValidatingAdmissionPolicyStatus() *ValidatingAdmissionPolicyStatusApplyConfiguration {
|
||||
return &ValidatingAdmissionPolicyStatusApplyConfiguration{}
|
||||
}
|
||||
|
||||
// WithObservedGeneration sets the ObservedGeneration field in the declarative configuration to the given value
|
||||
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||
// If called multiple times, the ObservedGeneration field is set to the value of the last call.
|
||||
func (b *ValidatingAdmissionPolicyStatusApplyConfiguration) WithObservedGeneration(value int64) *ValidatingAdmissionPolicyStatusApplyConfiguration {
|
||||
b.ObservedGeneration = &value
|
||||
return b
|
||||
}
|
||||
|
||||
// WithTypeChecking sets the TypeChecking field in the declarative configuration to the given value
|
||||
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||
// If called multiple times, the TypeChecking field is set to the value of the last call.
|
||||
func (b *ValidatingAdmissionPolicyStatusApplyConfiguration) WithTypeChecking(value *TypeCheckingApplyConfiguration) *ValidatingAdmissionPolicyStatusApplyConfiguration {
|
||||
b.TypeChecking = value
|
||||
return b
|
||||
}
|
||||
|
||||
// WithConditions adds the given value to the Conditions field in the declarative configuration
|
||||
// and returns the receiver, so that objects can be build by chaining "With" function invocations.
|
||||
// If called multiple times, values provided by each call will be appended to the Conditions field.
|
||||
func (b *ValidatingAdmissionPolicyStatusApplyConfiguration) WithConditions(values ...*v1.ConditionApplyConfiguration) *ValidatingAdmissionPolicyStatusApplyConfiguration {
|
||||
for i := range values {
|
||||
if values[i] == nil {
|
||||
panic("nil value passed to WithConditions")
|
||||
}
|
||||
b.Conditions = append(b.Conditions, *values[i])
|
||||
}
|
||||
return b
|
||||
}
|
@@ -236,6 +236,17 @@ var schemaYAML = typed.YAMLObject(`types:
|
||||
type:
|
||||
scalar: string
|
||||
default: ""
|
||||
- name: io.k8s.api.admissionregistration.v1alpha1.ExpressionWarning
|
||||
map:
|
||||
fields:
|
||||
- name: fieldRef
|
||||
type:
|
||||
scalar: string
|
||||
default: ""
|
||||
- name: warning
|
||||
type:
|
||||
scalar: string
|
||||
default: ""
|
||||
- name: io.k8s.api.admissionregistration.v1alpha1.MatchResources
|
||||
map:
|
||||
fields:
|
||||
@@ -318,6 +329,15 @@ var schemaYAML = typed.YAMLObject(`types:
|
||||
type:
|
||||
scalar: string
|
||||
elementRelationship: atomic
|
||||
- name: io.k8s.api.admissionregistration.v1alpha1.TypeChecking
|
||||
map:
|
||||
fields:
|
||||
- name: expressionWarnings
|
||||
type:
|
||||
list:
|
||||
elementType:
|
||||
namedType: io.k8s.api.admissionregistration.v1alpha1.ExpressionWarning
|
||||
elementRelationship: atomic
|
||||
- name: io.k8s.api.admissionregistration.v1alpha1.ValidatingAdmissionPolicy
|
||||
map:
|
||||
fields:
|
||||
@@ -335,6 +355,10 @@ var schemaYAML = typed.YAMLObject(`types:
|
||||
type:
|
||||
namedType: io.k8s.api.admissionregistration.v1alpha1.ValidatingAdmissionPolicySpec
|
||||
default: {}
|
||||
- name: status
|
||||
type:
|
||||
namedType: io.k8s.api.admissionregistration.v1alpha1.ValidatingAdmissionPolicyStatus
|
||||
default: {}
|
||||
- name: io.k8s.api.admissionregistration.v1alpha1.ValidatingAdmissionPolicyBinding
|
||||
map:
|
||||
fields:
|
||||
@@ -394,6 +418,23 @@ var schemaYAML = typed.YAMLObject(`types:
|
||||
elementType:
|
||||
namedType: io.k8s.api.admissionregistration.v1alpha1.Validation
|
||||
elementRelationship: atomic
|
||||
- name: io.k8s.api.admissionregistration.v1alpha1.ValidatingAdmissionPolicyStatus
|
||||
map:
|
||||
fields:
|
||||
- name: conditions
|
||||
type:
|
||||
list:
|
||||
elementType:
|
||||
namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Condition
|
||||
elementRelationship: associative
|
||||
keys:
|
||||
- type
|
||||
- name: observedGeneration
|
||||
type:
|
||||
scalar: numeric
|
||||
- name: typeChecking
|
||||
type:
|
||||
namedType: io.k8s.api.admissionregistration.v1alpha1.TypeChecking
|
||||
- name: io.k8s.api.admissionregistration.v1alpha1.Validation
|
||||
map:
|
||||
fields:
|
||||
|
@@ -141,6 +141,8 @@ func ForKind(kind schema.GroupVersionKind) interface{} {
|
||||
// Group=admissionregistration.k8s.io, Version=v1alpha1
|
||||
case v1alpha1.SchemeGroupVersion.WithKind("AuditAnnotation"):
|
||||
return &admissionregistrationv1alpha1.AuditAnnotationApplyConfiguration{}
|
||||
case v1alpha1.SchemeGroupVersion.WithKind("ExpressionWarning"):
|
||||
return &admissionregistrationv1alpha1.ExpressionWarningApplyConfiguration{}
|
||||
case v1alpha1.SchemeGroupVersion.WithKind("MatchResources"):
|
||||
return &admissionregistrationv1alpha1.MatchResourcesApplyConfiguration{}
|
||||
case v1alpha1.SchemeGroupVersion.WithKind("NamedRuleWithOperations"):
|
||||
@@ -149,6 +151,8 @@ func ForKind(kind schema.GroupVersionKind) interface{} {
|
||||
return &admissionregistrationv1alpha1.ParamKindApplyConfiguration{}
|
||||
case v1alpha1.SchemeGroupVersion.WithKind("ParamRef"):
|
||||
return &admissionregistrationv1alpha1.ParamRefApplyConfiguration{}
|
||||
case v1alpha1.SchemeGroupVersion.WithKind("TypeChecking"):
|
||||
return &admissionregistrationv1alpha1.TypeCheckingApplyConfiguration{}
|
||||
case v1alpha1.SchemeGroupVersion.WithKind("ValidatingAdmissionPolicy"):
|
||||
return &admissionregistrationv1alpha1.ValidatingAdmissionPolicyApplyConfiguration{}
|
||||
case v1alpha1.SchemeGroupVersion.WithKind("ValidatingAdmissionPolicyBinding"):
|
||||
@@ -157,6 +161,8 @@ func ForKind(kind schema.GroupVersionKind) interface{} {
|
||||
return &admissionregistrationv1alpha1.ValidatingAdmissionPolicyBindingSpecApplyConfiguration{}
|
||||
case v1alpha1.SchemeGroupVersion.WithKind("ValidatingAdmissionPolicySpec"):
|
||||
return &admissionregistrationv1alpha1.ValidatingAdmissionPolicySpecApplyConfiguration{}
|
||||
case v1alpha1.SchemeGroupVersion.WithKind("ValidatingAdmissionPolicyStatus"):
|
||||
return &admissionregistrationv1alpha1.ValidatingAdmissionPolicyStatusApplyConfiguration{}
|
||||
case v1alpha1.SchemeGroupVersion.WithKind("Validation"):
|
||||
return &admissionregistrationv1alpha1.ValidationApplyConfiguration{}
|
||||
|
||||
|
@@ -98,6 +98,17 @@ func (c *FakeValidatingAdmissionPolicies) Update(ctx context.Context, validating
|
||||
return obj.(*v1alpha1.ValidatingAdmissionPolicy), err
|
||||
}
|
||||
|
||||
// UpdateStatus was generated because the type contains a Status member.
|
||||
// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
|
||||
func (c *FakeValidatingAdmissionPolicies) UpdateStatus(ctx context.Context, validatingAdmissionPolicy *v1alpha1.ValidatingAdmissionPolicy, opts v1.UpdateOptions) (*v1alpha1.ValidatingAdmissionPolicy, error) {
|
||||
obj, err := c.Fake.
|
||||
Invokes(testing.NewRootUpdateSubresourceAction(validatingadmissionpoliciesResource, "status", validatingAdmissionPolicy), &v1alpha1.ValidatingAdmissionPolicy{})
|
||||
if obj == nil {
|
||||
return nil, err
|
||||
}
|
||||
return obj.(*v1alpha1.ValidatingAdmissionPolicy), err
|
||||
}
|
||||
|
||||
// Delete takes name of the validatingAdmissionPolicy and deletes it. Returns an error if one occurs.
|
||||
func (c *FakeValidatingAdmissionPolicies) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
|
||||
_, err := c.Fake.
|
||||
@@ -143,3 +154,25 @@ func (c *FakeValidatingAdmissionPolicies) Apply(ctx context.Context, validatingA
|
||||
}
|
||||
return obj.(*v1alpha1.ValidatingAdmissionPolicy), err
|
||||
}
|
||||
|
||||
// ApplyStatus was generated because the type contains a Status member.
|
||||
// Add a +genclient:noStatus comment above the type to avoid generating ApplyStatus().
|
||||
func (c *FakeValidatingAdmissionPolicies) ApplyStatus(ctx context.Context, validatingAdmissionPolicy *admissionregistrationv1alpha1.ValidatingAdmissionPolicyApplyConfiguration, opts v1.ApplyOptions) (result *v1alpha1.ValidatingAdmissionPolicy, err error) {
|
||||
if validatingAdmissionPolicy == nil {
|
||||
return nil, fmt.Errorf("validatingAdmissionPolicy provided to Apply must not be nil")
|
||||
}
|
||||
data, err := json.Marshal(validatingAdmissionPolicy)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
name := validatingAdmissionPolicy.Name
|
||||
if name == nil {
|
||||
return nil, fmt.Errorf("validatingAdmissionPolicy.Name must be provided to Apply")
|
||||
}
|
||||
obj, err := c.Fake.
|
||||
Invokes(testing.NewRootPatchSubresourceAction(validatingadmissionpoliciesResource, *name, types.ApplyPatchType, data, "status"), &v1alpha1.ValidatingAdmissionPolicy{})
|
||||
if obj == nil {
|
||||
return nil, err
|
||||
}
|
||||
return obj.(*v1alpha1.ValidatingAdmissionPolicy), err
|
||||
}
|
||||
|
@@ -43,6 +43,7 @@ type ValidatingAdmissionPoliciesGetter interface {
|
||||
type ValidatingAdmissionPolicyInterface interface {
|
||||
Create(ctx context.Context, validatingAdmissionPolicy *v1alpha1.ValidatingAdmissionPolicy, opts v1.CreateOptions) (*v1alpha1.ValidatingAdmissionPolicy, error)
|
||||
Update(ctx context.Context, validatingAdmissionPolicy *v1alpha1.ValidatingAdmissionPolicy, opts v1.UpdateOptions) (*v1alpha1.ValidatingAdmissionPolicy, error)
|
||||
UpdateStatus(ctx context.Context, validatingAdmissionPolicy *v1alpha1.ValidatingAdmissionPolicy, opts v1.UpdateOptions) (*v1alpha1.ValidatingAdmissionPolicy, error)
|
||||
Delete(ctx context.Context, name string, opts v1.DeleteOptions) error
|
||||
DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error
|
||||
Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha1.ValidatingAdmissionPolicy, error)
|
||||
@@ -50,6 +51,7 @@ type ValidatingAdmissionPolicyInterface interface {
|
||||
Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error)
|
||||
Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.ValidatingAdmissionPolicy, err error)
|
||||
Apply(ctx context.Context, validatingAdmissionPolicy *admissionregistrationv1alpha1.ValidatingAdmissionPolicyApplyConfiguration, opts v1.ApplyOptions) (result *v1alpha1.ValidatingAdmissionPolicy, err error)
|
||||
ApplyStatus(ctx context.Context, validatingAdmissionPolicy *admissionregistrationv1alpha1.ValidatingAdmissionPolicyApplyConfiguration, opts v1.ApplyOptions) (result *v1alpha1.ValidatingAdmissionPolicy, err error)
|
||||
ValidatingAdmissionPolicyExpansion
|
||||
}
|
||||
|
||||
@@ -132,6 +134,21 @@ func (c *validatingAdmissionPolicies) Update(ctx context.Context, validatingAdmi
|
||||
return
|
||||
}
|
||||
|
||||
// UpdateStatus was generated because the type contains a Status member.
|
||||
// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
|
||||
func (c *validatingAdmissionPolicies) UpdateStatus(ctx context.Context, validatingAdmissionPolicy *v1alpha1.ValidatingAdmissionPolicy, opts v1.UpdateOptions) (result *v1alpha1.ValidatingAdmissionPolicy, err error) {
|
||||
result = &v1alpha1.ValidatingAdmissionPolicy{}
|
||||
err = c.client.Put().
|
||||
Resource("validatingadmissionpolicies").
|
||||
Name(validatingAdmissionPolicy.Name).
|
||||
SubResource("status").
|
||||
VersionedParams(&opts, scheme.ParameterCodec).
|
||||
Body(validatingAdmissionPolicy).
|
||||
Do(ctx).
|
||||
Into(result)
|
||||
return
|
||||
}
|
||||
|
||||
// Delete takes name of the validatingAdmissionPolicy and deletes it. Returns an error if one occurs.
|
||||
func (c *validatingAdmissionPolicies) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
|
||||
return c.client.Delete().
|
||||
@@ -195,3 +212,32 @@ func (c *validatingAdmissionPolicies) Apply(ctx context.Context, validatingAdmis
|
||||
Into(result)
|
||||
return
|
||||
}
|
||||
|
||||
// ApplyStatus was generated because the type contains a Status member.
|
||||
// Add a +genclient:noStatus comment above the type to avoid generating ApplyStatus().
|
||||
func (c *validatingAdmissionPolicies) ApplyStatus(ctx context.Context, validatingAdmissionPolicy *admissionregistrationv1alpha1.ValidatingAdmissionPolicyApplyConfiguration, opts v1.ApplyOptions) (result *v1alpha1.ValidatingAdmissionPolicy, err error) {
|
||||
if validatingAdmissionPolicy == nil {
|
||||
return nil, fmt.Errorf("validatingAdmissionPolicy provided to Apply must not be nil")
|
||||
}
|
||||
patchOpts := opts.ToPatchOptions()
|
||||
data, err := json.Marshal(validatingAdmissionPolicy)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
name := validatingAdmissionPolicy.Name
|
||||
if name == nil {
|
||||
return nil, fmt.Errorf("validatingAdmissionPolicy.Name must be provided to Apply")
|
||||
}
|
||||
|
||||
result = &v1alpha1.ValidatingAdmissionPolicy{}
|
||||
err = c.client.Patch(types.ApplyPatchType).
|
||||
Resource("validatingadmissionpolicies").
|
||||
Name(*name).
|
||||
SubResource("status").
|
||||
VersionedParams(&patchOpts, scheme.ParameterCodec).
|
||||
Body(data).
|
||||
Do(ctx).
|
||||
Into(result)
|
||||
return
|
||||
}
|
||||
|
@@ -24,6 +24,7 @@ require (
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
||||
github.com/NYTimes/gziphandler v1.1.1 // indirect
|
||||
github.com/antlr/antlr4/runtime/Go/antlr v1.4.10 // indirect
|
||||
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/blang/semver/v4 v4.0.0 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.1.3 // indirect
|
||||
@@ -56,6 +57,7 @@ require (
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2 // indirect
|
||||
github.com/mitchellh/mapstructure v1.4.1 // indirect
|
||||
github.com/moby/term v0.0.0-20221205130635-1aeaba878587 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
|
4
staging/src/k8s.io/cloud-provider/go.sum
generated
4
staging/src/k8s.io/cloud-provider/go.sum
generated
@@ -48,6 +48,8 @@ github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk5
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
github.com/antlr/antlr4/runtime/Go/antlr v1.4.10 h1:yL7+Jz0jTC6yykIK/Wh74gnTJnrGr5AyrNMXuA0gves=
|
||||
github.com/antlr/antlr4/runtime/Go/antlr v1.4.10/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY=
|
||||
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA=
|
||||
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
||||
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
|
||||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
@@ -250,6 +252,8 @@ github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJ
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2 h1:hAHbPm5IJGijwng3PWk09JkG9WeqChjprR5s9bBZ+OM=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
|
||||
github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag=
|
||||
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/moby/term v0.0.0-20221205130635-1aeaba878587 h1:HfkjXDfhgVaN5rmueG8cL8KKeFNecRCXFhaJ2qZ5SKA=
|
||||
github.com/moby/term v0.0.0-20221205130635-1aeaba878587/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
|
@@ -20,6 +20,7 @@ require (
|
||||
require (
|
||||
github.com/NYTimes/gziphandler v1.1.1 // indirect
|
||||
github.com/antlr/antlr4/runtime/Go/antlr v1.4.10 // indirect
|
||||
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/blang/semver/v4 v4.0.0 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.1.3 // indirect
|
||||
@@ -52,6 +53,7 @@ require (
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2 // indirect
|
||||
github.com/mitchellh/mapstructure v1.4.1 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
|
4
staging/src/k8s.io/controller-manager/go.sum
generated
4
staging/src/k8s.io/controller-manager/go.sum
generated
@@ -45,6 +45,8 @@ github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk5
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
github.com/antlr/antlr4/runtime/Go/antlr v1.4.10 h1:yL7+Jz0jTC6yykIK/Wh74gnTJnrGr5AyrNMXuA0gves=
|
||||
github.com/antlr/antlr4/runtime/Go/antlr v1.4.10/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY=
|
||||
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA=
|
||||
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
||||
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
|
||||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
@@ -245,6 +247,8 @@ github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJ
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2 h1:hAHbPm5IJGijwng3PWk09JkG9WeqChjprR5s9bBZ+OM=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
|
||||
github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag=
|
||||
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
|
@@ -28,6 +28,7 @@ require (
|
||||
require (
|
||||
github.com/NYTimes/gziphandler v1.1.1 // indirect
|
||||
github.com/antlr/antlr4/runtime/Go/antlr v1.4.10 // indirect
|
||||
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/blang/semver/v4 v4.0.0 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.1.3 // indirect
|
||||
@@ -56,6 +57,7 @@ require (
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2 // indirect
|
||||
github.com/mitchellh/mapstructure v1.4.1 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
|
4
staging/src/k8s.io/kube-aggregator/go.sum
generated
4
staging/src/k8s.io/kube-aggregator/go.sum
generated
@@ -46,6 +46,8 @@ github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk5
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
github.com/antlr/antlr4/runtime/Go/antlr v1.4.10 h1:yL7+Jz0jTC6yykIK/Wh74gnTJnrGr5AyrNMXuA0gves=
|
||||
github.com/antlr/antlr4/runtime/Go/antlr v1.4.10/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY=
|
||||
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA=
|
||||
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
||||
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
|
||||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
@@ -247,6 +249,8 @@ github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJ
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2 h1:hAHbPm5IJGijwng3PWk09JkG9WeqChjprR5s9bBZ+OM=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
|
||||
github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag=
|
||||
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
|
@@ -23,6 +23,7 @@ require (
|
||||
require (
|
||||
github.com/NYTimes/gziphandler v1.1.1 // indirect
|
||||
github.com/antlr/antlr4/runtime/Go/antlr v1.4.10 // indirect
|
||||
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.1.3 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
@@ -54,6 +55,7 @@ require (
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2 // indirect
|
||||
github.com/mitchellh/mapstructure v1.4.1 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
|
4
staging/src/k8s.io/pod-security-admission/go.sum
generated
4
staging/src/k8s.io/pod-security-admission/go.sum
generated
@@ -46,6 +46,8 @@ github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk5
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
github.com/antlr/antlr4/runtime/Go/antlr v1.4.10 h1:yL7+Jz0jTC6yykIK/Wh74gnTJnrGr5AyrNMXuA0gves=
|
||||
github.com/antlr/antlr4/runtime/Go/antlr v1.4.10/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY=
|
||||
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA=
|
||||
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
||||
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
|
||||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
@@ -247,6 +249,8 @@ github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJ
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2 h1:hAHbPm5IJGijwng3PWk09JkG9WeqChjprR5s9bBZ+OM=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
|
||||
github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag=
|
||||
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
|
@@ -20,6 +20,7 @@ require (
|
||||
require (
|
||||
github.com/NYTimes/gziphandler v1.1.1 // indirect
|
||||
github.com/antlr/antlr4/runtime/Go/antlr v1.4.10 // indirect
|
||||
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/blang/semver/v4 v4.0.0 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.1.3 // indirect
|
||||
@@ -51,6 +52,7 @@ require (
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2 // indirect
|
||||
github.com/mitchellh/mapstructure v1.4.1 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
|
4
staging/src/k8s.io/sample-apiserver/go.sum
generated
4
staging/src/k8s.io/sample-apiserver/go.sum
generated
@@ -46,6 +46,8 @@ github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk5
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
github.com/antlr/antlr4/runtime/Go/antlr v1.4.10 h1:yL7+Jz0jTC6yykIK/Wh74gnTJnrGr5AyrNMXuA0gves=
|
||||
github.com/antlr/antlr4/runtime/Go/antlr v1.4.10/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY=
|
||||
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA=
|
||||
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
||||
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
|
||||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
@@ -247,6 +249,8 @@ github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJ
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2 h1:hAHbPm5IJGijwng3PWk09JkG9WeqChjprR5s9bBZ+OM=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.2/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
|
||||
github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag=
|
||||
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
|
Reference in New Issue
Block a user