Extending CloudForge With the CloudForge REST API

CloudForge REST API

Did you know that you can do many CloudForge administrative operations using a REST API? Actions like creating new user accounts and projects, adding services to projects, and even monitoring project activity can be integrated into your process tools using this simple, secure API.

One popular example: suppose you want to integrate your CloudForge accounts with your internal corporate user management, so that when an employee leaves the company, their account in CloudForge can be deleted at the same time as their account in your own systems. Without an API, this requires someone to log into CloudForge and manually delete the user account. But with the CloudForge REST API, you can write a simple script to do the job, or add a few lines to an existing script or automation tool.

Security

The CloudForge REST API uses the same security system as the CloudForge web UI, so you can be sure that no unauthorized changes are made. But you may not want to store passwords inside your tool configurations, so CloudForge also provides a User Authentication Key, to be used in place of the password.

Additionally, a Developer Key is provided, so that as the author of a CloudForge extension, you can disable your extension, if it gets out of your control.

Some Preliminaries

In order to program to the CloudForge REST API, you will need a Developer’s Key. To get this, your account owner or administrator must visit the CloudForge API admin pages, from the Admin menu (these menu items are only visible to someone with the appropriate permissions):

From there, go to the Developer Key page:

Then, click the “Show” link and see your key:

Copy that down somewhere. You’ll need it soon.

While you’re here, you may also want to generate a User Key, for whatever user account will be used by your integration. Just click the “User Key” link, and follow basically the same process as we just did.

The Code

With those preliminaries in place, it’s straight-forward to write code, in your favorite REST-speaking language, to use this API. I’ve written a sample program, available in both Ruby and Unix shell / curl versions.

For this example, I’ve used JSON, but throughout the API you can use either XML or JSON, as you choose. You don’t even have to be consistent from requests to request. Just specify the format in either the URL or the headers (you’ll see examples of each, in a moment).

Here’s a walk-through of the Ruby version.

Configuration

This script defines a bunch of variables we’ll use to construct our messages. If you want to try this script out, you’ll certainly have to change these values.

URL = "http://0.0.0.0:3000"
DEVORG = 'pinafore'
DEVKEY = 'e8a1669a6f0532686b4c732e974aa1f9a62dd69e'
USERORG = 'pinafore'
USERLOGIN = 'ralph'
USERPASS = 'password'
DELETEME = 'dauntless'

WARNING: This sample script deletes a user account, if you give it proper credentials with the power to do that. Use with caution!

Log In

In order to do anything with the API, both you and the author of the program must log in. In simple cases, of course, these might be the same person (and that’s true with this sample program), but more generally you might want to use extensions built by someone else, or even make your own extensions to share or sell, so we identify the author and the user separately.

There is a login call in the API, but strictly speaking it’s not necessary: you can include credentials with any API request, and any authenticated request will return you a session cookie, to make subsequent requests quicker (verifying the cookie is quicker than verifying credentials). But for this sample, we’ll use login.

The credentials are presented in the body (or “payload”) of the request. The Ruby library I’m using expects them to be presented as a properly formatted string (JSON, in this case, since that’s what I’m using), along with headers that notify the server what format to expect:

result = RestClient.post(URL + "/api/1/login",
                         { :credentials =>
                           { :developerOrganization => DEVORG,
                             :developerKey => DEVKEY,
                             :domain => USERORG,
                             :login => USERLOGIN,
                             :password => USERPASS }}.to_json,
                         :content_type => :json,
                         :accept => :json
                         )

Notice the credentials block, entered as a Ruby Hash then converted to JSON with to_json. This is followed by two headers: :content_type tells the server that your message is in JSON, while :accept tells it you want the reply in that same format.

The result returned is kind of magic. If you turn it into a string, with result.to_s, you get the string-formatted JSON or XML. But you can also access reply headers with result.headers, and reply cookies with result.cookies. In this case, we want the session ID cookie, for future use:

session_cookie = result.cookies["_session_id"]

List Current Users

It’s now a simple matter to request the list of all users within the account (just GET /api/1/users). So let’s take that a step further, and extract from that list the userId of the user we plan to delete:

users = JSON.parse(RestClient.get(URL + "/api/1/users.json",
                                  { :Cookie => "_session_id=#{ session_cookie }" }))
user = users.collect {|u| (u['login'] == DELETEME && u || nil)}.compact.first
unless user
  puts "User #{ DELETEME } not found."
  exit(1)
end
userId = user["userId"]

List the User to Delete

Just to be sure we’re deleting the right user, let’s display the matching record:

response = JSON.parse(RestClient.get(URL + "/api/1/users/#{ userId }.json",
                                     { :Cookie => "_session_id=#{ session_cookie }" }))
puts " => #{ response.inspect }"

Do it!

OK, we’re ready to do the actual deletion:

RestClient.delete(URL + "/api/1/users/#{ userId }.json",
                  { :Cookie => "_session_id=#{ session_cookie }" })

Confirm it

To confirm we’ve deleted the user, we can list it again, as before. This time, of course, we won’t find it. The RestClient library I’m using raises an exception in this case, so we need a block:

begin
  puts "Listing the deleted user (expect No such user)"
  user = JSON.parse(RestClient.get(URL + "/api/1/users/#{ userId }.json",
                                   { :Cookie => "_session_id=#{ session_cookie }" }))
rescue RestClient::ResourceNotFound => e
  puts "No such user"
end

And with that, we’re done!

Complete Program

Here’s  the complete Ruby program:

#!/usr/bin/env ruby
require 'rubygems'
require 'json'
require 'restclient'

# Configure these to suit your situation
URL = "http://0.0.0.0:3000"
DEVORG = 'pinafore'
DEVKEY = 'e8a1669a6f0532686b4c732e974aa1f9a62dd69e'
USERORG = 'pinafore'
USERLOGIN = 'ralph'
USERPASS = 'password'
DELETEME = 'dauntless'

# The rest should just work

# Log in
# POST /api/1/login
puts "Logging in"
result = RestClient.post(URL + "/api/1/login.json",
                         { :credentials =>
                           { :developerOrganization => DEVORG,
                             :developerKey => DEVKEY,
                             :domain => USERORG,
                             :login => USERLOGIN,
                             :password => USERPASS }}.to_json,
                         :content_type => :json,
                         :accept => :json
                         )
session_cookie = result.cookies["_session_id"]

# List users, find intended user
# GET /api/1/users
puts "Listing users"
users = JSON.parse(RestClient.get(URL + "/api/1/users.json",
                                  { :Cookie => "_session_id=#{ session_cookie }" }))
user = users.collect {|u| (u['login'] == DELETEME && u || nil)}.compact.first
unless user
  puts "User #{ DELETEME } not found."
  exit(1)
end
userId = user["userId"]

# List the intended user
# GET /api/1/users/NNNN
puts "Listing the user to be deleted"
response = JSON.parse(RestClient.get(URL + "/api/1/users/#{ userId }.json",
                                     { :Cookie => "_session_id=#{ session_cookie }" }))
puts " => #{ response.inspect }"

# Delete the intended user
# DELETE /api/1/users/NNNNN
puts "Deleting the user"
RestClient.delete(URL + "/api/1/users/#{ userId }.json",
                  { :Cookie => "_session_id=#{ session_cookie }" })

# List the deleted user
# GET /api/1/users/NNNNN
# Expect no such user
begin
  puts "Listing the deleted user (expect No such user)"
  user = JSON.parse(RestClient.get(URL + "/api/1/users/#{ userId }.json",
                                   { :Cookie => "_session_id=#{ session_cookie }" }))
rescue RestClient::ResourceNotFound => e
  puts "No such user"
end

Further References

The complete documentation for the CloudForge API is available on-line.

Tagged with: , , , , ,
Posted in CloudForge

Leave a Reply

Your email address will not be published. Required fields are marked *

*

CAPTCHA Image

*

connect with CollabNet
   Contact Us
Subscribe

Have new blog posts sent directly to your email.

looking for something
conversations

CloudForge: Join #CollabNet for the TeamForge® 8.1 release webinar and learn about its new powerful enterprise #Git features http://t.co/IHfnkoEfGr
Date: 1 September 2015 | 5:00 pm

CloudForge: Join this #CollabNet #webinar and learn how to reduce server loads with #Git replication and improve Git performance http://t.co/pB1DEsWFPh
Date: 31 August 2015 | 6:00 pm

CloudForge: Seamlessly integrate #Git upstream and downstream to tools such as #Jira and #Jenkins on this #CollabNet #webinar http://t.co/pB1DEsWFPh
Date: 28 August 2015 | 5:30 pm