#dagger up
1 messages ยท Page 1 of 1 (latest)
๐งต
Location: core/up.go:64
Comment:
This looks like it can hang on partial startup failure.
RunUpblocks onctx.Done()once a service has started
successfully, butparallel.New().Run(ctx)waits for all jobs and does not cancel siblings when one returns an
error. So if service A starts and service B later fails during startup, B returns an error, A keeps waiting
forever, anddagger upnever surfaces the startup failure unless the user hits Ctrl+C. I think this needs to
separate "start everything" from the long-lived wait, or use a cancel-on-error context/pool here.
It should be fixed (and covered by integration test) with https://github.com/dagger/dagger/pull/11959/changes/1a7d3b307fd64cb73d0cd486b7913f307e7fbb9e
Nice
My other question was more of a general UX question: did you get a chance to dogfood, and it did it feel right?
I'm not worried about the UI itself: I can already picture it, it helps that we already have dagger check and dagger generate for consistency.
I'm more thinking about our service API, and the networking & composition model behind it.
For example if I have 2 services with +up, A and B... and A actually calls B as a dependency...
When call dagger up, will B be run once or twice? Will there be dedup or not... And if the answer is "it depends", what are the rules?
None of that comes from dagger up itself... It was already there in the model, it's just that dagger up surfaces it now.
Also I'm wondering how we will allow composing services across modules, without writing a new module ๐ Also not really in scope for dagger up, but I can't help but think about it for modules v2. .. Since the goal is maximum module composition without writing custon code
Also for modules v2 we'll have to transpose your portMapping configuration into the new workspace config file ๐
(thanks for the thorough review and manual testing @quaint smelt )
Maybe in the future we could allow +up functions to receive other services as arguments, and present a cool configuration model for easily passing one up service as argument to another from the config file
like "plugging" services into each other
actually, this could be done via a constructor argument in the module - doesn't have to be an argument to the +up function itself
I'll read again this discussion to be sure.
I did multiple tries, with all +up functions in one service, with functions across multiple, etc.
Regarding dependencies, like service bindings, for now what I did is a +up function binding an other service from a dependency. So yes, in that case it's in the code.
Maybe this function configuration (to receive services) is close to the discussions regarding ship? As a way to compose, declare dependencies, etc
Yes it makes sense that it's in the code - I'm just curious about dedup in that case
Mmmm ship is pretty different, it's more about exporting dagger state to an external system (publishing, uploading, releasing, deploying...)
Yes, you can easily ended up with the same module loaded twice, once as a dependency and once as a "toolchain" dependending on the use cases.
For now we can't easily says one module will require a service from an other module (toolchain)
If we have a +up function that declares a service as an arg, a dagger up will not start it by default (because the arg is missing)
In the config.toml if we can configure it, saying "this up function's arg is the up function from this module" then it means we can build a graph, start the services in the right order and pass them
That should be doable. Question is how to make it nice enough in the config side.
I mean 2 services in the same module:
type MyApp {
pub frontend: Service! @up {
container().from("blabla").withServiceBinding("backend", backend).asService()
}
pub backend: Service! @up {}
}
$ dagger up
--> runs backend
--> runs frontend
...which runs backend (a 2nd time? or not? dedup or no dedup?)
ho ok, I was thinking about composing between across multiple modules
Yes, in that case the service will run two times, and might fail because of port conflict.
Because when we start to run the functions, we don't know about the binding
type MyApp {
pub frontend(srv: Service! @default("backend")): Service! @up {
container().from("blabla").withServiceBinding("backend", srv).asService()
}
pub backend: Service! @up {}
}
@default might not be the right annotation, just writing something
With something like that, this become part of the signature, so we can coordinate the execution
I'm experimenting on that, I think that could be an interesting solution. At least for dependencies inside a module. I'm not sure about using strings to point to a different function, but let's try.
And maybe that could then be extended for dependencies across modules:
type MyApp {
pub frontend(srv: Service!): Service! @up {
container().from("blabla").withServiceBinding("backend", srv).asService()
}
}
type MyDep {
pub backend(): Service! @up {
...
}
}
[my-app.frontend]
srv=my-dep.backend
(not sure at all about the naming, but just to share the idea)
A default dagger up (without the config) will not start frontend as the argument is not defined/injected, only backend will be. But with the toml glue it will work
Yes this is interesting for sure. But in any case, we can't force people to use arguments for this - so we'll need to better understand the dedup behavior, so we can document it (and fix it if it's wrong).
TLDR it's both ๐
OK I'm going to bed! Thanks!
An interesting approach suggested by Claude:
type MyApp {
pub frontend(backend: Service!): Service! @up {
container().from("blabla").withServiceBinding("backend", backend).asService()
}
pub backend: Service! @up {}
}
No annotation, just infering the argument name. This might be very fragile, and maybe too much magic ๐ช but this doesn't feel completely bad idea.
Like I said you can't prevent module devs from calling the real backend() function directly if they want to... So don't worry too much about inventing a different way to do it, since that would just mean we have to support 2 ways instead of 1 ๐
Ok, forgot everything, this is already working ๐
There's already deduplication of services on the engine side.
I tried with this example:
type MyModule struct{}
// +up
func (m *MyModule) Gitea() *dagger.Service {
return dag.Container().
From("gitea/gitea").
WithEnvVariable("DB_TYPE", "postgres").
WithEnvVariable("DB_HOST", "db:5432").
WithEnvVariable("DB_NAME", "gitea").
WithEnvVariable("DB_USER", "gitea").
WithEnvVariable("DB_PASSWD", "gitea").
WithExposedPort(3000).
WithServiceBinding("db", m.Db()).
AsService()
}
// +up
func (m *MyModule) Db() *dagger.Service {
return dag.Container().
From("postgres:alpine").
WithEnvVariable("POSTGRES_USER", "gitea").
WithEnvVariable("POSTGRES_PASSWORD", "gitea").
WithEnvVariable("POSTGRES_DB", "gitea").
WithExposedPort(5432).
AsService()
}
And it looks like to just work, no port conflict, no duplication of the db service.
I'll double check because I want to be sure this is really the case, as it looks too good ๐
But it feels just right, so nothing to add, no crazy configuration, nothing.
I tried by running the gitea function only and it started the service as part of the call, and I tried with dagger up and it also just works. Just the two calls are returning the same service instance so it's good.