Skip to content

Commit b8854f6

Browse files
committed
Add simple operator package
1 parent 8e28c33 commit b8854f6

File tree

5 files changed

+512
-0
lines changed

5 files changed

+512
-0
lines changed

pkg/operator/doc.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
Copyright 2018 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
// Package operator provides high-level porcelain wrapping the controller and manager libraries. It
18+
// has a minimalist approach to building simple application focused Controllers.
19+
//
20+
// Operator
21+
//
22+
// An Operator is a Controller that implements the operational logic for an Application. It is often used
23+
// to take off-the-shelf OSS applications, and make them Kubernetes native.
24+
//
25+
// // Create an Operator
26+
// op, err := operator.New(config.GetConfigOrDie())
27+
// if err != nil {
28+
// log.Fatal(err)
29+
// }
30+
//
31+
//
32+
// // Implement the Operator logic for a single type - e.g. ReplicaSet
33+
// rsRecFn := reconcile.Func(func(req reconcile.Request) (reconcile.Result, error) {
34+
// // Read the ReplicaSet
35+
// rs := &appsv1.ReplicaSet{}
36+
// err := op.GetClient().Get(context.TODO(), req.NamespacedName, rs)
37+
// if err != nil {
38+
// return reconcile.Result{}, err
39+
// }
40+
//
41+
// // TODO: User writes their implementation here using the op.GetClient() to read, list and write
42+
// // Kubernetes objects.
43+
// return reconcile.Result{}, nil
44+
// })
45+
//
46+
// // Handle events (e.g. create, update, delete operations) for a type and owned types - e.g. ReplicaSets and Pods
47+
// err = op.HandleType(rsRecFn, &appsv1.ReplicaSet{}, &corev1.Pod{})
48+
// if err != nil {
49+
// log.Fatal(err)
50+
// }
51+
package operator

pkg/operator/example_test.go

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
/*
2+
Copyright 2018 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package operator_test
18+
19+
import (
20+
"context"
21+
"log"
22+
23+
"fmt"
24+
25+
"github.com/kubernetes-sigs/controller-runtime/pkg/client"
26+
"github.com/kubernetes-sigs/controller-runtime/pkg/client/config"
27+
"github.com/kubernetes-sigs/controller-runtime/pkg/operator"
28+
"github.com/kubernetes-sigs/controller-runtime/pkg/reconcile"
29+
appsv1 "k8s.io/api/apps/v1"
30+
corev1 "k8s.io/api/core/v1"
31+
"k8s.io/apimachinery/pkg/labels"
32+
)
33+
34+
// This example creates a simple Operator that is configured for Deployments and ReplicaSets.
35+
//
36+
// * Create a new Operator
37+
//
38+
// * Setup ReplicaSet handling
39+
//
40+
// * Setup Deployment handling
41+
//
42+
// * Start the Operator
43+
func ExampleOperator() {
44+
// Create a new Operator
45+
op, err := operator.New(config.GetConfigOrDie())
46+
if err != nil {
47+
log.Fatal(err)
48+
}
49+
50+
// This function will be called when there is a change to a ReplicaSet or a Pod with an OwnerReference
51+
// to a ReplicaSet. It will contain the Namespace / Name of the ReplicaSet.
52+
//
53+
// Reconcile performs the following actions
54+
// * Read the ReplicaSet
55+
// * Read the Pods
56+
// * Set a Label on the ReplicaSet with the Pod count
57+
rsRecFn := reconcile.Func(func(req reconcile.Request) (reconcile.Result, error) {
58+
// Read the ReplicaSet
59+
rs := &appsv1.ReplicaSet{}
60+
err := op.GetClient().Get(context.TODO(), req.NamespacedName, rs)
61+
if err != nil {
62+
return reconcile.Result{}, err
63+
}
64+
65+
// List the Pods matching the PodTemplate Labels
66+
pods := &corev1.PodList{}
67+
err = op.GetClient().List(context.TODO(),
68+
&client.ListOptions{
69+
Namespace: req.Namespace,
70+
LabelSelector: labels.SelectorFromSet(rs.Spec.Template.Labels),
71+
}, pods)
72+
if err != nil {
73+
return reconcile.Result{}, err
74+
}
75+
76+
// Update the ReplicaSet
77+
rs.Labels["pod-count"] = fmt.Sprintf("%v", len(pods.Items))
78+
err = op.GetClient().Update(context.TODO(), rs)
79+
if err != nil {
80+
return reconcile.Result{}, err
81+
}
82+
83+
return reconcile.Result{}, nil
84+
})
85+
86+
// Watch ReplicaSets and Pods owned by ReplicaSets and call recFn with the Namespace / Name of the ReplicaSet
87+
err = op.HandleType(rsRecFn, &appsv1.ReplicaSet{}, &corev1.Pod{})
88+
if err != nil {
89+
log.Fatal(err)
90+
}
91+
92+
// This function will be called when there is a change to a Deployment or a ReplicaSet with an OwnerReference
93+
// to a Deployment. It will contain the Namespace / Name of the Deployment.
94+
depRecFn := reconcile.Func(func(req reconcile.Request) (reconcile.Result, error) {
95+
dep := &appsv1.Deployment{}
96+
err := op.GetClient().Get(context.TODO(), req.NamespacedName, dep)
97+
if err != nil {
98+
return reconcile.Result{}, err
99+
}
100+
101+
log.Printf("TODO: Implement Deployment Reconcile here.")
102+
return reconcile.Result{}, nil
103+
})
104+
105+
// Watch Deployments and ReplicaSets owned by Deployments and call recFn with the Namespace / Name of the
106+
// Deployment
107+
err = op.HandleType(depRecFn, &appsv1.Deployment{}, &appsv1.ReplicaSet{})
108+
if err != nil {
109+
log.Fatal(err)
110+
}
111+
112+
// Start the Operator
113+
log.Fatal(op.Start())
114+
}

pkg/operator/operator.go

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
/*
2+
Copyright 2018 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package operator
18+
19+
import (
20+
"fmt"
21+
"strings"
22+
23+
"github.com/kubernetes-sigs/controller-runtime/pkg/client"
24+
"github.com/kubernetes-sigs/controller-runtime/pkg/client/apiutil"
25+
"github.com/kubernetes-sigs/controller-runtime/pkg/controller"
26+
"github.com/kubernetes-sigs/controller-runtime/pkg/handler"
27+
"github.com/kubernetes-sigs/controller-runtime/pkg/manager"
28+
"github.com/kubernetes-sigs/controller-runtime/pkg/reconcile"
29+
"github.com/kubernetes-sigs/controller-runtime/pkg/runtime/signals"
30+
"github.com/kubernetes-sigs/controller-runtime/pkg/source"
31+
"k8s.io/apimachinery/pkg/runtime"
32+
"k8s.io/apimachinery/pkg/runtime/schema"
33+
"k8s.io/client-go/rest"
34+
)
35+
36+
// Operator is a very simple Controller that embeds a Manager.
37+
type Operator struct {
38+
mrg manager.Manager
39+
stopFn func() <-chan struct{}
40+
41+
// Allow deps to be mocked out for testing
42+
newController func(name string, mrg manager.Manager, options controller.Options) (controller.Controller, error)
43+
getGvk func(obj runtime.Object, scheme *runtime.Scheme) (schema.GroupVersionKind, error)
44+
}
45+
46+
// New returns a new Operator
47+
func New(config *rest.Config) (*Operator, error) {
48+
mrg, err := manager.New(config, manager.Options{})
49+
if err != nil {
50+
return nil, err
51+
}
52+
53+
o := &Operator{
54+
mrg: mrg,
55+
stopFn: signals.SetupSignalHandler,
56+
newController: controller.New,
57+
getGvk: apiutil.GVKForObject,
58+
}
59+
return o, nil
60+
}
61+
62+
// HandleType configures the Operator for a Kubernetes Resource type.
63+
//
64+
// * Watch for changes (e.g. create,update,delete operations) to objects of the Resource type
65+
//
66+
// * Watch for changes to objects created or managed by the Resource type.
67+
//
68+
// * Reconcile the object that was changed or that owns the changed objects.
69+
//
70+
// impl: the Reconcile implementation. reconcile.Reconcile is a function that may be called at anytime with the
71+
// name / Namespace of an object. When called, it will ensure that the state of the system matches what is
72+
// specified in the object at the time reconcile is called.
73+
//
74+
// reconcileObject: the type of the Kubernetes object that will be Watched and Reconciled - e.g. &v1.ReplicaSet{}
75+
//
76+
// ownedTypes: list of types of objects that may be created or managed by the reconcileObject. These
77+
// objects must have the OwnersReference set to the owning object in the ObjectMeta - e.g. &v1.Pod{}
78+
func (o *Operator) HandleType(impl reconcile.Reconcile,
79+
reconcileObject runtime.Object, ownedTypes ...runtime.Object) error {
80+
81+
gvk, err := o.getGvk(reconcileObject, o.mrg.GetScheme())
82+
if err != nil {
83+
return err
84+
}
85+
86+
// Create the controller
87+
name := fmt.Sprintf("%s-operator", strings.ToLower(gvk.Kind))
88+
c, err := o.newController(name, o.mrg, controller.Options{Reconcile: impl})
89+
if err != nil {
90+
return err
91+
}
92+
93+
// Reconcile type
94+
err = c.Watch(&source.Kind{Type: reconcileObject}, &handler.Enqueue{})
95+
if err != nil {
96+
return err
97+
}
98+
99+
// Watch the managed types
100+
for _, t := range ownedTypes {
101+
err = c.Watch(&source.Kind{Type: t}, &handler.EnqueueOwner{OwnerType: reconcileObject, IsController: true})
102+
if err != nil {
103+
return err
104+
}
105+
}
106+
107+
return err
108+
}
109+
110+
// Start starts the Operator and blocks until the program is shutdown.
111+
func (o *Operator) Start() error {
112+
return o.mrg.Start(o.stopFn())
113+
}
114+
115+
// GetClient returns a client configured with the Config
116+
func (o *Operator) GetClient() client.Client {
117+
return o.mrg.GetClient()
118+
}

pkg/operator/operator_suite_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package operator
2+
3+
import (
4+
"testing"
5+
6+
. "github.com/onsi/ginkgo"
7+
. "github.com/onsi/gomega"
8+
)
9+
10+
import (
11+
"time"
12+
13+
logf "github.com/kubernetes-sigs/controller-runtime/pkg/runtime/log"
14+
"github.com/kubernetes-sigs/controller-runtime/pkg/test"
15+
"k8s.io/client-go/rest"
16+
)
17+
18+
func TestSource(t *testing.T) {
19+
RegisterFailHandler(Fail)
20+
RunSpecsWithDefaultAndCustomReporters(t, "Operator Suite", []Reporter{test.NewlineReporter{}})
21+
}
22+
23+
var testenv *test.Environment
24+
var cfg *rest.Config
25+
26+
var _ = BeforeSuite(func(done Done) {
27+
logf.SetLogger(logf.ZapLogger(false))
28+
29+
testenv = &test.Environment{}
30+
31+
var err error
32+
cfg, err = testenv.Start()
33+
Expect(err).NotTo(HaveOccurred())
34+
35+
time.Sleep(1 * time.Second)
36+
37+
close(done)
38+
}, 60)
39+
40+
var _ = AfterSuite(func() {
41+
testenv.Stop()
42+
})

0 commit comments

Comments
 (0)