#Using `terminal()` effectively...

1 messages · Page 1 of 1 (latest)

terse jay
#

I'm exploring the use of dagger. ATM I'm looking at how debugging works and using/reading https://docs.dagger.io/features/debugging/ as my source of info.

The examples on that doc and https://docs.dagger.io/api/terminal work - but when I try to use the following (and many other variants) I'm not dropped into a terminal (I'm executing using the dagger cli: dagger call publish-local --svc=tcp://localhost:5001):

  @func()
  async publishLocal (@argument({defaultPath: "/"}) source: Directory, svc: Service): Promise<string> {
    await this.test(source);
    const registry = dag
    .container()
    .from("ubuntu:latest")
    .withServiceBinding("reg", svc)
    .terminal();
    return "Publishing...";
  }

Have I missed something in the docs?

What do I need to know about terminal() in order to use it effectively?

(incidentally that service binding isn't working either but once I'm able to use terminal() I'll poke around that a bit more).

Find and resolve errors quickly

Dagger provides an interactive terminal that can help greatly when trying to debug a workflow failure.

gaunt olive
#

@terse jay this is because the Dagger API is lazy and only evaluates pipelines when needed. In this case you don't return the container so dagger never actually evaluates it. You can force evaluation with sync()

#

or, if your function returns the container, and it's used by the caller, that will also trigger evaluation

terse jay
#

@gaunt olive - thank you for your swift response! So in this case:

  @func()
  async tester(): Promise<string> {
    return await dag
      .container()
      .from("alpine:latest")
      .terminal()
      .stdout()
  }

stdout() is maybe some how also invoking sync() or perhaps something else?

#

btw terminal().sync() works for me - thanks again!

#

In thinking about it some more - maybe a call to sync() should be included within terminal() - unless there's another reason someone would add a call terminal() ?

gaunt olive
#

the lazy behavior is not specific to terminal everything is lazy by default.

It's true that perhaps we could change terminal's behavior to be synchronous. We would have to be sure there's no legitimate use of it being lazy. cc @trim hull who developed the feature

noble forge
trim hull
#

It should be synchronous out of the box — I’m wondering if it’s a regression, although it’s automatically tested. I’ll try to repro

trim hull
# terse jay I'm exploring the use of dagger. ATM I'm looking at how debugging works and usin...

So as mentioned the problem is that everything is async AND that in this case, the container is not returned (it's returning "Publishing..." instead), so the CLI can't force the evaluation of the return value

There's 2 solutions:

  1. Actually return the container. For instance:
func (m *Repro) Repro() *dagger.Container {
    return dag.
        Container().
        From("alpine:latest").
        WithServiceBinding("reg", dag.Container().From("alpine:latest").AsService()).
        Terminal()
}
  1. Or, force it to be synchronous by calling Sync
func (m *Repro) Repro(ctx context.Context) string {
    dag.
        Container().
        From("alpine:latest").
        WithServiceBinding("reg", dag.Container().From("alpine:latest").AsService()).
        Terminal().
        Sync(ctx)
    return "Publishing"
}
#

Sorry it's in Go, faster for me to write that up. I've confirmed that both actually do work

trim hull
# trim hull So as mentioned the problem is that everything is async AND that in this case, t...

The nitty gritty details: Everything that is actually "used" (either by returning it to the CLI, or because you're accessing the container from a different function) gets "Sync()" automatically

If on the other hand you're creating "local variables" in your functions and never accessing them (e.g. never using them in a different function, never using them in the CLI), dagger "optimizes" them away thinking they're not needed

This is what you were seeing

#

Option 1 actually returns the container variable, so it's not "internal" to the function anymore

Option 2 keeps it internal to the function (never returned), but in that case it forces a Sync() manually so it doesn't get discarded by dagger

hallow oxide
#

This also shed a big amount of light on this topic thanks guys, there should probably some explicit mention of this in the container docs, if it is there I missed it.

terse jay
#

Thank you all! I'm a complete n00b w/r/t the use of dagger so my expectations are maybe naive but to improve developer experience I think the following should be considered: If terminal() is meant to be used as a way to "set one or more explicit breakpoints" then it seems that in all cases, when used on a container object, terminal() should put the developer in a place to inspect state at that point.

Thanks to you all and the time you took, I understand now that isn't the way it works - and even have an understanding of why - thus providing a way to move forward.

noble forge
# terse jay Thank you all! I'm a complete n00b w/r/t the use of dagger so my expectations ar...

@terse jay feedback received, thx for sharing. As Andrea mentioned, in this case is not about the terminal specifics but the Dagger laziness model. To re-iterate, in your example function you're not using the assignedregistry variable, so Dagger doesn't even execute the container.from("ubuntu")...terminal() part. The moment you use the registry variable for anything, that's when the breakpoint will trigger.

This is analogous to setting an endpoint in a function that never gets called basically.