Repository: shipwright-io/build (go)

Issue:
SHIP-0039: Allow custom scheduler to be used for `Build` and `BuildRun`
# Feature

Allow the pod `schedulerName` field to be set for `Build` and `BuildRun` objects.

Part of [SHIP-0039](https://github.com/shipwright-io/community/blob/main/ships/0039-build-scheduler-opts.md)

Pull request description:
Add Custom Scheduler Name to Build and BuildRun objects
# Changes

<!-- 🎉🎉🎉 Thank you for the PR!!! 🎉🎉🎉 -->

<!-- Describe your changes here- ideally you can get that description straight from
your descriptive commit message(s)! -->

<!-- If this PR fixes a GitHub issue, please mention it like so:

Fixes #<insert issue number here>

-->

Fixes #1637 

# Submitter Checklist

- [x] Includes tests if functionality changed/was added
- [x] Includes docs if changes are user-facing
- [x] [Set a kind label on this PR](https://prow.k8s.io/command-help#kind)
- [x] Release notes block has been filled in, or marked NONE

See [the contributor guide](https://github.com/shipwright-io/build/blob/main/CONTRIBUTING.md)
for details on coding conventions, github and prow interactions, and the code review process.

# Release Notes

```release-note
Add Custom Scheduler Name to Build and BuildRun objects
```

Patch:
diff --git a/deploy/crds/shipwright.io_buildruns.yaml b/deploy/crds/shipwright.io_buildruns.yaml
index 9797b138..595fdf66 100644
--- a/deploy/crds/shipwright.io_buildruns.yaml
+++ b/deploy/crds/shipwright.io_buildruns.yaml
@@ -7439,6 +7439,10 @@ spec:
                             format: duration
                             type: string
                         type: object
+                      schedulerName:
+                        description: SchedulerName specifies the scheduler to be used
+                          to dispatch the Pod
+                        type: string
                       source:
                         description: |-
                           Source refers to the location where the source code is,
@@ -9753,6 +9757,10 @@ spec:
                     format: duration
                     type: string
                 type: object
+              schedulerName:
+                description: SchedulerName specifies the scheduler to be used to dispatch
+                  the Pod
+                type: string
               serviceAccount:
                 description: |-
                   ServiceAccount refers to the kubernetes serviceaccount
@@ -11941,6 +11949,10 @@ spec:
                         format: duration
                         type: string
                     type: object
+                  schedulerName:
+                    description: SchedulerName specifies the scheduler to be used
+                      to dispatch the Pod
+                    type: string
                   source:
                     description: |-
                       Source refers to the location where the source code is,
diff --git a/deploy/crds/shipwright.io_builds.yaml b/deploy/crds/shipwright.io_builds.yaml
index 9873c959..e91fe479 100644
--- a/deploy/crds/shipwright.io_builds.yaml
+++ b/deploy/crds/shipwright.io_builds.yaml
@@ -2818,6 +2818,10 @@ spec:
                     format: duration
                     type: string
                 type: object
+              schedulerName:
+                description: SchedulerName specifies the scheduler to be used to dispatch
+                  the Pod
+                type: string
               source:
                 description: |-
                   Source refers to the location where the source code is,
diff --git a/docs/build.md b/docs/build.md
index 3bab038d..a4df9a3f 100644
--- a/docs/build.md
+++ b/docs/build.md
@@ -41,6 +41,7 @@ A `Build` resource allows the user to define:
 - volumes
 - nodeSelector
 - tolerations
+- schedulerName
 
 A `Build` is available within a namespace.
 
@@ -97,6 +98,7 @@ To prevent users from triggering `BuildRun`s (_execution of a Build_) that will
 | OutputTimestampNotValid                         | The output timestamp value is not valid.                                                                                                                                                                     |
 | NodeSelectorNotValid                            | The specified nodeSelector is not valid. |
 | TolerationNotValid                              | The specified tolerations are not valid. |
+| SchedulerNameNotValid                              | The specified schedulerName is not valid. |
 
 ## Configuring a Build
 
@@ -130,6 +132,7 @@ The `Build` definition supports the following fields:
   - `spec.retention.succeededLimit` - Specifies the number of successful buildrun can exist.
   - `spec.nodeSelector` - Specifies a selector which must match a node's labels for the build pod to be scheduled on that node. If nodeSelectors are specified in both a `Build` and `BuildRun`, `BuildRun` values take precedence.
   - `spec.tolerations` - Specifies the tolerations for the build pod. Only `key`, `value`, and `operator` are supported. Only `NoSchedule` taint `effect` is supported. If tolerations are specified in both a `Build` and `BuildRun`, `BuildRun` values take precedence.
+  - `spec.schedulerName` - Specifies the scheduler name for the build pod. If schedulerName is specified in both a `Build` and `BuildRun`, `BuildRun` values take precedence.
 
 ### Defining the Source
 
diff --git a/docs/buildrun.md b/docs/buildrun.md
index 35866480..47f4898c 100644
--- a/docs/buildrun.md
+++ b/docs/buildrun.md
@@ -77,6 +77,7 @@ The `BuildRun` definition supports the following fields:
   - `spec.env` - Specifies additional environment variables that should be passed to the build container. Overrides any environment variables that are specified in the `Build` resource. The available variables depend on the tool used by the chosen build strategy.
   - `spec.nodeSelector` - Specifies a selector which must match a node's labels for the build pod to be scheduled on that node. If nodeSelectors are specified in both a `Build` and `BuildRun`, `BuildRun` values take precedence.
   - `spec.tolerations` - Specifies the tolerations for the build pod. Only `key`, `value`, and `operator` are supported. Only `NoSchedule` taint `effect` is supported. If tolerations are specified in both a `Build` and `BuildRun`, `BuildRun` values take precedence.
+  - `spec.schedulerName` - Specifies the scheduler name for the build pod. If schedulerName is specified in both a `Build` and `BuildRun`, `BuildRun` values take precedence.
 
 **Note**: The `spec.build.name` and `spec.build.spec` are mutually exclusive. Furthermore, the overrides for `timeout`, `paramValues`, `output`, and `env` can only be combined with `spec.build.name`, but **not** with `spec.build.spec`.
 
diff --git a/pkg/apis/build/v1beta1/build_types.go b/pkg/apis/build/v1beta1/build_types.go
index dff003e6..81d3970b 100644
--- a/pkg/apis/build/v1beta1/build_types.go
+++ b/pkg/apis/build/v1beta1/build_types.go
@@ -80,7 +80,8 @@ const (
 	NodeSelectorNotValid BuildReason = "NodeSelectorNotValid"
 	// TolerationNotValid indicates that the Toleration value is not valid
 	TolerationNotValid BuildReason = "TolerationNotValid"
-
+	// SchedulerNameNotValid indicates that the Scheduler name is not valid
+	SchedulerNameNotValid BuildReason = "SchedulerNameNotValid"
 	// AllValidationsSucceeded indicates a Build was successfully validated
 	AllValidationsSucceeded = "all validations succeeded"
 )
@@ -191,6 +192,10 @@ type BuildSpec struct {
 	// +patchMergeKey=Key
 	// +patchStrategy=merge
 	Tolerations []corev1.Toleration `json:"tolerations,omitempty" patchStrategy:"merge" patchMergeKey:"Key"`
+
+	// SchedulerName specifies the scheduler to be used to dispatch the Pod
+	// +optional
+	SchedulerName *string `json:"schedulerName,omitempty"`
 }
 
 // BuildVolume is a volume that will be mounted in build pod during build step
diff --git a/pkg/apis/build/v1beta1/buildrun_types.go b/pkg/apis/build/v1beta1/buildrun_types.go
index cc3b8461..3e4cc2d2 100644
--- a/pkg/apis/build/v1beta1/buildrun_types.go
+++ b/pkg/apis/build/v1beta1/buildrun_types.go
@@ -121,6 +121,10 @@ type BuildRunSpec struct {
 	// +patchMergeKey=Key
 	// +patchStrategy=merge
 	Tolerations []corev1.Toleration `json:"tolerations,omitempty" patchStrategy:"merge" patchMergeKey:"Key"`
+
+	// SchedulerName specifies the scheduler to be used to dispatch the Pod
+	// +optional
+	SchedulerName *string `json:"schedulerName,omitempty"`
 }
 
 // BuildRunRequestedState defines the buildrun state the user can provide to override whatever is the current state.
diff --git a/pkg/reconciler/build/build.go b/pkg/reconciler/build/build.go
index 805d13a6..0a56bad3 100644
--- a/pkg/reconciler/build/build.go
+++ b/pkg/reconciler/build/build.go
@@ -35,6 +35,7 @@ var validationTypes = [...]string{
 	validate.Triggers,
 	validate.NodeSelector,
 	validate.Tolerations,
+	validate.SchedulerName,
 }
 
 // ReconcileBuild reconciles a Build object
diff --git a/pkg/reconciler/buildrun/buildrun.go b/pkg/reconciler/buildrun/buildrun.go
index 203e8b46..5c6609d1 100644
--- a/pkg/reconciler/buildrun/buildrun.go
+++ b/pkg/reconciler/buildrun/buildrun.go
@@ -162,6 +162,7 @@ func (r *ReconcileBuildRun) Reconcile(ctx context.Context, request reconcile.Req
 						validate.NewEnv(build),
 						validate.NewNodeSelector(build),
 						validate.NewTolerations(build),
+						validate.NewSchedulerName(build),
 					)
 
 					// an internal/technical error during validation happened
@@ -310,6 +311,15 @@ func (r *ReconcileBuildRun) Reconcile(ctx context.Context, request reconcile.Req
 				return reconcile.Result{}, nil
 			}
 
+			// Validate the schedulerName
+			valid, reason, message = validate.BuildRunSchedulerName(buildRun.Spec.SchedulerName)
+			if !valid {
+				if err := resources.UpdateConditionWithFalseStatus(ctx, r.client, buildRun, message, reason); err != nil {
+					return reconcile.Result{}, err
+				}
+				return reconcile.Result{}, nil
+			}
+
 			// Create the TaskRun, this needs to be the last step in this block to be idempotent
 			generatedTaskRun, err := r.createTaskRun(ctx, svcAccount, strategy, build, buildRun)
 			if err != nil {
diff --git a/pkg/reconciler/buildrun/resources/taskrun.go b/pkg/reconciler/buildrun/resources/taskrun.go
index f93bd9b9..abe3c78b 100644
--- a/pkg/reconciler/buildrun/resources/taskrun.go
+++ b/pkg/reconciler/buildrun/resources/taskrun.go
@@ -255,6 +255,13 @@ func GenerateTaskRun(
 		taskRunPodTemplate.Tolerations = taskRunTolerations
 	}
 
+	// Set custom scheduler name if specified, giving preference to BuildRun values
+	if buildRun.Spec.SchedulerName != nil {
+		taskRunPodTemplate.SchedulerName = *buildRun.Spec.SchedulerName
+	} else if build.Spec.SchedulerName != nil {
+		taskRunPodTemplate.SchedulerName = *build.Spec.SchedulerName
+	}
+
 	if !(taskRunPodTemplate.Equals(&pod.PodTemplate{})) {
 		expectedTaskRun.Spec.PodTemplate = taskRunPodTemplate
 	}
diff --git a/pkg/validate/nodeselector.go b/pkg/validate/nodeselector.go
index eee0cd01..3c1aa519 100644
--- a/pkg/validate/nodeselector.go
+++ b/pkg/validate/nodeselector.go
@@ -28,17 +28,11 @@ func NewNodeSelector(build *build.Build) *NodeSelectorRef {
 // ValidatePath implements BuildPath interface and validates
 // that NodeSelector keys/values are valid labels
 func (b *NodeSelectorRef) ValidatePath(_ context.Context) error {
-	for key, value := range b.Build.Spec.NodeSelector {
-		if errs := validation.IsQualifiedName(key); len(errs) > 0 {
-			b.Build.Status.Reason = ptr.To(build.NodeSelectorNotValid)
-			b.Build.Status.Message = ptr.To(fmt.Sprintf("Node selector key not valid: %v", strings.Join(errs, ", ")))
-		}
-		if errs := validation.IsValidLabelValue(value); len(errs) > 0 {
-			b.Build.Status.Reason = ptr.To(build.NodeSelectorNotValid)
-			b.Build.Status.Message = ptr.To(fmt.Sprintf("Node selector value not valid: %v", strings.Join(errs, ", ")))
-		}
+	ok, reason, msg := BuildRunNodeSelector(b.Build.Spec.NodeSelector)
+	if !ok {
+		b.Build.Status.Reason = ptr.To(build.BuildReason(reason))
+		b.Build.Status.Message = ptr.To(msg)
 	}
-
 	return nil
 }
 
diff --git a/pkg/validate/scheduler_name.go b/pkg/validate/scheduler_name.go
new file mode 100644
index 00000000..61d7f21e
--- /dev/null
+++ b/pkg/validate/scheduler_name.go
@@ -0,0 +1,47 @@
+// Copyright The Shipwright Contributors
+//
+// SPDX-License-Identifier: Apache-2.0
+
+package validate
+
+import (
+	"context"
+	"fmt"
+	"strings"
+
+	"k8s.io/apimachinery/pkg/util/validation"
+	"k8s.io/utils/ptr"
+
+	build "github.com/shipwright-io/build/pkg/apis/build/v1beta1"
+)
+
+// SchedulerNameRef contains all required fields
+// to validate a Scheduler name
+type SchedulerNameRef struct {
+	Build *build.Build // build instance for analysis
+}
+
+func NewSchedulerName(build *build.Build) *SchedulerNameRef {
+	return &SchedulerNameRef{build}
+}
+
+// ValidatePath implements BuildPath interface and validates
+// that SchedulerName values are valid
+func (b *SchedulerNameRef) ValidatePath(_ context.Context) error {
+	ok, reason, msg := BuildRunSchedulerName(b.Build.Spec.SchedulerName)
+	if !ok {
+		b.Build.Status.Reason = ptr.To(build.BuildReason(reason))
+		b.Build.Status.Message = ptr.To(msg)
+	}
+	return nil
+}
+
+// BuildSchedulerName is used to validate the schedulerName in the BuildRun object
+func BuildRunSchedulerName(schedulerName *string) (bool, string, string) {
+	if schedulerName != nil {
+		if errs := validation.IsQualifiedName(*schedulerName); len(errs) > 0 {
+			return false, string(build.SchedulerNameNotValid), fmt.Sprintf("Scheduler name not valid: %v", strings.Join(errs, ", "))
+		}
+	}
+	return true, "", ""
+}
diff --git a/pkg/validate/tolerations.go b/pkg/validate/tolerations.go
index ec2fdae8..51269b75 100644
--- a/pkg/validate/tolerations.go
+++ b/pkg/validate/tolerations.go
@@ -29,34 +29,11 @@ func NewTolerations(build *build.Build) *TolerationsRef {
 // ValidatePath implements BuildPath interface and validates
 // that tolerations key/operator/value are valid
 func (b *TolerationsRef) ValidatePath(_ context.Context) error {
-	for _, toleration := range b.Build.Spec.Tolerations {
-		// validate Key
-		if errs := validation.IsQualifiedName(toleration.Key); errs != nil {
-			b.Build.Status.Reason = ptr.To(build.TolerationNotValid)
-			b.Build.Status.Message = ptr.To(fmt.Sprintf("Toleration key not valid: %v", strings.Join(errs, ", ")))
-		}
-		// validate Operator
-		if !((toleration.Operator == v1.TolerationOpExists) || (toleration.Operator == v1.TolerationOpEqual)) {
-			b.Build.Status.Reason = ptr.To(build.TolerationNotValid)
-			b.Build.Status.Message = ptr.To(fmt.Sprintf("Toleration operator not valid. Must be one of: '%v', '%v'", v1.TolerationOpExists, v1.TolerationOpEqual))
-		}
-		// validate Value
-		if errs := validation.IsValidLabelValue(toleration.Value); errs != nil {
-			b.Build.Status.Reason = ptr.To(build.TolerationNotValid)
-			b.Build.Status.Message = ptr.To(fmt.Sprintf("Toleration value not valid: %v", strings.Join(errs, ", ")))
-		}
-		// validate Taint Effect, of which only "NoSchedule" is supported
-		if !((toleration.Effect) == "" || (toleration.Effect == v1.TaintEffectNoSchedule)) {
-			b.Build.Status.Reason = ptr.To(build.TolerationNotValid)
-			b.Build.Status.Message = ptr.To(fmt.Sprintf("Only the '%v' toleration effect is supported.", v1.TaintEffectNoSchedule))
-		}
-		// validate TolerationSeconds, which should not be specified
-		if toleration.TolerationSeconds != nil {
-			b.Build.Status.Reason = ptr.To(build.TolerationNotValid)
-			b.Build.Status.Message = ptr.To("Specifying TolerationSeconds is not supported.")
-		}
+	ok, reason, msg := BuildRunTolerations(b.Build.Spec.Tolerations)
+	if !ok {
+		b.Build.Status.Reason = ptr.To(build.BuildReason(reason))
+		b.Build.Status.Message = ptr.To(msg)
 	}
-
 	return nil
 }
 
diff --git a/pkg/validate/validate.go b/pkg/validate/validate.go
index e8228f70..0b2d0ec9 100644
--- a/pkg/validate/validate.go
+++ b/pkg/validate/validate.go
@@ -39,6 +39,8 @@ const (
 	NodeSelector = "nodeselector"
 	// Tolerations for validating `spec.tolerations` entry
 	Tolerations = "tolerations"
+	// SchedulerName for validating `spec.schedulerName` entry
+	SchedulerName = "schedulername"
 )
 
 const (
@@ -83,6 +85,8 @@ func NewValidation(
 		return &NodeSelectorRef{Build: build}, nil
 	case Tolerations:
 		return &TolerationsRef{Build: build}, nil
+	case SchedulerName:
+		return &SchedulerNameRef{Build: build}, nil
 	default:
 		return nil, fmt.Errorf("unknown validation type")
 	}
@@ -147,6 +151,11 @@ func BuildRunFields(buildRun *build.BuildRun) (string, string) {
 			return resources.BuildRunBuildFieldOverrideForbidden,
 				"cannot use 'tolerations' override and 'buildSpec' simultaneously"
 		}
+
+		if buildRun.Spec.SchedulerName != nil {
+			return resources.BuildRunBuildFieldOverrideForbidden,
+				"cannot use 'schedulerName' override and 'buildSpec' simultaneously"
+		}
 	}
 
 	return "", ""


Test patch:
diff --git a/pkg/reconciler/build/build_test.go b/pkg/reconciler/build/build_test.go
index c8d07d7f..6ed664cc 100644
--- a/pkg/reconciler/build/build_test.go
+++ b/pkg/reconciler/build/build_test.go
@@ -645,5 +645,20 @@ var _ = Describe("Reconcile Build", func() {
 				Expect(statusWriter.UpdateCallCount()).To(Equal(1))
 			})
 		})
+
+		Context("when SchedulerName is specified", func() {
+			It("should fail to validate when the SchedulerName is invalid", func() {
+				// set SchedulerName to be invalid
+				buildSample.Spec.SchedulerName = ptr.To(strings.Repeat("s", 64))
+				buildSample.Spec.Output.PushSecret = nil
+
+				statusCall := ctl.StubFunc(corev1.ConditionFalse, build.SchedulerNameNotValid, "Scheduler name not valid: name part "+validation.MaxLenError(63))
+				statusWriter.UpdateCalls(statusCall)
+
+				_, err := reconciler.Reconcile(context.TODO(), request)
+				Expect(err).To(BeNil())
+				Expect(statusWriter.UpdateCallCount()).To(Equal(1))
+			})
+		})
 	})
 })
diff --git a/pkg/reconciler/buildrun/buildrun_test.go b/pkg/reconciler/buildrun/buildrun_test.go
index 18771f3a..0d2e0182 100644
--- a/pkg/reconciler/buildrun/buildrun_test.go
+++ b/pkg/reconciler/buildrun/buildrun_test.go
@@ -1660,5 +1660,64 @@ var _ = Describe("Reconcile BuildRun", func() {
 				Expect(statusWriter.UpdateCallCount()).To(Equal(1))
 			})
 		})
+
+		Context("when SchedulerName is specified", func() {
+			It("should fail to validate when the SchedulerName is invalid", func() {
+				// set SchedulerName to be invalid
+				buildRunSample.Spec.SchedulerName = ptr.To(strings.Repeat("s", 64))
+
+				statusCall := ctl.StubFunc(corev1.ConditionFalse, build.SchedulerNameNotValid, validation.MaxLenError(64))
+				statusWriter.UpdateCalls(statusCall)
+
+				_, err := reconciler.Reconcile(context.TODO(), buildRunRequest)
+				Expect(err).To(BeNil())
+				Expect(statusWriter.UpdateCallCount()).To(Equal(1))
+			})
+		})
+
+		Context("when a buildrun has a buildSpec defined and overrides nodeSelector", func() {
+			BeforeEach(func() {
+				buildRunSample = ctl.BuildRunWithNodeSelectorOverride(buildRunName, buildName, map[string]string{"Key": "testkey", "Value": "testvalue"})
+			})
+
+			It("should fail to register", func() {
+				statusCall := ctl.StubFunc(corev1.ConditionFalse, build.BuildReason(resources.BuildRunBuildFieldOverrideForbidden), "cannot use 'nodeSelector' override and 'buildSpec' simultaneously")
+				statusWriter.UpdateCalls(statusCall)
+
+				_, err := reconciler.Reconcile(context.TODO(), buildRunRequest)
+				Expect(err).To(BeNil())
+				Expect(statusWriter.UpdateCallCount()).To(Equal(1))
+			})
+		})
+
+		Context("when a buildrun has a buildSpec defined and overrides Tolerations", func() {
+			BeforeEach(func() {
+				buildRunSample = ctl.BuildRunWithTolerationsOverride(buildRunName, buildName, []corev1.Toleration{{Key: "testkey", Value: "testvalue", Operator: corev1.TolerationOpEqual, Effect: corev1.TaintEffectNoSchedule}})
+			})
+
+			It("should fail to register", func() {
+				statusCall := ctl.StubFunc(corev1.ConditionFalse, build.BuildReason(resources.BuildRunBuildFieldOverrideForbidden), "cannot use 'tolerations' override and 'buildSpec' simultaneously")
+				statusWriter.UpdateCalls(statusCall)
+
+				_, err := reconciler.Reconcile(context.TODO(), buildRunRequest)
+				Expect(err).To(BeNil())
+				Expect(statusWriter.UpdateCallCount()).To(Equal(1))
+			})
+		})
+
+		Context("when a buildrun has a buildSpec defined and overrides schedulerName", func() {
+			BeforeEach(func() {
+				buildRunSample = ctl.BuildRunWithSchedulerNameOverride(buildRunName, buildName, "testSchedulerName")
+			})
+
+			It("should fail to register", func() {
+				statusCall := ctl.StubFunc(corev1.ConditionFalse, build.BuildReason(resources.BuildRunBuildFieldOverrideForbidden), "cannot use 'schedulerName' override and 'buildSpec' simultaneously")
+				statusWriter.UpdateCalls(statusCall)
+
+				_, err := reconciler.Reconcile(context.TODO(), buildRunRequest)
+				Expect(err).To(BeNil())
+				Expect(statusWriter.UpdateCallCount()).To(Equal(1))
+			})
+		})
 	})
 })
diff --git a/pkg/reconciler/buildrun/resources/taskrun_test.go b/pkg/reconciler/buildrun/resources/taskrun_test.go
index c81b0732..c2c178e6 100644
--- a/pkg/reconciler/buildrun/resources/taskrun_test.go
+++ b/pkg/reconciler/buildrun/resources/taskrun_test.go
@@ -678,5 +678,27 @@ var _ = Describe("GenerateTaskrun", func() {
 				Expect(got.Spec.PodTemplate.Tolerations[0].Value).To(Equal(buildRun.Spec.Tolerations[0].Value))
 			})
 		})
+
+		Context("when the build and buildrun both specify a SchedulerName", func() {
+			BeforeEach(func() {
+				build, err = ctl.LoadBuildYAML([]byte(test.MinimalBuildWithSchedulerName))
+				Expect(err).To(BeNil())
+
+				buildRun, err = ctl.LoadBuildRunFromBytes([]byte(test.MinimalBuildRunWithSchedulerName))
+				Expect(err).To(BeNil())
+
+				buildStrategy, err = ctl.LoadBuildStrategyFromBytes([]byte(test.ClusterBuildStrategyNoOp))
+				Expect(err).To(BeNil())
+			})
+
+			JustBeforeEach(func() {
+				got, err = resources.GenerateTaskRun(config.NewDefaultConfig(), build, buildRun, serviceAccountName, buildStrategy)
+				Expect(err).To(BeNil())
+			})
+
+			It("should give precedence to the SchedulerName value specified in the buildRun", func() {
+				Expect(got.Spec.PodTemplate.SchedulerName).To(Equal(*buildRun.Spec.SchedulerName))
+			})
+		})
 	})
 })
diff --git a/pkg/validate/nodeselector_test.go b/pkg/validate/nodeselector_test.go
new file mode 100644
index 00000000..24d6bcb2
--- /dev/null
+++ b/pkg/validate/nodeselector_test.go
@@ -0,0 +1,109 @@
+// Copyright The Shipwright Contributors
+//
+// SPDX-License-Identifier: Apache-2.0
+
+package validate_test
+
+import (
+	"context"
+
+	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/gomega"
+
+	corev1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+	. "github.com/shipwright-io/build/pkg/apis/build/v1beta1"
+	"github.com/shipwright-io/build/pkg/validate"
+)
+
+var _ = Describe("ValidateNodeSelector", func() {
+	var ctx context.Context
+
+	BeforeEach(func() {
+		ctx = context.TODO()
+	})
+
+	var validate = func(build *Build) {
+		GinkgoHelper()
+
+		var validator = &validate.NodeSelectorRef{Build: build}
+		Expect(validator.ValidatePath(ctx)).To(Succeed())
+	}
+
+	var sampleBuild = func(key string, value string) *Build {
+		return &Build{
+			ObjectMeta: corev1.ObjectMeta{
+				Namespace: "foo",
+				Name:      "bar",
+			},
+			Spec: BuildSpec{
+				NodeSelector: map[string]string{key: value},
+			},
+		}
+	}
+
+	Context("when node selector is specified", func() {
+		It("should fail an empty key and value", func() {
+			build := sampleBuild("", "")
+			validate(build)
+			Expect(*build.Status.Reason).To(Equal(NodeSelectorNotValid))
+			Expect(*build.Status.Message).To(ContainSubstring("Node selector key not valid"))
+		})
+
+		It("should fail an empty key and valid value", func() {
+			build := sampleBuild("", "validvalue")
+			validate(build)
+			Expect(*build.Status.Reason).To(Equal(NodeSelectorNotValid))
+			Expect(*build.Status.Message).To(ContainSubstring("Node selector key not valid"))
+		})
+
+		It("should fail an empty key and invalid value", func() {
+			build := sampleBuild("", "invalidvalue!")
+			validate(build)
+			Expect(*build.Status.Reason).To(Equal(NodeSelectorNotValid))
+			Expect(*build.Status.Message).To(ContainSubstring("Node selector key not valid"))
+		})
+
+		It("should pass a valid key and valid value", func() {
+			build := sampleBuild("validkey", "validvalue")
+			validate(build)
+			Expect(build.Status.Reason).To(BeNil())
+			Expect(build.Status.Message).To(BeNil())
+		})
+
+		It("should fail a valid key and invalid value", func() {
+			build := sampleBuild("validkey", "invalidvalue!")
+			validate(build)
+			Expect(*build.Status.Reason).To(Equal(NodeSelectorNotValid))
+			Expect(*build.Status.Message).To(ContainSubstring("Node selector value not valid"))
+		})
+
+		It("should pass a valid key and empty value", func() {
+			build := sampleBuild("validkey", "")
+			validate(build)
+			Expect(build.Status.Reason).To(BeNil())
+			Expect(build.Status.Message).To(BeNil())
+		})
+
+		It("should fail an invalid key and empty value", func() {
+			build := sampleBuild("invalidkey!", "")
+			validate(build)
+			Expect(*build.Status.Reason).To(Equal(NodeSelectorNotValid))
+			Expect(*build.Status.Message).To(ContainSubstring("Node selector key not valid"))
+		})
+
+		It("should fail an invalid key and valid value", func() {
+			build := sampleBuild("invalidkey!", "validvalue")
+			validate(build)
+			Expect(*build.Status.Reason).To(Equal(NodeSelectorNotValid))
+			Expect(*build.Status.Message).To(ContainSubstring("Node selector key not valid"))
+		})
+
+		It("should fail both an invalid key and invalid value", func() {
+			build := sampleBuild("invalidkey!", "invalidvalue!")
+			validate(build)
+			Expect(*build.Status.Reason).To(Equal(NodeSelectorNotValid))
+			Expect(*build.Status.Message).To(ContainSubstring("Node selector key not valid"))
+		})
+	})
+})
diff --git a/pkg/validate/scheduler_name_test.go b/pkg/validate/scheduler_name_test.go
new file mode 100644
index 00000000..4e8338f1
--- /dev/null
+++ b/pkg/validate/scheduler_name_test.go
@@ -0,0 +1,67 @@
+// Copyright The Shipwright Contributors
+//
+// SPDX-License-Identifier: Apache-2.0
+
+package validate_test
+
+import (
+	"context"
+
+	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/gomega"
+
+	corev1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+	. "github.com/shipwright-io/build/pkg/apis/build/v1beta1"
+	"github.com/shipwright-io/build/pkg/validate"
+)
+
+var _ = Describe("ValidateSchedulerName", func() {
+	var ctx context.Context
+
+	BeforeEach(func() {
+		ctx = context.TODO()
+	})
+
+	var validate = func(build *Build) {
+		GinkgoHelper()
+
+		var validator = &validate.SchedulerNameRef{Build: build}
+		Expect(validator.ValidatePath(ctx)).To(Succeed())
+	}
+
+	var sampleBuild = func(schedulerName string) *Build {
+		return &Build{
+			ObjectMeta: corev1.ObjectMeta{
+				Namespace: "foo",
+				Name:      "bar",
+			},
+			Spec: BuildSpec{
+				SchedulerName: &schedulerName,
+			},
+		}
+	}
+
+	Context("when schedulerName is specified", func() {
+		It("should fail an empty name", func() {
+			build := sampleBuild("")
+			validate(build)
+			Expect(*build.Status.Reason).To(Equal(SchedulerNameNotValid))
+			Expect(*build.Status.Message).To(ContainSubstring("Scheduler name not valid"))
+		})
+
+		It("should fail an invalid name", func() {
+			build := sampleBuild("invalidname!")
+			validate(build)
+			Expect(*build.Status.Reason).To(Equal(SchedulerNameNotValid))
+			Expect(*build.Status.Message).To(ContainSubstring("Scheduler name not valid"))
+		})
+
+		It("should pass a valid name", func() {
+			build := sampleBuild("validname")
+			validate(build)
+			Expect(build.Status.Reason).To(BeNil())
+			Expect(build.Status.Message).To(BeNil())
+		})
+	})
+})
diff --git a/pkg/validate/tolerations_test.go b/pkg/validate/tolerations_test.go
new file mode 100644
index 00000000..95ea0c0a
--- /dev/null
+++ b/pkg/validate/tolerations_test.go
@@ -0,0 +1,127 @@
+// Copyright The Shipwright Contributors
+//
+// SPDX-License-Identifier: Apache-2.0
+
+package validate_test
+
+import (
+	"context"
+	"fmt"
+
+	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/gomega"
+
+	v1 "k8s.io/api/core/v1"
+	corev1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/util/validation"
+	"k8s.io/utils/ptr"
+
+	. "github.com/shipwright-io/build/pkg/apis/build/v1beta1"
+	"github.com/shipwright-io/build/pkg/validate"
+)
+
+var _ = Describe("ValidateTolerations", func() {
+	var ctx context.Context
+
+	BeforeEach(func() {
+		ctx = context.TODO()
+	})
+
+	var validate = func(build *Build) {
+		GinkgoHelper()
+
+		var validator = &validate.TolerationsRef{Build: build}
+		Expect(validator.ValidatePath(ctx)).To(Succeed())
+	}
+
+	var sampleBuild = func(toleration v1.Toleration) *Build {
+		return &Build{
+			ObjectMeta: corev1.ObjectMeta{
+				Namespace: "foo",
+				Name:      "bar",
+			},
+			Spec: BuildSpec{
+				Tolerations: []v1.Toleration{toleration},
+			},
+		}
+	}
+
+	Context("when tolerations is specified", func() {
+		It("should fail an empty key and empty value", func() {
+			build := sampleBuild(v1.Toleration{Key: "", Value: "", Operator: v1.TolerationOpEqual, Effect: v1.TaintEffectNoSchedule})
+			validate(build)
+			Expect(*build.Status.Reason).To(Equal(TolerationNotValid))
+			Expect(*build.Status.Message).To(ContainSubstring(validation.EmptyError()))
+		})
+
+		It("should pass a valid key and valid value", func() {
+			build := sampleBuild(v1.Toleration{Key: "validkey", Value: "validvalue", Operator: v1.TolerationOpEqual, Effect: v1.TaintEffectNoSchedule})
+			validate(build)
+			Expect(build.Status.Reason).To(BeNil())
+			Expect(build.Status.Message).To(BeNil())
+		})
+
+		It("should pass a valid key and empty value", func() {
+			build := sampleBuild(v1.Toleration{Key: "validkey", Value: "", Operator: v1.TolerationOpEqual, Effect: v1.TaintEffectNoSchedule})
+			validate(build)
+			Expect(build.Status.Reason).To(BeNil())
+			Expect(build.Status.Message).To(BeNil())
+		})
+
+		It("should fail an invalid key and empty value", func() {
+			build := sampleBuild(v1.Toleration{Key: "invalidkey!", Value: "", Operator: v1.TolerationOpEqual, Effect: v1.TaintEffectNoSchedule})
+			validate(build)
+			Expect(*build.Status.Reason).To(Equal(TolerationNotValid))
+			Expect(*build.Status.Message).To(ContainSubstring("Toleration key not valid"))
+		})
+
+		It("should fail an invalid key and invalid value", func() {
+			build := sampleBuild(v1.Toleration{Key: "invalidkey!", Value: "invalidvalue!", Operator: v1.TolerationOpEqual, Effect: v1.TaintEffectNoSchedule})
+			validate(build)
+			Expect(*build.Status.Reason).To(Equal(TolerationNotValid))
+			Expect(*build.Status.Message).To(ContainSubstring("Toleration key not valid"))
+		})
+
+		It("should fail a valid key and invalid value", func() {
+			build := sampleBuild(v1.Toleration{Key: "validkey", Value: "invalidvalue!", Operator: v1.TolerationOpEqual, Effect: v1.TaintEffectNoSchedule})
+			validate(build)
+			Expect(*build.Status.Reason).To(Equal(TolerationNotValid))
+			Expect(*build.Status.Message).To(ContainSubstring("Toleration value not valid"))
+		})
+
+		It("should fail an invalid operator", func() {
+			build := sampleBuild(v1.Toleration{Key: "validkey", Value: "validvalue", Operator: "invalidoperator", Effect: v1.TaintEffectNoSchedule})
+			validate(build)
+			Expect(*build.Status.Reason).To(Equal(TolerationNotValid))
+			Expect(*build.Status.Message).To(ContainSubstring("Toleration operator not valid"))
+		})
+
+		It("should pass an empty taint effect", func() {
+			build := sampleBuild(v1.Toleration{Key: "validkey", Value: "validvalue", Operator: v1.TolerationOpEqual, Effect: ""})
+			validate(build)
+			Expect(build.Status.Reason).To(BeNil())
+			Expect(build.Status.Message).To(BeNil())
+		})
+
+		It("should pass a taint effect of NoSchedule", func() {
+			build := sampleBuild(v1.Toleration{Key: "validkey", Value: "validvalue", Operator: v1.TolerationOpEqual, Effect: v1.TaintEffectNoSchedule})
+			validate(build)
+			Expect(build.Status.Reason).To(BeNil())
+			Expect(build.Status.Message).To(BeNil())
+		})
+
+		It("should fail an invalid taint effect", func() {
+			build := sampleBuild(v1.Toleration{Key: "validkey", Value: "validvalue", Operator: v1.TolerationOpEqual, Effect: v1.TaintEffectNoExecute})
+			validate(build)
+			Expect(*build.Status.Reason).To(Equal(TolerationNotValid))
+			Expect(*build.Status.Message).To(ContainSubstring(fmt.Sprintf("Only the '%v' toleration effect is supported.", v1.TaintEffectNoSchedule)))
+		})
+
+		It("should fail specifying tolerationSeconds", func() {
+			build := sampleBuild(v1.Toleration{Key: "validkey", Value: "validvalue", Operator: v1.TolerationOpEqual, Effect: v1.TaintEffectNoSchedule, TolerationSeconds: ptr.To(int64(10))})
+			validate(build)
+			Expect(*build.Status.Reason).To(Equal(TolerationNotValid))
+			Expect(*build.Status.Message).To(ContainSubstring("Specifying TolerationSeconds is not supported"))
+		})
+	})
+})
diff --git a/test/e2e/v1beta1/e2e_test.go b/test/e2e/v1beta1/e2e_test.go
index 2c9c1e08..4e88ed78 100644
--- a/test/e2e/v1beta1/e2e_test.go
+++ b/test/e2e/v1beta1/e2e_test.go
@@ -11,14 +11,13 @@ import (
 	v1 "github.com/google/go-containerregistry/pkg/v1"
 	. "github.com/onsi/ginkgo/v2"
 	. "github.com/onsi/gomega"
+	corev1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/api/errors"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/types"
 
 	buildv1beta1 "github.com/shipwright-io/build/pkg/apis/build/v1beta1"
 	shpgit "github.com/shipwright-io/build/pkg/git"
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-
-	corev1 "k8s.io/api/core/v1"
 )
 
 var _ = Describe("For a Kubernetes cluster with Tekton and build installed", func() {
diff --git a/test/integration/build_to_taskruns_test.go b/test/integration/build_to_taskruns_test.go
index 675305e1..df3349fb 100644
--- a/test/integration/build_to_taskruns_test.go
+++ b/test/integration/build_to_taskruns_test.go
@@ -10,6 +10,7 @@ import (
 	. "github.com/onsi/ginkgo/v2"
 	. "github.com/onsi/gomega"
 	corev1 "k8s.io/api/core/v1"
+	"k8s.io/utils/ptr"
 
 	"github.com/shipwright-io/build/pkg/apis/build/v1beta1"
 	utils "github.com/shipwright-io/build/test/utils/v1beta1"
@@ -292,4 +293,46 @@ var _ = Describe("Integration tests Build and TaskRun", func() {
 			})
 		})
 	})
+
+	Context("when a build with SchedulerName is defined", func() {
+		BeforeEach(func() {
+			buildSample = []byte(test.MinimalBuildWithSchedulerName)
+			buildRunSample = []byte(test.MinimalBuildRun)
+		})
+
+		Context("when the TaskRun is created", func() {
+			It("should have the SchedulerName specified in the PodTemplate", func() {
+				Expect(tb.CreateBuild(buildObject)).To(BeNil())
+
+				buildObject, err = tb.GetBuildTillValidation(buildObject.Name)
+				Expect(err).To(BeNil())
+				Expect(*buildObject.Status.Message).To(Equal(v1beta1.AllValidationsSucceeded))
+				Expect(*buildObject.Status.Registered).To(Equal(corev1.ConditionTrue))
+				Expect(*buildObject.Status.Reason).To(Equal(v1beta1.SucceedStatus))
+
+				Expect(tb.CreateBR(buildRunObject)).To(BeNil())
+
+				_, err = tb.GetBRTillStartTime(buildRunObject.Name)
+				Expect(err).To(BeNil())
+
+				tr, err := tb.GetTaskRunFromBuildRun(buildRunObject.Name)
+				Expect(err).To(BeNil())
+				Expect(tr.Spec.PodTemplate.SchedulerName).To(Equal(*buildObject.Spec.SchedulerName))
+			})
+		})
+
+		Context("when the SchedulerName is invalid", func() {
+			It("fails the build with a proper error in Reason", func() {
+				// set SchedulerName to be invalid
+				buildObject.Spec.SchedulerName = ptr.To(strings.Repeat("s", 64))
+				Expect(tb.CreateBuild(buildObject)).To(BeNil())
+
+				buildObject, err = tb.GetBuildTillValidation(buildObject.Name)
+				Expect(err).To(BeNil())
+
+				Expect(*buildObject.Status.Registered).To(Equal(corev1.ConditionFalse))
+				Expect(*buildObject.Status.Reason).To(Equal(v1beta1.SchedulerNameNotValid))
+			})
+		})
+	})
 })
diff --git a/test/integration/buildruns_to_taskruns_test.go b/test/integration/buildruns_to_taskruns_test.go
index 585f8f7a..9782b6c1 100644
--- a/test/integration/buildruns_to_taskruns_test.go
+++ b/test/integration/buildruns_to_taskruns_test.go
@@ -24,6 +24,7 @@ import (
 	"k8s.io/apimachinery/pkg/util/validation"
 	"k8s.io/apimachinery/pkg/util/wait"
 	"k8s.io/apimachinery/pkg/watch"
+	"k8s.io/utils/ptr"
 )
 
 var _ = Describe("Integration tests BuildRuns and TaskRuns", func() {
@@ -633,4 +634,122 @@ var _ = Describe("Integration tests BuildRuns and TaskRuns", func() {
 			})
 		})
 	})
+
+	Context("when a buildrun is created with a SchedulerName defined", func() {
+		BeforeEach(func() {
+			buildSample = []byte(test.MinimalBuildWithSchedulerName)
+			buildRunSample = []byte(test.MinimalBuildRunWithSchedulerName)
+		})
+
+		Context("when the taskrun is created", func() {
+			It("should have the SchedulerName specified in the PodTemplate", func() {
+				Expect(tb.CreateBuild(buildObject)).To(BeNil())
+
+				buildObject, err = tb.GetBuildTillValidation(buildObject.Name)
+				Expect(err).To(BeNil())
+
+				Expect(tb.CreateBR(buildRunObject)).To(BeNil())
+
+				br, err := tb.GetBRTillStartTime(buildRunObject.Name)
+				Expect(err).To(BeNil())
+
+				tr, err := tb.GetTaskRunFromBuildRun(buildRunObject.Name)
+				Expect(err).To(BeNil())
+				Expect(tr.Spec.PodTemplate.SchedulerName).To(Equal(*br.Spec.SchedulerName))
+			})
+		})
+
+		Context("when the SchedulerName is invalid", func() {
+			It("fails the buildrun with a proper error in Reason", func() {
+				Expect(tb.CreateBuild(buildObject)).To(BeNil())
+
+				buildObject, err = tb.GetBuildTillValidation(buildObject.Name)
+				Expect(err).To(BeNil())
+
+				// set SchedulerName to be invalid
+				buildRunObject.Spec.SchedulerName = ptr.To(strings.Repeat("s", 64))
+				Expect(tb.CreateBR(buildRunObject)).To(BeNil())
+
+				br, err := tb.GetBRTillCompletion(buildRunObject.Name)
+				Expect(err).To(BeNil())
+
+				condition := br.Status.GetCondition(v1beta1.Succeeded)
+				Expect(condition.Status).To(Equal(corev1.ConditionFalse))
+				Expect(condition.Reason).To(Equal("SchedulerNameNotValid"))
+				Expect(condition.Message).To(ContainSubstring(validation.MaxLenError(63)))
+			})
+		})
+	})
+
+	Context("when a buildrun has a buildSpec defined and overrides nodeSelector", func() {
+		var standaloneBuildRunObject *v1beta1.BuildRun
+
+		BeforeEach(func() {
+			buildRunSample = []byte(test.MinimalBuildRunWithNodeSelectorOverride)
+			standaloneBuildRunObject, err = tb.Catalog.LoadStandAloneBuildRunWithNameAndStrategy(BUILDRUN+tb.Namespace+"-standalone", cbsObject, buildRunSample)
+			Expect(err).To(BeNil())
+		})
+
+		Context("when the buildRun is created", func() {
+			It("should fail to register", func() {
+				Expect(tb.CreateBR(standaloneBuildRunObject)).To(BeNil())
+
+				br, err := tb.GetBRTillCompletion(standaloneBuildRunObject.Name)
+				Expect(err).To(BeNil())
+
+				condition := br.Status.GetCondition(v1beta1.Succeeded)
+				Expect(condition.Status).To(Equal(corev1.ConditionFalse))
+				Expect(condition.Reason).To(Equal(resources.BuildRunBuildFieldOverrideForbidden))
+				Expect(condition.Message).To(ContainSubstring("cannot use 'nodeSelector' override and 'buildSpec' simultaneously"))
+			})
+		})
+	})
+
+	Context("when a buildrun has a buildSpec defined and overrides tolerations", func() {
+		var standaloneBuildRunObject *v1beta1.BuildRun
+
+		BeforeEach(func() {
+			buildRunSample = []byte(test.MinimalBuildRunWithTolerationsOverride)
+			standaloneBuildRunObject, err = tb.Catalog.LoadStandAloneBuildRunWithNameAndStrategy(BUILDRUN+tb.Namespace+"-standalone", cbsObject, buildRunSample)
+			Expect(err).To(BeNil())
+		})
+
+		Context("when the buildRun is created", func() {
+			It("should fail to register", func() {
+				Expect(tb.CreateBR(standaloneBuildRunObject)).To(BeNil())
+
+				br, err := tb.GetBRTillCompletion(standaloneBuildRunObject.Name)
+				Expect(err).To(BeNil())
+
+				condition := br.Status.GetCondition(v1beta1.Succeeded)
+				Expect(condition.Status).To(Equal(corev1.ConditionFalse))
+				Expect(condition.Reason).To(Equal(resources.BuildRunBuildFieldOverrideForbidden))
+				Expect(condition.Message).To(ContainSubstring("cannot use 'tolerations' override and 'buildSpec' simultaneously"))
+			})
+		})
+	})
+
+	Context("when a buildrun has a buildSpec defined and overrides schedulerName", func() {
+		var standaloneBuildRunObject *v1beta1.BuildRun
+
+		BeforeEach(func() {
+			buildRunSample = []byte(test.MinimalBuildRunWithSchedulerNameOverride)
+			standaloneBuildRunObject, err = tb.Catalog.LoadStandAloneBuildRunWithNameAndStrategy(BUILDRUN+tb.Namespace+"-standalone", cbsObject, buildRunSample)
+			Expect(err).To(BeNil())
+		})
+
+		Context("when the buildRun is created", func() {
+			It("should fail to register", func() {
+				Expect(tb.CreateBR(standaloneBuildRunObject)).To(BeNil())
+
+				br, err := tb.GetBRTillCompletion(standaloneBuildRunObject.Name)
+				Expect(err).To(BeNil())
+
+				condition := br.Status.GetCondition(v1beta1.Succeeded)
+				Expect(condition.Status).To(Equal(corev1.ConditionFalse))
+				Expect(condition.Reason).To(Equal(resources.BuildRunBuildFieldOverrideForbidden))
+				Expect(condition.Message).To(ContainSubstring("cannot use 'schedulerName' override and 'buildSpec' simultaneously"))
+			})
+		})
+	})
 })
diff --git a/test/v1beta1_samples/build_samples.go b/test/v1beta1_samples/build_samples.go
index eff97c32..538f6df6 100644
--- a/test/v1beta1_samples/build_samples.go
+++ b/test/v1beta1_samples/build_samples.go
@@ -581,6 +581,19 @@ spec:
       value: "build-test-value"
 `
 
+// MinimalBuildWithSchedulerName defines a simple
+// Build with a strategy, output, and a SchedulerName specified
+const MinimalBuildWithSchedulerName = `
+apiVersion: shipwright.io/v1beta1
+kind: Build
+spec:
+  strategy:
+    kind: ClusterBuildStrategy
+  output:
+    image: image-registry.openshift-image-registry.svc:5000/example/buildpacks-app
+  schedulerName: "build-test-schedulername"
+`
+
 // BuildWithUndefinedParameter defines a param that was not declared under the
 // strategy parameters
 const BuildWithUndefinedParam = `
diff --git a/test/v1beta1_samples/buildrun_samples.go b/test/v1beta1_samples/buildrun_samples.go
index cb728e0d..71daf668 100644
--- a/test/v1beta1_samples/buildrun_samples.go
+++ b/test/v1beta1_samples/buildrun_samples.go
@@ -240,6 +240,21 @@ spec:
     kubernetes.io/arch: amd64
 `
 
+// MinimalBuildRunWithNodeSelectorOverride defines a minimal BuildRun
+// with a build spec defined and a nodeSelector overriding the build spec
+const MinimalBuildRunWithNodeSelectorOverride = `
+apiVersion: shipwright.io/v1beta1
+kind: BuildRun
+spec:
+  build:
+    spec:
+      strategy:
+        kind: ClusterBuildStrategy
+        name: buildah
+  nodeSelector:
+    kubernetes.io/arch: amd64
+`
+
 // MinimalBuildRunWithToleration defines a minimal BuildRun
 // with a reference to a not existing Build,
 // and a Toleration specified
@@ -255,6 +270,49 @@ spec:
       value: "buildrun-test-value"
 `
 
+// MinimalBuildRunWithTolerationsOverride defines a minimal BuildRun
+// with a build spec defined and a toleration overriding the build spec
+const MinimalBuildRunWithTolerationsOverride = `
+apiVersion: shipwright.io/v1beta1
+kind: BuildRun
+spec:
+  build:
+    spec:
+      strategy:
+        kind: ClusterBuildStrategy
+        name: buildah
+  tolerations:
+    - key: "buildrun-test-key"
+      operator: "Equal"
+      value: "buildrun-test-value"
+`
+
+// MinimalBuildRunWithSchedulerName defines a minimal BuildRun
+// with a reference to a not existing Build,
+// and a SchedulerName specified
+const MinimalBuildRunWithSchedulerName = `
+apiVersion: shipwright.io/v1beta1
+kind: BuildRun
+spec:
+  build:
+    name: foobar
+  schedulerName: "buildrun-test-schedulername"
+`
+
+// MinimalBuildRunWithSchedulerNameOverride defines a minimal BuildRun
+// with a build spec defined and a nodeSelector overriding the build spec
+const MinimalBuildRunWithSchedulerNameOverride = `
+apiVersion: shipwright.io/v1beta1
+kind: BuildRun
+spec:
+  build:
+    spec:
+      strategy:
+        kind: ClusterBuildStrategy
+        name: buildah
+  schedulerName: "buildrun-test-schedulername"
+`
+
 // MinimalBuildRunWithVulnerabilityScan defines a BuildRun with
 // an override for the Build Output
 const MinimalBuildRunWithVulnerabilityScan = `
diff --git a/test/v1beta1_samples/catalog.go b/test/v1beta1_samples/catalog.go
index b0c2ed02..4d95eb33 100644
--- a/test/v1beta1_samples/catalog.go
+++ b/test/v1beta1_samples/catalog.go
@@ -937,6 +937,60 @@ func (c *Catalog) DefaultNamespacedBuildStrategy() *build.BuildStrategy {
 	}
 }
 
+// BuildRunWithNodeSelectorOverride returns a customized BuildRun object
+// that defines a buildspec and overrides the nodeSelector
+func (c *Catalog) BuildRunWithNodeSelectorOverride(buildRunName string, buildName string, nodeSelector map[string]string) *build.BuildRun {
+	return &build.BuildRun{
+		ObjectMeta: metav1.ObjectMeta{
+			Name: buildRunName,
+		},
+		Spec: build.BuildRunSpec{
+			Build: build.ReferencedBuild{
+				Name: &buildName,
+				Spec: &build.BuildSpec{Strategy: build.Strategy{Name: "foobar"}},
+			},
+			NodeSelector: nodeSelector,
+		},
+		Status: build.BuildRunStatus{},
+	}
+}
+
+// BuildRunWithTolerationsOverride returns a customized BuildRun object
+// that defines a buildspec and overrides the tolerations
+func (c *Catalog) BuildRunWithTolerationsOverride(buildRunName string, buildName string, tolerations []corev1.Toleration) *build.BuildRun {
+	return &build.BuildRun{
+		ObjectMeta: metav1.ObjectMeta{
+			Name: buildRunName,
+		},
+		Spec: build.BuildRunSpec{
+			Build: build.ReferencedBuild{
+				Name: &buildName,
+				Spec: &build.BuildSpec{Strategy: build.Strategy{Name: "foobar"}},
+			},
+			Tolerations: tolerations,
+		},
+		Status: build.BuildRunStatus{},
+	}
+}
+
+// BuildRunWithSchedulerNameOverride returns a customized BuildRun object
+// that defines a buildspec and overrides the schedulerName
+func (c *Catalog) BuildRunWithSchedulerNameOverride(buildRunName string, buildName string, schedulerName string) *build.BuildRun {
+	return &build.BuildRun{
+		ObjectMeta: metav1.ObjectMeta{
+			Name: buildRunName,
+		},
+		Spec: build.BuildRunSpec{
+			Build: build.ReferencedBuild{
+				Name: &buildName,
+				Spec: &build.BuildSpec{Strategy: build.Strategy{Name: "foobar"}},
+			},
+			SchedulerName: &schedulerName,
+		},
+		Status: build.BuildRunStatus{},
+	}
+}
+
 // BuildRunWithSucceededCondition returns a BuildRun with a single condition
 // of the type Succeeded
 func (c *Catalog) BuildRunWithSucceededCondition() *build.BuildRun {