From ee179100e3ac06e160ababef21ff35480a4588eb Mon Sep 17 00:00:00 2001 From: Roy Golan Date: Thu, 18 May 2023 19:37:42 +0300 Subject: [PATCH] MTA Assessment Workflow This is an assessment workflow which is based on the Migration Analysis infra workflow. The main differne is that the checker of that workflow downloads the html report and tries to extract a table of scores of issues found by the analysis. The ProcessAnalysisTask has a predicate passed in the constructor to test against the scores of the analysis and based on that the task returns the passOption or the defaultOption. The passOption is the next flow to invoke, that should be the move2kube. The flow is roughly: Start - Infra flow start - submit analysis report - End Start - Checker flow start - check report is ready - switch (is ready?) yes -> go to parse no -> go to check report is ready - parse - switch (match incidents on predicate) true - return pass option (move2kube) false - return default option - End Signed-off-by: Roy Golan --- .prettierrc.json | 1 + parodos-model-api/pom.xml | 5 + .../task/infrastructure/Notifier.java | 9 ++ prebuilt-tasks/pom.xml | 6 + .../CreateApplicationTask.java | 19 --- .../migrationtoolkit/GetAnalysisTask.java | 30 ++-- .../migrationtoolkit/GetApplicationTask.java | 20 --- .../migrationtoolkit/SubmitAnalysisTask.java | 17 --- .../NotificationWorkFlowTask.java | 12 +- prettierrc.json | 1 + .../OcpOnboardingWorkFlowConfiguration.java | 16 ++- .../PrebuiltWorkFlowConfiguration.java | 2 +- .../migrationtoolkit/MTAAnalysisReport.java | 53 +++++++ .../MigrationAnalysisWorkflow.java | 63 +++------ .../MigrationAssessmentWorkflow.java | 97 +++++++++++++ .../migrationtoolkit/ProcessAnalysisTask.java | 130 ++++++++++++++++++ .../MTAAnalysisReportTest.java | 59 ++++++++ .../redhat/parodos/NotificationSender.java | 40 ++++++ .../src/main/resources/application-local.yml | 10 ++ 19 files changed, 470 insertions(+), 120 deletions(-) create mode 100644 .prettierrc.json create mode 100644 parodos-model-api/src/main/java/com/redhat/parodos/workflow/task/infrastructure/Notifier.java create mode 100644 prettierrc.json create mode 100644 workflow-examples/src/main/java/com/redhat/parodos/examples/prebuilt/migrationtoolkit/MTAAnalysisReport.java create mode 100644 workflow-examples/src/main/java/com/redhat/parodos/examples/prebuilt/migrationtoolkit/MigrationAssessmentWorkflow.java create mode 100644 workflow-examples/src/main/java/com/redhat/parodos/examples/prebuilt/migrationtoolkit/ProcessAnalysisTask.java create mode 100644 workflow-examples/src/test/java/com/redhat/parodos/examples/prebuilt/migrationtoolkit/MTAAnalysisReportTest.java create mode 100644 workflow-service/src/main/java/com/redhat/parodos/NotificationSender.java diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1 @@ +{} diff --git a/parodos-model-api/pom.xml b/parodos-model-api/pom.xml index 71e989d86..13f89ed8d 100644 --- a/parodos-model-api/pom.xml +++ b/parodos-model-api/pom.xml @@ -52,6 +52,11 @@ com.fasterxml.jackson.core jackson-databind + + dev.parodos + notification-service-sdk + 1.0.14-SNAPSHOT + junit junit diff --git a/parodos-model-api/src/main/java/com/redhat/parodos/workflow/task/infrastructure/Notifier.java b/parodos-model-api/src/main/java/com/redhat/parodos/workflow/task/infrastructure/Notifier.java new file mode 100644 index 000000000..b3a6d1ca6 --- /dev/null +++ b/parodos-model-api/src/main/java/com/redhat/parodos/workflow/task/infrastructure/Notifier.java @@ -0,0 +1,9 @@ +package com.redhat.parodos.workflow.task.infrastructure; + +import com.redhat.parodos.notification.sdk.model.NotificationMessageCreateRequestDTO; + +public interface Notifier { + + void send(NotificationMessageCreateRequestDTO message); + +} diff --git a/prebuilt-tasks/pom.xml b/prebuilt-tasks/pom.xml index bdb8e4652..2faf69be8 100644 --- a/prebuilt-tasks/pom.xml +++ b/prebuilt-tasks/pom.xml @@ -122,6 +122,12 @@ ${jgit-version} + + org.jsoup + jsoup + 1.15.3 + + org.eclipse.jgit org.eclipse.jgit.archive diff --git a/prebuilt-tasks/src/main/java/com/redhat/parodos/tasks/migrationtoolkit/CreateApplicationTask.java b/prebuilt-tasks/src/main/java/com/redhat/parodos/tasks/migrationtoolkit/CreateApplicationTask.java index b2286211e..5ee15b127 100644 --- a/prebuilt-tasks/src/main/java/com/redhat/parodos/tasks/migrationtoolkit/CreateApplicationTask.java +++ b/prebuilt-tasks/src/main/java/com/redhat/parodos/tasks/migrationtoolkit/CreateApplicationTask.java @@ -1,17 +1,13 @@ package com.redhat.parodos.tasks.migrationtoolkit; import java.net.URI; -import java.util.List; import com.redhat.parodos.workflow.exception.MissingParameterException; -import com.redhat.parodos.workflow.parameter.WorkParameter; -import com.redhat.parodos.workflow.parameter.WorkParameterType; import com.redhat.parodos.workflow.task.infrastructure.BaseInfrastructureWorkFlowTask; import com.redhat.parodos.workflows.work.DefaultWorkReport; import com.redhat.parodos.workflows.work.WorkContext; import com.redhat.parodos.workflows.work.WorkReport; import com.redhat.parodos.workflows.work.WorkStatus; -import lombok.NonNull; import lombok.extern.slf4j.Slf4j; /** @@ -37,21 +33,6 @@ public CreateApplicationTask(URI serverURL, String bearerToken) { mtaClient = new MTAClient(serverURL, bearerToken); } - @Override - public @NonNull List getWorkFlowTaskParameters() { - return List.of( - WorkParameter.builder().key("applicationName").type(WorkParameterType.TEXT).optional(false) - .description("The application name. Can be generated from the repository name").build(), - WorkParameter.builder().key("repositoryURL").type(WorkParameterType.TEXT).optional(false) - .description("The application git repository URL. Can be generated from the repository name") - .build(), - WorkParameter.builder().key("serverURL").type(WorkParameterType.TEXT).optional(true).description( - "Base URL of the MTA instance - e.g https://mta-openshift-mta.app.clustername.clusterdomain") - .build(), - WorkParameter.builder().key("bearerToken").type(WorkParameterType.TEXT).optional(true) - .description("Bearer token to authenticate server requests").build()); - } - /** * @param workContext optional context values: serverURL, and bearerToken for the * mtaClient. diff --git a/prebuilt-tasks/src/main/java/com/redhat/parodos/tasks/migrationtoolkit/GetAnalysisTask.java b/prebuilt-tasks/src/main/java/com/redhat/parodos/tasks/migrationtoolkit/GetAnalysisTask.java index f31f22c02..bc1284dd6 100644 --- a/prebuilt-tasks/src/main/java/com/redhat/parodos/tasks/migrationtoolkit/GetAnalysisTask.java +++ b/prebuilt-tasks/src/main/java/com/redhat/parodos/tasks/migrationtoolkit/GetAnalysisTask.java @@ -2,13 +2,15 @@ import java.net.URI; import java.util.List; -import java.util.function.Consumer; -import com.redhat.parodos.email.Message; +import javax.inject.Inject; + +import com.redhat.parodos.notification.sdk.model.NotificationMessageCreateRequestDTO; import com.redhat.parodos.workflow.exception.MissingParameterException; import com.redhat.parodos.workflow.parameter.WorkParameter; import com.redhat.parodos.workflow.parameter.WorkParameterType; import com.redhat.parodos.workflow.task.infrastructure.BaseInfrastructureWorkFlowTask; +import com.redhat.parodos.workflow.task.infrastructure.Notifier; import com.redhat.parodos.workflows.work.DefaultWorkReport; import com.redhat.parodos.workflows.work.WorkContext; import com.redhat.parodos.workflows.work.WorkReport; @@ -35,12 +37,13 @@ public class GetAnalysisTask extends BaseInfrastructureWorkFlowTask { private URI serverUrl; - private final Consumer messageConsumer; + @Inject + private Notifier notificationSender; - public GetAnalysisTask(URI serverURL, String bearerToken, Consumer messageConsumer) { + public GetAnalysisTask(URI serverURL, String bearerToken, Notifier notifer) { this.serverUrl = serverURL; this.mtaClient = new MTAClient(serverURL, bearerToken); - this.messageConsumer = messageConsumer; + this.notificationSender = notifer; } @Override @@ -95,8 +98,8 @@ else if (result instanceof Result.Success success) { && success.value().tasks()[0].state().equals("Succeeded")) { String reportURL = String.format("%s/hub/applications/%d/bucket/%s", serverUrl, success.value().tasks()[0].application().id(), success.value().data().output()); - sendEmail(reportURL, getOptionalParameterValue("email", null)); addParameter("reportURL", reportURL); + sendNotification(reportURL); return new DefaultWorkReport(WorkStatus.COMPLETED, workContext); } else if ("Failed".equals(success.value().state())) { @@ -108,13 +111,14 @@ else if ("Failed".equals(success.value().state())) { throw new IllegalArgumentException(); } - private void sendEmail(String reportURL, String recipient) { - if (recipient == null) { - return; - } - messageConsumer.accept(new Message(recipient, "parodos-task-notification@redhat.com", - "Parodos: Analysis report is done", - String.format("The analysis report is done. Find it here %s", reportURL))); + private void sendNotification(String reportURL) { + var request = new NotificationMessageCreateRequestDTO(); + request.setMessageType("text"); + request.setBody("Report URL: " + reportURL); + request.subject("Migration Analysis Report Completed"); + request.setUsernames(List.of("test")); + request.setGroupNames(List.of("test")); + notificationSender.send(request); } } diff --git a/prebuilt-tasks/src/main/java/com/redhat/parodos/tasks/migrationtoolkit/GetApplicationTask.java b/prebuilt-tasks/src/main/java/com/redhat/parodos/tasks/migrationtoolkit/GetApplicationTask.java index e3a7f7156..0be40152c 100644 --- a/prebuilt-tasks/src/main/java/com/redhat/parodos/tasks/migrationtoolkit/GetApplicationTask.java +++ b/prebuilt-tasks/src/main/java/com/redhat/parodos/tasks/migrationtoolkit/GetApplicationTask.java @@ -1,17 +1,13 @@ package com.redhat.parodos.tasks.migrationtoolkit; import java.net.URI; -import java.util.List; import com.redhat.parodos.workflow.exception.MissingParameterException; -import com.redhat.parodos.workflow.parameter.WorkParameter; -import com.redhat.parodos.workflow.parameter.WorkParameterType; import com.redhat.parodos.workflow.task.infrastructure.BaseInfrastructureWorkFlowTask; import com.redhat.parodos.workflows.work.DefaultWorkReport; import com.redhat.parodos.workflows.work.WorkContext; import com.redhat.parodos.workflows.work.WorkReport; import com.redhat.parodos.workflows.work.WorkStatus; -import lombok.NonNull; import lombok.extern.slf4j.Slf4j; /** @@ -36,22 +32,6 @@ public GetApplicationTask(URI serverURL, String bearerToken) { mtaClient = new MTAClient(serverURL, bearerToken); } - @Override - public @NonNull List getWorkFlowTaskParameters() { - return List.of( - WorkParameter.builder().key("serverURL").type(WorkParameterType.TEXT).optional(true).description( - "Base URL of the MTA instance - e.g https://mta-openshift-mta.app.clustername.clusterdomain") - .build(), - WorkParameter.builder().key("bearerToken").type(WorkParameterType.TEXT).optional(true) - .description("Bearer token to authenticate server requests").build(), - WorkParameter.builder().key("applicationName").type(WorkParameterType.TEXT).optional(false).description( - "The application name as presented in the application hub. Can be generated from the repository name") - .build(), - WorkParameter.builder().key("sourceRepository").type(WorkParameterType.TEXT).optional(true).description( - "The application name as presented in the application hub. Can be generated from the repository name") - .build()); - } - /** * @param workContext optional context values: serverURL, and bearerToken for the * mtaClient. diff --git a/prebuilt-tasks/src/main/java/com/redhat/parodos/tasks/migrationtoolkit/SubmitAnalysisTask.java b/prebuilt-tasks/src/main/java/com/redhat/parodos/tasks/migrationtoolkit/SubmitAnalysisTask.java index 0bf01f9a8..1969429c2 100644 --- a/prebuilt-tasks/src/main/java/com/redhat/parodos/tasks/migrationtoolkit/SubmitAnalysisTask.java +++ b/prebuilt-tasks/src/main/java/com/redhat/parodos/tasks/migrationtoolkit/SubmitAnalysisTask.java @@ -1,17 +1,13 @@ package com.redhat.parodos.tasks.migrationtoolkit; import java.net.URI; -import java.util.List; import com.redhat.parodos.workflow.exception.MissingParameterException; -import com.redhat.parodos.workflow.parameter.WorkParameter; -import com.redhat.parodos.workflow.parameter.WorkParameterType; import com.redhat.parodos.workflow.task.infrastructure.BaseInfrastructureWorkFlowTask; import com.redhat.parodos.workflows.work.DefaultWorkReport; import com.redhat.parodos.workflows.work.WorkContext; import com.redhat.parodos.workflows.work.WorkReport; import com.redhat.parodos.workflows.work.WorkStatus; -import lombok.NonNull; import lombok.extern.slf4j.Slf4j; /** @@ -41,19 +37,6 @@ public SubmitAnalysisTask(URI serverURL, String bearerToken) { this.mtaClient = new MTAClient(serverURL, bearerToken); } - @Override - public @NonNull List getWorkFlowTaskParameters() { - return List.of( - WorkParameter.builder().key("applicationName").type(WorkParameterType.TEXT).optional(false).description( - "The application name as presented in the application hub. Can be generated from the repository name") - .build(), - WorkParameter.builder().key("serverURL").type(WorkParameterType.TEXT).optional(true).description( - "Base URL of the MTA instance - e.g https://mta-openshift-mta.app.clustername.clusterdomain") - .build(), - WorkParameter.builder().key("bearerToken").type(WorkParameterType.TEXT).optional(true) - .description("Bearer token to authenticate server requests").build()); - } - /** * @param workContext optional context values: serverURL, and bearerToken for the * mtaClient. diff --git a/prebuilt-tasks/src/main/java/com/redhat/parodos/tasks/notification/NotificationWorkFlowTask.java b/prebuilt-tasks/src/main/java/com/redhat/parodos/tasks/notification/NotificationWorkFlowTask.java index 6e8b13e16..231bd7e5c 100644 --- a/prebuilt-tasks/src/main/java/com/redhat/parodos/tasks/notification/NotificationWorkFlowTask.java +++ b/prebuilt-tasks/src/main/java/com/redhat/parodos/tasks/notification/NotificationWorkFlowTask.java @@ -5,6 +5,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.function.Function; import com.redhat.parodos.notification.sdk.api.ApiClient; import com.redhat.parodos.notification.sdk.api.ApiException; @@ -31,8 +32,15 @@ public class NotificationWorkFlowTask extends BaseWorkFlowTask { private final NotificationMessageApi apiInstance; - public NotificationWorkFlowTask(String basePath, String auth) { + private Function messageFunc = ( + context) -> new NotificationMessageCreateRequestDTO(); + + public NotificationWorkFlowTask(String basePath, String auth, + Function messageFunc) { this(basePath, null, auth); + if (messageFunc != null) { + this.messageFunc = messageFunc; + } } protected NotificationWorkFlowTask(String basePath, NotificationMessageApi apiInstance) { @@ -78,7 +86,7 @@ public HashMap> getAsJsonSchema() { @Override public WorkReport execute(WorkContext workContext) { - NotificationMessageCreateRequestDTO notificationMessageCreateRequestDTO = new NotificationMessageCreateRequestDTO(); + NotificationMessageCreateRequestDTO notificationMessageCreateRequestDTO = messageFunc.apply(workContext); try { notificationMessageCreateRequestDTO.messageType(getRequiredParameterValue("type")); diff --git a/prettierrc.json b/prettierrc.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/prettierrc.json @@ -0,0 +1 @@ +{} diff --git a/workflow-examples/src/main/java/com/redhat/parodos/examples/ocponboarding/OcpOnboardingWorkFlowConfiguration.java b/workflow-examples/src/main/java/com/redhat/parodos/examples/ocponboarding/OcpOnboardingWorkFlowConfiguration.java index bed8b14bc..11cd2caeb 100644 --- a/workflow-examples/src/main/java/com/redhat/parodos/examples/ocponboarding/OcpOnboardingWorkFlowConfiguration.java +++ b/workflow-examples/src/main/java/com/redhat/parodos/examples/ocponboarding/OcpOnboardingWorkFlowConfiguration.java @@ -41,8 +41,10 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; @Configuration +@Profile("local") public class OcpOnboardingWorkFlowConfiguration { // Assessment workflow @@ -69,13 +71,23 @@ WorkFlowOption notSupportOption() { .setDescription("Non-Supported Workflow Steps").build(); } + @Bean + WorkFlowOption analyzeOption() { + return new WorkFlowOption.Builder("analyzeOption", "AnalyzeApplication") + .addToDetails("Analyze an application's source code before migrating it." + + " Produces a containerization report by MTA") + .displayName("Migration Analysis").setDescription("Migration Analysis step").build(); + } + // An AssessmentTask returns one or more WorkFlowOption wrapped in a WorkflowOptions @Bean OnboardingOcpAssessmentTask onboardingAssessmentTask( @Qualifier("onboardingOcpOption") WorkFlowOption onboardingOcpOption, @Qualifier("badRepoOption") WorkFlowOption badRepoOption, - @Qualifier("notSupportOption") WorkFlowOption notSupportOption) { - return new OnboardingOcpAssessmentTask(List.of(onboardingOcpOption, badRepoOption, notSupportOption)); + @Qualifier("notSupportOption") WorkFlowOption notSupportOption, + @Qualifier("analyzeOption") WorkFlowOption analyzeOption) { + return new OnboardingOcpAssessmentTask( + List.of(onboardingOcpOption, badRepoOption, notSupportOption, analyzeOption)); } // A Workflow designed to execute and return WorkflowOption(s) that can be executed diff --git a/workflow-examples/src/main/java/com/redhat/parodos/examples/prebuilt/PrebuiltWorkFlowConfiguration.java b/workflow-examples/src/main/java/com/redhat/parodos/examples/prebuilt/PrebuiltWorkFlowConfiguration.java index 8ae971dfe..ee29b1507 100644 --- a/workflow-examples/src/main/java/com/redhat/parodos/examples/prebuilt/PrebuiltWorkFlowConfiguration.java +++ b/workflow-examples/src/main/java/com/redhat/parodos/examples/prebuilt/PrebuiltWorkFlowConfiguration.java @@ -25,7 +25,7 @@ NotificationWorkFlowTask notificationTask() { String notificationWorkFlowBasePath = "http://" + serverIp + ":" + serverPort; log.info("NotificationWorkFlowTask basePath: {}", notificationWorkFlowBasePath); return new NotificationWorkFlowTask(notificationWorkFlowBasePath, - "Basic " + CredUtils.getBase64Creds("test", "test")); + "Basic " + CredUtils.getBase64Creds("test", "test"), null); } @Bean(name = "prebuiltWorkFlow" + WorkFlowConstants.INFRASTRUCTURE_WORKFLOW) diff --git a/workflow-examples/src/main/java/com/redhat/parodos/examples/prebuilt/migrationtoolkit/MTAAnalysisReport.java b/workflow-examples/src/main/java/com/redhat/parodos/examples/prebuilt/migrationtoolkit/MTAAnalysisReport.java new file mode 100644 index 000000000..e9233c2aa --- /dev/null +++ b/workflow-examples/src/main/java/com/redhat/parodos/examples/prebuilt/migrationtoolkit/MTAAnalysisReport.java @@ -0,0 +1,53 @@ +package com.redhat.parodos.examples.prebuilt.migrationtoolkit; + +import org.jsoup.Jsoup; +import org.jsoup.nodes.Element; + +public class MTAAnalysisReport { + + /** + * migrationIssuesReport is assumed to be valid html. MTA 6.1 should supply a CSV as + * well. Throw and exception when the parsing fails or if it can't extract data + */ + public record AnalysisIncidents(int mandatory, int optional, int potential, int cloudMandatory, int cloudOptional, + int information) { + } + + /** + * Extract an AnalysisIncident from the report index html page + * @rpeortIndex is the content the page + * /report/reports/report_index_{name_of_project}.html + */ + public static AnalysisIncidents extractIncidents(String reportIndex) throws Exception { + var doc = Jsoup.parse(reportIndex); + + var tables = doc.select("tbody#incidentsByTypeTBody"); + if (tables.size() == 1) { + var t = tables.get(0); + int mandatory = countOf(t, "mandatory"); + int optional = countOf(t, "optional"); + int potential = countOf(t, "potential"); + int cloudMandatory = countOf(t, "cloud-mandatory"); + int cloudOptional = countOf(t, "cloud-optional"); + int information = countOf(t, "information"); + + return new AnalysisIncidents(mandatory, optional, potential, cloudMandatory, cloudOptional, information); + } + throw new Exception("Didn't find a table to parse and extract incidents from"); + } + + private static int countOf(Element t, String column) throws Exception { + var element = t.select("td:matches(^%s)".formatted(column)); + if (element != null && !element.isEmpty()) { + try { + String count = element.next().select("td.numeric-column").text(); + return Integer.parseInt(count); + } + catch (NumberFormatException | NullPointerException e) { + throw new Exception("Failed to extracts columns information from the table", e); + } + } + throw new Exception("Failed to find or parse a table cell with text " + column); + } + +} diff --git a/workflow-examples/src/main/java/com/redhat/parodos/examples/prebuilt/migrationtoolkit/MigrationAnalysisWorkflow.java b/workflow-examples/src/main/java/com/redhat/parodos/examples/prebuilt/migrationtoolkit/MigrationAnalysisWorkflow.java index 7932db877..5fda92483 100644 --- a/workflow-examples/src/main/java/com/redhat/parodos/examples/prebuilt/migrationtoolkit/MigrationAnalysisWorkflow.java +++ b/workflow-examples/src/main/java/com/redhat/parodos/examples/prebuilt/migrationtoolkit/MigrationAnalysisWorkflow.java @@ -2,42 +2,44 @@ import java.net.URI; import java.util.List; -import java.util.Objects; -import java.util.function.Consumer; -import com.redhat.parodos.email.Message; import com.redhat.parodos.tasks.migrationtoolkit.CreateApplicationTask; import com.redhat.parodos.tasks.migrationtoolkit.GetAnalysisTask; import com.redhat.parodos.tasks.migrationtoolkit.GetApplicationTask; import com.redhat.parodos.tasks.migrationtoolkit.SubmitAnalysisTask; import com.redhat.parodos.workflow.annotation.Checker; import com.redhat.parodos.workflow.annotation.Infrastructure; +import com.redhat.parodos.workflow.annotation.Parameter; +import com.redhat.parodos.workflow.parameter.WorkParameterType; +import com.redhat.parodos.workflow.task.infrastructure.Notifier; import com.redhat.parodos.workflows.workflow.WorkFlow; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; -import org.springframework.mail.SimpleMailMessage; -import org.springframework.mail.javamail.JavaMailSenderImpl; import static com.redhat.parodos.workflows.workflow.SequentialFlow.Builder.aNewSequentialFlow; /** * A workflow to analyze applications using Migration Toolkit for Applications - * + *

* This workflow will: - create an application with a name, and git repo URL - submit an * analysis report for it - wait till the report is back - send an email with the report * url - * + *

* ENV Variables: For security reasons the MTA url and bearer token can be passed to all * the task constructors For example use MTA_URL and MTA_BEARER_TOKEN To get an email * supply the following variables MAILER_HOST MAILER_PORT MAILER_USER MAILER_PASS */ -@Profile("examples") @Configuration +@Profile("dev") +@Slf4j public class MigrationAnalysisWorkflow { // the url of the MTA (migration toolkit for application) + @Value("${workflows.mta.url}") private String mtaUrl; // in the end you can send a message to an email server with the fields below. The @@ -47,20 +49,8 @@ public class MigrationAnalysisWorkflow { // See #messageConsumer // If you do remove or change the messaging consumer then make sure to update the // constructor checks. - private final String mailerHost; - - private final String mailerPort; - - private final String mailerUser; - - private final String mailerPass; public MigrationAnalysisWorkflow() { - mtaUrl = Objects.requireNonNull(System.getenv("MTA_URL")); - mailerHost = Objects.requireNonNull(System.getenv("MAILER_HOST")); - mailerPort = Objects.requireNonNull(System.getenv("MAILER_PORT")); - mailerUser = Objects.requireNonNull(System.getenv("MAILER_USER")); - mailerPass = Objects.requireNonNull(System.getenv("MAILER_PASS")); } @Bean @@ -81,12 +71,16 @@ public SubmitAnalysisTask submitAnalysisTask(WorkFlow fetchReportURL) { } @Bean - public GetAnalysisTask getAnalysisTask() { - return new GetAnalysisTask(URI.create(mtaUrl), "", messageConsumer()); + public GetAnalysisTask getAnalysisTask(Notifier notifier) { + return new GetAnalysisTask(URI.create(mtaUrl), "", notifier); } @Bean(name = "AnalyzeApplication") - @Infrastructure + @Infrastructure(parameters = { + @Parameter(key = "repositoryURL", description = "The repository with the code to analyze", + type = WorkParameterType.URL, optional = false), + @Parameter(key = "applicationName", description = "The name of the application to analyze", + type = WorkParameterType.TEXT, optional = false) }) public WorkFlow AnalyzeApplication(CreateApplicationTask createApplicationTask, GetApplicationTask getAppTask, SubmitAnalysisTask submitAnalysisTask) { return aNewSequentialFlow().named("AnalyzeApplication").execute(createApplicationTask).then(getAppTask) @@ -99,27 +93,4 @@ public WorkFlow fetchReportURL(GetAnalysisTask getAnalysisTask) { return aNewSequentialFlow().named("fetchReportURL").execute(getAnalysisTask).build(); } - private Consumer messageConsumer() { - var sender = new JavaMailSenderImpl(); - sender.setHost(mailerHost); - sender.setPort(Integer.valueOf(mailerPort)); - sender.setUsername(mailerUser); - sender.setPassword(mailerPass); - - Consumer messageConsumer = m -> { - var message = new SimpleMailMessage(); - message.setTo(m.to()); - message.setFrom(m.from()); - message.setSubject(m.subject()); - message.setText(m.data()); - try { - sender.send(message); - } - catch (Exception e) { - // TODO log handling - } - }; - return messageConsumer; - } - } diff --git a/workflow-examples/src/main/java/com/redhat/parodos/examples/prebuilt/migrationtoolkit/MigrationAssessmentWorkflow.java b/workflow-examples/src/main/java/com/redhat/parodos/examples/prebuilt/migrationtoolkit/MigrationAssessmentWorkflow.java new file mode 100644 index 000000000..e8305cc9c --- /dev/null +++ b/workflow-examples/src/main/java/com/redhat/parodos/examples/prebuilt/migrationtoolkit/MigrationAssessmentWorkflow.java @@ -0,0 +1,97 @@ +package com.redhat.parodos.examples.prebuilt.migrationtoolkit; + +import java.net.URI; +import java.util.List; +import java.util.Objects; + +import com.redhat.parodos.tasks.migrationtoolkit.CreateApplicationTask; +import com.redhat.parodos.tasks.migrationtoolkit.GetAnalysisTask; +import com.redhat.parodos.tasks.migrationtoolkit.GetApplicationTask; +import com.redhat.parodos.tasks.migrationtoolkit.SubmitAnalysisTask; +import com.redhat.parodos.workflow.annotation.Assessment; +import com.redhat.parodos.workflow.annotation.Checker; +import com.redhat.parodos.workflow.option.WorkFlowOption; +import com.redhat.parodos.workflow.task.infrastructure.Notifier; +import com.redhat.parodos.workflows.workflow.WorkFlow; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; + +import static com.redhat.parodos.workflows.workflow.SequentialFlow.Builder.aNewSequentialFlow; + +/** + * An assessment workflow to analyze applications using Migration Toolkit for Applications + * and return a move2kube option when needed. + *

+ * This workflow will: - create an application with a name, and git repo URL - submit an + * analysis report for it - wait till the report is back, analyze it, and return a + * workflow option to move2kube based on the findings + *

+ */ +@Configuration +@Profile("dev") +public class MigrationAssessmentWorkflow { + + // the url of the MTA (migration toolkit for application) + private String mtaUrl = Objects.requireNonNull(System.getenv("MTA_URL")); + + @Bean + public CreateApplicationTask createApplicationTask() { + return new CreateApplicationTask(URI.create(mtaUrl), ""); + } + + @Bean + public GetApplicationTask getApplicationTask() { + return new GetApplicationTask(URI.create(mtaUrl), ""); + } + + @Bean + public SubmitAnalysisTask submitAnalysisTask(WorkFlow fetchReportURL) { + SubmitAnalysisTask t = new SubmitAnalysisTask(URI.create(mtaUrl), ""); + t.setWorkFlowCheckers(List.of(fetchReportURL)); + return t; + } + + @Bean + public GetAnalysisTask getAnalysisTask(Notifier notifier) { + return new GetAnalysisTask(URI.create(mtaUrl), "", notifier); + } + + @Bean + ProcessAnalysisTask processAnalysisTask(WorkFlowOption move2kube, WorkFlowOption defaultOption) { + return new ProcessAnalysisTask(move2kube, defaultOption, + analysisIncidents -> analysisIncidents.mandatory() > 0); + } + + @Bean + WorkFlowOption move2kube() { + return new WorkFlowOption.Builder("move2kube", "move2kube").addToDetails("Migration to OCP") + .displayName("Migration to OCP") + .setDescription("This app is ready to be migrated to OCP. Click to migrate.").build(); + } + + @Bean + WorkFlowOption defaultOption() { + return new WorkFlowOption.Builder("defaultOption", "AnalyzeApplicationAssessment") + .addToDetails("Rerun Analysis").displayName("Run Migration Analysis") + .setDescription( + "This application didn't meet the expected analysis score. Update or apply the relevant fixes and re-run the analysis") + .build(); + } + + @Bean(name = "AnalyzeApplicationAssessment") + @Assessment + public WorkFlow AnalyzeApplicationAssessment(CreateApplicationTask createApplicationTask, + GetApplicationTask getAppTask, SubmitAnalysisTask submitAnalysisTask) { + return aNewSequentialFlow().named("AnalyzeApplicationAssessment").execute(createApplicationTask) + .then(getAppTask).then(submitAnalysisTask).build(); + } + + @Bean("fetchReportURL") + @Checker(cronExpression = "*/5 * * * * ?") + public WorkFlow fetchReportURL(GetAnalysisTask getAnalysisTask, ProcessAnalysisTask processAnalysisTask) { + return aNewSequentialFlow().named("fetchReportURL").execute(getAnalysisTask).then(processAnalysisTask).build(); + } + +} diff --git a/workflow-examples/src/main/java/com/redhat/parodos/examples/prebuilt/migrationtoolkit/ProcessAnalysisTask.java b/workflow-examples/src/main/java/com/redhat/parodos/examples/prebuilt/migrationtoolkit/ProcessAnalysisTask.java new file mode 100644 index 000000000..60de39eab --- /dev/null +++ b/workflow-examples/src/main/java/com/redhat/parodos/examples/prebuilt/migrationtoolkit/ProcessAnalysisTask.java @@ -0,0 +1,130 @@ +package com.redhat.parodos.examples.prebuilt.migrationtoolkit; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.List; +import java.util.function.Predicate; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; + +import com.redhat.parodos.workflow.context.WorkContextDelegate; +import com.redhat.parodos.workflow.exception.MissingParameterException; +import com.redhat.parodos.workflow.option.WorkFlowOption; +import com.redhat.parodos.workflow.option.WorkFlowOptions; +import com.redhat.parodos.workflow.task.assessment.BaseAssessmentTask; +import com.redhat.parodos.workflows.work.DefaultWorkReport; +import com.redhat.parodos.workflows.work.WorkContext; +import com.redhat.parodos.workflows.work.WorkReport; +import com.redhat.parodos.workflows.work.WorkStatus; +import org.jsoup.Jsoup; +import org.jsoup.select.Elements; + +public class ProcessAnalysisTask extends BaseAssessmentTask { + + private final WorkFlowOption passOption; + + private final WorkFlowOption defaultOption; + + private final Predicate incidentsPredicate; + + /** + * @param passOption option for a successful report analysis + * @param defaultOption default option to return + * @param passCriteria the predicate to test against the report incidents from the + * analysis task + */ + protected ProcessAnalysisTask(WorkFlowOption passOption, WorkFlowOption defaultOption, + Predicate passCriteria) { + super(List.of(passOption, defaultOption)); + this.passOption = passOption; + this.defaultOption = defaultOption; + this.incidentsPredicate = passCriteria; + } + + @Override + public WorkReport execute(WorkContext workContext) { + String reportURL; + try { + reportURL = getRequiredParameterValue("reportURL"); + } + catch (MissingParameterException e) { + return new DefaultWorkReport(WorkStatus.FAILED, workContext); + } + + // this is the "migration issues" section from report from url.It assumed to be in + // HTML format. In version 6.1 of MTA it can be + // downloaded as csv and would be simpler to process, hopefully. + URI uri = URI.create(reportURL).resolve("reports"); + try { + var reports = downloadReport(uri); + Elements reportLinks = Jsoup.parse(reports).select("a[href~=report_index_]"); + if (reportLinks.isEmpty()) { + return new DefaultWorkReport(WorkStatus.FAILED, workContext, + new Exception("failed to parse report index. Missing reports/report_index_*")); + } + String migrationIssuesReport = downloadReport(uri.resolve(reportLinks.get(0).text())); + MTAAnalysisReport.AnalysisIncidents incidents = MTAAnalysisReport.extractIncidents(migrationIssuesReport); + + if (incidentsPredicate.test(incidents)) { + WorkContextDelegate.write(workContext, WorkContextDelegate.ProcessType.WORKFLOW_EXECUTION, + WorkContextDelegate.Resource.WORKFLOW_OPTIONS, + new WorkFlowOptions.Builder().addNewOption(passOption).build()); + } + else { + WorkContextDelegate.write(workContext, WorkContextDelegate.ProcessType.WORKFLOW_EXECUTION, + WorkContextDelegate.Resource.WORKFLOW_OPTIONS, + new WorkFlowOptions.Builder().addNewOption(defaultOption).build()); + + } + return new DefaultWorkReport(WorkStatus.COMPLETED, workContext); + } + catch (Exception e) { + return new DefaultWorkReport(WorkStatus.FAILED, workContext, e); + } + } + + private String downloadReport(URI uri) throws NoSuchAlgorithmException, KeyManagementException { + SSLContext nonValidatingSSLContext = SSLContext.getInstance("SSL"); + nonValidatingSSLContext.init(null, new TrustManager[] { new X509TrustManager() { + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { + + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { + + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + } }, SecureRandom.getInstanceStrong()); + var c = HttpClient.newBuilder().sslContext(nonValidatingSSLContext).followRedirects(HttpClient.Redirect.ALWAYS) + .build(); + try { + HttpResponse get = c.send(HttpRequest.newBuilder().uri(uri).build(), + HttpResponse.BodyHandlers.ofString()); + if (get.statusCode() == HttpURLConnection.HTTP_OK) { + return get.body(); + } + } + catch (IOException | InterruptedException e) { + throw new RuntimeException(e); + } + return null; + } + +} diff --git a/workflow-examples/src/test/java/com/redhat/parodos/examples/prebuilt/migrationtoolkit/MTAAnalysisReportTest.java b/workflow-examples/src/test/java/com/redhat/parodos/examples/prebuilt/migrationtoolkit/MTAAnalysisReportTest.java new file mode 100644 index 000000000..858adf3ec --- /dev/null +++ b/workflow-examples/src/test/java/com/redhat/parodos/examples/prebuilt/migrationtoolkit/MTAAnalysisReportTest.java @@ -0,0 +1,59 @@ +package com.redhat.parodos.examples.prebuilt.migrationtoolkit; + +import org.jsoup.Jsoup; +import org.jsoup.select.Elements; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class MTAAnalysisReportTest { + + String validReport = """ + + + + + + + + + + + + + + + + + + +
Incidents by Category Incidents Total Story Points
mandatory10
optional20
potential30
cloud-mandatory477
cloud-optional50
information60
+ + + """; + + @Test + public void canExtractIncidents() throws Exception { + assertThat(MTAAnalysisReport.extractIncidents(validReport)) + .isEqualTo(new MTAAnalysisReport.AnalysisIncidents(1, 2, 3, 4, 5, 6)); + } + + String validPre = """ +

+			migration_issues.html
+			report_index_parodos.html
+			resources/
+			techReport_parodos.html
+			techReport_punch.html
+			windup_freemarkerfunctions.html
+			windup_ruleproviders.html
+			
+ """; + + @Test + public void canExtractReportIndex() throws Exception { + Elements select = Jsoup.parse(validPre).select("a[href~=report_index_]"); + assertThat(select.text()).isEqualTo("report_index_parodos.html"); + } + +} \ No newline at end of file diff --git a/workflow-service/src/main/java/com/redhat/parodos/NotificationSender.java b/workflow-service/src/main/java/com/redhat/parodos/NotificationSender.java new file mode 100644 index 000000000..0fad02afe --- /dev/null +++ b/workflow-service/src/main/java/com/redhat/parodos/NotificationSender.java @@ -0,0 +1,40 @@ +package com.redhat.parodos; + +import java.util.Base64; + +import javax.ws.rs.core.HttpHeaders; + +import com.redhat.parodos.notification.sdk.api.ApiClient; +import com.redhat.parodos.notification.sdk.api.ApiException; +import com.redhat.parodos.notification.sdk.api.NotificationMessageApi; +import com.redhat.parodos.notification.sdk.model.NotificationMessageCreateRequestDTO; +import com.redhat.parodos.workflow.task.infrastructure.Notifier; +import lombok.extern.slf4j.Slf4j; + +import org.springframework.beans.factory.annotation.Value; + +@Slf4j +@org.springframework.context.annotation.Configuration +public class NotificationSender implements Notifier { + + private final NotificationMessageApi client; + + public NotificationSender(@Value("${notification.url}") String url, + @Value("${notification.auth.basic.user}") String user, + @Value("${notification.auth.basic.password}") String password) { + ApiClient apiClient = new com.redhat.parodos.notification.sdk.api.ApiClient().setBasePath(url).addDefaultHeader( + HttpHeaders.AUTHORIZATION, + "Basic " + Base64.getEncoder().encodeToString((user + ":" + password).getBytes())); + client = new NotificationMessageApi(apiClient); + } + + public void send(NotificationMessageCreateRequestDTO message) { + try { + client.create(message); + } + catch (ApiException e) { + log.error("failed sending notification message due to: " + e); + } + } + +} diff --git a/workflow-service/src/main/resources/application-local.yml b/workflow-service/src/main/resources/application-local.yml index 7fc6da561..85e64ab9f 100644 --- a/workflow-service/src/main/resources/application-local.yml +++ b/workflow-service/src/main/resources/application-local.yml @@ -33,3 +33,13 @@ logging: # ApplicationProperties class to have type-safe configuration # =================================================================== +notification: + url: "${NOTIFICATION_SERVICE_URL:http://localhost:8081}" + auth: + basic: + user: test + password: test + +workflows: + mta: + url: "${MTA_URL:https://mta-openshift-mta.apps.parodos-dev.projects.ecosystem.sysdeseng.com}" \ No newline at end of file