loginsrv

Unnamed repository; edit this file 'description' to name the repository.
git clone git@jamesshield.xyz:repos/loginsrv.git
Log | Files | Refs | README | LICENSE

commit cf1fa0d70bcfaa7c0c22c0c1234440baf8e4ce8d
parent a0772eb6163c900b8d74b36b1416b1643ddda35b
Author: domano <angor.mail+github@googlemail.com>
Date:   Tue, 30 May 2017 15:13:57 +0200

Merge pull request #24 from domano/graceful-shutdown

Implemented graceful shutdown via go 1.8 Server.Shutdown(ctx)
Diffstat:
MREADME.md | 1+
Mcaddy/setup_test.go | 5+++++
Mlogging/logger.go | 15+++++++++++++++
Mlogging/logger_test.go | 22++++++++++++++++++++++
Mlogin/config.go | 3+++
Mlogin/config_test.go | 4++++
Mmain.go | 32++++++++++++++++++++++++--------
7 files changed, 74 insertions(+), 8 deletions(-)

diff --git a/README.md b/README.md @@ -61,6 +61,7 @@ _Note for Caddy users_: Not all parameters are available in Caddy. See the table | -success-url | string | "/" | X | The url to redirect after login | | -template | string | | X | An alternative template for the login form | | -text-logging | boolean | true | - | Log in text format instead of json | +| -grace-period | go duration | 5s | - | Duration to wait after SIGINT/SIGTERM for existing requests. No new requests are accepted. | ### Environment Variables All of the above Config Options can also be applied as environment variable, where the name is written in the way: `LOGINSRV_OPTION_NAME`. diff --git a/caddy/setup_test.go b/caddy/setup_test.go @@ -39,6 +39,7 @@ func TestSetup(t *testing.T) { }, }, Oauth: login.Options{}, + GracePeriod: 5*time.Second, }}, { input: `login { @@ -73,6 +74,7 @@ func TestSetup(t *testing.T) { }, }, Oauth: login.Options{}, + GracePeriod: 5*time.Second, }}, { // backwards compatibility // * login path as argument @@ -96,6 +98,7 @@ func TestSetup(t *testing.T) { }, }, Oauth: login.Options{}, + GracePeriod: 5*time.Second, }}, { // backwards compatibility // * login path as argument @@ -119,6 +122,7 @@ func TestSetup(t *testing.T) { }, }, Oauth: login.Options{}, + GracePeriod: 5*time.Second, }}, // error cases @@ -140,6 +144,7 @@ func TestSetup(t *testing.T) { }, }, Oauth: login.Options{}, + GracePeriod: 5*time.Second, }}, {input: "login {\n}", shouldErr: true}, {input: "login xx yy {\n}", shouldErr: true}, diff --git a/logging/logger.go b/logging/logger.go @@ -221,6 +221,21 @@ func LifecycleStop(appName string, signal os.Signal, err error) { } } +// LifecycleStop logs the stop of an application +func ServerClosed(appName string) { + fields := logrus.Fields{ + "type": "application", + "event": "stop", + } + + if os.Getenv("BUILD_NUMBER") != "" { + fields["build_number"] = os.Getenv("BUILD_NUMBER") + } + + Logger.WithFields(fields).Infof("http server was closed: %v", appName) +} + + func getRemoteIp(r *http.Request) string { if r.Header.Get("X-Cluster-Client-Ip") != "" { return r.Header.Get("X-Cluster-Client-Ip") diff --git a/logging/logger_test.go b/logging/logger_test.go @@ -267,6 +267,28 @@ func Test_Logger_LifecycleStop(t *testing.T) { a.Equal("b666", data["build_number"]) } +func Test_Logger_ServerClosed(t *testing.T) { + a := assert.New(t) + + // given a logger + b := bytes.NewBuffer(nil) + Logger.Out = b + + // and an Environment Variable with the Build Number is set + os.Setenv("BUILD_NUMBER", "b666") + + // when a LifecycleStart is logged + ServerClosed("my-app") + + // then: it is logged + data := mapFromBuffer(b) + a.Equal("info", data["level"]) + a.Equal("http server was closed: my-app", data["message"]) + a.Equal("application", data["type"]) + a.Equal("stop", data["event"]) + a.Equal("b666", data["build_number"]) +} + func Test_Logger_Cacheinfo(t *testing.T) { a := assert.New(t) diff --git a/login/config.go b/login/config.go @@ -34,6 +34,7 @@ func DefaultConfig() *Config { CookieHTTPOnly: true, Backends: Options{}, Oauth: Options{}, + GracePeriod: 5 * time.Second, } } @@ -57,6 +58,7 @@ type Config struct { CookieHTTPOnly bool Backends Options Oauth Options + GracePeriod time.Duration } // Options is the configuration structure for oauth and backend provider @@ -101,6 +103,7 @@ func (c *Config) ConfigureFlagSet(f *flag.FlagSet) { f.StringVar(&c.LogoutURL, "logout-url", c.LogoutURL, "The url or path to redirect after logout") f.StringVar(&c.Template, "template", c.Template, "An alternative template for the login form") f.StringVar(&c.LoginPath, "login-path", c.LoginPath, "The path of the login resource") + f.DurationVar(&c.GracePeriod, "grace-period", c.GracePeriod, "Graceful shutdown grace period") // the -backends is deprecated, but we support it for backwards compatibility deprecatedBackends := setFunc(func(optsKvList string) error { diff --git a/login/config_test.go b/login/config_test.go @@ -38,6 +38,7 @@ func TestConfig_ReadConfig(t *testing.T) { "--backend=provider=simple", "--backend=provider=foo", "--github=client_id=foo,client_secret=bar", + "--grace-period=4s", } expected := &Config{ @@ -65,6 +66,7 @@ func TestConfig_ReadConfig(t *testing.T) { "client_secret": "bar", }, }, + GracePeriod: 4*time.Second, } cfg, err := readConfig(flag.NewFlagSet("", flag.ContinueOnError), input) @@ -89,6 +91,7 @@ func TestConfig_ReadConfigFromEnv(t *testing.T) { NoError(t, os.Setenv("LOGINSRV_COOKIE_HTTP_ONLY", "false")) NoError(t, os.Setenv("LOGINSRV_SIMPLE", "foo=bar")) NoError(t, os.Setenv("LOGINSRV_GITHUB", "client_id=foo,client_secret=bar")) + NoError(t, os.Setenv("LOGINSRV_GRACE_PERIOD", "4s")) expected := &Config{ Host: "host", @@ -116,6 +119,7 @@ func TestConfig_ReadConfigFromEnv(t *testing.T) { "client_secret": "bar", }, }, + GracePeriod: 4*time.Second, } cfg, err := readConfig(flag.NewFlagSet("", flag.ContinueOnError), []string{}) diff --git a/main.go b/main.go @@ -6,6 +6,8 @@ import ( "github.com/tarent/loginsrv/login" + "context" + "fmt" "github.com/tarent/loginsrv/logging" "net/http" "os" @@ -21,8 +23,6 @@ func main() { exit(nil, err) } - logShutdownEvent() - configToLog := *config configToLog.JwtSecret = "..." logging.LifecycleStart(applicationName, configToLog) @@ -34,15 +34,31 @@ func main() { handlerChain := logging.NewLogMiddleware(h) - exit(nil, http.ListenAndServe(config.Host+":"+config.Port, handlerChain)) -} + stop := make(chan os.Signal) + signal.Notify(stop, syscall.SIGINT, syscall.SIGTERM) + + port := config.Port + if port != "" { + port = fmt.Sprintf(":%s", port) + } + + httpSrv := &http.Server{Addr: port, Handler: handlerChain} -func logShutdownEvent() { go func() { - c := make(chan os.Signal) - signal.Notify(c, syscall.SIGINT, syscall.SIGTERM) - exit(<-c, nil) + if err := httpSrv.ListenAndServe(); err != nil { + if err == http.ErrServerClosed { + logging.ServerClosed(applicationName) + } else { + exit(nil, err) + } + } }() + logging.LifecycleStop(applicationName, <-stop, nil) + + ctx, ctxCancel := context.WithTimeout(context.Background(), config.GracePeriod) + + httpSrv.Shutdown(ctx) + ctxCancel() } var exit = func(signal os.Signal, err error) {