#user defaults
1 messages Β· Page 1 of 1 (latest)
For context, we're trying to make modules that by convention define a "up" command that requires zero arguments. It will be called by a parent module that controls all of them. It'll be great to offer sensible defaults but allow overrides during testing.
Hi! You're not the only one asking π Not yet possible but we're working on it! Hoping to have it shipped in a few weeks
here's a POC: https://github.com/dagger/dagger/pull/10697
and some supporting building blocks:
Thank you for responding. So I understand, what's the state of default args today? It looks like *dagger.Directory is supported. Is that all for complex types?
And also, while that PR looks useful, it seems like it's defining more .env.dagger.json support. I'm wondering, can design our module constructors with default secrets today via the // +default syntax?
Does this default secrets not break the sandboxing a bit? I call a module from the Daggerverse that by default scoops AWS keys from the standard env vars?
Yes, that's why we took so long to get to it. It's harder than default directories which are scoped to the current "workspace".
TLDR: default secrets will not allow modules to reference this or that 1password key or env variable. Instead we're inverting it: the end user adds those values to a .env file (or a dagger-specific equivalent), which will magically be injected in the schema.
Example from the POC:
That POC defines a new .env.dagger.json but that's just a placeholder. I'm also experimenting with using an actual .env file. TBD
Ahh, so a module doesn't define a .env.dagger.json, a source directory does?
So say I was running dagger -m <path/to/gitlab/module> call <functions> from a directory that has a .env.dagger.json then it uses the defaults?
at the moment in that PR, the .env.dagger.json has to be present in the module directory. For example: cd ./my/module; call <functions> and there is ./my/module/.env.dagger.json
(so, same lifecycle as .env)
@ancient helm what you describe is also possible, but would require having a way to map arguments for multiple modules in the same env file
Yeah, trickier. Having it in the module is already an good option to have though. Presume it works for all secret types we currently have, including files?
Yes, and it would work for all other object types also. Goal is 1-1 parity with object "addresses" you can pass in CLI arguments today. For Container, Directory, Service etc
Hi Solomon, would we be able to define a .env file for a module, then install that module as a blueprint module and expect it to have access to the secrets it needs?
Good question, one benefit of blueprints is that once installed, the context is exactly the same as if it had been copy-pasted into the target directory.
So, for the purposes of this POC or any other feature, it should make no difference whether the module's implementation is unique, or comes from a blueprint
tomorrow I plan on using my building blocks and trying a new POC using an actual .env, maybe try supporting multiple modules, see how it feels
thanks for the feedback & questions, keep em coming! π
Cheers. I can see this is going to be very useful for us. Just this week our team had a meeting about how to solve the problem of each module (which we are using as a blueprint in company repos) requiring slightly different secret and confirg parameters to run. This feature looks very promising π
I have a few more questions Solomon:
- would it be possible to support a default value for a socket in the
.envfile. Ideally we would like to do something like this:
# .env
ssh=${SSH_AUTH_SOCK}
This would set our module constructor parameter ssh *dagger.Socket to the value of the system env variable ${SSH_AUTH_SOCK}
- Will it be possible to set env variables to other env variables (kind of implied by the first question)...? I.e.
# .env
addressOfSecretsManager=${BAO_ADDR}
The idea here is that you don't have to hardcode the value into the .env file, but can reference other environment variables, possibly even composing a new env var from multiple?
seems reasonable, do regular .env files support interpolation? ideally we would align with that, but if they don't, we could perhaps add it anyway. technically could break some .env files used by other tools, but maybe in practice not an issue?
I'll explore in my next poc
@pearl ether could you give me a realistic .env file that you would expect to write with this system? just to get a reference point of realistic use
Given the following module constructor:
func New(
ctx context.Context,
// the address of the bao server
baoAddr string,
// the token for the bao secrets manager
baoToken *dagger.Secret,
// the aws profile to use
localProfile string,
// the aws credentials to use
localCredentials *dagger.Secret,
// the source to compile
// +defaultPath="/"
// +ignore=["**/.venv", "**/__pycache__", "**/node_modules"]
src *dagger.Directory,
// ssh socket used for cloning the source
ssh *dagger.Socket,
) (*Main, error) {
...
}
We would want to be able to default everything with the following .env file:
BAO_ADDR=${BAO_ADDR}
BAO_TOKEN=env://VAULT_TOKEN
LOCAL_PROFILE=${AWS_PROFILE}
LOCAL_CREDENTIALS=file://~/.aws/credentials
SRC=.
SSH=${SSH_AUTH_SOCK}
Update: made zero progress developing the POC, spent the day fighting dagger's own build and codegen tooling.
(ironic I know)
@pearl ether @fringe cape @ancient helm --> https://github.com/dagger/dagger/pull/11000
This is a POC for loading default configuration from .env, and injecting it into the dagger schema.
How it works
Dagger automatically searches for .env by findup.
Each time a module is loaded, the...
Looks great. In two minds over whether I'd want this in the module itself or the workdir, but I think I could make either work fine
Hi Solomon. Thanks very much for working on this. I had a play with this and the initial feature looks promising. There are a few things I am wondering could be included in this feature:
- support for socket addresses ( I gave it a test, but got the error:
Address has no such field: "socket" - ability to interpolate variables from the environment in the
.envfile:
# .env
MY_PARAMETER=${HOST_VAR}
- ability for modules to ship their own
.envfile and when the module is installed aas a blueprint module, that.envcould be used. Use case is we have over 3000+ repos which we would like to plug our platform dagger modules into. If those platform dagger modules can bring along their own.envwhen installed as a blueprint module, this would be really convenient... don't need to create a.envfile for each repo
Yes we can add socket! I thought I had, looks like an oversight
In addition to this, I had some issues getting my .env variables to picked up when:
- the module name was in the format
something-else. It seems in this case env var prefixesSOMETHING_ELSE, norSOMETHINGELSEseem not to work. - if the parameter is in the format
somethingElse. It seems that env var namesSOMETHING_ELSE, norSOMETHINGELSEseem not to work.
ability to interpolate variables from the environment in the .env file:
That should be easy to try (didn't have time yet, but I didn't forget π
My only concern is possible incompatibilities with the broader .env format, which does not support interpolation universally.
the module name was in the format something-else. It seems in this case env var prefixes SOMETHING_ELSE, nor SOMETHINGELSE seem not to work.
Yeah I need to add that
Thanks Solomon!
ability for modules to ship their own .env file and when the module is installed aas a blueprint module, that .env could be used. Use case is we have over 3000+ repos which we would like to plug our platform dagger modules into. If those platform dagger modules can bring along their own .env when installed as a blueprint module, this would be really convenient... don't need to create a .env file for each repo
This one is tricky, I totally see the use case, but checking it into source control might create more problems than it solves, no?
Maybe we could integrate with specialized environment persistence services? Like vault, 1password? Or, maybe we should ship our own in Dagger Cloud π
We've also wanted to add a concept of persistent environment in Dagger itself... We need it for a variety of reasons.
If .env files are not meant to be used like that, totally understand. At nine we commit our .env files but they don't contain anything secure. We have a .env.override for stuff like that and it is .gitignore'd. Definitely open to specialised environment services. We already do a bit of this to avoid the parameter overload.. we use open bao ( a fork of vault ) and inject one secret into our dagger modules which authorises us to open bao, then we get all the secrets we want out. There is a still a developer experience issue with making the developer provide these flags to the dagger command so we ship a wrapper for doing this. One of the problems we are encountering however is that its hard to unify this wrapper across all the different variants of modules that are being developed. The idea of the .env file living in the module directory and being utilised appealed to me because it promised to get rid of the wrapper.
Open to other alternatives though
Or, we could add a CLI configuration field, where you point to a remote .env file and it automatically gets loaded on all calls. Then you push that config to all dagger users on the team. Then you simply upload the shared .env at that location and boom all clients load it
That sounds like it would work.
We're 100% aligned on the goal, we'll find a solution for you. Just want to make sure we do it right.
Committing your .env isn't necessarily wrong, if you're careful to not commit anything you regret. We just can't rely on all users being that careful, or even knowing that they should...
@pearl ether do you have a good way to push configuration to everyone's workstation? (or simply ask them to all install it) Or is that a pain?
I think we will probably be asking them to install something
We would require them to install some tool which would fetch and cache these .env files.
Its not the end of the world and in the past we have used this approach to deliver things to people's machines
If the dagger CLI itself can auto-fetch them, it would be strictly easier then. they'd just have to run a one-time command to configure dagger to do it
Sure that seems doable
eg. dagger config dotenv https://url/of/your/shared/dotenv or something like that
Yeah I think this could work. Would we be able to configure a TTL on this file or even have it fetched everytime?
I can imagine, someone adds a parameter to module x and wants to supply a new default for it. They would want users of that module to be able to get the change soon
Well it would probably use dagger to fetch it π So the caching mechanics would be the same
For http and git remote fetches, you would get the change right away as long as you don't pin to a git commit
Yeah let me run this by the team and get back to you.
I like this sound of this, and I think it would probably work
Spoke to some guys in my team and they were actually not against just sticking a .env in every repo haha.
So it could just be me who wants that
Will keep the convos going and get back to you.
ok π
Bottom line though is we want to dagger call <> wrather than wrapper call <>!
by the way we're happy to huddle with more people from your team if you'd like
Yeah that's the goal! Wrappers always have hidden costs...
Indeed
Anything that you feel is pushing you towards a wrapper, please let me know and we'll fix it
haha, well there is one more thing actually
because we have a traffic steering proxy in front of all our machines and it terminates our SSL traffic, we have to tell most tools to accept custom certs.
These certs are pushed to our machines from IT.
To get this working with dagger right now, we have to use the documented approach: boot our own dagger engine with the certs mounted, and set the environment variable to point the dagger CLI to this conatiner.
If this could be configured once on every machine via some config file living in the user directory, would be awesome
Right now our wrapper also contains a command to set this up which has to be run anytime you jump into a new shell
@pearl ether actually this came up just last week with another big team using Dagger... https://github.com/dagger/dagger/issues/4217
Ahh very nice.
Yeah at the moment these are the two things we are most concerned about that are causing us to use a wrapper:
- custom certs
- parameter defaults
I think that would also be a local config directory (eg ~/.config/dagger/certs) and boom, as soon as you copy them in there, all future containers automatically get those certs installed
Yeah that would be ideal
Thanks for all your help so far Solomon. I will discuss further with the team and get back to you
After discussing further with the team, I think they were very happy with the feature as it stands, and thought that we could live with putting our .env files in each repo
Next week we are going to trial it and see how well it works for our use case
Sounds good! I have a keynote tomorrow so had to pause dev work for 24h, but by tomorrow night I should have a poc3 with:
- Var expansion
- Support for socket arguments
- Code clean enough to get a proper review
Very prompt Solomon π Thanks and all the best for the keynote
@fresh moth π this is the thread for testing the "default secrets" PR
π
Hi Solomon, is there a way to "unset" a default over the commandline:
# .env
AWS_CREDENTIALS=file://.aws/credentials
dagger call my-function --aws-credentials=nil
We are intending to check in our .env files - this is intended to allow the local experience to be seamless. When running things in CI we sometimes need to turn off some of these parameters especially if the file doesn't exist ( when using secret providers ), otherwise the dagger cli would give us an error.
If not, all good. There are ways around it from our end. We could for example mutate the .env to leave parameters out before running the dagger function in CI.
@haughty basalt
@pearl ether one option that comes to mind is keeping .env.local and .env.ci, and selecting one or the other with eg. dagger --env-file .env.local
wdyt?
@visual olive Is the design you've settled on the <module_name>_<variable_name>=<env/file>:<location> showed earlier in this thread? And that file goes not in the module but in the source where you're calling the module?
that's the current implementation yes. still in testing though
I want to add detection of the current module, and in that case allow omitting the module_name_ prefix
Either would be fine. I like the explicit nature of module_name_ but I can see that could lead to repeated secrets in a .env
This - large numbers of secrets - came up in feedback to move one of our larger processes into Dagger yesterday, this feature is quite coincidentally under development at a good time
yup top priority for me π
Yeah I think that would work. If say there were two files in the directory: .env and .env.ci, would dagger by default use the .env unless the --env-file flag was supplied? If so, this would basically solve it for us. We would have two files in each repo. The developer would simply dagger call <function> for their day to day. When we run in CI, our github actions config would specify the --env-file and that would get us onto the "CI set" of defaults.
yes that's how it would work I think π
I've been making progress on the component features, to get them merged asap. But haven't integrated them back into the poc yet
-> socket support
-> var expansion
All good Solomon
I'm not too concerned about the need for both a local and ci .env file, but the option is nice. Reducing the verbosity of running the commands locally is my main concern - this really isn't a problem for CI as the params are defined in an GH workflow file.
Thanks for all the work Solomon!
honestly I can't wait to use this feature for myself π
thank you for the feedback it's super helpful!
Ah reason why I think we might need the ability to set the --env-file that @haughty basalt is because there will be cases where the local .env sets some fields that we don't want to have set in the CI. For example --aws-local-credentials=file://~/.aws/credentials. As this file is not available when running CI, we need this to not be a default, as dagger will error out when trying to find the file (even if the parameter is optional - i believe). Something to kind of "unset defaults" or switch to a different set of defaults in this case, would alleviate this issue.
I am pretty sure that even if the parameter is optional, if you pass a secret provider that doesn't exist, dagger will error out saying the file doesn't exist.
Agree the flag is a good idea --env-file
The way it will be implemented, it should still work. won't be evaluated until call time (so you can override it safely)
We could I guess override it at call time to an empty file --aws-local-credentials=file://empty-file,
We use other flags to tell our dagger module not to use it
Okay if that's going to work, we don't need the --env-file stuff
I think being able to opt in and out of the env is a good idea, even if the flag was inverted to --no-env-file
^ another good option I guess for us to avoid using the .env file in the CI
yeah that too
I like the idea of a clean command that automatically finds the .env, my main concerns are:
- the behaviour is not immediately clear to the user that defaults are being supplied
- It feels like it makes optional parameters unnecessarily complicated. We now have a clean default call, but if we want to avoid those defaults for an optional parameter a dummy value must be supplied.
I like the idea of supplying a --env-file flag to opt-in. It will make it clear to the user where the values are coming from, avoid the need to override/handle defaults you don't want to pass, and retain existing behaviour of how a function is called.
If automatic retrieval is preferred, I think --no-env-file is required so a user can opt out without needing to delete the file.
Just some thoughts. Happy with whatever you decide since you are doing the work, and the core feature is really what we are after π
Good idea, being to point to an external source would be great.
I think we need to have it opt-out, I see the potential downsides, but they are outweighed by the benefits, namely: everyone is tired of typing so many arguments all the damn time. It's the number one complaint.
For sure we should explicitly tell the user when defaults are applied. I think that will help avoid confusion.
Sounds good
All sounds good to me as well
local defaults
I've focused on secrets here, but I'm assuming with the name change this could extend to paths? What about git paths - if the caller has git auth it would just work?
module_name_attribute_name=https://gitlab.com/path/to/repository would work as a default path? If that's the case between secrets and paths I could eliminate all but two parameters
yes, it would support all the same object types as the dagger CLI:
- secrets
- tcp:// url for Service
- oci image refs for Container
- git or local paths for Directory and File
- local path for Socket
amazing thanks Solomon
I started a separate thread for the implementation/review sprint (to go from POC to release): #1415426492057391315
Rebased & cleaned up: https://github.com/dagger/dagger/pull/11034
Awesome Solomon!! Will have a play today
Hi Solomon have had a play today and it looks good. Played with the following .env file:
DFLOW_STRVALUE=awesome
DFLOW_SSH=/private/tmp/com.apple.launchd.FReBGYGEXd/Listeners
DFLOW_SECRET=env://MY_SECRET
DFLOW_FILE=file://go.mod
This all works great and the defaults are picked up π
Didn't have any luck when my module was installed as a blueprint unfortunately...
I had a module called 'dflow' which was installed as a blueprint module in my project directory:
{
"name": "project",
"engineVersion": "v0.18.17",
"blueprint": {
"name": "dflow",
"source": "../dflow"
}
}
With the above .env file was getting this error when running:
! required flag(s) "file", "secret", "ssh", "strvalue" not set
I imagine this is still WIP.
The other thing was the variable interpolation also wasn't working - I assume you also know about that !!
In which case you can tell me to calm down hahaha
But overall, this feature is shaping up to be exactly what we want. Very exciting. Thank you getting this together Solomon. This is going to unlock a lot of opportunities for us.
@pearl ether thanks, I got sucked into getting the lower-level PRs merged (lots of fixing tests, addressing nitpicks from my fellow maintainers etc π
Getting back on track on the feature itself today
interpolation should be working now
ooh cool will try
Yeah sorry if I'm bugging you too much with the feedback.. I'm sure you're like "yes I know"
hahaha
Yeah I ran into this issue also... It's matching against the name of the dowstream module (in your case project) rather than the blueprint (dflow).
Absolutely not. I am loving it.
And in fact I had forgotten to enable expansion in the PR..
Thanks Solomon will give another try with the expansion later today
@pearl ether 2 follow-up questions:
1. Quote stripping
.envformat: would you expect quotes to be interpreted, or left raw?
For example:
TOKEN="env://foo"
Would you expect the quotes to be stripped, or left as-is?
--> At the moment they are left as-is.
2. Blueprint vs. downstream name
In your example above, which var name would you expect to see work?
DFLOW_FILE(match against blueprint name)PROJECT_FILE(match against downstream module name)- both
DFLOW_FILEandPROJECT_FILE(match against both)
--> At the moment only PROJECT_FILE is matched
I'm also curious what others thing cc @soft aspen @vestal marten @agile quail @unkempt comet @runic wigeon (sampling people who may still be online π
would expect for the quotes to be stripped
I will get my team to help out here
This, but semantically, I guess I'd expect escapes to be handled too? Like "foo\"bar" -> foo"bar - However .envrc and .env normally works, if it does that
Yes if we interpret quotes, we'd handle escape also
I'm sad to say at the moment our newly merged EnvFile type does not interpret quotes. But it's an easy change
Technically that change would be a breaking change, but since we haven't released yet, I think we're good
@pearl ether I just pushed support for blueprint name. So in your example above, both DFLOW_xxx and PROJECT_xxx will match (if both are set, they will be merged, and the more specific PROJECT_xxx will win in case of conflict.
cc @unkempt comet
(since it's relevant to blueprints π
that sounds great
On the first point, I agree strip the quotes
Second point, if both are there could be great... I was personally geared towards matching against the blueprint module's name ( as this will make copy and paste within our organisation easier )!
I want to add re: expansion, we use file:~/.git-credentials to access anything in Gitlab, I'd want that ~ to expand as it currently does
I think it will. Did you try? If it doesn't we can fix
Not tried it yet, just wrote some docs about the upcoming changes as a result of this and realised one of the default args we'll need to pass is that one. I'll have to do a bit of module re-writing to test it as well (currently provides those attributes with a method) so not sure I'll get to that before my annual leave
@daring sandal looking into non-constructor arguments...
@daring sandal are you thinking arguments one level deep? (arguments to functions of the module's main object?). Or also functions attached to arbitrary types?
Update: support for any function is almost working π
Wow! IDK how I totally missed this entire thread. Apologies for not participating earlier but I read through all of it and I'm very happy with the progress! Thank you so much @visual olive for working through this. To answer your question,
are you thinking arguments one level deep? (arguments to functions of the module's main object?). Or also functions attached to arbitrary types?
I think it will be less confusing to consumers if it applied globally (to all functions attached to any dagger addressable object). Otherwise it adds a layer of complexity when using defaults.
Finally got TOKEN=foo to work
Can you elaborate what you mean by this? Is it the vars that don't have a module prefix?
I understand this now! It's for .env local to the module. sweet!
I think it will be less confusing to consumers if it applied globally (to all functions attached to any dagger addressable object). Otherwise it adds a layer of complexity when using defaults.
Agreed, I just pushed a commit that does this π
It's working really well... You can now configure defaults for any argument of any function of any type of any module π
WOLFI_CONTAINER_PACKAGES=["git", "openssh", "rsync"]
dagger -m github.com/dagger/dagger/modules/wolfi -c 'container | terminal'
$ cd dagger
$ cat .env
TEST_SPECIFIC_RUN=TestAddress/TestValue
$ dagger call test specific
Full bootstrap of a discord exporter workspace:
$ dagger init ./my-discord-export --blueprint github.com/shykes/x/discord
$ cd my-discord-export
$ cat >.env <<.
token=op://myvault/discord/token
.
$ dagger call servers
trying to think of any spooky accidental defaults, but, i guess they're defaults, not overrides so the risk should be kinda low?
So <module_name>_<function_name>_<parameter_name>?
my wishful thinking exactly π
One interesting side-effect is that, with explicit CLI flags becoming less important (because you can persist them in .env), dagger call becomes more viable
I'm still using dagger call. Couple of .dag scripts for stuff like tests but otherwise... CLI!
Yeah I just can't stand having to type 15 explicit CLI flags, and have to remember their names each time...
But if I don't have to pass those 15 flags anymore, that's different
I'd agree... If I typed them. They're all saved as parameterised workflows in Warp. I just tab through parameters picking from customised enums, it's a snappy workflow.
@soft aspen I was able to print a test log message on the TUI with the "global logger", thanks π
Is there a way to print just the message, without the trappings of a log entry, eg. INF, the date etc?
Also I need to find a clean way to plumb through those global log calls all the way down to TypeDef and other low-level core types, but that's a me problem π
yeah should be a small tweak, just use the underlying telemetry.NewWriter directly, or telemetry.SpanStdio
like just move all this to the telemetry package instead of engine/slog: https://github.com/dagger/dagger/pull/11034/commits/ed5772923fde04d021a13d6d2e7242e661eb4fb3#diff-952f2aa617abfae81c63ed3ee80fadf6b8d186fda0776ac7acf6ea9d2d6f85d2R62-R93
and/or turn the latter function into a GlobalLogsSpanContext(context.Context) context.Context helper (modulo naming) - that'd be more general, then we don't need all those variants
I have no concept of the respective roles of telemetry vs. engine/slog... How does moving those functions to telemetry help? (sorry)
see sdk/go/telemetry/logging.go - that's the more generic layer for sending i/o to span logs
i added this to engine/slog/ which is too specific, this plumbing should really move to the more generic layer
if you turn GlobalLogger into something that just returns the ctx it currently passes to SpanLogger it'll be more generic, and then you can just use that, like telemetry.NewWriter(telemetry.GlobalLogsSpanContext(ctx)) - and then just write your logs to that
(can make the change myself once im in between things if you want)
Yes please and thank you! If I get to it first I'll let you know. Right now wrangling handling of default arguments in TypeDef π
Hi Solomon, just jumping into this conversation (working with @pearl ether). Wanting to check if shell expansion was supported in the latest commit on the PR? Trying it out on my end seems to result in resource cannot have empty address. I could be missing something very obvious but I have confirmed that the env vars are defined in my environment π
@sick lava expansion should work, but it is sandboxed to other variables in the .env. Each variable can reference variables defined before it (to avoid cycles)
System env variables are not used in the expansion.
I'm not familiar with how other .env parsers handle expansion, my goal is to be as standard as possible, but I'm starting to wonder whether there is a standard π
System env variables are not used in the expansion.
Ah yep that would make sense
note that when passing arguments of type Secret, you can pass env://FOO as a value, which does reference an env variable on the host system. But that's unrelated to .env (you can pass the same value as a command-line flag)
Thanks for the clarification! Think I was a little too keen on trying to remove all of the command-line flags from our dagger calls so thought I could put env expansions into the .env
tell me more π what are the names and types of the flags you were trying to move to .env?
Some are along the lines of passing in local AWS credentials, which works great with using the file:// directive in the .env. Other ones are like passing in the local AWS profile through via the system env
OK after some light research, it appears most .env compatible tools do allow interpolation from system env vars, so I'll align with that
So as I understand it the expansion (at least at the moment) works in a similar fashion to how we could define and reuse variables in Dagger shell?
@visual olive pushed
in terms of isolation from
system env variables, yes. But I will fix it tomorrow
Amazing, thanks a lot!
Update: required cleaning up the envfile implementation, to better match the dotenv convention. It took longer than I though.. But now its done. https://github.com/dagger/dagger/pull/11111
Now I can build on top of this to add system env variable fallback, and check the rest of the boxes
Looks good!
Should be pretty trivial to add command expansion too, eg. $()
cool. Very powerful this feature. Basically solving the inputs to the dagger function completely
@fresh moth you were right about where to attach the local default values... I'm gradually converging to dedicated fields. It happened naturally as I cleaned up the code π
system env variable fallback
π π π this is becoming such an amazing feature... !!!!
I implemented it yesterday, but couldn't build it because docker hub went down π will finish that today. also added a bunch of tests to get us closer to merge
So great to see so much love for this feature ahah
It's definitely very motivating!
Which is good because I have a mysterious bug blocking me right now π
UPDATE: system variable fallback now works π
@agile quail my user defaults are applied correctly on call, but they don't show in introspection (eg dagger call --help, dagger -c .help). I thought I had the right entrypoint (Function.FieldSpec, now wrapped by ModuleFunction.FieldSpec) but maybe I'm missing one?
Ah I guess FieldSpec is only for graphql introspection... I need to also change the typedef itself sometime before install
yeah this one specifically I think https://github.com/sipsma/dagger/blob/40079fc9b551321329af4a48b1c0cce292c7e37d/core/typedef.go#L222-L222
Yeah that's how I used to do it, before switching to a lazy model
just walked all the typedefs and overwrote FunctionArg.DefaultValue at load and called it a day
Now I need to either 1) override FunctionArg.DefaultValue lazily (but where? at install I guess?), or 2) create a new LocalDefault field in the typedef, and change the client introspection behavior. I noticed the CLI doesn't show defaultPath so there's a little client work overdue
I'm just worried that client-side introspection behavior becomes super complicated
Also @agile quail how is +optional expressed in core.FunctionArg? I don't see an Optional bool field. Is it DefaultValue: JSON("null")?
Believe it's FunctionArg.TypeDef.Optional
Mm damn I don't know where to hook up my "lazy pull" model for typedef introspection...
Ah maybe ModuleObject.Install()
I traced it down to core.objFun(), I'll try adding my hook there
IT WORKED! π
And I think I can even remove my patched FieldSpec, since that's derived from the typedef anyway
Hi Solomon π Looking good! I gave this a little test on my machine. Here is my source code:
https://github.com/chrisjpalmer/env-test
I was getting some errors and it didn't seem to be picking up my .env file.
This happened if I tried running my dagger function in both the project and dflow directory.
Trace for running in the dflow directory: https://dagger.cloud/nine/traces/24feea32156bd59c5f9a03168fbb6563
Trace for running the the project directory: https://dagger.cloud/nine/traces/020197a55c3e9419785d31541f6df614
noticed you just force pushed to the branch... don't think I caught that latest commit
63a6091
Might try again a little later
Thanks @pearl ether . Yeah I'm doing some cleanup and stabilization. Will ping you in a little bit when I think it's ready
Let me check out your trace
I'm running the full test suite right now
(yeah looks like you hit the bug I was just fixing)
we're almost there!
hahaha nice
I have to get in on this engine dev at some point, it looks fun
I tried reading the source code the other day
it broke my brain
Ha ha I'm on that journey as well π
It gets easier
It's not an all-or-nothing, more like a video game where you unlock new levels as you go
I still have many more levels to go π But making progress
LLMs are a lifesaver... I could never navigate the codebase without one
@pearl ether should be good to go
Awesome will give it a test in a few hours time π
I will have to test this on monday Solomon, ran out of time for today
Thanks for everything so far and looking forward to it
@agile quail thanks for the review π
@agile quail there's one item on my checklist I'm struggling with... I need to nerf the feature so that it only applies to top-level modules, not dependencies. It's actually quite powerful that you can so easily inject dependencies anywhere in the dependency tree... But it's not what users will expect by default, so better to keep that particular pandora box closed for now π
The issue is, I'm not sure how to reliably check for that... I tried a few approaches but it kind of melted my brain
What's tricky is that the code to apply local defaults, for a given module and call, may be called in two different contexts:
-
At module install (to merge them into the typedefs for introspection) -> the context is the module install itself I believe? ie. calling
serve. Or maybe not?
-
At function call (to actually apply the defaults in the call) then the context is the one making the call. Could be a CLI calling top-level module; or module calling sub-module.
So my code needs to cleanly handle both
I can't think of any way to do it other than add an arg to ModuleSource.asModule (this api) that either enables/disables the .env default loading.
- You can hide the arg from the public schema if desired (example) so then it's only available to internal calls
Then you'd load dependencies with that arg set here
The downside is that it means we won't share cache for loading a module directly vs. loading a module as a dependency, but I think that's just an inherent implication of this feature. Also not that absolute end of the world
I see... What about moving it down one level to eg. Module.serve() maybe?
that way the modulesource remains the same?
oh wait, evermind. asModule() returns the Module
so ModuleSource would remain the same
yeah that makes sense actually.
@agile quail re: the caching issue. Are you saying that, even if a module source has no local defaults (and therefore loading it with or without local defaults applied, makes no difference), they would still not share a cache entry, because we do "recipe caching" as opposed to full-blown "content caching"?
Mmm I could refactor the code so that the local defaults to apply are passed as argument to asModule, rather than just a boolean flag
(if it makes a difference for caching)
mm no that's weird
I think you're right that the cache miss might be inherent to the feature
Well there's a mix of recipe and content caching. The call to asModule would not just hit the dagql cache for when the module is loaded top-level vs. as a dep. But internally in asModule we make calls that are setup to be content cached (i.e. runModuleDefInSDK). So honestly we'd still be hitting cache where it counts most, so not really worth worrying about
Ok nice. Yeah I remember updating the content-caching part of module loading in my PR, was cool to discover
ah interesting... recipe caching vs content caching
is recipe caching where the dagql is cached so it fetches a result based on the dagql, where as content caching is based on buildkit artifacts ?
forgive a poor man's attempt to explain ... i mostly have no idea what I am talking about π
Just noticed sometimes if dagger has evaluated a module's function before, it often doesn't run the second time
I believe both are recipe caching
ah cool
The difference is that there are infinite ways to produce the same directory. With content caching you only worry about the end result, so you get more cache hits. But you need to compute a hash of everything which would be prohibitively slow.
ah does dagger ( or buildkit ? ) decide which one to use based on the size of the directory?
no it focuses on the operations. Which operation was performed with what inputs
so most of the time things are recipe cached by the sounds of it?
Hi @visual olive awesome I tested this and it works π
This is a level up for dagger. So excited for this to ship!
I thought that if the .env file was checked into the module directory and the module was installed as a blueprint, it might still work... however this didn't work.
Just wondering if this is still on the cards or not ?
If not all good, just wanted to make sure
Otherwise, putting the .env in the repo's directory which installs the module as a blueprint works perfectly.
Awesome stuff
ah right, at the moment it will only work for local checkouts. So you can still check them into git, but they'll only be applied when dagger loads them locally.
I think it's safer to start thet way, then see if there's the need to loosen the restriction. It's a one way door..
yeah understandable I think this gets us enough ground to run with for now
but, we can add support for remote env file via a flag, if still interested π
Ship this first please π
if that was a flag that you could default to being on (through dagger config) that would be ideal
we would love to give developers dagger call x and nothing more if possible
haha
even if we have to put a .env in every repo, its better for adoption if the learning curve is low and developers only do dagger call x rather than something say dagger call --remote-env=<> x
Haha I see @ancient helm is keen to ship
Also share this sentiment π happy to wait
Yeah, I've got 3 git URLs, 3 secrets, and 1 secret file that can all be defaults, leaving perhaps 2 or 3 arguments
I've even written a function that creates the env file with either main (for testing) or latest release versions of those git URLs (for production)
that's interesting, what does that generator function do?
Returns a file that contains the env contents, with either latest released versions (queries Gitlab) or no ref to just use main (we run tests on main before releases go out to users)
Makes it very easy for my team to test
Approaching the finish line... (I hope!)
thank you!
Questions on specific implementation @visual olive:
Does my module need a constructor to use these, or is the default hidden constructor fine, and all I need to do is ensure the env file names match the struct attribute names?
Pre-filtering pragmas still apply to directories provided by default value?
Third maybe question, maybe misunderstanding. I was under the impression the env file was to be located in the directory calling the Dagger function, not the module itself, but the PR suggests it can be in either? So my Dagger module can have a .env full of THIS_MODULE_NAME_ATTRIBUTE_NAME=DAGGER_THING and the directories calling that module with dagger -m <git/url/to/module> <function> don't have to have that same env file? So if I have 30+ services calling one function, they don't all need a copy, it's enough to have one copy in the module?
yes it can be either. but "inner .env" (inside the module) only works for local modules, to avoid security issues (eg. an untrusted module has a .env and accesses your secret vault)
When you say "only works for local modules" that would mean dagger -m <git/path/to/module> from another directory wouldn't work, it wouldn't use the env file in the module?
that would work
I mean it does work
I'm happy to hear that, but I don't understand how given your previous statement. Not critical that I understand if it works though π
dagger -m <any/potentially/untrustworthy/module> could have an env file in it and scoop secrets
The engine checks whether the module is a local or git address. if the former it loads .env from it. if the latter it doesn't.
yes you're right that a local checkout could still be untrusted. It seemed like a reasonable tradeoff, we can fine tune it
So in my case it wouldn't work... We're calling modules from gitlab using https
yeah it wouldn't work. But you can distribute a single .env that everyone adds eg. to their home directory
the nice thing is you don't need to have secret values in there. only secret references
That's ok, we have a gitlab pipeline template that all the services include in their pipelines, it'll go in there. I've written a generator function for users running tests on their laptops to get a copy of the file
you can match any module, type, function and argument.
Not attributes though (you need something that can receive arguments)
for example to match:
func (m *MyModule) Build(source *dagger.Directory)
you can set MYMODULE_BUILD_SOURCE=...
or if the .env is inside the module: BUILD_SOURCE=...
So it does need a constructor, the attributes on the module struct aren't sufficient alone
e.g.
type ExampleModule struct {
ExampleAttribute string
}
Won't pick up an env file with EXAMPLE_MODULE_EXAMPLE_ATTRIBUTE=hello unless the module also has a constructor?
Yes, if you can't set it from the CLI, then you can's set it from .env
Thanks for the clarifying questions @ancient helm . All the above questions are great for documentation (maybe FAQ style?).
Yes, I struggled to even describe the feature clearly in the PR... I am dreading the docs process
I have a question of my own. How does this work with --blueprint? Is a blueprint considered a remote module? Or a local module for .env consumption?
blueprints are fully supported. the remote/local nature of the blueprint is not taken into account (if you trust a module, you trust its blueprint)
when explicitly naming the module in your .env variable, you may use either the blueprint or downstream module's name
Very cool! So I can have a .env that blueprint users get by default but is able to override themselves. sweet!
yup π
With spinning some stuff out to a followup is this ready to go in 0.19.1?
Quick update: Inching towards merge.
- Had to disable support for system variable lookup, temporarily, because of a caching issue (cc @fresh moth @agile quail). Will open a follow-up PR to re-enable it, but this helps us get something merged faster
- Had to disable
--env-fileflag, because it fails in nested clients (dagger-in-dagger) and I have no idea why. Will also open a follow-up PR once I get reinforcements. I have one failing test where I need help.
--> Then we're good to merge.
Without --env-file Dagger looks for specifically .env by default? If so none of the above impacts me, I just want muh feature
correct
I had a use case for the system vars, but having this feature sooner will be massive!!! env system vars can definitely wait π₯ π₯ π₯ π π π
The code for system vars is still all there, just a 3-line change to re-enable it once we sort out the caching
This cat emoji says it all
Failing tests fixed thanks to @agile quail π
almost at liftoff!
user defaults https://github.com/dagger/dagger/pull/11034
user defaults
@agile quail thanks for the β
Just double-checking, on the caching front, you're not concerned with the current implementation? eg. potentially having multiple instances of the same modulesource not sharing enough cache hits, because they're loading different .envs - or other scenarios like that?
From what I saw nothing was blocking. Definitely some areas where it could theoretically be refined more to get more cache hits but no regressions to existing modules or anything like that
And refining cache hits for modules that use .env is much better for a followup. Itβll be much easier to implement in isolation rather than mixed in with the baseline implementation
Nice. So... I can merge? π
Tracking follow-ups here: https://github.com/dagger/dagger/issues/11188
Yeah of course, shipit
@agile quail π₯ wild test failure out of left field.. https://dagger.cloud/dagger/traces/7726768ba728c227e806e53cc68fb59c
I don't understand, it was all green and all I did was fix one last lint error (unused function)
re-running on the odd chance it's a flake
Yeah I think that one is an unrelated flake, has popped up occasionally lately
I'll update a pair of modules tomorrow π―
FYI you should be able to upgrade your CLI and nothing else. No module upgrade necessary
(CLI + engine)
I plan to refactor a bit to take advantage of user defaults a bit better, tbd on how effective this might be
Let us know how it goes π
Remember you can set user defaults on any argument to any type & function, not just module constructors
Initial feedback: Absolutely love it!
Not a huge fan of how the defaults are displayed persistently in the TUI. It also bleeds into the final output. Maybe a hotkey to show/hide them? Hide by default?
- User defaults that are not relevant to the currently executing function are also shown in the TUI. Eg: I have funcA and funcB and they arent related. I have defaults in
.envfor funcA and funcB both but when I run funcB or funcA, all the user defaults are shown. Would it be possible to show only the relevant ones?
We can add a hotkey. I was worried about giving people clear feedback, otherwise your defaults can easily be silently dropped, or silently applied. Can be frustrating to iterate.
Ah that's a bug. Will fix.
This makes sense. It's a bit too in my face atm and a bit distracting. Maybe there's a better way to show it in the TUI. @soft aspen can do some more magic π
This is the magic @soft aspen gave me π But yeah we'll polish. Now that it's released it will get more real-world usage, so polish will be easier
Amazing work Solomon! Will give this a test
I think the current way actually reduces the screen real estate the TUI has (because it's bottom-up). I can see it being very crowded if there are a bunch of defaults.
True
Maybe it could go in the sidebar? Currently that's only used for LLM stuff, and disappears on exit, not sure what's desired there (sounds like maybe that's ok?)
Was wondering about that also
@visual olive it would be a good fit to add on top of https://github.com/dagger/dagger/pull/9327 π (ready for approval whenever)
an API for adding sidebar content (or if API is too bold, can at least add attachables)
Ok testing this now. Example, for a module named MyModule and a function named MyFunction with a parameter named GiveMe the env file should contain MY_MODULE_MY_FUNCTION_GIVE_ME=env:GIVE_VALUE? I'm getting required flag(s) "give-me" not set
I'm using dagger call -m <module> my-function <other-function>
at the moment MyModule has to remain MyModule (if you add a _ it won't be recognized). But you can use any casing, it's case-insensitive
So for example you can do: MyModule_MyFunction_GiveMe=env:GIVE_VALUE
(assuming this is a function on the main module type)
if the .env is in the module's root directory, then you can omit the module name: MyFunction_GiveMe=env:GIVE_VALUE
It is a function on the main module type. I'll try that
See the release note: https://github.com/dagger/dagger/releases/tag/v0.19.1
Function names also omit the _? What about parameter names
Based on this they all do?
MYMODULE_MYFUNCTION_GIVEME=env:GIVE_VALUE still results in required flag(s) not set
well if there's a _ in the name, you need to include it. But if it's Camel-case eg. MyModule you can't add _ in the variable name
(sorry if this is confusing)
do you mind showing a relevant snippet of the module code?
Main module type: type ServiceDeployer struct {; function name func (m *ServiceDeployer) WithCredentials(; parameter name: AccessKey *dagger.Secret,
Calling it with dagger -m <module> call with-credentials <but not providing params here> <other-function>
So ServiceDeployer_WithCredentials_AccessKey=env://something ?
oh it's env:// not env:
(although I believe we do have backwards-compat for the env: format)
ServiceDeployer_WithCredentials_AccessKey=env://AWS_ACCESS_KEY_ID results in required flag(s) not set
Is it possible there's a clash with AccessKey which is also an attribute on the main type ServiceDeployer albeit +private
Wouldn't have thought so given the function name is in the key
Conflict shouldn't be an issue, I think
Where is your .env relative to the dagger.json?
In the directory where the dagger call -m <git/path/to/module> with-credentials <other-function> is being called
ah I see the module itself is loaded remotely. Mmm that should work
fwiw it also took me ages to figure out the conversion scheme; my assumption was everything got SHOUTY_SNAKE_CASEd so I was trying things like DAGGER_DEV_GITHUB_TOKEN or GITHUB_TOKEN for a githubToken constructor arg to our module. there's no way I would have guessed DaggerDev_GithubToken which seems to be what it wants. or maybe it's case-insensitive and I would have eventually got there? but GITHUBTOKEN feels weird
Yeah I am trying to find a way to flatten that learning curve...
Maybe a command to generate a template .env with the legal keys commented out
yeah, it's tricky because you don't know what it looked for. the confirmation that it found it is there, at least
Generally I have been struggling with the name conversions across graphql, CLI, SDK-native etc. Had to hack my own conversions
I thought I remembered there are helpers in the code but I couldn't find them
also noticed it prints the value for defaults, which could be bad if someone doesn't use env:// or op:// or something for a secret. haven't confirmed if there's a safeguard for non-URI-looking values (too scared π)
I don't think we support a secret-by-value format do we?
Oh you mean if the user does that by mistake
Did you test any function args? I've tried basically every variation of MyModule_MyFunction_MyArgument without success
there's strcase.ToScreamingSnake - i think you could just apply that to each component and join them with _?
ah right, because this is equivalent of CLI args, same rules apply i guess? but since they're at least tucked into a file (albeit in plaintext on disk), a user might assume it's more safe than passing it on the CLI, and be more likely to that (see: me)
i'm not sure if CLI flags accept direct values for secrets
i tried that without the MyModule component and it worked (RunGithub_GithubToken)
So your .env is inside the module for that to work
yes it's all the same rules as CLI.
I've removed the +private from the main module type attributes and updated the .env to ServiceDeployer_AccessKey but still no luck there
could you tell me the module name from dagger.json?
"name": "service-deployer",
Maybe to isolate problems, try a local checkout, with a .env inside that omits the module name? To see if we can get it to work that way first
Is there anything in a cloud trace I should look for to be certain the .env is picked up?
So dagger call ../<module/path> and put the .env in the module?
And inside that .env: ServiceDeployer_WithCredentials_AccessKey format for keys?
git clone <YOUR_MODULE>
cd ./your_module
echo 'WithCredentials_AccessKey=env://foo' > .env
dagger call with-credentials
Ok great, so we know the problem is either 1) the module name prefix, or 2) a bug with loading remote modules
Extending that test to copying that .env to a service repository and calling dagger -m ../service-deployer with-credentials works too, even without adding the module name to the .env keys
to eliminate the first option, keep the exact same setup but add the module prefix to .env?
ah, looks like you just did that π
Adding ServiceDeployer still works
(When calling the module from local with dagger -m ../service-deployer with-credentials)
Can you keep the same workdir & .env that works, and just change the value of -m to point to the remote module?
But if I remove the .env from the module (so it's only in the working dir calling it) it no longer works
That isn't going to work because of π. Removing .env from the local module dir and calling it with dagger -m ../service-deployer errors. The .env seems to have to be in the module
it looks like the workdir in your case is a subdirectory of the module correct?
No, it's another dir on the same level
~/Projects/service-deployer (module), ~/Projects/billing-service (one of many services)
I assume in this test it's the exact same .env in the workdir as the one that worked when in the module root?
Yes
cp ../service-deployer/.env .
.env files in the working dir don't seem to be picked up at all, for local or remote module sources
If you want to know which .env files actually get loaded, you can call dagger -c '.core | module-source ../service-deployer | user-defaults | as-file | contents'
EDIT: forgot .core
find module "module-source": local path "module-source" does not exist
Furthermore, .env inside the module dir with module name prefix also doesn't work, it must be without the module name prefix
sorry I fixed the command (forgot .core |)
Or maybe it's my module name...
So in the module directory locally, this command shows the user defaults from the local .env _including` the module name prefix. But running the module in that same dir errors
So Dagger sees the user defaults, but doesn't use them... Is this my module name somehow?
The command above only confirms which .env files get loaded. It doesn't show which are matched (but I should add functions for that, will make debugging easier)
Doesn't work:
dagger -c '.core | module-source ../service-deployer | user-defaults | as-file | contents'
βΆ connect 0.3s
βΆ detect module: . 0.3s
βΆ load module: /Users/user.name/Projects/service-deployer 2.5s
β moduleSource(refString: "../service-deployer"): ModuleSource! 0.0s β 1
β .userDefaults: EnvFile! 0.0s
$ .asFile: File! 0.0s CACHED
βΆ .contents: String! 0.0s
ServiceDeployer_WithCredentials_AccessKey=env://AWS_ACCESS_KEY_ID
ServiceDeployer_WithCredentials_SecretKey=env://AWS_SECRET_ACCESS_KEY
ServiceDeployer_WithCredentials_SessionToken=env://AWS_SESSION_TOKEN
Setup tracing at https://dagger.cloud/traces/setup. To hide set DAGGER_NO_NAG=1
dagger call with-credentials
βΆ connect 0.4s
βΆ load module: . 0.5s
β parsing command line arguments 0.0s ERROR
! required flag(s) "access-key", "secret-key", "session-token" not set
Setup tracing at https://dagger.cloud/traces/setup. To hide set DAGGER_NO_NAG=1
Works:
dagger -c '.core | module-source ../service-deployer | user-defaults | as-file | contents'
βΆ connect 0.2s
βΆ detect module: . 0.3s
βΆ load module: /Users/user.name/Projects/service-deployer 0.3s
β moduleSource(refString: "../service-deployer"): ModuleSource! 0.0s β 1
β .userDefaults: EnvFile! 0.0s
$ .asFile: File! 0.0s CACHED
βΆ .contents: String! 0.0s
user default: service-deployer.withCredentials(secretKey="env://AWS_SECRET_ACCESS_KEY")
user default: service-deployer.withCredentials(sessionToken="env://AWS_SESSION_TOKEN")
user default: service-deployer.withCredentials(accessKey="env://AWS_ACCESS_KEY_ID")
WithCredentials_AccessKey=env://AWS_ACCESS_KEY_ID
WithCredentials_SecretKey=env://AWS_SECRET_ACCESS_KEY
WithCredentials_SessionToken=env://AWS_SESSION_TOKEN
Setup tracing at https://dagger.cloud/traces/setup. To hide set DAGGER_NO_NAG=1
dagger call with-credentials
βΆ connect 0.3s
βΆ load module: . 0.5s
β parsing command line arguments 0.0s
β serviceDeployer: ServiceDeployer! 0.0s
βΆ .withCredentials(
β accessKey: Address.secret: Secret!
β secretKey: Address.secret: Secret!
β sessionToken: Address.secret: Secret!
): ServiceDeployer! 2.4s CACHED
user default: service-deployer.withCredentials(accessKey="env://AWS_ACCESS_KEY_ID")
user default: service-deployer.withCredentials(secretKey="env://AWS_SECRET_ACCESS_KEY")
user default: service-deployer.withCredentials(sessionToken="env://AWS_SESSION_TOKEN")
ServiceDeployer@xxh3:db308fc01898b2b5
If within a module dir the module name prefix is optional then my module name is a problem because that seems to prevent matching the defaults to args
it's supposed to be optional. But I'm starting to worry that my tests have missed some bugs..
I'm sorry that this is not easier to setup
I'm going to change the module name from service-deployer to servicedeployer on the tiny chance this is a problem
Maybe try service_deployer as the module prefix in .env?
So I renamed the module in dagger.json, and all references of it, to remove the - and now:
cat .env
Servicedeployer_WithCredentials_AccessKey=env://AWS_ACCESS_KEY_ID
Servicedeployer_WithCredentials_SecretKey=env://AWS_SECRET_ACCESS_KEY
Servicedeployer_WithCredentials_SessionToken=env://AWS_SESSION_TOKEN
Works inside the module dir with the module name prefix
Though it does seem to duplicate the output:
user default: servicedeployer.withCredentials(secretKey="env://AWS_SECRET_ACCESS_KEY")
user default: servicedeployer.withCredentials(sessionToken="env://AWS_SESSION_TOKEN")
user default: servicedeployer.withCredentials(accessKey="env://AWS_ACCESS_KEY_ID")
Servicedeployer_WithCredentials_AccessKey=env://AWS_ACCESS_KEY_ID
Servicedeployer_WithCredentials_SecretKey=env://AWS_SECRET_ACCESS_KEY
Servicedeployer_WithCredentials_SessionToken=env://AWS_SESSION_TOKEN
WithCredentials_AccessKey=env://AWS_ACCESS_KEY_ID
WithCredentials_SecretKey=env://AWS_SECRET_ACCESS_KEY
WithCredentials_SessionToken=env://AWS_SESSION_TOKEN
WithCredentials_AccessKey=env://AWS_ACCESS_KEY_ID
WithCredentials_SecretKey=env://AWS_SECRET_ACCESS_KEY
WithCredentials_SessionToken=env://AWS_SESSION_TOKEN
So module name with - is at least one minor problem
Ok progress. Calling dagger -m ../service-deployer with-credentials with a .env (as above) in the working dir but not the module dir now works
Aaaand calling dagger -m <git/path/to/that/same/module> with-credentials with the .env in the working dir (but not the module dir in git) also works
So lesson is: don't have - in your module name
Thank you for debugging this... Adding to the follow-up list
FWIW having the defaults be blatantly obvious in the terminal view is a good thing
Sure it's a lot of screen space, but if they're not incredibly obvious it's going to cause headaches eventually
OK it's very late here... really must go to bed. Thanks for being patient
Hah, I thought you'd already slept and woken up at 4am! Thanks for working through this!
My kids keep me busy in the late afternoon & evening, so it's hard to resist getting a few more hours of quiet work time during the night π
Another potential issue with default args for Directory types:
argument "lcPath" must be of type Directory to apply ignore pattern ([<patterns_here>]) but type is dagql.Optional[github.com/dagger/dagger/dagql.IDType]{Value:dagql.ID[*github.com/dagger/dagger/core.Directory]{id:(*call.ID)(0x40032e1900),
inner:(*core.Directory)(nil)}, Valid:true}
If I provide the path by CLI arg it works, if I provide it by default arg it seems to think it's an optional and doesn't
Ah, looks like an issue with .env combined with +ignore
Indeed. Removed the +ignore for that path and that one worked, the next default dir (which also has a +ignore) didn't
Exactly the same error, so that's almost certainly a +ignore issue
Damn! I was testing it right now and kept getting not a bare assignment...
All my blueprints are named with - π !! π
Your tomorrow self won't be happy about tonight π
Thanks for working through the initial issues!
I will address this in priority
I agree with this. I had this exact same scenario and I tried GITHUB_TOKEN first. GITHUBTOKEN (for githubToken) does feel weird. Either one isn't consistent with the CLI because that's github-token. The SHOUTY_SNAKE_CASE makes more sense to me than SINGLESHOUTYSTRING as we are dealing with a .env file and that's the typical convention for env keys.
Yeah I agree I just need help with that aspect of the implementation
π I'm struggling to pass a []string through the localDefaults...
Is there any reference to the syntax that's expected? I keep getting that the input is not a valid JSON. Is it supported?
(I've tried many combinations of a "json" single value slice (which will pass the json.Valid() but ... no luck
@solar bough can you share .env contents you've tried?
The relevant line:
SimpleGoContainer_Deploy_Workloads=plat-usc1/jenkins/freeze-exception-bot.yaml
I've tried many iterations
SimpleGoContainer_Deploy_Workloads=[ plat-usc1/jenkins/freeze-exception-bot.yaml ]
...
SimpleGoContainer_Deploy_Workloads=[ "plat-usc1/jenkins/freeze-exception-bot.yaml" ]
...
and quoting, escaping, etc... without much luck so far. I reckon that this means it is supported, I just haven't found the right syntax yet π
It's a bash syntax parser (same as dagger shell) with a special exception made for FOO=BAR BAZ (which is interpreted as FOO="BAR BAZ".
Quoting works the same as shell. SO in doubt you can wrap the value in single quotes. Can you try:
SimpleGoContainer_Deploy_Workloads='["plat-usc1/jenkins/freeze-exception-bot.yaml"]'
It's possible that said exception interferes with SimpleGoContainer_Deploy_Workloads=[ "plat-usc1/jenkins/freeze-exception-bot.yaml" ]
(There can be ambiguity between "interpret this as a regular shell expression" and "interpret this as a simplified dotenv assignment")
Having a - in the module path, not just in the module name causing issues. When checking the users-defaults with dagger -c '.core|module-source ./dagger-modules/custodian | user-defaults | as-file | contents' it shows the contents of my .env file as CUSTODIAN_ARGS=something. When calling the module with dagger -m dagger-modules/custodian call list-cache the args constructor argument is not loaded and the CLI doesn't show the user default value. It works if calling from the same directory, dagger call list-cache will load the user default and the CLI will show it.
Removing the module name from the .env file and keep it like ARGS=something and it will be loaded from anywhere.
@solar bough update, I confirmed that you have to quote-protect your JSON values, just like in a shell script... Otherwise the JSON quotes get interpreted away. I realize that this might deviate from expected behavior of a regular .env file... The downside of using a bash parser. For now I added tests to track that behavior.
Don't suppose you've looked into the pragmas on directories bug yet? That's the most impactful for us currently, they're on basically every directory input we have
I started, want to fix it today. Sorry for the delay!
@ancient helm I'm having trouble reproducing the issue with +ignore..
let me push the test
You have a working test?
Directory argument (with +ignore) to function, provided by .env
@ancient helm https://github.com/dagger/dagger/pull/11219
Fix various issues reported by users after shipping user defaults.
dagger call -m github.com/shykes/dagger@user-defaults-fix test specific --run=TestUserDefaults/TestDirectoryIgnore
Can you take a look at my test, is it testing the wrong thing?
package main
import (
"dagger/reprodaggerdirdefault/internal/dagger"
)
type Reprodaggerdirdefault struct{}
func (m *Reprodaggerdirdefault) AddDir(dir *dagger.Directory) *dagger.Container {
return dag.Container().From("alpine:latest").WithDirectory("/input", dir)
}
func (m *Reprodaggerdirdefault) AddFilteredDir(
// +ignore=["**", "!goodbye.txt"]
dir *dagger.Directory,
) *dagger.Container {
return dag.Container().From("alpine:latest").WithDirectory("/input", dir)
}
From another directory, with the following files:
.env:
Reprodaggerdirdefault_AddDir_Dir=.
Reprodaggerdirdefault_AddFilteredDir_Dir=.
hello.txt: hello
goodbye.txt: goodbye
Run:
(No pragma, unfiltered, works): dagger -m ../reprodaggerdirdefault/ call add-dir directory --path /input entries
dagger -m ../reprodaggerdirdefault/ call add-dir directory --path /input entries
βΆ load module: ../reprodaggerdirdefault/ 2.8s
β parsing command line arguments 0.0s
β reprodaggerdirdefault: Reprodaggerdirdefault! 0.0s
βΆ .addDir(
β dir: Address.directory: Directory!
): Container! 0.9s
β .directory(path: "/input"): Directory! 0.1s
β .entries: [String!]! 0.0s
user default: reprodaggerdirdefault.addDir(dir=".")
.env
goodbye.txt
hello.txt
(Pragma, filtered, fails with error I initially posted): dagger -m ../reprodaggerdirdefault/ call add-filtered-dir directory --path /input entries
βΆ load module: ../reprodaggerdirdefault/ 0.5s
β parsing command line arguments 0.0s
β reprodaggerdirdefault: Reprodaggerdirdefault! 0.0s
β .addFilteredDir(
β dir: Address.directory: Directory!
): Container! 0.0s ERROR
! set call inputs: apply ignore pattern on arg "dir": argument "dir" must be of type Directory to apply ignore pattern ([**, !goodbye.txt]) but type is
dagql.Optional[github.com/dagger/dagger/dagql.IDType]{Value:dagql.ID[*github.com/dagger/dagger/core.Directory]{id:(*call.ID)(0x400affa480),
inner:(*core.Directory)(nil)}, Valid:true}
With minimum familiarity the test looks fine. Perhaps the only difference is in the .env key - mine is the full ModuleName_FunctionName_ArgumentName path?
Perhaps the repro above helps spot the cause
Ah, you're right I'll add that in the test
Could be specific to 1) non-constructor argument, 2) explicit module name
Ah! or 3) +ignore on a required argument
I can quickly test 1
Or not, I seem to be unable to get constructor args working with .env at all
package main
import (
"dagger/reprodaggerdirdefault/internal/dagger"
)
type Reprodaggerdirdefault struct {
DirArg *dagger.Directory
}
func (m *Reprodaggerdirdefault) New(
// +ignore=["**", "!goodbye.txt"]
dirArg *dagger.Directory,
) *Reprodaggerdirdefault {
return &Reprodaggerdirdefault{
DirArg: dirArg,
}
}
.env:
Reprodaggerdirdefault_DirArg=.
This should work no?
Apparently Reprodaggerdirdefault_New_DirArg=. is picked up as a default, but doesn't actually apply the value? π€·ββοΈ
βΌ .addFilteredConstructorDir: Container! 0.2s ERROR
β panic: unexpected nil pointer for argument "source"
! process "/runtime" did not complete successfully: exit code: 2
user default: reprodaggerdirdefault.new(dirArg=".")
user default: reprodaggerdirdefault.addDir(dir=".")
user default: reprodaggerdirdefault.addFilteredDir(dir=".")
Nevermind, New shouldn't be a method on Repodaggerdirdefault. Re-testing...
Same error on constructor arg @visual olive:
Module:
package main
import (
"dagger/reprodaggerdirdefault/internal/dagger"
)
type Reprodaggerdirdefault struct {
DirArg *dagger.Directory
}
func New(
// +ignore=["**", "!goodbye.txt"]
DirArg *dagger.Directory,
) *Reprodaggerdirdefault {
return &Reprodaggerdirdefault{
DirArg: DirArg,
}
}
func (m *Reprodaggerdirdefault) AddFilteredConstructorDir() *dagger.Container {
return dag.Container().From("alpine:latest").WithDirectory("/input", m.DirArg)
}
.env (in another dir):
Reprodaggerdirdefault_DirArg=.
dagger -m ../reprodaggerdirdefault/ call --dir-arg . add-filtered-constructor-dir directory --path /input entries
βΆ connect 0.4s
βΆ load module: ../reprodaggerdirdefault/ 0.5s
β parsing command line arguments 0.0s
β reprodaggerdirdefault(
β dirArg: Address.directory: Directory!
): Reprodaggerdirdefault! 0.0s ERROR
! set call inputs: apply ignore pattern on arg "dirArg": argument "DirArg" must be of type Directory to apply ignore pattern ([**, !goodbye.txt]) but
type is dagql.DynamicOptional{Elem:core.DynamicID{typeName:"Directory", id:(*call.ID)(nil)}, Value:core.DynamicID{typeName:"Directory",
id:(*call.ID)(0x401502c900)}, Valid:true}
FWIW: Seems like it does not recognize it when the module name is prepended on the .env:
ModuleName = Tests
FuncName = Hello
InputSlice = Strs
.env in the same folder as the module
Tests_Hello_Strs='[\"foobar\"]' fails
Hello_Strs='[\"foobar\"]' works
@ancient helm OK I now have a repro in the test suite (same PR). Confirming that it's the specific combination of user default + ignore + required argument
will add a test for that as well
Is it an easy fix?
I think it will be easy yes (once I understand why my test is failing with an error message that doesn't exist anywhere in my dev source..)
OK almost there
I have a "fix" which makes the error go away, but the test still fails because the ignore behavior is wrong.
in case you're around @fresh moth or other <@&946480760016207902>
Update: my fix seems to work with manual QA, but my test is still failing π€ . Hopefully it's just an issue with the test. Investigating
Sorry for the slow turnaround, the whole team is converging to California for our yearly team retreat... Everyone has been packing or traveling for the last several days
...it was indeed my test π¬
@ancient helm ok I have a fix
Will get it merged asap
Happy to hear it!
I'm having trouble reproducing this one... Are you getting it only with an argument of type array? Or other types also?
I'll create a repo tomorrow and share it in an issue. I only got it with the array type
Ok i'll add a test for that. surprising that it would only triggered by one type... π€
@ancient helm merged https://github.com/dagger/dagger/pull/11219
Tiny change! Will keep an eye on releases
@ancient helm @solar bough @daring sandal FYI I have a fix for better matching of module names with dashes: https://github.com/dagger/dagger/pull/11255
sorry it took so long
@solar bough could you tell me if it fixes your issue?
Iβll do as soon as I have 30m! Thanks so much!!
Your PR is still in draft mode, did you mean to flip it?
yes π thanks
merged & released in 0.19.3
Very early testing. I have a module that has dashes (my-sdk-go). I am calling it remotely like dagger call -m my-sdk-go generate-scaffold-prompts. It has a --github-token requirement in the constructor. My .env is not being honored
I am on 0.19.3, but the module itself is on 0.19.2. Does the module also have to match the version?
The .env file is in my local. Which is basically an empty folder
@daring sandal can you share the contents of the .env? It's fine to redact the values, I just need the exact var names
I think the module's engine version is irrelevant
Yeah one sec
Mmmm.... unless....
Maybe upgrade the module's engine field to 0.19.3 just in case? π
I don't see how that could be the reason
GITHUBTOKEN=env://GITHUB_TOKEN
Updating the module's engine version didn't have any effect
if the .env is outside the module root, you need to prefix it with the module name
ie. MY_SDK_GO_GITHUBTOKEN=env://GITHUB_TOKEN
or MySdkGo_GITHUBTOKEN= (will allow both)
this is assuming your constructor argument is called githubToken or githubtoken?
githubToken
yeah arg name should be fine (note to self, also make matching more flexible on arg names, the same I did for module name)
This didn't work either
I tried both MY_SDK_GO_GITHUBTOKEN and MYSDKGO_GITHUBTOKEN
@daring sandal when you say dagger call -m my-sdk-go are you actually typing dagger call -m github.com/something/something/my-sdk-go?
yes, sorry if that wasn't clear
it's a remote module
what's the actual module name in dagger.json? that's the source of truth
Use case is, scaffolding an app for a user. They start with and empty dir
um, dagger.json is SdkGo
if you want to scaffold, you could use dagger init --blueprint=my-sdk-go, then you can drop a .env in the root of that scaffolded app, with GITHUBTOKEN=env://...
yup drop the my- in the var name then
ok that worked!
Scaffold:
dagger init --blueprint=github.com/.../my-sdk-go
cat >>.env <<'EOF'
GITHUBTOKEN=env://GITHUB_TOKEN
EOF
In this case (.env at the root of the user's module) you can drop the SDK_GO_ prefix
So I should probably rename my module to match ther folder name
hmm, I don't think the end user will be using this dagger module. So I don't see a reason to add it as a blueprint
OK. I guess I misunderstood what you meant by scaffolding
so I have a go framework. I am going to create a skeleton app for the user based on certain inputs (which isn't a dagger module).
I have a separate go dagger module the user may use later.. But it's not part of the scaffolding yet
the scaffolding code is in the go-framework is because I want to keep everything in the same place
It's still early design, I may re-think this delivery mechanism. It's great to be able to use dagger to do such a thing (with Changeset)
Our next batch of features might be able to help π Demo soon
Can't wait!
Has there been any movement on fallback to system env ?
We are eagerly awaiting this feature π
@pearl ether sorry for the delay. My (naive) implementation was problematic so we had to roll it back. I think the underlying naΓ―vetΓ© has been fixed, so we can give it another try
@pearl ether we shipped another experimental feature called "toolchains". It's like blueprints but more modular - more like the original prototype where you could install multiple blueprint-like dependencies
yeah just been playing catch up. Already can see a few use cases for it
@rose juniper π this is where we've been discussing follow-up improvements to user defaults (aka .env suppport), including system env fallback
is there a plan to support loading an EnvFile into a container? e.g.
f := c.Host().File(".env").AsEnvFile()
c.Container().From(alpineImage).WithEnvVariablesFromEnvFile(f).WithExec([]{"env"})
or
myVars := c.Host().File(".env").Variables()
c.Container().From(alpineImage).WithEnvVariables(myVars).WithExec([]{"env"})
I think that would be super useful
and as a follow up question (more of an implementation question), I noticed that .AsEnvFile().AsFile() doesn't cause the variables values to get filled in -- is there a specific reason for this?
I think I have a work around, where I can just iterate over all the variables while calculating a custom digest -- but I'm thinking it might be a bit more efficient if .AsEnvFile() did the substitution at the same time as calulating the hash.
Open to API & implementation changes. But note that today you can also construct an env file from scratch envFile().withVariable("foo", "bar") etc. So would have to think carefully above how lazy or greedy we want expansion to be in different scenarios. eg. right now I can programmatically construct an env file and export it to the filesystem, would we no longer support that? maybe it's ok? I guess i can just join the strings myself, not exactly rocket science π
what EnvFile was lazy by default, and EnvFile.expand() returned a new EnvFile with all values fully expanded? There could be a dirty field to differentiate resolved & unresolved envfiles, and adjust caching accordingly (clean envfile is "pure" and fully cacheable).
Then some calls could trigger expand eg. Envfile.get() on a dirty envfile would evaluate as Envfile.expand().get()
ah that's really good to know, we'll need to leave it as-is then. I could see some gnarly caching coming up when doing something like envFile().withVariable("name", "${user}") where it would then reach out to the host for the value and potentially bust the cache.
@pearl ether @daring sandal @ancient helm @solar bough spoiler alert π https://github.com/dagger/dagger/pull/11442
very nice
Update: PR is up for review https://github.com/dagger/dagger/pull/11442
once this is released, it will be a game changer for us
