This is an automated email from the ASF dual-hosted git repository. cdeppisch pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel.git
commit 4912c140be44a2250e592271174221a704b96215 Author: Christoph Deppisch <cdeppi...@redhat.com> AuthorDate: Wed Jul 17 10:49:58 2024 +0200 CAMEL-20985: Camel K integration export - Add new subcommand to export Camel K integrations/pipes to arbitrary project - This enables the user to export the integration custom resource as normal project that can be run without the Camel K operator being installed on the Kubernetes cluster - Export automatically configures the Kubernetes deployment manifest with the Camel K integration traits --- .../dsl/jbang/core/commands/k/CamelKPlugin.java | 1 + .../jbang/core/commands/k/IntegrationExport.java | 126 +++++++++++++++++++++ .../dsl/jbang/core/commands/k/IntegrationRun.java | 22 +--- .../core/commands/k/IntegrationExportTest.java | 107 +++++++++++++++++ .../dsl/jbang/core/commands/k/integration.yaml | 3 + .../camel/dsl/jbang/core/commands/k/pipe.yaml | 1 + .../core/commands/kubernetes/KubernetesExport.java | 67 +++++------ .../commands/kubernetes/traits/TraitCatalog.java | 31 +++++ .../commands/kubernetes/traits/TraitHelper.java | 32 +++++- 9 files changed, 333 insertions(+), 57 deletions(-) diff --git a/dsl/camel-jbang/camel-jbang-plugin-k/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/CamelKPlugin.java b/dsl/camel-jbang/camel-jbang-plugin-k/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/CamelKPlugin.java index 39b5f2a22f0..19822239340 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-k/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/CamelKPlugin.java +++ b/dsl/camel-jbang/camel-jbang-plugin-k/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/CamelKPlugin.java @@ -31,6 +31,7 @@ public class CamelKPlugin implements Plugin { .addSubcommand("get", new picocli.CommandLine(new IntegrationGet(main))) .addSubcommand("run", new picocli.CommandLine(new IntegrationRun(main))) .addSubcommand("bind", new picocli.CommandLine(new Bind(main))) + .addSubcommand("export", new picocli.CommandLine(new IntegrationExport(main))) .addSubcommand("delete", new picocli.CommandLine(new IntegrationDelete(main))) .addSubcommand("logs", new picocli.CommandLine(new IntegrationLogs(main))); 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 new file mode 100644 index 00000000000..e481bf24337 --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-plugin-k/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationExport.java @@ -0,0 +1,126 @@ +/* + * 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.stream.Collectors; + +import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain; +import org.apache.camel.dsl.jbang.core.commands.kubernetes.KubernetesExport; +import org.apache.camel.dsl.jbang.core.commands.kubernetes.KubernetesHelper; +import org.apache.camel.dsl.jbang.core.common.RuntimeType; +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", + description = "Export integration as an arbitrary Maven/Gradle project with a Kubernetes deployment manifest", + sortOptions = false) +public class IntegrationExport extends KubernetesExport { + + private Integration integration; + private Pipe pipe; + + public IntegrationExport(CamelJBangMain main) { + super(main); + } + + public IntegrationExport(CamelJBangMain main, RuntimeType runtime, String[] files, String exportDir, String imageGroup, + boolean quiet) { + super(main, runtime, files, exportDir, quiet); + this.imageGroup = imageGroup; + } + + @Override + public Integer export() throws Exception { + if (files.size() != 1) { + if (!quiet) { + printer().println("Project export failed - requires single Integration or Pipe source file as an argument"); + } + return 1; + } + + Source source = SourceHelper.resolveSource(files.get(0)); + if (source.content().contains("kind: Integration")) { + integration = KubernetesHelper.yaml(this.getClass().getClassLoader()).loadAs(source.content(), Integration.class); + + if (integration.getMetadata().getAnnotations() != null) { + this.annotations = integration.getMetadata().getAnnotations().entrySet().stream() + .map(entry -> "%s=%s".formatted(entry.getKey(), entry.getValue())) + .collect(Collectors.toSet()).toArray(String[]::new); + } + + if (integration.getMetadata().getLabels() != null) { + this.labels = integration.getMetadata().getLabels().entrySet().stream() + .map(entry -> "%s=%s".formatted(entry.getKey(), entry.getValue())) + .collect(Collectors.toSet()).toArray(String[]::new); + } + } else if (source.content().contains("kind: Pipe")) { + pipe = KubernetesHelper.yaml(this.getClass().getClassLoader()).loadAs(source.content(), Pipe.class); + + if (pipe.getMetadata().getAnnotations() != null) { + this.annotations = pipe.getMetadata().getAnnotations().entrySet().stream() + .map(entry -> "%s=%s".formatted(entry.getKey(), entry.getValue())) + .collect(Collectors.toSet()).toArray(String[]::new); + } + + if (pipe.getMetadata().getLabels() != null) { + this.labels = pipe.getMetadata().getLabels().entrySet().stream() + .map(entry -> "%s=%s".formatted(entry.getKey(), entry.getValue())) + .collect(Collectors.toSet()).toArray(String[]::new); + } + } else { + if (!quiet) { + printer().println("Project export failed - not an Integration or Pipe source"); + } + return 1; + } + + return super.export(); + } + + @Override + protected Traits getTraitSpec() { + if (integration != null && integration.getSpec().getTraits() != null) { + return integration.getSpec().getTraits(); + } + + 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); + } + + return super.getTraitSpec(); + } + + @Override + protected String getProjectName() { + if (integration != null) { + return integration.getMetadata().getName(); + } + + if (pipe != null) { + return pipe.getMetadata().getName(); + } + return super.getProjectName(); + } +} 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 80a7b5d4f15..e17d49025ae 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 @@ -244,12 +244,7 @@ public class IntegrationRun extends KubernetesBaseCommand { .collect(Collectors.toMap(it -> it[0].trim(), it -> it[1].trim()))); } - Traits traitsSpec; - if (traits != null && traits.length > 0) { - traitsSpec = TraitHelper.parseTraits(traits); - } else { - traitsSpec = new Traits(); - } + Traits traitsSpec = TraitHelper.parseTraits(traits); if (image != null) { TraitHelper.configureContainerImage(traitsSpec, image, null, null, null, null); @@ -320,20 +315,7 @@ public class IntegrationRun extends KubernetesBaseCommand { TraitHelper.configureContainerImage(traitsSpec, image, "quay.io", null, integration.getMetadata().getName(), "1.0-SNAPSHOT"); - if (traitProfile != null) { - new TraitCatalog().traitsForProfile(TraitProfile.valueOf(traitProfile.toUpperCase(Locale.US))) - .forEach(t -> { - if (t.configure(traitsSpec, context)) { - t.apply(traitsSpec, context); - } - }); - } else { - new TraitCatalog().allTraits().forEach(t -> { - if (t.configure(traitsSpec, context)) { - t.apply(traitsSpec, context); - } - }); - } + new TraitCatalog().apply(traitsSpec, context, traitProfile); printer().println( context.buildItems().stream().map(KubernetesHelper::dumpYaml).collect(Collectors.joining("---"))); diff --git a/dsl/camel-jbang/camel-jbang-plugin-k/src/test/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationExportTest.java b/dsl/camel-jbang/camel-jbang-plugin-k/src/test/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationExportTest.java new file mode 100644 index 00000000000..ca6526cd88f --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-plugin-k/src/test/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationExportTest.java @@ -0,0 +1,107 @@ +/* + * 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.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.nio.file.Files; + +import io.fabric8.kubernetes.api.model.apps.Deployment; +import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain; +import org.apache.camel.dsl.jbang.core.commands.kubernetes.KubernetesHelper; +import org.apache.camel.dsl.jbang.core.commands.kubernetes.traits.BaseTrait; +import org.apache.camel.dsl.jbang.core.common.RuntimeType; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class IntegrationExportTest { + + private File workingDir; + + @BeforeEach + public void setup() throws IOException { + workingDir = Files.createTempDirectory("camel-k-integration-export").toFile(); + workingDir.deleteOnExit(); + } + + @Test + public void shouldExportFromIntegration() throws Exception { + IntegrationExport command + = createCommand(new String[] { "classpath:org/apache/camel/dsl/jbang/core/commands/k/integration.yaml" }, + workingDir.toString()); + int exit = command.doCall(); + + Assertions.assertEquals(0, exit); + + Deployment deployment = getDeployment(workingDir); + Assertions.assertEquals("routes", deployment.getMetadata().getName()); + Assertions.assertEquals(1, deployment.getSpec().getTemplate().getSpec().getContainers().size()); + Assertions.assertEquals("routes", deployment.getMetadata().getLabels().get(BaseTrait.INTEGRATION_LABEL)); + Assertions.assertEquals("routes", deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getName()); + Assertions.assertEquals(1, deployment.getSpec().getSelector().getMatchLabels().size()); + Assertions.assertEquals("routes", + deployment.getSpec().getSelector().getMatchLabels().get(BaseTrait.INTEGRATION_LABEL)); + Assertions.assertEquals("quay.io/camel-test/routes:1.0-SNAPSHOT", + deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getImage()); + Assertions.assertEquals(1, deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getEnv().size()); + Assertions.assertEquals("MY_ENV_VAR", + deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getEnv().get(0).getName()); + Assertions.assertEquals("foo", + deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getEnv().get(0).getValue()); + } + + @Test + public void shouldExportFromPipe() throws Exception { + IntegrationExport command = createCommand( + new String[] { "classpath:org/apache/camel/dsl/jbang/core/commands/k/pipe.yaml" }, workingDir.toString()); + int exit = command.doCall(); + + Assertions.assertEquals(0, exit); + + Deployment deployment = getDeployment(workingDir); + Assertions.assertEquals("timer-to-log", deployment.getMetadata().getName()); + Assertions.assertEquals(1, deployment.getSpec().getTemplate().getSpec().getContainers().size()); + Assertions.assertEquals("timer-to-log", deployment.getMetadata().getLabels().get(BaseTrait.INTEGRATION_LABEL)); + Assertions.assertEquals("timer-to-log", deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getName()); + Assertions.assertEquals(1, deployment.getSpec().getSelector().getMatchLabels().size()); + Assertions.assertEquals("timer-to-log", + deployment.getSpec().getSelector().getMatchLabels().get(BaseTrait.INTEGRATION_LABEL)); + Assertions.assertEquals("quay.io/camel-test/timer-to-log:1.0-SNAPSHOT", + deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getImage()); + Assertions.assertEquals(1, deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getEnv().size()); + Assertions.assertEquals("MY_ENV_VAR", + deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getEnv().get(0).getName()); + Assertions.assertEquals("foo", + deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getEnv().get(0).getValue()); + } + + private IntegrationExport createCommand(String[] files, String exportDir) { + return new IntegrationExport( + new CamelJBangMain(), + RuntimeType.quarkus, files, exportDir, "camel-test", false); + } + + private Deployment getDeployment(File workingDir) throws IOException { + try (FileInputStream fis = new FileInputStream(new File(workingDir, "src/main/kubernetes/kubernetes.yml"))) { + return KubernetesHelper.yaml().loadAs(fis, Deployment.class); + } + } + +} diff --git a/dsl/camel-jbang/camel-jbang-plugin-k/src/test/resources/org/apache/camel/dsl/jbang/core/commands/k/integration.yaml b/dsl/camel-jbang/camel-jbang-plugin-k/src/test/resources/org/apache/camel/dsl/jbang/core/commands/k/integration.yaml index a3d6d058745..1578da98df1 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-k/src/test/resources/org/apache/camel/dsl/jbang/core/commands/k/integration.yaml +++ b/dsl/camel-jbang/camel-jbang-plugin-k/src/test/resources/org/apache/camel/dsl/jbang/core/commands/k/integration.yaml @@ -30,5 +30,8 @@ spec: constant: Hello Camel !!! - to: log:info traits: + environment: + vars: + - MY_ENV_VAR=foo logging: level: DEBUG diff --git a/dsl/camel-jbang/camel-jbang-plugin-k/src/test/resources/org/apache/camel/dsl/jbang/core/commands/k/pipe.yaml b/dsl/camel-jbang/camel-jbang-plugin-k/src/test/resources/org/apache/camel/dsl/jbang/core/commands/k/pipe.yaml index 5ad3d12a255..b6ed18dcd70 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-k/src/test/resources/org/apache/camel/dsl/jbang/core/commands/k/pipe.yaml +++ b/dsl/camel-jbang/camel-jbang-plugin-k/src/test/resources/org/apache/camel/dsl/jbang/core/commands/k/pipe.yaml @@ -21,6 +21,7 @@ metadata: name: timer-to-log annotations: camel.apache.org/operator.id: camel-k + trait.camel.apache.org/environment.vars: "MY_ENV_VAR=foo" spec: source: ref: diff --git a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesExport.java b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesExport.java index 7d0335f4d06..fc2a1ca264c 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesExport.java +++ b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesExport.java @@ -23,7 +23,6 @@ import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.HashMap; import java.util.List; -import java.util.Locale; import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; @@ -43,62 +42,62 @@ import picocli.CommandLine.Command; @Command(name = "export", description = "Export as Maven/Gradle project that contains a Kubernetes deployment manifest", sortOptions = false) -class KubernetesExport extends Export { +public class KubernetesExport extends Export { @CommandLine.Option(names = { "--trait-profile" }, description = "The trait profile to use for the deployment.") - String traitProfile; + protected String traitProfile; @CommandLine.Option(names = { "--property" }, description = "Add a runtime property or properties file from a path, a config map or a secret (syntax: [my-key=my-value|file:/path/to/my-conf.properties|[configmap|secret]:name]).") - String[] properties; + protected String[] properties; @CommandLine.Option(names = { "--config" }, description = "Add a runtime configuration from a ConfigMap or a Secret (syntax: [configmap|secret]:name[/key], where name represents the configmap/secret name and key optionally represents the configmap/secret key to be filtered).") - String[] configs; + protected String[] configs; @CommandLine.Option(names = { "--resource" }, description = "Add a runtime resource from a Configmap or a Secret (syntax: [configmap|secret]:name[/key][@path], where name represents the configmap/secret name, key optionally represents the configmap/secret key to be filtered and path represents the destination path).") - String[] resources; + protected String[] resources; @CommandLine.Option(names = { "--open-api-spec" }, description = "Add an OpenAPI spec (syntax: [configmap|file]:name).") - String[] openApis; + protected String[] openApis; @CommandLine.Option(names = { "--env" }, description = "Set an environment variable in the integration container, for instance \"-e MY_VAR=my-value\".") - String[] envVars; + protected String[] envVars; @CommandLine.Option(names = { "--volume" }, description = "Mount a volume into the integration container, for instance \"-v pvcname:/container/path\".") - String[] volumes; + protected String[] volumes; @CommandLine.Option(names = { "--connect" }, description = "A Service that the integration should bind to, specified as [[apigroup/]version:]kind:[namespace/]name.") - String[] connects; + protected String[] connects; @CommandLine.Option(names = { "--annotation" }, description = "Add an annotation to the integration. Use name values pairs like \"--annotation my.company=hello\".") - String[] annotations; + protected String[] annotations; @CommandLine.Option(names = { "--label" }, description = "Add a label to the integration. Use name values pairs like \"--label my.company=hello\".") - String[] labels; + protected String[] labels; @CommandLine.Option(names = { "--trait" }, description = "Add a trait configuration to the integration. Use name values pairs like \"--trait trait.name.config=hello\".") - String[] traits; + protected String[] traits; @CommandLine.Option(names = { "--image" }, description = "The image name to be built.") - String image; + protected String image; @CommandLine.Option(names = { "--image-registry" }, defaultValue = "quay.io", description = "The image registry to hold the app container image.") - String imageRegistry = "quay.io"; + protected String imageRegistry = "quay.io"; @CommandLine.Option(names = { "--image-group" }, description = "The image registry group used to push images to.") - String imageGroup; + protected String imageGroup; public KubernetesExport(CamelJBangMain main) { super(main); @@ -206,12 +205,7 @@ class KubernetesExport extends Export { context.setProfile(TraitProfile.valueOf(traitProfile)); } - Traits traitsSpec; - if (traits != null && traits.length > 0) { - traitsSpec = TraitHelper.parseTraits(traits); - } else { - traitsSpec = new Traits(); - } + Traits traitsSpec = getTraitSpec(); TraitHelper.configureMountTrait(traitsSpec, configs, resources, volumes); TraitHelper.configureOpenApiSpec(traitsSpec, openApis); @@ -252,19 +246,7 @@ class KubernetesExport extends Export { printer().println("Building Kubernetes manifest ..."); } - if (traitProfile != null) { - new TraitCatalog().traitsForProfile(TraitProfile.valueOf(traitProfile.toUpperCase(Locale.US))).forEach(t -> { - if (t.configure(traitsSpec, context)) { - t.apply(traitsSpec, context); - } - }); - } else { - new TraitCatalog().allTraits().forEach(t -> { - if (t.configure(traitsSpec, context)) { - t.apply(traitsSpec, context); - } - }); - } + new TraitCatalog().apply(traitsSpec, context, traitProfile); String yaml = context.buildItems().stream().map(KubernetesHelper::dumpYaml).collect(Collectors.joining("---\n")); safeCopy(new ByteArrayInputStream(yaml.getBytes(StandardCharsets.UTF_8)), @@ -277,6 +259,19 @@ class KubernetesExport extends Export { return 0; } + protected Traits getTraitSpec() { + Traits traitsSpec; + if (traits != null && traits.length > 0) { + traitsSpec = TraitHelper.parseTraits(traits, annotations); + } else if (annotations != null && annotations.length > 0) { + traitsSpec = TraitHelper.parseTraits(new String[] {}, annotations); + } else { + traitsSpec = new Traits(); + } + + return traitsSpec; + } + private String resolveImageRegistry() { String resolvedImageRegistry = null; if (image != null) { @@ -332,7 +327,7 @@ class KubernetesExport extends Export { /** * Configurer used to customize internal options for the Export command. */ - record ExportConfigurer(RuntimeType runtime, + public record ExportConfigurer(RuntimeType runtime, String quarkusVersion, boolean symbolicLink, boolean mavenWrapper, diff --git a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/TraitCatalog.java b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/TraitCatalog.java index cb18dcee671..d566fd22d60 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/TraitCatalog.java +++ b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/TraitCatalog.java @@ -19,8 +19,16 @@ package org.apache.camel.dsl.jbang.core.commands.kubernetes.traits; import java.util.ArrayList; import java.util.List; +import java.util.Locale; import java.util.stream.Collectors; +import org.apache.camel.v1.integrationspec.Traits; + +/** + * Catalog of traits that get applied to a trait context in order to generate a set of Kubernetes resources as a + * manifest. Traits may be filtered according to a given trait profile. The given trait specification holds the trait + * configuration for the applied traits. + */ public class TraitCatalog { private final List<Trait> traits = new ArrayList<>(); @@ -46,4 +54,27 @@ public class TraitCatalog { public void register(Trait trait) { traits.add(trait); } + + /** + * Applies traits in this catalog for given profile or all traits if profile is not set. + * + * @param traitsSpec the trait configuration spec. + * @param context the trait context. + * @param traitProfile the optional trait profile to select traits. + */ + public void apply(Traits traitsSpec, TraitContext context, String traitProfile) { + if (traitProfile != null) { + new TraitCatalog().traitsForProfile(TraitProfile.valueOf(traitProfile.toUpperCase(Locale.US))).forEach(t -> { + if (t.configure(traitsSpec, context)) { + t.apply(traitsSpec, context); + } + }); + } else { + new TraitCatalog().allTraits().forEach(t -> { + if (t.configure(traitsSpec, context)) { + t.apply(traitsSpec, context); + } + }); + } + } } diff --git a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/TraitHelper.java b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/TraitHelper.java index 14bce2bb89f..9001489977a 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/TraitHelper.java +++ b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/TraitHelper.java @@ -24,6 +24,8 @@ import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.apache.camel.dsl.jbang.core.commands.kubernetes.KubernetesHelper; import org.apache.camel.util.StringHelper; @@ -53,9 +55,37 @@ public final class TraitHelper { * @return */ public static Traits parseTraits(String[] traits) { + if (traits == null || traits.length == 0) { + return new Traits(); + } + + return parseTraits(traits, null); + } + + /** + * Parses given list of trait expressions to proper trait model object. Supports trait options in the form of + * key=value and trait annotation configuration. + * + * @param traits trait key-value-pairs. + * @param annotations trait annotation configuration. + * @return + */ + public static Traits parseTraits(String[] traits, String[] annotations) { Map<String, Map<String, Object>> traitConfigMap = new HashMap<>(); - for (String traitExpression : traits) { + String[] traitExpressions; + if (annotations != null) { + Stream<String> annotationTraits = Stream.of(annotations) + .filter(annotation -> annotation.startsWith("trait.camel.apache.org/")) + .map(annotation -> StringHelper.after(annotation, "trait.camel.apache.org/")); + + traitExpressions + = Stream.concat(Stream.of(traits), annotationTraits).collect(Collectors.toSet()).toArray(String[]::new); + } else { + traitExpressions = traits; + } + + for (String traitExpression : traitExpressions) { //traitName.key=value final String[] trait = traitExpression.split("\\.", 2); final String[] traitConfig = trait[1].split("=", 2);