跳到主要内容

为后端数据源插件添加查询迁移

随着插件的演进,插件定义的查询模型可能需要进行重大更改。原因包括插件依赖的第三方服务发生重大变化、重构、新功能等等。当更改数据源查询模型时,插件应实施迁移逻辑,将现有查询从之前的格式转换为最新的格式。此迁移逻辑应包含在后端(对于非前端发起的查询,例如告警)和前端(以使查询适应较新版本的 QueryEditor)中。

为什么要添加查询迁移处理程序

为了确保兼容性并保持无缝性能,查询迁移处理程序将旧查询转换为当前格式。此方法允许您在不破坏现有查询或重复代码的情况下交付更新,从而在用户更新插件时提供平滑的过渡。

开始之前

根据您执行本指南中某个步骤所采用的方法,您可能需要满足某些先决条件。这些先决条件是

  1. 必须将 Grafana 配置为将数据源作为独立的 API 服务器运行,此行为位于功能标志 grafanaAPIServerWithExperimentalAPIs 之后。
  2. 插件必须在 Grafana 版本 11.4 或更高版本上运行。

有关这些先决条件的更多信息,请参见步骤 5,但如果您的插件无法满足这些要求,则可以使用替代方法,即使用现有 API。

实施后端迁移处理程序

要实施后端迁移处理程序,请在后端代码中添加迁移逻辑。本指南将引导您完成使用最新工具自动迁移查询并避免重复代码的必要步骤。

注意

本指南中详细介绍的迁移系统不支持双向迁移。仅涵盖向前迁移。查询迁移不会自动持久化,因此用户需要手动保存更改以确保该过程按预期工作。

步骤 1(可选):添加查询模式

首先,插件不需要具有强类型查询。虽然这降低了插件开发的门槛,但不定义类型的插件更难扩展和维护。本指南中的第一步是添加所需的文件来定义插件查询。

请参阅以下示例:grafana-plugin-examples#400。如您所见,需要创建多个文件。这些文件将用于生成 OpenAPI 文档和验证接收到的查询是否有效(但这仍然是一个正在进行中的功能,尚未可用)。

创建这些文件

  • query.go:此文件定义查询的 Golang 类型。为了使自动迁移工作,重要的是您的查询扩展新的 v0alpha1.CommonQueryProperties。之后,只需定义您的查询自定义属性。
  • query_test.go:此测试文件既用于检查所有 JSON 文件是否与查询模型保持最新,又用于生成它们。首次执行测试时,它将生成这些文件(因此请考虑 query.types.json 需要存在,即使它是空的)。
  • query.*.json:自动生成的文件。这些模式可用于 OpenAPI 文档。

步骤 2:更改查询模型

注意

有关如何添加查询迁移的完整示例(步骤 2、3 和 4),请参阅实验性 API稳定 API的代码。

一旦您的插件拥有自己的模式,就开始引入模型更改。由于主要版本(或同一 API 版本)内的查询需要兼容,因此请维护对旧数据格式的引用。此引用还有助于实现简单的迁移路径。

例如,假设您要更改插件的查询格式,并且您使用的 Multiplier 属性要更改为 Multiply,如下所示

 type DataQuery struct {
v0alpha1.CommonQueryProperties

- // Multiplier is the number to multiply the input by
+ // Multiply is the number to multiply the input by
+ Multiply int `json:"multiply,omitempty"`
+
+ // Deprecated: Use Multiply instead
Multiplier int `json:"multiplier,omitempty"`
}

在此示例中,您可以重新生成模式,运行 query_test.go 中的测试,以便您的新数据类型可以准备好使用。

请注意,尚不存在重大更改,因为所有新参数(在本例中为 Multiply)都标记为可选。此外,没有修改任何其他业务逻辑,因此一切都应像以前一样工作,使用已弃用的属性。在下一步中,实际上存在重大更改。

步骤 3:使用新的查询格式

修改插件代码以使用新的数据模型,忽略现有仪表板或查询继续使用旧模型的事实。请注意,您必须同时修改前端和后端代码。

首先将 Multiplier 的使用替换为 Multiply

这是一个后端示例

func (d *Datasource) query(ctx context.Context, pCtx backend.PluginContext, quer
return backend.DataResponse{}, fmt.Errorf("unmarshal: %w", err)
}
q := req.URL.Query()
- q.Add("multiplier", strconv.Itoa(input.Multiplier))
+ q.Add("multiplier", strconv.Itoa(input.Multiply))
req.URL.RawQuery = q.Encode()
}
httpResp, err := d.httpClient.Do(req)

这是一个前端示例

 export class QueryEditor extends PureComponent<Props> {
type="number"
id="multiplier"
name="multiplier"
- value={this.props.query.multiplier}
- onChange={(e) => this.props.onChange({ ...this.props.query, multiplier: e.currentTarget.valueAsNumber })}
+ value={this.props.query.multiply}
+ onChange={(e) => this.props.onChange({ ...this.props.query, multiply: e.currentTarget.valueAsNumber })}
/>
</HorizontalGroup>
);

此时,终于发生了重大更改。新查询将使用新格式并按预期工作,但旧查询将无法正常工作,因为它们未定义新属性。让我们修复它。

步骤 4:在后端添加迁移代码

在后端创建一个解析函数,该函数接受 QueryData 函数接收的通用 JSON blob,然后根据需要迁移格式。该函数应接收 backend.DataQuery 并返回您自己的 kinds.DataQuery

此函数应仅从原始 DataQuery 中解组 JSON 并将其解析为您自己的 DataQuery,并执行任何必要的迁移。在您的插件逻辑中使用此函数。通过此示例中的更改,无论查询使用旧模型 (Multiplier) 还是新模型 (Multiply),都可以按预期工作。

示例

func (d *Datasource) query(ctx context.Context, pCtx backend.PluginContext, quer
return backend.DataResponse{}, fmt.Errorf("new request with context: %w", err)
}
if len(query.JSON) > 0 {
- input := &kinds.DataQuery{}
- err = json.Unmarshal(query.JSON, input)
+ input, err := convertQuery(query)
if err != nil {
- return backend.DataResponse{}, fmt.Errorf("unmarshal: %w", err)
+ return backend.DataResponse{}, err
}
q := req.URL.Query()
q.Add("multiplier", strconv.Itoa(input.Multiply))
...

+func convertQuery(orig backend.DataQuery) (*kinds.DataQuery, error) {
+ input := &kinds.DataQuery{}
+ err := json.Unmarshal(orig.JSON, input)
+ if err != nil {
+ return nil, fmt.Errorf("unmarshal: %w", err)
+ }
+ if input.Multiplier != 0 && input.Multiply == 0 {
+ input.Multiply = input.Multiplier
+ input.Multiplier = 0
+ }
+ return input, nil
+}

步骤 5:从前端使用迁移代码(使用实验性 API)

注意

此功能取决于功能标志 grafanaAPIServerWithExperimentalAPIs。它还需要 @grafana/runtime > 11.4 包(仍为实验性功能)。如果您的插件实现了此功能,请将其 grafanaDepencency 升级到 ">=11.4.0"。如果您的插件无法满足这些要求,请参阅使用旧版 API 运行迁移

您应该能够从前端和后端调用您的 convertQuery 函数,因此我们的 QueryEditor 组件应该能够将查询转换为新格式。为了将此函数公开给前端,后端需要实现 QueryConversionHandler 接口。这只是 convertQuery 函数的包装器,但用于多个查询。请参阅此示例中如何实现此功能。

最后,调整前端,以便 @grafana/runtime 知道是否应运行迁移操作。分两步执行此操作

  1. 在插件的 DataSource 类中实现 @grafana/runtime 公开的 MigrationHandler。设置属性 hasBackendMigration(为 true)并实现函数 shouldMigrateshouldMigrate 函数接收一个查询,并验证它是否需要迁移(例如,通过检查最新的属性或检查预期的插件版本,如果它是模型的一部分)。此验证避免了对后端的不必要查询。
  2. 将包装器 QueryEditorWithMigration 与您的 QueryEditor 组件一起使用。此包装器将确保在渲染编辑器之前迁移查询。

就是这样。一旦插件实现这些步骤,现有查询和新查询将继续工作,而无需在多个位置重复迁移逻辑。

注意

要查看如何在完整示例中完成步骤 2 到 5,请参阅此示例

步骤 5(替代方法):使用旧版 API 运行迁移

除了使用实验性 API 运行迁移之外,还可以使用旧版 API 运行迁移。没有其他要求。

请按照以下步骤操作

  1. 在后端,将 convertQuery 作为资源公开,以便您可以使用资源端点(如 /migrate-query)检索它。
  2. 修复插件 QueryEditor,因为旧查询尝试渲染旧格式,而插件逻辑未为此做好准备。为此,请配置插件以使用刚刚定义的新迁移端点。
注意

要查看如何在完整示例中完成步骤 2 到 5,请参阅此示例

步骤 6(可选):添加 AdmissionHandler

注意

此步骤是可选的。仅当您使用功能标志 grafanaAPIServerWithExperimentalAPIs 运行 Grafana 时才需要此步骤。

当使用实验性 API 运行 Grafana 时,每个数据源都将作为隔离的 API 服务器运行。这意味着查询将路由到诸如 https://<grafana-host>/apis/<datasource>.datasource.grafana.app/v0alpha1/namespaces/stack-1/connections/<uid>/query 之类的服务器。

在这种情况下,为了确保您的插件以预期的 API 版本(最初为 v0alpha1)运行,请实现 AdmissionHandler。此 AdmissionHandler 确保给定的数据源设置满足正在运行的 API 版本的期望,因此它们可以处理该 API 版本的查询。

当插件处于 v0* 状态时,此步骤不是强制性的,但一旦插件达到 v1,则此步骤是强制性的。目前,它用于在保存数据源设置时验证它们

AdmissionHandler 方法应实现两个主要函数

  • ValidateAdmission:检查给定的实体是否有效(在本例中为数据源设置)。
  • MutateAdmission:允许在存储实体之前对其进行修改。

我们的示例中,这两个函数是可互换的,因为它们都执行相同的代码(即,仅执行验证,而没有任何修改)。

结论

添加查询迁移允许您的 Grafana 数据源插件不断演进,而不会破坏现有用户的功能。通过在后端和前端维护迁移处理程序,您可以确保兼容性,并在每次更新时提供更流畅的用户体验。