From 1003abaa639947e721ae9010c65c7dc135b670c4 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sat, 30 Nov 2019 18:07:05 -0800 Subject: [PATCH] [#3896bk] Configure base support for properties file parsing --- api/server_endpoints.go | 47 ++--------------- go.mod | 1 + go.sum | 2 + parser/parser.go | 98 ++++++++++++++++++++++++++++++++++++ server/config_parser.go | 33 ++++++++++++ server/environment_docker.go | 7 +++ 6 files changed, 145 insertions(+), 43 deletions(-) create mode 100644 parser/parser.go create mode 100644 server/config_parser.go diff --git a/api/server_endpoints.go b/api/server_endpoints.go index 2bea33e..02c8a58 100644 --- a/api/server_endpoints.go +++ b/api/server_endpoints.go @@ -3,55 +3,16 @@ package api import ( "encoding/json" "fmt" - "github.com/buger/jsonparser" - "github.com/pkg/errors" + "github.com/pterodactyl/wings/parser" "go.uber.org/zap" ) const ( - ProcessStopCommand = "command" - ProcessStopSignal = "signal" + ProcessStopCommand = "command" + ProcessStopSignal = "signal" ProcessStopNativeStop = "stop" ) -// Defines a single find/replace instance for a given server configuration file. -type ConfigurationFileReplacement struct { - Match string `json:"match"` - Value string `json:"value"` - ValueType jsonparser.ValueType `json:"-"` -} - -func (cfr *ConfigurationFileReplacement) UnmarshalJSON(data []byte) error { - if m, err := jsonparser.GetString(data, "match"); err != nil { - return err - } else { - cfr.Match = m - } - - if v, dt, _, err := jsonparser.Get(data, "value"); err != nil { - return err - } else { - if dt != jsonparser.String && dt != jsonparser.Number && dt != jsonparser.Boolean { - return errors.New( - fmt.Sprintf("cannot parse JSON: received unexpected replacement value type: %d", dt), - ) - } - - cfr.Value = string(v) - cfr.ValueType = dt - } - - return nil -} - -// Defines a configuration file for the server startup. These will be looped over -// and modified before the server finishes booting. -type ConfigurationFile struct { - FileName string `json:"file"` - Parser string `json:"parser"` - Replace []ConfigurationFileReplacement `json:"replace"` -} - // Defines the process configuration for a given server instance. This sets what the // daemon is looking for to mark a server as done starting, what to do when stopping, // and what changes to make to the configuration file for a server. @@ -64,7 +25,7 @@ type ServerConfiguration struct { Type string `json:"type"` Value string `json:"value"` } `json:"stop"` - ConfigurationFiles []ConfigurationFile `json:"configs"` + ConfigurationFiles []parser.ConfigurationFile `json:"configs"` } // Fetches the server configuration and returns the struct for it. diff --git a/go.mod b/go.mod index e8b18f3..564d203 100644 --- a/go.mod +++ b/go.mod @@ -23,6 +23,7 @@ require ( github.com/imdario/mergo v0.3.8 github.com/julienschmidt/httprouter v1.2.0 github.com/kr/pretty v0.1.0 // indirect + github.com/magiconair/properties v1.8.1 github.com/mcuadros/go-defaults v1.1.0 github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db github.com/olebedev/emitter v0.0.0-20190110104742-e8d1457e6aee diff --git a/go.sum b/go.sum index 1f30294..7c4fbc4 100644 --- a/go.sum +++ b/go.sum @@ -53,6 +53,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/magefile/mage v1.9.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= +github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mcuadros/go-defaults v1.1.0 h1:K0LgSNfsSUrbEHR7HgfZpOHVWYsPnYh/dKTA7pGeZ/I= github.com/mcuadros/go-defaults v1.1.0/go.mod h1:vl9cJiNIIHISQeboDhZBUCiCOa3GkeioLe3Y95NXF6Y= github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ= diff --git a/parser/parser.go b/parser/parser.go new file mode 100644 index 0000000..5e8eb35 --- /dev/null +++ b/parser/parser.go @@ -0,0 +1,98 @@ +package parser + +import ( + "fmt" + "github.com/buger/jsonparser" + "github.com/magiconair/properties" + "github.com/pkg/errors" + "go.uber.org/zap" + "os" +) + +type ConfigurationParser string + +// The file parsing options that are available for a server configuration file. +const ( + File = "file" + Yaml = "yaml" + Properties = "properties" + Ini = "ini" + Json = "json" + Xml = "xml" +) + +// Defines a configuration file for the server startup. These will be looped over +// and modified before the server finishes booting. +type ConfigurationFile struct { + FileName string `json:"file"` + Parser ConfigurationParser `json:"parser"` + Replace []ConfigurationFileReplacement `json:"replace"` +} + +// Defines a single find/replace instance for a given server configuration file. +type ConfigurationFileReplacement struct { + Match string `json:"match"` + Value string `json:"value"` + ValueType jsonparser.ValueType `json:"-"` +} + +func (cfr *ConfigurationFileReplacement) UnmarshalJSON(data []byte) error { + if m, err := jsonparser.GetString(data, "match"); err != nil { + return err + } else { + cfr.Match = m + } + + if v, dt, _, err := jsonparser.Get(data, "value"); err != nil { + return err + } else { + if dt != jsonparser.String && dt != jsonparser.Number && dt != jsonparser.Boolean { + return errors.New( + fmt.Sprintf("cannot parse JSON: received unexpected replacement value type: %d", dt), + ) + } + + cfr.Value = string(v) + cfr.ValueType = dt + } + + return nil +} + +// Parses a given configuration file and updates all of the values within as defined +// in the API response from the Panel. +func (f *ConfigurationFile) Parse(path string) error { + zap.S().Debugw("parsing configuration file", zap.String("path", path), zap.String("parser", string(f.Parser))) + + switch f.Parser { + case Properties: + f.parsePropertiesFile(path) + break + } + + return nil +} + +// Parses a properties file and updates the values within it to match those that +// are passed. Writes the file once completed. +func (f *ConfigurationFile) parsePropertiesFile(path string) error { + p, err := properties.LoadFile(path, properties.UTF8) + if err != nil { + return errors.WithStack(err) + } + + for _, replace := range f.Replace { + if _, _, err := p.Set(replace.Match, replace.Value); err != nil { + return errors.WithStack(err) + } + } + + w, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0644); + if err != nil { + return errors.WithStack(err) + } + + _, err = p.Write(w, properties.UTF8) + + return err +} diff --git a/server/config_parser.go b/server/config_parser.go new file mode 100644 index 0000000..17acc77 --- /dev/null +++ b/server/config_parser.go @@ -0,0 +1,33 @@ +package server + +import ( + "go.uber.org/zap" + "sync" +) + +// Parent function that will update all of the defined configuration files for a server +// automatically to ensure that they always use the specified values. +func (s *Server) UpdateConfigurationFiles() { + wg := new(sync.WaitGroup) + + for _, v := range s.processConfiguration.ConfigurationFiles { + wg.Add(1) + + go func(server *Server) { + defer wg.Done() + + p, err := s.Filesystem.SafePath(v.FileName) + if err != nil { + zap.S().Errorw("failed to generate safe path for configuration file", zap.String("server", server.Uuid), zap.Error(err)) + + return + } + + if err := v.Parse(p); err != nil { + zap.S().Errorw("failed to parse and update server configuration file", zap.String("server", server.Uuid), zap.Error(err)) + } + }(s) + } + + wg.Wait() +} \ No newline at end of file diff --git a/server/environment_docker.go b/server/environment_docker.go index 6c190da..de2b2ab 100644 --- a/server/environment_docker.go +++ b/server/environment_docker.go @@ -261,6 +261,13 @@ func (d *DockerEnvironment) Start() error { } } + // Update the configuration files defined for the server before beginning the boot process. + // This process executes a bunch of parallel updates, so we just block until that process + // is completed. Any errors as a result of this will just be bubbled out in the logger, + // we don't need to actively do anything about it at this point, worst comes to worst the + // server starts in a weird state and the user can manually adjust. + d.Server.UpdateConfigurationFiles() + // Reset the permissions on files for the server before actually trying // to start it. if err := d.Server.Filesystem.Chown("/"); err != nil {