first implementation of docker environment
This commit is contained in:
157
control/docker_environment.go
Normal file
157
control/docker_environment.go
Normal file
@@ -0,0 +1,157 @@
|
||||
package control
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/fsouza/go-dockerclient"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type dockerEnvironment struct {
|
||||
baseEnvironment
|
||||
|
||||
client *docker.Client
|
||||
container *docker.Container
|
||||
context context.Context
|
||||
|
||||
server *server
|
||||
}
|
||||
|
||||
// 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
|
||||
func NewDockerEnvironment(server *server) (Environment, error) {
|
||||
env := dockerEnvironment{}
|
||||
|
||||
env.server = server
|
||||
|
||||
client, err := docker.NewClient("unix:///var/run/docker.sock")
|
||||
env.client = client
|
||||
if err != nil {
|
||||
log.WithError(err).Fatal("Failed to connect to docker.")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if env.server.DockerContainer.ID != "" {
|
||||
if err := env.reattach(); err != nil {
|
||||
log.WithError(err).Error("Failed to reattach to existing container.")
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &env, nil
|
||||
}
|
||||
|
||||
func (env *dockerEnvironment) reattach() error {
|
||||
container, err := env.client.InspectContainer(env.server.DockerContainer.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
env.container = container
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create creates the docker container for the environment and applies all
|
||||
// settings to it
|
||||
func (env *dockerEnvironment) Create() error {
|
||||
log.WithField("serverID", env.server.UUID).Debug("Creating docker environment")
|
||||
// Split image repository and tag to feed it to the library
|
||||
imageParts := strings.Split(env.server.Service().DockerImage, ":")
|
||||
imageRepoParts := strings.Split(imageParts[0], "/")
|
||||
if len(imageRepoParts) >= 3 {
|
||||
// Handle possibly required authentication
|
||||
}
|
||||
|
||||
// Pull docker image
|
||||
var pullImageOpts = docker.PullImageOptions{
|
||||
Repository: imageParts[0],
|
||||
}
|
||||
if len(imageParts) >= 2 {
|
||||
pullImageOpts.Tag = imageParts[1]
|
||||
}
|
||||
log.WithField("image", env.server.service.DockerImage).Debug("Pulling docker image")
|
||||
err := env.client.PullImage(pullImageOpts, docker.AuthConfiguration{})
|
||||
if err != nil {
|
||||
log.WithError(err).WithField("serverID", env.server.UUID).Error("Failed to create docker environment")
|
||||
return err
|
||||
}
|
||||
|
||||
// Create docker container
|
||||
// TODO: apply cpu, io, disk limits.
|
||||
containerConfig := &docker.Config{
|
||||
Image: env.server.Service().DockerImage,
|
||||
}
|
||||
containerHostConfig := &docker.HostConfig{
|
||||
Memory: env.server.Settings.Memory,
|
||||
MemorySwap: env.server.Settings.Swap,
|
||||
}
|
||||
createContainerOpts := docker.CreateContainerOptions{
|
||||
Name: "ptdl_" + env.server.UUIDShort(),
|
||||
Config: containerConfig,
|
||||
HostConfig: containerHostConfig,
|
||||
Context: env.context,
|
||||
}
|
||||
container, err := env.client.CreateContainer(createContainerOpts)
|
||||
if err != nil {
|
||||
log.WithError(err).WithField("serverID", env.server.UUID).Error("Failed to create docker container")
|
||||
return err
|
||||
}
|
||||
env.server.DockerContainer.ID = container.ID
|
||||
env.container = container
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Destroy removes the environment's docker container
|
||||
func (env *dockerEnvironment) Destroy() error {
|
||||
log.WithField("serverID", env.server.UUID).Debug("Destroying docker environment")
|
||||
err := env.client.RemoveContainer(docker.RemoveContainerOptions{
|
||||
ID: env.server.DockerContainer.ID,
|
||||
})
|
||||
if err != nil {
|
||||
log.WithError(err).WithField("serverID", env.server.UUID).Error("Failed to destroy docker environment")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Start starts the environment's docker container
|
||||
func (env *dockerEnvironment) Start() error {
|
||||
log.WithField("serverID", env.server.UUID).Debug("Starting service in docker environment")
|
||||
if err := env.client.StartContainer(env.container.ID, nil); err != nil {
|
||||
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 {
|
||||
log.WithField("serverID", env.server.UUID).Debug("Stopping service in docker environment")
|
||||
if err := env.client.StopContainer(env.container.ID, 20000); err != nil {
|
||||
log.WithError(err).Error("Failed to stop docker container")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (env *dockerEnvironment) Kill() error {
|
||||
log.WithField("serverID", env.server.UUID).Debug("Killing service in docker environment")
|
||||
if err := env.client.KillContainer(docker.KillContainerOptions{
|
||||
ID: env.container.ID,
|
||||
}); err != nil {
|
||||
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 {
|
||||
return nil
|
||||
}
|
||||
133
control/docker_environment_test.go
Normal file
133
control/docker_environment_test.go
Normal file
@@ -0,0 +1,133 @@
|
||||
package control
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
docker "github.com/fsouza/go-dockerclient"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func testServer() *server {
|
||||
return &server{
|
||||
UUID: "testuuid-something-something",
|
||||
service: &service{
|
||||
DockerImage: "alpine:latest",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewDockerEnvironment(t *testing.T) {
|
||||
env, err := createTestDockerEnv(nil)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, env)
|
||||
assert.NotNil(t, env.client)
|
||||
}
|
||||
|
||||
func TestNewDockerEnvironmentExisting(t *testing.T) {
|
||||
eenv, _ := createTestDockerEnv(nil)
|
||||
eenv.Create()
|
||||
|
||||
env, err := createTestDockerEnv(eenv.server)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, env)
|
||||
assert.NotNil(t, env.container)
|
||||
|
||||
eenv.Destroy()
|
||||
}
|
||||
|
||||
func TestCreateDockerEnvironment(t *testing.T) {
|
||||
env, _ := createTestDockerEnv(nil)
|
||||
|
||||
err := env.Create()
|
||||
|
||||
a := assert.New(t)
|
||||
a.Nil(err)
|
||||
a.NotNil(env.container)
|
||||
a.Equal(env.container.Name, "ptdl_testuuid")
|
||||
|
||||
if err := env.client.RemoveContainer(docker.RemoveContainerOptions{
|
||||
ID: env.container.ID,
|
||||
}); err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDestroyDockerEnvironment(t *testing.T) {
|
||||
env, _ := createTestDockerEnv(nil)
|
||||
env.Create()
|
||||
|
||||
err := env.Destroy()
|
||||
|
||||
_, ierr := env.client.InspectContainer(env.container.ID)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.IsType(t, ierr, &docker.NoSuchContainer{})
|
||||
}
|
||||
|
||||
func TestStartDockerEnvironment(t *testing.T) {
|
||||
env, _ := createTestDockerEnv(nil)
|
||||
env.Create()
|
||||
err := env.Start()
|
||||
|
||||
i, ierr := env.client.InspectContainer(env.container.ID)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Nil(t, ierr)
|
||||
assert.True(t, i.State.Running)
|
||||
|
||||
env.client.KillContainer(docker.KillContainerOptions{
|
||||
ID: env.container.ID,
|
||||
})
|
||||
env.Destroy()
|
||||
}
|
||||
|
||||
func TestStopDockerEnvironment(t *testing.T) {
|
||||
env, _ := createTestDockerEnv(nil)
|
||||
env.Create()
|
||||
env.Start()
|
||||
err := env.Stop()
|
||||
|
||||
i, ierr := env.client.InspectContainer(env.container.ID)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Nil(t, ierr)
|
||||
assert.False(t, i.State.Running)
|
||||
|
||||
env.client.KillContainer(docker.KillContainerOptions{
|
||||
ID: env.container.ID,
|
||||
})
|
||||
env.Destroy()
|
||||
}
|
||||
|
||||
func TestKillDockerEnvironment(t *testing.T) {
|
||||
env, _ := createTestDockerEnv(nil)
|
||||
env.Create()
|
||||
env.Start()
|
||||
err := env.Kill()
|
||||
|
||||
i, ierr := env.client.InspectContainer(env.container.ID)
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Nil(t, ierr)
|
||||
assert.False(t, i.State.Running)
|
||||
|
||||
env.client.KillContainer(docker.KillContainerOptions{
|
||||
ID: env.container.ID,
|
||||
})
|
||||
env.Destroy()
|
||||
}
|
||||
|
||||
func TestExecDockerEnvironment(t *testing.T) {
|
||||
|
||||
}
|
||||
|
||||
func createTestDockerEnv(s *server) (*dockerEnvironment, error) {
|
||||
if s == nil {
|
||||
s = testServer()
|
||||
}
|
||||
env, err := NewDockerEnvironment(s)
|
||||
return env.(*dockerEnvironment), err
|
||||
}
|
||||
72
control/environment.go
Normal file
72
control/environment.go
Normal file
@@ -0,0 +1,72 @@
|
||||
package control
|
||||
|
||||
// Environment provides abstraction of different environments
|
||||
type Environment interface {
|
||||
// Create creates the environment
|
||||
Create() error
|
||||
|
||||
// Destroy destroys the environment
|
||||
Destroy() error
|
||||
|
||||
// Start starts the service in the environment
|
||||
Start() error
|
||||
|
||||
// Stop stops the service in the environment
|
||||
Stop() error
|
||||
|
||||
// Kill kills the service in the environment
|
||||
Kill() error
|
||||
|
||||
// Execute a command in the environment
|
||||
// This sends the command to the standard input of the environment
|
||||
Exec(command string) error
|
||||
|
||||
// Exists checks wether the Environment exists or not
|
||||
Exists() bool
|
||||
|
||||
// ReCreate recreates the environment by first Destroying and then Creating
|
||||
ReCreate() error
|
||||
}
|
||||
|
||||
type baseEnvironment struct {
|
||||
}
|
||||
|
||||
// Ensure BaseEnvironment implements Environment
|
||||
var _ Environment = &baseEnvironment{}
|
||||
|
||||
func (env *baseEnvironment) Create() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (env *baseEnvironment) Destroy() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (env *baseEnvironment) Start() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (env *baseEnvironment) Stop() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (env *baseEnvironment) Kill() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (env *baseEnvironment) Exists() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (env *baseEnvironment) ReCreate() error {
|
||||
if env.Exists() {
|
||||
if err := env.Destroy(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return env.Create()
|
||||
}
|
||||
|
||||
func (env *baseEnvironment) Exec(command string) error {
|
||||
return nil
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package environments
|
||||
|
||||
type DockerEnvironment struct {
|
||||
BaseEnvironment
|
||||
}
|
||||
|
||||
// Ensure DockerEnvironment implements Environment
|
||||
var _ Environment = &DockerEnvironment{}
|
||||
|
||||
func NewDockerEnvironment() *DockerEnvironment {
|
||||
return &DockerEnvironment{}
|
||||
}
|
||||
|
||||
func (env *DockerEnvironment) Exec() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (env *DockerEnvironment) Create() error {
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
package environments
|
||||
|
||||
// Environment provides abstraction of different environments
|
||||
type Environment interface {
|
||||
// Execute a command in the environment
|
||||
Exec() error
|
||||
|
||||
// Create creates the environment
|
||||
Create() error
|
||||
|
||||
// Destroy destroys the environment
|
||||
Destroy() error
|
||||
|
||||
// Exists checks wether the Environment exists or not
|
||||
Exists() bool
|
||||
|
||||
// ReCreate recreates the environment by first Destroying and then Creating
|
||||
ReCreate() error
|
||||
}
|
||||
|
||||
type BaseEnvironment struct {
|
||||
}
|
||||
|
||||
// Ensure BaseEnvironment implements Environment
|
||||
var _ Environment = &BaseEnvironment{}
|
||||
|
||||
func (env *BaseEnvironment) Create() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (env *BaseEnvironment) Destroy() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (env *BaseEnvironment) Exists() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (env *BaseEnvironment) ReCreate() error {
|
||||
if env.Exists() {
|
||||
if err := env.Destroy(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return env.Create()
|
||||
}
|
||||
|
||||
func (env *BaseEnvironment) Exec() error {
|
||||
return nil
|
||||
}
|
||||
@@ -2,26 +2,79 @@ package control
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// Server is a Server
|
||||
type Server interface {
|
||||
Start() error
|
||||
Stop() error
|
||||
Exec(command string) error
|
||||
Rebuild() error
|
||||
}
|
||||
|
||||
// Server is a single instance of a Service managed by the panel
|
||||
type Server struct {
|
||||
type server struct {
|
||||
// UUID is the unique identifier of the server
|
||||
UUID string `json:"uuid"`
|
||||
|
||||
// Service is the service the server is an instance of
|
||||
Service *Service `json:"-"`
|
||||
|
||||
// ServiceName is the name of the service. It is mainly used to allow storing the service
|
||||
// in the config
|
||||
ServiceName string `json:"serviceName"`
|
||||
service *service
|
||||
environment Environment
|
||||
|
||||
// StartupCommand is the command executed in the environment to start the server
|
||||
StartupCommand string `json:"startupCommand"`
|
||||
|
||||
// DockerContainer holds information regarding the docker container when the server
|
||||
// is running in a docker environment
|
||||
DockerContainer dockerContainer `json:"dockerContainer"`
|
||||
|
||||
// EnvironmentVariables are set in the Environment the server is running in
|
||||
EnvironmentVariables map[string]string `json:"env"`
|
||||
|
||||
// Allocations contains the ports and ip addresses assigned to the server
|
||||
Allocations allocations `json:"allocation"`
|
||||
|
||||
// Settings are the environment settings and limitations for the server
|
||||
Settings settings `json:"settings"`
|
||||
|
||||
// Keys are some auth keys we will hopefully replace by something better.
|
||||
Keys map[string][]string `json:"keys"`
|
||||
}
|
||||
|
||||
var servers map[string]*Server
|
||||
type allocations struct {
|
||||
Ports []int16 `json:"ports"`
|
||||
MainIP string `json:"ip"`
|
||||
MainPort int16 `json:"port"`
|
||||
}
|
||||
|
||||
type settings struct {
|
||||
Memory int64 `json:"memory"`
|
||||
Swap int64 `json:"swap"`
|
||||
IO int64 `json:"io"`
|
||||
CPU int16 `json:"cpu"`
|
||||
Disk int64 `json:"disk"`
|
||||
Image string `json:"image"`
|
||||
User string `json:"user"`
|
||||
UserID int16 `json:"userID"`
|
||||
}
|
||||
|
||||
type dockerContainer struct {
|
||||
ID string `json:"id"`
|
||||
Image string `json:"image"`
|
||||
}
|
||||
|
||||
// ensure server implements Server
|
||||
var _ Server = &server{}
|
||||
|
||||
var servers map[string]*server
|
||||
|
||||
// LoadServerConfigurations loads the configured servers from a specified path
|
||||
func LoadServerConfigurations(path string) error {
|
||||
@@ -29,7 +82,7 @@ func LoadServerConfigurations(path string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
servers = make(map[string]*Server)
|
||||
servers = make(map[string]*server)
|
||||
|
||||
for _, file := range serverFiles {
|
||||
if !file.IsDir() {
|
||||
@@ -44,14 +97,14 @@ func LoadServerConfigurations(path string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadServerConfiguration(path string) (*Server, error) {
|
||||
func loadServerConfiguration(path string) (*server, error) {
|
||||
file, err := ioutil.ReadFile(path)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
server := NewServer()
|
||||
server := &server{}
|
||||
if err := json.Unmarshal(file, server); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -60,17 +113,80 @@ func loadServerConfiguration(path string) (*Server, error) {
|
||||
}
|
||||
|
||||
// GetServer returns the server identified by the provided uuid
|
||||
func GetServer(uuid string) *Server {
|
||||
func GetServer(uuid string) Server {
|
||||
return servers[uuid]
|
||||
}
|
||||
|
||||
// NewServer creates a new Server
|
||||
func NewServer() *Server {
|
||||
return new(Server)
|
||||
func NewServer() Server {
|
||||
return new(server)
|
||||
}
|
||||
|
||||
func (s *server) Start() error {
|
||||
/*if err := s.Environment().Create(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.Environment().Start(); err != nil {
|
||||
return err
|
||||
}*/
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *server) Stop() error {
|
||||
/*if err := s.Environment().Stop(); err != nil {
|
||||
return err
|
||||
}*/
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *server) Exec(command string) error {
|
||||
/*if err := s.Environment().Exec(command); err != nil {
|
||||
return err
|
||||
}*/
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *server) Rebuild() error {
|
||||
/*if err := s.Environment().ReCreate(); err != nil {
|
||||
return err
|
||||
}*/
|
||||
return nil
|
||||
}
|
||||
|
||||
// Service returns the server's service configuration
|
||||
func (s *server) Service() *service {
|
||||
if s.service == nil {
|
||||
// TODO: Properly use the correct service, mock for now.
|
||||
s.service = &service{
|
||||
DockerImage: "quay.io/pterodactyl/core:java",
|
||||
EnvironmentName: "docker",
|
||||
}
|
||||
}
|
||||
return s.service
|
||||
}
|
||||
|
||||
// UUIDShort returns the first block of the UUID
|
||||
func (s *server) UUIDShort() string {
|
||||
return s.UUID[0:strings.Index(s.UUID, "-")]
|
||||
}
|
||||
|
||||
// Environment returns the servers environment
|
||||
func (s *server) Environment() (Environment, error) {
|
||||
var err error
|
||||
if s.environment == nil {
|
||||
switch s.Service().EnvironmentName {
|
||||
case "docker":
|
||||
s.environment, err = NewDockerEnvironment(s)
|
||||
default:
|
||||
log.WithField("service", s.ServiceName).Error("Invalid environment name")
|
||||
return nil, errors.New("Invalid environment name")
|
||||
}
|
||||
}
|
||||
return s.environment, err
|
||||
}
|
||||
|
||||
// HasPermission checks wether a provided token has a specific permission
|
||||
func (s *Server) HasPermission(token string, permission string) bool {
|
||||
func (s *server) HasPermission(token string, permission string) bool {
|
||||
for key, perms := range s.Keys {
|
||||
if key == token {
|
||||
for _, perm := range perms {
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
package control
|
||||
|
||||
import "github.com/Pterodactyl/wings/control/environments"
|
||||
type service struct {
|
||||
server *Server
|
||||
|
||||
type Service struct {
|
||||
Environment environments.Environment
|
||||
// EnvironmentName is the name of the environment used by the service
|
||||
EnvironmentName string `json:"environmentName"`
|
||||
|
||||
DockerImage string `json:"dockerImage"`
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user