diff --git a/docs/taskruns.md b/docs/taskruns.md index aa64088ef02..7becc13965e 100644 --- a/docs/taskruns.md +++ b/docs/taskruns.md @@ -303,6 +303,8 @@ For each step we also include the fully-qualified image used, with the digest. If any pods have been [`OOMKilled`](https://kubernetes.io/docs/tasks/administer-cluster/out-of-resource/) by Kubernetes, the `Taskrun` will be marked as failed even if the exit code is 0. +The exact Task Spec used to instantiate the TaskRun is also included in the Status for full auditability. + ### Steps If multiple `steps` are defined in the `Task` invoked by the `TaskRun`, we will see the diff --git a/pkg/apis/pipeline/v1beta1/taskrun_types.go b/pkg/apis/pipeline/v1beta1/taskrun_types.go index 08cefb8d195..79b08c498d6 100644 --- a/pkg/apis/pipeline/v1beta1/taskrun_types.go +++ b/pkg/apis/pipeline/v1beta1/taskrun_types.go @@ -177,6 +177,9 @@ type TaskRunStatusFields struct { // The list has one entry per sidecar in the manifest. Each entry is // represents the imageid of the corresponding sidecar. Sidecars []SidecarState `json:"sidecars,omitempty"` + + // TaskSpec contains the Spec from the dereferenced Task definition used to instantiate this TaskRun. + TaskSpec *TaskSpec `json:"taskSpec,omitempty"` } // TaskRunResult used to describe the results of a task diff --git a/pkg/apis/pipeline/v1beta1/zz_generated.deepcopy.go b/pkg/apis/pipeline/v1beta1/zz_generated.deepcopy.go index 1d53fb0f78e..1d92712e04a 100644 --- a/pkg/apis/pipeline/v1beta1/zz_generated.deepcopy.go +++ b/pkg/apis/pipeline/v1beta1/zz_generated.deepcopy.go @@ -1481,6 +1481,11 @@ func (in *TaskRunStatusFields) DeepCopyInto(out *TaskRunStatusFields) { (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.TaskSpec != nil { + in, out := &in.TaskSpec, &out.TaskSpec + *out = new(TaskSpec) + (*in).DeepCopyInto(*out) + } return } diff --git a/pkg/pod/status.go b/pkg/pod/status.go index e7ed666030d..df0cf0ad900 100644 --- a/pkg/pod/status.go +++ b/pkg/pod/status.go @@ -17,6 +17,7 @@ limitations under the License. package pod import ( + "context" "encoding/json" "fmt" "sort" @@ -24,6 +25,7 @@ import ( "time" "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1alpha1" + "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1beta1" "github.com/tektoncd/pipeline/pkg/names" "github.com/tektoncd/pipeline/pkg/termination" "go.uber.org/zap" @@ -116,6 +118,15 @@ func MakeTaskRunStatus(logger *zap.SugaredLogger, tr v1alpha1.TaskRun, pod *core trs.Steps = []v1alpha1.StepState{} trs.Sidecars = []v1alpha1.SidecarState{} + // Only set this once, the first time. + if trs.TaskSpec == nil { + ts := v1beta1.TaskSpec{} + if err := taskSpec.ConvertTo(context.Background(), &ts); err != nil { + logger.Errorf("error setting taskrun.Status.taskSpec in taskrun %s: %w", tr.Name, err) + } + trs.TaskSpec = &ts + } + for _, s := range pod.Status.ContainerStatuses { if IsContainerStep(s.Name) { if s.State.Terminated != nil && len(s.State.Terminated.Message) != 0 { diff --git a/pkg/pod/status_test.go b/pkg/pod/status_test.go index d8b4cd6bde0..6f170828a05 100644 --- a/pkg/pod/status_test.go +++ b/pkg/pod/status_test.go @@ -17,6 +17,7 @@ limitations under the License. package pod import ( + "context" "testing" "time" @@ -721,6 +722,11 @@ func TestMakeTaskRunStatus(t *testing.T) { // Common traits, set for test case brevity. c.want.PodName = "pod" c.want.StartTime = &metav1.Time{Time: startTime} + ts := v1beta1.TaskSpec{} + if err := c.taskSpec.ConvertTo(context.Background(), &ts); err != nil { + t.Errorf("error converting ts: %w", err) + } + c.want.TaskSpec = &ts ensureTimeNotNil := cmp.Comparer(func(x, y *metav1.Time) bool { if x == nil { @@ -738,6 +744,52 @@ func TestMakeTaskRunStatus(t *testing.T) { } } +func TestTaskSpecOnlyCopiedOnce(t *testing.T) { + now := metav1.Now() + startTime := time.Date(2010, 1, 1, 1, 1, 1, 1, time.UTC) + + tr := v1alpha1.TaskRun{ + ObjectMeta: metav1.ObjectMeta{ + Name: "task-run", + Namespace: "foo", + }, + Status: v1alpha1.TaskRunStatus{ + TaskRunStatusFields: v1alpha1.TaskRunStatusFields{ + StartTime: &metav1.Time{Time: startTime}, + }, + }, + } + pod := &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pod", + Namespace: "foo", + CreationTimestamp: now, + }, + } + + // Set it once, make sure it gets copied. + ts := v1alpha1.TaskSpec{} + want := &v1beta1.TaskSpec{} + if err := ts.ConvertTo(context.Background(), want); err != nil { + t.Errorf("error converting ts: %w", err) + } + logger, _ := logging.NewLogger("", "status") + tr.Status = MakeTaskRunStatus(logger, tr, pod, ts) + + if d := cmp.Diff(want, tr.Status.TaskSpec); d != "" { + t.Errorf("Diff(-want, +got): %s", d) + } + + // Now modify it and run through again, making sure it doesn't change. + ts.Description = "foo" + tr.Status = MakeTaskRunStatus(logger, tr, pod, ts) + // Keep the same "want" as before. + if d := cmp.Diff(want, tr.Status.TaskSpec); d != "" { + t.Errorf("Diff(-want, +got): %s", d) + } + +} + func TestSidecarsReady(t *testing.T) { for _, c := range []struct { desc string