Konveyor is an application modernization platform that helps to modernize and migrate applications to new technologies. This is done through the application of written rules: at its heart, Konveyor is a rules engine.
Konveyor comes packed by default with a set of pre-written rules for several different migration paths, but users are allowed and encouraged to write their own rules, which can be easily input before analysis. Ideally, users would contribute their own rules to the Konveyor Community, improving and expanding the number of available migration paths over time.
Kantra is a CLI wrapper for the rules engine that powers Konveyor. Kantra allows users to do quick static analysis without the need to install the full Konveyor platform in a Kubernetes environment. The result is a HTML report that mimics that of Konveyor.
- Installing kantra is easy, just go to the Kantra releases page and download a binary for your OS and architecture.
- Additionally, you will need a container platform installed in your system, either podman or docker.
- Kantra comes pre-loaded with several rulesets that provide different migration paths by default. These available paths
(also named targets in the Konveyor ecosystem) can be checked out by running
kantra rules list-targets. Specifying a migration path is desirable to have a targeted analysis. - Additionally, more rules can be specified with the
--rulesoption. - When analyzing an application, it is possible to also analyze the code of its dependencies too:
--modeallows choosing between analyzing only the source code (source-only) or dependencies too (full, the default)--analyze-known-librariestells the engine to also analyze dependencies that are open source, and therefore whose code is generally accessible. This option only makes sense when using--mode full.
- When analyzing Java code, both source code and binaries can be analyzed. This can be specified simply by the
--inputoption.
- Generate report to migrate source code application to EAP8 and OpenJDK21, save the report somewhere else:
kantra --input ~/Apps/my-application --target eap8 --target openjdk21 --output ~/Reports/my-application.report
- Generate report to analyze binary application for cloud readiness:
kantra --input ~/Apps/my-war-file.war --target cloud-readiness
- Generate report to migrate to Quarkus from EAP7, but do not analyze dependencies:
kantra --input ~/Apps/my-eap7-application --source eap7 --target quarkus --mode source-only
A rule in Konveyor is a formally written specification of a condition. If this condition happens to be true within the application being analyzed, it will raise an action (normally an issue) in the generated report, this is, a problem within the code that will need to be changed or fixed by a developer in order for the migration to succeed.
analysis -> condition is true -> issue
For instance, if we want to migrate an application from JakartaEE 8 to 9,
we will need to change all our namespaces from javax.* to jakarta.*. In this case, the condition would be:
if you find any usage of javax.*
and the action would be:
create an issue with a message telling the user to change from javax.* to jakarta.*
In the generated report, the user will find an issue, pointing to the specific part of the code where the condition was raised, telling exactly what needs to be modified.
Continuing with the previous example, that rule could be coded as follows:
# Metadata
ruleID: javax-to-jakarta-rule-00001
labels:
- konveyor.io/source
- konveyor.io/target=jakarta-ee9+
- javaee
# Condition
when:
java.referenced:
location: IMPORT
pattern: javax.*
# Action
description: The package 'javax' has been replaced by 'jakarta'.
message: Replace the `javax.*` import statement with `jakarta.*`
category:
links:
- title: Jakarta EE
url: https://jakarta.ee/There are three main parts that a rule consists of:
- Metadata: a set of fields for identification, categorization, etc.
- Rule ID: the ID of the rule, which must be unique
- Labels: a set of tags to categorize the rule in different ways, mostly used for filtering
- Condition (when): the condition that needs to be true for the rule to be triggered. There are different types of available conditions, and different language providers can have their own specific conditions unique to the language.
- Action: one or more fields that describe the actions to be taken if the rule is triggered:
- Description: a short title for the issue
- Message: a longer and more explanatory text describing the problem that needs solving
- Category: the type of issue, indicating whether the raised issue must be modified (mandatory), is optional (ie, for deprecated APIs), or potential (can't be decided)
- Effort: an approximate number of points indicating the effort needed to do the modification in the code
- Links: a set of links with more information about the issue
There are some additional fields that will be explained in the following sections.
Conditions are the most important part of a rule, since they indicate what needs to happen in the code for an issue to be raised. The different types of conditions are provided by the language providers. Since Konveyor can be extended to be able to analyze any language, and each language has its own quirks and specifications, each language provider can expose its own conditions (in the form of capabilities). In the case of the Java language provider, it exposes two types of conditions:
java.referenced: matches against code references, like a match against a method or an annotated fieldjava.dependency: matches against the existence of a given dependency of the application being analyzed
An exception to this is the builtin provider, which is a generic, built-in provider for creating conditions such as plain text regex matching or Xpath matching:
builtin.filecontent: matches a given regex on plaintextbuiltin.file: matches on a given filenamebuiltin.xml: matches on an Xpath expression
Conditions can be combined using the logical operators and, or and not:
when:
and:
- java.referenced:
location: ANNOTATION
pattern: org.konveyor.ExampleAnnotation
- or
- not: true
builtin.filecontent:
filePattern: ASpecificClass.java
pattern: some.*regex
- builtin.file:
pattern: "^.*\\.properties$"The best way to learn to write rules is to check the examples we have in the default ruleset. Additional documentation can also be found in the analyzer docs, with all available fields explained. Check the next section for a sample walkthrough of writing a rule, testing it and executing it.
To get a feel of the Kantra workflow, let's try this sample walkthrough:
- Install kantra:
- Go to Kantra releases and download for your arch and OS.
- Install podman or docker if not already installed.
- Clone the Coolstore app to a directory of your choice:
git clone git@github.com:konveyor-ecosystem/coolstore.git
- We are going to analyze the Coolstore app to perform a migration from Java EE to Quarkus. In order to do this,
run the following command:
kantra --input <path-to>/coolstore --target quarkus --target jakarta-ee9+ --target cloud-readiness --overwrite- The
--inputparameter indicates the path to the source code to analyze - The different
--targetparameters indicate the migration paths we want to check. Since we want to migrate to Quarkus, we might also need to migrate our app to the Jakarta namespaces. We also want to see if our app would be ready to be deployed to the cloud. - Lastly,
--overwritewill rewrite a previously generated report if it exists.
- The
- After running successfully, a link will appear to the generated report. Follow it and open
index.html. You should see a report like this.- In "Applications", you can find all the analyzed applications; in this case, since we have specified only one input, only one application appears: "coolstore".
- By clicking on the "Issues" link of the left hand panel, you can see all the issues raised by Konveyor.
- Let's filter them by target == "cloud-readiness" and check the one with title "File system - Java IO". Inside you can see all the source code files where the issue was found.
- Let's create a custom rule specifically for this application. Let's say we want to detect all
ProductServices declared as a field annotated with the@Injectannotation. For that, we will need a condition that looks like this:
# The rule ID. Must be unique.
- ruleID: coolstore-rule-00001
# The category. This indicates if the change MUST be done (mandatory), CAN be done (optional),
# or if it can't be decided (potential).
category: mandatory
# An approximate calculation of the effort it would take to fix the issue in the code
effort: 1
# A set of labels, including source and target technologies and tags
labels:
- konveyor.io/source=java-ee
- konveyor.io/source=jakarta-ee
- konveyor.io/target=quarkus
- quarkus
# The triggering condition:
when:
# This will be a java condition
java.referenced:
# Match on the ProductService class...
pattern: com.redhat.coolstore.service.ProductService
# ...when it's used in a field declaration...
location: FIELD
# ...and also when it's annotated with @Inject.
annotated:
pattern: javax.inject.Inject
# A short description of the issue.
description: Do not use ProductService with Inject
# A more descriptive and long explanation of the issue, potentially with code snippets and examples of solving.
message: "ProductService cannot be used with the @Inject annotation in version 2 of the coolstore application"
# An array of links with more information about the issue
links:
- title: 'Add some link here'
url: https://www.example.com- Now save this file in a folder as
rule.yaml, and add another file calledruleset.yamlin the same folder with the following content:
name: sample/ruleset
description: This is a sample ruleset- Run kantra again with the new rule and check the results:
kantra --input <path-to>/coolstore --target quarkus --target jakarta-ee9+ --target cloud-readiness --overwrite
It is ideal to have tests for each rule that we write. For that purpose, kantra has a test runner that allows us to write tests and run them against sample code. Here are the docs for the test runner.
Writing a test is easy. In the case of the rule we have just written, and having the coolstore app as data for our test,
we could have the following dir structure with a new rule.test.yaml file and the coolstore app as data:
.
├── ruleset.yaml
├── rule.test.yaml
├── rule.yaml
└── test-data
└── coolstore
test-data folder outside of the rule folder and change the test below accordingly.
Tests must be named following the *.test.yaml convention in order for the analyzer to ignore them when running a normal
analysis. In the case of the test runner, it will pick up whatever it finds ending in *.test.yaml.
Given this structure, our test could look like this:
rulesPath: ./rule.yaml
providers:
- name: java
dataPath: ./test-data/coolstore
tests:
- ruleID: coolstore-rule-00001
testCases:
- name: coolstore-rule-00001-test-01
hasIncidents:
exactly: 2
messageMatches: "ProductService cannot be used with the @Inject annotation in version 2 of the coolstore application"It will check that two incidents are occurring in the analyzer output, and that the message corresponds to that of the rule.
To execute it simply run kantra rules test ..