使用Golang docker SDK将用户输入os.stdin接受到容器-交互式容器 [英] Accept user Input os.stdin to container using Golang docker SDK - Interactive Container

查看:137
本文介绍了使用Golang docker SDK将用户输入os.stdin接受到容器-交互式容器的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我的最后一招是在这里问。我刚接触Golang,并编写了简单的程序。

My last resort is asking here. I'm new to Golang and I've made simple programs.

我正在尝试执行以下操作:
使用golang:
1-运行容器
2-接受输入stdin到容器

I'm trying to do the following: Using golang: 1 - run a container 2 - accept input stdin to the container

我要使用的示例是hashicorp / terraform docker映像,我想做一个简单的 terraform apply 但我需要等待用户输入

The example I want to use is the hashicorp/terraform docker image, I want to do a simple terraform apply but I need to wait for user input

下面是到目前为止我正在使用的代码...尝试以下确切代码的任何人都需要更新AWS环境变量或将terraform测试文件更改为其他提供程序...或仅使用其他docker镜像;-)

below is the code I have working so far...anyone trying the exact code below needs to update the AWS environment variables or change the terraform test file to another provider...or just use a different docker image ;-)

package main

import (
    "fmt"
    "github.com/docker/docker/api/types"
    "github.com/docker/docker/api/types/container"
    "github.com/docker/docker/api/types/mount"
    "github.com/docker/docker/client"
    "github.com/docker/docker/pkg/stdcopy"
    "golang.org/x/net/context"
    "io"
    "os"
)

const workingDir = "/home"


func main() {
    ctx := context.Background()
    cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
    if err != nil {
        panic(err)
    }

    reader, err := cli.ImagePull(ctx, "hashicorp/terraform", types.ImagePullOptions{})
    if err != nil {
        panic(err)
    }
    io.Copy(os.Stdout, reader)

    fmt.Println(os.Args)
    cwd, _ := os.Getwd()

    resp, err := cli.ContainerCreate(ctx, &container.Config{
        AttachStdin: true,
        Tty:         false,
        StdinOnce:   true,
        AttachStdout:true,
        Cmd:         os.Args[1:],
        Image:       "hashicorp/terraform",
        WorkingDir:   workingDir,
        Env:         []string{"AWS_ACCESS_KEY_ID=XXX", "AWS_SECRET_ACCESS_KEY=XXX", "AWS_SESSION_TOKEN=XXX"},
        },
    &container.HostConfig{

            Mounts: []mount.Mount{
                mount.Mount{
                    Type: mount.TypeBind,
                    Source: cwd,
                    Target: workingDir,
                },
            },
        },nil, "")
    if err != nil {
        panic(err)
    }

    if err := cli.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{}); err != nil {
        panic(err)
    }

    statusCh, errCh := cli.ContainerWait(ctx, resp.ID, container.WaitConditionNotRunning)
    select {
    case err := <-errCh:
        if err != nil {
            panic(err)
        }
    case <-statusCh:
    }




    out, err := cli.ContainerLogs(ctx, resp.ID, types.ContainerLogsOptions{ShowStdout: true})
    if err != nil {
        panic(err)
    }

    stdcopy.StdCopy(os.Stdout, os.Stderr, out)


}

我的示例Terraform文件test.tf

My example terraform file test.tf

provider "aws" {
  region                  = "eu-west-1"
}


resource "aws_vpc" "main" {
  cidr_block       = "10.0.0.0/16"
  instance_tenancy = "dedicated"

  tags = {
    Name = "test-main-vpc"
  }
}

因此,如果我建立那去文件和使用test.tf在同一目录中运行

so if I build that go file and run something like

./ build apply

我得到以下输出:


An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # aws_vpc.main will be created
  + resource "aws_vpc" "main" {
      + arn                              = (known after apply)
      + assign_generated_ipv6_cidr_block = false
      + cidr_block                       = "10.0.0.0/16"
      + default_network_acl_id           = (known after apply)
      + default_route_table_id           = (known after apply)
      + default_security_group_id        = (known after apply)
      + dhcp_options_id                  = (known after apply)
      + enable_classiclink               = (known after apply)
      + enable_classiclink_dns_support   = (known after apply)
      + enable_dns_hostnames             = (known after apply)
      + enable_dns_support               = true
      + id                               = (known after apply)
      + instance_tenancy                 = "dedicated"
      + ipv6_association_id              = (known after apply)
      + ipv6_cidr_block                  = (known after apply)
      + main_route_table_id              = (known after apply)
      + owner_id                         = (known after apply)
      + tags                             = {
          + "Name" = "test-main-vpc"
        }
    }

Plan: 1 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: 
Apply cancelled.

Process finished with exit code 0

我要做什么我一直想弄清楚如何等待用户输入。

What I've been trying to figure out is how to wait for user input.

我认为容器运行并退出后日志正在打印。所以我认为我需要使用以下各项的组合:

The logs are printing after the container is run and exits I think.. so I believe I need to use a mixture of these:

https://godoc.org/github.com/docker/docker/container/stream

https://godoc.org/github.com/docker/docker/client#Client.ContainerAttach

我只是不知道如何实现这些功能,因此没有示例。

I just dont know how to implement these and there are no examples.

任何想法都会有所帮助。我不想要完整的答案,我只是想知道如何使用容器/流和/或Client.ContainerAttach来等待用户输入的一般方向

Any ideas would be helpful. I dont want the full answer I just want the general direction on how to use the container/stream and/or Client.ContainerAttach to wait for user input

编辑:

我设法使其正常运行。以下是工作代码

I've managed to get it working. Below is the working code

package main

import (
    "bufio"
    "fmt"
    "github.com/docker/docker/api/types"
    "github.com/docker/docker/api/types/container"
    "github.com/docker/docker/api/types/mount"
    "github.com/docker/docker/client"
    "golang.org/x/net/context"
    "io"
    "os"
)

const workingDir = "/home"
var inout chan []byte

func main() {
    inout = make(chan []byte)

    ctx := context.Background()
    cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
    if err != nil {
        panic(err)
    }

    reader, err := cli.ImagePull(ctx, "hashicorp/terraform", types.ImagePullOptions{})
    if err != nil {
        panic(err)
    }
    go io.Copy(os.Stdout, reader)


    //fmt.Println(os.Args)
    cwd, _ := os.Getwd()

    resp, err := cli.ContainerCreate(ctx, &container.Config{
        AttachStderr:true,
        AttachStdin: true,
        Tty:         true,
        AttachStdout:true,
        OpenStdin:   true,
        Cmd:         os.Args[1:],
        Image:       "hashicorp/terraform",
        WorkingDir:   workingDir,
        Env:         []string{"AWS_ACCESS_KEY_ID=",
            "AWS_SECRET_ACCESS_KEY=",
            "AWS_SESSION_TOKEN=",

        },
    },
        &container.HostConfig{

            Mounts: []mount.Mount{
                mount.Mount{
                    Type: mount.TypeBind,
                    Source: cwd,
                    Target: workingDir,
                },
            },
        },nil, "")
    if err != nil {
        panic(err)
    }


    if err := cli.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{}); err != nil {
        panic(err)
    }



    waiter, err := cli.ContainerAttach(ctx, resp.ID, types.ContainerAttachOptions{
        Stderr:       true,
        Stdout:       true,
        Stdin:        true,
        Stream:       true,
    })

    go  io.Copy(os.Stdout, waiter.Reader)
    go  io.Copy(os.Stderr, waiter.Reader)

    if err != nil {
        panic(err)
    }

    go func() {
        scanner := bufio.NewScanner(os.Stdin)
        for scanner.Scan() {
            inout <- []byte(scanner.Text())
        }
    }()

    // Write to docker container
    go func(w io.WriteCloser) {
        for {
            data, ok := <-inout
            //log.Println("Received to send to docker", string(data))
            if !ok {
                fmt.Println("!ok")
                w.Close()
                return
            }

            w.Write(append(data, '\n'))
        }
    }(waiter.Conn)


    statusCh, errCh := cli.ContainerWait(ctx, resp.ID, container.WaitConditionNotRunning)
    select {
    case err := <-errCh:
        if err != nil {
            panic(err)
        }
    case <-statusCh:
    }


}


推荐答案

对您的问题的编辑非常有帮助,所以我只想更新对我有用的内容。

The edit to your question was super helpful so I just wanted to update with what had worked for me.

我在使用您的Stdin解决方案时遇到了问题(与Scanner和waiter.Conn有关),并接受nicerobot的建议使用Docker.cli源代码中使用的io.Copy。

I had problems using your solution for Stdin (pertaining to Scanner and waiter.Conn) and took nicerobot's advice to use io.Copy which is used in Docker's cli source code.

为了获得终端感觉(消除标准输入,自动完成等的回声),我引用了以下线程: https://github.com/fsouza/go-dockerclient/issues/707 wh ich基本上说过,您必须将stdin视为原始终端(后来意识到这也是在Docker的cli源代码中完成的)。同样重要的是要注意,您必须尽快恢复终端状态,否则您的其他打印语句可能会出现问题。

To get the terminal feel (removing the echo from stdin, autocomplete, etc), I referenced this thread: https://github.com/fsouza/go-dockerclient/issues/707 which basically said you have to treat stdin as a raw terminal (later realized this is also done in Docker's cli source code). It's also important to note that you have to restore the terminal state as soon as possible, otherwise your other print statements may be wonky.

最后,我的最终代码如下所示。 ..

In the end, my final code looks something like this...

// ContainerCreate
    
// ContainerAttach

go io.Copy(os.Stdout, waiter.Reader)
go io.Copy(os.Stderr, waiter.Reader)
go io.Copy(waiter.Conn, os.Stdin)

// ContainerStart

// import("golang.org/x/net/context")
fd := int(os.Stdin.Fd())
var oldState *terminal.State
if terminal.IsTerminal(fd) {
  oldState, err = terminal.MakeRaw(fd)
  if err != nil {
    // print error
  }
  defer terminal.Restore(fd, oldState)
}

// ContainerWait for exit

terminal.Restore(fd, oldState)

这篇关于使用Golang docker SDK将用户输入os.stdin接受到容器-交互式容器的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆