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

error: Pull the RFC 2119 error representation into its own package #437

Merged
merged 1 commit into from
Aug 9, 2017
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 10 additions & 9 deletions cmd/runtimetest/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ import (
"github.com/urfave/cli"

"github.com/opencontainers/runtime-tools/cmd/runtimetest/mount"
ociErr "github.com/opencontainers/runtime-tools/validate"
rfc2119 "github.com/opencontainers/runtime-tools/error"
"github.com/opencontainers/runtime-tools/validate"
)

// PrGetNoNewPrivs isn't exposed in Golang so we define it ourselves copying the value from
Expand Down Expand Up @@ -322,7 +323,7 @@ func validateDefaultFS(spec *rspec.Spec) error {

mountInfos, err := mount.GetMounts()
if err != nil {
return ociErr.NewError(ociErr.DefaultFilesystems, err.Error())
validate.NewError(validate.DefaultFilesystems, err.Error(), spec.Version)
}

mountsMap := make(map[string]string)
Expand All @@ -332,7 +333,7 @@ func validateDefaultFS(spec *rspec.Spec) error {

for fs, fstype := range defaultFS {
if !(mountsMap[fs] == fstype) {
return ociErr.NewError(ociErr.DefaultFilesystems, fmt.Sprintf("%v SHOULD exist and expected type is %v", fs, fstype))
return validate.NewError(validate.DefaultFilesystems, fmt.Sprintf("%v SHOULD exist and expected type is %v", fs, fstype), spec.Version)
}
}

Expand Down Expand Up @@ -611,7 +612,7 @@ func validateMountsExist(spec *rspec.Spec) error {
return nil
}

func validate(context *cli.Context) error {
func run(context *cli.Context) error {
logLevelString := context.String("log-level")
logLevel, err := logrus.ParseLevel(logLevelString)
if err != nil {
Expand Down Expand Up @@ -701,17 +702,17 @@ func validate(context *cli.Context) error {
t.Header(0)

complianceLevelString := context.String("compliance-level")
complianceLevel, err := ociErr.ParseLevel(complianceLevelString)
complianceLevel, err := rfc2119.ParseLevel(complianceLevelString)
if err != nil {
complianceLevel = ociErr.ComplianceMust
complianceLevel = rfc2119.Must
logrus.Warningf("%s, using 'MUST' by default.", err.Error())
}
var validationErrors error
for _, v := range defaultValidations {
err := v.test(spec)
t.Ok(err == nil, v.description)
if err != nil {
if e, ok := err.(*ociErr.Error); ok && e.Level < complianceLevel {
if e, ok := err.(*rfc2119.Error); ok && e.Level < complianceLevel {
continue
}
validationErrors = multierror.Append(validationErrors, err)
Expand All @@ -723,7 +724,7 @@ func validate(context *cli.Context) error {
err := v.test(spec)
t.Ok(err == nil, v.description)
if err != nil {
if e, ok := err.(*ociErr.Error); ok && e.Level < complianceLevel {
if e, ok := err.(*rfc2119.Error); ok && e.Level < complianceLevel {
continue
}
validationErrors = multierror.Append(validationErrors, err)
Expand Down Expand Up @@ -759,7 +760,7 @@ func main() {
},
}

app.Action = validate
app.Action = run
if err := app.Run(os.Args); err != nil {
logrus.Fatal(err)
}
Expand Down
87 changes: 87 additions & 0 deletions error/error.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// Package error implements generic tooling for tracking RFC 2119
// violations and linking back to the appropriate specification section.
package error

import (
"fmt"
"strings"
)

// Level represents the OCI compliance levels
type Level int

const (
// MAY-level

// May represents 'MAY' in RFC 2119.
May Level = iota
// Optional represents 'OPTIONAL' in RFC 2119.
Optional

// SHOULD-level

// Should represents 'SHOULD' in RFC 2119.
Should
// ShouldNot represents 'SHOULD NOT' in RFC 2119.
ShouldNot
// Recommended represents 'RECOMMENDED' in RFC 2119.
Recommended
// NotRecommended represents 'NOT RECOMMENDED' in RFC 2119.
NotRecommended

// MUST-level

// Must represents 'MUST' in RFC 2119
Must
// MustNot represents 'MUST NOT' in RFC 2119.
MustNot
// Shall represents 'SHALL' in RFC 2119.
Shall
// ShallNot represents 'SHALL NOT' in RFC 2119.
ShallNot
// Required represents 'REQUIRED' in RFC 2119.
Required
)

// Error represents an error with compliance level and OCI reference.
type Error struct {
Level Level
Reference string
Err error
}

// ParseLevel takes a string level and returns the OCI compliance level constant.
func ParseLevel(level string) (Level, error) {
switch strings.ToUpper(level) {
case "MAY":
fallthrough
case "OPTIONAL":
return May, nil
case "SHOULD":
fallthrough
case "SHOULDNOT":
fallthrough
case "RECOMMENDED":
fallthrough
case "NOTRECOMMENDED":
return Should, nil
case "MUST":
fallthrough
case "MUSTNOT":
fallthrough
case "SHALL":
fallthrough
case "SHALLNOT":
fallthrough
case "REQUIRED":
return Must, nil
}

var l Level
return l, fmt.Errorf("%q is not a valid compliance level", level)
}

// Error returns the error message with OCI reference
func (err *Error) Error() string {
return fmt.Sprintf("%s\nRefer to: %s", err.Err.Error(), err.Reference)
}
120 changes: 31 additions & 89 deletions validate/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,108 +3,50 @@ package validate
import (
"errors"
"fmt"
"strings"

rspec "github.com/opencontainers/runtime-spec/specs-go"
rfc2119 "github.com/opencontainers/runtime-tools/error"
)

// ComplianceLevel represents the OCI compliance levels
type ComplianceLevel int
const referenceTemplate = "https://github.com/opencontainers/runtime-spec/blob/v%s/%s"

const (
// MAY-level

// ComplianceMay represents 'MAY' in RFC2119
ComplianceMay ComplianceLevel = iota
// ComplianceOptional represents 'OPTIONAL' in RFC2119
ComplianceOptional

// SHOULD-level

// ComplianceShould represents 'SHOULD' in RFC2119
ComplianceShould
// ComplianceShouldNot represents 'SHOULD NOT' in RFC2119
ComplianceShouldNot
// ComplianceRecommended represents 'RECOMMENDED' in RFC2119
ComplianceRecommended
// ComplianceNotRecommended represents 'NOT RECOMMENDED' in RFC2119
ComplianceNotRecommended

// MUST-level

// ComplianceMust represents 'MUST' in RFC2119
ComplianceMust
// ComplianceMustNot represents 'MUST NOT' in RFC2119
ComplianceMustNot
// ComplianceShall represents 'SHALL' in RFC2119
ComplianceShall
// ComplianceShallNot represents 'SHALL NOT' in RFC2119
ComplianceShallNot
// ComplianceRequired represents 'REQUIRED' in RFC2119
ComplianceRequired
)

// ErrorCode represents the compliance content
// ErrorCode represents the compliance content.
type ErrorCode int

const (
// DefaultFilesystems represents the error code of default filesystems test
// DefaultFilesystems represents the error code of default filesystems test.
DefaultFilesystems ErrorCode = iota
)

// Error represents an error with compliance level and OCI reference
type Error struct {
Level ComplianceLevel
Reference string
Err error
type errorTemplate struct {
Level rfc2119.Level
Reference func(version string) (reference string, err error)
}

const referencePrefix = "https://github.com/opencontainers/runtime-spec/blob"

var ociErrors = map[ErrorCode]Error{
DefaultFilesystems: Error{Level: ComplianceShould, Reference: "config-linux.md#default-filesystems"},
var ociErrors = map[ErrorCode]errorTemplate{
DefaultFilesystems: errorTemplate{
Level: rfc2119.Should,
Reference: func(version string) (reference string, err error) {
return fmt.Sprintf(referenceTemplate, version, "config-linux.md#default-filesystems"), nil
},
},
}

// ParseLevel takes a string level and returns the OCI compliance level constant
func ParseLevel(level string) (ComplianceLevel, error) {
switch strings.ToUpper(level) {
case "MAY":
fallthrough
case "OPTIONAL":
return ComplianceMay, nil
case "SHOULD":
fallthrough
case "SHOULDNOT":
fallthrough
case "RECOMMENDED":
fallthrough
case "NOTRECOMMENDED":
return ComplianceShould, nil
case "MUST":
fallthrough
case "MUSTNOT":
fallthrough
case "SHALL":
fallthrough
case "SHALLNOT":
fallthrough
case "REQUIRED":
return ComplianceMust, nil
// NewError creates an Error referencing a spec violation. The error
// can be cast to a *runtime-tools.error.Error for extracting
// structured information about the level of the violation and a
// reference to the violated spec condition.
//
// A version string (for the version of the spec that was violated)
// must be set to get a working URL.
func NewError(code ErrorCode, msg string, version string) (err error) {
template := ociErrors[code]
reference, err := template.Reference(version)
if err != nil {
return err
}
return &rfc2119.Error{
Level: template.Level,
Reference: reference,
Err: errors.New(msg),
}

var l ComplianceLevel
return l, fmt.Errorf("%q is not a valid compliance level", level)
}

// NewError creates an Error by ErrorCode and message
func NewError(code ErrorCode, msg string) error {
err := ociErrors[code]
err.Err = errors.New(msg)

return &err
}

// Error returns the error message with OCI reference
func (oci *Error) Error() string {
return fmt.Sprintf("%s\nRefer to: %s/v%s/%s", oci.Err.Error(), referencePrefix, rspec.Version, oci.Reference)
}