current state of api & docker env implementation
This commit is contained in:
@@ -2,8 +2,12 @@ package control
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/Pterodactyl/wings/constants"
|
||||
|
||||
"github.com/fsouza/go-dockerclient"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
@@ -15,6 +19,10 @@ type dockerEnvironment struct {
|
||||
container *docker.Container
|
||||
context context.Context
|
||||
|
||||
containerInput io.Writer
|
||||
containerOutput io.Writer
|
||||
closeWaiter docker.CloseWaiter
|
||||
|
||||
server *ServerStruct
|
||||
}
|
||||
|
||||
@@ -38,16 +46,17 @@ func NewDockerEnvironment(server *ServerStruct) (Environment, error) {
|
||||
}
|
||||
|
||||
if env.server.DockerContainer.ID != "" {
|
||||
if err := env.reattach(); err != nil {
|
||||
log.WithError(err).Error("Failed to reattach to existing container.")
|
||||
return nil, err
|
||||
if err := env.checkContainerExists(); err != nil {
|
||||
log.WithError(err).Error("Failed to find the container with stored id, removing id.")
|
||||
env.server.DockerContainer.ID = ""
|
||||
env.server.Save()
|
||||
}
|
||||
}
|
||||
|
||||
return &env, nil
|
||||
}
|
||||
|
||||
func (env *dockerEnvironment) reattach() error {
|
||||
func (env *dockerEnvironment) checkContainerExists() error {
|
||||
container, err := env.client.InspectContainer(env.server.DockerContainer.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -56,10 +65,31 @@ func (env *dockerEnvironment) reattach() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (env *dockerEnvironment) attach() error {
|
||||
pr, pw := io.Pipe()
|
||||
|
||||
success := make(chan struct{})
|
||||
w, err := env.client.AttachToContainerNonBlocking(docker.AttachToContainerOptions{
|
||||
Container: env.server.DockerContainer.ID,
|
||||
InputStream: pr,
|
||||
OutputStream: os.Stdout,
|
||||
Stdin: true,
|
||||
Stdout: true,
|
||||
Stream: true,
|
||||
Success: success,
|
||||
})
|
||||
env.closeWaiter = w
|
||||
env.containerInput = pw
|
||||
|
||||
<-success
|
||||
close(success)
|
||||
return err
|
||||
}
|
||||
|
||||
// Create creates the docker container for the environment and applies all
|
||||
// settings to it
|
||||
func (env *dockerEnvironment) Create() error {
|
||||
log.WithField("serverID", env.server.ID).Debug("Creating docker environment")
|
||||
log.WithField("server", env.server.ID).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], "/")
|
||||
@@ -77,18 +107,25 @@ func (env *dockerEnvironment) Create() error {
|
||||
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.ID).Error("Failed to create docker environment")
|
||||
log.WithError(err).WithField("server", env.server.ID).Error("Failed to create docker environment")
|
||||
return err
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(env.server.dataPath(), constants.DefaultFolderPerms); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create docker container
|
||||
// TODO: apply cpu, io, disk limits.
|
||||
containerConfig := &docker.Config{
|
||||
Image: env.server.Service().DockerImage,
|
||||
Image: env.server.Service().DockerImage,
|
||||
Cmd: strings.Split(env.server.StartupCommand, " "),
|
||||
OpenStdin: true,
|
||||
}
|
||||
containerHostConfig := &docker.HostConfig{
|
||||
Memory: env.server.Settings.Memory,
|
||||
MemorySwap: env.server.Settings.Swap,
|
||||
Binds: []string{env.server.dataPath() + ":/home/container"},
|
||||
}
|
||||
createContainerOpts := docker.CreateContainerOptions{
|
||||
Name: "ptdl-" + env.server.UUIDShort(),
|
||||
@@ -98,31 +135,55 @@ func (env *dockerEnvironment) Create() error {
|
||||
}
|
||||
container, err := env.client.CreateContainer(createContainerOpts)
|
||||
if err != nil {
|
||||
log.WithError(err).WithField("serverID", env.server.ID).Error("Failed to create docker container")
|
||||
log.WithError(err).WithField("server", env.server.ID).Error("Failed to create docker container")
|
||||
return err
|
||||
}
|
||||
env.server.DockerContainer.ID = container.ID
|
||||
env.server.Save()
|
||||
env.container = container
|
||||
|
||||
log.WithField("server", env.server.ID).Debug("Docker environment created")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Destroy removes the environment's docker container
|
||||
func (env *dockerEnvironment) Destroy() error {
|
||||
log.WithField("serverID", env.server.ID).Debug("Destroying docker environment")
|
||||
log.WithField("server", env.server.ID).Debug("Destroying docker environment")
|
||||
if _, err := env.client.InspectContainer(env.server.DockerContainer.ID); err != nil {
|
||||
if _, ok := err.(*docker.NoSuchContainer); ok {
|
||||
log.WithField("server", env.server.ID).Debug("Container not found, docker environment destroyed already.")
|
||||
return nil
|
||||
}
|
||||
log.WithError(err).WithField("server", env.server.ID).Error("Could not destroy docker environment")
|
||||
return err
|
||||
}
|
||||
err := env.client.RemoveContainer(docker.RemoveContainerOptions{
|
||||
ID: env.server.DockerContainer.ID,
|
||||
})
|
||||
if err != nil {
|
||||
log.WithError(err).WithField("serverID", env.server.ID).Error("Failed to destroy docker environment")
|
||||
log.WithError(err).WithField("server", env.server.ID).Error("Failed to destroy docker environment")
|
||||
return err
|
||||
}
|
||||
|
||||
log.WithField("server", env.server.ID).Debug("Docker environment destroyed")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (env *dockerEnvironment) Exists() bool {
|
||||
if env.container != nil {
|
||||
return true
|
||||
}
|
||||
env.checkContainerExists()
|
||||
return env.container != nil
|
||||
}
|
||||
|
||||
// Start starts the environment's docker container
|
||||
func (env *dockerEnvironment) Start() error {
|
||||
log.WithField("serverID", env.server.ID).Debug("Starting service in docker environment")
|
||||
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")
|
||||
}
|
||||
if err := env.client.StartContainer(env.container.ID, nil); err != nil {
|
||||
log.WithError(err).Error("Failed to start docker container")
|
||||
return err
|
||||
@@ -132,7 +193,7 @@ func (env *dockerEnvironment) Start() error {
|
||||
|
||||
// Stop stops the environment's docker container
|
||||
func (env *dockerEnvironment) Stop() error {
|
||||
log.WithField("serverID", env.server.ID).Debug("Stopping service in docker environment")
|
||||
log.WithField("server", env.server.ID).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
|
||||
@@ -141,7 +202,7 @@ func (env *dockerEnvironment) Stop() error {
|
||||
}
|
||||
|
||||
func (env *dockerEnvironment) Kill() error {
|
||||
log.WithField("serverID", env.server.ID).Debug("Killing service in docker environment")
|
||||
log.WithField("server", env.server.ID).Debug("Killing service in docker environment")
|
||||
if err := env.client.KillContainer(docker.KillContainerOptions{
|
||||
ID: env.container.ID,
|
||||
}); err != nil {
|
||||
@@ -153,5 +214,7 @@ func (env *dockerEnvironment) Kill() error {
|
||||
|
||||
// Exec sends commands to the standard input of the docker container
|
||||
func (env *dockerEnvironment) Exec(command string) error {
|
||||
return nil
|
||||
log.Debug("Command: " + command)
|
||||
_, err := env.containerInput.Write([]byte(command + "\n"))
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -4,22 +4,42 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/Pterodactyl/wings/config"
|
||||
"github.com/Pterodactyl/wings/constants"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// ErrServerExists is returned when a server already exists on creation.
|
||||
type ErrServerExists struct {
|
||||
id string
|
||||
}
|
||||
|
||||
func (e ErrServerExists) Error() string {
|
||||
return "server " + e.id + " already exists"
|
||||
}
|
||||
|
||||
// Server is a Server
|
||||
type Server interface {
|
||||
Start() error
|
||||
Stop() error
|
||||
Restart() error
|
||||
Kill() error
|
||||
Exec(command string) error
|
||||
Rebuild() error
|
||||
|
||||
Save() error
|
||||
|
||||
Environment() (Environment, error)
|
||||
|
||||
HasPermission(string, string) bool
|
||||
}
|
||||
|
||||
// server is a single instance of a Service managed by the panel
|
||||
// ServerStruct is a single instance of a Service managed by the panel
|
||||
type ServerStruct struct {
|
||||
// ID is the unique identifier of the server
|
||||
ID string `json:"uuid"`
|
||||
@@ -27,7 +47,7 @@ type ServerStruct struct {
|
||||
// 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
|
||||
service *Service
|
||||
environment Environment
|
||||
|
||||
// StartupCommand is the command executed in the environment to start the server
|
||||
@@ -88,8 +108,8 @@ func LoadServerConfigurations(path string) error {
|
||||
servers = make(serversMap)
|
||||
|
||||
for _, file := range serverFiles {
|
||||
if !file.IsDir() {
|
||||
server, err := loadServerConfiguration(path + file.Name())
|
||||
if file.IsDir() {
|
||||
server, err := loadServerConfiguration(filepath.Join(path, file.Name(), constants.ServerConfigFile))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -114,6 +134,38 @@ func loadServerConfiguration(path string) (*ServerStruct, error) {
|
||||
return server, nil
|
||||
}
|
||||
|
||||
func storeServerConfiguration(server *ServerStruct) error {
|
||||
serverJSON, err := json.MarshalIndent(server, "", constants.JSONIndent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.MkdirAll(server.path(), constants.DefaultFolderPerms); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ioutil.WriteFile(server.configFilePath(), serverJSON, constants.DefaultFilePerms); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func storeServerConfigurations() error {
|
||||
for _, s := range servers {
|
||||
if err := storeServerConfiguration(s); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func deleteServerFolder(id string) error {
|
||||
path := filepath.Join(viper.GetString(config.DataPath), constants.ServersPath, id)
|
||||
folder, err := os.Stat(path)
|
||||
if os.IsNotExist(err) || !folder.IsDir() {
|
||||
return err
|
||||
}
|
||||
return os.RemoveAll(path)
|
||||
}
|
||||
|
||||
// GetServers returns an array of all servers the daemon manages
|
||||
func GetServers() []Server {
|
||||
serverArray := make([]Server, len(servers))
|
||||
@@ -136,53 +188,83 @@ func GetServer(id string) Server {
|
||||
|
||||
// CreateServer creates a new server
|
||||
func CreateServer(server *ServerStruct) (Server, error) {
|
||||
if servers[server.ID] != nil {
|
||||
return nil, ErrServerExists{server.ID}
|
||||
}
|
||||
servers[server.ID] = server
|
||||
if err := server.Save(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return server, nil
|
||||
}
|
||||
|
||||
// DeleteServer deletes a server and all related files
|
||||
// NOTE: This is not reversible.
|
||||
func DeleteServer(uuid string) error {
|
||||
delete(servers, uuid)
|
||||
func DeleteServer(id string) error {
|
||||
if err := deleteServerFolder(id); err != nil {
|
||||
log.WithField("server", id).WithError(err).Error("Failed to delete server.")
|
||||
}
|
||||
delete(servers, id)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ServerStruct) Start() error {
|
||||
/*if err := s.Environment().Create(); err != nil {
|
||||
env, err := s.Environment()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.Environment().Start(); err != nil {
|
||||
return err
|
||||
}*/
|
||||
return nil
|
||||
if !env.Exists() {
|
||||
if err := env.Create(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return env.Start()
|
||||
}
|
||||
|
||||
func (s *ServerStruct) Stop() error {
|
||||
/*if err := s.Environment().Stop(); err != nil {
|
||||
env, err := s.Environment()
|
||||
if err != nil {
|
||||
return err
|
||||
}*/
|
||||
return nil
|
||||
}
|
||||
return env.Stop()
|
||||
}
|
||||
|
||||
func (s *ServerStruct) Restart() error {
|
||||
if err := s.Stop(); err != nil {
|
||||
return err
|
||||
}
|
||||
return s.Start()
|
||||
}
|
||||
|
||||
func (s *ServerStruct) Kill() error {
|
||||
env, err := s.Environment()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return env.Kill()
|
||||
}
|
||||
|
||||
func (s *ServerStruct) Exec(command string) error {
|
||||
/*if err := s.Environment().Exec(command); err != nil {
|
||||
env, err := s.Environment()
|
||||
if err != nil {
|
||||
return err
|
||||
}*/
|
||||
return nil
|
||||
}
|
||||
return env.Exec(command)
|
||||
}
|
||||
|
||||
func (s *ServerStruct) Rebuild() error {
|
||||
/*if err := s.Environment().ReCreate(); err != nil {
|
||||
env, err := s.Environment()
|
||||
if err != nil {
|
||||
return err
|
||||
}*/
|
||||
return nil
|
||||
}
|
||||
return env.ReCreate()
|
||||
}
|
||||
|
||||
// Service returns the server's service configuration
|
||||
func (s *ServerStruct) Service() *service {
|
||||
func (s *ServerStruct) Service() *Service {
|
||||
if s.service == nil {
|
||||
// TODO: Properly use the correct service, mock for now.
|
||||
s.service = &service{
|
||||
s.service = &Service{
|
||||
DockerImage: "quay.io/pterodactyl/core:java",
|
||||
EnvironmentName: "docker",
|
||||
}
|
||||
@@ -225,6 +307,22 @@ func (s *ServerStruct) HasPermission(token string, permission string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *ServerStruct) save() {
|
||||
|
||||
func (s *ServerStruct) Save() error {
|
||||
if err := storeServerConfiguration(s); err != nil {
|
||||
log.WithField("server", s.ID).WithError(err).Error("Failed to store server configuration.")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ServerStruct) path() string {
|
||||
return filepath.Join(viper.GetString(config.DataPath), constants.ServersPath, s.ID)
|
||||
}
|
||||
|
||||
func (s *ServerStruct) dataPath() string {
|
||||
return filepath.Join(s.path(), constants.ServerDataPath)
|
||||
}
|
||||
|
||||
func (s *ServerStruct) configFilePath() string {
|
||||
return filepath.Join(s.path(), constants.ServerConfigFile)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package control
|
||||
|
||||
type service struct {
|
||||
type Service struct {
|
||||
server *Server
|
||||
|
||||
// EnvironmentName is the name of the environment used by the service
|
||||
|
||||
Reference in New Issue
Block a user