Allow to launch instances with any kind of public image.

Images that are not whitelisted will be launched as normal containers.
Only whitelisted ones will be launched as privileged.
Additionally pull the image if it doesn't exist.
This commit is contained in:
Jonathan Leibiusky @xetorthio
2017-05-27 20:04:37 -03:00
parent 07d0bd0b91
commit b0b9269ccc
4 changed files with 208 additions and 11 deletions

View File

@@ -14,10 +14,12 @@ import (
"strings"
"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/network"
"github.com/docker/docker/client"
"github.com/docker/docker/pkg/jsonmessage"
)
const (
@@ -190,6 +192,7 @@ type CreateContainerOpts struct {
ServerCert []byte
ServerKey []byte
CACert []byte
Privileged bool
}
func (d *docker) CreateContainer(opts CreateContainerOpts) (string, error) {
@@ -219,7 +222,7 @@ func (d *docker) CreateContainer(opts CreateContainerOpts) (string, error) {
h := &container.HostConfig{
NetworkMode: container.NetworkMode(opts.SessionId),
Privileged: true,
Privileged: opts.Privileged,
AutoRemove: true,
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)
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 {
@@ -283,6 +297,28 @@ func (d *docker) CreateContainer(opts CreateContainerOpts) (string, error) {
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 {
if len(content) > 0 {
return d.CopyToContainer(containerName, path, fileName, bytes.NewReader(content))

View File

@@ -3,6 +3,7 @@ package pwd
import (
"io"
"net"
"time"
"github.com/docker/docker/api/types"
"github.com/play-with-docker/play-with-docker/docker"
@@ -12,6 +13,7 @@ type mockDocker struct {
createNetwork func(string) error
connectNetwork func(container, network, ip string) (string, error)
containerResize func(string, uint, uint) error
createContainer func(opts docker.CreateContainerOpts) (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
}
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 {
return nil
@@ -56,7 +58,10 @@ func (m *mockDocker) DeleteContainer(id string) error {
return nil
}
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) {
return 0, nil
@@ -70,3 +75,38 @@ func (m *mockDocker) DeleteNetwork(id string) error {
func (m *mockDocker) Exec(instanceName string, command []string) (int, error) {
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,26 @@ 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] }
type Instance struct {
rw sync.Mutex
session *Session `json:"-"`
Image string `json:"image"`
Name string `json:"name"`
Hostname string `json:"hostname"`
IP string `json:"ip"`
conn net.Conn `json:"-"`
ctx context.Context `json:"-"`
docker docker.DockerApi `json:"-"`
IsManager *bool `json:"is_manager"`
Mem string `json:"mem"`
Cpu string `json:"cpu"`
Alias string `json:"alias"`
tempPorts []uint16 `json:"-"`
ServerCert []byte `json:"server_cert"`
ServerKey []byte `json:"server_key"`
CACert []byte `json:"ca_cert"`
Cert []byte `json:"cert"`
Key []byte `json:"key"`
session *Session `json:"-"`
conn net.Conn `json:"-"`
ctx context.Context `json:"-"`
docker docker.DockerApi `json:"-"`
tempPorts []uint16 `json:"-"`
Ports UInt16Slice
rw sync.Mutex
}
type InstanceConfig struct {
ImageName string
@@ -200,7 +201,7 @@ func (p *pwd) InstanceNew(session *Session, conf InstanceConfig) (*Instance, err
}
opts := docker.CreateContainerOpts{
Image: config.GetDindImageName(),
Image: conf.ImageName,
SessionId: session.Id,
PwdIpAddress: session.PwdIpAddress,
ContainerName: containerName,
@@ -208,6 +209,14 @@ func (p *pwd) InstanceNew(session *Session, conf InstanceConfig) (*Instance, err
ServerCert: conf.ServerCert,
ServerKey: conf.ServerKey,
CACert: conf.CACert,
Privileged: false,
}
for _, imageName := range p.InstanceAllowedImages() {
if conf.ImageName == imageName {
opts.Privileged = true
break
}
}
ip, err := p.docker.CreateContainer(opts)
@@ -216,6 +225,7 @@ func (p *pwd) InstanceNew(session *Session, conf InstanceConfig) (*Instance, err
}
instance := &Instance{}
instance.Image = opts.Image
instance.IP = ip
instance.Name = containerName
instance.Hostname = nodeName

View File

@@ -1,8 +1,12 @@
package pwd
import (
"fmt"
"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"
)
@@ -33,3 +37,110 @@ func TestInstanceResizeTerminal(t *testing.T) {
assert.Equal(t, uint(24), resizedRows)
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(),
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",
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())
}