×

作为操作符作者,您可以使用 Operator SDK 中的 scorecard 工具执行以下任务:

  • 验证您的操作符项目没有语法错误并且已正确打包

  • 查看有关改进操作符方法的建议

Red Hat 支持的 Operator SDK CLI 工具版本(包括与 Operator 项目相关的脚手架和测试工具)已弃用,并计划在未来版本的 OpenShift Dedicated 中删除。Red Hat 将在此版本的生命周期内为此功能提供错误修复和支持,但此功能将不再接收增强功能,并将从未来的 OpenShift Dedicated 版本中删除。

不建议使用 Red Hat 支持的 Operator SDK 版本创建新的 Operator 项目。拥有现有 Operator 项目的操作符作者可以使用 OpenShift Dedicated 发布的 Operator SDK CLI 工具版本来维护其项目并创建针对较新版本的 OpenShift Dedicated 的 Operator 版本。

以下与 Operator 项目相关的基础镜像 *未* 被弃用。这些基础镜像的运行时功能和配置 API 仍受支持以进行错误修复和解决 CVE。

  • 基于 Ansible 的 Operator 项目的基础镜像

  • 基于 Helm 的 Operator 项目的基础镜像

有关不受支持的社区维护版本的 Operator SDK 的信息,请参阅 Operator SDK (Operator Framework)

关于 scorecard 工具

虽然 Operator SDK 的 `bundle validate` 子命令可以验证本地 bundle 目录和远程 bundle 镜像的内容和结构,但您可以使用 `scorecard` 命令根据配置文件和测试镜像对您的 Operator 运行测试。这些测试是在配置和构建的测试镜像中实现的,这些测试镜像由 scorecard 执行。

Scorecard 假设它是在可以访问已配置的 Kubernetes 集群(例如 OpenShift Dedicated)的情况下运行的。Scorecard 在 Pod 内运行每个测试,从中聚合 Pod 日志并将测试结果发送到控制台。Scorecard 具有内置的基本测试和 Operator Lifecycle Manager (OLM) 测试,还提供了一种执行自定义测试定义的方法。

Scorecard 工作流程
  1. 创建任何相关自定义资源 (CR) 和 Operator 所需的所有资源

  2. 在 Operator 的部署中创建一个代理容器以记录对 API 服务器的调用并运行测试

  3. 检查 CR 中的参数

评分卡测试不对被测试操作符的状态做任何假设。创建操作符和操作符的 CR 超出了评分卡本身的范围。但是,如果测试设计用于资源创建,则评分卡测试可以创建它们所需的任何资源。

scorecard 命令语法
$ operator-sdk scorecard <bundle_dir_or_image> [flags]

评分卡需要一个位置参数,用于指定操作符捆绑包在磁盘上的路径或捆绑包镜像的名称。

有关标志的更多信息,请运行

$ operator-sdk scorecard -h

评分卡配置

评分卡工具使用一个配置,允许您配置内部插件以及多个全局配置选项。测试由名为config.yaml的配置文件驱动,该文件由make bundle命令生成,位于您的bundle/目录中。

./bundle
...
└── tests
    └── scorecard
        └── config.yaml
评分卡配置文件示例
kind: Configuration
apiversion: scorecard.operatorframework.io/v1alpha3
metadata:
  name: config
stages:
- parallel: true
  tests:
  - image: quay.io/operator-framework/scorecard-test:v1.36.1
    entrypoint:
    - scorecard-test
    - basic-check-spec
    labels:
      suite: basic
      test: basic-check-spec-test
  - image: quay.io/operator-framework/scorecard-test:v1.36.1
    entrypoint:
    - scorecard-test
    - olm-bundle-validation
    labels:
      suite: olm
      test: olm-bundle-validation-test

配置文件定义了评分卡可以执行的每个测试。评分卡配置文件的以下字段定义了测试:

配置字段 描述

image

实现测试的测试容器镜像名称

entrypoint

在测试镜像中调用以执行测试的命令和参数

labels

评分卡定义的或自定义的标签,用于选择要运行的测试

内置评分卡测试

评分卡附带预定义的测试,这些测试被安排到套件中:基本测试套件和操作符生命周期管理器 (OLM) 套件。

表 1. 基本测试套件
测试 描述 简称

Spec 块存在

此测试检查在集群中创建的自定义资源 (CR),以确保所有 CR 都具有spec块。

basic-check-spec-test

表 2. OLM 测试套件
测试 描述 简称

捆绑包验证

此测试验证传递到评分卡的捆绑包中找到的捆绑包清单。如果捆绑包内容包含错误,则测试结果输出将包括验证器日志以及验证库的错误消息。

olm-bundle-validation-test

提供的 API 具有验证

此测试验证提供的 CR 的自定义资源定义 (CRD) 是否包含验证部分,以及是否对在 CR 中检测到的每个specstatus字段进行验证。

olm-crds-have-validation-test

拥有的 CRD 已列出资源

此测试确保通过cr-manifest选项提供的每个 CR 的 CRD 在集群服务版本 (CSV) 的owned CRD 部分中具有resources子部分。如果测试检测到资源部分中未列出的已用资源,它会在测试结束时的建议中列出它们。用户需要在初始代码生成后填写资源部分才能通过此测试。

olm-crds-have-resources-test

具有描述符的 Spec 字段

此测试验证 CR 的spec部分中的每个字段在 CSV 中是否都有对应的描述符。

olm-spec-descriptors-test

具有描述符的状态字段

此测试验证 CR 的status部分中的每个字段在 CSV 中是否都有对应的描述符。

olm-status-descriptors-test

运行评分卡工具

运行init命令后,Operator SDK 会生成一组默认的 Kustomize 文件。生成的默认bundle/tests/scorecard/config.yaml文件可以立即用于针对您的操作符运行评分卡工具,或者您可以根据您的测试规范修改此文件。

先决条件
  • 使用 Operator SDK 生成的 Operator 项目

步骤
  1. 为您的 Operator 生成或重新生成捆绑包清单和元数据

    $ make bundle

    此命令会自动将评分卡注释添加到您的捆绑包元数据中,scorecard命令使用这些注释来运行测试。

  2. 针对操作符捆绑包在磁盘上的路径或捆绑包镜像的名称运行评分卡

    $ operator-sdk scorecard <bundle_dir_or_image>

评分卡输出

scorecard命令的--output标志指定评分卡结果输出格式:textjson

JSON 输出片段示例
{
  "apiVersion": "scorecard.operatorframework.io/v1alpha3",
  "kind": "TestList",
  "items": [
    {
      "kind": "Test",
      "apiVersion": "scorecard.operatorframework.io/v1alpha3",
      "spec": {
        "image": "quay.io/operator-framework/scorecard-test:v1.36.1",
        "entrypoint": [
          "scorecard-test",
          "olm-bundle-validation"
        ],
        "labels": {
          "suite": "olm",
          "test": "olm-bundle-validation-test"
        }
      },
      "status": {
        "results": [
          {
            "name": "olm-bundle-validation",
            "log": "time=\"2020-06-10T19:02:49Z\" level=debug msg=\"Found manifests directory\" name=bundle-test\ntime=\"2020-06-10T19:02:49Z\" level=debug msg=\"Found metadata directory\" name=bundle-test\ntime=\"2020-06-10T19:02:49Z\" level=debug msg=\"Getting mediaType info from manifests directory\" name=bundle-test\ntime=\"2020-06-10T19:02:49Z\" level=info msg=\"Found annotations file\" name=bundle-test\ntime=\"2020-06-10T19:02:49Z\" level=info msg=\"Could not find optional dependencies file\" name=bundle-test\n",
            "state": "pass"
          }
        ]
      }
    }
  ]
}
文本输出片段示例
--------------------------------------------------------------------------------
Image:      quay.io/operator-framework/scorecard-test:v1.36.1
Entrypoint: [scorecard-test olm-bundle-validation]
Labels:
	"suite":"olm"
	"test":"olm-bundle-validation-test"
Results:
	Name: olm-bundle-validation
	State: pass
	Log:
		time="2020-07-15T03:19:02Z" level=debug msg="Found manifests directory" name=bundle-test
		time="2020-07-15T03:19:02Z" level=debug msg="Found metadata directory" name=bundle-test
		time="2020-07-15T03:19:02Z" level=debug msg="Getting mediaType info from manifests directory" name=bundle-test
		time="2020-07-15T03:19:02Z" level=info msg="Found annotations file" name=bundle-test
		time="2020-07-15T03:19:02Z" level=info msg="Could not find optional dependencies file" name=bundle-test

输出格式规范与Test类型布局匹配。

选择测试

通过将--selector CLI 标志设置为一组标签字符串来选择评分卡测试。如果未提供选择器标志,则运行评分卡配置文件中的所有测试。

测试按顺序运行,测试结果由评分卡汇总并写入标准输出或stdout

步骤
  1. 例如,要选择单个测试basic-check-spec-test,请使用--selector标志指定测试。

    $ operator-sdk scorecard <bundle_dir_or_image> \
        -o text \
        --selector=test=basic-check-spec-test
  2. 例如,要选择一组测试olm,请指定所有 OLM 测试使用的标签。

    $ operator-sdk scorecard <bundle_dir_or_image> \
        -o text \
        --selector=suite=olm
  3. 要选择多个测试,请使用以下语法使用selector标志指定测试名称。

    $ operator-sdk scorecard <bundle_dir_or_image> \
        -o text \
        --selector='test in (basic-check-spec-test,olm-bundle-validation-test)'

启用并行测试

作为操作符作者,您可以使用评分卡配置文件为测试定义单独的阶段。阶段按在配置文件中定义的顺序依次运行。一个阶段包含一个测试列表和一个可配置的parallel设置。

默认情况下,或者当阶段明确将parallel设置为false时,阶段中的测试按在配置文件中定义的顺序依次运行。一次运行一个测试有助于保证没有两个测试相互交互和冲突。

但是,如果测试设计为完全隔离,则可以将其并行化。

步骤
  • 要并行运行一组隔离的测试,请将它们包含在同一个阶段中并将parallel设置为true

    apiVersion: scorecard.operatorframework.io/v1alpha3
    kind: Configuration
    metadata:
      name: config
    stages:
    - parallel: true (1)
      tests:
      - entrypoint:
        - scorecard-test
        - basic-check-spec
        image: quay.io/operator-framework/scorecard-test:v1.36.1
        labels:
          suite: basic
          test: basic-check-spec-test
      - entrypoint:
        - scorecard-test
        - olm-bundle-validation
        image: quay.io/operator-framework/scorecard-test:v1.36.1
        labels:
          suite: olm
          test: olm-bundle-validation-test
    1 启用并行测试

    并行阶段中的所有测试将同时执行,评分卡将等待所有测试完成,然后再继续下一个阶段。这可以使您的测试运行速度更快。

自定义评分卡测试

评分卡工具可以运行遵循以下强制约定的自定义测试:

  • 测试在容器镜像中实现

  • 测试接受包含命令和参数的入口点

  • 测试以 JSON 格式生成v1alpha3评分卡输出,测试输出中没有多余的日志

  • 测试可以在/bundle的共享挂载点获取捆绑包内容

  • 测试可以使用集群内客户端连接访问 Kubernetes API

如果测试镜像遵循上述指南,则可以使用其他编程语言编写自定义测试。

以下示例显示了用 Go 编写的自定义测试镜像。

自定义评分卡测试示例
// Copyright 2020 The Operator-SDK 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://apache.ac.cn/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 main

import (
	"encoding/json"
	"fmt"
	"log"
	"os"

	scapiv1alpha3 "github.com/operator-framework/api/pkg/apis/scorecard/v1alpha3"
	apimanifests "github.com/operator-framework/api/pkg/manifests"
)

// This is the custom scorecard test example binary
// As with the Redhat scorecard test image, the bundle that is under
// test is expected to be mounted so that tests can inspect the
// bundle contents as part of their test implementations.
// The actual test is to be run is named and that name is passed
// as an argument to this binary.  This argument mechanism allows
// this binary to run various tests all from within a single
// test image.

const PodBundleRoot = "/bundle"

func main() {
	entrypoint := os.Args[1:]
	if len(entrypoint) == 0 {
		log.Fatal("Test name argument is required")
	}

	// Read the pod's untar'd bundle from a well-known path.
	cfg, err := apimanifests.GetBundleFromDir(PodBundleRoot)
	if err != nil {
		log.Fatal(err.Error())
	}

	var result scapiv1alpha3.TestStatus

	// Names of the custom tests which would be passed in the
	// `operator-sdk` command.
	switch entrypoint[0] {
	case CustomTest1Name:
		result = CustomTest1(cfg)
	case CustomTest2Name:
		result = CustomTest2(cfg)
	default:
		result = printValidTests()
	}

	// Convert scapiv1alpha3.TestResult to json.
	prettyJSON, err := json.MarshalIndent(result, "", "    ")
	if err != nil {
		log.Fatal("Failed to generate json", err)
	}
	fmt.Printf("%s\n", string(prettyJSON))

}

// printValidTests will print out full list of test names to give a hint to the end user on what the valid tests are.
func printValidTests() scapiv1alpha3.TestStatus {
	result := scapiv1alpha3.TestResult{}
	result.State = scapiv1alpha3.FailState
	result.Errors = make([]string, 0)
	result.Suggestions = make([]string, 0)

	str := fmt.Sprintf("Valid tests for this image include: %s %s",
		CustomTest1Name,
		CustomTest2Name)
	result.Errors = append(result.Errors, str)
	return scapiv1alpha3.TestStatus{
		Results: []scapiv1alpha3.TestResult{result},
	}
}

const (
	CustomTest1Name = "customtest1"
	CustomTest2Name = "customtest2"
)

// Define any operator specific custom tests here.
// CustomTest1 and CustomTest2 are example test functions. Relevant operator specific
// test logic is to be implemented in similarly.

func CustomTest1(bundle *apimanifests.Bundle) scapiv1alpha3.TestStatus {
	r := scapiv1alpha3.TestResult{}
	r.Name = CustomTest1Name
	r.State = scapiv1alpha3.PassState
	r.Errors = make([]string, 0)
	r.Suggestions = make([]string, 0)
	almExamples := bundle.CSV.GetAnnotations()["alm-examples"]
	if almExamples == "" {
		fmt.Println("no alm-examples in the bundle CSV")
	}

	return wrapResult(r)
}

func CustomTest2(bundle *apimanifests.Bundle) scapiv1alpha3.TestStatus {
	r := scapiv1alpha3.TestResult{}
	r.Name = CustomTest2Name
	r.State = scapiv1alpha3.PassState
	r.Errors = make([]string, 0)
	r.Suggestions = make([]string, 0)
	almExamples := bundle.CSV.GetAnnotations()["alm-examples"]
	if almExamples == "" {
		fmt.Println("no alm-examples in the bundle CSV")
	}
	return wrapResult(r)
}

func wrapResult(r scapiv1alpha3.TestResult) scapiv1alpha3.TestStatus {
	return scapiv1alpha3.TestStatus{
		Results: []scapiv1alpha3.TestResult{r},
	}
}