mirror of https://github.com/knative/pkg.git
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:
parent
678bb6612d
commit
4c630bb1bb
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
# The OWNERS file is used by prow to automatically merge approved PRs.
|
||||
|
||||
approvers:
|
||||
- mattmoor
|
||||
- n3wscott
|
||||
|
|
@ -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.
|
||||
|
|
@ -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...)
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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...)
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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...)
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
@ -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)))
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
94
vendor/k8s.io/client-go/third_party/forked/golang/template/exec.go
generated
vendored
Normal file
94
vendor/k8s.io/client-go/third_party/forked/golang/template/exec.go
generated
vendored
Normal 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
|
||||
}
|
||||
599
vendor/k8s.io/client-go/third_party/forked/golang/template/funcs.go
generated
vendored
Normal file
599
vendor/k8s.io/client-go/third_party/forked/golang/template/funcs.go
generated
vendored
Normal 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(""") // shorter than """
|
||||
htmlApos = []byte("'") // shorter than "'" and apos was not in HTML until HTML5
|
||||
htmlAmp = []byte("&")
|
||||
htmlLt = []byte("<")
|
||||
htmlGt = []byte(">")
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
||||
|
|
@ -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"
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
Loading…
Reference in New Issue