20 September 2017 Engineering

Generating an Elastic Cloud Enterprise Client

By Greg Marzouka

At Elastic, we use the OpenAPI specification, formerly known as Swagger, for documenting the Elastic Cloud Enterprise REST API. The Elastic Cloud Enterprise (ECE) API allows for creating and managing clusters, performing upgrades and repairs, and other general automation tasks within ECE. See our previous blog post, Exploring the API for Elastic Cloud Enterprise, for a great overview of the API.


What is the OpenAPI specification? From the OpenAPI website:


The OpenAPI Specification (OAS) defines a standard, language-agnostic interface to RESTful APIs which allows both humans and computers to discover and understand the capabilities of the service without access to source code, documentation, or through network traffic inspection.

Adopting the OpenAPI specification allows us to generate our API Reference, providing a concise set of documentation; even more importantly, it empowers users to generate REST clients in the language of their choice. This ability to generate REST clients is incredibly useful for automating and developing your own software layer around Elastic Cloud Enterprise. In fact, we use our OpenAPI specification to generate many of the internal tools we use to manage Elastic Cloud.


In this blog post, we're going to focus on client generation and how you can leverage our specification to generate a client in whatever supported language you desire.


Getting the specification

If you have a running ECE installation, the OpenAPI specification can be retrieved through the API by issuing a GET request to the following endpoint: /api/v1/api-docs/swagger.json on the coordinator host:


$ curl -XGET https://ece-host:12443/api/v1/api-docs/swagger.json


Otherwise, you can download the specification directly from our documentation. Just change the version portion of the URL to the desired version of ECE.


Inspecting the specification JSON a bit, you'll notice that each API endpoint is represented as an object. This object contains:


  • a description of what the endpoint does
  • the query string parameters it accepts
  • the format of the request body
  • the response body and all of the possible returned status codes

For instance, the specification for shutting down an Elasticsearch cluster that’s running in ECE looks like this:


"/clusters/elasticsearch/{cluster_id}/_shutdown": {
     "post": {
       "security": [{
         "basicAuth": []
       }],
       "description": "Shuts down a running cluster and removes all nodes belonging to the cluster. The plan for the cluster is retained. Warning: this will lose all cluster data that is not saved in a snapshot repository.",
       "x-doc": {
         "tag": "Clusters - Elasticsearch - Commands"
       },
       "tags": ["ClustersElasticsearch"],
       "operationId": "shutdown-es-cluster",
       "parameters": [{
         "name": "cluster_id",
         "in": "path",
         "description": "Identifier for the Elasticsearch cluster",
         "type": "string",
         "required": true
       }],
       "summary": "Shut down cluster",
       "responses": {
         "202": {
           "description": "The shutdown command was issued successfully, use the \"GET\" command on the /{cluster_id} resource to monitor progress",
           "schema": {
             "$ref": "#/definitions/EmptyResponse"
           }
         },
         "404": {
           "description": "The cluster specified by {cluster_id} cannot be found (code: 'clusters.cluster_not_found')",
           "schema": {
             "$ref": "#/definitions/BasicFailedReply"
           }
         },
         "449": {
           "description": "When running as an administrator (other than root), sudo is required (code: 'root.needs_sudo')",
           "schema": {
             "$ref": "#/definitions/BasicFailedReply"
           }
         }
       }
     }
   }


Also, note that the Swagger version is available in the JSON definition. At the time of this blog post, we’re on version 2.0 of the specification:


"swagger": "2.0"


Swagger Editor


The Swagger website provides a great online tool called Swagger Editor which parses the specification and produces friendly documentation, which you can navigate through (similar to our API reference).


Now that you have the spec, go ahead and try pasting it into the editor. Hopefully, the benefits of using the OpenAPI specification will become obvious!


Generating a client

Generating a client is actually very straightforward.


The Swagger Editor gives you the ability to generate clients in many different languages with just the click of a button. This is great for experimentation. However, in practice you'll likely want to a use a library-based or command-line driven code generator for automating, configuring, and even customizing the generation of your client.


Introducing swagger-codegen


The official Swagger code generator is Swagger Codegen and is used in this blog post. Depending on your language of choice however, there may be other third-party implementations (go-swagger for example, which is a very popular golang implementation).


Swagger Codegen is entirely Java-based, but it supports generating clients in many different languages. As an example, we're going to generate both a Java and a Python client.


The first step is to obtain Swagger Codegen. Following the installation instructions, there are a few ways to go about doing so, but we'll try to stay as platform-agnostic as possible and will simply download and use the JAR file. You'll need Java >= 7 installed on your system.


Running swagger-codegen

Once you've downloaded the JAR file, you can generate a client with just a few commands.


First let's create a directory that will hold the generated client source code. This is not required, but desirable since by default swagger-codegen will spit out the files within the same directory that the command is ran from.


mkdir java-ece-client

Next, we'll invoke swagger-codegen to actually generate the client, pointing it at our newly created directory:

mkdir java-ece-client
java -jar swagger-codegen-cli-2.2.1.jar generate \
  -i https://ece-host:12443/api/v1/api-docs/swagger.json \
  -l java \
  -o java-ece-client


That's really all there is to it! You've just generated a Java client for Elastic Cloud Enterprise. If you cd into the java-ece-client directory, you'll see all of the supporting source code files complete with unit tests.


Let's take a closer look at the options we specified when we invoked swagger-codegen:


-i:  The location of our Elastic Cloud Enterprise OpenAPI specification. This can be a local file, or you can point it directly to the Swagger endpoint in your running ECE installation.


-l: The language we want to generate the client in, hence "java" to generate a Java client.


-o: The directory in which to place the generated source files.


Generating a client in a different language is as simple as changing the argument to the -l option.  For instance, to generate a Python client instead, just run:


mkdir python-ece-client
java -jar swagger-codegen-cli-2.2.1.jar generate \
  -i https://ece-host:12443/api/v1/api-docs/swagger.json \
  -l python \
  -o python-ece-client

Check out the swagger-codegen GitHub repository for the full list of supported languages.


A few usage examples

Now that we have a generated client, let's take a look at a few examples of how we can use it.


In our previous blog post, Exploring the API for Elastic Cloud Enterprise, we showed how to use the REST API directly (via curl) to retrieve and create Elasticsearch clusters. Let's mimic these examples, but this time using the client. We're going to use the Java client, but these examples should be semantically equivalent to any other language client generated by Swagger Codegen.


Initializing the client


The first step is to initialize an ApiClient instance with the URL and credentials to our Elastic Cloud Enterprise API:


ApiClient client = new ApiClient();
client.setBasePath("https://ece-host:12443/api/v1");
client.addDefaultHeader("Authorization", “Basic “ + Base64.getEncoder().encodeToString("user:pass".getBytes(StandardCharsets.UTF_8)););


The API uses basic authentication, so our credentials here are simply just our ECE username and password, base64-encoded.


Retrieving Elasticsearch clusters

Now that we have an ApiClient instance, we can use the ClustersElasticsearchApi class to start interacting with Elasticsearch clusters.


A curl request for retrieving all clusters in Elastic Cloud Enterprise, complete with all of its parameters, looks like the following:


curl -k -X GET -u user:pass https://ece-host:12443/api/v1/clusters/elasticsearch?from=0&size=10&show_security=false&show_metadata=true&show_plans=false&show_plan_defaults=true&show_system_alerts=0&show_hidden=false


We can execute the same request using the client as follows:


ClustersElasticsearchApi elasticsearchApi = new ClustersElasticsearchApi(client);
try {
   ElasticsearchClustersInfo response = elasticsearchApi.getEsClusters(0, 10, false, true, false, false, 0, false);
   for(ElasticsearchClusterInfo cluster : response.getElasticsearchClusters()) {
       System.out.println(cluster.getClusterName());
   }
} catch(ApiException ex) {
   System.out.println("Oops! " + ex.getMessage());
}


Note the strongly-typed request and response classes. No need to fiddle with JSON strings or maps!


Creating an Elasticsearch cluster


Next, let’s create an Elasticsearch cluster named “My First Cluster”, just as we did in the previous blog. Using curl, this would look like:


curl -k -X POST -u user:pass https://ece-host:12443/api/v1/clusters/elasticsearch -H 'content-type: application/json' -d '{
 "cluster_name": "My First Cluster",
 "plan": {
   "zone_count": 1,
   "cluster_topology": [{
     "memory_per_node": 1024,
     "node_count_per_zone": 1
   }],
   "elasticsearch": {
       "version": "5.5.2"
   }
 }
}'


And now the Java API equivalent:


ElasticsearchClusterTopologyElement topology = new ElasticsearchClusterTopologyElement();
topology.setMemoryPerNode(1024);
topology.setNodeCountPerZone(1);
ElasticsearchConfiguration configuration = new ElasticsearchConfiguration();
configuration.setVersion("5.5.2");
ElasticsearchClusterPlan plan = new ElasticsearchClusterPlan();
plan.setZoneCount(1);
plan.setElasticsearch(configuration);
plan.setClusterTopology(Arrays.asList(topology));
CreateElasticsearchClusterRequest request = new CreateElasticsearchClusterRequest();
request.setClusterName("My First Cluster");
request.setPlan(plan);
try {
   ClusterCrudResponse response = elasticsearchApi.createEsCluster(request, false);
   System.out.println(response.getElasticsearchClusterId());
} catch (ApiException ex) {
   System.out.println("Oops! " + ex.getMessage());
}

Known issues

When generating a client using the official Swagger Codegen distribution, we've encountered several issues with different languages.

For example, for Scala we found the following issues, which we maintain a fork for:

Depending on your language of choice, it is possible that you may run into similar issues. We recommend that you open an issue in the official swagger-codegen repository -- they have been very responsive to pull requests and bug reports from our experience.

Conclusion

To summarize, we covered how to obtain the Elastic Cloud Enterprise OpenAPI specification and how to generate a REST client from it using Swagger Codegen. We also mentioned some issues you may run into with using the official Swagger Codegen distribution. 


Also, depending on the language you wish to generate a client for, there may be other community third-party generators available. Since OpenAPI is just a specification that our API adheres to, you can even write your own generator!