diff --git a/http.go b/http.go index e978332..6ccefe1 100644 --- a/http.go +++ b/http.go @@ -5,7 +5,10 @@ import ( "fmt" "github.com/julienschmidt/httprouter" "github.com/pterodactyl/wings/server" + "go.uber.org/zap" + "io" "net/http" + "os" ) type ServerCollection []*server.Server @@ -57,12 +60,78 @@ func (rt *Router) routeServer(w http.ResponseWriter, _ *http.Request, ps httprou json.NewEncoder(w).Encode(s) } +type PowerActionRequest struct { + Action string `json:"action"` +} + +func (pr *PowerActionRequest) IsValid() bool { + return pr.Action == "start" || pr.Action == "stop" || pr.Action == "kill" || pr.Action == "restart" +} + +// Handles a request to control the power state of a server. If the action being passed +// through is invalid a 404 is returned. Otherwise, a HTTP/202 Accepted response is returned +// and the actual power action is run asynchronously so that we don't have to block the +// request until a potentially slow operation completes. +// +// This is done because for the most part the Panel is using websockets to determine when +// things are happening, so theres no reason to sit and wait for a request to finish. We'll +// just see over the socket if something isn't working correctly. +func (rt *Router) routeServerPower(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { + s := rt.Servers.Get(ps.ByName("server")) + defer r.Body.Close() + + dec := json.NewDecoder(r.Body) + var action PowerActionRequest + + if err := dec.Decode(&action); err != nil { + // Don't flood the logs with error messages if someone sends through bad + // JSON data. We don't really care. + if err != io.EOF { + zap.S().Errorw("failed to decode power action", zap.Error(err)) + } + + http.Error(w, "could not parse power action from request", http.StatusInternalServerError) + return + } + + if !action.IsValid() { + http.NotFound(w, r) + return + } + + // Pass the actual heavy processing off to a seperate thread to handle so that + // we can immediately return a response from the server. + go func(a string, s *server.Server) { + switch a { + case "start": + if err := s.Environment().Start(); err != nil { + zap.S().Error(err, zap.String("server", s.Uuid), zap.String("action", "start")) + } + break + case "stop": + if err := s.Environment().Stop(); err != nil { + zap.S().Error(err, zap.String("server", s.Uuid), zap.String("action", "stop")) + } + break + case "restart": + break + case "kill": + if err := s.Environment().Terminate(os.Kill); err != nil { + zap.S().Error(err, zap.String("server", s.Uuid), zap.String("action", "kill")) + } + } + }(action.Action, s) + + w.WriteHeader(http.StatusAccepted) +} + func (rt *Router) ConfigureRouter() *httprouter.Router { router := httprouter.New() router.GET("/", rt.routeIndex) router.GET("/api/servers", rt.routeAllServers) router.GET("/api/servers/:server", rt.AuthenticateServer(rt.routeServer)) + router.POST("/api/servers/:server/power", rt.AuthenticateServer(rt.routeServerPower)) return router -} \ No newline at end of file +} diff --git a/server/server.go b/server/server.go index 56111e4..078dc6c 100644 --- a/server/server.go +++ b/server/server.go @@ -191,6 +191,10 @@ func FromConfiguration(data []byte, cfg DockerConfiguration) (*Server, error) { return s, nil } +func (s *Server) Environment() Environment { + return s.environment +} + func (s *Server) Filesystem() *Filesystem { return s.fs }