-
Notifications
You must be signed in to change notification settings - Fork 17
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
20 changed files
with
1,841 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
160
controllers/buildstrategy/buildstrategy_controller_test.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.