You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by su...@apache.org on 2014/07/02 19:36:51 UTC

[3/4] AMBARI-6176. Integrate python shell into Ambari-shell module(subin)

http://git-wip-us.apache.org/repos/asf/ambari/blob/c8eceafc/ambari-shell/ambari-groovy-shell/src/main/java/org/apache/ambari/shell/flash/AbstractFlash.java
----------------------------------------------------------------------
diff --git a/ambari-shell/ambari-groovy-shell/src/main/java/org/apache/ambari/shell/flash/AbstractFlash.java b/ambari-shell/ambari-groovy-shell/src/main/java/org/apache/ambari/shell/flash/AbstractFlash.java
new file mode 100644
index 0000000..58abb75
--- /dev/null
+++ b/ambari-shell/ambari-groovy-shell/src/main/java/org/apache/ambari/shell/flash/AbstractFlash.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.ambari.shell.flash;
+
+import static java.lang.Thread.sleep;
+
+import java.util.logging.Level;
+
+import org.springframework.shell.core.JLineShellComponent;
+
+/**
+ * Base class for showing flash messages.
+ */
+public abstract class AbstractFlash implements Runnable {
+
+  private static final int SLEEP_TIME = 1500;
+  private volatile boolean stop;
+  private FlashType flashType;
+  private JLineShellComponent shell;
+
+  protected AbstractFlash(JLineShellComponent shell, FlashType flashType) {
+    this.shell = shell;
+    this.flashType = flashType;
+  }
+
+  @Override
+  public void run() {
+    while (!stop) {
+      String text = null;
+      try {
+        text = getText();
+        if (text.isEmpty()) {
+          stop = true;
+        }
+        sleep(SLEEP_TIME);
+      } catch (Exception e) {
+        // ignore
+      } finally {
+        shell.flash(Level.SEVERE, text == null ? "" : text, flashType.getName());
+      }
+    }
+  }
+
+  /**
+   * Returns the actual text of the flash messages. To remove the flash
+   * return an empty string.
+   *
+   * @return message
+   */
+  public abstract String getText();
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/c8eceafc/ambari-shell/ambari-groovy-shell/src/main/java/org/apache/ambari/shell/flash/FlashService.java
----------------------------------------------------------------------
diff --git a/ambari-shell/ambari-groovy-shell/src/main/java/org/apache/ambari/shell/flash/FlashService.java b/ambari-shell/ambari-groovy-shell/src/main/java/org/apache/ambari/shell/flash/FlashService.java
new file mode 100644
index 0000000..78977a6
--- /dev/null
+++ b/ambari-shell/ambari-groovy-shell/src/main/java/org/apache/ambari/shell/flash/FlashService.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.ambari.shell.flash;
+
+import java.util.concurrent.ExecutorService;
+
+import org.apache.ambari.groovy.client.AmbariClient;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.shell.core.JLineShellComponent;
+import org.springframework.stereotype.Service;
+
+/**
+ * Service for managing the flashes.
+ */
+@Service
+public class FlashService {
+
+  private AmbariClient client;
+  private JLineShellComponent shell;
+  private ExecutorService executorService;
+
+  @Autowired
+  public FlashService(AmbariClient client, JLineShellComponent shell, ExecutorService executorService) {
+    this.client = client;
+    this.shell = shell;
+    this.executorService = executorService;
+  }
+
+  public void showInstallProgress(boolean exit) {
+    executorService.submit(new InstallProgress(shell, client, exit));
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/c8eceafc/ambari-shell/ambari-groovy-shell/src/main/java/org/apache/ambari/shell/flash/FlashType.java
----------------------------------------------------------------------
diff --git a/ambari-shell/ambari-groovy-shell/src/main/java/org/apache/ambari/shell/flash/FlashType.java b/ambari-shell/ambari-groovy-shell/src/main/java/org/apache/ambari/shell/flash/FlashType.java
new file mode 100644
index 0000000..754a269
--- /dev/null
+++ b/ambari-shell/ambari-groovy-shell/src/main/java/org/apache/ambari/shell/flash/FlashType.java
@@ -0,0 +1,39 @@
+/**
+ * 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.ambari.shell.flash;
+
+/**
+ * Holds the unique names of the flashes.
+ */
+public enum FlashType {
+
+  /**
+   * Install progress percentage flash.
+   */
+  INSTALL("install");
+
+  private String name;
+
+  private FlashType(String name) {
+    this.name = name;
+  }
+
+  public String getName() {
+    return name;
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/c8eceafc/ambari-shell/ambari-groovy-shell/src/main/java/org/apache/ambari/shell/flash/InstallProgress.java
----------------------------------------------------------------------
diff --git a/ambari-shell/ambari-groovy-shell/src/main/java/org/apache/ambari/shell/flash/InstallProgress.java b/ambari-shell/ambari-groovy-shell/src/main/java/org/apache/ambari/shell/flash/InstallProgress.java
new file mode 100644
index 0000000..69164ea
--- /dev/null
+++ b/ambari-shell/ambari-groovy-shell/src/main/java/org/apache/ambari/shell/flash/InstallProgress.java
@@ -0,0 +1,79 @@
+/**
+ * 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.ambari.shell.flash;
+
+import static java.lang.Math.round;
+
+import java.math.BigDecimal;
+
+import org.apache.ambari.groovy.client.AmbariClient;
+import org.springframework.shell.core.JLineShellComponent;
+
+/**
+ * Show the install progress in % value.
+ */
+public class InstallProgress extends AbstractFlash {
+
+  private static final int SUCCESS = 100;
+  private static final int FAILED = -1;
+  private final boolean exit;
+  private AmbariClient client;
+  private volatile boolean done;
+
+  public InstallProgress(JLineShellComponent shell, AmbariClient client, boolean exit) {
+    super(shell, FlashType.INSTALL);
+    this.client = client;
+    this.exit = exit;
+  }
+
+  @Override
+  public String getText() {
+    StringBuilder sb = new StringBuilder();
+    if (!done) {
+      BigDecimal progress = client.getInstallProgress();
+      if (progress != null) {
+        BigDecimal decimal = progress.setScale(2, BigDecimal.ROUND_HALF_UP);
+        int intValue = decimal.intValue();
+        if (intValue != SUCCESS && intValue != FAILED) {
+          sb.append("Installation: ").append(decimal).append("% ");
+          int rounded = round(progress.setScale(0, BigDecimal.ROUND_UP).intValue() / 10);
+          for (int i = 0; i < 10; i++) {
+            if (i < rounded) {
+              sb.append("=");
+            } else {
+              sb.append("-");
+            }
+          }
+        } else if (intValue == FAILED) {
+          sb.append("Installation: FAILED");
+          done = true;
+        } else {
+          sb.append("Installation: COMPLETE");
+          done = true;
+        }
+      } else {
+        sb.append("Installation: WAITING..");
+      }
+    } else {
+      if (exit) {
+        System.exit(0);
+      }
+    }
+    return sb.toString();
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/c8eceafc/ambari-shell/ambari-groovy-shell/src/main/java/org/apache/ambari/shell/model/AmbariContext.java
----------------------------------------------------------------------
diff --git a/ambari-shell/ambari-groovy-shell/src/main/java/org/apache/ambari/shell/model/AmbariContext.java b/ambari-shell/ambari-groovy-shell/src/main/java/org/apache/ambari/shell/model/AmbariContext.java
new file mode 100644
index 0000000..af45dd6
--- /dev/null
+++ b/ambari-shell/ambari-groovy-shell/src/main/java/org/apache/ambari/shell/model/AmbariContext.java
@@ -0,0 +1,159 @@
+/**
+ * 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.ambari.shell.model;
+
+import org.springframework.stereotype.Component;
+
+/**
+ * Holds information about the connected Ambari Server.
+ */
+@Component
+public class AmbariContext {
+
+  private String cluster;
+  private boolean blueprintsAvailable;
+  private Focus focus;
+  private Hints hint;
+
+  public AmbariContext() {
+    this.focus = getRootFocus();
+  }
+
+  /**
+   * Sets the name of the cluster.
+   *
+   * @param cluster
+   */
+  public void setCluster(String cluster) {
+    this.cluster = cluster;
+  }
+
+  /**
+   * Sets the focus to the root.
+   */
+  public void resetFocus() {
+    this.focus = getRootFocus();
+  }
+
+  /**
+   * Sets the focus.
+   *
+   * @param id   target of the focus, can be anything (blueprint id, host id..)
+   * @param type type of the focus
+   */
+  public void setFocus(String id, FocusType type) {
+    this.focus = new Focus(id, type);
+  }
+
+  /**
+   * Returns the target of the focus.
+   *
+   * @return target
+   */
+  public String getFocusValue() {
+    return focus.getValue();
+  }
+
+  /**
+   * Checks whether blueprints are available or not.
+   */
+  public boolean areBlueprintsAvailable() {
+    return blueprintsAvailable;
+  }
+
+  /**
+   * Sets what should be the next hint message.
+   *
+   * @param hint the new message
+   */
+  public void setHint(Hints hint) {
+    this.hint = hint;
+  }
+
+  /**
+   * Returns the context sensitive prompt.
+   *
+   * @return text of the prompt
+   */
+  public String getPrompt() {
+    return focus.isType(FocusType.ROOT) ?
+      isConnectedToCluster() ? formatPrompt(focus.getPrefix(), cluster) : "ambari-shell>" :
+      formatPrompt(focus.getPrefix(), focus.getValue());
+  }
+
+  public boolean isConnectedToCluster() {
+    return cluster != null;
+  }
+
+  /**
+   * Checks whether the focus is on the host or not.
+   *
+   * @return true if the focus is on a host false otherwise
+   */
+  public boolean isFocusOnHost() {
+    return isFocusOn(FocusType.HOST);
+  }
+
+  /**
+   * Checks whether the focus is on the cluster build or not.
+   *
+   * @return true if the focus is on a cluster build false otherwise
+   */
+  public boolean isFocusOnClusterBuild() {
+    return isFocusOn(FocusType.CLUSTER_BUILD);
+  }
+
+  /**
+   * Returns some context sensitive hint.
+   *
+   * @return hint
+   */
+  public String getHint() {
+    return "Hint: " + hint.message();
+  }
+
+  /**
+   * Returns the name of the cluster.
+   *
+   * @return cluster's name
+   */
+  public String getCluster() {
+    return cluster;
+  }
+
+  /**
+   * Sets whether there are blueprints available or not.
+   *
+   * @param blueprintsAvailable
+   */
+  public void setBlueprintsAvailable(boolean blueprintsAvailable) {
+    this.blueprintsAvailable = blueprintsAvailable;
+  }
+
+  private boolean isFocusOn(FocusType type) {
+    return focus.isType(type);
+  }
+
+  private Focus getRootFocus() {
+    return new Focus("root", FocusType.ROOT);
+  }
+
+  private String formatPrompt(String prefix, String postfix) {
+    return String.format("%s:%s>", prefix, postfix);
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/c8eceafc/ambari-shell/ambari-groovy-shell/src/main/java/org/apache/ambari/shell/model/Focus.java
----------------------------------------------------------------------
diff --git a/ambari-shell/ambari-groovy-shell/src/main/java/org/apache/ambari/shell/model/Focus.java b/ambari-shell/ambari-groovy-shell/src/main/java/org/apache/ambari/shell/model/Focus.java
new file mode 100644
index 0000000..b637c87
--- /dev/null
+++ b/ambari-shell/ambari-groovy-shell/src/main/java/org/apache/ambari/shell/model/Focus.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.ambari.shell.model;
+
+/**
+ * Holds information about the focus. Focus give you the ability to
+ * provide context sensitive commands.
+ *
+ * @see org.apache.ambari.shell.model.FocusType
+ */
+public class Focus {
+
+  private final String value;
+  private final FocusType type;
+
+  public Focus(String value, FocusType type) {
+    this.value = value;
+    this.type = type;
+  }
+
+  public String getPrefix() {
+    return type.prefix();
+  }
+
+  public String getValue() {
+    return value;
+  }
+
+  /**
+   * Checks if the current focus exists with the provided one.
+   *
+   * @param type type to check with the current
+   * @return true if they match false otherwise
+   */
+  public boolean isType(FocusType type) {
+    return this.type == type;
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/c8eceafc/ambari-shell/ambari-groovy-shell/src/main/java/org/apache/ambari/shell/model/FocusType.java
----------------------------------------------------------------------
diff --git a/ambari-shell/ambari-groovy-shell/src/main/java/org/apache/ambari/shell/model/FocusType.java b/ambari-shell/ambari-groovy-shell/src/main/java/org/apache/ambari/shell/model/FocusType.java
new file mode 100644
index 0000000..121f7b4
--- /dev/null
+++ b/ambari-shell/ambari-groovy-shell/src/main/java/org/apache/ambari/shell/model/FocusType.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.ambari.shell.model;
+
+/**
+ * Types for different focuses. Its purpose to give the command availability
+ * checkers a chance in decision making.
+ */
+public enum FocusType {
+
+  /**
+   * The focus is on a selected host.
+   */
+  HOST("HOST"),
+
+  /**
+   * The focus is on the cluster building phase.
+   */
+  CLUSTER_BUILD("CLUSTER_BUILD"),
+
+  /**
+   * No focus at all.
+   */
+  ROOT("CLUSTER");
+
+  private final String prefix;
+
+  private FocusType(String prefix) {
+    this.prefix = prefix;
+  }
+
+  /**
+   * Prefix provided for the prompt.
+   *
+   * @return focus prefix
+   */
+  public String prefix() {
+    return prefix;
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/c8eceafc/ambari-shell/ambari-groovy-shell/src/main/java/org/apache/ambari/shell/model/Hints.java
----------------------------------------------------------------------
diff --git a/ambari-shell/ambari-groovy-shell/src/main/java/org/apache/ambari/shell/model/Hints.java b/ambari-shell/ambari-groovy-shell/src/main/java/org/apache/ambari/shell/model/Hints.java
new file mode 100644
index 0000000..990c832
--- /dev/null
+++ b/ambari-shell/ambari-groovy-shell/src/main/java/org/apache/ambari/shell/model/Hints.java
@@ -0,0 +1,59 @@
+/**
+ * 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.ambari.shell.model;
+
+/**
+ * Provides some guidance's to the user, what he/she can follow.
+ */
+public enum Hints {
+
+  /**
+   * Hint for adding blueprints.
+   */
+  ADD_BLUEPRINT("Add a blueprint with the 'blueprint add' or add the default blueprints with the 'blueprint defaults' command."),
+
+  /**
+   * Hint for start building a cluster.
+   */
+  BUILD_CLUSTER("Start building a cluster with the 'cluster build' command using a previously added blueprint."),
+
+  /**
+   * Hint for start assigning hosts to host groups in cluster build phase.
+   */
+  ASSIGN_HOSTS("Assign hosts to different host groups with the 'cluster assign' command."),
+
+  /**
+   * Hint for create a cluster from the assigned hosts.
+   */
+  CREATE_CLUSTER("Create the cluster with the 'cluster create' command or use the 'cluster reset' command and start over."),
+
+  /**
+   * Hint for check the cluster creation result.
+   */
+  PROGRESS("See the install progress with the 'tasks' command.");
+
+  private final String message;
+
+  private Hints(String message) {
+    this.message = message;
+  }
+
+  public String message() {
+    return message;
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/c8eceafc/ambari-shell/ambari-groovy-shell/src/main/java/org/apache/ambari/shell/support/TableRenderer.java
----------------------------------------------------------------------
diff --git a/ambari-shell/ambari-groovy-shell/src/main/java/org/apache/ambari/shell/support/TableRenderer.java b/ambari-shell/ambari-groovy-shell/src/main/java/org/apache/ambari/shell/support/TableRenderer.java
new file mode 100644
index 0000000..3e5af1f
--- /dev/null
+++ b/ambari-shell/ambari-groovy-shell/src/main/java/org/apache/ambari/shell/support/TableRenderer.java
@@ -0,0 +1,121 @@
+/**
+ * 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.ambari.shell.support;
+
+import static java.util.Collections.singletonList;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.springframework.shell.support.table.Table;
+import org.springframework.shell.support.table.TableHeader;
+
+/**
+ * Utility class used to render tables.
+ */
+public final class TableRenderer {
+
+  private TableRenderer() {
+    throw new IllegalStateException();
+  }
+
+  /**
+   * Renders a 2 columns wide table with the given headers and rows. If headers are provided it should match with the
+   * number of columns.
+   *
+   * @param rows    rows of the table
+   * @param headers headers of the table
+   * @return the formatted table
+   */
+  public static String renderSingleMap(Map<String, String> rows, String... headers) {
+    return renderMultiValueMap(convert(rows), headers);
+  }
+
+  /**
+   * Renders a 2 columns wide table with the given headers and rows. If headers are provided it should match with the
+   * number of columns.
+   *
+   * @param rows    rows of the table, each value will be added as a new row with the same key
+   * @param headers headers of the table
+   * @return formatted table
+   */
+  public static String renderMultiValueMap(Map<String, List<String>> rows, String... headers) {
+    Table table = createTable(headers);
+    if (rows != null) {
+      for (String key : rows.keySet()) {
+        List<String> values = rows.get(key);
+        if (values != null) {
+          for (String value : values) {
+            table.addRow(key, value);
+          }
+        }
+      }
+    }
+    return format(table);
+  }
+
+  /**
+   * Renders a 3 columns wide table with the given headers and rows. If headers are provided it should match with the
+   * number of columns.
+   *
+   * @param rows    rows of the table, value map will be added as the last 2 columns to the table
+   * @param headers headers of the table
+   * @return formatted table
+   */
+  public static String renderMapValueMap(Map<String, Map<String, String>> rows, String... headers) {
+    Table table = createTable(headers);
+    if (rows != null) {
+      for (String key1 : rows.keySet()) {
+        Map<String, String> values = rows.get(key1);
+        if (values != null) {
+          for (String key2 : values.keySet()) {
+            table.addRow(key1, key2, values.get(key2));
+          }
+        }
+      }
+    }
+    return format(table);
+  }
+
+  private static Table createTable(String... headers) {
+    Table table = new Table();
+    if (headers != null) {
+      int column = 1;
+      for (String header : headers) {
+        table.addHeader(column++, new TableHeader(header));
+      }
+    }
+    return table;
+  }
+
+  private static Map<String, List<String>> convert(Map<String, String> map) {
+    Map<String, List<String>> result = new HashMap<String, List<String>>(map.size());
+    if (map != null) {
+      for (String key : map.keySet()) {
+        result.put(key, singletonList(map.get(key)));
+      }
+    }
+    return result;
+  }
+
+  private static String format(Table table) {
+    table.calculateColumnWidths();
+    return table.toString();
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/c8eceafc/ambari-shell/ambari-groovy-shell/src/main/resources/elephant.txt
----------------------------------------------------------------------
diff --git a/ambari-shell/ambari-groovy-shell/src/main/resources/elephant.txt b/ambari-shell/ambari-groovy-shell/src/main/resources/elephant.txt
new file mode 100644
index 0000000..f51e284
--- /dev/null
+++ b/ambari-shell/ambari-groovy-shell/src/main/resources/elephant.txt
@@ -0,0 +1,8 @@
+                    .-.._
+              __  /`     '.
+           .-'  `/   (   a \
+          /      (    \,_   \
+         /|       '---` |\ =|
+        ` \    /__.-/  /  | |
+           |  / / \ \  \   \_\
+           |__|_|  |_|__\

http://git-wip-us.apache.org/repos/asf/ambari/blob/c8eceafc/ambari-shell/ambari-groovy-shell/src/test/java/org/apache/ambari/shell/commands/BlueprintCommandsTest.java
----------------------------------------------------------------------
diff --git a/ambari-shell/ambari-groovy-shell/src/test/java/org/apache/ambari/shell/commands/BlueprintCommandsTest.java b/ambari-shell/ambari-groovy-shell/src/test/java/org/apache/ambari/shell/commands/BlueprintCommandsTest.java
new file mode 100644
index 0000000..3c9277c
--- /dev/null
+++ b/ambari-shell/ambari-groovy-shell/src/test/java/org/apache/ambari/shell/commands/BlueprintCommandsTest.java
@@ -0,0 +1,128 @@
+/**
+ * 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.ambari.shell.commands;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+
+import org.apache.ambari.groovy.client.AmbariClient;
+import org.apache.ambari.shell.model.AmbariContext;
+import org.apache.ambari.shell.model.Hints;
+import org.apache.commons.io.IOUtils;
+import org.codehaus.jackson.JsonNode;
+import org.codehaus.jackson.map.ObjectMapper;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import groovyx.net.http.HttpResponseException;
+
+@RunWith(MockitoJUnitRunner.class)
+public class BlueprintCommandsTest {
+
+  @InjectMocks
+  private BlueprintCommands blueprintCommands;
+
+  @Mock
+  private AmbariClient ambariClient;
+  @Mock
+  private HttpResponseException responseException;
+  @Mock
+  private AmbariContext context;
+  @Mock
+  private ObjectMapper objectMapper;
+
+  @Test
+  public void testAddBlueprintForFileReadPrecedence() throws IOException {
+    File file = new File("src/test/resources/testBlueprint.json");
+    String json = IOUtils.toString(new FileInputStream(file));
+    JsonNode jsonNode = mock(JsonNode.class);
+    when(objectMapper.readTree(json.getBytes())).thenReturn(jsonNode);
+    when(jsonNode.get("Blueprints")).thenReturn(jsonNode);
+    when(jsonNode.get("blueprint_name")).thenReturn(jsonNode);
+    when(jsonNode.asText()).thenReturn("blueprintName");
+
+    String result = blueprintCommands.addBlueprint("url", file);
+
+    verify(ambariClient).addBlueprint(json);
+    verify(context).setHint(Hints.BUILD_CLUSTER);
+    verify(context).setBlueprintsAvailable(true);
+    assertEquals("Blueprint: 'blueprintName' has been added", result);
+  }
+
+  @Test
+  public void testAddBlueprintForException() throws IOException {
+    File file = new File("src/test/resources/testBlueprint.json");
+    String json = IOUtils.toString(new FileInputStream(file));
+    doThrow(responseException).when(ambariClient).addBlueprint(json);
+    when(responseException.getMessage()).thenReturn("error");
+
+    String result = blueprintCommands.addBlueprint("url", file);
+
+    verify(ambariClient).addBlueprint(json);
+    verify(responseException).getMessage();
+    assertEquals("Cannot add blueprint: error", result);
+  }
+
+  @Test
+  public void testAddBlueprintForDefaults() throws HttpResponseException {
+    String result = blueprintCommands.addBlueprint();
+
+    verify(ambariClient).addDefaultBlueprints();
+    assertEquals("Default blueprints added", result);
+  }
+
+  @Test
+  public void testAddBlueprintForUnspecifiedValue() throws HttpResponseException {
+    String response = blueprintCommands.addBlueprint(null, null);
+
+    assertEquals("No blueprint specified", response);
+    verify(ambariClient, times(0)).addBlueprint(null);
+  }
+
+  @Test
+  public void testAddBlueprintDefaultsForException() throws HttpResponseException {
+    doThrow(responseException).when(ambariClient).addDefaultBlueprints();
+    when(responseException.getMessage()).thenReturn("error");
+
+    String result = blueprintCommands.addBlueprint();
+
+    verify(responseException).getMessage();
+    assertEquals("Failed to add the default blueprints: error", result);
+  }
+
+  @Test
+  public void testAddBlueprintDefaultsForConnectionRefused() throws HttpResponseException {
+    doThrow(new RuntimeException("Connection refused")).when(ambariClient).addDefaultBlueprints();
+    when(responseException.getMessage()).thenReturn("error");
+
+    String result = blueprintCommands.addBlueprint();
+
+    assertEquals("Failed to add the default blueprints: Connection refused", result);
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/c8eceafc/ambari-shell/ambari-groovy-shell/src/test/java/org/apache/ambari/shell/commands/ClusterCommandsTest.java
----------------------------------------------------------------------
diff --git a/ambari-shell/ambari-groovy-shell/src/test/java/org/apache/ambari/shell/commands/ClusterCommandsTest.java b/ambari-shell/ambari-groovy-shell/src/test/java/org/apache/ambari/shell/commands/ClusterCommandsTest.java
new file mode 100644
index 0000000..777d05d
--- /dev/null
+++ b/ambari-shell/ambari-groovy-shell/src/test/java/org/apache/ambari/shell/commands/ClusterCommandsTest.java
@@ -0,0 +1,279 @@
+/**
+ * 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.ambari.shell.commands;
+
+import static java.util.Arrays.asList;
+import static java.util.Collections.emptyList;
+import static java.util.Collections.singletonMap;
+import static org.apache.ambari.shell.support.TableRenderer.renderMultiValueMap;
+import static org.apache.ambari.shell.support.TableRenderer.renderSingleMap;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.ambari.groovy.client.AmbariClient;
+import org.apache.ambari.shell.completion.Blueprint;
+import org.apache.ambari.shell.completion.Host;
+import org.apache.ambari.shell.flash.FlashService;
+import org.apache.ambari.shell.model.AmbariContext;
+import org.apache.ambari.shell.model.Hints;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import groovyx.net.http.HttpResponseException;
+
+@RunWith(MockitoJUnitRunner.class)
+public class ClusterCommandsTest {
+
+  @InjectMocks
+  private ClusterCommands clusterCommands;
+
+  @Mock
+  private AmbariClient client;
+  @Mock
+  private AmbariContext context;
+  @Mock
+  private HttpResponseException responseException;
+  @Mock
+  private FlashService flashService;
+
+  @Test
+  public void testIsClusterBuildCommandAvailable() {
+    when(context.isConnectedToCluster()).thenReturn(false);
+    when(context.isFocusOnClusterBuild()).thenReturn(false);
+    when(context.areBlueprintsAvailable()).thenReturn(true);
+
+    boolean result = clusterCommands.isClusterBuildCommandAvailable();
+
+    assertTrue(result);
+  }
+
+  @Test
+  public void testIsClusterBuildCommandAvailableAndFocusOnBuild() {
+    when(context.isConnectedToCluster()).thenReturn(false);
+    when(context.isFocusOnClusterBuild()).thenReturn(true);
+    when(context.areBlueprintsAvailable()).thenReturn(true);
+
+    boolean result = clusterCommands.isClusterBuildCommandAvailable();
+
+    assertFalse(result);
+  }
+
+  @Test
+  public void testIsClusterBuildCommandAvailableAndNoBlueprints() {
+    when(context.isConnectedToCluster()).thenReturn(false);
+    when(context.isFocusOnClusterBuild()).thenReturn(false);
+    when(context.areBlueprintsAvailable()).thenReturn(false);
+
+    boolean result = clusterCommands.isClusterBuildCommandAvailable();
+
+    assertFalse(result);
+  }
+
+  @Test
+  public void testBuildClusterForNonExistingBlueprint() {
+    when(client.doesBlueprintExist("id")).thenReturn(false);
+
+    String result = clusterCommands.buildCluster(new Blueprint("id"));
+
+    verify(client).doesBlueprintExist("id");
+    assertEquals("Not a valid blueprint id", result);
+  }
+
+  @Test
+  public void testBuildCluster() {
+    Map<String, String> hostNames = singletonMap("host1", "HEALTHY");
+    Map<String, List<String>> map = singletonMap("group1", asList("comp1", "comp2"));
+    when(client.doesBlueprintExist("id")).thenReturn(true);
+    when(client.getBlueprintMap("id")).thenReturn(map);
+    when(context.getFocusValue()).thenReturn("id");
+    when(client.getHostNames()).thenReturn(hostNames);
+
+    String result = clusterCommands.buildCluster(new Blueprint("id"));
+
+    verify(client).doesBlueprintExist("id");
+    verify(client).getBlueprintMap("id");
+    verify(client).getHostGroups("id");
+    assertEquals(String.format("%s\n%s", renderSingleMap(hostNames, "HOSTNAME", "STATE"),
+      renderMultiValueMap(map, "HOSTGROUP", "COMPONENT")), result);
+  }
+
+  @Test
+  public void testAssignForInvalidHostGroup() {
+    Map<String, List<String>> map = singletonMap("group1", asList("host", "host2"));
+    ReflectionTestUtils.setField(clusterCommands, "hostGroups", map);
+    when(client.getHostNames()).thenReturn(singletonMap("host3", "HEALTHY"));
+
+    String result = clusterCommands.assign(new Host("host3"), "group0");
+
+    assertEquals("group0 is not a valid host group", result);
+  }
+
+  @Test
+  public void testAssignForValidHostGroup() {
+    Map<String, List<String>> map = new HashMap<String, List<String>>();
+    map.put("group1", new ArrayList<String>());
+    ReflectionTestUtils.setField(clusterCommands, "hostGroups", map);
+    when(client.getHostNames()).thenReturn(singletonMap("host3", "HEALTHY"));
+
+    String result = clusterCommands.assign(new Host("host3"), "group1");
+
+    assertEquals("host3 has been added to group1", result);
+  }
+
+  @Test
+  public void testAssignForInvalidHost() {
+    Map<String, List<String>> map = new HashMap<String, List<String>>();
+    map.put("group1", new ArrayList<String>());
+    ReflectionTestUtils.setField(clusterCommands, "hostGroups", map);
+    when(client.getHostNames()).thenReturn(singletonMap("host2", "HEALTHY"));
+
+    String result = clusterCommands.assign(new Host("host3"), "group1");
+
+    assertEquals("host3 is not a valid hostname", result);
+  }
+
+  @Test
+  public void testCreateClusterForException() throws HttpResponseException {
+    String blueprint = "blueprint";
+    Map<String, List<String>> map = singletonMap("group1", asList("host", "host2"));
+    ReflectionTestUtils.setField(clusterCommands, "hostGroups", map);
+    when(context.getFocusValue()).thenReturn(blueprint);
+    doThrow(responseException).when(client).createCluster(blueprint, blueprint, map);
+    doThrow(responseException).when(client).deleteCluster(blueprint);
+
+    String result = clusterCommands.createCluster(false);
+
+    verify(client).createCluster(blueprint, blueprint, map);
+    verify(client).getHostGroups(blueprint);
+    verify(client).deleteCluster(blueprint);
+    assertTrue(result.contains("Failed"));
+  }
+
+  @Test
+  public void testCreateCluster() throws HttpResponseException {
+    String blueprint = "blueprint";
+    Map<String, List<String>> map = singletonMap("group1", asList("host", "host2"));
+    ReflectionTestUtils.setField(clusterCommands, "hostGroups", map);
+    when(context.getFocusValue()).thenReturn(blueprint);
+    when(client.getClusterName()).thenReturn("cluster");
+
+    String result = clusterCommands.createCluster(false);
+
+    verify(client).createCluster(blueprint, blueprint, map);
+    verify(context).resetFocus();
+    assertFalse(result.contains("Failed"));
+    assertTrue(result.contains("Successfully"));
+  }
+
+  @Test
+  public void testDeleteClusterForException() throws HttpResponseException {
+    when(context.getCluster()).thenReturn("cluster");
+    when(responseException.getMessage()).thenReturn("msg");
+    doThrow(responseException).when(client).deleteCluster("cluster");
+
+    String result = clusterCommands.deleteCluster();
+
+    verify(client).deleteCluster("cluster");
+    verify(context).getCluster();
+    verify(responseException).getMessage();
+    assertEquals("Could not delete the cluster: msg", result);
+  }
+
+  @Test
+  public void testDeleteCluster() throws HttpResponseException {
+    when(context.getCluster()).thenReturn("cluster");
+    when(responseException.getMessage()).thenReturn("msg");
+
+    String result = clusterCommands.deleteCluster();
+
+    verify(client).deleteCluster("cluster");
+    verify(context).getCluster();
+    assertEquals("Successfully deleted the cluster", result);
+  }
+
+  @Test
+  public void testIsClusterPreviewCommandAvailable() {
+    when(context.isFocusOnClusterBuild()).thenReturn(true);
+    ReflectionTestUtils.setField(clusterCommands, "hostGroups", singletonMap("group1", asList("host1")));
+
+    boolean result = clusterCommands.isClusterPreviewCommandAvailable();
+
+    assertTrue(result);
+  }
+
+  @Test
+  public void testIsClusterPreviewCommandAvailableForNoAssignments() {
+    when(context.isFocusOnClusterBuild()).thenReturn(true);
+    ReflectionTestUtils.setField(clusterCommands, "hostGroups", singletonMap("group1", emptyList()));
+
+    boolean result = clusterCommands.isClusterPreviewCommandAvailable();
+
+    assertFalse(result);
+  }
+
+  @Test
+  public void testIsClusterResetCommandAvailable() {
+    when(context.isFocusOnClusterBuild()).thenReturn(true);
+    ReflectionTestUtils.setField(clusterCommands, "hostGroups", singletonMap("group1", asList("host1")));
+
+    boolean result = clusterCommands.isClusterResetCommandAvailable();
+
+    assertTrue(result);
+  }
+
+  @Test
+  public void testAutoAssignForEmptyResult() {
+    Map<String, List<String>> hostGroups = singletonMap("group1", asList("host1"));
+    ReflectionTestUtils.setField(clusterCommands, "hostGroups", hostGroups);
+    when(context.getFocusValue()).thenReturn("blueprint");
+    when(client.recommendAssignments("blueprint")).thenReturn(new HashMap<String, List<String>>());
+
+    clusterCommands.autoAssign();
+
+    Map<String, List<String>> result = (Map<String, List<String>>) ReflectionTestUtils.getField(clusterCommands, "hostGroups");
+    assertEquals(hostGroups, result);
+  }
+
+  @Test
+  public void testAutoAssign() {
+    Map<String, List<String>> hostGroups = singletonMap("group1", asList("host1"));
+    Map<String, List<String>> newAssignments = singletonMap("group1", asList("host1"));
+    ReflectionTestUtils.setField(clusterCommands, "hostGroups", hostGroups);
+    when(context.getFocusValue()).thenReturn("blueprint");
+    when(client.recommendAssignments("blueprint")).thenReturn(newAssignments);
+
+    clusterCommands.autoAssign();
+
+    Map<String, List<String>> result = (Map<String, List<String>>) ReflectionTestUtils.getField(clusterCommands, "hostGroups");
+    assertEquals(newAssignments, result);
+    verify(context).setHint(Hints.CREATE_CLUSTER);
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/c8eceafc/ambari-shell/ambari-groovy-shell/src/test/java/org/apache/ambari/shell/commands/HostCommandsTest.java
----------------------------------------------------------------------
diff --git a/ambari-shell/ambari-groovy-shell/src/test/java/org/apache/ambari/shell/commands/HostCommandsTest.java b/ambari-shell/ambari-groovy-shell/src/test/java/org/apache/ambari/shell/commands/HostCommandsTest.java
new file mode 100644
index 0000000..6ca6d4d
--- /dev/null
+++ b/ambari-shell/ambari-groovy-shell/src/test/java/org/apache/ambari/shell/commands/HostCommandsTest.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.ambari.shell.commands;
+
+import static java.util.Collections.singletonMap;
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import org.apache.ambari.groovy.client.AmbariClient;
+import org.apache.ambari.shell.completion.Host;
+import org.apache.ambari.shell.model.AmbariContext;
+import org.apache.ambari.shell.model.FocusType;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class HostCommandsTest {
+
+  @InjectMocks
+  private HostCommands hostCommands;
+
+  @Mock
+  private AmbariClient client;
+  @Mock
+  private AmbariContext context;
+
+  @Test
+  public void testFocusHostForValidHost() {
+    when(client.getHostNames()).thenReturn(singletonMap("host1", "HEALTHY"));
+
+    String result = hostCommands.focusHost(new Host("host1"));
+
+    verify(context).setFocus("host1", FocusType.HOST);
+    assertEquals("Focus set to: host1", result);
+  }
+
+  @Test
+  public void testFocusHostForInvalidHost() {
+    when(client.getHostNames()).thenReturn(singletonMap("host3", "HEALTHY"));
+
+    String result = hostCommands.focusHost(new Host("host1"));
+
+    verify(context, times(0)).setFocus("host1", FocusType.HOST);
+    assertEquals("host1 is not a valid host name", result);
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/c8eceafc/ambari-shell/ambari-groovy-shell/src/test/java/org/apache/ambari/shell/customization/AmbariPromptTest.java
----------------------------------------------------------------------
diff --git a/ambari-shell/ambari-groovy-shell/src/test/java/org/apache/ambari/shell/customization/AmbariPromptTest.java b/ambari-shell/ambari-groovy-shell/src/test/java/org/apache/ambari/shell/customization/AmbariPromptTest.java
new file mode 100644
index 0000000..ba90d00
--- /dev/null
+++ b/ambari-shell/ambari-groovy-shell/src/test/java/org/apache/ambari/shell/customization/AmbariPromptTest.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.ambari.shell.customization;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.when;
+
+import org.apache.ambari.shell.model.AmbariContext;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class AmbariPromptTest {
+
+  @InjectMocks
+  private AmbariPrompt prompt;
+
+  @Mock
+  private AmbariContext context;
+
+  @Test
+  public void testGetProviderName() {
+    String result = prompt.getProviderName();
+
+    assertEquals(AmbariPrompt.class.getSimpleName(), result);
+  }
+
+  @Test
+  public void testGetPrompt(){
+    when(context.getPrompt()).thenReturn("prompt");
+
+    String result = prompt.getPrompt();
+
+    assertEquals("prompt", result);
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/c8eceafc/ambari-shell/ambari-groovy-shell/src/test/java/org/apache/ambari/shell/model/AmbariContextTest.java
----------------------------------------------------------------------
diff --git a/ambari-shell/ambari-groovy-shell/src/test/java/org/apache/ambari/shell/model/AmbariContextTest.java b/ambari-shell/ambari-groovy-shell/src/test/java/org/apache/ambari/shell/model/AmbariContextTest.java
new file mode 100644
index 0000000..ca9e919
--- /dev/null
+++ b/ambari-shell/ambari-groovy-shell/src/test/java/org/apache/ambari/shell/model/AmbariContextTest.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.ambari.shell.model;
+
+import static org.junit.Assert.assertEquals;
+
+import org.apache.ambari.groovy.client.AmbariClient;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+import org.springframework.test.util.ReflectionTestUtils;
+
+@RunWith(MockitoJUnitRunner.class)
+public class AmbariContextTest {
+
+  @InjectMocks
+  private AmbariContext ambariContext;
+
+  @Mock
+  private AmbariClient ambariClient;
+
+  @Test
+  public void testGetPromptForRoot() {
+    ReflectionTestUtils.setField(ambariContext, "cluster", "single-node");
+
+    String result = ambariContext.getPrompt();
+
+    assertEquals(FocusType.ROOT.prefix() + ":single-node>", result);
+  }
+
+  @Test
+  public void testGetPromptForRootButNotConnected() {
+    ReflectionTestUtils.setField(ambariContext, "cluster", null);
+
+    String result = ambariContext.getPrompt();
+
+    assertEquals("ambari-shell>", result);
+  }
+
+  @Test
+  public void testGetPromptForFocus() {
+    ReflectionTestUtils.setField(ambariContext, "cluster", "single-node");
+    ReflectionTestUtils.setField(ambariContext, "focus", new Focus("target", FocusType.HOST));
+
+    String result = ambariContext.getPrompt();
+
+    assertEquals(String.format("%s:%s>", FocusType.HOST.prefix(), "target"), result);
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/c8eceafc/ambari-shell/ambari-groovy-shell/src/test/java/org/apache/ambari/shell/support/TableRendererTest.java
----------------------------------------------------------------------
diff --git a/ambari-shell/ambari-groovy-shell/src/test/java/org/apache/ambari/shell/support/TableRendererTest.java b/ambari-shell/ambari-groovy-shell/src/test/java/org/apache/ambari/shell/support/TableRendererTest.java
new file mode 100644
index 0000000..e576390
--- /dev/null
+++ b/ambari-shell/ambari-groovy-shell/src/test/java/org/apache/ambari/shell/support/TableRendererTest.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.ambari.shell.support;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.io.IOUtils;
+import org.junit.Test;
+
+public class TableRendererTest {
+
+  @Test
+  public void testRenderMultiValueMap() throws IOException {
+    Map<String, List<String>> map = new HashMap<String, List<String>>();
+    map.put("HDFS", Collections.singletonList("DATANODE"));
+    map.put("MAPREDUCE2", Collections.singletonList("HISTORYSERVER"));
+    map.put("ZOOKEEPER", Collections.singletonList("ZOOKEEPER_SERVER"));
+    assertEquals(IOUtils.toString(new FileInputStream(new File("src/test/resources/2columns"))),
+      TableRenderer.renderMultiValueMap(map, "SERVICE", "COMPONENT"));
+  }
+
+  @Test
+  public void testRenderMapValueMap() throws IOException {
+    Map<String, Map<String, String>> map = new HashMap<String, Map<String, String>>();
+    map.put("HDFS", Collections.singletonMap("DATANODE", "STARTED"));
+    map.put("MAPREDUCE2", Collections.singletonMap("HISTORYSERVER", "STARTED"));
+    map.put("ZOOKEEPER", Collections.singletonMap("ZOOKEEPER_SERVER", "INSTALLED"));
+    assertEquals(IOUtils.toString(new FileInputStream(new File("src/test/resources/3columns"))),
+      TableRenderer.renderMapValueMap(map, "SERVICE", "COMPONENT", "STATE"));
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/c8eceafc/ambari-shell/ambari-groovy-shell/src/test/resources/2columns
----------------------------------------------------------------------
diff --git a/ambari-shell/ambari-groovy-shell/src/test/resources/2columns b/ambari-shell/ambari-groovy-shell/src/test/resources/2columns
new file mode 100644
index 0000000..190771b
--- /dev/null
+++ b/ambari-shell/ambari-groovy-shell/src/test/resources/2columns
@@ -0,0 +1,5 @@
+  SERVICE     COMPONENT
+  ----------  ----------------
+  ZOOKEEPER   ZOOKEEPER_SERVER
+  MAPREDUCE2  HISTORYSERVER
+  HDFS        DATANODE

http://git-wip-us.apache.org/repos/asf/ambari/blob/c8eceafc/ambari-shell/ambari-groovy-shell/src/test/resources/3columns
----------------------------------------------------------------------
diff --git a/ambari-shell/ambari-groovy-shell/src/test/resources/3columns b/ambari-shell/ambari-groovy-shell/src/test/resources/3columns
new file mode 100644
index 0000000..127a414
--- /dev/null
+++ b/ambari-shell/ambari-groovy-shell/src/test/resources/3columns
@@ -0,0 +1,5 @@
+  SERVICE     COMPONENT         STATE
+  ----------  ----------------  ---------
+  ZOOKEEPER   ZOOKEEPER_SERVER  INSTALLED
+  MAPREDUCE2  HISTORYSERVER     STARTED
+  HDFS        DATANODE          STARTED

http://git-wip-us.apache.org/repos/asf/ambari/blob/c8eceafc/ambari-shell/ambari-groovy-shell/src/test/resources/testBlueprint.json
----------------------------------------------------------------------
diff --git a/ambari-shell/ambari-groovy-shell/src/test/resources/testBlueprint.json b/ambari-shell/ambari-groovy-shell/src/test/resources/testBlueprint.json
new file mode 100644
index 0000000..971c46d
--- /dev/null
+++ b/ambari-shell/ambari-groovy-shell/src/test/resources/testBlueprint.json
@@ -0,0 +1,48 @@
+{
+  "host_groups" : [
+    {
+      "name" : "host_group_1",
+      "components" : [
+      {
+        "name" : "NAMENODE"
+      },
+      {
+        "name" : "SECONDARY_NAMENODE"
+      },
+      {
+        "name" : "DATANODE"
+      },
+      {
+        "name" : "HDFS_CLIENT"
+      },
+      {
+        "name" : "RESOURCEMANAGER"
+      },
+      {
+        "name" : "NODEMANAGER"
+      },
+      {
+        "name" : "YARN_CLIENT"
+      },
+      {
+        "name" : "HISTORYSERVER"
+      },
+      {
+        "name" : "MAPREDUCE2_CLIENT"
+      },
+      {
+        "name" : "ZOOKEEPER_SERVER"
+      },
+      {
+        "name" : "ZOOKEEPER_CLIENT"
+      }
+      ],
+      "cardinality" : "1"
+    }
+  ],
+  "Blueprints" : {
+    "blueprint_name" : "single-node-hdfs-yarn",
+    "stack_name" : "HDP",
+    "stack_version" : "2.0"
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/c8eceafc/ambari-shell/ambari-python-shell/conf/unix/ambari-shell
----------------------------------------------------------------------
diff --git a/ambari-shell/ambari-python-shell/conf/unix/ambari-shell b/ambari-shell/ambari-python-shell/conf/unix/ambari-shell
new file mode 100644
index 0000000..b6109fe
--- /dev/null
+++ b/ambari-shell/ambari-python-shell/conf/unix/ambari-shell
@@ -0,0 +1,62 @@
+#!/usr/bin/env bash
+
+# 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
+
+
+# Because Ambari rpm unpacks modules here on all systems
+PARAM="$@"
+export PYTHONPATH=/usr/lib/python2.6/site-packages:$PYTHONPATH
+SHELL_SCRIPT=/usr/lib/python2.6/site-packages/ambari_shell/ambari_shell.py
+OK=1
+NOTOK=0
+if [ -a /usr/bin/python2.7 ] && [ -z "$PYTHON" ]; then
+  PYTHON=/usr/bin/python2.7
+fi
+
+if [ -a /usr/bin/python2.6 ] && [ -z "$PYTHON" ]; then
+  PYTHON=/usr/bin/python2.6
+fi
+
+if [ "x$PYTHON" == "x" ]; then
+  PYTHON=/usr/bin/python
+fi
+
+export PYTHON=$PYTHON
+
+
+
+# check for version
+check_python_version ()
+{
+  echo "Verifying Python version compatibility..."
+  majversion=`$PYTHON -V 2>&1 | awk '{print $2}' | cut -d'.' -f1`
+  minversion=`$PYTHON -V 2>&1 | awk '{print $2}' | cut -d'.' -f2`
+  numversion=$(( 10 * $majversion + $minversion))
+  if (( $numversion < 26 )); then
+    echo "ERROR: Found Python version $majversion.$minversion. Ambari Agent requires Python version > 2.6"
+    return $NOTOK
+  fi
+  echo "Using python " $PYTHON
+  return $OK
+}
+
+retcode=0
+check_python_version
+if [ "$?" -eq "$NOTOK" ]; then
+	 exit -1
+fi
+$PYTHON $SHELL_SCRIPT $PARAM
+
+exit $retcode
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/c8eceafc/ambari-shell/ambari-python-shell/conf/unix/ambari-shell.ini
----------------------------------------------------------------------
diff --git a/ambari-shell/ambari-python-shell/conf/unix/ambari-shell.ini b/ambari-shell/ambari-python-shell/conf/unix/ambari-shell.ini
new file mode 100644
index 0000000..9ac158d
--- /dev/null
+++ b/ambari-shell/ambari-python-shell/conf/unix/ambari-shell.ini
@@ -0,0 +1,19 @@
+# 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
+
+[python_shell]
+loglevel=debug
+log_folder=/var/log/ambari-shell/
+cliplugin_folder=/root/workspace/python_shell/plugins
+default_plugin_folder=/usr/lib/python2.6/site-packages/ambari_shell/plugins
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/c8eceafc/ambari-shell/ambari-python-shell/pom.xml
----------------------------------------------------------------------
diff --git a/ambari-shell/ambari-python-shell/pom.xml b/ambari-shell/ambari-python-shell/pom.xml
new file mode 100644
index 0000000..881f745
--- /dev/null
+++ b/ambari-shell/ambari-python-shell/pom.xml
@@ -0,0 +1,237 @@
+<?xml version="1.0"?>
+<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/maven-v4_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 Licenseam for the specific language governing permissions and
+   limitations under the License.
+-->
+  <parent>
+    <groupId>org.apache.ambari</groupId>
+    <artifactId>ambari-shell</artifactId>
+    <version>1.3.0-SNAPSHOT</version>
+    <relativePath>../../ambari-shell</relativePath>
+  </parent>
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>org.apache.ambari</groupId>
+  <artifactId>ambari-python-shell</artifactId>
+  <packaging>pom</packaging>
+  <version>1.3.0-SNAPSHOT</version>
+  <name>Ambari Python Shell</name>
+  <description>Ambari Python Shell</description>
+  <properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    <final.name>${project.artifactId}-${project.version}</final.name>
+    <package.release>1</package.release>
+    <package.prefix>/usr</package.prefix>
+    <package.log.dir>/var/log/ambari-shell</package.log.dir>
+    <package.pid.dir>/var/run/ambari-shell</package.pid.dir>
+    <skipTests>false</skipTests>
+    <install.dir>/usr/lib/python2.6/site-packages/ambari_shell</install.dir>
+    <lib.dir>/usr/lib/ambari-shell/lib</lib.dir>
+    <python.ver>python &gt;= 2.6</python.ver>
+    <deb.python.ver>python (&gt;= 2.6)</deb.python.ver>
+    <deb.architecture>amd64</deb.architecture>
+    <deb.dependency.list>openssl, zlibc, ${deb.python.ver}</deb.dependency.list>
+  </properties>
+  <build>
+    <plugins>
+      <plugin>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <version>3.0</version>
+      </plugin>
+      <plugin>
+        <artifactId>maven-assembly-plugin</artifactId>
+        <configuration>
+          <tarLongFileMode>gnu</tarLongFileMode>
+          <descriptors>
+            <descriptor>src/packages/tarball/all.xml</descriptor>
+          </descriptors>
+        </configuration>
+        <executions>
+          <execution>
+            <id>build-tarball</id>
+            <phase>prepare-package</phase>
+            <goals>
+              <goal>single</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>exec-maven-plugin</artifactId>
+        <version>1.2</version>
+        <executions>
+          <execution>
+            <configuration>
+              <executable>${project.basedir}/../../ambari-common/src/main/unix/ambari-python-wrap</executable>
+              <workingDirectory>src/test/python</workingDirectory>
+              <arguments>
+                <argument>unitTests.py</argument>
+              </arguments>
+              <environmentVariables>
+                <PYTHONPATH>${project.basedir}/../../ambari-common/src/test/python:${project.basedir}/../../ambari-client/python-client/src/test/python:${project.basedir}/../../ambari-client/python-client/src/main/python/ambari_client:${project.basedir}/src/main/python/ambari_shell:${project.basedir}/src/test/python/utils:$PYTHONPATH</PYTHONPATH>
+              </environmentVariables>
+              <skip>${skipTests}</skip>
+            </configuration>
+            <id>python-test</id>
+            <phase>test</phase>
+            <goals>
+              <goal>exec</goal>
+            </goals>
+          </execution>
+          <execution>
+            <configuration>
+              <executable>${project.basedir}/../../ambari-common/src/main/unix/ambari-python-wrap</executable>
+              <workingDirectory>target/ambari-python-shell-${project.version}</workingDirectory>
+              <arguments>
+                <argument>${project.basedir}/src/main/python/setup.py</argument>
+                <argument>clean</argument>
+                <argument>bdist_dumb</argument>
+              </arguments>
+              <environmentVariables>
+                <PYTHONPATH>target/python-client-${project.version}:$PYTHONPATH</PYTHONPATH>
+              </environmentVariables>
+            </configuration>
+            <id>python-package</id>
+            <phase>package</phase>
+            <goals>
+              <goal>exec</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>rpm-maven-plugin</artifactId>
+        <version>2.0.1</version>
+        <executions>
+          <execution>
+            <phase>none</phase>
+            <goals>
+              <goal>rpm</goal>
+            </goals>
+          </execution>
+        </executions>
+        <configuration>
+          <copyright>2012, Apache Software Foundation</copyright>
+          <group>Development</group>
+          <description>Maven Recipe: RPM Package.</description>
+          <requires>
+            <require>openssl</require>
+            <require>zlib</require>
+            <require>${python.ver}</require>
+          </requires>
+          <needarch>x86_64</needarch>
+          <autoRequires>false</autoRequires>
+          <mappings>
+             <mapping>
+              <directory>/etc/ambari-shell/conf</directory>
+              <filemode>755</filemode>
+              <username>root</username>
+              <groupname>root</groupname>
+              <sources>
+                <source>
+                  <location>conf/unix/ambari-shell.ini</location>
+                </source>
+              </sources>
+            </mapping>
+            <mapping>
+              <directory>/usr/sbin</directory>
+              <filemode>755</filemode>
+              <username>root</username>
+              <groupname>root</groupname>
+              <directoryIncluded>false</directoryIncluded>
+              <sources>
+                <source>
+                  <location>conf/unix/ambari-shell</location>
+                  <filter>true</filter>
+                </source>
+              </sources>
+            </mapping>
+            <mapping>
+              <directory>${package.log.dir}</directory>
+              <filemode>755</filemode>
+              <username>root</username>
+              <groupname>root</groupname>
+            </mapping>
+            <mapping>
+              <directory>${install.dir}</directory>
+              <sources>
+                <source>
+                  <location>${project.build.directory}/${project.artifactId}-${project.version}/ambari_shell</location>
+                </source>
+              </sources>
+            </mapping>
+          </mappings>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.rat</groupId>
+        <artifactId>apache-rat-plugin</artifactId>
+        <configuration>
+          <excludes>
+            <exclude>src/examples/*</exclude>
+            <exclude>src/test/python/dummy*.txt</exclude>
+            <exclude>src/main/python/ambari_client/imports.txt</exclude>
+            <exclude>src/main/puppet/modules/stdlib/**</exclude>
+            <exclude>**/*.erb</exclude>
+            <exclude>**/*.json</exclude>
+          </excludes>
+        </configuration>
+        <executions>
+          <execution>
+            <phase>test</phase>
+            <goals>
+              <goal>check</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <groupId>org.vafer</groupId>
+        <artifactId>jdeb</artifactId>
+        <version>1.0.1</version>
+        <executions>
+          <execution>
+            <phase>none</phase>
+            <goals>
+              <goal>jdeb</goal>
+            </goals>
+          </execution>
+        </executions>
+        <configuration>
+          <controlDir>${basedir}/src/main/package/deb/control</controlDir>
+          <deb>${basedir}/target/${artifactId}_${package-version}-${package-release}.deb</deb>
+          <dataSet>
+            <data>
+              <src>${project.build.directory}/${project.artifactId}-${project.version}/ambari_shell</src>
+              <type>directory</type>
+              <mapper>
+                <type>perm</type>
+                <prefix>${install.dir}</prefix>
+              </mapper>
+            </data>
+          </dataSet>
+        </configuration>
+      </plugin>
+    </plugins>
+    <extensions>
+      <extension>
+        <groupId>org.apache.maven.wagon</groupId>
+        <artifactId>wagon-ssh-external</artifactId>
+      </extension>
+    </extensions>
+  </build>
+</project>

http://git-wip-us.apache.org/repos/asf/ambari/blob/c8eceafc/ambari-shell/ambari-python-shell/src/main/package/deb/control/control
----------------------------------------------------------------------
diff --git a/ambari-shell/ambari-python-shell/src/main/package/deb/control/control b/ambari-shell/ambari-python-shell/src/main/package/deb/control/control
new file mode 100644
index 0000000..40cd855
--- /dev/null
+++ b/ambari-shell/ambari-python-shell/src/main/package/deb/control/control
@@ -0,0 +1,22 @@
+# 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: [[artifactId]]
+Version: [[package-version]]-[[package-release]]
+Section: [[deb.section]]
+Priority: [[deb.priority]]
+Depends: [[deb.dependency.list]]
+Architecture: [[deb.architecture]]
+Description: [[description]]
+Maintainer: [[deb.publisher]]

http://git-wip-us.apache.org/repos/asf/ambari/blob/c8eceafc/ambari-shell/ambari-python-shell/src/main/package/deb/control/postinst
----------------------------------------------------------------------
diff --git a/ambari-shell/ambari-python-shell/src/main/package/deb/control/postinst b/ambari-shell/ambari-python-shell/src/main/package/deb/control/postinst
new file mode 100644
index 0000000..21a01fa
--- /dev/null
+++ b/ambari-shell/ambari-python-shell/src/main/package/deb/control/postinst
@@ -0,0 +1,15 @@
+#!/bin/bash
+# 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

http://git-wip-us.apache.org/repos/asf/ambari/blob/c8eceafc/ambari-shell/ambari-python-shell/src/main/package/deb/control/postrm
----------------------------------------------------------------------
diff --git a/ambari-shell/ambari-python-shell/src/main/package/deb/control/postrm b/ambari-shell/ambari-python-shell/src/main/package/deb/control/postrm
new file mode 100644
index 0000000..21a01fa
--- /dev/null
+++ b/ambari-shell/ambari-python-shell/src/main/package/deb/control/postrm
@@ -0,0 +1,15 @@
+#!/bin/bash
+# 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

http://git-wip-us.apache.org/repos/asf/ambari/blob/c8eceafc/ambari-shell/ambari-python-shell/src/main/package/deb/control/posttrm
----------------------------------------------------------------------
diff --git a/ambari-shell/ambari-python-shell/src/main/package/deb/control/posttrm b/ambari-shell/ambari-python-shell/src/main/package/deb/control/posttrm
new file mode 100644
index 0000000..21a01fa
--- /dev/null
+++ b/ambari-shell/ambari-python-shell/src/main/package/deb/control/posttrm
@@ -0,0 +1,15 @@
+#!/bin/bash
+# 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

http://git-wip-us.apache.org/repos/asf/ambari/blob/c8eceafc/ambari-shell/ambari-python-shell/src/main/package/deb/control/preinst
----------------------------------------------------------------------
diff --git a/ambari-shell/ambari-python-shell/src/main/package/deb/control/preinst b/ambari-shell/ambari-python-shell/src/main/package/deb/control/preinst
new file mode 100644
index 0000000..21a01fa
--- /dev/null
+++ b/ambari-shell/ambari-python-shell/src/main/package/deb/control/preinst
@@ -0,0 +1,15 @@
+#!/bin/bash
+# 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

http://git-wip-us.apache.org/repos/asf/ambari/blob/c8eceafc/ambari-shell/ambari-python-shell/src/main/package/deb/control/prerm
----------------------------------------------------------------------
diff --git a/ambari-shell/ambari-python-shell/src/main/package/deb/control/prerm b/ambari-shell/ambari-python-shell/src/main/package/deb/control/prerm
new file mode 100644
index 0000000..21a01fa
--- /dev/null
+++ b/ambari-shell/ambari-python-shell/src/main/package/deb/control/prerm
@@ -0,0 +1,15 @@
+#!/bin/bash
+# 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

http://git-wip-us.apache.org/repos/asf/ambari/blob/c8eceafc/ambari-shell/ambari-python-shell/src/main/python/ambari_shell/__init__.py
----------------------------------------------------------------------
diff --git a/ambari-shell/ambari-python-shell/src/main/python/ambari_shell/__init__.py b/ambari-shell/ambari-python-shell/src/main/python/ambari_shell/__init__.py
new file mode 100644
index 0000000..278df2e
--- /dev/null
+++ b/ambari-shell/ambari-python-shell/src/main/python/ambari_shell/__init__.py
@@ -0,0 +1,16 @@
+#
+#  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.