Environment variable based runtime configuration with sbt native packager - scala

I'm using the sbt native packager plugin to create a zip file of my application for deployment to elastic beanstalk. I would like to set environment variables in my beanstalk environment and have those be used to configure my application at runtime. I've attempted to reference the env variables in my Procfile like so:
web: ./bin/bridgeservice -Dhttp.port=$PORT
This does not work as $PORT is not interpolated by the start script generated by the packager.
I've also attempted to define the variables in my build.sbt like so:
import scala.util.Properties
javaOptions in Universal ++= Seq(
"-Dhttp.port=" + Properties.envOrElse("PORT", "9004"),
)
This also does not work as the packager expects the PORT env variable at the time of building the distributable zip and hardcodes the default value of 9004 in an application.ini file.
Is it possible to dynamically pass java options based on environment variables at application startup?

The settings in javaOptions in Universal are compiled into conf/application.ini file, but accordingly to sbt-native-packager docs application.ini currently does not support variable substitution:
The file will be installed to ${app_home}/conf/application.ini and
read from there by the startscript. You can use # for comments and new
lines as you like. This file currently doesn’t has any variable
substitution. We recommend using the build.sbt if you need any
information from your build.
So,the env var based runtime settings can be achieved in several ways:
Solution #1. Add extra definitions to generated start script
In build.sbt:
bashScriptExtraDefines += """addJava "-Dhttp.port=${PORT:-9004}""""
Check out Application and runtime configuration documentation for more info.
Solution #2: Set JAVA_OPTS env var in the target server
Just set JAVA_OPTS environment variable on the target server and make it available for the start script. This can be the easiest solution for environments like AWS ElasticBeanstalk where env vars can be set on the app's environment configuration page.

Not sure if this will help, but I had similar issue when building docker images of my multi module project.
I ended with this:
def sysPropOrDefault(propName: String, default: String): String = Option(System.getProperty(propName)).getOrElse(default)
val somePort = sysPropOrDefault("port", "9004")
and in the project definition:
lazy val someProject = project("some-project")
.enablePlugins(JavaServerAppPackaging)
.settings(
javaOptions in Universal ++= Seq(
s"-Dhttp.port=$somePort"
)
)
Adding javaOptions to project settings level was crucial in my case. And s before option is not a typo.
When running command from terminal I called:
sbt clean update -Dport=9005 docker:publishLocal

Related

How to use a separate configuration file for tests in CI, using Play and sbt?

For a project based on Play with sbt, I'd like to have multiple flavors for test runs, using different configuration files. Motivation is being able to run tests either against a local or a remote database.
There's already a custom config file speicified for general test runs (in build.sbt):
javaOptions in Test += "-Dconfig.file=conf/application.test.conf"
Now I would like to have another command where the same tests run against some configuration file conf/application.test-ci.conf.
Approaches tried so far
Adding a command alias
addCommandAlias("test-ci", ";test -Dconfig.file=conf/application.test-ci.conf")
This fails with an error message of a missing semicolon (;), indicating that sbt interprets the resulting command line as multiple commands, but I don't understand why.
Extend Test
lazy val CITest = config("ci") extend Test
lazy val config = (project in file(".")).enablePlugins(PlayScala)
.configs(CITest)
.settings(inConfig(CITest)(Defaults.testTasks): _*)
.settings(
javaOptions in CITest += "-Dconfig.file=conf/application.test-ci.conf"
)
javaOptions in CITest -= "-Dconfig.file=conf/application.test.conf"
I'm don't fully understand what this is doing, but it always seems to pick up the other test configuration file.
How can I specify multiple test setups picking up different configuration files?
Try first applying the setting via set command and then follow up with test task like so
addCommandAlias(
"test-ci",
""";set Test/javaOptions ++= Seq("-Dconfig.file=conf/application.test.con"); test"""
)
Notice how ; separates the set from test.
Another approach is to modify the setting based on the environment. Usually there is some environment variable set on CI like CI or BUILD, so you can modify javaOptions conditionally (without any custom configurations):
Test/javaOptions ++= {
if (sys.env.get("CI").isEmpty) Seq.empty
else Seq("-Dconfig.file=conf/application.test-ci.conf")
}
Note: Test/javaOptions is the new syntax for javaOptions in Test (since sbt 1)

Scala Standalone JAR with a conf Folder

I'm using the sbt assembly jar plugin to create a standalone jar file. My project folder structure would look like this:
MyProject
-src
- main
- scala
- mypackages and source files
- conf // contains application.conf, application.test.conf and so on
- test
-project // contains all the build related files
- README.md
I now want to be able to run the fat jar that I produce against a version of the application.conf that I specify as a System property!
So here is what I do in my unit test!
System.setProperty("environment", "test")
And this is how I load the config in one of the files in my src folder:
val someEnv = Option(System.getProperty("environment", "")).filter(_.nonEmpty) // gives me some(test)
val name = s"application.${someEnv.get}.conf"
I can see that the environment variable is set and I get the environment passed it. But later on I load the application.test.conf as below:
ConfigFactory.load(name).resolve()
It however loads just the edfault application.conf and not the one that I specify!
What is wrong in my case? Where should I put the conf folder? I'm trying to run it against my unit test which is inside the test folder!
I believe you need to specify the full name of the configuration file. The .conf is optional. Try
ConfigFactory.load(s"application.${someEnv.get}").resolve()
The docs for ConfigFactory.load(String) indicate you need to supply
name (optionally without extension) of a resource on classpath
Ok! Here is what I had to do! Change the name of the folder where the config file is located. I originally had it as conf and I had to rename it to resources and bang it worked!

Loading external settings in build.sbt

I'm trying to add an sbt plugin to a play application.
The plugin requires some configuration since it needs to connect to a database. These are the settings that the plugin requires in the build.sbt file:
jooqOptions := Seq("jdbc.driver" -> "com.mysql.jdbc.Driver",
"jdbc.url" -> "jdbc:mysql://localhost:3306/fnord",
"jdbc.user" -> "fnord",
"jdbc.password" -> "fnord",
"generator.database.name" -> "org.jooq.util.mysql.MySQLDatabase",
"generator.database.inputSchema" -> "fnord",
"generator.target.packageName" -> "com.myproject.jooq")
Since the user and password will depent on the specific machine on which i deploy the app, i would like to load them from somewhere where each user can assign the user and password himself.
How do i do that?
I've solved it using the accepted solution from:
How get application version in play framework and build.sbt
I've added this to my build.sbt:
import com.typesafe.config._
val conf = ConfigFactory.parseFile(new File("conf/application.conf")).resolve()
version := conf.getString("app.version")
and in my application.conf:
app.version="0.2-SNAPSHOT"
One way would be to read them from an environment variable, another to have a config file of some kind in a predefined path that you load and read in your sbt project.
Since the sbt config is scala code you can for example use sys.env to read environment variables.
You can find the scaladoc for sys.env here

What are the options to set base packaged directory for a package using sbt-native-packager?

I am trying to build a Debian package using sbt-native-packager as described in this build.sbt.
I set appName using
val appName = "megamgateway"
in project/Build.scala.
All works well. It is just that the contents are stored in /usr/share/megamgateway.
I'd like to have the contents under /usr/share/megam/gateway.
The only way I could find is to use
linuxPackageMapping
as shown here.
Before following along, I'd like to know about other approaches.
You could try to add to your project settings
name := "gateway"
defaultLinuxInstallLocation := "/usr/share/megam/"

Play Framework: Configuring system properties

In play framework (2.2.1 & sbt 0.13) I have an IntegrationSpec that brings up a TestServer. I need to be able to set SSL specific System Properties for the TestServer. So far the only way I have been able to set it up correctly is passing them as command line properties like below
play -Djavax.net.ssl.keyStore=... -Djavax.net.ssl.keyStorePassword=.... -D... test
I want the tests to run simply using play test. For that in Build.scala I configured SBT javaOptions as follows
val main = play.Project(appName, appVersion, appDependencies).settings(
Keys.fork in Test := false,
javaOptions in Test += "-Dconfig.file=conf/application.test.conf")
And in application.test.conf I set all the system properties. With this the TestServer is not even using application.test.conf. I was not able to figure out why. So I thought I will try the following:
play -Dconfig.file=conf/application.test.conf test
The TestServer did use application.test.conf but none of the system properties (javax.net.ssl.keyStore="..." etc.) configured in the file were being used.
So I have two questions
How to have this running only using play test? . (I do not want to pass a long Map of properties to FakeApplication in TestServer).
When I run play -Dconfig.file=conf/application.test.conf test, why are the system properties configured in application.test.conf not being used?
I'm not sure if this works for setting a property for reading the configuration file, but you can set your individual properties with System.setProperty like this:
System.setProperty("application.secret","psssst!")