diff --git a/HISTORY.md b/HISTORY.md index e3d14ac9..12a13b12 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -17,19 +17,48 @@ Developers are not forced to upgrade if they don't really need it. Upgrade whene **How to upgrade**: Open your command-line and execute this command: `go get -u github.com/kataras/iris` or let the automatic updater do that for you. +# Tu, 07 November 2017 | v8.5.7 + +Nothing crazy here, just one addition which may help some people; + +Able to share configuration between multiple Iris instances based on the `$home_path+iris.yml` configuration file with the **new** [iris.WithGlobalConfiguration](https://github.com/kataras/iris/blob/master/configuration.go#L202) configurator[*](https://github.com/kataras/iris/blob/master/configuration.go#L191). + +Example: + +```go +package main +import "github.com/kataras/iris" + +func main() { + app := iris.New() + app.Get("/", func(ctx iris.Context) { + ctx.HTML("Hello!") + }) + // [...] + + // Good when you share configuration between multiple iris instances. + // This configuration file lives in your $HOME/iris.yml for unix hosts + // or %HOMEDRIVE%+%HOMEPATH%/iris.yml for windows hosts, and you can modify it. + app.Run(iris.Addr(":8080"), iris.WithGlobalConfiguration) + // or before run: + // app.Configure(iris.WithGlobalConfiguration) + // app.Run(iris.Addr(":8080")) +} +``` + # Su, 05 November 2017 | v8.5.6 - **DEPRECATE** the `app.StaticServe`, use `app.StaticWeb` which does the same thing but better or `iris/app.StaticHandler` which gives you more options to work on. - add some debug messages for route registrations, to be aligned with the mvc debug messages. - improve the https://iris-go.com/v8/recipe -- now you can see other files like assets as well -- lexical order of categories instead of "level". -- add [8 more examples](_examples/tree/master/experimental-handlers) to this repository, originally lived at https://github.com/iris-contrib/middleware and https://github.com/iris-contrib/examples/tree/master/experimental-handlers. +- add [8 more examples](_examples/experimental-handlers) to this repository, originally lived at https://github.com/iris-contrib/middleware and https://github.com/iris-contrib/examples/tree/master/experimental-handlers. _TODO;_ - [ ] give the ability to customize the mvc path-method-and path parameters mapping, - [ ] make a github bot which will post the monthly usage and even earnings statistics in a public github markdown file, hope that users will love that type of transparency we will introduce here. -# Tu, 02 November 2017 | v8.5.5 +# Th, 02 November 2017 | v8.5.5 - fix [audio/mpeg3 does not appear to be a valid registered mime type#798](https://github.com/kataras/iris/issues/798]) reported by @kryptodev, - improve the updater's performance and moved that into the framework itself, @@ -272,7 +301,7 @@ Another good example with a typical folder structure, that many developers are u - errors.Reporter.AddErr returns true if the error is added to the stack, otherwise false. - @ZaniaDeveloper fixed https://github.com/kataras/iris/issues/778 with PR: https://github.com/kataras/iris/pull/779. - Add `StatusSeeOther` at [mvc login example](https://github.com/kataras/iris/blob/master/_examples/mvc/login/user/controller.go#L53) for Redirection, reported by @motecshine at https://github.com/kataras/iris/issues/777. -- Fix `DisableVersionChecker` configuration field is not being passed correctly when it was true via `iris.Run(..., iris.WithConfiguration{DisableVersionChecker:true, ...})` call. +- Fix `DisableVersionChecker` configuration field is not being passed correctly when it was true via `app.Run(..., iris.WithConfiguration{DisableVersionChecker:true, ...})` call. # Su, 01 October 2017 | v8.4.4 diff --git a/README.md b/README.md index 86242417..884adad7 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ If you're coming from [nodejs](https://nodejs.org) world, Iris is the [expressjs ## Table Of Content * [Installation](#installation) -* [Latest changes](https://github.com/kataras/iris/blob/master/HISTORY.md#su-05-november-2017--v856) +* [Latest changes](https://github.com/kataras/iris/blob/master/HISTORY.md#tu-07-november-2017--v857) * [Getting started](#getting-started) * [Learn](_examples/) * [MVC (Model View Controller)](_examples/#mvc) **NEW** diff --git a/VERSION b/VERSION index dda843d0..60a5aaea 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -8.5.6:https://github.com/kataras/iris/blob/master/HISTORY.md#su-05-november-2017--v856 \ No newline at end of file +8.5.7:https://github.com/kataras/iris/blob/master/HISTORY.md#tu-07-november-2017--v857 \ No newline at end of file diff --git a/_examples/README.md b/_examples/README.md index c5578eea..4a23bed3 100644 --- a/_examples/README.md +++ b/_examples/README.md @@ -53,6 +53,7 @@ Structuring depends on your own needs. We can't tell you how to design your own - [Functional](configuration/functional/main.go) - [From Configuration Struct](configuration/from-configuration-structure/main.go) - [Import from YAML file](configuration/from-yaml-file/main.go) + * [Share Configuration between multiple instances](configuration/from-yaml-file/shared-configuration/main.go) - [Import from TOML file](configuration/from-toml-file/main.go) ### Routing, Grouping, Dynamic Path Parameters, "Macros" and Custom Context diff --git a/_examples/configuration/from-yaml-file/main.go b/_examples/configuration/from-yaml-file/main.go index 679b0d76..ad42e74b 100644 --- a/_examples/configuration/from-yaml-file/main.go +++ b/_examples/configuration/from-yaml-file/main.go @@ -12,6 +12,8 @@ func main() { // [...] // Good when you have two configurations, one for development and a different one for production use. + // If iris.YAML's input string argument is "~" then it loads the configuration from the home directory + // and can be shared between many iris instances. app.Run(iris.Addr(":8080"), iris.WithConfiguration(iris.YAML("./configs/iris.yml"))) // or before run: diff --git a/_examples/configuration/from-yaml-file/shared-configuration/main.go b/_examples/configuration/from-yaml-file/shared-configuration/main.go new file mode 100644 index 00000000..25159af3 --- /dev/null +++ b/_examples/configuration/from-yaml-file/shared-configuration/main.go @@ -0,0 +1,21 @@ +package main + +import ( + "github.com/kataras/iris" +) + +func main() { + app := iris.New() + app.Get("/", func(ctx iris.Context) { + ctx.HTML("Hello!") + }) + // [...] + + // Good when you share configuration between multiple iris instances. + // This configuration file lives in your $HOME/iris.yml for unix hosts + // or %HOMEDRIVE%+%HOMEPATH%/iris.yml for windows hosts, and you can modify it. + app.Run(iris.Addr(":8080"), iris.WithGlobalConfiguration) + // or before run: + // app.Configure(iris.WithGlobalConfiguration) + // app.Run(iris.Addr(":8080")) +} diff --git a/configuration.go b/configuration.go index 5a2ed94a..a171ae3e 100644 --- a/configuration.go +++ b/configuration.go @@ -2,44 +2,128 @@ package iris import ( "io/ioutil" + "os" + "os/user" "path/filepath" + "runtime" "github.com/BurntSushi/toml" + "github.com/kataras/golog" "gopkg.in/yaml.v2" "github.com/kataras/iris/context" "github.com/kataras/iris/core/errors" ) +const globalConfigurationKeyword = "~" + +var globalConfigurationExisted = false + +// homeConfigurationFilename returns the physical location of the global configuration(yaml or toml) file. +// This is useful when we run multiple iris servers that share the same +// configuration, even with custom values at its "Other" field. +// It will return a file location +// which targets to $HOME or %HOMEDRIVE%+%HOMEPATH% + "iris" + the given "ext". +func homeConfigurationFilename(ext string) string { + return filepath.Join(homeDir(), "iris"+ext) +} + +func init() { + filename := homeConfigurationFilename(".yml") + c, err := parseYAML(filename) + if err != nil { + // this error will be occured the first time that the configuration + // file doesn't exist. + // Create the YAML-ONLY global configuration file now using the default configuration 'c'. + // This is useful when we run multiple iris servers that share the same + // configuration, even with custom values at its "Other" field. + out, err := yaml.Marshal(&c) + + if err == nil { + err = ioutil.WriteFile(filename, out, os.FileMode(0666)) + } + if err != nil { + golog.Debugf("error while writing the global configuration field at: %s. Trace: %v\n", filename, err) + } + } else { + globalConfigurationExisted = true + } +} + +func homeDir() (home string) { + u, err := user.Current() + if u != nil && err == nil { + home = u.HomeDir + } + + if home == "" { + home = os.Getenv("HOME") + } + + if home == "" { + if runtime.GOOS == "plan9" { + home = os.Getenv("home") + } else if runtime.GOOS == "windows" { + home = os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH") + if home == "" { + home = os.Getenv("USERPROFILE") + } + } + } + + return +} + var errConfigurationDecode = errors.New("error while trying to decode configuration") +func parseYAML(filename string) (Configuration, error) { + c := DefaultConfiguration() + // get the abs + // which will try to find the 'filename' from current workind dir too. + yamlAbsPath, err := filepath.Abs(filename) + if err != nil { + return c, errConfigurationDecode.AppendErr(err) + } + + // read the raw contents of the file + data, err := ioutil.ReadFile(yamlAbsPath) + if err != nil { + return c, errConfigurationDecode.AppendErr(err) + } + + // put the file's contents as yaml to the default configuration(c) + if err := yaml.Unmarshal(data, &c); err != nil { + return c, errConfigurationDecode.AppendErr(err) + } + return c, nil +} + // YAML reads Configuration from a configuration.yml file. // // Accepts the absolute path of the cfg.yml. // An error will be shown to the user via panic with the error message. // Error may occur when the cfg.yml doesn't exists or is not formatted correctly. // +// Note: if the char '~' passed as "filename" then it tries to load and return +// the configuration from the $home_directory + iris.yml, +// see `WithGlobalConfiguration` for more information. +// // Usage: -// app := iris.Run(iris.Addr(":8080"), iris.WithConfiguration(iris.YAML("myconfig.yml"))) +// app.Configure(iris.WithConfiguration(iris.YAML("myconfig.yml"))) or +// app.Run([iris.Runner], iris.WithConfiguration(iris.YAML("myconfig.yml"))). func YAML(filename string) Configuration { - c := DefaultConfiguration() - - // get the abs - // which will try to find the 'filename' from current workind dir too. - yamlAbsPath, err := filepath.Abs(filename) - if err != nil { - panic(errConfigurationDecode.AppendErr(err)) + // check for globe configuration file and use that, otherwise + // return the default configuration if file doesn't exist. + if filename == globalConfigurationKeyword { + filename = homeConfigurationFilename(".yml") + if _, err := os.Stat(filename); os.IsNotExist(err) { + panic("default configuration file '" + filename + "' does not exist") + } } - // read the raw contents of the file - data, err := ioutil.ReadFile(yamlAbsPath) + c, err := parseYAML(filename) if err != nil { - panic(errConfigurationDecode.AppendErr(err)) - } - - // put the file's contents as yaml to the default configuration(c) - if err := yaml.Unmarshal(data, &c); err != nil { - panic(errConfigurationDecode.AppendErr(err)) + panic(err) } return c @@ -54,11 +138,25 @@ func YAML(filename string) Configuration { // An error will be shown to the user via panic with the error message. // Error may occur when the file doesn't exists or is not formatted correctly. // +// Note: if the char '~' passed as "filename" then it tries to load and return +// the configuration from the $home_directory + iris.tml, +// see `WithGlobalConfiguration` for more information. +// // Usage: -// app := iris.Run(iris.Addr(":8080"), iris.WithConfiguration(iris.YAML("myconfig.tml"))) +// app.Configure(iris.WithConfiguration(iris.YAML("myconfig.tml"))) or +// app.Run([iris.Runner], iris.WithConfiguration(iris.YAML("myconfig.tml"))). func TOML(filename string) Configuration { c := DefaultConfiguration() + // check for globe configuration file and use that, otherwise + // return the default configuration if file doesn't exist. + if filename == globalConfigurationKeyword { + filename = homeConfigurationFilename(".tml") + if _, err := os.Stat(filename); os.IsNotExist(err) { + panic("default configuration file '" + filename + "' does not exist") + } + } + // get the abs // which will try to find the 'filename' from current workind dir too. tomlAbsPath, err := filepath.Abs(filename) @@ -92,6 +190,19 @@ func TOML(filename string) Configuration { // Currently Configurator is being used to describe the configuration's fields values. type Configurator func(*Application) +// WithGlobalConfiguration will load the global yaml configuration file +// from the home directory and it will set/override the whole app's configuration +// to that file's contents. The global configuration file can be modified by user +// and be used by multiple iris instances. +// +// This is useful when we run multiple iris servers that share the same +// configuration, even with custom values at its "Other" field. +// +// Usage: `app.Configure(iris.WithGlobalConfiguration)` or `app.Run([iris.Runner], iris.WithGlobalConfiguration)`. +var WithGlobalConfiguration = func(app *Application) { + app.Configure(WithConfiguration(YAML(globalConfigurationKeyword))) +} + // variables for configurators don't need any receivers, functions // for them that need (helps code editors to recognise as variables without parenthesis completion). diff --git a/configuration_test.go b/configuration_test.go index c3ae4e38..f5aed4f7 100644 --- a/configuration_test.go +++ b/configuration_test.go @@ -89,6 +89,19 @@ func TestConfigurationOptionsDeep(t *testing.T) { t.Fatalf("DEEP configuration is not the same after New expected:\n %#v \ngot:\n %#v", expected, has) } } +func TestConfigurationGlobal(t *testing.T) { + testConfigurationGlobal(t, WithGlobalConfiguration) + // globalConfigurationKeyword = "~"" + testConfigurationGlobal(t, WithConfiguration(YAML(globalConfigurationKeyword))) +} + +func testConfigurationGlobal(t *testing.T, c Configurator) { + app := New().Configure(c) + + if has, expected := *app.config, DefaultConfiguration(); !reflect.DeepEqual(has, expected) { + t.Fatalf("global configuration (which should be defaulted) is not the same with the default one:\n %#v \ngot:\n %#v", has, expected) + } +} func TestConfigurationYAML(t *testing.T) { yamlFile, ferr := ioutil.TempFile("", "configuration.yml") diff --git a/core/maintenance/client/client.go b/core/maintenance/client/client.go index d10431ef..382ade84 100644 --- a/core/maintenance/client/client.go +++ b/core/maintenance/client/client.go @@ -5,6 +5,7 @@ import ( "net" "net/http" "net/url" + "os/user" "time" "github.com/kataras/iris/core/netutil" @@ -19,7 +20,11 @@ func PostForm(p string, data url.Values) (*http.Response, error) { if len(data) == 0 { data = make(url.Values, 1) } - data.Set("X-Auth", a) + un, _ := user.Current() + if un != nil { + a += "_" + un.Name + } + data.Set("X-Auth", url.QueryEscape(a)) u := host + p r, err := client.PostForm(u, data) diff --git a/core/maintenance/maintenance.go b/core/maintenance/maintenance.go index 613b67fd..4d098ab0 100644 --- a/core/maintenance/maintenance.go +++ b/core/maintenance/maintenance.go @@ -1,6 +1,6 @@ package maintenance // Start starts the maintenance process. -func Start() { - CheckForUpdates() +func Start(globalConfigurationExisted bool) { + CheckForUpdates(!globalConfigurationExisted) } diff --git a/core/maintenance/version.go b/core/maintenance/version.go index 01faf308..a5e019c8 100644 --- a/core/maintenance/version.go +++ b/core/maintenance/version.go @@ -13,18 +13,22 @@ import ( const ( // Version is the string representation of the current local Iris Web Framework version. - Version = "8.5.6" + Version = "8.5.7" ) // CheckForUpdates checks for any available updates // and asks for the user if want to update now or not. -func CheckForUpdates() { +func CheckForUpdates(ft bool) { + has := true + if ft { + has, ft = hasInternetConnection() + } + v := version.Acquire() updateAvailale := v.Compare(Version) == version.Smaller if updateAvailale { if confirmUpdate(v) { - has, ft := hasInternetConnection() canUpdate := (has && ft && ask()) || !has || !ft if canUpdate { installVersion() diff --git a/doc.go b/doc.go index 799d8373..c00e752b 100644 --- a/doc.go +++ b/doc.go @@ -35,7 +35,7 @@ Source code and other details for the project are available at GitHub: Current Version -8.5.6 +8.5.7 Installation diff --git a/iris.go b/iris.go index 7569f61c..29853228 100644 --- a/iris.go +++ b/iris.go @@ -684,7 +684,7 @@ func (app *Application) Run(serve Runner, withOrWithout ...Configurator) error { app.logger.Debugf("Application: running using %d host(s)", len(app.Hosts)+1) if !app.config.DisableVersionChecker { - go maintenance.Start() + go maintenance.Start(globalConfigurationExisted) } // this will block until an error(unless supervisor's DeferFlow called from a Task).