2017-07-30 18:05:06 +00:00
|
|
|
package control
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2017-10-01 18:42:17 +00:00
|
|
|
"io"
|
|
|
|
"os"
|
2017-07-30 18:05:06 +00:00
|
|
|
"strings"
|
2018-03-14 09:33:23 +00:00
|
|
|
"time"
|
2017-07-30 18:05:06 +00:00
|
|
|
|
2018-03-14 09:33:23 +00:00
|
|
|
"github.com/docker/docker/api/types"
|
|
|
|
"github.com/docker/docker/api/types/container"
|
|
|
|
"github.com/docker/docker/client"
|
2018-02-20 20:25:31 +00:00
|
|
|
"github.com/pterodactyl/wings/constants"
|
2017-07-30 18:05:06 +00:00
|
|
|
log "github.com/sirupsen/logrus"
|
|
|
|
)
|
|
|
|
|
|
|
|
type dockerEnvironment struct {
|
|
|
|
baseEnvironment
|
|
|
|
|
2018-03-14 09:33:23 +00:00
|
|
|
client *client.Client
|
|
|
|
hires types.HijackedResponse
|
|
|
|
attached bool
|
2017-10-01 18:42:17 +00:00
|
|
|
|
2017-08-31 22:01:32 +00:00
|
|
|
server *ServerStruct
|
2017-07-30 18:05:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Ensure DockerEnvironment implements Environment
|
|
|
|
var _ Environment = &dockerEnvironment{}
|
|
|
|
|
|
|
|
// NewDockerEnvironment creates a new docker enviornment
|
|
|
|
// instance and connects to the docker client on the host system
|
|
|
|
// If the container is already running it will try to reattach
|
|
|
|
// to the running container
|
2017-08-31 22:01:32 +00:00
|
|
|
func NewDockerEnvironment(server *ServerStruct) (Environment, error) {
|
2017-07-30 18:05:06 +00:00
|
|
|
env := dockerEnvironment{}
|
|
|
|
|
|
|
|
env.server = server
|
2018-03-14 09:33:23 +00:00
|
|
|
env.attached = false
|
|
|
|
|
|
|
|
cli, err := client.NewEnvClient()
|
|
|
|
env.client = cli
|
|
|
|
ctx := context.TODO()
|
|
|
|
cli.NegotiateAPIVersion(ctx)
|
2017-07-30 18:05:06 +00:00
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
log.WithError(err).Fatal("Failed to connect to docker.")
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if env.server.DockerContainer.ID != "" {
|
2018-03-14 09:33:23 +00:00
|
|
|
if err := env.inspectContainer(ctx); err != nil {
|
2017-10-01 18:42:17 +00:00
|
|
|
log.WithError(err).Error("Failed to find the container with stored id, removing id.")
|
|
|
|
env.server.DockerContainer.ID = ""
|
|
|
|
env.server.Save()
|
2017-07-30 18:05:06 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return &env, nil
|
|
|
|
}
|
|
|
|
|
2018-03-14 09:33:23 +00:00
|
|
|
func (env *dockerEnvironment) inspectContainer(ctx context.Context) error {
|
|
|
|
_, err := env.client.ContainerInspect(ctx, env.server.DockerContainer.ID)
|
|
|
|
return err
|
2017-07-30 18:05:06 +00:00
|
|
|
}
|
|
|
|
|
2017-10-01 18:42:17 +00:00
|
|
|
func (env *dockerEnvironment) attach() error {
|
2018-02-20 22:38:29 +00:00
|
|
|
if env.attached {
|
|
|
|
return nil
|
|
|
|
}
|
2018-03-14 09:34:06 +00:00
|
|
|
|
|
|
|
cw := ConsoleHandler{
|
|
|
|
Websockets: env.server.websockets,
|
|
|
|
}
|
|
|
|
|
|
|
|
var err error
|
|
|
|
env.hires, err = env.client.ContainerAttach(context.TODO(), env.server.DockerContainer.ID,
|
|
|
|
types.ContainerAttachOptions{
|
|
|
|
Stdin: true,
|
|
|
|
Stdout: true,
|
|
|
|
Stderr: true,
|
|
|
|
Stream: true,
|
|
|
|
})
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
log.WithField("server", env.server.ID).WithError(err).Error("Failed to attach to docker container.")
|
|
|
|
return err
|
|
|
|
}
|
2018-02-20 22:38:29 +00:00
|
|
|
env.attached = true
|
2018-03-14 09:34:06 +00:00
|
|
|
|
|
|
|
go func() {
|
|
|
|
defer env.hires.Close()
|
|
|
|
defer func() {
|
|
|
|
env.attached = false
|
|
|
|
}()
|
|
|
|
io.Copy(cw, env.hires.Reader)
|
|
|
|
}()
|
|
|
|
|
2018-03-14 09:33:23 +00:00
|
|
|
return nil
|
2017-10-01 18:42:17 +00:00
|
|
|
}
|
|
|
|
|
2017-07-30 18:05:06 +00:00
|
|
|
// Create creates the docker container for the environment and applies all
|
|
|
|
// settings to it
|
|
|
|
func (env *dockerEnvironment) Create() error {
|
2017-10-01 18:42:17 +00:00
|
|
|
log.WithField("server", env.server.ID).Debug("Creating docker environment")
|
2017-07-30 18:05:06 +00:00
|
|
|
|
2018-03-14 09:33:23 +00:00
|
|
|
ctx := context.TODO()
|
|
|
|
|
|
|
|
if err := env.pullImage(ctx); err != nil {
|
|
|
|
log.WithError(err).WithField("image", env.server.GetService().DockerImage).WithField("server", env.server.ID).Error("Failed to pull docker image.")
|
2017-10-01 18:42:17 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := os.MkdirAll(env.server.dataPath(), constants.DefaultFolderPerms); err != nil {
|
2017-07-30 18:05:06 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create docker container
|
|
|
|
// TODO: apply cpu, io, disk limits.
|
2018-03-14 09:33:23 +00:00
|
|
|
|
|
|
|
containerConfig := &container.Config{
|
|
|
|
Image: env.server.GetService().DockerImage,
|
|
|
|
Cmd: strings.Split(env.server.StartupCommand, " "),
|
|
|
|
AttachStdin: true,
|
|
|
|
OpenStdin: true,
|
|
|
|
AttachStdout: true,
|
|
|
|
AttachStderr: true,
|
|
|
|
Tty: true,
|
|
|
|
Hostname: constants.DockerContainerPrefix + env.server.UUIDShort(),
|
|
|
|
}
|
|
|
|
|
|
|
|
containerHostConfig := &container.HostConfig{
|
|
|
|
Resources: container.Resources{
|
|
|
|
Memory: env.server.Settings.Memory,
|
|
|
|
MemorySwap: env.server.Settings.Swap,
|
|
|
|
},
|
2018-02-20 22:38:29 +00:00
|
|
|
// TODO: Allow custom binds via some kind of settings in the service
|
|
|
|
Binds: []string{env.server.dataPath() + ":/home/container"},
|
|
|
|
// TODO: Add port bindings
|
2017-07-30 18:05:06 +00:00
|
|
|
}
|
2018-03-14 09:33:23 +00:00
|
|
|
|
|
|
|
containerHostConfig.Memory = 0
|
|
|
|
|
|
|
|
container, err := env.client.ContainerCreate(ctx, containerConfig, containerHostConfig, nil, constants.DockerContainerPrefix+env.server.UUIDShort())
|
2017-07-30 18:05:06 +00:00
|
|
|
if err != nil {
|
2017-10-01 18:42:17 +00:00
|
|
|
log.WithError(err).WithField("server", env.server.ID).Error("Failed to create docker container")
|
2017-07-30 18:05:06 +00:00
|
|
|
return err
|
|
|
|
}
|
2018-02-20 22:38:29 +00:00
|
|
|
|
2017-07-30 18:05:06 +00:00
|
|
|
env.server.DockerContainer.ID = container.ID
|
2017-10-01 18:42:17 +00:00
|
|
|
env.server.Save()
|
2017-07-30 18:05:06 +00:00
|
|
|
|
2017-10-01 18:42:17 +00:00
|
|
|
log.WithField("server", env.server.ID).Debug("Docker environment created")
|
2017-07-30 18:05:06 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Destroy removes the environment's docker container
|
|
|
|
func (env *dockerEnvironment) Destroy() error {
|
2017-10-01 18:42:17 +00:00
|
|
|
log.WithField("server", env.server.ID).Debug("Destroying docker environment")
|
2018-03-14 09:33:23 +00:00
|
|
|
|
|
|
|
ctx := context.TODO()
|
|
|
|
|
|
|
|
if err := env.inspectContainer(ctx); err != nil {
|
|
|
|
log.WithError(err).Debug("Container not found error")
|
|
|
|
log.WithField("server", env.server.ID).Debug("Container not found, docker environment destroyed already.")
|
|
|
|
return nil
|
2017-10-01 18:42:17 +00:00
|
|
|
}
|
2018-03-14 09:33:23 +00:00
|
|
|
|
|
|
|
if err := env.client.ContainerRemove(ctx, env.server.DockerContainer.ID, types.ContainerRemoveOptions{}); err != nil {
|
2017-10-01 18:42:17 +00:00
|
|
|
log.WithError(err).WithField("server", env.server.ID).Error("Failed to destroy docker environment")
|
2017-07-30 18:05:06 +00:00
|
|
|
return err
|
|
|
|
}
|
2017-10-01 18:42:17 +00:00
|
|
|
|
|
|
|
log.WithField("server", env.server.ID).Debug("Docker environment destroyed")
|
2017-07-30 18:05:06 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2017-10-01 18:42:17 +00:00
|
|
|
func (env *dockerEnvironment) Exists() bool {
|
2018-03-14 09:33:23 +00:00
|
|
|
if err := env.inspectContainer(context.TODO()); err != nil {
|
|
|
|
return false
|
2017-10-01 18:42:17 +00:00
|
|
|
}
|
2018-03-14 09:33:23 +00:00
|
|
|
return true
|
2017-10-01 18:42:17 +00:00
|
|
|
}
|
|
|
|
|
2017-07-30 18:05:06 +00:00
|
|
|
// Start starts the environment's docker container
|
|
|
|
func (env *dockerEnvironment) Start() error {
|
2017-10-01 18:42:17 +00:00
|
|
|
log.WithField("server", env.server.ID).Debug("Starting service in docker environment")
|
|
|
|
if err := env.attach(); err != nil {
|
|
|
|
log.WithError(err).Error("Failed to attach to docker container")
|
|
|
|
}
|
2018-03-14 09:33:23 +00:00
|
|
|
|
|
|
|
if err := env.client.ContainerStart(context.TODO(), env.server.DockerContainer.ID, types.ContainerStartOptions{}); err != nil {
|
2017-07-30 18:05:06 +00:00
|
|
|
log.WithError(err).Error("Failed to start docker container")
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Stop stops the environment's docker container
|
|
|
|
func (env *dockerEnvironment) Stop() error {
|
2017-10-01 18:42:17 +00:00
|
|
|
log.WithField("server", env.server.ID).Debug("Stopping service in docker environment")
|
2018-03-14 09:33:23 +00:00
|
|
|
|
|
|
|
// TODO: Decide after what timeout to kill the container, currently 10min
|
|
|
|
timeout := time.Minute * 10
|
|
|
|
if err := env.client.ContainerStop(context.TODO(), env.server.DockerContainer.ID, &timeout); err != nil {
|
2017-07-30 18:05:06 +00:00
|
|
|
log.WithError(err).Error("Failed to stop docker container")
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (env *dockerEnvironment) Kill() error {
|
2017-10-01 18:42:17 +00:00
|
|
|
log.WithField("server", env.server.ID).Debug("Killing service in docker environment")
|
2018-03-14 09:33:23 +00:00
|
|
|
|
|
|
|
if err := env.client.ContainerKill(context.TODO(), env.server.DockerContainer.ID, "SIGKILL"); err != nil {
|
2017-07-30 18:05:06 +00:00
|
|
|
log.WithError(err).Error("Failed to kill docker container")
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Exec sends commands to the standard input of the docker container
|
|
|
|
func (env *dockerEnvironment) Exec(command string) error {
|
2018-03-14 09:33:23 +00:00
|
|
|
//log.Debug("Command: " + command)
|
|
|
|
//_, err := env.containerInput.Write([]byte(command + "\n"))
|
|
|
|
//return err
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (env *dockerEnvironment) pullImage(ctx context.Context) error {
|
|
|
|
// Split image repository and tag
|
|
|
|
imageParts := strings.Split(env.server.GetService().DockerImage, ":")
|
|
|
|
imageRepoParts := strings.Split(imageParts[0], "/")
|
|
|
|
if len(imageRepoParts) >= 3 {
|
|
|
|
// TODO: Handle possibly required authentication
|
|
|
|
}
|
|
|
|
|
|
|
|
// Pull docker image
|
|
|
|
log.WithField("image", env.server.GetService().DockerImage).Debug("Pulling docker image")
|
|
|
|
|
|
|
|
rc, err := env.client.ImagePull(ctx, env.server.GetService().DockerImage, types.ImagePullOptions{})
|
|
|
|
defer rc.Close()
|
2017-10-01 18:42:17 +00:00
|
|
|
return err
|
2017-07-30 18:05:06 +00:00
|
|
|
}
|