[#3896bk] Configure base support for properties file parsing

This commit is contained in:
Dane Everitt 2019-11-30 18:07:05 -08:00
parent 11c6738264
commit 1003abaa63
No known key found for this signature in database
GPG Key ID: EEA66103B3D71F53
6 changed files with 145 additions and 43 deletions

View File

@ -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.

1
go.mod
View File

@ -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

2
go.sum
View File

@ -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=

98
parser/parser.go Normal file
View File

@ -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
}

33
server/config_parser.go Normal file
View File

@ -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()
}

View File

@ -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 {