Skip to content

Commit

Permalink
feat: hypervisor run now supports nginx-proxy
Browse files Browse the repository at this point in the history
  • Loading branch information
moul committed Aug 22, 2019
1 parent 615832a commit f828382
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 28 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ module pathwar.land

require (
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect
github.com/GlenDC/go-external-ip v0.0.0-20170425150139-139229dcdddd
github.com/Microsoft/go-winio v0.4.11 // indirect
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect
github.com/brianvoe/gofakeit v3.17.0+incompatible
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7O
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/GlenDC/go-external-ip v0.0.0-20170425150139-139229dcdddd h1:rSJam98YSpHfphLFH6XKBPkYwMbXTkxtmgRbrS+9qek=
github.com/GlenDC/go-external-ip v0.0.0-20170425150139-139229dcdddd/go.mod h1:hv0fvWlrvguh97IboNbAyU1MwG+VpWUODlzwG5wNtrA=
github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
github.com/Microsoft/go-winio v0.4.11 h1:zoIOcVf0xPN1tnMVbTtEdI+P8OofVk3NObnwOQ6nK2Q=
github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
Expand Down
108 changes: 80 additions & 28 deletions hypervisor/cmd_hypervisor_run.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,13 @@ import (
"fmt"
"io"
"os"
"strings"
"time"

externalip "github.com/GlenDC/go-external-ip"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/api/types/strslice"
"github.com/docker/docker/client"
"github.com/docker/go-connections/nat"
Expand All @@ -28,11 +31,14 @@ import (
)

type runOptions struct {
target string `mapstructure:"target"`
webPort int `mapstructure:"web-port"`
detach bool `mapstructure:"detach"`
target string `mapstructure:"target"`
webPort int `mapstructure:"web-port"`
detach bool `mapstructure:"detach"`
nginxProxy bool `mapstructure:"nginx-proxy"`
nginxProxyNetwork string `mapstructure:"nginx-proxy-network"`
nginxProxyHost string `mapstructure:"nginx-proxy-host"`
// tcpPort, udpPort
// pull
// port, other options
// driver=docker
}

Expand Down Expand Up @@ -69,64 +75,105 @@ func (cmd *runCommand) ParseFlags(flags *pflag.FlagSet) {
flags.StringVarP(&cmd.opts.target, "target", "t", "", "target (image, path, etc)")
flags.IntVarP(&cmd.opts.webPort, "web-port", "p", -1, "web listening port (random if unset)")
flags.BoolVarP(&cmd.opts.detach, "detach", "d", false, "detach mode")
flags.BoolVarP(&cmd.opts.nginxProxy, "nginx-proxy", "", false, "use nginx-proxy instead of exposing web port")
flags.StringVarP(&cmd.opts.nginxProxyNetwork, "nginx-proxy-network", "", "service-proxy", "network name for nginx-proxy")
flags.StringVarP(&cmd.opts.nginxProxyHost, "nginx-proxy-host", "", "", "host name for nginx-proxy (use nip.io if empty)")
if err := viper.BindPFlags(flags); err != nil {
zap.L().Warn("failed to bind viper flags", zap.Error(err))
}
}

func runRun(opts runOptions) error {
// FIXME: ensure proxy is setup with xip.io as default hostname
// FIXME: ensure proxy is setup with nip.io as default hostname
ctx := context.Background()
cli, err := client.NewClientWithOpts(client.FromEnv)
if err != nil {
return errors.Wrap(err, "failed to create docker client")
}
zap.L().Debug("connected to Docker daemon",
zap.String("host", cli.DaemonHost()),
zap.String("version", cli.ClientVersion()),
)

// configure new container
zap.L().Debug("inspecting image", zap.String("target", opts.target))
imageInspect, _, err := cli.ImageInspectWithRaw(ctx, opts.target)
if err != nil {
return errors.Wrap(err, "failed to inspect image")
}
env := []string{"PATHWAR_HYPERVISOR=1"} // FIXME: use version
if opts.nginxProxy {
if opts.nginxProxyHost == "" {
consensus := externalip.DefaultConsensus(nil, nil)
ip, err := consensus.ExternalIP()
if err != nil {
return errors.Wrap(err, "failed to guess external IP, please specify --nginx-proxy-host instead")
}
opts.nginxProxyHost = fmt.Sprintf(
"%s.%s.nip.io",
strings.ToLower(randstring.RandString(10)),
ip.String(),
)
zap.L().Info("container is configured to use nginx-proxy",
zap.String("host", fmt.Sprintf("http://%s", opts.nginxProxyHost)),
)
}
env = append(env, fmt.Sprintf("VIRTUAL_HOST=%s", opts.nginxProxyHost))
}
containerWebPort := nat.Port("80/tcp")
exposedPorts := nat.PortSet{}
exposedPorts[containerWebPort] = struct{}{} // FIXME: autodetect source port or allow override
containerConfig := &container.Config{
Image: opts.target,
Tty: true,
OpenStdin: true,
// StdinOnce: true,
// AttachStdin: true,
Image: opts.target,
Tty: true,
OpenStdin: true,
AttachStdout: true,
AttachStderr: true,
ExposedPorts: nat.PortSet{
nat.Port("80/tcp"): {},
},
ExposedPorts: exposedPorts,
Env: env,
Entrypoint: strslice.StrSlice{"/bin/pwctl", "entrypoint"},
Cmd: append(imageInspect.Config.Entrypoint, imageInspect.Config.Cmd...),
Labels: map[string]string{createdByPathwarLabel: "true"},
// StdinOnce: true,
// AttachStdin: true,
// Hostname: ""
Entrypoint: strslice.StrSlice{"/bin/pwctl", "entrypoint"},
Cmd: append(imageInspect.Config.Entrypoint, imageInspect.Config.Cmd...),
Labels: map[string]string{createdByPathwarLabel: "true"},
}
hostPort := fmt.Sprintf("%d", opts.webPort)
if opts.webPort == -1 {
hostPort = ""
}
portBindings := nat.PortMap{}
if !opts.nginxProxy {
portBindings[containerWebPort] = []nat.PortBinding{
{HostIP: "0.0.0.0", HostPort: hostPort},
}
}
hostConfig := &container.HostConfig{
// Binds: /etc/timezone
PortBindings: nat.PortMap{
nat.Port("80/tcp"): []nat.PortBinding{
{HostIP: "0.0.0.0", HostPort: hostPort},
},
},
AutoRemove: true,
// RestartPolicy: "no"
PortBindings: portBindings,
Binds: []string{"/etc/timezone:/etc/timezone:ro"},
AutoRemove: true,
// Dns
// DnsOptions
// DnsSearch
}
//networkingConfig := &network.NetworkingConfig{SandboxID:"XXX",SandboxKey:"XXX"}

if opts.detach {
hostConfig.RestartPolicy = container.RestartPolicy{Name: "unless-stopped", MaximumRetryCount: 42}
hostConfig.AutoRemove = false
}
networkingConfig := &network.NetworkingConfig{
EndpointsConfig: map[string]*network.EndpointSettings{},
}
if opts.nginxProxy {
networkingConfig.EndpointsConfig[opts.nginxProxyNetwork] = &network.EndpointSettings{}
}
// FIXME: create a limited network config
// FIXME: restrict resources (cgroups, etc.)
// FIXME: configure env

cont, err := cli.ContainerCreate(ctx, containerConfig, hostConfig, nil, "")
zap.L().Debug("creating container",
zap.Any("container-config", containerConfig),
zap.Any("host-config", hostConfig),
)
cont, err := cli.ContainerCreate(ctx, containerConfig, hostConfig, networkingConfig, "")
if err != nil {
return err
}
Expand Down Expand Up @@ -179,6 +226,7 @@ func runRun(opts runOptions) error {
if err := tw.Close(); err != nil {
return err
}
zap.L().Debug("injecting pwctl into the container", zap.String("container-id", cont.ID))
if err := cli.CopyToContainer(
ctx,
cont.ID,
Expand All @@ -192,6 +240,7 @@ func runRun(opts runOptions) error {
// start container
ctxCancel, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
zap.L().Debug("starting container", zap.String("container-id", cont.ID))
if err := cli.ContainerStart(ctxCancel, cont.ID, types.ContainerStartOptions{}); err != nil {
return err
}
Expand All @@ -208,6 +257,7 @@ func runRun(opts runOptions) error {
}*/

// read logs
zap.L().Debug("connecting to container logs", zap.String("container-id", cont.ID))
stream, err := cli.ContainerLogs(ctx, cont.ID, types.ContainerLogsOptions{
ShowStdout: true,
ShowStderr: true,
Expand All @@ -222,10 +272,12 @@ func runRun(opts runOptions) error {

// cleanup
duration := 50 * time.Millisecond
zap.L().Debug("stopping container", zap.String("container-id", cont.ID), zap.Duration("timeout", duration))
if err := cli.ContainerStop(ctx, cont.ID, &duration); err != nil {
return errors.Wrap(err, "failed to stop container")
}

zap.L().Debug("removing container", zap.String("container-id", cont.ID))
if err := cli.ContainerRemove(ctx, cont.ID, types.ContainerRemoveOptions{
RemoveVolumes: true,
RemoveLinks: true,
Expand Down

0 comments on commit f828382

Please sign in to comment.