You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@karaf.apache.org by jb...@apache.org on 2017/12/21 08:10:53 UTC

[karaf-cave] branch master updated (6e89213 -> 9278931)

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

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


    from 6e89213  Use a generic extract approach in the deployer service
     new e84d1a8  [KARAF-5108] Add Deployer shell commands
     new 9278931  [KARAF-5108] Add Deployer MBean

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 assembly/src/main/resources/features.xml           |  17 ++
 .../apache/karaf/cave/deployer/api/Deployer.java   |  11 +-
 {server => deployer}/command/pom.xml               |  15 +-
 .../deployer/command/AssembleFeatureCommand.java   |  66 +++++
 .../deployer/command/BundleInstallCommand.java     |  26 +-
 .../cave/deployer/command/BundleListCommand.java   |  40 ++-
 .../cave/deployer/command/BundleStartCommand.java  |  25 +-
 .../cave/deployer/command/BundleStopCommand.java   |  25 +-
 .../deployer/command/BundleUninstallCommand.java   |  25 +-
 .../command/ClusterFeatureInstallCommand.java      |  28 +-
 .../ClusterFeatureRepositoryAddCommand.java        |  28 +-
 .../ClusterFeatureRepositoryRemoveCommand.java     |  28 +-
 .../command/ClusterFeatureUninstallCommand.java    |  28 +-
 .../deployer/command/ClusterGroupListCommand.java  |  37 ++-
 .../deployer/command/ClusterNodeListCommand.java   |  27 +-
 .../cave/deployer/command/ConfigCreateCommand.java |  25 +-
 .../cave/deployer/command/ConfigDeleteCommand.java |  25 +-
 .../command/ConfigPropertyAppendCommand.java       |  31 ++-
 .../command/ConfigPropertyDeleteCommand.java       |  28 +-
 .../command/ConfigPropertyListCommand.java         |  30 ++-
 .../deployer/command/ConfigPropertySetCommand.java |  31 ++-
 .../deployer/command/ConnectionDeleteCommand.java  |  22 +-
 .../deployer/command/ConnectionListCommand.java    |  26 +-
 .../command/ConnectionRegisterCommand.java         |  61 +++++
 .../cave/deployer/command/DownloadCommand.java     |  21 +-
 .../cave/deployer/command/ExplodeCommand.java      |  24 +-
 .../cave/deployer/command/ExtractCommand.java      |  21 +-
 .../deployer/command/FeatureInstallCommand.java    |  25 +-
 .../command/FeatureInstalledListCommand.java       |  27 +-
 .../cave/deployer/command/FeatureListCommand.java  |  34 ++-
 .../deployer/command/FeatureUninstallCommand.java  |  25 +-
 .../command/FeaturesRepositoryAddCommand.java      |  25 +-
 .../command/FeaturesRepositoryListCommand.java     |  33 ++-
 .../command/FeaturesRepositoryRemoveCommand.java   |  25 +-
 .../cave/deployer/command/KarInstallCommand.java   |  25 +-
 .../cave/deployer/command/KarListCommand.java      |  27 +-
 .../cave/deployer/command/KarUninstallCommand.java |  25 +-
 .../karaf/cave/deployer/command/UploadCommand.java |  34 ++-
 .../command/completers/ConnectionCompleter.java    |  13 +-
 {server => deployer}/management/pom.xml            |  19 +-
 .../deployer/management/CaveDeployerMBean.java     |  67 +++++
 .../deployer/management/internal/Activator.java    |  53 ++++
 .../management/internal/CaveDeployerMBeanImpl.java | 283 +++++++++++++++++++++
 deployer/pom.xml                                   |   2 +
 .../karaf/cave/deployer/rest/DeployerRest.java     |   4 +-
 .../cave/deployer/service/impl/DeployerImpl.java   |  28 +-
 .../cave/server/management/internal/Activator.java |  21 +-
 47 files changed, 1168 insertions(+), 398 deletions(-)
 copy {server => deployer}/command/pom.xml (83%)
 create mode 100644 deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/AssembleFeatureCommand.java
 copy server/command/src/main/java/org/apache/karaf/cave/server/command/GatewayRemoveCommand.java => deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/BundleInstallCommand.java (63%)
 copy server/command/src/main/java/org/apache/karaf/cave/server/command/GatewayRemoveCommand.java => deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/BundleListCommand.java (51%)
 copy server/command/src/main/java/org/apache/karaf/cave/server/command/GatewayRemoveCommand.java => deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/BundleStartCommand.java (62%)
 copy server/command/src/main/java/org/apache/karaf/cave/server/command/GatewayRemoveCommand.java => deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/BundleStopCommand.java (62%)
 copy server/command/src/main/java/org/apache/karaf/cave/server/command/GatewayRemoveCommand.java => deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/BundleUninstallCommand.java (61%)
 copy server/command/src/main/java/org/apache/karaf/cave/server/command/GatewayRemoveCommand.java => deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ClusterFeatureInstallCommand.java (56%)
 copy server/command/src/main/java/org/apache/karaf/cave/server/command/GatewayRemoveCommand.java => deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ClusterFeatureRepositoryAddCommand.java (56%)
 copy server/command/src/main/java/org/apache/karaf/cave/server/command/GatewayRemoveCommand.java => deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ClusterFeatureRepositoryRemoveCommand.java (56%)
 copy server/command/src/main/java/org/apache/karaf/cave/server/command/GatewayRemoveCommand.java => deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ClusterFeatureUninstallCommand.java (56%)
 copy server/command/src/main/java/org/apache/karaf/cave/server/command/GatewayRemoveCommand.java => deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ClusterGroupListCommand.java (51%)
 copy server/command/src/main/java/org/apache/karaf/cave/server/command/GatewayRemoveCommand.java => deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ClusterNodeListCommand.java (62%)
 copy server/command/src/main/java/org/apache/karaf/cave/server/command/GatewayRemoveCommand.java => deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ConfigCreateCommand.java (61%)
 copy server/command/src/main/java/org/apache/karaf/cave/server/command/GatewayRemoveCommand.java => deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ConfigDeleteCommand.java (62%)
 copy server/command/src/main/java/org/apache/karaf/cave/server/command/GatewayRemoveCommand.java => deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ConfigPropertyAppendCommand.java (53%)
 copy server/command/src/main/java/org/apache/karaf/cave/server/command/GatewayRemoveCommand.java => deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ConfigPropertyDeleteCommand.java (57%)
 copy server/command/src/main/java/org/apache/karaf/cave/server/command/GatewayRemoveCommand.java => deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ConfigPropertyListCommand.java (56%)
 copy server/command/src/main/java/org/apache/karaf/cave/server/command/GatewayRemoveCommand.java => deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ConfigPropertySetCommand.java (53%)
 copy server/command/src/main/java/org/apache/karaf/cave/server/command/GatewayRemoveCommand.java => deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ConnectionDeleteCommand.java (66%)
 copy server/command/src/main/java/org/apache/karaf/cave/server/command/GatewayListCommand.java => deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ConnectionListCommand.java (56%)
 create mode 100644 deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ConnectionRegisterCommand.java
 copy server/command/src/main/java/org/apache/karaf/cave/server/command/GatewayRegisterCommand.java => deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/DownloadCommand.java (64%)
 copy server/command/src/main/java/org/apache/karaf/cave/server/command/GatewayRemoveCommand.java => deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ExplodeCommand.java (65%)
 copy server/command/src/main/java/org/apache/karaf/cave/server/command/GatewayRegisterCommand.java => deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/ExtractCommand.java (64%)
 copy server/command/src/main/java/org/apache/karaf/cave/server/command/GatewayRemoveCommand.java => deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/FeatureInstallCommand.java (62%)
 copy server/command/src/main/java/org/apache/karaf/cave/server/command/GatewayRemoveCommand.java => deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/FeatureInstalledListCommand.java (61%)
 copy server/command/src/main/java/org/apache/karaf/cave/server/command/GatewayRemoveCommand.java => deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/FeatureListCommand.java (53%)
 copy server/command/src/main/java/org/apache/karaf/cave/server/command/GatewayRemoveCommand.java => deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/FeatureUninstallCommand.java (61%)
 copy server/command/src/main/java/org/apache/karaf/cave/server/command/GatewayRemoveCommand.java => deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/FeaturesRepositoryAddCommand.java (62%)
 copy server/command/src/main/java/org/apache/karaf/cave/server/command/GatewayRemoveCommand.java => deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/FeaturesRepositoryListCommand.java (52%)
 copy server/command/src/main/java/org/apache/karaf/cave/server/command/GatewayRemoveCommand.java => deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/FeaturesRepositoryRemoveCommand.java (61%)
 copy server/command/src/main/java/org/apache/karaf/cave/server/command/GatewayRemoveCommand.java => deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/KarInstallCommand.java (62%)
 copy server/command/src/main/java/org/apache/karaf/cave/server/command/GatewayRemoveCommand.java => deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/KarListCommand.java (63%)
 copy server/command/src/main/java/org/apache/karaf/cave/server/command/GatewayRemoveCommand.java => deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/KarUninstallCommand.java (62%)
 copy server/command/src/main/java/org/apache/karaf/cave/server/command/GatewayRemoveCommand.java => deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/UploadCommand.java (51%)
 copy server/command/src/main/java/org/apache/karaf/cave/server/command/completers/GatewayCompleter.java => deployer/command/src/main/java/org/apache/karaf/cave/deployer/command/completers/ConnectionCompleter.java (79%)
 copy {server => deployer}/management/pom.xml (81%)
 create mode 100644 deployer/management/src/main/java/org/apache/karaf/cave/deployer/management/CaveDeployerMBean.java
 create mode 100644 deployer/management/src/main/java/org/apache/karaf/cave/deployer/management/internal/Activator.java
 create mode 100644 deployer/management/src/main/java/org/apache/karaf/cave/deployer/management/internal/CaveDeployerMBeanImpl.java

-- 
To stop receiving notification emails like this one, please contact
['"commits@karaf.apache.org" <co...@karaf.apache.org>'].

[karaf-cave] 02/02: [KARAF-5108] Add Deployer MBean

Posted by jb...@apache.org.
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 92789311c77423da741b429eacab718c28eb5b09
Author: Jean-Baptiste Onofré <jb...@apache.org>
AuthorDate: Wed Dec 20 13:57:18 2017 +0100

    [KARAF-5108] Add Deployer MBean
---
 assembly/src/main/resources/features.xml           |  14 +-
 deployer/management/pom.xml                        |  77 ++++++
 .../deployer/management/CaveDeployerMBean.java     |  67 +++++
 .../deployer/management/internal/Activator.java    |  53 ++++
 .../management/internal/CaveDeployerMBeanImpl.java | 283 +++++++++++++++++++++
 deployer/pom.xml                                   |   1 +
 .../cave/server/management/internal/Activator.java |  21 +-
 7 files changed, 504 insertions(+), 12 deletions(-)

diff --git a/assembly/src/main/resources/features.xml b/assembly/src/main/resources/features.xml
index 82e5529..40a8d87 100644
--- a/assembly/src/main/resources/features.xml
+++ b/assembly/src/main/resources/features.xml
@@ -69,6 +69,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 version="${project.version}">cave-deployer-management</feature>
     </feature>
 
     <feature name="cave-deployer-service" version="${project.version}">
@@ -93,7 +94,18 @@
 
     <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>
+        <conditional>
+            <condition>shell</condition>
+            <bundle>mvn:org.apache.karaf.cave.deployer/org.apache.karaf.cave.deployer.command/${project.version}</bundle>
+        </conditional>
+    </feature>
+
+    <feature name="cave-deployer-management" version="${project.version}">
+        <feature version="${project.version}">cave-deployer-service</feature>
+        <conditional>
+            <condition>management</condition>
+            <bundle>mvn:org.apache.karaf.cave.deployer/org.apache.karaf.cave.deployer.management/${project.version}</bundle>
+        </conditional>
     </feature>
 
 </features>
\ No newline at end of file
diff --git a/deployer/management/pom.xml b/deployer/management/pom.xml
new file mode 100644
index 0000000..1e11e91
--- /dev/null
+++ b/deployer/management/pom.xml
@@ -0,0 +1,77 @@
+<?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.management</artifactId>
+    <packaging>bundle</packaging>
+    <name>Apache Karaf :: Cave :: Deployer :: Management</name>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.karaf.cave.deployer</groupId>
+            <artifactId>org.apache.karaf.cave.deployer.api</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.karaf</groupId>
+            <artifactId>org.apache.karaf.util</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>osgi.core</artifactId>
+        </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>
+                        <Export-Package>
+                            org.apache.karaf.cave.deployer.management
+                        </Export-Package>
+                        <Private-Package>
+                            org.apache.karaf.cave.server.deployer.internal,
+                            org.apache.karaf.util.tracker*
+                        </Private-Package>
+                    </instructions>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
\ No newline at end of file
diff --git a/deployer/management/src/main/java/org/apache/karaf/cave/deployer/management/CaveDeployerMBean.java b/deployer/management/src/main/java/org/apache/karaf/cave/deployer/management/CaveDeployerMBean.java
new file mode 100644
index 0000000..cc573ff
--- /dev/null
+++ b/deployer/management/src/main/java/org/apache/karaf/cave/deployer/management/CaveDeployerMBean.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed 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.management;
+
+import javax.management.openmbean.TabularData;
+import java.util.List;
+import java.util.Map;
+
+public interface CaveDeployerMBean {
+
+    void registerConnection(String name, String jmxUrl, String karafName, String user, String password) throws Exception;
+    void deleteConnection(String name) throws Exception;
+    TabularData getConnections() throws Exception;
+
+    void explode(String url, String repository) throws Exception;
+    void extract(String url, String directory) throws Exception;
+    void download(String url, String directory) throws Exception;
+    void upload(String groupId, String artifactId, String version, String artifactUrl, String repositoryUrl) throws Exception;
+
+    void assembleFeature(String groupId, String artifactId, String version, String repositoryUrl, String feature,
+                         List<String> repositories, List<String> features, List<String> bundles) throws Exception;
+
+    void installBundle(String url, String connection) throws Exception;
+    void uninstallBundle(String id, String connection) throws Exception;
+    void startBundle(String id, String connection) throws Exception;
+    void stopBundle(String id, String connection) throws Exception;
+    TabularData getBundles(String connection) throws Exception;
+
+    void installKar(String url, String connection) throws Exception;
+    void uninstallKar(String id, String connection) throws Exception;
+    List<String> getKars(String connection) throws Exception;
+
+    void addFeatureRepository(String url, String connection) throws Exception;
+    void removeFeatureRepository(String repository, String connection) throws Exception;
+    TabularData getFeatureRepositories(String connection) throws Exception;
+
+    void installFeature(String feature, String connection) throws Exception;
+    void uninstallFeature(String feature, String connection) throws Exception;
+    TabularData getFeatures(String connection) throws Exception;
+
+    void createConfig(String pid, String connection) throws Exception;
+    Map<String, String> getConfigProperties(String pid, String connection) throws Exception;
+    void deleteConfig(String pid, String connection) throws Exception;
+    void appendConfigProperty(String pid, String key, String value, String connection) throws Exception;
+    void setConfigProperty(String pid, String key, String value, String connection) throws Exception;
+    String getConfigProperty(String pid, String key, String connection) throws Exception;
+    void deleteConfigProperty(String pid, String key, String connection) throws Exception;
+
+    List<String> getClusterNodes(String connection) throws Exception;
+    Map<String, List<String>> getClusterGroups(String connection) throws Exception;
+    void clusterFeatureRepositoryAdd(String url, String clusterGroup, String connection) throws Exception;
+    void clusterFeatureRepositoryRemove(String url, String clusterGroup, String connection) throws Exception;
+    void clusterFeatureInstall(String feature, String clusterGroup, String connection) throws Exception;
+    void clusterFeatureUninstall(String feature, String clusterGroup, String connection) throws Exception;
+
+}
diff --git a/deployer/management/src/main/java/org/apache/karaf/cave/deployer/management/internal/Activator.java b/deployer/management/src/main/java/org/apache/karaf/cave/deployer/management/internal/Activator.java
new file mode 100644
index 0000000..4daf8a0
--- /dev/null
+++ b/deployer/management/src/main/java/org/apache/karaf/cave/deployer/management/internal/Activator.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed 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.management.internal;
+
+import org.apache.karaf.cave.deployer.api.Deployer;
+import org.apache.karaf.util.tracker.BaseActivator;
+import org.apache.karaf.util.tracker.annotation.RequireService;
+import org.apache.karaf.util.tracker.annotation.Services;
+import org.osgi.framework.ServiceRegistration;
+
+import java.util.Hashtable;
+
+@Services(
+        requires = {
+                @RequireService(Deployer.class)
+        }
+)
+public class Activator extends BaseActivator {
+
+    private volatile ServiceRegistration mbeanRegistration;
+
+    @Override
+    protected void doStart() throws Exception {
+        Deployer deployer = getTrackedService(Deployer.class);
+        CaveDeployerMBeanImpl mbean = new CaveDeployerMBeanImpl();
+        mbean.setDeployer(deployer);
+
+        Hashtable<String, Object> props = new Hashtable<>();
+        props.put("jmx.objectname", "org.apache.karaf.cave:type=deployer,name=" + System.getProperty("karaf.name"));
+        mbeanRegistration = this.bundleContext.registerService(getInterfaceNames(mbean), mbean, props);
+    }
+
+    @Override
+    protected void doStop() {
+        if (mbeanRegistration != null) {
+            mbeanRegistration.unregister();
+            mbeanRegistration = null;
+        }
+        super.doStop();
+    }
+
+}
diff --git a/deployer/management/src/main/java/org/apache/karaf/cave/deployer/management/internal/CaveDeployerMBeanImpl.java b/deployer/management/src/main/java/org/apache/karaf/cave/deployer/management/internal/CaveDeployerMBeanImpl.java
new file mode 100644
index 0000000..2fa006b
--- /dev/null
+++ b/deployer/management/src/main/java/org/apache/karaf/cave/deployer/management/internal/CaveDeployerMBeanImpl.java
@@ -0,0 +1,283 @@
+/*
+ * Licensed 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.management.internal;
+
+import org.apache.karaf.cave.deployer.api.*;
+import org.apache.karaf.cave.deployer.management.CaveDeployerMBean;
+
+import javax.management.NotCompliantMBeanException;
+import javax.management.StandardMBean;
+import javax.management.openmbean.*;
+import java.util.List;
+import java.util.Map;
+
+public class CaveDeployerMBeanImpl extends StandardMBean implements CaveDeployerMBean {
+
+    private Deployer deployer;
+
+    public CaveDeployerMBeanImpl() throws NotCompliantMBeanException {
+        super(CaveDeployerMBean.class);
+    }
+
+    public Deployer getDeployer() {
+        return deployer;
+    }
+
+    public void setDeployer(Deployer deployer) {
+        this.deployer = deployer;
+    }
+
+
+    @Override
+    public void registerConnection(String name, String jmxUrl, String karafName, String user, String password) throws Exception {
+        Connection connection = new Connection();
+        connection.setName(name);
+        connection.setJmxUrl(jmxUrl);
+        connection.setKarafName(karafName);
+        connection.setUser(user);
+        connection.setPassword(password);
+        deployer.registerConnection(connection);
+    }
+
+    @Override
+    public void deleteConnection(String name) throws Exception {
+        deployer.deleteConnection(name);
+    }
+
+    @Override
+    public TabularData getConnections() throws Exception {
+        List<Connection> connections = deployer.connections();
+
+        CompositeType connectionType = new CompositeType("Connection", "Connection to a Karaf instance",
+                new String[]{"Name", "JMX URL", "Karaf Name", "User", "Password"},
+                new String[]{"Name of the connection", "JMX URL of the Karaf instance", "Karaf instance name", "Username to connect", "Password of the user" },
+                new OpenType[]{SimpleType.STRING, SimpleType.STRING, SimpleType.STRING, SimpleType.STRING, SimpleType.STRING});
+        TabularType tabularType = new TabularType("Connections", "Table of all Cave Deployer connections", connectionType, new String[]{"Name"});
+        TabularData table = new TabularDataSupport(tabularType);
+        for (Connection connection : connections) {
+            CompositeData data = new CompositeDataSupport(connectionType,
+                    new String[]{"Name", "JMX URL", "Karaf Name", "User", "Password"},
+                    new Object[]{connection.getName(), connection.getJmxUrl(), connection.getKarafName(), connection.getUser(), connection.getPassword()});
+            table.put(data);
+        }
+        return table;
+    }
+
+    @Override
+    public void explode(String url, String repository) throws Exception {
+        deployer.explode(url, repository);
+    }
+
+    @Override
+    public void extract(String url, String directory) throws Exception {
+        deployer.extract(url, directory);
+    }
+
+    @Override
+    public void download(String url, String directory) throws Exception {
+        deployer.download(url, directory);
+    }
+
+    @Override
+    public void upload(String groupId, String artifactId, String version, String artifactUrl, String repositoryUrl) throws Exception {
+        deployer.upload(groupId, artifactId, version, artifactUrl, repositoryUrl);
+    }
+
+    @Override
+    public void assembleFeature(String groupId, String artifactId, String version, String repositoryUrl, String feature, List<String> repositories, List<String> features, List<String> bundles) throws Exception {
+        deployer.assembleFeature(groupId, artifactId, version, repositoryUrl, feature, repositories, features, bundles, null);
+    }
+
+    @Override
+    public void installBundle(String url, String connection) throws Exception {
+        deployer.installBundle(url, connection);
+    }
+
+    @Override
+    public void uninstallBundle(String id, String connection) throws Exception {
+        deployer.uninstallBundle(id, connection);
+    }
+
+    @Override
+    public void startBundle(String id, String connection) throws Exception {
+        deployer.startBundle(id, connection);
+    }
+
+    @Override
+    public void stopBundle(String id, String connection) throws Exception {
+        deployer.stopBundle(id, connection);
+    }
+
+    @Override
+    public TabularData getBundles(String connection) throws Exception {
+        CompositeType bundleType = new CompositeType("Bundle", "A bundle on the remote instance",
+                new String[]{"ID", "Name", "Version", "Start Level", "State"},
+                new String[]{"Bundle ID", "Bundle Name", "Bundle Version", "Bundle Start Level", "Bundle State"},
+                new OpenType[]{SimpleType.STRING, SimpleType.STRING, SimpleType.STRING, SimpleType.INTEGER, SimpleType.STRING});
+        TabularType tableType = new TabularType("Bundles", "Table of bundles", bundleType, new String[]{"ID"});
+        TabularData table = new TabularDataSupport(tableType);
+
+        List<Bundle> bundles = deployer.bundles(connection);
+
+        for (Bundle bundle : bundles) {
+            CompositeData data = new CompositeDataSupport(bundleType,
+                    new String[]{"ID", "Name", "Version", "Start Level", "State"},
+                    new Object[]{bundle.getId(), bundle.getName(), bundle.getVersion(), bundle.getStartLevel(), bundle.getState()});
+            table.put(data);
+        }
+
+        return table;
+    }
+
+    @Override
+    public void installKar(String url, String connection) throws Exception {
+        deployer.installKar(url, connection);
+    }
+
+    @Override
+    public void uninstallKar(String id, String connection) throws Exception {
+        deployer.uninstallKar(id, connection);
+    }
+
+    @Override
+    public List<String> getKars(String connection) throws Exception {
+        return deployer.kars(connection);
+    }
+
+    @Override
+    public void addFeatureRepository(String url, String connection) throws Exception {
+        deployer.addFeaturesRepository(url, connection);
+    }
+
+    @Override
+    public void removeFeatureRepository(String repository, String connection) throws Exception {
+        deployer.removeFeaturesRepository(repository, connection);
+    }
+
+    @Override
+    public TabularData getFeatureRepositories(String connection) throws Exception {
+        List<FeaturesRepository> repositories = deployer.featuresRepositories(connection);
+
+        CompositeType repositoryType = new CompositeType("Features Repository", "Features Repository",
+                new String[]{"Name", "URL"},
+                new String[]{"The features repository name", "The location of the features repository"},
+                new OpenType[]{SimpleType.STRING, SimpleType.STRING});
+        TabularType tableType = new TabularType("Features Repositories", "Table of features repositories", repositoryType, new String[]{"Name"});
+        TabularData table = new TabularDataSupport(tableType);
+
+        for (FeaturesRepository repository : repositories) {
+            CompositeData data = new CompositeDataSupport(repositoryType,
+                    new String[]{"Name", "URL"},
+                    new Object[]{repository.getName(), repository.getUri()});
+            table.put(data);
+        }
+        return table;
+    }
+
+    @Override
+    public void installFeature(String feature, String connection) throws Exception {
+        deployer.installFeature(feature, connection);
+    }
+
+    @Override
+    public void uninstallFeature(String feature, String connection) throws Exception {
+        deployer.uninstallFeature(feature, connection);
+    }
+
+    @Override
+    public TabularData getFeatures(String connection) throws Exception {
+        List<Feature> features = deployer.features(connection);
+
+        CompositeType featureType = new CompositeType("Feature", "Feature",
+                new String[]{"Name", "Version", "State"},
+                new String[]{"Name of the feature", "Version of the feature", "State of the feature"},
+                new OpenType[]{SimpleType.STRING, SimpleType.STRING, SimpleType.STRING});
+        TabularType tableType = new TabularType("Features", "Table of features",
+                featureType, new String[]{"Name", "Version"});
+        TabularData table = new TabularDataSupport(tableType);
+        for (Feature feature : features) {
+            CompositeData data = new CompositeDataSupport(featureType,
+                    new String[]{"Name", "Version", "State"},
+                    new Object[]{feature.getName(), feature.getVersion(), feature.getState()});
+            table.put(data);
+        }
+
+        return table;
+    }
+
+    @Override
+    public void createConfig(String pid, String connection) throws Exception {
+        deployer.createConfig(pid, connection);
+    }
+
+    @Override
+    public Map<String, String> getConfigProperties(String pid, String connection) throws Exception {
+        return deployer.configProperties(pid, connection);
+    }
+
+    @Override
+    public void deleteConfig(String pid, String connection) throws Exception {
+        deployer.deleteConfig(pid, connection);
+    }
+
+    @Override
+    public void appendConfigProperty(String pid, String key, String value, String connection) throws Exception {
+        deployer.appendConfigProperty(pid, key, value, connection);
+    }
+
+    @Override
+    public void setConfigProperty(String pid, String key, String value, String connection) throws Exception {
+        deployer.setConfigProperty(pid, key, value, connection);
+    }
+
+    @Override
+    public String getConfigProperty(String pid, String key, String connection) throws Exception {
+        return deployer.configProperty(pid, key, connection);
+    }
+
+    @Override
+    public void deleteConfigProperty(String pid, String key, String connection) throws Exception {
+        deployer.deleteConfigProperty(pid, key, connection);
+    }
+
+    @Override
+    public List<String> getClusterNodes(String connection) throws Exception {
+        return deployer.clusterNodes(connection);
+    }
+
+    @Override
+    public Map<String, List<String>> getClusterGroups(String connection) throws Exception {
+        return deployer.clusterGroups(connection);
+    }
+
+    @Override
+    public void clusterFeatureRepositoryAdd(String url, String clusterGroup, String connection) throws Exception {
+        deployer.clusterAddFeaturesRepository(url, clusterGroup, connection);
+    }
+
+    @Override
+    public void clusterFeatureRepositoryRemove(String url, String clusterGroup, String connection) throws Exception {
+        deployer.clusterRemoveFeaturesRepository(url, clusterGroup, connection);
+    }
+
+    @Override
+    public void clusterFeatureInstall(String feature, String clusterGroup, String connection) throws Exception {
+        deployer.clusterFeatureInstall(feature, clusterGroup, connection);
+    }
+
+    @Override
+    public void clusterFeatureUninstall(String feature, String clusterGroup, String connection) throws Exception {
+        deployer.clusterFeatureUninstall(feature, clusterGroup, connection);
+    }
+}
diff --git a/deployer/pom.xml b/deployer/pom.xml
index 9663f47..976cc09 100644
--- a/deployer/pom.xml
+++ b/deployer/pom.xml
@@ -37,6 +37,7 @@
         <module>api</module>
         <module>service</module>
         <module>command</module>
+        <module>management</module>
         <module>rest</module>
     </modules>
 
diff --git a/server/management/src/main/java/org/apache/karaf/cave/server/management/internal/Activator.java b/server/management/src/main/java/org/apache/karaf/cave/server/management/internal/Activator.java
index 7579caa..092b2f5 100644
--- a/server/management/src/main/java/org/apache/karaf/cave/server/management/internal/Activator.java
+++ b/server/management/src/main/java/org/apache/karaf/cave/server/management/internal/Activator.java
@@ -1,14 +1,3 @@
-package org.apache.karaf.cave.server.management.internal;
-
-import java.util.Hashtable;
-
-import org.apache.karaf.cave.server.api.CaveFeatureGateway;
-import org.apache.karaf.cave.server.api.CaveRepositoryService;
-import org.apache.karaf.util.tracker.BaseActivator;
-import org.apache.karaf.util.tracker.annotation.RequireService;
-import org.apache.karaf.util.tracker.annotation.Services;
-import org.osgi.framework.ServiceRegistration;
-
 /*
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -22,6 +11,16 @@ import org.osgi.framework.ServiceRegistration;
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package org.apache.karaf.cave.server.management.internal;
+
+import java.util.Hashtable;
+
+import org.apache.karaf.cave.server.api.CaveFeatureGateway;
+import org.apache.karaf.cave.server.api.CaveRepositoryService;
+import org.apache.karaf.util.tracker.BaseActivator;
+import org.apache.karaf.util.tracker.annotation.RequireService;
+import org.apache.karaf.util.tracker.annotation.Services;
+import org.osgi.framework.ServiceRegistration;
 
 @Services(
         requires = {

-- 
To stop receiving notification emails like this one, please contact
"commits@karaf.apache.org" <co...@karaf.apache.org>.

[karaf-cave] 01/02: [KARAF-5108] Add Deployer shell commands

Posted by jb...@apache.org.
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é <jb...@apache.org>
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
"commits@karaf.apache.org" <co...@karaf.apache.org>.