This is an automated email from the ASF dual-hosted git repository. gfournier pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel.git
commit 08135353a081115bf4df59db49616ebd501613cb Author: Gaelle Fournier <[email protected]> AuthorDate: Wed Sep 25 15:50:57 2024 +0200 CAMEL-21152: Adapt camel jbang plugin k to the modifications in camel jbang kubernetes plugin --- .../camel/dsl/jbang/core/commands/k/Bind.java | 5 +- .../jbang/core/commands/k/IntegrationExport.java | 11 +- .../dsl/jbang/core/commands/k/IntegrationRun.java | 27 +- .../core/commands/k/IntegrationTraitHelper.java | 314 +++++++++++++++++++++ 4 files changed, 338 insertions(+), 19 deletions(-) diff --git a/dsl/camel-jbang/camel-jbang-plugin-k/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/Bind.java b/dsl/camel-jbang/camel-jbang-plugin-k/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/Bind.java index e7405bf04dc..b5a0f8fd381 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-k/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/Bind.java +++ b/dsl/camel-jbang/camel-jbang-plugin-k/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/Bind.java @@ -27,7 +27,6 @@ import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain; import org.apache.camel.dsl.jbang.core.commands.bind.TemplateProvider; import org.apache.camel.dsl.jbang.core.commands.kubernetes.KubernetesBaseCommand; import org.apache.camel.dsl.jbang.core.commands.kubernetes.KubernetesHelper; -import org.apache.camel.dsl.jbang.core.commands.kubernetes.traits.TraitHelper; import org.apache.camel.util.ObjectHelper; import org.apache.camel.v1.Pipe; import org.apache.camel.v1.integrationspec.Traits; @@ -163,14 +162,14 @@ public class Bind extends KubernetesBaseCommand { String integrationSpec = ""; Traits traitsSpec = null; if (traits != null && traits.length > 0) { - traitsSpec = TraitHelper.parseTraits(traits); + traitsSpec = IntegrationTraitHelper.parseTraits(traits); } if (connects != null) { if (traitsSpec == null) { traitsSpec = new Traits(); } - TraitHelper.configureConnects(traitsSpec, connects); + IntegrationTraitHelper.configureConnects(traitsSpec, connects); } if (traitsSpec != null) { diff --git a/dsl/camel-jbang/camel-jbang-plugin-k/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationExport.java b/dsl/camel-jbang/camel-jbang-plugin-k/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationExport.java index 4e8f41f681f..7d32e93e138 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-k/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationExport.java +++ b/dsl/camel-jbang/camel-jbang-plugin-k/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationExport.java @@ -27,7 +27,6 @@ import org.apache.camel.dsl.jbang.core.common.Source; import org.apache.camel.dsl.jbang.core.common.SourceHelper; import org.apache.camel.v1.Integration; import org.apache.camel.v1.Pipe; -import org.apache.camel.v1.integrationspec.Traits; import picocli.CommandLine; @CommandLine.Command(name = "export", @@ -100,16 +99,20 @@ public class IntegrationExport extends KubernetesExport { } @Override - protected Traits getTraitSpec(String[] applicationProperties, String[] applicationProfileProperties) { + protected org.apache.camel.dsl.jbang.core.commands.kubernetes.traits.model.Traits getTraitSpec( + String[] applicationProperties, String[] applicationProfileProperties) { if (integration != null && integration.getSpec().getTraits() != null) { - return integration.getSpec().getTraits(); + return KubernetesHelper.yaml(this.getClass().getClassLoader()) + .loadAs(KubernetesHelper.dumpYaml(integration.getSpec().getTraits()), + org.apache.camel.dsl.jbang.core.commands.kubernetes.traits.model.Traits.class); } if (pipe != null && pipe.getSpec().getIntegration() != null && pipe.getSpec().getIntegration().getTraits() != null) { // convert pipe spec traits to integration spec traits return KubernetesHelper.yaml(this.getClass().getClassLoader()) - .loadAs(KubernetesHelper.dumpYaml(pipe.getSpec().getIntegration().getTraits()), Traits.class); + .loadAs(KubernetesHelper.dumpYaml(pipe.getSpec().getIntegration().getTraits()), + org.apache.camel.dsl.jbang.core.commands.kubernetes.traits.model.Traits.class); } return super.getTraitSpec(applicationProperties, applicationProfileProperties); diff --git a/dsl/camel-jbang/camel-jbang-plugin-k/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationRun.java b/dsl/camel-jbang/camel-jbang-plugin-k/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationRun.java index 1bae50309a7..45b36af7ede 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-k/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationRun.java +++ b/dsl/camel-jbang/camel-jbang-plugin-k/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationRun.java @@ -35,7 +35,6 @@ import org.apache.camel.dsl.jbang.core.commands.kubernetes.KubernetesBaseCommand import org.apache.camel.dsl.jbang.core.commands.kubernetes.KubernetesHelper; import org.apache.camel.dsl.jbang.core.commands.kubernetes.traits.TraitCatalog; import org.apache.camel.dsl.jbang.core.commands.kubernetes.traits.TraitContext; -import org.apache.camel.dsl.jbang.core.commands.kubernetes.traits.TraitHelper; import org.apache.camel.dsl.jbang.core.commands.kubernetes.traits.TraitProfile; import org.apache.camel.dsl.jbang.core.common.JSonHelper; import org.apache.camel.dsl.jbang.core.common.Printer; @@ -244,10 +243,10 @@ public class IntegrationRun extends KubernetesBaseCommand { .collect(Collectors.toMap(it -> it[0].trim(), it -> it[1].trim()))); } - Traits traitsSpec = TraitHelper.parseTraits(traits); + Traits traitsSpec = IntegrationTraitHelper.parseTraits(traits); if (image != null) { - TraitHelper.configureContainerImage(traitsSpec, image, null, null, null, null); + IntegrationTraitHelper.configureContainerImage(traitsSpec, image, null, null, null, null); } else { List<Source> resolvedSources = SourceHelper.resolveSources(integrationSources, compression); @@ -314,10 +313,14 @@ public class IntegrationRun extends KubernetesBaseCommand { List<Source> sources = SourceHelper.resolveSources(integrationSources); TraitContext context = new TraitContext(integration.getMetadata().getName(), "1.0-SNAPSHOT", printer(), sources); - TraitHelper.configureContainerImage(traitsSpec, image, "quay.io", null, integration.getMetadata().getName(), + IntegrationTraitHelper.configureContainerImage(traitsSpec, image, "quay.io", null, + integration.getMetadata().getName(), "1.0-SNAPSHOT"); - - new TraitCatalog().apply(traitsSpec, context, traitProfile); + org.apache.camel.dsl.jbang.core.commands.kubernetes.traits.model.Traits kubernetesTraits + = KubernetesHelper.yaml(this.getClass().getClassLoader()) + .loadAs(KubernetesHelper.dumpYaml(traits), + org.apache.camel.dsl.jbang.core.commands.kubernetes.traits.model.Traits.class); + new TraitCatalog().apply(kubernetesTraits, context, traitProfile); printer().println( context.buildItems().stream().map(KubernetesHelper::dumpYaml).collect(Collectors.joining("---"))); @@ -362,14 +365,14 @@ public class IntegrationRun extends KubernetesBaseCommand { } private void convertOptionsToTraits(Traits traitsSpec) { - TraitHelper.configureMountTrait(traitsSpec, configs, resources, volumes); + IntegrationTraitHelper.configureMountTrait(traitsSpec, configs, resources, volumes); if (openApis != null) { - Stream.of(openApis).forEach(openapi -> TraitHelper.configureOpenApiSpec(traitsSpec, openapi)); + Stream.of(openApis).forEach(openapi -> IntegrationTraitHelper.configureOpenApiSpec(traitsSpec, openapi)); } - TraitHelper.configureProperties(traitsSpec, properties); - TraitHelper.configureBuildProperties(traitsSpec, buildProperties); - TraitHelper.configureEnvVars(traitsSpec, envVars); - TraitHelper.configureConnects(traitsSpec, connects); + IntegrationTraitHelper.configureProperties(traitsSpec, properties); + IntegrationTraitHelper.configureBuildProperties(traitsSpec, buildProperties); + IntegrationTraitHelper.configureEnvVars(traitsSpec, envVars); + IntegrationTraitHelper.configureConnects(traitsSpec, connects); } private String getIntegrationName(List<String> sources) { diff --git a/dsl/camel-jbang/camel-jbang-plugin-k/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationTraitHelper.java b/dsl/camel-jbang/camel-jbang-plugin-k/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationTraitHelper.java new file mode 100644 index 00000000000..5e2817de83d --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-plugin-k/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationTraitHelper.java @@ -0,0 +1,314 @@ +/* + * 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.commands.k; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import org.apache.camel.dsl.jbang.core.commands.kubernetes.KubernetesHelper; +import org.apache.camel.dsl.jbang.core.commands.kubernetes.traits.ContainerTrait; +import org.apache.camel.util.StringHelper; +import org.apache.camel.v1.integrationspec.Traits; +import org.apache.camel.v1.integrationspec.traits.AddonsBuilder; +import org.apache.camel.v1.integrationspec.traits.Builder; +import org.apache.camel.v1.integrationspec.traits.Camel; +import org.apache.camel.v1.integrationspec.traits.Container; +import org.apache.camel.v1.integrationspec.traits.Environment; +import org.apache.camel.v1.integrationspec.traits.Mount; +import org.apache.camel.v1.integrationspec.traits.Openapi; +import org.apache.camel.v1.integrationspec.traits.ServiceBinding; + +public final class IntegrationTraitHelper { + + private IntegrationTraitHelper() { + //prevent instantiation of utility class. + } + + /** + * Parses given list of trait expressions to proper trait model object. Supports trait options in the form of + * key=value. + * + * @param traits trait key-value-pairs. + * @return + */ + public static Traits parseTraits(String[] traits) { + if (traits == null || traits.length == 0) { + return new Traits(); + } + + Map<String, Map<String, Object>> traitConfigMap = new HashMap<>(); + + for (String traitExpression : traits) { + //traitName.key=value + final String[] trait = traitExpression.split("\\.", 2); + final String[] traitConfig = trait[1].split("=", 2); + + // the CRD api is in CamelCase, then we have to + // convert the kebab-case to CamelCase + final String traitKey = StringHelper.dashToCamelCase(traitConfig[0]); + final Object traitValue = resolveTraitValue(traitKey, traitConfig[1].trim()); + if (traitConfigMap.containsKey(trait[0])) { + Map<String, Object> config = traitConfigMap.get(trait[0]); + + if (config.containsKey(traitKey)) { + Object existingValue = config.get(traitKey); + + if (existingValue instanceof List) { + List<String> values = (List<String>) existingValue; + if (traitValue instanceof List) { + List<String> traitValueList = (List<String>) traitValue; + values.addAll(traitValueList); + } else { + values.add(traitValue.toString()); + } + } else if (existingValue instanceof Map) { + Map<String, String> values = (Map<String, String>) existingValue; + if (traitValue instanceof Map) { + Map<String, String> traitValueList = (Map<String, String>) traitValue; + values.putAll(traitValueList); + } else { + final String[] traitValueConfig = traitValue.toString().split("=", 2); + values.put(traitValueConfig[0], traitValueConfig[1]); + } + } else if (traitValue instanceof List) { + List<String> traitValueList = (List<String>) traitValue; + traitValueList.add(0, existingValue.toString()); + config.put(traitKey, traitValueList); + } else if (traitValue instanceof Map) { + Map<String, String> traitValueMap = (Map<String, String>) traitValue; + final String[] existingValueConfig = existingValue.toString().split("=", 2); + traitValueMap.put(existingValueConfig[0], existingValueConfig[1]); + config.put(traitKey, traitValueMap); + } else { + if (traitKey.endsWith("annotations")) { + Map<String, String> map = new LinkedHashMap<>(); + final String[] traitValueConfig = traitValue.toString().split("=", 2); + final String[] existingValueConfig = existingValue.toString().split("=", 2); + map.put(traitValueConfig[0], traitValueConfig[1]); + map.put(existingValueConfig[0], existingValueConfig[1]); + config.put(traitKey, map); + } else { + config.put(traitKey, Arrays.asList(existingValue.toString(), traitValue)); + } + } + } else { + config.put(traitKey, traitValue); + } + } else { + Map<String, Object> config = new HashMap<>(); + config.put(traitKey, traitValue); + traitConfigMap.put(trait[0], config); + } + } + + Traits traitModel = KubernetesHelper.json().convertValue(traitConfigMap, Traits.class); + + // Handle leftover traits as addons + Set<?> knownTraits = KubernetesHelper.json().convertValue(traitModel, Map.class).keySet(); + if (knownTraits.size() < traitConfigMap.size()) { + traitModel.setAddons(new HashMap<>()); + for (Map.Entry<String, Map<String, Object>> traitConfig : traitConfigMap.entrySet()) { + if (!knownTraits.contains(traitConfig.getKey())) { + traitModel.getAddons().put(traitConfig.getKey(), + new AddonsBuilder().addToAdditionalProperties(traitConfig.getValue()).build()); + } + } + } + + return traitModel; + } + + /** + * Resolve trait value with automatic type conversion. Some trait keys (like enabled, verbose) need to be converted + * to boolean type. + * + */ + private static Object resolveTraitValue(String traitKey, String value) { + if (traitKey.equalsIgnoreCase("enabled") || + traitKey.equalsIgnoreCase("verbose")) { + return Boolean.valueOf(value); + } + + if (value.startsWith("[") && value.endsWith("]")) { + String valueArrayExpression = value.substring(1, value.length() - 1); + List<String> values = new ArrayList<>(); + if (valueArrayExpression.contains(",")) { + values.addAll(List.of(valueArrayExpression.split(","))); + } else { + values.add(valueArrayExpression); + } + return values; + } + + if (value.contains(",")) { + List<String> values = new ArrayList<>(); + for (String entry : value.split(",")) { + values.add(resolveTraitValue("", entry).toString()); + } + return values; + } + + if (value.startsWith("\"") && value.endsWith("\"")) { + return value.substring(1, value.length() - 1); + } + + if (value.startsWith("'") && value.endsWith("'")) { + return value.substring(1, value.length() - 1); + } + + try { + return Integer.parseInt(value); + } catch (NumberFormatException e) { + return value; + } + } + + public static void configureConnects(Traits traitsSpec, String[] connects) { + if (connects == null || connects.length == 0) { + return; + } + ServiceBinding serviceBindingTrait = Optional.ofNullable(traitsSpec.getServiceBinding()).orElseGet(ServiceBinding::new); + if (serviceBindingTrait.getServices() == null) { + serviceBindingTrait.setServices(new ArrayList<>()); + } + serviceBindingTrait.getServices().addAll(List.of(connects)); + traitsSpec.setServiceBinding(serviceBindingTrait); + } + + public static void configureEnvVars(Traits traitsSpec, String[] envVars) { + if (envVars == null || envVars.length == 0) { + return; + } + Environment environmentTrait = Optional.ofNullable(traitsSpec.getEnvironment()).orElseGet(Environment::new); + if (environmentTrait.getVars() == null) { + environmentTrait.setVars(new ArrayList<>()); + } + environmentTrait.getVars().addAll(List.of(envVars)); + traitsSpec.setEnvironment(environmentTrait); + } + + public static void configureBuildProperties(Traits traitsSpec, String[] buildProperties) { + if (buildProperties == null || buildProperties.length == 0) { + return; + } + + Builder builderTrait = Optional.ofNullable(traitsSpec.getBuilder()).orElseGet(Builder::new); + if (builderTrait.getProperties() == null) { + builderTrait.setProperties(new ArrayList<>()); + } + builderTrait.getProperties().addAll(List.of(buildProperties)); + traitsSpec.setBuilder(builderTrait); + } + + public static void configureProperties(Traits traitsSpec, String[] properties) { + if (properties == null || properties.length == 0) { + return; + } + + Camel camelTrait = Optional.ofNullable(traitsSpec.getCamel()).orElseGet(Camel::new); + if (camelTrait.getProperties() == null) { + camelTrait.setProperties(new ArrayList<>()); + } + camelTrait.getProperties().addAll(List.of(properties)); + traitsSpec.setCamel(camelTrait); + } + + public static void configureOpenApiSpec(Traits traitsSpec, String openApi) { + if (openApi == null || !openApi.startsWith("configmap:")) { + return; + } + + Openapi openapiTrait = Optional.ofNullable(traitsSpec.getOpenapi()).orElseGet(Openapi::new); + if (openapiTrait.getConfigmaps() == null) { + openapiTrait.setConfigmaps(new ArrayList<>()); + } + openapiTrait.getConfigmaps().add(openApi); + traitsSpec.setOpenapi(openapiTrait); + } + + public static void configureMountTrait(Traits traitsSpec, String[] configs, String[] resources, String[] volumes) { + if (configs == null && resources == null && volumes == null) { + return; + } + + Mount mountTrait = Optional.ofNullable(traitsSpec.getMount()).orElseGet(Mount::new); + + if (configs != null && configs.length > 0) { + if (mountTrait.getConfigs() == null) { + mountTrait.setConfigs(new ArrayList<>()); + } + mountTrait.getConfigs().addAll(List.of(configs)); + } + + if (resources != null && resources.length > 0) { + if (mountTrait.getResources() == null) { + mountTrait.setResources(new ArrayList<>()); + } + mountTrait.getResources().addAll(List.of(resources)); + } + + if (volumes != null && volumes.length > 0) { + if (mountTrait.getVolumes() == null) { + mountTrait.setVolumes(new ArrayList<>()); + } + mountTrait.getVolumes().addAll(List.of(volumes)); + } + + traitsSpec.setMount(mountTrait); + } + + public static void configureContainerImage( + Traits traitsSpec, String image, String imageRegistry, String imageGroup, String imageName, String version) { + Container containerTrait = Optional.ofNullable(traitsSpec.getContainer()).orElseGet(Container::new); + if (image != null) { + containerTrait.setImage(image); + traitsSpec.setContainer(containerTrait); + } else if (containerTrait.getImage() == null) { + String registryPrefix = ""; + if ("minikube".equals(imageRegistry) || "minikube-registry".equals(imageRegistry)) { + registryPrefix = "localhost:5000/"; + } else if ("kind".equals(imageRegistry) || "kind-registry".equals(imageRegistry)) { + registryPrefix = "localhost:5001/"; + } else if (imageRegistry != null && !imageRegistry.isEmpty()) { + registryPrefix = imageRegistry + "/"; + } + + imageGroup = Optional.ofNullable(imageGroup).orElse(""); + if (!imageGroup.isEmpty()) { + containerTrait.setImage("%s%s/%s:%s".formatted(registryPrefix, imageGroup, imageName, version)); + } else { + containerTrait.setImage("%s%s:%s".formatted(registryPrefix, imageName, version)); + } + + // Plain export command always exposes a health endpoint on 8080. + // Skip this, when we decide that the health endpoint can be disabled. + if (containerTrait.getPort() == null) { + containerTrait.setPortName(ContainerTrait.DEFAULT_CONTAINER_PORT_NAME); + containerTrait.setPort((long) ContainerTrait.DEFAULT_CONTAINER_PORT); + } + + traitsSpec.setContainer(containerTrait); + } + } + +}
