Repository: shipwright-io/build
Pull request #595 (2021-02-17 10:43:18)

Restructure buildrun code into separate resources 
# Changes

This is a rebase on top of #593 . This PR is doing the following:

- [1] Separate controller/reconcile logic. This ensures that between controller definition and reconcile logic, we have a clear separation.
- [2] Refactor buildRun reconciler class. Move some functions into new or existing classes under the internal resource package. This class was one of the most hardest files to read. So the goal here is to make it easier for future contributors.
- [3] Ensure proper code coverage. Because I externalize a lot of the functions in [2], into the internal `resource` pkg, I´m adding a bunch of new unit-tests to increase the coverage of those functions.


# Submitter Checklist

- [ ] Includes tests if functionality changed/was added
- [ ] Includes docs if changes are user-facing
- [x] Release notes block has been filled in, or marked NONE


# Release Notes

```release-note
Restructure BuildRun reconcile code into separate classes.
```



openshift-ci-robot: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by:
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please assign **qu1queee** after the PR has been reviewed.
You can assign the PR to them by writing `/assign @qu1queee` in a comment when ready.

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=shipwright-io%2Fbuild).

<details open>
Needs approval from an approver in each of these files:

- **[OWNERS](https://github.com/shipwright-io/build/blob/master/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["qu1queee"]} -->
openshift-ci[bot]: @qu1queee: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
ci/prow/4.4-unit | 715302da322764514c15dcc33d38962022e9250f | [link](https://prow.ci.openshift.org/view/gs/origin-ci-test/pr-logs/pull/shipwright-io_build/595/pull-ci-shipwright-io-build-master-4.4-unit/1362001177641750528) | `/test 4.4-unit`

[Full PR test history](https://prow.ci.openshift.org/pr-history?org=shipwright-io&repo=build&pr=595). [Your PR dashboard](https://prow.ci.openshift.org/pr?query=is:pr+state:open+author:qu1queee).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
openshift-ci[bot]: @qu1queee: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
ci/prow/4.4-unit | 38fcdb4ac4511b688e5e29ad05839434aaa46b1f | [link](https://prow.ci.openshift.org/view/gs/origin-ci-test/pr-logs/pull/shipwright-io_build/595/pull-ci-shipwright-io-build-master-4.4-unit/1362366891158409216) | `/test 4.4-unit`

[Full PR test history](https://prow.ci.openshift.org/pr-history?org=shipwright-io&repo=build&pr=595). [Your PR dashboard](https://prow.ci.openshift.org/pr?query=is:pr+state:open+author:qu1queee).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->

Patch:
diff --git a/Makefile b/Makefile
index 7dd03142..b5f2f649 100644
--- a/Makefile
+++ b/Makefile
@@ -20,7 +20,7 @@ GOBIN=$(shell go env GOBIN)
 endif
 
 # configure zap based logr
-ZAP_FLAGS ?= --zap-log-level=debug --zap-encoder=console
+ZAP_FLAGS ?= --zap-level=debug --zap-encoder=console
 # extra flags passed to operator-sdk
 OPERATOR_SDK_EXTRA_ARGS ?= --debug
 
@@ -172,7 +172,7 @@ golint:
 .PHONY: misspell
 misspell:
 	@echo "Checking misspell"
-	@find . -type f -not -path './vendor/*' -not -path './.git/*' -not -path './build/*' -print0 | xargs -0 misspell -source=text -error
+	@find . -type f | grep -v /vendor | xargs misspell -source=text -error
 
 # Install it via: go get -u honnef.co/go/tools/cmd/staticcheck
 .PHONY: staticcheck
diff --git a/README.md b/README.md
index 2a2f4346..61dafc01 100644
--- a/README.md
+++ b/README.md
@@ -33,7 +33,7 @@ as [Kaniko](https://github.com/GoogleContainerTools/kaniko),
 
 ## Build Strategies
 
-The following [Build Strategies](docs/buildstrategies.md) are installed by default:
+The following are the build strategies supported by this operator, out-of-the-box:
 
 * [Source-to-Image](docs/buildstrategies.md#source-to-image)
 * [Buildpacks-v3](docs/buildstrategies.md#buildpacks-v3)
@@ -42,9 +42,9 @@ The following [Build Strategies](docs/buildstrategies.md) are installed by defau
 
 Users have the option to define their own `BuildStrategy` or `ClusterBuildStrategy` resources and make them available for consumption via the `Build` resource.
 
-## Custom Resources
+## Operator Resources
 
-Shipwright defines four CRDs:
+This operator ships four CRDs :
 
 * The `BuildStrategy` CRD and the `ClusterBuildStrategy` CRD is used to register a strategy.
 * The `Build` CRD is used to define a build configuration.
@@ -62,7 +62,7 @@ Shipwright defines four CRDs:
 
 ## Examples
 
-Examples of `Build` resource using the example strategies installed by default.
+Examples of `Build` resource using the example strategies shipped with this operator.
 
 * [`buildah`](samples/build/build_buildah_cr.yaml)
 * [`buildpacks-v3-heroku`](samples/build/build_buildpacks-v3-heroku_cr.yaml)
@@ -73,74 +73,72 @@ Examples of `Build` resource using the example strategies installed by default.
 ## Try it!
 
 * Get a [Kubernetes](https://kubernetes.io/) cluster and [`kubectl`](https://kubernetes.io/docs/reference/kubectl/overview/) set up to connect to your cluster.
-* Clone this repository from GitHub at the v0.3.0 tag:
+* Clone this repository from GitHub:
 
-  ```bash
-  $ git clone --branch v0.3.0 https://github.com/shipwright-io/build.git
-  ...
-  $ cd build/
-  ```
-
-  _Coming soon - install Shipwright Build via kubectl!_
+```bash
+$ git clone https://github.com/shipwright-io/build.git
+...
+$ cd build/
+```
 
 * Install [Tekton](https://cloud.google.com/tekton) by running [hack/install-tekton.sh](hack/install-tekton.sh), it installs v0.20.1.
 
-  ```bash
-  $ hack/install-tekton.sh
-  ```
+```bash
+$ hack/install-tekton.sh
+```
 
-* Install Shipwright and sample strategies via `make`:
+* Install the operator and sample strategies via `make`:
 
-  ```bash
-  $ make install
-  ```
+```bash
+$ make install
+```
 
 * Add a push secret to your container image repository, such as one on Docker Hub or quay.io:
 
-  ```yaml
-  $ kubectl create secret generic push-secret \
-  --from-file=.dockerconfigjson=$HOME/.docker/config.json \
-  --type=kubernetes.io/dockerconfigjson
-  ```
+```yaml
+$ kubectl create secret generic push-secret \
+--from-file=.dockerconfigjson=$HOME/.docker/config.json \
+--type=kubernetes.io/dockerconfigjson
+```
 
 * Create a [Cloud Native Buildpacks](samples/build/build_buildpacks_v3_cr.yaml) build, replacing
   `<MY_REGISTRY>/<MY_USERNAME>/<MY_REPO>` with the registry hostname, username, and repository your
   cluster has access to and that you have permission to push images to.
 
-  ```bash
-  $ kubectl apply -f - <<EOF
-  apiVersion: build.dev/v1alpha1
-  kind: Build
-  metadata:
-    name: buildpack-nodejs-build
-  spec:
-    source:
-      url: https://github.com/adambkaplan/shipwright-example-nodejs.git
-    strategy:
-      name: buildpacks-v3
-      kind: ClusterBuildStrategy
-    output:
-      image: <MY_REGISTRY>/<MY_USERNAME>/<MY_REPO>:latest
-      credentials:
-        name: push-secret
-  EOF
-  ```
+```bash
+$ kubectl apply -f - <<EOF
+apiVersion: build.dev/v1alpha1
+kind: Build
+metadata:
+  name: buildpack-nodejs-build
+spec:
+  source:
+    url: https://github.com/adambkaplan/shipwright-example-nodejs.git
+  strategy:
+    name: buildpacks-v3
+    kind: ClusterBuildStrategy
+  output:
+    image: <MY_REGISTRY>/<MY_USERNAME>/<MY_REPO>:latest
+    credentials:
+      name: push-secret
+EOF
+```
 
 * Run your build:
 
-  ```bash
-  $ kubectl apply -f - <<EOF
-  apiVersion: build.dev/v1alpha1
-  kind: BuildRun
-  metadata:
-    name: buildpack-nodejs-build-1
-  spec:
-    buildRef:
-      name: buildpack-nodejs-build
-    serviceAccount:
-      name: default
-  EOF
-  ```
+```bash
+$ kubectl apply -f - <<EOF
+apiVersion: build.dev/v1alpha1
+kind: BuildRun
+metadata:
+  name: buildpack-nodejs-build-1
+spec:
+  buildRef:
+    name: buildpack-nodejs-build
+  serviceAccount:
+    name: default
+EOF
+```
 
 ## Roadmap
 
@@ -179,3 +177,7 @@ We are so excited to have you!
   Click [here](https://lists.shipwright.io/admin/lists/shipwright-users.lists.shipwright.io/) to join.
   - Contributors can discuss active development topics at [shipwright-dev@lists.shipwright.io](https://lists.shipwright.io/archives/list/shipwright-dev@lists.shipwright.io/).
   Click [here](https://lists.shipwright.io/admin/lists/shipwright-dev.lists.shipwright.io/) to join.
+
+[corev1container]: https://github.com/kubernetes/api/blob/v0.17.3/core/v1/types.go#L2106
+[pipelinesoperator]: https://www.openshift.com/learn/topics/pipelines
+[operatorsdk]: https://github.com/operator-framework/operator-sdk
diff --git a/cmd/manager/main.go b/cmd/manager/main.go
index ba0ab904..5eef103a 100644
--- a/cmd/manager/main.go
+++ b/cmd/manager/main.go
@@ -16,6 +16,7 @@ import (
 	kubemetrics "github.com/operator-framework/operator-sdk/pkg/kube-metrics"
 
 	// Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)
+	"github.com/operator-framework/operator-sdk/pkg/log/zap"
 	"github.com/operator-framework/operator-sdk/pkg/metrics"
 	"github.com/operator-framework/operator-sdk/pkg/ready"
 	sdkVersion "github.com/operator-framework/operator-sdk/version"
@@ -54,7 +55,7 @@ func printVersion(ctx context.Context) {
 func main() {
 	// Add the zap logger flag set to the CLI. The flag set must
 	// be added before calling pflag.Parse().
-	pflag.CommandLine.AddGoFlagSet(ctxlog.CustomZapFlagSet())
+	pflag.CommandLine.AddFlagSet(zap.FlagSet())
 
 	// Add flags registered by imported packages (e.g. glog and
 	// controller-runtime)
diff --git a/go.mod b/go.mod
index 4787667c..5f78c3b4 100644
--- a/go.mod
+++ b/go.mod
@@ -14,7 +14,6 @@ require (
 	github.com/prometheus/client_model v0.2.0
 	github.com/spf13/pflag v1.0.5
 	github.com/tektoncd/pipeline v0.20.1
-	go.uber.org/zap v1.16.0
 	k8s.io/api v0.18.12
 	k8s.io/apimachinery v0.19.0
 	k8s.io/client-go v12.0.0+incompatible
diff --git a/pkg/ctxlog/log.go b/pkg/ctxlog/log.go
index 7ab39d96..ebfd78e0 100644
--- a/pkg/ctxlog/log.go
+++ b/pkg/ctxlog/log.go
@@ -6,54 +6,37 @@ package ctxlog
 
 import (
 	"context"
-	"flag"
 	"io"
 
 	"github.com/go-logr/logr"
-	"go.uber.org/zap/zapcore"
-	"sigs.k8s.io/controller-runtime/pkg/log/zap"
+	"github.com/operator-framework/operator-sdk/pkg/log/zap"
 
 	logf "sigs.k8s.io/controller-runtime/pkg/log"
 )
 
-var (
-	defaultLogLevel = zapcore.DebugLevel
-
-	flagOptions = &zap.Options{
-		Level: &defaultLogLevel,
-	}
-)
-
-// CustomZapFlagSet creates a flag.FlagSet containing the zap logger default flags to configure the zap options and it
-// includes a custom flag `--zap-level` for backwards compatibility reasons
-func CustomZapFlagSet() *flag.FlagSet {
-	f := flag.NewFlagSet("zap", flag.ExitOnError)
-	flagOptions.BindFlags(f)
-
-	// Add --zap-level for backwards compatibility and hard-wire it against the log level settings. Remove this line and
-	// the defaultLogLevel as soon as enough time has passed to fix the flag usage.
-	f.Var(&defaultLogLevel, "zap-level", "Deprecated: Please use --zap-log-level instead; set log level")
-
-	return f
-}
-
 // NewLogger returns a new Logger instance
-// by using the controller-runtime log/zap logging
+// by using the operator-sdk log/zap logging
 // implementation
 func NewLogger(name string) logr.Logger {
-	l := zap.New(zap.UseFlagOptions(flagOptions))
+	l := zap.Logger()
+
 	logf.SetLogger(l)
 
-	return l.WithName(name)
+	l = l.WithName(name)
+
+	return l
 }
 
 // NewLoggerTo returns a new Logger which logs
 // to a given destination.
 func NewLoggerTo(destWriter io.Writer, name string) logr.Logger {
-	l := zap.New(zap.UseFlagOptions(flagOptions), zap.WriteTo(destWriter))
+	l := zap.LoggerTo(destWriter)
+
 	logf.SetLogger(l)
 
-	return l.WithName(name)
+	l = l.WithName(name)
+
+	return l
 }
 
 // Error returns an ERROR level log from an specified context
diff --git a/pkg/reconciler/build/build.go b/pkg/reconciler/build/build.go
index 58f942c8..a6851156 100644
--- a/pkg/reconciler/build/build.go
+++ b/pkg/reconciler/build/build.go
@@ -9,14 +9,40 @@ import (
 
 	corev1 "k8s.io/api/core/v1"
 	apierrors "k8s.io/apimachinery/pkg/api/errors"
+	"k8s.io/apimachinery/pkg/runtime"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/manager"
 	"sigs.k8s.io/controller-runtime/pkg/reconcile"
 
 	build "github.com/shipwright-io/build/pkg/apis/build/v1alpha1"
+	"github.com/shipwright-io/build/pkg/config"
 	"github.com/shipwright-io/build/pkg/ctxlog"
 	buildmetrics "github.com/shipwright-io/build/pkg/metrics"
 	"github.com/shipwright-io/build/pkg/validate"
 )
 
+// ReconcileBuild reconciles a Build object
+type ReconcileBuild struct {
+	// This client, initialized using mgr.Client() above, is a split client
+	// that reads objects from the cache and writes to the apiserver
+	ctx                   context.Context
+	config                *config.Config
+	client                client.Client
+	scheme                *runtime.Scheme
+	setOwnerReferenceFunc setOwnerReferenceFunc
+}
+
+// NewReconciler returns a new reconcile.Reconciler
+func NewReconciler(ctx context.Context, c *config.Config, mgr manager.Manager, ownerRef setOwnerReferenceFunc) reconcile.Reconciler {
+	return &ReconcileBuild{
+		ctx:                   ctx,
+		config:                c,
+		client:                mgr.GetClient(),
+		scheme:                mgr.GetScheme(),
+		setOwnerReferenceFunc: ownerRef,
+	}
+}
+
 // Reconcile reads that state of the cluster for a Build object and makes changes based on the state read
 // and what is in the Build.Spec
 func (r *ReconcileBuild) Reconcile(request reconcile.Request) (reconcile.Result, error) {
diff --git a/pkg/reconciler/build/controller.go b/pkg/reconciler/build/controller.go
index c08932cb..68671733 100644
--- a/pkg/reconciler/build/controller.go
+++ b/pkg/reconciler/build/controller.go
@@ -41,17 +41,6 @@ func Add(ctx context.Context, c *config.Config, mgr manager.Manager) error {
 	return add(ctx, mgr, NewReconciler(ctx, c, mgr, controllerutil.SetControllerReference))
 }
 
-// NewReconciler returns a new reconcile.Reconciler
-func NewReconciler(ctx context.Context, c *config.Config, mgr manager.Manager, ownerRef setOwnerReferenceFunc) reconcile.Reconciler {
-	return &ReconcileBuild{
-		ctx:                   ctx,
-		config:                c,
-		client:                mgr.GetClient(),
-		scheme:                mgr.GetScheme(),
-		setOwnerReferenceFunc: ownerRef,
-	}
-}
-
 // add adds a new Controller to mgr with r as the reconcile.Reconciler
 func add(ctx context.Context, mgr manager.Manager, r reconcile.Reconciler) error {
 	// Create a new controller
@@ -185,17 +174,6 @@ func add(ctx context.Context, mgr manager.Manager, r reconcile.Reconciler) error
 	}, preSecret)
 }
 
-// ReconcileBuild reconciles a Build object
-type ReconcileBuild struct {
-	// This client, initialized using mgr.Client() above, is a split client
-	// that reads objects from the cache and writes to the apiserver
-	ctx                   context.Context
-	config                *config.Config
-	client                client.Client
-	scheme                *runtime.Scheme
-	setOwnerReferenceFunc setOwnerReferenceFunc
-}
-
 func buildSecretRefAnnotationExist(annotation map[string]string) (string, bool) {
 	if val, ok := annotation[build.AnnotationBuildRefSecret]; ok {
 		return val, true
diff --git a/pkg/reconciler/buildrun/buildrun.go b/pkg/reconciler/buildrun/buildrun.go
index c313f46b..d3095ed9 100644
--- a/pkg/reconciler/buildrun/buildrun.go
+++ b/pkg/reconciler/buildrun/buildrun.go
@@ -9,7 +9,6 @@ import (
 	"fmt"
 	"regexp"
 	"strconv"
-	"strings"
 
 	"github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1"
 	"knative.dev/pkg/apis"
@@ -17,11 +16,14 @@ import (
 	corev1 "k8s.io/api/core/v1"
 	apierrors "k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/apimachinery/pkg/types"
-	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/manager"
 	"sigs.k8s.io/controller-runtime/pkg/reconcile"
 
 	buildv1alpha1 "github.com/shipwright-io/build/pkg/apis/build/v1alpha1"
+	"github.com/shipwright-io/build/pkg/config"
 	"github.com/shipwright-io/build/pkg/ctxlog"
 	buildmetrics "github.com/shipwright-io/build/pkg/metrics"
 	"github.com/shipwright-io/build/pkg/reconciler/buildrun/resources"
@@ -30,10 +32,34 @@ import (
 const (
 	namespace          string = "namespace"
 	name               string = "name"
-	pendingReason      string = "Pending"
 	generatedNameRegex        = "-[a-z0-9]{5,5}$"
 )
 
+// blank assignment to verify that ReconcileBuildRun implements reconcile.Reconciler
+var _ reconcile.Reconciler = &ReconcileBuildRun{}
+
+// ReconcileBuildRun reconciles a BuildRun object
+type ReconcileBuildRun struct {
+	// This client, initialized using mgr.Client() above, is a split client
+	// that reads objects from the cache and writes to the apiserver
+	ctx                   context.Context
+	config                *config.Config
+	client                client.Client
+	scheme                *runtime.Scheme
+	setOwnerReferenceFunc setOwnerReferenceFunc
+}
+
+// NewReconciler returns a new reconcile.Reconciler
+func NewReconciler(ctx context.Context, c *config.Config, mgr manager.Manager, ownerRef setOwnerReferenceFunc) reconcile.Reconciler {
+	return &ReconcileBuildRun{
+		ctx:                   ctx,
+		config:                c,
+		client:                mgr.GetClient(),
+		scheme:                mgr.GetScheme(),
+		setOwnerReferenceFunc: ownerRef,
+	}
+}
+
 // Reconcile reads that state of the cluster for a Build object and makes changes based on the state read
 // and what is in the Build.Spec
 func (r *ReconcileBuildRun) Reconcile(request reconcile.Request) (reconcile.Result, error) {
@@ -66,9 +92,9 @@ func (r *ReconcileBuildRun) Reconcile(request reconcile.Request) (reconcile.Resu
 			}
 
 			build = &buildv1alpha1.Build{}
-			if err = r.GetBuildObject(ctx, buildRun.Spec.BuildRef.Name, buildRun.Namespace, build); err != nil {
+			if err = resources.GetBuildObject(ctx, r.client, buildRun.Spec.BuildRef.Name, buildRun.Namespace, build); err != nil {
 				updateErr := r.updateBuildRunErrorStatus(ctx, buildRun, err.Error())
-				return reconcile.Result{}, handleError("Failed to fetch the Build instance", err, updateErr)
+				return reconcile.Result{}, resources.HandleError("Failed to fetch the Build instance", err, updateErr)
 			}
 
 			// Validate if the Build was successfully registered
@@ -82,7 +108,7 @@ func (r *ReconcileBuildRun) Reconcile(request reconcile.Request) (reconcile.Resu
 			}
 
 			// Set OwnerReference for Build and BuildRun only when build.build.dev/build-run-deletion is set "true"
-			if build.GetAnnotations()[buildv1alpha1.AnnotationBuildRunDeletion] == "true" && !isOwnedByBuild(build, buildRun.OwnerReferences) {
+			if build.GetAnnotations()[buildv1alpha1.AnnotationBuildRunDeletion] == "true" && !resources.IsOwnedByBuild(build, buildRun.OwnerReferences) {
 				if err := r.setOwnerReferenceFunc(build, buildRun, r.scheme); err != nil {
 					build.Status.Reason = buildv1alpha1.SetOwnerReferenceFailed
 					build.Status.Message = fmt.Sprintf("unexpected error when trying to set the ownerreference: %v", err)
@@ -125,7 +151,7 @@ func (r *ReconcileBuildRun) Reconcile(request reconcile.Request) (reconcile.Resu
 			ctxlog.Info(ctx, "creating TaskRun from BuildRun", namespace, request.Namespace, name, generatedTaskRun.GenerateName, "BuildRun", buildRun.Name)
 			if err = r.client.Create(ctx, generatedTaskRun); err != nil {
 				updateErr := r.updateBuildRunErrorStatus(ctx, buildRun, err.Error())
-				return reconcile.Result{}, handleError("Failed to create TaskRun if no TaskRun for that BuildRun exists", err, updateErr)
+				return reconcile.Result{}, resources.HandleError("Failed to create TaskRun if no TaskRun for that BuildRun exists", err, updateErr)
 			}
 
 			// Set the LastTaskRunRef in the BuildRun status
@@ -173,16 +199,16 @@ func (r *ReconcileBuildRun) Reconcile(request reconcile.Request) (reconcile.Resu
 
 		trCondition := lastTaskRun.Status.GetCondition(apis.ConditionSucceeded)
 		if trCondition != nil {
-			if err := r.updateBuildRunUsingTaskRunCondition(ctx, buildRun, lastTaskRun, trCondition); err != nil {
+			if err := resources.UpdateBuildRunUsingTaskRunCondition(ctx, r.client, buildRun, lastTaskRun, trCondition); err != nil {
 				return reconcile.Result{}, err
 			}
 
 			taskRunStatus := trCondition.Status
 
-			// check if we should delete the generated service account by checking the build run spec and that the task run is complete
-			if isGeneratedServiceAccountUsed(buildRun) && (taskRunStatus == corev1.ConditionTrue || taskRunStatus == corev1.ConditionFalse) {
+			// check if we should delete the generated service account by checking the build run spec and that the task run is csomplete
+			if resources.IsGeneratedServiceAccountUsed(buildRun) && (taskRunStatus == corev1.ConditionTrue || taskRunStatus == corev1.ConditionFalse) {
 				serviceAccount := &corev1.ServiceAccount{}
-				serviceAccount.Name = getGeneratedServiceAccountName(buildRun)
+				serviceAccount.Name = resources.GetGeneratedServiceAccountName(buildRun)
 				serviceAccount.Namespace = buildRun.Namespace
 
 				ctxlog.Info(ctx, "deleting service account", namespace, request.Namespace, name, request.Name)
@@ -272,20 +298,6 @@ func (r *ReconcileBuildRun) Reconcile(request reconcile.Request) (reconcile.Resu
 	return reconcile.Result{}, nil
 }
 
-// ValidateBuildRegistration verifies that a referenced Build is properly registered
-func (r *ReconcileBuildRun) ValidateBuildRegistration(ctx context.Context, build *buildv1alpha1.Build, buildRun *buildv1alpha1.BuildRun) error {
-	if build.Status.Registered == "" {
-		err := fmt.Errorf("the Build is not yet validated, build: %s", build.Name)
-		return err
-	}
-	if build.Status.Registered != corev1.ConditionTrue {
-		err := fmt.Errorf("the Build is not registered correctly, build: %s, registered status: %s, reason: %s", build.Name, build.Status.Registered, build.Status.Reason)
-		updateErr := r.updateBuildRunErrorStatus(ctx, buildRun, err.Error())
-		return handleError("Build is not ready", err, updateErr)
-	}
-	return nil
-}
-
 // GetBuildRunObject retrieves an existing BuildRun based on a name and namespace
 func (r *ReconcileBuildRun) GetBuildRunObject(ctx context.Context, objectName string, objectNS string, buildRun *buildv1alpha1.BuildRun) error {
 	if err := r.client.Get(ctx, types.NamespacedName{Name: objectName, Namespace: objectNS}, buildRun); err != nil {
@@ -294,26 +306,6 @@ func (r *ReconcileBuildRun) GetBuildRunObject(ctx context.Context, objectName st
 	return nil
 }
 
-// GetBuildObject retrieves an existing Build based on a name and namespace
-func (r *ReconcileBuildRun) GetBuildObject(ctx context.Context, objectName string, objectNS string, build *buildv1alpha1.Build) error {
-	if err := r.client.Get(ctx, types.NamespacedName{Name: objectName, Namespace: objectNS}, build); err != nil {
-		return err
-	}
-	return nil
-}
-
-// This function only returns multiple errors if each error is not nil.
-// And its error message.
-func handleError(message string, listOfErrors ...error) error {
-	var errSlice []string
-	for _, e := range listOfErrors {
-		if e != nil {
-			errSlice = append(errSlice, e.Error())
-		}
-	}
-	return fmt.Errorf("errors: %s, msg: %s", strings.Join(errSlice, ", "), message)
-}
-
 // VerifyRequestName parse a Reconcile request name and looks for an associated BuildRun name
 // If the BuildRun object exists, it will update it with an error.
 func (r *ReconcileBuildRun) VerifyRequestName(ctx context.Context, request reconcile.Request, buildRun *buildv1alpha1.BuildRun) {
@@ -337,91 +329,17 @@ func (r *ReconcileBuildRun) VerifyRequestName(ctx context.Context, request recon
 	}
 }
 
-func (r *ReconcileBuildRun) updateBuildRunUsingTaskRunCondition(ctx context.Context, buildRun *buildv1alpha1.BuildRun, taskRun *v1beta1.TaskRun, trCondition *apis.Condition) error {
-	var reason, message string = trCondition.Reason, trCondition.Message
-
-	switch v1beta1.TaskRunReason(reason) {
-	case v1beta1.TaskRunReasonTimedOut:
-		reason = "BuildRunTimeout"
-		message = fmt.Sprintf("BuildRun %s failed to finish within %s",
-			taskRun.GetLabels()[buildv1alpha1.LabelBuildRun],
-			taskRun.Spec.Timeout.Duration,
-		)
-
-	case v1beta1.TaskRunReasonFailed:
-		if taskRun.Status.CompletionTime != nil {
-			var pod corev1.Pod
-			if err := r.client.Get(ctx, types.NamespacedName{Namespace: taskRun.Namespace, Name: taskRun.Status.PodName}, &pod); err != nil {
-				// when trying to customize the Condition Message field, ensure the Message cover the case
-				// when a Pod is deleted.
-				// Note: this is an edge case, but not doing this prevent a BuildRun from being marked as Failed
-				// while the TaskRun is already with a Failed Reason in it´s condition.
-				if apierrors.IsNotFound(err) {
-					message = fmt.Sprintf("buildrun failed, pod %s not found", taskRun.Status.PodName)
-					break
-				}
-				return err
-			}
-
-			buildRun.Status.FailedAt = &buildv1alpha1.FailedAt{Pod: pod.Name}
-
-			// Since the container status list is not sorted, as a quick workaround mark all failed containers
-			var failures = make(map[string]struct{})
-			for _, containerStatus := range pod.Status.ContainerStatuses {
-				if containerStatus.State.Terminated != nil && containerStatus.State.Terminated.ExitCode != 0 {
-					failures[containerStatus.Name] = struct{}{}
-				}
-			}
-
-			// Find the first container that failed
-			var failedContainer *corev1.Container
-			for i, container := range pod.Spec.Containers {
-				if _, has := failures[container.Name]; has {
-					failedContainer = &pod.Spec.Containers[i]
-					break
-				}
-			}
-
-			if failedContainer != nil {
-				buildRun.Status.FailedAt.Container = failedContainer.Name
-				message = fmt.Sprintf("buildrun step failed in pod %s, for detailed information: kubectl --namespace %s logs %s --container=%s",
-					pod.Name,
-					pod.Namespace,
-					pod.Name,
-					failedContainer.Name,
-				)
-			} else {
-				message = fmt.Sprintf("buildrun failed due to an unexpected error in pod %s: for detailed information: kubectl --namespace %s logs %s --all-containers",
-					pod.Name,
-					pod.Namespace,
-					pod.Name,
-				)
-			}
-		}
-	}
-
-	buildRun.Status.SetCondition(&buildv1alpha1.Condition{
-		LastTransitionTime: metav1.Now(),
-		Type:               buildv1alpha1.Succeeded,
-		Status:             trCondition.Status,
-		Reason:             reason,
-		Message:            message,
-	})
-
-	return nil
-}
-
 func (r *ReconcileBuildRun) createTaskRun(ctx context.Context, build *buildv1alpha1.Build, buildRun *buildv1alpha1.BuildRun) (*v1beta1.TaskRun, error) {
 	var generatedTaskRun *v1beta1.TaskRun
 	// Choose a service account to use
-	serviceAccount, err := r.retrieveServiceAccount(ctx, build, buildRun)
+	serviceAccount, err := resources.RetrieveServiceAccount(ctx, r.client, build, buildRun)
 	if err != nil {
 		updateErr := r.updateBuildRunErrorStatus(ctx, buildRun, err.Error())
-		return nil, handleError("Failed to choose a service account to use", err, updateErr)
+		return nil, resources.HandleError("Failed to choose a service account to use", err, updateErr)
 	}
 
 	if build.Spec.StrategyRef.Kind == nil || *build.Spec.StrategyRef.Kind == buildv1alpha1.NamespacedBuildStrategyKind {
-		buildStrategy, err := r.retrieveBuildStrategy(ctx, build)
+		buildStrategy, err := resources.RetrieveBuildStrategy(ctx, r.client, build)
 		if err != nil {
 			return nil, err
 		}
@@ -429,11 +347,11 @@ func (r *ReconcileBuildRun) createTaskRun(ctx context.Context, build *buildv1alp
 			generatedTaskRun, err = resources.GenerateTaskRun(r.config, build, buildRun, serviceAccount.Name, buildStrategy)
 			if err != nil {
 				updateErr := r.updateBuildRunErrorStatus(ctx, buildRun, err.Error())
-				return nil, handleError("Failed to generate the taskrun with buildStrategy", err, updateErr)
+				return nil, resources.HandleError("Failed to generate the taskrun with buildStrategy", err, updateErr)
 			}
 		}
 	} else if *build.Spec.StrategyRef.Kind == buildv1alpha1.ClusterBuildStrategyKind {
-		clusterBuildStrategy, err := r.retrieveClusterBuildStrategy(ctx, build)
+		clusterBuildStrategy, err := resources.RetrieveClusterBuildStrategy(ctx, r.client, build)
 		if err != nil {
 			return nil, err
 		}
@@ -441,106 +359,24 @@ func (r *ReconcileBuildRun) createTaskRun(ctx context.Context, build *buildv1alp
 			generatedTaskRun, err = resources.GenerateTaskRun(r.config, build, buildRun, serviceAccount.Name, clusterBuildStrategy)
 			if err != nil {
 				updateErr := r.updateBuildRunErrorStatus(ctx, buildRun, err.Error())
-				return nil, handleError("Failed to generate the taskrun with clusterBuildStrategy", err, updateErr)
+				return nil, resources.HandleError("Failed to generate the taskrun with clusterBuildStrategy", err, updateErr)
 			}
 		}
 	} else {
 		err := fmt.Errorf("unknown strategy %s", string(*build.Spec.StrategyRef.Kind))
 		updateErr := r.updateBuildRunErrorStatus(ctx, buildRun, err.Error())
-		return nil, handleError(fmt.Sprintf("Unsupported BuildStrategy Kind: %v", build.Spec.StrategyRef.Kind), err, updateErr)
+		return nil, resources.HandleError(fmt.Sprintf("Unsupported BuildStrategy Kind: %v", build.Spec.StrategyRef.Kind), err, updateErr)
 	}
 
 	// Set OwnerReference for BuildRun and TaskRun
 	if err := r.setOwnerReferenceFunc(buildRun, generatedTaskRun, r.scheme); err != nil {
 		updateErr := r.updateBuildRunErrorStatus(ctx, buildRun, err.Error())
-		return nil, handleError("failed to set OwnerReference for BuildRun and TaskRun", err, updateErr)
+		return nil, resources.HandleError("failed to set OwnerReference for BuildRun and TaskRun", err, updateErr)
 	}
 
 	return generatedTaskRun, nil
 }
 
-func (r *ReconcileBuildRun) retrieveServiceAccount(ctx context.Context, build *buildv1alpha1.Build, buildRun *buildv1alpha1.BuildRun) (*corev1.ServiceAccount, error) {
-	serviceAccount := &corev1.ServiceAccount{}
-
-	if isGeneratedServiceAccountUsed(buildRun) {
-		serviceAccountName := getGeneratedServiceAccountName(buildRun)
-
-		serviceAccount.Name = serviceAccountName
-		serviceAccount.Namespace = buildRun.Namespace
-
-		// Create the service account, use CreateOrUpdate as it might exist already from a previous reconciliation that
-		// succeeded to create the service account but failed to update the build run that references it
-		ctxlog.Info(ctx, "create or update serviceAccount for BuildRun", namespace, buildRun.Namespace, name, serviceAccountName, "BuildRun", buildRun.Name)
-		op, err := controllerutil.CreateOrUpdate(ctx, r.client, serviceAccount, func() error {
-			serviceAccount.SetLabels(map[string]string{buildv1alpha1.LabelBuildRun: buildRun.Name})
-
-			ownerReference := metav1.NewControllerRef(buildRun, buildv1alpha1.SchemeGroupVersion.WithKind("BuildRun"))
-			serviceAccount.SetOwnerReferences([]metav1.OwnerReference{*ownerReference})
-
-			resources.ApplyCredentials(ctx, build, serviceAccount)
-
-			return nil
-		})
-		if err != nil {
-			return nil, err
-		}
-		ctxlog.Debug(ctx, "automatic generation of service account", namespace, serviceAccount.Namespace, name, serviceAccount.Name, "Operation", op)
-	} else {
-		// If ServiceAccount or the name of ServiceAccount in buildRun is nil, use pipeline serviceaccount
-		if buildRun.Spec.ServiceAccount == nil || buildRun.Spec.ServiceAccount.Name == nil {
-			serviceAccountName := resources.PipelineServiceAccountName
-			err := r.client.Get(ctx, types.NamespacedName{Name: serviceAccountName, Namespace: buildRun.Namespace}, serviceAccount)
-			if err != nil && !apierrors.IsNotFound(err) {
-				return nil, err
-			} else if apierrors.IsNotFound(err) {
-				serviceAccountName = resources.DefaultServiceAccountName
-				ctxlog.Info(ctx, "falling back to default serviceAccount", namespace, buildRun.Namespace)
-				err = r.client.Get(ctx, types.NamespacedName{Name: serviceAccountName, Namespace: buildRun.Namespace}, serviceAccount)
-				if err != nil {
-					return nil, err
-				}
-			}
-		} else {
-			serviceAccountName := *buildRun.Spec.ServiceAccount.Name
-			err := r.client.Get(ctx, types.NamespacedName{Name: serviceAccountName, Namespace: buildRun.Namespace}, serviceAccount)
-			if err != nil {
-				return nil, err
-			}
-		}
-
-		// Add credentials and update the service account
-		if modified := resources.ApplyCredentials(ctx, build, serviceAccount); modified {
-			ctxlog.Info(ctx, "updating ServiceAccount with secrets from build", namespace, serviceAccount.Namespace, name, serviceAccount.Name)
-			if err := r.client.Update(ctx, serviceAccount); err != nil {
-				return nil, err
-			}
-		}
-	}
-	return serviceAccount, nil
-}
-
-func (r *ReconcileBuildRun) retrieveBuildStrategy(ctx context.Context, build *buildv1alpha1.Build) (*buildv1alpha1.BuildStrategy, error) {
-	buildStrategyInstance := &buildv1alpha1.BuildStrategy{}
-
-	ctxlog.Debug(ctx, "retrieving BuildStrategy", namespace, build.Namespace, name, build.Name)
-	err := r.client.Get(ctx, types.NamespacedName{Name: build.Spec.StrategyRef.Name, Namespace: build.Namespace}, buildStrategyInstance)
-	if err != nil {
-		return nil, err
-	}
-	return buildStrategyInstance, nil
-}
-
-func (r *ReconcileBuildRun) retrieveClusterBuildStrategy(ctx context.Context, build *buildv1alpha1.Build) (*buildv1alpha1.ClusterBuildStrategy, error) {
-	clusterBuildStrategyInstance := &buildv1alpha1.ClusterBuildStrategy{}
-
-	ctxlog.Debug(ctx, "retrieving ClusterBuildStrategy", namespace, build.Namespace, name, build.Name)
-	err := r.client.Get(ctx, types.NamespacedName{Name: build.Spec.StrategyRef.Name}, clusterBuildStrategyInstance)
-	if err != nil {
-		return nil, err
-	}
-	return clusterBuildStrategyInstance, nil
-}
-
 // updateBuildRunErrorStatus updates buildRun status fields
 func (r *ReconcileBuildRun) updateBuildRunErrorStatus(ctx context.Context, buildRun *buildv1alpha1.BuildRun, errorMessage string) error {
 	// these two fields are deprecated and will be removed soon
@@ -561,23 +397,16 @@ func (r *ReconcileBuildRun) updateBuildRunErrorStatus(ctx context.Context, build
 	return updateErr
 }
 
-// getGeneratedServiceAccountName returns the name of the generated service account for a build run
-func getGeneratedServiceAccountName(buildRun *buildv1alpha1.BuildRun) string {
-	return buildRun.Name + "-sa"
-}
-
-// isGeneratedServiceAccountUsed checks if a build run uses a generated service account
-func isGeneratedServiceAccountUsed(buildRun *buildv1alpha1.BuildRun) bool {
-	return buildRun.Spec.ServiceAccount != nil && buildRun.Spec.ServiceAccount.Generate
-}
-
-/// isOwnedByBuild checks if the controllerReferences contains a well known owner Kind
-func isOwnedByBuild(build *buildv1alpha1.Build, controlledReferences []metav1.OwnerReference) bool {
-	for _, ref := range controlledReferences {
-		if ref.Kind == build.Kind && ref.Name == build.Name {
-			return true
-		}
+// ValidateBuildRegistration verifies that a referenced Build is properly registered
+func (r *ReconcileBuildRun) ValidateBuildRegistration(ctx context.Context, build *buildv1alpha1.Build, buildRun *buildv1alpha1.BuildRun) error {
+	if build.Status.Registered == "" {
+		err := fmt.Errorf("the Build is not yet validated, build: %s", build.Name)
+		return err
 	}
-
-	return false
+	if build.Status.Registered != corev1.ConditionTrue {
+		err := fmt.Errorf("the Build is not registered correctly, build: %s, registered status: %s, reason: %s", build.Name, build.Status.Registered, build.Status.Reason)
+		updateErr := r.updateBuildRunErrorStatus(ctx, buildRun, err.Error())
+		return resources.HandleError("Build is not ready", err, updateErr)
+	}
+	return nil
 }
diff --git a/pkg/reconciler/buildrun/controller.go b/pkg/reconciler/buildrun/controller.go
index 9ecb3532..ac66a3e4 100644
--- a/pkg/reconciler/buildrun/controller.go
+++ b/pkg/reconciler/buildrun/controller.go
@@ -13,7 +13,6 @@ import (
 	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/controller"
 	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
 	"sigs.k8s.io/controller-runtime/pkg/event"
@@ -28,11 +27,6 @@ import (
 	"github.com/shipwright-io/build/pkg/ctxlog"
 )
 
-/**
-* USER ACTION REQUIRED: This is a scaffold file intended for the user to modify with their own Controller
-* business logic.  Delete these comments after modifying this file.*
- */
-
 type setOwnerReferenceFunc func(owner, object metav1.Object, scheme *runtime.Scheme) error
 
 // Add creates a new BuildRun Controller and adds it to the Manager. The Manager will set fields on the Controller
@@ -42,17 +36,6 @@ func Add(ctx context.Context, c *config.Config, mgr manager.Manager) error {
 	return add(ctx, mgr, NewReconciler(ctx, c, mgr, controllerutil.SetControllerReference))
 }
 
-// NewReconciler returns a new reconcile.Reconciler
-func NewReconciler(ctx context.Context, c *config.Config, mgr manager.Manager, ownerRef setOwnerReferenceFunc) reconcile.Reconciler {
-	return &ReconcileBuildRun{
-		ctx:                   ctx,
-		config:                c,
-		client:                mgr.GetClient(),
-		scheme:                mgr.GetScheme(),
-		setOwnerReferenceFunc: ownerRef,
-	}
-}
-
 // add adds a new Controller to mgr with r as the reconcile.Reconciler
 func add(ctx context.Context, mgr manager.Manager, r reconcile.Reconciler) error {
 	// Create a new controller
@@ -141,17 +124,3 @@ func add(ctx context.Context, mgr manager.Manager, r reconcile.Reconciler) error
 		}),
 	}, predTaskRun)
 }
-
-// blank assignment to verify that ReconcileBuildRun implements reconcile.Reconciler
-var _ reconcile.Reconciler = &ReconcileBuildRun{}
-
-// ReconcileBuildRun reconciles a BuildRun object
-type ReconcileBuildRun struct {
-	// This client, initialized using mgr.Client() above, is a split client
-	// that reads objects from the cache and writes to the apiserver
-	ctx                   context.Context
-	config                *config.Config
-	client                client.Client
-	scheme                *runtime.Scheme
-	setOwnerReferenceFunc setOwnerReferenceFunc
-}
diff --git a/pkg/reconciler/buildrun/resources/build.go b/pkg/reconciler/buildrun/resources/build.go
new file mode 100644
index 00000000..3bfb640e
--- /dev/null
+++ b/pkg/reconciler/buildrun/resources/build.go
@@ -0,0 +1,30 @@
+// Copyright The Shipwright Contributors
+//
+// SPDX-License-Identifier: Apache-2.0
+
+package resources
+
+import (
+	"context"
+
+	buildv1alpha1 "github.com/shipwright-io/build/pkg/apis/build/v1alpha1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/types"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+)
+
+// GetBuildObject retrieves an existing Build based on a name and namespace
+func GetBuildObject(ctx context.Context, client client.Client, objectName string, objectNS string, build *buildv1alpha1.Build) error {
+	return client.Get(ctx, types.NamespacedName{Name: objectName, Namespace: objectNS}, build)
+}
+
+// IsOwnedByBuild checks if the controllerReferences contains a well known owner Kind
+func IsOwnedByBuild(build *buildv1alpha1.Build, controlledReferences []metav1.OwnerReference) bool {
+	for _, ref := range controlledReferences {
+		if ref.Kind == build.Kind && ref.Name == build.Name {
+			return true
+		}
+	}
+
+	return false
+}
diff --git a/pkg/reconciler/buildrun/resources/conditions.go b/pkg/reconciler/buildrun/resources/conditions.go
new file mode 100644
index 00000000..783f5e0c
--- /dev/null
+++ b/pkg/reconciler/buildrun/resources/conditions.go
@@ -0,0 +1,96 @@
+// Copyright The Shipwright Contributors
+//
+// SPDX-License-Identifier: Apache-2.0
+
+package resources
+
+import (
+	"context"
+	"fmt"
+
+	"github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1"
+	"knative.dev/pkg/apis"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+
+	corev1 "k8s.io/api/core/v1"
+	apierrors "k8s.io/apimachinery/pkg/api/errors"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/types"
+
+	buildv1alpha1 "github.com/shipwright-io/build/pkg/apis/build/v1alpha1"
+)
+
+// UpdateBuildRunUsingTaskRunCondition updates the BuildRun Succeeded Condition
+func UpdateBuildRunUsingTaskRunCondition(ctx context.Context, client client.Client, buildRun *buildv1alpha1.BuildRun, taskRun *v1beta1.TaskRun, trCondition *apis.Condition) error {
+	var reason, message string = trCondition.Reason, trCondition.Message
+
+	switch v1beta1.TaskRunReason(reason) {
+	case v1beta1.TaskRunReasonTimedOut:
+		reason = "BuildRunTimeout"
+		message = fmt.Sprintf("BuildRun %s failed to finish within %s",
+			buildRun.Name,
+			taskRun.Spec.Timeout.Duration,
+		)
+
+	case v1beta1.TaskRunReasonFailed:
+		if taskRun.Status.CompletionTime != nil {
+			var pod corev1.Pod
+			if err := client.Get(ctx, types.NamespacedName{Namespace: taskRun.Namespace, Name: taskRun.Status.PodName}, &pod); err != nil {
+				// when trying to customize the Condition Message field, ensure the Message cover the case
+				// when a Pod is deleted.
+				// Note: this is an edge case, but not doing this prevent a BuildRun from being marked as Failed
+				// while the TaskRun is already with a Failed Reason in it´s condition.
+				if apierrors.IsNotFound(err) {
+					message = fmt.Sprintf("buildrun failed, pod %s not found", taskRun.Status.PodName)
+					break
+				}
+				return err
+			}
+
+			buildRun.Status.FailedAt = &buildv1alpha1.FailedAt{Pod: pod.Name}
+
+			// Since the container status list is not sorted, as a quick workaround mark all failed containers
+			var failures = make(map[string]struct{})
+			for _, containerStatus := range pod.Status.ContainerStatuses {
+				if containerStatus.State.Terminated != nil && containerStatus.State.Terminated.ExitCode != 0 {
+					failures[containerStatus.Name] = struct{}{}
+				}
+			}
+
+			// Find the first container that failed
+			var failedContainer *corev1.Container
+			for i, container := range pod.Spec.Containers {
+				if _, has := failures[container.Name]; has {
+					failedContainer = &pod.Spec.Containers[i]
+					break
+				}
+			}
+
+			if failedContainer != nil {
+				buildRun.Status.FailedAt.Container = failedContainer.Name
+				message = fmt.Sprintf("buildrun step failed in pod %s, for detailed information: kubectl --namespace %s logs %s --container=%s",
+					pod.Name,
+					pod.Namespace,
+					pod.Name,
+					failedContainer.Name,
+				)
+			} else {
+				message = fmt.Sprintf("buildrun failed due to an unexpected error in pod %s: for detailed information: kubectl --namespace %s logs %s --all-containers",
+					pod.Name,
+					pod.Namespace,
+					pod.Name,
+				)
+			}
+		}
+	}
+
+	buildRun.Status.SetCondition(&buildv1alpha1.Condition{
+		LastTransitionTime: metav1.Now(),
+		Type:               buildv1alpha1.Succeeded,
+		Status:             trCondition.Status,
+		Reason:             reason,
+		Message:            message,
+	})
+
+	return nil
+}
diff --git a/pkg/reconciler/buildrun/resources/errors.go b/pkg/reconciler/buildrun/resources/errors.go
new file mode 100644
index 00000000..7394186a
--- /dev/null
+++ b/pkg/reconciler/buildrun/resources/errors.go
@@ -0,0 +1,22 @@
+// Copyright The Shipwright Contributors
+//
+// SPDX-License-Identifier: Apache-2.0
+
+package resources
+
+import (
+	"fmt"
+	"strings"
+)
+
+// HandleError returns multiple errors if each error is not nil.
+// And its error message.
+func HandleError(message string, listOfErrors ...error) error {
+	var errSlice []string
+	for _, e := range listOfErrors {
+		if e != nil {
+			errSlice = append(errSlice, e.Error())
+		}
+	}
+	return fmt.Errorf("errors: %s, msg: %s", strings.Join(errSlice, ", "), message)
+}
diff --git a/pkg/reconciler/buildrun/resources/service_accounts.go b/pkg/reconciler/buildrun/resources/service_accounts.go
new file mode 100644
index 00000000..513edab6
--- /dev/null
+++ b/pkg/reconciler/buildrun/resources/service_accounts.go
@@ -0,0 +1,102 @@
+// Copyright The Shipwright Contributors
+//
+// SPDX-License-Identifier: Apache-2.0
+
+package resources
+
+import (
+	"context"
+
+	buildv1alpha1 "github.com/shipwright-io/build/pkg/apis/build/v1alpha1"
+	"github.com/shipwright-io/build/pkg/ctxlog"
+	corev1 "k8s.io/api/core/v1"
+	apierrors "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"
+	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
+)
+
+const (
+	// DefaultServiceAccountName defines the default sa name
+	// in vanilla Kubernetes clusters
+	DefaultServiceAccountName = "default"
+	// PipelineServiceAccountName defines the default sa name
+	// in vanilla OpenShift clusters
+	PipelineServiceAccountName        = "pipeline"
+	namespace                  string = "namespace"
+	name                       string = "name"
+)
+
+// GetGeneratedServiceAccountName returns the name of the generated service account for a build run
+func GetGeneratedServiceAccountName(buildRun *buildv1alpha1.BuildRun) string {
+	return buildRun.Name + "-sa"
+}
+
+// IsGeneratedServiceAccountUsed checks if a build run uses a generated service account
+func IsGeneratedServiceAccountUsed(buildRun *buildv1alpha1.BuildRun) bool {
+	return buildRun.Spec.ServiceAccount != nil && buildRun.Spec.ServiceAccount.Generate
+}
+
+// RetrieveServiceAccount provides either a default sa with a referenced secret or it will generate a new sa on the fly.
+// When not using the generate feature, it will modify and return the default sa from a k8s namespace, which is "default"
+// or the default sa inside an openshift namespace, which is "pipeline".
+func RetrieveServiceAccount(ctx context.Context, client client.Client, build *buildv1alpha1.Build, buildRun *buildv1alpha1.BuildRun) (*corev1.ServiceAccount, error) {
+	serviceAccount := &corev1.ServiceAccount{}
+
+	if IsGeneratedServiceAccountUsed(buildRun) {
+		serviceAccountName := GetGeneratedServiceAccountName(buildRun)
+
+		serviceAccount.Name = serviceAccountName
+		serviceAccount.Namespace = buildRun.Namespace
+
+		// Create the service account, use CreateOrUpdate as it might exist already from a previous reconciliation that
+		// succeeded to create the service account but failed to update the build run that references it
+		ctxlog.Info(ctx, "create or update serviceAccount for BuildRun", namespace, buildRun.Namespace, name, serviceAccountName, "BuildRun", buildRun.Name)
+		op, err := controllerutil.CreateOrUpdate(ctx, client, serviceAccount, func() error {
+			serviceAccount.SetLabels(map[string]string{buildv1alpha1.LabelBuildRun: buildRun.Name})
+
+			ownerReference := metav1.NewControllerRef(buildRun, buildv1alpha1.SchemeGroupVersion.WithKind("BuildRun"))
+			serviceAccount.SetOwnerReferences([]metav1.OwnerReference{*ownerReference})
+
+			ApplyCredentials(ctx, build, serviceAccount)
+
+			return nil
+		})
+		if err != nil {
+			return nil, err
+		}
+		ctxlog.Debug(ctx, "automatic generation of service account", namespace, serviceAccount.Namespace, name, serviceAccount.Name, "Operation", op)
+	} else {
+		// If ServiceAccount or the name of ServiceAccount in buildRun is nil, use pipeline serviceaccount
+		if buildRun.Spec.ServiceAccount == nil || buildRun.Spec.ServiceAccount.Name == nil {
+			serviceAccountName := PipelineServiceAccountName
+			err := client.Get(ctx, types.NamespacedName{Name: serviceAccountName, Namespace: buildRun.Namespace}, serviceAccount)
+			if err != nil && !apierrors.IsNotFound(err) {
+				return nil, err
+			} else if apierrors.IsNotFound(err) {
+				serviceAccountName = DefaultServiceAccountName
+				ctxlog.Info(ctx, "falling back to default serviceAccount", namespace, buildRun.Namespace)
+				err = client.Get(ctx, types.NamespacedName{Name: serviceAccountName, Namespace: buildRun.Namespace}, serviceAccount)
+				if err != nil {
+					return nil, err
+				}
+			}
+		} else {
+			serviceAccountName := *buildRun.Spec.ServiceAccount.Name
+			err := client.Get(ctx, types.NamespacedName{Name: serviceAccountName, Namespace: buildRun.Namespace}, serviceAccount)
+			if err != nil {
+				return nil, err
+			}
+		}
+
+		// Add credentials and update the service account
+		if modified := ApplyCredentials(ctx, build, serviceAccount); modified {
+			ctxlog.Info(ctx, "updating ServiceAccount with secrets from build", namespace, serviceAccount.Namespace, name, serviceAccount.Name)
+			if err := client.Update(ctx, serviceAccount); err != nil {
+				return nil, err
+			}
+		}
+	}
+	return serviceAccount, nil
+}
diff --git a/pkg/reconciler/buildrun/resources/strategies.go b/pkg/reconciler/buildrun/resources/strategies.go
new file mode 100644
index 00000000..cbebb581
--- /dev/null
+++ b/pkg/reconciler/buildrun/resources/strategies.go
@@ -0,0 +1,37 @@
+// Copyright The Shipwright Contributors
+//
+// SPDX-License-Identifier: Apache-2.0
+
+package resources
+
+import (
+	"context"
+
+	buildv1alpha1 "github.com/shipwright-io/build/pkg/apis/build/v1alpha1"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+
+	"github.com/shipwright-io/build/pkg/ctxlog"
+	"k8s.io/apimachinery/pkg/types"
+)
+
+// RetrieveBuildStrategy returns a namespace scoped strategy
+func RetrieveBuildStrategy(ctx context.Context, client client.Client, build *buildv1alpha1.Build) (*buildv1alpha1.BuildStrategy, error) {
+	buildStrategyInstance := &buildv1alpha1.BuildStrategy{}
+
+	ctxlog.Debug(ctx, "retrieving BuildStrategy", namespace, build.Namespace, name, build.Name)
+	if err := client.Get(ctx, types.NamespacedName{Name: build.Spec.StrategyRef.Name, Namespace: build.Namespace}, buildStrategyInstance); err != nil {
+		return nil, err
+	}
+	return buildStrategyInstance, nil
+}
+
+// RetrieveClusterBuildStrategy returns a cluster scoped strategy
+func RetrieveClusterBuildStrategy(ctx context.Context, client client.Client, build *buildv1alpha1.Build) (*buildv1alpha1.ClusterBuildStrategy, error) {
+	clusterBuildStrategyInstance := &buildv1alpha1.ClusterBuildStrategy{}
+
+	ctxlog.Debug(ctx, "retrieving ClusterBuildStrategy", namespace, build.Namespace, name, build.Name)
+	if err := client.Get(ctx, types.NamespacedName{Name: build.Spec.StrategyRef.Name}, clusterBuildStrategyInstance); err != nil {
+		return nil, err
+	}
+	return clusterBuildStrategyInstance, nil
+}
diff --git a/pkg/reconciler/buildrun/resources/taskrun.go b/pkg/reconciler/buildrun/resources/taskrun.go
index 4d5ac1fe..f38ba9d2 100644
--- a/pkg/reconciler/buildrun/resources/taskrun.go
+++ b/pkg/reconciler/buildrun/resources/taskrun.go
@@ -20,20 +20,14 @@ import (
 )
 
 const (
-	// DefaultServiceAccountName defines the default sa name
-	// in vanilla Kubernetes clusters
-	DefaultServiceAccountName = "default"
-	// PipelineServiceAccountName defines the default sa name
-	// in vanilla OpenShift clusters
-	PipelineServiceAccountName = "pipeline"
-	inputSourceResourceName    = "source"
-	inputGitSourceURL          = "url"
-	inputGitSourceRevision     = "revision"
-	inputParamBuilderImage     = "BUILDER_IMAGE"
-	inputParamDockerfile       = "DOCKERFILE"
-	inputParamContextDir       = "CONTEXT_DIR"
-	outputImageResourceName    = "image"
-	outputImageResourceURL     = "url"
+	inputSourceResourceName = "source"
+	inputGitSourceURL       = "url"
+	inputGitSourceRevision  = "revision"
+	inputParamBuilderImage  = "BUILDER_IMAGE"
+	inputParamDockerfile    = "DOCKERFILE"
+	inputParamContextDir    = "CONTEXT_DIR"
+	outputImageResourceName = "image"
+	outputImageResourceURL  = "url"
 )
 
 // getStringTransformations gets us MANDATORY replacements using
diff --git a/pkg/reconciler/buildstrategy/buildstrategy.go b/pkg/reconciler/buildstrategy/buildstrategy.go
index 67c1869f..b9b593ff 100644
--- a/pkg/reconciler/buildstrategy/buildstrategy.go
+++ b/pkg/reconciler/buildstrategy/buildstrategy.go
@@ -7,10 +7,37 @@ package buildstrategy
 import (
 	"context"
 
+	"github.com/shipwright-io/build/pkg/config"
 	"github.com/shipwright-io/build/pkg/ctxlog"
+	"k8s.io/apimachinery/pkg/runtime"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/manager"
 	"sigs.k8s.io/controller-runtime/pkg/reconcile"
 )
 
+// blank assignment to verify that ReconcileBuildStrategy implements reconcile.Reconciler
+var _ reconcile.Reconciler = &ReconcileBuildStrategy{}
+
+// ReconcileBuildStrategy reconciles a BuildStrategy object
+type ReconcileBuildStrategy struct {
+	// This client, initialized using mgr.Client() above, is a split client
+	// that reads objects from the cache and writes to the apiserver
+	ctx    context.Context
+	config *config.Config
+	client client.Client
+	scheme *runtime.Scheme
+}
+
+// NewReconciler returns a new reconcile.Reconciler
+func NewReconciler(ctx context.Context, c *config.Config, mgr manager.Manager) reconcile.Reconciler {
+	return &ReconcileBuildStrategy{
+		ctx:    ctx,
+		config: c,
+		client: mgr.GetClient(),
+		scheme: mgr.GetScheme(),
+	}
+}
+
 // Reconcile reads that state of the cluster for a BuildStrategy object and makes changes based on the state read
 // and what is in the BuildStrategy.Spec
 func (r *ReconcileBuildStrategy) Reconcile(request reconcile.Request) (reconcile.Result, error) {
diff --git a/pkg/reconciler/buildstrategy/controller.go b/pkg/reconciler/buildstrategy/controller.go
index 8691b3cf..95ff5521 100644
--- a/pkg/reconciler/buildstrategy/controller.go
+++ b/pkg/reconciler/buildstrategy/controller.go
@@ -7,8 +7,6 @@ package buildstrategy
 import (
 	"context"
 
-	"k8s.io/apimachinery/pkg/runtime"
-	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/controller"
 	"sigs.k8s.io/controller-runtime/pkg/handler"
 	"sigs.k8s.io/controller-runtime/pkg/manager"
@@ -27,16 +25,6 @@ func Add(ctx context.Context, c *config.Config, mgr manager.Manager) error {
 	return add(ctx, mgr, NewReconciler(ctx, c, mgr))
 }
 
-// NewReconciler returns a new reconcile.Reconciler
-func NewReconciler(ctx context.Context, c *config.Config, mgr manager.Manager) reconcile.Reconciler {
-	return &ReconcileBuildStrategy{
-		ctx:    ctx,
-		config: c,
-		client: mgr.GetClient(),
-		scheme: mgr.GetScheme(),
-	}
-}
-
 // add adds a new Controller to mgr with r as the reconcile.Reconciler
 func add(ctx context.Context, mgr manager.Manager, r reconcile.Reconciler) error {
 	// Create a new controller
@@ -53,16 +41,3 @@ func add(ctx context.Context, mgr manager.Manager, r reconcile.Reconciler) error
 
 	return nil
 }
-
-// blank assignment to verify that ReconcileBuildStrategy implements reconcile.Reconciler
-var _ reconcile.Reconciler = &ReconcileBuildStrategy{}
-
-// ReconcileBuildStrategy reconciles a BuildStrategy object
-type ReconcileBuildStrategy struct {
-	// This client, initialized using mgr.Client() above, is a split client
-	// that reads objects from the cache and writes to the apiserver
-	ctx    context.Context
-	config *config.Config
-	client client.Client
-	scheme *runtime.Scheme
-}
diff --git a/pkg/reconciler/clusterbuildstrategy/clusterbuildstrategy.go b/pkg/reconciler/clusterbuildstrategy/clusterbuildstrategy.go
index 02ee2dd7..0772d979 100644
--- a/pkg/reconciler/clusterbuildstrategy/clusterbuildstrategy.go
+++ b/pkg/reconciler/clusterbuildstrategy/clusterbuildstrategy.go
@@ -7,11 +7,38 @@ package clusterbuildstrategy
 import (
 	"context"
 
+	"k8s.io/apimachinery/pkg/runtime"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/manager"
 	"sigs.k8s.io/controller-runtime/pkg/reconcile"
 
+	"github.com/shipwright-io/build/pkg/config"
 	"github.com/shipwright-io/build/pkg/ctxlog"
 )
 
+// blank assignment to verify that ReconcileClusterBuildStrategy implements reconcile.Reconciler
+var _ reconcile.Reconciler = &ReconcileClusterBuildStrategy{}
+
+// ReconcileClusterBuildStrategy reconciles a ClusterBuildStrategy object
+type ReconcileClusterBuildStrategy struct {
+	// This client, initialized using mgr.Client() above, is a split client
+	// that reads objects from the cache and writes to the apiserver
+	ctx    context.Context
+	config *config.Config
+	client client.Client
+	scheme *runtime.Scheme
+}
+
+// NewReconciler returns a new reconcile.Reconciler
+func NewReconciler(ctx context.Context, c *config.Config, mgr manager.Manager) reconcile.Reconciler {
+	return &ReconcileClusterBuildStrategy{
+		ctx:    ctx,
+		config: c,
+		client: mgr.GetClient(),
+		scheme: mgr.GetScheme(),
+	}
+}
+
 // Reconcile reads that state of the cluster for a ClusterBuildStrategy object and makes changes based on the state read
 // and what is in the BuildStrategy.Spec
 func (r *ReconcileClusterBuildStrategy) Reconcile(request reconcile.Request) (reconcile.Result, error) {
diff --git a/pkg/reconciler/clusterbuildstrategy/controller.go b/pkg/reconciler/clusterbuildstrategy/controller.go
index bcadb3ba..363b9cf0 100644
--- a/pkg/reconciler/clusterbuildstrategy/controller.go
+++ b/pkg/reconciler/clusterbuildstrategy/controller.go
@@ -7,8 +7,6 @@ package clusterbuildstrategy
 import (
 	"context"
 
-	"k8s.io/apimachinery/pkg/runtime"
-	"sigs.k8s.io/controller-runtime/pkg/client"
 	"sigs.k8s.io/controller-runtime/pkg/controller"
 	"sigs.k8s.io/controller-runtime/pkg/handler"
 	"sigs.k8s.io/controller-runtime/pkg/manager"
@@ -27,16 +25,6 @@ func Add(ctx context.Context, c *config.Config, mgr manager.Manager) error {
 	return add(ctx, mgr, NewReconciler(ctx, c, mgr))
 }
 
-// NewReconciler returns a new reconcile.Reconciler
-func NewReconciler(ctx context.Context, c *config.Config, mgr manager.Manager) reconcile.Reconciler {
-	return &ReconcileClusterBuildStrategy{
-		ctx:    ctx,
-		config: c,
-		client: mgr.GetClient(),
-		scheme: mgr.GetScheme(),
-	}
-}
-
 // add adds a new Controller to mgr with r as the reconcile.Reconciler
 func add(ctx context.Context, mgr manager.Manager, r reconcile.Reconciler) error {
 	// Create a new controller
@@ -53,16 +41,3 @@ func add(ctx context.Context, mgr manager.Manager, r reconcile.Reconciler) error
 
 	return nil
 }
-
-// blank assignment to verify that ReconcileClusterBuildStrategy implements reconcile.Reconciler
-var _ reconcile.Reconciler = &ReconcileClusterBuildStrategy{}
-
-// ReconcileClusterBuildStrategy reconciles a ClusterBuildStrategy object
-type ReconcileClusterBuildStrategy struct {
-	// This client, initialized using mgr.Client() above, is a split client
-	// that reads objects from the cache and writes to the apiserver
-	ctx    context.Context
-	config *config.Config
-	client client.Client
-	scheme *runtime.Scheme
-}
diff --git a/vendor/github.com/operator-framework/operator-sdk/pkg/log/zap/flags.go b/vendor/github.com/operator-framework/operator-sdk/pkg/log/zap/flags.go
new file mode 100644
index 00000000..b44ac6b6
--- /dev/null
+++ b/vendor/github.com/operator-framework/operator-sdk/pkg/log/zap/flags.go
@@ -0,0 +1,258 @@
+// Copyright 2019 The Operator-SDK Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package zap
+
+import (
+	"flag"
+	"fmt"
+	"strconv"
+	"strings"
+
+	"github.com/spf13/pflag"
+	"go.uber.org/zap"
+	"go.uber.org/zap/zapcore"
+	"k8s.io/klog"
+)
+
+var (
+	zapFlagSet *pflag.FlagSet
+
+	development     bool
+	encoderVal      encoderValue
+	levelVal        levelValue
+	sampleVal       sampleValue
+	timeEncodingVal timeEncodingValue
+	stacktraceLevel stackLevelValue
+)
+
+func init() {
+	zapFlagSet = pflag.NewFlagSet("zap", pflag.ExitOnError)
+	zapFlagSet.BoolVar(&development, "zap-devel", false, "Enable zap development mode"+
+		" (changes defaults to console encoder, debug log level, disables sampling and stacktrace from 'warning' level)")
+	zapFlagSet.Var(&encoderVal, "zap-encoder", "Zap log encoding ('json' or 'console')")
+	zapFlagSet.Var(&levelVal, "zap-level", "Zap log level (one of 'debug', 'info', 'error' or any integer value > 0)")
+	zapFlagSet.Var(&sampleVal, "zap-sample",
+		"Enable zap log sampling. Sampling will be disabled for integer log levels > 1")
+	zapFlagSet.Var(&timeEncodingVal, "zap-time-encoding",
+		"Sets the zap time format ('epoch', 'millis', 'nano', or 'iso8601')")
+	zapFlagSet.Var(&stacktraceLevel, "zap-stacktrace-level",
+		"Set the minimum log level that triggers stacktrace generation")
+}
+
+// FlagSet - The zap logging flagset.
+func FlagSet() *pflag.FlagSet {
+	return zapFlagSet
+}
+
+type encoderConfigFunc func(*zapcore.EncoderConfig)
+
+type encoderValue struct {
+	set        bool
+	newEncoder func(...encoderConfigFunc) zapcore.Encoder
+	str        string
+}
+
+func (v *encoderValue) Set(e string) error {
+	v.set = true
+	switch e {
+	case "json":
+		v.newEncoder = newJSONEncoder
+	case "console":
+		v.newEncoder = newConsoleEncoder
+	default:
+		return fmt.Errorf("unknown encoder \"%s\"", e)
+	}
+	v.str = e
+	return nil
+}
+
+func (v encoderValue) String() string {
+	return v.str
+}
+
+func (v encoderValue) Type() string {
+	return "encoder"
+}
+
+func newJSONEncoder(ecfs ...encoderConfigFunc) zapcore.Encoder {
+	encoderConfig := zap.NewProductionEncoderConfig()
+	for _, f := range ecfs {
+		f(&encoderConfig)
+	}
+	return zapcore.NewJSONEncoder(encoderConfig)
+}
+
+func newConsoleEncoder(ecfs ...encoderConfigFunc) zapcore.Encoder {
+	encoderConfig := zap.NewDevelopmentEncoderConfig()
+	for _, f := range ecfs {
+		f(&encoderConfig)
+	}
+	return zapcore.NewConsoleEncoder(encoderConfig)
+}
+
+type levelValue struct {
+	set   bool
+	level zapcore.Level
+}
+
+func (v *levelValue) Set(l string) error {
+	v.set = true
+	lvl, err := intLogLevel(l)
+	if err != nil {
+		return err
+	}
+
+	v.level = zapcore.Level(int8(lvl))
+	// If log level is greater than debug, set glog/klog level to that level.
+	if lvl < -3 {
+		fs := flag.NewFlagSet("", flag.ContinueOnError)
+		klog.InitFlags(fs)
+		err := fs.Set("v", fmt.Sprintf("%v", -1*lvl))
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func (v levelValue) String() string {
+	return v.level.String()
+}
+
+func (v levelValue) Type() string {
+	return "level"
+}
+
+type stackLevelValue struct {
+	set   bool
+	level zapcore.Level
+}
+
+func (v *stackLevelValue) Set(l string) error {
+	v.set = true
+	lvl, err := intLogLevel(l)
+	if err != nil {
+		return err
+	}
+
+	v.level = zapcore.Level(int8(lvl))
+	return nil
+}
+
+func (v stackLevelValue) String() string {
+	if v.set {
+		return v.level.String()
+	}
+
+	return "error"
+}
+
+func (v stackLevelValue) Type() string {
+	return "level"
+}
+
+func intLogLevel(l string) (int, error) {
+	lower := strings.ToLower(l)
+	var lvl int
+	switch lower {
+	case "debug":
+		lvl = -1
+	case "info":
+		lvl = 0
+	case "error":
+		lvl = 2
+	default:
+		i, err := strconv.Atoi(lower)
+		if err != nil {
+			return lvl, fmt.Errorf("invalid log level \"%s\"", l)
+		}
+
+		if i > 0 {
+			lvl = -1 * i
+		} else {
+			return lvl, fmt.Errorf("invalid log level \"%s\"", l)
+		}
+	}
+	return lvl, nil
+}
+
+type sampleValue struct {
+	set    bool
+	sample bool
+}
+
+func (v *sampleValue) Set(s string) error {
+	var err error
+	v.set = true
+	v.sample, err = strconv.ParseBool(s)
+	return err
+}
+
+func (v sampleValue) String() string {
+	return strconv.FormatBool(v.sample)
+}
+
+func (v sampleValue) IsBoolFlag() bool {
+	return true
+}
+
+func (v sampleValue) Type() string {
+	return "sample"
+}
+
+type timeEncodingValue struct {
+	set         bool
+	timeEncoder zapcore.TimeEncoder
+	str         string
+}
+
+func (v *timeEncodingValue) Set(s string) error {
+	v.set = true
+
+	// As of zap v1.9.1, UnmarshalText does not return an error. Instead, it
+	// uses the epoch time encoding when unknown strings are unmarshalled.
+	//
+	// Set s to "epoch" if it doesn't match one of the known formats, so that
+	// it aligns with the default time encoder function.
+	//
+	// TODO: remove this entire switch statement if UnmarshalText is ever
+	// refactored to return an error.
+	switch s {
+	case "iso8601", "ISO8601", "millis", "nanos":
+	default:
+		s = "epoch"
+	}
+
+	v.str = s
+	return v.timeEncoder.UnmarshalText([]byte(s))
+}
+
+func (v timeEncodingValue) String() string {
+	return v.str
+}
+
+func (v timeEncodingValue) IsBoolFlag() bool {
+	return false
+}
+
+func (v timeEncodingValue) Type() string {
+	return "timeEncoding"
+}
+
+func withTimeEncoding(te zapcore.TimeEncoder) encoderConfigFunc {
+	return func(ec *zapcore.EncoderConfig) {
+		ec.EncodeTime = te
+	}
+}
diff --git a/vendor/github.com/operator-framework/operator-sdk/pkg/log/zap/logger.go b/vendor/github.com/operator-framework/operator-sdk/pkg/log/zap/logger.go
new file mode 100644
index 00000000..f8de2450
--- /dev/null
+++ b/vendor/github.com/operator-framework/operator-sdk/pkg/log/zap/logger.go
@@ -0,0 +1,109 @@
+// Copyright 2019 The Operator-SDK Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package zap
+
+import (
+	"io"
+	"os"
+	"time"
+
+	"github.com/go-logr/logr"
+	"github.com/go-logr/zapr"
+	"go.uber.org/zap"
+	"go.uber.org/zap/zapcore"
+	zapf "sigs.k8s.io/controller-runtime/pkg/log/zap"
+)
+
+func Logger() logr.Logger {
+	return LoggerTo(os.Stderr)
+}
+
+func LoggerTo(destWriter io.Writer) logr.Logger {
+	conf := getConfig()
+	return createLogger(conf, destWriter)
+}
+
+func createLogger(conf config, destWriter io.Writer) logr.Logger {
+	syncer := zapcore.AddSync(destWriter)
+
+	conf.encoder = &zapf.KubeAwareEncoder{Encoder: conf.encoder, Verbose: conf.level.Level() < 0}
+	if conf.sample {
+		conf.opts = append(conf.opts, zap.WrapCore(func(core zapcore.Core) zapcore.Core {
+			return zapcore.NewSampler(core, time.Second, 100, 100)
+		}))
+	}
+	conf.opts = append(conf.opts, zap.AddCallerSkip(1), zap.ErrorOutput(syncer))
+	log := zap.New(zapcore.NewCore(conf.encoder, syncer, conf.level))
+	log = log.WithOptions(conf.opts...)
+	return zapr.NewLogger(log)
+}
+
+type config struct {
+	encoder         zapcore.Encoder
+	level           zap.AtomicLevel
+	opts            []zap.Option
+	stackTraceLevel zapcore.Level
+	sample          bool
+}
+
+func getConfig() config {
+	var c config
+
+	var newEncoder func(...encoderConfigFunc) zapcore.Encoder
+
+	// Set the defaults depending on the log mode (development vs. production)
+	if development {
+		newEncoder = newConsoleEncoder
+		c.level = zap.NewAtomicLevelAt(zap.DebugLevel)
+		c.opts = append(c.opts, zap.Development())
+		c.sample = false
+		c.stackTraceLevel = zap.WarnLevel
+	} else {
+		newEncoder = newJSONEncoder
+		c.level = zap.NewAtomicLevelAt(zap.InfoLevel)
+		c.sample = true
+		c.stackTraceLevel = zap.ErrorLevel
+	}
+
+	// Override the defaults if the flags were set explicitly on the command line
+	if stacktraceLevel.set {
+		c.stackTraceLevel = stacktraceLevel.level
+	}
+	c.opts = append(c.opts, zap.AddStacktrace(c.stackTraceLevel))
+
+	var ecfs []encoderConfigFunc
+	if encoderVal.set {
+		newEncoder = encoderVal.newEncoder
+	}
+	if timeEncodingVal.set {
+		ecfs = append(ecfs, withTimeEncoding(timeEncodingVal.timeEncoder))
+	}
+
+	c.encoder = newEncoder(ecfs...)
+
+	if levelVal.set {
+		c.level = zap.NewAtomicLevelAt(levelVal.level)
+	}
+	if sampleVal.set {
+		c.sample = sampleVal.sample
+	}
+
+	// Disable sampling when we are in debug mode. Otherwise, this will
+	// cause index out of bounds errors in the sampling code.
+	if c.level.Level() < -1 {
+		c.sample = false
+	}
+	return c
+}
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 728fc99d..0b85c305 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -246,6 +246,7 @@ github.com/operator-framework/operator-sdk/internal/util/kubebuilder
 github.com/operator-framework/operator-sdk/internal/util/projutil
 github.com/operator-framework/operator-sdk/pkg/k8sutil
 github.com/operator-framework/operator-sdk/pkg/kube-metrics
+github.com/operator-framework/operator-sdk/pkg/log/zap
 github.com/operator-framework/operator-sdk/pkg/metrics
 github.com/operator-framework/operator-sdk/pkg/ready
 github.com/operator-framework/operator-sdk/pkg/test
@@ -312,7 +313,6 @@ go.uber.org/atomic
 # go.uber.org/multierr v1.5.0
 go.uber.org/multierr
 # go.uber.org/zap v1.16.0
-## explicit
 go.uber.org/zap
 go.uber.org/zap/buffer
 go.uber.org/zap/internal/bufferpool


Test patch:
diff --git a/pkg/reconciler/buildrun/resources/build_test.go b/pkg/reconciler/buildrun/resources/build_test.go
new file mode 100644
index 00000000..3b076c17
--- /dev/null
+++ b/pkg/reconciler/buildrun/resources/build_test.go
@@ -0,0 +1,118 @@
+// Copyright The Shipwright Contributors
+//
+// SPDX-License-Identifier: Apache-2.0
+
+package resources_test
+
+import (
+	"context"
+	"errors"
+
+	. "github.com/onsi/ginkgo"
+	. "github.com/onsi/gomega"
+	build "github.com/shipwright-io/build/pkg/apis/build/v1alpha1"
+	"github.com/shipwright-io/build/pkg/controller/fakes"
+	"github.com/shipwright-io/build/pkg/reconciler/buildrun/resources"
+	"github.com/shipwright-io/build/test"
+	k8serrors "k8s.io/apimachinery/pkg/api/errors"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	"k8s.io/apimachinery/pkg/types"
+)
+
+var _ = Describe("Build Resource", func() {
+
+	var (
+		client    *fakes.FakeClient
+		ctl       test.Catalog
+		buildName string
+	)
+
+	Context("Operating on Build resources", func() {
+		// init vars
+		buildName = "foobuild"
+		client = &fakes.FakeClient{}
+
+		It("should be able to retrieve a build object if exists", func() {
+			buildSample := ctl.DefaultBuild(buildName, "foostrategy", build.ClusterBuildStrategyKind)
+
+			// stub a GET API call with buildSample contents
+			getClientStub := func(context context.Context, nn types.NamespacedName, object runtime.Object) error {
+				switch object := object.(type) {
+				case *build.Build:
+					buildSample.DeepCopyInto(object)
+					return nil
+				}
+				return k8serrors.NewNotFound(schema.GroupResource{}, nn.Name)
+			}
+
+			// fake the calls with the above stub definition
+			client.GetCalls(getClientStub)
+
+			build := &build.Build{}
+			Expect(resources.GetBuildObject(context.TODO(), client, buildName, "default", build)).To(BeNil())
+		})
+		It("should not retrieve a missing build object when missing", func() {
+			// stub a GET API call with buildSample contents that returns "not found"
+			getClientStub := func(context context.Context, nn types.NamespacedName, object runtime.Object) error {
+				switch object.(type) {
+				case *build.Build:
+					return errors.New("not found")
+				}
+				return k8serrors.NewNotFound(schema.GroupResource{}, nn.Name)
+			}
+			// fake the calls with the above stub
+			client.GetCalls(getClientStub)
+
+			build := &build.Build{}
+			Expect(resources.GetBuildObject(context.TODO(), client, buildName, "default", build)).ToNot(BeNil())
+		})
+		It("should be able to verify valid ownerships", func() {
+			managingController := true
+
+			buildSample := &build.Build{
+				TypeMeta: metav1.TypeMeta{
+					Kind: "fakekind",
+				},
+				ObjectMeta: metav1.ObjectMeta{
+					Name: buildName,
+				},
+			}
+			// fake an instance of OwnerReference with a
+			// well known fake Kind and Name
+			fakeOwnerRef := []metav1.OwnerReference{
+				{
+					Kind:       "fakekind",
+					Name:       buildName,
+					Controller: &managingController,
+				},
+			}
+			// Assert that our Build is owned by an owner
+			Expect(resources.IsOwnedByBuild(buildSample, fakeOwnerRef)).To(BeTrue())
+		})
+		It("should be able to verify invalid ownerships", func() {
+			managingController := true
+
+			buildSample := &build.Build{
+				TypeMeta: metav1.TypeMeta{
+					Kind: "notthatkind",
+				},
+				ObjectMeta: metav1.ObjectMeta{
+					Name: buildName,
+				},
+			}
+			// fake an instance of OwnerReference with a
+			// well known fake Kind and Name
+			fakeOwnerRef := []metav1.OwnerReference{
+				{
+					Kind:       "fakekind",
+					Name:       buildName,
+					Controller: &managingController,
+				},
+			}
+			// Assert that our Build is not owned by an owner
+			Expect(resources.IsOwnedByBuild(buildSample, fakeOwnerRef)).To(BeFalse())
+		})
+	})
+})
diff --git a/pkg/reconciler/buildrun/resources/conditions_test.go b/pkg/reconciler/buildrun/resources/conditions_test.go
index 5d6df208..4f26af1b 100644
--- a/pkg/reconciler/buildrun/resources/conditions_test.go
+++ b/pkg/reconciler/buildrun/resources/conditions_test.go
@@ -5,12 +5,22 @@
 package resources_test
 
 import (
+	"context"
+
 	. "github.com/onsi/ginkgo"
 	. "github.com/onsi/gomega"
 	build "github.com/shipwright-io/build/pkg/apis/build/v1alpha1"
+	"github.com/shipwright-io/build/pkg/controller/fakes"
+	"github.com/shipwright-io/build/pkg/reconciler/buildrun/resources"
 	"github.com/shipwright-io/build/test"
+	"github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1"
 	corev1 "k8s.io/api/core/v1"
+	k8serrors "k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	"k8s.io/apimachinery/pkg/types"
+	"knative.dev/pkg/apis"
 )
 
 var _ = Describe("Conditions", func() {
@@ -101,5 +111,157 @@ var _ = Describe("Conditions", func() {
 			condMsg := br.Status.GetCondition(build.Succeeded).GetMessage()
 			Expect(condMsg).To(Equal("foobar was updated"))
 		})
+
+	})
+	Context("Operating with TaskRun Conditions", func() {
+		var (
+			client *fakes.FakeClient
+			ctl    test.Catalog
+			br     *build.BuildRun
+			tr     *v1beta1.TaskRun
+		)
+
+		tr = ctl.TaskRunWithStatus("foo", "bar")
+		br = ctl.DefaultBuildRun("foo", "bar")
+		client = &fakes.FakeClient{}
+
+		It("updates BuildRun condition when TaskRun timeout", func() {
+
+			fakeTRCondition := &apis.Condition{
+				Type:    apis.ConditionSucceeded,
+				Reason:  "TaskRunTimeout",
+				Message: "not relevant",
+			}
+
+			Expect(resources.UpdateBuildRunUsingTaskRunCondition(
+				context.TODO(),
+				client,
+				br,
+				tr,
+				fakeTRCondition,
+			)).To(BeNil())
+		})
+
+		It("updates BuildRun condition when TaskRun fails and pod not found", func() {
+
+			// stub a GET API call that fails with not found
+			getClientStub := func(context context.Context, nn types.NamespacedName, object runtime.Object) error {
+				switch object.(type) {
+				case *corev1.Pod:
+					return k8serrors.NewNotFound(schema.GroupResource{}, nn.Name)
+				}
+				return k8serrors.NewNotFound(schema.GroupResource{}, nn.Name)
+			}
+			// fake the calls with the above stub
+			client.GetCalls(getClientStub)
+
+			fakeTRCondition := &apis.Condition{
+				Type:    apis.ConditionSucceeded,
+				Reason:  "Failed",
+				Message: "not relevant",
+			}
+
+			Expect(resources.UpdateBuildRunUsingTaskRunCondition(
+				context.TODO(),
+				client,
+				br,
+				tr,
+				fakeTRCondition,
+			)).To(BeNil())
+		})
+
+		It("updates a BuildRun condition when the related TaskRun fails and pod containers are available", func() {
+
+			// generate a pod that have a single container and
+			// one entry in the ContainerStatuses field, with
+			// an exitCode
+			taskRunGeneratedPod := corev1.Pod{
+				ObjectMeta: metav1.ObjectMeta{
+					Name: "foopod",
+				},
+				Spec: corev1.PodSpec{
+					Containers: []corev1.Container{
+						{
+							Name: "foobar-container",
+						},
+					},
+				},
+				Status: corev1.PodStatus{
+					ContainerStatuses: []corev1.ContainerStatus{
+						{
+							Name: "foobar-container",
+							State: corev1.ContainerState{
+								Terminated: &corev1.ContainerStateTerminated{
+									Reason:   "foobar",
+									ExitCode: 1,
+								},
+							},
+						},
+					},
+				},
+			}
+
+			// stub a GET API call with taskRunGeneratedPod
+			getClientStub := func(context context.Context, nn types.NamespacedName, object runtime.Object) error {
+				switch object := object.(type) {
+				case *corev1.Pod:
+					taskRunGeneratedPod.DeepCopyInto(object)
+					return nil
+				}
+				return k8serrors.NewNotFound(schema.GroupResource{}, nn.Name)
+			}
+
+			// fake the calls with the above stub
+			client.GetCalls(getClientStub)
+
+			fakeTRCondition := &apis.Condition{
+				Type:    apis.ConditionSucceeded,
+				Reason:  "Failed",
+				Message: "not relevant",
+			}
+
+			Expect(resources.UpdateBuildRunUsingTaskRunCondition(
+				context.TODO(),
+				client,
+				br,
+				tr,
+				fakeTRCondition,
+			)).To(BeNil())
+		})
+
+		It("updates a BuildRun condition when the related TaskRun fails and pod containers are not available", func() {
+
+			taskRunGeneratedPod := corev1.Pod{
+				ObjectMeta: metav1.ObjectMeta{
+					Name: "foobar",
+				},
+			}
+
+			// stub a GET API call with the above taskRunGeneratedPod spec
+			getClientStub := func(context context.Context, nn types.NamespacedName, object runtime.Object) error {
+				switch object := object.(type) {
+				case *corev1.Pod:
+					taskRunGeneratedPod.DeepCopyInto(object)
+					return nil
+				}
+				return k8serrors.NewNotFound(schema.GroupResource{}, nn.Name)
+			}
+			// fake the calls with the above stub
+			client.GetCalls(getClientStub)
+
+			fakeTRCondition := &apis.Condition{
+				Type:    apis.ConditionSucceeded,
+				Reason:  "Failed",
+				Message: "not relevant",
+			}
+
+			Expect(resources.UpdateBuildRunUsingTaskRunCondition(
+				context.TODO(),
+				client,
+				br,
+				tr,
+				fakeTRCondition,
+			)).To(BeNil())
+		})
 	})
 })
diff --git a/pkg/reconciler/buildrun/resources/errors_test.go b/pkg/reconciler/buildrun/resources/errors_test.go
new file mode 100644
index 00000000..c4be5db7
--- /dev/null
+++ b/pkg/reconciler/buildrun/resources/errors_test.go
@@ -0,0 +1,26 @@
+// Copyright The Shipwright Contributors
+//
+// SPDX-License-Identifier: Apache-2.0
+
+package resources_test
+
+import (
+	"errors"
+	"fmt"
+
+	. "github.com/onsi/ginkgo"
+	. "github.com/onsi/gomega"
+	"github.com/shipwright-io/build/pkg/reconciler/buildrun/resources"
+)
+
+var _ = Describe("Multiple errors", func() {
+	Context("Dealing with multiple errors", func() {
+		It("should handle multiple errors", func() {
+			error01 := errors.New("error01")
+			error02 := errors.New("error02")
+			msg := "handling multiple errors"
+			customError := resources.HandleError(msg, error01, error02)
+			Expect(customError).To(Equal(fmt.Errorf("errors: %s, %s, msg: %s", error01, error02, msg)))
+		})
+	})
+})
diff --git a/pkg/reconciler/buildrun/resources/resources_suite_test.go b/pkg/reconciler/buildrun/resources/resources_suite_test.go
new file mode 100644
index 00000000..366c43c2
--- /dev/null
+++ b/pkg/reconciler/buildrun/resources/resources_suite_test.go
@@ -0,0 +1,16 @@
+// Copyright The Shipwright Contributors
+//
+// SPDX-License-Identifier: Apache-2.0
+package resources_test
+
+import (
+	"testing"
+
+	. "github.com/onsi/ginkgo"
+	. "github.com/onsi/gomega"
+)
+
+func TestResources(t *testing.T) {
+	RegisterFailHandler(Fail)
+	RunSpecs(t, "Resources Suite")
+}
diff --git a/pkg/reconciler/buildrun/resources/service_accounts_test.go b/pkg/reconciler/buildrun/resources/service_accounts_test.go
new file mode 100644
index 00000000..b9154f1f
--- /dev/null
+++ b/pkg/reconciler/buildrun/resources/service_accounts_test.go
@@ -0,0 +1,61 @@
+// Copyright The Shipwright Contributors
+//
+// SPDX-License-Identifier: Apache-2.0
+
+package resources_test
+
+import (
+	"context"
+
+	. "github.com/onsi/ginkgo"
+	. "github.com/onsi/gomega"
+	"github.com/shipwright-io/build/pkg/controller/fakes"
+	"github.com/shipwright-io/build/pkg/reconciler/buildrun/resources"
+	"github.com/shipwright-io/build/test"
+	corev1 "k8s.io/api/core/v1"
+	k8serrors "k8s.io/apimachinery/pkg/api/errors"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	"k8s.io/apimachinery/pkg/types"
+)
+
+var _ = Describe("Operating service accounts", func() {
+	var (
+		client                  *fakes.FakeClient
+		ctl                     test.Catalog
+		buildName, buildRunName string
+	)
+
+	Context("Retrieving service accounts", func() {
+
+		// init vars
+		buildName = "foobuild"
+		buildRunName = "foobuildrun"
+		client = &fakes.FakeClient{}
+		buildRunSample := ctl.DefaultBuildRun(buildRunName, buildName)
+
+		It("should return a modified one with a secret ref", func() {
+
+			// stub a GET API call for a service account
+			getClientStub := func(context context.Context, nn types.NamespacedName, object runtime.Object) error {
+				switch object := object.(type) {
+				case *corev1.ServiceAccount:
+					ctl.DefaultServiceAccount("foobar").DeepCopyInto(object)
+					return nil
+				}
+				return k8serrors.NewNotFound(schema.GroupResource{}, nn.Name)
+			}
+			// fake the calls with the above stub
+			client.GetCalls(getClientStub)
+
+			sa, err := resources.RetrieveServiceAccount(context.TODO(), client, ctl.BuildWithOutputSecret(buildName, "default", "foosecret"), buildRunSample)
+			Expect(err).To(BeNil())
+			// assert the build output secret is defined in the default SA
+			Expect(len(sa.Secrets)).To(Equal(1))
+		})
+
+		It("should provide a generated sa name", func() {
+			Expect(resources.GetGeneratedServiceAccountName(buildRunSample)).To(Equal(buildRunSample.Name + "-sa"))
+		})
+	})
+})
diff --git a/pkg/reconciler/buildrun/resources/strategies_test.go b/pkg/reconciler/buildrun/resources/strategies_test.go
new file mode 100644
index 00000000..d1442132
--- /dev/null
+++ b/pkg/reconciler/buildrun/resources/strategies_test.go
@@ -0,0 +1,71 @@
+// Copyright The Shipwright Contributors
+//
+// SPDX-License-Identifier: Apache-2.0
+
+package resources_test
+
+import (
+	"context"
+
+	. "github.com/onsi/ginkgo"
+	. "github.com/onsi/gomega"
+	buildv1alpha1 "github.com/shipwright-io/build/pkg/apis/build/v1alpha1"
+	"github.com/shipwright-io/build/pkg/controller/fakes"
+	"github.com/shipwright-io/build/pkg/reconciler/buildrun/resources"
+	"github.com/shipwright-io/build/test"
+	k8serrors "k8s.io/apimachinery/pkg/api/errors"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	"k8s.io/apimachinery/pkg/types"
+)
+
+var _ = Describe("Operating Build strategies", func() {
+	var (
+		client *fakes.FakeClient
+		ctl    test.Catalog
+	)
+
+	Context("Retrieving build strategies", func() {
+		client = &fakes.FakeClient{}
+		buildSample := ctl.BuildWithBuildStrategy("foobuild", "foostrategy", "foostrategy")
+
+		It("should return a cluster buildstrategy", func() {
+
+			// stub a GET API call with a cluster strategy
+			getClientStub := func(context context.Context, nn types.NamespacedName, object runtime.Object) error {
+				switch object := object.(type) {
+				case *buildv1alpha1.ClusterBuildStrategy:
+					ctl.DefaultClusterBuildStrategy().DeepCopyInto(object)
+					return nil
+				}
+				return k8serrors.NewNotFound(schema.GroupResource{}, nn.Name)
+			}
+			// fake the calls with the above stub
+			client.GetCalls(getClientStub)
+
+			cbs, err := resources.RetrieveClusterBuildStrategy(context.TODO(), client, buildSample)
+			Expect(err).To(BeNil())
+			Expect(cbs.Name).To(Equal("foobar"))
+		})
+
+		It("should return a namespaced buildstrategy", func() {
+
+			// stub a GET API call with a namespace strategy
+			getClientStub := func(context context.Context, nn types.NamespacedName, object runtime.Object) error {
+				switch object := object.(type) {
+				case *buildv1alpha1.BuildStrategy:
+					ctl.DefaultNamespacedBuildStrategy().DeepCopyInto(object)
+					return nil
+				}
+				return k8serrors.NewNotFound(schema.GroupResource{}, nn.Name)
+			}
+			// fake the calls with the above stub
+			client.GetCalls(getClientStub)
+
+			cbs, err := resources.RetrieveBuildStrategy(context.TODO(), client, buildSample)
+			Expect(err).To(BeNil())
+			Expect(cbs.Name).To(Equal("foobar"))
+		})
+
+	})
+})
diff --git a/test/catalog.go b/test/catalog.go
index d2aa964e..00803cdc 100644
--- a/test/catalog.go
+++ b/test/catalog.go
@@ -165,6 +165,26 @@ func (c *Catalog) BuildWithNilBuildStrategyKind(name string, ns string, strategy
 	}
 }
 
+// BuildWithOutputSecret ....
+func (c *Catalog) BuildWithOutputSecret(name string, ns string, secretName string) *build.Build {
+	return &build.Build{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      name,
+			Namespace: ns,
+		},
+		Spec: build.BuildSpec{
+			Source: build.GitSource{
+				URL: "https://github.com/qu1queee/taxi",
+			},
+			Output: build.Image{
+				SecretRef: &corev1.LocalObjectReference{
+					Name: secretName,
+				},
+			},
+		},
+	}
+}
+
 // ClusterBuildStrategy to support tests
 func (c *Catalog) ClusterBuildStrategy(name string) *build.ClusterBuildStrategy {
 	return &build.ClusterBuildStrategy{
@@ -450,6 +470,41 @@ func (c *Catalog) StubBuildCRDsPodAndTaskRun(
 	}
 }
 
+// TaskRunWithStatus returns a minimal tekton TaskRun with an Status
+func (c *Catalog) TaskRunWithStatus(trName string, ns string) *v1beta1.TaskRun {
+	return &v1beta1.TaskRun{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      trName,
+			Namespace: ns,
+		},
+		Spec: v1beta1.TaskRunSpec{
+			Timeout: &metav1.Duration{
+				Duration: time.Minute * 2,
+			},
+		},
+		Status: v1beta1.TaskRunStatus{
+			Status: knativev1beta1.Status{
+				Conditions: knativev1beta1.Conditions{
+					{
+						Type:   apis.ConditionSucceeded,
+						Reason: "Unknown",
+						Status: corev1.ConditionUnknown,
+					},
+				},
+			},
+			TaskRunStatusFields: v1beta1.TaskRunStatusFields{
+				PodName: "foobar-pod",
+				StartTime: &metav1.Time{
+					Time: time.Now(),
+				},
+				CompletionTime: &metav1.Time{
+					Time: time.Now(),
+				},
+			},
+		},
+	}
+}
+
 // DefaultTaskRunWithStatus returns a minimal tekton TaskRun with an Status
 func (c *Catalog) DefaultTaskRunWithStatus(trName string, buildRunName string, ns string, status corev1.ConditionStatus, reason string) *v1beta1.TaskRun {
 	return &v1beta1.TaskRun{
@@ -739,6 +794,21 @@ func (c *Catalog) DefaultServiceAccount(name string) *corev1.ServiceAccount {
 	}
 }
 
+// ServiceAccountWithControllerRef ... TODO
+func (c *Catalog) ServiceAccountWithControllerRef(name string) *corev1.ServiceAccount {
+	return &corev1.ServiceAccount{
+		ObjectMeta: metav1.ObjectMeta{
+			Name: name,
+			OwnerReferences: []metav1.OwnerReference{
+				{
+					Name: "ss",
+					Kind: "BuildRun",
+				},
+			},
+		},
+	}
+}
+
 // DefaultClusterBuildStrategy returns a minimal ClusterBuildStrategy
 // object with a inmutable name
 func (c *Catalog) DefaultClusterBuildStrategy() *build.ClusterBuildStrategy {