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.