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
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (in *Destination) DeepCopyInto(out *Destination) {
|
||||
*out = *in
|
||||
|
|
Loading…
Reference in New Issue