From c4ff76506a82b38d61d0e69988ef0a067c6f6b58 Mon Sep 17 00:00:00 2001 From: Christoph Deppisch Date: Fri, 22 Mar 2024 23:52:27 +0100 Subject: [PATCH] CAMEL-20606: Add Camel K bind command - Enhance Camel JBang bind command with Kubernetes and Camel K specifics such as traits, annotations, service bindings - Uses arbitrary bind command as a base - Makes sure to use a proper Camel K operator id --- .../camel/dsl/jbang/core/commands/Bind.java | 90 +++++++- .../camel/dsl/jbang/core/commands/k/Bind.java | 184 +++++++++++++++ .../jbang/core/commands/k/IntegrationRun.java | 17 +- .../dsl/jbang/core/commands/k/KubePlugin.java | 1 + .../templates/pipe-kamelet-kamelet.yaml.tmpl | 21 ++ .../templates/pipe-kamelet-uri.yaml.tmpl | 18 ++ .../templates/pipe-uri-kamelet.yaml.tmpl | 18 ++ .../templates/pipe-uri-uri.yaml.tmpl | 15 ++ .../dsl/jbang/core/commands/k/BindTest.java | 215 ++++++++++++++++++ .../core/commands/k/IntegrationRunTest.java | 14 ++ 10 files changed, 576 insertions(+), 17 deletions(-) create mode 100644 dsl/camel-jbang/camel-jbang-plugin-k/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/Bind.java create mode 100644 dsl/camel-jbang/camel-jbang-plugin-k/src/main/resources/templates/pipe-kamelet-kamelet.yaml.tmpl create mode 100644 dsl/camel-jbang/camel-jbang-plugin-k/src/main/resources/templates/pipe-kamelet-uri.yaml.tmpl create mode 100644 dsl/camel-jbang/camel-jbang-plugin-k/src/main/resources/templates/pipe-uri-kamelet.yaml.tmpl create mode 100644 dsl/camel-jbang/camel-jbang-plugin-k/src/main/resources/templates/pipe-uri-uri.yaml.tmpl create mode 100644 dsl/camel-jbang/camel-jbang-plugin-k/src/test/java/org/apache/camel/dsl/jbang/core/commands/k/BindTest.java diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Bind.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Bind.java index c5bc9e3809c80..3a00564534270 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Bind.java +++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Bind.java @@ -84,12 +84,50 @@ public class Bind extends CamelCommand { description = "Output format generated by this command (supports: file, yaml or json).") String output; + private final TemplateProvider templateProvider; + public Bind(CamelJBangMain main) { + this(main, new TemplateProvider() { + }); + } + + public Bind(CamelJBangMain main, TemplateProvider templateProvider) { super(main); + this.templateProvider = templateProvider; + } + + /** + * Helper class provides access to the templates that construct the Pipe resource. Subclasses may overwrite the + * provider to inject their own templates. + */ + public interface TemplateProvider { + default InputStream getPipeTemplate(String in, String out) { + return Bind.class.getClassLoader().getResourceAsStream("templates/pipe-" + in + "-" + out + ".yaml.tmpl"); + } + + default InputStream getStepTemplate(String stepType) { + return Bind.class.getClassLoader().getResourceAsStream("templates/step-%s.yaml.tmpl".formatted(stepType)); + } + + default InputStream getErrorHandlerTemplate(String type) { + return Bind.class.getClassLoader() + .getResourceAsStream("templates/error-handler-%s.yaml.tmpl".formatted(type)); + } } @Override public Integer doCall() throws Exception { + String pipe = constructPipe(); + + if (pipe.isEmpty()) { + printer().println("Failed to construct Pipe resource"); + return -1; + } + + return dumpPipe(pipe); + } + + public String constructPipe() throws Exception { // the pipe source and sink can either be a kamelet or an uri String in = "kamelet"; String out = "kamelet"; @@ -119,7 +157,7 @@ public Integer doCall() throws Exception { } } - InputStream is = Bind.class.getClassLoader().getResourceAsStream("templates/pipe-" + in + "-" + out + ".yaml.tmpl"); + InputStream is = templateProvider.getPipeTemplate(in, out); String context = IOHelper.loadText(is); IOHelper.close(is); @@ -146,7 +184,7 @@ public Integer doCall() throws Exception { stepProperties = kameletProperties(step, stepProperties); } - is = Bind.class.getClassLoader().getResourceAsStream("templates/step-%s.yaml.tmpl".formatted(stepType)); + is = templateProvider.getStepTemplate(stepType); text = IOHelper.loadText(is); IOHelper.close(is); text = text.replaceFirst("\\{\\{ \\.Name }}", step); @@ -176,7 +214,8 @@ public Integer doCall() throws Exception { if (errorHandlerTokens.length != 2) { printer().println( "Invalid error handler syntax. Type 'sink' needs an endpoint configuration (ie sink:endpointUri)"); - return -1; + // Error abort Pipe construction + return ""; } String endpoint = errorHandlerTokens[1]; @@ -203,8 +242,7 @@ public Integer doCall() throws Exception { errorHandlerSinkProperties = kameletProperties(endpoint, errorHandlerSinkProperties); } - is = Bind.class.getClassLoader() - .getResourceAsStream("templates/error-handler-sink-%s.yaml.tmpl".formatted(sinkType)); + is = templateProvider.getErrorHandlerTemplate("sink-" + sinkType); errorHandlerSpec = IOHelper.loadText(is); IOHelper.close(is); errorHandlerSpec = errorHandlerSpec.replaceFirst("\\{\\{ \\.Name }}", endpoint); @@ -214,7 +252,7 @@ public Integer doCall() throws Exception { asErrorHandlerParameters(errorHandlerParameters)); break; case "log": - is = Bind.class.getClassLoader().getResourceAsStream("templates/error-handler-log.yaml.tmpl"); + is = templateProvider.getErrorHandlerTemplate("log"); errorHandlerSpec = IOHelper.loadText(is); IOHelper.close(is); errorHandlerSpec = errorHandlerSpec.replaceFirst("\\{\\{ \\.ErrorHandlerParameter }}", @@ -249,23 +287,26 @@ public Integer doCall() throws Exception { sinkProperties.putAll(sinkUriProperties); } context = context.replaceFirst("\\{\\{ \\.SinkProperties }}\n", asEndpointProperties(sinkProperties)); + return context; + } + public int dumpPipe(String pipe) throws Exception { switch (output) { case "file": if (file.endsWith(".yaml")) { - IOHelper.writeText(context, new FileOutputStream(file, false)); + IOHelper.writeText(pipe, new FileOutputStream(file, false)); } else if (file.endsWith(".json")) { - IOHelper.writeText(Jsoner.serialize(YamlHelper.yaml().loadAs(context, Map.class)), + IOHelper.writeText(Jsoner.serialize(YamlHelper.yaml().loadAs(pipe, Map.class)), new FileOutputStream(file, false)); } else { - IOHelper.writeText(context, new FileOutputStream(file + ".yaml", false)); + IOHelper.writeText(pipe, new FileOutputStream(file + ".yaml", false)); } break; case "yaml": - printer().println(context); + printer().println(pipe); break; case "json": - printer().println(JSonHelper.prettyPrint(Jsoner.serialize(YamlHelper.yaml().loadAs(context, Map.class)), 2) + printer().println(JSonHelper.prettyPrint(Jsoner.serialize(YamlHelper.yaml().loadAs(pipe, Map.class)), 2) .replaceAll("\\\\/", "/")); break; default: @@ -442,4 +483,31 @@ protected void doConsumeParameters(Stack args, Bind cmd) { } } + public void setFile(String file) { + this.file = file; + } + + public void setSource(String source) { + this.source = source; + } + + public void setSink(String sink) { + this.sink = sink; + } + + public void setSteps(String[] steps) { + this.steps = steps; + } + + public void setProperties(String[] properties) { + this.properties = properties; + } + + public void setErrorHandler(String errorHandler) { + this.errorHandler = errorHandler; + } + + public void setOutput(String output) { + this.output = output; + } } 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 new file mode 100644 index 0000000000000..c536045c68fa4 --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-plugin-k/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/Bind.java @@ -0,0 +1,184 @@ +/* + * 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.InputStream; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.List; +import java.util.Stack; + +import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain; +import org.apache.camel.util.ObjectHelper; +import org.apache.camel.v1.integrationspec.Traits; +import org.apache.camel.v1.integrationspec.traits.ServiceBinding; +import picocli.CommandLine; +import picocli.CommandLine.Command; + +@Command(name = "bind", + description = "Bind Kubernetes resources such as Kamelets in a new integration pipe connecting a source and a sink", + sortOptions = false) +public class Bind extends KubeBaseCommand { + + private final org.apache.camel.dsl.jbang.core.commands.Bind delegate; + + @CommandLine.Parameters(description = "Name of pipe", arity = "1", + paramLabel = "", parameterConsumer = NameConsumer.class) + Path pipeName; // Defined only for code completion; the field never used + String name; + + @CommandLine.Option(names = { "--source" }, description = "Source (from) such as a Kamelet or Camel endpoint uri", + required = true) + String source; + + @CommandLine.Option(names = { "--step" }, description = "Optional steps such as a Kamelet or Camel endpoint uri") + String[] steps; + + @CommandLine.Option(names = { "--sink" }, description = "Sink (to) such as a Kamelet or Camel endpoint uri", + required = true) + String sink; + + @CommandLine.Option(names = { "--error-handler" }, + description = "Add error handler (none|log|sink:). Sink endpoints are expected in the format \"[[apigroup/]version:]kind:[namespace/]name\", plain Camel URIs or Kamelet name.") + String errorHandler; + + @CommandLine.Option(names = { "--property" }, + description = "Adds a pipe property in the form of [source|sink|step-].= where is the step number starting from 1", + arity = "0") + String[] properties; + + @CommandLine.Option(names = { "--output" }, + defaultValue = "file", + description = "Output format generated by this command (supports: file, yaml or json).") + String output; + + @CommandLine.Option(names = { "--operator-id" }, + defaultValue = "camel-k", + description = "Operator id selected to manage this integration.") + String operatorId = "camel-k"; + + @CommandLine.Option(names = { "--connect" }, + description = "A Service that the integration should bind to, specified as [[apigroup/]version:]kind:[namespace/]name.") + String[] connects; + + @CommandLine.Option(names = { "--annotation" }, + description = "Add an annotation to the integration. Use name values pairs like \"--annotation my.company=hello\".") + String[] annotations; + + @CommandLine.Option(names = { "--traits" }, + description = "Add a label to the integration. Use name values pairs like \"--label my.company=hello\".") + String[] traits; + + public Bind(CamelJBangMain main) { + super(main); + delegate = new org.apache.camel.dsl.jbang.core.commands.Bind( + main, new org.apache.camel.dsl.jbang.core.commands.Bind.TemplateProvider() { + @Override + public InputStream getPipeTemplate(String in, String out) { + return Bind.class.getClassLoader() + .getResourceAsStream("templates/pipe-" + in + "-" + out + ".yaml.tmpl"); + } + }); + } + + @Override + public Integer doCall() throws Exception { + // Operator id must be set + if (ObjectHelper.isEmpty(operatorId)) { + printer().println("Operator id must be set"); + return -1; + } + + delegate.setFile(name); + delegate.setSource(source); + delegate.setSink(sink); + delegate.setSteps(steps); + delegate.setErrorHandler(errorHandler); + delegate.setProperties(properties); + delegate.setOutput(output); + + String pipe = delegate.constructPipe(); + + if (pipe.isEmpty()) { + // Error in delegate exit now + printer().println("Failed to construct Pipe resource"); + return -1; + } + + // --operator-id={id} is a syntax sugar for '--annotation camel.apache.org/operator.id={id}' + if (annotations == null) { + annotations = new String[] { "%s=%s".formatted(KubeCommand.OPERATOR_ID_LABEL, operatorId) }; + } else { + annotations = Arrays.copyOf(annotations, annotations.length + 1); + annotations[annotations.length - 1] = "%s=%s".formatted(KubeCommand.OPERATOR_ID_LABEL, operatorId); + } + + String annotationsContext = ""; + if (annotations != null) { + StringBuilder sb = new StringBuilder(" annotations:\n"); + + for (String annotation : annotations) { + String[] keyValue = annotation.split("=", 2); + if (keyValue.length != 2) { + printer().printf( + "annotation '%s' does not follow format =%n", + annotation); + continue; + } + + sb.append(" ").append(keyValue[0]).append(": ").append(keyValue[1]).append("\n"); + } + + annotationsContext = sb.toString(); + } + + pipe = pipe.replaceFirst("\\{\\{ \\.Annotations }}\n", annotationsContext); + + String integrationSpec = ""; + Traits traitsSpec = null; + if (traits != null && traits.length > 0) { + traitsSpec = TraitHelper.parseTraits(traits); + } + + if (connects != null && connects.length > 0) { + if (traitsSpec == null) { + traitsSpec = new Traits(); + } + + ServiceBinding serviceBindingTrait = new ServiceBinding(); + serviceBindingTrait.setServices(List.of(connects)); + traitsSpec.setServiceBinding(serviceBindingTrait); + } + + if (traitsSpec != null) { + String traitYaml = KubernetesHelper.yaml().dumpAsMap(traitsSpec).replaceAll("\n", "\n "); + integrationSpec = " integration:\n spec:\n traits:\n %s\n".formatted(traitYaml.trim()); + } + + pipe = pipe.replaceFirst("\\{\\{ \\.IntegrationSpec }}\n", integrationSpec); + + return delegate.dumpPipe(pipe); + } + + static class NameConsumer extends ParameterConsumer { + @Override + protected void doConsumeParameters(Stack args, Bind cmd) { + cmd.name = args.pop(); + } + } + +} 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 6d5b5a267ca12..a435ad67b147c 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 @@ -173,6 +173,12 @@ public IntegrationRun(CamelJBangMain main) { } public Integer doCall() throws Exception { + // Operator id must be set + if (ObjectHelper.isEmpty(operatorId)) { + printer().println("Operator id must be set"); + return -1; + } + List integrationSources = Stream.concat(Arrays.stream(Optional.ofNullable(filePaths).orElseGet(() -> new String[] {})), Arrays.stream(Optional.ofNullable(sources).orElseGet(() -> new String[] {}))).toList(); @@ -216,14 +222,13 @@ public Integer doCall() throws Exception { .collect(Collectors.toMap(it -> it[0].trim(), it -> it[1].trim()))); } - if (operatorId != null) { - if (integration.getMetadata().getAnnotations() == null) { - integration.getMetadata().setAnnotations(new HashMap<>()); - } - - integration.getMetadata().getAnnotations().put(KubeCommand.OPERATOR_ID_LABEL, operatorId); + if (integration.getMetadata().getAnnotations() == null) { + integration.getMetadata().setAnnotations(new HashMap<>()); } + // --operator-id={id} is a syntax sugar for '--annotation camel.apache.org/operator.id={id}' + integration.getMetadata().getAnnotations().put(KubeCommand.OPERATOR_ID_LABEL, operatorId); + if (labels != null && labels.length > 0) { integration.getMetadata().setLabels(Arrays.stream(labels) .filter(it -> it.contains("=")) diff --git a/dsl/camel-jbang/camel-jbang-plugin-k/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/KubePlugin.java b/dsl/camel-jbang/camel-jbang-plugin-k/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/KubePlugin.java index 6da3e3fa6da17..7d10cac8e0923 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-k/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/KubePlugin.java +++ b/dsl/camel-jbang/camel-jbang-plugin-k/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/KubePlugin.java @@ -31,6 +31,7 @@ public void customize(CommandLine commandLine, CamelJBangMain main) { .addSubcommand(Agent.ID, new picocli.CommandLine(new Agent(main))) .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("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/resources/templates/pipe-kamelet-kamelet.yaml.tmpl b/dsl/camel-jbang/camel-jbang-plugin-k/src/main/resources/templates/pipe-kamelet-kamelet.yaml.tmpl new file mode 100644 index 0000000000000..400ae37bedf6a --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-plugin-k/src/main/resources/templates/pipe-kamelet-kamelet.yaml.tmpl @@ -0,0 +1,21 @@ +apiVersion: camel.apache.org/v1 +kind: Pipe +metadata: + name: {{ .Name }} +{{ .Annotations }} +spec: +{{ .IntegrationSpec }} + source: + ref: + kind: Kamelet + apiVersion: camel.apache.org/v1 + name: {{ .Source }} + {{ .SourceProperties }} +{{ .Steps }} + sink: + ref: + kind: Kamelet + apiVersion: camel.apache.org/v1 + name: {{ .Sink }} + {{ .SinkProperties }} +{{ .ErrorHandler }} diff --git a/dsl/camel-jbang/camel-jbang-plugin-k/src/main/resources/templates/pipe-kamelet-uri.yaml.tmpl b/dsl/camel-jbang/camel-jbang-plugin-k/src/main/resources/templates/pipe-kamelet-uri.yaml.tmpl new file mode 100644 index 0000000000000..1ac52571ecea8 --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-plugin-k/src/main/resources/templates/pipe-kamelet-uri.yaml.tmpl @@ -0,0 +1,18 @@ +apiVersion: camel.apache.org/v1 +kind: Pipe +metadata: + name: {{ .Name }} +{{ .Annotations }} +spec: +{{ .IntegrationSpec }} + source: + ref: + kind: Kamelet + apiVersion: camel.apache.org/v1 + name: {{ .Source }} + {{ .SourceProperties }} +{{ .Steps }} + sink: + uri: {{ .Sink }} + {{ .SinkProperties }} +{{ .ErrorHandler }} diff --git a/dsl/camel-jbang/camel-jbang-plugin-k/src/main/resources/templates/pipe-uri-kamelet.yaml.tmpl b/dsl/camel-jbang/camel-jbang-plugin-k/src/main/resources/templates/pipe-uri-kamelet.yaml.tmpl new file mode 100644 index 0000000000000..e6c33789d943a --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-plugin-k/src/main/resources/templates/pipe-uri-kamelet.yaml.tmpl @@ -0,0 +1,18 @@ +apiVersion: camel.apache.org/v1 +kind: Pipe +metadata: + name: {{ .Name }} +{{ .Annotations }} +spec: +{{ .IntegrationSpec }} + source: + uri: {{ .Source }} + {{ .SourceProperties }} +{{ .Steps }} + sink: + ref: + kind: Kamelet + apiVersion: camel.apache.org/v1 + name: {{ .Sink }} + {{ .SinkProperties }} +{{ .ErrorHandler }} diff --git a/dsl/camel-jbang/camel-jbang-plugin-k/src/main/resources/templates/pipe-uri-uri.yaml.tmpl b/dsl/camel-jbang/camel-jbang-plugin-k/src/main/resources/templates/pipe-uri-uri.yaml.tmpl new file mode 100644 index 0000000000000..2a181b6ece522 --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-plugin-k/src/main/resources/templates/pipe-uri-uri.yaml.tmpl @@ -0,0 +1,15 @@ +apiVersion: camel.apache.org/v1 +kind: Pipe +metadata: + name: {{ .Name }} +{{ .Annotations }} +spec: +{{ .IntegrationSpec }} + source: + uri: {{ .Source }} + {{ .SourceProperties }} +{{ .Steps }} + sink: + uri: {{ .Sink }} + {{ .SinkProperties }} +{{ .ErrorHandler }} diff --git a/dsl/camel-jbang/camel-jbang-plugin-k/src/test/java/org/apache/camel/dsl/jbang/core/commands/k/BindTest.java b/dsl/camel-jbang/camel-jbang-plugin-k/src/test/java/org/apache/camel/dsl/jbang/core/commands/k/BindTest.java new file mode 100644 index 0000000000000..9809bf44ee761 --- /dev/null +++ b/dsl/camel-jbang/camel-jbang-plugin-k/src/test/java/org/apache/camel/dsl/jbang/core/commands/k/BindTest.java @@ -0,0 +1,215 @@ +/* + * 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 org.apache.camel.dsl.jbang.core.commands.CamelJBangMain; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class BindTest extends KubeBaseTest { + + @Test + public void shouldBindWithDefaultOperatorId() throws Exception { + Bind command = new Bind(new CamelJBangMain().withPrinter(printer)); + command.name = "timer-to-log"; + command.source = "timer-source"; + command.sink = "log-sink"; + command.output = "yaml"; + + command.doCall(); + + String output = printer.getOutput(); + Assertions.assertEquals(""" + apiVersion: camel.apache.org/v1 + kind: Pipe + metadata: + name: timer-to-log + annotations: + camel.apache.org/operator.id: camel-k + spec: + source: + ref: + kind: Kamelet + apiVersion: camel.apache.org/v1 + name: timer-source + properties: + message: "hello world" + sink: + ref: + kind: Kamelet + apiVersion: camel.apache.org/v1 + name: log-sink + #properties: + #key: "value" + """.trim(), output); + } + + @Test + public void shouldBindWithAnnotations() throws Exception { + Bind command = new Bind(new CamelJBangMain().withPrinter(printer)); + command.name = "timer-to-log"; + command.source = "timer-source"; + command.sink = "log-sink"; + command.output = "yaml"; + + command.annotations = new String[] { + "app=camel-k" + }; + + command.doCall(); + + String output = printer.getOutput(); + Assertions.assertEquals(""" + apiVersion: camel.apache.org/v1 + kind: Pipe + metadata: + name: timer-to-log + annotations: + app: camel-k + camel.apache.org/operator.id: camel-k + spec: + source: + ref: + kind: Kamelet + apiVersion: camel.apache.org/v1 + name: timer-source + properties: + message: "hello world" + sink: + ref: + kind: Kamelet + apiVersion: camel.apache.org/v1 + name: log-sink + #properties: + #key: "value" + """.trim(), output); + } + + @Test + public void shouldBindWithTraits() throws Exception { + Bind command = new Bind(new CamelJBangMain().withPrinter(printer)); + command.name = "timer-to-log"; + command.source = "timer-source"; + command.sink = "log-sink"; + command.output = "yaml"; + + command.traits = new String[] { + "mount.configs=configmap:my-cm", + "logging.color=true", + "logging.level=DEBUG" + }; + + command.doCall(); + + String output = printer.getOutput(); + Assertions.assertEquals(""" + apiVersion: camel.apache.org/v1 + kind: Pipe + metadata: + name: timer-to-log + annotations: + camel.apache.org/operator.id: camel-k + spec: + integration: + spec: + traits: + logging: + color: true + level: DEBUG + mount: + configs: + - configmap:my-cm + hotReload: false + source: + ref: + kind: Kamelet + apiVersion: camel.apache.org/v1 + name: timer-source + properties: + message: "hello world" + sink: + ref: + kind: Kamelet + apiVersion: camel.apache.org/v1 + name: log-sink + #properties: + #key: "value" + """.trim(), output); + } + + @Test + public void shouldBindWithServiceBindings() throws Exception { + Bind command = new Bind(new CamelJBangMain().withPrinter(printer)); + command.name = "timer-to-log"; + command.source = "timer-source"; + command.sink = "log-sink"; + command.output = "yaml"; + + command.connects = new String[] { + "serving.knative.dev/v1:Service:my-service" + }; + + command.doCall(); + + String output = printer.getOutput(); + Assertions.assertEquals(""" + apiVersion: camel.apache.org/v1 + kind: Pipe + metadata: + name: timer-to-log + annotations: + camel.apache.org/operator.id: camel-k + spec: + integration: + spec: + traits: + serviceBinding: + services: + - serving.knative.dev/v1:Service:my-service + source: + ref: + kind: Kamelet + apiVersion: camel.apache.org/v1 + name: timer-source + properties: + message: "hello world" + sink: + ref: + kind: Kamelet + apiVersion: camel.apache.org/v1 + name: log-sink + #properties: + #key: "value" + """.trim(), output); + } + + @Test + public void shouldFailWithMissingOperatorId() throws Exception { + Bind command = new Bind(new CamelJBangMain().withPrinter(printer)); + command.name = "timer-to-log"; + command.source = "timer-source"; + command.sink = "log-sink"; + command.output = "yaml"; + + command.operatorId = ""; + + Assertions.assertEquals(-1, command.doCall()); + + Assertions.assertEquals("Operator id must be set", printer.getOutput()); + } +} diff --git a/dsl/camel-jbang/camel-jbang-plugin-k/src/test/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationRunTest.java b/dsl/camel-jbang/camel-jbang-plugin-k/src/test/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationRunTest.java index 4dba66a048735..0a6a0e3df7c3b 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-k/src/test/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationRunTest.java +++ b/dsl/camel-jbang/camel-jbang-plugin-k/src/test/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationRunTest.java @@ -642,6 +642,20 @@ public void shouldHandleUseFlowsDisabledOption() throws Exception { traits: {}""", removeLicenseHeader(printer.getOutput())); } + @Test + public void shouldFailWithMissingOperatorId() throws Exception { + IntegrationRun command = createCommand(); + command.filePaths = new String[] { "classpath:route.yaml" }; + command.useFlows = false; + command.output = "yaml"; + + command.operatorId = ""; + + Assertions.assertEquals(-1, command.doCall()); + + Assertions.assertEquals("Operator id must be set", printer.getOutput()); + } + private IntegrationRun createCommand() { IntegrationRun command = new IntegrationRun(new CamelJBangMain().withPrinter(printer)); command.withClient(kubernetesClient);