Friday, October 28, 2011

Using lein to deploy Clojure ring applications to CloudBees

If you have a Clojure ring application and are using lein, then it is really easy to deploy that application to CloudBees using the lein CloudBees plugin, which is available on clojars.

Signup for CloudBees to create an account.

Download the CloudBees SDK and install. (See later for the case where the CloudBees SDK is not installed.)

Verify that you can execute bees app:list. The first time a bees command is executed it will prompt for your user name and password so that your API key and API secret can be downloaded and cached in the ~/.bees/bees.config properties file. This key and secret will be used to authenticate when using the CloudBees SDK or the lein CloudBees plugin.

Modify the project.clj file to identify the application:
:cloudbees-app-id "<account>/<appname>"
Where account is the name of your account and appname is the name of your application.

Modify the project.clj file to include the following development dependency:
[lein-cloudbees "1.0.1"]
for example:
(defproject mandel"1.0.0-SNAPSHOT"
  :description "A Mandelbrot web app"
  :cloudbees-app-id "sandoz/mandel"
  :dependencies [[org.clojure/clojure "1.3.0"]
                 [org.clojure/clojure-contrib "1.2.0"]
                 [compojure "0.6.4"]]
  :dev-dependencies [[lein-ring "0.4.6"]
                     [lein-cloudbees "1.0.1"]]
  :ring {:handler mandel.core/app})

Verify that lein cloudbees works, you should see something like the following:

$ lein cloudbees
Manage a ring-based application on Cloudbees.
Subtasks available:
list-apps   List the current applications deployed to CloudBees.
deploy      Deploy the ring application to CloudBees.
tail        Tail the runtime log of the deployed application.
restart     Restart the deployed application.
stop        Stop the deployed application.
start       Start the deployed application.
Arguments: ([list-apps deploy tail restart stop start])

Deploy the application:

$ lein cloudbees deploy
Created /Users/sandoz/Projects/clojure/mandel/.project.zip
Deploying app to CloudBees, please wait....
http://mandel.sandoz.cloudbees.net
Applcation deployed.
The deployment creates an uber war and then deploys that war to CloudBees. The deployment process is smart, only changes will be sent and furthermore any jars in WEB-INF/lib will be checked, securely, against a global cache, before sending. So even if the uber war is rather big the actual stuff sent across the wire may be much less than expected, even on the first deployment.

Eh Voila! your application is deployed and the state can be verified:
$ lein cloudbees list-apps
sandoz/mandel  -  active
If you don't have the CloudBees SDK installed then you can reference the API key and secret key in the project.clj, for example:
:cloudbees-api-key ~(.trim (slurp "/Users/sandoz/cloudbees/sandoz.apikey"))
:cloudbees-api-secret ~(.trim (slurp "/Users/sandoz/cloudbees/sandoz.secret"))
Such declarations will take precedence over any API key and secret key declared in the ~/.bees/bees.config properties file.

It's a bad idea to reference the key and secret directly in the project.clj and instead it is better to refer to that information in a file. Slurp them in from a file to a string, then trim to remove any white space or line-feeds (the plugin needs to be modified to trim those values).

6 comments:

  1. Hi

    I want to add your blog to Planet Clojure (if you'll continue to write about Clojure), but I need some label (for example, clojure) that could be used to get data into feed. I see label on this post, but terms aren't comma-separated, so they appear as one label, instead of several separate.
    Please, answer me, if you could provide posts with single label, and I'll add you to Planet. Thank you in advance...

    ReplyDelete
  2. I kept running into the following NPE when run $lein cloudbees deploy, but $lein cloudbees list-apps is fine. help please.

    lein cloudbees deploy
    Exception in thread "main" java.lang.NullPointerException (NO_SOURCE_FILE:0)
    at clojure.lang.Compiler.eval(Compiler.java:5440)
    at clojure.lang.Compiler.eval(Compiler.java:5391)
    at clojure.core$eval.invoke(core.clj:2382)
    at clojure.main$eval_opt.invoke(main.clj:235)
    at clojure.main$initialize.invoke(main.clj:254)
    at clojure.main$script_opt.invoke(main.clj:270)
    at clojure.main$main.doInvoke(main.clj:354)
    at clojure.lang.RestFn.invoke(RestFn.java:482)
    at clojure.lang.Var.invoke(Var.java:381)
    at clojure.lang.AFn.applyToHelper(AFn.java:178)
    at clojure.lang.Var.applyTo(Var.java:482)
    at clojure.main.main(main.java:37)
    Caused by: java.lang.NullPointerException
    at clojure.core$namespace.invoke(core.clj:1252)
    at leiningen.ring.war$compile_servlet.invoke(war.clj:131)
    at leiningen.ring.uberwar$uberwar.invoke(uberwar.clj:42)
    at leiningen.cloudbees$deploy.invoke(cloudbees.clj:35)
    at clojure.lang.AFn.applyToHelper(AFn.java:163)
    at clojure.lang.AFn.applyTo(AFn.java:151)
    at clojure.core$apply.invoke(core.clj:544)
    at leiningen.cloudbees$cloudbees.doInvoke(cloudbees.clj:115)
    at clojure.lang.RestFn.invoke(RestFn.java:425)
    at clojure.lang.Var.invoke(Var.java:369)
    at clojure.lang.AFn.applyToHelper(AFn.java:163)
    at clojure.lang.Var.applyTo(Var.java:482)
    at clojure.core$apply.invoke(core.clj:542)
    at leiningen.core$apply_task.invoke(core.clj:228)
    at leiningen.core$_main.doInvoke(core.clj:294)
    at clojure.lang.RestFn.applyTo(RestFn.java:139)
    at clojure.core$apply.invoke(core.clj:542)
    at leiningen.core$_main.invoke(core.clj:297)
    at user$eval42.invoke(NO_SOURCE_FILE:1)
    at clojure.lang.Compiler.eval(Compiler.java:5424)
    ... 11 more

    ReplyDelete
  3. @Alex: I updated the labels to be "," separated. Thanks for pointing that out. I do plan to write at least one more blog on Clojure, but i cannot promise it will be a regular :-)

    @Isaiah: It appears to be a problem creating the uber war file. What happens if you do "lein ring uberwar" ? The cloudbees plugin does (leiningen.ring.uberwar/uberwar project ".project.zip"). If explicitly creating the uber war works then could you zip up a project that reproduces the problem? as that would help me track down why things are failing.

    ReplyDelete
  4. Ok, I added your blog to Planet, thank you

    ReplyDelete
  5. @paul It turns out that I didn't configure the ring handler for my noir project. Great post and thanks for your help!

    ReplyDelete
  6. @Isaiah Peng

    Can you give a little more detail, or even an example of how you solved the problem to deploy with noir?

    Thanks in advance

    ReplyDelete