duct

lambder 2018-11-07T11:57:16.046Z

Hi, How can I use data collected by CLI args with duct config defined in edn file? With duct config as a data-structure is easy as I can inject the values into the map but I don't know how to do it if the config is sourced from edn.

2018-11-07T11:58:36.047200Z

@lambder You can either update your -main method to update the configuration before it's prepped, or - preferably - use environment variables instead of CLI args and take advantage of the #duct/env reader tag.

lambder 2018-11-07T12:06:55.047500Z

can I set env vars in jvm?

lambder 2018-11-07T12:07:44.048300Z

I'm thinking about writing my own data reader similar to #duct/env but sourcing info from java properties, which can be set during runtime

lambder 2018-11-07T12:08:20.048800Z

for the first advice, how do I update the configuration?

lambder 2018-11-07T12:08:35.049200Z

what value placeholders would you suggest in the edn?

2018-11-07T12:24:59.050500Z

@lambder Take a look at the generated -main function. The configuration is read in, then prepped and executed, but after it's read in and before it's prepped you can modify it as you want.

2018-11-07T12:25:20.050900Z

I don't know why you'd want to set variables during runtime. That seems a bad idea.

lambder 2018-11-07T12:29:24.051300Z

so I use org.clojure/tools.cli before I construct duct config

lambder 2018-11-07T12:29:49.051900Z

I want to some of the cli args to be injected to duct

lambder 2018-11-07T12:30:03.052400Z

it's too late to set the env vars

lambder 2018-11-07T12:30:12.052700Z

I could set the java props tho

2018-11-07T12:30:29.053100Z

Yes, if you really want to do that. Are you using 0.10 or 0.11-beta?

lambder 2018-11-07T12:32:20.053300Z

[duct/core "0.6.2"]

2018-11-07T12:49:13.053900Z

@lambder So your -main function should look like:

(defn -main [& args]
  (let [keys (or (duct/parse-keys args) [:duct/daemon])]
    (-> (duct/read-config (io/resource "bar/config.edn"))
        (duct/prep keys)
        (duct/exec keys))))

2018-11-07T12:50:47.054600Z

You can alter the configuration with whatever function you want after read-config

2018-11-07T12:51:16.055100Z

Though I'd advise sticking to environment variables.

lambder 2018-11-07T12:54:10.055500Z

the keys are the list of ig keys . correct?

2018-11-07T12:54:23.056Z

Yep

lambder 2018-11-07T12:54:28.056200Z

what rather than selecting the keys I want to take a http port form cli?

lambder 2018-11-07T12:54:39.056500Z

I know I can use env var for this.

lambder 2018-11-07T12:54:54.056900Z

I'm using this as an illustration of the case only.

2018-11-07T12:55:04.057300Z

Okay

lambder 2018-11-07T12:55:09.057600Z

so, how can I inject http port form cli to duct?

2018-11-07T12:56:11.058800Z

It's up to you. You can pass it as a raw argument or you can define a flag to take it, like -p 3000.

lambder 2018-11-07T12:57:03.059Z

sure

lambder 2018-11-07T12:57:15.059300Z

but how to pass it to duct conf?

2018-11-07T12:57:30.059600Z

The Duct configuration is just a map, returned by read-config

2018-11-07T12:57:37.059900Z

So you can use assoc-in as normal

2018-11-07T12:57:40.060100Z

Or whatever you want.

lambder 2018-11-07T13:24:41.061700Z

Ok, I've managed to implement what I wanted. I'm sharing here just in case you want to include it in the duct framework.

(defmacro local 
  "A string reader providing values of local bindings"
  []
  (let [lvals (vec (keys &env))
        lvars (mapv keyword (keys &env))]
    `(let [local-vars-map# (into {}
                     (map vector
                          ~lvars
                          ~lvals))]
       (fn [v#]
         ((keyword v#) local-vars-map#)))))
Usage:
(let [a 123
      b "xyz"]
  (duct/read-config
    (java.io.StringReader. "{:foo  {:x #ig/val a}
                             :bar  {:x #ig/val b}}")
    {'ig/val (local)}))
produces: {:foo {:x 123}, :bar {:x "xyz"}}

lambder 2018-11-07T13:25:59.062300Z

instead of symbols the (local) string reader cold take keywords or both

lambder 2018-11-07T13:29:11.062700Z

e.g

(let [a 123
      b "xyz"]
  (duct/read-config
    (java.io.StringReader. "{:foo  {:x #ig/val :a}
                             :bar  {:x #ig/val :b}}")
    {'ig/val (local)}))
works too

lambder 2018-11-07T14:45:55.064400Z

@weavejester I use [duct/core "0.6.2"] which seems to be latest. My print reader works with duct/read-config but if my config includes other one by :duct.core/include it looks like my reader is no longer being applied for the included edn. Any idea how to fix it?

2018-11-07T14:50:02.065800Z

@lambder The readers should be passed straight through to any includes. It might be something weird with the macro you're using, though it produces a closure at the end of it so it should be safe.

2018-11-07T14:50:52.066800Z

I think this is something you'll need to investigate yourself if you want to head down this route. If it turns out to be a bug in duct/core, send me an issue report and I'll put together a fix.

2018-11-07T14:52:24.067800Z

I'd also be interested in knowing why you're taking this approach to begin with 🙂

lambder 2018-11-07T14:55:50.069500Z

I take this approach to have the full duct configs in edn files. The injected dynamic values (established in runtime before the duct system is initialised) are used in the duct config on many levels (in the sense of nested maps).

2018-11-07T14:56:27.070600Z

What are the dynamic values for?

lambder 2018-11-07T14:56:33.070800Z

Transforming that would require post or pre walking the data structure and determining what should be repalaced.

lambder 2018-11-07T14:56:48.071100Z

the dynamic values are coming from the user

lambder 2018-11-07T14:57:15.071600Z

imagine the user runs it as a command line providing the dynamic values as args

lambder 2018-11-07T14:57:38.072100Z

I could convert the loaded duct conf as you suggested but is a but cumbersome

lambder 2018-11-07T14:57:53.072500Z

with my approach (local bindings) it is resolved automatically

lambder 2018-11-07T14:58:59.073200Z

I create my conf by:

(let [insert-record        (:insert-record opts)]
                              (duct/read-config
                                (io/resource duct-config ) {'local (u/local)}))

lambder 2018-11-07T14:59:07.073400Z

which works

lambder 2018-11-07T14:59:23.073800Z

if the duct-config is a string to my prod duct conf

lambder 2018-11-07T14:59:37.074300Z

if I point it to my dev one (dev includes prod)

lambder 2018-11-07T14:59:51.074900Z

the duct claims there is no #local reader

2018-11-07T14:59:57.075100Z

Okay, and the 'local isn't being transferred to files included with :duct.core/include?

lambder 2018-11-07T15:00:03.075400Z

correct

lambder 2018-11-07T15:00:49.075600Z

ah

lambder 2018-11-07T15:00:53.075800Z

one sec

lambder 2018-11-07T15:01:01.076100Z

I just killed my REPL

lambder 2018-11-07T15:01:10.076300Z

no

lambder 2018-11-07T15:01:17.076500Z

it still is not working

lambder 2018-11-07T15:01:52.077Z

I've ended up to repeat all the config from prod in dev (except the difference)

2018-11-07T15:02:26.077500Z

Are you passing the readers to the prep function as well as read-config?

lambder 2018-11-07T15:11:12.077800Z

no, just to read-config

lambder 2018-11-07T15:11:23.078200Z

should I do with prep as well?

lambder 2018-11-07T15:13:04.078900Z

if yes how can I supply the readers and require prep for all the keys, not just selected ones?

lambder 2018-11-07T15:13:16.079100Z

@weavejester

2018-11-07T15:14:11.080300Z

prep is what loads the include files, so you need to supply the readers to that if you want the include files to have readers.

2018-11-07T15:14:58.080900Z

@lambder You should just need to supply your reader function to both the read-config and prep stage.

2018-11-07T15:15:08.081200Z

The next version of Duct makes this process simpler.

lambder 2018-11-07T15:15:59.081400Z

(defn prep
  "Prep a configuration, ready to be initiated. Key namespaces are loaded,
  resources included, and modules applied.

  Like `init` and other Integrant functions, `prep` can be limited to a subset
  of keys. The keys supplied indicate which keys will be later initiated, and
  therefore which keys to load. Modules are always loaded and applied.

  A map of options may also be supplied. Currently the only supported option is
  `:readers`, which should contain a map of data readers that will be used when
  reading configurations imported through `:duct.core/include`. "
  ([config]
   (prep config nil))
  ([config keys]
   (prep config keys {}))
  ([config keys {:keys [readers] :or {readers {}}}]
   (-> config
       (apply-includes (memoize #(read-config % readers)))
       (doto (ig/load-namespaces [:duct/module]))
       (apply-modules)
       (doto (as-> cfg (if keys
                         (ig/load-namespaces cfg keys)
                         (ig/load-namespaces cfg)))))))

lambder 2018-11-07T15:16:31.082Z

if I call (prep conf :reders my-readers)

lambder 2018-11-07T15:16:51.082500Z

which arity will be called?

lambder 2018-11-07T15:17:02.082700Z

@weavejester

2018-11-07T15:17:23.083100Z

You mean: (prep conf {:readers my-readers})?

lambder 2018-11-07T15:17:35.083300Z

yes

lambder 2018-11-07T15:17:49.083800Z

that would be the 2-arity right?

2018-11-07T15:17:59.084200Z

Yes, because there are 2 arguments.

lambder 2018-11-07T15:18:07.084400Z

which is `([config keys] (prep config keys {}))`

lambder 2018-11-07T15:18:22.084800Z

that would break

2018-11-07T15:18:30.085Z

Yeah, so just supply the keys, or nil if you want the default.

2018-11-07T15:18:46.085500Z

Though -main supplies [:duct/daemon] as its default, mind.

lambder 2018-11-07T15:19:46.086200Z

ok, that was what I meant how do I require all the keys

lambder 2018-11-07T15:20:16.087Z

many thanks @weavejester

2018-11-07T15:20:29.087300Z

I believe just supply nil, though usually you just want [:duct/daemon] if you're running from -main, and nil if you're running from the REPL.

lambder 2018-11-07T15:22:45.087600Z

I run it a bit diferently

lambder 2018-11-07T15:22:52.087800Z

I construct a duct system

lambder 2018-11-07T15:23:22.088500Z

and then I have my own main driver which picks different duct components from the system

lambder 2018-11-07T15:23:38.088900Z

but that is a different story 😉

2018-11-07T15:24:22.089800Z

It sounds like you're doing some sort of customer/user-specific sharding.

lambder 2018-11-07T18:04:39.090400Z

@weavejester thank to your advices everything works perfect now. Thanks!

2018-11-07T20:41:49.090800Z

@lambder excellent news