diff --git a/api/config.go b/api/config.go index c0a191f..436f0f1 100644 --- a/api/config.go +++ b/api/config.go @@ -5,6 +5,8 @@ import ( "fmt" "net/http" "os" + "path/filepath" + "strings" "github.com/gin-gonic/gin" "github.com/go-gost/core/observer/stats" @@ -127,10 +129,11 @@ func getConfig(ctx *gin.Context) { // swagger:parameters saveConfigRequest type saveConfigRequest struct { - // output format, one of yaml|json, default is yaml. + // output format, one of yaml|json, default is derived from the file extension. // in: query Format string `form:"format" json:"format"` - // file path, default is gost.yaml|gost.json in current working directory. + // file path, default is the loaded config file, or gost.yaml|gost.json in + // the current working directory when no config file was loaded. // in: query Path string `form:"path" json:"path"` } @@ -155,16 +158,31 @@ func saveConfig(ctx *gin.Context) { var req saveConfigRequest ctx.ShouldBindQuery(&req) - file := "gost.yaml" - switch req.Format { - case "json": - file = "gost.json" - default: - req.Format = "yaml" + file := req.Path + if file == "" { + // Default to the config file that was loaded (via -C), falling back to + // the current working directory when the config came from elsewhere. + file = config.ConfigFile() } - - if req.Path != "" { - file = req.Path + if file == "" { + file = "gost.yaml" + if req.Format == "json" { + file = "gost.json" + } + } + switch strings.ToLower(req.Format) { + case "json": + req.Format = "json" + case "yaml": + req.Format = "yaml" + default: + // Derive the format from the file extension so a loaded JSON config + // is saved back as JSON (ReadFile forces format from extension). + if strings.ToLower(filepath.Ext(file)) == ".json" { + req.Format = "json" + } else { + req.Format = "yaml" + } } f, err := os.Create(file) diff --git a/config/config.go b/config/config.go index 966ad39..9c10be9 100644 --- a/config/config.go +++ b/config/config.go @@ -7,6 +7,7 @@ import ( "reflect" "strings" "sync" + "sync/atomic" "time" "github.com/mitchellh/mapstructure" @@ -28,8 +29,21 @@ func init() { var ( global = &Config{} globalMux sync.RWMutex + + // configFile remembers the last file loaded via ReadFile so the API can + // save back to the same location (issue go-gost/x#123). + configFile atomic.Value // string ) +// ConfigFile returns the path of the last config file loaded via ReadFile, +// or an empty string when the config came from stdin, a URL, or inline JSON. +func ConfigFile() string { + if s, ok := configFile.Load().(string); ok { + return s + } + return "" +} + func Global() *Config { globalMux.RLock() defer globalMux.RUnlock() @@ -780,6 +794,7 @@ func (c *Config) ReadFile(file string) error { if err := v.ReadInConfig(); err != nil { return err } + configFile.Store(file) return v.Unmarshal(c, viper.DecodeHook(gostDecodeHook())) }