Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Install build strategies via operator #171

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions bundle/manifests/shipwright-operator.clusterserviceversion.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -739,6 +739,18 @@ spec:
- delete
- patch
- update
- apiGroups:
- shipwright.io
resources:
- clusterbuildstrategies
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- authentication.k8s.io
resources:
Expand Down
12 changes: 12 additions & 0 deletions config/rbac/role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -361,3 +361,15 @@ rules:
- delete
- patch
- update
- apiGroups:
- shipwright.io
resources:
- clusterbuildstrategies
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
48 changes: 48 additions & 0 deletions controllers/buildstrategies_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package controllers

import (
"fmt"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

buildv1alpha1 "github.com/shipwright-io/build/pkg/apis/build/v1alpha1"
"github.com/shipwright-io/operator/api/v1alpha1"
"github.com/shipwright-io/operator/test"
)

var _ = Describe("Install embedded build strategies", func() {

var build *v1alpha1.ShipwrightBuild

BeforeEach(func(ctx SpecContext) {
setupTektonCRDs(ctx)
build = createShipwrightBuild(ctx, "shipwright")
test.CRDEventuallyExists(ctx, k8sClient, "clusterbuildstrategies.shipwright.io")
})

When("the install build strategies feature is enabled", func() {

It("applies the embedded build strategy manifests to the cluster", func(ctx SpecContext) {
expectedBuildStrategies, err := test.ParseBuildStrategyNames()
Expect(err).NotTo(HaveOccurred())
for _, strategy := range expectedBuildStrategies {
strategyObj := &buildv1alpha1.ClusterBuildStrategy{
ObjectMeta: metav1.ObjectMeta{
Name: strategy,
},
}
By(fmt.Sprintf("checking for build strategy %q", strategy))
test.EventuallyExists(ctx, k8sClient, strategyObj)
}

})
})

AfterEach(func(ctx SpecContext) {
deleteShipwrightBuild(ctx, build)
})

})
119 changes: 3 additions & 116 deletions controllers/default_test.go
Original file line number Diff line number Diff line change
@@ -1,39 +1,21 @@
package controllers

import (
"context"

g "github.com/onsi/ginkgo/v2"
o "github.com/onsi/gomega"

appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
crdv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"

"github.com/shipwright-io/operator/api/v1alpha1"
"github.com/shipwright-io/operator/pkg/common"
"github.com/shipwright-io/operator/test"
)

// createNamespace creates the namespace informed.
func createNamespace(ctx context.Context, name string) {
ns := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: name}}
err := k8sClient.Get(ctx, types.NamespacedName{Name: ns.Name}, ns)
if errors.IsNotFound(err) {
err = k8sClient.Create(ctx, ns, &client.CreateOptions{})
}
o.Expect(err).NotTo(o.HaveOccurred())
}

var _ = g.Describe("Reconcile default ShipwrightBuild installation", func() {

// namespace where ShipwrightBuild instance will be located
const namespace = "namespace"
// targetNamespace namespace where shipwright Controller and dependencies will be located
const targetNamespace = "target-namespace"
// build Build instance employed during testing
Expand Down Expand Up @@ -62,109 +44,14 @@ var _ = g.Describe("Reconcile default ShipwrightBuild installation", func() {
},
}

truePtr := true
g.BeforeEach(func(ctx g.SpecContext) {
// setting up the namespaces, where Shipwright Controller will be deployed
createNamespace(ctx, namespace)

g.By("does tekton taskrun crd exist")
err := k8sClient.Get(ctx, types.NamespacedName{Name: "taskruns.tekton.dev"}, &crdv1.CustomResourceDefinition{})
if errors.IsNotFound(err) {
g.By("creating tekton taskrun crd")
taskRunCRD := &crdv1.CustomResourceDefinition{}
taskRunCRD.Name = "taskruns.tekton.dev"
taskRunCRD.Spec.Group = "tekton.dev"
taskRunCRD.Spec.Scope = crdv1.NamespaceScoped
taskRunCRD.Spec.Versions = []crdv1.CustomResourceDefinitionVersion{
{
Name: "v1beta1",
Storage: true,
Schema: &crdv1.CustomResourceValidation{
OpenAPIV3Schema: &crdv1.JSONSchemaProps{
Type: "object",
XPreserveUnknownFields: &truePtr,
},
},
},
}
taskRunCRD.Spec.Names.Plural = "taskruns"
taskRunCRD.Spec.Names.Singular = "taskrun"
taskRunCRD.Spec.Names.Kind = "TaskRun"
taskRunCRD.Spec.Names.ListKind = "TaskRunList"
taskRunCRD.Status.StoredVersions = []string{"v1beta1"}
err = k8sClient.Create(ctx, taskRunCRD, &client.CreateOptions{})
o.Expect(err).NotTo(o.HaveOccurred())

}
o.Expect(err).NotTo(o.HaveOccurred())

g.By("does tektonconfig crd exist")
err = k8sClient.Get(ctx, types.NamespacedName{Name: "tektonconfigs.operator.tekton.dev"}, &crdv1.CustomResourceDefinition{})
if errors.IsNotFound(err) {
tektonOpCRD := &crdv1.CustomResourceDefinition{}
tektonOpCRD.Name = "tektonconfigs.operator.tekton.dev"
tektonOpCRD.Labels = map[string]string{"operator.tekton.dev/release": common.TektonOpMinSupportedVersion}
tektonOpCRD.Spec.Group = "operator.tekton.dev"
tektonOpCRD.Spec.Scope = crdv1.ClusterScoped
tektonOpCRD.Spec.Versions = []crdv1.CustomResourceDefinitionVersion{
{
Name: "v1alpha1",
Storage: true,
Schema: &crdv1.CustomResourceValidation{
OpenAPIV3Schema: &crdv1.JSONSchemaProps{
Type: "object",
XPreserveUnknownFields: &truePtr,
},
},
},
}
tektonOpCRD.Spec.Names.Plural = "tektonconfigs"
tektonOpCRD.Spec.Names.Singular = "tektonconfig"
tektonOpCRD.Spec.Names.Kind = "TektonConfig"
tektonOpCRD.Spec.Names.ListKind = "TektonConfigList"
tektonOpCRD.Status.StoredVersions = []string{"v1alpha1"}
err = k8sClient.Create(ctx, tektonOpCRD, &client.CreateOptions{})
o.Expect(err).NotTo(o.HaveOccurred())
}
o.Expect(err).NotTo(o.HaveOccurred())

g.By("creating a ShipwrightBuild instance")
build = &v1alpha1.ShipwrightBuild{
ObjectMeta: metav1.ObjectMeta{
Namespace: namespace,
Name: "cluster",
},
Spec: v1alpha1.ShipwrightBuildSpec{
TargetNamespace: targetNamespace,
},
}
err = k8sClient.Create(ctx, build, &client.CreateOptions{})
o.Expect(err).NotTo(o.HaveOccurred())

// when the finalizer is in place, the deployment of manifest elements is done, and therefore
// functional testing can proceed
g.By("waiting for the finalizer to be set")
test.EventuallyContainFinalizer(ctx, k8sClient, build, FinalizerAnnotation)
setupTektonCRDs(ctx)
build = createShipwrightBuild(ctx, targetNamespace)
})

g.AfterEach(func(ctx g.SpecContext) {
g.By("deleting the ShipwrightBuild instance")
namespacedName := types.NamespacedName{Namespace: namespace, Name: build.Name}
err := k8sClient.Get(ctx, namespacedName, build)
if errors.IsNotFound(err) {
return
}
o.Expect(err).NotTo(o.HaveOccurred())

err = k8sClient.Delete(ctx, build, &client.DeleteOptions{})
// the delete e2e's can delete this object before this AfterEach runs
if errors.IsNotFound(err) {
return
}
o.Expect(err).NotTo(o.HaveOccurred())

g.By("waiting for ShipwrightBuild instance to be completely removed")
test.EventuallyRemoved(ctx, k8sClient, build)
deleteShipwrightBuild(ctx, build)

g.By("checking that the shipwright-build-controller deployment has been removed")
deployment := baseDeployment.DeepCopy()
Expand Down
50 changes: 44 additions & 6 deletions controllers/shipwrightbuild_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package controllers
import (
"context"
"fmt"
"path/filepath"

"github.com/go-logr/logr"
"github.com/manifestival/manifestival"
Expand All @@ -25,6 +26,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/predicate"

"github.com/shipwright-io/operator/api/v1alpha1"
"github.com/shipwright-io/operator/pkg/buildstrategy"
"github.com/shipwright-io/operator/pkg/certmanager"
"github.com/shipwright-io/operator/pkg/common"
"github.com/shipwright-io/operator/pkg/tekton"
Expand Down Expand Up @@ -53,10 +55,11 @@ type ShipwrightBuildReconciler struct {
CRDClient crdclientv1.ApiextensionsV1Interface
TektonOperatorClient tektonoperatorv1alpha1client.OperatorV1alpha1Interface

Logger logr.Logger // decorated logger
Scheme *runtime.Scheme // runtime scheme
Manifest manifestival.Manifest // release manifests render
TektonManifest manifestival.Manifest // Tekton release manifest render
Logger logr.Logger // decorated logger
Scheme *runtime.Scheme // runtime scheme
Manifest manifestival.Manifest // release manifests render
TektonManifest manifestival.Manifest // Tekton release manifest render
BuildStrategyManifest manifestival.Manifest // Build strategies manifest to render
}

// setFinalizer append finalizer on the resource, and uses local client to update it immediately.
Expand Down Expand Up @@ -195,6 +198,11 @@ func (r *ShipwrightBuildReconciler) Reconcile(ctx context.Context, req ctrl.Requ
logger.Info("Finalizers removed, deletion of manifests completed!")
return NoRequeue()
}
logger.Info("Deleting cluster build strategies")
if err := r.BuildStrategyManifest.Delete(); err != nil {
logger.Error(err, "deleting cluster build strategies")
return RequeueWithError(err)
}

logger.Info("Deleting manifests...")
if err := manifest.Delete(); err != nil {
Expand Down Expand Up @@ -229,6 +237,29 @@ func (r *ShipwrightBuildReconciler) Reconcile(ctx context.Context, req ctrl.Requ
logger.Error(err, "setting the finalizer")
return RequeueWithError(err)
}

requeue, err = buildstrategy.ReconcileBuildStrategies(ctx,
r.CRDClient,
logger,
r.BuildStrategyManifest)
if err != nil {
logger.Error(err, "reconcile cluster build strategies")
return RequeueWithError(err)
}
if requeue {
logger.Info("requeue waiting for cluster build strategy preconditions")
apimeta.SetStatusCondition(&b.Status.Conditions, metav1.Condition{
Type: ConditionReady,
Status: metav1.ConditionUnknown,
Reason: "ClusterBuildStrategiesWaiting",
Message: "Waiting for cluster build strategies to be deployed",
})
if updateErr := r.Client.Status().Update(ctx, b); updateErr != nil {
return RequeueWithError(err)
}
return Requeue()
}

apimeta.SetStatusCondition(&b.Status.Conditions, metav1.Condition{
Type: ConditionReady,
Status: metav1.ConditionTrue,
Expand All @@ -246,8 +277,15 @@ func (r *ShipwrightBuildReconciler) Reconcile(ctx context.Context, req ctrl.Requ
// setupManifestival instantiate manifestival with local controller attributes, as well as tekton prereqs.
func (r *ShipwrightBuildReconciler) setupManifestival() error {
var err error
r.Manifest, err = common.SetupManifestival(r.Client, "release.yaml", r.Logger)
return err
r.Manifest, err = common.SetupManifestival(r.Client, "release.yaml", false, r.Logger)
if err != nil {
return err
}
r.BuildStrategyManifest, err = common.SetupManifestival(r.Client, filepath.Join("samples", "buildstrategy"), true, r.Logger)
if err != nil {
return err
}
return nil
}

// SetupWithManager sets up the controller with the Manager, by instantiating Manifestival and
Expand Down
22 changes: 18 additions & 4 deletions controllers/shipwrightbuild_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,12 +158,18 @@ func testShipwrightBuildReconcilerReconcile(t *testing.T, targetNamespace string
ctx := context.TODO()
res, err := r.Reconcile(ctx, req)
g.Expect(err).To(o.BeNil())
g.Expect(res.Requeue).To(o.BeFalse())
// TODO: Code technically uses two different clientsets that don't talk to each other.
// This makes testing brittle and unable to capture the behavior on a real cluster.
// Requeue can return "true" because the tests think the CRD for ClusterBuildStrategies
// do not exist yet.
g.Expect(res.Requeue).To(o.BeTrue(), "checking requeue for Reconcile")
err = c.Get(ctx, deploymentName, &appsv1.Deployment{})
g.Expect(err).To(o.BeNil())
err = c.Get(ctx, namespacedName, b)
g.Expect(err).To(o.BeNil())
g.Expect(b.Status.IsReady()).To(o.BeTrue())
// Likewise, the ShipwrightBuild object will not report itself ready because it is waiting
// for the ClusterBuildStrategy CRD to be created first.
g.Expect(b.Status.IsReady()).To(o.BeFalse(), "checking ShipwrightBuild readiness")
})

t.Run("rollout-manifests-with-images-env-vars", func(t *testing.T) {
Expand All @@ -174,14 +180,20 @@ func testShipwrightBuildReconcilerReconcile(t *testing.T, targetNamespace string
deployment := &appsv1.Deployment{}
res, err := r.Reconcile(ctx, req)
g.Expect(err).To(o.BeNil())
g.Expect(res.Requeue).To(o.BeFalse())
// TODO: Code technically uses two different clientsets that don't talk to each other.
// This makes testing brittle and unable to capture the behavior on a real cluster.
// Requeue can return "true" because the tests think the CRD for ClusterBuildStrategies
// do not exist yet.
g.Expect(res.Requeue).To(o.BeTrue())
err = c.Get(ctx, deploymentName, deployment)
g.Expect(err).To(o.BeNil())
containers := deployment.Spec.Template.Spec.Containers
g.Expect(containers[0].Image).To(o.Equal("ghcr.io/shipwright-io/build/shipwright-build-controller:nightly-2023-05-05-1683263383"))
err = c.Get(ctx, namespacedName, b)
g.Expect(err).To(o.BeNil())
g.Expect(b.Status.IsReady()).To(o.BeTrue())
// Likewise, the ShipwrightBuild object will not report itself ready because it is waiting
// for the ClusterBuildStrategy CRD to be created first.
g.Expect(b.Status.IsReady()).To(o.BeFalse())
})

// rolling back all changes, making sure the main deployment is also not found afterwards
Expand All @@ -193,6 +205,8 @@ func testShipwrightBuildReconcilerReconcile(t *testing.T, targetNamespace string

// setting a deletion timestemp on the build object, it triggers the rollback logic so the
// reconciliation should remove the objects previously deployed

// TODO: Refactor to use owner references so the rollback is handled by Kubernetes itself.
b.SetDeletionTimestamp(&metav1.Time{Time: time.Now()})
err = r.Update(ctx, b, &client.UpdateOptions{})
g.Expect(err).To(o.BeNil())
Expand Down
Loading
Loading