5.10. スコアカードツールを使用した Operator の検証

Operator の作成者は、Operator SDK でスコアカードツールを使用して以下のタスクを実行できます。

  • Operator プロジェクトに構文エラーがなく、正しくパッケージ化されていることを確認します。
  • Operator を強化する方法についての提案を確認します。

5.10.1. スコアカードツールについて

Operator SDK bundle validate サブコマンドは、コンテンツおよび構造のローカルバンドルディレクトリーおよびリモートバンドルイメージを検証することができますが、scorecard コマンドを使用して設定ファイルおよびテストイメージに基づいて Operator でテストを実行できます。これらのテストは、スコアカードによって実行されるよう設定され、ビルドされるテストイメージ内に実装されます。

スコアカードは、OpenShift Container Platform などの設定済みの Kubernetes クラスターへのアクセスと共に実行されることを前提とします。スコアカードは Pod 内で各テストを実行します。これにより Pod ログが集計され、テスト結果はコンソールに送信されます。スコアカードにはビルトインの基本的なテストおよび Operator Lifecycle Manager (OLM) テストがあり、カスタムテスト定義を実行する手段も提供します。

スコアカードのワークフロー

  1. 関連するカスタムリソース (CR) および Operator に必要なすべてのリソースを作成する
  2. プロキシーコンテナーを Operator のデプロイメントに作成し、API サーバーへの呼び出しを記録してテストを実行する
  3. CR のパラメーターを検査する

スコアカードテストは、テスト中の Operator の状態を想定しません。Operator の作成および Operator の CR の作成は、スコアカード自体では扱っていません。ただし、スコアカードテストは、テストがリソース作成用に設計されている場合は、必要なリソースをなんでも作成できます。

scorecard コマンド構文

$ operator-sdk scorecard <bundle_dir_or_image> [flags]

スコアカードには、Operator バンドルへのディスク上のパスまたはバンドルイメージの名前のいずれかの位置引数が必要です。

フラグの詳細については、以下を実行します。

$ operator-sdk scorecard -h

5.10.2. スコアカードの設定

スコアカードツールでは、内部プラグインの設定を可能にする設定と、複数のグローバル設定オプションを使用します。テストは、config.yaml という名前の設定ファイルによって実行されます。これは、bundle/ ディレクトリーにある make 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.25.4
    entrypoint:
    - scorecard-test
    - basic-check-spec
    labels:
      suite: basic
      test: basic-check-spec-test
  - image: quay.io/operator-framework/scorecard-test:v1.25.4
    entrypoint:
    - scorecard-test
    - olm-bundle-validation
    labels:
      suite: olm
      test: olm-bundle-validation-test

設定ファイルは、スコアカードが実行可能な各テストを定義します。スコアカード設定ファイルの以下のフィールドは、以下のようにテストを定義します。

設定フィールド説明

image

テストを実装するコンテナーイメージ名のテスト

entrypoint

テストを実行するために、テストイメージで呼び出されるコマンドおよび引数

labels

実行するテストを選択するスコアカードで定義されたラベルまたはカスタムラベル

5.10.3. ビルトインスコアカードのテスト

スコアカードには、スイート (基本的なテストスイートおよび Operator Lifecycle Manager (OLM) スイート) に編成される事前に定義されたテストが同梱されます。

表5.16 基本的なテストスイート

テスト説明短縮名

Spec Block Exists

このテストは、クラスターで作成されたカスタムリソース (CR) をチェックし、すべての CR に spec ブロックがあることを確認します。

basic-check-spec-test

表5.17 OLM テストスイート

テスト説明短縮名

Bundle Validation

このテストは、スコアカードに渡されるバンドルにあるバンドルマニフェストを検証します。バンドルの内容にエラーが含まれる場合、テスト結果の出力には検証ログと検証ライブラリーからのエラーメッセージが含まれます。

olm-bundle-validation-test

Provided APIs Have Validation

このテストは、提供された CR のカスタムリソース定義 (CRD) に検証セクションが含まれ、CR で検出される各 spec および status フィールドの検証があることを確認します。

olm-crds-have-validation-test

Owned CRDs Have Resources Listed

このテストでは、cr-manifest オプションが提供する各 CR の CRD に、ClusterServiceVersion (CSV) の owned CRD セクションの resources サブセクションがあることを確認します。テストでリソースセクションにリスト表示されていない使用済みのリソースを検出する場合、テストの最後にある提案にそれらのリソースをリスト表示します。このテストが合格となるには、初回のコード生成後に、resources セクションを記入する必要があります。

olm-crds-have-resources-test

Spec Fields With Descriptors

このテストは、CR の spec セクションのすべてのフィールドに、CSV にリスト表示される対応する記述子があることを確認します。

olm-spec-descriptors-test

Status Fields With Descriptors

このテストは、CR の status セクションのすべてのフィールドに、CSV にリスト表示される対応する記述子があることを確認します。

olm-status-descriptors-test

5.10.4. スコアカードツールの実行

Kustomize ファイルのデフォルトセットは、init コマンドの実行後に Operator SDK によって生成されます。生成されるデフォルトの bundle/tests/scorecard/config.yaml ファイルは、Operator に対してスコアカードツールを実行するためにすぐに使用できます。または、このファイルをテスト仕様に変更することができます。

前提条件

  • Operator プロジェクトが Operator SDK を使用して生成されていること。

手順

  1. Operator のバンドルマニフェストおよびメタデータを生成または再生成します。

    $ make bundle

    このコマンドは、テストを実行するために scorecard コマンドが使用するバンドルメタデータに、スコアカードアノテーションを自動的に追加します。

  2. Operator バンドルへのディスク上のパスまたはバンドルイメージの名前に対してスコアカードを実行します。

    $ operator-sdk scorecard <bundle_dir_or_image>

5.10.5. スコアカードの出力

scorecard コマンドの --output フラグは、スコアカード結果の出力形式 (text または json) を指定します。

例5.15 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.25.4",
        "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"
          }
        ]
      }
    }
  ]
}

例5.16 テキスト出力スニペットの例

--------------------------------------------------------------------------------
Image:      quay.io/operator-framework/scorecard-test:v1.25.4
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 タイプのレイアウトに一致します。

5.10.6. テストの選択

スコアカードテストは、--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)'

5.10.7. 並列テストの有効化

Operator の作成者は、スコアカード設定ファイルを使用して、テスト用の個別のステージを定義できます。ステージは、設定ファイルで定義されている順序で順次実行します。ステージには、テストのリストと設定可能な parallel 設定が含まれます。

デフォルトで、またはステージが明示的に parallelfalse に設定する場合は、ステージのテストは、設定ファイルで定義されている順序で順次実行されます。テストを一度に 1 つずつ実行することは、2 つのテストが対話したり、互いに競合したりしないことを保証する際に役立ちます。

ただし、テストが完全に分離されるように設計されている場合は、並列化することができます。

手順

  • 分離されたテストのセットを並行して実行するには、これらを同じステージに追加して、paralleltrue に設定します。

    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.25.4
        labels:
          suite: basic
          test: basic-check-spec-test
      - entrypoint:
        - scorecard-test
        - olm-bundle-validation
        image: quay.io/operator-framework/scorecard-test:v1.25.4
        labels:
          suite: olm
          test: olm-bundle-validation-test
    1
    並列テストを有効にします。

    並列ステージのすべてのテストは同時に実行され、スコアカードはすべてが完了するのを待ってから次のステージへ進みます。これにより、非常に迅速にテストが実行されます。

5.10.8. カスタムスコアカードのテスト

スコアカードツールは、以下の義務付けられた規則に従うカスタムテストを実行できます。

  • テストはコンテナーイメージ内に実装されます。
  • テストは、コマンドおよび引数を含むエントリーポイントを受け入れます。
  • テストは、テスト出力に不要なロギングがない JSON 形式で、v1alpha3 スコアカード出力を生成します。
  • テストは、/bundle の共有マウントポイントでバンドルコンテンツを取得できます。
  • テストは、クラスター内のクライアント接続を使用して Kubernetes API にアクセスできます。

テストイメージが上記のガイドラインに従う場合は、他のプログラミング言語でカスタムテストを作成することができます。

以下の例は、Go で書かれたカスタムテストイメージを示しています。

例5.17 カスタムスコアカードテストの例

// 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://www.apache.org/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},
	}
}