[
https://issues.apache.org/jira/browse/CAMEL-12629?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=16538240#comment-16538240
]
ASF GitHub Bot commented on CAMEL-12629:
----------------------------------------
onderson closed pull request #2414: CAMEL-12629 - add support for
Channel.CHANNEL_SHELL command execution
URL: https://github.com/apache/camel/pull/2414
This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:
As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):
diff --git a/components/camel-ssh/src/main/docs/ssh-component.adoc
b/components/camel-ssh/src/main/docs/ssh-component.adoc
index ca80dc4dd56..654710b45e3 100644
--- a/components/camel-ssh/src/main/docs/ssh-component.adoc
+++ b/components/camel-ssh/src/main/docs/ssh-component.adoc
@@ -31,8 +31,7 @@ ssh:[username[:password]@]host[:port][?options]
// component options: START
-The SSH component supports 12 options, which are listed below.
-
+The SSH component supports 15 options, which are listed below.
[width="100%",cols="2,5,^1,2",options="header"]
@@ -49,6 +48,9 @@ The SSH component supports 12 options, which are listed below.
| *timeout* (common) | Sets the timeout in milliseconds to wait in
establishing the remote SSH server connection. Defaults to 30000 milliseconds.
| | long
| *certFilename* (security) | *Deprecated* Sets the resource path of the
certificate to use for Authentication. | | String
| *certResource* (security) | Sets the resource path of the certificate to use
for Authentication. Will use ResourceHelperKeyPairProvider to resolve file
based certificate, and depends on keyType setting. | | String
+| *channelType* (advanced) | Sets the channel type to pass to the Channel as
part of command execution. Defaults to exec. | | String
+| *shellPrompt* (advanced) | Sets the shellPrompt to be dropped when response
is read after command execution | | String
+| *sleepForShellPrompt* (advanced) | Sets the sleep period in milliseconds to
wait reading response from shell prompt. Defaults to 100 milliseconds. | | long
| *resolveProperty Placeholders* (advanced) | Whether the component should
resolve property placeholders on itself when starting. Only properties which
are of String type can use property placeholders. | true | boolean
|===
// component options: END
@@ -76,7 +78,7 @@ with the following path and query parameters:
|===
-==== Query Parameters (28 parameters):
+==== Query Parameters (31 parameters):
[width="100%",cols="2,5,^1,2",options="header"]
@@ -91,6 +93,9 @@ with the following path and query parameters:
| *exceptionHandler* (consumer) | To let the consumer use a custom
ExceptionHandler. Notice if the option bridgeErrorHandler is enabled then this
options is not in use. By default the consumer will deal with exceptions, that
will be logged at WARN or ERROR level and ignored. | | ExceptionHandler
| *exchangePattern* (consumer) | Sets the exchange pattern when the consumer
creates an exchange. | | ExchangePattern
| *pollStrategy* (consumer) | A pluggable
org.apache.camel.PollingConsumerPollingStrategy allowing you to provide your
custom implementation to control error handling usually occurred during the
poll operation before an Exchange have been created and being routed in Camel.
| | PollingConsumerPoll Strategy
+| *channelType* (advanced) | Sets the channel type to pass to the Channel as
part of command execution. Defaults to exec. | exec | String
+| *shellPrompt* (advanced) | Sets the shellPrompt to be dropped when response
is read after command execution | | String
+| *sleepForShellPrompt* (advanced) | Sets the sleep period in milliseconds to
wait reading response from shell prompt. Defaults to 100 milliseconds. | 100 |
long
| *synchronous* (advanced) | Sets whether synchronous processing should be
strictly used, or Camel is allowed to use asynchronous processing (if
supported). | false | boolean
| *backoffErrorThreshold* (scheduler) | The number of subsequent error polls
(failed due some error) that should happen before the backoffMultipler should
kick-in. | | int
| *backoffIdleThreshold* (scheduler) | The number of subsequent idle polls
that should happen before the backoffMultipler should kick-in. | | int
diff --git
a/components/camel-ssh/src/main/java/org/apache/camel/component/ssh/SshComponent.java
b/components/camel-ssh/src/main/java/org/apache/camel/component/ssh/SshComponent.java
index d11923eb6f0..ee7c346a73f 100644
---
a/components/camel-ssh/src/main/java/org/apache/camel/component/ssh/SshComponent.java
+++
b/components/camel-ssh/src/main/java/org/apache/camel/component/ssh/SshComponent.java
@@ -208,4 +208,42 @@ public String getCertResource() {
public void setCertResource(String certResource) {
getConfiguration().setCertResource(certResource);
}
+
+ /**
+ * Sets the channel type to pass to the Channel as part of command
execution.
+ * Defaults to "exec".
+ *
+ * @param channelType
+ * String defining the type of Channel to use for command
execution.
+ *
+ * @see org.apache.sshd.common.channel.Channel
+ */
+ @Metadata(label = "advanced")
+ public void setChannelType(String channelType) {
+ getConfiguration().setChannelType(channelType);
+ }
+
+ /**
+ * Sets the shellPrompt to be dropped when response is read after command
execution
+ *
+ * @param shellPrompt
+ * String defining ending string of command line which has to
be dropped when response is
+ * read after command execution.
+ */
+ @Metadata(label = "advanced")
+ public void setShellPrompt(String shellPrompt) {
+ getConfiguration().setShellPrompt(shellPrompt);
+ }
+
+ /**
+ * Sets the sleep period in milliseconds to wait reading response from
shell prompt.
+ * Defaults to 100 milliseconds.
+ *
+ * @param sleepForShellPrompt
+ * long milliseconds to wait.
+ */
+ @Metadata(label = "advanced")
+ public void setSleepForShellPrompt(long sleepForShellPrompt) {
+ getConfiguration().setSleepForShellPrompt(sleepForShellPrompt);
+ }
}
diff --git
a/components/camel-ssh/src/main/java/org/apache/camel/component/ssh/SshConfiguration.java
b/components/camel-ssh/src/main/java/org/apache/camel/component/ssh/SshConfiguration.java
index d6f31899d2a..e2cf2931d4b 100644
---
a/components/camel-ssh/src/main/java/org/apache/camel/component/ssh/SshConfiguration.java
+++
b/components/camel-ssh/src/main/java/org/apache/camel/component/ssh/SshConfiguration.java
@@ -24,7 +24,7 @@
import org.apache.camel.spi.UriParams;
import org.apache.camel.spi.UriPath;
import org.apache.camel.util.StringHelper;
-
+import org.apache.sshd.common.channel.Channel;
import org.apache.sshd.common.keyprovider.KeyPairProvider;
@UriParams
@@ -54,7 +54,13 @@
private String knownHostsResource;
@UriParam(defaultValue = "false")
private boolean failOnUnknownHost;
-
+ @UriParam(label = "advanced", defaultValue = Channel.CHANNEL_EXEC)
+ private String channelType = Channel.CHANNEL_EXEC;
+ @UriParam(label = "advanced")
+ private String shellPrompt;
+ @UriParam(label = "advanced", defaultValue = "100")
+ private long sleepForShellPrompt;
+
public SshConfiguration() {
}
@@ -283,4 +289,51 @@ public boolean isFailOnUnknownHost() {
public void setFailOnUnknownHost(boolean failOnUnknownHost) {
this.failOnUnknownHost = failOnUnknownHost;
}
+
+ public String getChannelType() {
+ return channelType;
+ }
+
+ /**
+ * Sets the channel type to pass to the Channel as part of command
execution.
+ * Defaults to "exec".
+ *
+ * @param channelType
+ * String defining the type of Channel to use for command
execution.
+ *
+ * @see org.apache.sshd.common.channel.Channel
+ */
+ public void setChannelType(String channelType) {
+ this.channelType = channelType;
+ }
+
+ public String getShellPrompt() {
+ return shellPrompt;
+ }
+
+ /**
+ * Sets the shellPrompt to be dropped when response is read after command
execution
+ *
+ * @param shellPrompt
+ * String defining ending string of command line which has to
be dropped when response is
+ * read after command execution.
+ */
+ public void setShellPrompt(String shellPrompt) {
+ this.shellPrompt = shellPrompt;
+ }
+
+ public long getSleepForShellPrompt() {
+ return sleepForShellPrompt;
+ }
+
+ /**
+ * Sets the sleep period in milliseconds to wait reading response from
shell prompt.
+ * Defaults to 100 milliseconds.
+ *
+ * @param sleepForShellPrompt
+ * long milliseconds to wait.
+ */
+ public void setSleepForShellPrompt(long sleepForShellPrompt) {
+ this.sleepForShellPrompt = sleepForShellPrompt;
+ }
}
diff --git
a/components/camel-ssh/src/main/java/org/apache/camel/component/ssh/SshEndpoint.java
b/components/camel-ssh/src/main/java/org/apache/camel/component/ssh/SshEndpoint.java
index 50d6b00db7c..bb1dd6c986e 100644
---
a/components/camel-ssh/src/main/java/org/apache/camel/component/ssh/SshEndpoint.java
+++
b/components/camel-ssh/src/main/java/org/apache/camel/component/ssh/SshEndpoint.java
@@ -178,5 +178,29 @@ public boolean isFailOnUnknownHost() {
public void setFailOnUnknownHost(boolean failOnUnknownHost) {
getConfiguration().setFailOnUnknownHost(failOnUnknownHost);
}
+
+ public String getChannelType() {
+ return getConfiguration().getChannelType();
+ }
+
+ public void setChannelType(String channelType) {
+ getConfiguration().setChannelType(channelType);
+ }
+
+ public String getShellPrompt() {
+ return getConfiguration().getShellPrompt();
+ }
+
+ public void setShellPrompt(String shellPrompt) {
+ getConfiguration().setShellPrompt(shellPrompt);
+ }
+
+ public long getSleepForShellPrompt() {
+ return getConfiguration().getSleepForShellPrompt();
+ }
+
+ public void setSleepForShellPrompt(long sleepForShellPrompt) {
+ getConfiguration().setSleepForShellPrompt(sleepForShellPrompt);
+ }
}
diff --git
a/components/camel-ssh/src/main/java/org/apache/camel/component/ssh/SshHelper.java
b/components/camel-ssh/src/main/java/org/apache/camel/component/ssh/SshHelper.java
index aa519423c33..02082c0da7e 100644
---
a/components/camel-ssh/src/main/java/org/apache/camel/component/ssh/SshHelper.java
+++
b/components/camel-ssh/src/main/java/org/apache/camel/component/ssh/SshHelper.java
@@ -18,6 +18,10 @@
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+import java.io.UnsupportedEncodingException;
import java.security.KeyPair;
import java.util.Arrays;
import java.util.Map;
@@ -109,10 +113,23 @@ public static SshResult sendExecCommand(Map<String,
Object> headers, String comm
LOG.debug("Failed to authenticate");
throw new RuntimeCamelException("Failed to authenticate
username " + configuration.getUsername());
}
+
+ InputStream in = null;
+ PipedOutputStream reply = new PipedOutputStream();
- channel = session.createChannel(Channel.CHANNEL_EXEC, command);
+ // for now only two channel types are supported
+ // shell option is added for specific purpose for now
+ // may need further maintainance for further use cases
+ if (Channel.CHANNEL_EXEC.equals(endpoint.getChannelType())) {
+ channel = session.createChannel(Channel.CHANNEL_EXEC, command);
+ in = new ByteArrayInputStream(new byte[]{0});
+ } else if
(Channel.CHANNEL_SHELL.equals(endpoint.getChannelType())) {
+ // PipedOutputStream and PipedInputStream both are connected
to each other to create a communication pipe
+ // this approach is used to send the command and evaluate the
response
+ channel = session.createChannel(Channel.CHANNEL_SHELL);
+ in = new PipedInputStream(reply);
+ }
- ByteArrayInputStream in = new ByteArrayInputStream(new byte[]{0});
channel.setIn(in);
ByteArrayOutputStream out = new ByteArrayOutputStream();
@@ -123,13 +140,23 @@ public static SshResult sendExecCommand(Map<String,
Object> headers, String comm
OpenFuture openFuture = channel.open();
openFuture.await(configuration.getTimeout());
SshResult result = null;
- if (openFuture.isOpened()) {
- Set<ClientChannelEvent> events =
channel.waitFor(Arrays.asList(ClientChannelEvent.CLOSED), 0);
- if (!events.contains(ClientChannelEvent.TIMEOUT)) {
- result = new SshResult(command, channel.getExitStatus(),
- new ByteArrayInputStream(out.toByteArray()),
- new ByteArrayInputStream(err.toByteArray()));
+ if (Channel.CHANNEL_EXEC.equals(endpoint.getChannelType())) {
+ if (openFuture.isOpened()) {
+ Set<ClientChannelEvent> events =
channel.waitFor(Arrays.asList(ClientChannelEvent.CLOSED), 0);
+ if (!events.contains(ClientChannelEvent.TIMEOUT)) {
+ result = new SshResult(command,
channel.getExitStatus(),
+ new ByteArrayInputStream(out.toByteArray()),
+ new ByteArrayInputStream(err.toByteArray()));
+ }
}
+ } else if
(Channel.CHANNEL_SHELL.equals(endpoint.getChannelType())) {
+ getPrompt(channel, out, endpoint);
+ reply.write(command.getBytes());
+ reply.write(System.lineSeparator().getBytes());
+ String response = getPrompt(channel, out, endpoint);
+ result = new SshResult(command, channel.getExitStatus(),
+ new ByteArrayInputStream(response.getBytes()),
+ new ByteArrayInputStream(err.toByteArray()));
}
return result;
} finally {
@@ -144,4 +171,20 @@ public static SshResult sendExecCommand(Map<String,
Object> headers, String comm
}
+ private static String getPrompt(ClientChannel channel,
ByteArrayOutputStream output, SshEndpoint endpoint)
+ throws UnsupportedEncodingException, InterruptedException {
+
+ while (!channel.isClosed()) {
+
+ String response = new String(output.toByteArray(), "UTF-8");
+ if (response.trim().endsWith(endpoint.getShellPrompt())) {
+ output.reset();
+ return SshShellOutputStringHelper.betweenBeforeLast(response,
System.lineSeparator(), System.lineSeparator());
+ }
+
+ // avoid cpu burning cycles
+ Thread.sleep(endpoint.getSleepForShellPrompt());
+ }
+ return null;
+ }
}
diff --git
a/components/camel-ssh/src/main/java/org/apache/camel/component/ssh/SshShellOutputStringHelper.java
b/components/camel-ssh/src/main/java/org/apache/camel/component/ssh/SshShellOutputStringHelper.java
new file mode 100644
index 00000000000..e70a20b4531
--- /dev/null
+++
b/components/camel-ssh/src/main/java/org/apache/camel/component/ssh/SshShellOutputStringHelper.java
@@ -0,0 +1,100 @@
+/**
+ * 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.component.ssh;
+
+import java.util.Optional;
+import java.util.function.Function;
+
+import org.apache.camel.util.StringHelper;
+
+public final class SshShellOutputStringHelper {
+
+ private SshShellOutputStringHelper() {
+ // empty const
+ }
+
+ /**
+ * Returns the string before the given token
+ * If this token is repeating, than return all text
+ * before the last token
+ *
+ * @param text the text
+ * @param before the token which is expected to be repeated
+ * @return the text before the last token, or <tt>null</tt> if text does
not
+ * contain the token
+ */
+ public static String beforeLast(String text, String before) {
+ if (!text.contains(before)) {
+ return null;
+ }
+ return text.substring(0, text.lastIndexOf(before));
+ }
+
+
+ /**
+ * Returns an object before the given last token
+ *
+ * @param text the text
+ * @param before the last token
+ * @param mapper a mapping function to convert the string before the token
to type T
+ * @return an Optional describing the result of applying a mapping
function to the text before the token.
+ */
+ public static <T> Optional<T> beforeLast(String text, String beforeLast,
Function<String, T> mapper) {
+ String result = beforeLast(text, beforeLast);
+ if (result == null) {
+ return Optional.empty();
+ } else {
+ return Optional.ofNullable(mapper.apply(result));
+ }
+ }
+
+
+ /**
+ * Returns the string between the given tokens
+ *
+ * @param text the text
+ * @param after is the starting token to skip the text before that.
+ * @param before the last token
+ * @return the text between the tokens, or <tt>null</tt> if text does not
contain the tokens
+ */
+ public static String betweenBeforeLast(String text, String after, String
beforeLast) {
+ text = StringHelper.after(text, after);
+ if (text == null) {
+ return null;
+ }
+ return beforeLast(text, beforeLast);
+ }
+
+
+ /**
+ * Returns an object between the given token
+ *
+ * @param text the text
+ * @param after the before last token
+ * @param before the after token
+ * @param mapper a mapping function to convert the string between the
token to type T
+ * @return an Optional describing the result of applying a mapping
function to the text between the token.
+ */
+ public static <T> Optional<T> betweenBeforeLast(String text, String after,
String before, Function<String, T> mapper) {
+ String result = betweenBeforeLast(text, after, before);
+ if (result == null) {
+ return Optional.empty();
+ } else {
+ return Optional.ofNullable(mapper.apply(result));
+ }
+ }
+}
diff --git
a/components/camel-ssh/src/test/java/org/apache/camel/component/ssh/SshShellOutputStringHelperTest.java
b/components/camel-ssh/src/test/java/org/apache/camel/component/ssh/SshShellOutputStringHelperTest.java
new file mode 100644
index 00000000000..0953957098a
--- /dev/null
+++
b/components/camel-ssh/src/test/java/org/apache/camel/component/ssh/SshShellOutputStringHelperTest.java
@@ -0,0 +1,43 @@
+/**
+ * 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.component.ssh;
+
+import junit.framework.TestCase;
+
+public class SshShellOutputStringHelperTest extends TestCase {
+
+ public void testBeforeLast() {
+ assertEquals("Hello ", SshShellOutputStringHelper.beforeLast("Hello
World", "World"));
+ assertEquals("Hello World ",
SshShellOutputStringHelper.beforeLast("Hello World World", "World"));
+ assertEquals("Hello ", SshShellOutputStringHelper.beforeLast("Hello
World Again", "World"));
+ assertEquals(null, SshShellOutputStringHelper.beforeLast("Hello
Again", "Foo"));
+
+ assertTrue(SshShellOutputStringHelper.beforeLast("mykey:ignore:hello",
":", "mykey:ignore"::equals).orElse(false));
+
assertFalse(SshShellOutputStringHelper.beforeLast("ignore:ignore:world", ":",
"mykey"::equals).orElse(false));
+ }
+
+
+ public void testBetweenBeforeLast() {
+ assertEquals("foo bar' how are",
SshShellOutputStringHelper.betweenBeforeLast("Hello 'foo bar' how are' you",
"'", "'"));
+ assertEquals("foo bar",
SshShellOutputStringHelper.betweenBeforeLast("Hello ${foo bar} how are you",
"${", "}"));
+ assertEquals(null, SshShellOutputStringHelper.betweenBeforeLast("Hello
${foo bar} how are you", "'", "'"));
+
+
assertTrue(SshShellOutputStringHelper.betweenBeforeLast("begin:mykey:end:end",
"begin:", ":end", "mykey:end"::equals).orElse(false));
+
assertFalse(SshShellOutputStringHelper.betweenBeforeLast("begin:ignore:end:end",
"begin:", ":end", "mykey"::equals).orElse(false));
+ }
+
+}
----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
For queries about this service, please contact Infrastructure at:
[email protected]
> came-ssh - add support for Channel.CHANNEL_SHELL command execution
> ------------------------------------------------------------------
>
> Key: CAMEL-12629
> URL: https://issues.apache.org/jira/browse/CAMEL-12629
> Project: Camel
> Issue Type: New Feature
> Components: camel-ssh
> Reporter: Önder Sezgin
> Assignee: Önder Sezgin
> Priority: Minor
> Fix For: 2.23.0
>
>
--
This message was sent by Atlassian JIRA
(v7.6.3#76005)