This is an automated email from the ASF dual-hosted git repository.

jbonofre pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/karaf-cave.git

commit e84d1a8aebe8002760f424602b45391daf5479ce
Author: Jean-Baptiste Onofré <[email protected]>
AuthorDate: Tue Dec 19 18:42:55 2017 +0100

    [KARAF-5108] Add Deployer shell commands
---
 assembly/src/main/resources/features.xml           |  5 ++
 .../apache/karaf/cave/deployer/api/Deployer.java   | 11 +++-
 deployer/command/pom.xml                           | 66 ++++++++++++++++++++++
 .../deployer/command/AssembleFeatureCommand.java   | 66 ++++++++++++++++++++++
 .../deployer/command/BundleInstallCommand.java     | 47 +++++++++++++++
 .../cave/deployer/command/BundleListCommand.java   | 63 +++++++++++++++++++++
 .../cave/deployer/command/BundleStartCommand.java  | 48 ++++++++++++++++
 .../cave/deployer/command/BundleStopCommand.java   | 48 ++++++++++++++++
 .../deployer/command/BundleUninstallCommand.java   | 48 ++++++++++++++++
 .../command/ClusterFeatureInstallCommand.java      | 51 +++++++++++++++++
 .../ClusterFeatureRepositoryAddCommand.java        | 51 +++++++++++++++++
 .../ClusterFeatureRepositoryRemoveCommand.java     | 51 +++++++++++++++++
 .../command/ClusterFeatureUninstallCommand.java    | 51 +++++++++++++++++
 .../deployer/command/ClusterGroupListCommand.java  | 60 ++++++++++++++++++++
 .../deployer/command/ClusterNodeListCommand.java   | 50 ++++++++++++++++
 .../cave/deployer/command/ConfigCreateCommand.java | 48 ++++++++++++++++
 .../cave/deployer/command/ConfigDeleteCommand.java | 48 ++++++++++++++++
 .../command/ConfigPropertyAppendCommand.java       | 54 ++++++++++++++++++
 .../command/ConfigPropertyDeleteCommand.java       | 51 +++++++++++++++++
 .../command/ConfigPropertyListCommand.java         | 53 +++++++++++++++++
 .../deployer/command/ConfigPropertySetCommand.java | 54 ++++++++++++++++++
 .../deployer/command/ConnectionDeleteCommand.java  | 45 +++++++++++++++
 .../deployer/command/ConnectionListCommand.java    | 52 +++++++++++++++++
 .../command/ConnectionRegisterCommand.java         | 61 ++++++++++++++++++++
 .../cave/deployer/command/DownloadCommand.java     | 45 +++++++++++++++
 .../cave/deployer/command/ExplodeCommand.java      | 45 +++++++++++++++
 .../cave/deployer/command/ExtractCommand.java      | 45 +++++++++++++++
 .../deployer/command/FeatureInstallCommand.java    | 48 ++++++++++++++++
 .../command/FeatureInstalledListCommand.java       | 50 ++++++++++++++++
 .../cave/deployer/command/FeatureListCommand.java  | 57 +++++++++++++++++++
 .../deployer/command/FeatureUninstallCommand.java  | 48 ++++++++++++++++
 .../command/FeaturesRepositoryAddCommand.java      | 48 ++++++++++++++++
 .../command/FeaturesRepositoryListCommand.java     | 56 ++++++++++++++++++
 .../command/FeaturesRepositoryRemoveCommand.java   | 48 ++++++++++++++++
 .../cave/deployer/command/KarInstallCommand.java   | 48 ++++++++++++++++
 .../cave/deployer/command/KarListCommand.java      | 50 ++++++++++++++++
 .../cave/deployer/command/KarUninstallCommand.java | 48 ++++++++++++++++
 .../karaf/cave/deployer/command/UploadCommand.java | 55 ++++++++++++++++++
 .../command/completers/ConnectionCompleter.java    | 49 ++++++++++++++++
 deployer/pom.xml                                   |  1 +
 .../karaf/cave/deployer/rest/DeployerRest.java     |  4 +-
 .../cave/deployer/service/impl/DeployerImpl.java   | 28 ++++++++-
 42 files changed, 1948 insertions(+), 7 deletions(-)

diff --git a/assembly/src/main/resources/features.xml 
b/assembly/src/main/resources/features.xml
index 81754b4..82e5529 100644
--- a/assembly/src/main/resources/features.xml
+++ b/assembly/src/main/resources/features.xml
@@ -68,6 +68,7 @@
 
     <feature name="cave-deployer" version="${project.version}">
         <feature version="${project.version}">cave-deployer-rest</feature>
+        <feature version="${project.version}">cave-deployer-command</feature>
     </feature>
 
     <feature name="cave-deployer-service" version="${project.version}">
@@ -90,5 +91,9 @@
         
<bundle>mvn:org.apache.karaf.cave.deployer/org.apache.karaf.cave.deployer.rest/${project.version}</bundle>
     </feature>
 
+    <feature name="cave-deployer-command" version="${project.version}">
+        <feature version="${project.version}">cave-deployer-service</feature>
+        
<bundle>mvn:org.apache.karaf.cave.deployer/org.apache.karaf.cave.deployer.command/${project.version}</bundle>
+    </feature>
 
 </features>
\ No newline at end of file
diff --git 
a/deployer/api/src/main/java/org/apache/karaf/cave/deployer/api/Deployer.java 
b/deployer/api/src/main/java/org/apache/karaf/cave/deployer/api/Deployer.java
index ee5778b..c21d5aa 100644
--- 
a/deployer/api/src/main/java/org/apache/karaf/cave/deployer/api/Deployer.java
+++ 
b/deployer/api/src/main/java/org/apache/karaf/cave/deployer/api/Deployer.java
@@ -32,6 +32,11 @@ public interface Deployer {
     void deleteConnection(String connectionName) throws Exception;
 
     /**
+     * Get the connections registered in the Deployer service.
+     */
+    List<Connection> connections() throws Exception;
+
+    /**
      * Explode a file (KAR or zip) and upload the content on a Maven 
repository.
      *
      * @param url The location of the file.
@@ -92,15 +97,15 @@ public interface Deployer {
                          List<Config> configs) throws Exception;
 
     /**
-     * A simple remote deployment operation for bundle. You can deploy a 
bundle to a remote Karaf instance.
+     * A simple remote deployment operation for bundle. You can install a 
bundle to a remote Karaf instance.
      */
-    void deployBundle(String artifactUrl, String connection)
+    void installBundle(String artifactUrl, String connection)
         throws Exception;
 
     /**
      * A simple remote undeploy operation for bundle. You can undeploy a 
bundle from a remote Karaf instance.
      */
-    void undeployBundle(String id, String connection) throws Exception;
+    void uninstallBundle(String id, String connection) throws Exception;
 
     /**
      * A simple remote start operation for bundle. You can start a bundle on a 
remote Karaf instance.
diff --git a/deployer/command/pom.xml b/deployer/command/pom.xml
new file mode 100644
index 0000000..d566b3e
--- /dev/null
+++ b/deployer/command/pom.xml
@@ -0,0 +1,66 @@
+<?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>
+
+    <parent>
+        <groupId>org.apache.karaf.cave</groupId>
+        <artifactId>org.apache.karaf.cave.deployer</artifactId>
+        <version>4.1.0-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <groupId>org.apache.karaf.cave.deployer</groupId>
+    <artifactId>org.apache.karaf.cave.deployer.command</artifactId>
+    <name>Apache Karaf :: Cave :: Deployer :: Command</name>
+    <packaging>bundle</packaging>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.karaf.shell</groupId>
+            <artifactId>org.apache.karaf.shell.core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.karaf.cave.deployer</groupId>
+            <artifactId>org.apache.karaf.cave.deployer.api</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.karaf.tooling</groupId>
+                <artifactId>karaf-services-maven-plugin</artifactId>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <configuration>
+                    <instructions>
+                        
<Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName>
+                    </instructions>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
\ No newline at end of file
diff --git 
a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/AssembleFeatureCommand.java
 
b/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/AssembleFeatureCommand.java
new file mode 100644
index 0000000..0badc22
--- /dev/null
+++ 
b/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/AssembleFeatureCommand.java
@@ -0,0 +1,66 @@
+/*
+ * 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.cave.deployer.command;
+
+import org.apache.karaf.cave.deployer.api.Deployer;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+
+import java.util.List;
+
+@Service
+@Command(scope = "cave", name = "deployer-assemble-feature", description = 
"Create/assembly a feature based on existing resources")
+public class AssembleFeatureCommand implements Action {
+
+    @Reference
+    private Deployer deployer;
+
+    @Option(name = "-g", aliases = "--groupId", description = "Maven groupId", 
required = true, multiValued = false)
+    String groupId;
+
+    @Option(name = "-a", aliases = "--artifactId", description = "Maven 
artifactId", required = true, multiValued = false)
+    String artifactId;
+
+    @Option(name = "-v", aliases = "--version", description = "Maven version", 
required = true, multiValued = false)
+    String version;
+
+    @Argument(index = 1, name = "repository", description = "The location of 
the repository where to upload the assembled feature", required = true, 
multiValued = false)
+    String repository;
+
+    @Argument(index = 0, name = "feature", description = "Name of the 
assembled feature", required = true, multiValued = false)
+    String feature;
+
+    @Option(name = "-r", aliases = "--repositories", description = "The list 
of features repositories to include in the assembled feature", required = 
false, multiValued = true)
+    List<String> repositories;
+
+    @Option(name = "-f", aliases = "--features", description = "The list of 
features to include in the assembled feature", required = false, multiValued = 
true)
+    List<String> features;
+
+    @Option(name = "-b", aliases = "--bundles", description = "The list of 
bundles to include in the assembled feature", required = false, multiValued = 
true)
+    List<String> bundles;
+
+    @Override
+    public Object execute() throws Exception {
+        deployer.assembleFeature(groupId, artifactId, version, repository, 
feature, repositories, features, bundles, null);
+        return null;
+    }
+
+}
diff --git 
a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/BundleInstallCommand.java
 
b/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/BundleInstallCommand.java
new file mode 100644
index 0000000..3e3616c
--- /dev/null
+++ 
b/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/BundleInstallCommand.java
@@ -0,0 +1,47 @@
+/*
+ * 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.cave.deployer.command;
+
+import org.apache.karaf.cave.deployer.api.Deployer;
+import org.apache.karaf.cave.deployer.command.completers.ConnectionCompleter;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+
+@Service
+@Command(scope = "cave", name = "deployer-bundle-install", description = 
"Install a bundle on a remote instance")
+public class BundleInstallCommand implements Action {
+
+    @Reference
+    private Deployer deployer;
+
+    @Argument(index = 0, name = "The connection to use", required = true, 
multiValued = false)
+    @Completion(ConnectionCompleter.class)
+    String connection;
+
+    @Argument(index = 1, name = "The URL of the bundle to deploy", required = 
true, multiValued = false)
+    String bundle;
+
+    @Override
+    public Object execute() throws Exception {
+        deployer.installBundle(bundle, connection);
+        return null;
+    }
+}
diff --git 
a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/BundleListCommand.java
 
b/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/BundleListCommand.java
new file mode 100644
index 0000000..5d9e38b
--- /dev/null
+++ 
b/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/BundleListCommand.java
@@ -0,0 +1,63 @@
+/*
+ * 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.cave.deployer.command;
+
+import org.apache.karaf.cave.deployer.api.Bundle;
+import org.apache.karaf.cave.deployer.api.Deployer;
+import org.apache.karaf.cave.deployer.command.completers.ConnectionCompleter;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.support.table.ShellTable;
+
+import java.util.List;
+
+@Service
+@Command(scope = "cave", name = "deployer-bundle-list", description = "List 
the bundles on a remote instance")
+public class BundleListCommand implements Action {
+
+    @Reference
+    private Deployer deployer;
+
+    @Argument(index = 0, name = "connection", description = "The connection to 
use", required = true, multiValued = false)
+    @Completion(ConnectionCompleter.class)
+    String connection;
+
+    @Override
+    public Object execute() throws Exception {
+        ShellTable table = new ShellTable();
+        table.column("ID");
+        table.column("Name");
+        table.column("Version");
+        table.column("Start Level");
+        table.column("State");
+
+        List<Bundle> bundles = deployer.bundles(connection);
+
+        for (Bundle bundle : bundles) {
+            table.addRow().addContent(bundle.getId(), bundle.getName(), 
bundle.getVersion(), bundle.getStartLevel(), bundle.getStartLevel());
+        }
+
+        table.print(System.out);
+
+        return null;
+    }
+
+}
diff --git 
a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/BundleStartCommand.java
 
b/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/BundleStartCommand.java
new file mode 100644
index 0000000..2ea9e7f
--- /dev/null
+++ 
b/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/BundleStartCommand.java
@@ -0,0 +1,48 @@
+/*
+ * 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.cave.deployer.command;
+
+import org.apache.karaf.cave.deployer.api.Deployer;
+import org.apache.karaf.cave.deployer.command.completers.ConnectionCompleter;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+
+@Service
+@Command(scope = "cave", name = "deployer-bundle-start", description = "Start 
a bundle on a remote instance")
+public class BundleStartCommand implements Action {
+
+    @Reference
+    private Deployer deployer;
+
+    @Argument(index = 0, name = "connection", description = "The connection to 
use", required = true, multiValued = false)
+    @Completion(ConnectionCompleter.class)
+    String connection;
+
+    @Argument(index = 1, name = "bundle", description = "The bundle to start", 
required = true, multiValued = false)
+    String bundle;
+
+    @Override
+    public Object execute() throws Exception {
+        deployer.startBundle(bundle, connection);
+        return null;
+    }
+
+}
diff --git 
a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/BundleStopCommand.java
 
b/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/BundleStopCommand.java
new file mode 100644
index 0000000..1368266
--- /dev/null
+++ 
b/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/BundleStopCommand.java
@@ -0,0 +1,48 @@
+/*
+ * 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.cave.deployer.command;
+
+import org.apache.karaf.cave.deployer.api.Deployer;
+import org.apache.karaf.cave.deployer.command.completers.ConnectionCompleter;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+
+@Service
+@Command(scope = "cave", name = "deployer-bundle-stop", description = "Stop a 
bundle on a remote instance")
+public class BundleStopCommand implements Action {
+
+    @Reference
+    private Deployer deployer;
+
+    @Argument(index = 0, name = "connection", description = "The connection to 
use", required = true, multiValued = false)
+    @Completion(ConnectionCompleter.class)
+    String connection;
+
+    @Argument(index = 1, name = "bundle", description = "The bundle to stop", 
required = true, multiValued = false)
+    String bundle;
+
+    @Override
+    public Object execute() throws Exception {
+        deployer.stopBundle(bundle, connection);
+        return null;
+    }
+
+}
diff --git 
a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/BundleUninstallCommand.java
 
b/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/BundleUninstallCommand.java
new file mode 100644
index 0000000..4ee4a1b
--- /dev/null
+++ 
b/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/BundleUninstallCommand.java
@@ -0,0 +1,48 @@
+/*
+ * 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.cave.deployer.command;
+
+import org.apache.karaf.cave.deployer.api.Deployer;
+import org.apache.karaf.cave.deployer.command.completers.ConnectionCompleter;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+
+@Service
+@Command(scope = "cave", name = "deployer-bundle-uninstall", description = 
"Uninstall a bundle from a remote instance")
+public class BundleUninstallCommand implements Action {
+
+    @Reference
+    private Deployer deployer;
+
+    @Argument(index = 0, name = "connection", description = "The connection to 
use", required = true, multiValued = false)
+    @Completion(ConnectionCompleter.class)
+    String connection;
+
+    @Argument(index = 1, name = "bundle", description = "The bundle ID to 
undeploy", required = true, multiValued = false)
+    String bundle;
+
+    @Override
+    public Object execute() throws Exception {
+        deployer.uninstallBundle(bundle, connection);
+        return null;
+    }
+
+}
diff --git 
a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ClusterFeatureInstallCommand.java
 
b/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ClusterFeatureInstallCommand.java
new file mode 100644
index 0000000..4fc92df
--- /dev/null
+++ 
b/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ClusterFeatureInstallCommand.java
@@ -0,0 +1,51 @@
+/*
+ * 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.cave.deployer.command;
+
+import org.apache.karaf.cave.deployer.api.Deployer;
+import org.apache.karaf.cave.deployer.command.completers.ConnectionCompleter;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+
+@Service
+@Command(scope = "cave", name = "deployer-cluster-feature-install", 
description = "Install a feature on a cluster group using a remote instance")
+public class ClusterFeatureInstallCommand implements Action {
+
+    @Reference
+    private Deployer deployer;
+
+    @Argument(index = 0, name = "connection", description = "The connection to 
use", required = true, multiValued = false)
+    @Completion(ConnectionCompleter.class)
+    String connection;
+
+    @Argument(index = 1, name = "cluster", description = "The target cluster 
group", required = true, multiValued = false)
+    String cluster;
+
+    @Argument(index = 2, name = "feature", description = "The feature to 
install", required = true, multiValued = false)
+    String feature;
+
+    @Override
+    public Object execute() throws Exception {
+        deployer.clusterFeatureInstall(feature, cluster, connection);
+        return null;
+    }
+
+}
diff --git 
a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ClusterFeatureRepositoryAddCommand.java
 
b/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ClusterFeatureRepositoryAddCommand.java
new file mode 100644
index 0000000..7feaff6
--- /dev/null
+++ 
b/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ClusterFeatureRepositoryAddCommand.java
@@ -0,0 +1,51 @@
+/*
+ * 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.cave.deployer.command;
+
+import org.apache.karaf.cave.deployer.api.Deployer;
+import org.apache.karaf.cave.deployer.command.completers.ConnectionCompleter;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+
+@Service
+@Command(scope = "cave", name = "deployer-cluster-feature-repo-add", 
description = "Add a features repository in a cluster using a remote instance")
+public class ClusterFeatureRepositoryAddCommand implements Action {
+
+    @Reference
+    private Deployer deployer;
+
+    @Argument(index = 0, name = "connection", description = "The connection to 
use", required = true, multiValued = false)
+    @Completion(ConnectionCompleter.class)
+    String connection;
+
+    @Argument(index = 1, name = "cluster", description = "The target cluster 
group", required = true, multiValued = false)
+    String cluster;
+
+    @Argument(index = 2, name = "repository", description = "The features 
repository to add", required = true, multiValued = false)
+    String repository;
+
+    @Override
+    public Object execute() throws Exception {
+        deployer.clusterAddFeaturesRepository(repository, cluster, connection);
+        return null;
+    }
+
+}
diff --git 
a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ClusterFeatureRepositoryRemoveCommand.java
 
b/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ClusterFeatureRepositoryRemoveCommand.java
new file mode 100644
index 0000000..a5f6059
--- /dev/null
+++ 
b/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ClusterFeatureRepositoryRemoveCommand.java
@@ -0,0 +1,51 @@
+/*
+ * 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.cave.deployer.command;
+
+import org.apache.karaf.cave.deployer.api.Deployer;
+import org.apache.karaf.cave.deployer.command.completers.ConnectionCompleter;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+
+@Service
+@Command(scope = "cave", name = "cluster-feature-repo-remove", description = 
"Remove a features repository from a cluster group using a remote instance")
+public class ClusterFeatureRepositoryRemoveCommand implements Action {
+
+    @Reference
+    private Deployer deployer;
+
+    @Argument(index = 0, name = "connection", description = "The connection to 
use", required = true, multiValued = false)
+    @Completion(ConnectionCompleter.class)
+    String connection;
+
+    @Argument(index = 1, name = "cluster", description = "The target cluster 
group", required = true, multiValued = false)
+    String cluster;
+
+    @Argument(index = 2, name = "repository", description = "The features 
repository to remove", required = true, multiValued = false)
+    String repository;
+
+    @Override
+    public Object execute() throws Exception {
+        deployer.clusterRemoveFeaturesRepository(repository, cluster, 
connection);
+        return null;
+    }
+
+}
diff --git 
a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ClusterFeatureUninstallCommand.java
 
b/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ClusterFeatureUninstallCommand.java
new file mode 100644
index 0000000..2e44ad4
--- /dev/null
+++ 
b/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ClusterFeatureUninstallCommand.java
@@ -0,0 +1,51 @@
+/*
+ * 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.cave.deployer.command;
+
+import org.apache.karaf.cave.deployer.api.Deployer;
+import org.apache.karaf.cave.deployer.command.completers.ConnectionCompleter;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+
+@Service
+@Command(scope = "cave", name = "deployer-cluster-feature-uninstall", 
description = "Uninstall a feature on a cluster group using a remote instance")
+public class ClusterFeatureUninstallCommand implements Action {
+
+    @Reference
+    private Deployer deployer;
+
+    @Argument(index = 0, name = "connection", description = "The connection to 
use", required = true, multiValued = false)
+    @Completion(ConnectionCompleter.class)
+    String connection;
+
+    @Argument(index = 1, name = "cluster", description = "The target cluster 
group", required = true, multiValued = false)
+    String cluster;
+
+    @Argument(index = 2, name = "feature", description = "The feature to 
uninstall", required = true, multiValued = false)
+    String feature;
+
+    @Override
+    public Object execute() throws Exception {
+        deployer.clusterFeatureUninstall(feature, cluster, connection);
+        return null;
+    }
+
+}
diff --git 
a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ClusterGroupListCommand.java
 
b/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ClusterGroupListCommand.java
new file mode 100644
index 0000000..df00ffc
--- /dev/null
+++ 
b/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ClusterGroupListCommand.java
@@ -0,0 +1,60 @@
+/*
+ * 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.cave.deployer.command;
+
+import org.apache.karaf.cave.deployer.api.Deployer;
+import org.apache.karaf.cave.deployer.command.completers.ConnectionCompleter;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.support.table.ShellTable;
+
+import java.util.List;
+import java.util.Map;
+
+@Service
+@Command(scope = "cave", name = "deployer-cluster-group-list", description = 
"List the cluster groups on a remote instance")
+public class ClusterGroupListCommand implements Action {
+
+    @Reference
+    private Deployer deployer;
+
+    @Argument(index = 0, name = "connection", description = "The connection to 
use", required = true, multiValued = false)
+    @Completion(ConnectionCompleter.class)
+    String connection;
+
+    @Override
+    public Object execute() throws Exception {
+        ShellTable table = new ShellTable();
+        table.column("Group");
+        table.column("Nodes");
+        Map<String, List<String>> groups = deployer.clusterGroups(connection);
+        for (String group : groups.keySet()) {
+            StringBuilder builder = new StringBuilder();
+            for (String node : groups.get(group)) {
+                builder.append(node).append(" ");
+            }
+            table.addRow().addContent(group, builder.toString());
+        }
+        table.print(System.out);
+        return null;
+    }
+
+}
diff --git 
a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ClusterNodeListCommand.java
 
b/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ClusterNodeListCommand.java
new file mode 100644
index 0000000..78a6bbb
--- /dev/null
+++ 
b/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ClusterNodeListCommand.java
@@ -0,0 +1,50 @@
+/*
+ * 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.cave.deployer.command;
+
+import org.apache.karaf.cave.deployer.api.Deployer;
+import org.apache.karaf.cave.deployer.command.completers.ConnectionCompleter;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+
+import java.util.List;
+
+@Service
+@Command(scope = "cave", name = "deployer-cluster-node-list", description = 
"List the cluster nodes seen by a remote instance")
+public class ClusterNodeListCommand implements Action {
+
+    @Reference
+    private Deployer deployer;
+
+    @Argument(index = 0, name = "connection", description = "The connection to 
use", required = true, multiValued = false)
+    @Completion(ConnectionCompleter.class)
+    String connection;
+
+    @Override
+    public Object execute() throws Exception {
+        List<String> nodes = deployer.clusterNodes(connection);
+        for (String node : nodes) {
+            System.out.println(node);
+        }
+        return null;
+    }
+
+}
diff --git 
a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ConfigCreateCommand.java
 
b/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ConfigCreateCommand.java
new file mode 100644
index 0000000..86e895b
--- /dev/null
+++ 
b/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ConfigCreateCommand.java
@@ -0,0 +1,48 @@
+/*
+ * 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.cave.deployer.command;
+
+import org.apache.karaf.cave.deployer.api.Deployer;
+import org.apache.karaf.cave.deployer.command.completers.ConnectionCompleter;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+
+@Service
+@Command(scope = "cave", name = "deployer-config-create", description = 
"Create a configuration on a remote instance")
+public class ConfigCreateCommand implements Action {
+
+    @Reference
+    private Deployer deployer;
+
+    @Argument(index = 0, name = "connection", description = "The connection to 
use", required = true, multiValued = false)
+    @Completion(ConnectionCompleter.class)
+    String connection;
+
+    @Argument(index = 1, name = "pid", description = "The pid of the 
configuration to create", required = true, multiValued = false)
+    String pid;
+
+    @Override
+    public Object execute() throws Exception {
+        deployer.createConfig(pid, connection);
+        return null;
+    }
+
+}
diff --git 
a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ConfigDeleteCommand.java
 
b/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ConfigDeleteCommand.java
new file mode 100644
index 0000000..a9c8704
--- /dev/null
+++ 
b/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ConfigDeleteCommand.java
@@ -0,0 +1,48 @@
+/*
+ * 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.cave.deployer.command;
+
+import org.apache.karaf.cave.deployer.api.Deployer;
+import org.apache.karaf.cave.deployer.command.completers.ConnectionCompleter;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+
+@Service
+@Command(scope = "cave", name = "deployer-config-delete", description = 
"Delete a configuration on a remote instance")
+public class ConfigDeleteCommand implements Action {
+
+    @Reference
+    private Deployer deployer;
+
+    @Argument(index = 0, name = "connection", description = "The connection to 
use", required = true, multiValued = false)
+    @Completion(ConnectionCompleter.class)
+    String connection;
+
+    @Argument(index = 1, name = "pid", description = "The configuration PID", 
required = true, multiValued = false)
+    String pid;
+
+    @Override
+    public Object execute() throws Exception {
+        deployer.deleteConfig(pid, connection);
+        return null;
+    }
+
+}
diff --git 
a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ConfigPropertyAppendCommand.java
 
b/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ConfigPropertyAppendCommand.java
new file mode 100644
index 0000000..ad575c8
--- /dev/null
+++ 
b/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ConfigPropertyAppendCommand.java
@@ -0,0 +1,54 @@
+/*
+ * 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.cave.deployer.command;
+
+import org.apache.karaf.cave.deployer.api.Deployer;
+import org.apache.karaf.cave.deployer.command.completers.ConnectionCompleter;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+
+@Service
+@Command(scope = "cave", name = "config-property-append", description = 
"Append a value to a configuration property on a remote instance")
+public class ConfigPropertyAppendCommand implements Action {
+
+    @Reference
+    private Deployer deployer;
+
+    @Argument(index = 0, name = "connection", description = "The connection to 
use", required = true, multiValued = false)
+    @Completion(ConnectionCompleter.class)
+    String connection;
+
+    @Argument(index = 1, name = "pid", description = "The configuration PID", 
required = true, multiValued = false)
+    String pid;
+
+    @Argument(index = 2, name = "key", description = "The configuration 
property key", required = true, multiValued = false)
+    String key;
+
+    @Argument(index = 3, name = "value", description = "The value to append to 
the property", required = true, multiValued = false)
+    String value;
+
+    @Override
+    public Object execute() throws Exception {
+        deployer.appendConfigProperty(pid, key, value, connection);
+        return null;
+    }
+
+}
diff --git 
a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ConfigPropertyDeleteCommand.java
 
b/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ConfigPropertyDeleteCommand.java
new file mode 100644
index 0000000..e7f084f
--- /dev/null
+++ 
b/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ConfigPropertyDeleteCommand.java
@@ -0,0 +1,51 @@
+/*
+ * 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.cave.deployer.command;
+
+import org.apache.karaf.cave.deployer.api.Deployer;
+import org.apache.karaf.cave.deployer.command.completers.ConnectionCompleter;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+
+@Service
+@Command(scope = "cave", name = "deployer-config-property-delete", description 
= "Delete a configuration property on a remote instance")
+public class ConfigPropertyDeleteCommand implements Action {
+
+    @Reference
+    private Deployer deployer;
+
+    @Argument(index = 0, name = "connection", description = "The connection to 
use", required = true, multiValued = false)
+    @Completion(ConnectionCompleter.class)
+    String connection;
+
+    @Argument(index = 1, name = "pid", description = "The configuration PID", 
required = true, multiValued = false)
+    String pid;
+
+    @Argument(index = 2, name = "key", description = "The property key", 
required = true, multiValued = false)
+    String key;
+
+    @Override
+    public Object execute() throws Exception {
+        deployer.deleteConfigProperty(pid, key, connection);
+        return null;
+    }
+
+}
diff --git 
a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ConfigPropertyListCommand.java
 
b/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ConfigPropertyListCommand.java
new file mode 100644
index 0000000..e4f5ed0
--- /dev/null
+++ 
b/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ConfigPropertyListCommand.java
@@ -0,0 +1,53 @@
+/*
+ * 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.cave.deployer.command;
+
+import org.apache.karaf.cave.deployer.api.Deployer;
+import org.apache.karaf.cave.deployer.command.completers.ConnectionCompleter;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+
+import java.util.Map;
+
+@Service
+@Command(scope = "cave", name = "deployer-config-property-list", description = 
"List the configuration properties on a remote instance")
+public class ConfigPropertyListCommand implements Action {
+
+    @Reference
+    private Deployer deployer;
+
+    @Argument(index = 0, name = "connection", description = "The connection to 
use", required = true, multiValued = false)
+    @Completion(ConnectionCompleter.class)
+    String connection;
+
+    @Argument(index = 1, name = "pid", description = "The configuration pid", 
required = true, multiValued = false)
+    String pid;
+
+    @Override
+    public Object execute() throws Exception {
+        Map<String, String> properties = deployer.configProperties(pid, 
connection);
+        for (String key : properties.keySet()) {
+            System.out.println(key + "=" + properties.get(key));
+        }
+        return null;
+    }
+
+}
diff --git 
a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ConfigPropertySetCommand.java
 
b/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ConfigPropertySetCommand.java
new file mode 100644
index 0000000..3e72cdc
--- /dev/null
+++ 
b/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ConfigPropertySetCommand.java
@@ -0,0 +1,54 @@
+/*
+ * 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.cave.deployer.command;
+
+import org.apache.karaf.cave.deployer.api.Deployer;
+import org.apache.karaf.cave.deployer.command.completers.ConnectionCompleter;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+
+@Service
+@Command(scope = "cave", name = "deployer-config-property-set", description = 
"Set the property value of a configuration on a remote instance")
+public class ConfigPropertySetCommand implements Action {
+
+    @Reference
+    private Deployer deployer;
+
+    @Argument(index = 0, name = "connection", description = "The connection to 
use", required = true, multiValued = false)
+    @Completion(ConnectionCompleter.class)
+    String connection;
+
+    @Argument(index = 1, name = "pid", description = "The configuration PID", 
required = true, multiValued = false)
+    String pid;
+
+    @Argument(index = 2, name = "key", description = "The configuration 
property key", required = true, multiValued = false)
+    String key;
+
+    @Argument(index = 3, name = "value", description = "The configuration 
property value", required = true, multiValued = false)
+    String value;
+
+    @Override
+    public Object execute() throws Exception {
+        deployer.setConfigProperty(pid, key, value, connection);
+        return null;
+    }
+
+}
diff --git 
a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ConnectionDeleteCommand.java
 
b/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ConnectionDeleteCommand.java
new file mode 100644
index 0000000..9c1b481
--- /dev/null
+++ 
b/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ConnectionDeleteCommand.java
@@ -0,0 +1,45 @@
+/*
+ * 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.cave.deployer.command;
+
+import org.apache.karaf.cave.deployer.api.Deployer;
+import org.apache.karaf.cave.deployer.command.completers.ConnectionCompleter;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+
+@Service
+@Command(scope = "cave", name = "deployer-connection-delete", description = 
"Remove a connection from the deployer service")
+public class ConnectionDeleteCommand implements Action {
+
+    @Reference
+    private Deployer deployer;
+
+    @Argument(index = 0, name = "connection", description = "The connection 
name", required = true, multiValued = false)
+    @Completion(ConnectionCompleter.class)
+    String connection;
+
+    @Override
+    public Object execute() throws Exception {
+        deployer.deleteConnection(connection);
+        return null;
+    }
+
+}
diff --git 
a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ConnectionListCommand.java
 
b/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ConnectionListCommand.java
new file mode 100644
index 0000000..b35ec93
--- /dev/null
+++ 
b/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ConnectionListCommand.java
@@ -0,0 +1,52 @@
+/*
+ * 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.cave.deployer.command;
+
+import org.apache.karaf.cave.deployer.api.Connection;
+import org.apache.karaf.cave.deployer.api.Deployer;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.support.table.ShellTable;
+
+@Service
+@Command(scope = "cave", name = "deployer-connection", description = "List of 
registered connections in the deployer service")
+public class ConnectionListCommand implements Action {
+
+    @Reference
+    Deployer deployer;
+
+    @Override
+    public Object execute() throws Exception {
+        ShellTable table = new ShellTable();
+        table.column("Name");
+        table.column("JMX URL");
+        table.column("Instance");
+        table.column("Username");
+        table.column("Password");
+
+        for (Connection connection : deployer.connections()) {
+            table.addRow().addContent(connection.getName(), 
connection.getJmxUrl(), connection.getKarafName(), connection.getUser(), 
connection.getPassword());
+        }
+
+        table.print(System.out);
+
+        return null;
+    }
+
+}
diff --git 
a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ConnectionRegisterCommand.java
 
b/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ConnectionRegisterCommand.java
new file mode 100644
index 0000000..6907ee8
--- /dev/null
+++ 
b/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ConnectionRegisterCommand.java
@@ -0,0 +1,61 @@
+/*
+ * 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.cave.deployer.command;
+
+import org.apache.karaf.cave.deployer.api.Connection;
+import org.apache.karaf.cave.deployer.api.Deployer;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+
+@Service
+@Command(scope = "cave", name = "deployer-connection-register", description = 
"Register a connection in the deployer service")
+public class ConnectionRegisterCommand implements Action {
+
+    @Reference
+    private Deployer deployer;
+
+    @Argument(index = 0, name = "name", description = "Name of the 
connection", required = true, multiValued = false)
+    String name;
+
+    @Argument(index = 1, name = "jmxUrl", description= "JMX URL of the Karaf 
instance", required = true, multiValued = false)
+    String jmxUrl;
+
+    @Argument(index = 2, name = "karafName", description = "Name of the Karaf 
instance", required = true, multiValued = false)
+    String karafName;
+
+    @Argument(index = 3, name = "username", description = "Username on the 
Karaf instance", required = true, multiValued = false)
+    String username;
+
+    @Argument(index = 4, name = "password", description = "Password on the 
Karaf instance", required = true, multiValued = false)
+    String password;
+
+    @Override
+    public Object execute() throws Exception {
+        Connection connection = new Connection();
+        connection.setName(name);
+        connection.setJmxUrl(jmxUrl);
+        connection.setKarafName(karafName);
+        connection.setUser(username);
+        connection.setPassword(password);
+        deployer.registerConnection(connection);
+        return null;
+    }
+
+}
diff --git 
a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/DownloadCommand.java
 
b/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/DownloadCommand.java
new file mode 100644
index 0000000..7dc48d5
--- /dev/null
+++ 
b/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/DownloadCommand.java
@@ -0,0 +1,45 @@
+/*
+ * 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.cave.deployer.command;
+
+import org.apache.karaf.cave.deployer.api.Deployer;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+
+@Service
+@Command(scope = "cave", name = "deployer-download", description = "Download 
an artifact to the local filesystem")
+public class DownloadCommand implements Action {
+
+    @Reference
+    private Deployer deployer;
+
+    @Argument(index = 0, name = "artifact", description = "The artifact URL", 
required = true, multiValued = false)
+    String artifact;
+
+    @Argument(index = 1, name = "directory", description = "The directory 
where to download the artifact", required = true, multiValued = false)
+    String directory;
+
+    @Override
+    public Object execute() throws Exception {
+        deployer.download(artifact, directory);
+        return null;
+    }
+
+}
diff --git 
a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ExplodeCommand.java
 
b/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ExplodeCommand.java
new file mode 100644
index 0000000..5dd9a7c
--- /dev/null
+++ 
b/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ExplodeCommand.java
@@ -0,0 +1,45 @@
+/*
+ * 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.cave.deployer.command;
+
+import org.apache.karaf.cave.deployer.api.Deployer;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+
+@Service
+@Command(scope = "cave", name = "deployer-explode", description = "Explode a 
file (KAR or zip) and upload the content on a Maven repository.")
+public class ExplodeCommand implements Action {
+
+    @Reference
+    private Deployer deployer;
+
+    @Argument(index = 0, name = "url", description = "The location of the 
file", required = true, multiValued = false)
+    String url;
+
+    @Argument(index = 1, name = "repository", description = "The location of 
the repository", required = true, multiValued = false)
+    String repository;
+
+    @Override
+    public Object execute() throws Exception {
+        deployer.explode(url, repository);
+        return null;
+    }
+
+}
diff --git 
a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ExtractCommand.java
 
b/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ExtractCommand.java
new file mode 100644
index 0000000..2bb921f
--- /dev/null
+++ 
b/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ExtractCommand.java
@@ -0,0 +1,45 @@
+/*
+ * 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.cave.deployer.command;
+
+import org.apache.karaf.cave.deployer.api.Deployer;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+
+@Service
+@Command(scope = "cave", name = "deployer-extract", description = "Extract a 
file (KAR or zip) to a local Karaf directory.")
+public class ExtractCommand implements Action {
+
+    @Reference
+    private Deployer deployer;
+
+    @Argument(index = 0, name = "url", description = "The location of the 
file", required = true, multiValued = false)
+    String url;
+
+    @Argument(index = 1, name = "directory", description = "The location of 
the directory where to extract", required = true, multiValued = false)
+    String directory;
+
+    @Override
+    public Object execute() throws Exception {
+        deployer.extract(url, directory);
+        return null;
+    }
+
+}
diff --git 
a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/FeatureInstallCommand.java
 
b/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/FeatureInstallCommand.java
new file mode 100644
index 0000000..fe5d9be
--- /dev/null
+++ 
b/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/FeatureInstallCommand.java
@@ -0,0 +1,48 @@
+/*
+ * 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.cave.deployer.command;
+
+import org.apache.karaf.cave.deployer.api.Deployer;
+import org.apache.karaf.cave.deployer.command.completers.ConnectionCompleter;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+
+@Service
+@Command(scope = "cave", name = "deployer-feature-install", description = 
"Install a feature on a remote instance")
+public class FeatureInstallCommand implements Action {
+
+    @Reference
+    private Deployer deployer;
+
+    @Argument(index = 0, name = "connection", description = "The connection to 
use", required = true, multiValued = false)
+    @Completion(ConnectionCompleter.class)
+    String connection;
+
+    @Argument(index = 1, name ="feature", description = "The feature to 
install", required = true, multiValued = false)
+    String feature;
+
+    @Override
+    public Object execute() throws Exception {
+        deployer.installFeature(feature, connection);
+        return null;
+    }
+
+}
diff --git 
a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/FeatureInstalledListCommand.java
 
b/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/FeatureInstalledListCommand.java
new file mode 100644
index 0000000..4030a3a
--- /dev/null
+++ 
b/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/FeatureInstalledListCommand.java
@@ -0,0 +1,50 @@
+/*
+ * 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.cave.deployer.command;
+
+import org.apache.karaf.cave.deployer.api.Deployer;
+import org.apache.karaf.cave.deployer.command.completers.ConnectionCompleter;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+
+import java.util.List;
+
+@Service
+@Command(scope = "cave", name = "deployer-feature-installed-list", description 
= "List the features installed on a remote host")
+public class FeatureInstalledListCommand implements Action {
+
+    @Reference
+    private Deployer deployer;
+
+    @Argument(index = 0, name = "connection", description = "The connection to 
use", required = true, multiValued = false)
+    @Completion(ConnectionCompleter.class)
+    String connection;
+
+    @Override
+    public Object execute() throws Exception {
+        List<String> features = deployer.installedFeatures(connection);
+        for (String feature : features) {
+            System.out.println(feature);
+        }
+        return null;
+    }
+
+}
diff --git 
a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/FeatureListCommand.java
 
b/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/FeatureListCommand.java
new file mode 100644
index 0000000..1d1c181
--- /dev/null
+++ 
b/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/FeatureListCommand.java
@@ -0,0 +1,57 @@
+/*
+ * 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.cave.deployer.command;
+
+import org.apache.karaf.cave.deployer.api.Deployer;
+import org.apache.karaf.cave.deployer.api.Feature;
+import org.apache.karaf.cave.deployer.command.completers.ConnectionCompleter;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.support.table.ShellTable;
+
+import java.util.List;
+
+@Service
+@Command(scope = "cave", name = "deployer-feature-list", description = "List 
the features on a remote instance")
+public class FeatureListCommand implements Action {
+
+    @Reference
+    private Deployer deployer;
+
+    @Argument(index = 0, name = "connection", description = "The connection to 
use", required = true, multiValued = false)
+    @Completion(ConnectionCompleter.class)
+    String connection;
+
+    @Override
+    public Object execute() throws Exception {
+        ShellTable table = new ShellTable();
+        table.column("Name");
+        table.column("Version");
+        table.column("State");
+        List<Feature> features = deployer.features(connection);
+        for (Feature feature : features) {
+            table.addRow().addContent(feature.getName(), feature.getVersion(), 
feature.getState());
+        }
+        table.print(System.out);
+        return null;
+    }
+
+}
diff --git 
a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/FeatureUninstallCommand.java
 
b/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/FeatureUninstallCommand.java
new file mode 100644
index 0000000..b8c5a0b
--- /dev/null
+++ 
b/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/FeatureUninstallCommand.java
@@ -0,0 +1,48 @@
+/*
+ * 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.cave.deployer.command;
+
+import org.apache.karaf.cave.deployer.api.Deployer;
+import org.apache.karaf.cave.deployer.command.completers.ConnectionCompleter;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+
+@Service
+@Command(scope = "cave", name = "deployer-feature-uninstall", description = 
"Uninstall a feature from a remote instance")
+public class FeatureUninstallCommand implements Action {
+
+    @Reference
+    private Deployer deployer;
+
+    @Argument(index = 0, name = "connection", description = "The connection to 
use", required = true, multiValued = false)
+    @Completion(ConnectionCompleter.class)
+    String connection;
+
+    @Argument(index = 1, name = "feature", description = "The feature to 
uninstall", required = true, multiValued = false)
+    String feature;
+
+    @Override
+    public Object execute() throws Exception {
+        deployer.uninstallFeature(feature, connection);
+        return null;
+    }
+
+}
diff --git 
a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/FeaturesRepositoryAddCommand.java
 
b/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/FeaturesRepositoryAddCommand.java
new file mode 100644
index 0000000..0e6b7a4
--- /dev/null
+++ 
b/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/FeaturesRepositoryAddCommand.java
@@ -0,0 +1,48 @@
+/*
+ * 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.cave.deployer.command;
+
+import org.apache.karaf.cave.deployer.api.Deployer;
+import org.apache.karaf.cave.deployer.command.completers.ConnectionCompleter;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+
+@Service
+@Command(scope = "cave", name = "deployer-feature-repo-add", description = 
"Add a features repository to a remote instance")
+public class FeaturesRepositoryAddCommand implements Action {
+
+    @Reference
+    private Deployer deployer;
+
+    @Argument(index = 0, name = "connection", description = "The connection to 
use", required = true, multiValued = false)
+    @Completion(ConnectionCompleter.class)
+    String connection;
+
+    @Argument(index = 1, name = "repository", description = "The features 
repository URL", required = true, multiValued = false)
+    String repository;
+
+    @Override
+    public Object execute() throws Exception {
+        deployer.addFeaturesRepository(repository, connection);
+        return null;
+    }
+
+}
diff --git 
a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/FeaturesRepositoryListCommand.java
 
b/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/FeaturesRepositoryListCommand.java
new file mode 100644
index 0000000..d1288cb
--- /dev/null
+++ 
b/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/FeaturesRepositoryListCommand.java
@@ -0,0 +1,56 @@
+/*
+ * 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.cave.deployer.command;
+
+import org.apache.karaf.cave.deployer.api.Deployer;
+import org.apache.karaf.cave.deployer.api.FeaturesRepository;
+import org.apache.karaf.cave.deployer.command.completers.ConnectionCompleter;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.support.table.ShellTable;
+
+import java.util.List;
+
+@Service
+@Command(scope = "cave", name = "deployer-features-repo-list", description = 
"List the features repositories on a remote instance")
+public class FeaturesRepositoryListCommand implements Action {
+
+    @Reference
+    private Deployer deployer;
+
+    @Argument(index = 0, name = "connection", description = "The connection to 
use", required = true, multiValued = false)
+    @Completion(ConnectionCompleter.class)
+    String connection;
+
+    @Override
+    public Object execute() throws Exception {
+        ShellTable table = new ShellTable();
+        table.column("Name");
+        table.column("URI");
+        List<FeaturesRepository> repositories = 
deployer.featuresRepositories(connection);
+        for (FeaturesRepository repository : repositories) {
+            table.addRow().addContent(repository.getName(), 
repository.getUri());
+        }
+        table.print(System.out);
+        return null;
+    }
+
+}
diff --git 
a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/FeaturesRepositoryRemoveCommand.java
 
b/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/FeaturesRepositoryRemoveCommand.java
new file mode 100644
index 0000000..235540e
--- /dev/null
+++ 
b/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/FeaturesRepositoryRemoveCommand.java
@@ -0,0 +1,48 @@
+/*
+ * 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.cave.deployer.command;
+
+import org.apache.karaf.cave.deployer.api.Deployer;
+import org.apache.karaf.cave.deployer.command.completers.ConnectionCompleter;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+
+@Service
+@Command(scope = "cave", name = "deployer-feature-repo-remove", description = 
"Remove a features repository from a remote instance")
+public class FeaturesRepositoryRemoveCommand implements Action {
+
+    @Reference
+    private Deployer deployer;
+
+    @Argument(index = 0, name = "connection", description = "The connection to 
use", required = true, multiValued = false)
+    @Completion(ConnectionCompleter.class)
+    String connection;
+
+    @Argument(index = 1, name = "repository", description = "The features 
repository to remove", required = true, multiValued = false)
+    String repository;
+
+    @Override
+    public Object execute() throws Exception {
+        deployer.removeFeaturesRepository(repository, connection);
+        return null;
+    }
+
+}
diff --git 
a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/KarInstallCommand.java
 
b/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/KarInstallCommand.java
new file mode 100644
index 0000000..fd1ee6d
--- /dev/null
+++ 
b/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/KarInstallCommand.java
@@ -0,0 +1,48 @@
+/*
+ * 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.cave.deployer.command;
+
+import org.apache.karaf.cave.deployer.api.Deployer;
+import org.apache.karaf.cave.deployer.command.completers.ConnectionCompleter;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+
+@Service
+@Command(scope = "cave", name = "deployer-kar-install", description = "Install 
a KAR on a remote instance")
+public class KarInstallCommand implements Action {
+
+    @Reference
+    private Deployer deployer;
+
+    @Argument(index = 0, name = "connection", description = "The connection to 
use", required = true, multiValued = false)
+    @Completion(ConnectionCompleter.class)
+    String connection;
+
+    @Argument(index = 1, name ="kar", description = "The location (URL) of the 
KAR to install", required = true, multiValued = false)
+    String kar;
+
+    @Override
+    public Object execute() throws Exception {
+        deployer.installKar(kar, connection);
+        return null;
+    }
+
+}
diff --git 
a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/KarListCommand.java
 
b/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/KarListCommand.java
new file mode 100644
index 0000000..a488d08
--- /dev/null
+++ 
b/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/KarListCommand.java
@@ -0,0 +1,50 @@
+/*
+ * 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.cave.deployer.command;
+
+import org.apache.karaf.cave.deployer.api.Deployer;
+import org.apache.karaf.cave.deployer.command.completers.ConnectionCompleter;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+
+import java.util.List;
+
+@Service
+@Command(scope = "cave", name = "deployer-kar-list", description = "List the 
kars installed on a remote instance")
+public class KarListCommand implements Action {
+
+    @Reference
+    private Deployer deployer;
+
+    @Argument(index = 0, name = "connection", description = "The connection to 
use", required = true, multiValued = false)
+    @Completion(ConnectionCompleter.class)
+    String connection;
+
+    @Override
+    public Object execute() throws Exception {
+        List<String> kars = deployer.kars(connection);
+        for (String kar : kars) {
+            System.out.println(kar);
+        }
+        return null;
+    }
+
+}
diff --git 
a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/KarUninstallCommand.java
 
b/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/KarUninstallCommand.java
new file mode 100644
index 0000000..5ba1525
--- /dev/null
+++ 
b/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/KarUninstallCommand.java
@@ -0,0 +1,48 @@
+/*
+ * 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.cave.deployer.command;
+
+import org.apache.karaf.cave.deployer.api.Deployer;
+import org.apache.karaf.cave.deployer.command.completers.ConnectionCompleter;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+
+@Service
+@Command(scope = "cave", name = "deployer-kar-uninstall", description = 
"Uninstall a kar from a remote instance")
+public class KarUninstallCommand implements Action {
+
+    @Reference
+    private Deployer deployer;
+
+    @Argument(index = 0, name = "connection", description = "The connection to 
use", required = true, multiValued = false)
+    @Completion(ConnectionCompleter.class)
+    String connection;
+
+    @Argument(index = 1, name = "kar", description = "The kar to uninstall", 
required = true, multiValued = false)
+    String kar;
+
+    @Override
+    public Object execute() throws Exception {
+        deployer.uninstallKar(kar, connection);
+        return null;
+    }
+
+}
diff --git 
a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/UploadCommand.java
 
b/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/UploadCommand.java
new file mode 100644
index 0000000..da98973
--- /dev/null
+++ 
b/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/UploadCommand.java
@@ -0,0 +1,55 @@
+/*
+ * 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.cave.deployer.command;
+
+import org.apache.karaf.cave.deployer.api.Deployer;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+
+@Service
+@Command(scope = "cave", name = "deployer-upload", description = "Upload an 
artifact to a Maven repository using the given coordinates")
+public class UploadCommand implements Action {
+
+    @Reference
+    private Deployer deployer;
+
+    @Option(name = "-g", aliases = "--groupId", description = "Maven groupId", 
required = true, multiValued = false)
+    String groupId;
+
+    @Option(name = "-a", aliases = "--artifactId", description = "Maven 
artifactId", required = true, multiValued = false)
+    String artifactId;
+
+    @Option(name = "-v", aliases = "--version", description = "Maven version", 
required = true, multiValued = false)
+    String version;
+
+    @Argument(index = 0, name = "artifact", description = "Location of the 
artifact", required = true, multiValued = false)
+    String artifact;
+
+    @Argument(index = 1, name = "repository", description = "Location of the 
Maven repository", required = true, multiValued = false)
+    String repository;
+
+    @Override
+    public Object execute() throws Exception {
+        deployer.upload(groupId, artifactId, version, artifact, repository);
+        return null;
+    }
+
+}
diff --git 
a/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/completers/ConnectionCompleter.java
 
b/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/completers/ConnectionCompleter.java
new file mode 100644
index 0000000..1a072e1
--- /dev/null
+++ 
b/deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/completers/ConnectionCompleter.java
@@ -0,0 +1,49 @@
+/*
+ * 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.cave.deployer.command.completers;
+
+import org.apache.karaf.cave.deployer.api.Connection;
+import org.apache.karaf.cave.deployer.api.Deployer;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.api.console.CommandLine;
+import org.apache.karaf.shell.api.console.Completer;
+import org.apache.karaf.shell.api.console.Session;
+import org.apache.karaf.shell.support.completers.StringsCompleter;
+
+import java.util.List;
+
+@Service
+public class ConnectionCompleter implements Completer {
+
+    @Reference
+    private Deployer deployer;
+
+    @Override
+    public int complete(Session session, CommandLine commandLine, List<String> 
list) {
+        StringsCompleter delegate = new StringsCompleter();
+        try {
+            for (Connection connection : deployer.connections()) {
+                delegate.getStrings().add(connection.getName());
+            }
+        } catch (Exception e) {
+            // ignore
+        }
+        return delegate.complete(session, commandLine, list);
+    }
+
+}
diff --git a/deployer/pom.xml b/deployer/pom.xml
index 500bd50..9663f47 100644
--- a/deployer/pom.xml
+++ b/deployer/pom.xml
@@ -36,6 +36,7 @@
     <modules>
         <module>api</module>
         <module>service</module>
+        <module>command</module>
         <module>rest</module>
     </modules>
 
diff --git 
a/deployer/rest/src/main/java/org/apache/karaf/cave/deployer/rest/DeployerRest.java
 
b/deployer/rest/src/main/java/org/apache/karaf/cave/deployer/rest/DeployerRest.java
index 6a15c48..2b4d67b 100644
--- 
a/deployer/rest/src/main/java/org/apache/karaf/cave/deployer/rest/DeployerRest.java
+++ 
b/deployer/rest/src/main/java/org/apache/karaf/cave/deployer/rest/DeployerRest.java
@@ -179,13 +179,13 @@ public class DeployerRest {
     @Path("/{connection}/bundle/{url}")
     @POST
     public void deployBundle(@PathParam("connection") String connection, 
@PathParam("url") String url) throws Exception {
-        deployer.deployBundle(url, connection);
+        deployer.installBundle(url, connection);
     }
 
     @Path("/{connection}/bundle/{id}")
     @DELETE
     public void undeployBundle(@PathParam("connection") String connection, 
@PathParam("id") String id) throws Exception {
-        deployer.undeployBundle(id, connection);
+        deployer.uninstallBundle(id, connection);
     }
 
     @Path("/{connection}/bundle/{id}/start")
diff --git 
a/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/impl/DeployerImpl.java
 
b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/impl/DeployerImpl.java
index 23002e8..89ec0c8 100644
--- 
a/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/impl/DeployerImpl.java
+++ 
b/deployer/service/src/main/java/org/apache/karaf/cave/deployer/service/impl/DeployerImpl.java
@@ -93,6 +93,30 @@ public class DeployerImpl implements Deployer {
         configuration.update(properties);
     }
 
+    @Override
+    public List<Connection> connections() throws Exception {
+        List<Connection> connections = new ArrayList<>();
+
+        Configuration configuration = 
configurationAdmin.getConfiguration(CONFIG_PID);
+        Dictionary<String, Object> properties = configuration.getProperties();
+        Enumeration<String> keys = properties.keys();
+        while (keys.hasMoreElements()) {
+            String key = keys.nextElement();
+            if (key.endsWith(".jmx")) {
+                String connectionName = key.substring(0, key.indexOf(".jmx"));
+                Connection connection = new Connection();
+                connection.setName(connectionName);
+                connection.setJmxUrl((String) properties.get(connectionName + 
".jmx"));
+                connection.setKarafName((String) properties.get(connectionName 
+ ".instance"));
+                connection.setUser((String) properties.get(connectionName + 
".username"));
+                connection.setPassword((String) properties.get(connectionName 
+ ".password"));
+                connections.add(connection);
+            }
+        }
+
+        return connections;
+    }
+
     private Connection getConnection(String name) throws Exception {
         Connection connection = new Connection();
         Configuration configuration = 
configurationAdmin.getConfiguration(CONFIG_PID);
@@ -418,7 +442,7 @@ public class DeployerImpl implements Deployer {
     }
 
     @Override
-    public void deployBundle(String artifactUrl, String connectionName) throws 
Exception {
+    public void installBundle(String artifactUrl, String connectionName) 
throws Exception {
         Connection connection = getConnection(connectionName);
         JMXConnector jmxConnector = connect(connection.getJmxUrl(),
                 connection.getKarafName(),
@@ -436,7 +460,7 @@ public class DeployerImpl implements Deployer {
     }
 
     @Override
-    public void undeployBundle(String id, String connectionName) throws 
Exception {
+    public void uninstallBundle(String id, String connectionName) throws 
Exception {
         Connection connection = getConnection(connectionName);
         JMXConnector jmxConnector = connect(connection.getJmxUrl(),
                 connection.getKarafName(),

-- 
To stop receiving notification emails like this one, please contact
"[email protected]" <[email protected]>.

Reply via email to