瀏覽代碼

AMBARI-5482. Integrate Ambari Shell. (Janos Matyas and Krisztian Horvath via yusaku)

Yusaku Sako 11 年之前
父節點
當前提交
11dd9df85b
共有 100 個文件被更改,包括 3363 次插入157 次删除
  1. 20 0
      ambari-client/assemblies/client.xml
  2. 113 0
      ambari-client/groovy-client/pom.xml
  3. 510 0
      ambari-client/groovy-client/src/main/groovy/org/apache/ambari/groovy/client/AmbariClient.groovy
  4. 27 0
      ambari-client/groovy-client/src/main/groovy/org/apache/ambari/groovy/client/AmbariConnectionException.groovy
  5. 26 0
      ambari-client/groovy-client/src/main/java/org/apache/ambari/groovy/client/DoNotDeleteMe.java
  6. 57 0
      ambari-client/groovy-client/src/main/resources/blueprints/multi-node-hdfs-yarn
  7. 48 0
      ambari-client/groovy-client/src/main/resources/blueprints/single-node-hdfs-yarn
  8. 425 0
      ambari-client/groovy-client/src/test/groovy/com.sequenceiq.ambari.client/AmbariClientTest.groovy
  9. 27 0
      ambari-client/groovy-client/src/test/java/org/apache/ambari/groovy/client/NeitherMe.java
  10. 48 0
      ambari-client/groovy-client/src/test/resources/blueprint.json
  11. 21 0
      ambari-client/groovy-client/src/test/resources/blueprints.json
  12. 16 0
      ambari-client/groovy-client/src/test/resources/cluster.json
  13. 63 0
      ambari-client/groovy-client/src/test/resources/hdfsServiceComponents.json
  14. 14 0
      ambari-client/groovy-client/src/test/resources/services.json
  15. 24 0
      ambari-client/groovy-client/src/test/resources/tasks.json
  16. 8 143
      ambari-client/pom.xml
  17. 195 0
      ambari-client/python-client/pom.xml
  18. 0 0
      ambari-client/python-client/src/examples/create.py
  19. 0 0
      ambari-client/python-client/src/examples/delete.py
  20. 0 0
      ambari-client/python-client/src/examples/example.py
  21. 0 0
      ambari-client/python-client/src/examples/main.py
  22. 0 0
      ambari-client/python-client/src/examples/trial.py
  23. 0 0
      ambari-client/python-client/src/main/python/ambari_client/__init__.py
  24. 0 0
      ambari-client/python-client/src/main/python/ambari_client/ambari_api.py
  25. 0 0
      ambari-client/python-client/src/main/python/ambari_client/core/__init__.py
  26. 0 0
      ambari-client/python-client/src/main/python/ambari_client/core/coreutils.py
  27. 0 0
      ambari-client/python-client/src/main/python/ambari_client/core/errors.py
  28. 0 0
      ambari-client/python-client/src/main/python/ambari_client/core/http_client.py
  29. 0 0
      ambari-client/python-client/src/main/python/ambari_client/core/http_utils.py
  30. 0 0
      ambari-client/python-client/src/main/python/ambari_client/core/rest_resource.py
  31. 0 0
      ambari-client/python-client/src/main/python/ambari_client/model/__init__.py
  32. 0 0
      ambari-client/python-client/src/main/python/ambari_client/model/base_model.py
  33. 0 0
      ambari-client/python-client/src/main/python/ambari_client/model/cluster.py
  34. 0 0
      ambari-client/python-client/src/main/python/ambari_client/model/component.py
  35. 0 0
      ambari-client/python-client/src/main/python/ambari_client/model/configuration.py
  36. 0 0
      ambari-client/python-client/src/main/python/ambari_client/model/host.py
  37. 0 0
      ambari-client/python-client/src/main/python/ambari_client/model/paths.py
  38. 0 0
      ambari-client/python-client/src/main/python/ambari_client/model/service.py
  39. 0 0
      ambari-client/python-client/src/main/python/ambari_client/model/stack.py
  40. 0 0
      ambari-client/python-client/src/main/python/ambari_client/model/status.py
  41. 0 0
      ambari-client/python-client/src/main/python/ambari_client/model/utils.py
  42. 0 0
      ambari-client/python-client/src/main/python/ambari_client/resources/__init__.py
  43. 0 0
      ambari-client/python-client/src/main/python/ambari_client/resources/clusters.py
  44. 0 0
      ambari-client/python-client/src/main/python/ambari_client/resources/hosts.py
  45. 0 0
      ambari-client/python-client/src/main/python/ambari_client/resources/stacks.py
  46. 0 0
      ambari-client/python-client/src/main/python/setup.py
  47. 0 0
      ambari-client/python-client/src/packages/tarball/all.xml
  48. 0 0
      ambari-client/python-client/src/test/python/TestAmbariClient.py
  49. 0 0
      ambari-client/python-client/src/test/python/TestClusterModel.py
  50. 0 0
      ambari-client/python-client/src/test/python/TestComponentModel.py
  51. 0 0
      ambari-client/python-client/src/test/python/TestHostModel.py
  52. 0 0
      ambari-client/python-client/src/test/python/TestServiceModel.py
  53. 0 0
      ambari-client/python-client/src/test/python/TestStatusModel.py
  54. 0 0
      ambari-client/python-client/src/test/python/json/ambariclient_bootstrap_hosts.json
  55. 0 0
      ambari-client/python-client/src/test/python/json/ambariclient_get_all_clusters.json
  56. 0 0
      ambari-client/python-client/src/test/python/json/ambariclient_get_all_hosts.json
  57. 0 0
      ambari-client/python-client/src/test/python/json/ambariclient_get_components.json
  58. 0 0
      ambari-client/python-client/src/test/python/json/ambariclient_get_config.json
  59. 0 0
      ambari-client/python-client/src/test/python/json/ambariclient_get_host.json
  60. 0 0
      ambari-client/python-client/src/test/python/json/clustermodel_error_deleting_host.json
  61. 0 0
      ambari-client/python-client/src/test/python/json/clustermodel_get_all_hosts.json
  62. 0 0
      ambari-client/python-client/src/test/python/json/clustermodel_get_all_services.json
  63. 0 0
      ambari-client/python-client/src/test/python/json/clustermodel_get_cluster.json
  64. 0 0
      ambari-client/python-client/src/test/python/json/clustermodel_get_core_site_config.json
  65. 0 0
      ambari-client/python-client/src/test/python/json/clustermodel_get_global_config.json
  66. 0 0
      ambari-client/python-client/src/test/python/json/clustermodel_get_hdfs_site_config.json
  67. 0 0
      ambari-client/python-client/src/test/python/json/clustermodel_get_host.json
  68. 0 0
      ambari-client/python-client/src/test/python/json/clustermodel_get_mapred_site_config.json
  69. 0 0
      ambari-client/python-client/src/test/python/json/clustermodel_get_service.json
  70. 0 0
      ambari-client/python-client/src/test/python/json/componentmodel_get_metrics.json
  71. 0 0
      ambari-client/python-client/src/test/python/json/get_cluster_service.json
  72. 0 0
      ambari-client/python-client/src/test/python/json/get_components_from_stack.json
  73. 0 0
      ambari-client/python-client/src/test/python/json/get_host_component.json
  74. 0 0
      ambari-client/python-client/src/test/python/json/get_host_components.json
  75. 0 0
      ambari-client/python-client/src/test/python/json/get_service_component.json
  76. 0 0
      ambari-client/python-client/src/test/python/json/get_service_components.json
  77. 0 0
      ambari-client/python-client/src/test/python/json/hostmodel_get_host_component.json
  78. 0 0
      ambari-client/python-client/src/test/python/json/hostmodel_get_host_components.json
  79. 0 0
      ambari-client/python-client/src/test/python/json/servicemodel_get_component.json
  80. 0 0
      ambari-client/python-client/src/test/python/json/servicemodel_get_components.json
  81. 0 0
      ambari-client/python-client/src/test/python/json/status_error_with_message.json
  82. 0 0
      ambari-client/python-client/src/test/python/json/status_ok_with_id.json
  83. 0 0
      ambari-client/python-client/src/test/python/unitTests.py
  84. 0 0
      ambari-client/python-client/src/test/python/utils/HttpClientInvoker.py
  85. 58 14
      ambari-project/pom.xml
  86. 120 0
      ambari-shell/pom.xml
  87. 110 0
      ambari-shell/src/main/java/org/apache/ambari/shell/AmbariShell.java
  88. 173 0
      ambari-shell/src/main/java/org/apache/ambari/shell/commands/BasicCommands.java
  89. 184 0
      ambari-shell/src/main/java/org/apache/ambari/shell/commands/BlueprintCommands.java
  90. 260 0
      ambari-shell/src/main/java/org/apache/ambari/shell/commands/ClusterCommands.java
  91. 53 0
      ambari-shell/src/main/java/org/apache/ambari/shell/commands/ElephantCommand.java
  92. 117 0
      ambari-shell/src/main/java/org/apache/ambari/shell/commands/HostCommands.java
  93. 125 0
      ambari-shell/src/main/java/org/apache/ambari/shell/configuration/ConverterConfiguration.java
  94. 102 0
      ambari-shell/src/main/java/org/apache/ambari/shell/configuration/ShellConfiguration.java
  95. 50 0
      ambari-shell/src/main/java/org/apache/ambari/shell/customization/AmbariBanner.java
  96. 43 0
      ambari-shell/src/main/java/org/apache/ambari/shell/customization/AmbariPrompt.java
  97. 159 0
      ambari-shell/src/main/java/org/apache/ambari/shell/model/AmbariContext.java
  98. 53 0
      ambari-shell/src/main/java/org/apache/ambari/shell/model/Focus.java
  99. 55 0
      ambari-shell/src/main/java/org/apache/ambari/shell/model/FocusType.java
  100. 59 0
      ambari-shell/src/main/java/org/apache/ambari/shell/model/Hints.java

+ 20 - 0
ambari-client/assemblies/client.xml

@@ -0,0 +1,20 @@
+<?xml version="1.0"?>
+<!--
+  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.
+-->
+<assembly>
+</assembly>

+ 113 - 0
ambari-client/groovy-client/pom.xml

@@ -0,0 +1,113 @@
+<?xml version="1.0"?>
+<!-- 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.
+  See accompanying LICENSE file. -->
+<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">
+  <parent>
+    <artifactId>ambari-client</artifactId>
+    <groupId>org.apache.ambari</groupId>
+    <version>1.3.0-SNAPSHOT</version>
+    <relativePath>../../ambari-client/</relativePath>
+  </parent>
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>org.apache.ambari</groupId>
+  <artifactId>groovy-client</artifactId>
+  <packaging>jar</packaging>
+  <version>1.3.0-SNAPSHOT</version>
+  <name>Ambari Groovy Client</name>
+  <description>Ambari Groovy client</description>
+  <properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+  </properties>
+  <dependencies>
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-api</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-log4j12</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.codehaus.groovy</groupId>
+      <artifactId>groovy-all</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.codehaus.groovy.modules.http-builder</groupId>
+      <artifactId>http-builder</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.easymock</groupId>
+      <artifactId>easymock</artifactId>
+      <version>3.2</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.spockframework</groupId>
+      <artifactId>spock-core</artifactId>
+      <version>0.7-groovy-2.0</version>
+    </dependency>
+  </dependencies>
+  <build>
+    <plugins>
+      <plugin>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <!-- 2.8.0-01 and later require maven-compiler-plugin 3.1 or higher -->
+        <version>3.1</version>
+        <configuration>
+          <compilerId>groovy-eclipse-compiler</compilerId>
+        </configuration>
+        <dependencies>
+          <dependency>
+            <groupId>org.codehaus.groovy</groupId>
+            <artifactId>groovy-eclipse-compiler</artifactId>
+            <version>2.8.0-01</version>
+          </dependency>
+          <!-- for 2.8.0-01 and later you must have an explicit dependency on
+            groovy-eclipse-batch -->
+          <dependency>
+            <groupId>org.codehaus.groovy</groupId>
+            <artifactId>groovy-eclipse-batch</artifactId>
+            <version>2.1.8-01</version>
+          </dependency>
+        </dependencies>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.rat</groupId>
+        <artifactId>apache-rat-plugin</artifactId>
+        <configuration>
+          <excludes>
+            <exclude>src/main/resources/blueprints/multi-node-hdfs-yarn</exclude>
+            <exclude>src/main/resources/blueprints/single-node-hdfs-yarn</exclude>
+            <exclude>src/test/resources/blueprint.json</exclude>
+            <exclude>src/test/resources/blueprints.json</exclude>
+            <exclude>src/test/resources/cluster.json</exclude>
+            <exclude>src/test/resources/hdfsServiceComponents.json</exclude>
+            <exclude>src/test/resources/services.json</exclude>
+            <exclude>src/test/resources/tasks.json</exclude>
+          </excludes>
+        </configuration>
+        <executions>
+          <execution>
+            <phase>test</phase>
+            <goals>
+              <goal>check</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <artifactId>maven-assembly-plugin</artifactId>
+        <configuration>
+          <skipAssembly>true</skipAssembly>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+</project>

+ 510 - 0
ambari-client/groovy-client/src/main/groovy/org/apache/ambari/groovy/client/AmbariClient.groovy

@@ -0,0 +1,510 @@
+/**
+ * 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.groovy.client
+
+import groovy.json.JsonBuilder
+import groovy.json.JsonSlurper
+import groovyx.net.http.HttpResponseException
+import groovyx.net.http.RESTClient
+import org.apache.http.NoHttpResponseException
+import org.apache.http.client.ClientProtocolException
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+
+/**
+ * Basic client to send requests to the Ambari server.
+ */
+class AmbariClient {
+
+  private static final Logger LOGGER = LoggerFactory.getLogger(AmbariClient.class)
+  private static final int PAD = 30
+  private static final int OK_RESPONSE = 200
+  boolean debugEnabled = false;
+  def RESTClient ambari
+  def slurper = new JsonSlurper()
+  def clusterName
+
+  /**
+   * Connects to the ambari server.
+   *
+   * @param host host of the Ambari server; default value is localhost
+   * @param port port of the Ambari server; default value is 8080
+   * @param user username of the Ambari server; default is admin
+   * @param password password fom the Ambari server; default is admin
+   */
+  AmbariClient(host = 'localhost', port = '8080', user = 'admin', password = 'admin') {
+    ambari = new RESTClient("http://${host}:${port}/api/v1/" as String)
+    ambari.headers['Authorization'] = 'Basic ' + "$user:$password".getBytes('iso-8859-1').encodeBase64()
+    ambari.headers['X-Requested-By'] = 'ambari'
+  }
+
+  /**
+   * Connects to the ambari server.
+   *
+   * @param restClient underlying client
+   * @param slurper slurper to parse responses
+   */
+  AmbariClient(RESTClient restClient, JsonSlurper slurper) {
+    this.ambari = restClient
+    this.slurper = slurper
+  }
+
+  /**
+   * Sets the debug variable. Used by printing the API calls for the Ambari Shell.
+   *
+   * @param enabled enable or disable
+   */
+  def setDebugEnabled(boolean enabled) {
+    debugEnabled = enabled;
+  }
+
+  /**
+   * Returns the name of the cluster.
+   *
+   * @return the name of the cluster of null if no cluster yet
+   */
+  def String getClusterName() {
+    if (!clusterName) {
+      def clusters = getClusters();
+      if (clusters) {
+        clusterName = clusters.items[0]?.Clusters?.cluster_name
+      }
+    }
+    return clusterName
+  }
+
+  /**
+   * Checks whether the blueprint exists or not.
+   *
+   * @param id id of the blueprint
+   * @return true if exists false otherwise
+   */
+  def boolean doesBlueprintExists(String id) {
+    def result = false
+    try {
+      result = ambari.get(path: "blueprints/$id", query: ['fields': "Blueprints"]).status == OK_RESPONSE
+    } catch (e) {
+      LOGGER.info("Blueprint does not exist", e)
+    }
+    return result
+  }
+
+  /**
+   * Checks whether there are available blueprints or not.
+   *
+   * @return true if blueprints are available false otherwise
+   */
+  def boolean isBlueprintAvailable() {
+    return getBlueprints().items?.size > 0
+  }
+
+  /**
+   * Returns a pre-formatted String of the blueprint.
+   *
+   * @param id id of the blueprint
+   * @return formatted String
+   */
+  def String showBlueprint(String id) {
+    def resp = getBlueprint(id)
+    if (resp) {
+      def groups = resp.host_groups.collect {
+        def name = it.name
+        def comps = it.components.collect { it.name.padRight(PAD).padLeft(PAD + 10) }.join("\n")
+        return "HOSTGROUP: $name\n$comps"
+      }.join("\n")
+      return "[${resp.Blueprints.stack_name}:${resp.Blueprints.stack_version}]\n$groups"
+    }
+    return "Not found"
+  }
+
+  /**
+   * Returns the host group - components mapping of the blueprint.
+   *
+   * @param id id of the blueprint
+   * @return Map where the key is the host group and the value is the list of components
+   */
+  def Map<String, List<String>> getBlueprintMap(String id) {
+    def result = getBlueprint(id).host_groups?.collectEntries { [(it.name): it.components.collect { it.name }] }
+    result ?: new HashMap()
+  }
+
+  /**
+   * Returns a pre-formatted String of the blueprints.
+   *
+   * @return formatted blueprint list
+   */
+  def String showBlueprints() {
+    getBlueprints().items.collect { "${it.Blueprints.blueprint_name.padRight(PAD)} [${it.Blueprints.stack_name}:${it.Blueprints.stack_version}]" }.join("\n")
+  }
+
+  /**
+   * Returns a Map containing the blueprint name - stack association.
+   *
+   * @return Map where the key is the blueprint's name value is the used stack
+   */
+  def Map<String, String> getBlueprintsMap() {
+    def result = getBlueprints().items?.collectEntries { [(it.Blueprints.blueprint_name): it.Blueprints.stack_name + ":" + it.Blueprints.stack_version] }
+    result ?: new HashMap()
+  }
+
+  /**
+   * Returns the name of the host groups for a given blueprint.
+   *
+   * @param blueprint id of the blueprint
+   * @return host group list or empty list
+   */
+  def List<String> getHostGroups(String blueprint) {
+    def result = getBlueprint(blueprint)
+    result ? result.host_groups.collect { it.name } : new ArrayList<String>()
+  }
+
+  /**
+   * Returns a pre-formatted String of a blueprint used by the cluster.
+   *
+   * @return formatted String
+   */
+  def String showClusterBlueprint() {
+    ambari.get(path: "clusters/${getClusterName()}", query: ['format': "blueprint"]).data.text
+  }
+
+  /**
+   * Adds a blueprint to the Ambari server. Exception is thrown if fails.
+   *
+   * @param json blueprint as json
+   * @throws HttpResponseException in case of error
+   */
+  def void addBlueprint(String json) throws HttpResponseException {
+    if (json) {
+      postBlueprint(json)
+    }
+  }
+
+  /**
+   * Adds 2 default blueprints.
+   *
+   * @throws HttpResponseException in case of error
+   */
+  def void addDefaultBlueprints() throws HttpResponseException {
+    addBlueprint(getResourceContent("blueprints/multi-node-hdfs-yarn"))
+    addBlueprint(getResourceContent("blueprints/single-node-hdfs-yarn"))
+  }
+
+  /**
+   * Creates a cluster with the given blueprint and host group - host association.
+   *
+   * @param clusterName name of the cluster
+   * @param blueprintName blueprint id used to create this cluster
+   * @param hostGroups Map<String, List<String> key - host group, value - host list
+   * @return true if the creation was successful false otherwise
+   * @throws HttpResponseException in case of error
+   */
+  def void createCluster(String clusterName, String blueprintName, Map<String, List<String>> hostGroups) throws HttpResponseException {
+    ambari.post(path: "clusters/$clusterName", body: createClusterJson(blueprintName, hostGroups), { it })
+  }
+
+  /**
+   * Deletes the cluster.
+   *
+   * @param clusterName name of the cluster
+   * @throws HttpResponseException in case of error
+   */
+  def void deleteCluster(String clusterName) throws HttpResponseException {
+    ambari.delete(path: "clusters/$clusterName")
+  }
+
+  /**
+   * Returns the active cluster as json
+   *
+   * @return cluster as json String
+   * @throws HttpResponseException in case of error
+   */
+  def String getClusterAsJson() throws HttpResponseException {
+    getRequest("clusters/${getClusterName()}")
+  }
+
+  /**
+   * Returns all clusters as json
+   *
+   * @return json String
+   * @throws HttpResponseException in case of error
+   */
+  def getClustersAsJson() throws HttpResponseException {
+    getRequest("clusters")
+  }
+
+  /**
+   * Returns a pre-formatted String of the clusters.
+   *
+   * @return pre-formatted cluster list
+   */
+  def String showClusterList() {
+    getClusters().items.collect { "[$it.Clusters.cluster_id] $it.Clusters.cluster_name:$it.Clusters.version" }.join("\n")
+  }
+
+  /**
+   * Returns the task properties as Map.
+   *
+   * @param request request id; default is 1
+   * @return property Map or empty Map
+   */
+  def getTasks(request = 1) {
+    getAllResources("requests/$request", "tasks/Tasks")
+  }
+
+  /**
+   * Returns a pre-formatted task list.
+   *
+   * @param request request id; default is 1
+   * @return pre-formatted task list
+   */
+  def String showTaskList(request = 1) {
+    getTasks(request)?.tasks.collect { "${it.Tasks.command_detail.padRight(PAD)} [${it.Tasks.status}]" }.join("\n")
+  }
+
+  /**
+   * Returns a Map containing the task's command detail as key and the task's status as value.
+   *
+   * @param request request id; default is 1
+   * @return key task command detail; task value status
+   */
+  def Map<String, String> getTaskMap(request = 1) {
+    def result = getTasks(request).tasks?.collectEntries { [(it.Tasks.command_detail): it.Tasks.status] }
+    result ?: new HashMap()
+  }
+
+  /**
+   * Returns the available host names and its states.
+   *
+   * @return hostname state association
+   */
+  def Map<String, String> getHostNames() {
+    getHosts().items.collectEntries { [(it.Hosts.host_name): it.Hosts.host_status] }
+  }
+
+  /**
+   * Returns a pre-formatted list of the hosts.
+   *
+   * @return pre-formatted String
+   */
+  def String showHostList() {
+    getHosts().items.collect { "$it.Hosts.host_name [$it.Hosts.host_status] $it.Hosts.ip $it.Hosts.os_type:$it.Hosts.os_arch" }.join("\n")
+  }
+
+  /**
+   * Returns a pre-formatted list of the service components.
+   *
+   * @return pre-formatted String
+   */
+  def String showServiceComponents() {
+    getServices().items.collect {
+      def name = it.ServiceInfo.service_name
+      def state = it.ServiceInfo.state
+      def componentList = getServiceComponents(name).items.collect {
+        "    ${it.ServiceComponentInfo.component_name.padRight(PAD)}  [$it.ServiceComponentInfo.state]"
+      }.join("\n")
+      "${name.padRight(PAD)} [$state]\n$componentList"
+    }.join("\n")
+  }
+
+  /**
+   * Returns the service components properties as Map where the key is the service name and
+   * value is a component - state association.
+   *
+   * @return service name - [component name - status]
+   */
+  def Map<String, Map<String, String>> getServiceComponentsMap() {
+    def result = getServices().items.collectEntries {
+      def name = it.ServiceInfo.service_name
+      def componentList = getServiceComponents(name).items.collectEntries { [(it.ServiceComponentInfo.component_name): it.ServiceComponentInfo.state] }
+      [(name): componentList]
+    }
+    result ?: new HashMap()
+  }
+
+  /**
+   * Returns a pre-formatted service list.
+   *
+   * @return formatted String
+   */
+  def String showServiceList() {
+    getServices().items.collect { "${it.ServiceInfo.service_name.padRight(PAD)} [$it.ServiceInfo.state]" }.join("\n")
+  }
+
+  /**
+   * Returns the services properties as Map where the key is the service's name and the values is the service's state.
+   *
+   * @return service name - service state association as Map
+   */
+  def Map<String, String> getServicesMap() {
+    def result = getServices().items.collectEntries { [(it.ServiceInfo.service_name): it.ServiceInfo.state] }
+    result ?: new HashMap()
+  }
+
+  /**
+   * Returns a pre-formatted component list of a host.
+   *
+   * @param host which host's components are requested
+   * @return formatted String
+   */
+  def String showHostComponentList(host) {
+    getHostComponents(host).items.collect { "${it.HostRoles.component_name.padRight(PAD)} [$it.HostRoles.state]" }.join("\n")
+  }
+
+  /**
+   * Returns the host's components as Map where the key is the component's name and values is its state.
+   *
+   * @param host which host's components are requested
+   * @return component name - state association
+   */
+  def Map<String, String> getHostComponentsMap(host) {
+    def result = getHostComponents(host).items?.collectEntries { [(it.HostRoles.component_name): it.HostRoles.state] }
+    result ?: new HashMap()
+  }
+
+  /**
+   * Returns the blueprint json as String.
+   *
+   * @param id id of the blueprint
+   * @return json as String, exception if thrown is it fails
+   */
+  def String getBlueprintAsJson(id) {
+    return getRequest("blueprints/$id", "host_groups,Blueprints")
+  }
+
+  private def getAllResources(resourceName, fields) {
+    slurp("clusters/${getClusterName()}/$resourceName", "$fields/*")
+  }
+
+  /**
+   * Posts the blueprint JSON to Ambari with name 'bp' in the URL
+   * because it does not matter here. The blueprint's name is
+   * provided in the JSON.
+   *
+   * @param blueprint json
+   * @return response message
+   */
+  private void postBlueprint(String blueprint) {
+    ambari.post(path: "blueprints/bp", body: blueprint, { it })
+  }
+
+  private def createClusterJson(String name, Map hostGroups) {
+    def builder = new JsonBuilder()
+    def groups = hostGroups.collect {
+      def hostList = it.value.collect { ['fqdn': it] }
+      [name: it.key, hosts: hostList]
+    }
+    builder { "blueprint" name; "host-groups" groups }
+    builder.toPrettyString()
+  }
+
+  private def slurp(path, fields = "") {
+    def baseUri = ambari.getUri();
+    if (debugEnabled) {
+      println "[DEBUG] ${baseUri}${path}?fields=$fields"
+    }
+    def result = new HashMap()
+    try {
+      result = slurper.parseText(getRequest(path, fields))
+    } catch (e) {
+      if (e instanceof NoHttpResponseException || e instanceof ConnectException || e instanceof ClientProtocolException ||
+        e instanceof UnknownHostException || (e instanceof HttpResponseException && e.message == "Bad credentials")) {
+        throw new AmbariConnectionException("Cannot connect to Ambari $baseUri")
+      }
+      LOGGER.error("Error occurred during GET request to $baseUri/$path", e)
+    }
+    return result
+  }
+
+  /**
+   * Return the blueprint's properties as a Map.
+   *
+   * @param id id of the blueprint
+   * @return properties as Map
+   */
+  private def getBlueprint(id) {
+    slurp("blueprints/$id", "host_groups,Blueprints")
+  }
+
+  /**
+   * Returns a Map containing the blueprint's properties parsed from the Ambari response json.
+   *
+   * @return blueprint's properties as Map or empty Map
+   */
+  private def getBlueprints() {
+    slurp("blueprints", "Blueprints")
+  }
+
+  /**
+   * Returns a Map containing the cluster's properties parsed from the Ambari response json.
+   *
+   * @return cluster's properties as Map or empty Map
+   */
+  private def getClusters() {
+    slurp("clusters", "Clusters")
+  }
+
+  /**
+   * Returns the available hosts properties as a Map.
+   *
+   * @return Map containing the hosts properties
+   */
+  private def getHosts() {
+    slurp("hosts", "Hosts")
+  }
+
+  /**
+   * Returns the service components properties as Map.
+   *
+   * @param service id of the service
+   * @return service component properties as Map
+   */
+  private def getServiceComponents(service) {
+    getAllResources("services/$service/components", "ServiceComponentInfo")
+  }
+
+  /**
+   * Returns the services properties as Map parsed from Ambari response json.
+   *
+   * @return service properties as Map
+   */
+  private def getServices() {
+    getAllResources("services", "ServiceInfo")
+  }
+
+  /**
+   * Returns the properties of the host components as a Map parsed from the Ambari response json.
+   *
+   * @param host which host's components are requested
+   * @return component properties as Map
+   */
+  private def getHostComponents(host) {
+    getAllResources("hosts/$host/host_components", "HostRoles")
+  }
+
+  private String getRequest(path, fields = "") {
+    if (fields) {
+      ambari.get(path: "$path", query: ['fields': "$fields"]).data.text
+    } else {
+      ambari.get(path: "$path").data.text
+    }
+  }
+
+  private String getResourceContent(name) {
+    getClass().getClassLoader().getResourceAsStream(name)?.text
+  }
+}

+ 27 - 0
ambari-client/groovy-client/src/main/groovy/org/apache/ambari/groovy/client/AmbariConnectionException.groovy

@@ -0,0 +1,27 @@
+/**
+ * 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.groovy.client
+
+/**
+ * Exception used to indicate that there are connection issues.
+ */
+class AmbariConnectionException extends RuntimeException {
+  public AmbariConnectionException(String s) {
+    super(s)
+  }
+}

+ 26 - 0
ambari-client/groovy-client/src/main/java/org/apache/ambari/groovy/client/DoNotDeleteMe.java

@@ -0,0 +1,26 @@
+/**
+ * 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.groovy.client;
+
+/**
+ * The maven plugin will only compile the groovy classes into the jar
+ * if it contains at least one java class, otherwise it will tell you that
+ * there are no source classes found.
+ */
+public class DoNotDeleteMe {
+}

+ 57 - 0
ambari-client/groovy-client/src/main/resources/blueprints/multi-node-hdfs-yarn

@@ -0,0 +1,57 @@
+{
+  "host_groups" : [
+    {
+      "name" : "master",
+      "components" : [
+      {
+        "name" : "NAMENODE"
+      },
+      {
+        "name" : "SECONDARY_NAMENODE"
+      },       
+      {
+        "name" : "RESOURCEMANAGER"
+      },
+      {
+        "name" : "HISTORYSERVER"
+      },
+      {
+        "name" : "NAGIOS_SERVER"
+      },
+      {
+        "name" : "ZOOKEEPER_SERVER"
+      }
+      ],
+      "cardinality" : "1"
+    },
+    {
+      "name" : "slaves",
+      "components" : [
+      {
+        "name" : "DATANODE"
+      },
+      {
+        "name" : "HDFS_CLIENT"
+      },
+      {
+        "name" : "NODEMANAGER"
+      },
+      {
+        "name" : "YARN_CLIENT"
+      },
+      {
+        "name" : "MAPREDUCE2_CLIENT"
+      },
+      {
+        "name" : "ZOOKEEPER_CLIENT"
+      }
+      ],
+      "cardinality" : "2"
+    }
+  ],
+  "Blueprints" : {
+    "blueprint_name" : "multi-node-hdfs-yarn",
+    "stack_name" : "HDP",
+    "stack_version" : "2.0"
+  }
+}

+ 48 - 0
ambari-client/groovy-client/src/main/resources/blueprints/single-node-hdfs-yarn

@@ -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"
+  }
+}

+ 425 - 0
ambari-client/groovy-client/src/test/groovy/com.sequenceiq.ambari.client/AmbariClientTest.groovy

@@ -0,0 +1,425 @@
+/**
+ * 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.groovy.client
+
+import groovy.json.JsonSlurper
+import groovyx.net.http.HttpResponseDecorator
+import groovyx.net.http.HttpResponseException
+import groovyx.net.http.RESTClient
+import spock.lang.Specification
+
+class AmbariClientTest extends Specification {
+
+  def rest = Mock(RESTClient)
+  def slurper = Mock(JsonSlurper)
+  def decorator = Mock(HttpResponseDecorator)
+  def ambari = new AmbariClient(rest, slurper)
+  def realSlurper = new JsonSlurper()
+
+  def "test get the name of the cluster"() {
+    given:
+    def request = [path: "clusters", query: [fields: "Clusters"]]
+    def items = new ArrayList()
+    items.add([Clusters: [cluster_name: "cluster1"]])
+    def mockResponse = ["items": items]
+    rest.get(request) >> decorator
+    decorator.data >> [text: "map"]
+    slurper.parseText("map") >> mockResponse
+
+    when:
+    def result = ambari.getClusterName()
+
+    then:
+    "cluster1" == result
+  }
+
+  def "test get the name when there is no cluster"() {
+    given:
+    def request = [path: "clusters", query: [fields: "Clusters"]]
+    def mockResponse = ["items": new ArrayList()]
+    rest.get(request) >> decorator
+    decorator.data >> [text: "map"]
+    slurper.parseText("map") >> mockResponse
+
+    when:
+    def result = ambari.getClusterName()
+
+    then:
+    null == result
+  }
+
+  def "test if blueprint does not exists"() {
+    given:
+    decorator.status >> 500
+    def request = [path: "blueprints/bp", query: ['fields': "Blueprints"]]
+    rest.get(request) >> decorator
+
+    when:
+    def result = ambari.doesBlueprintExists("bp")
+
+    then:
+    result == false
+  }
+
+  def "test if blueprint exists"() {
+    given:
+    decorator.status >> 200
+    def request = [path: "blueprints/bp", query: ['fields': "Blueprints"]]
+    rest.get(request) >> decorator
+
+    when:
+    def result = ambari.doesBlueprintExists("bp")
+
+    then:
+    result == true
+  }
+
+  def "test get blueprint as map"() {
+    given:
+    def request = [path: "blueprints/bp", query: [fields: "host_groups,Blueprints"]]
+    rest.get(request) >> decorator
+    decorator.data >> [text: "map"]
+    def mockResponse = [host_groups: [[name: "bp", components: [[name: "hdfs"], [name: "yarn"]]]]]
+    slurper.parseText("map") >> mockResponse
+
+    when:
+    def response = ambari.getBlueprintMap("bp")
+
+    then:
+    [bp: ["hdfs", "yarn"]] == response
+  }
+
+  def "test get blueprint as map for empty result"() {
+    given:
+    def request = [path: "blueprints/bp", query: [fields: "host_groups,Blueprints"]]
+    rest.get(request) >> decorator
+    decorator.data >> [text: "map"]
+    slurper.parseText("map") >> [:]
+
+    when:
+    def response = ambari.getBlueprintMap("bp")
+
+    then:
+    [:] == response
+  }
+
+  def "test get host groups"() {
+    given:
+    def request = [path: "blueprints/bp", query: [fields: "host_groups,Blueprints"]]
+    rest.get(request) >> decorator
+    decorator.data >> [text: "map"]
+    slurper.parseText("map") >> [host_groups: [[name: "group1"], [name: "group2"]]]
+
+    when:
+    def result = ambari.getHostGroups("bp")
+
+    then:
+    ["group1", "group2"] == result
+  }
+
+  def "test get host groups for no groups"() {
+    given:
+    def request = [path: "blueprints/bp", query: [fields: "host_groups,Blueprints"]]
+    rest.get(request) >> decorator
+    decorator.data >> [text: "map"]
+    slurper.parseText("map") >> null
+
+    when:
+    def result = ambari.getHostGroups("bp")
+
+    then:
+    [] == result
+  }
+
+  def "test get host names"() {
+    given:
+    def request = [path: "hosts", query: [fields: "Hosts"]]
+    rest.get(request) >> decorator
+    decorator.data >> [text: "map"]
+    def hosts = [[Hosts: [host_name: "server.ambari.com", host_status: "HEALTHY"]]]
+    def mapResponse = [items: hosts]
+    slurper.parseText("map") >> mapResponse
+
+    when:
+    def result = ambari.getHostNames()
+
+    then:
+    "HEALTHY" == result["server.ambari.com"]
+    1 == result.size()
+  }
+
+  def "test get host names for empty result"() {
+    given:
+    def request = [path: "hosts", query: [fields: "Hosts"]]
+    rest.get(request) >> decorator
+    decorator.data >> [text: "map"]
+    slurper.parseText("map") >> []
+
+    when:
+    def result = ambari.getHostNames()
+
+    then:
+    [:] == result
+  }
+
+  def "test add blueprint for success"() {
+    given:
+    def json = getClass().getResourceAsStream("blueprint.json")
+    def request = [path: "blueprints/bp", body: json]
+    rest.post(request, { it })
+
+    when:
+    ambari.addBlueprint(json)
+
+    then:
+    notThrown(HttpResponseException)
+  }
+
+  def "test add blueprint for empty json"() {
+    given:
+    def request = [path: "blueprints/bp", body: ""]
+    rest.post(request, { it })
+
+    when:
+    ambari.addBlueprint("")
+
+    then:
+    notThrown(HttpResponseException)
+  }
+
+  def "test create cluster json"() {
+    given:
+    def json = getClass().getResourceAsStream("/cluster.json").text
+    def groups = [host_group_1: ["server.ambari.com", "server2.ambari.com"]]
+    def request = [path: "clusters/c1", body: json]
+
+    when:
+    ambari.createCluster("c1", "c1", groups)
+
+    then:
+    1 * rest.post(request, { it })
+  }
+
+  def "test get task as map"() {
+    given:
+    //get the name of the cluster
+    def req1 = [path: "clusters", query: [fields: "Clusters"]]
+    def items = new ArrayList()
+    items.add([Clusters: [cluster_name: "cluster1"]])
+    def mockResponse = ["items": items]
+    rest.get(req1) >> decorator
+    decorator.data >> [text: "map"]
+    slurper.parseText("map") >> mockResponse
+    // get the actual tasks
+    def req2 = [path: "clusters/cluster1/requests/1", query: ['fields': "tasks/Tasks/*"]]
+    def decorator2 = Mock(HttpResponseDecorator)
+    rest.get(req2) >> decorator2
+    decorator2.data >> [text: "map2"]
+    def json = realSlurper.parseText(getClass().getResourceAsStream("/tasks.json").text)
+    slurper.parseText("map2") >> json
+
+    when:
+    def result = ambari.getTaskMap()
+
+    then:
+    ["DATANODE INSTALL"          : "COMPLETED",
+     "DATANODE START"            : "COMPLETED",
+     "HDFS_CLIENT INSTALL"       : "COMPLETED",
+     "HISTORYSERVER INSTALL"     : "COMPLETED",
+     "HISTORYSERVER START"       : "COMPLETED",
+     "MAPREDUCE2_CLIENT INSTALL" : "COMPLETED",
+     "NAMENODE INSTALL"          : "COMPLETED",
+     "NAMENODE START"            : "COMPLETED",
+     "NODEMANAGER INSTALL"       : "COMPLETED",
+     "NODEMANAGER START"         : "COMPLETED",
+     "RESOURCEMANAGER INSTALL"   : "COMPLETED",
+     "RESOURCEMANAGER START"     : "COMPLETED",
+     "SECONDARY_NAMENODE INSTALL": "COMPLETED",
+     "SECONDARY_NAMENODE START"  : "COMPLETED",
+     "YARN_CLIENT INSTALL"       : "COMPLETED",
+     "ZOOKEEPER_CLIENT INSTALL"  : "COMPLETED",
+     "ZOOKEEPER_SERVER INSTALL"  : "COMPLETED",
+     "ZOOKEEPER_SERVER START"    : "COMPLETED"
+    ] == result
+  }
+
+  def "test get task as map for no task"() {
+    given:
+    //get the name of the cluster
+    def req1 = [path: "clusters", query: [fields: "Clusters"]]
+    def items = new ArrayList()
+    items.add([Clusters: [cluster_name: "cluster1"]])
+    def mockResponse = ["items": items]
+    rest.get(req1) >> decorator
+    decorator.data >> [text: "map"]
+    slurper.parseText("map") >> mockResponse
+    // get the actual tasks
+    def req2 = [path: "clusters/cluster1/requests/1", query: ['fields': "tasks/Tasks/*"]]
+    def decorator2 = Mock(HttpResponseDecorator)
+    rest.get(req2) >> decorator2
+    decorator2.data >> [text: "map2"]
+    slurper.parseText("map2") >> [:]
+
+    when:
+    def result = ambari.getTaskMap()
+
+    then:
+    [:] == result
+  }
+
+  def "test get service components map"() {
+    given:
+    // get the name of the cluster
+    def req1 = [path: "clusters", query: [fields: "Clusters"]]
+    def items = new ArrayList()
+    items.add([Clusters: [cluster_name: "cluster1"]])
+    def mockResponse = ["items": items]
+    rest.get(req1) >> decorator
+    decorator.data >> [text: "map"]
+    slurper.parseText("map") >> mockResponse
+    // get the services
+    def req2 = [path: "clusters/cluster1/services", query: ['fields': "ServiceInfo/*"]]
+    def decorator2 = Mock(HttpResponseDecorator)
+    rest.get(req2) >> decorator2
+    decorator2.data >> [text: "dec"]
+    def json = realSlurper.parseText(getClass().getResourceAsStream("/services.json").text)
+    slurper.parseText("dec") >> json
+    // get service components
+    def req3 = [path: "clusters/cluster1/services/HDFS/components", query: ['fields': "ServiceComponentInfo/*"]]
+    def decorator3 = Mock(HttpResponseDecorator)
+    rest.get(req3) >> decorator3
+    decorator3.data >> [text: "dec3"]
+    def json2 = realSlurper.parseText(getClass().getResourceAsStream("/hdfsServiceComponents.json").text)
+    slurper.parseText("dec3") >> json2
+
+    when:
+    def Map result = ambari.getServiceComponentsMap()
+
+    then:
+    [HDFS: [
+      DATANODE          : "STARTED",
+      HDFS_CLIENT       : "INSTALLED",
+      NAMENODE          : "STARTED",
+      SECONDARY_NAMENODE: "STARTED"]
+    ] == result
+  }
+
+  def "test get service components map for empty services"() {
+    given:
+    // get the name of the cluster
+    def req1 = [path: "clusters", query: [fields: "Clusters"]]
+    def items = new ArrayList()
+    items.add([Clusters: [cluster_name: "cluster1"]])
+    def mockResponse = ["items": items]
+    rest.get(req1) >> decorator
+    decorator.data >> [text: "map"]
+    slurper.parseText("map") >> mockResponse
+    // get the services
+    def req2 = [path: "clusters/cluster1/services", query: ['fields': "ServiceInfo/*"]]
+    def decorator2 = Mock(HttpResponseDecorator)
+    rest.get(req2) >> decorator2
+    decorator2.data >> [text: "dec"]
+    slurper.parseText("dec") >> []
+
+    when:
+    def Map result = ambari.getServiceComponentsMap()
+
+    then:
+    [:] == result
+  }
+
+  def "test get service components map for empty components"() {
+    given:
+    // get the name of the cluster
+    def req1 = [path: "clusters", query: [fields: "Clusters"]]
+    def items = new ArrayList()
+    items.add([Clusters: [cluster_name: "cluster1"]])
+    def mockResponse = ["items": items]
+    rest.get(req1) >> decorator
+    decorator.data >> [text: "map"]
+    slurper.parseText("map") >> mockResponse
+    // get the services
+    def req2 = [path: "clusters/cluster1/services", query: ['fields': "ServiceInfo/*"]]
+    def decorator2 = Mock(HttpResponseDecorator)
+    rest.get(req2) >> decorator2
+    decorator2.data >> [text: "dec"]
+    def json = realSlurper.parseText(getClass().getResourceAsStream("/services.json").text)
+    slurper.parseText("dec") >> json
+    // get service components
+    def req3 = [path: "clusters/cluster1/services/HDFS/components", query: ['fields': "ServiceComponentInfo/*"]]
+    def decorator3 = Mock(HttpResponseDecorator)
+    rest.get(req3) >> decorator3
+    decorator3.data >> [text: "dec3"]
+    slurper.parseText("dec3") >> []
+
+    when:
+    def Map result = ambari.getServiceComponentsMap()
+
+    then:
+    [HDFS: [:]] == result
+  }
+
+  def "test get services as map"() {
+    given:
+    // get the name of the cluster
+    def req1 = [path: "clusters", query: [fields: "Clusters"]]
+    def items = new ArrayList()
+    items.add([Clusters: [cluster_name: "cluster1"]])
+    def mockResponse = ["items": items]
+    rest.get(req1) >> decorator
+    decorator.data >> [text: "map"]
+    slurper.parseText("map") >> mockResponse
+    // get the services
+    def req2 = [path: "clusters/cluster1/services", query: ['fields': "ServiceInfo/*"]]
+    def decorator2 = Mock(HttpResponseDecorator)
+    rest.get(req2) >> decorator2
+    decorator2.data >> [text: "dec"]
+    def json = realSlurper.parseText(getClass().getResourceAsStream("/services.json").text)
+    slurper.parseText("dec") >> json
+
+    when:
+    def result = ambari.getServicesMap()
+
+    then:
+    [HDFS: "STARTED"] == result
+  }
+
+  def "test get services as map for empty result"() {
+    given:
+    // get the name of the cluster
+    def req1 = [path: "clusters", query: [fields: "Clusters"]]
+    def items = new ArrayList()
+    items.add([Clusters: [cluster_name: "cluster1"]])
+    def mockResponse = ["items": items]
+    rest.get(req1) >> decorator
+    decorator.data >> [text: "map"]
+    slurper.parseText("map") >> mockResponse
+    // get the services
+    def req2 = [path: "clusters/cluster1/services", query: ['fields': "ServiceInfo/*"]]
+    def decorator2 = Mock(HttpResponseDecorator)
+    rest.get(req2) >> decorator2
+    decorator2.data >> [text: "dec"]
+    slurper.parseText("dec") >> []
+
+    when:
+    def result = ambari.getServicesMap()
+
+    then:
+    [:] == result
+  }
+}

+ 27 - 0
ambari-client/groovy-client/src/test/java/org/apache/ambari/groovy/client/NeitherMe.java

@@ -0,0 +1,27 @@
+/**
+ * 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.groovy.client;
+
+/**
+ * The maven plugin will only run the tests if it contains
+ * at least one java class.
+ *
+ * @see org.apache.ambari.groovy.client.DoNotDeleteMe
+ */
+public class NeitherMe {
+}

+ 48 - 0
ambari-client/groovy-client/src/test/resources/blueprint.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"
+  }
+}

+ 21 - 0
ambari-client/groovy-client/src/test/resources/blueprints.json

@@ -0,0 +1,21 @@
+{
+  "href" : "http://localhost:49233/api/v1/blueprints?fields=Blueprints",
+  "items" : [
+    {
+      "href" : "http://localhost:49233/api/v1/blueprints/multi-node-hdfs-yarn",
+      "Blueprints" : {
+        "blueprint_name" : "multi-node-hdfs-yarn",
+        "stack_name" : "HDP",
+        "stack_version" : "2.0"
+      }
+    },
+    {
+      "href" : "http://localhost:49233/api/v1/blueprints/single-node-hdfs-yarn",
+      "Blueprints" : {
+        "blueprint_name" : "single-node-hdfs-yarn",
+        "stack_name" : "HDP",
+        "stack_version" : "2.0"
+      }
+    }
+  ]
+}

+ 16 - 0
ambari-client/groovy-client/src/test/resources/cluster.json

@@ -0,0 +1,16 @@
+{
+    "blueprint": "c1",
+    "host-groups": [
+        {
+            "name": "host_group_1",
+            "hosts": [
+                {
+                    "fqdn": "server.ambari.com"
+                },
+                {
+                    "fqdn": "server2.ambari.com"
+                }
+            ]
+        }
+    ]
+}

+ 63 - 0
ambari-client/groovy-client/src/test/resources/hdfsServiceComponents.json

@@ -0,0 +1,63 @@
+{
+  "href" : "http://localhost:49271/api/v1/clusters/single-node-hdfs-yarn/services/HDFS/components?fields=ServiceComponentInfo/*",
+  "items" : [
+    {
+      "href" : "http://localhost:49271/api/v1/clusters/single-node-hdfs-yarn/services/HDFS/components/DATANODE",
+      "ServiceComponentInfo" : {
+        "category" : "SLAVE",
+        "cluster_name" : "single-node-hdfs-yarn",
+        "component_name" : "DATANODE",
+        "service_name" : "HDFS",
+        "state" : "STARTED"
+      }
+    },
+    {
+      "href" : "http://localhost:49271/api/v1/clusters/single-node-hdfs-yarn/services/HDFS/components/HDFS_CLIENT",
+      "ServiceComponentInfo" : {
+        "category" : "CLIENT",
+        "cluster_name" : "single-node-hdfs-yarn",
+        "component_name" : "HDFS_CLIENT",
+        "service_name" : "HDFS",
+        "state" : "INSTALLED"
+      }
+    },
+    {
+      "href" : "http://localhost:49271/api/v1/clusters/single-node-hdfs-yarn/services/HDFS/components/NAMENODE",
+      "ServiceComponentInfo" : {
+        "CapacityRemaining" : 31924920320,
+        "CapacityTotal" : 39078707200,
+        "CapacityUsed" : 24576,
+        "DeadNodes" : "{}",
+        "DecomNodes" : "{}",
+        "HeapMemoryMax" : 1052770304,
+        "HeapMemoryUsed" : 100962448,
+        "LiveNodes" : "{\"server.ambari.com\":{\"infoAddr\":\"172.17.0.2:50075\",\"infoSecureAddr\":\"172.17.0.2:0\",\"xferaddr\":\"172.17.0.2:50010\",\"lastContact\":0,\"usedSpace\":24576,\"adminState\":\"In Service\",\"nonDfsUsedSpace\":7153762304,\"capacity\":39078707200,\"numBlocks\":0,\"version\":\"2.4.0.2.1.1.0-385\",\"used\":24576,\"remaining\":31924920320,\"blockScheduled\":0,\"blockPoolUsed\":24576,\"blockPoolUsedPercent\":6.2888466E-5,\"volfails\":0}}",
+        "NonDfsUsedSpace" : 7153762304,
+        "NonHeapMemoryMax" : 136314880,
+        "NonHeapMemoryUsed" : 33860904,
+        "PercentRemaining" : 81.6939,
+        "PercentUsed" : 6.2888466E-5,
+        "Safemode" : "",
+        "StartTime" : 1400572832732,
+        "TotalFiles" : 10,
+        "UpgradeFinalized" : true,
+        "Version" : "2.4.0.2.1.1.0-385, r68ceccf06a4441273e81a5ec856d41fc7e11c792",
+        "category" : "MASTER",
+        "cluster_name" : "single-node-hdfs-yarn",
+        "component_name" : "NAMENODE",
+        "service_name" : "HDFS",
+        "state" : "STARTED"
+      }
+    },
+    {
+      "href" : "http://localhost:49271/api/v1/clusters/single-node-hdfs-yarn/services/HDFS/components/SECONDARY_NAMENODE",
+      "ServiceComponentInfo" : {
+        "category" : "MASTER",
+        "cluster_name" : "single-node-hdfs-yarn",
+        "component_name" : "SECONDARY_NAMENODE",
+        "service_name" : "HDFS",
+        "state" : "STARTED"
+      }
+    }
+  ]
+}

+ 14 - 0
ambari-client/groovy-client/src/test/resources/services.json

@@ -0,0 +1,14 @@
+{
+  "href" : "http://localhost:49271/api/v1/clusters/single-node-hdfs-yarn/services?fields=ServiceInfo/*",
+  "items" : [
+    {
+      "href" : "http://localhost:49271/api/v1/clusters/single-node-hdfs-yarn/services/HDFS",
+      "ServiceInfo" : {
+        "cluster_name" : "single-node-hdfs-yarn",
+        "maintenance_state" : "OFF",
+        "service_name" : "HDFS",
+        "state" : "STARTED"
+      }
+    }
+  ]
+}

文件差異過大導致無法顯示
+ 24 - 0
ambari-client/groovy-client/src/test/resources/tasks.json


+ 8 - 143
ambari-client/pom.xml

@@ -1,5 +1,6 @@
 <?xml version="1.0"?>
 <?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">
+<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
    Licensed to the Apache Software Foundation (ASF) under one or more
    contributor license agreements.  See the NOTICE file distributed with
    contributor license agreements.  See the NOTICE file distributed with
@@ -28,31 +29,19 @@
   <packaging>pom</packaging>
   <packaging>pom</packaging>
   <version>1.3.0-SNAPSHOT</version>
   <version>1.3.0-SNAPSHOT</version>
   <name>Ambari Client</name>
   <name>Ambari Client</name>
-  <description>Ambari Python client</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-client</package.log.dir>
-    <package.pid.dir>/var/run/ambari-client</package.pid.dir>
-    <skipTests>false</skipTests>
-    <install.dir>/usr/lib/python2.6/site-packages/ambari_client</install.dir>
-    <lib.dir>/usr/lib/ambari-client/lib</lib.dir>
-    <python.ver>python &gt;= 2.6</python.ver>
-  </properties>
+  <description>Ambari client</description>
+  <modules>
+    <module>python-client</module>
+    <module>groovy-client</module>
+  </modules>
   <build>
   <build>
     <plugins>
     <plugins>
-      <plugin>
-        <artifactId>maven-compiler-plugin</artifactId>
-        <version>3.0</version>
-      </plugin>
       <plugin>
       <plugin>
         <artifactId>maven-assembly-plugin</artifactId>
         <artifactId>maven-assembly-plugin</artifactId>
         <configuration>
         <configuration>
           <tarLongFileMode>gnu</tarLongFileMode>
           <tarLongFileMode>gnu</tarLongFileMode>
           <descriptors>
           <descriptors>
-            <descriptor>src/packages/tarball/all.xml</descriptor>
+            <descriptor>assemblies/client.xml</descriptor>
           </descriptors>
           </descriptors>
         </configuration>
         </configuration>
         <executions>
         <executions>
@@ -65,130 +54,6 @@
           </execution>
           </execution>
         </executions>
         </executions>
       </plugin>
       </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}/src/main/python/ambari_client:${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-client-${project.version}</workingDirectory>
-              <arguments>
-                <argument>${project.basedir}/src/main/python/setup.py</argument>
-                <argument>clean</argument>
-                <argument>bdist_dumb</argument>
-              </arguments>
-              <environmentVariables>
-                <PYTHONPATH>target/ambari-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.1-alpha-2</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>${install.dir}</directory>
-              <sources>
-                <source>
-                  <location>${project.build.directory}/${project.artifactId}-${project.version}/ambari_client</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>**/*.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>
-           <skip>true</skip>
-           <submodules>false</submodules>
-         </configuration>
-      </plugin>
     </plugins>
     </plugins>
-    <extensions>
-      <extension>
-        <groupId>org.apache.maven.wagon</groupId>
-        <artifactId>wagon-ssh-external</artifactId>
-      </extension>
-    </extensions>
   </build>
   </build>
 </project>
 </project>

+ 195 - 0
ambari-client/python-client/pom.xml

@@ -0,0 +1,195 @@
+<?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-client</artifactId>
+    <version>1.3.0-SNAPSHOT</version>
+    <relativePath>../../ambari-client</relativePath>
+  </parent>
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>org.apache.ambari</groupId>
+  <artifactId>python-client</artifactId>
+  <packaging>pom</packaging>
+  <version>1.3.0-SNAPSHOT</version>
+  <name>Ambari Python Client</name>
+  <description>Ambari Python client</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/python-client</package.log.dir>
+    <package.pid.dir>/var/run/python-client</package.pid.dir>
+    <skipTests>false</skipTests>
+    <install.dir>/usr/lib/python2.6/site-packages/ambari_client</install.dir>
+    <lib.dir>/usr/lib/ambari-client/lib</lib.dir>
+    <python.ver>python &gt;= 2.6</python.ver>
+  </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>python2.6</executable>
+              <workingDirectory>src/test/python</workingDirectory>
+              <arguments>
+                <argument>unitTests.py</argument>
+              </arguments>
+              <environmentVariables>
+                <PYTHONPATH>${project.basedir}/../../ambari-common/src/test/python:${project.basedir}/src/main/python/ambari_client:${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>python2.6</executable>
+              <workingDirectory>target/python-client-${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.1-alpha-2</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>${install.dir}</directory>
+              <sources>
+                <source>
+                  <location>${project.build.directory}/${project.artifactId}-${project.version}/ambari_client</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>
+          <skip>true</skip>
+          <submodules>false</submodules>
+        </configuration>
+      </plugin>
+    </plugins>
+    <extensions>
+      <extension>
+        <groupId>org.apache.maven.wagon</groupId>
+        <artifactId>wagon-ssh-external</artifactId>
+      </extension>
+    </extensions>
+  </build>
+</project>

+ 0 - 0
ambari-client/src/examples/create.py → ambari-client/python-client/src/examples/create.py


+ 0 - 0
ambari-client/src/examples/delete.py → ambari-client/python-client/src/examples/delete.py


+ 0 - 0
ambari-client/src/examples/example.py → ambari-client/python-client/src/examples/example.py


+ 0 - 0
ambari-client/src/examples/main.py → ambari-client/python-client/src/examples/main.py


+ 0 - 0
ambari-client/src/examples/trial.py → ambari-client/python-client/src/examples/trial.py


+ 0 - 0
ambari-client/src/main/python/ambari_client/__init__.py → ambari-client/python-client/src/main/python/ambari_client/__init__.py


+ 0 - 0
ambari-client/src/main/python/ambari_client/ambari_api.py → ambari-client/python-client/src/main/python/ambari_client/ambari_api.py


+ 0 - 0
ambari-client/src/main/python/ambari_client/core/__init__.py → ambari-client/python-client/src/main/python/ambari_client/core/__init__.py


+ 0 - 0
ambari-client/src/main/python/ambari_client/core/coreutils.py → ambari-client/python-client/src/main/python/ambari_client/core/coreutils.py


+ 0 - 0
ambari-client/src/main/python/ambari_client/core/errors.py → ambari-client/python-client/src/main/python/ambari_client/core/errors.py


+ 0 - 0
ambari-client/src/main/python/ambari_client/core/http_client.py → ambari-client/python-client/src/main/python/ambari_client/core/http_client.py


+ 0 - 0
ambari-client/src/main/python/ambari_client/core/http_utils.py → ambari-client/python-client/src/main/python/ambari_client/core/http_utils.py


+ 0 - 0
ambari-client/src/main/python/ambari_client/core/rest_resource.py → ambari-client/python-client/src/main/python/ambari_client/core/rest_resource.py


+ 0 - 0
ambari-client/src/main/python/ambari_client/model/__init__.py → ambari-client/python-client/src/main/python/ambari_client/model/__init__.py


+ 0 - 0
ambari-client/src/main/python/ambari_client/model/base_model.py → ambari-client/python-client/src/main/python/ambari_client/model/base_model.py


+ 0 - 0
ambari-client/src/main/python/ambari_client/model/cluster.py → ambari-client/python-client/src/main/python/ambari_client/model/cluster.py


+ 0 - 0
ambari-client/src/main/python/ambari_client/model/component.py → ambari-client/python-client/src/main/python/ambari_client/model/component.py


+ 0 - 0
ambari-client/src/main/python/ambari_client/model/configuration.py → ambari-client/python-client/src/main/python/ambari_client/model/configuration.py


+ 0 - 0
ambari-client/src/main/python/ambari_client/model/host.py → ambari-client/python-client/src/main/python/ambari_client/model/host.py


+ 0 - 0
ambari-client/src/main/python/ambari_client/model/paths.py → ambari-client/python-client/src/main/python/ambari_client/model/paths.py


+ 0 - 0
ambari-client/src/main/python/ambari_client/model/service.py → ambari-client/python-client/src/main/python/ambari_client/model/service.py


+ 0 - 0
ambari-client/src/main/python/ambari_client/model/stack.py → ambari-client/python-client/src/main/python/ambari_client/model/stack.py


+ 0 - 0
ambari-client/src/main/python/ambari_client/model/status.py → ambari-client/python-client/src/main/python/ambari_client/model/status.py


+ 0 - 0
ambari-client/src/main/python/ambari_client/model/utils.py → ambari-client/python-client/src/main/python/ambari_client/model/utils.py


+ 0 - 0
ambari-client/src/main/python/ambari_client/resources/__init__.py → ambari-client/python-client/src/main/python/ambari_client/resources/__init__.py


+ 0 - 0
ambari-client/src/main/python/ambari_client/resources/clusters.py → ambari-client/python-client/src/main/python/ambari_client/resources/clusters.py


+ 0 - 0
ambari-client/src/main/python/ambari_client/resources/hosts.py → ambari-client/python-client/src/main/python/ambari_client/resources/hosts.py


+ 0 - 0
ambari-client/src/main/python/ambari_client/resources/stacks.py → ambari-client/python-client/src/main/python/ambari_client/resources/stacks.py


+ 0 - 0
ambari-client/src/main/python/setup.py → ambari-client/python-client/src/main/python/setup.py


+ 0 - 0
ambari-client/src/packages/tarball/all.xml → ambari-client/python-client/src/packages/tarball/all.xml


+ 0 - 0
ambari-client/src/test/python/TestAmbariClient.py → ambari-client/python-client/src/test/python/TestAmbariClient.py


+ 0 - 0
ambari-client/src/test/python/TestClusterModel.py → ambari-client/python-client/src/test/python/TestClusterModel.py


+ 0 - 0
ambari-client/src/test/python/TestComponentModel.py → ambari-client/python-client/src/test/python/TestComponentModel.py


+ 0 - 0
ambari-client/src/test/python/TestHostModel.py → ambari-client/python-client/src/test/python/TestHostModel.py


+ 0 - 0
ambari-client/src/test/python/TestServiceModel.py → ambari-client/python-client/src/test/python/TestServiceModel.py


+ 0 - 0
ambari-client/src/test/python/TestStatusModel.py → ambari-client/python-client/src/test/python/TestStatusModel.py


+ 0 - 0
ambari-client/src/test/python/json/ambariclient_bootstrap_hosts.json → ambari-client/python-client/src/test/python/json/ambariclient_bootstrap_hosts.json


+ 0 - 0
ambari-client/src/test/python/json/ambariclient_get_all_clusters.json → ambari-client/python-client/src/test/python/json/ambariclient_get_all_clusters.json


+ 0 - 0
ambari-client/src/test/python/json/ambariclient_get_all_hosts.json → ambari-client/python-client/src/test/python/json/ambariclient_get_all_hosts.json


+ 0 - 0
ambari-client/src/test/python/json/ambariclient_get_components.json → ambari-client/python-client/src/test/python/json/ambariclient_get_components.json


+ 0 - 0
ambari-client/src/test/python/json/ambariclient_get_config.json → ambari-client/python-client/src/test/python/json/ambariclient_get_config.json


+ 0 - 0
ambari-client/src/test/python/json/ambariclient_get_host.json → ambari-client/python-client/src/test/python/json/ambariclient_get_host.json


+ 0 - 0
ambari-client/src/test/python/json/clustermodel_error_deleting_host.json → ambari-client/python-client/src/test/python/json/clustermodel_error_deleting_host.json


+ 0 - 0
ambari-client/src/test/python/json/clustermodel_get_all_hosts.json → ambari-client/python-client/src/test/python/json/clustermodel_get_all_hosts.json


+ 0 - 0
ambari-client/src/test/python/json/clustermodel_get_all_services.json → ambari-client/python-client/src/test/python/json/clustermodel_get_all_services.json


+ 0 - 0
ambari-client/src/test/python/json/clustermodel_get_cluster.json → ambari-client/python-client/src/test/python/json/clustermodel_get_cluster.json


+ 0 - 0
ambari-client/src/test/python/json/clustermodel_get_core_site_config.json → ambari-client/python-client/src/test/python/json/clustermodel_get_core_site_config.json


+ 0 - 0
ambari-client/src/test/python/json/clustermodel_get_global_config.json → ambari-client/python-client/src/test/python/json/clustermodel_get_global_config.json


+ 0 - 0
ambari-client/src/test/python/json/clustermodel_get_hdfs_site_config.json → ambari-client/python-client/src/test/python/json/clustermodel_get_hdfs_site_config.json


+ 0 - 0
ambari-client/src/test/python/json/clustermodel_get_host.json → ambari-client/python-client/src/test/python/json/clustermodel_get_host.json


+ 0 - 0
ambari-client/src/test/python/json/clustermodel_get_mapred_site_config.json → ambari-client/python-client/src/test/python/json/clustermodel_get_mapred_site_config.json


+ 0 - 0
ambari-client/src/test/python/json/clustermodel_get_service.json → ambari-client/python-client/src/test/python/json/clustermodel_get_service.json


+ 0 - 0
ambari-client/src/test/python/json/componentmodel_get_metrics.json → ambari-client/python-client/src/test/python/json/componentmodel_get_metrics.json


+ 0 - 0
ambari-client/src/test/python/json/get_cluster_service.json → ambari-client/python-client/src/test/python/json/get_cluster_service.json


+ 0 - 0
ambari-client/src/test/python/json/get_components_from_stack.json → ambari-client/python-client/src/test/python/json/get_components_from_stack.json


+ 0 - 0
ambari-client/src/test/python/json/get_host_component.json → ambari-client/python-client/src/test/python/json/get_host_component.json


+ 0 - 0
ambari-client/src/test/python/json/get_host_components.json → ambari-client/python-client/src/test/python/json/get_host_components.json


+ 0 - 0
ambari-client/src/test/python/json/get_service_component.json → ambari-client/python-client/src/test/python/json/get_service_component.json


+ 0 - 0
ambari-client/src/test/python/json/get_service_components.json → ambari-client/python-client/src/test/python/json/get_service_components.json


+ 0 - 0
ambari-client/src/test/python/json/hostmodel_get_host_component.json → ambari-client/python-client/src/test/python/json/hostmodel_get_host_component.json


+ 0 - 0
ambari-client/src/test/python/json/hostmodel_get_host_components.json → ambari-client/python-client/src/test/python/json/hostmodel_get_host_components.json


+ 0 - 0
ambari-client/src/test/python/json/servicemodel_get_component.json → ambari-client/python-client/src/test/python/json/servicemodel_get_component.json


+ 0 - 0
ambari-client/src/test/python/json/servicemodel_get_components.json → ambari-client/python-client/src/test/python/json/servicemodel_get_components.json


+ 0 - 0
ambari-client/src/test/python/json/status_error_with_message.json → ambari-client/python-client/src/test/python/json/status_error_with_message.json


+ 0 - 0
ambari-client/src/test/python/json/status_ok_with_id.json → ambari-client/python-client/src/test/python/json/status_ok_with_id.json


+ 0 - 0
ambari-client/src/test/python/unitTests.py → ambari-client/python-client/src/test/python/unitTests.py


+ 0 - 0
ambari-client/src/test/python/utils/HttpClientInvoker.py → ambari-client/python-client/src/test/python/utils/HttpClientInvoker.py


+ 58 - 14
ambari-project/pom.xml

@@ -12,7 +12,8 @@
   See the License for the specific language governing permissions and
   See the License for the specific language governing permissions and
   limitations under the License. See accompanying LICENSE file.
   limitations under the License. See accompanying LICENSE file.
 -->
 -->
-<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">
+<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">
   <modelVersion>4.0.0</modelVersion>
   <modelVersion>4.0.0</modelVersion>
   <parent>
   <parent>
     <groupId>org.apache.ambari</groupId>
     <groupId>org.apache.ambari</groupId>
@@ -46,6 +47,14 @@
       <id>EclipseLink</id>
       <id>EclipseLink</id>
       <url>http://download.eclipse.org/rt/eclipselink/maven.repo</url>
       <url>http://download.eclipse.org/rt/eclipselink/maven.repo</url>
     </repository>
     </repository>
+    <repository>
+      <id>spring-milestones</id>
+      <name>Spring Milestones</name>
+      <url>http://repo.spring.io/milestone</url>
+      <snapshots>
+        <enabled>false</enabled>
+      </snapshots>
+    </repository>
   </repositories>
   </repositories>
   <profiles>
   <profiles>
     <profile>
     <profile>
@@ -102,7 +111,7 @@
         <artifactId>guice-servlet</artifactId>
         <artifactId>guice-servlet</artifactId>
         <version>3.0</version>
         <version>3.0</version>
       </dependency>
       </dependency>
-       <dependency>
+      <dependency>
         <groupId>org.codehaus.jettison</groupId>
         <groupId>org.codehaus.jettison</groupId>
         <artifactId>jettison</artifactId>
         <artifactId>jettison</artifactId>
         <version>1.1</version>
         <version>1.1</version>
@@ -164,19 +173,19 @@
         <version>1.3.1.RELEASE</version>
         <version>1.3.1.RELEASE</version>
       </dependency>
       </dependency>
       <dependency>
       <dependency>
-      <groupId>org.apache.directory.server</groupId>
-      <artifactId>apacheds-core</artifactId>
-      <version>1.5.5</version>
+        <groupId>org.apache.directory.server</groupId>
+        <artifactId>apacheds-core</artifactId>
+        <version>1.5.5</version>
       </dependency>
       </dependency>
       <dependency>
       <dependency>
-      <groupId>org.apache.directory.server</groupId>
-      <artifactId>apacheds-protocol-ldap</artifactId>
-      <version>1.5.5</version>
+        <groupId>org.apache.directory.server</groupId>
+        <artifactId>apacheds-protocol-ldap</artifactId>
+        <version>1.5.5</version>
       </dependency>
       </dependency>
       <dependency>
       <dependency>
-      <groupId>org.apache.directory.shared</groupId>
-      <artifactId>shared-ldap</artifactId>
-      <version>0.9.17</version>
+        <groupId>org.apache.directory.shared</groupId>
+        <artifactId>shared-ldap</artifactId>
+        <version>0.9.17</version>
       </dependency>
       </dependency>
       <dependency>
       <dependency>
         <groupId>org.slf4j</groupId>
         <groupId>org.slf4j</groupId>
@@ -213,7 +222,7 @@
         <artifactId>mockito-core</artifactId>
         <artifactId>mockito-core</artifactId>
         <version>1.8.5</version>
         <version>1.8.5</version>
       </dependency>
       </dependency>
-       <dependency>
+      <dependency>
         <groupId>org.eclipse.jetty</groupId>
         <groupId>org.eclipse.jetty</groupId>
         <artifactId>jetty-server</artifactId>
         <artifactId>jetty-server</artifactId>
         <version>7.6.7.v20120910</version>
         <version>7.6.7.v20120910</version>
@@ -366,6 +375,41 @@
         <artifactId>junit</artifactId>
         <artifactId>junit</artifactId>
         <version>4.10</version>
         <version>4.10</version>
       </dependency>
       </dependency>
+      <dependency>
+        <groupId>org.springframework.shell</groupId>
+        <artifactId>spring-shell</artifactId>
+        <version>1.1.0.RC3</version>
+      </dependency>
+      <dependency>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-starter</artifactId>
+        <version>1.0.2.RELEASE</version>
+      </dependency>
+      <dependency>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-starter-test</artifactId>
+        <version>1.0.2.RELEASE</version>
+      </dependency>
+      <dependency>
+        <groupId>com.github.lalyos</groupId>
+        <artifactId>jfiglet</artifactId>
+        <version>0.0.3</version>
+      </dependency>
+      <dependency>
+        <groupId>org.codehaus.groovy</groupId>
+        <artifactId>groovy-all</artifactId>
+        <version>2.1.8</version>
+      </dependency>
+      <dependency>
+        <groupId>org.codehaus.groovy.modules.http-builder</groupId>
+        <artifactId>http-builder</artifactId>
+        <version>0.7.1</version>
+      </dependency>
+      <dependency>
+        <groupId>jline</groupId>
+        <artifactId>jline</artifactId>
+        <version>2.11</version>
+      </dependency>
     </dependencies>
     </dependencies>
   </dependencyManagement>
   </dependencyManagement>
   <build>
   <build>
@@ -418,9 +462,9 @@
           </execution>
           </execution>
         </executions>
         </executions>
         <configuration>
         <configuration>
-            <controlDir>${basedir}/src/main/package/deb/control</controlDir>
+          <controlDir>${basedir}/src/main/package/deb/control</controlDir>
         </configuration>
         </configuration>
-        </plugin>
+      </plugin>
     </plugins>
     </plugins>
   </build>
   </build>
 </project>
 </project>

+ 120 - 0
ambari-shell/pom.xml

@@ -0,0 +1,120 @@
+<?xml version="1.0"?>
+<!-- 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.
+  See accompanying LICENSE file. -->
+<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">
+  <parent>
+    <groupId>org.apache.ambari</groupId>
+    <artifactId>ambari-project</artifactId>
+    <version>1.3.0-SNAPSHOT</version>
+    <relativePath>../ambari-project</relativePath>
+  </parent>
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>org.apache.ambari</groupId>
+  <artifactId>ambari-shell</artifactId>
+  <packaging>jar</packaging>
+  <name>Ambari Shell</name>
+  <version>1.3.0-SNAPSHOT</version>
+  <description>Ambari Shell</description>
+  <properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    <start-class>org.apache.ambari.shell.AmbariShell</start-class>
+  </properties>
+  <dependencies>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.springframework.shell</groupId>
+      <artifactId>spring-shell</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.springframework.boot</groupId>
+      <artifactId>spring-boot-starter</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.springframework.boot</groupId>
+      <artifactId>spring-boot-starter-test</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>com.github.lalyos</groupId>
+      <artifactId>jfiglet</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>commons-io</groupId>
+      <artifactId>commons-io</artifactId>
+      <version>2.3</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.ambari</groupId>
+      <artifactId>groovy-client</artifactId>
+      <version>1.3.0-SNAPSHOT</version>
+    </dependency>
+    <dependency>
+      <groupId>org.mockito</groupId>
+      <artifactId>mockito-core</artifactId>
+      <version>1.9.5</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>jline</groupId>
+      <artifactId>jline</artifactId>
+    </dependency>
+  </dependencies>
+  <build>
+    <plugins>
+      <plugin>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <version>3.0</version>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.rat</groupId>
+        <artifactId>apache-rat-plugin</artifactId>
+        <configuration>
+          <excludes>
+            <exclude>src/main/resources/elephant.txt</exclude>
+            <exclude>src/test/resources/2columns</exclude>
+            <exclude>src/test/resources/3columns</exclude>
+            <exclude>src/test/resources/testBlueprint.json</exclude>
+          </excludes>
+        </configuration>
+        <executions>
+          <execution>
+            <phase>test</phase>
+            <goals>
+              <goal>check</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <artifactId>maven-assembly-plugin</artifactId>
+        <configuration>
+          <skipAssembly>true</skipAssembly>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-maven-plugin</artifactId>
+        <version>1.0.2.RELEASE</version>
+        <executions>
+          <execution>
+            <goals>
+              <goal>repackage</goal>
+            </goals>
+          </execution>
+        </executions>
+        <configuration>
+          <mainClass>${start-class}</mainClass>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+</project>

+ 110 - 0
ambari-shell/src/main/java/org/apache/ambari/shell/AmbariShell.java

@@ -0,0 +1,110 @@
+/**
+ * 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;
+
+import org.apache.ambari.groovy.client.AmbariClient;
+import org.apache.ambari.shell.model.AmbariContext;
+import org.apache.ambari.shell.model.Hints;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.CommandLineRunner;
+import org.springframework.boot.builder.SpringApplicationBuilder;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.shell.CommandLine;
+import org.springframework.shell.core.JLineShellComponent;
+import org.springframework.shell.event.ShellStatus;
+import org.springframework.shell.event.ShellStatusListener;
+
+/**
+ * Shell bootstrap.
+ */
+@Configuration
+@ComponentScan(basePackageClasses = {AmbariShell.class})
+public class AmbariShell implements CommandLineRunner, ShellStatusListener {
+
+  @Autowired
+  private CommandLine commandLine;
+  @Autowired
+  private JLineShellComponent shell;
+  @Autowired
+  private AmbariContext context;
+  @Autowired
+  private AmbariClient client;
+
+  @Override
+  public void run(String... arg) throws Exception {
+    String[] shellCommandsToExecute = commandLine.getShellCommandsToExecute();
+    if (shellCommandsToExecute != null) {
+      for (String cmd : shellCommandsToExecute) {
+        if (!shell.executeScriptLine(cmd)) {
+          break;
+        }
+      }
+    } else {
+      shell.addShellStatusListener(this);
+      shell.start();
+      shell.promptLoop();
+      shell.waitForComplete();
+    }
+  }
+
+  @Override
+  public void onShellStatusChange(ShellStatus oldStatus, ShellStatus newStatus) {
+    if (newStatus.getStatus() == ShellStatus.Status.STARTED) {
+      try {
+        String cluster = client.getClusterName();
+        boolean available = client.isBlueprintAvailable();
+        if (cluster == null) {
+          if (available) {
+            context.setHint(Hints.BUILD_CLUSTER);
+          } else {
+            context.setHint(Hints.ADD_BLUEPRINT);
+          }
+        } else {
+          context.setHint(Hints.PROGRESS);
+        }
+        context.setCluster(cluster);
+        context.setBlueprintsAvailable(available);
+      } catch (Exception e) {
+        System.out.println(e.getMessage());
+        shell.executeCommand("quit");
+      }
+    }
+  }
+
+
+  public static void main(String[] args) {
+    if (args.length == 0) {
+      System.out.println(
+        "\nAmbari Shell: Interactive command line tool for managing Apache Ambari.\n\n" +
+          "Usage:\n" +
+          "  java -jar ambari-shell.jar                  : Starts Ambari Shell in interactive mode.\n" +
+          "  java -jar ambari-shell.jar --cmdfile=<FILE> : Ambari Shell executes commands read from the file.\n\n" +
+          "Options:\n" +
+          "  --ambari.host=<HOSTNAME>       Hostname of the Ambari Server [default: localhost].\n" +
+          "  --ambari.port=<PORT>           Port of the Ambari Server [default: 8080].\n" +
+          "  --ambari.user=<USER>           Username of the Ambari admin [default: admin].\n" +
+          "  --ambari.password=<PASSWORD>   Password of the Ambari admin [default: admin].\n\n" +
+          "Note:\n" +
+          "  At least one option is mandatory."
+      );
+      System.exit(1);
+    }
+    new SpringApplicationBuilder(AmbariShell.class).showBanner(false).run(args);
+  }
+}

+ 173 - 0
ambari-shell/src/main/java/org/apache/ambari/shell/commands/BasicCommands.java

@@ -0,0 +1,173 @@
+/**
+ * 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.apache.ambari.shell.support.TableRenderer.renderMapValueMap;
+import static org.apache.ambari.shell.support.TableRenderer.renderSingleMap;
+
+import org.apache.ambari.groovy.client.AmbariClient;
+import org.apache.ambari.shell.model.AmbariContext;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.shell.core.CommandMarker;
+import org.springframework.shell.core.annotation.CliAvailabilityIndicator;
+import org.springframework.shell.core.annotation.CliCommand;
+import org.springframework.shell.core.annotation.CliOption;
+import org.springframework.stereotype.Component;
+
+/**
+ * Basic commands used in the shell. Delegating the commands
+ * to the Ambari Server via a Groovy based client.
+ *
+ * @see org.apache.ambari.groovy.client.AmbariClient
+ */
+@Component
+public class BasicCommands implements CommandMarker {
+
+  private AmbariClient client;
+  private AmbariContext context;
+
+  @Autowired
+  public BasicCommands(AmbariClient client, AmbariContext context) {
+    this.client = client;
+    this.context = context;
+  }
+
+  /**
+   * Checks whether the tasks command is available or not.
+   *
+   * @return true if its available false otherwise
+   */
+  @CliAvailabilityIndicator("tasks")
+  public boolean isTasksCommandAvailable() {
+    return context.isConnectedToCluster();
+  }
+
+  /**
+   * Prints the tasks of the Ambari Server.
+   *
+   * @param id id of the request
+   * @return task list
+   */
+  @CliCommand(value = "tasks", help = "Lists the Ambari tasks")
+  public String tasks(
+    @CliOption(key = "id", mandatory = false, help = "Id of the request; default is: 1", unspecifiedDefaultValue = "1") String id) {
+    return renderSingleMap(client.getTaskMap(id), "TASK", "STATUS");
+  }
+
+  /**
+   * Checks whether the service list command is available or not.
+   *
+   * @return true if available false otherwise
+   */
+  @CliAvailabilityIndicator("service list")
+  public boolean isServiceListCommandAvailable() {
+    return context.isConnectedToCluster();
+  }
+
+  /**
+   * Prints the available service list of the Ambari Server.
+   *
+   * @return service list
+   */
+  @CliCommand(value = "service list", help = "Lists the available services")
+  public String servicesList() {
+    return renderSingleMap(client.getServicesMap(), "SERVICE", "STATE");
+  }
+
+  /**
+   * Checks whether the service components command is available or not.
+   *
+   * @return true if available false otherwise
+   */
+  @CliAvailabilityIndicator("service components")
+  public boolean isServiceComponentsCommandAvailable() {
+    return context.isConnectedToCluster();
+  }
+
+  /**
+   * Prints the service components of the Ambari Server.
+   *
+   * @return service component list
+   */
+  @CliCommand(value = "service components", help = "Lists all services with their components")
+  public String serviceComponents() {
+    return renderMapValueMap(client.getServiceComponentsMap(), "SERVICE", "COMPONENT", "STATE");
+  }
+
+  /**
+   * Checks whether the debug on command is available or not.
+   *
+   * @return true if available false otherwise
+   */
+  @CliAvailabilityIndicator("debug on")
+  public boolean isDebugOnCommandAvailable() {
+    return !client.isDebugEnabled();
+  }
+
+  /**
+   * Turns the debug on. From now on users will see the URLs of the API calls.
+   *
+   * @return status message
+   */
+  @CliCommand(value = "debug on", help = "Shows the URL of the API calls")
+  public String debugOn() {
+    client.setDebugEnabled(true);
+    return "debug enabled";
+  }
+
+  /**
+   * Checks whether the debug off command is available or not.
+   *
+   * @return true if available false otherwise
+   */
+  @CliAvailabilityIndicator("debug off")
+  public boolean isDebugOffCommandAvailable() {
+    return client.isDebugEnabled();
+  }
+
+  /**
+   * Turns the debug off. URLs are not visible anymore.
+   *
+   * @return status message
+   */
+  @CliCommand(value = "debug off", help = "Stops showing the URL of the API calls")
+  public String debugOff() {
+    client.setDebugEnabled(false);
+    return "debug disabled";
+  }
+
+  /**
+   * Checks whether the hint command is available or not.
+   *
+   * @return true if available false otherwise
+   */
+  @CliAvailabilityIndicator("hint")
+  public boolean isHintCommandAvailable() {
+    return true;
+  }
+
+  /**
+   * Provides some hints what you can do in the current context.
+   *
+   * @return hint message
+   */
+  @CliCommand(value = "hint", help = "Shows some hints")
+  public String hint() {
+    return context.getHint();
+  }
+}

+ 184 - 0
ambari-shell/src/main/java/org/apache/ambari/shell/commands/BlueprintCommands.java

@@ -0,0 +1,184 @@
+/**
+ * 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.apache.ambari.shell.support.TableRenderer.renderMultiValueMap;
+import static org.apache.ambari.shell.support.TableRenderer.renderSingleMap;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.net.URL;
+
+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.springframework.beans.factory.annotation.Autowired;
+import org.springframework.shell.core.CommandMarker;
+import org.springframework.shell.core.annotation.CliAvailabilityIndicator;
+import org.springframework.shell.core.annotation.CliCommand;
+import org.springframework.shell.core.annotation.CliOption;
+import org.springframework.stereotype.Component;
+
+/**
+ * Blueprint related commands used in the shell.
+ *
+ * @see org.apache.ambari.groovy.client.AmbariClient
+ */
+@Component
+public class BlueprintCommands implements CommandMarker {
+
+  private AmbariClient client;
+  private AmbariContext context;
+
+  @Autowired
+  public BlueprintCommands(AmbariClient client, AmbariContext context) {
+    this.client = client;
+    this.context = context;
+  }
+
+  /**
+   * Checks whether the blueprints command is available or not.
+   *
+   * @return true if available false otherwise
+   */
+  @CliAvailabilityIndicator("blueprint list")
+  public boolean isBlueprintListCommandAvailable() {
+    return context.areBlueprintsAvailable();
+  }
+
+  /**
+   * Prints all the blueprints.
+   *
+   * @return list of blueprints
+   */
+  @CliCommand(value = "blueprint list", help = "Lists all known blueprints")
+  public String listBlueprints() {
+    return renderSingleMap(client.getBlueprintsMap(), "BLUEPRINT", "STACK");
+  }
+
+  /**
+   * Checks whether the blueprint show command is available or not.
+   *
+   * @return true if available false otherwise
+   */
+  @CliAvailabilityIndicator(value = "blueprint show")
+  public boolean isBlueprintShowCommandAvailable() {
+    return context.areBlueprintsAvailable();
+  }
+
+  /**
+   * Shows the requested blueprint's details.
+   *
+   * @param id id of the blueprint
+   * @return blueprint as formatted table
+   */
+  @CliCommand(value = "blueprint show", help = "Shows the blueprint by its id")
+  public String showBlueprint(
+    @CliOption(key = "id", mandatory = true, help = "Id of the blueprint") String id) {
+    return renderMultiValueMap(client.getBlueprintMap(id), "HOSTGROUP", "COMPONENT");
+  }
+
+  /**
+   * Checks whether the blueprint add command is available or not.
+   *
+   * @return true if available false otherwise
+   */
+  @CliAvailabilityIndicator(value = "blueprint add")
+  public boolean isBlueprintAddCommandAvailable() {
+    return true;
+  }
+
+  /**
+   * Adds a blueprint to the Ambari server either through an URL or from a file.
+   * If both specified the file takes precedence.
+   *
+   * @param url  -optional, URL containing the blueprint json
+   * @param file - optional, file containing the blueprint json
+   * @return status message
+   */
+  @CliCommand(value = "blueprint add", help = "Add a new blueprint with either --url or --file")
+  public String addBlueprint(
+    @CliOption(key = "url", mandatory = false, help = "URL of the blueprint to download from") String url,
+    @CliOption(key = "file", mandatory = false, help = "File which contains the blueprint") File file) {
+    String message;
+    try {
+      String json = file == null ? readContent(url) : readContent(file);
+      if (json != null) {
+        client.addBlueprint(json);
+        message = "Blueprint added";
+        context.setHint(Hints.BUILD_CLUSTER);
+        context.setBlueprintsAvailable(true);
+      } else {
+        message = "No blueprint specified";
+      }
+    } catch (Exception e) {
+      message = "Cannot add blueprint: " + e.getMessage();
+    }
+    return message;
+  }
+
+  /**
+   * Checks whether the blueprint defaults command is available or not.
+   *
+   * @return true if available false otherwise
+   */
+  @CliAvailabilityIndicator(value = "blueprint defaults")
+  public boolean isBlueprintDefaultsAddCommandAvailable() {
+    return !context.areBlueprintsAvailable();
+  }
+
+  /**
+   * Adds two default blueprints to the Ambari server.
+   *
+   * @return status message
+   */
+  @CliCommand(value = "blueprint defaults", help = "Adds the default blueprints to Ambari")
+  public String addBlueprint() {
+    String message = "Default blueprints added";
+    try {
+      client.addDefaultBlueprints();
+      context.setHint(Hints.BUILD_CLUSTER);
+      context.setBlueprintsAvailable(true);
+    } catch (Exception e) {
+      message = "Failed to add the default blueprints: " + e.getMessage();
+    }
+    return message;
+  }
+
+  private String readContent(File file) {
+    String content = null;
+    try {
+      content = IOUtils.toString(new FileInputStream(file));
+    } catch (IOException e) {
+      // not important
+    }
+    return content;
+  }
+
+  private String readContent(String url) {
+    String content = null;
+    try {
+      content = IOUtils.toString(new URL(url));
+    } catch (IOException e) {
+      // not important
+    }
+    return content;
+  }
+}

+ 260 - 0
ambari-shell/src/main/java/org/apache/ambari/shell/commands/ClusterCommands.java

@@ -0,0 +1,260 @@
+/**
+ * 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.apache.ambari.shell.support.TableRenderer.renderMultiValueMap;
+import static org.apache.ambari.shell.support.TableRenderer.renderSingleMap;
+
+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.model.AmbariContext;
+import org.apache.ambari.shell.model.FocusType;
+import org.apache.ambari.shell.model.Hints;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.shell.core.CommandMarker;
+import org.springframework.shell.core.annotation.CliAvailabilityIndicator;
+import org.springframework.shell.core.annotation.CliCommand;
+import org.springframework.shell.core.annotation.CliOption;
+import org.springframework.stereotype.Component;
+
+import groovyx.net.http.HttpResponseException;
+
+/**
+ * Cluster related commands used in the shell.
+ *
+ * @see org.apache.ambari.groovy.client.AmbariClient
+ */
+@Component
+public class ClusterCommands implements CommandMarker {
+
+  private AmbariClient client;
+  private AmbariContext context;
+  private Map<String, List<String>> hostGroups;
+
+  @Autowired
+  public ClusterCommands(AmbariClient client, AmbariContext context) {
+    this.client = client;
+    this.context = context;
+  }
+
+  /**
+   * Checks whether the cluster build command is available or not.
+   *
+   * @return true if available false otherwise
+   */
+  @CliAvailabilityIndicator("cluster build")
+  public boolean isClusterBuildCommandAvailable() {
+    return !context.isConnectedToCluster() && !context.isFocusOnClusterBuild() && context.areBlueprintsAvailable();
+  }
+
+  /**
+   * Sets the focus on cluster building. Takes a blueprint id, if it does not exists it wont focus.
+   * After focus the users are able to assign hosts to host groups.
+   *
+   * @param id id of the blueprint
+   * @return prints the blueprint as formatted table if exists, otherwise error message
+   */
+  @CliCommand(value = "cluster build", help = "Starts to build a cluster")
+  public String buildCluster(
+    @CliOption(key = "blueprint", mandatory = true, help = "Id of the blueprint, use 'blueprints' command to see the list") String id) {
+    String message;
+    if (client.doesBlueprintExists(id)) {
+      context.setFocus(id, FocusType.CLUSTER_BUILD);
+      context.setHint(Hints.ASSIGN_HOSTS);
+      message = String.format("%s\n%s",
+        renderSingleMap(client.getHostNames(), "HOSTNAME", "STATE"),
+        renderMultiValueMap(client.getBlueprintMap(id), "HOSTGROUP", "COMPONENT"));
+      createNewHostGroups();
+    } else {
+      message = "Not a valid blueprint id";
+    }
+    return message;
+  }
+
+  /**
+   * Checks whether the cluster assign command is available or not.
+   *
+   * @return true if available false otherwise
+   */
+  @CliAvailabilityIndicator("cluster assign")
+  public boolean isAssignCommandAvailable() {
+    return context.isFocusOnClusterBuild();
+  }
+
+  /**
+   * Assign hosts to host groups provided in the blueprint.
+   *
+   * @param host  host to assign
+   * @param group which host group to
+   * @return status message
+   */
+  @CliCommand(value = "cluster assign", help = "Assign host to host group")
+  public String assign(
+    @CliOption(key = "host", mandatory = true, help = "Fully qualified host name") String host,
+    @CliOption(key = "hostGroup", mandatory = true, help = "Host group which to assign the host") String group) {
+    String message;
+    if (client.getHostNames().keySet().contains(host)) {
+      if (addHostToGroup(host, group)) {
+        context.setHint(Hints.CREATE_CLUSTER);
+        message = String.format("%s has been added to %s", host, group);
+      } else {
+        message = String.format("%s is not a valid host group", group);
+      }
+    } else {
+      message = String.format("%s is not a valid hostname", host);
+    }
+    return message;
+  }
+
+  /**
+   * Checks whether the cluster preview command is available or not.
+   *
+   * @return true if available false otherwise
+   */
+  @CliAvailabilityIndicator("cluster preview")
+  public boolean isClusterPreviewCommandAvailable() {
+    return context.isFocusOnClusterBuild() && isHostAssigned();
+  }
+
+  /**
+   * Shows the currently assigned hosts.
+   *
+   * @return formatted host - host group table
+   */
+  @CliCommand(value = "cluster preview", help = "Shows the currently assigned hosts")
+  public String showAssignments() {
+    return renderMultiValueMap(hostGroups, "HOSTGROUP", "HOST");
+  }
+
+  /**
+   * Checks whether the cluster create command is available or not.
+   *
+   * @return true if available false otherwise
+   */
+  @CliAvailabilityIndicator("cluster create")
+  public boolean isCreateClusterCommandAvailable() {
+    return context.isFocusOnClusterBuild() && isHostAssigned();
+  }
+
+  /**
+   * Creates a new cluster based on the provided host - host group associations and the selected blueprint.
+   * If the cluster creation fails, deletes the cluster.
+   *
+   * @return status message
+   */
+  @CliCommand(value = "cluster create", help = "Create a cluster based on current blueprint and assigned hosts")
+  public String createCluster() {
+    String message = "Successfully created the cluster";
+    String blueprint = context.getFocusValue();
+    try {
+      client.createCluster(blueprint, blueprint, hostGroups);
+      context.setCluster(blueprint);
+      context.resetFocus();
+      context.setHint(Hints.PROGRESS);
+    } catch (HttpResponseException e) {
+      createNewHostGroups();
+      message = "Failed to create the cluster: " + e.getMessage();
+      try {
+        deleteCluster(blueprint);
+      } catch (HttpResponseException e1) {
+        message += ". Failed to cleanup cluster creation: " + e1.getMessage();
+      }
+    }
+    return message;
+  }
+
+  /**
+   * Checks whether the cluster delete command is available or not.
+   *
+   * @return true if available false otherwise
+   */
+  @CliAvailabilityIndicator("cluster delete")
+  public boolean isDeleteClusterCommandAvailable() {
+    return context.isConnectedToCluster();
+  }
+
+  /**
+   * Deletes the cluster.
+   *
+   * @return status message
+   */
+  @CliCommand(value = "cluster delete", help = "Delete the cluster")
+  public String deleteCluster() {
+    String message = "Successfully deleted the cluster";
+    try {
+      deleteCluster(context.getCluster());
+    } catch (HttpResponseException e) {
+      message = "Could not delete the cluster: " + e.getMessage();
+    }
+    return message;
+  }
+
+  /**
+   * Checks whether the cluster reset command is available or not.
+   *
+   * @return true if available false otherwise
+   */
+  @CliAvailabilityIndicator(value = "cluster reset")
+  public boolean isClusterResetCommandAvailable() {
+    return context.isFocusOnClusterBuild() && isHostAssigned();
+  }
+
+  @CliCommand(value = "cluster reset", help = "Clears the host - host group assignments")
+  public void reset() {
+    context.setHint(Hints.ASSIGN_HOSTS);
+    createNewHostGroups();
+  }
+
+  private void deleteCluster(String id) throws HttpResponseException {
+    client.deleteCluster(id);
+  }
+
+  private void createNewHostGroups() {
+    Map<String, List<String>> groups = new HashMap<String, List<String>>();
+    for (String hostGroup : client.getHostGroups(context.getFocusValue())) {
+      groups.put(hostGroup, new ArrayList<String>());
+    }
+    this.hostGroups = groups;
+  }
+
+  private boolean addHostToGroup(String host, String group) {
+    boolean result = true;
+    List<String> hosts = hostGroups.get(group);
+    if (hosts == null) {
+      result = false;
+    } else {
+      hosts.add(host);
+    }
+    return result;
+  }
+
+  private boolean isHostAssigned() {
+    boolean result = false;
+    for (String group : hostGroups.keySet()) {
+      if (!hostGroups.get(group).isEmpty()) {
+        result = true;
+        break;
+      }
+    }
+    return result;
+  }
+}

+ 53 - 0
ambari-shell/src/main/java/org/apache/ambari/shell/commands/ElephantCommand.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.commands;
+
+import java.io.IOException;
+
+import org.apache.commons.io.IOUtils;
+import org.springframework.shell.core.CommandMarker;
+import org.springframework.shell.core.annotation.CliAvailabilityIndicator;
+import org.springframework.shell.core.annotation.CliCommand;
+import org.springframework.stereotype.Component;
+
+/**
+ * Draws an elephant to the console.
+ */
+@Component
+public class ElephantCommand implements CommandMarker {
+
+  /**
+   * Checks whether the hello command is available or not.
+   *
+   * @return true if available false otherwise
+   */
+  @CliAvailabilityIndicator("hello")
+  public boolean isCommandAvailable() {
+    return true;
+  }
+
+  /**
+   * Prints an elephant to the console.
+   *
+   * @return elephant
+   */
+  @CliCommand(value = "hello", help = "Prints a simple elephant to the console")
+  public String elephant() throws IOException {
+    return IOUtils.toString(getClass().getResourceAsStream("/elephant.txt"));
+  }
+}

+ 117 - 0
ambari-shell/src/main/java/org/apache/ambari/shell/commands/HostCommands.java

@@ -0,0 +1,117 @@
+/**
+ * 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.apache.ambari.shell.support.TableRenderer.renderSingleMap;
+
+import org.apache.ambari.groovy.client.AmbariClient;
+import org.apache.ambari.shell.model.AmbariContext;
+import org.apache.ambari.shell.model.FocusType;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.shell.core.CommandMarker;
+import org.springframework.shell.core.annotation.CliAvailabilityIndicator;
+import org.springframework.shell.core.annotation.CliCommand;
+import org.springframework.shell.core.annotation.CliOption;
+import org.springframework.stereotype.Component;
+
+/**
+ * Host related commands used in the shell.
+ *
+ * @see org.apache.ambari.groovy.client.AmbariClient
+ */
+@Component
+public class HostCommands implements CommandMarker {
+
+  private AmbariClient client;
+  private AmbariContext context;
+
+  @Autowired
+  public HostCommands(AmbariClient client, AmbariContext context) {
+    this.client = client;
+    this.context = context;
+  }
+
+  /**
+   * Checks whether the host list command is available or not.
+   *
+   * @return true if available false otherwise
+   */
+  @CliAvailabilityIndicator("host list")
+  public boolean isHostsCommandAvailable() {
+    return true;
+  }
+
+  /**
+   * Prints the available hosts of the Ambari Server.
+   *
+   * @return host list
+   */
+  @CliCommand(value = "host list", help = "Lists the available hosts")
+  public String hosts() {
+    return client.showHostList();
+  }
+
+  /**
+   * Checks whether the host focus command is available or not.
+   *
+   * @return true if available false otherwise
+   */
+  @CliAvailabilityIndicator("host focus")
+  public boolean isFocusHostCommandAvailable() {
+    return context.isConnectedToCluster();
+  }
+
+  /**
+   * Sets the focus to the specified host.
+   *
+   * @param host the host to set the focus to
+   * @return status message
+   */
+  @CliCommand(value = "host focus", help = "Sets the useHost to the specified host")
+  public String focusHost(
+    @CliOption(key = "host", mandatory = true, help = "hostname") String host) {
+    String message;
+    if (client.getHostNames().keySet().contains(host)) {
+      context.setFocus(host, FocusType.HOST);
+      message = "Focus set to: " + host;
+    } else {
+      message = host + " is not a valid host name";
+    }
+    return message;
+  }
+
+  /**
+   * Checks whether the host components command is available or not.
+   *
+   * @return true if available false otherwise
+   */
+  @CliAvailabilityIndicator("host components")
+  public boolean isHostComponentsCommandAvailable() {
+    return context.isFocusOnHost();
+  }
+
+  /**
+   * Prints the components which belongs to the host being focused on.
+   *
+   * @return list of host components
+   */
+  @CliCommand(value = "host components", help = "Lists the components assigned to the selected host")
+  public String hostComponents() {
+    return renderSingleMap(client.getHostComponentsMap(context.getFocusValue()), "COMPONENT", "STATE");
+  }
+}

+ 125 - 0
ambari-shell/src/main/java/org/apache/ambari/shell/configuration/ConverterConfiguration.java

@@ -0,0 +1,125 @@
+/**
+ * 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.configuration;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.shell.converters.AvailableCommandsConverter;
+import org.springframework.shell.converters.BigDecimalConverter;
+import org.springframework.shell.converters.BigIntegerConverter;
+import org.springframework.shell.converters.BooleanConverter;
+import org.springframework.shell.converters.CharacterConverter;
+import org.springframework.shell.converters.DateConverter;
+import org.springframework.shell.converters.DoubleConverter;
+import org.springframework.shell.converters.EnumConverter;
+import org.springframework.shell.converters.FloatConverter;
+import org.springframework.shell.converters.IntegerConverter;
+import org.springframework.shell.converters.LocaleConverter;
+import org.springframework.shell.converters.LongConverter;
+import org.springframework.shell.converters.ShortConverter;
+import org.springframework.shell.converters.SimpleFileConverter;
+import org.springframework.shell.converters.StaticFieldConverterImpl;
+import org.springframework.shell.converters.StringConverter;
+import org.springframework.shell.core.Converter;
+
+/**
+ * Configures the converters used by the shell.
+ */
+@Configuration
+public class ConverterConfiguration {
+
+  @Bean
+  Converter simpleFileConverter() {
+    return new SimpleFileConverter();
+  }
+
+  @Bean
+  Converter stringConverter() {
+    return new StringConverter();
+  }
+
+  @Bean
+  Converter availableCommandsConverter() {
+    return new AvailableCommandsConverter();
+  }
+
+  @Bean
+  Converter bigDecimalConverter() {
+    return new BigDecimalConverter();
+  }
+
+  @Bean
+  Converter bigIntegerConverter() {
+    return new BigIntegerConverter();
+  }
+
+  @Bean
+  Converter booleanConverter() {
+    return new BooleanConverter();
+  }
+
+  @Bean
+  Converter characterConverter() {
+    return new CharacterConverter();
+  }
+
+  @Bean
+  Converter dateConverter() {
+    return new DateConverter();
+  }
+
+  @Bean
+  Converter doubleConverter() {
+    return new DoubleConverter();
+  }
+
+  @Bean
+  Converter enumConverter() {
+    return new EnumConverter();
+  }
+
+  @Bean
+  Converter floatConverter() {
+    return new FloatConverter();
+  }
+
+  @Bean
+  Converter integerConverter() {
+    return new IntegerConverter();
+  }
+
+  @Bean
+  Converter localeConverter() {
+    return new LocaleConverter();
+  }
+
+  @Bean
+  Converter longConverter() {
+    return new LongConverter();
+  }
+
+  @Bean
+  Converter shortConverter() {
+    return new ShortConverter();
+  }
+
+  @Bean
+  Converter staticFieldConverterImpl() {
+    return new StaticFieldConverterImpl();
+  }
+}

+ 102 - 0
ambari-shell/src/main/java/org/apache/ambari/shell/configuration/ShellConfiguration.java

@@ -0,0 +1,102 @@
+/**
+ * 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.configuration;
+
+import org.apache.ambari.groovy.client.AmbariClient;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
+import org.springframework.shell.CommandLine;
+import org.springframework.shell.SimpleShellCommandLineOptions;
+import org.springframework.shell.commands.ExitCommands;
+import org.springframework.shell.commands.HelpCommands;
+import org.springframework.shell.commands.ScriptCommands;
+import org.springframework.shell.commands.VersionCommands;
+import org.springframework.shell.core.CommandMarker;
+import org.springframework.shell.core.JLineShellComponent;
+import org.springframework.shell.plugin.HistoryFileNameProvider;
+import org.springframework.shell.plugin.support.DefaultHistoryFileNameProvider;
+
+/**
+ * Spring bean definitions.
+ */
+@Configuration
+public class ShellConfiguration {
+
+  @Value("${ambari.host:localhost}")
+  private String host;
+
+  @Value("${ambari.port:8080}")
+  private String port;
+
+  @Value("${ambari.user:admin}")
+  private String user;
+
+  @Value("${ambari.password:admin}")
+  private String password;
+
+  @Value("${cmdfile:}")
+  private String cmdFile;
+
+  @Bean
+  AmbariClient createAmbariClient() {
+    return new AmbariClient(host, port, user, password);
+  }
+
+  @Bean
+  static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
+    return new PropertySourcesPlaceholderConfigurer();
+  }
+
+  @Bean
+  HistoryFileNameProvider defaultHistoryFileNameProvider() {
+    return new DefaultHistoryFileNameProvider();
+  }
+
+  @Bean(name = "shell")
+  JLineShellComponent shell() {
+    return new JLineShellComponent();
+  }
+
+  @Bean
+  CommandLine commandLine() throws Exception {
+    String[] args = cmdFile.length() > 0 ? new String[]{"--cmdfile", cmdFile} : new String[0];
+    return SimpleShellCommandLineOptions.parseCommandLine(args);
+  }
+
+  @Bean
+  CommandMarker exitCommand() {
+    return new ExitCommands();
+  }
+
+  @Bean
+  CommandMarker versionCommands() {
+    return new VersionCommands();
+  }
+
+  @Bean
+  CommandMarker helpCommands() {
+    return new HelpCommands();
+  }
+
+  @Bean
+  CommandMarker scriptCommands() {
+    return new ScriptCommands();
+  }
+}

+ 50 - 0
ambari-shell/src/main/java/org/apache/ambari/shell/customization/AmbariBanner.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.ambari.shell.customization;
+
+import org.springframework.shell.plugin.BannerProvider;
+import org.springframework.stereotype.Component;
+
+import com.github.lalyos.jfiglet.FigletFont;
+
+/**
+ * Prints the banner when the user starts the shell.
+ */
+@Component
+public class AmbariBanner implements BannerProvider {
+
+  @Override
+  public String getProviderName() {
+    return "AmbariShell";
+  }
+
+  @Override
+  public String getBanner() {
+    return FigletFont.convertOneLine("AmbariShell");
+  }
+
+  @Override
+  public String getVersion() {
+    return getClass().getPackage().getImplementationVersion();
+  }
+
+  @Override
+  public String getWelcomeMessage() {
+    return "Welcome to Ambari Shell. For command and param completion press TAB, for assistance type 'hint'.";
+  }
+}

+ 43 - 0
ambari-shell/src/main/java/org/apache/ambari/shell/customization/AmbariPrompt.java

@@ -0,0 +1,43 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ambari.shell.customization;
+
+import org.apache.ambari.shell.model.AmbariContext;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.shell.plugin.PromptProvider;
+import org.springframework.stereotype.Component;
+
+/**
+ * Manages the text of the shell's prompt.
+ */
+@Component
+public class AmbariPrompt implements PromptProvider {
+
+  @Autowired
+  private AmbariContext context;
+
+  @Override
+  public String getProviderName() {
+    return AmbariPrompt.class.getSimpleName();
+  }
+
+  @Override
+  public String getPrompt() {
+    return context.getPrompt();
+  }
+}

+ 159 - 0
ambari-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);
+  }
+}

+ 53 - 0
ambari-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;
+  }
+}

+ 55 - 0
ambari-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;
+  }
+}

+ 59 - 0
ambari-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;
+  }
+}

部分文件因文件數量過多而無法顯示