Skip to content

Commit 2a552ce

Browse files
committed
Refactor Build Controller logic
Adding support for secrets differenciation errors in a Build Definition of one-word camelCase constants for Build validations Adding support for Status.Reason and Status.Message fields Ensure usage of Build constants to populate the Status.Reason Ensure appropiate message for the Status.Message Signed-off-by: Zoe <[email protected]>
1 parent c3f56c5 commit 2a552ce

File tree

3 files changed

+113
-115
lines changed

3 files changed

+113
-115
lines changed

pkg/apis/build/v1alpha1/build_types.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,35 @@ import (
99
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1010
)
1111

12+
// BuildReason is a type used for populating the
13+
// Build Status.Reason field
14+
type BuildReason string
15+
16+
const (
17+
// SucceedStatus indicates that all validations Succeeded
18+
SucceedStatus BuildReason = "Succeeded"
19+
// BuildStrategyNotFound indicates that a namespaced-scope strategy was not found in the namespace
20+
BuildStrategyNotFound BuildReason = "BuildStrategyNotFound"
21+
// ClusterBuildStrategyNotFound indicates that a cluster-scope strategy was not found
22+
ClusterBuildStrategyNotFound BuildReason = "ClusterBuildStrategyNotFound"
23+
// SetOwnerReferenceFailed indicates that setting ownerReferences between a Build and a BuildRun failed
24+
SetOwnerReferenceFailed BuildReason = "SetOwnerReferenceFailed"
25+
// SpecSourceSecretRefNotFound indicates the referenced secret in source is missing
26+
SpecSourceSecretRefNotFound BuildReason = "SpecSourceSecretRefNotFound"
27+
// SpecOutputSecretRefNotFound indicates the referenced secret in output is missing
28+
SpecOutputSecretRefNotFound BuildReason = "SpecOutputSecretRefNotFound"
29+
// SpecRuntimeSecretRefNotFound indicates the referenced secret in runtime is missing
30+
SpecRuntimeSecretRefNotFound BuildReason = "SpecRuntimeSecretRefNotFound"
31+
// MultipleSecretRefNotFound indicates that multiple secrets are missing
32+
MultipleSecretRefNotFound BuildReason = "MultipleSecretRefNotFound"
33+
// RuntimePathsCanNotBeEmpty indicates that the spec.runtime feature is used but the paths were not specified
34+
RuntimePathsCanNotBeEmpty BuildReason = "RuntimePathsCanNotBeEmpty"
35+
// RemoteRepositoryUnreachable indicates the referenced repository is unreachable
36+
RemoteRepositoryUnreachable BuildReason = "RemoteRepositoryUnreachable"
37+
// AllValidationsSucceeded indicates a Build was successfully validated
38+
AllValidationsSucceeded = "all validations succeeded"
39+
)
40+
1241
const (
1342
// BuildDomain is the domain used for all labels and annotations for this resource
1443
BuildDomain = "build.build.dev"

pkg/controller/build/build_controller.go

Lines changed: 82 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@ import (
1010
"reflect"
1111
"strings"
1212

13-
"github.com/pkg/errors"
14-
1513
corev1 "k8s.io/api/core/v1"
1614
apierrors "k8s.io/apimachinery/pkg/api/errors"
1715
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -36,10 +34,10 @@ import (
3634
buildmetrics "github.com/shipwright-io/build/pkg/metrics"
3735
)
3836

39-
// succeedStatus default status for the Build CRD
40-
const succeedStatus string = "Succeeded"
41-
const namespace string = "namespace"
42-
const name string = "name"
37+
const (
38+
namespace string = "namespace"
39+
name string = "name"
40+
)
4341

4442
type setOwnerReferenceFunc func(owner, object metav1.Object, scheme *runtime.Scheme) error
4543

@@ -235,67 +233,60 @@ func (r *ReconcileBuild) Reconcile(request reconcile.Request) (reconcile.Result,
235233

236234
// Populate the status struct with default values
237235
b.Status.Registered = corev1.ConditionFalse
238-
b.Status.Reason = succeedStatus
236+
b.Status.Reason = build.SucceedStatus
239237

240238
// Validate if remote repository exists
241239
if b.Spec.Source.SecretRef == nil {
242240
if err := r.validateSourceURL(ctx, b, b.Spec.Source.URL); err != nil {
243-
b.Status.Reason = err.Error()
244-
if updateErr := r.client.Status().Update(ctx, b); updateErr != nil {
245-
return reconcile.Result{}, updateErr
246-
}
247-
return reconcile.Result{}, nil
241+
MarkBuildStatus(b, build.RemoteRepositoryUnreachable, err.Error())
242+
return r.UpdateBuildStatusAndRetreat(ctx, b)
248243
}
249244
}
250245

251246
// Validate if the referenced secrets exist in the namespace
252-
var secretNames []string
247+
secretRefMap := map[string]build.BuildReason{}
253248
if b.Spec.Output.SecretRef != nil && b.Spec.Output.SecretRef.Name != "" {
254-
secretNames = append(secretNames, b.Spec.Output.SecretRef.Name)
249+
secretRefMap[b.Spec.Output.SecretRef.Name] = build.SpecOutputSecretRefNotFound
255250
}
256251
if b.Spec.Source.SecretRef != nil && b.Spec.Source.SecretRef.Name != "" {
257-
secretNames = append(secretNames, b.Spec.Source.SecretRef.Name)
252+
secretRefMap[b.Spec.Source.SecretRef.Name] = build.SpecSourceSecretRefNotFound
258253
}
259254
if b.Spec.BuilderImage != nil && b.Spec.BuilderImage.SecretRef != nil && b.Spec.BuilderImage.SecretRef.Name != "" {
260-
secretNames = append(secretNames, b.Spec.BuilderImage.SecretRef.Name)
255+
secretRefMap[b.Spec.BuilderImage.SecretRef.Name] = build.SpecRuntimeSecretRefNotFound
261256
}
262257

263-
if len(secretNames) > 0 {
264-
if err := r.validateSecrets(ctx, secretNames, b.Namespace); err != nil {
265-
b.Status.Reason = err.Error()
266-
if updateErr := r.client.Status().Update(ctx, b); updateErr != nil {
267-
// return an error in case of transient failure, and expect the next
268-
// reconciliation to be able to update the Status of the object
269-
return reconcile.Result{}, fmt.Errorf("errors: %v %v", err, updateErr)
270-
}
271-
// The Secret Resource watcher will Reconcile again once the missing
272-
// secret is created, therefore no need to return an error and enter on an infinite
273-
// reconciliation
274-
return reconcile.Result{}, nil
258+
// Validate if the referenced secrets exist
259+
if len(secretRefMap) > 0 {
260+
if err := r.validateSecrets(ctx, secretRefMap, b); err != nil {
261+
return reconcile.Result{}, err
262+
}
263+
264+
if b.Status.Reason != build.SucceedStatus {
265+
return r.UpdateBuildStatusAndRetreat(ctx, b)
275266
}
276267
}
277268

278-
// Validate if the build strategy is defined
269+
// Validate if the referenced strategy exists
279270
if b.Spec.StrategyRef != nil {
280-
if err := r.validateStrategyRef(ctx, b.Spec.StrategyRef, b.Namespace); err != nil {
281-
b.Status.Reason = err.Error()
282-
updateErr := r.client.Status().Update(ctx, b)
283-
return reconcile.Result{}, fmt.Errorf("errors: %v %v", err, updateErr)
271+
if err := r.validateStrategyRef(ctx, b); err != nil {
272+
return reconcile.Result{}, err
273+
}
274+
275+
if b.Status.Reason != build.SucceedStatus {
276+
return r.UpdateBuildStatusAndRetreat(ctx, b)
284277
}
285278
ctxlog.Info(ctx, "buildStrategy found", namespace, b.Namespace, name, b.Name, "strategy", b.Spec.StrategyRef.Name)
286279
}
287280

288-
// validate if "spec.runtime" attributes are valid
281+
// Validate "spec.runtime" attributes
289282
if utils.IsRuntimeDefined(b) {
290-
if err := r.validateRuntime(b.Spec.Runtime); err != nil {
291-
ctxlog.Error(ctx, err, "failed validating runtime attributes", "Build", b.Name)
292-
b.Status.Reason = err.Error()
293-
updateErr := r.client.Status().Update(ctx, b)
294-
return reconcile.Result{}, fmt.Errorf("errors: %v %v", err, updateErr)
283+
if r.validateRuntimeFailed(b) {
284+
return r.UpdateBuildStatusAndRetreat(ctx, b)
295285
}
296286
}
297287

298288
b.Status.Registered = corev1.ConditionTrue
289+
b.Status.Message = build.AllValidationsSucceeded
299290
err = r.client.Status().Update(ctx, b)
300291
if err != nil {
301292
return reconcile.Result{}, err
@@ -308,114 +299,85 @@ func (r *ReconcileBuild) Reconcile(request reconcile.Request) (reconcile.Result,
308299
return reconcile.Result{}, nil
309300
}
310301

311-
func (r *ReconcileBuild) validateRuntime(runtime *build.Runtime) error {
312-
if len(runtime.Paths) == 0 {
313-
return fmt.Errorf("the property 'spec.runtime.paths' must not be empty")
302+
// UpdateBuildStatusAndRetreat returns an error if an update fails, this should force
303+
// a new reconcile until the API call succeeds. If return is nil, no further reconciliations
304+
// will take place
305+
func (r *ReconcileBuild) UpdateBuildStatusAndRetreat(ctx context.Context, b *build.Build) (reconcile.Result, error) {
306+
if err := r.client.Status().Update(ctx, b); err != nil {
307+
return reconcile.Result{}, err
314308
}
315-
return nil
309+
return reconcile.Result{}, nil
316310
}
317311

318-
func (r *ReconcileBuild) validateStrategyRef(ctx context.Context, s *build.StrategyRef, ns string) error {
319-
if s.Kind != nil {
320-
switch *s.Kind {
312+
func (r *ReconcileBuild) validateRuntimeFailed(b *build.Build) bool {
313+
if len(b.Spec.Runtime.Paths) == 0 {
314+
MarkBuildStatus(b, build.RuntimePathsCanNotBeEmpty, "the property 'spec.runtime.paths' must not be empty")
315+
return true
316+
}
317+
return false
318+
}
319+
320+
func (r *ReconcileBuild) validateStrategyRef(ctx context.Context, b *build.Build) error {
321+
322+
if b.Spec.StrategyRef.Kind != nil {
323+
switch *b.Spec.StrategyRef.Kind {
321324
case build.NamespacedBuildStrategyKind:
322-
if err := r.validateBuildStrategy(ctx, s.Name, ns); err != nil {
325+
if err := r.validateBuildStrategy(ctx, b.Spec.StrategyRef.Name, b); err != nil {
323326
return err
324327
}
325328
case build.ClusterBuildStrategyKind:
326-
if err := r.validateClusterBuildStrategy(ctx, s.Name); err != nil {
329+
if err := r.validateClusterBuildStrategy(ctx, b.Spec.StrategyRef.Name, b); err != nil {
327330
return err
328331
}
329332
default:
330-
return fmt.Errorf("unknown strategy %v", *s.Kind)
333+
return fmt.Errorf("unknown strategy kind: %v", *b.Spec.StrategyRef.Kind)
331334
}
332335
} else {
333336
ctxlog.Info(ctx, "buildStrategy kind is nil, use default NamespacedBuildStrategyKind")
334-
if err := r.validateBuildStrategy(ctx, s.Name, ns); err != nil {
337+
if err := r.validateBuildStrategy(ctx, b.Spec.StrategyRef.Name, b); err != nil {
335338
return err
336339
}
337340
}
341+
338342
return nil
339343
}
340344

341-
func (r *ReconcileBuild) validateBuildStrategy(ctx context.Context, n string, ns string) error {
342-
list := &build.BuildStrategyList{}
343-
344-
if err := r.client.List(ctx, list, &client.ListOptions{Namespace: ns}); err != nil {
345-
return errors.Wrapf(err, "listing BuildStrategies in ns %s failed", ns)
346-
}
347-
348-
if len(list.Items) == 0 {
349-
return errors.Errorf("none BuildStrategies found in namespace %s", ns)
345+
func (r *ReconcileBuild) validateBuildStrategy(ctx context.Context, strategyName string, b *build.Build) error {
346+
buildStrategy := &build.BuildStrategy{}
347+
if err := r.client.Get(ctx, types.NamespacedName{Name: strategyName, Namespace: b.Namespace}, buildStrategy); err != nil && !apierrors.IsNotFound(err) {
348+
return err
349+
} else if apierrors.IsNotFound(err) {
350+
MarkBuildStatus(b, build.BuildStrategyNotFound, fmt.Sprintf("buildStrategy %s does not exist in namespace %s", b.Spec.StrategyRef.Name, b.Namespace))
350351
}
351352

352-
if len(list.Items) > 0 {
353-
for _, s := range list.Items {
354-
if s.Name == n {
355-
return nil
356-
}
357-
}
358-
return fmt.Errorf("buildStrategy %s does not exist in namespace %s", n, ns)
359-
}
360353
return nil
361354
}
362355

363-
func (r *ReconcileBuild) validateClusterBuildStrategy(ctx context.Context, n string) error {
364-
list := &build.ClusterBuildStrategyList{}
365-
366-
if err := r.client.List(ctx, list); err != nil {
367-
return errors.Wrapf(err, "listing ClusterBuildStrategies failed")
368-
}
369-
370-
if len(list.Items) == 0 {
371-
return errors.Errorf("no ClusterBuildStrategies found")
372-
}
373-
374-
if len(list.Items) > 0 {
375-
for _, s := range list.Items {
376-
if s.Name == n {
377-
return nil
378-
}
379-
}
380-
return fmt.Errorf("clusterBuildStrategy %s does not exist", n)
356+
func (r *ReconcileBuild) validateClusterBuildStrategy(ctx context.Context, strategyName string, b *build.Build) error {
357+
clusterBuildStrategy := &build.ClusterBuildStrategy{}
358+
if err := r.client.Get(ctx, types.NamespacedName{Name: strategyName}, clusterBuildStrategy); err != nil && !apierrors.IsNotFound(err) {
359+
return err
360+
} else if apierrors.IsNotFound(err) {
361+
MarkBuildStatus(b, build.ClusterBuildStrategyNotFound, fmt.Sprintf("clusterBuildStrategy %s does not exist", b.Spec.StrategyRef.Name))
381362
}
382363
return nil
383364
}
384-
func (r *ReconcileBuild) validateSecrets(ctx context.Context, secretNames []string, ns string) error {
385-
list := &corev1.SecretList{}
386-
387-
if err := r.client.List(
388-
ctx,
389-
list,
390-
&client.ListOptions{
391-
Namespace: ns,
392-
},
393-
); err != nil {
394-
return errors.Wrapf(err, "listing secrets in namespace %s failed", ns)
395-
}
396365

397-
if len(list.Items) == 0 {
398-
return errors.Errorf("there are no secrets in namespace %s", ns)
399-
}
366+
func (r *ReconcileBuild) validateSecrets(ctx context.Context, secretNames map[string]build.BuildReason, b *build.Build) error {
400367

401-
var lookUp = map[string]bool{}
402-
for _, secretName := range secretNames {
403-
lookUp[secretName] = false
404-
}
405-
for _, secret := range list.Items {
406-
lookUp[secret.Name] = true
407-
}
408368
var missingSecrets []string
409-
for name, found := range lookUp {
410-
if !found {
411-
missingSecrets = append(missingSecrets, name)
369+
secret := &corev1.Secret{}
370+
for refSecret, secretType := range secretNames {
371+
if err := r.client.Get(ctx, types.NamespacedName{Name: refSecret, Namespace: b.Namespace}, secret); err != nil && !apierrors.IsNotFound(err) {
372+
return err
373+
} else if apierrors.IsNotFound(err) {
374+
MarkBuildStatus(b, secretType, fmt.Sprintf("referenced secret %s not found", refSecret))
375+
missingSecrets = append(missingSecrets, refSecret)
412376
}
413377
}
414378

415379
if len(missingSecrets) > 1 {
416-
return fmt.Errorf("secrets %s do not exist", strings.Join(missingSecrets, ", "))
417-
} else if len(missingSecrets) > 0 {
418-
return fmt.Errorf("secret %s does not exist", missingSecrets[0])
380+
MarkBuildStatus(b, build.MultipleSecretRefNotFound, fmt.Sprintf("missing secrets are %s", strings.Join(missingSecrets, ",")))
419381
}
420382

421383
return nil
@@ -436,7 +398,7 @@ func (r *ReconcileBuild) validateBuildRunOwnerReferences(ctx context.Context, b
436398
for _, buildRun := range buildRunList.Items {
437399
if index := r.validateBuildOwnerReference(buildRun.OwnerReferences, b); index == -1 {
438400
if err := r.setOwnerReferenceFunc(b, &buildRun, r.scheme); err != nil {
439-
b.Status.Reason = fmt.Sprintf("unexpected error when trying to set the ownerreference: %v", err)
401+
MarkBuildStatus(b, build.SetOwnerReferenceFailed, fmt.Sprintf("unexpected error when trying to set the ownerreference: %v", err))
440402
if err := r.client.Status().Update(ctx, b); err != nil {
441403
return err
442404
}
@@ -520,3 +482,9 @@ func buildSecretRefAnnotationExist(annotation map[string]string) (string, bool)
520482
}
521483
return "", false
522484
}
485+
486+
// MarkBuildStatus sets the Build Status fields
487+
func MarkBuildStatus(b *build.Build, reason build.BuildReason, msg string) {
488+
b.Status.Reason = reason
489+
b.Status.Message = msg
490+
}

pkg/controller/buildrun/buildrun_controller.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,8 @@ func (r *ReconcileBuildRun) Reconcile(request reconcile.Request) (reconcile.Resu
285285
// Set OwnerReference for Build and BuildRun only when build.build.dev/build-run-deletion is set "true"
286286
if build.GetAnnotations()[buildv1alpha1.AnnotationBuildRunDeletion] == "true" && !isOwnedByBuild(build, buildRun.OwnerReferences) {
287287
if err := r.setOwnerReferenceFunc(build, buildRun, r.scheme); err != nil {
288-
build.Status.Reason = fmt.Sprintf("unexpected error when trying to set the ownerreference: %v", err)
288+
build.Status.Reason = buildv1alpha1.SetOwnerReferenceFailed
289+
build.Status.Message = fmt.Sprintf("unexpected error when trying to set the ownerreference: %v", err)
289290
if err := r.client.Status().Update(ctx, build); err != nil {
290291
return reconcile.Result{}, err
291292
}

0 commit comments

Comments
 (0)