【发布时间】:2020-03-03 01:56:47
【问题描述】:
我最后的手段是在这里问。我是 Golang 的新手,我制作了简单的程序。
我正在尝试执行以下操作: 使用 golang: 1 - 运行一个容器 2 - 接受容器的输入标准输入
我要使用的例子是hashicorp/terraform docker镜像,我想做一个简单的terraform apply但是我需要等待用户输入
以下是我迄今为止工作的代码...任何尝试以下确切代码的人都需要更新 AWS 环境变量或将 terraform 测试文件更改为另一个提供商...或者只使用不同的 docker 映像;-)
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
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"
}
}
所以如果我构建那个 go 文件并运行类似的东西
./build apply与test.tf在同一目录
我得到以下输出:
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
我一直想弄清楚的是如何等待用户输入。
我认为在容器运行并退出后日志正在打印..所以我相信我需要混合使用这些:
https://godoc.org/github.com/docker/docker/container/stream
https://godoc.org/github.com/docker/docker/client#Client.ContainerAttach
我只是不知道如何实现这些并且没有示例。
任何想法都会有所帮助。我不想要完整的答案我只想要关于如何使用容器/流和/或 Client.ContainerAttach 来等待用户输入的大致方向
非常感谢!
编辑:
我已经设法让它工作了。下面是工作代码
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:
}
}
【问题讨论】:
-
对于 Terraform 等高度交互的程序,在 Docker 中运行它们并从其他程序中驱动它们都会增加显着的复杂性。您还将无法访问仅存在于主机上的 AWS 凭证等内容。由于 Terraform 是作为单个二进制文件分发的,因此安装它并不复杂;我会直接运行
terraform apply而不会尝试将其包装在任何东西中。 -
我使用 docker 运行良好,但我正在做的是在 golang 中运行 bash/shell 命令。这很好用,我用 aws mfa 很好地工作。我只是想用编程方式替换 docker shell 命令来运行交互式 docker 映像
-
而不是那些
Scanner和waiter.Conngoroutines,为什么不只是:go io.Copy(waiter.Conn, os.Stdin) -
除了复制标准输入和标准输出之外,
StdinOnce: true设置允许我在交互模式下保持正在运行的容器。但是,如果您有执行会话,则不需要这样做。
标签: docker go sdk terraform stdin