Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

converted updater initial delay to select statement, updated updater logging to specify binary being updated #812

Merged
merged 12 commits into from
Apr 5, 2022
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions cmd/launcher/internal/updater/mocks/updater.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//go:build !windows
// +build !windows

package main
package updater

import (
"context"
Expand All @@ -16,10 +16,10 @@ import (
"github.com/pkg/errors"
)

// updateFinalizer finalizes a launcher update. It assume the new
// UpdateFinalizer finalizes a launcher update. It assume the new
// binary has been copied into place, and calls exec, so we start a
// new running launcher in our place.
func updateFinalizer(logger log.Logger, shutdownOsquery func() error) func() error {
func UpdateFinalizer(logger log.Logger, shutdownOsquery func() error) func() error {
return func() error {
if err := shutdownOsquery(); err != nil {
level.Info(logger).Log("method", "updateFinalizer", "err", err)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//go:build windows
// +build windows

package main
package updater

import (
"context"
Expand All @@ -13,12 +13,12 @@ import (
"github.com/kolide/launcher/pkg/contexts/ctxlog"
)

// updateFinalizer finalizes a launcher update. As windows does not
// UpdateFinalizer finalizes a launcher update. As windows does not
// support an exec, we exit so the service manager will restart
// us. Exit(0) might be more correct, but that's harder to plumb
// through this stack. So, return an error here to trigger an exit
// higher in the stack.
func updateFinalizer(logger log.Logger, shutdownOsquery func() error) func() error {
func UpdateFinalizer(logger log.Logger, shutdownOsquery func() error) func() error {
return func() error {
if err := shutdownOsquery(); err != nil {
level.Info(logger).Log("msg", "calling shutdownOsquery", "method", "updateFinalizer", "err", err)
Expand Down
158 changes: 158 additions & 0 deletions cmd/launcher/internal/updater/updater.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
package updater

import (
"context"
"net/http"
"os"
"path/filepath"
"time"

"github.com/go-kit/kit/log"
"github.com/go-kit/kit/log/level"
"github.com/kolide/kit/actor"
"github.com/kolide/launcher/pkg/autoupdate"
"github.com/kolide/updater/tuf"
)

// UpdaterConfig is a struct of update related options. It's used to
// simplify the call to `createUpdater` from launcher's main blocks.
type UpdaterConfig struct {
Logger log.Logger
RootDirectory string // launcher's root dir. use for holding tuf staging and updates
AutoupdateInterval time.Duration
UpdateChannel autoupdate.UpdateChannel
InitialDelay time.Duration // start delay, to avoid whomping critical early data
NotaryURL string
MirrorURL string
NotaryPrefix string
HTTPClient *http.Client
SigChannel chan os.Signal
}

// NewUpdater returns an Actor suitable for an oklog/run group. It
// is a light wrapper around autoupdate.NewUpdater to simplify having
// multiple ones in launcher.
func NewUpdater(
ctx context.Context,
binaryPath string,
finalizer autoupdate.UpdateFinalizer,
config *UpdaterConfig,
) (*actor.Actor, error) {

if config.Logger == nil {
config.Logger = log.NewNopLogger()
}

config.Logger = log.With(config.Logger, "updater", filepath.Base(binaryPath))

// create the updater
updater, err := autoupdate.NewUpdater(
binaryPath,
config.RootDirectory,
autoupdate.WithLogger(config.Logger),
autoupdate.WithHTTPClient(config.HTTPClient),
autoupdate.WithNotaryURL(config.NotaryURL),
autoupdate.WithMirrorURL(config.MirrorURL),
autoupdate.WithNotaryPrefix(config.NotaryPrefix),
autoupdate.WithFinalizer(finalizer),
autoupdate.WithUpdateChannel(config.UpdateChannel),
autoupdate.WithSigChannel(config.SigChannel),
)
if err != nil {
return nil, err
}

updateCmd := &updaterCmd{
updater: updater,
ctx: ctx,
stopChan: make(chan bool),
config: config,
runUpdaterRetryInterval: 30 * time.Minute,
}

return &actor.Actor{
Execute: updateCmd.execute,
Interrupt: updateCmd.interrupt,
}, nil
Comment on lines +73 to +76
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hrm. Would this be cleaner if actor.Actor defined an interface, not a struct?

}

// updater allows us to mock *autoupdate.Updater during testing
type updater interface {
Run(opts ...tuf.Option) (stop func(), err error)
}

type updaterCmd struct {
updater updater
ctx context.Context
stopChan chan bool
stopExecution func()
config *UpdaterConfig
runUpdaterRetryInterval time.Duration
}

func (u *updaterCmd) execute() error {
// When launcher first starts, we'd like the
// server to start receiving data
// immediately. But, if updater is trying to
// run, this creates an awkward pause for restart.
// So, delay starting updates by an hour or two.
level.Debug(u.config.Logger).Log("msg", "updater entering initial delay", "delay", u.config.InitialDelay)

select {
case <-u.stopChan:
level.Debug(u.config.Logger).Log("msg", "updater stopped requested during initial delay, Breaking loop")
return nil
case <-time.After(u.config.InitialDelay):
level.Debug(u.config.Logger).Log("msg", "updater initial delay complete")
break
}

// Failing to start the updater is not a fatal launcher
// error. If there's a problem, sleep and try
// again. Implementing this is a bit gnarly. In the event of a
// success, we get a nil error, and a stop function. But I don't
// see a simple way to ensure the updater is still running in
// the background.
for {
level.Debug(u.config.Logger).Log("msg", "updater starting")

// run the updater and set the stop function so that the interrupt has access to it
stop, err := u.updater.Run(tuf.WithFrequency(u.config.AutoupdateInterval), tuf.WithLogger(u.config.Logger))
u.stopExecution = stop
if err == nil {
break
}

// err != nil, log it and loop again
level.Error(u.config.Logger).Log("msg", "error running updater", "err", err)
select {
case <-u.stopChan:
level.Debug(u.config.Logger).Log("msg", "updater stop requested, Breaking loop")
return nil
case <-time.After(u.runUpdaterRetryInterval):
break
}
}

level.Debug(u.config.Logger).Log("msg", "updater waiting ... just sitting until done signal")
<-u.ctx.Done()

return nil
}

func (u *updaterCmd) interrupt(err error) {

level.Info(u.config.Logger).Log("msg", "updater interrupted", "err", err)

// non-blocking channel send
select {
case u.stopChan <- true:
level.Info(u.config.Logger).Log("msg", "updater interrupt sent signal over stop channel")
default:
level.Info(u.config.Logger).Log("msg", "updater interrupt without sending signal over stop channel (no one to receive)")
}

if u.stopExecution != nil {
u.stopExecution()
}
}
Loading