istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/config/analysis/README.md (about) 1 # Analyzers 2 3 The purpose of analyzers is to examine Istio configuration for potential problems that should be surfaced back to the user. An analyzer takes as input a Context object that contains methods to inspect the configuration snapshot being analyzed, as well as methods for reporting any issues discovered. 4 5 ## Writing Analyzers 6 7 ### 1. Create the code 8 9 Analyzers need to implement the Analyzer interface ( in the `galley/pkg/config/analysis` package). They should be created under the analyzers subdirectory, and given their own appropriate subpackage. 10 11 An annotated example: 12 13 ```go 14 package virtualservice 15 16 // <imports here> 17 18 type GatewayAnalyzer struct{} 19 20 // Compile-time check that this Analyzer correctly implements the interface 21 var _ analysis.Analyzer = &GatewayAnalyzer{} 22 23 // Metadata implements Analyzer 24 func (s *GatewayAnalyzer) Metadata() analysis.Metadata { 25 return analysis.Metadata{ 26 // Each analyzer should have a unique name. Use <top-level-pkg>.<struct type> 27 Name: "virtualservice.GatewayAnalyzer", 28 // Each analyzer should have a short, one line description of what they 29 // do. This description is shown when --list-analyzers is called via 30 // the command line. 31 Description: "Checks that VirtualService resources reference Gateways that exist", 32 // Each analyzer should register the collections that it needs to use as input. 33 Inputs: collection.Names{ 34 gvk.Gateway, 35 gvk.VirtualService, 36 }, 37 } 38 } 39 40 // Analyze implements Analyzer 41 func (s *GatewayAnalyzer) Analyze(c analysis.Context) { 42 // The context object has several functions that let you access the configuration resources 43 // in the current snapshot. The available collections, and how they map to k8s resources, 44 // are defined in galley/pkg/config/schema/metadata.yaml 45 // Available resources are listed under the "localAnalysis" snapshot in that file. 46 c.ForEach(gvk.VirtualService, func(r *resource.Instance) bool { 47 s.analyzeVirtualService(r, c) 48 return true 49 }) 50 } 51 52 func (s *GatewayAnalyzer) analyzeVirtualService(r *resource.Instance, c analysis.Context) { 53 // The actual resource entry, represented as a protobuf message, can be obtained via 54 // the Item property of resource.Instance. It will need to be cast to the appropriate type. 55 // 56 // Since the resource.Instance also contains important metadata not included in the protobuf 57 // message (such as the resource namespace/name) it's often useful to not do this casting 58 // too early. 59 vs := r.Item.(*v1alpha3.VirtualService) 60 61 // The resource name includes the namespace, if one exists. It should generally be safe to 62 // assume that the namespace is not blank, except for cluster-scoped resources. 63 for i, gwName := range vs.Gateways { 64 if !c.Exists(collections.Gateway, resource.NewName(r.Metadata.FullName.Namespace, gwName)) { 65 // Messages are defined in galley/pkg/config/analysis/msg/messages.yaml 66 // From there, code is generated for each message type, including a constructor function 67 // that you can use to create a new validation message of each type. 68 msg := msg.NewReferencedResourceNotFound(r, "gateway", gwName) 69 70 // Field map contains the path of the field as the key, and its line number as the value was stored in the resource. 71 // 72 // From the util package, find the correct path template of the field that needs to be analyzed, and 73 // by giving the required parameters, the exact error line number can be found for the message for final displaying. 74 // 75 // If the path template does not exist, you can add the template in util/find_errorline_utils.go for future use. 76 // If this exact line feature is not applied, or the message does not have a specific field like SchemaValidationError, 77 // then the starting line number of the resource will be displayed instead. 78 if line, ok := util.ErrorLine(r, fmt.Sprintf(util.VSGateway, i)); ok { 79 msg.Line = line 80 } 81 82 // Messages are reported via the passed-in context object. 83 c.Report(gvk.VirtualService, msg) 84 } 85 } 86 } 87 ``` 88 89 If you are writing a multi-cluster analyzer, you can obtain the cluster name from the resource metadata: 90 91 ```go 92 clusterID := r.Origin.ClusterName() 93 ``` 94 95 ### 2. Add the Analyzer to All() 96 97 In order to be run, analyzers need to be registered in the [analyzers.All()](https://github.com/istio/istio/blob/master/pkg/config/analysis/analyzers/all.go) function. Add your analyzer as an entry there. 98 99 ### 3. Create new message types 100 101 If your analyzer requires any new message types (meaning a unique template and error code), you will need to do the following: 102 103 1. Add an entry to [galley/pkg/config/analysis/msg/messages.yaml](https://github.com/istio/istio/blob/master/pkg/config/analysis/msg/messages.yaml) e.g. 104 105 ```yaml 106 - name: "SandwichNotFound" 107 code: IST0199 108 level: Error 109 description: "This resource requires a sandwich to function." 110 template: "This resource requires a fresh %s on %s sandwich in order to run." 111 args: 112 - name: fillings 113 type: string 114 - name: bread 115 type: string 116 ``` 117 118 1. Run `BUILD_WITH_CONTAINER=1 make gen`: 119 120 1. Use the new type in your analyzer 121 122 ```go 123 msg := msg.NewSandwichNotFound(resourceEntry, "ham", "rye") 124 ``` 125 126 Also note: 127 128 * Messages can have different levels (Error, Warning, Info). 129 * The code range 0000-0100 is reserved for internal and/or future use. 130 * Please keep entries in `messages.yaml` ordered by code. 131 132 ### 4. Add path templates 133 134 If your analyzer requires to display the exact error line number, but the path template is not available, you should 135 add the template in [galley/pkg/config/analysis/analyzers/util/find_errorline_utils.go](https://github.com/istio/istio/blob/master/pkg/config/analysis/analyzers/util/find_errorline_utils.go) 136 137 e.g for the GatewayAnalyzer used as an example above, you would add something like this to `find_errorline_utils.go`: 138 139 ```go 140 // Path for VirtualService gateway. 141 // Required parameters: gateway index. 142 VSGateway = "{.spec.gateways[%d]}" 143 ``` 144 145 ### 5. Adding unit tests 146 147 For each new analyzer, you should add an entry to the test grid in 148 [analyzers_test.go](https://github.com/istio/istio/blob/master/pkg/config/analysis/analyzers/analyzers_test.go). 149 If you want to add additional unit testing beyond this, you can, but analyzers are required to be covered in this test grid. 150 151 e.g. for the GatewayAnalyzer used as an example above, you would add something like this to `analyzers_test.go`: 152 153 ```go 154 { 155 // Unique name for this test case 156 name: "virtualServiceGateways", 157 // List of input YAML files to load as resources before running analysis 158 inputFiles: []string{ 159 "testdata/virtualservice_gateways.yaml", 160 }, 161 // A single specific analyzer to run 162 analyzer: &virtualservice.GatewayAnalyzer{}, 163 // List of expected validation messages, as (messageType, <kind> <name>[.<namespace>]) tuples 164 expected: []message{ 165 {msg.ReferencedResourceNotFound, "VirtualService httpbin-bogus"}, 166 }, 167 }, 168 ``` 169 170 `virtualservice_gateways.yaml`: 171 172 ```yaml 173 apiVersion: networking.istio.io/v1alpha3 174 kind: Gateway 175 metadata: 176 name: httpbin-gateway 177 spec: 178 selector: 179 istio: ingressgateway 180 --- 181 apiVersion: networking.istio.io/v1alpha3 182 kind: VirtualService 183 metadata: 184 name: httpbin 185 spec: 186 hosts: 187 - "*" 188 gateways: 189 - httpbin-gateway # Expected: no validation error since this gateway exists 190 --- 191 apiVersion: networking.istio.io/v1alpha3 192 kind: VirtualService 193 metadata: 194 name: httpbin-bogus 195 spec: 196 hosts: 197 - "*" 198 gateways: 199 - httpbin-gateway-bogus # Expected: validation error since this gateway does not exist 200 ``` 201 202 You should include both positive and negative test cases in the input YAML: we want to verify both that bad entries 203 generate messages and that good entries do not. 204 205 Since the analysis is tightly scoped and the YAML isn't checked for completeness, it's OK to partially specify the 206 resources in YAML to keep the test cases as simple and legible as possible. 207 208 Note that this test framework will also verify that the resources requested in testing match the resources listed as 209 inputs in the analyzer metadata. This should help you find any unused inputs and/or missing test cases. 210 211 ### 6. Testing via istioctl 212 213 You can use `istioctl analyze` to run all analyzers, including your new one. e.g. 214 215 ```sh 216 make istioctl && $GOPATH/out/linux_amd64/release/istioctl analyze 217 ``` 218 219 ### 7. Write a user-facing documentation page 220 221 Each analysis message needs to be documented for customers. This is done by introducing a markdown file for 222 each message in the [istio.io](https://github.com/istio/istio.io) repo in the [content/en/docs/reference/config/analysis](https://github.com/istio/istio.io/tree/master/content/en/docs/reference/config/analysis) directory. You create 223 a subdirectory with the code of the error message, and add a `index.md` file that contains the 224 full description of the problem with potential remediation steps, examples, etc. See the existing 225 files in that directory for examples of how this is done. 226 227 ## FAQ 228 229 ### What if I need a resource not available as a collection? 230 231 Please open an issue (directed at the "Configuration" product area) or visit the 232 [\#config channel on Slack](https://istio.slack.com/messages/C7KSV4AHJ) to discuss it.