221 lines
5.1 KiB
Go
221 lines
5.1 KiB
Go
package serverinstall
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"time"
|
|
|
|
"github.com/docker/distribution/reference"
|
|
"github.com/docker/docker/api/types"
|
|
"github.com/docker/docker/api/types/container"
|
|
"github.com/docker/docker/api/types/filters"
|
|
"github.com/docker/docker/api/types/network"
|
|
"github.com/docker/docker/client"
|
|
"github.com/docker/docker/pkg/jsonmessage"
|
|
"github.com/docker/go-connections/nat"
|
|
"github.com/hashicorp/vagrant-plugin-sdk/terminal"
|
|
"github.com/hashicorp/vagrant/internal/clicontext"
|
|
"github.com/hashicorp/vagrant/internal/server/proto/vagrant_server"
|
|
"github.com/hashicorp/vagrant/internal/serverconfig"
|
|
)
|
|
|
|
func InstallDocker(
|
|
ctx context.Context, ui terminal.UI, scfg *Config) (
|
|
*clicontext.Config, *vagrant_server.ServerConfig_AdvertiseAddr, string, error,
|
|
) {
|
|
sg := ui.StepGroup()
|
|
defer sg.Wait()
|
|
|
|
s := sg.Add("Initializing Docker client...")
|
|
defer func() { s.Abort() }()
|
|
|
|
cli, err := client.NewClientWithOpts(client.FromEnv)
|
|
if err != nil {
|
|
return nil, nil, "", err
|
|
}
|
|
cli.NegotiateAPIVersion(ctx)
|
|
|
|
s.Update("Checking for existing installation...")
|
|
|
|
containers, err := cli.ContainerList(ctx, types.ContainerListOptions{
|
|
Filters: filters.NewArgs(filters.KeyValuePair{
|
|
Key: "label",
|
|
Value: "vagrant-type=server",
|
|
}),
|
|
})
|
|
if err != nil {
|
|
return nil, nil, "", err
|
|
}
|
|
|
|
grpcPort := "9701"
|
|
httpPort := "9702"
|
|
|
|
var (
|
|
clicfg clicontext.Config
|
|
addr vagrant_server.ServerConfig_AdvertiseAddr
|
|
httpAddr string
|
|
)
|
|
|
|
clicfg.Server = serverconfig.Client{
|
|
Address: "localhost:" + grpcPort,
|
|
Tls: true,
|
|
TlsSkipVerify: true,
|
|
}
|
|
|
|
addr.Addr = "vagrant-server:" + grpcPort
|
|
addr.Tls = true
|
|
addr.TlsSkipVerify = true
|
|
|
|
httpAddr = "localhost:" + httpPort
|
|
|
|
// If we already have a server, bolt.
|
|
if len(containers) > 0 {
|
|
s.Update("Detected existing Vagrant server.")
|
|
s.Status(terminal.StatusWarn)
|
|
s.Done()
|
|
return &clicfg, &addr, "", nil
|
|
}
|
|
|
|
s.Update("Checking for Docker image: %s", scfg.ServerImage)
|
|
|
|
imageRef, err := reference.ParseNormalizedNamed(scfg.ServerImage)
|
|
if err != nil {
|
|
return nil, nil, "", fmt.Errorf("Error parsing Docker image: %s", err)
|
|
}
|
|
|
|
imageList, err := cli.ImageList(ctx, types.ImageListOptions{
|
|
Filters: filters.NewArgs(filters.KeyValuePair{
|
|
Key: "reference",
|
|
Value: reference.FamiliarString(imageRef),
|
|
}),
|
|
})
|
|
if err != nil {
|
|
return nil, nil, "", err
|
|
}
|
|
|
|
if len(imageList) == 0 {
|
|
s.Update("Pulling image: %s", scfg.ServerImage)
|
|
|
|
resp, err := cli.ImagePull(ctx, reference.FamiliarString(imageRef), types.ImagePullOptions{})
|
|
if err != nil {
|
|
return nil, nil, "", err
|
|
}
|
|
defer resp.Close()
|
|
|
|
stdout, _, err := ui.OutputWriters()
|
|
if err != nil {
|
|
return nil, nil, "", err
|
|
}
|
|
|
|
var termFd uintptr
|
|
if f, ok := stdout.(*os.File); ok {
|
|
termFd = f.Fd()
|
|
}
|
|
|
|
err = jsonmessage.DisplayJSONMessagesStream(resp, s.TermOutput(), termFd, true, nil)
|
|
if err != nil {
|
|
return nil, nil, "", fmt.Errorf("unable to stream pull logs to the terminal: %s", err)
|
|
}
|
|
|
|
s.Done()
|
|
s = sg.Add("")
|
|
}
|
|
|
|
s.Update("Creating vagrant network...")
|
|
|
|
nets, err := cli.NetworkList(ctx, types.NetworkListOptions{
|
|
Filters: filters.NewArgs(filters.Arg("label", "use=vagrant")),
|
|
})
|
|
if err != nil {
|
|
return nil, nil, "", err
|
|
}
|
|
|
|
if len(nets) == 0 {
|
|
_, err = cli.NetworkCreate(ctx, "vagrant", types.NetworkCreate{
|
|
Driver: "bridge",
|
|
CheckDuplicate: true,
|
|
Internal: false,
|
|
Attachable: true,
|
|
Labels: map[string]string{
|
|
"use": "vagrant",
|
|
},
|
|
})
|
|
|
|
if err != nil {
|
|
return nil, nil, "", err
|
|
}
|
|
|
|
}
|
|
|
|
npGRPC, err := nat.NewPort("tcp", grpcPort)
|
|
if err != nil {
|
|
return nil, nil, "", err
|
|
}
|
|
|
|
npHTTP, err := nat.NewPort("tcp", httpPort)
|
|
if err != nil {
|
|
return nil, nil, "", err
|
|
}
|
|
|
|
s.Update("Installing Vagrant server to docker")
|
|
|
|
cfg := container.Config{
|
|
AttachStdout: true,
|
|
AttachStderr: true,
|
|
AttachStdin: true,
|
|
OpenStdin: true,
|
|
StdinOnce: true,
|
|
Image: scfg.ServerImage,
|
|
ExposedPorts: nat.PortSet{npGRPC: struct{}{}, npHTTP: struct{}{}},
|
|
Env: []string{"PORT=" + grpcPort},
|
|
Cmd: []string{"server", "run", "-accept-tos", "-vvv", "-db=/data/data.db", "-listen-grpc=0.0.0.0:9701", "-listen-http=0.0.0.0:9702"},
|
|
}
|
|
|
|
bindings := nat.PortMap{}
|
|
bindings[npGRPC] = []nat.PortBinding{
|
|
{
|
|
HostPort: grpcPort,
|
|
},
|
|
}
|
|
bindings[npHTTP] = []nat.PortBinding{
|
|
{
|
|
HostPort: httpPort,
|
|
},
|
|
}
|
|
hostconfig := container.HostConfig{
|
|
Binds: []string{"vagrant-server:/data"},
|
|
PortBindings: bindings,
|
|
}
|
|
|
|
netconfig := network.NetworkingConfig{
|
|
EndpointsConfig: map[string]*network.EndpointSettings{
|
|
"vagrant": {},
|
|
},
|
|
}
|
|
|
|
cfg.Labels = map[string]string{
|
|
"vagrant-type": "server",
|
|
}
|
|
|
|
cr, err := cli.ContainerCreate(ctx, &cfg, &hostconfig, &netconfig, "vagrant-server")
|
|
if err != nil {
|
|
return nil, nil, "", err
|
|
}
|
|
|
|
err = cli.ContainerStart(ctx, cr.ID, types.ContainerStartOptions{})
|
|
if err != nil {
|
|
return nil, nil, "", err
|
|
}
|
|
|
|
// KLUDGE: There isn't a way to find out if the container is up or not,
|
|
// so we just give it 5 seconds to normalize before trying to use it.
|
|
time.Sleep(5 * time.Second)
|
|
|
|
s.Done()
|
|
s = sg.Add("Server container started!")
|
|
s.Done()
|
|
|
|
return &clicfg, &addr, httpAddr, nil
|
|
}
|