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

clair: fix initialization error logging #1210

Merged
merged 1 commit into from Mar 23, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
77 changes: 56 additions & 21 deletions cmd/clair/main.go
Expand Up @@ -23,7 +23,7 @@ import (
"github.com/quay/clair/v4/introspection"
)

// Version is a version string, optionally injected at build time.
// Version is a version string, injected at build time for release builds.
var Version string

const (
Expand All @@ -46,6 +46,12 @@ func main() {
if confFile.String() == "" {
golog.Fatalf("must provide a -conf flag or set %q in the environment", envConfig)
}
fail := false
defer func() {
if fail {
os.Exit(1)
}
}()

// validate config
err := yaml.NewDecoder(confFile.file).Decode(&conf)
Expand All @@ -69,33 +75,35 @@ func main() {
Str("version", Version).
Msg("starting")

// Some machinery for starting and stopping server goroutines
// Some machinery for starting and stopping server goroutines:
down := &Shutdown{}
srvs, srvctx := errgroup.WithContext(ctx)

// Introspection server goroutine.
srvs.Go(func() (_ error) {
zlog.Info(ctx).Msg("launching introspection server")
i, err := introspection.New(ctx, conf, nil)
zlog.Info(srvctx).Msg("launching introspection server")
i, err := introspection.New(srvctx, conf, nil)
if err != nil {
zlog.Warn(ctx).
zlog.Warn(srvctx).
Err(err).Msg("introspection server configuration failed. continuing anyway")
return
}
down.Add(i.Server)
if err := i.ListenAndServe(); err != http.ErrServerClosed {
zlog.Warn(ctx).
zlog.Warn(srvctx).
Err(err).Msg("introspection server failed to launch. continuing anyway")
}
return
})

// HTTP API server goroutine.
srvs.Go(func() error {
zlog.Info(ctx).Msg("launching http transport")
srvs, err := initialize.Services(ctx, &conf)
zlog.Info(srvctx).Msg("launching http transport")
srvs, err := initialize.Services(srvctx, &conf)
if err != nil {
return fmt.Errorf("service initialization failed: %w", err)
}
h, err := httptransport.New(ctx, conf, srvs.Indexer, srvs.Matcher, srvs.Notifier)
h, err := httptransport.New(srvctx, conf, srvs.Indexer, srvs.Matcher, srvs.Notifier)
if err != nil {
return fmt.Errorf("http transport configuration failed: %w", err)
}
Expand All @@ -106,23 +114,50 @@ func main() {
return nil
})

c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
zlog.Info(ctx).Msg("registered signal handler")
zlog.Info(ctx).Str("version", Version).Msg("ready")
select {
case sig := <-c:
zlog.Info(ctx).
Stringer("signal", sig).
Msg("gracefully shutting down")
// Signal handler goroutine.
srvs.Go(func() error {
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
defer func() {
signal.Stop(c)
close(c)
zlog.Info(ctx).Msg("unregistered signal handler")
}()
zlog.Info(ctx).Msg("registered signal handler")
select {
case sig := <-c:
zlog.Info(ctx).
Stringer("signal", sig).
Msg("gracefully shutting down")
// Note that we're using the root context here, so that we get a
// full timeout if one errgroup goroutine returns uncleanly.
tctx, done := context.WithTimeout(ctx, 10*time.Second)
err := down.Shutdown(tctx)
done()
if err != nil {
zlog.Error(ctx).Err(err).Msg("error shutting down server")
}
case <-srvctx.Done():
}
return nil
})
// Spawn a goroutine outside to wait on the errgroup.
//
// This is needed to call shutdown and cause the servers to return when only
// one has returned an error.
go func() {
<-srvctx.Done()
tctx, done := context.WithTimeout(ctx, 10*time.Second)
err := down.Shutdown(tctx)
done()
if err != nil {
zlog.Error(ctx).Err(err).Msg("error shutting down server")
}
case <-srvctx.Done():
zlog.Error(ctx).Err(srvctx.Err()).Msg("initialization failed")
os.Exit(1)
}()

zlog.Info(ctx).Str("version", Version).Msg("ready")
if err := srvs.Wait(); err != nil {
zlog.Error(ctx).Err(err).Msg("fatal error")
fail = true
}
}
35 changes: 35 additions & 0 deletions cmd/clair/version.go
@@ -0,0 +1,35 @@
package main

import (
"bytes"
"context"
"os/exec"
"time"
)

// This is a helper for development. In production, we shouldn't assume that the
// process is running in a git repository or that git is installed. Our build
// system does this for release builds.

func init() {
ctx, done := context.WithTimeout(context.Background(), 5*time.Second)
defer done()
if Version != "" {
// Had our version injected at build: do nothing.
return
}
if _, err := exec.LookPath("git"); err != nil {
// Couldn't find a git binary: do nothing.
return
}
if err := exec.CommandContext(ctx, "git", "rev-parse", "--show-toplevel").Run(); err != nil {
// Couldn't find a git repository: do nothing.
return
}
out, err := exec.CommandContext(ctx, "git", "describe").Output()
if err != nil {
// Couldn't describe the current commit: do nothing.
return
}
Version = string(bytes.TrimSpace(out))
}