diff --git a/FAQ.md b/FAQ.md new file mode 100644 index 0000000000..ab926e7e3b --- /dev/null +++ b/FAQ.md @@ -0,0 +1,81 @@ +# FAQ + +### Q: How do I know which type of object a controller references? + +**A**: Each controller should only reconcile one object type. Other +affected objects should be mapped to a single type of root object, using +the `EnqueueRequestForOwner` or `EnqueueRequestsFromMapFunc` event +handlers, and potentially indicies. Then, your Reconcile method should +attempt to reconcile *all* state for that given root objects. + +### Q: How do I have different logic in my reconciler for different types of events (e.g. create, update, delete)? + +**A**: You should not. Reconcile functions should be idempotent, and +should always reconcile state by reading all the state it needs, then +writing updates. This allows your reconciler to correctly respond to +generic events, adjust to skipped or coalesced events, and easily deal +with application startup. The controller will enqueue reconcile requests +for both old and new objects if a mapping changes, but it's your +responsibility to make sure you have enough information to be able clean +up state that's no longer referenced. + +### Q: My cache might be stale if I read from a cache! How should I deal with that? + +**A**: There are several different approaches that can be taken, depending +on your situation. + +- When you can, take advantage of optimistic locking: use deterministic + names for objects you create, so that the Kubernetes API server will + warn you if the object already exists. Many controllers in Kubernetes + take this approach: the StatefulSet controller appends a specific number + to each pod that it creates, while the Deployment controller hashes the + pod template spec and appends that. + +- In the few cases when you cannot take advantage of deterministic names + (e.g. when using generateName), it may be useful in to track which + actions you took, and assume that they need to be repeated if they don't + occur after a given time (e.g. using a requeue result). This is what + the ReplicaSet controller does. + +In general, write your controller with the assumption that information +will eventually be correct, but may be slightly out of date. Make sure +that your reconcile function enforces the entire state of the world each +time it runs. If none of this works for you, you can always construct +a client that reads directly from the API server, but this is generally +considered to be a last resort, and the two approaches above should +generally cover most circumstances. + +### Q: Where's the fake client? How do I use it? + +**A**: The fake client +[exists](https://godoc.org/sigs.k8s.io/controller-runtime/pkg/client/fake), +but we generally recommend using +[envtest.Environment](https://godoc.org/sigs.k8s.io/controller-runtime/pkg/envtest#Environment) +to test against a real API server. In our experience, tests using fake +clients gradually re-implement poorly-written impressions of a real API +server, which leads to hard-to-maintain, complex test code. + +### Q: How should I write tests? Any suggestions for getting started? + +- Use the aforementioned + [envtest.Environment](https://godoc.org/sigs.k8s.io/controller-runtime/pkg/envtest#Environment) + to spin up a real API server instead of trying to mock one out. + +- Structure your tests to check that the state of the world is as you + expect it, *not* that a particular set of API calls were made, when + working with Kubernetes APIs. This will allow you to more easily + refactor and improve the internals of your controllers without changing + your tests. + +- Remember that any time you're interacting with the API server, changes + may have some delay between write time and reconcile time. + +### Q: What are these errors about no Kind being registered for a type? + +**A**: You're probably missing a fully-set-up Scheme. Schemes record the +mapping between Go types and group-version-kinds in Kubernetes. In +general, your application should have its own Scheme containing the types +from the API groups that it needs (be they Kubernetes types or your own). +See the [scheme builder +docs](https://godoc.org/sigs.k8s.io/controller-runtime/pkg/scheme) for +more information. diff --git a/README.md b/README.md index beace3b07f..5b9f276fde 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Users: - We follow [Semantic Versioning (semver)](https://semver.org) - Use releases with your dependency management to ensure that you get compatible code -- The master branch contains all the latest code, some of which may break compatibility (so "normal" `go get` is not reccomended) +- The master branch contains all the latest code, some of which may break compatibility (so "normal" `go get` is not recommended) Contributors: @@ -34,6 +34,10 @@ Contributors: * [Documentation Changes](/.github/PULL_REQUEST_TEMPLATE/docs.md) * [Test/Build/Other Changes](/.github/PULL_REQUEST_TEMPLATE/other.md) +## FAQ + +See [FAQ.md](FAQ.md) + ## Community, discussion, contribution, and support Learn how to engage with the Kubernetes community on the [community page](http://kubernetes.io/community/). diff --git a/TMP-LOGGING.md b/TMP-LOGGING.md index 2bb5dda888..78da1951de 100644 --- a/TMP-LOGGING.md +++ b/TMP-LOGGING.md @@ -74,7 +74,7 @@ logger.V(1).Info("this is particularly verbose!", "state of the world", allKubernetesObjectsEverywhere) ``` -While it's possible to use higher log levels, it's reccomended that you +While it's possible to use higher log levels, it's recommended that you stick with `V(1)` or V(0)` (which is equivalent to not specifying `V`), and then filter later based on key-value pairs or messages; different numbers tend to lose meaning easily over time, and you'll be left diff --git a/VERSIONING.md b/VERSIONING.md index 2ba11588ca..3b7831532b 100644 --- a/VERSIONING.md +++ b/VERSIONING.md @@ -10,7 +10,7 @@ - Use releases with your dependency management to ensure that you get compatible code - The master branch contains all the latest code, some of which may break - compatibility (so "normal" `go get` is not reccomended) + compatibility (so "normal" `go get` is not recommended) ### Contributors @@ -32,7 +32,7 @@ Don't be lazy, read the rest of this doc :-) ## Overview controller-runtime (and friends) follow [Semantic -Versioning](https://semver.org). I'd reccomend reading the aforementioned +Versioning](https://semver.org). I'd recommend reading the aforementioned link if you're not familiar, but essentially, for any given release X.Y.Z: - an X (*major*) release indicates a set of backwards-compatible code. diff --git a/alias.go b/alias.go index da85ceeb28..2620fab772 100644 --- a/alias.go +++ b/alias.go @@ -14,8 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -// Package controllerruntime alias' common functions and types to improve discoverability and reduce -// the number of imports for simple Controllers. package controllerruntime import ( @@ -27,8 +25,8 @@ import ( "sigs.k8s.io/controller-runtime/pkg/manager" "sigs.k8s.io/controller-runtime/pkg/reconcile" "sigs.k8s.io/controller-runtime/pkg/log" - "sigs.k8s.io/controller-runtime/pkg/runtime/scheme" - "sigs.k8s.io/controller-runtime/pkg/runtime/signals" + "sigs.k8s.io/controller-runtime/pkg/scheme" + "sigs.k8s.io/controller-runtime/pkg/manager/signals" ) // Builder builds an Application ControllerManagedBy (e.g. Operator) and returns a manager.Manager to start it. diff --git a/doc.go b/doc.go new file mode 100644 index 0000000000..5151792e27 --- /dev/null +++ b/doc.go @@ -0,0 +1,124 @@ +/* +Copyright 2018 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 controllerruntime provides tools to construct Kubernetes-style +// controllers that manipulate both Kubernetes CRDs and aggregated/built-in +// Kubernetes APIs. +// +// It defines easy helpers for the common use cases when building CRDs, built +// on top of customizable layers of abstraction. Common cases should be easy, +// and uncommon cases should be possible. In general, controller-runtime tries +// to guide users towards Kubernetes controller best-practices. +// +// Getting Started +// +// The main entrypoint for controller-runtime is this root package, which +// contains all of the common types needed to get started building controllers: +// import ( +// controllers "sigs.k8s.io/controller-runtime" +// ) +// +// The examples in this package walk through a basic controller setup. The +// kubebuilder book (https://book.kubebuilder.io) has some more in-depth +// walkthroughs. +// +// controller-runtime favors structs with sane defaults over constructors, so +// it's fairly common to see structs being used directly in controller-runtime. +// +// Organization +// +// A brief-ish walkthrough of the layout of this library can be found below. Each +// package contains more information about how to use it. +// +// Frequently asked questions about using controller-runtime and designing +// controllers can be found at +// https://github.com/kubernetes-sigs/controller-runtime/blob/master/FAQ. +// +// Managers +// +// Every controller and webhook is ultimately run by a Manager (pkg/manager). A +// manager is responsible for running controllers and webhooks, and setting up +// common dependencies (pkg/runtime/inject), like shared caches and clients, as +// well as managing leader election (pkg/leaderelection). Managers are +// generally configured to gracefully shut down controllers on pod termination +// by wiring up a signal handler (pkg/manager/signals). +// +// Controllers +// +// Controllers (pkg/controller) use events (pkg/events) to eventually trigger +// reconcile requests. They may be constructed manually, but are often +// constructed with a Builder (pkg/builder), which eases the wiring of event +// sources (pkg/source), like Kubernetes API object changes, to event handlers +// (pkg/handler), like "enqueue a reconcile request for the object owner". +// Predicates (pkg/predicate) can be used to filter which events actually +// trigger reconciles. There are pre-written utilies for the common cases, and +// interfaces and helpers for advanced cases. +// +// Reconcilers +// +// Controller logic is implemented in terms of Reconcilers (pkg/reconcile). A +// Reconciler implements a function which takes a reconcile Request containing +// the name and namespace of the object to reconcile, reconciles the object, +// and returns a Response or an error indicating whether to requeue for a +// second round of processing. +// +// Clients and Caches +// +// Reconcilers use Clients (pkg/client) to access API objects. The default +// client provided by the manager reads from a local shared cache (pkg/cache) +// and writes directly to the API server, but clients can be constructed that +// only talk to the API server, without a cache. The Cache will auto-populate +// with watched objects, as well as when other structured objects are +// requested. Caches may also have indexes, which can be created via a +// FieldIndexer (pkg/client) obtained from the manager. Indexes can used to +// quickly and easily look up all objects with certain fields set. Reconcilers +// may retrieve event recorders (pkg/recorder) to emit events using the +// manager. +// +// Schemes +// +// Clients, Caches, and many other things in Kubernetes use Schemes +// (pkg/scheme) to associate Go types to Kubernetes API Kinds +// (Group-Version-Kinds, to be specific). +// +// Webhooks +// +// Similarly, webhooks (pkg/webhook/admission) may be implemented directly, but +// are often constructed using a builder (pkg/webhook/admission/builder). They +// are run via a server (pkg/webhook) which is managed by a Manager. +// +// Logging and Metrics +// +// Logging (pkg/log) in controller-runtime is done via structured logs, using a +// log set of interfaces called logr +// (https://godoc.org/github.com/go-logr/logr). While controller-runtime +// provides easy setup for using Zap (https://go.uber.org/zap, pkg/log/zap), +// you can provide any implementation of logr as the base logger for +// controller-runtime. +// +// Metrics (pkg/metrics) provided by controller-runtime are registered into a +// controller-runtime-specific Prometheus metrics registery. The manager can +// serve these by an HTTP endpoint, and additional metrics may be registered to +// this Registry as normal. +// +// Testing +// +// You can easily build integration and unit tests for your controllers and +// webhooks using the test Environment (pkg/envtest). This will automatically +// stand up a copy of etcd and kube-apiserver, and provide the correct options +// to connect to the API server. It's designed to work well with the Ginkgo +// testing framework, but should work with any testing setup. +package controllerruntime diff --git a/example/main.go b/example/main.go index e0af176285..89b488ab35 100644 --- a/example/main.go +++ b/example/main.go @@ -31,7 +31,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/manager" logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/log/zap" - "sigs.k8s.io/controller-runtime/pkg/runtime/signals" + "sigs.k8s.io/controller-runtime/pkg/manager/signals" "sigs.k8s.io/controller-runtime/pkg/source" "sigs.k8s.io/controller-runtime/pkg/webhook" "sigs.k8s.io/controller-runtime/pkg/webhook/admission/builder" diff --git a/pkg/builder/example_test.go b/pkg/builder/example_test.go index 7b3c8e308e..25e0facbe4 100644 --- a/pkg/builder/example_test.go +++ b/pkg/builder/example_test.go @@ -30,7 +30,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client/config" "sigs.k8s.io/controller-runtime/pkg/manager" "sigs.k8s.io/controller-runtime/pkg/reconcile" - "sigs.k8s.io/controller-runtime/pkg/runtime/signals" + "sigs.k8s.io/controller-runtime/pkg/manager/signals" ) // This example creates a simple application ControllerManagedBy that is configured for ReplicaSets and Pods. diff --git a/pkg/cache/cache.go b/pkg/cache/cache.go index c6f3e6971f..d399bc6153 100644 --- a/pkg/cache/cache.go +++ b/pkg/cache/cache.go @@ -34,17 +34,20 @@ import ( var log = logf.RuntimeLog.WithName("object-cache") -// Cache implements CacheReader by reading objects from a cache populated by InformersMap +// Cache knows how to load Kubernetes objects, fetch informers to request +// to receive events for Kubernetes objects (at a low-level), +// and add indicies to fields on the objects stored in the cache. type Cache interface { - // Cache implements the client CacheReader + // Cache acts as a client to objects stored in the cache. client.Reader - // Cache implements InformersMap + // Cache loads informers and adds field indicies. Informers } -// Informers knows how to create or fetch informers for different group-version-kinds. -// It's safe to call GetInformer from multiple threads. +// Informers knows how to create or fetch informers for different +// group-version-kinds, and add indicies to those informers. It's safe to call +// GetInformer from multiple threads. type Informers interface { // GetInformer fetches or constructs an informer for the given object that corresponds to a single // API kind and resource. @@ -61,15 +64,11 @@ type Informers interface { // WaitForCacheSync waits for all the caches to sync. Returns false if it could not sync a cache. WaitForCacheSync(stop <-chan struct{}) bool - // IndexField adds an index with the given field name on the given object type - // by using the given function to extract the value for that field. If you want - // compatibility with the Kubernetes API server, only return one key, and only use - // fields that the API server supports. Otherwise, you can return multiple keys, - // and "equality" in the field selector means that at least one key matches the value. - IndexField(obj runtime.Object, field string, extractValue client.IndexerFunc) error + // Informers knows how to add indicies to the caches (informers) that it manages. + client.FieldIndexer } -// Options are the optional arguments for creating a new InformersMap object +// Options are the optional arguments for creating a new set of Informers. type Options struct { // Scheme is the scheme to use for mapping objects to GroupVersionKinds Scheme *runtime.Scheme @@ -87,7 +86,7 @@ type Options struct { var defaultResyncTime = 10 * time.Hour -// New initializes and returns a new Cache +// New initializes and returns a new Cache. func New(config *rest.Config, opts Options) (Cache, error) { opts, err := defaultOpts(config, opts) if err != nil { diff --git a/pkg/cache/doc.go b/pkg/cache/doc.go new file mode 100644 index 0000000000..e1742ac0f3 --- /dev/null +++ b/pkg/cache/doc.go @@ -0,0 +1,19 @@ +/* +Copyright 2019 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 cache provides object caches that act as caching client.Reader +// instances and help drive Kubernetes-object-based event handlers. +package cache diff --git a/pkg/client/apiutil/apimachinery.go b/pkg/client/apiutil/apimachinery.go index 614d454f1f..a1fd04a7a4 100644 --- a/pkg/client/apiutil/apimachinery.go +++ b/pkg/client/apiutil/apimachinery.go @@ -14,6 +14,9 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package apiutil contains utilities for working with raw Kubernetes +// API machinery, such as creating RESTMappers and raw REST clients, +// and extracting the GVK of an object. package apiutil import ( diff --git a/pkg/client/client.go b/pkg/client/client.go index 0650da0f35..adf46495b6 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -41,6 +41,15 @@ type Options struct { } // New returns a new Client using the provided config and Options. +// The returned client reads *and* writes directly from the server +// (it doesn't use object caches). It understands how to work with +// normal types (both custom resources and aggregated/built-in resources), +// as well as unstructured types. +// +// In the case of normal types, the scheme will be used to look up the +// corresponding group, version, and kind for the given type. In the +// case of unstrctured types, the group, version, and kind will be extracted +// from the corresponding fields on the object. func New(config *rest.Config, options Options) (Client, error) { if config == nil { return nil, fmt.Errorf("must provide non-nil rest.Config to client.New") diff --git a/pkg/client/config/doc.go b/pkg/client/config/doc.go index 3918958d27..796c9cf590 100644 --- a/pkg/client/config/doc.go +++ b/pkg/client/config/doc.go @@ -14,5 +14,5 @@ See the License for the specific language governing permissions and limitations under the License. */ -// Package config contains libraries for initializing rest configs for talking to the Kubernetes API +// Package config contains libraries for initializing REST configs for talking to the Kubernetes API package config diff --git a/pkg/client/doc.go b/pkg/client/doc.go new file mode 100644 index 0000000000..6c13af211f --- /dev/null +++ b/pkg/client/doc.go @@ -0,0 +1,49 @@ +/* +Copyright 2018 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 client contains functionality for interacting with Kubernetes API +// servers. +// +// Clients +// +// Clients are split into two interfaces -- Readers and Writers. Readers +// get and list, while writers create, update, and delete. +// +// The New function can be used to create a new client that talks directly +// to the API server. +// +// A common pattern in Kubernetes to read from a cache and write to the API +// server. This pattern is covered by the DelegatingClient type, which can +// be used to have a client whose Reader is different from the Writer. +// +// Options +// +// Many client operations in Kubernetes support options. These options are +// represented as variadic arguments at the end of a given method call. +// For instance, to use a label selector on list, you can call +// err := someReader.List(context.Background(), &podList, client.MatchingLabels(someLabelMap)) +// +// Indexing +// +// Indexes may be added to caches using a FieldIndexer. This allows you to easily +// and efficiently look up objects with certain properties. You can then make +// use of the index by specifying a field selector on calls to List on the Reader +// corresponding to the given Cache. +// +// For instance, a Secret controller might have an index on the +// `.spec.volumes.secret.secretName` field in Pod objects, so that it could +// easily look up all pods that reference a given secret. +package client diff --git a/pkg/client/example_test.go b/pkg/client/example_test.go index 8059812bbf..c0ab71301f 100644 --- a/pkg/client/example_test.go +++ b/pkg/client/example_test.go @@ -25,12 +25,14 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/config" ) var ( c client.Client + someIndexer client.FieldIndexer ) func ExampleNew() { @@ -196,3 +198,24 @@ func ExampleClient_delete() { }) _ = c.Delete(context.Background(), u) } + +// This example shows how to set up and consume a field selector over a pod's volumes' secretName field. +func ExampleFieldIndexer_secretName() { + // someIndexer is a FieldIndexer over a Cache + _ = someIndexer.IndexField(&corev1.Pod{}, "spec.volumes.secret.secretName", func(o runtime.Object) []string { + var res []string + for _, vol := range o.(*corev1.Pod).Spec.Volumes { + if vol.Secret == nil { + continue + } + // just return the raw field value -- the indexer will take care of dealing with namespaces for us + res = append(res, vol.Secret.SecretName) + } + return res + }) + + // elsewhere (e.g. in your reconciler) + mySecretName := "someSecret" // derived from the reconcile.Request, for instance + var podsWithSecrets corev1.PodList + _ = c.List(context.Background(), &podsWithSecrets, client.MatchingField("spec.volumes.secret.secretName", mySecretName)) +} diff --git a/pkg/client/fake/client.go b/pkg/client/fake/client.go index e0cda6efc8..f59b31a787 100644 --- a/pkg/client/fake/client.go +++ b/pkg/client/fake/client.go @@ -48,6 +48,8 @@ var _ client.Client = &fakeClient{} // NewFakeClient creates a new fake client for testing. // You can choose to initialize it with a slice of runtime.Object. +// Deprecated: use NewFakeClientWithScheme. You should always be +// passing an explicit Scheme. func NewFakeClient(initObjs ...runtime.Object) client.Client { return NewFakeClientWithScheme(scheme.Scheme, initObjs...) } diff --git a/pkg/client/fake/doc.go b/pkg/client/fake/doc.go index a45dbbef16..3b9099fa0a 100644 --- a/pkg/client/fake/doc.go +++ b/pkg/client/fake/doc.go @@ -23,5 +23,8 @@ You can create a fake client with optional objects. client := NewFakeClient(initObjs...) // initObjs is a slice of runtime.Object You can invoke the methods defined in the Client interface. + +When it doubt, it's almost always better not to use this package and instead use +envtest.Environment with a real client and API server. */ package fake diff --git a/pkg/client/interfaces.go b/pkg/client/interfaces.go index 087b5eb43b..c34f2b11d9 100644 --- a/pkg/client/interfaces.go +++ b/pkg/client/interfaces.go @@ -100,6 +100,8 @@ type FieldIndexer interface { // compatibility with the Kubernetes API server, only return one key, and only use // fields that the API server supports. Otherwise, you can return multiple keys, // and "equality" in the field selector means that at least one key matches the value. + // The FieldIndexer will automatically take care of indexing over namespace + // and supporting efficient all-namespace queries. IndexField(obj runtime.Object, field string, extractValue IndexerFunc) error } diff --git a/pkg/client/split.go b/pkg/client/split.go index 91519196a8..db7f16a717 100644 --- a/pkg/client/split.go +++ b/pkg/client/split.go @@ -23,18 +23,20 @@ import ( "k8s.io/apimachinery/pkg/runtime" ) -// DelegatingClient forms an interface Client by composing separate -// reader, writer and statusclient interfaces. This way, you can have an Client that -// reads from a cache and writes to the API server. +// DelegatingClient forms a Client by composing separate reader, writer and +// statusclient interfaces. This way, you can have an Client that reads from a +// cache and writes to the API server. type DelegatingClient struct { Reader Writer StatusClient } -// DelegatingReader forms a interface Reader that will cause Get and List -// requests for unstructured types to use the ClientReader while -// requests for any other type of object with use the CacheReader. +// DelegatingReader forms a Reader that will cause Get and List requests for +// unstructured types to use the ClientReader while requests for any other type +// of object with use the CacheReader. This avoids accidentally caching the +// entire cluster in the common case of loading arbitrary unstructured objects +// (e.g. from OwnerReferences). type DelegatingReader struct { CacheReader Reader ClientReader Reader diff --git a/pkg/controller/controller.go b/pkg/controller/controller.go index 64a27a7a22..0ad828c477 100644 --- a/pkg/controller/controller.go +++ b/pkg/controller/controller.go @@ -45,14 +45,16 @@ type Controller interface { // Reconciler is called to Reconciler an object by Namespace/Name reconcile.Reconciler - // Watch takes events provided by a Source and uses the EventHandler to enqueue reconcile.Requests in - // response to the events. + // Watch takes events provided by a Source and uses the EventHandler to + // enqueue reconcile.Requests in response to the events. // - // Watch may be provided one or more Predicates to filter events before they are given to the EventHandler. - // Events will be passed to the EventHandler iff all provided Predicates evaluate to true. + // Watch may be provided one or more Predicates to filter events before + // they are given to the EventHandler. Events will be passed to the + // EventHandler iff all provided Predicates evaluate to true. Watch(src source.Source, eventhandler handler.EventHandler, predicates ...predicate.Predicate) error - // Start starts the controller. Start blocks until stop is closed or a controller has an error starting. + // Start starts the controller. Start blocks until stop is closed or a + // controller has an error starting. Start(stop <-chan struct{}) error } @@ -83,7 +85,7 @@ func New(name string, mgr manager.Manager, options Options) (Controller, error) Config: mgr.GetConfig(), Scheme: mgr.GetScheme(), Client: mgr.GetClient(), - Recorder: mgr.GetRecorder(name), + Recorder: mgr.GetEventRecorderFor(name), Queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), name), MaxConcurrentReconciles: options.MaxConcurrentReconciles, Name: name, diff --git a/pkg/controller/controllertest/doc.go b/pkg/controller/controllertest/doc.go index 9e5d633b13..91c5a3e35e 100644 --- a/pkg/controller/controllertest/doc.go +++ b/pkg/controller/controllertest/doc.go @@ -15,4 +15,6 @@ limitations under the License. */ // Package controllertest contains fake informers for testing controllers +// When in doubt, it's almost always better to test against a real API server +// using envtest.Environment. package controllertest diff --git a/pkg/controller/example_test.go b/pkg/controller/example_test.go index 4c2a0de947..6ad7aaa4fa 100644 --- a/pkg/controller/example_test.go +++ b/pkg/controller/example_test.go @@ -27,7 +27,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/manager" "sigs.k8s.io/controller-runtime/pkg/reconcile" logf "sigs.k8s.io/controller-runtime/pkg/log" - "sigs.k8s.io/controller-runtime/pkg/runtime/signals" + "sigs.k8s.io/controller-runtime/pkg/manager/signals" "sigs.k8s.io/controller-runtime/pkg/source" ) diff --git a/pkg/envtest/doc.go b/pkg/envtest/doc.go index f558ac0d92..412e794cc8 100644 --- a/pkg/envtest/doc.go +++ b/pkg/envtest/doc.go @@ -15,4 +15,12 @@ limitations under the License. */ // Package envtest provides libraries for integration testing by starting a local control plane +// +// Control plane binaries (etcd and kube-apiserver) are loaded by default from +// /usr/local/kubebuilder/bin. This can be overridden by setting the +// KUBEBUILDER_ASSETS environment variable, or by directly creating a +// ControlPlane for the Environment to use. +// +// Environment can also be configured to work with an existing cluster, and +// simply load CRDs and provide client configuration. package envtest diff --git a/pkg/envtest/printer/ginkgo.go b/pkg/envtest/printer/ginkgo.go index 7487172f13..1435a1a435 100644 --- a/pkg/envtest/printer/ginkgo.go +++ b/pkg/envtest/printer/ginkgo.go @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package printer contains setup for a friendlier Ginkgo printer that's easier +// to parse by test automation. package printer import ( diff --git a/pkg/envtest/server.go b/pkg/envtest/server.go index e85e9908d1..8834be741d 100644 --- a/pkg/envtest/server.go +++ b/pkg/envtest/server.go @@ -73,7 +73,9 @@ type Environment struct { // ControlPlane is the ControlPlane including the apiserver and etcd ControlPlane integration.ControlPlane - // Config can be used to talk to the apiserver + // Config can be used to talk to the apiserver. It's automatically + // populated if not set using the standard controller-runtime config + // loading. Config *rest.Config // CRDs is a list of CRDs to install diff --git a/pkg/event/doc.go b/pkg/event/doc.go index ec9fe1d0c1..adba3bbc16 100644 --- a/pkg/event/doc.go +++ b/pkg/event/doc.go @@ -18,8 +18,11 @@ limitations under the License. Package event contains the definitions for the Event types produced by source.Sources and transformed into reconcile.Requests by handler.EventHandler. -The details of how events are produced and transformed into reconcile.Requests are not something most -users should need to use or understand. Instead of working with Events, users should use -source.Sources and handler.EventHandlers with Controller.Watch. +You should rarely need to work with these directly -- instead, use Controller.Watch with +source.Sources and handler.EventHandlers. + +Events generally contain both a full runtime.Object that caused the event, as well +as a direct handle to that object's metadata. This saves a lot of typecasting in +code that works with Events. */ package event diff --git a/pkg/handler/doc.go b/pkg/handler/doc.go index 3167e3526f..87df91fcd6 100644 --- a/pkg/handler/doc.go +++ b/pkg/handler/doc.go @@ -19,6 +19,8 @@ Package handler defines EventHandlers that enqueue reconcile.Requests in respons observed from Watching Kubernetes APIs. Users should provide a source.Source and handler.EventHandler to Controller.Watch in order to generate and enqueue reconcile.Request work items. +Generally, following premade event handlers should be sufficient for most use cases: + EventHandlers EnqueueRequestForObject - Enqueues a reconcile.Request containing the Name and Namespace of the object in the Event. This will diff --git a/pkg/internal/recorder/recorder_integration_test.go b/pkg/internal/recorder/recorder_integration_test.go index 6b98088637..f3134b0cb6 100644 --- a/pkg/internal/recorder/recorder_integration_test.go +++ b/pkg/internal/recorder/recorder_integration_test.go @@ -52,7 +52,7 @@ var _ = Describe("recorder", func() { Expect(err).NotTo(HaveOccurred()) By("Creating the Controller") - recorder := cm.GetRecorder("test-recorder") + recorder := cm.GetEventRecorderFor("test-recorder") instance, err := controller.New("foo-controller", cm, controller.Options{ Reconciler: reconcile.Func( func(request reconcile.Request) (reconcile.Result, error) { diff --git a/pkg/leaderelection/doc.go b/pkg/leaderelection/doc.go index 3f0f52a9bd..24654faa3c 100644 --- a/pkg/leaderelection/doc.go +++ b/pkg/leaderelection/doc.go @@ -15,6 +15,10 @@ limitations under the License. */ /* -Package leaderelection contains a constructors for a leader election resource lock +Package leaderelection contains a constructors for a leader election resource lock. +This is used to ensure that multiple copies of a controller manager can be run with +only one active set of controllers, for active-passive HA. + +It uses built-in Kubernetes leader election APIs. */ package leaderelection diff --git a/pkg/log/log.go b/pkg/log/log.go index b541231967..128e6542ea 100644 --- a/pkg/log/log.go +++ b/pkg/log/log.go @@ -16,7 +16,21 @@ limitations under the License. // Package log contains utilities for fetching a new logger // when one is not already available. -// Deprecated: use pkg/log +// +// The Log Handle +// +// This package contains a root logr.Logger Log. It may be used to +// get a handle to whatever the root logging implementation is. By +// default, no implementation exists, and the handle returns "promises" +// to loggers. When the implementation is set using SetLogger, these +// "promises" will be converted over to real loggers. +// +// Logr +// +// All logging in controller-runtime is structured, using a set of interfaces +// defined by a package called logr +// (https://godoc.org/github.com/go-logr/logr). The sub-package zap provides +// helpers for setting up logr backed by Zap (go.uber.org/zap). package log import ( diff --git a/pkg/manager/example_test.go b/pkg/manager/example_test.go index f718b38f8e..44a2e511bd 100644 --- a/pkg/manager/example_test.go +++ b/pkg/manager/example_test.go @@ -22,7 +22,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client/config" "sigs.k8s.io/controller-runtime/pkg/manager" logf "sigs.k8s.io/controller-runtime/pkg/log" - "sigs.k8s.io/controller-runtime/pkg/runtime/signals" + "sigs.k8s.io/controller-runtime/pkg/manager/signals" ) var ( diff --git a/pkg/manager/internal.go b/pkg/manager/internal.go index ac235adecd..716702c5ca 100644 --- a/pkg/manager/internal.go +++ b/pkg/manager/internal.go @@ -169,7 +169,7 @@ func (cm *controllerManager) GetCache() cache.Cache { return cm.cache } -func (cm *controllerManager) GetRecorder(name string) record.EventRecorder { +func (cm *controllerManager) GetEventRecorderFor(name string) record.EventRecorder { return cm.recorderProvider.GetEventRecorderFor(name) } diff --git a/pkg/manager/manager.go b/pkg/manager/manager.go index 8d346df6c1..10c7fa136a 100644 --- a/pkg/manager/manager.go +++ b/pkg/manager/manager.go @@ -74,8 +74,8 @@ type Manager interface { // GetCache returns a cache.Cache GetCache() cache.Cache - // GetRecorder returns a new EventRecorder for the provided name - GetRecorder(name string) record.EventRecorder + // GetEventRecorderFor returns a new EventRecorder for the provided name + GetEventRecorderFor(name string) record.EventRecorder // GetRESTMapper returns a RESTMapper GetRESTMapper() meta.RESTMapper @@ -84,7 +84,8 @@ type Manager interface { // Options are the arguments for creating a new Manager type Options struct { // Scheme is the scheme used to resolve runtime.Objects to GroupVersionKinds / Resources - // Defaults to the kubernetes/client-go scheme.Scheme + // Defaults to the kubernetes/client-go scheme.Scheme, but it's almost always better + // idea to pass your own scheme in. See the documentation in pkg/scheme for more information. Scheme *runtime.Scheme // MapperProvider provides the rest mapper used to map go types to Kubernetes APIs @@ -108,10 +109,12 @@ type Options struct { // will use for holding the leader lock. LeaderElectionID string - // Namespace if specified restricts the manager's cache to watch objects in the desired namespace - // Defaults to all namespaces - // Note: If a namespace is specified then controllers can still Watch for a cluster-scoped resource e.g Node - // For namespaced resources the cache will only hold objects from the desired namespace. + // Namespace if specified restricts the manager's cache to watch objects in + // the desired namespace Defaults to all namespaces + // + // Note: If a namespace is specified, controllers can still Watch for a + // cluster-scoped resource (e.g Node). For namespaced resources the cache + // will only hold objects from the desired namespace. Namespace string // MetricsBindAddress is the TCP address that the controller should bind to @@ -143,13 +146,18 @@ type NewCacheFunc func(config *rest.Config, opts cache.Options) (cache.Cache, er type NewClientFunc func(cache cache.Cache, config *rest.Config, options client.Options) (client.Client, error) // Runnable allows a component to be started. +// It's very important that Start blocks until +// it's done running. type Runnable interface { - // Start starts running the component. The component will stop running when the channel is closed. - // Start blocks until the channel is closed or an error occurs. + // Start starts running the component. The component will stop running + // when the channel is closed. Start blocks until the channel is closed or + // an error occurs. Start(<-chan struct{}) error } -// RunnableFunc implements Runnable +// RunnableFunc implements Runnable using a function. +// It's very important that the given function block +// until it's done running. type RunnableFunc func(<-chan struct{}) error // Start implements Runnable diff --git a/pkg/manager/manager_test.go b/pkg/manager/manager_test.go index a5a63e3e4f..c6a949508e 100644 --- a/pkg/manager/manager_test.go +++ b/pkg/manager/manager_test.go @@ -604,7 +604,7 @@ var _ = Describe("manger.Manager", func() { It("should provide a function to get the EventRecorder", func() { m, err := New(cfg, Options{}) Expect(err).NotTo(HaveOccurred()) - Expect(m.GetRecorder("test")).NotTo(BeNil()) + Expect(m.GetEventRecorderFor("test")).NotTo(BeNil()) }) }) diff --git a/pkg/runtime/signals/doc.go b/pkg/manager/signals/doc.go similarity index 78% rename from pkg/runtime/signals/doc.go rename to pkg/manager/signals/doc.go index 749321e7a1..737cc7eff2 100644 --- a/pkg/runtime/signals/doc.go +++ b/pkg/manager/signals/doc.go @@ -14,5 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// Package signals contains libraries for handling signals to shutdown the system. +// Package signals contains libraries for handling signals to gracefully +// shutdown the manager in combination with Kubernetes pod graceful termination +// policy. package signals diff --git a/pkg/manager/signals/signal.go b/pkg/manager/signals/signal.go new file mode 100644 index 0000000000..08eaef7b42 --- /dev/null +++ b/pkg/manager/signals/signal.go @@ -0,0 +1,43 @@ +/* +Copyright 2017 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 signals + +import ( + "os" + "os/signal" +) + +var onlyOneSignalHandler = make(chan struct{}) + +// SetupSignalHandler registers for SIGTERM and SIGINT. A stop channel is returned +// which is closed on one of these signals. If a second signal is caught, the program +// is terminated with exit code 1. +func SetupSignalHandler() (stopCh <-chan struct{}) { + close(onlyOneSignalHandler) // panics when called twice + + stop := make(chan struct{}) + c := make(chan os.Signal, 2) + signal.Notify(c, shutdownSignals...) + go func() { + <-c + close(stop) + <-c + os.Exit(1) // second signal. Exit directly. + }() + + return stop +} diff --git a/pkg/runtime/signals/signal_posix.go b/pkg/manager/signals/signal_posix.go similarity index 100% rename from pkg/runtime/signals/signal_posix.go rename to pkg/manager/signals/signal_posix.go diff --git a/pkg/runtime/signals/signal_test.go b/pkg/manager/signals/signal_test.go similarity index 100% rename from pkg/runtime/signals/signal_test.go rename to pkg/manager/signals/signal_test.go diff --git a/pkg/runtime/signals/signal_windows.go b/pkg/manager/signals/signal_windows.go similarity index 100% rename from pkg/runtime/signals/signal_windows.go rename to pkg/manager/signals/signal_windows.go diff --git a/pkg/runtime/signals/signals_suite_test.go b/pkg/manager/signals/signals_suite_test.go similarity index 100% rename from pkg/runtime/signals/signals_suite_test.go rename to pkg/manager/signals/signals_suite_test.go diff --git a/pkg/recorder/example_test.go b/pkg/recorder/example_test.go new file mode 100644 index 0000000000..3066c1c269 --- /dev/null +++ b/pkg/recorder/example_test.go @@ -0,0 +1,60 @@ +/* +Copyright 2018 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 recorder_test + +import ( + "time" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "sigs.k8s.io/controller-runtime/pkg/recorder" +) + +var ( + recorderProvider recorder.Provider + somePod *corev1.Pod // the object you're reconciling, for example +) + +func Example_event() { + // recorderProvider is a recorder.Provider + recorder := recorderProvider.GetEventRecorderFor("my-controller") + + // emit an event with a fixed message + recorder.Event(somePod, corev1.EventTypeWarning, + "WrongTrousers", "It's the wrong trousers, Gromit!") +} + +func Example_eventf() { + // recorderProvider is a recorder.Provider + recorder := recorderProvider.GetEventRecorderFor("my-controller") + + // emit an event with a variable message + mildCheese := "Wensleydale" + recorder.Eventf(somePod, corev1.EventTypeNormal, + "DislikesCheese", "Not even %s?", mildCheese) +} + +func Example_pastEventf() { + // recorderProvider is a recorder.Provider + recorder := recorderProvider.GetEventRecorderFor("my-controller") + + // emit a backdated event (potentially with variable message) + recorder.PastEventf(somePod, metav1.Time{Time: time.Now().Add(-5*time.Minute)}, + corev1.EventTypeWarning, "ForgottenCrackers", + "Crackers, Gromit! We forgot the crackers!") +} diff --git a/pkg/recorder/recorder.go b/pkg/recorder/recorder.go index 2bdcb199f7..f093f0a726 100644 --- a/pkg/recorder/recorder.go +++ b/pkg/recorder/recorder.go @@ -14,6 +14,10 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package recorder defines interfaces for working with Kubernetes event recorders. +// +// You can use these to emit Kubernetes events associated with a particular Kubernetes +// object. package recorder import ( diff --git a/pkg/runtime/doc.go b/pkg/runtime/doc.go new file mode 100644 index 0000000000..34101b3fa4 --- /dev/null +++ b/pkg/runtime/doc.go @@ -0,0 +1,21 @@ +/* +Copyright 2018 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 runtime contains not-quite-internal mechanisms for +// controller-runtime, plus some deprecated exports of functionality +// moved elsewhere. Most users should not need to import anything in +// pkg/runtime. +package runtime diff --git a/pkg/runtime/log/log.go b/pkg/runtime/log/log.go index 4ad91f456f..c5ec5a2393 100644 --- a/pkg/runtime/log/log.go +++ b/pkg/runtime/log/log.go @@ -14,8 +14,9 @@ See the License for the specific language governing permissions and limitations under the License. */ -// Package log contains utilities for fetching a new logger -// when one is not already available. +// Package log contains (deprecated) utilities for fetching a new logger when +// one is not already available. +// // Deprecated: use pkg/log package log diff --git a/pkg/runtime/scheme/scheme.go b/pkg/runtime/scheme/scheme.go index 79868214e3..a1d00141b0 100644 --- a/pkg/runtime/scheme/scheme.go +++ b/pkg/runtime/scheme/scheme.go @@ -1,5 +1,5 @@ /* -Copyright 2018 The Kubernetes Authors. +Copyright 2019 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. @@ -14,43 +14,10 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package scheme contains utilities for gradually building Schemes, +// which contain information associating Go types with Kubernetes +// groups, versions, and kinds. +// +// Deprecated: use pkg/scheme instead. package scheme -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" -) - -// Builder builds a new Scheme for mapping go types to Kubernetes GroupVersionKinds. -type Builder struct { - GroupVersion schema.GroupVersion - runtime.SchemeBuilder -} - -// Register adds one or objects to the SchemeBuilder so they can be added to a Scheme. Register mutates bld. -func (bld *Builder) Register(object ...runtime.Object) *Builder { - bld.SchemeBuilder.Register(func(scheme *runtime.Scheme) error { - scheme.AddKnownTypes(bld.GroupVersion, object...) - metav1.AddToGroupVersion(scheme, bld.GroupVersion) - return nil - }) - return bld -} - -// RegisterAll registers all types from the Builder argument. RegisterAll mutates bld. -func (bld *Builder) RegisterAll(b *Builder) *Builder { - bld.SchemeBuilder = append(bld.SchemeBuilder, b.SchemeBuilder...) - return bld -} - -// AddToScheme adds all registered types to s. -func (bld *Builder) AddToScheme(s *runtime.Scheme) error { - return bld.SchemeBuilder.AddToScheme(s) -} - -// Build returns a new Scheme containing the registered types. -func (bld *Builder) Build() (*runtime.Scheme, error) { - s := runtime.NewScheme() - return s, bld.AddToScheme(s) -} diff --git a/pkg/runtime/signals/signal.go b/pkg/runtime/signals/signal.go index 6bddfddb4f..2b25fe73dd 100644 --- a/pkg/runtime/signals/signal.go +++ b/pkg/runtime/signals/signal.go @@ -1,5 +1,5 @@ /* -Copyright 2017 The Kubernetes Authors. +Copyright 2019 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. @@ -14,30 +14,20 @@ See the License for the specific language governing permissions and limitations under the License. */ +// Package signals contains libraries for handling signals to gracefully +// shutdown the manager in combination with Kubernetes pod graceful termination +// policy. +// +// Deprecated: use pkg/manager/signals instead. package signals import ( - "os" - "os/signal" + "sigs.k8s.io/controller-runtime/pkg/manager/signals" ) -var onlyOneSignalHandler = make(chan struct{}) - -// SetupSignalHandler registered for SIGTERM and SIGINT. A stop channel is returned -// which is closed on one of these signals. If a second signal is caught, the program -// is terminated with exit code 1. -func SetupSignalHandler() (stopCh <-chan struct{}) { - close(onlyOneSignalHandler) // panics when called twice - - stop := make(chan struct{}) - c := make(chan os.Signal, 2) - signal.Notify(c, shutdownSignals...) - go func() { - <-c - close(stop) - <-c - os.Exit(1) // second signal. Exit directly. - }() - - return stop -} +var ( + // SetupSignalHandler registers for SIGTERM and SIGINT. A stop channel is returned + // which is closed on one of these signals. If a second signal is caught, the program + // is terminated with exit code 1. + SetupSignalHandler = signals.SetupSignalHandler +) diff --git a/pkg/scheme/scheme.go b/pkg/scheme/scheme.go new file mode 100644 index 0000000000..cd60319674 --- /dev/null +++ b/pkg/scheme/scheme.go @@ -0,0 +1,94 @@ +/* +Copyright 2018 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 scheme contains utilities for gradually building Schemes, +// which contain information associating Go types with Kubernetes +// groups, versions, and kinds. +// +// Each API group should define a utility function +// called AddToScheme for adding its types to a Scheme: +// +// // in package myapigroupv1... +// var ( +// SchemeGroupVersion = schema.GroupVersion{Group: "my.api.group", Version: "v1"} +// SchemeBuilder = &scheme.Builder{GroupVersion: SchemeGroupVersion} +// AddToScheme = SchemeBuilder.AddToScheme +// ) +// +// func init() { +// SchemeBuilder.Register(&MyType{}, &MyTypeList) +// } +// var ( +// scheme *runtime.Scheme = runtime.NewScheme() +// ) +// +// This also true of the built-in Kubernetes types. Then, in the entrypoint for +// your manager, assemble the scheme containing exactly the types you need. +// For instance, if our controller needs types from the core/v1 API group (e.g. Pod), +// plus types from my.api.group/v1: +// +// func init() { +// myapigroupv1.AddToScheme(scheme) +// kubernetesscheme.AddToScheme(scheme) +// } +// +// func main() { +// mgr := controllers.NewManager(controllers.GetConfigOrDie(), manager.Options{ +// Scheme: scheme, +// }) +// // ... +// } +// +package scheme + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// Builder builds a new Scheme for mapping go types to Kubernetes GroupVersionKinds. +type Builder struct { + GroupVersion schema.GroupVersion + runtime.SchemeBuilder +} + +// Register adds one or objects to the SchemeBuilder so they can be added to a Scheme. Register mutates bld. +func (bld *Builder) Register(object ...runtime.Object) *Builder { + bld.SchemeBuilder.Register(func(scheme *runtime.Scheme) error { + scheme.AddKnownTypes(bld.GroupVersion, object...) + metav1.AddToGroupVersion(scheme, bld.GroupVersion) + return nil + }) + return bld +} + +// RegisterAll registers all types from the Builder argument. RegisterAll mutates bld. +func (bld *Builder) RegisterAll(b *Builder) *Builder { + bld.SchemeBuilder = append(bld.SchemeBuilder, b.SchemeBuilder...) + return bld +} + +// AddToScheme adds all registered types to s. +func (bld *Builder) AddToScheme(s *runtime.Scheme) error { + return bld.SchemeBuilder.AddToScheme(s) +} + +// Build returns a new Scheme containing the registered types. +func (bld *Builder) Build() (*runtime.Scheme, error) { + s := runtime.NewScheme() + return s, bld.AddToScheme(s) +} diff --git a/pkg/runtime/scheme/scheme_suite_test.go b/pkg/scheme/scheme_suite_test.go similarity index 100% rename from pkg/runtime/scheme/scheme_suite_test.go rename to pkg/scheme/scheme_suite_test.go diff --git a/pkg/runtime/scheme/scheme_test.go b/pkg/scheme/scheme_test.go similarity index 98% rename from pkg/runtime/scheme/scheme_test.go rename to pkg/scheme/scheme_test.go index 35892a4e3b..cb7612203f 100644 --- a/pkg/runtime/scheme/scheme_test.go +++ b/pkg/scheme/scheme_test.go @@ -24,7 +24,7 @@ import ( appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime/schema" - "sigs.k8s.io/controller-runtime/pkg/runtime/scheme" + "sigs.k8s.io/controller-runtime/pkg/scheme" ) var _ = Describe("Scheme", func() { diff --git a/pkg/source/doc.go b/pkg/source/doc.go index 2e2ef2b36c..31935c83c1 100644 --- a/pkg/source/doc.go +++ b/pkg/source/doc.go @@ -15,7 +15,7 @@ limitations under the License. */ /* -Package source provides event streams provided to Controllers through Controller.Watch. Events are +Package source provides event streams to hook up to Controllers with Controller.Watch. Events are used with handler.EventHandlers to enqueue reconcile.Requests and trigger Reconciles for Kubernetes objects. */