diff --git a/cmd/oci-image-tool/create.go b/cmd/oci-image-tool/create.go index 20a233f..5c37ba0 100644 --- a/cmd/oci-image-tool/create.go +++ b/cmd/oci-image-tool/create.go @@ -19,6 +19,7 @@ import ( "strings" "github.com/opencontainers/image-tools/image" + "github.com/opencontainers/image-tools/logger" "github.com/urfave/cli" ) @@ -74,7 +75,7 @@ func createHandle(context *cli.Context) error { } if err != nil { - fmt.Printf("creating failed: %v\n", err) + logger.G(globalCtx).WithError(err).Infof("creating failed") } return err diff --git a/cmd/oci-image-tool/main.go b/cmd/oci-image-tool/main.go index e91d59a..fa817b0 100644 --- a/cmd/oci-image-tool/main.go +++ b/cmd/oci-image-tool/main.go @@ -18,11 +18,13 @@ import ( "fmt" "os" - "github.com/Sirupsen/logrus" + "github.com/opencontainers/image-tools/logger" "github.com/opencontainers/image-tools/version" "github.com/urfave/cli" ) +var globalCtx = logger.WithLogger(logger.Background(), logger.LogEntry) + // gitCommit will be the hash that the binary was built from // and will be populated by the Makefile var gitCommit = "" @@ -43,9 +45,7 @@ func main() { }, } app.Before = func(c *cli.Context) error { - if c.GlobalBool("debug") { - logrus.SetLevel(logrus.DebugLevel) - } + logger.EnableDebugMode(c.GlobalBool("debug")) return nil } app.Commands = []cli.Command{ @@ -55,6 +55,6 @@ func main() { } if err := app.Run(os.Args); err != nil { - logrus.Fatal(err) + os.Exit(1) } } diff --git a/cmd/oci-image-tool/unpack.go b/cmd/oci-image-tool/unpack.go index 21bd2a6..9b94b10 100644 --- a/cmd/oci-image-tool/unpack.go +++ b/cmd/oci-image-tool/unpack.go @@ -19,6 +19,7 @@ import ( "strings" "github.com/opencontainers/image-tools/image" + "github.com/opencontainers/image-tools/logger" "github.com/urfave/cli" ) @@ -67,7 +68,7 @@ func unpackHandle(context *cli.Context) error { } if err != nil { - fmt.Printf("unpacking failed: %v\n", err) + logger.G(globalCtx).WithError(err).Errorf("unpacking failed") } return err } diff --git a/cmd/oci-image-tool/validate.go b/cmd/oci-image-tool/validate.go index 4c705df..0df3ff7 100644 --- a/cmd/oci-image-tool/validate.go +++ b/cmd/oci-image-tool/validate.go @@ -16,12 +16,12 @@ package main import ( "fmt" - "log" "os" "strings" "github.com/opencontainers/image-spec/schema" "github.com/opencontainers/image-tools/image" + "github.com/opencontainers/image-tools/logger" "github.com/pkg/errors" "github.com/urfave/cli" ) @@ -36,9 +36,8 @@ var validateTypes = []string{ } type validateCmd struct { - stdout *log.Logger - typ string // the type to validate, can be empty string - refs []string + typ string // the type to validate, can be empty string + refs []string } var v validateCmd @@ -58,10 +57,11 @@ func validateHandler(context *cli.Context) error { var errs []string for _, arg := range context.Args() { + var ctx = logger.WithLogger(globalCtx, logger.G(globalCtx).WithField("target", arg)) err := validatePath(arg) if err == nil { - fmt.Printf("%s: OK\n", arg) + logger.G(ctx).Infof("OK") continue } @@ -78,9 +78,12 @@ func validateHandler(context *cli.Context) error { } if len(errs) > 0 { - return fmt.Errorf("%d errors detected: \n%s", len(errs), strings.Join(errs, "\n")) + err := fmt.Errorf("%d errors detected: \n%s", len(errs), strings.Join(errs, "\n")) + logger.G(globalCtx).WithError(err).Infof("validation failed") + return err } - fmt.Println("Validation succeeded") + + logger.G(globalCtx).Infof("Validation succeeded") return nil } @@ -98,13 +101,13 @@ func validatePath(name string) error { switch typ { case image.TypeImageLayout: - return image.ValidateLayout(name, v.refs, v.stdout) + return image.ValidateLayout(globalCtx, name, v.refs) case image.TypeImage: - return image.ValidateFile(name, v.refs, v.stdout) + return image.ValidateFile(globalCtx, name, v.refs) } if len(v.refs) != 0 { - fmt.Printf("WARNING: type %q does not support refs, which are only appropriate if type is image or imageLayout.\n", typ) + logger.G(globalCtx).Warnf("type %q does not support refs, which are only appropriate if type is image or imageLayout.", typ) } f, err := os.Open(name) diff --git a/image/image.go b/image/image.go index 4ff345c..58d4439 100644 --- a/image/image.go +++ b/image/image.go @@ -18,29 +18,29 @@ import ( "encoding/json" "fmt" "io" - "log" "os" "path/filepath" "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/opencontainers/image-tools/logger" "github.com/pkg/errors" ) // ValidateLayout walks through the given file tree and validates the manifest // pointed to by the given refs or returns an error if the validation failed. -func ValidateLayout(src string, refs []string, out *log.Logger) error { - return validate(newPathWalker(src), refs, out) +func ValidateLayout(ctx logger.Context, src string, refs []string) error { + return validate(ctx, newPathWalker(src), refs) } // ValidateFile opens the tar file given by the filename, then calls ValidateReader -func ValidateFile(tarFile string, refs []string, out *log.Logger) error { +func ValidateFile(ctx logger.Context, tarFile string, refs []string) error { f, err := os.Open(tarFile) if err != nil { return errors.Wrap(err, "unable to open file") } defer f.Close() - return Validate(f, refs, out) + return Validate(ctx, f, refs) } // Validate walks through a tar stream and validates the manifest. @@ -48,8 +48,8 @@ func ValidateFile(tarFile string, refs []string, out *log.Logger) error { // * Checks that all referred blobs are valid // * Checks that mime-types are correct // returns error on validation failure -func Validate(r io.ReadSeeker, refs []string, out *log.Logger) error { - return validate(newTarWalker(r), refs, out) +func Validate(ctx logger.Context, r io.ReadSeeker, refs []string) error { + return validate(ctx, newTarWalker(r), refs) } var validRefMediaTypes = []string{ @@ -57,16 +57,13 @@ var validRefMediaTypes = []string{ v1.MediaTypeImageIndex, } -func validate(w walker, refs []string, out *log.Logger) error { +func validate(ctx logger.Context, w walker, refs []string) error { ds, err := listReferences(w) if err != nil { return err } if len(refs) == 0 && len(ds) == 0 { - // TODO(runcom): ugly, we'll need a better way and library - // to express log levels. - // see https://github.com/opencontainers/image-spec/issues/288 - out.Print("WARNING: no descriptors found") + logger.G(ctx).Warnf("no descriptors found") } if len(refs) == 0 { @@ -96,9 +93,7 @@ func validate(w walker, refs []string, out *log.Logger) error { if err := m.validate(w); err != nil { return err } - if out != nil { - out.Printf("reference %q: OK", ref) - } + logger.G(ctx).WithField("reference", ref).Infof("validate OK") } return nil } diff --git a/image/image_test.go b/image/image_test.go index 35ecb29..7480bfc 100644 --- a/image/image_test.go +++ b/image/image_test.go @@ -28,6 +28,7 @@ import ( "github.com/opencontainers/go-digest" "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/opencontainers/image-tools/logger" ) const ( @@ -150,7 +151,7 @@ func TestValidateLayout(t *testing.T) { t.Fatal(err) } - err = ValidateLayout(root, []string{refTag}, nil) + err = ValidateLayout(logger.Background(), root, []string{refTag}) if err != nil { t.Fatal(err) } diff --git a/logger/context.go b/logger/context.go new file mode 100644 index 0000000..4006d1d --- /dev/null +++ b/logger/context.go @@ -0,0 +1,31 @@ +// Copyright 2016 The Linux Foundation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package logger + +import ( + "context" +) + +// Context is a copy of Context from the stdlib context package. +type Context interface { + context.Context +} + +// Background returns a non-nil, empty Context. The background context +// provides a single key, "instance.id" that is globally unique to the +// process. +func Background() context.Context { + return context.Background() +} diff --git a/logger/logger.go b/logger/logger.go new file mode 100644 index 0000000..9dc33fe --- /dev/null +++ b/logger/logger.go @@ -0,0 +1,69 @@ +// Copyright 2016 The Linux Foundation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package logger + +import ( + // TODO(xiekeyang): The adaptation should be checked for openSUSE + // on non-x86_64 architectures. If encounting problem, the golang + // version should be updated accordingly. + "context" + "os" + + "github.com/Sirupsen/logrus" +) + +var ( + // G is an alias for GetLogger. + // + // We may want to define this locally to a package to get package tagged log + // messages. + G = GetLogger + + // LogEntry provides a public and standard logger instance. + LogEntry = logrus.NewEntry(logrus.StandardLogger()) +) + +type ( + loggerKey struct{} +) + +// WithLogger returns a new context with the provided logger. Use in +// combination with logger.WithField(s) for great effect. +func WithLogger(ctx context.Context, logger *logrus.Entry) context.Context { + return context.WithValue(ctx, loggerKey{}, logger) +} + +// GetLogger retrieves the current logger from the context. If no logger is +// available, the default logger is returned. +func GetLogger(ctx context.Context) *logrus.Entry { + logger := ctx.Value(loggerKey{}) + + if logger == nil { + return LogEntry + } + + return logger.(*logrus.Entry) +} + +// EnableDebugMode enables a selectable debug mode. +func EnableDebugMode(debug bool) { + if debug { + LogEntry.Logger.Level = logrus.DebugLevel + } +} + +func init() { + LogEntry.Logger.Out = os.Stderr +} diff --git a/man/oci-image-tool.1.md b/man/oci-image-tool.1.md index f8d446e..f7e6f24 100644 --- a/man/oci-image-tool.1.md +++ b/man/oci-image-tool.1.md @@ -35,6 +35,8 @@ oci-image-tool is a collection of tools for working with the [OCI image specific Create an OCI runtime bundle See **oci-image-tool-create**(1) for full documentation on the **create** command. +During command run, the output stream will be sent to `OS stderr`. + # SEE ALSO **oci-image-tool-validate**(1), **oci-image-tool-unpack**(1), **oci-image-tool-create**(1)