Docker Compose 如何解析 `command` 字符串?

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

How docker-compose parse the `command` string?

问题

I ran the command:

  1. docker-compose -p foo -f - up <<YAML
  2. version: "3.8"
  3. services:
  4. main:
  5. image: alpine
  6. command: echo x && y
  7. YAML

Expected

  1. [+] Running 1/0
  2. Container foo-main-1 Created 0.0s
  3. Attaching to foo-main-1
  4. foo-main-1 | x && y
  5. foo-main-1 exited with code 0

Actual

  1. [+] Running 1/0
  2. Container foo-main-1 Created 0.0s
  3. Attaching to foo-main-1
  4. foo-main-1 | x
  5. foo-main-1 exited with code 0

I know CMD of Dockerfile supports exec form and shell form and expect docker-compose.yml would behave like Dockerfile, however, looks like docker-compose uses some hacky way to pass the command string.

command output
echo x y z x y z
echo x && y x && y
echo x | y x
echo x; y x; y

Question

  1. Does docker-compose simply use a regular expression to remove everything after ;, &&, and |?
  2. If not, how does docker-compose process the command string?

I would appreciate if you can also provide the source code reference of which line parsing command string.

https://github.com/docker/compose

My Environment

  1. λ docker -v
  2. Docker version 20.10.22, build 3a2c30b
  3. λ docker-compose -v
  4. Docker Compose version v2.15.1
英文:

I ran the command:

  1. docker-compose -p foo -f - up &lt;&lt;YAML
  2. version: &quot;3.8&quot;
  3. services:
  4. main:
  5. image: alpine
  6. command: echo x &amp;&amp; y
  7. YAML

Expected

  1. [+] Running 1/0
  2. Container foo-main-1 Created 0.0s
  3. Attaching to foo-main-1
  4. foo-main-1 | x &amp;&amp; y
  5. foo-main-1 exited with code 0

Actual

  1. [+] Running 1/0
  2. Container foo-main-1 Created 0.0s
  3. Attaching to foo-main-1
  4. foo-main-1 | x
  5. foo-main-1 exited with code 0

I know CMD of Dockerfile supports exec form and shell form and expect docker-compose.yml would behave like Dockerfile, however, looks like docker-compose uses some hacky way to pass the command string.

command output
echo x y z x y z
echo x && y x
echo x | y x
echo x; y x

Question

  1. Does docker-compose simply use a regular expression to remove everything after ;, &amp;&amp; and |?
  2. If not, how does docker-compose process the command string?

I would appreciate if you can also provide the source code reference of which line parsing command string.

https://github.com/docker/compose

My Environment

  1. λ docker -v
  2. Docker version 20.10.22, build 3a2c30b
  3. λ docker-compose -v
  4. Docker Compose version v2.15.1

答案1

得分: 2

Docker Compose V2已经用Go重写,它解析'command'的方式与V1不同。Docker Compose V2使用Go包'go-shellwords'来解析命令行,特定的特殊字符会被处理,比如:

  1. \`)(&quot;;&amp;|&lt;&gt;

对于字符串'echo x && y','&'字符会中断Parser()循环,导致字符串解析结束,输出结果为'echo x'。

此外,shell形式'echo x && y'表示'echo x',然后执行'y'。这可能不是您想要的,所以您可以明确声明命令字符串为'echo "x && y"'或'[ "sh", "-c", "echo x && y" ]',这取决于您的具体需求。

如果您想了解Docker Compose如何解析命令字符串的更多信息,可以参考源代码,特别是:

  1. https://github.com/docker/compose/blob/v2/cmd/compose/compose.go#L187
  1. func (o *ProjectOptions) ToProject(services []string, po ...cli.ProjectOptionsFn) (*types.Project, error) {
  2. ...
  3. project, err := cli.ProjectFromOptions(options)
  4. if err != nil {
  5. ...
  1. https://github.com/compose-spec/compose-go/blob/master/cli/options.go#L381
  1. func ProjectFromOptions(options *ProjectOptions) (*types.Project, error) {
  2. ...
  3. project, err := loader.Load(types.ConfigDetails{
  4. ConfigFiles: configs,
  5. WorkingDir: workingDir,
  6. Environment: options.Environment,
  7. }, options.loadOptions...)
  8. if err is not nil {
  9. return nil, err
  10. }
  1. https://github.com/compose-spec/compose-go/blob/master/loader/loader.go#L1156
  1. var transformShellCommand TransformerFunc = func(value interface{}) (interface{}, error) {
  2. if str, ok := value.(string); ok {
  3. return shellwords.Parse(str)
  4. }
  5. return value, nil
  6. }
  1. https://github.com/mattn/go-shellwords/blob/master/shellwords.go#L125
  1. func (p *Parser) Parse(line string) ([]string, error) {
  2. args := []
  3. buf := ""
  4. var escaped, doubleQuoted, singleQuoted, backQuote, dollarQuote bool
  5. backtick := ""
  6. ...

您可以查看shellswords.go以了解Parse()函数如何解析命令字符串。

英文:

Docker Compose V2 has been rewritten in Go, and the way it parses 'command' is different from V1. Docker Compose V2 uses Go package 'go-shellwords' to parse the command line, with certain special characters being handled specifically, such as:

  1. \`)(&quot;;&amp;|&lt;&gt;

In the case of the string 'echo x && y', the '&' character breaks the Parser() loop and leads to the end of string parsing, resulting in the output terminating in 'echo x'.

Additionally, the shell form 'echo x && y' means 'echo x' and then execute 'y'. This may not be what you intended, so you can explicitly declare the command string as 'echo "x && y"' or '["sh", "-c", "echo x && y"]', depends on your specific needs.

If you're interested in learning more about how command strings are parsed by Docker Compose, you can refer to the source code, specifically:

  1. https://github.com/docker/compose/blob/v2/cmd/compose/compose.go#L187
  1. func (o *ProjectOptions) ToProject(services []string, po ...cli.ProjectOptionsFn) (*types.Project, error) {
  2. ...
  3. project, err := cli.ProjectFromOptions(options)
  4. if err != nil {
  5. ...
  1. https://github.com/compose-spec/compose-go/blob/master/cli/options.go#L381
  1. func ProjectFromOptions(options *ProjectOptions) (*types.Project, error) {
  2. ...
  3. project, err := loader.Load(types.ConfigDetails{
  4. ConfigFiles: configs,
  5. WorkingDir: workingDir,
  6. Environment: options.Environment,
  7. }, options.loadOptions...)
  8. if err != nil {
  9. return nil, err
  10. }
  1. https://github.com/compose-spec/compose-go/blob/master/loader/loader.go#L1156
  1. var transformShellCommand TransformerFunc = func(value interface{}) (interface{}, error) {
  2. if str, ok := value.(string); ok {
  3. return shellwords.Parse(str)
  4. }
  5. return value, nil
  6. }
  1. https://github.com/mattn/go-shellwords/blob/master/shellwords.go#L125
  1. func (p *Parser) Parse(line string) ([]string, error) {
  2. args := []string{}
  3. buf := &quot;&quot;
  4. var escaped, doubleQuoted, singleQuoted, backQuote, dollarQuote bool
  5. backtick := &quot;&quot;
  6. ...

You can take a look at shellswords.go to see how command string is parsed by Parse() function.

huangapple
  • 本文由 发表于 2023年3月9日 17:16:12
  • 转载请务必保留本文链接:https://go.coder-hub.com/75682540.html
匿名

发表评论

匿名网友

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

确定