// Package specerror implements runtime-spec-specific tooling for // tracking RFC 2119 violations. package specerror import ( "fmt" "github.com/hashicorp/go-multierror" rfc2119 "github.com/opencontainers/runtime-tools/error" ) const referenceTemplate = "https://github.com/opencontainers/runtime-spec/blob/v%s/%s" // Code represents the spec violation, enumerating both // configuration violations and runtime violations. type Code int const ( // NonError represents that an input is not an error NonError Code = iota // NonRFCError represents that an error is not a rfc2119 error NonRFCError // ConfigFileExistence represents the error code of 'config.json' existence test ConfigFileExistence // ArtifactsInSingleDir represents the error code of artifacts place test ArtifactsInSingleDir // SpecVersion represents the error code of specfication version test SpecVersion // RootOnNonHyperV represents the error code of root setting test on non hyper-v containers RootOnNonHyperV // RootOnHyperV represents the error code of root setting test on hyper-v containers RootOnHyperV // PathFormatOnWindows represents the error code of the path format test on Window PathFormatOnWindows // PathName represents the error code of the path name test PathName // PathExistence represents the error code of the path existence test PathExistence // ReadonlyFilesystem represents the error code of readonly test ReadonlyFilesystem // ReadonlyOnWindows represents the error code of readonly setting test on Windows ReadonlyOnWindows // DefaultFilesystems represents the error code of default filesystems test DefaultFilesystems // CreateWithID represents the error code of 'create' lifecyle test with 'id' provided CreateWithID // CreateWithUniqueID represents the error code of 'create' lifecyle test with unique 'id' provided CreateWithUniqueID // CreateNewContainer represents the error code 'create' lifecyle test that creates new container CreateNewContainer ) type errorTemplate struct { Level rfc2119.Level Reference func(version string) (reference string, err error) } // Error represents a runtime-spec violation. type Error struct { // Err holds the RFC 2119 violation. Err rfc2119.Error // Code is a matchable holds a Code Code Code } var ( containerFormatRef = func(version string) (reference string, err error) { return fmt.Sprintf(referenceTemplate, version, "bundle.md#container-format"), nil } specVersionRef = func(version string) (reference string, err error) { return fmt.Sprintf(referenceTemplate, version, "config.md#specification-version"), nil } rootRef = func(version string) (reference string, err error) { return fmt.Sprintf(referenceTemplate, version, "config.md#root"), nil } defaultFSRef = func(version string) (reference string, err error) { return fmt.Sprintf(referenceTemplate, version, "config-linux.md#default-filesystems"), nil } runtimeCreateRef = func(version string) (reference string, err error) { return fmt.Sprintf(referenceTemplate, version, "runtime.md#create"), nil } ) var ociErrors = map[Code]errorTemplate{ // Bundle.md // Container Format ConfigFileExistence: {Level: rfc2119.Must, Reference: containerFormatRef}, ArtifactsInSingleDir: {Level: rfc2119.Must, Reference: containerFormatRef}, // Config.md // Specification Version SpecVersion: {Level: rfc2119.Must, Reference: specVersionRef}, // Root RootOnNonHyperV: {Level: rfc2119.Required, Reference: rootRef}, RootOnHyperV: {Level: rfc2119.Must, Reference: rootRef}, // TODO: add tests for 'PathFormatOnWindows' PathFormatOnWindows: {Level: rfc2119.Must, Reference: rootRef}, PathName: {Level: rfc2119.Should, Reference: rootRef}, PathExistence: {Level: rfc2119.Must, Reference: rootRef}, ReadonlyFilesystem: {Level: rfc2119.Must, Reference: rootRef}, ReadonlyOnWindows: {Level: rfc2119.Must, Reference: rootRef}, // Config-Linux.md // Default Filesystems DefaultFilesystems: {Level: rfc2119.Should, Reference: defaultFSRef}, // Runtime.md // Create CreateWithID: {Level: rfc2119.Must, Reference: runtimeCreateRef}, CreateWithUniqueID: {Level: rfc2119.Must, Reference: runtimeCreateRef}, CreateNewContainer: {Level: rfc2119.Must, Reference: runtimeCreateRef}, } // Error returns the error message with specification reference. func (err *Error) Error() string { return err.Err.Error() } // NewError creates an Error referencing a spec violation. The error // can be cast to an *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 Code, err error, version string) error { template := ociErrors[code] reference, err2 := template.Reference(version) if err2 != nil { return err2 } return &Error{ Err: rfc2119.Error{ Level: template.Level, Reference: reference, Err: err, }, Code: code, } } // FindError finds an error from a source error (multiple error) and // returns the error code if found. // If the source error is nil or empty, return NonError. // If the source error is not a multiple error, return NonRFCError. func FindError(err error, code Code) Code { if err == nil { return NonError } if merr, ok := err.(*multierror.Error); ok { if merr.ErrorOrNil() == nil { return NonError } for _, e := range merr.Errors { if rfcErr, ok := e.(*Error); ok { if rfcErr.Code == code { return code } } } } return NonRFCError }