diff --git a/demo-dep-output.yaml b/demo-dep-output.yaml index 656f10dd..742721ea 100644 --- a/demo-dep-output.yaml +++ b/demo-dep-output.yaml @@ -2473,3 +2473,12 @@ - konveyor.io/dep-source=open-source - konveyor.io/language=java prefix: file:///root/.m2/repository/org/yaml/snakeyaml/1.30 +- fileURI: io/javaoperatorsdk/operator/sample + provider: java + dependencies: + - name: io.javaoperatorsdk.operator.sample + version: 0.0.0 + labels: + - konveyor.io/dep-source=open-source + - konveyor.io/language=java + prefix: file://java-project/src/main/io/javaoperatorsdk/operator/sample diff --git a/demo-output.yaml b/demo-output.yaml index 35779750..1a8090dc 100644 --- a/demo-output.yaml +++ b/demo-output.yaml @@ -3,6 +3,7 @@ - Backend=Golang - Infra=Kubernetes - Java + - Java Operator SDK - Language=Golang - License=Apache violations: @@ -898,6 +899,30 @@ matchingXML: "" effort: 1 insights: + java-downloaded-maven-artifact: + description: | + This rule tests the application downloaded from maven artifact + labels: + - tag=Java Operator SDK + incidents: + - uri: file:///java-project/src/main/java/io/javaoperatorsdk/operator/sample/QuarkusOperator.java + message: "" + codeSnip: " 1 package io.javaoperatorsdk.operator.sample;\n 2 \n 3 import io.fabric8.kubernetes.client.KubernetesClient;\n 4 import io.javaoperatorsdk.operator.Operator;\n 5 import io.javaoperatorsdk.operator.api.config.ConfigurationService;\n 6 import io.javaoperatorsdk.operator.api.config.ControllerConfiguration;\n 7 import io.quarkus.runtime.Quarkus;\n 8 import io.quarkus.runtime.QuarkusApplication;\n 9 import io.quarkus.runtime.annotations.QuarkusMain;\n10 import javax.inject.Inject;\n11 \n12 @QuarkusMain\n13 public class QuarkusOperator implements QuarkusApplication {\n14 @Inject" + lineNumber: 4 + variables: + file: file:///java-project/src/main/java/io/javaoperatorsdk/operator/sample/QuarkusOperator.java + kind: Module + name: io.javaoperatorsdk.operator.Operator + package: io.javaoperatorsdk.operator.sample + - uri: file:///java-project/src/main/java/io/javaoperatorsdk/operator/sample/QuarkusOperator.java + message: "" + codeSnip: " 7 import io.quarkus.runtime.Quarkus;\n 8 import io.quarkus.runtime.QuarkusApplication;\n 9 import io.quarkus.runtime.annotations.QuarkusMain;\n10 import javax.inject.Inject;\n11 \n12 @QuarkusMain\n13 public class QuarkusOperator implements QuarkusApplication {\n14 @Inject\n15 KubernetesClient client;\n16 @Inject\n17 Operator operator;\n18 @Inject\n19 ConfigurationService configuration;\n20 @Inject\n21 CustomServiceController controller;\n22 \n23 public static void main(String... args) {\n24 Quarkus.run(QuarkusOperator.class, args);\n25 }\n26 \n27 public int run(String... args) throws Exception {" + lineNumber: 17 + variables: + file: file:///java-project/src/main/java/io/javaoperatorsdk/operator/sample/QuarkusOperator.java + kind: Field + name: operator + package: io.javaoperatorsdk.operator.sample multiple-actions-001: description: "" labels: @@ -1043,7 +1068,7 @@ errors: error-rule-001: |- unable to get query info: yaml: unmarshal errors: - line 9: cannot unmarshal !!map into string + line 10: cannot unmarshal !!map into string unmatched: - file-002 - lang-ref-002 diff --git a/docs/providers.md b/docs/providers.md index 96e58b5c..b58d7d0e 100644 --- a/docs/providers.md +++ b/docs/providers.md @@ -92,7 +92,7 @@ Here's an example config for `java` provider that is currently in-tree and does } ``` -The `location` can be a path to the application's source code or to a binary JAR, WAR, or EAR file. +The `location` can be a path to the application's source code or to a binary JAR, WAR, or EAR file. Optionally, coordinates to a maven artifact can be provided as input in the format `mvn://:::@`. The field `` is optional, it specifies a local path where the artifact will be downloaded. If not specified, provider will use the current working directory to download it. The `java` provider also takes following options in `providerSpecificConfig`: diff --git a/external-providers/java-external-provider/pkg/java_external_provider/provider.go b/external-providers/java-external-provider/pkg/java_external_provider/provider.go index b6bbe975..e329a63a 100644 --- a/external-providers/java-external-provider/pkg/java_external_provider/provider.go +++ b/external-providers/java-external-provider/pkg/java_external_provider/provider.go @@ -31,6 +31,7 @@ const ( WebArchive = ".war" EnterpriseArchive = ".ear" ClassFile = ".class" + MvnURIPrefix = "mvn://" ) // provider specific config keys @@ -220,6 +221,52 @@ func (p *javaProvider) Init(ctx context.Context, log logr.Logger, config provide var returnErr error // each service client should have their own context ctx, cancelFunc := context.WithCancel(ctx) + // location can be a coordinate to a remote mvn artifact + if strings.HasPrefix(config.Location, MvnURIPrefix) { + mvnUri := strings.Replace(config.Location, MvnURIPrefix, "", 1) + // URI format is :::@ + // is optional & points to a local path where it will be downloaded + mvnCoordinates, destPath, _ := strings.Cut(mvnUri, "@") + mvnCoordinatesParts := strings.Split(mvnCoordinates, ":") + if mvnCoordinates == "" || len(mvnCoordinatesParts) < 3 { + cancelFunc() + return nil, fmt.Errorf("invalid maven coordinates in location %s, must be in format mvn://:::@", config.Location) + } + outputDir := "." + if destPath != "" { + if stat, err := os.Stat(destPath); err != nil || !stat.IsDir() { + cancelFunc() + return nil, fmt.Errorf("output path does not exist or not a directory") + } + outputDir = destPath + } + mvnOptions := []string{ + "dependency:copy", + fmt.Sprintf("-Dartifact=%s", mvnCoordinates), + fmt.Sprintf("-DoutputDirectory=%s", outputDir), + } + if mavenSettingsFile != "" { + mvnOptions = append(mvnOptions, "-s", mavenSettingsFile) + } + log.Info("downloading maven artifact", "artifact", mvnCoordinates, "options", mvnOptions) + cmd := exec.CommandContext(ctx, "mvn", mvnOptions...) + cmd.Dir = outputDir + if err := cmd.Run(); err != nil { + cancelFunc() + return nil, fmt.Errorf("error downloading java artifact %s - %w", mvnUri, err) + } + downloadedPath := filepath.Join(outputDir, + fmt.Sprintf("%s.jar", strings.Join(mvnCoordinatesParts[1:3], "-"))) + if len(mvnCoordinatesParts) == 4 { + downloadedPath = filepath.Join(outputDir, + fmt.Sprintf("%s.%s", strings.Join(mvnCoordinatesParts[1:3], "-"), strings.ToLower(mvnCoordinatesParts[3]))) + } + if _, err := os.Stat(downloadedPath); err != nil { + return nil, fmt.Errorf("failed to download maven artifact to path %s - %w", downloadedPath, err) + } + config.Location = downloadedPath + } + extension := strings.ToLower(path.Ext(config.Location)) switch extension { case JavaArchive, WebArchive, EnterpriseArchive: diff --git a/provider_container_settings.json b/provider_container_settings.json index a296466d..d3f031e3 100644 --- a/provider_container_settings.json +++ b/provider_container_settings.json @@ -111,6 +111,16 @@ "bundles": "/jdtls/java-analyzer-bundle/java-analyzer-bundle.core/target/java-analyzer-bundle.core-1.0.0-SNAPSHOT.jar" }, "analysisMode": "source-only" + }, + { + "location": "mvn://io.javaoperatorsdk:quarkus:1.6.2:jar", + "providerSpecificConfig": { + "lspServerName": "java", + "lspServerPath": "/jdtls/bin/jdtls", + "depOpenSourceLabelsFile": "/usr/local/etc/maven.default.index", + "bundles": "/jdtls/java-analyzer-bundle/java-analyzer-bundle.core/target/java-analyzer-bundle.core-1.0.0-SNAPSHOT.jar" + }, + "analysisMode": "full" } ] }, diff --git a/provider_local_external_images.json b/provider_local_external_images.json index 3c23e8bf..1889001a 100644 --- a/provider_local_external_images.json +++ b/provider_local_external_images.json @@ -111,6 +111,16 @@ "bundles": "/jdtls/java-analyzer-bundle/java-analyzer-bundle.core/target/java-analyzer-bundle.core-1.0.0-SNAPSHOT.jar" }, "analysisMode": "source-only" + }, + { + "location": "mvn://io.javaoperatorsdk:quarkus:1.6.2:jar", + "providerSpecificConfig": { + "lspServerName": "java", + "lspServerPath": "/jdtls/bin/jdtls", + "depOpenSourceLabelsFile": "/usr/local/etc/maven.default.index", + "bundles": "/jdtls/java-analyzer-bundle/java-analyzer-bundle.core/target/java-analyzer-bundle.core-1.0.0-SNAPSHOT.jar" + }, + "analysisMode": "full" } ] }, diff --git a/provider_pod_local_settings.json b/provider_pod_local_settings.json index 0511fb09..116525f7 100644 --- a/provider_pod_local_settings.json +++ b/provider_pod_local_settings.json @@ -111,6 +111,16 @@ "bundles": "/jdtls/java-analyzer-bundle/java-analyzer-bundle.core/target/java-analyzer-bundle.core-1.0.0-SNAPSHOT.jar" }, "analysisMode": "source-only" + }, + { + "location": "mvn://io.javaoperatorsdk:quarkus:1.6.2:jar", + "providerSpecificConfig": { + "lspServerName": "java", + "lspServerPath": "/jdtls/bin/jdtls", + "depOpenSourceLabelsFile": "/usr/local/etc/maven.default.index", + "bundles": "/jdtls/java-analyzer-bundle/java-analyzer-bundle.core/target/java-analyzer-bundle.core-1.0.0-SNAPSHOT.jar" + }, + "analysisMode": "full" } ] }, diff --git a/rule-example.yaml b/rule-example.yaml index fc2cbf24..67bfb04c 100644 --- a/rule-example.yaml +++ b/rule-example.yaml @@ -321,3 +321,12 @@ when: java.referenced: pattern: com.sun.net.httpserver.HttpExchange +- category: mandatory + description: | + This rule tests the application downloaded from maven artifact + tag: + - Java Operator SDK + ruleID: java-downloaded-maven-artifact + when: + java.referenced: + pattern: io.javaoperatorsdk.operator.Operator \ No newline at end of file