mirror of https://github.com/knative/pkg.git
Introduce `duckv1.Pod`. (#2280)
This is mostly for symmetry since many folks that validate PodSpecable types often also want to validate `Pod`, so while `Pod` isn't as often a duck-type, the main value of this is exposing similar mechanisms to #2279 for `corev1.Pod` without folks needing to define their own `corev1.Pod` clone.
This commit is contained in:
parent
26db1ba732
commit
71508fc69a
|
@ -21,6 +21,7 @@ import (
|
|||
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
batchv1 "k8s.io/api/batch/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
|
||||
"knative.dev/pkg/apis/duck"
|
||||
"knative.dev/pkg/apis/duck/ducktypes"
|
||||
|
@ -33,6 +34,7 @@ func TestTypesImplements(t *testing.T) {
|
|||
}{
|
||||
{instance: &AddressableType{}, iface: &Addressable{}},
|
||||
{instance: &KResource{}, iface: &Conditions{}},
|
||||
{instance: &corev1.Pod{}, iface: &Pod{}},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
if err := duck.VerifyType(tc.instance, tc.iface); err != nil {
|
||||
|
|
|
@ -30,7 +30,7 @@ func (wp *WithPod) SetDefaults(ctx context.Context) {
|
|||
}
|
||||
}
|
||||
|
||||
// psvKey is used for associating a PodSpecDefaulter with a context.Context
|
||||
// psdKey is used for associating a PodSpecDefaulter with a context.Context
|
||||
type psdKey struct{}
|
||||
|
||||
func WithPodSpecDefaulter(ctx context.Context, psd PodSpecDefaulter) context.Context {
|
||||
|
@ -45,3 +45,29 @@ func GetPodSpecDefaulter(ctx context.Context) PodSpecDefaulter {
|
|||
}
|
||||
return untyped.(PodSpecDefaulter)
|
||||
}
|
||||
|
||||
// PodDefaulter is a callback to validate a Pod.
|
||||
type PodDefaulter func(context.Context, *Pod)
|
||||
|
||||
// SetDefaults implements apis.Defaultable
|
||||
func (p *Pod) SetDefaults(ctx context.Context) {
|
||||
if pd := GetPodDefaulter(ctx); pd != nil {
|
||||
pd(ctx, p)
|
||||
}
|
||||
}
|
||||
|
||||
// pdKey is used for associating a PodDefaulter with a context.Context
|
||||
type pdKey struct{}
|
||||
|
||||
func WithPodDefaulter(ctx context.Context, pd PodDefaulter) context.Context {
|
||||
return context.WithValue(ctx, pdKey{}, pd)
|
||||
}
|
||||
|
||||
// GetPodDefaulter extracts the PodDefaulter from the context.
|
||||
func GetPodDefaulter(ctx context.Context) PodDefaulter {
|
||||
untyped := ctx.Value(pdKey{})
|
||||
if untyped == nil {
|
||||
return nil
|
||||
}
|
||||
return untyped.(PodDefaulter)
|
||||
}
|
||||
|
|
|
@ -97,3 +97,68 @@ func TestPodSpecDefaulting(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPodDefaulting(t *testing.T) {
|
||||
p := Pod{
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{{
|
||||
Name: "blah",
|
||||
Image: "busybox",
|
||||
}},
|
||||
},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
with func(context.Context) context.Context
|
||||
want *Pod
|
||||
}{{
|
||||
name: "no check",
|
||||
with: func(ctx context.Context) context.Context {
|
||||
return ctx
|
||||
},
|
||||
want: p.DeepCopy(),
|
||||
}, {
|
||||
name: "no change",
|
||||
with: func(ctx context.Context) context.Context {
|
||||
return WithPodDefaulter(ctx, func(ctx context.Context, wp *Pod) {
|
||||
})
|
||||
},
|
||||
want: p.DeepCopy(),
|
||||
}, {
|
||||
name: "no busybox",
|
||||
with: func(ctx context.Context) context.Context {
|
||||
return WithPodDefaulter(ctx, func(ctx context.Context, wp *Pod) {
|
||||
for i, c := range wp.Spec.InitContainers {
|
||||
if !strings.Contains(c.Image, "@") {
|
||||
wp.Spec.InitContainers[i].Image = c.Image + "@sha256:deadbeef"
|
||||
}
|
||||
}
|
||||
for i, c := range wp.Spec.Containers {
|
||||
if !strings.Contains(c.Image, "@") {
|
||||
wp.Spec.Containers[i].Image = c.Image + "@sha256:deadbeef"
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
want: &Pod{
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{{
|
||||
Name: "blah",
|
||||
Image: "busybox@sha256:deadbeef",
|
||||
}},
|
||||
},
|
||||
},
|
||||
}}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
ctx := test.with(context.Background())
|
||||
got := p.DeepCopy()
|
||||
got.SetDefaults(ctx)
|
||||
if !cmp.Equal(test.want, got) {
|
||||
t.Errorf("SetDefaults (-want, +got) = %s", cmp.Diff(test.want, got))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,9 +45,6 @@ type WithPod struct {
|
|||
Spec WithPodSpec `json:"spec,omitempty"`
|
||||
}
|
||||
|
||||
var _ apis.Validatable = (*WithPod)(nil)
|
||||
var _ apis.Defaultable = (*WithPod)(nil)
|
||||
|
||||
// WithPodSpec is the shell around the PodSpecable within WithPod.
|
||||
type WithPodSpec struct {
|
||||
Template PodSpecable `json:"template,omitempty"`
|
||||
|
@ -55,6 +52,8 @@ type WithPodSpec struct {
|
|||
|
||||
// Verify WithPod resources meet duck contracts.
|
||||
var (
|
||||
_ apis.Validatable = (*WithPod)(nil)
|
||||
_ apis.Defaultable = (*WithPod)(nil)
|
||||
_ apis.Listable = (*WithPod)(nil)
|
||||
_ ducktypes.Populatable = (*WithPod)(nil)
|
||||
)
|
||||
|
@ -95,3 +94,52 @@ type WithPodList struct {
|
|||
|
||||
Items []WithPod `json:"items"`
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
|
||||
// Pod is a wrapper around Pod-like resource, which supports our interfaces
|
||||
// for webhooks
|
||||
type Pod struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
Spec corev1.PodSpec `json:"spec,omitempty"`
|
||||
}
|
||||
|
||||
// Verify Pod resources meet duck contracts.
|
||||
var (
|
||||
_ apis.Validatable = (*Pod)(nil)
|
||||
_ apis.Defaultable = (*Pod)(nil)
|
||||
_ apis.Listable = (*Pod)(nil)
|
||||
_ ducktypes.Populatable = (*Pod)(nil)
|
||||
)
|
||||
|
||||
// GetFullType implements duck.Implementable
|
||||
func (p *Pod) GetFullType() ducktypes.Populatable {
|
||||
return &Pod{}
|
||||
}
|
||||
|
||||
// Populate implements duck.Populatable
|
||||
func (p *Pod) Populate() {
|
||||
p.Spec = corev1.PodSpec{
|
||||
Containers: []corev1.Container{{
|
||||
Name: "container-name",
|
||||
Image: "container-image:latest",
|
||||
}},
|
||||
}
|
||||
}
|
||||
|
||||
// GetListType implements apis.Listable
|
||||
func (p *Pod) GetListType() runtime.Object {
|
||||
return &PodList{}
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
|
||||
// PodList is a list of WithPod resources
|
||||
type PodList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata"`
|
||||
|
||||
Items []WithPod `json:"items"`
|
||||
}
|
||||
|
|
|
@ -48,3 +48,30 @@ func GetPodSpecValidator(ctx context.Context) PodSpecValidator {
|
|||
}
|
||||
return untyped.(PodSpecValidator)
|
||||
}
|
||||
|
||||
// PodValidator is a callback to validate Pods.
|
||||
type PodValidator func(context.Context, *Pod) *apis.FieldError
|
||||
|
||||
// Validate implements apis.Validatable
|
||||
func (p *Pod) Validate(ctx context.Context) *apis.FieldError {
|
||||
if pv := GetPodValidator(ctx); pv != nil {
|
||||
return pv(ctx, p)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// pvKey is used for associating a PodValidator with a context.Context
|
||||
type pvKey struct{}
|
||||
|
||||
func WithPodValidator(ctx context.Context, pv PodValidator) context.Context {
|
||||
return context.WithValue(ctx, pvKey{}, pv)
|
||||
}
|
||||
|
||||
// GetPodValidator extracts the PodValidator from the context.
|
||||
func GetPodValidator(ctx context.Context) PodValidator {
|
||||
untyped := ctx.Value(pvKey{})
|
||||
if untyped == nil {
|
||||
return nil
|
||||
}
|
||||
return untyped.(PodValidator)
|
||||
}
|
||||
|
|
|
@ -85,3 +85,61 @@ func TestPodSpecValidation(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPodValidation(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
with func(context.Context) context.Context
|
||||
want *apis.FieldError
|
||||
}{{
|
||||
name: "no check",
|
||||
with: func(ctx context.Context) context.Context {
|
||||
return ctx
|
||||
},
|
||||
want: nil,
|
||||
}, {
|
||||
name: "no error",
|
||||
with: func(ctx context.Context) context.Context {
|
||||
return WithPodValidator(ctx, func(ctx context.Context, wp *Pod) *apis.FieldError {
|
||||
return nil
|
||||
})
|
||||
},
|
||||
want: nil,
|
||||
}, {
|
||||
name: "no busybox",
|
||||
with: func(ctx context.Context) context.Context {
|
||||
return WithPodValidator(ctx, func(ctx context.Context, wp *Pod) *apis.FieldError {
|
||||
for i, c := range wp.Spec.InitContainers {
|
||||
if c.Image == "busybox" {
|
||||
return apis.ErrInvalidValue(c.Image, "image").ViaFieldIndex("spec.template.spec.initContainers", i)
|
||||
}
|
||||
}
|
||||
for i, c := range wp.Spec.Containers {
|
||||
if c.Image == "busybox" {
|
||||
return apis.ErrInvalidValue(c.Image, "image").ViaFieldIndex("spec.template.spec.containers", i)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
},
|
||||
want: apis.ErrInvalidValue("busybox", "spec.template.spec.containers[0].image"),
|
||||
}}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
p := Pod{
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{{
|
||||
Name: "blah",
|
||||
Image: "busybox",
|
||||
}},
|
||||
},
|
||||
}
|
||||
ctx := test.with(context.Background())
|
||||
got := p.Validate(ctx)
|
||||
if test.want.Error() != got.Error() {
|
||||
t.Errorf("Validate() = %v, wanted %v", got, test.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -367,6 +367,66 @@ func (in *KResourceList) DeepCopyObject() runtime.Object {
|
|||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Pod) DeepCopyInto(out *Pod) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
in.Spec.DeepCopyInto(&out.Spec)
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Pod.
|
||||
func (in *Pod) DeepCopy() *Pod {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Pod)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *Pod) 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 *PodList) DeepCopyInto(out *PodList) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]WithPod, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PodList.
|
||||
func (in *PodList) DeepCopy() *PodList {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(PodList)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *PodList) 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 *PodSpecable) DeepCopyInto(out *PodSpecable) {
|
||||
*out = *in
|
||||
|
|
Loading…
Reference in New Issue