Merge pull request #11 from xetorthio/allow_any_image

Allow to launch instances with any kind of public image.
This commit is contained in:
Jonathan Leibiusky
2017-06-05 09:35:39 -03:00
committed by GitHub
8 changed files with 236 additions and 25 deletions

View File

@@ -14,10 +14,12 @@ import (
"strings" "strings"
"time" "time"
"github.com/docker/distribution/reference"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/network" "github.com/docker/docker/api/types/network"
"github.com/docker/docker/client" "github.com/docker/docker/client"
"github.com/docker/docker/pkg/jsonmessage"
) )
const ( const (
@@ -190,6 +192,7 @@ type CreateContainerOpts struct {
ServerCert []byte ServerCert []byte
ServerKey []byte ServerKey []byte
CACert []byte CACert []byte
Privileged bool
} }
func (d *docker) CreateContainer(opts CreateContainerOpts) (string, error) { func (d *docker) CreateContainer(opts CreateContainerOpts) (string, error) {
@@ -219,7 +222,7 @@ func (d *docker) CreateContainer(opts CreateContainerOpts) (string, error) {
h := &container.HostConfig{ h := &container.HostConfig{
NetworkMode: container.NetworkMode(opts.SessionId), NetworkMode: container.NetworkMode(opts.SessionId),
Privileged: true, Privileged: opts.Privileged,
AutoRemove: true, AutoRemove: true,
LogConfig: container.LogConfig{Config: map[string]string{"max-size": "10m", "max-file": "1"}}, LogConfig: container.LogConfig{Config: map[string]string{"max-size": "10m", "max-file": "1"}},
} }
@@ -257,7 +260,18 @@ func (d *docker) CreateContainer(opts CreateContainerOpts) (string, error) {
container, err := d.c.ContainerCreate(context.Background(), cf, h, networkConf, opts.ContainerName) container, err := d.c.ContainerCreate(context.Background(), cf, h, networkConf, opts.ContainerName)
if err != nil { if err != nil {
return "", err if client.IsErrImageNotFound(err) {
log.Printf("Unable to find image '%s' locally\n", opts.Image)
if err = d.pullImage(context.Background(), opts.Image); err != nil {
return "", err
}
container, err = d.c.ContainerCreate(context.Background(), cf, h, networkConf, opts.ContainerName)
if err != nil {
return "", err
}
} else {
return "", err
}
} }
if err := d.copyIfSet(opts.ServerCert, "cert.pem", containerCertDir, opts.ContainerName); err != nil { if err := d.copyIfSet(opts.ServerCert, "cert.pem", containerCertDir, opts.ContainerName); err != nil {
@@ -283,6 +297,28 @@ func (d *docker) CreateContainer(opts CreateContainerOpts) (string, error) {
return cinfo.NetworkSettings.Networks[opts.SessionId].IPAddress, nil return cinfo.NetworkSettings.Networks[opts.SessionId].IPAddress, nil
} }
func (d *docker) pullImage(ctx context.Context, image string) error {
_, err := reference.ParseNormalizedNamed(image)
if err != nil {
return err
}
options := types.ImageCreateOptions{}
responseBody, err := d.c.ImageCreate(ctx, image, options)
if err != nil {
return err
}
defer responseBody.Close()
return jsonmessage.DisplayJSONMessagesStream(
responseBody,
os.Stderr,
os.Stdout.Fd(),
false,
nil)
}
func (d *docker) copyIfSet(content []byte, fileName, path, containerName string) error { func (d *docker) copyIfSet(content []byte, fileName, path, containerName string) error {
if len(content) > 0 { if len(content) > 0 {
return d.CopyToContainer(containerName, path, fileName, bytes.NewReader(content)) return d.CopyToContainer(containerName, path, fileName, bytes.NewReader(content))

View File

@@ -10,6 +10,9 @@ type checkSwarmStatusTask struct {
} }
func (c checkSwarmStatusTask) Run(i *Instance) error { func (c checkSwarmStatusTask) Run(i *Instance) error {
if i.docker == nil {
return nil
}
if info, err := i.docker.GetDaemonInfo(); err == nil { if info, err := i.docker.GetDaemonInfo(); err == nil {
if info.Swarm.LocalNodeState != swarm.LocalNodeStateInactive && info.Swarm.LocalNodeState != swarm.LocalNodeStateLocked { if info.Swarm.LocalNodeState != swarm.LocalNodeStateInactive && info.Swarm.LocalNodeState != swarm.LocalNodeStateLocked {
i.IsManager = &info.Swarm.ControlAvailable i.IsManager = &info.Swarm.ControlAvailable

View File

@@ -9,6 +9,9 @@ type checkSwarmUsedPortsTask struct {
} }
func (c checkSwarmUsedPortsTask) Run(i *Instance) error { func (c checkSwarmUsedPortsTask) Run(i *Instance) error {
if i.docker == nil {
return nil
}
if i.IsManager != nil && *i.IsManager { if i.IsManager != nil && *i.IsManager {
sessionPrefix := i.session.Id[:8] sessionPrefix := i.session.Id[:8]
// This is a swarm manager instance, then check for ports // This is a swarm manager instance, then check for ports

View File

@@ -6,6 +6,9 @@ type checkUsedPortsTask struct {
} }
func (c checkUsedPortsTask) Run(i *Instance) error { func (c checkUsedPortsTask) Run(i *Instance) error {
if i.docker == nil {
return nil
}
if ports, err := i.docker.GetPorts(); err == nil { if ports, err := i.docker.GetPorts(); err == nil {
for _, p := range ports { for _, p := range ports {
i.setUsedPort(uint16(p)) i.setUsedPort(uint16(p))

View File

@@ -3,6 +3,7 @@ package pwd
import ( import (
"io" "io"
"net" "net"
"time"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/play-with-docker/play-with-docker/docker" "github.com/play-with-docker/play-with-docker/docker"
@@ -12,6 +13,7 @@ type mockDocker struct {
createNetwork func(string) error createNetwork func(string) error
connectNetwork func(container, network, ip string) (string, error) connectNetwork func(container, network, ip string) (string, error)
containerResize func(string, uint, uint) error containerResize func(string, uint, uint) error
createContainer func(opts docker.CreateContainerOpts) (string, error)
} }
func (m *mockDocker) CreateNetwork(id string) error { func (m *mockDocker) CreateNetwork(id string) error {
@@ -47,7 +49,7 @@ func (m *mockDocker) ContainerResize(name string, rows, cols uint) error {
return nil return nil
} }
func (m *mockDocker) CreateAttachConnection(name string) (net.Conn, error) { func (m *mockDocker) CreateAttachConnection(name string) (net.Conn, error) {
return nil, nil return &mockConn{}, nil
} }
func (m *mockDocker) CopyToContainer(containerName, destination, fileName string, content io.Reader) error { func (m *mockDocker) CopyToContainer(containerName, destination, fileName string, content io.Reader) error {
return nil return nil
@@ -56,7 +58,10 @@ func (m *mockDocker) DeleteContainer(id string) error {
return nil return nil
} }
func (m *mockDocker) CreateContainer(opts docker.CreateContainerOpts) (string, error) { func (m *mockDocker) CreateContainer(opts docker.CreateContainerOpts) (string, error) {
return "", nil if m.createContainer != nil {
return m.createContainer(opts)
}
return "10.0.0.1", nil
} }
func (m *mockDocker) ExecAttach(instanceName string, command []string, out io.Writer) (int, error) { func (m *mockDocker) ExecAttach(instanceName string, command []string, out io.Writer) (int, error) {
return 0, nil return 0, nil
@@ -70,3 +75,38 @@ func (m *mockDocker) DeleteNetwork(id string) error {
func (m *mockDocker) Exec(instanceName string, command []string) (int, error) { func (m *mockDocker) Exec(instanceName string, command []string) (int, error) {
return 0, nil return 0, nil
} }
type mockConn struct {
}
func (m *mockConn) Read(b []byte) (n int, err error) {
return len(b), nil
}
func (m *mockConn) Write(b []byte) (n int, err error) {
return len(b), nil
}
func (m *mockConn) Close() error {
return nil
}
func (m *mockConn) LocalAddr() net.Addr {
return &net.IPAddr{}
}
func (m *mockConn) RemoteAddr() net.Addr {
return &net.IPAddr{}
}
func (m *mockConn) SetDeadline(t time.Time) error {
return nil
}
func (m *mockConn) SetReadDeadline(t time.Time) error {
return nil
}
func (m *mockConn) SetWriteDeadline(t time.Time) error {
return nil
}

View File

@@ -35,25 +35,27 @@ func (p UInt16Slice) Less(i, j int) bool { return p[i] < p[j] }
func (p UInt16Slice) Swap(i, j int) { p[i], p[j] = p[j], p[i] } func (p UInt16Slice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
type Instance struct { type Instance struct {
rw sync.Mutex Image string `json:"image"`
session *Session `json:"-"` Name string `json:"name"`
Name string `json:"name"` Hostname string `json:"hostname"`
Hostname string `json:"hostname"` IP string `json:"ip"`
IP string `json:"ip"` IsManager *bool `json:"is_manager"`
conn net.Conn `json:"-"` Mem string `json:"mem"`
ctx context.Context `json:"-"` Cpu string `json:"cpu"`
docker docker.DockerApi `json:"-"` Alias string `json:"alias"`
IsManager *bool `json:"is_manager"` ServerCert []byte `json:"server_cert"`
Mem string `json:"mem"` ServerKey []byte `json:"server_key"`
Cpu string `json:"cpu"` CACert []byte `json:"ca_cert"`
Alias string `json:"alias"` Cert []byte `json:"cert"`
tempPorts []uint16 `json:"-"` Key []byte `json:"key"`
ServerCert []byte `json:"server_cert"` IsDockerHost bool `json:"is_docker_host"`
ServerKey []byte `json:"server_key"` session *Session `json:"-"`
CACert []byte `json:"ca_cert"` conn net.Conn `json:"-"`
Cert []byte `json:"cert"` ctx context.Context `json:"-"`
Key []byte `json:"key"` docker docker.DockerApi `json:"-"`
Ports UInt16Slice tempPorts []uint16 `json:"-"`
Ports UInt16Slice
rw sync.Mutex
} }
type InstanceConfig struct { type InstanceConfig struct {
ImageName string ImageName string
@@ -200,7 +202,7 @@ func (p *pwd) InstanceNew(session *Session, conf InstanceConfig) (*Instance, err
} }
opts := docker.CreateContainerOpts{ opts := docker.CreateContainerOpts{
Image: config.GetDindImageName(), Image: conf.ImageName,
SessionId: session.Id, SessionId: session.Id,
PwdIpAddress: session.PwdIpAddress, PwdIpAddress: session.PwdIpAddress,
ContainerName: containerName, ContainerName: containerName,
@@ -208,6 +210,14 @@ func (p *pwd) InstanceNew(session *Session, conf InstanceConfig) (*Instance, err
ServerCert: conf.ServerCert, ServerCert: conf.ServerCert,
ServerKey: conf.ServerKey, ServerKey: conf.ServerKey,
CACert: conf.CACert, CACert: conf.CACert,
Privileged: false,
}
for _, imageName := range p.InstanceAllowedImages() {
if conf.ImageName == imageName {
opts.Privileged = true
break
}
} }
ip, err := p.docker.CreateContainer(opts) ip, err := p.docker.CreateContainer(opts)
@@ -216,6 +226,7 @@ func (p *pwd) InstanceNew(session *Session, conf InstanceConfig) (*Instance, err
} }
instance := &Instance{} instance := &Instance{}
instance.Image = opts.Image
instance.IP = ip instance.IP = ip
instance.Name = containerName instance.Name = containerName
instance.Hostname = nodeName instance.Hostname = nodeName
@@ -226,6 +237,8 @@ func (p *pwd) InstanceNew(session *Session, conf InstanceConfig) (*Instance, err
instance.ServerKey = conf.ServerKey instance.ServerKey = conf.ServerKey
instance.CACert = conf.CACert instance.CACert = conf.CACert
instance.session = session instance.session = session
// For now this condition holds through. In the future we might need a more complex logic.
instance.IsDockerHost = opts.Privileged
if session.Instances == nil { if session.Instances == nil {
session.Instances = make(map[string]*Instance) session.Instances = make(map[string]*Instance)

View File

@@ -1,8 +1,12 @@
package pwd package pwd
import ( import (
"fmt"
"testing" "testing"
"time"
"github.com/play-with-docker/play-with-docker/config"
"github.com/play-with-docker/play-with-docker/docker"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@@ -33,3 +37,112 @@ func TestInstanceResizeTerminal(t *testing.T) {
assert.Equal(t, uint(24), resizedRows) assert.Equal(t, uint(24), resizedRows)
assert.Equal(t, uint(80), resizedCols) assert.Equal(t, uint(80), resizedCols)
} }
func TestInstanceNew(t *testing.T) {
containerOpts := docker.CreateContainerOpts{}
dock := &mockDocker{}
dock.createContainer = func(opts docker.CreateContainerOpts) (string, error) {
containerOpts = opts
return "10.0.0.1", nil
}
tasks := &mockTasks{}
broadcast := &mockBroadcast{}
storage := &mockStorage{}
p := NewPWD(dock, tasks, broadcast, storage)
session, err := p.SessionNew(time.Hour, "", "")
assert.Nil(t, err)
instance, err := p.InstanceNew(session, InstanceConfig{})
assert.Nil(t, err)
expectedInstance := Instance{
Name: fmt.Sprintf("%s_node1", session.Id[:8]),
Hostname: "node1",
IP: "10.0.0.1",
Alias: "",
Image: config.GetDindImageName(),
IsDockerHost: true,
session: session,
}
assert.Equal(t, expectedInstance, *instance)
expectedContainerOpts := docker.CreateContainerOpts{
Image: expectedInstance.Image,
SessionId: session.Id,
PwdIpAddress: session.PwdIpAddress,
ContainerName: expectedInstance.Name,
Hostname: expectedInstance.Hostname,
ServerCert: nil,
ServerKey: nil,
CACert: nil,
Privileged: true,
}
assert.Equal(t, expectedContainerOpts, containerOpts)
}
func TestInstanceNew_WithNotAllowedImage(t *testing.T) {
containerOpts := docker.CreateContainerOpts{}
dock := &mockDocker{}
dock.createContainer = func(opts docker.CreateContainerOpts) (string, error) {
containerOpts = opts
return "10.0.0.1", nil
}
tasks := &mockTasks{}
broadcast := &mockBroadcast{}
storage := &mockStorage{}
p := NewPWD(dock, tasks, broadcast, storage)
session, err := p.SessionNew(time.Hour, "", "")
assert.Nil(t, err)
instance, err := p.InstanceNew(session, InstanceConfig{ImageName: "redis"})
assert.Nil(t, err)
expectedInstance := Instance{
Name: fmt.Sprintf("%s_node1", session.Id[:8]),
Hostname: "node1",
IP: "10.0.0.1",
Alias: "",
Image: "redis",
IsDockerHost: false,
session: session,
}
assert.Equal(t, expectedInstance, *instance)
expectedContainerOpts := docker.CreateContainerOpts{
Image: expectedInstance.Image,
SessionId: session.Id,
PwdIpAddress: session.PwdIpAddress,
ContainerName: expectedInstance.Name,
Hostname: expectedInstance.Hostname,
ServerCert: nil,
ServerKey: nil,
CACert: nil,
Privileged: false,
}
assert.Equal(t, expectedContainerOpts, containerOpts)
}
func TestInstanceAllowedImages(t *testing.T) {
dock := &mockDocker{}
tasks := &mockTasks{}
broadcast := &mockBroadcast{}
storage := &mockStorage{}
p := NewPWD(dock, tasks, broadcast, storage)
expectedImages := []string{config.GetDindImageName(), "franela/dind:overlay2-dev"}
assert.Equal(t, expectedImages, p.InstanceAllowedImages())
}

View File

@@ -45,7 +45,7 @@ func (sch *scheduler) Schedule(s *Session) {
wg.Add(len(s.Instances)) wg.Add(len(s.Instances))
for _, ins := range s.Instances { for _, ins := range s.Instances {
var i *Instance = ins var i *Instance = ins
if i.docker == nil { if i.docker == nil && i.IsDockerHost {
// Need to create client to the DinD docker daemon // Need to create client to the DinD docker daemon
// We check if the client needs to use TLS // We check if the client needs to use TLS