Skip to content

Commit 556d002

Browse files
committed
Add controller package
1 parent 2542c58 commit 556d002

File tree

5 files changed

+378
-0
lines changed

5 files changed

+378
-0
lines changed

pkg/controller/add_onionservice.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
Copyright 2018 Louis Taylor.
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 controller
18+
19+
import (
20+
"github.com/kragniz/tor-controller/pkg/controller/onionservice"
21+
)
22+
23+
func init() {
24+
// AddToManagerFuncs is a list of functions to create controllers and add them to a manager.
25+
AddToManagerFuncs = append(AddToManagerFuncs, onionservice.Add)
26+
}

pkg/controller/controller.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
Copyright 2018 Louis Taylor.
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 controller
18+
19+
import (
20+
"sigs.k8s.io/controller-runtime/pkg/manager"
21+
)
22+
23+
// AddToManagerFuncs is a list of functions to add all Controllers to the Manager
24+
var AddToManagerFuncs []func(manager.Manager) error
25+
26+
// AddToManager adds all Controllers to the Manager
27+
func AddToManager(m manager.Manager) error {
28+
for _, f := range AddToManagerFuncs {
29+
if err := f(m); err != nil {
30+
return err
31+
}
32+
}
33+
return nil
34+
}
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
/*
2+
Copyright 2018 Louis Taylor.
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 onionservice
18+
19+
import (
20+
"context"
21+
"log"
22+
"reflect"
23+
24+
torv1alpha1 "github.com/kragniz/tor-controller/pkg/apis/tor/v1alpha1"
25+
appsv1 "k8s.io/api/apps/v1"
26+
corev1 "k8s.io/api/core/v1"
27+
"k8s.io/apimachinery/pkg/api/errors"
28+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
29+
"k8s.io/apimachinery/pkg/runtime"
30+
"k8s.io/apimachinery/pkg/types"
31+
"sigs.k8s.io/controller-runtime/pkg/client"
32+
"sigs.k8s.io/controller-runtime/pkg/controller"
33+
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
34+
"sigs.k8s.io/controller-runtime/pkg/handler"
35+
"sigs.k8s.io/controller-runtime/pkg/manager"
36+
"sigs.k8s.io/controller-runtime/pkg/reconcile"
37+
"sigs.k8s.io/controller-runtime/pkg/source"
38+
)
39+
40+
/**
41+
* USER ACTION REQUIRED: This is a scaffold file intended for the user to modify with their own Controller
42+
* business logic. Delete these comments after modifying this file.*
43+
*/
44+
45+
// Add creates a new OnionService Controller and adds it to the Manager with default RBAC. The Manager will set fields on the Controller
46+
// and Start it when the Manager is Started.
47+
// USER ACTION REQUIRED: update cmd/manager/main.go to call this tor.Add(mgr) to install this Controller
48+
func Add(mgr manager.Manager) error {
49+
return add(mgr, newReconciler(mgr))
50+
}
51+
52+
// newReconciler returns a new reconcile.Reconciler
53+
func newReconciler(mgr manager.Manager) reconcile.Reconciler {
54+
return &ReconcileOnionService{Client: mgr.GetClient(), scheme: mgr.GetScheme()}
55+
}
56+
57+
// add adds a new Controller to mgr with r as the reconcile.Reconciler
58+
func add(mgr manager.Manager, r reconcile.Reconciler) error {
59+
// Create a new controller
60+
c, err := controller.New("onionservice-controller", mgr, controller.Options{Reconciler: r})
61+
if err != nil {
62+
return err
63+
}
64+
65+
// Watch for changes to OnionService
66+
err = c.Watch(&source.Kind{Type: &torv1alpha1.OnionService{}}, &handler.EnqueueRequestForObject{})
67+
if err != nil {
68+
return err
69+
}
70+
71+
// TODO(user): Modify this to be the types you create
72+
// Uncomment watch a Deployment created by OnionService - change this for objects you create
73+
err = c.Watch(&source.Kind{Type: &appsv1.Deployment{}}, &handler.EnqueueRequestForOwner{
74+
IsController: true,
75+
OwnerType: &torv1alpha1.OnionService{},
76+
})
77+
if err != nil {
78+
return err
79+
}
80+
81+
return nil
82+
}
83+
84+
var _ reconcile.Reconciler = &ReconcileOnionService{}
85+
86+
// ReconcileOnionService reconciles a OnionService object
87+
type ReconcileOnionService struct {
88+
client.Client
89+
scheme *runtime.Scheme
90+
}
91+
92+
// Reconcile reads that state of the cluster for a OnionService object and makes changes based on the state read
93+
// and what is in the OnionService.Spec
94+
// TODO(user): Modify this Reconcile function to implement your Controller logic. The scaffolding writes
95+
// a Deployment as an example
96+
// Automatically generate RBAC rules to allow the Controller to read and write Deployments
97+
// +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete
98+
// +kubebuilder:rbac:groups=tor.k8s.io,resources=onionservices,verbs=get;list;watch;create;update;patch;delete
99+
func (r *ReconcileOnionService) Reconcile(request reconcile.Request) (reconcile.Result, error) {
100+
// Fetch the OnionService instance
101+
instance := &torv1alpha1.OnionService{}
102+
err := r.Get(context.TODO(), request.NamespacedName, instance)
103+
if err != nil {
104+
if errors.IsNotFound(err) {
105+
// Object not found, return. Created objects are automatically garbage collected.
106+
// For additional cleanup logic use finalizers.
107+
return reconcile.Result{}, nil
108+
}
109+
// Error reading the object - requeue the request.
110+
return reconcile.Result{}, err
111+
}
112+
113+
// TODO(user): Change this to be the object type created by your controller
114+
// Define the desired Deployment object
115+
deploy := &appsv1.Deployment{
116+
ObjectMeta: metav1.ObjectMeta{
117+
Name: instance.Name + "-deployment",
118+
Namespace: instance.Namespace,
119+
},
120+
Spec: appsv1.DeploymentSpec{
121+
Selector: &metav1.LabelSelector{
122+
MatchLabels: map[string]string{"deployment": instance.Name + "-deployment"},
123+
},
124+
Template: corev1.PodTemplateSpec{
125+
ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"deployment": instance.Name + "-deployment"}},
126+
Spec: corev1.PodSpec{
127+
Containers: []corev1.Container{
128+
{
129+
Name: "nginx",
130+
Image: "nginx",
131+
},
132+
},
133+
},
134+
},
135+
},
136+
}
137+
if err := controllerutil.SetControllerReference(instance, deploy, r.scheme); err != nil {
138+
return reconcile.Result{}, err
139+
}
140+
141+
// TODO(user): Change this for the object type created by your controller
142+
// Check if the Deployment already exists
143+
found := &appsv1.Deployment{}
144+
err = r.Get(context.TODO(), types.NamespacedName{Name: deploy.Name, Namespace: deploy.Namespace}, found)
145+
if err != nil && errors.IsNotFound(err) {
146+
log.Printf("Creating Deployment %s/%s\n", deploy.Namespace, deploy.Name)
147+
err = r.Create(context.TODO(), deploy)
148+
if err != nil {
149+
return reconcile.Result{}, err
150+
}
151+
} else if err != nil {
152+
return reconcile.Result{}, err
153+
}
154+
155+
// TODO(user): Change this for the object type created by your controller
156+
// Update the found object and write the result back if there are any changes
157+
if !reflect.DeepEqual(deploy.Spec, found.Spec) {
158+
found.Spec = deploy.Spec
159+
log.Printf("Updating Deployment %s/%s\n", deploy.Namespace, deploy.Name)
160+
err = r.Update(context.TODO(), found)
161+
if err != nil {
162+
return reconcile.Result{}, err
163+
}
164+
}
165+
return reconcile.Result{}, nil
166+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
Copyright 2018 Louis Taylor.
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 onionservice
18+
19+
import (
20+
"log"
21+
"os"
22+
"path/filepath"
23+
"testing"
24+
25+
"github.com/kragniz/tor-controller/pkg/apis"
26+
"github.com/onsi/gomega"
27+
"k8s.io/client-go/kubernetes/scheme"
28+
"k8s.io/client-go/rest"
29+
"sigs.k8s.io/controller-runtime/pkg/envtest"
30+
"sigs.k8s.io/controller-runtime/pkg/manager"
31+
"sigs.k8s.io/controller-runtime/pkg/reconcile"
32+
)
33+
34+
var cfg *rest.Config
35+
36+
func TestMain(m *testing.M) {
37+
t := &envtest.Environment{
38+
CRDDirectoryPaths: []string{filepath.Join("..", "..", "..", "config", "crds")},
39+
}
40+
apis.AddToScheme(scheme.Scheme)
41+
42+
var err error
43+
if cfg, err = t.Start(); err != nil {
44+
log.Fatal(err)
45+
}
46+
47+
code := m.Run()
48+
t.Stop()
49+
os.Exit(code)
50+
}
51+
52+
// SetupTestReconcile returns a reconcile.Reconcile implementation that delegates to inner and
53+
// writes the request to requests after Reconcile is finished.
54+
func SetupTestReconcile(inner reconcile.Reconciler) (reconcile.Reconciler, chan reconcile.Request) {
55+
requests := make(chan reconcile.Request)
56+
fn := reconcile.Func(func(req reconcile.Request) (reconcile.Result, error) {
57+
result, err := inner.Reconcile(req)
58+
requests <- req
59+
return result, err
60+
})
61+
return fn, requests
62+
}
63+
64+
// StartTestManager adds recFn
65+
func StartTestManager(mgr manager.Manager, g *gomega.GomegaWithT) chan struct{} {
66+
stop := make(chan struct{})
67+
go func() {
68+
g.Expect(mgr.Start(stop)).NotTo(gomega.HaveOccurred())
69+
}()
70+
return stop
71+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*
2+
Copyright 2018 Louis Taylor.
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 onionservice
18+
19+
import (
20+
"testing"
21+
"time"
22+
23+
torv1alpha1 "github.com/kragniz/tor-controller/pkg/apis/tor/v1alpha1"
24+
"github.com/onsi/gomega"
25+
"golang.org/x/net/context"
26+
appsv1 "k8s.io/api/apps/v1"
27+
apierrors "k8s.io/apimachinery/pkg/api/errors"
28+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
29+
"k8s.io/apimachinery/pkg/types"
30+
"sigs.k8s.io/controller-runtime/pkg/client"
31+
"sigs.k8s.io/controller-runtime/pkg/manager"
32+
"sigs.k8s.io/controller-runtime/pkg/reconcile"
33+
)
34+
35+
var c client.Client
36+
37+
var expectedRequest = reconcile.Request{NamespacedName: types.NamespacedName{Name: "foo", Namespace: "default"}}
38+
var depKey = types.NamespacedName{Name: "foo-deployment", Namespace: "default"}
39+
40+
const timeout = time.Second * 5
41+
42+
func TestReconcile(t *testing.T) {
43+
g := gomega.NewGomegaWithT(t)
44+
instance := &torv1alpha1.OnionService{ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "default"}}
45+
46+
// Setup the Manager and Controller. Wrap the Controller Reconcile function so it writes each request to a
47+
// channel when it is finished.
48+
mgr, err := manager.New(cfg, manager.Options{})
49+
g.Expect(err).NotTo(gomega.HaveOccurred())
50+
c = mgr.GetClient()
51+
52+
recFn, requests := SetupTestReconcile(newReconciler(mgr))
53+
g.Expect(add(mgr, recFn)).NotTo(gomega.HaveOccurred())
54+
defer close(StartTestManager(mgr, g))
55+
56+
// Create the OnionService object and expect the Reconcile and Deployment to be created
57+
err = c.Create(context.TODO(), instance)
58+
// The instance object may not be a valid object because it might be missing some required fields.
59+
// Please modify the instance object by adding required fields and then remove the following if statement.
60+
if apierrors.IsInvalid(err) {
61+
t.Logf("failed to create object, got an invalid object error: %v", err)
62+
return
63+
}
64+
g.Expect(err).NotTo(gomega.HaveOccurred())
65+
defer c.Delete(context.TODO(), instance)
66+
g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedRequest)))
67+
68+
deploy := &appsv1.Deployment{}
69+
g.Eventually(func() error { return c.Get(context.TODO(), depKey, deploy) }, timeout).
70+
Should(gomega.Succeed())
71+
72+
// Delete the Deployment and expect Reconcile to be called for Deployment deletion
73+
g.Expect(c.Delete(context.TODO(), deploy)).NotTo(gomega.HaveOccurred())
74+
g.Eventually(requests, timeout).Should(gomega.Receive(gomega.Equal(expectedRequest)))
75+
g.Eventually(func() error { return c.Get(context.TODO(), depKey, deploy) }, timeout).
76+
Should(gomega.Succeed())
77+
78+
// Manually delete Deployment since GC isn't enabled in the test control plane
79+
g.Expect(c.Delete(context.TODO(), deploy)).To(gomega.Succeed())
80+
81+
}

0 commit comments

Comments
 (0)