Recently I had to transition a Rails app from Google's old provisioning API to the new-ish Directory API. There were a few bits that I had trouble with, and some of the Stack Overflow threads reflected similar confusion, so here's a writeup in hopes of helping others.
The Directory API is a pretty standard REST setup, so you could just hit it with httparty or faraday or some such HTTP client. But I think most people will want to use the google-api-client gem since that's what Google provides. This gem uses an interesting technique; it doesn't define a bunch of methods like
Group#update and whatnot. Instead it downloads an API definition file and at runtime uses that to generate classes and methods. This means that Google doesn't have to release a new gem version every time they add an API endpoint, which is pretty cool. But it does mean that you can't just
bundle open the gem and see what the method names are.
You don't want to download that API file every time you hit the API, so you'll want to do something to cache it locally. I created a
GoogleAdminDirectoryWrapper class in
lib/ for easier testing and command line usage; here's the part of that class that caches the API definition file:
Then you can jump in a console, run
GoogleAdminDirectoryWrapper.new.dump_directory_to_file, and you've got the file locally. In order to use the API definition you need to register it with an instance of
Google::APIClient, so you'll want something like:
client method is another helper I wrote that returns a properly initialized
Google::APIClient, i.e., one that's been authorized with OAuth using an instance of
Signet::OAuth2::Client. Getting a service (i.e., a web app) to authenticate is a whole discussion in itself. The one hint (and the documentation calls this out, but still) that I'd give is that when authorizing you need to use a
:person entry in the hash where the value is the email of the administrative account that you're "impersonating", that is, the account with which you created the application that's making these calls.
So, back to API usage. When I was writing the code that calls the endpoints it wasn't always clear to me how to actually compose those method invocations. The invocations vary based on the HTTP method and the content of the request; they depend on what's in the URL itself and what's in the request body. It's a sort of mapping technique; for example, here's more or less my function for deleting a group:
Seems pretty straightforward. But adding a member to a group looks a little different since it's a HTTP POST that requires information in the request body. You have to supply an
api_method parameter, a
parameters containing the URL parameters, and a
body_object containing the POST request body:
Here's another example. To fetch all members of a group you're doing a GET and so everything is contained in the URL and in the parameters. So we need
parameters but no
body_object. The method below also takes care of unpacking the relevant (for my purposes) bits from the data structures that the gem returns:
After doing a couple of these a pattern becomes clear and you can just bang them out. I found it really helpful to sit in a console and do
load 'lib/google_admin_directory_wrapper.rb' while I experimented with the method definitions and only after I nailed it down would I write client code within my application.
I thought some of the error handling was a little surprising. For example, the API responds with a permissions error if the group does not exist, so you can do something like this to check for a group's existence:
For testing, I used
mocha to mock out method invocations on
GoogleAdminDirectoryWrapper and supply the expected results. I feel a little bad about not mocking things at a lower level - i.e., using
vcr. I didn't actually change anything, but I do feel bad, so that's got to count for something.
I think the lessons learned are pretty much the usual ones. Get started on API migrations early (which I didn't). Do yourself a favor and put together a rapid development cycle. Get simple things working and build from there. RTFM.