mirror of https://github.com/knative/caching.git
change status to duckv1.status, add reconciler framework (#454)
* add reconciler for imagecache * change status to duckv1.status * fix UT * fix static check * fix isReady * add UT case for generation
This commit is contained in:
parent
bfe63795ed
commit
a6535156c8
1
go.mod
1
go.mod
|
@ -12,6 +12,7 @@ require (
|
|||
github.com/tsenart/go-tsz v0.0.0-20180814235614-0bd30b3df1c3 // indirect
|
||||
github.com/tsenart/vegeta v12.7.1-0.20190725001342-b5f4fca92137+incompatible
|
||||
go.uber.org/multierr v1.6.0 // indirect
|
||||
go.uber.org/zap v1.16.0
|
||||
k8s.io/api v0.19.7
|
||||
k8s.io/apimachinery v0.19.7
|
||||
k8s.io/client-go v0.19.7
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
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 v1alpha1
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
duckv1 "knative.dev/pkg/apis/duck/v1"
|
||||
|
||||
"knative.dev/pkg/apis"
|
||||
)
|
||||
|
||||
const (
|
||||
// ImageConditionReady is set when the revision is starting to materialize
|
||||
// runtime resources, and becomes true when those resources are ready.
|
||||
ImageConditionReady = apis.ConditionReady
|
||||
)
|
||||
|
||||
var condSet = apis.NewLivingConditionSet()
|
||||
|
||||
// GetGroupVersionKind implements kmeta.OwnerRefable
|
||||
func (i *Image) GetGroupVersionKind() schema.GroupVersionKind {
|
||||
return SchemeGroupVersion.WithKind("Image")
|
||||
}
|
||||
|
||||
// GetConditionSet retrieves the condition set for this resource. Implements the KRShaped interface.
|
||||
func (i *Image) GetConditionSet() apis.ConditionSet {
|
||||
return condSet
|
||||
}
|
||||
|
||||
// InitializeConditions sets the initial values to the conditions.
|
||||
func (is *ImageStatus) InitializeConditions() {
|
||||
condSet.Manage(is).InitializeConditions()
|
||||
}
|
||||
|
||||
// IsReady looks at the conditions and if the Status has a condition
|
||||
// ImageConditionReady returns true if ConditionStatus is True
|
||||
func (i *Image) IsReady() bool {
|
||||
is := i.Status
|
||||
return is.ObservedGeneration == i.Generation &&
|
||||
is.GetCondition(ImageConditionReady).IsTrue()
|
||||
}
|
||||
|
||||
// GetStatus retrieves the status of the Image. Implements the KRShaped interface.
|
||||
func (t *Image) GetStatus() *duckv1.Status {
|
||||
return &t.Status.Status
|
||||
}
|
|
@ -0,0 +1,163 @@
|
|||
/*
|
||||
Copyright 2018 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 v1alpha1
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
duckv1 "knative.dev/pkg/apis/duck/v1"
|
||||
)
|
||||
|
||||
func TestIsReady(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
generation int64
|
||||
status ImageStatus
|
||||
isReady bool
|
||||
}{{
|
||||
name: "empty status should not be ready",
|
||||
status: ImageStatus{},
|
||||
isReady: false,
|
||||
}, {
|
||||
name: "Different condition type should not be ready",
|
||||
generation: 0,
|
||||
status: ImageStatus{
|
||||
Status: duckv1.Status{
|
||||
ObservedGeneration: 0,
|
||||
Conditions: duckv1.Conditions{{
|
||||
Type: "foo",
|
||||
Status: corev1.ConditionTrue,
|
||||
},
|
||||
}},
|
||||
},
|
||||
isReady: false,
|
||||
}, {
|
||||
name: "False condition status should not be ready",
|
||||
generation: 0,
|
||||
status: ImageStatus{
|
||||
Status: duckv1.Status{
|
||||
ObservedGeneration: 0,
|
||||
Conditions: duckv1.Conditions{{
|
||||
Type: ImageConditionReady,
|
||||
Status: corev1.ConditionFalse,
|
||||
}},
|
||||
},
|
||||
},
|
||||
isReady: false,
|
||||
}, {
|
||||
name: "Unknown condition status should not be ready",
|
||||
generation: 0,
|
||||
status: ImageStatus{
|
||||
Status: duckv1.Status{
|
||||
ObservedGeneration: 0,
|
||||
Conditions: duckv1.Conditions{{
|
||||
Type: ImageConditionReady,
|
||||
Status: corev1.ConditionUnknown,
|
||||
}},
|
||||
},
|
||||
},
|
||||
isReady: false,
|
||||
}, {
|
||||
name: "Missing condition status should not be ready",
|
||||
generation: 0,
|
||||
status: ImageStatus{
|
||||
Status: duckv1.Status{
|
||||
ObservedGeneration: 0,
|
||||
Conditions: duckv1.Conditions{{
|
||||
Type: ImageConditionReady,
|
||||
}},
|
||||
},
|
||||
},
|
||||
isReady: false,
|
||||
}, {
|
||||
name: "True condition status should be ready",
|
||||
generation: 0,
|
||||
status: ImageStatus{
|
||||
Status: duckv1.Status{
|
||||
ObservedGeneration: 0,
|
||||
Conditions: duckv1.Conditions{{
|
||||
Type: ImageConditionReady,
|
||||
Status: corev1.ConditionTrue,
|
||||
}},
|
||||
},
|
||||
},
|
||||
isReady: true,
|
||||
}, {
|
||||
name: "Multiple conditions with ready status should be ready",
|
||||
generation: 0,
|
||||
status: ImageStatus{
|
||||
Status: duckv1.Status{
|
||||
ObservedGeneration: 0,
|
||||
Conditions: duckv1.Conditions{{
|
||||
Type: "foo",
|
||||
Status: corev1.ConditionTrue,
|
||||
}, {
|
||||
Type: ImageConditionReady,
|
||||
Status: corev1.ConditionTrue,
|
||||
}},
|
||||
},
|
||||
},
|
||||
isReady: true,
|
||||
}, {
|
||||
name: "Multiple conditions with ready status false should not be ready",
|
||||
generation: 0,
|
||||
status: ImageStatus{
|
||||
Status: duckv1.Status{
|
||||
ObservedGeneration: 0,
|
||||
Conditions: duckv1.Conditions{{
|
||||
Type: "foo",
|
||||
Status: corev1.ConditionTrue,
|
||||
}, {
|
||||
Type: ImageConditionReady,
|
||||
Status: corev1.ConditionFalse,
|
||||
}},
|
||||
},
|
||||
},
|
||||
isReady: false,
|
||||
}, {
|
||||
name: "Generation not equal ObservedGeneration should not be ready",
|
||||
generation: 1,
|
||||
status: ImageStatus{
|
||||
Status: duckv1.Status{
|
||||
ObservedGeneration: 2,
|
||||
Conditions: duckv1.Conditions{{
|
||||
Type: "foo",
|
||||
Status: corev1.ConditionTrue,
|
||||
}, {
|
||||
Type: ImageConditionReady,
|
||||
Status: corev1.ConditionFalse,
|
||||
}},
|
||||
},
|
||||
},
|
||||
isReady: false,
|
||||
}}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
m := Image{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Generation: tc.generation,
|
||||
},
|
||||
Status: tc.status}
|
||||
if e, a := tc.isReady, m.IsReady(); e != a {
|
||||
t.Errorf("Ready = %v, want: %v", a, e)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -17,19 +17,16 @@ limitations under the License.
|
|||
package v1alpha1
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
|
||||
"knative.dev/pkg/apis"
|
||||
duckv1 "knative.dev/pkg/apis/duck/v1"
|
||||
"knative.dev/pkg/kmeta"
|
||||
)
|
||||
|
||||
// +genclient
|
||||
// +genreconciler
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
|
||||
// Image is a Knative abstraction that encapsulates the interface by which Knative
|
||||
|
@ -52,6 +49,7 @@ type Image struct {
|
|||
var _ apis.Validatable = (*Image)(nil)
|
||||
var _ apis.Defaultable = (*Image)(nil)
|
||||
var _ kmeta.OwnerRefable = (*Image)(nil)
|
||||
var _ duckv1.KRShaped = (*Image)(nil)
|
||||
|
||||
// ImageSpec holds the desired state of the Image (from the client).
|
||||
type ImageSpec struct {
|
||||
|
@ -72,41 +70,9 @@ type ImageSpec struct {
|
|||
ImagePullSecrets []corev1.LocalObjectReference `json:"imagePullSecrets,omitempty"`
|
||||
}
|
||||
|
||||
// ImageConditionType is used to communicate the status of the reconciliation process.
|
||||
type ImageConditionType string
|
||||
|
||||
const (
|
||||
// ImageConditionReady is set when the revision is starting to materialize
|
||||
// runtime resources, and becomes true when those resources are ready.
|
||||
ImageConditionReady ImageConditionType = "Ready"
|
||||
)
|
||||
|
||||
// ImageCondition defines a readiness condition for a Image.
|
||||
// See: https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#typical-status-properties
|
||||
type ImageCondition struct {
|
||||
Type ImageConditionType `json:"type" description:"type of Image condition"`
|
||||
|
||||
Status corev1.ConditionStatus `json:"status" description:"status of the condition, one of True, False, Unknown"`
|
||||
|
||||
// +optional
|
||||
// We use VolatileTime in place of metav1.Time to exclude this from creating equality.Semantic
|
||||
// differences (all other things held constant).
|
||||
LastTransitionTime apis.VolatileTime `json:"lastTransitionTime,omitempty" description:"last time the condition transit from one status to another"`
|
||||
|
||||
// +optional
|
||||
Reason string `json:"reason,omitempty" description:"one-word CamelCase reason for the condition's last transition"`
|
||||
|
||||
// +optional
|
||||
Message string `json:"message,omitempty" description:"human-readable message indicating details about last transition"`
|
||||
}
|
||||
|
||||
// ImageStatus communicates the observed state of the Image (from the controller).
|
||||
type ImageStatus struct {
|
||||
// Conditions communicates information about ongoing/complete
|
||||
// reconciliation processes that bring the "spec" inline with the observed
|
||||
// state of the world.
|
||||
// +optional
|
||||
Conditions []ImageCondition `json:"conditions,omitempty"`
|
||||
duckv1.Status `json:",inline"`
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
|
@ -118,65 +84,3 @@ type ImageList struct {
|
|||
|
||||
Items []Image `json:"items"`
|
||||
}
|
||||
|
||||
// IsReady looks at the conditions and if the Status has a condition
|
||||
// ImageConditionReady returns true if ConditionStatus is True
|
||||
func (rs *ImageStatus) IsReady() bool {
|
||||
if c := rs.GetCondition(ImageConditionReady); c != nil {
|
||||
return c.Status == corev1.ConditionTrue
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (rs *ImageStatus) GetCondition(t ImageConditionType) *ImageCondition {
|
||||
for _, cond := range rs.Conditions {
|
||||
if cond.Type == t {
|
||||
return &cond
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (rs *ImageStatus) SetCondition(new *ImageCondition) {
|
||||
if new == nil {
|
||||
return
|
||||
}
|
||||
|
||||
t := new.Type
|
||||
var conditions []ImageCondition
|
||||
for _, cond := range rs.Conditions {
|
||||
if cond.Type != t {
|
||||
conditions = append(conditions, cond)
|
||||
} else {
|
||||
// If we'd only update the LastTransitionTime, then return.
|
||||
new.LastTransitionTime = cond.LastTransitionTime
|
||||
if reflect.DeepEqual(new, &cond) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
new.LastTransitionTime = apis.VolatileTime{
|
||||
Inner: metav1.NewTime(time.Now()),
|
||||
}
|
||||
conditions = append(conditions, *new)
|
||||
// Deterministically order the conditions
|
||||
sort.Slice(conditions, func(i, j int) bool { return conditions[i].Type < conditions[j].Type })
|
||||
rs.Conditions = conditions
|
||||
}
|
||||
|
||||
func (rs *ImageStatus) InitializeConditions() {
|
||||
for _, cond := range []ImageConditionType{
|
||||
ImageConditionReady,
|
||||
} {
|
||||
if rc := rs.GetCondition(cond); rc == nil {
|
||||
rs.SetCondition(&ImageCondition{
|
||||
Type: cond,
|
||||
Status: corev1.ConditionUnknown,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (i *Image) GetGroupVersionKind() schema.GroupVersionKind {
|
||||
return SchemeGroupVersion.WithKind("Image")
|
||||
}
|
||||
|
|
|
@ -1,169 +0,0 @@
|
|||
/*
|
||||
Copyright 2018 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 v1alpha1
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"knative.dev/pkg/apis"
|
||||
)
|
||||
|
||||
func TestIsReady(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
status ImageStatus
|
||||
isReady bool
|
||||
}{{
|
||||
name: "empty status should not be ready",
|
||||
status: ImageStatus{},
|
||||
isReady: false,
|
||||
}, {
|
||||
name: "Different condition type should not be ready",
|
||||
status: ImageStatus{
|
||||
Conditions: []ImageCondition{{
|
||||
Type: "foo",
|
||||
Status: corev1.ConditionTrue,
|
||||
}},
|
||||
},
|
||||
isReady: false,
|
||||
}, {
|
||||
name: "False condition status should not be ready",
|
||||
status: ImageStatus{
|
||||
Conditions: []ImageCondition{{
|
||||
Type: ImageConditionReady,
|
||||
Status: corev1.ConditionFalse,
|
||||
}},
|
||||
},
|
||||
isReady: false,
|
||||
}, {
|
||||
name: "Unknown condition status should not be ready",
|
||||
status: ImageStatus{
|
||||
Conditions: []ImageCondition{{
|
||||
Type: ImageConditionReady,
|
||||
Status: corev1.ConditionUnknown,
|
||||
}},
|
||||
},
|
||||
isReady: false,
|
||||
}, {
|
||||
name: "Missing condition status should not be ready",
|
||||
status: ImageStatus{
|
||||
Conditions: []ImageCondition{{
|
||||
Type: ImageConditionReady,
|
||||
}},
|
||||
},
|
||||
isReady: false,
|
||||
}, {
|
||||
name: "True condition status should be ready",
|
||||
status: ImageStatus{
|
||||
Conditions: []ImageCondition{{
|
||||
Type: ImageConditionReady,
|
||||
Status: corev1.ConditionTrue,
|
||||
}},
|
||||
},
|
||||
isReady: true,
|
||||
}, {
|
||||
name: "Multiple conditions with ready status should be ready",
|
||||
status: ImageStatus{
|
||||
Conditions: []ImageCondition{{
|
||||
Type: "foo",
|
||||
Status: corev1.ConditionTrue,
|
||||
}, {
|
||||
Type: ImageConditionReady,
|
||||
Status: corev1.ConditionTrue,
|
||||
}},
|
||||
},
|
||||
isReady: true,
|
||||
}, {
|
||||
name: "Multiple conditions with ready status false should not be ready",
|
||||
status: ImageStatus{
|
||||
Conditions: []ImageCondition{{
|
||||
Type: "foo",
|
||||
Status: corev1.ConditionTrue,
|
||||
}, {
|
||||
Type: ImageConditionReady,
|
||||
Status: corev1.ConditionFalse,
|
||||
}},
|
||||
},
|
||||
isReady: false,
|
||||
}}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
if e, a := tc.isReady, tc.status.IsReady(); e != a {
|
||||
t.Errorf("%q expected: %v got: %v", tc.name, e, a)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestImageConditions(t *testing.T) {
|
||||
rev := &Image{}
|
||||
foo := &ImageCondition{
|
||||
Type: "Foo",
|
||||
Status: "True",
|
||||
}
|
||||
bar := &ImageCondition{
|
||||
Type: "Bar",
|
||||
Status: "True",
|
||||
}
|
||||
|
||||
// Add a new condition.
|
||||
rev.Status.SetCondition(foo)
|
||||
|
||||
if got, want := len(rev.Status.Conditions), 1; got != want {
|
||||
t.Fatalf("Unexpected Condition length; got %d, want %d", got, want)
|
||||
}
|
||||
|
||||
// Add nothing
|
||||
rev.Status.SetCondition(nil)
|
||||
|
||||
if got, want := len(rev.Status.Conditions), 1; got != want {
|
||||
t.Fatalf("Unexpected Condition length; got %d, want %d", got, want)
|
||||
}
|
||||
|
||||
// Add a second condition.
|
||||
rev.Status.SetCondition(bar)
|
||||
|
||||
if got, want := len(rev.Status.Conditions), 2; got != want {
|
||||
t.Fatalf("Unexpected Condition length; got %d, want %d", got, want)
|
||||
}
|
||||
|
||||
// Add nil condition.
|
||||
rev.Status.SetCondition(nil)
|
||||
|
||||
if got, want := len(rev.Status.Conditions), 2; got != want {
|
||||
t.Fatalf("Unexpected Condition length; got %d, want %d", got, want)
|
||||
}
|
||||
|
||||
// Add a condition that varies only in LastTransitionTime, and check that
|
||||
// things are not updated.
|
||||
bar = rev.Status.GetCondition("Bar")
|
||||
bar2 := bar.DeepCopy()
|
||||
bar2.LastTransitionTime = apis.VolatileTime{
|
||||
Inner: metav1.NewTime(time.Unix(1234, 0)),
|
||||
}
|
||||
rev.Status.SetCondition(bar2)
|
||||
|
||||
got, want := rev.Status.GetCondition("Bar"), bar
|
||||
if diff := cmp.Diff(want, got); diff != "" {
|
||||
t.Error("Unexpected traffic diff (-want +got):", diff)
|
||||
}
|
||||
}
|
|
@ -53,23 +53,6 @@ func (in *Image) DeepCopyObject() runtime.Object {
|
|||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ImageCondition) DeepCopyInto(out *ImageCondition) {
|
||||
*out = *in
|
||||
in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime)
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ImageCondition.
|
||||
func (in *ImageCondition) DeepCopy() *ImageCondition {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ImageCondition)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ImageList) DeepCopyInto(out *ImageList) {
|
||||
*out = *in
|
||||
|
@ -127,13 +110,7 @@ func (in *ImageSpec) DeepCopy() *ImageSpec {
|
|||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ImageStatus) DeepCopyInto(out *ImageStatus) {
|
||||
*out = *in
|
||||
if in.Conditions != nil {
|
||||
in, out := &in.Conditions, &out.Conditions
|
||||
*out = make([]ImageCondition, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
in.Status.DeepCopyInto(&out.Status)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,153 @@
|
|||
/*
|
||||
Copyright 2020 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.
|
||||
*/
|
||||
|
||||
// Code generated by injection-gen. DO NOT EDIT.
|
||||
|
||||
package image
|
||||
|
||||
import (
|
||||
context "context"
|
||||
fmt "fmt"
|
||||
reflect "reflect"
|
||||
strings "strings"
|
||||
|
||||
zap "go.uber.org/zap"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
labels "k8s.io/apimachinery/pkg/labels"
|
||||
types "k8s.io/apimachinery/pkg/types"
|
||||
watch "k8s.io/apimachinery/pkg/watch"
|
||||
scheme "k8s.io/client-go/kubernetes/scheme"
|
||||
v1 "k8s.io/client-go/kubernetes/typed/core/v1"
|
||||
record "k8s.io/client-go/tools/record"
|
||||
versionedscheme "knative.dev/caching/pkg/client/clientset/versioned/scheme"
|
||||
client "knative.dev/caching/pkg/client/injection/client"
|
||||
image "knative.dev/caching/pkg/client/injection/informers/caching/v1alpha1/image"
|
||||
kubeclient "knative.dev/pkg/client/injection/kube/client"
|
||||
controller "knative.dev/pkg/controller"
|
||||
logging "knative.dev/pkg/logging"
|
||||
logkey "knative.dev/pkg/logging/logkey"
|
||||
reconciler "knative.dev/pkg/reconciler"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultControllerAgentName = "image-controller"
|
||||
defaultFinalizerName = "images.caching.internal.knative.dev"
|
||||
)
|
||||
|
||||
// NewImpl returns a controller.Impl that handles queuing and feeding work from
|
||||
// the queue through an implementation of controller.Reconciler, delegating to
|
||||
// the provided Interface and optional Finalizer methods. OptionsFn is used to return
|
||||
// controller.Options to be used by the internal reconciler.
|
||||
func NewImpl(ctx context.Context, r Interface, optionsFns ...controller.OptionsFn) *controller.Impl {
|
||||
logger := logging.FromContext(ctx)
|
||||
|
||||
// Check the options function input. It should be 0 or 1.
|
||||
if len(optionsFns) > 1 {
|
||||
logger.Fatal("Up to one options function is supported, found: ", len(optionsFns))
|
||||
}
|
||||
|
||||
imageInformer := image.Get(ctx)
|
||||
|
||||
lister := imageInformer.Lister()
|
||||
|
||||
rec := &reconcilerImpl{
|
||||
LeaderAwareFuncs: reconciler.LeaderAwareFuncs{
|
||||
PromoteFunc: func(bkt reconciler.Bucket, enq func(reconciler.Bucket, types.NamespacedName)) error {
|
||||
all, err := lister.List(labels.Everything())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, elt := range all {
|
||||
// TODO: Consider letting users specify a filter in options.
|
||||
enq(bkt, types.NamespacedName{
|
||||
Namespace: elt.GetNamespace(),
|
||||
Name: elt.GetName(),
|
||||
})
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
Client: client.Get(ctx),
|
||||
Lister: lister,
|
||||
reconciler: r,
|
||||
finalizerName: defaultFinalizerName,
|
||||
}
|
||||
|
||||
ctrType := reflect.TypeOf(r).Elem()
|
||||
ctrTypeName := fmt.Sprintf("%s.%s", ctrType.PkgPath(), ctrType.Name())
|
||||
ctrTypeName = strings.ReplaceAll(ctrTypeName, "/", ".")
|
||||
|
||||
logger = logger.With(
|
||||
zap.String(logkey.ControllerType, ctrTypeName),
|
||||
zap.String(logkey.Kind, "caching.internal.knative.dev.Image"),
|
||||
)
|
||||
|
||||
impl := controller.NewImpl(rec, logger, ctrTypeName)
|
||||
agentName := defaultControllerAgentName
|
||||
|
||||
// Pass impl to the options. Save any optional results.
|
||||
for _, fn := range optionsFns {
|
||||
opts := fn(impl)
|
||||
if opts.ConfigStore != nil {
|
||||
rec.configStore = opts.ConfigStore
|
||||
}
|
||||
if opts.FinalizerName != "" {
|
||||
rec.finalizerName = opts.FinalizerName
|
||||
}
|
||||
if opts.AgentName != "" {
|
||||
agentName = opts.AgentName
|
||||
}
|
||||
if opts.SkipStatusUpdates {
|
||||
rec.skipStatusUpdates = true
|
||||
}
|
||||
if opts.DemoteFunc != nil {
|
||||
rec.DemoteFunc = opts.DemoteFunc
|
||||
}
|
||||
}
|
||||
|
||||
rec.Recorder = createRecorder(ctx, agentName)
|
||||
|
||||
return impl
|
||||
}
|
||||
|
||||
func createRecorder(ctx context.Context, agentName string) record.EventRecorder {
|
||||
logger := logging.FromContext(ctx)
|
||||
|
||||
recorder := controller.GetEventRecorder(ctx)
|
||||
if recorder == nil {
|
||||
// Create event broadcaster
|
||||
logger.Debug("Creating event broadcaster")
|
||||
eventBroadcaster := record.NewBroadcaster()
|
||||
watches := []watch.Interface{
|
||||
eventBroadcaster.StartLogging(logger.Named("event-broadcaster").Infof),
|
||||
eventBroadcaster.StartRecordingToSink(
|
||||
&v1.EventSinkImpl{Interface: kubeclient.Get(ctx).CoreV1().Events("")}),
|
||||
}
|
||||
recorder = eventBroadcaster.NewRecorder(scheme.Scheme, corev1.EventSource{Component: agentName})
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
for _, w := range watches {
|
||||
w.Stop()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
return recorder
|
||||
}
|
||||
|
||||
func init() {
|
||||
versionedscheme.AddToScheme(scheme.Scheme)
|
||||
}
|
|
@ -0,0 +1,449 @@
|
|||
/*
|
||||
Copyright 2020 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.
|
||||
*/
|
||||
|
||||
// Code generated by injection-gen. DO NOT EDIT.
|
||||
|
||||
package image
|
||||
|
||||
import (
|
||||
context "context"
|
||||
json "encoding/json"
|
||||
fmt "fmt"
|
||||
reflect "reflect"
|
||||
|
||||
zap "go.uber.org/zap"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
equality "k8s.io/apimachinery/pkg/api/equality"
|
||||
errors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
labels "k8s.io/apimachinery/pkg/labels"
|
||||
types "k8s.io/apimachinery/pkg/types"
|
||||
sets "k8s.io/apimachinery/pkg/util/sets"
|
||||
record "k8s.io/client-go/tools/record"
|
||||
v1alpha1 "knative.dev/caching/pkg/apis/caching/v1alpha1"
|
||||
versioned "knative.dev/caching/pkg/client/clientset/versioned"
|
||||
cachingv1alpha1 "knative.dev/caching/pkg/client/listers/caching/v1alpha1"
|
||||
controller "knative.dev/pkg/controller"
|
||||
kmp "knative.dev/pkg/kmp"
|
||||
logging "knative.dev/pkg/logging"
|
||||
reconciler "knative.dev/pkg/reconciler"
|
||||
)
|
||||
|
||||
// Interface defines the strongly typed interfaces to be implemented by a
|
||||
// controller reconciling v1alpha1.Image.
|
||||
type Interface interface {
|
||||
// ReconcileKind implements custom logic to reconcile v1alpha1.Image. Any changes
|
||||
// to the objects .Status or .Finalizers will be propagated to the stored
|
||||
// object. It is recommended that implementors do not call any update calls
|
||||
// for the Kind inside of ReconcileKind, it is the responsibility of the calling
|
||||
// controller to propagate those properties. The resource passed to ReconcileKind
|
||||
// will always have an empty deletion timestamp.
|
||||
ReconcileKind(ctx context.Context, o *v1alpha1.Image) reconciler.Event
|
||||
}
|
||||
|
||||
// Finalizer defines the strongly typed interfaces to be implemented by a
|
||||
// controller finalizing v1alpha1.Image.
|
||||
type Finalizer interface {
|
||||
// FinalizeKind implements custom logic to finalize v1alpha1.Image. Any changes
|
||||
// to the objects .Status or .Finalizers will be ignored. Returning a nil or
|
||||
// Normal type reconciler.Event will allow the finalizer to be deleted on
|
||||
// the resource. The resource passed to FinalizeKind will always have a set
|
||||
// deletion timestamp.
|
||||
FinalizeKind(ctx context.Context, o *v1alpha1.Image) reconciler.Event
|
||||
}
|
||||
|
||||
// ReadOnlyInterface defines the strongly typed interfaces to be implemented by a
|
||||
// controller reconciling v1alpha1.Image if they want to process resources for which
|
||||
// they are not the leader.
|
||||
type ReadOnlyInterface interface {
|
||||
// ObserveKind implements logic to observe v1alpha1.Image.
|
||||
// This method should not write to the API.
|
||||
ObserveKind(ctx context.Context, o *v1alpha1.Image) reconciler.Event
|
||||
}
|
||||
|
||||
// ReadOnlyFinalizer defines the strongly typed interfaces to be implemented by a
|
||||
// controller finalizing v1alpha1.Image if they want to process tombstoned resources
|
||||
// even when they are not the leader. Due to the nature of how finalizers are handled
|
||||
// there are no guarantees that this will be called.
|
||||
type ReadOnlyFinalizer interface {
|
||||
// ObserveFinalizeKind implements custom logic to observe the final state of v1alpha1.Image.
|
||||
// This method should not write to the API.
|
||||
ObserveFinalizeKind(ctx context.Context, o *v1alpha1.Image) reconciler.Event
|
||||
}
|
||||
|
||||
type doReconcile func(ctx context.Context, o *v1alpha1.Image) reconciler.Event
|
||||
|
||||
// reconcilerImpl implements controller.Reconciler for v1alpha1.Image resources.
|
||||
type reconcilerImpl struct {
|
||||
// LeaderAwareFuncs is inlined to help us implement reconciler.LeaderAware.
|
||||
reconciler.LeaderAwareFuncs
|
||||
|
||||
// Client is used to write back status updates.
|
||||
Client versioned.Interface
|
||||
|
||||
// Listers index properties about resources.
|
||||
Lister cachingv1alpha1.ImageLister
|
||||
|
||||
// Recorder is an event recorder for recording Event resources to the
|
||||
// Kubernetes API.
|
||||
Recorder record.EventRecorder
|
||||
|
||||
// configStore allows for decorating a context with config maps.
|
||||
// +optional
|
||||
configStore reconciler.ConfigStore
|
||||
|
||||
// reconciler is the implementation of the business logic of the resource.
|
||||
reconciler Interface
|
||||
|
||||
// finalizerName is the name of the finalizer to reconcile.
|
||||
finalizerName string
|
||||
|
||||
// skipStatusUpdates configures whether or not this reconciler automatically updates
|
||||
// the status of the reconciled resource.
|
||||
skipStatusUpdates bool
|
||||
}
|
||||
|
||||
// Check that our Reconciler implements controller.Reconciler.
|
||||
var _ controller.Reconciler = (*reconcilerImpl)(nil)
|
||||
|
||||
// Check that our generated Reconciler is always LeaderAware.
|
||||
var _ reconciler.LeaderAware = (*reconcilerImpl)(nil)
|
||||
|
||||
func NewReconciler(ctx context.Context, logger *zap.SugaredLogger, client versioned.Interface, lister cachingv1alpha1.ImageLister, recorder record.EventRecorder, r Interface, options ...controller.Options) controller.Reconciler {
|
||||
// Check the options function input. It should be 0 or 1.
|
||||
if len(options) > 1 {
|
||||
logger.Fatal("Up to one options struct is supported, found: ", len(options))
|
||||
}
|
||||
|
||||
// Fail fast when users inadvertently implement the other LeaderAware interface.
|
||||
// For the typed reconcilers, Promote shouldn't take any arguments.
|
||||
if _, ok := r.(reconciler.LeaderAware); ok {
|
||||
logger.Fatalf("%T implements the incorrect LeaderAware interface. Promote() should not take an argument as genreconciler handles the enqueuing automatically.", r)
|
||||
}
|
||||
// TODO: Consider validating when folks implement ReadOnlyFinalizer, but not Finalizer.
|
||||
|
||||
rec := &reconcilerImpl{
|
||||
LeaderAwareFuncs: reconciler.LeaderAwareFuncs{
|
||||
PromoteFunc: func(bkt reconciler.Bucket, enq func(reconciler.Bucket, types.NamespacedName)) error {
|
||||
all, err := lister.List(labels.Everything())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, elt := range all {
|
||||
// TODO: Consider letting users specify a filter in options.
|
||||
enq(bkt, types.NamespacedName{
|
||||
Namespace: elt.GetNamespace(),
|
||||
Name: elt.GetName(),
|
||||
})
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
Client: client,
|
||||
Lister: lister,
|
||||
Recorder: recorder,
|
||||
reconciler: r,
|
||||
finalizerName: defaultFinalizerName,
|
||||
}
|
||||
|
||||
for _, opts := range options {
|
||||
if opts.ConfigStore != nil {
|
||||
rec.configStore = opts.ConfigStore
|
||||
}
|
||||
if opts.FinalizerName != "" {
|
||||
rec.finalizerName = opts.FinalizerName
|
||||
}
|
||||
if opts.SkipStatusUpdates {
|
||||
rec.skipStatusUpdates = true
|
||||
}
|
||||
if opts.DemoteFunc != nil {
|
||||
rec.DemoteFunc = opts.DemoteFunc
|
||||
}
|
||||
}
|
||||
|
||||
return rec
|
||||
}
|
||||
|
||||
// Reconcile implements controller.Reconciler
|
||||
func (r *reconcilerImpl) Reconcile(ctx context.Context, key string) error {
|
||||
logger := logging.FromContext(ctx)
|
||||
|
||||
// Initialize the reconciler state. This will convert the namespace/name
|
||||
// string into a distinct namespace and name, determine if this instance of
|
||||
// the reconciler is the leader, and any additional interfaces implemented
|
||||
// by the reconciler. Returns an error is the resource key is invalid.
|
||||
s, err := newState(key, r)
|
||||
if err != nil {
|
||||
logger.Error("Invalid resource key: ", key)
|
||||
return nil
|
||||
}
|
||||
|
||||
// If we are not the leader, and we don't implement either ReadOnly
|
||||
// observer interfaces, then take a fast-path out.
|
||||
if s.isNotLeaderNorObserver() {
|
||||
return controller.NewSkipKey(key)
|
||||
}
|
||||
|
||||
// If configStore is set, attach the frozen configuration to the context.
|
||||
if r.configStore != nil {
|
||||
ctx = r.configStore.ToContext(ctx)
|
||||
}
|
||||
|
||||
// Add the recorder to context.
|
||||
ctx = controller.WithEventRecorder(ctx, r.Recorder)
|
||||
|
||||
// Get the resource with this namespace/name.
|
||||
|
||||
getter := r.Lister.Images(s.namespace)
|
||||
|
||||
original, err := getter.Get(s.name)
|
||||
|
||||
if errors.IsNotFound(err) {
|
||||
// The resource may no longer exist, in which case we stop processing.
|
||||
logger.Debugf("Resource %q no longer exists", key)
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Don't modify the informers copy.
|
||||
resource := original.DeepCopy()
|
||||
|
||||
var reconcileEvent reconciler.Event
|
||||
|
||||
name, do := s.reconcileMethodFor(resource)
|
||||
// Append the target method to the logger.
|
||||
logger = logger.With(zap.String("targetMethod", name))
|
||||
switch name {
|
||||
case reconciler.DoReconcileKind:
|
||||
// Set and update the finalizer on resource if r.reconciler
|
||||
// implements Finalizer.
|
||||
if resource, err = r.setFinalizerIfFinalizer(ctx, resource); err != nil {
|
||||
return fmt.Errorf("failed to set finalizers: %w", err)
|
||||
}
|
||||
|
||||
if !r.skipStatusUpdates {
|
||||
reconciler.PreProcessReconcile(ctx, resource)
|
||||
}
|
||||
|
||||
// Reconcile this copy of the resource and then write back any status
|
||||
// updates regardless of whether the reconciliation errored out.
|
||||
reconcileEvent = do(ctx, resource)
|
||||
|
||||
if !r.skipStatusUpdates {
|
||||
reconciler.PostProcessReconcile(ctx, resource, original)
|
||||
}
|
||||
|
||||
case reconciler.DoFinalizeKind:
|
||||
// For finalizing reconcilers, if this resource being marked for deletion
|
||||
// and reconciled cleanly (nil or normal event), remove the finalizer.
|
||||
reconcileEvent = do(ctx, resource)
|
||||
|
||||
if resource, err = r.clearFinalizer(ctx, resource, reconcileEvent); err != nil {
|
||||
return fmt.Errorf("failed to clear finalizers: %w", err)
|
||||
}
|
||||
|
||||
case reconciler.DoObserveKind, reconciler.DoObserveFinalizeKind:
|
||||
// Observe any changes to this resource, since we are not the leader.
|
||||
reconcileEvent = do(ctx, resource)
|
||||
|
||||
}
|
||||
|
||||
// Synchronize the status.
|
||||
switch {
|
||||
case r.skipStatusUpdates:
|
||||
// This reconciler implementation is configured to skip resource updates.
|
||||
// This may mean this reconciler does not observe spec, but reconciles external changes.
|
||||
case equality.Semantic.DeepEqual(original.Status, resource.Status):
|
||||
// If we didn't change anything then don't call updateStatus.
|
||||
// This is important because the copy we loaded from the injectionInformer's
|
||||
// cache may be stale and we don't want to overwrite a prior update
|
||||
// to status with this stale state.
|
||||
case !s.isLeader:
|
||||
// High-availability reconcilers may have many replicas watching the resource, but only
|
||||
// the elected leader is expected to write modifications.
|
||||
logger.Warn("Saw status changes when we aren't the leader!")
|
||||
default:
|
||||
if err = r.updateStatus(ctx, original, resource); err != nil {
|
||||
logger.Warnw("Failed to update resource status", zap.Error(err))
|
||||
r.Recorder.Eventf(resource, v1.EventTypeWarning, "UpdateFailed",
|
||||
"Failed to update status for %q: %v", resource.Name, err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Report the reconciler event, if any.
|
||||
if reconcileEvent != nil {
|
||||
var event *reconciler.ReconcilerEvent
|
||||
if reconciler.EventAs(reconcileEvent, &event) {
|
||||
logger.Infow("Returned an event", zap.Any("event", reconcileEvent))
|
||||
r.Recorder.Eventf(resource, event.EventType, event.Reason, event.Format, event.Args...)
|
||||
|
||||
// the event was wrapped inside an error, consider the reconciliation as failed
|
||||
if _, isEvent := reconcileEvent.(*reconciler.ReconcilerEvent); !isEvent {
|
||||
return reconcileEvent
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
logger.Errorw("Returned an error", zap.Error(reconcileEvent))
|
||||
r.Recorder.Event(resource, v1.EventTypeWarning, "InternalError", reconcileEvent.Error())
|
||||
return reconcileEvent
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *reconcilerImpl) updateStatus(ctx context.Context, existing *v1alpha1.Image, desired *v1alpha1.Image) error {
|
||||
existing = existing.DeepCopy()
|
||||
return reconciler.RetryUpdateConflicts(func(attempts int) (err error) {
|
||||
// The first iteration tries to use the injectionInformer's state, subsequent attempts fetch the latest state via API.
|
||||
if attempts > 0 {
|
||||
|
||||
getter := r.Client.CachingV1alpha1().Images(desired.Namespace)
|
||||
|
||||
existing, err = getter.Get(ctx, desired.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// If there's nothing to update, just return.
|
||||
if reflect.DeepEqual(existing.Status, desired.Status) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if diff, err := kmp.SafeDiff(existing.Status, desired.Status); err == nil && diff != "" {
|
||||
logging.FromContext(ctx).Debug("Updating status with: ", diff)
|
||||
}
|
||||
|
||||
existing.Status = desired.Status
|
||||
|
||||
updater := r.Client.CachingV1alpha1().Images(existing.Namespace)
|
||||
|
||||
_, err = updater.UpdateStatus(ctx, existing, metav1.UpdateOptions{})
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
// updateFinalizersFiltered will update the Finalizers of the resource.
|
||||
// TODO: this method could be generic and sync all finalizers. For now it only
|
||||
// updates defaultFinalizerName or its override.
|
||||
func (r *reconcilerImpl) updateFinalizersFiltered(ctx context.Context, resource *v1alpha1.Image) (*v1alpha1.Image, error) {
|
||||
|
||||
getter := r.Lister.Images(resource.Namespace)
|
||||
|
||||
actual, err := getter.Get(resource.Name)
|
||||
if err != nil {
|
||||
return resource, err
|
||||
}
|
||||
|
||||
// Don't modify the informers copy.
|
||||
existing := actual.DeepCopy()
|
||||
|
||||
var finalizers []string
|
||||
|
||||
// If there's nothing to update, just return.
|
||||
existingFinalizers := sets.NewString(existing.Finalizers...)
|
||||
desiredFinalizers := sets.NewString(resource.Finalizers...)
|
||||
|
||||
if desiredFinalizers.Has(r.finalizerName) {
|
||||
if existingFinalizers.Has(r.finalizerName) {
|
||||
// Nothing to do.
|
||||
return resource, nil
|
||||
}
|
||||
// Add the finalizer.
|
||||
finalizers = append(existing.Finalizers, r.finalizerName)
|
||||
} else {
|
||||
if !existingFinalizers.Has(r.finalizerName) {
|
||||
// Nothing to do.
|
||||
return resource, nil
|
||||
}
|
||||
// Remove the finalizer.
|
||||
existingFinalizers.Delete(r.finalizerName)
|
||||
finalizers = existingFinalizers.List()
|
||||
}
|
||||
|
||||
mergePatch := map[string]interface{}{
|
||||
"metadata": map[string]interface{}{
|
||||
"finalizers": finalizers,
|
||||
"resourceVersion": existing.ResourceVersion,
|
||||
},
|
||||
}
|
||||
|
||||
patch, err := json.Marshal(mergePatch)
|
||||
if err != nil {
|
||||
return resource, err
|
||||
}
|
||||
|
||||
patcher := r.Client.CachingV1alpha1().Images(resource.Namespace)
|
||||
|
||||
resourceName := resource.Name
|
||||
updated, err := patcher.Patch(ctx, resourceName, types.MergePatchType, patch, metav1.PatchOptions{})
|
||||
if err != nil {
|
||||
r.Recorder.Eventf(existing, v1.EventTypeWarning, "FinalizerUpdateFailed",
|
||||
"Failed to update finalizers for %q: %v", resourceName, err)
|
||||
} else {
|
||||
r.Recorder.Eventf(updated, v1.EventTypeNormal, "FinalizerUpdate",
|
||||
"Updated %q finalizers", resource.GetName())
|
||||
}
|
||||
return updated, err
|
||||
}
|
||||
|
||||
func (r *reconcilerImpl) setFinalizerIfFinalizer(ctx context.Context, resource *v1alpha1.Image) (*v1alpha1.Image, error) {
|
||||
if _, ok := r.reconciler.(Finalizer); !ok {
|
||||
return resource, nil
|
||||
}
|
||||
|
||||
finalizers := sets.NewString(resource.Finalizers...)
|
||||
|
||||
// If this resource is not being deleted, mark the finalizer.
|
||||
if resource.GetDeletionTimestamp().IsZero() {
|
||||
finalizers.Insert(r.finalizerName)
|
||||
}
|
||||
|
||||
resource.Finalizers = finalizers.List()
|
||||
|
||||
// Synchronize the finalizers filtered by r.finalizerName.
|
||||
return r.updateFinalizersFiltered(ctx, resource)
|
||||
}
|
||||
|
||||
func (r *reconcilerImpl) clearFinalizer(ctx context.Context, resource *v1alpha1.Image, reconcileEvent reconciler.Event) (*v1alpha1.Image, error) {
|
||||
if _, ok := r.reconciler.(Finalizer); !ok {
|
||||
return resource, nil
|
||||
}
|
||||
if resource.GetDeletionTimestamp().IsZero() {
|
||||
return resource, nil
|
||||
}
|
||||
|
||||
finalizers := sets.NewString(resource.Finalizers...)
|
||||
|
||||
if reconcileEvent != nil {
|
||||
var event *reconciler.ReconcilerEvent
|
||||
if reconciler.EventAs(reconcileEvent, &event) {
|
||||
if event.EventType == v1.EventTypeNormal {
|
||||
finalizers.Delete(r.finalizerName)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
finalizers.Delete(r.finalizerName)
|
||||
}
|
||||
|
||||
resource.Finalizers = finalizers.List()
|
||||
|
||||
// Synchronize the finalizers filtered by r.finalizerName.
|
||||
return r.updateFinalizersFiltered(ctx, resource)
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
Copyright 2020 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.
|
||||
*/
|
||||
|
||||
// Code generated by injection-gen. DO NOT EDIT.
|
||||
|
||||
package image
|
||||
|
||||
import (
|
||||
fmt "fmt"
|
||||
|
||||
types "k8s.io/apimachinery/pkg/types"
|
||||
cache "k8s.io/client-go/tools/cache"
|
||||
v1alpha1 "knative.dev/caching/pkg/apis/caching/v1alpha1"
|
||||
reconciler "knative.dev/pkg/reconciler"
|
||||
)
|
||||
|
||||
// state is used to track the state of a reconciler in a single run.
|
||||
type state struct {
|
||||
// key is the original reconciliation key from the queue.
|
||||
key string
|
||||
// namespace is the namespace split from the reconciliation key.
|
||||
namespace string
|
||||
// name is the name split from the reconciliation key.
|
||||
name string
|
||||
// reconciler is the reconciler.
|
||||
reconciler Interface
|
||||
// roi is the read only interface cast of the reconciler.
|
||||
roi ReadOnlyInterface
|
||||
// isROI (Read Only Interface) the reconciler only observes reconciliation.
|
||||
isROI bool
|
||||
// rof is the read only finalizer cast of the reconciler.
|
||||
rof ReadOnlyFinalizer
|
||||
// isROF (Read Only Finalizer) the reconciler only observes finalize.
|
||||
isROF bool
|
||||
// isLeader the instance of the reconciler is the elected leader.
|
||||
isLeader bool
|
||||
}
|
||||
|
||||
func newState(key string, r *reconcilerImpl) (*state, error) {
|
||||
// Convert the namespace/name string into a distinct namespace and name.
|
||||
namespace, name, err := cache.SplitMetaNamespaceKey(key)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid resource key: %s", key)
|
||||
}
|
||||
|
||||
roi, isROI := r.reconciler.(ReadOnlyInterface)
|
||||
rof, isROF := r.reconciler.(ReadOnlyFinalizer)
|
||||
|
||||
isLeader := r.IsLeaderFor(types.NamespacedName{
|
||||
Namespace: namespace,
|
||||
Name: name,
|
||||
})
|
||||
|
||||
return &state{
|
||||
key: key,
|
||||
namespace: namespace,
|
||||
name: name,
|
||||
reconciler: r.reconciler,
|
||||
roi: roi,
|
||||
isROI: isROI,
|
||||
rof: rof,
|
||||
isROF: isROF,
|
||||
isLeader: isLeader,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// isNotLeaderNorObserver checks to see if this reconciler with the current
|
||||
// state is enabled to do any work or not.
|
||||
// isNotLeaderNorObserver returns true when there is no work possible for the
|
||||
// reconciler.
|
||||
func (s *state) isNotLeaderNorObserver() bool {
|
||||
if !s.isLeader && !s.isROI && !s.isROF {
|
||||
// If we are not the leader, and we don't implement either ReadOnly
|
||||
// interface, then take a fast-path out.
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *state) reconcileMethodFor(o *v1alpha1.Image) (string, doReconcile) {
|
||||
if o.GetDeletionTimestamp().IsZero() {
|
||||
if s.isLeader {
|
||||
return reconciler.DoReconcileKind, s.reconciler.ReconcileKind
|
||||
} else if s.isROI {
|
||||
return reconciler.DoObserveKind, s.roi.ObserveKind
|
||||
}
|
||||
} else if fin, ok := s.reconciler.(Finalizer); s.isLeader && ok {
|
||||
return reconciler.DoFinalizeKind, fin.FinalizeKind
|
||||
} else if !s.isLeader && s.isROF {
|
||||
return reconciler.DoObserveFinalizeKind, s.rof.ObserveFinalizeKind
|
||||
}
|
||||
return "unknown", nil
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
Copyright 2020 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.
|
||||
*/
|
||||
|
||||
// Code generated by injection-gen. DO NOT EDIT.
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
context "context"
|
||||
|
||||
kubernetes "k8s.io/client-go/kubernetes"
|
||||
rest "k8s.io/client-go/rest"
|
||||
injection "knative.dev/pkg/injection"
|
||||
logging "knative.dev/pkg/logging"
|
||||
)
|
||||
|
||||
func init() {
|
||||
injection.Default.RegisterClient(withClient)
|
||||
injection.Default.RegisterClientFetcher(func(ctx context.Context) interface{} {
|
||||
return Get(ctx)
|
||||
})
|
||||
}
|
||||
|
||||
// Key is used as the key for associating information with a context.Context.
|
||||
type Key struct{}
|
||||
|
||||
func withClient(ctx context.Context, cfg *rest.Config) context.Context {
|
||||
return context.WithValue(ctx, Key{}, kubernetes.NewForConfigOrDie(cfg))
|
||||
}
|
||||
|
||||
// Get extracts the kubernetes.Interface client from the context.
|
||||
func Get(ctx context.Context) kubernetes.Interface {
|
||||
untyped := ctx.Value(Key{})
|
||||
if untyped == nil {
|
||||
if injection.GetConfig(ctx) == nil {
|
||||
logging.FromContext(ctx).Panic(
|
||||
"Unable to fetch k8s.io/client-go/kubernetes.Interface from context. This context is not the application context (which is typically given to constructors via sharedmain).")
|
||||
} else {
|
||||
logging.FromContext(ctx).Panic(
|
||||
"Unable to fetch k8s.io/client-go/kubernetes.Interface from context.")
|
||||
}
|
||||
}
|
||||
return untyped.(kubernetes.Interface)
|
||||
}
|
|
@ -227,6 +227,7 @@ go.uber.org/atomic
|
|||
## explicit
|
||||
go.uber.org/multierr
|
||||
# go.uber.org/zap v1.16.0
|
||||
## explicit
|
||||
go.uber.org/zap
|
||||
go.uber.org/zap/buffer
|
||||
go.uber.org/zap/internal/bufferpool
|
||||
|
@ -666,6 +667,7 @@ knative.dev/pkg/apis/duck
|
|||
knative.dev/pkg/apis/duck/ducktypes
|
||||
knative.dev/pkg/apis/duck/v1
|
||||
knative.dev/pkg/changeset
|
||||
knative.dev/pkg/client/injection/kube/client
|
||||
knative.dev/pkg/codegen/cmd/injection-gen
|
||||
knative.dev/pkg/codegen/cmd/injection-gen/args
|
||||
knative.dev/pkg/codegen/cmd/injection-gen/generators
|
||||
|
|
Loading…
Reference in New Issue