Skip to content

Commit

Permalink
Surface error results from TaskRun to BuildRun
Browse files Browse the repository at this point in the history
According to ship 0024, this commit adds a mechanism to surface errors
from TaskRun steps. The errors are published under `status.failure` in
BuildRun iff underlying TaskRun has failed.

Adds `pkg/reconciler/buildrun/resources/failures.go`, `pkg/reconciler/buildrun/resources/failures_test.go`
that contains logic for extracting error reason and message.

Modifies `pkg/reconciler/buildrun/resources/taskrun.go` by adding two
new generic results for errors.
  • Loading branch information
Baran Dalgic authored and dalbar committed Nov 3, 2021
1 parent 4ed2f1e commit 2d696d5
Show file tree
Hide file tree
Showing 7 changed files with 179 additions and 2 deletions.
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# Temporary Build Files
.DS_Store
git
shipwright-build-controller
build/_output
build/_test
Expand Down
8 changes: 8 additions & 0 deletions deploy/crds/shipwright.io_buildruns.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,14 @@ spec:
pod:
type: string
type: object
failure:
description: Failure contains error details that are collected and surfaced from TaskRun
properties:
message:
type: string
reason:
type: string
type: object
latestTaskRunRef:
description: "LatestTaskRunRef is the name of the TaskRun responsible for executing this BuildRun. \n TODO: This should be called something like \"TaskRunName\""
type: string
Expand Down
10 changes: 10 additions & 0 deletions pkg/apis/build/v1alpha1/buildrun_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,10 @@ type BuildRunStatus struct {
// FailedAt points to the resource where the BuildRun failed
// +optional
FailedAt *FailedAt `json:"failedAt,omitempty"`

// Failure contains error details that are collected and surfaced from TaskRun
// +optional
Failure *Failure `json:"failure,omitempty"`
}

// FailedAt describes the location where the failure happened
Expand All @@ -156,6 +160,12 @@ type FailedAt struct {
Container string `json:"container,omitempty"`
}

// Failure describes an error while building images
type Failure struct {
Reason string `json:"reason,omitempty"`
Message string `json:"message,omitempty"`
}

// BuildRef can be used to refer to a specific instance of a Build.
type BuildRef struct {
// Name of the referent; More info: http://kubernetes.io/docs/user-guide/identifiers#names
Expand Down
1 change: 1 addition & 0 deletions pkg/reconciler/buildrun/buildrun.go
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,7 @@ func (r *ReconcileBuildRun) Reconcile(request reconcile.Request) (reconcile.Resu
return reconcile.Result{}, err
}

resources.UpdateBuildRunUsingTaskFailures(buildRun, lastTaskRun)
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
Expand Down
52 changes: 52 additions & 0 deletions pkg/reconciler/buildrun/resources/failures.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package resources

import (
"encoding/json"
buildv1alpha1 "github.com/shipwright-io/build/pkg/apis/build/v1alpha1"
"github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1"
"knative.dev/pkg/apis"
)

const (
prefixedResultErrorReason = prefixParamsResultsVolumes + "-" + resultErrorReason
prefixedResultErrorMessage = prefixParamsResultsVolumes + "-" + resultErrorMessage
)

// UpdateBuildRunUsingTaskFailures is extracting failures from taskRun steps and adding them to buildRun (mutates)
func UpdateBuildRunUsingTaskFailures(buildRun *buildv1alpha1.BuildRun, taskRun *v1beta1.TaskRun) {
trCondition := taskRun.Status.GetCondition(apis.ConditionSucceeded)

// only extract failures when failing condition is present
if trCondition != nil && v1beta1.TaskRunReason(trCondition.Reason) == v1beta1.TaskRunReasonFailed {
buildRun.Status.Failure = extractErrorFromTaskRun(taskRun)
}
}

func extractErrorFromTaskRun(taskRun *v1beta1.TaskRun) *buildv1alpha1.Failure {
shipError := buildv1alpha1.Failure{}

for _, step := range taskRun.Status.Steps {
message := step.Terminated.Message
var taskRunResults []v1beta1.PipelineResourceResult

if err := json.Unmarshal([]byte(message), &taskRunResults); err != nil {
continue
}

for _, result := range taskRunResults {
if result.Key == prefixedResultErrorMessage {
shipError.Message = result.Value
}

if result.Key == prefixedResultErrorReason {
shipError.Reason = result.Value
}
}
}

if len(shipError.Message) == 0 || len(shipError.Reason) == 0 {
return nil
}

return &shipError
}
94 changes: 94 additions & 0 deletions pkg/reconciler/buildrun/resources/failures_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package resources

import (
"encoding/json"
"fmt"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/shipwright-io/build/pkg/apis/build/v1alpha1"
v1beta1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1"
v1 "k8s.io/api/core/v1"
"knative.dev/pkg/apis"
)

var _ = Describe("Surfacing errors", func() {
Context("resources.UpdateBuildRunUsingTaskFailures", func() {

It("surfaces errors to BuildRun in failed TaskRun", func() {
redTaskRun := v1beta1.TaskRun{}
redTaskRun.Status.Conditions = append(redTaskRun.Status.Conditions,
apis.Condition{Type: apis.ConditionSucceeded, Reason: v1beta1.TaskRunReasonFailed.String()})
failedStep := v1beta1.StepState{}

errorReasonValue := "val1"
errorMessageValue := "val2"
errorReasonKey := fmt.Sprintf("%s-%s", prefixParamsResultsVolumes, resultErrorReason)
errorMessageKey := fmt.Sprintf("%s-%s", prefixParamsResultsVolumes, resultErrorMessage)

errorReason := v1beta1.PipelineResourceResult{Key: errorReasonKey, Value: errorReasonValue}
errorMessage := v1beta1.PipelineResourceResult{Key: errorMessageKey, Value: errorMessageValue}
unrelated := v1beta1.PipelineResourceResult{Key: "unrelated", Value: "unrelated"}

message, _ := json.Marshal([]v1beta1.PipelineResourceResult{errorReason, errorMessage, unrelated})

failedStep.Terminated = &v1.ContainerStateTerminated{Message: string(message)}

redTaskRun.Status.Steps = append(redTaskRun.Status.Steps, failedStep)
redBuild := v1alpha1.BuildRun{}

UpdateBuildRunUsingTaskFailures(&redBuild, &redTaskRun)

Expect(redBuild.Status.Failure.Message).To(Equal(errorMessageValue))
Expect(redBuild.Status.Failure.Reason).To(Equal(errorReasonValue))
})

It("failed TaskRun without error reason and message", func() {
redTaskRun := v1beta1.TaskRun{}
redTaskRun.Status.Conditions = append(redTaskRun.Status.Conditions,
apis.Condition{Type: apis.ConditionSucceeded, Reason: v1beta1.TaskRunReasonFailed.String()})
failedStep := v1beta1.StepState{}

unrelated := v1beta1.PipelineResourceResult{Key: "unrelated", Value: "unrelated"}

message, _ := json.Marshal([]v1beta1.PipelineResourceResult{unrelated})

failedStep.Terminated = &v1.ContainerStateTerminated{Message: string(message)}

redTaskRun.Status.Steps = append(redTaskRun.Status.Steps, failedStep)
redBuild := v1alpha1.BuildRun{}

UpdateBuildRunUsingTaskFailures(&redBuild, &redTaskRun)

Expect(redBuild.Status.Failure).To(BeNil())
})

It("no errors present in BuildRun for successful TaskRun", func() {
greenTaskRun := v1beta1.TaskRun{}
greenTaskRun.Status.Conditions = append(greenTaskRun.Status.Conditions, apis.Condition{Type: apis.ConditionSucceeded})
greenBuildRun := v1alpha1.BuildRun{}

UpdateBuildRunUsingTaskFailures(&greenBuildRun, &greenTaskRun)

Expect(greenBuildRun.Status.Failure).To(BeNil())
})

It("TaskRun has not condition succeeded so nothing to do", func() {
unfinishedTaskRun := v1beta1.TaskRun{}
unfinishedTaskRun.Status.Conditions = append(unfinishedTaskRun.Status.Conditions, apis.Condition{Type: apis.ConditionReady})
unfinishedBuildRun := v1alpha1.BuildRun{}

UpdateBuildRunUsingTaskFailures(&unfinishedBuildRun, &unfinishedTaskRun)
Expect(unfinishedBuildRun.Status.Failure).To(BeNil())
})

It("TaskRun has a unknown reason", func() {
unknownTaskRun := v1beta1.TaskRun{}
unknownTaskRun.Status.Conditions = append(unknownTaskRun.Status.Conditions, apis.Condition{Type: apis.ConditionSucceeded, Reason: "random"})
unknownBuildRun := v1alpha1.BuildRun{}

UpdateBuildRunUsingTaskFailures(&unknownBuildRun, &unknownTaskRun)
Expect(unknownBuildRun.Status.Failure).To(BeNil())
})
})
})
15 changes: 14 additions & 1 deletion pkg/reconciler/buildrun/resources/taskrun.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ const (
paramSourceRoot = "source-root"
paramSourceContext = "source-context"

resultErrorMessage = "error-message"
resultErrorReason = "error-reason"

workspaceSource = "source"

inputParamBuilder = "BUILDER_IMAGE"
Expand Down Expand Up @@ -105,6 +108,16 @@ func GenerateTaskSpec(
Type: taskv1.ParamTypeString,
},
},
Results: []v1beta1.TaskResult{
{
Name: fmt.Sprintf("%s-%s", prefixParamsResultsVolumes, resultErrorMessage),
Description: "The error description of the task run",
},
{
Name: fmt.Sprintf("%s-%s", prefixParamsResultsVolumes, resultErrorReason),
Description: "The error reason of the task run",
},
},
Workspaces: []v1beta1.WorkspaceDeclaration{
// workspace for the source files
{
Expand All @@ -113,7 +126,7 @@ func GenerateTaskSpec(
},
}

generatedTaskSpec.Results = getTaskSpecResults()
generatedTaskSpec.Results = append(generatedTaskSpec.Results, getTaskSpecResults()...)

if build.Spec.Builder != nil {
InputBuilder := v1beta1.ParamSpec{
Expand Down

0 comments on commit 2d696d5

Please sign in to comment.