Skip to content

Commit

Permalink
add buildstrategy controller
Browse files Browse the repository at this point in the history
  • Loading branch information
jkhelil committed Oct 27, 2023
1 parent 92bc4f7 commit dd4fdf6
Show file tree
Hide file tree
Showing 20 changed files with 1,841 additions and 3 deletions.
17 changes: 17 additions & 0 deletions bundle/manifests/shipwright-operator.clusterserviceversion.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -426,9 +426,13 @@ spec:
resources:
- customresourcedefinitions
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- apps
resources:
Expand Down Expand Up @@ -739,6 +743,19 @@ spec:
- delete
- patch
- update
- apiGroups:
- shipwright.io
resources:
- buildstrategies
- clusterbuildstrategies
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- authentication.k8s.io
resources:
Expand Down
17 changes: 17 additions & 0 deletions config/rbac/role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,13 @@ rules:
resources:
- customresourcedefinitions
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- apps
resources:
Expand Down Expand Up @@ -361,3 +365,16 @@ rules:
- delete
- patch
- update
- apiGroups:
- shipwright.io
resources:
- buildstrategies
- clusterbuildstrategies
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
14 changes: 14 additions & 0 deletions controllers/add_buildstrategy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Copyright The Shipwright Contributors
//
// SPDX-License-Identifier: Apache-2.0

package controllers

import (
"github.com/shipwright-io/operator/controllers/buildstrategy"
)

func init() {
// AddToManagerFuncs is a list of functions to create controllers and add them to a manager.
AddToManagerFuncs = append(AddToManagerFuncs, buildstrategy.Add)
}
139 changes: 139 additions & 0 deletions controllers/buildstrategy/buildstrategy_controller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
// Copyright The Shipwright Contributors
//
// SPDX-License-Identifier: Apache-2.0

package buildstrategy

import (
"context"
"fmt"

"github.com/go-logr/logr"
"github.com/manifestival/manifestival"
corev1 "k8s.io/api/core/v1"
crdclientv1 "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/builder"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/event"
"sigs.k8s.io/controller-runtime/pkg/manager"
"sigs.k8s.io/controller-runtime/pkg/predicate"

v1beta1 "github.com/shipwright-io/build/pkg/apis/build/v1beta1"
"github.com/shipwright-io/operator/api/v1alpha1"
commonctrl "github.com/shipwright-io/operator/controllers/common"
"github.com/shipwright-io/operator/pkg/reconciler/build"
)

// BuildStrategyReconciler reconciles a ShipwrightBuild object
type BuildStrategyReconciler struct {
client.Client // controller kubernetes client
CRDClient crdclientv1.ApiextensionsV1Interface

Logger logr.Logger // decorated logger
Scheme *runtime.Scheme // runtime scheme
Manifest manifestival.Manifest // release manifests render
}

// Add creates a new buildStrategy Controller and adds it to the Manager. The Manager will set fields on the Controller
// and Start it when the Manager is Started.
func Add(mgr manager.Manager) error {
r, err := newReconciler(mgr)
if err != nil {
return err
}
return add(mgr, r)
}

// newReconciler returns a new reconcile.Reconciler
func newReconciler(mgr manager.Manager) (*BuildStrategyReconciler, error) {
c := mgr.GetClient()
scheme := mgr.GetScheme()
logger := ctrl.Log.WithName("controllers").WithName("buildstrategy")

crdClient, err := crdclientv1.NewForConfig(mgr.GetConfig())
if err != nil {
logger.Error(err, "unable to get crd client")
return nil, err
}

return &BuildStrategyReconciler{
CRDClient: crdClient,
Client: c,
Scheme: scheme,
Logger: logger,
}, nil
}

// add adds a new Controller to mgr with r as the reconcile.Reconciler
func add(mgr manager.Manager, r *BuildStrategyReconciler) error {

return ctrl.NewControllerManagedBy(mgr).
For(&v1alpha1.ShipwrightBuild{}, builder.WithPredicates(predicate.Funcs{
CreateFunc: func(ce event.CreateEvent) bool {
// all new objects must be subject to reconciliation
return true
},
DeleteFunc: func(e event.DeleteEvent) bool {
// objects that haven't been confirmed deleted must be subject to reconciliation
return !e.DeleteStateUnknown
},
UpdateFunc: func(e event.UpdateEvent) bool {
// objects that have updated generation must be subject to reconciliation
return e.ObjectOld.GetGeneration() != e.ObjectNew.GetGeneration()
},
})).
Complete(r)
}

// Reconcile performs the resource reconciliation steps to deploy or remove Shipwright Build
// instances. When deletion-timestamp is found, the removal of the previously deploy resources is
// executed, otherwise the regular deploy workflow takes place.
func (r *BuildStrategyReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
logger := r.Logger.WithValues("namespace", req.Namespace, "name", req.Name)

// retrieving the ShipwrightBuild instance requested for reconcile
b := &v1alpha1.ShipwrightBuild{}
if err := r.Get(ctx, req.NamespacedName, b); err != nil {
if errors.IsNotFound(err) {
logger.Info("Resource is not found!")
return commonctrl.NoRequeue()
}
logger.Error(err, "retrieving ShipwrightBuild object from cache")
return commonctrl.RequeueOnError(err)
}

// Check targetNamespace is created
targetNamespace := b.Spec.TargetNamespace
ns := &corev1.Namespace{}
if err := r.Get(ctx, types.NamespacedName{Name: targetNamespace}, ns); err != nil {
if !errors.IsNotFound(err) {
logger.Info("retrieving target namespace %s error: %s", targetNamespace, err.Error())
return commonctrl.RequeueAfterWithError(err)
}
}

// Reconcile BuildStrategy
requeue, err := build.ReconcileBuildStrategy(ctx, r.CRDClient, r.Client, r.Logger, targetNamespace)
if err != nil {
return commonctrl.RequeueAfterWithError(err)
}
if requeue {
return commonctrl.Requeue()
}

logger.Info("All done!")
return commonctrl.NoRequeue()
}

func (r *BuildStrategyReconciler) GetBuildStrategy(namespaced types.NamespacedName) (*v1beta1.ClusterBuildStrategy, error) {
// look up storage class by name
cls := &v1beta1.ClusterBuildStrategy{}
if err := r.Get(context.TODO(), namespaced, cls); err != nil {
return nil, fmt.Errorf("Unable to retrieve cls class")
}
return cls, nil
}
160 changes: 160 additions & 0 deletions controllers/buildstrategy/buildstrategy_controller_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
package buildstrategy

import (
"context"
"testing"

o "github.com/onsi/gomega"
v1beta1 "github.com/shipwright-io/build/pkg/apis/build/v1beta1"
"github.com/shipwright-io/operator/api/v1alpha1"
commonctrl "github.com/shipwright-io/operator/controllers/common"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
crdv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
crdclientv1 "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/fake"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)

// bootstrapBuildStrategyReconciler start up a new instance of BuildStrategyReconciler which is
// ready to interact with Manifestival, returning the Manifestival instance and the client.
func bootstrapBuildStrategyReconciler(
t *testing.T,
b *v1alpha1.ShipwrightBuild,
tcrds []*crdv1.CustomResourceDefinition,
) (client.Client, *crdclientv1.Clientset, *BuildStrategyReconciler) {
g := o.NewGomegaWithT(t)

s := runtime.NewScheme()
s.AddKnownTypes(corev1.SchemeGroupVersion, &corev1.Namespace{})
s.AddKnownTypes(appsv1.SchemeGroupVersion, &appsv1.Deployment{})
s.AddKnownTypes(v1beta1.SchemeGroupVersion, &v1beta1.ClusterBuildStrategy{})
s.AddKnownTypes(v1beta1.SchemeGroupVersion, &v1beta1.BuildStrategy{})
s.AddKnownTypes(v1alpha1.GroupVersion, &v1alpha1.ShipwrightBuild{})

logger := zap.New()

// create fake webhook deployment(prerequisite of build strategy installation)
webhookdep := &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{Namespace: b.Spec.TargetNamespace, Name: "shipwright-build-webhook"},
Status: appsv1.DeploymentStatus{
Conditions: []appsv1.DeploymentCondition{{
Type: appsv1.DeploymentAvailable,
Status: corev1.ConditionTrue,
}},
},
}
c := fake.NewClientBuilder().WithScheme(s).WithObjects(b, webhookdep).WithStatusSubresource(b, webhookdep).Build()

var crdClient *crdclientv1.Clientset
if len(tcrds) > 0 {
objs := []runtime.Object{}
for _, obj := range tcrds {
objs = append(objs, obj)
}
crdClient = crdclientv1.NewSimpleClientset(objs...)
} else {
crdClient = crdclientv1.NewSimpleClientset()
}

r := &BuildStrategyReconciler{CRDClient: crdClient.ApiextensionsV1(), Client: c, Scheme: s, Logger: logger}

if b.Spec.TargetNamespace != "" {
t.Logf("Creating test namespace '%s'", b.Spec.TargetNamespace)
t.Run("create-test-namespace", func(t *testing.T) {
err := c.Create(
context.TODO(),
&corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: b.Spec.TargetNamespace}},
&client.CreateOptions{},
)
g.Expect(err).To(o.BeNil())
})
}

return c, crdClient, r
}

// testBuildStrategyReconcilerReconcile simulates the reconciliation process for rolling out and
// rolling back manifests in the informed target namespace name.
func testBuildStrategyReconcilerReconcile(t *testing.T, targetNamespace string) {
g := o.NewGomegaWithT(t)

namespacedName := types.NamespacedName{Namespace: "default", Name: "name"}
clsName := types.NamespacedName{
Name: "kaniko",
}

req := reconcile.Request{NamespacedName: namespacedName}

b := &v1alpha1.ShipwrightBuild{
ObjectMeta: metav1.ObjectMeta{
Name: namespacedName.Name,
Namespace: namespacedName.Namespace,
},
Spec: v1alpha1.ShipwrightBuildSpec{
TargetNamespace: targetNamespace,
},
}
crd1 := &crdv1.CustomResourceDefinition{}
crd1.Name = "clusterbuildstrategies.shipwright.io"
crd2 := &crdv1.CustomResourceDefinition{}
crd2.Name = "buildstrategies.shipwright.io"
crds := []*crdv1.CustomResourceDefinition{crd1, crd2}
_, _, r := bootstrapBuildStrategyReconciler(t, b, crds)

t.Logf("Deploying BuildStrategy Controller against '%s' namespace", targetNamespace)

// rolling out all manifests on the desired namespace, making sure the build cluster strategies are created
t.Run("rollout-manifests", func(t *testing.T) {
ctx := context.TODO()
res, err := r.Reconcile(ctx, req)
g.Expect(err).To(o.BeNil())
g.Expect(res.Requeue).To(o.BeFalse())
err = r.Get(ctx, clsName, &v1beta1.ClusterBuildStrategy{})
g.Expect(err).To(o.BeNil())
})

// rolling back all changes, making sure the build cluster strategies are also not found afterwards
t.Run("rollback-manifests", func(t *testing.T) {
ctx := context.TODO()

err := r.Get(ctx, namespacedName, b)
g.Expect(err).To(o.BeNil())

/*b.SetDeletionTimestamp(&metav1.Time{Time: time.Now()})
err = r.Update(ctx, b, &client.UpdateOptions{Raw: &metav1.UpdateOptions{}})
g.Expect(err).To(o.BeNil())
res, err := r.Reconcile(ctx, req)
g.Expect(err).To(o.BeNil())
g.Expect(res.Requeue).To(o.BeFalse())
err = r.Get(ctx, clsName, &v1beta1.ClusterBuildStrategy{})
g.Expect(errors.IsNotFound(err)).To(o.BeTrue())*/
})
}

// TestBuildStrategyReconciler_Reconcile runs rollout/rollback tests against different namespaces.
func TestBuildStrategyReconciler_Reconcile(t *testing.T) {
tests := []struct {
testName string
targetNamespace string
}{{
testName: "target namespace is informed",
targetNamespace: "namespace",
}, {
testName: "target namespace is not informed",
targetNamespace: commonctrl.DefaultTargetNamespace,
}}

for _, tt := range tests {
t.Run(tt.testName, func(t *testing.T) {
testBuildStrategyReconcilerReconcile(t, tt.targetNamespace)
})
}
}
3 changes: 2 additions & 1 deletion controllers/controller_rbac.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ package controllers
// +kubebuilder:rbac:groups=core,resources=serviceaccounts,verbs=get;list;watch;create
// +kubebuilder:rbac:groups=core,resources=serviceaccounts,resourceNames=shipwright-build-controller,verbs=update;patch;delete
// +kubebuilder:rbac:groups=apiextensions.k8s.io,resources=customresourcedefinitions,verbs=get;list;watch;create
// +kubebuilder:rbac:groups=apiextensions.k8s.io,resources=customresourcedefinitions,resourceNames=builds.shipwright.io;buildruns.shipwright.io;buildstrategies.shipwright.io;clusterbuildstrategies.shipwright.io,verbs=update;patch;delete
// +kubebuilder:rbac:groups=apiextensions.k8s.io,resources=customresourcedefinitions,resourceNames=builds.shipwright.io;buildruns.shipwright.io;buildstrategies.shipwright.io;clusterbuildstrategies.shipwright.io,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=clusterroles,verbs=get;list;watch;create
// +kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=clusterroles,resourceNames=shipwright-build-aggregate-edit,verbs=update;patch;delete
// +kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=clusterroles,resourceNames=shipwright-build-aggregate-view,verbs=update;patch;delete
Expand Down Expand Up @@ -40,3 +40,4 @@ package controllers
// +kubebuilder:rbac:groups=cert-manager.io,resources=certificates,verbs=get;list;watch;create
// +kubebuilder:rbac:groups=cert-manager.io,resources=certificates,resourceNames=shipwright-build-webhook-cert,verbs=update;patch;delete
// +kubebuilder:rbac:groups=core,resources=pods;events;configmaps;secrets;limitranges;namespaces;services,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=shipwright.io,resources=clusterbuildstrategies;buildstrategies,verbs=get;list;watch;create;update;patch;delete
Loading

0 comments on commit dd4fdf6

Please sign in to comment.