Airbyte Tempo 声明式连接器深度解析:manifest-only 低代码架构、增量同步与验收测试实战
【免费下载链接】airbyteOpen-source data movement for ELT pipelines and AI agents — from APIs, databases & files to warehouses, lakes, and AI applications. Both self-hosted and Cloud.项目地址: https://gitcode.com/gh_mirrors/ai/airbyte
本篇技术指南以 Airbyte 开源仓库中的 source-tempo 连接器 为核心,剖析一个典型的manifest-only(仅配置文件)低代码连接器是如何从零构建的:它完全基于 Connector Builder 生成的 YAML 清单(manifest)运行,无需编写任何 Java/Python 代码。读完本文,你将掌握 Tempo 时间跟踪数据的四个核心 Stream(accounts、customers、worklogs、workload-schemes)的拉取机制、Bearer Token 认证配置、游标分页与增量同步的底层实现,以及该连接器在仓库中的验收测试(Connector Acceptance Tests)体系,并能直接对照仓库源码进行二次开发或排障。
一、连接器定位:声明式连接器的模板化 README 意味着什么
在 source-tempo 的 README 开头,第一句话即点明了它的技术身份:
This is a declarative connector built with the Connector Builder. For details on the underlying YAML format, see the Low-Code CDK Overview.
这句话包含三层关键信息:
- 它是声明式(declarative)连接器:连接器的全部行为由一份 YAML 清单描述,而不是由代码逻辑驱动。
- 它由 Connector Builder 构建:意味着该清单可以通过 Airbyte 平台的 Connector Builder UI 进行可视化编辑和生成。
- 底层格式遵循 Low-Code CDK 规范:
manifest.yaml中使用的DeclarativeSource、DeclarativeStream、HttpRequester、SimpleRetriever等类型,全部是 Low-Code CDK 的标准构件。
从 metadata.yaml 可以进一步确认其技术栈标记:
tags: - cdk:low-code - language:manifest-only connectorSubtype: api connectorType: source dockerRepository: airbyte/source-tempo dockerImageTag: 0.4.61其中language:manifest-only是当前仓库对这类连接器的正式归类:整个连接器就是一个清单文件,构建时基于airbyte/source-declarative-manifest基础镜像(见 metadata.yaml 中的connectorBuildOptions.baseImage)直接运行。因此 README 本身是 Airbyte 为所有声明式连接器统一生成的模板,真正的技术细节全部沉淀在同目录的manifest.yaml中——这也是阅读这类连接器时最重要的认知:README 只是入口,manifest 才是灵魂。
二、Tempo 连接器的核心配置:认证、端点与连接检查
2.1 连接配置(Spec):只有一个必填字段
在 manifest.yaml 的spec段定义了连接器唯一需要的用户输入:
spec: type: Spec connection_specification: type: object $schema: http://json-schema.org/draft-07/schema# required: - api_token properties: api_token: type: string title: API token description: >- Tempo API Token. Go to Tempo>Settings, scroll down to Data Access and select API integration. airbyte_secret: true order: 0要点说明:
| 属性 | 值 | 含义 |
|---|---|---|
api_token | string | Tempo API Token,从 Tempo 的 Settings → Data Access → API integration 中生成 |
airbyte_secret | true | 声明为机密字段,UI 中会以密码框展示并加密存储 |
required | [api_token] | 唯一必填项,没有其他可选项 |
对应的测试样例配置见 integration_tests/sample_config.json:
{ "api_token": "<api_token>" }这也印证了该连接器极其简洁:唯一的接入成本就是生成一个 Tempo API Token。
2.2 认证方式:BearerAuthenticator
所有四个 Stream 均通过BearerAuthenticator携带令牌访问 Tempo API v4:
authenticator: type: BearerAuthenticator api_token: "{{ config['api_token'] }}"这段配置表示:HTTP 请求头会以Authorization: Bearer <api_token>的形式注入,令牌值取自用户在连接配置中填写的api_token。连接器请求的基础地址统一为https://api.tempo.io/4/(即 Tempo Timesheets API v4),该域也登记在 metadata.yaml 的allowedHosts白名单中:
allowedHosts: hosts: - api.tempo.io2.3 连接检查(Check):用 workload-schemes 流做健康探测
连接器在用户创建连接时会执行一次连通性检查,manifest.yaml顶部定义了检查策略:
check: type: CheckStream stream_names: - workload-schemes即通过请求workload-schemes流来判断 Token 是否有效。选择该流作为探测目标,是因为它属于轻量级的元数据接口,且任何具备 Data Access 权限的 Token 都应当能访问。
三、四大数据流:从 Tempo API 到 Airbyte Stream 的映射
连接器定义了四个 Stream,分别对应 Tempo Timesheets API v4 的四个端点。下表汇总了它们在 manifest.yaml 中的定义与主键:
| Stream 名称 | API 路径 | 主键(primary_key) | 同步模式 |
|---|---|---|---|
accounts | /accounts | id | full_refresh |
customers | /customers | id | full_refresh |
worklogs | /worklogs | tempoWorklogId | full_refresh +incremental |
workload-schemes | /workload-schemes | id | full_refresh |
注意:
worklogs的主键字段是tempoWorklogId而非id,这是由 Tempo API 返回结构决定的——每条工作日志记录以tempoWorklogId标识,见 integration_tests/expected_records.jsonl 中的真实返回样例。
3.1 统一的数据提取与解析:DpathExtractor + results
四个 Stream 的响应体结构一致(分页包裹),因此统一使用DpathExtractor从响应 JSON 中按路径results提取记录数组:
record_selector: type: RecordSelector extractor: type: DpathExtractor field_path: - results这意味着 Tempo API v4 的列表接口返回形如{"results": [...], "metadata": {"next": ...}}的封装结构,连接器剥掉外壳后逐条产出记录。
3.2 游标分页:CursorPagination + metadata.next
所有 Stream 都配置了相同的分页策略,这是本连接器复用性最强的一段配置:
paginator: type: DefaultPaginator page_token_option: type: RequestPath page_size_option: type: RequestOption field_name: limit inject_into: request_parameter pagination_strategy: type: CursorPagination page_size: 50 cursor_value: "{{ response['metadata']['next'] }}" stop_condition: "{{ 'next' not in response['metadata'] }}"逐项解读:
- 每页 50 条:
page_size: 50,并通过请求参数limit=50传给 API; - 游标来自响应体:
cursor_value读取上一页响应的metadata.next字段作为下一页的地址; page_token_option.type: RequestPath:下一页游标被拼接到 URL 路径上继续请求;- 终止条件:
stop_condition判断响应metadata中不再存在next键时停止翻页。
这套设计让连接器能够稳定遍历 Tempo 的分页列表,同时不依赖页码递增(Tempo 使用基于游标的翻页语义),对数据一致性更友好。
3.3 403 容错:CompositeErrorHandler
每个 Stream 还配置了复合错误处理器:
error_handler: type: CompositeErrorHandler error_handlers: - type: DefaultErrorHandler response_filters: - type: HttpResponseFilter http_codes: - 403 action: IGNORE - type: DefaultErrorHandler含义是:当 API 返回403 Forbidden时,连接器选择IGNORE(跳过该流继续同步),而不是让整个同步失败。这一设计对应实际使用场景——Tempo Token 的权限范围(scope)可能只覆盖部分数据(例如只读 accounts)。acceptance-test-config.yml 中的测试用例也印证了这一点:
- config_path: "secrets/accounts_only_config.json" configured_catalog_path: "integration_tests/configured_catalog.json" empty_streams: - name: "worklogs" bypass_reason: "token scope does not include this stream" - name: "workload-schemes" bypass_reason: "token scope does not include this stream"即:仅具备 accounts 权限的 Token,同步时 worklogs 与 workload-schemes 返回空,但连接器通过 IGNORE 策略保证整体同步不中断。
四、增量同步(Incremental Sync):worklogs 流的 DatetimeBasedCursor
worklogs是唯一支持增量同步的 Stream,其incremental_sync配置值得单独展开:
incremental_sync: type: DatetimeBasedCursor cursor_field: startDate name: worklogs path: worklogs cursor_datetime_formats: - "%Y-%m-%d" datetime_format: "%Y-%m-%d" start_datetime: type: MinMaxDatetime datetime: "2020-01-01" datetime_format: "%Y-%m-%d" start_time_option: type: RequestOption field_name: from inject_into: request_parameter end_time_option: type: RequestOption field_name: to inject_into: request_parameter end_datetime: type: MinMaxDatetime datetime: "{{ today_utc() }}" datetime_format: "%Y-%m-%d" step: P1W cursor_granularity: P1D技术要点:
- 游标字段:
startDate,即每条工作日志的开始日期,日期格式为%Y-%m-%d(例如2021-01-24,见 expected_records 样例); - 时间窗口参数:增量请求通过
from(起始日期)与to(结束日期)两个请求参数传给/worklogs接口; - 起始时间:固定从
2020-01-01开始回溯(可保证首次同步覆盖历史数据); - 结束时间:动态取
{{ today_utc() }}(当前 UTC 日期),保证每次同步只取到当天; - 分片步长:
step: P1W表示将时间范围按周切块逐段请求,避免单次查询跨度过大触发 API 限制; - 游标粒度:
cursor_granularity: P1D表示游标状态按天精度推进,与 Tempo API 的日期参数粒度一致。
在 integration_tests/configured_catalog.json 中可以看到 worklogs 被标记为支持增量并指定默认游标字段:
{ "stream": { "name": "worklogs", "supported_sync_modes": ["full_refresh", "incremental"], "source_defined_cursor": true, "default_cursor_field": ["startDate"], "source_defined_primary_key": [["tempoWorklogId"]] }, "sync_mode": "incremental", "destination_sync_mode": "overwrite" }值得注意的是source_defined_cursor: true:游标字段由连接器(Source)定义而非用户在 UI 中选择,简化了配置流程。增量状态由 Airbyte 平台持久化,下一次同步从上次记录的startDate继续拉取。
五、Schema 设计:InlineSchemaLoader 与字段级语义
每个 Stream 的 JSON Schema 通过InlineSchemaLoader内联在 manifest 中(schemas段),schema 同时承载了字段类型、可空性与语义描述。以 worklogs 为例,其核心字段(见 manifest.yaml 中schemas.worklogs.properties):
| 字段 | 类型 | 说明(schema description) |
|---|---|---|
tempoWorklogId | integer | The ID of the tempo worklog(主键) |
startDate | string | Start Date of the worklog(增量游标) |
startTime | string/null | 开始时刻,如08:00:00 |
timeSpentSeconds | integer | 工作耗时(秒) |
billableSeconds | integer/null | 可计费时长(秒) |
description | string/null | 工作日志描述 |
author | object | 作者,含accountId与self |
issue | object | 关联 Jira issue,含id与self |
attributes | object | 附加属性值,values为键值对数组 |
createdAt/updatedAt | string | 创建/更新时间(ISO8601) |
self | string (uri) | 该工作日志的 API URL |
大部分只读字段标记了readOnly: true,表明 Tempo 侧生成、连接器仅透传。metadata.autoImportSchema段中四个流均设为false,说明 schema 完全由 manifest 内联定义,不会在运行时自动从 API 导入——这保证了同步结果的字段结构稳定可预期。
其余三个流的字段也值得一提:
- accounts:
id、key(如ACCOUNT1)、name、status(如OPEN)、global(是否全局账户)、lead(负责人 accountId)、monthlyBudget(月度预算,可空)、category/customer/contact等嵌套对象; - customers:极简结构,仅
id、key、name、self四字段; - workload-schemes:工作负载方案,含
days(周内每天的day与requiredSeconds数组)、defaultScheme、memberCount、description等,用于描述 Jira 时间跟踪配置中每日要求的工作时长。
从 expected_records.jsonl 可以看到真实数据形态,例如 workload-schemes 的样例:
{"stream": "workload-schemes", "data": { "self": "https://api.tempo.io/4/workload-schemes/2", "id": 2, "name": "Tempo Default Workload Scheme", "defaultScheme": true, "memberCount": 2, "days": [ {"day": "MONDAY", "requiredSeconds": 28800}, {"day": "TUESDAY", "requiredSeconds": 28800}, {"day": "SATURDAY", "requiredSeconds": 0}, {"day": "SUNDAY", "requiredSeconds": 0} ] }}这类真实记录与 manifest 中的 schema 完全对应,可作为开发时理解字段语义的权威参考。
六、本地开发与验收测试:如何验证连接器行为
6.1 本地开发指引
README 的 Development 一节指向了 Airbyte 的本地连接器开发文档(Developing Connectors Locally)。针对 manifest-only 连接器,仓库内的标准做法是:
- 使用
airbyte/source-tempo:dev镜像构建(见 acceptance-test-config.yml 中的connector_image); - 准备配置文件(Token 放在
secrets/config.json,可参考 sample_config.json 的格式); - 运行连接器验收测试套件验证 spec / connection / discovery / basic_read / full_refresh / incremental 六类行为。
6.2 Connector Acceptance Tests(CAT)配置解读
acceptance-test-config.yml 是理解该连接器质量保障体系的最佳入口:
connector_image: airbyte/source-tempo:dev acceptance_tests: spec: tests: - spec_path: "manifest.yaml" connection: tests: - config_path: "secrets/config.json" status: "succeed" - config_path: "integration_tests/invalid_config.json" status: "failed" discovery: tests: - config_path: "secrets/config.json" backward_compatibility_tests_config: disable_for_version: "0.2.6" basic_read: tests: - config_path: "secrets/config.json" configured_catalog_path: "integration_tests/configured_catalog.json" expect_records: path: "integration_tests/expected_records.jsonl" - config_path: "secrets/accounts_only_config.json" configured_catalog_path: "integration_tests/configured_catalog.json" empty_streams: - name: "worklogs" bypass_reason: "token scope does not include this stream" full_refresh: tests: - config_path: "secrets/config.json" configured_catalog_path: "integration_tests/configured_catalog.json" incremental: tests: - config_path: "secrets/config.json" configured_catalog_path: "integration_tests/configured_catalog.json" future_state: future_state_path: "integration_tests/abnormal_state.json"各测试维度说明:
- spec:校验 manifest 本身的 spec 定义有效性;
- connection:用有效配置断言连接成功、用
invalid_config.json断言连接失败; - discovery:校验 schema 发现与向后兼容性(对
0.2.6及更早版本关闭了向后兼容检查,说明该版本存在 schema 变更); - basic_read:读取记录并与
expected_records.jsonl逐条比对;同时用受限权限 Token 验证empty_streams场景(worklogs / workload-schemes 为空但不报错); - full_refresh:全量刷新模式回归;
- incremental:增量模式回归,其中
future_state使用 abnormal_state.json 注入未来的游标状态(startDate: "2031-04-14"),用于验证当游标已超前于数据时同步能够正确空跑而不报错。
6.3 发布与支持状态
从 metadata.yaml 可知:
- 连接器定义 ID:
d1aa448b-7c54-498e-ad95-263cbebcd2db; - 当前镜像版本:
airbyte/source-tempo:0.4.61; - 发布阶段:
beta,支持级别:community(社区维护); - 许可协议:ELv2;
- 同时启用 OSS 与 Cloud 注册(
registryOverrides),并配置了两套实时测试连接(liveTests)与来自 GSM 密钥库的测试凭证。
七、总结:从 Tempo 连接器看 manifest-only 连接器的通用范式
通过对 source-tempo 连接器 及其 manifest.yaml 的剖析,可以提炼出 Airbyte 声明式连接器的通用实现范式:
- 单一清单承载全部逻辑:认证(BearerAuthenticator)、分页(CursorPagination)、增量(DatetimeBasedCursor)、错误容错(CompositeErrorHandler)、Schema(InlineSchemaLoader)全部声明在 YAML 中;
- 面向 API 真实结构建模:
DpathExtractor路径、主键字段、游标字段均需与上游 API 的实际返回逐一对齐,仓库内的expected_records.jsonl是核对真实数据形态的权威素材; - 容错优先:通过
HttpResponseFilter对 403 做 IGNORE,使得 Token 权限受限时同步仍可部分完成; - 测试完备:spec/connection/discovery/basic_read/full_refresh/incremental 六类 CAT 测试 + 受限权限场景 + 未来状态场景,构成了声明式连接器可交付的质量底线。
对想要基于 Tempo API 构建数据管道(如工时合规分析、项目成本核算、计费报表)的团队而言,此连接器提供了开箱即用的数据接入能力:只需申请 Tempo API Token,即可将 accounts、customers、worklogs、workload-schemes 四类数据持续同步到任意 Airbyte 支持的仓库、数仓或 AI 应用;而对想要开发自有声明式连接器的工程师,本连接器则是研究分页、增量与容错配置的完整范本。
【免费下载链接】airbyteOpen-source data movement for ELT pipelines and AI agents — from APIs, databases & files to warehouses, lakes, and AI applications. Both self-hosted and Cloud.项目地址: https://gitcode.com/gh_mirrors/ai/airbyte
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考