You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@karaf.apache.org by jb...@apache.org on 2015/12/18 16:58:24 UTC

karaf git commit: KARAF-222 - Provide karaf:run, karaf:deploy, karaf:client Maven goals

Repository: karaf
Updated Branches:
  refs/heads/master 618a105e7 -> 335620adb


KARAF-222 - Provide karaf:run, karaf:deploy, karaf:client Maven goals


Project: http://git-wip-us.apache.org/repos/asf/karaf/repo
Commit: http://git-wip-us.apache.org/repos/asf/karaf/commit/335620ad
Tree: http://git-wip-us.apache.org/repos/asf/karaf/tree/335620ad
Diff: http://git-wip-us.apache.org/repos/asf/karaf/diff/335620ad

Branch: refs/heads/master
Commit: 335620adb7b1cc92380ec9cba7eb2dbbaa8bb96c
Parents: 618a105
Author: Jean-Baptiste Onofré <jb...@apache.org>
Authored: Fri Dec 18 16:57:37 2015 +0100
Committer: Jean-Baptiste Onofré <jb...@apache.org>
Committed: Fri Dec 18 16:58:09 2015 +0100

----------------------------------------------------------------------
 tooling/karaf-maven-plugin/pom.xml              |   8 +
 .../src/it/test-run-bundle/pom.xml              |  65 ++++
 .../src/main/java/test/Dummy.java               |  25 ++
 .../karaf-maven-plugin/src/it/test-run/pom.xml  |  52 ++++
 .../java/org/apache/karaf/tooling/RunMojo.java  | 304 +++++++++++++++++++
 .../apache/karaf/tooling/client/ClientMojo.java | 257 ++++++++++++++++
 .../apache/karaf/tooling/client/DeployMojo.java | 283 +++++++++++++++++
 7 files changed, 994 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/karaf/blob/335620ad/tooling/karaf-maven-plugin/pom.xml
----------------------------------------------------------------------
diff --git a/tooling/karaf-maven-plugin/pom.xml b/tooling/karaf-maven-plugin/pom.xml
index 86d6793..f0cc6fa 100644
--- a/tooling/karaf-maven-plugin/pom.xml
+++ b/tooling/karaf-maven-plugin/pom.xml
@@ -182,6 +182,14 @@
             <artifactId>org.apache.karaf.shell.console</artifactId>
         </dependency>
         <dependency>
+            <groupId>org.apache.karaf</groupId>
+            <artifactId>org.apache.karaf.main</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.karaf.diagnostic</groupId>
+            <artifactId>org.apache.karaf.diagnostic.boot</artifactId>
+        </dependency>
+        <dependency>
             <groupId>org.apache.commons</groupId>
             <artifactId>commons-compress</artifactId>
         </dependency>

http://git-wip-us.apache.org/repos/asf/karaf/blob/335620ad/tooling/karaf-maven-plugin/src/it/test-run-bundle/pom.xml
----------------------------------------------------------------------
diff --git a/tooling/karaf-maven-plugin/src/it/test-run-bundle/pom.xml b/tooling/karaf-maven-plugin/src/it/test-run-bundle/pom.xml
new file mode 100644
index 0000000..ad15e03
--- /dev/null
+++ b/tooling/karaf-maven-plugin/src/it/test-run-bundle/pom.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+    <!--
+
+        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.
+    -->
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>test</groupId>
+    <artifactId>test-run-bundle</artifactId>
+    <version>1.0-SNAPSHOT</version>
+    <packaging>bundle</packaging>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <version>2.5.4</version>
+                <inherited>true</inherited>
+                <extensions>true</extensions>
+                <configuration>
+                    <instructions>
+                        <Export-Package>*</Export-Package>
+                    </instructions>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.karaf.tooling</groupId>
+                <artifactId>karaf-maven-plugin</artifactId>
+                <version>@pom.version@</version>
+                <executions>
+                    <execution>
+                        <id>run</id>
+                        <phase>integration-test</phase>
+                        <goals>
+                            <goal>run</goal>
+                        </goals>
+                        <configuration>
+                            <karafDistribution>mvn:org.apache.karaf/apache-karaf/@pom.version@/zip</karafDistribution>
+                            <keepRunning>false</keepRunning>
+                            <deployProjectArtifact>true</deployProjectArtifact>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/karaf/blob/335620ad/tooling/karaf-maven-plugin/src/it/test-run-bundle/src/main/java/test/Dummy.java
----------------------------------------------------------------------
diff --git a/tooling/karaf-maven-plugin/src/it/test-run-bundle/src/main/java/test/Dummy.java b/tooling/karaf-maven-plugin/src/it/test-run-bundle/src/main/java/test/Dummy.java
new file mode 100644
index 0000000..082d7f2
--- /dev/null
+++ b/tooling/karaf-maven-plugin/src/it/test-run-bundle/src/main/java/test/Dummy.java
@@ -0,0 +1,25 @@
+/*
+ * 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 test;
+
+public class Dummy {
+
+    // nothing
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/karaf/blob/335620ad/tooling/karaf-maven-plugin/src/it/test-run/pom.xml
----------------------------------------------------------------------
diff --git a/tooling/karaf-maven-plugin/src/it/test-run/pom.xml b/tooling/karaf-maven-plugin/src/it/test-run/pom.xml
new file mode 100644
index 0000000..58d5905
--- /dev/null
+++ b/tooling/karaf-maven-plugin/src/it/test-run/pom.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+    <!--
+
+        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.
+    -->
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>test</groupId>
+    <artifactId>test-run</artifactId>
+    <version>1.0-SNAPSHOT</version>
+    <packaging>pom</packaging>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.karaf.tooling</groupId>
+                <artifactId>karaf-maven-plugin</artifactId>
+                <version>@pom.version@</version>
+                <executions>
+                    <execution>
+                        <id>run</id>
+                        <goals>
+                            <goal>run</goal>
+                        </goals>
+                        <configuration>
+                            <karafDistribution>mvn:org.apache.karaf/apache-karaf/@pom.version@/zip</karafDistribution>
+                            <keepRunning>false</keepRunning>
+                            <deployProjectArtifact>false</deployProjectArtifact>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/karaf/blob/335620ad/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/RunMojo.java
----------------------------------------------------------------------
diff --git a/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/RunMojo.java b/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/RunMojo.java
new file mode 100644
index 0000000..449c854
--- /dev/null
+++ b/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/RunMojo.java
@@ -0,0 +1,304 @@
+/*
+ * 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.karaf.tooling;
+
+import org.apache.commons.compress.archivers.ArchiveEntry;
+import org.apache.commons.compress.archivers.ArchiveInputStream;
+import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
+import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream;
+import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream;
+import org.apache.commons.compress.utils.IOUtils;
+import org.apache.commons.io.FileUtils;
+import org.apache.karaf.features.BootFinished;
+import org.apache.karaf.features.Feature;
+import org.apache.karaf.features.FeaturesListener;
+import org.apache.karaf.features.FeaturesService;
+import org.apache.karaf.main.Main;
+import org.apache.karaf.tooling.utils.MojoSupport;
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
+import org.apache.maven.artifact.resolver.ArtifactResolutionException;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.MojoFailureException;
+import org.apache.maven.plugins.annotations.LifecyclePhase;
+import org.apache.maven.plugins.annotations.Mojo;
+import org.apache.maven.plugins.annotations.Parameter;
+import org.apache.maven.plugins.annotations.ResolutionScope;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.util.tracker.ServiceTracker;
+
+import java.io.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Run a Karaf instance
+ */
+@Mojo(name = "run", defaultPhase = LifecyclePhase.PACKAGE, requiresDependencyResolution = ResolutionScope.RUNTIME)
+public class RunMojo extends MojoSupport {
+
+    /**
+     * Directory containing Karaf container base directory.
+     */
+    @Parameter(defaultValue = "${project.build.directory}/karaf")
+    private File karafDirectory = null;
+
+    /**
+     * Location where to download the Karaf distribution
+     */
+    @Parameter(defaultValue = "mvn:org.apache.karaf/apache-karaf/LATEST/zip")
+    private String karafDistribution = null;
+
+    /**
+     * Define if the project artifact should be deployed in the started container or not
+     */
+    @Parameter(defaultValue = "true")
+    private boolean deployProjectArtifact = true;
+
+    /**
+     * Define if the Karaf container keep running or stop just after the goal execution
+     */
+    @Parameter(defaultValue = "true")
+    private boolean keepRunning = true;
+
+    /**
+     * Define if the Karaf embedded sshd should be started or not
+     */
+    @Parameter(defaultValue = "false")
+    private String startSsh = "false";
+
+    private static final Pattern mvnPattern = Pattern.compile("mvn:([^/ ]+)/([^/ ]+)/([^/ ]*)(/([^/ ]+)(/([^/ ]+))?)?");
+
+    public void execute() throws MojoExecutionException, MojoFailureException {
+        if (karafDirectory.exists()) {
+            getLog().info("Using Karaf container located " + karafDirectory.getAbsolutePath());
+        } else {
+            getLog().info("Extracting Karaf container");
+            try {
+                File karafArchiveFile = resolveFile(karafDistribution);
+                extract(karafArchiveFile, karafDirectory);
+            } catch (Exception e) {
+                throw new MojoFailureException("Can't extract Karaf container", e);
+            }
+        }
+
+        getLog().info("Starting Karaf container");
+        System.setProperty("karaf.home", karafDirectory.getAbsolutePath());
+        System.setProperty("karaf.base", karafDirectory.getAbsolutePath());
+        System.setProperty("karaf.data", karafDirectory.getAbsolutePath() + "/data");
+        System.setProperty("karaf.etc", karafDirectory.getAbsolutePath() + "/etc");
+        System.setProperty("karaf.instances", karafDirectory.getAbsolutePath() + "/instances");
+        System.setProperty("karaf.startLocalConsole", "false");
+        System.setProperty("karaf.startRemoteShell", startSsh);
+        System.setProperty("karaf.lock", "false");
+        Main main = new Main(new String[0]);
+        try {
+            main.launch();
+            while (main.getFramework().getState() != Bundle.ACTIVE) {
+                Thread.sleep(1000);
+            }
+            BundleContext bundleContext = main.getFramework().getBundleContext();
+            Object bootFinished = null;
+            while (bootFinished == null) {
+                Thread.sleep(1000);
+                ServiceReference ref = bundleContext.getServiceReference(BootFinished.class);
+                if (ref != null) {
+                    bootFinished = bundleContext.getService(ref);
+                }
+            }
+            deploy(bundleContext);
+            if (keepRunning)
+                main.awaitShutdown();
+            main.destroy();
+        } catch (Throwable e) {
+            throw new MojoExecutionException("Can't start container", e);
+        } finally {
+            System.gc();
+        }
+    }
+
+    private void deploy(BundleContext bundleContext) throws MojoExecutionException {
+        if (deployProjectArtifact) {
+            File artifact = project.getArtifact().getFile();
+            if (artifact != null && artifact.exists()) {
+                if (project.getPackaging().equals("bundle")) {
+                    try {
+                        Bundle bundle = bundleContext.installBundle(artifact.toURI().toURL().toString());
+                        bundle.start();
+                    } catch (Exception e) {
+                        throw new MojoExecutionException("Can't deploy project artifact in container", e);
+                    }
+                } else {
+                    throw new MojoExecutionException("Packaging " + project.getPackaging() + " is not supported");
+                }
+            } else {
+                throw new MojoExecutionException("Project artifact doesn't exist");
+            }
+        }
+    }
+
+    public static void extract(File sourceFile, File targetFolder) throws IOException {
+        if (sourceFile.getAbsolutePath().indexOf(".zip") > 0) {
+            extractZipDistribution(sourceFile, targetFolder);
+        } else if (sourceFile.getAbsolutePath().indexOf(".tar.gz") > 0) {
+            extractTarGzDistribution(sourceFile, targetFolder);
+        } else {
+            throw new IllegalStateException("Unknown packaging of distribution; only zip or tar.gz could be handled.");
+        }
+        return;
+    }
+
+    private static void extractTarGzDistribution(File sourceDistribution, File _targetFolder)
+            throws IOException {
+        File uncompressedFile = File.createTempFile("uncompressedTarGz-", ".tar");
+        extractGzArchive(new FileInputStream(sourceDistribution), uncompressedFile);
+        extract(new TarArchiveInputStream(new FileInputStream(uncompressedFile)), _targetFolder);
+        FileUtils.forceDelete(uncompressedFile);
+    }
+
+    private static void extractZipDistribution(File sourceDistribution, File _targetFolder)
+            throws IOException {
+        extract(new ZipArchiveInputStream(new FileInputStream(sourceDistribution)), _targetFolder);
+    }
+
+    private static void extractGzArchive(InputStream tarGz, File tar) throws IOException {
+        BufferedInputStream in = new BufferedInputStream(tarGz);
+        FileOutputStream out = new FileOutputStream(tar);
+        GzipCompressorInputStream gzIn = new GzipCompressorInputStream(in);
+        final byte[] buffer = new byte[1000];
+        int n = 0;
+        while (-1 != (n = gzIn.read(buffer))) {
+            out.write(buffer, 0, n);
+        }
+        out.close();
+        gzIn.close();
+    }
+
+    private static void extract(ArchiveInputStream is, File targetDir) throws IOException {
+        try {
+            if (targetDir.exists()) {
+                FileUtils.forceDelete(targetDir);
+            }
+            targetDir.mkdirs();
+            ArchiveEntry entry = is.getNextEntry();
+            while (entry != null) {
+                String name = entry.getName();
+                name = name.substring(name.indexOf("/") + 1);
+                File file = new File(targetDir, name);
+                if (entry.isDirectory()) {
+                    file.mkdirs();
+                }
+                else {
+                    file.getParentFile().mkdirs();
+                    OutputStream os = new FileOutputStream(file);
+                    try {
+                        IOUtils.copy(is, os);
+                    }
+                    finally {
+                        IOUtils.closeQuietly(os);
+                    }
+                }
+                entry = is.getNextEntry();
+            }
+        }
+        finally {
+            is.close();
+        }
+    }
+
+    protected static boolean isMavenUrl(String name) {
+        Matcher m = mvnPattern.matcher(name);
+        return m.matches();
+    }
+
+    private File resolveFile(String file) {
+        File fileResolved = null;
+
+        if (isMavenUrl(file)) {
+            fileResolved = new File(fromMaven(file));
+            try {
+                Artifact artifactTemp = resourceToArtifact(file, false);
+                if (!fileResolved.exists()) {
+                    try {
+                        artifactResolver.resolve(artifactTemp, remoteRepos, localRepo);
+                        fileResolved = artifactTemp.getFile();
+                    } catch (ArtifactResolutionException e) {
+                        getLog().error("Artifact was not resolved", e);
+                    } catch (ArtifactNotFoundException e) {
+                        getLog().error("Artifact was not found", e);
+                    }
+                }
+            } catch (MojoExecutionException e) {
+                getLog().error(e);
+            }
+        } else {
+            fileResolved = new File(file);
+        }
+
+        return fileResolved;
+    }
+
+    /**
+     * Return a path for an artifact:
+     * - if the input is already a path (doesn't contain ':'), the same path is returned.
+     * - if the input is a Maven URL, the input is converted to a default repository location path, type and classifier
+     *   are optional.
+     *
+     * @param name artifact data
+     * @return path as supplied or a default Maven repository path
+     */
+    private static String fromMaven(String name) {
+        Matcher m = mvnPattern.matcher(name);
+        if (!m.matches()) {
+            return name;
+        }
+
+        StringBuilder b = new StringBuilder();
+        b.append(m.group(1));
+        for (int i = 0; i < b.length(); i++) {
+            if (b.charAt(i) == '.') {
+                b.setCharAt(i, '/');
+            }
+        }
+        b.append("/"); // groupId
+        String artifactId = m.group(2);
+        String version = m.group(3);
+        String extension = m.group(5);
+        String classifier = m.group(7);
+        b.append(artifactId).append("/"); // artifactId
+        b.append(version).append("/"); // version
+        b.append(artifactId).append("-").append(version);
+        if (present(classifier)) {
+            b.append("-").append(classifier);
+        }
+        if (present(classifier)) {
+            b.append(".").append(extension);
+        } else {
+            b.append(".jar");
+        }
+        return b.toString();
+    }
+
+    private static boolean present(String part) {
+        return part != null && !part.isEmpty();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/335620ad/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/client/ClientMojo.java
----------------------------------------------------------------------
diff --git a/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/client/ClientMojo.java b/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/client/ClientMojo.java
new file mode 100644
index 0000000..ae602c0
--- /dev/null
+++ b/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/client/ClientMojo.java
@@ -0,0 +1,257 @@
+/**
+ *
+ * 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.karaf.tooling.client;
+
+import org.apache.maven.plugin.AbstractMojo;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugins.annotations.LifecyclePhase;
+import org.apache.maven.plugins.annotations.Mojo;
+import org.apache.maven.plugins.annotations.Parameter;
+import org.apache.maven.plugins.annotations.ResolutionScope;
+import org.apache.sshd.ClientChannel;
+import org.apache.sshd.ClientSession;
+import org.apache.sshd.SshClient;
+import org.apache.sshd.agent.SshAgent;
+import org.apache.sshd.agent.local.AgentImpl;
+import org.apache.sshd.agent.local.LocalAgentFactory;
+import org.apache.sshd.client.UserInteraction;
+import org.apache.sshd.client.future.ConnectFuture;
+import org.apache.sshd.common.RuntimeSshException;
+import org.apache.sshd.common.keyprovider.FileKeyPairProvider;
+
+import org.fusesource.jansi.Ansi;
+import org.fusesource.jansi.Ansi.Color;
+import org.fusesource.jansi.AnsiConsole;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.Console;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOError;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.net.URL;
+import java.security.KeyPair;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Client MOJO to deployWithSsh command on a running Karaf instance
+ */
+@Mojo(name = "client", defaultPhase = LifecyclePhase.PACKAGE, requiresDependencyResolution = ResolutionScope.RUNTIME)
+public class ClientMojo extends AbstractMojo {
+
+    @Parameter(defaultValue = "8101")
+    private int port;
+
+    @Parameter(defaultValue = "localhost")
+    private String host;
+
+    @Parameter(defaultValue = "karaf")
+    private String user;
+
+    @Parameter(defaultValue = "karaf")
+    private String password;
+
+    @Parameter(defaultValue = "0")
+    private int attempts;
+
+    @Parameter(defaultValue = "2")
+    private int delay;
+
+    @Parameter
+    private List<String> commands;
+
+    @Parameter
+    private List<File> scripts;
+
+    @Parameter
+    private File keyFile;
+
+    private static final String NEW_LINE = System.getProperty("line.separator");
+
+    public void execute() throws MojoExecutionException {
+        // Add commands from scripts to already declared commands
+        for (File script : scripts) {
+            try (BufferedReader br = new BufferedReader(new FileReader(script))) {
+                String line;
+                while ((line = br.readLine()) != null) {
+                    line = line.trim();
+                    if (line.isEmpty()) {
+                        continue;
+                    }
+                    commands.add(line);
+                }
+            }
+            catch (Exception e) {
+                throw new MojoExecutionException(e, e.getMessage(), e.toString());
+            }
+        }
+
+        if (commands.isEmpty()) {
+            getLog().warn("No OSGi command was specified");
+            return;
+        }
+
+        StringWriter sw = new StringWriter();
+        PrintWriter pw = new PrintWriter(sw, true);
+        for (String cmd : commands) {
+            getLog().info(cmd);
+            pw.println(cmd);
+        }
+        execute(sw.toString());
+    }
+
+    protected void execute(String cmd) throws MojoExecutionException {
+        SshClient client = null;
+        try {
+            final Console console = System.console();
+            client = SshClient.setUpDefaultClient();
+            setupAgent(user, keyFile, client);
+
+            client.setUserInteraction( new UserInteraction() {
+                public void welcome(String banner) {
+                    console.printf(banner);
+                }
+
+                public String[] interactive(String destination, String name, String instruction, String[] prompt, boolean[] echo)
+                {
+                    String[] answers = new String[prompt.length];
+                    try {
+                        for (int i = 0; i < prompt.length; i++) {
+                            if (console != null) {
+                                if (echo[i]) {
+                                    answers[i] = console.readLine(prompt[i] + " ");
+                                }
+                                else {
+                                    answers[i] = new String( console.readPassword(prompt[i] + " "));
+                                }
+                            }
+                        }
+                    }
+                    catch (IOError e) {
+                    }
+                    return answers;
+                }
+            });
+            client.start();
+            if (console != null) {
+                console.printf("Logging in as %s\n", user);
+            }
+            ClientSession session = connect(client);
+            if (password != null) {
+                session.addPasswordIdentity(password);
+            }
+            session.auth().verify();
+
+            final ClientChannel channel = session.createChannel("exec", cmd.concat(NEW_LINE));
+            channel.setIn(new ByteArrayInputStream(new byte[0]));
+            final ByteArrayOutputStream sout = new ByteArrayOutputStream();
+            final ByteArrayOutputStream serr = new ByteArrayOutputStream();
+            channel.setOut( AnsiConsole.wrapOutputStream(sout));
+            channel.setErr( AnsiConsole.wrapOutputStream(serr));
+            channel.open();
+            channel.waitFor(ClientChannel.CLOSED, 0);
+
+            sout.writeTo(System.out);
+            serr.writeTo(System.err);
+
+            // Expects issue KARAF-2623 is fixed
+            final boolean isError = (channel.getExitStatus() != null && channel.getExitStatus().intValue() != 0);
+            if (isError) {
+                final String errorMarker = Ansi.ansi().fg(Color.RED).toString();
+                final int fromIndex = sout.toString().indexOf(errorMarker) + errorMarker.length();
+                final int toIndex = sout.toString().lastIndexOf(Ansi.ansi().fg(Color.DEFAULT ).toString());
+                throw new MojoExecutionException(NEW_LINE + sout.toString().substring(fromIndex, toIndex));
+            }
+        }
+        catch (MojoExecutionException e) {
+            throw e;
+        }
+        catch (Throwable t) {
+            throw new MojoExecutionException(t, t.getMessage(), t.toString());
+        }
+        finally {
+            try {
+                client.stop();
+            }
+            catch (Throwable t) {
+                throw new MojoExecutionException(t, t.getMessage(), t.toString());
+            }
+        }
+    }
+
+    private void setupAgent(String user, File keyFile, SshClient client) {
+        URL builtInPrivateKey = ClientMojo.class.getClassLoader().getResource("karaf.key");
+        SshAgent agent = startAgent(user, builtInPrivateKey, keyFile);
+        client.setAgentFactory( new LocalAgentFactory(agent));
+        client.getProperties().put(SshAgent.SSH_AUTHSOCKET_ENV_NAME, "local");
+    }
+
+    private SshAgent startAgent(String user, URL privateKeyUrl, File keyFile) {
+        try (InputStream is = privateKeyUrl.openStream())
+        {
+            SshAgent agent = new AgentImpl();
+            ObjectInputStream r = new ObjectInputStream(is);
+            KeyPair keyPair = (KeyPair) r.readObject();
+            is.close();
+            agent.addIdentity(keyPair, user);
+            if (keyFile != null) {
+                String[] keyFiles = new String[] { keyFile.getAbsolutePath() };
+                FileKeyPairProvider fileKeyPairProvider = new FileKeyPairProvider(keyFiles);
+                for (KeyPair key : fileKeyPairProvider.loadKeys()) {
+                    agent.addIdentity(key, user);
+                }
+            }
+            return agent;
+        }
+        catch (Throwable e) {
+            getLog().error("Error starting ssh agent for: " + e.getMessage(), e);
+            return null;
+        }
+    }
+
+    private ClientSession connect(SshClient client) throws IOException, InterruptedException {
+        int retries = 0;
+        ClientSession session = null;
+        do {
+            final ConnectFuture future = client.connect(user, host, port);
+            future.await();
+            try {
+                session = future.getSession();
+            }
+            catch (RuntimeSshException ex) {
+                if (retries++ < attempts) {
+                    Thread.sleep(TimeUnit.SECONDS.toMillis(delay));
+                    getLog().info("retrying (attempt " + retries + ") ...");
+                }
+                else {
+                    throw ex;
+                }
+            }
+        } while (session == null);
+        return session;
+    }
+
+}
+

http://git-wip-us.apache.org/repos/asf/karaf/blob/335620ad/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/client/DeployMojo.java
----------------------------------------------------------------------
diff --git a/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/client/DeployMojo.java b/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/client/DeployMojo.java
new file mode 100644
index 0000000..07b516c
--- /dev/null
+++ b/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/client/DeployMojo.java
@@ -0,0 +1,283 @@
+/**
+ *
+ * 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.karaf.tooling.client;
+
+import org.apache.karaf.tooling.utils.MojoSupport;
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.plugin.AbstractMojo;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugins.annotations.LifecyclePhase;
+import org.apache.maven.plugins.annotations.Mojo;
+import org.apache.maven.plugins.annotations.Parameter;
+import org.apache.maven.plugins.annotations.ResolutionScope;
+import org.apache.sshd.ClientChannel;
+import org.apache.sshd.ClientSession;
+import org.apache.sshd.SshClient;
+import org.apache.sshd.agent.SshAgent;
+import org.apache.sshd.agent.local.AgentImpl;
+import org.apache.sshd.agent.local.LocalAgentFactory;
+import org.apache.sshd.client.UserInteraction;
+import org.apache.sshd.client.future.ConnectFuture;
+import org.apache.sshd.common.RuntimeSshException;
+import org.apache.sshd.common.keyprovider.FileKeyPairProvider;
+
+import org.fusesource.jansi.Ansi;
+import org.fusesource.jansi.Ansi.Color;
+import org.fusesource.jansi.AnsiConsole;
+
+import javax.management.MBeanServerConnection;
+import javax.management.ObjectName;
+import javax.management.remote.JMXConnector;
+import javax.management.remote.JMXConnectorFactory;
+import javax.management.remote.JMXServiceURL;
+import javax.management.remote.MBeanServerForwarder;
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.Console;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOError;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.net.URL;
+import java.security.KeyPair;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Deploy MOJO to deploy an artifact remotely on a running Karaf instance, using ssh or JMX
+ */
+@Mojo(name = "deploy", defaultPhase = LifecyclePhase.PACKAGE, requiresDependencyResolution = ResolutionScope.RUNTIME)
+public class DeployMojo extends MojoSupport {
+
+    @Parameter(defaultValue = "8101")
+    private int port;
+
+    @Parameter(defaultValue = "localhost")
+    private String host;
+
+    @Parameter(defaultValue = "karaf")
+    private String user;
+
+    @Parameter(defaultValue = "karaf")
+    private String password;
+
+    @Parameter(defaultValue = "karaf-root")
+    private String instance = "karaf-root";
+
+    @Parameter(defaultValue = "0")
+    private int attempts;
+
+    @Parameter(defaultValue = "2")
+    private int delay;
+
+    @Parameter(defaultValue = "true")
+    private boolean useSsh = true;
+
+    @Parameter(defaultValue = "true")
+    private boolean useProjectArtifact = true;
+
+    @Parameter
+    List<String> artifactLocations;
+
+    @Parameter
+    private File keyFile;
+
+    private static final String NEW_LINE = System.getProperty("line.separator");
+
+    public void execute() throws MojoExecutionException {
+        List<String> artifacts = new ArrayList<>();
+        if (useProjectArtifact) {
+            Artifact projectArtifact = project.getArtifact();
+            artifacts.add("mvn:" + projectArtifact.getGroupId() + "/" + projectArtifact.getArtifactId() + "/" + projectArtifact.getVersion());
+        }
+        artifacts.addAll(artifactLocations);
+        if (useSsh)
+            deployWithSsh(artifactLocations);
+        else deployWithJmx(artifactLocations);
+    }
+
+    protected void deployWithJmx(List<String> locations) throws MojoExecutionException {
+        try {
+            JMXServiceURL jmxServiceURL = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://" + host + ":" + port + "/" + instance);
+            ArrayList<String> list = new ArrayList<>();
+            if (user != null)
+                list.add(user);
+            if (password != null)
+                list.add(password);
+            HashMap env = new HashMap();
+            String[] credentials = list.toArray(new String[list.size()]);
+            env.put(JMXConnector.CREDENTIALS, credentials);
+            JMXConnector jmxConnector = null;
+            if (credentials.length > 0)
+                jmxConnector = JMXConnectorFactory.connect(jmxServiceURL, env);
+            else jmxConnector = JMXConnectorFactory.connect(jmxServiceURL);
+            MBeanServerConnection mBeanServerConnection = jmxConnector.getMBeanServerConnection();
+            for (String location : locations) {
+                mBeanServerConnection.invoke(new ObjectName("org.apache.karaf:type=bundle,name=*"), "install", new Object[]{ location, true }, new String[]{ "java.lang.String", "boolean" });
+            }
+        } catch (Exception e) {
+            throw new MojoExecutionException("Can't deploy using JMX", e);
+        }
+    }
+
+    protected void deployWithSsh(List<String> locations) throws MojoExecutionException {
+        SshClient client = null;
+        try {
+            final Console console = System.console();
+            client = SshClient.setUpDefaultClient();
+            setupAgent(user, keyFile, client);
+
+            client.setUserInteraction( new UserInteraction() {
+                public void welcome(String banner) {
+                    console.printf(banner);
+                }
+
+                public String[] interactive(String destination, String name, String instruction, String[] prompt, boolean[] echo)
+                {
+                    String[] answers = new String[prompt.length];
+                    try {
+                        for (int i = 0; i < prompt.length; i++) {
+                            if (console != null) {
+                                if (echo[i]) {
+                                    answers[i] = console.readLine(prompt[i] + " ");
+                                }
+                                else {
+                                    answers[i] = new String( console.readPassword(prompt[i] + " "));
+                                }
+                            }
+                        }
+                    }
+                    catch (IOError e) {
+                    }
+                    return answers;
+                }
+            });
+            client.start();
+            if (console != null) {
+                console.printf("Logging in as %s\n", user);
+            }
+            ClientSession session = connect(client);
+            if (password != null) {
+                session.addPasswordIdentity(password);
+            }
+            session.auth().verify();
+
+            StringWriter writer = new StringWriter();
+            PrintWriter print = new PrintWriter(writer, true);
+            for (String location : locations) {
+                print.println("bundle:install -s " + location);
+            }
+
+            final ClientChannel channel = session.createChannel("exec", print.toString().concat(NEW_LINE));
+            channel.setIn(new ByteArrayInputStream(new byte[0]));
+            final ByteArrayOutputStream sout = new ByteArrayOutputStream();
+            final ByteArrayOutputStream serr = new ByteArrayOutputStream();
+            channel.setOut( AnsiConsole.wrapOutputStream(sout));
+            channel.setErr( AnsiConsole.wrapOutputStream(serr));
+            channel.open();
+            channel.waitFor(ClientChannel.CLOSED, 0);
+
+            sout.writeTo(System.out);
+            serr.writeTo(System.err);
+
+            // Expects issue KARAF-2623 is fixed
+            final boolean isError = (channel.getExitStatus() != null && channel.getExitStatus().intValue() != 0);
+            if (isError) {
+                final String errorMarker = Ansi.ansi().fg(Color.RED).toString();
+                final int fromIndex = sout.toString().indexOf(errorMarker) + errorMarker.length();
+                final int toIndex = sout.toString().lastIndexOf(Ansi.ansi().fg(Color.DEFAULT ).toString());
+                throw new MojoExecutionException(NEW_LINE + sout.toString().substring(fromIndex, toIndex));
+            }
+        }
+        catch (MojoExecutionException e) {
+            throw e;
+        }
+        catch (Throwable t) {
+            throw new MojoExecutionException(t, t.getMessage(), t.toString());
+        }
+        finally {
+            try {
+                client.stop();
+            }
+            catch (Throwable t) {
+                throw new MojoExecutionException(t, t.getMessage(), t.toString());
+            }
+        }
+    }
+
+    private void setupAgent(String user, File keyFile, SshClient client) {
+        URL builtInPrivateKey = ClientMojo.class.getClassLoader().getResource("karaf.key");
+        SshAgent agent = startAgent(user, builtInPrivateKey, keyFile);
+        client.setAgentFactory( new LocalAgentFactory(agent));
+        client.getProperties().put(SshAgent.SSH_AUTHSOCKET_ENV_NAME, "local");
+    }
+
+    private SshAgent startAgent(String user, URL privateKeyUrl, File keyFile) {
+        try (InputStream is = privateKeyUrl.openStream())
+        {
+            SshAgent agent = new AgentImpl();
+            ObjectInputStream r = new ObjectInputStream(is);
+            KeyPair keyPair = (KeyPair) r.readObject();
+            is.close();
+            agent.addIdentity(keyPair, user);
+            if (keyFile != null) {
+                String[] keyFiles = new String[] { keyFile.getAbsolutePath() };
+                FileKeyPairProvider fileKeyPairProvider = new FileKeyPairProvider(keyFiles);
+                for (KeyPair key : fileKeyPairProvider.loadKeys()) {
+                    agent.addIdentity(key, user);
+                }
+            }
+            return agent;
+        }
+        catch (Throwable e) {
+            getLog().error("Error starting ssh agent for: " + e.getMessage(), e);
+            return null;
+        }
+    }
+
+    private ClientSession connect(SshClient client) throws IOException, InterruptedException {
+        int retries = 0;
+        ClientSession session = null;
+        do {
+            final ConnectFuture future = client.connect(user, host, port);
+            future.await();
+            try {
+                session = future.getSession();
+            }
+            catch (RuntimeSshException ex) {
+                if (retries++ < attempts) {
+                    Thread.sleep(TimeUnit.SECONDS.toMillis(delay));
+                    getLog().info("retrying (attempt " + retries + ") ...");
+                }
+                else {
+                    throw ex;
+                }
+            }
+        } while (session == null);
+        return session;
+    }
+
+}
+