From f8fbb6ecc11dd5ba15baef4209c1a47377040375 Mon Sep 17 00:00:00 2001 From: Denis Yudin Date: Thu, 27 Jun 2024 17:44:40 +0300 Subject: [PATCH] feat: Add support for systemd notify (#1954) - Integrated systemd notification support using github.com/coreos/go-systemd/v22/daemon. - Updated manager/runner.go to signal readiness to systemd. - Modified .golangci.yml to include github.com/coreos/go-systemd in linters-settings. - Updated dependencies in go.mod and go.sum for github.com/coreos/go-systemd/v22 v22.5.0. --- .golangci.yml | 1 + cli.go | 31 +++++++++++++++++++++++++++++++ go.mod | 1 + go.sum | 3 +++ manager/runner.go | 21 +++++++++++++++++++++ 5 files changed, 57 insertions(+) diff --git a/.golangci.yml b/.golangci.yml index 279714be4..a46841f56 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -86,6 +86,7 @@ linters-settings: - github.com/pkg/errors - github.com/stretchr/testify/assert - github.com/stretchr/testify/require + - github.com/coreos/go-systemd run: timeout: 10m diff --git a/cli.go b/cli.go index 7a6bd3302..8aeb3692e 100644 --- a/cli.go +++ b/cli.go @@ -4,6 +4,7 @@ package main import ( + "context" "flag" "fmt" "io" @@ -13,6 +14,7 @@ import ( "sync" "time" + "github.com/coreos/go-systemd/v22/daemon" "github.com/hashicorp/consul-template/config" "github.com/hashicorp/consul-template/logging" "github.com/hashicorp/consul-template/manager" @@ -105,11 +107,38 @@ func (cli *CLI) Run(args []string) int { return ExitCodeOK } + // Create a context with cancellation + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + // Create a channel for signaling readiness to the systemd init system. + readyCh := make(chan struct{}) + + // Start a goroutine that listens for signals on the readyCh channel. + // When a signal (an empty struct) is received, it notifies systemd that + // the application is ready by calling daemon.SdNotify with SdNotifyReady. + // If an error occurs during the notification, it logs a warning message. + go func() { + for { + select { + case <-readyCh: + _, err := daemon.SdNotify(false, daemon.SdNotifyReady) + if err != nil { + log.Printf("[WARN] failed to signal readiness to systemd: %v", err) + } + case <-ctx.Done(): + return + } + } + }() + // Initial runner runner, err := manager.NewRunner(config, dry) if err != nil { return logError(err, ExitCodeRunnerError) } + + runner.SetReadyChannel(readyCh) go runner.Start() // Listen for monitored signals @@ -137,6 +166,7 @@ func (cli *CLI) Run(args []string) int { case <-service_os.Shutdown_Channel(): fmt.Fprintf(cli.errStream, "Cleaning up...\n") runner.StopImmediately() + cancel() // Cancel the context to stop the readyCh goroutine return ExitCodeInterrupt case s := <-cli.signalCh: log.Printf("[DEBUG] (cli) receiving signal %q", s) @@ -163,6 +193,7 @@ func (cli *CLI) Run(args []string) int { if err != nil { return logError(err, ExitCodeRunnerError) } + runner.SetReadyChannel(readyCh) go runner.Start() case *config.KillSignal: fmt.Fprintf(cli.errStream, "Cleaning up...\n") diff --git a/go.mod b/go.mod index 2fbb488f0..066434506 100644 --- a/go.mod +++ b/go.mod @@ -43,6 +43,7 @@ require ( github.com/Masterminds/semver/v3 v3.2.0 // indirect github.com/armon/go-metrics v0.4.1 // indirect github.com/cenkalti/backoff/v3 v3.2.2 // indirect + github.com/coreos/go-systemd/v22 v22.5.0 github.com/fatih/color v1.17.0 // indirect github.com/go-jose/go-jose/v3 v3.0.3 // indirect github.com/google/uuid v1.3.0 // indirect diff --git a/go.sum b/go.sum index 6aed2eb3c..0cc2cd4a8 100644 --- a/go.sum +++ b/go.sum @@ -29,6 +29,8 @@ github.com/cenkalti/backoff/v3 v3.2.2/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4r github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= +github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -52,6 +54,7 @@ github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw= github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= diff --git a/manager/runner.go b/manager/runner.go index 41b1094ea..8c16c727b 100644 --- a/manager/runner.go +++ b/manager/runner.go @@ -132,6 +132,12 @@ type Runner struct { // Runner config. This prevents risk of data races when reading config for // other elements started by the Runner, like template functions. finalConfigCopy config.Config + + // readyCh is a channel used to signal readiness to the systemd init system. + // When a struct{} is sent on this channel, it triggers a notification to systemd + // that the application is ready. This helps in managing application state and + // integration with systemd's readiness protocol. + readyCh chan struct{} } // RenderEvent captures the time and events that occurred for a template @@ -309,6 +315,14 @@ func (r *Runner) Start() { if r.allTemplatesRendered() { log.Printf("[DEBUG] (runner) all templates rendered") + + // If the readyCh channel is not nil, send an empty struct to signal readiness. + // This will notify the systemd init system that the application is ready to + // handle requests. This mechanism integrates with systemd's readiness protocol. + if r.readyCh != nil { + r.readyCh <- struct{}{} + } + // Enable quiescence for all templates if we have specified wait // intervals. NEXT_Q: @@ -722,6 +736,13 @@ func (r *Runner) Run() error { return nil } +// SetReadyChannel sets the readyCh channel which is used to signal readiness to the systemd init system. +// The channel should be a struct{} channel, and when an empty struct is sent on this channel, +// it will trigger a notification to systemd that the application is ready. +func (r *Runner) SetReadyChannel(ch chan struct{}) { + r.readyCh = ch +} + type templateRunCtx struct { // commands is the set of commands that will be executed after all templates // have run. When adding to the commands, care should be taken not to