Over a million developers have joined DZone.

Android Client for Chef: Cyllell

· DevOps Zone

The DevOps zone is brought to you in partnership with Sonatype Nexus. The Nexus suite helps scale your DevOps delivery with continuous component intelligence integrated into development tools, including Eclipse, IntelliJ, Jenkins, Bamboo, SonarQube and more. Schedule a demo today

TL;DR; – Download it here.

When you have a server estate the size of DataSift you need a configuration management platform to keep track of what does what, how it is configured and to assist you in rolling out 10 new memcached servers with an identical configuration to the others in production at the drop of a hat. To do that we use Chef from OpsCode.

To interact with Chef you can either use the Web Interface or the CLI client called Knife.

Knife is a powerful command-line interface (CLI) that comes with Chef.
It is used by administrators to interact with the Chef Server API and the local Chef repository. It provides the capability to manipulate nodes, cookbooks, roles, databags, environments, etc., and can also be used to provision cloud resources and to bootstrap systems.

I love CLI’s and prefer them to GUI’s whenever I get the choice but I’m a sucker for writing an Android app if there’s an API available (much like with Rhybudd for Zenoss). So some time ago I set about creating an Android application that could emulate some of the features of Knife from the convenience of your phone.

The biggest hurdle was authenticating with the API as it uses MixLib::Authentication which according to the documentation provides a class-based header signing authentication object. This signed header object is then sent via HTTPS (usually self signed) and has to be within a strict time window.

Looking at the Wiki documentation regarding authenticated API requests it seems quite straight forward but when you discover that versions of Android below 2.2 don’t even have a Base64 encode function you know it’s going to be a bit of a slog.

Making the authenticated headers requires three key steps, first the cryptographic signing of the Headers with your private key;

PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(Base64.decode(this.PrivateKey.getBytes(),0));
KeyFactory kf = KeyFactory.getInstance("RSA");
PrivateKey pk = kf.generatePrivate(spec);
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1PADDING");
cipher.init(Cipher.ENCRYPT_MODE, pk);
byte[] EncryptedStream = new byte[cipher.getOutputSize(dataToSign.length())];
 cipher.doFinal(dataToSign.getBytes(),0,dataToSign.length(), EncryptedStream,0);
catch (ShortBufferException e)
 // TODO Auto-generated catch block
return Base64.encodeToString(EncryptedStream, Base64.NO_WRAP);

Secondly creating a variety of headers with other information;

Headers.add(new BasicNameValuePair("Accept","application/json"));
Headers.add(new BasicNameValuePair("Content-Type","application/json"));
Headers.add(new BasicNameValuePair("X-Ops-Sign","version=1.0"));
Headers.add(new BasicNameValuePair("X-Ops-Userid",this.ClientName));
Headers.add(new BasicNameValuePair("X-Ops-Timestamp",TimeStamp));
Headers.add(new BasicNameValuePair("X-Ops-Content-Hash",Disgesteriser.hash_string(Body)));
signed_canonicalize_request = SignHeaders("Method:GET"+
"\nHashed Path:" + Disgesteriser.hash_string(Path) +

Finally the cyrptographic signature needs to be added to the headers with the proviso that each line can’t be more than 60 characters;

while(rubyLength < 61 && charLocation < signed_canonicalize_request.length())
if(signed_canonicalize_request.charAt(charLocation) != '\n' && signed_canonicalize_request.charAt(charLocation) != '\r')
AuthString += signed_canonicalize_request.charAt(charLocation);
Headers.add(new BasicNameValuePair("X-Ops-Authorization-"+Integer.toString(AuthorizationIteration),AuthString));

With all the authenticating work done getting the information is quite trivial from then on. As described on the OpsCode wiki the API endpoint for a list of cookbooks is simply /cookbooks.

String Path = "/cookbooks";
this.httpget = new HttpGet(this.ChefURL + Path);
List <NameValuePair> Headers = ChefAuth.GetHeaders(Path, "");
for(int i = 0; i < Headers.size(); i++)
String jsonTempString = httpClient.execute(this.httpget, responseHandler);

The App is styled in the same manner as the OpsCode website;

A Read Only functionality Beta version is available on Google Play now;

Android app on Google Play

Any feedback can be sent to @NetworkString, Gareth@NetworksAreMadeOfString.co.uk or by sending ICMP packets padded with your suggestion to 2a01:348:18e:2::2

The DevOps zone is brought to you in partnership with Sonatype Nexus. Use the Nexus Suite to automate your software supply chain and ensure you're using the highest quality open source components at every step of the development lifecycle. Get Nexus today


Published at DZone with permission of Gareth Llewellyn, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

The best of DZone straight to your inbox.

Please provide a valid email address.

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}