current state of api & docker env implementation
This commit is contained in:
@@ -29,6 +29,15 @@ func (api *API) Listen() {
|
||||
|
||||
api.router = gin.Default()
|
||||
|
||||
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", "X-Access-Token")
|
||||
})
|
||||
|
||||
api.registerRoutes()
|
||||
|
||||
listenString := fmt.Sprintf("%s:%d", viper.GetString(config.APIHost), viper.GetInt(config.APIPort))
|
||||
|
||||
@@ -55,7 +55,7 @@ func (a *authorizationManager) HasPermission(permission string) bool {
|
||||
return config.ContainsAuthKey(a.token)
|
||||
}
|
||||
if prefix == "s" {
|
||||
return a.server.HasPermission(a.token, permission)
|
||||
return a.server.HasPermission(a.token, permission) || config.ContainsAuthKey(a.token)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"runtime"
|
||||
|
||||
"github.com/Pterodactyl/wings/constants"
|
||||
"github.com/gin-gonic/gin"
|
||||
@@ -35,16 +36,18 @@ func handleGetIndex(c *gin.Context) {
|
||||
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:"os"`
|
||||
} `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
|
||||
@@ -57,7 +60,65 @@ func handleGetIndex(c *gin.Context) {
|
||||
c.String(http.StatusOK, constants.IndexPage)
|
||||
}
|
||||
|
||||
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 handlePatchConfig(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,27 +1,94 @@
|
||||
package api
|
||||
|
||||
import "github.com/gin-gonic/gin"
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/Pterodactyl/wings/control"
|
||||
"github.com/gin-gonic/gin"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// GET /servers
|
||||
// TODO: make jsonapi compliant
|
||||
func handleGetServers(c *gin.Context) {
|
||||
|
||||
servers := control.GetServers()
|
||||
c.JSON(http.StatusOK, servers)
|
||||
}
|
||||
|
||||
// POST /servers
|
||||
// TODO: make jsonapi compliant
|
||||
func handlePostServers(c *gin.Context) {
|
||||
|
||||
}
|
||||
|
||||
func handleDeleteServers(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.")
|
||||
c.Status(http.StatusBadRequest)
|
||||
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)
|
||||
return
|
||||
}
|
||||
log.WithField("server", server).WithError(err).Error("Failed to create server.")
|
||||
c.Status(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
env, err := srv.Environment()
|
||||
if err != nil {
|
||||
log.WithField("server", srv).WithError(err).Error("Failed to get server environment.")
|
||||
}
|
||||
env.Create()
|
||||
}()
|
||||
c.JSON(http.StatusOK, srv)
|
||||
}
|
||||
|
||||
// GET /servers/:server
|
||||
// TODO: make jsonapi compliant
|
||||
func handleGetServer(c *gin.Context) {
|
||||
|
||||
id := c.Param("server")
|
||||
server := control.GetServer(id)
|
||||
if server == nil {
|
||||
c.Status(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, server)
|
||||
}
|
||||
|
||||
// PATCH /servers/:server
|
||||
func handlePatchServer(c *gin.Context) {
|
||||
|
||||
}
|
||||
|
||||
// DELETE /servers/:server
|
||||
// TODO: make jsonapi compliant
|
||||
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 {
|
||||
log.WithError(err).WithField("server", server).Error("Failed to delete server.")
|
||||
}
|
||||
if err := env.Destroy(); err != nil {
|
||||
log.WithError(err).Error("Failed to delete server, the environment couldn't be destroyed.")
|
||||
}
|
||||
|
||||
if err := control.DeleteServer(id); err != nil {
|
||||
log.WithError(err).Error("Failed to delete server.")
|
||||
c.Status(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
c.Status(http.StatusOK)
|
||||
}
|
||||
|
||||
func handlePostServerReinstall(c *gin.Context) {
|
||||
|
||||
}
|
||||
@@ -34,12 +101,67 @@ func handlePostServerRebuild(c *gin.Context) {
|
||||
|
||||
}
|
||||
|
||||
func handlePutServerPower(c *gin.Context) {
|
||||
// POST /servers/:server/power
|
||||
// TODO: make jsonapi compliant
|
||||
func handlePostServerPower(c *gin.Context) {
|
||||
server := getServerFromContext(c)
|
||||
if server == nil {
|
||||
c.Status(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
auth := GetContextAuthManager(c)
|
||||
if auth == nil {
|
||||
c.Status(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
switch c.Query("action") {
|
||||
case "start":
|
||||
{
|
||||
if !auth.HasPermission("s:power:start") {
|
||||
c.Status(http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
server.Start()
|
||||
}
|
||||
case "stop":
|
||||
{
|
||||
if !auth.HasPermission("s:power:stop") {
|
||||
c.Status(http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
server.Stop()
|
||||
}
|
||||
case "restart":
|
||||
{
|
||||
if !auth.HasPermission("s:power:restart") {
|
||||
c.Status(http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
server.Restart()
|
||||
}
|
||||
case "kill":
|
||||
{
|
||||
if !auth.HasPermission("s:power:kill") {
|
||||
c.Status(http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
server.Kill()
|
||||
}
|
||||
default:
|
||||
{
|
||||
c.Status(http.StatusBadRequest)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// POST /servers/:server/command
|
||||
// TODO: make jsonapi compliant
|
||||
func handlePostServerCommand(c *gin.Context) {
|
||||
|
||||
server := getServerFromContext(c)
|
||||
cmd := c.Query("command")
|
||||
server.Exec(cmd)
|
||||
}
|
||||
|
||||
func handleGetServerLog(c *gin.Context) {
|
||||
|
||||
103
api/handlers_server_test.go
Normal file
103
api/handlers_server_test.go
Normal file
@@ -0,0 +1,103 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/Pterodactyl/wings/control"
|
||||
"github.com/gin-gonic/gin"
|
||||
"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
|
||||
}
|
||||
@@ -12,7 +12,7 @@ func (api *API) registerServerRoutes() {
|
||||
api.router.POST("/servers/:server/reinstall", AuthHandler("s:install-server"), handlePostServerReinstall)
|
||||
api.router.POST("/servers/:server/rebuild", AuthHandler("g:server:rebuild"), handlePostServerRebuild)
|
||||
api.router.POST("/servers/:server/password", AuthHandler(""), handlePostServerPassword)
|
||||
api.router.POST("/servers/:server/power", AuthHandler("s:power"), handlePutServerPower)
|
||||
api.router.POST("/servers/:server/power", AuthHandler("s:power"), handlePostServerPower)
|
||||
api.router.POST("/servers/:server/command", AuthHandler("s:command"), handlePostServerCommand)
|
||||
api.router.GET("/servers/:server/log", AuthHandler("s:console"), handleGetServerLog)
|
||||
api.router.POST("/servers/:server/suspend", AuthHandler(""), handlePostServerSuspend)
|
||||
|
||||
10
api/utils.go
Normal file
10
api/utils.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/Pterodactyl/wings/control"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func getServerFromContext(context *gin.Context) control.Server {
|
||||
return control.GetServer(context.Param("server"))
|
||||
}
|
||||
Reference in New Issue
Block a user