使用模板自定义 Task 概览

本指南将介绍如何使用 ConfigMap 支持的模板,在 TaskRunPipelineRunOverview 选项卡中渲染丰富的 HTML 报告。

与其将完整的 HTML 或 Markdown 报告写入单个 Task 结果,不如让你的 Task 输出较小的结构化指标,然后由 UI 使用来自 ConfigMap 的模板渲染最终的概览。

在以下情况下使用本指南:

  • 你希望为某个 Task 提供一个一致、精致的概览(例如代码扫描摘要)。
  • 你的 Markdown 概览已经接近或超过 Task 结果的有效大小限制。
  • 你希望在多个 Task 或集群之间重复使用相同的布局。

模板渲染是 overview-markdown 的一种替代方案。如果某个 Task 仍然输出名为 overview-markdown 的结果,UI 将优先采用该 Markdown,并跳过模板渲染。

先决条件

  • 集群中已安装 Tekton Pipelines
  • 具有在共享模板命名空间 kube-public 中创建 ConfigMap 的权限。

工作原理

  1. 你的 Task 输出一个或多个包含指标或摘要数据的结果。
  2. 你的 Task 元数据包含 annotations,用于告诉 UI:
    • 应使用哪个 ConfigMap 作为模板(通过 label selector)。
    • 应读取哪些 Task 结果 并传递给模板。
  3. UI:
    • 检查 TaskRun 是否有名为 overview-markdown 的结果。如果有,则渲染该 Markdown 并停止
    • 否则,从 TaskRun 中读取模板 annotations。
    • 查找匹配的 ConfigMap 并加载 template.ejs
    • 读取 TaskRun 中声明的结果,并将它们合并为一个 JSON 负载。
    • 使用该负载求值 EJS 模板,并在 Overview 选项卡中渲染生成的 HTML。

步骤

1. 在 Markdown 和模板之间进行选择

在以下情况下使用 overview-markdown 结果:

  • 内容较短,并且容易保持在结果大小限制以内。
  • 你只需要非常简单的格式化。

在以下情况下使用模板 ConfigMap

  • 你希望实现更复杂的布局(例如列、徽章、进度条等)。
  • 你已经有结构化指标,并且希望获得更美观的展示。
  • 你希望多个 Task 共享同一种概览布局。

如果两种方式都已配置,则 overview-markdown 会胜出。你只需不向 overview-markdown 结果写入任何内容,就可以查看模板输出。

2. 创建模板 ConfigMap

创建一个包含你的 EJS 模板以及用于标识其所属 Task 的标签的 ConfigMap

EJS 是一种简单的模板语言,允许你使用纯 JavaScript 生成 HTML 标记。 它不强求如何组织内容。也无需重新发明迭代和控制流。它就是纯粹的 JavaScript。 更多详情请参见:

示例:

apiVersion: v1
kind: ConfigMap
metadata:
  name: my-task-0.1-overview-template
  # configmap should be in kube-public namespace
  namespace: kube-public
  labels:
    # Which Task this template is for
    style.tekton.dev/overview-template-task: my-task
    # Version of the Task/template pairing
    style.tekton.dev/overview-template-task-version: "0.1"
    # Which engine the UI should use
    style.tekton.dev/overview-template-engine: ejs
data:
  template.ejs: |
    <%
      const m = metrics || {};
      const asNumber = (value, fallback = 0) => {
        const parsed = Number(value);
        return Number.isFinite(parsed) ? parsed : fallback;
      };
    %>

    <div style="font:12px sans-serif;margin:8px 0">
      <div style="display:flex;align-items:center;margin-bottom:6px">
        <strong>My Task Summary</strong>
        <% if (m.report_url) { %>
          <a href="<%= m.report_url %>" target="_blank" rel="noreferrer" style="margin-left:8px">
            View report
          </a>
        <% } %>
        <span style="margin-left:auto;color:#5c1">
          Status: <%= m.status || "UNKNOWN" %>
        </span>
      </div>

      <div style="display:flex;flex-wrap:wrap;gap:6px;color:#666">
        <div style="flex:1;min-width:110px">
          <b><%= asNumber(m.total) %></b> Total
        </div>
        <div style="flex:1;min-width:110px">
          <b><%= asNumber(m.passed) %></b> Passed
        </div>
        <div style="flex:1;min-width:110px">
          <b><%= asNumber(m.failed) %></b> Failed
        </div>
      </div>
    </div>

要点:

  • 模板文件名始终是 template.ejs
  • UI 会将结果中的变量注入模板。
  • 你可以使用标准 EJS 语法<% %><%= %>)来实现简单的逻辑和格式化。

3. 为你的 Task 添加一个结果

在你的 Task 上定义一个 result,用于承载模板所需的指标。

Task 示例:

apiVersion: tekton.dev/v1
kind: Task
metadata:
  name: my-task
spec:
  results:
    - name: TASK_METRICS
      type: object
      description: Summary metrics for the overview template.
      properties:
        total: {}
        passed: {}
        failed: {}
        status: {}
        report_url: {}
  steps:
    - name: run-and-collect-metrics
      image: <your-image>
      script: |
        #!/usr/bin/env sh
        set -eu

        # TODO: replace these with real values
        total=10
        passed=9
        failed=1
        status="OK"
        report_url="https://example.com/report/123"

        printf '{"total": "%s", "passed": "%s", "failed": "%s", "status": "%s", "report_url": "%s"}' \
          "$total" \
          "$passed" \
          "$failed" \
          "$status" \
          "$report_url" \
          > "$(results.TASK_METRICS.path)"

指导原则:

  • 保持结果小而扁平。使用计数、百分比、状态和 URL 等简单字段。
  • 不要嵌入大量文本块。
  • 如果使用 object result,请确保步骤写入的是有效 JSON(没有尾随逗号,且引号正确)。

4. 为 Task 添加注解以绑定模板

Task 上使用 annotations 告诉 UI 应使用哪个模板,以及要传递哪些结果。

单个结果示例:

apiVersion: tekton.dev/v1
kind: Task
metadata:
  name: my-task
  annotations:
    # 1) Where to find the template ConfigMap
    style.tekton.dev/overview-template-selector: >
      style.tekton.dev/overview-template-task=my-task,
      style.tekton.dev/overview-template-task-version=0.1

    # 2) Which Task result(s) to read from the TaskRun
    #    Here: expose TASK_METRICS as "metrics" in template.ejs
    #    If you do not specify an alias key, the result name is used (TASK_METRICS).
    style.tekton.dev/overview-template-result-key: TASK_METRICS:metrics
    
spec:
  results:
    - name: TASK_METRICS
      type: object
  steps:
    - name: run-and-collect-metrics
      image: <your-image>
      # ...

从概念上讲,UI 会构造如下负载:

{
  "metrics": {
    "total": "10",
    "passed": "9",
    "failed": "1",
    "status": "OK",
    "report_url": "https://example.com/report/123"
  }
}

然后它会使用该负载求值 template.ejs,这样你就可以使用 metrics.totalmetrics.status 等字段。

你可以将多个 Task 结果合并到一次模板渲染中。当以下情况出现时,这非常有用:

  • 一个结果保存指标。
  • 另一个结果保存完整报告的 URL。

示例:

metadata:
  annotations:
    style.tekton.dev/overview-template-selector: >
      style.tekton.dev/overview-template-task=sonarqube-scanner,
      style.tekton.dev/overview-template-task-version=0.5

    # Multiple results, separated by commas
    # Aliases:
    # - CODE_SCAN_METRICS -> metrics
    # - SCAN_RESULT_URL   -> SCAN_RESULT_URL (no alias)
    # - SCAN_PROJECT      -> SCAN_PROJECT (no alias)
    style.tekton.dev/overview-template-result-key: |
      CODE_SCAN_METRICS:metrics,SCAN_RESULT_URL,SCAN_PROJECT

UI 将构造如下类似的负载:

{
  "metrics": {
    "bugs": "2",
    "vulnerabilities": "1"
  },
  "SCAN_RESULT_URL": "https://devops-sonar.example.net/dashboard?id=project&branch=main",
  "SCAN_PROJECT": ["foo", "bar"]
}

5. 运行 Task 并检查 Overview 选项卡

  1. ConfigMapTask 应用到你的集群。

  2. 创建一个使用该 TaskTaskRunPipelineRun,例如:

    apiVersion: tekton.dev/v1
    kind: TaskRun
    metadata:
      name: my-task-run
    spec:
      taskRef:
        name: my-task
  3. 等待运行完成。

  4. 在 UI 中打开该运行并选择 Overview 选项卡。

如果一切配置正确,你应该会看到模板生成的 HTML。

如果 Task 同时输出了 overview-markdown,则会显示该 Markdown,而不是模板。

提示与约定

  • 优先使用较小的 metrics result。 将结果视为一个紧凑的摘要。
  • 保持模板逻辑简单。 关注展示,而不是业务逻辑。避免在模板中进行大量计算。
  • 对缺失数据进行防御性处理。 在模板中使用默认值,以免缺少字段导致渲染失败。

故障排查

Overview 选项卡中没有显示任何内容

  • Task 是否输出了空的 overview-markdown
    • 如果是,UI 会显示该空 Markdown 并忽略模板。你只需不向 overview-markdown 结果写入任何内容,就可以查看模板输出。
  • TaskRun 是否包含 overview-template-result-key 中列出的结果?
    • 检查 TaskRun YAML,并查看 .status.results
  • overview-template-selector 中的 selector 是否能在 kube-public 命名空间中精确匹配一个 ConfigMap
    • 使用 kubectl get configmap -l <your-selector> -n kube-public 进行测试。

模板已渲染,但数据显示为空或不正确

  • 如果你使用的是 object result,请确认该 result 有效,并且包含模板所期望的字段。
  • 检查 overview-template-result-key 中的名称是否与 Task result 名称完全一致。

结果大小或 termination message 限制

  • 移除结果中非必要的字段。
  • 将按文件的详细报告写入日志或外部系统,只在结果中保留摘要数字和 URL。
  • 请记住,每个 Task 仍然会在所有步骤和结果之间共享同一个 termination-message 预算。
  • 修改 result 限制

选中了错误的模板

  • 确保 overview-template-selector 足够具体(例如同时包含 Task 名称和版本)。
  • 避免对无关模板重复使用相同标签。
  • 如果有多个 ConfigMap 匹配该 selector,请收紧标签,使只选中一个 ConfigMap

一旦你拥有了 template ConfigMapmetric results 和正确的 Task annotations,就可以在不修改 UI 本身的情况下构建可复用、可版本化的 Task 概览布局。