Configuring and using rules for an MTA analysis
Create custom rules to enhance migration coverage.
Abstract
Making open source more inclusive
Red Hat is committed to replacing problematic language in our code, documentation, and web properties. We are beginning with these four terms: master, slave, blacklist, and whitelist. Because of the enormity of this endeavor, these changes will be implemented gradually over several upcoming releases. For more details, see our CTO Chris Wright’s message.
Chapter 1. Introduction to rules
This guide is intended for software engineers who want to create custom YAML-based rules for migration toolkit for applications (MTA) tools.
See the Introduction to the migration toolkit for applications for an overview and the CLI Guide for details.
1.1. The MTA rules
The migration toolkit for applications (MTA) contains rule-based migration tools called analyzers that use pluggable providers to analyze static code, dependencies, and other files in your application.
Analyzers work with providers to detect issues that cause problems when you migrate the application to target technologies. The rule definition contains metadata description and condition patterns that describe a violation.
The analyzer runs a search query on the source code to match the violation described in the rule definition. The rule definition also contains a message that MTA displays when triggering an issue because of the violation. The analyzer uses default and user-provided (custom) rules during an analysis to identify issues.
An MTA Architect can create custom rules. You can use custom rules to identify the use of custom libraries or other components in the source code that might not be covered by the standard migration rules.
A collection of one or more rules is called a ruleset. Creating rulesets provides a way of organizing multiple rules that analyze the source code for a specific target platform.
You can use rules or rulesets as input arguments in an analysis. You can perform a rule-based analysis in the MTA CLI, user interface, or by using one of the IDE plug-ins for MTA (Visual Studio Code or IntelliJ).
The rules can be used by Red Hat Developer Lightspeed for MTA to generate AI-assisted code resolutions. The issue description and metadata in rules are used by the Red Hat Developer Lightspeed for MTA to form well-defined contexts. These contexts generate AI-assisted suggestions to resolve issues in the code that are identified through an analysis. See Configuring and Using Red Hat Developer Lightspeed for MTA for more information.
1.2. YAML rule structure and syntax
Rules instruct the analyzer to take the specified actions when the given conditions match. In MTA, a rule file contains one or more rules.
Rules are written in YAML. They consist of:
- metadata
- conditions
- actions
MTA analyzer rules have the following pattern:
ruleID: labels: category: effort: description: when: <condition(s)> message: "message" tag: - tag_1, tag_2, tag_3 - key_1="value_1, value_2"
Chapter 2. Rule metadata
The rule metadata contains information about the migration defined by the Architect. You can use rule metadata to:
- Estimate the total migration effort
- Prioritize issues that must be resolved based on the effort
- Evaluate if a resolution is mandatory through rule category before migrating the applications to the target technologies or platforms
- Define labels for which MTA filters rules to trigger a violation.
2.1. Rule metadata structure
Rule metadata contains a unique rule ID, labels, effort, and category.
ruleID: "unique_id" labels: # key=value pair - "label1=val1" # valid label with value omitted - "label2" # valid label with empty value - "label3=" # subdomain prefixed key - "konveyor.io/label1=val1" effort: 1 category: mandatory
where:
- ruleID
- This is a unique ID for the rule. It must be unique within the ruleset.
- labels
- A list of string labels associated with the rule. (See Labels).
- effort
- Effort is an integer value that indicates the level of effort needed to resolve this issue.
- category
-
Category describes severity of the issue for migration. Values can be one of
mandatory,potentialoroptional. For more details, see Rule categories.
2.2. Rule labels
Labels are key=val pairs specified in rules to filter the rule by the label. During an analysis, you can use the --label-selector option to apply the filter for rules or rulesets and the --dep-label-selector option to apply the filter for dependencies.
For dependencies, a provider adds the labels to the dependencies when retrieving them. Labels on a ruleset are automatically inherited by all the rules that belong to it.
Label format
Labels are specified under the labels field as a list of strings in key=val format as follows:
labels: - "key1=val1" - "key2=val2"
The key of a label can be subdomain-prefixed:
labels: - "konveyor.io/key1=val1"
The value of a label can be empty:
labels: - "konveyor.io/key="
The value of a label can be omitted. In that case, it is treated as an empty value:
labels: - "konveyor.io/key"
Reserved labels
The analyzer defines some labels that have special meaning as follows:
-
konveyor.io/source: Identifies the source technology to which a rule or a ruleset applies. -
konveyor.io/target: Identifies the target technology to which a rule or a ruleset applies. -
konveyor.io/include: Overrides filter behavior for a rule irrespective of the label selector used. The value can either bealwaysornever. If you specifyalways, the analyzer always filters-in this rule, while fornever, the analyzer excludes this rule.
Label selector
The analyzer CLI takes the --label-selector field as an option. It is a string expression that supports logical AND, OR, and NOT operations. You can use it to filter-in or filter-out rules by their labels.
Examples:
To filter-in all rules that have a label with the key
konveyor.io/sourceand valueeap6:--label-selector="konveyor.io/source=eap6"To filter-in all rules that have a label with the key
konveyor.io/sourceand any value:--label-selector="konveyor.io/source"To perform logical AND operations on matches of multiple rules by using the
&&operator:--label-selector="key1=val1 && key2"To perform logical OR operations on matches of multiple rules by using the
||operator:--label-selector="key1=val1 || key2"To perform a NOT operation to filter-out rules that have
key1=val1label set by using the!operator:--label-selector="!key1=val1"To group sub-expressions and control precedence by using AND:
--label-selector="(key1=val1 || key2=val2) && !val3"
Dependency labels
The analyzer engine adds labels to dependencies. These labels provide additional information about a dependency, such as its programming language and whether the dependency is open source or internal.
Currently, the analyzer adds the following labels to dependencies:
labels: - konveyor.io/dep-source=internal - konveyor.io/language=java
Dependency label selector
The analyzer CLI accepts the --dep-label-selector option, which allows filtering-in or filtering-out incidents generated from a dependency by their labels.
For example, the analyzer adds a konveyor.io/dep-source label to dependencies with a value that indicates whether the dependency is a known open source dependency.
To exclude incidents for all such open source dependencies, you can use --dep-label-selector as follows:
$ mta-cli … --dep-label-selector !konveyor.io/dep-source=open-source
The Java provider in the analyzer can also add an exclude label to a list of packages. To exclude all such packages, you can use --dep-label-selector and the ! operator as follows:
konveyor-analyzer … --dep-label-selector !konveyor.io/exclude
mta-cli … --dep-label-selector !konveyor.io/exclude
2.3. Rule categories
Rule categories are applied per rule. The Architect assigns one of the following rule categories to indicate the risk associated with not resolving an issue.
-
mandatory: You must resolve the issue for a successful migration. If you do not make the changes, the resulting application will not build or run successfully. Examples include the replacement of proprietary APIs that are not supported in the target platform. -
optional: If you do not resolve the issue, the application should work, but the results might not be optimal. If you do not make the change at the time of migration, it is recommended to include it on the schedule soon after your migration is completed. -
potential: You need to examine the issue during the migration process, but there is not enough detailed information to determine if the task is mandatory for the migration to succeed. An example of such an issue is migrating a third-party proprietary type when there is no directly compatible type on the target platform.
Chapter 3. Providers and rule conditions
The providers are the modular components in charge of analyzing a given language. Providers are able to analyze code by leveraging the Language Server Protocol (LSP). Through the LSP, all code analysis is abstracted away from the analysis engine and left to the specific LSP server to run the search query defined in the rule on the source code.
Additionally, MTA provides a built-in provider with abilities such as XML parsing, running regular expressions on files, and so on.
Currently, MTA supports the following providers:
- Builtin
- Java
- Go
-
External providers (for
Python,DotnetandNode.jsapplications) initialized by the generic provider binary
You can use the generic provider binary to create an external provider for any language that is compliant with LSP 3.17 specifications.
Using the provider capability in custom rules
In a rule, the when block is where the conditions for matching the rule are specified. Each provider offers a series of capabilities to do matching.. The search query in the rule condition can contain patterns, code locations, specific dependencies to be found, etc, to evaluate the source code and dependencies. The provider sends the LSP server a request to check the search query against the application being analyzed. When the LSP server returns a match for the search in the source code, the analyzer triggers a violation.
The syntax for the when block is as follows: contains one condition, but that condition can have multiple conditions nested under it.
when:
<condition>
<nested-condition>3.1. Provider condition
The analyzer engine enables multi-language source code analysis by using providers. The source code of a technology is analyzed by the provider.
The provider publishes what they can do with the source code in terms of capabilities.
The provider condition instructs the analyzer to use a specific provider and one of its capabilities. In general, it follows the <provider_name>.<capability> pattern.
when:
<provider_name>.<capability>
<input_fields>
The analyzer currently supports the following provider conditions:
-
builtin -
java -
go -
node.js -
python -
dotnet
Dotnet provider is a Developer Preview feature only. Developer Preview features are not supported by Red Hat in any way and are not functionally complete or production-ready. Do not use Developer Preview features for production or business-critical workloads. Developer Preview features provide early access to upcoming product features in advance of their possible inclusion in a Red Hat product offering, enabling customers to test functionality and provide feedback during the development process. These features might not have any documentation, are subject to change or removal at any time, and testing is limited. Red Hat might provide ways to submit feedback on Developer Preview features without an associated SLA.
| Provider rule conditions | Provider name |
|---|---|
|
Providers that are fully supported and included in the product |
Java |
|
Providers that have rules already defined in the product |
.NET |
|
Providers that require custom rulesets for analysis |
|
The following table summarizes all the providers and their capabilities:
Table 3.1. Summary of providers and their capabilities
| Provider Name | Capabilities | Description |
|---|---|---|
|
|
referenced |
Find references of a pattern with an optional code location for detailed searches. For example, when:
java.referenced:
<fields>
|
|
dependency |
Check whether the application has a given dependency. For example, when:
java.dependency:
<fields>
| |
|
|
xml |
Search XML files using xpath queries. |
|
|
Search JSON files using when:
builtin.json:
<fields>
| |
|
filecontent |
Search content in regular files using regular expression patterns. For example, when:
builtin.filecontent:
<fields>
| |
|
file |
Find files with names matching a given pattern. For example, when:
builtin.file:
<fields>
| |
|
hasTags |
Check whether a tag is created for the application using a tagging rule. For example, when:
builtin.hasTags:
<fields>
| |
|
|
referenced |
Find references to a pattern. For example, when:
go.referenced:
<fields>
|
|
dependency |
Check whether the application has a given dependency. For example, when:
go.dependency:
<fields>
|
Following the example in the previous table, you can create the first part of the condition that does not contain any of the condition fields.
Example
To create a java provider condition that uses the referenced capability:
when:
java.referenced:
<fields>
Depending on the provider and the capability, there will be different <fields> in the condition.
The following table summarizes available providers, their capabilities and all of their fields:
Table 3.2. Summary of providers, their capabilities, and their fields
| Provider | Capability | Fields | Required | Description |
|---|---|---|---|---|
|
referenced |
pattern |
Yes |
Regular expression pattern. For example, when:
java.referenced:
location: PACKAGE
pattern: org.jboss*
| |
|
location |
No |
Source code location. See Java locations. For example, when:
java.referenced:
pattern: org.kubernetes*
location: IMPORT
| ||
|
annotated |
No |
Additional query to inspect annotations. See Annotation inspection. For example, when:
java.referenced:
location: ANNOTATION
pattern: javax.ejb.Singleton
| ||
|
dependency |
name |
Yes |
Name of the dependency. For example, when:
java.dependency:
name: junit.junit
| |
|
nameregex |
No |
Regular expression pattern to match the name. | ||
|
upperbound |
No |
Match versions lower than or equal to. For example, when:
java.dependency:
name: junit.junit
upperbound: 4.12.2
| ||
|
lowerbound |
No |
Match versions greater than or equal to. For example, when:
java.dependency:
name: junit.junit
upperbound: 4.12.2
lowerbound: 4.4.0
| ||
|
xml |
xpath |
Yes |
Xpath query when:
builtin.xml:
xpath: "//dependencies/dependency"
| |
|
namespaces |
No |
A map to scope down query to namespaces. when:
builtin.xml:
filepaths:
- beans.xml
namespaces:
b: http://xmlns.jcp.org/xml/ns/javaee
xpath: /b:beans
| ||
|
filepaths |
No |
Optional list of files to scope down search. when:
or:
- builtin.xml:
xpath: "//dependencies/dependency"
filepaths: "{{poms.filepaths}}"
from: poms
- builtin.file:
pattern: pom.xml
as: poms
ignore: true
| ||
|
json |
xpath |
Yes |
Xpath query For example, when:
and:
- builtin.json:
xpath: //inclusionTestNode
| |
|
filepaths |
No |
Optional list of files to scope down search. For example, when:
and:
- builtin.json:
xpath: //inclusionTestNode
filepaths: "{{incTest.filepaths}}"
| ||
|
filecontent |
pattern |
Yes |
Regular expression pattern to match in content. For example, when:
builtin.filecontent:
pattern: "import.*React"
| |
|
filePattern |
No |
Only search in files with names matching this pattern. For example, when:
builtin.filecontent:
pattern: "import.*React"
filePattern: "\\.tsx$"
| ||
|
file |
pattern |
Yes |
Find files with names matching this pattern. For example, when:
builtin.file:
pattern: "*.go"
| |
|
hasTags |
This is an inline list of string tags. See Tag action For example, when:
or:
- builtin.hasTags:
- Golang
- Kubernetes
| |||
|
referenced |
pattern |
Yes |
Regular expression pattern. For example, when:
go.referenced:
pattern: "v1beta1.CustomResourceDefinition"
| |
|
dependency |
name |
Yes |
Name of the dependency. For example, when:
- go.dependency:
name: sigs.k8s.io/structured-merge-diff/v4
| |
|
nameregex |
No |
Regular expression pattern to match the name. | ||
|
upperbound |
No |
Match versions lower than or equal to. For example, when:
- go.dependency:
name: sigs.k8s.io/structured-merge-diff/v4
upperbound: v4.2.2
| ||
|
lowerbound |
No |
Match versions greater than or equal to. For example, when:
- go.dependency:
name: sigs.k8s.io/structured-merge-diff/v4
lowerbound: v4.2.0
| ||
|
referenced |
pattern |
Yes |
Regular expression to match a reference in the source code. For example, | |
|
namespace |
Yes |
Specify the namespace within which the search query must be run. For example, |
3.2. Builtin provider
The builtin is an internal provider that can analyze various files and internal metadata generated by the engine. This provider has the following capabilities:
-
file -
filecontent -
xml -
json -
hasTags
file
By using the file capability, the provider searches for files in the source code that match a given pattern.
when:
builtin.file:
pattern: "<regular_expression_to_match_filenames>"filecontent
By using the filecontent capability, the provider searches for content that matches a given pattern.
when:
builtin.filecontent:
filePattern: "<regular_expression_to_match_filenames_to_scope_search>"
pattern: "<regular_expression_to_match_content_in_the_matching_files>"xml
The xml capability enables the provider to query XPath expressions on a list of provided XML files. This capability takes 2 input parameters, xpath and filepaths.
when:
builtin.xml:
xpath: "<xpath_expressions>"
filepaths:
- "/src/file1.xml"
- "/src/file2.xml"where:
xpath- must be a valid XPath expression.
filepaths- is a list of files to apply the XPath query to.
json
By using the json capability, the provider queries XPath expressions on a list of provided JSON files. Currently, json only takes XPath as input and performs the search on all JSON files in the codebase.
when:
builtin.json:
xpath: "<xpath_expressions>"where:
xpath- must be a valid XPath expression.
hasTags
By using the hasTags capability, the provider queries application tags. It queries the internal data structure to check whether the application has the given tags.
when:
# when more than one tag is given, a logical AND is implied
hasTags:
- "tag1"
- "tag2"where:
hasTags- When more than one tag is given, a logical AND is implied.
3.3. Java provider
The java provider analyzes Java source code.
This provider has the following capabilities:
-
referenced -
dependency
referenced
By using the referenced capability, the provider finds references in the source code. This capability takes three input parameters: pattern, location, and annotated.
when:
java.referenced:
pattern: "<pattern>"
location: "<location>"
annotated: "<annotated>"where:
pattern- A regular expression pattern to match.
location-
Specifies the exact location where the pattern needs to be matched, for example,
IMPORT. annotated-
Checks for specific annotations and their elements, such as name and value, in the Java code using a query. For example, the following query matches the
Bean(url = “http://www.example.com”) annotation in the method. +
annotated:
pattern: org.framework.Bean
elements:
- name: url
value: "http://www.example.com"
See Java condition and capabilities for a detailed explanation on java.referenced capabilities.
dependency
The Java provider has dependency capability. The dependency capability enables the provider to generate a list of dependencies for a given application. You can use a dependency condition to query this list and check whether a certain dependency, with a version range, exists for the application. The dependency can be internal or external/open source. For example, to check if a Java application has a certain dependency, you create a java.dependency condition:
when:
java.dependency:
name: junit.junit
upperbound: 4.12.2
lowerbound: 4.4.0
You can use the dependency capability to check if a Java application has Fabric8 Kubernetes client of version 5.0.100 or lower:
- java.dependency:
name: io.fabric8.kubernetes-client
lowerbound: 5.0.1003.4. Go provider
The go provider analyzes Go source code. This provider’s capabilities are referenced and dependency.
referenced
By using the referenced capability, the provider finds references in the source code.
when: go.referenced: "<regex_to_find_reference>"
dependency
By using the dependency capability, the provider finds dependencies for a Go application.
when:
go.dependency:
name: "<dependency_name>"
upperbound: "<version_string>"
lowerbound: "<version_string>"where:
name- Name of the dependency to search for.
upperbound- Upper bound on the version of the dependency.
lowerbound- Lower bound on the version of the dependency.
3.5. Dotnet provider
The dotnet provider is an external provider used to analyze .NET and C# source code. Currently, the provider supports the referenced capability.
referenced
By using the referenced capability, the provider finds references in the source code.
when:
dotnet.referenced:
pattern: "<pattern>"
namespace: "<namespace>"where:
pattern-
A regular expression pattern to match the desired reference. For example,
HttpNotFound. namespace-
Specifies the namespace to search within. For example,
System.Web.Mvc.
Chapter 4. Java condition and capabilities
The java.referenced capability in the when condition for Java rules can define search queries for the following fields in the source code:
- Locations - such as the classes, methods, packages and so on
- Annotations
- Patterns
You can refer to this section for a detailed explanation on the Locations, Annotations, and Patterns.
4.1. Java locations
The java provider allows scoping the search down to certain source code locations.
- IMPORT: IMPORT allows for searches on class imports. It can either be used with Fully Qualified Names (FQNs) or an asterisk to allow for wider matches:
java.referenced: pattern: org.apache.lucene.search* location: IMPORT
would match on each of these imports:
import org.apache.lucene.search.Query; import org.apache.lucene.search.Sort; import org.apache.lucene.search.SortField;
If you want to match using an asterisk (*) for a wider range of results, it is recommended to place it directly after the package, not after the dot:
INCORRECT: org.apache.lucene.search.* CORRECT: org.apache.lucene.search*
-
PACKAGE: the
PACKAGElocation matches on any usage of a package, be it in an import or used as part of an FQN in the code:
java.referenced: pattern: org.apache.lucene.search* location: PACKAGE
would match on each of the following import and the FQN usage:
import org.apache.lucene.search.Query; import org.apache.lucene.search.Sort; import org.apache.lucene.search.SortField;
public class Test {
private org.apache.lucene.search.Query query;
}- CONSTRUCTOR_CALL and METHOD_CALL: for matching constructors and methods, respectively. The pattern possibilities are quite varied, and it is possible to match against specific return types, arguments, etc.
For instance, looking for a method named method declared on org.konveyor.MyClass that returns a List of a type that extends java.lang.String and accepts a single parameter:
java.referenced: location: METHOD pattern: 'org.konveyor.Myclass.method(*) java.util.List<? extends java.lang.String>'
More information about the possibilities of these patterns can be found in the official Java documentaion, which contain all the information for building these patterns in the createPattern(String, int, int, int) section.
Presently, fully qualified static method matching is prone to errors.
Table 4.1. Supported Java locations
| Location | Description | Rule condition example |
|---|---|---|
|
TYPE |
Matches against all types including classes, interfaces, enum, and annotation types that appear anywhere. |
The following example looks for a class that implements when: java.referenced: location: TYPE pattern: java.util.List |
|
ANNOTATION |
Matches against annotations. |
The following example matches the when:
java.referenced:
location: ANNOTATION
pattern: org.framework.Bean
annotated:
elements:
- name: url
value: "http://www.example.com"
|
|
IMPLEMENTS_TYPE |
Matches against any type implementing the given type. |
The following example looks for a class that implements when: java.referenced: location: IMPLEMENTS_TYPE pattern: java.util.ArrayList |
|
RETURN_TYPE |
Matches against a type being returned by a method. |
The following example searches for when:
java.referenced:
location: RETURN_TYPE
pattern: javax.persistence.EntityManager
annotated:
pattern: javax.enterprise.inject.Produces
|
|
VARIABLE_DECLARATION |
Matches against a type being declared as a variable |
The following example searches for when:
- java.referenced:
location: VARIABLE_DECLARATION
pattern: javax.jms.QueueConnectionFactory
|
|
FIELD |
Matches against a type appearing in a field declaration. It can be coupled with an annotation match happening on the field. See Annotation inspection |
The following example searches for when:
- java.referenced:
location: FIELD_DECLARATION
pattern: javax.jms.QueueConnectionFactory
|
|
METHOD |
Matches against a given method declaration. It can be coupled with an annotation match. See Annotation inspection |
The following example locates all methods that return when: java.referenced: location: METHOD pattern: '* java.lang.String' |
|
METHOD_CALL |
Matches against the method call in the source code. |
The following example matches the when:
java.referenced:
location: METHOD_CALL
pattern: java.sql.DriverManager.getConnection(java.lang.String, java.lang.String, java.lang.String)
|
|
CLASS (declaration) |
Matches against a given method declaration. Can be coupled with an annotation match. See Annotation inspection. |
The following example locates all classes under the when: java.referenced: pattern: javax.xml* |
|
CONSTRUCTOR_CALL |
Matches against constructor calls in the source code. |
The following example matches the when:
java.referenced:
location: CONSTRUCTOR_CALL
pattern: java.io.FileOutputStream(java.lang.String, boolean)
|
|
IMPORT |
Matches against class imports. You can use it with FQNs or an asterisk (*) to allow for wider matches. |
The following example matches with the import of when: java.referenced: pattern: org.apache.lucene.search* location: IMPORT |
|
PACKAGE |
Matches on any usage of a package, be it in an import or used as part of a fully qualified name in the code. |
The following example matches all occurrences of when:
java.referenced:
location: PACKAGE
pattern: org.jboss*
|
|
INHERITANCE |
Matches against a class inheriting from a given type. |
The following example searches for interfaces that extend the when:
java.referenced:
pattern: 'org.jboss.system.ServiceMBeanSupport'
location: INHERITANCE
|
4.2. Annotation inspection
You can add a query to match against specific annotations and their elements, for example:
when:
java.referenced:
location: METHOD
pattern: org.package.MyApplication.runApplication(java.lang.String)
annotated:
pattern: org.framework.Bean
elements:
- name: url
value: "http://www.example.com"
This would match against the runApplication method in the following Java code:
package org.package
import org.framework.Bean;
class MyApplication {
@Bean(url = "http://www.example.com")
public String runApplication(String str) {
// ...
}
}
The structure of the annotated YAML element is:
annotated:
pattern: a Java regular expression to match the fully qualified name of the annotation (optional)
elements: an array of elements to match within the annotation (optional)
- name: the exact name of the element to match against
value: a Java regular expression to match the value of the element
It is also possible to match an annotation with specific elements, without having to specify the symbol it annotates. The following example would also match on the @Bean annotation in the same code as the previous example:
when:
java.referenced:
location: ANNOTATION
pattern: org.framework.Bean
annotated:
elements:
- name: url
value: "http://www.example.com"
The only element specified with a pattern is the annotation itself.
4.3. Condition patterns
The Language Server used by the Java provider is Eclipse’s JDTLS. Internally, the JDTLS uses the Eclipse Java Development Toolkit, which includes utilities for searching code in projects.
In the pattern element of a java.referenced condition, you can search through application code by using these utilities. For more details, see Class SearchPattern, which contains all the information for building these patterns for createPattern(String, int, int, int).
Examples
Search for any class under the
javax.xmlpackage, occurring in any location:java.referenced: pattern: javax.xml*
WarningWhen matching against packages, as in the previous example, the asterisk must not be after a dot. For example: *
pattern: javax.xml*and not: *pattern: javax.xml.*Search for method declarations that return
java.lang.String:java.referenced: location: METHOD pattern: '* java.lang.String'
Search for a method named “method” declared on
org.konveyor.MyClassthat returns aListof a type that extendsjava.lang.String:java.referenced: location: METHOD pattern: 'org.konveyor.Myclass.method(*) java.util.List<? extends java.lang.String>'
Search for a class that implements
java.util.List:java.referenced: location: IMPLEMENTS_TYPE pattern: java.util.List
Chapter 5. Logical conditions, condition chaining, and custom variables
You can create complex conditions in rules by using the following:
- Logical conditions inform the provider how to handle more than one condition in a when block. You can aggregate or filter conditions using logical operations.
- Condition chaining uses the output of one condition as the input in another condition of the when block. Assign the output to a variable in one condition and reuse it in other conditions in the when block.
- Nested conditions to create conditions that depend on the evaluation of other conditions.
- Custom variables to capture specific information from the code that is evaluated by a rule. You can use the custom variable in the message displayed for the code if it contains a violation defined by the rule.
5.1. Logical conditions
The analyzer provides two basic logical conditions, and and or, which you can use to aggregate results of other conditions.
5.2. AND condition
The and condition performs a logical AND operation on the results of an array of conditions.
The and condition matches when all of its child conditions match, for example:
when:
and:
- <condition1>
- <condition2>Example
when:
and:
- java.dependency:
name: junit.junit
upperbound: 4.12.2
lowerbound: 4.4.0
- java.referenced:
location: IMPORT
pattern: junit.junit
5.3. OR condition
The or condition performs a logical OR operation on the results of an array of conditions.
The or condition matches when any of its child conditions matches, for example:
when:
or:
- <condition1>
- <condition2>Example
when:
or:
- java.dependency:
name: junit.junit
upperbound: 4.12.2
lowerbound: 4.4.0
- java.referenced:
location: IMPORT
pattern: junit.junit
5.4. Chaining Condition Variables
You can use the output of one condition as the input for filtering another one in the and and or conditions. This is called condition chaining.
Example
when:
or:
- builtin.xml:
xpath: "//dependencies/dependency"
filepaths: "{{poms.filepaths}}"
from: poms
- builtin.file:
pattern: pom.xml
as: poms
ignore: true
In the above example, the output of the builtin.file condition is saved as poms:
+
[...]
as: poms
[...]
The variables of builtin.file can then be used in the builtin.xml condition, by writing from and then using mustache templates in the provider_ condition block.
This is how this particular condition knows how to use the variable set to the name poms.
+
[...]
from: poms
[...]Then you can use the variables by setting them as mustached templates in any of the inputs to the provider condition.
+
[...]
filepaths: "{{poms.filepaths}}"
[...]
If you only want to use the values of a condition as a chain, you can set ignore: true.
This will tell the engine not to use this condition to determine whether the rule has been violated or not:
+
[...]
ignore: true
[...]5.5. Chaining in the Java provider
In the java provider, the filepaths variable must be uppercased. for example:
when:
and:
- java.referenced:
pattern: org.springframework.web.bind.annotation.RequestMapping
location: ANNOTATION
as: annotation
- java.referenced:
pattern: org.springframework.stereotype.Controller
location: ANNOTATION
filepaths: "{{annotation.Filepaths}}"5.6. Nested conditions
Conditions can also be nested within other conditions.
when:
and:
- and:
- go.referenced: "*CustomResourceDefinition*"
- java.referenced:
pattern: "*CustomResourceDefinition*"
- go.referenced: "*CustomResourceDefinition*"5.7. Custom variables
Provider conditions can have associated custom variables. You can use custom variables to capture relevant information from the matched line in the source code. The values of these variables are interpolated with data matched in the source code. These values can be used to generate detailed template messages in a rule’s action (see Message actions). They can be added to a rule in the customVariables field:
- ruleID: lang-ref-004
customVariables:
- pattern: '([A-z]+)\.get\(\)'
name: VariableName
message: "Found generic call - {{ VariableName }}"
when:
java.referenced:
location: METHOD_CALL
pattern: com.example.apps.GenericClass.getwhere:
pattern- A regular expression pattern that is matched on the source code line when a match is found.
name- The name of the variable that can be used in templates.
message- A template for a message using a custom variable.
Chapter 6. Rule actions
You can use rule actions to generate information about the source code. The rule action information can be included in a violation or used to categorize the source code.
Rules can include the following types of actions:
-
Message: use message action to display an informative message in the static report that contains the violation triggered by the rule. See Message action for more information. -
Tag: use tags to categorize parts of the source code. For example, Backend=Java, where Backend is the key and Java is the value. See Tag actions for more information. Each rule includes either one of them or both of them.
6.1. Tag actions
A tag action instructs the analyzer to generate one or more tags for the application when a match is found. Each string in the tag field can be a comma-separated list of tags. Optionally, you can assign categories to tags.
tag: - "tag1,tag2,tag3" - "Category=tag4,tag5"
Example
- ruleID: test-rule
when:
<CONDITION>
tag:
- Language=Golang
- Env=production
- Source Code
A tag can be a string or a key=val pair, where the key is treated as a tag category in MTA. Any rule that has a tag action is referred to as a “tagging rule” in this document.
Issues are not created for rules that contain only tag actions.
6.2. Message action
A message action is used to generate an issue with the specified message when a rule condition matches the source code, for example:
- ruleID: test-rule
message: Test rule matched. Please resolve this migration issue.
when:
<CONDITION>Optionally, a message can include hyperlinks to external URLs that provide relevant information about the issue or a quick fix.
links:
- url: "konveyor.io"
title: "Short title for the link"You can also create a template message to include information about the match that has been interpolated through custom variables on the rule.
- ruleID: lang-ref-004
customVariables:
- pattern: '([A-z]+)\.get\(\)'
name: VariableName
message: "Found generic call - {{ VariableName }}"
when:
<CONDITION>6.3. Hyperlinks
Hyperlinks can be provided along with a message or tag action to provide relevant information about the issue, which has been discovered:
# links point to external hyperlinks # rule authors are expected to provide # relevant hyperlinks for quick fixes, docs and so on. links: - url: "konveyor.io" title: "short title for the link"
Chapter 7. Creating custom rules
You can refer to example rules that describe how to create a YAML rule. This assumes that you already have MTA installed. See the MTA CLI Guide for installation instructions.
7.1. Creating a YAML rule template
MTA YAML-based rules have the following basic structure:
when(condition) message(message) tag(tags)
Procedure
In the
/home/<USER>/directory, create a file containing the basic syntax for YAML rules as follows:- category: mandatory description: <DESCRIPTION TITLE> <DESCRIPTION TEXT> effort: <EFFORT> labels: - konveyor.io/source=<SOURCE_TECH> - konveyor.io/target=<TARGET_TECH> links: - url: <HYPERLINK> title: <HYPERLINK_TITLE> message: <MESSAGE> tag: - <TAG1> - <TAG2> ruleID: <RULE_ID> when: <CONDITIONS>
7.2. Creating a YAML rule
This section guides you through the process of creating and testing your first MTA YAML-based rule. This assumes that you have already installed MTA. See Installing and running the CLI in the CLI Guide for installation instructions.
In this example, you will create a rule to discover instances where an application defines a jboss-web.xml file containing a <class-loading> element and to provide a link to the documentation that describes how to migrate the code.
- Create a YAML file for your first rule.
$ mkdir /home/<USER>/rule.yaml
Create
jboss-web.xmlandpom.xmlfiles in a directory:mkdir /home/<USER>/data/ touch /home/<USER>/data/jboss-web.xml touch /home/<USER>/data/pom.xml
In the
jboss-web.xmlfile you created, paste the following content:<!DOCTYPE jboss-web PUBLIC "-//JBoss//DTD Web Application 4.2//EN" "http://www.jboss.org/j2ee/dtd/jboss-web_4_2.dtd"> <jboss-web> <class-loading java2ClassLoadingCompliance="false"> <loader-repository> seam.jboss.org:loader=@projectName@ <loader-repository-config>java2ParentDelegation=false</loader-repository-config> </loader-repository> </class-loading> </jboss-web>In the
pom.xmlfile you created, paste the following content:<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>test</groupId> <artifactId>test</artifactId> <version>1.1.0-SNAPSHOT</version> <properties> <maven.compiler.source>1.7</maven.compiler.source> <maven.compiler.target>1.7</maven.compiler.target> </properties> <dependencies> </dependencies> </project>
In the
rule.yamlfile you created, paste the following contents:- ruleID: <UNIQUE_RULE_ID> description: <DESCRIPTION> when: <CONDITION(S)> message: <MESSAGE> labels: <LABELS> effort: <EFFORT> links: - <LINKS>where:
ruleID-
Unique ID for your rule. For example,
jboss5-web-class-loading. description- Text description of the rule.
whenComplete the
whenblock specifying one or more conditions:Use the
builtinprovider’s XML capability because this rule checks for a match in an XML file.when: builtin.xml: xpath: jboss-web/class-loading
messageHelpful message explaining the migration issue. The message is generated in the report when the rule matches. For example:
[options="nowrap",subs="attributes+"] ---- message: The class-loading element is no longer valid in the jboss-web.xml file. ----
labels- List of string labels for the rule.
effort- Number of expected story points to fix this issue.
linksOne or more hyperlinks pointing to documentation around the migration issues that you find.+ [options="nowrap",subs="attributes+"] ---- links:
url: https://access.redhat.com/documentation/en-US/JBoss_Enterprise_Application_Platform/6.4/html-single/Migration_Guide/index.html#Create_or_Modify_Files_That_Control_Class_Loading_in_JBoss_Enterprise_Application_Platform_6 title: Create or Modify Files That Control Class Loading in JBoss EAP 6 ----
The rule is now complete and looks similar to the following:
- ruleID: jboss5-web-class-loading description: Find class loading element in JBoss XML file. when: builtin.xml: xpath: jboss-web/class-loading message: The class-loading element is no longer valid in the jboss-web.xml file. effort: 3 links: - url: https://access.redhat.com/documentation/en-US/JBoss_Enterprise_Application_Platform/6.4/html-single/Migration_Guide/index.html#Create_or_Modify_Files_That_Control_Class_Loading_in_JBoss_Enterprise_Application_Platform_6 title: Create or Modify Files That Control Class Loading in JBoss EAP 6
Point the CLI to the rule file you created :
–rules /home/<USER>/rules.yaml
To test the rule, point the input to the test data you created and pass the rule using the rules option in MTA CLI:
mta-cli analyze --input /home/<USER>/data/ --output /home/<USER>/output/ --rules /home/<USER>/rules.yaml
Review the report to be sure that it provides the expected results.
Once the analysis is complete, the command outputs the path to the HTML report:
INFO[0066] Static report created. Access it at this URL: URL="file:/home/<USER>/output/static-report/index.html"
Open
/home/<USER_NAME>/output/static-report/index.htmlin a web browser.- Navigate to the Issues tab in the left menu.
Verify that the rule is executed:
-
In the Issues table, type
JBoss XMLin the search bar. -
Verify that the issue with the title
Find class loading element in JBoss XML fileis present in the table.
-
In the Issues table, type
- Click the jboss-web.xml link to open the affected file.
7.3. Creating a custom Go rule
You can create custom rules for Golang (Go) applications based on the following example.
You can use the following custom rule to check if MTA triggers an incident when it detects the v1beta1.CustomResourceDefinition in the`go` file.
Procedure
-
Create a
go-rule-001.ymlfile in a directory. Copy the following rule in the
yamlfile:- message: golang apiextensions/v1/customresourcedefinitions found description: "golang apiextensions/v1/customresourcedefinitions found" ruleID: go-lang-ref-001 effort: 1 when: go.referenced: pattern: "v1beta1.CustomResourceDefinition"-
Create a test
gofile named example.go in yourHomedirectory. Paste the following code in the example.go file:
package main import ( "fmt" "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" ) func main() { fmt.Println(v1beta1.CustomResourceDefinition{}) }Run the following command in the MTA CLI:
$ ./mta-cli analyze -i _<path-to-Go-project>_ \ -o _<path-to-report-directory>_ --run-local=false --rules \ _<path-to-go-rule-001.yml>_
NoteAdd the
--overwriteoption if you want to use the same directory for the report when you run subsequent tests. MTA overwrites the current report with the result of the latest analysis that you run.-
Open the static report at
/home/<USER>/output/static-report/in your browser. -
Navigate to the issues to verify the
golang apiextensions/v1/customresourcedefinitions foundissue.
7.4. Creating a custom Node.js rule
You must create custom rules to analyze `Node.js` applications by using {ProductShortName}. A `Node.js` rule can contain `nodejs.referenced` capability which supports the `pattern` field.
The following example uses a custom rule to check if a .tsx file in the Node.js project imports the React framework.
Procedure
Create the
test-nodejsdirectory.$ mkdir -p ~/test-nodejs
Save the following rule as
nodejs-rule-001.ymlin thetest-nodejsdirectory:- ruleID: test-tsx-support-00000 description: Found React import in .tsx file message: Found React import in .tsx file effort: 1 when: nodejs.referenced: pattern: "React"Create the following test application in the
Component.tsxfile:import React from 'react'; export const MyComponent: React.FC = () => <div>Hello</div>;
Run the following command in the MTA CLI:
$ ./mta-cli analyze -i ~/test-nodejs/ -o \ ~/test-nodejs/report --run-local=false \ --rules ~/test-nodejs/nodejs-rule-001.yml
NoteAdd the
--overwriteoption if you want to use the same directory for the report when you run subsequent tests. MTA overwrites the current report with the result of the latest analysis that you run.-
Open the static report at
~/test-nodejs/report/static-report/index.htmlin your browser. - Click the <application_name> to open the Dashboard.
- Review the incidents in the Issues tab.
7.5. Creating custom Python rules
You must create custom rules to analyze Python applications by using MTA. A Python rule can contain python.referenced capability with the supported fields.
The following example uses two custom rules:
-
The first rule checks if
bad_methodis specified -
The second rule checks if
hello_worldis specified infile_a.pyin your project.
Procedure
Create the directory
test-python.$ mkdir -p ~/test-python
Create a
python-rule-001.ymlfile in the directory and add the following rule:- category: mandatory ruleID: python-rule-001 effort: 1 description: "Bad method" when: python.referenced: pattern: "bad_method"Create a
python-rule-002.ymlfile in the directory and add the following rule:- category: mandatory ruleID: python-rule-002 effort: 1 message: "Found a python" when: python.referenced: pattern: "hello_world"Save the following
Pythoncode asfile_b.py.import deprecated def hello_world(): return "Hello, world!" @deprecated.deprecated("This method is bad!") def bad_method(): return "I'm a bad method!"Save the following code as a second file,
file_a.py.import file_b print(file_b.hello_world()) print(file_b.bad_method())
Run the following command in the MTA CLI:
$ ./mta-cli analyze -i ~/test-python/ -o \ ~/test-python/report --run-local=false \ --rules ~/test-python/python-rule-001. \ --rules ~/test-python/python-rule-002.yml
NoteAdd the
--overwriteoption if you want to use the same directory for the report when you run subsequent tests. MTA overwrites the current report with the result of the latest analysis that you run.-
Open the static report at
~/test-python/report/static-report/index.htmlin your browser. - Click the <application_name> to open the Dashboard.
- Review the incidents in the Issues tab.
Chapter 8. Rulesets
A set of rules forms a ruleset. MTA does not require every rule file to belong to a ruleset, but you can use rulesets to group multiple rules that achieve a common goal and to pass the rules to the rules engine.
To use multiple rule files, you need to place them in a directory and to add a ruleset.yaml file. Then the directory is treated as a ruleset, and you can pass it as input to the --rules option. Note that if you want to use the --target or --source option in the CLI, the engine will only select rules that match the label for that target. Therefore, make sure that you have added target or source labels on your rules. See Reserved labels for more details.
8.1. Creating and using a custom ruleset
You can create a ruleset by placing one or more custom rules in a directory and creating a ruleset.yaml file at the directory root. When you perform an analysis, you pass this directory as input to the MTA CLI by using the --rules option. All rules in this directory are treated as a part of the ruleset defined by the ruleset.yaml file.
The ruleset.yaml file stores the metadata of the ruleset. You can group multiple similar custom rules and create a ruleset for them. When you pass this directory as input to the MTA CLI using the --rules option, MTA treats all the files in the directory as belonging to the ruleset defined in the ruleset.yaml file.
name: "Name of the ruleset" description: "Description of the ruleset" labels: - key=val
where:
name- The name must be unique within the provided rulesets.
labels- Ruleset labels are inherited by all rules that belong to the ruleset.
To perform any application analysis, enter:
$ mta-cli analyze --input=<application_to_analyze> --output=<output_dir> --rules=<custom_rule_dir> --enable-default-rulesets=false
-
Replace
<application_to_analyze>with the name of your application. -
Replace
<output_dir>with the directory of your choice. -
Replace
<custom_rule_dir>with the custom rule file. To use multiple rule files, you need to place them in a directory and to add a
ruleset.yamlfile. Then the directory is treated as a ruleset, and you can pass it as input to the--rulesoption.Note that if you want to use the
--targetor--sourceoption in the CLI, the engine will only select rules that match the label for that target. Therefore, make sure that you have added target or source labels on your rules. See Reserved labels for more details.
Chapter 9. Additional resources
- MTA Jira issue tracker: https://issues.redhat.com/projects/MTA/issues
- MTA mailing list: windup-eng@redhat.com