diff --git a/cmd/oci-runtime-tool/main.go b/cmd/oci-runtime-tool/main.go index 2268a0ac5..c4f4f589b 100644 --- a/cmd/oci-runtime-tool/main.go +++ b/cmd/oci-runtime-tool/main.go @@ -22,6 +22,11 @@ func main() { } app.Usage = "OCI (Open Container Initiative) runtime tools" app.Flags = []cli.Flag{ + cli.StringFlag{ + Name: "compliance-level", + Value: "must", + Usage: "compliance level (may, should, or must).", + }, cli.BoolFlag{ Name: "host-specific", Usage: "generate host-specific configs or do host-specific validations", diff --git a/cmd/oci-runtime-tool/validate.go b/cmd/oci-runtime-tool/validate.go index 58cdeb921..f506aa28b 100644 --- a/cmd/oci-runtime-tool/validate.go +++ b/cmd/oci-runtime-tool/validate.go @@ -3,7 +3,10 @@ package main import ( "fmt" + rfc2119 "github.com/opencontainers/runtime-tools/error" + "github.com/opencontainers/runtime-tools/specerror" "github.com/opencontainers/runtime-tools/validate" + "github.com/sirupsen/logrus" "github.com/urfave/cli" ) @@ -19,6 +22,12 @@ var bundleValidateCommand = cli.Command{ Before: before, Action: func(context *cli.Context) error { hostSpecific := context.GlobalBool("host-specific") + complianceLevelString := context.GlobalString("compliance-level") + complianceLevel, err := rfc2119.ParseLevel(complianceLevelString) + if err != nil { + complianceLevel = rfc2119.Must + logrus.Warningf("%s, using 'MUST' by default.", err.Error()) + } inputPath := context.String("path") platform := context.String("platform") v, err := validate.NewValidatorFromPath(inputPath, hostSpecific, platform) @@ -27,8 +36,14 @@ var bundleValidateCommand = cli.Command{ } if err := v.CheckAll(); err != nil { - return err - + levelErrors, err := specerror.SplitLevel(err, complianceLevel) + if err != nil { + return err + } + for _, e := range levelErrors.Warnings { + logrus.Warn(e) + } + return levelErrors.Error } fmt.Println("Bundle validation succeeded.") return nil diff --git a/man/oci-runtime-tool.1.md b/man/oci-runtime-tool.1.md index 880b587ff..5a66fe33b 100644 --- a/man/oci-runtime-tool.1.md +++ b/man/oci-runtime-tool.1.md @@ -33,7 +33,8 @@ oci-runtime-tool is a collection of tools for working with the [OCI runtime spec Log level (panic, fatal, error, warn, info, or debug) (default: "error"). **--compliance-level**=LEVEL - Compliance level (may, should or must) (default: "must"). + Compliance level (`may`, `should`, or `must`) (default: `must`). + For example, a SHOULD-level violation is fatal if `--compliance-level` is `may` or `should` but non-fatal if `--compliance-level` is `must`. **-v**, **--version** Print version information. diff --git a/specerror/error.go b/specerror/error.go index c75bb6b14..069cbc1e6 100644 --- a/specerror/error.go +++ b/specerror/error.go @@ -69,6 +69,16 @@ type Error struct { Code Code } +// LevelErrors represents Errors filtered into fatal and warnings. +type LevelErrors struct { + // Warnings holds Errors that were below a compliance-level threshold. + Warnings []*Error + + // Error holds errors that were at or above a compliance-level + // threshold, as well as errors that are not Errors. + Error *multierror.Error +} + var ( containerFormatRef = func(version string) (reference string, err error) { return fmt.Sprintf(referenceTemplate, version, "bundle.md#container-format"), nil @@ -168,3 +178,23 @@ func FindError(err error, code Code) Code { } return NonRFCError } + +// SplitLevel removes RFC 2119 errors with a level less than 'level' +// from the source error. If the source error is not a multierror, it +// is returned unchanged. +func SplitLevel(errIn error, level rfc2119.Level) (levelErrors LevelErrors, errOut error) { + merr, ok := errIn.(*multierror.Error) + if !ok { + return levelErrors, errIn + } + for _, err := range merr.Errors { + e, ok := err.(*Error) + if ok && e.Err.Level < level { + fmt.Println(e) + levelErrors.Warnings = append(levelErrors.Warnings, e) + continue + } + levelErrors.Error = multierror.Append(levelErrors.Error, err) + } + return levelErrors, nil +}