mirror of https://github.com/knative/pkg.git
duck cronjob (#2299)
This commit is contained in:
parent
f07c8e42ae
commit
221312a6a0
|
@ -0,0 +1,47 @@
|
||||||
|
/*
|
||||||
|
Copyright 2021 The Knative 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 (
|
||||||
|
"context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CronJobDefaulter is a callback to validate a CronJob.
|
||||||
|
type CronJobDefaulter func(context.Context, *CronJob)
|
||||||
|
|
||||||
|
// SetDefaults implements apis.Defaultable
|
||||||
|
func (c *CronJob) SetDefaults(ctx context.Context) {
|
||||||
|
if cd := GetCronJobDefaulter(ctx); cd != nil {
|
||||||
|
cd(ctx, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// cdKey is used for associating a CronJobDefaulter with a context.Context
|
||||||
|
type cdKey struct{}
|
||||||
|
|
||||||
|
func WithCronJobDefaulter(ctx context.Context, cd CronJobDefaulter) context.Context {
|
||||||
|
return context.WithValue(ctx, cdKey{}, cd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCronJobDefaulter extracts the CronJobDefaulter from the context.
|
||||||
|
func GetCronJobDefaulter(ctx context.Context) CronJobDefaulter {
|
||||||
|
untyped := ctx.Value(cdKey{})
|
||||||
|
if untyped == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return untyped.(CronJobDefaulter)
|
||||||
|
}
|
|
@ -0,0 +1,108 @@
|
||||||
|
/*
|
||||||
|
Copyright 2021 The Knative 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 (
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
batchv1 "k8s.io/api/batch/v1"
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCronJobDefaulting(t *testing.T) {
|
||||||
|
c := CronJob{
|
||||||
|
Spec: batchv1.CronJobSpec{
|
||||||
|
JobTemplate: batchv1.JobTemplateSpec{
|
||||||
|
Spec: batchv1.JobSpec{
|
||||||
|
Template: corev1.PodTemplateSpec{
|
||||||
|
Spec: corev1.PodSpec{
|
||||||
|
Containers: []corev1.Container{{
|
||||||
|
Name: "blah",
|
||||||
|
Image: "busybox",
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
with func(context.Context) context.Context
|
||||||
|
want *CronJob
|
||||||
|
}{{
|
||||||
|
name: "no check",
|
||||||
|
with: func(ctx context.Context) context.Context {
|
||||||
|
return ctx
|
||||||
|
},
|
||||||
|
want: c.DeepCopy(),
|
||||||
|
}, {
|
||||||
|
name: "no change",
|
||||||
|
with: func(ctx context.Context) context.Context {
|
||||||
|
return WithCronJobDefaulter(ctx, func(ctx context.Context, c *CronJob) {
|
||||||
|
})
|
||||||
|
},
|
||||||
|
want: c.DeepCopy(),
|
||||||
|
}, {
|
||||||
|
name: "no busybox",
|
||||||
|
with: func(ctx context.Context) context.Context {
|
||||||
|
return WithCronJobDefaulter(ctx, func(ctx context.Context, c *CronJob) {
|
||||||
|
for i, con := range c.Spec.JobTemplate.Spec.Template.Spec.InitContainers {
|
||||||
|
if !strings.Contains(con.Image, "@") {
|
||||||
|
c.Spec.JobTemplate.Spec.Template.Spec.InitContainers[i].Image = con.Image + "@sha256:deadbeef"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i, con := range c.Spec.JobTemplate.Spec.Template.Spec.Containers {
|
||||||
|
if !strings.Contains(con.Image, "@") {
|
||||||
|
c.Spec.JobTemplate.Spec.Template.Spec.Containers[i].Image = con.Image + "@sha256:deadbeef"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
want: &CronJob{
|
||||||
|
Spec: batchv1.CronJobSpec{
|
||||||
|
JobTemplate: batchv1.JobTemplateSpec{
|
||||||
|
Spec: batchv1.JobSpec{
|
||||||
|
Template: corev1.PodTemplateSpec{
|
||||||
|
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 := c.DeepCopy()
|
||||||
|
got.SetDefaults(ctx)
|
||||||
|
if !cmp.Equal(test.want, got) {
|
||||||
|
t.Errorf("SetDefaults (-want, +got) = %s", cmp.Diff(test.want, got))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,86 @@
|
||||||
|
/*
|
||||||
|
Copyright 2021 The Knative 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 (
|
||||||
|
batchv1 "k8s.io/api/batch/v1"
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
|
||||||
|
"knative.dev/pkg/apis"
|
||||||
|
"knative.dev/pkg/apis/duck/ducktypes"
|
||||||
|
)
|
||||||
|
|
||||||
|
// +genduck
|
||||||
|
|
||||||
|
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||||
|
|
||||||
|
// CronJob is a wrapper around CronJob resource, which supports our interfaces
|
||||||
|
// for webhooks
|
||||||
|
type CronJob struct {
|
||||||
|
metav1.TypeMeta `json:",inline"`
|
||||||
|
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||||
|
|
||||||
|
Spec batchv1.CronJobSpec `json:"spec,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify CronJob resources meet duck contracts.
|
||||||
|
var (
|
||||||
|
_ apis.Validatable = (*CronJob)(nil)
|
||||||
|
_ apis.Defaultable = (*CronJob)(nil)
|
||||||
|
_ apis.Listable = (*CronJob)(nil)
|
||||||
|
_ ducktypes.Populatable = (*CronJob)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetFullType implements duck.Implementable
|
||||||
|
func (c *CronJob) GetFullType() ducktypes.Populatable {
|
||||||
|
return &CronJob{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate implements duck.Populatable
|
||||||
|
func (c *CronJob) Populate() {
|
||||||
|
c.Spec = batchv1.CronJobSpec{
|
||||||
|
JobTemplate: batchv1.JobTemplateSpec{
|
||||||
|
Spec: batchv1.JobSpec{
|
||||||
|
Template: corev1.PodTemplateSpec{
|
||||||
|
Spec: corev1.PodSpec{
|
||||||
|
Containers: []corev1.Container{{
|
||||||
|
Name: "container-name",
|
||||||
|
Image: "container-image:latest",
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetListType implements apis.Listable
|
||||||
|
func (c *CronJob) GetListType() runtime.Object {
|
||||||
|
return &CronJob{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||||
|
|
||||||
|
// CronJobList is a list of CronJob resources
|
||||||
|
type CronJobList struct {
|
||||||
|
metav1.TypeMeta `json:",inline"`
|
||||||
|
metav1.ListMeta `json:"metadata"`
|
||||||
|
|
||||||
|
Items []CronJob `json:"items"`
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
/*
|
||||||
|
Copyright 2021 The Knative 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 (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"knative.dev/pkg/apis"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CronJobValidator is a callback to validate a CronJob.
|
||||||
|
type CronJobValidator func(context.Context, *CronJob) *apis.FieldError
|
||||||
|
|
||||||
|
// Validate implements apis.Validatable
|
||||||
|
func (c *CronJob) Validate(ctx context.Context) *apis.FieldError {
|
||||||
|
if cv := GetCronJobValidator(ctx); cv != nil {
|
||||||
|
return cv(ctx, c)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// cvKey is used for associating a CronJobValidator with a context.Context
|
||||||
|
type cvKey struct{}
|
||||||
|
|
||||||
|
func WithCronJobValidator(ctx context.Context, cv CronJobValidator) context.Context {
|
||||||
|
return context.WithValue(ctx, cvKey{}, cv)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCronJobValidator extracts the CronJobValidator from the context.
|
||||||
|
func GetCronJobValidator(ctx context.Context) CronJobValidator {
|
||||||
|
untyped := ctx.Value(cvKey{})
|
||||||
|
if untyped == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return untyped.(CronJobValidator)
|
||||||
|
}
|
|
@ -0,0 +1,92 @@
|
||||||
|
/*
|
||||||
|
Copyright 2021 The Knative 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 (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
batchv1 "k8s.io/api/batch/v1"
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
"knative.dev/pkg/apis"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCronJobValidation(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 WithCronJobValidator(ctx, func(ctx context.Context, c *CronJob) *apis.FieldError {
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
},
|
||||||
|
want: nil,
|
||||||
|
}, {
|
||||||
|
name: "no busybox",
|
||||||
|
with: func(ctx context.Context) context.Context {
|
||||||
|
return WithCronJobValidator(ctx, func(ctx context.Context, c *CronJob) *apis.FieldError {
|
||||||
|
for i, con := range c.Spec.JobTemplate.Spec.Template.Spec.InitContainers {
|
||||||
|
if con.Image == "busybox" {
|
||||||
|
return apis.ErrInvalidValue(con.Image, "image").ViaFieldIndex("spec.template.spec.initContainers", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i, con := range c.Spec.JobTemplate.Spec.Template.Spec.Containers {
|
||||||
|
if con.Image == "busybox" {
|
||||||
|
return apis.ErrInvalidValue(con.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) {
|
||||||
|
c := CronJob{
|
||||||
|
Spec: batchv1.CronJobSpec{
|
||||||
|
JobTemplate: batchv1.JobTemplateSpec{
|
||||||
|
Spec: batchv1.JobSpec{
|
||||||
|
Template: corev1.PodTemplateSpec{
|
||||||
|
Spec: corev1.PodSpec{
|
||||||
|
Containers: []corev1.Container{{
|
||||||
|
Name: "blah",
|
||||||
|
Image: "busybox",
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
ctx := test.with(context.Background())
|
||||||
|
got := c.Validate(ctx)
|
||||||
|
if test.want.Error() != got.Error() {
|
||||||
|
t.Errorf("Validate() = %v, wanted %v", got, test.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -265,6 +265,66 @@ func (in Conditions) DeepCopy() Conditions {
|
||||||
return *out
|
return *out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *CronJob) DeepCopyInto(out *CronJob) {
|
||||||
|
*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 CronJob.
|
||||||
|
func (in *CronJob) DeepCopy() *CronJob {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(CronJob)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||||
|
func (in *CronJob) 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 *CronJobList) DeepCopyInto(out *CronJobList) {
|
||||||
|
*out = *in
|
||||||
|
out.TypeMeta = in.TypeMeta
|
||||||
|
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||||
|
if in.Items != nil {
|
||||||
|
in, out := &in.Items, &out.Items
|
||||||
|
*out = make([]CronJob, len(*in))
|
||||||
|
for i := range *in {
|
||||||
|
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CronJobList.
|
||||||
|
func (in *CronJobList) DeepCopy() *CronJobList {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(CronJobList)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||||
|
func (in *CronJobList) 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.
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
func (in *Destination) DeepCopyInto(out *Destination) {
|
func (in *Destination) DeepCopyInto(out *Destination) {
|
||||||
*out = *in
|
*out = *in
|
||||||
|
|
Loading…
Reference in New Issue