gitclojureleiningencompojurehttp-kit

How to embed the current git sha in a URL route in Clojure


Typically I have a URL GET route for all of my server-side applications that returns the current git hash as an easy way to check the exact version of the code running on a given instance. In interpreted languages (e.g. Python, Node.js) this is easy, you just check the output of running the shell command in a subprocess. But I'm distributing my Clojure app to instances by packaging it up using lein uberjar.

So I can get the current git sha programmatically using clojure.java.shell like so:

(defn get-git-sha
  [_req]
  (trim ((sh "/bin/sh" "-c" "git rev-parse HEAD") :out)))

(defroutes server-routes
  (GET "/revision" [] get-git-sha))

(defn serve-http
  [port]
  (http-server/run-server server-routes {:port port}))

But I need a way to embed it in the code during the uberjar process (rather than at runtime when the jar is no longer in the repo) to be returned from the URL route I define using compojure and serve via http-kit. How do I run that function at compile time or build time and dump it to a constant or something else I can then return from the route?

While I'd like a solution along those lines, as stated the end game here is to be able to query a running instance via HTTP and find the exact version of the code running (strongly prefer git sha over e.g. a semver number) on a given instance in production.

I realize I can hack my way around this by cloning the repo to all instances and building the jar locally via e.g. ansible and look up the sha in the known directory but that seems, well, hacky, as well as error prone vs "signing" the jar file so to speak at build time.

EDIT:

My project.clj looks like this:

(defproject gps-server "0.1.0-SNAPSHOT"
  :description "Receives GPS data over TCP"
  :url "http://someurl"
  :license {:name "EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0"
            :url "https://www.eclipse.org/legal/epl-2.0/"}
  :dependencies [...]
  :main ^:skip-aot gps-server.core
  :target-path "target/%s"
  :profiles {:uberjar {:aot [gps-server.core]}})

Solution

  • I don't think I'm doing anything special to make this happen, so check inside your jar for

     META-INF/maven/your-project/name/pom.properties
    

    Where your-project/name comes from the project.clj you use to build your uberjar

    (defproject your-project/name "4.2.6"
      ...
    

    In pom.properties I have:

    #Leiningen
    #Tue Oct 01 13:20:45 CEST 2019
    version=4.2.6
    revision=4625a070a34ddc3c563b71025fa5dd907b406331
    groupId=your-project
    artifactId=name
    

    where the revision is from git.

    I have a /version endpoint that returns this information, making use of this function

    defn- get-version []
      (let [rsrc (io/resource "META-INF/maven/your-project/name/pom.properties")
            rdr (io/reader rsrc)
            props (java.util.Properties.)]
        (.load props rdr)
        (into {} (for [[k v] props] [k v]))))