#Wait for user input

1 messages · Page 1 of 1 (latest)

pale remnant
#

Hi! Dagger looks really cool and I am contemplating doing a PoC with it. But I have a hard requirement to implement “wait for user input” functionality (manually approving or rejecting deployments).
I can’t seem to figure out how to achieve such a thing using Dagger.
How would you go about this requirement? Are there any resources on this subject?

vital shore
#

I don't think ive seen an example about Authorization of a command, though I have thought of this myself a few times now. I did not think its much of an issue though, in most cases, you will always have a remote CI Environment, this CI environment is generally where priveleges, user groups, triggers of a pipeline will occur.

So you can still do a POC to remove all the execution of the 'happening' but still have some scripting or wrapping commands which 'control' when dagger is executed.

#

Some of that may be CI Platform specific controls. If you're thinking of a purely local solution, I think you would 'design the pipelines to be executed' in dagger, and then have some control around how the dagger pipeline is executed, that would be shell or powershell or something like this.

vital shore
#

Also maybe there is opportunity on the very recently introduced Terminal() feature, which gives you an interactive terminal into a container, but its core feature is really for debugging. I had a quick look, thinking you could possibly have some interactivity, create 1 container with a spawned terminal which has a prompt to the user, depending on the exit code of that container, either do something or return. Didn't manage to get that working.

trail matrix
#

you totally could have a Terminal, with a custom little shell that requires you to type either approve/deny, and that writes a file on the filesystem - then you can check if that was written or not
you could even encapsulate this behavior into a module potentially to be able to re-use it?

#

there's sadly not a way to do this in the core api, i can definitely see the value for it - but i guess i'm struggling to understand how this would integrate in a ci environment (where no Terminal api is available)

brave flicker
# trail matrix you totally could have a `Terminal`, with a custom little shell that requires yo...

^ this. Here's a workaround example where you can store the WithExec output in a cache volume file and read from that afterwards.

func (m *Lele) Test() {
    opt, _ := dag.Container().
        From("alpine").
        WithMountedCache("/tmp/replies", dag.CacheVolume("replies")).
        Terminal(dagger.ContainerTerminalOpts{Cmd: []string{"sh", "-c", `read -p "Pick one (Red/Blue): "  && echo -n $REPLY > /tmp/replies/pill`}}).WithExec([]string{"cat", "/tmp/replies/pill"}).Stdout(context.Background())

    fmt.Println("The selected option was", opt)
}

having said that, it'd be nice to do something like Terminal().Stdout to get the output directly from the terminal command which is not currently possible instead of doing all this cachevolume dancing

trail matrix
#

you don't even need the cache volume dance i don't think

#

you can just do it with a normal volume, write the file, read the file, unmount the volume

ember lynx
#

@pale remnant what build, CI, and deployment tooling are you looking to try Dagger with?

brave flicker
trail matrix
#

You can just to withDirectory

brave flicker
# trail matrix You can just to `withDirectory`

oh, tried that also but Terminal doesn't commit the changes to the underlying container FS (not sure how that works internally). So using WithDirectory didn't work for me.

i.e:

    d := dag.Directory()
    dag.Container().
        From("alpine").
        //WithMountedCache("/tmp/replies", dag.CacheVolume("replies")).
        WithDirectory("/tmp/replies", d).
        Terminal(dagger.ContainerTerminalOpts{Cmd: []string{"sh", "-c", `read -p "Pick one (Red/Blue): "  && echo -n $REPLY > /tmp/replies/pill`}}).WithExec([]string{"cat", "/tmp/replies/pill"}).Stdout(context.Background())

    fmt.Println(d.Entries(context.Background()))
pale remnant
#

what build, CI, and deployment tooling are you looking to try Dagger with?

Currently using Maven and Docker, Jenkins and Nomad.
Actually, there is a wish to migrate to GitHub Actions. But we have a huge amount of logic in Jenkins Groovy and I am not looking forward to migrate that all to a yaml based approach.
My hope is to start adopting Dagger icw Jenkins and later change Jenkins with GHA.

vital shore
#

I think...

#

the docs don't demonstrate the terminal writing anything back and it does strongly mention debugging. if we confirm terminal is still valid maybe a cookbook example could be added. though I also would like to know what happens with that in a CI platform.

brave flicker
vital shore
#

simulateTerminal and feed it input lines to execute on each terminal creation. (lol I'm joking)

#

but in all seriousness it could be possible to kind of follow how some Ci platforms allow tty St various stages. seems like it could get really messy though

#

sorry not tty. I mean like form inputs like gitlab has a form with arguments. that's not really the same thing I suppose.

#

@brave flicker so cache is required when using terminal if you need to write back? when I am back I want to fix my broken sample which tried to do take user input

brave flicker
brave flicker
trail matrix
#

it would be nice if we could writeback 🙂

#

instead of relying on cache volumes, which feels a bit yucky

brave flicker
vital shore
#

I cant get this example to work based off the cache example, i thought this would be a nice way to control it, but somehow deploy is always true. probably i am doing something wrong

package main

import (
    "context"
    "dagger/fakedeploy/internal/dagger"
    "fmt"
    "strings"
    "time"
)

type Fakedeploy struct {
    approved bool
}

func (m *Fakedeploy) WithApproval() (*Fakedeploy, error) {

    response, _ := dag.Container().
        From("alpine").
        WithMountedCache("/tmp/replies", dag.CacheVolume(fmt.Sprintf("reply-%d", time.Now()))).
        Terminal(dagger.ContainerTerminalOpts{
            Cmd: []string{
                "sh",
                "-c",
                "read -p 'Deploy: ' && echo $REPLY > /tmp/replies/reply"},
        }).
        WithExec([]string{"cat", "/tmp/replies/reply"}).
        Stdout(context.Background())

    m.approved = strings.Contains(response, "yes")
    return m, nil
}

func (m *Fakedeploy) Deploy() (string, error) {

    if m.approved == true {
        return dag.Container().
            From("ubuntu:latest").
            WithExec([]string{"echo", "Deploying..."}).
            WithExec([]string{"sleep", "5"}).
            WithExec([]string{"echo", "Deployed"}).Stdout(context.Background())
    } else {
        return "", fmt.Errorf("deployment not approved")
    }
}

dagger call with-approval deploy

#

thought maybe it was do to with the cache, so i added a time to it but still no luck

brave flicker
vital shore
#

but its defaulting to true regardless... gopher things i dont understand also

brave flicker
vital shore
#

huh

brave flicker
#

I know what's happening 😄

vital shore
#

I am more confused now than ever

brave flicker
#

your read -p is beign cached 😄

#

bust the cache before that

vital shore
brave flicker
#

the cat is being cached sorry

#

I saw this in the log.

    ✔ start sh -c read -p 'Deploy: ' && echo $REPLY > /tmp/replies/reply 2.3s
  ✔ Container.withExec(args: ["cat", "/tmp/replies/reply"]): Container! 0.0s
    ✔ load cache: exec cat /tmp/replies/reply 0.0s
vital shore
#

i could name the file with a date

brave flicker
#

the next relese of Dagger has some TUI improvements to show cached operations better

vital shore
#

i think the unintitive thing here is "we gotta use cache to grab the output" but then make sure the output is never cached

#

😄

#

haha

vital shore
#

Okay got it working now. Simple 'approval' chaining which might be a little nicer to manage and also if you design any functions, you can always check the approval before executing.

package main

import (
    "context"
    "dagger/fakedeploy/internal/dagger"
    "fmt"
    "strings"
    "time"
)

type Fakedeploy struct {
    Approved bool
}

func (m *Fakedeploy) WithApproval() (*Fakedeploy, error) {

    response, _ := dag.Container().
        From("alpine").
        WithMountedCache("/tmp/replies", dag.CacheVolume(fmt.Sprintf("reply-%d", time.Now()))).
        Terminal(dagger.ContainerTerminalOpts{
            Cmd: []string{
                "sh",
                "-c",
                "read -p 'Deploy: ' && echo $REPLY > /tmp/replies/reply"},
        }).
        WithExec([]string{"echo", fmt.Sprintf("'cache buster %s'", time.Now())}).
        WithExec([]string{"cat", "/tmp/replies/reply"}).
        Stdout(context.Background())

    m.Approved = strings.Contains(response, "yes")
    return m, nil
}

func (m *Fakedeploy) Deploy() (string, error) {

    if m.Approved == true {
        return dag.Container().
            From("ubuntu:latest").
            WithExec([]string{"echo", "Deploying..."}).
            WithExec([]string{"sleep", "5"}).
            WithExec([]string{"echo", "Deployed"}).Stdout(context.Background())
    } else {
        return "deployment not approved", nil
    }
}

dagger call deploy should fail, since the approval was not set to true by using the with-approval

dagger call with-approval deploy should fail, if the response is not 'yes' and should pass, if the response contains yes.

#

Since this is such a critical topic too (approvals in general, especially large companies) maybe we can add this for a larger discussion or add something on github about the DX and gate-keeper type of patterns that can be achieved with dagger?

brave flicker
vital shore
#

@pale remnant maybe the above could satisfy your requirement?

#

Think I'll add a second issue around Terminal documentation (maybe not leaning enough into its usefuless outside of debugging

spark palm
# pale remnant Hi! Dagger looks really cool and I am contemplating doing a PoC with it. But I h...

Late to this thread, but are you using an existing CI system that has this capability?

I've been thinking about this a bit with CircleCI for example that has a wait-for step in their pipeline and the way that I was thinking about solving this was to have it work like this

  1. Dagger Call function A
  2. CircleCI Wait for Input
  3. Dagger Call function B

So instead of trying to solve this inside of dagger, I would be taking advantage of the built in tooling in the CI system

brave flicker
#

In any case this could also be very easily achieved by implement it in userspace via polling a URL or something like that.

spark palm
vital shore
brave flicker
pale remnant
pale remnant
pale remnant
#

I consider it an anti-pattern. But in some industries there are requirements for a manual stamp of approval.