mirror of
https://github.com/ceph/ceph-csi.git
synced 2025-06-13 10:33:35 +00:00
build: move e2e dependencies into e2e/go.mod
Several packages are only used while running the e2e suite. These packages are less important to update, as the they can not influence the final executable that is part of the Ceph-CSI container-image. By moving these dependencies out of the main Ceph-CSI go.mod, it is easier to identify if a reported CVE affects Ceph-CSI, or only the testing (like most of the Kubernetes CVEs). Signed-off-by: Niels de Vos <ndevos@ibm.com>
This commit is contained in:
committed by
mergify[bot]
parent
15da101b1b
commit
bec6090996
23
vendor/k8s.io/api/imagepolicy/v1alpha1/doc.go
generated
vendored
23
vendor/k8s.io/api/imagepolicy/v1alpha1/doc.go
generated
vendored
@ -1,23 +0,0 @@
|
||||
/*
|
||||
Copyright 2016 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.
|
||||
*/
|
||||
|
||||
// +k8s:deepcopy-gen=package
|
||||
// +k8s:protobuf-gen=package
|
||||
// +k8s:openapi-gen=true
|
||||
|
||||
// +groupName=imagepolicy.k8s.io
|
||||
|
||||
package v1alpha1 // import "k8s.io/api/imagepolicy/v1alpha1"
|
1374
vendor/k8s.io/api/imagepolicy/v1alpha1/generated.pb.go
generated
vendored
1374
vendor/k8s.io/api/imagepolicy/v1alpha1/generated.pb.go
generated
vendored
File diff suppressed because it is too large
Load Diff
89
vendor/k8s.io/api/imagepolicy/v1alpha1/generated.proto
generated
vendored
89
vendor/k8s.io/api/imagepolicy/v1alpha1/generated.proto
generated
vendored
@ -1,89 +0,0 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
|
||||
// This file was autogenerated by go-to-protobuf. Do not edit it manually!
|
||||
|
||||
syntax = "proto2";
|
||||
|
||||
package k8s.io.api.imagepolicy.v1alpha1;
|
||||
|
||||
import "k8s.io/apimachinery/pkg/apis/meta/v1/generated.proto";
|
||||
import "k8s.io/apimachinery/pkg/runtime/generated.proto";
|
||||
import "k8s.io/apimachinery/pkg/runtime/schema/generated.proto";
|
||||
|
||||
// Package-wide variables from generator "generated".
|
||||
option go_package = "k8s.io/api/imagepolicy/v1alpha1";
|
||||
|
||||
// ImageReview checks if the set of images in a pod are allowed.
|
||||
message ImageReview {
|
||||
// Standard object's metadata.
|
||||
// More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata
|
||||
// +optional
|
||||
optional .k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1;
|
||||
|
||||
// Spec holds information about the pod being evaluated
|
||||
optional ImageReviewSpec spec = 2;
|
||||
|
||||
// Status is filled in by the backend and indicates whether the pod should be allowed.
|
||||
// +optional
|
||||
optional ImageReviewStatus status = 3;
|
||||
}
|
||||
|
||||
// ImageReviewContainerSpec is a description of a container within the pod creation request.
|
||||
message ImageReviewContainerSpec {
|
||||
// This can be in the form image:tag or image@SHA:012345679abcdef.
|
||||
// +optional
|
||||
optional string image = 1;
|
||||
}
|
||||
|
||||
// ImageReviewSpec is a description of the pod creation request.
|
||||
message ImageReviewSpec {
|
||||
// Containers is a list of a subset of the information in each container of the Pod being created.
|
||||
// +optional
|
||||
// +listType=atomic
|
||||
repeated ImageReviewContainerSpec containers = 1;
|
||||
|
||||
// Annotations is a list of key-value pairs extracted from the Pod's annotations.
|
||||
// It only includes keys which match the pattern `*.image-policy.k8s.io/*`.
|
||||
// It is up to each webhook backend to determine how to interpret these annotations, if at all.
|
||||
// +optional
|
||||
map<string, string> annotations = 2;
|
||||
|
||||
// Namespace is the namespace the pod is being created in.
|
||||
// +optional
|
||||
optional string namespace = 3;
|
||||
}
|
||||
|
||||
// ImageReviewStatus is the result of the review for the pod creation request.
|
||||
message ImageReviewStatus {
|
||||
// Allowed indicates that all images were allowed to be run.
|
||||
optional bool allowed = 1;
|
||||
|
||||
// Reason should be empty unless Allowed is false in which case it
|
||||
// may contain a short description of what is wrong. Kubernetes
|
||||
// may truncate excessively long errors when displaying to the user.
|
||||
// +optional
|
||||
optional string reason = 2;
|
||||
|
||||
// AuditAnnotations will be added to the attributes object of the
|
||||
// admission controller request using 'AddAnnotation'. The keys should
|
||||
// be prefix-less (i.e., the admission controller will add an
|
||||
// appropriate prefix).
|
||||
// +optional
|
||||
map<string, string> auditAnnotations = 3;
|
||||
}
|
||||
|
51
vendor/k8s.io/api/imagepolicy/v1alpha1/register.go
generated
vendored
51
vendor/k8s.io/api/imagepolicy/v1alpha1/register.go
generated
vendored
@ -1,51 +0,0 @@
|
||||
/*
|
||||
Copyright 2016 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 v1alpha1
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
// GroupName is the group name for this API.
|
||||
const GroupName = "imagepolicy.k8s.io"
|
||||
|
||||
// SchemeGroupVersion is group version used to register these objects
|
||||
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1alpha1"}
|
||||
|
||||
// Resource takes an unqualified resource and returns a Group qualified GroupResource
|
||||
func Resource(resource string) schema.GroupResource {
|
||||
return SchemeGroupVersion.WithResource(resource).GroupResource()
|
||||
}
|
||||
|
||||
var (
|
||||
// TODO: move SchemeBuilder with zz_generated.deepcopy.go to k8s.io/api.
|
||||
// localSchemeBuilder and AddToScheme will stay in k8s.io/kubernetes.
|
||||
SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
|
||||
localSchemeBuilder = &SchemeBuilder
|
||||
AddToScheme = localSchemeBuilder.AddToScheme
|
||||
)
|
||||
|
||||
// Adds the list of known types to the given scheme.
|
||||
func addKnownTypes(scheme *runtime.Scheme) error {
|
||||
scheme.AddKnownTypes(SchemeGroupVersion,
|
||||
&ImageReview{},
|
||||
)
|
||||
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
|
||||
return nil
|
||||
}
|
83
vendor/k8s.io/api/imagepolicy/v1alpha1/types.go
generated
vendored
83
vendor/k8s.io/api/imagepolicy/v1alpha1/types.go
generated
vendored
@ -1,83 +0,0 @@
|
||||
/*
|
||||
Copyright 2016 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 v1alpha1
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// +genclient
|
||||
// +genclient:nonNamespaced
|
||||
// +genclient:noVerbs
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
|
||||
// ImageReview checks if the set of images in a pod are allowed.
|
||||
type ImageReview struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
// Standard object's metadata.
|
||||
// More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata
|
||||
// +optional
|
||||
metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
|
||||
|
||||
// Spec holds information about the pod being evaluated
|
||||
Spec ImageReviewSpec `json:"spec" protobuf:"bytes,2,opt,name=spec"`
|
||||
|
||||
// Status is filled in by the backend and indicates whether the pod should be allowed.
|
||||
// +optional
|
||||
Status ImageReviewStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"`
|
||||
}
|
||||
|
||||
// ImageReviewSpec is a description of the pod creation request.
|
||||
type ImageReviewSpec struct {
|
||||
// Containers is a list of a subset of the information in each container of the Pod being created.
|
||||
// +optional
|
||||
// +listType=atomic
|
||||
Containers []ImageReviewContainerSpec `json:"containers,omitempty" protobuf:"bytes,1,rep,name=containers"`
|
||||
// Annotations is a list of key-value pairs extracted from the Pod's annotations.
|
||||
// It only includes keys which match the pattern `*.image-policy.k8s.io/*`.
|
||||
// It is up to each webhook backend to determine how to interpret these annotations, if at all.
|
||||
// +optional
|
||||
Annotations map[string]string `json:"annotations,omitempty" protobuf:"bytes,2,rep,name=annotations"`
|
||||
// Namespace is the namespace the pod is being created in.
|
||||
// +optional
|
||||
Namespace string `json:"namespace,omitempty" protobuf:"bytes,3,opt,name=namespace"`
|
||||
}
|
||||
|
||||
// ImageReviewContainerSpec is a description of a container within the pod creation request.
|
||||
type ImageReviewContainerSpec struct {
|
||||
// This can be in the form image:tag or image@SHA:012345679abcdef.
|
||||
// +optional
|
||||
Image string `json:"image,omitempty" protobuf:"bytes,1,opt,name=image"`
|
||||
// In future, we may add command line overrides, exec health check command lines, and so on.
|
||||
}
|
||||
|
||||
// ImageReviewStatus is the result of the review for the pod creation request.
|
||||
type ImageReviewStatus struct {
|
||||
// Allowed indicates that all images were allowed to be run.
|
||||
Allowed bool `json:"allowed" protobuf:"varint,1,opt,name=allowed"`
|
||||
// Reason should be empty unless Allowed is false in which case it
|
||||
// may contain a short description of what is wrong. Kubernetes
|
||||
// may truncate excessively long errors when displaying to the user.
|
||||
// +optional
|
||||
Reason string `json:"reason,omitempty" protobuf:"bytes,2,opt,name=reason"`
|
||||
// AuditAnnotations will be added to the attributes object of the
|
||||
// admission controller request using 'AddAnnotation'. The keys should
|
||||
// be prefix-less (i.e., the admission controller will add an
|
||||
// appropriate prefix).
|
||||
// +optional
|
||||
AuditAnnotations map[string]string `json:"auditAnnotations,omitempty" protobuf:"bytes,3,rep,name=auditAnnotations"`
|
||||
}
|
72
vendor/k8s.io/api/imagepolicy/v1alpha1/types_swagger_doc_generated.go
generated
vendored
72
vendor/k8s.io/api/imagepolicy/v1alpha1/types_swagger_doc_generated.go
generated
vendored
@ -1,72 +0,0 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package v1alpha1
|
||||
|
||||
// This file contains a collection of methods that can be used from go-restful to
|
||||
// generate Swagger API documentation for its models. Please read this PR for more
|
||||
// information on the implementation: https://github.com/emicklei/go-restful/pull/215
|
||||
//
|
||||
// TODOs are ignored from the parser (e.g. TODO(andronat):... || TODO:...) if and only if
|
||||
// they are on one line! For multiple line or blocks that you want to ignore use ---.
|
||||
// Any context after a --- is ignored.
|
||||
//
|
||||
// Those methods can be generated by using hack/update-codegen.sh
|
||||
|
||||
// AUTO-GENERATED FUNCTIONS START HERE. DO NOT EDIT.
|
||||
var map_ImageReview = map[string]string{
|
||||
"": "ImageReview checks if the set of images in a pod are allowed.",
|
||||
"metadata": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata",
|
||||
"spec": "Spec holds information about the pod being evaluated",
|
||||
"status": "Status is filled in by the backend and indicates whether the pod should be allowed.",
|
||||
}
|
||||
|
||||
func (ImageReview) SwaggerDoc() map[string]string {
|
||||
return map_ImageReview
|
||||
}
|
||||
|
||||
var map_ImageReviewContainerSpec = map[string]string{
|
||||
"": "ImageReviewContainerSpec is a description of a container within the pod creation request.",
|
||||
"image": "This can be in the form image:tag or image@SHA:012345679abcdef.",
|
||||
}
|
||||
|
||||
func (ImageReviewContainerSpec) SwaggerDoc() map[string]string {
|
||||
return map_ImageReviewContainerSpec
|
||||
}
|
||||
|
||||
var map_ImageReviewSpec = map[string]string{
|
||||
"": "ImageReviewSpec is a description of the pod creation request.",
|
||||
"containers": "Containers is a list of a subset of the information in each container of the Pod being created.",
|
||||
"annotations": "Annotations is a list of key-value pairs extracted from the Pod's annotations. It only includes keys which match the pattern `*.image-policy.k8s.io/*`. It is up to each webhook backend to determine how to interpret these annotations, if at all.",
|
||||
"namespace": "Namespace is the namespace the pod is being created in.",
|
||||
}
|
||||
|
||||
func (ImageReviewSpec) SwaggerDoc() map[string]string {
|
||||
return map_ImageReviewSpec
|
||||
}
|
||||
|
||||
var map_ImageReviewStatus = map[string]string{
|
||||
"": "ImageReviewStatus is the result of the review for the pod creation request.",
|
||||
"allowed": "Allowed indicates that all images were allowed to be run.",
|
||||
"reason": "Reason should be empty unless Allowed is false in which case it may contain a short description of what is wrong. Kubernetes may truncate excessively long errors when displaying to the user.",
|
||||
"auditAnnotations": "AuditAnnotations will be added to the attributes object of the admission controller request using 'AddAnnotation'. The keys should be prefix-less (i.e., the admission controller will add an appropriate prefix).",
|
||||
}
|
||||
|
||||
func (ImageReviewStatus) SwaggerDoc() map[string]string {
|
||||
return map_ImageReviewStatus
|
||||
}
|
||||
|
||||
// AUTO-GENERATED FUNCTIONS END HERE
|
121
vendor/k8s.io/api/imagepolicy/v1alpha1/zz_generated.deepcopy.go
generated
vendored
121
vendor/k8s.io/api/imagepolicy/v1alpha1/zz_generated.deepcopy.go
generated
vendored
@ -1,121 +0,0 @@
|
||||
//go:build !ignore_autogenerated
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
/*
|
||||
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 deepcopy-gen. DO NOT EDIT.
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ImageReview) DeepCopyInto(out *ImageReview) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
in.Spec.DeepCopyInto(&out.Spec)
|
||||
in.Status.DeepCopyInto(&out.Status)
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ImageReview.
|
||||
func (in *ImageReview) DeepCopy() *ImageReview {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ImageReview)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *ImageReview) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ImageReviewContainerSpec) DeepCopyInto(out *ImageReviewContainerSpec) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ImageReviewContainerSpec.
|
||||
func (in *ImageReviewContainerSpec) DeepCopy() *ImageReviewContainerSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ImageReviewContainerSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ImageReviewSpec) DeepCopyInto(out *ImageReviewSpec) {
|
||||
*out = *in
|
||||
if in.Containers != nil {
|
||||
in, out := &in.Containers, &out.Containers
|
||||
*out = make([]ImageReviewContainerSpec, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.Annotations != nil {
|
||||
in, out := &in.Annotations, &out.Annotations
|
||||
*out = make(map[string]string, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = val
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ImageReviewSpec.
|
||||
func (in *ImageReviewSpec) DeepCopy() *ImageReviewSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ImageReviewSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ImageReviewStatus) DeepCopyInto(out *ImageReviewStatus) {
|
||||
*out = *in
|
||||
if in.AuditAnnotations != nil {
|
||||
in, out := &in.AuditAnnotations, &out.AuditAnnotations
|
||||
*out = make(map[string]string, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = val
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ImageReviewStatus.
|
||||
func (in *ImageReviewStatus) DeepCopy() *ImageReviewStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ImageReviewStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
302
vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/deepcopy.go
generated
vendored
302
vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/deepcopy.go
generated
vendored
@ -1,302 +0,0 @@
|
||||
/*
|
||||
Copyright 2017 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 apiextensions
|
||||
|
||||
import "k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
// TODO: Update this after a tag is created for interface fields in DeepCopy
|
||||
func (in *JSONSchemaProps) DeepCopy() *JSONSchemaProps {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(JSONSchemaProps)
|
||||
|
||||
*out = *in
|
||||
|
||||
if in.Default != nil {
|
||||
defaultJSON := JSON(runtime.DeepCopyJSONValue(*(in.Default)))
|
||||
out.Default = &(defaultJSON)
|
||||
} else {
|
||||
out.Default = nil
|
||||
}
|
||||
|
||||
if in.Example != nil {
|
||||
exampleJSON := JSON(runtime.DeepCopyJSONValue(*(in.Example)))
|
||||
out.Example = &(exampleJSON)
|
||||
} else {
|
||||
out.Example = nil
|
||||
}
|
||||
|
||||
if in.Ref != nil {
|
||||
in, out := &in.Ref, &out.Ref
|
||||
if *in == nil {
|
||||
*out = nil
|
||||
} else {
|
||||
*out = new(string)
|
||||
**out = **in
|
||||
}
|
||||
}
|
||||
|
||||
if in.Maximum != nil {
|
||||
in, out := &in.Maximum, &out.Maximum
|
||||
if *in == nil {
|
||||
*out = nil
|
||||
} else {
|
||||
*out = new(float64)
|
||||
**out = **in
|
||||
}
|
||||
}
|
||||
|
||||
if in.Minimum != nil {
|
||||
in, out := &in.Minimum, &out.Minimum
|
||||
if *in == nil {
|
||||
*out = nil
|
||||
} else {
|
||||
*out = new(float64)
|
||||
**out = **in
|
||||
}
|
||||
}
|
||||
|
||||
if in.MaxLength != nil {
|
||||
in, out := &in.MaxLength, &out.MaxLength
|
||||
if *in == nil {
|
||||
*out = nil
|
||||
} else {
|
||||
*out = new(int64)
|
||||
**out = **in
|
||||
}
|
||||
}
|
||||
|
||||
if in.MinLength != nil {
|
||||
in, out := &in.MinLength, &out.MinLength
|
||||
if *in == nil {
|
||||
*out = nil
|
||||
} else {
|
||||
*out = new(int64)
|
||||
**out = **in
|
||||
}
|
||||
}
|
||||
if in.MaxItems != nil {
|
||||
in, out := &in.MaxItems, &out.MaxItems
|
||||
if *in == nil {
|
||||
*out = nil
|
||||
} else {
|
||||
*out = new(int64)
|
||||
**out = **in
|
||||
}
|
||||
}
|
||||
|
||||
if in.MinItems != nil {
|
||||
in, out := &in.MinItems, &out.MinItems
|
||||
if *in == nil {
|
||||
*out = nil
|
||||
} else {
|
||||
*out = new(int64)
|
||||
**out = **in
|
||||
}
|
||||
}
|
||||
|
||||
if in.MultipleOf != nil {
|
||||
in, out := &in.MultipleOf, &out.MultipleOf
|
||||
if *in == nil {
|
||||
*out = nil
|
||||
} else {
|
||||
*out = new(float64)
|
||||
**out = **in
|
||||
}
|
||||
}
|
||||
|
||||
if in.Enum != nil {
|
||||
out.Enum = make([]JSON, len(in.Enum))
|
||||
for i := range in.Enum {
|
||||
out.Enum[i] = runtime.DeepCopyJSONValue(in.Enum[i])
|
||||
}
|
||||
}
|
||||
|
||||
if in.MaxProperties != nil {
|
||||
in, out := &in.MaxProperties, &out.MaxProperties
|
||||
if *in == nil {
|
||||
*out = nil
|
||||
} else {
|
||||
*out = new(int64)
|
||||
**out = **in
|
||||
}
|
||||
}
|
||||
|
||||
if in.MinProperties != nil {
|
||||
in, out := &in.MinProperties, &out.MinProperties
|
||||
if *in == nil {
|
||||
*out = nil
|
||||
} else {
|
||||
*out = new(int64)
|
||||
**out = **in
|
||||
}
|
||||
}
|
||||
|
||||
if in.Required != nil {
|
||||
in, out := &in.Required, &out.Required
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
if *in == nil {
|
||||
*out = nil
|
||||
} else {
|
||||
*out = new(JSONSchemaPropsOrArray)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
if in.AllOf != nil {
|
||||
in, out := &in.AllOf, &out.AllOf
|
||||
*out = make([]JSONSchemaProps, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
|
||||
if in.OneOf != nil {
|
||||
in, out := &in.OneOf, &out.OneOf
|
||||
*out = make([]JSONSchemaProps, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
if in.AnyOf != nil {
|
||||
in, out := &in.AnyOf, &out.AnyOf
|
||||
*out = make([]JSONSchemaProps, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
|
||||
if in.Not != nil {
|
||||
in, out := &in.Not, &out.Not
|
||||
if *in == nil {
|
||||
*out = nil
|
||||
} else {
|
||||
*out = new(JSONSchemaProps)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
if in.Properties != nil {
|
||||
in, out := &in.Properties, &out.Properties
|
||||
*out = make(map[string]JSONSchemaProps, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = *val.DeepCopy()
|
||||
}
|
||||
}
|
||||
|
||||
if in.AdditionalProperties != nil {
|
||||
in, out := &in.AdditionalProperties, &out.AdditionalProperties
|
||||
if *in == nil {
|
||||
*out = nil
|
||||
} else {
|
||||
*out = new(JSONSchemaPropsOrBool)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
if in.PatternProperties != nil {
|
||||
in, out := &in.PatternProperties, &out.PatternProperties
|
||||
*out = make(map[string]JSONSchemaProps, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = *val.DeepCopy()
|
||||
}
|
||||
}
|
||||
|
||||
if in.Dependencies != nil {
|
||||
in, out := &in.Dependencies, &out.Dependencies
|
||||
*out = make(JSONSchemaDependencies, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = *val.DeepCopy()
|
||||
}
|
||||
}
|
||||
|
||||
if in.AdditionalItems != nil {
|
||||
in, out := &in.AdditionalItems, &out.AdditionalItems
|
||||
if *in == nil {
|
||||
*out = nil
|
||||
} else {
|
||||
*out = new(JSONSchemaPropsOrBool)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
if in.Definitions != nil {
|
||||
in, out := &in.Definitions, &out.Definitions
|
||||
*out = make(JSONSchemaDefinitions, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = *val.DeepCopy()
|
||||
}
|
||||
}
|
||||
|
||||
if in.ExternalDocs != nil {
|
||||
in, out := &in.ExternalDocs, &out.ExternalDocs
|
||||
if *in == nil {
|
||||
*out = nil
|
||||
} else {
|
||||
*out = new(ExternalDocumentation)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
if in.XPreserveUnknownFields != nil {
|
||||
in, out := &in.XPreserveUnknownFields, &out.XPreserveUnknownFields
|
||||
if *in == nil {
|
||||
*out = nil
|
||||
} else {
|
||||
*out = new(bool)
|
||||
**out = **in
|
||||
}
|
||||
}
|
||||
|
||||
if in.XListMapKeys != nil {
|
||||
in, out := &in.XListMapKeys, &out.XListMapKeys
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
|
||||
if in.XListType != nil {
|
||||
in, out := &in.XListType, &out.XListType
|
||||
if *in == nil {
|
||||
*out = nil
|
||||
} else {
|
||||
*out = new(string)
|
||||
**out = **in
|
||||
}
|
||||
}
|
||||
|
||||
if in.XMapType != nil {
|
||||
in, out := &in.XMapType, &out.XMapType
|
||||
*out = new(string)
|
||||
**out = **in
|
||||
}
|
||||
|
||||
if in.XValidations != nil {
|
||||
inValidations, outValidations := &in.XValidations, &out.XValidations
|
||||
*outValidations = make([]ValidationRule, len(*inValidations))
|
||||
for i := range *inValidations {
|
||||
in.XValidations[i].DeepCopyInto(&out.XValidations[i])
|
||||
}
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
21
vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/doc.go
generated
vendored
21
vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/doc.go
generated
vendored
@ -1,21 +0,0 @@
|
||||
/*
|
||||
Copyright 2017 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.
|
||||
*/
|
||||
|
||||
// +k8s:deepcopy-gen=package
|
||||
// +groupName=apiextensions.k8s.io
|
||||
|
||||
// Package apiextensions is the internal version of the API.
|
||||
package apiextensions // import "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
|
257
vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/helpers.go
generated
vendored
257
vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/helpers.go
generated
vendored
@ -1,257 +0,0 @@
|
||||
/*
|
||||
Copyright 2017 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 apiextensions
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
var swaggerMetadataDescriptions = metav1.ObjectMeta{}.SwaggerDoc()
|
||||
|
||||
// SetCRDCondition sets the status condition. It either overwrites the existing one or creates a new one.
|
||||
func SetCRDCondition(crd *CustomResourceDefinition, newCondition CustomResourceDefinitionCondition) {
|
||||
newCondition.LastTransitionTime = metav1.NewTime(time.Now())
|
||||
|
||||
existingCondition := FindCRDCondition(crd, newCondition.Type)
|
||||
if existingCondition == nil {
|
||||
crd.Status.Conditions = append(crd.Status.Conditions, newCondition)
|
||||
return
|
||||
}
|
||||
|
||||
if existingCondition.Status != newCondition.Status || existingCondition.LastTransitionTime.IsZero() {
|
||||
existingCondition.LastTransitionTime = newCondition.LastTransitionTime
|
||||
}
|
||||
|
||||
existingCondition.Status = newCondition.Status
|
||||
existingCondition.Reason = newCondition.Reason
|
||||
existingCondition.Message = newCondition.Message
|
||||
}
|
||||
|
||||
// RemoveCRDCondition removes the status condition.
|
||||
func RemoveCRDCondition(crd *CustomResourceDefinition, conditionType CustomResourceDefinitionConditionType) {
|
||||
newConditions := []CustomResourceDefinitionCondition{}
|
||||
for _, condition := range crd.Status.Conditions {
|
||||
if condition.Type != conditionType {
|
||||
newConditions = append(newConditions, condition)
|
||||
}
|
||||
}
|
||||
crd.Status.Conditions = newConditions
|
||||
}
|
||||
|
||||
// FindCRDCondition returns the condition you're looking for or nil.
|
||||
func FindCRDCondition(crd *CustomResourceDefinition, conditionType CustomResourceDefinitionConditionType) *CustomResourceDefinitionCondition {
|
||||
for i := range crd.Status.Conditions {
|
||||
if crd.Status.Conditions[i].Type == conditionType {
|
||||
return &crd.Status.Conditions[i]
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsCRDConditionTrue indicates if the condition is present and strictly true.
|
||||
func IsCRDConditionTrue(crd *CustomResourceDefinition, conditionType CustomResourceDefinitionConditionType) bool {
|
||||
return IsCRDConditionPresentAndEqual(crd, conditionType, ConditionTrue)
|
||||
}
|
||||
|
||||
// IsCRDConditionFalse indicates if the condition is present and false.
|
||||
func IsCRDConditionFalse(crd *CustomResourceDefinition, conditionType CustomResourceDefinitionConditionType) bool {
|
||||
return IsCRDConditionPresentAndEqual(crd, conditionType, ConditionFalse)
|
||||
}
|
||||
|
||||
// IsCRDConditionPresentAndEqual indicates if the condition is present and equal to the given status.
|
||||
func IsCRDConditionPresentAndEqual(crd *CustomResourceDefinition, conditionType CustomResourceDefinitionConditionType, status ConditionStatus) bool {
|
||||
for _, condition := range crd.Status.Conditions {
|
||||
if condition.Type == conditionType {
|
||||
return condition.Status == status
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsCRDConditionEquivalent returns true if the lhs and rhs are equivalent except for times.
|
||||
func IsCRDConditionEquivalent(lhs, rhs *CustomResourceDefinitionCondition) bool {
|
||||
if lhs == nil && rhs == nil {
|
||||
return true
|
||||
}
|
||||
if lhs == nil || rhs == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return lhs.Message == rhs.Message && lhs.Reason == rhs.Reason && lhs.Status == rhs.Status && lhs.Type == rhs.Type
|
||||
}
|
||||
|
||||
// CRDHasFinalizer returns true if the finalizer is in the list.
|
||||
func CRDHasFinalizer(crd *CustomResourceDefinition, needle string) bool {
|
||||
for _, finalizer := range crd.Finalizers {
|
||||
if finalizer == needle {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// CRDRemoveFinalizer removes the finalizer if present.
|
||||
func CRDRemoveFinalizer(crd *CustomResourceDefinition, needle string) {
|
||||
newFinalizers := []string{}
|
||||
for _, finalizer := range crd.Finalizers {
|
||||
if finalizer != needle {
|
||||
newFinalizers = append(newFinalizers, finalizer)
|
||||
}
|
||||
}
|
||||
crd.Finalizers = newFinalizers
|
||||
}
|
||||
|
||||
// HasServedCRDVersion returns true if the given version is in the list of CRD's versions and the Served flag is set.
|
||||
func HasServedCRDVersion(crd *CustomResourceDefinition, version string) bool {
|
||||
for _, v := range crd.Spec.Versions {
|
||||
if v.Name == version {
|
||||
return v.Served
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// GetCRDStorageVersion returns the storage version for given CRD.
|
||||
func GetCRDStorageVersion(crd *CustomResourceDefinition) (string, error) {
|
||||
for _, v := range crd.Spec.Versions {
|
||||
if v.Storage {
|
||||
return v.Name, nil
|
||||
}
|
||||
}
|
||||
// This should not happened if crd is valid
|
||||
return "", fmt.Errorf("invalid CustomResourceDefinition, no storage version")
|
||||
}
|
||||
|
||||
// IsStoredVersion returns whether the given version is the storage version of the CRD.
|
||||
func IsStoredVersion(crd *CustomResourceDefinition, version string) bool {
|
||||
for _, v := range crd.Status.StoredVersions {
|
||||
if version == v {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// GetSchemaForVersion returns the validation schema for the given version or nil.
|
||||
func GetSchemaForVersion(crd *CustomResourceDefinition, version string) (*CustomResourceValidation, error) {
|
||||
if !HasPerVersionSchema(crd.Spec.Versions) {
|
||||
return crd.Spec.Validation, nil
|
||||
}
|
||||
if crd.Spec.Validation != nil {
|
||||
return nil, fmt.Errorf("malformed CustomResourceDefinition %s version %s: top-level and per-version schemas must be mutual exclusive", crd.Name, version)
|
||||
}
|
||||
for _, v := range crd.Spec.Versions {
|
||||
if version == v.Name {
|
||||
return v.Schema, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("version %s not found in CustomResourceDefinition: %v", version, crd.Name)
|
||||
}
|
||||
|
||||
// GetSubresourcesForVersion returns the subresources for given version or nil.
|
||||
func GetSubresourcesForVersion(crd *CustomResourceDefinition, version string) (*CustomResourceSubresources, error) {
|
||||
if !HasPerVersionSubresources(crd.Spec.Versions) {
|
||||
return crd.Spec.Subresources, nil
|
||||
}
|
||||
if crd.Spec.Subresources != nil {
|
||||
return nil, fmt.Errorf("malformed CustomResourceDefinition %s version %s: top-level and per-version subresources must be mutual exclusive", crd.Name, version)
|
||||
}
|
||||
for _, v := range crd.Spec.Versions {
|
||||
if version == v.Name {
|
||||
return v.Subresources, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("version %s not found in CustomResourceDefinition: %v", version, crd.Name)
|
||||
}
|
||||
|
||||
// GetColumnsForVersion returns the columns for given version or nil.
|
||||
// NOTE: the newly logically-defaulted columns is not pointing to the original CRD object.
|
||||
// One cannot mutate the original CRD columns using the logically-defaulted columns. Please iterate through
|
||||
// the original CRD object instead.
|
||||
func GetColumnsForVersion(crd *CustomResourceDefinition, version string) ([]CustomResourceColumnDefinition, error) {
|
||||
if !HasPerVersionColumns(crd.Spec.Versions) {
|
||||
return serveDefaultColumnsIfEmpty(crd.Spec.AdditionalPrinterColumns), nil
|
||||
}
|
||||
if len(crd.Spec.AdditionalPrinterColumns) > 0 {
|
||||
return nil, fmt.Errorf("malformed CustomResourceDefinition %s version %s: top-level and per-version additionalPrinterColumns must be mutual exclusive", crd.Name, version)
|
||||
}
|
||||
for _, v := range crd.Spec.Versions {
|
||||
if version == v.Name {
|
||||
return serveDefaultColumnsIfEmpty(v.AdditionalPrinterColumns), nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("version %s not found in CustomResourceDefinition: %v", version, crd.Name)
|
||||
}
|
||||
|
||||
// HasPerVersionSchema returns true if a CRD uses per-version schema.
|
||||
func HasPerVersionSchema(versions []CustomResourceDefinitionVersion) bool {
|
||||
for _, v := range versions {
|
||||
if v.Schema != nil {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// HasPerVersionSubresources returns true if a CRD uses per-version subresources.
|
||||
func HasPerVersionSubresources(versions []CustomResourceDefinitionVersion) bool {
|
||||
for _, v := range versions {
|
||||
if v.Subresources != nil {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// HasPerVersionColumns returns true if a CRD uses per-version columns.
|
||||
func HasPerVersionColumns(versions []CustomResourceDefinitionVersion) bool {
|
||||
for _, v := range versions {
|
||||
if len(v.AdditionalPrinterColumns) > 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// serveDefaultColumnsIfEmpty applies logically defaulting to columns, if the input columns is empty.
|
||||
// NOTE: in this way, the newly logically-defaulted columns is not pointing to the original CRD object.
|
||||
// One cannot mutate the original CRD columns using the logically-defaulted columns. Please iterate through
|
||||
// the original CRD object instead.
|
||||
func serveDefaultColumnsIfEmpty(columns []CustomResourceColumnDefinition) []CustomResourceColumnDefinition {
|
||||
if len(columns) > 0 {
|
||||
return columns
|
||||
}
|
||||
return []CustomResourceColumnDefinition{
|
||||
{Name: "Age", Type: "date", Description: swaggerMetadataDescriptions["creationTimestamp"], JSONPath: ".metadata.creationTimestamp"},
|
||||
}
|
||||
}
|
||||
|
||||
// HasVersionServed returns true if given CRD has given version served.
|
||||
func HasVersionServed(crd *CustomResourceDefinition, version string) bool {
|
||||
for _, v := range crd.Spec.Versions {
|
||||
if !v.Served || v.Name != version {
|
||||
continue
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
51
vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/register.go
generated
vendored
51
vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/register.go
generated
vendored
@ -1,51 +0,0 @@
|
||||
/*
|
||||
Copyright 2017 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 apiextensions
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
const GroupName = "apiextensions.k8s.io"
|
||||
|
||||
// SchemeGroupVersion is group version used to register these objects
|
||||
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: runtime.APIVersionInternal}
|
||||
|
||||
// Kind takes an unqualified kind and returns back a Group qualified GroupKind
|
||||
func Kind(kind string) schema.GroupKind {
|
||||
return SchemeGroupVersion.WithKind(kind).GroupKind()
|
||||
}
|
||||
|
||||
// Resource takes an unqualified resource and returns back a Group qualified GroupResource
|
||||
func Resource(resource string) schema.GroupResource {
|
||||
return SchemeGroupVersion.WithResource(resource).GroupResource()
|
||||
}
|
||||
|
||||
var (
|
||||
SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
|
||||
AddToScheme = SchemeBuilder.AddToScheme
|
||||
)
|
||||
|
||||
// Adds the list of known types to the given scheme.
|
||||
func addKnownTypes(scheme *runtime.Scheme) error {
|
||||
scheme.AddKnownTypes(SchemeGroupVersion,
|
||||
&CustomResourceDefinition{},
|
||||
&CustomResourceDefinitionList{},
|
||||
)
|
||||
return nil
|
||||
}
|
447
vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/types.go
generated
vendored
447
vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/types.go
generated
vendored
@ -1,447 +0,0 @@
|
||||
/*
|
||||
Copyright 2017 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 apiextensions
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// ConversionStrategyType describes different conversion types.
|
||||
type ConversionStrategyType string
|
||||
|
||||
const (
|
||||
// NoneConverter is a converter that only sets apiversion of the CR and leave everything else unchanged.
|
||||
NoneConverter ConversionStrategyType = "None"
|
||||
// WebhookConverter is a converter that calls to an external webhook to convert the CR.
|
||||
WebhookConverter ConversionStrategyType = "Webhook"
|
||||
)
|
||||
|
||||
// CustomResourceDefinitionSpec describes how a user wants their resource to appear
|
||||
type CustomResourceDefinitionSpec struct {
|
||||
// Group is the group this resource belongs in
|
||||
Group string
|
||||
// Version is the version this resource belongs in
|
||||
// Should be always first item in Versions field if provided.
|
||||
// Optional, but at least one of Version or Versions must be set.
|
||||
// Deprecated: Please use `Versions`.
|
||||
Version string
|
||||
// Names are the names used to describe this custom resource
|
||||
Names CustomResourceDefinitionNames
|
||||
// Scope indicates whether this resource is cluster or namespace scoped. Default is namespaced
|
||||
Scope ResourceScope
|
||||
// Validation describes the validation methods for CustomResources
|
||||
// Optional, the global validation schema for all versions.
|
||||
// Top-level and per-version schemas are mutually exclusive.
|
||||
// +optional
|
||||
Validation *CustomResourceValidation
|
||||
// Subresources describes the subresources for CustomResource
|
||||
// Optional, the global subresources for all versions.
|
||||
// Top-level and per-version subresources are mutually exclusive.
|
||||
// +optional
|
||||
Subresources *CustomResourceSubresources
|
||||
// Versions is the list of all supported versions for this resource.
|
||||
// If Version field is provided, this field is optional.
|
||||
// Validation: All versions must use the same validation schema for now. i.e., top
|
||||
// level Validation field is applied to all of these versions.
|
||||
// Order: The version name will be used to compute the order.
|
||||
// If the version string is "kube-like", it will sort above non "kube-like" version strings, which are ordered
|
||||
// lexicographically. "Kube-like" versions start with a "v", then are followed by a number (the major version),
|
||||
// then optionally the string "alpha" or "beta" and another number (the minor version). These are sorted first
|
||||
// by GA > beta > alpha (where GA is a version with no suffix such as beta or alpha), and then by comparing
|
||||
// major version, then minor version. An example sorted list of versions:
|
||||
// v10, v2, v1, v11beta2, v10beta3, v3beta1, v12alpha1, v11alpha2, foo1, foo10.
|
||||
Versions []CustomResourceDefinitionVersion
|
||||
// AdditionalPrinterColumns are additional columns shown e.g. in kubectl next to the name. Defaults to a created-at column.
|
||||
// Optional, the global columns for all versions.
|
||||
// Top-level and per-version columns are mutually exclusive.
|
||||
// +optional
|
||||
AdditionalPrinterColumns []CustomResourceColumnDefinition
|
||||
// selectableFields specifies paths to fields that may be used as field selectors.
|
||||
// A maximum of 8 selectable fields are allowed.
|
||||
// See https://kubernetes.io/docs/concepts/overview/working-with-objects/field-selectors
|
||||
// Top-level and per-version columns are mutually exclusive.
|
||||
// +optional
|
||||
SelectableFields []SelectableField
|
||||
|
||||
// `conversion` defines conversion settings for the CRD.
|
||||
Conversion *CustomResourceConversion
|
||||
|
||||
// preserveUnknownFields disables pruning of object fields which are not
|
||||
// specified in the OpenAPI schema. apiVersion, kind, metadata and known
|
||||
// fields inside metadata are always preserved.
|
||||
// Defaults to true in v1beta and will default to false in v1.
|
||||
PreserveUnknownFields *bool
|
||||
}
|
||||
|
||||
// CustomResourceConversion describes how to convert different versions of a CR.
|
||||
type CustomResourceConversion struct {
|
||||
// `strategy` specifies the conversion strategy. Allowed values are:
|
||||
// - `None`: The converter only change the apiVersion and would not touch any other field in the CR.
|
||||
// - `Webhook`: API Server will call to an external webhook to do the conversion. Additional information
|
||||
// is needed for this option. This requires spec.preserveUnknownFields to be false.
|
||||
Strategy ConversionStrategyType
|
||||
|
||||
// `webhookClientConfig` is the instructions for how to call the webhook if strategy is `Webhook`.
|
||||
WebhookClientConfig *WebhookClientConfig
|
||||
|
||||
// ConversionReviewVersions is an ordered list of preferred `ConversionReview`
|
||||
// versions the Webhook expects. API server will try to use first version in
|
||||
// the list which it supports. If none of the versions specified in this list
|
||||
// supported by API server, conversion will fail for this object.
|
||||
// If a persisted Webhook configuration specifies allowed versions and does not
|
||||
// include any versions known to the API Server, calls to the webhook will fail.
|
||||
// +optional
|
||||
ConversionReviewVersions []string
|
||||
}
|
||||
|
||||
// WebhookClientConfig contains the information to make a TLS
|
||||
// connection with the webhook. It has the same field as admissionregistration.internal.WebhookClientConfig.
|
||||
type WebhookClientConfig struct {
|
||||
// `url` gives the location of the webhook, in standard URL form
|
||||
// (`scheme://host:port/path`). Exactly one of `url` or `service`
|
||||
// must be specified.
|
||||
//
|
||||
// The `host` should not refer to a service running in the cluster; use
|
||||
// the `service` field instead. The host might be resolved via external
|
||||
// DNS in some apiservers (e.g., `kube-apiserver` cannot resolve
|
||||
// in-cluster DNS as that would be a layering violation). `host` may
|
||||
// also be an IP address.
|
||||
//
|
||||
// Please note that using `localhost` or `127.0.0.1` as a `host` is
|
||||
// risky unless you take great care to run this webhook on all hosts
|
||||
// which run an apiserver which might need to make calls to this
|
||||
// webhook. Such installs are likely to be non-portable, i.e., not easy
|
||||
// to turn up in a new cluster.
|
||||
//
|
||||
// The scheme must be "https"; the URL must begin with "https://".
|
||||
//
|
||||
// A path is optional, and if present may be any string permissible in
|
||||
// a URL. You may use the path to pass an arbitrary string to the
|
||||
// webhook, for example, a cluster identifier.
|
||||
//
|
||||
// Attempting to use a user or basic auth e.g. "user:password@" is not
|
||||
// allowed. Fragments ("#...") and query parameters ("?...") are not
|
||||
// allowed, either.
|
||||
//
|
||||
// +optional
|
||||
URL *string
|
||||
|
||||
// `service` is a reference to the service for this webhook. Either
|
||||
// `service` or `url` must be specified.
|
||||
//
|
||||
// If the webhook is running within the cluster, then you should use `service`.
|
||||
//
|
||||
// +optional
|
||||
Service *ServiceReference
|
||||
|
||||
// `caBundle` is a PEM encoded CA bundle which will be used to validate the webhook's server certificate.
|
||||
// If unspecified, system trust roots on the apiserver are used.
|
||||
// +optional
|
||||
CABundle []byte
|
||||
}
|
||||
|
||||
// ServiceReference holds a reference to Service.legacy.k8s.io
|
||||
type ServiceReference struct {
|
||||
// `namespace` is the namespace of the service.
|
||||
// Required
|
||||
Namespace string
|
||||
// `name` is the name of the service.
|
||||
// Required
|
||||
Name string
|
||||
|
||||
// `path` is an optional URL path which will be sent in any request to
|
||||
// this service.
|
||||
// +optional
|
||||
Path *string
|
||||
|
||||
// If specified, the port on the service that hosting webhook.
|
||||
// `port` should be a valid port number (1-65535, inclusive).
|
||||
// +optional
|
||||
Port int32
|
||||
}
|
||||
|
||||
// CustomResourceDefinitionVersion describes a version for CRD.
|
||||
type CustomResourceDefinitionVersion struct {
|
||||
// Name is the version name, e.g. “v1”, “v2beta1”, etc.
|
||||
Name string
|
||||
// Served is a flag enabling/disabling this version from being served via REST APIs
|
||||
Served bool
|
||||
// Storage flags the version as storage version. There must be exactly one flagged
|
||||
// as storage version.
|
||||
Storage bool
|
||||
// deprecated indicates this version of the custom resource API is deprecated.
|
||||
// When set to true, API requests to this version receive a warning header in the server response.
|
||||
// Defaults to false.
|
||||
Deprecated bool
|
||||
// deprecationWarning overrides the default warning returned to API clients.
|
||||
// May only be set when `deprecated` is true.
|
||||
// The default warning indicates this version is deprecated and recommends use
|
||||
// of the newest served version of equal or greater stability, if one exists.
|
||||
DeprecationWarning *string
|
||||
// Schema describes the schema for CustomResource used in validation, pruning, and defaulting.
|
||||
// Top-level and per-version schemas are mutually exclusive.
|
||||
// Per-version schemas must not all be set to identical values (top-level validation schema should be used instead)
|
||||
// This field is alpha-level and is only honored by servers that enable the CustomResourceWebhookConversion feature.
|
||||
// +optional
|
||||
Schema *CustomResourceValidation
|
||||
// Subresources describes the subresources for CustomResource
|
||||
// Top-level and per-version subresources are mutually exclusive.
|
||||
// Per-version subresources must not all be set to identical values (top-level subresources should be used instead)
|
||||
// This field is alpha-level and is only honored by servers that enable the CustomResourceWebhookConversion feature.
|
||||
// +optional
|
||||
Subresources *CustomResourceSubresources
|
||||
// AdditionalPrinterColumns are additional columns shown e.g. in kubectl next to the name. Defaults to a created-at column.
|
||||
// Top-level and per-version columns are mutually exclusive.
|
||||
// Per-version columns must not all be set to identical values (top-level columns should be used instead)
|
||||
// This field is alpha-level and is only honored by servers that enable the CustomResourceWebhookConversion feature.
|
||||
// NOTE: CRDs created prior to 1.13 populated the top-level additionalPrinterColumns field by default. To apply an
|
||||
// update that changes to per-version additionalPrinterColumns, the top-level additionalPrinterColumns field must
|
||||
// be explicitly set to null
|
||||
// +optional
|
||||
AdditionalPrinterColumns []CustomResourceColumnDefinition
|
||||
|
||||
// selectableFields specifies paths to fields that may be used as field selectors.
|
||||
// A maximum of 8 selectable fields are allowed.
|
||||
// See https://kubernetes.io/docs/concepts/overview/working-with-objects/field-selectors
|
||||
// +optional
|
||||
SelectableFields []SelectableField
|
||||
}
|
||||
|
||||
// SelectableField specifies the JSON path of a field that may be used with field selectors.
|
||||
type SelectableField struct {
|
||||
// jsonPath is a simple JSON path which is evaluated against each custom resource to produce a
|
||||
// field selector value.
|
||||
// Only JSON paths without the array notation are allowed.
|
||||
// Must point to a field of type string, boolean or integer. Types with enum values
|
||||
// and strings with formats are allowed.
|
||||
// If jsonPath refers to absent field in a resource, the jsonPath evaluates to an empty string.
|
||||
// Must not point to metdata fields.
|
||||
// Required.
|
||||
JSONPath string
|
||||
}
|
||||
|
||||
// CustomResourceColumnDefinition specifies a column for server side printing.
|
||||
type CustomResourceColumnDefinition struct {
|
||||
// name is a human readable name for the column.
|
||||
Name string
|
||||
// type is an OpenAPI type definition for this column.
|
||||
// See https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#data-types for more.
|
||||
Type string
|
||||
// format is an optional OpenAPI type definition for this column. The 'name' format is applied
|
||||
// to the primary identifier column to assist in clients identifying column is the resource name.
|
||||
// See https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#data-types for more.
|
||||
Format string
|
||||
// description is a human readable description of this column.
|
||||
Description string
|
||||
// priority is an integer defining the relative importance of this column compared to others. Lower
|
||||
// numbers are considered higher priority. Columns that may be omitted in limited space scenarios
|
||||
// should be given a higher priority.
|
||||
Priority int32
|
||||
|
||||
// JSONPath is a simple JSON path, i.e. without array notation.
|
||||
JSONPath string
|
||||
}
|
||||
|
||||
// CustomResourceDefinitionNames indicates the names to serve this CustomResourceDefinition
|
||||
type CustomResourceDefinitionNames struct {
|
||||
// Plural is the plural name of the resource to serve. It must match the name of the CustomResourceDefinition-registration
|
||||
// too: plural.group and it must be all lowercase.
|
||||
Plural string
|
||||
// Singular is the singular name of the resource. It must be all lowercase Defaults to lowercased <kind>
|
||||
Singular string
|
||||
// ShortNames are short names for the resource. It must be all lowercase.
|
||||
ShortNames []string
|
||||
// Kind is the serialized kind of the resource. It is normally CamelCase and singular.
|
||||
Kind string
|
||||
// ListKind is the serialized kind of the list for this resource. Defaults to <kind>List.
|
||||
ListKind string
|
||||
// Categories is a list of grouped resources custom resources belong to (e.g. 'all')
|
||||
// +optional
|
||||
Categories []string
|
||||
}
|
||||
|
||||
// ResourceScope is an enum defining the different scopes available to a custom resource
|
||||
type ResourceScope string
|
||||
|
||||
const (
|
||||
ClusterScoped ResourceScope = "Cluster"
|
||||
NamespaceScoped ResourceScope = "Namespaced"
|
||||
)
|
||||
|
||||
type ConditionStatus string
|
||||
|
||||
// These are valid condition statuses. "ConditionTrue" means a resource is in the condition.
|
||||
// "ConditionFalse" means a resource is not in the condition. "ConditionUnknown" means kubernetes
|
||||
// can't decide if a resource is in the condition or not. In the future, we could add other
|
||||
// intermediate conditions, e.g. ConditionDegraded.
|
||||
const (
|
||||
ConditionTrue ConditionStatus = "True"
|
||||
ConditionFalse ConditionStatus = "False"
|
||||
ConditionUnknown ConditionStatus = "Unknown"
|
||||
)
|
||||
|
||||
// CustomResourceDefinitionConditionType is a valid value for CustomResourceDefinitionCondition.Type
|
||||
type CustomResourceDefinitionConditionType string
|
||||
|
||||
const (
|
||||
// Established means that the resource has become active. A resource is established when all names are
|
||||
// accepted without a conflict for the first time. A resource stays established until deleted, even during
|
||||
// a later NamesAccepted due to changed names. Note that not all names can be changed.
|
||||
Established CustomResourceDefinitionConditionType = "Established"
|
||||
// NamesAccepted means the names chosen for this CustomResourceDefinition do not conflict with others in
|
||||
// the group and are therefore accepted.
|
||||
NamesAccepted CustomResourceDefinitionConditionType = "NamesAccepted"
|
||||
// NonStructuralSchema means that one or more OpenAPI schema is not structural.
|
||||
//
|
||||
// A schema is structural if it specifies types for all values, with the only exceptions of those with
|
||||
// - x-kubernetes-int-or-string: true — for fields which can be integer or string
|
||||
// - x-kubernetes-preserve-unknown-fields: true — for raw, unspecified JSON values
|
||||
// and there is no type, additionalProperties, default, nullable or x-kubernetes-* vendor extenions
|
||||
// specified under allOf, anyOf, oneOf or not.
|
||||
//
|
||||
// Non-structural schemas will not be allowed anymore in v1 API groups. Moreover, new features will not be
|
||||
// available for non-structural CRDs:
|
||||
// - pruning
|
||||
// - defaulting
|
||||
// - read-only
|
||||
// - OpenAPI publishing
|
||||
// - webhook conversion
|
||||
NonStructuralSchema CustomResourceDefinitionConditionType = "NonStructuralSchema"
|
||||
// Terminating means that the CustomResourceDefinition has been deleted and is cleaning up.
|
||||
Terminating CustomResourceDefinitionConditionType = "Terminating"
|
||||
// KubernetesAPIApprovalPolicyConformant indicates that an API in *.k8s.io or *.kubernetes.io is or is not approved. For CRDs
|
||||
// outside those groups, this condition will not be set. For CRDs inside those groups, the condition will
|
||||
// be true if .metadata.annotations["api-approved.kubernetes.io"] is set to a URL, otherwise it will be false.
|
||||
// See https://github.com/kubernetes/enhancements/pull/1111 for more details.
|
||||
KubernetesAPIApprovalPolicyConformant CustomResourceDefinitionConditionType = "KubernetesAPIApprovalPolicyConformant"
|
||||
)
|
||||
|
||||
// CustomResourceDefinitionCondition contains details for the current condition of this pod.
|
||||
type CustomResourceDefinitionCondition struct {
|
||||
// Type is the type of the condition. Types include Established, NamesAccepted and Terminating.
|
||||
Type CustomResourceDefinitionConditionType
|
||||
// Status is the status of the condition.
|
||||
// Can be True, False, Unknown.
|
||||
Status ConditionStatus
|
||||
// Last time the condition transitioned from one status to another.
|
||||
// +optional
|
||||
LastTransitionTime metav1.Time
|
||||
// Unique, one-word, CamelCase reason for the condition's last transition.
|
||||
// +optional
|
||||
Reason string
|
||||
// Human-readable message indicating details about last transition.
|
||||
// +optional
|
||||
Message string
|
||||
}
|
||||
|
||||
// CustomResourceDefinitionStatus indicates the state of the CustomResourceDefinition
|
||||
type CustomResourceDefinitionStatus struct {
|
||||
// Conditions indicate state for particular aspects of a CustomResourceDefinition
|
||||
// +listType=map
|
||||
// +listMapKey=type
|
||||
Conditions []CustomResourceDefinitionCondition
|
||||
|
||||
// AcceptedNames are the names that are actually being used to serve discovery
|
||||
// They may be different than the names in spec.
|
||||
AcceptedNames CustomResourceDefinitionNames
|
||||
|
||||
// StoredVersions are all versions of CustomResources that were ever persisted. Tracking these
|
||||
// versions allows a migration path for stored versions in etcd. The field is mutable
|
||||
// so the migration controller can first finish a migration to another version (i.e.
|
||||
// that no old objects are left in the storage), and then remove the rest of the
|
||||
// versions from this list.
|
||||
// None of the versions in this list can be removed from the spec.Versions field.
|
||||
StoredVersions []string
|
||||
}
|
||||
|
||||
// CustomResourceCleanupFinalizer is the name of the finalizer which will delete instances of
|
||||
// a CustomResourceDefinition
|
||||
const CustomResourceCleanupFinalizer = "customresourcecleanup.apiextensions.k8s.io"
|
||||
|
||||
// +genclient
|
||||
// +genclient:nonNamespaced
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
|
||||
// CustomResourceDefinition represents a resource that should be exposed on the API server. Its name MUST be in the format
|
||||
// <.spec.name>.<.spec.group>.
|
||||
type CustomResourceDefinition struct {
|
||||
metav1.TypeMeta
|
||||
metav1.ObjectMeta
|
||||
|
||||
// Spec describes how the user wants the resources to appear
|
||||
Spec CustomResourceDefinitionSpec
|
||||
// Status indicates the actual state of the CustomResourceDefinition
|
||||
Status CustomResourceDefinitionStatus
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
|
||||
// CustomResourceDefinitionList is a list of CustomResourceDefinition objects.
|
||||
type CustomResourceDefinitionList struct {
|
||||
metav1.TypeMeta
|
||||
metav1.ListMeta
|
||||
|
||||
// Items individual CustomResourceDefinitions
|
||||
Items []CustomResourceDefinition
|
||||
}
|
||||
|
||||
// CustomResourceValidation is a list of validation methods for CustomResources.
|
||||
type CustomResourceValidation struct {
|
||||
// OpenAPIV3Schema is the OpenAPI v3 schema to be validated against.
|
||||
OpenAPIV3Schema *JSONSchemaProps
|
||||
}
|
||||
|
||||
// CustomResourceSubresources defines the status and scale subresources for CustomResources.
|
||||
type CustomResourceSubresources struct {
|
||||
// Status denotes the status subresource for CustomResources
|
||||
Status *CustomResourceSubresourceStatus
|
||||
// Scale denotes the scale subresource for CustomResources
|
||||
Scale *CustomResourceSubresourceScale
|
||||
}
|
||||
|
||||
// CustomResourceSubresourceStatus defines how to serve the status subresource for CustomResources.
|
||||
// Status is represented by the `.status` JSON path inside of a CustomResource. When set,
|
||||
// * exposes a /status subresource for the custom resource
|
||||
// * PUT requests to the /status subresource take a custom resource object, and ignore changes to anything except the status stanza
|
||||
// * PUT/POST/PATCH requests to the custom resource ignore changes to the status stanza
|
||||
type CustomResourceSubresourceStatus struct{}
|
||||
|
||||
// CustomResourceSubresourceScale defines how to serve the scale subresource for CustomResources.
|
||||
type CustomResourceSubresourceScale struct {
|
||||
// SpecReplicasPath defines the JSON path inside of a CustomResource that corresponds to Scale.Spec.Replicas.
|
||||
// Only JSON paths without the array notation are allowed.
|
||||
// Must be a JSON Path under .spec.
|
||||
// If there is no value under the given path in the CustomResource, the /scale subresource will return an error on GET.
|
||||
SpecReplicasPath string
|
||||
// StatusReplicasPath defines the JSON path inside of a CustomResource that corresponds to Scale.Status.Replicas.
|
||||
// Only JSON paths without the array notation are allowed.
|
||||
// Must be a JSON Path under .status.
|
||||
// If there is no value under the given path in the CustomResource, the status replica value in the /scale subresource
|
||||
// will default to 0.
|
||||
StatusReplicasPath string
|
||||
// LabelSelectorPath defines the JSON path inside of a CustomResource that corresponds to Scale.Status.Selector.
|
||||
// Only JSON paths without the array notation are allowed.
|
||||
// Must be a JSON Path under .status or .spec.
|
||||
// Must be set to work with HPA.
|
||||
// The field pointed by this JSON path must be a string field (not a complex selector struct)
|
||||
// which contains a serialized label selector in string form.
|
||||
// More info: https://kubernetes.io/docs/tasks/access-kubernetes-api/custom-resources/custom-resource-definitions#scale-subresource
|
||||
// If there is no value under the given path in the CustomResource, the status label selector value in the /scale
|
||||
// subresource will default to the empty string.
|
||||
// +optional
|
||||
LabelSelectorPath *string
|
||||
}
|
318
vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/types_jsonschema.go
generated
vendored
318
vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/types_jsonschema.go
generated
vendored
@ -1,318 +0,0 @@
|
||||
/*
|
||||
Copyright 2017 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 apiextensions
|
||||
|
||||
// FieldValueErrorReason is a machine-readable value providing more detail about why a field failed the validation.
|
||||
// +enum
|
||||
type FieldValueErrorReason string
|
||||
|
||||
const (
|
||||
// FieldValueRequired is used to report required values that are not
|
||||
// provided (e.g. empty strings, null values, or empty arrays).
|
||||
FieldValueRequired FieldValueErrorReason = "FieldValueRequired"
|
||||
// FieldValueDuplicate is used to report collisions of values that must be
|
||||
// unique (e.g. unique IDs).
|
||||
FieldValueDuplicate FieldValueErrorReason = "FieldValueDuplicate"
|
||||
// FieldValueInvalid is used to report malformed values (e.g. failed regex
|
||||
// match, too long, out of bounds).
|
||||
FieldValueInvalid FieldValueErrorReason = "FieldValueInvalid"
|
||||
// FieldValueForbidden is used to report valid (as per formatting rules)
|
||||
// values which would be accepted under some conditions, but which are not
|
||||
// permitted by the current conditions (such as security policy).
|
||||
FieldValueForbidden FieldValueErrorReason = "FieldValueForbidden"
|
||||
)
|
||||
|
||||
// JSONSchemaProps is a JSON-Schema following Specification Draft 4 (http://json-schema.org/).
|
||||
type JSONSchemaProps struct {
|
||||
ID string
|
||||
Schema JSONSchemaURL
|
||||
Ref *string
|
||||
Description string
|
||||
Type string
|
||||
Nullable bool
|
||||
Format string
|
||||
Title string
|
||||
Default *JSON
|
||||
Maximum *float64
|
||||
ExclusiveMaximum bool
|
||||
Minimum *float64
|
||||
ExclusiveMinimum bool
|
||||
MaxLength *int64
|
||||
MinLength *int64
|
||||
Pattern string
|
||||
MaxItems *int64
|
||||
MinItems *int64
|
||||
UniqueItems bool
|
||||
MultipleOf *float64
|
||||
Enum []JSON
|
||||
MaxProperties *int64
|
||||
MinProperties *int64
|
||||
Required []string
|
||||
Items *JSONSchemaPropsOrArray
|
||||
AllOf []JSONSchemaProps
|
||||
OneOf []JSONSchemaProps
|
||||
AnyOf []JSONSchemaProps
|
||||
Not *JSONSchemaProps
|
||||
Properties map[string]JSONSchemaProps
|
||||
AdditionalProperties *JSONSchemaPropsOrBool
|
||||
PatternProperties map[string]JSONSchemaProps
|
||||
Dependencies JSONSchemaDependencies
|
||||
AdditionalItems *JSONSchemaPropsOrBool
|
||||
Definitions JSONSchemaDefinitions
|
||||
ExternalDocs *ExternalDocumentation
|
||||
Example *JSON
|
||||
|
||||
// x-kubernetes-preserve-unknown-fields stops the API server
|
||||
// decoding step from pruning fields which are not specified
|
||||
// in the validation schema. This affects fields recursively,
|
||||
// but switches back to normal pruning behaviour if nested
|
||||
// properties or additionalProperties are specified in the schema.
|
||||
// This can either be true or undefined. False is forbidden.
|
||||
XPreserveUnknownFields *bool
|
||||
|
||||
// x-kubernetes-embedded-resource defines that the value is an
|
||||
// embedded Kubernetes runtime.Object, with TypeMeta and
|
||||
// ObjectMeta. The type must be object. It is allowed to further
|
||||
// restrict the embedded object. Both ObjectMeta and TypeMeta
|
||||
// are validated automatically. x-kubernetes-preserve-unknown-fields
|
||||
// must be true.
|
||||
XEmbeddedResource bool
|
||||
|
||||
// x-kubernetes-int-or-string specifies that this value is
|
||||
// either an integer or a string. If this is true, an empty
|
||||
// type is allowed and type as child of anyOf is permitted
|
||||
// if following one of the following patterns:
|
||||
//
|
||||
// 1) anyOf:
|
||||
// - type: integer
|
||||
// - type: string
|
||||
// 2) allOf:
|
||||
// - anyOf:
|
||||
// - type: integer
|
||||
// - type: string
|
||||
// - ... zero or more
|
||||
XIntOrString bool
|
||||
|
||||
// x-kubernetes-list-map-keys annotates an array with the x-kubernetes-list-type `map` by specifying the keys used
|
||||
// as the index of the map.
|
||||
//
|
||||
// This tag MUST only be used on lists that have the "x-kubernetes-list-type"
|
||||
// extension set to "map". Also, the values specified for this attribute must
|
||||
// be a scalar typed field of the child structure (no nesting is supported).
|
||||
XListMapKeys []string
|
||||
|
||||
// x-kubernetes-list-type annotates an array to further describe its topology.
|
||||
// This extension must only be used on lists and may have 3 possible values:
|
||||
//
|
||||
// 1) `atomic`: the list is treated as a single entity, like a scalar.
|
||||
// Atomic lists will be entirely replaced when updated. This extension
|
||||
// may be used on any type of list (struct, scalar, ...).
|
||||
// 2) `set`:
|
||||
// Sets are lists that must not have multiple items with the same value. Each
|
||||
// value must be a scalar, an object with x-kubernetes-map-type `atomic` or an
|
||||
// array with x-kubernetes-list-type `atomic`.
|
||||
// 3) `map`:
|
||||
// These lists are like maps in that their elements have a non-index key
|
||||
// used to identify them. Order is preserved upon merge. The map tag
|
||||
// must only be used on a list with elements of type object.
|
||||
XListType *string
|
||||
|
||||
// x-kubernetes-map-type annotates an object to further describe its topology.
|
||||
// This extension must only be used when type is object and may have 2 possible values:
|
||||
//
|
||||
// 1) `granular`:
|
||||
// These maps are actual maps (key-value pairs) and each fields are independent
|
||||
// from each other (they can each be manipulated by separate actors). This is
|
||||
// the default behaviour for all maps.
|
||||
// 2) `atomic`: the list is treated as a single entity, like a scalar.
|
||||
// Atomic maps will be entirely replaced when updated.
|
||||
// +optional
|
||||
XMapType *string
|
||||
|
||||
// x-kubernetes-validations -kubernetes-validations describes a list of validation rules written in the CEL expression language.
|
||||
// +patchMergeKey=rule
|
||||
// +patchStrategy=merge
|
||||
// +listType=map
|
||||
// +listMapKey=rule
|
||||
XValidations ValidationRules
|
||||
}
|
||||
|
||||
// ValidationRules describes a list of validation rules written in the CEL expression language.
|
||||
type ValidationRules []ValidationRule
|
||||
|
||||
// ValidationRule describes a validation rule written in the CEL expression language.
|
||||
type ValidationRule struct {
|
||||
// Rule represents the expression which will be evaluated by CEL.
|
||||
// ref: https://github.com/google/cel-spec
|
||||
// The Rule is scoped to the location of the x-kubernetes-validations extension in the schema.
|
||||
// The `self` variable in the CEL expression is bound to the scoped value.
|
||||
// Example:
|
||||
// - Rule scoped to the root of a resource with a status subresource: {"rule": "self.status.actual <= self.spec.maxDesired"}
|
||||
//
|
||||
// If the Rule is scoped to an object with properties, the accessible properties of the object are field selectable
|
||||
// via `self.field` and field presence can be checked via `has(self.field)`. Null valued fields are treated as
|
||||
// absent fields in CEL expressions.
|
||||
// If the Rule is scoped to an object with additionalProperties (i.e. a map) the value of the map
|
||||
// are accessible via `self[mapKey]`, map containment can be checked via `mapKey in self` and all entries of the map
|
||||
// are accessible via CEL macros and functions such as `self.all(...)`.
|
||||
// If the Rule is scoped to an array, the elements of the array are accessible via `self[i]` and also by macros and
|
||||
// functions.
|
||||
// If the Rule is scoped to a scalar, `self` is bound to the scalar value.
|
||||
// Examples:
|
||||
// - Rule scoped to a map of objects: {"rule": "self.components['Widget'].priority < 10"}
|
||||
// - Rule scoped to a list of integers: {"rule": "self.values.all(value, value >= 0 && value < 100)"}
|
||||
// - Rule scoped to a string value: {"rule": "self.startsWith('kube')"}
|
||||
//
|
||||
// The `apiVersion`, `kind`, `metadata.name` and `metadata.generateName` are always accessible from the root of the
|
||||
// object and from any x-kubernetes-embedded-resource annotated objects. No other metadata properties are accessible.
|
||||
//
|
||||
// Unknown data preserved in custom resources via x-kubernetes-preserve-unknown-fields is not accessible in CEL
|
||||
// expressions. This includes:
|
||||
// - Unknown field values that are preserved by object schemas with x-kubernetes-preserve-unknown-fields.
|
||||
// - Object properties where the property schema is of an "unknown type". An "unknown type" is recursively defined as:
|
||||
// - A schema with no type and x-kubernetes-preserve-unknown-fields set to true
|
||||
// - An array where the items schema is of an "unknown type"
|
||||
// - An object where the additionalProperties schema is of an "unknown type"
|
||||
//
|
||||
// Only 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:
|
||||
// "true", "false", "null", "in", "as", "break", "const", "continue", "else", "for", "function", "if",
|
||||
// "import", "let", "loop", "package", "namespace", "return".
|
||||
// Examples:
|
||||
// - Rule accessing a property named "namespace": {"rule": "self.__namespace__ > 0"}
|
||||
// - Rule accessing a property named "x-prop": {"rule": "self.x__dash__prop > 0"}
|
||||
// - Rule accessing a property named "redact__d": {"rule": "self.redact__underscores__d > 0"}
|
||||
//
|
||||
// Equality on arrays with x-kubernetes-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:
|
||||
// - 'set': `X + Y` performs a union where the array positions of all elements in `X` are preserved and
|
||||
// non-intersecting elements in `Y` are appended, retaining their partial order.
|
||||
// - 'map': `X + Y` performs a merge where the array positions of all keys in `X` are preserved but the values
|
||||
// are overwritten by values in `Y` when the key sets of `X` and `Y` intersect. Elements in `Y` with
|
||||
// non-intersecting keys are appended, retaining their partial order.
|
||||
//
|
||||
// If `rule` makes use of the `oldSelf` variable it is implicitly a
|
||||
// `transition rule`.
|
||||
//
|
||||
// By default, the `oldSelf` variable is the same type as `self`.
|
||||
// When `optionalOldSelf` is true, the `oldSelf` variable is a CEL optional
|
||||
// variable whose value() is the same type as `self`.
|
||||
// See the documentation for the `optionalOldSelf` field for details.
|
||||
//
|
||||
// Transition rules by default are applied only on UPDATE requests and are
|
||||
// skipped if an old value could not be found. You can opt a transition
|
||||
// rule into unconditional evaluation by setting `optionalOldSelf` to true.
|
||||
//
|
||||
Rule string
|
||||
// Message represents the message displayed when validation fails. The message is required if the Rule contains
|
||||
// line breaks. The message must not contain line breaks.
|
||||
// If unset, the message is "failed rule: {Rule}".
|
||||
// e.g. "must be a URL with the host matching spec.host"
|
||||
Message string
|
||||
// MessageExpression declares a CEL expression that evaluates to the validation failure message that is returned when this rule fails.
|
||||
// Since messageExpression is used as a failure message, it must evaluate to a string.
|
||||
// If both message and messageExpression are present on a rule, then messageExpression will be used if validation
|
||||
// fails. If messageExpression results in a runtime error, the runtime error is logged, and the validation failure message is produced
|
||||
// as if the messageExpression field were unset. If messageExpression evaluates to an empty string, a string with only spaces, or a string
|
||||
// that contains line breaks, then the validation failure message will also be produced as if the messageExpression field were unset, and
|
||||
// the fact that messageExpression produced an empty string/string with only spaces/string with line breaks will be logged.
|
||||
// messageExpression has access to all the same variables as the rule; the only difference is the return type.
|
||||
// Example:
|
||||
// "x must be less than max ("+string(self.max)+")"
|
||||
// +optional
|
||||
MessageExpression string
|
||||
// reason provides a machine-readable validation failure reason that is returned to the caller when a request fails this validation rule.
|
||||
// The HTTP status code returned to the caller will match the reason of the reason of the first failed validation rule.
|
||||
// The currently supported reasons are: "FieldValueInvalid", "FieldValueForbidden", "FieldValueRequired", "FieldValueDuplicate".
|
||||
// If not set, default to use "FieldValueInvalid".
|
||||
// All future added reasons must be accepted by clients when reading this value and unknown reasons should be treated as FieldValueInvalid.
|
||||
// +optional
|
||||
Reason *FieldValueErrorReason
|
||||
// fieldPath represents the field path returned when the validation fails.
|
||||
// It must be a relative JSON path (i.e. with array notation) scoped to the location of this x-kubernetes-validations extension in the schema and refer to an existing field.
|
||||
// e.g. when validation checks if a specific attribute `foo` under a map `testMap`, the fieldPath could be set to `.testMap.foo`
|
||||
// If the validation checks two lists must have unique attributes, the fieldPath could be set to either of the list: e.g. `.testList`
|
||||
// It does not support list numeric index.
|
||||
// It supports child operation to refer to an existing field currently. Refer to [JSONPath support in Kubernetes](https://kubernetes.io/docs/reference/kubectl/jsonpath/) for more info.
|
||||
// Numeric index of array is not supported.
|
||||
// For field name which contains special characters, use `['specialName']` to refer the field name.
|
||||
// e.g. for attribute `foo.34$` appears in a list `testList`, the fieldPath could be set to `.testList['foo.34$']`
|
||||
// +optional
|
||||
FieldPath string
|
||||
|
||||
// optionalOldSelf is used to opt a transition rule into evaluation
|
||||
// even when the object is first created, or if the old object is
|
||||
// missing the value.
|
||||
//
|
||||
// When enabled `oldSelf` will be a CEL optional whose value will be
|
||||
// `None` if there is no old value, or when the object is initially created.
|
||||
//
|
||||
// You may check for presence of oldSelf using `oldSelf.hasValue()` and
|
||||
// unwrap it after checking using `oldSelf.value()`. Check the CEL
|
||||
// documentation for Optional types for more information:
|
||||
// https://pkg.go.dev/github.com/google/cel-go/cel#OptionalTypes
|
||||
//
|
||||
// May not be set unless `oldSelf` is used in `rule`.
|
||||
//
|
||||
// +featureGate=CRDValidationRatcheting
|
||||
// +optional
|
||||
OptionalOldSelf *bool
|
||||
}
|
||||
|
||||
// JSON represents any valid JSON value.
|
||||
// These types are supported: bool, int64, float64, string, []interface{}, map[string]interface{} and nil.
|
||||
type JSON interface{}
|
||||
|
||||
// JSONSchemaURL represents a schema url.
|
||||
type JSONSchemaURL string
|
||||
|
||||
// JSONSchemaPropsOrArray represents a value that can either be a JSONSchemaProps
|
||||
// or an array of JSONSchemaProps. Mainly here for serialization purposes.
|
||||
type JSONSchemaPropsOrArray struct {
|
||||
Schema *JSONSchemaProps
|
||||
JSONSchemas []JSONSchemaProps
|
||||
}
|
||||
|
||||
// JSONSchemaPropsOrBool represents JSONSchemaProps or a boolean value.
|
||||
// Defaults to true for the boolean property.
|
||||
type JSONSchemaPropsOrBool struct {
|
||||
Allows bool
|
||||
Schema *JSONSchemaProps
|
||||
}
|
||||
|
||||
// JSONSchemaDependencies represent a dependencies property.
|
||||
type JSONSchemaDependencies map[string]JSONSchemaPropsOrStringArray
|
||||
|
||||
// JSONSchemaPropsOrStringArray represents a JSONSchemaProps or a string array.
|
||||
type JSONSchemaPropsOrStringArray struct {
|
||||
Schema *JSONSchemaProps
|
||||
Property []string
|
||||
}
|
||||
|
||||
// JSONSchemaDefinitions contains the models explicitly defined in this spec.
|
||||
type JSONSchemaDefinitions map[string]JSONSchemaProps
|
||||
|
||||
// ExternalDocumentation allows referencing an external resource for extended documentation.
|
||||
type ExternalDocumentation struct {
|
||||
Description string
|
||||
URL string
|
||||
}
|
5
vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1/.import-restrictions
generated
vendored
5
vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1/.import-restrictions
generated
vendored
@ -1,5 +0,0 @@
|
||||
inverseRules:
|
||||
# Allow use of this package in all k8s.io packages.
|
||||
- selectorRegexp: k8s[.]io
|
||||
allowedPrefixes:
|
||||
- ''
|
237
vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1/conversion.go
generated
vendored
237
vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1/conversion.go
generated
vendored
@ -1,237 +0,0 @@
|
||||
/*
|
||||
Copyright 2019 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 v1
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
unsafe "unsafe"
|
||||
|
||||
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
|
||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||
"k8s.io/apimachinery/pkg/conversion"
|
||||
"k8s.io/apimachinery/pkg/util/json"
|
||||
)
|
||||
|
||||
func Convert_apiextensions_JSONSchemaProps_To_v1_JSONSchemaProps(in *apiextensions.JSONSchemaProps, out *JSONSchemaProps, s conversion.Scope) error {
|
||||
if err := autoConvert_apiextensions_JSONSchemaProps_To_v1_JSONSchemaProps(in, out, s); err != nil {
|
||||
return err
|
||||
}
|
||||
if in.Default != nil && *(in.Default) == nil {
|
||||
out.Default = nil
|
||||
}
|
||||
if in.Example != nil && *(in.Example) == nil {
|
||||
out.Example = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var nullLiteral = []byte(`null`)
|
||||
|
||||
func Convert_apiextensions_JSON_To_v1_JSON(in *apiextensions.JSON, out *JSON, s conversion.Scope) error {
|
||||
raw, err := json.Marshal(*in)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(raw) == 0 || bytes.Equal(raw, nullLiteral) {
|
||||
// match JSON#UnmarshalJSON treatment of literal nulls
|
||||
out.Raw = nil
|
||||
} else {
|
||||
out.Raw = raw
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Convert_v1_JSON_To_apiextensions_JSON(in *JSON, out *apiextensions.JSON, s conversion.Scope) error {
|
||||
if in != nil {
|
||||
var i interface{}
|
||||
if len(in.Raw) > 0 && !bytes.Equal(in.Raw, nullLiteral) {
|
||||
if err := json.Unmarshal(in.Raw, &i); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*out = i
|
||||
} else {
|
||||
out = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Convert_apiextensions_CustomResourceDefinitionSpec_To_v1_CustomResourceDefinitionSpec(in *apiextensions.CustomResourceDefinitionSpec, out *CustomResourceDefinitionSpec, s conversion.Scope) error {
|
||||
if err := autoConvert_apiextensions_CustomResourceDefinitionSpec_To_v1_CustomResourceDefinitionSpec(in, out, s); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(out.Versions) == 0 && len(in.Version) > 0 {
|
||||
// no versions were specified, and a version name was specified
|
||||
out.Versions = []CustomResourceDefinitionVersion{{Name: in.Version, Served: true, Storage: true}}
|
||||
}
|
||||
|
||||
// If spec.{subresources,validation,additionalPrinterColumns,selectableFields} exists, move to versions
|
||||
if in.Subresources != nil {
|
||||
subresources := &CustomResourceSubresources{}
|
||||
if err := Convert_apiextensions_CustomResourceSubresources_To_v1_CustomResourceSubresources(in.Subresources, subresources, s); err != nil {
|
||||
return err
|
||||
}
|
||||
for i := range out.Versions {
|
||||
out.Versions[i].Subresources = subresources
|
||||
}
|
||||
}
|
||||
if in.Validation != nil {
|
||||
schema := &CustomResourceValidation{}
|
||||
if err := Convert_apiextensions_CustomResourceValidation_To_v1_CustomResourceValidation(in.Validation, schema, s); err != nil {
|
||||
return err
|
||||
}
|
||||
for i := range out.Versions {
|
||||
out.Versions[i].Schema = schema
|
||||
}
|
||||
}
|
||||
if in.AdditionalPrinterColumns != nil {
|
||||
additionalPrinterColumns := make([]CustomResourceColumnDefinition, len(in.AdditionalPrinterColumns))
|
||||
for i := range in.AdditionalPrinterColumns {
|
||||
if err := Convert_apiextensions_CustomResourceColumnDefinition_To_v1_CustomResourceColumnDefinition(&in.AdditionalPrinterColumns[i], &additionalPrinterColumns[i], s); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for i := range out.Versions {
|
||||
out.Versions[i].AdditionalPrinterColumns = additionalPrinterColumns
|
||||
}
|
||||
}
|
||||
if in.SelectableFields != nil {
|
||||
selectableFields := make([]SelectableField, len(in.SelectableFields))
|
||||
for i := range in.SelectableFields {
|
||||
if err := Convert_apiextensions_SelectableField_To_v1_SelectableField(&in.SelectableFields[i], &selectableFields[i], s); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for i := range out.Versions {
|
||||
out.Versions[i].SelectableFields = selectableFields
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Convert_v1_CustomResourceDefinitionSpec_To_apiextensions_CustomResourceDefinitionSpec(in *CustomResourceDefinitionSpec, out *apiextensions.CustomResourceDefinitionSpec, s conversion.Scope) error {
|
||||
if err := autoConvert_v1_CustomResourceDefinitionSpec_To_apiextensions_CustomResourceDefinitionSpec(in, out, s); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(out.Versions) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Copy versions[0] to version
|
||||
out.Version = out.Versions[0].Name
|
||||
|
||||
// If versions[*].{subresources,schema,additionalPrinterColumns,selectableFields} are identical, move to spec
|
||||
subresources := out.Versions[0].Subresources
|
||||
subresourcesIdentical := true
|
||||
validation := out.Versions[0].Schema
|
||||
validationIdentical := true
|
||||
additionalPrinterColumns := out.Versions[0].AdditionalPrinterColumns
|
||||
additionalPrinterColumnsIdentical := true
|
||||
selectableFields := out.Versions[0].SelectableFields
|
||||
selectableFieldsIdentical := true
|
||||
|
||||
// Detect if per-version fields are identical
|
||||
for _, v := range out.Versions {
|
||||
if subresourcesIdentical && !apiequality.Semantic.DeepEqual(v.Subresources, subresources) {
|
||||
subresourcesIdentical = false
|
||||
}
|
||||
if validationIdentical && !apiequality.Semantic.DeepEqual(v.Schema, validation) {
|
||||
validationIdentical = false
|
||||
}
|
||||
if additionalPrinterColumnsIdentical && !apiequality.Semantic.DeepEqual(v.AdditionalPrinterColumns, additionalPrinterColumns) {
|
||||
additionalPrinterColumnsIdentical = false
|
||||
}
|
||||
if selectableFieldsIdentical && !apiequality.Semantic.DeepEqual(v.SelectableFields, selectableFields) {
|
||||
selectableFieldsIdentical = false
|
||||
}
|
||||
}
|
||||
|
||||
// If they are, set the top-level fields and clear the per-version fields
|
||||
if subresourcesIdentical {
|
||||
out.Subresources = subresources
|
||||
}
|
||||
if validationIdentical {
|
||||
out.Validation = validation
|
||||
}
|
||||
if additionalPrinterColumnsIdentical {
|
||||
out.AdditionalPrinterColumns = additionalPrinterColumns
|
||||
}
|
||||
if selectableFieldsIdentical {
|
||||
out.SelectableFields = selectableFields
|
||||
}
|
||||
for i := range out.Versions {
|
||||
if subresourcesIdentical {
|
||||
out.Versions[i].Subresources = nil
|
||||
}
|
||||
if validationIdentical {
|
||||
out.Versions[i].Schema = nil
|
||||
}
|
||||
if additionalPrinterColumnsIdentical {
|
||||
out.Versions[i].AdditionalPrinterColumns = nil
|
||||
}
|
||||
if selectableFieldsIdentical {
|
||||
out.Versions[i].SelectableFields = nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func Convert_v1_CustomResourceConversion_To_apiextensions_CustomResourceConversion(in *CustomResourceConversion, out *apiextensions.CustomResourceConversion, s conversion.Scope) error {
|
||||
if err := autoConvert_v1_CustomResourceConversion_To_apiextensions_CustomResourceConversion(in, out, s); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
out.WebhookClientConfig = nil
|
||||
out.ConversionReviewVersions = nil
|
||||
if in.Webhook != nil {
|
||||
out.ConversionReviewVersions = in.Webhook.ConversionReviewVersions
|
||||
if in.Webhook.ClientConfig != nil {
|
||||
out.WebhookClientConfig = &apiextensions.WebhookClientConfig{}
|
||||
if err := Convert_v1_WebhookClientConfig_To_apiextensions_WebhookClientConfig(in.Webhook.ClientConfig, out.WebhookClientConfig, s); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Convert_apiextensions_CustomResourceConversion_To_v1_CustomResourceConversion(in *apiextensions.CustomResourceConversion, out *CustomResourceConversion, s conversion.Scope) error {
|
||||
if err := autoConvert_apiextensions_CustomResourceConversion_To_v1_CustomResourceConversion(in, out, s); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
out.Webhook = nil
|
||||
if in.WebhookClientConfig != nil || in.ConversionReviewVersions != nil {
|
||||
out.Webhook = &WebhookConversion{}
|
||||
out.Webhook.ConversionReviewVersions = in.ConversionReviewVersions
|
||||
if in.WebhookClientConfig != nil {
|
||||
out.Webhook.ClientConfig = &WebhookClientConfig{}
|
||||
if err := Convert_apiextensions_WebhookClientConfig_To_v1_WebhookClientConfig(in.WebhookClientConfig, out.Webhook.ClientConfig, s); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Convert_apiextensions_ValidationRules_To_v1_ValidationRules(in *apiextensions.ValidationRules, out *ValidationRules, s conversion.Scope) error {
|
||||
*out = *(*ValidationRules)(unsafe.Pointer(in))
|
||||
return nil
|
||||
}
|
262
vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1/deepcopy.go
generated
vendored
262
vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1/deepcopy.go
generated
vendored
@ -1,262 +0,0 @@
|
||||
/*
|
||||
Copyright 2019 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 v1
|
||||
|
||||
// TODO: Update this after a tag is created for interface fields in DeepCopy
|
||||
func (in *JSONSchemaProps) DeepCopy() *JSONSchemaProps {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(JSONSchemaProps)
|
||||
*out = *in
|
||||
|
||||
if in.Ref != nil {
|
||||
in, out := &in.Ref, &out.Ref
|
||||
if *in == nil {
|
||||
*out = nil
|
||||
} else {
|
||||
*out = new(string)
|
||||
**out = **in
|
||||
}
|
||||
}
|
||||
|
||||
if in.Maximum != nil {
|
||||
in, out := &in.Maximum, &out.Maximum
|
||||
if *in == nil {
|
||||
*out = nil
|
||||
} else {
|
||||
*out = new(float64)
|
||||
**out = **in
|
||||
}
|
||||
}
|
||||
|
||||
if in.Minimum != nil {
|
||||
in, out := &in.Minimum, &out.Minimum
|
||||
if *in == nil {
|
||||
*out = nil
|
||||
} else {
|
||||
*out = new(float64)
|
||||
**out = **in
|
||||
}
|
||||
}
|
||||
|
||||
if in.MaxLength != nil {
|
||||
in, out := &in.MaxLength, &out.MaxLength
|
||||
if *in == nil {
|
||||
*out = nil
|
||||
} else {
|
||||
*out = new(int64)
|
||||
**out = **in
|
||||
}
|
||||
}
|
||||
|
||||
if in.MinLength != nil {
|
||||
in, out := &in.MinLength, &out.MinLength
|
||||
if *in == nil {
|
||||
*out = nil
|
||||
} else {
|
||||
*out = new(int64)
|
||||
**out = **in
|
||||
}
|
||||
}
|
||||
if in.MaxItems != nil {
|
||||
in, out := &in.MaxItems, &out.MaxItems
|
||||
if *in == nil {
|
||||
*out = nil
|
||||
} else {
|
||||
*out = new(int64)
|
||||
**out = **in
|
||||
}
|
||||
}
|
||||
|
||||
if in.MinItems != nil {
|
||||
in, out := &in.MinItems, &out.MinItems
|
||||
if *in == nil {
|
||||
*out = nil
|
||||
} else {
|
||||
*out = new(int64)
|
||||
**out = **in
|
||||
}
|
||||
}
|
||||
|
||||
if in.MultipleOf != nil {
|
||||
in, out := &in.MultipleOf, &out.MultipleOf
|
||||
if *in == nil {
|
||||
*out = nil
|
||||
} else {
|
||||
*out = new(float64)
|
||||
**out = **in
|
||||
}
|
||||
}
|
||||
|
||||
if in.MaxProperties != nil {
|
||||
in, out := &in.MaxProperties, &out.MaxProperties
|
||||
if *in == nil {
|
||||
*out = nil
|
||||
} else {
|
||||
*out = new(int64)
|
||||
**out = **in
|
||||
}
|
||||
}
|
||||
|
||||
if in.MinProperties != nil {
|
||||
in, out := &in.MinProperties, &out.MinProperties
|
||||
if *in == nil {
|
||||
*out = nil
|
||||
} else {
|
||||
*out = new(int64)
|
||||
**out = **in
|
||||
}
|
||||
}
|
||||
|
||||
if in.Required != nil {
|
||||
in, out := &in.Required, &out.Required
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
if *in == nil {
|
||||
*out = nil
|
||||
} else {
|
||||
*out = new(JSONSchemaPropsOrArray)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
if in.AllOf != nil {
|
||||
in, out := &in.AllOf, &out.AllOf
|
||||
*out = make([]JSONSchemaProps, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
|
||||
if in.OneOf != nil {
|
||||
in, out := &in.OneOf, &out.OneOf
|
||||
*out = make([]JSONSchemaProps, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
if in.AnyOf != nil {
|
||||
in, out := &in.AnyOf, &out.AnyOf
|
||||
*out = make([]JSONSchemaProps, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
|
||||
if in.Not != nil {
|
||||
in, out := &in.Not, &out.Not
|
||||
if *in == nil {
|
||||
*out = nil
|
||||
} else {
|
||||
*out = new(JSONSchemaProps)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
if in.Properties != nil {
|
||||
in, out := &in.Properties, &out.Properties
|
||||
*out = make(map[string]JSONSchemaProps, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = *val.DeepCopy()
|
||||
}
|
||||
}
|
||||
|
||||
if in.AdditionalProperties != nil {
|
||||
in, out := &in.AdditionalProperties, &out.AdditionalProperties
|
||||
if *in == nil {
|
||||
*out = nil
|
||||
} else {
|
||||
*out = new(JSONSchemaPropsOrBool)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
if in.PatternProperties != nil {
|
||||
in, out := &in.PatternProperties, &out.PatternProperties
|
||||
*out = make(map[string]JSONSchemaProps, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = *val.DeepCopy()
|
||||
}
|
||||
}
|
||||
|
||||
if in.Dependencies != nil {
|
||||
in, out := &in.Dependencies, &out.Dependencies
|
||||
*out = make(JSONSchemaDependencies, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = *val.DeepCopy()
|
||||
}
|
||||
}
|
||||
|
||||
if in.AdditionalItems != nil {
|
||||
in, out := &in.AdditionalItems, &out.AdditionalItems
|
||||
if *in == nil {
|
||||
*out = nil
|
||||
} else {
|
||||
*out = new(JSONSchemaPropsOrBool)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
if in.Definitions != nil {
|
||||
in, out := &in.Definitions, &out.Definitions
|
||||
*out = make(JSONSchemaDefinitions, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = *val.DeepCopy()
|
||||
}
|
||||
}
|
||||
|
||||
if in.ExternalDocs != nil {
|
||||
in, out := &in.ExternalDocs, &out.ExternalDocs
|
||||
if *in == nil {
|
||||
*out = nil
|
||||
} else {
|
||||
*out = new(ExternalDocumentation)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
}
|
||||
|
||||
if in.XPreserveUnknownFields != nil {
|
||||
in, out := &in.XPreserveUnknownFields, &out.XPreserveUnknownFields
|
||||
if *in == nil {
|
||||
*out = nil
|
||||
} else {
|
||||
*out = new(bool)
|
||||
**out = **in
|
||||
}
|
||||
}
|
||||
|
||||
if in.XMapType != nil {
|
||||
in, out := &in.XMapType, &out.XMapType
|
||||
*out = new(string)
|
||||
**out = **in
|
||||
}
|
||||
|
||||
if in.XValidations != nil {
|
||||
inValidations, outValidations := &in.XValidations, &out.XValidations
|
||||
*outValidations = make([]ValidationRule, len(*inValidations))
|
||||
for i := range *inValidations {
|
||||
in.XValidations[i].DeepCopyInto(&out.XValidations[i])
|
||||
}
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
61
vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1/defaults.go
generated
vendored
61
vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1/defaults.go
generated
vendored
@ -1,61 +0,0 @@
|
||||
/*
|
||||
Copyright 2019 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 v1
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
utilpointer "k8s.io/utils/pointer"
|
||||
)
|
||||
|
||||
func addDefaultingFuncs(scheme *runtime.Scheme) error {
|
||||
return RegisterDefaults(scheme)
|
||||
}
|
||||
|
||||
func SetDefaults_CustomResourceDefinition(obj *CustomResourceDefinition) {
|
||||
SetDefaults_CustomResourceDefinitionSpec(&obj.Spec)
|
||||
if len(obj.Status.StoredVersions) == 0 {
|
||||
for _, v := range obj.Spec.Versions {
|
||||
if v.Storage {
|
||||
obj.Status.StoredVersions = append(obj.Status.StoredVersions, v.Name)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func SetDefaults_CustomResourceDefinitionSpec(obj *CustomResourceDefinitionSpec) {
|
||||
if len(obj.Names.Singular) == 0 {
|
||||
obj.Names.Singular = strings.ToLower(obj.Names.Kind)
|
||||
}
|
||||
if len(obj.Names.ListKind) == 0 && len(obj.Names.Kind) > 0 {
|
||||
obj.Names.ListKind = obj.Names.Kind + "List"
|
||||
}
|
||||
if obj.Conversion == nil {
|
||||
obj.Conversion = &CustomResourceConversion{
|
||||
Strategy: NoneConverter,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SetDefaults_ServiceReference sets defaults for Webhook's ServiceReference
|
||||
func SetDefaults_ServiceReference(obj *ServiceReference) {
|
||||
if obj.Port == nil {
|
||||
obj.Port = utilpointer.Int32Ptr(443)
|
||||
}
|
||||
}
|
26
vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1/doc.go
generated
vendored
26
vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1/doc.go
generated
vendored
@ -1,26 +0,0 @@
|
||||
/*
|
||||
Copyright 2019 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.
|
||||
*/
|
||||
|
||||
// +k8s:deepcopy-gen=package
|
||||
// +k8s:protobuf-gen=package
|
||||
// +k8s:conversion-gen=k8s.io/apiextensions-apiserver/pkg/apis/apiextensions
|
||||
// +k8s:defaulter-gen=TypeMeta
|
||||
// +k8s:openapi-gen=true
|
||||
// +k8s:prerelease-lifecycle-gen=true
|
||||
// +groupName=apiextensions.k8s.io
|
||||
|
||||
// Package v1 is the v1 version of the API.
|
||||
package v1 // import "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
9644
vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1/generated.pb.go
generated
vendored
9644
vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1/generated.pb.go
generated
vendored
File diff suppressed because it is too large
Load Diff
829
vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1/generated.proto
generated
vendored
829
vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1/generated.proto
generated
vendored
@ -1,829 +0,0 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
|
||||
// This file was autogenerated by go-to-protobuf. Do not edit it manually!
|
||||
|
||||
syntax = "proto2";
|
||||
|
||||
package k8s.io.apiextensions_apiserver.pkg.apis.apiextensions.v1;
|
||||
|
||||
import "k8s.io/apimachinery/pkg/apis/meta/v1/generated.proto";
|
||||
import "k8s.io/apimachinery/pkg/runtime/generated.proto";
|
||||
import "k8s.io/apimachinery/pkg/runtime/schema/generated.proto";
|
||||
|
||||
// Package-wide variables from generator "generated".
|
||||
option go_package = "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1";
|
||||
|
||||
// ConversionRequest describes the conversion request parameters.
|
||||
message ConversionRequest {
|
||||
// uid is an identifier for the individual request/response. It allows distinguishing instances of requests which are
|
||||
// otherwise identical (parallel requests, etc).
|
||||
// The UID is meant to track the round trip (request/response) between the Kubernetes API server and the webhook, not the user request.
|
||||
// It is suitable for correlating log entries between the webhook and apiserver, for either auditing or debugging.
|
||||
optional string uid = 1;
|
||||
|
||||
// desiredAPIVersion is the version to convert given objects to. e.g. "myapi.example.com/v1"
|
||||
optional string desiredAPIVersion = 2;
|
||||
|
||||
// objects is the list of custom resource objects to be converted.
|
||||
// +listType=atomic
|
||||
repeated .k8s.io.apimachinery.pkg.runtime.RawExtension objects = 3;
|
||||
}
|
||||
|
||||
// ConversionResponse describes a conversion response.
|
||||
message ConversionResponse {
|
||||
// uid is an identifier for the individual request/response.
|
||||
// This should be copied over from the corresponding `request.uid`.
|
||||
optional string uid = 1;
|
||||
|
||||
// convertedObjects is the list of converted version of `request.objects` if the `result` is successful, otherwise empty.
|
||||
// The webhook is expected to set `apiVersion` of these objects to the `request.desiredAPIVersion`. The list
|
||||
// must also have the same size as the input list with the same objects in the same order (equal kind, metadata.uid, metadata.name and metadata.namespace).
|
||||
// The webhook is allowed to mutate labels and annotations. Any other change to the metadata is silently ignored.
|
||||
// +listType=atomic
|
||||
repeated .k8s.io.apimachinery.pkg.runtime.RawExtension convertedObjects = 2;
|
||||
|
||||
// result contains the result of conversion with extra details if the conversion failed. `result.status` determines if
|
||||
// the conversion failed or succeeded. The `result.status` field is required and represents the success or failure of the
|
||||
// conversion. A successful conversion must set `result.status` to `Success`. A failed conversion must set
|
||||
// `result.status` to `Failure` and provide more details in `result.message` and return http status 200. The `result.message`
|
||||
// will be used to construct an error message for the end user.
|
||||
optional .k8s.io.apimachinery.pkg.apis.meta.v1.Status result = 3;
|
||||
}
|
||||
|
||||
// ConversionReview describes a conversion request/response.
|
||||
message ConversionReview {
|
||||
// request describes the attributes for the conversion request.
|
||||
// +optional
|
||||
optional ConversionRequest request = 1;
|
||||
|
||||
// response describes the attributes for the conversion response.
|
||||
// +optional
|
||||
optional ConversionResponse response = 2;
|
||||
}
|
||||
|
||||
// CustomResourceColumnDefinition specifies a column for server side printing.
|
||||
message CustomResourceColumnDefinition {
|
||||
// name is a human readable name for the column.
|
||||
optional string name = 1;
|
||||
|
||||
// type is an OpenAPI type definition for this column.
|
||||
// See https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#data-types for details.
|
||||
optional string type = 2;
|
||||
|
||||
// format is an optional OpenAPI type definition for this column. The 'name' format is applied
|
||||
// to the primary identifier column to assist in clients identifying column is the resource name.
|
||||
// See https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#data-types for details.
|
||||
// +optional
|
||||
optional string format = 3;
|
||||
|
||||
// description is a human readable description of this column.
|
||||
// +optional
|
||||
optional string description = 4;
|
||||
|
||||
// priority is an integer defining the relative importance of this column compared to others. Lower
|
||||
// numbers are considered higher priority. Columns that may be omitted in limited space scenarios
|
||||
// should be given a priority greater than 0.
|
||||
// +optional
|
||||
optional int32 priority = 5;
|
||||
|
||||
// jsonPath is a simple JSON path (i.e. with array notation) which is evaluated against
|
||||
// each custom resource to produce the value for this column.
|
||||
optional string jsonPath = 6;
|
||||
}
|
||||
|
||||
// CustomResourceConversion describes how to convert different versions of a CR.
|
||||
message CustomResourceConversion {
|
||||
// strategy specifies how custom resources are converted between versions. Allowed values are:
|
||||
// - `"None"`: The converter only change the apiVersion and would not touch any other field in the custom resource.
|
||||
// - `"Webhook"`: API Server will call to an external webhook to do the conversion. Additional information
|
||||
// is needed for this option. This requires spec.preserveUnknownFields to be false, and spec.conversion.webhook to be set.
|
||||
optional string strategy = 1;
|
||||
|
||||
// webhook describes how to call the conversion webhook. Required when `strategy` is set to `"Webhook"`.
|
||||
// +optional
|
||||
optional WebhookConversion webhook = 2;
|
||||
}
|
||||
|
||||
// CustomResourceDefinition represents a resource that should be exposed on the API server. Its name MUST be in the format
|
||||
// <.spec.name>.<.spec.group>.
|
||||
message CustomResourceDefinition {
|
||||
// Standard object's metadata
|
||||
// More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata
|
||||
// +optional
|
||||
optional .k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1;
|
||||
|
||||
// spec describes how the user wants the resources to appear
|
||||
optional CustomResourceDefinitionSpec spec = 2;
|
||||
|
||||
// status indicates the actual state of the CustomResourceDefinition
|
||||
// +optional
|
||||
optional CustomResourceDefinitionStatus status = 3;
|
||||
}
|
||||
|
||||
// CustomResourceDefinitionCondition contains details for the current condition of this pod.
|
||||
message CustomResourceDefinitionCondition {
|
||||
// type is the type of the condition. Types include Established, NamesAccepted and Terminating.
|
||||
optional string type = 1;
|
||||
|
||||
// status is the status of the condition.
|
||||
// Can be True, False, Unknown.
|
||||
optional string status = 2;
|
||||
|
||||
// lastTransitionTime last time the condition transitioned from one status to another.
|
||||
// +optional
|
||||
optional .k8s.io.apimachinery.pkg.apis.meta.v1.Time lastTransitionTime = 3;
|
||||
|
||||
// reason is a unique, one-word, CamelCase reason for the condition's last transition.
|
||||
// +optional
|
||||
optional string reason = 4;
|
||||
|
||||
// message is a human-readable message indicating details about last transition.
|
||||
// +optional
|
||||
optional string message = 5;
|
||||
}
|
||||
|
||||
// CustomResourceDefinitionList is a list of CustomResourceDefinition objects.
|
||||
message CustomResourceDefinitionList {
|
||||
// Standard object's metadata
|
||||
// More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata
|
||||
// +optional
|
||||
optional .k8s.io.apimachinery.pkg.apis.meta.v1.ListMeta metadata = 1;
|
||||
|
||||
// items list individual CustomResourceDefinition objects
|
||||
repeated CustomResourceDefinition items = 2;
|
||||
}
|
||||
|
||||
// CustomResourceDefinitionNames indicates the names to serve this CustomResourceDefinition
|
||||
message CustomResourceDefinitionNames {
|
||||
// plural is the plural name of the resource to serve.
|
||||
// The custom resources are served under `/apis/<group>/<version>/.../<plural>`.
|
||||
// Must match the name of the CustomResourceDefinition (in the form `<names.plural>.<group>`).
|
||||
// Must be all lowercase.
|
||||
optional string plural = 1;
|
||||
|
||||
// singular is the singular name of the resource. It must be all lowercase. Defaults to lowercased `kind`.
|
||||
// +optional
|
||||
optional string singular = 2;
|
||||
|
||||
// shortNames are short names for the resource, exposed in API discovery documents,
|
||||
// and used by clients to support invocations like `kubectl get <shortname>`.
|
||||
// It must be all lowercase.
|
||||
// +optional
|
||||
// +listType=atomic
|
||||
repeated string shortNames = 3;
|
||||
|
||||
// kind is the serialized kind of the resource. It is normally CamelCase and singular.
|
||||
// Custom resource instances will use this value as the `kind` attribute in API calls.
|
||||
optional string kind = 4;
|
||||
|
||||
// listKind is the serialized kind of the list for this resource. Defaults to "`kind`List".
|
||||
// +optional
|
||||
optional string listKind = 5;
|
||||
|
||||
// categories is a list of grouped resources this custom resource belongs to (e.g. 'all').
|
||||
// This is published in API discovery documents, and used by clients to support invocations like
|
||||
// `kubectl get all`.
|
||||
// +optional
|
||||
// +listType=atomic
|
||||
repeated string categories = 6;
|
||||
}
|
||||
|
||||
// CustomResourceDefinitionSpec describes how a user wants their resource to appear
|
||||
message CustomResourceDefinitionSpec {
|
||||
// group is the API group of the defined custom resource.
|
||||
// The custom resources are served under `/apis/<group>/...`.
|
||||
// Must match the name of the CustomResourceDefinition (in the form `<names.plural>.<group>`).
|
||||
optional string group = 1;
|
||||
|
||||
// names specify the resource and kind names for the custom resource.
|
||||
optional CustomResourceDefinitionNames names = 3;
|
||||
|
||||
// scope indicates whether the defined custom resource is cluster- or namespace-scoped.
|
||||
// Allowed values are `Cluster` and `Namespaced`.
|
||||
optional string scope = 4;
|
||||
|
||||
// versions is the list of all API versions of the defined custom resource.
|
||||
// Version names are used to compute the order in which served versions are listed in API discovery.
|
||||
// If the version string is "kube-like", it will sort above non "kube-like" version strings, which are ordered
|
||||
// lexicographically. "Kube-like" versions start with a "v", then are followed by a number (the major version),
|
||||
// then optionally the string "alpha" or "beta" and another number (the minor version). These are sorted first
|
||||
// by GA > beta > alpha (where GA is a version with no suffix such as beta or alpha), and then by comparing
|
||||
// major version, then minor version. An example sorted list of versions:
|
||||
// v10, v2, v1, v11beta2, v10beta3, v3beta1, v12alpha1, v11alpha2, foo1, foo10.
|
||||
// +listType=atomic
|
||||
repeated CustomResourceDefinitionVersion versions = 7;
|
||||
|
||||
// conversion defines conversion settings for the CRD.
|
||||
// +optional
|
||||
optional CustomResourceConversion conversion = 9;
|
||||
|
||||
// preserveUnknownFields indicates that object fields which are not specified
|
||||
// in the OpenAPI schema should be preserved when persisting to storage.
|
||||
// apiVersion, kind, metadata and known fields inside metadata are always preserved.
|
||||
// This field is deprecated in favor of setting `x-preserve-unknown-fields` to true in `spec.versions[*].schema.openAPIV3Schema`.
|
||||
// See https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#field-pruning for details.
|
||||
// +optional
|
||||
optional bool preserveUnknownFields = 10;
|
||||
}
|
||||
|
||||
// CustomResourceDefinitionStatus indicates the state of the CustomResourceDefinition
|
||||
message CustomResourceDefinitionStatus {
|
||||
// conditions indicate state for particular aspects of a CustomResourceDefinition
|
||||
// +optional
|
||||
// +listType=map
|
||||
// +listMapKey=type
|
||||
repeated CustomResourceDefinitionCondition conditions = 1;
|
||||
|
||||
// acceptedNames are the names that are actually being used to serve discovery.
|
||||
// They may be different than the names in spec.
|
||||
// +optional
|
||||
optional CustomResourceDefinitionNames acceptedNames = 2;
|
||||
|
||||
// storedVersions lists all versions of CustomResources that were ever persisted. Tracking these
|
||||
// versions allows a migration path for stored versions in etcd. The field is mutable
|
||||
// so a migration controller can finish a migration to another version (ensuring
|
||||
// no old objects are left in storage), and then remove the rest of the
|
||||
// versions from this list.
|
||||
// Versions may not be removed from `spec.versions` while they exist in this list.
|
||||
// +optional
|
||||
// +listType=atomic
|
||||
repeated string storedVersions = 3;
|
||||
}
|
||||
|
||||
// CustomResourceDefinitionVersion describes a version for CRD.
|
||||
message CustomResourceDefinitionVersion {
|
||||
// name is the version name, e.g. “v1”, “v2beta1”, etc.
|
||||
// The custom resources are served under this version at `/apis/<group>/<version>/...` if `served` is true.
|
||||
optional string name = 1;
|
||||
|
||||
// served is a flag enabling/disabling this version from being served via REST APIs
|
||||
optional bool served = 2;
|
||||
|
||||
// storage indicates this version should be used when persisting custom resources to storage.
|
||||
// There must be exactly one version with storage=true.
|
||||
optional bool storage = 3;
|
||||
|
||||
// deprecated indicates this version of the custom resource API is deprecated.
|
||||
// When set to true, API requests to this version receive a warning header in the server response.
|
||||
// Defaults to false.
|
||||
// +optional
|
||||
optional bool deprecated = 7;
|
||||
|
||||
// deprecationWarning overrides the default warning returned to API clients.
|
||||
// May only be set when `deprecated` is true.
|
||||
// The default warning indicates this version is deprecated and recommends use
|
||||
// of the newest served version of equal or greater stability, if one exists.
|
||||
// +optional
|
||||
optional string deprecationWarning = 8;
|
||||
|
||||
// schema describes the schema used for validation, pruning, and defaulting of this version of the custom resource.
|
||||
// +optional
|
||||
optional CustomResourceValidation schema = 4;
|
||||
|
||||
// subresources specify what subresources this version of the defined custom resource have.
|
||||
// +optional
|
||||
optional CustomResourceSubresources subresources = 5;
|
||||
|
||||
// additionalPrinterColumns specifies additional columns returned in Table output.
|
||||
// See https://kubernetes.io/docs/reference/using-api/api-concepts/#receiving-resources-as-tables for details.
|
||||
// If no columns are specified, a single column displaying the age of the custom resource is used.
|
||||
// +optional
|
||||
// +listType=atomic
|
||||
repeated CustomResourceColumnDefinition additionalPrinterColumns = 6;
|
||||
|
||||
// selectableFields specifies paths to fields that may be used as field selectors.
|
||||
// A maximum of 8 selectable fields are allowed.
|
||||
// See https://kubernetes.io/docs/concepts/overview/working-with-objects/field-selectors
|
||||
//
|
||||
// +featureGate=CustomResourceFieldSelectors
|
||||
// +optional
|
||||
// +listType=atomic
|
||||
repeated SelectableField selectableFields = 9;
|
||||
}
|
||||
|
||||
// CustomResourceSubresourceScale defines how to serve the scale subresource for CustomResources.
|
||||
message CustomResourceSubresourceScale {
|
||||
// specReplicasPath defines the JSON path inside of a custom resource that corresponds to Scale `spec.replicas`.
|
||||
// Only JSON paths without the array notation are allowed.
|
||||
// Must be a JSON Path under `.spec`.
|
||||
// If there is no value under the given path in the custom resource, the `/scale` subresource will return an error on GET.
|
||||
optional string specReplicasPath = 1;
|
||||
|
||||
// statusReplicasPath defines the JSON path inside of a custom resource that corresponds to Scale `status.replicas`.
|
||||
// Only JSON paths without the array notation are allowed.
|
||||
// Must be a JSON Path under `.status`.
|
||||
// If there is no value under the given path in the custom resource, the `status.replicas` value in the `/scale` subresource
|
||||
// will default to 0.
|
||||
optional string statusReplicasPath = 2;
|
||||
|
||||
// labelSelectorPath defines the JSON path inside of a custom resource that corresponds to Scale `status.selector`.
|
||||
// Only JSON paths without the array notation are allowed.
|
||||
// Must be a JSON Path under `.status` or `.spec`.
|
||||
// Must be set to work with HorizontalPodAutoscaler.
|
||||
// The field pointed by this JSON path must be a string field (not a complex selector struct)
|
||||
// which contains a serialized label selector in string form.
|
||||
// More info: https://kubernetes.io/docs/tasks/access-kubernetes-api/custom-resources/custom-resource-definitions#scale-subresource
|
||||
// If there is no value under the given path in the custom resource, the `status.selector` value in the `/scale`
|
||||
// subresource will default to the empty string.
|
||||
// +optional
|
||||
optional string labelSelectorPath = 3;
|
||||
}
|
||||
|
||||
// CustomResourceSubresourceStatus defines how to serve the status subresource for CustomResources.
|
||||
// Status is represented by the `.status` JSON path inside of a CustomResource. When set,
|
||||
// * exposes a /status subresource for the custom resource
|
||||
// * PUT requests to the /status subresource take a custom resource object, and ignore changes to anything except the status stanza
|
||||
// * PUT/POST/PATCH requests to the custom resource ignore changes to the status stanza
|
||||
message CustomResourceSubresourceStatus {
|
||||
}
|
||||
|
||||
// CustomResourceSubresources defines the status and scale subresources for CustomResources.
|
||||
message CustomResourceSubresources {
|
||||
// status indicates the custom resource should serve a `/status` subresource.
|
||||
// When enabled:
|
||||
// 1. requests to the custom resource primary endpoint ignore changes to the `status` stanza of the object.
|
||||
// 2. requests to the custom resource `/status` subresource ignore changes to anything other than the `status` stanza of the object.
|
||||
// +optional
|
||||
optional CustomResourceSubresourceStatus status = 1;
|
||||
|
||||
// scale indicates the custom resource should serve a `/scale` subresource that returns an `autoscaling/v1` Scale object.
|
||||
// +optional
|
||||
optional CustomResourceSubresourceScale scale = 2;
|
||||
}
|
||||
|
||||
// CustomResourceValidation is a list of validation methods for CustomResources.
|
||||
message CustomResourceValidation {
|
||||
// openAPIV3Schema is the OpenAPI v3 schema to use for validation and pruning.
|
||||
// +optional
|
||||
optional JSONSchemaProps openAPIV3Schema = 1;
|
||||
}
|
||||
|
||||
// ExternalDocumentation allows referencing an external resource for extended documentation.
|
||||
message ExternalDocumentation {
|
||||
optional string description = 1;
|
||||
|
||||
optional string url = 2;
|
||||
}
|
||||
|
||||
// JSON represents any valid JSON value.
|
||||
// These types are supported: bool, int64, float64, string, []interface{}, map[string]interface{} and nil.
|
||||
message JSON {
|
||||
optional bytes raw = 1;
|
||||
}
|
||||
|
||||
// JSONSchemaProps is a JSON-Schema following Specification Draft 4 (http://json-schema.org/).
|
||||
message JSONSchemaProps {
|
||||
optional string id = 1;
|
||||
|
||||
optional string schema = 2;
|
||||
|
||||
optional string ref = 3;
|
||||
|
||||
optional string description = 4;
|
||||
|
||||
optional string type = 5;
|
||||
|
||||
// format is an OpenAPI v3 format string. Unknown formats are ignored. The following formats are validated:
|
||||
//
|
||||
// - bsonobjectid: a bson object ID, i.e. a 24 characters hex string
|
||||
// - uri: an URI as parsed by Golang net/url.ParseRequestURI
|
||||
// - email: an email address as parsed by Golang net/mail.ParseAddress
|
||||
// - hostname: a valid representation for an Internet host name, as defined by RFC 1034, section 3.1 [RFC1034].
|
||||
// - ipv4: an IPv4 IP as parsed by Golang net.ParseIP
|
||||
// - ipv6: an IPv6 IP as parsed by Golang net.ParseIP
|
||||
// - cidr: a CIDR as parsed by Golang net.ParseCIDR
|
||||
// - mac: a MAC address as parsed by Golang net.ParseMAC
|
||||
// - uuid: an UUID that allows uppercase defined by the regex (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{12}$
|
||||
// - uuid3: an UUID3 that allows uppercase defined by the regex (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?3[0-9a-f]{3}-?[0-9a-f]{4}-?[0-9a-f]{12}$
|
||||
// - uuid4: an UUID4 that allows uppercase defined by the regex (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?4[0-9a-f]{3}-?[89ab][0-9a-f]{3}-?[0-9a-f]{12}$
|
||||
// - uuid5: an UUID5 that allows uppercase defined by the regex (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?5[0-9a-f]{3}-?[89ab][0-9a-f]{3}-?[0-9a-f]{12}$
|
||||
// - isbn: an ISBN10 or ISBN13 number string like "0321751043" or "978-0321751041"
|
||||
// - isbn10: an ISBN10 number string like "0321751043"
|
||||
// - isbn13: an ISBN13 number string like "978-0321751041"
|
||||
// - creditcard: a credit card number defined by the regex ^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\\d{3})\\d{11})$ with any non digit characters mixed in
|
||||
// - ssn: a U.S. social security number following the regex ^\\d{3}[- ]?\\d{2}[- ]?\\d{4}$
|
||||
// - hexcolor: an hexadecimal color code like "#FFFFFF: following the regex ^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$
|
||||
// - rgbcolor: an RGB color code like rgb like "rgb(255,255,2559"
|
||||
// - byte: base64 encoded binary data
|
||||
// - password: any kind of string
|
||||
// - date: a date string like "2006-01-02" as defined by full-date in RFC3339
|
||||
// - duration: a duration string like "22 ns" as parsed by Golang time.ParseDuration or compatible with Scala duration format
|
||||
// - datetime: a date time string like "2014-12-15T19:30:20.000Z" as defined by date-time in RFC3339.
|
||||
optional string format = 6;
|
||||
|
||||
optional string title = 7;
|
||||
|
||||
// default is a default value for undefined object fields.
|
||||
// Defaulting is a beta feature under the CustomResourceDefaulting feature gate.
|
||||
// Defaulting requires spec.preserveUnknownFields to be false.
|
||||
optional JSON default = 8;
|
||||
|
||||
optional double maximum = 9;
|
||||
|
||||
optional bool exclusiveMaximum = 10;
|
||||
|
||||
optional double minimum = 11;
|
||||
|
||||
optional bool exclusiveMinimum = 12;
|
||||
|
||||
optional int64 maxLength = 13;
|
||||
|
||||
optional int64 minLength = 14;
|
||||
|
||||
optional string pattern = 15;
|
||||
|
||||
optional int64 maxItems = 16;
|
||||
|
||||
optional int64 minItems = 17;
|
||||
|
||||
optional bool uniqueItems = 18;
|
||||
|
||||
optional double multipleOf = 19;
|
||||
|
||||
// +listType=atomic
|
||||
repeated JSON enum = 20;
|
||||
|
||||
optional int64 maxProperties = 21;
|
||||
|
||||
optional int64 minProperties = 22;
|
||||
|
||||
// +listType=atomic
|
||||
repeated string required = 23;
|
||||
|
||||
optional JSONSchemaPropsOrArray items = 24;
|
||||
|
||||
// +listType=atomic
|
||||
repeated JSONSchemaProps allOf = 25;
|
||||
|
||||
// +listType=atomic
|
||||
repeated JSONSchemaProps oneOf = 26;
|
||||
|
||||
// +listType=atomic
|
||||
repeated JSONSchemaProps anyOf = 27;
|
||||
|
||||
optional JSONSchemaProps not = 28;
|
||||
|
||||
map<string, JSONSchemaProps> properties = 29;
|
||||
|
||||
optional JSONSchemaPropsOrBool additionalProperties = 30;
|
||||
|
||||
map<string, JSONSchemaProps> patternProperties = 31;
|
||||
|
||||
map<string, JSONSchemaPropsOrStringArray> dependencies = 32;
|
||||
|
||||
optional JSONSchemaPropsOrBool additionalItems = 33;
|
||||
|
||||
map<string, JSONSchemaProps> definitions = 34;
|
||||
|
||||
optional ExternalDocumentation externalDocs = 35;
|
||||
|
||||
optional JSON example = 36;
|
||||
|
||||
optional bool nullable = 37;
|
||||
|
||||
// x-kubernetes-preserve-unknown-fields stops the API server
|
||||
// decoding step from pruning fields which are not specified
|
||||
// in the validation schema. This affects fields recursively,
|
||||
// but switches back to normal pruning behaviour if nested
|
||||
// properties or additionalProperties are specified in the schema.
|
||||
// This can either be true or undefined. False is forbidden.
|
||||
optional bool xKubernetesPreserveUnknownFields = 38;
|
||||
|
||||
// x-kubernetes-embedded-resource defines that the value is an
|
||||
// embedded Kubernetes runtime.Object, with TypeMeta and
|
||||
// ObjectMeta. The type must be object. It is allowed to further
|
||||
// restrict the embedded object. kind, apiVersion and metadata
|
||||
// are validated automatically. x-kubernetes-preserve-unknown-fields
|
||||
// is allowed to be true, but does not have to be if the object
|
||||
// is fully specified (up to kind, apiVersion, metadata).
|
||||
optional bool xKubernetesEmbeddedResource = 39;
|
||||
|
||||
// x-kubernetes-int-or-string specifies that this value is
|
||||
// either an integer or a string. If this is true, an empty
|
||||
// type is allowed and type as child of anyOf is permitted
|
||||
// if following one of the following patterns:
|
||||
//
|
||||
// 1) anyOf:
|
||||
// - type: integer
|
||||
// - type: string
|
||||
// 2) allOf:
|
||||
// - anyOf:
|
||||
// - type: integer
|
||||
// - type: string
|
||||
// - ... zero or more
|
||||
optional bool xKubernetesIntOrString = 40;
|
||||
|
||||
// x-kubernetes-list-map-keys annotates an array with the x-kubernetes-list-type `map` by specifying the keys used
|
||||
// as the index of the map.
|
||||
//
|
||||
// This tag MUST only be used on lists that have the "x-kubernetes-list-type"
|
||||
// extension set to "map". Also, the values specified for this attribute must
|
||||
// be a scalar typed field of the child structure (no nesting is supported).
|
||||
//
|
||||
// The properties specified must either be required or have a default value,
|
||||
// to ensure those properties are present for all list items.
|
||||
//
|
||||
// +optional
|
||||
// +listType=atomic
|
||||
repeated string xKubernetesListMapKeys = 41;
|
||||
|
||||
// x-kubernetes-list-type annotates an array to further describe its topology.
|
||||
// This extension must only be used on lists and may have 3 possible values:
|
||||
//
|
||||
// 1) `atomic`: the list is treated as a single entity, like a scalar.
|
||||
// Atomic lists will be entirely replaced when updated. This extension
|
||||
// may be used on any type of list (struct, scalar, ...).
|
||||
// 2) `set`:
|
||||
// Sets are lists that must not have multiple items with the same value. Each
|
||||
// value must be a scalar, an object with x-kubernetes-map-type `atomic` or an
|
||||
// array with x-kubernetes-list-type `atomic`.
|
||||
// 3) `map`:
|
||||
// These lists are like maps in that their elements have a non-index key
|
||||
// used to identify them. Order is preserved upon merge. The map tag
|
||||
// must only be used on a list with elements of type object.
|
||||
// Defaults to atomic for arrays.
|
||||
// +optional
|
||||
optional string xKubernetesListType = 42;
|
||||
|
||||
// x-kubernetes-map-type annotates an object to further describe its topology.
|
||||
// This extension must only be used when type is object and may have 2 possible values:
|
||||
//
|
||||
// 1) `granular`:
|
||||
// These maps are actual maps (key-value pairs) and each fields are independent
|
||||
// from each other (they can each be manipulated by separate actors). This is
|
||||
// the default behaviour for all maps.
|
||||
// 2) `atomic`: the list is treated as a single entity, like a scalar.
|
||||
// Atomic maps will be entirely replaced when updated.
|
||||
// +optional
|
||||
optional string xKubernetesMapType = 43;
|
||||
|
||||
// x-kubernetes-validations describes a list of validation rules written in the CEL expression language.
|
||||
// +patchMergeKey=rule
|
||||
// +patchStrategy=merge
|
||||
// +listType=map
|
||||
// +listMapKey=rule
|
||||
repeated ValidationRule xKubernetesValidations = 44;
|
||||
}
|
||||
|
||||
// JSONSchemaPropsOrArray represents a value that can either be a JSONSchemaProps
|
||||
// or an array of JSONSchemaProps. Mainly here for serialization purposes.
|
||||
message JSONSchemaPropsOrArray {
|
||||
optional JSONSchemaProps schema = 1;
|
||||
|
||||
// +listType=atomic
|
||||
repeated JSONSchemaProps jSONSchemas = 2;
|
||||
}
|
||||
|
||||
// JSONSchemaPropsOrBool represents JSONSchemaProps or a boolean value.
|
||||
// Defaults to true for the boolean property.
|
||||
message JSONSchemaPropsOrBool {
|
||||
optional bool allows = 1;
|
||||
|
||||
optional JSONSchemaProps schema = 2;
|
||||
}
|
||||
|
||||
// JSONSchemaPropsOrStringArray represents a JSONSchemaProps or a string array.
|
||||
message JSONSchemaPropsOrStringArray {
|
||||
optional JSONSchemaProps schema = 1;
|
||||
|
||||
// +listType=atomic
|
||||
repeated string property = 2;
|
||||
}
|
||||
|
||||
// SelectableField specifies the JSON path of a field that may be used with field selectors.
|
||||
message SelectableField {
|
||||
// jsonPath is a simple JSON path which is evaluated against each custom resource to produce a
|
||||
// field selector value.
|
||||
// Only JSON paths without the array notation are allowed.
|
||||
// Must point to a field of type string, boolean or integer. Types with enum values
|
||||
// and strings with formats are allowed.
|
||||
// If jsonPath refers to absent field in a resource, the jsonPath evaluates to an empty string.
|
||||
// Must not point to metdata fields.
|
||||
// Required.
|
||||
optional string jsonPath = 1;
|
||||
}
|
||||
|
||||
// ServiceReference holds a reference to Service.legacy.k8s.io
|
||||
message ServiceReference {
|
||||
// namespace is the namespace of the service.
|
||||
// Required
|
||||
optional string namespace = 1;
|
||||
|
||||
// name is the name of the service.
|
||||
// Required
|
||||
optional string name = 2;
|
||||
|
||||
// path is an optional URL path at which the webhook will be contacted.
|
||||
// +optional
|
||||
optional string path = 3;
|
||||
|
||||
// port is an optional service port at which the webhook will be contacted.
|
||||
// `port` should be a valid port number (1-65535, inclusive).
|
||||
// Defaults to 443 for backward compatibility.
|
||||
// +optional
|
||||
optional int32 port = 4;
|
||||
}
|
||||
|
||||
// ValidationRule describes a validation rule written in the CEL expression language.
|
||||
message ValidationRule {
|
||||
// Rule represents the expression which will be evaluated by CEL.
|
||||
// ref: https://github.com/google/cel-spec
|
||||
// The Rule is scoped to the location of the x-kubernetes-validations extension in the schema.
|
||||
// The `self` variable in the CEL expression is bound to the scoped value.
|
||||
// Example:
|
||||
// - Rule scoped to the root of a resource with a status subresource: {"rule": "self.status.actual <= self.spec.maxDesired"}
|
||||
//
|
||||
// If the Rule is scoped to an object with properties, the accessible properties of the object are field selectable
|
||||
// via `self.field` and field presence can be checked via `has(self.field)`. Null valued fields are treated as
|
||||
// absent fields in CEL expressions.
|
||||
// If the Rule is scoped to an object with additionalProperties (i.e. a map) the value of the map
|
||||
// are accessible via `self[mapKey]`, map containment can be checked via `mapKey in self` and all entries of the map
|
||||
// are accessible via CEL macros and functions such as `self.all(...)`.
|
||||
// If the Rule is scoped to an array, the elements of the array are accessible via `self[i]` and also by macros and
|
||||
// functions.
|
||||
// If the Rule is scoped to a scalar, `self` is bound to the scalar value.
|
||||
// Examples:
|
||||
// - Rule scoped to a map of objects: {"rule": "self.components['Widget'].priority < 10"}
|
||||
// - Rule scoped to a list of integers: {"rule": "self.values.all(value, value >= 0 && value < 100)"}
|
||||
// - Rule scoped to a string value: {"rule": "self.startsWith('kube')"}
|
||||
//
|
||||
// The `apiVersion`, `kind`, `metadata.name` and `metadata.generateName` are always accessible from the root of the
|
||||
// object and from any x-kubernetes-embedded-resource annotated objects. No other metadata properties are accessible.
|
||||
//
|
||||
// Unknown data preserved in custom resources via x-kubernetes-preserve-unknown-fields is not accessible in CEL
|
||||
// expressions. This includes:
|
||||
// - Unknown field values that are preserved by object schemas with x-kubernetes-preserve-unknown-fields.
|
||||
// - Object properties where the property schema is of an "unknown type". An "unknown type" is recursively defined as:
|
||||
// - A schema with no type and x-kubernetes-preserve-unknown-fields set to true
|
||||
// - An array where the items schema is of an "unknown type"
|
||||
// - An object where the additionalProperties schema is of an "unknown type"
|
||||
//
|
||||
// Only 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:
|
||||
// "true", "false", "null", "in", "as", "break", "const", "continue", "else", "for", "function", "if",
|
||||
// "import", "let", "loop", "package", "namespace", "return".
|
||||
// Examples:
|
||||
// - Rule accessing a property named "namespace": {"rule": "self.__namespace__ > 0"}
|
||||
// - Rule accessing a property named "x-prop": {"rule": "self.x__dash__prop > 0"}
|
||||
// - Rule accessing a property named "redact__d": {"rule": "self.redact__underscores__d > 0"}
|
||||
//
|
||||
// Equality on arrays with x-kubernetes-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:
|
||||
// - 'set': `X + Y` performs a union where the array positions of all elements in `X` are preserved and
|
||||
// non-intersecting elements in `Y` are appended, retaining their partial order.
|
||||
// - 'map': `X + Y` performs a merge where the array positions of all keys in `X` are preserved but the values
|
||||
// are overwritten by values in `Y` when the key sets of `X` and `Y` intersect. Elements in `Y` with
|
||||
// non-intersecting keys are appended, retaining their partial order.
|
||||
//
|
||||
// If `rule` makes use of the `oldSelf` variable it is implicitly a
|
||||
// `transition rule`.
|
||||
//
|
||||
// By default, the `oldSelf` variable is the same type as `self`.
|
||||
// When `optionalOldSelf` is true, the `oldSelf` variable is a CEL optional
|
||||
// variable whose value() is the same type as `self`.
|
||||
// See the documentation for the `optionalOldSelf` field for details.
|
||||
//
|
||||
// Transition rules by default are applied only on UPDATE requests and are
|
||||
// skipped if an old value could not be found. You can opt a transition
|
||||
// rule into unconditional evaluation by setting `optionalOldSelf` to true.
|
||||
optional string rule = 1;
|
||||
|
||||
// Message represents the message displayed when validation fails. The message is required if the Rule contains
|
||||
// line breaks. The message must not contain line breaks.
|
||||
// If unset, the message is "failed rule: {Rule}".
|
||||
// e.g. "must be a URL with the host matching spec.host"
|
||||
optional string message = 2;
|
||||
|
||||
// MessageExpression declares a CEL expression that evaluates to the validation failure message that is returned when this rule fails.
|
||||
// Since messageExpression is used as a failure message, it must evaluate to a string.
|
||||
// If both message and messageExpression are present on a rule, then messageExpression will be used if validation
|
||||
// fails. If messageExpression results in a runtime error, the runtime error is logged, and the validation failure message is produced
|
||||
// as if the messageExpression field were unset. If messageExpression evaluates to an empty string, a string with only spaces, or a string
|
||||
// that contains line breaks, then the validation failure message will also be produced as if the messageExpression field were unset, and
|
||||
// the fact that messageExpression produced an empty string/string with only spaces/string with line breaks will be logged.
|
||||
// messageExpression has access to all the same variables as the rule; the only difference is the return type.
|
||||
// Example:
|
||||
// "x must be less than max ("+string(self.max)+")"
|
||||
// +optional
|
||||
optional string messageExpression = 3;
|
||||
|
||||
// reason provides a machine-readable validation failure reason that is returned to the caller when a request fails this validation rule.
|
||||
// The HTTP status code returned to the caller will match the reason of the reason of the first failed validation rule.
|
||||
// The currently supported reasons are: "FieldValueInvalid", "FieldValueForbidden", "FieldValueRequired", "FieldValueDuplicate".
|
||||
// If not set, default to use "FieldValueInvalid".
|
||||
// All future added reasons must be accepted by clients when reading this value and unknown reasons should be treated as FieldValueInvalid.
|
||||
// +optional
|
||||
optional string reason = 4;
|
||||
|
||||
// fieldPath represents the field path returned when the validation fails.
|
||||
// It must be a relative JSON path (i.e. with array notation) scoped to the location of this x-kubernetes-validations extension in the schema and refer to an existing field.
|
||||
// e.g. when validation checks if a specific attribute `foo` under a map `testMap`, the fieldPath could be set to `.testMap.foo`
|
||||
// If the validation checks two lists must have unique attributes, the fieldPath could be set to either of the list: e.g. `.testList`
|
||||
// It does not support list numeric index.
|
||||
// It supports child operation to refer to an existing field currently. Refer to [JSONPath support in Kubernetes](https://kubernetes.io/docs/reference/kubectl/jsonpath/) for more info.
|
||||
// Numeric index of array is not supported.
|
||||
// For field name which contains special characters, use `['specialName']` to refer the field name.
|
||||
// e.g. for attribute `foo.34$` appears in a list `testList`, the fieldPath could be set to `.testList['foo.34$']`
|
||||
// +optional
|
||||
optional string fieldPath = 5;
|
||||
|
||||
// optionalOldSelf is used to opt a transition rule into evaluation
|
||||
// even when the object is first created, or if the old object is
|
||||
// missing the value.
|
||||
//
|
||||
// When enabled `oldSelf` will be a CEL optional whose value will be
|
||||
// `None` if there is no old value, or when the object is initially created.
|
||||
//
|
||||
// You may check for presence of oldSelf using `oldSelf.hasValue()` and
|
||||
// unwrap it after checking using `oldSelf.value()`. Check the CEL
|
||||
// documentation for Optional types for more information:
|
||||
// https://pkg.go.dev/github.com/google/cel-go/cel#OptionalTypes
|
||||
//
|
||||
// May not be set unless `oldSelf` is used in `rule`.
|
||||
//
|
||||
// +featureGate=CRDValidationRatcheting
|
||||
// +optional
|
||||
optional bool optionalOldSelf = 6;
|
||||
}
|
||||
|
||||
// WebhookClientConfig contains the information to make a TLS connection with the webhook.
|
||||
message WebhookClientConfig {
|
||||
// url gives the location of the webhook, in standard URL form
|
||||
// (`scheme://host:port/path`). Exactly one of `url` or `service`
|
||||
// must be specified.
|
||||
//
|
||||
// The `host` should not refer to a service running in the cluster; use
|
||||
// the `service` field instead. The host might be resolved via external
|
||||
// DNS in some apiservers (e.g., `kube-apiserver` cannot resolve
|
||||
// in-cluster DNS as that would be a layering violation). `host` may
|
||||
// also be an IP address.
|
||||
//
|
||||
// Please note that using `localhost` or `127.0.0.1` as a `host` is
|
||||
// risky unless you take great care to run this webhook on all hosts
|
||||
// which run an apiserver which might need to make calls to this
|
||||
// webhook. Such installs are likely to be non-portable, i.e., not easy
|
||||
// to turn up in a new cluster.
|
||||
//
|
||||
// The scheme must be "https"; the URL must begin with "https://".
|
||||
//
|
||||
// A path is optional, and if present may be any string permissible in
|
||||
// a URL. You may use the path to pass an arbitrary string to the
|
||||
// webhook, for example, a cluster identifier.
|
||||
//
|
||||
// Attempting to use a user or basic auth e.g. "user:password@" is not
|
||||
// allowed. Fragments ("#...") and query parameters ("?...") are not
|
||||
// allowed, either.
|
||||
//
|
||||
// +optional
|
||||
optional string url = 3;
|
||||
|
||||
// service is a reference to the service for this webhook. Either
|
||||
// service or url must be specified.
|
||||
//
|
||||
// If the webhook is running within the cluster, then you should use `service`.
|
||||
//
|
||||
// +optional
|
||||
optional ServiceReference service = 1;
|
||||
|
||||
// caBundle is a PEM encoded CA bundle which will be used to validate the webhook's server certificate.
|
||||
// If unspecified, system trust roots on the apiserver are used.
|
||||
// +optional
|
||||
optional bytes caBundle = 2;
|
||||
}
|
||||
|
||||
// WebhookConversion describes how to call a conversion webhook
|
||||
message WebhookConversion {
|
||||
// clientConfig is the instructions for how to call the webhook if strategy is `Webhook`.
|
||||
// +optional
|
||||
optional WebhookClientConfig clientConfig = 2;
|
||||
|
||||
// conversionReviewVersions is an ordered list of preferred `ConversionReview`
|
||||
// versions the Webhook expects. The API server will use the first version in
|
||||
// the list which it supports. If none of the versions specified in this list
|
||||
// are supported by API server, conversion will fail for the custom resource.
|
||||
// If a persisted Webhook configuration specifies allowed versions and does not
|
||||
// include any versions known to the API Server, calls to the webhook will fail.
|
||||
// +listType=atomic
|
||||
repeated string conversionReviewVersions = 3;
|
||||
}
|
||||
|
295
vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1/marshal.go
generated
vendored
295
vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1/marshal.go
generated
vendored
@ -1,295 +0,0 @@
|
||||
/*
|
||||
Copyright 2019 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 v1
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
|
||||
cbor "k8s.io/apimachinery/pkg/runtime/serializer/cbor/direct"
|
||||
"k8s.io/apimachinery/pkg/util/json"
|
||||
)
|
||||
|
||||
var jsTrue = []byte("true")
|
||||
var jsFalse = []byte("false")
|
||||
|
||||
// The CBOR parsing related constants and functions below are not exported so they can be
|
||||
// easily removed at a future date when the CBOR library provides equivalent functionality.
|
||||
|
||||
type cborMajorType int
|
||||
|
||||
const (
|
||||
// https://www.rfc-editor.org/rfc/rfc8949.html#section-3.1
|
||||
cborUnsignedInteger cborMajorType = 0
|
||||
cborNegativeInteger cborMajorType = 1
|
||||
cborByteString cborMajorType = 2
|
||||
cborTextString cborMajorType = 3
|
||||
cborArray cborMajorType = 4
|
||||
cborMap cborMajorType = 5
|
||||
cborTag cborMajorType = 6
|
||||
cborOther cborMajorType = 7
|
||||
)
|
||||
|
||||
const (
|
||||
// from https://www.rfc-editor.org/rfc/rfc8949.html#name-jump-table-for-initial-byte.
|
||||
// additionally, see https://www.rfc-editor.org/rfc/rfc8949.html#section-3.3-5.
|
||||
cborFalseValue = 0xf4
|
||||
cborTrueValue = 0xf5
|
||||
cborNullValue = 0xf6
|
||||
)
|
||||
|
||||
func cborType(b byte) cborMajorType {
|
||||
return cborMajorType(b >> 5)
|
||||
}
|
||||
|
||||
func (s JSONSchemaPropsOrBool) MarshalJSON() ([]byte, error) {
|
||||
if s.Schema != nil {
|
||||
return json.Marshal(s.Schema)
|
||||
}
|
||||
|
||||
if s.Schema == nil && !s.Allows {
|
||||
return jsFalse, nil
|
||||
}
|
||||
return jsTrue, nil
|
||||
}
|
||||
|
||||
func (s *JSONSchemaPropsOrBool) UnmarshalJSON(data []byte) error {
|
||||
var nw JSONSchemaPropsOrBool
|
||||
switch {
|
||||
case len(data) == 0:
|
||||
case data[0] == '{':
|
||||
var sch JSONSchemaProps
|
||||
if err := json.Unmarshal(data, &sch); err != nil {
|
||||
return err
|
||||
}
|
||||
nw.Allows = true
|
||||
nw.Schema = &sch
|
||||
case len(data) == 4 && string(data) == "true":
|
||||
nw.Allows = true
|
||||
case len(data) == 5 && string(data) == "false":
|
||||
nw.Allows = false
|
||||
default:
|
||||
return errors.New("boolean or JSON schema expected")
|
||||
}
|
||||
*s = nw
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s JSONSchemaPropsOrBool) MarshalCBOR() ([]byte, error) {
|
||||
if s.Schema != nil {
|
||||
return cbor.Marshal(s.Schema)
|
||||
}
|
||||
return cbor.Marshal(s.Allows)
|
||||
}
|
||||
|
||||
func (s *JSONSchemaPropsOrBool) UnmarshalCBOR(data []byte) error {
|
||||
switch {
|
||||
case len(data) == 0:
|
||||
// ideally we would avoid modifying *s here, but we are matching the behavior of UnmarshalJSON
|
||||
*s = JSONSchemaPropsOrBool{}
|
||||
return nil
|
||||
case cborType(data[0]) == cborMap:
|
||||
var p JSONSchemaProps
|
||||
if err := cbor.Unmarshal(data, &p); err != nil {
|
||||
return err
|
||||
}
|
||||
*s = JSONSchemaPropsOrBool{Allows: true, Schema: &p}
|
||||
return nil
|
||||
case data[0] == cborTrueValue:
|
||||
*s = JSONSchemaPropsOrBool{Allows: true}
|
||||
return nil
|
||||
case data[0] == cborFalseValue:
|
||||
*s = JSONSchemaPropsOrBool{Allows: false}
|
||||
return nil
|
||||
default:
|
||||
// ideally, this case would not also capture a null input value,
|
||||
// but we are matching the behavior of the UnmarshalJSON
|
||||
return errors.New("boolean or JSON schema expected")
|
||||
}
|
||||
}
|
||||
|
||||
func (s JSONSchemaPropsOrStringArray) MarshalJSON() ([]byte, error) {
|
||||
if len(s.Property) > 0 {
|
||||
return json.Marshal(s.Property)
|
||||
}
|
||||
if s.Schema != nil {
|
||||
return json.Marshal(s.Schema)
|
||||
}
|
||||
return []byte("null"), nil
|
||||
}
|
||||
|
||||
func (s *JSONSchemaPropsOrStringArray) UnmarshalJSON(data []byte) error {
|
||||
var first byte
|
||||
if len(data) > 1 {
|
||||
first = data[0]
|
||||
}
|
||||
var nw JSONSchemaPropsOrStringArray
|
||||
if first == '{' {
|
||||
var sch JSONSchemaProps
|
||||
if err := json.Unmarshal(data, &sch); err != nil {
|
||||
return err
|
||||
}
|
||||
nw.Schema = &sch
|
||||
}
|
||||
if first == '[' {
|
||||
if err := json.Unmarshal(data, &nw.Property); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*s = nw
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s JSONSchemaPropsOrStringArray) MarshalCBOR() ([]byte, error) {
|
||||
if len(s.Property) > 0 {
|
||||
return cbor.Marshal(s.Property)
|
||||
}
|
||||
if s.Schema != nil {
|
||||
return cbor.Marshal(s.Schema)
|
||||
}
|
||||
return cbor.Marshal(nil)
|
||||
}
|
||||
|
||||
func (s *JSONSchemaPropsOrStringArray) UnmarshalCBOR(data []byte) error {
|
||||
if len(data) > 0 && cborType(data[0]) == cborArray {
|
||||
var a []string
|
||||
if err := cbor.Unmarshal(data, &a); err != nil {
|
||||
return err
|
||||
}
|
||||
*s = JSONSchemaPropsOrStringArray{Property: a}
|
||||
return nil
|
||||
}
|
||||
if len(data) > 0 && cborType(data[0]) == cborMap {
|
||||
var p JSONSchemaProps
|
||||
if err := cbor.Unmarshal(data, &p); err != nil {
|
||||
return err
|
||||
}
|
||||
*s = JSONSchemaPropsOrStringArray{Schema: &p}
|
||||
return nil
|
||||
}
|
||||
// At this point we either have: empty data, a null value, or an
|
||||
// unexpected type. In order to match the behavior of the existing
|
||||
// UnmarshalJSON, no error is returned and *s is overwritten here.
|
||||
*s = JSONSchemaPropsOrStringArray{}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s JSONSchemaPropsOrArray) MarshalJSON() ([]byte, error) {
|
||||
if len(s.JSONSchemas) > 0 {
|
||||
return json.Marshal(s.JSONSchemas)
|
||||
}
|
||||
return json.Marshal(s.Schema)
|
||||
}
|
||||
|
||||
func (s *JSONSchemaPropsOrArray) UnmarshalJSON(data []byte) error {
|
||||
var nw JSONSchemaPropsOrArray
|
||||
var first byte
|
||||
if len(data) > 1 {
|
||||
first = data[0]
|
||||
}
|
||||
if first == '{' {
|
||||
var sch JSONSchemaProps
|
||||
if err := json.Unmarshal(data, &sch); err != nil {
|
||||
return err
|
||||
}
|
||||
nw.Schema = &sch
|
||||
}
|
||||
if first == '[' {
|
||||
if err := json.Unmarshal(data, &nw.JSONSchemas); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
*s = nw
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s JSONSchemaPropsOrArray) MarshalCBOR() ([]byte, error) {
|
||||
if len(s.JSONSchemas) > 0 {
|
||||
return cbor.Marshal(s.JSONSchemas)
|
||||
}
|
||||
return cbor.Marshal(s.Schema)
|
||||
}
|
||||
|
||||
func (s *JSONSchemaPropsOrArray) UnmarshalCBOR(data []byte) error {
|
||||
if len(data) > 0 && cborType(data[0]) == cborMap {
|
||||
var p JSONSchemaProps
|
||||
if err := cbor.Unmarshal(data, &p); err != nil {
|
||||
return err
|
||||
}
|
||||
*s = JSONSchemaPropsOrArray{Schema: &p}
|
||||
return nil
|
||||
}
|
||||
if len(data) > 0 && cborType(data[0]) == cborArray {
|
||||
var a []JSONSchemaProps
|
||||
if err := cbor.Unmarshal(data, &a); err != nil {
|
||||
return err
|
||||
}
|
||||
*s = JSONSchemaPropsOrArray{JSONSchemas: a}
|
||||
return nil
|
||||
}
|
||||
// At this point we either have: empty data, a null value, or an
|
||||
// unexpected type. In order to match the behavior of the existing
|
||||
// UnmarshalJSON, no error is returned and *s is overwritten here.
|
||||
*s = JSONSchemaPropsOrArray{}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s JSON) MarshalJSON() ([]byte, error) {
|
||||
if len(s.Raw) > 0 {
|
||||
return s.Raw, nil
|
||||
}
|
||||
return []byte("null"), nil
|
||||
|
||||
}
|
||||
|
||||
func (s *JSON) UnmarshalJSON(data []byte) error {
|
||||
if len(data) > 0 && !bytes.Equal(data, nullLiteral) {
|
||||
s.Raw = append(s.Raw[0:0], data...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s JSON) MarshalCBOR() ([]byte, error) {
|
||||
// Note that non-semantic whitespace is lost during the transcoding performed here.
|
||||
// We do not forsee this to be a problem given the current known uses of this type.
|
||||
// Other limitations that arise when roundtripping JSON via dynamic clients also apply
|
||||
// here, for example: insignificant whitespace handling, number handling, and map key ordering.
|
||||
if len(s.Raw) == 0 {
|
||||
return []byte{cborNullValue}, nil
|
||||
}
|
||||
var u any
|
||||
if err := json.Unmarshal(s.Raw, &u); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cbor.Marshal(u)
|
||||
}
|
||||
|
||||
func (s *JSON) UnmarshalCBOR(data []byte) error {
|
||||
if len(data) == 0 || data[0] == cborNullValue {
|
||||
return nil
|
||||
}
|
||||
var u any
|
||||
if err := cbor.Unmarshal(data, &u); err != nil {
|
||||
return err
|
||||
}
|
||||
raw, err := json.Marshal(u)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.Raw = raw
|
||||
return nil
|
||||
}
|
62
vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1/register.go
generated
vendored
62
vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1/register.go
generated
vendored
@ -1,62 +0,0 @@
|
||||
/*
|
||||
Copyright 2019 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 v1
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
const GroupName = "apiextensions.k8s.io"
|
||||
|
||||
// SchemeGroupVersion is group version used to register these objects
|
||||
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1"}
|
||||
|
||||
// Kind takes an unqualified kind and returns back a Group qualified GroupKind
|
||||
func Kind(kind string) schema.GroupKind {
|
||||
return SchemeGroupVersion.WithKind(kind).GroupKind()
|
||||
}
|
||||
|
||||
// Resource takes an unqualified resource and returns back a Group qualified GroupResource
|
||||
func Resource(resource string) schema.GroupResource {
|
||||
return SchemeGroupVersion.WithResource(resource).GroupResource()
|
||||
}
|
||||
|
||||
var (
|
||||
SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes, addDefaultingFuncs)
|
||||
localSchemeBuilder = &SchemeBuilder
|
||||
AddToScheme = localSchemeBuilder.AddToScheme
|
||||
)
|
||||
|
||||
// Adds the list of known types to the given scheme.
|
||||
func addKnownTypes(scheme *runtime.Scheme) error {
|
||||
scheme.AddKnownTypes(SchemeGroupVersion,
|
||||
&CustomResourceDefinition{},
|
||||
&CustomResourceDefinitionList{},
|
||||
&ConversionReview{},
|
||||
)
|
||||
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
// We only register manually written functions here. The registration of the
|
||||
// generated functions takes place in the generated files. The separation
|
||||
// makes the code compile even when the generated files are missing.
|
||||
localSchemeBuilder.Register(addDefaultingFuncs)
|
||||
}
|
518
vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1/types.go
generated
vendored
518
vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1/types.go
generated
vendored
@ -1,518 +0,0 @@
|
||||
/*
|
||||
Copyright 2019 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 v1
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
)
|
||||
|
||||
// ConversionStrategyType describes different conversion types.
|
||||
type ConversionStrategyType string
|
||||
|
||||
const (
|
||||
// KubeAPIApprovedAnnotation is an annotation that must be set to create a CRD for the k8s.io, *.k8s.io, kubernetes.io, or *.kubernetes.io namespaces.
|
||||
// The value should be a link to a URL where the current spec was approved, so updates to the spec should also update the URL.
|
||||
// If the API is unapproved, you may set the annotation to a string starting with `"unapproved"`. For instance, `"unapproved, temporarily squatting"` or `"unapproved, experimental-only"`. This is discouraged.
|
||||
KubeAPIApprovedAnnotation = "api-approved.kubernetes.io"
|
||||
|
||||
// NoneConverter is a converter that only sets apiversion of the CR and leave everything else unchanged.
|
||||
NoneConverter ConversionStrategyType = "None"
|
||||
// WebhookConverter is a converter that calls to an external webhook to convert the CR.
|
||||
WebhookConverter ConversionStrategyType = "Webhook"
|
||||
)
|
||||
|
||||
// CustomResourceDefinitionSpec describes how a user wants their resource to appear
|
||||
type CustomResourceDefinitionSpec struct {
|
||||
// group is the API group of the defined custom resource.
|
||||
// The custom resources are served under `/apis/<group>/...`.
|
||||
// Must match the name of the CustomResourceDefinition (in the form `<names.plural>.<group>`).
|
||||
Group string `json:"group" protobuf:"bytes,1,opt,name=group"`
|
||||
// names specify the resource and kind names for the custom resource.
|
||||
Names CustomResourceDefinitionNames `json:"names" protobuf:"bytes,3,opt,name=names"`
|
||||
// scope indicates whether the defined custom resource is cluster- or namespace-scoped.
|
||||
// Allowed values are `Cluster` and `Namespaced`.
|
||||
Scope ResourceScope `json:"scope" protobuf:"bytes,4,opt,name=scope,casttype=ResourceScope"`
|
||||
// versions is the list of all API versions of the defined custom resource.
|
||||
// Version names are used to compute the order in which served versions are listed in API discovery.
|
||||
// If the version string is "kube-like", it will sort above non "kube-like" version strings, which are ordered
|
||||
// lexicographically. "Kube-like" versions start with a "v", then are followed by a number (the major version),
|
||||
// then optionally the string "alpha" or "beta" and another number (the minor version). These are sorted first
|
||||
// by GA > beta > alpha (where GA is a version with no suffix such as beta or alpha), and then by comparing
|
||||
// major version, then minor version. An example sorted list of versions:
|
||||
// v10, v2, v1, v11beta2, v10beta3, v3beta1, v12alpha1, v11alpha2, foo1, foo10.
|
||||
// +listType=atomic
|
||||
Versions []CustomResourceDefinitionVersion `json:"versions" protobuf:"bytes,7,rep,name=versions"`
|
||||
|
||||
// conversion defines conversion settings for the CRD.
|
||||
// +optional
|
||||
Conversion *CustomResourceConversion `json:"conversion,omitempty" protobuf:"bytes,9,opt,name=conversion"`
|
||||
|
||||
// preserveUnknownFields indicates that object fields which are not specified
|
||||
// in the OpenAPI schema should be preserved when persisting to storage.
|
||||
// apiVersion, kind, metadata and known fields inside metadata are always preserved.
|
||||
// This field is deprecated in favor of setting `x-preserve-unknown-fields` to true in `spec.versions[*].schema.openAPIV3Schema`.
|
||||
// See https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#field-pruning for details.
|
||||
// +optional
|
||||
PreserveUnknownFields bool `json:"preserveUnknownFields,omitempty" protobuf:"varint,10,opt,name=preserveUnknownFields"`
|
||||
}
|
||||
|
||||
// CustomResourceConversion describes how to convert different versions of a CR.
|
||||
type CustomResourceConversion struct {
|
||||
// strategy specifies how custom resources are converted between versions. Allowed values are:
|
||||
// - `"None"`: The converter only change the apiVersion and would not touch any other field in the custom resource.
|
||||
// - `"Webhook"`: API Server will call to an external webhook to do the conversion. Additional information
|
||||
// is needed for this option. This requires spec.preserveUnknownFields to be false, and spec.conversion.webhook to be set.
|
||||
Strategy ConversionStrategyType `json:"strategy" protobuf:"bytes,1,name=strategy"`
|
||||
|
||||
// webhook describes how to call the conversion webhook. Required when `strategy` is set to `"Webhook"`.
|
||||
// +optional
|
||||
Webhook *WebhookConversion `json:"webhook,omitempty" protobuf:"bytes,2,opt,name=webhook"`
|
||||
}
|
||||
|
||||
// WebhookConversion describes how to call a conversion webhook
|
||||
type WebhookConversion struct {
|
||||
// clientConfig is the instructions for how to call the webhook if strategy is `Webhook`.
|
||||
// +optional
|
||||
ClientConfig *WebhookClientConfig `json:"clientConfig,omitempty" protobuf:"bytes,2,name=clientConfig"`
|
||||
|
||||
// conversionReviewVersions is an ordered list of preferred `ConversionReview`
|
||||
// versions the Webhook expects. The API server will use the first version in
|
||||
// the list which it supports. If none of the versions specified in this list
|
||||
// are supported by API server, conversion will fail for the custom resource.
|
||||
// If a persisted Webhook configuration specifies allowed versions and does not
|
||||
// include any versions known to the API Server, calls to the webhook will fail.
|
||||
// +listType=atomic
|
||||
ConversionReviewVersions []string `json:"conversionReviewVersions" protobuf:"bytes,3,rep,name=conversionReviewVersions"`
|
||||
}
|
||||
|
||||
// WebhookClientConfig contains the information to make a TLS connection with the webhook.
|
||||
type WebhookClientConfig struct {
|
||||
// url gives the location of the webhook, in standard URL form
|
||||
// (`scheme://host:port/path`). Exactly one of `url` or `service`
|
||||
// must be specified.
|
||||
//
|
||||
// The `host` should not refer to a service running in the cluster; use
|
||||
// the `service` field instead. The host might be resolved via external
|
||||
// DNS in some apiservers (e.g., `kube-apiserver` cannot resolve
|
||||
// in-cluster DNS as that would be a layering violation). `host` may
|
||||
// also be an IP address.
|
||||
//
|
||||
// Please note that using `localhost` or `127.0.0.1` as a `host` is
|
||||
// risky unless you take great care to run this webhook on all hosts
|
||||
// which run an apiserver which might need to make calls to this
|
||||
// webhook. Such installs are likely to be non-portable, i.e., not easy
|
||||
// to turn up in a new cluster.
|
||||
//
|
||||
// The scheme must be "https"; the URL must begin with "https://".
|
||||
//
|
||||
// A path is optional, and if present may be any string permissible in
|
||||
// a URL. You may use the path to pass an arbitrary string to the
|
||||
// webhook, for example, a cluster identifier.
|
||||
//
|
||||
// Attempting to use a user or basic auth e.g. "user:password@" is not
|
||||
// allowed. Fragments ("#...") and query parameters ("?...") are not
|
||||
// allowed, either.
|
||||
//
|
||||
// +optional
|
||||
URL *string `json:"url,omitempty" protobuf:"bytes,3,opt,name=url"`
|
||||
|
||||
// service is a reference to the service for this webhook. Either
|
||||
// service or url must be specified.
|
||||
//
|
||||
// If the webhook is running within the cluster, then you should use `service`.
|
||||
//
|
||||
// +optional
|
||||
Service *ServiceReference `json:"service,omitempty" protobuf:"bytes,1,opt,name=service"`
|
||||
|
||||
// caBundle is a PEM encoded CA bundle which will be used to validate the webhook's server certificate.
|
||||
// If unspecified, system trust roots on the apiserver are used.
|
||||
// +optional
|
||||
CABundle []byte `json:"caBundle,omitempty" protobuf:"bytes,2,opt,name=caBundle"`
|
||||
}
|
||||
|
||||
// ServiceReference holds a reference to Service.legacy.k8s.io
|
||||
type ServiceReference struct {
|
||||
// namespace is the namespace of the service.
|
||||
// Required
|
||||
Namespace string `json:"namespace" protobuf:"bytes,1,opt,name=namespace"`
|
||||
// name is the name of the service.
|
||||
// Required
|
||||
Name string `json:"name" protobuf:"bytes,2,opt,name=name"`
|
||||
|
||||
// path is an optional URL path at which the webhook will be contacted.
|
||||
// +optional
|
||||
Path *string `json:"path,omitempty" protobuf:"bytes,3,opt,name=path"`
|
||||
|
||||
// port is an optional service port at which the webhook will be contacted.
|
||||
// `port` should be a valid port number (1-65535, inclusive).
|
||||
// Defaults to 443 for backward compatibility.
|
||||
// +optional
|
||||
Port *int32 `json:"port,omitempty" protobuf:"varint,4,opt,name=port"`
|
||||
}
|
||||
|
||||
// CustomResourceDefinitionVersion describes a version for CRD.
|
||||
type CustomResourceDefinitionVersion struct {
|
||||
// name is the version name, e.g. “v1”, “v2beta1”, etc.
|
||||
// The custom resources are served under this version at `/apis/<group>/<version>/...` if `served` is true.
|
||||
Name string `json:"name" protobuf:"bytes,1,opt,name=name"`
|
||||
// served is a flag enabling/disabling this version from being served via REST APIs
|
||||
Served bool `json:"served" protobuf:"varint,2,opt,name=served"`
|
||||
// storage indicates this version should be used when persisting custom resources to storage.
|
||||
// There must be exactly one version with storage=true.
|
||||
Storage bool `json:"storage" protobuf:"varint,3,opt,name=storage"`
|
||||
// deprecated indicates this version of the custom resource API is deprecated.
|
||||
// When set to true, API requests to this version receive a warning header in the server response.
|
||||
// Defaults to false.
|
||||
// +optional
|
||||
Deprecated bool `json:"deprecated,omitempty" protobuf:"varint,7,opt,name=deprecated"`
|
||||
// deprecationWarning overrides the default warning returned to API clients.
|
||||
// May only be set when `deprecated` is true.
|
||||
// The default warning indicates this version is deprecated and recommends use
|
||||
// of the newest served version of equal or greater stability, if one exists.
|
||||
// +optional
|
||||
DeprecationWarning *string `json:"deprecationWarning,omitempty" protobuf:"bytes,8,opt,name=deprecationWarning"`
|
||||
// schema describes the schema used for validation, pruning, and defaulting of this version of the custom resource.
|
||||
// +optional
|
||||
Schema *CustomResourceValidation `json:"schema,omitempty" protobuf:"bytes,4,opt,name=schema"`
|
||||
// subresources specify what subresources this version of the defined custom resource have.
|
||||
// +optional
|
||||
Subresources *CustomResourceSubresources `json:"subresources,omitempty" protobuf:"bytes,5,opt,name=subresources"`
|
||||
// additionalPrinterColumns specifies additional columns returned in Table output.
|
||||
// See https://kubernetes.io/docs/reference/using-api/api-concepts/#receiving-resources-as-tables for details.
|
||||
// If no columns are specified, a single column displaying the age of the custom resource is used.
|
||||
// +optional
|
||||
// +listType=atomic
|
||||
AdditionalPrinterColumns []CustomResourceColumnDefinition `json:"additionalPrinterColumns,omitempty" protobuf:"bytes,6,rep,name=additionalPrinterColumns"`
|
||||
|
||||
// selectableFields specifies paths to fields that may be used as field selectors.
|
||||
// A maximum of 8 selectable fields are allowed.
|
||||
// See https://kubernetes.io/docs/concepts/overview/working-with-objects/field-selectors
|
||||
//
|
||||
// +featureGate=CustomResourceFieldSelectors
|
||||
// +optional
|
||||
// +listType=atomic
|
||||
SelectableFields []SelectableField `json:"selectableFields,omitempty" protobuf:"bytes,9,rep,name=selectableFields"`
|
||||
}
|
||||
|
||||
// SelectableField specifies the JSON path of a field that may be used with field selectors.
|
||||
type SelectableField struct {
|
||||
// jsonPath is a simple JSON path which is evaluated against each custom resource to produce a
|
||||
// field selector value.
|
||||
// Only JSON paths without the array notation are allowed.
|
||||
// Must point to a field of type string, boolean or integer. Types with enum values
|
||||
// and strings with formats are allowed.
|
||||
// If jsonPath refers to absent field in a resource, the jsonPath evaluates to an empty string.
|
||||
// Must not point to metdata fields.
|
||||
// Required.
|
||||
JSONPath string `json:"jsonPath" protobuf:"bytes,1,opt,name=jsonPath"`
|
||||
}
|
||||
|
||||
// CustomResourceColumnDefinition specifies a column for server side printing.
|
||||
type CustomResourceColumnDefinition struct {
|
||||
// name is a human readable name for the column.
|
||||
Name string `json:"name" protobuf:"bytes,1,opt,name=name"`
|
||||
// type is an OpenAPI type definition for this column.
|
||||
// See https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#data-types for details.
|
||||
Type string `json:"type" protobuf:"bytes,2,opt,name=type"`
|
||||
// format is an optional OpenAPI type definition for this column. The 'name' format is applied
|
||||
// to the primary identifier column to assist in clients identifying column is the resource name.
|
||||
// See https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#data-types for details.
|
||||
// +optional
|
||||
Format string `json:"format,omitempty" protobuf:"bytes,3,opt,name=format"`
|
||||
// description is a human readable description of this column.
|
||||
// +optional
|
||||
Description string `json:"description,omitempty" protobuf:"bytes,4,opt,name=description"`
|
||||
// priority is an integer defining the relative importance of this column compared to others. Lower
|
||||
// numbers are considered higher priority. Columns that may be omitted in limited space scenarios
|
||||
// should be given a priority greater than 0.
|
||||
// +optional
|
||||
Priority int32 `json:"priority,omitempty" protobuf:"bytes,5,opt,name=priority"`
|
||||
// jsonPath is a simple JSON path (i.e. with array notation) which is evaluated against
|
||||
// each custom resource to produce the value for this column.
|
||||
JSONPath string `json:"jsonPath" protobuf:"bytes,6,opt,name=jsonPath"`
|
||||
}
|
||||
|
||||
// CustomResourceDefinitionNames indicates the names to serve this CustomResourceDefinition
|
||||
type CustomResourceDefinitionNames struct {
|
||||
// plural is the plural name of the resource to serve.
|
||||
// The custom resources are served under `/apis/<group>/<version>/.../<plural>`.
|
||||
// Must match the name of the CustomResourceDefinition (in the form `<names.plural>.<group>`).
|
||||
// Must be all lowercase.
|
||||
Plural string `json:"plural" protobuf:"bytes,1,opt,name=plural"`
|
||||
// singular is the singular name of the resource. It must be all lowercase. Defaults to lowercased `kind`.
|
||||
// +optional
|
||||
Singular string `json:"singular,omitempty" protobuf:"bytes,2,opt,name=singular"`
|
||||
// shortNames are short names for the resource, exposed in API discovery documents,
|
||||
// and used by clients to support invocations like `kubectl get <shortname>`.
|
||||
// It must be all lowercase.
|
||||
// +optional
|
||||
// +listType=atomic
|
||||
ShortNames []string `json:"shortNames,omitempty" protobuf:"bytes,3,opt,name=shortNames"`
|
||||
// kind is the serialized kind of the resource. It is normally CamelCase and singular.
|
||||
// Custom resource instances will use this value as the `kind` attribute in API calls.
|
||||
Kind string `json:"kind" protobuf:"bytes,4,opt,name=kind"`
|
||||
// listKind is the serialized kind of the list for this resource. Defaults to "`kind`List".
|
||||
// +optional
|
||||
ListKind string `json:"listKind,omitempty" protobuf:"bytes,5,opt,name=listKind"`
|
||||
// categories is a list of grouped resources this custom resource belongs to (e.g. 'all').
|
||||
// This is published in API discovery documents, and used by clients to support invocations like
|
||||
// `kubectl get all`.
|
||||
// +optional
|
||||
// +listType=atomic
|
||||
Categories []string `json:"categories,omitempty" protobuf:"bytes,6,rep,name=categories"`
|
||||
}
|
||||
|
||||
// ResourceScope is an enum defining the different scopes available to a custom resource
|
||||
type ResourceScope string
|
||||
|
||||
const (
|
||||
ClusterScoped ResourceScope = "Cluster"
|
||||
NamespaceScoped ResourceScope = "Namespaced"
|
||||
)
|
||||
|
||||
type ConditionStatus string
|
||||
|
||||
// These are valid condition statuses. "ConditionTrue" means a resource is in the condition.
|
||||
// "ConditionFalse" means a resource is not in the condition. "ConditionUnknown" means kubernetes
|
||||
// can't decide if a resource is in the condition or not. In the future, we could add other
|
||||
// intermediate conditions, e.g. ConditionDegraded.
|
||||
const (
|
||||
ConditionTrue ConditionStatus = "True"
|
||||
ConditionFalse ConditionStatus = "False"
|
||||
ConditionUnknown ConditionStatus = "Unknown"
|
||||
)
|
||||
|
||||
// CustomResourceDefinitionConditionType is a valid value for CustomResourceDefinitionCondition.Type
|
||||
type CustomResourceDefinitionConditionType string
|
||||
|
||||
const (
|
||||
// Established means that the resource has become active. A resource is established when all names are
|
||||
// accepted without a conflict for the first time. A resource stays established until deleted, even during
|
||||
// a later NamesAccepted due to changed names. Note that not all names can be changed.
|
||||
Established CustomResourceDefinitionConditionType = "Established"
|
||||
// NamesAccepted means the names chosen for this CustomResourceDefinition do not conflict with others in
|
||||
// the group and are therefore accepted.
|
||||
NamesAccepted CustomResourceDefinitionConditionType = "NamesAccepted"
|
||||
// NonStructuralSchema means that one or more OpenAPI schema is not structural.
|
||||
//
|
||||
// A schema is structural if it specifies types for all values, with the only exceptions of those with
|
||||
// - x-kubernetes-int-or-string: true — for fields which can be integer or string
|
||||
// - x-kubernetes-preserve-unknown-fields: true — for raw, unspecified JSON values
|
||||
// and there is no type, additionalProperties, default, nullable or x-kubernetes-* vendor extenions
|
||||
// specified under allOf, anyOf, oneOf or not.
|
||||
//
|
||||
// Non-structural schemas will not be allowed anymore in v1 API groups. Moreover, new features will not be
|
||||
// available for non-structural CRDs:
|
||||
// - pruning
|
||||
// - defaulting
|
||||
// - read-only
|
||||
// - OpenAPI publishing
|
||||
// - webhook conversion
|
||||
NonStructuralSchema CustomResourceDefinitionConditionType = "NonStructuralSchema"
|
||||
// Terminating means that the CustomResourceDefinition has been deleted and is cleaning up.
|
||||
Terminating CustomResourceDefinitionConditionType = "Terminating"
|
||||
// KubernetesAPIApprovalPolicyConformant indicates that an API in *.k8s.io or *.kubernetes.io is or is not approved. For CRDs
|
||||
// outside those groups, this condition will not be set. For CRDs inside those groups, the condition will
|
||||
// be true if .metadata.annotations["api-approved.kubernetes.io"] is set to a URL, otherwise it will be false.
|
||||
// See https://github.com/kubernetes/enhancements/pull/1111 for more details.
|
||||
KubernetesAPIApprovalPolicyConformant CustomResourceDefinitionConditionType = "KubernetesAPIApprovalPolicyConformant"
|
||||
)
|
||||
|
||||
// CustomResourceDefinitionCondition contains details for the current condition of this pod.
|
||||
type CustomResourceDefinitionCondition struct {
|
||||
// type is the type of the condition. Types include Established, NamesAccepted and Terminating.
|
||||
Type CustomResourceDefinitionConditionType `json:"type" protobuf:"bytes,1,opt,name=type,casttype=CustomResourceDefinitionConditionType"`
|
||||
// status is the status of the condition.
|
||||
// Can be True, False, Unknown.
|
||||
Status ConditionStatus `json:"status" protobuf:"bytes,2,opt,name=status,casttype=ConditionStatus"`
|
||||
// lastTransitionTime last time the condition transitioned from one status to another.
|
||||
// +optional
|
||||
LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty" protobuf:"bytes,3,opt,name=lastTransitionTime"`
|
||||
// reason is a unique, one-word, CamelCase reason for the condition's last transition.
|
||||
// +optional
|
||||
Reason string `json:"reason,omitempty" protobuf:"bytes,4,opt,name=reason"`
|
||||
// message is a human-readable message indicating details about last transition.
|
||||
// +optional
|
||||
Message string `json:"message,omitempty" protobuf:"bytes,5,opt,name=message"`
|
||||
}
|
||||
|
||||
// CustomResourceDefinitionStatus indicates the state of the CustomResourceDefinition
|
||||
type CustomResourceDefinitionStatus struct {
|
||||
// conditions indicate state for particular aspects of a CustomResourceDefinition
|
||||
// +optional
|
||||
// +listType=map
|
||||
// +listMapKey=type
|
||||
Conditions []CustomResourceDefinitionCondition `json:"conditions" protobuf:"bytes,1,opt,name=conditions"`
|
||||
|
||||
// acceptedNames are the names that are actually being used to serve discovery.
|
||||
// They may be different than the names in spec.
|
||||
// +optional
|
||||
AcceptedNames CustomResourceDefinitionNames `json:"acceptedNames" protobuf:"bytes,2,opt,name=acceptedNames"`
|
||||
|
||||
// storedVersions lists all versions of CustomResources that were ever persisted. Tracking these
|
||||
// versions allows a migration path for stored versions in etcd. The field is mutable
|
||||
// so a migration controller can finish a migration to another version (ensuring
|
||||
// no old objects are left in storage), and then remove the rest of the
|
||||
// versions from this list.
|
||||
// Versions may not be removed from `spec.versions` while they exist in this list.
|
||||
// +optional
|
||||
// +listType=atomic
|
||||
StoredVersions []string `json:"storedVersions" protobuf:"bytes,3,rep,name=storedVersions"`
|
||||
}
|
||||
|
||||
// CustomResourceCleanupFinalizer is the name of the finalizer which will delete instances of
|
||||
// a CustomResourceDefinition
|
||||
const CustomResourceCleanupFinalizer = "customresourcecleanup.apiextensions.k8s.io"
|
||||
|
||||
// +genclient
|
||||
// +genclient:nonNamespaced
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
// +k8s:prerelease-lifecycle-gen:introduced=1.16
|
||||
|
||||
// CustomResourceDefinition represents a resource that should be exposed on the API server. Its name MUST be in the format
|
||||
// <.spec.name>.<.spec.group>.
|
||||
type CustomResourceDefinition struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
// Standard object's metadata
|
||||
// More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata
|
||||
// +optional
|
||||
metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
|
||||
|
||||
// spec describes how the user wants the resources to appear
|
||||
Spec CustomResourceDefinitionSpec `json:"spec" protobuf:"bytes,2,opt,name=spec"`
|
||||
// status indicates the actual state of the CustomResourceDefinition
|
||||
// +optional
|
||||
Status CustomResourceDefinitionStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"`
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
// +k8s:prerelease-lifecycle-gen:introduced=1.16
|
||||
|
||||
// CustomResourceDefinitionList is a list of CustomResourceDefinition objects.
|
||||
type CustomResourceDefinitionList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
|
||||
// Standard object's metadata
|
||||
// More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata
|
||||
// +optional
|
||||
metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
|
||||
|
||||
// items list individual CustomResourceDefinition objects
|
||||
Items []CustomResourceDefinition `json:"items" protobuf:"bytes,2,rep,name=items"`
|
||||
}
|
||||
|
||||
// CustomResourceValidation is a list of validation methods for CustomResources.
|
||||
type CustomResourceValidation struct {
|
||||
// openAPIV3Schema is the OpenAPI v3 schema to use for validation and pruning.
|
||||
// +optional
|
||||
OpenAPIV3Schema *JSONSchemaProps `json:"openAPIV3Schema,omitempty" protobuf:"bytes,1,opt,name=openAPIV3Schema"`
|
||||
}
|
||||
|
||||
// CustomResourceSubresources defines the status and scale subresources for CustomResources.
|
||||
type CustomResourceSubresources struct {
|
||||
// status indicates the custom resource should serve a `/status` subresource.
|
||||
// When enabled:
|
||||
// 1. requests to the custom resource primary endpoint ignore changes to the `status` stanza of the object.
|
||||
// 2. requests to the custom resource `/status` subresource ignore changes to anything other than the `status` stanza of the object.
|
||||
// +optional
|
||||
Status *CustomResourceSubresourceStatus `json:"status,omitempty" protobuf:"bytes,1,opt,name=status"`
|
||||
// scale indicates the custom resource should serve a `/scale` subresource that returns an `autoscaling/v1` Scale object.
|
||||
// +optional
|
||||
Scale *CustomResourceSubresourceScale `json:"scale,omitempty" protobuf:"bytes,2,opt,name=scale"`
|
||||
}
|
||||
|
||||
// CustomResourceSubresourceStatus defines how to serve the status subresource for CustomResources.
|
||||
// Status is represented by the `.status` JSON path inside of a CustomResource. When set,
|
||||
// * exposes a /status subresource for the custom resource
|
||||
// * PUT requests to the /status subresource take a custom resource object, and ignore changes to anything except the status stanza
|
||||
// * PUT/POST/PATCH requests to the custom resource ignore changes to the status stanza
|
||||
type CustomResourceSubresourceStatus struct{}
|
||||
|
||||
// CustomResourceSubresourceScale defines how to serve the scale subresource for CustomResources.
|
||||
type CustomResourceSubresourceScale struct {
|
||||
// specReplicasPath defines the JSON path inside of a custom resource that corresponds to Scale `spec.replicas`.
|
||||
// Only JSON paths without the array notation are allowed.
|
||||
// Must be a JSON Path under `.spec`.
|
||||
// If there is no value under the given path in the custom resource, the `/scale` subresource will return an error on GET.
|
||||
SpecReplicasPath string `json:"specReplicasPath" protobuf:"bytes,1,name=specReplicasPath"`
|
||||
// statusReplicasPath defines the JSON path inside of a custom resource that corresponds to Scale `status.replicas`.
|
||||
// Only JSON paths without the array notation are allowed.
|
||||
// Must be a JSON Path under `.status`.
|
||||
// If there is no value under the given path in the custom resource, the `status.replicas` value in the `/scale` subresource
|
||||
// will default to 0.
|
||||
StatusReplicasPath string `json:"statusReplicasPath" protobuf:"bytes,2,opt,name=statusReplicasPath"`
|
||||
// labelSelectorPath defines the JSON path inside of a custom resource that corresponds to Scale `status.selector`.
|
||||
// Only JSON paths without the array notation are allowed.
|
||||
// Must be a JSON Path under `.status` or `.spec`.
|
||||
// Must be set to work with HorizontalPodAutoscaler.
|
||||
// The field pointed by this JSON path must be a string field (not a complex selector struct)
|
||||
// which contains a serialized label selector in string form.
|
||||
// More info: https://kubernetes.io/docs/tasks/access-kubernetes-api/custom-resources/custom-resource-definitions#scale-subresource
|
||||
// If there is no value under the given path in the custom resource, the `status.selector` value in the `/scale`
|
||||
// subresource will default to the empty string.
|
||||
// +optional
|
||||
LabelSelectorPath *string `json:"labelSelectorPath,omitempty" protobuf:"bytes,3,opt,name=labelSelectorPath"`
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
// +k8s:prerelease-lifecycle-gen:introduced=1.16
|
||||
|
||||
// ConversionReview describes a conversion request/response.
|
||||
type ConversionReview struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
// request describes the attributes for the conversion request.
|
||||
// +optional
|
||||
Request *ConversionRequest `json:"request,omitempty" protobuf:"bytes,1,opt,name=request"`
|
||||
// response describes the attributes for the conversion response.
|
||||
// +optional
|
||||
Response *ConversionResponse `json:"response,omitempty" protobuf:"bytes,2,opt,name=response"`
|
||||
}
|
||||
|
||||
// ConversionRequest describes the conversion request parameters.
|
||||
type ConversionRequest struct {
|
||||
// uid is an identifier for the individual request/response. It allows distinguishing instances of requests which are
|
||||
// otherwise identical (parallel requests, etc).
|
||||
// The UID is meant to track the round trip (request/response) between the Kubernetes API server and the webhook, not the user request.
|
||||
// It is suitable for correlating log entries between the webhook and apiserver, for either auditing or debugging.
|
||||
UID types.UID `json:"uid" protobuf:"bytes,1,name=uid"`
|
||||
// desiredAPIVersion is the version to convert given objects to. e.g. "myapi.example.com/v1"
|
||||
DesiredAPIVersion string `json:"desiredAPIVersion" protobuf:"bytes,2,name=desiredAPIVersion"`
|
||||
// objects is the list of custom resource objects to be converted.
|
||||
// +listType=atomic
|
||||
Objects []runtime.RawExtension `json:"objects" protobuf:"bytes,3,rep,name=objects"`
|
||||
}
|
||||
|
||||
// ConversionResponse describes a conversion response.
|
||||
type ConversionResponse struct {
|
||||
// uid is an identifier for the individual request/response.
|
||||
// This should be copied over from the corresponding `request.uid`.
|
||||
UID types.UID `json:"uid" protobuf:"bytes,1,name=uid"`
|
||||
// convertedObjects is the list of converted version of `request.objects` if the `result` is successful, otherwise empty.
|
||||
// The webhook is expected to set `apiVersion` of these objects to the `request.desiredAPIVersion`. The list
|
||||
// must also have the same size as the input list with the same objects in the same order (equal kind, metadata.uid, metadata.name and metadata.namespace).
|
||||
// The webhook is allowed to mutate labels and annotations. Any other change to the metadata is silently ignored.
|
||||
// +listType=atomic
|
||||
ConvertedObjects []runtime.RawExtension `json:"convertedObjects" protobuf:"bytes,2,rep,name=convertedObjects"`
|
||||
// result contains the result of conversion with extra details if the conversion failed. `result.status` determines if
|
||||
// the conversion failed or succeeded. The `result.status` field is required and represents the success or failure of the
|
||||
// conversion. A successful conversion must set `result.status` to `Success`. A failed conversion must set
|
||||
// `result.status` to `Failure` and provide more details in `result.message` and return http status 200. The `result.message`
|
||||
// will be used to construct an error message for the end user.
|
||||
Result metav1.Status `json:"result" protobuf:"bytes,3,name=result"`
|
||||
}
|
419
vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1/types_jsonschema.go
generated
vendored
419
vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1/types_jsonschema.go
generated
vendored
@ -1,419 +0,0 @@
|
||||
/*
|
||||
Copyright 2019 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 v1
|
||||
|
||||
// FieldValueErrorReason is a machine-readable value providing more detail about why a field failed the validation.
|
||||
// +enum
|
||||
type FieldValueErrorReason string
|
||||
|
||||
const (
|
||||
// FieldValueRequired is used to report required values that are not
|
||||
// provided (e.g. empty strings, null values, or empty arrays).
|
||||
FieldValueRequired FieldValueErrorReason = "FieldValueRequired"
|
||||
// FieldValueDuplicate is used to report collisions of values that must be
|
||||
// unique (e.g. unique IDs).
|
||||
FieldValueDuplicate FieldValueErrorReason = "FieldValueDuplicate"
|
||||
// FieldValueInvalid is used to report malformed values (e.g. failed regex
|
||||
// match, too long, out of bounds).
|
||||
FieldValueInvalid FieldValueErrorReason = "FieldValueInvalid"
|
||||
// FieldValueForbidden is used to report valid (as per formatting rules)
|
||||
// values which would be accepted under some conditions, but which are not
|
||||
// permitted by the current conditions (such as security policy).
|
||||
FieldValueForbidden FieldValueErrorReason = "FieldValueForbidden"
|
||||
)
|
||||
|
||||
// JSONSchemaProps is a JSON-Schema following Specification Draft 4 (http://json-schema.org/).
|
||||
type JSONSchemaProps struct {
|
||||
ID string `json:"id,omitempty" protobuf:"bytes,1,opt,name=id"`
|
||||
Schema JSONSchemaURL `json:"$schema,omitempty" protobuf:"bytes,2,opt,name=schema"`
|
||||
Ref *string `json:"$ref,omitempty" protobuf:"bytes,3,opt,name=ref"`
|
||||
Description string `json:"description,omitempty" protobuf:"bytes,4,opt,name=description"`
|
||||
Type string `json:"type,omitempty" protobuf:"bytes,5,opt,name=type"`
|
||||
|
||||
// format is an OpenAPI v3 format string. Unknown formats are ignored. The following formats are validated:
|
||||
//
|
||||
// - bsonobjectid: a bson object ID, i.e. a 24 characters hex string
|
||||
// - uri: an URI as parsed by Golang net/url.ParseRequestURI
|
||||
// - email: an email address as parsed by Golang net/mail.ParseAddress
|
||||
// - hostname: a valid representation for an Internet host name, as defined by RFC 1034, section 3.1 [RFC1034].
|
||||
// - ipv4: an IPv4 IP as parsed by Golang net.ParseIP
|
||||
// - ipv6: an IPv6 IP as parsed by Golang net.ParseIP
|
||||
// - cidr: a CIDR as parsed by Golang net.ParseCIDR
|
||||
// - mac: a MAC address as parsed by Golang net.ParseMAC
|
||||
// - uuid: an UUID that allows uppercase defined by the regex (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{12}$
|
||||
// - uuid3: an UUID3 that allows uppercase defined by the regex (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?3[0-9a-f]{3}-?[0-9a-f]{4}-?[0-9a-f]{12}$
|
||||
// - uuid4: an UUID4 that allows uppercase defined by the regex (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?4[0-9a-f]{3}-?[89ab][0-9a-f]{3}-?[0-9a-f]{12}$
|
||||
// - uuid5: an UUID5 that allows uppercase defined by the regex (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?5[0-9a-f]{3}-?[89ab][0-9a-f]{3}-?[0-9a-f]{12}$
|
||||
// - isbn: an ISBN10 or ISBN13 number string like "0321751043" or "978-0321751041"
|
||||
// - isbn10: an ISBN10 number string like "0321751043"
|
||||
// - isbn13: an ISBN13 number string like "978-0321751041"
|
||||
// - creditcard: a credit card number defined by the regex ^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\\d{3})\\d{11})$ with any non digit characters mixed in
|
||||
// - ssn: a U.S. social security number following the regex ^\\d{3}[- ]?\\d{2}[- ]?\\d{4}$
|
||||
// - hexcolor: an hexadecimal color code like "#FFFFFF: following the regex ^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$
|
||||
// - rgbcolor: an RGB color code like rgb like "rgb(255,255,2559"
|
||||
// - byte: base64 encoded binary data
|
||||
// - password: any kind of string
|
||||
// - date: a date string like "2006-01-02" as defined by full-date in RFC3339
|
||||
// - duration: a duration string like "22 ns" as parsed by Golang time.ParseDuration or compatible with Scala duration format
|
||||
// - datetime: a date time string like "2014-12-15T19:30:20.000Z" as defined by date-time in RFC3339.
|
||||
Format string `json:"format,omitempty" protobuf:"bytes,6,opt,name=format"`
|
||||
|
||||
Title string `json:"title,omitempty" protobuf:"bytes,7,opt,name=title"`
|
||||
// default is a default value for undefined object fields.
|
||||
// Defaulting is a beta feature under the CustomResourceDefaulting feature gate.
|
||||
// Defaulting requires spec.preserveUnknownFields to be false.
|
||||
Default *JSON `json:"default,omitempty" protobuf:"bytes,8,opt,name=default"`
|
||||
Maximum *float64 `json:"maximum,omitempty" protobuf:"bytes,9,opt,name=maximum"`
|
||||
ExclusiveMaximum bool `json:"exclusiveMaximum,omitempty" protobuf:"bytes,10,opt,name=exclusiveMaximum"`
|
||||
Minimum *float64 `json:"minimum,omitempty" protobuf:"bytes,11,opt,name=minimum"`
|
||||
ExclusiveMinimum bool `json:"exclusiveMinimum,omitempty" protobuf:"bytes,12,opt,name=exclusiveMinimum"`
|
||||
MaxLength *int64 `json:"maxLength,omitempty" protobuf:"bytes,13,opt,name=maxLength"`
|
||||
MinLength *int64 `json:"minLength,omitempty" protobuf:"bytes,14,opt,name=minLength"`
|
||||
Pattern string `json:"pattern,omitempty" protobuf:"bytes,15,opt,name=pattern"`
|
||||
MaxItems *int64 `json:"maxItems,omitempty" protobuf:"bytes,16,opt,name=maxItems"`
|
||||
MinItems *int64 `json:"minItems,omitempty" protobuf:"bytes,17,opt,name=minItems"`
|
||||
UniqueItems bool `json:"uniqueItems,omitempty" protobuf:"bytes,18,opt,name=uniqueItems"`
|
||||
MultipleOf *float64 `json:"multipleOf,omitempty" protobuf:"bytes,19,opt,name=multipleOf"`
|
||||
// +listType=atomic
|
||||
Enum []JSON `json:"enum,omitempty" protobuf:"bytes,20,rep,name=enum"`
|
||||
MaxProperties *int64 `json:"maxProperties,omitempty" protobuf:"bytes,21,opt,name=maxProperties"`
|
||||
MinProperties *int64 `json:"minProperties,omitempty" protobuf:"bytes,22,opt,name=minProperties"`
|
||||
// +listType=atomic
|
||||
Required []string `json:"required,omitempty" protobuf:"bytes,23,rep,name=required"`
|
||||
Items *JSONSchemaPropsOrArray `json:"items,omitempty" protobuf:"bytes,24,opt,name=items"`
|
||||
// +listType=atomic
|
||||
AllOf []JSONSchemaProps `json:"allOf,omitempty" protobuf:"bytes,25,rep,name=allOf"`
|
||||
// +listType=atomic
|
||||
OneOf []JSONSchemaProps `json:"oneOf,omitempty" protobuf:"bytes,26,rep,name=oneOf"`
|
||||
// +listType=atomic
|
||||
AnyOf []JSONSchemaProps `json:"anyOf,omitempty" protobuf:"bytes,27,rep,name=anyOf"`
|
||||
Not *JSONSchemaProps `json:"not,omitempty" protobuf:"bytes,28,opt,name=not"`
|
||||
Properties map[string]JSONSchemaProps `json:"properties,omitempty" protobuf:"bytes,29,rep,name=properties"`
|
||||
AdditionalProperties *JSONSchemaPropsOrBool `json:"additionalProperties,omitempty" protobuf:"bytes,30,opt,name=additionalProperties"`
|
||||
PatternProperties map[string]JSONSchemaProps `json:"patternProperties,omitempty" protobuf:"bytes,31,rep,name=patternProperties"`
|
||||
Dependencies JSONSchemaDependencies `json:"dependencies,omitempty" protobuf:"bytes,32,opt,name=dependencies"`
|
||||
AdditionalItems *JSONSchemaPropsOrBool `json:"additionalItems,omitempty" protobuf:"bytes,33,opt,name=additionalItems"`
|
||||
Definitions JSONSchemaDefinitions `json:"definitions,omitempty" protobuf:"bytes,34,opt,name=definitions"`
|
||||
ExternalDocs *ExternalDocumentation `json:"externalDocs,omitempty" protobuf:"bytes,35,opt,name=externalDocs"`
|
||||
Example *JSON `json:"example,omitempty" protobuf:"bytes,36,opt,name=example"`
|
||||
Nullable bool `json:"nullable,omitempty" protobuf:"bytes,37,opt,name=nullable"`
|
||||
|
||||
// x-kubernetes-preserve-unknown-fields stops the API server
|
||||
// decoding step from pruning fields which are not specified
|
||||
// in the validation schema. This affects fields recursively,
|
||||
// but switches back to normal pruning behaviour if nested
|
||||
// properties or additionalProperties are specified in the schema.
|
||||
// This can either be true or undefined. False is forbidden.
|
||||
XPreserveUnknownFields *bool `json:"x-kubernetes-preserve-unknown-fields,omitempty" protobuf:"bytes,38,opt,name=xKubernetesPreserveUnknownFields"`
|
||||
|
||||
// x-kubernetes-embedded-resource defines that the value is an
|
||||
// embedded Kubernetes runtime.Object, with TypeMeta and
|
||||
// ObjectMeta. The type must be object. It is allowed to further
|
||||
// restrict the embedded object. kind, apiVersion and metadata
|
||||
// are validated automatically. x-kubernetes-preserve-unknown-fields
|
||||
// is allowed to be true, but does not have to be if the object
|
||||
// is fully specified (up to kind, apiVersion, metadata).
|
||||
XEmbeddedResource bool `json:"x-kubernetes-embedded-resource,omitempty" protobuf:"bytes,39,opt,name=xKubernetesEmbeddedResource"`
|
||||
|
||||
// x-kubernetes-int-or-string specifies that this value is
|
||||
// either an integer or a string. If this is true, an empty
|
||||
// type is allowed and type as child of anyOf is permitted
|
||||
// if following one of the following patterns:
|
||||
//
|
||||
// 1) anyOf:
|
||||
// - type: integer
|
||||
// - type: string
|
||||
// 2) allOf:
|
||||
// - anyOf:
|
||||
// - type: integer
|
||||
// - type: string
|
||||
// - ... zero or more
|
||||
XIntOrString bool `json:"x-kubernetes-int-or-string,omitempty" protobuf:"bytes,40,opt,name=xKubernetesIntOrString"`
|
||||
|
||||
// x-kubernetes-list-map-keys annotates an array with the x-kubernetes-list-type `map` by specifying the keys used
|
||||
// as the index of the map.
|
||||
//
|
||||
// This tag MUST only be used on lists that have the "x-kubernetes-list-type"
|
||||
// extension set to "map". Also, the values specified for this attribute must
|
||||
// be a scalar typed field of the child structure (no nesting is supported).
|
||||
//
|
||||
// The properties specified must either be required or have a default value,
|
||||
// to ensure those properties are present for all list items.
|
||||
//
|
||||
// +optional
|
||||
// +listType=atomic
|
||||
XListMapKeys []string `json:"x-kubernetes-list-map-keys,omitempty" protobuf:"bytes,41,rep,name=xKubernetesListMapKeys"`
|
||||
|
||||
// x-kubernetes-list-type annotates an array to further describe its topology.
|
||||
// This extension must only be used on lists and may have 3 possible values:
|
||||
//
|
||||
// 1) `atomic`: the list is treated as a single entity, like a scalar.
|
||||
// Atomic lists will be entirely replaced when updated. This extension
|
||||
// may be used on any type of list (struct, scalar, ...).
|
||||
// 2) `set`:
|
||||
// Sets are lists that must not have multiple items with the same value. Each
|
||||
// value must be a scalar, an object with x-kubernetes-map-type `atomic` or an
|
||||
// array with x-kubernetes-list-type `atomic`.
|
||||
// 3) `map`:
|
||||
// These lists are like maps in that their elements have a non-index key
|
||||
// used to identify them. Order is preserved upon merge. The map tag
|
||||
// must only be used on a list with elements of type object.
|
||||
// Defaults to atomic for arrays.
|
||||
// +optional
|
||||
XListType *string `json:"x-kubernetes-list-type,omitempty" protobuf:"bytes,42,opt,name=xKubernetesListType"`
|
||||
|
||||
// x-kubernetes-map-type annotates an object to further describe its topology.
|
||||
// This extension must only be used when type is object and may have 2 possible values:
|
||||
//
|
||||
// 1) `granular`:
|
||||
// These maps are actual maps (key-value pairs) and each fields are independent
|
||||
// from each other (they can each be manipulated by separate actors). This is
|
||||
// the default behaviour for all maps.
|
||||
// 2) `atomic`: the list is treated as a single entity, like a scalar.
|
||||
// Atomic maps will be entirely replaced when updated.
|
||||
// +optional
|
||||
XMapType *string `json:"x-kubernetes-map-type,omitempty" protobuf:"bytes,43,opt,name=xKubernetesMapType"`
|
||||
|
||||
// x-kubernetes-validations describes a list of validation rules written in the CEL expression language.
|
||||
// +patchMergeKey=rule
|
||||
// +patchStrategy=merge
|
||||
// +listType=map
|
||||
// +listMapKey=rule
|
||||
XValidations ValidationRules `json:"x-kubernetes-validations,omitempty" patchStrategy:"merge" patchMergeKey:"rule" protobuf:"bytes,44,rep,name=xKubernetesValidations"`
|
||||
}
|
||||
|
||||
// ValidationRules describes a list of validation rules written in the CEL expression language.
|
||||
type ValidationRules []ValidationRule
|
||||
|
||||
// ValidationRule describes a validation rule written in the CEL expression language.
|
||||
type ValidationRule struct {
|
||||
// Rule represents the expression which will be evaluated by CEL.
|
||||
// ref: https://github.com/google/cel-spec
|
||||
// The Rule is scoped to the location of the x-kubernetes-validations extension in the schema.
|
||||
// The `self` variable in the CEL expression is bound to the scoped value.
|
||||
// Example:
|
||||
// - Rule scoped to the root of a resource with a status subresource: {"rule": "self.status.actual <= self.spec.maxDesired"}
|
||||
//
|
||||
// If the Rule is scoped to an object with properties, the accessible properties of the object are field selectable
|
||||
// via `self.field` and field presence can be checked via `has(self.field)`. Null valued fields are treated as
|
||||
// absent fields in CEL expressions.
|
||||
// If the Rule is scoped to an object with additionalProperties (i.e. a map) the value of the map
|
||||
// are accessible via `self[mapKey]`, map containment can be checked via `mapKey in self` and all entries of the map
|
||||
// are accessible via CEL macros and functions such as `self.all(...)`.
|
||||
// If the Rule is scoped to an array, the elements of the array are accessible via `self[i]` and also by macros and
|
||||
// functions.
|
||||
// If the Rule is scoped to a scalar, `self` is bound to the scalar value.
|
||||
// Examples:
|
||||
// - Rule scoped to a map of objects: {"rule": "self.components['Widget'].priority < 10"}
|
||||
// - Rule scoped to a list of integers: {"rule": "self.values.all(value, value >= 0 && value < 100)"}
|
||||
// - Rule scoped to a string value: {"rule": "self.startsWith('kube')"}
|
||||
//
|
||||
// The `apiVersion`, `kind`, `metadata.name` and `metadata.generateName` are always accessible from the root of the
|
||||
// object and from any x-kubernetes-embedded-resource annotated objects. No other metadata properties are accessible.
|
||||
//
|
||||
// Unknown data preserved in custom resources via x-kubernetes-preserve-unknown-fields is not accessible in CEL
|
||||
// expressions. This includes:
|
||||
// - Unknown field values that are preserved by object schemas with x-kubernetes-preserve-unknown-fields.
|
||||
// - Object properties where the property schema is of an "unknown type". An "unknown type" is recursively defined as:
|
||||
// - A schema with no type and x-kubernetes-preserve-unknown-fields set to true
|
||||
// - An array where the items schema is of an "unknown type"
|
||||
// - An object where the additionalProperties schema is of an "unknown type"
|
||||
//
|
||||
// Only 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:
|
||||
// "true", "false", "null", "in", "as", "break", "const", "continue", "else", "for", "function", "if",
|
||||
// "import", "let", "loop", "package", "namespace", "return".
|
||||
// Examples:
|
||||
// - Rule accessing a property named "namespace": {"rule": "self.__namespace__ > 0"}
|
||||
// - Rule accessing a property named "x-prop": {"rule": "self.x__dash__prop > 0"}
|
||||
// - Rule accessing a property named "redact__d": {"rule": "self.redact__underscores__d > 0"}
|
||||
//
|
||||
// Equality on arrays with x-kubernetes-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:
|
||||
// - 'set': `X + Y` performs a union where the array positions of all elements in `X` are preserved and
|
||||
// non-intersecting elements in `Y` are appended, retaining their partial order.
|
||||
// - 'map': `X + Y` performs a merge where the array positions of all keys in `X` are preserved but the values
|
||||
// are overwritten by values in `Y` when the key sets of `X` and `Y` intersect. Elements in `Y` with
|
||||
// non-intersecting keys are appended, retaining their partial order.
|
||||
//
|
||||
// If `rule` makes use of the `oldSelf` variable it is implicitly a
|
||||
// `transition rule`.
|
||||
//
|
||||
// By default, the `oldSelf` variable is the same type as `self`.
|
||||
// When `optionalOldSelf` is true, the `oldSelf` variable is a CEL optional
|
||||
// variable whose value() is the same type as `self`.
|
||||
// See the documentation for the `optionalOldSelf` field for details.
|
||||
//
|
||||
// Transition rules by default are applied only on UPDATE requests and are
|
||||
// skipped if an old value could not be found. You can opt a transition
|
||||
// rule into unconditional evaluation by setting `optionalOldSelf` to true.
|
||||
//
|
||||
Rule string `json:"rule" protobuf:"bytes,1,opt,name=rule"`
|
||||
// Message represents the message displayed when validation fails. The message is required if the Rule contains
|
||||
// line breaks. The message must not contain line breaks.
|
||||
// If unset, the message is "failed rule: {Rule}".
|
||||
// e.g. "must be a URL with the host matching spec.host"
|
||||
Message string `json:"message,omitempty" protobuf:"bytes,2,opt,name=message"`
|
||||
// MessageExpression declares a CEL expression that evaluates to the validation failure message that is returned when this rule fails.
|
||||
// Since messageExpression is used as a failure message, it must evaluate to a string.
|
||||
// If both message and messageExpression are present on a rule, then messageExpression will be used if validation
|
||||
// fails. If messageExpression results in a runtime error, the runtime error is logged, and the validation failure message is produced
|
||||
// as if the messageExpression field were unset. If messageExpression evaluates to an empty string, a string with only spaces, or a string
|
||||
// that contains line breaks, then the validation failure message will also be produced as if the messageExpression field were unset, and
|
||||
// the fact that messageExpression produced an empty string/string with only spaces/string with line breaks will be logged.
|
||||
// messageExpression has access to all the same variables as the rule; the only difference is the return type.
|
||||
// Example:
|
||||
// "x must be less than max ("+string(self.max)+")"
|
||||
// +optional
|
||||
MessageExpression string `json:"messageExpression,omitempty" protobuf:"bytes,3,opt,name=messageExpression"`
|
||||
// reason provides a machine-readable validation failure reason that is returned to the caller when a request fails this validation rule.
|
||||
// The HTTP status code returned to the caller will match the reason of the reason of the first failed validation rule.
|
||||
// The currently supported reasons are: "FieldValueInvalid", "FieldValueForbidden", "FieldValueRequired", "FieldValueDuplicate".
|
||||
// If not set, default to use "FieldValueInvalid".
|
||||
// All future added reasons must be accepted by clients when reading this value and unknown reasons should be treated as FieldValueInvalid.
|
||||
// +optional
|
||||
Reason *FieldValueErrorReason `json:"reason,omitempty" protobuf:"bytes,4,opt,name=reason"`
|
||||
// fieldPath represents the field path returned when the validation fails.
|
||||
// It must be a relative JSON path (i.e. with array notation) scoped to the location of this x-kubernetes-validations extension in the schema and refer to an existing field.
|
||||
// e.g. when validation checks if a specific attribute `foo` under a map `testMap`, the fieldPath could be set to `.testMap.foo`
|
||||
// If the validation checks two lists must have unique attributes, the fieldPath could be set to either of the list: e.g. `.testList`
|
||||
// It does not support list numeric index.
|
||||
// It supports child operation to refer to an existing field currently. Refer to [JSONPath support in Kubernetes](https://kubernetes.io/docs/reference/kubectl/jsonpath/) for more info.
|
||||
// Numeric index of array is not supported.
|
||||
// For field name which contains special characters, use `['specialName']` to refer the field name.
|
||||
// e.g. for attribute `foo.34$` appears in a list `testList`, the fieldPath could be set to `.testList['foo.34$']`
|
||||
// +optional
|
||||
FieldPath string `json:"fieldPath,omitempty" protobuf:"bytes,5,opt,name=fieldPath"`
|
||||
|
||||
// optionalOldSelf is used to opt a transition rule into evaluation
|
||||
// even when the object is first created, or if the old object is
|
||||
// missing the value.
|
||||
//
|
||||
// When enabled `oldSelf` will be a CEL optional whose value will be
|
||||
// `None` if there is no old value, or when the object is initially created.
|
||||
//
|
||||
// You may check for presence of oldSelf using `oldSelf.hasValue()` and
|
||||
// unwrap it after checking using `oldSelf.value()`. Check the CEL
|
||||
// documentation for Optional types for more information:
|
||||
// https://pkg.go.dev/github.com/google/cel-go/cel#OptionalTypes
|
||||
//
|
||||
// May not be set unless `oldSelf` is used in `rule`.
|
||||
//
|
||||
// +featureGate=CRDValidationRatcheting
|
||||
// +optional
|
||||
OptionalOldSelf *bool `json:"optionalOldSelf,omitempty" protobuf:"bytes,6,opt,name=optionalOldSelf"`
|
||||
}
|
||||
|
||||
// JSON represents any valid JSON value.
|
||||
// These types are supported: bool, int64, float64, string, []interface{}, map[string]interface{} and nil.
|
||||
type JSON struct {
|
||||
Raw []byte `json:"-" protobuf:"bytes,1,opt,name=raw"`
|
||||
}
|
||||
|
||||
// OpenAPISchemaType is used by the kube-openapi generator when constructing
|
||||
// the OpenAPI spec of this type.
|
||||
//
|
||||
// See: https://github.com/kubernetes/kube-openapi/tree/master/pkg/generators
|
||||
func (_ JSON) OpenAPISchemaType() []string {
|
||||
// TODO: return actual types when anyOf is supported
|
||||
return nil
|
||||
}
|
||||
|
||||
// OpenAPISchemaFormat is used by the kube-openapi generator when constructing
|
||||
// the OpenAPI spec of this type.
|
||||
func (_ JSON) OpenAPISchemaFormat() string { return "" }
|
||||
|
||||
// JSONSchemaURL represents a schema url.
|
||||
type JSONSchemaURL string
|
||||
|
||||
// JSONSchemaPropsOrArray represents a value that can either be a JSONSchemaProps
|
||||
// or an array of JSONSchemaProps. Mainly here for serialization purposes.
|
||||
type JSONSchemaPropsOrArray struct {
|
||||
Schema *JSONSchemaProps `protobuf:"bytes,1,opt,name=schema"`
|
||||
// +listType=atomic
|
||||
JSONSchemas []JSONSchemaProps `protobuf:"bytes,2,rep,name=jSONSchemas"`
|
||||
}
|
||||
|
||||
// OpenAPISchemaType is used by the kube-openapi generator when constructing
|
||||
// the OpenAPI spec of this type.
|
||||
//
|
||||
// See: https://github.com/kubernetes/kube-openapi/tree/master/pkg/generators
|
||||
func (_ JSONSchemaPropsOrArray) OpenAPISchemaType() []string {
|
||||
// TODO: return actual types when anyOf is supported
|
||||
return nil
|
||||
}
|
||||
|
||||
// OpenAPISchemaFormat is used by the kube-openapi generator when constructing
|
||||
// the OpenAPI spec of this type.
|
||||
func (_ JSONSchemaPropsOrArray) OpenAPISchemaFormat() string { return "" }
|
||||
|
||||
// JSONSchemaPropsOrBool represents JSONSchemaProps or a boolean value.
|
||||
// Defaults to true for the boolean property.
|
||||
type JSONSchemaPropsOrBool struct {
|
||||
Allows bool `protobuf:"varint,1,opt,name=allows"`
|
||||
Schema *JSONSchemaProps `protobuf:"bytes,2,opt,name=schema"`
|
||||
}
|
||||
|
||||
// OpenAPISchemaType is used by the kube-openapi generator when constructing
|
||||
// the OpenAPI spec of this type.
|
||||
//
|
||||
// See: https://github.com/kubernetes/kube-openapi/tree/master/pkg/generators
|
||||
func (_ JSONSchemaPropsOrBool) OpenAPISchemaType() []string {
|
||||
// TODO: return actual types when anyOf is supported
|
||||
return nil
|
||||
}
|
||||
|
||||
// OpenAPISchemaFormat is used by the kube-openapi generator when constructing
|
||||
// the OpenAPI spec of this type.
|
||||
func (_ JSONSchemaPropsOrBool) OpenAPISchemaFormat() string { return "" }
|
||||
|
||||
// JSONSchemaDependencies represent a dependencies property.
|
||||
type JSONSchemaDependencies map[string]JSONSchemaPropsOrStringArray
|
||||
|
||||
// JSONSchemaPropsOrStringArray represents a JSONSchemaProps or a string array.
|
||||
type JSONSchemaPropsOrStringArray struct {
|
||||
Schema *JSONSchemaProps `protobuf:"bytes,1,opt,name=schema"`
|
||||
// +listType=atomic
|
||||
Property []string `protobuf:"bytes,2,rep,name=property"`
|
||||
}
|
||||
|
||||
// OpenAPISchemaType is used by the kube-openapi generator when constructing
|
||||
// the OpenAPI spec of this type.
|
||||
//
|
||||
// See: https://github.com/kubernetes/kube-openapi/tree/master/pkg/generators
|
||||
func (_ JSONSchemaPropsOrStringArray) OpenAPISchemaType() []string {
|
||||
// TODO: return actual types when anyOf is supported
|
||||
return nil
|
||||
}
|
||||
|
||||
// OpenAPISchemaFormat is used by the kube-openapi generator when constructing
|
||||
// the OpenAPI spec of this type.
|
||||
func (_ JSONSchemaPropsOrStringArray) OpenAPISchemaFormat() string { return "" }
|
||||
|
||||
// JSONSchemaDefinitions contains the models explicitly defined in this spec.
|
||||
type JSONSchemaDefinitions map[string]JSONSchemaProps
|
||||
|
||||
// ExternalDocumentation allows referencing an external resource for extended documentation.
|
||||
type ExternalDocumentation struct {
|
||||
Description string `json:"description,omitempty" protobuf:"bytes,1,opt,name=description"`
|
||||
URL string `json:"url,omitempty" protobuf:"bytes,2,opt,name=url"`
|
||||
}
|
1359
vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1/zz_generated.conversion.go
generated
vendored
1359
vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1/zz_generated.conversion.go
generated
vendored
File diff suppressed because it is too large
Load Diff
738
vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1/zz_generated.deepcopy.go
generated
vendored
738
vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1/zz_generated.deepcopy.go
generated
vendored
@ -1,738 +0,0 @@
|
||||
//go:build !ignore_autogenerated
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
/*
|
||||
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 deepcopy-gen. DO NOT EDIT.
|
||||
|
||||
package v1
|
||||
|
||||
import (
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ConversionRequest) DeepCopyInto(out *ConversionRequest) {
|
||||
*out = *in
|
||||
if in.Objects != nil {
|
||||
in, out := &in.Objects, &out.Objects
|
||||
*out = make([]runtime.RawExtension, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConversionRequest.
|
||||
func (in *ConversionRequest) DeepCopy() *ConversionRequest {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ConversionRequest)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ConversionResponse) DeepCopyInto(out *ConversionResponse) {
|
||||
*out = *in
|
||||
if in.ConvertedObjects != nil {
|
||||
in, out := &in.ConvertedObjects, &out.ConvertedObjects
|
||||
*out = make([]runtime.RawExtension, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
in.Result.DeepCopyInto(&out.Result)
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConversionResponse.
|
||||
func (in *ConversionResponse) DeepCopy() *ConversionResponse {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ConversionResponse)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ConversionReview) DeepCopyInto(out *ConversionReview) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
if in.Request != nil {
|
||||
in, out := &in.Request, &out.Request
|
||||
*out = new(ConversionRequest)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.Response != nil {
|
||||
in, out := &in.Response, &out.Response
|
||||
*out = new(ConversionResponse)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConversionReview.
|
||||
func (in *ConversionReview) DeepCopy() *ConversionReview {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ConversionReview)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *ConversionReview) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *CustomResourceColumnDefinition) DeepCopyInto(out *CustomResourceColumnDefinition) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomResourceColumnDefinition.
|
||||
func (in *CustomResourceColumnDefinition) DeepCopy() *CustomResourceColumnDefinition {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(CustomResourceColumnDefinition)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *CustomResourceConversion) DeepCopyInto(out *CustomResourceConversion) {
|
||||
*out = *in
|
||||
if in.Webhook != nil {
|
||||
in, out := &in.Webhook, &out.Webhook
|
||||
*out = new(WebhookConversion)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomResourceConversion.
|
||||
func (in *CustomResourceConversion) DeepCopy() *CustomResourceConversion {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(CustomResourceConversion)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *CustomResourceDefinition) DeepCopyInto(out *CustomResourceDefinition) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
in.Spec.DeepCopyInto(&out.Spec)
|
||||
in.Status.DeepCopyInto(&out.Status)
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomResourceDefinition.
|
||||
func (in *CustomResourceDefinition) DeepCopy() *CustomResourceDefinition {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(CustomResourceDefinition)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *CustomResourceDefinition) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *CustomResourceDefinitionCondition) DeepCopyInto(out *CustomResourceDefinitionCondition) {
|
||||
*out = *in
|
||||
in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime)
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomResourceDefinitionCondition.
|
||||
func (in *CustomResourceDefinitionCondition) DeepCopy() *CustomResourceDefinitionCondition {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(CustomResourceDefinitionCondition)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *CustomResourceDefinitionList) DeepCopyInto(out *CustomResourceDefinitionList) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]CustomResourceDefinition, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomResourceDefinitionList.
|
||||
func (in *CustomResourceDefinitionList) DeepCopy() *CustomResourceDefinitionList {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(CustomResourceDefinitionList)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *CustomResourceDefinitionList) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *CustomResourceDefinitionNames) DeepCopyInto(out *CustomResourceDefinitionNames) {
|
||||
*out = *in
|
||||
if in.ShortNames != nil {
|
||||
in, out := &in.ShortNames, &out.ShortNames
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.Categories != nil {
|
||||
in, out := &in.Categories, &out.Categories
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomResourceDefinitionNames.
|
||||
func (in *CustomResourceDefinitionNames) DeepCopy() *CustomResourceDefinitionNames {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(CustomResourceDefinitionNames)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *CustomResourceDefinitionSpec) DeepCopyInto(out *CustomResourceDefinitionSpec) {
|
||||
*out = *in
|
||||
in.Names.DeepCopyInto(&out.Names)
|
||||
if in.Versions != nil {
|
||||
in, out := &in.Versions, &out.Versions
|
||||
*out = make([]CustomResourceDefinitionVersion, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
if in.Conversion != nil {
|
||||
in, out := &in.Conversion, &out.Conversion
|
||||
*out = new(CustomResourceConversion)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomResourceDefinitionSpec.
|
||||
func (in *CustomResourceDefinitionSpec) DeepCopy() *CustomResourceDefinitionSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(CustomResourceDefinitionSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *CustomResourceDefinitionStatus) DeepCopyInto(out *CustomResourceDefinitionStatus) {
|
||||
*out = *in
|
||||
if in.Conditions != nil {
|
||||
in, out := &in.Conditions, &out.Conditions
|
||||
*out = make([]CustomResourceDefinitionCondition, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
in.AcceptedNames.DeepCopyInto(&out.AcceptedNames)
|
||||
if in.StoredVersions != nil {
|
||||
in, out := &in.StoredVersions, &out.StoredVersions
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomResourceDefinitionStatus.
|
||||
func (in *CustomResourceDefinitionStatus) DeepCopy() *CustomResourceDefinitionStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(CustomResourceDefinitionStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *CustomResourceDefinitionVersion) DeepCopyInto(out *CustomResourceDefinitionVersion) {
|
||||
*out = *in
|
||||
if in.DeprecationWarning != nil {
|
||||
in, out := &in.DeprecationWarning, &out.DeprecationWarning
|
||||
*out = new(string)
|
||||
**out = **in
|
||||
}
|
||||
if in.Schema != nil {
|
||||
in, out := &in.Schema, &out.Schema
|
||||
*out = new(CustomResourceValidation)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.Subresources != nil {
|
||||
in, out := &in.Subresources, &out.Subresources
|
||||
*out = new(CustomResourceSubresources)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.AdditionalPrinterColumns != nil {
|
||||
in, out := &in.AdditionalPrinterColumns, &out.AdditionalPrinterColumns
|
||||
*out = make([]CustomResourceColumnDefinition, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.SelectableFields != nil {
|
||||
in, out := &in.SelectableFields, &out.SelectableFields
|
||||
*out = make([]SelectableField, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomResourceDefinitionVersion.
|
||||
func (in *CustomResourceDefinitionVersion) DeepCopy() *CustomResourceDefinitionVersion {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(CustomResourceDefinitionVersion)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *CustomResourceSubresourceScale) DeepCopyInto(out *CustomResourceSubresourceScale) {
|
||||
*out = *in
|
||||
if in.LabelSelectorPath != nil {
|
||||
in, out := &in.LabelSelectorPath, &out.LabelSelectorPath
|
||||
*out = new(string)
|
||||
**out = **in
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomResourceSubresourceScale.
|
||||
func (in *CustomResourceSubresourceScale) DeepCopy() *CustomResourceSubresourceScale {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(CustomResourceSubresourceScale)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *CustomResourceSubresourceStatus) DeepCopyInto(out *CustomResourceSubresourceStatus) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomResourceSubresourceStatus.
|
||||
func (in *CustomResourceSubresourceStatus) DeepCopy() *CustomResourceSubresourceStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(CustomResourceSubresourceStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *CustomResourceSubresources) DeepCopyInto(out *CustomResourceSubresources) {
|
||||
*out = *in
|
||||
if in.Status != nil {
|
||||
in, out := &in.Status, &out.Status
|
||||
*out = new(CustomResourceSubresourceStatus)
|
||||
**out = **in
|
||||
}
|
||||
if in.Scale != nil {
|
||||
in, out := &in.Scale, &out.Scale
|
||||
*out = new(CustomResourceSubresourceScale)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomResourceSubresources.
|
||||
func (in *CustomResourceSubresources) DeepCopy() *CustomResourceSubresources {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(CustomResourceSubresources)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *CustomResourceValidation) DeepCopyInto(out *CustomResourceValidation) {
|
||||
*out = *in
|
||||
if in.OpenAPIV3Schema != nil {
|
||||
in, out := &in.OpenAPIV3Schema, &out.OpenAPIV3Schema
|
||||
*out = (*in).DeepCopy()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomResourceValidation.
|
||||
func (in *CustomResourceValidation) DeepCopy() *CustomResourceValidation {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(CustomResourceValidation)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ExternalDocumentation) DeepCopyInto(out *ExternalDocumentation) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalDocumentation.
|
||||
func (in *ExternalDocumentation) DeepCopy() *ExternalDocumentation {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ExternalDocumentation)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *JSON) DeepCopyInto(out *JSON) {
|
||||
*out = *in
|
||||
if in.Raw != nil {
|
||||
in, out := &in.Raw, &out.Raw
|
||||
*out = make([]byte, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JSON.
|
||||
func (in *JSON) DeepCopy() *JSON {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(JSON)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in JSONSchemaDefinitions) DeepCopyInto(out *JSONSchemaDefinitions) {
|
||||
{
|
||||
in := &in
|
||||
*out = make(JSONSchemaDefinitions, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = *val.DeepCopy()
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JSONSchemaDefinitions.
|
||||
func (in JSONSchemaDefinitions) DeepCopy() JSONSchemaDefinitions {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(JSONSchemaDefinitions)
|
||||
in.DeepCopyInto(out)
|
||||
return *out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in JSONSchemaDependencies) DeepCopyInto(out *JSONSchemaDependencies) {
|
||||
{
|
||||
in := &in
|
||||
*out = make(JSONSchemaDependencies, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = *val.DeepCopy()
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JSONSchemaDependencies.
|
||||
func (in JSONSchemaDependencies) DeepCopy() JSONSchemaDependencies {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(JSONSchemaDependencies)
|
||||
in.DeepCopyInto(out)
|
||||
return *out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *JSONSchemaProps) DeepCopyInto(out *JSONSchemaProps) {
|
||||
clone := in.DeepCopy()
|
||||
*out = *clone
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *JSONSchemaPropsOrArray) DeepCopyInto(out *JSONSchemaPropsOrArray) {
|
||||
*out = *in
|
||||
if in.Schema != nil {
|
||||
in, out := &in.Schema, &out.Schema
|
||||
*out = (*in).DeepCopy()
|
||||
}
|
||||
if in.JSONSchemas != nil {
|
||||
in, out := &in.JSONSchemas, &out.JSONSchemas
|
||||
*out = make([]JSONSchemaProps, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JSONSchemaPropsOrArray.
|
||||
func (in *JSONSchemaPropsOrArray) DeepCopy() *JSONSchemaPropsOrArray {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(JSONSchemaPropsOrArray)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *JSONSchemaPropsOrBool) DeepCopyInto(out *JSONSchemaPropsOrBool) {
|
||||
*out = *in
|
||||
if in.Schema != nil {
|
||||
in, out := &in.Schema, &out.Schema
|
||||
*out = (*in).DeepCopy()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JSONSchemaPropsOrBool.
|
||||
func (in *JSONSchemaPropsOrBool) DeepCopy() *JSONSchemaPropsOrBool {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(JSONSchemaPropsOrBool)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *JSONSchemaPropsOrStringArray) DeepCopyInto(out *JSONSchemaPropsOrStringArray) {
|
||||
*out = *in
|
||||
if in.Schema != nil {
|
||||
in, out := &in.Schema, &out.Schema
|
||||
*out = (*in).DeepCopy()
|
||||
}
|
||||
if in.Property != nil {
|
||||
in, out := &in.Property, &out.Property
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JSONSchemaPropsOrStringArray.
|
||||
func (in *JSONSchemaPropsOrStringArray) DeepCopy() *JSONSchemaPropsOrStringArray {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(JSONSchemaPropsOrStringArray)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *SelectableField) DeepCopyInto(out *SelectableField) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SelectableField.
|
||||
func (in *SelectableField) DeepCopy() *SelectableField {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(SelectableField)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ServiceReference) DeepCopyInto(out *ServiceReference) {
|
||||
*out = *in
|
||||
if in.Path != nil {
|
||||
in, out := &in.Path, &out.Path
|
||||
*out = new(string)
|
||||
**out = **in
|
||||
}
|
||||
if in.Port != nil {
|
||||
in, out := &in.Port, &out.Port
|
||||
*out = new(int32)
|
||||
**out = **in
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceReference.
|
||||
func (in *ServiceReference) DeepCopy() *ServiceReference {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ServiceReference)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ValidationRule) DeepCopyInto(out *ValidationRule) {
|
||||
*out = *in
|
||||
if in.Reason != nil {
|
||||
in, out := &in.Reason, &out.Reason
|
||||
*out = new(FieldValueErrorReason)
|
||||
**out = **in
|
||||
}
|
||||
if in.OptionalOldSelf != nil {
|
||||
in, out := &in.OptionalOldSelf, &out.OptionalOldSelf
|
||||
*out = new(bool)
|
||||
**out = **in
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ValidationRule.
|
||||
func (in *ValidationRule) DeepCopy() *ValidationRule {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ValidationRule)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in ValidationRules) DeepCopyInto(out *ValidationRules) {
|
||||
{
|
||||
in := &in
|
||||
*out = make(ValidationRules, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ValidationRules.
|
||||
func (in ValidationRules) DeepCopy() ValidationRules {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ValidationRules)
|
||||
in.DeepCopyInto(out)
|
||||
return *out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *WebhookClientConfig) DeepCopyInto(out *WebhookClientConfig) {
|
||||
*out = *in
|
||||
if in.URL != nil {
|
||||
in, out := &in.URL, &out.URL
|
||||
*out = new(string)
|
||||
**out = **in
|
||||
}
|
||||
if in.Service != nil {
|
||||
in, out := &in.Service, &out.Service
|
||||
*out = new(ServiceReference)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.CABundle != nil {
|
||||
in, out := &in.CABundle, &out.CABundle
|
||||
*out = make([]byte, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WebhookClientConfig.
|
||||
func (in *WebhookClientConfig) DeepCopy() *WebhookClientConfig {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(WebhookClientConfig)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *WebhookConversion) DeepCopyInto(out *WebhookConversion) {
|
||||
*out = *in
|
||||
if in.ClientConfig != nil {
|
||||
in, out := &in.ClientConfig, &out.ClientConfig
|
||||
*out = new(WebhookClientConfig)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.ConversionReviewVersions != nil {
|
||||
in, out := &in.ConversionReviewVersions, &out.ConversionReviewVersions
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WebhookConversion.
|
||||
func (in *WebhookConversion) DeepCopy() *WebhookConversion {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(WebhookConversion)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
58
vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1/zz_generated.defaults.go
generated
vendored
58
vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1/zz_generated.defaults.go
generated
vendored
@ -1,58 +0,0 @@
|
||||
//go:build !ignore_autogenerated
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
/*
|
||||
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 defaulter-gen. DO NOT EDIT.
|
||||
|
||||
package v1
|
||||
|
||||
import (
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// RegisterDefaults adds defaulters functions to the given scheme.
|
||||
// Public to allow building arbitrary schemes.
|
||||
// All generated defaulters are covering - they call all nested defaulters.
|
||||
func RegisterDefaults(scheme *runtime.Scheme) error {
|
||||
scheme.AddTypeDefaultingFunc(&CustomResourceDefinition{}, func(obj interface{}) { SetObjectDefaults_CustomResourceDefinition(obj.(*CustomResourceDefinition)) })
|
||||
scheme.AddTypeDefaultingFunc(&CustomResourceDefinitionList{}, func(obj interface{}) {
|
||||
SetObjectDefaults_CustomResourceDefinitionList(obj.(*CustomResourceDefinitionList))
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func SetObjectDefaults_CustomResourceDefinition(in *CustomResourceDefinition) {
|
||||
SetDefaults_CustomResourceDefinition(in)
|
||||
SetDefaults_CustomResourceDefinitionSpec(&in.Spec)
|
||||
if in.Spec.Conversion != nil {
|
||||
if in.Spec.Conversion.Webhook != nil {
|
||||
if in.Spec.Conversion.Webhook.ClientConfig != nil {
|
||||
if in.Spec.Conversion.Webhook.ClientConfig.Service != nil {
|
||||
SetDefaults_ServiceReference(in.Spec.Conversion.Webhook.ClientConfig.Service)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func SetObjectDefaults_CustomResourceDefinitionList(in *CustomResourceDefinitionList) {
|
||||
for i := range in.Items {
|
||||
a := &in.Items[i]
|
||||
SetObjectDefaults_CustomResourceDefinition(a)
|
||||
}
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
//go:build !ignore_autogenerated
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
/*
|
||||
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 prerelease-lifecycle-gen. DO NOT EDIT.
|
||||
|
||||
package v1
|
||||
|
||||
// APILifecycleIntroduced is an autogenerated function, returning the release in which the API struct was introduced as int versions of major and minor for comparison.
|
||||
// It is controlled by "k8s:prerelease-lifecycle-gen:introduced" tags in types.go.
|
||||
func (in *ConversionReview) APILifecycleIntroduced() (major, minor int) {
|
||||
return 1, 16
|
||||
}
|
||||
|
||||
// APILifecycleIntroduced is an autogenerated function, returning the release in which the API struct was introduced as int versions of major and minor for comparison.
|
||||
// It is controlled by "k8s:prerelease-lifecycle-gen:introduced" tags in types.go.
|
||||
func (in *CustomResourceDefinition) APILifecycleIntroduced() (major, minor int) {
|
||||
return 1, 16
|
||||
}
|
||||
|
||||
// APILifecycleIntroduced is an autogenerated function, returning the release in which the API struct was introduced as int versions of major and minor for comparison.
|
||||
// It is controlled by "k8s:prerelease-lifecycle-gen:introduced" tags in types.go.
|
||||
func (in *CustomResourceDefinitionList) APILifecycleIntroduced() (major, minor int) {
|
||||
return 1, 16
|
||||
}
|
634
vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/zz_generated.deepcopy.go
generated
vendored
634
vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/zz_generated.deepcopy.go
generated
vendored
@ -1,634 +0,0 @@
|
||||
//go:build !ignore_autogenerated
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
/*
|
||||
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 deepcopy-gen. DO NOT EDIT.
|
||||
|
||||
package apiextensions
|
||||
|
||||
import (
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *CustomResourceColumnDefinition) DeepCopyInto(out *CustomResourceColumnDefinition) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomResourceColumnDefinition.
|
||||
func (in *CustomResourceColumnDefinition) DeepCopy() *CustomResourceColumnDefinition {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(CustomResourceColumnDefinition)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *CustomResourceConversion) DeepCopyInto(out *CustomResourceConversion) {
|
||||
*out = *in
|
||||
if in.WebhookClientConfig != nil {
|
||||
in, out := &in.WebhookClientConfig, &out.WebhookClientConfig
|
||||
*out = new(WebhookClientConfig)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.ConversionReviewVersions != nil {
|
||||
in, out := &in.ConversionReviewVersions, &out.ConversionReviewVersions
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomResourceConversion.
|
||||
func (in *CustomResourceConversion) DeepCopy() *CustomResourceConversion {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(CustomResourceConversion)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *CustomResourceDefinition) DeepCopyInto(out *CustomResourceDefinition) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
in.Spec.DeepCopyInto(&out.Spec)
|
||||
in.Status.DeepCopyInto(&out.Status)
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomResourceDefinition.
|
||||
func (in *CustomResourceDefinition) DeepCopy() *CustomResourceDefinition {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(CustomResourceDefinition)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *CustomResourceDefinition) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *CustomResourceDefinitionCondition) DeepCopyInto(out *CustomResourceDefinitionCondition) {
|
||||
*out = *in
|
||||
in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime)
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomResourceDefinitionCondition.
|
||||
func (in *CustomResourceDefinitionCondition) DeepCopy() *CustomResourceDefinitionCondition {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(CustomResourceDefinitionCondition)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *CustomResourceDefinitionList) DeepCopyInto(out *CustomResourceDefinitionList) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]CustomResourceDefinition, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomResourceDefinitionList.
|
||||
func (in *CustomResourceDefinitionList) DeepCopy() *CustomResourceDefinitionList {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(CustomResourceDefinitionList)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *CustomResourceDefinitionList) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *CustomResourceDefinitionNames) DeepCopyInto(out *CustomResourceDefinitionNames) {
|
||||
*out = *in
|
||||
if in.ShortNames != nil {
|
||||
in, out := &in.ShortNames, &out.ShortNames
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.Categories != nil {
|
||||
in, out := &in.Categories, &out.Categories
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomResourceDefinitionNames.
|
||||
func (in *CustomResourceDefinitionNames) DeepCopy() *CustomResourceDefinitionNames {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(CustomResourceDefinitionNames)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *CustomResourceDefinitionSpec) DeepCopyInto(out *CustomResourceDefinitionSpec) {
|
||||
*out = *in
|
||||
in.Names.DeepCopyInto(&out.Names)
|
||||
if in.Validation != nil {
|
||||
in, out := &in.Validation, &out.Validation
|
||||
*out = new(CustomResourceValidation)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.Subresources != nil {
|
||||
in, out := &in.Subresources, &out.Subresources
|
||||
*out = new(CustomResourceSubresources)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.Versions != nil {
|
||||
in, out := &in.Versions, &out.Versions
|
||||
*out = make([]CustomResourceDefinitionVersion, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
if in.AdditionalPrinterColumns != nil {
|
||||
in, out := &in.AdditionalPrinterColumns, &out.AdditionalPrinterColumns
|
||||
*out = make([]CustomResourceColumnDefinition, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.SelectableFields != nil {
|
||||
in, out := &in.SelectableFields, &out.SelectableFields
|
||||
*out = make([]SelectableField, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.Conversion != nil {
|
||||
in, out := &in.Conversion, &out.Conversion
|
||||
*out = new(CustomResourceConversion)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.PreserveUnknownFields != nil {
|
||||
in, out := &in.PreserveUnknownFields, &out.PreserveUnknownFields
|
||||
*out = new(bool)
|
||||
**out = **in
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomResourceDefinitionSpec.
|
||||
func (in *CustomResourceDefinitionSpec) DeepCopy() *CustomResourceDefinitionSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(CustomResourceDefinitionSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *CustomResourceDefinitionStatus) DeepCopyInto(out *CustomResourceDefinitionStatus) {
|
||||
*out = *in
|
||||
if in.Conditions != nil {
|
||||
in, out := &in.Conditions, &out.Conditions
|
||||
*out = make([]CustomResourceDefinitionCondition, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
in.AcceptedNames.DeepCopyInto(&out.AcceptedNames)
|
||||
if in.StoredVersions != nil {
|
||||
in, out := &in.StoredVersions, &out.StoredVersions
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomResourceDefinitionStatus.
|
||||
func (in *CustomResourceDefinitionStatus) DeepCopy() *CustomResourceDefinitionStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(CustomResourceDefinitionStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *CustomResourceDefinitionVersion) DeepCopyInto(out *CustomResourceDefinitionVersion) {
|
||||
*out = *in
|
||||
if in.DeprecationWarning != nil {
|
||||
in, out := &in.DeprecationWarning, &out.DeprecationWarning
|
||||
*out = new(string)
|
||||
**out = **in
|
||||
}
|
||||
if in.Schema != nil {
|
||||
in, out := &in.Schema, &out.Schema
|
||||
*out = new(CustomResourceValidation)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.Subresources != nil {
|
||||
in, out := &in.Subresources, &out.Subresources
|
||||
*out = new(CustomResourceSubresources)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.AdditionalPrinterColumns != nil {
|
||||
in, out := &in.AdditionalPrinterColumns, &out.AdditionalPrinterColumns
|
||||
*out = make([]CustomResourceColumnDefinition, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.SelectableFields != nil {
|
||||
in, out := &in.SelectableFields, &out.SelectableFields
|
||||
*out = make([]SelectableField, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomResourceDefinitionVersion.
|
||||
func (in *CustomResourceDefinitionVersion) DeepCopy() *CustomResourceDefinitionVersion {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(CustomResourceDefinitionVersion)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *CustomResourceSubresourceScale) DeepCopyInto(out *CustomResourceSubresourceScale) {
|
||||
*out = *in
|
||||
if in.LabelSelectorPath != nil {
|
||||
in, out := &in.LabelSelectorPath, &out.LabelSelectorPath
|
||||
*out = new(string)
|
||||
**out = **in
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomResourceSubresourceScale.
|
||||
func (in *CustomResourceSubresourceScale) DeepCopy() *CustomResourceSubresourceScale {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(CustomResourceSubresourceScale)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *CustomResourceSubresourceStatus) DeepCopyInto(out *CustomResourceSubresourceStatus) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomResourceSubresourceStatus.
|
||||
func (in *CustomResourceSubresourceStatus) DeepCopy() *CustomResourceSubresourceStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(CustomResourceSubresourceStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *CustomResourceSubresources) DeepCopyInto(out *CustomResourceSubresources) {
|
||||
*out = *in
|
||||
if in.Status != nil {
|
||||
in, out := &in.Status, &out.Status
|
||||
*out = new(CustomResourceSubresourceStatus)
|
||||
**out = **in
|
||||
}
|
||||
if in.Scale != nil {
|
||||
in, out := &in.Scale, &out.Scale
|
||||
*out = new(CustomResourceSubresourceScale)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomResourceSubresources.
|
||||
func (in *CustomResourceSubresources) DeepCopy() *CustomResourceSubresources {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(CustomResourceSubresources)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *CustomResourceValidation) DeepCopyInto(out *CustomResourceValidation) {
|
||||
*out = *in
|
||||
if in.OpenAPIV3Schema != nil {
|
||||
in, out := &in.OpenAPIV3Schema, &out.OpenAPIV3Schema
|
||||
*out = (*in).DeepCopy()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomResourceValidation.
|
||||
func (in *CustomResourceValidation) DeepCopy() *CustomResourceValidation {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(CustomResourceValidation)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ExternalDocumentation) DeepCopyInto(out *ExternalDocumentation) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ExternalDocumentation.
|
||||
func (in *ExternalDocumentation) DeepCopy() *ExternalDocumentation {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ExternalDocumentation)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in JSONSchemaDefinitions) DeepCopyInto(out *JSONSchemaDefinitions) {
|
||||
{
|
||||
in := &in
|
||||
*out = make(JSONSchemaDefinitions, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = *val.DeepCopy()
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JSONSchemaDefinitions.
|
||||
func (in JSONSchemaDefinitions) DeepCopy() JSONSchemaDefinitions {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(JSONSchemaDefinitions)
|
||||
in.DeepCopyInto(out)
|
||||
return *out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in JSONSchemaDependencies) DeepCopyInto(out *JSONSchemaDependencies) {
|
||||
{
|
||||
in := &in
|
||||
*out = make(JSONSchemaDependencies, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = *val.DeepCopy()
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JSONSchemaDependencies.
|
||||
func (in JSONSchemaDependencies) DeepCopy() JSONSchemaDependencies {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(JSONSchemaDependencies)
|
||||
in.DeepCopyInto(out)
|
||||
return *out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *JSONSchemaProps) DeepCopyInto(out *JSONSchemaProps) {
|
||||
clone := in.DeepCopy()
|
||||
*out = *clone
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *JSONSchemaPropsOrArray) DeepCopyInto(out *JSONSchemaPropsOrArray) {
|
||||
*out = *in
|
||||
if in.Schema != nil {
|
||||
in, out := &in.Schema, &out.Schema
|
||||
*out = (*in).DeepCopy()
|
||||
}
|
||||
if in.JSONSchemas != nil {
|
||||
in, out := &in.JSONSchemas, &out.JSONSchemas
|
||||
*out = make([]JSONSchemaProps, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JSONSchemaPropsOrArray.
|
||||
func (in *JSONSchemaPropsOrArray) DeepCopy() *JSONSchemaPropsOrArray {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(JSONSchemaPropsOrArray)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *JSONSchemaPropsOrBool) DeepCopyInto(out *JSONSchemaPropsOrBool) {
|
||||
*out = *in
|
||||
if in.Schema != nil {
|
||||
in, out := &in.Schema, &out.Schema
|
||||
*out = (*in).DeepCopy()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JSONSchemaPropsOrBool.
|
||||
func (in *JSONSchemaPropsOrBool) DeepCopy() *JSONSchemaPropsOrBool {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(JSONSchemaPropsOrBool)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *JSONSchemaPropsOrStringArray) DeepCopyInto(out *JSONSchemaPropsOrStringArray) {
|
||||
*out = *in
|
||||
if in.Schema != nil {
|
||||
in, out := &in.Schema, &out.Schema
|
||||
*out = (*in).DeepCopy()
|
||||
}
|
||||
if in.Property != nil {
|
||||
in, out := &in.Property, &out.Property
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JSONSchemaPropsOrStringArray.
|
||||
func (in *JSONSchemaPropsOrStringArray) DeepCopy() *JSONSchemaPropsOrStringArray {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(JSONSchemaPropsOrStringArray)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *SelectableField) DeepCopyInto(out *SelectableField) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SelectableField.
|
||||
func (in *SelectableField) DeepCopy() *SelectableField {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(SelectableField)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ServiceReference) DeepCopyInto(out *ServiceReference) {
|
||||
*out = *in
|
||||
if in.Path != nil {
|
||||
in, out := &in.Path, &out.Path
|
||||
*out = new(string)
|
||||
**out = **in
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceReference.
|
||||
func (in *ServiceReference) DeepCopy() *ServiceReference {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ServiceReference)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ValidationRule) DeepCopyInto(out *ValidationRule) {
|
||||
*out = *in
|
||||
if in.Reason != nil {
|
||||
in, out := &in.Reason, &out.Reason
|
||||
*out = new(FieldValueErrorReason)
|
||||
**out = **in
|
||||
}
|
||||
if in.OptionalOldSelf != nil {
|
||||
in, out := &in.OptionalOldSelf, &out.OptionalOldSelf
|
||||
*out = new(bool)
|
||||
**out = **in
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ValidationRule.
|
||||
func (in *ValidationRule) DeepCopy() *ValidationRule {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ValidationRule)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in ValidationRules) DeepCopyInto(out *ValidationRules) {
|
||||
{
|
||||
in := &in
|
||||
*out = make(ValidationRules, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ValidationRules.
|
||||
func (in ValidationRules) DeepCopy() ValidationRules {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ValidationRules)
|
||||
in.DeepCopyInto(out)
|
||||
return *out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *WebhookClientConfig) DeepCopyInto(out *WebhookClientConfig) {
|
||||
*out = *in
|
||||
if in.URL != nil {
|
||||
in, out := &in.URL, &out.URL
|
||||
*out = new(string)
|
||||
**out = **in
|
||||
}
|
||||
if in.Service != nil {
|
||||
in, out := &in.Service, &out.Service
|
||||
*out = new(ServiceReference)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.CABundle != nil {
|
||||
in, out := &in.CABundle, &out.CABundle
|
||||
*out = make([]byte, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WebhookClientConfig.
|
||||
func (in *WebhookClientConfig) DeepCopy() *WebhookClientConfig {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(WebhookClientConfig)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
68
vendor/k8s.io/apimachinery/pkg/api/validation/path/name.go
generated
vendored
68
vendor/k8s.io/apimachinery/pkg/api/validation/path/name.go
generated
vendored
@ -1,68 +0,0 @@
|
||||
/*
|
||||
Copyright 2015 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 path
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// NameMayNotBe specifies strings that cannot be used as names specified as path segments (like the REST API or etcd store)
|
||||
var NameMayNotBe = []string{".", ".."}
|
||||
|
||||
// NameMayNotContain specifies substrings that cannot be used in names specified as path segments (like the REST API or etcd store)
|
||||
var NameMayNotContain = []string{"/", "%"}
|
||||
|
||||
// IsValidPathSegmentName validates the name can be safely encoded as a path segment
|
||||
func IsValidPathSegmentName(name string) []string {
|
||||
for _, illegalName := range NameMayNotBe {
|
||||
if name == illegalName {
|
||||
return []string{fmt.Sprintf(`may not be '%s'`, illegalName)}
|
||||
}
|
||||
}
|
||||
|
||||
var errors []string
|
||||
for _, illegalContent := range NameMayNotContain {
|
||||
if strings.Contains(name, illegalContent) {
|
||||
errors = append(errors, fmt.Sprintf(`may not contain '%s'`, illegalContent))
|
||||
}
|
||||
}
|
||||
|
||||
return errors
|
||||
}
|
||||
|
||||
// IsValidPathSegmentPrefix validates the name can be used as a prefix for a name which will be encoded as a path segment
|
||||
// It does not check for exact matches with disallowed names, since an arbitrary suffix might make the name valid
|
||||
func IsValidPathSegmentPrefix(name string) []string {
|
||||
var errors []string
|
||||
for _, illegalContent := range NameMayNotContain {
|
||||
if strings.Contains(name, illegalContent) {
|
||||
errors = append(errors, fmt.Sprintf(`may not contain '%s'`, illegalContent))
|
||||
}
|
||||
}
|
||||
|
||||
return errors
|
||||
}
|
||||
|
||||
// ValidatePathSegmentName validates the name can be safely encoded as a path segment
|
||||
func ValidatePathSegmentName(name string, prefix bool) []string {
|
||||
if prefix {
|
||||
return IsValidPathSegmentPrefix(name)
|
||||
}
|
||||
|
||||
return IsValidPathSegmentName(name)
|
||||
}
|
33
vendor/k8s.io/apimachinery/pkg/apis/meta/v1beta1/validation/validation.go
generated
vendored
33
vendor/k8s.io/apimachinery/pkg/apis/meta/v1beta1/validation/validation.go
generated
vendored
@ -1,33 +0,0 @@
|
||||
/*
|
||||
Copyright 2018 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 validation
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
)
|
||||
|
||||
// ValidateTableOptions returns any invalid flags on TableOptions.
|
||||
func ValidateTableOptions(opts *metav1.TableOptions) field.ErrorList {
|
||||
var allErrs field.ErrorList
|
||||
switch opts.IncludeObject {
|
||||
case metav1.IncludeMetadata, metav1.IncludeNone, metav1.IncludeObject, "":
|
||||
default:
|
||||
allErrs = append(allErrs, field.Invalid(field.NewPath("includeObject"), opts.IncludeObject, "must be 'Metadata', 'Object', 'None', or empty"))
|
||||
}
|
||||
return allErrs
|
||||
}
|
19
vendor/k8s.io/apimachinery/pkg/util/httpstream/doc.go
generated
vendored
19
vendor/k8s.io/apimachinery/pkg/util/httpstream/doc.go
generated
vendored
@ -1,19 +0,0 @@
|
||||
/*
|
||||
Copyright 2015 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 httpstream adds multiplexed streaming support to HTTP requests and
|
||||
// responses via connection upgrades.
|
||||
package httpstream // import "k8s.io/apimachinery/pkg/util/httpstream"
|
189
vendor/k8s.io/apimachinery/pkg/util/httpstream/httpstream.go
generated
vendored
189
vendor/k8s.io/apimachinery/pkg/util/httpstream/httpstream.go
generated
vendored
@ -1,189 +0,0 @@
|
||||
/*
|
||||
Copyright 2015 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 httpstream
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
HeaderConnection = "Connection"
|
||||
HeaderUpgrade = "Upgrade"
|
||||
HeaderProtocolVersion = "X-Stream-Protocol-Version"
|
||||
HeaderAcceptedProtocolVersions = "X-Accepted-Stream-Protocol-Versions"
|
||||
)
|
||||
|
||||
// NewStreamHandler defines a function that is called when a new Stream is
|
||||
// received. If no error is returned, the Stream is accepted; otherwise,
|
||||
// the stream is rejected. After the reply frame has been sent, replySent is closed.
|
||||
type NewStreamHandler func(stream Stream, replySent <-chan struct{}) error
|
||||
|
||||
// NoOpNewStreamHandler is a stream handler that accepts a new stream and
|
||||
// performs no other logic.
|
||||
func NoOpNewStreamHandler(stream Stream, replySent <-chan struct{}) error { return nil }
|
||||
|
||||
// Dialer knows how to open a streaming connection to a server.
|
||||
type Dialer interface {
|
||||
|
||||
// Dial opens a streaming connection to a server using one of the protocols
|
||||
// specified (in order of most preferred to least preferred).
|
||||
Dial(protocols ...string) (Connection, string, error)
|
||||
}
|
||||
|
||||
// UpgradeRoundTripper is a type of http.RoundTripper that is able to upgrade
|
||||
// HTTP requests to support multiplexed bidirectional streams. After RoundTrip()
|
||||
// is invoked, if the upgrade is successful, clients may retrieve the upgraded
|
||||
// connection by calling UpgradeRoundTripper.Connection().
|
||||
type UpgradeRoundTripper interface {
|
||||
http.RoundTripper
|
||||
// NewConnection validates the response and creates a new Connection.
|
||||
NewConnection(resp *http.Response) (Connection, error)
|
||||
}
|
||||
|
||||
// ResponseUpgrader knows how to upgrade HTTP requests and responses to
|
||||
// add streaming support to them.
|
||||
type ResponseUpgrader interface {
|
||||
// UpgradeResponse upgrades an HTTP response to one that supports multiplexed
|
||||
// streams. newStreamHandler will be called asynchronously whenever the
|
||||
// other end of the upgraded connection creates a new stream.
|
||||
UpgradeResponse(w http.ResponseWriter, req *http.Request, newStreamHandler NewStreamHandler) Connection
|
||||
}
|
||||
|
||||
// Connection represents an upgraded HTTP connection.
|
||||
type Connection interface {
|
||||
// CreateStream creates a new Stream with the supplied headers.
|
||||
CreateStream(headers http.Header) (Stream, error)
|
||||
// Close resets all streams and closes the connection.
|
||||
Close() error
|
||||
// CloseChan returns a channel that is closed when the underlying connection is closed.
|
||||
CloseChan() <-chan bool
|
||||
// SetIdleTimeout sets the amount of time the connection may remain idle before
|
||||
// it is automatically closed.
|
||||
SetIdleTimeout(timeout time.Duration)
|
||||
// RemoveStreams can be used to remove a set of streams from the Connection.
|
||||
RemoveStreams(streams ...Stream)
|
||||
}
|
||||
|
||||
// Stream represents a bidirectional communications channel that is part of an
|
||||
// upgraded connection.
|
||||
type Stream interface {
|
||||
io.ReadWriteCloser
|
||||
// Reset closes both directions of the stream, indicating that neither client
|
||||
// or server can use it any more.
|
||||
Reset() error
|
||||
// Headers returns the headers used to create the stream.
|
||||
Headers() http.Header
|
||||
// Identifier returns the stream's ID.
|
||||
Identifier() uint32
|
||||
}
|
||||
|
||||
// UpgradeFailureError encapsulates the cause for why the streaming
|
||||
// upgrade request failed. Implements error interface.
|
||||
type UpgradeFailureError struct {
|
||||
Cause error
|
||||
}
|
||||
|
||||
func (u *UpgradeFailureError) Error() string {
|
||||
return fmt.Sprintf("unable to upgrade streaming request: %s", u.Cause)
|
||||
}
|
||||
|
||||
// IsUpgradeFailure returns true if the passed error is (or wrapped error contains)
|
||||
// the UpgradeFailureError.
|
||||
func IsUpgradeFailure(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
var upgradeErr *UpgradeFailureError
|
||||
return errors.As(err, &upgradeErr)
|
||||
}
|
||||
|
||||
// isHTTPSProxyError returns true if error is Gorilla/Websockets HTTPS Proxy dial error;
|
||||
// false otherwise (see https://github.com/kubernetes/kubernetes/issues/126134).
|
||||
func IsHTTPSProxyError(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
return strings.Contains(err.Error(), "proxy: unknown scheme: https")
|
||||
}
|
||||
|
||||
// IsUpgradeRequest returns true if the given request is a connection upgrade request
|
||||
func IsUpgradeRequest(req *http.Request) bool {
|
||||
for _, h := range req.Header[http.CanonicalHeaderKey(HeaderConnection)] {
|
||||
if strings.Contains(strings.ToLower(h), strings.ToLower(HeaderUpgrade)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func negotiateProtocol(clientProtocols, serverProtocols []string) string {
|
||||
for i := range clientProtocols {
|
||||
for j := range serverProtocols {
|
||||
if clientProtocols[i] == serverProtocols[j] {
|
||||
return clientProtocols[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func commaSeparatedHeaderValues(header []string) []string {
|
||||
var parsedClientProtocols []string
|
||||
for i := range header {
|
||||
for _, clientProtocol := range strings.Split(header[i], ",") {
|
||||
if proto := strings.Trim(clientProtocol, " "); len(proto) > 0 {
|
||||
parsedClientProtocols = append(parsedClientProtocols, proto)
|
||||
}
|
||||
}
|
||||
}
|
||||
return parsedClientProtocols
|
||||
}
|
||||
|
||||
// Handshake performs a subprotocol negotiation. If the client did request a
|
||||
// subprotocol, Handshake will select the first common value found in
|
||||
// serverProtocols. If a match is found, Handshake adds a response header
|
||||
// indicating the chosen subprotocol. If no match is found, HTTP forbidden is
|
||||
// returned, along with a response header containing the list of protocols the
|
||||
// server can accept.
|
||||
func Handshake(req *http.Request, w http.ResponseWriter, serverProtocols []string) (string, error) {
|
||||
clientProtocols := commaSeparatedHeaderValues(req.Header[http.CanonicalHeaderKey(HeaderProtocolVersion)])
|
||||
if len(clientProtocols) == 0 {
|
||||
return "", fmt.Errorf("unable to upgrade: %s is required", HeaderProtocolVersion)
|
||||
}
|
||||
|
||||
if len(serverProtocols) == 0 {
|
||||
panic(fmt.Errorf("unable to upgrade: serverProtocols is required"))
|
||||
}
|
||||
|
||||
negotiatedProtocol := negotiateProtocol(clientProtocols, serverProtocols)
|
||||
if len(negotiatedProtocol) == 0 {
|
||||
for i := range serverProtocols {
|
||||
w.Header().Add(HeaderAcceptedProtocolVersions, serverProtocols[i])
|
||||
}
|
||||
err := fmt.Errorf("unable to upgrade: unable to negotiate protocol: client supports %v, server accepts %v", clientProtocols, serverProtocols)
|
||||
http.Error(w, err.Error(), http.StatusForbidden)
|
||||
return "", err
|
||||
}
|
||||
|
||||
w.Header().Add(HeaderProtocolVersion, negotiatedProtocol)
|
||||
return negotiatedProtocol, nil
|
||||
}
|
204
vendor/k8s.io/apimachinery/pkg/util/httpstream/spdy/connection.go
generated
vendored
204
vendor/k8s.io/apimachinery/pkg/util/httpstream/spdy/connection.go
generated
vendored
@ -1,204 +0,0 @@
|
||||
/*
|
||||
Copyright 2015 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 spdy
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/moby/spdystream"
|
||||
"k8s.io/apimachinery/pkg/util/httpstream"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
// connection maintains state about a spdystream.Connection and its associated
|
||||
// streams.
|
||||
type connection struct {
|
||||
conn *spdystream.Connection
|
||||
streams map[uint32]httpstream.Stream
|
||||
streamLock sync.Mutex
|
||||
newStreamHandler httpstream.NewStreamHandler
|
||||
ping func() (time.Duration, error)
|
||||
}
|
||||
|
||||
// NewClientConnection creates a new SPDY client connection.
|
||||
func NewClientConnection(conn net.Conn) (httpstream.Connection, error) {
|
||||
return NewClientConnectionWithPings(conn, 0)
|
||||
}
|
||||
|
||||
// NewClientConnectionWithPings creates a new SPDY client connection.
|
||||
//
|
||||
// If pingPeriod is non-zero, a background goroutine will send periodic Ping
|
||||
// frames to the server. Use this to keep idle connections through certain load
|
||||
// balancers alive longer.
|
||||
func NewClientConnectionWithPings(conn net.Conn, pingPeriod time.Duration) (httpstream.Connection, error) {
|
||||
spdyConn, err := spdystream.NewConnection(conn, false)
|
||||
if err != nil {
|
||||
defer conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newConnection(spdyConn, httpstream.NoOpNewStreamHandler, pingPeriod, spdyConn.Ping), nil
|
||||
}
|
||||
|
||||
// NewServerConnection creates a new SPDY server connection. newStreamHandler
|
||||
// will be invoked when the server receives a newly created stream from the
|
||||
// client.
|
||||
func NewServerConnection(conn net.Conn, newStreamHandler httpstream.NewStreamHandler) (httpstream.Connection, error) {
|
||||
return NewServerConnectionWithPings(conn, newStreamHandler, 0)
|
||||
}
|
||||
|
||||
// NewServerConnectionWithPings creates a new SPDY server connection.
|
||||
// newStreamHandler will be invoked when the server receives a newly created
|
||||
// stream from the client.
|
||||
//
|
||||
// If pingPeriod is non-zero, a background goroutine will send periodic Ping
|
||||
// frames to the server. Use this to keep idle connections through certain load
|
||||
// balancers alive longer.
|
||||
func NewServerConnectionWithPings(conn net.Conn, newStreamHandler httpstream.NewStreamHandler, pingPeriod time.Duration) (httpstream.Connection, error) {
|
||||
spdyConn, err := spdystream.NewConnection(conn, true)
|
||||
if err != nil {
|
||||
defer conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newConnection(spdyConn, newStreamHandler, pingPeriod, spdyConn.Ping), nil
|
||||
}
|
||||
|
||||
// newConnection returns a new connection wrapping conn. newStreamHandler
|
||||
// will be invoked when the server receives a newly created stream from the
|
||||
// client.
|
||||
func newConnection(conn *spdystream.Connection, newStreamHandler httpstream.NewStreamHandler, pingPeriod time.Duration, pingFn func() (time.Duration, error)) httpstream.Connection {
|
||||
c := &connection{
|
||||
conn: conn,
|
||||
newStreamHandler: newStreamHandler,
|
||||
ping: pingFn,
|
||||
streams: make(map[uint32]httpstream.Stream),
|
||||
}
|
||||
go conn.Serve(c.newSpdyStream)
|
||||
if pingPeriod > 0 && pingFn != nil {
|
||||
go c.sendPings(pingPeriod)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// createStreamResponseTimeout indicates how long to wait for the other side to
|
||||
// acknowledge the new stream before timing out.
|
||||
const createStreamResponseTimeout = 30 * time.Second
|
||||
|
||||
// Close first sends a reset for all of the connection's streams, and then
|
||||
// closes the underlying spdystream.Connection.
|
||||
func (c *connection) Close() error {
|
||||
c.streamLock.Lock()
|
||||
for _, s := range c.streams {
|
||||
// calling Reset instead of Close ensures that all streams are fully torn down
|
||||
s.Reset()
|
||||
}
|
||||
c.streams = make(map[uint32]httpstream.Stream, 0)
|
||||
c.streamLock.Unlock()
|
||||
|
||||
// now that all streams are fully torn down, it's safe to call close on the underlying connection,
|
||||
// which should be able to terminate immediately at this point, instead of waiting for any
|
||||
// remaining graceful stream termination.
|
||||
return c.conn.Close()
|
||||
}
|
||||
|
||||
// RemoveStreams can be used to removes a set of streams from the Connection.
|
||||
func (c *connection) RemoveStreams(streams ...httpstream.Stream) {
|
||||
c.streamLock.Lock()
|
||||
for _, stream := range streams {
|
||||
// It may be possible that the provided stream is nil if timed out.
|
||||
if stream != nil {
|
||||
delete(c.streams, stream.Identifier())
|
||||
}
|
||||
}
|
||||
c.streamLock.Unlock()
|
||||
}
|
||||
|
||||
// CreateStream creates a new stream with the specified headers and registers
|
||||
// it with the connection.
|
||||
func (c *connection) CreateStream(headers http.Header) (httpstream.Stream, error) {
|
||||
stream, err := c.conn.CreateStream(headers, nil, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = stream.WaitTimeout(createStreamResponseTimeout); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.registerStream(stream)
|
||||
return stream, nil
|
||||
}
|
||||
|
||||
// registerStream adds the stream s to the connection's list of streams that
|
||||
// it owns.
|
||||
func (c *connection) registerStream(s httpstream.Stream) {
|
||||
c.streamLock.Lock()
|
||||
c.streams[s.Identifier()] = s
|
||||
c.streamLock.Unlock()
|
||||
}
|
||||
|
||||
// CloseChan returns a channel that, when closed, indicates that the underlying
|
||||
// spdystream.Connection has been closed.
|
||||
func (c *connection) CloseChan() <-chan bool {
|
||||
return c.conn.CloseChan()
|
||||
}
|
||||
|
||||
// newSpdyStream is the internal new stream handler used by spdystream.Connection.Serve.
|
||||
// It calls connection's newStreamHandler, giving it the opportunity to accept or reject
|
||||
// the stream. If newStreamHandler returns an error, the stream is rejected. If not, the
|
||||
// stream is accepted and registered with the connection.
|
||||
func (c *connection) newSpdyStream(stream *spdystream.Stream) {
|
||||
replySent := make(chan struct{})
|
||||
err := c.newStreamHandler(stream, replySent)
|
||||
rejectStream := (err != nil)
|
||||
if rejectStream {
|
||||
klog.Warningf("Stream rejected: %v", err)
|
||||
stream.Reset()
|
||||
return
|
||||
}
|
||||
|
||||
c.registerStream(stream)
|
||||
stream.SendReply(http.Header{}, rejectStream)
|
||||
close(replySent)
|
||||
}
|
||||
|
||||
// SetIdleTimeout sets the amount of time the connection may remain idle before
|
||||
// it is automatically closed.
|
||||
func (c *connection) SetIdleTimeout(timeout time.Duration) {
|
||||
c.conn.SetIdleTimeout(timeout)
|
||||
}
|
||||
|
||||
func (c *connection) sendPings(period time.Duration) {
|
||||
t := time.NewTicker(period)
|
||||
defer t.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-c.conn.CloseChan():
|
||||
return
|
||||
case <-t.C:
|
||||
}
|
||||
if _, err := c.ping(); err != nil {
|
||||
klog.V(3).Infof("SPDY Ping failed: %v", err)
|
||||
// Continue, in case this is a transient failure.
|
||||
// c.conn.CloseChan above will tell us when the connection is
|
||||
// actually closed.
|
||||
}
|
||||
}
|
||||
}
|
399
vendor/k8s.io/apimachinery/pkg/util/httpstream/spdy/roundtripper.go
generated
vendored
399
vendor/k8s.io/apimachinery/pkg/util/httpstream/spdy/roundtripper.go
generated
vendored
@ -1,399 +0,0 @@
|
||||
/*
|
||||
Copyright 2015 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 spdy
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/proxy"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
"k8s.io/apimachinery/pkg/util/httpstream"
|
||||
utilnet "k8s.io/apimachinery/pkg/util/net"
|
||||
apiproxy "k8s.io/apimachinery/pkg/util/proxy"
|
||||
"k8s.io/apimachinery/third_party/forked/golang/netutil"
|
||||
)
|
||||
|
||||
// SpdyRoundTripper knows how to upgrade an HTTP request to one that supports
|
||||
// multiplexed streams. After RoundTrip() is invoked, Conn will be set
|
||||
// and usable. SpdyRoundTripper implements the UpgradeRoundTripper interface.
|
||||
type SpdyRoundTripper struct {
|
||||
//tlsConfig holds the TLS configuration settings to use when connecting
|
||||
//to the remote server.
|
||||
tlsConfig *tls.Config
|
||||
|
||||
/* TODO according to http://golang.org/pkg/net/http/#RoundTripper, a RoundTripper
|
||||
must be safe for use by multiple concurrent goroutines. If this is absolutely
|
||||
necessary, we could keep a map from http.Request to net.Conn. In practice,
|
||||
a client will create an http.Client, set the transport to a new insteace of
|
||||
SpdyRoundTripper, and use it a single time, so this hopefully won't be an issue.
|
||||
*/
|
||||
// conn is the underlying network connection to the remote server.
|
||||
conn net.Conn
|
||||
|
||||
// Dialer is the dialer used to connect. Used if non-nil.
|
||||
Dialer *net.Dialer
|
||||
|
||||
// proxier knows which proxy to use given a request, defaults to http.ProxyFromEnvironment
|
||||
// Used primarily for mocking the proxy discovery in tests.
|
||||
proxier func(req *http.Request) (*url.URL, error)
|
||||
|
||||
// pingPeriod is a period for sending Ping frames over established
|
||||
// connections.
|
||||
pingPeriod time.Duration
|
||||
|
||||
// upgradeTransport is an optional substitute for dialing if present. This field is
|
||||
// mutually exclusive with the "tlsConfig", "Dialer", and "proxier".
|
||||
upgradeTransport http.RoundTripper
|
||||
}
|
||||
|
||||
var _ utilnet.TLSClientConfigHolder = &SpdyRoundTripper{}
|
||||
var _ httpstream.UpgradeRoundTripper = &SpdyRoundTripper{}
|
||||
var _ utilnet.Dialer = &SpdyRoundTripper{}
|
||||
|
||||
// NewRoundTripper creates a new SpdyRoundTripper that will use the specified
|
||||
// tlsConfig.
|
||||
func NewRoundTripper(tlsConfig *tls.Config) (*SpdyRoundTripper, error) {
|
||||
return NewRoundTripperWithConfig(RoundTripperConfig{
|
||||
TLS: tlsConfig,
|
||||
UpgradeTransport: nil,
|
||||
})
|
||||
}
|
||||
|
||||
// NewRoundTripperWithProxy creates a new SpdyRoundTripper that will use the
|
||||
// specified tlsConfig and proxy func.
|
||||
func NewRoundTripperWithProxy(tlsConfig *tls.Config, proxier func(*http.Request) (*url.URL, error)) (*SpdyRoundTripper, error) {
|
||||
return NewRoundTripperWithConfig(RoundTripperConfig{
|
||||
TLS: tlsConfig,
|
||||
Proxier: proxier,
|
||||
UpgradeTransport: nil,
|
||||
})
|
||||
}
|
||||
|
||||
// NewRoundTripperWithConfig creates a new SpdyRoundTripper with the specified
|
||||
// configuration. Returns an error if the SpdyRoundTripper is misconfigured.
|
||||
func NewRoundTripperWithConfig(cfg RoundTripperConfig) (*SpdyRoundTripper, error) {
|
||||
// Process UpgradeTransport, which is mutually exclusive to TLSConfig and Proxier.
|
||||
if cfg.UpgradeTransport != nil {
|
||||
if cfg.TLS != nil || cfg.Proxier != nil {
|
||||
return nil, fmt.Errorf("SpdyRoundTripper: UpgradeTransport is mutually exclusive to TLSConfig or Proxier")
|
||||
}
|
||||
tlsConfig, err := utilnet.TLSClientConfig(cfg.UpgradeTransport)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("SpdyRoundTripper: Unable to retrieve TLSConfig from UpgradeTransport: %v", err)
|
||||
}
|
||||
cfg.TLS = tlsConfig
|
||||
}
|
||||
if cfg.Proxier == nil {
|
||||
cfg.Proxier = utilnet.NewProxierWithNoProxyCIDR(http.ProxyFromEnvironment)
|
||||
}
|
||||
return &SpdyRoundTripper{
|
||||
tlsConfig: cfg.TLS,
|
||||
proxier: cfg.Proxier,
|
||||
pingPeriod: cfg.PingPeriod,
|
||||
upgradeTransport: cfg.UpgradeTransport,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// RoundTripperConfig is a set of options for an SpdyRoundTripper.
|
||||
type RoundTripperConfig struct {
|
||||
// TLS configuration used by the round tripper if UpgradeTransport not present.
|
||||
TLS *tls.Config
|
||||
// Proxier is a proxy function invoked on each request. Optional.
|
||||
Proxier func(*http.Request) (*url.URL, error)
|
||||
// PingPeriod is a period for sending SPDY Pings on the connection.
|
||||
// Optional.
|
||||
PingPeriod time.Duration
|
||||
// UpgradeTransport is a subtitute transport used for dialing. If set,
|
||||
// this field will be used instead of "TLS" and "Proxier" for connection creation.
|
||||
// Optional.
|
||||
UpgradeTransport http.RoundTripper
|
||||
}
|
||||
|
||||
// TLSClientConfig implements pkg/util/net.TLSClientConfigHolder for proper TLS checking during
|
||||
// proxying with a spdy roundtripper.
|
||||
func (s *SpdyRoundTripper) TLSClientConfig() *tls.Config {
|
||||
return s.tlsConfig
|
||||
}
|
||||
|
||||
// Dial implements k8s.io/apimachinery/pkg/util/net.Dialer.
|
||||
func (s *SpdyRoundTripper) Dial(req *http.Request) (net.Conn, error) {
|
||||
var conn net.Conn
|
||||
var err error
|
||||
if s.upgradeTransport != nil {
|
||||
conn, err = apiproxy.DialURL(req.Context(), req.URL, s.upgradeTransport)
|
||||
} else {
|
||||
conn, err = s.dial(req)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := req.Write(conn); err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
// dial dials the host specified by req, using TLS if appropriate, optionally
|
||||
// using a proxy server if one is configured via environment variables.
|
||||
func (s *SpdyRoundTripper) dial(req *http.Request) (net.Conn, error) {
|
||||
proxyURL, err := s.proxier(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if proxyURL == nil {
|
||||
return s.dialWithoutProxy(req.Context(), req.URL)
|
||||
}
|
||||
|
||||
switch proxyURL.Scheme {
|
||||
case "socks5":
|
||||
return s.dialWithSocks5Proxy(req, proxyURL)
|
||||
case "https", "http", "":
|
||||
return s.dialWithHttpProxy(req, proxyURL)
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("proxy URL scheme not supported: %s", proxyURL.Scheme)
|
||||
}
|
||||
|
||||
// dialWithHttpProxy dials the host specified by url through an http or an https proxy.
|
||||
func (s *SpdyRoundTripper) dialWithHttpProxy(req *http.Request, proxyURL *url.URL) (net.Conn, error) {
|
||||
// ensure we use a canonical host with proxyReq
|
||||
targetHost := netutil.CanonicalAddr(req.URL)
|
||||
|
||||
// proxying logic adapted from http://blog.h6t.eu/post/74098062923/golang-websocket-with-http-proxy-support
|
||||
proxyReq := http.Request{
|
||||
Method: "CONNECT",
|
||||
URL: &url.URL{},
|
||||
Host: targetHost,
|
||||
}
|
||||
|
||||
proxyReq = *proxyReq.WithContext(req.Context())
|
||||
|
||||
if pa := s.proxyAuth(proxyURL); pa != "" {
|
||||
proxyReq.Header = http.Header{}
|
||||
proxyReq.Header.Set("Proxy-Authorization", pa)
|
||||
}
|
||||
|
||||
proxyDialConn, err := s.dialWithoutProxy(proxyReq.Context(), proxyURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//nolint:staticcheck // SA1019 ignore deprecated httputil.NewProxyClientConn
|
||||
proxyClientConn := httputil.NewProxyClientConn(proxyDialConn, nil)
|
||||
response, err := proxyClientConn.Do(&proxyReq)
|
||||
//nolint:staticcheck // SA1019 ignore deprecated httputil.ErrPersistEOF: it might be
|
||||
// returned from the invocation of proxyClientConn.Do
|
||||
if err != nil && err != httputil.ErrPersistEOF {
|
||||
return nil, err
|
||||
}
|
||||
if response != nil && response.StatusCode >= 300 || response.StatusCode < 200 {
|
||||
return nil, fmt.Errorf("CONNECT request to %s returned response: %s", proxyURL.Redacted(), response.Status)
|
||||
}
|
||||
|
||||
rwc, _ := proxyClientConn.Hijack()
|
||||
|
||||
if req.URL.Scheme == "https" {
|
||||
return s.tlsConn(proxyReq.Context(), rwc, targetHost)
|
||||
}
|
||||
return rwc, nil
|
||||
}
|
||||
|
||||
// dialWithSocks5Proxy dials the host specified by url through a socks5 proxy.
|
||||
func (s *SpdyRoundTripper) dialWithSocks5Proxy(req *http.Request, proxyURL *url.URL) (net.Conn, error) {
|
||||
// ensure we use a canonical host with proxyReq
|
||||
targetHost := netutil.CanonicalAddr(req.URL)
|
||||
proxyDialAddr := netutil.CanonicalAddr(proxyURL)
|
||||
|
||||
var auth *proxy.Auth
|
||||
if proxyURL.User != nil {
|
||||
pass, _ := proxyURL.User.Password()
|
||||
auth = &proxy.Auth{
|
||||
User: proxyURL.User.Username(),
|
||||
Password: pass,
|
||||
}
|
||||
}
|
||||
|
||||
dialer := s.Dialer
|
||||
if dialer == nil {
|
||||
dialer = &net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
}
|
||||
}
|
||||
|
||||
proxyDialer, err := proxy.SOCKS5("tcp", proxyDialAddr, auth, dialer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// According to the implementation of proxy.SOCKS5, the type assertion will always succeed
|
||||
contextDialer, ok := proxyDialer.(proxy.ContextDialer)
|
||||
if !ok {
|
||||
return nil, errors.New("SOCKS5 Dialer must implement ContextDialer")
|
||||
}
|
||||
|
||||
proxyDialConn, err := contextDialer.DialContext(req.Context(), "tcp", targetHost)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if req.URL.Scheme == "https" {
|
||||
return s.tlsConn(req.Context(), proxyDialConn, targetHost)
|
||||
}
|
||||
return proxyDialConn, nil
|
||||
}
|
||||
|
||||
// tlsConn returns a TLS client side connection using rwc as the underlying transport.
|
||||
func (s *SpdyRoundTripper) tlsConn(ctx context.Context, rwc net.Conn, targetHost string) (net.Conn, error) {
|
||||
|
||||
host, _, err := net.SplitHostPort(targetHost)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tlsConfig := s.tlsConfig
|
||||
switch {
|
||||
case tlsConfig == nil:
|
||||
tlsConfig = &tls.Config{ServerName: host}
|
||||
case len(tlsConfig.ServerName) == 0:
|
||||
tlsConfig = tlsConfig.Clone()
|
||||
tlsConfig.ServerName = host
|
||||
}
|
||||
|
||||
tlsConn := tls.Client(rwc, tlsConfig)
|
||||
|
||||
if err := tlsConn.HandshakeContext(ctx); err != nil {
|
||||
tlsConn.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return tlsConn, nil
|
||||
}
|
||||
|
||||
// dialWithoutProxy dials the host specified by url, using TLS if appropriate.
|
||||
func (s *SpdyRoundTripper) dialWithoutProxy(ctx context.Context, url *url.URL) (net.Conn, error) {
|
||||
dialAddr := netutil.CanonicalAddr(url)
|
||||
dialer := s.Dialer
|
||||
if dialer == nil {
|
||||
dialer = &net.Dialer{}
|
||||
}
|
||||
|
||||
if url.Scheme == "http" {
|
||||
return dialer.DialContext(ctx, "tcp", dialAddr)
|
||||
}
|
||||
|
||||
tlsDialer := tls.Dialer{
|
||||
NetDialer: dialer,
|
||||
Config: s.tlsConfig,
|
||||
}
|
||||
return tlsDialer.DialContext(ctx, "tcp", dialAddr)
|
||||
}
|
||||
|
||||
// proxyAuth returns, for a given proxy URL, the value to be used for the Proxy-Authorization header
|
||||
func (s *SpdyRoundTripper) proxyAuth(proxyURL *url.URL) string {
|
||||
if proxyURL == nil || proxyURL.User == nil {
|
||||
return ""
|
||||
}
|
||||
username := proxyURL.User.Username()
|
||||
password, _ := proxyURL.User.Password()
|
||||
auth := username + ":" + password
|
||||
return "Basic " + base64.StdEncoding.EncodeToString([]byte(auth))
|
||||
}
|
||||
|
||||
// RoundTrip executes the Request and upgrades it. After a successful upgrade,
|
||||
// clients may call SpdyRoundTripper.Connection() to retrieve the upgraded
|
||||
// connection.
|
||||
func (s *SpdyRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
req = utilnet.CloneRequest(req)
|
||||
req.Header.Add(httpstream.HeaderConnection, httpstream.HeaderUpgrade)
|
||||
req.Header.Add(httpstream.HeaderUpgrade, HeaderSpdy31)
|
||||
|
||||
conn, err := s.Dial(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
responseReader := bufio.NewReader(conn)
|
||||
|
||||
resp, err := http.ReadResponse(responseReader, nil)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.conn = conn
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// NewConnection validates the upgrade response, creating and returning a new
|
||||
// httpstream.Connection if there were no errors.
|
||||
func (s *SpdyRoundTripper) NewConnection(resp *http.Response) (httpstream.Connection, error) {
|
||||
connectionHeader := strings.ToLower(resp.Header.Get(httpstream.HeaderConnection))
|
||||
upgradeHeader := strings.ToLower(resp.Header.Get(httpstream.HeaderUpgrade))
|
||||
if (resp.StatusCode != http.StatusSwitchingProtocols) || !strings.Contains(connectionHeader, strings.ToLower(httpstream.HeaderUpgrade)) || !strings.Contains(upgradeHeader, strings.ToLower(HeaderSpdy31)) {
|
||||
defer resp.Body.Close()
|
||||
responseError := ""
|
||||
responseErrorBytes, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
responseError = "unable to read error from server response"
|
||||
} else {
|
||||
// TODO: I don't belong here, I should be abstracted from this class
|
||||
if obj, _, err := statusCodecs.UniversalDecoder().Decode(responseErrorBytes, nil, &metav1.Status{}); err == nil {
|
||||
if status, ok := obj.(*metav1.Status); ok {
|
||||
return nil, &apierrors.StatusError{ErrStatus: *status}
|
||||
}
|
||||
}
|
||||
responseError = string(responseErrorBytes)
|
||||
responseError = strings.TrimSpace(responseError)
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("unable to upgrade connection: %s", responseError)
|
||||
}
|
||||
|
||||
return NewClientConnectionWithPings(s.conn, s.pingPeriod)
|
||||
}
|
||||
|
||||
// statusScheme is private scheme for the decoding here until someone fixes the TODO in NewConnection
|
||||
var statusScheme = runtime.NewScheme()
|
||||
|
||||
// ParameterCodec knows about query parameters used with the meta v1 API spec.
|
||||
var statusCodecs = serializer.NewCodecFactory(statusScheme)
|
||||
|
||||
func init() {
|
||||
statusScheme.AddUnversionedTypes(metav1.SchemeGroupVersion,
|
||||
&metav1.Status{},
|
||||
)
|
||||
}
|
120
vendor/k8s.io/apimachinery/pkg/util/httpstream/spdy/upgrade.go
generated
vendored
120
vendor/k8s.io/apimachinery/pkg/util/httpstream/spdy/upgrade.go
generated
vendored
@ -1,120 +0,0 @@
|
||||
/*
|
||||
Copyright 2015 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 spdy
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/httpstream"
|
||||
"k8s.io/apimachinery/pkg/util/runtime"
|
||||
)
|
||||
|
||||
const HeaderSpdy31 = "SPDY/3.1"
|
||||
|
||||
// responseUpgrader knows how to upgrade HTTP responses. It
|
||||
// implements the httpstream.ResponseUpgrader interface.
|
||||
type responseUpgrader struct {
|
||||
pingPeriod time.Duration
|
||||
}
|
||||
|
||||
// connWrapper is used to wrap a hijacked connection and its bufio.Reader. All
|
||||
// calls will be handled directly by the underlying net.Conn with the exception
|
||||
// of Read and Close calls, which will consider data in the bufio.Reader. This
|
||||
// ensures that data already inside the used bufio.Reader instance is also
|
||||
// read.
|
||||
type connWrapper struct {
|
||||
net.Conn
|
||||
closed int32
|
||||
bufReader *bufio.Reader
|
||||
}
|
||||
|
||||
func (w *connWrapper) Read(b []byte) (n int, err error) {
|
||||
if atomic.LoadInt32(&w.closed) == 1 {
|
||||
return 0, io.EOF
|
||||
}
|
||||
return w.bufReader.Read(b)
|
||||
}
|
||||
|
||||
func (w *connWrapper) Close() error {
|
||||
err := w.Conn.Close()
|
||||
atomic.StoreInt32(&w.closed, 1)
|
||||
return err
|
||||
}
|
||||
|
||||
// NewResponseUpgrader returns a new httpstream.ResponseUpgrader that is
|
||||
// capable of upgrading HTTP responses using SPDY/3.1 via the
|
||||
// spdystream package.
|
||||
func NewResponseUpgrader() httpstream.ResponseUpgrader {
|
||||
return NewResponseUpgraderWithPings(0)
|
||||
}
|
||||
|
||||
// NewResponseUpgraderWithPings returns a new httpstream.ResponseUpgrader that
|
||||
// is capable of upgrading HTTP responses using SPDY/3.1 via the spdystream
|
||||
// package.
|
||||
//
|
||||
// If pingPeriod is non-zero, for each incoming connection a background
|
||||
// goroutine will send periodic Ping frames to the server. Use this to keep
|
||||
// idle connections through certain load balancers alive longer.
|
||||
func NewResponseUpgraderWithPings(pingPeriod time.Duration) httpstream.ResponseUpgrader {
|
||||
return responseUpgrader{pingPeriod: pingPeriod}
|
||||
}
|
||||
|
||||
// UpgradeResponse upgrades an HTTP response to one that supports multiplexed
|
||||
// streams. newStreamHandler will be called synchronously whenever the
|
||||
// other end of the upgraded connection creates a new stream.
|
||||
func (u responseUpgrader) UpgradeResponse(w http.ResponseWriter, req *http.Request, newStreamHandler httpstream.NewStreamHandler) httpstream.Connection {
|
||||
connectionHeader := strings.ToLower(req.Header.Get(httpstream.HeaderConnection))
|
||||
upgradeHeader := strings.ToLower(req.Header.Get(httpstream.HeaderUpgrade))
|
||||
if !strings.Contains(connectionHeader, strings.ToLower(httpstream.HeaderUpgrade)) || !strings.Contains(upgradeHeader, strings.ToLower(HeaderSpdy31)) {
|
||||
errorMsg := fmt.Sprintf("unable to upgrade: missing upgrade headers in request: %#v", req.Header)
|
||||
http.Error(w, errorMsg, http.StatusBadRequest)
|
||||
return nil
|
||||
}
|
||||
|
||||
hijacker, ok := w.(http.Hijacker)
|
||||
if !ok {
|
||||
errorMsg := "unable to upgrade: unable to hijack response"
|
||||
http.Error(w, errorMsg, http.StatusInternalServerError)
|
||||
return nil
|
||||
}
|
||||
|
||||
w.Header().Add(httpstream.HeaderConnection, httpstream.HeaderUpgrade)
|
||||
w.Header().Add(httpstream.HeaderUpgrade, HeaderSpdy31)
|
||||
w.WriteHeader(http.StatusSwitchingProtocols)
|
||||
|
||||
conn, bufrw, err := hijacker.Hijack()
|
||||
if err != nil {
|
||||
runtime.HandleError(fmt.Errorf("unable to upgrade: error hijacking response: %v", err))
|
||||
return nil
|
||||
}
|
||||
|
||||
connWithBuf := &connWrapper{Conn: conn, bufReader: bufrw.Reader}
|
||||
spdyConn, err := NewServerConnectionWithPings(connWithBuf, newStreamHandler, u.pingPeriod)
|
||||
if err != nil {
|
||||
runtime.HandleError(fmt.Errorf("unable to upgrade: error creating SPDY server connection: %v", err))
|
||||
return nil
|
||||
}
|
||||
|
||||
return spdyConn
|
||||
}
|
452
vendor/k8s.io/apimachinery/pkg/util/httpstream/wsstream/conn.go
generated
vendored
452
vendor/k8s.io/apimachinery/pkg/util/httpstream/wsstream/conn.go
generated
vendored
@ -1,452 +0,0 @@
|
||||
/*
|
||||
Copyright 2015 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 wsstream
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/websocket"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/httpstream"
|
||||
"k8s.io/apimachinery/pkg/util/portforward"
|
||||
"k8s.io/apimachinery/pkg/util/remotecommand"
|
||||
"k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
const WebSocketProtocolHeader = "Sec-Websocket-Protocol"
|
||||
|
||||
// The Websocket subprotocol "channel.k8s.io" prepends each binary message with a byte indicating
|
||||
// the channel number (zero indexed) the message was sent on. Messages in both directions should
|
||||
// prefix their messages with this channel byte. When used for remote execution, the channel numbers
|
||||
// are by convention defined to match the POSIX file-descriptors assigned to STDIN, STDOUT, and STDERR
|
||||
// (0, 1, and 2). No other conversion is performed on the raw subprotocol - writes are sent as they
|
||||
// are received by the server.
|
||||
//
|
||||
// Example client session:
|
||||
//
|
||||
// CONNECT http://server.com with subprotocol "channel.k8s.io"
|
||||
// WRITE []byte{0, 102, 111, 111, 10} # send "foo\n" on channel 0 (STDIN)
|
||||
// READ []byte{1, 10} # receive "\n" on channel 1 (STDOUT)
|
||||
// CLOSE
|
||||
const ChannelWebSocketProtocol = "channel.k8s.io"
|
||||
|
||||
// The Websocket subprotocol "base64.channel.k8s.io" base64 encodes each message with a character
|
||||
// indicating the channel number (zero indexed) the message was sent on. Messages in both directions
|
||||
// should prefix their messages with this channel char. When used for remote execution, the channel
|
||||
// numbers are by convention defined to match the POSIX file-descriptors assigned to STDIN, STDOUT,
|
||||
// and STDERR ('0', '1', and '2'). The data received on the server is base64 decoded (and must be
|
||||
// be valid) and data written by the server to the client is base64 encoded.
|
||||
//
|
||||
// Example client session:
|
||||
//
|
||||
// CONNECT http://server.com with subprotocol "base64.channel.k8s.io"
|
||||
// WRITE []byte{48, 90, 109, 57, 118, 67, 103, 111, 61} # send "foo\n" (base64: "Zm9vCgo=") on channel '0' (STDIN)
|
||||
// READ []byte{49, 67, 103, 61, 61} # receive "\n" (base64: "Cg==") on channel '1' (STDOUT)
|
||||
// CLOSE
|
||||
const Base64ChannelWebSocketProtocol = "base64.channel.k8s.io"
|
||||
|
||||
type codecType int
|
||||
|
||||
const (
|
||||
rawCodec codecType = iota
|
||||
base64Codec
|
||||
)
|
||||
|
||||
type ChannelType int
|
||||
|
||||
const (
|
||||
IgnoreChannel ChannelType = iota
|
||||
ReadChannel
|
||||
WriteChannel
|
||||
ReadWriteChannel
|
||||
)
|
||||
|
||||
// IsWebSocketRequest returns true if the incoming request contains connection upgrade headers
|
||||
// for WebSockets.
|
||||
func IsWebSocketRequest(req *http.Request) bool {
|
||||
if !strings.EqualFold(req.Header.Get("Upgrade"), "websocket") {
|
||||
return false
|
||||
}
|
||||
return httpstream.IsUpgradeRequest(req)
|
||||
}
|
||||
|
||||
// IsWebSocketRequestWithStreamCloseProtocol returns true if the request contains headers
|
||||
// identifying that it is requesting a websocket upgrade with a remotecommand protocol
|
||||
// version that supports the "CLOSE" signal; false otherwise.
|
||||
func IsWebSocketRequestWithStreamCloseProtocol(req *http.Request) bool {
|
||||
if !IsWebSocketRequest(req) {
|
||||
return false
|
||||
}
|
||||
requestedProtocols := strings.TrimSpace(req.Header.Get(WebSocketProtocolHeader))
|
||||
for _, requestedProtocol := range strings.Split(requestedProtocols, ",") {
|
||||
if protocolSupportsStreamClose(strings.TrimSpace(requestedProtocol)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// IsWebSocketRequestWithTunnelingProtocol returns true if the request contains headers
|
||||
// identifying that it is requesting a websocket upgrade with a tunneling protocol;
|
||||
// false otherwise.
|
||||
func IsWebSocketRequestWithTunnelingProtocol(req *http.Request) bool {
|
||||
if !IsWebSocketRequest(req) {
|
||||
return false
|
||||
}
|
||||
requestedProtocols := strings.TrimSpace(req.Header.Get(WebSocketProtocolHeader))
|
||||
for _, requestedProtocol := range strings.Split(requestedProtocols, ",") {
|
||||
if protocolSupportsWebsocketTunneling(strings.TrimSpace(requestedProtocol)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// IgnoreReceives reads from a WebSocket until it is closed, then returns. If timeout is set, the
|
||||
// read and write deadlines are pushed every time a new message is received.
|
||||
func IgnoreReceives(ws *websocket.Conn, timeout time.Duration) {
|
||||
defer runtime.HandleCrash()
|
||||
var data []byte
|
||||
for {
|
||||
resetTimeout(ws, timeout)
|
||||
if err := websocket.Message.Receive(ws, &data); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// handshake ensures the provided user protocol matches one of the allowed protocols. It returns
|
||||
// no error if no protocol is specified.
|
||||
func handshake(config *websocket.Config, req *http.Request, allowed []string) error {
|
||||
protocols := config.Protocol
|
||||
if len(protocols) == 0 {
|
||||
protocols = []string{""}
|
||||
}
|
||||
|
||||
for _, protocol := range protocols {
|
||||
for _, allow := range allowed {
|
||||
if allow == protocol {
|
||||
config.Protocol = []string{protocol}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("requested protocol(s) are not supported: %v; supports %v", config.Protocol, allowed)
|
||||
}
|
||||
|
||||
// ChannelProtocolConfig describes a websocket subprotocol with channels.
|
||||
type ChannelProtocolConfig struct {
|
||||
Binary bool
|
||||
Channels []ChannelType
|
||||
}
|
||||
|
||||
// NewDefaultChannelProtocols returns a channel protocol map with the
|
||||
// subprotocols "", "channel.k8s.io", "base64.channel.k8s.io" and the given
|
||||
// channels.
|
||||
func NewDefaultChannelProtocols(channels []ChannelType) map[string]ChannelProtocolConfig {
|
||||
return map[string]ChannelProtocolConfig{
|
||||
"": {Binary: true, Channels: channels},
|
||||
ChannelWebSocketProtocol: {Binary: true, Channels: channels},
|
||||
Base64ChannelWebSocketProtocol: {Binary: false, Channels: channels},
|
||||
}
|
||||
}
|
||||
|
||||
// Conn supports sending multiple binary channels over a websocket connection.
|
||||
type Conn struct {
|
||||
protocols map[string]ChannelProtocolConfig
|
||||
selectedProtocol string
|
||||
channels []*websocketChannel
|
||||
codec codecType
|
||||
ready chan struct{}
|
||||
ws *websocket.Conn
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
// NewConn creates a WebSocket connection that supports a set of channels. Channels begin each
|
||||
// web socket message with a single byte indicating the channel number (0-N). 255 is reserved for
|
||||
// future use. The channel types for each channel are passed as an array, supporting the different
|
||||
// duplex modes. Read and Write refer to whether the channel can be used as a Reader or Writer.
|
||||
//
|
||||
// The protocols parameter maps subprotocol names to ChannelProtocols. The empty string subprotocol
|
||||
// name is used if websocket.Config.Protocol is empty.
|
||||
func NewConn(protocols map[string]ChannelProtocolConfig) *Conn {
|
||||
return &Conn{
|
||||
ready: make(chan struct{}),
|
||||
protocols: protocols,
|
||||
}
|
||||
}
|
||||
|
||||
// SetIdleTimeout sets the interval for both reads and writes before timeout. If not specified,
|
||||
// there is no timeout on the connection.
|
||||
func (conn *Conn) SetIdleTimeout(duration time.Duration) {
|
||||
conn.timeout = duration
|
||||
}
|
||||
|
||||
// SetWriteDeadline sets a timeout on writing to the websocket connection. The
|
||||
// passed "duration" identifies how far into the future the write must complete
|
||||
// by before the timeout fires.
|
||||
func (conn *Conn) SetWriteDeadline(duration time.Duration) {
|
||||
conn.ws.SetWriteDeadline(time.Now().Add(duration)) //nolint:errcheck
|
||||
}
|
||||
|
||||
// Open the connection and create channels for reading and writing. It returns
|
||||
// the selected subprotocol, a slice of channels and an error.
|
||||
func (conn *Conn) Open(w http.ResponseWriter, req *http.Request) (string, []io.ReadWriteCloser, error) {
|
||||
// serveHTTPComplete is channel that is closed/selected when "websocket#ServeHTTP" finishes.
|
||||
serveHTTPComplete := make(chan struct{})
|
||||
// Ensure panic in spawned goroutine is propagated into the parent goroutine.
|
||||
panicChan := make(chan any, 1)
|
||||
go func() {
|
||||
// If websocket server returns, propagate panic if necessary. Otherwise,
|
||||
// signal HTTPServe finished by closing "serveHTTPComplete".
|
||||
defer func() {
|
||||
if p := recover(); p != nil {
|
||||
panicChan <- p
|
||||
} else {
|
||||
close(serveHTTPComplete)
|
||||
}
|
||||
}()
|
||||
websocket.Server{Handshake: conn.handshake, Handler: conn.handle}.ServeHTTP(w, req)
|
||||
}()
|
||||
|
||||
// In normal circumstances, "websocket.Server#ServeHTTP" calls "initialize" which closes
|
||||
// "conn.ready" and then blocks until serving is complete.
|
||||
select {
|
||||
case <-conn.ready:
|
||||
klog.V(8).Infof("websocket server initialized--serving")
|
||||
case <-serveHTTPComplete:
|
||||
// websocket server returned before completing initialization; cleanup and return error.
|
||||
conn.closeNonThreadSafe() //nolint:errcheck
|
||||
return "", nil, fmt.Errorf("websocket server finished before becoming ready")
|
||||
case p := <-panicChan:
|
||||
panic(p)
|
||||
}
|
||||
|
||||
rwc := make([]io.ReadWriteCloser, len(conn.channels))
|
||||
for i := range conn.channels {
|
||||
rwc[i] = conn.channels[i]
|
||||
}
|
||||
return conn.selectedProtocol, rwc, nil
|
||||
}
|
||||
|
||||
func (conn *Conn) initialize(ws *websocket.Conn) {
|
||||
negotiated := ws.Config().Protocol
|
||||
conn.selectedProtocol = negotiated[0]
|
||||
p := conn.protocols[conn.selectedProtocol]
|
||||
if p.Binary {
|
||||
conn.codec = rawCodec
|
||||
} else {
|
||||
conn.codec = base64Codec
|
||||
}
|
||||
conn.ws = ws
|
||||
conn.channels = make([]*websocketChannel, len(p.Channels))
|
||||
for i, t := range p.Channels {
|
||||
switch t {
|
||||
case ReadChannel:
|
||||
conn.channels[i] = newWebsocketChannel(conn, byte(i), true, false)
|
||||
case WriteChannel:
|
||||
conn.channels[i] = newWebsocketChannel(conn, byte(i), false, true)
|
||||
case ReadWriteChannel:
|
||||
conn.channels[i] = newWebsocketChannel(conn, byte(i), true, true)
|
||||
case IgnoreChannel:
|
||||
conn.channels[i] = newWebsocketChannel(conn, byte(i), false, false)
|
||||
}
|
||||
}
|
||||
|
||||
close(conn.ready)
|
||||
}
|
||||
|
||||
func (conn *Conn) handshake(config *websocket.Config, req *http.Request) error {
|
||||
supportedProtocols := make([]string, 0, len(conn.protocols))
|
||||
for p := range conn.protocols {
|
||||
supportedProtocols = append(supportedProtocols, p)
|
||||
}
|
||||
return handshake(config, req, supportedProtocols)
|
||||
}
|
||||
|
||||
func (conn *Conn) resetTimeout() {
|
||||
if conn.timeout > 0 {
|
||||
conn.ws.SetDeadline(time.Now().Add(conn.timeout))
|
||||
}
|
||||
}
|
||||
|
||||
// closeNonThreadSafe cleans up by closing streams and the websocket
|
||||
// connection *without* waiting for the "ready" channel.
|
||||
func (conn *Conn) closeNonThreadSafe() error {
|
||||
for _, s := range conn.channels {
|
||||
s.Close()
|
||||
}
|
||||
var err error
|
||||
if conn.ws != nil {
|
||||
err = conn.ws.Close()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Close is only valid after Open has been called
|
||||
func (conn *Conn) Close() error {
|
||||
<-conn.ready
|
||||
return conn.closeNonThreadSafe()
|
||||
}
|
||||
|
||||
// protocolSupportsStreamClose returns true if the passed protocol
|
||||
// supports the stream close signal (currently only V5 remotecommand);
|
||||
// false otherwise.
|
||||
func protocolSupportsStreamClose(protocol string) bool {
|
||||
return protocol == remotecommand.StreamProtocolV5Name
|
||||
}
|
||||
|
||||
// protocolSupportsWebsocketTunneling returns true if the passed protocol
|
||||
// is a tunneled Kubernetes spdy protocol; false otherwise.
|
||||
func protocolSupportsWebsocketTunneling(protocol string) bool {
|
||||
return strings.HasPrefix(protocol, portforward.WebsocketsSPDYTunnelingPrefix) && strings.HasSuffix(protocol, portforward.KubernetesSuffix)
|
||||
}
|
||||
|
||||
// handle implements a websocket handler.
|
||||
func (conn *Conn) handle(ws *websocket.Conn) {
|
||||
conn.initialize(ws)
|
||||
defer conn.Close()
|
||||
supportsStreamClose := protocolSupportsStreamClose(conn.selectedProtocol)
|
||||
|
||||
for {
|
||||
conn.resetTimeout()
|
||||
var data []byte
|
||||
if err := websocket.Message.Receive(ws, &data); err != nil {
|
||||
if err != io.EOF {
|
||||
klog.Errorf("Error on socket receive: %v", err)
|
||||
}
|
||||
break
|
||||
}
|
||||
if len(data) == 0 {
|
||||
continue
|
||||
}
|
||||
if supportsStreamClose && data[0] == remotecommand.StreamClose {
|
||||
if len(data) != 2 {
|
||||
klog.Errorf("Single channel byte should follow stream close signal. Got %d bytes", len(data)-1)
|
||||
break
|
||||
} else {
|
||||
channel := data[1]
|
||||
if int(channel) >= len(conn.channels) {
|
||||
klog.Errorf("Close is targeted for a channel %d that is not valid, possible protocol error", channel)
|
||||
break
|
||||
}
|
||||
klog.V(4).Infof("Received half-close signal from client; close %d stream", channel)
|
||||
conn.channels[channel].Close() // After first Close, other closes are noop.
|
||||
}
|
||||
continue
|
||||
}
|
||||
channel := data[0]
|
||||
if conn.codec == base64Codec {
|
||||
channel = channel - '0'
|
||||
}
|
||||
data = data[1:]
|
||||
if int(channel) >= len(conn.channels) {
|
||||
klog.V(6).Infof("Frame is targeted for a reader %d that is not valid, possible protocol error", channel)
|
||||
continue
|
||||
}
|
||||
if _, err := conn.channels[channel].DataFromSocket(data); err != nil {
|
||||
klog.Errorf("Unable to write frame (%d bytes) to %d: %v", len(data), channel, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// write multiplexes the specified channel onto the websocket
|
||||
func (conn *Conn) write(num byte, data []byte) (int, error) {
|
||||
conn.resetTimeout()
|
||||
switch conn.codec {
|
||||
case rawCodec:
|
||||
frame := make([]byte, len(data)+1)
|
||||
frame[0] = num
|
||||
copy(frame[1:], data)
|
||||
if err := websocket.Message.Send(conn.ws, frame); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
case base64Codec:
|
||||
frame := string('0'+num) + base64.StdEncoding.EncodeToString(data)
|
||||
if err := websocket.Message.Send(conn.ws, frame); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
return len(data), nil
|
||||
}
|
||||
|
||||
// websocketChannel represents a channel in a connection
|
||||
type websocketChannel struct {
|
||||
conn *Conn
|
||||
num byte
|
||||
r io.Reader
|
||||
w io.WriteCloser
|
||||
|
||||
read, write bool
|
||||
}
|
||||
|
||||
// newWebsocketChannel creates a pipe for writing to a websocket. Do not write to this pipe
|
||||
// prior to the connection being opened. It may be no, half, or full duplex depending on
|
||||
// read and write.
|
||||
func newWebsocketChannel(conn *Conn, num byte, read, write bool) *websocketChannel {
|
||||
r, w := io.Pipe()
|
||||
return &websocketChannel{conn, num, r, w, read, write}
|
||||
}
|
||||
|
||||
func (p *websocketChannel) Write(data []byte) (int, error) {
|
||||
if !p.write {
|
||||
return len(data), nil
|
||||
}
|
||||
return p.conn.write(p.num, data)
|
||||
}
|
||||
|
||||
// DataFromSocket is invoked by the connection receiver to move data from the connection
|
||||
// into a specific channel.
|
||||
func (p *websocketChannel) DataFromSocket(data []byte) (int, error) {
|
||||
if !p.read {
|
||||
return len(data), nil
|
||||
}
|
||||
|
||||
switch p.conn.codec {
|
||||
case rawCodec:
|
||||
return p.w.Write(data)
|
||||
case base64Codec:
|
||||
dst := make([]byte, len(data))
|
||||
n, err := base64.StdEncoding.Decode(dst, data)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return p.w.Write(dst[:n])
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (p *websocketChannel) Read(data []byte) (int, error) {
|
||||
if !p.read {
|
||||
return 0, io.EOF
|
||||
}
|
||||
return p.r.Read(data)
|
||||
}
|
||||
|
||||
func (p *websocketChannel) Close() error {
|
||||
return p.w.Close()
|
||||
}
|
69
vendor/k8s.io/apimachinery/pkg/util/httpstream/wsstream/doc.go
generated
vendored
69
vendor/k8s.io/apimachinery/pkg/util/httpstream/wsstream/doc.go
generated
vendored
@ -1,69 +0,0 @@
|
||||
/*
|
||||
Copyright 2015 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 wsstream contains utilities for streaming content over WebSockets.
|
||||
// The Conn type allows callers to multiplex multiple read/write channels over
|
||||
// a single websocket.
|
||||
//
|
||||
// "channel.k8s.io"
|
||||
//
|
||||
// The Websocket RemoteCommand subprotocol "channel.k8s.io" prepends each binary message with a
|
||||
// byte indicating the channel number (zero indexed) the message was sent on. Messages in both
|
||||
// directions should prefix their messages with this channel byte. Used for remote execution,
|
||||
// the channel numbers are by convention defined to match the POSIX file-descriptors assigned
|
||||
// to STDIN, STDOUT, and STDERR (0, 1, and 2). No other conversion is performed on the raw
|
||||
// subprotocol - writes are sent as they are received by the server.
|
||||
//
|
||||
// Example client session:
|
||||
//
|
||||
// CONNECT http://server.com with subprotocol "channel.k8s.io"
|
||||
// WRITE []byte{0, 102, 111, 111, 10} # send "foo\n" on channel 0 (STDIN)
|
||||
// READ []byte{1, 10} # receive "\n" on channel 1 (STDOUT)
|
||||
// CLOSE
|
||||
//
|
||||
// "v2.channel.k8s.io"
|
||||
//
|
||||
// The second Websocket subprotocol version "v2.channel.k8s.io" is the same as version 1,
|
||||
// but it is the first "versioned" subprotocol.
|
||||
//
|
||||
// "v3.channel.k8s.io"
|
||||
//
|
||||
// The third version of the Websocket RemoteCommand subprotocol adds another channel
|
||||
// for terminal resizing events. This channel is prepended with the byte '3', and it
|
||||
// transmits two window sizes (encoding TerminalSize struct) with integers in the range
|
||||
// (0,65536].
|
||||
//
|
||||
// "v4.channel.k8s.io"
|
||||
//
|
||||
// The fourth version of the Websocket RemoteCommand subprotocol adds a channel for
|
||||
// errors. This channel returns structured errors containing process exit codes. The
|
||||
// error is "apierrors.StatusError{}".
|
||||
//
|
||||
// "v5.channel.k8s.io"
|
||||
//
|
||||
// The fifth version of the Websocket RemoteCommand subprotocol adds a CLOSE signal,
|
||||
// which is sent as the first byte of the message. The second byte is the channel
|
||||
// id. This CLOSE signal is handled by the websocket server by closing the stream,
|
||||
// allowing the other streams to complete transmission if necessary, and gracefully
|
||||
// shutdown the connection.
|
||||
//
|
||||
// Example client session:
|
||||
//
|
||||
// CONNECT http://server.com with subprotocol "v5.channel.k8s.io"
|
||||
// WRITE []byte{0, 102, 111, 111, 10} # send "foo\n" on channel 0 (STDIN)
|
||||
// WRITE []byte{255, 0} # send CLOSE signal (STDIN)
|
||||
// CLOSE
|
||||
package wsstream // import "k8s.io/apimachinery/pkg/util/httpstream/wsstream"
|
177
vendor/k8s.io/apimachinery/pkg/util/httpstream/wsstream/stream.go
generated
vendored
177
vendor/k8s.io/apimachinery/pkg/util/httpstream/wsstream/stream.go
generated
vendored
@ -1,177 +0,0 @@
|
||||
/*
|
||||
Copyright 2015 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 wsstream
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"io"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/websocket"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/runtime"
|
||||
)
|
||||
|
||||
// The WebSocket subprotocol "binary.k8s.io" will only send messages to the
|
||||
// client and ignore messages sent to the server. The received messages are
|
||||
// the exact bytes written to the stream. Zero byte messages are possible.
|
||||
const binaryWebSocketProtocol = "binary.k8s.io"
|
||||
|
||||
// The WebSocket subprotocol "base64.binary.k8s.io" will only send messages to the
|
||||
// client and ignore messages sent to the server. The received messages are
|
||||
// a base64 version of the bytes written to the stream. Zero byte messages are
|
||||
// possible.
|
||||
const base64BinaryWebSocketProtocol = "base64.binary.k8s.io"
|
||||
|
||||
// ReaderProtocolConfig describes a websocket subprotocol with one stream.
|
||||
type ReaderProtocolConfig struct {
|
||||
Binary bool
|
||||
}
|
||||
|
||||
// NewDefaultReaderProtocols returns a stream protocol map with the
|
||||
// subprotocols "", "channel.k8s.io", "base64.channel.k8s.io".
|
||||
func NewDefaultReaderProtocols() map[string]ReaderProtocolConfig {
|
||||
return map[string]ReaderProtocolConfig{
|
||||
"": {Binary: true},
|
||||
binaryWebSocketProtocol: {Binary: true},
|
||||
base64BinaryWebSocketProtocol: {Binary: false},
|
||||
}
|
||||
}
|
||||
|
||||
// Reader supports returning an arbitrary byte stream over a websocket channel.
|
||||
type Reader struct {
|
||||
err chan error
|
||||
r io.Reader
|
||||
ping bool
|
||||
timeout time.Duration
|
||||
protocols map[string]ReaderProtocolConfig
|
||||
selectedProtocol string
|
||||
|
||||
handleCrash func(additionalHandlers ...func(interface{})) // overridable for testing
|
||||
}
|
||||
|
||||
// NewReader creates a WebSocket pipe that will copy the contents of r to a provided
|
||||
// WebSocket connection. If ping is true, a zero length message will be sent to the client
|
||||
// before the stream begins reading.
|
||||
//
|
||||
// The protocols parameter maps subprotocol names to StreamProtocols. The empty string
|
||||
// subprotocol name is used if websocket.Config.Protocol is empty.
|
||||
func NewReader(r io.Reader, ping bool, protocols map[string]ReaderProtocolConfig) *Reader {
|
||||
return &Reader{
|
||||
r: r,
|
||||
err: make(chan error),
|
||||
ping: ping,
|
||||
protocols: protocols,
|
||||
handleCrash: runtime.HandleCrash,
|
||||
}
|
||||
}
|
||||
|
||||
// SetIdleTimeout sets the interval for both reads and writes before timeout. If not specified,
|
||||
// there is no timeout on the reader.
|
||||
func (r *Reader) SetIdleTimeout(duration time.Duration) {
|
||||
r.timeout = duration
|
||||
}
|
||||
|
||||
func (r *Reader) handshake(config *websocket.Config, req *http.Request) error {
|
||||
supportedProtocols := make([]string, 0, len(r.protocols))
|
||||
for p := range r.protocols {
|
||||
supportedProtocols = append(supportedProtocols, p)
|
||||
}
|
||||
return handshake(config, req, supportedProtocols)
|
||||
}
|
||||
|
||||
// Copy the reader to the response. The created WebSocket is closed after this
|
||||
// method completes.
|
||||
func (r *Reader) Copy(w http.ResponseWriter, req *http.Request) error {
|
||||
go func() {
|
||||
defer r.handleCrash()
|
||||
websocket.Server{Handshake: r.handshake, Handler: r.handle}.ServeHTTP(w, req)
|
||||
}()
|
||||
return <-r.err
|
||||
}
|
||||
|
||||
// handle implements a WebSocket handler.
|
||||
func (r *Reader) handle(ws *websocket.Conn) {
|
||||
// Close the connection when the client requests it, or when we finish streaming, whichever happens first
|
||||
closeConnOnce := &sync.Once{}
|
||||
closeConn := func() {
|
||||
closeConnOnce.Do(func() {
|
||||
ws.Close()
|
||||
})
|
||||
}
|
||||
|
||||
negotiated := ws.Config().Protocol
|
||||
r.selectedProtocol = negotiated[0]
|
||||
defer close(r.err)
|
||||
defer closeConn()
|
||||
|
||||
go func() {
|
||||
defer runtime.HandleCrash()
|
||||
// This blocks until the connection is closed.
|
||||
// Client should not send anything.
|
||||
IgnoreReceives(ws, r.timeout)
|
||||
// Once the client closes, we should also close
|
||||
closeConn()
|
||||
}()
|
||||
|
||||
r.err <- messageCopy(ws, r.r, !r.protocols[r.selectedProtocol].Binary, r.ping, r.timeout)
|
||||
}
|
||||
|
||||
func resetTimeout(ws *websocket.Conn, timeout time.Duration) {
|
||||
if timeout > 0 {
|
||||
ws.SetDeadline(time.Now().Add(timeout))
|
||||
}
|
||||
}
|
||||
|
||||
func messageCopy(ws *websocket.Conn, r io.Reader, base64Encode, ping bool, timeout time.Duration) error {
|
||||
buf := make([]byte, 2048)
|
||||
if ping {
|
||||
resetTimeout(ws, timeout)
|
||||
if base64Encode {
|
||||
if err := websocket.Message.Send(ws, ""); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := websocket.Message.Send(ws, []byte{}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
for {
|
||||
resetTimeout(ws, timeout)
|
||||
n, err := r.Read(buf)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
if n > 0 {
|
||||
if base64Encode {
|
||||
if err := websocket.Message.Send(ws, base64.StdEncoding.EncodeToString(buf[:n])); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := websocket.Message.Send(ws, buf[:n]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
24
vendor/k8s.io/apimachinery/pkg/util/portforward/constants.go
generated
vendored
24
vendor/k8s.io/apimachinery/pkg/util/portforward/constants.go
generated
vendored
@ -1,24 +0,0 @@
|
||||
/*
|
||||
Copyright 2016 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 portforward
|
||||
|
||||
const (
|
||||
PortForwardV1Name = "portforward.k8s.io"
|
||||
WebsocketsSPDYTunnelingPrefix = "SPDY/3.1+"
|
||||
KubernetesSuffix = ".k8s.io"
|
||||
WebsocketsSPDYTunnelingPortForwardV1 = WebsocketsSPDYTunnelingPrefix + PortForwardV1Name
|
||||
)
|
122
vendor/k8s.io/apimachinery/pkg/util/proxy/dial.go
generated
vendored
122
vendor/k8s.io/apimachinery/pkg/util/proxy/dial.go
generated
vendored
@ -1,122 +0,0 @@
|
||||
/*
|
||||
Copyright 2015 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 proxy
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
utilnet "k8s.io/apimachinery/pkg/util/net"
|
||||
"k8s.io/apimachinery/third_party/forked/golang/netutil"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
// DialURL will dial the specified URL using the underlying dialer held by the passed
|
||||
// RoundTripper. The primary use of this method is to support proxying upgradable connections.
|
||||
// For this reason this method will prefer to negotiate http/1.1 if the URL scheme is https.
|
||||
// If you wish to ensure ALPN negotiates http2 then set NextProto=[]string{"http2"} in the
|
||||
// TLSConfig of the http.Transport
|
||||
func DialURL(ctx context.Context, url *url.URL, transport http.RoundTripper) (net.Conn, error) {
|
||||
dialAddr := netutil.CanonicalAddr(url)
|
||||
|
||||
dialer, err := utilnet.DialerFor(transport)
|
||||
if err != nil {
|
||||
klog.V(5).Infof("Unable to unwrap transport %T to get dialer: %v", transport, err)
|
||||
}
|
||||
|
||||
switch url.Scheme {
|
||||
case "http":
|
||||
if dialer != nil {
|
||||
return dialer(ctx, "tcp", dialAddr)
|
||||
}
|
||||
var d net.Dialer
|
||||
return d.DialContext(ctx, "tcp", dialAddr)
|
||||
case "https":
|
||||
// Get the tls config from the transport if we recognize it
|
||||
tlsConfig, err := utilnet.TLSClientConfig(transport)
|
||||
if err != nil {
|
||||
klog.V(5).Infof("Unable to unwrap transport %T to get at TLS config: %v", transport, err)
|
||||
}
|
||||
|
||||
if dialer != nil {
|
||||
// We have a dialer; use it to open the connection, then
|
||||
// create a tls client using the connection.
|
||||
netConn, err := dialer(ctx, "tcp", dialAddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if tlsConfig == nil {
|
||||
// tls.Client requires non-nil config
|
||||
klog.Warning("using custom dialer with no TLSClientConfig. Defaulting to InsecureSkipVerify")
|
||||
// tls.Handshake() requires ServerName or InsecureSkipVerify
|
||||
tlsConfig = &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
}
|
||||
} else if len(tlsConfig.ServerName) == 0 && !tlsConfig.InsecureSkipVerify {
|
||||
// tls.HandshakeContext() requires ServerName or InsecureSkipVerify
|
||||
// infer the ServerName from the hostname we're connecting to.
|
||||
inferredHost := dialAddr
|
||||
if host, _, err := net.SplitHostPort(dialAddr); err == nil {
|
||||
inferredHost = host
|
||||
}
|
||||
// Make a copy to avoid polluting the provided config
|
||||
tlsConfigCopy := tlsConfig.Clone()
|
||||
tlsConfigCopy.ServerName = inferredHost
|
||||
tlsConfig = tlsConfigCopy
|
||||
}
|
||||
|
||||
// Since this method is primarily used within a "Connection: Upgrade" call we assume the caller is
|
||||
// going to write HTTP/1.1 request to the wire. http2 should not be allowed in the TLSConfig.NextProtos,
|
||||
// so we explicitly set that here. We only do this check if the TLSConfig support http/1.1.
|
||||
if supportsHTTP11(tlsConfig.NextProtos) {
|
||||
tlsConfig = tlsConfig.Clone()
|
||||
tlsConfig.NextProtos = []string{"http/1.1"}
|
||||
}
|
||||
|
||||
tlsConn := tls.Client(netConn, tlsConfig)
|
||||
if err := tlsConn.HandshakeContext(ctx); err != nil {
|
||||
netConn.Close()
|
||||
return nil, err
|
||||
}
|
||||
return tlsConn, nil
|
||||
} else {
|
||||
// Dial.
|
||||
tlsDialer := tls.Dialer{
|
||||
Config: tlsConfig,
|
||||
}
|
||||
return tlsDialer.DialContext(ctx, "tcp", dialAddr)
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown scheme: %s", url.Scheme)
|
||||
}
|
||||
}
|
||||
|
||||
func supportsHTTP11(nextProtos []string) bool {
|
||||
if len(nextProtos) == 0 {
|
||||
return true
|
||||
}
|
||||
for _, proto := range nextProtos {
|
||||
if proto == "http/1.1" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
18
vendor/k8s.io/apimachinery/pkg/util/proxy/doc.go
generated
vendored
18
vendor/k8s.io/apimachinery/pkg/util/proxy/doc.go
generated
vendored
@ -1,18 +0,0 @@
|
||||
/*
|
||||
Copyright 2014 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 proxy provides transport and upgrade support for proxies.
|
||||
package proxy // import "k8s.io/apimachinery/pkg/util/proxy"
|
272
vendor/k8s.io/apimachinery/pkg/util/proxy/transport.go
generated
vendored
272
vendor/k8s.io/apimachinery/pkg/util/proxy/transport.go
generated
vendored
@ -1,272 +0,0 @@
|
||||
/*
|
||||
Copyright 2014 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 proxy
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/flate"
|
||||
"compress/gzip"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/net/html"
|
||||
"golang.org/x/net/html/atom"
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/util/net"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
)
|
||||
|
||||
// atomsToAttrs states which attributes of which tags require URL substitution.
|
||||
// Sources: http://www.w3.org/TR/REC-html40/index/attributes.html
|
||||
//
|
||||
// http://www.w3.org/html/wg/drafts/html/master/index.html#attributes-1
|
||||
var atomsToAttrs = map[atom.Atom]sets.String{
|
||||
atom.A: sets.NewString("href"),
|
||||
atom.Applet: sets.NewString("codebase"),
|
||||
atom.Area: sets.NewString("href"),
|
||||
atom.Audio: sets.NewString("src"),
|
||||
atom.Base: sets.NewString("href"),
|
||||
atom.Blockquote: sets.NewString("cite"),
|
||||
atom.Body: sets.NewString("background"),
|
||||
atom.Button: sets.NewString("formaction"),
|
||||
atom.Command: sets.NewString("icon"),
|
||||
atom.Del: sets.NewString("cite"),
|
||||
atom.Embed: sets.NewString("src"),
|
||||
atom.Form: sets.NewString("action"),
|
||||
atom.Frame: sets.NewString("longdesc", "src"),
|
||||
atom.Head: sets.NewString("profile"),
|
||||
atom.Html: sets.NewString("manifest"),
|
||||
atom.Iframe: sets.NewString("longdesc", "src"),
|
||||
atom.Img: sets.NewString("longdesc", "src", "usemap"),
|
||||
atom.Input: sets.NewString("src", "usemap", "formaction"),
|
||||
atom.Ins: sets.NewString("cite"),
|
||||
atom.Link: sets.NewString("href"),
|
||||
atom.Object: sets.NewString("classid", "codebase", "data", "usemap"),
|
||||
atom.Q: sets.NewString("cite"),
|
||||
atom.Script: sets.NewString("src"),
|
||||
atom.Source: sets.NewString("src"),
|
||||
atom.Video: sets.NewString("poster", "src"),
|
||||
|
||||
// TODO: css URLs hidden in style elements.
|
||||
}
|
||||
|
||||
// Transport is a transport for text/html content that replaces URLs in html
|
||||
// content with the prefix of the proxy server
|
||||
type Transport struct {
|
||||
Scheme string
|
||||
Host string
|
||||
PathPrepend string
|
||||
|
||||
http.RoundTripper
|
||||
}
|
||||
|
||||
// RoundTrip implements the http.RoundTripper interface
|
||||
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
// Add reverse proxy headers.
|
||||
forwardedURI := path.Join(t.PathPrepend, req.URL.EscapedPath())
|
||||
if strings.HasSuffix(req.URL.Path, "/") {
|
||||
forwardedURI = forwardedURI + "/"
|
||||
}
|
||||
req.Header.Set("X-Forwarded-Uri", forwardedURI)
|
||||
if len(t.Host) > 0 {
|
||||
req.Header.Set("X-Forwarded-Host", t.Host)
|
||||
}
|
||||
if len(t.Scheme) > 0 {
|
||||
req.Header.Set("X-Forwarded-Proto", t.Scheme)
|
||||
}
|
||||
|
||||
rt := t.RoundTripper
|
||||
if rt == nil {
|
||||
rt = http.DefaultTransport
|
||||
}
|
||||
resp, err := rt.RoundTrip(req)
|
||||
|
||||
if err != nil {
|
||||
return nil, errors.NewServiceUnavailable(fmt.Sprintf("error trying to reach service: %v", err))
|
||||
}
|
||||
|
||||
if redirect := resp.Header.Get("Location"); redirect != "" {
|
||||
targetURL, err := url.Parse(redirect)
|
||||
if err != nil {
|
||||
return nil, errors.NewInternalError(fmt.Errorf("error trying to parse Location header: %v", err))
|
||||
}
|
||||
resp.Header.Set("Location", t.rewriteURL(targetURL, req.URL, req.Host))
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
cType := resp.Header.Get("Content-Type")
|
||||
cType = strings.TrimSpace(strings.SplitN(cType, ";", 2)[0])
|
||||
if cType != "text/html" {
|
||||
// Do nothing, simply pass through
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
return t.rewriteResponse(req, resp)
|
||||
}
|
||||
|
||||
var _ = net.RoundTripperWrapper(&Transport{})
|
||||
|
||||
func (rt *Transport) WrappedRoundTripper() http.RoundTripper {
|
||||
return rt.RoundTripper
|
||||
}
|
||||
|
||||
// rewriteURL rewrites a single URL to go through the proxy, if the URL refers
|
||||
// to the same host as sourceURL, which is the page on which the target URL
|
||||
// occurred, or if the URL matches the sourceRequestHost.
|
||||
func (t *Transport) rewriteURL(url *url.URL, sourceURL *url.URL, sourceRequestHost string) string {
|
||||
// Example:
|
||||
// When API server processes a proxy request to a service (e.g. /api/v1/namespace/foo/service/bar/proxy/),
|
||||
// the sourceURL.Host (i.e. req.URL.Host) is the endpoint IP address of the service. The
|
||||
// sourceRequestHost (i.e. req.Host) is the Host header that specifies the host on which the
|
||||
// URL is sought, which can be different from sourceURL.Host. For example, if user sends the
|
||||
// request through "kubectl proxy" locally (i.e. localhost:8001/api/v1/namespace/foo/service/bar/proxy/),
|
||||
// sourceRequestHost is "localhost:8001".
|
||||
//
|
||||
// If the service's response URL contains non-empty host, and url.Host is equal to either sourceURL.Host
|
||||
// or sourceRequestHost, we should not consider the returned URL to be a completely different host.
|
||||
// It's the API server's responsibility to rewrite a same-host-and-absolute-path URL and append the
|
||||
// necessary URL prefix (i.e. /api/v1/namespace/foo/service/bar/proxy/).
|
||||
isDifferentHost := url.Host != "" && url.Host != sourceURL.Host && url.Host != sourceRequestHost
|
||||
isRelative := !strings.HasPrefix(url.Path, "/")
|
||||
if isDifferentHost || isRelative {
|
||||
return url.String()
|
||||
}
|
||||
|
||||
// Do not rewrite scheme and host if the Transport has empty scheme and host
|
||||
// when targetURL already contains the sourceRequestHost
|
||||
if !(url.Host == sourceRequestHost && t.Scheme == "" && t.Host == "") {
|
||||
url.Scheme = t.Scheme
|
||||
url.Host = t.Host
|
||||
}
|
||||
|
||||
origPath := url.Path
|
||||
// Do not rewrite URL if the sourceURL already contains the necessary prefix.
|
||||
if strings.HasPrefix(url.Path, t.PathPrepend) {
|
||||
return url.String()
|
||||
}
|
||||
url.Path = path.Join(t.PathPrepend, url.Path)
|
||||
if strings.HasSuffix(origPath, "/") {
|
||||
// Add back the trailing slash, which was stripped by path.Join().
|
||||
url.Path += "/"
|
||||
}
|
||||
|
||||
return url.String()
|
||||
}
|
||||
|
||||
// rewriteHTML scans the HTML for tags with url-valued attributes, and updates
|
||||
// those values with the urlRewriter function. The updated HTML is output to the
|
||||
// writer.
|
||||
func rewriteHTML(reader io.Reader, writer io.Writer, urlRewriter func(*url.URL) string) error {
|
||||
// Note: This assumes the content is UTF-8.
|
||||
tokenizer := html.NewTokenizer(reader)
|
||||
|
||||
var err error
|
||||
for err == nil {
|
||||
tokenType := tokenizer.Next()
|
||||
switch tokenType {
|
||||
case html.ErrorToken:
|
||||
err = tokenizer.Err()
|
||||
case html.StartTagToken, html.SelfClosingTagToken:
|
||||
token := tokenizer.Token()
|
||||
if urlAttrs, ok := atomsToAttrs[token.DataAtom]; ok {
|
||||
for i, attr := range token.Attr {
|
||||
if urlAttrs.Has(attr.Key) {
|
||||
url, err := url.Parse(attr.Val)
|
||||
if err != nil {
|
||||
// Do not rewrite the URL if it isn't valid. It is intended not
|
||||
// to error here to prevent the inability to understand the
|
||||
// content of the body to cause a fatal error.
|
||||
continue
|
||||
}
|
||||
token.Attr[i].Val = urlRewriter(url)
|
||||
}
|
||||
}
|
||||
}
|
||||
_, err = writer.Write([]byte(token.String()))
|
||||
default:
|
||||
_, err = writer.Write(tokenizer.Raw())
|
||||
}
|
||||
}
|
||||
if err != io.EOF {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// rewriteResponse modifies an HTML response by updating absolute links referring
|
||||
// to the original host to instead refer to the proxy transport.
|
||||
func (t *Transport) rewriteResponse(req *http.Request, resp *http.Response) (*http.Response, error) {
|
||||
origBody := resp.Body
|
||||
defer origBody.Close()
|
||||
|
||||
newContent := &bytes.Buffer{}
|
||||
var reader io.Reader = origBody
|
||||
var writer io.Writer = newContent
|
||||
encoding := resp.Header.Get("Content-Encoding")
|
||||
switch encoding {
|
||||
case "gzip":
|
||||
var err error
|
||||
reader, err = gzip.NewReader(reader)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("errorf making gzip reader: %v", err)
|
||||
}
|
||||
gzw := gzip.NewWriter(writer)
|
||||
defer gzw.Close()
|
||||
writer = gzw
|
||||
case "deflate":
|
||||
var err error
|
||||
reader = flate.NewReader(reader)
|
||||
flw, err := flate.NewWriter(writer, flate.BestCompression)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("errorf making flate writer: %v", err)
|
||||
}
|
||||
defer func() {
|
||||
flw.Close()
|
||||
flw.Flush()
|
||||
}()
|
||||
writer = flw
|
||||
case "":
|
||||
// This is fine
|
||||
default:
|
||||
// Some encoding we don't understand-- don't try to parse this
|
||||
klog.Errorf("Proxy encountered encoding %v for text/html; can't understand this so not fixing links.", encoding)
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
urlRewriter := func(targetUrl *url.URL) string {
|
||||
return t.rewriteURL(targetUrl, req.URL, req.Host)
|
||||
}
|
||||
err := rewriteHTML(reader, writer, urlRewriter)
|
||||
if err != nil {
|
||||
klog.Errorf("Failed to rewrite URLs: %v", err)
|
||||
return resp, err
|
||||
}
|
||||
|
||||
resp.Body = io.NopCloser(newContent)
|
||||
// Update header node with new content-length
|
||||
// TODO: Remove any hash/signature headers here?
|
||||
resp.Header.Del("Content-Length")
|
||||
resp.ContentLength = int64(newContent.Len())
|
||||
|
||||
return resp, err
|
||||
}
|
558
vendor/k8s.io/apimachinery/pkg/util/proxy/upgradeaware.go
generated
vendored
558
vendor/k8s.io/apimachinery/pkg/util/proxy/upgradeaware.go
generated
vendored
@ -1,558 +0,0 @@
|
||||
/*
|
||||
Copyright 2017 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 proxy
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/util/httpstream"
|
||||
utilnet "k8s.io/apimachinery/pkg/util/net"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
|
||||
"github.com/mxk/go-flowrate/flowrate"
|
||||
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
// UpgradeRequestRoundTripper provides an additional method to decorate a request
|
||||
// with any authentication or other protocol level information prior to performing
|
||||
// an upgrade on the server. Any response will be handled by the intercepting
|
||||
// proxy.
|
||||
type UpgradeRequestRoundTripper interface {
|
||||
http.RoundTripper
|
||||
// WrapRequest takes a valid HTTP request and returns a suitably altered version
|
||||
// of request with any HTTP level values required to complete the request half of
|
||||
// an upgrade on the server. It does not get a chance to see the response and
|
||||
// should bypass any request side logic that expects to see the response.
|
||||
WrapRequest(*http.Request) (*http.Request, error)
|
||||
}
|
||||
|
||||
// UpgradeAwareHandler is a handler for proxy requests that may require an upgrade
|
||||
type UpgradeAwareHandler struct {
|
||||
// UpgradeRequired will reject non-upgrade connections if true.
|
||||
UpgradeRequired bool
|
||||
// Location is the location of the upstream proxy. It is used as the location to Dial on the upstream server
|
||||
// for upgrade requests unless UseRequestLocationOnUpgrade is true.
|
||||
Location *url.URL
|
||||
// AppendLocationPath determines if the original path of the Location should be appended to the upstream proxy request path
|
||||
AppendLocationPath bool
|
||||
// Transport provides an optional round tripper to use to proxy. If nil, the default proxy transport is used
|
||||
Transport http.RoundTripper
|
||||
// UpgradeTransport, if specified, will be used as the backend transport when upgrade requests are provided.
|
||||
// This allows clients to disable HTTP/2.
|
||||
UpgradeTransport UpgradeRequestRoundTripper
|
||||
// WrapTransport indicates whether the provided Transport should be wrapped with default proxy transport behavior (URL rewriting, X-Forwarded-* header setting)
|
||||
WrapTransport bool
|
||||
// UseRequestLocation will use the incoming request URL when talking to the backend server.
|
||||
UseRequestLocation bool
|
||||
// UseLocationHost overrides the HTTP host header in requests to the backend server to use the Host from Location.
|
||||
// This will override the req.Host field of a request, while UseRequestLocation will override the req.URL field
|
||||
// of a request. The req.URL.Host specifies the server to connect to, while the req.Host field
|
||||
// specifies the Host header value to send in the HTTP request. If this is false, the incoming req.Host header will
|
||||
// just be forwarded to the backend server.
|
||||
UseLocationHost bool
|
||||
// FlushInterval controls how often the standard HTTP proxy will flush content from the upstream.
|
||||
FlushInterval time.Duration
|
||||
// MaxBytesPerSec controls the maximum rate for an upstream connection. No rate is imposed if the value is zero.
|
||||
MaxBytesPerSec int64
|
||||
// Responder is passed errors that occur while setting up proxying.
|
||||
Responder ErrorResponder
|
||||
// Reject to forward redirect response
|
||||
RejectForwardingRedirects bool
|
||||
}
|
||||
|
||||
const defaultFlushInterval = 200 * time.Millisecond
|
||||
|
||||
// ErrorResponder abstracts error reporting to the proxy handler to remove the need to hardcode a particular
|
||||
// error format.
|
||||
type ErrorResponder interface {
|
||||
Error(w http.ResponseWriter, req *http.Request, err error)
|
||||
}
|
||||
|
||||
// SimpleErrorResponder is the legacy implementation of ErrorResponder for callers that only
|
||||
// service a single request/response per proxy.
|
||||
type SimpleErrorResponder interface {
|
||||
Error(err error)
|
||||
}
|
||||
|
||||
func NewErrorResponder(r SimpleErrorResponder) ErrorResponder {
|
||||
return simpleResponder{r}
|
||||
}
|
||||
|
||||
type simpleResponder struct {
|
||||
responder SimpleErrorResponder
|
||||
}
|
||||
|
||||
func (r simpleResponder) Error(w http.ResponseWriter, req *http.Request, err error) {
|
||||
r.responder.Error(err)
|
||||
}
|
||||
|
||||
// upgradeRequestRoundTripper implements proxy.UpgradeRequestRoundTripper.
|
||||
type upgradeRequestRoundTripper struct {
|
||||
http.RoundTripper
|
||||
upgrader http.RoundTripper
|
||||
}
|
||||
|
||||
var (
|
||||
_ UpgradeRequestRoundTripper = &upgradeRequestRoundTripper{}
|
||||
_ utilnet.RoundTripperWrapper = &upgradeRequestRoundTripper{}
|
||||
)
|
||||
|
||||
// WrappedRoundTripper returns the round tripper that a caller would use.
|
||||
func (rt *upgradeRequestRoundTripper) WrappedRoundTripper() http.RoundTripper {
|
||||
return rt.RoundTripper
|
||||
}
|
||||
|
||||
// WriteToRequest calls the nested upgrader and then copies the returned request
|
||||
// fields onto the passed request.
|
||||
func (rt *upgradeRequestRoundTripper) WrapRequest(req *http.Request) (*http.Request, error) {
|
||||
resp, err := rt.upgrader.RoundTrip(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp.Request, nil
|
||||
}
|
||||
|
||||
// onewayRoundTripper captures the provided request - which is assumed to have
|
||||
// been modified by other round trippers - and then returns a fake response.
|
||||
type onewayRoundTripper struct{}
|
||||
|
||||
// RoundTrip returns a simple 200 OK response that captures the provided request.
|
||||
func (onewayRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
return &http.Response{
|
||||
Status: "200 OK",
|
||||
StatusCode: http.StatusOK,
|
||||
Body: io.NopCloser(&bytes.Buffer{}),
|
||||
Request: req,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// MirrorRequest is a round tripper that can be called to get back the calling request as
|
||||
// the core round tripper in a chain.
|
||||
var MirrorRequest http.RoundTripper = onewayRoundTripper{}
|
||||
|
||||
// NewUpgradeRequestRoundTripper takes two round trippers - one for the underlying TCP connection, and
|
||||
// one that is able to write headers to an HTTP request. The request rt is used to set the request headers
|
||||
// and that is written to the underlying connection rt.
|
||||
func NewUpgradeRequestRoundTripper(connection, request http.RoundTripper) UpgradeRequestRoundTripper {
|
||||
return &upgradeRequestRoundTripper{
|
||||
RoundTripper: connection,
|
||||
upgrader: request,
|
||||
}
|
||||
}
|
||||
|
||||
// normalizeLocation returns the result of parsing the full URL, with scheme set to http if missing
|
||||
func normalizeLocation(location *url.URL) *url.URL {
|
||||
normalized, _ := url.Parse(location.String())
|
||||
if len(normalized.Scheme) == 0 {
|
||||
normalized.Scheme = "http"
|
||||
}
|
||||
return normalized
|
||||
}
|
||||
|
||||
// NewUpgradeAwareHandler creates a new proxy handler with a default flush interval. Responder is required for returning
|
||||
// errors to the caller.
|
||||
func NewUpgradeAwareHandler(location *url.URL, transport http.RoundTripper, wrapTransport, upgradeRequired bool, responder ErrorResponder) *UpgradeAwareHandler {
|
||||
return &UpgradeAwareHandler{
|
||||
Location: normalizeLocation(location),
|
||||
Transport: transport,
|
||||
WrapTransport: wrapTransport,
|
||||
UpgradeRequired: upgradeRequired,
|
||||
FlushInterval: defaultFlushInterval,
|
||||
Responder: responder,
|
||||
}
|
||||
}
|
||||
|
||||
func proxyRedirectsforRootPath(path string, w http.ResponseWriter, req *http.Request) bool {
|
||||
redirect := false
|
||||
method := req.Method
|
||||
|
||||
// From pkg/genericapiserver/endpoints/handlers/proxy.go#ServeHTTP:
|
||||
// Redirect requests with an empty path to a location that ends with a '/'
|
||||
// This is essentially a hack for https://issue.k8s.io/4958.
|
||||
// Note: Keep this code after tryUpgrade to not break that flow.
|
||||
if len(path) == 0 && (method == http.MethodGet || method == http.MethodHead) {
|
||||
var queryPart string
|
||||
if len(req.URL.RawQuery) > 0 {
|
||||
queryPart = "?" + req.URL.RawQuery
|
||||
}
|
||||
w.Header().Set("Location", req.URL.Path+"/"+queryPart)
|
||||
w.WriteHeader(http.StatusMovedPermanently)
|
||||
redirect = true
|
||||
}
|
||||
return redirect
|
||||
}
|
||||
|
||||
// ServeHTTP handles the proxy request
|
||||
func (h *UpgradeAwareHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
if h.tryUpgrade(w, req) {
|
||||
return
|
||||
}
|
||||
if h.UpgradeRequired {
|
||||
h.Responder.Error(w, req, errors.NewBadRequest("Upgrade request required"))
|
||||
return
|
||||
}
|
||||
|
||||
loc := *h.Location
|
||||
loc.RawQuery = req.URL.RawQuery
|
||||
|
||||
// If original request URL ended in '/', append a '/' at the end of the
|
||||
// of the proxy URL
|
||||
if !strings.HasSuffix(loc.Path, "/") && strings.HasSuffix(req.URL.Path, "/") {
|
||||
loc.Path += "/"
|
||||
}
|
||||
|
||||
proxyRedirect := proxyRedirectsforRootPath(loc.Path, w, req)
|
||||
if proxyRedirect {
|
||||
return
|
||||
}
|
||||
|
||||
if h.Transport == nil || h.WrapTransport {
|
||||
h.Transport = h.defaultProxyTransport(req.URL, h.Transport)
|
||||
}
|
||||
|
||||
// WithContext creates a shallow clone of the request with the same context.
|
||||
newReq := req.WithContext(req.Context())
|
||||
newReq.Header = utilnet.CloneHeader(req.Header)
|
||||
if !h.UseRequestLocation {
|
||||
newReq.URL = &loc
|
||||
}
|
||||
if h.UseLocationHost {
|
||||
// exchanging req.Host with the backend location is necessary for backends that act on the HTTP host header (e.g. API gateways),
|
||||
// because req.Host has preference over req.URL.Host in filling this header field
|
||||
newReq.Host = h.Location.Host
|
||||
}
|
||||
|
||||
// create the target location to use for the reverse proxy
|
||||
reverseProxyLocation := &url.URL{Scheme: h.Location.Scheme, Host: h.Location.Host}
|
||||
if h.AppendLocationPath {
|
||||
reverseProxyLocation.Path = h.Location.Path
|
||||
}
|
||||
|
||||
proxy := httputil.NewSingleHostReverseProxy(reverseProxyLocation)
|
||||
proxy.Transport = h.Transport
|
||||
proxy.FlushInterval = h.FlushInterval
|
||||
proxy.ErrorLog = log.New(noSuppressPanicError{}, "", log.LstdFlags)
|
||||
if h.RejectForwardingRedirects {
|
||||
oldModifyResponse := proxy.ModifyResponse
|
||||
proxy.ModifyResponse = func(response *http.Response) error {
|
||||
code := response.StatusCode
|
||||
if code >= 300 && code <= 399 && len(response.Header.Get("Location")) > 0 {
|
||||
// close the original response
|
||||
response.Body.Close()
|
||||
msg := "the backend attempted to redirect this request, which is not permitted"
|
||||
// replace the response
|
||||
*response = http.Response{
|
||||
StatusCode: http.StatusBadGateway,
|
||||
Status: fmt.Sprintf("%d %s", response.StatusCode, http.StatusText(response.StatusCode)),
|
||||
Body: io.NopCloser(strings.NewReader(msg)),
|
||||
ContentLength: int64(len(msg)),
|
||||
}
|
||||
} else {
|
||||
if oldModifyResponse != nil {
|
||||
if err := oldModifyResponse(response); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if h.Responder != nil {
|
||||
// if an optional error interceptor/responder was provided wire it
|
||||
// the custom responder might be used for providing a unified error reporting
|
||||
// or supporting retry mechanisms by not sending non-fatal errors to the clients
|
||||
proxy.ErrorHandler = h.Responder.Error
|
||||
}
|
||||
proxy.ServeHTTP(w, newReq)
|
||||
}
|
||||
|
||||
type noSuppressPanicError struct{}
|
||||
|
||||
func (noSuppressPanicError) Write(p []byte) (n int, err error) {
|
||||
// skip "suppressing panic for copyResponse error in test; copy error" error message
|
||||
// that ends up in CI tests on each kube-apiserver termination as noise and
|
||||
// everybody thinks this is fatal.
|
||||
if strings.Contains(string(p), "suppressing panic") {
|
||||
return len(p), nil
|
||||
}
|
||||
return os.Stderr.Write(p)
|
||||
}
|
||||
|
||||
// tryUpgrade returns true if the request was handled.
|
||||
func (h *UpgradeAwareHandler) tryUpgrade(w http.ResponseWriter, req *http.Request) bool {
|
||||
if !httpstream.IsUpgradeRequest(req) {
|
||||
klog.V(6).Infof("Request was not an upgrade")
|
||||
return false
|
||||
}
|
||||
|
||||
var (
|
||||
backendConn net.Conn
|
||||
rawResponse []byte
|
||||
err error
|
||||
)
|
||||
|
||||
location := *h.Location
|
||||
if h.UseRequestLocation {
|
||||
location = *req.URL
|
||||
location.Scheme = h.Location.Scheme
|
||||
location.Host = h.Location.Host
|
||||
if h.AppendLocationPath {
|
||||
location.Path = singleJoiningSlash(h.Location.Path, location.Path)
|
||||
}
|
||||
}
|
||||
|
||||
clone := utilnet.CloneRequest(req)
|
||||
// Only append X-Forwarded-For in the upgrade path, since httputil.NewSingleHostReverseProxy
|
||||
// handles this in the non-upgrade path.
|
||||
utilnet.AppendForwardedForHeader(clone)
|
||||
klog.V(6).Infof("Connecting to backend proxy (direct dial) %s\n Headers: %v", &location, clone.Header)
|
||||
if h.UseLocationHost {
|
||||
clone.Host = h.Location.Host
|
||||
}
|
||||
clone.URL = &location
|
||||
klog.V(6).Infof("UpgradeAwareProxy: dialing for SPDY upgrade with headers: %v", clone.Header)
|
||||
backendConn, err = h.DialForUpgrade(clone)
|
||||
if err != nil {
|
||||
klog.V(6).Infof("Proxy connection error: %v", err)
|
||||
h.Responder.Error(w, req, err)
|
||||
return true
|
||||
}
|
||||
defer backendConn.Close()
|
||||
|
||||
// determine the http response code from the backend by reading from rawResponse+backendConn
|
||||
backendHTTPResponse, headerBytes, err := getResponse(io.MultiReader(bytes.NewReader(rawResponse), backendConn))
|
||||
if err != nil {
|
||||
klog.V(6).Infof("Proxy connection error: %v", err)
|
||||
h.Responder.Error(w, req, err)
|
||||
return true
|
||||
}
|
||||
if len(headerBytes) > len(rawResponse) {
|
||||
// we read beyond the bytes stored in rawResponse, update rawResponse to the full set of bytes read from the backend
|
||||
rawResponse = headerBytes
|
||||
}
|
||||
|
||||
// If the backend did not upgrade the request, return an error to the client. If the response was
|
||||
// an error, the error is forwarded directly after the connection is hijacked. Otherwise, just
|
||||
// return a generic error here.
|
||||
if backendHTTPResponse.StatusCode != http.StatusSwitchingProtocols && backendHTTPResponse.StatusCode < 400 {
|
||||
err := fmt.Errorf("invalid upgrade response: status code %d", backendHTTPResponse.StatusCode)
|
||||
klog.Errorf("Proxy upgrade error: %v", err)
|
||||
h.Responder.Error(w, req, err)
|
||||
return true
|
||||
}
|
||||
|
||||
// Once the connection is hijacked, the ErrorResponder will no longer work, so
|
||||
// hijacking should be the last step in the upgrade.
|
||||
requestHijacker, ok := w.(http.Hijacker)
|
||||
if !ok {
|
||||
klog.Errorf("Unable to hijack response writer: %T", w)
|
||||
h.Responder.Error(w, req, fmt.Errorf("request connection cannot be hijacked: %T", w))
|
||||
return true
|
||||
}
|
||||
requestHijackedConn, _, err := requestHijacker.Hijack()
|
||||
if err != nil {
|
||||
klog.Errorf("Unable to hijack response: %v", err)
|
||||
h.Responder.Error(w, req, fmt.Errorf("error hijacking connection: %v", err))
|
||||
return true
|
||||
}
|
||||
defer requestHijackedConn.Close()
|
||||
|
||||
if backendHTTPResponse.StatusCode != http.StatusSwitchingProtocols {
|
||||
// If the backend did not upgrade the request, echo the response from the backend to the client and return, closing the connection.
|
||||
klog.V(6).Infof("Proxy upgrade error, status code %d", backendHTTPResponse.StatusCode)
|
||||
// set read/write deadlines
|
||||
deadline := time.Now().Add(10 * time.Second)
|
||||
backendConn.SetReadDeadline(deadline)
|
||||
requestHijackedConn.SetWriteDeadline(deadline)
|
||||
// write the response to the client
|
||||
err := backendHTTPResponse.Write(requestHijackedConn)
|
||||
if err != nil && !strings.Contains(err.Error(), "use of closed network connection") {
|
||||
klog.Errorf("Error proxying data from backend to client: %v", err)
|
||||
}
|
||||
// Indicate we handled the request
|
||||
return true
|
||||
}
|
||||
|
||||
// Forward raw response bytes back to client.
|
||||
if len(rawResponse) > 0 {
|
||||
klog.V(6).Infof("Writing %d bytes to hijacked connection", len(rawResponse))
|
||||
if _, err = requestHijackedConn.Write(rawResponse); err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("Error proxying response from backend to client: %v", err))
|
||||
}
|
||||
}
|
||||
|
||||
// Proxy the connection. This is bidirectional, so we need a goroutine
|
||||
// to copy in each direction. Once one side of the connection exits, we
|
||||
// exit the function which performs cleanup and in the process closes
|
||||
// the other half of the connection in the defer.
|
||||
writerComplete := make(chan struct{})
|
||||
readerComplete := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
var writer io.WriteCloser
|
||||
if h.MaxBytesPerSec > 0 {
|
||||
writer = flowrate.NewWriter(backendConn, h.MaxBytesPerSec)
|
||||
} else {
|
||||
writer = backendConn
|
||||
}
|
||||
_, err := io.Copy(writer, requestHijackedConn)
|
||||
if err != nil && !strings.Contains(err.Error(), "use of closed network connection") {
|
||||
klog.Errorf("Error proxying data from client to backend: %v", err)
|
||||
}
|
||||
close(writerComplete)
|
||||
}()
|
||||
|
||||
go func() {
|
||||
var reader io.ReadCloser
|
||||
if h.MaxBytesPerSec > 0 {
|
||||
reader = flowrate.NewReader(backendConn, h.MaxBytesPerSec)
|
||||
} else {
|
||||
reader = backendConn
|
||||
}
|
||||
_, err := io.Copy(requestHijackedConn, reader)
|
||||
if err != nil && !strings.Contains(err.Error(), "use of closed network connection") {
|
||||
klog.Errorf("Error proxying data from backend to client: %v", err)
|
||||
}
|
||||
close(readerComplete)
|
||||
}()
|
||||
|
||||
// Wait for one half the connection to exit. Once it does the defer will
|
||||
// clean up the other half of the connection.
|
||||
select {
|
||||
case <-writerComplete:
|
||||
case <-readerComplete:
|
||||
}
|
||||
klog.V(6).Infof("Disconnecting from backend proxy %s\n Headers: %v", &location, clone.Header)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// FIXME: Taken from net/http/httputil/reverseproxy.go as singleJoiningSlash is not exported to be re-used.
|
||||
// See-also: https://github.com/golang/go/issues/44290
|
||||
func singleJoiningSlash(a, b string) string {
|
||||
aslash := strings.HasSuffix(a, "/")
|
||||
bslash := strings.HasPrefix(b, "/")
|
||||
switch {
|
||||
case aslash && bslash:
|
||||
return a + b[1:]
|
||||
case !aslash && !bslash:
|
||||
return a + "/" + b
|
||||
}
|
||||
return a + b
|
||||
}
|
||||
|
||||
func (h *UpgradeAwareHandler) DialForUpgrade(req *http.Request) (net.Conn, error) {
|
||||
if h.UpgradeTransport == nil {
|
||||
return dial(req, h.Transport)
|
||||
}
|
||||
updatedReq, err := h.UpgradeTransport.WrapRequest(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dial(updatedReq, h.UpgradeTransport)
|
||||
}
|
||||
|
||||
// getResponseCode reads a http response from the given reader, returns the response,
|
||||
// the bytes read from the reader, and any error encountered
|
||||
func getResponse(r io.Reader) (*http.Response, []byte, error) {
|
||||
rawResponse := bytes.NewBuffer(make([]byte, 0, 256))
|
||||
// Save the bytes read while reading the response headers into the rawResponse buffer
|
||||
resp, err := http.ReadResponse(bufio.NewReader(io.TeeReader(r, rawResponse)), nil)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
// return the http response and the raw bytes consumed from the reader in the process
|
||||
return resp, rawResponse.Bytes(), nil
|
||||
}
|
||||
|
||||
// dial dials the backend at req.URL and writes req to it.
|
||||
func dial(req *http.Request, transport http.RoundTripper) (net.Conn, error) {
|
||||
conn, err := DialURL(req.Context(), req.URL, transport)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error dialing backend: %v", err)
|
||||
}
|
||||
|
||||
if err = req.Write(conn); err != nil {
|
||||
conn.Close()
|
||||
return nil, fmt.Errorf("error sending request: %v", err)
|
||||
}
|
||||
|
||||
return conn, err
|
||||
}
|
||||
|
||||
func (h *UpgradeAwareHandler) defaultProxyTransport(url *url.URL, internalTransport http.RoundTripper) http.RoundTripper {
|
||||
scheme := url.Scheme
|
||||
host := url.Host
|
||||
suffix := h.Location.Path
|
||||
if strings.HasSuffix(url.Path, "/") && !strings.HasSuffix(suffix, "/") {
|
||||
suffix += "/"
|
||||
}
|
||||
pathPrepend := strings.TrimSuffix(url.Path, suffix)
|
||||
rewritingTransport := &Transport{
|
||||
Scheme: scheme,
|
||||
Host: host,
|
||||
PathPrepend: pathPrepend,
|
||||
RoundTripper: internalTransport,
|
||||
}
|
||||
return &corsRemovingTransport{
|
||||
RoundTripper: rewritingTransport,
|
||||
}
|
||||
}
|
||||
|
||||
// corsRemovingTransport is a wrapper for an internal transport. It removes CORS headers
|
||||
// from the internal response.
|
||||
// Implements pkg/util/net.RoundTripperWrapper
|
||||
type corsRemovingTransport struct {
|
||||
http.RoundTripper
|
||||
}
|
||||
|
||||
var _ = utilnet.RoundTripperWrapper(&corsRemovingTransport{})
|
||||
|
||||
func (rt *corsRemovingTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
resp, err := rt.RoundTripper.RoundTrip(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
removeCORSHeaders(resp)
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (rt *corsRemovingTransport) WrappedRoundTripper() http.RoundTripper {
|
||||
return rt.RoundTripper
|
||||
}
|
||||
|
||||
// removeCORSHeaders strip CORS headers sent from the backend
|
||||
// This should be called on all responses before returning
|
||||
func removeCORSHeaders(resp *http.Response) {
|
||||
resp.Header.Del("Access-Control-Allow-Credentials")
|
||||
resp.Header.Del("Access-Control-Allow-Headers")
|
||||
resp.Header.Del("Access-Control-Allow-Methods")
|
||||
resp.Header.Del("Access-Control-Allow-Origin")
|
||||
}
|
127
vendor/k8s.io/apimachinery/pkg/util/rand/rand.go
generated
vendored
127
vendor/k8s.io/apimachinery/pkg/util/rand/rand.go
generated
vendored
@ -1,127 +0,0 @@
|
||||
/*
|
||||
Copyright 2015 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 rand provides utilities related to randomization.
|
||||
package rand
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var rng = struct {
|
||||
sync.Mutex
|
||||
rand *rand.Rand
|
||||
}{
|
||||
rand: rand.New(rand.NewSource(time.Now().UnixNano())),
|
||||
}
|
||||
|
||||
// Int returns a non-negative pseudo-random int.
|
||||
func Int() int {
|
||||
rng.Lock()
|
||||
defer rng.Unlock()
|
||||
return rng.rand.Int()
|
||||
}
|
||||
|
||||
// Intn generates an integer in range [0,max).
|
||||
// By design this should panic if input is invalid, <= 0.
|
||||
func Intn(max int) int {
|
||||
rng.Lock()
|
||||
defer rng.Unlock()
|
||||
return rng.rand.Intn(max)
|
||||
}
|
||||
|
||||
// IntnRange generates an integer in range [min,max).
|
||||
// By design this should panic if input is invalid, <= 0.
|
||||
func IntnRange(min, max int) int {
|
||||
rng.Lock()
|
||||
defer rng.Unlock()
|
||||
return rng.rand.Intn(max-min) + min
|
||||
}
|
||||
|
||||
// IntnRange generates an int64 integer in range [min,max).
|
||||
// By design this should panic if input is invalid, <= 0.
|
||||
func Int63nRange(min, max int64) int64 {
|
||||
rng.Lock()
|
||||
defer rng.Unlock()
|
||||
return rng.rand.Int63n(max-min) + min
|
||||
}
|
||||
|
||||
// Seed seeds the rng with the provided seed.
|
||||
func Seed(seed int64) {
|
||||
rng.Lock()
|
||||
defer rng.Unlock()
|
||||
|
||||
rng.rand = rand.New(rand.NewSource(seed))
|
||||
}
|
||||
|
||||
// Perm returns, as a slice of n ints, a pseudo-random permutation of the integers [0,n)
|
||||
// from the default Source.
|
||||
func Perm(n int) []int {
|
||||
rng.Lock()
|
||||
defer rng.Unlock()
|
||||
return rng.rand.Perm(n)
|
||||
}
|
||||
|
||||
const (
|
||||
// We omit vowels from the set of available characters to reduce the chances
|
||||
// of "bad words" being formed.
|
||||
alphanums = "bcdfghjklmnpqrstvwxz2456789"
|
||||
// No. of bits required to index into alphanums string.
|
||||
alphanumsIdxBits = 5
|
||||
// Mask used to extract last alphanumsIdxBits of an int.
|
||||
alphanumsIdxMask = 1<<alphanumsIdxBits - 1
|
||||
// No. of random letters we can extract from a single int63.
|
||||
maxAlphanumsPerInt = 63 / alphanumsIdxBits
|
||||
)
|
||||
|
||||
// String generates a random alphanumeric string, without vowels, which is n
|
||||
// characters long. This will panic if n is less than zero.
|
||||
// How the random string is created:
|
||||
// - we generate random int63's
|
||||
// - from each int63, we are extracting multiple random letters by bit-shifting and masking
|
||||
// - if some index is out of range of alphanums we neglect it (unlikely to happen multiple times in a row)
|
||||
func String(n int) string {
|
||||
b := make([]byte, n)
|
||||
rng.Lock()
|
||||
defer rng.Unlock()
|
||||
|
||||
randomInt63 := rng.rand.Int63()
|
||||
remaining := maxAlphanumsPerInt
|
||||
for i := 0; i < n; {
|
||||
if remaining == 0 {
|
||||
randomInt63, remaining = rng.rand.Int63(), maxAlphanumsPerInt
|
||||
}
|
||||
if idx := int(randomInt63 & alphanumsIdxMask); idx < len(alphanums) {
|
||||
b[i] = alphanums[idx]
|
||||
i++
|
||||
}
|
||||
randomInt63 >>= alphanumsIdxBits
|
||||
remaining--
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// SafeEncodeString encodes s using the same characters as rand.String. This reduces the chances of bad words and
|
||||
// ensures that strings generated from hash functions appear consistent throughout the API.
|
||||
func SafeEncodeString(s string) string {
|
||||
r := make([]byte, len(s))
|
||||
for i, b := range []rune(s) {
|
||||
r[i] = alphanums[(int(b) % len(alphanums))]
|
||||
}
|
||||
return string(r)
|
||||
}
|
67
vendor/k8s.io/apimachinery/pkg/util/remotecommand/constants.go
generated
vendored
67
vendor/k8s.io/apimachinery/pkg/util/remotecommand/constants.go
generated
vendored
@ -1,67 +0,0 @@
|
||||
/*
|
||||
Copyright 2016 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 remotecommand
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultStreamCreationTimeout = 30 * time.Second
|
||||
|
||||
// The SPDY subprotocol "channel.k8s.io" is used for remote command
|
||||
// attachment/execution. This represents the initial unversioned subprotocol,
|
||||
// which has the known bugs https://issues.k8s.io/13394 and
|
||||
// https://issues.k8s.io/13395.
|
||||
StreamProtocolV1Name = "channel.k8s.io"
|
||||
|
||||
// The SPDY subprotocol "v2.channel.k8s.io" is used for remote command
|
||||
// attachment/execution. It is the second version of the subprotocol and
|
||||
// resolves the issues present in the first version.
|
||||
StreamProtocolV2Name = "v2.channel.k8s.io"
|
||||
|
||||
// The SPDY subprotocol "v3.channel.k8s.io" is used for remote command
|
||||
// attachment/execution. It is the third version of the subprotocol and
|
||||
// adds support for resizing container terminals.
|
||||
StreamProtocolV3Name = "v3.channel.k8s.io"
|
||||
|
||||
// The SPDY subprotocol "v4.channel.k8s.io" is used for remote command
|
||||
// attachment/execution. It is the 4th version of the subprotocol and
|
||||
// adds support for exit codes.
|
||||
StreamProtocolV4Name = "v4.channel.k8s.io"
|
||||
|
||||
// The subprotocol "v5.channel.k8s.io" is used for remote command
|
||||
// attachment/execution. It is the 5th version of the subprotocol and
|
||||
// adds support for a CLOSE signal.
|
||||
StreamProtocolV5Name = "v5.channel.k8s.io"
|
||||
|
||||
NonZeroExitCodeReason = metav1.StatusReason("NonZeroExitCode")
|
||||
ExitCodeCauseType = metav1.CauseType("ExitCode")
|
||||
|
||||
// RemoteCommand stream identifiers. The first three identifiers (for STDIN,
|
||||
// STDOUT, STDERR) are the same as their file descriptors.
|
||||
StreamStdIn = 0
|
||||
StreamStdOut = 1
|
||||
StreamStdErr = 2
|
||||
StreamErr = 3
|
||||
StreamResize = 4
|
||||
StreamClose = 255
|
||||
)
|
||||
|
||||
var SupportedStreamingProtocols = []string{StreamProtocolV4Name, StreamProtocolV3Name, StreamProtocolV2Name, StreamProtocolV1Name}
|
19
vendor/k8s.io/apimachinery/pkg/util/waitgroup/doc.go
generated
vendored
19
vendor/k8s.io/apimachinery/pkg/util/waitgroup/doc.go
generated
vendored
@ -1,19 +0,0 @@
|
||||
/*
|
||||
Copyright 2017 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 waitgroup implements SafeWaitGroup wrap of sync.WaitGroup.
|
||||
// Add with positive delta when waiting will fail, to prevent sync.WaitGroup race issue.
|
||||
package waitgroup // import "k8s.io/apimachinery/pkg/util/waitgroup"
|
134
vendor/k8s.io/apimachinery/pkg/util/waitgroup/ratelimited_waitgroup.go
generated
vendored
134
vendor/k8s.io/apimachinery/pkg/util/waitgroup/ratelimited_waitgroup.go
generated
vendored
@ -1,134 +0,0 @@
|
||||
/*
|
||||
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 waitgroup
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// RateLimiter abstracts the rate limiter used by RateLimitedSafeWaitGroup.
|
||||
// The implementation must be thread-safe.
|
||||
type RateLimiter interface {
|
||||
Wait(ctx context.Context) error
|
||||
}
|
||||
|
||||
// RateLimiterFactoryFunc is used by the RateLimitedSafeWaitGroup to create a new
|
||||
// instance of a RateLimiter that will be used to rate limit the return rate
|
||||
// of the active number of request(s). 'count' is the number of requests in
|
||||
// flight that are expected to invoke 'Done' on this wait group.
|
||||
type RateLimiterFactoryFunc func(count int) (RateLimiter, context.Context, context.CancelFunc)
|
||||
|
||||
// RateLimitedSafeWaitGroup must not be copied after first use.
|
||||
type RateLimitedSafeWaitGroup struct {
|
||||
wg sync.WaitGroup
|
||||
// Once Wait is initiated, all consecutive Done invocation will be
|
||||
// rate limited using this rate limiter.
|
||||
limiter RateLimiter
|
||||
stopCtx context.Context
|
||||
|
||||
mu sync.Mutex
|
||||
// wait indicate whether Wait is called, if true,
|
||||
// then any Add with positive delta will return error.
|
||||
wait bool
|
||||
// number of request(s) currently using the wait group
|
||||
count int
|
||||
}
|
||||
|
||||
// Add adds delta, which may be negative, similar to sync.WaitGroup.
|
||||
// If Add with a positive delta happens after Wait, it will return error,
|
||||
// which prevent unsafe Add.
|
||||
func (wg *RateLimitedSafeWaitGroup) Add(delta int) error {
|
||||
wg.mu.Lock()
|
||||
defer wg.mu.Unlock()
|
||||
|
||||
if wg.wait && delta > 0 {
|
||||
return fmt.Errorf("add with positive delta after Wait is forbidden")
|
||||
}
|
||||
wg.wg.Add(delta)
|
||||
wg.count += delta
|
||||
return nil
|
||||
}
|
||||
|
||||
// Done decrements the WaitGroup counter, rate limiting is applied only
|
||||
// when the wait group is in waiting mode.
|
||||
func (wg *RateLimitedSafeWaitGroup) Done() {
|
||||
var limiter RateLimiter
|
||||
func() {
|
||||
wg.mu.Lock()
|
||||
defer wg.mu.Unlock()
|
||||
|
||||
wg.count -= 1
|
||||
if wg.wait {
|
||||
// we are using the limiter outside the scope of the lock
|
||||
limiter = wg.limiter
|
||||
}
|
||||
}()
|
||||
|
||||
defer wg.wg.Done()
|
||||
if limiter != nil {
|
||||
limiter.Wait(wg.stopCtx)
|
||||
}
|
||||
}
|
||||
|
||||
// Wait blocks until the WaitGroup counter is zero or a hard limit has elapsed.
|
||||
// It returns the number of active request(s) accounted for at the time Wait
|
||||
// has been invoked, number of request(s) that have drianed (done using the
|
||||
// wait group immediately before Wait returns).
|
||||
// Ideally, the both numbers returned should be equal, to indicate that all
|
||||
// request(s) using the wait group have released their lock.
|
||||
func (wg *RateLimitedSafeWaitGroup) Wait(limiterFactory RateLimiterFactoryFunc) (int, int, error) {
|
||||
if limiterFactory == nil {
|
||||
return 0, 0, fmt.Errorf("rate limiter factory must be specified")
|
||||
}
|
||||
|
||||
var cancel context.CancelFunc
|
||||
var countNow, countAfter int
|
||||
func() {
|
||||
wg.mu.Lock()
|
||||
defer wg.mu.Unlock()
|
||||
|
||||
wg.limiter, wg.stopCtx, cancel = limiterFactory(wg.count)
|
||||
countNow = wg.count
|
||||
wg.wait = true
|
||||
}()
|
||||
|
||||
defer cancel()
|
||||
// there should be a hard stop, in case request(s) are not responsive
|
||||
// enough to invoke Done before the grace period is over.
|
||||
waitDoneCh := make(chan struct{})
|
||||
go func() {
|
||||
defer close(waitDoneCh)
|
||||
wg.wg.Wait()
|
||||
}()
|
||||
|
||||
var err error
|
||||
select {
|
||||
case <-wg.stopCtx.Done():
|
||||
err = wg.stopCtx.Err()
|
||||
case <-waitDoneCh:
|
||||
}
|
||||
|
||||
func() {
|
||||
wg.mu.Lock()
|
||||
defer wg.mu.Unlock()
|
||||
|
||||
countAfter = wg.count
|
||||
}()
|
||||
return countNow, countAfter, err
|
||||
}
|
57
vendor/k8s.io/apimachinery/pkg/util/waitgroup/waitgroup.go
generated
vendored
57
vendor/k8s.io/apimachinery/pkg/util/waitgroup/waitgroup.go
generated
vendored
@ -1,57 +0,0 @@
|
||||
/*
|
||||
Copyright 2017 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 waitgroup
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// SafeWaitGroup must not be copied after first use.
|
||||
type SafeWaitGroup struct {
|
||||
wg sync.WaitGroup
|
||||
mu sync.RWMutex
|
||||
// wait indicate whether Wait is called, if true,
|
||||
// then any Add with positive delta will return error.
|
||||
wait bool
|
||||
}
|
||||
|
||||
// Add adds delta, which may be negative, similar to sync.WaitGroup.
|
||||
// If Add with a positive delta happens after Wait, it will return error,
|
||||
// which prevent unsafe Add.
|
||||
func (wg *SafeWaitGroup) Add(delta int) error {
|
||||
wg.mu.RLock()
|
||||
defer wg.mu.RUnlock()
|
||||
if wg.wait && delta > 0 {
|
||||
return fmt.Errorf("add with positive delta after Wait is forbidden")
|
||||
}
|
||||
wg.wg.Add(delta)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Done decrements the WaitGroup counter.
|
||||
func (wg *SafeWaitGroup) Done() {
|
||||
wg.wg.Done()
|
||||
}
|
||||
|
||||
// Wait blocks until the WaitGroup counter is zero.
|
||||
func (wg *SafeWaitGroup) Wait() {
|
||||
wg.mu.Lock()
|
||||
wg.wait = true
|
||||
wg.mu.Unlock()
|
||||
wg.wg.Wait()
|
||||
}
|
28
vendor/k8s.io/apimachinery/third_party/forked/golang/netutil/addr.go
generated
vendored
28
vendor/k8s.io/apimachinery/third_party/forked/golang/netutil/addr.go
generated
vendored
@ -1,28 +0,0 @@
|
||||
package netutil
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// FROM: http://golang.org/src/net/http/client.go
|
||||
// Given a string of the form "host", "host:port", or "[ipv6::address]:port",
|
||||
// return true if the string includes a port.
|
||||
func hasPort(s string) bool { return strings.LastIndex(s, ":") > strings.LastIndex(s, "]") }
|
||||
|
||||
// FROM: http://golang.org/src/net/http/transport.go
|
||||
var portMap = map[string]string{
|
||||
"http": "80",
|
||||
"https": "443",
|
||||
"socks5": "1080",
|
||||
}
|
||||
|
||||
// FROM: http://golang.org/src/net/http/transport.go
|
||||
// canonicalAddr returns url.Host but always with a ":port" suffix
|
||||
func CanonicalAddr(url *url.URL) string {
|
||||
addr := url.Host
|
||||
if !hasPort(addr) {
|
||||
return addr + ":" + portMap[url.Scheme]
|
||||
}
|
||||
return addr
|
||||
}
|
211
vendor/k8s.io/apiserver/pkg/admission/attributes.go
generated
vendored
211
vendor/k8s.io/apiserver/pkg/admission/attributes.go
generated
vendored
@ -1,211 +0,0 @@
|
||||
/*
|
||||
Copyright 2014 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 admission
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/validation"
|
||||
auditinternal "k8s.io/apiserver/pkg/apis/audit"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
)
|
||||
|
||||
type attributesRecord struct {
|
||||
kind schema.GroupVersionKind
|
||||
namespace string
|
||||
name string
|
||||
resource schema.GroupVersionResource
|
||||
subresource string
|
||||
operation Operation
|
||||
options runtime.Object
|
||||
dryRun bool
|
||||
object runtime.Object
|
||||
oldObject runtime.Object
|
||||
userInfo user.Info
|
||||
|
||||
// other elements are always accessed in single goroutine.
|
||||
// But ValidatingAdmissionWebhook add annotations concurrently.
|
||||
annotations map[string]annotation
|
||||
annotationsLock sync.RWMutex
|
||||
|
||||
reinvocationContext ReinvocationContext
|
||||
}
|
||||
|
||||
type annotation struct {
|
||||
level auditinternal.Level
|
||||
value string
|
||||
}
|
||||
|
||||
func NewAttributesRecord(object runtime.Object, oldObject runtime.Object, kind schema.GroupVersionKind, namespace, name string, resource schema.GroupVersionResource, subresource string, operation Operation, operationOptions runtime.Object, dryRun bool, userInfo user.Info) Attributes {
|
||||
return &attributesRecord{
|
||||
kind: kind,
|
||||
namespace: namespace,
|
||||
name: name,
|
||||
resource: resource,
|
||||
subresource: subresource,
|
||||
operation: operation,
|
||||
options: operationOptions,
|
||||
dryRun: dryRun,
|
||||
object: object,
|
||||
oldObject: oldObject,
|
||||
userInfo: userInfo,
|
||||
reinvocationContext: &reinvocationContext{},
|
||||
}
|
||||
}
|
||||
|
||||
func (record *attributesRecord) GetKind() schema.GroupVersionKind {
|
||||
return record.kind
|
||||
}
|
||||
|
||||
func (record *attributesRecord) GetNamespace() string {
|
||||
return record.namespace
|
||||
}
|
||||
|
||||
func (record *attributesRecord) GetName() string {
|
||||
return record.name
|
||||
}
|
||||
|
||||
func (record *attributesRecord) GetResource() schema.GroupVersionResource {
|
||||
return record.resource
|
||||
}
|
||||
|
||||
func (record *attributesRecord) GetSubresource() string {
|
||||
return record.subresource
|
||||
}
|
||||
|
||||
func (record *attributesRecord) GetOperation() Operation {
|
||||
return record.operation
|
||||
}
|
||||
|
||||
func (record *attributesRecord) GetOperationOptions() runtime.Object {
|
||||
return record.options
|
||||
}
|
||||
|
||||
func (record *attributesRecord) IsDryRun() bool {
|
||||
return record.dryRun
|
||||
}
|
||||
|
||||
func (record *attributesRecord) GetObject() runtime.Object {
|
||||
return record.object
|
||||
}
|
||||
|
||||
func (record *attributesRecord) GetOldObject() runtime.Object {
|
||||
return record.oldObject
|
||||
}
|
||||
|
||||
func (record *attributesRecord) GetUserInfo() user.Info {
|
||||
return record.userInfo
|
||||
}
|
||||
|
||||
// getAnnotations implements privateAnnotationsGetter.It's a private method used
|
||||
// by WithAudit decorator.
|
||||
func (record *attributesRecord) getAnnotations(maxLevel auditinternal.Level) map[string]string {
|
||||
record.annotationsLock.RLock()
|
||||
defer record.annotationsLock.RUnlock()
|
||||
|
||||
if record.annotations == nil {
|
||||
return nil
|
||||
}
|
||||
cp := make(map[string]string, len(record.annotations))
|
||||
for key, value := range record.annotations {
|
||||
if value.level.Less(maxLevel) || value.level == maxLevel {
|
||||
cp[key] = value.value
|
||||
}
|
||||
}
|
||||
return cp
|
||||
}
|
||||
|
||||
// AddAnnotation adds an annotation to attributesRecord with Metadata audit level
|
||||
func (record *attributesRecord) AddAnnotation(key, value string) error {
|
||||
return record.AddAnnotationWithLevel(key, value, auditinternal.LevelMetadata)
|
||||
}
|
||||
|
||||
func (record *attributesRecord) AddAnnotationWithLevel(key, value string, level auditinternal.Level) error {
|
||||
if err := checkKeyFormat(key); err != nil {
|
||||
return err
|
||||
}
|
||||
if level.Less(auditinternal.LevelMetadata) {
|
||||
return fmt.Errorf("admission annotations are not allowed to be set at audit level lower than Metadata, key: %q, level: %s", key, level)
|
||||
}
|
||||
record.annotationsLock.Lock()
|
||||
defer record.annotationsLock.Unlock()
|
||||
|
||||
if record.annotations == nil {
|
||||
record.annotations = make(map[string]annotation)
|
||||
}
|
||||
annotation := annotation{level: level, value: value}
|
||||
if v, ok := record.annotations[key]; ok && v != annotation {
|
||||
return fmt.Errorf("admission annotations are not allowd to be overwritten, key:%q, old value: %v, new value: %v", key, record.annotations[key], annotation)
|
||||
}
|
||||
record.annotations[key] = annotation
|
||||
return nil
|
||||
}
|
||||
|
||||
func (record *attributesRecord) GetReinvocationContext() ReinvocationContext {
|
||||
return record.reinvocationContext
|
||||
}
|
||||
|
||||
type reinvocationContext struct {
|
||||
// isReinvoke is true when admission plugins are being reinvoked
|
||||
isReinvoke bool
|
||||
// reinvokeRequested is true when an admission plugin requested a re-invocation of the chain
|
||||
reinvokeRequested bool
|
||||
// values stores reinvoke context values per plugin.
|
||||
values map[string]interface{}
|
||||
}
|
||||
|
||||
func (rc *reinvocationContext) IsReinvoke() bool {
|
||||
return rc.isReinvoke
|
||||
}
|
||||
|
||||
func (rc *reinvocationContext) SetIsReinvoke() {
|
||||
rc.isReinvoke = true
|
||||
}
|
||||
|
||||
func (rc *reinvocationContext) ShouldReinvoke() bool {
|
||||
return rc.reinvokeRequested
|
||||
}
|
||||
|
||||
func (rc *reinvocationContext) SetShouldReinvoke() {
|
||||
rc.reinvokeRequested = true
|
||||
}
|
||||
|
||||
func (rc *reinvocationContext) SetValue(plugin string, v interface{}) {
|
||||
if rc.values == nil {
|
||||
rc.values = map[string]interface{}{}
|
||||
}
|
||||
rc.values[plugin] = v
|
||||
}
|
||||
|
||||
func (rc *reinvocationContext) Value(plugin string) interface{} {
|
||||
return rc.values[plugin]
|
||||
}
|
||||
|
||||
func checkKeyFormat(key string) error {
|
||||
parts := strings.Split(key, "/")
|
||||
if len(parts) != 2 {
|
||||
return fmt.Errorf("annotation key has invalid format, the right format is a DNS subdomain prefix and '/' and key name. (e.g. 'podsecuritypolicy.admission.k8s.io/admit-policy')")
|
||||
}
|
||||
if msgs := validation.IsQualifiedName(key); len(msgs) != 0 {
|
||||
return fmt.Errorf("annotation key has invalid format %s. A qualified name like 'podsecuritypolicy.admission.k8s.io/admit-policy' is required.", strings.Join(msgs, ","))
|
||||
}
|
||||
return nil
|
||||
}
|
102
vendor/k8s.io/apiserver/pkg/admission/audit.go
generated
vendored
102
vendor/k8s.io/apiserver/pkg/admission/audit.go
generated
vendored
@ -1,102 +0,0 @@
|
||||
/*
|
||||
Copyright 2018 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 admission
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"k8s.io/apiserver/pkg/audit"
|
||||
)
|
||||
|
||||
// auditHandler logs annotations set by other admission handlers
|
||||
type auditHandler struct {
|
||||
Interface
|
||||
}
|
||||
|
||||
var _ Interface = &auditHandler{}
|
||||
var _ MutationInterface = &auditHandler{}
|
||||
var _ ValidationInterface = &auditHandler{}
|
||||
|
||||
// WithAudit is a decorator for a admission phase. It saves annotations
|
||||
// of attribute into the audit event. Attributes passed to the Admit and
|
||||
// Validate function must be instance of privateAnnotationsGetter or
|
||||
// AnnotationsGetter, otherwise an error is returned.
|
||||
func WithAudit(i Interface) Interface {
|
||||
if i == nil {
|
||||
return i
|
||||
}
|
||||
return &auditHandler{Interface: i}
|
||||
}
|
||||
|
||||
func (handler *auditHandler) Admit(ctx context.Context, a Attributes, o ObjectInterfaces) error {
|
||||
if !handler.Interface.Handles(a.GetOperation()) {
|
||||
return nil
|
||||
}
|
||||
if err := ensureAnnotationGetter(a); err != nil {
|
||||
return err
|
||||
}
|
||||
var err error
|
||||
if mutator, ok := handler.Interface.(MutationInterface); ok {
|
||||
err = mutator.Admit(ctx, a, o)
|
||||
handler.logAnnotations(ctx, a)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (handler *auditHandler) Validate(ctx context.Context, a Attributes, o ObjectInterfaces) error {
|
||||
if !handler.Interface.Handles(a.GetOperation()) {
|
||||
return nil
|
||||
}
|
||||
if err := ensureAnnotationGetter(a); err != nil {
|
||||
return err
|
||||
}
|
||||
var err error
|
||||
if validator, ok := handler.Interface.(ValidationInterface); ok {
|
||||
err = validator.Validate(ctx, a, o)
|
||||
handler.logAnnotations(ctx, a)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func ensureAnnotationGetter(a Attributes) error {
|
||||
_, okPrivate := a.(privateAnnotationsGetter)
|
||||
_, okPublic := a.(AnnotationsGetter)
|
||||
if okPrivate || okPublic {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("attributes must be an instance of privateAnnotationsGetter or AnnotationsGetter")
|
||||
}
|
||||
|
||||
func (handler *auditHandler) logAnnotations(ctx context.Context, a Attributes) {
|
||||
ae := audit.AuditEventFrom(ctx)
|
||||
if ae == nil {
|
||||
return
|
||||
}
|
||||
|
||||
var annotations map[string]string
|
||||
switch a := a.(type) {
|
||||
case privateAnnotationsGetter:
|
||||
annotations = a.getAnnotations(ae.Level)
|
||||
case AnnotationsGetter:
|
||||
annotations = a.GetAnnotations(ae.Level)
|
||||
default:
|
||||
// this will never happen, because we have already checked it in ensureAnnotationGetter
|
||||
}
|
||||
|
||||
audit.AddAuditAnnotationsMap(ctx, annotations)
|
||||
}
|
70
vendor/k8s.io/apiserver/pkg/admission/chain.go
generated
vendored
70
vendor/k8s.io/apiserver/pkg/admission/chain.go
generated
vendored
@ -1,70 +0,0 @@
|
||||
/*
|
||||
Copyright 2014 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 admission
|
||||
|
||||
import "context"
|
||||
|
||||
// chainAdmissionHandler is an instance of admission.NamedHandler that performs admission control using
|
||||
// a chain of admission handlers
|
||||
type chainAdmissionHandler []Interface
|
||||
|
||||
// NewChainHandler creates a new chain handler from an array of handlers. Used for testing.
|
||||
func NewChainHandler(handlers ...Interface) chainAdmissionHandler {
|
||||
return chainAdmissionHandler(handlers)
|
||||
}
|
||||
|
||||
// Admit performs an admission control check using a chain of handlers, and returns immediately on first error
|
||||
func (admissionHandler chainAdmissionHandler) Admit(ctx context.Context, a Attributes, o ObjectInterfaces) error {
|
||||
for _, handler := range admissionHandler {
|
||||
if !handler.Handles(a.GetOperation()) {
|
||||
continue
|
||||
}
|
||||
if mutator, ok := handler.(MutationInterface); ok {
|
||||
err := mutator.Admit(ctx, a, o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate performs an admission control check using a chain of handlers, and returns immediately on first error
|
||||
func (admissionHandler chainAdmissionHandler) Validate(ctx context.Context, a Attributes, o ObjectInterfaces) error {
|
||||
for _, handler := range admissionHandler {
|
||||
if !handler.Handles(a.GetOperation()) {
|
||||
continue
|
||||
}
|
||||
if validator, ok := handler.(ValidationInterface); ok {
|
||||
err := validator.Validate(ctx, a, o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Handles will return true if any of the handlers handles the given operation
|
||||
func (admissionHandler chainAdmissionHandler) Handles(operation Operation) bool {
|
||||
for _, handler := range admissionHandler {
|
||||
if handler.Handles(operation) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
174
vendor/k8s.io/apiserver/pkg/admission/config.go
generated
vendored
174
vendor/k8s.io/apiserver/pkg/admission/config.go
generated
vendored
@ -1,174 +0,0 @@
|
||||
/*
|
||||
Copyright 2017 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 admission
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
|
||||
"k8s.io/klog/v2"
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apiserver/pkg/apis/apiserver"
|
||||
apiserverv1 "k8s.io/apiserver/pkg/apis/apiserver/v1"
|
||||
)
|
||||
|
||||
func makeAbs(path, base string) (string, error) {
|
||||
if filepath.IsAbs(path) {
|
||||
return path, nil
|
||||
}
|
||||
if len(base) == 0 || base == "." {
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
base = cwd
|
||||
}
|
||||
return filepath.Join(base, path), nil
|
||||
}
|
||||
|
||||
// ReadAdmissionConfiguration reads the admission configuration at the specified path.
|
||||
// It returns the loaded admission configuration if the input file aligns with the required syntax.
|
||||
// If it does not align with the provided syntax, it returns a default configuration for the enumerated
|
||||
// set of pluginNames whose config location references the specified configFilePath.
|
||||
// It does this to preserve backward compatibility when admission control files were opaque.
|
||||
// It returns an error if the file did not exist.
|
||||
func ReadAdmissionConfiguration(pluginNames []string, configFilePath string, configScheme *runtime.Scheme) (ConfigProvider, error) {
|
||||
if configFilePath == "" {
|
||||
return configProvider{config: &apiserver.AdmissionConfiguration{}}, nil
|
||||
}
|
||||
// a file was provided, so we just read it.
|
||||
data, err := os.ReadFile(configFilePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to read admission control configuration from %q [%v]", configFilePath, err)
|
||||
}
|
||||
codecs := serializer.NewCodecFactory(configScheme, serializer.EnableStrict)
|
||||
decoder := codecs.UniversalDecoder()
|
||||
decodedObj, err := runtime.Decode(decoder, data)
|
||||
// we were able to decode the file successfully
|
||||
if err == nil {
|
||||
decodedConfig, ok := decodedObj.(*apiserver.AdmissionConfiguration)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unexpected type: %T", decodedObj)
|
||||
}
|
||||
baseDir := path.Dir(configFilePath)
|
||||
for i := range decodedConfig.Plugins {
|
||||
if decodedConfig.Plugins[i].Path == "" {
|
||||
continue
|
||||
}
|
||||
// we update relative file paths to absolute paths
|
||||
absPath, err := makeAbs(decodedConfig.Plugins[i].Path, baseDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
decodedConfig.Plugins[i].Path = absPath
|
||||
}
|
||||
return configProvider{
|
||||
config: decodedConfig,
|
||||
}, nil
|
||||
}
|
||||
// we got an error where the decode wasn't related to a missing type
|
||||
if !(runtime.IsMissingVersion(err) || runtime.IsMissingKind(err) || runtime.IsNotRegisteredError(err)) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Only tolerate load errors if the file appears to be one of the two legacy plugin configs
|
||||
unstructuredData := map[string]interface{}{}
|
||||
if err2 := yaml.Unmarshal(data, &unstructuredData); err2 != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, isLegacyImagePolicy := unstructuredData["imagePolicy"]
|
||||
_, isLegacyPodNodeSelector := unstructuredData["podNodeSelectorPluginConfig"]
|
||||
if !isLegacyImagePolicy && !isLegacyPodNodeSelector {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// convert the legacy format to the new admission control format
|
||||
// in order to preserve backwards compatibility, we set plugins that
|
||||
// previously read input from a non-versioned file configuration to the
|
||||
// current input file.
|
||||
legacyPluginsWithUnversionedConfig := sets.NewString("ImagePolicyWebhook", "PodNodeSelector")
|
||||
externalConfig := &apiserverv1.AdmissionConfiguration{}
|
||||
for _, pluginName := range pluginNames {
|
||||
if legacyPluginsWithUnversionedConfig.Has(pluginName) {
|
||||
externalConfig.Plugins = append(externalConfig.Plugins,
|
||||
apiserverv1.AdmissionPluginConfiguration{
|
||||
Name: pluginName,
|
||||
Path: configFilePath})
|
||||
}
|
||||
}
|
||||
configScheme.Default(externalConfig)
|
||||
internalConfig := &apiserver.AdmissionConfiguration{}
|
||||
if err := configScheme.Convert(externalConfig, internalConfig, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return configProvider{
|
||||
config: internalConfig,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type configProvider struct {
|
||||
config *apiserver.AdmissionConfiguration
|
||||
}
|
||||
|
||||
// GetAdmissionPluginConfigurationFor returns a reader that holds the admission plugin configuration.
|
||||
func GetAdmissionPluginConfigurationFor(pluginCfg apiserver.AdmissionPluginConfiguration) (io.Reader, error) {
|
||||
// if there is a nest object, return it directly
|
||||
if pluginCfg.Configuration != nil {
|
||||
return bytes.NewBuffer(pluginCfg.Configuration.Raw), nil
|
||||
}
|
||||
// there is nothing nested, so we delegate to path
|
||||
if pluginCfg.Path != "" {
|
||||
content, err := os.ReadFile(pluginCfg.Path)
|
||||
if err != nil {
|
||||
klog.Fatalf("Couldn't open admission plugin configuration %s: %#v", pluginCfg.Path, err)
|
||||
return nil, err
|
||||
}
|
||||
return bytes.NewBuffer(content), nil
|
||||
}
|
||||
// there is no special config at all
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// ConfigFor returns a reader for the specified plugin.
|
||||
// If no specific configuration is present, we return a nil reader.
|
||||
func (p configProvider) ConfigFor(pluginName string) (io.Reader, error) {
|
||||
// there is no config, so there is no potential config
|
||||
if p.config == nil {
|
||||
return nil, nil
|
||||
}
|
||||
// look for matching plugin and get configuration
|
||||
for _, pluginCfg := range p.config.Plugins {
|
||||
if pluginName != pluginCfg.Name {
|
||||
continue
|
||||
}
|
||||
pluginConfig, err := GetAdmissionPluginConfigurationFor(pluginCfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return pluginConfig, nil
|
||||
}
|
||||
// there is no registered config that matches on plugin name.
|
||||
return nil, nil
|
||||
}
|
166
vendor/k8s.io/apiserver/pkg/admission/configuration/configuration_manager.go
generated
vendored
166
vendor/k8s.io/apiserver/pkg/admission/configuration/configuration_manager.go
generated
vendored
@ -1,166 +0,0 @@
|
||||
/*
|
||||
Copyright 2017 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 configuration
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultInterval = 1 * time.Second
|
||||
defaultFailureThreshold = 5
|
||||
defaultBootstrapRetries = 5
|
||||
defaultBootstrapGraceperiod = 5 * time.Second
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNotReady = fmt.Errorf("configuration is not ready")
|
||||
ErrDisabled = fmt.Errorf("disabled")
|
||||
)
|
||||
|
||||
type getFunc func() (runtime.Object, error)
|
||||
|
||||
// When running, poller calls `get` every `interval`. If `get` is
|
||||
// successful, `Ready()` returns ready and `configuration()` returns the
|
||||
// `mergedConfiguration`; if `get` has failed more than `failureThreshold ` times,
|
||||
// `Ready()` returns not ready and `configuration()` returns nil configuration.
|
||||
// In an HA setup, the poller is consistent only if the `get` is
|
||||
// doing consistent read.
|
||||
type poller struct {
|
||||
// a function to consistently read the latest configuration
|
||||
get getFunc
|
||||
// consistent read interval
|
||||
// read-only
|
||||
interval time.Duration
|
||||
// if the number of consecutive read failure equals or exceeds the failureThreshold , the
|
||||
// configuration is regarded as not ready.
|
||||
// read-only
|
||||
failureThreshold int
|
||||
// number of consecutive failures so far.
|
||||
failures int
|
||||
// If the poller has passed the bootstrap phase. The poller is considered
|
||||
// bootstrapped either bootstrapGracePeriod after the first call of
|
||||
// configuration(), or when setConfigurationAndReady() is called, whichever
|
||||
// comes first.
|
||||
bootstrapped bool
|
||||
// configuration() retries bootstrapRetries times if poller is not bootstrapped
|
||||
// read-only
|
||||
bootstrapRetries int
|
||||
// Grace period for bootstrapping
|
||||
// read-only
|
||||
bootstrapGracePeriod time.Duration
|
||||
once sync.Once
|
||||
// if the configuration is regarded as ready.
|
||||
ready bool
|
||||
mergedConfiguration runtime.Object
|
||||
lastErr error
|
||||
// lock must be hold when reading/writing the data fields of poller.
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
func newPoller(get getFunc) *poller {
|
||||
p := poller{
|
||||
get: get,
|
||||
interval: defaultInterval,
|
||||
failureThreshold: defaultFailureThreshold,
|
||||
bootstrapRetries: defaultBootstrapRetries,
|
||||
bootstrapGracePeriod: defaultBootstrapGraceperiod,
|
||||
}
|
||||
return &p
|
||||
}
|
||||
|
||||
func (a *poller) lastError(err error) {
|
||||
a.lock.Lock()
|
||||
defer a.lock.Unlock()
|
||||
a.lastErr = err
|
||||
}
|
||||
|
||||
func (a *poller) notReady() {
|
||||
a.lock.Lock()
|
||||
defer a.lock.Unlock()
|
||||
a.ready = false
|
||||
}
|
||||
|
||||
func (a *poller) bootstrapping() {
|
||||
// bootstrapGracePeriod is read-only, so no lock is required
|
||||
timer := time.NewTimer(a.bootstrapGracePeriod)
|
||||
go func() {
|
||||
defer timer.Stop()
|
||||
<-timer.C
|
||||
a.lock.Lock()
|
||||
defer a.lock.Unlock()
|
||||
a.bootstrapped = true
|
||||
}()
|
||||
}
|
||||
|
||||
// If the poller is not bootstrapped yet, the configuration() gets a few chances
|
||||
// to retry. This hides transient failures during system startup.
|
||||
func (a *poller) configuration() (runtime.Object, error) {
|
||||
a.once.Do(a.bootstrapping)
|
||||
a.lock.RLock()
|
||||
defer a.lock.RUnlock()
|
||||
retries := 1
|
||||
if !a.bootstrapped {
|
||||
retries = a.bootstrapRetries
|
||||
}
|
||||
for count := 0; count < retries; count++ {
|
||||
if count > 0 {
|
||||
a.lock.RUnlock()
|
||||
time.Sleep(a.interval)
|
||||
a.lock.RLock()
|
||||
}
|
||||
if a.ready {
|
||||
return a.mergedConfiguration, nil
|
||||
}
|
||||
}
|
||||
if a.lastErr != nil {
|
||||
return nil, a.lastErr
|
||||
}
|
||||
return nil, ErrNotReady
|
||||
}
|
||||
|
||||
func (a *poller) setConfigurationAndReady(value runtime.Object) {
|
||||
a.lock.Lock()
|
||||
defer a.lock.Unlock()
|
||||
a.bootstrapped = true
|
||||
a.mergedConfiguration = value
|
||||
a.ready = true
|
||||
a.lastErr = nil
|
||||
}
|
||||
|
||||
func (a *poller) Run(stopCh <-chan struct{}) {
|
||||
go wait.Until(a.sync, a.interval, stopCh)
|
||||
}
|
||||
|
||||
func (a *poller) sync() {
|
||||
configuration, err := a.get()
|
||||
if err != nil {
|
||||
a.failures++
|
||||
a.lastError(err)
|
||||
if a.failures >= a.failureThreshold {
|
||||
a.notReady()
|
||||
}
|
||||
return
|
||||
}
|
||||
a.failures = 0
|
||||
a.setConfigurationAndReady(configuration)
|
||||
}
|
157
vendor/k8s.io/apiserver/pkg/admission/configuration/mutating_webhook_manager.go
generated
vendored
157
vendor/k8s.io/apiserver/pkg/admission/configuration/mutating_webhook_manager.go
generated
vendored
@ -1,157 +0,0 @@
|
||||
/*
|
||||
Copyright 2017 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 configuration
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
v1 "k8s.io/api/admissionregistration/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/generic"
|
||||
"k8s.io/client-go/informers"
|
||||
admissionregistrationlisters "k8s.io/client-go/listers/admissionregistration/v1"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
"k8s.io/client-go/tools/cache/synctrack"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
// Type for test injection.
|
||||
type mutatingWebhookAccessorCreator func(uid string, configurationName string, h *v1.MutatingWebhook) webhook.WebhookAccessor
|
||||
|
||||
// mutatingWebhookConfigurationManager collects the mutating webhook objects so that they can be called.
|
||||
type mutatingWebhookConfigurationManager struct {
|
||||
lister admissionregistrationlisters.MutatingWebhookConfigurationLister
|
||||
hasSynced func() bool
|
||||
lazy synctrack.Lazy[[]webhook.WebhookAccessor]
|
||||
configurationsCache sync.Map
|
||||
// createMutatingWebhookAccessor is used to instantiate webhook accessors.
|
||||
// This function is defined as field instead of a struct method to allow injection
|
||||
// during tests
|
||||
createMutatingWebhookAccessor mutatingWebhookAccessorCreator
|
||||
}
|
||||
|
||||
var _ generic.Source = &mutatingWebhookConfigurationManager{}
|
||||
|
||||
func NewMutatingWebhookConfigurationManager(f informers.SharedInformerFactory) generic.Source {
|
||||
informer := f.Admissionregistration().V1().MutatingWebhookConfigurations()
|
||||
manager := &mutatingWebhookConfigurationManager{
|
||||
lister: informer.Lister(),
|
||||
createMutatingWebhookAccessor: webhook.NewMutatingWebhookAccessor,
|
||||
}
|
||||
manager.lazy.Evaluate = manager.getConfiguration
|
||||
|
||||
handle, _ := informer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
|
||||
AddFunc: func(_ interface{}) { manager.lazy.Notify() },
|
||||
UpdateFunc: func(old, new interface{}) {
|
||||
obj := new.(*v1.MutatingWebhookConfiguration)
|
||||
manager.configurationsCache.Delete(obj.GetName())
|
||||
manager.lazy.Notify()
|
||||
},
|
||||
DeleteFunc: func(obj interface{}) {
|
||||
vwc, ok := obj.(*v1.MutatingWebhookConfiguration)
|
||||
if !ok {
|
||||
tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
|
||||
if !ok {
|
||||
klog.V(2).Infof("Couldn't get object from tombstone %#v", obj)
|
||||
return
|
||||
}
|
||||
vwc, ok = tombstone.Obj.(*v1.MutatingWebhookConfiguration)
|
||||
if !ok {
|
||||
klog.V(2).Infof("Tombstone contained object that is not expected %#v", obj)
|
||||
return
|
||||
}
|
||||
}
|
||||
manager.configurationsCache.Delete(vwc.Name)
|
||||
manager.lazy.Notify()
|
||||
},
|
||||
})
|
||||
manager.hasSynced = handle.HasSynced
|
||||
|
||||
return manager
|
||||
}
|
||||
|
||||
// Webhooks returns the merged MutatingWebhookConfiguration.
|
||||
func (m *mutatingWebhookConfigurationManager) Webhooks() []webhook.WebhookAccessor {
|
||||
out, err := m.lazy.Get()
|
||||
if err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("error getting webhook configuration: %v", err))
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// HasSynced returns true if the initial set of mutating webhook configurations
|
||||
// has been loaded.
|
||||
func (m *mutatingWebhookConfigurationManager) HasSynced() bool { return m.hasSynced() }
|
||||
|
||||
func (m *mutatingWebhookConfigurationManager) getConfiguration() ([]webhook.WebhookAccessor, error) {
|
||||
configurations, err := m.lister.List(labels.Everything())
|
||||
if err != nil {
|
||||
return []webhook.WebhookAccessor{}, err
|
||||
}
|
||||
return m.getMutatingWebhookConfigurations(configurations), nil
|
||||
}
|
||||
|
||||
// getMutatingWebhookConfigurations returns the webhook accessors for a given list of
|
||||
// mutating webhook configurations.
|
||||
//
|
||||
// This function will, first, try to load the webhook accessors from the cache and avoid
|
||||
// recreating them, which can be expessive (requiring CEL expression recompilation).
|
||||
func (m *mutatingWebhookConfigurationManager) getMutatingWebhookConfigurations(configurations []*v1.MutatingWebhookConfiguration) []webhook.WebhookAccessor {
|
||||
// The internal order of webhooks for each configuration is provided by the user
|
||||
// but configurations themselves can be in any order. As we are going to run these
|
||||
// webhooks in serial, they are sorted here to have a deterministic order.
|
||||
sort.SliceStable(configurations, MutatingWebhookConfigurationSorter(configurations).ByName)
|
||||
size := 0
|
||||
for _, cfg := range configurations {
|
||||
size += len(cfg.Webhooks)
|
||||
}
|
||||
accessors := make([]webhook.WebhookAccessor, 0, size)
|
||||
|
||||
for _, c := range configurations {
|
||||
cachedConfigurationAccessors, ok := m.configurationsCache.Load(c.Name)
|
||||
if ok {
|
||||
// Pick an already cached webhookAccessor
|
||||
accessors = append(accessors, cachedConfigurationAccessors.([]webhook.WebhookAccessor)...)
|
||||
continue
|
||||
}
|
||||
|
||||
// webhook names are not validated for uniqueness, so we check for duplicates and
|
||||
// add a int suffix to distinguish between them
|
||||
names := map[string]int{}
|
||||
configurationAccessors := make([]webhook.WebhookAccessor, 0, len(c.Webhooks))
|
||||
for i := range c.Webhooks {
|
||||
n := c.Webhooks[i].Name
|
||||
uid := fmt.Sprintf("%s/%s/%d", c.Name, n, names[n])
|
||||
names[n]++
|
||||
configurationAccessor := m.createMutatingWebhookAccessor(uid, c.Name, &c.Webhooks[i])
|
||||
configurationAccessors = append(configurationAccessors, configurationAccessor)
|
||||
}
|
||||
accessors = append(accessors, configurationAccessors...)
|
||||
m.configurationsCache.Store(c.Name, configurationAccessors)
|
||||
}
|
||||
return accessors
|
||||
}
|
||||
|
||||
type MutatingWebhookConfigurationSorter []*v1.MutatingWebhookConfiguration
|
||||
|
||||
func (a MutatingWebhookConfigurationSorter) ByName(i, j int) bool {
|
||||
return a[i].Name < a[j].Name
|
||||
}
|
155
vendor/k8s.io/apiserver/pkg/admission/configuration/validating_webhook_manager.go
generated
vendored
155
vendor/k8s.io/apiserver/pkg/admission/configuration/validating_webhook_manager.go
generated
vendored
@ -1,155 +0,0 @@
|
||||
/*
|
||||
Copyright 2017 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 configuration
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
v1 "k8s.io/api/admissionregistration/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/generic"
|
||||
"k8s.io/client-go/informers"
|
||||
admissionregistrationlisters "k8s.io/client-go/listers/admissionregistration/v1"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
"k8s.io/client-go/tools/cache/synctrack"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
// Type for test injection.
|
||||
type validatingWebhookAccessorCreator func(uid string, configurationName string, h *v1.ValidatingWebhook) webhook.WebhookAccessor
|
||||
|
||||
// validatingWebhookConfigurationManager collects the validating webhook objects so that they can be called.
|
||||
type validatingWebhookConfigurationManager struct {
|
||||
lister admissionregistrationlisters.ValidatingWebhookConfigurationLister
|
||||
hasSynced func() bool
|
||||
lazy synctrack.Lazy[[]webhook.WebhookAccessor]
|
||||
configurationsCache sync.Map
|
||||
// createValidatingWebhookAccessor is used to instantiate webhook accessors.
|
||||
// This function is defined as field instead of a struct method to allow injection
|
||||
// during tests
|
||||
createValidatingWebhookAccessor validatingWebhookAccessorCreator
|
||||
}
|
||||
|
||||
var _ generic.Source = &validatingWebhookConfigurationManager{}
|
||||
|
||||
func NewValidatingWebhookConfigurationManager(f informers.SharedInformerFactory) generic.Source {
|
||||
informer := f.Admissionregistration().V1().ValidatingWebhookConfigurations()
|
||||
manager := &validatingWebhookConfigurationManager{
|
||||
lister: informer.Lister(),
|
||||
createValidatingWebhookAccessor: webhook.NewValidatingWebhookAccessor,
|
||||
}
|
||||
manager.lazy.Evaluate = manager.getConfiguration
|
||||
|
||||
handle, _ := informer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
|
||||
AddFunc: func(_ interface{}) { manager.lazy.Notify() },
|
||||
UpdateFunc: func(old, new interface{}) {
|
||||
obj := new.(*v1.ValidatingWebhookConfiguration)
|
||||
manager.configurationsCache.Delete(obj.GetName())
|
||||
manager.lazy.Notify()
|
||||
},
|
||||
DeleteFunc: func(obj interface{}) {
|
||||
vwc, ok := obj.(*v1.ValidatingWebhookConfiguration)
|
||||
if !ok {
|
||||
tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
|
||||
if !ok {
|
||||
klog.V(2).Infof("Couldn't get object from tombstone %#v", obj)
|
||||
return
|
||||
}
|
||||
vwc, ok = tombstone.Obj.(*v1.ValidatingWebhookConfiguration)
|
||||
if !ok {
|
||||
klog.V(2).Infof("Tombstone contained object that is not expected %#v", obj)
|
||||
return
|
||||
}
|
||||
}
|
||||
manager.configurationsCache.Delete(vwc.Name)
|
||||
manager.lazy.Notify()
|
||||
},
|
||||
})
|
||||
manager.hasSynced = handle.HasSynced
|
||||
|
||||
return manager
|
||||
}
|
||||
|
||||
// Webhooks returns the merged ValidatingWebhookConfiguration.
|
||||
func (v *validatingWebhookConfigurationManager) Webhooks() []webhook.WebhookAccessor {
|
||||
out, err := v.lazy.Get()
|
||||
if err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("error getting webhook configuration: %v", err))
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// HasSynced returns true if the initial set of validating webhook configurations
|
||||
// has been loaded.
|
||||
func (v *validatingWebhookConfigurationManager) HasSynced() bool { return v.hasSynced() }
|
||||
|
||||
func (v *validatingWebhookConfigurationManager) getConfiguration() ([]webhook.WebhookAccessor, error) {
|
||||
configurations, err := v.lister.List(labels.Everything())
|
||||
if err != nil {
|
||||
return []webhook.WebhookAccessor{}, err
|
||||
}
|
||||
return v.getValidatingWebhookConfigurations(configurations), nil
|
||||
}
|
||||
|
||||
// getMutatingWebhookConfigurations returns the webhook accessors for a given list of
|
||||
// mutating webhook configurations.
|
||||
//
|
||||
// This function will, first, try to load the webhook accessors from the cache and avoid
|
||||
// recreating them, which can be expessive (requiring CEL expression recompilation).
|
||||
func (v *validatingWebhookConfigurationManager) getValidatingWebhookConfigurations(configurations []*v1.ValidatingWebhookConfiguration) []webhook.WebhookAccessor {
|
||||
sort.SliceStable(configurations, ValidatingWebhookConfigurationSorter(configurations).ByName)
|
||||
size := 0
|
||||
for _, cfg := range configurations {
|
||||
size += len(cfg.Webhooks)
|
||||
}
|
||||
accessors := make([]webhook.WebhookAccessor, 0, size)
|
||||
|
||||
for _, c := range configurations {
|
||||
cachedConfigurationAccessors, ok := v.configurationsCache.Load(c.Name)
|
||||
if ok {
|
||||
// Pick an already cached webhookAccessor
|
||||
accessors = append(accessors, cachedConfigurationAccessors.([]webhook.WebhookAccessor)...)
|
||||
continue
|
||||
}
|
||||
|
||||
// webhook names are not validated for uniqueness, so we check for duplicates and
|
||||
// add a int suffix to distinguish between them
|
||||
names := map[string]int{}
|
||||
configurationAccessors := make([]webhook.WebhookAccessor, 0, len(c.Webhooks))
|
||||
for i := range c.Webhooks {
|
||||
n := c.Webhooks[i].Name
|
||||
uid := fmt.Sprintf("%s/%s/%d", c.Name, n, names[n])
|
||||
names[n]++
|
||||
configurationAccessor := v.createValidatingWebhookAccessor(uid, c.Name, &c.Webhooks[i])
|
||||
configurationAccessors = append(configurationAccessors, configurationAccessor)
|
||||
}
|
||||
accessors = append(accessors, configurationAccessors...)
|
||||
v.configurationsCache.Store(c.Name, configurationAccessors)
|
||||
}
|
||||
|
||||
return accessors
|
||||
}
|
||||
|
||||
type ValidatingWebhookConfigurationSorter []*v1.ValidatingWebhookConfiguration
|
||||
|
||||
func (a ValidatingWebhookConfigurationSorter) ByName(i, j int) bool {
|
||||
return a[i].Name < a[j].Name
|
||||
}
|
136
vendor/k8s.io/apiserver/pkg/admission/conversion.go
generated
vendored
136
vendor/k8s.io/apiserver/pkg/admission/conversion.go
generated
vendored
@ -1,136 +0,0 @@
|
||||
/*
|
||||
Copyright 2017 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 admission
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
// VersionedAttributes is a wrapper around the original admission attributes, adding versioned
|
||||
// variants of the object and old object.
|
||||
type VersionedAttributes struct {
|
||||
// Attributes holds the original admission attributes
|
||||
Attributes
|
||||
// VersionedOldObject holds Attributes.OldObject (if non-nil), converted to VersionedKind.
|
||||
// It must never be mutated.
|
||||
VersionedOldObject runtime.Object
|
||||
// VersionedObject holds Attributes.Object (if non-nil), converted to VersionedKind.
|
||||
// If mutated, Dirty must be set to true by the mutator.
|
||||
VersionedObject runtime.Object
|
||||
// VersionedKind holds the fully qualified kind
|
||||
VersionedKind schema.GroupVersionKind
|
||||
// Dirty indicates VersionedObject has been modified since being converted from Attributes.Object
|
||||
Dirty bool
|
||||
}
|
||||
|
||||
// GetObject overrides the Attributes.GetObject()
|
||||
func (v *VersionedAttributes) GetObject() runtime.Object {
|
||||
if v.VersionedObject != nil {
|
||||
return v.VersionedObject
|
||||
}
|
||||
return v.Attributes.GetObject()
|
||||
}
|
||||
|
||||
// ConvertToGVK converts object to the desired gvk.
|
||||
func ConvertToGVK(obj runtime.Object, gvk schema.GroupVersionKind, o ObjectInterfaces) (runtime.Object, error) {
|
||||
// Unlike other resources, custom resources do not have internal version, so
|
||||
// if obj is a custom resource, it should not need conversion.
|
||||
if obj.GetObjectKind().GroupVersionKind() == gvk {
|
||||
return obj, nil
|
||||
}
|
||||
out, err := o.GetObjectCreater().New(gvk)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = o.GetObjectConvertor().Convert(obj, out, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Explicitly set the GVK
|
||||
out.GetObjectKind().SetGroupVersionKind(gvk)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// NewVersionedAttributes returns versioned attributes with the old and new object (if non-nil) converted to the requested kind
|
||||
func NewVersionedAttributes(attr Attributes, gvk schema.GroupVersionKind, o ObjectInterfaces) (*VersionedAttributes, error) {
|
||||
// convert the old and new objects to the requested version
|
||||
versionedAttr := &VersionedAttributes{
|
||||
Attributes: attr,
|
||||
VersionedKind: gvk,
|
||||
}
|
||||
if oldObj := attr.GetOldObject(); oldObj != nil {
|
||||
out, err := ConvertToGVK(oldObj, gvk, o)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
versionedAttr.VersionedOldObject = out
|
||||
}
|
||||
if obj := attr.GetObject(); obj != nil {
|
||||
out, err := ConvertToGVK(obj, gvk, o)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
versionedAttr.VersionedObject = out
|
||||
}
|
||||
return versionedAttr, nil
|
||||
}
|
||||
|
||||
// ConvertVersionedAttributes converts VersionedObject and VersionedOldObject to the specified kind, if needed.
|
||||
// If attr.VersionedKind already matches the requested kind, no conversion is performed.
|
||||
// If conversion is required:
|
||||
// * attr.VersionedObject is used as the source for the new object if Dirty=true (and is round-tripped through attr.Attributes.Object, clearing Dirty in the process)
|
||||
// * attr.Attributes.Object is used as the source for the new object if Dirty=false
|
||||
// * attr.Attributes.OldObject is used as the source for the old object
|
||||
func ConvertVersionedAttributes(attr *VersionedAttributes, gvk schema.GroupVersionKind, o ObjectInterfaces) error {
|
||||
// we already have the desired kind, we're done
|
||||
if attr.VersionedKind == gvk {
|
||||
return nil
|
||||
}
|
||||
|
||||
// convert the original old object to the desired GVK
|
||||
if oldObj := attr.Attributes.GetOldObject(); oldObj != nil {
|
||||
out, err := ConvertToGVK(oldObj, gvk, o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
attr.VersionedOldObject = out
|
||||
}
|
||||
|
||||
if attr.VersionedObject != nil {
|
||||
// convert the existing versioned object to internal
|
||||
if attr.Dirty {
|
||||
err := o.GetObjectConvertor().Convert(attr.VersionedObject, attr.Attributes.GetObject(), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// and back to external
|
||||
out, err := ConvertToGVK(attr.Attributes.GetObject(), gvk, o)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
attr.VersionedObject = out
|
||||
}
|
||||
|
||||
// Remember we converted to this version
|
||||
attr.VersionedKind = gvk
|
||||
attr.Dirty = false
|
||||
|
||||
return nil
|
||||
}
|
39
vendor/k8s.io/apiserver/pkg/admission/decorator.go
generated
vendored
39
vendor/k8s.io/apiserver/pkg/admission/decorator.go
generated
vendored
@ -1,39 +0,0 @@
|
||||
/*
|
||||
Copyright 2018 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 admission
|
||||
|
||||
type Decorator interface {
|
||||
Decorate(handler Interface, name string) Interface
|
||||
}
|
||||
|
||||
type DecoratorFunc func(handler Interface, name string) Interface
|
||||
|
||||
func (d DecoratorFunc) Decorate(handler Interface, name string) Interface {
|
||||
return d(handler, name)
|
||||
}
|
||||
|
||||
type Decorators []Decorator
|
||||
|
||||
// Decorate applies the decorator in inside-out order, i.e. the first decorator in the slice is first applied to the given handler.
|
||||
func (d Decorators) Decorate(handler Interface, name string) Interface {
|
||||
result := handler
|
||||
for _, d := range d {
|
||||
result = d.Decorate(result, name)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
72
vendor/k8s.io/apiserver/pkg/admission/errors.go
generated
vendored
72
vendor/k8s.io/apiserver/pkg/admission/errors.go
generated
vendored
@ -1,72 +0,0 @@
|
||||
/*
|
||||
Copyright 2015 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 admission
|
||||
|
||||
import (
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||
)
|
||||
|
||||
func extractResourceName(a Attributes) (name string, resource schema.GroupResource, err error) {
|
||||
resource = a.GetResource().GroupResource()
|
||||
|
||||
if len(a.GetName()) > 0 {
|
||||
return a.GetName(), resource, nil
|
||||
}
|
||||
|
||||
name = "Unknown"
|
||||
obj := a.GetObject()
|
||||
if obj != nil {
|
||||
accessor, err := meta.Accessor(obj)
|
||||
if err != nil {
|
||||
// not all object have ObjectMeta. If we don't, return a name with a slash (always illegal)
|
||||
return "Unknown/errorGettingName", resource, nil
|
||||
}
|
||||
|
||||
// this is necessary because name object name generation has not occurred yet
|
||||
if len(accessor.GetName()) > 0 {
|
||||
name = accessor.GetName()
|
||||
} else if len(accessor.GetGenerateName()) > 0 {
|
||||
name = accessor.GetGenerateName()
|
||||
}
|
||||
}
|
||||
return name, resource, nil
|
||||
}
|
||||
|
||||
// NewForbidden is a utility function to return a well-formatted admission control error response
|
||||
func NewForbidden(a Attributes, internalError error) error {
|
||||
// do not double wrap an error of same type
|
||||
if apierrors.IsForbidden(internalError) {
|
||||
return internalError
|
||||
}
|
||||
name, resource, err := extractResourceName(a)
|
||||
if err != nil {
|
||||
return apierrors.NewInternalError(utilerrors.NewAggregate([]error{internalError, err}))
|
||||
}
|
||||
return apierrors.NewForbidden(resource, name, internalError)
|
||||
}
|
||||
|
||||
// NewNotFound is a utility function to return a well-formatted admission control error response
|
||||
func NewNotFound(a Attributes) error {
|
||||
name, resource, err := extractResourceName(a)
|
||||
if err != nil {
|
||||
return apierrors.NewInternalError(err)
|
||||
}
|
||||
return apierrors.NewNotFound(resource, name)
|
||||
}
|
79
vendor/k8s.io/apiserver/pkg/admission/handler.go
generated
vendored
79
vendor/k8s.io/apiserver/pkg/admission/handler.go
generated
vendored
@ -1,79 +0,0 @@
|
||||
/*
|
||||
Copyright 2015 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 admission
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
)
|
||||
|
||||
const (
|
||||
// timeToWaitForReady is the amount of time to wait to let an admission controller to be ready to satisfy a request.
|
||||
// this is useful when admission controllers need to warm their caches before letting requests through.
|
||||
timeToWaitForReady = 10 * time.Second
|
||||
)
|
||||
|
||||
// ReadyFunc is a function that returns true if the admission controller is ready to handle requests.
|
||||
type ReadyFunc func() bool
|
||||
|
||||
// Handler is a base for admission control handlers that
|
||||
// support a predefined set of operations
|
||||
type Handler struct {
|
||||
operations sets.String
|
||||
readyFunc ReadyFunc
|
||||
}
|
||||
|
||||
// Handles returns true for methods that this handler supports
|
||||
func (h *Handler) Handles(operation Operation) bool {
|
||||
return h.operations.Has(string(operation))
|
||||
}
|
||||
|
||||
// NewHandler creates a new base handler that handles the passed
|
||||
// in operations
|
||||
func NewHandler(ops ...Operation) *Handler {
|
||||
operations := sets.NewString()
|
||||
for _, op := range ops {
|
||||
operations.Insert(string(op))
|
||||
}
|
||||
return &Handler{
|
||||
operations: operations,
|
||||
}
|
||||
}
|
||||
|
||||
// SetReadyFunc allows late registration of a ReadyFunc to know if the handler is ready to process requests.
|
||||
func (h *Handler) SetReadyFunc(readyFunc ReadyFunc) {
|
||||
h.readyFunc = readyFunc
|
||||
}
|
||||
|
||||
// WaitForReady will wait for the readyFunc (if registered) to return ready, and in case of timeout, will return false.
|
||||
func (h *Handler) WaitForReady() bool {
|
||||
// there is no ready func configured, so we return immediately
|
||||
if h.readyFunc == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
timeout := time.After(timeToWaitForReady)
|
||||
for !h.readyFunc() {
|
||||
select {
|
||||
case <-time.After(100 * time.Millisecond):
|
||||
case <-timeout:
|
||||
return h.readyFunc()
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
95
vendor/k8s.io/apiserver/pkg/admission/initializer/initializer.go
generated
vendored
95
vendor/k8s.io/apiserver/pkg/admission/initializer/initializer.go
generated
vendored
@ -1,95 +0,0 @@
|
||||
/*
|
||||
Copyright 2017 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 initializer
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
"k8s.io/client-go/dynamic"
|
||||
"k8s.io/client-go/informers"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/component-base/featuregate"
|
||||
)
|
||||
|
||||
type pluginInitializer struct {
|
||||
externalClient kubernetes.Interface
|
||||
dynamicClient dynamic.Interface
|
||||
externalInformers informers.SharedInformerFactory
|
||||
authorizer authorizer.Authorizer
|
||||
featureGates featuregate.FeatureGate
|
||||
stopCh <-chan struct{}
|
||||
restMapper meta.RESTMapper
|
||||
}
|
||||
|
||||
// New creates an instance of admission plugins initializer.
|
||||
// This constructor is public with a long param list so that callers immediately know that new information can be expected
|
||||
// during compilation when they update a level.
|
||||
func New(
|
||||
extClientset kubernetes.Interface,
|
||||
dynamicClient dynamic.Interface,
|
||||
extInformers informers.SharedInformerFactory,
|
||||
authz authorizer.Authorizer,
|
||||
featureGates featuregate.FeatureGate,
|
||||
stopCh <-chan struct{},
|
||||
restMapper meta.RESTMapper,
|
||||
) pluginInitializer {
|
||||
return pluginInitializer{
|
||||
externalClient: extClientset,
|
||||
dynamicClient: dynamicClient,
|
||||
externalInformers: extInformers,
|
||||
authorizer: authz,
|
||||
featureGates: featureGates,
|
||||
stopCh: stopCh,
|
||||
restMapper: restMapper,
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize checks the initialization interfaces implemented by a plugin
|
||||
// and provide the appropriate initialization data
|
||||
func (i pluginInitializer) Initialize(plugin admission.Interface) {
|
||||
// First tell the plugin about drained notification, so it can pass it to further initializations.
|
||||
if wants, ok := plugin.(WantsDrainedNotification); ok {
|
||||
wants.SetDrainedNotification(i.stopCh)
|
||||
}
|
||||
|
||||
// Second tell the plugin about enabled features, so it can decide whether to start informers or not
|
||||
if wants, ok := plugin.(WantsFeatures); ok {
|
||||
wants.InspectFeatureGates(i.featureGates)
|
||||
}
|
||||
|
||||
if wants, ok := plugin.(WantsExternalKubeClientSet); ok {
|
||||
wants.SetExternalKubeClientSet(i.externalClient)
|
||||
}
|
||||
|
||||
if wants, ok := plugin.(WantsDynamicClient); ok {
|
||||
wants.SetDynamicClient(i.dynamicClient)
|
||||
}
|
||||
|
||||
if wants, ok := plugin.(WantsExternalKubeInformerFactory); ok {
|
||||
wants.SetExternalKubeInformerFactory(i.externalInformers)
|
||||
}
|
||||
|
||||
if wants, ok := plugin.(WantsAuthorizer); ok {
|
||||
wants.SetAuthorizer(i.authorizer)
|
||||
}
|
||||
if wants, ok := plugin.(WantsRESTMapper); ok {
|
||||
wants.SetRESTMapper(i.restMapper)
|
||||
}
|
||||
}
|
||||
|
||||
var _ admission.PluginInitializer = pluginInitializer{}
|
99
vendor/k8s.io/apiserver/pkg/admission/initializer/interfaces.go
generated
vendored
99
vendor/k8s.io/apiserver/pkg/admission/initializer/interfaces.go
generated
vendored
@ -1,99 +0,0 @@
|
||||
/*
|
||||
Copyright 2017 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 initializer
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"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"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/component-base/featuregate"
|
||||
)
|
||||
|
||||
// WantsExternalKubeClientSet defines a function which sets external ClientSet for admission plugins that need it
|
||||
type WantsExternalKubeClientSet interface {
|
||||
SetExternalKubeClientSet(kubernetes.Interface)
|
||||
admission.InitializationValidator
|
||||
}
|
||||
|
||||
// WantsExternalKubeInformerFactory defines a function which sets InformerFactory for admission plugins that need it
|
||||
type WantsExternalKubeInformerFactory interface {
|
||||
SetExternalKubeInformerFactory(informers.SharedInformerFactory)
|
||||
admission.InitializationValidator
|
||||
}
|
||||
|
||||
// WantsAuthorizer defines a function which sets Authorizer for admission plugins that need it.
|
||||
type WantsAuthorizer interface {
|
||||
SetAuthorizer(authorizer.Authorizer)
|
||||
admission.InitializationValidator
|
||||
}
|
||||
|
||||
// WantsQuotaConfiguration defines a function which sets quota configuration for admission plugins that need it.
|
||||
type WantsQuotaConfiguration interface {
|
||||
SetQuotaConfiguration(quota.Configuration)
|
||||
admission.InitializationValidator
|
||||
}
|
||||
|
||||
// WantsDrainedNotification defines a function which sets the notification of where the apiserver
|
||||
// has already been drained for admission plugins that need it.
|
||||
// After receiving that notification, Admit/Validate calls won't be called anymore.
|
||||
type WantsDrainedNotification interface {
|
||||
SetDrainedNotification(<-chan struct{})
|
||||
admission.InitializationValidator
|
||||
}
|
||||
|
||||
// WantsFeatureGate defines a function which passes the featureGates for inspection by an admission plugin.
|
||||
// Admission plugins should not hold a reference to the featureGates. Instead, they should query a particular one
|
||||
// and assign it to a simple bool in the admission plugin struct.
|
||||
//
|
||||
// func (a *admissionPlugin) InspectFeatureGates(features featuregate.FeatureGate){
|
||||
// a.myFeatureIsOn = features.Enabled("my-feature")
|
||||
// }
|
||||
type WantsFeatures interface {
|
||||
InspectFeatureGates(featuregate.FeatureGate)
|
||||
admission.InitializationValidator
|
||||
}
|
||||
|
||||
type WantsDynamicClient interface {
|
||||
SetDynamicClient(dynamic.Interface)
|
||||
admission.InitializationValidator
|
||||
}
|
||||
|
||||
// WantsRESTMapper defines a function which sets RESTMapper for admission plugins that need it.
|
||||
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
|
||||
}
|
||||
|
||||
// WantsExcludedAdmissionResources defines a function which sets the ExcludedAdmissionResources
|
||||
// for an admission plugin that needs it.
|
||||
type WantsExcludedAdmissionResources interface {
|
||||
SetExcludedAdmissionResources(excludedAdmissionResources []schema.GroupResource)
|
||||
admission.InitializationValidator
|
||||
}
|
172
vendor/k8s.io/apiserver/pkg/admission/interfaces.go
generated
vendored
172
vendor/k8s.io/apiserver/pkg/admission/interfaces.go
generated
vendored
@ -1,172 +0,0 @@
|
||||
/*
|
||||
Copyright 2014 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 admission
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
auditinternal "k8s.io/apiserver/pkg/apis/audit"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
)
|
||||
|
||||
// Attributes is an interface used by AdmissionController to get information about a request
|
||||
// that is used to make an admission decision.
|
||||
type Attributes interface {
|
||||
// GetName returns the name of the object as presented in the request. On a CREATE operation, the client
|
||||
// may omit name and rely on the server to generate the name. If that is the case, this method will return
|
||||
// the empty string
|
||||
GetName() string
|
||||
// GetNamespace is the namespace associated with the request (if any)
|
||||
GetNamespace() string
|
||||
// GetResource is the name of the resource being requested. This is not the kind. For example: pods
|
||||
GetResource() schema.GroupVersionResource
|
||||
// GetSubresource is the name of the subresource being requested. This is a different resource, scoped to the parent resource, but it may have a different kind.
|
||||
// For instance, /pods has the resource "pods" and the kind "Pod", while /pods/foo/status has the resource "pods", the sub resource "status", and the kind "Pod"
|
||||
// (because status operates on pods). The binding resource for a pod though may be /pods/foo/binding, which has resource "pods", subresource "binding", and kind "Binding".
|
||||
GetSubresource() string
|
||||
// GetOperation is the operation being performed
|
||||
GetOperation() Operation
|
||||
// GetOperationOptions is the options for the operation being performed
|
||||
GetOperationOptions() runtime.Object
|
||||
// IsDryRun indicates that modifications will definitely not be persisted for this request. This is to prevent
|
||||
// admission controllers with side effects and a method of reconciliation from being overwhelmed.
|
||||
// However, a value of false for this does not mean that the modification will be persisted, because it
|
||||
// could still be rejected by a subsequent validation step.
|
||||
IsDryRun() bool
|
||||
// GetObject is the object from the incoming request prior to default values being applied
|
||||
GetObject() runtime.Object
|
||||
// GetOldObject is the existing object. Only populated for UPDATE and DELETE requests.
|
||||
GetOldObject() runtime.Object
|
||||
// GetKind is the type of object being manipulated. For example: Pod
|
||||
GetKind() schema.GroupVersionKind
|
||||
// GetUserInfo is information about the requesting user
|
||||
GetUserInfo() user.Info
|
||||
|
||||
// AddAnnotation sets annotation according to key-value pair. The key should be qualified, e.g., podsecuritypolicy.admission.k8s.io/admit-policy, where
|
||||
// "podsecuritypolicy" is the name of the plugin, "admission.k8s.io" is the name of the organization, "admit-policy" is the key name.
|
||||
// An error is returned if the format of key is invalid. When trying to overwrite annotation with a new value, an error is returned.
|
||||
// Both ValidationInterface and MutationInterface are allowed to add Annotations.
|
||||
// By default, an annotation gets logged into audit event if the request's audit level is greater or
|
||||
// equal to Metadata.
|
||||
AddAnnotation(key, value string) error
|
||||
|
||||
// AddAnnotationWithLevel sets annotation according to key-value pair with additional intended audit level.
|
||||
// An Annotation gets logged into audit event if the request's audit level is greater or equal to the
|
||||
// intended audit level.
|
||||
AddAnnotationWithLevel(key, value string, level auditinternal.Level) error
|
||||
|
||||
// GetReinvocationContext tracks the admission request information relevant to the re-invocation policy.
|
||||
GetReinvocationContext() ReinvocationContext
|
||||
}
|
||||
|
||||
// ObjectInterfaces is an interface used by AdmissionController to get object interfaces
|
||||
// such as Converter or Defaulter. These interfaces are normally coming from Request Scope
|
||||
// to handle special cases like CRDs.
|
||||
type ObjectInterfaces interface {
|
||||
// GetObjectCreater is the ObjectCreator appropriate for the requested object.
|
||||
GetObjectCreater() runtime.ObjectCreater
|
||||
// GetObjectTyper is the ObjectTyper appropriate for the requested object.
|
||||
GetObjectTyper() runtime.ObjectTyper
|
||||
// GetObjectDefaulter is the ObjectDefaulter appropriate for the requested object.
|
||||
GetObjectDefaulter() runtime.ObjectDefaulter
|
||||
// GetObjectConvertor is the ObjectConvertor appropriate for the requested object.
|
||||
GetObjectConvertor() runtime.ObjectConvertor
|
||||
// GetEquivalentResourceMapper is the EquivalentResourceMapper appropriate for finding equivalent resources and expected kind for the requested object.
|
||||
GetEquivalentResourceMapper() runtime.EquivalentResourceMapper
|
||||
}
|
||||
|
||||
// privateAnnotationsGetter is a private interface which allows users to get annotations from Attributes.
|
||||
type privateAnnotationsGetter interface {
|
||||
getAnnotations(maxLevel auditinternal.Level) map[string]string
|
||||
}
|
||||
|
||||
// AnnotationsGetter allows users to get annotations from Attributes. An alternate Attribute should implement
|
||||
// this interface.
|
||||
type AnnotationsGetter interface {
|
||||
GetAnnotations(maxLevel auditinternal.Level) map[string]string
|
||||
}
|
||||
|
||||
// ReinvocationContext provides access to the admission related state required to implement the re-invocation policy.
|
||||
type ReinvocationContext interface {
|
||||
// IsReinvoke returns true if the current admission check is a re-invocation.
|
||||
IsReinvoke() bool
|
||||
// SetIsReinvoke sets the current admission check as a re-invocation.
|
||||
SetIsReinvoke()
|
||||
// ShouldReinvoke returns true if any plugin has requested a re-invocation.
|
||||
ShouldReinvoke() bool
|
||||
// SetShouldReinvoke signals that a re-invocation is desired.
|
||||
SetShouldReinvoke()
|
||||
// AddValue set a value for a plugin name, possibly overriding a previous value.
|
||||
SetValue(plugin string, v interface{})
|
||||
// Value reads a value for a webhook.
|
||||
Value(plugin string) interface{}
|
||||
}
|
||||
|
||||
// Interface is an abstract, pluggable interface for Admission Control decisions.
|
||||
type Interface interface {
|
||||
// Handles returns true if this admission controller can handle the given operation
|
||||
// where operation can be one of CREATE, UPDATE, DELETE, or CONNECT
|
||||
Handles(operation Operation) bool
|
||||
}
|
||||
|
||||
type MutationInterface interface {
|
||||
Interface
|
||||
|
||||
// Admit makes an admission decision based on the request attributes.
|
||||
// Context is used only for timeout/deadline/cancellation and tracing information.
|
||||
Admit(ctx context.Context, a Attributes, o ObjectInterfaces) (err error)
|
||||
}
|
||||
|
||||
// ValidationInterface is an abstract, pluggable interface for Admission Control decisions.
|
||||
type ValidationInterface interface {
|
||||
Interface
|
||||
|
||||
// Validate makes an admission decision based on the request attributes. It is NOT allowed to mutate
|
||||
// Context is used only for timeout/deadline/cancellation and tracing information.
|
||||
Validate(ctx context.Context, a Attributes, o ObjectInterfaces) (err error)
|
||||
}
|
||||
|
||||
// Operation is the type of resource operation being checked for admission control
|
||||
type Operation string
|
||||
|
||||
// Operation constants
|
||||
const (
|
||||
Create Operation = "CREATE"
|
||||
Update Operation = "UPDATE"
|
||||
Delete Operation = "DELETE"
|
||||
Connect Operation = "CONNECT"
|
||||
)
|
||||
|
||||
// PluginInitializer is used for initialization of shareable resources between admission plugins.
|
||||
// After initialization the resources have to be set separately
|
||||
type PluginInitializer interface {
|
||||
Initialize(plugin Interface)
|
||||
}
|
||||
|
||||
// InitializationValidator holds ValidateInitialization functions, which are responsible for validation of initialized
|
||||
// shared resources and should be implemented on admission plugins
|
||||
type InitializationValidator interface {
|
||||
ValidateInitialization() error
|
||||
}
|
||||
|
||||
// ConfigProvider provides a way to get configuration for an admission plugin based on its name
|
||||
type ConfigProvider interface {
|
||||
ConfigFor(pluginName string) (io.Reader, error)
|
||||
}
|
356
vendor/k8s.io/apiserver/pkg/admission/metrics/metrics.go
generated
vendored
356
vendor/k8s.io/apiserver/pkg/admission/metrics/metrics.go
generated
vendored
@ -1,356 +0,0 @@
|
||||
/*
|
||||
Copyright 2017 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 metrics
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/component-base/metrics"
|
||||
"k8s.io/component-base/metrics/legacyregistry"
|
||||
)
|
||||
|
||||
// WebhookRejectionErrorType defines different error types that happen in a webhook rejection.
|
||||
type WebhookRejectionErrorType string
|
||||
|
||||
const (
|
||||
namespace = "apiserver"
|
||||
subsystem = "admission"
|
||||
|
||||
// WebhookRejectionCallingWebhookError identifies a calling webhook error which causes
|
||||
// a webhook admission to reject a request
|
||||
WebhookRejectionCallingWebhookError WebhookRejectionErrorType = "calling_webhook_error"
|
||||
// WebhookRejectionAPIServerInternalError identifies an apiserver internal error which
|
||||
// causes a webhook admission to reject a request
|
||||
WebhookRejectionAPIServerInternalError WebhookRejectionErrorType = "apiserver_internal_error"
|
||||
// WebhookRejectionNoError identifies a webhook properly rejected a request
|
||||
WebhookRejectionNoError WebhookRejectionErrorType = "no_error"
|
||||
)
|
||||
|
||||
var (
|
||||
latencySummaryMaxAge = 5 * time.Hour
|
||||
|
||||
// Metrics provides access to all admission metrics.
|
||||
Metrics = newAdmissionMetrics()
|
||||
)
|
||||
|
||||
// ObserverFunc is a func that emits metrics.
|
||||
type ObserverFunc func(ctx context.Context, elapsed time.Duration, rejected bool, attr admission.Attributes, stepType string, extraLabels ...string)
|
||||
|
||||
const (
|
||||
kindWebhook = "webhook"
|
||||
kindPolicy = "policy"
|
||||
stepValidate = "validate"
|
||||
stepAdmit = "admit"
|
||||
)
|
||||
|
||||
// WithControllerMetrics is a decorator for named admission handlers.
|
||||
func WithControllerMetrics(i admission.Interface, name string) admission.Interface {
|
||||
return WithMetrics(i, Metrics.ObserveAdmissionController, name)
|
||||
}
|
||||
|
||||
// WithStepMetrics is a decorator for a whole admission phase, i.e. admit or validation.admission step.
|
||||
func WithStepMetrics(i admission.Interface) admission.Interface {
|
||||
return WithMetrics(i, Metrics.ObserveAdmissionStep)
|
||||
}
|
||||
|
||||
// WithMetrics is a decorator for admission handlers with a generic observer func.
|
||||
func WithMetrics(i admission.Interface, observer ObserverFunc, extraLabels ...string) admission.Interface {
|
||||
return &pluginHandlerWithMetrics{
|
||||
Interface: i,
|
||||
observer: observer,
|
||||
extraLabels: extraLabels,
|
||||
}
|
||||
}
|
||||
|
||||
// pluginHandlerWithMetrics decorates a admission handler with metrics.
|
||||
type pluginHandlerWithMetrics struct {
|
||||
admission.Interface
|
||||
observer ObserverFunc
|
||||
extraLabels []string
|
||||
}
|
||||
|
||||
// Admit performs a mutating admission control check and emit metrics.
|
||||
func (p pluginHandlerWithMetrics) Admit(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) error {
|
||||
mutatingHandler, ok := p.Interface.(admission.MutationInterface)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
err := mutatingHandler.Admit(ctx, a, o)
|
||||
p.observer(ctx, time.Since(start), err != nil, a, stepAdmit, p.extraLabels...)
|
||||
return err
|
||||
}
|
||||
|
||||
// Validate performs a non-mutating admission control check and emits metrics.
|
||||
func (p pluginHandlerWithMetrics) Validate(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) error {
|
||||
validatingHandler, ok := p.Interface.(admission.ValidationInterface)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
err := validatingHandler.Validate(ctx, a, o)
|
||||
p.observer(ctx, time.Since(start), err != nil, a, stepValidate, p.extraLabels...)
|
||||
return err
|
||||
}
|
||||
|
||||
// AdmissionMetrics instruments admission with prometheus metrics.
|
||||
type AdmissionMetrics struct {
|
||||
step *metricSet
|
||||
controller *metricSet
|
||||
webhook *metricSet
|
||||
webhookRejection *metrics.CounterVec
|
||||
webhookFailOpen *metrics.CounterVec
|
||||
webhookRequest *metrics.CounterVec
|
||||
matchConditionEvalErrors *metrics.CounterVec
|
||||
matchConditionExclusions *metrics.CounterVec
|
||||
matchConditionEvaluationSeconds *metricSet
|
||||
}
|
||||
|
||||
// newAdmissionMetrics create a new AdmissionMetrics, configured with default metric names.
|
||||
func newAdmissionMetrics() *AdmissionMetrics {
|
||||
// Admission metrics for a step of the admission flow. The entire admission flow is broken down into a series of steps
|
||||
// Each step is identified by a distinct type label value.
|
||||
// Use buckets ranging from 5 ms to 2.5 seconds.
|
||||
step := &metricSet{
|
||||
latencies: metrics.NewHistogramVec(
|
||||
&metrics.HistogramOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: "step_admission_duration_seconds",
|
||||
Help: "Admission sub-step latency histogram in seconds, broken out for each operation and API resource and step type (validate or admit).",
|
||||
Buckets: []float64{0.005, 0.025, 0.1, 0.5, 1.0, 2.5},
|
||||
StabilityLevel: metrics.STABLE,
|
||||
},
|
||||
[]string{"type", "operation", "rejected"},
|
||||
),
|
||||
|
||||
latenciesSummary: metrics.NewSummaryVec(
|
||||
&metrics.SummaryOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: "step_admission_duration_seconds_summary",
|
||||
Help: "Admission sub-step latency summary in seconds, broken out for each operation and API resource and step type (validate or admit).",
|
||||
MaxAge: latencySummaryMaxAge,
|
||||
StabilityLevel: metrics.ALPHA,
|
||||
},
|
||||
[]string{"type", "operation", "rejected"},
|
||||
),
|
||||
}
|
||||
|
||||
// Built-in admission controller metrics. Each admission controller is identified by name.
|
||||
// Use buckets ranging from 5 ms to 2.5 seconds.
|
||||
controller := &metricSet{
|
||||
latencies: metrics.NewHistogramVec(
|
||||
&metrics.HistogramOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: "controller_admission_duration_seconds",
|
||||
Help: "Admission controller latency histogram in seconds, identified by name and broken out for each operation and API resource and type (validate or admit).",
|
||||
Buckets: []float64{0.005, 0.025, 0.1, 0.5, 1.0, 2.5},
|
||||
StabilityLevel: metrics.STABLE,
|
||||
},
|
||||
[]string{"name", "type", "operation", "rejected"},
|
||||
),
|
||||
|
||||
latenciesSummary: nil,
|
||||
}
|
||||
|
||||
// Admission webhook metrics. Each webhook is identified by name.
|
||||
// Use buckets ranging from 5 ms to 2.5 seconds (admission webhooks timeout at 30 seconds by default).
|
||||
webhook := &metricSet{
|
||||
latencies: metrics.NewHistogramVec(
|
||||
&metrics.HistogramOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: "webhook_admission_duration_seconds",
|
||||
Help: "Admission webhook latency histogram in seconds, identified by name and broken out for each operation and API resource and type (validate or admit).",
|
||||
Buckets: []float64{0.005, 0.025, 0.1, 0.5, 1.0, 2.5, 10, 25},
|
||||
StabilityLevel: metrics.STABLE,
|
||||
},
|
||||
[]string{"name", "type", "operation", "rejected"},
|
||||
),
|
||||
|
||||
latenciesSummary: nil,
|
||||
}
|
||||
|
||||
webhookRejection := metrics.NewCounterVec(
|
||||
&metrics.CounterOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: "webhook_rejection_count",
|
||||
Help: "Admission webhook rejection count, identified by name and broken out for each admission type (validating or admit) and operation. Additional labels specify an error type (calling_webhook_error or apiserver_internal_error if an error occurred; no_error otherwise) and optionally a non-zero rejection code if the webhook rejects the request with an HTTP status code (honored by the apiserver when the code is greater or equal to 400). Codes greater than 600 are truncated to 600, to keep the metrics cardinality bounded.",
|
||||
StabilityLevel: metrics.ALPHA,
|
||||
},
|
||||
[]string{"name", "type", "operation", "error_type", "rejection_code"})
|
||||
|
||||
webhookFailOpen := metrics.NewCounterVec(
|
||||
&metrics.CounterOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: "webhook_fail_open_count",
|
||||
Help: "Admission webhook fail open count, identified by name and broken out for each admission type (validating or admit).",
|
||||
StabilityLevel: metrics.ALPHA,
|
||||
},
|
||||
[]string{"name", "type"})
|
||||
|
||||
webhookRequest := metrics.NewCounterVec(
|
||||
&metrics.CounterOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: "webhook_request_total",
|
||||
Help: "Admission webhook request total, identified by name and broken out for each admission type (validating or admit) and operation. Additional labels specify whether the request was rejected or not and an HTTP status code. Codes greater than 600 are truncated to 600, to keep the metrics cardinality bounded.",
|
||||
StabilityLevel: metrics.ALPHA,
|
||||
},
|
||||
[]string{"name", "type", "operation", "code", "rejected"})
|
||||
|
||||
matchConditionEvalError := metrics.NewCounterVec(
|
||||
&metrics.CounterOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: "match_condition_evaluation_errors_total",
|
||||
Help: "Admission match condition evaluation errors count, identified by name of resource containing the match condition and broken out for each kind containing matchConditions (webhook or policy), operation and admission type (validate or admit).",
|
||||
StabilityLevel: metrics.ALPHA,
|
||||
},
|
||||
[]string{"name", "kind", "type", "operation"})
|
||||
|
||||
matchConditionExclusions := metrics.NewCounterVec(
|
||||
&metrics.CounterOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: "match_condition_exclusions_total",
|
||||
Help: "Admission match condition evaluation exclusions count, identified by name of resource containing the match condition and broken out for each kind containing matchConditions (webhook or policy), operation and admission type (validate or admit).",
|
||||
StabilityLevel: metrics.ALPHA,
|
||||
},
|
||||
[]string{"name", "kind", "type", "operation"})
|
||||
|
||||
matchConditionEvaluationSeconds := &metricSet{
|
||||
latencies: metrics.NewHistogramVec(
|
||||
&metrics.HistogramOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: "match_condition_evaluation_seconds",
|
||||
Help: "Admission match condition evaluation time in seconds, identified by name and broken out for each kind containing matchConditions (webhook or policy), operation and type (validate or admit).",
|
||||
Buckets: []float64{0.001, 0.005, 0.01, 0.025, 0.1, 0.2, 0.25},
|
||||
StabilityLevel: metrics.ALPHA,
|
||||
},
|
||||
[]string{"name", "kind", "type", "operation"},
|
||||
),
|
||||
latenciesSummary: nil,
|
||||
}
|
||||
|
||||
step.mustRegister()
|
||||
controller.mustRegister()
|
||||
webhook.mustRegister()
|
||||
matchConditionEvaluationSeconds.mustRegister()
|
||||
legacyregistry.MustRegister(webhookRejection)
|
||||
legacyregistry.MustRegister(webhookFailOpen)
|
||||
legacyregistry.MustRegister(webhookRequest)
|
||||
legacyregistry.MustRegister(matchConditionEvalError)
|
||||
legacyregistry.MustRegister(matchConditionExclusions)
|
||||
return &AdmissionMetrics{step: step, controller: controller, webhook: webhook, webhookRejection: webhookRejection, webhookFailOpen: webhookFailOpen, webhookRequest: webhookRequest, matchConditionEvalErrors: matchConditionEvalError, matchConditionExclusions: matchConditionExclusions, matchConditionEvaluationSeconds: matchConditionEvaluationSeconds}
|
||||
}
|
||||
|
||||
func (m *AdmissionMetrics) reset() {
|
||||
m.step.reset()
|
||||
m.controller.reset()
|
||||
m.webhook.reset()
|
||||
}
|
||||
|
||||
// ObserveAdmissionStep records admission related metrics for a admission step, identified by step type.
|
||||
func (m *AdmissionMetrics) ObserveAdmissionStep(ctx context.Context, elapsed time.Duration, rejected bool, attr admission.Attributes, stepType string, extraLabels ...string) {
|
||||
m.step.observe(ctx, elapsed, append(extraLabels, stepType, string(attr.GetOperation()), strconv.FormatBool(rejected))...)
|
||||
}
|
||||
|
||||
// ObserveAdmissionController records admission related metrics for a built-in admission controller, identified by it's plugin handler name.
|
||||
func (m *AdmissionMetrics) ObserveAdmissionController(ctx context.Context, elapsed time.Duration, rejected bool, attr admission.Attributes, stepType string, extraLabels ...string) {
|
||||
m.controller.observe(ctx, elapsed, append(extraLabels, stepType, string(attr.GetOperation()), strconv.FormatBool(rejected))...)
|
||||
}
|
||||
|
||||
// ObserveWebhook records admission related metrics for a admission webhook.
|
||||
func (m *AdmissionMetrics) ObserveWebhook(ctx context.Context, name string, elapsed time.Duration, rejected bool, attr admission.Attributes, stepType string, code int) {
|
||||
// We truncate codes greater than 600 to keep the cardinality bounded.
|
||||
if code > 600 {
|
||||
code = 600
|
||||
}
|
||||
m.webhookRequest.WithContext(ctx).WithLabelValues(name, stepType, string(attr.GetOperation()), strconv.Itoa(code), strconv.FormatBool(rejected)).Inc()
|
||||
m.webhook.observe(ctx, elapsed, name, stepType, string(attr.GetOperation()), strconv.FormatBool(rejected))
|
||||
}
|
||||
|
||||
// ObserveWebhookRejection records admission related metrics for an admission webhook rejection.
|
||||
func (m *AdmissionMetrics) ObserveWebhookRejection(ctx context.Context, name, stepType, operation string, errorType WebhookRejectionErrorType, rejectionCode int) {
|
||||
// We truncate codes greater than 600 to keep the cardinality bounded.
|
||||
// This should be rarely done by a malfunctioning webhook server.
|
||||
if rejectionCode > 600 {
|
||||
rejectionCode = 600
|
||||
}
|
||||
m.webhookRejection.WithContext(ctx).WithLabelValues(name, stepType, operation, string(errorType), strconv.Itoa(rejectionCode)).Inc()
|
||||
}
|
||||
|
||||
// ObserveWebhookFailOpen records validating or mutating webhook that fail open.
|
||||
func (m *AdmissionMetrics) ObserveWebhookFailOpen(ctx context.Context, name, stepType string) {
|
||||
m.webhookFailOpen.WithContext(ctx).WithLabelValues(name, stepType).Inc()
|
||||
}
|
||||
|
||||
// ObserveMatchConditionEvalError records validating or mutating webhook that are not called due to match conditions
|
||||
func (m *AdmissionMetrics) ObserveMatchConditionEvalError(ctx context.Context, name, kind, stepType, operation string) {
|
||||
m.matchConditionEvalErrors.WithContext(ctx).WithLabelValues(name, kind, stepType, operation).Inc()
|
||||
}
|
||||
|
||||
// ObserveMatchConditionExclusion records validating or mutating webhook that are not called due to match conditions
|
||||
func (m *AdmissionMetrics) ObserveMatchConditionExclusion(ctx context.Context, name, kind, stepType, operation string) {
|
||||
m.matchConditionExclusions.WithContext(ctx).WithLabelValues(name, kind, stepType, operation).Inc()
|
||||
}
|
||||
|
||||
// ObserveMatchConditionEvaluationTime records duration of match condition evaluation process.
|
||||
func (m *AdmissionMetrics) ObserveMatchConditionEvaluationTime(ctx context.Context, elapsed time.Duration, name, kind, stepType, operation string) {
|
||||
m.matchConditionEvaluationSeconds.observe(ctx, elapsed, name, kind, stepType, operation)
|
||||
}
|
||||
|
||||
type metricSet struct {
|
||||
latencies *metrics.HistogramVec
|
||||
latenciesSummary *metrics.SummaryVec
|
||||
}
|
||||
|
||||
// MustRegister registers all the prometheus metrics in the metricSet.
|
||||
func (m *metricSet) mustRegister() {
|
||||
legacyregistry.MustRegister(m.latencies)
|
||||
if m.latenciesSummary != nil {
|
||||
legacyregistry.MustRegister(m.latenciesSummary)
|
||||
}
|
||||
}
|
||||
|
||||
// Reset resets all the prometheus metrics in the metricSet.
|
||||
func (m *metricSet) reset() {
|
||||
m.latencies.Reset()
|
||||
if m.latenciesSummary != nil {
|
||||
m.latenciesSummary.Reset()
|
||||
}
|
||||
}
|
||||
|
||||
// Observe records an observed admission event to all metrics in the metricSet.
|
||||
func (m *metricSet) observe(ctx context.Context, elapsed time.Duration, labels ...string) {
|
||||
elapsedSeconds := elapsed.Seconds()
|
||||
m.latencies.WithContext(ctx).WithLabelValues(labels...).Observe(elapsedSeconds)
|
||||
if m.latenciesSummary != nil {
|
||||
m.latenciesSummary.WithContext(ctx).WithLabelValues(labels...).Observe(elapsedSeconds)
|
||||
}
|
||||
}
|
155
vendor/k8s.io/apiserver/pkg/admission/plugin/authorizer/caching_authorizer.go
generated
vendored
155
vendor/k8s.io/apiserver/pkg/admission/plugin/authorizer/caching_authorizer.go
generated
vendored
@ -1,155 +0,0 @@
|
||||
/*
|
||||
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 authorizer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
)
|
||||
|
||||
type authzResult struct {
|
||||
authorized authorizer.Decision
|
||||
reason string
|
||||
err error
|
||||
}
|
||||
|
||||
type cachingAuthorizer struct {
|
||||
authorizer authorizer.Authorizer
|
||||
decisions map[string]authzResult
|
||||
}
|
||||
|
||||
// NewCachingAuthorizer returns an authorizer that caches decisions for the duration
|
||||
// of the authorizers use. Intended to be used for short-lived operations such as
|
||||
// the handling of a request in the admission chain, and then discarded.
|
||||
func NewCachingAuthorizer(in authorizer.Authorizer) authorizer.Authorizer {
|
||||
return &cachingAuthorizer{
|
||||
authorizer: in,
|
||||
decisions: make(map[string]authzResult),
|
||||
}
|
||||
}
|
||||
|
||||
// The attribute accessors known to cache key construction. If this fails to compile, the cache
|
||||
// implementation may need to be updated.
|
||||
var _ authorizer.Attributes = (interface {
|
||||
GetUser() user.Info
|
||||
GetVerb() string
|
||||
IsReadOnly() bool
|
||||
GetNamespace() string
|
||||
GetResource() string
|
||||
GetSubresource() string
|
||||
GetName() string
|
||||
GetAPIGroup() string
|
||||
GetAPIVersion() string
|
||||
IsResourceRequest() bool
|
||||
GetPath() string
|
||||
GetFieldSelector() (fields.Requirements, error)
|
||||
GetLabelSelector() (labels.Requirements, error)
|
||||
})(nil)
|
||||
|
||||
// The user info accessors known to cache key construction. If this fails to compile, the cache
|
||||
// implementation may need to be updated.
|
||||
var _ user.Info = (interface {
|
||||
GetName() string
|
||||
GetUID() string
|
||||
GetGroups() []string
|
||||
GetExtra() map[string][]string
|
||||
})(nil)
|
||||
|
||||
// Authorize returns an authorization decision by delegating to another Authorizer. If an equivalent
|
||||
// check has already been performed, a cached result is returned. Not safe for concurrent use.
|
||||
func (ca *cachingAuthorizer) Authorize(ctx context.Context, a authorizer.Attributes) (authorizer.Decision, string, error) {
|
||||
type SerializableAttributes struct {
|
||||
authorizer.AttributesRecord
|
||||
LabelSelector string
|
||||
}
|
||||
|
||||
serializableAttributes := SerializableAttributes{
|
||||
AttributesRecord: authorizer.AttributesRecord{
|
||||
Verb: a.GetVerb(),
|
||||
Namespace: a.GetNamespace(),
|
||||
APIGroup: a.GetAPIGroup(),
|
||||
APIVersion: a.GetAPIVersion(),
|
||||
Resource: a.GetResource(),
|
||||
Subresource: a.GetSubresource(),
|
||||
Name: a.GetName(),
|
||||
ResourceRequest: a.IsResourceRequest(),
|
||||
Path: a.GetPath(),
|
||||
},
|
||||
}
|
||||
// in the error case, we won't honor this field selector, so the cache doesn't need it.
|
||||
if fieldSelector, err := a.GetFieldSelector(); len(fieldSelector) > 0 {
|
||||
serializableAttributes.FieldSelectorRequirements, serializableAttributes.FieldSelectorParsingErr = fieldSelector, err
|
||||
}
|
||||
if labelSelector, _ := a.GetLabelSelector(); len(labelSelector) > 0 {
|
||||
// the labels requirements have private elements so those don't help us serialize to a unique key
|
||||
serializableAttributes.LabelSelector = labelSelector.String()
|
||||
}
|
||||
|
||||
if u := a.GetUser(); u != nil {
|
||||
di := &user.DefaultInfo{
|
||||
Name: u.GetName(),
|
||||
UID: u.GetUID(),
|
||||
}
|
||||
|
||||
// Differently-ordered groups or extras could cause otherwise-equivalent checks to
|
||||
// have distinct cache keys.
|
||||
if groups := u.GetGroups(); len(groups) > 0 {
|
||||
di.Groups = make([]string, len(groups))
|
||||
copy(di.Groups, groups)
|
||||
sort.Strings(di.Groups)
|
||||
}
|
||||
|
||||
if extra := u.GetExtra(); len(extra) > 0 {
|
||||
di.Extra = make(map[string][]string, len(extra))
|
||||
for k, vs := range extra {
|
||||
vdupe := make([]string, len(vs))
|
||||
copy(vdupe, vs)
|
||||
sort.Strings(vdupe)
|
||||
di.Extra[k] = vdupe
|
||||
}
|
||||
}
|
||||
|
||||
serializableAttributes.User = di
|
||||
}
|
||||
|
||||
var b strings.Builder
|
||||
if err := json.NewEncoder(&b).Encode(serializableAttributes); err != nil {
|
||||
return authorizer.DecisionNoOpinion, "", err
|
||||
}
|
||||
key := b.String()
|
||||
|
||||
if cached, ok := ca.decisions[key]; ok {
|
||||
return cached.authorized, cached.reason, cached.err
|
||||
}
|
||||
|
||||
authorized, reason, err := ca.authorizer.Authorize(ctx, a)
|
||||
|
||||
ca.decisions[key] = authzResult{
|
||||
authorized: authorized,
|
||||
reason: reason,
|
||||
err: err,
|
||||
}
|
||||
|
||||
return authorized, reason, err
|
||||
}
|
10
vendor/k8s.io/apiserver/pkg/admission/plugin/cel/OWNERS
generated
vendored
10
vendor/k8s.io/apiserver/pkg/admission/plugin/cel/OWNERS
generated
vendored
@ -1,10 +0,0 @@
|
||||
# See the OWNERS docs at https://go.k8s.io/owners
|
||||
|
||||
approvers:
|
||||
- jpbetz
|
||||
- cici37
|
||||
- alexzielenski
|
||||
reviewers:
|
||||
- jpbetz
|
||||
- cici37
|
||||
- alexzielenski
|
190
vendor/k8s.io/apiserver/pkg/admission/plugin/cel/activation.go
generated
vendored
190
vendor/k8s.io/apiserver/pkg/admission/plugin/cel/activation.go
generated
vendored
@ -1,190 +0,0 @@
|
||||
/*
|
||||
Copyright 2024 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 cel
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/google/cel-go/interpreter"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
admissionv1 "k8s.io/api/admission/v1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/cel"
|
||||
"k8s.io/apiserver/pkg/cel/library"
|
||||
)
|
||||
|
||||
// newActivation creates an activation for CEL admission plugins from the given request, admission chain and
|
||||
// variable binding information.
|
||||
func newActivation(compositionCtx CompositionContext, versionedAttr *admission.VersionedAttributes, request *admissionv1.AdmissionRequest, inputs OptionalVariableBindings, namespace *v1.Namespace) (*evaluationActivation, error) {
|
||||
oldObjectVal, err := objectToResolveVal(versionedAttr.VersionedOldObject)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to prepare oldObject variable for evaluation: %w", err)
|
||||
}
|
||||
objectVal, err := objectToResolveVal(versionedAttr.VersionedObject)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to prepare object variable for evaluation: %w", err)
|
||||
}
|
||||
var paramsVal, authorizerVal, requestResourceAuthorizerVal any
|
||||
if inputs.VersionedParams != nil {
|
||||
paramsVal, err = objectToResolveVal(inputs.VersionedParams)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to prepare params variable for evaluation: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if inputs.Authorizer != nil {
|
||||
authorizerVal = library.NewAuthorizerVal(versionedAttr.GetUserInfo(), inputs.Authorizer)
|
||||
requestResourceAuthorizerVal = library.NewResourceAuthorizerVal(versionedAttr.GetUserInfo(), inputs.Authorizer, versionedAttr)
|
||||
}
|
||||
|
||||
requestVal, err := convertObjectToUnstructured(request)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to prepare request variable for evaluation: %w", err)
|
||||
}
|
||||
namespaceVal, err := objectToResolveVal(namespace)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to prepare namespace variable for evaluation: %w", err)
|
||||
}
|
||||
va := &evaluationActivation{
|
||||
object: objectVal,
|
||||
oldObject: oldObjectVal,
|
||||
params: paramsVal,
|
||||
request: requestVal.Object,
|
||||
namespace: namespaceVal,
|
||||
authorizer: authorizerVal,
|
||||
requestResourceAuthorizer: requestResourceAuthorizerVal,
|
||||
}
|
||||
|
||||
// composition is an optional feature that only applies for ValidatingAdmissionPolicy and MutatingAdmissionPolicy.
|
||||
if compositionCtx != nil {
|
||||
va.variables = compositionCtx.Variables(va)
|
||||
}
|
||||
return va, nil
|
||||
}
|
||||
|
||||
type evaluationActivation struct {
|
||||
object, oldObject, params, request, namespace, authorizer, requestResourceAuthorizer, variables interface{}
|
||||
}
|
||||
|
||||
// ResolveName returns a value from the activation by qualified name, or false if the name
|
||||
// could not be found.
|
||||
func (a *evaluationActivation) ResolveName(name string) (interface{}, bool) {
|
||||
switch name {
|
||||
case ObjectVarName:
|
||||
return a.object, true
|
||||
case OldObjectVarName:
|
||||
return a.oldObject, true
|
||||
case ParamsVarName:
|
||||
return a.params, true // params may be null
|
||||
case RequestVarName:
|
||||
return a.request, true
|
||||
case NamespaceVarName:
|
||||
return a.namespace, true
|
||||
case AuthorizerVarName:
|
||||
return a.authorizer, a.authorizer != nil
|
||||
case RequestResourceAuthorizerVarName:
|
||||
return a.requestResourceAuthorizer, a.requestResourceAuthorizer != nil
|
||||
case VariableVarName: // variables always present
|
||||
return a.variables, true
|
||||
default:
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
|
||||
// Parent returns the parent of the current activation, may be nil.
|
||||
// If non-nil, the parent will be searched during resolve calls.
|
||||
func (a *evaluationActivation) Parent() interpreter.Activation {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Evaluate runs a compiled CEL admission plugin expression using the provided activation and CEL
|
||||
// runtime cost budget.
|
||||
func (a *evaluationActivation) Evaluate(ctx context.Context, compositionCtx CompositionContext, compilationResult CompilationResult, remainingBudget int64) (EvaluationResult, int64, error) {
|
||||
var evaluation = EvaluationResult{}
|
||||
if compilationResult.ExpressionAccessor == nil { // in case of placeholder
|
||||
return evaluation, remainingBudget, nil
|
||||
}
|
||||
|
||||
evaluation.ExpressionAccessor = compilationResult.ExpressionAccessor
|
||||
if compilationResult.Error != nil {
|
||||
evaluation.Error = &cel.Error{
|
||||
Type: cel.ErrorTypeInvalid,
|
||||
Detail: fmt.Sprintf("compilation error: %v", compilationResult.Error),
|
||||
Cause: compilationResult.Error,
|
||||
}
|
||||
return evaluation, remainingBudget, nil
|
||||
}
|
||||
if compilationResult.Program == nil {
|
||||
evaluation.Error = &cel.Error{
|
||||
Type: cel.ErrorTypeInternal,
|
||||
Detail: "unexpected internal error compiling expression",
|
||||
}
|
||||
return evaluation, remainingBudget, nil
|
||||
}
|
||||
t1 := time.Now()
|
||||
evalResult, evalDetails, err := compilationResult.Program.ContextEval(ctx, a)
|
||||
// budget may be spent due to lazy evaluation of composited variables
|
||||
if compositionCtx != nil {
|
||||
compositionCost := compositionCtx.GetAndResetCost()
|
||||
if compositionCost > remainingBudget {
|
||||
return evaluation, -1, &cel.Error{
|
||||
Type: cel.ErrorTypeInvalid,
|
||||
Detail: "validation failed due to running out of cost budget, no further validation rules will be run",
|
||||
Cause: cel.ErrOutOfBudget,
|
||||
}
|
||||
}
|
||||
remainingBudget -= compositionCost
|
||||
}
|
||||
elapsed := time.Since(t1)
|
||||
evaluation.Elapsed = elapsed
|
||||
if evalDetails == nil {
|
||||
return evaluation, -1, &cel.Error{
|
||||
Type: cel.ErrorTypeInternal,
|
||||
Detail: fmt.Sprintf("runtime cost could not be calculated for expression: %v, no further expression will be run", compilationResult.ExpressionAccessor.GetExpression()),
|
||||
}
|
||||
} else {
|
||||
rtCost := evalDetails.ActualCost()
|
||||
if rtCost == nil {
|
||||
return evaluation, -1, &cel.Error{
|
||||
Type: cel.ErrorTypeInvalid,
|
||||
Detail: fmt.Sprintf("runtime cost could not be calculated for expression: %v, no further expression will be run", compilationResult.ExpressionAccessor.GetExpression()),
|
||||
Cause: cel.ErrOutOfBudget,
|
||||
}
|
||||
} else {
|
||||
if *rtCost > math.MaxInt64 || int64(*rtCost) > remainingBudget {
|
||||
return evaluation, -1, &cel.Error{
|
||||
Type: cel.ErrorTypeInvalid,
|
||||
Detail: "validation failed due to running out of cost budget, no further validation rules will be run",
|
||||
Cause: cel.ErrOutOfBudget,
|
||||
}
|
||||
}
|
||||
remainingBudget -= int64(*rtCost)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
evaluation.Error = &cel.Error{
|
||||
Type: cel.ErrorTypeInvalid,
|
||||
Detail: fmt.Sprintf("expression '%v' resulted in error: %v", compilationResult.ExpressionAccessor.GetExpression(), err),
|
||||
}
|
||||
} else {
|
||||
evaluation.EvalResult = evalResult
|
||||
}
|
||||
return evaluation, remainingBudget, nil
|
||||
}
|
305
vendor/k8s.io/apiserver/pkg/admission/plugin/cel/compile.go
generated
vendored
305
vendor/k8s.io/apiserver/pkg/admission/plugin/cel/compile.go
generated
vendored
@ -1,305 +0,0 @@
|
||||
/*
|
||||
Copyright 2022 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 cel
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/google/cel-go/cel"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/version"
|
||||
celconfig "k8s.io/apiserver/pkg/apis/cel"
|
||||
apiservercel "k8s.io/apiserver/pkg/cel"
|
||||
"k8s.io/apiserver/pkg/cel/common"
|
||||
"k8s.io/apiserver/pkg/cel/environment"
|
||||
"k8s.io/apiserver/pkg/cel/library"
|
||||
"k8s.io/apiserver/pkg/cel/mutation"
|
||||
)
|
||||
|
||||
const (
|
||||
ObjectVarName = "object"
|
||||
OldObjectVarName = "oldObject"
|
||||
ParamsVarName = "params"
|
||||
RequestVarName = "request"
|
||||
NamespaceVarName = "namespaceObject"
|
||||
AuthorizerVarName = "authorizer"
|
||||
RequestResourceAuthorizerVarName = "authorizer.requestResource"
|
||||
VariableVarName = "variables"
|
||||
)
|
||||
|
||||
// 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 {
|
||||
field := func(name string, declType *apiservercel.DeclType, required bool) *apiservercel.DeclField {
|
||||
return apiservercel.NewDeclField(name, declType, required, nil, nil)
|
||||
}
|
||||
fields := func(fields ...*apiservercel.DeclField) map[string]*apiservercel.DeclField {
|
||||
result := make(map[string]*apiservercel.DeclField, len(fields))
|
||||
for _, f := range fields {
|
||||
result[f.Name] = f
|
||||
}
|
||||
return result
|
||||
}
|
||||
gvkType := apiservercel.NewObjectType("kubernetes.GroupVersionKind", fields(
|
||||
field("group", apiservercel.StringType, true),
|
||||
field("version", apiservercel.StringType, true),
|
||||
field("kind", apiservercel.StringType, true),
|
||||
))
|
||||
gvrType := apiservercel.NewObjectType("kubernetes.GroupVersionResource", fields(
|
||||
field("group", apiservercel.StringType, true),
|
||||
field("version", apiservercel.StringType, true),
|
||||
field("resource", apiservercel.StringType, true),
|
||||
))
|
||||
userInfoType := apiservercel.NewObjectType("kubernetes.UserInfo", fields(
|
||||
field("username", apiservercel.StringType, false),
|
||||
field("uid", apiservercel.StringType, false),
|
||||
field("groups", apiservercel.NewListType(apiservercel.StringType, -1), false),
|
||||
field("extra", apiservercel.NewMapType(apiservercel.StringType, apiservercel.NewListType(apiservercel.StringType, -1), -1), false),
|
||||
))
|
||||
return apiservercel.NewObjectType("kubernetes.AdmissionRequest", fields(
|
||||
field("kind", gvkType, true),
|
||||
field("resource", gvrType, true),
|
||||
field("subResource", apiservercel.StringType, false),
|
||||
field("requestKind", gvkType, true),
|
||||
field("requestResource", gvrType, true),
|
||||
field("requestSubResource", apiservercel.StringType, false),
|
||||
field("name", apiservercel.StringType, true),
|
||||
field("namespace", apiservercel.StringType, false),
|
||||
field("operation", apiservercel.StringType, true),
|
||||
field("userInfo", userInfoType, true),
|
||||
field("dryRun", apiservercel.BoolType, false),
|
||||
field("options", apiservercel.DynType, false),
|
||||
))
|
||||
}
|
||||
|
||||
// BuildNamespaceType generates a DeclType for Namespace.
|
||||
// Certain nested fields in Namespace (e.g. managedFields, ownerReferences etc.) are omitted in the generated DeclType
|
||||
// by design.
|
||||
func BuildNamespaceType() *apiservercel.DeclType {
|
||||
field := func(name string, declType *apiservercel.DeclType, required bool) *apiservercel.DeclField {
|
||||
return apiservercel.NewDeclField(name, declType, required, nil, nil)
|
||||
}
|
||||
fields := func(fields ...*apiservercel.DeclField) map[string]*apiservercel.DeclField {
|
||||
result := make(map[string]*apiservercel.DeclField, len(fields))
|
||||
for _, f := range fields {
|
||||
result[f.Name] = f
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
specType := apiservercel.NewObjectType("kubernetes.NamespaceSpec", fields(
|
||||
field("finalizers", apiservercel.NewListType(apiservercel.StringType, -1), true),
|
||||
))
|
||||
conditionType := apiservercel.NewObjectType("kubernetes.NamespaceCondition", fields(
|
||||
field("status", apiservercel.StringType, true),
|
||||
field("type", apiservercel.StringType, true),
|
||||
field("lastTransitionTime", apiservercel.TimestampType, true),
|
||||
field("message", apiservercel.StringType, true),
|
||||
field("reason", apiservercel.StringType, true),
|
||||
))
|
||||
statusType := apiservercel.NewObjectType("kubernetes.NamespaceStatus", fields(
|
||||
field("conditions", apiservercel.NewListType(conditionType, -1), true),
|
||||
field("phase", apiservercel.StringType, true),
|
||||
))
|
||||
metadataType := apiservercel.NewObjectType("kubernetes.NamespaceMetadata", fields(
|
||||
field("name", apiservercel.StringType, true),
|
||||
field("generateName", apiservercel.StringType, true),
|
||||
field("namespace", apiservercel.StringType, true),
|
||||
field("labels", apiservercel.NewMapType(apiservercel.StringType, apiservercel.StringType, -1), true),
|
||||
field("annotations", apiservercel.NewMapType(apiservercel.StringType, apiservercel.StringType, -1), true),
|
||||
field("UID", apiservercel.StringType, true),
|
||||
field("creationTimestamp", apiservercel.TimestampType, true),
|
||||
field("deletionGracePeriodSeconds", apiservercel.IntType, true),
|
||||
field("deletionTimestamp", apiservercel.TimestampType, true),
|
||||
field("generation", apiservercel.IntType, true),
|
||||
field("resourceVersion", apiservercel.StringType, true),
|
||||
field("finalizers", apiservercel.NewListType(apiservercel.StringType, -1), true),
|
||||
))
|
||||
return apiservercel.NewObjectType("kubernetes.Namespace", fields(
|
||||
field("metadata", metadataType, true),
|
||||
field("spec", specType, true),
|
||||
field("status", statusType, true),
|
||||
))
|
||||
}
|
||||
|
||||
// CompilationResult represents a compiled validations expression.
|
||||
type CompilationResult struct {
|
||||
Program cel.Program
|
||||
Error *apiservercel.Error
|
||||
ExpressionAccessor ExpressionAccessor
|
||||
OutputType *cel.Type
|
||||
}
|
||||
|
||||
// Compiler provides a CEL expression compiler configured with the desired admission related CEL variables and
|
||||
// environment mode.
|
||||
type Compiler interface {
|
||||
CompileCELExpression(expressionAccessor ExpressionAccessor, options OptionalVariableDeclarations, mode environment.Type) CompilationResult
|
||||
}
|
||||
|
||||
type compiler struct {
|
||||
varEnvs variableDeclEnvs
|
||||
}
|
||||
|
||||
func NewCompiler(env *environment.EnvSet) Compiler {
|
||||
return &compiler{varEnvs: mustBuildEnvs(env)}
|
||||
}
|
||||
|
||||
type variableDeclEnvs map[OptionalVariableDeclarations]*environment.EnvSet
|
||||
|
||||
// CompileCELExpression returns a compiled CEL expression.
|
||||
// perCallLimit was added for testing purpose only. Callers should always use const PerCallLimit from k8s.io/apiserver/pkg/apis/cel/config.go as input.
|
||||
func (c compiler) CompileCELExpression(expressionAccessor ExpressionAccessor, options OptionalVariableDeclarations, envType environment.Type) CompilationResult {
|
||||
resultError := func(errorString string, errType apiservercel.ErrorType, cause error) CompilationResult {
|
||||
return CompilationResult{
|
||||
Error: &apiservercel.Error{
|
||||
Type: errType,
|
||||
Detail: errorString,
|
||||
Cause: cause,
|
||||
},
|
||||
ExpressionAccessor: expressionAccessor,
|
||||
}
|
||||
}
|
||||
|
||||
env, err := c.varEnvs[options].Env(envType)
|
||||
if err != nil {
|
||||
return resultError(fmt.Sprintf("unexpected error loading CEL environment: %v", err), apiservercel.ErrorTypeInternal, nil)
|
||||
}
|
||||
|
||||
ast, issues := env.Compile(expressionAccessor.GetExpression())
|
||||
if issues != nil {
|
||||
return resultError("compilation failed: "+issues.String(), apiservercel.ErrorTypeInvalid, apiservercel.NewCompilationError(issues))
|
||||
}
|
||||
found := false
|
||||
returnTypes := expressionAccessor.ReturnTypes()
|
||||
for _, returnType := range returnTypes {
|
||||
if ast.OutputType().IsExactType(returnType) || cel.AnyType.IsExactType(returnType) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
var reason string
|
||||
if len(returnTypes) == 1 {
|
||||
reason = fmt.Sprintf("must evaluate to %v but got %v", returnTypes[0].String(), ast.OutputType().String())
|
||||
} else {
|
||||
reason = fmt.Sprintf("must evaluate to one of %v but got %v", returnTypes, ast.OutputType().String())
|
||||
}
|
||||
|
||||
return resultError(reason, apiservercel.ErrorTypeInvalid, nil)
|
||||
}
|
||||
|
||||
_, err = cel.AstToCheckedExpr(ast)
|
||||
if err != nil {
|
||||
// should be impossible since env.Compile returned no issues
|
||||
return resultError("unexpected compilation error: "+err.Error(), apiservercel.ErrorTypeInternal, nil)
|
||||
}
|
||||
prog, err := env.Program(ast,
|
||||
cel.InterruptCheckFrequency(celconfig.CheckFrequency),
|
||||
)
|
||||
if err != nil {
|
||||
return resultError("program instantiation failed: "+err.Error(), apiservercel.ErrorTypeInternal, nil)
|
||||
}
|
||||
return CompilationResult{
|
||||
Program: prog,
|
||||
ExpressionAccessor: expressionAccessor,
|
||||
OutputType: ast.OutputType(),
|
||||
}
|
||||
}
|
||||
|
||||
func mustBuildEnvs(baseEnv *environment.EnvSet) variableDeclEnvs {
|
||||
requestType := BuildRequestType()
|
||||
namespaceType := BuildNamespaceType()
|
||||
envs := make(variableDeclEnvs, 8) // since the number of variable combinations is small, pre-build a environment for each
|
||||
for _, hasParams := range []bool{false, true} {
|
||||
for _, hasAuthorizer := range []bool{false, true} {
|
||||
var err error
|
||||
for _, strictCost := range []bool{false, true} {
|
||||
decl := OptionalVariableDeclarations{HasParams: hasParams, HasAuthorizer: hasAuthorizer, StrictCost: strictCost}
|
||||
envs[decl], err = createEnvForOpts(baseEnv, namespaceType, requestType, decl)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
// We only need this ObjectTypes where strict cost is true
|
||||
decl := OptionalVariableDeclarations{HasParams: hasParams, HasAuthorizer: hasAuthorizer, StrictCost: true, HasPatchTypes: true}
|
||||
envs[decl], err = createEnvForOpts(baseEnv, namespaceType, requestType, decl)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return envs
|
||||
}
|
||||
|
||||
func createEnvForOpts(baseEnv *environment.EnvSet, namespaceType *apiservercel.DeclType, requestType *apiservercel.DeclType, opts OptionalVariableDeclarations) (*environment.EnvSet, error) {
|
||||
var envOpts []cel.EnvOption
|
||||
envOpts = append(envOpts,
|
||||
cel.Variable(ObjectVarName, cel.DynType),
|
||||
cel.Variable(OldObjectVarName, cel.DynType),
|
||||
cel.Variable(NamespaceVarName, namespaceType.CelType()),
|
||||
cel.Variable(RequestVarName, requestType.CelType()))
|
||||
if opts.HasParams {
|
||||
envOpts = append(envOpts, cel.Variable(ParamsVarName, cel.DynType))
|
||||
}
|
||||
if opts.HasAuthorizer {
|
||||
envOpts = append(envOpts,
|
||||
cel.Variable(AuthorizerVarName, library.AuthorizerType),
|
||||
cel.Variable(RequestResourceAuthorizerVarName, library.ResourceCheckType))
|
||||
}
|
||||
|
||||
extended, err := baseEnv.Extend(
|
||||
environment.VersionedOptions{
|
||||
// Feature epoch was actually 1.26, but we artificially set it to 1.0 because these
|
||||
// options should always be present.
|
||||
IntroducedVersion: version.MajorMinor(1, 0),
|
||||
EnvOptions: envOpts,
|
||||
DeclTypes: []*apiservercel.DeclType{
|
||||
namespaceType,
|
||||
requestType,
|
||||
},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("environment misconfigured: %w", err)
|
||||
}
|
||||
if opts.StrictCost {
|
||||
extended, err = extended.Extend(environment.StrictCostOpt)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("environment misconfigured: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if opts.HasPatchTypes {
|
||||
extended, err = extended.Extend(hasPatchTypes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("environment misconfigured: %w", err)
|
||||
}
|
||||
}
|
||||
return extended, nil
|
||||
}
|
||||
|
||||
var hasPatchTypes = environment.VersionedOptions{
|
||||
// Feature epoch was actually 1.32, but we artificially set it to 1.0 because these
|
||||
// options should always be present.
|
||||
IntroducedVersion: version.MajorMinor(1, 0),
|
||||
EnvOptions: []cel.EnvOption{
|
||||
common.ResolverEnvOption(&mutation.DynamicTypeResolver{}),
|
||||
environment.UnversionedLib(library.JSONPatch), // for jsonPatch.escape() function
|
||||
},
|
||||
}
|
276
vendor/k8s.io/apiserver/pkg/admission/plugin/cel/composition.go
generated
vendored
276
vendor/k8s.io/apiserver/pkg/admission/plugin/cel/composition.go
generated
vendored
@ -1,276 +0,0 @@
|
||||
/*
|
||||
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 cel
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math"
|
||||
|
||||
"github.com/google/cel-go/cel"
|
||||
"github.com/google/cel-go/common/types"
|
||||
"github.com/google/cel-go/common/types/ref"
|
||||
"github.com/google/cel-go/common/types/traits"
|
||||
|
||||
v1 "k8s.io/api/admission/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/util/version"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
apiservercel "k8s.io/apiserver/pkg/cel"
|
||||
"k8s.io/apiserver/pkg/cel/environment"
|
||||
"k8s.io/apiserver/pkg/cel/lazy"
|
||||
)
|
||||
|
||||
const VariablesTypeName = "kubernetes.variables"
|
||||
|
||||
// CompositedCompiler compiles expressions with variable composition.
|
||||
type CompositedCompiler struct {
|
||||
Compiler
|
||||
ConditionCompiler
|
||||
MutatingCompiler
|
||||
|
||||
CompositionEnv *CompositionEnv
|
||||
}
|
||||
|
||||
// CompositedConditionEvaluator provides evaluation of a condition expression with variable composition.
|
||||
// The expressions must return a boolean.
|
||||
type CompositedConditionEvaluator struct {
|
||||
ConditionEvaluator
|
||||
|
||||
compositionEnv *CompositionEnv
|
||||
}
|
||||
|
||||
// CompositedEvaluator provides evaluation of a single expression with variable composition.
|
||||
// The types that may returned by the expression is determined at compilation time.
|
||||
type CompositedEvaluator struct {
|
||||
MutatingEvaluator
|
||||
|
||||
compositionEnv *CompositionEnv
|
||||
}
|
||||
|
||||
func NewCompositedCompiler(envSet *environment.EnvSet) (*CompositedCompiler, error) {
|
||||
compositionContext, err := NewCompositionEnv(VariablesTypeName, envSet)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewCompositedCompilerFromTemplate(compositionContext), nil
|
||||
}
|
||||
|
||||
func NewCompositedCompilerFromTemplate(context *CompositionEnv) *CompositedCompiler {
|
||||
context = &CompositionEnv{
|
||||
MapType: context.MapType,
|
||||
EnvSet: context.EnvSet,
|
||||
CompiledVariables: map[string]CompilationResult{},
|
||||
}
|
||||
compiler := NewCompiler(context.EnvSet)
|
||||
conditionCompiler := &conditionCompiler{compiler}
|
||||
mutation := &mutatingCompiler{compiler}
|
||||
return &CompositedCompiler{
|
||||
Compiler: compiler,
|
||||
ConditionCompiler: conditionCompiler,
|
||||
MutatingCompiler: mutation,
|
||||
CompositionEnv: context,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CompositedCompiler) CompileAndStoreVariables(variables []NamedExpressionAccessor, options OptionalVariableDeclarations, mode environment.Type) {
|
||||
for _, v := range variables {
|
||||
_ = c.CompileAndStoreVariable(v, options, mode)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CompositedCompiler) CompileAndStoreVariable(variable NamedExpressionAccessor, options OptionalVariableDeclarations, mode environment.Type) CompilationResult {
|
||||
result := c.Compiler.CompileCELExpression(variable, options, mode)
|
||||
c.CompositionEnv.AddField(variable.GetName(), result.OutputType)
|
||||
c.CompositionEnv.CompiledVariables[variable.GetName()] = result
|
||||
return result
|
||||
}
|
||||
|
||||
func (c *CompositedCompiler) CompileCondition(expressions []ExpressionAccessor, optionalDecls OptionalVariableDeclarations, envType environment.Type) ConditionEvaluator {
|
||||
condition := c.ConditionCompiler.CompileCondition(expressions, optionalDecls, envType)
|
||||
return &CompositedConditionEvaluator{
|
||||
ConditionEvaluator: condition,
|
||||
compositionEnv: c.CompositionEnv,
|
||||
}
|
||||
}
|
||||
|
||||
// CompileEvaluator compiles an mutatingEvaluator for the given expression, options and environment.
|
||||
func (c *CompositedCompiler) CompileMutatingEvaluator(expression ExpressionAccessor, optionalDecls OptionalVariableDeclarations, envType environment.Type) MutatingEvaluator {
|
||||
mutation := c.MutatingCompiler.CompileMutatingEvaluator(expression, optionalDecls, envType)
|
||||
return &CompositedEvaluator{
|
||||
MutatingEvaluator: mutation,
|
||||
compositionEnv: c.CompositionEnv,
|
||||
}
|
||||
}
|
||||
|
||||
type CompositionEnv struct {
|
||||
*environment.EnvSet
|
||||
|
||||
MapType *apiservercel.DeclType
|
||||
CompiledVariables map[string]CompilationResult
|
||||
}
|
||||
|
||||
func (c *CompositionEnv) AddField(name string, celType *cel.Type) {
|
||||
c.MapType.Fields[name] = apiservercel.NewDeclField(name, convertCelTypeToDeclType(celType), true, nil, nil)
|
||||
}
|
||||
|
||||
func NewCompositionEnv(typeName string, baseEnvSet *environment.EnvSet) (*CompositionEnv, error) {
|
||||
declType := apiservercel.NewObjectType(typeName, map[string]*apiservercel.DeclField{})
|
||||
envSet, err := baseEnvSet.Extend(environment.VersionedOptions{
|
||||
// set to 1.0 because composition is one of the fundamental components
|
||||
IntroducedVersion: version.MajorMinor(1, 0),
|
||||
EnvOptions: []cel.EnvOption{
|
||||
cel.Variable("variables", declType.CelType()),
|
||||
},
|
||||
DeclTypes: []*apiservercel.DeclType{
|
||||
declType,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &CompositionEnv{
|
||||
MapType: declType,
|
||||
EnvSet: envSet,
|
||||
CompiledVariables: map[string]CompilationResult{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *CompositionEnv) CreateContext(parent context.Context) CompositionContext {
|
||||
return &compositionContext{
|
||||
Context: parent,
|
||||
compositionEnv: c,
|
||||
}
|
||||
}
|
||||
|
||||
type CompositionContext interface {
|
||||
context.Context
|
||||
Variables(activation any) ref.Val
|
||||
GetAndResetCost() int64
|
||||
}
|
||||
|
||||
type compositionContext struct {
|
||||
context.Context
|
||||
|
||||
compositionEnv *CompositionEnv
|
||||
accumulatedCost int64
|
||||
}
|
||||
|
||||
func (c *compositionContext) Variables(activation any) ref.Val {
|
||||
lazyMap := lazy.NewMapValue(c.compositionEnv.MapType)
|
||||
for name, result := range c.compositionEnv.CompiledVariables {
|
||||
accessor := &variableAccessor{
|
||||
name: name,
|
||||
result: result,
|
||||
activation: activation,
|
||||
context: c,
|
||||
}
|
||||
lazyMap.Append(name, accessor.Callback)
|
||||
}
|
||||
return lazyMap
|
||||
}
|
||||
|
||||
func (f *CompositedConditionEvaluator) ForInput(ctx context.Context, versionedAttr *admission.VersionedAttributes, request *v1.AdmissionRequest, optionalVars OptionalVariableBindings, namespace *corev1.Namespace, runtimeCELCostBudget int64) ([]EvaluationResult, int64, error) {
|
||||
ctx = f.compositionEnv.CreateContext(ctx)
|
||||
return f.ConditionEvaluator.ForInput(ctx, versionedAttr, request, optionalVars, namespace, runtimeCELCostBudget)
|
||||
}
|
||||
|
||||
func (c *compositionContext) reportCost(cost int64) {
|
||||
c.accumulatedCost += cost
|
||||
}
|
||||
|
||||
func (c *compositionContext) GetAndResetCost() int64 {
|
||||
cost := c.accumulatedCost
|
||||
c.accumulatedCost = 0
|
||||
return cost
|
||||
}
|
||||
|
||||
type variableAccessor struct {
|
||||
name string
|
||||
result CompilationResult
|
||||
activation any
|
||||
context *compositionContext
|
||||
}
|
||||
|
||||
func (a *variableAccessor) Callback(_ *lazy.MapValue) ref.Val {
|
||||
if a.result.Error != nil {
|
||||
return types.NewErr("composited variable %q fails to compile: %v", a.name, a.result.Error)
|
||||
}
|
||||
|
||||
v, details, err := a.result.Program.ContextEval(a.context, a.activation)
|
||||
if details == nil {
|
||||
return types.NewErr("unable to get evaluation details of variable %q", a.name)
|
||||
}
|
||||
costPtr := details.ActualCost()
|
||||
if costPtr == nil {
|
||||
return types.NewErr("unable to calculate cost of variable %q", a.name)
|
||||
}
|
||||
cost := int64(*costPtr)
|
||||
if *costPtr > math.MaxInt64 {
|
||||
cost = math.MaxInt64
|
||||
}
|
||||
a.context.reportCost(cost)
|
||||
|
||||
if err != nil {
|
||||
return types.NewErr("composited variable %q fails to evaluate: %v", a.name, err)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// convertCelTypeToDeclType converts a cel.Type to DeclType, for the use of
|
||||
// the TypeProvider and the cost estimator.
|
||||
// List and map types are created on-demand with their parameters converted recursively.
|
||||
func convertCelTypeToDeclType(celType *cel.Type) *apiservercel.DeclType {
|
||||
if celType == nil {
|
||||
return apiservercel.DynType
|
||||
}
|
||||
switch celType {
|
||||
case cel.AnyType:
|
||||
return apiservercel.AnyType
|
||||
case cel.BoolType:
|
||||
return apiservercel.BoolType
|
||||
case cel.BytesType:
|
||||
return apiservercel.BytesType
|
||||
case cel.DoubleType:
|
||||
return apiservercel.DoubleType
|
||||
case cel.DurationType:
|
||||
return apiservercel.DurationType
|
||||
case cel.IntType:
|
||||
return apiservercel.IntType
|
||||
case cel.NullType:
|
||||
return apiservercel.NullType
|
||||
case cel.StringType:
|
||||
return apiservercel.StringType
|
||||
case cel.TimestampType:
|
||||
return apiservercel.TimestampType
|
||||
case cel.UintType:
|
||||
return apiservercel.UintType
|
||||
default:
|
||||
if celType.HasTrait(traits.ContainerType) && celType.HasTrait(traits.IndexerType) {
|
||||
parameters := celType.Parameters()
|
||||
switch len(parameters) {
|
||||
case 1:
|
||||
elemType := convertCelTypeToDeclType(parameters[0])
|
||||
return apiservercel.NewListType(elemType, -1)
|
||||
case 2:
|
||||
keyType := convertCelTypeToDeclType(parameters[0])
|
||||
valueType := convertCelTypeToDeclType(parameters[1])
|
||||
return apiservercel.NewMapType(keyType, valueType, -1)
|
||||
}
|
||||
}
|
||||
return apiservercel.DynType
|
||||
}
|
||||
}
|
216
vendor/k8s.io/apiserver/pkg/admission/plugin/cel/condition.go
generated
vendored
216
vendor/k8s.io/apiserver/pkg/admission/plugin/cel/condition.go
generated
vendored
@ -1,216 +0,0 @@
|
||||
/*
|
||||
Copyright 2022 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 cel
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
|
||||
admissionv1 "k8s.io/api/admission/v1"
|
||||
authenticationv1 "k8s.io/api/authentication/v1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/cel/environment"
|
||||
)
|
||||
|
||||
// conditionCompiler implement the interface ConditionCompiler.
|
||||
type conditionCompiler struct {
|
||||
compiler Compiler
|
||||
}
|
||||
|
||||
func NewConditionCompiler(env *environment.EnvSet) ConditionCompiler {
|
||||
return &conditionCompiler{compiler: NewCompiler(env)}
|
||||
}
|
||||
|
||||
// CompileCondition compiles the cel expressions defined in the ExpressionAccessors into a ConditionEvaluator
|
||||
func (c *conditionCompiler) CompileCondition(expressionAccessors []ExpressionAccessor, options OptionalVariableDeclarations, mode environment.Type) ConditionEvaluator {
|
||||
compilationResults := make([]CompilationResult, len(expressionAccessors))
|
||||
for i, expressionAccessor := range expressionAccessors {
|
||||
if expressionAccessor == nil {
|
||||
continue
|
||||
}
|
||||
compilationResults[i] = c.compiler.CompileCELExpression(expressionAccessor, options, mode)
|
||||
}
|
||||
return NewCondition(compilationResults)
|
||||
}
|
||||
|
||||
// condition implements the ConditionEvaluator interface
|
||||
type condition struct {
|
||||
compilationResults []CompilationResult
|
||||
}
|
||||
|
||||
func NewCondition(compilationResults []CompilationResult) ConditionEvaluator {
|
||||
return &condition{
|
||||
compilationResults,
|
||||
}
|
||||
}
|
||||
|
||||
func convertObjectToUnstructured(obj interface{}) (*unstructured.Unstructured, error) {
|
||||
if obj == nil || reflect.ValueOf(obj).IsNil() {
|
||||
return &unstructured.Unstructured{Object: nil}, nil
|
||||
}
|
||||
ret, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &unstructured.Unstructured{Object: ret}, nil
|
||||
}
|
||||
|
||||
func objectToResolveVal(r runtime.Object) (interface{}, error) {
|
||||
if r == nil || reflect.ValueOf(r).IsNil() {
|
||||
return nil, nil
|
||||
}
|
||||
v, err := convertObjectToUnstructured(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return v.Object, nil
|
||||
}
|
||||
|
||||
// ForInput evaluates the compiled CEL expressions converting them into CELEvaluations
|
||||
// errors per evaluation are returned on the Evaluation object
|
||||
// runtimeCELCostBudget was added for testing purpose only. Callers should always use const RuntimeCELCostBudget from k8s.io/apiserver/pkg/apis/cel/config.go as input.
|
||||
func (c *condition) ForInput(ctx context.Context, versionedAttr *admission.VersionedAttributes, request *admissionv1.AdmissionRequest, inputs OptionalVariableBindings, namespace *v1.Namespace, runtimeCELCostBudget int64) ([]EvaluationResult, int64, error) {
|
||||
// TODO: replace unstructured with ref.Val for CEL variables when native type support is available
|
||||
evaluations := make([]EvaluationResult, len(c.compilationResults))
|
||||
var err error
|
||||
|
||||
// if this activation supports composition, we will need the compositionCtx. It may be nil.
|
||||
compositionCtx, _ := ctx.(CompositionContext)
|
||||
|
||||
activation, err := newActivation(compositionCtx, versionedAttr, request, inputs, namespace)
|
||||
if err != nil {
|
||||
return nil, -1, err
|
||||
}
|
||||
|
||||
remainingBudget := runtimeCELCostBudget
|
||||
for i, compilationResult := range c.compilationResults {
|
||||
evaluations[i], remainingBudget, err = activation.Evaluate(ctx, compositionCtx, compilationResult, remainingBudget)
|
||||
if err != nil {
|
||||
return nil, -1, err
|
||||
}
|
||||
}
|
||||
|
||||
return evaluations, remainingBudget, nil
|
||||
}
|
||||
|
||||
// TODO: to reuse https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/request/admissionreview.go#L154
|
||||
func CreateAdmissionRequest(attr admission.Attributes, equivalentGVR metav1.GroupVersionResource, equivalentKind metav1.GroupVersionKind) *admissionv1.AdmissionRequest {
|
||||
// Attempting to use same logic as webhook for constructing resource
|
||||
// GVK, GVR, subresource
|
||||
// Use the GVK, GVR that the matcher decided was equivalent to that of the request
|
||||
// https://github.com/kubernetes/kubernetes/blob/90c362b3430bcbbf8f245fadbcd521dab39f1d7c/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/generic/webhook.go#L182-L210
|
||||
gvk := equivalentKind
|
||||
gvr := equivalentGVR
|
||||
subresource := attr.GetSubresource()
|
||||
|
||||
requestGVK := attr.GetKind()
|
||||
requestGVR := attr.GetResource()
|
||||
requestSubResource := attr.GetSubresource()
|
||||
|
||||
aUserInfo := attr.GetUserInfo()
|
||||
var userInfo authenticationv1.UserInfo
|
||||
if aUserInfo != nil {
|
||||
userInfo = authenticationv1.UserInfo{
|
||||
Extra: make(map[string]authenticationv1.ExtraValue),
|
||||
Groups: aUserInfo.GetGroups(),
|
||||
UID: aUserInfo.GetUID(),
|
||||
Username: aUserInfo.GetName(),
|
||||
}
|
||||
// Convert the extra information in the user object
|
||||
for key, val := range aUserInfo.GetExtra() {
|
||||
userInfo.Extra[key] = authenticationv1.ExtraValue(val)
|
||||
}
|
||||
}
|
||||
|
||||
dryRun := attr.IsDryRun()
|
||||
|
||||
return &admissionv1.AdmissionRequest{
|
||||
Kind: metav1.GroupVersionKind{
|
||||
Group: gvk.Group,
|
||||
Kind: gvk.Kind,
|
||||
Version: gvk.Version,
|
||||
},
|
||||
Resource: metav1.GroupVersionResource{
|
||||
Group: gvr.Group,
|
||||
Resource: gvr.Resource,
|
||||
Version: gvr.Version,
|
||||
},
|
||||
SubResource: subresource,
|
||||
RequestKind: &metav1.GroupVersionKind{
|
||||
Group: requestGVK.Group,
|
||||
Kind: requestGVK.Kind,
|
||||
Version: requestGVK.Version,
|
||||
},
|
||||
RequestResource: &metav1.GroupVersionResource{
|
||||
Group: requestGVR.Group,
|
||||
Resource: requestGVR.Resource,
|
||||
Version: requestGVR.Version,
|
||||
},
|
||||
RequestSubResource: requestSubResource,
|
||||
Name: attr.GetName(),
|
||||
Namespace: attr.GetNamespace(),
|
||||
Operation: admissionv1.Operation(attr.GetOperation()),
|
||||
UserInfo: userInfo,
|
||||
// Leave Object and OldObject unset since we don't provide access to them via request
|
||||
DryRun: &dryRun,
|
||||
Options: runtime.RawExtension{
|
||||
Object: attr.GetOperationOptions(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// CreateNamespaceObject creates a Namespace object that is suitable for the CEL evaluation.
|
||||
// If the namespace is nil, CreateNamespaceObject returns nil
|
||||
func CreateNamespaceObject(namespace *v1.Namespace) *v1.Namespace {
|
||||
if namespace == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &v1.Namespace{
|
||||
Status: namespace.Status,
|
||||
Spec: namespace.Spec,
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: namespace.Name,
|
||||
GenerateName: namespace.GenerateName,
|
||||
Namespace: namespace.Namespace,
|
||||
UID: namespace.UID,
|
||||
ResourceVersion: namespace.ResourceVersion,
|
||||
Generation: namespace.Generation,
|
||||
CreationTimestamp: namespace.CreationTimestamp,
|
||||
DeletionTimestamp: namespace.DeletionTimestamp,
|
||||
DeletionGracePeriodSeconds: namespace.DeletionGracePeriodSeconds,
|
||||
Labels: namespace.Labels,
|
||||
Annotations: namespace.Annotations,
|
||||
Finalizers: namespace.Finalizers,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// CompilationErrors returns a list of all the errors from the compilation of the mutatingEvaluator
|
||||
func (c *condition) CompilationErrors() []error {
|
||||
compilationErrors := []error{}
|
||||
for _, result := range c.compilationResults {
|
||||
if result.Error != nil {
|
||||
compilationErrors = append(compilationErrors, result.Error)
|
||||
}
|
||||
}
|
||||
return compilationErrors
|
||||
}
|
122
vendor/k8s.io/apiserver/pkg/admission/plugin/cel/interface.go
generated
vendored
122
vendor/k8s.io/apiserver/pkg/admission/plugin/cel/interface.go
generated
vendored
@ -1,122 +0,0 @@
|
||||
/*
|
||||
Copyright 2022 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 cel
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/google/cel-go/cel"
|
||||
"github.com/google/cel-go/common/types/ref"
|
||||
|
||||
v1 "k8s.io/api/admission/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
"k8s.io/apiserver/pkg/cel/environment"
|
||||
)
|
||||
|
||||
type ExpressionAccessor interface {
|
||||
GetExpression() string
|
||||
ReturnTypes() []*cel.Type
|
||||
}
|
||||
|
||||
// NamedExpressionAccessor extends NamedExpressionAccessor with a name.
|
||||
type NamedExpressionAccessor interface {
|
||||
ExpressionAccessor
|
||||
|
||||
GetName() string // follows the naming convention of ExpressionAccessor
|
||||
}
|
||||
|
||||
// EvaluationResult contains the minimal required fields and metadata of a cel evaluation
|
||||
type EvaluationResult struct {
|
||||
EvalResult ref.Val
|
||||
ExpressionAccessor ExpressionAccessor
|
||||
Elapsed time.Duration
|
||||
Error error
|
||||
}
|
||||
|
||||
// OptionalVariableDeclarations declares which optional CEL variables
|
||||
// are declared for an expression.
|
||||
type OptionalVariableDeclarations struct {
|
||||
// HasParams specifies if the "params" variable is declared.
|
||||
// The "params" variable may still be bound to "null" when declared.
|
||||
HasParams bool
|
||||
// HasAuthorizer specifies if the "authorizer" and "authorizer.requestResource"
|
||||
// variables are declared. When declared, the authorizer variables are
|
||||
// expected to be non-null.
|
||||
HasAuthorizer bool
|
||||
// StrictCost specifies if the CEL cost limitation is strict for extended libraries as well as native libraries.
|
||||
StrictCost bool
|
||||
// HasPatchTypes specifies if JSONPatch, Object, Object.metadata and similar types are available in CEL. These can be used
|
||||
// to initialize the typed objects in CEL required to create patches.
|
||||
HasPatchTypes bool
|
||||
}
|
||||
|
||||
// ConditionCompiler contains a function to assist with converting types and values to/from CEL-typed values.
|
||||
type ConditionCompiler interface {
|
||||
// CompileCondition is used for the cel expression compilation
|
||||
CompileCondition(expressions []ExpressionAccessor, optionalDecls OptionalVariableDeclarations, envType environment.Type) ConditionEvaluator
|
||||
}
|
||||
|
||||
// OptionalVariableBindings provides expression bindings for optional CEL variables.
|
||||
type OptionalVariableBindings struct {
|
||||
// VersionedParams provides the "params" variable binding. This variable binding may
|
||||
// be set to nil even when OptionalVariableDeclarations.HashParams is set to true.
|
||||
VersionedParams runtime.Object
|
||||
// Authorizer provides the authorizer used for the "authorizer" and
|
||||
// "authorizer.requestResource" variable bindings. If the expression was compiled with
|
||||
// OptionalVariableDeclarations.HasAuthorizer set to true this must be non-nil.
|
||||
Authorizer authorizer.Authorizer
|
||||
}
|
||||
|
||||
// ConditionEvaluator contains the result of compiling a CEL expression
|
||||
// that evaluates to a condition. This is used both for validation and pre-conditions.
|
||||
// It expects the inbound object to already have been converted to the version expected
|
||||
// by the underlying CEL code (which is indicated by the match criteria of a policy definition).
|
||||
// versionedParams may be nil.
|
||||
type ConditionEvaluator interface {
|
||||
// ForInput converts compiled CEL-typed values into evaluated CEL-typed value.
|
||||
// runtimeCELCostBudget was added for testing purpose only. Callers should always use const RuntimeCELCostBudget from k8s.io/apiserver/pkg/apis/cel/config.go as input.
|
||||
// If cost budget is calculated, the condition should return the remaining budget.
|
||||
ForInput(ctx context.Context, versionedAttr *admission.VersionedAttributes, request *v1.AdmissionRequest, optionalVars OptionalVariableBindings, namespace *corev1.Namespace, runtimeCELCostBudget int64) ([]EvaluationResult, int64, error)
|
||||
|
||||
// CompilationErrors returns a list of errors from the compilation of the mutatingEvaluator
|
||||
CompilationErrors() []error
|
||||
}
|
||||
|
||||
// MutatingCompiler contains a function to assist with converting types and values to/from CEL-typed values.
|
||||
type MutatingCompiler interface {
|
||||
// CompileMutatingEvaluator is used for the cel expression compilation
|
||||
CompileMutatingEvaluator(expression ExpressionAccessor, optionalDecls OptionalVariableDeclarations, envType environment.Type) MutatingEvaluator
|
||||
}
|
||||
|
||||
// MutatingEvaluator contains the result of compiling a CEL expression
|
||||
// that evaluates to a mutation.
|
||||
// It expects the inbound object to already have been converted to the version expected
|
||||
// by the underlying CEL code (which is indicated by the match criteria of a policy definition).
|
||||
// versionedParams may be nil.
|
||||
type MutatingEvaluator interface {
|
||||
// ForInput converts compiled CEL-typed values into a CEL-typed value representing a mutation.
|
||||
// runtimeCELCostBudget was added for testing purpose only. Callers should always use const RuntimeCELCostBudget from k8s.io/apiserver/pkg/apis/cel/config.go as input.
|
||||
// If cost budget is calculated, the condition should return the remaining budget.
|
||||
ForInput(ctx context.Context, versionedAttr *admission.VersionedAttributes, request *v1.AdmissionRequest, optionalVars OptionalVariableBindings, namespace *corev1.Namespace, runtimeCELCostBudget int64) (EvaluationResult, int64, error)
|
||||
|
||||
// CompilationErrors returns a list of errors from the compilation of the mutatingEvaluator
|
||||
CompilationErrors() []error
|
||||
}
|
73
vendor/k8s.io/apiserver/pkg/admission/plugin/cel/mutation.go
generated
vendored
73
vendor/k8s.io/apiserver/pkg/admission/plugin/cel/mutation.go
generated
vendored
@ -1,73 +0,0 @@
|
||||
/*
|
||||
Copyright 2024 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 cel
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
admissionv1 "k8s.io/api/admission/v1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/cel/environment"
|
||||
)
|
||||
|
||||
// mutatingCompiler provides a MutatingCompiler implementation.
|
||||
type mutatingCompiler struct {
|
||||
compiler Compiler
|
||||
}
|
||||
|
||||
// CompileMutatingEvaluator compiles a CEL expression for admission plugins and returns an MutatingEvaluator for executing the
|
||||
// compiled CEL expression.
|
||||
func (p *mutatingCompiler) CompileMutatingEvaluator(expressionAccessor ExpressionAccessor, options OptionalVariableDeclarations, mode environment.Type) MutatingEvaluator {
|
||||
compilationResult := p.compiler.CompileCELExpression(expressionAccessor, options, mode)
|
||||
return NewMutatingEvaluator(compilationResult)
|
||||
}
|
||||
|
||||
type mutatingEvaluator struct {
|
||||
compilationResult CompilationResult
|
||||
}
|
||||
|
||||
func NewMutatingEvaluator(compilationResult CompilationResult) MutatingEvaluator {
|
||||
return &mutatingEvaluator{compilationResult}
|
||||
}
|
||||
|
||||
// ForInput evaluates the compiled CEL expression and returns an evaluation result
|
||||
// errors per evaluation are returned in the evaluation result
|
||||
// runtimeCELCostBudget was added for testing purpose only. Callers should always use const RuntimeCELCostBudget from k8s.io/apiserver/pkg/apis/cel/config.go as input.
|
||||
func (p *mutatingEvaluator) ForInput(ctx context.Context, versionedAttr *admission.VersionedAttributes, request *admissionv1.AdmissionRequest, inputs OptionalVariableBindings, namespace *v1.Namespace, runtimeCELCostBudget int64) (EvaluationResult, int64, error) {
|
||||
// if this activation supports composition, we will need the compositionCtx. It may be nil.
|
||||
compositionCtx, _ := ctx.(CompositionContext)
|
||||
|
||||
activation, err := newActivation(compositionCtx, versionedAttr, request, inputs, namespace)
|
||||
if err != nil {
|
||||
return EvaluationResult{}, -1, err
|
||||
}
|
||||
evaluation, remainingBudget, err := activation.Evaluate(ctx, compositionCtx, p.compilationResult, runtimeCELCostBudget)
|
||||
if err != nil {
|
||||
return evaluation, -1, err
|
||||
}
|
||||
return evaluation, remainingBudget, nil
|
||||
|
||||
}
|
||||
|
||||
// CompilationErrors returns a list of all the errors from the compilation of the mutatingEvaluator
|
||||
func (p *mutatingEvaluator) CompilationErrors() (compilationErrors []error) {
|
||||
if p.compilationResult.Error != nil {
|
||||
return []error{p.compilationResult.Error}
|
||||
}
|
||||
return nil
|
||||
}
|
233
vendor/k8s.io/apiserver/pkg/admission/plugin/namespace/lifecycle/admission.go
generated
vendored
233
vendor/k8s.io/apiserver/pkg/admission/plugin/namespace/lifecycle/admission.go
generated
vendored
@ -1,233 +0,0 @@
|
||||
/*
|
||||
Copyright 2015 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 lifecycle
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
utilcache "k8s.io/apimachinery/pkg/util/cache"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/admission/initializer"
|
||||
"k8s.io/client-go/informers"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
corelisters "k8s.io/client-go/listers/core/v1"
|
||||
"k8s.io/utils/clock"
|
||||
)
|
||||
|
||||
const (
|
||||
// PluginName indicates the name of admission plug-in
|
||||
PluginName = "NamespaceLifecycle"
|
||||
// how long a namespace stays in the force live lookup cache before expiration.
|
||||
forceLiveLookupTTL = 30 * time.Second
|
||||
// how long to wait for a missing namespace before re-checking the cache (and then doing a live lookup)
|
||||
// this accomplishes two things:
|
||||
// 1. It allows a watch-fed cache time to observe a namespace creation event
|
||||
// 2. It allows time for a namespace creation to distribute to members of a storage cluster,
|
||||
// so the live lookup has a better chance of succeeding even if it isn't performed against the leader.
|
||||
missingNamespaceWait = 50 * time.Millisecond
|
||||
)
|
||||
|
||||
// Register registers a plugin
|
||||
func Register(plugins *admission.Plugins) {
|
||||
plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) {
|
||||
return NewLifecycle(sets.NewString(metav1.NamespaceDefault, metav1.NamespaceSystem, metav1.NamespacePublic))
|
||||
})
|
||||
}
|
||||
|
||||
// Lifecycle is an implementation of admission.Interface.
|
||||
// It enforces life-cycle constraints around a Namespace depending on its Phase
|
||||
type Lifecycle struct {
|
||||
*admission.Handler
|
||||
client kubernetes.Interface
|
||||
immortalNamespaces sets.String
|
||||
namespaceLister corelisters.NamespaceLister
|
||||
// forceLiveLookupCache holds a list of entries for namespaces that we have a strong reason to believe are stale in our local cache.
|
||||
// if a namespace is in this cache, then we will ignore our local state and always fetch latest from api server.
|
||||
forceLiveLookupCache *utilcache.LRUExpireCache
|
||||
}
|
||||
|
||||
var _ = initializer.WantsExternalKubeInformerFactory(&Lifecycle{})
|
||||
var _ = initializer.WantsExternalKubeClientSet(&Lifecycle{})
|
||||
|
||||
// Admit makes an admission decision based on the request attributes
|
||||
func (l *Lifecycle) Admit(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) error {
|
||||
// prevent deletion of immortal namespaces
|
||||
if a.GetOperation() == admission.Delete && a.GetKind().GroupKind() == v1.SchemeGroupVersion.WithKind("Namespace").GroupKind() && l.immortalNamespaces.Has(a.GetName()) {
|
||||
return errors.NewForbidden(a.GetResource().GroupResource(), a.GetName(), fmt.Errorf("this namespace may not be deleted"))
|
||||
}
|
||||
|
||||
// always allow non-namespaced resources
|
||||
if len(a.GetNamespace()) == 0 && a.GetKind().GroupKind() != v1.SchemeGroupVersion.WithKind("Namespace").GroupKind() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if a.GetKind().GroupKind() == v1.SchemeGroupVersion.WithKind("Namespace").GroupKind() {
|
||||
// if a namespace is deleted, we want to prevent all further creates into it
|
||||
// while it is undergoing termination. to reduce incidences where the cache
|
||||
// is slow to update, we add the namespace into a force live lookup list to ensure
|
||||
// we are not looking at stale state.
|
||||
if a.GetOperation() == admission.Delete {
|
||||
l.forceLiveLookupCache.Add(a.GetName(), true, forceLiveLookupTTL)
|
||||
}
|
||||
// allow all operations to namespaces
|
||||
return nil
|
||||
}
|
||||
|
||||
// always allow deletion of other resources
|
||||
if a.GetOperation() == admission.Delete {
|
||||
return nil
|
||||
}
|
||||
|
||||
// always allow access review checks. Returning status about the namespace would be leaking information
|
||||
if isAccessReview(a) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// we need to wait for our caches to warm
|
||||
if !l.WaitForReady() {
|
||||
return admission.NewForbidden(a, fmt.Errorf("not yet ready to handle request"))
|
||||
}
|
||||
|
||||
var (
|
||||
exists bool
|
||||
err error
|
||||
)
|
||||
|
||||
namespace, err := l.namespaceLister.Get(a.GetNamespace())
|
||||
if err != nil {
|
||||
if !errors.IsNotFound(err) {
|
||||
return errors.NewInternalError(err)
|
||||
}
|
||||
} else {
|
||||
exists = true
|
||||
}
|
||||
|
||||
if !exists && a.GetOperation() == admission.Create {
|
||||
// give the cache time to observe the namespace before rejecting a create.
|
||||
// this helps when creating a namespace and immediately creating objects within it.
|
||||
time.Sleep(missingNamespaceWait)
|
||||
namespace, err = l.namespaceLister.Get(a.GetNamespace())
|
||||
switch {
|
||||
case errors.IsNotFound(err):
|
||||
// no-op
|
||||
case err != nil:
|
||||
return errors.NewInternalError(err)
|
||||
default:
|
||||
exists = true
|
||||
}
|
||||
if exists {
|
||||
klog.V(4).InfoS("Namespace existed in cache after waiting", "namespace", klog.KRef("", a.GetNamespace()))
|
||||
}
|
||||
}
|
||||
|
||||
// forceLiveLookup if true will skip looking at local cache state and instead always make a live call to server.
|
||||
forceLiveLookup := false
|
||||
if _, ok := l.forceLiveLookupCache.Get(a.GetNamespace()); ok {
|
||||
// we think the namespace was marked for deletion, but our current local cache says otherwise, we will force a live lookup.
|
||||
forceLiveLookup = exists && namespace.Status.Phase == v1.NamespaceActive
|
||||
}
|
||||
|
||||
// refuse to operate on non-existent namespaces
|
||||
if !exists || forceLiveLookup {
|
||||
// as a last resort, make a call directly to storage
|
||||
namespace, err = l.client.CoreV1().Namespaces().Get(context.TODO(), a.GetNamespace(), metav1.GetOptions{})
|
||||
switch {
|
||||
case errors.IsNotFound(err):
|
||||
return err
|
||||
case err != nil:
|
||||
return errors.NewInternalError(err)
|
||||
}
|
||||
|
||||
klog.V(4).InfoS("Found namespace via storage lookup", "namespace", klog.KRef("", a.GetNamespace()))
|
||||
}
|
||||
|
||||
// ensure that we're not trying to create objects in terminating namespaces
|
||||
if a.GetOperation() == admission.Create {
|
||||
if namespace.Status.Phase != v1.NamespaceTerminating {
|
||||
return nil
|
||||
}
|
||||
|
||||
err := admission.NewForbidden(a, fmt.Errorf("unable to create new content in namespace %s because it is being terminated", a.GetNamespace()))
|
||||
if apierr, ok := err.(*errors.StatusError); ok {
|
||||
apierr.ErrStatus.Details.Causes = append(apierr.ErrStatus.Details.Causes, metav1.StatusCause{
|
||||
Type: v1.NamespaceTerminatingCause,
|
||||
Message: fmt.Sprintf("namespace %s is being terminated", a.GetNamespace()),
|
||||
Field: "metadata.namespace",
|
||||
})
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewLifecycle creates a new namespace Lifecycle admission control handler
|
||||
func NewLifecycle(immortalNamespaces sets.String) (*Lifecycle, error) {
|
||||
return newLifecycleWithClock(immortalNamespaces, clock.RealClock{})
|
||||
}
|
||||
|
||||
func newLifecycleWithClock(immortalNamespaces sets.String, clock utilcache.Clock) (*Lifecycle, error) {
|
||||
forceLiveLookupCache := utilcache.NewLRUExpireCacheWithClock(100, clock)
|
||||
return &Lifecycle{
|
||||
Handler: admission.NewHandler(admission.Create, admission.Update, admission.Delete),
|
||||
immortalNamespaces: immortalNamespaces,
|
||||
forceLiveLookupCache: forceLiveLookupCache,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// SetExternalKubeInformerFactory implements the WantsExternalKubeInformerFactory interface.
|
||||
func (l *Lifecycle) SetExternalKubeInformerFactory(f informers.SharedInformerFactory) {
|
||||
namespaceInformer := f.Core().V1().Namespaces()
|
||||
l.namespaceLister = namespaceInformer.Lister()
|
||||
l.SetReadyFunc(namespaceInformer.Informer().HasSynced)
|
||||
}
|
||||
|
||||
// SetExternalKubeClientSet implements the WantsExternalKubeClientSet interface.
|
||||
func (l *Lifecycle) SetExternalKubeClientSet(client kubernetes.Interface) {
|
||||
l.client = client
|
||||
}
|
||||
|
||||
// ValidateInitialization implements the InitializationValidator interface.
|
||||
func (l *Lifecycle) ValidateInitialization() error {
|
||||
if l.namespaceLister == nil {
|
||||
return fmt.Errorf("missing namespaceLister")
|
||||
}
|
||||
if l.client == nil {
|
||||
return fmt.Errorf("missing client")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// accessReviewResources are resources which give a view into permissions in a namespace. Users must be allowed to create these
|
||||
// resources because returning "not found" errors allows someone to search for the "people I'm going to fire in 2017" namespace.
|
||||
var accessReviewResources = map[schema.GroupResource]bool{
|
||||
{Group: "authorization.k8s.io", Resource: "localsubjectaccessreviews"}: true,
|
||||
}
|
||||
|
||||
func isAccessReview(a admission.Attributes) bool {
|
||||
return accessReviewResources[a.GetResource().GroupResource()]
|
||||
}
|
43
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/generic/accessor.go
generated
vendored
43
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/generic/accessor.go
generated
vendored
@ -1,43 +0,0 @@
|
||||
/*
|
||||
Copyright 2024 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 generic
|
||||
|
||||
import (
|
||||
"k8s.io/api/admissionregistration/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
)
|
||||
|
||||
type PolicyAccessor interface {
|
||||
GetName() string
|
||||
GetNamespace() string
|
||||
GetParamKind() *v1.ParamKind
|
||||
GetMatchConstraints() *v1.MatchResources
|
||||
GetFailurePolicy() *v1.FailurePolicyType
|
||||
}
|
||||
|
||||
type BindingAccessor interface {
|
||||
GetName() string
|
||||
GetNamespace() string
|
||||
|
||||
// GetPolicyName returns the name of the (Validating/Mutating)AdmissionPolicy,
|
||||
// which is cluster-scoped, so namespace is usually left blank.
|
||||
// But we leave the door open to add a namespaced vesion in the future
|
||||
GetPolicyName() types.NamespacedName
|
||||
GetParamRef() *v1.ParamRef
|
||||
|
||||
GetMatchResources() *v1.MatchResources
|
||||
}
|
67
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/generic/interfaces.go
generated
vendored
67
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/generic/interfaces.go
generated
vendored
@ -1,67 +0,0 @@
|
||||
/*
|
||||
Copyright 2024 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 generic
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
)
|
||||
|
||||
// Hook represents a dynamic admission hook. The hook may be a webhook or a
|
||||
// policy. For webhook, the Hook may describe how to contact the endpoint, expected
|
||||
// cert, etc. For policies, the hook may describe a compiled policy-binding pair.
|
||||
type Hook interface {
|
||||
// All hooks are expected to contain zero or more match conditions, object
|
||||
// selectors, namespace selectors to help the dispatcher decide when to apply
|
||||
// the hook.
|
||||
//
|
||||
// Methods of matching logic is applied are specific to the hook and left up
|
||||
// to the implementation.
|
||||
}
|
||||
|
||||
// Source can list dynamic admission plugins.
|
||||
type Source[H Hook] interface {
|
||||
// Hooks returns the list of currently known admission hooks.
|
||||
Hooks() []H
|
||||
|
||||
// Run the source. This method should be called only once at startup.
|
||||
Run(ctx context.Context) error
|
||||
|
||||
// HasSynced returns true if the source has completed its initial sync.
|
||||
HasSynced() bool
|
||||
}
|
||||
|
||||
// Dispatcher dispatches evaluates an admission request against the currently
|
||||
// active hooks returned by the source.
|
||||
type Dispatcher[H Hook] interface {
|
||||
// Start the dispatcher. This method should be called only once at startup.
|
||||
Start(ctx context.Context) error
|
||||
|
||||
// Dispatch a request to the policies. Dispatcher may choose not to
|
||||
// call a hook, either because the rules of the hook does not match, or
|
||||
// the namespaceSelector or the objectSelector of the hook does not
|
||||
// match. A non-nil error means the request is rejected.
|
||||
Dispatch(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces, hooks []H) error
|
||||
}
|
||||
|
||||
// An evaluator represents a compiled CEL expression that can be evaluated a
|
||||
// given a set of inputs used by the generic PolicyHook for Mutating and
|
||||
// ValidatingAdmissionPolicy.
|
||||
// Mutating and Validating may have different forms of evaluators
|
||||
type Evaluator interface {
|
||||
}
|
221
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/generic/plugin.go
generated
vendored
221
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/generic/plugin.go
generated
vendored
@ -1,221 +0,0 @@
|
||||
/*
|
||||
Copyright 2024 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 generic
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/admission/initializer"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/policy/matching"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
"k8s.io/client-go/dynamic"
|
||||
"k8s.io/client-go/informers"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
)
|
||||
|
||||
// H is the Hook type generated by the source and consumed by the dispatcher.
|
||||
// !TODO: Just pass in a Plugin[H] with accessors to all this information
|
||||
type sourceFactory[H any] func(informers.SharedInformerFactory, kubernetes.Interface, dynamic.Interface, meta.RESTMapper) Source[H]
|
||||
type dispatcherFactory[H any] func(authorizer.Authorizer, *matching.Matcher, kubernetes.Interface) Dispatcher[H]
|
||||
|
||||
// admissionResources is the list of resources related to CEL-based admission
|
||||
// features.
|
||||
var admissionResources = []schema.GroupResource{
|
||||
{Group: admissionregistrationv1.GroupName, Resource: "validatingadmissionpolicies"},
|
||||
{Group: admissionregistrationv1.GroupName, Resource: "validatingadmissionpolicybindings"},
|
||||
{Group: admissionregistrationv1.GroupName, Resource: "mutatingadmissionpolicies"},
|
||||
{Group: admissionregistrationv1.GroupName, Resource: "mutatingadmissionpolicybindings"},
|
||||
}
|
||||
|
||||
// AdmissionPolicyManager is an abstract admission plugin with all the
|
||||
// infrastructure to define Admit or Validate on-top.
|
||||
type Plugin[H any] struct {
|
||||
*admission.Handler
|
||||
|
||||
sourceFactory sourceFactory[H]
|
||||
dispatcherFactory dispatcherFactory[H]
|
||||
|
||||
source Source[H]
|
||||
dispatcher Dispatcher[H]
|
||||
matcher *matching.Matcher
|
||||
|
||||
informerFactory informers.SharedInformerFactory
|
||||
client kubernetes.Interface
|
||||
restMapper meta.RESTMapper
|
||||
dynamicClient dynamic.Interface
|
||||
excludedResources sets.Set[schema.GroupResource]
|
||||
stopCh <-chan struct{}
|
||||
authorizer authorizer.Authorizer
|
||||
enabled bool
|
||||
}
|
||||
|
||||
var (
|
||||
_ initializer.WantsExternalKubeInformerFactory = &Plugin[any]{}
|
||||
_ initializer.WantsExternalKubeClientSet = &Plugin[any]{}
|
||||
_ initializer.WantsRESTMapper = &Plugin[any]{}
|
||||
_ initializer.WantsDynamicClient = &Plugin[any]{}
|
||||
_ initializer.WantsDrainedNotification = &Plugin[any]{}
|
||||
_ initializer.WantsAuthorizer = &Plugin[any]{}
|
||||
_ initializer.WantsExcludedAdmissionResources = &Plugin[any]{}
|
||||
_ admission.InitializationValidator = &Plugin[any]{}
|
||||
)
|
||||
|
||||
func NewPlugin[H any](
|
||||
handler *admission.Handler,
|
||||
sourceFactory sourceFactory[H],
|
||||
dispatcherFactory dispatcherFactory[H],
|
||||
) *Plugin[H] {
|
||||
return &Plugin[H]{
|
||||
Handler: handler,
|
||||
sourceFactory: sourceFactory,
|
||||
dispatcherFactory: dispatcherFactory,
|
||||
|
||||
// always exclude admission/mutating policies and bindings
|
||||
excludedResources: sets.New(admissionResources...),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Plugin[H]) SetExternalKubeInformerFactory(f informers.SharedInformerFactory) {
|
||||
c.informerFactory = f
|
||||
}
|
||||
|
||||
func (c *Plugin[H]) SetExternalKubeClientSet(client kubernetes.Interface) {
|
||||
c.client = client
|
||||
}
|
||||
|
||||
func (c *Plugin[H]) SetRESTMapper(mapper meta.RESTMapper) {
|
||||
c.restMapper = mapper
|
||||
}
|
||||
|
||||
func (c *Plugin[H]) SetDynamicClient(client dynamic.Interface) {
|
||||
c.dynamicClient = client
|
||||
}
|
||||
|
||||
func (c *Plugin[H]) SetDrainedNotification(stopCh <-chan struct{}) {
|
||||
c.stopCh = stopCh
|
||||
}
|
||||
|
||||
func (c *Plugin[H]) SetAuthorizer(authorizer authorizer.Authorizer) {
|
||||
c.authorizer = authorizer
|
||||
}
|
||||
|
||||
func (c *Plugin[H]) SetMatcher(matcher *matching.Matcher) {
|
||||
c.matcher = matcher
|
||||
}
|
||||
|
||||
func (c *Plugin[H]) SetEnabled(enabled bool) {
|
||||
c.enabled = enabled
|
||||
}
|
||||
|
||||
func (c *Plugin[H]) SetExcludedAdmissionResources(excludedResources []schema.GroupResource) {
|
||||
c.excludedResources.Insert(excludedResources...)
|
||||
}
|
||||
|
||||
// ValidateInitialization - once clientset and informer factory are provided, creates and starts the admission controller
|
||||
func (c *Plugin[H]) ValidateInitialization() error {
|
||||
// By default enabled is set to false. It is up to types which embed this
|
||||
// struct to set it to true (if feature gate is enabled, or other conditions)
|
||||
if !c.enabled {
|
||||
return nil
|
||||
}
|
||||
if c.Handler == nil {
|
||||
return errors.New("missing handler")
|
||||
}
|
||||
if c.informerFactory == nil {
|
||||
return errors.New("missing informer factory")
|
||||
}
|
||||
if c.client == nil {
|
||||
return errors.New("missing kubernetes client")
|
||||
}
|
||||
if c.restMapper == nil {
|
||||
return errors.New("missing rest mapper")
|
||||
}
|
||||
if c.dynamicClient == nil {
|
||||
return errors.New("missing dynamic client")
|
||||
}
|
||||
if c.stopCh == nil {
|
||||
return errors.New("missing stop channel")
|
||||
}
|
||||
if c.authorizer == nil {
|
||||
return errors.New("missing authorizer")
|
||||
}
|
||||
|
||||
// Use default matcher
|
||||
namespaceInformer := c.informerFactory.Core().V1().Namespaces()
|
||||
c.matcher = matching.NewMatcher(namespaceInformer.Lister(), c.client)
|
||||
|
||||
if err := c.matcher.ValidateInitialization(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.source = c.sourceFactory(c.informerFactory, c.client, c.dynamicClient, c.restMapper)
|
||||
c.dispatcher = c.dispatcherFactory(c.authorizer, c.matcher, c.client)
|
||||
|
||||
pluginContext, pluginContextCancel := context.WithCancel(context.Background())
|
||||
go func() {
|
||||
defer pluginContextCancel()
|
||||
<-c.stopCh
|
||||
}()
|
||||
|
||||
go func() {
|
||||
err := c.source.Run(pluginContext)
|
||||
if err != nil && !errors.Is(err, context.Canceled) {
|
||||
utilruntime.HandleError(fmt.Errorf("policy source context unexpectedly closed: %w", err))
|
||||
}
|
||||
}()
|
||||
|
||||
err := c.dispatcher.Start(pluginContext)
|
||||
if err != nil && !errors.Is(err, context.Canceled) {
|
||||
utilruntime.HandleError(fmt.Errorf("policy dispatcher context unexpectedly closed: %w", err))
|
||||
}
|
||||
|
||||
c.SetReadyFunc(func() bool {
|
||||
return namespaceInformer.Informer().HasSynced() && c.source.HasSynced()
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Plugin[H]) Dispatch(
|
||||
ctx context.Context,
|
||||
a admission.Attributes,
|
||||
o admission.ObjectInterfaces,
|
||||
) (err error) {
|
||||
if !c.enabled {
|
||||
return nil
|
||||
} else if c.shouldIgnoreResource(a) {
|
||||
return nil
|
||||
} else if !c.WaitForReady() {
|
||||
return admission.NewForbidden(a, fmt.Errorf("not yet ready to handle request"))
|
||||
}
|
||||
|
||||
return c.dispatcher.Dispatch(ctx, a, o, c.source.Hooks())
|
||||
}
|
||||
|
||||
func (c *Plugin[H]) shouldIgnoreResource(attr admission.Attributes) bool {
|
||||
gvr := attr.GetResource()
|
||||
// exclusion decision ignores the version.
|
||||
gr := gvr.GroupResource()
|
||||
return c.excludedResources.Has(gr)
|
||||
}
|
417
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/generic/policy_dispatcher.go
generated
vendored
417
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/generic/policy_dispatcher.go
generated
vendored
@ -1,417 +0,0 @@
|
||||
/*
|
||||
Copyright 2024 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 generic
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"k8s.io/api/admissionregistration/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"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/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/policy/matching"
|
||||
webhookgeneric "k8s.io/apiserver/pkg/admission/plugin/webhook/generic"
|
||||
"k8s.io/client-go/informers"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
)
|
||||
|
||||
// PolicyInvocation is a single policy-binding-param tuple from a Policy Hook
|
||||
// in the context of a specific request. The params have already been resolved
|
||||
// and any error in configuration or setting up the invocation is stored in
|
||||
// the Error field.
|
||||
type PolicyInvocation[P runtime.Object, B runtime.Object, E Evaluator] struct {
|
||||
// Relevant policy for this hook.
|
||||
// This field is always populated
|
||||
Policy P
|
||||
|
||||
// Matched Kind for the request given the policy's matchconstraints
|
||||
// May be empty if there was an error matching the resource
|
||||
Kind schema.GroupVersionKind
|
||||
|
||||
// Matched Resource for the request given the policy's matchconstraints
|
||||
// May be empty if there was an error matching the resource
|
||||
Resource schema.GroupVersionResource
|
||||
|
||||
// Relevant binding for this hook.
|
||||
// May be empty if there was an error with the policy's configuration itself
|
||||
Binding B
|
||||
|
||||
// Compiled policy evaluator
|
||||
Evaluator E
|
||||
|
||||
// Params fetched by the binding to use to evaluate the policy
|
||||
Param runtime.Object
|
||||
}
|
||||
|
||||
// dispatcherDelegate is called during a request with a pre-filtered list
|
||||
// of (Policy, Binding, Param) tuples that are active and match the request.
|
||||
// The dispatcher delegate is responsible for updating the object on the
|
||||
// admission attributes in the case of mutation, or returning a status error in
|
||||
// the case of validation.
|
||||
//
|
||||
// The delegate provides the "validation" or "mutation" aspect of dispatcher functionality
|
||||
// (in contrast to generic.PolicyDispatcher which only selects active policies and params)
|
||||
type dispatcherDelegate[P, B runtime.Object, E Evaluator] func(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces, versionedAttributes webhookgeneric.VersionedAttributeAccessor, invocations []PolicyInvocation[P, B, E]) ([]PolicyError, *apierrors.StatusError)
|
||||
|
||||
type policyDispatcher[P runtime.Object, B runtime.Object, E Evaluator] struct {
|
||||
newPolicyAccessor func(P) PolicyAccessor
|
||||
newBindingAccessor func(B) BindingAccessor
|
||||
matcher PolicyMatcher
|
||||
delegate dispatcherDelegate[P, B, E]
|
||||
}
|
||||
|
||||
func NewPolicyDispatcher[P runtime.Object, B runtime.Object, E Evaluator](
|
||||
newPolicyAccessor func(P) PolicyAccessor,
|
||||
newBindingAccessor func(B) BindingAccessor,
|
||||
matcher *matching.Matcher,
|
||||
delegate dispatcherDelegate[P, B, E],
|
||||
) Dispatcher[PolicyHook[P, B, E]] {
|
||||
return &policyDispatcher[P, B, E]{
|
||||
newPolicyAccessor: newPolicyAccessor,
|
||||
newBindingAccessor: newBindingAccessor,
|
||||
matcher: NewPolicyMatcher(matcher),
|
||||
delegate: delegate,
|
||||
}
|
||||
}
|
||||
|
||||
// Dispatch implements generic.Dispatcher. It loops through all active hooks
|
||||
// (policy x binding pairs) and selects those which are active for the current
|
||||
// request. It then resolves all params and creates an Invocation for each
|
||||
// matching policy-binding-param tuple. The delegate is then called with the
|
||||
// list of tuples.
|
||||
func (d *policyDispatcher[P, B, E]) Start(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Note: MatchConditions expressions are not evaluated here. The dispatcher delegate
|
||||
// is expected to ignore the result of any policies whose match conditions dont pass.
|
||||
// This may be possible to refactor so matchconditions are checked here instead.
|
||||
func (d *policyDispatcher[P, B, E]) Dispatch(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces, hooks []PolicyHook[P, B, E]) error {
|
||||
var relevantHooks []PolicyInvocation[P, B, E]
|
||||
// Construct all the versions we need to call our webhooks
|
||||
versionedAttrAccessor := &versionedAttributeAccessor{
|
||||
versionedAttrs: map[schema.GroupVersionKind]*admission.VersionedAttributes{},
|
||||
attr: a,
|
||||
objectInterfaces: o,
|
||||
}
|
||||
|
||||
var policyErrors []PolicyError
|
||||
addConfigError := func(err error, definition PolicyAccessor, binding BindingAccessor) {
|
||||
var message error
|
||||
if binding == nil {
|
||||
message = fmt.Errorf("failed to configure policy: %w", err)
|
||||
} else {
|
||||
message = fmt.Errorf("failed to configure binding: %w", err)
|
||||
}
|
||||
|
||||
policyErrors = append(policyErrors, PolicyError{
|
||||
Policy: definition,
|
||||
Binding: binding,
|
||||
Message: message,
|
||||
})
|
||||
}
|
||||
|
||||
for _, hook := range hooks {
|
||||
policyAccessor := d.newPolicyAccessor(hook.Policy)
|
||||
matches, matchGVR, matchGVK, err := d.matcher.DefinitionMatches(a, o, policyAccessor)
|
||||
if err != nil {
|
||||
// There was an error evaluating if this policy matches anything.
|
||||
addConfigError(err, policyAccessor, nil)
|
||||
continue
|
||||
} else if !matches {
|
||||
continue
|
||||
} else if hook.ConfigurationError != nil {
|
||||
addConfigError(hook.ConfigurationError, policyAccessor, nil)
|
||||
continue
|
||||
}
|
||||
|
||||
for _, binding := range hook.Bindings {
|
||||
bindingAccessor := d.newBindingAccessor(binding)
|
||||
matches, err = d.matcher.BindingMatches(a, o, bindingAccessor)
|
||||
if err != nil {
|
||||
// There was an error evaluating if this binding matches anything.
|
||||
addConfigError(err, policyAccessor, bindingAccessor)
|
||||
continue
|
||||
} else if !matches {
|
||||
continue
|
||||
}
|
||||
|
||||
// here the binding matches.
|
||||
// VersionedAttr result will be cached and reused later during parallel
|
||||
// hook calls.
|
||||
if _, err = versionedAttrAccessor.VersionedAttribute(matchGVK); err != nil {
|
||||
// VersionedAttr result will be cached and reused later during parallel
|
||||
// hook calls.
|
||||
addConfigError(err, policyAccessor, nil)
|
||||
continue
|
||||
}
|
||||
|
||||
// Collect params for this binding
|
||||
params, err := CollectParams(
|
||||
policyAccessor.GetParamKind(),
|
||||
hook.ParamInformer,
|
||||
hook.ParamScope,
|
||||
bindingAccessor.GetParamRef(),
|
||||
a.GetNamespace(),
|
||||
)
|
||||
if err != nil {
|
||||
// There was an error collecting params for this binding.
|
||||
addConfigError(err, policyAccessor, bindingAccessor)
|
||||
continue
|
||||
}
|
||||
|
||||
// If params is empty and there was no error, that means that
|
||||
// ParamNotFoundAction is ignore, so it shouldnt be added to list
|
||||
for _, param := range params {
|
||||
relevantHooks = append(relevantHooks, PolicyInvocation[P, B, E]{
|
||||
Policy: hook.Policy,
|
||||
Binding: binding,
|
||||
Kind: matchGVK,
|
||||
Resource: matchGVR,
|
||||
Param: param,
|
||||
Evaluator: hook.Evaluator,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(relevantHooks) > 0 {
|
||||
extraPolicyErrors, statusError := d.delegate(ctx, a, o, versionedAttrAccessor, relevantHooks)
|
||||
if statusError != nil {
|
||||
return statusError
|
||||
}
|
||||
policyErrors = append(policyErrors, extraPolicyErrors...)
|
||||
}
|
||||
|
||||
var filteredErrors []PolicyError
|
||||
for _, e := range policyErrors {
|
||||
// we always default the FailurePolicy if it is unset and validate it in API level
|
||||
var policy v1.FailurePolicyType
|
||||
if fp := e.Policy.GetFailurePolicy(); fp == nil {
|
||||
policy = v1.Fail
|
||||
} else {
|
||||
policy = *fp
|
||||
}
|
||||
|
||||
switch policy {
|
||||
case v1.Ignore:
|
||||
// TODO: add metrics for ignored error here
|
||||
continue
|
||||
case v1.Fail:
|
||||
filteredErrors = append(filteredErrors, e)
|
||||
default:
|
||||
filteredErrors = append(filteredErrors, e)
|
||||
}
|
||||
}
|
||||
|
||||
if len(filteredErrors) > 0 {
|
||||
|
||||
forbiddenErr := admission.NewForbidden(a, fmt.Errorf("admission request denied by policy"))
|
||||
|
||||
// The forbiddenErr is always a StatusError.
|
||||
var err *apierrors.StatusError
|
||||
if !errors.As(forbiddenErr, &err) {
|
||||
// Should never happen.
|
||||
return apierrors.NewInternalError(fmt.Errorf("failed to create status error"))
|
||||
}
|
||||
err.ErrStatus.Message = ""
|
||||
|
||||
for _, policyError := range filteredErrors {
|
||||
message := policyError.Error()
|
||||
|
||||
// If this is the first denied decision, use its message and reason
|
||||
// for the status error message.
|
||||
if err.ErrStatus.Message == "" {
|
||||
err.ErrStatus.Message = message
|
||||
if policyError.Reason != "" {
|
||||
err.ErrStatus.Reason = policyError.Reason
|
||||
}
|
||||
}
|
||||
|
||||
// Add the denied decision's message to the status error's details
|
||||
err.ErrStatus.Details.Causes = append(
|
||||
err.ErrStatus.Details.Causes,
|
||||
metav1.StatusCause{Message: message})
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Returns params to use to evaluate a policy-binding with given param
|
||||
// configuration. If the policy-binding has no param configuration, it
|
||||
// returns a single-element list with a nil param.
|
||||
func CollectParams(
|
||||
paramKind *v1.ParamKind,
|
||||
paramInformer informers.GenericInformer,
|
||||
paramScope meta.RESTScope,
|
||||
paramRef *v1.ParamRef,
|
||||
namespace string,
|
||||
) ([]runtime.Object, error) {
|
||||
// If definition has paramKind, paramRef is required in binding.
|
||||
// If definition has no paramKind, paramRef set in binding will be ignored.
|
||||
var params []runtime.Object
|
||||
var paramStore cache.GenericNamespaceLister
|
||||
|
||||
// Make sure the param kind is ready to use
|
||||
if paramKind != nil && paramRef != nil {
|
||||
if paramInformer == nil {
|
||||
return nil, fmt.Errorf("paramKind kind `%v` not known",
|
||||
paramKind.String())
|
||||
}
|
||||
|
||||
// Set up cluster-scoped, or namespaced access to the params
|
||||
// "default" if not provided, and paramKind is namespaced
|
||||
paramStore = paramInformer.Lister()
|
||||
if paramScope.Name() == meta.RESTScopeNameNamespace {
|
||||
paramsNamespace := namespace
|
||||
if len(paramRef.Namespace) > 0 {
|
||||
paramsNamespace = paramRef.Namespace
|
||||
} else if len(paramsNamespace) == 0 {
|
||||
// You must supply namespace if your matcher can possibly
|
||||
// match a cluster-scoped resource
|
||||
return nil, fmt.Errorf("cannot use namespaced paramRef in policy binding that matches cluster-scoped resources")
|
||||
}
|
||||
|
||||
paramStore = paramInformer.Lister().ByNamespace(paramsNamespace)
|
||||
}
|
||||
|
||||
// If the param informer for this admission policy has not yet
|
||||
// had time to perform an initial listing, don't attempt to use
|
||||
// it.
|
||||
timeoutCtx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if !cache.WaitForCacheSync(timeoutCtx.Done(), paramInformer.Informer().HasSynced) {
|
||||
return nil, fmt.Errorf("paramKind kind `%v` not yet synced to use for admission",
|
||||
paramKind.String())
|
||||
}
|
||||
}
|
||||
|
||||
// Find params to use with policy
|
||||
switch {
|
||||
case paramKind == nil:
|
||||
// ParamKind is unset. Ignore any globalParamRef or namespaceParamRef
|
||||
// setting.
|
||||
return []runtime.Object{nil}, nil
|
||||
case paramRef == nil:
|
||||
// Policy ParamKind is set, but binding does not use it.
|
||||
// Validate with nil params
|
||||
return []runtime.Object{nil}, nil
|
||||
case len(paramRef.Namespace) > 0 && paramScope.Name() == meta.RESTScopeRoot.Name():
|
||||
// Not allowed to set namespace for cluster-scoped param
|
||||
return nil, fmt.Errorf("paramRef.namespace must not be provided for a cluster-scoped `paramKind`")
|
||||
|
||||
case len(paramRef.Name) > 0:
|
||||
if paramRef.Selector != nil {
|
||||
// This should be validated, but just in case.
|
||||
return nil, fmt.Errorf("paramRef.name and paramRef.selector are mutually exclusive")
|
||||
}
|
||||
|
||||
switch param, err := paramStore.Get(paramRef.Name); {
|
||||
case err == nil:
|
||||
params = []runtime.Object{param}
|
||||
case apierrors.IsNotFound(err):
|
||||
// Param not yet available. User may need to wait a bit
|
||||
// before being able to use it for validation.
|
||||
//
|
||||
// Set params to nil to prepare for not found action
|
||||
params = nil
|
||||
case apierrors.IsInvalid(err):
|
||||
// Param mis-configured
|
||||
// require to set namespace for namespaced resource
|
||||
// and unset namespace for cluster scoped resource
|
||||
return nil, err
|
||||
default:
|
||||
// Internal error
|
||||
utilruntime.HandleError(err)
|
||||
return nil, err
|
||||
}
|
||||
case paramRef.Selector != nil:
|
||||
// Select everything by default if empty name and selector
|
||||
selector, err := metav1.LabelSelectorAsSelector(paramRef.Selector)
|
||||
if err != nil {
|
||||
// Cannot parse label selector: configuration error
|
||||
return nil, err
|
||||
|
||||
}
|
||||
|
||||
paramList, err := paramStore.List(selector)
|
||||
if err != nil {
|
||||
// There was a bad internal error
|
||||
utilruntime.HandleError(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Successfully grabbed params
|
||||
params = paramList
|
||||
default:
|
||||
// Should be unreachable due to validation
|
||||
return nil, fmt.Errorf("one of name or selector must be provided")
|
||||
}
|
||||
|
||||
// Apply fail action for params not found case
|
||||
if len(params) == 0 && paramRef.ParameterNotFoundAction != nil && *paramRef.ParameterNotFoundAction == v1.DenyAction {
|
||||
return nil, errors.New("no params found for policy binding with `Deny` parameterNotFoundAction")
|
||||
}
|
||||
|
||||
return params, nil
|
||||
}
|
||||
|
||||
var _ webhookgeneric.VersionedAttributeAccessor = &versionedAttributeAccessor{}
|
||||
|
||||
type versionedAttributeAccessor struct {
|
||||
versionedAttrs map[schema.GroupVersionKind]*admission.VersionedAttributes
|
||||
attr admission.Attributes
|
||||
objectInterfaces admission.ObjectInterfaces
|
||||
}
|
||||
|
||||
func (v *versionedAttributeAccessor) VersionedAttribute(gvk schema.GroupVersionKind) (*admission.VersionedAttributes, error) {
|
||||
if val, ok := v.versionedAttrs[gvk]; ok {
|
||||
return val, nil
|
||||
}
|
||||
versionedAttr, err := admission.NewVersionedAttributes(v.attr, gvk, v.objectInterfaces)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v.versionedAttrs[gvk] = versionedAttr
|
||||
return versionedAttr, nil
|
||||
}
|
||||
|
||||
type PolicyError struct {
|
||||
Policy PolicyAccessor
|
||||
Binding BindingAccessor
|
||||
Message error
|
||||
Reason metav1.StatusReason
|
||||
}
|
||||
|
||||
func (c PolicyError) Error() string {
|
||||
if c.Binding != nil {
|
||||
return fmt.Sprintf("policy '%s' with binding '%s' denied request: %s", c.Policy.GetName(), c.Binding.GetName(), c.Message.Error())
|
||||
}
|
||||
|
||||
return fmt.Sprintf("policy %q denied request: %s", c.Policy.GetName(), c.Message.Error())
|
||||
}
|
108
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/generic/policy_matcher.go
generated
vendored
108
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/generic/policy_matcher.go
generated
vendored
@ -1,108 +0,0 @@
|
||||
/*
|
||||
Copyright 2022 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 generic
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/policy/matching"
|
||||
)
|
||||
|
||||
// Matcher is used for matching ValidatingAdmissionPolicy and ValidatingAdmissionPolicyBinding to attributes
|
||||
type PolicyMatcher interface {
|
||||
admission.InitializationValidator
|
||||
|
||||
// DefinitionMatches says whether this policy definition matches the provided admission
|
||||
// resource request
|
||||
DefinitionMatches(a admission.Attributes, o admission.ObjectInterfaces, definition PolicyAccessor) (bool, schema.GroupVersionResource, schema.GroupVersionKind, error)
|
||||
|
||||
// BindingMatches says whether this policy definition matches the provided admission
|
||||
// resource request
|
||||
BindingMatches(a admission.Attributes, o admission.ObjectInterfaces, binding BindingAccessor) (bool, error)
|
||||
|
||||
// GetNamespace retrieves the Namespace resource by the given name. The name may be empty, in which case
|
||||
// GetNamespace must return nil, nil
|
||||
GetNamespace(name string) (*corev1.Namespace, error)
|
||||
}
|
||||
|
||||
type matcher struct {
|
||||
Matcher *matching.Matcher
|
||||
}
|
||||
|
||||
func NewPolicyMatcher(m *matching.Matcher) PolicyMatcher {
|
||||
return &matcher{
|
||||
Matcher: m,
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateInitialization checks if Matcher is initialized.
|
||||
func (c *matcher) ValidateInitialization() error {
|
||||
return c.Matcher.ValidateInitialization()
|
||||
}
|
||||
|
||||
// DefinitionMatches returns whether this ValidatingAdmissionPolicy matches the provided admission resource request
|
||||
func (c *matcher) DefinitionMatches(a admission.Attributes, o admission.ObjectInterfaces, definition PolicyAccessor) (bool, schema.GroupVersionResource, schema.GroupVersionKind, error) {
|
||||
constraints := definition.GetMatchConstraints()
|
||||
if constraints == nil {
|
||||
return false, schema.GroupVersionResource{}, schema.GroupVersionKind{}, fmt.Errorf("policy contained no match constraints, a required field")
|
||||
}
|
||||
criteria := matchCriteria{constraints: constraints}
|
||||
return c.Matcher.Matches(a, o, &criteria)
|
||||
}
|
||||
|
||||
// BindingMatches returns whether this ValidatingAdmissionPolicyBinding matches the provided admission resource request
|
||||
func (c *matcher) BindingMatches(a admission.Attributes, o admission.ObjectInterfaces, binding BindingAccessor) (bool, error) {
|
||||
matchResources := binding.GetMatchResources()
|
||||
if matchResources == nil {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
criteria := matchCriteria{constraints: matchResources}
|
||||
isMatch, _, _, err := c.Matcher.Matches(a, o, &criteria)
|
||||
return isMatch, err
|
||||
}
|
||||
|
||||
func (c *matcher) GetNamespace(name string) (*corev1.Namespace, error) {
|
||||
return c.Matcher.GetNamespace(name)
|
||||
}
|
||||
|
||||
var _ matching.MatchCriteria = &matchCriteria{}
|
||||
|
||||
type matchCriteria struct {
|
||||
constraints *admissionregistrationv1.MatchResources
|
||||
}
|
||||
|
||||
// GetParsedNamespaceSelector returns the converted LabelSelector which implements labels.Selector
|
||||
func (m *matchCriteria) GetParsedNamespaceSelector() (labels.Selector, error) {
|
||||
return metav1.LabelSelectorAsSelector(m.constraints.NamespaceSelector)
|
||||
}
|
||||
|
||||
// GetParsedObjectSelector returns the converted LabelSelector which implements labels.Selector
|
||||
func (m *matchCriteria) GetParsedObjectSelector() (labels.Selector, error) {
|
||||
return metav1.LabelSelectorAsSelector(m.constraints.ObjectSelector)
|
||||
}
|
||||
|
||||
// GetMatchResources returns the matchConstraints
|
||||
func (m *matchCriteria) GetMatchResources() admissionregistrationv1.MatchResources {
|
||||
return *m.constraints
|
||||
}
|
493
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/generic/policy_source.go
generated
vendored
493
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/generic/policy_source.go
generated
vendored
@ -1,493 +0,0 @@
|
||||
/*
|
||||
Copyright 2024 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 generic
|
||||
|
||||
import (
|
||||
"context"
|
||||
goerrors "errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/policy/internal/generic"
|
||||
"k8s.io/client-go/dynamic"
|
||||
"k8s.io/client-go/dynamic/dynamicinformer"
|
||||
"k8s.io/client-go/informers"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
// Interval for refreshing policies.
|
||||
// TODO: Consider reducing this to a shorter duration or replacing this entirely
|
||||
// with checks that detect when a policy change took effect.
|
||||
const policyRefreshIntervalDefault = 1 * time.Second
|
||||
|
||||
var policyRefreshInterval = policyRefreshIntervalDefault
|
||||
|
||||
type policySource[P runtime.Object, B runtime.Object, E Evaluator] struct {
|
||||
ctx context.Context
|
||||
policyInformer generic.Informer[P]
|
||||
bindingInformer generic.Informer[B]
|
||||
restMapper meta.RESTMapper
|
||||
newPolicyAccessor func(P) PolicyAccessor
|
||||
newBindingAccessor func(B) BindingAccessor
|
||||
|
||||
informerFactory informers.SharedInformerFactory
|
||||
dynamicClient dynamic.Interface
|
||||
|
||||
compiler func(P) E
|
||||
|
||||
// Currently compiled list of valid/active policy-binding pairs
|
||||
policies atomic.Pointer[[]PolicyHook[P, B, E]]
|
||||
// Whether the cache of policies is dirty and needs to be recompiled
|
||||
policiesDirty atomic.Bool
|
||||
|
||||
lock sync.Mutex
|
||||
compiledPolicies map[types.NamespacedName]compiledPolicyEntry[E]
|
||||
|
||||
// Temporary until we use the dynamic informer factory
|
||||
paramsCRDControllers map[schema.GroupVersionKind]*paramInfo
|
||||
}
|
||||
|
||||
type paramInfo struct {
|
||||
mapping meta.RESTMapping
|
||||
|
||||
// When the param is changed, or the informer is done being used, the cancel
|
||||
// func should be called to stop/cleanup the original informer
|
||||
cancelFunc func()
|
||||
|
||||
// The lister for this param
|
||||
informer informers.GenericInformer
|
||||
}
|
||||
|
||||
type compiledPolicyEntry[E Evaluator] struct {
|
||||
policyVersion string
|
||||
evaluator E
|
||||
}
|
||||
|
||||
type PolicyHook[P runtime.Object, B runtime.Object, E Evaluator] struct {
|
||||
Policy P
|
||||
Bindings []B
|
||||
|
||||
// ParamInformer is the informer for the param CRD for this policy, or nil if
|
||||
// there is no param or if there was a configuration error
|
||||
ParamInformer informers.GenericInformer
|
||||
ParamScope meta.RESTScope
|
||||
|
||||
Evaluator E
|
||||
ConfigurationError error
|
||||
}
|
||||
|
||||
var _ Source[PolicyHook[runtime.Object, runtime.Object, Evaluator]] = &policySource[runtime.Object, runtime.Object, Evaluator]{}
|
||||
|
||||
func NewPolicySource[P runtime.Object, B runtime.Object, E Evaluator](
|
||||
policyInformer cache.SharedIndexInformer,
|
||||
bindingInformer cache.SharedIndexInformer,
|
||||
newPolicyAccessor func(P) PolicyAccessor,
|
||||
newBindingAccessor func(B) BindingAccessor,
|
||||
compiler func(P) E,
|
||||
paramInformerFactory informers.SharedInformerFactory,
|
||||
dynamicClient dynamic.Interface,
|
||||
restMapper meta.RESTMapper,
|
||||
) Source[PolicyHook[P, B, E]] {
|
||||
res := &policySource[P, B, E]{
|
||||
compiler: compiler,
|
||||
policyInformer: generic.NewInformer[P](policyInformer),
|
||||
bindingInformer: generic.NewInformer[B](bindingInformer),
|
||||
compiledPolicies: map[types.NamespacedName]compiledPolicyEntry[E]{},
|
||||
newPolicyAccessor: newPolicyAccessor,
|
||||
newBindingAccessor: newBindingAccessor,
|
||||
paramsCRDControllers: map[schema.GroupVersionKind]*paramInfo{},
|
||||
informerFactory: paramInformerFactory,
|
||||
dynamicClient: dynamicClient,
|
||||
restMapper: restMapper,
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// SetPolicyRefreshIntervalForTests allows the refresh interval to be overridden during tests.
|
||||
// This should only be called from tests.
|
||||
func SetPolicyRefreshIntervalForTests(interval time.Duration) func() {
|
||||
policyRefreshInterval = interval
|
||||
return func() {
|
||||
policyRefreshInterval = policyRefreshIntervalDefault
|
||||
}
|
||||
}
|
||||
|
||||
func (s *policySource[P, B, E]) Run(ctx context.Context) error {
|
||||
if s.ctx != nil {
|
||||
return fmt.Errorf("policy source already running")
|
||||
}
|
||||
|
||||
// Wait for initial cache sync of policies and informers before reconciling
|
||||
// any
|
||||
if !cache.WaitForNamedCacheSync(fmt.Sprintf("%T", s), ctx.Done(), s.UpstreamHasSynced) {
|
||||
err := ctx.Err()
|
||||
if err == nil {
|
||||
err = fmt.Errorf("initial cache sync for %T failed", s)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
s.ctx = ctx
|
||||
|
||||
// Perform initial policy compilation after initial list has finished
|
||||
s.notify()
|
||||
s.refreshPolicies()
|
||||
|
||||
notifyFuncs := cache.ResourceEventHandlerFuncs{
|
||||
AddFunc: func(_ interface{}) {
|
||||
s.notify()
|
||||
},
|
||||
UpdateFunc: func(_, _ interface{}) {
|
||||
s.notify()
|
||||
},
|
||||
DeleteFunc: func(_ interface{}) {
|
||||
s.notify()
|
||||
},
|
||||
}
|
||||
handle, err := s.policyInformer.AddEventHandler(notifyFuncs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if err := s.policyInformer.RemoveEventHandler(handle); err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("failed to remove policy event handler: %w", err))
|
||||
}
|
||||
}()
|
||||
|
||||
bindingHandle, err := s.bindingInformer.AddEventHandler(notifyFuncs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if err := s.bindingInformer.RemoveEventHandler(bindingHandle); err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("failed to remove binding event handler: %w", err))
|
||||
}
|
||||
}()
|
||||
|
||||
// Start a worker that checks every second to see if policy data is dirty
|
||||
// and needs to be recompiled
|
||||
go func() {
|
||||
// Loop every 1 second until context is cancelled, refreshing policies
|
||||
wait.Until(s.refreshPolicies, policyRefreshInterval, ctx.Done())
|
||||
}()
|
||||
|
||||
<-ctx.Done()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *policySource[P, B, E]) UpstreamHasSynced() bool {
|
||||
return s.policyInformer.HasSynced() && s.bindingInformer.HasSynced()
|
||||
}
|
||||
|
||||
// HasSynced implements Source.
|
||||
func (s *policySource[P, B, E]) HasSynced() bool {
|
||||
// As an invariant we never store `nil` into the atomic list of processed
|
||||
// policy hooks. If it is nil, then we haven't compiled all the policies
|
||||
// and stored them yet.
|
||||
return s.Hooks() != nil
|
||||
}
|
||||
|
||||
// Hooks implements Source.
|
||||
func (s *policySource[P, B, E]) Hooks() []PolicyHook[P, B, E] {
|
||||
res := s.policies.Load()
|
||||
|
||||
// Error case should not happen since evaluation function never
|
||||
// returns error
|
||||
if res == nil {
|
||||
// Not yet synced
|
||||
return nil
|
||||
}
|
||||
|
||||
return *res
|
||||
}
|
||||
|
||||
func (s *policySource[P, B, E]) refreshPolicies() {
|
||||
if !s.UpstreamHasSynced() {
|
||||
return
|
||||
} else if !s.policiesDirty.Swap(false) {
|
||||
return
|
||||
}
|
||||
|
||||
// It is ok the cache gets marked dirty again between us clearing the
|
||||
// flag and us calculating the policies. The dirty flag would be marked again,
|
||||
// and we'd have a no-op after comparing resource versions on the next sync.
|
||||
klog.Infof("refreshing policies")
|
||||
policies, err := s.calculatePolicyData()
|
||||
|
||||
// Intentionally store policy list regardless of error. There may be
|
||||
// an error returned if there was a configuration error in one of the policies,
|
||||
// but we would still want those policies evaluated
|
||||
// (for instance to return error on failaction). Or if there was an error
|
||||
// listing all policies at all, we would want to wipe the list.
|
||||
s.policies.Store(&policies)
|
||||
|
||||
if err != nil {
|
||||
// An error was generated while syncing policies. Mark it as dirty again
|
||||
// so we can retry later
|
||||
utilruntime.HandleError(fmt.Errorf("encountered error syncing policies: %w. Rescheduling policy sync", err))
|
||||
s.notify()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *policySource[P, B, E]) notify() {
|
||||
s.policiesDirty.Store(true)
|
||||
}
|
||||
|
||||
// calculatePolicyData calculates the list of policies and bindings for each
|
||||
// policy. If there is an error in generation, it will return the error and
|
||||
// the partial list of policies that were able to be generated. Policies that
|
||||
// have an error will have a non-nil ConfigurationError field, but still be
|
||||
// included in the result.
|
||||
//
|
||||
// This function caches the result of the intermediate compilations
|
||||
func (s *policySource[P, B, E]) calculatePolicyData() ([]PolicyHook[P, B, E], error) {
|
||||
if !s.UpstreamHasSynced() {
|
||||
return nil, fmt.Errorf("cannot calculate policy data until upstream has synced")
|
||||
}
|
||||
|
||||
// Fat-fingered lock that can be made more fine-tuned if required
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
// Create a local copy of all policies and bindings
|
||||
policiesToBindings := map[types.NamespacedName][]B{}
|
||||
bindingList, err := s.bindingInformer.List(labels.Everything())
|
||||
if err != nil {
|
||||
// This should never happen unless types are misconfigured
|
||||
// (can't use meta.accessor on them)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Gather a list of all active policy bindings
|
||||
for _, bindingSpec := range bindingList {
|
||||
bindingAccessor := s.newBindingAccessor(bindingSpec)
|
||||
policyKey := bindingAccessor.GetPolicyName()
|
||||
|
||||
// Add this binding to the list of bindings for this policy
|
||||
policiesToBindings[policyKey] = append(policiesToBindings[policyKey], bindingSpec)
|
||||
}
|
||||
|
||||
result := make([]PolicyHook[P, B, E], 0, len(bindingList))
|
||||
usedParams := map[schema.GroupVersionKind]struct{}{}
|
||||
var errs []error
|
||||
for policyKey, bindingSpecs := range policiesToBindings {
|
||||
var inf generic.NamespacedLister[P] = s.policyInformer
|
||||
if len(policyKey.Namespace) > 0 {
|
||||
inf = s.policyInformer.Namespaced(policyKey.Namespace)
|
||||
}
|
||||
policySpec, err := inf.Get(policyKey.Name)
|
||||
if errors.IsNotFound(err) {
|
||||
// Policy for bindings doesn't exist. This can happen if the policy
|
||||
// was deleted before the binding, or the binding was created first.
|
||||
//
|
||||
// Just skip bindings that refer to non-existent policies
|
||||
// If the policy is recreated, the cache will be marked dirty and
|
||||
// this function will run again.
|
||||
continue
|
||||
} else if err != nil {
|
||||
// This should never happen since fetching from a cache should never
|
||||
// fail and this function checks that the cache was synced before
|
||||
// even getting to this point.
|
||||
errs = append(errs, err)
|
||||
continue
|
||||
}
|
||||
|
||||
var parsedParamKind *schema.GroupVersionKind
|
||||
policyAccessor := s.newPolicyAccessor(policySpec)
|
||||
|
||||
if paramKind := policyAccessor.GetParamKind(); paramKind != nil {
|
||||
groupVersion, err := schema.ParseGroupVersion(paramKind.APIVersion)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("failed to parse paramKind APIVersion: %w", err))
|
||||
continue
|
||||
}
|
||||
parsedParamKind = &schema.GroupVersionKind{
|
||||
Group: groupVersion.Group,
|
||||
Version: groupVersion.Version,
|
||||
Kind: paramKind.Kind,
|
||||
}
|
||||
|
||||
// TEMPORARY UNTIL WE HAVE SHARED PARAM INFORMERS
|
||||
usedParams[*parsedParamKind] = struct{}{}
|
||||
}
|
||||
|
||||
paramInformer, paramScope, configurationError := s.ensureParamsForPolicyLocked(parsedParamKind)
|
||||
result = append(result, PolicyHook[P, B, E]{
|
||||
Policy: policySpec,
|
||||
Bindings: bindingSpecs,
|
||||
Evaluator: s.compilePolicyLocked(policySpec),
|
||||
ParamInformer: paramInformer,
|
||||
ParamScope: paramScope,
|
||||
ConfigurationError: configurationError,
|
||||
})
|
||||
|
||||
// Should queue a re-sync for policy sync error. If our shared param
|
||||
// informer can notify us when CRD discovery changes we can remove this
|
||||
// and just rely on the informer to notify us when the CRDs change
|
||||
if configurationError != nil {
|
||||
errs = append(errs, configurationError)
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up orphaned policies by replacing the old cache of compiled policies
|
||||
// (the map of used policies is updated by `compilePolicy`)
|
||||
for policyKey := range s.compiledPolicies {
|
||||
if _, wasSeen := policiesToBindings[policyKey]; !wasSeen {
|
||||
delete(s.compiledPolicies, policyKey)
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up orphaned param informers
|
||||
for paramKind, info := range s.paramsCRDControllers {
|
||||
if _, wasSeen := usedParams[paramKind]; !wasSeen {
|
||||
info.cancelFunc()
|
||||
delete(s.paramsCRDControllers, paramKind)
|
||||
}
|
||||
}
|
||||
|
||||
err = nil
|
||||
if len(errs) > 0 {
|
||||
err = goerrors.Join(errs...)
|
||||
}
|
||||
return result, err
|
||||
}
|
||||
|
||||
// ensureParamsForPolicyLocked ensures that the informer for the paramKind is
|
||||
// started and returns the informer and the scope of the paramKind.
|
||||
//
|
||||
// Must be called under write lock
|
||||
func (s *policySource[P, B, E]) ensureParamsForPolicyLocked(paramSource *schema.GroupVersionKind) (informers.GenericInformer, meta.RESTScope, error) {
|
||||
if paramSource == nil {
|
||||
return nil, nil, nil
|
||||
} else if info, ok := s.paramsCRDControllers[*paramSource]; ok {
|
||||
return info.informer, info.mapping.Scope, nil
|
||||
}
|
||||
|
||||
mapping, err := s.restMapper.RESTMapping(schema.GroupKind{
|
||||
Group: paramSource.Group,
|
||||
Kind: paramSource.Kind,
|
||||
}, paramSource.Version)
|
||||
|
||||
if err != nil {
|
||||
// Failed to resolve. Return error so we retry again (rate limited)
|
||||
// Save a record of this definition with an evaluator that unconditionally
|
||||
return nil, nil, fmt.Errorf("failed to find resource referenced by paramKind: '%v'", *paramSource)
|
||||
}
|
||||
|
||||
// We are not watching this param. Start an informer for it.
|
||||
instanceContext, instanceCancel := context.WithCancel(s.ctx)
|
||||
|
||||
var informer informers.GenericInformer
|
||||
|
||||
// Try to see if our provided informer factory has an informer for this type.
|
||||
// We assume the informer is already started, and starts all types associated
|
||||
// with it.
|
||||
if genericInformer, err := s.informerFactory.ForResource(mapping.Resource); err == nil {
|
||||
informer = genericInformer
|
||||
|
||||
// Start the informer
|
||||
s.informerFactory.Start(instanceContext.Done())
|
||||
|
||||
} else {
|
||||
// Dynamic JSON informer fallback.
|
||||
// Cannot use shared dynamic informer since it would be impossible
|
||||
// to clean CRD informers properly with multiple dependents
|
||||
// (cannot start ahead of time, and cannot track dependencies via stopCh)
|
||||
informer = dynamicinformer.NewFilteredDynamicInformer(
|
||||
s.dynamicClient,
|
||||
mapping.Resource,
|
||||
corev1.NamespaceAll,
|
||||
// Use same interval as is used for k8s typed sharedInformerFactory
|
||||
// https://github.com/kubernetes/kubernetes/blob/7e0923899fed622efbc8679cca6b000d43633e38/cmd/kube-apiserver/app/server.go#L430
|
||||
10*time.Minute,
|
||||
cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc},
|
||||
nil,
|
||||
)
|
||||
go informer.Informer().Run(instanceContext.Done())
|
||||
}
|
||||
|
||||
klog.Infof("informer started for %v", *paramSource)
|
||||
ret := ¶mInfo{
|
||||
mapping: *mapping,
|
||||
cancelFunc: instanceCancel,
|
||||
informer: informer,
|
||||
}
|
||||
s.paramsCRDControllers[*paramSource] = ret
|
||||
return ret.informer, mapping.Scope, nil
|
||||
}
|
||||
|
||||
// For testing
|
||||
func (s *policySource[P, B, E]) getParamInformer(param schema.GroupVersionKind) (informers.GenericInformer, meta.RESTScope) {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
if info, ok := s.paramsCRDControllers[param]; ok {
|
||||
return info.informer, info.mapping.Scope
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// compilePolicyLocked compiles the policy and returns the evaluator for it.
|
||||
// If the policy has not changed since the last compilation, it will return
|
||||
// the cached evaluator.
|
||||
//
|
||||
// Must be called under write lock
|
||||
func (s *policySource[P, B, E]) compilePolicyLocked(policySpec P) E {
|
||||
policyMeta, err := meta.Accessor(policySpec)
|
||||
if err != nil {
|
||||
// This should not happen if P, and B have ObjectMeta, but
|
||||
// unfortunately there is no way to express "able to call
|
||||
// meta.Accessor" as a type constraint
|
||||
utilruntime.HandleError(err)
|
||||
var emptyEvaluator E
|
||||
return emptyEvaluator
|
||||
}
|
||||
key := types.NamespacedName{
|
||||
Namespace: policyMeta.GetNamespace(),
|
||||
Name: policyMeta.GetName(),
|
||||
}
|
||||
|
||||
compiledPolicy, wasCompiled := s.compiledPolicies[key]
|
||||
|
||||
// If the policy or binding has changed since it was last compiled,
|
||||
// and if there is no configuration error (like a missing param CRD)
|
||||
// then we recompile
|
||||
if !wasCompiled ||
|
||||
compiledPolicy.policyVersion != policyMeta.GetResourceVersion() {
|
||||
|
||||
compiledPolicy = compiledPolicyEntry[E]{
|
||||
policyVersion: policyMeta.GetResourceVersion(),
|
||||
evaluator: s.compiler(policySpec),
|
||||
}
|
||||
s.compiledPolicies[key] = compiledPolicy
|
||||
}
|
||||
|
||||
return compiledPolicy.evaluator
|
||||
}
|
626
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/generic/policy_test_context.go
generated
vendored
626
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/generic/policy_test_context.go
generated
vendored
@ -1,626 +0,0 @@
|
||||
/*
|
||||
Copyright 2024 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 generic
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/uuid"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
"k8s.io/client-go/dynamic"
|
||||
dynamicfake "k8s.io/client-go/dynamic/fake"
|
||||
"k8s.io/client-go/informers"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
clienttesting "k8s.io/client-go/testing"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
"k8s.io/component-base/featuregate"
|
||||
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/admission/initializer"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
)
|
||||
|
||||
// PolicyTestContext is everything you need to unit test a policy plugin
|
||||
type PolicyTestContext[P runtime.Object, B runtime.Object, E Evaluator] struct {
|
||||
context.Context
|
||||
Plugin *Plugin[PolicyHook[P, B, E]]
|
||||
Source Source[PolicyHook[P, B, E]]
|
||||
Start func() error
|
||||
|
||||
scheme *runtime.Scheme
|
||||
restMapper *meta.DefaultRESTMapper
|
||||
policyGVR schema.GroupVersionResource
|
||||
bindingGVR schema.GroupVersionResource
|
||||
|
||||
policyGVK schema.GroupVersionKind
|
||||
bindingGVK schema.GroupVersionKind
|
||||
|
||||
nativeTracker clienttesting.ObjectTracker
|
||||
policyAndBindingTracker clienttesting.ObjectTracker
|
||||
unstructuredTracker clienttesting.ObjectTracker
|
||||
}
|
||||
|
||||
func NewPolicyTestContext[P, B runtime.Object, E Evaluator](
|
||||
newPolicyAccessor func(P) PolicyAccessor,
|
||||
newBindingAccessor func(B) BindingAccessor,
|
||||
compileFunc func(P) E,
|
||||
dispatcher dispatcherFactory[PolicyHook[P, B, E]],
|
||||
initialObjects []runtime.Object,
|
||||
paramMappings []meta.RESTMapping,
|
||||
) (*PolicyTestContext[P, B, E], func(), error) {
|
||||
var Pexample P
|
||||
var Bexample B
|
||||
|
||||
// Create a fake resource and kind for the provided policy and binding types
|
||||
fakePolicyGVR := schema.GroupVersionResource{
|
||||
Group: "policy.example.com",
|
||||
Version: "v1",
|
||||
Resource: "fakepolicies",
|
||||
}
|
||||
fakeBindingGVR := schema.GroupVersionResource{
|
||||
Group: "policy.example.com",
|
||||
Version: "v1",
|
||||
Resource: "fakebindings",
|
||||
}
|
||||
fakePolicyGVK := fakePolicyGVR.GroupVersion().WithKind("FakePolicy")
|
||||
fakeBindingGVK := fakeBindingGVR.GroupVersion().WithKind("FakeBinding")
|
||||
|
||||
policySourceTestScheme, err := func() (*runtime.Scheme, error) {
|
||||
scheme := runtime.NewScheme()
|
||||
|
||||
if err := fake.AddToScheme(scheme); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
scheme.AddKnownTypeWithName(fakePolicyGVK, Pexample)
|
||||
scheme.AddKnownTypeWithName(fakeBindingGVK, Bexample)
|
||||
scheme.AddKnownTypeWithName(fakePolicyGVK.GroupVersion().WithKind(fakePolicyGVK.Kind+"List"), &FakeList[P]{})
|
||||
scheme.AddKnownTypeWithName(fakeBindingGVK.GroupVersion().WithKind(fakeBindingGVK.Kind+"List"), &FakeList[B]{})
|
||||
|
||||
for _, mapping := range paramMappings {
|
||||
// Skip if it is in the scheme already
|
||||
if scheme.Recognizes(mapping.GroupVersionKind) {
|
||||
continue
|
||||
}
|
||||
scheme.AddKnownTypeWithName(mapping.GroupVersionKind, &unstructured.Unstructured{})
|
||||
scheme.AddKnownTypeWithName(mapping.GroupVersionKind.GroupVersion().WithKind(mapping.GroupVersionKind.Kind+"List"), &unstructured.UnstructuredList{})
|
||||
}
|
||||
|
||||
return scheme, nil
|
||||
}()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
fakeRestMapper := func() *meta.DefaultRESTMapper {
|
||||
res := meta.NewDefaultRESTMapper([]schema.GroupVersion{
|
||||
{
|
||||
Group: "",
|
||||
Version: "v1",
|
||||
},
|
||||
})
|
||||
|
||||
res.Add(fakePolicyGVK, meta.RESTScopeRoot)
|
||||
res.Add(fakeBindingGVK, meta.RESTScopeRoot)
|
||||
res.Add(corev1.SchemeGroupVersion.WithKind("ConfigMap"), meta.RESTScopeNamespace)
|
||||
|
||||
for _, mapping := range paramMappings {
|
||||
res.AddSpecific(mapping.GroupVersionKind, mapping.Resource, mapping.Resource, mapping.Scope)
|
||||
}
|
||||
|
||||
return res
|
||||
}()
|
||||
|
||||
nativeClient := fake.NewSimpleClientset()
|
||||
dynamicClient := dynamicfake.NewSimpleDynamicClient(policySourceTestScheme)
|
||||
fakeInformerFactory := informers.NewSharedInformerFactory(nativeClient, 30*time.Second)
|
||||
|
||||
// Make an object tracker specifically for our policies and bindings
|
||||
policiesAndBindingsTracker := clienttesting.NewObjectTracker(
|
||||
policySourceTestScheme,
|
||||
serializer.NewCodecFactory(policySourceTestScheme).UniversalDecoder())
|
||||
|
||||
// Make an informer for our policies and bindings
|
||||
|
||||
policyInformer := cache.NewSharedIndexInformer(
|
||||
&cache.ListWatch{
|
||||
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
|
||||
return policiesAndBindingsTracker.List(fakePolicyGVR, fakePolicyGVK, "")
|
||||
},
|
||||
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
|
||||
return policiesAndBindingsTracker.Watch(fakePolicyGVR, "")
|
||||
},
|
||||
},
|
||||
Pexample,
|
||||
30*time.Second,
|
||||
cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc},
|
||||
)
|
||||
bindingInformer := cache.NewSharedIndexInformer(
|
||||
&cache.ListWatch{
|
||||
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
|
||||
return policiesAndBindingsTracker.List(fakeBindingGVR, fakeBindingGVK, "")
|
||||
},
|
||||
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
|
||||
return policiesAndBindingsTracker.Watch(fakeBindingGVR, "")
|
||||
},
|
||||
},
|
||||
Bexample,
|
||||
30*time.Second,
|
||||
cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc},
|
||||
)
|
||||
|
||||
var source Source[PolicyHook[P, B, E]]
|
||||
plugin := NewPlugin[PolicyHook[P, B, E]](
|
||||
admission.NewHandler(admission.Connect, admission.Create, admission.Delete, admission.Update),
|
||||
func(sif informers.SharedInformerFactory, i1 kubernetes.Interface, i2 dynamic.Interface, r meta.RESTMapper) Source[PolicyHook[P, B, E]] {
|
||||
source = NewPolicySource[P, B, E](
|
||||
policyInformer,
|
||||
bindingInformer,
|
||||
newPolicyAccessor,
|
||||
newBindingAccessor,
|
||||
compileFunc,
|
||||
sif,
|
||||
i2,
|
||||
r,
|
||||
)
|
||||
return source
|
||||
}, dispatcher)
|
||||
plugin.SetEnabled(true)
|
||||
|
||||
featureGate := featuregate.NewFeatureGate()
|
||||
testContext, testCancel := context.WithCancel(context.Background())
|
||||
genericInitializer := initializer.New(
|
||||
nativeClient,
|
||||
dynamicClient,
|
||||
fakeInformerFactory,
|
||||
fakeAuthorizer{},
|
||||
featureGate,
|
||||
testContext.Done(),
|
||||
fakeRestMapper,
|
||||
)
|
||||
genericInitializer.Initialize(plugin)
|
||||
plugin.SetRESTMapper(fakeRestMapper)
|
||||
|
||||
if err := plugin.ValidateInitialization(); err != nil {
|
||||
testCancel()
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
res := &PolicyTestContext[P, B, E]{
|
||||
Context: testContext,
|
||||
Plugin: plugin,
|
||||
Source: source,
|
||||
|
||||
restMapper: fakeRestMapper,
|
||||
scheme: policySourceTestScheme,
|
||||
policyGVK: fakePolicyGVK,
|
||||
bindingGVK: fakeBindingGVK,
|
||||
policyGVR: fakePolicyGVR,
|
||||
bindingGVR: fakeBindingGVR,
|
||||
nativeTracker: nativeClient.Tracker(),
|
||||
policyAndBindingTracker: policiesAndBindingsTracker,
|
||||
unstructuredTracker: dynamicClient.Tracker(),
|
||||
}
|
||||
|
||||
for _, obj := range initialObjects {
|
||||
err := res.updateOne(obj)
|
||||
if err != nil {
|
||||
testCancel()
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
res.Start = func() error {
|
||||
fakeInformerFactory.Start(res.Done())
|
||||
go policyInformer.Run(res.Done())
|
||||
go bindingInformer.Run(res.Done())
|
||||
|
||||
if !cache.WaitForCacheSync(res.Done(), res.Source.HasSynced) {
|
||||
return fmt.Errorf("timed out waiting for initial cache sync")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return res, testCancel, nil
|
||||
}
|
||||
|
||||
// UpdateAndWait updates the given object in the test, or creates it if it doesn't exist
|
||||
// Depending upon object type, waits afterward until the object is synced
|
||||
// by the policy source
|
||||
//
|
||||
// Be aware the UpdateAndWait will modify the ResourceVersion of the
|
||||
// provided objects.
|
||||
func (p *PolicyTestContext[P, B, E]) UpdateAndWait(objects ...runtime.Object) error {
|
||||
return p.update(true, objects...)
|
||||
}
|
||||
|
||||
// Update updates the given object in the test, or creates it if it doesn't exist
|
||||
//
|
||||
// Be aware the Update will modify the ResourceVersion of the
|
||||
// provided objects.
|
||||
func (p *PolicyTestContext[P, B, E]) Update(objects ...runtime.Object) error {
|
||||
return p.update(false, objects...)
|
||||
}
|
||||
|
||||
// Objects the given object in the test, or creates it if it doesn't exist
|
||||
// Depending upon object type, waits afterward until the object is synced
|
||||
// by the policy source
|
||||
func (p *PolicyTestContext[P, B, E]) update(wait bool, objects ...runtime.Object) error {
|
||||
for _, object := range objects {
|
||||
if err := p.updateOne(object); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if wait {
|
||||
timeoutCtx, timeoutCancel := context.WithTimeout(p, 3*time.Second)
|
||||
defer timeoutCancel()
|
||||
|
||||
for _, object := range objects {
|
||||
if err := p.WaitForReconcile(timeoutCtx, object); err != nil {
|
||||
return fmt.Errorf("error waiting for reconcile of %v: %v", object, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Depending upon object type, waits afterward until the object is synced
|
||||
// by the policy source. Note that policies that are not bound are skipped,
|
||||
// so you should not try to wait for an unbound policy. Create both the binding
|
||||
// and policy, then wait.
|
||||
func (p *PolicyTestContext[P, B, E]) WaitForReconcile(timeoutCtx context.Context, object runtime.Object) error {
|
||||
if !p.Source.HasSynced() {
|
||||
return nil
|
||||
}
|
||||
|
||||
objectMeta, err := meta.Accessor(object)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
objectGVK, _, err := p.inferGVK(object)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch objectGVK {
|
||||
case p.policyGVK:
|
||||
return wait.PollUntilContextCancel(timeoutCtx, 100*time.Millisecond, true, func(ctx context.Context) (done bool, err error) {
|
||||
policies := p.Source.Hooks()
|
||||
for _, policy := range policies {
|
||||
policyMeta, err := meta.Accessor(policy.Policy)
|
||||
if err != nil {
|
||||
return true, err
|
||||
} else if policyMeta.GetName() == objectMeta.GetName() && policyMeta.GetResourceVersion() == objectMeta.GetResourceVersion() {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
})
|
||||
case p.bindingGVK:
|
||||
return wait.PollUntilContextCancel(timeoutCtx, 100*time.Millisecond, true, func(ctx context.Context) (done bool, err error) {
|
||||
policies := p.Source.Hooks()
|
||||
for _, policy := range policies {
|
||||
for _, binding := range policy.Bindings {
|
||||
bindingMeta, err := meta.Accessor(binding)
|
||||
if err != nil {
|
||||
return true, err
|
||||
} else if bindingMeta.GetName() == objectMeta.GetName() && bindingMeta.GetResourceVersion() == objectMeta.GetResourceVersion() {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
})
|
||||
|
||||
default:
|
||||
// Do nothing, params are visible immediately
|
||||
// Loop until one of the params is visible via get of the param informer
|
||||
return wait.PollUntilContextCancel(timeoutCtx, 100*time.Millisecond, true, func(ctx context.Context) (done bool, err error) {
|
||||
informer, scope := p.Source.(*policySource[P, B, E]).getParamInformer(objectGVK)
|
||||
if informer == nil {
|
||||
// Informer does not exist yet, keep waiting for sync
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if !cache.WaitForCacheSync(timeoutCtx.Done(), informer.Informer().HasSynced) {
|
||||
return false, fmt.Errorf("timed out waiting for cache sync of param informer")
|
||||
}
|
||||
|
||||
var lister cache.GenericNamespaceLister = informer.Lister()
|
||||
if scope == meta.RESTScopeNamespace {
|
||||
lister = informer.Lister().ByNamespace(objectMeta.GetNamespace())
|
||||
}
|
||||
|
||||
fetched, err := lister.Get(objectMeta.GetName())
|
||||
if err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
return false, nil
|
||||
}
|
||||
return true, err
|
||||
}
|
||||
|
||||
// Ensure RV matches
|
||||
fetchedMeta, err := meta.Accessor(fetched)
|
||||
if err != nil {
|
||||
return true, err
|
||||
} else if fetchedMeta.GetResourceVersion() != objectMeta.GetResourceVersion() {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return true, nil
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PolicyTestContext[P, B, E]) waitForDelete(ctx context.Context, objectGVK schema.GroupVersionKind, name types.NamespacedName) error {
|
||||
srce := p.Source.(*policySource[P, B, E])
|
||||
|
||||
return wait.PollUntilContextCancel(ctx, 100*time.Millisecond, true, func(ctx context.Context) (done bool, err error) {
|
||||
switch objectGVK {
|
||||
case p.policyGVK:
|
||||
for _, hook := range p.Source.Hooks() {
|
||||
accessor := srce.newPolicyAccessor(hook.Policy)
|
||||
if accessor.GetName() == name.Name && accessor.GetNamespace() == name.Namespace {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
return true, nil
|
||||
case p.bindingGVK:
|
||||
for _, hook := range p.Source.Hooks() {
|
||||
for _, binding := range hook.Bindings {
|
||||
accessor := srce.newBindingAccessor(binding)
|
||||
if accessor.GetName() == name.Name && accessor.GetNamespace() == name.Namespace {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
default:
|
||||
// Do nothing, params are visible immediately
|
||||
// Loop until one of the params is visible via get of the param informer
|
||||
informer, scope := p.Source.(*policySource[P, B, E]).getParamInformer(objectGVK)
|
||||
if informer == nil {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
var lister cache.GenericNamespaceLister = informer.Lister()
|
||||
if scope == meta.RESTScopeNamespace {
|
||||
lister = informer.Lister().ByNamespace(name.Namespace)
|
||||
}
|
||||
|
||||
_, err = lister.Get(name.Name)
|
||||
if err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
return true, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (p *PolicyTestContext[P, B, E]) updateOne(object runtime.Object) error {
|
||||
objectMeta, err := meta.Accessor(object)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
objectMeta.SetResourceVersion(string(uuid.NewUUID()))
|
||||
objectGVK, gvr, err := p.inferGVK(object)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch objectGVK {
|
||||
case p.policyGVK:
|
||||
err := p.policyAndBindingTracker.Update(p.policyGVR, object, objectMeta.GetNamespace())
|
||||
if errors.IsNotFound(err) {
|
||||
err = p.policyAndBindingTracker.Create(p.policyGVR, object, objectMeta.GetNamespace())
|
||||
}
|
||||
|
||||
return err
|
||||
case p.bindingGVK:
|
||||
err := p.policyAndBindingTracker.Update(p.bindingGVR, object, objectMeta.GetNamespace())
|
||||
if errors.IsNotFound(err) {
|
||||
err = p.policyAndBindingTracker.Create(p.bindingGVR, object, objectMeta.GetNamespace())
|
||||
}
|
||||
|
||||
return err
|
||||
default:
|
||||
if _, ok := object.(*unstructured.Unstructured); ok {
|
||||
if err := p.unstructuredTracker.Create(gvr, object, objectMeta.GetNamespace()); err != nil {
|
||||
if errors.IsAlreadyExists(err) {
|
||||
return p.unstructuredTracker.Update(gvr, object, objectMeta.GetNamespace())
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
} else if err := p.nativeTracker.Create(gvr, object, objectMeta.GetNamespace()); err != nil {
|
||||
if errors.IsAlreadyExists(err) {
|
||||
return p.nativeTracker.Update(gvr, object, objectMeta.GetNamespace())
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Depending upon object type, waits afterward until the object is synced
|
||||
// by the policy source
|
||||
func (p *PolicyTestContext[P, B, E]) DeleteAndWait(object ...runtime.Object) error {
|
||||
for _, object := range object {
|
||||
if err := p.deleteOne(object); err != nil && !errors.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
timeoutCtx, timeoutCancel := context.WithTimeout(p, 3*time.Second)
|
||||
defer timeoutCancel()
|
||||
|
||||
for _, object := range object {
|
||||
accessor, err := meta.Accessor(object)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
objectGVK, _, err := p.inferGVK(object)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := p.waitForDelete(
|
||||
timeoutCtx,
|
||||
objectGVK,
|
||||
types.NamespacedName{Name: accessor.GetName(), Namespace: accessor.GetNamespace()}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *PolicyTestContext[P, B, E]) deleteOne(object runtime.Object) error {
|
||||
objectMeta, err := meta.Accessor(object)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
objectMeta.SetResourceVersion(string(uuid.NewUUID()))
|
||||
objectGVK, gvr, err := p.inferGVK(object)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch objectGVK {
|
||||
case p.policyGVK:
|
||||
return p.policyAndBindingTracker.Delete(p.policyGVR, objectMeta.GetNamespace(), objectMeta.GetName())
|
||||
case p.bindingGVK:
|
||||
return p.policyAndBindingTracker.Delete(p.bindingGVR, objectMeta.GetNamespace(), objectMeta.GetName())
|
||||
default:
|
||||
if _, ok := object.(*unstructured.Unstructured); ok {
|
||||
return p.unstructuredTracker.Delete(gvr, objectMeta.GetNamespace(), objectMeta.GetName())
|
||||
}
|
||||
return p.nativeTracker.Delete(gvr, objectMeta.GetNamespace(), objectMeta.GetName())
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PolicyTestContext[P, B, E]) Dispatch(
|
||||
new, old runtime.Object,
|
||||
operation admission.Operation,
|
||||
) error {
|
||||
if old == nil && new == nil {
|
||||
return fmt.Errorf("both old and new objects cannot be nil")
|
||||
}
|
||||
|
||||
nonNilObject := new
|
||||
if nonNilObject == nil {
|
||||
nonNilObject = old
|
||||
}
|
||||
|
||||
gvk, gvr, err := p.inferGVK(nonNilObject)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
nonNilMeta, err := meta.Accessor(nonNilObject)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return p.Plugin.Dispatch(
|
||||
p,
|
||||
admission.NewAttributesRecord(
|
||||
new,
|
||||
old,
|
||||
gvk,
|
||||
nonNilMeta.GetName(),
|
||||
nonNilMeta.GetNamespace(),
|
||||
gvr,
|
||||
"",
|
||||
operation,
|
||||
nil,
|
||||
false,
|
||||
nil,
|
||||
), admission.NewObjectInterfacesFromScheme(p.scheme))
|
||||
}
|
||||
|
||||
func (p *PolicyTestContext[P, B, E]) inferGVK(object runtime.Object) (schema.GroupVersionKind, schema.GroupVersionResource, error) {
|
||||
objectGVK := object.GetObjectKind().GroupVersionKind()
|
||||
if objectGVK.Empty() {
|
||||
// If the object doesn't have a GVK, ask the schema for preferred GVK
|
||||
knownKinds, _, err := p.scheme.ObjectKinds(object)
|
||||
if err != nil {
|
||||
return schema.GroupVersionKind{}, schema.GroupVersionResource{}, err
|
||||
} else if len(knownKinds) == 0 {
|
||||
return schema.GroupVersionKind{}, schema.GroupVersionResource{}, fmt.Errorf("no known GVKs for object in schema: %T", object)
|
||||
}
|
||||
toTake := 0
|
||||
|
||||
// Prefer GVK if it is our fake policy or binding
|
||||
for i, knownKind := range knownKinds {
|
||||
if knownKind == p.policyGVK || knownKind == p.bindingGVK {
|
||||
toTake = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
objectGVK = knownKinds[toTake]
|
||||
}
|
||||
|
||||
// Make sure GVK is known to the fake rest mapper. To prevent cryptic error
|
||||
mapping, err := p.restMapper.RESTMapping(objectGVK.GroupKind(), objectGVK.Version)
|
||||
if err != nil {
|
||||
return schema.GroupVersionKind{}, schema.GroupVersionResource{}, err
|
||||
}
|
||||
return objectGVK, mapping.Resource, nil
|
||||
}
|
||||
|
||||
type FakeList[T runtime.Object] struct {
|
||||
metav1.TypeMeta
|
||||
metav1.ListMeta
|
||||
Items []T
|
||||
}
|
||||
|
||||
func (fl *FakeList[P]) DeepCopyObject() runtime.Object {
|
||||
copiedItems := make([]P, len(fl.Items))
|
||||
for i, item := range fl.Items {
|
||||
copiedItems[i] = item.DeepCopyObject().(P)
|
||||
}
|
||||
return &FakeList[P]{
|
||||
TypeMeta: fl.TypeMeta,
|
||||
ListMeta: fl.ListMeta,
|
||||
Items: copiedItems,
|
||||
}
|
||||
}
|
||||
|
||||
type fakeAuthorizer struct{}
|
||||
|
||||
func (f fakeAuthorizer) Authorize(ctx context.Context, a authorizer.Attributes) (authorizer.Decision, string, error) {
|
||||
return authorizer.DecisionAllow, "", nil
|
||||
}
|
283
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/internal/generic/controller.go
generated
vendored
283
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/internal/generic/controller.go
generated
vendored
@ -1,283 +0,0 @@
|
||||
/*
|
||||
Copyright 2022 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 generic
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
kerrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
"k8s.io/client-go/tools/cache/synctrack"
|
||||
"k8s.io/client-go/util/workqueue"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
var _ Controller[runtime.Object] = &controller[runtime.Object]{}
|
||||
|
||||
type controller[T runtime.Object] struct {
|
||||
informer Informer[T]
|
||||
queue workqueue.TypedRateLimitingInterface[string]
|
||||
|
||||
// Returns an error if there was a transient error during reconciliation
|
||||
// and the object should be tried again later.
|
||||
reconciler func(namespace, name string, newObj T) error
|
||||
|
||||
options ControllerOptions
|
||||
|
||||
// must hold a func() bool or nil
|
||||
notificationsDelivered atomic.Value
|
||||
|
||||
hasProcessed synctrack.AsyncTracker[string]
|
||||
}
|
||||
|
||||
type ControllerOptions struct {
|
||||
Name string
|
||||
Workers uint
|
||||
}
|
||||
|
||||
func (c *controller[T]) Informer() Informer[T] {
|
||||
return c.informer
|
||||
}
|
||||
|
||||
func NewController[T runtime.Object](
|
||||
informer Informer[T],
|
||||
reconciler func(namepace, name string, newObj T) error,
|
||||
options ControllerOptions,
|
||||
) Controller[T] {
|
||||
if options.Workers == 0 {
|
||||
options.Workers = 2
|
||||
}
|
||||
|
||||
if len(options.Name) == 0 {
|
||||
options.Name = fmt.Sprintf("%T-controller", *new(T))
|
||||
}
|
||||
|
||||
c := &controller[T]{
|
||||
options: options,
|
||||
informer: informer,
|
||||
reconciler: reconciler,
|
||||
queue: nil,
|
||||
}
|
||||
c.hasProcessed.UpstreamHasSynced = func() bool {
|
||||
f := c.notificationsDelivered.Load()
|
||||
if f == nil {
|
||||
return false
|
||||
}
|
||||
return f.(func() bool)()
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// Runs the controller and returns an error explaining why running was stopped.
|
||||
// Reconciliation ends as soon as the context completes. If there are events
|
||||
// waiting to be processed at that itme, they will be dropped.
|
||||
func (c *controller[T]) Run(ctx context.Context) error {
|
||||
klog.Infof("starting %s", c.options.Name)
|
||||
defer klog.Infof("stopping %s", c.options.Name)
|
||||
|
||||
c.queue = workqueue.NewTypedRateLimitingQueueWithConfig(
|
||||
workqueue.DefaultTypedControllerRateLimiter[string](),
|
||||
workqueue.TypedRateLimitingQueueConfig[string]{Name: c.options.Name},
|
||||
)
|
||||
|
||||
// Forcefully shutdown workqueue. Drop any enqueued items.
|
||||
// Important to do this in a `defer` at the start of `Run`.
|
||||
// Otherwise, if there are any early returns without calling this, we
|
||||
// would never shut down the workqueue
|
||||
defer c.queue.ShutDown()
|
||||
|
||||
enqueue := func(obj interface{}, isInInitialList bool) {
|
||||
var key string
|
||||
var err error
|
||||
if key, err = cache.DeletionHandlingMetaNamespaceKeyFunc(obj); err != nil {
|
||||
utilruntime.HandleError(err)
|
||||
return
|
||||
}
|
||||
if isInInitialList {
|
||||
c.hasProcessed.Start(key)
|
||||
}
|
||||
|
||||
c.queue.Add(key)
|
||||
}
|
||||
|
||||
registration, err := c.informer.AddEventHandler(cache.ResourceEventHandlerDetailedFuncs{
|
||||
AddFunc: enqueue,
|
||||
UpdateFunc: func(oldObj, newObj interface{}) {
|
||||
oldMeta, err1 := meta.Accessor(oldObj)
|
||||
newMeta, err2 := meta.Accessor(newObj)
|
||||
|
||||
if err1 != nil || err2 != nil {
|
||||
if err1 != nil {
|
||||
utilruntime.HandleError(err1)
|
||||
}
|
||||
|
||||
if err2 != nil {
|
||||
utilruntime.HandleError(err2)
|
||||
}
|
||||
return
|
||||
} else if oldMeta.GetResourceVersion() == newMeta.GetResourceVersion() {
|
||||
if len(oldMeta.GetResourceVersion()) == 0 {
|
||||
klog.Warningf("%v throwing out update with empty RV. this is likely to happen if a test did not supply a resource version on an updated object", c.options.Name)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
enqueue(newObj, false)
|
||||
},
|
||||
DeleteFunc: func(obj interface{}) {
|
||||
// Enqueue
|
||||
enqueue(obj, false)
|
||||
},
|
||||
})
|
||||
|
||||
// Error might be raised if informer was started and stopped already
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.notificationsDelivered.Store(registration.HasSynced)
|
||||
|
||||
// Make sure event handler is removed from informer in case return early from
|
||||
// an error
|
||||
defer func() {
|
||||
c.notificationsDelivered.Store(func() bool { return false })
|
||||
// Remove event handler and Handle Error here. Error should only be raised
|
||||
// for improper usage of event handler API.
|
||||
if err := c.informer.RemoveEventHandler(registration); err != nil {
|
||||
utilruntime.HandleError(err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Wait for initial cache list to complete before beginning to reconcile
|
||||
// objects.
|
||||
if !cache.WaitForNamedCacheSync(c.options.Name, ctx.Done(), c.informer.HasSynced) {
|
||||
// ctx cancelled during cache sync. return early
|
||||
err := ctx.Err()
|
||||
if err == nil {
|
||||
// if context wasnt cancelled then the sync failed for another reason
|
||||
err = errors.New("cache sync failed")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
waitGroup := sync.WaitGroup{}
|
||||
|
||||
for i := uint(0); i < c.options.Workers; i++ {
|
||||
waitGroup.Add(1)
|
||||
go func() {
|
||||
defer waitGroup.Done()
|
||||
wait.Until(c.runWorker, time.Second, ctx.Done())
|
||||
}()
|
||||
}
|
||||
|
||||
klog.Infof("Started %v workers for %v", c.options.Workers, c.options.Name)
|
||||
|
||||
// Wait for context cancel.
|
||||
<-ctx.Done()
|
||||
|
||||
// Forcefully shutdown workqueue. Drop any enqueued items.
|
||||
c.queue.ShutDown()
|
||||
|
||||
// Workqueue shutdown signals for workers to stop. Wait for all workers to
|
||||
// clean up
|
||||
waitGroup.Wait()
|
||||
|
||||
// Only way for workers to ever stop is for caller to cancel the context
|
||||
return ctx.Err()
|
||||
}
|
||||
|
||||
func (c *controller[T]) HasSynced() bool {
|
||||
return c.hasProcessed.HasSynced()
|
||||
}
|
||||
|
||||
func (c *controller[T]) runWorker() {
|
||||
for {
|
||||
key, shutdown := c.queue.Get()
|
||||
if shutdown {
|
||||
return
|
||||
}
|
||||
|
||||
// We wrap this block in a func so we can defer c.workqueue.Done.
|
||||
err := func(obj string) error {
|
||||
// We call Done here so the workqueue knows we have finished
|
||||
// processing this item. We also must remember to call Forget if we
|
||||
// do not want this work item being re-queued. For example, we do
|
||||
// not call Forget if a transient error occurs, instead the item is
|
||||
// put back on the workqueue and attempted again after a back-off
|
||||
// period.
|
||||
defer c.queue.Done(obj)
|
||||
defer c.hasProcessed.Finished(key)
|
||||
|
||||
if err := c.reconcile(key); err != nil {
|
||||
// Put the item back on the workqueue to handle any transient errors.
|
||||
c.queue.AddRateLimited(key)
|
||||
return fmt.Errorf("error syncing '%s': %s, requeuing", key, err.Error())
|
||||
}
|
||||
// Finally, if no error occurs we Forget this item so it is allowed
|
||||
// to be re-enqueued without a long rate limit
|
||||
c.queue.Forget(obj)
|
||||
klog.V(4).Infof("syncAdmissionPolicy(%q)", key)
|
||||
return nil
|
||||
}(key)
|
||||
|
||||
if err != nil {
|
||||
utilruntime.HandleError(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *controller[T]) reconcile(key string) error {
|
||||
var newObj T
|
||||
var err error
|
||||
var namespace string
|
||||
var name string
|
||||
var lister NamespacedLister[T]
|
||||
|
||||
// Convert the namespace/name string into a distinct namespace and name
|
||||
namespace, name, err = cache.SplitMetaNamespaceKey(key)
|
||||
if err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("invalid resource key: %s", key))
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(namespace) > 0 {
|
||||
lister = c.informer.Namespaced(namespace)
|
||||
} else {
|
||||
lister = c.informer
|
||||
}
|
||||
|
||||
newObj, err = lister.Get(name)
|
||||
if err != nil {
|
||||
if !kerrors.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
// Deleted object. Inform reconciler with empty
|
||||
}
|
||||
|
||||
return c.reconciler(namespace, name, newObj)
|
||||
}
|
29
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/internal/generic/doc.go
generated
vendored
29
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/internal/generic/doc.go
generated
vendored
@ -1,29 +0,0 @@
|
||||
/*
|
||||
Copyright 2022 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 generic contains a typed wrapper over cache SharedIndexInformer
|
||||
// and Lister (maybe eventually should have a home there?)
|
||||
//
|
||||
// This interface is being experimented with as an easier way to write controllers
|
||||
// with a bit less boilerplate.
|
||||
//
|
||||
// Informer/Lister classes are thin wrappers providing a type-safe interface
|
||||
// over regular interface{}-based Informers/Listers
|
||||
//
|
||||
// Controller[T] provides a reusable way to reconcile objects out of an informer
|
||||
// using the tried and true controller design pattern found all over k8s
|
||||
// codebase based upon syncFunc/reconcile
|
||||
package generic
|
40
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/internal/generic/informer.go
generated
vendored
40
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/internal/generic/informer.go
generated
vendored
@ -1,40 +0,0 @@
|
||||
/*
|
||||
Copyright 2022 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 generic
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
)
|
||||
|
||||
var _ Informer[runtime.Object] = informer[runtime.Object]{}
|
||||
|
||||
type informer[T runtime.Object] struct {
|
||||
cache.SharedIndexInformer
|
||||
lister[T]
|
||||
}
|
||||
|
||||
// Creates a generic informer around a type-erased cache.SharedIndexInformer
|
||||
// It is incumbent on the caller to ensure that the generic type argument is
|
||||
// consistent with the type of the objects stored inside the SharedIndexInformer
|
||||
// as they will be casted.
|
||||
func NewInformer[T runtime.Object](informe cache.SharedIndexInformer) Informer[T] {
|
||||
return informer[T]{
|
||||
SharedIndexInformer: informe,
|
||||
lister: NewLister[T](informe.GetIndexer()),
|
||||
}
|
||||
}
|
62
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/internal/generic/interface.go
generated
vendored
62
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/internal/generic/interface.go
generated
vendored
@ -1,62 +0,0 @@
|
||||
/*
|
||||
Copyright 2022 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 generic
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
)
|
||||
|
||||
type Controller[T runtime.Object] interface {
|
||||
// Meant to be run inside a goroutine
|
||||
// Waits for and reacts to changes in whatever type the controller
|
||||
// is concerned with.
|
||||
//
|
||||
// Returns an error always non-nil explaining why the worker stopped
|
||||
Run(ctx context.Context) error
|
||||
|
||||
// Retrieves the informer used to back this controller
|
||||
Informer() Informer[T]
|
||||
|
||||
// Returns true if the informer cache has synced, and all the objects from
|
||||
// the initial list have been reconciled at least once.
|
||||
HasSynced() bool
|
||||
}
|
||||
|
||||
type NamespacedLister[T any] interface {
|
||||
// List lists all ValidationRuleSets in the indexer for a given namespace.
|
||||
// Objects returned here must be treated as read-only.
|
||||
List(selector labels.Selector) (ret []T, err error)
|
||||
// Get retrieves the ValidationRuleSet from the indexer for a given namespace and name.
|
||||
// Objects returned here must be treated as read-only.
|
||||
Get(name string) (T, error)
|
||||
}
|
||||
|
||||
type Informer[T any] interface {
|
||||
cache.SharedIndexInformer
|
||||
Lister[T]
|
||||
}
|
||||
|
||||
// Lister[T] helps list Ts.
|
||||
// All objects returned here must be treated as read-only.
|
||||
type Lister[T any] interface {
|
||||
NamespacedLister[T]
|
||||
Namespaced(namespace string) NamespacedLister[T]
|
||||
}
|
100
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/internal/generic/lister.go
generated
vendored
100
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/internal/generic/lister.go
generated
vendored
@ -1,100 +0,0 @@
|
||||
/*
|
||||
Copyright 2022 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 generic
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
kerrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
)
|
||||
|
||||
var _ Lister[runtime.Object] = lister[runtime.Object]{}
|
||||
|
||||
type namespacedLister[T runtime.Object] struct {
|
||||
indexer cache.Indexer
|
||||
namespace string
|
||||
}
|
||||
|
||||
func (w namespacedLister[T]) List(selector labels.Selector) (ret []T, err error) {
|
||||
err = cache.ListAllByNamespace(w.indexer, w.namespace, selector, func(m interface{}) {
|
||||
ret = append(ret, m.(T))
|
||||
})
|
||||
return ret, err
|
||||
}
|
||||
|
||||
func (w namespacedLister[T]) Get(name string) (T, error) {
|
||||
var result T
|
||||
|
||||
obj, exists, err := w.indexer.GetByKey(w.namespace + "/" + name)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
if !exists {
|
||||
return result, &kerrors.StatusError{ErrStatus: metav1.Status{
|
||||
Status: metav1.StatusFailure,
|
||||
Code: http.StatusNotFound,
|
||||
Reason: metav1.StatusReasonNotFound,
|
||||
Message: fmt.Sprintf("%s not found", name),
|
||||
}}
|
||||
}
|
||||
result = obj.(T)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
type lister[T runtime.Object] struct {
|
||||
indexer cache.Indexer
|
||||
}
|
||||
|
||||
func (w lister[T]) List(selector labels.Selector) (ret []T, err error) {
|
||||
err = cache.ListAll(w.indexer, selector, func(m interface{}) {
|
||||
ret = append(ret, m.(T))
|
||||
})
|
||||
return ret, err
|
||||
}
|
||||
|
||||
func (w lister[T]) Get(name string) (T, error) {
|
||||
var result T
|
||||
|
||||
obj, exists, err := w.indexer.GetByKey(name)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
if !exists {
|
||||
// kerrors.StatusNotFound requires a GroupResource we cannot provide
|
||||
return result, &kerrors.StatusError{ErrStatus: metav1.Status{
|
||||
Status: metav1.StatusFailure,
|
||||
Code: http.StatusNotFound,
|
||||
Reason: metav1.StatusReasonNotFound,
|
||||
Message: fmt.Sprintf("%s not found", name),
|
||||
}}
|
||||
}
|
||||
result = obj.(T)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (w lister[T]) Namespaced(namespace string) NamespacedLister[T] {
|
||||
return namespacedLister[T]{namespace: namespace, indexer: w.indexer}
|
||||
}
|
||||
|
||||
func NewLister[T runtime.Object](indexer cache.Indexer) lister[T] {
|
||||
return lister[T]{indexer: indexer}
|
||||
}
|
200
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/matching/matching.go
generated
vendored
200
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/matching/matching.go
generated
vendored
@ -1,200 +0,0 @@
|
||||
/*
|
||||
Copyright 2022 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 matching
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
v1 "k8s.io/api/admissionregistration/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
listersv1 "k8s.io/client-go/listers/core/v1"
|
||||
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/predicates/namespace"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/predicates/object"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/predicates/rules"
|
||||
)
|
||||
|
||||
type MatchCriteria interface {
|
||||
namespace.NamespaceSelectorProvider
|
||||
object.ObjectSelectorProvider
|
||||
|
||||
GetMatchResources() v1.MatchResources
|
||||
}
|
||||
|
||||
// Matcher decides if a request matches against matchCriteria
|
||||
type Matcher struct {
|
||||
namespaceMatcher *namespace.Matcher
|
||||
objectMatcher *object.Matcher
|
||||
}
|
||||
|
||||
func (m *Matcher) GetNamespace(name string) (*corev1.Namespace, error) {
|
||||
return m.namespaceMatcher.GetNamespace(name)
|
||||
}
|
||||
|
||||
// NewMatcher initialize the matcher with dependencies requires
|
||||
func NewMatcher(
|
||||
namespaceLister listersv1.NamespaceLister,
|
||||
client kubernetes.Interface,
|
||||
) *Matcher {
|
||||
return &Matcher{
|
||||
namespaceMatcher: &namespace.Matcher{
|
||||
NamespaceLister: namespaceLister,
|
||||
Client: client,
|
||||
},
|
||||
objectMatcher: &object.Matcher{},
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateInitialization verify if the matcher is ready before use
|
||||
func (m *Matcher) ValidateInitialization() error {
|
||||
if err := m.namespaceMatcher.Validate(); err != nil {
|
||||
return fmt.Errorf("namespaceMatcher is not properly setup: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Matcher) Matches(attr admission.Attributes, o admission.ObjectInterfaces, criteria MatchCriteria) (bool, schema.GroupVersionResource, schema.GroupVersionKind, error) {
|
||||
matches, matchNsErr := m.namespaceMatcher.MatchNamespaceSelector(criteria, attr)
|
||||
// Should not return an error here for policy which do not apply to the request, even if err is an unexpected scenario.
|
||||
if !matches && matchNsErr == nil {
|
||||
return false, schema.GroupVersionResource{}, schema.GroupVersionKind{}, nil
|
||||
}
|
||||
|
||||
matches, matchObjErr := m.objectMatcher.MatchObjectSelector(criteria, attr)
|
||||
// Should not return an error here for policy which do not apply to the request, even if err is an unexpected scenario.
|
||||
if !matches && matchObjErr == nil {
|
||||
return false, schema.GroupVersionResource{}, schema.GroupVersionKind{}, nil
|
||||
}
|
||||
|
||||
matchResources := criteria.GetMatchResources()
|
||||
matchPolicy := matchResources.MatchPolicy
|
||||
if isExcluded, _, _, err := matchesResourceRules(matchResources.ExcludeResourceRules, matchPolicy, attr, o); isExcluded || err != nil {
|
||||
return false, schema.GroupVersionResource{}, schema.GroupVersionKind{}, err
|
||||
}
|
||||
|
||||
var (
|
||||
isMatch bool
|
||||
matchResource schema.GroupVersionResource
|
||||
matchKind schema.GroupVersionKind
|
||||
matchErr error
|
||||
)
|
||||
if len(matchResources.ResourceRules) == 0 {
|
||||
isMatch = true
|
||||
matchKind = attr.GetKind()
|
||||
matchResource = attr.GetResource()
|
||||
} else {
|
||||
isMatch, matchResource, matchKind, matchErr = matchesResourceRules(matchResources.ResourceRules, matchPolicy, attr, o)
|
||||
}
|
||||
if matchErr != nil {
|
||||
return false, schema.GroupVersionResource{}, schema.GroupVersionKind{}, matchErr
|
||||
}
|
||||
if !isMatch {
|
||||
return false, schema.GroupVersionResource{}, schema.GroupVersionKind{}, nil
|
||||
}
|
||||
|
||||
// now that we know this applies to this request otherwise, if there were selector errors, return them
|
||||
if matchNsErr != nil {
|
||||
return false, schema.GroupVersionResource{}, schema.GroupVersionKind{}, matchNsErr
|
||||
}
|
||||
if matchObjErr != nil {
|
||||
return false, schema.GroupVersionResource{}, schema.GroupVersionKind{}, matchObjErr
|
||||
}
|
||||
|
||||
return true, matchResource, matchKind, nil
|
||||
}
|
||||
|
||||
func matchesResourceRules(namedRules []v1.NamedRuleWithOperations, matchPolicy *v1.MatchPolicyType, attr admission.Attributes, o admission.ObjectInterfaces) (bool, schema.GroupVersionResource, schema.GroupVersionKind, error) {
|
||||
matchKind := attr.GetKind()
|
||||
matchResource := attr.GetResource()
|
||||
|
||||
for _, namedRule := range namedRules {
|
||||
rule := v1.RuleWithOperations(namedRule.RuleWithOperations)
|
||||
ruleMatcher := rules.Matcher{
|
||||
Rule: rule,
|
||||
Attr: attr,
|
||||
}
|
||||
if !ruleMatcher.Matches() {
|
||||
continue
|
||||
}
|
||||
// an empty name list always matches
|
||||
if len(namedRule.ResourceNames) == 0 {
|
||||
return true, matchResource, matchKind, nil
|
||||
}
|
||||
// TODO: GetName() can return an empty string if the user is relying on
|
||||
// the API server to generate the name... figure out what to do for this edge case
|
||||
name := attr.GetName()
|
||||
for _, matchedName := range namedRule.ResourceNames {
|
||||
if name == matchedName {
|
||||
return true, matchResource, matchKind, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if match policy is undefined or exact, don't perform fuzzy matching
|
||||
// note that defaulting to fuzzy matching is set by the API
|
||||
if matchPolicy == nil || *matchPolicy == v1.Exact {
|
||||
return false, schema.GroupVersionResource{}, schema.GroupVersionKind{}, nil
|
||||
}
|
||||
|
||||
attrWithOverride := &attrWithResourceOverride{Attributes: attr}
|
||||
equivalents := o.GetEquivalentResourceMapper().EquivalentResourcesFor(attr.GetResource(), attr.GetSubresource())
|
||||
for _, namedRule := range namedRules {
|
||||
for _, equivalent := range equivalents {
|
||||
if equivalent == attr.GetResource() {
|
||||
// we have already checked the original resource
|
||||
continue
|
||||
}
|
||||
attrWithOverride.resource = equivalent
|
||||
rule := v1.RuleWithOperations(namedRule.RuleWithOperations)
|
||||
m := rules.Matcher{
|
||||
Rule: rule,
|
||||
Attr: attrWithOverride,
|
||||
}
|
||||
if !m.Matches() {
|
||||
continue
|
||||
}
|
||||
matchKind = o.GetEquivalentResourceMapper().KindFor(equivalent, attr.GetSubresource())
|
||||
if matchKind.Empty() {
|
||||
return false, schema.GroupVersionResource{}, schema.GroupVersionKind{}, fmt.Errorf("unable to convert to %v: unknown kind", equivalent)
|
||||
}
|
||||
// an empty name list always matches
|
||||
if len(namedRule.ResourceNames) == 0 {
|
||||
return true, equivalent, matchKind, nil
|
||||
}
|
||||
|
||||
// TODO: GetName() can return an empty string if the user is relying on
|
||||
// the API server to generate the name... figure out what to do for this edge case
|
||||
name := attr.GetName()
|
||||
for _, matchedName := range namedRule.ResourceNames {
|
||||
if name == matchedName {
|
||||
return true, equivalent, matchKind, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false, schema.GroupVersionResource{}, schema.GroupVersionKind{}, nil
|
||||
}
|
||||
|
||||
type attrWithResourceOverride struct {
|
||||
admission.Attributes
|
||||
resource schema.GroupVersionResource
|
||||
}
|
||||
|
||||
func (a *attrWithResourceOverride) GetResource() schema.GroupVersionResource { return a.resource }
|
144
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/accessor.go
generated
vendored
144
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/accessor.go
generated
vendored
@ -1,144 +0,0 @@
|
||||
/*
|
||||
Copyright 2024 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 mutating
|
||||
|
||||
import (
|
||||
v1 "k8s.io/api/admissionregistration/v1"
|
||||
"k8s.io/api/admissionregistration/v1alpha1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/policy/generic"
|
||||
)
|
||||
|
||||
func NewMutatingAdmissionPolicyAccessor(obj *Policy) generic.PolicyAccessor {
|
||||
return &mutatingAdmissionPolicyAccessor{
|
||||
Policy: obj,
|
||||
}
|
||||
}
|
||||
|
||||
func NewMutatingAdmissionPolicyBindingAccessor(obj *PolicyBinding) generic.BindingAccessor {
|
||||
return &mutatingAdmissionPolicyBindingAccessor{
|
||||
PolicyBinding: obj,
|
||||
}
|
||||
}
|
||||
|
||||
type mutatingAdmissionPolicyAccessor struct {
|
||||
*Policy
|
||||
}
|
||||
|
||||
func (v *mutatingAdmissionPolicyAccessor) GetNamespace() string {
|
||||
return v.Namespace
|
||||
}
|
||||
|
||||
func (v *mutatingAdmissionPolicyAccessor) GetName() string {
|
||||
return v.Name
|
||||
}
|
||||
|
||||
func (v *mutatingAdmissionPolicyAccessor) GetParamKind() *v1.ParamKind {
|
||||
pk := v.Spec.ParamKind
|
||||
if pk == nil {
|
||||
return nil
|
||||
}
|
||||
return &v1.ParamKind{
|
||||
APIVersion: pk.APIVersion,
|
||||
Kind: pk.Kind,
|
||||
}
|
||||
}
|
||||
|
||||
func (v *mutatingAdmissionPolicyAccessor) GetMatchConstraints() *v1.MatchResources {
|
||||
return convertV1alpha1ResourceRulesToV1(v.Spec.MatchConstraints)
|
||||
}
|
||||
|
||||
func (v *mutatingAdmissionPolicyAccessor) GetFailurePolicy() *v1.FailurePolicyType {
|
||||
return toV1FailurePolicy(v.Spec.FailurePolicy)
|
||||
}
|
||||
|
||||
func toV1FailurePolicy(failurePolicy *v1alpha1.FailurePolicyType) *v1.FailurePolicyType {
|
||||
if failurePolicy == nil {
|
||||
return nil
|
||||
}
|
||||
fp := v1.FailurePolicyType(*failurePolicy)
|
||||
return &fp
|
||||
}
|
||||
|
||||
type mutatingAdmissionPolicyBindingAccessor struct {
|
||||
*PolicyBinding
|
||||
}
|
||||
|
||||
func (v *mutatingAdmissionPolicyBindingAccessor) GetNamespace() string {
|
||||
return v.Namespace
|
||||
}
|
||||
|
||||
func (v *mutatingAdmissionPolicyBindingAccessor) GetName() string {
|
||||
return v.Name
|
||||
}
|
||||
|
||||
func (v *mutatingAdmissionPolicyBindingAccessor) GetPolicyName() types.NamespacedName {
|
||||
return types.NamespacedName{
|
||||
Namespace: "",
|
||||
Name: v.Spec.PolicyName,
|
||||
}
|
||||
}
|
||||
|
||||
func (v *mutatingAdmissionPolicyBindingAccessor) GetMatchResources() *v1.MatchResources {
|
||||
return convertV1alpha1ResourceRulesToV1(v.Spec.MatchResources)
|
||||
}
|
||||
|
||||
func (v *mutatingAdmissionPolicyBindingAccessor) GetParamRef() *v1.ParamRef {
|
||||
if v.Spec.ParamRef == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var nfa *v1.ParameterNotFoundActionType
|
||||
if v.Spec.ParamRef.ParameterNotFoundAction != nil {
|
||||
nfa = new(v1.ParameterNotFoundActionType)
|
||||
*nfa = v1.ParameterNotFoundActionType(*v.Spec.ParamRef.ParameterNotFoundAction)
|
||||
}
|
||||
|
||||
return &v1.ParamRef{
|
||||
Name: v.Spec.ParamRef.Name,
|
||||
Namespace: v.Spec.ParamRef.Namespace,
|
||||
Selector: v.Spec.ParamRef.Selector,
|
||||
ParameterNotFoundAction: nfa,
|
||||
}
|
||||
}
|
||||
|
||||
func convertV1alpha1ResourceRulesToV1(mc *v1alpha1.MatchResources) *v1.MatchResources {
|
||||
if mc == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var res v1.MatchResources
|
||||
res.NamespaceSelector = mc.NamespaceSelector
|
||||
res.ObjectSelector = mc.ObjectSelector
|
||||
for _, ex := range mc.ExcludeResourceRules {
|
||||
res.ExcludeResourceRules = append(res.ExcludeResourceRules, v1.NamedRuleWithOperations{
|
||||
ResourceNames: ex.ResourceNames,
|
||||
RuleWithOperations: ex.RuleWithOperations,
|
||||
})
|
||||
}
|
||||
for _, ex := range mc.ResourceRules {
|
||||
res.ResourceRules = append(res.ResourceRules, v1.NamedRuleWithOperations{
|
||||
ResourceNames: ex.ResourceNames,
|
||||
RuleWithOperations: ex.RuleWithOperations,
|
||||
})
|
||||
}
|
||||
if mc.MatchPolicy != nil {
|
||||
mp := v1.MatchPolicyType(*mc.MatchPolicy)
|
||||
res.MatchPolicy = &mp
|
||||
}
|
||||
return &res
|
||||
}
|
81
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/compilation.go
generated
vendored
81
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/compilation.go
generated
vendored
@ -1,81 +0,0 @@
|
||||
/*
|
||||
Copyright 2024 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 mutating
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"k8s.io/api/admissionregistration/v1alpha1"
|
||||
plugincel "k8s.io/apiserver/pkg/admission/plugin/cel"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/policy/mutating/patch"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/matchconditions"
|
||||
apiservercel "k8s.io/apiserver/pkg/cel"
|
||||
"k8s.io/apiserver/pkg/cel/environment"
|
||||
)
|
||||
|
||||
// compilePolicy compiles the policy into a PolicyEvaluator
|
||||
// any error is stored and delayed until invocation.
|
||||
//
|
||||
// Each individual mutation is compiled into MutationEvaluationFunc and
|
||||
// returned is a PolicyEvaluator in the same order as the mutations appeared in the policy.
|
||||
func compilePolicy(policy *Policy) PolicyEvaluator {
|
||||
opts := plugincel.OptionalVariableDeclarations{HasParams: policy.Spec.ParamKind != nil, StrictCost: true, HasAuthorizer: true}
|
||||
compiler, err := plugincel.NewCompositedCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), true))
|
||||
if err != nil {
|
||||
return PolicyEvaluator{Error: &apiservercel.Error{
|
||||
Type: apiservercel.ErrorTypeInternal,
|
||||
Detail: fmt.Sprintf("failed to initialize CEL compiler: %v", err),
|
||||
}}
|
||||
}
|
||||
|
||||
// Compile and store variables
|
||||
compiler.CompileAndStoreVariables(convertv1alpha1Variables(policy.Spec.Variables), opts, environment.StoredExpressions)
|
||||
|
||||
// Compile matchers
|
||||
var matcher matchconditions.Matcher = nil
|
||||
matchConditions := policy.Spec.MatchConditions
|
||||
if len(matchConditions) > 0 {
|
||||
matchExpressionAccessors := make([]plugincel.ExpressionAccessor, len(matchConditions))
|
||||
for i := range matchConditions {
|
||||
matchExpressionAccessors[i] = (*matchconditions.MatchCondition)(&matchConditions[i])
|
||||
}
|
||||
matcher = matchconditions.NewMatcher(compiler.CompileCondition(matchExpressionAccessors, opts, environment.StoredExpressions), toV1FailurePolicy(policy.Spec.FailurePolicy), "policy", "validate", policy.Name)
|
||||
}
|
||||
|
||||
// Compiler patchers
|
||||
var patchers []patch.Patcher
|
||||
patchOptions := opts
|
||||
patchOptions.HasPatchTypes = true
|
||||
for _, m := range policy.Spec.Mutations {
|
||||
switch m.PatchType {
|
||||
case v1alpha1.PatchTypeJSONPatch:
|
||||
if m.JSONPatch != nil {
|
||||
accessor := &patch.JSONPatchCondition{Expression: m.JSONPatch.Expression}
|
||||
compileResult := compiler.CompileMutatingEvaluator(accessor, patchOptions, environment.StoredExpressions)
|
||||
patchers = append(patchers, patch.NewJSONPatcher(compileResult))
|
||||
}
|
||||
case v1alpha1.PatchTypeApplyConfiguration:
|
||||
if m.ApplyConfiguration != nil {
|
||||
accessor := &patch.ApplyConfigurationCondition{Expression: m.ApplyConfiguration.Expression}
|
||||
compileResult := compiler.CompileMutatingEvaluator(accessor, patchOptions, environment.StoredExpressions)
|
||||
patchers = append(patchers, patch.NewApplyConfigurationPatcher(compileResult))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return PolicyEvaluator{Matcher: matcher, Mutators: patchers, CompositionEnv: compiler.CompositionEnv}
|
||||
}
|
295
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/dispatcher.go
generated
vendored
295
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/dispatcher.go
generated
vendored
@ -1,295 +0,0 @@
|
||||
/*
|
||||
Copyright 2024 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 mutating
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"k8s.io/api/admissionregistration/v1alpha1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||
k8serrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
admissionauthorizer "k8s.io/apiserver/pkg/admission/plugin/authorizer"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/cel"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/policy/generic"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/policy/matching"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/policy/mutating/patch"
|
||||
webhookgeneric "k8s.io/apiserver/pkg/admission/plugin/webhook/generic"
|
||||
celconfig "k8s.io/apiserver/pkg/apis/cel"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
)
|
||||
|
||||
func NewDispatcher(a authorizer.Authorizer, m *matching.Matcher, tcm patch.TypeConverterManager) generic.Dispatcher[PolicyHook] {
|
||||
res := &dispatcher{
|
||||
matcher: m,
|
||||
authz: a,
|
||||
typeConverterManager: tcm,
|
||||
}
|
||||
res.Dispatcher = generic.NewPolicyDispatcher[*Policy, *PolicyBinding, PolicyEvaluator](
|
||||
NewMutatingAdmissionPolicyAccessor,
|
||||
NewMutatingAdmissionPolicyBindingAccessor,
|
||||
m,
|
||||
res.dispatchInvocations,
|
||||
)
|
||||
return res
|
||||
}
|
||||
|
||||
type dispatcher struct {
|
||||
matcher *matching.Matcher
|
||||
authz authorizer.Authorizer
|
||||
typeConverterManager patch.TypeConverterManager
|
||||
generic.Dispatcher[PolicyHook]
|
||||
}
|
||||
|
||||
func (d *dispatcher) Start(ctx context.Context) error {
|
||||
go d.typeConverterManager.Run(ctx)
|
||||
return d.Dispatcher.Start(ctx)
|
||||
}
|
||||
|
||||
func (d *dispatcher) dispatchInvocations(
|
||||
ctx context.Context,
|
||||
a admission.Attributes,
|
||||
o admission.ObjectInterfaces,
|
||||
versionedAttributes webhookgeneric.VersionedAttributeAccessor,
|
||||
invocations []generic.PolicyInvocation[*Policy, *PolicyBinding, PolicyEvaluator],
|
||||
) ([]generic.PolicyError, *k8serrors.StatusError) {
|
||||
var lastVersionedAttr *admission.VersionedAttributes
|
||||
|
||||
reinvokeCtx := a.GetReinvocationContext()
|
||||
var policyReinvokeCtx *policyReinvokeContext
|
||||
if v := reinvokeCtx.Value(PluginName); v != nil {
|
||||
policyReinvokeCtx = v.(*policyReinvokeContext)
|
||||
} else {
|
||||
policyReinvokeCtx = &policyReinvokeContext{}
|
||||
reinvokeCtx.SetValue(PluginName, policyReinvokeCtx)
|
||||
}
|
||||
|
||||
if reinvokeCtx.IsReinvoke() && policyReinvokeCtx.IsOutputChangedSinceLastPolicyInvocation(a.GetObject()) {
|
||||
// If the object has changed, we know the in-tree plugin re-invocations have mutated the object,
|
||||
// and we need to reinvoke all eligible policies.
|
||||
policyReinvokeCtx.RequireReinvokingPreviouslyInvokedPlugins()
|
||||
}
|
||||
defer func() {
|
||||
policyReinvokeCtx.SetLastPolicyInvocationOutput(a.GetObject())
|
||||
}()
|
||||
|
||||
var policyErrors []generic.PolicyError
|
||||
addConfigError := func(err error, invocation generic.PolicyInvocation[*Policy, *PolicyBinding, PolicyEvaluator], reason metav1.StatusReason) {
|
||||
policyErrors = append(policyErrors, generic.PolicyError{
|
||||
Message: err,
|
||||
Policy: NewMutatingAdmissionPolicyAccessor(invocation.Policy),
|
||||
Binding: NewMutatingAdmissionPolicyBindingAccessor(invocation.Binding),
|
||||
Reason: reason,
|
||||
})
|
||||
}
|
||||
|
||||
// There is at least one invocation to invoke. Make sure we have a namespace
|
||||
// object if the incoming object is not cluster scoped to pass into the evaluator.
|
||||
var namespace *v1.Namespace
|
||||
var err error
|
||||
namespaceName := a.GetNamespace()
|
||||
|
||||
// Special case, the namespace object has the namespace of itself (maybe a bug).
|
||||
// unset it if the incoming object is a namespace
|
||||
if gvk := a.GetKind(); gvk.Kind == "Namespace" && gvk.Version == "v1" && gvk.Group == "" {
|
||||
namespaceName = ""
|
||||
}
|
||||
|
||||
// if it is cluster scoped, namespaceName will be empty
|
||||
// Otherwise, get the Namespace resource.
|
||||
if namespaceName != "" {
|
||||
namespace, err = d.matcher.GetNamespace(namespaceName)
|
||||
if err != nil {
|
||||
return nil, k8serrors.NewNotFound(schema.GroupResource{Group: "", Resource: "namespaces"}, namespaceName)
|
||||
}
|
||||
}
|
||||
|
||||
authz := admissionauthorizer.NewCachingAuthorizer(d.authz)
|
||||
|
||||
// Should loop through invocations, handling possible error and invoking
|
||||
// evaluator to apply patch, also should handle re-invocations
|
||||
for _, invocation := range invocations {
|
||||
if invocation.Evaluator.CompositionEnv != nil {
|
||||
ctx = invocation.Evaluator.CompositionEnv.CreateContext(ctx)
|
||||
}
|
||||
if len(invocation.Evaluator.Mutators) != len(invocation.Policy.Spec.Mutations) {
|
||||
// This would be a bug. The compiler should always return exactly as
|
||||
// many evaluators as there are mutations
|
||||
return nil, k8serrors.NewInternalError(fmt.Errorf("expected %v compiled evaluators for policy %v, got %v",
|
||||
len(invocation.Policy.Spec.Mutations), invocation.Policy.Name, len(invocation.Evaluator.Mutators)))
|
||||
}
|
||||
|
||||
versionedAttr, err := versionedAttributes.VersionedAttribute(invocation.Kind)
|
||||
if err != nil {
|
||||
// This should never happen, we pre-warm versoined attribute
|
||||
// accessors before starting the dispatcher
|
||||
return nil, k8serrors.NewInternalError(err)
|
||||
}
|
||||
|
||||
if invocation.Evaluator.Matcher != nil {
|
||||
matchResults := invocation.Evaluator.Matcher.Match(ctx, versionedAttr, invocation.Param, authz)
|
||||
if matchResults.Error != nil {
|
||||
addConfigError(matchResults.Error, invocation, metav1.StatusReasonInvalid)
|
||||
continue
|
||||
}
|
||||
|
||||
// if preconditions are not met, then skip mutations
|
||||
if !matchResults.Matches {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
invocationKey, invocationKeyErr := keyFor(invocation)
|
||||
if invocationKeyErr != nil {
|
||||
// This should never happen. It occurs if there is a programming
|
||||
// error causing the Param not to be a valid object.
|
||||
return nil, k8serrors.NewInternalError(invocationKeyErr)
|
||||
}
|
||||
if reinvokeCtx.IsReinvoke() && !policyReinvokeCtx.ShouldReinvoke(invocationKey) {
|
||||
continue
|
||||
}
|
||||
|
||||
objectBeforeMutations := versionedAttr.VersionedObject
|
||||
// Mutations for a single invocation of a MutatingAdmissionPolicy are evaluated
|
||||
// in order.
|
||||
for mutationIndex := range invocation.Policy.Spec.Mutations {
|
||||
lastVersionedAttr = versionedAttr
|
||||
if versionedAttr.VersionedObject == nil { // Do not call patchers if there is no object to patch.
|
||||
continue
|
||||
}
|
||||
|
||||
patcher := invocation.Evaluator.Mutators[mutationIndex]
|
||||
optionalVariables := cel.OptionalVariableBindings{VersionedParams: invocation.Param, Authorizer: authz}
|
||||
err = d.dispatchOne(ctx, patcher, o, versionedAttr, namespace, invocation.Resource, optionalVariables)
|
||||
if err != nil {
|
||||
var statusError *k8serrors.StatusError
|
||||
if errors.As(err, &statusError) {
|
||||
return nil, statusError
|
||||
}
|
||||
|
||||
addConfigError(err, invocation, metav1.StatusReasonInvalid)
|
||||
continue
|
||||
}
|
||||
}
|
||||
if !apiequality.Semantic.DeepEqual(objectBeforeMutations, versionedAttr.VersionedObject) {
|
||||
// The mutation has changed the object. Prepare to reinvoke all previous mutations that are eligible for re-invocation.
|
||||
policyReinvokeCtx.RequireReinvokingPreviouslyInvokedPlugins()
|
||||
reinvokeCtx.SetShouldReinvoke()
|
||||
}
|
||||
if invocation.Policy.Spec.ReinvocationPolicy == v1alpha1.IfNeededReinvocationPolicy {
|
||||
policyReinvokeCtx.AddReinvocablePolicyToPreviouslyInvoked(invocationKey)
|
||||
}
|
||||
}
|
||||
|
||||
if lastVersionedAttr != nil && lastVersionedAttr.VersionedObject != nil && lastVersionedAttr.Dirty {
|
||||
policyReinvokeCtx.RequireReinvokingPreviouslyInvokedPlugins()
|
||||
reinvokeCtx.SetShouldReinvoke()
|
||||
if err := o.GetObjectConvertor().Convert(lastVersionedAttr.VersionedObject, lastVersionedAttr.Attributes.GetObject(), nil); err != nil {
|
||||
return nil, k8serrors.NewInternalError(fmt.Errorf("failed to convert object: %w", err))
|
||||
}
|
||||
}
|
||||
|
||||
return policyErrors, nil
|
||||
}
|
||||
|
||||
func (d *dispatcher) dispatchOne(
|
||||
ctx context.Context,
|
||||
patcher patch.Patcher,
|
||||
o admission.ObjectInterfaces,
|
||||
versionedAttributes *admission.VersionedAttributes,
|
||||
namespace *v1.Namespace,
|
||||
resource schema.GroupVersionResource,
|
||||
optionalVariables cel.OptionalVariableBindings,
|
||||
) (err error) {
|
||||
if patcher == nil {
|
||||
// internal error. this should not happen
|
||||
return k8serrors.NewInternalError(fmt.Errorf("policy evaluator is nil"))
|
||||
}
|
||||
|
||||
// Find type converter for the invoked Group-Version.
|
||||
typeConverter := d.typeConverterManager.GetTypeConverter(versionedAttributes.VersionedKind)
|
||||
if typeConverter == nil {
|
||||
// This can happen if the request is for a resource whose schema
|
||||
// has not been registered with the type converter manager.
|
||||
return k8serrors.NewServiceUnavailable(fmt.Sprintf("Resource kind %s not found. There can be a delay between when CustomResourceDefinitions are created and when they are available.", versionedAttributes.VersionedKind))
|
||||
}
|
||||
|
||||
patchRequest := patch.Request{
|
||||
MatchedResource: resource,
|
||||
VersionedAttributes: versionedAttributes,
|
||||
ObjectInterfaces: o,
|
||||
OptionalVariables: optionalVariables,
|
||||
Namespace: namespace,
|
||||
TypeConverter: typeConverter,
|
||||
}
|
||||
newVersionedObject, err := patcher.Patch(ctx, patchRequest, celconfig.RuntimeCELCostBudget)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch versionedAttributes.VersionedObject.(type) {
|
||||
case *unstructured.Unstructured:
|
||||
// No conversion needed before defaulting for the patch object if the admitted object is unstructured.
|
||||
default:
|
||||
// Before defaulting, if the admitted object is a typed object, convert unstructured patch result back to a typed object.
|
||||
newVersionedObject, err = o.GetObjectConvertor().ConvertToVersion(newVersionedObject, versionedAttributes.GetKind().GroupVersion())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
o.GetObjectDefaulter().Default(newVersionedObject)
|
||||
|
||||
versionedAttributes.Dirty = true
|
||||
versionedAttributes.VersionedObject = newVersionedObject
|
||||
return nil
|
||||
}
|
||||
|
||||
func keyFor(invocation generic.PolicyInvocation[*Policy, *PolicyBinding, PolicyEvaluator]) (key, error) {
|
||||
var paramUID types.NamespacedName
|
||||
if invocation.Param != nil {
|
||||
paramAccessor, err := meta.Accessor(invocation.Param)
|
||||
if err != nil {
|
||||
// This should never happen, as the param should have been validated
|
||||
// before being passed to the plugin.
|
||||
return key{}, err
|
||||
}
|
||||
paramUID = types.NamespacedName{
|
||||
Name: paramAccessor.GetName(),
|
||||
Namespace: paramAccessor.GetNamespace(),
|
||||
}
|
||||
}
|
||||
|
||||
return key{
|
||||
PolicyUID: types.NamespacedName{
|
||||
Name: invocation.Policy.GetName(),
|
||||
Namespace: invocation.Policy.GetNamespace(),
|
||||
},
|
||||
BindingUID: types.NamespacedName{
|
||||
Name: invocation.Binding.GetName(),
|
||||
Namespace: invocation.Binding.GetNamespace(),
|
||||
},
|
||||
ParamUID: paramUID,
|
||||
}, nil
|
||||
}
|
45
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/patch/interface.go
generated
vendored
45
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/patch/interface.go
generated
vendored
@ -1,45 +0,0 @@
|
||||
/*
|
||||
Copyright 2024 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 patch
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/managedfields"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/cel"
|
||||
)
|
||||
|
||||
// Patcher provides a patch function to perform a mutation to an object in the admission chain.
|
||||
type Patcher interface {
|
||||
// Patch returns a copy of the object in the request, modified to change specified by the patch.
|
||||
// The original object in the request MUST NOT be modified in-place.
|
||||
Patch(ctx context.Context, request Request, runtimeCELCostBudget int64) (runtime.Object, error)
|
||||
}
|
||||
|
||||
// Request defines the arguments required by a patcher.
|
||||
type Request struct {
|
||||
MatchedResource schema.GroupVersionResource
|
||||
VersionedAttributes *admission.VersionedAttributes
|
||||
ObjectInterfaces admission.ObjectInterfaces
|
||||
OptionalVariables cel.OptionalVariableBindings
|
||||
Namespace *v1.Namespace
|
||||
TypeConverter managedfields.TypeConverter
|
||||
}
|
192
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/patch/json_patch.go
generated
vendored
192
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/patch/json_patch.go
generated
vendored
@ -1,192 +0,0 @@
|
||||
/*
|
||||
Copyright 2024 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 patch
|
||||
|
||||
import (
|
||||
"context"
|
||||
gojson "encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
celgo "github.com/google/cel-go/cel"
|
||||
"reflect"
|
||||
"strconv"
|
||||
|
||||
"github.com/google/cel-go/common/types"
|
||||
"github.com/google/cel-go/common/types/traits"
|
||||
"google.golang.org/protobuf/types/known/structpb"
|
||||
jsonpatch "gopkg.in/evanphx/json-patch.v4"
|
||||
|
||||
admissionv1 "k8s.io/api/admission/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer/json"
|
||||
plugincel "k8s.io/apiserver/pkg/admission/plugin/cel"
|
||||
"k8s.io/apiserver/pkg/cel/mutation"
|
||||
"k8s.io/apiserver/pkg/cel/mutation/dynamic"
|
||||
pointer "k8s.io/utils/ptr"
|
||||
)
|
||||
|
||||
// JSONPatchCondition contains the inputs needed to compile and evaluate a cel expression
|
||||
// that returns a JSON patch value.
|
||||
type JSONPatchCondition struct {
|
||||
Expression string
|
||||
}
|
||||
|
||||
var _ plugincel.ExpressionAccessor = &JSONPatchCondition{}
|
||||
|
||||
func (v *JSONPatchCondition) GetExpression() string {
|
||||
return v.Expression
|
||||
}
|
||||
|
||||
func (v *JSONPatchCondition) ReturnTypes() []*celgo.Type {
|
||||
return []*celgo.Type{celgo.ListType(jsonPatchType)}
|
||||
}
|
||||
|
||||
var jsonPatchType = types.NewObjectType("JSONPatch")
|
||||
|
||||
// NewJSONPatcher creates a patcher that performs a JSON Patch mutation.
|
||||
func NewJSONPatcher(patchEvaluator plugincel.MutatingEvaluator) Patcher {
|
||||
return &jsonPatcher{patchEvaluator}
|
||||
}
|
||||
|
||||
type jsonPatcher struct {
|
||||
PatchEvaluator plugincel.MutatingEvaluator
|
||||
}
|
||||
|
||||
func (e *jsonPatcher) Patch(ctx context.Context, r Request, runtimeCELCostBudget int64) (runtime.Object, error) {
|
||||
admissionRequest := plugincel.CreateAdmissionRequest(
|
||||
r.VersionedAttributes.Attributes,
|
||||
metav1.GroupVersionResource(r.MatchedResource),
|
||||
metav1.GroupVersionKind(r.VersionedAttributes.VersionedKind))
|
||||
|
||||
compileErrors := e.PatchEvaluator.CompilationErrors()
|
||||
if len(compileErrors) > 0 {
|
||||
return nil, errors.Join(compileErrors...)
|
||||
}
|
||||
patchObj, _, err := e.evaluatePatchExpression(ctx, e.PatchEvaluator, runtimeCELCostBudget, r, admissionRequest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
o := r.ObjectInterfaces
|
||||
jsonSerializer := json.NewSerializerWithOptions(json.DefaultMetaFactory, o.GetObjectCreater(), o.GetObjectTyper(), json.SerializerOptions{Pretty: false, Strict: true})
|
||||
objJS, err := runtime.Encode(jsonSerializer, r.VersionedAttributes.VersionedObject)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create JSON patch: %w", err)
|
||||
}
|
||||
patchedJS, err := patchObj.Apply(objJS)
|
||||
if err != nil {
|
||||
if errors.Is(err, jsonpatch.ErrTestFailed) {
|
||||
// If a json patch fails a test operation, the patch must not be applied
|
||||
return r.VersionedAttributes.VersionedObject, nil
|
||||
}
|
||||
return nil, fmt.Errorf("JSON Patch: %w", err)
|
||||
}
|
||||
|
||||
var newVersionedObject runtime.Object
|
||||
if _, ok := r.VersionedAttributes.VersionedObject.(*unstructured.Unstructured); ok {
|
||||
newVersionedObject = &unstructured.Unstructured{}
|
||||
} else {
|
||||
newVersionedObject, err = o.GetObjectCreater().New(r.VersionedAttributes.VersionedKind)
|
||||
if err != nil {
|
||||
return nil, apierrors.NewInternalError(err)
|
||||
}
|
||||
}
|
||||
|
||||
if newVersionedObject, _, err = jsonSerializer.Decode(patchedJS, nil, newVersionedObject); err != nil {
|
||||
return nil, apierrors.NewInternalError(err)
|
||||
}
|
||||
|
||||
return newVersionedObject, nil
|
||||
}
|
||||
|
||||
func (e *jsonPatcher) evaluatePatchExpression(ctx context.Context, patchEvaluator plugincel.MutatingEvaluator, remainingBudget int64, r Request, admissionRequest *admissionv1.AdmissionRequest) (jsonpatch.Patch, int64, error) {
|
||||
var err error
|
||||
var eval plugincel.EvaluationResult
|
||||
eval, remainingBudget, err = patchEvaluator.ForInput(ctx, r.VersionedAttributes, admissionRequest, r.OptionalVariables, r.Namespace, remainingBudget)
|
||||
if err != nil {
|
||||
return nil, -1, err
|
||||
}
|
||||
if eval.Error != nil {
|
||||
return nil, -1, eval.Error
|
||||
}
|
||||
refVal := eval.EvalResult
|
||||
|
||||
// the return type can be any valid CEL value.
|
||||
// Scalars, maps and lists are used to set the value when the path points to a field of that type.
|
||||
// ObjectVal is used when the path points to a struct. A map like "{"field1": 1, "fieldX": bool}" is not
|
||||
// possible in Kubernetes CEL because maps and lists may not have mixed types.
|
||||
|
||||
iter, ok := refVal.(traits.Lister)
|
||||
if !ok {
|
||||
// Should never happen since compiler checks return type.
|
||||
return nil, -1, fmt.Errorf("type mismatch: JSONPatchType.expression should evaluate to array")
|
||||
}
|
||||
result := jsonpatch.Patch{}
|
||||
for it := iter.Iterator(); it.HasNext() == types.True; {
|
||||
v := it.Next()
|
||||
patchObj, err := v.ConvertToNative(reflect.TypeOf(&mutation.JSONPatchVal{}))
|
||||
if err != nil {
|
||||
// Should never happen since return type is checked by compiler.
|
||||
return nil, -1, fmt.Errorf("type mismatch: JSONPatchType.expression should evaluate to array of JSONPatch: %w", err)
|
||||
}
|
||||
op, ok := patchObj.(*mutation.JSONPatchVal)
|
||||
if !ok {
|
||||
// Should never happen since return type is checked by compiler.
|
||||
return nil, -1, fmt.Errorf("type mismatch: JSONPatchType.expression should evaluate to array of JSONPatch, got element of %T", patchObj)
|
||||
}
|
||||
|
||||
// Construct a JSON Patch from the evaluated CEL expression
|
||||
resultOp := jsonpatch.Operation{}
|
||||
resultOp["op"] = pointer.To(gojson.RawMessage(strconv.Quote(op.Op)))
|
||||
resultOp["path"] = pointer.To(gojson.RawMessage(strconv.Quote(op.Path)))
|
||||
if len(op.From) > 0 {
|
||||
resultOp["from"] = pointer.To(gojson.RawMessage(strconv.Quote(op.From)))
|
||||
}
|
||||
if op.Val != nil {
|
||||
if objVal, ok := op.Val.(*dynamic.ObjectVal); ok {
|
||||
// TODO: Object initializers are insufficiently type checked.
|
||||
// In the interim, we use this sanity check to detect type mismatches
|
||||
// between field names and Object initializers. For example,
|
||||
// "Object.spec{ selector: Object.spec.wrong{}}" is detected as a mismatch.
|
||||
// Before beta, attaching full type information both to Object initializers and
|
||||
// the "object" and "oldObject" variables is needed. This will allow CEL to
|
||||
// perform comprehensive runtime type checking.
|
||||
err := objVal.CheckTypeNamesMatchFieldPathNames()
|
||||
if err != nil {
|
||||
return nil, -1, fmt.Errorf("type mismatch: %w", err)
|
||||
}
|
||||
}
|
||||
// CEL data literals representing arbitrary JSON values can be serialized to JSON for use in
|
||||
// JSON Patch if first converted to pb.Value.
|
||||
v, err := op.Val.ConvertToNative(reflect.TypeOf(&structpb.Value{}))
|
||||
if err != nil {
|
||||
return nil, -1, fmt.Errorf("JSONPath valueExpression evaluated to a type that could not marshal to JSON: %w", err)
|
||||
}
|
||||
b, err := gojson.Marshal(v)
|
||||
if err != nil {
|
||||
return nil, -1, fmt.Errorf("JSONPath valueExpression evaluated to a type that could not marshal to JSON: %w", err)
|
||||
}
|
||||
resultOp["value"] = pointer.To[gojson.RawMessage](b)
|
||||
}
|
||||
|
||||
result = append(result, resultOp)
|
||||
}
|
||||
|
||||
return result, remainingBudget, nil
|
||||
}
|
217
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/patch/smd.go
generated
vendored
217
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/patch/smd.go
generated
vendored
@ -1,217 +0,0 @@
|
||||
/*
|
||||
Copyright 2024 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 patch
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
celgo "github.com/google/cel-go/cel"
|
||||
celtypes "github.com/google/cel-go/common/types"
|
||||
"strings"
|
||||
|
||||
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
|
||||
"sigs.k8s.io/structured-merge-diff/v4/schema"
|
||||
"sigs.k8s.io/structured-merge-diff/v4/typed"
|
||||
"sigs.k8s.io/structured-merge-diff/v4/value"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/managedfields"
|
||||
plugincel "k8s.io/apiserver/pkg/admission/plugin/cel"
|
||||
"k8s.io/apiserver/pkg/cel/mutation/dynamic"
|
||||
)
|
||||
|
||||
// ApplyConfigurationCondition contains the inputs needed to compile and evaluate a cel expression
|
||||
// that returns an apply configuration
|
||||
type ApplyConfigurationCondition struct {
|
||||
Expression string
|
||||
}
|
||||
|
||||
var _ plugincel.ExpressionAccessor = &ApplyConfigurationCondition{}
|
||||
|
||||
func (v *ApplyConfigurationCondition) GetExpression() string {
|
||||
return v.Expression
|
||||
}
|
||||
|
||||
func (v *ApplyConfigurationCondition) ReturnTypes() []*celgo.Type {
|
||||
return []*celgo.Type{applyConfigObjectType}
|
||||
}
|
||||
|
||||
var applyConfigObjectType = celtypes.NewObjectType("Object")
|
||||
|
||||
// NewApplyConfigurationPatcher creates a patcher that performs an applyConfiguration mutation.
|
||||
func NewApplyConfigurationPatcher(expressionEvaluator plugincel.MutatingEvaluator) Patcher {
|
||||
return &applyConfigPatcher{expressionEvaluator: expressionEvaluator}
|
||||
}
|
||||
|
||||
type applyConfigPatcher struct {
|
||||
expressionEvaluator plugincel.MutatingEvaluator
|
||||
}
|
||||
|
||||
func (e *applyConfigPatcher) Patch(ctx context.Context, r Request, runtimeCELCostBudget int64) (runtime.Object, error) {
|
||||
admissionRequest := plugincel.CreateAdmissionRequest(
|
||||
r.VersionedAttributes.Attributes,
|
||||
metav1.GroupVersionResource(r.MatchedResource),
|
||||
metav1.GroupVersionKind(r.VersionedAttributes.VersionedKind))
|
||||
|
||||
compileErrors := e.expressionEvaluator.CompilationErrors()
|
||||
if len(compileErrors) > 0 {
|
||||
return nil, errors.Join(compileErrors...)
|
||||
}
|
||||
eval, _, err := e.expressionEvaluator.ForInput(ctx, r.VersionedAttributes, admissionRequest, r.OptionalVariables, r.Namespace, runtimeCELCostBudget)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if eval.Error != nil {
|
||||
return nil, eval.Error
|
||||
}
|
||||
v := eval.EvalResult
|
||||
|
||||
// The compiler ensures that the return type is an ObjectVal with type name of "Object".
|
||||
objVal, ok := v.(*dynamic.ObjectVal)
|
||||
if !ok {
|
||||
// Should not happen since the compiler type checks the return type.
|
||||
return nil, fmt.Errorf("unsupported return type from ApplyConfiguration expression: %v", v.Type())
|
||||
}
|
||||
// TODO: Object initializers are insufficiently type checked.
|
||||
// In the interim, we use this sanity check to detect type mismatches
|
||||
// between field names and Object initializers. For example,
|
||||
// "Object.spec{ selector: Object.spec.wrong{}}" is detected as a mismatch.
|
||||
// Before beta, attaching full type information both to Object initializers and
|
||||
// the "object" and "oldObject" variables is needed. This will allow CEL to
|
||||
// perform comprehensive runtime type checking.
|
||||
err = objVal.CheckTypeNamesMatchFieldPathNames()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("type mismatch: %w", err)
|
||||
}
|
||||
|
||||
value, ok := objVal.Value().(map[string]any)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid return type: %T", v)
|
||||
}
|
||||
|
||||
patchObject := unstructured.Unstructured{Object: value}
|
||||
patchObject.SetGroupVersionKind(r.VersionedAttributes.VersionedObject.GetObjectKind().GroupVersionKind())
|
||||
patched, err := ApplyStructuredMergeDiff(r.TypeConverter, r.VersionedAttributes.VersionedObject, &patchObject)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error applying patch: %w", err)
|
||||
}
|
||||
return patched, nil
|
||||
}
|
||||
|
||||
// ApplyStructuredMergeDiff applies a structured merge diff to an object and returns a copy of the object
|
||||
// with the patch applied.
|
||||
func ApplyStructuredMergeDiff(
|
||||
typeConverter managedfields.TypeConverter,
|
||||
originalObject runtime.Object,
|
||||
patch *unstructured.Unstructured,
|
||||
) (runtime.Object, error) {
|
||||
if patch.GroupVersionKind() != originalObject.GetObjectKind().GroupVersionKind() {
|
||||
return nil, fmt.Errorf("patch (%v) and original object (%v) are not of the same gvk", patch.GroupVersionKind().String(), originalObject.GetObjectKind().GroupVersionKind().String())
|
||||
} else if typeConverter == nil {
|
||||
return nil, fmt.Errorf("type converter must not be nil")
|
||||
}
|
||||
|
||||
patchObjTyped, err := typeConverter.ObjectToTyped(patch)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to convert patch object to typed object: %w", err)
|
||||
}
|
||||
|
||||
err = validatePatch(patchObjTyped)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid ApplyConfiguration: %w", err)
|
||||
}
|
||||
|
||||
liveObjTyped, err := typeConverter.ObjectToTyped(originalObject)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to convert original object to typed object: %w", err)
|
||||
}
|
||||
|
||||
newObjTyped, err := liveObjTyped.Merge(patchObjTyped)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to merge patch: %w", err)
|
||||
}
|
||||
|
||||
// Our mutating admission policy sets the fields but does not track ownership.
|
||||
// Newly introduced fields in the patch won't be tracked by a field manager
|
||||
// (so if the original object is updated again but the mutating policy is
|
||||
// not active, the fields will be dropped).
|
||||
//
|
||||
// This necessarily means that changes to an object by a mutating policy
|
||||
// are only preserved if the policy was active at the time of the change.
|
||||
// (If the policy is not active, the changes may be dropped.)
|
||||
|
||||
newObj, err := typeConverter.TypedToObject(newObjTyped)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to convert typed object to object: %w", err)
|
||||
}
|
||||
|
||||
return newObj, nil
|
||||
}
|
||||
|
||||
// validatePatch searches an apply configuration for any arrays, maps or structs elements that are atomic and returns
|
||||
// an error if any are found.
|
||||
// This prevents accidental removal of fields that can occur when the user intends to modify some
|
||||
// fields in an atomic type, not realizing that all fields not explicitly set in the new value
|
||||
// of the atomic will be removed.
|
||||
func validatePatch(v *typed.TypedValue) error {
|
||||
atomics := findAtomics(nil, v.Schema(), v.TypeRef(), v.AsValue())
|
||||
if len(atomics) > 0 {
|
||||
return fmt.Errorf("may not mutate atomic arrays, maps or structs: %v", strings.Join(atomics, ", "))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// findAtomics returns field paths for any atomic arrays, maps or structs found when traversing the given value.
|
||||
func findAtomics(path []fieldpath.PathElement, s *schema.Schema, tr schema.TypeRef, v value.Value) (atomics []string) {
|
||||
if a, ok := s.Resolve(tr); ok { // Validation pass happens before this and checks that all schemas can be resolved
|
||||
if v.IsMap() && a.Map != nil {
|
||||
if a.Map.ElementRelationship == schema.Atomic {
|
||||
atomics = append(atomics, pathString(path))
|
||||
}
|
||||
v.AsMap().Iterate(func(key string, val value.Value) bool {
|
||||
pe := fieldpath.PathElement{FieldName: &key}
|
||||
if sf, ok := a.Map.FindField(key); ok {
|
||||
tr = sf.Type
|
||||
atomics = append(atomics, findAtomics(append(path, pe), s, tr, val)...)
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
if v.IsList() && a.List != nil {
|
||||
if a.List.ElementRelationship == schema.Atomic {
|
||||
atomics = append(atomics, pathString(path))
|
||||
}
|
||||
list := v.AsList()
|
||||
for i := 0; i < list.Length(); i++ {
|
||||
pe := fieldpath.PathElement{Index: &i}
|
||||
atomics = append(atomics, findAtomics(append(path, pe), s, a.List.ElementType, list.At(i))...)
|
||||
}
|
||||
}
|
||||
}
|
||||
return atomics
|
||||
}
|
||||
|
||||
func pathString(path []fieldpath.PathElement) string {
|
||||
sb := strings.Builder{}
|
||||
for _, p := range path {
|
||||
sb.WriteString(p.String())
|
||||
}
|
||||
return sb.String()
|
||||
}
|
187
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/patch/typeconverter.go
generated
vendored
187
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/patch/typeconverter.go
generated
vendored
@ -1,187 +0,0 @@
|
||||
/*
|
||||
Copyright 2024 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 patch
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/managedfields"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/client-go/openapi"
|
||||
"k8s.io/kube-openapi/pkg/spec3"
|
||||
)
|
||||
|
||||
type TypeConverterManager interface {
|
||||
// GetTypeConverter returns a type converter for the given GVK
|
||||
GetTypeConverter(gvk schema.GroupVersionKind) managedfields.TypeConverter
|
||||
Run(ctx context.Context)
|
||||
}
|
||||
|
||||
func NewTypeConverterManager(
|
||||
staticTypeConverter managedfields.TypeConverter,
|
||||
openapiClient openapi.Client,
|
||||
) TypeConverterManager {
|
||||
return &typeConverterManager{
|
||||
staticTypeConverter: staticTypeConverter,
|
||||
openapiClient: openapiClient,
|
||||
typeConverterMap: make(map[schema.GroupVersion]typeConverterCacheEntry),
|
||||
lastFetchedPaths: make(map[schema.GroupVersion]openapi.GroupVersion),
|
||||
}
|
||||
}
|
||||
|
||||
type typeConverterCacheEntry struct {
|
||||
typeConverter managedfields.TypeConverter
|
||||
entry openapi.GroupVersion
|
||||
}
|
||||
|
||||
// typeConverterManager helps us make sure we have an up to date schema and
|
||||
// type converter for our openapi models. It should be connfigured to use a
|
||||
// static type converter for natively typed schemas, and fetches the schema
|
||||
// for CRDs/other over the network on demand (trying to reduce network calls where necessary)
|
||||
type typeConverterManager struct {
|
||||
// schemaCache is used to cache the schema for a given GVK
|
||||
staticTypeConverter managedfields.TypeConverter
|
||||
|
||||
// discoveryClient is used to fetch the schema for a given GVK
|
||||
openapiClient openapi.Client
|
||||
|
||||
lock sync.RWMutex
|
||||
|
||||
typeConverterMap map[schema.GroupVersion]typeConverterCacheEntry
|
||||
lastFetchedPaths map[schema.GroupVersion]openapi.GroupVersion
|
||||
}
|
||||
|
||||
func (t *typeConverterManager) Run(ctx context.Context) {
|
||||
// Loop every 5s refershing the OpenAPI schema list to know which
|
||||
// schemas have been invalidated. This should use e-tags under the hood
|
||||
_ = wait.PollUntilContextCancel(ctx, 5*time.Second, true, func(_ context.Context) (done bool, err error) {
|
||||
paths, err := t.openapiClient.Paths()
|
||||
if err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("failed to fetch openapi paths: %w", err))
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// The /openapi/v3 endpoint contains a list of paths whose ServerRelativeURL
|
||||
// value changes every time the schema is updated. So we poll /openapi/v3
|
||||
// to get the "version number" for each schema, and invalidate our cache
|
||||
// if the version number has changed since we pulled it.
|
||||
parsedPaths := make(map[schema.GroupVersion]openapi.GroupVersion, len(paths))
|
||||
for path, entry := range paths {
|
||||
if !strings.HasPrefix(path, "apis/") && !strings.HasPrefix(path, "api/") {
|
||||
continue
|
||||
}
|
||||
path = strings.TrimPrefix(path, "apis/")
|
||||
path = strings.TrimPrefix(path, "api/")
|
||||
|
||||
gv, err := schema.ParseGroupVersion(path)
|
||||
if err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("failed to parse group version %q: %w", path, err))
|
||||
return false, nil
|
||||
}
|
||||
|
||||
parsedPaths[gv] = entry
|
||||
}
|
||||
|
||||
t.lock.Lock()
|
||||
defer t.lock.Unlock()
|
||||
t.lastFetchedPaths = parsedPaths
|
||||
return false, nil
|
||||
})
|
||||
}
|
||||
|
||||
func (t *typeConverterManager) GetTypeConverter(gvk schema.GroupVersionKind) managedfields.TypeConverter {
|
||||
// Check to see if the static type converter handles this GVK
|
||||
if t.staticTypeConverter != nil {
|
||||
//!TODO: Add ability to check existence to type converter
|
||||
// working around for now but seeing if getting a typed version of an
|
||||
// empty object returns error
|
||||
stub := &unstructured.Unstructured{}
|
||||
stub.SetGroupVersionKind(gvk)
|
||||
|
||||
if _, err := t.staticTypeConverter.ObjectToTyped(stub); err == nil {
|
||||
return t.staticTypeConverter
|
||||
}
|
||||
}
|
||||
|
||||
gv := gvk.GroupVersion()
|
||||
|
||||
existing, entry, err := func() (managedfields.TypeConverter, openapi.GroupVersion, error) {
|
||||
t.lock.RLock()
|
||||
defer t.lock.RUnlock()
|
||||
|
||||
// If schema is not supported by static type converter, ask discovery
|
||||
// for the schema
|
||||
entry, ok := t.lastFetchedPaths[gv]
|
||||
if !ok {
|
||||
// If we can't get the schema, we can't do anything
|
||||
return nil, nil, fmt.Errorf("no schema for %v", gvk)
|
||||
}
|
||||
|
||||
// If the entry schema has not changed, used the same type converter
|
||||
if existing, ok := t.typeConverterMap[gv]; ok && existing.entry.ServerRelativeURL() == entry.ServerRelativeURL() {
|
||||
// If we have a type converter for this GVK, return it
|
||||
return existing.typeConverter, existing.entry, nil
|
||||
}
|
||||
|
||||
return nil, entry, nil
|
||||
}()
|
||||
if err != nil {
|
||||
utilruntime.HandleError(err)
|
||||
return nil
|
||||
} else if existing != nil {
|
||||
return existing
|
||||
}
|
||||
|
||||
schBytes, err := entry.Schema(runtime.ContentTypeJSON)
|
||||
if err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("failed to get schema for %v: %w", gvk, err))
|
||||
return nil
|
||||
}
|
||||
|
||||
var sch spec3.OpenAPI
|
||||
if err := json.Unmarshal(schBytes, &sch); err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("failed to unmarshal schema for %v: %w", gvk, err))
|
||||
return nil
|
||||
}
|
||||
|
||||
// The schema has changed, or there is no entry for it, generate
|
||||
// a new type converter for this GV
|
||||
tc, err := managedfields.NewTypeConverter(sch.Components.Schemas, false)
|
||||
if err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("failed to create type converter for %v: %w", gvk, err))
|
||||
return nil
|
||||
}
|
||||
|
||||
t.lock.Lock()
|
||||
defer t.lock.Unlock()
|
||||
|
||||
t.typeConverterMap[gv] = typeConverterCacheEntry{
|
||||
typeConverter: tc,
|
||||
entry: entry,
|
||||
}
|
||||
|
||||
return tc
|
||||
}
|
151
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/plugin.go
generated
vendored
151
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/plugin.go
generated
vendored
@ -1,151 +0,0 @@
|
||||
/*
|
||||
Copyright 2024 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 mutating
|
||||
|
||||
import (
|
||||
"context"
|
||||
celgo "github.com/google/cel-go/cel"
|
||||
"io"
|
||||
|
||||
"k8s.io/api/admissionregistration/v1alpha1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/managedfields"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/cel"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/policy/generic"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/policy/matching"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/policy/mutating/patch"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/matchconditions"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
"k8s.io/apiserver/pkg/features"
|
||||
"k8s.io/client-go/dynamic"
|
||||
"k8s.io/client-go/informers"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/component-base/featuregate"
|
||||
)
|
||||
|
||||
const (
|
||||
// PluginName indicates the name of admission plug-in
|
||||
PluginName = "MutatingAdmissionPolicy"
|
||||
)
|
||||
|
||||
// Register registers a plugin
|
||||
func Register(plugins *admission.Plugins) {
|
||||
plugins.Register(PluginName, func(configFile io.Reader) (admission.Interface, error) {
|
||||
return NewPlugin(configFile), nil
|
||||
})
|
||||
}
|
||||
|
||||
type Policy = v1alpha1.MutatingAdmissionPolicy
|
||||
type PolicyBinding = v1alpha1.MutatingAdmissionPolicyBinding
|
||||
type PolicyMutation = v1alpha1.Mutation
|
||||
type PolicyHook = generic.PolicyHook[*Policy, *PolicyBinding, PolicyEvaluator]
|
||||
|
||||
type Mutator struct {
|
||||
}
|
||||
type MutationEvaluationFunc func(
|
||||
ctx context.Context,
|
||||
matchedResource schema.GroupVersionResource,
|
||||
versionedAttr *admission.VersionedAttributes,
|
||||
o admission.ObjectInterfaces,
|
||||
versionedParams runtime.Object,
|
||||
namespace *corev1.Namespace,
|
||||
typeConverter managedfields.TypeConverter,
|
||||
runtimeCELCostBudget int64,
|
||||
authorizer authorizer.Authorizer,
|
||||
) (runtime.Object, error)
|
||||
|
||||
type PolicyEvaluator struct {
|
||||
Matcher matchconditions.Matcher
|
||||
Mutators []patch.Patcher
|
||||
CompositionEnv *cel.CompositionEnv
|
||||
Error error
|
||||
}
|
||||
|
||||
// Plugin is an implementation of admission.Interface.
|
||||
type Plugin struct {
|
||||
*generic.Plugin[PolicyHook]
|
||||
}
|
||||
|
||||
var _ admission.Interface = &Plugin{}
|
||||
var _ admission.MutationInterface = &Plugin{}
|
||||
|
||||
// NewPlugin returns a generic admission webhook plugin.
|
||||
func NewPlugin(_ io.Reader) *Plugin {
|
||||
// There is no request body to mutate for DELETE, so this plugin never handles that operation.
|
||||
handler := admission.NewHandler(admission.Create, admission.Update, admission.Connect)
|
||||
res := &Plugin{}
|
||||
res.Plugin = generic.NewPlugin(
|
||||
handler,
|
||||
func(f informers.SharedInformerFactory, client kubernetes.Interface, dynamicClient dynamic.Interface, restMapper meta.RESTMapper) generic.Source[PolicyHook] {
|
||||
return generic.NewPolicySource(
|
||||
f.Admissionregistration().V1alpha1().MutatingAdmissionPolicies().Informer(),
|
||||
f.Admissionregistration().V1alpha1().MutatingAdmissionPolicyBindings().Informer(),
|
||||
NewMutatingAdmissionPolicyAccessor,
|
||||
NewMutatingAdmissionPolicyBindingAccessor,
|
||||
compilePolicy,
|
||||
//!TODO: Create a way to share param informers between
|
||||
// mutating/validating plugins
|
||||
f,
|
||||
dynamicClient,
|
||||
restMapper,
|
||||
)
|
||||
},
|
||||
func(a authorizer.Authorizer, m *matching.Matcher, client kubernetes.Interface) generic.Dispatcher[PolicyHook] {
|
||||
return NewDispatcher(a, m, patch.NewTypeConverterManager(nil, client.Discovery().OpenAPIV3()))
|
||||
},
|
||||
)
|
||||
return res
|
||||
}
|
||||
|
||||
// Admit makes an admission decision based on the request attributes.
|
||||
func (a *Plugin) Admit(ctx context.Context, attr admission.Attributes, o admission.ObjectInterfaces) error {
|
||||
return a.Plugin.Dispatch(ctx, attr, o)
|
||||
}
|
||||
|
||||
func (a *Plugin) InspectFeatureGates(featureGates featuregate.FeatureGate) {
|
||||
a.Plugin.SetEnabled(featureGates.Enabled(features.MutatingAdmissionPolicy))
|
||||
}
|
||||
|
||||
// Variable is a named expression for composition.
|
||||
type Variable struct {
|
||||
Name string
|
||||
Expression string
|
||||
}
|
||||
|
||||
func (v *Variable) GetExpression() string {
|
||||
return v.Expression
|
||||
}
|
||||
|
||||
func (v *Variable) ReturnTypes() []*celgo.Type {
|
||||
return []*celgo.Type{celgo.AnyType, celgo.DynType}
|
||||
}
|
||||
|
||||
func (v *Variable) GetName() string {
|
||||
return v.Name
|
||||
}
|
||||
|
||||
func convertv1alpha1Variables(variables []v1alpha1.Variable) []cel.NamedExpressionAccessor {
|
||||
namedExpressions := make([]cel.NamedExpressionAccessor, len(variables))
|
||||
for i, variable := range variables {
|
||||
namedExpressions[i] = &Variable{Name: variable.Name, Expression: variable.Expression}
|
||||
}
|
||||
return namedExpressions
|
||||
}
|
76
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/reinvocationcontext.go
generated
vendored
76
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/mutating/reinvocationcontext.go
generated
vendored
@ -1,76 +0,0 @@
|
||||
/*
|
||||
Copyright 2024 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 mutating
|
||||
|
||||
import (
|
||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
)
|
||||
|
||||
type key struct {
|
||||
PolicyUID types.NamespacedName
|
||||
BindingUID types.NamespacedName
|
||||
ParamUID types.NamespacedName
|
||||
MutationIndex int
|
||||
}
|
||||
|
||||
type policyReinvokeContext struct {
|
||||
// lastPolicyOutput holds the result of the last Policy admission plugin call
|
||||
lastPolicyOutput runtime.Object
|
||||
// previouslyInvokedReinvocablePolicys holds the set of policies that have been invoked and
|
||||
// should be reinvoked if a later mutation occurs
|
||||
previouslyInvokedReinvocablePolicies sets.Set[key]
|
||||
// reinvokePolicies holds the set of Policies that should be reinvoked
|
||||
reinvokePolicies sets.Set[key]
|
||||
}
|
||||
|
||||
func (rc *policyReinvokeContext) ShouldReinvoke(policy key) bool {
|
||||
return rc.reinvokePolicies.Has(policy)
|
||||
}
|
||||
|
||||
func (rc *policyReinvokeContext) IsOutputChangedSinceLastPolicyInvocation(object runtime.Object) bool {
|
||||
return !apiequality.Semantic.DeepEqual(rc.lastPolicyOutput, object)
|
||||
}
|
||||
|
||||
func (rc *policyReinvokeContext) SetLastPolicyInvocationOutput(object runtime.Object) {
|
||||
if object == nil {
|
||||
rc.lastPolicyOutput = nil
|
||||
return
|
||||
}
|
||||
rc.lastPolicyOutput = object.DeepCopyObject()
|
||||
}
|
||||
|
||||
func (rc *policyReinvokeContext) AddReinvocablePolicyToPreviouslyInvoked(policy key) {
|
||||
if rc.previouslyInvokedReinvocablePolicies == nil {
|
||||
rc.previouslyInvokedReinvocablePolicies = sets.New[key]()
|
||||
}
|
||||
rc.previouslyInvokedReinvocablePolicies.Insert(policy)
|
||||
}
|
||||
|
||||
func (rc *policyReinvokeContext) RequireReinvokingPreviouslyInvokedPlugins() {
|
||||
if len(rc.previouslyInvokedReinvocablePolicies) > 0 {
|
||||
if rc.reinvokePolicies == nil {
|
||||
rc.reinvokePolicies = sets.New[key]()
|
||||
}
|
||||
for s := range rc.previouslyInvokedReinvocablePolicies {
|
||||
rc.reinvokePolicies.Insert(s)
|
||||
}
|
||||
rc.previouslyInvokedReinvocablePolicies = sets.New[key]()
|
||||
}
|
||||
}
|
86
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/validating/accessor.go
generated
vendored
86
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/validating/accessor.go
generated
vendored
@ -1,86 +0,0 @@
|
||||
/*
|
||||
Copyright 2024 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 validating
|
||||
|
||||
import (
|
||||
"k8s.io/api/admissionregistration/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/policy/generic"
|
||||
)
|
||||
|
||||
func NewValidatingAdmissionPolicyAccessor(obj *v1.ValidatingAdmissionPolicy) generic.PolicyAccessor {
|
||||
return &validatingAdmissionPolicyAccessor{
|
||||
ValidatingAdmissionPolicy: obj,
|
||||
}
|
||||
}
|
||||
|
||||
func NewValidatingAdmissionPolicyBindingAccessor(obj *v1.ValidatingAdmissionPolicyBinding) generic.BindingAccessor {
|
||||
return &validatingAdmissionPolicyBindingAccessor{
|
||||
ValidatingAdmissionPolicyBinding: obj,
|
||||
}
|
||||
}
|
||||
|
||||
type validatingAdmissionPolicyAccessor struct {
|
||||
*v1.ValidatingAdmissionPolicy
|
||||
}
|
||||
|
||||
func (v *validatingAdmissionPolicyAccessor) GetNamespace() string {
|
||||
return v.Namespace
|
||||
}
|
||||
|
||||
func (v *validatingAdmissionPolicyAccessor) GetName() string {
|
||||
return v.Name
|
||||
}
|
||||
|
||||
func (v *validatingAdmissionPolicyAccessor) GetParamKind() *v1.ParamKind {
|
||||
return v.Spec.ParamKind
|
||||
}
|
||||
|
||||
func (v *validatingAdmissionPolicyAccessor) GetMatchConstraints() *v1.MatchResources {
|
||||
return v.Spec.MatchConstraints
|
||||
}
|
||||
|
||||
func (v *validatingAdmissionPolicyAccessor) GetFailurePolicy() *v1.FailurePolicyType {
|
||||
return v.Spec.FailurePolicy
|
||||
}
|
||||
|
||||
type validatingAdmissionPolicyBindingAccessor struct {
|
||||
*v1.ValidatingAdmissionPolicyBinding
|
||||
}
|
||||
|
||||
func (v *validatingAdmissionPolicyBindingAccessor) GetNamespace() string {
|
||||
return v.Namespace
|
||||
}
|
||||
|
||||
func (v *validatingAdmissionPolicyBindingAccessor) GetName() string {
|
||||
return v.Name
|
||||
}
|
||||
|
||||
func (v *validatingAdmissionPolicyBindingAccessor) GetPolicyName() types.NamespacedName {
|
||||
return types.NamespacedName{
|
||||
Namespace: "",
|
||||
Name: v.Spec.PolicyName,
|
||||
}
|
||||
}
|
||||
|
||||
func (v *validatingAdmissionPolicyBindingAccessor) GetMatchResources() *v1.MatchResources {
|
||||
return v.Spec.MatchResources
|
||||
}
|
||||
|
||||
func (v *validatingAdmissionPolicyBindingAccessor) GetParamRef() *v1.ParamRef {
|
||||
return v.Spec.ParamRef
|
||||
}
|
420
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/validating/dispatcher.go
generated
vendored
420
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/validating/dispatcher.go
generated
vendored
@ -1,420 +0,0 @@
|
||||
/*
|
||||
Copyright 2022 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 validating
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
k8serrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
utiljson "k8s.io/apimachinery/pkg/util/json"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
admissionauthorizer "k8s.io/apiserver/pkg/admission/plugin/authorizer"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/policy/generic"
|
||||
celmetrics "k8s.io/apiserver/pkg/admission/plugin/policy/validating/metrics"
|
||||
celconfig "k8s.io/apiserver/pkg/apis/cel"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
"k8s.io/apiserver/pkg/warning"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
type dispatcher struct {
|
||||
matcher generic.PolicyMatcher
|
||||
authz authorizer.Authorizer
|
||||
}
|
||||
|
||||
var _ generic.Dispatcher[PolicyHook] = &dispatcher{}
|
||||
|
||||
func NewDispatcher(
|
||||
authorizer authorizer.Authorizer,
|
||||
matcher generic.PolicyMatcher,
|
||||
) generic.Dispatcher[PolicyHook] {
|
||||
return &dispatcher{
|
||||
matcher: matcher,
|
||||
authz: authorizer,
|
||||
}
|
||||
}
|
||||
|
||||
// contains the cel PolicyDecisions along with the ValidatingAdmissionPolicy and ValidatingAdmissionPolicyBinding
|
||||
// that determined the decision
|
||||
type policyDecisionWithMetadata struct {
|
||||
PolicyDecision
|
||||
Definition *admissionregistrationv1.ValidatingAdmissionPolicy
|
||||
Binding *admissionregistrationv1.ValidatingAdmissionPolicyBinding
|
||||
}
|
||||
|
||||
func (c *dispatcher) Start(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Dispatch implements generic.Dispatcher.
|
||||
func (c *dispatcher) Dispatch(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces, hooks []PolicyHook) error {
|
||||
|
||||
var deniedDecisions []policyDecisionWithMetadata
|
||||
|
||||
addConfigError := func(err error, definition *admissionregistrationv1.ValidatingAdmissionPolicy, binding *admissionregistrationv1.ValidatingAdmissionPolicyBinding) {
|
||||
// we always default the FailurePolicy if it is unset and validate it in API level
|
||||
var policy admissionregistrationv1.FailurePolicyType
|
||||
if definition.Spec.FailurePolicy == nil {
|
||||
policy = admissionregistrationv1.Fail
|
||||
} else {
|
||||
policy = *definition.Spec.FailurePolicy
|
||||
}
|
||||
|
||||
// apply FailurePolicy specified in ValidatingAdmissionPolicy, the default would be Fail
|
||||
switch policy {
|
||||
case admissionregistrationv1.Ignore:
|
||||
// TODO: add metrics for ignored error here
|
||||
return
|
||||
case admissionregistrationv1.Fail:
|
||||
var message string
|
||||
if binding == nil {
|
||||
message = fmt.Errorf("failed to configure policy: %w", err).Error()
|
||||
} else {
|
||||
message = fmt.Errorf("failed to configure binding: %w", err).Error()
|
||||
}
|
||||
deniedDecisions = append(deniedDecisions, policyDecisionWithMetadata{
|
||||
PolicyDecision: PolicyDecision{
|
||||
Action: ActionDeny,
|
||||
Message: message,
|
||||
},
|
||||
Definition: definition,
|
||||
Binding: binding,
|
||||
})
|
||||
default:
|
||||
deniedDecisions = append(deniedDecisions, policyDecisionWithMetadata{
|
||||
PolicyDecision: PolicyDecision{
|
||||
Action: ActionDeny,
|
||||
Message: fmt.Errorf("unrecognized failure policy: '%v'", policy).Error(),
|
||||
},
|
||||
Definition: definition,
|
||||
Binding: binding,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
authz := admissionauthorizer.NewCachingAuthorizer(c.authz)
|
||||
|
||||
for _, hook := range hooks {
|
||||
// versionedAttributes will be set to non-nil inside of the loop, but
|
||||
// is scoped outside of the param loop so we only convert once. We defer
|
||||
// conversion so that it is only performed when we know a policy matches,
|
||||
// saving the cost of converting non-matching requests.
|
||||
var versionedAttr *admission.VersionedAttributes
|
||||
|
||||
definition := hook.Policy
|
||||
matches, matchResource, matchKind, err := c.matcher.DefinitionMatches(a, o, NewValidatingAdmissionPolicyAccessor(definition))
|
||||
if err != nil {
|
||||
// Configuration error.
|
||||
addConfigError(err, definition, nil)
|
||||
continue
|
||||
}
|
||||
if !matches {
|
||||
// Policy definition does not match request
|
||||
continue
|
||||
} else if hook.ConfigurationError != nil {
|
||||
// Configuration error.
|
||||
addConfigError(hook.ConfigurationError, definition, nil)
|
||||
continue
|
||||
}
|
||||
|
||||
auditAnnotationCollector := newAuditAnnotationCollector()
|
||||
for _, binding := range hook.Bindings {
|
||||
// If the key is inside dependentBindings, there is guaranteed to
|
||||
// be a bindingInfo for it
|
||||
matches, err := c.matcher.BindingMatches(a, o, NewValidatingAdmissionPolicyBindingAccessor(binding))
|
||||
if err != nil {
|
||||
// Configuration error.
|
||||
addConfigError(err, definition, binding)
|
||||
continue
|
||||
}
|
||||
if !matches {
|
||||
continue
|
||||
}
|
||||
|
||||
params, err := generic.CollectParams(
|
||||
hook.Policy.Spec.ParamKind,
|
||||
hook.ParamInformer,
|
||||
hook.ParamScope,
|
||||
binding.Spec.ParamRef,
|
||||
a.GetNamespace(),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
addConfigError(err, definition, binding)
|
||||
continue
|
||||
} else if versionedAttr == nil && len(params) > 0 {
|
||||
// As optimization versionedAttr creation is deferred until
|
||||
// first use. Since > 0 params, we will validate
|
||||
va, err := admission.NewVersionedAttributes(a, matchKind, o)
|
||||
if err != nil {
|
||||
wrappedErr := fmt.Errorf("failed to convert object version: %w", err)
|
||||
addConfigError(wrappedErr, definition, binding)
|
||||
continue
|
||||
}
|
||||
versionedAttr = va
|
||||
}
|
||||
|
||||
var validationResults []ValidateResult
|
||||
var namespace *v1.Namespace
|
||||
namespaceName := a.GetNamespace()
|
||||
|
||||
// Special case, the namespace object has the namespace of itself (maybe a bug).
|
||||
// unset it if the incoming object is a namespace
|
||||
if gvk := a.GetKind(); gvk.Kind == "Namespace" && gvk.Version == "v1" && gvk.Group == "" {
|
||||
namespaceName = ""
|
||||
}
|
||||
|
||||
// if it is cluster scoped, namespaceName will be empty
|
||||
// Otherwise, get the Namespace resource.
|
||||
if namespaceName != "" {
|
||||
namespace, err = c.matcher.GetNamespace(namespaceName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, param := range params {
|
||||
var p runtime.Object = param
|
||||
if p != nil && p.GetObjectKind().GroupVersionKind().Empty() {
|
||||
// Make sure param has TypeMeta populated
|
||||
// This is a simple hack to make sure typeMeta is
|
||||
// available to CEL without making copies of objects, etc.
|
||||
p = &wrappedParam{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: definition.Spec.ParamKind.APIVersion,
|
||||
Kind: definition.Spec.ParamKind.Kind,
|
||||
},
|
||||
nested: param,
|
||||
}
|
||||
}
|
||||
|
||||
validationResults = append(validationResults,
|
||||
hook.Evaluator.Validate(
|
||||
ctx,
|
||||
matchResource,
|
||||
versionedAttr,
|
||||
p,
|
||||
namespace,
|
||||
celconfig.RuntimeCELCostBudget,
|
||||
authz,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
for _, validationResult := range validationResults {
|
||||
for i, decision := range validationResult.Decisions {
|
||||
switch decision.Action {
|
||||
case ActionAdmit:
|
||||
if decision.Evaluation == EvalError {
|
||||
celmetrics.Metrics.ObserveAdmission(ctx, decision.Elapsed, definition.Name, binding.Name, ErrorType(&decision))
|
||||
}
|
||||
case ActionDeny:
|
||||
for _, action := range binding.Spec.ValidationActions {
|
||||
switch action {
|
||||
case admissionregistrationv1.Deny:
|
||||
deniedDecisions = append(deniedDecisions, policyDecisionWithMetadata{
|
||||
Definition: definition,
|
||||
Binding: binding,
|
||||
PolicyDecision: decision,
|
||||
})
|
||||
celmetrics.Metrics.ObserveRejection(ctx, decision.Elapsed, definition.Name, binding.Name, ErrorType(&decision))
|
||||
case admissionregistrationv1.Audit:
|
||||
publishValidationFailureAnnotation(binding, i, decision, versionedAttr)
|
||||
celmetrics.Metrics.ObserveAudit(ctx, decision.Elapsed, definition.Name, binding.Name, ErrorType(&decision))
|
||||
case admissionregistrationv1.Warn:
|
||||
warning.AddWarning(ctx, "", fmt.Sprintf("Validation failed for ValidatingAdmissionPolicy '%s' with binding '%s': %s", definition.Name, binding.Name, decision.Message))
|
||||
celmetrics.Metrics.ObserveWarn(ctx, decision.Elapsed, definition.Name, binding.Name, ErrorType(&decision))
|
||||
}
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unrecognized evaluation decision '%s' for ValidatingAdmissionPolicyBinding '%s' with ValidatingAdmissionPolicy '%s'",
|
||||
decision.Action, binding.Name, definition.Name)
|
||||
}
|
||||
}
|
||||
|
||||
for _, auditAnnotation := range validationResult.AuditAnnotations {
|
||||
switch auditAnnotation.Action {
|
||||
case AuditAnnotationActionPublish:
|
||||
value := auditAnnotation.Value
|
||||
if len(auditAnnotation.Value) > maxAuditAnnotationValueLength {
|
||||
value = value[:maxAuditAnnotationValueLength]
|
||||
}
|
||||
auditAnnotationCollector.add(auditAnnotation.Key, value)
|
||||
case AuditAnnotationActionError:
|
||||
// When failurePolicy=fail, audit annotation errors result in deny
|
||||
d := policyDecisionWithMetadata{
|
||||
Definition: definition,
|
||||
Binding: binding,
|
||||
PolicyDecision: PolicyDecision{
|
||||
Action: ActionDeny,
|
||||
Evaluation: EvalError,
|
||||
Message: auditAnnotation.Error,
|
||||
Elapsed: auditAnnotation.Elapsed,
|
||||
},
|
||||
}
|
||||
deniedDecisions = append(deniedDecisions, d)
|
||||
celmetrics.Metrics.ObserveRejection(ctx, auditAnnotation.Elapsed, definition.Name, binding.Name, ErrorType(&d.PolicyDecision))
|
||||
case AuditAnnotationActionExclude: // skip it
|
||||
default:
|
||||
return fmt.Errorf("unsupported AuditAnnotation Action: %s", auditAnnotation.Action)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
auditAnnotationCollector.publish(definition.Name, a)
|
||||
}
|
||||
|
||||
if len(deniedDecisions) > 0 {
|
||||
// TODO: refactor admission.NewForbidden so the name extraction is reusable but the code/reason is customizable
|
||||
var message string
|
||||
deniedDecision := deniedDecisions[0]
|
||||
if deniedDecision.Binding != nil {
|
||||
message = fmt.Sprintf("ValidatingAdmissionPolicy '%s' with binding '%s' denied request: %s", deniedDecision.Definition.Name, deniedDecision.Binding.Name, deniedDecision.Message)
|
||||
} else {
|
||||
message = fmt.Sprintf("ValidatingAdmissionPolicy '%s' denied request: %s", deniedDecision.Definition.Name, deniedDecision.Message)
|
||||
}
|
||||
err := admission.NewForbidden(a, errors.New(message)).(*k8serrors.StatusError)
|
||||
reason := deniedDecision.Reason
|
||||
if len(reason) == 0 {
|
||||
reason = metav1.StatusReasonInvalid
|
||||
}
|
||||
err.ErrStatus.Reason = reason
|
||||
err.ErrStatus.Code = reasonToCode(reason)
|
||||
err.ErrStatus.Details.Causes = append(err.ErrStatus.Details.Causes, metav1.StatusCause{Message: message})
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func publishValidationFailureAnnotation(binding *admissionregistrationv1.ValidatingAdmissionPolicyBinding, expressionIndex int, decision PolicyDecision, attributes admission.Attributes) {
|
||||
key := "validation.policy.admission.k8s.io/validation_failure"
|
||||
// Marshal to a list of failures since, in the future, we may need to support multiple failures
|
||||
valueJSON, err := utiljson.Marshal([]ValidationFailureValue{{
|
||||
ExpressionIndex: expressionIndex,
|
||||
Message: decision.Message,
|
||||
ValidationActions: binding.Spec.ValidationActions,
|
||||
Binding: binding.Name,
|
||||
Policy: binding.Spec.PolicyName,
|
||||
}})
|
||||
if err != nil {
|
||||
klog.Warningf("Failed to set admission audit annotation %s for ValidatingAdmissionPolicy %s and ValidatingAdmissionPolicyBinding %s: %v", key, binding.Spec.PolicyName, binding.Name, err)
|
||||
}
|
||||
value := string(valueJSON)
|
||||
if err := attributes.AddAnnotation(key, value); err != nil {
|
||||
klog.Warningf("Failed to set admission audit annotation %s to %s for ValidatingAdmissionPolicy %s and ValidatingAdmissionPolicyBinding %s: %v", key, value, binding.Spec.PolicyName, binding.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
const maxAuditAnnotationValueLength = 10 * 1024
|
||||
|
||||
// validationFailureValue defines the JSON format of a "validation.policy.admission.k8s.io/validation_failure" audit
|
||||
// annotation value.
|
||||
type ValidationFailureValue struct {
|
||||
Message string `json:"message"`
|
||||
Policy string `json:"policy"`
|
||||
Binding string `json:"binding"`
|
||||
ExpressionIndex int `json:"expressionIndex"`
|
||||
ValidationActions []admissionregistrationv1.ValidationAction `json:"validationActions"`
|
||||
}
|
||||
|
||||
type auditAnnotationCollector struct {
|
||||
annotations map[string][]string
|
||||
}
|
||||
|
||||
func newAuditAnnotationCollector() auditAnnotationCollector {
|
||||
return auditAnnotationCollector{annotations: map[string][]string{}}
|
||||
}
|
||||
|
||||
func (a auditAnnotationCollector) add(key, value string) {
|
||||
// If multiple bindings produces the exact same key and value for an audit annotation,
|
||||
// ignore the duplicates.
|
||||
for _, v := range a.annotations[key] {
|
||||
if v == value {
|
||||
return
|
||||
}
|
||||
}
|
||||
a.annotations[key] = append(a.annotations[key], value)
|
||||
}
|
||||
|
||||
func (a auditAnnotationCollector) publish(policyName string, attributes admission.Attributes) {
|
||||
for key, bindingAnnotations := range a.annotations {
|
||||
var value string
|
||||
if len(bindingAnnotations) == 1 {
|
||||
value = bindingAnnotations[0]
|
||||
} else {
|
||||
// Multiple distinct values can exist when binding params are used in the valueExpression of an auditAnnotation.
|
||||
// When this happens, the values are concatenated into a comma-separated list.
|
||||
value = strings.Join(bindingAnnotations, ", ")
|
||||
}
|
||||
if err := attributes.AddAnnotation(policyName+"/"+key, value); err != nil {
|
||||
klog.Warningf("Failed to set admission audit annotation %s to %s for ValidatingAdmissionPolicy %s: %v", key, value, policyName, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A workaround to fact that native types do not have TypeMeta populated, which
|
||||
// is needed for CEL expressions to be able to access the value.
|
||||
type wrappedParam struct {
|
||||
metav1.TypeMeta
|
||||
nested runtime.Object
|
||||
}
|
||||
|
||||
func (w *wrappedParam) MarshalJSON() ([]byte, error) {
|
||||
return nil, errors.New("MarshalJSON unimplemented for wrappedParam")
|
||||
}
|
||||
|
||||
func (w *wrappedParam) UnmarshalJSON(data []byte) error {
|
||||
return errors.New("UnmarshalJSON unimplemented for wrappedParam")
|
||||
}
|
||||
|
||||
func (w *wrappedParam) ToUnstructured() interface{} {
|
||||
res, err := runtime.DefaultUnstructuredConverter.ToUnstructured(w.nested)
|
||||
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
metaRes, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&w.TypeMeta)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
for k, v := range metaRes {
|
||||
res[k] = v
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func (w *wrappedParam) DeepCopyObject() runtime.Object {
|
||||
return &wrappedParam{
|
||||
TypeMeta: w.TypeMeta,
|
||||
nested: w.nested.DeepCopyObject(),
|
||||
}
|
||||
}
|
||||
|
||||
func (w *wrappedParam) GetObjectKind() schema.ObjectKind {
|
||||
return w
|
||||
}
|
38
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/validating/errors.go
generated
vendored
38
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/validating/errors.go
generated
vendored
@ -1,38 +0,0 @@
|
||||
/*
|
||||
Copyright 2024 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 validating
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
celmetrics "k8s.io/apiserver/pkg/admission/plugin/policy/validating/metrics"
|
||||
)
|
||||
|
||||
// ErrorType decodes the error to determine the error type
|
||||
// that the metrics understand.
|
||||
func ErrorType(decision *PolicyDecision) celmetrics.ValidationErrorType {
|
||||
if decision.Evaluation == EvalAdmit {
|
||||
return celmetrics.ValidationNoError
|
||||
}
|
||||
if strings.HasPrefix(decision.Message, "compilation") {
|
||||
return celmetrics.ValidationCompileError
|
||||
}
|
||||
if strings.HasPrefix(decision.Message, "validation failed due to running out of cost budget") {
|
||||
return celmetrics.ValidatingOutOfBudget
|
||||
}
|
||||
return celmetrics.ValidatingInvalidError
|
||||
}
|
31
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/validating/initializer.go
generated
vendored
31
vendor/k8s.io/apiserver/pkg/admission/plugin/policy/validating/initializer.go
generated
vendored
@ -1,31 +0,0 @@
|
||||
/*
|
||||
Copyright 2022 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 validating
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
)
|
||||
|
||||
type CELPolicyEvaluator interface {
|
||||
admission.InitializationValidator
|
||||
|
||||
Validate(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) error
|
||||
HasSynced() bool
|
||||
Run(stopCh <-chan struct{})
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user