为什么在不使用 –interactive 运行 Docker 容器时 expect 失败?

huangapple go评论61阅读模式
英文:

Why is expect failing when the docker container is run without --interactive

问题

我在运行一个位于Docker容器内的expect脚本时遇到了错误。

首先,我创建了一个简单的expect脚本test.expect

#!/usr/bin/expect -f 

set timeout 3

puts "--- Start"
lassign $argv name duration

spawn bash -c "echo Hello ${name} && sleep ${duration}"
expect {
    eof {
        puts "No timeout";
    }
}
puts "--- End"

注意:expect_before起初被注释掉了。

现在是Docker镜像的Dockerfile

FROM alpine:latest

RUN apk update && \
    apk upgrade && \
    apk add bash expect

COPY test.expect /root
ENV PATH $PATH:/root

WORKDIR /root
ENTRYPOINT ["test.expect"]

现在构建并运行Docker:

$ docker build -t test-expect .
$ docker run -i test-expect Martin 2
--- Start
spawn bash -c echo Hello Martin && sleep 2
Hello Martin
No timeout
--- End
$ docker run -i test-expect Martin 5
--- Start
spawn bash -c echo Hello Martin && sleep 5
Hello Martin
--- End
$ docker run test-expect Martin 2
--- Start
spawn bash -c echo Hello Martin && sleep 2
Hello Martin
No timeout
--- End
$ docker run test-expect Martin 5
--- Start
spawn bash -c echo Hello Martin && sleep 5
Hello Martin
--- End

脚本的两个路径在Docker内外都正常工作,无论是否使用了-i--interactive)。现在我取消了expect_before的注释。重新构建和运行:

$ docker build -t test-expect .
$ docker run -i test-expect Martin 2
--- Start
spawn bash -c echo Hello Martin && sleep 2
Hello Martin
No timeout
--- End
$ docker run -i test-expect Martin 5
--- Start
spawn bash -c echo Hello Martin && sleep 5
Hello Martin

Timeout fired
$ docker run test-expect Martin 2
--- Start
spawn bash -c echo Hello Martin && sleep 2
error writing "stdout": bad file number
    while executing
"puts "--- End""
    (file "/root/test.expect" line 15)
$ docker run test-expect Martin 5
--- Start
spawn bash -c echo Hello Martin && sleep 5
error writing "stdout": bad file number
    while executing
"puts "--- End""
    (file "/root/test.expect" line 15)

自然地,在我实际的流水线(GitHub工作流程,调用操作,运行大容器,使用复杂脚本,需要我的实际expect脚本)中第一次看到这个问题时,我假设它与在没有标准输入和/或标准输出的环境中运行expect有关。直到我最终创建了这个简化的测试用例,我才找到了我的错误的真正原因。

我无法理解为什么expect_before会引起这种行为,更不用说我可以做什么来修复它了。

使用expect -d并没有给我带来任何启示。

$ docker run test-expect Martin 5
expect version 5.45.4
argv[0] = /usr/bin/expect  argv[1] = -df  argv[2] = /root/test.expect  argv[3] = Martin  argv[4] = 5
set argc 2
set argv0 "/root/test.expect"
set argv "Martin 5"
executing commands from command file /root/test.expect
--- Start
spawn bash -c echo Hello Martin && sleep 5
parent: waiting for sync byte
parent: telling child to go ahead
parent: now unsynchronized from child
spawn: returns {7}
expect: read eof
expect: set expect_out(spawn_id) "exp0"
expect: set expect_out(buffer) ""
error writing "stdout": bad file number
    while executing
"puts "--- End""
    (file "/root/test.expect" line 15)
英文:

I am getting an error when I run an expect script inside a docker container.

First I created a simple expect script test.expect

#!/usr/bin/expect -f 

set timeout 3
#expect_before timeout { puts "\nTimeout fired"; exit 1 }

puts "--- Start"
lassign $argv name duration

spawn bash -c "echo Hello ${name} && sleep ${duration} "
expect {
    eof {
        puts "No timeout";
    }
}
puts "--- End"

Note: the expect_before is commented out at first

Now the docker image Dockerfile

FROM alpine:latest

RUN apk update && \
    apk upgrade && \
    apk add bash expect

COPY test.expect /root
ENV PATH $PATH:/root

WORKDIR /root
ENTRYPOINT ["test.expect"]

Now build and run the docker:

$ docker build -t test-expect .
$ docker run -i test-expect Martin 2
--- Start
spawn bash -c echo Hello Martin && sleep 2
Hello Martin
No timeout
--- End
$ docker run -i test-expect Martin 5
--- Start
spawn bash -c echo Hello Martin && sleep 5
Hello Martin
--- End
$ docker run test-expect Martin 2
--- Start
spawn bash -c echo Hello Martin && sleep 2
Hello Martin
No timeout
--- End
$ docker run test-expect Martin 5
--- Start
spawn bash -c echo Hello Martin && sleep 5
Hello Martin
--- End

Both paths through the script work fine with or without docker -i (--interactive). Now I uncomment the expect_before. Rebuild and rerun:

$ docker build -t test-expect .
$ docker run -i test-expect Martin 2
--- Start
spawn bash -c echo Hello Martin && sleep 2
Hello Martin
No timeout
--- End
$ docker run -i test-expect Martin 5
--- Start
spawn bash -c echo Hello Martin && sleep 5
Hello Martin

Timeout fired
$ docker run test-expect Martin 2
--- Start
spawn bash -c echo Hello Martin && sleep 2
error writing "stdout": bad file number
    while executing
"puts "--- End""
    (file "/root/test.expect" line 15)
$ docker run test-expect Martin 5
--- Start
spawn bash -c echo Hello Martin && sleep 5
error writing "stdout": bad file number
    while executing
"puts "--- End""
    (file "/root/test.expect" line 15)

Naturally when I first saw this in my actual pipeline (GitHub workflows, calling Actions, running large containers, with convoluted scripts, that need my actual expect script) I assumed it was related to the fact that expect was being run in an environment where there was no stdin and or stdout. It wasn't until I eventually created this much simpler test case did I find the actual cause of my error.

I cannot fathom why expect_before causes this behaviour, let alone what I can do to fix it.

Running expect with -d did not give me any insight

$ docker run test-expect Martin 5
expect version 5.45.4
argv[0] = /usr/bin/expect  argv[1] = -df  argv[2] = /root/test.expect  argv[3] = Martin  argv[4] = 5
set argc 2
set argv0 "/root/test.expect"
set argv "Martin 5"
executing commands from command file /root/test.expect
--- Start
spawn bash -c echo Hello Martin && sleep 5
parent: waiting for sync byte
parent: telling child to go ahead
parent: now unsynchronized from child
spawn: returns {7}
expect: read eof
expect: set expect_out(spawn_id) "exp0"
expect: set expect_out(buffer) ""
error writing "stdout": bad file number
    while executing
"puts "--- End""
    (file "/root/test.expect" line 15)

答案1

得分: 2

I stumbled upon the actual answer!

> Rather than add all the alternatives into every expect command in your program, you can make use of expect_before and expect_after. These allow you to set up alternative sequences that every subsequent expect command in the currently spawned process will react to in addition to the ones that you've explicitly written into the expect command. Where the returned sequence from the spawned command matches several alternatives, the order of priority taken is
>
> ~~~
> expect_before
> expect
> expect_after
> ~~~
>
> Now - one word of caution. You should specify any expect_before and expect_after commands AFTER you have spawned the process you wish to control. Expect can control multiple spawned processes, and if you specify your _befores and _afters at the wrong point, you can end up applying them to the wrong process.
>
> If you have no spawned process at all and you expect_before, you're liable to get erratic results - for example, an application which runs interactively but fails from crontab, or one which works on one release of an operating system but fails on the next release. Very hard problem to identify if you're not aware of the risk!

So the fix is really simple:

#!/usr/bin/expect -f 

set timeout 3

puts "--- Start"
lassign $argv name duration

spawn bash -c "echo Hello ${name} && sleep ${duration}"
expect_before timeout { puts "\nTimeout fired"; exit 1 }
expect {
    eof {
        puts "No timeout";
    }
}
puts "--- End"

And now it always works regardless of the -i flag

$ docker run -i test-expect Martin 2
--- Start
spawn bash -c echo Hello Martin && sleep 2
Hello Martin
No timeout
--- End
$ docker run -i test-expect Martin 5
--- Start
spawn bash -c echo Hello Martin && sleep 5
Hello Martin

Timeout fired
$ docker run test-expect Martin 2
--- Start
spawn bash -c echo Hello Martin && sleep 2
Hello Martin
No timeout
--- End
$ docker run test-expect Martin 5
--- Start
spawn bash -c echo Hello Martin && sleep 5
Hello Martin

Timeout fired
英文:

I stumbled upon the actual answer!

> Rather than add all the alternatives into every expect command in your program, you can make use of expect_before and expect_after. These allow you to set up alternative sequences that every subsequent expect command in the currently spawned process will react to in addition to the ones that you've explicitly written into the expect command. Where the returned sequence from the spawned command matches several alternatives, the order of priority taken is
>
> ~~~
> expect_before
> expect
> expect_after
> ~~~
>
> Now - one word of caution. You should specify any expect_before and expect_after commands AFTER you have spawned the process you wish to control. Expect can control multiple spawned processes, and if you specify your _befores and _afters at the wrong point, you can end up applying them to the wrong process.
>
> If you have no spawned process at all and you expect_before, you're liable to get erratic results - for example, an application which runs interactively but fails from crontab, or one which works on one release of an operating system but fails on the next release. Very hard problem to identify if you're not aware of the risk!

So the fix is really simple:

#!/usr/bin/expect -f 

set timeout 3

puts "--- Start"
lassign $argv name duration

spawn bash -c "echo Hello ${name} && sleep ${duration} "
expect_before timeout { puts "\nTimeout fired"; exit 1 }
expect {
    eof {
        puts "No timeout";
    }
}
puts "--- End"

And now it always works regardless of the -i flag

$ docker run -i test-expect Martin 2
--- Start
spawn bash -c echo Hello Martin && sleep 2
Hello Martin
No timeout
--- End
$ docker run -i test-expect Martin 5
--- Start
spawn bash -c echo Hello Martin && sleep 5
Hello Martin

Timeout fired
$ docker run test-expect Martin 2
--- Start
spawn bash -c echo Hello Martin && sleep 2
Hello Martin
No timeout
--- End
$ docker run test-expect Martin 5
--- Start
spawn bash -c echo Hello Martin && sleep 5
Hello Martin

Timeout fired

huangapple
  • 本文由 发表于 2023年3月31日 18:26:23
  • 转载请务必保留本文链接:https://go.coder-hub.com/75897455.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定