mirror of https://github.com/knative/caching.git
Auto-update dependencies (#145)
Produced via: `dep ensure -update knative.dev/test-infra knative.dev/pkg` /assign n3wscott
This commit is contained in:
parent
4f108c7677
commit
cf0d28345f
|
@ -933,7 +933,7 @@
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
digest = "1:09a00f21eaeeae387d973fd3f82cf06a8627f88566b4e6811ba97003dd207eac"
|
digest = "1:bfb453e78558b31d99d1335840a7d92328fc85fe35c58d4c5179caddb28f7416"
|
||||||
name = "knative.dev/pkg"
|
name = "knative.dev/pkg"
|
||||||
packages = [
|
packages = [
|
||||||
"apis",
|
"apis",
|
||||||
|
@ -952,18 +952,18 @@
|
||||||
"metrics/metricskey",
|
"metrics/metricskey",
|
||||||
]
|
]
|
||||||
pruneopts = "T"
|
pruneopts = "T"
|
||||||
revision = "944655d6cc17ceb861b0764046c56a465fe5bb75"
|
revision = "c259ed6f2e751582d4586c2d3a4e1cb98e526068"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
digest = "1:85fe0cadd6ab83f3d7f948c60b6d422dc9cd16664246249968dab5d828ae8dfd"
|
digest = "1:6e839a9b4183b0fbf0df609fe9a6d226c941fa9221052ba0075b25cb353588a5"
|
||||||
name = "knative.dev/test-infra"
|
name = "knative.dev/test-infra"
|
||||||
packages = [
|
packages = [
|
||||||
"scripts",
|
"scripts",
|
||||||
"tools/dep-collector",
|
"tools/dep-collector",
|
||||||
]
|
]
|
||||||
pruneopts = "UT"
|
pruneopts = "UT"
|
||||||
revision = "2c4cd9df33c648a86d95bdd4119386cba04cde56"
|
revision = "9363d2f4cafd2ad3c1bfc925f447c91a1776be00"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
digest = "1:8730e0150dfb2b7e173890c8b9868e7a273082ef8e39f4940e3506a481cf895c"
|
digest = "1:8730e0150dfb2b7e173890c8b9868e7a273082ef8e39f4940e3506a481cf895c"
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
/*
|
||||||
|
Copyright 2019 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 duck
|
||||||
|
|
||||||
|
const (
|
||||||
|
// BindingExcludeLabel is a label that is placed on namespaces and
|
||||||
|
// resources to exclude them from consideration when binding things.
|
||||||
|
// It is critical that bindings dealing with Deployments label their
|
||||||
|
// controller Deployment (or enclosing namespace).
|
||||||
|
BindingExcludeLabel = "bindings.knative.dev/exclude"
|
||||||
|
)
|
|
@ -19,6 +19,8 @@ package duck
|
||||||
import (
|
import (
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
"k8s.io/client-go/tools/cache"
|
"k8s.io/client-go/tools/cache"
|
||||||
|
"knative.dev/pkg/kmeta"
|
||||||
|
"knative.dev/pkg/tracker"
|
||||||
)
|
)
|
||||||
|
|
||||||
// InformerFactory is used to create Informer/Lister pairs for a schema.GroupVersionResource
|
// InformerFactory is used to create Informer/Lister pairs for a schema.GroupVersionResource
|
||||||
|
@ -26,3 +28,42 @@ type InformerFactory interface {
|
||||||
// Get returns a synced Informer/Lister pair for the provided schema.GroupVersionResource.
|
// Get returns a synced Informer/Lister pair for the provided schema.GroupVersionResource.
|
||||||
Get(schema.GroupVersionResource) (cache.SharedIndexInformer, cache.GenericLister, error)
|
Get(schema.GroupVersionResource) (cache.SharedIndexInformer, cache.GenericLister, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OneOfOurs is the union of our Accessor interface and the OwnerRefable interface
|
||||||
|
// that is implemented by our resources that implement the kmeta.Accessor.
|
||||||
|
type OneOfOurs interface {
|
||||||
|
kmeta.Accessor
|
||||||
|
kmeta.OwnerRefable
|
||||||
|
}
|
||||||
|
|
||||||
|
// BindableStatus is the interface that the .status of Bindable resources must
|
||||||
|
// implement to work smoothly with our BaseReconciler.
|
||||||
|
type BindableStatus interface {
|
||||||
|
// InitializeConditions seeds the resource's status.conditions field
|
||||||
|
// with all of the conditions that this Binding surfaces.
|
||||||
|
InitializeConditions()
|
||||||
|
|
||||||
|
// MarkBindingAvailable notes that this Binding has been properly
|
||||||
|
// configured.
|
||||||
|
MarkBindingAvailable()
|
||||||
|
|
||||||
|
// MarkBindingUnavailable notes the provided reason for why the Binding
|
||||||
|
// has failed.
|
||||||
|
MarkBindingUnavailable(reason string, message string)
|
||||||
|
|
||||||
|
// SetObservedGeneration updates the .status.observedGeneration to the
|
||||||
|
// provided generation value.
|
||||||
|
SetObservedGeneration(int64)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bindable may be implemented by Binding resources to use shared libraries.
|
||||||
|
type Bindable interface {
|
||||||
|
OneOfOurs
|
||||||
|
|
||||||
|
// GetSubject returns the standard Binding duck's "Subject" field.
|
||||||
|
GetSubject() tracker.Reference
|
||||||
|
|
||||||
|
// GetBindingStatus returns the status of the Binding, which must
|
||||||
|
// implement BindableStatus.
|
||||||
|
GetBindingStatus() BindableStatus
|
||||||
|
}
|
||||||
|
|
|
@ -20,16 +20,8 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
"knative.dev/pkg/kmeta"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// OneOfOurs is the union of our Accessor interface and the OwnerRefable interface
|
|
||||||
// that is implemented by our resources that implement the kmeta.Accessor.
|
|
||||||
type OneOfOurs interface {
|
|
||||||
kmeta.Accessor
|
|
||||||
kmeta.OwnerRefable
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToUnstructured takes an instance of a OneOfOurs compatible type and
|
// ToUnstructured takes an instance of a OneOfOurs compatible type and
|
||||||
// converts it to unstructured.Unstructured. We take OneOfOurs in place
|
// converts it to unstructured.Unstructured. We take OneOfOurs in place
|
||||||
// or runtime.Object because sometimes we get resources that do not have their
|
// or runtime.Object because sometimes we get resources that do not have their
|
||||||
|
|
|
@ -72,7 +72,7 @@ ${CODEGEN_PKG}/generate-groups.sh "deepcopy" \
|
||||||
|
|
||||||
# Depends on generate-groups.sh to install bin/deepcopy-gen
|
# Depends on generate-groups.sh to install bin/deepcopy-gen
|
||||||
${GOPATH}/bin/deepcopy-gen --input-dirs \
|
${GOPATH}/bin/deepcopy-gen --input-dirs \
|
||||||
knative.dev/pkg/apis,knative.dev/pkg/tracker,knative.dev/pkg/logging,knative.dev/pkg/metrics,knative.dev/pkg/testing \
|
knative.dev/pkg/apis,knative.dev/pkg/tracker,knative.dev/pkg/logging,knative.dev/pkg/metrics,knative.dev/pkg/testing,knative.dev/pkg/testing/duck \
|
||||||
-O zz_generated.deepcopy \
|
-O zz_generated.deepcopy \
|
||||||
--go-header-file ${REPO_ROOT_DIR}/hack/boilerplate/boilerplate.go.txt
|
--go-header-file ${REPO_ROOT_DIR}/hack/boilerplate/boilerplate.go.txt
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
/*
|
||||||
|
Copyright 2019 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// +k8s:deepcopy-gen=package
|
||||||
|
// +groupName=duck.knative.dev
|
||||||
|
package duck
|
|
@ -0,0 +1,42 @@
|
||||||
|
/*
|
||||||
|
Copyright 2019 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 duck
|
||||||
|
|
||||||
|
import (
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SchemeGroupVersion is group version used to register these objects
|
||||||
|
var SchemeGroupVersion = schema.GroupVersion{Group: "duck.knative.dev", Version: "v3"}
|
||||||
|
|
||||||
|
var (
|
||||||
|
SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
|
||||||
|
AddToScheme = SchemeBuilder.AddToScheme
|
||||||
|
)
|
||||||
|
|
||||||
|
// Adds the list of known types to Scheme.
|
||||||
|
func addKnownTypes(scheme *runtime.Scheme) error {
|
||||||
|
scheme.AddKnownTypes(
|
||||||
|
SchemeGroupVersion,
|
||||||
|
&TestBindable{},
|
||||||
|
(&TestBindable{}).GetListType(),
|
||||||
|
)
|
||||||
|
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,169 @@
|
||||||
|
/*
|
||||||
|
Copyright 2019 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 duck
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
"knative.dev/pkg/apis"
|
||||||
|
"knative.dev/pkg/apis/duck"
|
||||||
|
duckv1 "knative.dev/pkg/apis/duck/v1"
|
||||||
|
duckv1alpha1 "knative.dev/pkg/apis/duck/v1alpha1"
|
||||||
|
"knative.dev/pkg/tracker"
|
||||||
|
)
|
||||||
|
|
||||||
|
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||||
|
|
||||||
|
// TestBindable is a simple resource that's compatible with our webhook
|
||||||
|
type TestBindable struct {
|
||||||
|
metav1.TypeMeta `json:",inline"`
|
||||||
|
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||||
|
|
||||||
|
Spec TestBindableSpec `json:"spec,omitempty"`
|
||||||
|
Status TestBindableStatus `json:"status,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that TestBindable may be validated and defaulted.
|
||||||
|
var _ apis.Listable = (*TestBindable)(nil)
|
||||||
|
var _ duck.Bindable = (*TestBindable)(nil)
|
||||||
|
|
||||||
|
// TestBindableSpec represents test resource spec.
|
||||||
|
type TestBindableSpec struct {
|
||||||
|
duckv1alpha1.BindingSpec `json:",inline"`
|
||||||
|
|
||||||
|
Foo string `json:"foo"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestBindableStatus represents the status of our test binding.
|
||||||
|
type TestBindableStatus struct {
|
||||||
|
duckv1.Status `json:",inline"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetGroupVersionKind returns the GroupVersionKind.
|
||||||
|
func (tb *TestBindable) GetGroupVersionKind() schema.GroupVersionKind {
|
||||||
|
return SchemeGroupVersion.WithKind("TestBindable")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUntypedSpec returns the spec of the resource.
|
||||||
|
func (tb *TestBindable) GetUntypedSpec() interface{} {
|
||||||
|
return tb.Spec
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetListType implements apis.Listable
|
||||||
|
func (tb *TestBindable) GetListType() runtime.Object {
|
||||||
|
return &TestBindableList{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSubject implements psbinding.Bindable
|
||||||
|
func (tb *TestBindable) GetSubject() tracker.Reference {
|
||||||
|
return tb.Spec.Subject
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBindingStatus implements psbinding.Bindable
|
||||||
|
func (tb *TestBindable) GetBindingStatus() duck.BindableStatus {
|
||||||
|
return &tb.Status
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetObservedGeneration implements psbinding.BindableStatus
|
||||||
|
func (tbs *TestBindableStatus) SetObservedGeneration(gen int64) {
|
||||||
|
tbs.ObservedGeneration = gen
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do implements psbinding.Bindable
|
||||||
|
func (tb *TestBindable) Do(ctx context.Context, ps *duckv1.WithPod) {
|
||||||
|
// First undo so that we can just unconditionally append below.
|
||||||
|
tb.Undo(ctx, ps)
|
||||||
|
|
||||||
|
spec := ps.Spec.Template.Spec
|
||||||
|
for i := range spec.InitContainers {
|
||||||
|
spec.InitContainers[i].Env = append(spec.InitContainers[i].Env, corev1.EnvVar{
|
||||||
|
Name: "FOO",
|
||||||
|
Value: tb.Spec.Foo,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
for i := range spec.Containers {
|
||||||
|
spec.Containers[i].Env = append(spec.Containers[i].Env, corev1.EnvVar{
|
||||||
|
Name: "FOO",
|
||||||
|
Value: tb.Spec.Foo,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tb *TestBindable) Undo(ctx context.Context, ps *duckv1.WithPod) {
|
||||||
|
spec := ps.Spec.Template.Spec
|
||||||
|
for i, c := range spec.InitContainers {
|
||||||
|
for j, ev := range c.Env {
|
||||||
|
if ev.Name == "FOO" {
|
||||||
|
spec.InitContainers[i].Env = append(spec.InitContainers[i].Env[:j], spec.InitContainers[i].Env[j+1:]...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i, c := range spec.Containers {
|
||||||
|
for j, ev := range c.Env {
|
||||||
|
if ev.Name == "FOO" {
|
||||||
|
spec.Containers[i].Env = append(spec.Containers[i].Env[:j], spec.Containers[i].Env[j+1:]...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var tbCondSet = apis.NewLivingConditionSet()
|
||||||
|
|
||||||
|
// clearLTT clears the last transition time from our conditions because
|
||||||
|
// they are hard to ignore in the context of *unstructured.Unstructured,
|
||||||
|
// which is how psbinding updates statuses.
|
||||||
|
func (tbs *TestBindableStatus) clearLTT() {
|
||||||
|
for i := range tbs.Conditions {
|
||||||
|
tbs.Conditions[i].LastTransitionTime = apis.VolatileTime{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// InitializeConditions populates the TestBindableStatus's conditions field
|
||||||
|
// with all of its conditions configured to Unknown.
|
||||||
|
func (tbs *TestBindableStatus) InitializeConditions() {
|
||||||
|
tbCondSet.Manage(tbs).InitializeConditions()
|
||||||
|
tbs.clearLTT()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkBindingUnavailable marks the TestBindable's Ready condition to False with
|
||||||
|
// the provided reason and message.
|
||||||
|
func (tbs *TestBindableStatus) MarkBindingUnavailable(reason, message string) {
|
||||||
|
tbCondSet.Manage(tbs).MarkFalse(apis.ConditionReady, reason, message)
|
||||||
|
tbs.clearLTT()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkBindingAvailable marks the TestBindable's Ready condition to True.
|
||||||
|
func (tbs *TestBindableStatus) MarkBindingAvailable() {
|
||||||
|
tbCondSet.Manage(tbs).MarkTrue(apis.ConditionReady)
|
||||||
|
tbs.clearLTT()
|
||||||
|
}
|
||||||
|
|
||||||
|
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||||
|
|
||||||
|
// TestBindableList is a list of TestBindable resources
|
||||||
|
type TestBindableList struct {
|
||||||
|
metav1.TypeMeta `json:",inline"`
|
||||||
|
metav1.ListMeta `json:"metadata"`
|
||||||
|
|
||||||
|
Items []TestBindable `json:"items"`
|
||||||
|
}
|
|
@ -0,0 +1,120 @@
|
||||||
|
// +build !ignore_autogenerated
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copyright 2019 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 deepcopy-gen. DO NOT EDIT.
|
||||||
|
|
||||||
|
package duck
|
||||||
|
|
||||||
|
import (
|
||||||
|
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *TestBindable) DeepCopyInto(out *TestBindable) {
|
||||||
|
*out = *in
|
||||||
|
out.TypeMeta = in.TypeMeta
|
||||||
|
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||||
|
in.Spec.DeepCopyInto(&out.Spec)
|
||||||
|
in.Status.DeepCopyInto(&out.Status)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TestBindable.
|
||||||
|
func (in *TestBindable) DeepCopy() *TestBindable {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(TestBindable)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||||
|
func (in *TestBindable) 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 *TestBindableList) DeepCopyInto(out *TestBindableList) {
|
||||||
|
*out = *in
|
||||||
|
out.TypeMeta = in.TypeMeta
|
||||||
|
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||||
|
if in.Items != nil {
|
||||||
|
in, out := &in.Items, &out.Items
|
||||||
|
*out = make([]TestBindable, len(*in))
|
||||||
|
for i := range *in {
|
||||||
|
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TestBindableList.
|
||||||
|
func (in *TestBindableList) DeepCopy() *TestBindableList {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(TestBindableList)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||||
|
func (in *TestBindableList) 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 *TestBindableSpec) DeepCopyInto(out *TestBindableSpec) {
|
||||||
|
*out = *in
|
||||||
|
in.BindingSpec.DeepCopyInto(&out.BindingSpec)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TestBindableSpec.
|
||||||
|
func (in *TestBindableSpec) DeepCopy() *TestBindableSpec {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(TestBindableSpec)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *TestBindableStatus) DeepCopyInto(out *TestBindableStatus) {
|
||||||
|
*out = *in
|
||||||
|
in.Status.DeepCopyInto(&out.Status)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TestBindableStatus.
|
||||||
|
func (in *TestBindableStatus) DeepCopy() *TestBindableStatus {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(TestBindableStatus)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
|
@ -91,14 +91,6 @@ func (gs *GKEClient) Setup(r GKERequest) ClusterOperations {
|
||||||
gc.NeedsCleanup = true
|
gc.NeedsCleanup = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.ClusterName == "" {
|
|
||||||
var err error
|
|
||||||
r.ClusterName, err = getResourceName(ClusterResource)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Failed getting cluster name: '%v'", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.MinNodes == 0 {
|
if r.MinNodes == 0 {
|
||||||
r.MinNodes = DefaultGKEMinNodes
|
r.MinNodes = DefaultGKEMinNodes
|
||||||
}
|
}
|
||||||
|
@ -192,6 +184,14 @@ func (gc *GKECluster) Acquire() error {
|
||||||
request := gc.Request.DeepCopy()
|
request := gc.Request.DeepCopy()
|
||||||
// We are going to use request for creating cluster, set its Project
|
// We are going to use request for creating cluster, set its Project
|
||||||
request.Project = gc.Project
|
request.Project = gc.Project
|
||||||
|
// Set the cluster name if it doesn't exist
|
||||||
|
if request.ClusterName == "" {
|
||||||
|
var err error
|
||||||
|
request.ClusterName, err = getResourceName(ClusterResource)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed getting cluster name: '%v'", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Combine Region with BackupRegions, these will be the regions used for
|
// Combine Region with BackupRegions, these will be the regions used for
|
||||||
// retrying creation logic
|
// retrying creation logic
|
||||||
|
|
|
@ -0,0 +1,205 @@
|
||||||
|
# "Pod Spec"-able Bindings
|
||||||
|
|
||||||
|
The `psbinding` package provides facilities to make authoring [Bindings](https://docs.google.com/document/d/1t5WVrj2KQZ2u5s0LvIUtfHnSonBv5Vcv8Gl2k5NXrCQ/edit) whose subjects adhere to [`duckv1.PodSpecable`](https://github.com/knative/pkg/blob/master/apis/duck/v1/podspec_types.go#L32) easier. The Bindings doc mentions two key elements of the controller architecture:
|
||||||
|
1. The standard controller,
|
||||||
|
1. The mutating webhook (or "admission controller")
|
||||||
|
|
||||||
|
This package provides facilities for bootstrapping both of these elements. To leverage the `psbinding` package, folks should adjust their Binding types to implement `psbinding.Bindable`, which contains a variety of methods that will look familiar to Knative controller authors with two new key methods: `Do` and `Undo` (aka the "mutation" methods).
|
||||||
|
|
||||||
|
The mutation methods on the Binding take in `(context.Context, *duckv1.WithPod)`, and are expected to alter the `*duckv1.WithPod` appropriately to achieve the semantics of the Binding. So for example, if the Binding's runtime contract is the inclusion of a new environment variable `FOO` with some value extracted from the Binding's `spec` then in `Do()` the `duckv1.WithPod` would be altered so that each of the `containers:` contains:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
env:
|
||||||
|
- name: "FOO"
|
||||||
|
value: "<from Binding spec>"
|
||||||
|
```
|
||||||
|
|
||||||
|
... and `Undo()` would remove these variables. `Do` is invoked for active Bindings, and `Undo` is invoked when they are being deleted, but their subjects remain.
|
||||||
|
|
||||||
|
|
||||||
|
We will walk through a simple example Binding whose runtime contract is to mount secrets for talking to Github under `/var/bindings/github`.
|
||||||
|
[See also](https://github.com/mattmoor/bindings#githubbinding) on which this is based.
|
||||||
|
|
||||||
|
### `Do` and `Undo`
|
||||||
|
|
||||||
|
The `Undo` method itself is simply: remove the named secret volume and any mounts of it:
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (fb *GithubBinding) Undo(ctx context.Context, ps *duckv1.WithPod) {
|
||||||
|
spec := ps.Spec.Template.Spec
|
||||||
|
|
||||||
|
// Make sure the PodSpec does NOT have the github volume.
|
||||||
|
for i, v := range spec.Volumes {
|
||||||
|
if v.Name == github.VolumeName {
|
||||||
|
ps.Spec.Template.Spec.Volumes = append(spec.Volumes[:i], spec.Volumes[i+1:]...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure that none of the [init]containers have the github volume mount
|
||||||
|
for i, c := range spec.InitContainers {
|
||||||
|
for j, vm := range c.VolumeMounts {
|
||||||
|
if vm.Name == github.VolumeName {
|
||||||
|
spec.InitContainers[i].VolumeMounts = append(vm[:j], vm[j+1:]...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i, c := range spec.Containers {
|
||||||
|
for j, vm := range c.VolumeMounts {
|
||||||
|
if vm.Name == github.VolumeName {
|
||||||
|
spec.Containers[i].VolumeMounts = append(vm[:j], vm[j+1:]...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `Do` method is the dual of this: ensure that the volume exists, and all containers have it mounted.
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (fb *GithubBinding) Do(ctx context.Context, ps *duckv1.WithPod) {
|
||||||
|
|
||||||
|
// First undo so that we can just unconditionally append below.
|
||||||
|
fb.Undo(ctx, ps)
|
||||||
|
|
||||||
|
// Make sure the PodSpec has a Volume like this:
|
||||||
|
volume := corev1.Volume{
|
||||||
|
Name: github.VolumeName,
|
||||||
|
VolumeSource: corev1.VolumeSource{
|
||||||
|
Secret: &corev1.SecretVolumeSource{
|
||||||
|
SecretName: fb.Spec.Secret.Name,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
ps.Spec.Template.Spec.Volumes = append(ps.Spec.Template.Spec.Volumes, volume)
|
||||||
|
|
||||||
|
// Make sure that each [init]container in the PodSpec has a VolumeMount like this:
|
||||||
|
volumeMount := corev1.VolumeMount{
|
||||||
|
Name: github.VolumeName,
|
||||||
|
ReadOnly: true,
|
||||||
|
MountPath: github.MountPath,
|
||||||
|
}
|
||||||
|
spec := ps.Spec.Template.Spec
|
||||||
|
for i := range spec.InitContainers {
|
||||||
|
spec.InitContainers[i].VolumeMounts = append(spec.InitContainers[i].VolumeMounts, volumeMount)
|
||||||
|
}
|
||||||
|
for i := range spec.Containers {
|
||||||
|
spec.Containers[i].VolumeMounts = append(spec.Containers[i].VolumeMounts, volumeMount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> Note: if additional context is needed to perform the mutation, then it may be attached-to / extracted-from the supplied `context.Context`.
|
||||||
|
|
||||||
|
### The standard controller
|
||||||
|
|
||||||
|
For simple Bindings (such as our `GithubBinding`), we should be able to implement our `*controller.Impl` by directly leveraging `*psbinding.BaseReconciler` to fully implement reconciliation.
|
||||||
|
|
||||||
|
```go
|
||||||
|
// NewController returns a new GithubBinding reconciler.
|
||||||
|
func NewController(
|
||||||
|
ctx context.Context,
|
||||||
|
cmw configmap.Watcher,
|
||||||
|
) *controller.Impl {
|
||||||
|
logger := logging.FromContext(ctx)
|
||||||
|
|
||||||
|
ghInformer := ghinformer.Get(ctx)
|
||||||
|
dc := dynamicclient.Get(ctx)
|
||||||
|
psInformerFactory := podspecable.Get(ctx)
|
||||||
|
|
||||||
|
c := &psbinding.BaseReconciler{
|
||||||
|
GVR: v1alpha1.SchemeGroupVersion.WithResource("githubbindings"),
|
||||||
|
Get: func(namespace string, name string) (psbinding.Bindable, error) {
|
||||||
|
return ghInformer.Lister().GithubBindings(namespace).Get(name)
|
||||||
|
},
|
||||||
|
DynamicClient: dc,
|
||||||
|
Recorder: record.NewBroadcaster().NewRecorder(
|
||||||
|
scheme.Scheme, corev1.EventSource{Component: controllerAgentName}),
|
||||||
|
}
|
||||||
|
impl := controller.NewImpl(c, logger, "GithubBindings")
|
||||||
|
|
||||||
|
logger.Info("Setting up event handlers")
|
||||||
|
|
||||||
|
ghInformer.Informer().AddEventHandler(controller.HandleAll(impl.Enqueue))
|
||||||
|
|
||||||
|
c.Tracker = tracker.New(impl.EnqueueKey, controller.GetTrackerLease(ctx))
|
||||||
|
c.Factory = &duck.CachedInformerFactory{
|
||||||
|
Delegate: &duck.EnqueueInformerFactory{
|
||||||
|
Delegate: psInformerFactory,
|
||||||
|
EventHandler: controller.HandleAll(c.Tracker.OnChanged),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// If our `Do` / `Undo` methods need additional context, then we can
|
||||||
|
// setup a callback to infuse the `context.Context` here:
|
||||||
|
// c.WithContext = ...
|
||||||
|
// Note that this can also set up additional informer watch events to
|
||||||
|
// trigger reconciliation when the infused context changes.
|
||||||
|
|
||||||
|
return impl
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> Note: if customized reconciliation logic is needed (e.g. synthesizing additional resources), then the `psbinding.BaseReconciler` may be embedded and a custom `Reconcile()` defined, which can still take advantage of the shared `Finalizer` handling, `Status` manipulation or `Subject`-reconciliation.
|
||||||
|
|
||||||
|
### The mutating webhook
|
||||||
|
|
||||||
|
Setting up the mutating webhook is even simpler:
|
||||||
|
|
||||||
|
```go
|
||||||
|
func NewWebhook(ctx context.Context, cmw configmap.Watcher) *controller.Impl {
|
||||||
|
return psbinding.NewAdmissionController(ctx,
|
||||||
|
// Name of the resource webhook.
|
||||||
|
"githubbindings.webhook.bindings.mattmoor.dev",
|
||||||
|
|
||||||
|
// The path on which to serve the webhook.
|
||||||
|
"/githubbindings",
|
||||||
|
|
||||||
|
// How to get all the Bindables for configuring the mutating webhook.
|
||||||
|
ListAll,
|
||||||
|
|
||||||
|
// How to setup the context prior to invoking Do/Undo.
|
||||||
|
func(ctx context.Context, b psbinding.Bindable) (context.Context, error) {
|
||||||
|
return ctx, nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListAll enumerates all of the GithubBindings as Bindables so that the webhook
|
||||||
|
// can reprogram itself as-needed.
|
||||||
|
func ListAll(ctx context.Context, handler cache.ResourceEventHandler) psbinding.ListAll {
|
||||||
|
ghInformer := ghinformer.Get(ctx)
|
||||||
|
|
||||||
|
// Whenever a GithubBinding changes our webhook programming might change.
|
||||||
|
ghInformer.Informer().AddEventHandler(handler)
|
||||||
|
|
||||||
|
return func() ([]psbinding.Bindable, error) {
|
||||||
|
l, err := ghInformer.Lister().List(labels.Everything())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
bl := make([]psbinding.Bindable, 0, len(l))
|
||||||
|
for _, elt := range l {
|
||||||
|
bl = append(bl, elt)
|
||||||
|
}
|
||||||
|
return bl, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Putting it together
|
||||||
|
|
||||||
|
With the above defined, then in our webhook's `main.go` we invoke `sharedmain.MainWithContext` passing the additional controller constructors:
|
||||||
|
|
||||||
|
```go
|
||||||
|
sharedmain.MainWithContext(ctx, "webhook",
|
||||||
|
// Our other controllers.
|
||||||
|
// ...
|
||||||
|
|
||||||
|
// For each binding we have our controller and binding webhook.
|
||||||
|
githubbinding.NewController, githubbinding.NewWebhook,
|
||||||
|
)
|
||||||
|
```
|
|
@ -0,0 +1,121 @@
|
||||||
|
/*
|
||||||
|
Copyright 2019 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 psbinding
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
// Injection stuff
|
||||||
|
kubeclient "knative.dev/pkg/client/injection/kube/client"
|
||||||
|
mwhinformer "knative.dev/pkg/client/injection/kube/informers/admissionregistration/v1beta1/mutatingwebhookconfiguration"
|
||||||
|
secretinformer "knative.dev/pkg/client/injection/kube/informers/core/v1/secret"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/types"
|
||||||
|
"k8s.io/client-go/tools/cache"
|
||||||
|
"knative.dev/pkg/apis/duck"
|
||||||
|
duckv1 "knative.dev/pkg/apis/duck/v1"
|
||||||
|
"knative.dev/pkg/controller"
|
||||||
|
"knative.dev/pkg/logging"
|
||||||
|
"knative.dev/pkg/system"
|
||||||
|
"knative.dev/pkg/webhook"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Bindable is implemented by Binding resources whose subjects are PodSpecable
|
||||||
|
// and that want to leverage this shared logic to simplify binding authorship.
|
||||||
|
type Bindable interface {
|
||||||
|
duck.Bindable
|
||||||
|
|
||||||
|
// Do performs this binding's mutation with the specified context on the
|
||||||
|
// provided PodSpecable. The provided context may be decorated by
|
||||||
|
// passing a BindableContext to both NewAdmissionController and
|
||||||
|
// BaseReconciler.
|
||||||
|
Do(context.Context, *duckv1.WithPod)
|
||||||
|
|
||||||
|
// Undo is the dual of Do, it undoes the binding.
|
||||||
|
Undo(context.Context, *duckv1.WithPod)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mutation is the type of the Do/Undo methods.
|
||||||
|
type Mutation func(context.Context, *duckv1.WithPod)
|
||||||
|
|
||||||
|
// ListAll is the type of methods for enumerating all of the Bindables on the
|
||||||
|
// cluster in order to index the covered types to program the admission webhook.
|
||||||
|
type ListAll func() ([]Bindable, error)
|
||||||
|
|
||||||
|
// GetListAll is a factory method for the ListAll method, which may also be
|
||||||
|
// supplied with a ResourceEventHandler to register a callback with the Informer
|
||||||
|
// that sits behind the returned ListAll so that the handler can queue work
|
||||||
|
// whenever the result of ListAll changes.
|
||||||
|
type GetListAll func(context.Context, cache.ResourceEventHandler) ListAll
|
||||||
|
|
||||||
|
// BindableContext is the type of context decorator methods that may be supplied
|
||||||
|
// to NewAdmissionController and BaseReconciler.
|
||||||
|
type BindableContext func(context.Context, Bindable) (context.Context, error)
|
||||||
|
|
||||||
|
// NewAdmissionController constructs the webhook portion of the pair of
|
||||||
|
// reconcilers that implement the semantics of our Binding.
|
||||||
|
func NewAdmissionController(
|
||||||
|
ctx context.Context,
|
||||||
|
name, path string,
|
||||||
|
gla GetListAll,
|
||||||
|
WithContext BindableContext,
|
||||||
|
) *controller.Impl {
|
||||||
|
|
||||||
|
// Extract the assorted things from our context.
|
||||||
|
client := kubeclient.Get(ctx)
|
||||||
|
mwhInformer := mwhinformer.Get(ctx)
|
||||||
|
secretInformer := secretinformer.Get(ctx)
|
||||||
|
options := webhook.GetOptions(ctx)
|
||||||
|
|
||||||
|
// Construct the reconciler for the mutating webhook configuration.
|
||||||
|
wh := &Reconciler{
|
||||||
|
Name: name,
|
||||||
|
HandlerPath: path,
|
||||||
|
SecretName: options.SecretName,
|
||||||
|
|
||||||
|
// This is the user-provided context-decorator, which allows
|
||||||
|
// them to infuse the context passed to Do/Undo.
|
||||||
|
WithContext: WithContext,
|
||||||
|
|
||||||
|
Client: client,
|
||||||
|
MWHLister: mwhInformer.Lister(),
|
||||||
|
SecretLister: secretInformer.Lister(),
|
||||||
|
}
|
||||||
|
c := controller.NewImpl(wh, logging.FromContext(ctx), name)
|
||||||
|
|
||||||
|
// It doesn't matter what we enqueue because we will always Reconcile
|
||||||
|
// the named MWH resource.
|
||||||
|
handler := controller.HandleAll(c.EnqueueSentinel(types.NamespacedName{}))
|
||||||
|
|
||||||
|
// Reconcile when the named MutatingWebhookConfiguration changes.
|
||||||
|
mwhInformer.Informer().AddEventHandler(cache.FilteringResourceEventHandler{
|
||||||
|
FilterFunc: controller.FilterWithName(name),
|
||||||
|
Handler: handler,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Reconcile when the cert bundle changes.
|
||||||
|
secretInformer.Informer().AddEventHandler(cache.FilteringResourceEventHandler{
|
||||||
|
FilterFunc: controller.FilterWithNameAndNamespace(system.Namespace(), wh.SecretName),
|
||||||
|
Handler: handler,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Give the reconciler a way to list all of the Bindable resources,
|
||||||
|
// and configure the controller to handle changes to those resources.
|
||||||
|
wh.ListAll = gla(ctx, handler)
|
||||||
|
|
||||||
|
return c
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
/*
|
||||||
|
Copyright 2019 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 requ ired 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 psbinding provides facilities to make authoring Bindings that
|
||||||
|
// work with "Pod Spec"-able subjects easier. There are two key components
|
||||||
|
// 1. The AdmissionController, which lives in psbinding.go (controller.go)
|
||||||
|
// sets it up.
|
||||||
|
// 2. The BaseReconciler, which lives in reconciler.go and can either be
|
||||||
|
// used directly as a Reconciler, or it can be wrapped as a base
|
||||||
|
// implementation for a customized reconciler that wants to take advantage
|
||||||
|
// of its facilities (e.g. for updating status and manipulating finalizers).
|
||||||
|
//
|
||||||
|
// The core concept to consuming psbinding is the Bindable interface. By
|
||||||
|
// implementing Bindable on your binding resource, you enable the BaseReconciler
|
||||||
|
// to take over a significant amount of the boilerplate reconciliation (maybe
|
||||||
|
// all of it). A lot of the interface methods will seem pretty standard to
|
||||||
|
// Knative folks, but the two key methods to call our are Do and Undo. These
|
||||||
|
// "mutation" methods carry the business logic for the "Pod Spec"-able binding.
|
||||||
|
//
|
||||||
|
// The mutation methods have the signature:
|
||||||
|
// func(context.Context, *v1alpha1.WithPod)
|
||||||
|
// These methods are called to have the Binding perform its mutation on the
|
||||||
|
// supplied WithPod (our "Pod Spec"-able wrapper type). However, in some
|
||||||
|
// cases the binding may need additional context. Similar to apis.Validatable
|
||||||
|
// and apis.Defaultable these mutations take a context.Context, and similar
|
||||||
|
// to our "resourcesemantics" webhook, the "psbinding" package provides a hook
|
||||||
|
// to allow consumers to infuse this context.Context with additional... context.
|
||||||
|
// The signature of these hooks is BindableContext, and they may be supplied to
|
||||||
|
// both the AdmissionController and the BaseReconciler.
|
||||||
|
package psbinding
|
|
@ -0,0 +1,81 @@
|
||||||
|
/*
|
||||||
|
Copyright 2019 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 requ ired 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 psbinding
|
||||||
|
|
||||||
|
import "k8s.io/apimachinery/pkg/labels"
|
||||||
|
|
||||||
|
// exactKey is the type for keys that match exactly.
|
||||||
|
type exactKey struct {
|
||||||
|
Group string
|
||||||
|
Kind string
|
||||||
|
Namespace string
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
// exactMatcher is our reverse index from subjects to the Bindings that apply to
|
||||||
|
// them.
|
||||||
|
type exactMatcher map[exactKey]Bindable
|
||||||
|
|
||||||
|
// Add writes a key into the reverse index.
|
||||||
|
func (em exactMatcher) Add(key exactKey, b Bindable) {
|
||||||
|
em[key] = b
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get fetches the key from the reverse index, if present.
|
||||||
|
func (em exactMatcher) Get(key exactKey) (bindable Bindable, present bool) {
|
||||||
|
b, ok := em[key]
|
||||||
|
return b, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// inexactKey is the type for keys that match inexactly (via selector)
|
||||||
|
type inexactKey struct {
|
||||||
|
Group string
|
||||||
|
Kind string
|
||||||
|
Namespace string
|
||||||
|
}
|
||||||
|
|
||||||
|
// pair holds selectors and bindables for a particular inexactKey.
|
||||||
|
type pair struct {
|
||||||
|
selector labels.Selector
|
||||||
|
sb Bindable
|
||||||
|
}
|
||||||
|
|
||||||
|
// inexactMatcher is our reverse index from subjects to the Bindings that apply to
|
||||||
|
// them.
|
||||||
|
type inexactMatcher map[inexactKey][]pair
|
||||||
|
|
||||||
|
// Add writes a key into the reverse index.
|
||||||
|
func (im inexactMatcher) Add(key inexactKey, selector labels.Selector, b Bindable) {
|
||||||
|
pl := im[key]
|
||||||
|
pl = append(pl, pair{
|
||||||
|
selector: selector,
|
||||||
|
sb: b,
|
||||||
|
})
|
||||||
|
im[key] = pl
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get fetches the key from the reverse index, if present.
|
||||||
|
func (im inexactMatcher) Get(key inexactKey, ls labels.Set) (bindable Bindable, present bool) {
|
||||||
|
// Iterate over the list of pairs matched for this GK + namespace and return the first
|
||||||
|
// Bindable that matches our selector.
|
||||||
|
for _, p := range im[key] {
|
||||||
|
if p.selector.Matches(ls) {
|
||||||
|
return p.sb, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, false
|
||||||
|
}
|
|
@ -0,0 +1,327 @@
|
||||||
|
/*
|
||||||
|
Copyright 2019 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 psbinding
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/markbates/inflect"
|
||||||
|
admissionv1beta1 "k8s.io/api/admission/v1beta1"
|
||||||
|
admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1"
|
||||||
|
appsv1 "k8s.io/api/apps/v1"
|
||||||
|
batchv1 "k8s.io/api/batch/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/labels"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
|
"k8s.io/client-go/kubernetes"
|
||||||
|
admissionlisters "k8s.io/client-go/listers/admissionregistration/v1beta1"
|
||||||
|
corelisters "k8s.io/client-go/listers/core/v1"
|
||||||
|
"knative.dev/pkg/apis/duck"
|
||||||
|
duckv1 "knative.dev/pkg/apis/duck/v1"
|
||||||
|
"knative.dev/pkg/controller"
|
||||||
|
"knative.dev/pkg/kmp"
|
||||||
|
"knative.dev/pkg/logging"
|
||||||
|
"knative.dev/pkg/ptr"
|
||||||
|
"knative.dev/pkg/system"
|
||||||
|
"knative.dev/pkg/webhook"
|
||||||
|
certresources "knative.dev/pkg/webhook/certificates/resources"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Reconciler implements an AdmissionController for altering PodSpecable
|
||||||
|
// resources that are the subject of a particular type of Binding.
|
||||||
|
// The two key methods are:
|
||||||
|
// 1. reconcileMutatingWebhook: which enumerates all of the Bindings and
|
||||||
|
// compiles a list of resource types that should be intercepted by our
|
||||||
|
// webhook. It also builds an index that can be used to efficiently
|
||||||
|
// handle Admit requests.
|
||||||
|
// 2. Admit: which leverages the index built by the Reconciler to apply
|
||||||
|
// mutations to resources.
|
||||||
|
type Reconciler struct {
|
||||||
|
Name string
|
||||||
|
HandlerPath string
|
||||||
|
SecretName string
|
||||||
|
|
||||||
|
Client kubernetes.Interface
|
||||||
|
MWHLister admissionlisters.MutatingWebhookConfigurationLister
|
||||||
|
SecretLister corelisters.SecretLister
|
||||||
|
ListAll ListAll
|
||||||
|
|
||||||
|
// WithContext is a callback that infuses the context supplied to
|
||||||
|
// Do/Undo with additional context to enable them to complete their
|
||||||
|
// respective tasks.
|
||||||
|
WithContext BindableContext
|
||||||
|
|
||||||
|
// lock protects access to exact and inexact
|
||||||
|
lock sync.RWMutex
|
||||||
|
exact exactMatcher
|
||||||
|
inexact inexactMatcher
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ controller.Reconciler = (*Reconciler)(nil)
|
||||||
|
var _ webhook.AdmissionController = (*Reconciler)(nil)
|
||||||
|
|
||||||
|
// We need to specifically exclude our deployment(s) from consideration, but this provides a way
|
||||||
|
// of excluding other things as well.
|
||||||
|
var (
|
||||||
|
ExclusionSelector = metav1.LabelSelector{
|
||||||
|
MatchExpressions: []metav1.LabelSelectorRequirement{{
|
||||||
|
Key: duck.BindingExcludeLabel,
|
||||||
|
Operator: metav1.LabelSelectorOpNotIn,
|
||||||
|
Values: []string{"true"},
|
||||||
|
}},
|
||||||
|
// TODO(mattmoor): Consider also having a GVR-based one, e.g.
|
||||||
|
// foobindings.blah.knative.dev/exclude: "true"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Reconcile implements controller.Reconciler
|
||||||
|
func (ac *Reconciler) Reconcile(ctx context.Context, key string) error {
|
||||||
|
// Look up the webhook secret, and fetch the CA cert bundle.
|
||||||
|
secret, err := ac.SecretLister.Secrets(system.Namespace()).Get(ac.SecretName)
|
||||||
|
if err != nil {
|
||||||
|
logging.FromContext(ctx).Errorf("Error fetching secret: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
caCert, ok := secret.Data[certresources.CACert]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("secret %q is missing %q key", ac.SecretName, certresources.CACert)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reconcile the webhook configuration.
|
||||||
|
return ac.reconcileMutatingWebhook(ctx, caCert)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Path implements AdmissionController
|
||||||
|
func (ac *Reconciler) Path() string {
|
||||||
|
return ac.HandlerPath
|
||||||
|
}
|
||||||
|
|
||||||
|
// Admit implements AdmissionController
|
||||||
|
func (ac *Reconciler) Admit(ctx context.Context, request *admissionv1beta1.AdmissionRequest) *admissionv1beta1.AdmissionResponse {
|
||||||
|
switch request.Operation {
|
||||||
|
case admissionv1beta1.Create, admissionv1beta1.Update:
|
||||||
|
default:
|
||||||
|
logging.FromContext(ctx).Infof("Unhandled webhook operation, letting it through %v", request.Operation)
|
||||||
|
return &admissionv1beta1.AdmissionResponse{Allowed: true}
|
||||||
|
}
|
||||||
|
|
||||||
|
orig := &duckv1.WithPod{}
|
||||||
|
decoder := json.NewDecoder(bytes.NewBuffer(request.Object.Raw))
|
||||||
|
if err := decoder.Decode(&orig); err != nil {
|
||||||
|
return webhook.MakeErrorStatus("unable to decode object: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look up the Bindable for this resource.
|
||||||
|
fb := func() Bindable {
|
||||||
|
ac.lock.RLock()
|
||||||
|
defer ac.lock.RUnlock()
|
||||||
|
|
||||||
|
// Always try to find an exact match first.
|
||||||
|
if sb, ok := ac.exact.Get(exactKey{
|
||||||
|
Group: request.Kind.Group,
|
||||||
|
Kind: request.Kind.Kind,
|
||||||
|
Namespace: request.Namespace,
|
||||||
|
Name: orig.Name,
|
||||||
|
}); ok {
|
||||||
|
return sb
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next look for inexact matches.
|
||||||
|
if sb, ok := ac.inexact.Get(inexactKey{
|
||||||
|
Group: request.Kind.Group,
|
||||||
|
Kind: request.Kind.Kind,
|
||||||
|
Namespace: request.Namespace,
|
||||||
|
}, labels.Set(orig.Labels)); ok {
|
||||||
|
return sb
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}()
|
||||||
|
if fb == nil {
|
||||||
|
// This doesn't apply!
|
||||||
|
return &admissionv1beta1.AdmissionResponse{Allowed: true}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Callback into the user's code to setup the context with additional
|
||||||
|
// information needed to perform the mutation.
|
||||||
|
if ac.WithContext != nil {
|
||||||
|
var err error
|
||||||
|
ctx, err = ac.WithContext(ctx, fb)
|
||||||
|
if err != nil {
|
||||||
|
return webhook.MakeErrorStatus("unable to setup binding context: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mutate a copy according to the deletion state of the Bindable.
|
||||||
|
delta := orig.DeepCopy()
|
||||||
|
if fb.GetDeletionTimestamp() != nil {
|
||||||
|
fb.Undo(ctx, delta)
|
||||||
|
} else {
|
||||||
|
fb.Do(ctx, delta)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Synthesize a patch from the changes and return it in our AdmissionResponse
|
||||||
|
patchBytes, err := duck.CreateBytePatch(orig, delta)
|
||||||
|
if err != nil {
|
||||||
|
return webhook.MakeErrorStatus("unable to create patch with binding: %v", err)
|
||||||
|
}
|
||||||
|
return &admissionv1beta1.AdmissionResponse{
|
||||||
|
Patch: patchBytes,
|
||||||
|
Allowed: true,
|
||||||
|
PatchType: func() *admissionv1beta1.PatchType {
|
||||||
|
pt := admissionv1beta1.PatchTypeJSONPatch
|
||||||
|
return &pt
|
||||||
|
}(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ac *Reconciler) reconcileMutatingWebhook(ctx context.Context, caCert []byte) error {
|
||||||
|
// Build a deduplicated list of all of the GVKs we see.
|
||||||
|
// We seed the Kubernetes built-ins.
|
||||||
|
gks := map[schema.GroupKind]sets.String{
|
||||||
|
appsv1.SchemeGroupVersion.WithKind("Deployment").GroupKind(): sets.NewString("v1"),
|
||||||
|
appsv1.SchemeGroupVersion.WithKind("StatefulSet").GroupKind(): sets.NewString("v1"),
|
||||||
|
appsv1.SchemeGroupVersion.WithKind("DaemonSet").GroupKind(): sets.NewString("v1"),
|
||||||
|
batchv1.SchemeGroupVersion.WithKind("Job").GroupKind(): sets.NewString("v1"),
|
||||||
|
}
|
||||||
|
|
||||||
|
// When reconciling the webhook, enumerate all of the bindings, so that
|
||||||
|
// we can index them to efficiently respond to webhook requests.
|
||||||
|
fbs, err := ac.ListAll()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
exact := make(exactMatcher, len(fbs))
|
||||||
|
inexact := make(inexactMatcher, len(fbs))
|
||||||
|
for _, fb := range fbs {
|
||||||
|
ref := fb.GetSubject()
|
||||||
|
gv, err := schema.ParseGroupVersion(ref.APIVersion)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
gk := schema.GroupKind{
|
||||||
|
Group: gv.Group,
|
||||||
|
Kind: ref.Kind,
|
||||||
|
}
|
||||||
|
set := gks[gk]
|
||||||
|
if set == nil {
|
||||||
|
set = sets.NewString()
|
||||||
|
}
|
||||||
|
set.Insert(gv.Version)
|
||||||
|
gks[gk] = set
|
||||||
|
|
||||||
|
if ref.Name != "" {
|
||||||
|
exact.Add(exactKey{
|
||||||
|
Group: gk.Group,
|
||||||
|
Kind: gk.Kind,
|
||||||
|
Namespace: ref.Namespace,
|
||||||
|
Name: ref.Name,
|
||||||
|
}, fb)
|
||||||
|
} else {
|
||||||
|
selector, err := metav1.LabelSelectorAsSelector(ref.Selector)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
inexact.Add(inexactKey{
|
||||||
|
Group: gk.Group,
|
||||||
|
Kind: gk.Kind,
|
||||||
|
Namespace: ref.Namespace,
|
||||||
|
}, selector, fb)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update our indices
|
||||||
|
func() {
|
||||||
|
ac.lock.Lock()
|
||||||
|
defer ac.lock.Unlock()
|
||||||
|
ac.exact = exact
|
||||||
|
ac.inexact = inexact
|
||||||
|
}()
|
||||||
|
|
||||||
|
var rules []admissionregistrationv1beta1.RuleWithOperations
|
||||||
|
for gk, versions := range gks {
|
||||||
|
plural := strings.ToLower(inflect.Pluralize(gk.Kind))
|
||||||
|
|
||||||
|
rules = append(rules, admissionregistrationv1beta1.RuleWithOperations{
|
||||||
|
Operations: []admissionregistrationv1beta1.OperationType{
|
||||||
|
admissionregistrationv1beta1.Create,
|
||||||
|
admissionregistrationv1beta1.Update,
|
||||||
|
},
|
||||||
|
Rule: admissionregistrationv1beta1.Rule{
|
||||||
|
APIGroups: []string{gk.Group},
|
||||||
|
APIVersions: versions.List(),
|
||||||
|
Resources: []string{plural + "/*"},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort the rules by Group, Version, Kind so that things are deterministically ordered.
|
||||||
|
sort.Slice(rules, func(i, j int) bool {
|
||||||
|
lhs, rhs := rules[i], rules[j]
|
||||||
|
if lhs.APIGroups[0] != rhs.APIGroups[0] {
|
||||||
|
return lhs.APIGroups[0] < rhs.APIGroups[0]
|
||||||
|
}
|
||||||
|
if lhs.APIVersions[0] != rhs.APIVersions[0] {
|
||||||
|
return lhs.APIVersions[0] < rhs.APIVersions[0]
|
||||||
|
}
|
||||||
|
return lhs.Resources[0] < rhs.Resources[0]
|
||||||
|
})
|
||||||
|
|
||||||
|
configuredWebhook, err := ac.MWHLister.Get(ac.Name)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error retrieving webhook: %v", err)
|
||||||
|
}
|
||||||
|
webhook := configuredWebhook.DeepCopy()
|
||||||
|
|
||||||
|
// Use the "Equivalent" match policy so that we don't need to enumerate versions for same-types.
|
||||||
|
// This is only supported by 1.15+ clusters.
|
||||||
|
matchPolicy := admissionregistrationv1beta1.Equivalent
|
||||||
|
|
||||||
|
for i, wh := range webhook.Webhooks {
|
||||||
|
if wh.Name != webhook.Name {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
webhook.Webhooks[i].MatchPolicy = &matchPolicy
|
||||||
|
webhook.Webhooks[i].Rules = rules
|
||||||
|
webhook.Webhooks[i].NamespaceSelector = &ExclusionSelector
|
||||||
|
webhook.Webhooks[i].ObjectSelector = &ExclusionSelector // 1.15+ only
|
||||||
|
webhook.Webhooks[i].ClientConfig.CABundle = caCert
|
||||||
|
if webhook.Webhooks[i].ClientConfig.Service == nil {
|
||||||
|
return fmt.Errorf("missing service reference for webhook: %s", wh.Name)
|
||||||
|
}
|
||||||
|
webhook.Webhooks[i].ClientConfig.Service.Path = ptr.String(ac.Path())
|
||||||
|
}
|
||||||
|
|
||||||
|
if ok, err := kmp.SafeEqual(configuredWebhook, webhook); err != nil {
|
||||||
|
return fmt.Errorf("error diffing webhooks: %v", err)
|
||||||
|
} else if !ok {
|
||||||
|
logging.FromContext(ctx).Info("Updating webhook")
|
||||||
|
mwhclient := ac.Client.AdmissionregistrationV1beta1().MutatingWebhookConfigurations()
|
||||||
|
if _, err := mwhclient.Update(webhook); err != nil {
|
||||||
|
return fmt.Errorf("failed to update webhook: %v", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logging.FromContext(ctx).Info("Webhook is valid")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,375 @@
|
||||||
|
/*
|
||||||
|
Copyright 2019 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 psbinding
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/api/equality"
|
||||||
|
apierrs "k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
"k8s.io/apimachinery/pkg/types"
|
||||||
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
|
"k8s.io/client-go/dynamic"
|
||||||
|
"k8s.io/client-go/tools/cache"
|
||||||
|
"k8s.io/client-go/tools/record"
|
||||||
|
"knative.dev/pkg/apis"
|
||||||
|
"knative.dev/pkg/apis/duck"
|
||||||
|
duckv1 "knative.dev/pkg/apis/duck/v1"
|
||||||
|
"knative.dev/pkg/controller"
|
||||||
|
"knative.dev/pkg/kmeta"
|
||||||
|
"knative.dev/pkg/logging"
|
||||||
|
"knative.dev/pkg/tracker"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BaseReconciler helps implement controller.Reconciler for Binding resources.
|
||||||
|
type BaseReconciler struct {
|
||||||
|
// The GVR of the "primary key" resource for this reconciler.
|
||||||
|
// This is used along with the DynamicClient for updating the status
|
||||||
|
// and managing finalizers of the resources being reconciled.
|
||||||
|
GVR schema.GroupVersionResource
|
||||||
|
|
||||||
|
// Get is a callback that fetches the Bindable with the provided name
|
||||||
|
// and namespace (for this GVR).
|
||||||
|
Get func(namespace string, name string) (Bindable, error)
|
||||||
|
|
||||||
|
// WithContext is a callback that infuses the context supplied to
|
||||||
|
// Do/Undo with additional context to enable them to complete their
|
||||||
|
// respective tasks.
|
||||||
|
WithContext BindableContext
|
||||||
|
|
||||||
|
// DynamicClient is used to patch subjects and apply mutations to
|
||||||
|
// Bindable resources (determined by GVR) to reflect status updates.
|
||||||
|
DynamicClient dynamic.Interface
|
||||||
|
|
||||||
|
// Factory is used for producing listers for the object references we
|
||||||
|
// encounter.
|
||||||
|
Factory duck.InformerFactory
|
||||||
|
|
||||||
|
// The tracker builds an index of what resources are watching other
|
||||||
|
// resources so that we can immediately react to changes to changes in
|
||||||
|
// tracked resources.
|
||||||
|
Tracker tracker.Interface
|
||||||
|
|
||||||
|
// Recorder is an event recorder for recording Event resources to the
|
||||||
|
// Kubernetes API.
|
||||||
|
Recorder record.EventRecorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that our Reconciler implements controller.Reconciler
|
||||||
|
var _ controller.Reconciler = (*BaseReconciler)(nil)
|
||||||
|
|
||||||
|
// Reconcile implements controller.Reconciler
|
||||||
|
func (r *BaseReconciler) Reconcile(ctx context.Context, key string) error {
|
||||||
|
// Convert the namespace/name string into a distinct namespace and name
|
||||||
|
namespace, name, err := cache.SplitMetaNamespaceKey(key)
|
||||||
|
if err != nil {
|
||||||
|
logging.FromContext(ctx).Errorf("invalid resource key: %s", key)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the resource with this namespace/name.
|
||||||
|
original, err := r.Get(namespace, name)
|
||||||
|
if apierrs.IsNotFound(err) {
|
||||||
|
// The resource may no longer exist, in which case we stop processing.
|
||||||
|
logging.FromContext(ctx).Errorf("resource %q no longer exists", key)
|
||||||
|
return nil
|
||||||
|
} else if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Don't modify the informers copy.
|
||||||
|
resource := original.DeepCopyObject().(Bindable)
|
||||||
|
|
||||||
|
// Reconcile this copy of the resource and then write back any status
|
||||||
|
// updates regardless of whether the reconciliation errored out.
|
||||||
|
reconcileErr := r.reconcile(ctx, resource)
|
||||||
|
if equality.Semantic.DeepEqual(original.GetBindingStatus(), resource.GetBindingStatus()) {
|
||||||
|
// If we didn't change anything then don't call updateStatus.
|
||||||
|
// This is important because the copy we loaded from the informer's
|
||||||
|
// cache may be stale and we don't want to overwrite a prior update
|
||||||
|
// to status with this stale state.
|
||||||
|
} else if err = r.UpdateStatus(ctx, resource); err != nil {
|
||||||
|
logging.FromContext(ctx).Warnw("Failed to update resource status", zap.Error(err))
|
||||||
|
r.Recorder.Eventf(resource, corev1.EventTypeWarning, "UpdateFailed",
|
||||||
|
"Failed to update status for %q: %v", resource.GetName(), err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if reconcileErr != nil {
|
||||||
|
r.Recorder.Event(resource, corev1.EventTypeWarning, "InternalError", reconcileErr.Error())
|
||||||
|
}
|
||||||
|
return reconcileErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// reconcile is a reference implementation of a simple Binding flow, it and
|
||||||
|
// Reconcile may be overridden and the exported helpers used to implement a
|
||||||
|
// customized reconciliation flow.
|
||||||
|
func (r *BaseReconciler) reconcile(ctx context.Context, fb Bindable) error {
|
||||||
|
if fb.GetDeletionTimestamp() != nil {
|
||||||
|
// Check for a DeletionTimestamp. If present, elide the normal
|
||||||
|
// reconcile logic and do our finalizer handling.
|
||||||
|
return r.ReconcileDeletion(ctx, fb)
|
||||||
|
}
|
||||||
|
// Make sure that our conditions have been initialized.
|
||||||
|
fb.GetBindingStatus().InitializeConditions()
|
||||||
|
|
||||||
|
// Make sure that the resource has a Finalizer configured, which
|
||||||
|
// enables us to undo our binding upon deletion.
|
||||||
|
if err := r.EnsureFinalizer(ctx, fb); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform our Binding's Do() method on the subject(s) of the Binding.
|
||||||
|
if err := r.ReconcileSubject(ctx, fb, fb.Do); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the observed generation once we have successfully reconciled
|
||||||
|
// our spec.
|
||||||
|
fb.GetBindingStatus().SetObservedGeneration(fb.GetGeneration())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReconcileDeletion handles reconcile a resource that is being deleted, which
|
||||||
|
// amounts to properly finalizing the resource.
|
||||||
|
func (r *BaseReconciler) ReconcileDeletion(ctx context.Context, fb Bindable) error {
|
||||||
|
// If we are not the controller finalizing this resource, then we
|
||||||
|
// are done.
|
||||||
|
if !r.IsFinalizing(ctx, fb) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it is our turn to finalize the Binding, then first undo the effect
|
||||||
|
// of our Binding on the resource.
|
||||||
|
logging.FromContext(ctx).Infof("Removing the binding for %s", fb.GetName())
|
||||||
|
if err := r.ReconcileSubject(ctx, fb, fb.Undo); apierrs.IsNotFound(err) {
|
||||||
|
// If the subject has been deleted, then there is nothing to undo.
|
||||||
|
} else if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Once the Binding has been undone, remove our finalizer allowing the
|
||||||
|
// Binding resource's deletion to progress.
|
||||||
|
return r.RemoveFinalizer(ctx, fb)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsFinalizing determines whether it is our reconciler's turn to finalize a
|
||||||
|
// resource in the process of being deleted. This means that our finalizer is
|
||||||
|
// at the head of the metadata.finalizers list.
|
||||||
|
func (r *BaseReconciler) IsFinalizing(ctx context.Context, fb kmeta.Accessor) bool {
|
||||||
|
return len(fb.GetFinalizers()) != 0 && fb.GetFinalizers()[0] == r.GVR.GroupResource().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnsureFinalizer makes sure that the provided resource has a finalizer in the
|
||||||
|
// form of this BaseReconciler's GVR's stringified GroupResource.
|
||||||
|
func (r *BaseReconciler) EnsureFinalizer(ctx context.Context, fb kmeta.Accessor) error {
|
||||||
|
// If it has the finalizer, then we're done.
|
||||||
|
finalizers := sets.NewString(fb.GetFinalizers()...)
|
||||||
|
if finalizers.Has(r.GVR.GroupResource().String()) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it doesn't have our finalizer, then synthesize a patch to add it.
|
||||||
|
patch, err := json.Marshal(map[string]interface{}{
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"finalizers": append(fb.GetFinalizers(), r.GVR.GroupResource().String()),
|
||||||
|
"resourceVersion": fb.GetResourceVersion(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ... and apply it.
|
||||||
|
_, err = r.DynamicClient.Resource(r.GVR).Namespace(fb.GetNamespace()).Patch(fb.GetName(),
|
||||||
|
types.MergePatchType, patch, metav1.PatchOptions{})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveFinalizer is the dual of EnsureFinalizer, it removes our finalizer from
|
||||||
|
// the Binding resource
|
||||||
|
func (r *BaseReconciler) RemoveFinalizer(ctx context.Context, fb kmeta.Accessor) error {
|
||||||
|
logging.FromContext(ctx).Info("Removing Finalizer")
|
||||||
|
|
||||||
|
// Synthesize a patch removing our finalizer from the head of the
|
||||||
|
// finalizer list.
|
||||||
|
patch, err := json.Marshal(map[string]interface{}{
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"finalizers": fb.GetFinalizers()[1:],
|
||||||
|
"resourceVersion": fb.GetResourceVersion(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ... and apply it.
|
||||||
|
_, err = r.DynamicClient.Resource(r.GVR).Namespace(fb.GetNamespace()).Patch(fb.GetName(),
|
||||||
|
types.MergePatchType, patch, metav1.PatchOptions{})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReconcileSubject handles applying the provided Binding "mutation" (Do or
|
||||||
|
// Undo) to the Binding's subject(s).
|
||||||
|
func (r *BaseReconciler) ReconcileSubject(ctx context.Context, fb Bindable, mutation Mutation) error {
|
||||||
|
// Access the subject of our Binding and have the tracker queue this
|
||||||
|
// Bindable whenever it changes.
|
||||||
|
subject := fb.GetSubject()
|
||||||
|
if err := r.Tracker.TrackReference(subject, fb); err != nil {
|
||||||
|
logging.FromContext(ctx).Errorf("Error tracking subject %v: %v", subject, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine the GroupVersionResource of the subject reference
|
||||||
|
gv, err := schema.ParseGroupVersion(subject.APIVersion)
|
||||||
|
if err != nil {
|
||||||
|
logging.FromContext(ctx).Errorf("Error parsing GroupVersion %v: %v", subject.APIVersion, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
gvr := apis.KindToResource(gv.WithKind(subject.Kind))
|
||||||
|
|
||||||
|
// Use the GVR of the subject(s) to get ahold of a lister that we can
|
||||||
|
// use to fetch our PodSpecable resources.
|
||||||
|
_, lister, err := r.Factory.Get(gvr)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error getting a lister for resource '%+v': %v", gvr, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Based on the type of subject reference, build up a list of referents.
|
||||||
|
var referents []*duckv1.WithPod
|
||||||
|
if subject.Name != "" {
|
||||||
|
// If name is specified, then fetch it from the lister and turn
|
||||||
|
// it into a singleton list.
|
||||||
|
psObj, err := lister.ByNamespace(subject.Namespace).Get(subject.Name)
|
||||||
|
if apierrs.IsNotFound(err) {
|
||||||
|
fb.GetBindingStatus().MarkBindingUnavailable("SubjectMissing", err.Error())
|
||||||
|
return err
|
||||||
|
} else if err != nil {
|
||||||
|
return fmt.Errorf("error fetching Pod Speccable %v: %v", subject, err)
|
||||||
|
}
|
||||||
|
referents = append(referents, psObj.(*duckv1.WithPod))
|
||||||
|
} else {
|
||||||
|
// Otherwise, the subject is referenced by selector, so compile
|
||||||
|
// the selector and pass it to the lister.
|
||||||
|
selector, err := metav1.LabelSelectorAsSelector(subject.Selector)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
psObjs, err := lister.ByNamespace(subject.Namespace).List(selector)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error fetching Pod Speccable %v: %v", subject, err)
|
||||||
|
}
|
||||||
|
// Type cast the returned resources into our referent list.
|
||||||
|
for _, psObj := range psObjs {
|
||||||
|
referents = append(referents, psObj.(*duckv1.WithPod))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Callback into the user's code to setup the context with additional
|
||||||
|
// information needed to perform the mutation.
|
||||||
|
if r.WithContext != nil {
|
||||||
|
ctx, err = r.WithContext(ctx, fb)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For each of the referents, apply the mutation.
|
||||||
|
eg := errgroup.Group{}
|
||||||
|
for _, ps := range referents {
|
||||||
|
ps := ps
|
||||||
|
eg.Go(func() error {
|
||||||
|
// Do the binding to the pod speccable.
|
||||||
|
orig := ps.DeepCopy()
|
||||||
|
mutation(ctx, ps)
|
||||||
|
|
||||||
|
// If nothing changed, then bail early.
|
||||||
|
if equality.Semantic.DeepEqual(orig, ps) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we encountered changes, then synthesize and apply
|
||||||
|
// a patch.
|
||||||
|
patchBytes, err := duck.CreateBytePatch(orig, ps)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(mattmoor): This might fail because a binding changed after
|
||||||
|
// a Job started or completed, which can be fine. Consider treating
|
||||||
|
// certain error codes as acceptable.
|
||||||
|
_, err = r.DynamicClient.Resource(gvr).Namespace(ps.Namespace).Patch(
|
||||||
|
ps.Name, types.JSONPatchType, patchBytes, metav1.PatchOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "failed binding subject "+ps.Name)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Based on the success of the referent binding, update the Binding's readiness.
|
||||||
|
if err := eg.Wait(); err != nil {
|
||||||
|
fb.GetBindingStatus().MarkBindingUnavailable("BindingFailed", err.Error())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fb.GetBindingStatus().MarkBindingAvailable()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateStatus updates the status of the resource. Caller is responsible for
|
||||||
|
// checking for semantic differences before calling.
|
||||||
|
func (r *BaseReconciler) UpdateStatus(ctx context.Context, desired Bindable) error {
|
||||||
|
actual, err := r.Get(desired.GetNamespace(), desired.GetName())
|
||||||
|
if err != nil {
|
||||||
|
logging.FromContext(ctx).Errorf("Error fetching actual: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to unstructured for use with the dynamic client.
|
||||||
|
ua, err := duck.ToUnstructured(actual)
|
||||||
|
if err != nil {
|
||||||
|
logging.FromContext(ctx).Errorf("Error converting actual: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ud, err := duck.ToUnstructured(desired)
|
||||||
|
if err != nil {
|
||||||
|
logging.FromContext(ctx).Errorf("Error converting desired: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// One last check that status changed.
|
||||||
|
actualStatus := ua.Object["status"]
|
||||||
|
desiredStatus := ud.Object["status"]
|
||||||
|
if reflect.DeepEqual(actualStatus, desiredStatus) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy the status over to the refetched resource to avoid updating
|
||||||
|
// anything other than status.
|
||||||
|
forUpdate := ua
|
||||||
|
forUpdate.Object["status"] = desiredStatus
|
||||||
|
_, err = r.DynamicClient.Resource(r.GVR).Namespace(desired.GetNamespace()).UpdateStatus(
|
||||||
|
forUpdate, metav1.UpdateOptions{})
|
||||||
|
return err
|
||||||
|
}
|
|
@ -33,6 +33,7 @@ import (
|
||||||
istiolisters "knative.dev/pkg/client/istio/listers/networking/v1alpha3"
|
istiolisters "knative.dev/pkg/client/istio/listers/networking/v1alpha3"
|
||||||
"knative.dev/pkg/reconciler/testing"
|
"knative.dev/pkg/reconciler/testing"
|
||||||
pkgtesting "knative.dev/pkg/testing"
|
pkgtesting "knative.dev/pkg/testing"
|
||||||
|
pkgducktesting "knative.dev/pkg/testing/duck"
|
||||||
)
|
)
|
||||||
|
|
||||||
var clientSetSchemes = []func(*runtime.Scheme) error{
|
var clientSetSchemes = []func(*runtime.Scheme) error{
|
||||||
|
@ -40,6 +41,7 @@ var clientSetSchemes = []func(*runtime.Scheme) error{
|
||||||
fakeistioclientset.AddToScheme,
|
fakeistioclientset.AddToScheme,
|
||||||
autoscalingv2beta1.AddToScheme,
|
autoscalingv2beta1.AddToScheme,
|
||||||
pkgtesting.AddToScheme,
|
pkgtesting.AddToScheme,
|
||||||
|
pkgducktesting.AddToScheme,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Listers is used to synthesize informer-style Listers from fixed lists of resources in tests.
|
// Listers is used to synthesize informer-style Listers from fixed lists of resources in tests.
|
||||||
|
@ -95,6 +97,11 @@ func (l *Listers) GetTestObjects() []runtime.Object {
|
||||||
return l.sorter.ObjectsForSchemeFunc(pkgtesting.AddToScheme)
|
return l.sorter.ObjectsForSchemeFunc(pkgtesting.AddToScheme)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetDuckObjects filters the Listers initial list of objects to types defined in knative/pkg
|
||||||
|
func (l *Listers) GetDuckObjects() []runtime.Object {
|
||||||
|
return l.sorter.ObjectsForSchemeFunc(pkgducktesting.AddToScheme)
|
||||||
|
}
|
||||||
|
|
||||||
// GetHorizontalPodAutoscalerLister gets lister for HorizontalPodAutoscaler resources.
|
// GetHorizontalPodAutoscalerLister gets lister for HorizontalPodAutoscaler resources.
|
||||||
func (l *Listers) GetHorizontalPodAutoscalerLister() autoscalingv2beta1listers.HorizontalPodAutoscalerLister {
|
func (l *Listers) GetHorizontalPodAutoscalerLister() autoscalingv2beta1listers.HorizontalPodAutoscalerLister {
|
||||||
return autoscalingv2beta1listers.NewHorizontalPodAutoscalerLister(l.IndexerFor(&autoscalingv2beta1.HorizontalPodAutoscaler{}))
|
return autoscalingv2beta1listers.NewHorizontalPodAutoscalerLister(l.IndexerFor(&autoscalingv2beta1.HorizontalPodAutoscaler{}))
|
||||||
|
|
|
@ -156,8 +156,10 @@ function default_build_test_runner() {
|
||||||
markdown_build_tests || failed=1
|
markdown_build_tests || failed=1
|
||||||
# For documentation PRs, just check the md files
|
# For documentation PRs, just check the md files
|
||||||
(( IS_DOCUMENTATION_PR )) && return ${failed}
|
(( IS_DOCUMENTATION_PR )) && return ${failed}
|
||||||
|
# Don't merge these two lines, or return code will always be 0.
|
||||||
|
local go_pkg_dirs
|
||||||
|
go_pkg_dirs="$(go list ./...)" || return 1
|
||||||
# Skip build test if there is no go code
|
# Skip build test if there is no go code
|
||||||
local go_pkg_dirs="$(go list ./...)"
|
|
||||||
[[ -z "${go_pkg_dirs}" ]] && return ${failed}
|
[[ -z "${go_pkg_dirs}" ]] && return ${failed}
|
||||||
# Ensure all the code builds
|
# Ensure all the code builds
|
||||||
subheader "Checking that go code builds"
|
subheader "Checking that go code builds"
|
||||||
|
|
Loading…
Reference in New Issue