Skip to content

Commit 6410d30

Browse files
committed
add buildstrategy controller
1 parent 2295a22 commit 6410d30

15 files changed

+1803
-0
lines changed

controllers/add_buildstrategy.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Copyright The Shipwright Contributors
2+
//
3+
// SPDX-License-Identifier: Apache-2.0
4+
5+
package controllers
6+
7+
import (
8+
"github.com/shipwright-io/operator/controllers/buildstrategy"
9+
)
10+
11+
func init() {
12+
// AddToManagerFuncs is a list of functions to create controllers and add them to a manager.
13+
AddToManagerFuncs = append(AddToManagerFuncs, buildstrategy.Add)
14+
}
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
// Copyright The Shipwright Contributors
2+
//
3+
// SPDX-License-Identifier: Apache-2.0
4+
5+
package buildstrategy
6+
7+
import (
8+
"context"
9+
"fmt"
10+
11+
"github.com/go-logr/logr"
12+
"github.com/manifestival/manifestival"
13+
corev1 "k8s.io/api/core/v1"
14+
crdclientv1 "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1"
15+
"k8s.io/apimachinery/pkg/api/errors"
16+
"k8s.io/apimachinery/pkg/runtime"
17+
"k8s.io/apimachinery/pkg/types"
18+
ctrl "sigs.k8s.io/controller-runtime"
19+
"sigs.k8s.io/controller-runtime/pkg/builder"
20+
"sigs.k8s.io/controller-runtime/pkg/client"
21+
"sigs.k8s.io/controller-runtime/pkg/event"
22+
"sigs.k8s.io/controller-runtime/pkg/manager"
23+
"sigs.k8s.io/controller-runtime/pkg/predicate"
24+
25+
v1beta1 "github.com/shipwright-io/build/pkg/apis/build/v1beta1"
26+
"github.com/shipwright-io/operator/api/v1alpha1"
27+
commonctrl "github.com/shipwright-io/operator/controllers/common"
28+
"github.com/shipwright-io/operator/pkg/reconciler/build"
29+
)
30+
31+
// BuildStrategyReconciler reconciles a ShipwrightBuild object
32+
type BuildStrategyReconciler struct {
33+
client.Client // controller kubernetes client
34+
CRDClient crdclientv1.ApiextensionsV1Interface
35+
36+
Logger logr.Logger // decorated logger
37+
Scheme *runtime.Scheme // runtime scheme
38+
Manifest manifestival.Manifest // release manifests render
39+
}
40+
41+
// Add creates a new buildStrategy Controller and adds it to the Manager. The Manager will set fields on the Controller
42+
// and Start it when the Manager is Started.
43+
func Add(mgr manager.Manager) error {
44+
r, err := newReconciler(mgr)
45+
if err != nil {
46+
return err
47+
}
48+
return add(mgr, r)
49+
}
50+
51+
// newReconciler returns a new reconcile.Reconciler
52+
func newReconciler(mgr manager.Manager) (*BuildStrategyReconciler, error) {
53+
c := mgr.GetClient()
54+
scheme := mgr.GetScheme()
55+
logger := ctrl.Log.WithName("controllers").WithName("buildstrategy")
56+
57+
crdClient, err := crdclientv1.NewForConfig(mgr.GetConfig())
58+
if err != nil {
59+
logger.Error(err, "unable to get crd client")
60+
return nil, err
61+
}
62+
63+
return &BuildStrategyReconciler{
64+
CRDClient: crdClient,
65+
Client: c,
66+
Scheme: scheme,
67+
Logger: logger,
68+
}, nil
69+
}
70+
71+
// add adds a new Controller to mgr with r as the reconcile.Reconciler
72+
func add(mgr manager.Manager, r *BuildStrategyReconciler) error {
73+
74+
return ctrl.NewControllerManagedBy(mgr).
75+
For(&v1alpha1.ShipwrightBuild{}, builder.WithPredicates(predicate.Funcs{
76+
CreateFunc: func(ce event.CreateEvent) bool {
77+
// all new objects must be subject to reconciliation
78+
return true
79+
},
80+
DeleteFunc: func(e event.DeleteEvent) bool {
81+
// objects that haven't been confirmed deleted must be subject to reconciliation
82+
return !e.DeleteStateUnknown
83+
},
84+
UpdateFunc: func(e event.UpdateEvent) bool {
85+
// objects that have updated generation must be subject to reconciliation
86+
return e.ObjectOld.GetGeneration() != e.ObjectNew.GetGeneration()
87+
},
88+
})).
89+
Complete(r)
90+
}
91+
92+
// Reconcile performs the resource reconciliation steps to deploy or remove Shipwright Build
93+
// instances. When deletion-timestamp is found, the removal of the previously deploy resources is
94+
// executed, otherwise the regular deploy workflow takes place.
95+
func (r *BuildStrategyReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
96+
logger := r.Logger.WithValues("namespace", req.Namespace, "name", req.Name)
97+
98+
// retrieving the ShipwrightBuild instance requested for reconcile
99+
b := &v1alpha1.ShipwrightBuild{}
100+
if err := r.Get(ctx, req.NamespacedName, b); err != nil {
101+
if errors.IsNotFound(err) {
102+
logger.Info("Resource is not found!")
103+
return commonctrl.NoRequeue()
104+
}
105+
logger.Error(err, "retrieving ShipwrightBuild object from cache")
106+
return commonctrl.RequeueOnError(err)
107+
}
108+
109+
// Check targetNamespace is created
110+
targetNamespace := b.Spec.TargetNamespace
111+
ns := &corev1.Namespace{}
112+
if err := r.Get(ctx, types.NamespacedName{Name: targetNamespace}, ns); err != nil {
113+
if !errors.IsNotFound(err) {
114+
logger.Info("retrieving target namespace %s error: %s", targetNamespace, err.Error())
115+
return commonctrl.RequeueAfterWithError(err)
116+
}
117+
}
118+
119+
// Reconcile BuildStrategy
120+
requeue, err := build.ReconcileBuildStrategy(ctx, r.CRDClient, r.Client, r.Logger, targetNamespace)
121+
if err != nil {
122+
return commonctrl.RequeueAfterWithError(err)
123+
}
124+
if requeue {
125+
return commonctrl.Requeue()
126+
}
127+
128+
logger.Info("All done!")
129+
return commonctrl.NoRequeue()
130+
}
131+
132+
func (r *BuildStrategyReconciler) GetBuildStrategy(namespaced types.NamespacedName) (*v1beta1.ClusterBuildStrategy, error) {
133+
// look up storage class by name
134+
cls := &v1beta1.ClusterBuildStrategy{}
135+
if err := r.Get(context.TODO(), namespaced, cls); err != nil {
136+
return nil, fmt.Errorf("Unable to retrieve cls class")
137+
}
138+
return cls, nil
139+
}
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
package buildstrategy
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
o "github.com/onsi/gomega"
8+
v1beta1 "github.com/shipwright-io/build/pkg/apis/build/v1beta1"
9+
"github.com/shipwright-io/operator/api/v1alpha1"
10+
commonctrl "github.com/shipwright-io/operator/controllers/common"
11+
appsv1 "k8s.io/api/apps/v1"
12+
corev1 "k8s.io/api/core/v1"
13+
crdv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
14+
crdclientv1 "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/fake"
15+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
16+
"k8s.io/apimachinery/pkg/runtime"
17+
"k8s.io/apimachinery/pkg/types"
18+
"sigs.k8s.io/controller-runtime/pkg/client"
19+
"sigs.k8s.io/controller-runtime/pkg/client/fake"
20+
"sigs.k8s.io/controller-runtime/pkg/log/zap"
21+
"sigs.k8s.io/controller-runtime/pkg/reconcile"
22+
)
23+
24+
// bootstrapBuildStrategyReconciler start up a new instance of BuildStrategyReconciler which is
25+
// ready to interact with Manifestival, returning the Manifestival instance and the client.
26+
func bootstrapBuildStrategyReconciler(
27+
t *testing.T,
28+
b *v1alpha1.ShipwrightBuild,
29+
tcrds []*crdv1.CustomResourceDefinition,
30+
) (client.Client, *crdclientv1.Clientset, *BuildStrategyReconciler) {
31+
g := o.NewGomegaWithT(t)
32+
33+
s := runtime.NewScheme()
34+
s.AddKnownTypes(corev1.SchemeGroupVersion, &corev1.Namespace{})
35+
s.AddKnownTypes(appsv1.SchemeGroupVersion, &appsv1.Deployment{})
36+
s.AddKnownTypes(v1beta1.SchemeGroupVersion, &v1beta1.ClusterBuildStrategy{})
37+
s.AddKnownTypes(v1beta1.SchemeGroupVersion, &v1beta1.BuildStrategy{})
38+
s.AddKnownTypes(v1alpha1.GroupVersion, &v1alpha1.ShipwrightBuild{})
39+
40+
logger := zap.New()
41+
42+
// create fake webhook deployment(prerequisite of build strategy installation)
43+
webhookdep := &appsv1.Deployment{
44+
ObjectMeta: metav1.ObjectMeta{Namespace: b.Spec.TargetNamespace, Name: "shp-build-webhook"},
45+
Status: appsv1.DeploymentStatus{
46+
Conditions: []appsv1.DeploymentCondition{{
47+
Type: appsv1.DeploymentAvailable,
48+
Status: corev1.ConditionTrue,
49+
}},
50+
},
51+
}
52+
c := fake.NewClientBuilder().WithScheme(s).WithObjects(b, webhookdep).WithStatusSubresource(b, webhookdep).Build()
53+
54+
var crdClient *crdclientv1.Clientset
55+
if len(tcrds) > 0 {
56+
objs := []runtime.Object{}
57+
for _, obj := range tcrds {
58+
objs = append(objs, obj)
59+
}
60+
crdClient = crdclientv1.NewSimpleClientset(objs...)
61+
} else {
62+
crdClient = crdclientv1.NewSimpleClientset()
63+
}
64+
65+
r := &BuildStrategyReconciler{CRDClient: crdClient.ApiextensionsV1(), Client: c, Scheme: s, Logger: logger}
66+
67+
if b.Spec.TargetNamespace != "" {
68+
t.Logf("Creating test namespace '%s'", b.Spec.TargetNamespace)
69+
t.Run("create-test-namespace", func(t *testing.T) {
70+
err := c.Create(
71+
context.TODO(),
72+
&corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: b.Spec.TargetNamespace}},
73+
&client.CreateOptions{},
74+
)
75+
g.Expect(err).To(o.BeNil())
76+
})
77+
}
78+
79+
return c, crdClient, r
80+
}
81+
82+
// testBuildStrategyReconcilerReconcile simulates the reconciliation process for rolling out and
83+
// rolling back manifests in the informed target namespace name.
84+
func testBuildStrategyReconcilerReconcile(t *testing.T, targetNamespace string) {
85+
g := o.NewGomegaWithT(t)
86+
87+
namespacedName := types.NamespacedName{Namespace: "default", Name: "name"}
88+
clsName := types.NamespacedName{
89+
Name: "kaniko",
90+
}
91+
92+
req := reconcile.Request{NamespacedName: namespacedName}
93+
94+
b := &v1alpha1.ShipwrightBuild{
95+
ObjectMeta: metav1.ObjectMeta{
96+
Name: namespacedName.Name,
97+
Namespace: namespacedName.Namespace,
98+
},
99+
Spec: v1alpha1.ShipwrightBuildSpec{
100+
TargetNamespace: targetNamespace,
101+
},
102+
}
103+
crd1 := &crdv1.CustomResourceDefinition{}
104+
crd1.Name = "clusterbuildstrategies.shipwright.io"
105+
crd2 := &crdv1.CustomResourceDefinition{}
106+
crd2.Name = "buildstrategies.shipwright.io"
107+
crds := []*crdv1.CustomResourceDefinition{crd1, crd2}
108+
_, _, r := bootstrapBuildStrategyReconciler(t, b, crds)
109+
110+
t.Logf("Deploying BuildStrategy Controller against '%s' namespace", targetNamespace)
111+
112+
// rolling out all manifests on the desired namespace, making sure the build cluster strategies are created
113+
t.Run("rollout-manifests", func(t *testing.T) {
114+
ctx := context.TODO()
115+
res, err := r.Reconcile(ctx, req)
116+
g.Expect(err).To(o.BeNil())
117+
g.Expect(res.Requeue).To(o.BeFalse())
118+
err = r.Get(ctx, clsName, &v1beta1.ClusterBuildStrategy{})
119+
g.Expect(err).To(o.BeNil())
120+
})
121+
122+
// rolling back all changes, making sure the build cluster strategies are also not found afterwards
123+
t.Run("rollback-manifests", func(t *testing.T) {
124+
ctx := context.TODO()
125+
126+
err := r.Get(ctx, namespacedName, b)
127+
g.Expect(err).To(o.BeNil())
128+
129+
/*b.SetDeletionTimestamp(&metav1.Time{Time: time.Now()})
130+
err = r.Update(ctx, b, &client.UpdateOptions{Raw: &metav1.UpdateOptions{}})
131+
g.Expect(err).To(o.BeNil())
132+
133+
res, err := r.Reconcile(ctx, req)
134+
g.Expect(err).To(o.BeNil())
135+
g.Expect(res.Requeue).To(o.BeFalse())
136+
137+
err = r.Get(ctx, clsName, &v1beta1.ClusterBuildStrategy{})
138+
g.Expect(errors.IsNotFound(err)).To(o.BeTrue())*/
139+
})
140+
}
141+
142+
// TestBuildStrategyReconciler_Reconcile runs rollout/rollback tests against different namespaces.
143+
func TestBuildStrategyReconciler_Reconcile(t *testing.T) {
144+
tests := []struct {
145+
testName string
146+
targetNamespace string
147+
}{{
148+
testName: "target namespace is informed",
149+
targetNamespace: "namespace",
150+
}, {
151+
testName: "target namespace is not informed",
152+
targetNamespace: commonctrl.DefaultTargetNamespace,
153+
}}
154+
155+
for _, tt := range tests {
156+
t.Run(tt.testName, func(t *testing.T) {
157+
testBuildStrategyReconcilerReconcile(t, tt.targetNamespace)
158+
})
159+
}
160+
}

0 commit comments

Comments
 (0)