diff --git a/config/300-crds/300-pipelinerun.yaml b/config/300-crds/300-pipelinerun.yaml
index 6fb4a179738..ebbacb060d5 100644
--- a/config/300-crds/300-pipelinerun.yaml
+++ b/config/300-crds/300-pipelinerun.yaml
@@ -2364,6 +2364,8 @@ spec:
type: boolean
enableKubernetesSidecar:
type: boolean
+ enableNotices:
+ type: boolean
enableParamEnum:
type: boolean
enableProvenanceInStatus:
@@ -2744,6 +2746,8 @@ spec:
type: boolean
enableKubernetesSidecar:
type: boolean
+ enableNotices:
+ type: boolean
enableParamEnum:
type: boolean
enableProvenanceInStatus:
@@ -3017,6 +3021,8 @@ spec:
type: boolean
enableKubernetesSidecar:
type: boolean
+ enableNotices:
+ type: boolean
enableParamEnum:
type: boolean
enableProvenanceInStatus:
@@ -5463,6 +5469,8 @@ spec:
type: boolean
enableKubernetesSidecar:
type: boolean
+ enableNotices:
+ type: boolean
enableParamEnum:
type: boolean
enableProvenanceInStatus:
diff --git a/config/300-crds/300-taskrun.yaml b/config/300-crds/300-taskrun.yaml
index 1aa0dc32834..22c55532c69 100644
--- a/config/300-crds/300-taskrun.yaml
+++ b/config/300-crds/300-taskrun.yaml
@@ -1879,6 +1879,8 @@ spec:
type: boolean
enableKubernetesSidecar:
type: boolean
+ enableNotices:
+ type: boolean
enableParamEnum:
type: boolean
enableProvenanceInStatus:
@@ -2152,6 +2154,8 @@ spec:
type: boolean
enableKubernetesSidecar:
type: boolean
+ enableNotices:
+ type: boolean
enableParamEnum:
type: boolean
enableProvenanceInStatus:
@@ -3996,6 +4000,48 @@ spec:
type:
description: Type of condition.
type: string
+ notices:
+ description: Notices are structured messages emitted by controllers that do not affect the task's success/failure status.
+ type: array
+ items:
+ description: Notice represents a structured message emitted by a controller that does not affect the run's success/failure status.
+ type: object
+ required:
+ - level
+ - message
+ properties:
+ file:
+ description: |-
+ File is the source file path related to this notice.
+ Used by VCS integrations to create inline annotations.
+ Maximum length: 256 characters.
+ type: string
+ maxLength: 256
+ level:
+ description: |-
+ Level indicates the severity of the notice.
+ Valid values: "info", "warning".
+ type: string
+ enum:
+ - info
+ - warning
+ message:
+ description: |-
+ Message is the human-readable notice text.
+ Maximum length: 1024 characters.
+ type: string
+ maxLength: 1024
+ startLine:
+ description: |-
+ StartLine is the starting line number in the source file (1-based).
+ Pointer type so that absence (nil) is distinguishable from line 0.
+ type: integer
+ step:
+ description: |-
+ Step is the name of the step that emitted this notice.
+ Empty for controller-emitted notices.
+ type: string
+ x-kubernetes-list-type: atomic
observedGeneration:
description: |-
ObservedGeneration is the 'Generation' of the Service that
@@ -4033,6 +4079,8 @@ spec:
type: boolean
enableKubernetesSidecar:
type: boolean
+ enableNotices:
+ type: boolean
enableParamEnum:
type: boolean
enableProvenanceInStatus:
@@ -4298,6 +4346,8 @@ spec:
type: boolean
enableKubernetesSidecar:
type: boolean
+ enableNotices:
+ type: boolean
enableParamEnum:
type: boolean
enableProvenanceInStatus:
diff --git a/config/config-feature-flags.yaml b/config/config-feature-flags.yaml
index 0faa1e55702..aeea74cb2ba 100644
--- a/config/config-feature-flags.yaml
+++ b/config/config-feature-flags.yaml
@@ -118,6 +118,8 @@ data:
# Setting this flag to "true" will enable the use of Artifacts in Steps
# This feature is in preview mode and not implemented yet. Please check #7693 for updates.
enable-artifacts: "false"
+ # Setting this flag to "true" will enable controller notices in TaskRun status.
+ enable-notices: "false"
# Setting this flag to "true" will enable the built-in param input validation via param enum.
enable-param-enum: "false"
# Setting this flag to "pipeline,pipelinerun,taskrun" will prevent users from creating
diff --git a/docs/pipeline-api.md b/docs/pipeline-api.md
index 2444f3e737c..f20d864e480 100644
--- a/docs/pipeline-api.md
+++ b/docs/pipeline-api.md
@@ -424,6 +424,44 @@ _Appears in:_
| `params` _[Params](#params)_ | Params is a list of parameters used to fan out the pipelineTask
Params takes only `Parameters` of type `"array"`
Each array element is supplied to the `PipelineTask` by substituting `params` of type `"string"` in the underlying `Task`.
The names of the `params` in the `Matrix` must match the names of the `params` in the underlying `Task` that they will be substituting. | | |
+#### Notice
+
+
+
+Notice represents a structured message emitted by a controller that does not affect the run's success/failure status.
+
+
+
+_Appears in:_
+- [TaskRunStatus](#taskrunstatus)
+- [TaskRunStatusFields](#taskrunstatusfields)
+
+| Field | Description | Default | Validation |
+| --- | --- | --- | --- |
+| `level` _[NoticeLevel](#noticelevel)_ | Level indicates the severity of the notice.
Valid values: "info", "warning". | | Enum: [info warning]
|
+| `message` _string_ | Message is the human-readable notice text.
Maximum length: 1024 characters. | | MaxLength: 1024
|
+| `step` _string_ | Step is the name of the step that emitted this notice.
Empty for controller-emitted notices. | | Optional: \{\}
|
+| `file` _string_ | File is the source file path related to this notice.
Used by VCS integrations to create inline annotations.
Maximum length: 256 characters. | | MaxLength: 256
Optional: \{\}
|
+| `startLine` _integer_ | StartLine is the starting line number in the source file (1-based).
Pointer type so that absence (nil) is distinguishable from line 0. | | Optional: \{\}
|
+
+
+#### NoticeLevel
+
+_Underlying type:_ _string_
+
+NoticeLevel indicates the severity of a notice.
+
+
+
+_Appears in:_
+- [Notice](#notice)
+
+| Field | Description |
+| --- | --- |
+| `info` | NoticeLevelInfo represents an informational message, no action needed.
|
+| `warning` | NoticeLevelWarning represents something to address, but not blocking.
|
+
+
#### OnErrorType
_Underlying type:_ _string_
@@ -1578,6 +1616,7 @@ _Appears in:_
| `retriesStatus` _[RetriesStatus](#retriesstatus)_ | RetriesStatus contains the history of TaskRunStatus in case of a retry in order to keep record of failures.
All TaskRunStatus stored in RetriesStatus will have no date within the RetriesStatus as is redundant. | | Schemaless: \{\}
Optional: \{\}
|
| `results` _[TaskRunResult](#taskrunresult) array_ | Results are the list of results written out by the task's containers | | Optional: \{\}
|
| `artifacts` _[Artifacts](#artifacts)_ | Artifacts are the list of artifacts written out by the task's containers | | Optional: \{\}
|
+| `notices` _[Notice](#notice) array_ | Notices are structured messages emitted by controllers that do not affect the task's success/failure status. | | Optional: \{\}
|
| `sidecars` _[SidecarState](#sidecarstate) array_ | The list has one entry per sidecar in the manifest. Each entry is
represents the imageid of the corresponding sidecar. | | |
| `taskSpec` _[TaskSpec](#taskspec)_ | TaskSpec contains the Spec from the dereferenced Task definition used to instantiate this TaskRun. | | |
| `provenance` _[Provenance](#provenance)_ | Provenance contains some key authenticated metadata about how a software artifact was built (what sources, what inputs/outputs, etc.). | | Optional: \{\}
|
@@ -1606,6 +1645,7 @@ _Appears in:_
| `retriesStatus` _[RetriesStatus](#retriesstatus)_ | RetriesStatus contains the history of TaskRunStatus in case of a retry in order to keep record of failures.
All TaskRunStatus stored in RetriesStatus will have no date within the RetriesStatus as is redundant. | | Schemaless: \{\}
Optional: \{\}
|
| `results` _[TaskRunResult](#taskrunresult) array_ | Results are the list of results written out by the task's containers | | Optional: \{\}
|
| `artifacts` _[Artifacts](#artifacts)_ | Artifacts are the list of artifacts written out by the task's containers | | Optional: \{\}
|
+| `notices` _[Notice](#notice) array_ | Notices are structured messages emitted by controllers that do not affect the task's success/failure status. | | Optional: \{\}
|
| `sidecars` _[SidecarState](#sidecarstate) array_ | The list has one entry per sidecar in the manifest. Each entry is
represents the imageid of the corresponding sidecar. | | |
| `taskSpec` _[TaskSpec](#taskspec)_ | TaskSpec contains the Spec from the dereferenced Task definition used to instantiate this TaskRun. | | |
| `provenance` _[Provenance](#provenance)_ | Provenance contains some key authenticated metadata about how a software artifact was built (what sources, what inputs/outputs, etc.). | | Optional: \{\}
|
diff --git a/pkg/apis/config/feature_flags.go b/pkg/apis/config/feature_flags.go
index c2de63f06f8..026086b0bfd 100644
--- a/pkg/apis/config/feature_flags.go
+++ b/pkg/apis/config/feature_flags.go
@@ -101,6 +101,8 @@ const (
EnableCELInWhenExpression = "enable-cel-in-whenexpression"
// EnableArtifacts is the flag to enable the use of Artifacts in Steps
EnableArtifacts = "enable-artifacts"
+ // EnableNotices is the flag to enable controller notices in TaskRun status
+ EnableNotices = "enable-notices"
// EnableParamEnum is the flag to enabled enum in params
EnableParamEnum = "enable-param-enum"
// EnableConciseResolverSyntax is the flag to enable concise resolver syntax
@@ -172,6 +174,13 @@ var (
Enabled: DefaultAlphaFeatureEnabled,
}
+ // DefaultEnableNotices is the default PerFeatureFlag value for EnableNotices
+ DefaultEnableNotices = PerFeatureFlag{
+ Name: EnableNotices,
+ Stability: AlphaAPIFields,
+ Enabled: DefaultAlphaFeatureEnabled,
+ }
+
// DefaultEnableParamEnum is the default PerFeatureFlag value for EnableParamEnum
DefaultEnableParamEnum = PerFeatureFlag{
Name: EnableParamEnum,
@@ -230,6 +239,7 @@ type FeatureFlags struct {
EnableStepActions bool `json:"enableStepActions,omitempty"`
EnableParamEnum bool `json:"enableParamEnum,omitempty"`
EnableArtifacts bool `json:"enableArtifacts,omitempty"`
+ EnableNotices bool `json:"enableNotices,omitempty"`
DisableInlineSpec string `json:"disableInlineSpec,omitempty"`
EnableConciseResolverSyntax bool `json:"enableConciseResolverSyntax,omitempty"`
EnableKubernetesSidecar bool `json:"enableKubernetesSidecar,omitempty"`
@@ -335,6 +345,9 @@ func NewFeatureFlagsFromMap(cfgMap map[string]string) (*FeatureFlags, error) {
if err := setPerFeatureFlag(EnableArtifacts, DefaultEnableArtifacts, &tc.EnableArtifacts); err != nil {
return nil, err
}
+ if err := setPerFeatureFlag(EnableNotices, DefaultEnableNotices, &tc.EnableNotices); err != nil {
+ return nil, err
+ }
if err := setFeatureInlineSpec(cfgMap, DisableInlineSpec, DefaultDisableInlineSpec, &tc.DisableInlineSpec); err != nil {
return nil, err
diff --git a/pkg/apis/config/feature_flags_test.go b/pkg/apis/config/feature_flags_test.go
index 14079efd734..22b57e3ad5b 100644
--- a/pkg/apis/config/feature_flags_test.go
+++ b/pkg/apis/config/feature_flags_test.go
@@ -76,6 +76,7 @@ func TestNewFeatureFlagsFromConfigMap(t *testing.T) {
Coschedule: config.CoscheduleDisabled,
EnableCELInWhenExpression: true,
EnableArtifacts: true,
+ EnableNotices: true,
EnableParamEnum: true,
DisableInlineSpec: "pipeline,pipelinerun,taskrun",
EnableConciseResolverSyntax: true,
@@ -294,6 +295,9 @@ func TestNewFeatureFlagsConfigMapErrors(t *testing.T) {
}, {
fileName: "feature-flags-invalid-enable-artifacts",
want: `failed parsing feature flags config "invalid": strconv.ParseBool: parsing "invalid": invalid syntax for feature enable-artifacts`,
+ }, {
+ fileName: "feature-flags-invalid-enable-notices",
+ want: `failed parsing feature flags config "invalid": strconv.ParseBool: parsing "invalid": invalid syntax for feature enable-notices`,
}, {
fileName: "feature-flags-invalid-enable-concise-resolver-syntax",
want: `failed parsing feature flags config "invalid": strconv.ParseBool: parsing "invalid": invalid syntax for feature enable-concise-resolver-syntax`,
diff --git a/pkg/apis/config/testdata/feature-flags-all-flags-set.yaml b/pkg/apis/config/testdata/feature-flags-all-flags-set.yaml
index 67f0e2db80c..ca448ac6fc8 100644
--- a/pkg/apis/config/testdata/feature-flags-all-flags-set.yaml
+++ b/pkg/apis/config/testdata/feature-flags-all-flags-set.yaml
@@ -35,6 +35,7 @@ data:
enable-cel-in-whenexpression: "true"
enable-param-enum: "true"
enable-artifacts: "true"
+ enable-notices: "true"
disable-inline-spec: "pipeline,pipelinerun,taskrun"
enable-concise-resolver-syntax: "true"
enable-kubernetes-sidecar: "true"
diff --git a/pkg/apis/config/testdata/feature-flags-invalid-enable-notices.yaml b/pkg/apis/config/testdata/feature-flags-invalid-enable-notices.yaml
new file mode 100644
index 00000000000..ecd402fee01
--- /dev/null
+++ b/pkg/apis/config/testdata/feature-flags-invalid-enable-notices.yaml
@@ -0,0 +1,21 @@
+# Copyright 2026 The Tekton 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
+#
+# https://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.
+
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: feature-flags
+ namespace: tekton-pipelines
+data:
+ enable-notices: "invalid"
diff --git a/pkg/apis/pipeline/v1/notice_types.go b/pkg/apis/pipeline/v1/notice_types.go
new file mode 100644
index 00000000000..a49ada2666c
--- /dev/null
+++ b/pkg/apis/pipeline/v1/notice_types.go
@@ -0,0 +1,94 @@
+/*
+Copyright 2026 The Tekton 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 v1
+
+const (
+ maxControllerNotices = 10
+ maxNoticeMessageLen = 1024
+ maxNoticeFileLen = 256
+)
+
+// NoticeLevel indicates the severity of a notice.
+type NoticeLevel string
+
+const (
+ // NoticeLevelInfo represents an informational message, no action needed.
+ NoticeLevelInfo NoticeLevel = "info"
+ // NoticeLevelWarning represents something to address, but not blocking.
+ NoticeLevelWarning NoticeLevel = "warning"
+)
+
+// AllNoticeLevels can be used for NoticeLevel validation.
+var AllNoticeLevels = []NoticeLevel{NoticeLevelInfo, NoticeLevelWarning}
+
+// Notice represents a structured message emitted by a controller that does not affect the run's success/failure status.
+type Notice struct {
+ // Level indicates the severity of the notice.
+ // Valid values: "info", "warning".
+ // +kubebuilder:validation:Enum=info;warning
+ Level NoticeLevel `json:"level"`
+
+ // Message is the human-readable notice text.
+ // Maximum length: 1024 characters.
+ // +kubebuilder:validation:MaxLength=1024
+ Message string `json:"message"`
+
+ // Step is the name of the step that emitted this notice.
+ // Empty for controller-emitted notices.
+ // +optional
+ Step string `json:"step,omitempty"`
+
+ // File is the source file path related to this notice.
+ // Used by VCS integrations to create inline annotations.
+ // Maximum length: 256 characters.
+ // +optional
+ // +kubebuilder:validation:MaxLength=256
+ File string `json:"file,omitempty"`
+
+ // StartLine is the starting line number in the source file (1-based).
+ // Pointer type so that absence (nil) is distinguishable from line 0.
+ // +optional
+ StartLine *int `json:"startLine,omitempty"`
+}
+
+// AddControllerNotice appends a bounded, deduplicated controller notice to TaskRun status.
+func (trs *TaskRunStatus) AddControllerNotice(notice Notice) {
+ if notice.Level != NoticeLevelInfo && notice.Level != NoticeLevelWarning {
+ return
+ }
+ notice.Message = truncateString(notice.Message, maxNoticeMessageLen)
+ notice.File = truncateString(notice.File, maxNoticeFileLen)
+ notice.Step = ""
+
+ for _, existing := range trs.Notices {
+ if existing.Level == notice.Level && existing.Message == notice.Message {
+ return
+ }
+ }
+ if len(trs.Notices) >= maxControllerNotices {
+ return
+ }
+ trs.Notices = append(trs.Notices, notice)
+}
+
+func truncateString(value string, limit int) string {
+ runes := []rune(value)
+ if len(runes) <= limit {
+ return value
+ }
+ return string(runes[:limit])
+}
diff --git a/pkg/apis/pipeline/v1/notice_types_test.go b/pkg/apis/pipeline/v1/notice_types_test.go
new file mode 100644
index 00000000000..ae2f537e016
--- /dev/null
+++ b/pkg/apis/pipeline/v1/notice_types_test.go
@@ -0,0 +1,58 @@
+/*
+Copyright 2026 The Tekton 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 v1
+
+import (
+ "strings"
+ "testing"
+
+ "github.com/google/go-cmp/cmp"
+ "github.com/tektoncd/pipeline/test/diff"
+)
+
+func TestTaskRunStatusAddControllerNotice(t *testing.T) {
+ longMessage := strings.Repeat("m", 1100)
+ longFile := strings.Repeat("f", 300)
+ trs := &TaskRunStatus{}
+
+ trs.AddControllerNotice(Notice{Level: NoticeLevelWarning, Message: "warn", Step: "ignored"})
+ trs.AddControllerNotice(Notice{Level: NoticeLevelWarning, Message: "warn"})
+ trs.AddControllerNotice(Notice{Level: NoticeLevelInfo, Message: longMessage, File: longFile})
+ trs.AddControllerNotice(Notice{Level: NoticeLevel("bad"), Message: "drop"})
+
+ want := []Notice{{
+ Level: NoticeLevelWarning,
+ Message: "warn",
+ }, {
+ Level: NoticeLevelInfo,
+ Message: strings.Repeat("m", 1024),
+ File: strings.Repeat("f", 256),
+ }}
+ if d := cmp.Diff(want, trs.Notices); d != "" {
+ t.Fatal(diff.PrintWantGot(d))
+ }
+}
+
+func TestTaskRunStatusAddControllerNoticeCaps(t *testing.T) {
+ trs := &TaskRunStatus{}
+ for i := range maxControllerNotices + 1 {
+ trs.AddControllerNotice(Notice{Level: NoticeLevelWarning, Message: string(rune('a' + i))})
+ }
+ if got := len(trs.Notices); got != maxControllerNotices {
+ t.Fatalf("notices length = %d, want %d", got, maxControllerNotices)
+ }
+}
diff --git a/pkg/apis/pipeline/v1/openapi_generated.go b/pkg/apis/pipeline/v1/openapi_generated.go
index 6cbcb68b5e7..036c09f53c5 100644
--- a/pkg/apis/pipeline/v1/openapi_generated.go
+++ b/pkg/apis/pipeline/v1/openapi_generated.go
@@ -37,6 +37,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA
"github.com/tektoncd/pipeline/pkg/apis/pipeline/v1.EmbeddedTask": schema_pkg_apis_pipeline_v1_EmbeddedTask(ref),
"github.com/tektoncd/pipeline/pkg/apis/pipeline/v1.IncludeParams": schema_pkg_apis_pipeline_v1_IncludeParams(ref),
"github.com/tektoncd/pipeline/pkg/apis/pipeline/v1.Matrix": schema_pkg_apis_pipeline_v1_Matrix(ref),
+ "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1.Notice": schema_pkg_apis_pipeline_v1_Notice(ref),
"github.com/tektoncd/pipeline/pkg/apis/pipeline/v1.Param": schema_pkg_apis_pipeline_v1_Param(ref),
"github.com/tektoncd/pipeline/pkg/apis/pipeline/v1.ParamSpec": schema_pkg_apis_pipeline_v1_ParamSpec(ref),
"github.com/tektoncd/pipeline/pkg/apis/pipeline/v1.ParamValue": schema_pkg_apis_pipeline_v1_ParamValue(ref),
@@ -848,6 +849,57 @@ func schema_pkg_apis_pipeline_v1_Matrix(ref common.ReferenceCallback) common.Ope
}
}
+func schema_pkg_apis_pipeline_v1_Notice(ref common.ReferenceCallback) common.OpenAPIDefinition {
+ return common.OpenAPIDefinition{
+ Schema: spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Description: "Notice represents a structured message emitted by a controller that does not affect the run's success/failure status.",
+ Type: []string{"object"},
+ Properties: map[string]spec.Schema{
+ "level": {
+ SchemaProps: spec.SchemaProps{
+ Description: "Level indicates the severity of the notice. Valid values: \"info\", \"warning\".",
+ Default: "",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "message": {
+ SchemaProps: spec.SchemaProps{
+ Description: "Message is the human-readable notice text. Maximum length: 1024 characters.",
+ Default: "",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "step": {
+ SchemaProps: spec.SchemaProps{
+ Description: "Step is the name of the step that emitted this notice. Empty for controller-emitted notices.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "file": {
+ SchemaProps: spec.SchemaProps{
+ Description: "File is the source file path related to this notice. Used by VCS integrations to create inline annotations. Maximum length: 256 characters.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "startLine": {
+ SchemaProps: spec.SchemaProps{
+ Description: "StartLine is the starting line number in the source file (1-based). Pointer type so that absence (nil) is distinguishable from line 0.",
+ Type: []string{"integer"},
+ Format: "int32",
+ },
+ },
+ },
+ Required: []string{"level", "message"},
+ },
+ },
+ }
+}
+
func schema_pkg_apis_pipeline_v1_Param(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
@@ -4233,6 +4285,25 @@ func schema_pkg_apis_pipeline_v1_TaskRunStatus(ref common.ReferenceCallback) com
Ref: ref("github.com/tektoncd/pipeline/pkg/apis/pipeline/v1.Artifacts"),
},
},
+ "notices": {
+ VendorExtensible: spec.VendorExtensible{
+ Extensions: spec.Extensions{
+ "x-kubernetes-list-type": "atomic",
+ },
+ },
+ SchemaProps: spec.SchemaProps{
+ Description: "Notices are structured messages emitted by controllers that do not affect the task's success/failure status.",
+ Type: []string{"array"},
+ Items: &spec.SchemaOrArray{
+ Schema: &spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Default: map[string]interface{}{},
+ Ref: ref("github.com/tektoncd/pipeline/pkg/apis/pipeline/v1.Notice"),
+ },
+ },
+ },
+ },
+ },
"sidecars": {
VendorExtensible: spec.VendorExtensible{
Extensions: spec.Extensions{
@@ -4285,7 +4356,7 @@ func schema_pkg_apis_pipeline_v1_TaskRunStatus(ref common.ReferenceCallback) com
},
},
Dependencies: []string{
- "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1.Artifacts", "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1.Provenance", "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1.SidecarState", "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1.StepState", "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1.TaskRunResult", "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1.TaskRunStatus", "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1.TaskSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.Time", "knative.dev/pkg/apis.Condition"},
+ "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1.Artifacts", "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1.Notice", "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1.Provenance", "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1.SidecarState", "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1.StepState", "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1.TaskRunResult", "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1.TaskRunStatus", "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1.TaskSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.Time", "knative.dev/pkg/apis.Condition"},
}
}
@@ -4374,6 +4445,25 @@ func schema_pkg_apis_pipeline_v1_TaskRunStatusFields(ref common.ReferenceCallbac
Ref: ref("github.com/tektoncd/pipeline/pkg/apis/pipeline/v1.Artifacts"),
},
},
+ "notices": {
+ VendorExtensible: spec.VendorExtensible{
+ Extensions: spec.Extensions{
+ "x-kubernetes-list-type": "atomic",
+ },
+ },
+ SchemaProps: spec.SchemaProps{
+ Description: "Notices are structured messages emitted by controllers that do not affect the task's success/failure status.",
+ Type: []string{"array"},
+ Items: &spec.SchemaOrArray{
+ Schema: &spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Default: map[string]interface{}{},
+ Ref: ref("github.com/tektoncd/pipeline/pkg/apis/pipeline/v1.Notice"),
+ },
+ },
+ },
+ },
+ },
"sidecars": {
VendorExtensible: spec.VendorExtensible{
Extensions: spec.Extensions{
@@ -4426,7 +4516,7 @@ func schema_pkg_apis_pipeline_v1_TaskRunStatusFields(ref common.ReferenceCallbac
},
},
Dependencies: []string{
- "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1.Artifacts", "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1.Provenance", "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1.SidecarState", "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1.StepState", "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1.TaskRunResult", "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1.TaskRunStatus", "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1.TaskSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.Time"},
+ "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1.Artifacts", "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1.Notice", "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1.Provenance", "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1.SidecarState", "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1.StepState", "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1.TaskRunResult", "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1.TaskRunStatus", "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1.TaskSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.Time"},
}
}
diff --git a/pkg/apis/pipeline/v1/swagger.json b/pkg/apis/pipeline/v1/swagger.json
index a0964b1d49b..4cb8ce3d394 100644
--- a/pkg/apis/pipeline/v1/swagger.json
+++ b/pkg/apis/pipeline/v1/swagger.json
@@ -384,6 +384,39 @@
}
}
},
+ "v1.Notice": {
+ "description": "Notice represents a structured message emitted by a controller that does not affect the run's success/failure status.",
+ "type": "object",
+ "required": [
+ "level",
+ "message"
+ ],
+ "properties": {
+ "file": {
+ "description": "File is the source file path related to this notice. Used by VCS integrations to create inline annotations. Maximum length: 256 characters.",
+ "type": "string"
+ },
+ "level": {
+ "description": "Level indicates the severity of the notice. Valid values: \"info\", \"warning\".",
+ "type": "string",
+ "default": ""
+ },
+ "message": {
+ "description": "Message is the human-readable notice text. Maximum length: 1024 characters.",
+ "type": "string",
+ "default": ""
+ },
+ "startLine": {
+ "description": "StartLine is the starting line number in the source file (1-based). Pointer type so that absence (nil) is distinguishable from line 0.",
+ "type": "integer",
+ "format": "int32"
+ },
+ "step": {
+ "description": "Step is the name of the step that emitted this notice. Empty for controller-emitted notices.",
+ "type": "string"
+ }
+ }
+ },
"v1.Param": {
"description": "Param declares an ParamValues to use for the parameter called name.",
"type": "object",
@@ -2174,6 +2207,15 @@
"x-kubernetes-patch-merge-key": "type",
"x-kubernetes-patch-strategy": "merge"
},
+ "notices": {
+ "description": "Notices are structured messages emitted by controllers that do not affect the task's success/failure status.",
+ "type": "array",
+ "items": {
+ "default": {},
+ "$ref": "#/definitions/v1.Notice"
+ },
+ "x-kubernetes-list-type": "atomic"
+ },
"observedGeneration": {
"description": "ObservedGeneration is the 'Generation' of the Service that was last processed by the controller.",
"type": "integer",
@@ -2256,6 +2298,15 @@
"description": "CompletionTime is the time the build completed.",
"$ref": "#/definitions/v1.Time"
},
+ "notices": {
+ "description": "Notices are structured messages emitted by controllers that do not affect the task's success/failure status.",
+ "type": "array",
+ "items": {
+ "default": {},
+ "$ref": "#/definitions/v1.Notice"
+ },
+ "x-kubernetes-list-type": "atomic"
+ },
"podName": {
"description": "PodName is the name of the pod responsible for executing this task's steps.",
"type": "string",
diff --git a/pkg/apis/pipeline/v1/taskrun_types.go b/pkg/apis/pipeline/v1/taskrun_types.go
index d6f92c72f89..dd0c7154d5a 100644
--- a/pkg/apis/pipeline/v1/taskrun_types.go
+++ b/pkg/apis/pipeline/v1/taskrun_types.go
@@ -345,6 +345,11 @@ type TaskRunStatusFields struct {
// +optional
Artifacts *Artifacts `json:"artifacts,omitempty"`
+ // Notices are structured messages emitted by controllers that do not affect the task's success/failure status.
+ // +optional
+ // +listType=atomic
+ Notices []Notice `json:"notices,omitempty"`
+
// The list has one entry per sidecar in the manifest. Each entry is
// represents the imageid of the corresponding sidecar.
// +listType=atomic
diff --git a/pkg/apis/pipeline/v1/zz_generated.deepcopy.go b/pkg/apis/pipeline/v1/zz_generated.deepcopy.go
index 3cddb19a5dc..fb27e6a8b44 100644
--- a/pkg/apis/pipeline/v1/zz_generated.deepcopy.go
+++ b/pkg/apis/pipeline/v1/zz_generated.deepcopy.go
@@ -275,6 +275,27 @@ func (in *Matrix) DeepCopy() *Matrix {
return out
}
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *Notice) DeepCopyInto(out *Notice) {
+ *out = *in
+ if in.StartLine != nil {
+ in, out := &in.StartLine, &out.StartLine
+ *out = new(int)
+ **out = **in
+ }
+ return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Notice.
+func (in *Notice) DeepCopy() *Notice {
+ if in == nil {
+ return nil
+ }
+ out := new(Notice)
+ in.DeepCopyInto(out)
+ return out
+}
+
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Param) DeepCopyInto(out *Param) {
*out = *in
@@ -2025,6 +2046,13 @@ func (in *TaskRunStatusFields) DeepCopyInto(out *TaskRunStatusFields) {
*out = new(Artifacts)
(*in).DeepCopyInto(*out)
}
+ if in.Notices != nil {
+ in, out := &in.Notices, &out.Notices
+ *out = make([]Notice, len(*in))
+ for i := range *in {
+ (*in)[i].DeepCopyInto(&(*out)[i])
+ }
+ }
if in.Sidecars != nil {
in, out := &in.Sidecars, &out.Sidecars
*out = make([]SidecarState, len(*in))
diff --git a/pkg/reconciler/taskrun/taskrun.go b/pkg/reconciler/taskrun/taskrun.go
index db18f0f9070..14d0d3a03eb 100644
--- a/pkg/reconciler/taskrun/taskrun.go
+++ b/pkg/reconciler/taskrun/taskrun.go
@@ -576,6 +576,12 @@ func (c *Reconciler) prepare(ctx context.Context, tr *v1.TaskRun) (*v1.TaskSpec,
Status: corev1.ConditionFalse,
Message: taskMeta.VerificationResult.Err.Error(),
})
+ if config.FromContextOrDefaults(ctx).FeatureFlags.EnableNotices {
+ tr.Status.AddControllerNotice(v1.Notice{
+ Level: v1.NoticeLevelWarning,
+ Message: taskMeta.VerificationResult.Err.Error(),
+ })
+ }
case trustedresources.VerificationPass:
tr.Status.SetCondition(&apis.Condition{
Type: trustedresources.ConditionTrustedResourcesVerified,
diff --git a/pkg/reconciler/taskrun/taskrun_test.go b/pkg/reconciler/taskrun/taskrun_test.go
index ff6c1bca65b..472a165fcf7 100644
--- a/pkg/reconciler/taskrun/taskrun_test.go
+++ b/pkg/reconciler/taskrun/taskrun_test.go
@@ -7278,8 +7278,10 @@ status:
name string
task []*v1.Task
noMatchPolicy string
+ enableNotices bool
verificationPolicies []*v1alpha1.VerificationPolicy
wantTrustedResourcesCondition *apis.Condition
+ wantNotices []v1.Notice
}{
{
name: "ignore no match policy",
@@ -7291,6 +7293,16 @@ status:
noMatchPolicy: config.WarnNoMatchPolicy,
verificationPolicies: noMatchPolicy,
wantTrustedResourcesCondition: failNoMatchCondition,
+ }, {
+ name: "warn no match policy with notices enabled",
+ noMatchPolicy: config.WarnNoMatchPolicy,
+ enableNotices: true,
+ verificationPolicies: noMatchPolicy,
+ wantTrustedResourcesCondition: failNoMatchCondition,
+ wantNotices: []v1.Notice{{
+ Level: v1.NoticeLevelWarning,
+ Message: failNoMatchCondition.Message,
+ }},
}, {
name: "pass enforce policy",
noMatchPolicy: config.FailNoMatchPolicy,
@@ -7313,9 +7325,12 @@ status:
},
},
}
+ if tc.enableNotices {
+ cms[0].Data[config.EnableNotices] = "true"
+ }
rr := getResolvedResolutionRequest(t, resolverName, signedTaskBytes, tr.Namespace, tr.Name)
d := test.Data{
- TaskRuns: []*v1.TaskRun{tr},
+ TaskRuns: []*v1.TaskRun{tr.DeepCopy()},
ConfigMaps: cms,
VerificationPolicies: tc.verificationPolicies,
ResolutionRequests: []*resolutionv1beta1.ResolutionRequest{&rr},
@@ -7344,6 +7359,9 @@ status:
if d := cmp.Diff(tc.wantTrustedResourcesCondition, gotVerificationCondition, ignoreLastTransitionTime); d != "" {
t.Error(diff.PrintWantGot(d))
}
+ if d := cmp.Diff(tc.wantNotices, reconciledRun.Status.Notices); d != "" {
+ t.Error(diff.PrintWantGot(d))
+ }
})
}
}
diff --git a/test/featureflags.go b/test/featureflags.go
index a9356b9dbd8..0cd7f28fafe 100644
--- a/test/featureflags.go
+++ b/test/featureflags.go
@@ -117,6 +117,7 @@ func getFeatureFlagsBaseOnAPIFlag(t *testing.T) *config.FeatureFlags {
"enable-cel-in-whenexpression": "true",
"enable-param-enum": "true",
"enable-artifacts": "true",
+ "enable-notices": "true",
"enable-concise-resolver-syntax": "true",
"enable-kubernetes-sidecar": "true",
"keep-pod-on-cancel": "true",
diff --git a/test/per_feature_flags_test.go b/test/per_feature_flags_test.go
index 0a927e87ced..e64453cbb5e 100644
--- a/test/per_feature_flags_test.go
+++ b/test/per_feature_flags_test.go
@@ -44,7 +44,7 @@ const (
)
var (
- alphaFeatureFlags = []string{"enable-param-enum", "keep-pod-enabled-cancel", "enable-cel-in-whenexpression", "enable-artifacts"}
+ alphaFeatureFlags = []string{"enable-param-enum", "keep-pod-enabled-cancel", "enable-cel-in-whenexpression", "enable-artifacts", "enable-notices"}
betaFeatureFlags = []string{}
perFeatureFlags = map[string][]string{
"alpha": alphaFeatureFlags,