×

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

  • 验证您的Operator项目是否没有语法错误并正确打包

  • 查看有关改进Operator方法的建议

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

不建议使用Red Hat支持的Operator SDK版本创建新的Operator项目。拥有现有Operator项目的Operator作者可以使用Red Hat OpenShift Service on AWS发布的Operator SDK CLI工具版本来维护其项目并创建针对较新版本的Red Hat OpenShift Service on AWS的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集群(例如Red Hat OpenShift Service on AWS)的情况下运行的。scorecard在pod内运行每个测试,从中聚合pod日志并将测试结果发送到控制台。scorecard具有内置的基本测试和Operator Lifecycle Manager (OLM)测试,并且还提供了一种执行自定义测试定义的方法。

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

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

  3. 检查CR中的参数

scorecard测试不对被测试的Operator的状态做任何假设。创建Operators和CRs超出了scorecard本身的范围。但是,如果测试设计用于资源创建,则scorecard测试可以创建它们所需的任何资源。

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

scorecard需要一个位置参数,用于指定Operator bundle的磁盘路径或bundle镜像的名称。

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

$ operator-sdk scorecard -h

Scorecard配置

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

./bundle
...
└── tests
    └── scorecard
        └── config.yaml
Scorecard配置文件示例
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

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

配置字段 描述

image

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

entrypoint

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

labels

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

内置Scorecard测试

scorecard附带预定义的测试,这些测试被安排到套件中:基本测试套件和Operator Lifecycle Manager (OLM)套件。

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

Spec Block Exists

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

basic-check-spec-test

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

Bundle Validation

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

olm-bundle-validation-test

Provided APIs Have Validation

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

olm-crds-have-validation-test

已拥有 CRD 资源列表

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

olm-crds-have-resources-test

带有描述符的 Spec 字段

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

olm-spec-descriptors-test

带有描述符的 Status 字段

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

olm-status-descriptors-test

运行 scorecard 工具

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

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

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

    $ make bundle

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

  2. 针对 Operator bundle 的本地磁盘路径或 bundle 镜像名称运行 scorecard

    $ operator-sdk scorecard <bundle_dir_or_image>

Scorecard 输出

scorecard命令的--output标志指定 scorecard 结果的输出格式: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类型布局匹配。

选择测试

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

测试按顺序运行,测试结果由 scorecard 聚合并写入标准输出或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)'

启用并行测试

作为 Operator 作者,您可以使用 scorecard 配置文件为测试定义单独的阶段。阶段按其在配置文件中定义的顺序依次运行。一个阶段包含一个测试列表和一个可配置的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 启用并行测试

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

自定义 scorecard 测试

scorecard 工具可以运行遵循这些强制约定 的自定义测试。

  • 测试在容器镜像中实现。

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

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

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

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

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

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

自定义 scorecard 测试示例
// 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},
	}
}