From 830700d587072f277c9af9226df3dd1f87c0c4a4 Mon Sep 17 00:00:00 2001 From: Jakob Schrettenbrunner Date: Mon, 9 Oct 2017 20:52:53 +0200 Subject: [PATCH 01/19] add gorilla websocket --- glide.lock | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/glide.lock b/glide.lock index 58f8bba..427c2fd 100644 --- a/glide.lock +++ b/glide.lock @@ -1,10 +1,10 @@ -hash: e20502ccc280db349be5bb38269606eb6b84533b70ff81d7735ecc0e03256ead -updated: 2017-07-24T20:38:57.261980868+02:00 +hash: c55402eac6e4ebdefbac59afb32018eb6cb39af7062e85dd5d077fc975dd9909 +updated: 2017-10-04T15:36:59.363968994+02:00 imports: - name: bitbucket.org/tebeka/strftime version: 2194253a23c090a4d5953b152a3c63fb5da4f5a4 - name: github.com/Azure/go-ansiterm - version: 19f72df4d05d31cbe1c56bfc8045c96babff6c7e + version: d6e3b3328b783f23731bc4d058875b0371ff8109 subpackages: - winterm - name: github.com/docker/docker @@ -58,6 +58,8 @@ imports: version: 2402d76f3d41f928c7902a765dfc872356dd3aad subpackages: - proto +- name: github.com/gorilla/websocket + version: ea4d1f681babbce9545c9c5f3d5194a789c89f5b - name: github.com/hashicorp/go-cleanhttp version: 3573b8b52aa7b37b9358d966a898feb387f62437 - name: github.com/hashicorp/hcl @@ -92,7 +94,7 @@ imports: - name: github.com/Nvveen/Gotty version: cd527374f1e5bff4938207604a14f2e38a9cf512 - name: github.com/opencontainers/runc - version: 6ca8b741bb67839b7170d96257dde5c246f8b784 + version: 0351df1c5a66838d0c392b4ac4cf9450de844e2d subpackages: - libcontainer/system - libcontainer/user @@ -103,7 +105,7 @@ imports: - name: github.com/rifflock/lfshook version: 6844c808343cb8fa357d7f141b1b990e05d24e41 - name: github.com/shirou/gopsutil - version: aa0a3bce9d1f4efc710ed812f19f77851da2eedd + version: 6e221c482653ef05c9f6a7bf71ddceea0e40bac5 subpackages: - cpu - host @@ -114,9 +116,9 @@ imports: - name: github.com/shirou/w32 version: bb4de0191aa41b5507caa14b0650cdbddcd9280b - name: github.com/sirupsen/logrus - version: a3f95b5c423586578a4e099b11a46c2479628cac + version: f006c2ac4710855cf0f916dd6b77acf6b048dc6e - name: github.com/Sirupsen/logrus - version: a3f95b5c423586578a4e099b11a46c2479628cac + version: f006c2ac4710855cf0f916dd6b77acf6b048dc6e repo: https://github.com/sirupsen/logrus.git vcs: git - name: github.com/spf13/afero @@ -135,6 +137,10 @@ imports: version: c1de95864d73a5465492829d7cb2dd422b19ac96 - name: github.com/StackExchange/wmi version: ea383cf3ba6ec950874b8486cd72356d007c768f +- name: golang.org/x/crypto + version: 9419663f5a44be8b34ca85f08abc5fe1be11f8a3 + subpackages: + - ssh/terminal - name: golang.org/x/net version: f315505cf3349909cdf013ea56690da34e96a451 subpackages: From eb9102d32932e2f9f53906c7d96a3070c3769383 Mon Sep 17 00:00:00 2001 From: Jakob Schrettenbrunner Date: Mon, 9 Oct 2017 20:53:05 +0200 Subject: [PATCH 02/19] Revert "add gorilla websocket" This reverts commit 830700d587072f277c9af9226df3dd1f87c0c4a4. --- glide.lock | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/glide.lock b/glide.lock index 427c2fd..58f8bba 100644 --- a/glide.lock +++ b/glide.lock @@ -1,10 +1,10 @@ -hash: c55402eac6e4ebdefbac59afb32018eb6cb39af7062e85dd5d077fc975dd9909 -updated: 2017-10-04T15:36:59.363968994+02:00 +hash: e20502ccc280db349be5bb38269606eb6b84533b70ff81d7735ecc0e03256ead +updated: 2017-07-24T20:38:57.261980868+02:00 imports: - name: bitbucket.org/tebeka/strftime version: 2194253a23c090a4d5953b152a3c63fb5da4f5a4 - name: github.com/Azure/go-ansiterm - version: d6e3b3328b783f23731bc4d058875b0371ff8109 + version: 19f72df4d05d31cbe1c56bfc8045c96babff6c7e subpackages: - winterm - name: github.com/docker/docker @@ -58,8 +58,6 @@ imports: version: 2402d76f3d41f928c7902a765dfc872356dd3aad subpackages: - proto -- name: github.com/gorilla/websocket - version: ea4d1f681babbce9545c9c5f3d5194a789c89f5b - name: github.com/hashicorp/go-cleanhttp version: 3573b8b52aa7b37b9358d966a898feb387f62437 - name: github.com/hashicorp/hcl @@ -94,7 +92,7 @@ imports: - name: github.com/Nvveen/Gotty version: cd527374f1e5bff4938207604a14f2e38a9cf512 - name: github.com/opencontainers/runc - version: 0351df1c5a66838d0c392b4ac4cf9450de844e2d + version: 6ca8b741bb67839b7170d96257dde5c246f8b784 subpackages: - libcontainer/system - libcontainer/user @@ -105,7 +103,7 @@ imports: - name: github.com/rifflock/lfshook version: 6844c808343cb8fa357d7f141b1b990e05d24e41 - name: github.com/shirou/gopsutil - version: 6e221c482653ef05c9f6a7bf71ddceea0e40bac5 + version: aa0a3bce9d1f4efc710ed812f19f77851da2eedd subpackages: - cpu - host @@ -116,9 +114,9 @@ imports: - name: github.com/shirou/w32 version: bb4de0191aa41b5507caa14b0650cdbddcd9280b - name: github.com/sirupsen/logrus - version: f006c2ac4710855cf0f916dd6b77acf6b048dc6e + version: a3f95b5c423586578a4e099b11a46c2479628cac - name: github.com/Sirupsen/logrus - version: f006c2ac4710855cf0f916dd6b77acf6b048dc6e + version: a3f95b5c423586578a4e099b11a46c2479628cac repo: https://github.com/sirupsen/logrus.git vcs: git - name: github.com/spf13/afero @@ -137,10 +135,6 @@ imports: version: c1de95864d73a5465492829d7cb2dd422b19ac96 - name: github.com/StackExchange/wmi version: ea383cf3ba6ec950874b8486cd72356d007c768f -- name: golang.org/x/crypto - version: 9419663f5a44be8b34ca85f08abc5fe1be11f8a3 - subpackages: - - ssh/terminal - name: golang.org/x/net version: f315505cf3349909cdf013ea56690da34e96a451 subpackages: From 659dbcb8c32d0fef6d2d66a5b82efbff98f4868c Mon Sep 17 00:00:00 2001 From: Jakob Schrettenbrunner Date: Mon, 9 Oct 2017 21:00:55 +0200 Subject: [PATCH 03/19] add github.com/gorilla/websocket --- glide.lock | 18 ++++++++++++------ glide.yaml | 2 ++ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/glide.lock b/glide.lock index 58f8bba..83fec3a 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ -hash: e20502ccc280db349be5bb38269606eb6b84533b70ff81d7735ecc0e03256ead -updated: 2017-07-24T20:38:57.261980868+02:00 +hash: c55402eac6e4ebdefbac59afb32018eb6cb39af7062e85dd5d077fc975dd9909 +updated: 2017-10-09T20:56:24.390723792+02:00 imports: - name: bitbucket.org/tebeka/strftime version: 2194253a23c090a4d5953b152a3c63fb5da4f5a4 @@ -58,6 +58,8 @@ imports: version: 2402d76f3d41f928c7902a765dfc872356dd3aad subpackages: - proto +- name: github.com/gorilla/websocket + version: ea4d1f681babbce9545c9c5f3d5194a789c89f5b - name: github.com/hashicorp/go-cleanhttp version: 3573b8b52aa7b37b9358d966a898feb387f62437 - name: github.com/hashicorp/hcl @@ -103,7 +105,7 @@ imports: - name: github.com/rifflock/lfshook version: 6844c808343cb8fa357d7f141b1b990e05d24e41 - name: github.com/shirou/gopsutil - version: aa0a3bce9d1f4efc710ed812f19f77851da2eedd + version: 6e221c482653ef05c9f6a7bf71ddceea0e40bac5 subpackages: - cpu - host @@ -113,12 +115,12 @@ imports: - process - name: github.com/shirou/w32 version: bb4de0191aa41b5507caa14b0650cdbddcd9280b -- name: github.com/sirupsen/logrus - version: a3f95b5c423586578a4e099b11a46c2479628cac - name: github.com/Sirupsen/logrus - version: a3f95b5c423586578a4e099b11a46c2479628cac + version: f006c2ac4710855cf0f916dd6b77acf6b048dc6e repo: https://github.com/sirupsen/logrus.git vcs: git +- name: github.com/sirupsen/logrus + version: f006c2ac4710855cf0f916dd6b77acf6b048dc6e - name: github.com/spf13/afero version: 9be650865eab0c12963d8753212f4f9c66cdcf12 subpackages: @@ -135,6 +137,10 @@ imports: version: c1de95864d73a5465492829d7cb2dd422b19ac96 - name: github.com/StackExchange/wmi version: ea383cf3ba6ec950874b8486cd72356d007c768f +- name: golang.org/x/crypto + version: 9419663f5a44be8b34ca85f08abc5fe1be11f8a3 + subpackages: + - ssh/terminal - name: golang.org/x/net version: f315505cf3349909cdf013ea56690da34e96a451 subpackages: diff --git a/glide.yaml b/glide.yaml index 2d49599..4b6fe4f 100644 --- a/glide.yaml +++ b/glide.yaml @@ -22,6 +22,8 @@ import: - client - package: github.com/fsouza/go-dockerclient - package: github.com/hashicorp/go-cleanhttp +- package: github.com/gorilla/websocket + version: ~1.2.0 testImport: - package: github.com/stretchr/testify version: ~1.1.4 From 72ac95267b4c13b6a12e1cf89fe0823b8bbeffc7 Mon Sep 17 00:00:00 2001 From: Jakob Schrettenbrunner Date: Mon, 9 Oct 2017 21:01:24 +0200 Subject: [PATCH 04/19] fix some merge fails --- api/api.go | 5 +---- api/handlers.go | 2 +- api/routes.go | 46 +++++++++++++++++++++++----------------------- command/root.go | 4 ++-- utils/logging.go | 2 +- 5 files changed, 28 insertions(+), 31 deletions(-) diff --git a/api/api.go b/api/api.go index ce06713..4622c53 100644 --- a/api/api.go +++ b/api/api.go @@ -21,15 +21,12 @@ func NewAPI() InternalAPI { // Configure the API and begin listening on the configured IP and Port. func (api *InternalAPI) Listen() { - listener := fmt.Sprintf("%s:%d", viper.GetString(config.APIHost), viper.GetInt(config.APIPort)) - if !viper.GetBool(config.Debug) { gin.SetMode(gin.ReleaseMode) } api.router = gin.Default() api.router.RedirectTrailingSlash = false - api.RegisterRoutes() api.router.Use(func(c *gin.Context) { c.Header("Access-Control-Allow-Origin", "*") @@ -40,7 +37,7 @@ func (api *InternalAPI) Listen() { c.Header("Access-Control-Allow-Headers", "X-Access-Token") }) - api.registerRoutes() + api.RegisterRoutes() listenString := fmt.Sprintf("%s:%d", viper.GetString(config.APIHost), viper.GetInt(config.APIPort)) diff --git a/api/handlers.go b/api/handlers.go index 77a43eb..acb97dc 100644 --- a/api/handlers.go +++ b/api/handlers.go @@ -104,7 +104,7 @@ type incomingConfiguration struct { } // handlePatchConfig handles PATCH /config -func handlePatchConfig(c *gin.Context) { +func PatchConfiguration(c *gin.Context) { // reqBody, err := ioutil.ReadAll(c.Request.Body) // if err != nil { // log.WithError(err).Error("Failed to read input.") diff --git a/api/routes.go b/api/routes.go index 7804e19..f9a0c23 100644 --- a/api/routes.go +++ b/api/routes.go @@ -5,7 +5,7 @@ func (api *InternalAPI) RegisterRoutes() { // the existing Nodejs Daemon API. v1 := api.router.Group("/v1") { - v1.GET("/", AuthHandler(""), GetIndex) + v1.GET("", AuthHandler(""), GetIndex) v1.PATCH("/config", AuthHandler("c:config"), PatchConfiguration) v1.GET("/servers", AuthHandler("c:list"), handleGetServers) @@ -13,9 +13,9 @@ func (api *InternalAPI) RegisterRoutes() { 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.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) @@ -26,24 +26,24 @@ func (api *InternalAPI) RegisterRoutes() { 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) - } + //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) + //} } } diff --git a/command/root.go b/command/root.go index 195b86c..cdcbcc4 100644 --- a/command/root.go +++ b/command/root.go @@ -35,12 +35,12 @@ func Execute() { } func run(cmd *cobra.Command, args []string) { - tools.InitLogging() + utils.InitLogging() log.Info("Loading configuration...") if err := config.LoadConfiguration(configPath); err != nil { log.WithError(err).Fatal("Failed to find configuration file") } - tools.ConfigureLogging() + utils.ConfigureLogging() log.Info(` ____`) log.Info(`__ Pterodactyl _____/___/_______ _______ ______`) diff --git a/utils/logging.go b/utils/logging.go index ff05ebd..6312779 100644 --- a/utils/logging.go +++ b/utils/logging.go @@ -1,4 +1,4 @@ -package tools +package utils import ( "os" From 0b3fb72a97dac12d31b7d08e517a9bcd1bbfee61 Mon Sep 17 00:00:00 2001 From: Jakob Schrettenbrunner Date: Tue, 20 Feb 2018 21:16:50 +0100 Subject: [PATCH 05/19] update Vagrant to use bento/ubuntu and lowercase github namespace --- .dev/vagrant/provision.sh | 16 ++++++++-------- Vagrantfile | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.dev/vagrant/provision.sh b/.dev/vagrant/provision.sh index c92e9be..4048639 100644 --- a/.dev/vagrant/provision.sh +++ b/.dev/vagrant/provision.sh @@ -1,9 +1,9 @@ #!/bin/bash echo "Provisioning development environment for Pterodactyl go daemon." -cp /home/ubuntu/go/github.com/Pterodactyl/wings.go/.dev/vagrant/motd.txt /etc/motd +cp /home/vagrant/go/github.com/pterodactyl/wings.go/.dev/vagrant/motd.txt /etc/motd -chown -R ubuntu:ubuntu /home/ubuntu/go +chown -R ubuntu:ubuntu /home/vagrant/go chown -R ubuntu:ubuntu /srv echo "Update apt repositories" @@ -17,20 +17,20 @@ usermod -aG docker ubuntu echo "Install go" apt-get install -y golang-go -echo "export GOPATH=/home/ubuntu/go" >> /home/ubuntu/.profile +echo "export GOPATH=/home/vagrant/go" >> /home/vagrant/.profile export GOPATH=/go -echo 'export PATH=$PATH:$GOPATH/bin' >> /home/ubuntu/.profile +echo 'export PATH=$PATH:$GOPATH/bin' >> /home/vagrant/.profile echo "Install go dep" -sudo -H -u ubuntu bash -c 'go get -u github.com/golang/dep/cmd/dep' +sudo -H -u vagrant bash -c 'go get -u github.com/golang/dep/cmd/dep' echo "Install delve for debugging" -sudo -H -u ubuntu bash -c 'go get -u github.com/derekparker/delve/cmd/dlv' +sudo -H -u vagrant bash -c 'go get -u github.com/derekparker/delve/cmd/dlv' echo "Install additional dependencies" apt-get -y install mercurial #tar unzip make gcc g++ python > /dev/null echo " ------------" -echo "Gopath is /home/ubuntu/go" -echo "The project is mounted to /home/ubuntu/go/src/github.com/Pterodactyl/wings.go" +echo "Gopath is /home/vagrant/go" +echo "The project is mounted to /home/vagrant/go/src/github.com/pterodactyl/wings" echo "Provisioning is completed." diff --git a/Vagrantfile b/Vagrantfile index 8656ee4..22bcea7 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -1,7 +1,7 @@ Vagrant.configure("2") do |cfg| - cfg.vm.box = "ubuntu/xenial64" + cfg.vm.box = "bento/ubuntu-16.04" - cfg.vm.synced_folder "./", "/home/ubuntu/go/src/github.com/Pterodactyl/wings" + cfg.vm.synced_folder "./", "/home/vagrant/go/src/github.com/pterodactyl/wings" cfg.vm.provision :shell, path: ".dev/vagrant/provision.sh" From e4e060a82f1d0553fbf7f84e0cd01630105c3e0a Mon Sep 17 00:00:00 2001 From: Jakob Schrettenbrunner Date: Tue, 20 Feb 2018 21:25:31 +0100 Subject: [PATCH 06/19] rename to new lowercase github namespace --- .dev/vagrant/motd.txt | 2 +- api/api.go | 2 +- api/auth.go | 4 ++-- api/auth_test.go | 4 ++-- api/handlers.go | 2 +- api/handlers_server.go | 2 +- api/handlers_server_test.go | 2 +- api/utils.go | 2 +- command/root.go | 10 +++++----- control/docker_environment.go | 3 ++- control/server.go | 3 +-- utils/logging.go | 4 ++-- wings.go | 2 +- 13 files changed, 21 insertions(+), 21 deletions(-) diff --git a/.dev/vagrant/motd.txt b/.dev/vagrant/motd.txt index 39c0731..bb533fc 100644 --- a/.dev/vagrant/motd.txt +++ b/.dev/vagrant/motd.txt @@ -2,7 +2,7 @@ Pterodactyl go Daemon Vagrant VM Gopath: /home/ubuntu/go -Daemon: /home/ubuntu/go/src/github.com/Pterodactyl/wings.go +Daemon: /home/ubuntu/go/src/github.com/pterodactyl/wings.go Data: /srv/daemon-data ##################################################### diff --git a/api/api.go b/api/api.go index 4622c53..ee0751c 100644 --- a/api/api.go +++ b/api/api.go @@ -8,7 +8,7 @@ import ( log "github.com/sirupsen/logrus" "github.com/spf13/viper" - "github.com/Pterodactyl/wings/config" + "github.com/pterodactyl/wings/config" ) type InternalAPI struct { diff --git a/api/auth.go b/api/auth.go index 00cde92..ef67b66 100644 --- a/api/auth.go +++ b/api/auth.go @@ -3,9 +3,9 @@ package api import ( "net/http" - "github.com/Pterodactyl/wings/config" - "github.com/Pterodactyl/wings/control" "github.com/gin-gonic/gin" + "github.com/pterodactyl/wings/config" + "github.com/pterodactyl/wings/control" log "github.com/sirupsen/logrus" ) diff --git a/api/auth_test.go b/api/auth_test.go index c66e868..692e64a 100644 --- a/api/auth_test.go +++ b/api/auth_test.go @@ -8,8 +8,8 @@ import ( "github.com/gin-gonic/gin" "github.com/stretchr/testify/assert" - "github.com/Pterodactyl/wings/config" - "github.com/Pterodactyl/wings/control" + "github.com/pterodactyl/wings/config" + "github.com/pterodactyl/wings/control" ) const configFile = "_testdata/config.yml" diff --git a/api/handlers.go b/api/handlers.go index acb97dc..83cff30 100644 --- a/api/handlers.go +++ b/api/handlers.go @@ -4,8 +4,8 @@ import ( "net/http" "runtime" - "github.com/Pterodactyl/wings/constants" "github.com/gin-gonic/gin" + "github.com/pterodactyl/wings/constants" "github.com/shirou/gopsutil/cpu" "github.com/shirou/gopsutil/host" "github.com/shirou/gopsutil/mem" diff --git a/api/handlers_server.go b/api/handlers_server.go index 608cb54..09b10c3 100644 --- a/api/handlers_server.go +++ b/api/handlers_server.go @@ -3,8 +3,8 @@ package api import ( "net/http" - "github.com/Pterodactyl/wings/control" "github.com/gin-gonic/gin" + "github.com/pterodactyl/wings/control" log "github.com/sirupsen/logrus" ) diff --git a/api/handlers_server_test.go b/api/handlers_server_test.go index 25d15d9..34d3911 100644 --- a/api/handlers_server_test.go +++ b/api/handlers_server_test.go @@ -8,8 +8,8 @@ import ( "net/http/httptest" "testing" - "github.com/Pterodactyl/wings/control" "github.com/gin-gonic/gin" + "github.com/pterodactyl/wings/control" "github.com/stretchr/testify/assert" ) diff --git a/api/utils.go b/api/utils.go index d9a972e..c748c3a 100644 --- a/api/utils.go +++ b/api/utils.go @@ -1,8 +1,8 @@ package api import ( - "github.com/Pterodactyl/wings/control" "github.com/gin-gonic/gin" + "github.com/pterodactyl/wings/control" ) func getServerFromContext(context *gin.Context) control.Server { diff --git a/command/root.go b/command/root.go index cdcbcc4..272b1ea 100644 --- a/command/root.go +++ b/command/root.go @@ -6,11 +6,11 @@ import ( "github.com/spf13/viper" - "github.com/Pterodactyl/wings/api" - "github.com/Pterodactyl/wings/config" - "github.com/Pterodactyl/wings/constants" - "github.com/Pterodactyl/wings/control" - "github.com/Pterodactyl/wings/utils" + "github.com/pterodactyl/wings/api" + "github.com/pterodactyl/wings/config" + "github.com/pterodactyl/wings/constants" + "github.com/pterodactyl/wings/control" + "github.com/pterodactyl/wings/utils" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) diff --git a/control/docker_environment.go b/control/docker_environment.go index f9dd19f..8ae0051 100644 --- a/control/docker_environment.go +++ b/control/docker_environment.go @@ -6,9 +6,10 @@ import ( "os" "strings" - "github.com/Pterodactyl/wings/constants" + "github.com/pterodactyl/wings/constants" "github.com/fsouza/go-dockerclient" + "github.com/pterodactyl/wings/api/websockets" log "github.com/sirupsen/logrus" ) diff --git a/control/server.go b/control/server.go index 26fadbd..715636c 100644 --- a/control/server.go +++ b/control/server.go @@ -8,8 +8,7 @@ import ( "path/filepath" "strings" - "github.com/Pterodactyl/wings/config" - "github.com/Pterodactyl/wings/constants" + "github.com/pterodactyl/wings/api/websockets" log "github.com/sirupsen/logrus" "github.com/spf13/viper" ) diff --git a/utils/logging.go b/utils/logging.go index 6312779..d1a6851 100644 --- a/utils/logging.go +++ b/utils/logging.go @@ -5,14 +5,14 @@ import ( "path/filepath" "time" - "github.com/Pterodactyl/wings/constants" + "github.com/pterodactyl/wings/constants" rotatelogs "github.com/lestrrat/go-file-rotatelogs" "github.com/rifflock/lfshook" log "github.com/sirupsen/logrus" "github.com/spf13/viper" - "github.com/Pterodactyl/wings/config" + "github.com/pterodactyl/wings/config" ) // InitLogging initalizes the logging library for first use. diff --git a/wings.go b/wings.go index 68f8c0e..99a4704 100644 --- a/wings.go +++ b/wings.go @@ -4,7 +4,7 @@ import ( "fmt" "os" - "github.com/Pterodactyl/wings/command" + "github.com/pterodactyl/wings/command" ) func main() { From 31f4b465c17926f028c5bb26f01a0269998a58b2 Mon Sep 17 00:00:00 2001 From: Jakob Schrettenbrunner Date: Tue, 20 Feb 2018 21:36:07 +0100 Subject: [PATCH 07/19] replace glide with go dep --- Gopkg.lock | 362 +++++++++++++++++++++++++++++++++++++++++++++++++++++ Gopkg.toml | 62 +++++++++ glide.lock | 8 +- glide.yaml | 4 +- 4 files changed, 432 insertions(+), 4 deletions(-) create mode 100644 Gopkg.lock create mode 100644 Gopkg.toml diff --git a/Gopkg.lock b/Gopkg.lock new file mode 100644 index 0000000..0e18007 --- /dev/null +++ b/Gopkg.lock @@ -0,0 +1,362 @@ +# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. + + +[[projects]] + branch = "default" + name = "bitbucket.org/tebeka/strftime" + packages = ["."] + revision = "2194253a23c090a4d5953b152a3c63fb5da4f5a4" + +[[projects]] + name = "github.com/Azure/go-ansiterm" + packages = [ + ".", + "winterm" + ] + revision = "19f72df4d05d31cbe1c56bfc8045c96babff6c7e" + +[[projects]] + name = "github.com/Microsoft/go-winio" + packages = ["."] + revision = "c4dc1301f1dc0307acd38e611aa375a64dfe0642" + version = "v0.4.3" + +[[projects]] + branch = "master" + name = "github.com/Nvveen/Gotty" + packages = ["."] + revision = "cd527374f1e5bff4938207604a14f2e38a9cf512" + +[[projects]] + name = "github.com/StackExchange/wmi" + packages = ["."] + revision = "ea383cf3ba6ec950874b8486cd72356d007c768f" + +[[projects]] + branch = "master" + name = "github.com/containerd/continuity" + packages = ["pathdriver"] + revision = "d8fb8589b0e8e85b8c8bbaa8840226d0dfeb7371" + +[[projects]] + name = "github.com/davecgh/go-spew" + packages = ["spew"] + revision = "5215b55f46b2b919f50a1df0eaa5886afe4e3b3d" + +[[projects]] + branch = "master" + name = "github.com/docker/docker" + packages = [ + "api/types", + "api/types/blkiodev", + "api/types/container", + "api/types/filters", + "api/types/mount", + "api/types/network", + "api/types/registry", + "api/types/strslice", + "api/types/swarm", + "api/types/swarm/runtime", + "api/types/versions", + "opts", + "pkg/archive", + "pkg/fileutils", + "pkg/homedir", + "pkg/idtools", + "pkg/ioutils", + "pkg/jsonmessage", + "pkg/longpath", + "pkg/mount", + "pkg/pools", + "pkg/stdcopy", + "pkg/system", + "pkg/term", + "pkg/term/windows" + ] + revision = "ee9abc212032353e19e5b0f5e6410ad67a0cc9b1" + +[[projects]] + name = "github.com/docker/go-connections" + packages = ["nat"] + revision = "3ede32e2033de7505e6500d6c868c2b9ed9f169d" + version = "v0.3.0" + +[[projects]] + name = "github.com/docker/go-units" + packages = ["."] + revision = "0dadbb0345b35ec7ef35e228dabb8de89a65bf52" + version = "v0.3.2" + +[[projects]] + name = "github.com/fsnotify/fsnotify" + packages = ["."] + revision = "4da3e2cfbabc9f751898f250b49f2439785783a1" + +[[projects]] + name = "github.com/fsouza/go-dockerclient" + packages = ["."] + revision = "4df4873b288c855e4186534280c3a3a1af403e67" + +[[projects]] + name = "github.com/gin-gonic/gin" + packages = [ + ".", + "binding", + "render" + ] + revision = "e2212d40c62a98b388a5eb48ecbdcf88534688ba" + version = "v1.1.4" + +[[projects]] + name = "github.com/go-ole/go-ole" + packages = [ + ".", + "oleutil" + ] + revision = "085abb85892dc1949567b726dff00fa226c60c45" + +[[projects]] + name = "github.com/gogo/protobuf" + packages = ["proto"] + revision = "1adfc126b41513cc696b209667c8656ea7aac67c" + version = "v1.0.0" + +[[projects]] + name = "github.com/golang/protobuf" + packages = ["proto"] + revision = "2402d76f3d41f928c7902a765dfc872356dd3aad" + +[[projects]] + name = "github.com/google/jsonapi" + packages = ["."] + revision = "46d3ced0434461be12e555852e2f1a9ed382e139" + version = "1.0.0" + +[[projects]] + name = "github.com/gorilla/websocket" + packages = ["."] + revision = "ea4d1f681babbce9545c9c5f3d5194a789c89f5b" + version = "v1.2.0" + +[[projects]] + name = "github.com/hashicorp/go-cleanhttp" + packages = ["."] + revision = "3573b8b52aa7b37b9358d966a898feb387f62437" + +[[projects]] + name = "github.com/hashicorp/hcl" + packages = [ + ".", + "hcl/ast", + "hcl/parser", + "hcl/scanner", + "hcl/strconv", + "hcl/token", + "json/parser", + "json/scanner", + "json/token" + ] + revision = "392dba7d905ed5d04a5794ba89f558b27e2ba1ca" + +[[projects]] + name = "github.com/inconshreveable/mousetrap" + packages = ["."] + revision = "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75" + version = "v1.0" + +[[projects]] + name = "github.com/lestrrat/go-file-rotatelogs" + packages = ["."] + revision = "ab335c655133cea61d8164853c1ed0e97d6c77cb" + version = "v2.0.0" + +[[projects]] + name = "github.com/magiconair/properties" + packages = ["."] + revision = "51463bfca2576e06c62a8504b5c0f06d61312647" + +[[projects]] + branch = "master" + name = "github.com/manucorporat/sse" + packages = ["."] + revision = "ee05b128a739a0fb76c7ebd3ae4810c1de808d6d" + +[[projects]] + name = "github.com/mattn/go-isatty" + packages = ["."] + revision = "fc9e8d8ef48496124e79ae0df75490096eccf6fe" + version = "v0.0.2" + +[[projects]] + name = "github.com/mitchellh/mapstructure" + packages = ["."] + revision = "d0303fe809921458f417bcf828397a65db30a7e4" + +[[projects]] + name = "github.com/opencontainers/go-digest" + packages = ["."] + revision = "279bed98673dd5bef374d3b6e4b09e2af76183bf" + version = "v1.0.0-rc1" + +[[projects]] + name = "github.com/opencontainers/image-spec" + packages = [ + "specs-go", + "specs-go/v1" + ] + revision = "d60099175f88c47cd379c4738d158884749ed235" + version = "v1.0.1" + +[[projects]] + name = "github.com/opencontainers/runc" + packages = [ + "libcontainer/system", + "libcontainer/user" + ] + revision = "6ca8b741bb67839b7170d96257dde5c246f8b784" + +[[projects]] + name = "github.com/pelletier/go-buffruneio" + packages = ["."] + revision = "c37440a7cf42ac63b919c752ca73a85067e05992" + version = "v0.2.0" + +[[projects]] + name = "github.com/pelletier/go-toml" + packages = ["."] + revision = "4a000a21a414d139727f616a8bb97f847b1b310b" + +[[projects]] + name = "github.com/pkg/errors" + packages = ["."] + revision = "645ef00459ed84a119197bfb8d8205042c6df63d" + version = "v0.8.0" + +[[projects]] + name = "github.com/pmezard/go-difflib" + packages = ["difflib"] + revision = "792786c7400a136282c1664665ae0a8db921c6c2" + version = "v1.0.0" + +[[projects]] + name = "github.com/rifflock/lfshook" + packages = ["."] + revision = "6844c808343cb8fa357d7f141b1b990e05d24e41" + version = "v1.7" + +[[projects]] + name = "github.com/shirou/gopsutil" + packages = [ + "cpu", + "host", + "internal/common", + "mem", + "net", + "process" + ] + revision = "6e221c482653ef05c9f6a7bf71ddceea0e40bac5" + version = "v2.17.09" + +[[projects]] + branch = "master" + name = "github.com/shirou/w32" + packages = ["."] + revision = "bb4de0191aa41b5507caa14b0650cdbddcd9280b" + +[[projects]] + name = "github.com/sirupsen/logrus" + packages = ["."] + revision = "f006c2ac4710855cf0f916dd6b77acf6b048dc6e" + version = "v1.0.3" + +[[projects]] + name = "github.com/spf13/afero" + packages = [ + ".", + "mem" + ] + revision = "9be650865eab0c12963d8753212f4f9c66cdcf12" + +[[projects]] + name = "github.com/spf13/cast" + packages = ["."] + revision = "acbeb36b902d72a7a4c18e8f3241075e7ab763e4" + version = "v1.1.0" + +[[projects]] + name = "github.com/spf13/cobra" + packages = ["."] + revision = "90fc11bbc0a789c29272c21b5ff9e93db183f8dc" + +[[projects]] + name = "github.com/spf13/jwalterweatherman" + packages = ["."] + revision = "0efa5202c04663c757d84f90f5219c1250baf94f" + +[[projects]] + name = "github.com/spf13/pflag" + packages = ["."] + revision = "e57e3eeb33f795204c1ca35f56c44f83227c6e66" + version = "v1.0.0" + +[[projects]] + name = "github.com/spf13/viper" + packages = ["."] + revision = "c1de95864d73a5465492829d7cb2dd422b19ac96" + +[[projects]] + name = "github.com/stretchr/testify" + packages = ["assert"] + revision = "69483b4bd14f5845b5a1e55bca19e954e827f1d0" + version = "v1.1.4" + +[[projects]] + name = "golang.org/x/crypto" + packages = ["ssh/terminal"] + revision = "bd6f299fb381e4c3393d1c4b1f0b94f5e77650c8" + +[[projects]] + name = "golang.org/x/net" + packages = [ + "context", + "context/ctxhttp" + ] + revision = "f315505cf3349909cdf013ea56690da34e96a451" + +[[projects]] + name = "golang.org/x/sys" + packages = [ + "unix", + "windows" + ] + revision = "f7928cfef4d09d1b080aa2b6fd3ca9ba1567c733" + +[[projects]] + name = "golang.org/x/text" + packages = [ + "internal/gen", + "internal/triegen", + "internal/ucd", + "transform", + "unicode/cldr", + "unicode/norm" + ] + revision = "5a2c30c33799f1e813f7f3259000d594a5ed493a" + +[[projects]] + name = "gopkg.in/go-playground/validator.v8" + packages = ["."] + revision = "c193cecd124b5cc722d7ee5538e945bdb3348435" + version = "v8.15.1" + +[[projects]] + name = "gopkg.in/yaml.v2" + packages = ["."] + revision = "cd8b52f8269e0feb286dfeef29f8fe4d5b397e0b" + +[solve-meta] + analyzer-name = "dep" + analyzer-version = 1 + inputs-digest = "983bf3264628787237fa4445a0aa430be028dbfb056756d99a30e583d14e8514" + solver-name = "gps-cdcl" + solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml new file mode 100644 index 0000000..31785ab --- /dev/null +++ b/Gopkg.toml @@ -0,0 +1,62 @@ +# Gopkg.toml example +# +# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html +# for detailed Gopkg.toml documentation. +# +# required = ["github.com/user/thing/cmd/thing"] +# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] +# +# [[constraint]] +# name = "github.com/user/project" +# version = "1.0.0" +# +# [[constraint]] +# name = "github.com/user/project2" +# branch = "dev" +# source = "github.com/myfork/project2" +# +# [[override]] +# name = "github.com/x/y" +# version = "2.4.0" +# +# [prune] +# non-go = false +# go-tests = true +# unused-packages = true + + +[[constraint]] + name = "github.com/gin-gonic/gin" + version = "~1.1.4" + +[[constraint]] + name = "github.com/google/jsonapi" + version = "~1.0.0" + +[[constraint]] + name = "github.com/gorilla/websocket" + version = "~1.2.0" + +[[constraint]] + name = "github.com/lestrrat/go-file-rotatelogs" + version = "~2.0.0" + +[[constraint]] + name = "github.com/rifflock/lfshook" + version = "~1.7.0" + +[[constraint]] + name = "github.com/shirou/gopsutil" + version = "2.17.6" + +[[constraint]] + name = "github.com/sirupsen/logrus" + version = "~1.0.0" + +[[constraint]] + name = "github.com/stretchr/testify" + version = "~1.1.4" + +[prune] + go-tests = true + unused-packages = true diff --git a/glide.lock b/glide.lock index 83fec3a..5368cd9 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ -hash: c55402eac6e4ebdefbac59afb32018eb6cb39af7062e85dd5d077fc975dd9909 -updated: 2017-10-09T20:56:24.390723792+02:00 +hash: 59375e40229965f33d35e16d1ce13db87188dc5a70f89cf54c2921eb9475ba4f +updated: 2017-11-03T18:09:41.337408376+01:00 imports: - name: bitbucket.org/tebeka/strftime version: 2194253a23c090a4d5953b152a3c63fb5da4f5a4 @@ -58,6 +58,8 @@ imports: version: 2402d76f3d41f928c7902a765dfc872356dd3aad subpackages: - proto +- name: github.com/google/jsonapi + version: 46d3ced0434461be12e555852e2f1a9ed382e139 - name: github.com/gorilla/websocket version: ea4d1f681babbce9545c9c5f3d5194a789c89f5b - name: github.com/hashicorp/go-cleanhttp @@ -138,7 +140,7 @@ imports: - name: github.com/StackExchange/wmi version: ea383cf3ba6ec950874b8486cd72356d007c768f - name: golang.org/x/crypto - version: 9419663f5a44be8b34ca85f08abc5fe1be11f8a3 + version: bd6f299fb381e4c3393d1c4b1f0b94f5e77650c8 subpackages: - ssh/terminal - name: golang.org/x/net diff --git a/glide.yaml b/glide.yaml index 4b6fe4f..e79a056 100644 --- a/glide.yaml +++ b/glide.yaml @@ -1,4 +1,4 @@ -package: github.com/Pterodactyl/wings +package: github.com/pterodactyl/wings import: - package: github.com/gin-gonic/gin version: ~1.1.4 @@ -24,6 +24,8 @@ import: - package: github.com/hashicorp/go-cleanhttp - package: github.com/gorilla/websocket version: ~1.2.0 +- package: github.com/google/jsonapi + version: ~1.0.0 testImport: - package: github.com/stretchr/testify version: ~1.1.4 From 184d7e0afe1531b6c5bd43430ac756468ba00cb1 Mon Sep 17 00:00:00 2001 From: Jakob Schrettenbrunner Date: Tue, 20 Feb 2018 23:36:17 +0100 Subject: [PATCH 08/19] lots of api changes use jsonapi to format responses add somewhat working websockets --- api/auth.go | 30 ++++---- api/handlers_server.go | 57 +++++++++------ api/routes.go | 1 + api/utils.go | 35 ++++++++++ api/websockets/client.go | 76 ++++++++++++++++++++ api/websockets/consolewriter.go | 28 ++++++++ api/websockets/hub.go | 120 ++++++++++++++++++++++++++++++++ api/websockets/message.go | 59 ++++++++++++++++ 8 files changed, 370 insertions(+), 36 deletions(-) create mode 100644 api/websockets/client.go create mode 100644 api/websockets/consolewriter.go create mode 100644 api/websockets/hub.go create mode 100644 api/websockets/message.go diff --git a/api/auth.go b/api/auth.go index ef67b66..4f35b55 100644 --- a/api/auth.go +++ b/api/auth.go @@ -3,7 +3,10 @@ package api import ( "net/http" + "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" @@ -62,15 +65,23 @@ func (a *authorizationManager) HasPermission(permission string) bool { // 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 == "" { + requestToken = c.Query("token") + } requestServer := c.Param("server") var server control.Server if requestToken == "" && permission != "" { - log.Debug("Token missing in request.") - c.JSON(http.StatusBadRequest, responseError{"Missing required " + accessTokenHeader + " header."}) + sendErrors(c, http.StatusUnauthorized, &jsonapi.ErrorObject{ + Title: "Missing required " + accessTokenHeader + " header or token param.", + Status: strconv.Itoa(http.StatusUnauthorized), + }) c.Abort() return } @@ -90,7 +101,7 @@ func AuthHandler(permission string) gin.HandlerFunc { return } - c.JSON(http.StatusForbidden, responseError{"You do not have permission to perform this action."}) + sendForbidden(c) c.Abort() } } @@ -107,16 +118,3 @@ func GetContextAuthManager(c *gin.Context) AuthorizationManager { } return nil } - -// GetContextServer returns a control.Server contained in a gin.Context -// or null -func GetContextServer(c *gin.Context) control.Server { - server, exists := c.Get(contextVarAuth) - if !exists { - return nil - } - if server, ok := server.(control.Server); ok { - return server - } - return nil -} diff --git a/api/handlers_server.go b/api/handlers_server.go index 09b10c3..a339b3c 100644 --- a/api/handlers_server.go +++ b/api/handlers_server.go @@ -3,25 +3,29 @@ 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 -// TODO: make jsonapi compliant func handleGetServers(c *gin.Context) { servers := control.GetServers() - c.JSON(http.StatusOK, servers) + sendData(c, servers) } // POST /servers -// TODO: make jsonapi compliant 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.") - c.Status(http.StatusBadRequest) + sendErrors(c, http.StatusBadRequest, &jsonapi.ErrorObject{ + Status: strconv.Itoa(http.StatusBadRequest), + Title: "The passed server object is invalid.", + }) return } var srv control.Server @@ -30,10 +34,14 @@ func handlePostServers(c *gin.Context) { 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.") - c.Status(http.StatusInternalServerError) + sendInternalError(c, "Failed to create the server", "") return } go func() { @@ -43,19 +51,22 @@ func handlePostServers(c *gin.Context) { } env.Create() }() - c.JSON(http.StatusOK, srv) + sendDataStatus(c, http.StatusCreated, 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) + 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 } - c.JSON(http.StatusOK, server) + sendData(c, server) } // PATCH /servers/:server @@ -64,7 +75,6 @@ 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) @@ -75,18 +85,21 @@ func handleDeleteServer(c *gin.Context) { env, err := server.Environment() if err != nil { - log.WithError(err).WithField("server", server).Error("Failed to delete server.") + 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.") - c.Status(http.StatusInternalServerError) + sendInternalError(c, "The server could not be deleted.", "") return } - c.Status(http.StatusOK) + c.Status(http.StatusNoContent) } func handlePostServerReinstall(c *gin.Context) { @@ -102,7 +115,6 @@ func handlePostServerRebuild(c *gin.Context) { } // POST /servers/:server/power -// TODO: make jsonapi compliant func handlePostServerPower(c *gin.Context) { server := getServerFromContext(c) if server == nil { @@ -112,7 +124,7 @@ func handlePostServerPower(c *gin.Context) { auth := GetContextAuthManager(c) if auth == nil { - c.Status(http.StatusInternalServerError) + sendInternalError(c, "An internal error occured.", "") return } @@ -120,7 +132,7 @@ func handlePostServerPower(c *gin.Context) { case "start": { if !auth.HasPermission("s:power:start") { - c.Status(http.StatusForbidden) + sendForbidden(c) return } server.Start() @@ -128,7 +140,7 @@ func handlePostServerPower(c *gin.Context) { case "stop": { if !auth.HasPermission("s:power:stop") { - c.Status(http.StatusForbidden) + sendForbidden(c) return } server.Stop() @@ -136,7 +148,7 @@ func handlePostServerPower(c *gin.Context) { case "restart": { if !auth.HasPermission("s:power:restart") { - c.Status(http.StatusForbidden) + sendForbidden(c) return } server.Restart() @@ -144,7 +156,7 @@ func handlePostServerPower(c *gin.Context) { case "kill": { if !auth.HasPermission("s:power:kill") { - c.Status(http.StatusForbidden) + sendForbidden(c) return } server.Kill() @@ -157,11 +169,16 @@ func handlePostServerPower(c *gin.Context) { } // POST /servers/:server/command -// TODO: make jsonapi compliant 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 handleGetServerLog(c *gin.Context) { diff --git a/api/routes.go b/api/routes.go index f9a0c23..678c589 100644 --- a/api/routes.go +++ b/api/routes.go @@ -21,6 +21,7 @@ func (api *InternalAPI) RegisterRoutes() { v1ServerRoutes.POST("/password", AuthHandler(""), handlePostServerPassword) v1ServerRoutes.POST("/power", AuthHandler("s:power"), handlePostServerPower) v1ServerRoutes.POST("/command", AuthHandler("s:command"), handlePostServerCommand) + v1ServerRoutes.GET("/console", AuthHandler("s:console"), handleGetConsole) v1ServerRoutes.GET("/log", AuthHandler("s:console"), handleGetServerLog) v1ServerRoutes.POST("/suspend", AuthHandler(""), handlePostServerSuspend) v1ServerRoutes.POST("/unsuspend", AuthHandler(""), handlePostServerUnsuspend) diff --git a/api/utils.go b/api/utils.go index c748c3a..9f00adb 100644 --- a/api/utils.go +++ b/api/utils.go @@ -1,10 +1,45 @@ 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) +} diff --git a/api/websockets/client.go b/api/websockets/client.go new file mode 100644 index 0000000..43147c4 --- /dev/null +++ b/api/websockets/client.go @@ -0,0 +1,76 @@ +package websockets + +import "github.com/gorilla/websocket" +import ( + "time" + + log "github.com/sirupsen/logrus" +) + +type Client struct { + hub *Hub + + socket *websocket.Conn + + send chan []byte +} + +func (c *Client) readPump() { + defer func() { + c.hub.unregister <- c + c.socket.Close() + }() + c.socket.SetReadLimit(maxMessageSize) + c.socket.SetReadDeadline(time.Now().Add(pongWait)) + c.socket.SetPongHandler(func(string) error { + c.socket.SetReadDeadline(time.Now().Add(pongWait)) + return nil + }) + for { + _, _, err := c.socket.ReadMessage() + if err != nil { + if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway) { + log.WithError(err).Debug("Websocket closed unexpectedly.") + } + return + } + } +} + +func (c *Client) writePump() { + ticker := time.NewTicker(pingPeriod) + defer func() { + ticker.Stop() + c.socket.Close() + }() + for { + select { + case m, ok := <-c.send: + c.socket.SetWriteDeadline(time.Now().Add(writeWait)) + if !ok { + // The hub closed the channel + c.socket.WriteMessage(websocket.CloseMessage, []byte{}) + return + } + w, err := c.socket.NextWriter(websocket.TextMessage) + if err != nil { + return + } + w.Write([]byte{'['}) + w.Write(m) + for i := 0; i < len(c.send)+1; i++ { + w.Write([]byte{','}) + w.Write(<-c.send) + } + w.Write([]byte{']'}) + if err := w.Close(); err != nil { + return + } + case <-ticker.C: + c.socket.SetWriteDeadline(time.Now().Add(writeWait)) + if err := c.socket.WriteMessage(websocket.PingMessage, []byte{}); err != nil { + return + } + } + } +} diff --git a/api/websockets/consolewriter.go b/api/websockets/consolewriter.go new file mode 100644 index 0000000..13df333 --- /dev/null +++ b/api/websockets/consolewriter.go @@ -0,0 +1,28 @@ +package websockets + +import "io" + +type ConsoleWriter struct { + Hub *Hub + HandlerFunc *func(string) +} + +var _ io.Writer = ConsoleWriter{} + +func (c ConsoleWriter) Write(b []byte) (n int, e error) { + line := make([]byte, len(b)) + copy(line, b) + m := Message{ + Type: MessageTypeConsole, + Payload: ConsolePayload{ + Line: string(line), + Level: ConsoleLevelPlain, + Source: ConsoleSourceServer, + }, + } + c.Hub.Broadcast <- m + if c.HandlerFunc != nil { + (*c.HandlerFunc)(string(line)) + } + return len(b), nil +} diff --git a/api/websockets/hub.go b/api/websockets/hub.go new file mode 100644 index 0000000..11d42d1 --- /dev/null +++ b/api/websockets/hub.go @@ -0,0 +1,120 @@ +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[*Client]bool + +type Hub struct { + clients websocketMap + + Broadcast chan Message + + register chan *Client + unregister chan *Client + close chan bool +} + +//var _ io.Writer = &Hub{} + +func NewHub() *Hub { + return &Hub{ + Broadcast: make(chan Message), + register: make(chan *Client), + unregister: make(chan *Client), + close: make(chan bool), + clients: make(websocketMap), + } +} + +func (h *Hub) 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 + } + c := &Client{ + hub: h, + socket: socket, + send: make(chan []byte, 256), + } + h.register <- c + + go c.readPump() + go c.writePump() +} + +func (h *Hub) Subscribe(c *Client) { + h.register <- c +} + +func (h *Hub) Unsubscribe(c *Client) { + h.unregister <- c +} + +func (h *Hub) Run() { + defer func() { + for s := range h.clients { + close(s.send) + delete(h.clients, s) + } + close(h.register) + close(h.unregister) + close(h.Broadcast) + close(h.close) + }() + for { + select { + case s := <-h.register: + h.clients[s] = true + case s := <-h.unregister: + if _, ok := h.clients[s]; ok { + delete(h.clients, s) + close(s.send) + } + case m := <-h.Broadcast: + b, err := json.Marshal(m) + if err != nil { + log.WithError(err).Error("Failed to encode websocket message.") + continue + } + for s := range h.clients { + select { + case s.send <- b: + default: + close(s.send) + delete(h.clients, s) + } + } + case <-h.close: + return + } + } +} + +func (h *Hub) Close() { + h.close <- true +} diff --git a/api/websockets/message.go b/api/websockets/message.go new file mode 100644 index 0000000..52b31e9 --- /dev/null +++ b/api/websockets/message.go @@ -0,0 +1,59 @@ +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 (h *Hub) Log(l ConsoleLevel, m string) { + h.Broadcast <- Message{ + Type: MessageTypeConsole, + Payload: ConsolePayload{ + Source: ConsoleSourceWings, + Level: l, + Line: m, + }, + } +} From 501409827e0a4ba05167228c05a6d38f9657a955 Mon Sep 17 00:00:00 2001 From: Jakob Schrettenbrunner Date: Tue, 20 Feb 2018 23:38:29 +0100 Subject: [PATCH 09/19] some reorganisation etc. in control package some minor stuff just pushing so I can try to replace fsouza/go-dockerclient with moby/moby/client --- config/keys.go | 2 +- constants/constants.go | 4 + control/docker_environment.go | 47 ++++++-- control/server.go | 204 ++++++++++------------------------ control/server_persistance.go | 103 +++++++++++++++++ control/server_util.go | 49 ++++++++ control/service.go | 4 +- utils/logging.go | 2 +- 8 files changed, 251 insertions(+), 164 deletions(-) create mode 100644 control/server_persistance.go create mode 100644 control/server_util.go diff --git a/config/keys.go b/config/keys.go index fcf6304..a7076da 100644 --- a/config/keys.go +++ b/config/keys.go @@ -6,7 +6,7 @@ const ( // DataPath is a string containing the path where data should // be stored on the system - DataPath = "datapath" + DataPath = "data" // APIHost is a string containing the interface ip address // on what the api should listen on diff --git a/constants/constants.go b/constants/constants.go index 29c8dfb..d1918e4 100644 --- a/constants/constants.go +++ b/constants/constants.go @@ -26,3 +26,7 @@ const ServerDataPath = "data" // JSONIndent is the indent to use with the json.MarshalIndent() function. const JSONIndent = " " + +// DockerContainerPrefix is the prefix used for naming Docker containers. +// It's also used to prefix the hostnames of the docker containers. +const DockerContainerPrefix = "ptdl-" diff --git a/control/docker_environment.go b/control/docker_environment.go index 8ae0051..397606b 100644 --- a/control/docker_environment.go +++ b/control/docker_environment.go @@ -20,6 +20,7 @@ type dockerEnvironment struct { container *docker.Container context context.Context + attached bool containerInput io.Writer containerOutput io.Writer closeWaiter docker.CloseWaiter @@ -67,23 +68,33 @@ func (env *dockerEnvironment) checkContainerExists() error { } func (env *dockerEnvironment) attach() error { + if env.attached { + return nil + } pr, pw := io.Pipe() + env.containerInput = pw + + cw := websockets.ConsoleWriter{ + Hub: env.server.websockets, + } success := make(chan struct{}) w, err := env.client.AttachToContainerNonBlocking(docker.AttachToContainerOptions{ Container: env.server.DockerContainer.ID, InputStream: pr, - OutputStream: os.Stdout, + OutputStream: cw, + ErrorStream: cw, Stdin: true, Stdout: true, + Stderr: true, Stream: true, Success: success, }) env.closeWaiter = w - env.containerInput = pw <-success close(success) + env.attached = true return err } @@ -91,11 +102,11 @@ func (env *dockerEnvironment) attach() error { // settings to it func (env *dockerEnvironment) Create() error { log.WithField("server", env.server.ID).Debug("Creating docker environment") - // Split image repository and tag to feed it to the library - imageParts := strings.Split(env.server.Service().DockerImage, ":") + // Split image repository and tag + imageParts := strings.Split(env.server.GetService().DockerImage, ":") imageRepoParts := strings.Split(imageParts[0], "/") if len(imageRepoParts) >= 3 { - // Handle possibly required authentication + // TODO: Handle possibly required authentication } // Pull docker image @@ -105,7 +116,7 @@ func (env *dockerEnvironment) Create() error { if len(imageParts) >= 2 { pullImageOpts.Tag = imageParts[1] } - log.WithField("image", env.server.service.DockerImage).Debug("Pulling docker image") + log.WithField("image", env.server.GetService().DockerImage).Debug("Pulling docker image") err := env.client.PullImage(pullImageOpts, docker.AuthConfiguration{}) if err != nil { log.WithError(err).WithField("server", env.server.ID).Error("Failed to create docker environment") @@ -119,17 +130,21 @@ func (env *dockerEnvironment) Create() error { // Create docker container // TODO: apply cpu, io, disk limits. containerConfig := &docker.Config{ - Image: env.server.Service().DockerImage, - Cmd: strings.Split(env.server.StartupCommand, " "), - OpenStdin: true, + Image: env.server.GetService().DockerImage, + Cmd: strings.Split(env.server.StartupCommand, " "), + OpenStdin: true, + ArgsEscaped: false, + Hostname: constants.DockerContainerPrefix + env.server.UUIDShort(), } containerHostConfig := &docker.HostConfig{ Memory: env.server.Settings.Memory, MemorySwap: env.server.Settings.Swap, - Binds: []string{env.server.dataPath() + ":/home/container"}, + // TODO: Allow custom binds via some kind of settings in the service + Binds: []string{env.server.dataPath() + ":/home/container"}, + // TODO: Add port bindings } createContainerOpts := docker.CreateContainerOptions{ - Name: "ptdl-" + env.server.UUIDShort(), + Name: constants.DockerContainerPrefix + env.server.UUIDShort(), Config: containerConfig, HostConfig: containerHostConfig, Context: env.context, @@ -139,12 +154,16 @@ func (env *dockerEnvironment) Create() error { log.WithError(err).WithField("server", env.server.ID).Error("Failed to create docker container") return err } + env.server.DockerContainer.ID = container.ID env.server.Save() env.container = container + if env.closeWaiter != nil { + env.closeWaiter.Close() + } + env.attached = false log.WithField("server", env.server.ID).Debug("Docker environment created") - return nil } @@ -167,6 +186,10 @@ func (env *dockerEnvironment) Destroy() error { return err } + if env.closeWaiter != nil { + env.closeWaiter.Close() + } + env.attached = false log.WithField("server", env.server.ID).Debug("Docker environment destroyed") return nil } diff --git a/control/server.go b/control/server.go index 715636c..bedc721 100644 --- a/control/server.go +++ b/control/server.go @@ -1,16 +1,19 @@ package control import ( - "encoding/json" "errors" - "io/ioutil" - "os" - "path/filepath" - "strings" "github.com/pterodactyl/wings/api/websockets" log "github.com/sirupsen/logrus" - "github.com/spf13/viper" +) + +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. @@ -34,6 +37,7 @@ type Server interface { Save() error Environment() (Environment, error) + Websockets() *websockets.Hub HasPermission(string, string) bool } @@ -41,32 +45,36 @@ type Server interface { // 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"` + 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 + 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"` + 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"` + DockerContainer dockerContainer `json:"dockerContainer" jsonapi:"attr,docker_container"` // EnvironmentVariables are set in the Environment the server is running in - EnvironmentVariables map[string]string `json:"env"` + 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"` + Allocations allocations `json:"allocation" jsonapi:"attr,allocations"` // Settings are the environment settings and limitations for the server - Settings settings `json:"settings"` + 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.Hub + status Status } type allocations struct { @@ -98,73 +106,6 @@ type serversMap map[string]*ServerStruct var servers = make(serversMap) -// 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 - } - return server, nil -} - -func storeServerConfiguration(server *ServerStruct) error { - serverJSON, err := json.MarshalIndent(server, "", constants.JSONIndent) - if err != nil { - return err - } - if err := os.MkdirAll(server.path(), constants.DefaultFolderPerms); err != nil { - return err - } - if err := ioutil.WriteFile(server.configFilePath(), serverJSON, constants.DefaultFilePerms); err != nil { - return err - } - return nil -} - -func storeServerConfigurations() error { - for _, s := range servers { - if err := storeServerConfiguration(s); err != nil { - return err - } - } - return nil -} - -func deleteServerFolder(id string) error { - path := filepath.Join(viper.GetString(config.DataPath), constants.ServersPath, id) - folder, err := os.Stat(path) - if os.IsNotExist(err) || !folder.IsDir() { - return err - } - return os.RemoveAll(path) -} - // GetServers returns an array of all servers the daemon manages func GetServers() []Server { serverArray := make([]Server, len(servers)) @@ -192,6 +133,11 @@ func CreateServer(server *ServerStruct) (Server, error) { } 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 @@ -207,13 +153,40 @@ func DeleteServer(id string) error { 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.NewHub() + 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 } } @@ -221,8 +194,10 @@ func (s *ServerStruct) Start() error { } func (s *ServerStruct) Stop() error { + s.SetStatus(StatusStopping) env, err := s.Environment() if err != nil { + s.SetStatus(StatusRunning) return err } return env.Stop() @@ -258,70 +233,3 @@ func (s *ServerStruct) Rebuild() error { } return env.ReCreate() } - -// Service returns the server's service configuration -func (s *ServerStruct) Service() *Service { - if s.service == nil { - // TODO: Properly use the correct service, mock for now. - s.service = &Service{ - DockerImage: "quay.io/pterodactyl/core:java", - EnvironmentName: "docker", - } - } - return s.service -} - -// UUIDShort returns the first block of the UUID -func (s *ServerStruct) UUIDShort() string { - return s.ID[0:strings.Index(s.ID, "-")] -} - -// Environment returns the servers environment -func (s *ServerStruct) Environment() (Environment, error) { - var err error - if s.environment == nil { - switch s.Service().EnvironmentName { - case "docker": - s.environment, err = NewDockerEnvironment(s) - default: - log.WithField("service", s.ServiceName).Error("Invalid environment name") - return nil, errors.New("Invalid environment name") - } - } - return s.environment, err -} - -// HasPermission checks wether a provided token has a specific permission -func (s *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 -} - -func (s *ServerStruct) Save() error { - if err := storeServerConfiguration(s); err != nil { - log.WithField("server", s.ID).WithError(err).Error("Failed to store server configuration.") - return err - } - return nil -} - -func (s *ServerStruct) path() string { - return filepath.Join(viper.GetString(config.DataPath), constants.ServersPath, s.ID) -} - -func (s *ServerStruct) dataPath() string { - return filepath.Join(s.path(), constants.ServerDataPath) -} - -func (s *ServerStruct) configFilePath() string { - return filepath.Join(s.path(), constants.ServerConfigFile) -} diff --git a/control/server_persistance.go b/control/server_persistance.go new file mode 100644 index 0000000..98c93bd --- /dev/null +++ b/control/server_persistance.go @@ -0,0 +1,103 @@ +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 { + return filepath.Join(viper.GetString(config.DataPath), constants.ServersPath, s.ID) +} + +func (s *ServerStruct) dataPath() string { + return filepath.Join(s.path(), constants.ServerDataPath) +} + +func (s *ServerStruct) configFilePath() string { + return filepath.Join(s.path(), constants.ServerConfigFile) +} diff --git a/control/server_util.go b/control/server_util.go new file mode 100644 index 0000000..03d20e5 --- /dev/null +++ b/control/server_util.go @@ -0,0 +1,49 @@ +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.Hub { + 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 +} diff --git a/control/service.go b/control/service.go index e982e7c..8b7f06c 100644 --- a/control/service.go +++ b/control/service.go @@ -4,7 +4,7 @@ type Service struct { server *Server // EnvironmentName is the name of the environment used by the service - EnvironmentName string `json:"environmentName"` + EnvironmentName string `json:"environmentName" jsonapi:"primary,service"` - DockerImage string `json:"dockerImage"` + DockerImage string `json:"dockerImage" jsonapi:"attr,docker_image"` } diff --git a/utils/logging.go b/utils/logging.go index d1a6851..876c735 100644 --- a/utils/logging.go +++ b/utils/logging.go @@ -7,7 +7,7 @@ import ( "github.com/pterodactyl/wings/constants" - rotatelogs "github.com/lestrrat/go-file-rotatelogs" + "github.com/lestrrat/go-file-rotatelogs" "github.com/rifflock/lfshook" log "github.com/sirupsen/logrus" "github.com/spf13/viper" From 5836c320ff23f05fa2b6be729dbab5f9ad67f56b Mon Sep 17 00:00:00 2001 From: Jakob Schrettenbrunner Date: Tue, 20 Feb 2018 23:38:47 +0100 Subject: [PATCH 10/19] fixes for vagrant and new go dep lockfile --- .dev/vagrant/provision.sh | 6 +- Gopkg.lock | 112 +++++++++++++++++++------------------- 2 files changed, 60 insertions(+), 58 deletions(-) diff --git a/.dev/vagrant/provision.sh b/.dev/vagrant/provision.sh index 4048639..07cc69d 100644 --- a/.dev/vagrant/provision.sh +++ b/.dev/vagrant/provision.sh @@ -3,8 +3,8 @@ echo "Provisioning development environment for Pterodactyl go daemon." cp /home/vagrant/go/github.com/pterodactyl/wings.go/.dev/vagrant/motd.txt /etc/motd -chown -R ubuntu:ubuntu /home/vagrant/go -chown -R ubuntu:ubuntu /srv +chown -R vagrant:vagrant /home/vagrant/go +chown -R vagrant:vagrant /srv echo "Update apt repositories" sudo add-apt-repository ppa:longsleep/golang-backports @@ -13,7 +13,7 @@ apt-get update > /dev/null echo "Install docker" curl -sSL https://get.docker.com/ | sh systemctl enable docker -usermod -aG docker ubuntu +usermod -aG docker vagrant echo "Install go" apt-get install -y golang-go diff --git a/Gopkg.lock b/Gopkg.lock index 0e18007..1265fb3 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -2,24 +2,25 @@ [[projects]] - branch = "default" name = "bitbucket.org/tebeka/strftime" packages = ["."] - revision = "2194253a23c090a4d5953b152a3c63fb5da4f5a4" + revision = "af5e0ef38369dfb5819b56d27d593142841e4600" + version = "0.1.2" [[projects]] + branch = "master" name = "github.com/Azure/go-ansiterm" packages = [ ".", "winterm" ] - revision = "19f72df4d05d31cbe1c56bfc8045c96babff6c7e" + revision = "d6e3b3328b783f23731bc4d058875b0371ff8109" [[projects]] name = "github.com/Microsoft/go-winio" packages = ["."] - revision = "c4dc1301f1dc0307acd38e611aa375a64dfe0642" - version = "v0.4.3" + revision = "7da180ee92d8bd8bb8c37fc560e673e6557c392f" + version = "v0.4.7" [[projects]] branch = "master" @@ -28,9 +29,10 @@ revision = "cd527374f1e5bff4938207604a14f2e38a9cf512" [[projects]] + branch = "master" name = "github.com/StackExchange/wmi" packages = ["."] - revision = "ea383cf3ba6ec950874b8486cd72356d007c768f" + revision = "5d049714c4a64225c3c79a7cf7d02f7fb5b96338" [[projects]] branch = "master" @@ -41,10 +43,10 @@ [[projects]] name = "github.com/davecgh/go-spew" packages = ["spew"] - revision = "5215b55f46b2b919f50a1df0eaa5886afe4e3b3d" + revision = "346938d642f2ec3594ed81d874461961cd0faa76" + version = "v1.1.0" [[projects]] - branch = "master" name = "github.com/docker/docker" packages = [ "api/types", @@ -73,7 +75,7 @@ "pkg/term", "pkg/term/windows" ] - revision = "ee9abc212032353e19e5b0f5e6410ad67a0cc9b1" + revision = "fe8aac6f5ae413a967adb0adad0b54abdfb825c4" [[projects]] name = "github.com/docker/go-connections" @@ -90,12 +92,14 @@ [[projects]] name = "github.com/fsnotify/fsnotify" packages = ["."] - revision = "4da3e2cfbabc9f751898f250b49f2439785783a1" + revision = "c2828203cd70a50dcccfb2761f8b1f8ceef9a8e9" + version = "v1.4.7" [[projects]] name = "github.com/fsouza/go-dockerclient" packages = ["."] - revision = "4df4873b288c855e4186534280c3a3a1af403e67" + revision = "2ff310040c161b75fa19fb9b287a90a6e03c0012" + version = "1.1" [[projects]] name = "github.com/gin-gonic/gin" @@ -113,7 +117,8 @@ ".", "oleutil" ] - revision = "085abb85892dc1949567b726dff00fa226c60c45" + revision = "a41e3c4b706f6ae8dfbff342b06e40fa4d2d0506" + version = "v1.2.1" [[projects]] name = "github.com/gogo/protobuf" @@ -124,7 +129,8 @@ [[projects]] name = "github.com/golang/protobuf" packages = ["proto"] - revision = "2402d76f3d41f928c7902a765dfc872356dd3aad" + revision = "925541529c1fa6821df4e44ce2723319eb2be768" + version = "v1.0.0" [[projects]] name = "github.com/google/jsonapi" @@ -139,11 +145,7 @@ version = "v1.2.0" [[projects]] - name = "github.com/hashicorp/go-cleanhttp" - packages = ["."] - revision = "3573b8b52aa7b37b9358d966a898feb387f62437" - -[[projects]] + branch = "master" name = "github.com/hashicorp/hcl" packages = [ ".", @@ -156,7 +158,7 @@ "json/scanner", "json/token" ] - revision = "392dba7d905ed5d04a5794ba89f558b27e2ba1ca" + revision = "23c074d0eceb2b8a5bfdbb271ab780cde70f05a8" [[projects]] name = "github.com/inconshreveable/mousetrap" @@ -173,7 +175,8 @@ [[projects]] name = "github.com/magiconair/properties" packages = ["."] - revision = "51463bfca2576e06c62a8504b5c0f06d61312647" + revision = "c3beff4c2358b44d0493c7dda585e7db7ff28ae6" + version = "v1.7.6" [[projects]] branch = "master" @@ -184,13 +187,14 @@ [[projects]] name = "github.com/mattn/go-isatty" packages = ["."] - revision = "fc9e8d8ef48496124e79ae0df75490096eccf6fe" - version = "v0.0.2" + revision = "0360b2af4f38e8d38c7fce2a9f4e702702d73a39" + version = "v0.0.3" [[projects]] + branch = "master" name = "github.com/mitchellh/mapstructure" packages = ["."] - revision = "d0303fe809921458f417bcf828397a65db30a7e4" + revision = "a4e142e9c047c904fa2f1e144d9a84e6133024bc" [[projects]] name = "github.com/opencontainers/go-digest" @@ -213,18 +217,14 @@ "libcontainer/system", "libcontainer/user" ] - revision = "6ca8b741bb67839b7170d96257dde5c246f8b784" - -[[projects]] - name = "github.com/pelletier/go-buffruneio" - packages = ["."] - revision = "c37440a7cf42ac63b919c752ca73a85067e05992" - version = "v0.2.0" + revision = "baf6536d6259209c3edfa2b22237af82942d3dfa" + version = "v0.1.1" [[projects]] name = "github.com/pelletier/go-toml" packages = ["."] - revision = "4a000a21a414d139727f616a8bb97f847b1b310b" + revision = "acdc4509485b587f5e675510c4f2c63e90ff68a8" + version = "v1.1.0" [[projects]] name = "github.com/pkg/errors" @@ -251,23 +251,16 @@ "host", "internal/common", "mem", - "net", "process" ] - revision = "6e221c482653ef05c9f6a7bf71ddceea0e40bac5" - version = "v2.17.09" - -[[projects]] - branch = "master" - name = "github.com/shirou/w32" - packages = ["."] - revision = "bb4de0191aa41b5507caa14b0650cdbddcd9280b" + revision = "c432be29ccce470088d07eea25b3ea7e68a8afbb" + version = "v2.18.01" [[projects]] name = "github.com/sirupsen/logrus" packages = ["."] - revision = "f006c2ac4710855cf0f916dd6b77acf6b048dc6e" - version = "v1.0.3" + revision = "d682213848ed68c0a260ca37d6dd5ace8423f5ba" + version = "v1.0.4" [[projects]] name = "github.com/spf13/afero" @@ -275,23 +268,26 @@ ".", "mem" ] - revision = "9be650865eab0c12963d8753212f4f9c66cdcf12" + revision = "bb8f1927f2a9d3ab41c9340aa034f6b803f4359c" + version = "v1.0.2" [[projects]] name = "github.com/spf13/cast" packages = ["."] - revision = "acbeb36b902d72a7a4c18e8f3241075e7ab763e4" - version = "v1.1.0" + revision = "8965335b8c7107321228e3e3702cab9832751bac" + version = "v1.2.0" [[projects]] name = "github.com/spf13/cobra" packages = ["."] - revision = "90fc11bbc0a789c29272c21b5ff9e93db183f8dc" + revision = "7b2c5ac9fc04fc5efafb60700713d4fa609b777b" + version = "v0.0.1" [[projects]] + branch = "master" name = "github.com/spf13/jwalterweatherman" packages = ["."] - revision = "0efa5202c04663c757d84f90f5219c1250baf94f" + revision = "7c0cea34c8ece3fbeb2b27ab9b59511d360fb394" [[projects]] name = "github.com/spf13/pflag" @@ -302,7 +298,8 @@ [[projects]] name = "github.com/spf13/viper" packages = ["."] - revision = "c1de95864d73a5465492829d7cb2dd422b19ac96" + revision = "25b30aa063fc18e48662b86996252eabdcf2f0c7" + version = "v1.0.0" [[projects]] name = "github.com/stretchr/testify" @@ -311,27 +308,31 @@ version = "v1.1.4" [[projects]] + branch = "master" name = "golang.org/x/crypto" packages = ["ssh/terminal"] - revision = "bd6f299fb381e4c3393d1c4b1f0b94f5e77650c8" + revision = "432090b8f568c018896cd8a0fb0345872bbac6ce" [[projects]] + branch = "master" name = "golang.org/x/net" packages = [ "context", "context/ctxhttp" ] - revision = "f315505cf3349909cdf013ea56690da34e96a451" + revision = "cbe0f9307d0156177f9dd5dc85da1a31abc5f2fb" [[projects]] + branch = "master" name = "golang.org/x/sys" packages = [ "unix", "windows" ] - revision = "f7928cfef4d09d1b080aa2b6fd3ca9ba1567c733" + revision = "37707fdb30a5b38865cfb95e5aab41707daec7fd" [[projects]] + branch = "master" name = "golang.org/x/text" packages = [ "internal/gen", @@ -341,18 +342,19 @@ "unicode/cldr", "unicode/norm" ] - revision = "5a2c30c33799f1e813f7f3259000d594a5ed493a" + revision = "4e4a3210bb54bb31f6ab2cdca2edcc0b50c420c1" [[projects]] name = "gopkg.in/go-playground/validator.v8" packages = ["."] - revision = "c193cecd124b5cc722d7ee5538e945bdb3348435" - version = "v8.15.1" + revision = "5f1438d3fca68893a817e4a66806cea46a9e4ebf" + version = "v8.18.2" [[projects]] + branch = "v2" name = "gopkg.in/yaml.v2" packages = ["."] - revision = "cd8b52f8269e0feb286dfeef29f8fe4d5b397e0b" + revision = "d670f9405373e636a5a2765eea47fac0c9bc91a4" [solve-meta] analyzer-name = "dep" From d2aa896a1cc33ec0129af0e4d6946df26951897c Mon Sep 17 00:00:00 2001 From: Jakob Schrettenbrunner Date: Tue, 20 Feb 2018 23:43:33 +0100 Subject: [PATCH 11/19] use go dep with travis --- .travis.yml | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9347948..0fd2fd6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,23 +6,17 @@ go: services: - docker -addons: - apt: - sources: - - sourceline: 'ppa:masterminds/glide' - packages: - - glide - install: - mkdir -p $GOPATH/bin -# Install other tools +# Install used tools +- go get github.com/golang/dep/cmd/dep - go get github.com/mitchellh/gox - go get github.com/haya14busa/goverage - go get github.com/schrej/godacov -# Install project dependencies with glide -- glide install +# Install project dependencies with dep +- dep ensure script: - make cross-build From b8b0702f84483e1fa74fdfb5c5795abd8c0e2701 Mon Sep 17 00:00:00 2001 From: Jakob Schrettenbrunner Date: Wed, 14 Mar 2018 10:33:23 +0100 Subject: [PATCH 12/19] switch to moby/moby in the docker_environment --- Gopkg.lock | 85 +++++-------- Gopkg.toml | 8 ++ constants/constants.go | 3 + control/console_handler.go | 33 +++++ control/docker_environment.go | 190 ++++++++++++---------------- control/docker_environment_test.go | 196 ++++++++++++++--------------- wings-api.paw | Bin 0 -> 16154 bytes 7 files changed, 250 insertions(+), 265 deletions(-) create mode 100644 control/console_handler.go create mode 100644 wings-api.paw diff --git a/Gopkg.lock b/Gopkg.lock index 1265fb3..115549b 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -7,79 +7,63 @@ revision = "af5e0ef38369dfb5819b56d27d593142841e4600" version = "0.1.2" -[[projects]] - branch = "master" - name = "github.com/Azure/go-ansiterm" - packages = [ - ".", - "winterm" - ] - revision = "d6e3b3328b783f23731bc4d058875b0371ff8109" - [[projects]] name = "github.com/Microsoft/go-winio" packages = ["."] revision = "7da180ee92d8bd8bb8c37fc560e673e6557c392f" version = "v0.4.7" -[[projects]] - branch = "master" - name = "github.com/Nvveen/Gotty" - packages = ["."] - revision = "cd527374f1e5bff4938207604a14f2e38a9cf512" - [[projects]] branch = "master" name = "github.com/StackExchange/wmi" packages = ["."] revision = "5d049714c4a64225c3c79a7cf7d02f7fb5b96338" -[[projects]] - branch = "master" - name = "github.com/containerd/continuity" - packages = ["pathdriver"] - revision = "d8fb8589b0e8e85b8c8bbaa8840226d0dfeb7371" - [[projects]] name = "github.com/davecgh/go-spew" packages = ["spew"] revision = "346938d642f2ec3594ed81d874461961cd0faa76" version = "v1.1.0" +[[projects]] + branch = "master" + name = "github.com/docker/distribution" + packages = [ + "digestset", + "reference" + ] + revision = "6664ec703991875e14419ff4960921cce7678bab" + [[projects]] name = "github.com/docker/docker" packages = [ + "api", "api/types", "api/types/blkiodev", "api/types/container", + "api/types/events", "api/types/filters", + "api/types/image", "api/types/mount", "api/types/network", "api/types/registry", "api/types/strslice", "api/types/swarm", "api/types/swarm/runtime", + "api/types/time", "api/types/versions", - "opts", - "pkg/archive", - "pkg/fileutils", - "pkg/homedir", - "pkg/idtools", - "pkg/ioutils", - "pkg/jsonmessage", - "pkg/longpath", - "pkg/mount", - "pkg/pools", - "pkg/stdcopy", - "pkg/system", - "pkg/term", - "pkg/term/windows" + "api/types/volume", + "client" ] - revision = "fe8aac6f5ae413a967adb0adad0b54abdfb825c4" + revision = "e3831a62a3052472d7252049bc59835d5d7dc8bd" [[projects]] name = "github.com/docker/go-connections" - packages = ["nat"] + packages = [ + "nat", + "sockets", + "tlsconfig" + ] revision = "3ede32e2033de7505e6500d6c868c2b9ed9f169d" version = "v0.3.0" @@ -95,12 +79,6 @@ revision = "c2828203cd70a50dcccfb2761f8b1f8ceef9a8e9" version = "v1.4.7" -[[projects]] - name = "github.com/fsouza/go-dockerclient" - packages = ["."] - revision = "2ff310040c161b75fa19fb9b287a90a6e03c0012" - version = "1.1" - [[projects]] name = "github.com/gin-gonic/gin" packages = [ @@ -211,15 +189,6 @@ revision = "d60099175f88c47cd379c4738d158884749ed235" version = "v1.0.1" -[[projects]] - name = "github.com/opencontainers/runc" - packages = [ - "libcontainer/system", - "libcontainer/user" - ] - revision = "baf6536d6259209c3edfa2b22237af82942d3dfa" - version = "v0.1.1" - [[projects]] name = "github.com/pelletier/go-toml" packages = ["."] @@ -251,11 +220,18 @@ "host", "internal/common", "mem", + "net", "process" ] revision = "c432be29ccce470088d07eea25b3ea7e68a8afbb" version = "v2.18.01" +[[projects]] + branch = "master" + name = "github.com/shirou/w32" + packages = ["."] + revision = "bb4de0191aa41b5507caa14b0650cdbddcd9280b" + [[projects]] name = "github.com/sirupsen/logrus" packages = ["."] @@ -318,7 +294,8 @@ name = "golang.org/x/net" packages = [ "context", - "context/ctxhttp" + "context/ctxhttp", + "proxy" ] revision = "cbe0f9307d0156177f9dd5dc85da1a31abc5f2fb" @@ -359,6 +336,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "983bf3264628787237fa4445a0aa430be028dbfb056756d99a30e583d14e8514" + inputs-digest = "c81145698e213e2c8f26c3d5b7e52033c4a2438cfb8eda51b6864770fac71fe4" solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index 31785ab..99b9f5a 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -60,3 +60,11 @@ [prune] go-tests = true unused-packages = true + +[[constraint]] + name = "github.com/docker/docker" + revision = "e3831a62a3052472d7252049bc59835d5d7dc8bd" + +[[override]] + name = "github.com/docker/distribution" + branch = "master" diff --git a/constants/constants.go b/constants/constants.go index d1918e4..614262e 100644 --- a/constants/constants.go +++ b/constants/constants.go @@ -30,3 +30,6 @@ const JSONIndent = " " // DockerContainerPrefix is the prefix used for naming Docker containers. // It's also used to prefix the hostnames of the docker containers. const DockerContainerPrefix = "ptdl-" + +// WSMaxMessages is the maximum number of messages that are sent in one transfer. +const WSMaxMessages = 10 diff --git a/control/console_handler.go b/control/console_handler.go new file mode 100644 index 0000000..cc88ea3 --- /dev/null +++ b/control/console_handler.go @@ -0,0 +1,33 @@ +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 +} diff --git a/control/docker_environment.go b/control/docker_environment.go index 397606b..9c0b2b1 100644 --- a/control/docker_environment.go +++ b/control/docker_environment.go @@ -5,25 +5,21 @@ import ( "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" - - "github.com/fsouza/go-dockerclient" - "github.com/pterodactyl/wings/api/websockets" log "github.com/sirupsen/logrus" ) type dockerEnvironment struct { baseEnvironment - client *docker.Client - container *docker.Container - context context.Context - - attached bool - containerInput io.Writer - containerOutput io.Writer - closeWaiter docker.CloseWaiter + client *client.Client + hires types.HijackedResponse + attached bool server *ServerStruct } @@ -39,16 +35,20 @@ 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) - client, err := docker.NewClient("unix:///var/run/docker.sock") - env.client = client if err != nil { log.WithError(err).Fatal("Failed to connect to docker.") return nil, err } if env.server.DockerContainer.ID != "" { - if err := env.checkContainerExists(); err != nil { + 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() @@ -58,68 +58,28 @@ func NewDockerEnvironment(server *ServerStruct) (Environment, error) { return &env, nil } -func (env *dockerEnvironment) checkContainerExists() error { - container, err := env.client.InspectContainer(env.server.DockerContainer.ID) - if err != nil { - return err - } - env.container = container - return 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 } - pr, pw := io.Pipe() - env.containerInput = pw - - cw := websockets.ConsoleWriter{ - Hub: env.server.websockets, - } - - success := make(chan struct{}) - w, err := env.client.AttachToContainerNonBlocking(docker.AttachToContainerOptions{ - Container: env.server.DockerContainer.ID, - InputStream: pr, - OutputStream: cw, - ErrorStream: cw, - Stdin: true, - Stdout: true, - Stderr: true, - Stream: true, - Success: success, - }) - env.closeWaiter = w - - <-success - close(success) env.attached = true - return err + 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") - // 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 - var pullImageOpts = docker.PullImageOptions{ - Repository: imageParts[0], - } - if len(imageParts) >= 2 { - pullImageOpts.Tag = imageParts[1] - } - log.WithField("image", env.server.GetService().DockerImage).Debug("Pulling docker image") - err := env.client.PullImage(pullImageOpts, docker.AuthConfiguration{}) - if err != nil { - log.WithError(err).WithField("server", env.server.ID).Error("Failed to create 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 } @@ -129,27 +89,31 @@ func (env *dockerEnvironment) Create() error { // Create docker container // TODO: apply cpu, io, disk limits. - containerConfig := &docker.Config{ - Image: env.server.GetService().DockerImage, - Cmd: strings.Split(env.server.StartupCommand, " "), - OpenStdin: true, - ArgsEscaped: false, - Hostname: constants.DockerContainerPrefix + env.server.UUIDShort(), + + 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 := &docker.HostConfig{ - Memory: env.server.Settings.Memory, - MemorySwap: env.server.Settings.Swap, + + 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 } - createContainerOpts := docker.CreateContainerOptions{ - Name: constants.DockerContainerPrefix + env.server.UUIDShort(), - Config: containerConfig, - HostConfig: containerHostConfig, - Context: env.context, - } - container, err := env.client.CreateContainer(createContainerOpts) + + 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 @@ -157,11 +121,6 @@ func (env *dockerEnvironment) Create() error { env.server.DockerContainer.ID = container.ID env.server.Save() - env.container = container - if env.closeWaiter != nil { - env.closeWaiter.Close() - } - env.attached = false log.WithField("server", env.server.ID).Debug("Docker environment created") return nil @@ -170,36 +129,29 @@ func (env *dockerEnvironment) Create() error { // Destroy removes the environment's docker container func (env *dockerEnvironment) Destroy() error { log.WithField("server", env.server.ID).Debug("Destroying docker environment") - if _, err := env.client.InspectContainer(env.server.DockerContainer.ID); err != nil { - if _, ok := err.(*docker.NoSuchContainer); ok { - log.WithField("server", env.server.ID).Debug("Container not found, docker environment destroyed already.") - return nil - } - log.WithError(err).WithField("server", env.server.ID).Error("Could not destroy docker environment") - return err + + 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 } - err := env.client.RemoveContainer(docker.RemoveContainerOptions{ - ID: env.server.DockerContainer.ID, - }) - if err != 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 } - if env.closeWaiter != nil { - env.closeWaiter.Close() - } - env.attached = false log.WithField("server", env.server.ID).Debug("Docker environment destroyed") return nil } func (env *dockerEnvironment) Exists() bool { - if env.container != nil { - return true + if err := env.inspectContainer(context.TODO()); err != nil { + return false } - env.checkContainerExists() - return env.container != nil + return true } // Start starts the environment's docker container @@ -208,7 +160,8 @@ func (env *dockerEnvironment) Start() error { if err := env.attach(); err != nil { log.WithError(err).Error("Failed to attach to docker container") } - if err := env.client.StartContainer(env.container.ID, nil); err != nil { + + 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 } @@ -218,7 +171,10 @@ func (env *dockerEnvironment) Start() error { // Stop stops the environment's docker container func (env *dockerEnvironment) Stop() error { log.WithField("server", env.server.ID).Debug("Stopping service in docker environment") - if err := env.client.StopContainer(env.container.ID, 20000); err != nil { + + // TODO: Decide after what timeout to kill the container, currently 10min + timeout := time.Minute * 10 + 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 } @@ -227,9 +183,8 @@ func (env *dockerEnvironment) Stop() error { func (env *dockerEnvironment) Kill() error { log.WithField("server", env.server.ID).Debug("Killing service in docker environment") - if err := env.client.KillContainer(docker.KillContainerOptions{ - ID: env.container.ID, - }); err != nil { + + if err := env.client.ContainerKill(context.TODO(), env.server.DockerContainer.ID, "SIGKILL"); err != nil { log.WithError(err).Error("Failed to kill docker container") return err } @@ -238,7 +193,24 @@ func (env *dockerEnvironment) Kill() error { // Exec sends commands to the standard input of the docker container func (env *dockerEnvironment) Exec(command string) error { - log.Debug("Command: " + command) - _, err := env.containerInput.Write([]byte(command + "\n")) + //log.Debug("Command: " + command) + //_, err := env.containerInput.Write([]byte(command + "\n")) + //return err + return nil +} + +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{}) + defer rc.Close() return err } diff --git a/control/docker_environment_test.go b/control/docker_environment_test.go index 4564ac3..9d8f438 100644 --- a/control/docker_environment_test.go +++ b/control/docker_environment_test.go @@ -1,133 +1,125 @@ package control -import ( - "fmt" - "testing" +// func testServer() *ServerStruct { +// return &ServerStruct{ +// ID: "testuuid-something-something", +// service: &service{ +// DockerImage: "alpine:latest", +// }, +// } +// } - docker "github.com/fsouza/go-dockerclient" - "github.com/stretchr/testify/assert" -) +// func TestNewDockerEnvironment(t *testing.T) { +// env, err := createTestDockerEnv(nil) -func testServer() *ServerStruct { - return &ServerStruct{ - ID: "testuuid-something-something", - service: &service{ - DockerImage: "alpine:latest", - }, - } -} +// assert.Nil(t, err) +// assert.NotNil(t, env) +// assert.NotNil(t, env.client) +// } -func TestNewDockerEnvironment(t *testing.T) { - env, err := createTestDockerEnv(nil) +// func TestNewDockerEnvironmentExisting(t *testing.T) { +// eenv, _ := createTestDockerEnv(nil) +// eenv.Create() - assert.Nil(t, err) - assert.NotNil(t, env) - assert.NotNil(t, env.client) -} +// env, err := createTestDockerEnv(eenv.server) -func TestNewDockerEnvironmentExisting(t *testing.T) { - eenv, _ := createTestDockerEnv(nil) - eenv.Create() +// assert.Nil(t, err) +// assert.NotNil(t, env) +// assert.NotNil(t, env.container) - env, err := createTestDockerEnv(eenv.server) +// eenv.Destroy() +// } - assert.Nil(t, err) - assert.NotNil(t, env) - assert.NotNil(t, env.container) +// func TestCreateDockerEnvironment(t *testing.T) { +// env, _ := createTestDockerEnv(nil) - eenv.Destroy() -} +// err := env.Create() -func TestCreateDockerEnvironment(t *testing.T) { - env, _ := createTestDockerEnv(nil) +// a := assert.New(t) +// a.Nil(err) +// a.NotNil(env.container) +// a.Equal(env.container.Name, "ptdl_testuuid") - err := env.Create() +// if err := env.client.RemoveContainer(docker.RemoveContainerOptions{ +// ID: env.container.ID, +// }); err != nil { +// fmt.Println(err) +// } +// } - a := assert.New(t) - a.Nil(err) - a.NotNil(env.container) - a.Equal(env.container.Name, "ptdl_testuuid") +// func TestDestroyDockerEnvironment(t *testing.T) { +// env, _ := createTestDockerEnv(nil) +// env.Create() - if err := env.client.RemoveContainer(docker.RemoveContainerOptions{ - ID: env.container.ID, - }); err != nil { - fmt.Println(err) - } -} +// err := env.Destroy() -func TestDestroyDockerEnvironment(t *testing.T) { - env, _ := createTestDockerEnv(nil) - env.Create() +// _, ierr := env.client.InspectContainer(env.container.ID) - err := env.Destroy() +// assert.Nil(t, err) +// assert.IsType(t, ierr, &docker.NoSuchContainer{}) +// } - _, ierr := env.client.InspectContainer(env.container.ID) +// func TestStartDockerEnvironment(t *testing.T) { +// env, _ := createTestDockerEnv(nil) +// env.Create() +// err := env.Start() - assert.Nil(t, err) - assert.IsType(t, ierr, &docker.NoSuchContainer{}) -} +// i, ierr := env.client.InspectContainer(env.container.ID) -func TestStartDockerEnvironment(t *testing.T) { - env, _ := createTestDockerEnv(nil) - env.Create() - err := env.Start() +// assert.Nil(t, err) +// assert.Nil(t, ierr) +// assert.True(t, i.State.Running) - i, ierr := env.client.InspectContainer(env.container.ID) +// env.client.KillContainer(docker.KillContainerOptions{ +// ID: env.container.ID, +// }) +// env.Destroy() +// } - assert.Nil(t, err) - assert.Nil(t, ierr) - assert.True(t, i.State.Running) +// func TestStopDockerEnvironment(t *testing.T) { +// env, _ := createTestDockerEnv(nil) +// env.Create() +// env.Start() +// err := env.Stop() - env.client.KillContainer(docker.KillContainerOptions{ - ID: env.container.ID, - }) - env.Destroy() -} +// i, ierr := env.client.InspectContainer(env.container.ID) -func TestStopDockerEnvironment(t *testing.T) { - env, _ := createTestDockerEnv(nil) - env.Create() - env.Start() - err := env.Stop() +// assert.Nil(t, err) +// assert.Nil(t, ierr) +// assert.False(t, i.State.Running) - i, ierr := env.client.InspectContainer(env.container.ID) +// env.client.KillContainer(docker.KillContainerOptions{ +// ID: env.container.ID, +// }) +// env.Destroy() +// } - assert.Nil(t, err) - assert.Nil(t, ierr) - assert.False(t, i.State.Running) +// func TestKillDockerEnvironment(t *testing.T) { +// env, _ := createTestDockerEnv(nil) +// env.Create() +// env.Start() +// err := env.Kill() - env.client.KillContainer(docker.KillContainerOptions{ - ID: env.container.ID, - }) - env.Destroy() -} +// i, ierr := env.client.InspectContainer(env.container.ID) -func TestKillDockerEnvironment(t *testing.T) { - env, _ := createTestDockerEnv(nil) - env.Create() - env.Start() - err := env.Kill() +// assert.Nil(t, err) +// assert.Nil(t, ierr) +// assert.False(t, i.State.Running) - i, ierr := env.client.InspectContainer(env.container.ID) +// env.client.KillContainer(docker.KillContainerOptions{ +// ID: env.container.ID, +// }) +// env.Destroy() +// } - assert.Nil(t, err) - assert.Nil(t, ierr) - assert.False(t, i.State.Running) +// func TestExecDockerEnvironment(t *testing.T) { - env.client.KillContainer(docker.KillContainerOptions{ - ID: env.container.ID, - }) - 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 -} +// func createTestDockerEnv(s *ServerStruct) (*dockerEnvironment, error) { +// if s == nil { +// s = testServer() +// } +// env, err := NewDockerEnvironment(s) +// return env.(*dockerEnvironment), err +// } diff --git a/wings-api.paw b/wings-api.paw new file mode 100644 index 0000000000000000000000000000000000000000..78945495cc7d01c72af52ac24589d082fb9d8c46 GIT binary patch literal 16154 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 C7wM`1 literal 0 HcmV?d00001 From 53704fb0d7288b71882a9e3ff939622c5b981ae8 Mon Sep 17 00:00:00 2001 From: Jakob Schrettenbrunner Date: Wed, 14 Mar 2018 10:34:06 +0100 Subject: [PATCH 13/19] refactor websockets implementation --- api/websockets/client.go | 76 -------------------- api/websockets/collection.go | 120 ++++++++++++++++++++++++++++++++ api/websockets/consolewriter.go | 28 -------- api/websockets/hub.go | 120 -------------------------------- api/websockets/message.go | 4 +- api/websockets/socket.go | 81 +++++++++++++++++++++ control/docker_environment.go | 27 +++++++ control/server.go | 6 +- control/server_util.go | 2 +- 9 files changed, 234 insertions(+), 230 deletions(-) delete mode 100644 api/websockets/client.go create mode 100644 api/websockets/collection.go delete mode 100644 api/websockets/consolewriter.go delete mode 100644 api/websockets/hub.go create mode 100644 api/websockets/socket.go diff --git a/api/websockets/client.go b/api/websockets/client.go deleted file mode 100644 index 43147c4..0000000 --- a/api/websockets/client.go +++ /dev/null @@ -1,76 +0,0 @@ -package websockets - -import "github.com/gorilla/websocket" -import ( - "time" - - log "github.com/sirupsen/logrus" -) - -type Client struct { - hub *Hub - - socket *websocket.Conn - - send chan []byte -} - -func (c *Client) readPump() { - defer func() { - c.hub.unregister <- c - c.socket.Close() - }() - c.socket.SetReadLimit(maxMessageSize) - c.socket.SetReadDeadline(time.Now().Add(pongWait)) - c.socket.SetPongHandler(func(string) error { - c.socket.SetReadDeadline(time.Now().Add(pongWait)) - return nil - }) - for { - _, _, err := c.socket.ReadMessage() - if err != nil { - if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway) { - log.WithError(err).Debug("Websocket closed unexpectedly.") - } - return - } - } -} - -func (c *Client) writePump() { - ticker := time.NewTicker(pingPeriod) - defer func() { - ticker.Stop() - c.socket.Close() - }() - for { - select { - case m, ok := <-c.send: - c.socket.SetWriteDeadline(time.Now().Add(writeWait)) - if !ok { - // The hub closed the channel - c.socket.WriteMessage(websocket.CloseMessage, []byte{}) - return - } - w, err := c.socket.NextWriter(websocket.TextMessage) - if err != nil { - return - } - w.Write([]byte{'['}) - w.Write(m) - for i := 0; i < len(c.send)+1; i++ { - w.Write([]byte{','}) - w.Write(<-c.send) - } - w.Write([]byte{']'}) - if err := w.Close(); err != nil { - return - } - case <-ticker.C: - c.socket.SetWriteDeadline(time.Now().Add(writeWait)) - if err := c.socket.WriteMessage(websocket.PingMessage, []byte{}); err != nil { - return - } - } - } -} diff --git a/api/websockets/collection.go b/api/websockets/collection.go new file mode 100644 index 0000000..dc5425b --- /dev/null +++ b/api/websockets/collection.go @@ -0,0 +1,120 @@ +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 +} diff --git a/api/websockets/consolewriter.go b/api/websockets/consolewriter.go deleted file mode 100644 index 13df333..0000000 --- a/api/websockets/consolewriter.go +++ /dev/null @@ -1,28 +0,0 @@ -package websockets - -import "io" - -type ConsoleWriter struct { - Hub *Hub - HandlerFunc *func(string) -} - -var _ io.Writer = ConsoleWriter{} - -func (c ConsoleWriter) Write(b []byte) (n int, e error) { - line := make([]byte, len(b)) - copy(line, b) - m := Message{ - Type: MessageTypeConsole, - Payload: ConsolePayload{ - Line: string(line), - Level: ConsoleLevelPlain, - Source: ConsoleSourceServer, - }, - } - c.Hub.Broadcast <- m - if c.HandlerFunc != nil { - (*c.HandlerFunc)(string(line)) - } - return len(b), nil -} diff --git a/api/websockets/hub.go b/api/websockets/hub.go deleted file mode 100644 index 11d42d1..0000000 --- a/api/websockets/hub.go +++ /dev/null @@ -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[*Client]bool - -type Hub struct { - clients websocketMap - - Broadcast chan Message - - register chan *Client - unregister chan *Client - close chan bool -} - -//var _ io.Writer = &Hub{} - -func NewHub() *Hub { - return &Hub{ - Broadcast: make(chan Message), - register: make(chan *Client), - unregister: make(chan *Client), - close: make(chan bool), - clients: make(websocketMap), - } -} - -func (h *Hub) 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 - } - c := &Client{ - hub: h, - socket: socket, - send: make(chan []byte, 256), - } - h.register <- c - - go c.readPump() - go c.writePump() -} - -func (h *Hub) Subscribe(c *Client) { - h.register <- c -} - -func (h *Hub) Unsubscribe(c *Client) { - h.unregister <- c -} - -func (h *Hub) Run() { - defer func() { - for s := range h.clients { - close(s.send) - delete(h.clients, s) - } - close(h.register) - close(h.unregister) - close(h.Broadcast) - close(h.close) - }() - for { - select { - case s := <-h.register: - h.clients[s] = true - case s := <-h.unregister: - if _, ok := h.clients[s]; ok { - delete(h.clients, s) - close(s.send) - } - case m := <-h.Broadcast: - b, err := json.Marshal(m) - if err != nil { - log.WithError(err).Error("Failed to encode websocket message.") - continue - } - for s := range h.clients { - select { - case s.send <- b: - default: - close(s.send) - delete(h.clients, s) - } - } - case <-h.close: - return - } - } -} - -func (h *Hub) Close() { - h.close <- true -} diff --git a/api/websockets/message.go b/api/websockets/message.go index 52b31e9..8ce2230 100644 --- a/api/websockets/message.go +++ b/api/websockets/message.go @@ -47,8 +47,8 @@ type ConsolePayload struct { Line string `json:"line"` } -func (h *Hub) Log(l ConsoleLevel, m string) { - h.Broadcast <- Message{ +func (c *Collection) Log(l ConsoleLevel, m string) { + c.Broadcast <- Message{ Type: MessageTypeConsole, Payload: ConsolePayload{ Source: ConsoleSourceWings, diff --git a/api/websockets/socket.go b/api/websockets/socket.go new file mode 100644 index 0000000..fe977cd --- /dev/null +++ b/api/websockets/socket.go @@ -0,0 +1,81 @@ +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 + } + } + } +} diff --git a/control/docker_environment.go b/control/docker_environment.go index 9c0b2b1..08b1bcb 100644 --- a/control/docker_environment.go +++ b/control/docker_environment.go @@ -67,7 +67,34 @@ 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 } diff --git a/control/server.go b/control/server.go index bedc721..c21fcd3 100644 --- a/control/server.go +++ b/control/server.go @@ -37,7 +37,7 @@ type Server interface { Save() error Environment() (Environment, error) - Websockets() *websockets.Hub + Websockets() *websockets.Collection HasPermission(string, string) bool } @@ -73,7 +73,7 @@ type ServerStruct struct { // TODO remove Keys map[string][]string `json:"keys"` - websockets *websockets.Hub + websockets *websockets.Collection status Status } @@ -161,7 +161,7 @@ func (s *ServerStruct) init() error { } s.status = StatusStopped - s.websockets = websockets.NewHub() + s.websockets = websockets.NewCollection() go s.websockets.Run() var err error diff --git a/control/server_util.go b/control/server_util.go index 03d20e5..d5d6ada 100644 --- a/control/server_util.go +++ b/control/server_util.go @@ -29,7 +29,7 @@ func (s *ServerStruct) Environment() (Environment, error) { return s.environment, nil } -func (s *ServerStruct) Websockets() *websockets.Hub { +func (s *ServerStruct) Websockets() *websockets.Collection { return s.websockets } From 4b182e9fa67074e9e10cb5ada35155e78f73ccd9 Mon Sep 17 00:00:00 2001 From: Jakob Schrettenbrunner Date: Wed, 14 Mar 2018 10:34:28 +0100 Subject: [PATCH 14/19] add ctop to vagrant provision for fancy monitoring of containers --- .dev/vagrant/provision.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.dev/vagrant/provision.sh b/.dev/vagrant/provision.sh index 07cc69d..94c732b 100644 --- a/.dev/vagrant/provision.sh +++ b/.dev/vagrant/provision.sh @@ -30,6 +30,10 @@ sudo -H -u vagrant bash -c 'go get -u github.com/derekparker/delve/cmd/dlv' echo "Install additional dependencies" apt-get -y install mercurial #tar unzip make gcc g++ python > /dev/null +echo "Install ctop for fancy container monitoring" +wget https://github.com/bcicen/ctop/releases/download/v0.7.1/ctop-0.7.1-linux-amd64 -O /usr/local/bin/ctop +chmod +x /usr/local/bin/ctop + echo " ------------" echo "Gopath is /home/vagrant/go" echo "The project is mounted to /home/vagrant/go/src/github.com/pterodactyl/wings" From 8b43e3a43d174d3f40ede749ccd145b2bd10822c Mon Sep 17 00:00:00 2001 From: Jakob Schrettenbrunner Date: Wed, 14 Mar 2018 10:35:49 +0100 Subject: [PATCH 15/19] rename docker_environment to environment_docker --- control/{docker_environment.go => environment_docker.go} | 0 .../{docker_environment_test.go => environment_docker_test.go} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename control/{docker_environment.go => environment_docker.go} (100%) rename control/{docker_environment_test.go => environment_docker_test.go} (100%) diff --git a/control/docker_environment.go b/control/environment_docker.go similarity index 100% rename from control/docker_environment.go rename to control/environment_docker.go diff --git a/control/docker_environment_test.go b/control/environment_docker_test.go similarity index 100% rename from control/docker_environment_test.go rename to control/environment_docker_test.go From 5bba1c0d9015f77026a7af256f4d6fd5756ca288 Mon Sep 17 00:00:00 2001 From: Jakob Schrettenbrunner Date: Wed, 14 Mar 2018 13:59:57 +0100 Subject: [PATCH 16/19] readd docker environment tests implement Exec() --- control/environment_docker.go | 28 +++-- control/environment_docker_test.go | 196 +++++++++++++++-------------- control/server_persistance.go | 7 +- wings-api.paw | Bin 16154 -> 18283 bytes 4 files changed, 123 insertions(+), 108 deletions(-) diff --git a/control/environment_docker.go b/control/environment_docker.go index 08b1bcb..1d7daec 100644 --- a/control/environment_docker.go +++ b/control/environment_docker.go @@ -199,8 +199,8 @@ func (env *dockerEnvironment) Start() error { 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 10min - timeout := time.Minute * 10 + // 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 @@ -211,7 +211,7 @@ func (env *dockerEnvironment) Stop() error { 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, "SIGKILL"); err != nil { + 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 } @@ -220,24 +220,26 @@ func (env *dockerEnvironment) Kill() error { // Exec sends commands to the standard input of the docker container func (env *dockerEnvironment) Exec(command string) error { - //log.Debug("Command: " + command) - //_, err := env.containerInput.Write([]byte(command + "\n")) - //return err - return nil + 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 - } + //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 err + return nil } diff --git a/control/environment_docker_test.go b/control/environment_docker_test.go index 9d8f438..68aeb10 100644 --- a/control/environment_docker_test.go +++ b/control/environment_docker_test.go @@ -1,125 +1,133 @@ package control -// func testServer() *ServerStruct { -// return &ServerStruct{ -// ID: "testuuid-something-something", -// service: &service{ -// DockerImage: "alpine:latest", -// }, -// } -// } +import ( + "context" + "fmt" + "testing" -// func TestNewDockerEnvironment(t *testing.T) { -// env, err := createTestDockerEnv(nil) + "github.com/pterodactyl/wings/api/websockets" -// assert.Nil(t, err) -// assert.NotNil(t, env) -// assert.NotNil(t, env.client) -// } + "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 TestNewDockerEnvironmentExisting(t *testing.T) { -// eenv, _ := createTestDockerEnv(nil) -// eenv.Create() +func testServer() *ServerStruct { + viper.SetDefault(config.DataPath, "./data") + return &ServerStruct{ + ID: "testuuid-something-something", + Service: &Service{ + DockerImage: "alpine:latest", + }, + StartupCommand: "/bin/ash echo hello && sleep 100", + websockets: websockets.NewCollection(), + } +} -// env, err := createTestDockerEnv(eenv.server) +func TestNewDockerEnvironment(t *testing.T) { + env, err := createTestDockerEnv(nil) -// assert.Nil(t, err) -// assert.NotNil(t, env) -// assert.NotNil(t, env.container) + assert.Nil(t, err) + assert.NotNil(t, env) + assert.NotNil(t, env.client) +} -// eenv.Destroy() -// } +func TestNewDockerEnvironmentExisting(t *testing.T) { + eenv, _ := createTestDockerEnv(nil) + eenv.Create() -// func TestCreateDockerEnvironment(t *testing.T) { -// env, _ := createTestDockerEnv(nil) + env, err := createTestDockerEnv(eenv.server) -// err := env.Create() + assert.Nil(t, err) + assert.NotNil(t, env) + assert.NotNil(t, env.server.DockerContainer) -// a := assert.New(t) -// a.Nil(err) -// a.NotNil(env.container) -// a.Equal(env.container.Name, "ptdl_testuuid") + eenv.Destroy() +} -// if err := env.client.RemoveContainer(docker.RemoveContainerOptions{ -// ID: env.container.ID, -// }); err != nil { -// fmt.Println(err) -// } -// } +func TestCreateDockerEnvironment(t *testing.T) { + env, _ := createTestDockerEnv(nil) -// func TestDestroyDockerEnvironment(t *testing.T) { -// env, _ := createTestDockerEnv(nil) -// env.Create() + err := env.Create() -// err := env.Destroy() + a := assert.New(t) + a.Nil(err) + a.NotNil(env.server.DockerContainer.ID) -// _, ierr := env.client.InspectContainer(env.container.ID) + if err := env.client.ContainerRemove(context.TODO(), env.server.DockerContainer.ID, types.ContainerRemoveOptions{}); err != nil { + fmt.Println(err) + } +} -// assert.Nil(t, err) -// assert.IsType(t, ierr, &docker.NoSuchContainer{}) -// } +func TestDestroyDockerEnvironment(t *testing.T) { + env, _ := createTestDockerEnv(nil) + env.Create() -// func TestStartDockerEnvironment(t *testing.T) { -// env, _ := createTestDockerEnv(nil) -// env.Create() -// err := env.Start() + err := env.Destroy() -// i, ierr := env.client.InspectContainer(env.container.ID) + _, ierr := env.client.ContainerInspect(context.TODO(), env.server.DockerContainer.ID) -// assert.Nil(t, err) -// assert.Nil(t, ierr) -// assert.True(t, i.State.Running) + assert.Nil(t, err) + assert.True(t, client.IsErrNotFound(ierr)) +} -// env.client.KillContainer(docker.KillContainerOptions{ -// ID: env.container.ID, -// }) -// env.Destroy() -// } +func TestStartDockerEnvironment(t *testing.T) { + env, _ := createTestDockerEnv(nil) + env.Create() + err := env.Start() -// 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) -// i, ierr := env.client.InspectContainer(env.container.ID) + assert.Nil(t, err) + assert.Nil(t, ierr) + assert.True(t, i.State.Running) -// 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() +} -// env.client.KillContainer(docker.KillContainerOptions{ -// ID: env.container.ID, -// }) -// env.Destroy() -// } +func TestStopDockerEnvironment(t *testing.T) { + env, _ := createTestDockerEnv(nil) + env.Create() + env.Start() + err := env.Stop() -// 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) -// i, ierr := env.client.InspectContainer(env.container.ID) + assert.Nil(t, err) + assert.Nil(t, ierr) + assert.False(t, i.State.Running) -// 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() +} -// env.client.KillContainer(docker.KillContainerOptions{ -// ID: env.container.ID, -// }) -// env.Destroy() -// } +func TestKillDockerEnvironment(t *testing.T) { + env, _ := createTestDockerEnv(nil) + env.Create() + env.Start() + err := env.Kill() -// func TestExecDockerEnvironment(t *testing.T) { + 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) -// func createTestDockerEnv(s *ServerStruct) (*dockerEnvironment, error) { -// if s == nil { -// s = testServer() -// } -// env, err := NewDockerEnvironment(s) -// return env.(*dockerEnvironment), err -// } + 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 +} diff --git a/control/server_persistance.go b/control/server_persistance.go index 98c93bd..14c2520 100644 --- a/control/server_persistance.go +++ b/control/server_persistance.go @@ -91,7 +91,12 @@ func (s *ServerStruct) Save() error { } func (s *ServerStruct) path() string { - return filepath.Join(viper.GetString(config.DataPath), constants.ServersPath, s.ID) + 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 { diff --git a/wings-api.paw b/wings-api.paw index 78945495cc7d01c72af52ac24589d082fb9d8c46..0df8451d2e35917e905d70e0954081b69731de63 100644 GIT binary patch delta 8726 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 CHY6wj From 27b397c71235a8741889be3e166dc7e6e3407543 Mon Sep 17 00:00:00 2001 From: Jakob Schrettenbrunner Date: Sun, 1 Apr 2018 23:41:45 +0200 Subject: [PATCH 17/19] fix environment_docker_test data path --- .gitignore | 2 ++ control/environment_docker_test.go | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 281344a..a92947b 100644 --- a/.gitignore +++ b/.gitignore @@ -39,6 +39,8 @@ wings.exe # IDE/Editor files (VS Code) /.vscode +# test files +test_*/ # Keep all gitkeep files (This needs to stay at the bottom) !.gitkeep diff --git a/control/environment_docker_test.go b/control/environment_docker_test.go index 68aeb10..f5ce62e 100644 --- a/control/environment_docker_test.go +++ b/control/environment_docker_test.go @@ -15,7 +15,7 @@ import ( ) func testServer() *ServerStruct { - viper.SetDefault(config.DataPath, "./data") + viper.SetDefault(config.DataPath, "./test_data") return &ServerStruct{ ID: "testuuid-something-something", Service: &Service{ From 1ce617c9a4441a806bd14b36557247a933b5d5fa Mon Sep 17 00:00:00 2001 From: Jakob Schrettenbrunner Date: Sun, 1 Apr 2018 23:52:23 +0200 Subject: [PATCH 18/19] spell persistence correctly --- control/{server_persistance.go => server_persistence.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename control/{server_persistance.go => server_persistence.go} (100%) diff --git a/control/server_persistance.go b/control/server_persistence.go similarity index 100% rename from control/server_persistance.go rename to control/server_persistence.go From 53a3fbc46479e3c316245997c5e7bb7bd2113dcf Mon Sep 17 00:00:00 2001 From: Jakob Schrettenbrunner Date: Thu, 17 May 2018 21:18:02 +0200 Subject: [PATCH 19/19] remove trailing slashes from api use correct handler to provide server logs remove Baerer from Authorization header to compare it with known tokens fix wings-api.paw routes --- api/api.go | 10 +++++----- api/auth.go | 7 +++++-- api/handlers_server.go | 4 ---- config/config_test.go | 2 +- wings-api.paw | Bin 20278 -> 21104 bytes 5 files changed, 11 insertions(+), 12 deletions(-) diff --git a/api/api.go b/api/api.go index 42f62c5..7fde00a 100644 --- a/api/api.go +++ b/api/api.go @@ -51,7 +51,7 @@ func (api *InternalAPI) Listen() { func (api *InternalAPI) register() { v1 := api.router.Group("/api/v1") { - v1.GET("/", AuthHandler(""), GetIndex) + v1.GET("", AuthHandler(""), GetIndex) //v1.PATCH("/config", AuthHandler("c:config"), PatchConfiguration) v1.GET("/servers", AuthHandler("c:list"), handleGetServers) @@ -59,15 +59,15 @@ func (api *InternalAPI) register() { 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.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"), handleGetServerLog) + v1ServerRoutes.GET("/log", AuthHandler("s:console"), handleGetConsole) v1ServerRoutes.POST("/suspend", AuthHandler(""), handlePostServerSuspend) v1ServerRoutes.POST("/unsuspend", AuthHandler(""), handlePostServerUnsuspend) } diff --git a/api/auth.go b/api/auth.go index b9bb645..f19efee 100644 --- a/api/auth.go +++ b/api/auth.go @@ -2,8 +2,9 @@ package api import ( "net/http" + "strings" - "strconv" + "strconv" "github.com/gin-gonic/gin" "github.com/google/jsonapi" @@ -71,7 +72,9 @@ func (a *authorizationManager) HasPermission(permission string) bool { func AuthHandler(permission string) gin.HandlerFunc { return func(c *gin.Context) { requestToken := c.Request.Header.Get(accessTokenHeader) - if requestToken == "" { + if requestToken != "" && strings.HasPrefix(requestToken, "Baerer ") { + requestToken = requestToken[7:] + } else { requestToken = c.Query("token") } requestServer := c.Param("server") diff --git a/api/handlers_server.go b/api/handlers_server.go index a339b3c..3107f12 100644 --- a/api/handlers_server.go +++ b/api/handlers_server.go @@ -181,10 +181,6 @@ func handleGetConsole(c *gin.Context) { server.Websockets().Upgrade(c.Writer, c.Request) } -func handleGetServerLog(c *gin.Context) { - -} - func handlePostServerSuspend(c *gin.Context) { } diff --git a/config/config_test.go b/config/config_test.go index 1773208..7450c44 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -8,7 +8,7 @@ import ( "github.com/stretchr/testify/assert" ) -const configFile = "../config.yml.example" +const configFile = "../config.example.yml" func TestLoadConfiguraiton(t *testing.T) { err := LoadConfiguration(configFile) diff --git a/wings-api.paw b/wings-api.paw index a13dc590345a1890092ccd3384a997a5aab126c4..af01f9c6e041240ed4be513d378e9f571771e70e 100644 GIT binary patch delta 10396 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 delta 9670 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