5.9. スコアカードツールを使用した Operator の検証
Operator の作成者は、Operator SDK でスコアカードツールを使用して以下のタスクを実行できます。
- Operator プロジェクトに構文エラーがなく、正しくパッケージ化されていることを確認します。
- Operator を強化する方法についての提案を確認します。
5.9.1. スコアカードツールについて
Operator SDK bundle validate サブコマンドは、コンテンツおよび構造のローカルバンドルディレクトリーおよびリモートバンドルイメージを検証することができますが、scorecard コマンドを使用して設定ファイルおよびテストイメージに基づいて Operator でテストを実行できます。これらのテストは、スコアカードによって実行されるよう設定され、ビルドされるテストイメージ内に実装されます。
スコアカードは、OpenShift Container Platform などの設定済みの Kubernetes クラスターへのアクセスと共に実行されることを前提とします。スコアカードは Pod 内で各テストを実行します。これにより Pod ログが集計され、テスト結果はコンソールに送信されます。スコアカードにはビルトインの基本的なテストおよび Operator Lifecycle Manager (OLM) テストがあり、カスタムテスト定義を実行する手段も提供します。
スコアカードのワークフロー
- 関連するカスタムリソース (CR) および Operator に必要なすべてのリソースを作成する
- プロキシーコンテナーを Operator のデプロイメントに作成し、API サーバーへの呼び出しを記録してテストを実行する
- CR のパラメーターを検査する
スコアカードテストは、テスト中の Operator の状態を想定しません。Operator の作成および Operator の CR の作成は、スコアカード自体では扱っていません。ただし、スコアカードテストは、テストがリソース作成用に設計されている場合は、必要なリソースをなんでも作成できます。
scorecard コマンド構文
$ operator-sdk scorecard <bundle_dir_or_image> [flags]
スコアカードには、Operator バンドルへのディスク上のパスまたはバンドルイメージの名前のいずれかの位置引数が必要です。
フラグの詳細については、以下を実行します。
$ operator-sdk scorecard -h
5.9.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.8.0
entrypoint:
- scorecard-test
- basic-check-spec
labels:
suite: basic
test: basic-check-spec-test
- image: quay.io/operator-framework/scorecard-test:v1.8.0
entrypoint:
- scorecard-test
- olm-bundle-validation
labels:
suite: olm
test: olm-bundle-validation-test
設定ファイルは、スコアカードが実行可能な各テストを定義します。スコアカード設定ファイルの以下のフィールドは、以下のようにテストを定義します。
| 設定フィールド | 説明 |
|---|---|
|
| テストを実装するコンテナーイメージ名のテスト |
|
| テストを実行するために、テストイメージで呼び出されるコマンドおよび引数 |
|
| 実行するテストを選択するスコアカードで定義されたラベルまたはカスタムラベル |
5.9.3. ビルトインスコアカードのテスト
スコアカードには、スイート (基本的なテストスイートおよび Operator Lifecycle Manager (OLM) スイート) に編成される事前に定義されたテストが同梱されます。
表5.16 基本的なテストスイート
| テスト | 説明 | 短縮名 |
|---|---|---|
| Spec Block Exists |
このテストは、クラスターで作成されたカスタムリソース (CR) をチェックし、すべての CR に |
|
表5.17 OLM テストスイート
| テスト | 説明 | 短縮名 |
|---|---|---|
| Bundle Validation | このテストは、スコアカードに渡されるバンドルにあるバンドルマニフェストを検証します。バンドルの内容にエラーが含まれる場合、テスト結果の出力には検証ログと検証ライブラリーからのエラーメッセージが含まれます。 |
|
| Provided APIs Have Validation |
このテストは、提供された CR のカスタムリソース定義 (CRD) に検証セクションが含まれ、CR で検出される各 |
|
| Owned CRDs Have Resources Listed |
このテストでは、 |
|
| Spec Fields With Descriptors |
このテストは、CR の |
|
| Status Fields With Descriptors |
このテストは、CR の |
|
5.9.4. スコアカードツールの実行
Kustomize ファイルのデフォルトセットは、init コマンドの実行後に Operator SDK によって生成されます。生成されるデフォルトの bundle/tests/scorecard/config.yaml ファイルは、Operator に対してスコアカードツールを実行するためにすぐに使用できます。または、このファイルをテスト仕様に変更することができます。
前提条件
- Operator プロジェクトが Operator SDK を使用して生成されていること。
手順
Operator のバンドルマニフェストおよびメタデータを生成または再生成します。
$ make bundle
このコマンドは、テストを実行するために
scorecardコマンドが使用するバンドルメタデータに、スコアカードアノテーションを自動的に追加します。Operator バンドルへのディスク上のパスまたはバンドルイメージの名前に対してスコアカードを実行します。
$ operator-sdk scorecard <bundle_dir_or_image>
5.9.5. スコアカードの出力
scorecard コマンドの --output フラグは、スコアカード結果の出力形式 (text または json) を指定します。
例5.29 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.8.0",
"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.30 テキスト出力スニペットの例
-------------------------------------------------------------------------------- Image: quay.io/operator-framework/scorecard-test:v1.8.0 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.9.6. テストの選択
スコアカードテストは、--selector CLI フラグをラベル文字列のセットに設定して選択されます。セレクターフラグが指定されていない場合は、スコアカード設定ファイル内のすべてのテストが実行されます。
テストは、テスト結果がスコアカードによって集計され、標準出力 (stdout) に書き込まれる形で連続的に実行されます。
手順
basic-check-spec-testなどの単一のテストを選択するには、--selectorフラグを使用してテストを指定します。$ operator-sdk scorecard <bundle_dir_or_image> \ -o text \ --selector=test=basic-check-spec-testテストのスイートを選択するには (例:
olm)、すべての OLM テストで使用されるラベルを指定します。$ operator-sdk scorecard <bundle_dir_or_image> \ -o text \ --selector=suite=olm複数のテストを選択するには、以下の構文を使用して
selectorフラグを使用し、テスト名を指定します。$ operator-sdk scorecard <bundle_dir_or_image> \ -o text \ --selector='test in (basic-check-spec-test,olm-bundle-validation-test)'
5.9.7. 並列テストの有効化
Operator の作成者は、スコアカード設定ファイルを使用して、テスト用の個別のステージを定義できます。ステージは、設定ファイルで定義されている順序で順次実行します。ステージには、テストの一覧と設定可能な parallel 設定が含まれます。
デフォルトで、またはステージが明示的に parallel を false に設定する場合は、ステージのテストは、設定ファイルで定義されている順序で順次実行されます。テストを一度に 1 つずつ実行することは、2 つのテストが対話したり、互いに競合したりしないことを保証する際に役立ちます。
ただし、テストが完全に分離されるように設計されている場合は、並列化することができます。
手順
分離されたテストのセットを並行して実行するには、これらを同じステージに追加して、
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.8.0 labels: suite: basic test: basic-check-spec-test - entrypoint: - scorecard-test - olm-bundle-validation image: quay.io/operator-framework/scorecard-test:v1.8.0 labels: suite: olm test: olm-bundle-validation-test- 1
- 並列テストを有効にします。
並列ステージのすべてのテストは同時に実行され、スコアカードはすべてが完了するのを待ってから次のステージへ進みます。これにより、非常に迅速にテストが実行されます。
5.9.8. カスタムスコアカードのテスト
スコアカードツールは、以下の義務付けられた規則に従うカスタムテストを実行できます。
- テストはコンテナーイメージ内に実装されます。
- テストは、コマンドおよび引数を含むエントリーポイントを受け入れます。
-
テストは、テスト出力に不要なロギングがない JSON 形式で、
v1alpha3スコアカード出力を生成します。 -
テストは、
/bundleの共有マウントポイントでバンドルコンテンツを取得できます。 - テストは、クラスター内のクライアント接続を使用して Kubernetes API にアクセスできます。
テストイメージが上記のガイドラインに従う場合は、他のプログラミング言語でカスタムテストを作成することができます。
以下の例は、Go で書かれたカスタムテストイメージを示しています。
例5.31 カスタムスコアカードテストの例
// 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},
}
}