Skip to content

Commit 297e2ee

Browse files
committed
add a check for tektonconfig
Signed-off-by: Hasan Awad <hasan.m.awad94@gmail.com>
1 parent 7c692d8 commit 297e2ee

File tree

2 files changed

+171
-6
lines changed

2 files changed

+171
-6
lines changed

controllers/shipwrightbuild_controller.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,44 @@ func deleteObjectsIfPresent(ctx context.Context, k8sClient client.Client, objs [
104104
return nil
105105
}
106106

107+
// Check and fetch TektonConfig
108+
func (r *ShipwrightBuildReconciler) fetchAndCheckTektonConfig(ctx context.Context, logger logr.Logger, b *v1alpha1.ShipwrightBuild) (bool, error) {
109+
tektonConfig, err := r.TektonOperatorClient.TektonConfigs().Get(ctx, "config", metav1.GetOptions{})
110+
if err != nil {
111+
logger.Error(err, "Failed to fetch TektonConfig")
112+
apimeta.SetStatusCondition(&b.Status.Conditions, metav1.Condition{
113+
Type: ConditionReady,
114+
Status: metav1.ConditionFalse,
115+
Reason: "TektonConfigMissing",
116+
Message: "Unable to fetch TektonConfig from cluster",
117+
})
118+
if updateErr := r.Client.Status().Update(ctx, b); updateErr != nil {
119+
logger.Error(updateErr, "Failed to update ShipwrightBuild status after TektonConfig fetch failure")
120+
}
121+
return false, err
122+
}
123+
124+
// Check if TektonConfig is ready
125+
for _, condition := range tektonConfig.Status.Conditions {
126+
if condition.Type == ConditionReady && condition.Status == corev1.ConditionTrue {
127+
return true, nil
128+
}
129+
}
130+
131+
// TektonConfig is not ready
132+
logger.Info("TektonConfig is not ready yet")
133+
apimeta.SetStatusCondition(&b.Status.Conditions, metav1.Condition{
134+
Type: ConditionReady,
135+
Status: metav1.ConditionFalse,
136+
Reason: "TektonNotReady",
137+
Message: "Waiting for TektonConfig to become Ready",
138+
})
139+
if updateErr := r.Client.Status().Update(ctx, b); updateErr != nil {
140+
logger.Error(updateErr, "Failed to update ShipwrightBuild status when TektonConfig is not ready")
141+
}
142+
return false, nil
143+
}
144+
107145
// Reconcile performs the resource reconciliation steps to deploy or remove Shipwright Build
108146
// instances. When deletion-timestamp is found, the removal of the previously deploy resources is
109147
// executed, otherwise the regular deploy workflow takes place.
@@ -143,6 +181,15 @@ func (r *ShipwrightBuildReconciler) Reconcile(ctx context.Context, req ctrl.Requ
143181
}
144182
}
145183

184+
// Requeue incase TektonConfig is not ready
185+
ready, err := r.fetchAndCheckTektonConfig(ctx, logger, b)
186+
if err != nil {
187+
return RequeueWithError(err)
188+
}
189+
if !ready {
190+
return Requeue()
191+
}
192+
146193
// selecting the target namespace based on the CRD information, when not informed using the
147194
// default namespace instead
148195
targetNamespace := b.Spec.TargetNamespace

controllers/shipwrightbuild_controller_test.go

Lines changed: 124 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,23 +9,25 @@ import (
99

1010
o "github.com/onsi/gomega"
1111

12+
buildv1alpha1 "github.com/shipwright-io/build/pkg/apis/build/v1alpha1"
13+
"github.com/shipwright-io/operator/api/v1alpha1"
14+
tektonoperatorv1alpha1 "github.com/tektoncd/operator/pkg/apis/operator/v1alpha1"
15+
tektonoperatorv1alpha1client "github.com/tektoncd/operator/pkg/client/clientset/versioned/fake"
1216
appsv1 "k8s.io/api/apps/v1"
1317
corev1 "k8s.io/api/core/v1"
1418
rbacv1 "k8s.io/api/rbac/v1"
19+
crdv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
1520
crdclientv1 "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/fake"
1621
"k8s.io/apimachinery/pkg/api/errors"
1722
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1823
"k8s.io/apimachinery/pkg/runtime"
1924
"k8s.io/apimachinery/pkg/types"
25+
"knative.dev/pkg/apis"
26+
duckv1 "knative.dev/pkg/apis/duck/v1"
2027
"sigs.k8s.io/controller-runtime/pkg/client"
2128
"sigs.k8s.io/controller-runtime/pkg/client/fake"
2229
"sigs.k8s.io/controller-runtime/pkg/log/zap"
2330
"sigs.k8s.io/controller-runtime/pkg/reconcile"
24-
25-
"github.com/shipwright-io/operator/api/v1alpha1"
26-
tektonoperatorv1alpha1 "github.com/tektoncd/operator/pkg/apis/operator/v1alpha1"
27-
tektonoperatorv1alpha1client "github.com/tektoncd/operator/pkg/client/clientset/versioned/fake"
28-
crdv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
2931
)
3032

3133
// bootstrapShipwrightBuildReconciler start up a new instance of ShipwrightBuildReconciler which is
@@ -44,10 +46,12 @@ func bootstrapShipwrightBuildReconciler(
4446
s.AddKnownTypes(v1alpha1.GroupVersion, &v1alpha1.ShipwrightBuild{})
4547
s.AddKnownTypes(rbacv1.SchemeGroupVersion, &rbacv1.ClusterRoleBinding{})
4648
s.AddKnownTypes(rbacv1.SchemeGroupVersion, &rbacv1.ClusterRole{})
49+
s.AddKnownTypes(tektonoperatorv1alpha1.SchemeGroupVersion, &tektonoperatorv1alpha1.TektonConfig{})
50+
s.AddKnownTypes(buildv1alpha1.SchemeGroupVersion, &buildv1alpha1.ClusterBuildStrategy{})
4751

4852
logger := zap.New()
4953

50-
c := fake.NewClientBuilder().WithScheme(s).WithObjects(b).Build()
54+
c := fake.NewClientBuilder().WithScheme(s).WithObjects(b).WithStatusSubresource(&v1alpha1.ShipwrightBuild{}).Build()
5155
var crdClient *crdclientv1.Clientset
5256
var toClient *tektonoperatorv1alpha1client.Clientset
5357
if len(tcrds) > 0 {
@@ -245,3 +249,117 @@ func TestShipwrightBuildReconciler_Reconcile(t *testing.T) {
245249
})
246250
}
247251
}
252+
253+
func TestShipwrightBuildReconciler_OperandReadiness(t *testing.T) {
254+
g := o.NewGomegaWithT(t)
255+
256+
// ShipwrightBuild object
257+
b := &v1alpha1.ShipwrightBuild{
258+
ObjectMeta: metav1.ObjectMeta{
259+
Name: "name",
260+
Namespace: "default",
261+
},
262+
Spec: v1alpha1.ShipwrightBuildSpec{
263+
TargetNamespace: "namespace",
264+
},
265+
Status: v1alpha1.ShipwrightBuildStatus{
266+
Conditions: []metav1.Condition{
267+
{
268+
Type: ConditionReady,
269+
Status: metav1.ConditionTrue,
270+
},
271+
},
272+
},
273+
}
274+
275+
// Mock TektonConfig to simulate Tekton Operator installation
276+
tektonConfig := &tektonoperatorv1alpha1.TektonConfig{
277+
ObjectMeta: metav1.ObjectMeta{
278+
Name: "config",
279+
},
280+
Status: tektonoperatorv1alpha1.TektonConfigStatus{
281+
Status: duckv1.Status{
282+
Conditions: duckv1.Conditions{
283+
{
284+
Type: ConditionReady,
285+
Status: corev1.ConditionFalse,
286+
Reason: "Installed",
287+
Message: "TektonConfig is not ready",
288+
},
289+
},
290+
},
291+
},
292+
}
293+
// Preload the fake client with a valid ClusterBuildStrategy to pass buildstrategy reconciliation
294+
cbs := &buildv1alpha1.ClusterBuildStrategy{
295+
ObjectMeta: metav1.ObjectMeta{
296+
Name: "buildah",
297+
},
298+
Spec: buildv1alpha1.BuildStrategySpec{
299+
BuildSteps: []buildv1alpha1.BuildStep{
300+
{
301+
Container: corev1.Container{
302+
Name: "build-step",
303+
Image: "quay.io/buildah/stable",
304+
},
305+
},
306+
},
307+
},
308+
}
309+
310+
// Prepare cr
311+
crd1 := &crdv1.CustomResourceDefinition{}
312+
crd1.Name = "taskruns.tekton.dev"
313+
crd2 := &crdv1.CustomResourceDefinition{}
314+
crd2.Name = "tektonconfigs.operator.tekton.dev"
315+
crd2.Labels = map[string]string{"operator.tekton.dev/release": common.TektonOpMinSupportedVersion}
316+
crd3 := &crdv1.CustomResourceDefinition{}
317+
crd3.Name = "clusterbuildstrategies.shipwright.io"
318+
crds := []*crdv1.CustomResourceDefinition{crd1, crd2, crd3}
319+
320+
// Bootstrap the reconciler with the mock objects
321+
c, _, _, r := bootstrapShipwrightBuildReconciler(t, b, nil, crds)
322+
323+
// Inject a pre-created valid tektonconfig
324+
_, err := r.TektonOperatorClient.TektonConfigs().Create(context.TODO(), tektonConfig, metav1.CreateOptions{})
325+
g.Expect(err).To(o.BeNil())
326+
327+
// Verify creation of tektonconfig
328+
cfg, err := r.TektonOperatorClient.TektonConfigs().Get(context.TODO(), "config", metav1.GetOptions{})
329+
g.Expect(err).To(o.BeNil())
330+
g.Expect(cfg.Name).To(o.Equal("config"))
331+
g.Expect(cfg.Status.Conditions[0].Type).To(o.Equal(apis.ConditionReady))
332+
333+
// Simulate reconciliation
334+
namespacedName := types.NamespacedName{Namespace: "default", Name: "name"}
335+
req := reconcile.Request{NamespacedName: namespacedName}
336+
res, err := r.Reconcile(context.TODO(), req)
337+
g.Expect(err).To(o.BeNil())
338+
g.Expect(res.Requeue).To(o.BeTrue(), "Reconciliation should requeue when TektonConfig is not ready")
339+
340+
// Verify that the ShipwrightBuild is marked as not ready
341+
updated := &v1alpha1.ShipwrightBuild{}
342+
err = c.Get(context.TODO(), req.NamespacedName, updated)
343+
g.Expect(err).To(o.BeNil())
344+
g.Expect(updated.Status.IsReady()).To(o.BeFalse(), "ShipwrightBuild should not be ready when TektonConfig is not ready")
345+
346+
// Simulate TektonConfig becoming ready
347+
tektonConfig.Status.Conditions[0].Status = corev1.ConditionTrue
348+
tektonConfig.Status.Conditions[0].Reason = "Installed"
349+
tektonConfig.Status.Conditions[0].Message = "TektonConfig is now ready"
350+
_, err = r.TektonOperatorClient.TektonConfigs().Update(context.TODO(), tektonConfig, metav1.UpdateOptions{})
351+
g.Expect(err).To(o.BeNil())
352+
353+
// Inject a pre-created valid ClusterBuildStrategy
354+
err = c.Create(context.TODO(), cbs)
355+
g.Expect(err).To(o.BeNil())
356+
// Trigger reconciliation again
357+
res, err = r.Reconcile(context.TODO(), req)
358+
g.Expect(err).To(o.BeNil())
359+
g.Expect(res.Requeue).To(o.BeFalse(), "Should not requeue after TektonConfig is ready")
360+
361+
// Fetch and verify ShipwrightBuild is now ready
362+
err = c.Get(context.TODO(), req.NamespacedName, updated)
363+
g.Expect(err).To(o.BeNil())
364+
g.Expect(updated.Status.IsReady()).To(o.BeTrue(), "ShipwrightBuild should be ready when TektonConfig is ready")
365+
}

0 commit comments

Comments
 (0)