Merge pull request #11 from xetorthio/allow_any_image
Allow to launch instances with any kind of public image.
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))
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|||||||
@@ -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,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)
|
||||||
|
|||||||
@@ -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())
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user