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:
@@ -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))
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
|||||||
@@ -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] }
|
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"`
|
||||||
conn net.Conn `json:"-"`
|
|
||||||
ctx context.Context `json:"-"`
|
|
||||||
docker docker.DockerApi `json:"-"`
|
|
||||||
IsManager *bool `json:"is_manager"`
|
IsManager *bool `json:"is_manager"`
|
||||||
Mem string `json:"mem"`
|
Mem string `json:"mem"`
|
||||||
Cpu string `json:"cpu"`
|
Cpu string `json:"cpu"`
|
||||||
Alias string `json:"alias"`
|
Alias string `json:"alias"`
|
||||||
tempPorts []uint16 `json:"-"`
|
|
||||||
ServerCert []byte `json:"server_cert"`
|
ServerCert []byte `json:"server_cert"`
|
||||||
ServerKey []byte `json:"server_key"`
|
ServerKey []byte `json:"server_key"`
|
||||||
CACert []byte `json:"ca_cert"`
|
CACert []byte `json:"ca_cert"`
|
||||||
Cert []byte `json:"cert"`
|
Cert []byte `json:"cert"`
|
||||||
Key []byte `json:"key"`
|
Key []byte `json:"key"`
|
||||||
|
session *Session `json:"-"`
|
||||||
|
conn net.Conn `json:"-"`
|
||||||
|
ctx context.Context `json:"-"`
|
||||||
|
docker docker.DockerApi `json:"-"`
|
||||||
|
tempPorts []uint16 `json:"-"`
|
||||||
Ports UInt16Slice
|
Ports UInt16Slice
|
||||||
|
rw sync.Mutex
|
||||||
}
|
}
|
||||||
type InstanceConfig struct {
|
type InstanceConfig struct {
|
||||||
ImageName string
|
ImageName string
|
||||||
@@ -200,7 +201,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 +209,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 +225,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
|
||||||
|
|||||||
@@ -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,110 @@ 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(),
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user