github.com/joelanford/operator-sdk@v0.8.2/internal/pkg/scorecard/basic_tests.go (about) 1 // Copyright 2019 The Operator-SDK Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package scorecard 16 17 import ( 18 "context" 19 "encoding/json" 20 "fmt" 21 "strings" 22 23 v1 "k8s.io/api/core/v1" 24 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 25 "k8s.io/apimachinery/pkg/types" 26 "sigs.k8s.io/controller-runtime/pkg/client" 27 ) 28 29 // BasicTestConfig contains all variables required by the BasicTest TestSuite 30 type BasicTestConfig struct { 31 Client client.Client 32 CR *unstructured.Unstructured 33 ProxyPod *v1.Pod 34 } 35 36 // Test Defintions 37 38 // CheckSpecTest is a scorecard test that verifies that the CR has a spec block 39 type CheckSpecTest struct { 40 TestInfo 41 BasicTestConfig 42 } 43 44 // NewCheckSpecTest returns a new CheckSpecTest object 45 func NewCheckSpecTest(conf BasicTestConfig) *CheckSpecTest { 46 return &CheckSpecTest{ 47 BasicTestConfig: conf, 48 TestInfo: TestInfo{ 49 Name: "Spec Block Exists", 50 Description: "Custom Resource has a Spec Block", 51 Cumulative: false, 52 }, 53 } 54 } 55 56 // CheckStatusTest is a scorecard test that verifies that the CR has a status block 57 type CheckStatusTest struct { 58 TestInfo 59 BasicTestConfig 60 } 61 62 // NewCheckStatusTest returns a new CheckStatusTest object 63 func NewCheckStatusTest(conf BasicTestConfig) *CheckStatusTest { 64 return &CheckStatusTest{ 65 BasicTestConfig: conf, 66 TestInfo: TestInfo{ 67 Name: "Status Block Exists", 68 Description: "Custom Resource has a Status Block", 69 Cumulative: false, 70 }, 71 } 72 } 73 74 // WritingIntoCRsHasEffectTest is a scorecard test that verifies that the operator is making PUT and/or POST requests to the API server 75 type WritingIntoCRsHasEffectTest struct { 76 TestInfo 77 BasicTestConfig 78 } 79 80 // NewWritingIntoCRsHasEffectTest returns a new WritingIntoCRsHasEffectTest object 81 func NewWritingIntoCRsHasEffectTest(conf BasicTestConfig) *WritingIntoCRsHasEffectTest { 82 return &WritingIntoCRsHasEffectTest{ 83 BasicTestConfig: conf, 84 TestInfo: TestInfo{ 85 Name: "Writing into CRs has an effect", 86 Description: "A CR sends PUT/POST requests to the API server to modify resources in response to spec block changes", 87 Cumulative: false, 88 }, 89 } 90 } 91 92 // NewBasicTestSuite returns a new TestSuite object containing basic, functional operator tests 93 func NewBasicTestSuite(conf BasicTestConfig) *TestSuite { 94 ts := NewTestSuite( 95 "Basic Tests", 96 "Test suite that runs basic, functional operator tests", 97 ) 98 ts.AddTest(NewCheckSpecTest(conf), 1.5) 99 ts.AddTest(NewCheckStatusTest(conf), 1) 100 ts.AddTest(NewWritingIntoCRsHasEffectTest(conf), 1) 101 102 return ts 103 } 104 105 // Test Implementations 106 107 // Run - implements Test interface 108 func (t *CheckSpecTest) Run(ctx context.Context) *TestResult { 109 res := &TestResult{Test: t, MaximumPoints: 1} 110 err := t.Client.Get(ctx, types.NamespacedName{Namespace: t.CR.GetNamespace(), Name: t.CR.GetName()}, t.CR) 111 if err != nil { 112 res.Errors = append(res.Errors, fmt.Errorf("error getting custom resource: %v", err)) 113 return res 114 } 115 if t.CR.Object["spec"] != nil { 116 res.EarnedPoints++ 117 } 118 if res.EarnedPoints != 1 { 119 res.Suggestions = append(res.Suggestions, "Add a 'spec' field to your Custom Resource") 120 } 121 return res 122 } 123 124 // Run - implements Test interface 125 func (t *CheckStatusTest) Run(ctx context.Context) *TestResult { 126 res := &TestResult{Test: t, MaximumPoints: 1} 127 err := t.Client.Get(ctx, types.NamespacedName{Namespace: t.CR.GetNamespace(), Name: t.CR.GetName()}, t.CR) 128 if err != nil { 129 res.Errors = append(res.Errors, fmt.Errorf("error getting custom resource: %v", err)) 130 return res 131 } 132 if t.CR.Object["status"] != nil { 133 res.EarnedPoints++ 134 } 135 if res.EarnedPoints != 1 { 136 res.Suggestions = append(res.Suggestions, "Add a 'status' field to your Custom Resource") 137 } 138 return res 139 } 140 141 // Run - implements Test interface 142 func (t *WritingIntoCRsHasEffectTest) Run(ctx context.Context) *TestResult { 143 res := &TestResult{Test: t, MaximumPoints: 1} 144 logs, err := getProxyLogs(t.ProxyPod) 145 if err != nil { 146 res.Errors = append(res.Errors, fmt.Errorf("error getting proxy logs: %v", err)) 147 return res 148 } 149 msgMap := make(map[string]interface{}) 150 for _, msg := range strings.Split(logs, "\n") { 151 if err := json.Unmarshal([]byte(msg), &msgMap); err != nil { 152 continue 153 } 154 method, ok := msgMap["method"].(string) 155 if !ok { 156 continue 157 } 158 if method == "PUT" || method == "POST" { 159 res.EarnedPoints = 1 160 break 161 } 162 } 163 if res.EarnedPoints != 1 { 164 res.Suggestions = append(res.Suggestions, "The operator should write into objects to update state. No PUT or POST requests from the operator were recorded by the scorecard.") 165 } 166 return res 167 }