This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch CAMEL-26931-bad-version in repository https://gitbox.apache.org/repos/asf/camel.git
commit 0c945b18a946e7782d55b53ad709d2d0d35b350e Author: Claus Ibsen <[email protected]> AuthorDate: Sat Nov 14 18:47:53 2026 +0100 CAMEL-26931: camel-jbang - a Camel version that does not exist says so and exits with 3 Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Claus Ibsen <[email protected]> --- .../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc | 9 ++ .../modules/ROOT/pages/camel-jbang-projects.adoc | 3 + .../jbang-commands/camel-jbang-version-set.adoc | 2 +- .../META-INF/camel-jbang-commands-metadata.json | 2 +- .../core/commands/catalog/CatalogBaseCommand.java | 18 ++++ .../jbang/core/commands/version/VersionSet.java | 30 +++++- .../dsl/jbang/core/common/CamelVersionCheck.java | 101 +++++++++++++++++++++ .../core/commands/version/VersionSetTest.java | 60 ++++++++++-- .../jbang/core/common/CamelVersionCheckTest.java | 83 +++++++++++++++++ .../src/test/resources/golden/cli-help.txt | 3 +- .../commands/validate/CatalogVersionMixin.java | 16 ++++ .../camel/dsl/jbang/run/common/CatalogLoader.java | 18 ++++ .../run/common/CatalogLoaderNotFoundTest.java | 86 ++++++++++++++++++ 13 files changed, 418 insertions(+), 13 deletions(-) diff --git a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc index 028422a474ff..383cdcf71eab 100644 --- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc +++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc @@ -1459,6 +1459,15 @@ with code `1` (fix the route) instead of `70` (internal error), and prints the e The stack trace is in the log of the integration, and is no longer printed a second time on stderr (add `--verbose` for it). Scripts that checked for `70` after a failed start should check for `1`. +=== camel-jbang - a Camel version that does not exist exits with 3 + +A `--camel-version` that was never released, in `camel catalog`, `camel doc` and `camel validate`, now says +`Camel version X not found in <repositories>` and exits with code `3` (not found) instead of `1` with "Cannot +resolve dependencies". A Maven repository that cannot be reached still exits with `1`. `camel version set` checks +that the version exists before it stores it: a version that does not exist exits with `3` and is not stored, and when +the repositories cannot be reached the version is stored with a warning. `camel version set` without a version, +`--runtime`, `--repo` or `--reset` is now a usage error (exit code `2`) instead of doing nothing. + === camel-resilience4j, camel-microprofile-fault-tolerance - a circuit breaker in a doTry A circuit breaker in the steps of a `doTry` no longer ends that `doTry` for the steps after it. Before, when the diff --git a/docs/user-manual/modules/ROOT/pages/camel-jbang-projects.adoc b/docs/user-manual/modules/ROOT/pages/camel-jbang-projects.adoc index 5b4820208a6b..8fbb4e52b56c 100644 --- a/docs/user-manual/modules/ROOT/pages/camel-jbang-projects.adoc +++ b/docs/user-manual/modules/ROOT/pages/camel-jbang-projects.adoc @@ -450,6 +450,9 @@ camel version set 3.18.2 camel version set --reset ---- +`camel version set` checks in the Maven repositories that the version exists, and refuses one that does not +(exit code 3). `--reset` removes the `camel-version`, `kamelets-version`, `repos` and `runtime` settings. + IMPORTANT: You cannot combine `camel version set` with `--camel-version`. For SNAPSHOT versions: diff --git a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-version-set.adoc b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-version-set.adoc index c9b5c7fe4759..dfebcca7e399 100644 --- a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-version-set.adoc +++ b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-version-set.adoc @@ -21,7 +21,7 @@ camel version set [options] | Option | Description | Default | Type | `--global` | Use global or local configuration | | boolean | `--repo,--repos` | Maven repository for downloading the dependencies (Use commas to separate multiple repositories) | | String -| `--reset` | Reset by removing any custom version settings | | boolean +| `--reset` | Reset by removing the version settings: camel-version, kamelets-version, repos and runtime | | boolean | `--runtime` | Runtime (camel-main, spring-boot, quarkus) | | RuntimeType | `-h,--help` | Display this help and exit | | boolean |=== diff --git a/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json b/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json index a41581312714..30ad2d68876e 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json +++ b/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json @@ -38,7 +38,7 @@ { "name": "trace", "fullName": "trace", "description": "Tail message traces from running Camel integrations", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.action.CamelTraceAction", "options": [ { "names": "--action", "description": "Action to start, stop, clear, list status, or dump traces", "defaultValue": "status", "javaType": "java.lang.String", "type": "string" }, { "names": "--ago", "description": "Use ago instead of yyyy-MM-dd HH:mm:ss in timestamp.", "javaType": "b [...] { "name": "transform", "fullName": "transform", "description": "Transform message or Camel routes", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.TransformCommand", "options": [ { "names": "-h,--help", "description": "Display this help and exit", "javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name": "dataweave", "fullName": "transform dataweave", "description": "Convert DataWeave scripts to DataSonnet format", "sourceClass": "org.apache.camel.dsl.jbang.c [...] { "name": "update", "fullName": "update", "description": "Update Camel project", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.update.UpdateCommand", "options": [ { "names": "-h,--help", "description": "Display this help and exit", "javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name": "list", "fullName": "update list", "description": "List available update versions for Camel and its runtime variants", "sourceClass": "org.apache.camel.dsl.jbang.core.comma [...] - { "name": "version", "fullName": "version", "description": "Manage Camel versions", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.version.VersionCommand", "options": [ { "names": "-h,--help", "description": "Display this help and exit", "javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name": "get", "fullName": "version get", "description": "Displays current Camel version", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.version.VersionGet", "optio [...] + { "name": "version", "fullName": "version", "description": "Manage Camel versions", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.version.VersionCommand", "options": [ { "names": "-h,--help", "description": "Display this help and exit", "javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name": "get", "fullName": "version get", "description": "Displays current Camel version", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.version.VersionGet", "optio [...] { "name": "wrapper", "fullName": "wrapper", "description": "Install Camel wrapper scripts for version pinning", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.WrapperCommand", "options": [ { "names": "--camel-version", "description": "Camel version to pin (defaults to current version)", "javaType": "java.lang.String", "type": "string" }, { "names": "--command-name", "description": "The name of the command to use (camel or camelw)", "defaultValue": "camel", "javaType": "java [...] ] } diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/catalog/CatalogBaseCommand.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/catalog/CatalogBaseCommand.java index 6c720d18ec5c..8748d1919136 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/catalog/CatalogBaseCommand.java +++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/catalog/CatalogBaseCommand.java @@ -35,6 +35,8 @@ import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain; import org.apache.camel.dsl.jbang.core.commands.MavenResolverMixin; import org.apache.camel.dsl.jbang.core.commands.QuarkusPlatformMixin; import org.apache.camel.dsl.jbang.core.common.CamelTableColumns; +import org.apache.camel.dsl.jbang.core.common.CamelVersionCheck; +import org.apache.camel.dsl.jbang.core.common.CliException; import org.apache.camel.dsl.jbang.core.common.RowSort; import org.apache.camel.dsl.jbang.core.common.RuntimeCompletionCandidates; import org.apache.camel.dsl.jbang.core.common.RuntimeTypeConverter; @@ -43,6 +45,7 @@ import org.apache.camel.dsl.jbang.core.model.CatalogBaseDTO; import org.apache.camel.dsl.jbang.run.common.CatalogLoader; import org.apache.camel.dsl.jbang.run.common.RuntimeType; import org.apache.camel.dsl.jbang.run.common.VersionHelper; +import org.apache.camel.main.download.DownloadException; import org.apache.camel.main.util.SuggestSimilarHelper; import org.apache.camel.tooling.maven.MavenGav; import org.apache.camel.tooling.model.ArtifactModel; @@ -116,6 +119,21 @@ public abstract class CatalogBaseCommand extends CamelCommand { static CamelCatalog loadCatalog( RuntimeType runtime, String camelVersion, QuarkusPlatformMixin quarkusPlatform, MavenResolverMixin mavenResolver) throws Exception { + try { + return doLoadCatalog(runtime, camelVersion, quarkusPlatform, mavenResolver); + } catch (DownloadException e) { + // a version that was never released says so, and is not found (CAMEL-26931) + CliException notFound = CamelVersionCheck.notFound(e, runtime, camelVersion); + if (notFound != null) { + throw notFound; + } + throw e; + } + } + + private static CamelCatalog doLoadCatalog( + RuntimeType runtime, String camelVersion, QuarkusPlatformMixin quarkusPlatform, MavenResolverMixin mavenResolver) + throws Exception { if (RuntimeType.springBoot == runtime) { return CatalogLoader.loadSpringBootCatalog(mavenResolver.repos(), camelVersion, mavenResolver.download()); } else if (RuntimeType.quarkus == runtime) { diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/version/VersionSet.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/version/VersionSet.java index f006d8ebced9..51785631cc30 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/version/VersionSet.java +++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/version/VersionSet.java @@ -16,11 +16,14 @@ */ package org.apache.camel.dsl.jbang.core.commands.version; +import java.util.function.BiFunction; + import org.aesh.command.CommandDefinition; import org.aesh.command.option.Argument; import org.aesh.command.option.Option; import org.apache.camel.dsl.jbang.core.commands.CamelCommand; import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain; +import org.apache.camel.dsl.jbang.core.common.CamelVersionCheck; import org.apache.camel.dsl.jbang.core.common.CliExitCode; import org.apache.camel.dsl.jbang.core.common.CommandLineHelper; import org.apache.camel.dsl.jbang.core.common.RuntimeCompletionCandidates; @@ -46,12 +49,16 @@ public class VersionSet extends CamelCommand { description = "Maven repository for downloading the dependencies (Use commas to separate multiple repositories)") String repo; - @Option(name = "reset", hasValue = false, description = "Reset by removing any custom version settings") + @Option(name = "reset", hasValue = false, + description = "Reset by removing the version settings: camel-version, kamelets-version, repos and runtime") boolean reset; @Option(name = "global", hasValue = false, description = "Use global or local configuration") boolean global = true; + /** Whether a version exists, given the version and the extra repositories: true, false, or null when unknown. */ + BiFunction<String, String, Boolean> versionCheck = CamelVersionCheck::exists; + public VersionSet(CamelJBangMain main) { super(main); } @@ -59,6 +66,27 @@ public class VersionSet extends CamelCommand { @Override public Integer doCall() throws Exception { boolean local = !global; + if (!reset && version == null && repo == null && runtime == null) { + printer().printErr("Give the Camel version to set, --runtime, --repo, or --reset"); + return CliExitCode.USAGE; + } + if (!reset && version != null) { + // a version that was never released fails every command that uses it later (CAMEL-26931) + String repos = repo; + if (repos == null) { + String[] stored = new String[1]; + CommandLineHelper.loadProperties(p -> stored[0] = p.getProperty("repos"), local, printer()); + repos = stored[0]; + } + Boolean exists = versionCheck.apply(version, repos); + if (Boolean.FALSE.equals(exists)) { + printer().printErr("Camel version " + version + " not found (camel version list shows the released versions)"); + return CliExitCode.NOT_FOUND; + } else if (exists == null) { + printer().printDiagnostic("Cannot check that Camel version " + version + + " exists: the Maven repositories cannot be reached"); + } + } CommandLineHelper.createPropertyFile(local); boolean ok = CommandLineHelper.loadProperties(properties -> { diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/CamelVersionCheck.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/CamelVersionCheck.java new file mode 100644 index 000000000000..e3a79ca1746b --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/CamelVersionCheck.java @@ -0,0 +1,101 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.dsl.jbang.core.common; + +import java.io.IOException; +import java.util.List; +import java.util.Set; + +import org.apache.camel.dsl.jbang.run.common.CatalogLoader; +import org.apache.camel.dsl.jbang.run.common.RuntimeType; +import org.apache.camel.main.download.DownloadException; +import org.apache.camel.main.download.MavenDependencyDownloader; +import org.apache.camel.tooling.maven.MavenResolutionException; + +/** + * Tells a Camel version that does not exist from a Maven repository that cannot be reached, so a wrong + * {@code --camel-version} says so and exits with {@link CliExitCode#NOT_FOUND} (CAMEL-26931). + */ +public final class CamelVersionCheck { + + private CamelVersionCheck() { + } + + /** + * The error for a {@code --camel-version} that the Maven repositories say does not exist, or null when the + * exception is something else. With the Quarkus runtime the version picks a platform, which reports its own errors. + * + * @param e the exception of loading the catalog of the version + * @param runtime the runtime, or null for Camel Main + * @param version the version, or null for the version of the CLI + */ + public static CliException notFound(Throwable e, RuntimeType runtime, String version) { + if (RuntimeType.quarkus == runtime) { + return null; + } + return notFound(e, RuntimeType.springBoot == runtime ? "Camel Spring Boot" : "Camel", version); + } + + /** + * The error for a version that the Maven repositories say does not exist, or null when the exception is something + * else, such as a repository that cannot be reached. + * + * @param e the exception of loading the artifacts of the version + * @param what what the version is of, such as {@code Camel} or {@code Camel Spring Boot} + * @param version the version + */ + private static CliException notFound(Throwable e, String what, String version) { + if (version == null || !CatalogLoader.isArtifactNotFound(e)) { + return null; + } + String repos = null; + for (Throwable c = e; c != null && repos == null; c = c.getCause()) { + if (c instanceof MavenResolutionException mre && mre.getRepositories() != null + && !mre.getRepositories().isEmpty()) { + repos = String.join(", ", mre.getRepositories()); + } + } + return new CliException( + CliExitCode.NOT_FOUND, + what + " version " + version + " not found" + (repos != null ? " in " + repos : "") + + " (camel version list shows the released versions)", + e); + } + + /** + * Whether the Camel version exists: {@code true} or {@code false} as the Maven repositories answer, or null when + * they cannot be reached. + * + * @param version the Camel version + * @param repos extra Maven repositories (comma separated), or null + */ + public static Boolean exists(String version, String repos) { + try (MavenDependencyDownloader downloader = new MavenDependencyDownloader()) { + downloader.setRepositories(repos); + downloader.setDownload(true); + downloader.start(); + downloader.resolveDependenciesViaAether(List.of("org.apache.camel:camel-api:pom:" + version), Set.of(), + false, version.endsWith("-SNAPSHOT")); + return true; + } catch (DownloadException e) { + return CatalogLoader.isArtifactNotFound(e) ? false : null; + } catch (IOException e) { + // closing the downloader; the answer is known already + return true; + } + } +} diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/version/VersionSetTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/version/VersionSetTest.java index 5977c7e094bd..d32df6cf2591 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/version/VersionSetTest.java +++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/version/VersionSetTest.java @@ -44,7 +44,7 @@ class VersionSetTest extends BaseConfigTestSupport { void shouldStoreVersionInGlobalConfig() throws Exception { UserConfigHelper.createUserConfig(""); - VersionSet command = new VersionSet(new CamelJBangMain().withPrinter(printer)); + VersionSet command = command(); command.version = "4.10.0"; command.doCall(); @@ -60,7 +60,7 @@ class VersionSetTest extends BaseConfigTestSupport { void shouldStoreVersionRepoAndRuntimeTogether() throws Exception { UserConfigHelper.createUserConfig(""); - VersionSet command = new VersionSet(new CamelJBangMain().withPrinter(printer)); + VersionSet command = command(); command.version = "4.10.0"; command.repo = "https://repo.example.org/maven"; command.runtime = RuntimeType.springBoot; @@ -75,18 +75,55 @@ class VersionSetTest extends BaseConfigTestSupport { } @Test - void shouldLeaveExistingConfigUntouchedWhenNothingProvided() throws Exception { + void shouldRefuseACallWithNothingToSet() throws Exception { UserConfigHelper.createUserConfig(""" camel-version=4.9.0 """); - VersionSet command = new VersionSet(new CamelJBangMain().withPrinter(printer)); - // no version, repo, runtime or reset: doCall must not change the stored value - command.doCall(); + VersionSet command = command(); + // no version, repo, runtime or reset: it was a silent no-op (CAMEL-26931) + assertEquals(CliExitCode.USAGE, command.doCall()); + assertTrue(printer.getStderr().contains("Give the Camel version to set"), printer.getOutput()); + + CommandLineHelper.loadProperties(properties -> assertEquals("4.9.0", properties.get("camel-version"))); + } + + @Test + void shouldRefuseAVersionThatDoesNotExist() throws Exception { + UserConfigHelper.createUserConfig(""" + camel-version=4.9.0 + """); + + VersionSet command = command(); + command.version = "9.9.9"; + command.versionCheck = (version, repos) -> false; + assertEquals(CliExitCode.NOT_FOUND, command.doCall()); + assertTrue(printer.getStderr().contains("Camel version 9.9.9 not found"), printer.getOutput()); CommandLineHelper.loadProperties(properties -> assertEquals("4.9.0", properties.get("camel-version"))); } + @Test + void shouldSetAVersionThatCannotBeCheckedWithAWarning() throws Exception { + UserConfigHelper.createUserConfig(""" + repos=https://repo.example.org/maven + """); + + String[] asked = new String[1]; + VersionSet command = command(); + command.version = "4.10.0"; + command.versionCheck = (version, repos) -> { + asked[0] = repos; + return null; + }; + assertEquals(CliExitCode.OK, command.doCall()); + assertTrue(printer.getStderr().contains("Cannot check that Camel version 4.10.0 exists"), printer.getOutput()); + // the check asks the repositories that are set already + assertEquals("https://repo.example.org/maven", asked[0]); + + CommandLineHelper.loadProperties(properties -> assertEquals("4.10.0", properties.get("camel-version"))); + } + @Test void shouldRemoveAllSettingsOnReset() throws Exception { UserConfigHelper.createUserConfig(""" @@ -97,7 +134,7 @@ class VersionSetTest extends BaseConfigTestSupport { other=keep-me """); - VersionSet command = new VersionSet(new CamelJBangMain().withPrinter(printer)); + VersionSet command = command(); command.reset = true; command.doCall(); @@ -113,7 +150,7 @@ class VersionSetTest extends BaseConfigTestSupport { @Test void shouldStoreVersionInLocalConfigWhenNotGlobal() throws Exception { - VersionSet command = new VersionSet(new CamelJBangMain().withPrinter(printer)); + VersionSet command = command(); command.version = "4.10.0"; command.global = false; command.doCall(); @@ -131,7 +168,7 @@ class VersionSetTest extends BaseConfigTestSupport { String originalContent = Files.readString( CommandLineHelper.getHomeDir().resolve(CommandLineHelper.USER_CONFIG)); - VersionSet command = new VersionSet(new CamelJBangMain().withPrinter(printer)); + VersionSet command = command(); command.version = "4.10.0"; Integer exitCode = command.doCall(); @@ -142,4 +179,9 @@ class VersionSetTest extends BaseConfigTestSupport { assertEquals(originalContent, afterContent, "malformed config file must not be overwritten"); } + private VersionSet command() { + VersionSet command = new VersionSet(new CamelJBangMain().withPrinter(printer)); + command.versionCheck = (version, repos) -> true; + return command; + } } diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/common/CamelVersionCheckTest.java b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/common/CamelVersionCheckTest.java new file mode 100644 index 000000000000..e23c6cd7c838 --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/common/CamelVersionCheckTest.java @@ -0,0 +1,83 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.dsl.jbang.core.common; + +import java.net.ConnectException; +import java.util.List; + +import org.apache.camel.dsl.jbang.run.common.RuntimeType; +import org.apache.camel.main.download.DownloadException; +import org.apache.camel.tooling.maven.MavenResolutionException; +import org.eclipse.aether.artifact.DefaultArtifact; +import org.eclipse.aether.repository.RemoteRepository; +import org.eclipse.aether.resolution.ArtifactRequest; +import org.eclipse.aether.resolution.ArtifactResolutionException; +import org.eclipse.aether.resolution.ArtifactResult; +import org.eclipse.aether.transfer.ArtifactNotFoundException; +import org.eclipse.aether.transfer.ArtifactTransferException; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +/** + * A {@code --camel-version} that was never released says so and exits with 3, instead of "Cannot resolve dependencies" + * with exit 1 (CAMEL-26931). + */ +class CamelVersionCheckTest { + + private static final DefaultArtifact CATALOG = new DefaultArtifact("org.apache.camel:camel-core-catalog:9.9.9"); + private static final RemoteRepository CENTRAL + = new RemoteRepository.Builder("central", "default", "https://repo1.maven.org/maven2").build(); + + @Test + void aVersionThatWasNeverReleasedIsNotFound() { + CliException e = CamelVersionCheck.notFound(download(new ArtifactNotFoundException(CATALOG, CENTRAL)), null, "9.9.9"); + assertEquals(CliExitCode.NOT_FOUND, e.getExitCode()); + assertEquals("Camel version 9.9.9 not found in https://repo1.maven.org/maven2" + + " (camel version list shows the released versions)", + e.getMessage()); + + CliException sb = CamelVersionCheck.notFound( + download(new ArtifactNotFoundException(CATALOG, CENTRAL)), RuntimeType.springBoot, "9.9.9"); + assertEquals("Camel Spring Boot version 9.9.9 not found in https://repo1.maven.org/maven2" + + " (camel version list shows the released versions)", + sb.getMessage()); + } + + @Test + void aRepositoryThatCannotBeReachedKeepsItsError() { + Exception refused = new ArtifactTransferException(CATALOG, CENTRAL, new ConnectException("Connection refused")); + assertNull(CamelVersionCheck.notFound(download(refused), null, "9.9.9")); + } + + @Test + void theQuarkusPlatformReportsItsOwnErrors() { + assertNull(CamelVersionCheck.notFound( + download(new ArtifactNotFoundException(CATALOG, CENTRAL)), RuntimeType.quarkus, "9.9.9")); + } + + /** As {@code MavenDependencyDownloader} throws it: the resolver error in a MavenResolutionException. */ + private static DownloadException download(Exception error) { + ArtifactResult result = new ArtifactResult(new ArtifactRequest(CATALOG, List.of(CENTRAL), null)); + result.addException(error); + ArtifactResolutionException are = new ArtifactResolutionException(List.of(result)); + MavenResolutionException mre = new MavenResolutionException(are.getMessage(), are); + mre.getRepositories().add("https://repo1.maven.org/maven2"); + return new DownloadException("Cannot resolve dependencies in https://repo1.maven.org/maven2", mre); + } +} diff --git a/dsl/camel-jbang/camel-jbang-core/src/test/resources/golden/cli-help.txt b/dsl/camel-jbang/camel-jbang-core/src/test/resources/golden/cli-help.txt index 6f71ecd415c2..b1f57ff37e32 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/test/resources/golden/cli-help.txt +++ b/dsl/camel-jbang/camel-jbang-core/src/test/resources/golden/cli-help.txt @@ -6799,7 +6799,8 @@ Options: --repo, --repos=<repo> Maven repository for downloading the dependencies (Use commas to separate multiple repositories) - --reset Reset by removing any custom version settings + --reset Reset by removing the version settings: + camel-version, kamelets-version, repos and runtime --global Use global or local configuration <version> Camel version diff --git a/dsl/camel-jbang/camel-jbang-plugin-validate/src/main/java/org/apache/camel/dsl/jbang/core/commands/validate/CatalogVersionMixin.java b/dsl/camel-jbang/camel-jbang-plugin-validate/src/main/java/org/apache/camel/dsl/jbang/core/commands/validate/CatalogVersionMixin.java index e4ded1a62655..4f23380608c9 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-validate/src/main/java/org/apache/camel/dsl/jbang/core/commands/validate/CatalogVersionMixin.java +++ b/dsl/camel-jbang/camel-jbang-plugin-validate/src/main/java/org/apache/camel/dsl/jbang/core/commands/validate/CatalogVersionMixin.java @@ -22,12 +22,15 @@ import org.apache.camel.catalog.CamelCatalog; import org.apache.camel.catalog.DefaultCamelCatalog; import org.apache.camel.dsl.jbang.core.commands.MavenResolverMixin; import org.apache.camel.dsl.jbang.core.commands.QuarkusPlatformMixin; +import org.apache.camel.dsl.jbang.core.common.CamelVersionCheck; +import org.apache.camel.dsl.jbang.core.common.CliException; import org.apache.camel.dsl.jbang.core.common.RuntimeCompletionCandidates; import org.apache.camel.dsl.jbang.core.common.RuntimeTypeConverter; import org.apache.camel.dsl.jbang.run.common.CatalogLoader; import org.apache.camel.dsl.jbang.run.common.QuarkusHelper; import org.apache.camel.dsl.jbang.run.common.RuntimeType; import org.apache.camel.dsl.yaml.validator.YamlValidator; +import org.apache.camel.main.download.DownloadException; /** * The Camel version and runtime a validation answers for (CAMEL-24711): the catalog of that version and runtime, as @@ -60,6 +63,19 @@ public class CatalogVersionMixin { * Loads the catalog of the version and runtime asked for; the built-in catalog when none is. */ public Loaded load() throws Exception { + try { + return doLoad(); + } catch (DownloadException e) { + // a version that was never released says so, and is not found (CAMEL-26931) + CliException notFound = CamelVersionCheck.notFound(e, runtime, camelVersion); + if (notFound != null) { + throw notFound; + } + throw e; + } + } + + private Loaded doLoad() throws Exception { if (RuntimeType.springBoot == runtime) { String version = camelVersion != null ? camelVersion : new DefaultCamelCatalog().getCatalogVersion(); return new Loaded( diff --git a/dsl/camel-jbang/camel-jbang-run/src/main/java/org/apache/camel/dsl/jbang/run/common/CatalogLoader.java b/dsl/camel-jbang/camel-jbang-run/src/main/java/org/apache/camel/dsl/jbang/run/common/CatalogLoader.java index 7cb1acb0ba7d..37dd38d43cf8 100644 --- a/dsl/camel-jbang/camel-jbang-run/src/main/java/org/apache/camel/dsl/jbang/run/common/CatalogLoader.java +++ b/dsl/camel-jbang/camel-jbang-run/src/main/java/org/apache/camel/dsl/jbang/run/common/CatalogLoader.java @@ -43,6 +43,9 @@ import org.apache.camel.main.download.MavenDependencyDownloader; import org.apache.camel.support.ObjectHelper; import org.apache.camel.tooling.maven.MavenArtifact; import org.apache.camel.tooling.maven.MavenGav; +import org.eclipse.aether.resolution.ArtifactResolutionException; +import org.eclipse.aether.transfer.ArtifactNotFoundException; +import org.eclipse.aether.transfer.RepositoryOfflineException; public final class CatalogLoader { @@ -53,6 +56,21 @@ public final class CatalogLoader { private CatalogLoader() { } + /** + * Whether the Maven repositories answered that the artifact does not exist, such as a Camel version that was never + * released. It is false when a repository could not be reached, or is offline and has not got the artifact. + */ + public static boolean isArtifactNotFound(Throwable e) { + for (Throwable c = e; c != null; c = c.getCause()) { + if (c instanceof ArtifactResolutionException are) { + List<Exception> errors = are.getResults().stream().flatMap(r -> r.getExceptions().stream()).toList(); + return !errors.isEmpty() && errors.stream().allMatch( + x -> x instanceof ArtifactNotFoundException && !(x.getCause() instanceof RepositoryOfflineException)); + } + } + return false; + } + public static CamelCatalog loadCatalog(String repos, String version, boolean download) throws Exception { return loadCatalog(repos, version, null, download); } diff --git a/dsl/camel-jbang/camel-jbang-run/src/test/java/org/apache/camel/dsl/jbang/run/common/CatalogLoaderNotFoundTest.java b/dsl/camel-jbang/camel-jbang-run/src/test/java/org/apache/camel/dsl/jbang/run/common/CatalogLoaderNotFoundTest.java new file mode 100644 index 000000000000..552e6cad4c7e --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-run/src/test/java/org/apache/camel/dsl/jbang/run/common/CatalogLoaderNotFoundTest.java @@ -0,0 +1,86 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.dsl.jbang.run.common; + +import java.net.ConnectException; +import java.util.List; + +import org.apache.camel.main.download.DownloadException; +import org.apache.camel.tooling.maven.MavenResolutionException; +import org.eclipse.aether.artifact.DefaultArtifact; +import org.eclipse.aether.repository.RemoteRepository; +import org.eclipse.aether.resolution.ArtifactRequest; +import org.eclipse.aether.resolution.ArtifactResolutionException; +import org.eclipse.aether.resolution.ArtifactResult; +import org.eclipse.aether.transfer.ArtifactNotFoundException; +import org.eclipse.aether.transfer.ArtifactTransferException; +import org.eclipse.aether.transfer.RepositoryOfflineException; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * {@link CatalogLoader#isArtifactNotFound(Throwable)} tells a version that does not exist from a repository that cannot + * be reached (CAMEL-26931). + */ +class CatalogLoaderNotFoundTest { + + private static final DefaultArtifact CATALOG = new DefaultArtifact("org.apache.camel:camel-core-catalog:9.9.9"); + private static final RemoteRepository CENTRAL + = new RemoteRepository.Builder("central", "default", "https://repo1.maven.org/maven2").build(); + private static final RemoteRepository CUSTOM + = new RemoteRepository.Builder("custom1", "default", "http://127.0.0.1:9").build(); + + @Test + void aVersionNoRepositoryHasIsNotFound() { + assertThat(CatalogLoader.isArtifactNotFound(download(new ArtifactNotFoundException(CATALOG, CENTRAL)))).isTrue(); + } + + @Test + void aRepositoryThatCannotBeReachedIsNotANotFound() { + Exception refused = new ArtifactTransferException(CATALOG, CUSTOM, new ConnectException("Connection refused")); + assertThat(CatalogLoader.isArtifactNotFound(download(refused))).isFalse(); + // central has not got it, but the other repository was not asked + assertThat(CatalogLoader.isArtifactNotFound(download(new ArtifactNotFoundException(CATALOG, CENTRAL), refused))) + .isFalse(); + } + + @Test + void anOfflineRepositoryIsNotANotFound() { + Exception offline = new ArtifactNotFoundException( + CATALOG, CENTRAL, "Cannot access central in offline mode", new RepositoryOfflineException(CENTRAL)); + assertThat(CatalogLoader.isArtifactNotFound(download(offline))).isFalse(); + } + + @Test + void anotherErrorIsNotANotFound() { + assertThat(CatalogLoader.isArtifactNotFound(new DownloadException("Unknown error"))).isFalse(); + assertThat(CatalogLoader.isArtifactNotFound(null)).isFalse(); + } + + /** As {@code MavenDependencyDownloader} throws it: the resolver error in a MavenResolutionException. */ + private static DownloadException download(Exception... errors) { + ArtifactResult result = new ArtifactResult(new ArtifactRequest(CATALOG, List.of(CENTRAL, CUSTOM), null)); + for (Exception e : errors) { + result.addException(e); + } + ArtifactResolutionException are = new ArtifactResolutionException(List.of(result)); + MavenResolutionException mre = new MavenResolutionException(are.getMessage(), are); + mre.getRepositories().add("https://repo1.maven.org/maven2"); + return new DownloadException("Cannot resolve dependencies in https://repo1.maven.org/maven2", mre); + } +}
