You have lots of configuration files that needs to be generated based on input. The input is spread over multiple files and changes depending on what environment you’re producing the config files for. You end up using Makefile (good) and bash/python/ruby/insert-here and question your life choices.
You can now use porter and still question your life choices. But at least it’ll be in EDN and Babashka.
-
build-outputtakes [env, src, opts, ctx-paths] as arguments-
env is required can be things like :local, :staging, :production, etc
-
src is the source input in the form of a string or a file
-
opts is a map with optional keys
:dest,:print,:namespaces,:classpaths,:execand:injections -
ctx-paths is the context to be read from files to be put into the src
-
(build-output :test "{{:test-data}}" {} ["build-resources/config.test.edn"])-
:dest - if we wish to send the output to a destination file
-
:print - if we wish to have the output printed to stdout
-
:print-ctx - if we wish to have the context map printed to stdout
-
:print-injections - if we wish to have the injection map printed to stdout
-
:namespaces - for evaling in ctx-paths
-
:classpaths - classpaths to add to the library. Used for the SCI interpreter
-
:injections - a map which will be merged with the context map
-
:exec - do we wish to execute the src as a command in a shell
When the context is created, a recursive lookup (up to 30 degrees) is done, allowing for building up a complete hashmap with all data required for building the output.
It’s possible to manually skip processing by adding ^:skip as a
metadata in front of any element inside the EDN ctx map. Main use is
when you wish to protect arguments sent in to ctx functions from
being processed (as lookup vectors).
{:client/name "client-vip-name"
:image.name/platform "platform"
:version "1.0"
:docker {:production {:platform {:image-namespace [:client/name]
:image-name [:image.name/platform]
:tag [:version]}}}}The above would produce the following output being run through build-output
{:client/name "client-vip-name"
:image.name/platform "platform"
:version "1.0"
:docker {:production {:platform {:image-namespace "client-vip-name"
:image-name "platform"
:tag "1.0"}}}}It’s also possible to use functions in the context files to build the
output. Porter supports the user namespace by default. Anything else
has to be provided.
The user namespace has a ctx var injected into the SCI context and
holds the context map created from ctx-paths.
The ctx var has the env var injected under the :env key and can
be refered to as a key in the map.
;; source code for get-namespace+tag in 'example-ns
(defn get-namespace+tag [ns n tag]
(format "%s.%s:%s" ns n tag))
;; config.test.edn
{:client/name "client-vip-name"
:image.name/platform "platform"
:version "1.0"
:docker {:production {:platform {:image-namespace [:client/name]
:image-name [:image.name/platform]
:tag [:version]}}}
:docker.image/platform+tag (example-ns/get-namespace+tag
[:docker [:env] :platform :image-namespace]
[:docker [:env] :platform :image-name]
[:docker [:env] :platform :tag])}
(build-output :test
"config.test.edn"
{:print true
:classpaths ["classpath-example"]
:namespaces ['example-ns]}
["build-resources/config.test.edn"])
=> {:client/name "client-vip-name"
:image.name/platform "platform"
:version "1.0"
:docker {:production {:platform {:image-namespace "client-vip-name"
:image-name "platform"
:tag "1.0"}}}
:docker.image/platform+tag "client-vip-name.platform:1.0"}When referring paths from ctx in src there are two options available.
-
{{:my.path/here}}- this is equalivent to a Clojure keyword -
{{[:my :path :here]}}- this is equalivent to a Clojure vector
Both options are paths to the context where data can be found.
There is a porter CLI tool which automatically downloads and uses this library via the command line.
curl -o /usr/local/bin/porter https://raw.githubusercontent.com/emil0r/porter/refs/heads/master/porter
chmod +x /usr/local/bin/porter./porter --env :local \
--src "dev-resources/test.tester.yml" \
--ctx-paths "dev-resources/test.tester.edn" \
--print true \
--dest testus.yml \
--injections '{:version "1.0.0"}' \
--classpaths "clj" \
--namespaces testerGives the following output.
-
Adding classpath 'clj'is from the classpath specified -
Requiring testeris a namespace used -
This is from tester 1.0.0is from a function called from thetesternamespace -
Invalid inputgives output of either empty paths or broken paths for the src file-
Empty paths and Broken paths will be colored red with a white background
-
Adding classpath 'clj'
Requiring tester
This is from tester 1.0.0
Invalid input
Empty paths #{[:infra :local :postgres :user] [:infra :local :postgres :password]}, :broken-paths #{}porter --help
-- Options --
--namespaces Namespaces to add
--print-injections Print injections to stdout
--injections Map of injections to the context map
--exec Execute the src as a shell command
--src Source of input. String or file. Can be multiple lines which will be joined together with a newline in between each entry. This is meant mainly for exec (blame Make)
--env Which environment are we using
--classpaths Classpaths to add
--print Print the output to stdout
--print-ctx Print ctx map to stdout
--ctx-paths vector of strings to context edn files
--dest Which file to write the output toThere is a basic example of how to use porter in the example directory.
cd example
make ENV=:production build-config-files-service
make ENV=:production build-docker-anchorpoint