diff --git a/.golangci.yml b/.golangci.yml index 36e2ab6145..345704360d 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,3 +1,6 @@ +exclude-dirs: + - vendor + linters-settings: govet: disable: diff --git a/cmd/osbuild-composer/main.go b/cmd/osbuild-composer/main.go index eeb724c9eb..f5c3c9dcc6 100644 --- a/cmd/osbuild-composer/main.go +++ b/cmd/osbuild-composer/main.go @@ -50,7 +50,7 @@ func main() { logLevel, err := logrus.ParseLevel(config.LogLevel) logrus.SetReportCaller(true) - // Add context hook to log operation_id and external_id + logrus.AddHook(&common.BuildHook{}) logrus.AddHook(&common.ContextHook{}) if err == nil { diff --git a/cmd/osbuild-worker/config.go b/cmd/osbuild-worker/config.go index ac3dae5926..bb7cf124f8 100644 --- a/cmd/osbuild-worker/config.go +++ b/cmd/osbuild-worker/config.go @@ -13,6 +13,12 @@ type composerConfig struct { Proxy string `toml:"proxy"` } +type loggingConfig struct { + Format string `toml:"format"` // journal, text or json (defaults to journal or text depending on the OS) + Level string `toml:"level"` + NoDiscard bool `toml:"no_discard"` // do not discard stdout/stderr logs (useful for debugging) +} + type kerberosConfig struct { Principal string `toml:"principal"` KeyTab string `toml:"keytab"` @@ -88,6 +94,7 @@ type repositoryMTLSConfig struct { type workerConfig struct { Composer *composerConfig `toml:"composer"` + Logging *loggingConfig `toml:"logging"` Koji map[string]kojiServerConfig `toml:"koji"` GCP *gcpConfig `toml:"gcp"` Azure *azureConfig `toml:"azure"` @@ -115,6 +122,10 @@ func parseConfig(file string) (*workerConfig, error) { Type: "host", }, DeploymentChannel: "local", + Logging: &loggingConfig{ + Format: "journal", + Level: "info", + }, } _, err := toml.DecodeFile(file, &config) diff --git a/cmd/osbuild-worker/config_test.go b/cmd/osbuild-worker/config_test.go index 540570d2fd..07ffcef504 100644 --- a/cmd/osbuild-worker/config_test.go +++ b/cmd/osbuild-worker/config_test.go @@ -75,6 +75,10 @@ type = "aws.ec2" iam_profile = "osbuild-worker" key_name = "osbuild-worker" cloudwatch_group = "osbuild-worker" + +[logging] +level = "debug" +format = "text" `, want: &workerConfig{ BasePath: "/api/image-builder-worker/v1", @@ -137,6 +141,10 @@ cloudwatch_group = "osbuild-worker" ServerURL: "https://example.com/pulp", }, DeploymentChannel: "local", + Logging: &loggingConfig{ + Level: "debug", + Format: "text", + }, }, }, { @@ -148,6 +156,10 @@ cloudwatch_group = "osbuild-worker" Type: "host", }, DeploymentChannel: "local", + Logging: &loggingConfig{ + Format: "journal", + Level: "info", + }, }, }, { @@ -159,6 +171,10 @@ cloudwatch_group = "osbuild-worker" Type: "host", }, DeploymentChannel: "staging", + Logging: &loggingConfig{ + Format: "journal", + Level: "info", + }, }, }, } diff --git a/cmd/osbuild-worker/main.go b/cmd/osbuild-worker/main.go index bd95e71157..1d74770199 100644 --- a/cmd/osbuild-worker/main.go +++ b/cmd/osbuild-worker/main.go @@ -7,6 +7,8 @@ import ( "errors" "flag" "fmt" + "io" + "log" "net/url" "os" "path" @@ -17,11 +19,13 @@ import ( slogger "github.com/osbuild/osbuild-composer/pkg/splunk_logger" "github.com/BurntSushi/toml" + "github.com/coreos/go-systemd/journal" "github.com/sirupsen/logrus" "github.com/osbuild/images/pkg/arch" "github.com/osbuild/images/pkg/dnfjson" "github.com/osbuild/osbuild-composer/internal/cloud/awscloud" + "github.com/osbuild/osbuild-composer/internal/common" "github.com/osbuild/osbuild-composer/internal/upload/azure" "github.com/osbuild/osbuild-composer/internal/upload/koji" "github.com/osbuild/osbuild-composer/internal/upload/oci" @@ -168,6 +172,11 @@ func RequestAndRunJob(client *worker.Client, acceptedJobTypes []string, jobImpls } var run = func() { + // Redirect Go standard logger into logrus before it's used by other packages + log.SetOutput(common.Logger()) + // Ensure the Go standard logger does not have any prefix or timestamp + log.SetFlags(0) + var unix bool flag.BoolVar(&unix, "unix", false, "Interpret 'address' as a path to a unix domain socket instead of a network address") @@ -189,6 +198,39 @@ var run = func() { logrus.Fatalf("Could not load config file '%s': %v", configFile, err) } + logrus.SetReportCaller(true) + logrus.AddHook(&common.BuildHook{}) + logrus.SetOutput(os.Stdout) + logLevel, err := logrus.ParseLevel(config.Logging.Level) + if err == nil { + logrus.SetLevel(logLevel) + } else { + logrus.Info("Failed to load loglevel from config:", err) + } + + // logger configuration + switch config.Logging.Format { + case "journal", "": + // If we are running under systemd, use the journal. Otherwise, + // fallback to text formatter. + if journal.Enabled() { + logrus.Info("Switching to journal logging mode, use logging.no_discard to keep writing to standard output/error") + logrus.SetFormatter(&logrus.JSONFormatter{}) + logrus.AddHook(&common.JournalHook{}) + if !config.Logging.NoDiscard { + logrus.SetOutput(io.Discard) + } + } else { + logrus.SetFormatter(&logrus.TextFormatter{}) + } + case "text": + logrus.SetFormatter(&logrus.TextFormatter{}) + case "json": + logrus.SetFormatter(&logrus.JSONFormatter{}) + default: + logrus.Infof("Failed to set logging format from config, '%s' is not a valid option", config.Logging.Format) + } + logrus.Info("Composer configuration:") encoder := toml.NewEncoder(logrus.StandardLogger().WriterLevel(logrus.InfoLevel)) err = encoder.Encode(&config) diff --git a/internal/cloudapi/v2/handler.go b/internal/cloudapi/v2/handler.go index 34ecd88212..662a4b42dd 100644 --- a/internal/cloudapi/v2/handler.go +++ b/internal/cloudapi/v2/handler.go @@ -45,6 +45,19 @@ func (b binder) Bind(i interface{}, ctx echo.Context) error { return nil } +func (h *apiHandlers) GetVersion(ctx echo.Context) error { + spec, err := GetSwagger() + if err != nil { + return HTTPError(ErrorFailedToLoadOpenAPISpec) + } + version := Version{ + Version: spec.Info.Version, + BuildCommit: common.ToPtr(common.BuildCommit), + BuildTime: common.ToPtr(common.BuildTime), + } + return ctx.JSON(http.StatusOK, version) +} + func (h *apiHandlers) GetOpenapi(ctx echo.Context) error { spec, err := GetSwagger() if err != nil { diff --git a/internal/cloudapi/v2/openapi.v2.gen.go b/internal/cloudapi/v2/openapi.v2.gen.go index 580cb00cbb..efff53d7d8 100644 --- a/internal/cloudapi/v2/openapi.v2.gen.go +++ b/internal/cloudapi/v2/openapi.v2.gen.go @@ -1210,6 +1210,13 @@ type User struct { Password *string `json:"password,omitempty"` } +// Version defines model for Version. +type Version struct { + BuildCommit *string `json:"build_commit,omitempty"` + BuildTime *string `json:"build_time,omitempty"` + Version string `json:"version"` +} + // Page defines model for page. type Page string @@ -1272,6 +1279,9 @@ type ServerInterface interface { // Get the openapi spec in json format // (GET /openapi) GetOpenapi(ctx echo.Context) error + // get the service version + // (GET /version) + GetVersion(ctx echo.Context) error } // ServerInterfaceWrapper converts echo contexts to parameters. @@ -1464,6 +1474,15 @@ func (w *ServerInterfaceWrapper) GetOpenapi(ctx echo.Context) error { return err } +// GetVersion converts echo context to params. +func (w *ServerInterfaceWrapper) GetVersion(ctx echo.Context) error { + var err error + + // Invoke the callback with all the unmarshalled arguments + err = w.Handler.GetVersion(ctx) + return err +} + // This is a simple interface which specifies echo.Route addition functions which // are present on both echo.Echo and echo.Group, since we want to allow using // either of them for path registration @@ -1503,197 +1522,199 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL router.GET(baseURL+"/errors", wrapper.GetErrorList) router.GET(baseURL+"/errors/:id", wrapper.GetError) router.GET(baseURL+"/openapi", wrapper.GetOpenapi) + router.GET(baseURL+"/version", wrapper.GetVersion) } // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "ygAA", + "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", + "b9//fwAAAP//TLFh3DPMAAA=", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/internal/cloudapi/v2/openapi.v2.yml b/internal/cloudapi/v2/openapi.v2.yml index 65496a7c97..7fd53cf4b1 100644 --- a/internal/cloudapi/v2/openapi.v2.yml +++ b/internal/cloudapi/v2/openapi.v2.yml @@ -17,6 +17,21 @@ servers: description: current domain paths: + /version: + get: + summary: get the service version + description: "get the service version" + operationId: getVersion + tags: + - meta + responses: + '200': + description: a service version + content: + application/json: + schema: + $ref: '#/components/schemas/Version' + /openapi: get: operationId: getOpenapi @@ -487,6 +502,17 @@ paths: components: schemas: + Version: + required: + - version + properties: + version: + type: string + build_time: + type: string + build_commit: + type: string + ObjectReference: type: object required: diff --git a/internal/cloudapi/v2/server.go b/internal/cloudapi/v2/server.go index 53245fe6d1..beef46cfee 100644 --- a/internal/cloudapi/v2/server.go +++ b/internal/cloudapi/v2/server.go @@ -130,17 +130,37 @@ func (s *Server) Handler(path string) http.Handler { server: s, } + // middlewares with auth mws := []echo.MiddlewareFunc{ prometheus.StatusMiddleware(prometheus.ComposerSubsystem), } + // middlewares without auth + mwsna := []echo.MiddlewareFunc{ + prometheus.StatusMiddleware(prometheus.ComposerSubsystem), + } if s.config.JWTEnabled { mws = append(mws, auth.TenantChannelMiddleware(s.config.TenantProviderFields, HTTPError(ErrorTenantNotFound))) } - mws = append(mws, - prometheus.HTTPDurationMiddleware(prometheus.ComposerSubsystem), + mws = append(mws, prometheus.HTTPDurationMiddleware(prometheus.ComposerSubsystem), prometheus.MetricsMiddleware, s.ValidateRequest) + mwsna = append(mwsna, prometheus.HTTPDurationMiddleware(prometheus.ComposerSubsystem), + prometheus.MetricsMiddleware, s.ValidateRequest) + RegisterHandlers(e.Group(path, mws...), &handler) + // no auth endpoints + e.GET("/version", func(c echo.Context) error { + return handler.GetVersion(c) + }, mwsna...) + + e.GET("/status", func(c echo.Context) error { + return c.NoContent(http.StatusOK) + }) + + e.GET("/ready", func(c echo.Context) error { + return c.NoContent(http.StatusOK) + }) + return e } diff --git a/internal/cloudapi/v2/v2_test.go b/internal/cloudapi/v2/v2_test.go index eb64fa2d34..ae675ee424 100644 --- a/internal/cloudapi/v2/v2_test.go +++ b/internal/cloudapi/v2/v2_test.go @@ -21,6 +21,7 @@ import ( "github.com/osbuild/images/pkg/rpmmd" "github.com/osbuild/images/pkg/sbom" v2 "github.com/osbuild/osbuild-composer/internal/cloudapi/v2" + "github.com/osbuild/osbuild-composer/internal/common" "github.com/osbuild/osbuild-composer/internal/jobqueue/fsjobqueue" "github.com/osbuild/osbuild-composer/internal/target" "github.com/osbuild/osbuild-composer/internal/test" @@ -195,6 +196,21 @@ func newV2Server(t *testing.T, dir string, depsolveChannels []string, enableJWT return v2Server, workerServer, q, cancelWithWait } +func TestVersion(t *testing.T) { + srv, _, _, cancel := newV2Server(t, t.TempDir(), []string{""}, false, false) + defer cancel() + + common.BuildCommit = "abcdef" + common.BuildTime = "2013-05-13T00:00:00Z" + + test.TestRoute(t, srv.Handler("/api/image-builder-composer/v2"), false, "GET", "/api/image-builder-composer/v2/version", ``, http.StatusOK, ` + { + "version": "2", + "build_commit": "abcdef", + "build_time": "2013-05-13T00:00:00Z" + }`, "operation_id", "details") +} + func TestUnknownRoute(t *testing.T) { srv, _, _, cancel := newV2Server(t, t.TempDir(), []string{""}, false, false) defer cancel() diff --git a/internal/common/build_hook.go b/internal/common/build_hook.go new file mode 100644 index 0000000000..8318d5b365 --- /dev/null +++ b/internal/common/build_hook.go @@ -0,0 +1,26 @@ +package common + +import ( + "github.com/sirupsen/logrus" +) + +type BuildHook struct { +} + +func (h *BuildHook) Levels() []logrus.Level { + return []logrus.Level{ + logrus.DebugLevel, + logrus.InfoLevel, + logrus.WarnLevel, + logrus.ErrorLevel, + logrus.FatalLevel, + logrus.PanicLevel, + } +} + +func (h *BuildHook) Fire(e *logrus.Entry) error { + e.Data["build_commit"] = BuildCommit + e.Data["build_time"] = BuildTime + + return nil +} diff --git a/internal/common/runtime.go b/internal/common/runtime.go new file mode 100644 index 0000000000..e1490989fe --- /dev/null +++ b/internal/common/runtime.go @@ -0,0 +1,35 @@ +package common + +import "runtime/debug" + +var ( + // Git SHA commit (only first few characters) + BuildCommit string + + // Build date and time + BuildTime string + + // BuildGoVersion carries Go version the binary was built with + BuildGoVersion string +) + +func init() { + BuildTime = "N/A" + BuildCommit = "HEAD" + + if bi, ok := debug.ReadBuildInfo(); ok { + BuildGoVersion = bi.GoVersion + + for _, bs := range bi.Settings { + switch bs.Key { + case "vcs.revision": + if len(bs.Value) > 6 { + BuildCommit = bs.Value[0:6] + } + case "vcs.time": + BuildTime = bs.Value + } + } + } + +}