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]]
|
||||
branch = "master"
|
||||
digest = "1:09a00f21eaeeae387d973fd3f82cf06a8627f88566b4e6811ba97003dd207eac"
|
||||
digest = "1:bfb453e78558b31d99d1335840a7d92328fc85fe35c58d4c5179caddb28f7416"
|
||||
name = "knative.dev/pkg"
|
||||
packages = [
|
||||
"apis",
|
||||
|
@ -952,18 +952,18 @@
|
|||
"metrics/metricskey",
|
||||
]
|
||||
pruneopts = "T"
|
||||
revision = "944655d6cc17ceb861b0764046c56a465fe5bb75"
|
||||
revision = "c259ed6f2e751582d4586c2d3a4e1cb98e526068"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:85fe0cadd6ab83f3d7f948c60b6d422dc9cd16664246249968dab5d828ae8dfd"
|
||||
digest = "1:6e839a9b4183b0fbf0df609fe9a6d226c941fa9221052ba0075b25cb353588a5"
|
||||
name = "knative.dev/test-infra"
|
||||
packages = [
|
||||
"scripts",
|
||||
"tools/dep-collector",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "2c4cd9df33c648a86d95bdd4119386cba04cde56"
|
||||
revision = "9363d2f4cafd2ad3c1bfc925f447c91a1776be00"
|
||||
|
||||
[[projects]]
|
||||
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 (
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"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
|
||||
|
@ -26,3 +28,42 @@ type InformerFactory interface {
|
|||
// Get returns a synced Informer/Lister pair for the provided schema.GroupVersionResource.
|
||||
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"
|
||||
|
||||
"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
|
||||
// converts it to unstructured.Unstructured. We take OneOfOurs in place
|
||||
// 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
|
||||
${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 \
|
||||
--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
|
||||
}
|
||||
|
||||
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 {
|
||||
r.MinNodes = DefaultGKEMinNodes
|
||||
}
|
||||
|
@ -192,6 +184,14 @@ func (gc *GKECluster) Acquire() error {
|
|||
request := gc.Request.DeepCopy()
|
||||
// We are going to use request for creating cluster, set its 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
|
||||
// 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"
|
||||
"knative.dev/pkg/reconciler/testing"
|
||||
pkgtesting "knative.dev/pkg/testing"
|
||||
pkgducktesting "knative.dev/pkg/testing/duck"
|
||||
)
|
||||
|
||||
var clientSetSchemes = []func(*runtime.Scheme) error{
|
||||
|
@ -40,6 +41,7 @@ var clientSetSchemes = []func(*runtime.Scheme) error{
|
|||
fakeistioclientset.AddToScheme,
|
||||
autoscalingv2beta1.AddToScheme,
|
||||
pkgtesting.AddToScheme,
|
||||
pkgducktesting.AddToScheme,
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (l *Listers) GetHorizontalPodAutoscalerLister() autoscalingv2beta1listers.HorizontalPodAutoscalerLister {
|
||||
return autoscalingv2beta1listers.NewHorizontalPodAutoscalerLister(l.IndexerFor(&autoscalingv2beta1.HorizontalPodAutoscaler{}))
|
||||
|
|
|
@ -156,8 +156,10 @@ function default_build_test_runner() {
|
|||
markdown_build_tests || failed=1
|
||||
# For documentation PRs, just check the md files
|
||||
(( 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
|
||||
local go_pkg_dirs="$(go list ./...)"
|
||||
[[ -z "${go_pkg_dirs}" ]] && return ${failed}
|
||||
# Ensure all the code builds
|
||||
subheader "Checking that go code builds"
|
||||
|
|
Loading…
Reference in New Issue