Remove initial iterations of code for now, will ref it later
This commit is contained in:
parent
b84ed20c24
commit
25053cb2db
|
@ -1 +0,0 @@
|
||||||
authKey: 'existingkey'
|
|
|
@ -1,14 +0,0 @@
|
||||||
{
|
|
||||||
"uuid": "existingserver",
|
|
||||||
"keys": {
|
|
||||||
"existinggkey": [
|
|
||||||
"g:*"
|
|
||||||
],
|
|
||||||
"existingspecificskey": [
|
|
||||||
"s:test"
|
|
||||||
],
|
|
||||||
"existingglobalskey": [
|
|
||||||
"s:*"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
95
api/api.go
95
api/api.go
|
@ -1,95 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"github.com/pterodactyl/wings/config"
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
)
|
|
||||||
|
|
||||||
type InternalAPI struct {
|
|
||||||
router *gin.Engine
|
|
||||||
}
|
|
||||||
|
|
||||||
// Configure the API and begin listening on the configured IP and Port.
|
|
||||||
func (api *InternalAPI) Listen() {
|
|
||||||
if !viper.GetBool(config.Debug) {
|
|
||||||
gin.SetMode(gin.ReleaseMode)
|
|
||||||
}
|
|
||||||
|
|
||||||
api.router = gin.Default()
|
|
||||||
api.router.RedirectTrailingSlash = false
|
|
||||||
|
|
||||||
// Setup Access-Control origin headers. Down the road once this is closer to
|
|
||||||
// release we should setup this header properly and lock it down to the domain
|
|
||||||
// used to run the Panel.
|
|
||||||
api.router.Use(func(c *gin.Context) {
|
|
||||||
c.Header("Access-Control-Allow-Origin", "*")
|
|
||||||
})
|
|
||||||
|
|
||||||
api.router.OPTIONS("/", func(c *gin.Context) {
|
|
||||||
c.Header("Access-Control-Allow-Methods", "POST, GET, PUT, DELETE, OPTIONS")
|
|
||||||
c.Header("Access-Control-Allow-Headers", "Authorization")
|
|
||||||
})
|
|
||||||
|
|
||||||
// Register all of the API route bindings.
|
|
||||||
api.register()
|
|
||||||
|
|
||||||
l := fmt.Sprintf("%s:%d", viper.GetString(config.APIHost), viper.GetInt(config.APIPort))
|
|
||||||
api.router.Run(l)
|
|
||||||
|
|
||||||
logrus.Info("API Server is now listening on %s", l)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register routes for v1 of the API. This API should be fully backwards compatable with
|
|
||||||
// the existing Nodejs Daemon API.
|
|
||||||
//
|
|
||||||
// Routes that are not yet completed are commented out. Routes are grouped where possible
|
|
||||||
// to keep this function organized.
|
|
||||||
func (api *InternalAPI) register() {
|
|
||||||
v1 := api.router.Group("/api/v1")
|
|
||||||
{
|
|
||||||
v1.GET("", AuthHandler(""), GetIndex)
|
|
||||||
//v1.PATCH("/config", AuthHandler("c:config"), PatchConfiguration)
|
|
||||||
|
|
||||||
v1.GET("/servers", AuthHandler("c:list"), handleGetServers)
|
|
||||||
v1.POST("/servers", AuthHandler("c:create"), handlePostServers)
|
|
||||||
|
|
||||||
v1ServerRoutes := v1.Group("/servers/:server")
|
|
||||||
{
|
|
||||||
v1ServerRoutes.GET("", AuthHandler("s:get"), handleGetServer)
|
|
||||||
v1ServerRoutes.PATCH("", AuthHandler("s:config"), handlePatchServer)
|
|
||||||
v1ServerRoutes.DELETE("", AuthHandler("g:server:delete"), handleDeleteServer)
|
|
||||||
v1ServerRoutes.POST("/reinstall", AuthHandler("s:install-server"), handlePostServerReinstall)
|
|
||||||
v1ServerRoutes.POST("/rebuild", AuthHandler("g:server:rebuild"), handlePostServerRebuild)
|
|
||||||
v1ServerRoutes.POST("/password", AuthHandler(""), handlePostServerPassword)
|
|
||||||
v1ServerRoutes.POST("/power", AuthHandler("s:power"), handlePostServerPower)
|
|
||||||
v1ServerRoutes.POST("/command", AuthHandler("s:command"), handlePostServerCommand)
|
|
||||||
v1ServerRoutes.GET("/log", AuthHandler("s:console"), handleGetConsole)
|
|
||||||
v1ServerRoutes.POST("/suspend", AuthHandler(""), handlePostServerSuspend)
|
|
||||||
v1ServerRoutes.POST("/unsuspend", AuthHandler(""), handlePostServerUnsuspend)
|
|
||||||
}
|
|
||||||
|
|
||||||
//v1ServerFileRoutes := v1.Group("/servers/:server/files")
|
|
||||||
//{
|
|
||||||
// v1ServerFileRoutes.GET("/file/:file", AuthHandler("s:files:read"), handleGetFile)
|
|
||||||
// v1ServerFileRoutes.GET("/stat/:file", AuthHandler("s:files:"), handleGetFileStat)
|
|
||||||
// v1ServerFileRoutes.GET("/dir/:directory", AuthHandler("s:files:get"), handleGetDirectory)
|
|
||||||
//
|
|
||||||
// v1ServerFileRoutes.POST("/dir/:directory", AuthHandler("s:files:create"), handlePostFilesFolder)
|
|
||||||
// v1ServerFileRoutes.POST("/file/:file", AuthHandler("s:files:post"), handlePostFile)
|
|
||||||
//
|
|
||||||
// v1ServerFileRoutes.POST("/copy/:file", AuthHandler("s:files:copy"), handlePostFileCopy)
|
|
||||||
// v1ServerFileRoutes.POST("/move/:file", AuthHandler("s:files:move"), handlePostFileMove)
|
|
||||||
// v1ServerFileRoutes.POST("/rename/:file", AuthHandler("s:files:move"), handlePostFileMove)
|
|
||||||
// v1ServerFileRoutes.POST("/compress/:file", AuthHandler("s:files:compress"), handlePostFileCompress)
|
|
||||||
// v1ServerFileRoutes.POST("/decompress/:file", AuthHandler("s:files:decompress"), handlePostFileDecompress)
|
|
||||||
//
|
|
||||||
// v1ServerFileRoutes.DELETE("/file/:file", AuthHandler("s:files:delete"), handleDeleteFile)
|
|
||||||
//
|
|
||||||
// v1ServerFileRoutes.GET("/download/:token", handleGetDownloadFile)
|
|
||||||
//}
|
|
||||||
}
|
|
||||||
}
|
|
123
api/auth.go
123
api/auth.go
|
@ -1,123 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"github.com/google/jsonapi"
|
|
||||||
"github.com/pterodactyl/wings/config"
|
|
||||||
"github.com/pterodactyl/wings/control"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
accessTokenHeader = "Authorization"
|
|
||||||
|
|
||||||
contextVarServer = "server"
|
|
||||||
contextVarAuth = "auth"
|
|
||||||
)
|
|
||||||
|
|
||||||
type responseError struct {
|
|
||||||
Error string `json:"error"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// AuthorizationManager handles permission checks
|
|
||||||
type AuthorizationManager interface {
|
|
||||||
HasPermission(string) bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type authorizationManager struct {
|
|
||||||
token string
|
|
||||||
server control.Server
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ AuthorizationManager = &authorizationManager{}
|
|
||||||
|
|
||||||
func newAuthorizationManager(token string, server control.Server) *authorizationManager {
|
|
||||||
return &authorizationManager{
|
|
||||||
token: token,
|
|
||||||
server: server,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *authorizationManager) HasPermission(permission string) bool {
|
|
||||||
if permission == "" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
prefix := permission[:1]
|
|
||||||
if prefix == "c" {
|
|
||||||
return config.ContainsAuthKey(a.token)
|
|
||||||
}
|
|
||||||
if a.server == nil {
|
|
||||||
log.WithField("permission", permission).Error("Auth: Server required but none found.")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if prefix == "g" {
|
|
||||||
return config.ContainsAuthKey(a.token)
|
|
||||||
}
|
|
||||||
if prefix == "s" {
|
|
||||||
return a.server.HasPermission(a.token, permission) || config.ContainsAuthKey(a.token)
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// AuthHandler returns a HandlerFunc that checks request authentication
|
|
||||||
// permission is a permission string describing the required permission to access the route
|
|
||||||
//
|
|
||||||
// The AuthHandler looks for an access token header (defined in accessTokenHeader)
|
|
||||||
// or a `token` request parameter
|
|
||||||
func AuthHandler(permission string) gin.HandlerFunc {
|
|
||||||
return func(c *gin.Context) {
|
|
||||||
requestToken := c.Request.Header.Get(accessTokenHeader)
|
|
||||||
if requestToken != "" && strings.HasPrefix(requestToken, "Baerer ") {
|
|
||||||
requestToken = requestToken[7:]
|
|
||||||
} else {
|
|
||||||
requestToken = c.Query("token")
|
|
||||||
}
|
|
||||||
requestServer := c.Param("server")
|
|
||||||
var server control.Server
|
|
||||||
|
|
||||||
if requestToken == "" && permission != "" {
|
|
||||||
sendErrors(c, http.StatusUnauthorized, &jsonapi.ErrorObject{
|
|
||||||
Title: "Missing required " + accessTokenHeader + " header or token param.",
|
|
||||||
Status: strconv.Itoa(http.StatusUnauthorized),
|
|
||||||
})
|
|
||||||
c.Abort()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if requestServer != "" {
|
|
||||||
server = control.GetServer(requestServer)
|
|
||||||
//fmt.Println(server)
|
|
||||||
if server == nil {
|
|
||||||
log.WithField("serverUUID", requestServer).Error("Auth: Requested server not found.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auth := newAuthorizationManager(requestToken, server)
|
|
||||||
|
|
||||||
if auth.HasPermission(permission) {
|
|
||||||
c.Set(contextVarServer, server)
|
|
||||||
c.Set(contextVarAuth, auth)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
sendForbidden(c)
|
|
||||||
c.Abort()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetContextAuthManager returns a AuthorizationManager contained in
|
|
||||||
// a gin.Context or nil
|
|
||||||
func GetContextAuthManager(c *gin.Context) AuthorizationManager {
|
|
||||||
auth, exists := c.Get(contextVarAuth)
|
|
||||||
if !exists {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if auth, ok := auth.(AuthorizationManager); ok {
|
|
||||||
return auth
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
143
api/auth_test.go
143
api/auth_test.go
|
@ -1,143 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
|
|
||||||
"github.com/pterodactyl/wings/config"
|
|
||||||
"github.com/pterodactyl/wings/control"
|
|
||||||
)
|
|
||||||
|
|
||||||
const configFile = "_testdata/config.yml"
|
|
||||||
|
|
||||||
func TestAuthHandler(t *testing.T) {
|
|
||||||
gin.SetMode(gin.ReleaseMode)
|
|
||||||
|
|
||||||
t.Run("rejects missing token", func(t *testing.T) {
|
|
||||||
loadConfiguration(t, false)
|
|
||||||
|
|
||||||
responded, rec := requestMiddlewareWith("c:somepermission", "", "")
|
|
||||||
|
|
||||||
assert.False(t, responded)
|
|
||||||
assert.Equal(t, http.StatusBadRequest, rec.Code)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("rejects c:* with invalid key", func(t *testing.T) {
|
|
||||||
loadConfiguration(t, false)
|
|
||||||
|
|
||||||
responded, rec := requestMiddlewareWith("c:somepermission", "invalidkey", "")
|
|
||||||
|
|
||||||
assert.False(t, responded)
|
|
||||||
assert.Equal(t, http.StatusForbidden, rec.Code)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("accepts existing c: key", func(t *testing.T) {
|
|
||||||
loadConfiguration(t, false)
|
|
||||||
|
|
||||||
responded, rec := requestMiddlewareWith("c:somepermission", "existingkey", "") // TODO: working token
|
|
||||||
|
|
||||||
assert.True(t, responded)
|
|
||||||
assert.Equal(t, http.StatusOK, rec.Code)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("rejects not existing server", func(t *testing.T) {
|
|
||||||
loadConfiguration(t, true)
|
|
||||||
|
|
||||||
responded, rec := requestMiddlewareWith("g:testnotexisting", "existingkey", "notexistingserver")
|
|
||||||
|
|
||||||
assert.False(t, responded)
|
|
||||||
assert.Equal(t, http.StatusForbidden, rec.Code)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("accepts server with existing g: key", func(t *testing.T) {
|
|
||||||
loadConfiguration(t, true)
|
|
||||||
|
|
||||||
responded, rec := requestMiddlewareWith("g:test", "existingkey", "existingserver")
|
|
||||||
|
|
||||||
assert.True(t, responded)
|
|
||||||
assert.Equal(t, http.StatusOK, rec.Code)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("rejects server with not existing g: key", func(t *testing.T) {
|
|
||||||
loadConfiguration(t, true)
|
|
||||||
|
|
||||||
responded, rec := requestMiddlewareWith("g:test", "notexistingkey", "existingserver")
|
|
||||||
|
|
||||||
assert.False(t, responded)
|
|
||||||
assert.Equal(t, http.StatusForbidden, rec.Code)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("rejects server with not existing s: key", func(t *testing.T) {
|
|
||||||
loadConfiguration(t, true)
|
|
||||||
|
|
||||||
responded, rec := requestMiddlewareWith("s:test", "notexistingskey", "existingserver")
|
|
||||||
|
|
||||||
assert.False(t, responded)
|
|
||||||
assert.Equal(t, http.StatusForbidden, rec.Code)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("accepts server with existing s: key with specific permission", func(t *testing.T) {
|
|
||||||
loadConfiguration(t, true)
|
|
||||||
|
|
||||||
responded, rec := requestMiddlewareWith("s:test", "existingspecificskey", "existingserver")
|
|
||||||
|
|
||||||
assert.True(t, responded)
|
|
||||||
assert.Equal(t, http.StatusOK, rec.Code)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("accepts server with existing s: key with gloabl permission", func(t *testing.T) {
|
|
||||||
loadConfiguration(t, true)
|
|
||||||
|
|
||||||
responded, rec := requestMiddlewareWith("s:test", "existingglobalskey", "existingserver")
|
|
||||||
|
|
||||||
assert.True(t, responded)
|
|
||||||
assert.Equal(t, http.StatusOK, rec.Code)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("rejects server with existing s: key without permission", func(t *testing.T) {
|
|
||||||
loadConfiguration(t, true)
|
|
||||||
|
|
||||||
responded, rec := requestMiddlewareWith("s:without", "existingspecificskey", "existingserver")
|
|
||||||
|
|
||||||
assert.False(t, responded)
|
|
||||||
assert.Equal(t, http.StatusForbidden, rec.Code)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func requestMiddlewareWith(neededPermission string, token string, serverUUID string) (responded bool, recorder *httptest.ResponseRecorder) {
|
|
||||||
router := gin.New()
|
|
||||||
responded = false
|
|
||||||
recorder = httptest.NewRecorder()
|
|
||||||
req, _ := http.NewRequest("GET", "/"+serverUUID, nil)
|
|
||||||
|
|
||||||
endpoint := "/"
|
|
||||||
if serverUUID != "" {
|
|
||||||
endpoint += ":server"
|
|
||||||
}
|
|
||||||
|
|
||||||
router.GET(endpoint, AuthHandler(neededPermission), func(c *gin.Context) {
|
|
||||||
c.String(http.StatusOK, "Access granted.")
|
|
||||||
responded = true
|
|
||||||
})
|
|
||||||
|
|
||||||
req.Header.Set(accessTokenHeader, token)
|
|
||||||
router.ServeHTTP(recorder, req)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadConfiguration(t *testing.T, serverConfig bool) {
|
|
||||||
if err := config.LoadConfiguration(configFile); err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if serverConfig {
|
|
||||||
if err := control.LoadServerConfigurations("_testdata/servers/"); err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
144
api/handlers.go
144
api/handlers.go
|
@ -1,144 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
//"runtime"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"github.com/shirou/gopsutil/cpu"
|
|
||||||
//"github.com/shirou/gopsutil/host"
|
|
||||||
//"github.com/shirou/gopsutil/mem"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
func GetIndex(c *gin.Context) {
|
|
||||||
//auth := GetContextAuthManager(c)
|
|
||||||
//if auth == nil {
|
|
||||||
// c.Header("Content-Type", "text/html")
|
|
||||||
// c.String(http.StatusOK, constants.IndexPage)
|
|
||||||
//}
|
|
||||||
|
|
||||||
s, err := cpu.Counts(true)
|
|
||||||
if err != nil {
|
|
||||||
log.WithError(err).Error("Failed to retrieve host information.")
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println(s)
|
|
||||||
i := struct {
|
|
||||||
Name string
|
|
||||||
Cpu struct {
|
|
||||||
Cores int
|
|
||||||
}
|
|
||||||
}{
|
|
||||||
Name: "Wings",
|
|
||||||
}
|
|
||||||
|
|
||||||
i.Cpu.Cores = s
|
|
||||||
|
|
||||||
c.JSON(http.StatusOK, i)
|
|
||||||
return
|
|
||||||
|
|
||||||
//if auth != nil && auth.HasPermission("c:info") {
|
|
||||||
// hostInfo, err := host.Info()
|
|
||||||
// if err != nil {
|
|
||||||
// log.WithError(err).Error("Failed to retrieve host information.")
|
|
||||||
// }
|
|
||||||
// cpuInfo, err := cpu.Info()
|
|
||||||
// if err != nil {
|
|
||||||
// log.WithError(err).Error("Failed to retrieve CPU information.")
|
|
||||||
// }
|
|
||||||
// memInfo, err := mem.VirtualMemory()
|
|
||||||
// if err != nil {
|
|
||||||
// log.WithError(err).Error("Failed to retrieve memory information.")
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// info := struct {
|
|
||||||
// Name string `json:"name"`
|
|
||||||
// Version string `json:"version"`
|
|
||||||
// System struct {
|
|
||||||
// SystemType string `json:"type"`
|
|
||||||
// Platform string `json:"platform"`
|
|
||||||
// Arch string `json:"arch"`
|
|
||||||
// Release string `json:"release"`
|
|
||||||
// Cpus int32 `json:"cpus"`
|
|
||||||
// Freemem uint64 `json:"freemem"`
|
|
||||||
// } `json:"system"`
|
|
||||||
// }{
|
|
||||||
// Name: "Pterodactyl wings",
|
|
||||||
// Version: constants.Version,
|
|
||||||
// }
|
|
||||||
// info.System.SystemType = hostInfo.OS
|
|
||||||
// info.System.Platform = hostInfo.Platform
|
|
||||||
// info.System.Arch = runtime.GOARCH
|
|
||||||
// info.System.Release = hostInfo.KernelVersion
|
|
||||||
// info.System.Cpus = cpuInfo[0].Cores
|
|
||||||
// info.System.Freemem = memInfo.Free
|
|
||||||
//
|
|
||||||
// c.JSON(http.StatusOK, info)
|
|
||||||
// return
|
|
||||||
//}
|
|
||||||
}
|
|
||||||
|
|
||||||
type incomingConfiguration struct {
|
|
||||||
Debug bool `mapstructure:"debug"`
|
|
||||||
Web struct {
|
|
||||||
ListenHost string `mapstructure:"host"`
|
|
||||||
ListenPort int16 `mapstructure:"port"`
|
|
||||||
SSL struct {
|
|
||||||
Enabled bool `mapstructure:"enabled"`
|
|
||||||
Certificate string `mapstructure:"certificate"`
|
|
||||||
Key string `mapstructure:"key"`
|
|
||||||
} `mapstructure:"ssl"`
|
|
||||||
|
|
||||||
Uploads struct {
|
|
||||||
MaximumSize int64 `mapstructure:"maximumSize"`
|
|
||||||
} `mapstructure:"uploads"`
|
|
||||||
} `mapstructure:"web"`
|
|
||||||
|
|
||||||
Docker struct {
|
|
||||||
Socket string `mapstructure:"socket"`
|
|
||||||
AutoupdateImages bool `mapstructure:"autoupdateImages"`
|
|
||||||
NetworkInterface string `mapstructure:"networkInterface"`
|
|
||||||
TimezonePath string `mapstructure:"timezonePath"`
|
|
||||||
} `mapstructure:"docker"`
|
|
||||||
|
|
||||||
Sftp struct {
|
|
||||||
Path string `mapstructure:"path"`
|
|
||||||
Port int16 `mapstructure:"port"`
|
|
||||||
} `mapstructure:"sftp"`
|
|
||||||
|
|
||||||
Query struct {
|
|
||||||
KillOnFail bool `mapstructure:"killOnFail"`
|
|
||||||
FailLimit bool `mapstructure:"failLimit"`
|
|
||||||
} `mapstructure:"query"`
|
|
||||||
|
|
||||||
Remote string `mapstructure:"remote"`
|
|
||||||
|
|
||||||
Log struct {
|
|
||||||
Path string `mapstructure:"path"`
|
|
||||||
Level string `mapstructure:"level"`
|
|
||||||
DeleteAfterDays int `mapstructure:"deleteAfterDays"`
|
|
||||||
} `mapstructure:"log"`
|
|
||||||
|
|
||||||
AuthKeys []string `mapstructure:"authKeys"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// handlePatchConfig handles PATCH /config
|
|
||||||
func PatchConfiguration(c *gin.Context) {
|
|
||||||
// reqBody, err := ioutil.ReadAll(c.Request.Body)
|
|
||||||
// if err != nil {
|
|
||||||
// log.WithError(err).Error("Failed to read input.")
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
// reqJSON := new(incomingConfiguration)
|
|
||||||
// err = json.Unmarshal(reqBody, reqJSON)
|
|
||||||
// if err != nil {
|
|
||||||
// log.WithError(err).Error("Failed to decode JSON.")
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
var json incomingConfiguration
|
|
||||||
if err := c.BindJSON(&json); err != nil {
|
|
||||||
log.WithError(err).Error("Failed to bind Json.")
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,190 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"github.com/google/jsonapi"
|
|
||||||
"github.com/pterodactyl/wings/control"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
// GET /servers
|
|
||||||
func handleGetServers(c *gin.Context) {
|
|
||||||
servers := control.GetServers()
|
|
||||||
sendData(c, servers)
|
|
||||||
}
|
|
||||||
|
|
||||||
// POST /servers
|
|
||||||
func handlePostServers(c *gin.Context) {
|
|
||||||
server := control.ServerStruct{}
|
|
||||||
if err := c.BindJSON(&server); err != nil {
|
|
||||||
log.WithField("server", server).WithError(err).Error("Failed to parse server request.")
|
|
||||||
sendErrors(c, http.StatusBadRequest, &jsonapi.ErrorObject{
|
|
||||||
Status: strconv.Itoa(http.StatusBadRequest),
|
|
||||||
Title: "The passed server object is invalid.",
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var srv control.Server
|
|
||||||
var err error
|
|
||||||
if srv, err = control.CreateServer(&server); err != nil {
|
|
||||||
if _, ok := err.(control.ErrServerExists); ok {
|
|
||||||
log.WithError(err).Error("Cannot create server, it already exists.")
|
|
||||||
c.Status(http.StatusBadRequest)
|
|
||||||
sendErrors(c, http.StatusConflict, &jsonapi.ErrorObject{
|
|
||||||
Status: strconv.Itoa(http.StatusConflict),
|
|
||||||
Title: "A server with this ID already exists.",
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
log.WithField("server", server).WithError(err).Error("Failed to create server.")
|
|
||||||
sendInternalError(c, "Failed to create the server", "")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
go func() {
|
|
||||||
env, err := srv.Environment()
|
|
||||||
if err != nil {
|
|
||||||
log.WithField("server", srv).WithError(err).Error("Failed to get server environment.")
|
|
||||||
}
|
|
||||||
env.Create()
|
|
||||||
}()
|
|
||||||
sendDataStatus(c, http.StatusCreated, srv)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GET /servers/:server
|
|
||||||
func handleGetServer(c *gin.Context) {
|
|
||||||
id := c.Param("server")
|
|
||||||
server := control.GetServer(id)
|
|
||||||
if server == nil {
|
|
||||||
sendErrors(c, http.StatusNotFound, &jsonapi.ErrorObject{
|
|
||||||
Code: strconv.Itoa(http.StatusNotFound),
|
|
||||||
Title: "Server not found.",
|
|
||||||
Detail: "The requested Server with the id " + id + " couldn't be found.",
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
sendData(c, server)
|
|
||||||
}
|
|
||||||
|
|
||||||
// PATCH /servers/:server
|
|
||||||
func handlePatchServer(c *gin.Context) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// DELETE /servers/:server
|
|
||||||
func handleDeleteServer(c *gin.Context) {
|
|
||||||
id := c.Param("server")
|
|
||||||
server := control.GetServer(id)
|
|
||||||
if server == nil {
|
|
||||||
c.Status(http.StatusNotFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
env, err := server.Environment()
|
|
||||||
if err != nil {
|
|
||||||
sendInternalError(c, "The server could not be deleted.", "")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := env.Destroy(); err != nil {
|
|
||||||
log.WithError(err).Error("Failed to delete server, the environment couldn't be destroyed.")
|
|
||||||
sendInternalError(c, "The server could not be deleted.", "The server environment couldn't be destroyed.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := control.DeleteServer(id); err != nil {
|
|
||||||
log.WithError(err).Error("Failed to delete server.")
|
|
||||||
sendInternalError(c, "The server could not be deleted.", "")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.Status(http.StatusNoContent)
|
|
||||||
}
|
|
||||||
|
|
||||||
func handlePostServerReinstall(c *gin.Context) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func handlePostServerPassword(c *gin.Context) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func handlePostServerRebuild(c *gin.Context) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// POST /servers/:server/power
|
|
||||||
func handlePostServerPower(c *gin.Context) {
|
|
||||||
server := getServerFromContext(c)
|
|
||||||
if server == nil {
|
|
||||||
c.Status(http.StatusNotFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
auth := GetContextAuthManager(c)
|
|
||||||
if auth == nil {
|
|
||||||
sendInternalError(c, "An internal error occured.", "")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
switch c.Query("action") {
|
|
||||||
case "start":
|
|
||||||
{
|
|
||||||
if !auth.HasPermission("s:power:start") {
|
|
||||||
sendForbidden(c)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
server.Start()
|
|
||||||
}
|
|
||||||
case "stop":
|
|
||||||
{
|
|
||||||
if !auth.HasPermission("s:power:stop") {
|
|
||||||
sendForbidden(c)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
server.Stop()
|
|
||||||
}
|
|
||||||
case "restart":
|
|
||||||
{
|
|
||||||
if !auth.HasPermission("s:power:restart") {
|
|
||||||
sendForbidden(c)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
server.Restart()
|
|
||||||
}
|
|
||||||
case "kill":
|
|
||||||
{
|
|
||||||
if !auth.HasPermission("s:power:kill") {
|
|
||||||
sendForbidden(c)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
server.Kill()
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
{
|
|
||||||
c.Status(http.StatusBadRequest)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// POST /servers/:server/command
|
|
||||||
func handlePostServerCommand(c *gin.Context) {
|
|
||||||
server := getServerFromContext(c)
|
|
||||||
cmd := c.Query("command")
|
|
||||||
server.Exec(cmd)
|
|
||||||
c.Status(204)
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleGetConsole(c *gin.Context) {
|
|
||||||
server := getServerFromContext(c)
|
|
||||||
server.Websockets().Upgrade(c.Writer, c.Request)
|
|
||||||
}
|
|
||||||
|
|
||||||
func handlePostServerSuspend(c *gin.Context) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func handlePostServerUnsuspend(c *gin.Context) {
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,103 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"github.com/pterodactyl/wings/control"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestHandleGetServers(t *testing.T) {
|
|
||||||
router := gin.New()
|
|
||||||
req, _ := http.NewRequest("GET", "/servers", nil)
|
|
||||||
|
|
||||||
router.GET("/servers", handleGetServers)
|
|
||||||
|
|
||||||
t.Run("returns an empty json array when no servers are configured", func(t *testing.T) {
|
|
||||||
rec := httptest.NewRecorder()
|
|
||||||
router.ServeHTTP(rec, req)
|
|
||||||
body, err := ioutil.ReadAll(rec.Body)
|
|
||||||
assert.Equal(t, http.StatusOK, rec.Code)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.Equal(t, "[]\n", string(body))
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("returns an array of servers", func(t *testing.T) {
|
|
||||||
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
var testServer = control.ServerStruct{
|
|
||||||
ID: "id1",
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHandlePostServer(t *testing.T) {
|
|
||||||
// We need to decide how deep we want to go with testing here
|
|
||||||
// Should we just verify it works in general or test validation and
|
|
||||||
// minimal required config options as well?
|
|
||||||
// This will also vary depending on the Environment the server runs in
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHandleGetServer(t *testing.T) {
|
|
||||||
router := gin.New()
|
|
||||||
router.GET("/servers/:server", handleGetServer)
|
|
||||||
|
|
||||||
control.CreateServer(&testServer)
|
|
||||||
|
|
||||||
t.Run("returns not found when the server doesn't exist", func(t *testing.T) {
|
|
||||||
req, _ := http.NewRequest("GET", "/servers/id0", nil)
|
|
||||||
rec := httptest.NewRecorder()
|
|
||||||
router.ServeHTTP(rec, req)
|
|
||||||
assert.Equal(t, http.StatusNotFound, rec.Code)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("returns the server as json", func(t *testing.T) {
|
|
||||||
req, _ := http.NewRequest("GET", "/servers/id1", nil)
|
|
||||||
rec := httptest.NewRecorder()
|
|
||||||
router.ServeHTTP(rec, req)
|
|
||||||
assert.Equal(t, http.StatusOK, rec.Code)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHandlePatchServer(t *testing.T) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHandleDeleteServer(t *testing.T) {
|
|
||||||
router := gin.New()
|
|
||||||
router.DELETE("/servers/:server", handleDeleteServer)
|
|
||||||
|
|
||||||
control.CreateServer(&testServer)
|
|
||||||
|
|
||||||
t.Run("returns not found when the server doesn't exist", func(t *testing.T) {
|
|
||||||
req, _ := http.NewRequest("DELETE", "/servers/id0", nil)
|
|
||||||
rec := httptest.NewRecorder()
|
|
||||||
router.ServeHTTP(rec, req)
|
|
||||||
assert.Equal(t, http.StatusNotFound, rec.Code)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("deletes the server", func(t *testing.T) {
|
|
||||||
req, _ := http.NewRequest("DELETE", "/servers/id1", nil)
|
|
||||||
rec := httptest.NewRecorder()
|
|
||||||
router.ServeHTTP(rec, req)
|
|
||||||
assert.Equal(t, http.StatusOK, rec.Code)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func bodyToJSON(body *bytes.Buffer, v interface{}) error {
|
|
||||||
reqBody, err := ioutil.ReadAll(body)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = json.Unmarshal(reqBody, v)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,34 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestHandleGetIndex(t *testing.T) {
|
|
||||||
router := gin.New()
|
|
||||||
recorder := httptest.NewRecorder()
|
|
||||||
req, _ := http.NewRequest("GET", "/", nil)
|
|
||||||
|
|
||||||
router.GET("/", handleGetIndex)
|
|
||||||
router.ServeHTTP(recorder, req)
|
|
||||||
|
|
||||||
assert.Equal(t, http.StatusOK, recorder.Code)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHandlePatchConfig(t *testing.T) {
|
|
||||||
router := gin.New()
|
|
||||||
recorder := httptest.NewRecorder()
|
|
||||||
|
|
||||||
req, _ := http.NewRequest("PATCH", "/", strings.NewReader("{}"))
|
|
||||||
|
|
||||||
router.PATCH("/", handlePatchConfig)
|
|
||||||
router.ServeHTTP(recorder, req)
|
|
||||||
|
|
||||||
assert.Equal(t, http.StatusOK, recorder.Code)
|
|
||||||
}
|
|
45
api/utils.go
45
api/utils.go
|
@ -1,45 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"github.com/google/jsonapi"
|
|
||||||
"github.com/pterodactyl/wings/control"
|
|
||||||
)
|
|
||||||
|
|
||||||
func getServerFromContext(context *gin.Context) control.Server {
|
|
||||||
return control.GetServer(context.Param("server"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func sendErrors(c *gin.Context, s int, err ...*jsonapi.ErrorObject) {
|
|
||||||
c.Status(s)
|
|
||||||
c.Header("Content-Type", "application/json")
|
|
||||||
jsonapi.MarshalErrors(c.Writer, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func sendInternalError(c *gin.Context, title string, detail string) {
|
|
||||||
sendErrors(c, http.StatusInternalServerError, &jsonapi.ErrorObject{
|
|
||||||
Status: strconv.Itoa(http.StatusInternalServerError),
|
|
||||||
Title: title,
|
|
||||||
Detail: detail,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func sendForbidden(c *gin.Context) {
|
|
||||||
sendErrors(c, http.StatusForbidden, &jsonapi.ErrorObject{
|
|
||||||
Title: "The provided token has insufficient permissions to perform this action.",
|
|
||||||
Status: strconv.Itoa(http.StatusForbidden),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func sendData(c *gin.Context, payload interface{}) {
|
|
||||||
sendDataStatus(c, http.StatusOK, payload)
|
|
||||||
}
|
|
||||||
|
|
||||||
func sendDataStatus(c *gin.Context, status int, payload interface{}) {
|
|
||||||
c.Status(status)
|
|
||||||
c.Header("Content-Type", "application/json")
|
|
||||||
jsonapi.MarshalPayload(c.Writer, payload)
|
|
||||||
}
|
|
|
@ -1,120 +0,0 @@
|
||||||
package websockets
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/gorilla/websocket"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
writeWait = 10 * time.Second
|
|
||||||
pongWait = 60 * time.Second
|
|
||||||
|
|
||||||
pingPeriod = pongWait * 9 / 10
|
|
||||||
|
|
||||||
maxMessageSize = 512
|
|
||||||
)
|
|
||||||
|
|
||||||
var wsupgrader = websocket.Upgrader{
|
|
||||||
ReadBufferSize: 1024,
|
|
||||||
WriteBufferSize: 1024,
|
|
||||||
CheckOrigin: func(r *http.Request) bool {
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
type websocketMap map[*Socket]bool
|
|
||||||
|
|
||||||
type Collection struct {
|
|
||||||
sockets websocketMap
|
|
||||||
|
|
||||||
Broadcast chan Message
|
|
||||||
|
|
||||||
register chan *Socket
|
|
||||||
unregister chan *Socket
|
|
||||||
close chan bool
|
|
||||||
}
|
|
||||||
|
|
||||||
//var _ io.Writer = &Collection{}
|
|
||||||
|
|
||||||
func NewCollection() *Collection {
|
|
||||||
return &Collection{
|
|
||||||
Broadcast: make(chan Message),
|
|
||||||
register: make(chan *Socket),
|
|
||||||
unregister: make(chan *Socket),
|
|
||||||
close: make(chan bool),
|
|
||||||
sockets: make(websocketMap),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Collection) Upgrade(w http.ResponseWriter, r *http.Request) {
|
|
||||||
socket, err := wsupgrader.Upgrade(w, r, nil)
|
|
||||||
if err != nil {
|
|
||||||
log.WithError(err).Error("Failed to upgrade to websocket")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
s := &Socket{
|
|
||||||
collection: c,
|
|
||||||
conn: socket,
|
|
||||||
send: make(chan []byte, 256),
|
|
||||||
}
|
|
||||||
c.register <- s
|
|
||||||
|
|
||||||
go s.readPump()
|
|
||||||
go s.writePump()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Collection) Add(s *Socket) {
|
|
||||||
c.register <- s
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Collection) Remove(s *Socket) {
|
|
||||||
c.unregister <- s
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Collection) Run() {
|
|
||||||
defer func() {
|
|
||||||
for s := range c.sockets {
|
|
||||||
close(s.send)
|
|
||||||
delete(c.sockets, s)
|
|
||||||
}
|
|
||||||
close(c.register)
|
|
||||||
close(c.unregister)
|
|
||||||
close(c.Broadcast)
|
|
||||||
close(c.close)
|
|
||||||
}()
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case s := <-c.register:
|
|
||||||
c.sockets[s] = true
|
|
||||||
case s := <-c.unregister:
|
|
||||||
if _, ok := c.sockets[s]; ok {
|
|
||||||
delete(c.sockets, s)
|
|
||||||
close(s.send)
|
|
||||||
}
|
|
||||||
case m := <-c.Broadcast:
|
|
||||||
b, err := json.Marshal(m)
|
|
||||||
if err != nil {
|
|
||||||
log.WithError(err).Error("Failed to encode websocket message.")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for s := range c.sockets {
|
|
||||||
select {
|
|
||||||
case s.send <- b:
|
|
||||||
default:
|
|
||||||
close(s.send)
|
|
||||||
delete(c.sockets, s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case <-c.close:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Collection) Close() {
|
|
||||||
c.close <- true
|
|
||||||
}
|
|
|
@ -1,59 +0,0 @@
|
||||||
package websockets
|
|
||||||
|
|
||||||
type MessageType string
|
|
||||||
|
|
||||||
const (
|
|
||||||
MessageTypeProc MessageType = "proc"
|
|
||||||
MessageTypeConsole MessageType = "console"
|
|
||||||
MessageTypeStatus MessageType = "status"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Message is a message that can be sent using a websocket in JSON format
|
|
||||||
type Message struct {
|
|
||||||
// Type is the type of a websocket message
|
|
||||||
Type MessageType `json:"type"`
|
|
||||||
// Payload is the payload of the message
|
|
||||||
// The payload needs to support encoding in JSON
|
|
||||||
Payload interface{} `json:"payload"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ProcPayload struct {
|
|
||||||
Memory int `json:"memory"`
|
|
||||||
CPUCores []int `json:"cpu_cores"`
|
|
||||||
CPUTotal int `json:"cpu_total"`
|
|
||||||
Disk int `json:"disk"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ConsoleSource string
|
|
||||||
type ConsoleLevel string
|
|
||||||
|
|
||||||
const (
|
|
||||||
ConsoleSourceWings ConsoleSource = "wings"
|
|
||||||
ConsoleSourceServer ConsoleSource = "server"
|
|
||||||
|
|
||||||
ConsoleLevelPlain ConsoleLevel = "plain"
|
|
||||||
ConsoleLevelInfo ConsoleLevel = "info"
|
|
||||||
ConsoleLevelWarn ConsoleLevel = "warn"
|
|
||||||
ConsoleLevelError ConsoleLevel = "error"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ConsolePayload struct {
|
|
||||||
// Source is the source of the console line, either ConsoleSourceWings or ConsoleSourceServer
|
|
||||||
Source ConsoleSource `json:"source"`
|
|
||||||
// Level is the level of the message.
|
|
||||||
// Use one of plain, info, warn or error. If omitted the default is plain.
|
|
||||||
Level ConsoleLevel `json:"level,omitempty"`
|
|
||||||
// Line is the actual line to print to the console.
|
|
||||||
Line string `json:"line"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Collection) Log(l ConsoleLevel, m string) {
|
|
||||||
c.Broadcast <- Message{
|
|
||||||
Type: MessageTypeConsole,
|
|
||||||
Payload: ConsolePayload{
|
|
||||||
Source: ConsoleSourceWings,
|
|
||||||
Level: l,
|
|
||||||
Line: m,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,81 +0,0 @@
|
||||||
package websockets
|
|
||||||
|
|
||||||
import "github.com/gorilla/websocket"
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Socket struct {
|
|
||||||
collection *Collection
|
|
||||||
|
|
||||||
conn *websocket.Conn
|
|
||||||
|
|
||||||
send chan []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Socket) readPump() {
|
|
||||||
defer func() {
|
|
||||||
s.collection.unregister <- s
|
|
||||||
s.conn.Close()
|
|
||||||
}()
|
|
||||||
s.conn.SetReadLimit(maxMessageSize)
|
|
||||||
s.conn.SetReadDeadline(time.Now().Add(pongWait))
|
|
||||||
s.conn.SetPongHandler(func(string) error {
|
|
||||||
s.conn.SetReadDeadline(time.Now().Add(pongWait))
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
for {
|
|
||||||
t, m, err := s.conn.ReadMessage()
|
|
||||||
if err != nil {
|
|
||||||
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway) {
|
|
||||||
log.WithError(err).Debug("Websocket closed unexpectedly.")
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Handle websocket responses somehow
|
|
||||||
if t == websocket.TextMessage {
|
|
||||||
log.Debug("Received websocket message: " + string(m))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Socket) writePump() {
|
|
||||||
ticker := time.NewTicker(pingPeriod)
|
|
||||||
defer func() {
|
|
||||||
ticker.Stop()
|
|
||||||
s.conn.Close()
|
|
||||||
}()
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case m, ok := <-s.send:
|
|
||||||
s.conn.SetWriteDeadline(time.Now().Add(writeWait))
|
|
||||||
if !ok {
|
|
||||||
// The collection closed the channel
|
|
||||||
s.conn.WriteMessage(websocket.CloseMessage, []byte{})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
w, err := s.conn.NextWriter(websocket.TextMessage)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
w.Write([]byte{'['})
|
|
||||||
w.Write(m)
|
|
||||||
n := len(s.send) - 1
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
w.Write([]byte{','})
|
|
||||||
w.Write(<-s.send)
|
|
||||||
}
|
|
||||||
w.Write([]byte{']'})
|
|
||||||
if err := w.Close(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
case <-ticker.C:
|
|
||||||
s.conn.SetWriteDeadline(time.Now().Add(writeWait))
|
|
||||||
if err := s.conn.WriteMessage(websocket.PingMessage, nil); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,33 +0,0 @@
|
||||||
package control
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/pterodactyl/wings/api/websockets"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ConsoleHandler struct {
|
|
||||||
Websockets *websockets.Collection
|
|
||||||
HandlerFunc *func(string)
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ io.Writer = ConsoleHandler{}
|
|
||||||
|
|
||||||
func (c ConsoleHandler) Write(b []byte) (n int, e error) {
|
|
||||||
l := make([]byte, len(b))
|
|
||||||
copy(l, b)
|
|
||||||
line := string(l)
|
|
||||||
m := websockets.Message{
|
|
||||||
Type: websockets.MessageTypeConsole,
|
|
||||||
Payload: websockets.ConsolePayload{
|
|
||||||
Line: line,
|
|
||||||
Level: websockets.ConsoleLevelPlain,
|
|
||||||
Source: websockets.ConsoleSourceServer,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
c.Websockets.Broadcast <- m
|
|
||||||
if c.HandlerFunc != nil {
|
|
||||||
(*c.HandlerFunc)(line)
|
|
||||||
}
|
|
||||||
return len(b), nil
|
|
||||||
}
|
|
|
@ -1,72 +0,0 @@
|
||||||
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,245 +0,0 @@
|
||||||
package control
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
|
||||||
"github.com/docker/docker/api/types/container"
|
|
||||||
"github.com/docker/docker/client"
|
|
||||||
"github.com/pterodactyl/wings/constants"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
type dockerEnvironment struct {
|
|
||||||
baseEnvironment
|
|
||||||
|
|
||||||
client *client.Client
|
|
||||||
hires types.HijackedResponse
|
|
||||||
attached bool
|
|
||||||
|
|
||||||
server *ServerStruct
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 *ServerStruct) (Environment, error) {
|
|
||||||
env := dockerEnvironment{}
|
|
||||||
|
|
||||||
env.server = server
|
|
||||||
env.attached = false
|
|
||||||
|
|
||||||
cli, err := client.NewEnvClient()
|
|
||||||
env.client = cli
|
|
||||||
ctx := context.TODO()
|
|
||||||
cli.NegotiateAPIVersion(ctx)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.WithError(err).Fatal("Failed to connect to docker.")
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if env.server.DockerContainer.ID != "" {
|
|
||||||
if err := env.inspectContainer(ctx); 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) inspectContainer(ctx context.Context) error {
|
|
||||||
_, err := env.client.ContainerInspect(ctx, env.server.DockerContainer.ID)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (env *dockerEnvironment) attach() error {
|
|
||||||
if env.attached {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
env.attached = true
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
defer env.hires.Close()
|
|
||||||
defer func() {
|
|
||||||
env.attached = false
|
|
||||||
}()
|
|
||||||
io.Copy(cw, env.hires.Reader)
|
|
||||||
}()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create creates the docker container for the environment and applies all
|
|
||||||
// settings to it
|
|
||||||
func (env *dockerEnvironment) Create() error {
|
|
||||||
log.WithField("server", env.server.ID).Debug("Creating docker environment")
|
|
||||||
|
|
||||||
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.")
|
|
||||||
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 := &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,
|
|
||||||
},
|
|
||||||
// TODO: Allow custom binds via some kind of settings in the service
|
|
||||||
Binds: []string{env.server.dataPath() + ":/home/container"},
|
|
||||||
// TODO: Add port bindings
|
|
||||||
}
|
|
||||||
|
|
||||||
containerHostConfig.Memory = 0
|
|
||||||
|
|
||||||
container, err := env.client.ContainerCreate(ctx, containerConfig, containerHostConfig, nil, constants.DockerContainerPrefix+env.server.UUIDShort())
|
|
||||||
if err != nil {
|
|
||||||
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()
|
|
||||||
|
|
||||||
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("server", env.server.ID).Debug("Destroying docker environment")
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := env.client.ContainerRemove(ctx, env.server.DockerContainer.ID, types.ContainerRemoveOptions{}); err != nil {
|
|
||||||
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 err := env.inspectContainer(context.TODO()); err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start starts the environment's docker container
|
|
||||||
func (env *dockerEnvironment) Start() error {
|
|
||||||
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.ContainerStart(context.TODO(), env.server.DockerContainer.ID, types.ContainerStartOptions{}); 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("server", env.server.ID).Debug("Stopping service in docker environment")
|
|
||||||
|
|
||||||
// TODO: Decide after what timeout to kill the container, currently 30 seconds
|
|
||||||
timeout := 30 * time.Second
|
|
||||||
if err := env.client.ContainerStop(context.TODO(), env.server.DockerContainer.ID, &timeout); err != nil {
|
|
||||||
log.WithError(err).Error("Failed to stop docker container")
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (env *dockerEnvironment) Kill() error {
|
|
||||||
log.WithField("server", env.server.ID).Debug("Killing service in docker environment")
|
|
||||||
|
|
||||||
if err := env.client.ContainerKill(context.TODO(), env.server.DockerContainer.ID, "KILL"); 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 {
|
|
||||||
log.Debug("Command: " + command)
|
|
||||||
_, err := env.hires.Conn.Write([]byte(command + "\n"))
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
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{})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer rc.Close()
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,133 +0,0 @@
|
||||||
package control
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/pterodactyl/wings/api/websockets"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
|
||||||
"github.com/docker/docker/client"
|
|
||||||
"github.com/pterodactyl/wings/config"
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func testServer() *ServerStruct {
|
|
||||||
viper.SetDefault(config.DataPath, "./test_data")
|
|
||||||
return &ServerStruct{
|
|
||||||
ID: "testuuid-something-something",
|
|
||||||
Service: &Service{
|
|
||||||
DockerImage: "alpine:latest",
|
|
||||||
},
|
|
||||||
StartupCommand: "/bin/ash echo hello && sleep 100",
|
|
||||||
websockets: websockets.NewCollection(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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.server.DockerContainer)
|
|
||||||
|
|
||||||
eenv.Destroy()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCreateDockerEnvironment(t *testing.T) {
|
|
||||||
env, _ := createTestDockerEnv(nil)
|
|
||||||
|
|
||||||
err := env.Create()
|
|
||||||
|
|
||||||
a := assert.New(t)
|
|
||||||
a.Nil(err)
|
|
||||||
a.NotNil(env.server.DockerContainer.ID)
|
|
||||||
|
|
||||||
if err := env.client.ContainerRemove(context.TODO(), env.server.DockerContainer.ID, types.ContainerRemoveOptions{}); err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDestroyDockerEnvironment(t *testing.T) {
|
|
||||||
env, _ := createTestDockerEnv(nil)
|
|
||||||
env.Create()
|
|
||||||
|
|
||||||
err := env.Destroy()
|
|
||||||
|
|
||||||
_, ierr := env.client.ContainerInspect(context.TODO(), env.server.DockerContainer.ID)
|
|
||||||
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.True(t, client.IsErrNotFound(ierr))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStartDockerEnvironment(t *testing.T) {
|
|
||||||
env, _ := createTestDockerEnv(nil)
|
|
||||||
env.Create()
|
|
||||||
err := env.Start()
|
|
||||||
|
|
||||||
i, ierr := env.client.ContainerInspect(context.TODO(), env.server.DockerContainer.ID)
|
|
||||||
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.Nil(t, ierr)
|
|
||||||
assert.True(t, i.State.Running)
|
|
||||||
|
|
||||||
env.client.ContainerKill(context.TODO(), env.server.DockerContainer.ID, "KILL")
|
|
||||||
env.Destroy()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStopDockerEnvironment(t *testing.T) {
|
|
||||||
env, _ := createTestDockerEnv(nil)
|
|
||||||
env.Create()
|
|
||||||
env.Start()
|
|
||||||
err := env.Stop()
|
|
||||||
|
|
||||||
i, ierr := env.client.ContainerInspect(context.TODO(), env.server.DockerContainer.ID)
|
|
||||||
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.Nil(t, ierr)
|
|
||||||
assert.False(t, i.State.Running)
|
|
||||||
|
|
||||||
env.client.ContainerKill(context.TODO(), env.server.DockerContainer.ID, "KILL")
|
|
||||||
env.Destroy()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestKillDockerEnvironment(t *testing.T) {
|
|
||||||
env, _ := createTestDockerEnv(nil)
|
|
||||||
env.Create()
|
|
||||||
env.Start()
|
|
||||||
err := env.Kill()
|
|
||||||
|
|
||||||
i, ierr := env.client.ContainerInspect(context.TODO(), env.server.DockerContainer.ID)
|
|
||||||
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.Nil(t, ierr)
|
|
||||||
assert.False(t, i.State.Running)
|
|
||||||
|
|
||||||
env.client.ContainerKill(context.TODO(), env.server.DockerContainer.ID, "KILL")
|
|
||||||
env.Destroy()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestExecDockerEnvironment(t *testing.T) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func createTestDockerEnv(s *ServerStruct) (*dockerEnvironment, error) {
|
|
||||||
if s == nil {
|
|
||||||
s = testServer()
|
|
||||||
}
|
|
||||||
env, err := NewDockerEnvironment(s)
|
|
||||||
return env.(*dockerEnvironment), err
|
|
||||||
}
|
|
|
@ -1,235 +0,0 @@
|
||||||
package control
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
"github.com/pterodactyl/wings/api/websockets"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Status string
|
|
||||||
|
|
||||||
const (
|
|
||||||
StatusStopped Status = "stopped"
|
|
||||||
StatusStarting Status = "starting"
|
|
||||||
StatusRunning Status = "running"
|
|
||||||
StatusStopping Status = "stopping"
|
|
||||||
)
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
Websockets() *websockets.Collection
|
|
||||||
|
|
||||||
HasPermission(string, string) bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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" jsonapi:"primary,server"`
|
|
||||||
|
|
||||||
// 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 `json:"-" jsonapi:"relation,service"`
|
|
||||||
environment Environment
|
|
||||||
|
|
||||||
// StartupCommand is the command executed in the environment to start the server
|
|
||||||
StartupCommand string `json:"startupCommand" jsonapi:"attr,startup_command"`
|
|
||||||
|
|
||||||
// DockerContainer holds information regarding the docker container when the server
|
|
||||||
// is running in a docker environment
|
|
||||||
DockerContainer dockerContainer `json:"dockerContainer" jsonapi:"attr,docker_container"`
|
|
||||||
|
|
||||||
// EnvironmentVariables are set in the Environment the server is running in
|
|
||||||
EnvironmentVariables map[string]string `json:"environmentVariables" jsonapi:"attr,environment_variables"`
|
|
||||||
|
|
||||||
// Allocations contains the ports and ip addresses assigned to the server
|
|
||||||
Allocations allocations `json:"allocation" jsonapi:"attr,allocations"`
|
|
||||||
|
|
||||||
// Settings are the environment settings and limitations for the server
|
|
||||||
Settings settings `json:"settings" jsonapi:"attr,settings"`
|
|
||||||
|
|
||||||
// Keys are some auth keys we will hopefully replace by something better.
|
|
||||||
// TODO remove
|
|
||||||
Keys map[string][]string `json:"keys"`
|
|
||||||
|
|
||||||
websockets *websockets.Collection
|
|
||||||
status Status
|
|
||||||
}
|
|
||||||
|
|
||||||
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 = &ServerStruct{}
|
|
||||||
|
|
||||||
type serversMap map[string]*ServerStruct
|
|
||||||
|
|
||||||
var servers = make(serversMap)
|
|
||||||
|
|
||||||
// GetServers returns an array of all servers the daemon manages
|
|
||||||
func GetServers() []Server {
|
|
||||||
serverArray := make([]Server, len(servers))
|
|
||||||
i := 0
|
|
||||||
for _, s := range servers {
|
|
||||||
serverArray[i] = s
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
return serverArray
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetServer returns the server identified by the provided uuid
|
|
||||||
func GetServer(id string) Server {
|
|
||||||
server := servers[id]
|
|
||||||
if server == nil {
|
|
||||||
return nil // https://golang.org/doc/faq#nil_error
|
|
||||||
}
|
|
||||||
return 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 {
|
|
||||||
delete(servers, server.ID)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := server.init(); err != nil {
|
|
||||||
DeleteServer(server.ID)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return server, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteServer deletes a server and all related files
|
|
||||||
// NOTE: This is not reversible.
|
|
||||||
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) init() error {
|
|
||||||
// TODO: Properly use the correct service, mock for now.
|
|
||||||
s.Service = &Service{
|
|
||||||
DockerImage: "quay.io/pterodactyl/core:java",
|
|
||||||
EnvironmentName: "docker",
|
|
||||||
}
|
|
||||||
s.status = StatusStopped
|
|
||||||
|
|
||||||
s.websockets = websockets.NewCollection()
|
|
||||||
go s.websockets.Run()
|
|
||||||
|
|
||||||
var err error
|
|
||||||
if s.environment == nil {
|
|
||||||
switch s.GetService().EnvironmentName {
|
|
||||||
case "docker":
|
|
||||||
s.environment, err = NewDockerEnvironment(s)
|
|
||||||
default:
|
|
||||||
log.WithField("service", s.ServiceName).Error("Invalid environment name")
|
|
||||||
return errors.New("Invalid environment name")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ServerStruct) Start() error {
|
|
||||||
s.SetStatus(StatusStarting)
|
|
||||||
env, err := s.Environment()
|
|
||||||
if err != nil {
|
|
||||||
s.SetStatus(StatusStopped)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !env.Exists() {
|
|
||||||
if err := env.Create(); err != nil {
|
|
||||||
s.SetStatus(StatusStopped)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return env.Start()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ServerStruct) Stop() error {
|
|
||||||
s.SetStatus(StatusStopping)
|
|
||||||
env, err := s.Environment()
|
|
||||||
if err != nil {
|
|
||||||
s.SetStatus(StatusRunning)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
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 {
|
|
||||||
env, err := s.Environment()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return env.Exec(command)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ServerStruct) Rebuild() error {
|
|
||||||
env, err := s.Environment()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return env.ReCreate()
|
|
||||||
}
|
|
|
@ -1,108 +0,0 @@
|
||||||
package control
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"github.com/pterodactyl/wings/config"
|
|
||||||
"github.com/pterodactyl/wings/constants"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
)
|
|
||||||
|
|
||||||
// LoadServerConfigurations loads the configured servers from a specified path
|
|
||||||
func LoadServerConfigurations(path string) error {
|
|
||||||
serverFiles, err := ioutil.ReadDir(path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
servers = make(serversMap)
|
|
||||||
|
|
||||||
for _, file := range serverFiles {
|
|
||||||
if file.IsDir() {
|
|
||||||
server, err := loadServerConfiguration(filepath.Join(path, file.Name(), constants.ServerConfigFile))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
servers[server.ID] = server
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadServerConfiguration(path string) (*ServerStruct, error) {
|
|
||||||
file, err := ioutil.ReadFile(path)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
server := &ServerStruct{}
|
|
||||||
if err := json.Unmarshal(file, server); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := server.init(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
p, err := filepath.Abs(viper.GetString(config.DataPath))
|
|
||||||
if err != nil {
|
|
||||||
log.WithError(err).WithField("server", s.ID).Error("Failed to get absolute data path for server.")
|
|
||||||
p = viper.GetString(config.DataPath)
|
|
||||||
}
|
|
||||||
return filepath.Join(p, 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,49 +0,0 @@
|
||||||
package control
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/pterodactyl/wings/api/websockets"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (s *ServerStruct) SetStatus(st Status) {
|
|
||||||
s.status = st
|
|
||||||
s.websockets.Broadcast <- websockets.Message{
|
|
||||||
Type: websockets.MessageTypeStatus,
|
|
||||||
Payload: s.status,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Service returns the server's service configuration
|
|
||||||
func (s *ServerStruct) GetService() *Service {
|
|
||||||
return s.Service
|
|
||||||
}
|
|
||||||
|
|
||||||
// UUIDShort returns the first block of the UUID
|
|
||||||
func (s *ServerStruct) UUIDShort() string {
|
|
||||||
return s.ID[0:strings.Index(s.ID, "-")]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Environment returns the servers environment
|
|
||||||
func (s *ServerStruct) Environment() (Environment, error) {
|
|
||||||
return s.environment, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ServerStruct) Websockets() *websockets.Collection {
|
|
||||||
return s.websockets
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasPermission checks wether a provided token has a specific permission
|
|
||||||
func (s *ServerStruct) HasPermission(token string, permission string) bool {
|
|
||||||
for key, perms := range s.Keys {
|
|
||||||
if key == token {
|
|
||||||
for _, perm := range perms {
|
|
||||||
if perm == permission || perm == "s:*" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
package control
|
|
||||||
|
|
||||||
type Service struct {
|
|
||||||
server *Server
|
|
||||||
|
|
||||||
// EnvironmentName is the name of the environment used by the service
|
|
||||||
EnvironmentName string `json:"environmentName" jsonapi:"primary,service"`
|
|
||||||
|
|
||||||
DockerImage string `json:"dockerImage" jsonapi:"attr,docker_image"`
|
|
||||||
}
|
|
12
go.sum
12
go.sum
|
@ -1,13 +1,9 @@
|
||||||
github.com/Microsoft/go-winio v0.4.7/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
|
github.com/Microsoft/go-winio v0.4.7/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
|
||||||
github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
|
github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/docker/distribution v0.0.0-20180327202408-83389a148052 h1:bYklS+YB8BZreSEY+/WqaH+S8upfuYf0Hq/EmNOQMIA=
|
|
||||||
github.com/docker/distribution v0.0.0-20180327202408-83389a148052/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
github.com/docker/distribution v0.0.0-20180327202408-83389a148052/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||||
github.com/docker/docker v1.13.1 h1:IkZjBSIc8hBjLpqeAbeE5mca5mNgeatLHBy3GO78BWo=
|
|
||||||
github.com/docker/docker v1.13.1/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
github.com/docker/docker v1.13.1/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||||
github.com/docker/go-connections v0.3.0 h1:3lOnM9cSzgGwx8VfK/NGOW5fLQ0GjIlCkaktF+n1M6o=
|
|
||||||
github.com/docker/go-connections v0.3.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
github.com/docker/go-connections v0.3.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
||||||
github.com/docker/go-units v0.3.2 h1:Kjm80apys7gTtfVmCvVY8gwu10uofaFSrmAKOVrtueE=
|
|
||||||
github.com/docker/go-units v0.3.2/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
github.com/docker/go-units v0.3.2/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
|
github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
|
||||||
|
@ -24,14 +20,11 @@ github.com/lestrrat/go-strftime v0.0.0-20180220042222-ba3bf9c1d042/go.mod h1:TPp
|
||||||
github.com/magiconair/properties v1.7.6/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
github.com/magiconair/properties v1.7.6/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||||
github.com/mitchellh/mapstructure v0.0.0-20180220230111-00c29f56e238/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
github.com/mitchellh/mapstructure v0.0.0-20180220230111-00c29f56e238/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||||
github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ=
|
|
||||||
github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
|
github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
|
||||||
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
|
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
|
||||||
github.com/pelletier/go-toml v1.1.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
github.com/pelletier/go-toml v1.1.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||||
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
|
|
||||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/remeh/sizedwaitgroup v0.0.0-20180822144253-5e7302b12cce h1:aP+C+YbHZfOQlutA4p4soHi7rVUqHQdWEVMSkHfDTqY=
|
|
||||||
github.com/remeh/sizedwaitgroup v0.0.0-20180822144253-5e7302b12cce/go.mod h1:3j2R4OIe/SeS6YDhICBy22RWjJC5eNCJ1V+9+NVNYlo=
|
github.com/remeh/sizedwaitgroup v0.0.0-20180822144253-5e7302b12cce/go.mod h1:3j2R4OIe/SeS6YDhICBy22RWjJC5eNCJ1V+9+NVNYlo=
|
||||||
github.com/rifflock/lfshook v0.0.0-20180227222202-bf539943797a/go.mod h1:GEXHk5HgEKCvEIIrSpFI3ozzG5xOKA2DVlEX/gGnewM=
|
github.com/rifflock/lfshook v0.0.0-20180227222202-bf539943797a/go.mod h1:GEXHk5HgEKCvEIIrSpFI3ozzG5xOKA2DVlEX/gGnewM=
|
||||||
github.com/shirou/gopsutil v0.0.0-20180227225847-5776ff9c7c5d/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
github.com/shirou/gopsutil v0.0.0-20180227225847-5776ff9c7c5d/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
||||||
|
@ -44,19 +37,14 @@ github.com/spf13/pflag v1.0.0/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn
|
||||||
github.com/spf13/viper v1.0.2/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM=
|
github.com/spf13/viper v1.0.2/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM=
|
||||||
github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/ugorji/go v0.0.0-20180112141927-9831f2c3ac10/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ=
|
github.com/ugorji/go v0.0.0-20180112141927-9831f2c3ac10/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ=
|
||||||
go.uber.org/atomic v1.3.2 h1:2Oa65PReHzfn29GpvgsYwloV9AVFHPDk8tYxt2c2tr4=
|
|
||||||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||||
go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI=
|
|
||||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||||
go.uber.org/zap v1.9.1 h1:XCJQEf3W6eZaVwhRBof6ImoYGJSITeKWsyeh3HFu/5o=
|
|
||||||
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||||
golang.org/x/crypto v0.0.0-20180330210355-12892e8c234f/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.0.0-20180330210355-12892e8c234f/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
golang.org/x/net v0.0.0-20180330215511-b68f30494add h1:oGr9qHpQTQvl/BmeWw95ZrQKahW4qdIPUiGfQkJYDsA=
|
|
||||||
golang.org/x/net v0.0.0-20180330215511-b68f30494add/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180330215511-b68f30494add/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/sys v0.0.0-20180329131831-378d26f46672/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180329131831-378d26f46672/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
|
gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
|
||||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
|
Loading…
Reference in New Issue
Block a user