From c1efe97527f2a676aa863e8caddfee87cb9d7565 Mon Sep 17 00:00:00 2001 From: Lukas Zapletal Date: Wed, 20 Nov 2024 14:29:35 +0100 Subject: [PATCH] cloudapi: git sha version api and journal for workers --- cmd/osbuild-composer/main.go | 2 +- cmd/osbuild-worker/config.go | 11 + cmd/osbuild-worker/config_test.go | 16 ++ cmd/osbuild-worker/main.go | 42 +++ internal/cloudapi/v2/handler.go | 13 + internal/cloudapi/v2/openapi.v2.gen.go | 374 +++++++++++++------------ internal/cloudapi/v2/openapi.v2.yml | 26 ++ internal/cloudapi/v2/server.go | 20 +- internal/cloudapi/v2/v2_test.go | 16 ++ internal/common/build_hook.go | 26 ++ internal/common/runtime.go | 35 +++ 11 files changed, 402 insertions(+), 179 deletions(-) create mode 100644 internal/common/build_hook.go create mode 100644 internal/common/runtime.go 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 dc4eb69583..585174164d 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" @@ -16,11 +18,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" @@ -167,6 +171,11 @@ func RequestAndRunJob(client *worker.Client, acceptedJobTypes []string, jobImpls } func main() { + // 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") @@ -188,6 +197,39 @@ func main() { 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 c76902471c..96b314849c 100644 --- a/internal/cloudapi/v2/openapi.v2.gen.go +++ b/internal/cloudapi/v2/openapi.v2.gen.go @@ -1195,6 +1195,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 @@ -1257,6 +1264,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. @@ -1449,6 +1459,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 @@ -1488,195 +1507,198 @@ 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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", - "sEnehu2DYPLqPYzvR5f7X4LD1n39ClUeu+QC35X6p5d1bu8c+PzorXbx+YtNLshN+/MJe+lcnx1UvAfm", - "Nmxy2BnYj/f1l6eh/zA4mPBKYXcXXXXJYFhk52RSfLkcD2HQL+C7+pW1/WV0MXw5v704dWp3u/dnk9Pg", - "4UG8j7+Ql4vL2sPt0f7rWZU/Ue/iokv6otc5KX2uTXq3D4VGZbTfg2+3D2Wxc/d++WK9o2H76RDD88vd", + "qo1G0xneTHt/uXxsFHb2H33HnbQbT48ng5fJ2aBLCl/62+/X/ftR76RcPHytDFs7V0f7l0Vy/vXL/l3J", + "C0btL6+doF15OGf7Fa9yHLjCP7s9PD07F17t8KBLSuz4/WuDdkoTf/exVT9vHNgXzebV5KXxwunDXX3n", + "8S5ofin0yAvroNvy+e1Vsz+5bu5sP+zWa/jqvku8WvtLj98cjHea5XPm2o2L6sVBQCdPpTYWx/CpenZz", + "fi++dA5hqYr5Y/u4+fJOd64f6/eV06thrdglzuuDUy9fFnpe+fC9vdOpVx4OD3old/RSbbmjN6f1eoac", + "Uun96+Obxx7bT6enzf7ovf/FvWxvB2/OSZe8vBVOixP3qXyOe8ds+7jRmFzt3j2wxlN73L4oHlovnfr4", + "sEnehu2DYPLqPYzvR5f7X4PD1n39ClUeu+QC35X6p5d1bu8c+PzorXbx5atNLshN+8sJe+lcnx1UvAfm", + "Nmxy2BnYj/f1l6eh/zA4mPBKYXcXXXXJYFhk52RSfLkcD2HQL+C7+pW1/XV0MXw5v704dWp3u/dnk9Pg", + "4UG8j7+Sl4vL2sPt0f7rWZU/Ue/iokv6otc5KX2pTXq3D4VGZbTfg2+3D2Wxc/d++WK9o2H76RDD88vd", "88KJddps3ZZujurb9fKB3XAPj3btLhmWnRv82L5pQHhaPD1tvJ+Mboe3p+fnzln58eYRn1zeT8qicjo5", "6nMGvdq43Xy46g+uUWtyvt95Ou2SEfMv3ese6vPObm2n0y/vX7YC5/2JNWv3bwfts+GTczso3R+P2q0b", - "0py8D28m24d35ddrHz/UdqWMGly3vjyxM2qdVc7O27sF/H5607l1xctF47cu+e2639npEnW6HF4eLDt6", - "FpS7ogw9c+6mH9I/qx6ufvRnqTv4ox4Bilf/SfW6yPFCx4YuEaSc6jGtCHKp0HCgTK5YRomqPNQlv4aR", - "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", + "ittiywAA", } // 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 95c61bfc83..2d31744932 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..5c2121b6e4 100644 --- a/internal/cloudapi/v2/server.go +++ b/internal/cloudapi/v2/server.go @@ -130,17 +130,33 @@ 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("/status", func(c echo.Context) error { + return handler.GetVersion(c) + }) + + 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 8df071bfe7..87db6d501c 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 + } + } + } + +}