Create a library for Reconciler dependency injection. (#423)

This creates a base library that enables us to leverage the dependency
graph to convey injection dependencies.  Our controllers will start
to get their informers off of a `context.Context` via `fooinformers.Get(ctx)`.

By simply linking `fooinformers` to access the informer in this way, we trigger
a cascade of `init`-time registrations for the informer, and its transitive
dependencies (e.g. shared informer factory, client).

The `ctx` is infused with these informers in `package main` by linking the
appropriate reconcilers (transitively registering everything) and then calling
`sharedmain.Main()` (from `github.com/knative/pkg/injection/sharedmain`).
This commit is contained in:
Matt Moore 2019-06-05 07:37:38 -07:00 committed by Knative Prow Robot
parent 678bb6612d
commit 4c630bb1bb
62 changed files with 5308 additions and 1 deletions

9
Gopkg.lock generated
View File

@ -836,7 +836,7 @@
version = "kubernetes-1.12.6"
[[projects]]
digest = "1:b7dd0420e85cb2968ffb945f2810ea6c796dc2a08660618e2200c08c596f0624"
digest = "1:07be043078c2dc2ee33e81278b264a84f364c6d711811d2932aa42212fc4f2ae"
name = "k8s.io/client-go"
packages = [
"discovery",
@ -984,9 +984,11 @@
"pkg/apis/clientauthentication/v1beta1",
"pkg/version",
"plugin/pkg/client/auth/exec",
"plugin/pkg/client/auth/gcp",
"rest",
"rest/watch",
"testing",
"third_party/forked/golang/template",
"tools/auth",
"tools/cache",
"tools/clientcmd",
@ -1004,6 +1006,7 @@
"util/flowcontrol",
"util/homedir",
"util/integer",
"util/jsonpath",
"util/retry",
"util/workqueue",
]
@ -1142,11 +1145,15 @@
"k8s.io/client-go/dynamic",
"k8s.io/client-go/dynamic/fake",
"k8s.io/client-go/informers",
"k8s.io/client-go/informers/apps/v1",
"k8s.io/client-go/informers/autoscaling/v1",
"k8s.io/client-go/informers/autoscaling/v2beta1",
"k8s.io/client-go/informers/core/v1",
"k8s.io/client-go/kubernetes",
"k8s.io/client-go/kubernetes/fake",
"k8s.io/client-go/kubernetes/typed/admissionregistration/v1beta1",
"k8s.io/client-go/kubernetes/typed/core/v1",
"k8s.io/client-go/plugin/pkg/client/auth/gcp",
"k8s.io/client-go/rest",
"k8s.io/client-go/testing",
"k8s.io/client-go/tools/cache",

5
injection/OWNERS Normal file
View File

@ -0,0 +1,5 @@
# The OWNERS file is used by prow to automatically merge approved PRs.
approvers:
- mattmoor
- n3wscott

182
injection/README.md Normal file
View File

@ -0,0 +1,182 @@
# Knative Dependency Injection
This library supports the production of controller processes with
minimal boilerplate outside of the reconciler implementation.
## Registering Controllers
To adopt this model of controller construction, implementations
should start with the following controller constructor:
```go
import (
"context"
"github.com/knative/pkg/configmap"
"github.com/knative/pkg/controller"
"github.com/knative/pkg/injection"
"github.com/knative/pkg/logging"
)
func NewController(ctx context.Context, cmw configmap.Watcher) *controller.Impl {
logger := logging.FromContext(ctx)
// TODO(you): Access informers
c := &Reconciler{
// TODO(you): Pass listers, clients, and other stuff.
}
impl := controller.NewImpl(c, logger, "NameOfController")
// TODO(you): Set up event handlers.
return impl
}
// Register our controller process.
func init() {
injection.Default.RegisterController(NewController)
}
```
## Consuming Informers
Knative controllers use "informers" to set up the various event hooks needed to
queue work, and pass the "listers" fed by the informers' caches to the nested
"Reconciler" for accessing objects.
Our controller constructor is passed a `context.Context` onto which we inject
any informers we access. The accessors for these informers are in little stub
libraries, which we have hand rolled for Kubernetes (more on how to generate
these below).
```go
import (
// These are how you access a client or informer off of the "ctx" passed
// to set up the controller.
"github.com/knative/pkg/injection/clients/kubeclient"
svcinformer "github.com/knative/pkg/injection/informers/kubeinformers/corev1/service"
// Other imports ...
)
func NewController(ctx context.Context, cmw configmap.Watcher) *controller.Impl {
logger := logging.FromContext(ctx)
// Access informers
svcInformer := svcinformer.Get(ctx)
c := &Reconciler{
// Pass the lister and client to the Reconciler.
Client: kubeclient.Get(ctx),
ServiceLister: svcInformer.Lister(),
}
impl := controller.NewImpl(c, logger, "NameOfController")
// Set up event handlers.
svcInformer.Informer().AddEventHandler(...)
return impl
}
```
> How it works: by importing the accessor for a client or informer we link
> it and trigger the `init()` method for its package to run at startup.
> Each of these libraries registers themselves similar to our `init()` and
> controller processes can leverage this to setup and inject all of the
> registered things onto a context to pass to your `NewController()`.
## Testing Controllers
Similar to `injection.Default`, we also have `injection.Fake`. While linking
the normal accessors sets up the former, linking their fakes set up the latter.
```
import (
"testing"
// Link the fakes for any informers our controller accesses.
_ "github.com/knative/pkg/injection/informers/kubeinformers/corev1/service/fake"
"k8s.io/client-go/rest"
"github.com/knative/pkg/injection"
logtesting "github.com/knative/pkg/logging/testing"
)
func TestFoo(t *testing.T) {
ctx := logtesting.TestContextWithLogger(t)
// Setup a context from all of the injected fakes.
ctx, _ = injection.Fake.SetupInformers(ctx, &rest.Config{})
cmw := configmap.NewStaticWatcher(...)
ctrl := NewController(ctx, cmw)
// Test the controller process.
}
```
The fake clients also support manually setting up contexts seeded with objects:
```
import (
"testing"
fakekubeclient "github.com/knative/pkg/injection/clients/kubeclient/fake"
"k8s.io/client-go/rest"
"github.com/knative/pkg/injection"
logtesting "github.com/knative/pkg/logging/testing"
)
func TestFoo(t *testing.T) {
ctx := logtesting.TestContextWithLogger(t)
objs := []runtime.Object{
// Some list of initial objects in the client.
}
ctx, kubeClient := fakekubeclient.With(ctx, objs...)
// The fake clients returned by our library are the actual fake type,
// which enables us to access test-specific methods, e.g.
kubeClient.AppendReactor(...)
c := &Reconciler{
Client: kubeClient,
}
// Test the reconciler...
}
```
## Starting controllers
By registering our controller with `injection.Default` via `init()` above we
enable our shared main method to bootstrap the entire container process. All
we do is link the controller packages containing the `init()` registering them
and this transitively links in all of the things it needs. Then our shared
main method sets it all up and runs our controllers.
```go
package main
import (
// The set of controllers this process will run.
_ "github.com/knative/foo/pkg/reconciler/bar"
_ "github.com/knative/baz/pkg/reconciler/blah"
// This defines the shared main for injected controllers.
"github.com/knative/pkg/injection/sharedmain"
)
func main() {
sharedmain.Main()
}
```
## Generating Injection Stubs.
> TODO(mattmoor): Update this once the code-gen lands.

42
injection/clients.go Normal file
View File

@ -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 injection
import (
"context"
"k8s.io/client-go/rest"
)
// ClientInjector holds the type of a callback that attaches a particular
// client type to a context.
type ClientInjector func(context.Context, *rest.Config) context.Context
func (i *impl) RegisterClient(ci ClientInjector) {
i.m.Lock()
defer i.m.Unlock()
i.clients = append(i.clients, ci)
}
func (i *impl) GetClients() []ClientInjector {
i.m.RLock()
defer i.m.RUnlock()
// Copy the slice before returning.
return append(i.clients[:0:0], i.clients...)
}

View File

@ -0,0 +1,47 @@
/*
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 dynamicclient
import (
"context"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/rest"
"github.com/knative/pkg/injection"
)
func init() {
injection.Default.RegisterClient(withClient)
}
// Key is used as the key for associating information
// with a context.Context.
type Key struct{}
func withClient(ctx context.Context, cfg *rest.Config) context.Context {
return context.WithValue(ctx, Key{}, dynamic.NewForConfigOrDie(cfg))
}
// Get extracts the Dynamic client from the context.
func Get(ctx context.Context) dynamic.Interface {
untyped := ctx.Value(Key{})
if untyped == nil {
return nil
}
return untyped.(dynamic.Interface)
}

View File

@ -0,0 +1,56 @@
/*
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 dynamicclient
import (
"context"
"testing"
"k8s.io/client-go/rest"
"github.com/knative/pkg/controller"
"github.com/knative/pkg/injection"
)
func TestRegistration(t *testing.T) {
ctx := context.Background()
// Get before registration
if empty := Get(ctx); empty != nil {
t.Errorf("Unexpected informer: %v", empty)
}
// Check how many informers have registered.
inffs := injection.Default.GetClients()
if want, got := 1, len(inffs); want != got {
t.Errorf("GetClients() = %d, wanted %d", want, got)
}
// Setup the informers.
var infs []controller.Informer
ctx, infs = injection.Default.SetupInformers(ctx, &rest.Config{})
// We should see that a single informer was set up.
if want, got := 0, len(infs); want != got {
t.Errorf("SetupInformers() = %d, wanted %d", want, got)
}
// Get our informer from the context.
if inf := Get(ctx); inf == nil {
t.Error("Get() = nil, wanted non-nil")
}
}

View File

@ -0,0 +1,51 @@
/*
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 fake
import (
"context"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/dynamic/fake"
"k8s.io/client-go/rest"
"github.com/knative/pkg/injection"
"github.com/knative/pkg/injection/clients/dynamicclient"
)
func init() {
injection.Fake.RegisterClient(withClient)
}
func withClient(ctx context.Context, cfg *rest.Config) context.Context {
ctx, _ = With(ctx, runtime.NewScheme())
return ctx
}
func With(ctx context.Context, scheme *runtime.Scheme, objects ...runtime.Object) (context.Context, *fake.FakeDynamicClient) {
cs := fake.NewSimpleDynamicClient(scheme, objects...)
return context.WithValue(ctx, dynamicclient.Key{}, cs), cs
}
// Get extracts the Kubernetes client from the context.
func Get(ctx context.Context) *fake.FakeDynamicClient {
untyped := ctx.Value(dynamicclient.Key{})
if untyped == nil {
return nil
}
return untyped.(*fake.FakeDynamicClient)
}

View File

@ -0,0 +1,56 @@
/*
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 fake
import (
"context"
"testing"
"k8s.io/client-go/rest"
"github.com/knative/pkg/controller"
"github.com/knative/pkg/injection"
)
func TestRegistration(t *testing.T) {
ctx := context.Background()
// Get before registration
if empty := Get(ctx); empty != nil {
t.Errorf("Unexpected informer: %v", empty)
}
// Check how many informers have registered.
inffs := injection.Fake.GetClients()
if want, got := 1, len(inffs); want != got {
t.Errorf("GetClients() = %d, wanted %d", want, got)
}
// Setup the informers.
var infs []controller.Informer
ctx, infs = injection.Fake.SetupInformers(ctx, &rest.Config{})
// We should see that a single informer was set up.
if want, got := 0, len(infs); want != got {
t.Errorf("SetupInformers() = %d, wanted %d", want, got)
}
// Get our informer from the context.
if inf := Get(ctx); inf == nil {
t.Error("Get() = nil, wanted non-nil")
}
}

View File

@ -0,0 +1,51 @@
/*
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 fake
import (
"context"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/kubernetes/fake"
"k8s.io/client-go/rest"
"github.com/knative/pkg/injection"
"github.com/knative/pkg/injection/clients/kubeclient"
)
func init() {
injection.Fake.RegisterClient(withClient)
}
func withClient(ctx context.Context, cfg *rest.Config) context.Context {
ctx, _ = With(ctx)
return ctx
}
func With(ctx context.Context, objects ...runtime.Object) (context.Context, *fake.Clientset) {
cs := fake.NewSimpleClientset(objects...)
return context.WithValue(ctx, kubeclient.Key{}, cs), cs
}
// Get extracts the Kubernetes client from the context.
func Get(ctx context.Context) *fake.Clientset {
untyped := ctx.Value(kubeclient.Key{})
if untyped == nil {
return nil
}
return untyped.(*fake.Clientset)
}

View File

@ -0,0 +1,56 @@
/*
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 fake
import (
"context"
"testing"
"k8s.io/client-go/rest"
"github.com/knative/pkg/controller"
"github.com/knative/pkg/injection"
)
func TestRegistration(t *testing.T) {
ctx := context.Background()
// Get before registration
if empty := Get(ctx); empty != nil {
t.Errorf("Unexpected informer: %v", empty)
}
// Check how many informers have registered.
inffs := injection.Fake.GetClients()
if want, got := 1, len(inffs); want != got {
t.Errorf("GetClients() = %d, wanted %d", want, got)
}
// Setup the informers.
var infs []controller.Informer
ctx, infs = injection.Fake.SetupInformers(ctx, &rest.Config{})
// We should see that a single informer was set up.
if want, got := 0, len(infs); want != got {
t.Errorf("SetupInformers() = %d, wanted %d", want, got)
}
// Get our informer from the context.
if inf := Get(ctx); inf == nil {
t.Error("Get() = nil, wanted non-nil")
}
}

View File

@ -0,0 +1,47 @@
/*
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 kubeclient
import (
"context"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"github.com/knative/pkg/injection"
)
func init() {
injection.Default.RegisterClient(withClient)
}
// Key is used as the key for associating information
// with a context.Context.
type Key struct{}
func withClient(ctx context.Context, cfg *rest.Config) context.Context {
return context.WithValue(ctx, Key{}, kubernetes.NewForConfigOrDie(cfg))
}
// Get extracts the Kubernetes client from the context.
func Get(ctx context.Context) kubernetes.Interface {
untyped := ctx.Value(Key{})
if untyped == nil {
return nil
}
return untyped.(kubernetes.Interface)
}

View File

@ -0,0 +1,56 @@
/*
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 kubeclient
import (
"context"
"testing"
"k8s.io/client-go/rest"
"github.com/knative/pkg/controller"
"github.com/knative/pkg/injection"
)
func TestRegistration(t *testing.T) {
ctx := context.Background()
// Get before registration
if empty := Get(ctx); empty != nil {
t.Errorf("Unexpected informer: %v", empty)
}
// Check how many informers have registered.
inffs := injection.Default.GetClients()
if want, got := 1, len(inffs); want != got {
t.Errorf("GetClients() = %d, wanted %d", want, got)
}
// Setup the informers.
var infs []controller.Informer
ctx, infs = injection.Default.SetupInformers(ctx, &rest.Config{})
// We should see that a single informer was set up.
if want, got := 0, len(infs); want != got {
t.Errorf("SetupInformers() = %d, wanted %d", want, got)
}
// Get our informer from the context.
if inf := Get(ctx); inf == nil {
t.Error("Get() = nil, wanted non-nil")
}
}

52
injection/clients_test.go Normal file
View File

@ -0,0 +1,52 @@
/*
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 injection
import (
"context"
"testing"
"k8s.io/client-go/rest"
)
func injectFoo(ctx context.Context, cfg *rest.Config) context.Context {
return ctx
}
func injectBar(ctx context.Context, cfg *rest.Config) context.Context {
return ctx
}
func TestRegisterClient(t *testing.T) {
i := &impl{}
if want, got := 0, len(i.GetClients()); got != want {
t.Errorf("GetClients() = %d, wanted %d", want, got)
}
i.RegisterClient(injectFoo)
if want, got := 1, len(i.GetClients()); got != want {
t.Errorf("GetClients() = %d, wanted %d", want, got)
}
i.RegisterClient(injectBar)
if want, got := 2, len(i.GetClients()); got != want {
t.Errorf("GetClients() = %d, wanted %d", want, got)
}
}

43
injection/controllers.go Normal file
View File

@ -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 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 injection
import (
"context"
"github.com/knative/pkg/configmap"
"github.com/knative/pkg/controller"
)
// ControllerInjector holds the type of a callback that attaches a particular
// controller type to a context.
type ControllerInjector func(context.Context, configmap.Watcher) *controller.Impl
func (i *impl) RegisterController(ii ControllerInjector) {
i.m.Lock()
defer i.m.Unlock()
i.controllers = append(i.controllers, ii)
}
func (i *impl) GetControllers() []ControllerInjector {
i.m.RLock()
defer i.m.RUnlock()
// Copy the slice before returning.
return append(i.controllers[:0:0], i.controllers...)
}

View File

@ -0,0 +1,53 @@
/*
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 injection
import (
"context"
"testing"
"github.com/knative/pkg/configmap"
"github.com/knative/pkg/controller"
)
func injectFooController(ctx context.Context, cmw configmap.Watcher) *controller.Impl {
return nil
}
func injectBarController(ctx context.Context, cmw configmap.Watcher) *controller.Impl {
return nil
}
func TestRegisterController(t *testing.T) {
i := &impl{}
if want, got := 0, len(i.GetControllers()); got != want {
t.Errorf("GetControllers() = %d, wanted %d", want, got)
}
i.RegisterController(injectFooController)
if want, got := 1, len(i.GetControllers()); got != want {
t.Errorf("GetControllers() = %d, wanted %d", want, got)
}
i.RegisterController(injectBarController)
if want, got := 2, len(i.GetControllers()); got != want {
t.Errorf("GetControllers() = %d, wanted %d", want, got)
}
}

65
injection/doc.go Normal file
View File

@ -0,0 +1,65 @@
/*
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 injection defines the mechanisms through which clients, informers
// and shared informer factories are injected into a shared controller binary
// implementation.
//
// There are two primary contexts where the usage of the injection package is
// interesting. The first is in the context of implementations of
// `controller.Reconciler` being wrapped in a `*controller.Impl`:
//
// import (
// // Simply linking this triggers the injection of the informer, which links
// // the factory triggering its injection, and which links the client,
// // triggering its injection.
// deployinformer "github.com/knative/pkg/injection/informers/kubeinformers/appsv1/deployment"
// "github.com/knative/pkg/injection"
// )
//
// func NewController(ctx context.Context) *controller.Impl {
// deploymentInformer := deployinformer.Get(ctx)
// // Pass deploymentInformer.Lister() to Reconciler
// ...
// // Set up events on deploymentInformer.Informer()
// ...
// }
//
// func init() {
// injection.Default.RegisterController(NewController)
// }
//
// Then in `package main` the entire controller process can be set up via:
//
// package main
//
// import (
// // The set of controllers this controller process runs.
// // Linking these will register the controllers and their transitive
// // dependencies, after which the shared main can set up the rest.
// _ "github.com/knative/foo/pkg/reconciler/matt"
// _ "github.com/knative/foo/pkg/reconciler/scott"
// _ "github.com/knative/foo/pkg/reconciler/ville"
// _ "github.com/knative/foo/pkg/reconciler/dave"
//
// // This defines the shared main for injected controllers.
// "github.com/knative/pkg/injection/sharedmain"
// )
//
// func main() {
// sharedmain.Main()
// }
package injection

40
injection/factories.go Normal file
View File

@ -0,0 +1,40 @@
/*
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 injection
import (
"context"
)
// InformerFactoryInjector holds the type of a callback that attaches a particular
// factory type to a context.
type InformerFactoryInjector func(context.Context) context.Context
func (i *impl) RegisterInformerFactory(ifi InformerFactoryInjector) {
i.m.Lock()
defer i.m.Unlock()
i.factories = append(i.factories, ifi)
}
func (i *impl) GetInformerFactories() []InformerFactoryInjector {
i.m.RLock()
defer i.m.RUnlock()
// Copy the slice before returning.
return append(i.factories[:0:0], i.factories...)
}

View File

@ -0,0 +1,50 @@
/*
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 injection
import (
"context"
"testing"
)
func injectFooFactory(ctx context.Context) context.Context {
return ctx
}
func injectBarFactory(ctx context.Context) context.Context {
return ctx
}
func TestRegisterInformerFactory(t *testing.T) {
i := &impl{}
if want, got := 0, len(i.GetInformerFactories()); got != want {
t.Errorf("GetInformerFactories() = %d, wanted %d", want, got)
}
i.RegisterInformerFactory(injectFooFactory)
if want, got := 1, len(i.GetInformerFactories()); got != want {
t.Errorf("GetInformerFactories() = %d, wanted %d", want, got)
}
i.RegisterInformerFactory(injectBarFactory)
if want, got := 2, len(i.GetInformerFactories()); got != want {
t.Errorf("GetInformerFactories() = %d, wanted %d", want, got)
}
}

68
injection/informers.go Normal file
View File

@ -0,0 +1,68 @@
/*
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 injection
import (
"context"
"k8s.io/client-go/rest"
"github.com/knative/pkg/controller"
)
// InformerInjector holds the type of a callback that attaches a particular
// informer type to a context.
type InformerInjector func(context.Context) (context.Context, controller.Informer)
func (i *impl) RegisterInformer(ii InformerInjector) {
i.m.Lock()
defer i.m.Unlock()
i.informers = append(i.informers, ii)
}
func (i *impl) GetInformers() []InformerInjector {
i.m.RLock()
defer i.m.RUnlock()
// Copy the slice before returning.
return append(i.informers[:0:0], i.informers...)
}
func (i *impl) SetupInformers(ctx context.Context, cfg *rest.Config) (context.Context, []controller.Informer) {
// Based on the reconcilers we have linked, build up a set of clients and inject
// them onto the context.
for _, ci := range i.GetClients() {
ctx = ci(ctx, cfg)
}
// Based on the reconcilers we have linked, build up a set of informer factories
// and inject them onto the context.
for _, ifi := range i.GetInformerFactories() {
ctx = ifi(ctx)
}
// Based on the reconcilers we have linked, build up a set of informers
// and inject them onto the context.
var inf controller.Informer
informers := make([]controller.Informer, 0, len(i.GetInformers()))
for _, ii := range i.GetInformers() {
ctx, inf = ii(ctx)
informers = append(informers, inf)
}
return ctx, informers
}

View File

@ -0,0 +1,50 @@
/*
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 deployment
import (
"context"
appsv1 "k8s.io/client-go/informers/apps/v1"
"github.com/knative/pkg/controller"
"github.com/knative/pkg/injection"
"github.com/knative/pkg/injection/informers/kubeinformers/factory"
)
func init() {
injection.Default.RegisterInformer(withInformer)
}
// Key is used as the key for associating information
// with a context.Context.
type Key struct{}
func withInformer(ctx context.Context) (context.Context, controller.Informer) {
f := factory.Get(ctx)
inf := f.Apps().V1().Deployments()
return context.WithValue(ctx, Key{}, inf), inf.Informer()
}
// Get extracts the Kubernetes Deployment informer from the context.
func Get(ctx context.Context) appsv1.DeploymentInformer {
untyped := ctx.Value(Key{})
if untyped == nil {
return nil
}
return untyped.(appsv1.DeploymentInformer)
}

View File

@ -0,0 +1,56 @@
/*
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 deployment
import (
"context"
"testing"
"k8s.io/client-go/rest"
"github.com/knative/pkg/controller"
"github.com/knative/pkg/injection"
)
func TestRegistration(t *testing.T) {
ctx := context.Background()
// Get before registration
if empty := Get(ctx); empty != nil {
t.Errorf("Unexpected informer: %v", empty)
}
// Check how many informers have registered.
inffs := injection.Default.GetInformers()
if want, got := 1, len(inffs); want != got {
t.Errorf("GetInformers() = %d, wanted %d", want, got)
}
// Setup the informers.
var infs []controller.Informer
ctx, infs = injection.Default.SetupInformers(ctx, &rest.Config{})
// We should see that a single informer was set up.
if want, got := 1, len(infs); want != got {
t.Errorf("SetupInformers() = %d, wanted %d", want, got)
}
// Get our informer from the context.
if inf := Get(ctx); inf == nil {
t.Error("Get() = nil, wanted non-nil")
}
}

View File

@ -0,0 +1,38 @@
/*
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 fake
import (
"context"
"github.com/knative/pkg/controller"
"github.com/knative/pkg/injection"
"github.com/knative/pkg/injection/informers/kubeinformers/appsv1/deployment"
"github.com/knative/pkg/injection/informers/kubeinformers/factory/fake"
)
var Get = deployment.Get
func init() {
injection.Fake.RegisterInformer(withInformer)
}
func withInformer(ctx context.Context) (context.Context, controller.Informer) {
f := fake.Get(ctx)
inf := f.Apps().V1().Deployments()
return context.WithValue(ctx, deployment.Key{}, inf), inf.Informer()
}

View File

@ -0,0 +1,56 @@
/*
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 fake
import (
"context"
"testing"
"k8s.io/client-go/rest"
"github.com/knative/pkg/controller"
"github.com/knative/pkg/injection"
)
func TestRegistration(t *testing.T) {
ctx := context.Background()
// Get before registration
if empty := Get(ctx); empty != nil {
t.Errorf("Unexpected informer: %v", empty)
}
// Check how many informers have registered.
inffs := injection.Fake.GetInformers()
if want, got := 1, len(inffs); want != got {
t.Errorf("GetInformers() = %d, wanted %d", want, got)
}
// Setup the informers.
var infs []controller.Informer
ctx, infs = injection.Fake.SetupInformers(ctx, &rest.Config{})
// We should see that a single informer was set up.
if want, got := 1, len(infs); want != got {
t.Errorf("SetupInformers() = %d, wanted %d", want, got)
}
// Get our informer from the context.
if inf := Get(ctx); inf == nil {
t.Error("Get() = nil, wanted non-nil")
}
}

View File

@ -0,0 +1,38 @@
/*
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 fake
import (
"context"
"github.com/knative/pkg/controller"
"github.com/knative/pkg/injection"
"github.com/knative/pkg/injection/informers/kubeinformers/autoscalingv1/hpa"
"github.com/knative/pkg/injection/informers/kubeinformers/factory/fake"
)
var Get = hpa.Get
func init() {
injection.Fake.RegisterInformer(withInformer)
}
func withInformer(ctx context.Context) (context.Context, controller.Informer) {
f := fake.Get(ctx)
inf := f.Autoscaling().V1().HorizontalPodAutoscalers()
return context.WithValue(ctx, hpa.Key{}, inf), inf.Informer()
}

View File

@ -0,0 +1,56 @@
/*
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 fake
import (
"context"
"testing"
"k8s.io/client-go/rest"
"github.com/knative/pkg/controller"
"github.com/knative/pkg/injection"
)
func TestRegistration(t *testing.T) {
ctx := context.Background()
// Get before registration
if empty := Get(ctx); empty != nil {
t.Errorf("Unexpected informer: %v", empty)
}
// Check how many informers have registered.
inffs := injection.Fake.GetInformers()
if want, got := 1, len(inffs); want != got {
t.Errorf("GetInformers() = %d, wanted %d", want, got)
}
// Setup the informers.
var infs []controller.Informer
ctx, infs = injection.Fake.SetupInformers(ctx, &rest.Config{})
// We should see that a single informer was set up.
if want, got := 1, len(infs); want != got {
t.Errorf("SetupInformers() = %d, wanted %d", want, got)
}
// Get our informer from the context.
if inf := Get(ctx); inf == nil {
t.Error("Get() = nil, wanted non-nil")
}
}

View File

@ -0,0 +1,50 @@
/*
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 hpa
import (
"context"
autoscalingv1 "k8s.io/client-go/informers/autoscaling/v1"
"github.com/knative/pkg/controller"
"github.com/knative/pkg/injection"
"github.com/knative/pkg/injection/informers/kubeinformers/factory"
)
func init() {
injection.Default.RegisterInformer(withInformer)
}
// Key is used as the key for associating information
// with a context.Context.
type Key struct{}
func withInformer(ctx context.Context) (context.Context, controller.Informer) {
f := factory.Get(ctx)
inf := f.Autoscaling().V1().HorizontalPodAutoscalers()
return context.WithValue(ctx, Key{}, inf), inf.Informer()
}
// Get extracts the Kubernetes Hpa informer from the context.
func Get(ctx context.Context) autoscalingv1.HorizontalPodAutoscalerInformer {
untyped := ctx.Value(Key{})
if untyped == nil {
return nil
}
return untyped.(autoscalingv1.HorizontalPodAutoscalerInformer)
}

View File

@ -0,0 +1,56 @@
/*
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 hpa
import (
"context"
"testing"
"k8s.io/client-go/rest"
"github.com/knative/pkg/controller"
"github.com/knative/pkg/injection"
)
func TestRegistration(t *testing.T) {
ctx := context.Background()
// Get before registration
if empty := Get(ctx); empty != nil {
t.Errorf("Unexpected informer: %v", empty)
}
// Check how many informers have registered.
inffs := injection.Default.GetInformers()
if want, got := 1, len(inffs); want != got {
t.Errorf("GetInformers() = %d, wanted %d", want, got)
}
// Setup the informers.
var infs []controller.Informer
ctx, infs = injection.Default.SetupInformers(ctx, &rest.Config{})
// We should see that a single informer was set up.
if want, got := 1, len(infs); want != got {
t.Errorf("SetupInformers() = %d, wanted %d", want, got)
}
// Get our informer from the context.
if inf := Get(ctx); inf == nil {
t.Error("Get() = nil, wanted non-nil")
}
}

View File

@ -0,0 +1,38 @@
/*
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 fake
import (
"context"
"github.com/knative/pkg/controller"
"github.com/knative/pkg/injection"
"github.com/knative/pkg/injection/informers/kubeinformers/autoscalingv2beta1/hpa"
"github.com/knative/pkg/injection/informers/kubeinformers/factory/fake"
)
var Get = hpa.Get
func init() {
injection.Fake.RegisterInformer(withInformer)
}
func withInformer(ctx context.Context) (context.Context, controller.Informer) {
f := fake.Get(ctx)
inf := f.Autoscaling().V2beta1().HorizontalPodAutoscalers()
return context.WithValue(ctx, hpa.Key{}, inf), inf.Informer()
}

View File

@ -0,0 +1,56 @@
/*
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 fake
import (
"context"
"testing"
"k8s.io/client-go/rest"
"github.com/knative/pkg/controller"
"github.com/knative/pkg/injection"
)
func TestRegistration(t *testing.T) {
ctx := context.Background()
// Get before registration
if empty := Get(ctx); empty != nil {
t.Errorf("Unexpected informer: %v", empty)
}
// Check how many informers have registered.
inffs := injection.Fake.GetInformers()
if want, got := 1, len(inffs); want != got {
t.Errorf("GetInformers() = %d, wanted %d", want, got)
}
// Setup the informers.
var infs []controller.Informer
ctx, infs = injection.Fake.SetupInformers(ctx, &rest.Config{})
// We should see that a single informer was set up.
if want, got := 1, len(infs); want != got {
t.Errorf("SetupInformers() = %d, wanted %d", want, got)
}
// Get our informer from the context.
if inf := Get(ctx); inf == nil {
t.Error("Get() = nil, wanted non-nil")
}
}

View File

@ -0,0 +1,50 @@
/*
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 hpa
import (
"context"
autoscalingv2beta1 "k8s.io/client-go/informers/autoscaling/v2beta1"
"github.com/knative/pkg/controller"
"github.com/knative/pkg/injection"
"github.com/knative/pkg/injection/informers/kubeinformers/factory"
)
func init() {
injection.Default.RegisterInformer(withInformer)
}
// Key is used as the key for associating information
// with a context.Context.
type Key struct{}
func withInformer(ctx context.Context) (context.Context, controller.Informer) {
f := factory.Get(ctx)
inf := f.Autoscaling().V2beta1().HorizontalPodAutoscalers()
return context.WithValue(ctx, Key{}, inf), inf.Informer()
}
// Get extracts the Kubernetes Hpa informer from the context.
func Get(ctx context.Context) autoscalingv2beta1.HorizontalPodAutoscalerInformer {
untyped := ctx.Value(Key{})
if untyped == nil {
return nil
}
return untyped.(autoscalingv2beta1.HorizontalPodAutoscalerInformer)
}

View File

@ -0,0 +1,56 @@
/*
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 hpa
import (
"context"
"testing"
"k8s.io/client-go/rest"
"github.com/knative/pkg/controller"
"github.com/knative/pkg/injection"
)
func TestRegistration(t *testing.T) {
ctx := context.Background()
// Get before registration
if empty := Get(ctx); empty != nil {
t.Errorf("Unexpected informer: %v", empty)
}
// Check how many informers have registered.
inffs := injection.Default.GetInformers()
if want, got := 1, len(inffs); want != got {
t.Errorf("GetInformers() = %d, wanted %d", want, got)
}
// Setup the informers.
var infs []controller.Informer
ctx, infs = injection.Default.SetupInformers(ctx, &rest.Config{})
// We should see that a single informer was set up.
if want, got := 1, len(infs); want != got {
t.Errorf("SetupInformers() = %d, wanted %d", want, got)
}
// Get our informer from the context.
if inf := Get(ctx); inf == nil {
t.Error("Get() = nil, wanted non-nil")
}
}

View File

@ -0,0 +1,50 @@
/*
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 configmap
import (
"context"
corev1 "k8s.io/client-go/informers/core/v1"
"github.com/knative/pkg/controller"
"github.com/knative/pkg/injection"
"github.com/knative/pkg/injection/informers/kubeinformers/factory"
)
func init() {
injection.Default.RegisterInformer(withInformer)
}
// Key is used as the key for associating information
// with a context.Context.
type Key struct{}
func withInformer(ctx context.Context) (context.Context, controller.Informer) {
f := factory.Get(ctx)
inf := f.Core().V1().ConfigMaps()
return context.WithValue(ctx, Key{}, inf), inf.Informer()
}
// Get extracts the Kubernetes ConfigMap informer from the context.
func Get(ctx context.Context) corev1.ConfigMapInformer {
untyped := ctx.Value(Key{})
if untyped == nil {
return nil
}
return untyped.(corev1.ConfigMapInformer)
}

View File

@ -0,0 +1,56 @@
/*
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 configmap
import (
"context"
"testing"
"k8s.io/client-go/rest"
"github.com/knative/pkg/controller"
"github.com/knative/pkg/injection"
)
func TestRegistration(t *testing.T) {
ctx := context.Background()
// Get before registration
if empty := Get(ctx); empty != nil {
t.Errorf("Unexpected informer: %v", empty)
}
// Check how many informers have registered.
inffs := injection.Default.GetInformers()
if want, got := 1, len(inffs); want != got {
t.Errorf("GetInformers() = %d, wanted %d", want, got)
}
// Setup the informers.
var infs []controller.Informer
ctx, infs = injection.Default.SetupInformers(ctx, &rest.Config{})
// We should see that a single informer was set up.
if want, got := 1, len(infs); want != got {
t.Errorf("SetupInformers() = %d, wanted %d", want, got)
}
// Get our informer from the context.
if inf := Get(ctx); inf == nil {
t.Error("Get() = nil, wanted non-nil")
}
}

View File

@ -0,0 +1,38 @@
/*
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 fake
import (
"context"
"github.com/knative/pkg/controller"
"github.com/knative/pkg/injection"
"github.com/knative/pkg/injection/informers/kubeinformers/corev1/configmap"
"github.com/knative/pkg/injection/informers/kubeinformers/factory/fake"
)
var Get = configmap.Get
func init() {
injection.Fake.RegisterInformer(withInformer)
}
func withInformer(ctx context.Context) (context.Context, controller.Informer) {
f := fake.Get(ctx)
inf := f.Core().V1().ConfigMaps()
return context.WithValue(ctx, configmap.Key{}, inf), inf.Informer()
}

View File

@ -0,0 +1,56 @@
/*
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 fake
import (
"context"
"testing"
"k8s.io/client-go/rest"
"github.com/knative/pkg/controller"
"github.com/knative/pkg/injection"
)
func TestRegistration(t *testing.T) {
ctx := context.Background()
// Get before registration
if empty := Get(ctx); empty != nil {
t.Errorf("Unexpected informer: %v", empty)
}
// Check how many informers have registered.
inffs := injection.Fake.GetInformers()
if want, got := 1, len(inffs); want != got {
t.Errorf("GetInformers() = %d, wanted %d", want, got)
}
// Setup the informers.
var infs []controller.Informer
ctx, infs = injection.Fake.SetupInformers(ctx, &rest.Config{})
// We should see that a single informer was set up.
if want, got := 1, len(infs); want != got {
t.Errorf("SetupInformers() = %d, wanted %d", want, got)
}
// Get our informer from the context.
if inf := Get(ctx); inf == nil {
t.Error("Get() = nil, wanted non-nil")
}
}

View File

@ -0,0 +1,50 @@
/*
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 endpoints
import (
"context"
corev1 "k8s.io/client-go/informers/core/v1"
"github.com/knative/pkg/controller"
"github.com/knative/pkg/injection"
"github.com/knative/pkg/injection/informers/kubeinformers/factory"
)
func init() {
injection.Default.RegisterInformer(withInformer)
}
// Key is used as the key for associating information
// with a context.Context.
type Key struct{}
func withInformer(ctx context.Context) (context.Context, controller.Informer) {
f := factory.Get(ctx)
inf := f.Core().V1().Endpoints()
return context.WithValue(ctx, Key{}, inf), inf.Informer()
}
// Get extracts the Kubernetes Endpoints informer from the context.
func Get(ctx context.Context) corev1.EndpointsInformer {
untyped := ctx.Value(Key{})
if untyped == nil {
return nil
}
return untyped.(corev1.EndpointsInformer)
}

View File

@ -0,0 +1,56 @@
/*
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 endpoints
import (
"context"
"testing"
"k8s.io/client-go/rest"
"github.com/knative/pkg/controller"
"github.com/knative/pkg/injection"
)
func TestRegistration(t *testing.T) {
ctx := context.Background()
// Get before registration
if empty := Get(ctx); empty != nil {
t.Errorf("Unexpected informer: %v", empty)
}
// Check how many informers have registered.
inffs := injection.Default.GetInformers()
if want, got := 1, len(inffs); want != got {
t.Errorf("GetInformers() = %d, wanted %d", want, got)
}
// Setup the informers.
var infs []controller.Informer
ctx, infs = injection.Default.SetupInformers(ctx, &rest.Config{})
// We should see that a single informer was set up.
if want, got := 1, len(infs); want != got {
t.Errorf("SetupInformers() = %d, wanted %d", want, got)
}
// Get our informer from the context.
if inf := Get(ctx); inf == nil {
t.Error("Get() = nil, wanted non-nil")
}
}

View File

@ -0,0 +1,38 @@
/*
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 fake
import (
"context"
"github.com/knative/pkg/controller"
"github.com/knative/pkg/injection"
"github.com/knative/pkg/injection/informers/kubeinformers/corev1/endpoints"
"github.com/knative/pkg/injection/informers/kubeinformers/factory/fake"
)
var Get = endpoints.Get
func init() {
injection.Fake.RegisterInformer(withInformer)
}
func withInformer(ctx context.Context) (context.Context, controller.Informer) {
f := fake.Get(ctx)
inf := f.Core().V1().Endpoints()
return context.WithValue(ctx, endpoints.Key{}, inf), inf.Informer()
}

View File

@ -0,0 +1,56 @@
/*
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 fake
import (
"context"
"testing"
"k8s.io/client-go/rest"
"github.com/knative/pkg/controller"
"github.com/knative/pkg/injection"
)
func TestRegistration(t *testing.T) {
ctx := context.Background()
// Get before registration
if empty := Get(ctx); empty != nil {
t.Errorf("Unexpected informer: %v", empty)
}
// Check how many informers have registered.
inffs := injection.Fake.GetInformers()
if want, got := 1, len(inffs); want != got {
t.Errorf("GetInformers() = %d, wanted %d", want, got)
}
// Setup the informers.
var infs []controller.Informer
ctx, infs = injection.Fake.SetupInformers(ctx, &rest.Config{})
// We should see that a single informer was set up.
if want, got := 1, len(infs); want != got {
t.Errorf("SetupInformers() = %d, wanted %d", want, got)
}
// Get our informer from the context.
if inf := Get(ctx); inf == nil {
t.Error("Get() = nil, wanted non-nil")
}
}

View File

@ -0,0 +1,38 @@
/*
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 fake
import (
"context"
"github.com/knative/pkg/controller"
"github.com/knative/pkg/injection"
"github.com/knative/pkg/injection/informers/kubeinformers/corev1/secret"
"github.com/knative/pkg/injection/informers/kubeinformers/factory/fake"
)
var Get = secret.Get
func init() {
injection.Fake.RegisterInformer(withInformer)
}
func withInformer(ctx context.Context) (context.Context, controller.Informer) {
f := fake.Get(ctx)
inf := f.Core().V1().Secrets()
return context.WithValue(ctx, secret.Key{}, inf), inf.Informer()
}

View File

@ -0,0 +1,56 @@
/*
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 fake
import (
"context"
"testing"
"k8s.io/client-go/rest"
"github.com/knative/pkg/controller"
"github.com/knative/pkg/injection"
)
func TestRegistration(t *testing.T) {
ctx := context.Background()
// Get before registration
if empty := Get(ctx); empty != nil {
t.Errorf("Unexpected informer: %v", empty)
}
// Check how many informers have registered.
inffs := injection.Fake.GetInformers()
if want, got := 1, len(inffs); want != got {
t.Errorf("GetInformers() = %d, wanted %d", want, got)
}
// Setup the informers.
var infs []controller.Informer
ctx, infs = injection.Fake.SetupInformers(ctx, &rest.Config{})
// We should see that a single informer was set up.
if want, got := 1, len(infs); want != got {
t.Errorf("SetupInformers() = %d, wanted %d", want, got)
}
// Get our informer from the context.
if inf := Get(ctx); inf == nil {
t.Error("Get() = nil, wanted non-nil")
}
}

View File

@ -0,0 +1,50 @@
/*
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 secret
import (
"context"
corev1 "k8s.io/client-go/informers/core/v1"
"github.com/knative/pkg/controller"
"github.com/knative/pkg/injection"
"github.com/knative/pkg/injection/informers/kubeinformers/factory"
)
func init() {
injection.Default.RegisterInformer(withInformer)
}
// Key is used as the key for associating information
// with a context.Context.
type Key struct{}
func withInformer(ctx context.Context) (context.Context, controller.Informer) {
f := factory.Get(ctx)
inf := f.Core().V1().Secrets()
return context.WithValue(ctx, Key{}, inf), inf.Informer()
}
// Get extracts the Kubernetes Secret informer from the context.
func Get(ctx context.Context) corev1.SecretInformer {
untyped := ctx.Value(Key{})
if untyped == nil {
return nil
}
return untyped.(corev1.SecretInformer)
}

View File

@ -0,0 +1,56 @@
/*
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 secret
import (
"context"
"testing"
"k8s.io/client-go/rest"
"github.com/knative/pkg/controller"
"github.com/knative/pkg/injection"
)
func TestRegistration(t *testing.T) {
ctx := context.Background()
// Get before registration
if empty := Get(ctx); empty != nil {
t.Errorf("Unexpected informer: %v", empty)
}
// Check how many informers have registered.
inffs := injection.Default.GetInformers()
if want, got := 1, len(inffs); want != got {
t.Errorf("GetInformers() = %d, wanted %d", want, got)
}
// Setup the informers.
var infs []controller.Informer
ctx, infs = injection.Default.SetupInformers(ctx, &rest.Config{})
// We should see that a single informer was set up.
if want, got := 1, len(infs); want != got {
t.Errorf("SetupInformers() = %d, wanted %d", want, got)
}
// Get our informer from the context.
if inf := Get(ctx); inf == nil {
t.Error("Get() = nil, wanted non-nil")
}
}

View File

@ -0,0 +1,38 @@
/*
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 fake
import (
"context"
"github.com/knative/pkg/controller"
"github.com/knative/pkg/injection"
"github.com/knative/pkg/injection/informers/kubeinformers/corev1/service"
"github.com/knative/pkg/injection/informers/kubeinformers/factory/fake"
)
var Get = service.Get
func init() {
injection.Fake.RegisterInformer(withInformer)
}
func withInformer(ctx context.Context) (context.Context, controller.Informer) {
f := fake.Get(ctx)
inf := f.Core().V1().Services()
return context.WithValue(ctx, service.Key{}, inf), inf.Informer()
}

View File

@ -0,0 +1,56 @@
/*
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 fake
import (
"context"
"testing"
"k8s.io/client-go/rest"
"github.com/knative/pkg/controller"
"github.com/knative/pkg/injection"
)
func TestRegistration(t *testing.T) {
ctx := context.Background()
// Get before registration
if empty := Get(ctx); empty != nil {
t.Errorf("Unexpected informer: %v", empty)
}
// Check how many informers have registered.
inffs := injection.Fake.GetInformers()
if want, got := 1, len(inffs); want != got {
t.Errorf("GetInformers() = %d, wanted %d", want, got)
}
// Setup the informers.
var infs []controller.Informer
ctx, infs = injection.Fake.SetupInformers(ctx, &rest.Config{})
// We should see that a single informer was set up.
if want, got := 1, len(infs); want != got {
t.Errorf("SetupInformers() = %d, wanted %d", want, got)
}
// Get our informer from the context.
if inf := Get(ctx); inf == nil {
t.Error("Get() = nil, wanted non-nil")
}
}

View File

@ -0,0 +1,50 @@
/*
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 service
import (
"context"
corev1 "k8s.io/client-go/informers/core/v1"
"github.com/knative/pkg/controller"
"github.com/knative/pkg/injection"
"github.com/knative/pkg/injection/informers/kubeinformers/factory"
)
func init() {
injection.Default.RegisterInformer(withInformer)
}
// Key is used as the key for associating information
// with a context.Context.
type Key struct{}
func withInformer(ctx context.Context) (context.Context, controller.Informer) {
f := factory.Get(ctx)
inf := f.Core().V1().Services()
return context.WithValue(ctx, Key{}, inf), inf.Informer()
}
// Get extracts the Kubernetes Service informer from the context.
func Get(ctx context.Context) corev1.ServiceInformer {
untyped := ctx.Value(Key{})
if untyped == nil {
return nil
}
return untyped.(corev1.ServiceInformer)
}

View File

@ -0,0 +1,56 @@
/*
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 service
import (
"context"
"testing"
"k8s.io/client-go/rest"
"github.com/knative/pkg/controller"
"github.com/knative/pkg/injection"
)
func TestRegistration(t *testing.T) {
ctx := context.Background()
// Get before registration
if empty := Get(ctx); empty != nil {
t.Errorf("Unexpected informer: %v", empty)
}
// Check how many informers have registered.
inffs := injection.Default.GetInformers()
if want, got := 1, len(inffs); want != got {
t.Errorf("GetInformers() = %d, wanted %d", want, got)
}
// Setup the informers.
var infs []controller.Informer
ctx, infs = injection.Default.SetupInformers(ctx, &rest.Config{})
// We should see that a single informer was set up.
if want, got := 1, len(infs); want != got {
t.Errorf("SetupInformers() = %d, wanted %d", want, got)
}
// Get our informer from the context.
if inf := Get(ctx); inf == nil {
t.Error("Get() = nil, wanted non-nil")
}
}

View File

@ -0,0 +1,50 @@
/*
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 factory
import (
"context"
"k8s.io/client-go/informers"
"github.com/knative/pkg/controller"
"github.com/knative/pkg/injection"
"github.com/knative/pkg/injection/clients/kubeclient"
)
func init() {
injection.Default.RegisterInformerFactory(withInformerFactory)
}
// Key is used as the key for associating information
// with a context.Context.
type Key struct{}
func withInformerFactory(ctx context.Context) context.Context {
kc := kubeclient.Get(ctx)
return context.WithValue(ctx, Key{},
informers.NewSharedInformerFactory(kc, controller.GetResyncPeriod(ctx)))
}
// Get extracts the Kubernetes InformerFactory from the context.
func Get(ctx context.Context) informers.SharedInformerFactory {
untyped := ctx.Value(Key{})
if untyped == nil {
return nil
}
return untyped.(informers.SharedInformerFactory)
}

View File

@ -0,0 +1,56 @@
/*
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 factory
import (
"context"
"testing"
"k8s.io/client-go/rest"
"github.com/knative/pkg/controller"
"github.com/knative/pkg/injection"
)
func TestRegistration(t *testing.T) {
ctx := context.Background()
// Get before registration
if empty := Get(ctx); empty != nil {
t.Errorf("Unexpected informer factory: %v", empty)
}
// Check how many informer factories have registered.
inffs := injection.Default.GetInformerFactories()
if want, got := 1, len(inffs); want != got {
t.Errorf("GetInformerFactories() = %d, wanted %d", want, got)
}
// Setup the informers.
var infs []controller.Informer
ctx, infs = injection.Default.SetupInformers(ctx, &rest.Config{})
// We should see that a single informer was set up.
if want, got := 0, len(infs); want != got {
t.Errorf("SetupInformers() = %d, wanted %d", want, got)
}
// Get our informer from the context.
if inf := Get(ctx); inf == nil {
t.Error("Get() = nil, wanted non-nil")
}
}

View File

@ -0,0 +1,40 @@
/*
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 fake
import (
"context"
"k8s.io/client-go/informers"
"github.com/knative/pkg/controller"
"github.com/knative/pkg/injection"
"github.com/knative/pkg/injection/clients/kubeclient/fake"
"github.com/knative/pkg/injection/informers/kubeinformers/factory"
)
var Get = factory.Get
func init() {
injection.Fake.RegisterInformerFactory(withInformerFactory)
}
func withInformerFactory(ctx context.Context) context.Context {
kc := fake.Get(ctx)
return context.WithValue(ctx, factory.Key{},
informers.NewSharedInformerFactory(kc, controller.GetResyncPeriod(ctx)))
}

View File

@ -0,0 +1,56 @@
/*
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 fake
import (
"context"
"testing"
"k8s.io/client-go/rest"
"github.com/knative/pkg/controller"
"github.com/knative/pkg/injection"
)
func TestRegistration(t *testing.T) {
ctx := context.Background()
// Get before registration
if empty := Get(ctx); empty != nil {
t.Errorf("Unexpected informer factory: %v", empty)
}
// Check how many informer factories have registered.
inffs := injection.Fake.GetInformerFactories()
if want, got := 1, len(inffs); want != got {
t.Errorf("GetInformerFactories() = %d, wanted %d", want, got)
}
// Setup the informers.
var infs []controller.Informer
ctx, infs = injection.Fake.SetupInformers(ctx, &rest.Config{})
// We should see that a single informer was set up.
if want, got := 0, len(infs); want != got {
t.Errorf("SetupInformers() = %d, wanted %d", want, got)
}
// Get our informer from the context.
if inf := Get(ctx); inf == nil {
t.Error("Get() = nil, wanted non-nil")
}
}

View File

@ -0,0 +1,70 @@
/*
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 injection
import (
"context"
"testing"
"k8s.io/client-go/rest"
"github.com/knative/pkg/controller"
)
type fakeInformer struct{}
// HasSynced implements controller.Informer
func (*fakeInformer) HasSynced() bool {
return false
}
// Run implements controller.Informer
func (*fakeInformer) Run(<-chan struct{}) {}
var _ controller.Informer = (*fakeInformer)(nil)
func injectFooInformer(ctx context.Context) (context.Context, controller.Informer) {
return ctx, nil
}
func injectBarInformer(ctx context.Context) (context.Context, controller.Informer) {
return ctx, nil
}
func TestRegisterInformersAndSetup(t *testing.T) {
i := &impl{}
if want, got := 0, len(i.GetInformers()); got != want {
t.Errorf("GetInformerFactories() = %d, wanted %d", want, got)
}
i.RegisterClient(injectFoo)
i.RegisterClient(injectBar)
i.RegisterInformerFactory(injectFooFactory)
i.RegisterInformerFactory(injectBarFactory)
i.RegisterInformer(injectFooInformer)
i.RegisterInformer(injectBarInformer)
ctx, infs := context.Background(), []controller.Informer{}
ctx, infs = i.SetupInformers(ctx, &rest.Config{})
if want, got := 2, len(infs); got != want {
t.Errorf("SetupInformers() = %d, wanted %d", want, got)
}
}

90
injection/interface.go Normal file
View File

@ -0,0 +1,90 @@
/*
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 injection
import (
"context"
"sync"
"k8s.io/client-go/rest"
"github.com/knative/pkg/controller"
)
// Interface is the interface for interacting with injection
// implementations, such as our Default and Fake below.
type Interface interface {
// RegisterClient registers a new injector callback for associating
// a new client with a context.
RegisterClient(ClientInjector)
// GetClients fetches all of the registered client injectors.
GetClients() []ClientInjector
// RegisterInformerFactory registers a new injector callback for associating
// a new informer factory with a context.
RegisterInformerFactory(InformerFactoryInjector)
// GetInformerFactories fetches all of the registered informer factory injectors.
GetInformerFactories() []InformerFactoryInjector
// RegisterInformer registers a new injector callback for associating
// a new informer with a context.
RegisterInformer(InformerInjector)
// GetInformers fetches all of the registered informer injectors.
GetInformers() []InformerInjector
// SetupInformers runs all of the injectors against a context, starting with
// the clients and the given rest.Config. The resulting context is returned
// along with a list of the .Informer() for each of the injected informers,
// which is suitable for passing to controller.StartInformers().
// This does not setup or start any controllers.
// TODO(mattmoor): Consider setting up and starting controllers?
SetupInformers(context.Context, *rest.Config) (context.Context, []controller.Informer)
// RegisterController registers a new injector callback for associating
// a new controller with a context.
RegisterController(ControllerInjector)
// GetControllers fetches all of the registered controller injectors.
GetControllers() []ControllerInjector
}
var (
// Check that impl implements Interface
_ Interface = (*impl)(nil)
// Default is the injection interface with which informers should register
// to make themselves available to the controller process when reconcilers
// are being run for real.
Default Interface = &impl{}
// Fake is the injection interface with which informers should register
// to make themselves available to the controller process when it is being
// unit tested.
Fake Interface = &impl{}
)
type impl struct {
m sync.RWMutex
clients []ClientInjector
factories []InformerFactoryInjector
informers []InformerInjector
controllers []ControllerInjector
}

View File

@ -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 sharedmain
import (
"context"
"flag"
"log"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
// Uncomment the following line to load the gcp plugin (only required to authenticate against GKE clusters).
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
"github.com/knative/pkg/configmap"
"github.com/knative/pkg/controller"
"github.com/knative/pkg/injection"
"github.com/knative/pkg/injection/clients/kubeclient"
"github.com/knative/pkg/logging"
"github.com/knative/pkg/metrics"
"github.com/knative/pkg/signals"
"github.com/knative/pkg/system"
"go.uber.org/zap"
)
func Main() {
// The default component name is "controller"
MainWithComponent("controller")
}
func MainWithComponent(component string) {
// Set up signals so we handle the first shutdown signal gracefully.
MainWithContext(signals.NewContext(), component)
}
func MainWithContext(ctx context.Context, component string) {
var (
masterURL = flag.String("master", "", "The address of the Kubernetes API server. Overrides any value in kubeconfig. Only required if out-of-cluster.")
kubeconfig = flag.String("kubeconfig", "", "Path to a kubeconfig. Only required if out-of-cluster.")
)
flag.Parse()
cfg, err := clientcmd.BuildConfigFromFlags(*masterURL, *kubeconfig)
if err != nil {
log.Fatal("Error building kubeconfig", err)
}
MainWithConfig(ctx, component, cfg)
}
func MainWithConfig(ctx context.Context, component string, cfg *rest.Config) {
// Set up our logger.
loggingConfigMap, err := configmap.Load("/etc/config-logging")
if err != nil {
log.Fatal("Error loading logging configuration:", err)
}
loggingConfig, err := logging.NewConfigFromMap(loggingConfigMap)
if err != nil {
log.Fatal("Error parsing logging configuration:", err)
}
logger, atomicLevel := logging.NewLoggerFromConfig(loggingConfig, component)
defer flush(logger)
ctx = logging.WithLogger(ctx, logger)
logger.Infof("Registering %d clients", len(injection.Default.GetClients()))
logger.Infof("Registering %d informer factories", len(injection.Default.GetInformerFactories()))
logger.Infof("Registering %d informers", len(injection.Default.GetInformers()))
logger.Infof("Registering %d controllers", len(injection.Default.GetControllers()))
// Adjust our client's rate limits based on the number of controller's we are running.
cfg.QPS = float32(len(injection.Default.GetControllers())) * rest.DefaultQPS
cfg.Burst = len(injection.Default.GetControllers()) * rest.DefaultBurst
ctx, informers := injection.Default.SetupInformers(ctx, cfg)
// TODO(mattmoor): This should itself take a context and be injection-based.
cmw := configmap.NewInformedWatcher(kubeclient.Get(ctx), system.Namespace())
// Based on the reconcilers we have linked, build up the set of controllers to run.
controllers := make([]*controller.Impl, 0, len(injection.Default.GetControllers()))
for _, cf := range injection.Default.GetControllers() {
controllers = append(controllers, cf(ctx, cmw))
}
// Watch the logging config map and dynamically update logging levels.
cmw.Watch(logging.ConfigMapName(), logging.UpdateLevelFromConfigMap(logger, atomicLevel, component))
// Watch the observability config map and dynamically update metrics exporter.
cmw.Watch(metrics.ConfigMapName(), metrics.UpdateExporterFromConfigMap(component, logger))
if err := cmw.Start(ctx.Done()); err != nil {
logger.Fatalw("failed to start configuration manager", zap.Error(err))
}
// Start all of the informers and wait for them to sync.
logger.Info("Starting informers.")
if err := controller.StartInformers(ctx.Done(), informers...); err != nil {
logger.Fatalw("Failed to start informers", err)
}
// Start all of the controllers.
logger.Info("Starting controllers...")
controller.StartAll(ctx.Done(), controllers...)
}
func flush(logger *zap.SugaredLogger) {
logger.Sync()
metrics.FlushExporter()
}

View File

@ -18,6 +18,10 @@
# It is started by prow for each PR.
# For convenience, it can also be executed manually.
# Markdown linting failures don't show up properly in Gubernator resulting
# in a net-negative contributor experience.
export DISABLE_MD_LINTING=1
source $(dirname $0)/../vendor/github.com/knative/test-infra/scripts/presubmit-tests.sh
# TODO(#17): Write integration tests.

View File

@ -0,0 +1,383 @@
/*
Copyright 2016 The Kubernetes 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 gcp
import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
"os/exec"
"strings"
"sync"
"time"
"github.com/golang/glog"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
"k8s.io/apimachinery/pkg/util/net"
"k8s.io/apimachinery/pkg/util/yaml"
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/util/jsonpath"
)
func init() {
if err := restclient.RegisterAuthProviderPlugin("gcp", newGCPAuthProvider); err != nil {
glog.Fatalf("Failed to register gcp auth plugin: %v", err)
}
}
var (
// Stubbable for testing
execCommand = exec.Command
// defaultScopes:
// - cloud-platform is the base scope to authenticate to GCP.
// - userinfo.email is used to authenticate to GKE APIs with gserviceaccount
// email instead of numeric uniqueID.
defaultScopes = []string{
"https://www.googleapis.com/auth/cloud-platform",
"https://www.googleapis.com/auth/userinfo.email"}
)
// gcpAuthProvider is an auth provider plugin that uses GCP credentials to provide
// tokens for kubectl to authenticate itself to the apiserver. A sample json config
// is provided below with all recognized options described.
//
// {
// 'auth-provider': {
// # Required
// "name": "gcp",
//
// 'config': {
// # Authentication options
// # These options are used while getting a token.
//
// # comma-separated list of GCP API scopes. default value of this field
// # is "https://www.googleapis.com/auth/cloud-platform,https://www.googleapis.com/auth/userinfo.email".
// # to override the API scopes, specify this field explicitly.
// "scopes": "https://www.googleapis.com/auth/cloud-platform"
//
// # Caching options
//
// # Raw string data representing cached access token.
// "access-token": "ya29.CjWdA4GiBPTt",
// # RFC3339Nano expiration timestamp for cached access token.
// "expiry": "2016-10-31 22:31:9.123",
//
// # Command execution options
// # These options direct the plugin to execute a specified command and parse
// # token and expiry time from the output of the command.
//
// # Command to execute for access token. Command output will be parsed as JSON.
// # If "cmd-args" is not present, this value will be split on whitespace, with
// # the first element interpreted as the command, remaining elements as args.
// "cmd-path": "/usr/bin/gcloud",
//
// # Arguments to pass to command to execute for access token.
// "cmd-args": "config config-helper --output=json"
//
// # JSONPath to the string field that represents the access token in
// # command output. If omitted, defaults to "{.access_token}".
// "token-key": "{.credential.access_token}",
//
// # JSONPath to the string field that represents expiration timestamp
// # of the access token in the command output. If omitted, defaults to
// # "{.token_expiry}"
// "expiry-key": ""{.credential.token_expiry}",
//
// # golang reference time in the format that the expiration timestamp uses.
// # If omitted, defaults to time.RFC3339Nano
// "time-fmt": "2006-01-02 15:04:05.999999999"
// }
// }
// }
//
type gcpAuthProvider struct {
tokenSource oauth2.TokenSource
persister restclient.AuthProviderConfigPersister
}
func newGCPAuthProvider(_ string, gcpConfig map[string]string, persister restclient.AuthProviderConfigPersister) (restclient.AuthProvider, error) {
ts, err := tokenSource(isCmdTokenSource(gcpConfig), gcpConfig)
if err != nil {
return nil, err
}
cts, err := newCachedTokenSource(gcpConfig["access-token"], gcpConfig["expiry"], persister, ts, gcpConfig)
if err != nil {
return nil, err
}
return &gcpAuthProvider{cts, persister}, nil
}
func isCmdTokenSource(gcpConfig map[string]string) bool {
_, ok := gcpConfig["cmd-path"]
return ok
}
func tokenSource(isCmd bool, gcpConfig map[string]string) (oauth2.TokenSource, error) {
// Command-based token source
if isCmd {
cmd := gcpConfig["cmd-path"]
if len(cmd) == 0 {
return nil, fmt.Errorf("missing access token cmd")
}
if gcpConfig["scopes"] != "" {
return nil, fmt.Errorf("scopes can only be used when kubectl is using a gcp service account key")
}
var args []string
if cmdArgs, ok := gcpConfig["cmd-args"]; ok {
args = strings.Fields(cmdArgs)
} else {
fields := strings.Fields(cmd)
cmd = fields[0]
args = fields[1:]
}
return newCmdTokenSource(cmd, args, gcpConfig["token-key"], gcpConfig["expiry-key"], gcpConfig["time-fmt"]), nil
}
// Google Application Credentials-based token source
scopes := parseScopes(gcpConfig)
ts, err := google.DefaultTokenSource(context.Background(), scopes...)
if err != nil {
return nil, fmt.Errorf("cannot construct google default token source: %v", err)
}
return ts, nil
}
// parseScopes constructs a list of scopes that should be included in token source
// from the config map.
func parseScopes(gcpConfig map[string]string) []string {
scopes, ok := gcpConfig["scopes"]
if !ok {
return defaultScopes
}
if scopes == "" {
return []string{}
}
return strings.Split(gcpConfig["scopes"], ",")
}
func (g *gcpAuthProvider) WrapTransport(rt http.RoundTripper) http.RoundTripper {
var resetCache map[string]string
if cts, ok := g.tokenSource.(*cachedTokenSource); ok {
resetCache = cts.baseCache()
} else {
resetCache = make(map[string]string)
}
return &conditionalTransport{&oauth2.Transport{Source: g.tokenSource, Base: rt}, g.persister, resetCache}
}
func (g *gcpAuthProvider) Login() error { return nil }
type cachedTokenSource struct {
lk sync.Mutex
source oauth2.TokenSource
accessToken string
expiry time.Time
persister restclient.AuthProviderConfigPersister
cache map[string]string
}
func newCachedTokenSource(accessToken, expiry string, persister restclient.AuthProviderConfigPersister, ts oauth2.TokenSource, cache map[string]string) (*cachedTokenSource, error) {
var expiryTime time.Time
if parsedTime, err := time.Parse(time.RFC3339Nano, expiry); err == nil {
expiryTime = parsedTime
}
if cache == nil {
cache = make(map[string]string)
}
return &cachedTokenSource{
source: ts,
accessToken: accessToken,
expiry: expiryTime,
persister: persister,
cache: cache,
}, nil
}
func (t *cachedTokenSource) Token() (*oauth2.Token, error) {
tok := t.cachedToken()
if tok.Valid() && !tok.Expiry.IsZero() {
return tok, nil
}
tok, err := t.source.Token()
if err != nil {
return nil, err
}
cache := t.update(tok)
if t.persister != nil {
if err := t.persister.Persist(cache); err != nil {
glog.V(4).Infof("Failed to persist token: %v", err)
}
}
return tok, nil
}
func (t *cachedTokenSource) cachedToken() *oauth2.Token {
t.lk.Lock()
defer t.lk.Unlock()
return &oauth2.Token{
AccessToken: t.accessToken,
TokenType: "Bearer",
Expiry: t.expiry,
}
}
func (t *cachedTokenSource) update(tok *oauth2.Token) map[string]string {
t.lk.Lock()
defer t.lk.Unlock()
t.accessToken = tok.AccessToken
t.expiry = tok.Expiry
ret := map[string]string{}
for k, v := range t.cache {
ret[k] = v
}
ret["access-token"] = t.accessToken
ret["expiry"] = t.expiry.Format(time.RFC3339Nano)
return ret
}
// baseCache is the base configuration value for this TokenSource, without any cached ephemeral tokens.
func (t *cachedTokenSource) baseCache() map[string]string {
t.lk.Lock()
defer t.lk.Unlock()
ret := map[string]string{}
for k, v := range t.cache {
ret[k] = v
}
delete(ret, "access-token")
delete(ret, "expiry")
return ret
}
type commandTokenSource struct {
cmd string
args []string
tokenKey string
expiryKey string
timeFmt string
}
func newCmdTokenSource(cmd string, args []string, tokenKey, expiryKey, timeFmt string) *commandTokenSource {
if len(timeFmt) == 0 {
timeFmt = time.RFC3339Nano
}
if len(tokenKey) == 0 {
tokenKey = "{.access_token}"
}
if len(expiryKey) == 0 {
expiryKey = "{.token_expiry}"
}
return &commandTokenSource{
cmd: cmd,
args: args,
tokenKey: tokenKey,
expiryKey: expiryKey,
timeFmt: timeFmt,
}
}
func (c *commandTokenSource) Token() (*oauth2.Token, error) {
fullCmd := strings.Join(append([]string{c.cmd}, c.args...), " ")
cmd := execCommand(c.cmd, c.args...)
var stderr bytes.Buffer
cmd.Stderr = &stderr
output, err := cmd.Output()
if err != nil {
return nil, fmt.Errorf("error executing access token command %q: err=%v output=%s stderr=%s", fullCmd, err, output, string(stderr.Bytes()))
}
token, err := c.parseTokenCmdOutput(output)
if err != nil {
return nil, fmt.Errorf("error parsing output for access token command %q: %v", fullCmd, err)
}
return token, nil
}
func (c *commandTokenSource) parseTokenCmdOutput(output []byte) (*oauth2.Token, error) {
output, err := yaml.ToJSON(output)
if err != nil {
return nil, err
}
var data interface{}
if err := json.Unmarshal(output, &data); err != nil {
return nil, err
}
accessToken, err := parseJSONPath(data, "token-key", c.tokenKey)
if err != nil {
return nil, fmt.Errorf("error parsing token-key %q from %q: %v", c.tokenKey, string(output), err)
}
expiryStr, err := parseJSONPath(data, "expiry-key", c.expiryKey)
if err != nil {
return nil, fmt.Errorf("error parsing expiry-key %q from %q: %v", c.expiryKey, string(output), err)
}
var expiry time.Time
if t, err := time.Parse(c.timeFmt, expiryStr); err != nil {
glog.V(4).Infof("Failed to parse token expiry from %s (fmt=%s): %v", expiryStr, c.timeFmt, err)
} else {
expiry = t
}
return &oauth2.Token{
AccessToken: accessToken,
TokenType: "Bearer",
Expiry: expiry,
}, nil
}
func parseJSONPath(input interface{}, name, template string) (string, error) {
j := jsonpath.New(name)
buf := new(bytes.Buffer)
if err := j.Parse(template); err != nil {
return "", err
}
if err := j.Execute(buf, input); err != nil {
return "", err
}
return buf.String(), nil
}
type conditionalTransport struct {
oauthTransport *oauth2.Transport
persister restclient.AuthProviderConfigPersister
resetCache map[string]string
}
var _ net.RoundTripperWrapper = &conditionalTransport{}
func (t *conditionalTransport) RoundTrip(req *http.Request) (*http.Response, error) {
if len(req.Header.Get("Authorization")) != 0 {
return t.oauthTransport.Base.RoundTrip(req)
}
res, err := t.oauthTransport.RoundTrip(req)
if err != nil {
return nil, err
}
if res.StatusCode == 401 {
glog.V(4).Infof("The credentials that were supplied are invalid for the target cluster")
t.persister.Persist(t.resetCache)
}
return res, nil
}
func (t *conditionalTransport) WrappedRoundTripper() http.RoundTripper { return t.oauthTransport.Base }

View File

@ -0,0 +1,94 @@
//This package is copied from Go library text/template.
//The original private functions indirect and printableValue
//are exported as public functions.
package template
import (
"fmt"
"reflect"
)
var Indirect = indirect
var PrintableValue = printableValue
var (
errorType = reflect.TypeOf((*error)(nil)).Elem()
fmtStringerType = reflect.TypeOf((*fmt.Stringer)(nil)).Elem()
)
// indirect returns the item at the end of indirection, and a bool to indicate if it's nil.
// We indirect through pointers and empty interfaces (only) because
// non-empty interfaces have methods we might need.
func indirect(v reflect.Value) (rv reflect.Value, isNil bool) {
for ; v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface; v = v.Elem() {
if v.IsNil() {
return v, true
}
if v.Kind() == reflect.Interface && v.NumMethod() > 0 {
break
}
}
return v, false
}
// printableValue returns the, possibly indirected, interface value inside v that
// is best for a call to formatted printer.
func printableValue(v reflect.Value) (interface{}, bool) {
if v.Kind() == reflect.Ptr {
v, _ = indirect(v) // fmt.Fprint handles nil.
}
if !v.IsValid() {
return "<no value>", true
}
if !v.Type().Implements(errorType) && !v.Type().Implements(fmtStringerType) {
if v.CanAddr() && (reflect.PtrTo(v.Type()).Implements(errorType) || reflect.PtrTo(v.Type()).Implements(fmtStringerType)) {
v = v.Addr()
} else {
switch v.Kind() {
case reflect.Chan, reflect.Func:
return nil, false
}
}
}
return v.Interface(), true
}
// canBeNil reports whether an untyped nil can be assigned to the type. See reflect.Zero.
func canBeNil(typ reflect.Type) bool {
switch typ.Kind() {
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
return true
}
return false
}
// isTrue reports whether the value is 'true', in the sense of not the zero of its type,
// and whether the value has a meaningful truth value.
func isTrue(val reflect.Value) (truth, ok bool) {
if !val.IsValid() {
// Something like var x interface{}, never set. It's a form of nil.
return false, true
}
switch val.Kind() {
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
truth = val.Len() > 0
case reflect.Bool:
truth = val.Bool()
case reflect.Complex64, reflect.Complex128:
truth = val.Complex() != 0
case reflect.Chan, reflect.Func, reflect.Ptr, reflect.Interface:
truth = !val.IsNil()
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
truth = val.Int() != 0
case reflect.Float32, reflect.Float64:
truth = val.Float() != 0
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
truth = val.Uint() != 0
case reflect.Struct:
truth = true // Struct values are always true.
default:
return
}
return truth, true
}

View File

@ -0,0 +1,599 @@
//This package is copied from Go library text/template.
//The original private functions eq, ge, gt, le, lt, and ne
//are exported as public functions.
package template
import (
"bytes"
"errors"
"fmt"
"io"
"net/url"
"reflect"
"strings"
"unicode"
"unicode/utf8"
)
var Equal = eq
var GreaterEqual = ge
var Greater = gt
var LessEqual = le
var Less = lt
var NotEqual = ne
// FuncMap is the type of the map defining the mapping from names to functions.
// Each function must have either a single return value, or two return values of
// which the second has type error. In that case, if the second (error)
// return value evaluates to non-nil during execution, execution terminates and
// Execute returns that error.
type FuncMap map[string]interface{}
var builtins = FuncMap{
"and": and,
"call": call,
"html": HTMLEscaper,
"index": index,
"js": JSEscaper,
"len": length,
"not": not,
"or": or,
"print": fmt.Sprint,
"printf": fmt.Sprintf,
"println": fmt.Sprintln,
"urlquery": URLQueryEscaper,
// Comparisons
"eq": eq, // ==
"ge": ge, // >=
"gt": gt, // >
"le": le, // <=
"lt": lt, // <
"ne": ne, // !=
}
var builtinFuncs = createValueFuncs(builtins)
// createValueFuncs turns a FuncMap into a map[string]reflect.Value
func createValueFuncs(funcMap FuncMap) map[string]reflect.Value {
m := make(map[string]reflect.Value)
addValueFuncs(m, funcMap)
return m
}
// addValueFuncs adds to values the functions in funcs, converting them to reflect.Values.
func addValueFuncs(out map[string]reflect.Value, in FuncMap) {
for name, fn := range in {
v := reflect.ValueOf(fn)
if v.Kind() != reflect.Func {
panic("value for " + name + " not a function")
}
if !goodFunc(v.Type()) {
panic(fmt.Errorf("can't install method/function %q with %d results", name, v.Type().NumOut()))
}
out[name] = v
}
}
// AddFuncs adds to values the functions in funcs. It does no checking of the input -
// call addValueFuncs first.
func addFuncs(out, in FuncMap) {
for name, fn := range in {
out[name] = fn
}
}
// goodFunc checks that the function or method has the right result signature.
func goodFunc(typ reflect.Type) bool {
// We allow functions with 1 result or 2 results where the second is an error.
switch {
case typ.NumOut() == 1:
return true
case typ.NumOut() == 2 && typ.Out(1) == errorType:
return true
}
return false
}
// findFunction looks for a function in the template, and global map.
func findFunction(name string) (reflect.Value, bool) {
if fn := builtinFuncs[name]; fn.IsValid() {
return fn, true
}
return reflect.Value{}, false
}
// Indexing.
// index returns the result of indexing its first argument by the following
// arguments. Thus "index x 1 2 3" is, in Go syntax, x[1][2][3]. Each
// indexed item must be a map, slice, or array.
func index(item interface{}, indices ...interface{}) (interface{}, error) {
v := reflect.ValueOf(item)
for _, i := range indices {
index := reflect.ValueOf(i)
var isNil bool
if v, isNil = indirect(v); isNil {
return nil, fmt.Errorf("index of nil pointer")
}
switch v.Kind() {
case reflect.Array, reflect.Slice, reflect.String:
var x int64
switch index.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
x = index.Int()
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
x = int64(index.Uint())
default:
return nil, fmt.Errorf("cannot index slice/array with type %s", index.Type())
}
if x < 0 || x >= int64(v.Len()) {
return nil, fmt.Errorf("index out of range: %d", x)
}
v = v.Index(int(x))
case reflect.Map:
if !index.IsValid() {
index = reflect.Zero(v.Type().Key())
}
if !index.Type().AssignableTo(v.Type().Key()) {
return nil, fmt.Errorf("%s is not index type for %s", index.Type(), v.Type())
}
if x := v.MapIndex(index); x.IsValid() {
v = x
} else {
v = reflect.Zero(v.Type().Elem())
}
default:
return nil, fmt.Errorf("can't index item of type %s", v.Type())
}
}
return v.Interface(), nil
}
// Length
// length returns the length of the item, with an error if it has no defined length.
func length(item interface{}) (int, error) {
v, isNil := indirect(reflect.ValueOf(item))
if isNil {
return 0, fmt.Errorf("len of nil pointer")
}
switch v.Kind() {
case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice, reflect.String:
return v.Len(), nil
}
return 0, fmt.Errorf("len of type %s", v.Type())
}
// Function invocation
// call returns the result of evaluating the first argument as a function.
// The function must return 1 result, or 2 results, the second of which is an error.
func call(fn interface{}, args ...interface{}) (interface{}, error) {
v := reflect.ValueOf(fn)
typ := v.Type()
if typ.Kind() != reflect.Func {
return nil, fmt.Errorf("non-function of type %s", typ)
}
if !goodFunc(typ) {
return nil, fmt.Errorf("function called with %d args; should be 1 or 2", typ.NumOut())
}
numIn := typ.NumIn()
var dddType reflect.Type
if typ.IsVariadic() {
if len(args) < numIn-1 {
return nil, fmt.Errorf("wrong number of args: got %d want at least %d", len(args), numIn-1)
}
dddType = typ.In(numIn - 1).Elem()
} else {
if len(args) != numIn {
return nil, fmt.Errorf("wrong number of args: got %d want %d", len(args), numIn)
}
}
argv := make([]reflect.Value, len(args))
for i, arg := range args {
value := reflect.ValueOf(arg)
// Compute the expected type. Clumsy because of variadics.
var argType reflect.Type
if !typ.IsVariadic() || i < numIn-1 {
argType = typ.In(i)
} else {
argType = dddType
}
if !value.IsValid() && canBeNil(argType) {
value = reflect.Zero(argType)
}
if !value.Type().AssignableTo(argType) {
return nil, fmt.Errorf("arg %d has type %s; should be %s", i, value.Type(), argType)
}
argv[i] = value
}
result := v.Call(argv)
if len(result) == 2 && !result[1].IsNil() {
return result[0].Interface(), result[1].Interface().(error)
}
return result[0].Interface(), nil
}
// Boolean logic.
func truth(a interface{}) bool {
t, _ := isTrue(reflect.ValueOf(a))
return t
}
// and computes the Boolean AND of its arguments, returning
// the first false argument it encounters, or the last argument.
func and(arg0 interface{}, args ...interface{}) interface{} {
if !truth(arg0) {
return arg0
}
for i := range args {
arg0 = args[i]
if !truth(arg0) {
break
}
}
return arg0
}
// or computes the Boolean OR of its arguments, returning
// the first true argument it encounters, or the last argument.
func or(arg0 interface{}, args ...interface{}) interface{} {
if truth(arg0) {
return arg0
}
for i := range args {
arg0 = args[i]
if truth(arg0) {
break
}
}
return arg0
}
// not returns the Boolean negation of its argument.
func not(arg interface{}) (truth bool) {
truth, _ = isTrue(reflect.ValueOf(arg))
return !truth
}
// Comparison.
// TODO: Perhaps allow comparison between signed and unsigned integers.
var (
errBadComparisonType = errors.New("invalid type for comparison")
errBadComparison = errors.New("incompatible types for comparison")
errNoComparison = errors.New("missing argument for comparison")
)
type kind int
const (
invalidKind kind = iota
boolKind
complexKind
intKind
floatKind
integerKind
stringKind
uintKind
)
func basicKind(v reflect.Value) (kind, error) {
switch v.Kind() {
case reflect.Bool:
return boolKind, nil
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return intKind, nil
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return uintKind, nil
case reflect.Float32, reflect.Float64:
return floatKind, nil
case reflect.Complex64, reflect.Complex128:
return complexKind, nil
case reflect.String:
return stringKind, nil
}
return invalidKind, errBadComparisonType
}
// eq evaluates the comparison a == b || a == c || ...
func eq(arg1 interface{}, arg2 ...interface{}) (bool, error) {
v1 := reflect.ValueOf(arg1)
k1, err := basicKind(v1)
if err != nil {
return false, err
}
if len(arg2) == 0 {
return false, errNoComparison
}
for _, arg := range arg2 {
v2 := reflect.ValueOf(arg)
k2, err := basicKind(v2)
if err != nil {
return false, err
}
truth := false
if k1 != k2 {
// Special case: Can compare integer values regardless of type's sign.
switch {
case k1 == intKind && k2 == uintKind:
truth = v1.Int() >= 0 && uint64(v1.Int()) == v2.Uint()
case k1 == uintKind && k2 == intKind:
truth = v2.Int() >= 0 && v1.Uint() == uint64(v2.Int())
default:
return false, errBadComparison
}
} else {
switch k1 {
case boolKind:
truth = v1.Bool() == v2.Bool()
case complexKind:
truth = v1.Complex() == v2.Complex()
case floatKind:
truth = v1.Float() == v2.Float()
case intKind:
truth = v1.Int() == v2.Int()
case stringKind:
truth = v1.String() == v2.String()
case uintKind:
truth = v1.Uint() == v2.Uint()
default:
panic("invalid kind")
}
}
if truth {
return true, nil
}
}
return false, nil
}
// ne evaluates the comparison a != b.
func ne(arg1, arg2 interface{}) (bool, error) {
// != is the inverse of ==.
equal, err := eq(arg1, arg2)
return !equal, err
}
// lt evaluates the comparison a < b.
func lt(arg1, arg2 interface{}) (bool, error) {
v1 := reflect.ValueOf(arg1)
k1, err := basicKind(v1)
if err != nil {
return false, err
}
v2 := reflect.ValueOf(arg2)
k2, err := basicKind(v2)
if err != nil {
return false, err
}
truth := false
if k1 != k2 {
// Special case: Can compare integer values regardless of type's sign.
switch {
case k1 == intKind && k2 == uintKind:
truth = v1.Int() < 0 || uint64(v1.Int()) < v2.Uint()
case k1 == uintKind && k2 == intKind:
truth = v2.Int() >= 0 && v1.Uint() < uint64(v2.Int())
default:
return false, errBadComparison
}
} else {
switch k1 {
case boolKind, complexKind:
return false, errBadComparisonType
case floatKind:
truth = v1.Float() < v2.Float()
case intKind:
truth = v1.Int() < v2.Int()
case stringKind:
truth = v1.String() < v2.String()
case uintKind:
truth = v1.Uint() < v2.Uint()
default:
panic("invalid kind")
}
}
return truth, nil
}
// le evaluates the comparison <= b.
func le(arg1, arg2 interface{}) (bool, error) {
// <= is < or ==.
lessThan, err := lt(arg1, arg2)
if lessThan || err != nil {
return lessThan, err
}
return eq(arg1, arg2)
}
// gt evaluates the comparison a > b.
func gt(arg1, arg2 interface{}) (bool, error) {
// > is the inverse of <=.
lessOrEqual, err := le(arg1, arg2)
if err != nil {
return false, err
}
return !lessOrEqual, nil
}
// ge evaluates the comparison a >= b.
func ge(arg1, arg2 interface{}) (bool, error) {
// >= is the inverse of <.
lessThan, err := lt(arg1, arg2)
if err != nil {
return false, err
}
return !lessThan, nil
}
// HTML escaping.
var (
htmlQuot = []byte("&#34;") // shorter than "&quot;"
htmlApos = []byte("&#39;") // shorter than "&apos;" and apos was not in HTML until HTML5
htmlAmp = []byte("&amp;")
htmlLt = []byte("&lt;")
htmlGt = []byte("&gt;")
)
// HTMLEscape writes to w the escaped HTML equivalent of the plain text data b.
func HTMLEscape(w io.Writer, b []byte) {
last := 0
for i, c := range b {
var html []byte
switch c {
case '"':
html = htmlQuot
case '\'':
html = htmlApos
case '&':
html = htmlAmp
case '<':
html = htmlLt
case '>':
html = htmlGt
default:
continue
}
w.Write(b[last:i])
w.Write(html)
last = i + 1
}
w.Write(b[last:])
}
// HTMLEscapeString returns the escaped HTML equivalent of the plain text data s.
func HTMLEscapeString(s string) string {
// Avoid allocation if we can.
if strings.IndexAny(s, `'"&<>`) < 0 {
return s
}
var b bytes.Buffer
HTMLEscape(&b, []byte(s))
return b.String()
}
// HTMLEscaper returns the escaped HTML equivalent of the textual
// representation of its arguments.
func HTMLEscaper(args ...interface{}) string {
return HTMLEscapeString(evalArgs(args))
}
// JavaScript escaping.
var (
jsLowUni = []byte(`\u00`)
hex = []byte("0123456789ABCDEF")
jsBackslash = []byte(`\\`)
jsApos = []byte(`\'`)
jsQuot = []byte(`\"`)
jsLt = []byte(`\x3C`)
jsGt = []byte(`\x3E`)
)
// JSEscape writes to w the escaped JavaScript equivalent of the plain text data b.
func JSEscape(w io.Writer, b []byte) {
last := 0
for i := 0; i < len(b); i++ {
c := b[i]
if !jsIsSpecial(rune(c)) {
// fast path: nothing to do
continue
}
w.Write(b[last:i])
if c < utf8.RuneSelf {
// Quotes, slashes and angle brackets get quoted.
// Control characters get written as \u00XX.
switch c {
case '\\':
w.Write(jsBackslash)
case '\'':
w.Write(jsApos)
case '"':
w.Write(jsQuot)
case '<':
w.Write(jsLt)
case '>':
w.Write(jsGt)
default:
w.Write(jsLowUni)
t, b := c>>4, c&0x0f
w.Write(hex[t : t+1])
w.Write(hex[b : b+1])
}
} else {
// Unicode rune.
r, size := utf8.DecodeRune(b[i:])
if unicode.IsPrint(r) {
w.Write(b[i : i+size])
} else {
fmt.Fprintf(w, "\\u%04X", r)
}
i += size - 1
}
last = i + 1
}
w.Write(b[last:])
}
// JSEscapeString returns the escaped JavaScript equivalent of the plain text data s.
func JSEscapeString(s string) string {
// Avoid allocation if we can.
if strings.IndexFunc(s, jsIsSpecial) < 0 {
return s
}
var b bytes.Buffer
JSEscape(&b, []byte(s))
return b.String()
}
func jsIsSpecial(r rune) bool {
switch r {
case '\\', '\'', '"', '<', '>':
return true
}
return r < ' ' || utf8.RuneSelf <= r
}
// JSEscaper returns the escaped JavaScript equivalent of the textual
// representation of its arguments.
func JSEscaper(args ...interface{}) string {
return JSEscapeString(evalArgs(args))
}
// URLQueryEscaper returns the escaped value of the textual representation of
// its arguments in a form suitable for embedding in a URL query.
func URLQueryEscaper(args ...interface{}) string {
return url.QueryEscape(evalArgs(args))
}
// evalArgs formats the list of arguments into a string. It is therefore equivalent to
// fmt.Sprint(args...)
// except that each argument is indirected (if a pointer), as required,
// using the same rules as the default string evaluation during template
// execution.
func evalArgs(args []interface{}) string {
ok := false
var s string
// Fast path for simple common case.
if len(args) == 1 {
s, ok = args[0].(string)
}
if !ok {
for i, arg := range args {
a, ok := printableValue(reflect.ValueOf(arg))
if ok {
args[i] = a
} // else left fmt do its thing
}
s = fmt.Sprint(args...)
}
return s
}

20
vendor/k8s.io/client-go/util/jsonpath/doc.go generated vendored Normal file
View File

@ -0,0 +1,20 @@
/*
Copyright 2015 The Kubernetes 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 jsonpath is a template engine using jsonpath syntax,
// which can be seen at http://goessner.net/articles/JsonPath/.
// In addition, it has {range} {end} function to iterate list and slice.
package jsonpath // import "k8s.io/client-go/util/jsonpath"

517
vendor/k8s.io/client-go/util/jsonpath/jsonpath.go generated vendored Normal file
View File

@ -0,0 +1,517 @@
/*
Copyright 2015 The Kubernetes 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 jsonpath
import (
"bytes"
"fmt"
"io"
"reflect"
"strings"
"k8s.io/client-go/third_party/forked/golang/template"
)
type JSONPath struct {
name string
parser *Parser
stack [][]reflect.Value // push and pop values in different scopes
cur []reflect.Value // current scope values
beginRange int
inRange int
endRange int
allowMissingKeys bool
}
// New creates a new JSONPath with the given name.
func New(name string) *JSONPath {
return &JSONPath{
name: name,
beginRange: 0,
inRange: 0,
endRange: 0,
}
}
// AllowMissingKeys allows a caller to specify whether they want an error if a field or map key
// cannot be located, or simply an empty result. The receiver is returned for chaining.
func (j *JSONPath) AllowMissingKeys(allow bool) *JSONPath {
j.allowMissingKeys = allow
return j
}
// Parse parses the given template and returns an error.
func (j *JSONPath) Parse(text string) error {
var err error
j.parser, err = Parse(j.name, text)
return err
}
// Execute bounds data into template and writes the result.
func (j *JSONPath) Execute(wr io.Writer, data interface{}) error {
fullResults, err := j.FindResults(data)
if err != nil {
return err
}
for ix := range fullResults {
if err := j.PrintResults(wr, fullResults[ix]); err != nil {
return err
}
}
return nil
}
func (j *JSONPath) FindResults(data interface{}) ([][]reflect.Value, error) {
if j.parser == nil {
return nil, fmt.Errorf("%s is an incomplete jsonpath template", j.name)
}
j.cur = []reflect.Value{reflect.ValueOf(data)}
nodes := j.parser.Root.Nodes
fullResult := [][]reflect.Value{}
for i := 0; i < len(nodes); i++ {
node := nodes[i]
results, err := j.walk(j.cur, node)
if err != nil {
return nil, err
}
// encounter an end node, break the current block
if j.endRange > 0 && j.endRange <= j.inRange {
j.endRange -= 1
break
}
// encounter a range node, start a range loop
if j.beginRange > 0 {
j.beginRange -= 1
j.inRange += 1
for k, value := range results {
j.parser.Root.Nodes = nodes[i+1:]
if k == len(results)-1 {
j.inRange -= 1
}
nextResults, err := j.FindResults(value.Interface())
if err != nil {
return nil, err
}
fullResult = append(fullResult, nextResults...)
}
break
}
fullResult = append(fullResult, results)
}
return fullResult, nil
}
// PrintResults writes the results into writer
func (j *JSONPath) PrintResults(wr io.Writer, results []reflect.Value) error {
for i, r := range results {
text, err := j.evalToText(r)
if err != nil {
return err
}
if i != len(results)-1 {
text = append(text, ' ')
}
if _, err = wr.Write(text); err != nil {
return err
}
}
return nil
}
// walk visits tree rooted at the given node in DFS order
func (j *JSONPath) walk(value []reflect.Value, node Node) ([]reflect.Value, error) {
switch node := node.(type) {
case *ListNode:
return j.evalList(value, node)
case *TextNode:
return []reflect.Value{reflect.ValueOf(node.Text)}, nil
case *FieldNode:
return j.evalField(value, node)
case *ArrayNode:
return j.evalArray(value, node)
case *FilterNode:
return j.evalFilter(value, node)
case *IntNode:
return j.evalInt(value, node)
case *BoolNode:
return j.evalBool(value, node)
case *FloatNode:
return j.evalFloat(value, node)
case *WildcardNode:
return j.evalWildcard(value, node)
case *RecursiveNode:
return j.evalRecursive(value, node)
case *UnionNode:
return j.evalUnion(value, node)
case *IdentifierNode:
return j.evalIdentifier(value, node)
default:
return value, fmt.Errorf("unexpected Node %v", node)
}
}
// evalInt evaluates IntNode
func (j *JSONPath) evalInt(input []reflect.Value, node *IntNode) ([]reflect.Value, error) {
result := make([]reflect.Value, len(input))
for i := range input {
result[i] = reflect.ValueOf(node.Value)
}
return result, nil
}
// evalFloat evaluates FloatNode
func (j *JSONPath) evalFloat(input []reflect.Value, node *FloatNode) ([]reflect.Value, error) {
result := make([]reflect.Value, len(input))
for i := range input {
result[i] = reflect.ValueOf(node.Value)
}
return result, nil
}
// evalBool evaluates BoolNode
func (j *JSONPath) evalBool(input []reflect.Value, node *BoolNode) ([]reflect.Value, error) {
result := make([]reflect.Value, len(input))
for i := range input {
result[i] = reflect.ValueOf(node.Value)
}
return result, nil
}
// evalList evaluates ListNode
func (j *JSONPath) evalList(value []reflect.Value, node *ListNode) ([]reflect.Value, error) {
var err error
curValue := value
for _, node := range node.Nodes {
curValue, err = j.walk(curValue, node)
if err != nil {
return curValue, err
}
}
return curValue, nil
}
// evalIdentifier evaluates IdentifierNode
func (j *JSONPath) evalIdentifier(input []reflect.Value, node *IdentifierNode) ([]reflect.Value, error) {
results := []reflect.Value{}
switch node.Name {
case "range":
j.stack = append(j.stack, j.cur)
j.beginRange += 1
results = input
case "end":
if j.endRange < j.inRange { // inside a loop, break the current block
j.endRange += 1
break
}
// the loop is about to end, pop value and continue the following execution
if len(j.stack) > 0 {
j.cur, j.stack = j.stack[len(j.stack)-1], j.stack[:len(j.stack)-1]
} else {
return results, fmt.Errorf("not in range, nothing to end")
}
default:
return input, fmt.Errorf("unrecognized identifier %v", node.Name)
}
return results, nil
}
// evalArray evaluates ArrayNode
func (j *JSONPath) evalArray(input []reflect.Value, node *ArrayNode) ([]reflect.Value, error) {
result := []reflect.Value{}
for _, value := range input {
value, isNil := template.Indirect(value)
if isNil {
continue
}
if value.Kind() != reflect.Array && value.Kind() != reflect.Slice {
return input, fmt.Errorf("%v is not array or slice", value.Type())
}
params := node.Params
if !params[0].Known {
params[0].Value = 0
}
if params[0].Value < 0 {
params[0].Value += value.Len()
}
if !params[1].Known {
params[1].Value = value.Len()
}
if params[1].Value < 0 {
params[1].Value += value.Len()
}
sliceLength := value.Len()
if params[1].Value != params[0].Value { // if you're requesting zero elements, allow it through.
if params[0].Value >= sliceLength || params[0].Value < 0 {
return input, fmt.Errorf("array index out of bounds: index %d, length %d", params[0].Value, sliceLength)
}
if params[1].Value > sliceLength || params[1].Value < 0 {
return input, fmt.Errorf("array index out of bounds: index %d, length %d", params[1].Value-1, sliceLength)
}
}
if !params[2].Known {
value = value.Slice(params[0].Value, params[1].Value)
} else {
value = value.Slice3(params[0].Value, params[1].Value, params[2].Value)
}
for i := 0; i < value.Len(); i++ {
result = append(result, value.Index(i))
}
}
return result, nil
}
// evalUnion evaluates UnionNode
func (j *JSONPath) evalUnion(input []reflect.Value, node *UnionNode) ([]reflect.Value, error) {
result := []reflect.Value{}
for _, listNode := range node.Nodes {
temp, err := j.evalList(input, listNode)
if err != nil {
return input, err
}
result = append(result, temp...)
}
return result, nil
}
func (j *JSONPath) findFieldInValue(value *reflect.Value, node *FieldNode) (reflect.Value, error) {
t := value.Type()
var inlineValue *reflect.Value
for ix := 0; ix < t.NumField(); ix++ {
f := t.Field(ix)
jsonTag := f.Tag.Get("json")
parts := strings.Split(jsonTag, ",")
if len(parts) == 0 {
continue
}
if parts[0] == node.Value {
return value.Field(ix), nil
}
if len(parts[0]) == 0 {
val := value.Field(ix)
inlineValue = &val
}
}
if inlineValue != nil {
if inlineValue.Kind() == reflect.Struct {
// handle 'inline'
match, err := j.findFieldInValue(inlineValue, node)
if err != nil {
return reflect.Value{}, err
}
if match.IsValid() {
return match, nil
}
}
}
return value.FieldByName(node.Value), nil
}
// evalField evaluates field of struct or key of map.
func (j *JSONPath) evalField(input []reflect.Value, node *FieldNode) ([]reflect.Value, error) {
results := []reflect.Value{}
// If there's no input, there's no output
if len(input) == 0 {
return results, nil
}
for _, value := range input {
var result reflect.Value
value, isNil := template.Indirect(value)
if isNil {
continue
}
if value.Kind() == reflect.Struct {
var err error
if result, err = j.findFieldInValue(&value, node); err != nil {
return nil, err
}
} else if value.Kind() == reflect.Map {
mapKeyType := value.Type().Key()
nodeValue := reflect.ValueOf(node.Value)
// node value type must be convertible to map key type
if !nodeValue.Type().ConvertibleTo(mapKeyType) {
return results, fmt.Errorf("%s is not convertible to %s", nodeValue, mapKeyType)
}
result = value.MapIndex(nodeValue.Convert(mapKeyType))
}
if result.IsValid() {
results = append(results, result)
}
}
if len(results) == 0 {
if j.allowMissingKeys {
return results, nil
}
return results, fmt.Errorf("%s is not found", node.Value)
}
return results, nil
}
// evalWildcard extracts all contents of the given value
func (j *JSONPath) evalWildcard(input []reflect.Value, node *WildcardNode) ([]reflect.Value, error) {
results := []reflect.Value{}
for _, value := range input {
value, isNil := template.Indirect(value)
if isNil {
continue
}
kind := value.Kind()
if kind == reflect.Struct {
for i := 0; i < value.NumField(); i++ {
results = append(results, value.Field(i))
}
} else if kind == reflect.Map {
for _, key := range value.MapKeys() {
results = append(results, value.MapIndex(key))
}
} else if kind == reflect.Array || kind == reflect.Slice || kind == reflect.String {
for i := 0; i < value.Len(); i++ {
results = append(results, value.Index(i))
}
}
}
return results, nil
}
// evalRecursive visits the given value recursively and pushes all of them to result
func (j *JSONPath) evalRecursive(input []reflect.Value, node *RecursiveNode) ([]reflect.Value, error) {
result := []reflect.Value{}
for _, value := range input {
results := []reflect.Value{}
value, isNil := template.Indirect(value)
if isNil {
continue
}
kind := value.Kind()
if kind == reflect.Struct {
for i := 0; i < value.NumField(); i++ {
results = append(results, value.Field(i))
}
} else if kind == reflect.Map {
for _, key := range value.MapKeys() {
results = append(results, value.MapIndex(key))
}
} else if kind == reflect.Array || kind == reflect.Slice || kind == reflect.String {
for i := 0; i < value.Len(); i++ {
results = append(results, value.Index(i))
}
}
if len(results) != 0 {
result = append(result, value)
output, err := j.evalRecursive(results, node)
if err != nil {
return result, err
}
result = append(result, output...)
}
}
return result, nil
}
// evalFilter filters array according to FilterNode
func (j *JSONPath) evalFilter(input []reflect.Value, node *FilterNode) ([]reflect.Value, error) {
results := []reflect.Value{}
for _, value := range input {
value, _ = template.Indirect(value)
if value.Kind() != reflect.Array && value.Kind() != reflect.Slice {
return input, fmt.Errorf("%v is not array or slice and cannot be filtered", value)
}
for i := 0; i < value.Len(); i++ {
temp := []reflect.Value{value.Index(i)}
lefts, err := j.evalList(temp, node.Left)
//case exists
if node.Operator == "exists" {
if len(lefts) > 0 {
results = append(results, value.Index(i))
}
continue
}
if err != nil {
return input, err
}
var left, right interface{}
switch {
case len(lefts) == 0:
continue
case len(lefts) > 1:
return input, fmt.Errorf("can only compare one element at a time")
}
left = lefts[0].Interface()
rights, err := j.evalList(temp, node.Right)
if err != nil {
return input, err
}
switch {
case len(rights) == 0:
continue
case len(rights) > 1:
return input, fmt.Errorf("can only compare one element at a time")
}
right = rights[0].Interface()
pass := false
switch node.Operator {
case "<":
pass, err = template.Less(left, right)
case ">":
pass, err = template.Greater(left, right)
case "==":
pass, err = template.Equal(left, right)
case "!=":
pass, err = template.NotEqual(left, right)
case "<=":
pass, err = template.LessEqual(left, right)
case ">=":
pass, err = template.GreaterEqual(left, right)
default:
return results, fmt.Errorf("unrecognized filter operator %s", node.Operator)
}
if err != nil {
return results, err
}
if pass {
results = append(results, value.Index(i))
}
}
}
return results, nil
}
// evalToText translates reflect value to corresponding text
func (j *JSONPath) evalToText(v reflect.Value) ([]byte, error) {
iface, ok := template.PrintableValue(v)
if !ok {
return nil, fmt.Errorf("can't print type %s", v.Type())
}
var buffer bytes.Buffer
fmt.Fprint(&buffer, iface)
return buffer.Bytes(), nil
}

255
vendor/k8s.io/client-go/util/jsonpath/node.go generated vendored Normal file
View File

@ -0,0 +1,255 @@
/*
Copyright 2015 The Kubernetes 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 jsonpath
import "fmt"
// NodeType identifies the type of a parse tree node.
type NodeType int
// Type returns itself and provides an easy default implementation
func (t NodeType) Type() NodeType {
return t
}
func (t NodeType) String() string {
return NodeTypeName[t]
}
const (
NodeText NodeType = iota
NodeArray
NodeList
NodeField
NodeIdentifier
NodeFilter
NodeInt
NodeFloat
NodeWildcard
NodeRecursive
NodeUnion
NodeBool
)
var NodeTypeName = map[NodeType]string{
NodeText: "NodeText",
NodeArray: "NodeArray",
NodeList: "NodeList",
NodeField: "NodeField",
NodeIdentifier: "NodeIdentifier",
NodeFilter: "NodeFilter",
NodeInt: "NodeInt",
NodeFloat: "NodeFloat",
NodeWildcard: "NodeWildcard",
NodeRecursive: "NodeRecursive",
NodeUnion: "NodeUnion",
NodeBool: "NodeBool",
}
type Node interface {
Type() NodeType
String() string
}
// ListNode holds a sequence of nodes.
type ListNode struct {
NodeType
Nodes []Node // The element nodes in lexical order.
}
func newList() *ListNode {
return &ListNode{NodeType: NodeList}
}
func (l *ListNode) append(n Node) {
l.Nodes = append(l.Nodes, n)
}
func (l *ListNode) String() string {
return l.Type().String()
}
// TextNode holds plain text.
type TextNode struct {
NodeType
Text string // The text; may span newlines.
}
func newText(text string) *TextNode {
return &TextNode{NodeType: NodeText, Text: text}
}
func (t *TextNode) String() string {
return fmt.Sprintf("%s: %s", t.Type(), t.Text)
}
// FieldNode holds field of struct
type FieldNode struct {
NodeType
Value string
}
func newField(value string) *FieldNode {
return &FieldNode{NodeType: NodeField, Value: value}
}
func (f *FieldNode) String() string {
return fmt.Sprintf("%s: %s", f.Type(), f.Value)
}
// IdentifierNode holds an identifier
type IdentifierNode struct {
NodeType
Name string
}
func newIdentifier(value string) *IdentifierNode {
return &IdentifierNode{
NodeType: NodeIdentifier,
Name: value,
}
}
func (f *IdentifierNode) String() string {
return fmt.Sprintf("%s: %s", f.Type(), f.Name)
}
// ParamsEntry holds param information for ArrayNode
type ParamsEntry struct {
Value int
Known bool // whether the value is known when parse it
}
// ArrayNode holds start, end, step information for array index selection
type ArrayNode struct {
NodeType
Params [3]ParamsEntry // start, end, step
}
func newArray(params [3]ParamsEntry) *ArrayNode {
return &ArrayNode{
NodeType: NodeArray,
Params: params,
}
}
func (a *ArrayNode) String() string {
return fmt.Sprintf("%s: %v", a.Type(), a.Params)
}
// FilterNode holds operand and operator information for filter
type FilterNode struct {
NodeType
Left *ListNode
Right *ListNode
Operator string
}
func newFilter(left, right *ListNode, operator string) *FilterNode {
return &FilterNode{
NodeType: NodeFilter,
Left: left,
Right: right,
Operator: operator,
}
}
func (f *FilterNode) String() string {
return fmt.Sprintf("%s: %s %s %s", f.Type(), f.Left, f.Operator, f.Right)
}
// IntNode holds integer value
type IntNode struct {
NodeType
Value int
}
func newInt(num int) *IntNode {
return &IntNode{NodeType: NodeInt, Value: num}
}
func (i *IntNode) String() string {
return fmt.Sprintf("%s: %d", i.Type(), i.Value)
}
// FloatNode holds float value
type FloatNode struct {
NodeType
Value float64
}
func newFloat(num float64) *FloatNode {
return &FloatNode{NodeType: NodeFloat, Value: num}
}
func (i *FloatNode) String() string {
return fmt.Sprintf("%s: %f", i.Type(), i.Value)
}
// WildcardNode means a wildcard
type WildcardNode struct {
NodeType
}
func newWildcard() *WildcardNode {
return &WildcardNode{NodeType: NodeWildcard}
}
func (i *WildcardNode) String() string {
return i.Type().String()
}
// RecursiveNode means a recursive descent operator
type RecursiveNode struct {
NodeType
}
func newRecursive() *RecursiveNode {
return &RecursiveNode{NodeType: NodeRecursive}
}
func (r *RecursiveNode) String() string {
return r.Type().String()
}
// UnionNode is union of ListNode
type UnionNode struct {
NodeType
Nodes []*ListNode
}
func newUnion(nodes []*ListNode) *UnionNode {
return &UnionNode{NodeType: NodeUnion, Nodes: nodes}
}
func (u *UnionNode) String() string {
return u.Type().String()
}
// BoolNode holds bool value
type BoolNode struct {
NodeType
Value bool
}
func newBool(value bool) *BoolNode {
return &BoolNode{NodeType: NodeBool, Value: value}
}
func (b *BoolNode) String() string {
return fmt.Sprintf("%s: %t", b.Type(), b.Value)
}

525
vendor/k8s.io/client-go/util/jsonpath/parser.go generated vendored Normal file
View File

@ -0,0 +1,525 @@
/*
Copyright 2015 The Kubernetes 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 jsonpath
import (
"errors"
"fmt"
"regexp"
"strconv"
"strings"
"unicode"
"unicode/utf8"
)
const eof = -1
const (
leftDelim = "{"
rightDelim = "}"
)
type Parser struct {
Name string
Root *ListNode
input string
cur *ListNode
pos int
start int
width int
}
var (
ErrSyntax = errors.New("invalid syntax")
dictKeyRex = regexp.MustCompile(`^'([^']*)'$`)
sliceOperatorRex = regexp.MustCompile(`^(-?[\d]*)(:-?[\d]*)?(:[\d]*)?$`)
)
// Parse parsed the given text and return a node Parser.
// If an error is encountered, parsing stops and an empty
// Parser is returned with the error
func Parse(name, text string) (*Parser, error) {
p := NewParser(name)
err := p.Parse(text)
if err != nil {
p = nil
}
return p, err
}
func NewParser(name string) *Parser {
return &Parser{
Name: name,
}
}
// parseAction parsed the expression inside delimiter
func parseAction(name, text string) (*Parser, error) {
p, err := Parse(name, fmt.Sprintf("%s%s%s", leftDelim, text, rightDelim))
// when error happens, p will be nil, so we need to return here
if err != nil {
return p, err
}
p.Root = p.Root.Nodes[0].(*ListNode)
return p, nil
}
func (p *Parser) Parse(text string) error {
p.input = text
p.Root = newList()
p.pos = 0
return p.parseText(p.Root)
}
// consumeText return the parsed text since last cosumeText
func (p *Parser) consumeText() string {
value := p.input[p.start:p.pos]
p.start = p.pos
return value
}
// next returns the next rune in the input.
func (p *Parser) next() rune {
if p.pos >= len(p.input) {
p.width = 0
return eof
}
r, w := utf8.DecodeRuneInString(p.input[p.pos:])
p.width = w
p.pos += p.width
return r
}
// peek returns but does not consume the next rune in the input.
func (p *Parser) peek() rune {
r := p.next()
p.backup()
return r
}
// backup steps back one rune. Can only be called once per call of next.
func (p *Parser) backup() {
p.pos -= p.width
}
func (p *Parser) parseText(cur *ListNode) error {
for {
if strings.HasPrefix(p.input[p.pos:], leftDelim) {
if p.pos > p.start {
cur.append(newText(p.consumeText()))
}
return p.parseLeftDelim(cur)
}
if p.next() == eof {
break
}
}
// Correctly reached EOF.
if p.pos > p.start {
cur.append(newText(p.consumeText()))
}
return nil
}
// parseLeftDelim scans the left delimiter, which is known to be present.
func (p *Parser) parseLeftDelim(cur *ListNode) error {
p.pos += len(leftDelim)
p.consumeText()
newNode := newList()
cur.append(newNode)
cur = newNode
return p.parseInsideAction(cur)
}
func (p *Parser) parseInsideAction(cur *ListNode) error {
prefixMap := map[string]func(*ListNode) error{
rightDelim: p.parseRightDelim,
"[?(": p.parseFilter,
"..": p.parseRecursive,
}
for prefix, parseFunc := range prefixMap {
if strings.HasPrefix(p.input[p.pos:], prefix) {
return parseFunc(cur)
}
}
switch r := p.next(); {
case r == eof || isEndOfLine(r):
return fmt.Errorf("unclosed action")
case r == ' ':
p.consumeText()
case r == '@' || r == '$': //the current object, just pass it
p.consumeText()
case r == '[':
return p.parseArray(cur)
case r == '"' || r == '\'':
return p.parseQuote(cur, r)
case r == '.':
return p.parseField(cur)
case r == '+' || r == '-' || unicode.IsDigit(r):
p.backup()
return p.parseNumber(cur)
case isAlphaNumeric(r):
p.backup()
return p.parseIdentifier(cur)
default:
return fmt.Errorf("unrecognized character in action: %#U", r)
}
return p.parseInsideAction(cur)
}
// parseRightDelim scans the right delimiter, which is known to be present.
func (p *Parser) parseRightDelim(cur *ListNode) error {
p.pos += len(rightDelim)
p.consumeText()
cur = p.Root
return p.parseText(cur)
}
// parseIdentifier scans build-in keywords, like "range" "end"
func (p *Parser) parseIdentifier(cur *ListNode) error {
var r rune
for {
r = p.next()
if isTerminator(r) {
p.backup()
break
}
}
value := p.consumeText()
if isBool(value) {
v, err := strconv.ParseBool(value)
if err != nil {
return fmt.Errorf("can not parse bool '%s': %s", value, err.Error())
}
cur.append(newBool(v))
} else {
cur.append(newIdentifier(value))
}
return p.parseInsideAction(cur)
}
// parseRecursive scans the recursive desent operator ..
func (p *Parser) parseRecursive(cur *ListNode) error {
p.pos += len("..")
p.consumeText()
cur.append(newRecursive())
if r := p.peek(); isAlphaNumeric(r) {
return p.parseField(cur)
}
return p.parseInsideAction(cur)
}
// parseNumber scans number
func (p *Parser) parseNumber(cur *ListNode) error {
r := p.peek()
if r == '+' || r == '-' {
r = p.next()
}
for {
r = p.next()
if r != '.' && !unicode.IsDigit(r) {
p.backup()
break
}
}
value := p.consumeText()
i, err := strconv.Atoi(value)
if err == nil {
cur.append(newInt(i))
return p.parseInsideAction(cur)
}
d, err := strconv.ParseFloat(value, 64)
if err == nil {
cur.append(newFloat(d))
return p.parseInsideAction(cur)
}
return fmt.Errorf("cannot parse number %s", value)
}
// parseArray scans array index selection
func (p *Parser) parseArray(cur *ListNode) error {
Loop:
for {
switch p.next() {
case eof, '\n':
return fmt.Errorf("unterminated array")
case ']':
break Loop
}
}
text := p.consumeText()
text = text[1 : len(text)-1]
if text == "*" {
text = ":"
}
//union operator
strs := strings.Split(text, ",")
if len(strs) > 1 {
union := []*ListNode{}
for _, str := range strs {
parser, err := parseAction("union", fmt.Sprintf("[%s]", strings.Trim(str, " ")))
if err != nil {
return err
}
union = append(union, parser.Root)
}
cur.append(newUnion(union))
return p.parseInsideAction(cur)
}
// dict key
value := dictKeyRex.FindStringSubmatch(text)
if value != nil {
parser, err := parseAction("arraydict", fmt.Sprintf(".%s", value[1]))
if err != nil {
return err
}
for _, node := range parser.Root.Nodes {
cur.append(node)
}
return p.parseInsideAction(cur)
}
//slice operator
value = sliceOperatorRex.FindStringSubmatch(text)
if value == nil {
return fmt.Errorf("invalid array index %s", text)
}
value = value[1:]
params := [3]ParamsEntry{}
for i := 0; i < 3; i++ {
if value[i] != "" {
if i > 0 {
value[i] = value[i][1:]
}
if i > 0 && value[i] == "" {
params[i].Known = false
} else {
var err error
params[i].Known = true
params[i].Value, err = strconv.Atoi(value[i])
if err != nil {
return fmt.Errorf("array index %s is not a number", value[i])
}
}
} else {
if i == 1 {
params[i].Known = true
params[i].Value = params[0].Value + 1
} else {
params[i].Known = false
params[i].Value = 0
}
}
}
cur.append(newArray(params))
return p.parseInsideAction(cur)
}
// parseFilter scans filter inside array selection
func (p *Parser) parseFilter(cur *ListNode) error {
p.pos += len("[?(")
p.consumeText()
begin := false
end := false
var pair rune
Loop:
for {
r := p.next()
switch r {
case eof, '\n':
return fmt.Errorf("unterminated filter")
case '"', '\'':
if begin == false {
//save the paired rune
begin = true
pair = r
continue
}
//only add when met paired rune
if p.input[p.pos-2] != '\\' && r == pair {
end = true
}
case ')':
//in rightParser below quotes only appear zero or once
//and must be paired at the beginning and end
if begin == end {
break Loop
}
}
}
if p.next() != ']' {
return fmt.Errorf("unclosed array expect ]")
}
reg := regexp.MustCompile(`^([^!<>=]+)([!<>=]+)(.+?)$`)
text := p.consumeText()
text = text[:len(text)-2]
value := reg.FindStringSubmatch(text)
if value == nil {
parser, err := parseAction("text", text)
if err != nil {
return err
}
cur.append(newFilter(parser.Root, newList(), "exists"))
} else {
leftParser, err := parseAction("left", value[1])
if err != nil {
return err
}
rightParser, err := parseAction("right", value[3])
if err != nil {
return err
}
cur.append(newFilter(leftParser.Root, rightParser.Root, value[2]))
}
return p.parseInsideAction(cur)
}
// parseQuote unquotes string inside double or single quote
func (p *Parser) parseQuote(cur *ListNode, end rune) error {
Loop:
for {
switch p.next() {
case eof, '\n':
return fmt.Errorf("unterminated quoted string")
case end:
//if it's not escape break the Loop
if p.input[p.pos-2] != '\\' {
break Loop
}
}
}
value := p.consumeText()
s, err := UnquoteExtend(value)
if err != nil {
return fmt.Errorf("unquote string %s error %v", value, err)
}
cur.append(newText(s))
return p.parseInsideAction(cur)
}
// parseField scans a field until a terminator
func (p *Parser) parseField(cur *ListNode) error {
p.consumeText()
for p.advance() {
}
value := p.consumeText()
if value == "*" {
cur.append(newWildcard())
} else {
cur.append(newField(strings.Replace(value, "\\", "", -1)))
}
return p.parseInsideAction(cur)
}
// advance scans until next non-escaped terminator
func (p *Parser) advance() bool {
r := p.next()
if r == '\\' {
p.next()
} else if isTerminator(r) {
p.backup()
return false
}
return true
}
// isTerminator reports whether the input is at valid termination character to appear after an identifier.
func isTerminator(r rune) bool {
if isSpace(r) || isEndOfLine(r) {
return true
}
switch r {
case eof, '.', ',', '[', ']', '$', '@', '{', '}':
return true
}
return false
}
// isSpace reports whether r is a space character.
func isSpace(r rune) bool {
return r == ' ' || r == '\t'
}
// isEndOfLine reports whether r is an end-of-line character.
func isEndOfLine(r rune) bool {
return r == '\r' || r == '\n'
}
// isAlphaNumeric reports whether r is an alphabetic, digit, or underscore.
func isAlphaNumeric(r rune) bool {
return r == '_' || unicode.IsLetter(r) || unicode.IsDigit(r)
}
// isBool reports whether s is a boolean value.
func isBool(s string) bool {
return s == "true" || s == "false"
}
//UnquoteExtend is almost same as strconv.Unquote(), but it support parse single quotes as a string
func UnquoteExtend(s string) (string, error) {
n := len(s)
if n < 2 {
return "", ErrSyntax
}
quote := s[0]
if quote != s[n-1] {
return "", ErrSyntax
}
s = s[1 : n-1]
if quote != '"' && quote != '\'' {
return "", ErrSyntax
}
// Is it trivial? Avoid allocation.
if !contains(s, '\\') && !contains(s, quote) {
return s, nil
}
var runeTmp [utf8.UTFMax]byte
buf := make([]byte, 0, 3*len(s)/2) // Try to avoid more allocations.
for len(s) > 0 {
c, multibyte, ss, err := strconv.UnquoteChar(s, quote)
if err != nil {
return "", err
}
s = ss
if c < utf8.RuneSelf || !multibyte {
buf = append(buf, byte(c))
} else {
n := utf8.EncodeRune(runeTmp[:], c)
buf = append(buf, runeTmp[:n]...)
}
}
return string(buf), nil
}
func contains(s string, c byte) bool {
for i := 0; i < len(s); i++ {
if s[i] == c {
return true
}
}
return false
}