I have a Clojure project that uses the lein-beanstalk Leiningen plugin to deploy my app to Amazon Elastic Beanstalk.
My Elastic Beanstalk application has multiple environments (e.g. development, staging, production, etc.), each with different configuration. The configuration is managed by Elastic Beanstalk environment properties, which are set in the JVM as system properties. lein-beanstalk manages these properties with the following entry in project.clj:
(defproject eb-app "1.0-SNAPSHOT"
:aws {:beanstalk {:environments [{:name "staging"
:env {"com.example.fooLimit" "3"
"com.example.barName" "BAR"}}
{:name "production"
:env {"com.example.fooLimit" "27"
"com.example.barName" "BAR"}}]}})
In this example, only com.example.fooLimit varies between my environments, so I'd like to use a var to hold the shared conf. Since defproject is a macro that quotes everything, I can accomplish what I want with a syntax unquote:
(def cfg {"com.example.barName" "BAR"})
(defproject eb-app "1.0-SNAPSHOT"
:aws {:environments [{:name "staging"
:env ~(merge cfg {"com.example.fooLimit" "3"}})
{:name "production"
:env ~(merge cfg {"com.example.fooLimit" "27"}})}]}})
My question is whether this is common in Clojure projects, or if there is a more idiomatic or industry-standard way (among Clojure programmers) to DRY up project.clj?
Related
I currently have a setup for my Clojure project which allows me to toggle configuration from dev to test or prod by configuring profiles in Leiningen's project map. The section related to the profiles looks like this:
:main ^:skip-aot myproject.core
:target-path "target/%s"
:profiles {:uberjar {:aot :all}
:dev {:env {:clj-env :development
:database-uri "jdbc:postgresql://localhost:5432/db_dev"}}
:test {:env {:clj-env :test
:database-uri "jdbc:postgresql://localhost:5432/db_test"}}
:production {:env {:clj-env :production
:database-uri "jdbc:postgresql://localhost:5432/db"}}})
The issue is I would further like to enhance this and be able to toggle between multiple mains. I have seen in other posts that people usually achieve this by configuring the profiles like here.
What I don't know how to do is how to preserve the configuration I have so that profiles correspond to environments but also further configure it in order to be able to choose the main class by simply adding a parameter to lein run.
I have figured out that one way is obviously to keep having just one main class and add that multiplexing with actual Clojure code, but I was hoping to be able to do it via lein configuration.
After trying multiple options, the only way I have found to do this is via aliases.
First setting the :main option to nil, so the MANIFEST.MF doesn't have any Main.class set, and then simply adding a couple of aliases specifying which main class to run.
:main nil
:target-path "target/%s"
:aliases {"main1" ["run" "-m" "project.main1.core"]
"main2" ["run" "-m" "project.main2.core"]}
I would like to add some configuration to Jetty Adapter and run it with the lein-ring plugin, but i could not find any information.
I can run this configured from the main function by using lein run.
(jet/run-jetty main-handler {:port 8080 :join? false})
But I want to set those configurations in the project.clj so I can use "lein ring server".
The lein-ring documentation suggests you can put a map of options for your ring adapter in the project.clj file, like this:
:ring {:handler hello-world.core/handler
:adapter {:join? false
:port 8080}}
Though you probably do not want to use :join? false I'd think.
I have a Clojure app that I can use both from the command-line, and as a Compojure app.
I did that by putting a ring handler and a main function (clojure.tools.cli) in my leiningen project.clj.
{...
:main my-app.core
:ring {:handler my-app.handler/handler }}
The handler is defined (defroutes handler ...).
Now if I want to run the CLI app, I can run lein uberjar and then java -jar arguments.
And I can also run the Compojure app via lein ring server PORT.
Now, how do I deploy the thing as a Compojure app (and not a CLI app) in a production server ? (Bonus points for explaining how lein ring server works.)
Note : I already use nginx if that can help, and I'm flexible on the container to be used.
Here's kind of the default template I use for new projects. It allows you to do dependency injection into ring apps and run the app from the command line as an uberjar. You can read more here: http://www.sparxeng.com/blog/software/improved-clojure-workflow
; handler.clj
(defn wrap-inject-deps
"Ring middleware that injects the dependencies into each ring request map"
[handler deps]
(fn [req]
(handler (assoc req :deps deps))))
(defn create-handler
"Similar to the default ring 'handler', but with a parameter to let you inject dependencies"
[deps]
(-> (routes
api-routes
web-routes
(route/resources "/"))
(kwp/wrap-keyword-params)
(params/wrap-params)
(json/wrap-json-params)
(json/wrap-json-response)
(wrap-inject-deps deps))) ; this injects dependencies
(defn start-jetty
"Launch the Jetty instance"
[deps]
(let [port (Integer. (or (-> system :env :port) 5000))
handler (create-handler deps)]
(jetty/run-jetty handler {:port port :join? false})))
; system.clj
(defn get-env
"Initialize configuration vars"
[]
{:aws-access-key-id (System/getenv "AWS_ACCESS_KEY_ID")
:aws-secret-access-key (System/getenv "AWS_SECRET_ACCESS_KEY")
:mongo-url (System/getenv "MONGO_URL"))
(defn start
"Launch dependencies such as DB connection and Jetty. Return these as a map, for use in REPL"
[& [env]]
(let [env (or env (get-env))
deps {:env env
:monger (db/init env)}
jetty (handler/start-jetty deps)]
(assoc deps :jetty jetty)))
; program.clj
(defn -main [& [port]]
"App entrypoint"
(let [env (system/get-env) ; "env" is just a map of config variables, that can be hard-coded, read from file, or whatever you want.
env (if port (assoc env :port port) env)]
(system/start env)))
You can then use leiningen profiles if you need to create multiple apps with different entrypoints from your codebase.
lein ring server fires up jetty to serve the web application you've built. It does this by starting the server with the options you specify in :ring. If you specify LEIN_NO_DEV in your environment, the server will run in production mode. Whether jetty is the right fit for your productions needs or not, is up to you to decide.
If you would like to run your web application on a different server, there are multiple options. One of them is running on Tomcat. Take another look at the documentation for lein ring, in particular the section on war files. Basically, you would call lein war or lein uberwar to generate a war file. You can then e.g. drop the war file into the webapps directory of Tomcat.
Another option would be using immutant, which has substantial documentation on installation and running applications.
We are hosting a corporate repository which acts as a proxy to the well-known repositories (e.g. Maven Central and Clojars). I want Leiningen to hit the corporate repository in the first place. Only when the corporate repository fails to deliver the artifact Leiningen should ask the standard repositories. This should be the default behaviour of all my projects. What configuration I have to do?
I have added the corporate repository as a mirror in ~/.lein/profiles.clj:
{:user {:mirrors {"our-repo" {:name "our-repo"
:url "http://our-repo/all/"}}}}
Unfortunately this setting has no impact. Leiningen downloads the artifacts from Maven Central:
PS> lein repl
Retrieving org/clojure/clojure/1.5.1/clojure-1.5.1.pom from central
...
Update
xsc suggests to overwrite the Maven Central repository with a mirror definition which points to the corporate repository. It works. Now instead of going to the external Maven Repository Leiningen retrieves the artifacts from the corporate repository.
S/He also suggests to specify an additional repository definition to install a fallback mechanism.
Unfortunately this does not work so well because Leiningen complains about this setting:
:repositories detected in user-level profiles! [:user]
See https://github.com/technomancy/leiningen/wiki/Repeatability
This warning is very annoying. For this reason I would abstain from this setting. Is there another way to install a fallback mechanism?
Here's what works for me:
{:user {:mirrors {#".+" {:url "http://nexus.example.com:8081/nexus/content/groups/public"}}
:repositories [["snapshots" {:id "NudaySnapshots"
:url "http://nexus.example.com:8081/nexus/content/repositories/snapshots"}]
["releases" {:id "NudayReleases"
:url "http://nexus.example.com:8081/nexus/content/repositories/releases"
:sign-releases false}]]}
:auth {:repository-auth {#"nexus.example.com" {:username "deployment"
:password "foo bar baz"}}}}
This handles both resolving dependencies through my Nexus mirror and publishing artifacts to it with lein deploy.
I get the annoying "Repeatability" warning, but I'm working on getting rid of that.
As far as I can see in Leiningen's example project.clj you have to use the name of the repository to mirror as the key in the :mirrors map. So, try this:
{:mirrors {"central" { ... }}}
This will most likely replace the repository completely, so you might want to add the original again:
{:mirrors {"central" {:url "..." }}
:repositories {"maven" {:url "http://repo1.maven.org/maven2/"}}}
I am using monger to fetch and save some data in MongoDb from my Clojure simple app. I have strong Ruby on Rails background so I am familiar with database settings per environment (development, test, production). I want to have something similar in Clojure. How can I add the environment to my code? I want to do it in Clojure-way, code as data, without any yaml files. I am using Leiningen if it changes something.
You can use Leiningen profiles feature.
In your project.clj define your profiles (most cases you need dev and prod)
:profiles {:dev {:resource-paths ["resource-dev"]}
:prod {:resource-paths ["resource-prod"]}}
Now create 2 directories resource-dev and resource-prod and create config.clj file in both of them which will have define a map to store configuration. Something like:
(ns myapp.config)
(def config {:database "dev"})
Then in your app code you can use below snippet to load the config file (only once) and access the config map:
(use 'clojure.java.io)
(def config (delay (load-file (.getFile (resource "config.clj")))))
(defn get-config []
#(force config))
Now you can use get-config function to access the config map.
Have a look at clj-boilerplate, a sample web app I created.
There's info in the README about how it understands environments out of the box and an example environment file can be seen here - but it looks something like this:
(def config
(let [env (or (System/getenv "ENVIRONMENT") "development")]
((keyword env)
{:development
{:database-url "postgres://lborges:#localhost/clj-boilerplate"}
:test
{:database-url "postgres://lborges:#localhost/clj-boilerplate-test"
:production
{:database-url (System/getenv "DATABASE_URL")}})))
I have since evolved this approach but this should get you started.
Hope this helps.
Have a look at using Confijulate (plug for a personal project!):
https://github.com/bbbates/confijulate
It allows you to define environment specific configuration maps, and specify which one to use via JVM system properties.